Merge branch 'release-candidate' into release

This commit is contained in:
Aleksandr Kutuzov 2022-06-15 20:00:39 +09:00
commit e82001aaa6
No known key found for this signature in database
GPG Key ID: 0D0011717914BBCD
100 changed files with 2296 additions and 1361 deletions

132
.github/CODEOWNERS vendored
View File

@ -1,63 +1,91 @@
# Who owns all the fish by default # Who owns all the fish by default
* @skotopes @DrZlo13 @hedger
* @skotopes @DrZlo13 # Apps
/applications/about/ @skotopes @DrZlo13 @hedger
/applications/accessor/ @skotopes @DrZlo13 @hedger
/applications/archive/ @skotopes @DrZlo13 @hedger @nminaylov
/applications/bad_usb/ @skotopes @DrZlo13 @hedger @nminaylov
/applications/bt/ @skotopes @DrZlo13 @hedger @gornekich
/applications/cli/ @skotopes @DrZlo13 @hedger @nminaylov
/applications/crypto/ @skotopes @DrZlo13 @hedger @nminaylov
/applications/debug_tools/ @skotopes @DrZlo13 @hedger
/applications/desktop/ @skotopes @DrZlo13 @hedger @nminaylov
/applications/dialogs/ @skotopes @DrZlo13 @hedger
/applications/dolphin/ @skotopes @DrZlo13 @hedger
/applications/gpio/ @skotopes @DrZlo13 @hedger @nminaylov
/applications/gui/ @skotopes @DrZlo13 @hedger
/applications/ibutton/ @skotopes @DrZlo13 @hedger @gsurkov
/applications/infrared/ @skotopes @DrZlo13 @hedger @gsurkov
/applications/infrared_monitor/ @skotopes @DrZlo13 @hedger @gsurkov
/applications/input/ @skotopes @DrZlo13 @hedger
/applications/lfrfid/ @skotopes @DrZlo13 @hedger
/applications/lfrfid_debug/ @skotopes @DrZlo13 @hedger
/applications/loader/ @skotopes @DrZlo13 @hedger
/applications/music_player/ @skotopes @DrZlo13 @hedger
/applications/nfc/ @skotopes @DrZlo13 @hedger @gornekich
/applications/notification/ @skotopes @DrZlo13 @hedger
/applications/power/ @skotopes @DrZlo13 @hedger
/applications/rpc/ @skotopes @DrZlo13 @hedger @nminaylov
/applications/snake_game/ @skotopes @DrZlo13 @hedger
/applications/storage/ @skotopes @DrZlo13 @hedger
/applications/storage_settings/ @skotopes @DrZlo13 @hedger
/applications/subghz/ @skotopes @DrZlo13 @hedger @Skorpionm
/applications/system/ @skotopes @DrZlo13 @hedger
/applications/u2f/ @skotopes @DrZlo13 @hedger @nminaylov
/applications/unit_tests/ @skotopes @DrZlo13 @hedger
/applications/updater/ @skotopes @DrZlo13 @hedger
# Applications # Assets
applications/** @skotopes @DrZlo13 /assets/ @skotopes @DrZlo13 @hedger
applications/accessor/** @skotopes @DrZlo13
applications/loader/** @skotopes @DrZlo13 @gornekich
applications/bt/** @skotopes @DrZlo13
applications/cli/** @skotopes @DrZlo13
applications/dolphin/** @skotopes @DrZlo13
applications/gpio-tester/** @skotopes @DrZlo13
applications/gui/** @skotopes @DrZlo13
applications/gui-test/** @skotopes @DrZlo13
applications/ibutton/** @skotopes @DrZlo13
applications/input/** @skotopes @DrZlo13
applications/infrared/** @skotopes @DrZlo13
applications/lf-rfid/** @skotopes @DrZlo13
applications/menu/** @skotopes @DrZlo13
applications/music-player/** @skotopes @DrZlo13
applications/nfc/** @skotopes @DrZlo13 @gornekich
applications/power/** @skotopes @DrZlo13
applications/sd-card-test/** @skotopes @DrZlo13
applications/sd-filesystem/** @skotopes @DrZlo13
applications/subghz/** @skotopes @DrZlo13
applications/template/** @skotopes @DrZlo13
applications/tests/** @skotopes @DrZlo13
applications/updater/** @skotopes @DrZlo13 @hedger
# Assets and asset generator # Furi Core
assets/** @skotopes @DrZlo13 /core/ @skotopes @DrZlo13 @hedger
# Bootloader # Debug tools and plugins
bootloader/** @skotopes @DrZlo13 /debug/ @skotopes @DrZlo13 @hedger
# Core, HAL and applocation loader # Docker
core/** @skotopes @DrZlo13 /docker/ @skotopes @DrZlo13 @hedger @aprosvetova
/docker-compose.yml @skotopes @DrZlo13 @hedger @aprosvetova
# Debug tools # Documentation
debug/** @skotopes @DrZlo13 /documentation/ @skotopes @DrZlo13 @hedger @aprosvetova
# Firmware # Firmware targets
firmware/** @skotopes @DrZlo13 /firmware/ @skotopes @DrZlo13 @hedger
# Lib # Lib
lib/app-template/** @skotopes @DrZlo13 /lib/FreeRTOS-Kernel/ @skotopes @DrZlo13 @hedger
lib/callback-connector/** @skotopes @DrZlo13 /lib/FreeRTOS-glue/ @skotopes @DrZlo13 @hedger
lib/common-api/** @skotopes @DrZlo13 /lib/ST25RFAL002/ @skotopes @DrZlo13 @hedger @gornekich
lib/cyfral/** @skotopes @DrZlo13 /lib/STM32CubeWB/ @skotopes @DrZlo13 @hedger @gornekich
lib/drivers/** @skotopes @DrZlo13 @gornekich /lib/app-scened-template/ @skotopes @DrZlo13 @hedger
lib/fatfs/** @skotopes @DrZlo13 /lib/callback-connector/ @skotopes @DrZlo13 @hedger
lib/fnv1a-hash/** @skotopes @DrZlo13 /lib/digital_signal/ @skotopes @DrZlo13 @hedger @gornekich
lib/littlefs/** @skotopes @DrZlo13 /lib/drivers/ @skotopes @DrZlo13 @hedger
lib/mlib/** @skotopes @DrZlo13 /lib/fatfs/ @skotopes @DrZlo13 @hedger
lib/onewire/** @skotopes @DrZlo13 /lib/flipper_format/ @skotopes @DrZlo13 @hedger
lib/qrcode/** @skotopes @DrZlo13 /lib/fnv1a-hash/ @skotopes @DrZlo13 @hedger
lib/ST25RFAL002/** @skotopes @DrZlo13 @gornekich /lib/heatshrink/ @skotopes @DrZlo13 @hedger
lib/STM32CubeWB/** @skotopes @DrZlo13 /lib/infrared/ @skotopes @DrZlo13 @hedger @gsurkov
lib/u8g2/** @skotopes @DrZlo13 /lib/libusb_stm32/ @skotopes @DrZlo13 @hedger @nminaylov
lib/version/** @skotopes @DrZlo13 /lib/littlefs/ @skotopes @DrZlo13 @hedger
/lib/lfs_config.h @skotopes @DrZlo13 @hedger
/lib/micro-ecc/ @skotopes @DrZlo13 @hedger @nminaylov
/lib/microtar/ @skotopes @DrZlo13 @hedger
/lib/mlib/ @skotopes @DrZlo13 @hedger
/lib/nanopb/ @skotopes @DrZlo13 @hedger
/lib/nfc_protocols/ @skotopes @DrZlo13 @hedger @gornekich
/lib/one_wire/ @skotopes @DrZlo13 @hedger
/lib/qrcode/ @skotopes @DrZlo13 @hedger
/lib/subghz/ @skotopes @DrZlo13 @hedger @Skorpionm
/lib/toolbox/ @skotopes @DrZlo13 @hedger
/lib/u8g2/ @skotopes @DrZlo13 @hedger
/lib/update_util/ @skotopes @DrZlo13 @hedger
# Make # Make tools
make/** @skotopes @DrZlo13 /make/ @skotopes @DrZlo13 @hedger @aprosvetova
# Helper scripts
/scripts/ @skotopes @DrZlo13 @hedger

View File

@ -152,7 +152,7 @@ jobs:
with: with:
issue-number: ${{ github.event.pull_request.number }} issue-number: ${{ github.event.pull_request.number }}
comment-author: 'github-actions[bot]' comment-author: 'github-actions[bot]'
body-includes: 'to flash the' body-includes: 'Install with web updater'
- name: 'Create or update comment' - name: 'Create or update comment'
if: ${{ !github.event.pull_request.head.repo.fork && github.event.pull_request}} if: ${{ !github.event.pull_request.head.repo.fork && github.event.pull_request}}
@ -161,7 +161,7 @@ jobs:
comment-id: ${{ steps.fc.outputs.comment-id }} comment-id: ${{ steps.fc.outputs.comment-id }}
issue-number: ${{ github.event.pull_request.number }} issue-number: ${{ github.event.pull_request.number }}
body: | 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 edit-mode: replace
compact: compact:

View File

@ -102,7 +102,8 @@ updater_package: firmware_all updater assets_manifest
--bundlever "$(VERSION_STRING)" \ --bundlever "$(VERSION_STRING)" \
--radio $(COPRO_STACK_BIN_PATH) \ --radio $(COPRO_STACK_BIN_PATH) \
--radiotype $(COPRO_STACK_TYPE) \ --radiotype $(COPRO_STACK_TYPE) \
--obdata $(PROJECT_ROOT)/scripts/ob.data $(COPRO_DISCLAIMER) \
--obdata $(PROJECT_ROOT)/scripts/$(COPRO_OB_DATA)
.PHONY: assets_manifest .PHONY: assets_manifest
assets_manifest: assets_manifest:

View File

@ -1,4 +1,5 @@
#include "archive_i.h" #include "archive_i.h"
#include "m-string.h"
bool archive_custom_event_callback(void* context, uint32_t event) { bool archive_custom_event_callback(void* context, uint32_t event) {
furi_assert(context); furi_assert(context);
@ -17,6 +18,7 @@ ArchiveApp* archive_alloc() {
archive->gui = furi_record_open("gui"); archive->gui = furi_record_open("gui");
archive->text_input = text_input_alloc(); archive->text_input = text_input_alloc();
string_init(archive->fav_move_str);
archive->view_dispatcher = view_dispatcher_alloc(); archive->view_dispatcher = view_dispatcher_alloc();
archive->scene_manager = scene_manager_alloc(&archive_scene_handlers, archive); 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); view_dispatcher_free(archive->view_dispatcher);
scene_manager_free(archive->scene_manager); scene_manager_free(archive->scene_manager);
browser_free(archive->browser); browser_free(archive->browser);
string_clear(archive->fav_move_str);
text_input_free(archive->text_input); text_input_free(archive->text_input);

View File

@ -28,6 +28,7 @@ struct ArchiveApp {
TextInput* text_input; TextInput* text_input;
Widget* widget; Widget* widget;
FuriPubSubSubscription* loader_stop_subscription; FuriPubSubSubscription* loader_stop_subscription;
string_t fav_move_str;
char text_store[MAX_NAME_LEN]; char text_store[MAX_NAME_LEN];
char file_extension[MAX_EXT_LEN + 1]; char file_extension[MAX_EXT_LEN + 1];
}; };

View File

@ -7,8 +7,14 @@ static const char* known_apps[] = {
}; };
ArchiveAppTypeEnum archive_get_app_type(const char* path) { 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++) { 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; return i;
} }
} }

View File

@ -1,8 +1,91 @@
#include "archive/views/archive_browser_view.h"
#include "archive_files.h" #include "archive_files.h"
#include "archive_apps.h" #include "archive_apps.h"
#include "archive_browser.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 <math.h> #include <math.h>
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) { bool archive_is_item_in_array(ArchiveBrowserViewModel* model, uint32_t idx) {
size_t array_size = files_array_size(model->files); 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(browser);
furi_assert(target); 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); archive_switch_tab(browser, TAB_RIGHT);
} else { } else {
with_view_model( with_view_model(
@ -49,7 +132,7 @@ void archive_update_focus(ArchiveBrowserView* browser, const char* target) {
uint16_t idx = 0; uint16_t idx = 0;
while(idx < files_array_size(model->files)) { while(idx < files_array_size(model->files)) {
ArchiveFile_t* current = files_array_get(model->files, idx); 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; model->item_idx = idx + model->array_offset;
break; break;
} }
@ -102,7 +185,7 @@ void archive_file_array_rm_selected(ArchiveBrowserView* browser) {
return false; 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); 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); furi_assert(browser);
int32_t offset_new = 0; int32_t offset_new = 0;
@ -160,22 +243,17 @@ bool archive_file_array_load(ArchiveBrowserView* browser, int8_t dir) {
} else { } else {
offset_new = model->item_idx - FILE_LIST_BUF_LEN / 4 * 1; 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; return false;
}); });
bool res = archive_dir_read_items( file_browser_worker_load(browser->worker, offset_new, FILE_LIST_BUF_LEN);
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;
} }
ArchiveFile_t* archive_get_current_file(ArchiveBrowserView* browser) { ArchiveFile_t* archive_get_current_file(ArchiveBrowserView* browser) {
@ -218,26 +296,20 @@ ArchiveTabEnum archive_get_tab(ArchiveBrowserView* browser) {
return tab_id; return tab_id;
} }
uint8_t archive_get_depth(ArchiveBrowserView* browser) { bool archive_is_home(ArchiveBrowserView* browser) {
furi_assert(browser); furi_assert(browser);
uint8_t depth; if(browser->is_root) {
with_view_model( return true;
browser->view, (ArchiveBrowserViewModel * model) { }
depth = idx_last_array_size(model->idx_last);
return false;
});
return depth; 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_path(ArchiveBrowserView* browser) {
return string_get_cstr(browser->path);
} }
const char* archive_get_name(ArchiveBrowserView* browser) { const char* archive_get_name(ArchiveBrowserView* browser) {
ArchiveFile_t* selected = archive_get_current_file(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) { void archive_set_tab(ArchiveBrowserView* browser, ArchiveTabEnum tab) {
@ -249,37 +321,15 @@ void archive_set_tab(ArchiveBrowserView* browser, ArchiveTabEnum tab) {
return false; 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) { void archive_add_app_item(ArchiveBrowserView* browser, const char* name) {
furi_assert(browser); furi_assert(browser);
furi_assert(name); furi_assert(name);
ArchiveFile_t item; 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); ArchiveFile_t_init(&item);
string_init_set_str(item.name, name); string_set_str(item.path, name);
archive_set_file_type(&item, NULL, app_name + 1, true); archive_set_file_type(&item, name, false, true);
with_view_model( with_view_model(
browser->view, (ArchiveBrowserViewModel * model) { browser->view, (ArchiveBrowserViewModel * model) {
@ -288,29 +338,24 @@ void archive_add_app_item(ArchiveBrowserView* browser, const char* name) {
return false; return false;
}); });
ArchiveFile_t_clear(&item); 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(browser);
furi_assert(file_info);
furi_assert(name); furi_assert(name);
ArchiveFile_t item; ArchiveFile_t item;
if(archive_filter_by_extension( ArchiveFile_t_init(&item);
file_info, archive_get_tab_ext(archive_get_tab(browser)), name)) { string_init_set_str(item.path, name);
ArchiveFile_t_init(&item); archive_set_file_type(&item, string_get_cstr(browser->path), is_folder, false);
string_init_set_str(item.name, name);
archive_set_file_type(&item, file_info, archive_get_path(browser), false);
with_view_model( with_view_model(
browser->view, (ArchiveBrowserViewModel * model) { browser->view, (ArchiveBrowserViewModel * model) {
files_array_push_back(model->files, item); files_array_push_back(model->files, item);
return false; return false;
}); });
ArchiveFile_t_clear(&item); ArchiveFile_t_clear(&item);
}
} }
void archive_show_file_menu(ArchiveBrowserView* browser, bool show) { 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; model->menu_idx = 0;
ArchiveFile_t* selected = ArchiveFile_t* selected =
files_array_get(model->files, model->item_idx - model->array_offset); 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 { } else {
model->menu = false; 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) { void archive_switch_tab(ArchiveBrowserView* browser, InputKey key) {
furi_assert(browser); furi_assert(browser);
ArchiveTabEnum tab = archive_get_tab(browser); ArchiveTabEnum tab = archive_get_tab(browser);
browser->last_tab_switch_dir = key;
if(key == InputKeyLeft) { if(key == InputKeyLeft) {
tab = ((tab - 1) + ArchiveTabTotal) % ArchiveTabTotal; tab = ((tab - 1) + ArchiveTabTotal) % ArchiveTabTotal;
} else if(key == InputKeyRight) { } else {
tab = (tab + 1) % ArchiveTabTotal; tab = (tab + 1) % ArchiveTabTotal;
} }
browser->is_root = true;
archive_set_tab(browser, tab); 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; bool tab_empty = true;
if(tab == ArchiveTabFavorites) { if(tab == ArchiveTabFavorites) {
if(archive_favorites_count(browser) > 0) tab_empty = false; if(archive_favorites_count(browser) > 0) {
} else if(strncmp(path, "/app:", 5) == 0) { tab_empty = false;
if(archive_app_is_available(browser, path)) 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 { } else {
uint32_t files_cnt = archive_dir_count_items(browser, archive_get_default_path(tab)); ArchiveTabEnum tab = archive_get_tab(browser);
if(files_cnt > 0) tab_empty = false; 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)) { if((tab_empty) && (tab != ArchiveTabBrowser)) {
@ -381,60 +430,46 @@ void archive_switch_tab(ArchiveBrowserView* browser, InputKey key) {
} else { } else {
with_view_model( with_view_model(
browser->view, (ArchiveBrowserViewModel * model) { browser->view, (ArchiveBrowserViewModel * model) {
if(model->last_tab != model->tab_idx) { model->item_idx = 0;
model->item_idx = 0; model->array_offset = 0;
model->array_offset = 0;
idx_last_array_reset(model->idx_last);
}
return false; 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(browser);
furi_assert(name); furi_assert(path);
if(string_size(name) >= (MAX_NAME_LEN - 1)) { int32_t idx_temp = 0;
return;
}
if(string_cmp(browser->path, name) != 0) { with_view_model(
with_view_model( browser->view, (ArchiveBrowserViewModel * model) {
browser->view, (ArchiveBrowserViewModel * model) { idx_temp = model->item_idx;
idx_last_array_push_back(model->idx_last, model->item_idx); return false;
model->array_offset = 0; });
model->item_idx = 0;
return false;
});
string_set(browser->path, name); string_set(browser->path, path);
} file_browser_worker_folder_enter(browser->worker, path, idx_temp);
archive_dir_count_items(browser, string_get_cstr(name));
archive_switch_dir(browser, string_get_cstr(browser->path));
} }
void archive_leave_dir(ArchiveBrowserView* browser) { void archive_leave_dir(ArchiveBrowserView* browser) {
furi_assert(browser); furi_assert(browser);
const char* path = archive_get_path(browser); file_browser_worker_folder_exit(browser->worker);
char* last_char_ptr = strrchr(path, '/'); }
if(last_char_ptr) { void archive_refresh_dir(ArchiveBrowserView* browser) {
size_t pos = last_char_ptr - path; furi_assert(browser);
string_left(browser->path, pos);
}
archive_dir_count_items(browser, path); int32_t idx_temp = 0;
with_view_model( with_view_model(
browser->view, (ArchiveBrowserViewModel * model) { browser->view, (ArchiveBrowserViewModel * model) {
idx_last_array_pop_back(&model->item_idx, model->idx_last); idx_temp = model->item_idx;
return false; return false;
}); });
file_browser_worker_folder_refresh(browser->worker, idx_temp);
archive_switch_dir(browser, path);
} }

View File

@ -2,11 +2,12 @@
#include "../archive_i.h" #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 #define FILE_LIST_BUF_LEN 100
static const char* tab_default_paths[] = { static const char* tab_default_paths[] = {
[ArchiveTabFavorites] = "/any/favorites", [ArchiveTabFavorites] = "/app:favorites",
[ArchiveTabIButton] = "/any/ibutton", [ArchiveTabIButton] = "/any/ibutton",
[ArchiveTabNFC] = "/any/nfc", [ArchiveTabNFC] = "/any/nfc",
[ArchiveTabSubGhz] = "/any/subghz", [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_offset(ArchiveBrowserView* browser);
void archive_update_focus(ArchiveBrowserView* browser, const char* target); 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); size_t archive_file_get_array_size(ArchiveBrowserView* browser);
void archive_file_array_rm_selected(ArchiveBrowserView* browser); void archive_file_array_rm_selected(ArchiveBrowserView* browser);
void archive_file_array_swap(ArchiveBrowserView* browser, int8_t dir); 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_current_file(ArchiveBrowserView* browser);
ArchiveFile_t* archive_get_file_at(ArchiveBrowserView* browser, size_t idx); ArchiveFile_t* archive_get_file_at(ArchiveBrowserView* browser, size_t idx);
ArchiveTabEnum archive_get_tab(ArchiveBrowserView* browser); ArchiveTabEnum archive_get_tab(ArchiveBrowserView* browser);
uint8_t archive_get_depth(ArchiveBrowserView* browser); bool archive_is_home(ArchiveBrowserView* browser);
const char* archive_get_path(ArchiveBrowserView* browser);
const char* archive_get_name(ArchiveBrowserView* browser); const char* archive_get_name(ArchiveBrowserView* browser);
void archive_add_app_item(ArchiveBrowserView* browser, const char* name); 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_show_file_menu(ArchiveBrowserView* browser, bool show);
void archive_favorites_move_mode(ArchiveBrowserView* browser, bool active); void archive_favorites_move_mode(ArchiveBrowserView* browser, bool active);
void archive_switch_tab(ArchiveBrowserView* browser, InputKey key); void archive_switch_tab(ArchiveBrowserView* browser, InputKey key);
void archive_enter_dir(ArchiveBrowserView* browser, string_t name); void archive_enter_dir(ArchiveBrowserView* browser, string_t name);
void archive_leave_dir(ArchiveBrowserView* browser); void archive_leave_dir(ArchiveBrowserView* browser);
void archive_refresh_dir(ArchiveBrowserView* browser);
void archive_file_browser_set_callbacks(ArchiveBrowserView* browser);

View File

@ -168,7 +168,8 @@ bool archive_favorites_read(void* context) {
if(file_exists) { if(file_exists) {
storage_common_stat(fs_api, string_get_cstr(buffer), &file_info); storage_common_stat(fs_api, string_get_cstr(buffer), &file_info);
storage_file_close(fav_item_file); 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++; file_count++;
} else { } else {
storage_file_close(fav_item_file); 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++) { for(size_t i = 0; i < archive_file_get_array_size(browser); i++) {
ArchiveFile_t* item = archive_get_file_at(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); storage_common_remove(fs_api, ARCHIVE_FAV_PATH);

View File

@ -6,59 +6,18 @@
#define ASSETS_DIR "assets" #define ASSETS_DIR "assets"
bool archive_filter_by_extension(FileInfo* file_info, const char* tab_ext, const char* name) { void archive_set_file_type(ArchiveFile_t* file, const char* path, bool is_folder, bool is_app) {
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) {
furi_assert(file); furi_assert(file);
file->is_app = is_app; file->is_app = is_app;
if(is_app) { if(is_app) {
file->type = archive_get_app_filetype(archive_get_app_type(path)); file->type = archive_get_app_filetype(archive_get_app_type(path));
} else { } else {
furi_assert(file_info);
for(size_t i = 0; i < COUNT_OF(known_ext); i++) { for(size_t i = 0; i < COUNT_OF(known_ext); i++) {
if((known_ext[i][0] == '?') || (known_ext[i][0] == '*')) continue; 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(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) { 0) {
file->type = i; file->type = i;
return; // *.txt file is a BadUSB script only if it is in BadUSB folder 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; file->type = ArchiveFileTypeFolder;
} else { } else {
file->type = ArchiveFileTypeUnknown; 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); furi_assert(context);
bool res; bool res = false;
ArchiveBrowserView* browser = context; ArchiveBrowserView* browser = context;
if(archive_get_tab(browser) == ArchiveTabFavorites) { if(archive_get_tab(browser) == ArchiveTabFavorites) {
res = archive_favorites_read(browser); res = archive_favorites_read(browser);
} else if(strncmp(path, "/app:", 5) == 0) { } else if(strncmp(path, "/app:", 5) == 0) {
res = archive_app_read_dir(browser, path); res = archive_app_read_dir(browser, path);
} else {
res = archive_file_array_load(browser, 0);
} }
return res; 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, ...) { void archive_file_append(const char* path, const char* format, ...) {
furi_assert(path); furi_assert(path);

View File

@ -19,7 +19,7 @@ typedef enum {
} ArchiveFileTypeEnum; } ArchiveFileTypeEnum;
typedef struct { typedef struct {
string_t name; string_t path;
ArchiveFileTypeEnum type; ArchiveFileTypeEnum type;
bool fav; bool fav;
bool is_app; bool is_app;
@ -29,25 +29,25 @@ static void ArchiveFile_t_init(ArchiveFile_t* obj) {
obj->type = ArchiveFileTypeUnknown; obj->type = ArchiveFileTypeUnknown;
obj->is_app = false; obj->is_app = false;
obj->fav = 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) { static void ArchiveFile_t_init_set(ArchiveFile_t* obj, const ArchiveFile_t* src) {
obj->type = src->type; obj->type = src->type;
obj->is_app = src->is_app; obj->is_app = src->is_app;
obj->fav = src->fav; 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) { static void ArchiveFile_t_set(ArchiveFile_t* obj, const ArchiveFile_t* src) {
obj->type = src->type; obj->type = src->type;
obj->is_app = src->is_app; obj->is_app = src->is_app;
obj->fav = src->fav; obj->fav = src->fav;
string_set(obj->name, src->name); string_set(obj->path, src->path);
} }
static void ArchiveFile_t_clear(ArchiveFile_t* obj) { static void ArchiveFile_t_clear(ArchiveFile_t* obj) {
string_clear(obj->name); string_clear(obj->path);
} }
ARRAY_DEF( ARRAY_DEF(
@ -58,12 +58,7 @@ ARRAY_DEF(
INIT_SET(API_6(ArchiveFile_t_init_set)), INIT_SET(API_6(ArchiveFile_t_init_set)),
CLEAR(API_2(ArchiveFile_t_clear)))) 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, const char* path, bool is_folder, bool is_app);
void archive_set_file_type(ArchiveFile_t* file, FileInfo* file_info, const char* path, bool is_app); bool archive_get_items(void* context, const char* path);
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_file_append(const char* path, const char* format, ...); void archive_file_append(const char* path, const char* format, ...);
void archive_delete_file(void* context, const char* format, ...); void archive_delete_file(void* context, const char* format, ...);

View File

@ -4,9 +4,13 @@
#include "../helpers/archive_favorites.h" #include "../helpers/archive_favorites.h"
#include "../helpers/archive_browser.h" #include "../helpers/archive_browser.h"
#include "../views/archive_browser_view.h" #include "../views/archive_browser_view.h"
#include "archive/scenes/archive_scene.h"
#define TAG "ArchiveSceneBrowser" #define TAG "ArchiveSceneBrowser"
#define SCENE_STATE_DEFAULT (0)
#define SCENE_STATE_NEED_REFRESH (1)
static const char* flipper_app_name[] = { static const char* flipper_app_name[] = {
[ArchiveFileTypeIButton] = "iButton", [ArchiveFileTypeIButton] = "iButton",
[ArchiveFileTypeNFC] = "NFC", [ArchiveFileTypeNFC] = "NFC",
@ -26,7 +30,7 @@ static void archive_loader_callback(const void* message, void* context) {
if(event->type == LoaderEventTypeApplicationStopped) { if(event->type == LoaderEventTypeApplicationStopped) {
view_dispatcher_send_custom_event( 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; LoaderStatus status;
if(selected->is_app) { if(selected->is_app) {
char* param = strrchr(string_get_cstr(selected->name), '/'); char* param = strrchr(string_get_cstr(selected->path), '/');
if(param != NULL) { if(param != NULL) {
param++; param++;
} }
status = loader_start(loader, flipper_app_name[selected->type], param); status = loader_start(loader, flipper_app_name[selected->type], param);
} else { } else {
status = loader_start( 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) { if(status != LoaderStatusOk) {
@ -61,6 +65,7 @@ void archive_scene_browser_callback(ArchiveBrowserEvent event, void* context) {
void archive_scene_browser_on_enter(void* context) { void archive_scene_browser_on_enter(void* context) {
ArchiveApp* archive = (ArchiveApp*)context; ArchiveApp* archive = (ArchiveApp*)context;
ArchiveBrowserView* browser = archive->browser; ArchiveBrowserView* browser = archive->browser;
browser->is_root = true;
archive_browser_set_callback(browser, archive_scene_browser_callback, archive); archive_browser_set_callback(browser, archive_scene_browser_callback, archive);
archive_update_focus(browser, archive->text_store); archive_update_focus(browser, archive->text_store);
@ -70,6 +75,16 @@ void archive_scene_browser_on_enter(void* context) {
archive->loader_stop_subscription = archive->loader_stop_subscription =
furi_pubsub_subscribe(loader_get_pubsub(loader), archive_loader_callback, archive); furi_pubsub_subscribe(loader_get_pubsub(loader), archive_loader_callback, archive);
furi_record_close("loader"); 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) { 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: case ArchiveBrowserEventFileMenuRun:
if(known_app) { if(known_app) {
archive_run_in_app(browser, selected); archive_run_in_app(browser, selected);
archive_show_file_menu(browser, false);
} }
archive_show_file_menu(browser, false);
consumed = true; consumed = true;
break; break;
case ArchiveBrowserEventFileMenuPin: case ArchiveBrowserEventFileMenuPin:
@ -115,11 +130,13 @@ bool archive_scene_browser_on_event(void* context, SceneManagerEvent event) {
consumed = true; consumed = true;
break; break;
case ArchiveBrowserEventFileMenuAction: case ArchiveBrowserEventFileMenuRename:
if(favorites) { if(favorites) {
browser->callback(ArchiveBrowserEventEnterFavMove, browser->context); browser->callback(ArchiveBrowserEventEnterFavMove, browser->context);
} else if((known_app) && (selected->is_app == false)) { } else if((known_app) && (selected->is_app == false)) {
archive_show_file_menu(browser, 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); scene_manager_next_scene(archive->scene_manager, ArchiveAppSceneRename);
} }
consumed = true; consumed = true;
@ -131,7 +148,7 @@ bool archive_scene_browser_on_event(void* context, SceneManagerEvent event) {
consumed = true; consumed = true;
break; break;
case ArchiveBrowserEventEnterDir: case ArchiveBrowserEventEnterDir:
archive_enter_dir(browser, selected->name); archive_enter_dir(browser, selected->path);
consumed = true; consumed = true;
break; break;
case ArchiveBrowserEventFavMoveUp: case ArchiveBrowserEventFavMoveUp:
@ -143,13 +160,13 @@ bool archive_scene_browser_on_event(void* context, SceneManagerEvent event) {
consumed = true; consumed = true;
break; break;
case ArchiveBrowserEventEnterFavMove: 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_show_file_menu(browser, false);
archive_favorites_move_mode(archive->browser, true); archive_favorites_move_mode(archive->browser, true);
consumed = true; consumed = true;
break; break;
case ArchiveBrowserEventExitFavMove: 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); archive_favorites_move_mode(archive->browser, false);
consumed = true; consumed = true;
break; break;
@ -166,18 +183,17 @@ bool archive_scene_browser_on_event(void* context, SceneManagerEvent event) {
archive_file_array_load(archive->browser, 1); archive_file_array_load(archive->browser, 1);
consumed = true; consumed = true;
break; break;
case ArchiveBrowserEventLoaderAppExit: case ArchiveBrowserEventListRefresh:
if(!favorites) { if(!favorites) {
archive_enter_dir(browser, browser->path); archive_refresh_dir(browser);
} else { } else {
archive_favorites_read(browser); archive_favorites_read(browser);
} }
consumed = true; consumed = true;
break; break;
case ArchiveBrowserEventExit: case ArchiveBrowserEventExit:
if(archive_get_depth(browser)) { if(!archive_is_home(browser)) {
archive_leave_dir(browser); archive_leave_dir(browser);
} else { } else {
Loader* loader = furi_record_open("loader"); Loader* loader = furi_record_open("loader");

View File

@ -3,6 +3,8 @@
#include "../helpers/archive_files.h" #include "../helpers/archive_files.h"
#include "../helpers/archive_apps.h" #include "../helpers/archive_apps.h"
#include "../helpers/archive_browser.h" #include "../helpers/archive_browser.h"
#include "toolbox/path.h"
#include "m-string.h"
#define SCENE_DELETE_CUSTOM_EVENT (0UL) #define SCENE_DELETE_CUSTOM_EVENT (0UL)
#define MAX_TEXT_INPUT_LEN 22 #define MAX_TEXT_INPUT_LEN 22
@ -24,18 +26,19 @@ void archive_scene_delete_on_enter(void* context) {
widget_add_button_element( widget_add_button_element(
app->widget, GuiButtonTypeRight, "Delete", archive_scene_delete_widget_callback, app); 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); ArchiveFile_t* current = archive_get_current_file(app->browser);
strlcpy(app->text_store, string_get_cstr(current->name), MAX_NAME_LEN); path_extract_filename(current->path, filename, false);
char* name = strrchr(app->text_store, '/');
if(name != NULL) {
name++;
}
char delete_str[64]; 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( widget_add_text_box_element(
app->widget, 0, 0, 128, 23, AlignCenter, AlignCenter, delete_str, false); app->widget, 0, 0, 128, 23, AlignCenter, AlignCenter, delete_str, false);
string_clear(filename);
view_dispatcher_switch_to_view(app->view_dispatcher, ArchiveViewWidget); view_dispatcher_switch_to_view(app->view_dispatcher, ArchiveViewWidget);
} }

View File

@ -2,6 +2,8 @@
#include "../helpers/archive_favorites.h" #include "../helpers/archive_favorites.h"
#include "../helpers/archive_files.h" #include "../helpers/archive_files.h"
#include "../helpers/archive_browser.h" #include "../helpers/archive_browser.h"
#include "archive/views/archive_browser_view.h"
#include "toolbox/path.h"
#define SCENE_RENAME_CUSTOM_EVENT (0UL) #define SCENE_RENAME_CUSTOM_EVENT (0UL)
#define MAX_TEXT_INPUT_LEN 22 #define MAX_TEXT_INPUT_LEN 22
@ -16,10 +18,13 @@ void archive_scene_rename_on_enter(void* context) {
TextInput* text_input = archive->text_input; TextInput* text_input = archive->text_input;
ArchiveFile_t* current = archive_get_current_file(archive->browser); 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); string_t filename;
archive_trim_file_path(archive->text_store, true); 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:"); text_input_set_header_text(text_input, "Rename:");
@ -32,9 +37,11 @@ void archive_scene_rename_on_enter(void* context) {
false); false);
ValidatorIsFile* validator_is_file = validator_is_file_alloc_init( 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); 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); 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) { if(event.event == SCENE_RENAME_CUSTOM_EVENT) {
Storage* fs_api = furi_record_open("storage"); Storage* fs_api = furi_record_open("storage");
string_t buffer_src; const char* path_src = archive_get_name(archive->browser);
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
ArchiveFile_t* file = archive_get_current_file(archive->browser); ArchiveFile_t* file = archive_get_current_file(archive->browser);
string_cat(buffer_dst, known_ext[file->type]); string_t path_dst;
storage_common_rename( string_init(path_dst);
fs_api, string_get_cstr(buffer_src), string_get_cstr(buffer_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"); furi_record_close("storage");
if(file->fav) { 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(path_dst);
string_clear(buffer_dst);
scene_manager_next_scene(archive->scene_manager, ArchiveAppSceneBrowser); scene_manager_next_scene(archive->scene_manager, ArchiveAppSceneBrowser);
consumed = true; consumed = true;

View File

@ -1,3 +1,5 @@
#include "assets_icons.h"
#include "toolbox/path.h"
#include <furi.h> #include <furi.h>
#include "../archive_i.h" #include "../archive_i.h"
#include "archive_browser_view.h" #include "archive_browser_view.h"
@ -26,7 +28,7 @@ static const Icon* ArchiveItemIcons[] = {
[ArchiveFileTypeUpdateManifest] = &I_update_10px, [ArchiveFileTypeUpdateManifest] = &I_update_10px,
[ArchiveFileTypeFolder] = &I_dir_10px, [ArchiveFileTypeFolder] = &I_dir_10px,
[ArchiveFileTypeUnknown] = &I_unknown_10px, [ArchiveFileTypeUnknown] = &I_unknown_10px,
[ArchiveFileTypeLoading] = &I_unknown_10px, // TODO loading icon [ArchiveFileTypeLoading] = &I_loading_10px,
}; };
void archive_browser_set_callback( 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); 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) { static void draw_list(Canvas* canvas, ArchiveBrowserViewModel* model) {
furi_assert(model); furi_assert(model);
@ -107,8 +125,8 @@ static void draw_list(Canvas* canvas, ArchiveBrowserViewModel* model) {
bool scrollbar = model->item_cnt > 4; bool scrollbar = model->item_cnt > 4;
for(uint32_t i = 0; i < MIN(model->item_cnt, MENU_ITEMS); ++i) { for(uint32_t i = 0; i < MIN(model->item_cnt, MENU_ITEMS); ++i) {
string_t str_buff; string_t str_buf;
char cstr_buff[MAX_NAME_LEN]; string_init(str_buf);
int32_t idx = CLAMP((uint32_t)(i + model->list_offset), model->item_cnt, 0u); 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; 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)) { if(archive_is_item_in_array(model, idx)) {
ArchiveFile_t* file = files_array_get( ArchiveFile_t* file = files_array_get(
model->files, CLAMP(idx - model->array_offset, (int32_t)(array_size - 1), 0)); 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); path_extract_filename(file->path, str_buf, archive_is_known_app(file->type));
archive_trim_file_path(cstr_buff, archive_is_known_app(file->type));
string_init_set_str(str_buff, cstr_buff);
file_type = file->type; file_type = file->type;
} else { } else {
string_init_set_str(str_buff, "---"); string_set_str(str_buf, "---");
} }
elements_string_fit_width( 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) { if(model->item_idx == idx) {
archive_draw_frame(canvas, i, scrollbar, model->move_fav); 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_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) { if(scrollbar) {
@ -190,7 +206,9 @@ void archive_view_render(Canvas* canvas, void* mdl) {
archive_render_status_bar(canvas, 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); draw_list(canvas, model);
} else { } else {
canvas_draw_str_aligned( canvas_draw_str_aligned(
@ -350,25 +368,31 @@ ArchiveBrowserView* browser_alloc() {
view_set_draw_callback(browser->view, archive_view_render); view_set_draw_callback(browser->view, archive_view_render);
view_set_input_callback(browser->view, archive_view_input); 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( with_view_model(
browser->view, (ArchiveBrowserViewModel * model) { browser->view, (ArchiveBrowserViewModel * model) {
files_array_init(model->files); files_array_init(model->files);
idx_last_array_init(model->idx_last); model->tab_idx = TAB_DEFAULT;
return true; 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; return browser;
} }
void browser_free(ArchiveBrowserView* browser) { void browser_free(ArchiveBrowserView* browser) {
furi_assert(browser); furi_assert(browser);
file_browser_worker_free(browser->worker);
with_view_model( with_view_model(
browser->view, (ArchiveBrowserViewModel * model) { browser->view, (ArchiveBrowserViewModel * model) {
files_array_clear(model->files); files_array_clear(model->files);
idx_last_array_clear(model->idx_last);
return false; return false;
}); });

View File

@ -8,6 +8,7 @@
#include <storage/storage.h> #include <storage/storage.h>
#include "../helpers/archive_files.h" #include "../helpers/archive_files.h"
#include "../helpers/archive_favorites.h" #include "../helpers/archive_favorites.h"
#include "gui/modules/file_browser_worker.h"
#define MAX_LEN_PX 110 #define MAX_LEN_PX 110
#define MAX_NAME_LEN 255 #define MAX_NAME_LEN 255
@ -34,7 +35,7 @@ typedef enum {
ArchiveBrowserEventFileMenuClose, ArchiveBrowserEventFileMenuClose,
ArchiveBrowserEventFileMenuRun, ArchiveBrowserEventFileMenuRun,
ArchiveBrowserEventFileMenuPin, ArchiveBrowserEventFileMenuPin,
ArchiveBrowserEventFileMenuAction, ArchiveBrowserEventFileMenuRename,
ArchiveBrowserEventFileMenuDelete, ArchiveBrowserEventFileMenuDelete,
ArchiveBrowserEventEnterDir, ArchiveBrowserEventEnterDir,
@ -48,7 +49,7 @@ typedef enum {
ArchiveBrowserEventLoadPrevItems, ArchiveBrowserEventLoadPrevItems,
ArchiveBrowserEventLoadNextItems, ArchiveBrowserEventLoadNextItems,
ArchiveBrowserEventLoaderAppExit, ArchiveBrowserEventListRefresh,
ArchiveBrowserEventExit, ArchiveBrowserEventExit,
} ArchiveBrowserEvent; } ArchiveBrowserEvent;
@ -56,7 +57,7 @@ typedef enum {
static const uint8_t file_menu_actions[MENU_ITEMS] = { static const uint8_t file_menu_actions[MENU_ITEMS] = {
[0] = ArchiveBrowserEventFileMenuRun, [0] = ArchiveBrowserEventFileMenuRun,
[1] = ArchiveBrowserEventFileMenuPin, [1] = ArchiveBrowserEventFileMenuPin,
[2] = ArchiveBrowserEventFileMenuAction, [2] = ArchiveBrowserEventFileMenuRename,
[3] = ArchiveBrowserEventFileMenuDelete, [3] = ArchiveBrowserEventFileMenuDelete,
}; };
@ -72,23 +73,23 @@ typedef enum {
struct ArchiveBrowserView { struct ArchiveBrowserView {
View* view; View* view;
BrowserWorker* worker;
ArchiveBrowserViewCallback callback; ArchiveBrowserViewCallback callback;
void* context; void* context;
string_t path; string_t path;
InputKey last_tab_switch_dir;
bool is_root;
}; };
ARRAY_DEF(idx_last_array, int32_t)
typedef struct { typedef struct {
ArchiveTabEnum tab_idx; ArchiveTabEnum tab_idx;
ArchiveTabEnum last_tab;
files_array_t files; files_array_t files;
idx_last_array_t idx_last;
uint8_t menu_idx; uint8_t menu_idx;
bool move_fav;
bool menu; bool menu;
bool move_fav;
bool list_loading; bool list_loading;
bool folder_loading;
uint32_t item_cnt; uint32_t item_cnt;
int32_t item_idx; int32_t item_idx;

View File

@ -155,7 +155,7 @@ void bad_usb_set_file_name(BadUsb* bad_usb, const char* name) {
furi_assert(name); furi_assert(name);
with_view_model( with_view_model(
bad_usb->view, (BadUsbModel * model) { bad_usb->view, (BadUsbModel * model) {
strncpy(model->file_name, name, MAX_NAME_LEN); strlcpy(model->file_name, name, MAX_NAME_LEN);
return true; return true;
}); });
} }

View File

@ -3,14 +3,9 @@
#include <applications/cli/cli.h> #include <applications/cli/cli.h>
#include <lib/toolbox/args.h> #include <lib/toolbox/args.h>
#include "ble.h"
#include "bt_settings.h" #include "bt_settings.h"
#include "bt_service/bt.h"
static const char* bt_cli_address_types[] = {
"Public Device Address",
"Random Device Address",
"Public Identity Address",
"Random (Static) Identity Address",
};
static void bt_cli_command_hci_info(Cli* cli, string_t args, void* context) { static void bt_cli_command_hci_info(Cli* cli, string_t args, void* context) {
UNUSED(cli); UNUSED(cli);
@ -38,7 +33,9 @@ static void bt_cli_command_carrier_tx(Cli* cli, string_t args, void* context) {
break; 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("Transmitting carrier at %d channel at %d dB power\r\n", channel, power);
printf("Press CTRL+C to stop\r\n"); printf("Press CTRL+C to stop\r\n");
furi_hal_bt_start_tone_tx(channel, 0x19 + power); 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); osDelay(250);
} }
furi_hal_bt_stop_tone_tx(); furi_hal_bt_stop_tone_tx();
bt_set_profile(bt, BtProfileSerial);
furi_record_close("bt");
} while(false); } while(false);
} }
@ -60,7 +60,9 @@ static void bt_cli_command_carrier_rx(Cli* cli, string_t args, void* context) {
break; 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("Receiving carrier at %d channel\r\n", channel);
printf("Press CTRL+C to stop\r\n"); 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(); furi_hal_bt_stop_packet_test();
bt_set_profile(bt, BtProfileSerial);
furi_record_close("bt");
} while(false); } while(false);
} }
@ -102,7 +107,9 @@ static void bt_cli_command_packet_tx(Cli* cli, string_t args, void* context) {
break; break;
} }
furi_hal_bt_stop_advertising(); Bt* bt = furi_record_open("bt");
bt_disconnect(bt);
furi_hal_bt_reinit();
printf( printf(
"Transmitting %d pattern packet at %d channel at %d M datarate\r\n", "Transmitting %d pattern packet at %d channel at %d M datarate\r\n",
pattern, 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(); furi_hal_bt_stop_packet_test();
printf("Transmitted %lu packets", furi_hal_bt_get_transmitted_packets()); printf("Transmitted %lu packets", furi_hal_bt_get_transmitted_packets());
bt_set_profile(bt, BtProfileSerial);
furi_record_close("bt");
} while(false); } while(false);
} }
@ -135,7 +144,9 @@ static void bt_cli_command_packet_rx(Cli* cli, string_t args, void* context) {
break; 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("Receiving packets at %d channel at %d M datarate\r\n", channel, datarate);
printf("Press CTRL+C to stop\r\n"); printf("Press CTRL+C to stop\r\n");
furi_hal_bt_start_packet_rx(channel, datarate); furi_hal_bt_start_packet_rx(channel, datarate);
@ -147,51 +158,23 @@ static void bt_cli_command_packet_rx(Cli* cli, string_t args, void* context) {
} }
uint16_t packets_received = furi_hal_bt_stop_packet_test(); uint16_t packets_received = furi_hal_bt_stop_packet_test();
printf("Received %hu packets", packets_received); printf("Received %hu packets", packets_received);
bt_set_profile(bt, BtProfileSerial);
furi_record_close("bt");
} while(false); } 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);
}
static void bt_cli_print_usage() { static void bt_cli_print_usage() {
printf("Usage:\r\n"); printf("Usage:\r\n");
printf("bt <cmd> <args>\r\n"); printf("bt <cmd> <args>\r\n");
printf("Cmd list:\r\n"); printf("Cmd list:\r\n");
printf("\thci_info\t - HCI info\r\n"); printf("\thci_info\t - HCI info\r\n");
if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug) && if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug) && furi_hal_bt_is_testing_supported()) {
furi_hal_bt_get_radio_stack() == FuriHalBtStackHciLayer) {
printf("\ttx_carrier <channel:0-39> <power:0-6>\t - start tx carrier test\r\n"); printf("\ttx_carrier <channel:0-39> <power:0-6>\t - start tx carrier test\r\n");
printf("\trx_carrier <channel:0-39>\t - start rx carrier test\r\n"); printf("\trx_carrier <channel:0-39>\t - start rx carrier test\r\n");
printf("\ttx_pt <channel:0-39> <pattern:0-5> <datarate:1-2>\t - start tx packet test\r\n"); printf(
printf("\trx_pt <channel:0-39> <datarate:1-2>\t - start rx packer test\r\n"); "\ttx_packet <channel:0-39> <pattern:0-5> <datarate:1-2>\t - start tx packet test\r\n");
printf("\tscan\t - start scanner\r\n"); printf("\trx_packet <channel:0-39> <datarate:1-2>\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); bt_cli_command_hci_info(cli, args, NULL);
break; break;
} }
if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug) && if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug) && furi_hal_bt_is_testing_supported()) {
furi_hal_bt_get_radio_stack() == FuriHalBtStackHciLayer) { if(string_cmp_str(cmd, "tx_carrier") == 0) {
if(string_cmp_str(cmd, "carrier_tx") == 0) {
bt_cli_command_carrier_tx(cli, args, NULL); bt_cli_command_carrier_tx(cli, args, NULL);
break; 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); bt_cli_command_carrier_rx(cli, args, NULL);
break; 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); bt_cli_command_packet_tx(cli, args, NULL);
break; 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); bt_cli_command_packet_rx(cli, args, NULL);
break; break;
} }
if(string_cmp_str(cmd, "scan") == 0) {
bt_cli_command_scan(cli, args, NULL);
break;
}
} }
bt_cli_print_usage(); bt_cli_print_usage();

View File

@ -97,8 +97,8 @@ void bt_debug_app_free(BtDebugApp* app) {
int32_t bt_debug_app(void* p) { int32_t bt_debug_app(void* p) {
UNUSED(p); UNUSED(p);
if(furi_hal_bt_get_radio_stack() != FuriHalBtStackHciLayer) { if(!furi_hal_bt_is_testing_supported()) {
FURI_LOG_E(TAG, "Incorrect radio stack, replace with HciLayer for tests."); FURI_LOG_E(TAG, "Incorrect radio stack: radio testing fetures are absent.");
DialogsApp* dialogs = furi_record_open("dialogs"); DialogsApp* dialogs = furi_record_open("dialogs");
dialog_message_show_storage_error(dialogs, "Incorrect\nRadioStack"); dialog_message_show_storage_error(dialogs, "Incorrect\nRadioStack");
return 255; return 255;

View File

@ -293,17 +293,20 @@ static void bt_show_warning(Bt* bt, const char* text) {
dialog_message_show(bt->dialogs, bt->dialog_message); 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) { static void bt_change_profile(Bt* bt, BtMessage* message) {
FuriHalBtStack stack = furi_hal_bt_get_radio_stack(); if(furi_hal_bt_is_ble_gatt_gap_supported()) {
if(stack == FuriHalBtStackLight) {
bt_settings_load(&bt->bt_settings); bt_settings_load(&bt->bt_settings);
if(bt->profile == BtProfileSerial && bt->rpc_session) { bt_close_rpc_connection(bt);
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;
}
FuriHalBtProfile furi_profile; FuriHalBtProfile furi_profile;
if(message->data.profile == BtProfileHidKeyboard) { 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); 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() { int32_t bt_srv() {
Bt* bt = bt_alloc(); Bt* bt = bt_alloc();
@ -350,14 +358,8 @@ int32_t bt_srv() {
if(!furi_hal_bt_start_radio_stack()) { if(!furi_hal_bt_start_radio_stack()) {
FURI_LOG_E(TAG, "Radio stack start failed"); FURI_LOG_E(TAG, "Radio stack start failed");
} }
FuriHalBtStack stack_type = furi_hal_bt_get_radio_stack();
if(stack_type == FuriHalBtStackUnknown) { if(furi_hal_bt_is_ble_gatt_gap_supported()) {
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_start_app(FuriHalBtProfileSerial, bt_on_gap_event_callback, bt)) { if(!furi_hal_bt_start_app(FuriHalBtProfileSerial, bt_on_gap_event_callback, bt)) {
FURI_LOG_E(TAG, "BLE App start failed"); FURI_LOG_E(TAG, "BLE App start failed");
} else { } else {
@ -366,6 +368,9 @@ int32_t bt_srv() {
} }
furi_hal_bt_set_key_storage_change_callback(bt_on_key_storage_change_callback, bt); 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); furi_record_create("bt", bt);
@ -392,6 +397,8 @@ int32_t bt_srv() {
bt_keys_storage_save(bt); bt_keys_storage_save(bt);
} else if(message.type == BtMessageTypeSetProfile) { } else if(message.type == BtMessageTypeSetProfile) {
bt_change_profile(bt, &message); bt_change_profile(bt, &message);
} else if(message.type == BtMessageTypeDisconnect) {
bt_close_connection(bt);
} else if(message.type == BtMessageTypeForgetBondedDevices) { } else if(message.type == BtMessageTypeForgetBondedDevices) {
bt_keys_storage_delete(bt); bt_keys_storage_delete(bt);
} }

View File

@ -33,6 +33,12 @@ typedef void (*BtStatusChangedCallback)(BtStatus status, void* context);
*/ */
bool bt_set_profile(Bt* bt, BtProfile profile); 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 /** Set callback for Bluetooth status change notification
* *
* @param bt Bt instance * @param bt Bt instance

View File

@ -14,6 +14,16 @@ bool bt_set_profile(Bt* bt, BtProfile profile) {
return result; 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) { void bt_set_status_changed_callback(Bt* bt, BtStatusChangedCallback callback, void* context) {
furi_assert(bt); furi_assert(bt);

View File

@ -25,6 +25,7 @@ typedef enum {
BtMessageTypePinCodeShow, BtMessageTypePinCodeShow,
BtMessageTypeKeysStorageUpdated, BtMessageTypeKeysStorageUpdated,
BtMessageTypeSetProfile, BtMessageTypeSetProfile,
BtMessageTypeDisconnect,
BtMessageTypeForgetBondedDevices, BtMessageTypeForgetBondedDevices,
} BtMessageType; } BtMessageType;

View File

@ -39,8 +39,7 @@ void bt_settings_scene_start_on_enter(void* context) {
VariableItemList* var_item_list = app->var_item_list; VariableItemList* var_item_list = app->var_item_list;
VariableItem* item; VariableItem* item;
FuriHalBtStack stack_type = furi_hal_bt_get_radio_stack(); if(furi_hal_bt_is_ble_gatt_gap_supported()) {
if(stack_type == FuriHalBtStackLight) {
item = variable_item_list_add( item = variable_item_list_add(
var_item_list, var_item_list,
"Bluetooth", "Bluetooth",

View File

@ -0,0 +1,228 @@
#include "cli_command_gpio.h"
#include <furi.h>
#include <furi_hal.h>
#include <lib/toolbox/args.h>
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 <cmd> <args>\r\n");
printf("Cmd list:\r\n");
printf("\tmode <pin_name> <0|1>\t - Set gpio mode: 0 - input, 1 - output\r\n");
printf("\tset <pin_name> <0|1>\t - Set gpio value\r\n");
printf("\tread <pin_name>\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", "<pin_name> <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", "<pin_name> <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);
}

View File

@ -0,0 +1,5 @@
#pragma once
#include "cli_i.h"
void cli_command_gpio(Cli* cli, string_t args, void* context);

View File

@ -1,6 +1,7 @@
#include "cli_commands.h" #include "cli_commands.h"
#include "cli_command_gpio.h"
#include <furi_hal.h> #include <furi_hal.h>
#include <furi_hal_gpio.h>
#include <furi_hal_info.h> #include <furi_hal_info.h>
#include <task_control_block.h> #include <task_control_block.h>
#include <time.h> #include <time.h>
@ -248,101 +249,6 @@ void cli_command_led(Cli* cli, string_t args, void* context) {
furi_record_close("notification"); 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", "<pin_name> <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) { void cli_command_ps(Cli* cli, string_t args, void* context) {
UNUSED(cli); UNUSED(cli);
UNUSED(args); 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, "vibro", CliCommandFlagDefault, cli_command_vibro, NULL);
cli_add_command(cli, "led", CliCommandFlagDefault, cli_command_led, 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); cli_add_command(cli, "i2c", CliCommandFlagDefault, cli_command_i2c, NULL);
} }

View File

@ -1,3 +1,4 @@
#include "furi/common_defines.h"
#include <furi.h> #include <furi.h>
#include <furi_hal.h> #include <furi_hal.h>
@ -6,8 +7,6 @@
#include <notification/notification_messages.h> #include <notification/notification_messages.h>
#define BLINK_COLOR_COUNT 7
typedef enum { typedef enum {
BlinkEventTypeTick, BlinkEventTypeTick,
BlinkEventTypeInput, BlinkEventTypeInput,
@ -18,6 +17,42 @@ typedef struct {
InputEvent input; InputEvent input;
} BlinkEvent; } 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) { static void blink_test_update(void* ctx) {
furi_assert(ctx); furi_assert(ctx);
osMessageQueueId_t event_queue = ctx; osMessageQueueId_t event_queue = ctx;
@ -58,16 +93,6 @@ int32_t blink_test_app(void* p) {
NotificationApp* notifications = furi_record_open("notification"); 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; uint8_t state = 0;
BlinkEvent event; BlinkEvent event;
@ -78,14 +103,16 @@ int32_t blink_test_app(void* p) {
break; break;
} }
} else { } else {
notification_message(notifications, colors[state]); notification_message(notifications, blink_test_colors[state]);
state++; state++;
if(state >= BLINK_COLOR_COUNT) { if(state >= COUNT_OF(blink_test_colors)) {
state = 0; state = 0;
} }
} }
} }
notification_message(notifications, &blink_test_sequence_hw_blink_stop);
osTimerDelete(timer); osTimerDelete(timer);
gui_remove_view_port(gui, view_port); gui_remove_view_port(gui, view_port);

View File

@ -42,8 +42,8 @@ void usb_test_submenu_callback(void* context, uint32_t index) {
} else if(index == UsbTestSubmenuIndexHidWithParams) { } else if(index == UsbTestSubmenuIndexHidWithParams) {
app->hid_cfg.vid = 0x1234; app->hid_cfg.vid = 0x1234;
app->hid_cfg.pid = 0xabcd; app->hid_cfg.pid = 0xabcd;
strncpy(app->hid_cfg.manuf, "WEN", sizeof(app->hid_cfg.manuf)); strlcpy(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.product, "FLIP", sizeof(app->hid_cfg.product));
furi_hal_usb_set_config(&usb_hid, &app->hid_cfg); furi_hal_usb_set_config(&usb_hid, &app->hid_cfg);
} else if(index == UsbTestSubmenuIndexHidU2F) { } else if(index == UsbTestSubmenuIndexHidU2F) {
furi_hal_usb_set_config(&usb_hid_u2f, NULL); furi_hal_usb_set_config(&usb_hid_u2f, NULL);

View File

@ -5,7 +5,7 @@
#include <stdbool.h> #include <stdbool.h>
#include <toolbox/saved_struct.h> #include <toolbox/saved_struct.h>
#define DESKTOP_SETTINGS_VER (3) #define DESKTOP_SETTINGS_VER (4)
#define DESKTOP_SETTINGS_PATH "/int/desktop.settings" #define DESKTOP_SETTINGS_PATH "/int/desktop.settings"
#define DESKTOP_SETTINGS_MAGIC (0x17) #define DESKTOP_SETTINGS_MAGIC (0x17)
#define PIN_MAX_LENGTH 12 #define PIN_MAX_LENGTH 12
@ -37,7 +37,8 @@ typedef struct {
} PinCode; } PinCode;
typedef struct { typedef struct {
uint16_t favorite; uint16_t favorite_primary;
uint16_t favorite_secondary;
PinCode pin_code; PinCode pin_code;
uint8_t is_locked; uint8_t is_locked;
uint32_t auto_lock_delay_ms; uint32_t auto_lock_delay_ms;

View File

@ -38,5 +38,4 @@ typedef struct {
bool pincode_buffer_filled; bool pincode_buffer_filled;
uint8_t menu_idx; uint8_t menu_idx;
} DesktopSettingsApp; } DesktopSettingsApp;

View File

@ -21,8 +21,17 @@ void desktop_settings_scene_favorite_on_enter(void* context) {
app); app);
} }
submenu_set_header(app->submenu, "Quick access app:"); uint32_t primary_favorite =
submenu_set_selected_item(app->submenu, app->settings.favorite); scene_manager_get_scene_state(app->scene_manager, DesktopSettingsAppSceneFavorite);
submenu_set_header(
app->submenu, primary_favorite ? "Primary favorite app:" : "Secondary favorite app:");
if(primary_favorite) {
submenu_set_selected_item(app->submenu, app->settings.favorite_primary);
} else {
submenu_set_selected_item(app->submenu, app->settings.favorite_secondary);
}
view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewMenu); view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewMenu);
} }
@ -30,14 +39,17 @@ bool desktop_settings_scene_favorite_on_event(void* context, SceneManagerEvent e
DesktopSettingsApp* app = context; DesktopSettingsApp* app = context;
bool consumed = false; bool consumed = false;
uint32_t primary_favorite =
scene_manager_get_scene_state(app->scene_manager, DesktopSettingsAppSceneFavorite);
if(event.type == SceneManagerEventTypeCustom) { if(event.type == SceneManagerEventTypeCustom) {
switch(event.event) { if(primary_favorite) {
default: app->settings.favorite_primary = event.event;
app->settings.favorite = event.event; } else {
scene_manager_previous_scene(app->scene_manager); app->settings.favorite_secondary = event.event;
consumed = true;
break;
} }
scene_manager_previous_scene(app->scene_manager);
consumed = true;
} }
return consumed; return consumed;
} }

View File

@ -4,9 +4,10 @@
#include "../desktop_settings_app.h" #include "../desktop_settings_app.h"
#include "desktop_settings_scene.h" #include "desktop_settings_scene.h"
#define SCENE_EVENT_SELECT_FAVORITE 0 #define SCENE_EVENT_SELECT_FAVORITE_PRIMARY 0
#define SCENE_EVENT_SELECT_PIN_SETUP 1 #define SCENE_EVENT_SELECT_FAVORITE_SECONDARY 1
#define SCENE_EVENT_SELECT_AUTO_LOCK_DELAY 2 #define SCENE_EVENT_SELECT_PIN_SETUP 2
#define SCENE_EVENT_SELECT_AUTO_LOCK_DELAY 3
#define AUTO_LOCK_DELAY_COUNT 6 #define AUTO_LOCK_DELAY_COUNT 6
const char* const auto_lock_delay_text[AUTO_LOCK_DELAY_COUNT] = { const char* const auto_lock_delay_text[AUTO_LOCK_DELAY_COUNT] = {
@ -41,7 +42,9 @@ void desktop_settings_scene_start_on_enter(void* context) {
VariableItem* item; VariableItem* item;
uint8_t value_index; uint8_t value_index;
variable_item_list_add(variable_item_list, "Favorite App", 1, NULL, NULL); variable_item_list_add(variable_item_list, "Primary Favorite App", 1, NULL, NULL);
variable_item_list_add(variable_item_list, "Secondary Favorite App", 1, NULL, NULL);
variable_item_list_add(variable_item_list, "PIN Setup", 1, NULL, NULL); variable_item_list_add(variable_item_list, "PIN Setup", 1, NULL, NULL);
@ -68,7 +71,13 @@ bool desktop_settings_scene_start_on_event(void* context, SceneManagerEvent even
if(event.type == SceneManagerEventTypeCustom) { if(event.type == SceneManagerEventTypeCustom) {
switch(event.event) { switch(event.event) {
case SCENE_EVENT_SELECT_FAVORITE: case SCENE_EVENT_SELECT_FAVORITE_PRIMARY:
scene_manager_set_scene_state(app->scene_manager, DesktopSettingsAppSceneFavorite, 1);
scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneFavorite);
consumed = true;
break;
case SCENE_EVENT_SELECT_FAVORITE_SECONDARY:
scene_manager_set_scene_state(app->scene_manager, DesktopSettingsAppSceneFavorite, 0);
scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneFavorite); scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneFavorite);
consumed = true; consumed = true;
break; break;

View File

@ -103,16 +103,40 @@ bool desktop_scene_main_on_event(void* context, SceneManagerEvent event) {
consumed = true; consumed = true;
break; break;
case DesktopMainEventOpenFavorite: case DesktopMainEventOpenPowerOff: {
LoaderStatus status = loader_start(desktop->loader, "Power", "off");
if(status != LoaderStatusOk) {
FURI_LOG_E(TAG, "loader_start failed: %d", status);
}
consumed = true;
break;
}
case DesktopMainEventOpenFavoritePrimary:
LOAD_DESKTOP_SETTINGS(&desktop->settings); LOAD_DESKTOP_SETTINGS(&desktop->settings);
if(desktop->settings.favorite < FLIPPER_APPS_COUNT) { if(desktop->settings.favorite_primary < FLIPPER_APPS_COUNT) {
LoaderStatus status = loader_start( LoaderStatus status = loader_start(
desktop->loader, FLIPPER_APPS[desktop->settings.favorite].name, NULL); desktop->loader, FLIPPER_APPS[desktop->settings.favorite_primary].name, NULL);
if(status != LoaderStatusOk) { if(status != LoaderStatusOk) {
FURI_LOG_E(TAG, "loader_start failed: %d", status); FURI_LOG_E(TAG, "loader_start failed: %d", status);
} }
} else { } else {
FURI_LOG_E(TAG, "Can't find favorite application"); FURI_LOG_E(TAG, "Can't find primary favorite application");
}
consumed = true;
break;
case DesktopMainEventOpenFavoriteSecondary:
LOAD_DESKTOP_SETTINGS(&desktop->settings);
if(desktop->settings.favorite_secondary < FLIPPER_APPS_COUNT) {
LoaderStatus status = loader_start(
desktop->loader,
FLIPPER_APPS[desktop->settings.favorite_secondary].name,
NULL);
if(status != LoaderStatusOk) {
FURI_LOG_E(TAG, "loader_start failed: %d", status);
}
} else {
FURI_LOG_E(TAG, "Can't find secondary favorite application");
} }
consumed = true; consumed = true;
break; break;

View File

@ -3,10 +3,12 @@
typedef enum { typedef enum {
DesktopMainEventOpenLockMenu, DesktopMainEventOpenLockMenu,
DesktopMainEventOpenArchive, DesktopMainEventOpenArchive,
DesktopMainEventOpenFavorite, DesktopMainEventOpenFavoritePrimary,
DesktopMainEventOpenFavoriteSecondary,
DesktopMainEventOpenMenu, DesktopMainEventOpenMenu,
DesktopMainEventOpenDebug, DesktopMainEventOpenDebug,
DesktopMainEventOpenPassport, /**< Broken, don't use it */ DesktopMainEventOpenPassport, /**< Broken, don't use it */
DesktopMainEventOpenPowerOff,
DesktopLockedEventUnlocked, DesktopLockedEventUnlocked,
DesktopLockedEventUpdate, DesktopLockedEventUpdate,

