diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d37622f17fd..0c161866542 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -161,7 +161,7 @@ jobs: comment-id: ${{ steps.fc.outputs.comment-id }} issue-number: ${{ github.event.pull_request.number }} body: | - [Click here](https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.artifacts-path}}/flipper-z-${{steps.names.outputs.default-target}}-full-${{steps.names.outputs.suffix}}.dfu) for the DFU file to flash the `${{steps.names.outputs.short-hash}}` version of this branch with the [`Install from file` option in qFlipper](https://docs.flipperzero.one/basics/firmware-update). + [Install with web updater](https://my.flipp.dev/?url=https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.artifacts-path}}/flipper-z-${{steps.names.outputs.default-target}}-update-${{steps.names.outputs.suffix}}.tgz&channel=${{steps.names.outputs.artifacts-path}}&version=${{steps.names.outputs.short-hash}}) edit-mode: replace compact: diff --git a/Makefile b/Makefile index 5304cfa5465..8834cba6ceb 100644 --- a/Makefile +++ b/Makefile @@ -102,7 +102,8 @@ updater_package: firmware_all updater assets_manifest --bundlever "$(VERSION_STRING)" \ --radio $(COPRO_STACK_BIN_PATH) \ --radiotype $(COPRO_STACK_TYPE) \ - --obdata $(PROJECT_ROOT)/scripts/ob.data + $(COPRO_DISCLAIMER) \ + --obdata $(PROJECT_ROOT)/scripts/$(COPRO_OB_DATA) .PHONY: assets_manifest assets_manifest: diff --git a/applications/archive/archive.c b/applications/archive/archive.c index 5a38700b7fe..99b9bb02faa 100644 --- a/applications/archive/archive.c +++ b/applications/archive/archive.c @@ -1,4 +1,5 @@ #include "archive_i.h" +#include "m-string.h" bool archive_custom_event_callback(void* context, uint32_t event) { furi_assert(context); @@ -17,6 +18,7 @@ ArchiveApp* archive_alloc() { archive->gui = furi_record_open("gui"); archive->text_input = text_input_alloc(); + string_init(archive->fav_move_str); archive->view_dispatcher = view_dispatcher_alloc(); archive->scene_manager = scene_manager_alloc(&archive_scene_handlers, archive); @@ -56,6 +58,7 @@ void archive_free(ArchiveApp* archive) { view_dispatcher_free(archive->view_dispatcher); scene_manager_free(archive->scene_manager); browser_free(archive->browser); + string_clear(archive->fav_move_str); text_input_free(archive->text_input); diff --git a/applications/archive/archive_i.h b/applications/archive/archive_i.h index 0822a801d7a..865e837dcbc 100644 --- a/applications/archive/archive_i.h +++ b/applications/archive/archive_i.h @@ -28,6 +28,7 @@ struct ArchiveApp { TextInput* text_input; Widget* widget; FuriPubSubSubscription* loader_stop_subscription; + string_t fav_move_str; char text_store[MAX_NAME_LEN]; char file_extension[MAX_EXT_LEN + 1]; }; diff --git a/applications/archive/helpers/archive_apps.c b/applications/archive/helpers/archive_apps.c index f5edbbb32f5..3393993d498 100644 --- a/applications/archive/helpers/archive_apps.c +++ b/applications/archive/helpers/archive_apps.c @@ -7,8 +7,14 @@ static const char* known_apps[] = { }; ArchiveAppTypeEnum archive_get_app_type(const char* path) { + const char* app_name = strchr(path, ':'); + if(app_name == NULL) { + return ArchiveAppTypeUnknown; + } + app_name++; + for(size_t i = 0; i < COUNT_OF(known_apps); i++) { - if(strncmp(path, known_apps[i], strlen(known_apps[i])) == 0) { + if(strncmp(app_name, known_apps[i], strlen(known_apps[i])) == 0) { return i; } } diff --git a/applications/archive/helpers/archive_browser.c b/applications/archive/helpers/archive_browser.c index cb3d3ca83e2..b2f39c0778d 100644 --- a/applications/archive/helpers/archive_browser.c +++ b/applications/archive/helpers/archive_browser.c @@ -1,8 +1,91 @@ +#include "archive/views/archive_browser_view.h" #include "archive_files.h" #include "archive_apps.h" #include "archive_browser.h" +#include "furi/common_defines.h" +#include "furi/log.h" +#include "gui/modules/file_browser_worker.h" +#include "m-string.h" #include +static void + archive_folder_open_cb(void* context, uint32_t item_cnt, int32_t file_idx, bool is_root) { + furi_assert(context); + ArchiveBrowserView* browser = (ArchiveBrowserView*)context; + + int32_t load_offset = 0; + browser->is_root = is_root; + + if((item_cnt == 0) && (archive_is_home(browser))) { + archive_switch_tab(browser, browser->last_tab_switch_dir); + } else if(!string_start_with_str_p(browser->path, "/app:")) { + with_view_model( + browser->view, (ArchiveBrowserViewModel * model) { + files_array_reset(model->files); + model->item_cnt = item_cnt; + model->item_idx = (file_idx > 0) ? file_idx : 0; + load_offset = + CLAMP(model->item_idx - FILE_LIST_BUF_LEN / 2, (int32_t)model->item_cnt, 0); + model->array_offset = 0; + model->list_offset = 0; + model->list_loading = true; + model->folder_loading = false; + return false; + }); + archive_update_offset(browser); + + file_browser_worker_load(browser->worker, load_offset, FILE_LIST_BUF_LEN); + } +} + +static void archive_list_load_cb(void* context, uint32_t list_load_offset) { + furi_assert(context); + ArchiveBrowserView* browser = (ArchiveBrowserView*)context; + + with_view_model( + browser->view, (ArchiveBrowserViewModel * model) { + files_array_reset(model->files); + model->array_offset = list_load_offset; + return false; + }); +} + +static void archive_list_item_cb(void* context, string_t item_path, bool is_folder, bool is_last) { + furi_assert(context); + ArchiveBrowserView* browser = (ArchiveBrowserView*)context; + + if(!is_last) { + archive_add_file_item(browser, is_folder, string_get_cstr(item_path)); + } else { + with_view_model( + browser->view, (ArchiveBrowserViewModel * model) { + model->list_loading = false; + return true; + }); + } +} + +static void archive_long_load_cb(void* context) { + furi_assert(context); + ArchiveBrowserView* browser = (ArchiveBrowserView*)context; + + with_view_model( + browser->view, (ArchiveBrowserViewModel * model) { + model->folder_loading = true; + return true; + }); +} + +void archive_file_browser_set_callbacks(ArchiveBrowserView* browser) { + furi_assert(browser); + + file_browser_worker_set_callback_context(browser->worker, browser); + file_browser_worker_set_folder_callback(browser->worker, archive_folder_open_cb); + file_browser_worker_set_list_callback(browser->worker, archive_list_load_cb); + file_browser_worker_set_item_callback(browser->worker, archive_list_item_cb); + file_browser_worker_set_long_load_callback(browser->worker, archive_long_load_cb); +} + bool archive_is_item_in_array(ArchiveBrowserViewModel* model, uint32_t idx) { size_t array_size = files_array_size(model->files); @@ -39,9 +122,9 @@ void archive_update_focus(ArchiveBrowserView* browser, const char* target) { furi_assert(browser); furi_assert(target); - archive_get_filenames(browser, string_get_cstr(browser->path)); + archive_get_items(browser, string_get_cstr(browser->path)); - if(!archive_file_get_array_size(browser) && !archive_get_depth(browser)) { + if(!archive_file_get_array_size(browser) && archive_is_home(browser)) { archive_switch_tab(browser, TAB_RIGHT); } else { with_view_model( @@ -49,7 +132,7 @@ void archive_update_focus(ArchiveBrowserView* browser, const char* target) { uint16_t idx = 0; while(idx < files_array_size(model->files)) { ArchiveFile_t* current = files_array_get(model->files, idx); - if(!string_search(current->name, target)) { + if(!string_search(current->path, target)) { model->item_idx = idx + model->array_offset; break; } @@ -102,7 +185,7 @@ void archive_file_array_rm_selected(ArchiveBrowserView* browser) { return false; }); - if((items_cnt == 0) && (archive_get_depth(browser) == 0)) { + if((items_cnt == 0) && (archive_is_home(browser))) { archive_switch_tab(browser, TAB_RIGHT); } @@ -145,7 +228,7 @@ void archive_file_array_rm_all(ArchiveBrowserView* browser) { }); } -bool archive_file_array_load(ArchiveBrowserView* browser, int8_t dir) { +void archive_file_array_load(ArchiveBrowserView* browser, int8_t dir) { furi_assert(browser); int32_t offset_new = 0; @@ -160,22 +243,17 @@ bool archive_file_array_load(ArchiveBrowserView* browser, int8_t dir) { } else { offset_new = model->item_idx - FILE_LIST_BUF_LEN / 4 * 1; } - offset_new = CLAMP(offset_new, (int32_t)model->item_cnt - FILE_LIST_BUF_LEN, 0); + if(offset_new > 0) { + offset_new = + CLAMP(offset_new, (int32_t)model->item_cnt - FILE_LIST_BUF_LEN, 0); + } else { + offset_new = 0; + } } return false; }); - bool res = archive_dir_read_items( - browser, string_get_cstr(browser->path), offset_new, FILE_LIST_BUF_LEN); - - with_view_model( - browser->view, (ArchiveBrowserViewModel * model) { - model->array_offset = offset_new; - model->list_loading = false; - return true; - }); - - return res; + file_browser_worker_load(browser->worker, offset_new, FILE_LIST_BUF_LEN); } ArchiveFile_t* archive_get_current_file(ArchiveBrowserView* browser) { @@ -218,26 +296,20 @@ ArchiveTabEnum archive_get_tab(ArchiveBrowserView* browser) { return tab_id; } -uint8_t archive_get_depth(ArchiveBrowserView* browser) { +bool archive_is_home(ArchiveBrowserView* browser) { furi_assert(browser); - uint8_t depth; - with_view_model( - browser->view, (ArchiveBrowserViewModel * model) { - depth = idx_last_array_size(model->idx_last); - return false; - }); - - return depth; -} + if(browser->is_root) { + return true; + } -const char* archive_get_path(ArchiveBrowserView* browser) { - return string_get_cstr(browser->path); + const char* default_path = archive_get_default_path(archive_get_tab(browser)); + return (string_cmp_str(browser->path, default_path) == 0); } const char* archive_get_name(ArchiveBrowserView* browser) { ArchiveFile_t* selected = archive_get_current_file(browser); - return string_get_cstr(selected->name); + return string_get_cstr(selected->path); } void archive_set_tab(ArchiveBrowserView* browser, ArchiveTabEnum tab) { @@ -249,37 +321,15 @@ void archive_set_tab(ArchiveBrowserView* browser, ArchiveTabEnum tab) { return false; }); } -void archive_set_last_tab(ArchiveBrowserView* browser, ArchiveTabEnum tab) { - UNUSED(tab); // FIXME? - furi_assert(browser); - - with_view_model( - browser->view, (ArchiveBrowserViewModel * model) { - model->last_tab = model->tab_idx; - return false; - }); -} void archive_add_app_item(ArchiveBrowserView* browser, const char* name) { furi_assert(browser); furi_assert(name); ArchiveFile_t item; - - string_t full_name; - - string_init_set(full_name, browser->path); - string_cat_printf(full_name, "/%s", name); - - char* app_name = strchr(string_get_cstr(full_name), ':'); - if(app_name == NULL) { - string_clear(full_name); - return; - } - ArchiveFile_t_init(&item); - string_init_set_str(item.name, name); - archive_set_file_type(&item, NULL, app_name + 1, true); + string_set_str(item.path, name); + archive_set_file_type(&item, name, false, true); with_view_model( browser->view, (ArchiveBrowserViewModel * model) { @@ -288,29 +338,24 @@ void archive_add_app_item(ArchiveBrowserView* browser, const char* name) { return false; }); ArchiveFile_t_clear(&item); - string_clear(full_name); } -void archive_add_file_item(ArchiveBrowserView* browser, FileInfo* file_info, const char* name) { +void archive_add_file_item(ArchiveBrowserView* browser, bool is_folder, const char* name) { furi_assert(browser); - furi_assert(file_info); furi_assert(name); ArchiveFile_t item; - if(archive_filter_by_extension( - file_info, archive_get_tab_ext(archive_get_tab(browser)), name)) { - ArchiveFile_t_init(&item); - string_init_set_str(item.name, name); - archive_set_file_type(&item, file_info, archive_get_path(browser), false); + ArchiveFile_t_init(&item); + string_init_set_str(item.path, name); + archive_set_file_type(&item, string_get_cstr(browser->path), is_folder, false); - with_view_model( - browser->view, (ArchiveBrowserViewModel * model) { - files_array_push_back(model->files, item); - return false; - }); - ArchiveFile_t_clear(&item); - } + with_view_model( + browser->view, (ArchiveBrowserViewModel * model) { + files_array_push_back(model->files, item); + return false; + }); + ArchiveFile_t_clear(&item); } void archive_show_file_menu(ArchiveBrowserView* browser, bool show) { @@ -323,7 +368,7 @@ void archive_show_file_menu(ArchiveBrowserView* browser, bool show) { model->menu_idx = 0; ArchiveFile_t* selected = files_array_get(model->files, model->item_idx - model->array_offset); - selected->fav = archive_is_favorite("%s", string_get_cstr(selected->name)); + selected->fav = archive_is_favorite("%s", string_get_cstr(selected->path)); } } else { model->menu = false; @@ -344,36 +389,40 @@ void archive_favorites_move_mode(ArchiveBrowserView* browser, bool active) { }); } -void archive_switch_dir(ArchiveBrowserView* browser, const char* path) { - furi_assert(browser); - furi_assert(path); - - string_set(browser->path, path); - archive_get_filenames(browser, string_get_cstr(browser->path)); - archive_update_offset(browser); -} - void archive_switch_tab(ArchiveBrowserView* browser, InputKey key) { furi_assert(browser); ArchiveTabEnum tab = archive_get_tab(browser); + browser->last_tab_switch_dir = key; + if(key == InputKeyLeft) { tab = ((tab - 1) + ArchiveTabTotal) % ArchiveTabTotal; - } else if(key == InputKeyRight) { + } else { tab = (tab + 1) % ArchiveTabTotal; } + browser->is_root = true; archive_set_tab(browser, tab); - const char* path = archive_get_default_path(tab); + string_set_str(browser->path, archive_get_default_path(tab)); bool tab_empty = true; if(tab == ArchiveTabFavorites) { - if(archive_favorites_count(browser) > 0) tab_empty = false; - } else if(strncmp(path, "/app:", 5) == 0) { - if(archive_app_is_available(browser, path)) tab_empty = false; + if(archive_favorites_count(browser) > 0) { + tab_empty = false; + } + } else if(string_start_with_str_p(browser->path, "/app:")) { + char* app_name = strchr(string_get_cstr(browser->path), ':'); + if(app_name != NULL) { + if(archive_app_is_available(browser, string_get_cstr(browser->path))) { + tab_empty = false; + } + } } else { - uint32_t files_cnt = archive_dir_count_items(browser, archive_get_default_path(tab)); - if(files_cnt > 0) tab_empty = false; + ArchiveTabEnum tab = archive_get_tab(browser); + bool skip_assets = (strcmp(archive_get_tab_ext(tab), "*") == 0) ? false : true; + file_browser_worker_set_config( + browser->worker, browser->path, archive_get_tab_ext(tab), skip_assets); + tab_empty = false; // Empty check will be performed later } if((tab_empty) && (tab != ArchiveTabBrowser)) { @@ -381,60 +430,46 @@ void archive_switch_tab(ArchiveBrowserView* browser, InputKey key) { } else { with_view_model( browser->view, (ArchiveBrowserViewModel * model) { - if(model->last_tab != model->tab_idx) { - model->item_idx = 0; - model->array_offset = 0; - idx_last_array_reset(model->idx_last); - } + model->item_idx = 0; + model->array_offset = 0; return false; }); - archive_switch_dir(browser, archive_get_default_path(tab)); + archive_get_items(browser, string_get_cstr(browser->path)); + archive_update_offset(browser); } - archive_set_last_tab(browser, tab); } -void archive_enter_dir(ArchiveBrowserView* browser, string_t name) { +void archive_enter_dir(ArchiveBrowserView* browser, string_t path) { furi_assert(browser); - furi_assert(name); - - if(string_size(name) >= (MAX_NAME_LEN - 1)) { - return; - } + furi_assert(path); - if(string_cmp(browser->path, name) != 0) { - with_view_model( - browser->view, (ArchiveBrowserViewModel * model) { - idx_last_array_push_back(model->idx_last, model->item_idx); - model->array_offset = 0; - model->item_idx = 0; - return false; - }); + int32_t idx_temp = 0; - string_set(browser->path, name); - } + with_view_model( + browser->view, (ArchiveBrowserViewModel * model) { + idx_temp = model->item_idx; + return false; + }); - archive_dir_count_items(browser, string_get_cstr(name)); - archive_switch_dir(browser, string_get_cstr(browser->path)); + string_set(browser->path, path); + file_browser_worker_folder_enter(browser->worker, path, idx_temp); } void archive_leave_dir(ArchiveBrowserView* browser) { furi_assert(browser); - const char* path = archive_get_path(browser); - char* last_char_ptr = strrchr(path, '/'); + file_browser_worker_folder_exit(browser->worker); +} - if(last_char_ptr) { - size_t pos = last_char_ptr - path; - string_left(browser->path, pos); - } +void archive_refresh_dir(ArchiveBrowserView* browser) { + furi_assert(browser); - archive_dir_count_items(browser, path); + int32_t idx_temp = 0; with_view_model( browser->view, (ArchiveBrowserViewModel * model) { - idx_last_array_pop_back(&model->item_idx, model->idx_last); + idx_temp = model->item_idx; return false; }); - - archive_switch_dir(browser, path); + file_browser_worker_folder_refresh(browser->worker, idx_temp); } diff --git a/applications/archive/helpers/archive_browser.h b/applications/archive/helpers/archive_browser.h index 3395541e3ca..c4283123a39 100644 --- a/applications/archive/helpers/archive_browser.h +++ b/applications/archive/helpers/archive_browser.h @@ -2,11 +2,12 @@ #include "../archive_i.h" -#define TAB_RIGHT InputKeyRight //default tab swith direction +#define TAB_RIGHT InputKeyRight // Default tab swith direction +#define TAB_DEFAULT ArchiveTabFavorites // Start tab #define FILE_LIST_BUF_LEN 100 static const char* tab_default_paths[] = { - [ArchiveTabFavorites] = "/any/favorites", + [ArchiveTabFavorites] = "/app:favorites", [ArchiveTabIButton] = "/any/ibutton", [ArchiveTabNFC] = "/any/nfc", [ArchiveTabSubGhz] = "/any/subghz", @@ -62,7 +63,7 @@ bool archive_is_item_in_array(ArchiveBrowserViewModel* model, uint32_t idx); void archive_update_offset(ArchiveBrowserView* browser); void archive_update_focus(ArchiveBrowserView* browser, const char* target); -bool archive_file_array_load(ArchiveBrowserView* browser, int8_t dir); +void archive_file_array_load(ArchiveBrowserView* browser, int8_t dir); size_t archive_file_get_array_size(ArchiveBrowserView* browser); void archive_file_array_rm_selected(ArchiveBrowserView* browser); void archive_file_array_swap(ArchiveBrowserView* browser, int8_t dir); @@ -73,15 +74,16 @@ void archive_set_item_count(ArchiveBrowserView* browser, uint32_t count); ArchiveFile_t* archive_get_current_file(ArchiveBrowserView* browser); ArchiveFile_t* archive_get_file_at(ArchiveBrowserView* browser, size_t idx); ArchiveTabEnum archive_get_tab(ArchiveBrowserView* browser); -uint8_t archive_get_depth(ArchiveBrowserView* browser); -const char* archive_get_path(ArchiveBrowserView* browser); +bool archive_is_home(ArchiveBrowserView* browser); const char* archive_get_name(ArchiveBrowserView* browser); void archive_add_app_item(ArchiveBrowserView* browser, const char* name); -void archive_add_file_item(ArchiveBrowserView* browser, FileInfo* file_info, const char* name); +void archive_add_file_item(ArchiveBrowserView* browser, bool is_folder, const char* name); void archive_show_file_menu(ArchiveBrowserView* browser, bool show); void archive_favorites_move_mode(ArchiveBrowserView* browser, bool active); void archive_switch_tab(ArchiveBrowserView* browser, InputKey key); void archive_enter_dir(ArchiveBrowserView* browser, string_t name); void archive_leave_dir(ArchiveBrowserView* browser); +void archive_refresh_dir(ArchiveBrowserView* browser); +void archive_file_browser_set_callbacks(ArchiveBrowserView* browser); diff --git a/applications/archive/helpers/archive_favorites.c b/applications/archive/helpers/archive_favorites.c index ac5b9d294f9..af7927e91f8 100644 --- a/applications/archive/helpers/archive_favorites.c +++ b/applications/archive/helpers/archive_favorites.c @@ -168,7 +168,8 @@ bool archive_favorites_read(void* context) { if(file_exists) { storage_common_stat(fs_api, string_get_cstr(buffer), &file_info); storage_file_close(fav_item_file); - archive_add_file_item(browser, &file_info, string_get_cstr(buffer)); + archive_add_file_item( + browser, (file_info.flags & FSF_DIRECTORY), string_get_cstr(buffer)); file_count++; } else { storage_file_close(fav_item_file); @@ -337,7 +338,7 @@ void archive_favorites_save(void* context) { for(size_t i = 0; i < archive_file_get_array_size(browser); i++) { ArchiveFile_t* item = archive_get_file_at(browser, i); - archive_file_append(ARCHIVE_FAV_TEMP_PATH, "%s\n", string_get_cstr(item->name)); + archive_file_append(ARCHIVE_FAV_TEMP_PATH, "%s\n", string_get_cstr(item->path)); } storage_common_remove(fs_api, ARCHIVE_FAV_PATH); diff --git a/applications/archive/helpers/archive_files.c b/applications/archive/helpers/archive_files.c index 3b1e8944678..7ff54fedb0f 100644 --- a/applications/archive/helpers/archive_files.c +++ b/applications/archive/helpers/archive_files.c @@ -6,59 +6,18 @@ #define ASSETS_DIR "assets" -bool archive_filter_by_extension(FileInfo* file_info, const char* tab_ext, const char* name) { - furi_assert(file_info); - furi_assert(tab_ext); - furi_assert(name); - - bool result = false; - - if(strcmp(tab_ext, "*") == 0) { - result = true; - } else if(strstr(name, tab_ext) != NULL) { - result = true; - } else if(file_info->flags & FSF_DIRECTORY) { - if(strstr(name, ASSETS_DIR) != NULL) { - result = false; // Skip assets folder in all tabs except browser - } else { - result = true; - } - } - - return result; -} - -void archive_trim_file_path(char* name, bool ext) { - char* slash = strrchr(name, '/') + 1; - if(strlen(slash)) strlcpy(name, slash, strlen(slash) + 1); - if(ext) { - char* dot = strrchr(name, '.'); - if(strlen(dot)) *dot = '\0'; - } -} - -void archive_get_file_extension(char* name, char* ext) { - char* dot = strrchr(name, '.'); - if(dot == NULL) - *ext = '\0'; - else - strncpy(ext, dot, MAX_EXT_LEN); -} - -void archive_set_file_type(ArchiveFile_t* file, FileInfo* file_info, const char* path, bool is_app) { +void archive_set_file_type(ArchiveFile_t* file, const char* path, bool is_folder, bool is_app) { furi_assert(file); file->is_app = is_app; if(is_app) { file->type = archive_get_app_filetype(archive_get_app_type(path)); } else { - furi_assert(file_info); - for(size_t i = 0; i < COUNT_OF(known_ext); i++) { if((known_ext[i][0] == '?') || (known_ext[i][0] == '*')) continue; - if(string_search_str(file->name, known_ext[i], 0) != STRING_FAILURE) { + if(string_search_str(file->path, known_ext[i], 0) != STRING_FAILURE) { if(i == ArchiveFileTypeBadUsb) { - if(string_search_str(file->name, archive_get_default_path(ArchiveTabBadUsb)) == + if(string_search_str(file->path, archive_get_default_path(ArchiveTabBadUsb)) == 0) { file->type = i; return; // *.txt file is a BadUSB script only if it is in BadUSB folder @@ -70,7 +29,7 @@ void archive_set_file_type(ArchiveFile_t* file, FileInfo* file_info, const char* } } - if(file_info->flags & FSF_DIRECTORY) { + if(is_folder) { file->type = ArchiveFileTypeFolder; } else { file->type = ArchiveFileTypeUnknown; @@ -78,120 +37,20 @@ void archive_set_file_type(ArchiveFile_t* file, FileInfo* file_info, const char* } } -bool archive_get_filenames(void* context, const char* path) { +bool archive_get_items(void* context, const char* path) { furi_assert(context); - bool res; + bool res = false; ArchiveBrowserView* browser = context; if(archive_get_tab(browser) == ArchiveTabFavorites) { res = archive_favorites_read(browser); } else if(strncmp(path, "/app:", 5) == 0) { res = archive_app_read_dir(browser, path); - } else { - res = archive_file_array_load(browser, 0); } return res; } -uint32_t archive_dir_count_items(void* context, const char* path) { - furi_assert(context); - ArchiveBrowserView* browser = context; - - FileInfo file_info; - Storage* fs_api = furi_record_open("storage"); - File* directory = storage_file_alloc(fs_api); - char name[MAX_NAME_LEN]; - - if(!storage_dir_open(directory, path)) { - storage_dir_close(directory); - storage_file_free(directory); - return 0; - } - - uint32_t files_found = 0; - while(1) { - if(!storage_dir_read(directory, &file_info, name, MAX_NAME_LEN)) { - break; - } - if((storage_file_get_error(directory) == FSE_OK) && (name[0])) { - if(archive_filter_by_extension( - &file_info, archive_get_tab_ext(archive_get_tab(browser)), name)) { - files_found++; - } - } - } - storage_dir_close(directory); - storage_file_free(directory); - - furi_record_close("storage"); - - archive_set_item_count(browser, files_found); - - return files_found; -} - -uint32_t archive_dir_read_items(void* context, const char* path, uint32_t offset, uint32_t count) { - furi_assert(context); - - ArchiveBrowserView* browser = context; - FileInfo file_info; - Storage* fs_api = furi_record_open("storage"); - File* directory = storage_file_alloc(fs_api); - char name[MAX_NAME_LEN]; - snprintf(name, MAX_NAME_LEN, "%s/", path); - size_t path_len = strlen(name); - - if(!storage_dir_open(directory, path)) { - storage_dir_close(directory); - storage_file_free(directory); - return false; - } - - // Skip items before offset - uint32_t items_cnt = 0; - while(items_cnt < offset) { - if(!storage_dir_read(directory, &file_info, &name[path_len], MAX_NAME_LEN)) { - break; - } - if(storage_file_get_error(directory) == FSE_OK) { - if(archive_filter_by_extension( - &file_info, archive_get_tab_ext(archive_get_tab(browser)), name)) { - items_cnt++; - } - } else { - break; - } - } - if(items_cnt != offset) { - storage_dir_close(directory); - storage_file_free(directory); - furi_record_close("storage"); - - return false; - } - - items_cnt = 0; - archive_file_array_rm_all(browser); - while(items_cnt < count) { - if(!storage_dir_read(directory, &file_info, &name[path_len], MAX_NAME_LEN - path_len)) { - break; - } - - if(storage_file_get_error(directory) == FSE_OK) { - archive_add_file_item(browser, &file_info, name); - items_cnt++; - } else { - break; - } - } - storage_dir_close(directory); - storage_file_free(directory); - furi_record_close("storage"); - - return (items_cnt == count); -} - void archive_file_append(const char* path, const char* format, ...) { furi_assert(path); diff --git a/applications/archive/helpers/archive_files.h b/applications/archive/helpers/archive_files.h index 58dd00115f1..84b7e24a62f 100644 --- a/applications/archive/helpers/archive_files.h +++ b/applications/archive/helpers/archive_files.h @@ -19,7 +19,7 @@ typedef enum { } ArchiveFileTypeEnum; typedef struct { - string_t name; + string_t path; ArchiveFileTypeEnum type; bool fav; bool is_app; @@ -29,25 +29,25 @@ static void ArchiveFile_t_init(ArchiveFile_t* obj) { obj->type = ArchiveFileTypeUnknown; obj->is_app = false; obj->fav = false; - string_init(obj->name); + string_init(obj->path); } static void ArchiveFile_t_init_set(ArchiveFile_t* obj, const ArchiveFile_t* src) { obj->type = src->type; obj->is_app = src->is_app; obj->fav = src->fav; - string_init_set(obj->name, src->name); + string_init_set(obj->path, src->path); } static void ArchiveFile_t_set(ArchiveFile_t* obj, const ArchiveFile_t* src) { obj->type = src->type; obj->is_app = src->is_app; obj->fav = src->fav; - string_set(obj->name, src->name); + string_set(obj->path, src->path); } static void ArchiveFile_t_clear(ArchiveFile_t* obj) { - string_clear(obj->name); + string_clear(obj->path); } ARRAY_DEF( @@ -58,12 +58,7 @@ ARRAY_DEF( INIT_SET(API_6(ArchiveFile_t_init_set)), CLEAR(API_2(ArchiveFile_t_clear)))) -bool archive_filter_by_extension(FileInfo* file_info, const char* tab_ext, const char* name); -void archive_set_file_type(ArchiveFile_t* file, FileInfo* file_info, const char* path, bool is_app); -void archive_trim_file_path(char* name, bool ext); -void archive_get_file_extension(char* name, char* ext); -bool archive_get_filenames(void* context, const char* path); -uint32_t archive_dir_count_items(void* context, const char* path); -uint32_t archive_dir_read_items(void* context, const char* path, uint32_t offset, uint32_t count); +void archive_set_file_type(ArchiveFile_t* file, const char* path, bool is_folder, bool is_app); +bool archive_get_items(void* context, const char* path); void archive_file_append(const char* path, const char* format, ...); void archive_delete_file(void* context, const char* format, ...); diff --git a/applications/archive/scenes/archive_scene_browser.c b/applications/archive/scenes/archive_scene_browser.c index 9df3463befe..e3581538cf6 100644 --- a/applications/archive/scenes/archive_scene_browser.c +++ b/applications/archive/scenes/archive_scene_browser.c @@ -4,9 +4,13 @@ #include "../helpers/archive_favorites.h" #include "../helpers/archive_browser.h" #include "../views/archive_browser_view.h" +#include "archive/scenes/archive_scene.h" #define TAG "ArchiveSceneBrowser" +#define SCENE_STATE_DEFAULT (0) +#define SCENE_STATE_NEED_REFRESH (1) + static const char* flipper_app_name[] = { [ArchiveFileTypeIButton] = "iButton", [ArchiveFileTypeNFC] = "NFC", @@ -26,7 +30,7 @@ static void archive_loader_callback(const void* message, void* context) { if(event->type == LoaderEventTypeApplicationStopped) { view_dispatcher_send_custom_event( - archive->view_dispatcher, ArchiveBrowserEventLoaderAppExit); + archive->view_dispatcher, ArchiveBrowserEventListRefresh); } } @@ -36,14 +40,14 @@ static void archive_run_in_app(ArchiveBrowserView* browser, ArchiveFile_t* selec LoaderStatus status; if(selected->is_app) { - char* param = strrchr(string_get_cstr(selected->name), '/'); + char* param = strrchr(string_get_cstr(selected->path), '/'); if(param != NULL) { param++; } status = loader_start(loader, flipper_app_name[selected->type], param); } else { status = loader_start( - loader, flipper_app_name[selected->type], string_get_cstr(selected->name)); + loader, flipper_app_name[selected->type], string_get_cstr(selected->path)); } if(status != LoaderStatusOk) { @@ -61,6 +65,7 @@ void archive_scene_browser_callback(ArchiveBrowserEvent event, void* context) { void archive_scene_browser_on_enter(void* context) { ArchiveApp* archive = (ArchiveApp*)context; ArchiveBrowserView* browser = archive->browser; + browser->is_root = true; archive_browser_set_callback(browser, archive_scene_browser_callback, archive); archive_update_focus(browser, archive->text_store); @@ -70,6 +75,16 @@ void archive_scene_browser_on_enter(void* context) { archive->loader_stop_subscription = furi_pubsub_subscribe(loader_get_pubsub(loader), archive_loader_callback, archive); furi_record_close("loader"); + + uint32_t state = scene_manager_get_scene_state(archive->scene_manager, ArchiveAppSceneBrowser); + + if(state == SCENE_STATE_NEED_REFRESH) { + view_dispatcher_send_custom_event( + archive->view_dispatcher, ArchiveBrowserEventListRefresh); + } + + scene_manager_set_scene_state( + archive->scene_manager, ArchiveAppSceneBrowser, SCENE_STATE_DEFAULT); } bool archive_scene_browser_on_event(void* context, SceneManagerEvent event) { @@ -95,8 +110,8 @@ bool archive_scene_browser_on_event(void* context, SceneManagerEvent event) { case ArchiveBrowserEventFileMenuRun: if(known_app) { archive_run_in_app(browser, selected); + archive_show_file_menu(browser, false); } - archive_show_file_menu(browser, false); consumed = true; break; case ArchiveBrowserEventFileMenuPin: @@ -115,11 +130,13 @@ bool archive_scene_browser_on_event(void* context, SceneManagerEvent event) { consumed = true; break; - case ArchiveBrowserEventFileMenuAction: + case ArchiveBrowserEventFileMenuRename: if(favorites) { browser->callback(ArchiveBrowserEventEnterFavMove, browser->context); } else if((known_app) && (selected->is_app == false)) { archive_show_file_menu(browser, false); + scene_manager_set_scene_state( + archive->scene_manager, ArchiveAppSceneBrowser, SCENE_STATE_NEED_REFRESH); scene_manager_next_scene(archive->scene_manager, ArchiveAppSceneRename); } consumed = true; @@ -131,7 +148,7 @@ bool archive_scene_browser_on_event(void* context, SceneManagerEvent event) { consumed = true; break; case ArchiveBrowserEventEnterDir: - archive_enter_dir(browser, selected->name); + archive_enter_dir(browser, selected->path); consumed = true; break; case ArchiveBrowserEventFavMoveUp: @@ -143,13 +160,13 @@ bool archive_scene_browser_on_event(void* context, SceneManagerEvent event) { consumed = true; break; case ArchiveBrowserEventEnterFavMove: - strlcpy(archive->text_store, archive_get_name(browser), MAX_NAME_LEN); + string_set(archive->fav_move_str, selected->path); archive_show_file_menu(browser, false); archive_favorites_move_mode(archive->browser, true); consumed = true; break; case ArchiveBrowserEventExitFavMove: - archive_update_focus(browser, archive->text_store); + archive_update_focus(browser, string_get_cstr(archive->fav_move_str)); archive_favorites_move_mode(archive->browser, false); consumed = true; break; @@ -166,18 +183,17 @@ bool archive_scene_browser_on_event(void* context, SceneManagerEvent event) { archive_file_array_load(archive->browser, 1); consumed = true; break; - case ArchiveBrowserEventLoaderAppExit: + case ArchiveBrowserEventListRefresh: if(!favorites) { - archive_enter_dir(browser, browser->path); + archive_refresh_dir(browser); } else { archive_favorites_read(browser); } - consumed = true; break; case ArchiveBrowserEventExit: - if(archive_get_depth(browser)) { + if(!archive_is_home(browser)) { archive_leave_dir(browser); } else { Loader* loader = furi_record_open("loader"); diff --git a/applications/archive/scenes/archive_scene_delete.c b/applications/archive/scenes/archive_scene_delete.c index fbcba77733a..d882fd066e0 100644 --- a/applications/archive/scenes/archive_scene_delete.c +++ b/applications/archive/scenes/archive_scene_delete.c @@ -3,6 +3,8 @@ #include "../helpers/archive_files.h" #include "../helpers/archive_apps.h" #include "../helpers/archive_browser.h" +#include "toolbox/path.h" +#include "m-string.h" #define SCENE_DELETE_CUSTOM_EVENT (0UL) #define MAX_TEXT_INPUT_LEN 22 @@ -24,18 +26,19 @@ void archive_scene_delete_on_enter(void* context) { widget_add_button_element( app->widget, GuiButtonTypeRight, "Delete", archive_scene_delete_widget_callback, app); + string_t filename; + string_init(filename); + ArchiveFile_t* current = archive_get_current_file(app->browser); - strlcpy(app->text_store, string_get_cstr(current->name), MAX_NAME_LEN); - char* name = strrchr(app->text_store, '/'); - if(name != NULL) { - name++; - } + path_extract_filename(current->path, filename, false); char delete_str[64]; - snprintf(delete_str, sizeof(delete_str), "\e#Delete %s?\e#", name); + snprintf(delete_str, sizeof(delete_str), "\e#Delete %s?\e#", string_get_cstr(filename)); widget_add_text_box_element( app->widget, 0, 0, 128, 23, AlignCenter, AlignCenter, delete_str, false); + string_clear(filename); + view_dispatcher_switch_to_view(app->view_dispatcher, ArchiveViewWidget); } diff --git a/applications/archive/scenes/archive_scene_rename.c b/applications/archive/scenes/archive_scene_rename.c index e0c4ba6c66d..d292dd60f73 100644 --- a/applications/archive/scenes/archive_scene_rename.c +++ b/applications/archive/scenes/archive_scene_rename.c @@ -2,6 +2,8 @@ #include "../helpers/archive_favorites.h" #include "../helpers/archive_files.h" #include "../helpers/archive_browser.h" +#include "archive/views/archive_browser_view.h" +#include "toolbox/path.h" #define SCENE_RENAME_CUSTOM_EVENT (0UL) #define MAX_TEXT_INPUT_LEN 22 @@ -16,10 +18,13 @@ void archive_scene_rename_on_enter(void* context) { TextInput* text_input = archive->text_input; ArchiveFile_t* current = archive_get_current_file(archive->browser); - strlcpy(archive->text_store, string_get_cstr(current->name), MAX_NAME_LEN); - archive_get_file_extension(archive->text_store, archive->file_extension); - archive_trim_file_path(archive->text_store, true); + string_t filename; + string_init(filename); + path_extract_filename(current->path, filename, true); + strlcpy(archive->text_store, string_get_cstr(filename), MAX_NAME_LEN); + + path_extract_extension(current->path, archive->file_extension, MAX_EXT_LEN); text_input_set_header_text(text_input, "Rename:"); @@ -32,9 +37,11 @@ void archive_scene_rename_on_enter(void* context) { false); ValidatorIsFile* validator_is_file = validator_is_file_alloc_init( - archive_get_path(archive->browser), archive->file_extension, NULL); + string_get_cstr(archive->browser->path), archive->file_extension, NULL); text_input_set_validator(text_input, validator_is_file_callback, validator_is_file); + string_clear(filename); + view_dispatcher_switch_to_view(archive->view_dispatcher, ArchiveViewTextInput); } @@ -46,30 +53,22 @@ bool archive_scene_rename_on_event(void* context, SceneManagerEvent event) { if(event.event == SCENE_RENAME_CUSTOM_EVENT) { Storage* fs_api = furi_record_open("storage"); - string_t buffer_src; - string_t buffer_dst; - - const char* path = archive_get_path(archive->browser); - const char* name = archive_get_name(archive->browser); - - string_init_printf(buffer_src, "%s", name); - //TODO: take path from src name - string_init_printf(buffer_dst, "%s/%s", path, archive->text_store); - - // append extension + const char* path_src = archive_get_name(archive->browser); ArchiveFile_t* file = archive_get_current_file(archive->browser); - string_cat(buffer_dst, known_ext[file->type]); - storage_common_rename( - fs_api, string_get_cstr(buffer_src), string_get_cstr(buffer_dst)); + string_t path_dst; + string_init(path_dst); + path_extract_dirname(path_src, path_dst); + string_cat_printf(path_dst, "/%s%s", archive->text_store, known_ext[file->type]); + + storage_common_rename(fs_api, path_src, string_get_cstr(path_dst)); furi_record_close("storage"); if(file->fav) { - archive_favorites_rename(name, string_get_cstr(buffer_dst)); + archive_favorites_rename(path_src, string_get_cstr(path_dst)); } - string_clear(buffer_src); - string_clear(buffer_dst); + string_clear(path_dst); scene_manager_next_scene(archive->scene_manager, ArchiveAppSceneBrowser); consumed = true; diff --git a/applications/archive/views/archive_browser_view.c b/applications/archive/views/archive_browser_view.c index fc578b36523..34b875b8c69 100644 --- a/applications/archive/views/archive_browser_view.c +++ b/applications/archive/views/archive_browser_view.c @@ -1,3 +1,5 @@ +#include "assets_icons.h" +#include "toolbox/path.h" #include #include "../archive_i.h" #include "archive_browser_view.h" @@ -26,7 +28,7 @@ static const Icon* ArchiveItemIcons[] = { [ArchiveFileTypeUpdateManifest] = &I_update_10px, [ArchiveFileTypeFolder] = &I_dir_10px, [ArchiveFileTypeUnknown] = &I_unknown_10px, - [ArchiveFileTypeLoading] = &I_unknown_10px, // TODO loading icon + [ArchiveFileTypeLoading] = &I_loading_10px, }; void archive_browser_set_callback( @@ -100,6 +102,22 @@ static void archive_draw_frame(Canvas* canvas, uint16_t idx, bool scrollbar, boo canvas_draw_dot(canvas, scrollbar ? 121 : 126, (15 + idx * FRAME_HEIGHT) + 11); } +static void archive_draw_loading(Canvas* canvas, ArchiveBrowserViewModel* model) { + furi_assert(model); + + uint8_t width = 49; + uint8_t height = 47; + uint8_t x = 128 / 2 - width / 2; + uint8_t y = 64 / 2 - height / 2 + 6; + + elements_bold_rounded_frame(canvas, x, y, width, height); + + canvas_set_font(canvas, FontSecondary); + elements_multiline_text(canvas, x + 7, y + 13, "Loading..."); + + canvas_draw_icon(canvas, x + 13, y + 19, &A_Loading_24); +} + static void draw_list(Canvas* canvas, ArchiveBrowserViewModel* model) { furi_assert(model); @@ -107,8 +125,8 @@ static void draw_list(Canvas* canvas, ArchiveBrowserViewModel* model) { bool scrollbar = model->item_cnt > 4; for(uint32_t i = 0; i < MIN(model->item_cnt, MENU_ITEMS); ++i) { - string_t str_buff; - char cstr_buff[MAX_NAME_LEN]; + string_t str_buf; + string_init(str_buf); int32_t idx = CLAMP((uint32_t)(i + model->list_offset), model->item_cnt, 0u); uint8_t x_offset = (model->move_fav && model->item_idx == idx) ? MOVE_OFFSET : 0; @@ -117,16 +135,14 @@ static void draw_list(Canvas* canvas, ArchiveBrowserViewModel* model) { if(archive_is_item_in_array(model, idx)) { ArchiveFile_t* file = files_array_get( model->files, CLAMP(idx - model->array_offset, (int32_t)(array_size - 1), 0)); - strlcpy(cstr_buff, string_get_cstr(file->name), string_size(file->name) + 1); - archive_trim_file_path(cstr_buff, archive_is_known_app(file->type)); - string_init_set_str(str_buff, cstr_buff); + path_extract_filename(file->path, str_buf, archive_is_known_app(file->type)); file_type = file->type; } else { - string_init_set_str(str_buff, "---"); + string_set_str(str_buf, "---"); } elements_string_fit_width( - canvas, str_buff, (scrollbar ? MAX_LEN_PX - 6 : MAX_LEN_PX) - x_offset); + canvas, str_buf, (scrollbar ? MAX_LEN_PX - 6 : MAX_LEN_PX) - x_offset); if(model->item_idx == idx) { archive_draw_frame(canvas, i, scrollbar, model->move_fav); @@ -135,9 +151,9 @@ static void draw_list(Canvas* canvas, ArchiveBrowserViewModel* model) { } canvas_draw_icon(canvas, 2 + x_offset, 16 + i * FRAME_HEIGHT, ArchiveItemIcons[file_type]); - canvas_draw_str(canvas, 15 + x_offset, 24 + i * FRAME_HEIGHT, string_get_cstr(str_buff)); + canvas_draw_str(canvas, 15 + x_offset, 24 + i * FRAME_HEIGHT, string_get_cstr(str_buf)); - string_clear(str_buff); + string_clear(str_buf); } if(scrollbar) { @@ -190,7 +206,9 @@ void archive_view_render(Canvas* canvas, void* mdl) { archive_render_status_bar(canvas, mdl); - if(model->item_cnt > 0) { + if(model->folder_loading) { + archive_draw_loading(canvas, model); + } else if(model->item_cnt > 0) { draw_list(canvas, model); } else { canvas_draw_str_aligned( @@ -350,25 +368,31 @@ ArchiveBrowserView* browser_alloc() { view_set_draw_callback(browser->view, archive_view_render); view_set_input_callback(browser->view, archive_view_input); - string_init(browser->path); + string_init_set_str(browser->path, archive_get_default_path(TAB_DEFAULT)); with_view_model( browser->view, (ArchiveBrowserViewModel * model) { files_array_init(model->files); - idx_last_array_init(model->idx_last); + model->tab_idx = TAB_DEFAULT; return true; }); + browser->worker = file_browser_worker_alloc(browser->path, "*", false); + archive_file_browser_set_callbacks(browser); + + file_browser_worker_set_callback_context(browser->worker, browser); + return browser; } void browser_free(ArchiveBrowserView* browser) { furi_assert(browser); + file_browser_worker_free(browser->worker); + with_view_model( browser->view, (ArchiveBrowserViewModel * model) { files_array_clear(model->files); - idx_last_array_clear(model->idx_last); return false; }); diff --git a/applications/archive/views/archive_browser_view.h b/applications/archive/views/archive_browser_view.h index db079d6bc1c..2de04a166c0 100644 --- a/applications/archive/views/archive_browser_view.h +++ b/applications/archive/views/archive_browser_view.h @@ -8,6 +8,7 @@ #include #include "../helpers/archive_files.h" #include "../helpers/archive_favorites.h" +#include "gui/modules/file_browser_worker.h" #define MAX_LEN_PX 110 #define MAX_NAME_LEN 255 @@ -34,7 +35,7 @@ typedef enum { ArchiveBrowserEventFileMenuClose, ArchiveBrowserEventFileMenuRun, ArchiveBrowserEventFileMenuPin, - ArchiveBrowserEventFileMenuAction, + ArchiveBrowserEventFileMenuRename, ArchiveBrowserEventFileMenuDelete, ArchiveBrowserEventEnterDir, @@ -48,7 +49,7 @@ typedef enum { ArchiveBrowserEventLoadPrevItems, ArchiveBrowserEventLoadNextItems, - ArchiveBrowserEventLoaderAppExit, + ArchiveBrowserEventListRefresh, ArchiveBrowserEventExit, } ArchiveBrowserEvent; @@ -56,7 +57,7 @@ typedef enum { static const uint8_t file_menu_actions[MENU_ITEMS] = { [0] = ArchiveBrowserEventFileMenuRun, [1] = ArchiveBrowserEventFileMenuPin, - [2] = ArchiveBrowserEventFileMenuAction, + [2] = ArchiveBrowserEventFileMenuRename, [3] = ArchiveBrowserEventFileMenuDelete, }; @@ -72,23 +73,23 @@ typedef enum { struct ArchiveBrowserView { View* view; + BrowserWorker* worker; ArchiveBrowserViewCallback callback; void* context; string_t path; + InputKey last_tab_switch_dir; + bool is_root; }; -ARRAY_DEF(idx_last_array, int32_t) - typedef struct { ArchiveTabEnum tab_idx; - ArchiveTabEnum last_tab; files_array_t files; - idx_last_array_t idx_last; uint8_t menu_idx; - bool move_fav; bool menu; + bool move_fav; bool list_loading; + bool folder_loading; uint32_t item_cnt; int32_t item_idx; diff --git a/applications/bad_usb/views/bad_usb_view.c b/applications/bad_usb/views/bad_usb_view.c index 430885df3d6..0679669f6b5 100644 --- a/applications/bad_usb/views/bad_usb_view.c +++ b/applications/bad_usb/views/bad_usb_view.c @@ -155,7 +155,7 @@ void bad_usb_set_file_name(BadUsb* bad_usb, const char* name) { furi_assert(name); with_view_model( bad_usb->view, (BadUsbModel * model) { - strncpy(model->file_name, name, MAX_NAME_LEN); + strlcpy(model->file_name, name, MAX_NAME_LEN); return true; }); } diff --git a/applications/bt/bt_cli.c b/applications/bt/bt_cli.c index 1c9b198bebc..98f73d22978 100644 --- a/applications/bt/bt_cli.c +++ b/applications/bt/bt_cli.c @@ -3,14 +3,9 @@ #include #include +#include "ble.h" #include "bt_settings.h" - -static const char* bt_cli_address_types[] = { - "Public Device Address", - "Random Device Address", - "Public Identity Address", - "Random (Static) Identity Address", -}; +#include "bt_service/bt.h" static void bt_cli_command_hci_info(Cli* cli, string_t args, void* context) { UNUSED(cli); @@ -38,7 +33,9 @@ static void bt_cli_command_carrier_tx(Cli* cli, string_t args, void* context) { break; } - furi_hal_bt_stop_advertising(); + Bt* bt = furi_record_open("bt"); + bt_disconnect(bt); + furi_hal_bt_reinit(); printf("Transmitting carrier at %d channel at %d dB power\r\n", channel, power); printf("Press CTRL+C to stop\r\n"); furi_hal_bt_start_tone_tx(channel, 0x19 + power); @@ -47,6 +44,9 @@ static void bt_cli_command_carrier_tx(Cli* cli, string_t args, void* context) { osDelay(250); } furi_hal_bt_stop_tone_tx(); + + bt_set_profile(bt, BtProfileSerial); + furi_record_close("bt"); } while(false); } @@ -60,7 +60,9 @@ static void bt_cli_command_carrier_rx(Cli* cli, string_t args, void* context) { break; } - furi_hal_bt_stop_advertising(); + Bt* bt = furi_record_open("bt"); + bt_disconnect(bt); + furi_hal_bt_reinit(); printf("Receiving carrier at %d channel\r\n", channel); printf("Press CTRL+C to stop\r\n"); @@ -73,6 +75,9 @@ static void bt_cli_command_carrier_rx(Cli* cli, string_t args, void* context) { } furi_hal_bt_stop_packet_test(); + + bt_set_profile(bt, BtProfileSerial); + furi_record_close("bt"); } while(false); } @@ -102,7 +107,9 @@ static void bt_cli_command_packet_tx(Cli* cli, string_t args, void* context) { break; } - furi_hal_bt_stop_advertising(); + Bt* bt = furi_record_open("bt"); + bt_disconnect(bt); + furi_hal_bt_reinit(); printf( "Transmitting %d pattern packet at %d channel at %d M datarate\r\n", pattern, @@ -117,6 +124,8 @@ static void bt_cli_command_packet_tx(Cli* cli, string_t args, void* context) { furi_hal_bt_stop_packet_test(); printf("Transmitted %lu packets", furi_hal_bt_get_transmitted_packets()); + bt_set_profile(bt, BtProfileSerial); + furi_record_close("bt"); } while(false); } @@ -135,7 +144,9 @@ static void bt_cli_command_packet_rx(Cli* cli, string_t args, void* context) { break; } - furi_hal_bt_stop_advertising(); + Bt* bt = furi_record_open("bt"); + bt_disconnect(bt); + furi_hal_bt_reinit(); printf("Receiving packets at %d channel at %d M datarate\r\n", channel, datarate); printf("Press CTRL+C to stop\r\n"); furi_hal_bt_start_packet_rx(channel, datarate); @@ -147,37 +158,10 @@ static void bt_cli_command_packet_rx(Cli* cli, string_t args, void* context) { } uint16_t packets_received = furi_hal_bt_stop_packet_test(); printf("Received %hu packets", packets_received); - } while(false); -} - -static void bt_cli_scan_callback(GapAddress address, void* context) { - furi_assert(context); - osMessageQueueId_t queue = context; - osMessageQueuePut(queue, &address, 0, 250); -} -static void bt_cli_command_scan(Cli* cli, string_t args, void* context) { - UNUSED(context); - UNUSED(args); - osMessageQueueId_t queue = osMessageQueueNew(20, sizeof(GapAddress), NULL); - furi_hal_bt_start_scan(bt_cli_scan_callback, queue); - - GapAddress address = {}; - bool exit = false; - while(!exit) { - if(osMessageQueueGet(queue, &address, NULL, 250) == osOK) { - if(address.type < sizeof(bt_cli_address_types)) { - printf("Found new device. Type: %s, MAC: ", bt_cli_address_types[address.type]); - for(uint8_t i = 0; i < sizeof(address.mac) - 1; i++) { - printf("%02X:", address.mac[i]); - } - printf("%02X\r\n", address.mac[sizeof(address.mac) - 1]); - } - } - exit = cli_cmd_interrupt_received(cli); - } - furi_hal_bt_stop_scan(); - osMessageQueueDelete(queue); + bt_set_profile(bt, BtProfileSerial); + furi_record_close("bt"); + } while(false); } static void bt_cli_print_usage() { @@ -185,13 +169,12 @@ static void bt_cli_print_usage() { printf("bt \r\n"); printf("Cmd list:\r\n"); printf("\thci_info\t - HCI info\r\n"); - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug) && - furi_hal_bt_get_radio_stack() == FuriHalBtStackHciLayer) { + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug) && furi_hal_bt_is_testing_supported()) { printf("\ttx_carrier \t - start tx carrier test\r\n"); printf("\trx_carrier \t - start rx carrier test\r\n"); - printf("\ttx_pt \t - start tx packet test\r\n"); - printf("\trx_pt \t - start rx packer test\r\n"); - printf("\tscan\t - start scanner\r\n"); + printf( + "\ttx_packet \t - start tx packet test\r\n"); + printf("\trx_packet \t - start rx packer test\r\n"); } } @@ -213,28 +196,23 @@ static void bt_cli(Cli* cli, string_t args, void* context) { bt_cli_command_hci_info(cli, args, NULL); break; } - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug) && - furi_hal_bt_get_radio_stack() == FuriHalBtStackHciLayer) { - if(string_cmp_str(cmd, "carrier_tx") == 0) { + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug) && furi_hal_bt_is_testing_supported()) { + if(string_cmp_str(cmd, "tx_carrier") == 0) { bt_cli_command_carrier_tx(cli, args, NULL); break; } - if(string_cmp_str(cmd, "carrier_rx") == 0) { + if(string_cmp_str(cmd, "rx_carrier") == 0) { bt_cli_command_carrier_rx(cli, args, NULL); break; } - if(string_cmp_str(cmd, "packet_tx") == 0) { + if(string_cmp_str(cmd, "tx_packet") == 0) { bt_cli_command_packet_tx(cli, args, NULL); break; } - if(string_cmp_str(cmd, "packet_rx") == 0) { + if(string_cmp_str(cmd, "rx_packet") == 0) { bt_cli_command_packet_rx(cli, args, NULL); break; } - if(string_cmp_str(cmd, "scan") == 0) { - bt_cli_command_scan(cli, args, NULL); - break; - } } bt_cli_print_usage(); diff --git a/applications/bt/bt_debug_app/bt_debug_app.c b/applications/bt/bt_debug_app/bt_debug_app.c index 58119f5dc8f..468f8a54755 100644 --- a/applications/bt/bt_debug_app/bt_debug_app.c +++ b/applications/bt/bt_debug_app/bt_debug_app.c @@ -97,8 +97,8 @@ void bt_debug_app_free(BtDebugApp* app) { int32_t bt_debug_app(void* p) { UNUSED(p); - if(furi_hal_bt_get_radio_stack() != FuriHalBtStackHciLayer) { - FURI_LOG_E(TAG, "Incorrect radio stack, replace with HciLayer for tests."); + if(!furi_hal_bt_is_testing_supported()) { + FURI_LOG_E(TAG, "Incorrect radio stack: radio testing fetures are absent."); DialogsApp* dialogs = furi_record_open("dialogs"); dialog_message_show_storage_error(dialogs, "Incorrect\nRadioStack"); return 255; diff --git a/applications/bt/bt_service/bt.c b/applications/bt/bt_service/bt.c index 38aadc49ba2..f387ec6de92 100644 --- a/applications/bt/bt_service/bt.c +++ b/applications/bt/bt_service/bt.c @@ -293,17 +293,20 @@ static void bt_show_warning(Bt* bt, const char* text) { dialog_message_show(bt->dialogs, bt->dialog_message); } +static void bt_close_rpc_connection(Bt* bt) { + if(bt->profile == BtProfileSerial && bt->rpc_session) { + FURI_LOG_I(TAG, "Close RPC connection"); + osEventFlagsSet(bt->rpc_event, BT_RPC_EVENT_DISCONNECTED); + rpc_session_close(bt->rpc_session); + furi_hal_bt_serial_set_event_callback(0, NULL, NULL); + bt->rpc_session = NULL; + } +} + static void bt_change_profile(Bt* bt, BtMessage* message) { - FuriHalBtStack stack = furi_hal_bt_get_radio_stack(); - if(stack == FuriHalBtStackLight) { + if(furi_hal_bt_is_ble_gatt_gap_supported()) { bt_settings_load(&bt->bt_settings); - if(bt->profile == BtProfileSerial && bt->rpc_session) { - FURI_LOG_I(TAG, "Close RPC connection"); - osEventFlagsSet(bt->rpc_event, BT_RPC_EVENT_DISCONNECTED); - rpc_session_close(bt->rpc_session); - furi_hal_bt_serial_set_event_callback(0, NULL, NULL); - bt->rpc_session = NULL; - } + bt_close_rpc_connection(bt); FuriHalBtProfile furi_profile; if(message->data.profile == BtProfileHidKeyboard) { @@ -331,6 +334,11 @@ static void bt_change_profile(Bt* bt, BtMessage* message) { osEventFlagsSet(bt->api_event, BT_API_UNLOCK_EVENT); } +static void bt_close_connection(Bt* bt) { + bt_close_rpc_connection(bt); + osEventFlagsSet(bt->api_event, BT_API_UNLOCK_EVENT); +} + int32_t bt_srv() { Bt* bt = bt_alloc(); @@ -350,14 +358,8 @@ int32_t bt_srv() { if(!furi_hal_bt_start_radio_stack()) { FURI_LOG_E(TAG, "Radio stack start failed"); } - FuriHalBtStack stack_type = furi_hal_bt_get_radio_stack(); - if(stack_type == FuriHalBtStackUnknown) { - bt_show_warning(bt, "Unsupported radio stack"); - bt->status = BtStatusUnavailable; - } else if(stack_type == FuriHalBtStackHciLayer) { - bt->status = BtStatusUnavailable; - } else if(stack_type == FuriHalBtStackLight) { + if(furi_hal_bt_is_ble_gatt_gap_supported()) { if(!furi_hal_bt_start_app(FuriHalBtProfileSerial, bt_on_gap_event_callback, bt)) { FURI_LOG_E(TAG, "BLE App start failed"); } else { @@ -366,6 +368,9 @@ int32_t bt_srv() { } furi_hal_bt_set_key_storage_change_callback(bt_on_key_storage_change_callback, bt); } + } else { + bt_show_warning(bt, "Unsupported radio stack"); + bt->status = BtStatusUnavailable; } furi_record_create("bt", bt); @@ -392,6 +397,8 @@ int32_t bt_srv() { bt_keys_storage_save(bt); } else if(message.type == BtMessageTypeSetProfile) { bt_change_profile(bt, &message); + } else if(message.type == BtMessageTypeDisconnect) { + bt_close_connection(bt); } else if(message.type == BtMessageTypeForgetBondedDevices) { bt_keys_storage_delete(bt); } diff --git a/applications/bt/bt_service/bt.h b/applications/bt/bt_service/bt.h index e39717d6b11..4ca6a32f1f8 100644 --- a/applications/bt/bt_service/bt.h +++ b/applications/bt/bt_service/bt.h @@ -33,6 +33,12 @@ typedef void (*BtStatusChangedCallback)(BtStatus status, void* context); */ bool bt_set_profile(Bt* bt, BtProfile profile); +/** Disconnect from Central + * + * @param bt Bt instance + */ +void bt_disconnect(Bt* bt); + /** Set callback for Bluetooth status change notification * * @param bt Bt instance diff --git a/applications/bt/bt_service/bt_api.c b/applications/bt/bt_service/bt_api.c index 96d72e63b17..884ec4a91a8 100755 --- a/applications/bt/bt_service/bt_api.c +++ b/applications/bt/bt_service/bt_api.c @@ -14,6 +14,16 @@ bool bt_set_profile(Bt* bt, BtProfile profile) { return result; } +void bt_disconnect(Bt* bt) { + furi_assert(bt); + + // Send message + BtMessage message = {.type = BtMessageTypeDisconnect}; + furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK); + // Wait for unlock + osEventFlagsWait(bt->api_event, BT_API_UNLOCK_EVENT, osFlagsWaitAny, osWaitForever); +} + void bt_set_status_changed_callback(Bt* bt, BtStatusChangedCallback callback, void* context) { furi_assert(bt); diff --git a/applications/bt/bt_service/bt_i.h b/applications/bt/bt_service/bt_i.h index caa45360afc..90ee0851265 100644 --- a/applications/bt/bt_service/bt_i.h +++ b/applications/bt/bt_service/bt_i.h @@ -25,6 +25,7 @@ typedef enum { BtMessageTypePinCodeShow, BtMessageTypeKeysStorageUpdated, BtMessageTypeSetProfile, + BtMessageTypeDisconnect, BtMessageTypeForgetBondedDevices, } BtMessageType; diff --git a/applications/bt/bt_settings_app/scenes/bt_settings_scene_start.c b/applications/bt/bt_settings_app/scenes/bt_settings_scene_start.c index 089038dda63..bcbc902b248 100755 --- a/applications/bt/bt_settings_app/scenes/bt_settings_scene_start.c +++ b/applications/bt/bt_settings_app/scenes/bt_settings_scene_start.c @@ -39,8 +39,7 @@ void bt_settings_scene_start_on_enter(void* context) { VariableItemList* var_item_list = app->var_item_list; VariableItem* item; - FuriHalBtStack stack_type = furi_hal_bt_get_radio_stack(); - if(stack_type == FuriHalBtStackLight) { + if(furi_hal_bt_is_ble_gatt_gap_supported()) { item = variable_item_list_add( var_item_list, "Bluetooth", diff --git a/applications/cli/cli_command_gpio.c b/applications/cli/cli_command_gpio.c new file mode 100644 index 00000000000..d5ec8d65440 --- /dev/null +++ b/applications/cli/cli_command_gpio.c @@ -0,0 +1,228 @@ +#include "cli_command_gpio.h" + +#include +#include +#include + +typedef struct { + const GpioPin* pin; + const char* name; + const bool debug; +} CliCommandGpio; + +const CliCommandGpio cli_command_gpio_pins[] = { + {.pin = &gpio_ext_pc0, .name = "PC0", .debug = false}, + {.pin = &gpio_ext_pc1, .name = "PC1", .debug = false}, + {.pin = &gpio_ext_pc3, .name = "PC3", .debug = false}, + {.pin = &gpio_ext_pb2, .name = "PB2", .debug = false}, + {.pin = &gpio_ext_pb3, .name = "PB3", .debug = false}, + {.pin = &gpio_ext_pa4, .name = "PA4", .debug = false}, + {.pin = &gpio_ext_pa6, .name = "PA6", .debug = false}, + {.pin = &gpio_ext_pa7, .name = "PA7", .debug = false}, + /* Dangerous pins, may damage hardware */ + {.pin = &gpio_infrared_rx, .name = "PA0", .debug = true}, + {.pin = &gpio_usart_rx, .name = "PB7", .debug = true}, + {.pin = &gpio_speaker, .name = "PB8", .debug = true}, + {.pin = &gpio_infrared_tx, .name = "PB9", .debug = true}, +}; + +void cli_command_gpio_print_usage() { + printf("Usage:\r\n"); + printf("gpio \r\n"); + printf("Cmd list:\r\n"); + printf("\tmode <0|1>\t - Set gpio mode: 0 - input, 1 - output\r\n"); + printf("\tset <0|1>\t - Set gpio value\r\n"); + printf("\tread \t - Read gpio value\r\n"); +} + +static bool pin_name_to_int(string_t pin_name, size_t* result) { + bool found = false; + bool debug = furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug); + for(size_t i = 0; i < COUNT_OF(cli_command_gpio_pins); i++) { + if(!string_cmp(pin_name, cli_command_gpio_pins[i].name)) { + if(!cli_command_gpio_pins[i].debug || (cli_command_gpio_pins[i].debug && debug)) { + *result = i; + found = true; + break; + } + } + } + + return found; +} + +static void gpio_print_pins(void) { + printf("Wrong pin name. Available pins: "); + bool debug = furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug); + for(size_t i = 0; i < COUNT_OF(cli_command_gpio_pins); i++) { + if(!cli_command_gpio_pins[i].debug || (cli_command_gpio_pins[i].debug && debug)) { + printf("%s ", cli_command_gpio_pins[i].name); + } + } +} + +typedef enum { OK, ERR_CMD_SYNTAX, ERR_PIN, ERR_VALUE } GpioParseError; + +static GpioParseError gpio_command_parse(string_t args, size_t* pin_num, uint8_t* value) { + string_t pin_name; + string_init(pin_name); + + size_t ws = string_search_char(args, ' '); + if(ws == STRING_FAILURE) { + return ERR_CMD_SYNTAX; + } + + string_set_n(pin_name, args, 0, ws); + string_right(args, ws); + string_strim(args); + + if(!pin_name_to_int(pin_name, pin_num)) { + string_clear(pin_name); + return ERR_PIN; + } + + string_clear(pin_name); + + if(!string_cmp(args, "0")) { + *value = 0; + } else if(!string_cmp(args, "1")) { + *value = 1; + } else { + return ERR_VALUE; + } + + return OK; +} + +void cli_command_gpio_mode(Cli* cli, string_t args, void* context) { + UNUSED(cli); + UNUSED(context); + + size_t num = 0; + uint8_t value = 255; + + GpioParseError err = gpio_command_parse(args, &num, &value); + + if(ERR_CMD_SYNTAX == err) { + cli_print_usage("gpio mode", " <0|1>", string_get_cstr(args)); + return; + } else if(ERR_PIN == err) { + gpio_print_pins(); + return; + } else if(ERR_VALUE == err) { + printf("Value is invalid. Enter 1 for input or 0 for output"); + return; + } + + if(cli_command_gpio_pins[num].debug) { + printf( + "Changeing this pin mode may damage hardware. Are you sure you want to continue? (y/n)?\r\n"); + char c = cli_getc(cli); + if(c != 'y' && c != 'Y') { + printf("Cancelled.\r\n"); + return; + } + } + + if(value == 1) { // output + furi_hal_gpio_write(cli_command_gpio_pins[num].pin, false); + furi_hal_gpio_init_simple(cli_command_gpio_pins[num].pin, GpioModeOutputPushPull); + printf("Pin %s is now an output (low)", cli_command_gpio_pins[num].name); + } else { // input + furi_hal_gpio_init_simple(cli_command_gpio_pins[num].pin, GpioModeInput); + printf("Pin %s is now an input", cli_command_gpio_pins[num].name); + } +} + +void cli_command_gpio_read(Cli* cli, string_t args, void* context) { + UNUSED(cli); + UNUSED(context); + + size_t num = 0; + if(!pin_name_to_int(args, &num)) { + gpio_print_pins(); + return; + } + + if(LL_GPIO_MODE_INPUT != + LL_GPIO_GetPinMode( + cli_command_gpio_pins[num].pin->port, cli_command_gpio_pins[num].pin->pin)) { + printf("Err: pin %s is not set as an input.", cli_command_gpio_pins[num].name); + return; + } + + uint8_t val = !!furi_hal_gpio_read(cli_command_gpio_pins[num].pin); + + printf("Pin %s <= %u", cli_command_gpio_pins[num].name, val); +} + +void cli_command_gpio_set(Cli* cli, string_t args, void* context) { + UNUSED(context); + + size_t num = 0; + uint8_t value = 0; + GpioParseError err = gpio_command_parse(args, &num, &value); + + if(ERR_CMD_SYNTAX == err) { + cli_print_usage("gpio set", " <0|1>", string_get_cstr(args)); + return; + } else if(ERR_PIN == err) { + gpio_print_pins(); + return; + } else if(ERR_VALUE == err) { + printf("Value is invalid. Enter 1 for high or 0 for low"); + return; + } + + if(LL_GPIO_MODE_OUTPUT != + LL_GPIO_GetPinMode( + cli_command_gpio_pins[num].pin->port, cli_command_gpio_pins[num].pin->pin)) { + printf("Err: pin %s is not set as an output.", cli_command_gpio_pins[num].name); + return; + } + + // Extra check if debug pins used + if(cli_command_gpio_pins[num].debug) { + printf( + "Setting this pin may damage hardware. Are you sure you want to continue? (y/n)?\r\n"); + char c = cli_getc(cli); + if(c != 'y' && c != 'Y') { + printf("Cancelled.\r\n"); + return; + } + } + + furi_hal_gpio_write(cli_command_gpio_pins[num].pin, !!value); + printf("Pin %s => %u", cli_command_gpio_pins[num].name, !!value); +} + +void cli_command_gpio(Cli* cli, string_t args, void* context) { + string_t cmd; + string_init(cmd); + + do { + if(!args_read_string_and_trim(args, cmd)) { + cli_command_gpio_print_usage(); + break; + } + + if(string_cmp_str(cmd, "mode") == 0) { + cli_command_gpio_mode(cli, args, context); + break; + } + + if(string_cmp_str(cmd, "set") == 0) { + cli_command_gpio_set(cli, args, context); + break; + } + + if(string_cmp_str(cmd, "read") == 0) { + cli_command_gpio_read(cli, args, context); + break; + } + + cli_command_gpio_print_usage(); + } while(false); + + string_clear(cmd); +} diff --git a/applications/cli/cli_command_gpio.h b/applications/cli/cli_command_gpio.h new file mode 100644 index 00000000000..c9b908a0853 --- /dev/null +++ b/applications/cli/cli_command_gpio.h @@ -0,0 +1,5 @@ +#pragma once + +#include "cli_i.h" + +void cli_command_gpio(Cli* cli, string_t args, void* context); diff --git a/applications/cli/cli_commands.c b/applications/cli/cli_commands.c index 2d059bd3ffe..8271ab31c8d 100644 --- a/applications/cli/cli_commands.c +++ b/applications/cli/cli_commands.c @@ -1,6 +1,7 @@ #include "cli_commands.h" +#include "cli_command_gpio.h" + #include -#include #include #include #include @@ -248,101 +249,6 @@ void cli_command_led(Cli* cli, string_t args, void* context) { furi_record_close("notification"); } -void cli_command_gpio_set(Cli* cli, string_t args, void* context) { - UNUSED(context); - char pin_names[][4] = { - "PC0", - "PC1", - "PC3", - "PB2", - "PB3", - "PA4", - "PA6", - "PA7", -#ifdef FURI_DEBUG - "PA0", - "PB7", - "PB8", - "PB9" -#endif - }; - GpioPin gpio[] = { - {.port = GPIOC, .pin = LL_GPIO_PIN_0}, - {.port = GPIOC, .pin = LL_GPIO_PIN_1}, - {.port = GPIOC, .pin = LL_GPIO_PIN_3}, - {.port = GPIOB, .pin = LL_GPIO_PIN_2}, - {.port = GPIOB, .pin = LL_GPIO_PIN_3}, - {.port = GPIOA, .pin = LL_GPIO_PIN_4}, - {.port = GPIOA, .pin = LL_GPIO_PIN_6}, - {.port = GPIOA, .pin = LL_GPIO_PIN_7}, -#ifdef FURI_DEBUG - {.port = GPIOA, .pin = LL_GPIO_PIN_0}, // IR_RX (PA0) - {.port = GPIOB, .pin = LL_GPIO_PIN_7}, // UART RX (PB7) - {.port = GPIOB, .pin = LL_GPIO_PIN_8}, // SPEAKER (PB8) - {.port = GPIOB, .pin = LL_GPIO_PIN_9}, // IR_TX (PB9) -#endif - }; - uint8_t num = 0; - bool pin_found = false; - - // Get first word as pin name - string_t pin_name; - string_init(pin_name); - size_t ws = string_search_char(args, ' '); - if(ws == STRING_FAILURE) { - cli_print_usage("gpio_set", " <0|1>", string_get_cstr(args)); - string_clear(pin_name); - return; - } else { - string_set_n(pin_name, args, 0, ws); - string_right(args, ws); - string_strim(args); - } - // Search correct pin name - for(num = 0; num < sizeof(pin_names) / sizeof(char*); num++) { - if(!string_cmp(pin_name, pin_names[num])) { - pin_found = true; - break; - } - } - if(!pin_found) { - printf("Wrong pin name. Available pins: "); - for(uint8_t i = 0; i < sizeof(pin_names) / sizeof(char*); i++) { - printf("%s ", pin_names[i]); - } - string_clear(pin_name); - return; - } - string_clear(pin_name); - // Read "0" or "1" as second argument to set or reset pin - if(!string_cmp(args, "0")) { - LL_GPIO_SetPinMode(gpio[num].port, gpio[num].pin, LL_GPIO_MODE_OUTPUT); - LL_GPIO_SetPinOutputType(gpio[num].port, gpio[num].pin, LL_GPIO_OUTPUT_PUSHPULL); - LL_GPIO_ResetOutputPin(gpio[num].port, gpio[num].pin); - } else if(!string_cmp(args, "1")) { -#ifdef FURI_DEBUG - if(num == 8) { // PA0 - printf( - "Setting PA0 pin HIGH with TSOP connected can damage IR receiver. Are you sure you want to continue? (y/n)?\r\n"); - char c = cli_getc(cli); - if(c != 'y' && c != 'Y') { - printf("Cancelled.\r\n"); - return; - } - } -#else - UNUSED(cli); -#endif - - LL_GPIO_SetPinMode(gpio[num].port, gpio[num].pin, LL_GPIO_MODE_OUTPUT); - LL_GPIO_SetPinOutputType(gpio[num].port, gpio[num].pin, LL_GPIO_OUTPUT_PUSHPULL); - LL_GPIO_SetOutputPin(gpio[num].port, gpio[num].pin); - } else { - printf("Wrong 2nd argument. Use \"1\" to set, \"0\" to reset"); - } - return; -} - void cli_command_ps(Cli* cli, string_t args, void* context) { UNUSED(cli); UNUSED(args); @@ -424,6 +330,6 @@ void cli_commands_init(Cli* cli) { cli_add_command(cli, "vibro", CliCommandFlagDefault, cli_command_vibro, NULL); cli_add_command(cli, "led", CliCommandFlagDefault, cli_command_led, NULL); - cli_add_command(cli, "gpio_set", CliCommandFlagDefault, cli_command_gpio_set, NULL); + cli_add_command(cli, "gpio", CliCommandFlagDefault, cli_command_gpio, NULL); cli_add_command(cli, "i2c", CliCommandFlagDefault, cli_command_i2c, NULL); -} \ No newline at end of file +} diff --git a/applications/debug_tools/blink_test.c b/applications/debug_tools/blink_test.c index 2efaa4dabb2..68139a431ce 100644 --- a/applications/debug_tools/blink_test.c +++ b/applications/debug_tools/blink_test.c @@ -1,3 +1,4 @@ +#include "furi/common_defines.h" #include #include @@ -6,8 +7,6 @@ #include -#define BLINK_COLOR_COUNT 7 - typedef enum { BlinkEventTypeTick, BlinkEventTypeInput, @@ -18,6 +17,42 @@ typedef struct { InputEvent input; } BlinkEvent; +static const NotificationSequence blink_test_sequence_hw_blink_start_red = { + &message_blink_start_10, + &message_blink_set_color_red, + &message_do_not_reset, + NULL, +}; + +static const NotificationSequence blink_test_sequence_hw_blink_green = { + &message_blink_set_color_green, + NULL, +}; + +static const NotificationSequence blink_test_sequence_hw_blink_blue = { + &message_blink_set_color_blue, + NULL, +}; + +static const NotificationSequence blink_test_sequence_hw_blink_stop = { + &message_blink_stop, + NULL, +}; + +static const NotificationSequence* blink_test_colors[] = { + &sequence_blink_red_100, + &sequence_blink_green_100, + &sequence_blink_blue_100, + &sequence_blink_yellow_100, + &sequence_blink_cyan_100, + &sequence_blink_magenta_100, + &sequence_blink_white_100, + &blink_test_sequence_hw_blink_start_red, + &blink_test_sequence_hw_blink_green, + &blink_test_sequence_hw_blink_blue, + &blink_test_sequence_hw_blink_stop, +}; + static void blink_test_update(void* ctx) { furi_assert(ctx); osMessageQueueId_t event_queue = ctx; @@ -58,16 +93,6 @@ int32_t blink_test_app(void* p) { NotificationApp* notifications = furi_record_open("notification"); - const NotificationSequence* colors[BLINK_COLOR_COUNT] = { - &sequence_blink_red_100, - &sequence_blink_green_100, - &sequence_blink_blue_100, - &sequence_blink_yellow_100, - &sequence_blink_cyan_100, - &sequence_blink_magenta_100, - &sequence_blink_white_100, - }; - uint8_t state = 0; BlinkEvent event; @@ -78,14 +103,16 @@ int32_t blink_test_app(void* p) { break; } } else { - notification_message(notifications, colors[state]); + notification_message(notifications, blink_test_colors[state]); state++; - if(state >= BLINK_COLOR_COUNT) { + if(state >= COUNT_OF(blink_test_colors)) { state = 0; } } } + notification_message(notifications, &blink_test_sequence_hw_blink_stop); + osTimerDelete(timer); gui_remove_view_port(gui, view_port); diff --git a/applications/debug_tools/usb_test.c b/applications/debug_tools/usb_test.c index 44a17ceb84b..a4f42f4708e 100644 --- a/applications/debug_tools/usb_test.c +++ b/applications/debug_tools/usb_test.c @@ -42,8 +42,8 @@ void usb_test_submenu_callback(void* context, uint32_t index) { } else if(index == UsbTestSubmenuIndexHidWithParams) { app->hid_cfg.vid = 0x1234; app->hid_cfg.pid = 0xabcd; - strncpy(app->hid_cfg.manuf, "WEN", sizeof(app->hid_cfg.manuf)); - strncpy(app->hid_cfg.product, "FLIP", sizeof(app->hid_cfg.product)); + strlcpy(app->hid_cfg.manuf, "WEN", sizeof(app->hid_cfg.manuf)); + strlcpy(app->hid_cfg.product, "FLIP", sizeof(app->hid_cfg.product)); furi_hal_usb_set_config(&usb_hid, &app->hid_cfg); } else if(index == UsbTestSubmenuIndexHidU2F) { furi_hal_usb_set_config(&usb_hid_u2f, NULL); diff --git a/applications/gui/modules/file_browser_worker.c b/applications/gui/modules/file_browser_worker.c index 93baba0089b..4931cb26838 100644 --- a/applications/gui/modules/file_browser_worker.c +++ b/applications/gui/modules/file_browser_worker.c @@ -22,10 +22,13 @@ typedef enum { WorkerEvtLoad = (1 << 1), WorkerEvtFolderEnter = (1 << 2), WorkerEvtFolderExit = (1 << 3), + WorkerEvtFolderRefresh = (1 << 4), + WorkerEvtConfigChange = (1 << 5), } WorkerEvtFlags; -#define WORKER_FLAGS_ALL \ - (WorkerEvtStop | WorkerEvtLoad | WorkerEvtFolderEnter | WorkerEvtFolderExit) +#define WORKER_FLAGS_ALL \ + (WorkerEvtStop | WorkerEvtLoad | WorkerEvtFolderEnter | WorkerEvtFolderExit | \ + WorkerEvtFolderRefresh | WorkerEvtConfigChange) ARRAY_DEF(idx_last_array, int32_t) @@ -253,19 +256,25 @@ static int32_t browser_worker(void* context) { string_init_set_str(path, BROWSER_ROOT); browser->item_sel_idx = -1; - // If start path is a path to the file - try finding index of this file in a folder string_t filename; string_init(filename); - if(browser_path_is_file(browser->path_next)) { - path_extract_filename(browser->path_next, filename, false); - } - osThreadFlagsSet(furi_thread_get_thread_id(browser->thread), WorkerEvtFolderEnter); + osThreadFlagsSet(furi_thread_get_thread_id(browser->thread), WorkerEvtConfigChange); while(1) { uint32_t flags = osThreadFlagsWait(WORKER_FLAGS_ALL, osFlagsWaitAny, osWaitForever); furi_assert((flags & osFlagsError) == 0); + if(flags & WorkerEvtConfigChange) { + // If start path is a path to the file - try finding index of this file in a folder + if(browser_path_is_file(browser->path_next)) { + path_extract_filename(browser->path_next, filename, false); + } + idx_last_array_reset(browser->idx_last); + + osThreadFlagsSet(furi_thread_get_thread_id(browser->thread), WorkerEvtFolderEnter); + } + if(flags & WorkerEvtFolderEnter) { string_set(path, browser->path_next); bool is_root = browser_folder_check_and_switch(path); @@ -304,6 +313,23 @@ static int32_t browser_worker(void* context) { } } + if(flags & WorkerEvtFolderRefresh) { + bool is_root = browser_folder_check_and_switch(path); + + int32_t file_idx = 0; + string_reset(filename); + browser_folder_init(browser, path, filename, &items_cnt, &file_idx); + FURI_LOG_D( + TAG, + "Refresh folder: %s items: %u idx: %d", + string_get_cstr(path), + items_cnt, + browser->item_sel_idx); + if(browser->folder_cb) { + browser->folder_cb(browser->cb_ctx, items_cnt, browser->item_sel_idx, is_root); + } + } + if(flags & WorkerEvtLoad) { FURI_LOG_D(TAG, "Load offset: %u cnt: %u", browser->load_offset, browser->load_count); browser_folder_load(browser, path, browser->load_offset, browser->load_count); @@ -388,6 +414,18 @@ void file_browser_worker_set_long_load_callback( browser->long_load_cb = cb; } +void file_browser_worker_set_config( + BrowserWorker* browser, + string_t path, + const char* filter_ext, + bool skip_assets) { + furi_assert(browser); + string_set(browser->path_next, path); + string_set_str(browser->filter_extension, filter_ext); + browser->skip_assets = skip_assets; + osThreadFlagsSet(furi_thread_get_thread_id(browser->thread), WorkerEvtConfigChange); +} + void file_browser_worker_folder_enter(BrowserWorker* browser, string_t path, int32_t item_idx) { furi_assert(browser); string_set(browser->path_next, path); @@ -400,6 +438,12 @@ void file_browser_worker_folder_exit(BrowserWorker* browser) { osThreadFlagsSet(furi_thread_get_thread_id(browser->thread), WorkerEvtFolderExit); } +void file_browser_worker_folder_refresh(BrowserWorker* browser, int32_t item_idx) { + furi_assert(browser); + browser->item_sel_idx = item_idx; + osThreadFlagsSet(furi_thread_get_thread_id(browser->thread), WorkerEvtFolderRefresh); +} + void file_browser_worker_load(BrowserWorker* browser, uint32_t offset, uint32_t count) { furi_assert(browser); browser->load_offset = offset; diff --git a/applications/gui/modules/file_browser_worker.h b/applications/gui/modules/file_browser_worker.h index b0d360a38be..18c0b48174c 100644 --- a/applications/gui/modules/file_browser_worker.h +++ b/applications/gui/modules/file_browser_worker.h @@ -44,10 +44,18 @@ void file_browser_worker_set_long_load_callback( BrowserWorker* browser, BrowserWorkerLongLoadCallback cb); +void file_browser_worker_set_config( + BrowserWorker* browser, + string_t path, + const char* filter_ext, + bool skip_assets); + void file_browser_worker_folder_enter(BrowserWorker* browser, string_t path, int32_t item_idx); void file_browser_worker_folder_exit(BrowserWorker* browser); +void file_browser_worker_folder_refresh(BrowserWorker* browser, int32_t item_idx); + void file_browser_worker_load(BrowserWorker* browser, uint32_t offset, uint32_t count); #ifdef __cplusplus diff --git a/applications/nfc/helpers/nfc_debug_pcap.c b/applications/nfc/helpers/nfc_debug_pcap.c new file mode 100644 index 00000000000..c54dbedd664 --- /dev/null +++ b/applications/nfc/helpers/nfc_debug_pcap.c @@ -0,0 +1,99 @@ +#include "nfc_debug_pcap.h" + +#include + +#define TAG "NfcDebugPcap" + +#define PCAP_MAGIC 0xa1b2c3d4 +#define PCAP_MAJOR 2 +#define PCAP_MINOR 4 +#define DLT_ISO_14443 264 + +#define DATA_PICC_TO_PCD 0xFF +#define DATA_PCD_TO_PICC 0xFE +#define DATA_PICC_TO_PCD_CRC_DROPPED 0xFB +#define DATA_PCD_TO_PICC_CRC_DROPPED 0xFA + +File* nfc_debug_pcap_open(Storage* storage) { + File* file = storage_file_alloc(storage); + if(!storage_file_open(file, "/ext/nfc/debug.pcap", FSAM_WRITE, FSOM_OPEN_APPEND)) { + storage_file_free(file); + return NULL; + } + if(!storage_file_tell(file)) { + struct { + uint32_t magic; + uint16_t major, minor; + uint32_t reserved[2]; + uint32_t snaplen; + uint32_t link_type; + } __attribute__((__packed__)) pcap_hdr = { + .magic = PCAP_MAGIC, + .major = PCAP_MAJOR, + .minor = PCAP_MINOR, + .snaplen = FURI_HAL_NFC_DATA_BUFF_SIZE, + .link_type = DLT_ISO_14443, + }; + if(storage_file_write(file, &pcap_hdr, sizeof(pcap_hdr)) != sizeof(pcap_hdr)) { + FURI_LOG_E(TAG, "Failed to write pcap header"); + } + } + return file; +} + +void nfc_debug_pcap_write(Storage* storage, uint8_t event, uint8_t* data, uint16_t len) { + File* file = nfc_debug_pcap_open(storage); + if(!file) return; + + FuriHalRtcDateTime datetime; + furi_hal_rtc_get_datetime(&datetime); + + struct { + // https://wiki.wireshark.org/Development/LibpcapFileFormat#record-packet-header + uint32_t ts_sec; + uint32_t ts_usec; + uint32_t incl_len; + uint32_t orig_len; + // https://www.kaiser.cx/posts/pcap-iso14443/#_packet_data + uint8_t version; + uint8_t event; + uint16_t len; + } __attribute__((__packed__)) pkt_hdr = { + .ts_sec = furi_hal_rtc_datetime_to_timestamp(&datetime), + .ts_usec = 0, + .incl_len = len + 4, + .orig_len = len + 4, + .version = 0, + .event = event, + .len = len << 8 | len >> 8, + }; + if(storage_file_write(file, &pkt_hdr, sizeof(pkt_hdr)) != sizeof(pkt_hdr)) { + FURI_LOG_E(TAG, "Failed to write pcap packet header"); + } else if(storage_file_write(file, data, len) != len) { + FURI_LOG_E(TAG, "Failed to write pcap packet data"); + } + storage_file_free(file); +} + +void nfc_debug_pcap_write_tx(uint8_t* data, uint16_t bits, bool crc_dropped, void* context) { + uint8_t event = crc_dropped ? DATA_PCD_TO_PICC_CRC_DROPPED : DATA_PCD_TO_PICC; + nfc_debug_pcap_write(context, event, data, bits / 8); +} + +void nfc_debug_pcap_write_rx(uint8_t* data, uint16_t bits, bool crc_dropped, void* context) { + uint8_t event = crc_dropped ? DATA_PICC_TO_PCD_CRC_DROPPED : DATA_PICC_TO_PCD; + nfc_debug_pcap_write(context, event, data, bits / 8); +} + +void nfc_debug_pcap_prepare_tx_rx(FuriHalNfcTxRxContext* tx_rx, Storage* storage, bool is_picc) { + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + if(is_picc) { + tx_rx->sniff_tx = nfc_debug_pcap_write_rx; + tx_rx->sniff_rx = nfc_debug_pcap_write_tx; + } else { + tx_rx->sniff_tx = nfc_debug_pcap_write_tx; + tx_rx->sniff_rx = nfc_debug_pcap_write_rx; + } + tx_rx->sniff_context = storage; + } +} diff --git a/applications/nfc/helpers/nfc_debug_pcap.h b/applications/nfc/helpers/nfc_debug_pcap.h new file mode 100644 index 00000000000..69ba86ebb84 --- /dev/null +++ b/applications/nfc/helpers/nfc_debug_pcap.h @@ -0,0 +1,12 @@ +#pragma once + +#include +#include + +/** Prepare tx/rx context for debug pcap logging, if enabled. + * + * @param tx_rx TX/RX context to log + * @param storage Storage to log to + * @param is_picc if true, record Flipper as PICC, else PCD. + */ +void nfc_debug_pcap_prepare_tx_rx(FuriHalNfcTxRxContext* tx_rx, Storage* storage, bool is_picc); diff --git a/applications/nfc/nfc_worker.c b/applications/nfc/nfc_worker.c index 88729bf43bd..c058809e0c9 100644 --- a/applications/nfc/nfc_worker.c +++ b/applications/nfc/nfc_worker.c @@ -10,6 +10,7 @@ #include #include "helpers/nfc_mf_classic_dict.h" +#include "helpers/nfc_debug_pcap.h" #define TAG "NfcWorker" @@ -153,6 +154,7 @@ void nfc_worker_detect(NfcWorker* nfc_worker) { void nfc_worker_emulate(NfcWorker* nfc_worker) { FuriHalNfcTxRxContext tx_rx = {}; + nfc_debug_pcap_prepare_tx_rx(&tx_rx, nfc_worker->storage, true); FuriHalNfcDevData* data = &nfc_worker->dev_data->nfc_data; NfcReaderRequestData* reader_data = &nfc_worker->dev_data->reader_data; @@ -175,6 +177,7 @@ void nfc_worker_emulate(NfcWorker* nfc_worker) { void nfc_worker_read_emv_app(NfcWorker* nfc_worker) { FuriHalNfcTxRxContext tx_rx = {}; + nfc_debug_pcap_prepare_tx_rx(&tx_rx, nfc_worker->storage, false); EmvApplication emv_app = {}; NfcDeviceData* result = nfc_worker->dev_data; FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data; @@ -206,6 +209,7 @@ void nfc_worker_read_emv_app(NfcWorker* nfc_worker) { void nfc_worker_read_emv(NfcWorker* nfc_worker) { FuriHalNfcTxRxContext tx_rx = {}; + nfc_debug_pcap_prepare_tx_rx(&tx_rx, nfc_worker->storage, false); EmvApplication emv_app = {}; NfcDeviceData* result = nfc_worker->dev_data; FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data; @@ -254,6 +258,7 @@ void nfc_worker_read_emv(NfcWorker* nfc_worker) { void nfc_worker_emulate_apdu(NfcWorker* nfc_worker) { FuriHalNfcTxRxContext tx_rx = {}; + nfc_debug_pcap_prepare_tx_rx(&tx_rx, nfc_worker->storage, true); FuriHalNfcDevData params = { .uid = {0xCF, 0x72, 0xd4, 0x40}, .uid_len = 4, @@ -278,6 +283,7 @@ void nfc_worker_emulate_apdu(NfcWorker* nfc_worker) { void nfc_worker_read_mifare_ultralight(NfcWorker* nfc_worker) { FuriHalNfcTxRxContext tx_rx = {}; + nfc_debug_pcap_prepare_tx_rx(&tx_rx, nfc_worker->storage, false); MfUltralightReader reader = {}; MfUltralightData data = {}; NfcDeviceData* result = nfc_worker->dev_data; @@ -342,6 +348,7 @@ void nfc_worker_emulate_mifare_ul(NfcWorker* nfc_worker) { void nfc_worker_mifare_classic_dict_attack(NfcWorker* nfc_worker) { furi_assert(nfc_worker->callback); FuriHalNfcTxRxContext tx_rx_ctx = {}; + nfc_debug_pcap_prepare_tx_rx(&tx_rx_ctx, nfc_worker->storage, false); MfClassicAuthContext auth_ctx = {}; MfClassicReader reader = {}; uint64_t curr_key = 0; @@ -483,7 +490,8 @@ void nfc_worker_mifare_classic_dict_attack(NfcWorker* nfc_worker) { } void nfc_worker_emulate_mifare_classic(NfcWorker* nfc_worker) { - FuriHalNfcTxRxContext tx_rx; + FuriHalNfcTxRxContext tx_rx = {}; + nfc_debug_pcap_prepare_tx_rx(&tx_rx, nfc_worker->storage, true); FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data; MfClassicEmulator emulator = { .cuid = nfc_util_bytes2num(&nfc_data->uid[nfc_data->uid_len - 4], 4), @@ -511,11 +519,8 @@ void nfc_worker_emulate_mifare_classic(NfcWorker* nfc_worker) { } void nfc_worker_read_mifare_desfire(NfcWorker* nfc_worker) { - ReturnCode err; - uint8_t tx_buff[64] = {}; - uint16_t tx_len = 0; - uint8_t rx_buff[512] = {}; - uint16_t rx_len; + FuriHalNfcTxRxContext tx_rx = {}; + nfc_debug_pcap_prepare_tx_rx(&tx_rx, nfc_worker->storage, false); NfcDeviceData* result = nfc_worker->dev_data; nfc_device_data_clear(result); MifareDesfireData* data = &result->mf_df_data; @@ -540,37 +545,36 @@ void nfc_worker_read_mifare_desfire(NfcWorker* nfc_worker) { result->protocol = NfcDeviceProtocolMifareDesfire; // Get DESFire version - tx_len = mf_df_prepare_get_version(tx_buff); - err = furi_hal_nfc_exchange_full(tx_buff, tx_len, rx_buff, sizeof(rx_buff), &rx_len); - if(err != ERR_NONE) { - FURI_LOG_W(TAG, "Bad exchange getting version, err: %d", err); + tx_rx.tx_bits = 8 * mf_df_prepare_get_version(tx_rx.tx_data); + if(!furi_hal_nfc_tx_rx_full(&tx_rx)) { + FURI_LOG_W(TAG, "Bad exchange getting version"); continue; } - if(!mf_df_parse_get_version_response(rx_buff, rx_len, &data->version)) { + if(!mf_df_parse_get_version_response(tx_rx.rx_data, tx_rx.rx_bits / 8, &data->version)) { FURI_LOG_W(TAG, "Bad DESFire GET_VERSION response"); continue; } - tx_len = mf_df_prepare_get_free_memory(tx_buff); - err = furi_hal_nfc_exchange_full(tx_buff, tx_len, rx_buff, sizeof(rx_buff), &rx_len); - if(err == ERR_NONE) { + tx_rx.tx_bits = 8 * mf_df_prepare_get_free_memory(tx_rx.tx_data); + if(furi_hal_nfc_tx_rx_full(&tx_rx)) { data->free_memory = malloc(sizeof(MifareDesfireFreeMemory)); memset(data->free_memory, 0, sizeof(MifareDesfireFreeMemory)); - if(!mf_df_parse_get_free_memory_response(rx_buff, rx_len, data->free_memory)) { + if(!mf_df_parse_get_free_memory_response( + tx_rx.rx_data, tx_rx.rx_bits / 8, data->free_memory)) { FURI_LOG_D(TAG, "Bad DESFire GET_FREE_MEMORY response (normal for pre-EV1 cards)"); free(data->free_memory); data->free_memory = NULL; } } - tx_len = mf_df_prepare_get_key_settings(tx_buff); - err = furi_hal_nfc_exchange_full(tx_buff, tx_len, rx_buff, sizeof(rx_buff), &rx_len); - if(err != ERR_NONE) { - FURI_LOG_D(TAG, "Bad exchange getting key settings, err: %d", err); + tx_rx.tx_bits = 8 * mf_df_prepare_get_key_settings(tx_rx.tx_data); + if(!furi_hal_nfc_tx_rx_full(&tx_rx)) { + FURI_LOG_D(TAG, "Bad exchange getting key settings"); } else { data->master_key_settings = malloc(sizeof(MifareDesfireKeySettings)); memset(data->master_key_settings, 0, sizeof(MifareDesfireKeySettings)); - if(!mf_df_parse_get_key_settings_response(rx_buff, rx_len, data->master_key_settings)) { + if(!mf_df_parse_get_key_settings_response( + tx_rx.rx_data, tx_rx.rx_bits / 8, data->master_key_settings)) { FURI_LOG_W(TAG, "Bad DESFire GET_KEY_SETTINGS response"); free(data->master_key_settings); data->master_key_settings = NULL; @@ -580,17 +584,16 @@ void nfc_worker_read_mifare_desfire(NfcWorker* nfc_worker) { MifareDesfireKeyVersion** key_version_head = &data->master_key_settings->key_version_head; for(uint8_t key_id = 0; key_id < data->master_key_settings->max_keys; key_id++) { - tx_len = mf_df_prepare_get_key_version(tx_buff, key_id); - err = - furi_hal_nfc_exchange_full(tx_buff, tx_len, rx_buff, sizeof(rx_buff), &rx_len); - if(err != ERR_NONE) { - FURI_LOG_W(TAG, "Bad exchange getting key version, err: %d", err); + tx_rx.tx_bits = 8 * mf_df_prepare_get_key_version(tx_rx.tx_data, key_id); + if(!furi_hal_nfc_tx_rx_full(&tx_rx)) { + FURI_LOG_W(TAG, "Bad exchange getting key version"); continue; } MifareDesfireKeyVersion* key_version = malloc(sizeof(MifareDesfireKeyVersion)); memset(key_version, 0, sizeof(MifareDesfireKeyVersion)); key_version->id = key_id; - if(!mf_df_parse_get_key_version_response(rx_buff, rx_len, key_version)) { + if(!mf_df_parse_get_key_version_response( + tx_rx.rx_data, tx_rx.rx_bits / 8, key_version)) { FURI_LOG_W(TAG, "Bad DESFire GET_KEY_VERSION response"); free(key_version); continue; @@ -600,31 +603,31 @@ void nfc_worker_read_mifare_desfire(NfcWorker* nfc_worker) { } } - tx_len = mf_df_prepare_get_application_ids(tx_buff); - err = furi_hal_nfc_exchange_full(tx_buff, tx_len, rx_buff, sizeof(rx_buff), &rx_len); - if(err != ERR_NONE) { - FURI_LOG_W(TAG, "Bad exchange getting application IDs, err: %d", err); + tx_rx.tx_bits = 8 * mf_df_prepare_get_application_ids(tx_rx.tx_data); + if(!furi_hal_nfc_tx_rx_full(&tx_rx)) { + FURI_LOG_W(TAG, "Bad exchange getting application IDs"); } else { - if(!mf_df_parse_get_application_ids_response(rx_buff, rx_len, &data->app_head)) { + if(!mf_df_parse_get_application_ids_response( + tx_rx.rx_data, tx_rx.rx_bits / 8, &data->app_head)) { FURI_LOG_W(TAG, "Bad DESFire GET_APPLICATION_IDS response"); } } for(MifareDesfireApplication* app = data->app_head; app; app = app->next) { - tx_len = mf_df_prepare_select_application(tx_buff, app->id); - err = furi_hal_nfc_exchange_full(tx_buff, tx_len, rx_buff, sizeof(rx_buff), &rx_len); - if(!mf_df_parse_select_application_response(rx_buff, rx_len)) { - FURI_LOG_W(TAG, "Bad exchange selecting application, err: %d", err); + tx_rx.tx_bits = 8 * mf_df_prepare_select_application(tx_rx.tx_data, app->id); + if(!furi_hal_nfc_tx_rx_full(&tx_rx) || + !mf_df_parse_select_application_response(tx_rx.rx_data, tx_rx.rx_bits / 8)) { + FURI_LOG_W(TAG, "Bad exchange selecting application"); continue; } - tx_len = mf_df_prepare_get_key_settings(tx_buff); - err = furi_hal_nfc_exchange_full(tx_buff, tx_len, rx_buff, sizeof(rx_buff), &rx_len); - if(err != ERR_NONE) { - FURI_LOG_W(TAG, "Bad exchange getting key settings, err: %d", err); + tx_rx.tx_bits = 8 * mf_df_prepare_get_key_settings(tx_rx.tx_data); + if(!furi_hal_nfc_tx_rx_full(&tx_rx)) { + FURI_LOG_W(TAG, "Bad exchange getting key settings"); } else { app->key_settings = malloc(sizeof(MifareDesfireKeySettings)); memset(app->key_settings, 0, sizeof(MifareDesfireKeySettings)); - if(!mf_df_parse_get_key_settings_response(rx_buff, rx_len, app->key_settings)) { + if(!mf_df_parse_get_key_settings_response( + tx_rx.rx_data, tx_rx.rx_bits / 8, app->key_settings)) { FURI_LOG_W(TAG, "Bad DESFire GET_KEY_SETTINGS response"); free(app->key_settings); app->key_settings = NULL; @@ -633,17 +636,16 @@ void nfc_worker_read_mifare_desfire(NfcWorker* nfc_worker) { MifareDesfireKeyVersion** key_version_head = &app->key_settings->key_version_head; for(uint8_t key_id = 0; key_id < app->key_settings->max_keys; key_id++) { - tx_len = mf_df_prepare_get_key_version(tx_buff, key_id); - err = furi_hal_nfc_exchange_full( - tx_buff, tx_len, rx_buff, sizeof(rx_buff), &rx_len); - if(err != ERR_NONE) { - FURI_LOG_W(TAG, "Bad exchange getting key version, err: %d", err); + tx_rx.tx_bits = 8 * mf_df_prepare_get_key_version(tx_rx.tx_data, key_id); + if(!furi_hal_nfc_tx_rx_full(&tx_rx)) { + FURI_LOG_W(TAG, "Bad exchange getting key version"); continue; } MifareDesfireKeyVersion* key_version = malloc(sizeof(MifareDesfireKeyVersion)); memset(key_version, 0, sizeof(MifareDesfireKeyVersion)); key_version->id = key_id; - if(!mf_df_parse_get_key_version_response(rx_buff, rx_len, key_version)) { + if(!mf_df_parse_get_key_version_response( + tx_rx.rx_data, tx_rx.rx_bits / 8, key_version)) { FURI_LOG_W(TAG, "Bad DESFire GET_KEY_VERSION response"); free(key_version); continue; @@ -653,48 +655,45 @@ void nfc_worker_read_mifare_desfire(NfcWorker* nfc_worker) { } } - tx_len = mf_df_prepare_get_file_ids(tx_buff); - err = furi_hal_nfc_exchange_full(tx_buff, tx_len, rx_buff, sizeof(rx_buff), &rx_len); - if(err != ERR_NONE) { - FURI_LOG_W(TAG, "Bad exchange getting file IDs, err: %d", err); + tx_rx.tx_bits = 8 * mf_df_prepare_get_file_ids(tx_rx.tx_data); + if(!furi_hal_nfc_tx_rx_full(&tx_rx)) { + FURI_LOG_W(TAG, "Bad exchange getting file IDs"); } else { - if(!mf_df_parse_get_file_ids_response(rx_buff, rx_len, &app->file_head)) { + if(!mf_df_parse_get_file_ids_response( + tx_rx.rx_data, tx_rx.rx_bits / 8, &app->file_head)) { FURI_LOG_W(TAG, "Bad DESFire GET_FILE_IDS response"); } } for(MifareDesfireFile* file = app->file_head; file; file = file->next) { - tx_len = mf_df_prepare_get_file_settings(tx_buff, file->id); - err = - furi_hal_nfc_exchange_full(tx_buff, tx_len, rx_buff, sizeof(rx_buff), &rx_len); - if(err != ERR_NONE) { - FURI_LOG_W(TAG, "Bad exchange getting file settings, err: %d", err); + tx_rx.tx_bits = 8 * mf_df_prepare_get_file_settings(tx_rx.tx_data, file->id); + if(!furi_hal_nfc_tx_rx_full(&tx_rx)) { + FURI_LOG_W(TAG, "Bad exchange getting file settings"); continue; } - if(!mf_df_parse_get_file_settings_response(rx_buff, rx_len, file)) { + if(!mf_df_parse_get_file_settings_response( + tx_rx.rx_data, tx_rx.rx_bits / 8, file)) { FURI_LOG_W(TAG, "Bad DESFire GET_FILE_SETTINGS response"); continue; } switch(file->type) { case MifareDesfireFileTypeStandard: case MifareDesfireFileTypeBackup: - tx_len = mf_df_prepare_read_data(tx_buff, file->id, 0, 0); + tx_rx.tx_bits = 8 * mf_df_prepare_read_data(tx_rx.tx_data, file->id, 0, 0); break; case MifareDesfireFileTypeValue: - tx_len = mf_df_prepare_get_value(tx_buff, file->id); + tx_rx.tx_bits = 8 * mf_df_prepare_get_value(tx_rx.tx_data, file->id); break; case MifareDesfireFileTypeLinearRecord: case MifareDesfireFileTypeCyclicRecord: - tx_len = mf_df_prepare_read_records(tx_buff, file->id, 0, 0); + tx_rx.tx_bits = 8 * mf_df_prepare_read_records(tx_rx.tx_data, file->id, 0, 0); break; } - err = - furi_hal_nfc_exchange_full(tx_buff, tx_len, rx_buff, sizeof(rx_buff), &rx_len); - if(err != ERR_NONE) { - FURI_LOG_W(TAG, "Bad exchange reading file %d, err: %d", file->id, err); + if(!furi_hal_nfc_tx_rx_full(&tx_rx)) { + FURI_LOG_W(TAG, "Bad exchange reading file %d", file->id); continue; } - if(!mf_df_parse_read_data_response(rx_buff, rx_len, file)) { + if(!mf_df_parse_read_data_response(tx_rx.rx_data, tx_rx.rx_bits / 8, file)) { FURI_LOG_W(TAG, "Bad response reading file %d", file->id); continue; } diff --git a/applications/notification/notification.h b/applications/notification/notification.h index 4cb9dc5e020..14ca0ac2bb9 100644 --- a/applications/notification/notification.h +++ b/applications/notification/notification.h @@ -1,6 +1,7 @@ #pragma once #include "stdint.h" #include "stdbool.h" +#include #ifdef __cplusplus extern "C" { @@ -30,9 +31,16 @@ typedef struct { float display_brightness; } NotificationMessageDataForcedSettings; +typedef struct { + uint16_t on_time; + uint16_t period; + Light color; +} NotificationMessageDataLedBlink; + typedef union { NotificationMessageDataSound sound; NotificationMessageDataLed led; + NotificationMessageDataLedBlink led_blink; NotificationMessageDataVibro vibro; NotificationMessageDataDelay delay; NotificationMessageDataForcedSettings forced_settings; @@ -48,6 +56,10 @@ typedef enum { NotificationMessageTypeLedGreen, NotificationMessageTypeLedBlue, + NotificationMessageTypeLedBlinkStart, + NotificationMessageTypeLedBlinkStop, + NotificationMessageTypeLedBlinkColor, + NotificationMessageTypeDelay, NotificationMessageTypeLedDisplayBacklight, diff --git a/applications/notification/notification_app.c b/applications/notification/notification_app.c index 7db4986d5ae..687672c9af6 100644 --- a/applications/notification/notification_app.c +++ b/applications/notification/notification_app.c @@ -1,3 +1,4 @@ +#include "furi_hal_light.h" #include #include #include @@ -17,6 +18,7 @@ static const uint8_t reset_blue_mask = 1 << 2; static const uint8_t reset_vibro_mask = 1 << 3; static const uint8_t reset_sound_mask = 1 << 4; static const uint8_t reset_display_mask = 1 << 5; +static const uint8_t reset_blink_mask = 1 << 6; void notification_vibro_on(); void notification_vibro_off(); @@ -100,6 +102,9 @@ void notification_reset_notification_layer(NotificationApp* app, uint8_t reset_m if(reset_mask & reset_blue_mask) { notification_reset_notification_led_layer(&app->led[2]); } + if(reset_mask & reset_blink_mask) { + furi_hal_light_blink_stop(); + } if(reset_mask & reset_vibro_mask) { notification_vibro_off(); } @@ -229,6 +234,24 @@ void notification_process_notification_message( app->led[2].value_last[LayerNotification] = led_values[2]; reset_mask |= reset_blue_mask; break; + case NotificationMessageTypeLedBlinkStart: + // store and send on delay or after seq + led_active = true; + furi_hal_light_blink_start( + notification_message->data.led_blink.color, + app->settings.led_brightness * 255, + notification_message->data.led_blink.on_time, + notification_message->data.led_blink.period); + reset_mask |= reset_blink_mask; + break; + case NotificationMessageTypeLedBlinkColor: + led_active = true; + furi_hal_light_blink_set_color(notification_message->data.led_blink.color); + break; + case NotificationMessageTypeLedBlinkStop: + furi_hal_light_blink_stop(); + reset_mask &= ~reset_blink_mask; + break; case NotificationMessageTypeVibro: if(notification_message->data.vibro.on) { if(vibro_setting) notification_vibro_on(); diff --git a/applications/notification/notification_messages.c b/applications/notification/notification_messages.c index 5a95556e373..b14c08e3b46 100644 --- a/applications/notification/notification_messages.c +++ b/applications/notification/notification_messages.c @@ -1,3 +1,4 @@ +#include "furi_hal_resources.h" #include "notification.h" #include "notification_messages_notes.h" #include @@ -60,6 +61,59 @@ const NotificationMessage message_blue_0 = { .data.led.value = 0x00, }; +const NotificationMessage message_blink_start_10 = { + .type = NotificationMessageTypeLedBlinkStart, + .data.led_blink.color = 0, + .data.led_blink.on_time = 10, + .data.led_blink.period = 100, +}; + +const NotificationMessage message_blink_start_100 = { + .type = NotificationMessageTypeLedBlinkStart, + .data.led_blink.color = 0, + .data.led_blink.on_time = 100, + .data.led_blink.period = 1000, +}; + +const NotificationMessage message_blink_stop = { + .type = NotificationMessageTypeLedBlinkStop, +}; + +const NotificationMessage message_blink_set_color_red = { + .type = NotificationMessageTypeLedBlinkColor, + .data.led_blink.color = LightRed, +}; + +const NotificationMessage message_blink_set_color_green = { + .type = NotificationMessageTypeLedBlinkColor, + .data.led_blink.color = LightGreen, +}; + +const NotificationMessage message_blink_set_color_blue = { + .type = NotificationMessageTypeLedBlinkColor, + .data.led_blink.color = LightBlue, +}; + +const NotificationMessage message_blink_set_color_cyan = { + .type = NotificationMessageTypeLedBlinkColor, + .data.led_blink.color = LightBlue | LightGreen, +}; + +const NotificationMessage message_blink_set_color_magenta = { + .type = NotificationMessageTypeLedBlinkColor, + .data.led_blink.color = LightBlue | LightRed, +}; + +const NotificationMessage message_blink_set_color_yellow = { + .type = NotificationMessageTypeLedBlinkColor, + .data.led_blink.color = LightGreen | LightRed, +}; + +const NotificationMessage message_blink_set_color_white = { + .type = NotificationMessageTypeLedBlinkColor, + .data.led_blink.color = LightRed | LightGreen | LightBlue, +}; + // Delay const NotificationMessage message_delay_1 = { .type = NotificationMessageTypeDelay, diff --git a/applications/notification/notification_messages.h b/applications/notification/notification_messages.h index c1ab340712b..a0ef654e7c7 100644 --- a/applications/notification/notification_messages.h +++ b/applications/notification/notification_messages.h @@ -24,6 +24,19 @@ extern const NotificationMessage message_red_0; extern const NotificationMessage message_green_0; extern const NotificationMessage message_blue_0; +// Led hardware blink control +extern const NotificationMessage message_blink_start_10; +extern const NotificationMessage message_blink_start_100; +extern const NotificationMessage message_blink_stop; + +extern const NotificationMessage message_blink_set_color_red; +extern const NotificationMessage message_blink_set_color_green; +extern const NotificationMessage message_blink_set_color_blue; +extern const NotificationMessage message_blink_set_color_cyan; +extern const NotificationMessage message_blink_set_color_magenta; +extern const NotificationMessage message_blink_set_color_yellow; +extern const NotificationMessage message_blink_set_color_white; + // Delay extern const NotificationMessage message_delay_1; extern const NotificationMessage message_delay_10; diff --git a/applications/storage/storage_cli.c b/applications/storage/storage_cli.c index 4ce91770af1..dd423cc610e 100644 --- a/applications/storage/storage_cli.c +++ b/applications/storage/storage_cli.c @@ -184,14 +184,14 @@ static void storage_cli_read(Cli* cli, string_t path) { File* file = storage_file_alloc(api); if(storage_file_open(file, string_get_cstr(path), FSAM_READ, FSOM_OPEN_EXISTING)) { - const uint16_t size_to_read = 128; + const uint16_t buffer_size = 128; uint16_t read_size = 0; - uint8_t* data = malloc(read_size); + uint8_t* data = malloc(buffer_size); printf("Size: %lu\r\n", (uint32_t)storage_file_size(file)); do { - read_size = storage_file_read(file, data, size_to_read); + read_size = storage_file_read(file, data, buffer_size); for(uint16_t i = 0; i < read_size; i++) { printf("%c", data[i]); } @@ -449,15 +449,15 @@ static void storage_cli_md5(Cli* cli, string_t path) { File* file = storage_file_alloc(api); if(storage_file_open(file, string_get_cstr(path), FSAM_READ, FSOM_OPEN_EXISTING)) { - const uint16_t size_to_read = 512; + const uint16_t buffer_size = 512; const uint8_t hash_size = 16; - uint8_t* data = malloc(size_to_read); + uint8_t* data = malloc(buffer_size); uint8_t* hash = malloc(sizeof(uint8_t) * hash_size); md5_context* md5_ctx = malloc(sizeof(md5_context)); md5_starts(md5_ctx); while(true) { - uint16_t read_size = storage_file_read(file, data, size_to_read); + uint16_t read_size = storage_file_read(file, data, buffer_size); if(read_size == 0) break; md5_update(md5_ctx, data, read_size); } diff --git a/applications/subghz/scenes/subghz_scene_receiver_config.c b/applications/subghz/scenes/subghz_scene_receiver_config.c index 7c2d031f0c6..8789a2434a5 100644 --- a/applications/subghz/scenes/subghz_scene_receiver_config.c +++ b/applications/subghz/scenes/subghz_scene_receiver_config.c @@ -43,14 +43,14 @@ uint8_t subghz_scene_receiver_config_uint32_value_index( uint8_t subghz_scene_receiver_config_next_frequency(const uint32_t value, void* context) { furi_assert(context); SubGhz* subghz = context; - int64_t last_value = INT64_MIN; uint8_t index = 0; for(uint8_t i = 0; i < subghz_setting_get_frequency_count(subghz->setting); i++) { - if((value >= last_value) && (value <= subghz_setting_get_frequency(subghz->setting, i))) { + if(value == subghz_setting_get_frequency(subghz->setting, i)) { index = i; break; + } else { + index = subghz_setting_get_frequency_default_index(subghz->setting); } - last_value = subghz_setting_get_frequency(subghz->setting, i); } return index; } diff --git a/applications/updater/util/update_task.c b/applications/updater/util/update_task.c index 1238bdb0f83..fd2344c10d1 100644 --- a/applications/updater/util/update_task.c +++ b/applications/updater/util/update_task.c @@ -26,7 +26,7 @@ static const char* update_task_stage_descr[] = { [UpdateTaskStageResourcesUpdate] = "Updating resources", [UpdateTaskStageCompleted] = "Restarting...", [UpdateTaskStageError] = "Error", - [UpdateTaskStageOBError] = "OB Err, report", + [UpdateTaskStageOBError] = "OB, report", }; typedef struct { diff --git a/assets/copro.mk b/assets/copro.mk index 004bd8d94d5..eb9b83b7e19 100644 --- a/assets/copro.mk +++ b/assets/copro.mk @@ -1,8 +1,10 @@ COPRO_CUBE_VERSION := 1.13.3 COPRO_MCU_FAMILY := STM32WB5x -COPRO_STACK_BIN := stm32wb5x_BLE_Stack_light_fw.bin +COPRO_STACK_BIN ?= stm32wb5x_BLE_Stack_light_fw.bin # See __STACK_TYPE_CODES in scripts/flipper/assets/coprobin.py -COPRO_STACK_TYPE := ble_light +COPRO_STACK_TYPE ?= ble_light +COPRO_DISCLAIMER ?= +COPRO_OB_DATA ?= ob.data # Keep 0 for auto, or put a value from release_notes for chosen stack COPRO_STACK_ADDR := 0 diff --git a/documentation/OTA.md b/documentation/OTA.md index 472e81b6b76..3f4e400a3dd 100644 --- a/documentation/OTA.md +++ b/documentation/OTA.md @@ -86,20 +86,56 @@ Even if something goes wrong, Updater gives you an option to retry failed operat | | | **50** | Package has mismatching HW target | | | | **60** | Missing DFU file | | | | **80** | Missing radio firmware file | -| Checking DFU file | **2** | **0** | Error opening DFU file | -| | | **1-98** | Error reading DFU file | -| | | **99-100** | Corrupted DFU file | -| Writing flash | **3** | **0-100** | Block read/write error | -| Validating flash | **4** | **0-100** | Block read/write error | -| Checking radio FW | **5** | **0-99** | Error reading radio firmware file | +| Backing up LFS | **2** | **0-100** | FS read/write error | +| Checking radio FW | **3** | **0-99** | Error reading radio firmware file | | | | **100** | CRC mismatch | -| Uninstalling radio FW | **6** | **0** | SHCI Install command error | +| Uninstalling radio FW | **4** | **0** | SHCI Delete command error | | | | **80** | Error awaiting command status | -| Writing radio FW | **7** | **0-100** | Block read/write error | -| Installing radio FW | **8** | **0** | SHCI Install command error | +| Writing radio FW | **5** | **0-100** | Block read/write error | +| Installing radio FW | **6** | **0** | SHCI Install command error | | | | **80** | Error awaiting command status | -| Radio is updating | **9** | **10** | Error waiting for operation completion | -| Validating opt. bytes | **10** | **yy** | Option byte code | -| Backing up LFS | **11** | **0-100** | Block read/write error | -| Restoring LFS | **12** | **0-100** | Block read/write error | +| Radio is updating | **7** | **10** | Error waiting for operation completion | +| Validating opt. bytes | **8** | **yy** | Option byte code | +| Checking DFU file | **9** | **0** | Error opening DFU file | +| | | **1-98** | Error reading DFU file | +| | | **99-100** | Corrupted DFU file | +| Writing flash | **10** | **0-100** | Block read/write error | +| Validating flash | **11** | **0-100** | Block read/write error | +| Restoring LFS | **12** | **0-100** | FS read/write error | | Updating resources | **13** | **0-100** | SD card read/write error | + + +# Building update packages + + +## Full package + +To build a basic update package, run `make COMPACT=1 DEBUG=0 updater_package` + + +## Customizing update bundles + +Default update packages are built with Bluetooth Light stack. +You can pick a different stack, if your firmware version supports it, and build a bundle with it passing stack type and binary name to `make`: + +`make updater_package COMPACT=1 DEBUG=0 COPRO_OB_DATA=ob_custradio.data COPRO_STACK_BIN=stm32wb5x_BLE_Stack_full_fw.bin COPRO_STACK_TYPE=ble_full` + +Note that `COPRO_OB_DATA` must point to a valid file in `scripts` folder containing reference Option Byte data matching to your radio stack type. + +In certain cases, you might have to confirm your intentions by adding `COPRO_DISCLAIMER=...` to the build command line. + + +## Building partial update packages + +You can customize package contents by calling `scripts/update.py` directly. +For example, to build a package only for installing BLE FULL stack: + +```shell +scripts/update.py generate \ + -t f7 -d r13.3_full -v "BLE FULL 13.3" \ + --stage dist/f7/flipper-z-f7-updater-*.bin \ + --radio lib/STM32CubeWB/Projects/STM32WB_Copro_Wireless_Binaries/STM32WB5x/stm32wb5x_BLE_Stack_full_fw.bin \ + --radiotype ble_full +``` + +For full list of options, check `scripts/update.py generate` help. diff --git a/firmware/targets/f7/ble_glue/gap.c b/firmware/targets/f7/ble_glue/gap.c index f2a1a96f1f5..7965d3f5e1d 100644 --- a/firmware/targets/f7/ble_glue/gap.c +++ b/firmware/targets/f7/ble_glue/gap.c @@ -43,11 +43,6 @@ typedef enum { GapCommandKillThread, } GapCommand; -typedef struct { - GapScanCallback callback; - void* context; -} GapScan; - // Identity root key static const uint8_t gap_irk[16] = {0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0}; @@ -56,7 +51,6 @@ static const uint8_t gap_erk[16] = {0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, 0x21, 0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, 0x21}; static Gap* gap = NULL; -static GapScan* gap_scan = NULL; static void gap_advertise_start(GapState new_state); static int32_t gap_app(void* context); @@ -169,23 +163,6 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification(void* pckt) { aci_gap_slave_security_req(event->Connection_Handle); } break; - case EVT_LE_ADVERTISING_REPORT: { - if(gap_scan) { - GapAddress address; - hci_le_advertising_report_event_rp0* evt = - (hci_le_advertising_report_event_rp0*)meta_evt->data; - for(uint8_t i = 0; i < evt->Num_Reports; i++) { - Advertising_Report_t* rep = &evt->Advertising_Report[i]; - address.type = rep->Address_Type; - // Original MAC addres is in inverted order - for(uint8_t j = 0; j < sizeof(address.mac); j++) { - address.mac[j] = rep->Address[sizeof(address.mac) - j - 1]; - } - gap_scan->callback(address, gap_scan->context); - } - } - } break; - default: break; } @@ -550,23 +527,6 @@ GapState gap_get_state() { return state; } -void gap_start_scan(GapScanCallback callback, void* context) { - furi_assert(callback); - gap_scan = malloc(sizeof(GapScan)); - gap_scan->callback = callback; - gap_scan->context = context; - // Scan interval 250 ms - hci_le_set_scan_parameters(1, 4000, 200, 0, 0); - hci_le_set_scan_enable(1, 1); -} - -void gap_stop_scan() { - furi_assert(gap_scan); - hci_le_set_scan_enable(0, 1); - free(gap_scan); - gap_scan = NULL; -} - void gap_thread_stop() { if(gap) { osMutexAcquire(gap->state_mutex, osWaitForever); diff --git a/firmware/targets/f7/ble_glue/gap.h b/firmware/targets/f7/ble_glue/gap.h index 7ad4e819613..1e207299f23 100644 --- a/firmware/targets/f7/ble_glue/gap.h +++ b/firmware/targets/f7/ble_glue/gap.h @@ -33,13 +33,6 @@ typedef struct { typedef bool (*GapEventCallback)(GapEvent event, void* context); -typedef struct { - uint8_t type; - uint8_t mac[6]; -} GapAddress; - -typedef void (*GapScanCallback)(GapAddress address, void* context); - typedef enum { GapStateUninitialized, GapStateIdle, @@ -88,10 +81,6 @@ GapState gap_get_state(); void gap_thread_stop(); -void gap_start_scan(GapScanCallback callback, void* context); - -void gap_stop_scan(); - #ifdef __cplusplus } #endif diff --git a/firmware/targets/f7/furi_hal/furi_hal_bt.c b/firmware/targets/f7/furi_hal/furi_hal_bt.c old mode 100755 new mode 100644 index 1e8690771a4..c7ee7dc23e0 --- a/firmware/targets/f7/furi_hal/furi_hal_bt.c +++ b/firmware/targets/f7/furi_hal/furi_hal_bt.c @@ -104,15 +104,18 @@ void furi_hal_bt_unlock_core2() { static bool furi_hal_bt_radio_stack_is_supported(const BleGlueC2Info* info) { bool supported = false; - if(info->StackType == INFO_STACK_TYPE_BLE_HCI) { - furi_hal_bt_stack = FuriHalBtStackHciLayer; - supported = true; - } else if(info->StackType == INFO_STACK_TYPE_BLE_LIGHT) { + if(info->StackType == INFO_STACK_TYPE_BLE_LIGHT) { if(info->VersionMajor >= FURI_HAL_BT_STACK_VERSION_MAJOR && info->VersionMinor >= FURI_HAL_BT_STACK_VERSION_MINOR) { furi_hal_bt_stack = FuriHalBtStackLight; supported = true; } + } else if(info->StackType == INFO_STACK_TYPE_BLE_FULL) { + if(info->VersionMajor >= FURI_HAL_BT_STACK_VERSION_MAJOR && + info->VersionMinor >= FURI_HAL_BT_STACK_VERSION_MINOR) { + furi_hal_bt_stack = FuriHalBtStackFull; + supported = true; + } } else { furi_hal_bt_stack = FuriHalBtStackUnknown; } @@ -168,6 +171,22 @@ FuriHalBtStack furi_hal_bt_get_radio_stack() { return furi_hal_bt_stack; } +bool furi_hal_bt_is_ble_gatt_gap_supported() { + if(furi_hal_bt_stack == FuriHalBtStackLight || furi_hal_bt_stack == FuriHalBtStackFull) { + return true; + } else { + return false; + } +} + +bool furi_hal_bt_is_testing_supported() { + if(furi_hal_bt_stack == FuriHalBtStackFull) { + return true; + } else { + return false; + } +} + bool furi_hal_bt_start_app(FuriHalBtProfile profile, GapEventCallback event_cb, void* context) { furi_assert(event_cb); furi_assert(profile < FuriHalBtProfileNumber); @@ -178,7 +197,7 @@ bool furi_hal_bt_start_app(FuriHalBtProfile profile, GapEventCallback event_cb, FURI_LOG_E(TAG, "Can't start BLE App - radio stack did not start"); break; } - if(furi_hal_bt_stack != FuriHalBtStackLight) { + if(!furi_hal_bt_is_ble_gatt_gap_supported()) { FURI_LOG_E(TAG, "Can't start Ble App - unsupported radio stack"); break; } @@ -209,7 +228,7 @@ bool furi_hal_bt_start_app(FuriHalBtProfile profile, GapEventCallback event_cb, break; } // Start selected profile services - if(furi_hal_bt_stack == FuriHalBtStackLight) { + if(furi_hal_bt_is_ble_gatt_gap_supported()) { profile_config[profile].start(); } ret = true; @@ -411,20 +430,6 @@ void furi_hal_bt_stop_rx() { aci_hal_rx_stop(); } -bool furi_hal_bt_start_scan(GapScanCallback callback, void* context) { - if(furi_hal_bt_stack != FuriHalBtStackHciLayer) { - return false; - } - gap_start_scan(callback, context); - return true; -} - -void furi_hal_bt_stop_scan() { - if(furi_hal_bt_stack == FuriHalBtStackHciLayer) { - gap_stop_scan(); - } -} - bool furi_hal_bt_ensure_c2_mode(BleGlueC2Mode mode) { BleGlueCommandResult fw_start_res = ble_glue_force_c2_mode(mode); if(fw_start_res == BleGlueCommandResultOK) { diff --git a/firmware/targets/f7/furi_hal/furi_hal_light.c b/firmware/targets/f7/furi_hal/furi_hal_light.c index 864b3c6e7be..5ff02f4a07e 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_light.c +++ b/firmware/targets/f7/furi_hal/furi_hal_light.c @@ -1,6 +1,9 @@ +#include "furi/common_defines.h" +#include "furi_hal_resources.h" #include #include #include +#include #define LED_CURRENT_RED 50 #define LED_CURRENT_GREEN 50 @@ -29,29 +32,63 @@ void furi_hal_light_init() { } void furi_hal_light_set(Light light, uint8_t value) { - uint8_t prev = 0; furi_hal_i2c_acquire(&furi_hal_i2c_handle_power); - switch(light) { - case LightRed: + if(light & LightRed) { lp5562_set_channel_value(&furi_hal_i2c_handle_power, LP5562ChannelRed, value); - break; - case LightGreen: + } + if(light & LightGreen) { lp5562_set_channel_value(&furi_hal_i2c_handle_power, LP5562ChannelGreen, value); - break; - case LightBlue: + } + if(light & LightBlue) { lp5562_set_channel_value(&furi_hal_i2c_handle_power, LP5562ChannelBlue, value); - break; - case LightBacklight: - prev = lp5562_get_channel_value(&furi_hal_i2c_handle_power, LP5562ChannelWhite); + } + if(light & LightBacklight) { + uint8_t prev = lp5562_get_channel_value(&furi_hal_i2c_handle_power, LP5562ChannelWhite); lp5562_execute_ramp( &furi_hal_i2c_handle_power, LP5562Engine1, LP5562ChannelWhite, prev, value, 100); - break; - default: - break; } furi_hal_i2c_release(&furi_hal_i2c_handle_power); } +void furi_hal_light_blink_start(Light light, uint8_t brightness, uint16_t on_time, uint16_t period) { + furi_hal_i2c_acquire(&furi_hal_i2c_handle_power); + lp5562_set_channel_src( + &furi_hal_i2c_handle_power, + LP5562ChannelRed | LP5562ChannelGreen | LP5562ChannelBlue, + LP5562Direct); + LP5562Channel led_ch = 0; + if(light & LightRed) led_ch |= LP5562ChannelRed; + if(light & LightGreen) led_ch |= LP5562ChannelGreen; + if(light & LightBlue) led_ch |= LP5562ChannelBlue; + lp5562_execute_blink( + &furi_hal_i2c_handle_power, LP5562Engine2, led_ch, on_time, period, brightness); + furi_hal_i2c_release(&furi_hal_i2c_handle_power); +} + +void furi_hal_light_blink_stop() { + furi_hal_i2c_acquire(&furi_hal_i2c_handle_power); + lp5562_set_channel_src( + &furi_hal_i2c_handle_power, + LP5562ChannelRed | LP5562ChannelGreen | LP5562ChannelBlue, + LP5562Direct); + lp5562_stop_program(&furi_hal_i2c_handle_power, LP5562Engine2); + furi_hal_i2c_release(&furi_hal_i2c_handle_power); +} + +void furi_hal_light_blink_set_color(Light light) { + furi_hal_i2c_acquire(&furi_hal_i2c_handle_power); + LP5562Channel led_ch = 0; + lp5562_set_channel_src( + &furi_hal_i2c_handle_power, + LP5562ChannelRed | LP5562ChannelGreen | LP5562ChannelBlue, + LP5562Direct); + if(light & LightRed) led_ch |= LP5562ChannelRed; + if(light & LightGreen) led_ch |= LP5562ChannelGreen; + if(light & LightBlue) led_ch |= LP5562ChannelBlue; + lp5562_set_channel_src(&furi_hal_i2c_handle_power, led_ch, LP5562Engine2); + furi_hal_i2c_release(&furi_hal_i2c_handle_power); +} + void furi_hal_light_sequence(const char* sequence) { do { if(*sequence == 'R') { diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc.c b/firmware/targets/f7/furi_hal/furi_hal_nfc.c index 00c96ec3cf5..070a33f68c9 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_nfc.c +++ b/firmware/targets/f7/furi_hal/furi_hal_nfc.c @@ -366,44 +366,6 @@ bool furi_hal_nfc_emulate_nfca( return true; } -ReturnCode furi_hal_nfc_data_exchange( - uint8_t* tx_buff, - uint16_t tx_len, - uint8_t** rx_buff, - uint16_t** rx_len, - bool deactivate) { - furi_assert(rx_buff); - furi_assert(rx_len); - - ReturnCode ret; - rfalNfcState state = RFAL_NFC_STATE_ACTIVATED; - ret = rfalNfcDataExchangeStart(tx_buff, tx_len, rx_buff, rx_len, 0, RFAL_TXRX_FLAGS_DEFAULT); - if(ret != ERR_NONE) { - return ret; - } - uint32_t start = DWT->CYCCNT; - while(state != RFAL_NFC_STATE_DATAEXCHANGE_DONE) { - rfalNfcWorker(); - state = rfalNfcGetState(); - ret = rfalNfcDataExchangeGetStatus(); - if(ret == ERR_BUSY) { - if(DWT->CYCCNT - start > 1000 * clocks_in_ms) { - ret = ERR_TIMEOUT; - break; - } - continue; - } else { - start = DWT->CYCCNT; - } - taskYIELD(); - } - if(deactivate) { - rfalNfcDeactivate(false); - rfalLowPowerModeStart(); - } - return ret; -} - static bool furi_hal_nfc_transparent_tx_rx(FuriHalNfcTxRxContext* tx_rx, uint16_t timeout_ms) { furi_assert(tx_rx->nfca_signal); @@ -576,6 +538,12 @@ bool furi_hal_nfc_tx_rx(FuriHalNfcTxRxContext* tx_rx, uint16_t timeout_ms) { FURI_LOG_E(TAG, "Failed to start data exchange"); return false; } + + if(tx_rx->sniff_tx) { + bool crc_dropped = !(flags & RFAL_TXRX_FLAGS_CRC_TX_MANUAL); + tx_rx->sniff_tx(tx_rx->tx_data, tx_rx->tx_bits, crc_dropped, tx_rx->sniff_context); + } + uint32_t start = DWT->CYCCNT; while(state != RFAL_NFC_STATE_DATAEXCHANGE_DONE) { rfalNfcWorker(); @@ -602,42 +570,42 @@ bool furi_hal_nfc_tx_rx(FuriHalNfcTxRxContext* tx_rx, uint16_t timeout_ms) { tx_rx->rx_bits = *temp_rx_bits; } + if(tx_rx->sniff_rx) { + bool crc_dropped = !(flags & RFAL_TXRX_FLAGS_CRC_RX_KEEP); + tx_rx->sniff_rx(tx_rx->rx_data, tx_rx->rx_bits, crc_dropped, tx_rx->sniff_context); + } + return true; } -ReturnCode furi_hal_nfc_exchange_full( - uint8_t* tx_buff, - uint16_t tx_len, - uint8_t* rx_buff, - uint16_t rx_cap, - uint16_t* rx_len) { - ReturnCode err; - uint8_t* part_buff; - uint16_t* part_len_bits; +bool furi_hal_nfc_tx_rx_full(FuriHalNfcTxRxContext* tx_rx) { uint16_t part_len_bytes; - err = furi_hal_nfc_data_exchange(tx_buff, tx_len, &part_buff, &part_len_bits, false); - part_len_bytes = *part_len_bits / 8; - if(part_len_bytes > rx_cap) { - return ERR_OVERRUN; + if(!furi_hal_nfc_tx_rx(tx_rx, 1000)) { + return false; } - memcpy(rx_buff, part_buff, part_len_bytes); - *rx_len = part_len_bytes; - while(err == ERR_NONE && rx_buff[0] == 0xAF) { - err = furi_hal_nfc_data_exchange(rx_buff, 1, &part_buff, &part_len_bits, false); - part_len_bytes = *part_len_bits / 8; - if(part_len_bytes > rx_cap - *rx_len) { - return ERR_OVERRUN; + while(tx_rx->rx_bits && tx_rx->rx_data[0] == 0xAF) { + FuriHalNfcTxRxContext tmp = *tx_rx; + tmp.tx_data[0] = 0xAF; + tmp.tx_bits = 8; + if(!furi_hal_nfc_tx_rx(&tmp, 1000)) { + return false; + } + part_len_bytes = tmp.rx_bits / 8; + if(part_len_bytes > FURI_HAL_NFC_DATA_BUFF_SIZE - tx_rx->rx_bits / 8) { + FURI_LOG_W(TAG, "Overrun rx buf"); + return false; } if(part_len_bytes == 0) { - return ERR_PROTO; + FURI_LOG_W(TAG, "Empty 0xAF response"); + return false; } - memcpy(rx_buff + *rx_len, part_buff + 1, part_len_bytes - 1); - *rx_buff = *part_buff; - *rx_len += part_len_bytes - 1; + memcpy(tx_rx->rx_data + tx_rx->rx_bits / 8, tmp.rx_data + 1, part_len_bytes - 1); + tx_rx->rx_data[0] = tmp.rx_data[0]; + tx_rx->rx_bits += 8 * (part_len_bytes - 1); } - return err; + return true; } void furi_hal_nfc_sleep() { diff --git a/firmware/targets/f7/furi_hal/furi_hal_resources.h b/firmware/targets/f7/furi_hal/furi_hal_resources.h index f12edcb30d4..e9d198277d0 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_resources.h +++ b/firmware/targets/f7/furi_hal/furi_hal_resources.h @@ -24,10 +24,10 @@ typedef enum { /* Light */ typedef enum { - LightRed, - LightGreen, - LightBlue, - LightBacklight, + LightRed = (1 << 0), + LightGreen = (1 << 1), + LightBlue = (1 << 2), + LightBacklight = (1 << 3), } Light; typedef struct { diff --git a/firmware/targets/furi_hal_include/furi_hal_bt.h b/firmware/targets/furi_hal_include/furi_hal_bt.h index 3a05081bd39..3f1169d1675 100644 --- a/firmware/targets/furi_hal_include/furi_hal_bt.h +++ b/firmware/targets/furi_hal_include/furi_hal_bt.h @@ -15,7 +15,7 @@ #include "furi_hal_bt_serial.h" #define FURI_HAL_BT_STACK_VERSION_MAJOR (1) -#define FURI_HAL_BT_STACK_VERSION_MINOR (13) +#define FURI_HAL_BT_STACK_VERSION_MINOR (12) #define FURI_HAL_BT_C2_START_TIMEOUT 1000 #ifdef __cplusplus @@ -24,8 +24,8 @@ extern "C" { typedef enum { FuriHalBtStackUnknown, - FuriHalBtStackHciLayer, FuriHalBtStackLight, + FuriHalBtStackFull, } FuriHalBtStack; typedef enum { @@ -58,6 +58,18 @@ bool furi_hal_bt_start_radio_stack(); */ FuriHalBtStack furi_hal_bt_get_radio_stack(); +/** Check if radio stack supports BLE GAT/GAP + * + * @return true if supported + */ +bool furi_hal_bt_is_ble_gatt_gap_supported(); + +/** Check if radio stack supports testing + * + * @return true if supported + */ +bool furi_hal_bt_is_testing_supported(); + /** Start BLE app * * @param profile FuriHalBtProfile instance @@ -206,17 +218,6 @@ float furi_hal_bt_get_rssi(); */ uint32_t furi_hal_bt_get_transmitted_packets(); -/** Start MAC addresses scan - * @note Works only with HciLayer 2nd core firmware - * - * @param callback GapScanCallback instance - * @param context pointer to context - */ -bool furi_hal_bt_start_scan(GapScanCallback callback, void* context); - -/** Stop MAC addresses scan */ -void furi_hal_bt_stop_scan(); - /** Check & switch C2 to given mode * * @param[in] mode mode to switch into diff --git a/firmware/targets/furi_hal_include/furi_hal_light.h b/firmware/targets/furi_hal_include/furi_hal_light.h index e3d244c3cbc..0e06bd4239c 100644 --- a/firmware/targets/furi_hal_include/furi_hal_light.h +++ b/firmware/targets/furi_hal_include/furi_hal_light.h @@ -24,6 +24,25 @@ void furi_hal_light_init(); */ void furi_hal_light_set(Light light, uint8_t value); +/** Start hardware LED blinking mode + * + * @param light Light + * @param brightness light brightness [0-255] + * @param on_time LED on time in ms + * @param period LED blink period in ms + */ +void furi_hal_light_blink_start(Light light, uint8_t brightness, uint16_t on_time, uint16_t period); + +/** Stop hardware LED blinking mode + */ +void furi_hal_light_blink_stop(); + +/** Set color in hardware LED blinking mode + * + * @param light Light + */ +void furi_hal_light_blink_set_color(Light light); + /** Execute sequence * * @param sequence Sequence to execute diff --git a/firmware/targets/furi_hal_include/furi_hal_nfc.h b/firmware/targets/furi_hal_include/furi_hal_nfc.h old mode 100755 new mode 100644 index 30672fb9aa7..6e438367b93 --- a/firmware/targets/furi_hal_include/furi_hal_nfc.h +++ b/firmware/targets/furi_hal_include/furi_hal_nfc.h @@ -17,7 +17,7 @@ extern "C" { #endif #define FURI_HAL_NFC_UID_MAX_LEN 10 -#define FURI_HAL_NFC_DATA_BUFF_SIZE (256) +#define FURI_HAL_NFC_DATA_BUFF_SIZE (512) #define FURI_HAL_NFC_PARITY_BUFF_SIZE (FURI_HAL_NFC_DATA_BUFF_SIZE / 8) #define FURI_HAL_NFC_TXRX_DEFAULT \ @@ -80,6 +80,9 @@ typedef struct { uint8_t sak; } FuriHalNfcDevData; +typedef void ( + *FuriHalNfcTxRxSniffCallback)(uint8_t* data, uint16_t bits, bool crc_dropped, void* context); + typedef struct { uint8_t tx_data[FURI_HAL_NFC_DATA_BUFF_SIZE]; uint8_t tx_parity[FURI_HAL_NFC_PARITY_BUFF_SIZE]; @@ -89,6 +92,10 @@ typedef struct { uint16_t rx_bits; FuriHalNfcTxRxType tx_rx_type; NfcaSignal* nfca_signal; + + FuriHalNfcTxRxSniffCallback sniff_tx; + FuriHalNfcTxRxSniffCallback sniff_rx; + void* sniff_context; } FuriHalNfcTxRxContext; /** Init nfc @@ -165,23 +172,6 @@ bool furi_hal_nfc_emulate_nfca( void* context, uint32_t timeout); -/** NFC data exchange - * - * @param tx_buff transmit buffer - * @param tx_len transmit buffer length - * @param rx_buff receive buffer - * @param rx_len receive buffer length - * @param deactivate deactivate flag - * - * @return ST ReturnCode - */ -ReturnCode furi_hal_nfc_data_exchange( - uint8_t* tx_buff, - uint16_t tx_len, - uint8_t** rx_buff, - uint16_t** rx_len, - bool deactivate); - /** NFC data exchange * * @param tx_rx_ctx FuriHalNfcTxRxContext instance @@ -192,20 +182,11 @@ bool furi_hal_nfc_tx_rx(FuriHalNfcTxRxContext* tx_rx, uint16_t timeout_ms); /** NFC data full exhange * - * @param tx_buff transmit buffer - * @param tx_len transmit buffer length - * @param rx_buff receive buffer - * @param rx_cap receive buffer capacity - * @param rx_len receive buffer length + * @param tx_rx_ctx FuriHalNfcTxRxContext instance * - * @return ST ReturnCode + * @return true on success */ -ReturnCode furi_hal_nfc_exchange_full( - uint8_t* tx_buff, - uint16_t tx_len, - uint8_t* rx_buff, - uint16_t rx_cap, - uint16_t* rx_len); +bool furi_hal_nfc_tx_rx_full(FuriHalNfcTxRxContext* tx_rx); /** NFC deactivate and start sleep */ diff --git a/lib/drivers/lp5562.c b/lib/drivers/lp5562.c index 34b67ff6220..7eef2a83c38 100644 --- a/lib/drivers/lp5562.c +++ b/lib/drivers/lp5562.c @@ -1,4 +1,5 @@ #include "lp5562.h" +#include "furi/common_defines.h" #include "lp5562_reg.h" #include @@ -79,27 +80,32 @@ uint8_t lp5562_get_channel_value(FuriHalI2cBusHandle* handle, LP5562Channel chan return value; } -static void - lp5562_set_channel_src(FuriHalI2cBusHandle* handle, LP5562Channel channel, LP5562Engine src) { +void lp5562_set_channel_src(FuriHalI2cBusHandle* handle, LP5562Channel channel, LP5562Engine src) { uint8_t reg_val = 0; uint8_t bit_offset = 0; - if(channel == LP5562ChannelRed) { - bit_offset = 4; - } else if(channel == LP5562ChannelGreen) { - bit_offset = 2; - } else if(channel == LP5562ChannelBlue) { - bit_offset = 0; - } else if(channel == LP5562ChannelWhite) { - bit_offset = 6; - } else { - return; - } + do { + if(channel & LP5562ChannelRed) { + bit_offset = 4; + channel &= ~LP5562ChannelRed; + } else if(channel & LP5562ChannelGreen) { + bit_offset = 2; + channel &= ~LP5562ChannelGreen; + } else if(channel & LP5562ChannelBlue) { + bit_offset = 0; + channel &= ~LP5562ChannelBlue; + } else if(channel & LP5562ChannelWhite) { + bit_offset = 6; + channel &= ~LP5562ChannelWhite; + } else { + return; + } - furi_hal_i2c_read_reg_8(handle, LP5562_ADDRESS, 0x70, ®_val, LP5562_I2C_TIMEOUT); - reg_val &= ~(0x3 << bit_offset); - reg_val |= ((src & 0x03) << bit_offset); - furi_hal_i2c_write_reg_8(handle, LP5562_ADDRESS, 0x70, reg_val, LP5562_I2C_TIMEOUT); + furi_hal_i2c_read_reg_8(handle, LP5562_ADDRESS, 0x70, ®_val, LP5562_I2C_TIMEOUT); + reg_val &= ~(0x3 << bit_offset); + reg_val |= ((src & 0x03) << bit_offset); + furi_hal_i2c_write_reg_8(handle, LP5562_ADDRESS, 0x70, reg_val, LP5562_I2C_TIMEOUT); + } while(channel); } void lp5562_execute_program( @@ -151,6 +157,19 @@ void lp5562_execute_program( furi_hal_i2c_write_reg_8(handle, LP5562_ADDRESS, 0x00, enable_reg, LP5562_I2C_TIMEOUT); } +void lp5562_stop_program(FuriHalI2cBusHandle* handle, LP5562Engine eng) { + if((eng < LP5562Engine1) || (eng > LP5562Engine3)) return; + uint8_t reg_val = 0; + uint8_t bit_offset = 0; + + // Engine configuration + bit_offset = (3 - eng) * 2; + furi_hal_i2c_read_reg_8(handle, LP5562_ADDRESS, 0x01, ®_val, LP5562_I2C_TIMEOUT); + reg_val &= ~(0x3 << bit_offset); + reg_val |= (0x00 << bit_offset); // Disabled + furi_hal_i2c_write_reg_8(handle, LP5562_ADDRESS, 0x01, reg_val, LP5562_I2C_TIMEOUT); +} + void lp5562_execute_ramp( FuriHalI2cBusHandle* handle, LP5562Engine eng, @@ -194,3 +213,54 @@ void lp5562_execute_ramp( // Write end value to register lp5562_set_channel_value(handle, ch, val_end); } + +void lp5562_execute_blink( + FuriHalI2cBusHandle* handle, + LP5562Engine eng, + LP5562Channel ch, + uint16_t on_time, + uint16_t period, + uint8_t brightness) { + // Temporary switch to constant value from register + lp5562_set_channel_src(handle, ch, LP5562Direct); + + // Prepare command sequence + uint16_t program[16]; + uint16_t time_step = 0; + uint8_t prescaller = 0; + + program[0] = 0x4000 | brightness; // Set PWM + + time_step = on_time * 2; + if(time_step > 0x3F) { + time_step /= 32; + prescaller = 1; + } else { + prescaller = 0; + } + if(time_step == 0) { + time_step = 1; + } else if(time_step > 0x3F) + time_step = 0x3F; + program[1] = (prescaller << 14) | (time_step << 8); // Delay + + program[2] = 0x4000 | 0; // Set PWM + + time_step = (period - on_time) * 2; + if(time_step > 0x3F) { + time_step /= 32; + prescaller = 1; + } else { + prescaller = 0; + } + if(time_step == 0) { + time_step = 1; + } else if(time_step > 0x3F) + time_step = 0x3F; + program[3] = (prescaller << 14) | (time_step << 8); // Delay + + program[4] = 0x0000; // Go to start + + // Execute program + lp5562_execute_program(handle, eng, ch, program); +} diff --git a/lib/drivers/lp5562.h b/lib/drivers/lp5562.h index 33790d00dbd..f5ebeeae233 100644 --- a/lib/drivers/lp5562.h +++ b/lib/drivers/lp5562.h @@ -6,10 +6,10 @@ /** Channel types */ typedef enum { - LP5562ChannelRed, - LP5562ChannelGreen, - LP5562ChannelBlue, - LP5562ChannelWhite, + LP5562ChannelRed = (1 << 0), + LP5562ChannelGreen = (1 << 1), + LP5562ChannelBlue = (1 << 2), + LP5562ChannelWhite = (1 << 3), } LP5562Channel; typedef enum { @@ -37,6 +37,9 @@ void lp5562_set_channel_value(FuriHalI2cBusHandle* handle, LP5562Channel channel /** Get channel PWM value */ uint8_t lp5562_get_channel_value(FuriHalI2cBusHandle* handle, LP5562Channel channel); +/** Set channel source */ +void lp5562_set_channel_src(FuriHalI2cBusHandle* handle, LP5562Channel channel, LP5562Engine src); + /** Execute program sequence */ void lp5562_execute_program( FuriHalI2cBusHandle* handle, @@ -44,6 +47,9 @@ void lp5562_execute_program( LP5562Channel ch, uint16_t* program); +/** Stop program sequence */ +void lp5562_stop_program(FuriHalI2cBusHandle* handle, LP5562Engine eng); + /** Execute ramp program sequence */ void lp5562_execute_ramp( FuriHalI2cBusHandle* handle, @@ -52,3 +58,12 @@ void lp5562_execute_ramp( uint8_t val_start, uint8_t val_end, uint16_t time); + +/** Start blink program sequence */ +void lp5562_execute_blink( + FuriHalI2cBusHandle* handle, + LP5562Engine eng, + LP5562Channel ch, + uint16_t on_time, + uint16_t period, + uint8_t brightness); diff --git a/lib/nfc_protocols/emv.c b/lib/nfc_protocols/emv.c index 416e63a54d0..b69675fdd2f 100644 --- a/lib/nfc_protocols/emv.c +++ b/lib/nfc_protocols/emv.c @@ -397,7 +397,8 @@ bool emv_read_bank_card(FuriHalNfcTxRxContext* tx_rx, EmvApplication* emv_app) { bool emv_card_emulation(FuriHalNfcTxRxContext* tx_rx) { furi_assert(tx_rx); bool emulation_complete = false; - memset(tx_rx, 0, sizeof(FuriHalNfcTxRxContext)); + tx_rx->tx_bits = 0; + tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; do { FURI_LOG_D(TAG, "Read select PPSE command"); diff --git a/lib/nfc_protocols/mifare_classic.c b/lib/nfc_protocols/mifare_classic.c index 38c47127bb2..b44f77cb8d8 100644 --- a/lib/nfc_protocols/mifare_classic.c +++ b/lib/nfc_protocols/mifare_classic.c @@ -270,7 +270,9 @@ static bool mf_classic_auth( MfClassicKey key_type, Crypto1* crypto) { bool auth_success = false; - memset(tx_rx, 0, sizeof(FuriHalNfcTxRxContext)); + memset(tx_rx->tx_data, 0, sizeof(tx_rx->tx_data)); + memset(tx_rx->tx_parity, 0, sizeof(tx_rx->tx_parity)); + tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; do { if(key_type == MfClassicKeyA) { @@ -372,7 +374,8 @@ bool mf_classic_read_block( bool read_block_success = false; uint8_t plain_cmd[4] = {MF_CLASSIC_READ_SECT_CMD, block_num, 0x00, 0x00}; nfca_append_crc16(plain_cmd, 2); - memset(tx_rx, 0, sizeof(FuriHalNfcTxRxContext)); + memset(tx_rx->tx_data, 0, sizeof(tx_rx->tx_data)); + memset(tx_rx->tx_parity, 0, sizeof(tx_rx->tx_parity)); for(uint8_t i = 0; i < 4; i++) { tx_rx->tx_data[i] = crypto1_byte(crypto, 0x00, 0) ^ plain_cmd[i]; diff --git a/lib/toolbox/path.c b/lib/toolbox/path.c index a99e57d1f63..c6add856a3c 100644 --- a/lib/toolbox/path.c +++ b/lib/toolbox/path.c @@ -1,4 +1,6 @@ #include "path.h" +#include "m-string.h" +#include void path_extract_filename_no_ext(const char* path, string_t filename) { string_set(filename, path); @@ -33,6 +35,15 @@ void path_extract_filename(string_t path, string_t name, bool trim_ext) { } } +void path_extract_extension(string_t path, char* ext, size_t ext_len_max) { + size_t dot = string_search_rchar(path, '.'); + size_t filename_start = string_search_rchar(path, '/'); + + if((dot > 0) && (filename_start < dot)) { + strlcpy(ext, &(string_get_cstr(path))[dot], ext_len_max); + } +} + static inline void path_cleanup(string_t path) { string_strim(path); while(string_end_with_str_p(path, "/")) { diff --git a/lib/toolbox/path.h b/lib/toolbox/path.h index 76e501cc190..5c91e3fced4 100644 --- a/lib/toolbox/path.h +++ b/lib/toolbox/path.h @@ -23,6 +23,15 @@ void path_extract_filename_no_ext(const char* path, string_t filename); */ void path_extract_filename(string_t path, string_t filename, bool trim_ext); +/** + * @brief Extract file extension from path. + * + * @param path path string + * @param ext output extension string + * @param ext_len_max maximum extension string length + */ +void path_extract_extension(string_t path, char* ext, size_t ext_len_max); + /** * @brief Extract last path component * diff --git a/scripts/flipper/app.py b/scripts/flipper/app.py index bd0cbc37635..a75a8a9e84d 100644 --- a/scripts/flipper/app.py +++ b/scripts/flipper/app.py @@ -15,16 +15,20 @@ def __init__(self, no_exit=False): # Application specific initialization self.init() - def __call__(self, args=None): + def __call__(self, args=None, skip_logger_init=False): self.args, self.other_args = self.parser.parse_known_args(args=args) # configure log output + # if skip_logger_init: self.log_level = logging.DEBUG if self.args.debug else logging.INFO self.logger.setLevel(self.log_level) - self.handler = logging.StreamHandler(sys.stdout) - self.handler.setLevel(self.log_level) - self.formatter = logging.Formatter("%(asctime)s [%(levelname)s] %(message)s") - self.handler.setFormatter(self.formatter) - self.logger.addHandler(self.handler) + if not self.logger.hasHandlers(): + self.handler = logging.StreamHandler(sys.stdout) + self.handler.setLevel(self.log_level) + self.formatter = logging.Formatter( + "%(asctime)s [%(levelname)s] %(message)s" + ) + self.handler.setFormatter(self.formatter) + self.logger.addHandler(self.handler) # execute requested function self.before() diff --git a/scripts/ob_custradio.data b/scripts/ob_custradio.data new file mode 100644 index 00000000000..0dbbb8d1a8d --- /dev/null +++ b/scripts/ob_custradio.data @@ -0,0 +1,34 @@ +RDP:0xAA:r +BOR_LEV:0x4:rw +nBOOT0:0x1:r +nBOOT1:0x1:rw +nSWBOOT0:0x1:rw +SRAM2RST:0x0:rw +SRAM2PE:0x1:rw +nRST_STOP:0x1:rw +nRST_STDBY:0x1:rw +nRSTSHDW:0x1:rw +WWDGSW:0x1:rw +IWGDSTDBY:0x1:rw +IWDGSTOP:0x1:rw +IWDGSW:0x1:rw +IPCCDBA:0x0:rw +ESE:0x1:r +#SFSA:0xD7:r +FSD:0x0:r +DDS:0x1:r +#C2OPT:0x1:r +#NBRSD:0x0:r +#SNBRSA:0xD:r +#BRSD:0x0:r +#SBRSA:0x12:r +#SBRV:0x35C00:r +PCROP1A_STRT:0x1FF:r +PCROP1A_END:0x0:r +PCROP_RDP:0x1:rw +PCROP1B_STRT:0x1FF:r +PCROP1B_END:0x0:r +WRP1A_STRT:0xFF:r +WRP1A_END:0x0:r +WRP1B_STRT:0xFF:r +WRP1B_END:0x0:r diff --git a/scripts/update.py b/scripts/update.py index 4769565ca95..1fb0779f505 100755 --- a/scripts/update.py +++ b/scripts/update.py @@ -3,7 +3,7 @@ from flipper.app import App from flipper.utils.fff import FlipperFormatFile from flipper.assets.coprobin import CoproBinary, get_stack_type -from flipper.assets.obdata import OptionBytesData +from flipper.assets.obdata import OptionBytesData, ObReferenceValues from os.path import basename, join, exists import os import shutil @@ -21,6 +21,16 @@ class Main(App): RESOURCE_TAR_FORMAT = tarfile.USTAR_FORMAT RESOURCE_FILE_NAME = "resources.tar" + WHITELISTED_STACK_TYPES = set( + map( + get_stack_type, + ["BLE_FULL", "BLE_LIGHT", "BLE_BASIC"], + ) + ) + + FLASH_BASE = 0x8000000 + MIN_LFS_PAGES = 6 + def init(self): self.subparsers = self.parser.add_subparsers(help="sub-command help") @@ -53,6 +63,9 @@ def init(self): ) self.parser_generate.add_argument("--obdata", dest="obdata", required=False) + self.parser_generate.add_argument( + "--I-understand-what-I-am-doing", dest="disclaimer", required=False + ) self.parser_generate.set_defaults(func=self.generate) @@ -70,10 +83,20 @@ def generate(self): raise ValueError("Missing --radiotype") radio_meta = CoproBinary(self.args.radiobin) radio_version = self.copro_version_as_int(radio_meta, self.args.radiotype) + if ( + get_stack_type(self.args.radiotype) not in self.WHITELISTED_STACK_TYPES + and self.args.disclaimer != "yes" + ): + self.logger.error( + f"You are trying to bundle a non-standard stack type '{self.args.radiotype}'." + ) + self.disclaimer() + return 1 + if radio_addr == 0: radio_addr = radio_meta.get_flash_load_addr() self.logger.info( - f"Using guessed radio address 0x{radio_addr:X}, verify with Release_Notes" + f"Using guessed radio address 0x{radio_addr:08X}, verify with Release_Notes" " or specify --radioaddr" ) @@ -81,7 +104,9 @@ def generate(self): os.makedirs(self.args.directory) shutil.copyfile(self.args.stage, join(self.args.directory, stage_basename)) + dfu_size = 0 if self.args.dfu: + dfu_size = os.stat(self.args.dfu).st_size shutil.copyfile(self.args.dfu, join(self.args.directory, dfu_basename)) if radiobin_basename: shutil.copyfile( @@ -93,6 +118,12 @@ def generate(self): self.args.resources, join(self.args.directory, resources_basename) ) + if not self.layout_check(dfu_size, radio_addr): + self.logger.warn("Memory layout looks suspicious") + if not self.args.disclaimer == "yes": + self.disclaimer() + return 2 + file = FlipperFormatFile() file.setHeader( "Flipper firmware upgrade configuration", self.UPDATE_MANIFEST_VERSION @@ -111,19 +142,43 @@ def generate(self): else: file.writeKey("Radio CRC", self.int2ffhex(0)) file.writeKey("Resources", resources_basename) - file.writeComment( - "NEVER EVER MESS WITH THESE VALUES, YOU WILL BRICK YOUR DEVICE" - ) + obvalues = ObReferenceValues((), (), ()) if self.args.obdata: obd = OptionBytesData(self.args.obdata) obvalues = obd.gen_values().export() - file.writeKey("OB reference", self.bytes2ffhex(obvalues.reference)) - file.writeKey("OB mask", self.bytes2ffhex(obvalues.compare_mask)) - file.writeKey("OB write mask", self.bytes2ffhex(obvalues.write_mask)) + file.writeComment( + "NEVER EVER MESS WITH THESE VALUES, YOU WILL BRICK YOUR DEVICE" + ) + file.writeKey("OB reference", self.bytes2ffhex(obvalues.reference)) + file.writeKey("OB mask", self.bytes2ffhex(obvalues.compare_mask)) + file.writeKey("OB write mask", self.bytes2ffhex(obvalues.write_mask)) file.save(join(self.args.directory, self.UPDATE_MANIFEST_NAME)) return 0 + def layout_check(self, fw_size, radio_addr): + if fw_size == 0 or radio_addr == 0: + self.logger.info("Cannot validate layout for partial package") + return True + + lfs_span = radio_addr - self.FLASH_BASE - fw_size + self.logger.debug(f"Expected LFS size: {lfs_span}") + lfs_span_pages = lfs_span / (4 * 1024) + if lfs_span_pages < self.MIN_LFS_PAGES: + self.logger.warn( + f"Expected LFS size is too small (~{int(lfs_span_pages)} pages)" + ) + return False + return True + + def disclaimer(self): + self.logger.error( + "You might brick you device into a state in which you'd need an SWD programmer to fix it." + ) + self.logger.error( + "Please confirm that you REALLY want to do that with --I-understand-what-I-am-doing=yes" + ) + def package_resources(self, srcdir: str, dst_name: str): with tarfile.open( dst_name, self.RESOURCE_TAR_MODE, format=self.RESOURCE_TAR_FORMAT