Merge branch 'DarkFlippers:dev' into dev

This commit is contained in:
TQMatvey 2022-12-21 06:44:25 +07:00 committed by GitHub
commit d1418bf7f6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 831 additions and 82 deletions

View File

@ -0,0 +1,110 @@
#include <furi.h>
#include <furi_hal.h>
#include "../minunit.h"
#include <bt/bt_service/bt_keys_storage.h>
#include <storage/storage.h>
#define BT_TEST_KEY_STORAGE_FILE_PATH EXT_PATH("unit_tests/bt_test.keys")
#define BT_TEST_NVM_RAM_BUFF_SIZE (507 * 4) // The same as in ble NVM storage
typedef struct {
Storage* storage;
BtKeysStorage* bt_keys_storage;
uint8_t* nvm_ram_buff_dut;
uint8_t* nvm_ram_buff_ref;
} BtTest;
BtTest* bt_test = NULL;
void bt_test_alloc() {
bt_test = malloc(sizeof(BtTest));
bt_test->storage = furi_record_open(RECORD_STORAGE);
bt_test->nvm_ram_buff_dut = malloc(BT_TEST_NVM_RAM_BUFF_SIZE);
bt_test->nvm_ram_buff_ref = malloc(BT_TEST_NVM_RAM_BUFF_SIZE);
bt_test->bt_keys_storage = bt_keys_storage_alloc(BT_TEST_KEY_STORAGE_FILE_PATH);
bt_keys_storage_set_ram_params(
bt_test->bt_keys_storage, bt_test->nvm_ram_buff_dut, BT_TEST_NVM_RAM_BUFF_SIZE);
}
void bt_test_free() {
furi_assert(bt_test);
free(bt_test->nvm_ram_buff_ref);
free(bt_test->nvm_ram_buff_dut);
bt_keys_storage_free(bt_test->bt_keys_storage);
furi_record_close(RECORD_STORAGE);
free(bt_test);
bt_test = NULL;
}
static void bt_test_keys_storage_profile() {
// Emulate nvm change on initial connection
const int nvm_change_size_on_connection = 88;
for(size_t i = 0; i < nvm_change_size_on_connection; i++) {
bt_test->nvm_ram_buff_dut[i] = rand();
bt_test->nvm_ram_buff_ref[i] = bt_test->nvm_ram_buff_dut[i];
}
// Emulate update storage on initial connect
mu_assert(
bt_keys_storage_update(
bt_test->bt_keys_storage, bt_test->nvm_ram_buff_dut, nvm_change_size_on_connection),
"Failed to update key storage on initial connect");
memset(bt_test->nvm_ram_buff_dut, 0, BT_TEST_NVM_RAM_BUFF_SIZE);
mu_assert(bt_keys_storage_load(bt_test->bt_keys_storage), "Failed to load NVM");
mu_assert(
memcmp(
bt_test->nvm_ram_buff_ref, bt_test->nvm_ram_buff_dut, nvm_change_size_on_connection) ==
0,
"Wrong buffer loaded");
const int nvm_disconnect_update_offset = 84;
const int nvm_disconnect_update_size = 324;
const int nvm_total_size = nvm_change_size_on_connection -
(nvm_change_size_on_connection - nvm_disconnect_update_offset) +
nvm_disconnect_update_size;
// Emulate update storage on initial disconnect
for(size_t i = nvm_disconnect_update_offset;
i < nvm_disconnect_update_offset + nvm_disconnect_update_size;
i++) {
bt_test->nvm_ram_buff_dut[i] = rand();
bt_test->nvm_ram_buff_ref[i] = bt_test->nvm_ram_buff_dut[i];
}
mu_assert(
bt_keys_storage_update(
bt_test->bt_keys_storage,
&bt_test->nvm_ram_buff_dut[nvm_disconnect_update_offset],
nvm_disconnect_update_size),
"Failed to update key storage on initial disconnect");
memset(bt_test->nvm_ram_buff_dut, 0, BT_TEST_NVM_RAM_BUFF_SIZE);
mu_assert(bt_keys_storage_load(bt_test->bt_keys_storage), "Failed to load NVM");
mu_assert(
memcmp(bt_test->nvm_ram_buff_ref, bt_test->nvm_ram_buff_dut, nvm_total_size) == 0,
"Wrong buffer loaded");
}
static void bt_test_keys_remove_test_file() {
mu_assert(
storage_simply_remove(bt_test->storage, BT_TEST_KEY_STORAGE_FILE_PATH),
"Can't remove test file");
}
MU_TEST(bt_test_keys_storage_serial_profile) {
furi_assert(bt_test);
bt_test_keys_remove_test_file();
bt_test_keys_storage_profile();
bt_test_keys_remove_test_file();
}
MU_TEST_SUITE(test_bt) {
bt_test_alloc();
MU_RUN_TEST(bt_test_keys_storage_serial_profile);
bt_test_free();
}
int run_minunit_test_bt() {
MU_RUN_SUITE(test_bt);
return MU_EXIT_CODE;
}

View File