View File

@ -13,8 +13,16 @@ struct DesktopMainView {
View* view; View* view;
DesktopMainViewCallback callback; DesktopMainViewCallback callback;
void* context; void* context;
TimerHandle_t poweroff_timer;
}; };
#define DESKTOP_MAIN_VIEW_POWEROFF_TIMEOUT 5000
static void desktop_main_poweroff_timer_callback(TimerHandle_t timer) {
DesktopMainView* main_view = pvTimerGetTimerID(timer);
main_view->callback(DesktopMainEventOpenPowerOff, main_view->context);
}
void desktop_main_set_callback( void desktop_main_set_callback(
DesktopMainView* main_view, DesktopMainView* main_view,
DesktopMainViewCallback callback, DesktopMainViewCallback callback,
@ -44,13 +52,26 @@ bool desktop_main_input(InputEvent* event, void* context) {
} else if(event->key == InputKeyDown) { } else if(event->key == InputKeyDown) {
main_view->callback(DesktopMainEventOpenArchive, main_view->context); main_view->callback(DesktopMainEventOpenArchive, main_view->context);
} else if(event->key == InputKeyLeft) { } else if(event->key == InputKeyLeft) {
main_view->callback(DesktopMainEventOpenFavorite, main_view->context); main_view->callback(DesktopMainEventOpenFavoritePrimary, main_view->context);
} else if(event->key == InputKeyRight) { } else if(event->key == InputKeyRight) {
main_view->callback(DesktopMainEventOpenPassport, main_view->context); main_view->callback(DesktopMainEventOpenPassport, main_view->context);
} }
} else if(event->type == InputTypeLong) { } else if(event->type == InputTypeLong) {
if(event->key == InputKeyDown) { if(event->key == InputKeyDown) {
main_view->callback(DesktopMainEventOpenDebug, main_view->context); main_view->callback(DesktopMainEventOpenDebug, main_view->context);
} else if(event->key == InputKeyLeft) {
main_view->callback(DesktopMainEventOpenFavoriteSecondary, main_view->context);
}
}
if(event->key == InputKeyBack) {
if(event->type == InputTypePress) {
xTimerChangePeriod(
main_view->poweroff_timer,
pdMS_TO_TICKS(DESKTOP_MAIN_VIEW_POWEROFF_TIMEOUT),
portMAX_DELAY);
} else if(event->type == InputTypeRelease) {
xTimerStop(main_view->poweroff_timer, portMAX_DELAY);
} }
} }
@ -65,11 +86,19 @@ DesktopMainView* desktop_main_alloc() {
view_set_context(main_view->view, main_view); view_set_context(main_view->view, main_view);
view_set_input_callback(main_view->view, desktop_main_input); view_set_input_callback(main_view->view, desktop_main_input);
main_view->poweroff_timer = xTimerCreate(
NULL,
pdMS_TO_TICKS(DESKTOP_MAIN_VIEW_POWEROFF_TIMEOUT),
pdFALSE,
main_view,
desktop_main_poweroff_timer_callback);
return main_view; return main_view;
} }
void desktop_main_free(DesktopMainView* main_view) { void desktop_main_free(DesktopMainView* main_view) {
furi_assert(main_view); furi_assert(main_view);
view_free(main_view->view); view_free(main_view->view);
osTimerDelete(main_view->poweroff_timer);
free(main_view); free(main_view);
} }

