From 809418b9dabef8914f4f56badd12ae7a0d34f19c Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Fri, 1 Sep 2023 04:23:37 +0300 Subject: [PATCH 01/26] [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 533a8a9c..645e75e8 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 93c7043e..ee024b82 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 7e011a95..327163d5 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 b6fcc70f..bd804fc8 100644 --- a/fbt_options.py +++ b/fbt_options.py @@ -72,6 +72,7 @@ FIRMWARE_APPS = { "unit_tests": [ "basic_services", "updater_app", + "radio_device_cc1101_ext", "unit_tests", ], } diff --git a/scripts/fwflash.py b/scripts/fwflash.py index c119aaf8..6948bd7f 100755 --- a/scripts/fwflash.py +++ b/scripts/fwflash.py @@ -10,6 +10,7 @@ from abc import ABC, abstractmethod 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 @@ class OpenOCDProgrammer(Programmer): 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 @@ class OpenOCDProgrammer(Programmer): 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 02/26] [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 47b2cc0b..42ab073b 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 03/26] 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 | 95 ++++++++++--------- .../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, 137 insertions(+), 98 deletions(-) diff --git a/applications/debug/unit_tests/furi/furi_memmgr_test.c b/applications/debug/unit_tests/furi/furi_memmgr_test.c index 05d967a9..a28632cf 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 02f7d95b..746abe03 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 0627652a..547883e9 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 b96f89db..0bdc999b 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 83f0edbe..0119abc2 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 57c0fddb..0f300c16 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 90f48ac9..444e1a27 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 e39a84ea..ad3a5114 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 63da03e0..efd39977 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}, + // 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 - {.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}, + // 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 7d2caab3..fed7802a 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 746df71c..2c30612b 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 e0539c34..f6e27f53 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 4d52960d..d519484d 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 6e585c51..6ca6f9df 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 4a9feed9..a3a88603 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 04/26] 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 bbaf9603..dcf39c3a 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 658bb208..00000000 --- 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 05/26] 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 011747d5..a72d4aac 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 06/26] 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 bd85858a..07b21971 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 00000000..705888a4 --- /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 b85409ec..62e02b8a 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 02016666..fedc4b87 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 cb5b5027..24964a30 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 5645f609..82cb0468 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 07/26] [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 b3ed4417..3e9665ad 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 08/26] 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 ad5b233b..67873690 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 50927921..077b1480 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 7bd49df8..e6236dc3 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 cbb09a52..5f762d12 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 aa7510a9..2bef08df 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 72b06193..d94a5f86 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 771f2f60..3a38e213 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 56d98a8c..4ac793b5 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 a7b97aac..b18e1763 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 58e4b042..bbcc4325 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 86eddfe8..394dda89 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 9ff61837..c03efe5e 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 6e35dcff..37edc5d3 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 0f300c16..6723a777 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 dd3c0dc6..d19b4747 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 a61679bc..a26db882 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 8baec5a8..6ad88d9b 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 186d22f0..c457b690 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 2c92b5bf..c5c8b433 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 20df4f89..76de0b61 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 c9d2b301..4a8ae1d5 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 cccf70ba..93f94cc7 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 761755d2..04fd8567 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 00000000..36366485 --- /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 00000000..bc17d54c --- /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 64e712c7..00000000 --- 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 358ea685..00000000 --- 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 09/26] [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 dcf39c3a..387ac2de 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 10/26] 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 e32a5748..1900f204 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 0a9599d0..4fb249c6 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 51f5a0d1..1f119315 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 f2d0272f..2c2cc8a8 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 d6104c13..7a297b01 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 acc5903a..7e71e0d2 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 12eba01c..3525b39e 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 f27b0a08..2b137e1d 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 f194178a..11c74c01 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 d073b4c8..cc713135 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 67873690..0e4c621b 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 bbcc4325..f2ab6577 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 fe97c8a0..f7d6b3a1 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 88ac129c..b074cdc9 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 0ed5ebb2..ce70212a 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 8489ed91..34360f3d 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 9b625c1f..d7d7e6cf 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 1816bf92..a6229af8 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 dccf2959..1abc8ed0 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 585ded41..2ba58f9c 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 63e44c9d..41da6c3f 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 f1064034..4323296c 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 9e13bf83..01bc2038 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 70cb7b92..00126af6 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 35b3ee25..080ac4fa 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 07ddbcf2..18d1f714 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 9f41061b..0e667024 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 0c15116b..1b9970f9 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 486f0760..33bb9552 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; + 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 { + 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); - 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_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 d6dc13e3..c5603199 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 a26db882..52f9a4d9 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 6ad88d9b..2222d170 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 6c17d6ad..bdfe0b0a 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 6db5fb5f..ef22ee9a 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 083c3ca1..ec3fc22e 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 bea7c123..7ac4c655 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 101471dc..e2a7b83f 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 27c6f247..ca29770f 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 aa962a47..344c2afa 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 8a266777..32e25325 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 68937d16..dbff2f21 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 41a0609d..284c717f 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 e8bb8705..16c715f8 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 de13472a..7fce9444 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 9509280c..03f4c5bd 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 14b2e010..40ae05ba 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 6fe61581..1d79d220 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 be0877fb..0dd0d2b0 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 d9dd6840..cdee7cd9 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 831f824d..302b7859 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 7fd8d66d..fcf28220 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 4c5c68cc..fc490e9d 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 90c0fb0e..e96e6566 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 1d134f7b..9b63271b 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 cd027b99..0b2a102c 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 af81d9f9..f60e07fb 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 4ed9766e..2416a9d0 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 4a8ae1d5..95dff309 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 783351c6..644b2fba 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 12d2fac7..374c407b 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 bfb36b76..0b9755b8 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 11/26] 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 | 128 +++++++++++++++++----------- .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, 167 insertions(+), 88 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 07b21971..510542c3 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 }} - run: | - tar czpf "artifacts/flipper-z-any-scripts-${SUFFIX}.tgz" scripts - - - name: 'Build the firmware' - 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" + - name: 'Check for uncommitted changes' run: | git diff --exit-code - - name: 'Bundle core2 firmware' - if: ${{ !github.event.pull_request.head.repo.fork }} + - name: 'Copy build output' run: | + set -e + 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: | + tar czpf "artifacts/flipper-z-any-scripts-${SUFFIX}.tgz" scripts cp build/core2_firmware.tgz "artifacts/flipper-z-any-core2_firmware-${SUFFIX}.tgz" - - name: 'Copy map analyser files' + - name: 'Upload artifacts to update server' if: ${{ !github.event.pull_request.head.repo.fork }} + run: | + 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 & 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 705888a4..37ebc0ca 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 a91b7cdf..2a82db37 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 b8b8a8d6..7bb8e40b 100644 --- a/scripts/fbt/appmanifest.py +++ b/scripts/fbt/appmanifest.py @@ -193,8 +193,19 @@ class AppManager: 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 @@ class AppBuildset: 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 @@ class AppBuildset: 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 074cac6b..e3d93a31 100644 --- a/scripts/fbt/sdk/cache.py +++ b/scripts/fbt/sdk/cache.py @@ -255,3 +255,18 @@ class SdkCache: 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 cbb3bf72..9a071805 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 1630135c..98e6b638 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 97b7ac09..f9227ed3 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 12/26] [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 37ecfde4..891435c9 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 13/26] [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 0e4c621b..76091809 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 14/26] 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 510542c3..3736816a 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 37ebc0ca..7556c15a 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 15/26] 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 891435c9..37ecfde4 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 00000000..a1fb314b --- /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 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 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 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 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 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 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 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 +#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 00000000..49d8b4e0 --- /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 00000000..71277136 --- /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 00000000..543363bf --- /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 00000000..84bfed4c --- /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 00000000..468529d5 --- /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 00000000..4aa51dc1 --- /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 00000000..30a9d9d0 --- /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 00000000..d9fb2fd8 --- /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 00000000..d85affc4 --- /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 00000000..d72847ba --- /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 00000000..15547eb2 --- /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 00000000..0813b435 --- /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 00000000..e1f9f4be --- /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 00000000..b2efc369 --- /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 00000000..9a99ebac --- /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 00000000..6852cb21 --- /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 a640b9a5..86b11e8a 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 52f9a4d9..2803af5c 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 efd39977..11893587 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 2222d170..7a38df49 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 d519484d..fe4640d5 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 fea92c26..addbd5e4 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 d09ec900..5b87b811 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_compatible( +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_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 fbcf2973..d56a8a7e 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 20baae82..a119cf53 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 0142e3e2..9463d131 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 fa689b01..3175a1b0 100644 --- a/scripts/flipper/utils/fff.py +++ b/scripts/flipper/utils/fff.py @@ -67,7 +67,10 @@ class FlipperFormatFile: 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 00000000..9fa44a90 --- /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 b7a60161..3480f275 100755 --- a/scripts/ob.py +++ b/scripts/ob.py @@ -22,7 +22,8 @@ class Main(App): 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 16/26] 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 | 38 +- 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 | 82 +- 13 files changed, 1208 insertions(+), 1225 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 62e02b8a..d24422b7 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 88064039..842334d5 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 080ac4fa..4630d99e 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); + FuriHalSdInfo info; + FuriStatus status = furi_hal_sd_info(&info); - 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; + 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 e7f2d8f2..da00cbdf 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 2803af5c..0c206493 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 7a38df49..08198515 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 d420524d..00000000 --- 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 954c78c4..00000000 --- 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 6663d119..85e5cad4 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; + FuriHalSdInfo sd_info; - 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); + 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 7b3f2bb9..db636fbb 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 1b0de562..619f6f89 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 52030541..378e74a5 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 e1c08a35..645403b7 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 - */ -void hal_sd_detect_init(void); +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 */ -/** Set SD card detect pin to low - */ -void hal_sd_detect_set_low(void); + 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; -/** Get SD card status - * - * @return true if SD card present, false if SD card not present +/** + * @brief Init SD card presence detection */ -bool hal_sd_detect(void); +void furi_hal_sd_presence_init(); -/** Pointer to currently used SPI Handle */ -extern FuriHalSpiBusHandle* furi_hal_sd_spi_handle; +/** + * @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 + */ +uint8_t furi_hal_sd_max_mount_retry_count(); + +/** + * @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 + */ +FuriStatus furi_hal_sd_read_blocks(uint32_t* buff, uint32_t sector, uint32_t count); + +/** + * @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 17/26] 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 00000000..05ed20b0 --- /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 3736816a..810b70b0 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 18/26] 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 6852cb21..bd7f1ce1 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 19/26] [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 2a82db37..65782270 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 20/26] [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 747ed732..e3e06695 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 21/26] 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 810b70b0..12d22f35 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 22/26] 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 05ed20b0..2428c32d 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 23/26] 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 2428c32d..5de5d3d8 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 12d22f35..84e89059 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 24/26] 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 5de5d3d8..269185d5 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 84e89059..7a718883 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 25/26] 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 17769832..a8884105 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 26/26] 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 4fb249c6..c7083162 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 bd724f0b..ac71b5f6 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;