mirror of
https://github.com/DarkFlippers/unleashed-firmware.git
synced 2024-11-27 05:42:16 +03:00
Merge remote-tracking branch 'origin/dev' into electra
This commit is contained in:
commit
bf721d7a5f
@ -17,6 +17,5 @@ ADD_SCENE(ibutton, delete_confirm, DeleteConfirm)
|
||||
ADD_SCENE(ibutton, delete_success, DeleteSuccess)
|
||||
ADD_SCENE(ibutton, retry_confirm, RetryConfirm)
|
||||
ADD_SCENE(ibutton, exit_confirm, ExitConfirm)
|
||||
ADD_SCENE(ibutton, read_exit_confirm, ReadExitConfirm)
|
||||
ADD_SCENE(ibutton, view_data, ViewData)
|
||||
ADD_SCENE(ibutton, rpc, Rpc)
|
||||
|
@ -1,59 +0,0 @@
|
||||
#include "../ibutton_i.h"
|
||||
|
||||
static void ibutton_scene_read_exit_confirm_widget_callback(
|
||||
GuiButtonType result,
|
||||
InputType type,
|
||||
void* context) {
|
||||
iButton* ibutton = context;
|
||||
if(type == InputTypeShort) {
|
||||
view_dispatcher_send_custom_event(ibutton->view_dispatcher, result);
|
||||
}
|
||||
}
|
||||
|
||||
void ibutton_scene_read_exit_confirm_on_enter(void* context) {
|
||||
iButton* ibutton = context;
|
||||
Widget* widget = ibutton->widget;
|
||||
|
||||
widget_add_button_element(
|
||||
widget,
|
||||
GuiButtonTypeLeft,
|
||||
"Exit",
|
||||
ibutton_scene_read_exit_confirm_widget_callback,
|
||||
ibutton);
|
||||
widget_add_button_element(
|
||||
widget,
|
||||
GuiButtonTypeRight,
|
||||
"Stay",
|
||||
ibutton_scene_read_exit_confirm_widget_callback,
|
||||
ibutton);
|
||||
widget_add_string_element(
|
||||
widget, 64, 19, AlignCenter, AlignBottom, FontPrimary, "Retry Reading?");
|
||||
widget_add_string_element(
|
||||
widget, 64, 31, AlignCenter, AlignBottom, FontSecondary, "All unsaved data will be lost!");
|
||||
|
||||
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget);
|
||||
}
|
||||
|
||||
bool ibutton_scene_read_exit_confirm_on_event(void* context, SceneManagerEvent event) {
|
||||
iButton* ibutton = context;
|
||||
SceneManager* scene_manager = ibutton->scene_manager;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeBack) {
|
||||
consumed = true; // Ignore Back button presses
|
||||
} else if(event.type == SceneManagerEventTypeCustom) {
|
||||
consumed = true;
|
||||
if(event.event == GuiButtonTypeLeft) {
|
||||
scene_manager_search_and_switch_to_previous_scene(scene_manager, iButtonSceneRead);
|
||||
} else if(event.event == GuiButtonTypeRight) {
|
||||
scene_manager_previous_scene(scene_manager);
|
||||
}
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void ibutton_scene_read_exit_confirm_on_exit(void* context) {
|
||||
iButton* ibutton = context;
|
||||
widget_reset(ibutton->widget);
|
||||
}
|
@ -44,7 +44,7 @@ bool ibutton_scene_read_success_on_event(void* context, SceneManagerEvent event)
|
||||
|
||||
if(event.type == SceneManagerEventTypeBack) {
|
||||
consumed = true;
|
||||
scene_manager_next_scene(scene_manager, iButtonSceneReadExitConfirm);
|
||||
scene_manager_next_scene(scene_manager, iButtonSceneExitConfirm);
|
||||
} else if(event.type == SceneManagerEventTypeCustom) {
|
||||
consumed = true;
|
||||
if(event.event == GuiButtonTypeRight) {
|
||||
|
@ -15,7 +15,7 @@ void ibutton_scene_retry_confirm_on_enter(void* context) {
|
||||
Widget* widget = ibutton->widget;
|
||||
|
||||
widget_add_button_element(
|
||||
widget, GuiButtonTypeLeft, "Exit", ibutton_scene_retry_confirm_widget_callback, ibutton);
|
||||
widget, GuiButtonTypeLeft, "Retry", ibutton_scene_retry_confirm_widget_callback, ibutton);
|
||||
widget_add_button_element(
|
||||
widget, GuiButtonTypeRight, "Stay", ibutton_scene_retry_confirm_widget_callback, ibutton);
|
||||
widget_add_string_element(
|
||||
|
@ -506,7 +506,7 @@ bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString*
|
||||
|
||||
if(data_block.valid_from_date == 0 || data_block.valid_to_date == 0) {
|
||||
furi_string_cat(result, "\e#No ticket");
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
//remaining_trips
|
||||
furi_string_cat_printf(result, "Trips: %d\n", data_block.total_trips);
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
#include "../iso14443_4a/iso14443_4a_render.h"
|
||||
|
||||
#define MF_DESFIRE_RENDER_MAX_RECORD_SIZE (256U)
|
||||
|
||||
void nfc_render_mf_desfire_info(
|
||||
const MfDesfireData* data,
|
||||
NfcProtocolFormatType format_type,
|
||||
@ -212,8 +214,6 @@ void nfc_render_mf_desfire_file_settings_data(
|
||||
uint32_t record_count = 1;
|
||||
uint32_t record_size = 0;
|
||||
|
||||
const uint32_t total_size = simple_array_get_count(data->data);
|
||||
|
||||
switch(settings->type) {
|
||||
case MfDesfireFileTypeStandard:
|
||||
case MfDesfireFileTypeBackup:
|
||||
@ -257,17 +257,14 @@ void nfc_render_mf_desfire_file_settings_data(
|
||||
return;
|
||||
}
|
||||
|
||||
for(uint32_t rec = 0; rec < record_count; rec++) {
|
||||
const uint32_t size_offset = rec * record_size;
|
||||
const uint32_t size_remaining = total_size > size_offset ? total_size - size_offset : 0;
|
||||
// Limit record size
|
||||
bool trim_data = record_size > MF_DESFIRE_RENDER_MAX_RECORD_SIZE;
|
||||
if(trim_data) {
|
||||
record_size = MF_DESFIRE_RENDER_MAX_RECORD_SIZE;
|
||||
}
|
||||
|
||||
if(size_remaining < record_size) {
|
||||
furi_string_cat_printf(
|
||||
str, "record %lu (partial %lu of %lu)\n", rec, size_remaining, record_size);
|
||||
record_size = size_remaining;
|
||||
} else {
|
||||
furi_string_cat_printf(str, "record %lu\n", rec);
|
||||
}
|
||||
for(uint32_t rec = 0; rec < record_count; rec++) {
|
||||
furi_string_cat_printf(str, "record %lu\n", rec);
|
||||
|
||||
for(uint32_t ch = 0; ch < record_size; ch += 4) {
|
||||
furi_string_cat_printf(str, "%03lx|", ch);
|
||||
@ -296,6 +293,9 @@ void nfc_render_mf_desfire_file_settings_data(
|
||||
|
||||
furi_string_push_back(str, '\n');
|
||||
}
|
||||
if(trim_data) {
|
||||
furi_string_cat_str(str, "...");
|
||||
}
|
||||
|
||||
furi_string_push_back(str, '\n');
|
||||
}
|
||||
|
121
applications/main/nfc/helpers/protocol_support/mf_plus/mf_plus.c
Normal file
121
applications/main/nfc/helpers/protocol_support/mf_plus/mf_plus.c
Normal file
@ -0,0 +1,121 @@
|
||||
#include "mf_plus.h"
|
||||
#include "mf_plus_render.h"
|
||||
|
||||
#include <nfc/protocols/mf_plus/mf_plus_poller.h>
|
||||
|
||||
#include "nfc/nfc_app_i.h"
|
||||
|
||||
#include "../nfc_protocol_support_common.h"
|
||||
#include "../nfc_protocol_support_gui_common.h"
|
||||
#include "../iso14443_4a/iso14443_4a_i.h"
|
||||
|
||||
static void nfc_scene_info_on_enter_mf_plus(NfcApp* instance) {
|
||||
const NfcDevice* device = instance->nfc_device;
|
||||
const MfPlusData* data = nfc_device_get_data(device, NfcProtocolMfPlus);
|
||||
|
||||
FuriString* temp_str = furi_string_alloc();
|
||||
nfc_append_filename_string_when_present(instance, temp_str);
|
||||
furi_string_cat_printf(
|
||||
temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull));
|
||||
furi_string_replace(temp_str, "Mifare", "MIFARE");
|
||||
nfc_render_mf_plus_info(data, NfcProtocolFormatTypeFull, temp_str);
|
||||
|
||||
widget_add_text_scroll_element(
|
||||
instance->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str));
|
||||
|
||||
furi_string_free(temp_str);
|
||||
}
|
||||
static NfcCommand nfc_scene_read_poller_callback_mf_plus(NfcGenericEvent event, void* context) {
|
||||
furi_assert(event.protocol == NfcProtocolMfPlus);
|
||||
|
||||
NfcApp* instance = context;
|
||||
const MfPlusPollerEvent* mf_plus_event = event.event_data;
|
||||
|
||||
if(mf_plus_event->type == MfPlusPollerEventTypeReadSuccess) {
|
||||
nfc_device_set_data(
|
||||
instance->nfc_device, NfcProtocolMfPlus, nfc_poller_get_data(instance->poller));
|
||||
FURI_LOG_D(
|
||||
"MFP",
|
||||
"Read success: %s",
|
||||
nfc_device_get_name(instance->nfc_device, NfcDeviceNameTypeFull));
|
||||
view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess);
|
||||
return NfcCommandStop;
|
||||
}
|
||||
|
||||
return NfcCommandContinue;
|
||||
}
|
||||
|
||||
static void nfc_scene_read_on_enter_mf_plus(NfcApp* instance) {
|
||||
nfc_poller_start(instance->poller, nfc_scene_read_poller_callback_mf_plus, instance);
|
||||
}
|
||||
|
||||
static void nfc_scene_read_success_on_enter_mf_plus(NfcApp* instance) {
|
||||
const NfcDevice* device = instance->nfc_device;
|
||||
const MfPlusData* data = nfc_device_get_data(device, NfcProtocolMfPlus);
|
||||
|
||||
FuriString* temp_str = furi_string_alloc();
|
||||
furi_string_cat_printf(
|
||||
temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull));
|
||||
furi_string_replace(temp_str, "Mifare", "MIFARE");
|
||||
nfc_render_mf_plus_info(data, NfcProtocolFormatTypeShort, temp_str);
|
||||
|
||||
widget_add_text_scroll_element(
|
||||
instance->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str));
|
||||
|
||||
furi_string_free(temp_str);
|
||||
}
|
||||
|
||||
static void nfc_scene_emulate_on_enter_mf_plus(NfcApp* instance) {
|
||||
const Iso14443_4aData* iso14443_4a_data =
|
||||
nfc_device_get_data(instance->nfc_device, NfcProtocolIso14443_4a);
|
||||
|
||||
instance->listener =
|
||||
nfc_listener_alloc(instance->nfc, NfcProtocolIso14443_4a, iso14443_4a_data);
|
||||
nfc_listener_start(
|
||||
instance->listener, nfc_scene_emulate_listener_callback_iso14443_4a, instance);
|
||||
}
|
||||
|
||||
const NfcProtocolSupportBase nfc_protocol_support_mf_plus = {
|
||||
.features = NfcProtocolFeatureMoreInfo,
|
||||
|
||||
.scene_info =
|
||||
{
|
||||
.on_enter = nfc_scene_info_on_enter_mf_plus,
|
||||
.on_event = nfc_protocol_support_common_on_event_empty,
|
||||
},
|
||||
.scene_more_info =
|
||||
{
|
||||
.on_enter = nfc_protocol_support_common_on_enter_empty,
|
||||
.on_event = nfc_protocol_support_common_on_event_empty,
|
||||
},
|
||||
.scene_read =
|
||||
{
|
||||
.on_enter = nfc_scene_read_on_enter_mf_plus,
|
||||
.on_event = nfc_protocol_support_common_on_event_empty,
|
||||
},
|
||||
.scene_read_menu =
|
||||
{
|
||||
.on_enter = nfc_protocol_support_common_on_enter_empty,
|
||||
.on_event = nfc_protocol_support_common_on_event_empty,
|
||||
},
|
||||
.scene_read_success =
|
||||
{
|
||||
.on_enter = nfc_scene_read_success_on_enter_mf_plus,
|
||||
.on_event = nfc_protocol_support_common_on_event_empty,
|
||||
},
|
||||
.scene_saved_menu =
|
||||
{
|
||||
.on_enter = nfc_protocol_support_common_on_enter_empty,
|
||||
.on_event = nfc_protocol_support_common_on_event_empty,
|
||||
},
|
||||
.scene_save_name =
|
||||
{
|
||||
.on_enter = nfc_protocol_support_common_on_enter_empty,
|
||||
.on_event = nfc_protocol_support_common_on_event_empty,
|
||||
},
|
||||
.scene_emulate =
|
||||
{
|
||||
.on_enter = nfc_scene_emulate_on_enter_mf_plus,
|
||||
.on_event = nfc_protocol_support_common_on_event_empty,
|
||||
},
|
||||
};
|
@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include "../nfc_protocol_support_base.h"
|
||||
|
||||
extern const NfcProtocolSupportBase nfc_protocol_support_mf_plus;
|
@ -0,0 +1,67 @@
|
||||
#include "mf_plus_render.h"
|
||||
|
||||
#include "../iso14443_4a/iso14443_4a_render.h"
|
||||
|
||||
void nfc_render_mf_plus_info(
|
||||
const MfPlusData* data,
|
||||
NfcProtocolFormatType format_type,
|
||||
FuriString* str) {
|
||||
nfc_render_iso14443_4a_brief(mf_plus_get_base_data(data), str);
|
||||
|
||||
if(format_type != NfcProtocolFormatTypeFull) return;
|
||||
|
||||
furi_string_cat(str, "\n\e#ISO14443-4 data");
|
||||
nfc_render_iso14443_4a_extra(mf_plus_get_base_data(data), str);
|
||||
}
|
||||
|
||||
void nfc_render_mf_plus_data(const MfPlusData* data, FuriString* str) {
|
||||
nfc_render_mf_plus_version(&data->version, str);
|
||||
}
|
||||
|
||||
void nfc_render_mf_plus_version(const MfPlusVersion* data, FuriString* str) {
|
||||
furi_string_cat_printf(
|
||||
str,
|
||||
"%02x:%02x:%02x:%02x:%02x:%02x:%02x\n",
|
||||
data->uid[0],
|
||||
data->uid[1],
|
||||
data->uid[2],
|
||||
data->uid[3],
|
||||
data->uid[4],
|
||||
data->uid[5],
|
||||
data->uid[6]);
|
||||
furi_string_cat_printf(
|
||||
str,
|
||||
"hw %02x type %02x sub %02x\n"
|
||||
" maj %02x min %02x\n"
|
||||
" size %02x proto %02x\n",
|
||||
data->hw_vendor,
|
||||
data->hw_type,
|
||||
data->hw_subtype,
|
||||
data->hw_major,
|
||||
data->hw_minor,
|
||||
data->hw_storage,
|
||||
data->hw_proto);
|
||||
furi_string_cat_printf(
|
||||
str,
|
||||
"sw %02x type %02x sub %02x\n"
|
||||
" maj %02x min %02x\n"
|
||||
" size %02x proto %02x\n",
|
||||
data->sw_vendor,
|
||||
data->sw_type,
|
||||
data->sw_subtype,
|
||||
data->sw_major,
|
||||
data->sw_minor,
|
||||
data->sw_storage,
|
||||
data->sw_proto);
|
||||
furi_string_cat_printf(
|
||||
str,
|
||||
"batch %02x:%02x:%02x:%02x:%02x\n"
|
||||
"week %d year %d\n",
|
||||
data->batch[0],
|
||||
data->batch[1],
|
||||
data->batch[2],
|
||||
data->batch[3],
|
||||
data->batch[4],
|
||||
data->prod_week,
|
||||
data->prod_year);
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include <nfc/protocols/mf_plus/mf_plus.h>
|
||||
|
||||
#include "../nfc_protocol_support_render_common.h"
|
||||
|
||||
void nfc_render_mf_plus_info(
|
||||
const MfPlusData* data,
|
||||
NfcProtocolFormatType format_type,
|
||||
FuriString* str);
|
||||
|
||||
void nfc_render_mf_plus_data(const MfPlusData* data, FuriString* str);
|
||||
|
||||
void nfc_render_mf_plus_version(const MfPlusVersion* data, FuriString* str);
|
@ -17,6 +17,7 @@
|
||||
#include "felica/felica.h"
|
||||
#include "mf_ultralight/mf_ultralight.h"
|
||||
#include "mf_classic/mf_classic.h"
|
||||
#include "mf_plus/mf_plus.h"
|
||||
#include "mf_desfire/mf_desfire.h"
|
||||
#include "emv/emv.h"
|
||||
#include "slix/slix.h"
|
||||
@ -39,6 +40,7 @@ const NfcProtocolSupportBase* nfc_protocol_support[NfcProtocolNum] = {
|
||||
[NfcProtocolFelica] = &nfc_protocol_support_felica,
|
||||
[NfcProtocolMfUltralight] = &nfc_protocol_support_mf_ultralight,
|
||||
[NfcProtocolMfClassic] = &nfc_protocol_support_mf_classic,
|
||||
[NfcProtocolMfPlus] = &nfc_protocol_support_mf_plus,
|
||||
[NfcProtocolMfDesfire] = &nfc_protocol_support_mf_desfire,
|
||||
[NfcProtocolSlix] = &nfc_protocol_support_slix,
|
||||
[NfcProtocolSt25tb] = &nfc_protocol_support_st25tb,
|
||||
|
@ -83,6 +83,20 @@ static const MfClassicKeyPair troika_4k_keys[] = {
|
||||
{.a = 0xBB52F8CCE07F, .b = 0x6B6119752C70}, //40
|
||||
};
|
||||
|
||||
static void troika_render_section_header(
|
||||
FuriString* str,
|
||||
const char* name,
|
||||
uint8_t prefix_separator_cnt,
|
||||
uint8_t suffix_separator_cnt) {
|
||||
for(uint8_t i = 0; i < prefix_separator_cnt; i++) {
|
||||
furi_string_cat_printf(str, ":");
|
||||
}
|
||||
furi_string_cat_printf(str, "[ %s ]", name);
|
||||
for(uint8_t i = 0; i < suffix_separator_cnt; i++) {
|
||||
furi_string_cat_printf(str, ":");
|
||||
}
|
||||
}
|
||||
|
||||
static bool troika_get_card_config(TroikaCardConfig* config, MfClassicType type) {
|
||||
bool success = true;
|
||||
|
||||
@ -204,18 +218,19 @@ static bool troika_parse(const NfcDevice* device, FuriString* parsed_data) {
|
||||
bool result3 = mosgortrans_parse_transport_block(&data->block[16], tat_result);
|
||||
|
||||
furi_string_cat_printf(parsed_data, "\e#Troyka card\n");
|
||||
if(result1) {
|
||||
furi_string_cat_printf(
|
||||
parsed_data, "\e#Metro\n%s\n", furi_string_get_cstr(metro_result));
|
||||
if(result1 && !furi_string_empty(metro_result)) {
|
||||
troika_render_section_header(parsed_data, "Metro", 22, 21);
|
||||
furi_string_cat_printf(parsed_data, "%s\n", furi_string_get_cstr(metro_result));
|
||||
}
|
||||
|
||||
if(result2) {
|
||||
furi_string_cat_printf(
|
||||
parsed_data, "\n\e#Ediniy\n%s\n", furi_string_get_cstr(ground_result));
|
||||
if(result2 && !furi_string_empty(ground_result)) {
|
||||
troika_render_section_header(parsed_data, "Ediny", 22, 22);
|
||||
furi_string_cat_printf(parsed_data, "%s\n", furi_string_get_cstr(ground_result));
|
||||
}
|
||||
|
||||
if(result3) {
|
||||
furi_string_cat_printf(parsed_data, "\n\e#TAT\n%s", furi_string_get_cstr(tat_result));
|
||||
if(result3 && !furi_string_empty(tat_result)) {
|
||||
troika_render_section_header(parsed_data, "TAT", 24, 23);
|
||||
furi_string_cat_printf(parsed_data, "%s\n", furi_string_get_cstr(tat_result));
|
||||
}
|
||||
|
||||
furi_string_free(tat_result);
|
||||
|
@ -299,7 +299,7 @@ Desktop* desktop_alloc(void) {
|
||||
|
||||
desktop->lock_menu = desktop_lock_menu_alloc();
|
||||
desktop->debug_view = desktop_debug_alloc();
|
||||
desktop->hw_mismatch_popup = popup_alloc();
|
||||
desktop->popup = popup_alloc();
|
||||
desktop->locked_view = desktop_view_locked_alloc();
|
||||
desktop->pin_input_view = desktop_view_pin_input_alloc();
|
||||
desktop->pin_timeout_view = desktop_view_pin_timeout_alloc();
|
||||
@ -335,9 +335,7 @@ Desktop* desktop_alloc(void) {
|
||||
view_dispatcher_add_view(
|
||||
desktop->view_dispatcher, DesktopViewIdDebug, desktop_debug_get_view(desktop->debug_view));
|
||||
view_dispatcher_add_view(
|
||||
desktop->view_dispatcher,
|
||||
DesktopViewIdHwMismatch,
|
||||
popup_get_view(desktop->hw_mismatch_popup));
|
||||
desktop->view_dispatcher, DesktopViewIdPopup, popup_get_view(desktop->popup));
|
||||
view_dispatcher_add_view(
|
||||
desktop->view_dispatcher,
|
||||
DesktopViewIdPinTimeout,
|
||||
@ -477,6 +475,17 @@ int32_t desktop_srv(void* p) {
|
||||
scene_manager_next_scene(desktop->scene_manager, DesktopSceneFault);
|
||||
}
|
||||
|
||||
uint8_t keys_total, keys_valid;
|
||||
if(!furi_hal_crypto_enclave_verify(&keys_total, &keys_valid)) {
|
||||
FURI_LOG_E(
|
||||
TAG,
|
||||
"Secure Enclave verification failed: total %hhu, valid %hhu",
|
||||
keys_total,
|
||||
keys_valid);
|
||||
|
||||
scene_manager_next_scene(desktop->scene_manager, DesktopSceneSecureEnclave);
|
||||
}
|
||||
|
||||
// Special case: autostart application is already running
|
||||
if(loader_is_locked(desktop->loader) &&
|
||||
animation_manager_is_animation_loaded(desktop->animation_manager)) {
|
||||
|
@ -28,7 +28,7 @@ typedef enum {
|
||||
DesktopViewIdLockMenu,
|
||||
DesktopViewIdLocked,
|
||||
DesktopViewIdDebug,
|
||||
DesktopViewIdHwMismatch,
|
||||
DesktopViewIdPopup,
|
||||
DesktopViewIdPinInput,
|
||||
DesktopViewIdPinTimeout,
|
||||
DesktopViewIdSlideshow,
|
||||
@ -43,7 +43,7 @@ struct Desktop {
|
||||
ViewDispatcher* view_dispatcher;
|
||||
SceneManager* scene_manager;
|
||||
|
||||
Popup* hw_mismatch_popup;
|
||||
Popup* popup;
|
||||
DesktopLockMenuView* lock_menu;
|
||||
DesktopDebugView* debug_view;
|
||||
DesktopViewLocked* locked_view;
|
||||
|
@ -7,3 +7,4 @@ ADD_SCENE(desktop, locked, Locked)
|
||||
ADD_SCENE(desktop, pin_input, PinInput)
|
||||
ADD_SCENE(desktop, pin_timeout, PinTimeout)
|
||||
ADD_SCENE(desktop, slideshow, Slideshow)
|
||||
ADD_SCENE(desktop, secure_enclave, SecureEnclave)
|
@ -12,20 +12,21 @@ void desktop_scene_fault_callback(void* context) {
|
||||
void desktop_scene_fault_on_enter(void* context) {
|
||||
Desktop* desktop = (Desktop*)context;
|
||||
|
||||
Popup* popup = desktop->hw_mismatch_popup;
|
||||
Popup* popup = desktop->popup;
|
||||
popup_set_context(popup, desktop);
|
||||
popup_set_header(
|
||||
popup,
|
||||
"Flipper crashed\n and was rebooted",
|
||||
60,
|
||||
64,
|
||||
14 + STATUS_BAR_Y_SHIFT,
|
||||
AlignCenter,
|
||||
AlignCenter);
|
||||
|
||||
char* message = (char*)furi_hal_rtc_get_fault_data();
|
||||
popup_set_text(popup, message, 60, 37 + STATUS_BAR_Y_SHIFT, AlignCenter, AlignCenter);
|
||||
popup_set_text(popup, message, 64, 37 + STATUS_BAR_Y_SHIFT, AlignCenter, AlignCenter);
|
||||
popup_set_callback(popup, desktop_scene_fault_callback);
|
||||
view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewIdHwMismatch);
|
||||
|
||||
view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewIdPopup);
|
||||
}
|
||||
|
||||
bool desktop_scene_fault_on_event(void* context, SceneManagerEvent event) {
|
||||
@ -47,6 +48,11 @@ bool desktop_scene_fault_on_event(void* context, SceneManagerEvent event) {
|
||||
}
|
||||
|
||||
void desktop_scene_fault_on_exit(void* context) {
|
||||
UNUSED(context);
|
||||
Desktop* desktop = (Desktop*)context;
|
||||
furi_assert(desktop);
|
||||
|
||||
Popup* popup = desktop->popup;
|
||||
popup_reset(popup);
|
||||
|
||||
furi_hal_rtc_set_fault_data(0);
|
||||
}
|
||||
|
@ -4,17 +4,15 @@
|
||||
#include "desktop_scene.h"
|
||||
#include "../desktop_i.h"
|
||||
|
||||
#define HW_MISMATCH_BACK_EVENT (0UL)
|
||||
|
||||
void desktop_scene_hw_mismatch_callback(void* context) {
|
||||
Desktop* desktop = (Desktop*)context;
|
||||
view_dispatcher_send_custom_event(desktop->view_dispatcher, HW_MISMATCH_BACK_EVENT);
|
||||
view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopHwMismatchExit);
|
||||
}
|
||||
|
||||
void desktop_scene_hw_mismatch_on_enter(void* context) {
|
||||
Desktop* desktop = (Desktop*)context;
|
||||
furi_assert(desktop);
|
||||
Popup* popup = desktop->hw_mismatch_popup;
|
||||
Popup* popup = desktop->popup;
|
||||
|
||||
char* text_buffer = malloc(256);
|
||||
scene_manager_set_scene_state(
|
||||
@ -28,10 +26,10 @@ void desktop_scene_hw_mismatch_on_enter(void* context) {
|
||||
version_get_target(NULL));
|
||||
popup_set_context(popup, desktop);
|
||||
popup_set_header(
|
||||
popup, "!!!! HW Mismatch !!!!", 60, 14 + STATUS_BAR_Y_SHIFT, AlignCenter, AlignCenter);
|
||||
popup_set_text(popup, text_buffer, 60, 37 + STATUS_BAR_Y_SHIFT, AlignCenter, AlignCenter);
|
||||
popup, "!!!! HW Mismatch !!!!", 64, 12 + STATUS_BAR_Y_SHIFT, AlignCenter, AlignBottom);
|
||||
popup_set_text(popup, text_buffer, 64, 33 + STATUS_BAR_Y_SHIFT, AlignCenter, AlignCenter);
|
||||
popup_set_callback(popup, desktop_scene_hw_mismatch_callback);
|
||||
view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewIdHwMismatch);
|
||||
view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewIdPopup);
|
||||
}
|
||||
|
||||
bool desktop_scene_hw_mismatch_on_event(void* context, SceneManagerEvent event) {
|
||||
@ -40,11 +38,10 @@ bool desktop_scene_hw_mismatch_on_event(void* context, SceneManagerEvent event)
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
switch(event.event) {
|
||||
case HW_MISMATCH_BACK_EVENT:
|
||||
case DesktopHwMismatchExit:
|
||||
scene_manager_previous_scene(desktop->scene_manager);
|
||||
consumed = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -55,11 +52,10 @@ bool desktop_scene_hw_mismatch_on_event(void* context, SceneManagerEvent event)
|
||||
void desktop_scene_hw_mismatch_on_exit(void* context) {
|
||||
Desktop* desktop = (Desktop*)context;
|
||||
furi_assert(desktop);
|
||||
Popup* popup = desktop->hw_mismatch_popup;
|
||||
popup_set_header(popup, NULL, 0, 0, AlignCenter, AlignBottom);
|
||||
popup_set_text(popup, NULL, 0, 0, AlignCenter, AlignTop);
|
||||
popup_set_callback(popup, NULL);
|
||||
popup_set_context(popup, NULL);
|
||||
|
||||
Popup* popup = desktop->popup;
|
||||
popup_reset(popup);
|
||||
|
||||
char* text_buffer =
|
||||
(char*)scene_manager_get_scene_state(desktop->scene_manager, DesktopSceneHwMismatch);
|
||||
free(text_buffer);
|
||||
|
@ -0,0 +1,57 @@
|
||||
#include <gui/scene_manager.h>
|
||||
#include <furi_hal.h>
|
||||
|
||||
#include "desktop_scene.h"
|
||||
#include "../desktop_i.h"
|
||||
|
||||
void desktop_scene_secure_enclave_callback(void* context) {
|
||||
Desktop* desktop = (Desktop*)context;
|
||||
view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopEnclaveExit);
|
||||
}
|
||||
|
||||
void desktop_scene_secure_enclave_on_enter(void* context) {
|
||||
Desktop* desktop = (Desktop*)context;
|
||||
furi_assert(desktop);
|
||||
|
||||
Popup* popup = desktop->popup;
|
||||
popup_set_context(popup, desktop);
|
||||
popup_set_header(
|
||||
popup, "No Factory Keys Found", 64, 12 + STATUS_BAR_Y_SHIFT, AlignCenter, AlignBottom);
|
||||
popup_set_text(
|
||||
popup,
|
||||
"Secure Enclave is damaged.\n"
|
||||
"Some apps will not work.",
|
||||
64,
|
||||
33 + STATUS_BAR_Y_SHIFT,
|
||||
AlignCenter,
|
||||
AlignCenter);
|
||||
popup_set_callback(popup, desktop_scene_secure_enclave_callback);
|
||||
|
||||
view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewIdPopup);
|
||||
}
|
||||
|
||||
bool desktop_scene_secure_enclave_on_event(void* context, SceneManagerEvent event) {
|
||||
Desktop* desktop = (Desktop*)context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
switch(event.event) {
|
||||
case DesktopEnclaveExit:
|
||||
scene_manager_previous_scene(desktop->scene_manager);
|
||||
consumed = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void desktop_scene_secure_enclave_on_exit(void* context) {
|
||||
Desktop* desktop = (Desktop*)context;
|
||||
furi_assert(desktop);
|
||||
|
||||
Popup* popup = desktop->popup;
|
||||
popup_reset(popup);
|
||||
}
|
@ -51,6 +51,10 @@ typedef enum {
|
||||
DesktopSlideshowCompleted,
|
||||
DesktopSlideshowPoweroff,
|
||||
|
||||
DesktopHwMismatchExit,
|
||||
|
||||
DesktopEnclaveExit,
|
||||
|
||||
// Global events
|
||||
DesktopGlobalBeforeAppStarted,
|
||||
DesktopGlobalAfterAppFinished,
|
||||
|
@ -117,3 +117,11 @@ App(
|
||||
requires=["js_app"],
|
||||
sources=["modules/js_widget.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="js_vgm",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="js_vgm_ep",
|
||||
requires=["js_app"],
|
||||
sources=["modules/js_vgm/*.c", "modules/js_vgm/ICM42688P/*.c"],
|
||||
)
|
||||
|
297
applications/system/js_app/modules/js_vgm/ICM42688P/ICM42688P.c
Normal file
297
applications/system/js_app/modules/js_vgm/ICM42688P/ICM42688P.c
Normal file
@ -0,0 +1,297 @@
|
||||
#include "ICM42688P_regs.h"
|
||||
#include "ICM42688P.h"
|
||||
|
||||
#define TAG "ICM42688P"
|
||||
|
||||
#define ICM42688P_TIMEOUT 100
|
||||
|
||||
struct ICM42688P {
|
||||
FuriHalSpiBusHandle* spi_bus;
|
||||
const GpioPin* irq_pin;
|
||||
float accel_scale;
|
||||
float gyro_scale;
|
||||
};
|
||||
|
||||
static const struct AccelFullScale {
|
||||
float value;
|
||||
uint8_t reg_mask;
|
||||
} accel_fs_modes[] = {
|
||||
[AccelFullScale16G] = {16.f, ICM42688_AFS_16G},
|
||||
[AccelFullScale8G] = {8.f, ICM42688_AFS_8G},
|
||||
[AccelFullScale4G] = {4.f, ICM42688_AFS_4G},
|
||||
[AccelFullScale2G] = {2.f, ICM42688_AFS_2G},
|
||||
};
|
||||
|
||||
static const struct GyroFullScale {
|
||||
float value;
|
||||
uint8_t reg_mask;
|
||||
} gyro_fs_modes[] = {
|
||||
[GyroFullScale2000DPS] = {2000.f, ICM42688_GFS_2000DPS},
|
||||
[GyroFullScale1000DPS] = {1000.f, ICM42688_GFS_1000DPS},
|
||||
[GyroFullScale500DPS] = {500.f, ICM42688_GFS_500DPS},
|
||||
[GyroFullScale250DPS] = {250.f, ICM42688_GFS_250DPS},
|
||||
[GyroFullScale125DPS] = {125.f, ICM42688_GFS_125DPS},
|
||||
[GyroFullScale62_5DPS] = {62.5f, ICM42688_GFS_62_5DPS},
|
||||
[GyroFullScale31_25DPS] = {31.25f, ICM42688_GFS_31_25DPS},
|
||||
[GyroFullScale15_625DPS] = {15.625f, ICM42688_GFS_15_625DPS},
|
||||
};
|
||||
|
||||
static bool icm42688p_write_reg(FuriHalSpiBusHandle* spi_bus, uint8_t addr, uint8_t value) {
|
||||
bool res = false;
|
||||
furi_hal_spi_acquire(spi_bus);
|
||||
do {
|
||||
uint8_t cmd_data[2] = {addr & 0x7F, value};
|
||||
if(!furi_hal_spi_bus_tx(spi_bus, cmd_data, 2, ICM42688P_TIMEOUT)) break;
|
||||
res = true;
|
||||
} while(0);
|
||||
furi_hal_spi_release(spi_bus);
|
||||
return res;
|
||||
}
|
||||
|
||||
static bool icm42688p_read_reg(FuriHalSpiBusHandle* spi_bus, uint8_t addr, uint8_t* value) {
|
||||
bool res = false;
|
||||
furi_hal_spi_acquire(spi_bus);
|
||||
do {
|
||||
uint8_t cmd_byte = addr | (1 << 7);
|
||||
if(!furi_hal_spi_bus_tx(spi_bus, &cmd_byte, 1, ICM42688P_TIMEOUT)) break;
|
||||
if(!furi_hal_spi_bus_rx(spi_bus, value, 1, ICM42688P_TIMEOUT)) break;
|
||||
res = true;
|
||||
} while(0);
|
||||
furi_hal_spi_release(spi_bus);
|
||||
return res;
|
||||
}
|
||||
|
||||
static bool
|
||||
icm42688p_read_mem(FuriHalSpiBusHandle* spi_bus, uint8_t addr, uint8_t* data, uint8_t len) {
|
||||
bool res = false;
|
||||
furi_hal_spi_acquire(spi_bus);
|
||||
do {
|
||||
uint8_t cmd_byte = addr | (1 << 7);
|
||||
if(!furi_hal_spi_bus_tx(spi_bus, &cmd_byte, 1, ICM42688P_TIMEOUT)) break;
|
||||
if(!furi_hal_spi_bus_rx(spi_bus, data, len, ICM42688P_TIMEOUT)) break;
|
||||
res = true;
|
||||
} while(0);
|
||||
furi_hal_spi_release(spi_bus);
|
||||
return res;
|
||||
}
|
||||
|
||||
bool icm42688p_accel_config(
|
||||
ICM42688P* icm42688p,
|
||||
ICM42688PAccelFullScale full_scale,
|
||||
ICM42688PDataRate rate) {
|
||||
icm42688p->accel_scale = accel_fs_modes[full_scale].value;
|
||||
uint8_t reg_value = accel_fs_modes[full_scale].reg_mask | rate;
|
||||
return icm42688p_write_reg(icm42688p->spi_bus, ICM42688_ACCEL_CONFIG0, reg_value);
|
||||
}
|
||||
|
||||
float icm42688p_accel_get_full_scale(ICM42688P* icm42688p) {
|
||||
return icm42688p->accel_scale;
|
||||
}
|
||||
|
||||
bool icm42688p_gyro_config(
|
||||
ICM42688P* icm42688p,
|
||||
ICM42688PGyroFullScale full_scale,
|
||||
ICM42688PDataRate rate) {
|
||||
icm42688p->gyro_scale = gyro_fs_modes[full_scale].value;
|
||||
uint8_t reg_value = gyro_fs_modes[full_scale].reg_mask | rate;
|
||||
return icm42688p_write_reg(icm42688p->spi_bus, ICM42688_GYRO_CONFIG0, reg_value);
|
||||
}
|
||||
|
||||
float icm42688p_gyro_get_full_scale(ICM42688P* icm42688p) {
|
||||
return icm42688p->gyro_scale;
|
||||
}
|
||||
|
||||
bool icm42688p_read_accel_raw(ICM42688P* icm42688p, ICM42688PRawData* data) {
|
||||
bool ret = icm42688p_read_mem(
|
||||
icm42688p->spi_bus, ICM42688_ACCEL_DATA_X1, (uint8_t*)data, sizeof(ICM42688PRawData));
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool icm42688p_read_gyro_raw(ICM42688P* icm42688p, ICM42688PRawData* data) {
|
||||
bool ret = icm42688p_read_mem(
|
||||
icm42688p->spi_bus, ICM42688_GYRO_DATA_X1, (uint8_t*)data, sizeof(ICM42688PRawData));
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool icm42688p_write_gyro_offset(ICM42688P* icm42688p, ICM42688PScaledData* scaled_data) {
|
||||
if((fabsf(scaled_data->x) > 64.f) || (fabsf(scaled_data->y) > 64.f) ||
|
||||
(fabsf(scaled_data->z) > 64.f)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint16_t offset_x = (uint16_t)(-(int16_t)(scaled_data->x * 32.f) * 16) >> 4;
|
||||
uint16_t offset_y = (uint16_t)(-(int16_t)(scaled_data->y * 32.f) * 16) >> 4;
|
||||
uint16_t offset_z = (uint16_t)(-(int16_t)(scaled_data->z * 32.f) * 16) >> 4;
|
||||
|
||||
uint8_t offset_regs[9];
|
||||
icm42688p_write_reg(icm42688p->spi_bus, ICM42688_REG_BANK_SEL, 4);
|
||||
icm42688p_read_mem(icm42688p->spi_bus, ICM42688_OFFSET_USER0, offset_regs, 5);
|
||||
|
||||
offset_regs[0] = offset_x & 0xFF;
|
||||
offset_regs[1] = (offset_x & 0xF00) >> 8;
|
||||
offset_regs[1] |= (offset_y & 0xF00) >> 4;
|
||||
offset_regs[2] = offset_y & 0xFF;
|
||||
offset_regs[3] = offset_z & 0xFF;
|
||||
offset_regs[4] &= 0xF0;
|
||||
offset_regs[4] |= (offset_z & 0x0F00) >> 8;
|
||||
|
||||
icm42688p_write_reg(icm42688p->spi_bus, ICM42688_OFFSET_USER0, offset_regs[0]);
|
||||
icm42688p_write_reg(icm42688p->spi_bus, ICM42688_OFFSET_USER1, offset_regs[1]);
|
||||
icm42688p_write_reg(icm42688p->spi_bus, ICM42688_OFFSET_USER2, offset_regs[2]);
|
||||
icm42688p_write_reg(icm42688p->spi_bus, ICM42688_OFFSET_USER3, offset_regs[3]);
|
||||
icm42688p_write_reg(icm42688p->spi_bus, ICM42688_OFFSET_USER4, offset_regs[4]);
|
||||
|
||||
icm42688p_write_reg(icm42688p->spi_bus, ICM42688_REG_BANK_SEL, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
void icm42688p_apply_scale(ICM42688PRawData* raw_data, float full_scale, ICM42688PScaledData* data) {
|
||||
data->x = ((float)(raw_data->x)) / 32768.f * full_scale;
|
||||
data->y = ((float)(raw_data->y)) / 32768.f * full_scale;
|
||||
data->z = ((float)(raw_data->z)) / 32768.f * full_scale;
|
||||
}
|
||||
|
||||
void icm42688p_apply_scale_fifo(
|
||||
ICM42688P* icm42688p,
|
||||
ICM42688PFifoPacket* fifo_data,
|
||||
ICM42688PScaledData* accel_data,
|
||||
ICM42688PScaledData* gyro_data) {
|
||||
float full_scale = icm42688p->accel_scale;
|
||||
accel_data->x = ((float)(fifo_data->a_x)) / 32768.f * full_scale;
|
||||
accel_data->y = ((float)(fifo_data->a_y)) / 32768.f * full_scale;
|
||||
accel_data->z = ((float)(fifo_data->a_z)) / 32768.f * full_scale;
|
||||
|
||||
full_scale = icm42688p->gyro_scale;
|
||||
gyro_data->x = ((float)(fifo_data->g_x)) / 32768.f * full_scale;
|
||||
gyro_data->y = ((float)(fifo_data->g_y)) / 32768.f * full_scale;
|
||||
gyro_data->z = ((float)(fifo_data->g_z)) / 32768.f * full_scale;
|
||||
}
|
||||
|
||||
float icm42688p_read_temp(ICM42688P* icm42688p) {
|
||||
uint8_t reg_val[2];
|
||||
|
||||
icm42688p_read_mem(icm42688p->spi_bus, ICM42688_TEMP_DATA1, reg_val, 2);
|
||||
int16_t temp_int = (reg_val[0] << 8) | reg_val[1];
|
||||
return ((float)temp_int / 132.48f) + 25.f;
|
||||
}
|
||||
|
||||
void icm42688_fifo_enable(
|
||||
ICM42688P* icm42688p,
|
||||
ICM42688PIrqCallback irq_callback,
|
||||
void* irq_context) {
|
||||
// FIFO mode: stream
|
||||
icm42688p_write_reg(icm42688p->spi_bus, ICM42688_FIFO_CONFIG, (1 << 6));
|
||||
// Little-endian data, FIFO count in records
|
||||
icm42688p_write_reg(icm42688p->spi_bus, ICM42688_INTF_CONFIG0, (1 << 7) | (1 << 6));
|
||||
// FIFO partial read, FIFO packet: gyro + accel TODO: 20bit
|
||||
icm42688p_write_reg(
|
||||
icm42688p->spi_bus, ICM42688_FIFO_CONFIG1, (1 << 6) | (1 << 5) | (1 << 1) | (1 << 0));
|
||||
// FIFO irq watermark
|
||||
uint16_t fifo_watermark = 1;
|
||||
icm42688p_write_reg(icm42688p->spi_bus, ICM42688_FIFO_CONFIG2, fifo_watermark & 0xFF);
|
||||
icm42688p_write_reg(icm42688p->spi_bus, ICM42688_FIFO_CONFIG3, fifo_watermark >> 8);
|
||||
|
||||
// IRQ1: push-pull, active high
|
||||
icm42688p_write_reg(icm42688p->spi_bus, ICM42688_INT_CONFIG, (1 << 1) | (1 << 0));
|
||||
// Clear IRQ on status read
|
||||
icm42688p_write_reg(icm42688p->spi_bus, ICM42688_INT_CONFIG0, 0);
|
||||
// IRQ pulse duration
|
||||
icm42688p_write_reg(icm42688p->spi_bus, ICM42688_INT_CONFIG1, (1 << 6) | (1 << 5));
|
||||
|
||||
uint8_t reg_data = 0;
|
||||
icm42688p_read_reg(icm42688p->spi_bus, ICM42688_INT_STATUS, ®_data);
|
||||
|
||||
furi_hal_gpio_init(icm42688p->irq_pin, GpioModeInterruptRise, GpioPullDown, GpioSpeedVeryHigh);
|
||||
furi_hal_gpio_remove_int_callback(icm42688p->irq_pin);
|
||||
furi_hal_gpio_add_int_callback(icm42688p->irq_pin, irq_callback, irq_context);
|
||||
|
||||
// IRQ1 source: FIFO threshold
|
||||
icm42688p_write_reg(icm42688p->spi_bus, ICM42688_INT_SOURCE0, (1 << 2));
|
||||
}
|
||||
|
||||
void icm42688_fifo_disable(ICM42688P* icm42688p) {
|
||||
furi_hal_gpio_remove_int_callback(icm42688p->irq_pin);
|
||||
furi_hal_gpio_init(icm42688p->irq_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
|
||||
|
||||
icm42688p_write_reg(icm42688p->spi_bus, ICM42688_INT_SOURCE0, 0);
|
||||
|
||||
// FIFO mode: bypass
|
||||
icm42688p_write_reg(icm42688p->spi_bus, ICM42688_FIFO_CONFIG, 0);
|
||||
}
|
||||
|
||||
uint16_t icm42688_fifo_get_count(ICM42688P* icm42688p) {
|
||||
uint16_t reg_val = 0;
|
||||
icm42688p_read_mem(icm42688p->spi_bus, ICM42688_FIFO_COUNTH, (uint8_t*)®_val, 2);
|
||||
return reg_val;
|
||||
}
|
||||
|
||||
bool icm42688_fifo_read(ICM42688P* icm42688p, ICM42688PFifoPacket* data) {
|
||||
icm42688p_read_mem(
|
||||
icm42688p->spi_bus, ICM42688_FIFO_DATA, (uint8_t*)data, sizeof(ICM42688PFifoPacket));
|
||||
return (data->header) & (1 << 7);
|
||||
}
|
||||
|
||||
ICM42688P* icm42688p_alloc(FuriHalSpiBusHandle* spi_bus, const GpioPin* irq_pin) {
|
||||
ICM42688P* icm42688p = malloc(sizeof(ICM42688P));
|
||||
icm42688p->spi_bus = spi_bus;
|
||||
icm42688p->irq_pin = irq_pin;
|
||||
return icm42688p;
|
||||
}
|
||||
|
||||
void icm42688p_free(ICM42688P* icm42688p) {
|
||||
free(icm42688p);
|
||||
}
|
||||
|
||||
bool icm42688p_init(ICM42688P* icm42688p) {
|
||||
furi_hal_spi_bus_handle_init(icm42688p->spi_bus);
|
||||
|
||||
// Software reset
|
||||
icm42688p_write_reg(icm42688p->spi_bus, ICM42688_REG_BANK_SEL, 0); // Set reg bank to 0
|
||||
icm42688p_write_reg(icm42688p->spi_bus, ICM42688_DEVICE_CONFIG, 0x01); // SPI Mode 0, SW reset
|
||||
furi_delay_ms(1);
|
||||
|
||||
uint8_t reg_value = 0;
|
||||
bool read_ok = icm42688p_read_reg(icm42688p->spi_bus, ICM42688_WHO_AM_I, ®_value);
|
||||
if(!read_ok) {
|
||||
FURI_LOG_E(TAG, "Chip ID read failed");
|
||||
return false;
|
||||
} else if(reg_value != ICM42688_WHOAMI) {
|
||||
FURI_LOG_E(
|
||||
TAG, "Sensor returned wrong ID 0x%02X, expected 0x%02X", reg_value, ICM42688_WHOAMI);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Disable all interrupts
|
||||
icm42688p_write_reg(icm42688p->spi_bus, ICM42688_INT_SOURCE0, 0);
|
||||
icm42688p_write_reg(icm42688p->spi_bus, ICM42688_INT_SOURCE1, 0);
|
||||
icm42688p_write_reg(icm42688p->spi_bus, ICM42688_INT_SOURCE3, 0);
|
||||
icm42688p_write_reg(icm42688p->spi_bus, ICM42688_INT_SOURCE4, 0);
|
||||
icm42688p_write_reg(icm42688p->spi_bus, ICM42688_REG_BANK_SEL, 4);
|
||||
icm42688p_write_reg(icm42688p->spi_bus, ICM42688_INT_SOURCE6, 0);
|
||||
icm42688p_write_reg(icm42688p->spi_bus, ICM42688_INT_SOURCE7, 0);
|
||||
icm42688p_write_reg(icm42688p->spi_bus, ICM42688_REG_BANK_SEL, 0);
|
||||
|
||||
// Data format: little endian
|
||||
icm42688p_write_reg(icm42688p->spi_bus, ICM42688_INTF_CONFIG0, 0);
|
||||
|
||||
// Enable all sensors
|
||||
icm42688p_write_reg(
|
||||
icm42688p->spi_bus,
|
||||
ICM42688_PWR_MGMT0,
|
||||
ICM42688_PWR_TEMP_ON | ICM42688_PWR_GYRO_MODE_LN | ICM42688_PWR_ACCEL_MODE_LN);
|
||||
furi_delay_ms(45);
|
||||
|
||||
icm42688p_accel_config(icm42688p, AccelFullScale16G, DataRate1kHz);
|
||||
icm42688p_gyro_config(icm42688p, GyroFullScale2000DPS, DataRate1kHz);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool icm42688p_deinit(ICM42688P* icm42688p) {
|
||||
// Software reset
|
||||
icm42688p_write_reg(icm42688p->spi_bus, ICM42688_REG_BANK_SEL, 0); // Set reg bank to 0
|
||||
icm42688p_write_reg(icm42688p->spi_bus, ICM42688_DEVICE_CONFIG, 0x01); // SPI Mode 0, SW reset
|
||||
|
||||
furi_hal_spi_bus_handle_deinit(icm42688p->spi_bus);
|
||||
return true;
|
||||
}
|
127
applications/system/js_app/modules/js_vgm/ICM42688P/ICM42688P.h
Normal file
127
applications/system/js_app/modules/js_vgm/ICM42688P/ICM42688P.h
Normal file
@ -0,0 +1,127 @@
|
||||
#pragma once
|
||||
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
DataRate32kHz = 0x01,
|
||||
DataRate16kHz = 0x02,
|
||||
DataRate8kHz = 0x03,
|
||||
DataRate4kHz = 0x04,
|
||||
DataRate2kHz = 0x05,
|
||||
DataRate1kHz = 0x06,
|
||||
DataRate200Hz = 0x07,
|
||||
DataRate100Hz = 0x08,
|
||||
DataRate50Hz = 0x09,
|
||||
DataRate25Hz = 0x0A,
|
||||
DataRate12_5Hz = 0x0B,
|
||||
DataRate6_25Hz = 0x0C, // Accelerometer only
|
||||
DataRate3_125Hz = 0x0D, // Accelerometer only
|
||||
DataRate1_5625Hz = 0x0E, // Accelerometer only
|
||||
DataRate500Hz = 0x0F,
|
||||
} ICM42688PDataRate;
|
||||
|
||||
typedef enum {
|
||||
AccelFullScale16G = 0,
|
||||
AccelFullScale8G,
|
||||
AccelFullScale4G,
|
||||
AccelFullScale2G,
|
||||
AccelFullScaleTotal,
|
||||
} ICM42688PAccelFullScale;
|
||||
|
||||
typedef enum {
|
||||
GyroFullScale2000DPS = 0,
|
||||
GyroFullScale1000DPS,
|
||||
GyroFullScale500DPS,
|
||||
GyroFullScale250DPS,
|
||||
GyroFullScale125DPS,
|
||||
GyroFullScale62_5DPS,
|
||||
GyroFullScale31_25DPS,
|
||||
GyroFullScale15_625DPS,
|
||||
GyroFullScaleTotal,
|
||||
} ICM42688PGyroFullScale;
|
||||
|
||||
typedef struct {
|
||||
int16_t x;
|
||||
int16_t y;
|
||||
int16_t z;
|
||||
} __attribute__((packed)) ICM42688PRawData;
|
||||
|
||||
typedef struct {
|
||||
uint8_t header;
|
||||
int16_t a_x;
|
||||
int16_t a_y;
|
||||
int16_t a_z;
|
||||
int16_t g_x;
|
||||
int16_t g_y;
|
||||
int16_t g_z;
|
||||
uint8_t temp;
|
||||
uint16_t ts;
|
||||
} __attribute__((packed)) ICM42688PFifoPacket;
|
||||
|
||||
typedef struct {
|
||||
float x;
|
||||
float y;
|
||||
float z;
|
||||
} ICM42688PScaledData;
|
||||
|
||||
typedef struct ICM42688P ICM42688P;
|
||||
|
||||
typedef void (*ICM42688PIrqCallback)(void* ctx);
|
||||
|
||||
ICM42688P* icm42688p_alloc(FuriHalSpiBusHandle* spi_bus, const GpioPin* irq_pin);
|
||||
|
||||
bool icm42688p_init(ICM42688P* icm42688p);
|
||||
|
||||
bool icm42688p_deinit(ICM42688P* icm42688p);
|
||||
|
||||
void icm42688p_free(ICM42688P* icm42688p);
|
||||
|
||||
bool icm42688p_accel_config(
|
||||
ICM42688P* icm42688p,
|
||||
ICM42688PAccelFullScale full_scale,
|
||||
ICM42688PDataRate rate);
|
||||
|
||||
float icm42688p_accel_get_full_scale(ICM42688P* icm42688p);
|
||||
|
||||
bool icm42688p_gyro_config(
|
||||
ICM42688P* icm42688p,
|
||||
ICM42688PGyroFullScale full_scale,
|
||||
ICM42688PDataRate rate);
|
||||
|
||||
float icm42688p_gyro_get_full_scale(ICM42688P* icm42688p);
|
||||
|
||||
bool icm42688p_read_accel_raw(ICM42688P* icm42688p, ICM42688PRawData* data);
|
||||
|
||||
bool icm42688p_read_gyro_raw(ICM42688P* icm42688p, ICM42688PRawData* data);
|
||||
|
||||
bool icm42688p_write_gyro_offset(ICM42688P* icm42688p, ICM42688PScaledData* scaled_data);
|
||||
|
||||
void icm42688p_apply_scale(ICM42688PRawData* raw_data, float full_scale, ICM42688PScaledData* data);
|
||||
|
||||
void icm42688p_apply_scale_fifo(
|
||||
ICM42688P* icm42688p,
|
||||
ICM42688PFifoPacket* fifo_data,
|
||||
ICM42688PScaledData* accel_data,
|
||||
ICM42688PScaledData* gyro_data);
|
||||
|
||||
float icm42688p_read_temp(ICM42688P* icm42688p);
|
||||
|
||||
void icm42688_fifo_enable(
|
||||
ICM42688P* icm42688p,
|
||||
ICM42688PIrqCallback irq_callback,
|
||||
void* irq_context);
|
||||
|
||||
void icm42688_fifo_disable(ICM42688P* icm42688p);
|
||||
|
||||
uint16_t icm42688_fifo_get_count(ICM42688P* icm42688p);
|
||||
|
||||
bool icm42688_fifo_read(ICM42688P* icm42688p, ICM42688PFifoPacket* data);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
@ -0,0 +1,176 @@
|
||||
#pragma once
|
||||
|
||||
#define ICM42688_WHOAMI 0x47
|
||||
|
||||
// Bank 0
|
||||
#define ICM42688_DEVICE_CONFIG 0x11
|
||||
#define ICM42688_DRIVE_CONFIG 0x13
|
||||
#define ICM42688_INT_CONFIG 0x14
|
||||
#define ICM42688_FIFO_CONFIG 0x16
|
||||
#define ICM42688_TEMP_DATA1 0x1D
|
||||
#define ICM42688_TEMP_DATA0 0x1E
|
||||
#define ICM42688_ACCEL_DATA_X1 0x1F
|
||||
#define ICM42688_ACCEL_DATA_X0 0x20
|
||||
#define ICM42688_ACCEL_DATA_Y1 0x21
|
||||
#define ICM42688_ACCEL_DATA_Y0 0x22
|
||||
#define ICM42688_ACCEL_DATA_Z1 0x23
|
||||
#define ICM42688_ACCEL_DATA_Z0 0x24
|
||||
#define ICM42688_GYRO_DATA_X1 0x25
|
||||
#define ICM42688_GYRO_DATA_X0 0x26
|
||||
#define ICM42688_GYRO_DATA_Y1 0x27
|
||||
#define ICM42688_GYRO_DATA_Y0 0x28
|
||||
#define ICM42688_GYRO_DATA_Z1 0x29
|
||||
#define ICM42688_GYRO_DATA_Z0 0x2A
|
||||
#define ICM42688_TMST_FSYNCH 0x2B
|
||||
#define ICM42688_TMST_FSYNCL 0x2C
|
||||
#define ICM42688_INT_STATUS 0x2D
|
||||
#define ICM42688_FIFO_COUNTH 0x2E
|
||||
#define ICM42688_FIFO_COUNTL 0x2F
|
||||
#define ICM42688_FIFO_DATA 0x30
|
||||
#define ICM42688_APEX_DATA0 0x31
|
||||
#define ICM42688_APEX_DATA1 0x32
|
||||
#define ICM42688_APEX_DATA2 0x33
|
||||
#define ICM42688_APEX_DATA3 0x34
|
||||
#define ICM42688_APEX_DATA4 0x35
|
||||
#define ICM42688_APEX_DATA5 0x36
|
||||
#define ICM42688_INT_STATUS2 0x37
|
||||
#define ICM42688_INT_STATUS3 0x38
|
||||
#define ICM42688_SIGNAL_PATH_RESET 0x4B
|
||||
#define ICM42688_INTF_CONFIG0 0x4C
|
||||
#define ICM42688_INTF_CONFIG1 0x4D
|
||||
#define ICM42688_PWR_MGMT0 0x4E
|
||||
#define ICM42688_GYRO_CONFIG0 0x4F
|
||||
#define ICM42688_ACCEL_CONFIG0 0x50
|
||||
#define ICM42688_GYRO_CONFIG1 0x51
|
||||
#define ICM42688_GYRO_ACCEL_CONFIG0 0x52
|
||||
#define ICM42688_ACCEL_CONFIG1 0x53
|
||||
#define ICM42688_TMST_CONFIG 0x54
|
||||
#define ICM42688_APEX_CONFIG0 0x56
|
||||
#define ICM42688_SMD_CONFIG 0x57
|
||||
#define ICM42688_FIFO_CONFIG1 0x5F
|
||||
#define ICM42688_FIFO_CONFIG2 0x60
|
||||
#define ICM42688_FIFO_CONFIG3 0x61
|
||||
#define ICM42688_FSYNC_CONFIG 0x62
|
||||
#define ICM42688_INT_CONFIG0 0x63
|
||||
#define ICM42688_INT_CONFIG1 0x64
|
||||
#define ICM42688_INT_SOURCE0 0x65
|
||||
#define ICM42688_INT_SOURCE1 0x66
|
||||
#define ICM42688_INT_SOURCE3 0x68
|
||||
#define ICM42688_INT_SOURCE4 0x69
|
||||
#define ICM42688_FIFO_LOST_PKT0 0x6C
|
||||
#define ICM42688_FIFO_LOST_PKT1 0x6D
|
||||
#define ICM42688_SELF_TEST_CONFIG 0x70
|
||||
#define ICM42688_WHO_AM_I 0x75
|
||||
#define ICM42688_REG_BANK_SEL 0x76
|
||||
|
||||
// Bank 1
|
||||
#define ICM42688_SENSOR_CONFIG0 0x03
|
||||
#define ICM42688_GYRO_CONFIG_STATIC2 0x0B
|
||||
#define ICM42688_GYRO_CONFIG_STATIC3 0x0C
|
||||
#define ICM42688_GYRO_CONFIG_STATIC4 0x0D
|
||||
#define ICM42688_GYRO_CONFIG_STATIC5 0x0E
|
||||
#define ICM42688_GYRO_CONFIG_STATIC6 0x0F
|
||||
#define ICM42688_GYRO_CONFIG_STATIC7 0x10
|
||||
#define ICM42688_GYRO_CONFIG_STATIC8 0x11
|
||||
#define ICM42688_GYRO_CONFIG_STATIC9 0x12
|
||||
#define ICM42688_GYRO_CONFIG_STATIC10 0x13
|
||||
#define ICM42688_XG_ST_DATA 0x5F
|
||||
#define ICM42688_YG_ST_DATA 0x60
|
||||
#define ICM42688_ZG_ST_DATA 0x61
|
||||
#define ICM42688_TMSTVAL0 0x62
|
||||
#define ICM42688_TMSTVAL1 0x63
|
||||
#define ICM42688_TMSTVAL2 0x64
|
||||
#define ICM42688_INTF_CONFIG4 0x7A
|
||||
#define ICM42688_INTF_CONFIG5 0x7B
|
||||
#define ICM42688_INTF_CONFIG6 0x7C
|
||||
|
||||
// Bank 2
|
||||
#define ICM42688_ACCEL_CONFIG_STATIC2 0x03
|
||||
#define ICM42688_ACCEL_CONFIG_STATIC3 0x04
|
||||
#define ICM42688_ACCEL_CONFIG_STATIC4 0x05
|
||||
#define ICM42688_XA_ST_DATA 0x3B
|
||||
#define ICM42688_YA_ST_DATA 0x3C
|
||||
#define ICM42688_ZA_ST_DATA 0x3D
|
||||
|
||||
// Bank 4
|
||||
#define ICM42688_APEX_CONFIG1 0x40
|
||||
#define ICM42688_APEX_CONFIG2 0x41
|
||||
#define ICM42688_APEX_CONFIG3 0x42
|
||||
#define ICM42688_APEX_CONFIG4 0x43
|
||||
#define ICM42688_APEX_CONFIG5 0x44
|
||||
#define ICM42688_APEX_CONFIG6 0x45
|
||||
#define ICM42688_APEX_CONFIG7 0x46
|
||||
#define ICM42688_APEX_CONFIG8 0x47
|
||||
#define ICM42688_APEX_CONFIG9 0x48
|
||||
#define ICM42688_ACCEL_WOM_X_THR 0x4A
|
||||
#define ICM42688_ACCEL_WOM_Y_THR 0x4B
|
||||
#define ICM42688_ACCEL_WOM_Z_THR 0x4C
|
||||
#define ICM42688_INT_SOURCE6 0x4D
|
||||
#define ICM42688_INT_SOURCE7 0x4E
|
||||
#define ICM42688_INT_SOURCE8 0x4F
|
||||
#define ICM42688_INT_SOURCE9 0x50
|
||||
#define ICM42688_INT_SOURCE10 0x51
|
||||
#define ICM42688_OFFSET_USER0 0x77
|
||||
#define ICM42688_OFFSET_USER1 0x78
|
||||
#define ICM42688_OFFSET_USER2 0x79
|
||||
#define ICM42688_OFFSET_USER3 0x7A
|
||||
#define ICM42688_OFFSET_USER4 0x7B
|
||||
#define ICM42688_OFFSET_USER5 0x7C
|
||||
#define ICM42688_OFFSET_USER6 0x7D
|
||||
#define ICM42688_OFFSET_USER7 0x7E
|
||||
#define ICM42688_OFFSET_USER8 0x7F
|
||||
|
||||
// PWR_MGMT0
|
||||
#define ICM42688_PWR_TEMP_ON (0 << 5)
|
||||
#define ICM42688_PWR_TEMP_OFF (1 << 5)
|
||||
#define ICM42688_PWR_IDLE (1 << 4)
|
||||
#define ICM42688_PWR_GYRO_MODE_OFF (0 << 2)
|
||||
#define ICM42688_PWR_GYRO_MODE_LN (3 << 2)
|
||||
#define ICM42688_PWR_ACCEL_MODE_OFF (0 << 0)
|
||||
#define ICM42688_PWR_ACCEL_MODE_LP (2 << 0)
|
||||
#define ICM42688_PWR_ACCEL_MODE_LN (3 << 0)
|
||||
|
||||
// GYRO_CONFIG0
|
||||
#define ICM42688_GFS_2000DPS (0x00 << 5)
|
||||
#define ICM42688_GFS_1000DPS (0x01 << 5)
|
||||
#define ICM42688_GFS_500DPS (0x02 << 5)
|
||||
#define ICM42688_GFS_250DPS (0x03 << 5)
|
||||
#define ICM42688_GFS_125DPS (0x04 << 5)
|
||||
#define ICM42688_GFS_62_5DPS (0x05 << 5)
|
||||
#define ICM42688_GFS_31_25DPS (0x06 << 5)
|
||||
#define ICM42688_GFS_15_625DPS (0x07 << 5)
|
||||
|
||||
#define ICM42688_GODR_32kHz 0x01
|
||||
#define ICM42688_GODR_16kHz 0x02
|
||||
#define ICM42688_GODR_8kHz 0x03
|
||||
#define ICM42688_GODR_4kHz 0x04
|
||||
#define ICM42688_GODR_2kHz 0x05
|
||||
#define ICM42688_GODR_1kHz 0x06
|
||||
#define ICM42688_GODR_200Hz 0x07
|
||||
#define ICM42688_GODR_100Hz 0x08
|
||||
#define ICM42688_GODR_50Hz 0x09
|
||||
#define ICM42688_GODR_25Hz 0x0A
|
||||
#define ICM42688_GODR_12_5Hz 0x0B
|
||||
#define ICM42688_GODR_500Hz 0x0F
|
||||
|
||||
// ACCEL_CONFIG0
|
||||
#define ICM42688_AFS_16G (0x00 << 5)
|
||||
#define ICM42688_AFS_8G (0x01 << 5)
|
||||
#define ICM42688_AFS_4G (0x02 << 5)
|
||||
#define ICM42688_AFS_2G (0x03 << 5)
|
||||
|
||||
#define ICM42688_AODR_32kHz 0x01
|
||||
#define ICM42688_AODR_16kHz 0x02
|
||||
#define ICM42688_AODR_8kHz 0x03
|
||||
#define ICM42688_AODR_4kHz 0x04
|
||||
#define ICM42688_AODR_2kHz 0x05
|
||||
#define ICM42688_AODR_1kHz 0x06
|
||||
#define ICM42688_AODR_200Hz 0x07
|
||||
#define ICM42688_AODR_100Hz 0x08
|
||||
#define ICM42688_AODR_50Hz 0x09
|
||||
#define ICM42688_AODR_25Hz 0x0A
|
||||
#define ICM42688_AODR_12_5Hz 0x0B
|
||||
#define ICM42688_AODR_6_25Hz 0x0C
|
||||
#define ICM42688_AODR_3_125Hz 0x0D
|
||||
#define ICM42688_AODR_1_5625Hz 0x0E
|
||||
#define ICM42688_AODR_500Hz 0x0F
|
3
applications/system/js_app/modules/js_vgm/README.md
Normal file
3
applications/system/js_app/modules/js_vgm/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
The files imu.c, imu.h, and ICM42688P are from https://github.com/flipperdevices/flipperzero-game-engine.git
|
||||
|
||||
Please see that file for the license.
|
328
applications/system/js_app/modules/js_vgm/imu.c
Normal file
328
applications/system/js_app/modules/js_vgm/imu.c
Normal file
@ -0,0 +1,328 @@
|
||||
#include <furi.h>
|
||||
#include "imu.h"
|
||||
#include "ICM42688P/ICM42688P.h"
|
||||
|
||||
#define TAG "IMU"
|
||||
|
||||
#define ACCEL_GYRO_RATE DataRate100Hz
|
||||
|
||||
#define FILTER_SAMPLE_FREQ 100.f
|
||||
#define FILTER_BETA 0.08f
|
||||
|
||||
#define SAMPLE_RATE_DIV 5
|
||||
|
||||
#define SENSITIVITY_K 30.f
|
||||
#define EXP_RATE 1.1f
|
||||
|
||||
#define IMU_CALI_AVG 64
|
||||
|
||||
typedef enum {
|
||||
ImuStop = (1 << 0),
|
||||
ImuNewData = (1 << 1),
|
||||
} ImuThreadFlags;
|
||||
|
||||
#define FLAGS_ALL (ImuStop | ImuNewData)
|
||||
|
||||
typedef struct {
|
||||
float q0;
|
||||
float q1;
|
||||
float q2;
|
||||
float q3;
|
||||
float roll;
|
||||
float pitch;
|
||||
float yaw;
|
||||
} ImuProcessedData;
|
||||
|
||||
typedef struct {
|
||||
FuriThread* thread;
|
||||
ICM42688P* icm42688p;
|
||||
ImuProcessedData processed_data;
|
||||
bool lefty;
|
||||
} ImuThread;
|
||||
|
||||
static void imu_madgwick_filter(
|
||||
ImuProcessedData* out,
|
||||
ICM42688PScaledData* accel,
|
||||
ICM42688PScaledData* gyro);
|
||||
|
||||
static void imu_irq_callback(void* context) {
|
||||
furi_assert(context);
|
||||
ImuThread* imu = context;
|
||||
furi_thread_flags_set(furi_thread_get_id(imu->thread), ImuNewData);
|
||||
}
|
||||
|
||||
static void imu_process_data(ImuThread* imu, ICM42688PFifoPacket* in_data) {
|
||||
ICM42688PScaledData accel_data;
|
||||
ICM42688PScaledData gyro_data;
|
||||
|
||||
// Get accel and gyro data in g and degrees/s
|
||||
icm42688p_apply_scale_fifo(imu->icm42688p, in_data, &accel_data, &gyro_data);
|
||||
|
||||
// Gyro: degrees/s to rads/s
|
||||
gyro_data.x = gyro_data.x / 180.f * M_PI;
|
||||
gyro_data.y = gyro_data.y / 180.f * M_PI;
|
||||
gyro_data.z = gyro_data.z / 180.f * M_PI;
|
||||
|
||||
// Sensor Fusion algorithm
|
||||
ImuProcessedData* out = &imu->processed_data;
|
||||
imu_madgwick_filter(out, &accel_data, &gyro_data);
|
||||
|
||||
// Quaternion to euler angles
|
||||
float roll = atan2f(
|
||||
out->q0 * out->q1 + out->q2 * out->q3, 0.5f - out->q1 * out->q1 - out->q2 * out->q2);
|
||||
float pitch = asinf(-2.0f * (out->q1 * out->q3 - out->q0 * out->q2));
|
||||
float yaw = atan2f(
|
||||
out->q1 * out->q2 + out->q0 * out->q3, 0.5f - out->q2 * out->q2 - out->q3 * out->q3);
|
||||
|
||||
// Euler angles: rads to degrees
|
||||
out->roll = roll / M_PI * 180.f;
|
||||
out->pitch = pitch / M_PI * 180.f;
|
||||
out->yaw = yaw / M_PI * 180.f;
|
||||
}
|
||||
|
||||
static void calibrate_gyro(ImuThread* imu) {
|
||||
ICM42688PRawData data;
|
||||
ICM42688PScaledData offset_scaled = {.x = 0.f, .y = 0.f, .z = 0.f};
|
||||
|
||||
icm42688p_write_gyro_offset(imu->icm42688p, &offset_scaled);
|
||||
furi_delay_ms(10);
|
||||
|
||||
int32_t avg_x = 0;
|
||||
int32_t avg_y = 0;
|
||||
int32_t avg_z = 0;
|
||||
|
||||
for(uint8_t i = 0; i < IMU_CALI_AVG; i++) {
|
||||
icm42688p_read_gyro_raw(imu->icm42688p, &data);
|
||||
avg_x += data.x;
|
||||
avg_y += data.y;
|
||||
avg_z += data.z;
|
||||
furi_delay_ms(2);
|
||||
}
|
||||
|
||||
data.x = avg_x / IMU_CALI_AVG;
|
||||
data.y = avg_y / IMU_CALI_AVG;
|
||||
data.z = avg_z / IMU_CALI_AVG;
|
||||
|
||||
icm42688p_apply_scale(&data, icm42688p_gyro_get_full_scale(imu->icm42688p), &offset_scaled);
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"Offsets: x %f, y %f, z %f",
|
||||
(double)offset_scaled.x,
|
||||
(double)offset_scaled.y,
|
||||
(double)offset_scaled.z);
|
||||
icm42688p_write_gyro_offset(imu->icm42688p, &offset_scaled);
|
||||
}
|
||||
|
||||
// static float imu_angle_diff(float a, float b) {
|
||||
// float diff = a - b;
|
||||
// if(diff > 180.f)
|
||||
// diff -= 360.f;
|
||||
// else if(diff < -180.f)
|
||||
// diff += 360.f;
|
||||
|
||||
// return diff;
|
||||
// }
|
||||
|
||||
static int32_t imu_thread(void* context) {
|
||||
furi_assert(context);
|
||||
ImuThread* imu = context;
|
||||
|
||||
// float yaw_last = 0.f;
|
||||
// float pitch_last = 0.f;
|
||||
// float diff_x = 0.f;
|
||||
// float diff_y = 0.f;
|
||||
|
||||
calibrate_gyro(imu);
|
||||
|
||||
icm42688p_accel_config(imu->icm42688p, AccelFullScale16G, ACCEL_GYRO_RATE);
|
||||
icm42688p_gyro_config(imu->icm42688p, GyroFullScale2000DPS, ACCEL_GYRO_RATE);
|
||||
|
||||
imu->processed_data.q0 = 1.f;
|
||||
imu->processed_data.q1 = 0.f;
|
||||
imu->processed_data.q2 = 0.f;
|
||||
imu->processed_data.q3 = 0.f;
|
||||
icm42688_fifo_enable(imu->icm42688p, imu_irq_callback, imu);
|
||||
|
||||
while(1) {
|
||||
uint32_t events = furi_thread_flags_wait(FLAGS_ALL, FuriFlagWaitAny, FuriWaitForever);
|
||||
|
||||
if(events & ImuStop) {
|
||||
break;
|
||||
}
|
||||
|
||||
if(events & ImuNewData) {
|
||||
uint16_t data_pending = icm42688_fifo_get_count(imu->icm42688p);
|
||||
ICM42688PFifoPacket data;
|
||||
while(data_pending--) {
|
||||
icm42688_fifo_read(imu->icm42688p, &data);
|
||||
imu_process_data(imu, &data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
icm42688_fifo_disable(imu->icm42688p);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
ImuThread* imu_start(ICM42688P* icm42688p) {
|
||||
ImuThread* imu = malloc(sizeof(ImuThread));
|
||||
imu->icm42688p = icm42688p;
|
||||
imu->thread = furi_thread_alloc_ex("ImuThread", 4096, imu_thread, imu);
|
||||
imu->lefty = furi_hal_rtc_is_flag_set(FuriHalRtcFlagHandOrient);
|
||||
furi_thread_start(imu->thread);
|
||||
|
||||
return imu;
|
||||
}
|
||||
|
||||
void imu_stop(ImuThread* imu) {
|
||||
furi_assert(imu);
|
||||
|
||||
furi_thread_flags_set(furi_thread_get_id(imu->thread), ImuStop);
|
||||
|
||||
furi_thread_join(imu->thread);
|
||||
furi_thread_free(imu->thread);
|
||||
|
||||
free(imu);
|
||||
}
|
||||
|
||||
static float imu_inv_sqrt(float number) {
|
||||
union {
|
||||
float f;
|
||||
uint32_t i;
|
||||
} conv = {.f = number};
|
||||
conv.i = 0x5F3759Df - (conv.i >> 1);
|
||||
conv.f *= 1.5f - (number * 0.5f * conv.f * conv.f);
|
||||
return conv.f;
|
||||
}
|
||||
|
||||
/* Simple madgwik filter, based on: https://github.com/arduino-libraries/MadgwickAHRS/ */
|
||||
|
||||
static void imu_madgwick_filter(
|
||||
ImuProcessedData* out,
|
||||
ICM42688PScaledData* accel,
|
||||
ICM42688PScaledData* gyro) {
|
||||
float recipNorm;
|
||||
float s0, s1, s2, s3;
|
||||
float qDot1, qDot2, qDot3, qDot4;
|
||||
float _2q0, _2q1, _2q2, _2q3, _4q0, _4q1, _4q2, _8q1, _8q2, q0q0, q1q1, q2q2, q3q3;
|
||||
|
||||
// Rate of change of quaternion from gyroscope
|
||||
qDot1 = 0.5f * (-out->q1 * gyro->x - out->q2 * gyro->y - out->q3 * gyro->z);
|
||||
qDot2 = 0.5f * (out->q0 * gyro->x + out->q2 * gyro->z - out->q3 * gyro->y);
|
||||
qDot3 = 0.5f * (out->q0 * gyro->y - out->q1 * gyro->z + out->q3 * gyro->x);
|
||||
qDot4 = 0.5f * (out->q0 * gyro->z + out->q1 * gyro->y - out->q2 * gyro->x);
|
||||
|
||||
// Compute feedback only if accelerometer measurement valid (avoids NaN in accelerometer normalisation)
|
||||
if(!((accel->x == 0.0f) && (accel->y == 0.0f) && (accel->z == 0.0f))) {
|
||||
// Normalise accelerometer measurement
|
||||
recipNorm = imu_inv_sqrt(accel->x * accel->x + accel->y * accel->y + accel->z * accel->z);
|
||||
accel->x *= recipNorm;
|
||||
accel->y *= recipNorm;
|
||||
accel->z *= recipNorm;
|
||||
|
||||
// Auxiliary variables to avoid repeated arithmetic
|
||||
_2q0 = 2.0f * out->q0;
|
||||
_2q1 = 2.0f * out->q1;
|
||||
_2q2 = 2.0f * out->q2;
|
||||
_2q3 = 2.0f * out->q3;
|
||||
_4q0 = 4.0f * out->q0;
|
||||
_4q1 = 4.0f * out->q1;
|
||||
_4q2 = 4.0f * out->q2;
|
||||
_8q1 = 8.0f * out->q1;
|
||||
_8q2 = 8.0f * out->q2;
|
||||
q0q0 = out->q0 * out->q0;
|
||||
q1q1 = out->q1 * out->q1;
|
||||
q2q2 = out->q2 * out->q2;
|
||||
q3q3 = out->q3 * out->q3;
|
||||
|
||||
// Gradient decent algorithm corrective step
|
||||
s0 = _4q0 * q2q2 + _2q2 * accel->x + _4q0 * q1q1 - _2q1 * accel->y;
|
||||
s1 = _4q1 * q3q3 - _2q3 * accel->x + 4.0f * q0q0 * out->q1 - _2q0 * accel->y - _4q1 +
|
||||
_8q1 * q1q1 + _8q1 * q2q2 + _4q1 * accel->z;
|
||||
s2 = 4.0f * q0q0 * out->q2 + _2q0 * accel->x + _4q2 * q3q3 - _2q3 * accel->y - _4q2 +
|
||||
_8q2 * q1q1 + _8q2 * q2q2 + _4q2 * accel->z;
|
||||
s3 = 4.0f * q1q1 * out->q3 - _2q1 * accel->x + 4.0f * q2q2 * out->q3 - _2q2 * accel->y;
|
||||
recipNorm =
|
||||
imu_inv_sqrt(s0 * s0 + s1 * s1 + s2 * s2 + s3 * s3); // normalise step magnitude
|
||||
s0 *= recipNorm;
|
||||
s1 *= recipNorm;
|
||||
s2 *= recipNorm;
|
||||
s3 *= recipNorm;
|
||||
|
||||
// Apply feedback step
|
||||
qDot1 -= FILTER_BETA * s0;
|
||||
qDot2 -= FILTER_BETA * s1;
|
||||
qDot3 -= FILTER_BETA * s2;
|
||||
qDot4 -= FILTER_BETA * s3;
|
||||
}
|
||||
|
||||
// Integrate rate of change of quaternion to yield quaternion
|
||||
out->q0 += qDot1 * (1.0f / FILTER_SAMPLE_FREQ);
|
||||
out->q1 += qDot2 * (1.0f / FILTER_SAMPLE_FREQ);
|
||||
out->q2 += qDot3 * (1.0f / FILTER_SAMPLE_FREQ);
|
||||
out->q3 += qDot4 * (1.0f / FILTER_SAMPLE_FREQ);
|
||||
|
||||
// Normalise quaternion
|
||||
recipNorm = imu_inv_sqrt(
|
||||
out->q0 * out->q0 + out->q1 * out->q1 + out->q2 * out->q2 + out->q3 * out->q3);
|
||||
out->q0 *= recipNorm;
|
||||
out->q1 *= recipNorm;
|
||||
out->q2 *= recipNorm;
|
||||
out->q3 *= recipNorm;
|
||||
}
|
||||
|
||||
/* IMU API */
|
||||
|
||||
struct Imu {
|
||||
FuriHalSpiBusHandle* icm42688p_device;
|
||||
ICM42688P* icm42688p;
|
||||
ImuThread* thread;
|
||||
bool present;
|
||||
};
|
||||
|
||||
Imu* imu_alloc(void) {
|
||||
Imu* imu = malloc(sizeof(Imu));
|
||||
imu->icm42688p_device = malloc(sizeof(FuriHalSpiBusHandle));
|
||||
memcpy(imu->icm42688p_device, &furi_hal_spi_bus_handle_external, sizeof(FuriHalSpiBusHandle));
|
||||
imu->icm42688p_device->cs = &gpio_ext_pc3;
|
||||
|
||||
imu->icm42688p = icm42688p_alloc(imu->icm42688p_device, &gpio_ext_pb2);
|
||||
imu->present = icm42688p_init(imu->icm42688p);
|
||||
|
||||
if(imu->present) {
|
||||
imu->thread = imu_start(imu->icm42688p);
|
||||
}
|
||||
|
||||
return imu;
|
||||
}
|
||||
|
||||
void imu_free(Imu* imu) {
|
||||
if(imu->present) {
|
||||
imu_stop(imu->thread);
|
||||
}
|
||||
icm42688p_deinit(imu->icm42688p);
|
||||
icm42688p_free(imu->icm42688p);
|
||||
free(imu->icm42688p_device);
|
||||
free(imu);
|
||||
}
|
||||
|
||||
bool imu_present(Imu* imu) {
|
||||
return imu->present;
|
||||
}
|
||||
|
||||
float imu_pitch_get(Imu* imu) {
|
||||
// we pretend that reading a float is an atomic operation
|
||||
return imu->thread->lefty ? -imu->thread->processed_data.pitch :
|
||||
imu->thread->processed_data.pitch;
|
||||
}
|
||||
|
||||
float imu_roll_get(Imu* imu) {
|
||||
// we pretend that reading a float is an atomic operation
|
||||
return imu->thread->processed_data.roll;
|
||||
}
|
||||
|
||||
float imu_yaw_get(Imu* imu) {
|
||||
// we pretend that reading a float is an atomic operation
|
||||
return imu->thread->lefty ? -imu->thread->processed_data.yaw : imu->thread->processed_data.yaw;
|
||||
}
|
15
applications/system/js_app/modules/js_vgm/imu.h
Normal file
15
applications/system/js_app/modules/js_vgm/imu.h
Normal file
@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
typedef struct Imu Imu;
|
||||
|
||||
Imu* imu_alloc(void);
|
||||
|
||||
void imu_free(Imu* imu);
|
||||
|
||||
bool imu_present(Imu* imu);
|
||||
|
||||
float imu_pitch_get(Imu* imu);
|
||||
|
||||
float imu_roll_get(Imu* imu);
|
||||
|
||||
float imu_yaw_get(Imu* imu);
|
159
applications/system/js_app/modules/js_vgm/js_vgm.c
Normal file
159
applications/system/js_app/modules/js_vgm/js_vgm.c
Normal file
@ -0,0 +1,159 @@
|
||||
#include "../../js_modules.h"
|
||||
#include <furi.h>
|
||||
#include <toolbox/path.h>
|
||||
#include "imu.h"
|
||||
|
||||
#define TAG "JsVgm"
|
||||
|
||||
typedef struct {
|
||||
Imu* imu;
|
||||
bool present;
|
||||
} JsVgmInst;
|
||||
|
||||
static void js_vgm_get_pitch(struct mjs* mjs) {
|
||||
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0);
|
||||
JsVgmInst* vgm = mjs_get_ptr(mjs, obj_inst);
|
||||
furi_assert(vgm);
|
||||
|
||||
if(vgm->present) {
|
||||
mjs_return(mjs, mjs_mk_number(mjs, imu_pitch_get(vgm->imu)));
|
||||
} else {
|
||||
FURI_LOG_T(TAG, "VGM not present.");
|
||||
mjs_return(mjs, mjs_mk_undefined());
|
||||
}
|
||||
}
|
||||
|
||||
static void js_vgm_get_roll(struct mjs* mjs) {
|
||||
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0);
|
||||
JsVgmInst* vgm = mjs_get_ptr(mjs, obj_inst);
|
||||
furi_assert(vgm);
|
||||
|
||||
if(vgm->present) {
|
||||
mjs_return(mjs, mjs_mk_number(mjs, imu_roll_get(vgm->imu)));
|
||||
} else {
|
||||
FURI_LOG_T(TAG, "VGM not present.");
|
||||
mjs_return(mjs, mjs_mk_undefined());
|
||||
}
|
||||
}
|
||||
|
||||
static void js_vgm_get_yaw(struct mjs* mjs) {
|
||||
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0);
|
||||
JsVgmInst* vgm = mjs_get_ptr(mjs, obj_inst);
|
||||
furi_assert(vgm);
|
||||
|
||||
if(vgm->present) {
|
||||
mjs_return(mjs, mjs_mk_number(mjs, imu_yaw_get(vgm->imu)));
|
||||
} else {
|
||||
FURI_LOG_T(TAG, "VGM not present.");
|
||||
mjs_return(mjs, mjs_mk_undefined());
|
||||
}
|
||||
}
|
||||
|
||||
static float distance(float current, float start, float angle) {
|
||||
// make 0 to 359.999...
|
||||
current = (current < 0) ? current + 360 : current;
|
||||
start = (start < 0) ? start + 360 : start;
|
||||
|
||||
// get max and min
|
||||
bool max_is_current = current > start;
|
||||
float max = (max_is_current) ? current : start;
|
||||
float min = (max_is_current) ? start : current;
|
||||
|
||||
// get diff, check if it's greater than 180, and adjust
|
||||
float diff = max - min;
|
||||
bool diff_gt_180 = diff > 180;
|
||||
if(diff_gt_180) {
|
||||
diff = 360 - diff;
|
||||
}
|
||||
|
||||
// if diff is less than angle return 0
|
||||
if(diff < angle) {
|
||||
FURI_LOG_T(TAG, "diff: %f, angle: %f", (double)diff, (double)angle);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// return diff with sign
|
||||
return (max_is_current ^ diff_gt_180) ? -diff : diff;
|
||||
}
|
||||
|
||||
static void js_vgm_delta_yaw(struct mjs* mjs) {
|
||||
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0);
|
||||
JsVgmInst* vgm = mjs_get_ptr(mjs, obj_inst);
|
||||
furi_assert(vgm);
|
||||
size_t num_args = mjs_nargs(mjs);
|
||||
if(num_args < 1 || num_args > 2) {
|
||||
mjs_prepend_errorf(
|
||||
mjs,
|
||||
MJS_BAD_ARGS_ERROR,
|
||||
"Invalid args. Pass (angle [, timeout]). Got %d args.",
|
||||
num_args);
|
||||
mjs_return(mjs, mjs_mk_undefined());
|
||||
return;
|
||||
}
|
||||
|
||||
if(!vgm->present) {
|
||||
FURI_LOG_T(TAG, "VGM not present.");
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
return;
|
||||
}
|
||||
|
||||
double angle = mjs_get_double(mjs, mjs_arg(mjs, 0));
|
||||
if(isnan(angle)) {
|
||||
mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "Invalid arg (angle).");
|
||||
mjs_return(mjs, mjs_mk_undefined());
|
||||
return;
|
||||
}
|
||||
uint32_t ms = (num_args == 2) ? mjs_get_int(mjs, mjs_arg(mjs, 1)) : 3000;
|
||||
uint32_t timeout = furi_get_tick() + ms;
|
||||
float initial_yaw = imu_yaw_get(vgm->imu);
|
||||
do {
|
||||
float current_yaw = imu_yaw_get(vgm->imu);
|
||||
double diff = distance(current_yaw, initial_yaw, angle);
|
||||
if(diff != 0) {
|
||||
mjs_return(mjs, mjs_mk_number(mjs, diff));
|
||||
return;
|
||||
}
|
||||
furi_delay_ms(100);
|
||||
} while(furi_get_tick() < timeout);
|
||||
|
||||
mjs_return(mjs, mjs_mk_number(mjs, 0));
|
||||
}
|
||||
|
||||
static void* js_vgm_create(struct mjs* mjs, mjs_val_t* object) {
|
||||
JsVgmInst* vgm = malloc(sizeof(JsVgmInst));
|
||||
vgm->imu = imu_alloc();
|
||||
vgm->present = imu_present(vgm->imu);
|
||||
if(!vgm->present) FURI_LOG_W(TAG, "VGM not present.");
|
||||
mjs_val_t vgm_obj = mjs_mk_object(mjs);
|
||||
mjs_set(mjs, vgm_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, vgm));
|
||||
mjs_set(mjs, vgm_obj, "getPitch", ~0, MJS_MK_FN(js_vgm_get_pitch));
|
||||
mjs_set(mjs, vgm_obj, "getRoll", ~0, MJS_MK_FN(js_vgm_get_roll));
|
||||
mjs_set(mjs, vgm_obj, "getYaw", ~0, MJS_MK_FN(js_vgm_get_yaw));
|
||||
mjs_set(mjs, vgm_obj, "deltaYaw", ~0, MJS_MK_FN(js_vgm_delta_yaw));
|
||||
*object = vgm_obj;
|
||||
return vgm;
|
||||
}
|
||||
|
||||
static void js_vgm_destroy(void* inst) {
|
||||
JsVgmInst* vgm = inst;
|
||||
furi_assert(vgm);
|
||||
imu_free(vgm->imu);
|
||||
vgm->imu = NULL;
|
||||
free(vgm);
|
||||
}
|
||||
|
||||
static const JsModuleDescriptor js_vgm_desc = {
|
||||
name: "vgm",
|
||||
create: js_vgm_create,
|
||||
destroy: js_vgm_destroy,
|
||||
};
|
||||
|
||||
static const FlipperAppPluginDescriptor plugin_descriptor = {
|
||||
.appid = PLUGIN_APP_ID,
|
||||
.ep_api_version = PLUGIN_API_VERSION,
|
||||
.entry_point = &js_vgm_desc,
|
||||
};
|
||||
|
||||
const FlipperAppPluginDescriptor* js_vgm_ep(void) {
|
||||
return &plugin_descriptor;
|
||||
}
|
@ -851,9 +851,8 @@ static void widget_remove_view(void* context) {
|
||||
ComponentArray_it(it, model->component);
|
||||
while(!ComponentArray_end_p(it)) {
|
||||
WidgetComponent* component = *ComponentArray_ref(it);
|
||||
if(component->free) {
|
||||
if(component && component->free) {
|
||||
component->free(component);
|
||||
component->free = NULL;
|
||||
}
|
||||
ComponentArray_next(it);
|
||||
}
|
||||
|
@ -27,6 +27,7 @@ static bool dallas_ds1420_write_blank(OneWireHost*, iButtonProtocolData*);
|
||||
static void dallas_ds1420_emulate(OneWireSlave*, iButtonProtocolData*);
|
||||
static bool dallas_ds1420_load(FlipperFormat*, uint32_t, iButtonProtocolData*);
|
||||
static bool dallas_ds1420_save(FlipperFormat*, const iButtonProtocolData*);
|
||||
static void dallas_ds1420_render_uid(FuriString*, const iButtonProtocolData*);
|
||||
static void dallas_ds1420_render_brief_data(FuriString*, const iButtonProtocolData*);
|
||||
static void dallas_ds1420_render_error(FuriString*, const iButtonProtocolData*);
|
||||
static bool dallas_ds1420_is_data_valid(const iButtonProtocolData*);
|
||||
@ -46,6 +47,7 @@ const iButtonProtocolDallasBase ibutton_protocol_ds1420 = {
|
||||
.emulate = dallas_ds1420_emulate,
|
||||
.save = dallas_ds1420_save,
|
||||
.load = dallas_ds1420_load,
|
||||
.render_uid = dallas_ds1420_render_uid,
|
||||
.render_data = NULL, /* No data to render */
|
||||
.render_brief_data = dallas_ds1420_render_brief_data,
|
||||
.render_error = dallas_ds1420_render_error,
|
||||
@ -117,12 +119,20 @@ bool dallas_ds1420_load(
|
||||
return dallas_common_load_rom_data(ff, format_version, &data->rom_data);
|
||||
}
|
||||
|
||||
void dallas_ds1420_render_uid(FuriString* result, const iButtonProtocolData* protocol_data) {
|
||||
const DS1420ProtocolData* data = protocol_data;
|
||||
|
||||
dallas_common_render_uid(result, &data->rom_data);
|
||||
}
|
||||
|
||||
void dallas_ds1420_render_brief_data(FuriString* result, const iButtonProtocolData* protocol_data) {
|
||||
const DS1420ProtocolData* data = protocol_data;
|
||||
|
||||
furi_string_cat_printf(result, "ID: ");
|
||||
for(size_t i = 0; i < sizeof(DallasCommonRomData); ++i) {
|
||||
furi_string_cat_printf(result, "%02X ", data->rom_data.bytes[i]);
|
||||
}
|
||||
furi_string_cat_printf(result, "\nFamily Code: %02X\n", data->rom_data.bytes[0]);
|
||||
}
|
||||
|
||||
void dallas_ds1420_render_error(FuriString* result, const iButtonProtocolData* protocol_data) {
|
||||
|
@ -218,19 +218,15 @@ void dallas_ds1971_render_uid(FuriString* result, const iButtonProtocolData* pro
|
||||
|
||||
void dallas_ds1971_render_data(FuriString* result, const iButtonProtocolData* protocol_data) {
|
||||
const DS1971ProtocolData* data = protocol_data;
|
||||
FuriString* data_string = furi_string_alloc();
|
||||
|
||||
furi_string_cat_printf(result, "\e#Memory Data\n--------------------\n");
|
||||
|
||||
pretty_format_bytes_hex_canonical(
|
||||
data_string,
|
||||
result,
|
||||
DS1971_DATA_BYTE_COUNT,
|
||||
PRETTY_FORMAT_FONT_MONOSPACE,
|
||||
data->eeprom_data,
|
||||
DS1971_EEPROM_DATA_SIZE);
|
||||
|
||||
furi_string_cat_printf(result, "\e#Memory Data\n--------------------\n");
|
||||
furi_string_cat_printf(result, "%s", furi_string_get_cstr(data_string));
|
||||
|
||||
furi_string_free(data_string);
|
||||
}
|
||||
|
||||
void dallas_ds1971_render_brief_data(FuriString* result, const iButtonProtocolData* protocol_data) {
|
||||
|
@ -191,19 +191,15 @@ void dallas_ds1992_render_uid(FuriString* result, const iButtonProtocolData* pro
|
||||
|
||||
void dallas_ds1992_render_data(FuriString* result, const iButtonProtocolData* protocol_data) {
|
||||
const DS1992ProtocolData* data = protocol_data;
|
||||
FuriString* data_string = furi_string_alloc();
|
||||
|
||||
furi_string_cat_printf(result, "\e#Memory Data\n--------------------\n");
|
||||
|
||||
pretty_format_bytes_hex_canonical(
|
||||
data_string,
|
||||
result,
|
||||
DS1992_DATA_BYTE_COUNT,
|
||||
PRETTY_FORMAT_FONT_MONOSPACE,
|
||||
data->sram_data,
|
||||
DS1992_SRAM_DATA_SIZE);
|
||||
|
||||
furi_string_cat_printf(result, "\e#Memory Data\n--------------------\n");
|
||||
furi_string_cat_printf(result, "%s", furi_string_get_cstr(data_string));
|
||||
|
||||
furi_string_free(data_string);
|
||||
}
|
||||
|
||||
void dallas_ds1992_render_brief_data(FuriString* result, const iButtonProtocolData* protocol_data) {
|
||||
|
@ -217,19 +217,14 @@ void dallas_ds1996_render_uid(FuriString* result, const iButtonProtocolData* pro
|
||||
void dallas_ds1996_render_data(FuriString* result, const iButtonProtocolData* protocol_data) {
|
||||
const DS1996ProtocolData* data = protocol_data;
|
||||
|
||||
FuriString* data_string = furi_string_alloc();
|
||||
furi_string_cat_printf(result, "\e#Memory Data\n--------------------\n");
|
||||
|
||||
pretty_format_bytes_hex_canonical(
|
||||
data_string,
|
||||
result,
|
||||
DS1996_DATA_BYTE_COUNT,
|
||||
PRETTY_FORMAT_FONT_MONOSPACE,
|
||||
data->sram_data,
|
||||
DS1996_SRAM_DATA_SIZE);
|
||||
|
||||
furi_string_cat_printf(result, "\e#Memory Data\n--------------------\n");
|
||||
furi_string_cat_printf(result, "%s", furi_string_get_cstr(data_string));
|
||||
|
||||
furi_string_free(data_string);
|
||||
}
|
||||
|
||||
void dallas_ds1996_render_brief_data(FuriString* result, const iButtonProtocolData* protocol_data) {
|
||||
|
@ -325,7 +325,7 @@ static LevelDuration protocol_cyfral_encoder_yield(ProtocolCyfral* proto) {
|
||||
return result;
|
||||
}
|
||||
|
||||
static void protocol_cyfral_render_uid(FuriString* result, ProtocolCyfral* proto) {
|
||||
static void protocol_cyfral_render_uid(ProtocolCyfral* proto, FuriString* result) {
|
||||
furi_string_cat_printf(result, "ID: ");
|
||||
for(size_t i = 0; i < CYFRAL_DATA_SIZE; ++i) {
|
||||
furi_string_cat_printf(result, "%02X ", ((uint8_t*)&proto->data)[i]);
|
||||
@ -333,10 +333,7 @@ static void protocol_cyfral_render_uid(FuriString* result, ProtocolCyfral* proto
|
||||
}
|
||||
|
||||
static void protocol_cyfral_render_brief_data(ProtocolCyfral* proto, FuriString* result) {
|
||||
furi_string_cat_printf(result, "ID: ");
|
||||
for(size_t i = 0; i < CYFRAL_DATA_SIZE; ++i) {
|
||||
furi_string_cat_printf(result, "%02X ", ((uint8_t*)&proto->data)[i]);
|
||||
}
|
||||
protocol_cyfral_render_uid(proto, result);
|
||||
}
|
||||
|
||||
const ProtocolBase ibutton_protocol_misc_cyfral = {
|
||||
|
@ -200,6 +200,16 @@ static bool ibutton_protocol_group_misc_load(
|
||||
}
|
||||
}
|
||||
|
||||
static void ibutton_protocol_group_misc_render_uid(
|
||||
iButtonProtocolGroupMisc* group,
|
||||
const iButtonProtocolData* data,
|
||||
iButtonProtocolLocalId id,
|
||||
FuriString* result) {
|
||||
const size_t data_size = protocol_dict_get_data_size(group->dict, id);
|
||||
protocol_dict_set_data(group->dict, id, data, data_size);
|
||||
protocol_dict_render_uid(group->dict, result, id);
|
||||
}
|
||||
|
||||
static void ibutton_protocol_group_misc_render_data(
|
||||
iButtonProtocolGroupMisc* group,
|
||||
const iButtonProtocolData* data,
|
||||
@ -283,6 +293,7 @@ const iButtonProtocolGroupBase ibutton_protocol_group_misc = {
|
||||
.save = (iButtonProtocolGroupSaveFunc)ibutton_protocol_group_misc_save,
|
||||
.load = (iButtonProtocolGroupLoadFunc)ibutton_protocol_group_misc_load,
|
||||
|
||||
.render_uid = (iButtonProtocolGroupRenderFunc)ibutton_protocol_group_misc_render_uid,
|
||||
.render_data = (iButtonProtocolGroupRenderFunc)ibutton_protocol_group_misc_render_data,
|
||||
.render_brief_data =
|
||||
(iButtonProtocolGroupRenderFunc)ibutton_protocol_group_misc_render_brief_data,
|
||||
|
@ -21,6 +21,7 @@ env.Append(
|
||||
File("protocols/iso14443_4b/iso14443_4b.h"),
|
||||
File("protocols/mf_ultralight/mf_ultralight.h"),
|
||||
File("protocols/mf_classic/mf_classic.h"),
|
||||
File("protocols/mf_plus/mf_plus.h"),
|
||||
File("protocols/mf_desfire/mf_desfire.h"),
|
||||
File("protocols/emv/emv.h"),
|
||||
File("protocols/slix/slix.h"),
|
||||
@ -33,6 +34,7 @@ env.Append(
|
||||
File("protocols/iso14443_4b/iso14443_4b_poller.h"),
|
||||
File("protocols/mf_ultralight/mf_ultralight_poller.h"),
|
||||
File("protocols/mf_classic/mf_classic_poller.h"),
|
||||
File("protocols/mf_plus/mf_plus_poller.h"),
|
||||
File("protocols/mf_desfire/mf_desfire_poller.h"),
|
||||
File("protocols/emv/emv_poller.h"),
|
||||
File("protocols/st25tb/st25tb_poller.h"),
|
||||
|
@ -66,6 +66,11 @@ bool iso14443_4_layer_decode_block(
|
||||
|
||||
bool ret = false;
|
||||
|
||||
// TODO: Fix properly! this is a very big kostyl na velosipede
|
||||
// (bit_buffer_copy_right are called to copy bigger buffer into smaller buffer causing crash on furi check) issue comes iso14443_4a_poller_send_block at line 109
|
||||
if(bit_buffer_get_size_bytes(output_data) < bit_buffer_get_size_bytes(output_data) - 1)
|
||||
return ret;
|
||||
|
||||
do {
|
||||
if(!bit_buffer_starts_with_byte(block_data, instance->pcb_prev)) break;
|
||||
bit_buffer_copy_right(output_data, block_data, 1);
|
||||
|
@ -75,10 +75,10 @@ MfDesfireError mf_desfire_send_chunks(
|
||||
const size_t rx_capacity_remaining =
|
||||
bit_buffer_get_capacity_bytes(rx_buffer) - bit_buffer_get_size_bytes(rx_buffer);
|
||||
|
||||
if(rx_size <= rx_capacity_remaining) {
|
||||
if(rx_size - 1 <= rx_capacity_remaining) {
|
||||
bit_buffer_append_right(rx_buffer, instance->rx_buffer, sizeof(uint8_t));
|
||||
} else {
|
||||
FURI_LOG_W(TAG, "RX buffer overflow: ignoring %zu bytes", rx_size);
|
||||
FURI_LOG_W(TAG, "RX buffer overflow: ignoring %zu bytes", rx_size - 1);
|
||||
}
|
||||
}
|
||||
} while(false);
|
||||
@ -327,36 +327,63 @@ MfDesfireError mf_desfire_poller_read_file_settings_multi(
|
||||
return error;
|
||||
}
|
||||
|
||||
MfDesfireError mf_desfire_poller_read_file_data(
|
||||
static MfDesfireError mf_desfire_poller_read_file(
|
||||
MfDesfirePoller* instance,
|
||||
MfDesfireFileId id,
|
||||
uint8_t read_cmd,
|
||||
uint32_t offset,
|
||||
size_t size,
|
||||
MfDesfireFileData* data) {
|
||||
furi_check(instance);
|
||||
furi_check(data);
|
||||
|
||||
bit_buffer_reset(instance->input_buffer);
|
||||
bit_buffer_append_byte(instance->input_buffer, MF_DESFIRE_CMD_READ_DATA);
|
||||
bit_buffer_append_byte(instance->input_buffer, id);
|
||||
bit_buffer_append_bytes(instance->input_buffer, (const uint8_t*)&offset, 3);
|
||||
bit_buffer_append_bytes(instance->input_buffer, (const uint8_t*)&size, 3);
|
||||
MfDesfireError error = MfDesfireErrorNone;
|
||||
simple_array_init(data->data, size);
|
||||
|
||||
MfDesfireError error;
|
||||
size_t buffer_capacity = bit_buffer_get_capacity_bytes(instance->result_buffer);
|
||||
uint32_t current_offset = offset;
|
||||
uint32_t bytes_read = 0;
|
||||
|
||||
while(bytes_read < size) {
|
||||
size_t bytes_to_read = MIN(buffer_capacity, size - bytes_read);
|
||||
bit_buffer_reset(instance->input_buffer);
|
||||
bit_buffer_append_byte(instance->input_buffer, read_cmd);
|
||||
bit_buffer_append_byte(instance->input_buffer, id);
|
||||
bit_buffer_append_bytes(instance->input_buffer, (const uint8_t*)¤t_offset, 3);
|
||||
bit_buffer_append_bytes(instance->input_buffer, (const uint8_t*)&bytes_to_read, 3);
|
||||
|
||||
do {
|
||||
error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer);
|
||||
|
||||
if(error != MfDesfireErrorNone) break;
|
||||
|
||||
if(!mf_desfire_file_data_parse(data, instance->result_buffer)) {
|
||||
size_t bytes_received = bit_buffer_get_size_bytes(instance->result_buffer);
|
||||
if(bytes_received != bytes_to_read) {
|
||||
FURI_LOG_W(TAG, "Read %zu out of %zu bytes", bytes_received, bytes_to_read);
|
||||
error = MfDesfireErrorProtocol;
|
||||
break;
|
||||
}
|
||||
} while(false);
|
||||
|
||||
uint8_t* file_data = simple_array_get_data(data->data);
|
||||
bit_buffer_write_bytes(instance->result_buffer, &file_data[current_offset], bytes_to_read);
|
||||
bytes_read += bytes_to_read;
|
||||
current_offset += bytes_to_read;
|
||||
}
|
||||
|
||||
if(error != MfDesfireErrorNone) {
|
||||
simple_array_reset(data->data);
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
MfDesfireError mf_desfire_poller_read_file_data(
|
||||
MfDesfirePoller* instance,
|
||||
MfDesfireFileId id,
|
||||
uint32_t offset,
|
||||
size_t size,
|
||||
MfDesfireFileData* data) {
|
||||
return mf_desfire_poller_read_file(instance, id, MF_DESFIRE_CMD_READ_DATA, offset, size, data);
|
||||
}
|
||||
|
||||
MfDesfireError mf_desfire_poller_read_file_value(
|
||||
MfDesfirePoller* instance,
|
||||
MfDesfireFileId id,
|
||||
@ -389,28 +416,8 @@ MfDesfireError mf_desfire_poller_read_file_records(
|
||||
uint32_t offset,
|
||||
size_t size,
|
||||
MfDesfireFileData* data) {
|
||||
furi_check(instance);
|
||||
furi_check(data);
|
||||
|
||||
bit_buffer_reset(instance->input_buffer);
|
||||
bit_buffer_append_byte(instance->input_buffer, MF_DESFIRE_CMD_READ_RECORDS);
|
||||
bit_buffer_append_byte(instance->input_buffer, id);
|
||||
bit_buffer_append_bytes(instance->input_buffer, (const uint8_t*)&offset, 3);
|
||||
bit_buffer_append_bytes(instance->input_buffer, (const uint8_t*)&size, 3);
|
||||
|
||||
MfDesfireError error;
|
||||
|
||||
do {
|
||||
error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer);
|
||||
|
||||
if(error != MfDesfireErrorNone) break;
|
||||
|
||||
if(!mf_desfire_file_data_parse(data, instance->result_buffer)) {
|
||||
error = MfDesfireErrorProtocol;
|
||||
}
|
||||
} while(false);
|
||||
|
||||
return error;
|
||||
return mf_desfire_poller_read_file(
|
||||
instance, id, MF_DESFIRE_CMD_READ_RECORDS, offset, size, data);
|
||||
}
|
||||
|
||||
MfDesfireError mf_desfire_poller_read_file_data_multi(
|
||||
|
189
lib/nfc/protocols/mf_plus/mf_plus.c
Normal file
189
lib/nfc/protocols/mf_plus/mf_plus.c
Normal file
@ -0,0 +1,189 @@
|
||||
#include "mf_plus_i.h"
|
||||
|
||||
#include <bit_lib/bit_lib.h>
|
||||
#include <furi.h>
|
||||
|
||||
#define MF_PLUS_PROTOCOL_NAME "Mifare Plus"
|
||||
|
||||
static const char* mf_plus_type_strings[] = {
|
||||
[MfPlusTypeS] = "Plus S",
|
||||
[MfPlusTypeX] = "Plus X",
|
||||
[MfPlusTypeSE] = "Plus SE",
|
||||
[MfPlusTypeEV1] = "Plus EV1",
|
||||
[MfPlusTypeEV2] = "Plus EV2",
|
||||
[MfPlusTypePlus] = "Plus",
|
||||
[MfPlusTypeUnknown] = "Unknown",
|
||||
};
|
||||
|
||||
static const char* mf_plus_size_strings[] = {
|
||||
[MfPlusSize1K] = "1K",
|
||||
[MfPlusSize2K] = "2K",
|
||||
[MfPlusSize4K] = "4K",
|
||||
[MfPlusSizeUnknown] = "Unknown",
|
||||
};
|
||||
|
||||
static const char* mf_plus_security_level_strings[] = {
|
||||
[MfPlusSecurityLevel0] = "SL0",
|
||||
[MfPlusSecurityLevel1] = "SL1",
|
||||
[MfPlusSecurityLevel2] = "SL2",
|
||||
[MfPlusSecurityLevel3] = "SL3",
|
||||
[MfPlusSecurityLevelUnknown] = "Unknown",
|
||||
};
|
||||
|
||||
const NfcDeviceBase nfc_device_mf_plus = {
|
||||
.protocol_name = MF_PLUS_PROTOCOL_NAME,
|
||||
.alloc = (NfcDeviceAlloc)mf_plus_alloc,
|
||||
.free = (NfcDeviceFree)mf_plus_free,
|
||||
.reset = (NfcDeviceReset)mf_plus_reset,
|
||||
.copy = (NfcDeviceCopy)mf_plus_copy,
|
||||
.verify = (NfcDeviceVerify)mf_plus_verify,
|
||||
.load = (NfcDeviceLoad)mf_plus_load,
|
||||
.save = (NfcDeviceSave)mf_plus_save,
|
||||
.is_equal = (NfcDeviceEqual)mf_plus_is_equal,
|
||||
.get_name = (NfcDeviceGetName)mf_plus_get_device_name,
|
||||
.get_uid = (NfcDeviceGetUid)mf_plus_get_uid,
|
||||
.set_uid = (NfcDeviceSetUid)mf_plus_set_uid,
|
||||
.get_base_data = (NfcDeviceGetBaseData)mf_plus_get_base_data,
|
||||
};
|
||||
|
||||
MfPlusData* mf_plus_alloc(void) {
|
||||
MfPlusData* data = malloc(sizeof(MfPlusData));
|
||||
data->device_name = furi_string_alloc();
|
||||
data->iso14443_4a_data = iso14443_4a_alloc();
|
||||
|
||||
data->type = MfPlusTypeUnknown;
|
||||
data->security_level = MfPlusSecurityLevelUnknown;
|
||||
data->size = MfPlusSizeUnknown;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
void mf_plus_free(MfPlusData* data) {
|
||||
furi_check(data);
|
||||
furi_string_free(data->device_name);
|
||||
iso14443_4a_free(data->iso14443_4a_data);
|
||||
free(data);
|
||||
}
|
||||
|
||||
void mf_plus_reset(MfPlusData* data) {
|
||||
furi_check(data);
|
||||
iso14443_4a_reset(data->iso14443_4a_data);
|
||||
|
||||
memset(&data->version, 0, sizeof(data->version));
|
||||
data->type = MfPlusTypeUnknown;
|
||||
data->security_level = MfPlusSecurityLevelUnknown;
|
||||
data->size = MfPlusSizeUnknown;
|
||||
}
|
||||
|
||||
void mf_plus_copy(MfPlusData* data, const MfPlusData* other) {
|
||||
furi_check(data);
|
||||
furi_check(other);
|
||||
iso14443_4a_copy(data->iso14443_4a_data, other->iso14443_4a_data);
|
||||
|
||||
data->version = other->version;
|
||||
data->type = other->type;
|
||||
data->security_level = other->security_level;
|
||||
data->size = other->size;
|
||||
}
|
||||
|
||||
bool mf_plus_verify(MfPlusData* data, const FuriString* device_type) {
|
||||
UNUSED(data);
|
||||
return furi_string_equal_str(device_type, MF_PLUS_PROTOCOL_NAME);
|
||||
}
|
||||
|
||||
bool mf_plus_load(MfPlusData* data, FlipperFormat* ff, uint32_t version) {
|
||||
furi_assert(data);
|
||||
|
||||
bool success = false;
|
||||
|
||||
do {
|
||||
if(!iso14443_4a_load(data->iso14443_4a_data, ff, version)) break;
|
||||
if(!mf_plus_version_load(&data->version, ff)) break;
|
||||
if(!mf_plus_type_load(&data->type, ff)) break;
|
||||
if(!mf_plus_security_level_load(&data->security_level, ff)) break;
|
||||
if(!mf_plus_size_load(&data->size, ff)) break;
|
||||
success = true;
|
||||
} while(false);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool mf_plus_save(const MfPlusData* data, FlipperFormat* ff) {
|
||||
furi_assert(data);
|
||||
|
||||
bool success = false;
|
||||
|
||||
do {
|
||||
if(!iso14443_4a_save(data->iso14443_4a_data, ff)) break;
|
||||
if(!flipper_format_write_comment_cstr(ff, MF_PLUS_PROTOCOL_NAME " specific data")) break;
|
||||
if(!mf_plus_version_save(&data->version, ff)) break;
|
||||
if(!mf_plus_type_save(&data->type, ff)) break;
|
||||
if(!mf_plus_security_level_save(&data->security_level, ff)) break;
|
||||
if(!mf_plus_size_save(&data->size, ff)) break;
|
||||
success = true;
|
||||
} while(false);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool mf_plus_is_equal(const MfPlusData* data, const MfPlusData* other) {
|
||||
furi_assert(data);
|
||||
furi_assert(other);
|
||||
bool equal = false;
|
||||
|
||||
do {
|
||||
if(!iso14443_4a_is_equal(data->iso14443_4a_data, other->iso14443_4a_data)) break;
|
||||
if(memcmp(&data->version, &other->version, sizeof(data->version)) != 0) break;
|
||||
if(data->security_level != other->security_level) break;
|
||||
if(data->type != other->type) break;
|
||||
if(data->size != other->size) break;
|
||||
equal = true;
|
||||
} while(false);
|
||||
|
||||
return equal;
|
||||
}
|
||||
|
||||
const char* mf_plus_get_device_name(const MfPlusData* data, NfcDeviceNameType name_type) {
|
||||
furi_check(data);
|
||||
|
||||
FuriString* full_name = furi_string_alloc();
|
||||
const char* name = NULL;
|
||||
|
||||
do {
|
||||
if(name_type == NfcDeviceNameTypeFull) {
|
||||
furi_string_reset(data->device_name);
|
||||
furi_string_cat_printf(
|
||||
data->device_name,
|
||||
"Mifare %s %s %s",
|
||||
mf_plus_type_strings[data->type], // Includes "Plus" for regular Mifare Plus cards
|
||||
mf_plus_size_strings[data->size],
|
||||
mf_plus_security_level_strings[data->security_level]);
|
||||
name = furi_string_get_cstr(data->device_name);
|
||||
FURI_LOG_D("Mifare Plus", "Full name: %s", name);
|
||||
} else if(name_type == NfcDeviceNameTypeShort) {
|
||||
name = "Mifare Plus";
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} while(false);
|
||||
|
||||
furi_string_free(full_name);
|
||||
FURI_LOG_D("Mifare Plus", "Name: %s", name);
|
||||
return name;
|
||||
}
|
||||
|
||||
const uint8_t* mf_plus_get_uid(const MfPlusData* data, size_t* uid_len) {
|
||||
furi_assert(data);
|
||||
|
||||
return iso14443_4a_get_uid(data->iso14443_4a_data, uid_len);
|
||||
}
|
||||
|
||||
bool mf_plus_set_uid(MfPlusData* data, const uint8_t* uid, size_t uid_len) {
|
||||
furi_assert(data);
|
||||
|
||||
return iso14443_4a_set_uid(data->iso14443_4a_data, uid, uid_len);
|
||||
}
|
||||
Iso14443_4aData* mf_plus_get_base_data(const MfPlusData* data) {
|
||||
furi_check(data);
|
||||
return data->iso14443_4a_data;
|
||||
}
|
117
lib/nfc/protocols/mf_plus/mf_plus.h
Normal file
117
lib/nfc/protocols/mf_plus/mf_plus.h
Normal file
@ -0,0 +1,117 @@
|
||||
#pragma once
|
||||
|
||||
#include <lib/nfc/protocols/iso14443_4a/iso14443_4a.h>
|
||||
|
||||
#include <lib/toolbox/simple_array.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define MF_PLUS_UID_SIZE (7)
|
||||
#define MF_PLUS_BATCH_SIZE (5)
|
||||
|
||||
#define MF_PLUS_CMD_GET_VERSION (0x60)
|
||||
|
||||
typedef enum {
|
||||
MfPlusErrorNone,
|
||||
MfPlusErrorUnknown,
|
||||
MfPlusErrorNotPresent,
|
||||
MfPlusErrorProtocol,
|
||||
MfPlusErrorAuth,
|
||||
MfPlusErrorPartialRead,
|
||||
MfPlusErrorTimeout,
|
||||
} MfPlusError;
|
||||
|
||||
typedef enum {
|
||||
MfPlusTypePlus,
|
||||
MfPlusTypeEV1,
|
||||
MfPlusTypeEV2,
|
||||
MfPlusTypeS,
|
||||
MfPlusTypeSE,
|
||||
MfPlusTypeX,
|
||||
|
||||
MfPlusTypeUnknown,
|
||||
MfPlusTypeNum,
|
||||
} MfPlusType;
|
||||
|
||||
typedef enum {
|
||||
MfPlusSize1K,
|
||||
MfPlusSize2K,
|
||||
MfPlusSize4K,
|
||||
|
||||
MfPlusSizeUnknown,
|
||||
MfPlusSizeNum,
|
||||
} MfPlusSize;
|
||||
|
||||
typedef enum {
|
||||
MfPlusSecurityLevel0,
|
||||
MfPlusSecurityLevel1,
|
||||
MfPlusSecurityLevel2,
|
||||
MfPlusSecurityLevel3,
|
||||
|
||||
MfPlusSecurityLevelUnknown,
|
||||
MfPlusSecurityLevelNum,
|
||||
} MfPlusSecurityLevel;
|
||||
|
||||
typedef struct {
|
||||
uint8_t hw_vendor;
|
||||
uint8_t hw_type;
|
||||
uint8_t hw_subtype;
|
||||
uint8_t hw_major;
|
||||
uint8_t hw_minor;
|
||||
uint8_t hw_storage;
|
||||
uint8_t hw_proto;
|
||||
|
||||
uint8_t sw_vendor;
|
||||
uint8_t sw_type;
|
||||
uint8_t sw_subtype;
|
||||
uint8_t sw_major;
|
||||
uint8_t sw_minor;
|
||||
uint8_t sw_storage;
|
||||
uint8_t sw_proto;
|
||||
|
||||
uint8_t uid[MF_PLUS_UID_SIZE];
|
||||
uint8_t batch[MF_PLUS_BATCH_SIZE];
|
||||
uint8_t prod_week;
|
||||
uint8_t prod_year;
|
||||
} MfPlusVersion;
|
||||
|
||||
typedef struct {
|
||||
Iso14443_4aData* iso14443_4a_data;
|
||||
MfPlusVersion version;
|
||||
MfPlusType type;
|
||||
MfPlusSize size;
|
||||
MfPlusSecurityLevel security_level;
|
||||
FuriString* device_name;
|
||||
} MfPlusData;
|
||||
|
||||
extern const NfcDeviceBase nfc_device_mf_plus;
|
||||
|
||||
MfPlusData* mf_plus_alloc(void);
|
||||
|
||||
void mf_plus_free(MfPlusData* data);
|
||||
|
||||
void mf_plus_reset(MfPlusData* data);
|
||||
|
||||
void mf_plus_copy(MfPlusData* data, const MfPlusData* other);
|
||||
|
||||
bool mf_plus_verify(MfPlusData* data, const FuriString* device_type);
|
||||
|
||||
bool mf_plus_load(MfPlusData* data, FlipperFormat* ff, uint32_t version);
|
||||
|
||||
bool mf_plus_save(const MfPlusData* data, FlipperFormat* ff);
|
||||
|
||||
bool mf_plus_is_equal(const MfPlusData* data, const MfPlusData* other);
|
||||
|
||||
const char* mf_plus_get_device_name(const MfPlusData* data, NfcDeviceNameType name_type);
|
||||
|
||||
const uint8_t* mf_plus_get_uid(const MfPlusData* data, size_t* uid_len);
|
||||
|
||||
bool mf_plus_set_uid(MfPlusData* data, const uint8_t* uid, size_t uid_len);
|
||||
|
||||
Iso14443_4aData* mf_plus_get_base_data(const MfPlusData* data);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
216
lib/nfc/protocols/mf_plus/mf_plus_i.c
Normal file
216
lib/nfc/protocols/mf_plus/mf_plus_i.c
Normal file
@ -0,0 +1,216 @@
|
||||
#include "mf_plus_i.h"
|
||||
|
||||
#define MF_PLUS_FFF_VERSION_KEY \
|
||||
MF_PLUS_FFF_PICC_PREFIX " " \
|
||||
"Version"
|
||||
|
||||
#define MF_PLUS_FFF_SECURITY_LEVEL_KEY "Security Level"
|
||||
#define MF_PLUS_FFF_CARD_TYPE_KEY "Card Type"
|
||||
#define MF_PLUS_FFF_MEMORY_SIZE_KEY "Memory Size"
|
||||
|
||||
bool mf_plus_version_parse(MfPlusVersion* data, const BitBuffer* buf) {
|
||||
const bool can_parse = bit_buffer_get_size_bytes(buf) == sizeof(MfPlusVersion);
|
||||
|
||||
if(can_parse) {
|
||||
bit_buffer_write_bytes(buf, data, sizeof(MfPlusVersion));
|
||||
}
|
||||
|
||||
return can_parse;
|
||||
}
|
||||
|
||||
bool mf_plus_security_level_parse(MfPlusSecurityLevel* data, const BitBuffer* buf) {
|
||||
const bool can_parse = bit_buffer_get_size_bytes(buf) == sizeof(MfPlusSecurityLevel);
|
||||
|
||||
if(can_parse) {
|
||||
bit_buffer_write_bytes(buf, data, sizeof(MfPlusSecurityLevel));
|
||||
}
|
||||
|
||||
return can_parse;
|
||||
}
|
||||
|
||||
bool mf_plus_type_parse(MfPlusType* data, const BitBuffer* buf) {
|
||||
const bool can_parse = bit_buffer_get_size_bytes(buf) == sizeof(MfPlusType);
|
||||
|
||||
if(can_parse) {
|
||||
bit_buffer_write_bytes(buf, data, sizeof(MfPlusType));
|
||||
}
|
||||
|
||||
return can_parse;
|
||||
}
|
||||
|
||||
bool mf_plus_size_parse(MfPlusSize* data, const BitBuffer* buf) {
|
||||
const bool can_parse = bit_buffer_get_size_bytes(buf) == sizeof(MfPlusSize);
|
||||
|
||||
if(can_parse) {
|
||||
bit_buffer_write_bytes(buf, data, sizeof(MfPlusSize));
|
||||
}
|
||||
|
||||
return can_parse;
|
||||
}
|
||||
|
||||
bool mf_plus_version_load(MfPlusVersion* data, FlipperFormat* ff) {
|
||||
return flipper_format_read_hex(
|
||||
ff, MF_PLUS_FFF_VERSION_KEY, (uint8_t*)data, sizeof(MfPlusVersion));
|
||||
}
|
||||
|
||||
bool mf_plus_security_level_load(MfPlusSecurityLevel* data, FlipperFormat* ff) {
|
||||
FuriString* security_level_string = furi_string_alloc();
|
||||
flipper_format_read_string(ff, MF_PLUS_FFF_SECURITY_LEVEL_KEY, security_level_string);
|
||||
|
||||
// Take the last character of the string
|
||||
char security_level_char = furi_string_get_char(
|
||||
security_level_string, furi_string_utf8_length(security_level_string) - 1);
|
||||
|
||||
switch(security_level_char) {
|
||||
case '0':
|
||||
*data = MfPlusSecurityLevel0;
|
||||
break;
|
||||
case '1':
|
||||
*data = MfPlusSecurityLevel1;
|
||||
break;
|
||||
case '2':
|
||||
*data = MfPlusSecurityLevel2;
|
||||
break;
|
||||
case '3':
|
||||
*data = MfPlusSecurityLevel3;
|
||||
break;
|
||||
default:
|
||||
*data = MfPlusSecurityLevelUnknown;
|
||||
break;
|
||||
}
|
||||
|
||||
furi_string_free(security_level_string);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mf_plus_type_load(MfPlusType* data, FlipperFormat* ff) {
|
||||
FuriString* type_string = furi_string_alloc();
|
||||
flipper_format_read_string(ff, MF_PLUS_FFF_CARD_TYPE_KEY, type_string);
|
||||
|
||||
if(furi_string_equal_str(type_string, "Mifare Plus")) {
|
||||
*data = MfPlusTypePlus;
|
||||
} else if(furi_string_equal_str(type_string, "Mifare Plus X")) {
|
||||
*data = MfPlusTypeX;
|
||||
} else if(furi_string_equal_str(type_string, "Mifare Plus S")) {
|
||||
*data = MfPlusTypeS;
|
||||
} else if(furi_string_equal_str(type_string, "Mifare Plus SE")) {
|
||||
*data = MfPlusTypeSE;
|
||||
} else if(furi_string_equal_str(type_string, "Mifare Plus EV1")) {
|
||||
*data = MfPlusTypeEV1;
|
||||
} else if(furi_string_equal_str(type_string, "Mifare Plus EV2")) {
|
||||
*data = MfPlusTypeEV2;
|
||||
} else {
|
||||
*data = MfPlusTypeUnknown;
|
||||
}
|
||||
|
||||
furi_string_free(type_string);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mf_plus_size_load(MfPlusSize* data, FlipperFormat* ff) {
|
||||
FuriString* size_string = furi_string_alloc();
|
||||
flipper_format_read_string(ff, MF_PLUS_FFF_MEMORY_SIZE_KEY, size_string);
|
||||
|
||||
if(furi_string_equal_str(size_string, "1K")) {
|
||||
*data = MfPlusSize1K;
|
||||
} else if(furi_string_equal_str(size_string, "2K")) {
|
||||
*data = MfPlusSize2K;
|
||||
} else if(furi_string_equal_str(size_string, "4K")) {
|
||||
*data = MfPlusSize4K;
|
||||
} else {
|
||||
*data = MfPlusSizeUnknown;
|
||||
}
|
||||
|
||||
furi_string_free(size_string);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mf_plus_version_save(const MfPlusVersion* data, FlipperFormat* ff) {
|
||||
return flipper_format_write_hex(
|
||||
ff, MF_PLUS_FFF_VERSION_KEY, (const uint8_t*)data, sizeof(MfPlusVersion));
|
||||
}
|
||||
|
||||
bool mf_plus_security_level_save(const MfPlusSecurityLevel* data, FlipperFormat* ff) {
|
||||
FuriString* security_level_string = furi_string_alloc();
|
||||
|
||||
switch(*data) {
|
||||
case MfPlusSecurityLevel0:
|
||||
furi_string_cat(security_level_string, "SL0");
|
||||
break;
|
||||
case MfPlusSecurityLevel1:
|
||||
furi_string_cat(security_level_string, "SL1");
|
||||
break;
|
||||
case MfPlusSecurityLevel2:
|
||||
furi_string_cat(security_level_string, "SL2");
|
||||
break;
|
||||
case MfPlusSecurityLevel3:
|
||||
furi_string_cat(security_level_string, "SL3");
|
||||
break;
|
||||
default:
|
||||
furi_string_cat(security_level_string, "Unknown");
|
||||
break;
|
||||
}
|
||||
|
||||
flipper_format_write_string(ff, MF_PLUS_FFF_SECURITY_LEVEL_KEY, security_level_string);
|
||||
furi_string_free(security_level_string);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mf_plus_type_save(const MfPlusType* data, FlipperFormat* ff) {
|
||||
FuriString* type_string = furi_string_alloc();
|
||||
|
||||
switch(*data) {
|
||||
case MfPlusTypePlus:
|
||||
furi_string_cat(type_string, "Mifare Plus");
|
||||
break;
|
||||
case MfPlusTypeX:
|
||||
furi_string_cat(type_string, "Mifare Plus X");
|
||||
break;
|
||||
case MfPlusTypeS:
|
||||
furi_string_cat(type_string, "Mifare Plus S");
|
||||
break;
|
||||
case MfPlusTypeSE:
|
||||
furi_string_cat(type_string, "Mifare Plus SE");
|
||||
break;
|
||||
case MfPlusTypeEV1:
|
||||
furi_string_cat(type_string, "Mifare Plus EV1");
|
||||
break;
|
||||
case MfPlusTypeEV2:
|
||||
furi_string_cat(type_string, "Mifare Plus EV2");
|
||||
break;
|
||||
default:
|
||||
furi_string_cat(type_string, "Unknown");
|
||||
break;
|
||||
}
|
||||
|
||||
flipper_format_write_string(ff, MF_PLUS_FFF_CARD_TYPE_KEY, type_string);
|
||||
furi_string_free(type_string);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mf_plus_size_save(const MfPlusSize* data, FlipperFormat* ff) {
|
||||
FuriString* size_string = furi_string_alloc();
|
||||
|
||||
switch(*data) {
|
||||
case MfPlusSize1K:
|
||||
furi_string_cat(size_string, "1K");
|
||||
break;
|
||||
case MfPlusSize2K:
|
||||
furi_string_cat(size_string, "2K");
|
||||
break;
|
||||
case MfPlusSize4K:
|
||||
furi_string_cat(size_string, "4K");
|
||||
break;
|
||||
default:
|
||||
furi_string_cat(size_string, "Unknown");
|
||||
break;
|
||||
}
|
||||
|
||||
flipper_format_write_string(ff, MF_PLUS_FFF_MEMORY_SIZE_KEY, size_string);
|
||||
furi_string_free(size_string);
|
||||
|
||||
return true;
|
||||
}
|
29
lib/nfc/protocols/mf_plus/mf_plus_i.h
Normal file
29
lib/nfc/protocols/mf_plus/mf_plus_i.h
Normal file
@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include "mf_plus.h"
|
||||
|
||||
#define MF_PLUS_FFF_PICC_PREFIX "PICC"
|
||||
|
||||
bool mf_plus_version_parse(MfPlusVersion* data, const BitBuffer* buf);
|
||||
|
||||
bool mf_plus_security_level_parse(MfPlusSecurityLevel* data, const BitBuffer* buf);
|
||||
|
||||
bool mf_plus_type_parse(MfPlusType* data, const BitBuffer* buf);
|
||||
|
||||
bool mf_plus_size_parse(MfPlusSize* data, const BitBuffer* buf);
|
||||
|
||||
bool mf_plus_version_load(MfPlusVersion* data, FlipperFormat* ff);
|
||||
|
||||
bool mf_plus_security_level_load(MfPlusSecurityLevel* data, FlipperFormat* ff);
|
||||
|
||||
bool mf_plus_type_load(MfPlusType* data, FlipperFormat* ff);
|
||||
|
||||
bool mf_plus_size_load(MfPlusSize* data, FlipperFormat* ff);
|
||||
|
||||
bool mf_plus_version_save(const MfPlusVersion* data, FlipperFormat* ff);
|
||||
|
||||
bool mf_plus_security_level_save(const MfPlusSecurityLevel* data, FlipperFormat* ff);
|
||||
|
||||
bool mf_plus_type_save(const MfPlusType* data, FlipperFormat* ff);
|
||||
|
||||
bool mf_plus_size_save(const MfPlusSize* data, FlipperFormat* ff);
|
367
lib/nfc/protocols/mf_plus/mf_plus_poller.c
Normal file
367
lib/nfc/protocols/mf_plus/mf_plus_poller.c
Normal file
@ -0,0 +1,367 @@
|
||||
#include "mf_plus_poller_i.h"
|
||||
|
||||
#include <nfc/protocols/nfc_poller_base.h>
|
||||
|
||||
#include <furi.h>
|
||||
|
||||
#define TAG "MfPlusPoller"
|
||||
|
||||
#define MF_PLUS_BUF_SIZE (64U)
|
||||
#define MF_PLUS_RESULT_BUF_SIZE (512U)
|
||||
|
||||
const char* mf_plus_ats_t1_tk_values[] = {
|
||||
"\xC1\x05\x2F\x2F\x00\x35\xC7", // Mifare Plus S
|
||||
"\xC1\x05\x2F\x2F\x01\xBC\xD6", // Mifare Plus X
|
||||
"\xC1\x05\x2F\x2F\x00\xF6\xD1", // Mifare Plus SE
|
||||
"\xC1\x05\x2F\x2F\x01\xF6\xD1", // Mifare Plus SE
|
||||
};
|
||||
|
||||
typedef NfcCommand (*MfPlusPollerReadHandler)(MfPlusPoller* instance);
|
||||
|
||||
const MfPlusData* mf_plus_poller_get_data(MfPlusPoller* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
return instance->data;
|
||||
}
|
||||
|
||||
bool mf_plus_poller_get_type_from_iso4(const Iso14443_4aData* iso4_data, MfPlusData* mf_plus_data) {
|
||||
furi_assert(iso4_data);
|
||||
furi_assert(mf_plus_data);
|
||||
|
||||
switch(iso4_data->iso14443_3a_data->sak) {
|
||||
case 0x08:
|
||||
if(memcmp(
|
||||
simple_array_get_data(iso4_data->ats_data.t1_tk),
|
||||
mf_plus_ats_t1_tk_values[0],
|
||||
simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) {
|
||||
// Mifare Plus S 2K SL1
|
||||
mf_plus_data->type = MfPlusTypeS;
|
||||
mf_plus_data->size = MfPlusSize2K;
|
||||
mf_plus_data->security_level = MfPlusSecurityLevel1;
|
||||
FURI_LOG_D(TAG, "Mifare Plus S 2K SL1");
|
||||
return true;
|
||||
} else if(
|
||||
memcmp(
|
||||
simple_array_get_data(iso4_data->ats_data.t1_tk),
|
||||
mf_plus_ats_t1_tk_values[1],
|
||||
simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) {
|
||||
// Mifare Plus X 2K SL1
|
||||
mf_plus_data->type = MfPlusTypeX;
|
||||
mf_plus_data->size = MfPlusSize2K;
|
||||
mf_plus_data->security_level = MfPlusSecurityLevel1;
|
||||
FURI_LOG_D(TAG, "Mifare Plus X 2K SL1");
|
||||
return true;
|
||||
} else if(
|
||||
memcmp(
|
||||
simple_array_get_data(iso4_data->ats_data.t1_tk),
|
||||
mf_plus_ats_t1_tk_values[2],
|
||||
simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0 ||
|
||||
memcmp(
|
||||
simple_array_get_data(iso4_data->ats_data.t1_tk),
|
||||
mf_plus_ats_t1_tk_values[3],
|
||||
simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) {
|
||||
// Mifare Plus SE 1K SL1
|
||||
mf_plus_data->type = MfPlusTypeSE;
|
||||
mf_plus_data->size = MfPlusSize1K;
|
||||
mf_plus_data->security_level = MfPlusSecurityLevel1;
|
||||
FURI_LOG_D(TAG, "Mifare Plus SE 1K SL1");
|
||||
return true;
|
||||
} else {
|
||||
FURI_LOG_D(TAG, "Sak 08 but no known Mifare Plus type");
|
||||
return false;
|
||||
}
|
||||
case 0x18:
|
||||
if(memcmp(
|
||||
simple_array_get_data(iso4_data->ats_data.t1_tk),
|
||||
mf_plus_ats_t1_tk_values[0],
|
||||
simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) {
|
||||
// Mifare Plus S 4K SL1
|
||||
mf_plus_data->type = MfPlusTypeS;
|
||||
mf_plus_data->size = MfPlusSize4K;
|
||||
mf_plus_data->security_level = MfPlusSecurityLevel1;
|
||||
FURI_LOG_D(TAG, "Mifare Plus S 4K SL1");
|
||||
return true;
|
||||
} else if(
|
||||
memcmp(
|
||||
simple_array_get_data(iso4_data->ats_data.t1_tk),
|
||||
mf_plus_ats_t1_tk_values[1],
|
||||
simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) {
|
||||
// Mifare Plus X 4K SL1
|
||||
mf_plus_data->type = MfPlusTypeX;
|
||||
mf_plus_data->size = MfPlusSize4K;
|
||||
mf_plus_data->security_level = MfPlusSecurityLevel1;
|
||||
FURI_LOG_D(TAG, "Mifare Plus X 4K SL1");
|
||||
return true;
|
||||
} else {
|
||||
FURI_LOG_D(TAG, "Sak 18 but no known Mifare Plus type");
|
||||
return false;
|
||||
}
|
||||
case 0x20:
|
||||
if(memcmp(
|
||||
iso4_data->ats_data.t1_tk,
|
||||
mf_plus_ats_t1_tk_values[0],
|
||||
simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) {
|
||||
// Mifare Plus S 2/4K SL3
|
||||
mf_plus_data->type = MfPlusTypeS;
|
||||
mf_plus_data->security_level = MfPlusSecurityLevel3;
|
||||
|
||||
if(iso4_data->iso14443_3a_data->atqa[1] == 0x04) {
|
||||
// Mifare Plus S 2K SL3
|
||||
mf_plus_data->size = MfPlusSize2K;
|
||||
FURI_LOG_D(TAG, "Mifare Plus S 2K SL3");
|
||||
} else if(iso4_data->iso14443_3a_data->atqa[1] == 0x02) {
|
||||
// Mifare Plus S 4K SL3
|
||||
mf_plus_data->size = MfPlusSize4K;
|
||||
FURI_LOG_D(TAG, "Mifare Plus S 4K SL3");
|
||||
} else {
|
||||
FURI_LOG_D(TAG, "Sak 20 but no known Mifare Plus type (S)");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
} else if(
|
||||
memcmp(
|
||||
iso4_data->ats_data.t1_tk,
|
||||
mf_plus_ats_t1_tk_values[1],
|
||||
simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) {
|
||||
mf_plus_data->type = MfPlusTypeX;
|
||||
mf_plus_data->security_level = MfPlusSecurityLevel3;
|
||||
|
||||
if(iso4_data->iso14443_3a_data->atqa[1] == 0x04) {
|
||||
mf_plus_data->size = MfPlusSize2K;
|
||||
FURI_LOG_D(TAG, "Mifare Plus X 2K SL3");
|
||||
} else if(iso4_data->iso14443_3a_data->atqa[1] == 0x02) {
|
||||
mf_plus_data->size = MfPlusSize4K;
|
||||
FURI_LOG_D(TAG, "Mifare Plus X 4K SL3");
|
||||
} else {
|
||||
FURI_LOG_D(TAG, "Sak 20 but no known Mifare Plus type (X)");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
FURI_LOG_D(TAG, "Sak 20 but no known Mifare Plus type");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
FURI_LOG_D(TAG, "No known Mifare Plus type");
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool mf_plus_poller_detect_type(MfPlusPoller* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
bool detected = false;
|
||||
|
||||
const Iso14443_4aData* iso14443_4a_data =
|
||||
iso14443_4a_poller_get_data(instance->iso14443_4a_poller);
|
||||
const MfPlusError error = mf_plus_poller_read_version(instance, &instance->data->version);
|
||||
if(error == MfPlusErrorNone) {
|
||||
FURI_LOG_D(TAG, "Read version success: %d", error);
|
||||
if(instance->data->version.hw_major == 0x02 || instance->data->version.hw_major == 0x82) {
|
||||
detected = true;
|
||||
if(iso14443_4a_data->iso14443_3a_data->sak == 0x10) {
|
||||
// Mifare Plus 2K SL2
|
||||
instance->data->type = MfPlusTypePlus;
|
||||
instance->data->size = MfPlusSize2K;
|
||||
instance->data->security_level = MfPlusSecurityLevel2;
|
||||
} else if(iso14443_4a_data->iso14443_3a_data->sak == 0x11) {
|
||||
// Mifare Plus 4K SL3
|
||||
instance->data->type = MfPlusTypePlus;
|
||||
instance->data->size = MfPlusSize4K;
|
||||
instance->data->security_level = MfPlusSecurityLevel3;
|
||||
} else {
|
||||
// Mifare Plus EV1/EV2
|
||||
|
||||
// Revision
|
||||
switch(instance->data->version.hw_major) {
|
||||
case 0x11:
|
||||
instance->data->type = MfPlusTypeEV1;
|
||||
break;
|
||||
case 0x22:
|
||||
instance->data->type = MfPlusTypeEV2;
|
||||
break;
|
||||
default:
|
||||
instance->data->type = MfPlusTypeUnknown;
|
||||
break;
|
||||
}
|
||||
|
||||
// Storage size
|
||||
switch(instance->data->version.hw_storage) {
|
||||
case 0x16:
|
||||
instance->data->size = MfPlusSize2K;
|
||||
break;
|
||||
case 0x18:
|
||||
instance->data->size = MfPlusSize4K;
|
||||
break;
|
||||
default:
|
||||
instance->data->size = MfPlusSizeUnknown;
|
||||
break;
|
||||
}
|
||||
|
||||
// Security level
|
||||
if(iso14443_4a_data->iso14443_3a_data->sak == 0x20) {
|
||||
// Mifare Plus EV1/2 SL3
|
||||
instance->data->security_level = MfPlusSecurityLevel3;
|
||||
} else {
|
||||
// Mifare Plus EV1/2 SL1
|
||||
instance->data->security_level = MfPlusSecurityLevel1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
FURI_LOG_D(TAG, "Read version error: %d", error);
|
||||
detected = mf_plus_poller_get_type_from_iso4(iso14443_4a_data, instance->data);
|
||||
}
|
||||
|
||||
return detected;
|
||||
}
|
||||
|
||||
MfPlusPoller* mf_plus_poller_alloc(Iso14443_4aPoller* iso14443_4a_poller) {
|
||||
furi_assert(iso14443_4a_poller);
|
||||
|
||||
MfPlusPoller* instance = malloc(sizeof(MfPlusPoller));
|
||||
furi_assert(instance);
|
||||
|
||||
instance->iso14443_4a_poller = iso14443_4a_poller;
|
||||
|
||||
instance->data = mf_plus_alloc();
|
||||
|
||||
instance->tx_buffer = bit_buffer_alloc(MF_PLUS_BUF_SIZE);
|
||||
instance->rx_buffer = bit_buffer_alloc(MF_PLUS_BUF_SIZE);
|
||||
instance->input_buffer = bit_buffer_alloc(MF_PLUS_BUF_SIZE);
|
||||
instance->result_buffer = bit_buffer_alloc(MF_PLUS_RESULT_BUF_SIZE);
|
||||
|
||||
instance->general_event.protocol = NfcProtocolMfPlus;
|
||||
instance->general_event.event_data = &instance->mfp_event;
|
||||
instance->general_event.instance = instance;
|
||||
|
||||
instance->mfp_event.data = &instance->mfp_event_data;
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
static NfcCommand mf_plus_poller_handler_idle(MfPlusPoller* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
bit_buffer_reset(instance->input_buffer);
|
||||
bit_buffer_reset(instance->result_buffer);
|
||||
bit_buffer_reset(instance->tx_buffer);
|
||||
bit_buffer_reset(instance->rx_buffer);
|
||||
|
||||
iso14443_4a_copy(
|
||||
instance->data->iso14443_4a_data,
|
||||
iso14443_4a_poller_get_data(instance->iso14443_4a_poller));
|
||||
|
||||
instance->state = MfPlusPollerStateReadVersion;
|
||||
return NfcCommandContinue;
|
||||
}
|
||||
|
||||
static NfcCommand mf_plus_poller_handler_read_version(MfPlusPoller* instance) {
|
||||
bool success = mf_plus_poller_detect_type(instance);
|
||||
if(success) {
|
||||
instance->state = MfPlusPollerStateReadSuccess;
|
||||
} else {
|
||||
instance->state = MfPlusPollerStateReadFailed;
|
||||
}
|
||||
|
||||
return NfcCommandContinue;
|
||||
}
|
||||
|
||||
static NfcCommand mf_plus_poller_handler_read_failed(MfPlusPoller* instance) {
|
||||
furi_assert(instance);
|
||||
FURI_LOG_D(TAG, "Read failed");
|
||||
iso14443_4a_poller_halt(instance->iso14443_4a_poller);
|
||||
instance->mfp_event.data->error = instance->error;
|
||||
NfcCommand command = instance->callback(instance->general_event, instance->context);
|
||||
instance->state = MfPlusPollerStateIdle;
|
||||
return command;
|
||||
}
|
||||
|
||||
static NfcCommand mf_plus_poller_handler_read_success(MfPlusPoller* instance) {
|
||||
furi_assert(instance);
|
||||
FURI_LOG_D(TAG, "Read success");
|
||||
iso14443_4a_poller_halt(instance->iso14443_4a_poller);
|
||||
instance->mfp_event.type = MfPlusPollerEventTypeReadSuccess;
|
||||
NfcCommand command = instance->callback(instance->general_event, instance->context);
|
||||
return command;
|
||||
}
|
||||
|
||||
static const MfPlusPollerReadHandler mf_plus_poller_read_handler[MfPlusPollerStateNum] = {
|
||||
[MfPlusPollerStateIdle] = mf_plus_poller_handler_idle,
|
||||
[MfPlusPollerStateReadVersion] = mf_plus_poller_handler_read_version,
|
||||
[MfPlusPollerStateReadFailed] = mf_plus_poller_handler_read_failed,
|
||||
[MfPlusPollerStateReadSuccess] = mf_plus_poller_handler_read_success,
|
||||
};
|
||||
|
||||
static void mf_plus_poller_set_callback(
|
||||
MfPlusPoller* instance,
|
||||
NfcGenericCallback callback,
|
||||
void* context) {
|
||||
furi_assert(instance);
|
||||
furi_assert(callback);
|
||||
|
||||
instance->callback = callback;
|
||||
instance->context = context;
|
||||
}
|
||||
|
||||
static NfcCommand mf_plus_poller_run(NfcGenericEvent event, void* context) {
|
||||
furi_assert(event.protocol = NfcProtocolIso14443_4a);
|
||||
|
||||
MfPlusPoller* instance = context;
|
||||
furi_assert(instance);
|
||||
|
||||
const Iso14443_4aPollerEvent* iso14443_4a_event = event.event_data;
|
||||
furi_assert(iso14443_4a_event);
|
||||
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
|
||||
if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeReady) {
|
||||
command = mf_plus_poller_read_handler[instance->state](instance);
|
||||
} else if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeError) {
|
||||
instance->mfp_event.type = MfPlusPollerEventTypeReadFailed;
|
||||
command = instance->callback(instance->general_event, instance->context);
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
void mf_plus_poller_free(MfPlusPoller* instance) {
|
||||
furi_assert(instance);
|
||||
furi_assert(instance->data);
|
||||
|
||||
bit_buffer_free(instance->tx_buffer);
|
||||
bit_buffer_free(instance->rx_buffer);
|
||||
bit_buffer_free(instance->input_buffer);
|
||||
bit_buffer_free(instance->result_buffer);
|
||||
mf_plus_free(instance->data);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
static bool mf_plus_poller_detect(NfcGenericEvent event, void* context) {
|
||||
furi_assert(event.protocol = NfcProtocolIso14443_4a);
|
||||
|
||||
MfPlusPoller* instance = context;
|
||||
furi_assert(instance);
|
||||
|
||||
Iso14443_4aPollerEvent* iso14443_4a_event = event.event_data;
|
||||
furi_assert(iso14443_4a_event);
|
||||
|
||||
bool detected = false;
|
||||
|
||||
if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeReady) {
|
||||
detected = mf_plus_poller_detect_type(instance);
|
||||
}
|
||||
|
||||
return detected;
|
||||
}
|
||||
|
||||
const NfcPollerBase mf_plus_poller = {
|
||||
.alloc = (NfcPollerAlloc)mf_plus_poller_alloc,
|
||||
.free = (NfcPollerFree)mf_plus_poller_free,
|
||||
.set_callback = (NfcPollerSetCallback)mf_plus_poller_set_callback,
|
||||
.run = (NfcPollerRun)mf_plus_poller_run,
|
||||
.detect = (NfcPollerDetect)mf_plus_poller_detect,
|
||||
.get_data = (NfcPollerGetData)mf_plus_poller_get_data,
|
||||
};
|
55
lib/nfc/protocols/mf_plus/mf_plus_poller.h
Normal file
55
lib/nfc/protocols/mf_plus/mf_plus_poller.h
Normal file
@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
|
||||
#include "mf_plus.h"
|
||||
|
||||
#include <lib/nfc/protocols/iso14443_4a/iso14443_4a.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief MIFARE Plus poller opaque type definition.
|
||||
*/
|
||||
typedef struct MfPlusPoller MfPlusPoller;
|
||||
|
||||
/**
|
||||
* @brief Enumeration of possible MfPlus poller event types.
|
||||
*/
|
||||
|
||||
typedef enum {
|
||||
MfPlusPollerEventTypeReadSuccess, /**< Card was read successfully. */
|
||||
MfPlusPollerEventTypeReadFailed, /**< Poller failed to read the card. */
|
||||
} MfPlusPollerEventType;
|
||||
|
||||
/**
|
||||
* @brief MIFARE Plus poller event data.
|
||||
*/
|
||||
typedef union {
|
||||
MfPlusError error; /**< Error code indicating card reading fail reason. */
|
||||
} MfPlusPollerEventData;
|
||||
|
||||
/**
|
||||
* @brief MIFARE Plus poller event structure.
|
||||
*
|
||||
* Upon emission of an event, an instance of this struct will be passed to the callback.
|
||||
*/
|
||||
typedef struct {
|
||||
MfPlusPollerEventType type; /**< Type of emitted event. */
|
||||
MfPlusPollerEventData* data; /**< Pointer to event specific data. */
|
||||
} MfPlusPollerEvent;
|
||||
|
||||
/**
|
||||
* @brief Read MfPlus card version.
|
||||
*
|
||||
* Must ONLY be used inside the callback function.
|
||||
*
|
||||
* @param[in, out] instance pointer to the instance to be used in the transaction.
|
||||
* @param[out] data pointer to the MfPlusVersion structure to be filled with version data.
|
||||
* @return MfPlusErrorNone on success, an error code on failure.
|
||||
*/
|
||||
MfPlusError mf_plus_poller_read_version(MfPlusPoller* instance, MfPlusVersion* data);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
5
lib/nfc/protocols/mf_plus/mf_plus_poller_defs.h
Normal file
5
lib/nfc/protocols/mf_plus/mf_plus_poller_defs.h
Normal file
@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <nfc/protocols/nfc_poller_base.h>
|
||||
|
||||
extern const NfcPollerBase mf_plus_poller;
|
73
lib/nfc/protocols/mf_plus/mf_plus_poller_i.c
Normal file
73
lib/nfc/protocols/mf_plus/mf_plus_poller_i.c
Normal file
@ -0,0 +1,73 @@
|
||||
#include "mf_plus_poller_i.h"
|
||||
|
||||
#include <furi.h>
|
||||
|
||||
#include "mf_plus_i.h"
|
||||
|
||||
#define TAG "MfPlusPoller"
|
||||
|
||||
MfPlusError mf_plus_process_error(Iso14443_4aError error) {
|
||||
switch(error) {
|
||||
case Iso14443_4aErrorNone:
|
||||
return MfPlusErrorNone;
|
||||
case Iso14443_4aErrorNotPresent:
|
||||
return MfPlusErrorNotPresent;
|
||||
case Iso14443_4aErrorTimeout:
|
||||
return MfPlusErrorTimeout;
|
||||
default:
|
||||
return MfPlusErrorProtocol;
|
||||
}
|
||||
}
|
||||
|
||||
MfPlusError
|
||||
mf_plus_send_chunk(MfPlusPoller* instance, const BitBuffer* tx_buffer, BitBuffer* rx_buffer) {
|
||||
furi_assert(instance);
|
||||
furi_assert(instance->iso14443_4a_poller);
|
||||
furi_assert(instance->tx_buffer);
|
||||
furi_assert(instance->rx_buffer);
|
||||
furi_assert(tx_buffer);
|
||||
furi_assert(rx_buffer);
|
||||
|
||||
MfPlusError error = MfPlusErrorNone;
|
||||
|
||||
do {
|
||||
Iso14443_4aError iso14443_4a_error = iso14443_4a_poller_send_block(
|
||||
instance->iso14443_4a_poller, tx_buffer, instance->rx_buffer);
|
||||
|
||||
if(iso14443_4a_error != Iso14443_4aErrorNone) {
|
||||
error = mf_plus_process_error(iso14443_4a_error);
|
||||
break;
|
||||
}
|
||||
|
||||
bit_buffer_reset(instance->tx_buffer);
|
||||
|
||||
if(bit_buffer_get_size_bytes(instance->rx_buffer) > sizeof(uint8_t)) {
|
||||
bit_buffer_copy_right(rx_buffer, instance->rx_buffer, sizeof(uint8_t));
|
||||
} else {
|
||||
bit_buffer_reset(rx_buffer);
|
||||
}
|
||||
} while(false);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
MfPlusError mf_plus_poller_read_version(MfPlusPoller* instance, MfPlusVersion* data) {
|
||||
furi_assert(instance);
|
||||
|
||||
bit_buffer_reset(instance->input_buffer);
|
||||
bit_buffer_append_byte(instance->input_buffer, MF_PLUS_CMD_GET_VERSION);
|
||||
|
||||
MfPlusError error;
|
||||
|
||||
do {
|
||||
error = mf_plus_send_chunk(instance, instance->input_buffer, instance->result_buffer);
|
||||
|
||||
if(error != MfPlusErrorNone) break;
|
||||
|
||||
if(!mf_plus_version_parse(data, instance->result_buffer)) {
|
||||
error = MfPlusErrorProtocol;
|
||||
}
|
||||
} while(false);
|
||||
|
||||
return error;
|
||||
}
|
54
lib/nfc/protocols/mf_plus/mf_plus_poller_i.h
Normal file
54
lib/nfc/protocols/mf_plus/mf_plus_poller_i.h
Normal file
@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
#include "mf_plus_poller.h"
|
||||
|
||||
#include <lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define MF_PLUS_FWT_FC (60000)
|
||||
|
||||
typedef enum {
|
||||
MfPlusCardStateDetected,
|
||||
MfPlusCardStateLost,
|
||||
} MfPlusCardState;
|
||||
|
||||
typedef enum {
|
||||
MfPlusPollerStateIdle,
|
||||
MfPlusPollerStateReadVersion,
|
||||
MfPlusPollerStateReadFailed,
|
||||
MfPlusPollerStateReadSuccess,
|
||||
|
||||
MfPlusPollerStateNum,
|
||||
} MfPlusPollerState;
|
||||
|
||||
struct MfPlusPoller {
|
||||
Iso14443_4aPoller* iso14443_4a_poller;
|
||||
|
||||
MfPlusData* data;
|
||||
MfPlusPollerState state;
|
||||
|
||||
BitBuffer* tx_buffer;
|
||||
BitBuffer* rx_buffer;
|
||||
BitBuffer* input_buffer;
|
||||
BitBuffer* result_buffer;
|
||||
|
||||
MfPlusError error;
|
||||
NfcGenericEvent general_event;
|
||||
MfPlusPollerEvent mfp_event;
|
||||
MfPlusPollerEventData mfp_event_data;
|
||||
NfcGenericCallback callback;
|
||||
void* context;
|
||||
};
|
||||
|
||||
MfPlusError mf_plus_process_error(Iso14443_4aError error);
|
||||
|
||||
MfPlusPoller* mf_plus_poller_alloc(Iso14443_4aPoller* iso14443_4a_poller);
|
||||
|
||||
void mf_plus_poller_free(MfPlusPoller* instance);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
@ -20,6 +20,7 @@
|
||||
#include <nfc/protocols/felica/felica.h>
|
||||
#include <nfc/protocols/mf_ultralight/mf_ultralight.h>
|
||||
#include <nfc/protocols/mf_classic/mf_classic.h>
|
||||
#include <nfc/protocols/mf_plus/mf_plus.h>
|
||||
#include <nfc/protocols/mf_desfire/mf_desfire.h>
|
||||
#include <nfc/protocols/emv/emv.h>
|
||||
#include <nfc/protocols/slix/slix_device_defs.h>
|
||||
@ -40,6 +41,7 @@ const NfcDeviceBase* nfc_devices[NfcProtocolNum] = {
|
||||
[NfcProtocolFelica] = &nfc_device_felica,
|
||||
[NfcProtocolMfUltralight] = &nfc_device_mf_ultralight,
|
||||
[NfcProtocolMfClassic] = &nfc_device_mf_classic,
|
||||
[NfcProtocolMfPlus] = &nfc_device_mf_plus,
|
||||
[NfcProtocolMfDesfire] = &nfc_device_mf_desfire,
|
||||
[NfcProtocolSlix] = &nfc_device_slix,
|
||||
[NfcProtocolSt25tb] = &nfc_device_st25tb,
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include <nfc/protocols/felica/felica_poller_defs.h>
|
||||
#include <nfc/protocols/mf_ultralight/mf_ultralight_poller_defs.h>
|
||||
#include <nfc/protocols/mf_classic/mf_classic_poller_defs.h>
|
||||
#include <nfc/protocols/mf_plus/mf_plus_poller_defs.h>
|
||||
#include <nfc/protocols/mf_desfire/mf_desfire_poller_defs.h>
|
||||
#include <nfc/protocols/emv/emv_poller_defs.h>
|
||||
#include <nfc/protocols/slix/slix_poller_defs.h>
|
||||
@ -22,6 +23,7 @@ const NfcPollerBase* nfc_pollers_api[NfcProtocolNum] = {
|
||||
[NfcProtocolFelica] = &nfc_poller_felica,
|
||||
[NfcProtocolMfUltralight] = &mf_ultralight_poller,
|
||||
[NfcProtocolMfClassic] = &mf_classic_poller,
|
||||
[NfcProtocolMfPlus] = &mf_plus_poller,
|
||||
[NfcProtocolMfDesfire] = &mf_desfire_poller,
|
||||
[NfcProtocolSlix] = &nfc_poller_slix,
|
||||
[NfcProtocolSt25tb] = &nfc_poller_st25tb,
|
||||
|
@ -63,6 +63,7 @@ static const NfcProtocol nfc_protocol_iso14443_3b_children_protocol[] = {
|
||||
/** List of ISO14443-4A child protocols. */
|
||||
static const NfcProtocol nfc_protocol_iso14443_4a_children_protocol[] = {
|
||||
NfcProtocolMfDesfire,
|
||||
NfcProtocolMfPlus,
|
||||
NfcProtocolEmv,
|
||||
};
|
||||
|
||||
@ -131,6 +132,12 @@ static const NfcProtocolTreeNode nfc_protocol_nodes[NfcProtocolNum] = {
|
||||
.children_num = 0,
|
||||
.children_protocol = NULL,
|
||||
},
|
||||
[NfcProtocolMfPlus] =
|
||||
{
|
||||
.parent_protocol = NfcProtocolIso14443_4a,
|
||||
.children_num = 0,
|
||||
.children_protocol = NULL,
|
||||
},
|
||||
[NfcProtocolMfDesfire] =
|
||||
{
|
||||
.parent_protocol = NfcProtocolIso14443_4a,
|
||||
|
@ -184,6 +184,7 @@ typedef enum {
|
||||
NfcProtocolFelica,
|
||||
NfcProtocolMfUltralight,
|
||||
NfcProtocolMfClassic,
|
||||
NfcProtocolMfPlus,
|
||||
NfcProtocolMfDesfire,
|
||||
NfcProtocolSlix,
|
||||
NfcProtocolSt25tb,
|
||||
|
@ -428,6 +428,9 @@ static bool
|
||||
(strcmp(instance->manufacture_name, "Dea_Mio") == 0)) {
|
||||
klq_last_custom_btn = 0xF;
|
||||
}
|
||||
if((strcmp(instance->manufacture_name, "FAAC_RC,XT") == 0)) {
|
||||
klq_last_custom_btn = 0xB;
|
||||
}
|
||||
|
||||
btn = subghz_protocol_keeloq_get_btn_code(klq_last_custom_btn);
|
||||
|
||||
|
@ -28,8 +28,7 @@ void pretty_format_bytes_hex_canonical(
|
||||
const size_t line_length = (line_prefix ? strlen(line_prefix) : 0) + 4 * num_places + 2;
|
||||
|
||||
/* Reserve memory in adance in order to avoid unnecessary reallocs */
|
||||
furi_string_reset(result);
|
||||
furi_string_reserve(result, line_count * line_length);
|
||||
furi_string_reserve(result, furi_string_size(result) + line_count * line_length);
|
||||
|
||||
for(size_t i = 0; i < data_size; i += num_places) {
|
||||
if(line_prefix) {
|
||||
|
@ -198,12 +198,21 @@ LevelDuration protocol_dict_encoder_yield(ProtocolDict* dict, size_t protocol_in
|
||||
}
|
||||
}
|
||||
|
||||
void protocol_dict_render_uid(ProtocolDict* dict, FuriString* result, size_t protocol_index) {
|
||||
furi_check(protocol_index < dict->count);
|
||||
ProtocolRenderData fn = dict->base[protocol_index]->render_uid;
|
||||
|
||||
if(fn) {
|
||||
fn(dict->data[protocol_index], result);
|
||||
}
|
||||
}
|
||||
|
||||
void protocol_dict_render_data(ProtocolDict* dict, FuriString* result, size_t protocol_index) {
|
||||
furi_check(protocol_index < dict->count);
|
||||
ProtocolRenderData fn = dict->base[protocol_index]->render_data;
|
||||
|
||||
if(fn) {
|
||||
return fn(dict->data[protocol_index], result);
|
||||
fn(dict->data[protocol_index], result);
|
||||
}
|
||||
}
|
||||
|
||||
@ -212,7 +221,7 @@ void protocol_dict_render_brief_data(ProtocolDict* dict, FuriString* result, siz
|
||||
ProtocolRenderData fn = dict->base[protocol_index]->render_brief_data;
|
||||
|
||||
if(fn) {
|
||||
return fn(dict->data[protocol_index], result);
|
||||
fn(dict->data[protocol_index], result);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -58,6 +58,8 @@ bool protocol_dict_encoder_start(ProtocolDict* dict, size_t protocol_index);
|
||||
|
||||
LevelDuration protocol_dict_encoder_yield(ProtocolDict* dict, size_t protocol_index);
|
||||
|
||||
void protocol_dict_render_uid(ProtocolDict* dict, FuriString* result, size_t protocol_index);
|
||||
|
||||
void protocol_dict_render_data(ProtocolDict* dict, FuriString* result, size_t protocol_index);
|
||||
|
||||
void protocol_dict_render_brief_data(ProtocolDict* dict, FuriString* result, size_t protocol_index);
|
||||
|
@ -1,5 +1,5 @@
|
||||
entry,status,name,type,params
|
||||
Version,+,61.1,,
|
||||
Version,+,61.2,,
|
||||
Header,+,applications/services/bt/bt_service/bt.h,,
|
||||
Header,+,applications/services/cli/cli.h,,
|
||||
Header,+,applications/services/cli/cli_vcp.h,,
|
||||
@ -2251,6 +2251,7 @@ Function,+,protocol_dict_get_validate_count,uint32_t,"ProtocolDict*, size_t"
|
||||
Function,+,protocol_dict_get_write_data,_Bool,"ProtocolDict*, size_t, void*"
|
||||
Function,+,protocol_dict_render_brief_data,void,"ProtocolDict*, FuriString*, size_t"
|
||||
Function,+,protocol_dict_render_data,void,"ProtocolDict*, FuriString*, size_t"
|
||||
Function,+,protocol_dict_render_uid,void,"ProtocolDict*, FuriString*, size_t"
|
||||
Function,+,protocol_dict_set_data,void,"ProtocolDict*, size_t, const uint8_t*, size_t"
|
||||
Function,-,pulse_reader_alloc,PulseReader*,"const GpioPin*, uint32_t"
|
||||
Function,-,pulse_reader_free,void,PulseReader*
|
||||
|
|
@ -1,5 +1,5 @@
|
||||
entry,status,name,type,params
|
||||
Version,+,61.1,,
|
||||
Version,+,61.2,,
|
||||
Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,,
|
||||
Header,+,applications/services/bt/bt_service/bt.h,,
|
||||
Header,+,applications/services/cli/cli.h,,
|
||||
@ -154,6 +154,8 @@ Header,+,lib/nfc/protocols/mf_classic/mf_classic_poller.h,,
|
||||
Header,+,lib/nfc/protocols/mf_classic/mf_classic_poller_sync.h,,
|
||||
Header,+,lib/nfc/protocols/mf_desfire/mf_desfire.h,,
|
||||
Header,+,lib/nfc/protocols/mf_desfire/mf_desfire_poller.h,,
|
||||
Header,+,lib/nfc/protocols/mf_plus/mf_plus.h,,
|
||||
Header,+,lib/nfc/protocols/mf_plus/mf_plus_poller.h,,
|
||||
Header,+,lib/nfc/protocols/mf_ultralight/mf_ultralight.h,,
|
||||
Header,+,lib/nfc/protocols/mf_ultralight/mf_ultralight_listener.h,,
|
||||
Header,+,lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.h,,
|
||||
@ -2546,6 +2548,19 @@ Function,+,mf_desfire_save,_Bool,"const MfDesfireData*, FlipperFormat*"
|
||||
Function,+,mf_desfire_send_chunks,MfDesfireError,"MfDesfirePoller*, const BitBuffer*, BitBuffer*"
|
||||
Function,+,mf_desfire_set_uid,_Bool,"MfDesfireData*, const uint8_t*, size_t"
|
||||
Function,+,mf_desfire_verify,_Bool,"MfDesfireData*, const FuriString*"
|
||||
Function,+,mf_plus_alloc,MfPlusData*,
|
||||
Function,+,mf_plus_copy,void,"MfPlusData*, const MfPlusData*"
|
||||
Function,+,mf_plus_free,void,MfPlusData*
|
||||
Function,+,mf_plus_get_base_data,Iso14443_4aData*,const MfPlusData*
|
||||
Function,+,mf_plus_get_device_name,const char*,"const MfPlusData*, NfcDeviceNameType"
|
||||
Function,+,mf_plus_get_uid,const uint8_t*,"const MfPlusData*, size_t*"
|
||||
Function,+,mf_plus_is_equal,_Bool,"const MfPlusData*, const MfPlusData*"
|
||||
Function,+,mf_plus_load,_Bool,"MfPlusData*, FlipperFormat*, uint32_t"
|
||||
Function,+,mf_plus_poller_read_version,MfPlusError,"MfPlusPoller*, MfPlusVersion*"
|
||||
Function,+,mf_plus_reset,void,MfPlusData*
|
||||
Function,+,mf_plus_save,_Bool,"const MfPlusData*, FlipperFormat*"
|
||||
Function,+,mf_plus_set_uid,_Bool,"MfPlusData*, const uint8_t*, size_t"
|
||||
Function,+,mf_plus_verify,_Bool,"MfPlusData*, const FuriString*"
|
||||
Function,+,mf_ultralight_alloc,MfUltralightData*,
|
||||
Function,+,mf_ultralight_copy,void,"MfUltralightData*, const MfUltralightData*"
|
||||
Function,+,mf_ultralight_detect_protocol,_Bool,const Iso14443_3aData*
|
||||
@ -2898,6 +2913,7 @@ Function,+,protocol_dict_get_validate_count,uint32_t,"ProtocolDict*, size_t"
|
||||
Function,+,protocol_dict_get_write_data,_Bool,"ProtocolDict*, size_t, void*"
|
||||
Function,+,protocol_dict_render_brief_data,void,"ProtocolDict*, FuriString*, size_t"
|
||||
Function,+,protocol_dict_render_data,void,"ProtocolDict*, FuriString*, size_t"
|
||||
Function,+,protocol_dict_render_uid,void,"ProtocolDict*, FuriString*, size_t"
|
||||
Function,+,protocol_dict_set_data,void,"ProtocolDict*, size_t, const uint8_t*, size_t"
|
||||
Function,-,pulse_reader_alloc,PulseReader*,"const GpioPin*, uint32_t"
|
||||
Function,-,pulse_reader_free,void,PulseReader*
|
||||
@ -3858,6 +3874,7 @@ Variable,-,nfc_device_emv,const NfcDeviceBase,
|
||||
Variable,-,nfc_device_felica,const NfcDeviceBase,
|
||||
Variable,-,nfc_device_mf_classic,const NfcDeviceBase,
|
||||
Variable,-,nfc_device_mf_desfire,const NfcDeviceBase,
|
||||
Variable,-,nfc_device_mf_plus,const NfcDeviceBase,
|
||||
Variable,-,nfc_device_mf_ultralight,const NfcDeviceBase,
|
||||
Variable,-,nfc_device_st25tb,const NfcDeviceBase,
|
||||
Variable,+,sequence_audiovisual_alert,const NotificationSequence,
|
||||
|
|
Loading…
Reference in New Issue
Block a user