View File

@ -22,10 +22,13 @@ typedef enum {
WorkerEvtLoad = (1 << 1), WorkerEvtLoad = (1 << 1),
WorkerEvtFolderEnter = (1 << 2), WorkerEvtFolderEnter = (1 << 2),
WorkerEvtFolderExit = (1 << 3), WorkerEvtFolderExit = (1 << 3),
WorkerEvtFolderRefresh = (1 << 4),
WorkerEvtConfigChange = (1 << 5),
} WorkerEvtFlags; } WorkerEvtFlags;
#define WORKER_FLAGS_ALL \ #define WORKER_FLAGS_ALL \
(WorkerEvtStop | WorkerEvtLoad | WorkerEvtFolderEnter | WorkerEvtFolderExit) (WorkerEvtStop | WorkerEvtLoad | WorkerEvtFolderEnter | WorkerEvtFolderExit | \
WorkerEvtFolderRefresh | WorkerEvtConfigChange)
ARRAY_DEF(idx_last_array, int32_t) 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); string_init_set_str(path, BROWSER_ROOT);
browser->item_sel_idx = -1; 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_t filename;
string_init(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) { while(1) {
uint32_t flags = osThreadFlagsWait(WORKER_FLAGS_ALL, osFlagsWaitAny, osWaitForever); uint32_t flags = osThreadFlagsWait(WORKER_FLAGS_ALL, osFlagsWaitAny, osWaitForever);
furi_assert((flags & osFlagsError) == 0); 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) { if(flags & WorkerEvtFolderEnter) {
string_set(path, browser->path_next); string_set(path, browser->path_next);
bool is_root = browser_folder_check_and_switch(path); 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) { if(flags & WorkerEvtLoad) {
FURI_LOG_D(TAG, "Load offset: %u cnt: %u", browser->load_offset, browser->load_count); 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); 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; 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) { void file_browser_worker_folder_enter(BrowserWorker* browser, string_t path, int32_t item_idx) {
furi_assert(browser); furi_assert(browser);
string_set(browser->path_next, path); 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); 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) { void file_browser_worker_load(BrowserWorker* browser, uint32_t offset, uint32_t count) {
furi_assert(browser); furi_assert(browser);
browser->load_offset = offset; browser->load_offset = offset;

View File

@ -44,10 +44,18 @@ void file_browser_worker_set_long_load_callback(
BrowserWorker* browser, BrowserWorker* browser,
BrowserWorkerLongLoadCallback cb); 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_enter(BrowserWorker* browser, string_t path, int32_t item_idx);
void file_browser_worker_folder_exit(BrowserWorker* browser); 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); void file_browser_worker_load(BrowserWorker* browser, uint32_t offset, uint32_t count);
#ifdef __cplusplus #ifdef __cplusplus

View File

@ -6,16 +6,47 @@
#include <toolbox/path.h> #include <toolbox/path.h>
#include <flipper_format/flipper_format.h> #include <flipper_format/flipper_format.h>
static const NotificationSequence sequence_blink_start_cyan = {
&message_blink_start_10,
&message_blink_set_color_cyan,
&message_do_not_reset,
NULL,
};
static const NotificationSequence sequence_blink_start_magenta = {
&message_blink_start_10,
&message_blink_set_color_magenta,
&message_do_not_reset,
NULL,
};
static const NotificationSequence sequence_blink_set_yellow = {
&message_blink_set_color_yellow,
NULL,
};
static const NotificationSequence sequence_blink_set_magenta = {
&message_blink_set_color_magenta,
NULL,
};
static const NotificationSequence sequence_blink_stop = {
&message_blink_stop,
NULL,
};
static const NotificationSequence* ibutton_notification_sequences[] = { static const NotificationSequence* ibutton_notification_sequences[] = {
&sequence_error, &sequence_error,
&sequence_success, &sequence_success,
&sequence_blink_cyan_10, &sequence_blink_start_cyan,
&sequence_blink_magenta_10, &sequence_blink_start_magenta,
&sequence_blink_yellow_10, &sequence_blink_set_yellow,
&sequence_blink_set_magenta,
&sequence_set_red_255, &sequence_set_red_255,
&sequence_reset_red, &sequence_reset_red,
&sequence_set_green_255, &sequence_set_green_255,
&sequence_reset_green, &sequence_reset_green,
&sequence_blink_stop,
}; };
static void ibutton_make_app_folder(iButton* ibutton) { static void ibutton_make_app_folder(iButton* ibutton) {

View File

@ -64,13 +64,15 @@ typedef enum {
typedef enum { typedef enum {
iButtonNotificationMessageError, iButtonNotificationMessageError,
iButtonNotificationMessageSuccess, iButtonNotificationMessageSuccess,
iButtonNotificationMessageRead, iButtonNotificationMessageReadStart,
iButtonNotificationMessageEmulate, iButtonNotificationMessageEmulateStart,
iButtonNotificationMessageYellowBlink, iButtonNotificationMessageYellowBlink,
iButtonNotificationMessageEmulateBlink,
iButtonNotificationMessageRedOn, iButtonNotificationMessageRedOn,
iButtonNotificationMessageRedOff, iButtonNotificationMessageRedOff,
iButtonNotificationMessageGreenOn, iButtonNotificationMessageGreenOn,
iButtonNotificationMessageGreenOff, iButtonNotificationMessageGreenOff,
iButtonNotificationMessageBlinkStop,
} iButtonNotificationMessage; } iButtonNotificationMessage;
bool ibutton_file_select(iButton* ibutton); bool ibutton_file_select(iButton* ibutton);

View File

@ -1,4 +1,5 @@
#include "../ibutton_i.h" #include "../ibutton_i.h"
#include "furi/log.h"
#include <dolphin/dolphin.h> #include <dolphin/dolphin.h>
#include <toolbox/path.h> #include <toolbox/path.h>
@ -85,6 +86,8 @@ void ibutton_scene_emulate_on_enter(void* context) {
ibutton_worker_emulate_start(ibutton->key_worker, key); ibutton_worker_emulate_start(ibutton->key_worker, key);
string_clear(key_name); string_clear(key_name);
ibutton_notification_message(ibutton, iButtonNotificationMessageEmulateStart);
} }
bool ibutton_scene_emulate_on_event(void* context, SceneManagerEvent event) { bool ibutton_scene_emulate_on_event(void* context, SceneManagerEvent event) {
@ -93,7 +96,6 @@ bool ibutton_scene_emulate_on_event(void* context, SceneManagerEvent event) {
if(event.type == SceneManagerEventTypeTick) { if(event.type == SceneManagerEventTypeTick) {
consumed = true; consumed = true;
ibutton_notification_message(ibutton, iButtonNotificationMessageEmulate);
} else if(event.type == SceneManagerEventTypeCustom) { } else if(event.type == SceneManagerEventTypeCustom) {
consumed = true; consumed = true;
if(event.event == iButtonCustomEventWorkerEmulated) { if(event.event == iButtonCustomEventWorkerEmulated) {
@ -111,4 +113,5 @@ void ibutton_scene_emulate_on_exit(void* context) {
popup_set_header(popup, NULL, 0, 0, AlignCenter, AlignBottom); popup_set_header(popup, NULL, 0, 0, AlignCenter, AlignBottom);
popup_set_text(popup, NULL, 0, 0, AlignCenter, AlignTop); popup_set_text(popup, NULL, 0, 0, AlignCenter, AlignTop);
popup_set_icon(popup, 0, 0, NULL); popup_set_icon(popup, 0, 0, NULL);
ibutton_notification_message(ibutton, iButtonNotificationMessageBlinkStop);
} }

View File

@ -22,6 +22,8 @@ void ibutton_scene_read_on_enter(void* context) {
ibutton_worker_read_set_callback(worker, ibutton_scene_read_callback, ibutton); ibutton_worker_read_set_callback(worker, ibutton_scene_read_callback, ibutton);
ibutton_worker_read_start(worker, key); ibutton_worker_read_start(worker, key);
ibutton_notification_message(ibutton, iButtonNotificationMessageReadStart);
} }
bool ibutton_scene_read_on_event(void* context, SceneManagerEvent event) { bool ibutton_scene_read_on_event(void* context, SceneManagerEvent event) {
@ -31,7 +33,6 @@ bool ibutton_scene_read_on_event(void* context, SceneManagerEvent event) {
if(event.type == SceneManagerEventTypeTick) { if(event.type == SceneManagerEventTypeTick) {
consumed = true; consumed = true;
ibutton_notification_message(ibutton, iButtonNotificationMessageRead);
} else if(event.type == SceneManagerEventTypeCustom) { } else if(event.type == SceneManagerEventTypeCustom) {
consumed = true; consumed = true;
if(event.event == iButtonCustomEventWorkerRead) { if(event.event == iButtonCustomEventWorkerRead) {
@ -69,4 +70,6 @@ void ibutton_scene_read_on_exit(void* context) {
popup_set_header(popup, NULL, 0, 0, AlignCenter, AlignBottom); popup_set_header(popup, NULL, 0, 0, AlignCenter, AlignBottom);
popup_set_text(popup, NULL, 0, 0, AlignCenter, AlignTop); popup_set_text(popup, NULL, 0, 0, AlignCenter, AlignTop);
popup_set_icon(popup, 0, 0, NULL); popup_set_icon(popup, 0, 0, NULL);
ibutton_notification_message(ibutton, iButtonNotificationMessageBlinkStop);
} }

View File

@ -80,14 +80,14 @@ void ibutton_scene_write_on_enter(void* context) {
popup_set_icon(popup, 2, 10, &I_iButtonKey_49x44); popup_set_icon(popup, 2, 10, &I_iButtonKey_49x44);
scene_manager_set_scene_state(
ibutton->scene_manager, iButtonSceneWrite, iButtonSceneWriteStateDefault);
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewPopup); view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewPopup);
ibutton_worker_write_set_callback(worker, ibutton_scene_write_callback, ibutton); ibutton_worker_write_set_callback(worker, ibutton_scene_write_callback, ibutton);
ibutton_worker_write_start(worker, key); ibutton_worker_write_start(worker, key);
string_clear(key_name); string_clear(key_name);
ibutton_notification_message(ibutton, iButtonNotificationMessageEmulateStart);
} }
bool ibutton_scene_write_on_event(void* context, SceneManagerEvent event) { bool ibutton_scene_write_on_event(void* context, SceneManagerEvent event) {
@ -100,21 +100,13 @@ bool ibutton_scene_write_on_event(void* context, SceneManagerEvent event) {
if((event.event == iButtonWorkerWriteOK) || (event.event == iButtonWorkerWriteSameKey)) { if((event.event == iButtonWorkerWriteOK) || (event.event == iButtonWorkerWriteSameKey)) {
scene_manager_next_scene(scene_manager, iButtonSceneWriteSuccess); scene_manager_next_scene(scene_manager, iButtonSceneWriteSuccess);
} else if(event.event == iButtonWorkerWriteNoDetect) { } else if(event.event == iButtonWorkerWriteNoDetect) {
scene_manager_set_scene_state( ibutton_notification_message(ibutton, iButtonNotificationMessageEmulateBlink);
scene_manager, iButtonSceneWrite, iButtonSceneWriteStateDefault);
} else if(event.event == iButtonWorkerWriteCannotWrite) { } else if(event.event == iButtonWorkerWriteCannotWrite) {
scene_manager_set_scene_state( ibutton_notification_message(ibutton, iButtonNotificationMessageYellowBlink);
scene_manager, iButtonSceneWrite, iButtonSceneWriteStateBlinkYellow);
} }
} else if(event.type == SceneManagerEventTypeTick) { } else if(event.type == SceneManagerEventTypeTick) {
consumed = true; consumed = true;
if(scene_manager_get_scene_state(scene_manager, iButtonSceneWrite) ==
iButtonSceneWriteStateBlinkYellow) {
ibutton_notification_message(ibutton, iButtonNotificationMessageYellowBlink);
} else {
ibutton_notification_message(ibutton, iButtonNotificationMessageEmulate);
}
} }
return consumed; return consumed;
@ -127,4 +119,6 @@ void ibutton_scene_write_on_exit(void* context) {
popup_set_header(popup, NULL, 0, 0, AlignCenter, AlignBottom); popup_set_header(popup, NULL, 0, 0, AlignCenter, AlignBottom);
popup_set_text(popup, NULL, 0, 0, AlignCenter, AlignTop); popup_set_text(popup, NULL, 0, 0, AlignCenter, AlignTop);
popup_set_icon(popup, 0, 0, NULL); popup_set_icon(popup, 0, 0, NULL);
ibutton_notification_message(ibutton, iButtonNotificationMessageBlinkStop);
} }

View File

@ -1,6 +1,19 @@
#include "lfrfid_app_scene_emulate.h" #include "lfrfid_app_scene_emulate.h"
#include "furi/common_defines.h"
#include <dolphin/dolphin.h> #include <dolphin/dolphin.h>
static const NotificationSequence sequence_blink_start_magenta = {
&message_blink_start_10,
&message_blink_set_color_magenta,
&message_do_not_reset,
NULL,
};
static const NotificationSequence sequence_blink_stop = {
&message_blink_stop,
NULL,
};
void LfRfidAppSceneEmulate::on_enter(LfRfidApp* app, bool /* need_restore */) { void LfRfidAppSceneEmulate::on_enter(LfRfidApp* app, bool /* need_restore */) {
string_init(data_string); string_init(data_string);
@ -23,15 +36,14 @@ void LfRfidAppSceneEmulate::on_enter(LfRfidApp* app, bool /* need_restore */) {
app->view_controller.switch_to<PopupVM>(); app->view_controller.switch_to<PopupVM>();
app->worker.start_emulate(); app->worker.start_emulate();
notification_message(app->notification, &sequence_blink_start_magenta);
} }
bool LfRfidAppSceneEmulate::on_event(LfRfidApp* app, LfRfidApp::Event* event) { bool LfRfidAppSceneEmulate::on_event(LfRfidApp* app, LfRfidApp::Event* event) {
UNUSED(app);
UNUSED(event);
bool consumed = false; bool consumed = false;
if(event->type == LfRfidApp::EventType::Tick) {
notification_message(app->notification, &sequence_blink_magenta_10);
}
return consumed; return consumed;
} }
@ -39,4 +51,5 @@ void LfRfidAppSceneEmulate::on_exit(LfRfidApp* app) {
app->view_controller.get<PopupVM>()->clean(); app->view_controller.get<PopupVM>()->clean();
app->worker.stop_emulate(); app->worker.stop_emulate();
string_clear(data_string); string_clear(data_string);
notification_message(app->notification, &sequence_blink_stop);
} }

View File

@ -0,0 +1,166 @@
#include "nfc_debug_pcap.h"
#include <furi_hal_rtc.h>
#include <stream_buffer.h>
#define TAG "NfcDebugPcap"
#define PCAP_MAGIC 0xa1b2c3d4
#define PCAP_MAJOR 2
#define PCAP_MINOR 4
#define DLT_ISO_14443 264
#define DATA_PICC_TO_PCD 0xFF
#define DATA_PCD_TO_PICC 0xFE
#define DATA_PICC_TO_PCD_CRC_DROPPED 0xFB
#define DATA_PCD_TO_PICC_CRC_DROPPED 0xFA
#define NFC_DEBUG_PCAP_FILENAME "/ext/nfc/debug.pcap"
#define NFC_DEBUG_PCAP_BUFFER_SIZE 64
struct NfcDebugPcapWorker {
bool alive;
Storage* storage;
File* file;
StreamBufferHandle_t stream;
FuriThread* thread;
};
static File* nfc_debug_pcap_open(Storage* storage) {
File* file = storage_file_alloc(storage);
if(!storage_file_open(file, NFC_DEBUG_PCAP_FILENAME, FSAM_WRITE, FSOM_OPEN_APPEND)) {
storage_file_free(file);
return NULL;
}
if(!storage_file_tell(file)) {
struct {
uint32_t magic;
uint16_t major, minor;
uint32_t reserved[2];
uint32_t snaplen;
uint32_t link_type;
} __attribute__((__packed__)) pcap_hdr = {
.magic = PCAP_MAGIC,
.major = PCAP_MAJOR,
.minor = PCAP_MINOR,
.snaplen = FURI_HAL_NFC_DATA_BUFF_SIZE,
.link_type = DLT_ISO_14443,
};
if(storage_file_write(file, &pcap_hdr, sizeof(pcap_hdr)) != sizeof(pcap_hdr)) {
FURI_LOG_E(TAG, "Failed to write pcap header");
}
}
return file;
}
static void
nfc_debug_pcap_write(NfcDebugPcapWorker* instance, uint8_t event, uint8_t* data, uint16_t len) {
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,
};
xStreamBufferSend(instance->stream, &pkt_hdr, sizeof(pkt_hdr), osWaitForever);
xStreamBufferSend(instance->stream, data, len, osWaitForever);
}
static void
nfc_debug_pcap_write_tx(uint8_t* data, uint16_t bits, bool crc_dropped, void* context) {
NfcDebugPcapWorker* instance = context;
uint8_t event = crc_dropped ? DATA_PCD_TO_PICC_CRC_DROPPED : DATA_PCD_TO_PICC;
nfc_debug_pcap_write(instance, event, data, bits / 8);
}
static void
nfc_debug_pcap_write_rx(uint8_t* data, uint16_t bits, bool crc_dropped, void* context) {
NfcDebugPcapWorker* instance = context;
uint8_t event = crc_dropped ? DATA_PICC_TO_PCD_CRC_DROPPED : DATA_PICC_TO_PCD;
nfc_debug_pcap_write(instance, event, data, bits / 8);
}
int32_t nfc_debug_pcap_thread(void* context) {
NfcDebugPcapWorker* instance = context;
uint8_t buffer[NFC_DEBUG_PCAP_BUFFER_SIZE];
while(instance->alive) {
size_t ret =
xStreamBufferReceive(instance->stream, buffer, NFC_DEBUG_PCAP_BUFFER_SIZE, 50);
if(storage_file_write(instance->file, buffer, ret) != ret) {
FURI_LOG_E(TAG, "Failed to write pcap data");
}
}
return 0;
}
NfcDebugPcapWorker* nfc_debug_pcap_alloc(Storage* storage) {
NfcDebugPcapWorker* instance = malloc(sizeof(NfcDebugPcapWorker));
instance->alive = true;
instance->storage = storage;
instance->file = nfc_debug_pcap_open(storage);
instance->stream = xStreamBufferCreate(4096, 1);
instance->thread = furi_thread_alloc();
furi_thread_set_name(instance->thread, "PcapWorker");
furi_thread_set_stack_size(instance->thread, 1024);
furi_thread_set_callback(instance->thread, nfc_debug_pcap_thread);
furi_thread_set_context(instance->thread, instance);
furi_thread_start(instance->thread);
return instance;
}
void nfc_debug_pcap_free(NfcDebugPcapWorker* instance) {
furi_assert(instance);
instance->alive = false;
furi_thread_join(instance->thread);
furi_thread_free(instance->thread);
vStreamBufferDelete(instance->stream);
if(instance->file) storage_file_free(instance->file);
instance->storage = NULL;
free(instance);
}
void nfc_debug_pcap_prepare_tx_rx(
NfcDebugPcapWorker* instance,
FuriHalNfcTxRxContext* tx_rx,
bool is_picc) {
if(!instance || !instance->file) return;
if(is_picc) {
tx_rx->sniff_tx = nfc_debug_pcap_write_rx;
tx_rx->sniff_rx = nfc_debug_pcap_write_tx;
} else {
tx_rx->sniff_tx = nfc_debug_pcap_write_tx;
tx_rx->sniff_rx = nfc_debug_pcap_write_rx;
}
tx_rx->sniff_context = instance;
}

View File

@ -0,0 +1,21 @@
#pragma once
#include <furi_hal_nfc.h>
#include <storage/storage.h>
typedef struct NfcDebugPcapWorker NfcDebugPcapWorker;
NfcDebugPcapWorker* nfc_debug_pcap_alloc(Storage* storage);
void nfc_debug_pcap_free(NfcDebugPcapWorker* instance);
/** Prepare tx/rx context for debug pcap logging, if enabled.
*
* @param instance NfcDebugPcapWorker* instance, can be NULL
* @param tx_rx TX/RX context to log
* @param is_picc if true, record Flipper as PICC, else PCD.
*/
void nfc_debug_pcap_prepare_tx_rx(
NfcDebugPcapWorker* instance,
FuriHalNfcTxRxContext* tx_rx,
bool is_picc);

View File

@ -164,6 +164,26 @@ void nfc_text_store_clear(Nfc* nfc) {
memset(nfc->text_store, 0, sizeof(nfc->text_store)); memset(nfc->text_store, 0, sizeof(nfc->text_store));
} }
static const NotificationSequence sequence_blink_start_blue = {
&message_blink_start_10,
&message_blink_set_color_blue,
&message_do_not_reset,
NULL,
};
static const NotificationSequence sequence_blink_stop = {
&message_blink_stop,
NULL,
};
void nfc_blink_start(Nfc* nfc) {
notification_message(nfc->notifications, &sequence_blink_start_blue);
}
void nfc_blink_stop(Nfc* nfc) {
notification_message(nfc->notifications, &sequence_blink_stop);
}
int32_t nfc_app(void* p) { int32_t nfc_app(void* p) {
Nfc* nfc = nfc_alloc(); Nfc* nfc = nfc_alloc();
char* args = p; char* args = p;

View File

@ -76,3 +76,7 @@ int32_t nfc_task(void* p);
void nfc_text_store_set(Nfc* nfc, const char* text, ...); void nfc_text_store_set(Nfc* nfc, const char* text, ...);
void nfc_text_store_clear(Nfc* nfc); void nfc_text_store_clear(Nfc* nfc);
void nfc_blink_start(Nfc* nfc);
void nfc_blink_stop(Nfc* nfc);

View File

@ -1,16 +1,6 @@
#include "nfc_worker_i.h" #include "nfc_worker_i.h"
#include <furi_hal.h> #include <furi_hal.h>
#include <lib/nfc_protocols/nfc_util.h>
#include <lib/nfc_protocols/emv.h>
#include <lib/nfc_protocols/mifare_common.h>
#include <lib/nfc_protocols/mifare_ultralight.h>
#include <lib/nfc_protocols/mifare_classic.h>
#include <lib/nfc_protocols/mifare_desfire.h>
#include <lib/nfc_protocols/nfca.h>
#include "helpers/nfc_mf_classic_dict.h"
#define TAG "NfcWorker" #define TAG "NfcWorker"
/***************************** NFC Worker API *******************************/ /***************************** NFC Worker API *******************************/
@ -35,13 +25,22 @@ NfcWorker* nfc_worker_alloc() {
} }
nfc_worker_change_state(nfc_worker, NfcWorkerStateReady); nfc_worker_change_state(nfc_worker, NfcWorkerStateReady);
if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) {
nfc_worker->debug_pcap_worker = nfc_debug_pcap_alloc(nfc_worker->storage);
}
return nfc_worker; return nfc_worker;
} }
void nfc_worker_free(NfcWorker* nfc_worker) { void nfc_worker_free(NfcWorker* nfc_worker) {
furi_assert(nfc_worker); furi_assert(nfc_worker);
furi_thread_free(nfc_worker->thread); furi_thread_free(nfc_worker->thread);
furi_record_close("storage"); furi_record_close("storage");
if(nfc_worker->debug_pcap_worker) nfc_debug_pcap_free(nfc_worker->debug_pcap_worker);
free(nfc_worker); free(nfc_worker);
} }
@ -153,6 +152,7 @@ void nfc_worker_detect(NfcWorker* nfc_worker) {
void nfc_worker_emulate(NfcWorker* nfc_worker) { void nfc_worker_emulate(NfcWorker* nfc_worker) {
FuriHalNfcTxRxContext tx_rx = {}; FuriHalNfcTxRxContext tx_rx = {};
nfc_debug_pcap_prepare_tx_rx(nfc_worker->debug_pcap_worker, &tx_rx, true);
FuriHalNfcDevData* data = &nfc_worker->dev_data->nfc_data; FuriHalNfcDevData* data = &nfc_worker->dev_data->nfc_data;
NfcReaderRequestData* reader_data = &nfc_worker->dev_data->reader_data; NfcReaderRequestData* reader_data = &nfc_worker->dev_data->reader_data;
@ -175,6 +175,7 @@ void nfc_worker_emulate(NfcWorker* nfc_worker) {
void nfc_worker_read_emv_app(NfcWorker* nfc_worker) { void nfc_worker_read_emv_app(NfcWorker* nfc_worker) {
FuriHalNfcTxRxContext tx_rx = {}; FuriHalNfcTxRxContext tx_rx = {};
nfc_debug_pcap_prepare_tx_rx(nfc_worker->debug_pcap_worker, &tx_rx, false);
EmvApplication emv_app = {}; EmvApplication emv_app = {};
NfcDeviceData* result = nfc_worker->dev_data; NfcDeviceData* result = nfc_worker->dev_data;
FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data; FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data;
@ -206,6 +207,7 @@ void nfc_worker_read_emv_app(NfcWorker* nfc_worker) {
void nfc_worker_read_emv(NfcWorker* nfc_worker) { void nfc_worker_read_emv(NfcWorker* nfc_worker) {
FuriHalNfcTxRxContext tx_rx = {}; FuriHalNfcTxRxContext tx_rx = {};
nfc_debug_pcap_prepare_tx_rx(nfc_worker->debug_pcap_worker, &tx_rx, false);
EmvApplication emv_app = {}; EmvApplication emv_app = {};
NfcDeviceData* result = nfc_worker->dev_data; NfcDeviceData* result = nfc_worker->dev_data;
FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data; FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data;
@ -254,6 +256,7 @@ void nfc_worker_read_emv(NfcWorker* nfc_worker) {
void nfc_worker_emulate_apdu(NfcWorker* nfc_worker) { void nfc_worker_emulate_apdu(NfcWorker* nfc_worker) {
FuriHalNfcTxRxContext tx_rx = {}; FuriHalNfcTxRxContext tx_rx = {};
nfc_debug_pcap_prepare_tx_rx(nfc_worker->debug_pcap_worker, &tx_rx, true);
FuriHalNfcDevData params = { FuriHalNfcDevData params = {
.uid = {0xCF, 0x72, 0xd4, 0x40}, .uid = {0xCF, 0x72, 0xd4, 0x40},
.uid_len = 4, .uid_len = 4,
@ -278,6 +281,7 @@ void nfc_worker_emulate_apdu(NfcWorker* nfc_worker) {
void nfc_worker_read_mifare_ultralight(NfcWorker* nfc_worker) { void nfc_worker_read_mifare_ultralight(NfcWorker* nfc_worker) {
FuriHalNfcTxRxContext tx_rx = {}; FuriHalNfcTxRxContext tx_rx = {};
nfc_debug_pcap_prepare_tx_rx(nfc_worker->debug_pcap_worker, &tx_rx, false);
MfUltralightReader reader = {}; MfUltralightReader reader = {};
MfUltralightData data = {}; MfUltralightData data = {};
NfcDeviceData* result = nfc_worker->dev_data; NfcDeviceData* result = nfc_worker->dev_data;
@ -342,6 +346,7 @@ void nfc_worker_emulate_mifare_ul(NfcWorker* nfc_worker) {
void nfc_worker_mifare_classic_dict_attack(NfcWorker* nfc_worker) { void nfc_worker_mifare_classic_dict_attack(NfcWorker* nfc_worker) {
furi_assert(nfc_worker->callback); furi_assert(nfc_worker->callback);
FuriHalNfcTxRxContext tx_rx_ctx = {}; FuriHalNfcTxRxContext tx_rx_ctx = {};
nfc_debug_pcap_prepare_tx_rx(nfc_worker->debug_pcap_worker, &tx_rx_ctx, false);
MfClassicAuthContext auth_ctx = {}; MfClassicAuthContext auth_ctx = {};
MfClassicReader reader = {}; MfClassicReader reader = {};
uint64_t curr_key = 0; uint64_t curr_key = 0;
@ -483,7 +488,8 @@ void nfc_worker_mifare_classic_dict_attack(NfcWorker* nfc_worker) {
} }
void nfc_worker_emulate_mifare_classic(NfcWorker* nfc_worker) { void nfc_worker_emulate_mifare_classic(NfcWorker* nfc_worker) {
FuriHalNfcTxRxContext tx_rx; FuriHalNfcTxRxContext tx_rx = {};
nfc_debug_pcap_prepare_tx_rx(nfc_worker->debug_pcap_worker, &tx_rx, true);
FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data; FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data;
MfClassicEmulator emulator = { MfClassicEmulator emulator = {
.cuid = nfc_util_bytes2num(&nfc_data->uid[nfc_data->uid_len - 4], 4), .cuid = nfc_util_bytes2num(&nfc_data->uid[nfc_data->uid_len - 4], 4),
@ -511,11 +517,8 @@ void nfc_worker_emulate_mifare_classic(NfcWorker* nfc_worker) {
} }
void nfc_worker_read_mifare_desfire(NfcWorker* nfc_worker) { void nfc_worker_read_mifare_desfire(NfcWorker* nfc_worker) {
ReturnCode err; FuriHalNfcTxRxContext tx_rx = {};
uint8_t tx_buff[64] = {}; nfc_debug_pcap_prepare_tx_rx(nfc_worker->debug_pcap_worker, &tx_rx, false);
uint16_t tx_len = 0;
uint8_t rx_buff[512] = {};
uint16_t rx_len;
NfcDeviceData* result = nfc_worker->dev_data; NfcDeviceData* result = nfc_worker->dev_data;
nfc_device_data_clear(result); nfc_device_data_clear(result);
MifareDesfireData* data = &result->mf_df_data; MifareDesfireData* data = &result->mf_df_data;
@ -540,37 +543,36 @@ void nfc_worker_read_mifare_desfire(NfcWorker* nfc_worker) {
result->protocol = NfcDeviceProtocolMifareDesfire; result->protocol = NfcDeviceProtocolMifareDesfire;
// Get DESFire version // Get DESFire version
tx_len = mf_df_prepare_get_version(tx_buff); tx_rx.tx_bits = 8 * mf_df_prepare_get_version(tx_rx.tx_data);
err = furi_hal_nfc_exchange_full(tx_buff, tx_len, rx_buff, sizeof(rx_buff), &rx_len); if(!furi_hal_nfc_tx_rx_full(&tx_rx)) {
if(err != ERR_NONE) { FURI_LOG_W(TAG, "Bad exchange getting version");
FURI_LOG_W(TAG, "Bad exchange getting version, err: %d", err);
continue; 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"); FURI_LOG_W(TAG, "Bad DESFire GET_VERSION response");
continue; continue;
} }
tx_len = mf_df_prepare_get_free_memory(tx_buff); tx_rx.tx_bits = 8 * mf_df_prepare_get_free_memory(tx_rx.tx_data);
err = furi_hal_nfc_exchange_full(tx_buff, tx_len, rx_buff, sizeof(rx_buff), &rx_len); if(furi_hal_nfc_tx_rx_full(&tx_rx)) {
if(err == ERR_NONE) {
data->free_memory = malloc(sizeof(MifareDesfireFreeMemory)); data->free_memory = malloc(sizeof(MifareDesfireFreeMemory));
memset(data->free_memory, 0, 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)"); FURI_LOG_D(TAG, "Bad DESFire GET_FREE_MEMORY response (normal for pre-EV1 cards)");
free(data->free_memory); free(data->free_memory);
data->free_memory = NULL; data->free_memory = NULL;
} }
} }
tx_len = mf_df_prepare_get_key_settings(tx_buff); tx_rx.tx_bits = 8 * mf_df_prepare_get_key_settings(tx_rx.tx_data);
err = furi_hal_nfc_exchange_full(tx_buff, tx_len, rx_buff, sizeof(rx_buff), &rx_len); if(!furi_hal_nfc_tx_rx_full(&tx_rx)) {
if(err != ERR_NONE) { FURI_LOG_D(TAG, "Bad exchange getting key settings");
FURI_LOG_D(TAG, "Bad exchange getting key settings, err: %d", err);
} else { } else {
data->master_key_settings = malloc(sizeof(MifareDesfireKeySettings)); data->master_key_settings = malloc(sizeof(MifareDesfireKeySettings));
memset(data->master_key_settings, 0, 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"); FURI_LOG_W(TAG, "Bad DESFire GET_KEY_SETTINGS response");
free(data->master_key_settings); free(data->master_key_settings);
data->master_key_settings = NULL; data->master_key_settings = NULL;
@ -580,17 +582,16 @@ void nfc_worker_read_mifare_desfire(NfcWorker* nfc_worker) {
MifareDesfireKeyVersion** key_version_head = MifareDesfireKeyVersion** key_version_head =
&data->master_key_settings->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++) { 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); tx_rx.tx_bits = 8 * mf_df_prepare_get_key_version(tx_rx.tx_data, key_id);
err = if(!furi_hal_nfc_tx_rx_full(&tx_rx)) {
furi_hal_nfc_exchange_full(tx_buff, tx_len, rx_buff, sizeof(rx_buff), &rx_len); FURI_LOG_W(TAG, "Bad exchange getting key version");
if(err != ERR_NONE) {
FURI_LOG_W(TAG, "Bad exchange getting key version, err: %d", err);
continue; continue;
} }
MifareDesfireKeyVersion* key_version = malloc(sizeof(MifareDesfireKeyVersion)); MifareDesfireKeyVersion* key_version = malloc(sizeof(MifareDesfireKeyVersion));
memset(key_version, 0, sizeof(MifareDesfireKeyVersion)); memset(key_version, 0, sizeof(MifareDesfireKeyVersion));
key_version->id = key_id; 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"); FURI_LOG_W(TAG, "Bad DESFire GET_KEY_VERSION response");
free(key_version); free(key_version);
continue; continue;
@ -600,31 +601,31 @@ void nfc_worker_read_mifare_desfire(NfcWorker* nfc_worker) {
} }
} }
tx_len = mf_df_prepare_get_application_ids(tx_buff); tx_rx.tx_bits = 8 * mf_df_prepare_get_application_ids(tx_rx.tx_data);
err = furi_hal_nfc_exchange_full(tx_buff, tx_len, rx_buff, sizeof(rx_buff), &rx_len); if(!furi_hal_nfc_tx_rx_full(&tx_rx)) {
if(err != ERR_NONE) { FURI_LOG_W(TAG, "Bad exchange getting application IDs");
FURI_LOG_W(TAG, "Bad exchange getting application IDs, err: %d", err);
} else { } 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"); FURI_LOG_W(TAG, "Bad DESFire GET_APPLICATION_IDS response");
} }
} }
for(MifareDesfireApplication* app = data->app_head; app; app = app->next) { for(MifareDesfireApplication* app = data->app_head; app; app = app->next) {
tx_len = mf_df_prepare_select_application(tx_buff, app->id); tx_rx.tx_bits = 8 * mf_df_prepare_select_application(tx_rx.tx_data, app->id);
err = furi_hal_nfc_exchange_full(tx_buff, tx_len, rx_buff, sizeof(rx_buff), &rx_len); if(!furi_hal_nfc_tx_rx_full(&tx_rx) ||
if(!mf_df_parse_select_application_response(rx_buff, rx_len)) { !mf_df_parse_select_application_response(tx_rx.rx_data, tx_rx.rx_bits / 8)) {
FURI_LOG_W(TAG, "Bad exchange selecting application, err: %d", err); FURI_LOG_W(TAG, "Bad exchange selecting application");
continue; continue;
} }
tx_len = mf_df_prepare_get_key_settings(tx_buff); tx_rx.tx_bits = 8 * mf_df_prepare_get_key_settings(tx_rx.tx_data);
err = furi_hal_nfc_exchange_full(tx_buff, tx_len, rx_buff, sizeof(rx_buff), &rx_len); if(!furi_hal_nfc_tx_rx_full(&tx_rx)) {
if(err != ERR_NONE) { FURI_LOG_W(TAG, "Bad exchange getting key settings");
FURI_LOG_W(TAG, "Bad exchange getting key settings, err: %d", err);
} else { } else {
app->key_settings = malloc(sizeof(MifareDesfireKeySettings)); app->key_settings = malloc(sizeof(MifareDesfireKeySettings));
memset(app->key_settings, 0, 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"); FURI_LOG_W(TAG, "Bad DESFire GET_KEY_SETTINGS response");
free(app->key_settings); free(app->key_settings);
app->key_settings = NULL; app->key_settings = NULL;
@ -633,17 +634,16 @@ void nfc_worker_read_mifare_desfire(NfcWorker* nfc_worker) {
MifareDesfireKeyVersion** key_version_head = &app->key_settings->key_version_head; 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++) { 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); tx_rx.tx_bits = 8 * mf_df_prepare_get_key_version(tx_rx.tx_data, key_id);
err = furi_hal_nfc_exchange_full( if(!furi_hal_nfc_tx_rx_full(&tx_rx)) {
tx_buff, tx_len, rx_buff, sizeof(rx_buff), &rx_len); FURI_LOG_W(TAG, "Bad exchange getting key version");
if(err != ERR_NONE) {
FURI_LOG_W(TAG, "Bad exchange getting key version, err: %d", err);
continue; continue;
} }
MifareDesfireKeyVersion* key_version = malloc(sizeof(MifareDesfireKeyVersion)); MifareDesfireKeyVersion* key_version = malloc(sizeof(MifareDesfireKeyVersion));
memset(key_version, 0, sizeof(MifareDesfireKeyVersion)); memset(key_version, 0, sizeof(MifareDesfireKeyVersion));
key_version->id = key_id; 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"); FURI_LOG_W(TAG, "Bad DESFire GET_KEY_VERSION response");
free(key_version); free(key_version);
continue; continue;
@ -653,48 +653,45 @@ void nfc_worker_read_mifare_desfire(NfcWorker* nfc_worker) {
} }
} }
tx_len = mf_df_prepare_get_file_ids(tx_buff); tx_rx.tx_bits = 8 * mf_df_prepare_get_file_ids(tx_rx.tx_data);
err = furi_hal_nfc_exchange_full(tx_buff, tx_len, rx_buff, sizeof(rx_buff), &rx_len); if(!furi_hal_nfc_tx_rx_full(&tx_rx)) {
if(err != ERR_NONE) { FURI_LOG_W(TAG, "Bad exchange getting file IDs");
FURI_LOG_W(TAG, "Bad exchange getting file IDs, err: %d", err);
} else { } 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"); FURI_LOG_W(TAG, "Bad DESFire GET_FILE_IDS response");
} }
} }
for(MifareDesfireFile* file = app->file_head; file; file = file->next) { for(MifareDesfireFile* file = app->file_head; file; file = file->next) {
tx_len = mf_df_prepare_get_file_settings(tx_buff, file->id); tx_rx.tx_bits = 8 * mf_df_prepare_get_file_settings(tx_rx.tx_data, file->id);
err = if(!furi_hal_nfc_tx_rx_full(&tx_rx)) {
furi_hal_nfc_exchange_full(tx_buff, tx_len, rx_buff, sizeof(rx_buff), &rx_len); FURI_LOG_W(TAG, "Bad exchange getting file settings");
if(err != ERR_NONE) {
FURI_LOG_W(TAG, "Bad exchange getting file settings, err: %d", err);
continue; 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"); FURI_LOG_W(TAG, "Bad DESFire GET_FILE_SETTINGS response");
continue; continue;
} }
switch(file->type) { switch(file->type) {
case MifareDesfireFileTypeStandard: case MifareDesfireFileTypeStandard:
case MifareDesfireFileTypeBackup: 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; break;
case MifareDesfireFileTypeValue: 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; break;
case MifareDesfireFileTypeLinearRecord: case MifareDesfireFileTypeLinearRecord:
case MifareDesfireFileTypeCyclicRecord: 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; break;
} }
err = if(!furi_hal_nfc_tx_rx_full(&tx_rx)) {
furi_hal_nfc_exchange_full(tx_buff, tx_len, rx_buff, sizeof(rx_buff), &rx_len); FURI_LOG_W(TAG, "Bad exchange reading file %d", file->id);
if(err != ERR_NONE) {
FURI_LOG_W(TAG, "Bad exchange reading file %d, err: %d", file->id, err);
continue; 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); FURI_LOG_W(TAG, "Bad response reading file %d", file->id);
continue; continue;
} }

15
applications/nfc/nfc_worker_i.h Executable file → Normal file
View File

@ -1,11 +1,22 @@
#pragma once #pragma once
#include "nfc_i.h"
#include "nfc_worker.h" #include "nfc_worker.h"
#include "nfc_i.h"
#include <furi.h> #include <furi.h>
#include <lib/toolbox/stream/file_stream.h> #include <lib/toolbox/stream/file_stream.h>
#include <lib/nfc_protocols/nfc_util.h>
#include <lib/nfc_protocols/emv.h>
#include <lib/nfc_protocols/mifare_common.h>
#include <lib/nfc_protocols/mifare_ultralight.h>
#include <lib/nfc_protocols/mifare_classic.h>
#include <lib/nfc_protocols/mifare_desfire.h>
#include <lib/nfc_protocols/nfca.h>
#include "helpers/nfc_mf_classic_dict.h"
#include "helpers/nfc_debug_pcap.h"
struct NfcWorker { struct NfcWorker {
FuriThread* thread; FuriThread* thread;
Storage* storage; Storage* storage;
@ -17,6 +28,8 @@ struct NfcWorker {
void* context; void* context;
NfcWorkerState state; NfcWorkerState state;
NfcDebugPcapWorker* debug_pcap_worker;
}; };
void nfc_worker_change_state(NfcWorker* nfc_worker, NfcWorkerState state); void nfc_worker_change_state(NfcWorker* nfc_worker, NfcWorkerState state);

View File

@ -1,4 +1,5 @@
#include "../nfc_i.h" #include "../nfc_i.h"
#include "furi/common_defines.h"
void nfc_scene_emulate_apdu_sequence_on_enter(void* context) { void nfc_scene_emulate_apdu_sequence_on_enter(void* context) {
Nfc* nfc = context; Nfc* nfc = context;
@ -10,17 +11,14 @@ void nfc_scene_emulate_apdu_sequence_on_enter(void* context) {
// Setup and start worker // Setup and start worker
view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup);
nfc_worker_start(nfc->worker, NfcWorkerStateEmulateApdu, &nfc->dev->dev_data, NULL, nfc); nfc_worker_start(nfc->worker, NfcWorkerStateEmulateApdu, &nfc->dev->dev_data, NULL, nfc);
nfc_blink_start(nfc);
} }
bool nfc_scene_emulate_apdu_sequence_on_event(void* context, SceneManagerEvent event) { bool nfc_scene_emulate_apdu_sequence_on_event(void* context, SceneManagerEvent event) {
Nfc* nfc = context; UNUSED(context);
UNUSED(event);
bool consumed = false; bool consumed = false;
if(event.type == SceneManagerEventTypeTick) {
notification_message(nfc->notifications, &sequence_blink_blue_10);
consumed = true;
}
return consumed; return consumed;
} }
@ -31,4 +29,6 @@ void nfc_scene_emulate_apdu_sequence_on_exit(void* context) {
nfc_worker_stop(nfc->worker); nfc_worker_stop(nfc->worker);
// Clear view // Clear view
popup_reset(nfc->popup); popup_reset(nfc->popup);
nfc_blink_stop(nfc);
} }

View File

@ -32,6 +32,7 @@ void nfc_scene_emulate_mifare_classic_on_enter(void* context) {
&nfc->dev->dev_data, &nfc->dev->dev_data,
nfc_emulate_mifare_classic_worker_callback, nfc_emulate_mifare_classic_worker_callback,
nfc); nfc);
nfc_blink_start(nfc);
} }
bool nfc_scene_emulate_mifare_classic_on_event(void* context, SceneManagerEvent event) { bool nfc_scene_emulate_mifare_classic_on_event(void* context, SceneManagerEvent event) {
@ -39,7 +40,6 @@ bool nfc_scene_emulate_mifare_classic_on_event(void* context, SceneManagerEvent
bool consumed = false; bool consumed = false;
if(event.type == SceneManagerEventTypeTick) { if(event.type == SceneManagerEventTypeTick) {
notification_message(nfc->notifications, &sequence_blink_blue_10);
consumed = true; consumed = true;
} else if(event.type == SceneManagerEventTypeBack) { } else if(event.type == SceneManagerEventTypeBack) {
// Stop worker // Stop worker
@ -61,4 +61,6 @@ void nfc_scene_emulate_mifare_classic_on_exit(void* context) {
// Clear view // Clear view
popup_reset(nfc->popup); popup_reset(nfc->popup);
nfc_blink_stop(nfc);
} }

View File

@ -32,6 +32,7 @@ void nfc_scene_emulate_mifare_ul_on_enter(void* context) {
&nfc->dev->dev_data, &nfc->dev->dev_data,
nfc_emulate_mifare_ul_worker_callback, nfc_emulate_mifare_ul_worker_callback,
nfc); nfc);
nfc_blink_start(nfc);
} }
bool nfc_scene_emulate_mifare_ul_on_event(void* context, SceneManagerEvent event) { bool nfc_scene_emulate_mifare_ul_on_event(void* context, SceneManagerEvent event) {
@ -39,7 +40,6 @@ bool nfc_scene_emulate_mifare_ul_on_event(void* context, SceneManagerEvent event
bool consumed = false; bool consumed = false;
if(event.type == SceneManagerEventTypeTick) { if(event.type == SceneManagerEventTypeTick) {
notification_message(nfc->notifications, &sequence_blink_blue_10);
consumed = true; consumed = true;
} else if(event.type == SceneManagerEventTypeBack) { } else if(event.type == SceneManagerEventTypeBack) {
// Stop worker // Stop worker
@ -61,4 +61,6 @@ void nfc_scene_emulate_mifare_ul_on_exit(void* context) {
// Clear view // Clear view
popup_reset(nfc->popup); popup_reset(nfc->popup);
nfc_blink_stop(nfc);
} }

View File

@ -80,6 +80,8 @@ void nfc_scene_emulate_uid_on_enter(void* context) {
&nfc->dev->dev_data, &nfc->dev->dev_data,
nfc_emulate_uid_worker_callback, nfc_emulate_uid_worker_callback,
nfc); nfc);
nfc_blink_start(nfc);
} }
bool nfc_scene_emulate_uid_on_event(void* context, SceneManagerEvent event) { bool nfc_scene_emulate_uid_on_event(void* context, SceneManagerEvent event) {
@ -89,7 +91,6 @@ bool nfc_scene_emulate_uid_on_event(void* context, SceneManagerEvent event) {
bool consumed = false; bool consumed = false;
if(event.type == SceneManagerEventTypeTick) { if(event.type == SceneManagerEventTypeTick) {
notification_message(nfc->notifications, &sequence_blink_blue_10);
consumed = true; consumed = true;
} else if(event.type == SceneManagerEventTypeCustom) { } else if(event.type == SceneManagerEventTypeCustom) {
if(event.event == NfcCustomEventWorkerExit) { if(event.event == NfcCustomEventWorkerExit) {
@ -141,4 +142,6 @@ void nfc_scene_emulate_uid_on_exit(void* context) {
widget_reset(nfc->widget); widget_reset(nfc->widget);
text_box_reset(nfc->text_box); text_box_reset(nfc->text_box);
string_reset(nfc->text_box_store); string_reset(nfc->text_box_store);
nfc_blink_stop(nfc);
} }

View File

@ -20,6 +20,8 @@ void nfc_scene_read_card_on_enter(void* context) {
view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup);
nfc_worker_start( nfc_worker_start(
nfc->worker, NfcWorkerStateDetect, &nfc->dev->dev_data, nfc_read_card_worker_callback, nfc); nfc->worker, NfcWorkerStateDetect, &nfc->dev->dev_data, nfc_read_card_worker_callback, nfc);
nfc_blink_start(nfc);
} }
bool nfc_scene_read_card_on_event(void* context, SceneManagerEvent event) { bool nfc_scene_read_card_on_event(void* context, SceneManagerEvent event) {
@ -32,7 +34,6 @@ bool nfc_scene_read_card_on_event(void* context, SceneManagerEvent event) {
consumed = true; consumed = true;
} }
} else if(event.type == SceneManagerEventTypeTick) { } else if(event.type == SceneManagerEventTypeTick) {
notification_message(nfc->notifications, &sequence_blink_blue_10);
consumed = true; consumed = true;
} }
return consumed; return consumed;
@ -45,4 +46,6 @@ void nfc_scene_read_card_on_exit(void* context) {
nfc_worker_stop(nfc->worker); nfc_worker_stop(nfc->worker);
// Clear view // Clear view
popup_reset(nfc->popup); popup_reset(nfc->popup);
nfc_blink_stop(nfc);
} }

View File

@ -24,6 +24,7 @@ void nfc_scene_read_emv_app_on_enter(void* context) {
&nfc->dev->dev_data, &nfc->dev->dev_data,
nfc_read_emv_app_worker_callback, nfc_read_emv_app_worker_callback,
nfc); nfc);
nfc_blink_start(nfc);
} }
bool nfc_scene_read_emv_app_on_event(void* context, SceneManagerEvent event) { bool nfc_scene_read_emv_app_on_event(void* context, SceneManagerEvent event) {
@ -38,7 +39,6 @@ bool nfc_scene_read_emv_app_on_event(void* context, SceneManagerEvent event) {
consumed = true; consumed = true;
} }
} else if(event.type == SceneManagerEventTypeTick) { } else if(event.type == SceneManagerEventTypeTick) {
notification_message(nfc->notifications, &sequence_blink_blue_10);
consumed = true; consumed = true;
} }
@ -53,4 +53,6 @@ void nfc_scene_read_emv_app_on_exit(void* context) {
// Clear view // Clear view
popup_reset(nfc->popup); popup_reset(nfc->popup);
nfc_blink_stop(nfc);
} }

View File

@ -26,6 +26,8 @@ void nfc_scene_read_emv_data_on_enter(void* context) {
&nfc->dev->dev_data, &nfc->dev->dev_data,
nfc_read_emv_data_worker_callback, nfc_read_emv_data_worker_callback,
nfc); nfc);
nfc_blink_start(nfc);
} }
bool nfc_scene_read_emv_data_on_event(void* context, SceneManagerEvent event) { bool nfc_scene_read_emv_data_on_event(void* context, SceneManagerEvent event) {
@ -40,7 +42,6 @@ bool nfc_scene_read_emv_data_on_event(void* context, SceneManagerEvent event) {
consumed = true; consumed = true;
} }
} else if(event.type == SceneManagerEventTypeTick) { } else if(event.type == SceneManagerEventTypeTick) {
notification_message(nfc->notifications, &sequence_blink_blue_10);
consumed = true; consumed = true;
} }
return consumed; return consumed;
@ -53,4 +54,6 @@ void nfc_scene_read_emv_data_on_exit(void* context) {
nfc_worker_stop(nfc->worker); nfc_worker_stop(nfc->worker);
// Clear view // Clear view
popup_reset(nfc->popup); popup_reset(nfc->popup);
nfc_blink_stop(nfc);
} }

View File

@ -33,17 +33,15 @@ void nfc_scene_read_mifare_classic_on_enter(void* context) {
&nfc->dev->dev_data, &nfc->dev->dev_data,
nfc_read_mifare_classic_worker_callback, nfc_read_mifare_classic_worker_callback,
nfc); nfc);
nfc_blink_start(nfc);
} }
bool nfc_scene_read_mifare_classic_on_event(void* context, SceneManagerEvent event) { bool nfc_scene_read_mifare_classic_on_event(void* context, SceneManagerEvent event) {
Nfc* nfc = context; Nfc* nfc = context;
bool consumed = false; bool consumed = false;
uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneReadMifareClassic);
if(event.type == SceneManagerEventTypeTick) { if(event.type == SceneManagerEventTypeTick) {
if(state == NfcSceneReadMifareClassicStateInProgress) {
notification_message(nfc->notifications, &sequence_blink_blue_10);
}
consumed = true; consumed = true;
} else if(event.type == SceneManagerEventTypeCustom) { } else if(event.type == SceneManagerEventTypeCustom) {
if(event.event == NfcCustomEventDictAttackDone) { if(event.event == NfcCustomEventDictAttackDone) {
@ -70,12 +68,14 @@ bool nfc_scene_read_mifare_classic_on_event(void* context, SceneManagerEvent eve
} else if(event.event == NfcWorkerEventSuccess) { } else if(event.event == NfcWorkerEventSuccess) {
scene_manager_set_scene_state( scene_manager_set_scene_state(
nfc->scene_manager, NfcSceneReadMifareClassic, NfcSceneReadMifareClassicStateDone); nfc->scene_manager, NfcSceneReadMifareClassic, NfcSceneReadMifareClassicStateDone);
nfc_blink_stop(nfc);
notification_message(nfc->notifications, &sequence_success); notification_message(nfc->notifications, &sequence_success);
dict_attack_set_result(nfc->dict_attack, true); dict_attack_set_result(nfc->dict_attack, true);
consumed = true; consumed = true;
} else if(event.event == NfcWorkerEventFail) { } else if(event.event == NfcWorkerEventFail) {
scene_manager_set_scene_state( scene_manager_set_scene_state(
nfc->scene_manager, NfcSceneReadMifareClassic, NfcSceneReadMifareClassicStateDone); nfc->scene_manager, NfcSceneReadMifareClassic, NfcSceneReadMifareClassicStateDone);
nfc_blink_stop(nfc);
dict_attack_set_result(nfc->dict_attack, false); dict_attack_set_result(nfc->dict_attack, false);
consumed = true; consumed = true;
} else if(event.event == NfcWorkerEventNoDictFound) { } else if(event.event == NfcWorkerEventNoDictFound) {
@ -91,4 +91,6 @@ void nfc_scene_read_mifare_classic_on_exit(void* context) {
// Stop worker // Stop worker
nfc_worker_stop(nfc->worker); nfc_worker_stop(nfc->worker);
dict_attack_reset(nfc->dict_attack); dict_attack_reset(nfc->dict_attack);
nfc_blink_stop(nfc);
} }

View File

@ -24,6 +24,7 @@ void nfc_scene_read_mifare_desfire_on_enter(void* context) {
&nfc->dev->dev_data, &nfc->dev->dev_data,
nfc_read_mifare_desfire_worker_callback, nfc_read_mifare_desfire_worker_callback,
nfc); nfc);
nfc_blink_start(nfc);
} }
bool nfc_scene_read_mifare_desfire_on_event(void* context, SceneManagerEvent event) { bool nfc_scene_read_mifare_desfire_on_event(void* context, SceneManagerEvent event) {
@ -33,12 +34,11 @@ bool nfc_scene_read_mifare_desfire_on_event(void* context, SceneManagerEvent eve
if(event.type == SceneManagerEventTypeCustom) { if(event.type == SceneManagerEventTypeCustom) {
if(event.event == NfcCustomEventWorkerExit) { if(event.event == NfcCustomEventWorkerExit) {
notification_message(nfc->notifications, &sequence_success); notification_message(nfc->notifications, &sequence_success);
DOLPHIN_DEED(DolphinDeedNfcReadSuccess);
scene_manager_next_scene(nfc->scene_manager, NfcSceneReadMifareDesfireSuccess); scene_manager_next_scene(nfc->scene_manager, NfcSceneReadMifareDesfireSuccess);
consumed = true; consumed = true;
} }
} else if(event.type == SceneManagerEventTypeTick) { } else if(event.type == SceneManagerEventTypeTick) {
notification_message(nfc->notifications, &sequence_blink_blue_10);
DOLPHIN_DEED(DolphinDeedNfcReadSuccess);
consumed = true; consumed = true;
} }
return consumed; return consumed;
@ -51,4 +51,6 @@ void nfc_scene_read_mifare_desfire_on_exit(void* context) {
nfc_worker_stop(nfc->worker); nfc_worker_stop(nfc->worker);
// Clear view // Clear view
popup_reset(nfc->popup); popup_reset(nfc->popup);
nfc_blink_stop(nfc);
} }

View File

@ -24,6 +24,7 @@ void nfc_scene_read_mifare_ul_on_enter(void* context) {
&nfc->dev->dev_data, &nfc->dev->dev_data,
nfc_read_mifare_ul_worker_callback, nfc_read_mifare_ul_worker_callback,
nfc); nfc);
nfc_blink_start(nfc);
} }
bool nfc_scene_read_mifare_ul_on_event(void* context, SceneManagerEvent event) { bool nfc_scene_read_mifare_ul_on_event(void* context, SceneManagerEvent event) {
@ -36,7 +37,6 @@ bool nfc_scene_read_mifare_ul_on_event(void* context, SceneManagerEvent event) {
consumed = true; consumed = true;
} }
} else if(event.type == SceneManagerEventTypeTick) { } else if(event.type == SceneManagerEventTypeTick) {
notification_message(nfc->notifications, &sequence_blink_blue_10);
consumed = true; consumed = true;
} }
return consumed; return consumed;
@ -49,4 +49,6 @@ void nfc_scene_read_mifare_ul_on_exit(void* context) {
nfc_worker_stop(nfc->worker); nfc_worker_stop(nfc->worker);
// Clear view // Clear view
popup_reset(nfc->popup); popup_reset(nfc->popup);
nfc_blink_stop(nfc);
} }

View File

@ -1,6 +1,7 @@
#pragma once #pragma once
#include "stdint.h" #include "stdint.h"
#include "stdbool.h" #include "stdbool.h"
#include <furi_hal_resources.h>
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
@ -30,9 +31,16 @@ typedef struct {
float display_brightness; float display_brightness;
} NotificationMessageDataForcedSettings; } NotificationMessageDataForcedSettings;
typedef struct {
uint16_t on_time;
uint16_t period;
Light color;
} NotificationMessageDataLedBlink;
typedef union { typedef union {
NotificationMessageDataSound sound; NotificationMessageDataSound sound;
NotificationMessageDataLed led; NotificationMessageDataLed led;
NotificationMessageDataLedBlink led_blink;
NotificationMessageDataVibro vibro; NotificationMessageDataVibro vibro;
NotificationMessageDataDelay delay; NotificationMessageDataDelay delay;
NotificationMessageDataForcedSettings forced_settings; NotificationMessageDataForcedSettings forced_settings;
@ -48,6 +56,10 @@ typedef enum {
NotificationMessageTypeLedGreen, NotificationMessageTypeLedGreen,
NotificationMessageTypeLedBlue, NotificationMessageTypeLedBlue,
NotificationMessageTypeLedBlinkStart,
NotificationMessageTypeLedBlinkStop,
NotificationMessageTypeLedBlinkColor,
NotificationMessageTypeDelay, NotificationMessageTypeDelay,
NotificationMessageTypeLedDisplayBacklight, NotificationMessageTypeLedDisplayBacklight,

View File

@ -1,3 +1,4 @@
#include "furi_hal_light.h"
#include <furi.h> #include <furi.h>
#include <furi_hal.h> #include <furi_hal.h>
#include <storage/storage.h> #include <storage/storage.h>
@ -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_vibro_mask = 1 << 3;
static const uint8_t reset_sound_mask = 1 << 4; static const uint8_t reset_sound_mask = 1 << 4;
static const uint8_t reset_display_mask = 1 << 5; static const uint8_t reset_display_mask = 1 << 5;
static const uint8_t reset_blink_mask = 1 << 6;
void notification_vibro_on(); void notification_vibro_on();
void notification_vibro_off(); 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) { if(reset_mask & reset_blue_mask) {
notification_reset_notification_led_layer(&app->led[2]); 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) { if(reset_mask & reset_vibro_mask) {
notification_vibro_off(); notification_vibro_off();
} }
@ -229,6 +234,24 @@ void notification_process_notification_message(
app->led[2].value_last[LayerNotification] = led_values[2]; app->led[2].value_last[LayerNotification] = led_values[2];
reset_mask |= reset_blue_mask; reset_mask |= reset_blue_mask;
break; 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: case NotificationMessageTypeVibro:
if(notification_message->data.vibro.on) { if(notification_message->data.vibro.on) {
if(vibro_setting) notification_vibro_on(); if(vibro_setting) notification_vibro_on();

View File

@ -1,3 +1,4 @@
#include "furi_hal_resources.h"
#include "notification.h" #include "notification.h"
#include "notification_messages_notes.h" #include "notification_messages_notes.h"
#include <stddef.h> #include <stddef.h>
@ -60,6 +61,59 @@ const NotificationMessage message_blue_0 = {
.data.led.value = 0x00, .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 // Delay
const NotificationMessage message_delay_1 = { const NotificationMessage message_delay_1 = {
.type = NotificationMessageTypeDelay, .type = NotificationMessageTypeDelay,

View File

@ -24,6 +24,19 @@ extern const NotificationMessage message_red_0;
extern const NotificationMessage message_green_0; extern const NotificationMessage message_green_0;
extern const NotificationMessage message_blue_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 // Delay
extern const NotificationMessage message_delay_1; extern const NotificationMessage message_delay_1;
extern const NotificationMessage message_delay_10; extern const NotificationMessage message_delay_10;

View File

@ -18,7 +18,7 @@ static void power_settings_tick_event_callback(void* context) {
scene_manager_handle_tick_event(app->scene_manager); scene_manager_handle_tick_event(app->scene_manager);
} }
PowerSettingsApp* power_settings_app_alloc() { PowerSettingsApp* power_settings_app_alloc(uint32_t first_scene) {
PowerSettingsApp* app = malloc(sizeof(PowerSettingsApp)); PowerSettingsApp* app = malloc(sizeof(PowerSettingsApp));
// Records // Records
@ -52,7 +52,7 @@ PowerSettingsApp* power_settings_app_alloc() {
app->view_dispatcher, PowerSettingsAppViewDialog, dialog_ex_get_view(app->dialog)); app->view_dispatcher, PowerSettingsAppViewDialog, dialog_ex_get_view(app->dialog));
// Set first scene // Set first scene
scene_manager_next_scene(app->scene_manager, PowerSettingsAppSceneStart); scene_manager_next_scene(app->scene_manager, first_scene);
return app; return app;
} }
@ -75,8 +75,11 @@ void power_settings_app_free(PowerSettingsApp* app) {
} }
int32_t power_settings_app(void* p) { int32_t power_settings_app(void* p) {
UNUSED(p); uint32_t first_scene = PowerSettingsAppSceneStart;
PowerSettingsApp* app = power_settings_app_alloc(); if(p && !strcmp(p, "off")) {
first_scene = PowerSettingsAppScenePowerOff;
}
PowerSettingsApp* app = power_settings_app_alloc(first_scene);
view_dispatcher_run(app->view_dispatcher); view_dispatcher_run(app->view_dispatcher);
power_settings_app_free(app); power_settings_app_free(app);
return 0; return 0;

View File

@ -28,7 +28,10 @@ bool power_settings_scene_power_off_on_event(void* context, SceneManagerEvent ev
if(event.type == SceneManagerEventTypeCustom) { if(event.type == SceneManagerEventTypeCustom) {
if(event.event == DialogExResultLeft) { if(event.event == DialogExResultLeft) {
scene_manager_previous_scene(app->scene_manager); if(!scene_manager_previous_scene(app->scene_manager)) {
scene_manager_stop(app->scene_manager);
view_dispatcher_stop(app->view_dispatcher);
}
} else if(event.event == DialogExResultRight) { } else if(event.event == DialogExResultRight) {
power_off(app->power); power_off(app->power);
} }

View File

@ -184,14 +184,14 @@ static void storage_cli_read(Cli* cli, string_t path) {
File* file = storage_file_alloc(api); File* file = storage_file_alloc(api);
if(storage_file_open(file, string_get_cstr(path), FSAM_READ, FSOM_OPEN_EXISTING)) { if(storage_file_open(file, 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; 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)); printf("Size: %lu\r\n", (uint32_t)storage_file_size(file));
do { 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++) { for(uint16_t i = 0; i < read_size; i++) {
printf("%c", data[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); File* file = storage_file_alloc(api);
if(storage_file_open(file, string_get_cstr(path), FSAM_READ, FSOM_OPEN_EXISTING)) { if(storage_file_open(file, 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; 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); uint8_t* hash = malloc(sizeof(uint8_t) * hash_size);
md5_context* md5_ctx = malloc(sizeof(md5_context)); md5_context* md5_ctx = malloc(sizeof(md5_context));
md5_starts(md5_ctx); md5_starts(md5_ctx);
while(true) { 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; if(read_size == 0) break;
md5_update(md5_ctx, data, read_size); md5_update(md5_ctx, data, read_size);
} }

View File

@ -134,6 +134,10 @@ bool storage_file_close(File* file) {
} }
uint16_t storage_file_read(File* file, void* buff, uint16_t bytes_to_read) { uint16_t storage_file_read(File* file, void* buff, uint16_t bytes_to_read) {
if(bytes_to_read == 0) {
return 0;
}
S_FILE_API_PROLOGUE; S_FILE_API_PROLOGUE;
S_API_PROLOGUE; S_API_PROLOGUE;
@ -150,6 +154,10 @@ uint16_t storage_file_read(File* file, void* buff, uint16_t bytes_to_read) {
} }
uint16_t storage_file_write(File* file, const void* buff, uint16_t bytes_to_write) { uint16_t storage_file_write(File* file, const void* buff, uint16_t bytes_to_write) {
if(bytes_to_write == 0) {
return 0;
}
S_FILE_API_PROLOGUE; S_FILE_API_PROLOGUE;
S_API_PROLOGUE; S_API_PROLOGUE;

View File

@ -113,11 +113,14 @@ void storage_settings_scene_benchmark_on_enter(void* context) {
StorageSettings* app = context; StorageSettings* app = context;
DialogEx* dialog_ex = app->dialog_ex; DialogEx* dialog_ex = app->dialog_ex;
FS_Error sd_status = storage_sd_status(app->fs_api);
scene_manager_set_scene_state(app->scene_manager, StorageSettingsBenchmark, sd_status);
dialog_ex_set_context(dialog_ex, app); dialog_ex_set_context(dialog_ex, app);
dialog_ex_set_result_callback(dialog_ex, storage_settings_scene_benchmark_dialog_callback); dialog_ex_set_result_callback(dialog_ex, storage_settings_scene_benchmark_dialog_callback);
view_dispatcher_switch_to_view(app->view_dispatcher, StorageSettingsViewDialogEx); view_dispatcher_switch_to_view(app->view_dispatcher, StorageSettingsViewDialogEx);
if(storage_sd_status(app->fs_api) != FSE_OK) { if(sd_status != FSE_OK) {
dialog_ex_set_header(dialog_ex, "SD card not mounted", 64, 10, AlignCenter, AlignCenter); dialog_ex_set_header(dialog_ex, "SD card not mounted", 64, 10, AlignCenter, AlignCenter);
dialog_ex_set_text( dialog_ex_set_text(
dialog_ex, dialog_ex,
@ -126,7 +129,7 @@ void storage_settings_scene_benchmark_on_enter(void* context) {
32, 32,
AlignCenter, AlignCenter,
AlignCenter); AlignCenter);
dialog_ex_set_left_button_text(dialog_ex, "Back"); dialog_ex_set_center_button_text(dialog_ex, "Ok");
} else { } else {
storage_settings_scene_benchmark(app); storage_settings_scene_benchmark(app);
notification_message(app->notification, &sequence_blink_green_100); notification_message(app->notification, &sequence_blink_green_100);
@ -137,13 +140,19 @@ bool storage_settings_scene_benchmark_on_event(void* context, SceneManagerEvent
StorageSettings* app = context; StorageSettings* app = context;
bool consumed = false; bool consumed = false;
FS_Error sd_status =
scene_manager_get_scene_state(app->scene_manager, StorageSettingsBenchmark);
if(event.type == SceneManagerEventTypeCustom) { if(event.type == SceneManagerEventTypeCustom) {
switch(event.event) { switch(event.event) {
case DialogExResultLeft: case DialogExResultCenter:
consumed = scene_manager_previous_scene(app->scene_manager); consumed = scene_manager_previous_scene(app->scene_manager);
break; break;
} }
} else if(event.type == SceneManagerEventTypeBack && sd_status != FSE_OK) {
consumed = true;
} }
return consumed; return consumed;
} }

View File

@ -9,9 +9,9 @@ static void
void storage_settings_scene_format_confirm_on_enter(void* context) { void storage_settings_scene_format_confirm_on_enter(void* context) {
StorageSettings* app = context; StorageSettings* app = context;
FS_Error sd_status = storage_sd_status(app->fs_api);
DialogEx* dialog_ex = app->dialog_ex; DialogEx* dialog_ex = app->dialog_ex;
dialog_ex_set_left_button_text(dialog_ex, "Cancel");
FS_Error sd_status = storage_sd_status(app->fs_api);
if(sd_status == FSE_NOT_READY) { if(sd_status == FSE_NOT_READY) {
dialog_ex_set_header(dialog_ex, "SD card not mounted", 64, 10, AlignCenter, AlignCenter); dialog_ex_set_header(dialog_ex, "SD card not mounted", 64, 10, AlignCenter, AlignCenter);
@ -22,10 +22,12 @@ void storage_settings_scene_format_confirm_on_enter(void* context) {
32, 32,
AlignCenter, AlignCenter,
AlignCenter); AlignCenter);
dialog_ex_set_center_button_text(dialog_ex, "Ok");
} else { } else {
dialog_ex_set_right_button_text(dialog_ex, "Format");
dialog_ex_set_header(dialog_ex, "Format SD card?", 64, 10, AlignCenter, AlignCenter); dialog_ex_set_header(dialog_ex, "Format SD card?", 64, 10, AlignCenter, AlignCenter);
dialog_ex_set_text(dialog_ex, "All data will be lost", 64, 32, AlignCenter, AlignCenter); dialog_ex_set_text(dialog_ex, "All data will be lost", 64, 32, AlignCenter, AlignCenter);
dialog_ex_set_left_button_text(dialog_ex, "Cancel");
dialog_ex_set_right_button_text(dialog_ex, "Format");
} }
dialog_ex_set_context(dialog_ex, app); dialog_ex_set_context(dialog_ex, app);
@ -44,12 +46,18 @@ bool storage_settings_scene_format_confirm_on_event(void* context, SceneManagerE
case DialogExResultLeft: case DialogExResultLeft:
consumed = scene_manager_previous_scene(app->scene_manager); consumed = scene_manager_previous_scene(app->scene_manager);
break; break;
case DialogExResultCenter:
consumed = scene_manager_previous_scene(app->scene_manager);
break;
case DialogExResultRight: case DialogExResultRight:
scene_manager_next_scene(app->scene_manager, StorageSettingsFormatting); scene_manager_next_scene(app->scene_manager, StorageSettingsFormatting);
consumed = true; consumed = true;
break; break;
} }
} else if(event.type == SceneManagerEventTypeBack) {
consumed = true;
} }
return consumed; return consumed;
} }

View File

@ -8,9 +8,11 @@ static void storage_settings_scene_sd_info_dialog_callback(DialogExResult result
void storage_settings_scene_sd_info_on_enter(void* context) { void storage_settings_scene_sd_info_on_enter(void* context) {
StorageSettings* app = context; StorageSettings* app = context;
DialogEx* dialog_ex = app->dialog_ex;
SDInfo sd_info; SDInfo sd_info;
FS_Error sd_status = storage_sd_info(app->fs_api, &sd_info); FS_Error sd_status = storage_sd_info(app->fs_api, &sd_info);
DialogEx* dialog_ex = app->dialog_ex; scene_manager_set_scene_state(app->scene_manager, StorageSettingsSDInfo, sd_status);
dialog_ex_set_context(dialog_ex, app); dialog_ex_set_context(dialog_ex, app);
dialog_ex_set_result_callback(dialog_ex, storage_settings_scene_sd_info_dialog_callback); dialog_ex_set_result_callback(dialog_ex, storage_settings_scene_sd_info_dialog_callback);
@ -24,6 +26,7 @@ void storage_settings_scene_sd_info_on_enter(void* context) {
32, 32,
AlignCenter, AlignCenter,
AlignCenter); AlignCenter);
dialog_ex_set_center_button_text(dialog_ex, "Ok");
} else { } else {
string_printf( string_printf(
app->text_string, app->text_string,
@ -43,17 +46,25 @@ bool storage_settings_scene_sd_info_on_event(void* context, SceneManagerEvent ev
StorageSettings* app = context; StorageSettings* app = context;
bool consumed = false; bool consumed = false;
FS_Error sd_status = scene_manager_get_scene_state(app->scene_manager, StorageSettingsSDInfo);
if(event.type == SceneManagerEventTypeCustom) { if(event.type == SceneManagerEventTypeCustom) {
switch(event.event) { switch(event.event) {
case DialogExResultLeft: case DialogExResultLeft:
consumed = scene_manager_previous_scene(app->scene_manager); consumed = scene_manager_previous_scene(app->scene_manager);
break; break;
case DialogExResultCenter:
consumed = scene_manager_previous_scene(app->scene_manager);
break;
case DialogExResultRight: case DialogExResultRight:
scene_manager_next_scene(app->scene_manager, StorageSettingsUnmounted); scene_manager_next_scene(app->scene_manager, StorageSettingsUnmounted);
consumed = true; consumed = true;
break; break;
} }
} else if(event.type == SceneManagerEventTypeBack && sd_status != FSE_OK) {
consumed = true;
} }
return consumed; return consumed;
} }

View File

@ -9,11 +9,11 @@ static void
void storage_settings_scene_unmount_confirm_on_enter(void* context) { void storage_settings_scene_unmount_confirm_on_enter(void* context) {
StorageSettings* app = context; StorageSettings* app = context;
FS_Error sd_status = storage_sd_status(app->fs_api);
DialogEx* dialog_ex = app->dialog_ex; DialogEx* dialog_ex = app->dialog_ex;
FS_Error sd_status = storage_sd_status(app->fs_api);
if(sd_status == FSE_NOT_READY) { if(sd_status == FSE_NOT_READY) {
dialog_ex_set_center_button_text(dialog_ex, "OK");
dialog_ex_set_header(dialog_ex, "SD card not mounted", 64, 10, AlignCenter, AlignCenter); dialog_ex_set_header(dialog_ex, "SD card not mounted", 64, 10, AlignCenter, AlignCenter);
dialog_ex_set_text( dialog_ex_set_text(
dialog_ex, dialog_ex,
@ -22,12 +22,13 @@ void storage_settings_scene_unmount_confirm_on_enter(void* context) {
32, 32,
AlignCenter, AlignCenter,
AlignCenter); AlignCenter);
dialog_ex_set_center_button_text(dialog_ex, "OK");
} else { } else {
dialog_ex_set_left_button_text(dialog_ex, "Cancel");
dialog_ex_set_right_button_text(dialog_ex, "Unmount");
dialog_ex_set_header(dialog_ex, "Unmount SD card?", 64, 10, AlignCenter, AlignCenter); dialog_ex_set_header(dialog_ex, "Unmount SD card?", 64, 10, AlignCenter, AlignCenter);
dialog_ex_set_text( dialog_ex_set_text(
dialog_ex, "SD card will be\nunavailable", 64, 32, AlignCenter, AlignCenter); dialog_ex, "SD card will be\nunavailable", 64, 32, AlignCenter, AlignCenter);
dialog_ex_set_left_button_text(dialog_ex, "Cancel");
dialog_ex_set_right_button_text(dialog_ex, "Unmount");
} }
dialog_ex_set_context(dialog_ex, app); dialog_ex_set_context(dialog_ex, app);

View File

@ -200,8 +200,6 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) {
} }
if((subghz->txrx->txrx_state == SubGhzTxRxStateIDLE) || if((subghz->txrx->txrx_state == SubGhzTxRxStateIDLE) ||
(subghz->txrx->txrx_state == SubGhzTxRxStateSleep)) { (subghz->txrx->txrx_state == SubGhzTxRxStateSleep)) {
//ToDo FIX
if(!subghz_tx_start(subghz, subghz->txrx->fff_data)) { if(!subghz_tx_start(subghz, subghz->txrx->fff_data)) {
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowOnlyRx); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowOnlyRx);
} else { } else {

View File

@ -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) { uint8_t subghz_scene_receiver_config_next_frequency(const uint32_t value, void* context) {
furi_assert(context); furi_assert(context);
SubGhz* subghz = context; SubGhz* subghz = context;
int64_t last_value = INT64_MIN;
uint8_t index = 0; uint8_t index = 0;
for(uint8_t i = 0; i < subghz_setting_get_frequency_count(subghz->setting); i++) { 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; index = i;
break; break;
} else {
index = subghz_setting_get_frequency_default_index(subghz->setting);
} }
last_value = subghz_setting_get_frequency(subghz->setting, i);
} }
return index; return index;
} }

View File

@ -39,12 +39,12 @@ void subghz_scene_save_name_on_enter(void* context) {
path_extract_filename(subghz->file_path, file_name, true); path_extract_filename(subghz->file_path, file_name, true);
if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneReadRAW) != if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneReadRAW) !=
SubGhzCustomEventManagerNoSet) { SubGhzCustomEventManagerNoSet) {
subghz_get_next_name_file(subghz, SUBGHZ_MAX_LEN_NAME);
path_extract_filename(subghz->file_path, file_name, true);
if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneReadRAW) == if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneReadRAW) ==
SubGhzCustomEventManagerSetRAW) { SubGhzCustomEventManagerSetRAW) {
dev_name_empty = true; dev_name_empty = true;
subghz_get_next_name_file(subghz, SUBGHZ_MAX_LEN_NAME);
} }
path_extract_filename(subghz->file_path, file_name, true);
} }
string_set(subghz->file_path, dir_name); string_set(subghz->file_path, dir_name);
} }
@ -72,7 +72,11 @@ void subghz_scene_save_name_on_enter(void* context) {
bool subghz_scene_save_name_on_event(void* context, SceneManagerEvent event) { bool subghz_scene_save_name_on_event(void* context, SceneManagerEvent event) {
SubGhz* subghz = context; SubGhz* subghz = context;
if(event.type == SceneManagerEventTypeBack) { if(event.type == SceneManagerEventTypeBack) {
string_set(subghz->file_path, subghz->file_path_tmp); if(!strcmp(subghz->file_name_tmp, "") ||
scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneReadRAW) !=
SubGhzCustomEventManagerNoSet) {
string_set(subghz->file_path, subghz->file_path_tmp);
}
scene_manager_previous_scene(subghz->scene_manager); scene_manager_previous_scene(subghz->scene_manager);
return true; return true;
} else if(event.type == SceneManagerEventTypeCustom) { } else if(event.type == SceneManagerEventTypeCustom) {

View File

@ -26,7 +26,7 @@ static const char* update_task_stage_descr[] = {
[UpdateTaskStageResourcesUpdate] = "Updating resources", [UpdateTaskStageResourcesUpdate] = "Updating resources",
[UpdateTaskStageCompleted] = "Restarting...", [UpdateTaskStageCompleted] = "Restarting...",
[UpdateTaskStageError] = "Error", [UpdateTaskStageError] = "Error",
[UpdateTaskStageOBError] = "OB Err, report", [UpdateTaskStageOBError] = "OB, report",
}; };
typedef struct { typedef struct {

View File

@ -1,8 +1,10 @@
COPRO_CUBE_VERSION := 1.13.3 COPRO_CUBE_VERSION := 1.13.3
COPRO_MCU_FAMILY := STM32WB5x 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 # 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 # Keep 0 for auto, or put a value from release_notes for chosen stack
COPRO_STACK_ADDR := 0 COPRO_STACK_ADDR := 0

View File

@ -1,5 +1,5 @@
V:0 V:0
T:1654009290 T:1655152832
D:badusb D:badusb
D:dolphin D:dolphin
D:infrared D:infrared
@ -7,7 +7,7 @@ D:music_player
D:nfc D:nfc
D:subghz D:subghz
D:u2f D:u2f
F:bb8ffef2d052f171760ce3dc5220cbad:1591:badusb/demo_macos.txt F:0e41ba26498b7511d7c9e6e6b5e3b149:1592:badusb/demo_macos.txt
F:e538ad2ce5a06ec45e1b5b24824901b1:1552:badusb/demo_windows.txt F:e538ad2ce5a06ec45e1b5b24824901b1:1552:badusb/demo_windows.txt
D:dolphin/L1_Boxing_128x64 D:dolphin/L1_Boxing_128x64
D:dolphin/L1_Cry_128x64 D:dolphin/L1_Cry_128x64

View File

@ -13,7 +13,7 @@ DELAY 500
ENTER ENTER
DELAY 750 DELAY 750
REM Copy-Paste previuos string REM Copy-Paste previous string
UP UP
CTRL c CTRL c
@ -77,7 +77,7 @@ ENTER
STRING Flipper Zero BadUSB feature is compatible with USB Rubber Ducky script format STRING Flipper Zero BadUSB feature is compatible with USB Rubber Ducky script format
ENTER ENTER
STRING More information about script synax can be found here: STRING More information about script syntax can be found here:
ENTER ENTER
STRING https://github.com/hak5darren/USB-Rubber-Ducky/wiki/Duckyscript STRING https://github.com/hak5darren/USB-Rubber-Ducky/wiki/Duckyscript
ENTER ENTER

View File

@ -86,20 +86,56 @@ Even if something goes wrong, Updater gives you an option to retry failed operat
| | | **50** | Package has mismatching HW target | | | | **50** | Package has mismatching HW target |
| | | **60** | Missing DFU file | | | | **60** | Missing DFU file |
| | | **80** | Missing radio firmware file | | | | **80** | Missing radio firmware file |
| Checking DFU file | **2** | **0** | Error opening DFU file | | Backing up LFS | **2** | **0-100** | FS read/write error |
| Checking radio FW | **3** | **0-99** | Error reading radio firmware file |
| | | **100** | CRC mismatch |
| Uninstalling radio FW | **4** | **0** | SHCI Delete command error |
| | | **80** | Error awaiting command status |
| Writing radio FW | **5** | **0-100** | Block read/write error |
| Installing radio FW | **6** | **0** | SHCI Install command error |
| | | **80** | Error awaiting command status |
| Radio is updating | **7** | **10** | Error waiting for operation completion |
| Validating opt. bytes | **8** | **yy** | Option byte code |
| Checking DFU file | **9** | **0** | Error opening DFU file |
| | | **1-98** | Error reading DFU file | | | | **1-98** | Error reading DFU file |
| | | **99-100** | Corrupted DFU file | | | | **99-100** | Corrupted DFU file |
| Writing flash | **3** | **0-100** | Block read/write error | | Writing flash | **10** | **0-100** | Block read/write error |
| Validating flash | **4** | **0-100** | Block read/write error | | Validating flash | **11** | **0-100** | Block read/write error |
| Checking radio FW | **5** | **0-99** | Error reading radio firmware file | | Restoring LFS | **12** | **0-100** | FS read/write error |
| | | **100** | CRC mismatch |
| Uninstalling radio FW | **6** | **0** | SHCI Install 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 |
| | | **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 |
| Updating resources | **13** | **0-100** | SD card 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.

View File

@ -43,11 +43,6 @@ typedef enum {
GapCommandKillThread, GapCommandKillThread,
} GapCommand; } GapCommand;
typedef struct {
GapScanCallback callback;
void* context;
} GapScan;
// Identity root key // Identity root key
static const uint8_t gap_irk[16] = static const uint8_t gap_irk[16] =
{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0}; {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}; {0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, 0x21, 0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, 0x21};
static Gap* gap = NULL; static Gap* gap = NULL;
static GapScan* gap_scan = NULL;
static void gap_advertise_start(GapState new_state); static void gap_advertise_start(GapState new_state);
static int32_t gap_app(void* context); 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); aci_gap_slave_security_req(event->Connection_Handle);
} break; } 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: default:
break; break;
} }
@ -550,23 +527,6 @@ GapState gap_get_state() {
return 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() { void gap_thread_stop() {
if(gap) { if(gap) {
osMutexAcquire(gap->state_mutex, osWaitForever); osMutexAcquire(gap->state_mutex, osWaitForever);

View File

@ -33,13 +33,6 @@ typedef struct {
typedef bool (*GapEventCallback)(GapEvent event, void* context); 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 { typedef enum {
GapStateUninitialized, GapStateUninitialized,
GapStateIdle, GapStateIdle,
@ -88,10 +81,6 @@ GapState gap_get_state();
void gap_thread_stop(); void gap_thread_stop();
void gap_start_scan(GapScanCallback callback, void* context);
void gap_stop_scan();
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

45
firmware/targets/f7/furi_hal/furi_hal_bt.c Executable file → Normal file
View File

@ -104,15 +104,18 @@ void furi_hal_bt_unlock_core2() {
static bool furi_hal_bt_radio_stack_is_supported(const BleGlueC2Info* info) { static bool furi_hal_bt_radio_stack_is_supported(const BleGlueC2Info* info) {
bool supported = false; bool supported = false;
if(info->StackType == INFO_STACK_TYPE_BLE_HCI) { if(info->StackType == INFO_STACK_TYPE_BLE_LIGHT) {
furi_hal_bt_stack = FuriHalBtStackHciLayer;
supported = true;
} else if(info->StackType == INFO_STACK_TYPE_BLE_LIGHT) {
if(info->VersionMajor >= FURI_HAL_BT_STACK_VERSION_MAJOR && if(info->VersionMajor >= FURI_HAL_BT_STACK_VERSION_MAJOR &&
info->VersionMinor >= FURI_HAL_BT_STACK_VERSION_MINOR) { info->VersionMinor >= FURI_HAL_BT_STACK_VERSION_MINOR) {
furi_hal_bt_stack = FuriHalBtStackLight; furi_hal_bt_stack = FuriHalBtStackLight;
supported = true; 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 { } else {
furi_hal_bt_stack = FuriHalBtStackUnknown; furi_hal_bt_stack = FuriHalBtStackUnknown;
} }
@ -168,6 +171,22 @@ FuriHalBtStack furi_hal_bt_get_radio_stack() {
return furi_hal_bt_stack; return furi_hal_bt_stack;
} }
bool furi_hal_bt_is_ble_gatt_gap_supported() {
if(furi_hal_bt_stack == FuriHalBtStackLight || furi_hal_bt_stack == FuriHalBtStackFull) {
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) { bool furi_hal_bt_start_app(FuriHalBtProfile profile, GapEventCallback event_cb, void* context) {
furi_assert(event_cb); furi_assert(event_cb);
furi_assert(profile < FuriHalBtProfileNumber); 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"); FURI_LOG_E(TAG, "Can't start BLE App - radio stack did not start");
break; 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"); FURI_LOG_E(TAG, "Can't start Ble App - unsupported radio stack");
break; break;
} }
@ -209,7 +228,7 @@ bool furi_hal_bt_start_app(FuriHalBtProfile profile, GapEventCallback event_cb,
break; break;
} }
// Start selected profile services // Start selected profile services
if(furi_hal_bt_stack == FuriHalBtStackLight) { if(furi_hal_bt_is_ble_gatt_gap_supported()) {
profile_config[profile].start(); profile_config[profile].start();
} }
ret = true; ret = true;
@ -411,20 +430,6 @@ void furi_hal_bt_stop_rx() {
aci_hal_rx_stop(); 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) { bool furi_hal_bt_ensure_c2_mode(BleGlueC2Mode mode) {
BleGlueCommandResult fw_start_res = ble_glue_force_c2_mode(mode); BleGlueCommandResult fw_start_res = ble_glue_force_c2_mode(mode);
if(fw_start_res == BleGlueCommandResultOK) { if(fw_start_res == BleGlueCommandResultOK) {

View File

@ -1,6 +1,9 @@
#include "furi/common_defines.h"
#include "furi_hal_resources.h"
#include <furi_hal_light.h> #include <furi_hal_light.h>
#include <furi_hal_delay.h> #include <furi_hal_delay.h>
#include <lp5562.h> #include <lp5562.h>
#include <stdint.h>
#define LED_CURRENT_RED 50 #define LED_CURRENT_RED 50
#define LED_CURRENT_GREEN 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) { void furi_hal_light_set(Light light, uint8_t value) {
uint8_t prev = 0;
furi_hal_i2c_acquire(&furi_hal_i2c_handle_power); furi_hal_i2c_acquire(&furi_hal_i2c_handle_power);
switch(light) { if(light & LightRed) {
case LightRed:
lp5562_set_channel_value(&furi_hal_i2c_handle_power, LP5562ChannelRed, value); 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); 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); lp5562_set_channel_value(&furi_hal_i2c_handle_power, LP5562ChannelBlue, value);
break; }
case LightBacklight: if(light & LightBacklight) {
prev = lp5562_get_channel_value(&furi_hal_i2c_handle_power, LP5562ChannelWhite); uint8_t prev = lp5562_get_channel_value(&furi_hal_i2c_handle_power, LP5562ChannelWhite);
lp5562_execute_ramp( lp5562_execute_ramp(
&furi_hal_i2c_handle_power, LP5562Engine1, LP5562ChannelWhite, prev, value, 100); &furi_hal_i2c_handle_power, LP5562Engine1, LP5562ChannelWhite, prev, value, 100);
break;
default:
break;
} }
furi_hal_i2c_release(&furi_hal_i2c_handle_power); 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) { void furi_hal_light_sequence(const char* sequence) {
do { do {
if(*sequence == 'R') { if(*sequence == 'R') {

View File

@ -366,44 +366,6 @@ bool furi_hal_nfc_emulate_nfca(
return true; 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) { static bool furi_hal_nfc_transparent_tx_rx(FuriHalNfcTxRxContext* tx_rx, uint16_t timeout_ms) {
furi_assert(tx_rx->nfca_signal); furi_assert(tx_rx->nfca_signal);
@ -430,6 +392,10 @@ static bool furi_hal_nfc_transparent_tx_rx(FuriHalNfcTxRxContext* tx_rx, uint16_
furi_hal_spi_bus_handle_init(&furi_hal_spi_bus_handle_nfc); furi_hal_spi_bus_handle_init(&furi_hal_spi_bus_handle_nfc);
st25r3916ExecuteCommand(ST25R3916_CMD_UNMASK_RECEIVE_DATA); st25r3916ExecuteCommand(ST25R3916_CMD_UNMASK_RECEIVE_DATA);
if(tx_rx->sniff_tx) {
tx_rx->sniff_tx(tx_rx->tx_data, tx_rx->tx_bits, false, tx_rx->sniff_context);
}
// Manually wait for interrupt // Manually wait for interrupt
furi_hal_gpio_init(&gpio_rfid_pull, GpioModeInput, GpioPullDown, GpioSpeedVeryHigh); furi_hal_gpio_init(&gpio_rfid_pull, GpioModeInput, GpioPullDown, GpioSpeedVeryHigh);
st25r3916ClearAndEnableInterrupts(ST25R3916_IRQ_MASK_RXE); st25r3916ClearAndEnableInterrupts(ST25R3916_IRQ_MASK_RXE);
@ -466,6 +432,10 @@ static bool furi_hal_nfc_transparent_tx_rx(FuriHalNfcTxRxContext* tx_rx, uint16_
tx_rx->rx_bits = len * 8; tx_rx->rx_bits = len * 8;
memcpy(tx_rx->rx_data, rx, len); memcpy(tx_rx->rx_data, rx, len);
if(tx_rx->sniff_rx) {
tx_rx->sniff_rx(tx_rx->rx_data, tx_rx->rx_bits, false, tx_rx->sniff_context);
}
ret = true; ret = true;
} else { } else {
FURI_LOG_E(TAG, "Timeout error"); FURI_LOG_E(TAG, "Timeout error");
@ -576,6 +546,12 @@ bool furi_hal_nfc_tx_rx(FuriHalNfcTxRxContext* tx_rx, uint16_t timeout_ms) {
FURI_LOG_E(TAG, "Failed to start data exchange"); FURI_LOG_E(TAG, "Failed to start data exchange");
return false; 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; uint32_t start = DWT->CYCCNT;
while(state != RFAL_NFC_STATE_DATAEXCHANGE_DONE) { while(state != RFAL_NFC_STATE_DATAEXCHANGE_DONE) {
rfalNfcWorker(); rfalNfcWorker();
@ -602,42 +578,42 @@ bool furi_hal_nfc_tx_rx(FuriHalNfcTxRxContext* tx_rx, uint16_t timeout_ms) {
tx_rx->rx_bits = *temp_rx_bits; 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; return true;
} }
ReturnCode furi_hal_nfc_exchange_full( bool furi_hal_nfc_tx_rx_full(FuriHalNfcTxRxContext* tx_rx) {
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;
uint16_t part_len_bytes; uint16_t part_len_bytes;
err = furi_hal_nfc_data_exchange(tx_buff, tx_len, &part_buff, &part_len_bits, false); if(!furi_hal_nfc_tx_rx(tx_rx, 1000)) {
part_len_bytes = *part_len_bits / 8; return false;
if(part_len_bytes > rx_cap) {
return ERR_OVERRUN;
} }
memcpy(rx_buff, part_buff, part_len_bytes); while(tx_rx->rx_bits && tx_rx->rx_data[0] == 0xAF) {
*rx_len = part_len_bytes; FuriHalNfcTxRxContext tmp = *tx_rx;
while(err == ERR_NONE && rx_buff[0] == 0xAF) { tmp.tx_data[0] = 0xAF;
err = furi_hal_nfc_data_exchange(rx_buff, 1, &part_buff, &part_len_bits, false); tmp.tx_bits = 8;
part_len_bytes = *part_len_bits / 8; if(!furi_hal_nfc_tx_rx(&tmp, 1000)) {
if(part_len_bytes > rx_cap - *rx_len) { return false;
return ERR_OVERRUN; }
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) { 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); memcpy(tx_rx->rx_data + tx_rx->rx_bits / 8, tmp.rx_data + 1, part_len_bytes - 1);
*rx_buff = *part_buff; tx_rx->rx_data[0] = tmp.rx_data[0];
*rx_len += part_len_bytes - 1; tx_rx->rx_bits += 8 * (part_len_bytes - 1);
} }
return err; return true;
} }
void furi_hal_nfc_sleep() { void furi_hal_nfc_sleep() {

View File

@ -24,10 +24,10 @@ typedef enum {
/* Light */ /* Light */
typedef enum { typedef enum {
LightRed, LightRed = (1 << 0),
LightGreen, LightGreen = (1 << 1),
LightBlue, LightBlue = (1 << 2),
LightBacklight, LightBacklight = (1 << 3),
} Light; } Light;
typedef struct { typedef struct {

View File

@ -1,7 +1,8 @@
#include "furi_hal_subghz.h" #include "furi_hal_subghz.h"
#include "furi_hal_version.h" #include "furi_hal_subghz_configs.h"
#include "furi_hal_rtc.h"
#include <furi_hal_version.h>
#include <furi_hal_rtc.h>
#include <furi_hal_gpio.h> #include <furi_hal_gpio.h>
#include <furi_hal_spi.h> #include <furi_hal_spi.h>
#include <furi_hal_interrupt.h> #include <furi_hal_interrupt.h>
@ -15,317 +16,22 @@
#define TAG "FuriHalSubGhz" #define TAG "FuriHalSubGhz"
static volatile SubGhzState furi_hal_subghz_state = SubGhzStateInit; typedef struct {
static volatile SubGhzRegulation furi_hal_subghz_regulation = SubGhzRegulationTxRx; volatile SubGhzState state;
static volatile FuriHalSubGhzPreset furi_hal_subghz_preset = FuriHalSubGhzPresetIDLE; volatile SubGhzRegulation regulation;
volatile FuriHalSubGhzPreset preset;
} FuriHalSubGhz;
static const uint8_t furi_hal_subghz_preset_ook_270khz_async_regs[][2] = { volatile FuriHalSubGhz furi_hal_subghz = {
// https://e2e.ti.com/support/wireless-connectivity/sub-1-ghz-group/sub-1-ghz/f/sub-1-ghz-forum/382066/cc1101---don-t-know-the-correct-registers-configuration .state = SubGhzStateInit,
.regulation = SubGhzRegulationTxRx,
/* GPIO GD0 */ .preset = FuriHalSubGhzPresetIDLE,
{CC1101_IOCFG0, 0x0D}, // GD0 as async serial data output/input
/* FIFO and internals */
{CC1101_FIFOTHR, 0x47}, // The only important bit is ADC_RETENTION, FIFO Tx=33 Rx=32
/* Packet engine */
{CC1101_PKTCTRL0, 0x32}, // Async, continious, no whitening
/* Frequency Synthesizer Control */
{CC1101_FSCTRL1, 0x06}, // IF = (26*10^6) / (2^10) * 0x06 = 152343.75Hz
// Modem Configuration
{CC1101_MDMCFG0, 0x00}, // Channel spacing is 25kHz
{CC1101_MDMCFG1, 0x00}, // Channel spacing is 25kHz
{CC1101_MDMCFG2, 0x30}, // Format ASK/OOK, No preamble/sync
{CC1101_MDMCFG3, 0x32}, // Data rate is 3.79372 kBaud
{CC1101_MDMCFG4, 0x67}, // Rx BW filter is 270.833333kHz
/* Main Radio Control State Machine */
{CC1101_MCSM0, 0x18}, // Autocalibrate on idle-to-rx/tx, PO_TIMEOUT is 64 cycles(149-155us)
/* Frequency Offset Compensation Configuration */
{CC1101_FOCCFG,
0x18}, // no frequency offset compensation, POST_K same as PRE_K, PRE_K is 4K, GATE is off
/* Automatic Gain Control */
{CC1101_AGCCTRL0,
0x40}, // 01 - Low hysteresis, small asymmetric dead zone, medium gain; 00 - 8 samples agc; 00 - Normal AGC, 00 - 4dB boundary
{CC1101_AGCCTRL1,
0x00}, // 0; 0 - LNA 2 gain is decreased to minimum before decreasing LNA gain; 00 - Relative carrier sense threshold disabled; 0000 - RSSI to MAIN_TARGET
{CC1101_AGCCTRL2, 0x03}, // 00 - DVGA all; 000 - MAX LNA+LNA2; 011 - MAIN_TARGET 24 dB
/* Wake on radio and timeouts control */
{CC1101_WORCTRL, 0xFB}, // WOR_RES is 2^15 periods (0.91 - 0.94 s) 16.5 - 17.2 hours
/* Frontend configuration */
{CC1101_FREND0, 0x11}, // Adjusts current TX LO buffer + high is PATABLE[1]
{CC1101_FREND1, 0xB6}, //
/* End */
{0, 0},
}; };
static const uint8_t furi_hal_subghz_preset_ook_650khz_async_regs[][2] = {
// https://e2e.ti.com/support/wireless-connectivity/sub-1-ghz-group/sub-1-ghz/f/sub-1-ghz-forum/382066/cc1101---don-t-know-the-correct-registers-configuration
/* GPIO GD0 */
{CC1101_IOCFG0, 0x0D}, // GD0 as async serial data output/input
/* FIFO and internals */
{CC1101_FIFOTHR, 0x07}, // The only important bit is ADC_RETENTION
/* Packet engine */
{CC1101_PKTCTRL0, 0x32}, // Async, continious, no whitening
/* Frequency Synthesizer Control */
{CC1101_FSCTRL1, 0x06}, // IF = (26*10^6) / (2^10) * 0x06 = 152343.75Hz
// Modem Configuration
{CC1101_MDMCFG0, 0x00}, // Channel spacing is 25kHz
{CC1101_MDMCFG1, 0x00}, // Channel spacing is 25kHz
{CC1101_MDMCFG2, 0x30}, // Format ASK/OOK, No preamble/sync
{CC1101_MDMCFG3, 0x32}, // Data rate is 3.79372 kBaud
{CC1101_MDMCFG4, 0x17}, // Rx BW filter is 650.000kHz
/* Main Radio Control State Machine */
{CC1101_MCSM0, 0x18}, // Autocalibrate on idle-to-rx/tx, PO_TIMEOUT is 64 cycles(149-155us)
/* Frequency Offset Compensation Configuration */
{CC1101_FOCCFG,
0x18}, // no frequency offset compensation, POST_K same as PRE_K, PRE_K is 4K, GATE is off
/* Automatic Gain Control */
// {CC1101_AGCTRL0,0x40}, // 01 - Low hysteresis, small asymmetric dead zone, medium gain; 00 - 8 samples agc; 00 - Normal AGC, 00 - 4dB boundary
// {CC1101_AGCTRL1,0x00}, // 0; 0 - LNA 2 gain is decreased to minimum before decreasing LNA gain; 00 - Relative carrier sense threshold disabled; 0000 - RSSI to MAIN_TARGET
// {CC1101_AGCCTRL2, 0x03}, // 00 - DVGA all; 000 - MAX LNA+LNA2; 011 - MAIN_TARGET 24 dB
//MAGN_TARGET for RX filter BW =< 100 kHz is 0x3. For higher RX filter BW's MAGN_TARGET is 0x7.
{CC1101_AGCCTRL0,
0x91}, // 10 - Medium hysteresis, medium asymmetric dead zone, medium gain ; 01 - 16 samples agc; 00 - Normal AGC, 01 - 8dB boundary
{CC1101_AGCCTRL1,
0x0}, // 0; 0 - LNA 2 gain is decreased to minimum before decreasing LNA gain; 00 - Relative carrier sense threshold disabled; 0000 - RSSI to MAIN_TARGET
{CC1101_AGCCTRL2, 0x07}, // 00 - DVGA all; 000 - MAX LNA+LNA2; 111 - MAIN_TARGET 42 dB
/* Wake on radio and timeouts control */
{CC1101_WORCTRL, 0xFB}, // WOR_RES is 2^15 periods (0.91 - 0.94 s) 16.5 - 17.2 hours
/* Frontend configuration */
{CC1101_FREND0, 0x11}, // Adjusts current TX LO buffer + high is PATABLE[1]
{CC1101_FREND1, 0xB6}, //
/* End */
{0, 0},
};
static const uint8_t furi_hal_subghz_preset_2fsk_dev2_38khz_async_regs[][2] = {
/* GPIO GD0 */
{CC1101_IOCFG0, 0x0D}, // GD0 as async serial data output/input
/* Frequency Synthesizer Control */
{CC1101_FSCTRL1, 0x06}, // IF = (26*10^6) / (2^10) * 0x06 = 152343.75Hz
/* Packet engine */
{CC1101_PKTCTRL0, 0x32}, // Async, continious, no whitening
{CC1101_PKTCTRL1, 0x04},
// // Modem Configuration
{CC1101_MDMCFG0, 0x00},
{CC1101_MDMCFG1, 0x02},
{CC1101_MDMCFG2, 0x04}, // Format 2-FSK/FM, No preamble/sync, Disable (current optimized)
{CC1101_MDMCFG3, 0x83}, // Data rate is 4.79794 kBaud
{CC1101_MDMCFG4, 0x67}, //Rx BW filter is 270.833333 kHz
{CC1101_DEVIATN, 0x04}, //Deviation 2.380371 kHz
/* Main Radio Control State Machine */
{CC1101_MCSM0, 0x18}, // Autocalibrate on idle-to-rx/tx, PO_TIMEOUT is 64 cycles(149-155us)
/* Frequency Offset Compensation Configuration */
{CC1101_FOCCFG,
0x16}, // no frequency offset compensation, POST_K same as PRE_K, PRE_K is 4K, GATE is off
/* Automatic Gain Control */
{CC1101_AGCCTRL0,
0x91}, //10 - Medium hysteresis, medium asymmetric dead zone, medium gain ; 01 - 16 samples agc; 00 - Normal AGC, 01 - 8dB boundary
{CC1101_AGCCTRL1,
0x00}, // 0; 0 - LNA 2 gain is decreased to minimum before decreasing LNA gain; 00 - Relative carrier sense threshold disabled; 0000 - RSSI to MAIN_TARGET
{CC1101_AGCCTRL2, 0x07}, // 00 - DVGA all; 000 - MAX LNA+LNA2; 111 - MAIN_TARGET 42 dB
/* Wake on radio and timeouts control */
{CC1101_WORCTRL, 0xFB}, // WOR_RES is 2^15 periods (0.91 - 0.94 s) 16.5 - 17.2 hours
/* Frontend configuration */
{CC1101_FREND0, 0x10}, // Adjusts current TX LO buffer
{CC1101_FREND1, 0x56},
/* End */
{0, 0},
};
static const uint8_t furi_hal_subghz_preset_2fsk_dev47_6khz_async_regs[][2] = {
/* GPIO GD0 */
{CC1101_IOCFG0, 0x0D}, // GD0 as async serial data output/input
/* Frequency Synthesizer Control */
{CC1101_FSCTRL1, 0x06}, // IF = (26*10^6) / (2^10) * 0x06 = 152343.75Hz
/* Packet engine */
{CC1101_PKTCTRL0, 0x32}, // Async, continious, no whitening
{CC1101_PKTCTRL1, 0x04},
// // Modem Configuration
{CC1101_MDMCFG0, 0x00},
{CC1101_MDMCFG1, 0x02},
{CC1101_MDMCFG2, 0x04}, // Format 2-FSK/FM, No preamble/sync, Disable (current optimized)
{CC1101_MDMCFG3, 0x83}, // Data rate is 4.79794 kBaud
{CC1101_MDMCFG4, 0x67}, //Rx BW filter is 270.833333 kHz
{CC1101_DEVIATN, 0x47}, //Deviation 47.60742 kHz
/* Main Radio Control State Machine */
{CC1101_MCSM0, 0x18}, // Autocalibrate on idle-to-rx/tx, PO_TIMEOUT is 64 cycles(149-155us)
/* Frequency Offset Compensation Configuration */
{CC1101_FOCCFG,
0x16}, // no frequency offset compensation, POST_K same as PRE_K, PRE_K is 4K, GATE is off
/* Automatic Gain Control */
{CC1101_AGCCTRL0,
0x91}, //10 - Medium hysteresis, medium asymmetric dead zone, medium gain ; 01 - 16 samples agc; 00 - Normal AGC, 01 - 8dB boundary
{CC1101_AGCCTRL1,
0x00}, // 0; 0 - LNA 2 gain is decreased to minimum before decreasing LNA gain; 00 - Relative carrier sense threshold disabled; 0000 - RSSI to MAIN_TARGET
{CC1101_AGCCTRL2, 0x07}, // 00 - DVGA all; 000 - MAX LNA+LNA2; 111 - MAIN_TARGET 42 dB
/* Wake on radio and timeouts control */
{CC1101_WORCTRL, 0xFB}, // WOR_RES is 2^15 periods (0.91 - 0.94 s) 16.5 - 17.2 hours
/* Frontend configuration */
{CC1101_FREND0, 0x10}, // Adjusts current TX LO buffer
{CC1101_FREND1, 0x56},
/* End */
{0, 0},
};
static const uint8_t furi_hal_subghz_preset_msk_99_97kb_async_regs[][2] = {
/* GPIO GD0 */
{CC1101_IOCFG0, 0x06},
{CC1101_FIFOTHR, 0x07}, // The only important bit is ADC_RETENTION
{CC1101_SYNC1, 0x46},
{CC1101_SYNC0, 0x4C},
{CC1101_ADDR, 0x00},
{CC1101_PKTLEN, 0x00},
{CC1101_CHANNR, 0x00},
{CC1101_PKTCTRL0, 0x05},
{CC1101_FSCTRL0, 0x23},
{CC1101_FSCTRL1, 0x06},
{CC1101_MDMCFG0, 0xF8},
{CC1101_MDMCFG1, 0x22},
{CC1101_MDMCFG2, 0x72},
{CC1101_MDMCFG3, 0xF8},
{CC1101_MDMCFG4, 0x5B},
{CC1101_DEVIATN, 0x47},
{CC1101_MCSM0, 0x18},
{CC1101_FOCCFG, 0x16},
{CC1101_AGCCTRL0, 0xB2},
{CC1101_AGCCTRL1, 0x00},
{CC1101_AGCCTRL2, 0xC7},
{CC1101_FREND0, 0x10},
{CC1101_FREND1, 0x56},
{CC1101_BSCFG, 0x1C},
{CC1101_FSTEST, 0x59},
/* End */
{0, 0},
};
static const uint8_t furi_hal_subghz_preset_gfsk_9_99kb_async_regs[][2] = {
{CC1101_IOCFG0, 0x06}, //GDO0 Output Pin Configuration
{CC1101_FIFOTHR, 0x47}, //RX FIFO and TX FIFO Thresholds
//1 : CRC calculation in TX and CRC check in RX enabled,
//1 : Variable packet length mode. Packet length configured by the first byte after sync word
{CC1101_PKTCTRL0, 0x05},
{CC1101_FSCTRL1, 0x06}, //Frequency Synthesizer Control
{CC1101_SYNC1, 0x46},
{CC1101_SYNC0, 0x4C},
{CC1101_ADDR, 0x00},
{CC1101_PKTLEN, 0x00},
{CC1101_MDMCFG4, 0xC8}, //Modem Configuration 9.99
{CC1101_MDMCFG3, 0x93}, //Modem Configuration
{CC1101_MDMCFG2, 0x12}, // 2: 16/16 sync word bits detected
{CC1101_DEVIATN, 0x34}, //Deviation = 19.042969
{CC1101_MCSM0, 0x18}, //Main Radio Control State Machine Configuration
{CC1101_FOCCFG, 0x16}, //Frequency Offset Compensation Configuration
{CC1101_AGCCTRL2, 0x43}, //AGC Control
{CC1101_AGCCTRL1, 0x40},
{CC1101_AGCCTRL0, 0x91},
{CC1101_WORCTRL, 0xFB}, //Wake On Radio Control
/* End */
{0, 0},
};
static const uint8_t furi_hal_subghz_preset_ook_async_patable[8] = {
0x00,
0xC0, // 12dBm 0xC0, 10dBm 0xC5, 7dBm 0xCD, 5dBm 0x86, 0dBm 0x50, -6dBm 0x37, -10dBm 0x26, -15dBm 0x1D, -20dBm 0x17, -30dBm 0x03
0x00,
0x00,
0x00,
0x00,
0x00,
0x00};
static const uint8_t furi_hal_subghz_preset_ook_async_patable_au[8] = {
0x00,
0x37, // 12dBm 0xC0, 10dBm 0xC5, 7dBm 0xCD, 5dBm 0x86, 0dBm 0x50, -6dBm 0x37, -10dBm 0x26, -15dBm 0x1D, -20dBm 0x17, -30dBm 0x03
0x00,
0x00,
0x00,
0x00,
0x00,
0x00};
static const uint8_t furi_hal_subghz_preset_2fsk_async_patable[8] = {
0xC0, // 10dBm 0xC0, 7dBm 0xC8, 5dBm 0x84, 0dBm 0x60, -10dBm 0x34, -15dBm 0x1D, -20dBm 0x0E, -30dBm 0x12
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00};
static const uint8_t furi_hal_subghz_preset_msk_async_patable[8] = {
0xC0, // 10dBm 0xC0, 7dBm 0xC8, 5dBm 0x84, 0dBm 0x60, -10dBm 0x34, -15dBm 0x1D, -20dBm 0x0E, -30dBm 0x12
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00};
static const uint8_t furi_hal_subghz_preset_gfsk_async_patable[8] = {
0xC0, // 10dBm 0xC0, 7dBm 0xC8, 5dBm 0x84, 0dBm 0x60, -10dBm 0x34, -15dBm 0x1D, -20dBm 0x0E, -30dBm 0x12
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00};
void furi_hal_subghz_init() { void furi_hal_subghz_init() {
furi_assert(furi_hal_subghz_state == SubGhzStateInit); furi_assert(furi_hal_subghz.state == SubGhzStateInit);
furi_hal_subghz_state = SubGhzStateIdle; furi_hal_subghz.state = SubGhzStateIdle;
furi_hal_subghz_preset = FuriHalSubGhzPresetIDLE; furi_hal_subghz.preset = FuriHalSubGhzPresetIDLE;
furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz);
@ -368,7 +74,7 @@ void furi_hal_subghz_init() {
} }
void furi_hal_subghz_sleep() { void furi_hal_subghz_sleep() {
furi_assert(furi_hal_subghz_state == SubGhzStateIdle); furi_assert(furi_hal_subghz.state == SubGhzStateIdle);
furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz);
cc1101_switch_to_idle(&furi_hal_spi_bus_handle_subghz); cc1101_switch_to_idle(&furi_hal_spi_bus_handle_subghz);
@ -380,7 +86,7 @@ void furi_hal_subghz_sleep() {
furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz);
furi_hal_subghz_preset = FuriHalSubGhzPresetIDLE; furi_hal_subghz.preset = FuriHalSubGhzPresetIDLE;
} }
void furi_hal_subghz_dump_state() { void furi_hal_subghz_dump_state() {
@ -414,7 +120,7 @@ void furi_hal_subghz_load_preset(FuriHalSubGhzPreset preset) {
} else { } else {
furi_crash("SubGhz: Missing config."); furi_crash("SubGhz: Missing config.");
} }
furi_hal_subghz_preset = preset; furi_hal_subghz.preset = preset;
} }
void furi_hal_subghz_load_registers(const uint8_t data[][2]) { void furi_hal_subghz_load_registers(const uint8_t data[][2]) {
@ -515,7 +221,7 @@ void furi_hal_subghz_rx() {
} }
bool furi_hal_subghz_tx() { bool furi_hal_subghz_tx() {
if(furi_hal_subghz_regulation != SubGhzRegulationTxRx) return false; if(furi_hal_subghz.regulation != SubGhzRegulationTxRx) return false;
furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz);
cc1101_switch_to_tx(&furi_hal_spi_bus_handle_subghz); cc1101_switch_to_tx(&furi_hal_spi_bus_handle_subghz);
furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz);
@ -589,8 +295,8 @@ bool furi_hal_subghz_is_tx_allowed(uint32_t value) {
} else { } else {
if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) {
if((value >= 304100000 && value <= 321950000) && if((value >= 304100000 && value <= 321950000) &&
((furi_hal_subghz_preset == FuriHalSubGhzPresetOok270Async) || ((furi_hal_subghz.preset == FuriHalSubGhzPresetOok270Async) ||
(furi_hal_subghz_preset == FuriHalSubGhzPresetOok650Async))) { (furi_hal_subghz.preset == FuriHalSubGhzPresetOok650Async))) {
furi_hal_subghz_load_patable(furi_hal_subghz_preset_ook_async_patable_au); furi_hal_subghz_load_patable(furi_hal_subghz_preset_ook_async_patable_au);
} }
} }
@ -615,9 +321,9 @@ bool furi_hal_subghz_is_tx_allowed(uint32_t value) {
uint32_t furi_hal_subghz_set_frequency(uint32_t value) { uint32_t furi_hal_subghz_set_frequency(uint32_t value) {
if(furi_hal_subghz_is_tx_allowed(value)) { if(furi_hal_subghz_is_tx_allowed(value)) {
furi_hal_subghz_regulation = SubGhzRegulationTxRx; furi_hal_subghz.regulation = SubGhzRegulationTxRx;
} else { } else {
furi_hal_subghz_regulation = SubGhzRegulationOnlyRx; furi_hal_subghz.regulation = SubGhzRegulationOnlyRx;
} }
furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz);
@ -684,8 +390,8 @@ static void furi_hal_subghz_capture_ISR() {
} }
void furi_hal_subghz_start_async_rx(FuriHalSubGhzCaptureCallback callback, void* context) { void furi_hal_subghz_start_async_rx(FuriHalSubGhzCaptureCallback callback, void* context) {
furi_assert(furi_hal_subghz_state == SubGhzStateIdle); furi_assert(furi_hal_subghz.state == SubGhzStateIdle);
furi_hal_subghz_state = SubGhzStateAsyncRx; furi_hal_subghz.state = SubGhzStateAsyncRx;
furi_hal_subghz_capture_callback = callback; furi_hal_subghz_capture_callback = callback;
furi_hal_subghz_capture_callback_context = context; furi_hal_subghz_capture_callback_context = context;
@ -741,8 +447,8 @@ void furi_hal_subghz_start_async_rx(FuriHalSubGhzCaptureCallback callback, void*
} }
void furi_hal_subghz_stop_async_rx() { void furi_hal_subghz_stop_async_rx() {
furi_assert(furi_hal_subghz_state == SubGhzStateAsyncRx); furi_assert(furi_hal_subghz.state == SubGhzStateAsyncRx);
furi_hal_subghz_state = SubGhzStateIdle; furi_hal_subghz.state = SubGhzStateIdle;
// Shutdown radio // Shutdown radio
furi_hal_subghz_idle(); furi_hal_subghz_idle();
@ -822,7 +528,10 @@ static void furi_hal_subghz_async_tx_refill(uint32_t* buffer, size_t samples) {
} }
static void furi_hal_subghz_async_tx_dma_isr() { static void furi_hal_subghz_async_tx_dma_isr() {
furi_assert(furi_hal_subghz_state == SubGhzStateAsyncTx); furi_assert(
furi_hal_subghz.state == SubGhzStateAsyncTx ||
furi_hal_subghz.state == SubGhzStateAsyncTxEnd ||
furi_hal_subghz.state == SubGhzStateAsyncTxLast);
if(LL_DMA_IsActiveFlag_HT1(DMA1)) { if(LL_DMA_IsActiveFlag_HT1(DMA1)) {
LL_DMA_ClearFlag_HT1(DMA1); LL_DMA_ClearFlag_HT1(DMA1);
furi_hal_subghz_async_tx_refill( furi_hal_subghz_async_tx_refill(
@ -840,12 +549,12 @@ static void furi_hal_subghz_async_tx_timer_isr() {
if(LL_TIM_IsActiveFlag_UPDATE(TIM2)) { if(LL_TIM_IsActiveFlag_UPDATE(TIM2)) {
LL_TIM_ClearFlag_UPDATE(TIM2); LL_TIM_ClearFlag_UPDATE(TIM2);
if(LL_TIM_GetAutoReload(TIM2) == 0) { if(LL_TIM_GetAutoReload(TIM2) == 0) {
if(furi_hal_subghz_state == SubGhzStateAsyncTx) { if(furi_hal_subghz.state == SubGhzStateAsyncTx) {
furi_hal_subghz_state = SubGhzStateAsyncTxLast; furi_hal_subghz.state = SubGhzStateAsyncTxLast;
//forcibly pulls the pin to the ground so that there is no carrier //forcibly pulls the pin to the ground so that there is no carrier
furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullDown, GpioSpeedLow); furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullDown, GpioSpeedLow);
} else { } else {
furi_hal_subghz_state = SubGhzStateAsyncTxEnd; furi_hal_subghz.state = SubGhzStateAsyncTxEnd;
LL_TIM_DisableCounter(TIM2); LL_TIM_DisableCounter(TIM2);
} }
} }
@ -853,16 +562,16 @@ static void furi_hal_subghz_async_tx_timer_isr() {
} }
bool furi_hal_subghz_start_async_tx(FuriHalSubGhzAsyncTxCallback callback, void* context) { bool furi_hal_subghz_start_async_tx(FuriHalSubGhzAsyncTxCallback callback, void* context) {
furi_assert(furi_hal_subghz_state == SubGhzStateIdle); furi_assert(furi_hal_subghz.state == SubGhzStateIdle);
furi_assert(callback); furi_assert(callback);
//If transmission is prohibited by regional settings //If transmission is prohibited by regional settings
if(furi_hal_subghz_regulation != SubGhzRegulationTxRx) return false; if(furi_hal_subghz.regulation != SubGhzRegulationTxRx) return false;
furi_hal_subghz_async_tx.callback = callback; furi_hal_subghz_async_tx.callback = callback;
furi_hal_subghz_async_tx.callback_context = context; furi_hal_subghz_async_tx.callback_context = context;
furi_hal_subghz_state = SubGhzStateAsyncTx; furi_hal_subghz.state = SubGhzStateAsyncTx;
furi_hal_subghz_async_tx.duty_low = 0; furi_hal_subghz_async_tx.duty_low = 0;
furi_hal_subghz_async_tx.duty_high = 0; furi_hal_subghz_async_tx.duty_high = 0;
@ -935,14 +644,14 @@ bool furi_hal_subghz_start_async_tx(FuriHalSubGhzAsyncTxCallback callback, void*
} }
bool furi_hal_subghz_is_async_tx_complete() { bool furi_hal_subghz_is_async_tx_complete() {
return furi_hal_subghz_state == SubGhzStateAsyncTxEnd; return furi_hal_subghz.state == SubGhzStateAsyncTxEnd;
} }
void furi_hal_subghz_stop_async_tx() { void furi_hal_subghz_stop_async_tx() {
furi_assert( furi_assert(
furi_hal_subghz_state == SubGhzStateAsyncTx || furi_hal_subghz.state == SubGhzStateAsyncTx ||
furi_hal_subghz_state == SubGhzStateAsyncTxLast || furi_hal_subghz.state == SubGhzStateAsyncTxLast ||
furi_hal_subghz_state == SubGhzStateAsyncTxEnd); furi_hal_subghz.state == SubGhzStateAsyncTxEnd);
// Shutdown radio // Shutdown radio
furi_hal_subghz_idle(); furi_hal_subghz_idle();
@ -976,5 +685,5 @@ void furi_hal_subghz_stop_async_tx() {
(double)furi_hal_subghz_async_tx.duty_low, (double)furi_hal_subghz_async_tx.duty_low,
(double)duty_cycle); (double)duty_cycle);
furi_hal_subghz_state = SubGhzStateIdle; furi_hal_subghz.state = SubGhzStateIdle;
} }

View File

@ -0,0 +1,314 @@
#pragma once
#include <cc1101.h>
static const uint8_t furi_hal_subghz_preset_ook_270khz_async_regs[][2] = {
// https://e2e.ti.com/support/wireless-connectivity/sub-1-ghz-group/sub-1-ghz/f/sub-1-ghz-forum/382066/cc1101---don-t-know-the-correct-registers-configuration
/* GPIO GD0 */
{CC1101_IOCFG0, 0x0D}, // GD0 as async serial data output/input
/* FIFO and internals */
{CC1101_FIFOTHR, 0x47}, // The only important bit is ADC_RETENTION, FIFO Tx=33 Rx=32
/* Packet engine */
{CC1101_PKTCTRL0, 0x32}, // Async, continious, no whitening
/* Frequency Synthesizer Control */
{CC1101_FSCTRL1, 0x06}, // IF = (26*10^6) / (2^10) * 0x06 = 152343.75Hz
// Modem Configuration
{CC1101_MDMCFG0, 0x00}, // Channel spacing is 25kHz
{CC1101_MDMCFG1, 0x00}, // Channel spacing is 25kHz
{CC1101_MDMCFG2, 0x30}, // Format ASK/OOK, No preamble/sync
{CC1101_MDMCFG3, 0x32}, // Data rate is 3.79372 kBaud
{CC1101_MDMCFG4, 0x67}, // Rx BW filter is 270.833333kHz
/* Main Radio Control State Machine */
{CC1101_MCSM0, 0x18}, // Autocalibrate on idle-to-rx/tx, PO_TIMEOUT is 64 cycles(149-155us)
/* Frequency Offset Compensation Configuration */
{CC1101_FOCCFG,
0x18}, // no frequency offset compensation, POST_K same as PRE_K, PRE_K is 4K, GATE is off
/* Automatic Gain Control */
{CC1101_AGCCTRL0,
0x40}, // 01 - Low hysteresis, small asymmetric dead zone, medium gain; 00 - 8 samples agc; 00 - Normal AGC, 00 - 4dB boundary
{CC1101_AGCCTRL1,
0x00}, // 0; 0 - LNA 2 gain is decreased to minimum before decreasing LNA gain; 00 - Relative carrier sense threshold disabled; 0000 - RSSI to MAIN_TARGET
{CC1101_AGCCTRL2, 0x03}, // 00 - DVGA all; 000 - MAX LNA+LNA2; 011 - MAIN_TARGET 24 dB
/* Wake on radio and timeouts control */
{CC1101_WORCTRL, 0xFB}, // WOR_RES is 2^15 periods (0.91 - 0.94 s) 16.5 - 17.2 hours
/* Frontend configuration */
{CC1101_FREND0, 0x11}, // Adjusts current TX LO buffer + high is PATABLE[1]
{CC1101_FREND1, 0xB6}, //
/* End */
{0, 0},
};
static const uint8_t furi_hal_subghz_preset_ook_650khz_async_regs[][2] = {
// https://e2e.ti.com/support/wireless-connectivity/sub-1-ghz-group/sub-1-ghz/f/sub-1-ghz-forum/382066/cc1101---don-t-know-the-correct-registers-configuration
/* GPIO GD0 */
{CC1101_IOCFG0, 0x0D}, // GD0 as async serial data output/input
/* FIFO and internals */
{CC1101_FIFOTHR, 0x07}, // The only important bit is ADC_RETENTION
/* Packet engine */
{CC1101_PKTCTRL0, 0x32}, // Async, continious, no whitening
/* Frequency Synthesizer Control */
{CC1101_FSCTRL1, 0x06}, // IF = (26*10^6) / (2^10) * 0x06 = 152343.75Hz
// Modem Configuration
{CC1101_MDMCFG0, 0x00}, // Channel spacing is 25kHz
{CC1101_MDMCFG1, 0x00}, // Channel spacing is 25kHz
{CC1101_MDMCFG2, 0x30}, // Format ASK/OOK, No preamble/sync
{CC1101_MDMCFG3, 0x32}, // Data rate is 3.79372 kBaud
{CC1101_MDMCFG4, 0x17}, // Rx BW filter is 650.000kHz
/* Main Radio Control State Machine */
{CC1101_MCSM0, 0x18}, // Autocalibrate on idle-to-rx/tx, PO_TIMEOUT is 64 cycles(149-155us)
/* Frequency Offset Compensation Configuration */
{CC1101_FOCCFG,
0x18}, // no frequency offset compensation, POST_K same as PRE_K, PRE_K is 4K, GATE is off
/* Automatic Gain Control */
// {CC1101_AGCTRL0,0x40}, // 01 - Low hysteresis, small asymmetric dead zone, medium gain; 00 - 8 samples agc; 00 - Normal AGC, 00 - 4dB boundary
// {CC1101_AGCTRL1,0x00}, // 0; 0 - LNA 2 gain is decreased to minimum before decreasing LNA gain; 00 - Relative carrier sense threshold disabled; 0000 - RSSI to MAIN_TARGET
// {CC1101_AGCCTRL2, 0x03}, // 00 - DVGA all; 000 - MAX LNA+LNA2; 011 - MAIN_TARGET 24 dB
//MAGN_TARGET for RX filter BW =< 100 kHz is 0x3. For higher RX filter BW's MAGN_TARGET is 0x7.
{CC1101_AGCCTRL0,
0x91}, // 10 - Medium hysteresis, medium asymmetric dead zone, medium gain ; 01 - 16 samples agc; 00 - Normal AGC, 01 - 8dB boundary
{CC1101_AGCCTRL1,
0x0}, // 0; 0 - LNA 2 gain is decreased to minimum before decreasing LNA gain; 00 - Relative carrier sense threshold disabled; 0000 - RSSI to MAIN_TARGET
{CC1101_AGCCTRL2, 0x07}, // 00 - DVGA all; 000 - MAX LNA+LNA2; 111 - MAIN_TARGET 42 dB
/* Wake on radio and timeouts control */
{CC1101_WORCTRL, 0xFB}, // WOR_RES is 2^15 periods (0.91 - 0.94 s) 16.5 - 17.2 hours
/* Frontend configuration */
{CC1101_FREND0, 0x11}, // Adjusts current TX LO buffer + high is PATABLE[1]
{CC1101_FREND1, 0xB6}, //
/* End */
{0, 0},
};
static const uint8_t furi_hal_subghz_preset_2fsk_dev2_38khz_async_regs[][2] = {
/* GPIO GD0 */
{CC1101_IOCFG0, 0x0D}, // GD0 as async serial data output/input
/* Frequency Synthesizer Control */
{CC1101_FSCTRL1, 0x06}, // IF = (26*10^6) / (2^10) * 0x06 = 152343.75Hz
/* Packet engine */
{CC1101_PKTCTRL0, 0x32}, // Async, continious, no whitening
{CC1101_PKTCTRL1, 0x04},
// // Modem Configuration
{CC1101_MDMCFG0, 0x00},
{CC1101_MDMCFG1, 0x02},
{CC1101_MDMCFG2, 0x04}, // Format 2-FSK/FM, No preamble/sync, Disable (current optimized)
{CC1101_MDMCFG3, 0x83}, // Data rate is 4.79794 kBaud
{CC1101_MDMCFG4, 0x67}, //Rx BW filter is 270.833333 kHz
{CC1101_DEVIATN, 0x04}, //Deviation 2.380371 kHz
/* Main Radio Control State Machine */
{CC1101_MCSM0, 0x18}, // Autocalibrate on idle-to-rx/tx, PO_TIMEOUT is 64 cycles(149-155us)
/* Frequency Offset Compensation Configuration */
{CC1101_FOCCFG,
0x16}, // no frequency offset compensation, POST_K same as PRE_K, PRE_K is 4K, GATE is off
/* Automatic Gain Control */
{CC1101_AGCCTRL0,
0x91}, //10 - Medium hysteresis, medium asymmetric dead zone, medium gain ; 01 - 16 samples agc; 00 - Normal AGC, 01 - 8dB boundary
{CC1101_AGCCTRL1,
0x00}, // 0; 0 - LNA 2 gain is decreased to minimum before decreasing LNA gain; 00 - Relative carrier sense threshold disabled; 0000 - RSSI to MAIN_TARGET
{CC1101_AGCCTRL2, 0x07}, // 00 - DVGA all; 000 - MAX LNA+LNA2; 111 - MAIN_TARGET 42 dB
/* Wake on radio and timeouts control */
{CC1101_WORCTRL, 0xFB}, // WOR_RES is 2^15 periods (0.91 - 0.94 s) 16.5 - 17.2 hours
/* Frontend configuration */
{CC1101_FREND0, 0x10}, // Adjusts current TX LO buffer
{CC1101_FREND1, 0x56},
/* End */
{0, 0},
};
static const uint8_t furi_hal_subghz_preset_2fsk_dev47_6khz_async_regs[][2] = {
/* GPIO GD0 */
{CC1101_IOCFG0, 0x0D}, // GD0 as async serial data output/input
/* Frequency Synthesizer Control */
{CC1101_FSCTRL1, 0x06}, // IF = (26*10^6) / (2^10) * 0x06 = 152343.75Hz
/* Packet engine */
{CC1101_PKTCTRL0, 0x32}, // Async, continious, no whitening
{CC1101_PKTCTRL1, 0x04},
// // Modem Configuration
{CC1101_MDMCFG0, 0x00},
{CC1101_MDMCFG1, 0x02},
{CC1101_MDMCFG2, 0x04}, // Format 2-FSK/FM, No preamble/sync, Disable (current optimized)
{CC1101_MDMCFG3, 0x83}, // Data rate is 4.79794 kBaud
{CC1101_MDMCFG4, 0x67}, //Rx BW filter is 270.833333 kHz
{CC1101_DEVIATN, 0x47}, //Deviation 47.60742 kHz
/* Main Radio Control State Machine */
{CC1101_MCSM0, 0x18}, // Autocalibrate on idle-to-rx/tx, PO_TIMEOUT is 64 cycles(149-155us)
/* Frequency Offset Compensation Configuration */
{CC1101_FOCCFG,
0x16}, // no frequency offset compensation, POST_K same as PRE_K, PRE_K is 4K, GATE is off
/* Automatic Gain Control */
{CC1101_AGCCTRL0,
0x91}, //10 - Medium hysteresis, medium asymmetric dead zone, medium gain ; 01 - 16 samples agc; 00 - Normal AGC, 01 - 8dB boundary
{CC1101_AGCCTRL1,
0x00}, // 0; 0 - LNA 2 gain is decreased to minimum before decreasing LNA gain; 00 - Relative carrier sense threshold disabled; 0000 - RSSI to MAIN_TARGET
{CC1101_AGCCTRL2, 0x07}, // 00 - DVGA all; 000 - MAX LNA+LNA2; 111 - MAIN_TARGET 42 dB
/* Wake on radio and timeouts control */
{CC1101_WORCTRL, 0xFB}, // WOR_RES is 2^15 periods (0.91 - 0.94 s) 16.5 - 17.2 hours
/* Frontend configuration */
{CC1101_FREND0, 0x10}, // Adjusts current TX LO buffer
{CC1101_FREND1, 0x56},
/* End */
{0, 0},
};
static const uint8_t furi_hal_subghz_preset_msk_99_97kb_async_regs[][2] = {
/* GPIO GD0 */
{CC1101_IOCFG0, 0x06},
{CC1101_FIFOTHR, 0x07}, // The only important bit is ADC_RETENTION
{CC1101_SYNC1, 0x46},
{CC1101_SYNC0, 0x4C},
{CC1101_ADDR, 0x00},
{CC1101_PKTLEN, 0x00},
{CC1101_CHANNR, 0x00},
{CC1101_PKTCTRL0, 0x05},
{CC1101_FSCTRL0, 0x23},
{CC1101_FSCTRL1, 0x06},
{CC1101_MDMCFG0, 0xF8},
{CC1101_MDMCFG1, 0x22},
{CC1101_MDMCFG2, 0x72},
{CC1101_MDMCFG3, 0xF8},
{CC1101_MDMCFG4, 0x5B},
{CC1101_DEVIATN, 0x47},
{CC1101_MCSM0, 0x18},
{CC1101_FOCCFG, 0x16},
{CC1101_AGCCTRL0, 0xB2},
{CC1101_AGCCTRL1, 0x00},
{CC1101_AGCCTRL2, 0xC7},
{CC1101_FREND0, 0x10},
{CC1101_FREND1, 0x56},
{CC1101_BSCFG, 0x1C},
{CC1101_FSTEST, 0x59},
/* End */
{0, 0},
};
static const uint8_t furi_hal_subghz_preset_gfsk_9_99kb_async_regs[][2] = {
{CC1101_IOCFG0, 0x06}, //GDO0 Output Pin Configuration
{CC1101_FIFOTHR, 0x47}, //RX FIFO and TX FIFO Thresholds
//1 : CRC calculation in TX and CRC check in RX enabled,
//1 : Variable packet length mode. Packet length configured by the first byte after sync word
{CC1101_PKTCTRL0, 0x05},
{CC1101_FSCTRL1, 0x06}, //Frequency Synthesizer Control
{CC1101_SYNC1, 0x46},
{CC1101_SYNC0, 0x4C},
{CC1101_ADDR, 0x00},
{CC1101_PKTLEN, 0x00},
{CC1101_MDMCFG4, 0xC8}, //Modem Configuration 9.99
{CC1101_MDMCFG3, 0x93}, //Modem Configuration
{CC1101_MDMCFG2, 0x12}, // 2: 16/16 sync word bits detected
{CC1101_DEVIATN, 0x34}, //Deviation = 19.042969
{CC1101_MCSM0, 0x18}, //Main Radio Control State Machine Configuration
{CC1101_FOCCFG, 0x16}, //Frequency Offset Compensation Configuration
{CC1101_AGCCTRL2, 0x43}, //AGC Control
{CC1101_AGCCTRL1, 0x40},
{CC1101_AGCCTRL0, 0x91},
{CC1101_WORCTRL, 0xFB}, //Wake On Radio Control
/* End */
{0, 0},
};
static const uint8_t furi_hal_subghz_preset_ook_async_patable[8] = {
0x00,
0xC0, // 12dBm 0xC0, 10dBm 0xC5, 7dBm 0xCD, 5dBm 0x86, 0dBm 0x50, -6dBm 0x37, -10dBm 0x26, -15dBm 0x1D, -20dBm 0x17, -30dBm 0x03
0x00,
0x00,
0x00,
0x00,
0x00,
0x00};
static const uint8_t furi_hal_subghz_preset_ook_async_patable_au[8] = {
0x00,
0x37, // 12dBm 0xC0, 10dBm 0xC5, 7dBm 0xCD, 5dBm 0x86, 0dBm 0x50, -6dBm 0x37, -10dBm 0x26, -15dBm 0x1D, -20dBm 0x17, -30dBm 0x03
0x00,
0x00,
0x00,
0x00,
0x00,
0x00};
static const uint8_t furi_hal_subghz_preset_2fsk_async_patable[8] = {
0xC0, // 10dBm 0xC0, 7dBm 0xC8, 5dBm 0x84, 0dBm 0x60, -10dBm 0x34, -15dBm 0x1D, -20dBm 0x0E, -30dBm 0x12
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00};
static const uint8_t furi_hal_subghz_preset_msk_async_patable[8] = {
0xC0, // 10dBm 0xC0, 7dBm 0xC8, 5dBm 0x84, 0dBm 0x60, -10dBm 0x34, -15dBm 0x1D, -20dBm 0x0E, -30dBm 0x12
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00};
static const uint8_t furi_hal_subghz_preset_gfsk_async_patable[8] = {
0xC0, // 10dBm 0xC0, 7dBm 0xC8, 5dBm 0x84, 0dBm 0x60, -10dBm 0x34, -15dBm 0x1D, -20dBm 0x0E, -30dBm 0x12
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00};

View File

@ -15,7 +15,7 @@
#include "furi_hal_bt_serial.h" #include "furi_hal_bt_serial.h"
#define FURI_HAL_BT_STACK_VERSION_MAJOR (1) #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 #define FURI_HAL_BT_C2_START_TIMEOUT 1000
#ifdef __cplusplus #ifdef __cplusplus
@ -24,8 +24,8 @@ extern "C" {
typedef enum { typedef enum {
FuriHalBtStackUnknown, FuriHalBtStackUnknown,
FuriHalBtStackHciLayer,
FuriHalBtStackLight, FuriHalBtStackLight,
FuriHalBtStackFull,
} FuriHalBtStack; } FuriHalBtStack;
typedef enum { typedef enum {
@ -58,6 +58,18 @@ bool furi_hal_bt_start_radio_stack();
*/ */
FuriHalBtStack furi_hal_bt_get_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 /** Start BLE app
* *
* @param profile FuriHalBtProfile instance * @param profile FuriHalBtProfile instance
@ -206,17 +218,6 @@ float furi_hal_bt_get_rssi();
*/ */
uint32_t furi_hal_bt_get_transmitted_packets(); 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 /** Check & switch C2 to given mode
* *
* @param[in] mode mode to switch into * @param[in] mode mode to switch into

View File

@ -24,6 +24,25 @@ void furi_hal_light_init();
*/ */
void furi_hal_light_set(Light light, uint8_t value); 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 /** Execute sequence
* *
* @param sequence Sequence to execute * @param sequence Sequence to execute

41
firmware/targets/furi_hal_include/furi_hal_nfc.h Executable file → Normal file
View File

@ -17,7 +17,7 @@ extern "C" {
#endif #endif
#define FURI_HAL_NFC_UID_MAX_LEN 10 #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_PARITY_BUFF_SIZE (FURI_HAL_NFC_DATA_BUFF_SIZE / 8)
#define FURI_HAL_NFC_TXRX_DEFAULT \ #define FURI_HAL_NFC_TXRX_DEFAULT \
@ -80,6 +80,9 @@ typedef struct {
uint8_t sak; uint8_t sak;
} FuriHalNfcDevData; } FuriHalNfcDevData;
typedef void (
*FuriHalNfcTxRxSniffCallback)(uint8_t* data, uint16_t bits, bool crc_dropped, void* context);
typedef struct { typedef struct {
uint8_t tx_data[FURI_HAL_NFC_DATA_BUFF_SIZE]; uint8_t tx_data[FURI_HAL_NFC_DATA_BUFF_SIZE];
uint8_t tx_parity[FURI_HAL_NFC_PARITY_BUFF_SIZE]; uint8_t tx_parity[FURI_HAL_NFC_PARITY_BUFF_SIZE];
@ -89,6 +92,10 @@ typedef struct {
uint16_t rx_bits; uint16_t rx_bits;
FuriHalNfcTxRxType tx_rx_type; FuriHalNfcTxRxType tx_rx_type;
NfcaSignal* nfca_signal; NfcaSignal* nfca_signal;
FuriHalNfcTxRxSniffCallback sniff_tx;
FuriHalNfcTxRxSniffCallback sniff_rx;
void* sniff_context;
} FuriHalNfcTxRxContext; } FuriHalNfcTxRxContext;
/** Init nfc /** Init nfc
@ -165,23 +172,6 @@ bool furi_hal_nfc_emulate_nfca(
void* context, void* context,
uint32_t timeout); 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 /** NFC data exchange
* *
* @param tx_rx_ctx FuriHalNfcTxRxContext instance * @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 /** NFC data full exhange
* *
* @param tx_buff transmit buffer * @param tx_rx_ctx FuriHalNfcTxRxContext instance
* @param tx_len transmit buffer length
* @param rx_buff receive buffer
* @param rx_cap receive buffer capacity
* @param rx_len receive buffer length
* *
* @return ST ReturnCode * @return true on success
*/ */
ReturnCode furi_hal_nfc_exchange_full( bool furi_hal_nfc_tx_rx_full(FuriHalNfcTxRxContext* tx_rx);
uint8_t* tx_buff,
uint16_t tx_len,
uint8_t* rx_buff,
uint16_t rx_cap,
uint16_t* rx_len);
/** NFC deactivate and start sleep /** NFC deactivate and start sleep
*/ */

View File

@ -1,4 +1,5 @@
#include "lp5562.h" #include "lp5562.h"
#include "furi/common_defines.h"
#include "lp5562_reg.h" #include "lp5562_reg.h"
#include <furi_hal.h> #include <furi_hal.h>
@ -79,27 +80,32 @@ uint8_t lp5562_get_channel_value(FuriHalI2cBusHandle* handle, LP5562Channel chan
return value; return value;
} }
static void void lp5562_set_channel_src(FuriHalI2cBusHandle* handle, LP5562Channel channel, LP5562Engine src) {
lp5562_set_channel_src(FuriHalI2cBusHandle* handle, LP5562Channel channel, LP5562Engine src) {
uint8_t reg_val = 0; uint8_t reg_val = 0;
uint8_t bit_offset = 0; uint8_t bit_offset = 0;
if(channel == LP5562ChannelRed) { do {
bit_offset = 4; if(channel & LP5562ChannelRed) {
} else if(channel == LP5562ChannelGreen) { bit_offset = 4;
bit_offset = 2; channel &= ~LP5562ChannelRed;
} else if(channel == LP5562ChannelBlue) { } else if(channel & LP5562ChannelGreen) {
bit_offset = 0; bit_offset = 2;
} else if(channel == LP5562ChannelWhite) { channel &= ~LP5562ChannelGreen;
bit_offset = 6; } else if(channel & LP5562ChannelBlue) {
} else { bit_offset = 0;
return; channel &= ~LP5562ChannelBlue;
} } else if(channel & LP5562ChannelWhite) {
bit_offset = 6;
channel &= ~LP5562ChannelWhite;
} else {
return;
}
furi_hal_i2c_read_reg_8(handle, LP5562_ADDRESS, 0x70, &reg_val, LP5562_I2C_TIMEOUT); furi_hal_i2c_read_reg_8(handle, LP5562_ADDRESS, 0x70, &reg_val, LP5562_I2C_TIMEOUT);
reg_val &= ~(0x3 << bit_offset); reg_val &= ~(0x3 << bit_offset);
reg_val |= ((src & 0x03) << 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_write_reg_8(handle, LP5562_ADDRESS, 0x70, reg_val, LP5562_I2C_TIMEOUT);
} while(channel);
} }
void lp5562_execute_program( 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); 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, &reg_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( void lp5562_execute_ramp(
FuriHalI2cBusHandle* handle, FuriHalI2cBusHandle* handle,
LP5562Engine eng, LP5562Engine eng,
@ -194,3 +213,54 @@ void lp5562_execute_ramp(
// Write end value to register // Write end value to register
lp5562_set_channel_value(handle, ch, val_end); 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);
}

View File

@ -6,10 +6,10 @@
/** Channel types */ /** Channel types */
typedef enum { typedef enum {
LP5562ChannelRed, LP5562ChannelRed = (1 << 0),
LP5562ChannelGreen, LP5562ChannelGreen = (1 << 1),
LP5562ChannelBlue, LP5562ChannelBlue = (1 << 2),
LP5562ChannelWhite, LP5562ChannelWhite = (1 << 3),
} LP5562Channel; } LP5562Channel;
typedef enum { typedef enum {
@ -37,6 +37,9 @@ void lp5562_set_channel_value(FuriHalI2cBusHandle* handle, LP5562Channel channel
/** Get channel PWM value */ /** Get channel PWM value */
uint8_t lp5562_get_channel_value(FuriHalI2cBusHandle* handle, LP5562Channel channel); 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 */ /** Execute program sequence */
void lp5562_execute_program( void lp5562_execute_program(
FuriHalI2cBusHandle* handle, FuriHalI2cBusHandle* handle,
@ -44,6 +47,9 @@ void lp5562_execute_program(
LP5562Channel ch, LP5562Channel ch,
uint16_t* program); uint16_t* program);
/** Stop program sequence */
void lp5562_stop_program(FuriHalI2cBusHandle* handle, LP5562Engine eng);
/** Execute ramp program sequence */ /** Execute ramp program sequence */
void lp5562_execute_ramp( void lp5562_execute_ramp(
FuriHalI2cBusHandle* handle, FuriHalI2cBusHandle* handle,
@ -52,3 +58,12 @@ void lp5562_execute_ramp(
uint8_t val_start, uint8_t val_start,
uint8_t val_end, uint8_t val_end,
uint16_t time); 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);

View File

@ -397,7 +397,8 @@ bool emv_read_bank_card(FuriHalNfcTxRxContext* tx_rx, EmvApplication* emv_app) {
bool emv_card_emulation(FuriHalNfcTxRxContext* tx_rx) { bool emv_card_emulation(FuriHalNfcTxRxContext* tx_rx) {
furi_assert(tx_rx); furi_assert(tx_rx);
bool emulation_complete = false; bool emulation_complete = false;
memset(tx_rx, 0, sizeof(FuriHalNfcTxRxContext)); tx_rx->tx_bits = 0;
tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault;
do { do {
FURI_LOG_D(TAG, "Read select PPSE command"); FURI_LOG_D(TAG, "Read select PPSE command");

View File

@ -270,7 +270,9 @@ static bool mf_classic_auth(
MfClassicKey key_type, MfClassicKey key_type,
Crypto1* crypto) { Crypto1* crypto) {
bool auth_success = false; 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 { do {
if(key_type == MfClassicKeyA) { if(key_type == MfClassicKeyA) {
@ -372,7 +374,8 @@ bool mf_classic_read_block(
bool read_block_success = false; bool read_block_success = false;
uint8_t plain_cmd[4] = {MF_CLASSIC_READ_SECT_CMD, block_num, 0x00, 0x00}; uint8_t plain_cmd[4] = {MF_CLASSIC_READ_SECT_CMD, block_num, 0x00, 0x00};
nfca_append_crc16(plain_cmd, 2); 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++) { for(uint8_t i = 0; i < 4; i++) {
tx_rx->tx_data[i] = crypto1_byte(crypto, 0x00, 0) ^ plain_cmd[i]; tx_rx->tx_data[i] = crypto1_byte(crypto, 0x00, 0) ^ plain_cmd[i];

View File

@ -1,4 +1,6 @@
#include "path.h" #include "path.h"
#include "m-string.h"
#include <stddef.h>
void path_extract_filename_no_ext(const char* path, string_t filename) { void path_extract_filename_no_ext(const char* path, string_t filename) {
string_set(filename, path); 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) { static inline void path_cleanup(string_t path) {
string_strim(path); string_strim(path);
while(string_end_with_str_p(path, "/")) { while(string_end_with_str_p(path, "/")) {

View File

@ -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); 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 * @brief Extract last path component
* *

View File

@ -15,16 +15,20 @@ class App:
# Application specific initialization # Application specific initialization
self.init() 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) self.args, self.other_args = self.parser.parse_known_args(args=args)
# configure log output # configure log output
# if skip_logger_init:
self.log_level = logging.DEBUG if self.args.debug else logging.INFO self.log_level = logging.DEBUG if self.args.debug else logging.INFO
self.logger.setLevel(self.log_level) self.logger.setLevel(self.log_level)
self.handler = logging.StreamHandler(sys.stdout) if not self.logger.hasHandlers():
self.handler.setLevel(self.log_level) self.handler = logging.StreamHandler(sys.stdout)
self.formatter = logging.Formatter("%(asctime)s [%(levelname)s] %(message)s") self.handler.setLevel(self.log_level)
self.handler.setFormatter(self.formatter) self.formatter = logging.Formatter(
self.logger.addHandler(self.handler) "%(asctime)s [%(levelname)s] %(message)s"
)
self.handler.setFormatter(self.formatter)
self.logger.addHandler(self.handler)
# execute requested function # execute requested function
self.before() self.before()

34
scripts/ob_custradio.data Normal file
View File

@ -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

View File

@ -3,7 +3,7 @@
from flipper.app import App from flipper.app import App
from flipper.utils.fff import FlipperFormatFile from flipper.utils.fff import FlipperFormatFile
from flipper.assets.coprobin import CoproBinary, get_stack_type 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 from os.path import basename, join, exists
import os import os
import shutil import shutil
@ -21,6 +21,16 @@ class Main(App):
RESOURCE_TAR_FORMAT = tarfile.USTAR_FORMAT RESOURCE_TAR_FORMAT = tarfile.USTAR_FORMAT
RESOURCE_FILE_NAME = "resources.tar" 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): def init(self):
self.subparsers = self.parser.add_subparsers(help="sub-command help") self.subparsers = self.parser.add_subparsers(help="sub-command help")
@ -53,6 +63,9 @@ class Main(App):
) )
self.parser_generate.add_argument("--obdata", dest="obdata", required=False) 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) self.parser_generate.set_defaults(func=self.generate)
@ -70,10 +83,20 @@ class Main(App):
raise ValueError("Missing --radiotype") raise ValueError("Missing --radiotype")
radio_meta = CoproBinary(self.args.radiobin) radio_meta = CoproBinary(self.args.radiobin)
radio_version = self.copro_version_as_int(radio_meta, self.args.radiotype) 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: if radio_addr == 0:
radio_addr = radio_meta.get_flash_load_addr() radio_addr = radio_meta.get_flash_load_addr()
self.logger.info( 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" " or specify --radioaddr"
) )
@ -81,7 +104,9 @@ class Main(App):
os.makedirs(self.args.directory) os.makedirs(self.args.directory)
shutil.copyfile(self.args.stage, join(self.args.directory, stage_basename)) shutil.copyfile(self.args.stage, join(self.args.directory, stage_basename))
dfu_size = 0
if self.args.dfu: if self.args.dfu:
dfu_size = os.stat(self.args.dfu).st_size
shutil.copyfile(self.args.dfu, join(self.args.directory, dfu_basename)) shutil.copyfile(self.args.dfu, join(self.args.directory, dfu_basename))
if radiobin_basename: if radiobin_basename:
shutil.copyfile( shutil.copyfile(
@ -93,6 +118,12 @@ class Main(App):
self.args.resources, join(self.args.directory, resources_basename) 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 = FlipperFormatFile()
file.setHeader( file.setHeader(
"Flipper firmware upgrade configuration", self.UPDATE_MANIFEST_VERSION "Flipper firmware upgrade configuration", self.UPDATE_MANIFEST_VERSION
@ -111,19 +142,43 @@ class Main(App):
else: else:
file.writeKey("Radio CRC", self.int2ffhex(0)) file.writeKey("Radio CRC", self.int2ffhex(0))
file.writeKey("Resources", resources_basename) file.writeKey("Resources", resources_basename)
file.writeComment( obvalues = ObReferenceValues((), (), ())
"NEVER EVER MESS WITH THESE VALUES, YOU WILL BRICK YOUR DEVICE"
)
if self.args.obdata: if self.args.obdata:
obd = OptionBytesData(self.args.obdata) obd = OptionBytesData(self.args.obdata)
obvalues = obd.gen_values().export() obvalues = obd.gen_values().export()
file.writeKey("OB reference", self.bytes2ffhex(obvalues.reference)) file.writeComment(
file.writeKey("OB mask", self.bytes2ffhex(obvalues.compare_mask)) "NEVER EVER MESS WITH THESE VALUES, YOU WILL BRICK YOUR DEVICE"
file.writeKey("OB write mask", self.bytes2ffhex(obvalues.write_mask)) )
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)) file.save(join(self.args.directory, self.UPDATE_MANIFEST_NAME))
return 0 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): def package_resources(self, srcdir: str, dst_name: str):
with tarfile.open( with tarfile.open(
dst_name, self.RESOURCE_TAR_MODE, format=self.RESOURCE_TAR_FORMAT dst_name, self.RESOURCE_TAR_MODE, format=self.RESOURCE_TAR_FORMAT