@ -24,6 +24,7 @@ int run_minunit_test_protocol_dict();
int run_minunit_test_lfrfid_protocols();
int run_minunit_test_nfc();
int run_minunit_test_bit_lib();
int run_minunit_test_bt();
typedef int (*UnitTestEntry)();
@ -49,6 +50,7 @@ const UnitTest unit_tests[] = {
{.name = "protocol_dict", .entry = run_minunit_test_protocol_dict},
{.name = "lfrfid", .entry = run_minunit_test_lfrfid_protocols},
{.name = "bit_lib", .entry = run_minunit_test_bit_lib},
{.name = "bt", .entry = run_minunit_test_bt},
};
void minunit_print_progress() {

View File

@ -6,6 +6,8 @@
#include "../helpers/archive_browser.h"
#define TAG "Archive"
#define SCROLL_INTERVAL (333)
#define SCROLL_DELAY (2)
static const char* ArchiveTabNames[] = {
[ArchiveTabFavorites] = "Favorites",
@ -268,13 +270,18 @@ static void draw_list(Canvas* canvas, ArchiveBrowserViewModel* model) {
furi_string_set(str_buf, "---");
}
elements_string_fit_width(
canvas, str_buf, (scrollbar ? MAX_LEN_PX - 6 : MAX_LEN_PX) - x_offset);
size_t scroll_counter = model->scroll_counter;
if(model->item_idx == idx) {
archive_draw_frame(canvas, i, scrollbar, model->move_fav);
if(scroll_counter < SCROLL_DELAY) {
scroll_counter = 0;
} else {
scroll_counter -= SCROLL_DELAY;
}
} else {
canvas_set_color(canvas, ColorBlack);
scroll_counter = 0;
}
if(custom_icon_data) {
@ -284,8 +291,15 @@ static void draw_list(Canvas* canvas, ArchiveBrowserViewModel* model) {
canvas_draw_icon(
canvas, 2 + x_offset, 16 + i * FRAME_HEIGHT, ArchiveItemIcons[file_type]);
}
canvas_draw_str(
canvas, 15 + x_offset, 24 + i * FRAME_HEIGHT, furi_string_get_cstr(str_buf));
elements_scrollable_text_line(
canvas,
15 + x_offset,
24 + i * FRAME_HEIGHT,
((scrollbar ? MAX_LEN_PX - 6 : MAX_LEN_PX) - x_offset),
str_buf,
scroll_counter,
(model->item_idx != idx));
furi_string_free(str_buf);
}
@ -454,6 +468,7 @@ static bool archive_view_input(InputEvent* event, void* context) {
if(move_fav_mode) {
browser->callback(ArchiveBrowserEventFavMoveUp, browser->context);
}
model->scroll_counter = 0;
} else if(event->key == InputKeyDown) {
model->item_idx = (model->item_idx + 1) % model->item_cnt;
if(is_file_list_load_required(model)) {
@ -463,6 +478,7 @@ static bool archive_view_input(InputEvent* event, void* context) {
if(move_fav_mode) {
browser->callback(ArchiveBrowserEventFavMoveDown, browser->context);
}
model->scroll_counter = 0;
}
},
true);
@ -502,6 +518,27 @@ static bool archive_view_input(InputEvent* event, void* context) {
return true;
}
static void browser_scroll_timer(void* context) {
furi_assert(context);
ArchiveBrowserView* browser = context;
with_view_model(
browser->view, ArchiveBrowserViewModel * model, { model->scroll_counter++; }, true);
}
static void browser_view_enter(void* context) {
furi_assert(context);
ArchiveBrowserView* browser = context;
with_view_model(
browser->view, ArchiveBrowserViewModel * model, { model->scroll_counter = 0; }, true);
furi_timer_start(browser->scroll_timer, SCROLL_INTERVAL);
}
static void browser_view_exit(void* context) {
furi_assert(context);
ArchiveBrowserView* browser = context;
furi_timer_stop(browser->scroll_timer);
}
ArchiveBrowserView* browser_alloc() {
ArchiveBrowserView* browser = malloc(sizeof(ArchiveBrowserView));
browser->view = view_alloc();
@ -509,6 +546,10 @@ ArchiveBrowserView* browser_alloc() {
view_set_context(browser->view, browser);
view_set_draw_callback(browser->view, archive_view_render);
view_set_input_callback(browser->view, archive_view_input);
view_set_enter_callback(browser->view, browser_view_enter);
view_set_exit_callback(browser->view, browser_view_exit);
browser->scroll_timer = furi_timer_alloc(browser_scroll_timer, FuriTimerTypePeriodic, browser);
browser->path = furi_string_alloc_set(archive_get_default_path(TAB_DEFAULT));
@ -528,6 +569,8 @@ ArchiveBrowserView* browser_alloc() {
void browser_free(ArchiveBrowserView* browser) {
furi_assert(browser);
furi_timer_free(browser->scroll_timer);
if(browser->worker_running) {
file_browser_worker_free(browser->worker);
}

View File

@ -78,6 +78,7 @@ struct ArchiveBrowserView {
FuriString* path;
InputKey last_tab_switch_dir;
bool is_root;
FuriTimer* scroll_timer;
};
typedef struct {
@ -96,6 +97,7 @@ typedef struct {
int32_t item_idx;
int32_t array_offset;
int32_t list_offset;
size_t scroll_counter;
} ArchiveBrowserViewModel;
void archive_browser_set_callback(

View File

@ -367,9 +367,17 @@ int32_t hid_ble_app(void* p) {
Hid* app = hid_alloc(HidTransportBle);
app = hid_app_alloc_view(app);
bt_disconnect(app->bt);
// Wait 2nd core to update nvm storage
furi_delay_ms(200);
bt_keys_storage_set_storage_path(app->bt, HID_BT_KEYS_STORAGE_PATH);
if(!bt_set_profile(app->bt, BtProfileHidKeyboard)) {
FURI_LOG_E(TAG, "Failed to switch profile");
FURI_LOG_E(TAG, "Failed to switch to HID profile");
}
furi_hal_bt_start_advertising();
bt_set_status_changed_callback(app->bt, bt_hid_connection_status_changed_callback, app);
@ -378,7 +386,17 @@ int32_t hid_ble_app(void* p) {
view_dispatcher_run(app->view_dispatcher);
bt_set_status_changed_callback(app->bt, NULL, NULL);
bt_set_profile(app->bt, BtProfileSerial);
bt_disconnect(app->bt);
// Wait 2nd core to update nvm storage
furi_delay_ms(200);
bt_keys_storage_set_default_path(app->bt);
if(!bt_set_profile(app->bt, BtProfileSerial)) {
FURI_LOG_E(TAG, "Failed to switch to Serial profile");
}
hid_free(app);

View File

@ -11,6 +11,7 @@
#include <gui/view.h>
#include <gui/view_dispatcher.h>
#include <notification/notification.h>
#include <storage/storage.h>
#include <gui/modules/submenu.h>
#include <gui/modules/dialog_ex.h>
@ -22,6 +23,8 @@
#include "views/hid_mouse_jiggler.h"
#include "views/hid_tiktok.h"
#define HID_BT_KEYS_STORAGE_PATH EXT_PATH("apps/Tools/.bt_hid.keys")
typedef enum {
HidTransportUsb,
HidTransportBle,

View File

@ -117,6 +117,8 @@ Bt* bt_alloc() {
if(!bt_settings_load(&bt->bt_settings)) {
bt_settings_save(&bt->bt_settings);
}
// Keys storage
bt->keys_storage = bt_keys_storage_alloc(BT_KEYS_STORAGE_PATH);
// Alloc queue
bt->message_queue = furi_message_queue_alloc(8, sizeof(BtMessage));
@ -285,8 +287,10 @@ static bool bt_on_gap_event_callback(GapEvent event, void* context) {
static void bt_on_key_storage_change_callback(uint8_t* addr, uint16_t size, void* context) {
furi_assert(context);
Bt* bt = context;
FURI_LOG_I(TAG, "Changed addr start: %p, size changed: %d", addr, size);
BtMessage message = {.type = BtMessageTypeKeysStorageUpdated};
BtMessage message = {
.type = BtMessageTypeKeysStorageUpdated,
.data.key_storage_data.start_address = addr,
.data.key_storage_data.size = size};
furi_check(
furi_message_queue_put(bt->message_queue, &message, FuriWaitForever) == FuriStatusOk);
}
@ -331,6 +335,8 @@ static void bt_change_profile(Bt* bt, BtMessage* message) {
furi_profile = FuriHalBtProfileSerial;
}
bt_keys_storage_load(bt->keys_storage);
if(furi_hal_bt_change_app(furi_profile, bt_on_gap_event_callback, bt)) {
FURI_LOG_I(TAG, "Bt App started");
if(bt->bt_settings.enabled) {
@ -358,6 +364,7 @@ static void bt_change_profile(Bt* bt, BtMessage* message) {
static void bt_close_connection(Bt* bt) {
bt_close_rpc_connection(bt);
furi_hal_bt_stop_advertising();
furi_event_flag_set(bt->api_event, BT_API_UNLOCK_EVENT);
}
@ -372,8 +379,8 @@ int32_t bt_srv(void* p) {
return 0;
}
// Read keys
if(!bt_keys_storage_load(bt)) {
// Load keys
if(!bt_keys_storage_load(bt->keys_storage)) {
FURI_LOG_W(TAG, "Failed to load bonding keys");
}
@ -418,13 +425,16 @@ int32_t bt_srv(void* p) {
// Display PIN code
bt_pin_code_show(bt, message.data.pin_code);
} else if(message.type == BtMessageTypeKeysStorageUpdated) {
bt_keys_storage_save(bt);
bt_keys_storage_update(
bt->keys_storage,
message.data.key_storage_data.start_address,
message.data.key_storage_data.size);
} else if(message.type == BtMessageTypeSetProfile) {
bt_change_profile(bt, &message);
} else if(message.type == BtMessageTypeDisconnect) {
bt_close_connection(bt);
} else if(message.type == BtMessageTypeForgetBondedDevices) {
bt_keys_storage_delete(bt);
bt_keys_storage_delete(bt->keys_storage);
}
}
return 0;

View File

@ -56,6 +56,19 @@ void bt_set_status_changed_callback(Bt* bt, BtStatusChangedCallback callback, vo
*/
void bt_forget_bonded_devices(Bt* bt);
/** Set keys storage file path
*
* @param bt Bt instance
* @param keys_storage_path Path to file with saved keys
*/
void bt_keys_storage_set_storage_path(Bt* bt, const char* keys_storage_path);
/** Set default keys storage file path
*
* @param bt Bt instance
*/
void bt_keys_storage_set_default_path(Bt* bt);
#ifdef __cplusplus
}
#endif

View File

@ -39,3 +39,18 @@ void bt_forget_bonded_devices(Bt* bt) {
furi_check(
furi_message_queue_put(bt->message_queue, &message, FuriWaitForever) == FuriStatusOk);
}
void bt_keys_storage_set_storage_path(Bt* bt, const char* keys_storage_path) {
furi_assert(bt);
furi_assert(bt->keys_storage);
furi_assert(keys_storage_path);
bt_keys_storage_set_file_path(bt->keys_storage, keys_storage_path);
}
void bt_keys_storage_set_default_path(Bt* bt) {
furi_assert(bt);
furi_assert(bt->keys_storage);
bt_keys_storage_set_file_path(bt->keys_storage, BT_KEYS_STORAGE_PATH);
}

View File

@ -13,8 +13,14 @@
#include <power/power_service/power.h>
#include <rpc/rpc.h>
#include <notification/notification.h>
#include <storage/storage.h>
#include <bt/bt_settings.h>
#include <bt/bt_service/bt_keys_storage.h>
#include "bt_keys_filename.h"
#define BT_KEYS_STORAGE_PATH INT_PATH(BT_KEYS_STORAGE_FILE_NAME)
#define BT_API_UNLOCK_EVENT (1UL << 0)
@ -29,10 +35,16 @@ typedef enum {
BtMessageTypeForgetBondedDevices,
} BtMessageType;
typedef struct {
uint8_t* start_address;
uint16_t size;
} BtKeyStorageUpdateData;
typedef union {
uint32_t pin_code;
uint8_t battery_level;
BtProfile profile;
BtKeyStorageUpdateData key_storage_data;
} BtMessageData;
typedef struct {
@ -46,6 +58,7 @@ struct Bt {
uint16_t bt_keys_size;
uint16_t max_packet_size;
BtSettings bt_settings;
BtKeysStorage* keys_storage;
BtStatus status;
BtProfile profile;
FuriMessageQueue* message_queue;

View File

@ -1,49 +1,24 @@
#include "bt_keys_storage.h"
#include <furi.h>
#include <furi_hal_bt.h>
#include <lib/toolbox/saved_struct.h>
#include <storage/storage.h>
#define BT_KEYS_STORAGE_PATH INT_PATH(BT_KEYS_STORAGE_FILE_NAME)
#define BT_KEYS_STORAGE_VERSION (0)
#define BT_KEYS_STORAGE_MAGIC (0x18)
bool bt_keys_storage_load(Bt* bt) {
furi_assert(bt);
bool file_loaded = false;
#define TAG "BtKeyStorage"
furi_hal_bt_get_key_storage_buff(&bt->bt_keys_addr_start, &bt->bt_keys_size);
furi_hal_bt_nvm_sram_sem_acquire();
file_loaded = saved_struct_load(
BT_KEYS_STORAGE_PATH,
bt->bt_keys_addr_start,
bt->bt_keys_size,
BT_KEYS_STORAGE_MAGIC,
BT_KEYS_STORAGE_VERSION);
furi_hal_bt_nvm_sram_sem_release();
struct BtKeysStorage {
uint8_t* nvm_sram_buff;
uint16_t nvm_sram_buff_size;
FuriString* file_path;
};
return file_loaded;
}
bool bt_keys_storage_delete(BtKeysStorage* instance) {
furi_assert(instance);
bool bt_keys_storage_save(Bt* bt) {
furi_assert(bt);
furi_assert(bt->bt_keys_addr_start);
bool file_saved = false;
furi_hal_bt_nvm_sram_sem_acquire();
file_saved = saved_struct_save(
BT_KEYS_STORAGE_PATH,
bt->bt_keys_addr_start,
bt->bt_keys_size,
BT_KEYS_STORAGE_MAGIC,
BT_KEYS_STORAGE_VERSION);
furi_hal_bt_nvm_sram_sem_release();
return file_saved;
}
bool bt_keys_storage_delete(Bt* bt) {
furi_assert(bt);
bool delete_succeed = false;
bool bt_is_active = furi_hal_bt_is_active();
@ -55,3 +30,117 @@ bool bt_keys_storage_delete(Bt* bt) {
return delete_succeed;
}
BtKeysStorage* bt_keys_storage_alloc(const char* keys_storage_path) {
furi_assert(keys_storage_path);
BtKeysStorage* instance = malloc(sizeof(BtKeysStorage));
// Set default nvm ram parameters
furi_hal_bt_get_key_storage_buff(&instance->nvm_sram_buff, &instance->nvm_sram_buff_size);
// Set key storage file
instance->file_path = furi_string_alloc();
furi_string_set_str(instance->file_path, keys_storage_path);
return instance;
}
void bt_keys_storage_free(BtKeysStorage* instance) {
furi_assert(instance);
furi_string_free(instance->file_path);
free(instance);
}
void bt_keys_storage_set_file_path(BtKeysStorage* instance, const char* path) {
furi_assert(instance);
furi_assert(path);
furi_string_set_str(instance->file_path, path);
}
void bt_keys_storage_set_ram_params(BtKeysStorage* instance, uint8_t* buff, uint16_t size) {
furi_assert(instance);
furi_assert(buff);
instance->nvm_sram_buff = buff;
instance->nvm_sram_buff_size = size;
}
bool bt_keys_storage_load(BtKeysStorage* instance) {
furi_assert(instance);
bool loaded = false;
do {
// Get payload size
size_t payload_size = 0;
if(!saved_struct_get_payload_size(
furi_string_get_cstr(instance->file_path),
BT_KEYS_STORAGE_MAGIC,
BT_KEYS_STORAGE_VERSION,
&payload_size)) {
FURI_LOG_E(TAG, "Failed to read payload size");
break;
}
if(payload_size > instance->nvm_sram_buff_size) {
FURI_LOG_E(TAG, "Saved data doesn't fit ram buffer");
break;
}
// Load saved data to ram
furi_hal_bt_nvm_sram_sem_acquire();
bool data_loaded = saved_struct_load(
furi_string_get_cstr(instance->file_path),
instance->nvm_sram_buff,
payload_size,
BT_KEYS_STORAGE_MAGIC,
BT_KEYS_STORAGE_VERSION);
furi_hal_bt_nvm_sram_sem_release();
if(!data_loaded) {
FURI_LOG_E(TAG, "Failed to load struct");
break;
}
loaded = true;
} while(false);
return loaded;
}
bool bt_keys_storage_update(BtKeysStorage* instance, uint8_t* start_addr, uint32_t size) {
furi_assert(instance);
furi_assert(start_addr);
bool updated = false;
FURI_LOG_I(
TAG,
"Base address: %p. Start update address: %p. Size changed: %ld",
(void*)instance->nvm_sram_buff,
start_addr,
size);
do {
size_t new_size = start_addr - instance->nvm_sram_buff + size;
if(new_size > instance->nvm_sram_buff_size) {
FURI_LOG_E(TAG, "NVM RAM buffer overflow");
break;
}
furi_hal_bt_nvm_sram_sem_acquire();
bool data_updated = saved_struct_save(
furi_string_get_cstr(instance->file_path),
instance->nvm_sram_buff,
new_size,
BT_KEYS_STORAGE_MAGIC,
BT_KEYS_STORAGE_VERSION);
furi_hal_bt_nvm_sram_sem_release();
if(!data_updated) {
FURI_LOG_E(TAG, "Failed to update key storage");
break;
}
updated = true;
} while(false);
return updated;
}

View File

@ -1,10 +1,20 @@
#pragma once
#include "bt_i.h"
#include "bt_keys_filename.h"
#include <stdint.h>
#include <stdbool.h>
bool bt_keys_storage_load(Bt* bt);
typedef struct BtKeysStorage BtKeysStorage;
bool bt_keys_storage_save(Bt* bt);
BtKeysStorage* bt_keys_storage_alloc(const char* keys_storage_path);
bool bt_keys_storage_delete(Bt* bt);
void bt_keys_storage_free(BtKeysStorage* instance);
void bt_keys_storage_set_file_path(BtKeysStorage* instance, const char* path);
void bt_keys_storage_set_ram_params(BtKeysStorage* instance, uint8_t* buff, uint16_t size);
bool bt_keys_storage_load(BtKeysStorage* instance);
bool bt_keys_storage_update(BtKeysStorage* instance, uint8_t* start_addr, uint32_t size);
bool bt_keys_storage_delete(BtKeysStorage* instance);

View File

@ -50,6 +50,10 @@ void view_holder_free(ViewHolder* view_holder) {
void view_holder_set_view(ViewHolder* view_holder, View* view) {
furi_assert(view_holder);
if(view_holder->view) {
if(view_holder->view->exit_callback) {
view_holder->view->exit_callback(view_holder->view->context);
}
view_set_update_callback(view_holder->view, NULL);
view_set_update_callback_context(view_holder->view, NULL);
}
@ -59,6 +63,10 @@ void view_holder_set_view(ViewHolder* view_holder, View* view) {
if(view_holder->view) {
view_set_update_callback(view_holder->view, view_holder_update);
view_set_update_callback_context(view_holder->view, view_holder);
if(view_holder->view->enter_callback) {
view_holder->view->enter_callback(view_holder->view->context);
}
}
}

View File

@ -547,6 +547,52 @@ void elements_string_fit_width(Canvas* canvas, FuriString* string, uint8_t width
}
}
void elements_scrollable_text_line(
Canvas* canvas,
uint8_t x,
uint8_t y,
uint8_t width,
FuriString* string,
size_t scroll,
bool ellipsis) {
FuriString* line = furi_string_alloc_set(string);
size_t len_px = canvas_string_width(canvas, furi_string_get_cstr(line));
if(len_px > width) {
if(ellipsis) {
width -= canvas_string_width(canvas, "...");
}
// Calculate scroll size
size_t scroll_size = furi_string_size(string);
size_t right_width = 0;
for(size_t i = scroll_size; i > 0; i--) {
right_width += canvas_glyph_width(canvas, furi_string_get_char(line, i));
if(right_width > width) break;
scroll_size--;
if(!scroll_size) break;
}
// Ensure that we have something to scroll
if(scroll_size) {
scroll_size += 3;
scroll = scroll % scroll_size;
furi_string_right(line, scroll);
}
do {
furi_string_left(line, furi_string_size(line) - 1);
len_px = canvas_string_width(canvas, furi_string_get_cstr(line));
} while(len_px > width);
if(ellipsis) {
furi_string_cat(line, "...");
}
}
canvas_draw_str(canvas, x, y, furi_string_get_cstr(line));
furi_string_free(line);
}
void elements_text_box(
Canvas* canvas,
uint8_t x,

View File

@ -192,6 +192,25 @@ void elements_bubble_str(
*/
void elements_string_fit_width(Canvas* canvas, FuriString* string, uint8_t width);
/** Draw scrollable text line
*
* @param canvas The canvas
* @param[in] x X coordinate
* @param[in] y Y coordinate
* @param[in] width The width
* @param string The string
* @param[in] scroll The scroll counter: 0 - no scroll, any other number - scroll. Just count up, everything else will be calculated on the inside.
* @param[in] ellipsis The ellipsis flag: true to add ellipse
*/
void elements_scrollable_text_line(
Canvas* canvas,
uint8_t x,
uint8_t y,
uint8_t width,
FuriString* string,
size_t scroll,
bool ellipsis);
/** Draw text box element
*
* @param canvas Canvas instance

View File

@ -19,6 +19,9 @@
#define CUSTOM_ICON_MAX_SIZE 32
#define SCROLL_INTERVAL (333)
#define SCROLL_DELAY (2)
typedef enum {
BrowserItemTypeLoading,
BrowserItemTypeBack,
@ -95,6 +98,7 @@ struct FileBrowser {
void* item_context;
FuriString* result_path;
FuriTimer* scroll_timer;
};
typedef struct {
@ -110,6 +114,7 @@ typedef struct {
const Icon* file_icon;
bool hide_ext;
size_t scroll_counter;
} FileBrowserModel;
static const Icon* BrowserItemIcons[] = {
@ -129,6 +134,27 @@ static void
browser_list_item_cb(void* context, FuriString* item_path, bool is_folder, bool is_last);
static void browser_long_load_cb(void* context);
static void file_browser_scroll_timer_callback(void* context) {
furi_assert(context);
FileBrowser* browser = context;
with_view_model(
browser->view, FileBrowserModel * model, { model->scroll_counter++; }, true);
}
static void file_browser_view_enter_callback(void* context) {
furi_assert(context);
FileBrowser* browser = context;
with_view_model(
browser->view, FileBrowserModel * model, { model->scroll_counter = 0; }, true);
furi_timer_start(browser->scroll_timer, SCROLL_INTERVAL);
}
static void file_browser_view_exit_callback(void* context) {
furi_assert(context);
FileBrowser* browser = context;
furi_timer_stop(browser->scroll_timer);
}
FileBrowser* file_browser_alloc(FuriString* result_path) {
furi_assert(result_path);
FileBrowser* browser = malloc(sizeof(FileBrowser));
@ -137,6 +163,11 @@ FileBrowser* file_browser_alloc(FuriString* result_path) {
view_set_context(browser->view, browser);
view_set_draw_callback(browser->view, file_browser_view_draw_callback);
view_set_input_callback(browser->view, file_browser_view_input_callback);
view_set_enter_callback(browser->view, file_browser_view_enter_callback);
view_set_exit_callback(browser->view, file_browser_view_exit_callback);
browser->scroll_timer =
furi_timer_alloc(file_browser_scroll_timer_callback, FuriTimerTypePeriodic, browser);
browser->result_path = result_path;
@ -149,6 +180,8 @@ FileBrowser* file_browser_alloc(FuriString* result_path) {
void file_browser_free(FileBrowser* browser) {
furi_assert(browser);
furi_timer_free(browser->scroll_timer);
with_view_model(
browser->view, FileBrowserModel * model, { items_array_clear(model->items); }, false);
@ -468,13 +501,17 @@ static void browser_draw_list(Canvas* canvas, FileBrowserModel* model) {
furi_string_set(filename, ". .");
}
elements_string_fit_width(
canvas, filename, (show_scrollbar ? MAX_LEN_PX - 6 : MAX_LEN_PX));
size_t scroll_counter = model->scroll_counter;
if(model->item_idx == idx) {
browser_draw_frame(canvas, i, show_scrollbar);
if(scroll_counter < SCROLL_DELAY) {
scroll_counter = 0;
} else {
scroll_counter -= SCROLL_DELAY;
}
} else {
canvas_set_color(canvas, ColorBlack);
scroll_counter = 0;
}
if(custom_icon_data) {
@ -487,8 +524,14 @@ static void browser_draw_list(Canvas* canvas, FileBrowserModel* model) {
canvas_draw_icon(
canvas, 2, Y_OFFSET + 1 + i * FRAME_HEIGHT, BrowserItemIcons[item_type]);
}
canvas_draw_str(
canvas, 15, Y_OFFSET + 9 + i * FRAME_HEIGHT, furi_string_get_cstr(filename));
elements_scrollable_text_line(
canvas,
15,
Y_OFFSET + 9 + i * FRAME_HEIGHT,
(show_scrollbar ? MAX_LEN_PX - 6 : MAX_LEN_PX),
filename,
scroll_counter,
(model->item_idx != idx));
}
if(show_scrollbar) {
@ -543,6 +586,7 @@ static bool file_browser_view_input_callback(InputEvent* event, void* context) {
file_browser_worker_load(
browser->worker, load_offset, ITEM_LIST_LEN_MAX);
}
model->scroll_counter = 0;
} else if(event->key == InputKeyDown) {
model->item_idx = (model->item_idx + 1) % model->item_cnt;
if(browser_is_list_load_required(model)) {
@ -554,6 +598,7 @@ static bool file_browser_view_input_callback(InputEvent* event, void* context) {
file_browser_worker_load(
browser->worker, load_offset, ITEM_LIST_LEN_MAX);
}
model->scroll_counter = 0;
}
},
true);

View File

@ -51,6 +51,9 @@ void locale_format_time(
} else {
am_pm = 1;
}
if(hours == 0) {
hours = 12;
}
}
if(show_seconds) {

View File

@ -0,0 +1,255 @@
# NFC Flipper File Formats
## NFC-A (UID) + Header
### Example
Filetype: Flipper NFC device
Version: 3
# Nfc device type can be UID, Mifare Ultralight, Mifare Classic, Bank card
Device type: UID
# UID, ATQA and SAK are common for all formats
UID: 04 85 92 8A A0 61 81
ATQA: 00 44
SAK: 00
### Description
This file format is used to store the UID, SAK and ATQA of a NFC-A device. It does not store any internal data, so it can be used for multiple different card types. Also used as a header for other formats.
Version differences:
1. Initial version, deprecated
2. LSB ATQA (e.g. 4400 instead of 0044)
3. MSB ATQA (current version)
UID can be either 4 or 7 bytes long. ATQA is 2 bytes long. SAK is 1 byte long.
## Mifare Ultralight/NTAG
### Example
Filetype: Flipper NFC device
Version: 3
# Nfc device type can be UID, Mifare Ultralight, Mifare Classic
Device type: NTAG216
# UID, ATQA and SAK are common for all formats
UID: 04 85 90 54 12 98 23
ATQA: 00 44
SAK: 00
# Mifare Ultralight specific data
Data format version: 1
Signature: 1B 84 EB 70 BD 4C BD 1B 1D E4 98 0B 18 58 BD 7C 72 85 B4 E4 7B 38 8E 96 CF 88 6B EE A3 43 AD 90
Mifare version: 00 04 04 02 01 00 13 03
Counter 0: 0
Tearing 0: 00
Counter 1: 0
Tearing 1: 00
Counter 2: 0
Tearing 2: 00
Pages total: 231
Pages read: 231
Page 0: 04 85 92 9B
Page 1: 8A A0 61 81
Page 2: CA 48 0F 00
...
Page 224: 00 00 00 00
Page 225: 00 00 00 00
Page 226: 00 00 7F BD
Page 227: 04 00 00 E2
Page 228: 00 05 00 00
Page 229: 00 00 00 00
Page 230: 00 00 00 00
Failed authentication attempts: 0
### Description
This file format is used to store the UID, SAK and ATQA of a Mifare Ultralight/NTAG device. It also stores the internal data of the card, the signature, the version, and the counters. The data is stored in pages, just like on the card itself.
The "Signature" field contains the reply of the tag to the READ_SIG command. More on that can be found here: <https://www.nxp.com/docs/en/data-sheet/MF0ULX1.pdf> (page 31)
The "Mifare version" field is not related to the file format version, but to the Mifare Ultralight version. It contains the responce of the tag to the GET_VERSION command. More on that can be found here: <https://www.nxp.com/docs/en/data-sheet/MF0ULX1.pdf> (page 21)
Other fields are the direct representation of the card's internal state, more on them can be found in the same datasheet.
Version differences:
1. Current version
## Mifare Classic
### Example
Filetype: Flipper NFC device
Version: 3
# Nfc device type can be UID, Mifare Ultralight, Mifare Classic
Device type: Mifare Classic
# UID, ATQA and SAK are common for all formats
UID: BA E2 7C 9D
ATQA: 00 02
SAK: 18
# Mifare Classic specific data
Mifare Classic type: 4K
Data format version: 2
# Mifare Classic blocks, '??' means unknown data
Block 0: BA E2 7C 9D B9 18 02 00 46 44 53 37 30 56 30 31
Block 1: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Block 2: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Block 3: FF FF FF FF FF FF FF 07 80 69 FF FF FF FF FF FF
Block 4: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Block 5: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Block 6: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Block 7: FF FF FF FF FF FF FF 07 80 69 FF FF FF FF FF FF
...
Block 238: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Block 239: FF FF FF FF FF FF FF 07 80 69 FF FF FF FF FF FF
Block 240: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Block 241: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Block 242: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Block 243: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Block 244: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Block 245: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Block 246: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Block 247: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Block 248: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Block 249: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Block 250: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Block 251: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Block 252: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Block 253: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Block 254: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Block 255: FF FF FF FF FF FF FF 07 80 69 FF FF FF FF FF FF
### Description
This file format is used to store the NFC-A and Mifare Classic specific data of a Mifare Classic card. Aside from the NFC-A data, it stores the card type (1K/4K) and the internal data of the card. The data is stored in blocks, there is no sector grouping. If the block's data is unknown, it is represented by '??'. Otherwise, the data is represented as a hex string.
Version differences:
1. Initial version, has Key A and Key B masks instead of marking unknown data with '??'.
Example:
...
Data format version: 1
# Key map is the bit mask indicating valid key in each sector
Key A map: 000000000000FFFF
Key B map: 000000000000FFFF
# Mifare Classic blocks
...
2. Current version
## Mifare DESFire
### Example
Filetype: Flipper NFC device
Version: 3
# Nfc device type can be UID, Mifare Ultralight, Mifare Classic
Device type: Mifare DESFire
# UID, ATQA and SAK are common for all formats
UID: 04 2F 19 0A CD 66 80
ATQA: 03 44
SAK: 20
# Mifare DESFire specific data
PICC Version: 04 01 01 12 00 1A 05 04 01 01 02 01 1A 05 04 2F 19 0A CD 66 80 CE ED D4 51 80 31 19
PICC Free Memory: 7520
PICC Change Key ID: 00
PICC Config Changeable: true
PICC Free Create Delete: true
PICC Free Directory List: true
PICC Key Changeable: true
PICC Max Keys: 01
PICC Key 0 Version: 00
Application Count: 1
Application IDs: 56 34 12
Application 563412 Change Key ID: 00
Application 563412 Config Changeable: true
Application 563412 Free Create Delete: true
Application 563412 Free Directory List: true
Application 563412 Key Changeable: true
Application 563412 Max Keys: 0E
Application 563412 Key 0 Version: 00
Application 563412 Key 1 Version: 00
Application 563412 Key 2 Version: 00
Application 563412 Key 3 Version: 00
Application 563412 Key 4 Version: 00
Application 563412 Key 5 Version: 00
Application 563412 Key 6 Version: 00
Application 563412 Key 7 Version: 00
Application 563412 Key 8 Version: 00
Application 563412 Key 9 Version: 00
Application 563412 Key 10 Version: 00
Application 563412 Key 11 Version: 00
Application 563412 Key 12 Version: 00
Application 563412 Key 13 Version: 00
Application 563412 File IDs: 01
Application 563412 File 1 Type: 00
Application 563412 File 1 Communication Settings: 00
Application 563412 File 1 Access Rights: EE EE
Application 563412 File 1 Size: 256
Application 563412 File 1: 13 37 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
### Description
This file format is used to store the NFC-A and Mifare DESFire specific data of a Mifare DESFire card. Aside from the NFC-A data, it stores the card type (DESFire) and the internal data of the card. The data is stored per-application, and per-file. Here, the card was written using those pm3 commands:
hf mfdes createapp --aid 123456 --fid 2345 --dfname astra
hf mfdes createfile --aid 123456 --fid 01 --isofid 0001 --size 000100
hf mfdes write --aid 123456 --fid 01 -d 1337
Version differences:
None, there are no versions yet.
## Mifare Classic Dictionary
### Example
# Key dictionary from https://github.com/ikarus23/MifareClassicTool.git
# More well known keys!
# Standard keys
FFFFFFFFFFFF
A0A1A2A3A4A5
D3F7D3F7D3F7
000000000000
# Keys from mfoc
B0B1B2B3B4B5
4D3A99C351DD
1A982C7E459A
AABBCCDDEEFF
714C5C886E97
587EE5F9350F
A0478CC39091
533CB6C723F6
8FD0A4F256E9
...
### Description
This file contains a list of Mifare Classic keys. Each key is represented as a hex string. Lines starting with '#' are ignored as comments. Blank lines are ignored as well.
## EMV resources
### Example
Filetype: Flipper EMV resources
Version: 1
# EMV currency code: currency name
0997: USN
0994: XSU
0990: CLF
0986: BRL
0985: PLN
0984: BOV
...
### Description
This file stores a list of EMV currency codes, country codes, or AIDs and their names. Each line contains a hex value and a name separated by a colon and a space.
Version differences:
1. Initial version

View File

@ -1,5 +1,5 @@
entry,status,name,type,params
Version,+,11.1,,
Version,+,11.2,,
Header,+,applications/services/bt/bt_service/bt.h,,
Header,+,applications/services/cli/cli.h,,
Header,+,applications/services/cli/cli_vcp.h,,
@ -570,6 +570,8 @@ Function,+,ble_glue_wait_for_c2_start,_Bool,int32_t
Function,-,bsearch,void*,"const void*, const void*, size_t, size_t, __compar_fn_t"
Function,+,bt_disconnect,void,Bt*
Function,+,bt_forget_bonded_devices,void,Bt*
Function,+,bt_keys_storage_set_storage_path,void,"Bt*, const char*"
Function,+,bt_keys_storage_set_default_path,void,Bt*
Function,+,bt_set_profile,_Bool,"Bt*, BtProfile"
Function,+,bt_set_status_changed_callback,void,"Bt*, BtStatusChangedCallback, void*"
Function,+,buffered_file_stream_alloc,Stream*,Storage*
@ -771,6 +773,7 @@ Function,+,elements_multiline_text,void,"Canvas*, uint8_t, uint8_t, const char*"
Function,+,elements_multiline_text_aligned,void,"Canvas*, uint8_t, uint8_t, Align, Align, const char*"
Function,+,elements_multiline_text_framed,void,"Canvas*, uint8_t, uint8_t, const char*"
Function,+,elements_progress_bar,void,"Canvas*, uint8_t, uint8_t, uint8_t, float"
Function,+,elements_scrollable_text_line,void,"Canvas*, uint8_t, uint8_t, uint8_t, FuriString*, size_t, _Bool"
Function,+,elements_scrollbar,void,"Canvas*, uint16_t, uint16_t"
Function,+,elements_scrollbar_pos,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint16_t, uint16_t"
Function,+,elements_slightly_rounded_box,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t"
@ -2341,6 +2344,7 @@ Function,+,rpc_system_app_set_data_exchange_callback,void,"RpcAppSystem*, RpcApp
Function,+,rpc_system_app_set_error_code,void,"RpcAppSystem*, uint32_t"
Function,+,rpc_system_app_set_error_text,void,"RpcAppSystem*, const char*"
Function,-,rpmatch,int,const char*
Function,+,saved_struct_get_payload_size,_Bool,"const char*, uint8_t, uint8_t, size_t*"
Function,+,saved_struct_load,_Bool,"const char*, void*, size_t, uint8_t, uint8_t"
Function,+,saved_struct_save,_Bool,"const char*, void*, size_t, uint8_t, uint8_t"
Function,-,scalbln,double,"double, long int"

1 entry status name type params
2 Version + 11.1 11.2
3 Header + applications/services/bt/bt_service/bt.h
4 Header + applications/services/cli/cli.h
5 Header + applications/services/cli/cli_vcp.h
570 Function - bsearch void* const void*, const void*, size_t, size_t, __compar_fn_t
571 Function + bt_disconnect void Bt*
572 Function + bt_forget_bonded_devices void Bt*
573 Function + bt_keys_storage_set_storage_path void Bt*, const char*
574 Function + bt_keys_storage_set_default_path void Bt*
575 Function + bt_set_profile _Bool Bt*, BtProfile
576 Function + bt_set_status_changed_callback void Bt*, BtStatusChangedCallback, void*
577 Function + buffered_file_stream_alloc Stream* Storage*
773 Function + elements_multiline_text_aligned void Canvas*, uint8_t, uint8_t, Align, Align, const char*
774 Function + elements_multiline_text_framed void Canvas*, uint8_t, uint8_t, const char*
775 Function + elements_progress_bar void Canvas*, uint8_t, uint8_t, uint8_t, float
776 Function + elements_scrollable_text_line void Canvas*, uint8_t, uint8_t, uint8_t, FuriString*, size_t, _Bool
777 Function + elements_scrollbar void Canvas*, uint16_t, uint16_t
778 Function + elements_scrollbar_pos void Canvas*, uint8_t, uint8_t, uint8_t, uint16_t, uint16_t
779 Function + elements_slightly_rounded_box void Canvas*, uint8_t, uint8_t, uint8_t, uint8_t
2344 Function + rpc_system_app_set_error_code void RpcAppSystem*, uint32_t
2345 Function + rpc_system_app_set_error_text void RpcAppSystem*, const char*
2346 Function - rpmatch int const char*
2347 Function + saved_struct_get_payload_size _Bool const char*, uint8_t, uint8_t, size_t*
2348 Function + saved_struct_load _Bool const char*, void*, size_t, uint8_t, uint8_t
2349 Function + saved_struct_save _Bool const char*, void*, size_t, uint8_t, uint8_t
2350 Function - scalbln double double, long int

View File

@ -8,6 +8,8 @@
#define TAG "SubGhzProtocolHormannHSM"
#define HORMANN_HSM_PATTERN 0xFF000000003
static const SubGhzBlockConst subghz_protocol_hormann_const = {
.te_short = 500,
.te_long = 1000,
@ -101,20 +103,13 @@ static bool subghz_protocol_encoder_hormann_get_upload(SubGhzProtocolEncoderHorm
furi_assert(instance);
size_t index = 0;
size_t size_upload = 3 + (instance->generic.data_count_bit * 2 + 2) * 20 + 1;
size_t size_upload = (instance->generic.data_count_bit * 2 + 2) * 20 + 1;
if(size_upload > instance->encoder.size_upload) {
FURI_LOG_E(TAG, "Size upload exceeds allocated encoder buffer.");
return false;
} else {
instance->encoder.size_upload = size_upload;
}
//Send header
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_hormann_const.te_short * 64);
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_hormann_const.te_short * 64);
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_hormann_const.te_short * 64);
instance->encoder.repeat = 10; //original remote does 10 repeats
for(size_t repeat = 0; repeat < 20; repeat++) {
@ -209,6 +204,10 @@ void subghz_protocol_decoder_hormann_free(void* context) {
free(instance);
}
static bool subghz_protocol_decoder_hormann_check_pattern(SubGhzProtocolDecoderHormann* instance) {
return (instance->decoder.decode_data & HORMANN_HSM_PATTERN) == HORMANN_HSM_PATTERN;
}
void subghz_protocol_decoder_hormann_reset(void* context) {
furi_assert(context);
SubGhzProtocolDecoderHormann* instance = context;
@ -221,25 +220,9 @@ void subghz_protocol_decoder_hormann_feed(void* context, bool level, uint32_t du
switch(instance->decoder.parser_step) {
case HormannDecoderStepReset:
if((level) && (DURATION_DIFF(duration, subghz_protocol_hormann_const.te_short * 64) <
subghz_protocol_hormann_const.te_delta * 64)) {
instance->decoder.parser_step = HormannDecoderStepFoundStartHeader;
}
break;
case HormannDecoderStepFoundStartHeader:
if((!level) && (DURATION_DIFF(duration, subghz_protocol_hormann_const.te_short * 64) <
subghz_protocol_hormann_const.te_delta * 64)) {
instance->decoder.parser_step = HormannDecoderStepFoundHeader;
} else {
instance->decoder.parser_step = HormannDecoderStepReset;
}
break;
case HormannDecoderStepFoundHeader:
if((level) && (DURATION_DIFF(duration, subghz_protocol_hormann_const.te_short * 24) <
subghz_protocol_hormann_const.te_delta * 24)) {
instance->decoder.parser_step = HormannDecoderStepFoundStartBit;
} else {
instance->decoder.parser_step = HormannDecoderStepReset;
}
break;
case HormannDecoderStepFoundStartBit:
@ -254,7 +237,8 @@ void subghz_protocol_decoder_hormann_feed(void* context, bool level, uint32_t du
break;
case HormannDecoderStepSaveDuration:
if(level) { //save interval
if(duration >= (subghz_protocol_hormann_const.te_short * 5)) {
if(duration >= (subghz_protocol_hormann_const.te_short * 5) &&
subghz_protocol_decoder_hormann_check_pattern(instance)) {
instance->decoder.parser_step = HormannDecoderStepFoundStartBit;
if(instance->decoder.decode_count_bit >=
subghz_protocol_hormann_const.min_count_bit_for_found) {

View File

@ -125,3 +125,54 @@ bool saved_struct_load(const char* path, void* data, size_t size, uint8_t magic,
return result;
}
bool saved_struct_get_payload_size(
const char* path,
uint8_t magic,
uint8_t version,
size_t* payload_size) {
furi_assert(path);
furi_assert(payload_size);
SavedStructHeader header;
Storage* storage = furi_record_open(RECORD_STORAGE);
File* file = storage_file_alloc(storage);
bool result = false;
do {
if(!storage_file_open(file, path, FSAM_READ, FSOM_OPEN_EXISTING)) {
FURI_LOG_E(
TAG, "Failed to read \"%s\". Error: %s", path, storage_file_get_error_desc(file));
break;
}
uint16_t bytes_count = storage_file_read(file, &header, sizeof(SavedStructHeader));
if(bytes_count != sizeof(SavedStructHeader)) {
FURI_LOG_E(TAG, "Failed to read header");
break;
}
if((header.magic != magic) || (header.version != version)) {
FURI_LOG_E(
TAG,
"Magic(%d != %d) or Version(%d != %d) mismatch of file \"%s\"",
header.magic,
magic,
header.version,
version,
path);
break;
}
uint64_t file_size = storage_file_size(file);
*payload_size = file_size - sizeof(SavedStructHeader);
result = true;
} while(false);
storage_file_close(file);
storage_file_free(file);
furi_record_close(RECORD_STORAGE);
return result;
}

View File

@ -12,6 +12,12 @@ bool saved_struct_load(const char* path, void* data, size_t size, uint8_t magic,
bool saved_struct_save(const char* path, void* data, size_t size, uint8_t magic, uint8_t version);
bool saved_struct_get_payload_size(
const char* path,
uint8_t magic,
uint8_t version,
size_t* payload_size);
#ifdef __cplusplus
}
#endif