Merge branch 'dev' into feature/random_mouse_jiggler

This commit is contained in:
MX 2024-05-03 21:09:21 +03:00
commit 786dbfe0c3
No known key found for this signature in database
GPG key ID: 7CCC66B7DBDD1C83
89 changed files with 3250 additions and 424 deletions

View file

@ -1,19 +1,37 @@
## New changes
* NFC: Temp fix for `iso14443_4_layer_decode_block` crash
* NFC: CharlieCard parser (by @zacharyweiss)
* SubGHz: FAAC RC XT - add 0xB button code on arrow buttons for programming mode
* SubGHz: Add Manually - Sommer FM fixes
* SubGHz: Enabled tx-rx state on unused gpio pin by default (**external amp option was removed and is enabled by default now**)
* SubGHz: **Status output !TX/RX on the GDO2 CC1101 pin** (by @quen0n | PR #742)
* SubGHz: Reworked saved settings (by @xMasterX and @Willy-JL)
* Desktop: Fixes for animation unload (by @Willy-JL)
* iButton: Updated DS1420 for latest ibutton changes
* Misc: Allow no prefix usage of name_generator_make_detailed_datetime
* Misc: Allow setting view dispatcher callbacks to NULL
* Misc: Added `void` due to `-Wstrict-prototypes`
* Misc: Some code cleanup and proper log levels in nfc parsers
* Infrared: Allow external apps to use infrared settings (by @Willy-JL)
* JS & HAL: Various fixes and FURI_HAL_RANDOM_MAX define added (by @Willy-JL)
* JS: **BadUSB layout support** (by @Willy-JL)
* JS: Module `widget` and path globals (by @jamisonderek)
* JS: New Modules `widget`, `vgm` and path globals (by @jamisonderek)
* Apps: NFC Magic - **Gen2 writing support, Gen4 NTAG password and PACK fixes** (by @Astrrra)
* Apps: MFKey - **fixed crashes** (by @noproto)
* Apps: MFKey - **fixed crashes**, add more free ram (by @noproto & @Willy-JL)
* Apps: **Check out Apps updates by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev)
* OFW PR 3616: NFC: Mf Desfire fix reading big files (by gornekich)
* OFW: iButton: fix crash when deleting some keys
* OFW: Desktop: cleanup error popups
* OFW: Troika parser visual fixes
* OFW: Fix the retry/exit confirmation prompts in iButton
* OFW: nfc app: add legacy keys for plantain cards
* OFW: GUI: Fix array out of bounds in menu exit
* OFW: add support for S(WTX) request in iso14443_4a_poller
* OFW: Mosgortrans parser output fixes
* OFW: BLE: Add GapPairingNone support
* OFW: iButton new UI
* OFW: FuriHal: add ADC API
* OFW: Mf Desfire multiple file rights support
* OFW: **Felica poller** (NFC-F)
* OFW: Desktop/Loader: Unload animations before loading FAPs
* OFW: JS Documentation
@ -49,8 +67,7 @@
#### Known NFC post-refactor regressions list:
- Mifare Mini clones reading is broken (original mini working fine) (OFW)
- NFC CLI was removed with refactoring (OFW) (will be back soon)
- Current list of affected apps: https://github.com/xMasterX/all-the-plugins/tree/dev/apps_broken_by_last_refactors
- Also in app **Enhanced Sub-GHz Chat** - NFC part was temporarily removed to make app usable, NFC part of the app requires remaking it with new nfc stack
- Mifare Nested not ported to latest API yet, `unlshd-065` is the latest version on old NFC API that works with "nested app"
----

View file

@ -7,10 +7,13 @@
#include <nfc/nfc_poller.h>
#include <nfc/nfc_listener.h>
#include <nfc/protocols/iso14443_3a/iso14443_3a.h>
#include <nfc/protocols/iso14443_3a/iso14443_3a_poller.h>
#include <nfc/protocols/iso14443_3a/iso14443_3a_poller_sync.h>
#include <nfc/protocols/mf_ultralight/mf_ultralight.h>
#include <nfc/protocols/mf_ultralight/mf_ultralight_poller_sync.h>
#include <nfc/protocols/mf_classic/mf_classic_poller_sync.h>
#include <nfc/protocols/mf_classic/mf_classic_poller.h>
#include <nfc/nfc_poller.h>
#include <toolbox/keys_dict.h>
#include <nfc/nfc.h>
@ -22,6 +25,23 @@
#define NFC_TEST_NFC_DEV_PATH EXT_PATH("unit_tests/nfc/nfc_device_test.nfc")
#define NFC_APP_MF_CLASSIC_DICT_UNIT_TEST_PATH EXT_PATH("unit_tests/mf_dict.nfc")
#define NFC_TEST_FLAG_WORKER_DONE (1)
typedef enum {
NfcTestMfClassicSendFrameTestStateAuth,
NfcTestMfClassicSendFrameTestStateReadBlock,
NfcTestMfClassicSendFrameTestStateFail,
NfcTestMfClassicSendFrameTestStateSuccess,
} NfcTestMfClassicSendFrameTestState;
typedef struct {
NfcTestMfClassicSendFrameTestState state;
BitBuffer* tx_buf;
BitBuffer* rx_buf;
FuriThreadId thread_id;
} NfcTestMfClassicSendFrameTest;
typedef struct {
Storage* storage;
} NfcTest;
@ -435,6 +455,109 @@ static void mf_classic_value_block(void) {
nfc_free(poller);
}
NfcCommand mf_classic_poller_send_frame_callback(NfcGenericEventEx event, void* context) {
furi_check(event.poller);
furi_check(event.parent_event_data);
furi_check(context);
NfcCommand command = NfcCommandContinue;
MfClassicPoller* instance = event.poller;
NfcTestMfClassicSendFrameTest* frame_test = context;
Iso14443_3aPollerEvent* iso3_event = event.parent_event_data;
MfClassicError error = MfClassicErrorNone;
if(iso3_event->type == Iso14443_3aPollerEventTypeReady) {
if(frame_test->state == NfcTestMfClassicSendFrameTestStateAuth) {
MfClassicKey key = {
.data = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
};
error = mf_classic_poller_auth(instance, 0, &key, MfClassicKeyTypeA, NULL);
frame_test->state = (error == MfClassicErrorNone) ?
NfcTestMfClassicSendFrameTestStateReadBlock :
NfcTestMfClassicSendFrameTestStateFail;
} else if(frame_test->state == NfcTestMfClassicSendFrameTestStateReadBlock) {
do {
const uint8_t read_block_cmd[] = {
0x30,
0x01,
0x8b,
0xb9,
};
bit_buffer_copy_bytes(frame_test->tx_buf, read_block_cmd, sizeof(read_block_cmd));
error = mf_classic_poller_send_encrypted_frame(
instance, frame_test->tx_buf, frame_test->rx_buf, 200000);
if(error != MfClassicErrorNone) break;
if(bit_buffer_get_size_bytes(frame_test->rx_buf) != 18) {
error = MfClassicErrorProtocol;
break;
}
const uint8_t* rx_data = bit_buffer_get_data(frame_test->rx_buf);
const uint8_t rx_data_ref[16] = {0};
if(memcmp(rx_data, rx_data_ref, sizeof(rx_data_ref)) != 0) {
error = MfClassicErrorProtocol;
break;
}
} while(false);
frame_test->state = (error == MfClassicErrorNone) ?
NfcTestMfClassicSendFrameTestStateSuccess :
NfcTestMfClassicSendFrameTestStateFail;
} else if(frame_test->state == NfcTestMfClassicSendFrameTestStateSuccess) {
command = NfcCommandStop;
} else if(frame_test->state == NfcTestMfClassicSendFrameTestStateFail) {
command = NfcCommandStop;
}
} else {
frame_test->state = NfcTestMfClassicSendFrameTestStateFail;
command = NfcCommandStop;
}
if(command == NfcCommandStop) {
furi_thread_flags_set(frame_test->thread_id, NFC_TEST_FLAG_WORKER_DONE);
}
return command;
}
MU_TEST(mf_classic_send_frame_test) {
Nfc* poller = nfc_alloc();
Nfc* listener = nfc_alloc();
NfcDevice* nfc_device = nfc_device_alloc();
nfc_data_generator_fill_data(NfcDataGeneratorTypeMfClassic4k_7b, nfc_device);
NfcListener* mfc_listener = nfc_listener_alloc(
listener, NfcProtocolMfClassic, nfc_device_get_data(nfc_device, NfcProtocolMfClassic));
nfc_listener_start(mfc_listener, NULL, NULL);
NfcPoller* mfc_poller = nfc_poller_alloc(poller, NfcProtocolMfClassic);
NfcTestMfClassicSendFrameTest context = {
.state = NfcTestMfClassicSendFrameTestStateAuth,
.thread_id = furi_thread_get_current_id(),
.tx_buf = bit_buffer_alloc(32),
.rx_buf = bit_buffer_alloc(32),
};
nfc_poller_start_ex(mfc_poller, mf_classic_poller_send_frame_callback, &context);
uint32_t flag =
furi_thread_flags_wait(NFC_TEST_FLAG_WORKER_DONE, FuriFlagWaitAny, FuriWaitForever);
mu_assert(flag == NFC_TEST_FLAG_WORKER_DONE, "Wrong thread flag");
nfc_poller_stop(mfc_poller);
nfc_poller_free(mfc_poller);
mu_assert(
context.state == NfcTestMfClassicSendFrameTestStateSuccess, "Wrong test state at the end");
bit_buffer_free(context.tx_buf);
bit_buffer_free(context.rx_buf);
nfc_listener_stop(mfc_listener);
nfc_listener_free(mfc_listener);
nfc_device_free(nfc_device);
nfc_free(listener);
nfc_free(poller);
}
MU_TEST(mf_classic_dict_test) {
Storage* storage = furi_record_open(RECORD_STORAGE);
if(storage_common_stat(storage, NFC_APP_MF_CLASSIC_DICT_UNIT_TEST_PATH, NULL) == FSE_OK) {
@ -538,11 +661,11 @@ MU_TEST_SUITE(nfc) {
MU_RUN_TEST(mf_classic_1k_7b_file_test);
MU_RUN_TEST(mf_classic_4k_4b_file_test);
MU_RUN_TEST(mf_classic_4k_7b_file_test);
MU_RUN_TEST(mf_classic_reader);
MU_RUN_TEST(mf_classic_reader);
MU_RUN_TEST(mf_classic_write);
MU_RUN_TEST(mf_classic_value_block);
MU_RUN_TEST(mf_classic_send_frame_test);
MU_RUN_TEST(mf_classic_dict_test);
nfc_test_free();

View file

@ -0,0 +1,9 @@
App(
appid="example_adc",
name="Example: ADC",
apptype=FlipperAppType.EXTERNAL,
entry_point="example_adc_main",
requires=["gui"],
stack_size=1 * 1024,
fap_category="Examples",
)

View file

@ -0,0 +1,176 @@
/**
* @file example_adc.c
* @brief ADC example.
*/
#include <furi.h>
#include <furi_hal.h>
#include <gui/gui.h>
#include <gui/elements.h>
#include <input/input.h>
const uint8_t font[] =
"`\2\3\2\3\4\1\2\4\5\11\0\376\6\376\7\377\1M\2\263\3\370 \6\315\364\371\6!\12\315"
"\364\201\260\35\312Q\0\42\11\315tJI\316\13\0#\14\315\264\223dP*\203R'\1$\15\315\264"
"\262A\311\266D\251l\71\0%\15\315\264\7%\61)J\42\345 \0&\14\315\264\263$\13\223\266$"
"\7\1'\10\315\364\201\60\347\10(\10\315\364\32[\313\0)\11\315\64\322b[\35\2*\12\315\264\263"
"(\222j\71\15+\11\315\364I\331\226\23\1,\10\315\364\271\205Y\10-\10\315\364\31t\26\0.\10"
"\315\364\71\346(\0/\14\315\364\221\60\13\263\60\13C\0\60\13\315\264\245Jb)E:\12\61\12\315"
"\364\201Ll\333A\0\62\12\315\264\245bV\33r\20\63\13\315\264\245Z\232D\221\216\2\64\14\315\364"
"\201LJ\242!\313v\20\65\14\315t\207$\134\223(\322Q\0\66\13\315\264\245p\252D\221\216\2\67"
"\12\315t\207\60+\326a\0\70\13\315\264\245\222T\211\42\35\5\71\13\315\264\245J\24\215\221\216\2:"
"\11\315\364i\71!G\1;\12\315\364I\71!\314B\0<\11\315\364\341\254Z\7\1=\12\315\364)"
"C<\344$\0>\11\315\364\301\264V\207\1\77\12\315\264\245Z\35\312a\0@\14\315\264\245J\242$"
"J\272\203\0A\15\315\264\245J\224\14I\224D\71\10B\13\315t\247\312T\211\222\35\5C\12\315\264"
"\245JX\212t\24D\15\315t\247J\224DI\224\354(\0E\14\315t\207$\234\302p\310A\0F"
"\12\315t\207$\234\302:\1G\14\315\264\245J\230(Q\244\243\0H\17\315t\243$J\206$J\242"
"$\312A\0I\11\315\264\267\260m\7\1J\12\315\364\221\260%\212t\24K\14\315t\243\244\244iI"
"T\7\1L\11\315t\303\216C\16\2M\17\315t\243dH\206$J\242$\312A\0N\16\315t\243"
"D\251(Q\22%Q\16\2O\15\315\264\245J\224DI\24\351(\0P\12\315t\247J\224LaN"
"Q\15\315\264\245J\224DI\42\251\61\0R\14\315t\247J\224L\225(\7\1S\13\315\264\245\222\232"
"D\221\216\2T\10\315\264\267\260;\12U\16\315t\243$J\242$J\242HG\1V\15\315t\243$"
"J\242$Jj\71\14W\17\315t\243$J\242dH\206$\312A\0X\15\315t\243$\212\64\251\22"
"\345 \0Y\13\315t\243$Jja\35\6Z\12\315t\207\60k\34r\20[\10\315\264\264\260G\31"
"\134\12\315\264\303\64L\303\64\14]\10\315t\304\276\351\0^\11\315\364\201,\311\271\1_\7\315\364y"
"\35\4`\10\315t\322\234'\0a\14\315\364IK\224$R\222\203\0b\13\315t\303p\252D\311\216"
"\2c\12\315\364IR%\335A\0d\14\315\364\221\60Z\242$\212v\20e\12\315\364I\322\220\244;"
"\10f\12\315\364\221,\333\302:\12g\14\315\364IK\224D\321\30I\0h\14\315t\303p\252DI"
"\224\203\0i\12\315\364\201\34\21k;\10j\12\315\364\201\34\21\273e\0k\13\315t\303J\244%Q"
"\35\4l\10\315\264\305n;\10m\14\315\364)CRQ\22\245\216\1n\13\315\364)%\245\224D\71"
"\10o\12\315\364IR%\212t\24p\13\315\364)S%J\246\60\4q\13\315\364IK\224D\321X"
"\1r\11\315\364)%\245\230\23s\12\315\364I\313\232\354(\0t\13\315\364\201\60\333\302\64\7\1u"
"\15\315\364)Q\22%\211\224\344 \0v\13\315\364)Q\22%\265\34\6w\13\315\364)\25%Q\272"
"\203\0x\12\315\364)Q\244Iu\20y\15\315\364)Q\22%Q\64F\22\0z\12\315\364)CV"
"\33r\20{\12\315\364\212\265\64\254&\0|\7\315\264\302~\7}\12\315t\322\260\232\205\265\14~\11"
"\315\364II;\13\0\177\6\315\364\371\6\0\0\0\4\377\377\0";
#define FONT_HEIGHT (8u)
typedef float (*ValueConverter)(FuriHalAdcHandle* handle, uint16_t value);
typedef struct {
const GpioPinRecord* pin;
float value;
ValueConverter converter;
const char* suffix;
} DataItem;
typedef struct {
size_t count;
DataItem* items;
} Data;
const GpioPinRecord item_vref = {.name = "VREF", .channel = FuriHalAdcChannelVREFINT};
const GpioPinRecord item_temp = {.name = "TEMP", .channel = FuriHalAdcChannelTEMPSENSOR};
const GpioPinRecord item_vbat = {.name = "VBAT", .channel = FuriHalAdcChannelVBAT};
static void app_draw_callback(Canvas* canvas, void* ctx) {
furi_assert(ctx);
Data* data = ctx;
canvas_set_custom_u8g2_font(canvas, font);
char buffer[64];
int32_t x = 0, y = FONT_HEIGHT;
for(size_t i = 0; i < data->count; i++) {
if(i == canvas_height(canvas) / FONT_HEIGHT) {
x = 64;
y = FONT_HEIGHT;
}
snprintf(
buffer,
sizeof(buffer),
"%4s: %4.0f%s\n",
data->items[i].pin->name,
(double)data->items[i].value,
data->items[i].suffix);
canvas_draw_str(canvas, x, y, buffer);
y += FONT_HEIGHT;
}
}
static void app_input_callback(InputEvent* input_event, void* ctx) {
furi_assert(ctx);
FuriMessageQueue* event_queue = ctx;
furi_message_queue_put(event_queue, input_event, FuriWaitForever);
}
int32_t example_adc_main(void* p) {
UNUSED(p);
// Data
Data data = {};
for(size_t i = 0; i < gpio_pins_count; i++) {
if(gpio_pins[i].channel != FuriHalAdcChannelNone) {
data.count++;
}
}
data.count += 3; // Special channels
data.items = malloc(data.count * sizeof(DataItem));
size_t item_pos = 0;
for(size_t i = 0; i < gpio_pins_count; i++) {
if(gpio_pins[i].channel != FuriHalAdcChannelNone) {
furi_hal_gpio_init(gpio_pins[i].pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
data.items[item_pos].pin = &gpio_pins[i];
data.items[item_pos].converter = furi_hal_adc_convert_to_voltage;
data.items[item_pos].suffix = "mV";
item_pos++;
}
}
data.items[item_pos].pin = &item_vref;
data.items[item_pos].converter = furi_hal_adc_convert_vref;
data.items[item_pos].suffix = "mV";
item_pos++;
data.items[item_pos].pin = &item_temp;
data.items[item_pos].converter = furi_hal_adc_convert_temp;
data.items[item_pos].suffix = "C";
item_pos++;
data.items[item_pos].pin = &item_vbat;
data.items[item_pos].converter = furi_hal_adc_convert_vbat;
data.items[item_pos].suffix = "mV";
item_pos++;
furi_assert(item_pos == data.count);
// Alloc message queue
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
// Configure view port
ViewPort* view_port = view_port_alloc();
view_port_draw_callback_set(view_port, app_draw_callback, &data);
view_port_input_callback_set(view_port, app_input_callback, event_queue);
// Register view port in GUI
Gui* gui = furi_record_open(RECORD_GUI);
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
// Initialize ADC
FuriHalAdcHandle* adc_handle = furi_hal_adc_acquire();
furi_hal_adc_configure(adc_handle);
// Process events
InputEvent event;
bool running = true;
while(running) {
if(furi_message_queue_get(event_queue, &event, 100) == FuriStatusOk) {
if(event.type == InputTypePress && event.key == InputKeyBack) {
running = false;
}
} else {
for(size_t i = 0; i < data.count; i++) {
data.items[i].value = data.items[i].converter(
adc_handle, furi_hal_adc_read(adc_handle, data.items[i].pin->channel));
}
view_port_update(view_port);
}
}
furi_hal_adc_release(adc_handle);
view_port_enabled_set(view_port, false);
gui_remove_view_port(gui, view_port);
view_port_free(view_port);
furi_message_queue_free(event_queue);
furi_record_close(RECORD_GUI);
free(data.items);
return 0;
}

View file

@ -94,7 +94,7 @@ int32_t example_custom_font_main(void* p) {
// Configure view port
ViewPort* view_port = view_port_alloc();
view_port_draw_callback_set(view_port, app_draw_callback, view_port);
view_port_draw_callback_set(view_port, app_draw_callback, NULL);
view_port_input_callback_set(view_port, app_input_callback, event_queue);
// Register view port in GUI

View file

@ -39,7 +39,7 @@ int32_t example_images_main(void* p) {
// Configure view port
ViewPort* view_port = view_port_alloc();
view_port_draw_callback_set(view_port, app_draw_callback, view_port);
view_port_draw_callback_set(view_port, app_draw_callback, NULL);
view_port_input_callback_set(view_port, app_input_callback, event_queue);
// Register view port in GUI

View file

@ -7,23 +7,40 @@ void ibutton_scene_delete_confirm_on_enter(void* context) {
Widget* widget = ibutton->widget;
FuriString* tmp = furi_string_alloc();
FuriString* uid = furi_string_alloc();
widget_add_button_element(widget, GuiButtonTypeLeft, "Back", ibutton_widget_callback, context);
widget_add_button_element(
widget, GuiButtonTypeRight, "Delete", ibutton_widget_callback, context);
furi_string_printf(tmp, "\e#Delete %s?\e#", ibutton->key_name);
furi_string_printf(tmp, "\e#Delete %s?\e#\n", ibutton->key_name);
ibutton_protocols_render_uid(ibutton->protocols, key, uid);
furi_string_cat(tmp, uid);
furi_string_push_back(tmp, '\n');
const char* protocol =
ibutton_protocols_get_name(ibutton->protocols, ibutton_key_get_protocol_id(key));
const char* manufacturer =
ibutton_protocols_get_manufacturer(ibutton->protocols, ibutton_key_get_protocol_id(key));
if(strcasecmp(protocol, manufacturer) != 0 && strcasecmp(manufacturer, "N/A") != 0) {
furi_string_cat_printf(tmp, "%s ", manufacturer);
}
furi_string_cat(tmp, protocol);
widget_add_text_box_element(
widget, 0, 0, 128, 23, AlignCenter, AlignCenter, furi_string_get_cstr(tmp), false);
widget, 0, 0, 128, 64, AlignCenter, AlignTop, furi_string_get_cstr(tmp), false);
furi_string_reset(tmp);
ibutton_protocols_render_brief_data(ibutton->protocols, key, tmp);
widget_add_string_multiline_element(
widget, 128 / 2, 24, AlignCenter, AlignTop, FontSecondary, furi_string_get_cstr(tmp));
furi_string_reset(uid);
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget);
furi_string_free(tmp);
furi_string_free(uid);
}
bool ibutton_scene_delete_confirm_on_event(void* context, SceneManagerEvent event) {

View file

@ -28,10 +28,10 @@ void ibutton_scene_emulate_on_enter(void* context) {
furi_string_empty(ibutton->file_path) ? "Unsaved Key" : ibutton->key_name);
widget_add_text_box_element(
widget, 52, 30, 75, 40, AlignCenter, AlignCenter, furi_string_get_cstr(tmp), true);
widget, 52, 24, 75, 40, AlignCenter, AlignTop, furi_string_get_cstr(tmp), true);
widget_add_string_multiline_element(
widget, 88, 5, AlignCenter, AlignTop, FontPrimary, "iButton\nemulating");
widget, 88, 10, AlignCenter, AlignTop, FontPrimary, "Emulating");
ibutton_worker_emulate_set_callback(ibutton->worker, ibutton_scene_emulate_callback, ibutton);
ibutton_worker_emulate_start(ibutton->worker, key);

View file

@ -8,19 +8,35 @@ void ibutton_scene_info_on_enter(void* context) {
const iButtonProtocolId protocol_id = ibutton_key_get_protocol_id(key);
FuriString* tmp = furi_string_alloc();
FuriString* keynumber = furi_string_alloc();
FuriString* brief_data = furi_string_alloc();
ibutton_protocols_render_brief_data(ibutton->protocols, key, keynumber);
if((strcmp(
ibutton_protocols_get_manufacturer(ibutton->protocols, protocol_id),
ibutton_protocols_get_name(ibutton->protocols, protocol_id)) != 0) &&
(strcmp(ibutton_protocols_get_manufacturer(ibutton->protocols, protocol_id), "N/A") != 0)) {
furi_string_printf(
tmp,
"Name:%s\n\e#%s %s\e#\n",
ibutton->key_name,
ibutton_protocols_get_manufacturer(ibutton->protocols, protocol_id),
ibutton_protocols_get_name(ibutton->protocols, protocol_id));
} else {
furi_string_printf(
tmp,
"Name:%s\n\e#%s\e#\n",
ibutton->key_name,
ibutton_protocols_get_name(ibutton->protocols, protocol_id));
}
furi_string_printf(
tmp,
"\e#%s\n[%s]\e#\n%s",
ibutton->key_name,
ibutton_protocols_get_name(ibutton->protocols, protocol_id),
furi_string_get_cstr(keynumber));
ibutton_protocols_render_brief_data(ibutton->protocols, key, brief_data);
furi_string_cat(tmp, brief_data);
widget_add_text_box_element(
widget, 0, 2, 128, 64, AlignLeft, AlignTop, furi_string_get_cstr(tmp), true);
widget, 0, 0, 128, 64, AlignLeft, AlignTop, furi_string_get_cstr(tmp), false);
furi_string_reset(tmp);
furi_string_reset(brief_data);
if(ibutton_protocols_get_features(ibutton->protocols, protocol_id) &
iButtonProtocolFeatureExtData) {
@ -30,7 +46,7 @@ void ibutton_scene_info_on_enter(void* context) {
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget);
furi_string_free(tmp);
furi_string_free(keynumber);
furi_string_free(brief_data);
}
bool ibutton_scene_info_on_event(void* context, SceneManagerEvent event) {

View file

@ -12,9 +12,9 @@ void ibutton_scene_read_on_enter(void* context) {
iButtonKey* key = ibutton->key;
iButtonWorker* worker = ibutton->worker;
popup_set_header(popup, "iButton", 95, 26, AlignCenter, AlignBottom);
popup_set_text(popup, "Apply key to\nFlipper's back", 95, 30, AlignCenter, AlignTop);
popup_set_icon(popup, 0, 5, &I_DolphinWait_61x59);
popup_set_header(popup, "Reading", 95, 26, AlignCenter, AlignBottom);
popup_set_text(popup, "Connect key\nwith pogo pins", 95, 30, AlignCenter, AlignTop);
popup_set_icon(popup, 0, 10, &I_DolphinWait_59x54);
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewPopup);

View file

@ -14,13 +14,10 @@ void ibutton_scene_read_error_on_enter(void* context) {
widget_add_button_element(
widget, GuiButtonTypeRight, "More", ibutton_widget_callback, context);
widget_add_string_element(
widget, 128 / 2, 2, AlignCenter, AlignTop, FontPrimary, "Read Error");
ibutton_protocols_render_error(ibutton->protocols, key, tmp);
widget_add_string_multiline_element(
widget, 128 / 2, 16, AlignCenter, AlignTop, FontSecondary, furi_string_get_cstr(tmp));
widget_add_text_box_element(
widget, 0, 0, 128, 48, AlignCenter, AlignTop, furi_string_get_cstr(tmp), false);
ibutton_notification_message(ibutton, iButtonNotificationMessageError);
ibutton_notification_message(ibutton, iButtonNotificationMessageRedOn);

View file

@ -30,19 +30,10 @@ void ibutton_scene_read_key_menu_on_enter(void* context) {
ibutton_scene_read_key_menu_submenu_callback,
ibutton);
if(features & iButtonProtocolFeatureExtData) {
submenu_add_item(
submenu,
"View Data",
SubmenuIndexViewData,
ibutton_scene_read_key_menu_submenu_callback,
ibutton);
}
if(features & iButtonProtocolFeatureWriteBlank) {
submenu_add_item(
submenu,
"Write Blank",
"Write ID",
SubmenuIndexWriteBlank,
ibutton_scene_read_key_menu_submenu_callback,
ibutton);
@ -51,12 +42,21 @@ void ibutton_scene_read_key_menu_on_enter(void* context) {
if(features & iButtonProtocolFeatureWriteCopy) {
submenu_add_item(
submenu,
"Write Copy",
"Full Write on Same Type",
SubmenuIndexWriteCopy,
ibutton_scene_read_key_menu_submenu_callback,
ibutton);
}
if(features & iButtonProtocolFeatureExtData) {
submenu_add_item(
submenu,
"Data Info",
SubmenuIndexViewData,
ibutton_scene_read_key_menu_submenu_callback,
ibutton);
}
submenu_set_selected_item(
submenu, scene_manager_get_scene_state(ibutton->scene_manager, iButtonSceneReadKeyMenu));
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewSubmenu);

View file

@ -18,9 +18,9 @@ void ibutton_scene_read_success_on_enter(void* context) {
furi_string_printf(
tmp,
"%s[%s]",
ibutton_protocols_get_name(ibutton->protocols, protocol_id),
ibutton_protocols_get_manufacturer(ibutton->protocols, protocol_id));
"%s %s",
ibutton_protocols_get_manufacturer(ibutton->protocols, protocol_id),
ibutton_protocols_get_name(ibutton->protocols, protocol_id));
widget_add_string_element(
widget, 0, 2, AlignLeft, AlignTop, FontPrimary, furi_string_get_cstr(tmp));

View file

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

View file

@ -21,12 +21,16 @@ void ibutton_scene_saved_key_menu_on_enter(void* context) {
if(features & iButtonProtocolFeatureWriteBlank) {
submenu_add_item(
submenu, "Write Blank", SubmenuIndexWriteBlank, ibutton_submenu_callback, ibutton);
submenu, "Write ID", SubmenuIndexWriteBlank, ibutton_submenu_callback, ibutton);
}
if(features & iButtonProtocolFeatureWriteCopy) {
submenu_add_item(
submenu, "Write Copy", SubmenuIndexWriteCopy, ibutton_submenu_callback, ibutton);
submenu,
"Full Write on Same Type",
SubmenuIndexWriteCopy,
ibutton_submenu_callback,
ibutton);
}
submenu_add_item(submenu, "Edit", SubmenuIndexEdit, ibutton_submenu_callback, ibutton);

View file

@ -42,23 +42,21 @@ void ibutton_scene_write_on_enter(void* context) {
furi_string_printf(
tmp,
"[%s]\n%s ",
"[%s]\n%s",
ibutton_protocols_get_name(ibutton->protocols, protocol_id),
ibutton->key_name);
furi_string_empty(ibutton->file_path) ? "Unsaved Key" : ibutton->key_name);
widget_add_text_box_element(
widget, 52, 30, 75, 40, AlignCenter, AlignCenter, furi_string_get_cstr(tmp), true);
widget, 52, 24, 75, 40, AlignCenter, AlignTop, furi_string_get_cstr(tmp), true);
ibutton_worker_write_set_callback(worker, ibutton_scene_write_callback, ibutton);
furi_string_set(tmp, "iButton\nwriting ");
if(ibutton->write_mode == iButtonWriteModeBlank) {
furi_string_cat(tmp, "Blank");
furi_string_set(tmp, "Writing ID");
ibutton_worker_write_blank_start(worker, key);
} else if(ibutton->write_mode == iButtonWriteModeCopy) {
furi_string_cat(tmp, "Copy");
furi_string_set(tmp, "Full Writing");
ibutton_worker_write_copy_start(worker, key);
}

View file

@ -6,16 +6,15 @@ void lfrfid_scene_emulate_on_enter(void* context) {
FuriString* display_text = furi_string_alloc_set("\e#Emulating\e#\n");
if(furi_string_empty(app->file_name)) {
furi_string_cat(display_text, "Unsaved\n");
furi_string_cat(display_text, protocol_dict_get_name(app->dict, app->protocol_id));
} else {
furi_string_cat(display_text, app->file_name);
}
furi_string_cat_printf(
display_text,
"[%s]\n%s",
protocol_dict_get_name(app->dict, app->protocol_id),
furi_string_empty(app->file_name) ? "Unsaved Tag" : furi_string_get_cstr(app->file_name));
widget_add_icon_element(widget, 0, 0, &I_NFC_dolphin_emulation_51x64);
widget_add_text_box_element(
widget, 55, 16, 67, 48, AlignCenter, AlignTop, furi_string_get_cstr(display_text), true);
widget, 51, 6, 79, 50, AlignCenter, AlignTop, furi_string_get_cstr(display_text), false);
furi_string_free(display_text);

View file

@ -25,12 +25,18 @@ void lfrfid_scene_write_on_enter(void* context) {
popup_set_header(popup, "Writing", 94, 16, AlignCenter, AlignTop);
if(!furi_string_empty(app->file_name)) {
popup_set_text(popup, furi_string_get_cstr(app->file_name), 94, 29, AlignCenter, AlignTop);
snprintf(
app->text_store,
LFRFID_TEXT_STORE_SIZE,
"[%s]\n%s",
protocol_dict_get_name(app->dict, app->protocol_id),
furi_string_get_cstr(app->file_name));
popup_set_text(popup, app->text_store, 94, 29, AlignCenter, AlignTop);
} else {
snprintf(
app->text_store,
LFRFID_TEXT_STORE_SIZE,
"Unsaved\n%s",
"[%s]\nUnsaved Tag",
protocol_dict_get_name(app->dict, app->protocol_id));
popup_set_text(popup, app->text_store, 94, 29, AlignCenter, AlignTop);
}

View file

@ -22,8 +22,14 @@ void lfrfid_scene_write_and_set_pass_on_enter(void* context) {
LfRfid* app = context;
Popup* popup = app->popup;
popup_set_header(popup, "Writing\nwith password", 89, 30, AlignCenter, AlignTop);
popup_set_icon(popup, 0, 3, &I_RFIDDolphinSend_97x61);
popup_set_header(popup, "Writing\nwith\npassword", 94, 8, AlignCenter, AlignTop);
popup_set_icon(popup, 0, 8, &I_NFC_manual_60x50);
snprintf(
app->text_store,
LFRFID_TEXT_STORE_SIZE,
"[%s]",
protocol_dict_get_name(app->dict, app->protocol_id));
popup_set_text(popup, app->text_store, 94, 45, AlignCenter, AlignTop);
view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidViewPopup);

View file

@ -505,8 +505,8 @@ bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString*
card_use_before_date_s.year);
if(data_block.valid_from_date == 0 || data_block.valid_to_date == 0) {
furi_string_cat(result, "\e#No ticket\n");
return true;
furi_string_cat(result, "\e#No ticket");
return false;
}
//remaining_trips
furi_string_cat_printf(result, "Trips: %d\n", data_block.total_trips);
@ -625,7 +625,7 @@ bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString*
data_block.valid_from_date + data_block.valid_for_days, &card_valid_to_date_s, 1992);
furi_string_cat_printf(
result,
"Valid to: %02d.%02d.%04d\n",
"Valid to: %02d.%02d.%04d",
card_valid_to_date_s.day,
card_valid_to_date_s.month,
card_valid_to_date_s.year);
@ -663,7 +663,7 @@ bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString*
2016);
furi_string_cat_printf(
result,
"Valid to: %02d.%02d.%04d\n",
"Valid to: %02d.%02d.%04d",
card_valid_to_date_s.day,
card_valid_to_date_s.month,
card_valid_to_date_s.year);
@ -676,7 +676,7 @@ bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString*
2016);
furi_string_cat_printf(
result,
"Trip from: %02d.%02d.%04d %02d:%02d\n",
"\nTrip from: %02d.%02d.%04d %02d:%02d",
card_start_trip_minutes_s.day,
card_start_trip_minutes_s.month,
card_start_trip_minutes_s.year,
@ -693,7 +693,7 @@ bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString*
2016);
furi_string_cat_printf(
result,
"Trip switch: %02d.%02d.%04d %02d:%02d\n",
"\nTrip switch: %02d.%02d.%04d %02d:%02d",
card_start_switch_trip_minutes_s.day,
card_start_switch_trip_minutes_s.month,
card_start_switch_trip_minutes_s.year,
@ -703,10 +703,10 @@ bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString*
//transport
FuriString* transport = furi_string_alloc();
parse_transport_type(&data_block, transport);
furi_string_cat_printf(result, "Transport: %s\n", furi_string_get_cstr(transport));
furi_string_cat_printf(result, "\nTransport: %s", furi_string_get_cstr(transport));
//validator
if(data_block.validator) {
furi_string_cat_printf(result, "Validator: %05d\n", data_block.validator);
furi_string_cat_printf(result, "\nValidator: %05d", data_block.validator);
}
furi_string_free(transport);
break;
@ -740,9 +740,9 @@ bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString*
card_valid_to_date_s.month,
card_valid_to_date_s.year);
//remaining_trips
furi_string_cat_printf(result, "Trips left: %d\n", data_block.remaining_trips);
furi_string_cat_printf(result, "Trips left: %d", data_block.remaining_trips);
//trip_from
if(data_block.start_trip_date) {
if(data_block.start_trip_date) { // TODO: (-nofl) unused
DateTime card_start_trip_minutes_s = {0};
from_minutes_to_datetime(
data_block.start_trip_date * 24 * 60 + data_block.start_trip_time,
@ -751,7 +751,7 @@ bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString*
}
//validator
if(data_block.validator) {
furi_string_cat_printf(result, "Validator: %05d", data_block.validator);
furi_string_cat_printf(result, "\nValidator: %05d", data_block.validator);
}
break;
}
@ -785,12 +785,12 @@ bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString*
data_block.valid_from_date + data_block.valid_for_days, &card_valid_to_date_s, 1992);
furi_string_cat_printf(
result,
"Valid to: %02d.%02d.%04d\n",
"Valid to: %02d.%02d.%04d",
card_valid_to_date_s.day,
card_valid_to_date_s.month,
card_valid_to_date_s.year);
//trip_from
if(data_block.start_trip_date) {
if(data_block.start_trip_date) { // TODO: (-nofl) unused
DateTime card_start_trip_minutes_s = {0};
from_minutes_to_datetime(
data_block.start_trip_date * 24 * 60 + data_block.start_trip_time,
@ -798,7 +798,7 @@ bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString*
1992);
}
//trip_switch
if(data_block.passage_5_minutes) {
if(data_block.passage_5_minutes) { // TODO: (-nofl) unused
DateTime card_start_switch_trip_minutes_s = {0};
from_minutes_to_datetime(
data_block.start_trip_date * 24 * 60 + data_block.start_trip_time +
@ -808,7 +808,7 @@ bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString*
}
//validator
if(data_block.validator) {
furi_string_cat_printf(result, "Validator: %05d", data_block.validator);
furi_string_cat_printf(result, "\nValidator: %05d", data_block.validator);
}
break;
}
@ -870,10 +870,10 @@ bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString*
furi_string_cat(transport, "");
break;
}
furi_string_cat_printf(result, "Transport: %s\n", furi_string_get_cstr(transport));
furi_string_cat_printf(result, "Transport: %s", furi_string_get_cstr(transport));
//validator
if(data_block.validator) {
furi_string_cat_printf(result, "Validator: %05d", data_block.validator);
furi_string_cat_printf(result, "\nValidator: %05d", data_block.validator);
}
furi_string_free(transport);
break;
@ -899,7 +899,7 @@ bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString*
from_days_to_datetime(data_block.valid_from_date, &card_valid_from_date_s, 1992);
furi_string_cat_printf(
result,
"Valid from: %02d.%02d.%04d\n",
"Valid from: %02d.%02d.%04d",
card_valid_from_date_s.day,
card_valid_from_date_s.month,
card_valid_from_date_s.year);
@ -912,7 +912,7 @@ bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString*
1992);
furi_string_cat_printf(
result,
"Valid to: %02d.%02d.%04d\n",
"\nValid to: %02d.%02d.%04d",
card_valid_to_date_s.day,
card_valid_to_date_s.month,
card_valid_to_date_s.year);
@ -924,7 +924,7 @@ bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString*
1992);
furi_string_cat_printf(
result,
"Valid to: %02d.%02d.%04d\n",
"\nValid to: %02d.%02d.%04d",
card_valid_to_date_s.day,
card_valid_to_date_s.month,
card_valid_to_date_s.year);
@ -939,7 +939,7 @@ bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString*
1992); //-time
furi_string_cat_printf(
result,
"Trip from: %02d.%02d.%04d %02d:%02d\n",
"\nTrip from: %02d.%02d.%04d %02d:%02d",
card_start_trip_minutes_s.day,
card_start_trip_minutes_s.month,
card_start_trip_minutes_s.year,
@ -956,7 +956,7 @@ bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString*
1992);
furi_string_cat_printf(
result,
"Trip switch: %02d.%02d.%04d %02d:%02d\n",
"\nTrip switch: %02d.%02d.%04d %02d:%02d",
card_start_switch_trip_minutes_s.day,
card_start_switch_trip_minutes_s.month,
card_start_switch_trip_minutes_s.year,
@ -965,7 +965,7 @@ bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString*
}
//transport
FuriString* transport = furi_string_alloc();
switch(data_block.transport_type) {
switch(data_block.transport_type) { // TODO: (-nofl) unused
case 1:
furi_string_cat(transport, "Metro");
break;
@ -981,7 +981,7 @@ bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString*
}
//validator
if(data_block.validator) {
furi_string_cat_printf(result, "Validator: %05d", data_block.validator);
furi_string_cat_printf(result, "\nValidator: %05d", data_block.validator);
}
furi_string_free(transport);
break;
@ -1094,14 +1094,14 @@ bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString*
data_block.valid_from_date * 24 * 60 + data_block.valid_for_minutes -
data_block.start_trip_neg_minutes,
&card_start_trip_minutes_s,
2016);
2016); // TODO: (-nofl) unused
//transport
FuriString* transport = furi_string_alloc();
parse_transport_type(&data_block, transport);
furi_string_cat_printf(result, "Transport: %s\n", furi_string_get_cstr(transport));
furi_string_cat_printf(result, "Transport: %s", furi_string_get_cstr(transport));
// validator
if(data_block.validator) {
furi_string_cat_printf(result, "Validator: %05d", data_block.validator);
furi_string_cat_printf(result, "\nValidator: %05d", data_block.validator);
}
furi_string_free(transport);
break;
@ -1121,7 +1121,7 @@ bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString*
card_use_before_date_s.month,
card_use_before_date_s.year);
//remaining_funds
furi_string_cat_printf(result, "Balance: %ld rub\n", data_block.remaining_funds / 100);
furi_string_cat_printf(result, "Balance: %ld rub", data_block.remaining_funds / 100);
//start_trip_minutes
if(data_block.start_trip_minutes) {
DateTime card_start_trip_minutes_s = {0};
@ -1129,7 +1129,7 @@ bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString*
data_block.start_trip_minutes, &card_start_trip_minutes_s, 2019);
furi_string_cat_printf(
result,
"Trip from: %02d.%02d.%04d %02d:%02d\n",
"\nTrip from: %02d.%02d.%04d %02d:%02d",
card_start_trip_minutes_s.day,
card_start_trip_minutes_s.month,
card_start_trip_minutes_s.year,
@ -1145,7 +1145,7 @@ bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString*
2019);
furi_string_cat_printf(
result,
"(M) from: %02d.%02d.%04d %02d:%02d\n",
"\n(M) from: %02d.%02d.%04d %02d:%02d",
card_start_m_trip_minutes_s.day,
card_start_m_trip_minutes_s.month,
card_start_m_trip_minutes_s.year,
@ -1160,7 +1160,7 @@ bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString*
2019);
furi_string_cat_printf(
result,
"Trip edit: %02d.%02d.%04d %02d:%02d\n",
"\nTrip edit: %02d.%02d.%04d %02d:%02d",
card_start_change_trip_minutes_s.day,
card_start_change_trip_minutes_s.month,
card_start_change_trip_minutes_s.year,
@ -1170,7 +1170,7 @@ bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString*
//transport
//validator
if(data_block.validator) {
furi_string_cat_printf(result, "Validator: %05d", data_block.validator);
furi_string_cat_printf(result, "\nValidator: %05d", data_block.validator);
}
break;
}
@ -1207,7 +1207,7 @@ bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString*
2019);
furi_string_cat_printf(
result,
"Valid to: %02d.%02d.%04d\n",
"Valid to: %02d.%02d.%04d",
card_use_to_date_s.day,
card_use_to_date_s.month,
card_use_to_date_s.year);
@ -1221,7 +1221,7 @@ bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString*
2019); //-time
furi_string_cat_printf(
result,
"Trip from: %02d.%02d.%04d %02d:%02d\n",
"\nTrip from: %02d.%02d.%04d %02d:%02d",
card_start_trip_minutes_s.day,
card_start_trip_minutes_s.month,
card_start_trip_minutes_s.year,
@ -1238,7 +1238,7 @@ bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString*
2019);
furi_string_cat_printf(
result,
"(M) from: %02d.%02d.%04d %02d:%02d\n",
"\n(M) from: %02d.%02d.%04d %02d:%02d",
card_start_trip_m_minutes_s.day,
card_start_trip_m_minutes_s.month,
card_start_trip_m_minutes_s.year,
@ -1248,7 +1248,7 @@ bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString*
//transport
//validator
if(data_block.validator) {
furi_string_cat_printf(result, "Validator: %05d", data_block.validator);
furi_string_cat_printf(result, "\nValidator: %05d", data_block.validator);
}
break;
}

View file

@ -34,6 +34,8 @@ static void nfc_scene_more_info_on_enter_mf_desfire(NfcApp* instance) {
static NfcCommand nfc_scene_read_poller_callback_mf_desfire(NfcGenericEvent event, void* context) {
furi_assert(event.protocol == NfcProtocolMfDesfire);
NfcCommand command = NfcCommandContinue;
NfcApp* instance = context;
const MfDesfirePollerEvent* mf_desfire_event = event.event_data;
@ -41,10 +43,12 @@ static NfcCommand nfc_scene_read_poller_callback_mf_desfire(NfcGenericEvent even
nfc_device_set_data(
instance->nfc_device, NfcProtocolMfDesfire, nfc_poller_get_data(instance->poller));
view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess);
return NfcCommandStop;
command = NfcCommandStop;
} else if(mf_desfire_event->type == MfDesfirePollerEventTypeReadFailed) {
command = NfcCommandReset;
}
return NfcCommandContinue;
return command;
}
static void nfc_scene_read_on_enter_mf_desfire(NfcApp* instance) {

View file

@ -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,
@ -11,24 +13,29 @@ void nfc_render_mf_desfire_info(
const uint32_t bytes_total = 1UL << (data->version.sw_storage >> 1);
const uint32_t bytes_free = data->free_memory.is_present ? data->free_memory.bytes_free : 0;
if(data->master_key_settings.is_free_directory_list) {
const uint32_t app_count = simple_array_get_count(data->applications);
uint32_t file_count = 0;
for(uint32_t i = 0; i < app_count; ++i) {
const MfDesfireApplication* app = simple_array_cget(data->applications, i);
if(app->key_settings.is_free_directory_list) {
file_count += simple_array_get_count(app->file_ids);
}
}
furi_string_cat_printf(str, "\n%lu Application%s", app_count, app_count != 1 ? "s" : "");
furi_string_cat_printf(str, ", %lu File%s", file_count, file_count != 1 ? "s" : "");
} else {
furi_string_cat_printf(str, "\nAuth required to read apps!");
}
furi_string_cat_printf(str, "\n%lu", bytes_total);
if(data->version.sw_storage & 1) {
furi_string_push_back(str, '+');
}
furi_string_cat_printf(str, " bytes, %lu bytes free\n", bytes_free);
const uint32_t app_count = simple_array_get_count(data->applications);
uint32_t file_count = 0;
for(uint32_t i = 0; i < app_count; ++i) {
const MfDesfireApplication* app = simple_array_cget(data->applications, i);
file_count += simple_array_get_count(app->file_ids);
}
furi_string_cat_printf(str, "%lu Application%s", app_count, app_count != 1 ? "s" : "");
furi_string_cat_printf(str, ", %lu File%s", file_count, file_count != 1 ? "s" : "");
furi_string_cat_printf(str, " bytes, %lu bytes free", bytes_free);
if(format_type != NfcProtocolFormatTypeFull) return;
@ -101,17 +108,29 @@ void nfc_render_mf_desfire_free_memory(const MfDesfireFreeMemory* data, FuriStri
}
void nfc_render_mf_desfire_key_settings(const MfDesfireKeySettings* data, FuriString* str) {
furi_string_cat_printf(str, "changeKeyID %d\n", data->change_key_id);
furi_string_cat_printf(str, "configChangeable %d\n", data->is_config_changeable);
furi_string_cat_printf(str, "freeCreateDelete %d\n", data->is_free_create_delete);
furi_string_cat_printf(str, "freeDirectoryList %d\n", data->is_free_directory_list);
furi_string_cat_printf(str, "masterChangeable %d\n", data->is_master_key_changeable);
if(data->is_free_directory_list) {
furi_string_cat_printf(str, "changeKeyID %d\n", data->change_key_id);
furi_string_cat_printf(str, "configChangeable %d\n", data->is_config_changeable);
furi_string_cat_printf(str, "freeCreateDelete %d\n", data->is_free_create_delete);
furi_string_cat_printf(str, "freeDirectoryList %d\n", data->is_free_directory_list);
furi_string_cat_printf(str, "masterChangeable %d\n", data->is_master_key_changeable);
} else {
furi_string_cat_printf(str, "changeKeyID ??\n");
furi_string_cat_printf(str, "configChangeable ??\n");
furi_string_cat_printf(str, "freeCreateDelete ??\n");
furi_string_cat_printf(str, "freeDirectoryList 0\n");
furi_string_cat_printf(str, "masterChangeable ??\n");
}
if(data->flags) {
furi_string_cat_printf(str, "flags %d\n", data->flags);
}
furi_string_cat_printf(str, "maxKeys %d\n", data->max_keys);
if(data->is_free_directory_list) {
furi_string_cat_printf(str, "maxKeys %d\n", data->max_keys);
} else {
furi_string_cat_printf(str, "maxKeys ??\n");
}
}
void nfc_render_mf_desfire_key_version(
@ -123,14 +142,16 @@ void nfc_render_mf_desfire_key_version(
void nfc_render_mf_desfire_application_id(const MfDesfireApplicationId* data, FuriString* str) {
const uint8_t* app_id = data->data;
furi_string_cat_printf(str, "Application %02x%02x%02x\n", app_id[0], app_id[1], app_id[2]);
furi_string_cat_printf(str, "Application %02x%02x%02x\n", app_id[2], app_id[1], app_id[0]);
}
void nfc_render_mf_desfire_application(const MfDesfireApplication* data, FuriString* str) {
nfc_render_mf_desfire_key_settings(&data->key_settings, str);
for(uint32_t i = 0; i < simple_array_get_count(data->key_versions); ++i) {
nfc_render_mf_desfire_key_version(simple_array_cget(data->key_versions, i), i, str);
if(data->key_settings.is_free_directory_list) {
for(uint32_t i = 0; i < simple_array_get_count(data->key_versions); ++i) {
nfc_render_mf_desfire_key_version(simple_array_cget(data->key_versions, i), i, str);
}
}
}
@ -179,19 +200,20 @@ void nfc_render_mf_desfire_file_settings_data(
}
furi_string_cat_printf(str, "%s %s\n", type, comm);
furi_string_cat_printf(
str,
"r %d w %d rw %d c %d\n",
settings->access_rights >> 12 & 0xF,
settings->access_rights >> 8 & 0xF,
settings->access_rights >> 4 & 0xF,
settings->access_rights & 0xF);
for(size_t i = 0; i < settings->access_rights_len; i++) {
furi_string_cat_printf(
str,
"r %d w %d rw %d c %d\n",
settings->access_rights[i] >> 12 & 0xF,
settings->access_rights[i] >> 8 & 0xF,
settings->access_rights[i] >> 4 & 0xF,
settings->access_rights[i] & 0xF);
}
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:
@ -217,21 +239,32 @@ void nfc_render_mf_desfire_file_settings_data(
break;
}
bool is_auth_required = true;
for(size_t i = 0; i < settings->access_rights_len; i++) {
uint8_t read_rights = (settings->access_rights[i] >> 12) & 0x0f;
uint8_t read_write_rights = (settings->access_rights[i] >> 4) & 0x0f;
if((read_rights == 0x0e) || (read_write_rights == 0x0e)) {
is_auth_required = false;
break;
}
}
if(is_auth_required) {
furi_string_cat_printf(str, "Auth required to read file data\n");
return;
}
if(simple_array_get_count(data->data) == 0) {
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);
@ -260,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');
}

View file

@ -59,6 +59,29 @@ static const MfClassicKeyPair plantain_4k_keys[] = {
{.a = 0xb27addfb64b0, .b = 0x152fd0c420a7}, {.a = 0x7259fa0197c6, .b = 0x5583698df085},
};
static const MfClassicKeyPair plantain_4k_keys_legacy[] = {
{.a = 0xffffffffffff, .b = 0xffffffffffff}, {.a = 0xffffffffffff, .b = 0xffffffffffff},
{.a = 0xffffffffffff, .b = 0xffffffffffff}, {.a = 0xffffffffffff, .b = 0xffffffffffff},
{.a = 0xe56ac127dd45, .b = 0x19fc84a3784b}, {.a = 0x77dabc9825e1, .b = 0x9764fec3154a},
{.a = 0xffffffffffff, .b = 0xffffffffffff}, {.a = 0xffffffffffff, .b = 0xffffffffffff},
{.a = 0x26973ea74321, .b = 0xd27058c6e2c7}, {.a = 0xeb0a8ff88ade, .b = 0x578a9ada41e3},
{.a = 0xea0fd73cb149, .b = 0x29c35fa068fb}, {.a = 0xc76bf71a2509, .b = 0x9ba241db3f56},
{.a = 0xacffffffffff, .b = 0x71f3a315ad26}, {.a = 0xffffffffffff, .b = 0xffffffffffff},
{.a = 0xffffffffffff, .b = 0xffffffffffff}, {.a = 0xffffffffffff, .b = 0xffffffffffff},
{.a = 0x72f96bdd3714, .b = 0x462225cd34cf}, {.a = 0x044ce1872bc3, .b = 0x8c90c70cff4a},
{.a = 0xbc2d1791dec1, .b = 0xca96a487de0b}, {.a = 0x8791b2ccb5c4, .b = 0xc956c3b80da3},
{.a = 0x8e26e45e7d65, .b = 0x8e65b3af7d22}, {.a = 0x0f318130ed18, .b = 0x0c420a20e056},
{.a = 0x045ceca15535, .b = 0x31bec3d9e510}, {.a = 0x9d993c5d4ef4, .b = 0x86120e488abf},
{.a = 0xc65d4eaa645b, .b = 0xb69d40d1a439}, {.a = 0x46d78e850a7e, .b = 0xa470f8130991},
{.a = 0x42e9b54e51ab, .b = 0x0231b86df52e}, {.a = 0x0f01ceff2742, .b = 0x6fec74559ca7},
{.a = 0xb81f2b0c2f66, .b = 0xa7e2d95f0003}, {.a = 0x9ea3387a63c1, .b = 0x437e59f57561},
{.a = 0x0eb23cc8110b, .b = 0x04dc35277635}, {.a = 0xbc4580b7f20b, .b = 0xd0a4131fb290},
{.a = 0x7a396f0d633d, .b = 0xad2bdc097023}, {.a = 0xa3faa6daff67, .b = 0x7600e889adf9},
{.a = 0xfd8705e721b0, .b = 0x296fc317a513}, {.a = 0x22052b480d11, .b = 0xe19504c39461},
{.a = 0xa7141147d430, .b = 0xff16014fefc7}, {.a = 0x8a8d88151a00, .b = 0x038b5f9b5a2a},
{.a = 0xb27addfb64b0, .b = 0x152fd0c420a7}, {.a = 0x7259fa0197c6, .b = 0x5583698df085},
};
static bool plantain_get_card_config(PlantainCardConfig* config, MfClassicType type) {
bool success = true;
@ -125,6 +148,21 @@ static bool plantain_read(Nfc* nfc, NfcDevice* device) {
PlantainCardConfig cfg = {};
if(!plantain_get_card_config(&cfg, data->type)) break;
const uint8_t legacy_check_sec_num = 26;
const uint8_t legacy_check_block_num =
mf_classic_get_first_block_num_of_sector(legacy_check_sec_num);
MfClassicKey key = {0};
bit_lib_num_to_bytes_be(
plantain_4k_keys_legacy[legacy_check_sec_num].a, COUNT_OF(key.data), key.data);
error = mf_classic_poller_sync_auth(
nfc, legacy_check_block_num, &key, MfClassicKeyTypeA, NULL);
if(error == MfClassicErrorNone) {
FURI_LOG_D(TAG, "Legacy keys detected");
cfg.keys = plantain_4k_keys_legacy;
}
MfClassicDeviceKeys keys = {};
for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) {
bit_lib_num_to_bytes_be(cfg.keys[i].a, sizeof(MfClassicKey), keys.key_a[i].data);

View file

@ -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, "\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, "\e#TAT\n%s\n", 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);

View file

@ -35,7 +35,7 @@ void nfc_scene_mf_desfire_more_info_on_enter(void* context) {
for(uint32_t i = 0; i < simple_array_get_count(data->application_ids); ++i) {
const MfDesfireApplicationId* app_id = simple_array_cget(data->application_ids, i);
furi_string_printf(
label, "App %02x%02x%02x", app_id->data[0], app_id->data[1], app_id->data[2]);
label, "App %02x%02x%02x", app_id->data[2], app_id->data[1], app_id->data[0]);
submenu_add_item(
submenu,
furi_string_get_cstr(label),

View file

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

View file

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

View file

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

View file

@ -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);
}

View file

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

View file

@ -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);
}

View file

@ -51,6 +51,10 @@ typedef enum {
DesktopSlideshowCompleted,
DesktopSlideshowPoweroff,
DesktopHwMismatchExit,
DesktopEnclaveExit,
// Global events
DesktopGlobalBeforeAppStarted,
DesktopGlobalAfterAppFinished,

View file

@ -44,25 +44,19 @@ static void menu_draw_callback(Canvas* canvas, void* _model) {
canvas_set_font(canvas, FontSecondary);
shift_position = (0 + position + items_count - 1) % items_count;
item = MenuItemArray_get(model->items, shift_position);
if(item->icon) {
canvas_draw_icon_animation(canvas, 4, 3, item->icon);
}
canvas_draw_icon_animation(canvas, 4, 3, item->icon);
canvas_draw_str(canvas, 22, 14, item->label);
// Second line main
canvas_set_font(canvas, FontPrimary);
shift_position = (1 + position + items_count - 1) % items_count;
item = MenuItemArray_get(model->items, shift_position);
if(item->icon) {
canvas_draw_icon_animation(canvas, 4, 25, item->icon);
}
canvas_draw_icon_animation(canvas, 4, 25, item->icon);
canvas_draw_str(canvas, 22, 36, item->label);
// Third line
canvas_set_font(canvas, FontSecondary);
shift_position = (2 + position + items_count - 1) % items_count;
item = MenuItemArray_get(model->items, shift_position);
if(item->icon) {
canvas_draw_icon_animation(canvas, 4, 47, item->icon);
}
canvas_draw_icon_animation(canvas, 4, 47, item->icon);
canvas_draw_str(canvas, 22, 58, item->label);
// Frame and scrollbar
elements_frame(canvas, 0, 21, 128 - 5, 21);
@ -107,8 +101,8 @@ static void menu_enter(void* context) {
menu->view,
MenuModel * model,
{
MenuItem* item = MenuItemArray_get(model->items, model->position);
if(item && item->icon) {
if(MenuItemArray_size(model->items)) {
MenuItem* item = MenuItemArray_get(model->items, model->position);
icon_animation_start(item->icon);
}
},
@ -121,8 +115,8 @@ static void menu_exit(void* context) {
menu->view,
MenuModel * model,
{
MenuItem* item = MenuItemArray_get(model->items, model->position);
if(item && item->icon) {
if(MenuItemArray_size(model->items)) {
MenuItem* item = MenuItemArray_get(model->items, model->position);
icon_animation_stop(item->icon);
}
},
@ -230,19 +224,17 @@ static void menu_process_up(Menu* menu) {
menu->view,
MenuModel * model,
{
MenuItem* item = MenuItemArray_get(model->items, model->position);
if(item && item->icon) {
if(MenuItemArray_size(model->items)) {
MenuItem* item = MenuItemArray_get(model->items, model->position);
icon_animation_stop(item->icon);
}
if(model->position > 0) {
model->position--;
} else {
model->position = MenuItemArray_size(model->items) - 1;
}
if(model->position > 0) {
model->position--;
} else {
model->position = MenuItemArray_size(model->items) - 1;
}
item = MenuItemArray_get(model->items, model->position);
if(item && item->icon) {
item = MenuItemArray_get(model->items, model->position);
icon_animation_start(item->icon);
}
},
@ -254,19 +246,17 @@ static void menu_process_down(Menu* menu) {
menu->view,
MenuModel * model,
{
MenuItem* item = MenuItemArray_get(model->items, model->position);
if(item && item->icon) {
if(MenuItemArray_size(model->items)) {
MenuItem* item = MenuItemArray_get(model->items, model->position);
icon_animation_stop(item->icon);
}
if(model->position < MenuItemArray_size(model->items) - 1) {
model->position++;
} else {
model->position = 0;
}
if(model->position < MenuItemArray_size(model->items) - 1) {
model->position++;
} else {
model->position = 0;
}
item = MenuItemArray_get(model->items, model->position);
if(item && item->icon) {
item = MenuItemArray_get(model->items, model->position);
icon_animation_start(item->icon);
}
},
@ -279,7 +269,7 @@ static void menu_process_ok(Menu* menu) {
menu->view,
MenuModel * model,
{
if(model->position < MenuItemArray_size(model->items)) {
if(MenuItemArray_size(model->items)) {
item = MenuItemArray_get(model->items, model->position);
}
},

View file

@ -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"],
)

View 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, &reg_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*)&reg_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, &reg_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;
}

View 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

View file

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

View 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.

View 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;
}

View 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);

View 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;
}

View file

@ -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);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -287,6 +287,17 @@ bool ibutton_protocols_load(iButtonProtocols* protocols, iButtonKey* key, const
return success;
}
void ibutton_protocols_render_uid(
iButtonProtocols* protocols,
const iButtonKey* key,
FuriString* result) {
const iButtonProtocolId id = ibutton_key_get_protocol_id(key);
const iButtonProtocolData* data = ibutton_key_get_protocol_data(key);
GET_PROTOCOL_GROUP(id);
GROUP_BASE->render_uid(GROUP_DATA, data, PROTOCOL_ID, result);
}
void ibutton_protocols_render_data(
iButtonProtocols* protocols,
const iButtonKey* key,

View file

@ -133,6 +133,17 @@ bool ibutton_protocols_save(
*/
bool ibutton_protocols_load(iButtonProtocols* protocols, iButtonKey* key, const char* file_name);
/**
* Format a string containing defice UID
* @param [in] protocols pointer to an iButtonProtocols object
* @param [in] key pointer to the key to be rendered
* @param [out] result pointer to the FuriString instance (must be initialized)
*/
void ibutton_protocols_render_uid(
iButtonProtocols* protocols,
const iButtonKey* key,
FuriString* result);
/**
* Format a string containing device full data
* @param [in] protocols pointer to an iButtonProtocols object

View file

@ -208,15 +208,26 @@ bool dallas_common_is_valid_crc(const DallasCommonRomData* rom_data) {
return crc_calculated == crc_received;
}
void dallas_common_render_uid(FuriString* result, const DallasCommonRomData* rom_data) {
furi_string_cat_printf(result, "ID: ");
for(size_t i = 0; i < sizeof(DallasCommonRomData); ++i) {
furi_string_cat_printf(result, "%02X ", rom_data->bytes[i]);
}
}
void dallas_common_render_brief_data(
FuriString* result,
const DallasCommonRomData* rom_data,
const uint8_t* mem_data,
size_t mem_size,
const char* mem_name) {
UNUSED(mem_data);
furi_string_cat_printf(result, "ID: ");
for(size_t i = 0; i < sizeof(rom_data->bytes); ++i) {
furi_string_cat_printf(result, "%02X ", rom_data->bytes[i]);
}
furi_string_cat_printf(result, "\nFamily Code: %02X\n", rom_data->bytes[0]);
const char* size_prefix = "";
size_t mem_size_bits = mem_size * BITS_IN_BYTE;
@ -229,28 +240,23 @@ void dallas_common_render_brief_data(
mem_size_bits /= BITS_IN_KBIT;
}
furi_string_cat_printf(
result, "\nInternal %s: %zu %sbit\n", mem_name, mem_size_bits, size_prefix);
for(size_t i = 0; i < DALLAS_COMMON_BRIEF_HEAD_COUNT; ++i) {
furi_string_cat_printf(result, "%02X ", mem_data[i]);
}
furi_string_cat_printf(result, "[ . . . ]");
for(size_t i = mem_size - DALLAS_COMMON_BRIEF_TAIL_COUNT; i < mem_size; ++i) {
furi_string_cat_printf(result, " %02X", mem_data[i]);
}
furi_string_cat_printf(result, "%s: %zu %sbit\n", mem_name, mem_size_bits, size_prefix);
}
void dallas_common_render_crc_error(FuriString* result, const DallasCommonRomData* rom_data) {
furi_string_set(result, "CRC Error\n");
furi_string_set(result, "\e#CRC Error\e#\n");
const size_t data_size = sizeof(DallasCommonRomData);
for(size_t i = 0; i < data_size; ++i) {
furi_string_cat_printf(result, (i < data_size - 1) ? "%02X " : "%02X", rom_data->bytes[i]);
furi_string_cat_printf(
result, (i < data_size - 1) ? "%02X " : "\e!%02X\e!", rom_data->bytes[i]);
}
furi_string_cat_printf(
result,
"\nExpected CRC: \e!%02X\e!",
maxim_crc8(rom_data->bytes, sizeof(DallasCommonRomData) - 1, MAXIM_CRC8_INIT));
}
void dallas_common_apply_edits(DallasCommonRomData* rom_data, uint8_t family_code) {

View file

@ -96,6 +96,8 @@ bool dallas_common_load_rom_data(
/* Miscellaneous */
bool dallas_common_is_valid_crc(const DallasCommonRomData* rom_data);
void dallas_common_render_uid(FuriString* result, const DallasCommonRomData* rom_data);
void dallas_common_render_brief_data(
FuriString* result,
const DallasCommonRomData* rom_data,

View file

@ -30,6 +30,7 @@ typedef struct {
iButtonProtocolDallasEmulateFunc emulate;
iButtonProtocolDallasSaveFunc save;
iButtonProtocolDallasLoadFunc load;
iButtonProtocolDallasRenderDataFunc render_uid;
iButtonProtocolDallasRenderDataFunc render_data;
iButtonProtocolDallasRenderDataFunc render_brief_data;
iButtonProtocolDallasRenderDataFunc render_error;

View file

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

View file

@ -35,6 +35,7 @@ static bool dallas_ds1971_write_copy(OneWireHost*, iButtonProtocolData*);
static void dallas_ds1971_emulate(OneWireSlave*, iButtonProtocolData*);
static bool dallas_ds1971_load(FlipperFormat*, uint32_t, iButtonProtocolData*);
static bool dallas_ds1971_save(FlipperFormat*, const iButtonProtocolData*);
static void dallas_ds1971_render_uid(FuriString*, const iButtonProtocolData*);
static void dallas_ds1971_render_data(FuriString*, const iButtonProtocolData*);
static void dallas_ds1971_render_brief_data(FuriString*, const iButtonProtocolData*);
static void dallas_ds1971_render_error(FuriString*, const iButtonProtocolData*);
@ -58,6 +59,7 @@ const iButtonProtocolDallasBase ibutton_protocol_ds1971 = {
.emulate = dallas_ds1971_emulate,
.save = dallas_ds1971_save,
.load = dallas_ds1971_load,
.render_uid = dallas_ds1971_render_uid,
.render_data = dallas_ds1971_render_data,
.render_brief_data = dallas_ds1971_render_brief_data,
.render_error = dallas_ds1971_render_error,
@ -209,8 +211,16 @@ bool dallas_ds1971_save(FlipperFormat* ff, const iButtonProtocolData* protocol_d
return success;
}
void dallas_ds1971_render_uid(FuriString* result, const iButtonProtocolData* protocol_data) {
const DS1971ProtocolData* data = protocol_data;
dallas_common_render_uid(result, &data->rom_data);
}
void dallas_ds1971_render_data(FuriString* result, const iButtonProtocolData* protocol_data) {
const DS1971ProtocolData* data = protocol_data;
furi_string_cat_printf(result, "\e#Memory Data\n--------------------\n");
pretty_format_bytes_hex_canonical(
result,
DS1971_DATA_BYTE_COUNT,

View file

@ -27,6 +27,7 @@ static bool dallas_ds1990_write_blank(OneWireHost*, iButtonProtocolData*);
static void dallas_ds1990_emulate(OneWireSlave*, iButtonProtocolData*);
static bool dallas_ds1990_load(FlipperFormat*, uint32_t, iButtonProtocolData*);
static bool dallas_ds1990_save(FlipperFormat*, const iButtonProtocolData*);
static void dallas_ds1990_render_uid(FuriString*, const iButtonProtocolData*);
static void dallas_ds1990_render_brief_data(FuriString*, const iButtonProtocolData*);
static void dallas_ds1990_render_error(FuriString*, const iButtonProtocolData*);
static bool dallas_ds1990_is_data_valid(const iButtonProtocolData*);
@ -46,6 +47,7 @@ const iButtonProtocolDallasBase ibutton_protocol_ds1990 = {
.emulate = dallas_ds1990_emulate,
.save = dallas_ds1990_save,
.load = dallas_ds1990_load,
.render_uid = dallas_ds1990_render_uid,
.render_data = NULL, /* No data to render */
.render_brief_data = dallas_ds1990_render_brief_data,
.render_error = dallas_ds1990_render_error,
@ -117,12 +119,20 @@ bool dallas_ds1990_load(
return dallas_common_load_rom_data(ff, format_version, &data->rom_data);
}
void dallas_ds1990_render_uid(FuriString* result, const iButtonProtocolData* protocol_data) {
const DS1990ProtocolData* data = protocol_data;
dallas_common_render_uid(result, &data->rom_data);
}
void dallas_ds1990_render_brief_data(FuriString* result, const iButtonProtocolData* protocol_data) {
const DS1990ProtocolData* 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_ds1990_render_error(FuriString* result, const iButtonProtocolData* protocol_data) {

View file

@ -36,6 +36,7 @@ static bool dallas_ds1992_write_copy(OneWireHost*, iButtonProtocolData*);
static void dallas_ds1992_emulate(OneWireSlave*, iButtonProtocolData*);
static bool dallas_ds1992_load(FlipperFormat*, uint32_t, iButtonProtocolData*);
static bool dallas_ds1992_save(FlipperFormat*, const iButtonProtocolData*);
static void dallas_ds1992_render_uid(FuriString*, const iButtonProtocolData*);
static void dallas_ds1992_render_data(FuriString*, const iButtonProtocolData*);
static void dallas_ds1992_render_brief_data(FuriString*, const iButtonProtocolData*);
static void dallas_ds1992_render_error(FuriString*, const iButtonProtocolData*);
@ -57,6 +58,7 @@ const iButtonProtocolDallasBase ibutton_protocol_ds1992 = {
.emulate = dallas_ds1992_emulate,
.save = dallas_ds1992_save,
.load = dallas_ds1992_load,
.render_uid = dallas_ds1992_render_uid,
.render_data = dallas_ds1992_render_data,
.render_brief_data = dallas_ds1992_render_brief_data,
.render_error = dallas_ds1992_render_error,
@ -182,8 +184,16 @@ bool dallas_ds1992_save(FlipperFormat* ff, const iButtonProtocolData* protocol_d
return success;
}
void dallas_ds1992_render_uid(FuriString* result, const iButtonProtocolData* protocol_data) {
const DS1992ProtocolData* data = protocol_data;
dallas_common_render_uid(result, &data->rom_data);
}
void dallas_ds1992_render_data(FuriString* result, const iButtonProtocolData* protocol_data) {
const DS1992ProtocolData* data = protocol_data;
furi_string_cat_printf(result, "\e#Memory Data\n--------------------\n");
pretty_format_bytes_hex_canonical(
result,
DS1992_DATA_BYTE_COUNT,

View file

@ -33,6 +33,7 @@ static bool dallas_ds1996_write_copy(OneWireHost*, iButtonProtocolData*);
static void dallas_ds1996_emulate(OneWireSlave*, iButtonProtocolData*);
static bool dallas_ds1996_load(FlipperFormat*, uint32_t, iButtonProtocolData*);
static bool dallas_ds1996_save(FlipperFormat*, const iButtonProtocolData*);
static void dallas_ds1996_render_uid(FuriString*, const iButtonProtocolData*);
static void dallas_ds1996_render_data(FuriString*, const iButtonProtocolData*);
static void dallas_ds1996_render_brief_data(FuriString*, const iButtonProtocolData*);
static void dallas_ds1996_render_error(FuriString*, const iButtonProtocolData*);
@ -53,6 +54,7 @@ const iButtonProtocolDallasBase ibutton_protocol_ds1996 = {
.emulate = dallas_ds1996_emulate,
.save = dallas_ds1996_save,
.load = dallas_ds1996_load,
.render_uid = dallas_ds1996_render_uid,
.render_data = dallas_ds1996_render_data,
.render_brief_data = dallas_ds1996_render_brief_data,
.render_error = dallas_ds1996_render_error,
@ -207,9 +209,16 @@ bool dallas_ds1996_save(FlipperFormat* ff, const iButtonProtocolData* protocol_d
return success;
}
void dallas_ds1996_render_uid(FuriString* result, const iButtonProtocolData* protocol_data) {
const DS1996ProtocolData* data = protocol_data;
dallas_common_render_uid(result, &data->rom_data);
}
void dallas_ds1996_render_data(FuriString* result, const iButtonProtocolData* protocol_data) {
const DS1996ProtocolData* data = protocol_data;
furi_string_cat_printf(result, "\e#Memory Data\n--------------------\n");
pretty_format_bytes_hex_canonical(
result,
DS1996_DATA_BYTE_COUNT,

View file

@ -8,7 +8,7 @@
#include "../blanks/tm2004.h"
#define DALLAS_GENERIC_FAMILY_CODE 0x00U
#define DALLAS_GENERIC_FAMILY_NAME "DSGeneric"
#define DALLAS_GENERIC_FAMILY_NAME "(non-specific)"
typedef struct {
OneWireSlave* bus;
@ -24,6 +24,7 @@ static bool ds_generic_write_blank(OneWireHost*, iButtonProtocolData*);
static void ds_generic_emulate(OneWireSlave*, iButtonProtocolData*);
static bool ds_generic_load(FlipperFormat*, uint32_t, iButtonProtocolData*);
static bool ds_generic_save(FlipperFormat*, const iButtonProtocolData*);
static void ds_generic_render_uid(FuriString*, const iButtonProtocolData*);
static void ds_generic_render_brief_data(FuriString*, const iButtonProtocolData*);
static void ds_generic_render_error(FuriString*, const iButtonProtocolData*);
static bool ds_generic_is_data_valid(const iButtonProtocolData*);
@ -44,6 +45,7 @@ const iButtonProtocolDallasBase ibutton_protocol_ds_generic = {
.save = ds_generic_save,
.load = ds_generic_load,
.render_data = NULL, /* No data to render */
.render_uid = ds_generic_render_uid,
.render_brief_data = ds_generic_render_brief_data,
.render_error = ds_generic_render_error,
.is_valid = ds_generic_is_data_valid,
@ -111,9 +113,15 @@ bool ds_generic_load(
return dallas_common_load_rom_data(ff, format_version, &data->rom_data);
}
void ds_generic_render_uid(FuriString* result, const iButtonProtocolData* protocol_data) {
const DallasGenericProtocolData* data = protocol_data;
dallas_common_render_uid(result, &data->rom_data);
}
void ds_generic_render_brief_data(FuriString* result, const iButtonProtocolData* protocol_data) {
const DallasGenericProtocolData* 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]);
}

View file

@ -51,6 +51,12 @@ static bool ibutton_protocol_group_dallas_get_id_by_name(
return true;
}
// Handle files that refer to Dallas "Raw Data" as DSGeneric
if(strcmp(name, "DSGeneric") == 0) {
*id = iButtonProtocolDSGeneric;
return true;
}
for(iButtonProtocolLocalId i = 0; i < iButtonProtocolDSMax; ++i) {
if(strcmp(ibutton_protocols_dallas[i]->name, name) == 0) {
*id = i;
@ -212,6 +218,18 @@ static bool ibutton_protocol_group_dallas_load(
return ibutton_protocols_dallas[id]->load(ff, version, data);
}
static void ibutton_protocol_group_dallas_render_uid(
iButtonProtocolGroupDallas* group,
const iButtonProtocolData* data,
iButtonProtocolLocalId id,
FuriString* result) {
UNUSED(group);
furi_assert(id < iButtonProtocolDSMax);
const iButtonProtocolDallasBase* protocol = ibutton_protocols_dallas[id];
furi_assert(protocol->render_uid);
protocol->render_uid(result, data);
}
static void ibutton_protocol_group_dallas_render_data(
iButtonProtocolGroupDallas* group,
const iButtonProtocolData* data,
@ -298,6 +316,7 @@ const iButtonProtocolGroupBase ibutton_protocol_group_dallas = {
.save = (iButtonProtocolGroupSaveFunc)ibutton_protocol_group_dallas_save,
.load = (iButtonProtocolGroupLoadFunc)ibutton_protocol_group_dallas_load,
.render_uid = (iButtonProtocolGroupRenderFunc)ibutton_protocol_group_dallas_render_uid,
.render_data = (iButtonProtocolGroupRenderFunc)ibutton_protocol_group_dallas_render_data,
.render_brief_data =
(iButtonProtocolGroupRenderFunc)ibutton_protocol_group_dallas_render_brief_data,

View file

@ -325,12 +325,17 @@ static LevelDuration protocol_cyfral_encoder_yield(ProtocolCyfral* proto) {
return result;
}
static void protocol_cyfral_render_brief_data(ProtocolCyfral* proto, FuriString* result) {
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]);
}
}
static void protocol_cyfral_render_brief_data(ProtocolCyfral* proto, FuriString* result) {
protocol_cyfral_render_uid(proto, result);
}
const ProtocolBase ibutton_protocol_misc_cyfral = {
.name = "Cyfral",
.manufacturer = "Cyfral",
@ -348,5 +353,6 @@ const ProtocolBase ibutton_protocol_misc_cyfral = {
.start = (ProtocolEncoderStart)protocol_cyfral_encoder_start,
.yield = (ProtocolEncoderYield)protocol_cyfral_encoder_yield,
},
.render_uid = (ProtocolRenderData)protocol_cyfral_render_uid,
.render_brief_data = (ProtocolRenderData)protocol_cyfral_render_brief_data,
};

View file

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

View file

@ -301,12 +301,17 @@ static LevelDuration protocol_metakom_encoder_yield(ProtocolMetakom* proto) {
return result;
}
static void protocol_metakom_render_brief_data(ProtocolMetakom* proto, FuriString* result) {
static void protocol_metakom_render_uid(ProtocolMetakom* proto, FuriString* result) {
furi_string_cat_printf(result, "ID: ");
for(size_t i = 0; i < METAKOM_DATA_SIZE; ++i) {
furi_string_cat_printf(result, "%02X ", ((uint8_t*)&proto->data)[i]);
}
}
static void protocol_metakom_render_brief_data(ProtocolMetakom* proto, FuriString* result) {
protocol_metakom_render_uid(proto, result);
}
const ProtocolBase ibutton_protocol_misc_metakom = {
.name = "Metakom",
.manufacturer = "Metakom",
@ -324,5 +329,6 @@ const ProtocolBase ibutton_protocol_misc_metakom = {
.start = (ProtocolEncoderStart)protocol_metakom_encoder_start,
.yield = (ProtocolEncoderYield)protocol_metakom_encoder_yield,
},
.render_uid = (ProtocolRenderData)protocol_metakom_render_uid,
.render_brief_data = (ProtocolRenderData)protocol_metakom_render_brief_data,
};

View file

@ -93,6 +93,7 @@ typedef struct {
iButtonProtocolGroupSaveFunc save;
iButtonProtocolGroupLoadFunc load;
iButtonProtocolGroupRenderFunc render_uid;
iButtonProtocolGroupRenderFunc render_data;
iButtonProtocolGroupRenderFunc render_brief_data;
iButtonProtocolGroupRenderFunc render_error;

View file

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

View file

@ -8,6 +8,10 @@
#define ISO14443_4A_FSDI_256 (0x8U)
#define ISO14443_4A_SEND_BLOCK_MAX_ATTEMPTS (20)
#define ISO14443_4A_FWT_MAX (4096UL << 14)
#define ISO14443_4A_WTXM_MASK (0x3FU)
#define ISO14443_4A_WTXM_MAX (0x3BU)
#define ISO14443_4A_SWTX (0xF2U)
Iso14443_4aError iso14443_4a_poller_halt(Iso14443_4aPoller* instance) {
furi_check(instance);
@ -75,9 +79,35 @@ Iso14443_4aError iso14443_4a_poller_send_block(
if(iso14443_3a_error != Iso14443_3aErrorNone) {
error = iso14443_4a_process_error(iso14443_3a_error);
break;
}
} else if(!iso14443_4_layer_decode_block(
instance->iso14443_4_layer, rx_buffer, instance->rx_buffer)) {
if(bit_buffer_starts_with_byte(instance->rx_buffer, ISO14443_4A_SWTX)) {
do {
uint8_t wtxm = bit_buffer_get_byte(instance->rx_buffer, 1) & ISO14443_4A_WTXM_MASK;
if(wtxm > ISO14443_4A_WTXM_MAX) {
return Iso14443_4aErrorProtocol;
}
bit_buffer_reset(instance->tx_buffer);
bit_buffer_copy_left(instance->tx_buffer, instance->rx_buffer, 1);
bit_buffer_append_byte(instance->tx_buffer, wtxm);
iso14443_3a_error = iso14443_3a_poller_send_standard_frame(
instance->iso14443_3a_poller,
instance->tx_buffer,
instance->rx_buffer,
MAX(iso14443_4a_get_fwt_fc_max(instance->data) * wtxm, ISO14443_4A_FWT_MAX));
if(iso14443_3a_error != Iso14443_3aErrorNone) {
error = iso14443_4a_process_error(iso14443_3a_error);
return error;
}
} while(bit_buffer_starts_with_byte(instance->rx_buffer, ISO14443_4A_SWTX));
}
if(!iso14443_4_layer_decode_block(
instance->iso14443_4_layer, rx_buffer, instance->rx_buffer)) {
error = Iso14443_4aErrorProtocol;
break;
}

View file

@ -315,6 +315,89 @@ MfClassicError mf_classic_poller_value_cmd(
*/
MfClassicError mf_classic_poller_value_transfer(MfClassicPoller* instance, uint8_t block_num);
/**
* @brief Transmit and receive Iso14443_3a standard frames in poller mode.
*
* Must ONLY be used inside the callback function.
*
* The rx_buffer will be filled with any data received as a response to data
* sent from tx_buffer, with a timeout defined by the fwt parameter.
*
* @param[in, out] instance pointer to the instance to be used in the transaction.
* @param[in] tx_buffer pointer to the buffer containing the data to be transmitted.
* @param[out] rx_buffer pointer to the buffer to be filled with received data.
* @param[in] fwt frame wait time (response timeout), in carrier cycles.
* @return MfClassicErrorNone on success, an error code on failure.
*/
MfClassicError mf_classic_poller_send_standard_frame(
MfClassicPoller* instance,
const BitBuffer* tx_buffer,
BitBuffer* rx_buffer,
uint32_t fwt_fc);
/**
* @brief Transmit and receive Iso14443_3a frames in poller mode.
*
* Must ONLY be used inside the callback function.
*
* The rx_buffer will be filled with any data received as a response to data
* sent from tx_buffer, with a timeout defined by the fwt parameter.
*
* @param[in, out] instance pointer to the instance to be used in the transaction.
* @param[in] tx_buffer pointer to the buffer containing the data to be transmitted.
* @param[out] rx_buffer pointer to the buffer to be filled with received data.
* @param[in] fwt frame wait time (response timeout), in carrier cycles.
* @return MfClassicErrorNone on success, an error code on failure.
*/
MfClassicError mf_classic_poller_send_frame(
MfClassicPoller* instance,
const BitBuffer* tx_buffer,
BitBuffer* rx_buffer,
uint32_t fwt_fc);
/**
* @brief Transmit and receive Iso14443_3a frames with custom parity bits in poller mode.
*
* Must ONLY be used inside the callback function.
*
* The rx_buffer will be filled with any data received as a response to data
* sent from tx_buffer, with a timeout defined by the fwt parameter.
*
* Custom parity bits must be set in the tx_buffer. The rx_buffer will contain
* the received data with the parity bits.
*
* @param[in, out] instance pointer to the instance to be used in the transaction.
* @param[in] tx_buffer pointer to the buffer containing the data to be transmitted.
* @param[out] rx_buffer pointer to the buffer to be filled with received data.
* @param[in] fwt frame wait time (response timeout), in carrier cycles.
* @return MfClassicErrorNone on success, an error code on failure.
*/
MfClassicError mf_classic_poller_send_custom_parity_frame(
MfClassicPoller* instance,
const BitBuffer* tx_buffer,
BitBuffer* rx_buffer,
uint32_t fwt_fc);
/**
* @brief Transmit and receive Mifare Classic encrypted frames with custom parity bits in poller mode.
*
* Must ONLY be used inside the callback function.
*
* The rx_buffer will be filled with any data received as a response to data
* sent from tx_buffer, with a timeout defined by the fwt parameter.
*
* @param[in, out] instance pointer to the instance to be used in the transaction.
* @param[in] tx_buffer pointer to the buffer containing the plain data to be transmitted.
* @param[out] rx_buffer pointer to the buffer to be filled with decyphered received data.
* @param[in] fwt frame wait time (response timeout), in carrier cycles.
* @return MfClassicErrorNone on success, an error code on failure.
*/
MfClassicError mf_classic_poller_send_encrypted_frame(
MfClassicPoller* instance,
const BitBuffer* tx_buffer,
BitBuffer* rx_buffer,
uint32_t fwt_fc);
#ifdef __cplusplus
}
#endif

View file

@ -465,3 +465,77 @@ MfClassicError mf_classic_poller_value_transfer(MfClassicPoller* instance, uint8
return ret;
}
MfClassicError mf_classic_poller_send_standard_frame(
MfClassicPoller* instance,
const BitBuffer* tx_buffer,
BitBuffer* rx_buffer,
uint32_t fwt_fc) {
furi_check(instance);
furi_check(tx_buffer);
furi_check(rx_buffer);
Iso14443_3aError error = iso14443_3a_poller_send_standard_frame(
instance->iso14443_3a_poller, tx_buffer, rx_buffer, fwt_fc);
return mf_classic_process_error(error);
}
MfClassicError mf_classic_poller_send_frame(
MfClassicPoller* instance,
const BitBuffer* tx_buffer,
BitBuffer* rx_buffer,
uint32_t fwt_fc) {
furi_check(instance);
furi_check(tx_buffer);
furi_check(rx_buffer);
Iso14443_3aError error =
iso14443_3a_poller_txrx(instance->iso14443_3a_poller, tx_buffer, rx_buffer, fwt_fc);
return mf_classic_process_error(error);
}
MfClassicError mf_classic_poller_send_custom_parity_frame(
MfClassicPoller* instance,
const BitBuffer* tx_buffer,
BitBuffer* rx_buffer,
uint32_t fwt_fc) {
furi_check(instance);
furi_check(tx_buffer);
furi_check(rx_buffer);
Iso14443_3aError error = iso14443_3a_poller_txrx_custom_parity(
instance->iso14443_3a_poller, tx_buffer, rx_buffer, fwt_fc);
return mf_classic_process_error(error);
}
MfClassicError mf_classic_poller_send_encrypted_frame(
MfClassicPoller* instance,
const BitBuffer* tx_buffer,
BitBuffer* rx_buffer,
uint32_t fwt_fc) {
furi_check(instance);
furi_check(tx_buffer);
furi_check(rx_buffer);
MfClassicError ret = MfClassicErrorNone;
do {
crypto1_encrypt(instance->crypto, NULL, tx_buffer, instance->tx_encrypted_buffer);
Iso14443_3aError error = iso14443_3a_poller_txrx_custom_parity(
instance->iso14443_3a_poller,
instance->tx_encrypted_buffer,
instance->rx_encrypted_buffer,
fwt_fc);
if(error != Iso14443_3aErrorNone) {
ret = mf_classic_process_error(error);
break;
}
crypto1_decrypt(instance->crypto, instance->rx_encrypted_buffer, rx_buffer);
} while(false);
return ret;
}

View file

@ -21,8 +21,6 @@ extern "C" {
#define MF_DESFIRE_CMD_GET_VALUE (0x6C)
#define MF_DESFIRE_CMD_READ_RECORDS (0xBB)
#define MF_DESFIRE_FLAG_HAS_NEXT (0xAF)
#define MF_DESFIRE_MAX_KEYS (14)
#define MF_DESFIRE_MAX_FILES (32)
@ -71,11 +69,6 @@ typedef struct {
typedef uint8_t MfDesfireKeyVersion;
typedef struct {
MfDesfireKeySettings key_settings;
SimpleArray* key_versions;
} MfDesfireKeyConfiguration;
typedef enum {
MfDesfireFileTypeStandard = 0,
MfDesfireFileTypeBackup = 1,
@ -96,7 +89,8 @@ typedef uint16_t MfDesfireFileAccessRights;
typedef struct {
MfDesfireFileType type;
MfDesfireFileCommunicationSettings comm;
MfDesfireFileAccessRights access_rights;
MfDesfireFileAccessRights access_rights[MF_DESFIRE_MAX_KEYS];
uint8_t access_rights_len;
union {
struct {
uint32_t size;
@ -136,6 +130,7 @@ typedef enum {
MfDesfireErrorNotPresent,
MfDesfireErrorProtocol,
MfDesfireErrorTimeout,
MfDesfireErrorAuthentication,
} MfDesfireError;
typedef struct {

View file

@ -1,5 +1,7 @@
#include "mf_desfire_i.h"
#define TAG "MfDesfire"
#define BITS_IN_BYTE (8U)
#define MF_DESFIRE_FFF_VERSION_KEY \
@ -175,59 +177,88 @@ bool mf_desfire_file_settings_parse(MfDesfireFileSettings* data, const BitBuffer
};
} MfDesfireFileSettingsLayout;
MfDesfireFileSettings file_settings_temp = {};
do {
const size_t data_size = bit_buffer_get_size_bytes(buf);
const uint8_t* data_ptr = bit_buffer_get_data(buf);
const size_t min_data_size =
sizeof(MfDesfireFileSettingsHeader) + sizeof(MfDesfireFileSettingsData);
const size_t max_data_size =
sizeof(MfDesfireFileSettingsHeader) + sizeof(MfDesfireFileSettingsValue);
if(data_size < min_data_size) break;
if(data_size <= max_data_size) {
MfDesfireFileSettingsLayout layout;
bit_buffer_write_bytes(buf, &layout, sizeof(MfDesfireFileSettingsLayout));
data->type = layout.header.type;
data->comm = layout.header.comm;
data->access_rights = layout.header.access_rights;
if(data->type == MfDesfireFileTypeStandard || data->type == MfDesfireFileTypeBackup) {
if(data_size != min_data_size) break;
data->data.size = layout.data.size;
} else if(data->type == MfDesfireFileTypeValue) {
if(data_size !=
sizeof(MfDesfireFileSettingsHeader) + sizeof(MfDesfireFileSettingsValue))
break;
data->value.lo_limit = layout.value.lo_limit;
data->value.hi_limit = layout.value.hi_limit;
data->value.limited_credit_value = layout.value.limited_credit_value;
data->value.limited_credit_enabled = layout.value.limited_credit_enabled;
} else if(
data->type == MfDesfireFileTypeLinearRecord ||
data->type == MfDesfireFileTypeCyclicRecord) {
if(data_size !=
sizeof(MfDesfireFileSettingsHeader) + sizeof(MfDesfireFileSettingsRecord))
break;
data->record.size = layout.record.size;
data->record.max = layout.record.max;
data->record.cur = layout.record.cur;
} else {
break;
}
} else {
// TODO FL-3750: process HID Desfire command response here
// Set default fields for now
data->type = 0;
data->comm = 0;
data->access_rights = 0;
data->data.size = 0;
if(data_size < min_data_size) {
FURI_LOG_E(
TAG, "File settings size %zu less than minimum %zu", data_size, min_data_size);
break;
}
size_t bytes_processed = sizeof(MfDesfireFileSettingsHeader);
MfDesfireFileSettingsLayout layout = {};
memcpy(&layout.header, data_ptr, sizeof(MfDesfireFileSettingsHeader));
bool has_additional_access_rights = (layout.header.comm & 0x80) != 0;
file_settings_temp.type = layout.header.type;
file_settings_temp.comm = layout.header.comm & 0x03;
file_settings_temp.access_rights_len = 1;
file_settings_temp.access_rights[0] = layout.header.access_rights;
if(file_settings_temp.type == MfDesfireFileTypeStandard ||
file_settings_temp.type == MfDesfireFileTypeBackup) {
memcpy(
&layout.data,
&data_ptr[sizeof(MfDesfireFileSettingsHeader)],
sizeof(MfDesfireFileSettingsData));
file_settings_temp.data.size = layout.data.size;
bytes_processed += sizeof(MfDesfireFileSettingsData);
} else if(file_settings_temp.type == MfDesfireFileTypeValue) {
memcpy(
&layout.value,
&data_ptr[sizeof(MfDesfireFileSettingsHeader)],
sizeof(MfDesfireFileSettingsValue));
file_settings_temp.value.lo_limit = layout.value.lo_limit;
file_settings_temp.value.hi_limit = layout.value.hi_limit;
file_settings_temp.value.limited_credit_value = layout.value.limited_credit_value;
file_settings_temp.value.limited_credit_enabled = layout.value.limited_credit_enabled;
bytes_processed += sizeof(MfDesfireFileSettingsValue);
} else if(
file_settings_temp.type == MfDesfireFileTypeLinearRecord ||
file_settings_temp.type == MfDesfireFileTypeCyclicRecord) {
memcpy(
&layout.record,
&data_ptr[sizeof(MfDesfireFileSettingsHeader)],
sizeof(MfDesfireFileSettingsRecord));
file_settings_temp.record.size = layout.record.size;
file_settings_temp.record.max = layout.record.max;
file_settings_temp.record.cur = layout.record.cur;
bytes_processed += sizeof(MfDesfireFileSettingsRecord);
} else {
FURI_LOG_W(TAG, "Unknown file type: %02x", file_settings_temp.type);
break;
}
if(has_additional_access_rights) {
uint8_t additional_access_rights_len = bit_buffer_get_byte(buf, bytes_processed);
FURI_LOG_D(TAG, "Has additional rights: %d", additional_access_rights_len);
if(data_size != bytes_processed +
additional_access_rights_len * sizeof(MfDesfireFileAccessRights) +
1) {
FURI_LOG_W(TAG, "Unexpected command length: %zu", data_size);
for(size_t i = 0; i < bit_buffer_get_size_bytes(buf); i++) {
printf("%02X ", bit_buffer_get_byte(buf, i));
}
printf("\r\n");
break;
}
if(additional_access_rights_len >
MF_DESFIRE_MAX_KEYS * sizeof(MfDesfireFileAccessRights))
break;
memcpy(
&file_settings_temp.access_rights[1],
&data_ptr[bytes_processed],
additional_access_rights_len * sizeof(MfDesfireFileAccessRights));
file_settings_temp.access_rights_len += additional_access_rights_len;
}
*data = file_settings_temp;
parsed = true;
} while(false);
@ -392,18 +423,19 @@ bool mf_desfire_file_settings_load(
break;
furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_ACCESS_RIGHTS_KEY);
if(!flipper_format_read_hex(
ff,
furi_string_get_cstr(key),
(uint8_t*)&data->access_rights,
sizeof(MfDesfireFileAccessRights)))
uint32_t access_rights_len = 0;
if(!flipper_format_get_value_count(ff, furi_string_get_cstr(key), &access_rights_len))
break;
if((access_rights_len == 0) || ((access_rights_len % 2) != 0)) break;
if(!flipper_format_read_hex(
ff, furi_string_get_cstr(key), (uint8_t*)&data->access_rights, access_rights_len))
break;
data->access_rights_len = access_rights_len / sizeof(MfDesfireFileAccessRights);
if(data->type == MfDesfireFileTypeStandard || data->type == MfDesfireFileTypeBackup) {
furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_SIZE_KEY);
if(!flipper_format_read_uint32(ff, furi_string_get_cstr(key), &data->data.size, 1))
break;
} else if(data->type == MfDesfireFileTypeValue) {
furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_HI_LIMIT_KEY);
if(!flipper_format_read_uint32(ff, furi_string_get_cstr(key), &data->value.hi_limit, 1))
@ -641,8 +673,8 @@ bool mf_desfire_file_settings_save(
if(!flipper_format_write_hex(
ff,
furi_string_get_cstr(key),
(const uint8_t*)&data->access_rights,
sizeof(MfDesfireFileAccessRights)))
(const uint8_t*)data->access_rights,
data->access_rights_len * sizeof(MfDesfireFileAccessRights)))
break;
if(data->type == MfDesfireFileTypeStandard || data->type == MfDesfireFileTypeBackup) {
@ -737,8 +769,11 @@ bool mf_desfire_application_save(
if(i != key_version_count) break;
const uint32_t file_count = simple_array_get_count(data->file_ids);
if(!mf_desfire_file_ids_save(simple_array_get_data(data->file_ids), file_count, prefix, ff))
break;
if(file_count > 0) {
if(!mf_desfire_file_ids_save(
simple_array_get_data(data->file_ids), file_count, prefix, ff))
break;
}
for(i = 0; i < file_count; ++i) {
const MfDesfireFileId* file_id = simple_array_cget(data->file_ids, i);

View file

@ -5,6 +5,52 @@
#define MF_DESFIRE_FFF_PICC_PREFIX "PICC"
#define MF_DESFIRE_FFF_APP_PREFIX "Application"
// Successful operation
#define MF_DESFIRE_STATUS_OPERATION_OK (0x00)
// No changes done to backup files, CommitTransaction / AbortTransaction not necessary
#define MF_DESFIRE_STATUS_NO_CHANGES (0x0C)
// Insufficient NV-Memory to complete command
#define MF_DESFIRE_STATUS_OUT_OF_EEPROM_ERROR (0x0E)
// Command code not supported
#define MF_DESFIRE_STATUS_ILLEGAL_COMMAND_CODE (0x1C)
// CRC or MAC does not match data Padding bytes not valid
#define MF_DESFIRE_STATUS_INTEGRITY_ERROR (0x1E)
// Invalid key number specified
#define MF_DESFIRE_STATUS_NO_SUCH_KEY (0x40)
// Length of command string invalid
#define MF_DESFIRE_STATUS_LENGTH_ERROR (0x7E)
// Current configuration / status does not allow the requested command
#define MF_DESFIRE_STATUS_PERMISSION_DENIED (0x9D)
// Value of the parameter(s) invalid
#define MF_DESFIRE_STATUS_PARAMETER_ERROR (0x9E)
// Requested AID not present on PICC
#define MF_DESFIRE_STATUS_APPLICATION_NOT_FOUND (0xA0)
// Unrecoverable error within application, application will be disabled
#define MF_DESFIRE_STATUS_APPL_INTEGRITY_ERROR (0xA1)
// Current authentication status does not allow the requested command
#define MF_DESFIRE_STATUS_AUTHENTICATION_ERROR (0xAE)
// Additional data frame is expected to be sent
#define MF_DESFIRE_STATUS_ADDITIONAL_FRAME (0xAF)
// Attempt to read/write data from/to beyond the file's/record's limits
// Attempt to exceed the limits of a value file.
#define MF_DESFIRE_STATUS_BOUNDARY_ERROR (0xBE)
// Unrecoverable error within PICC, PICC will be disabled
#define MF_DESFIRE_STATUS_PICC_INTEGRITY_ERROR (0xC1)
// Previous Command was not fully completed. Not all Frames were requested or provided by the PCD
#define MF_DESFIRE_STATUS_COMMAND_ABORTED (0xCA)
// PICC was disabled by an unrecoverable error
#define MF_DESFIRE_STATUS_PICC_DISABLED_ERROR (0xCD)
// Number of Applications limited to 28, no additional CreateApplication possible
#define MF_DESFIRE_STATUS_COUNT_ERROR (0xCE)
// Creation of file/application failed because file/application with same number already exists
#define MF_DESFIRE_STATUS_DUBLICATE_ERROR (0xDE)
// Could not complete NV-write operation due to loss of power, internal backup/rollback mechanism activated
#define MF_DESFIRE_STATUS_EEPROM_ERROR (0xEE)
// Specified file number does not exist
#define MF_DESFIRE_STATUS_FILE_NOT_FOUND (0xF0)
// Unrecoverable error within file, file will be disabled
#define MF_DESFIRE_STATUS_FILE_INTEGRITY_ERROR (0xF1)
// SimpleArray configurations
extern const SimpleArrayConfig mf_desfire_key_version_array_config;

View file

@ -75,17 +75,23 @@ static NfcCommand mf_desfire_poller_handler_read_version(MfDesfirePoller* instan
}
static NfcCommand mf_desfire_poller_handler_read_free_memory(MfDesfirePoller* instance) {
NfcCommand command = NfcCommandContinue;
instance->error = mf_desfire_poller_read_free_memory(instance, &instance->data->free_memory);
if(instance->error == MfDesfireErrorNone) {
FURI_LOG_D(TAG, "Read free memory success");
instance->state = MfDesfirePollerStateReadMasterKeySettings;
} else if(instance->error == MfDesfireErrorNotPresent) {
FURI_LOG_D(TAG, "Read free memoty is unsupported");
instance->state = MfDesfirePollerStateReadMasterKeySettings;
command = NfcCommandReset;
} else {
FURI_LOG_E(TAG, "Failed to read free memory");
iso14443_4a_poller_halt(instance->iso14443_4a_poller);
instance->state = MfDesfirePollerStateReadFailed;
}
return NfcCommandContinue;
return command;
}
static NfcCommand mf_desfire_poller_handler_read_master_key_settings(MfDesfirePoller* instance) {
@ -94,6 +100,11 @@ static NfcCommand mf_desfire_poller_handler_read_master_key_settings(MfDesfirePo
if(instance->error == MfDesfireErrorNone) {
FURI_LOG_D(TAG, "Read master key settings success");
instance->state = MfDesfirePollerStateReadMasterKeyVersion;
} else if(instance->error == MfDesfireErrorAuthentication) {
FURI_LOG_D(TAG, "Auth is required to read master key settings and app ids");
instance->data->master_key_settings.is_free_directory_list = false;
instance->data->master_key_settings.max_keys = 1;
instance->state = MfDesfirePollerStateReadMasterKeyVersion;
} else {
FURI_LOG_E(TAG, "Failed to read master key settings");
iso14443_4a_poller_halt(instance->iso14443_4a_poller);
@ -110,7 +121,11 @@ static NfcCommand mf_desfire_poller_handler_read_master_key_version(MfDesfirePol
instance->data->master_key_settings.max_keys);
if(instance->error == MfDesfireErrorNone) {
FURI_LOG_D(TAG, "Read master key version success");
instance->state = MfDesfirePollerStateReadApplicationIds;
if(instance->data->master_key_settings.is_free_directory_list) {
instance->state = MfDesfirePollerStateReadApplicationIds;
} else {
instance->state = MfDesfirePollerStateReadSuccess;
}
} else {
FURI_LOG_E(TAG, "Failed to read master key version");
iso14443_4a_poller_halt(instance->iso14443_4a_poller);
@ -126,6 +141,9 @@ static NfcCommand mf_desfire_poller_handler_read_application_ids(MfDesfirePoller
if(instance->error == MfDesfireErrorNone) {
FURI_LOG_D(TAG, "Read application ids success");
instance->state = MfDesfirePollerStateReadApplications;
} else if(instance->error == MfDesfireErrorAuthentication) {
FURI_LOG_D(TAG, "Read application ids impossible without authentication");
instance->state = MfDesfirePollerStateReadSuccess;
} else {
FURI_LOG_E(TAG, "Failed to read application ids");
iso14443_4a_poller_halt(instance->iso14443_4a_poller);

View file

@ -19,6 +19,17 @@ MfDesfireError mf_desfire_process_error(Iso14443_4aError error) {
}
}
MfDesfireError mf_desfire_process_status_code(uint8_t status_code) {
switch(status_code) {
case MF_DESFIRE_STATUS_OPERATION_OK:
return MfDesfireErrorNone;
case MF_DESFIRE_STATUS_AUTHENTICATION_ERROR:
return MfDesfireErrorAuthentication;
default:
return MfDesfireErrorProtocol;
}
}
MfDesfireError mf_desfire_send_chunks(
MfDesfirePoller* instance,
const BitBuffer* tx_buffer,
@ -42,7 +53,7 @@ MfDesfireError mf_desfire_send_chunks(
}
bit_buffer_reset(instance->tx_buffer);
bit_buffer_append_byte(instance->tx_buffer, MF_DESFIRE_FLAG_HAS_NEXT);
bit_buffer_append_byte(instance->tx_buffer, MF_DESFIRE_STATUS_ADDITIONAL_FRAME);
if(bit_buffer_get_size_bytes(instance->rx_buffer) > sizeof(uint8_t)) {
bit_buffer_copy_right(rx_buffer, instance->rx_buffer, sizeof(uint8_t));
@ -50,7 +61,8 @@ MfDesfireError mf_desfire_send_chunks(
bit_buffer_reset(rx_buffer);
}
while(bit_buffer_starts_with_byte(instance->rx_buffer, MF_DESFIRE_FLAG_HAS_NEXT)) {
while(
bit_buffer_starts_with_byte(instance->rx_buffer, MF_DESFIRE_STATUS_ADDITIONAL_FRAME)) {
Iso14443_4aError iso14443_4a_error = iso14443_4a_poller_send_block(
instance->iso14443_4a_poller, instance->tx_buffer, instance->rx_buffer);
@ -63,14 +75,19 @@ 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);
if(error == MfDesfireErrorNone) {
uint8_t err_code = bit_buffer_get_byte(instance->rx_buffer, 0);
error = mf_desfire_process_status_code(err_code);
}
return error;
}
@ -110,7 +127,7 @@ MfDesfireError
if(error != MfDesfireErrorNone) break;
if(!mf_desfire_free_memory_parse(data, instance->result_buffer)) {
error = MfDesfireErrorProtocol;
error = MfDesfireErrorNotPresent;
}
} while(false);
@ -310,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*)&current_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,
@ -372,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(
@ -414,13 +438,25 @@ MfDesfireError mf_desfire_poller_read_file_data_multi(
simple_array_init(data, file_id_count);
}
for(uint32_t i = 0; i < file_id_count; ++i) {
for(size_t i = 0; i < file_id_count; ++i) {
const MfDesfireFileId file_id = *(const MfDesfireFileId*)simple_array_cget(file_ids, i);
const MfDesfireFileSettings* file_settings_cur = simple_array_cget(file_settings, i);
const MfDesfireFileType file_type = file_settings_cur->type;
MfDesfireFileData* file_data = simple_array_get(data, i);
bool can_read_data = false;
for(size_t j = 0; j < file_settings_cur->access_rights_len; j++) {
uint8_t read_access = (file_settings_cur->access_rights[j] >> 12) & 0x0f;
uint8_t read_write_access = (file_settings_cur->access_rights[j] >> 4) & 0x0f;
can_read_data = (read_access == 0x0e) || (read_write_access == 0x0e);
if(can_read_data) break;
}
if(!can_read_data) {
FURI_LOG_D(TAG, "Can't read file %zu data without authentication", i);
continue;
}
if(file_type == MfDesfireFileTypeStandard || file_type == MfDesfireFileTypeBackup) {
error = mf_desfire_poller_read_file_data(
instance, file_id, 0, file_settings_cur->data.size, file_data);
@ -432,8 +468,6 @@ MfDesfireError mf_desfire_poller_read_file_data_multi(
error = mf_desfire_poller_read_file_records(
instance, file_id, 0, file_settings_cur->data.size, file_data);
}
if(error != MfDesfireErrorNone) break;
}
return error;
@ -448,22 +482,36 @@ MfDesfireError
do {
error = mf_desfire_poller_read_key_settings(instance, &data->key_settings);
if(error == MfDesfireErrorAuthentication) {
FURI_LOG_D(TAG, "Auth is required to read master key settings and app ids");
data->key_settings.is_free_directory_list = false;
error = MfDesfireErrorNone;
break;
}
if(error != MfDesfireErrorNone) break;
error = mf_desfire_poller_read_key_versions(
instance, data->key_versions, data->key_settings.max_keys);
if(error != MfDesfireErrorNone) break;
if(error != MfDesfireErrorNone) {
FURI_LOG_E(TAG, "Failed to read key version: %d", error);
break;
}
error = mf_desfire_poller_read_file_ids(instance, data->file_ids);
if(error != MfDesfireErrorNone) break;
if(error != MfDesfireErrorNone) {
FURI_LOG_E(TAG, "Failed to read file ids: %d", error);
break;
}
error = mf_desfire_poller_read_file_settings_multi(
instance, data->file_ids, data->file_settings);
if(error != MfDesfireErrorNone) break;
if(error != MfDesfireErrorNone) {
FURI_LOG_E(TAG, "Failed to read file settings: %d", error);
break;
}
error = mf_desfire_poller_read_file_data_multi(
instance, data->file_ids, data->file_settings, data->file_data);
if(error != MfDesfireErrorNone) break;
} while(false);
@ -484,11 +532,13 @@ MfDesfireError mf_desfire_poller_read_applications(
simple_array_init(data, app_id_count);
}
for(uint32_t i = 0; i < app_id_count; ++i) {
for(size_t i = 0; i < app_id_count; ++i) {
do {
FURI_LOG_D(TAG, "Selecting app %zu", i);
error = mf_desfire_poller_select_application(instance, simple_array_cget(app_ids, i));
if(error != MfDesfireErrorNone) break;
FURI_LOG_D(TAG, "Reading app %zu", i);
MfDesfireApplication* current_app = simple_array_get(data, i);
error = mf_desfire_poller_read_application(instance, current_app);

View file

@ -47,6 +47,8 @@ struct MfDesfirePoller {
MfDesfireError mf_desfire_process_error(Iso14443_4aError error);
MfDesfireError mf_desfire_process_status_code(uint8_t status_code);
const MfDesfireData* mf_desfire_poller_get_data(MfDesfirePoller* instance);
#ifdef __cplusplus

View file

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

View file

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

View file

@ -40,6 +40,7 @@ typedef struct {
ProtocolGetData get_data;
ProtocolDecoder decoder;
ProtocolEncoder encoder;
ProtocolRenderData render_uid;
ProtocolRenderData render_data;
ProtocolRenderData render_brief_data;
ProtocolWriteData write_data;

View file

@ -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);
}
}

View file

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

View file

@ -1,5 +1,5 @@
entry,status,name,type,params
Version,+,60.7,,
Version,+,61.2,,
Header,+,applications/services/bt/bt_service/bt.h,,
Header,+,applications/services/cli/cli.h,,
Header,+,applications/services/cli/cli_vcp.h,,
@ -199,6 +199,7 @@ Header,+,targets/f7/platform_specific/cxx_virtual_stub.h,,
Header,+,targets/f7/platform_specific/intrinsic_export.h,,
Header,+,targets/f7/platform_specific/math_wrapper.h,,
Header,+,targets/furi_hal_include/furi_hal.h,,
Header,+,targets/furi_hal_include/furi_hal_adc.h,,
Header,+,targets/furi_hal_include/furi_hal_bt.h,,
Header,+,targets/furi_hal_include/furi_hal_cortex.h,,
Header,+,targets/furi_hal_include/furi_hal_crypto.h,,
@ -1086,6 +1087,16 @@ Function,+,furi_event_flag_get,uint32_t,FuriEventFlag*
Function,+,furi_event_flag_set,uint32_t,"FuriEventFlag*, uint32_t"
Function,+,furi_event_flag_wait,uint32_t,"FuriEventFlag*, uint32_t, uint32_t, uint32_t"
Function,+,furi_get_tick,uint32_t,
Function,+,furi_hal_adc_acquire,FuriHalAdcHandle*,
Function,+,furi_hal_adc_configure,void,FuriHalAdcHandle*
Function,+,furi_hal_adc_configure_ex,void,"FuriHalAdcHandle*, FuriHalAdcScale, FuriHalAdcClock, FuriHalAdcOversample, FuriHalAdcSamplingTime"
Function,+,furi_hal_adc_convert_temp,float,"FuriHalAdcHandle*, uint16_t"
Function,+,furi_hal_adc_convert_to_voltage,float,"FuriHalAdcHandle*, uint16_t"
Function,+,furi_hal_adc_convert_vbat,float,"FuriHalAdcHandle*, uint16_t"
Function,+,furi_hal_adc_convert_vref,float,"FuriHalAdcHandle*, uint16_t"
Function,+,furi_hal_adc_init,void,
Function,+,furi_hal_adc_read,uint16_t,"FuriHalAdcHandle*, FuriHalAdcChannel"
Function,+,furi_hal_adc_release,void,FuriHalAdcHandle*
Function,+,furi_hal_bt_change_app,FuriHalBleProfileBase*,"const FuriHalBleProfileTemplate*, FuriHalBleProfileParams, GapEventCallback, void*"
Function,+,furi_hal_bt_check_profile_type,_Bool,"FuriHalBleProfileBase*, const FuriHalBleProfileTemplate*"
Function,+,furi_hal_bt_clear_white_list,_Bool,
@ -2240,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 entry status name type params
2 Version + 60.7 61.2
3 Header + applications/services/bt/bt_service/bt.h
4 Header + applications/services/cli/cli.h
5 Header + applications/services/cli/cli_vcp.h
199 Header + targets/f7/platform_specific/intrinsic_export.h
200 Header + targets/f7/platform_specific/math_wrapper.h
201 Header + targets/furi_hal_include/furi_hal.h
202 Header + targets/furi_hal_include/furi_hal_adc.h
203 Header + targets/furi_hal_include/furi_hal_bt.h
204 Header + targets/furi_hal_include/furi_hal_cortex.h
205 Header + targets/furi_hal_include/furi_hal_crypto.h
1087 Function + furi_event_flag_set uint32_t FuriEventFlag*, uint32_t
1088 Function + furi_event_flag_wait uint32_t FuriEventFlag*, uint32_t, uint32_t, uint32_t
1089 Function + furi_get_tick uint32_t
1090 Function + furi_hal_adc_acquire FuriHalAdcHandle*
1091 Function + furi_hal_adc_configure void FuriHalAdcHandle*
1092 Function + furi_hal_adc_configure_ex void FuriHalAdcHandle*, FuriHalAdcScale, FuriHalAdcClock, FuriHalAdcOversample, FuriHalAdcSamplingTime
1093 Function + furi_hal_adc_convert_temp float FuriHalAdcHandle*, uint16_t
1094 Function + furi_hal_adc_convert_to_voltage float FuriHalAdcHandle*, uint16_t
1095 Function + furi_hal_adc_convert_vbat float FuriHalAdcHandle*, uint16_t
1096 Function + furi_hal_adc_convert_vref float FuriHalAdcHandle*, uint16_t
1097 Function + furi_hal_adc_init void
1098 Function + furi_hal_adc_read uint16_t FuriHalAdcHandle*, FuriHalAdcChannel
1099 Function + furi_hal_adc_release void FuriHalAdcHandle*
1100 Function + furi_hal_bt_change_app FuriHalBleProfileBase* const FuriHalBleProfileTemplate*, FuriHalBleProfileParams, GapEventCallback, void*
1101 Function + furi_hal_bt_check_profile_type _Bool FuriHalBleProfileBase*, const FuriHalBleProfileTemplate*
1102 Function + furi_hal_bt_clear_white_list _Bool
2251 Function + protocol_dict_get_write_data _Bool ProtocolDict*, size_t, void*
2252 Function + protocol_dict_render_brief_data void ProtocolDict*, FuriString*, size_t
2253 Function + protocol_dict_render_data void ProtocolDict*, FuriString*, size_t
2254 Function + protocol_dict_render_uid void ProtocolDict*, FuriString*, size_t
2255 Function + protocol_dict_set_data void ProtocolDict*, size_t, const uint8_t*, size_t
2256 Function - pulse_reader_alloc PulseReader* const GpioPin*, uint32_t
2257 Function - pulse_reader_free void PulseReader*

View file

@ -31,6 +31,7 @@ void furi_hal_deinit_early(void) {
void furi_hal_init(void) {
furi_hal_mpu_init();
furi_hal_adc_init();
furi_hal_clock_init();
furi_hal_random_init();
furi_hal_serial_control_init();

View file

@ -68,49 +68,161 @@ const GpioPin gpio_usb_dp = {.port = GPIOA, .pin = LL_GPIO_PIN_12};
const GpioPinRecord gpio_pins[] = {
// 5V: 1
{.pin = &gpio_ext_pa7, .name = "PA7", .number = 2, .debug = false},
{.pin = &gpio_ext_pa6, .name = "PA6", .number = 3, .debug = false},
{.pin = &gpio_ext_pa4, .name = "PA4", .number = 4, .debug = false},
{.pin = &gpio_ext_pb3, .name = "PB3", .number = 5, .debug = false},
{.pin = &gpio_ext_pb2, .name = "PB2", .number = 6, .debug = false},
{.pin = &gpio_ext_pc3, .name = "PC3", .number = 7, .debug = false},
{.pin = &gpio_ext_pa7,
.name = "PA7",
.channel = FuriHalAdcChannel12,
.number = 2,
.debug = false},
{.pin = &gpio_ext_pa6,
.name = "PA6",
.channel = FuriHalAdcChannel11,
.number = 3,
.debug = false},
{.pin = &gpio_ext_pa4,
.name = "PA4",
.channel = FuriHalAdcChannel9,
.number = 4,
.debug = false},
{.pin = &gpio_ext_pb3,
.name = "PB3",
.channel = FuriHalAdcChannelNone,
.number = 5,
.debug = false},
{.pin = &gpio_ext_pb2,
.name = "PB2",
.channel = FuriHalAdcChannelNone,
.number = 6,
.debug = false},
{.pin = &gpio_ext_pc3,
.name = "PC3",
.channel = FuriHalAdcChannel4,
.number = 7,
.debug = false},
// GND: 8
// Space
// 3v3: 9
{.pin = &gpio_swclk, .name = "PA14", .number = 10, .debug = true},
{.pin = &gpio_swclk,
.name = "PA14",
.channel = FuriHalAdcChannelNone,
.number = 10,
.debug = true},
// GND: 11
{.pin = &gpio_swdio, .name = "PA13", .number = 12, .debug = true},
{.pin = &gpio_usart_tx, .name = "PB6", .number = 13, .debug = true},
{.pin = &gpio_usart_rx, .name = "PB7", .number = 14, .debug = true},
{.pin = &gpio_ext_pc1, .name = "PC1", .number = 15, .debug = false},
{.pin = &gpio_ext_pc0, .name = "PC0", .number = 16, .debug = false},
{.pin = &gpio_ibutton, .name = "PB14", .number = 17, .debug = true},
{.pin = &gpio_swdio,
.name = "PA13",
.channel = FuriHalAdcChannelNone,
.number = 12,
.debug = true},
{.pin = &gpio_usart_tx,
.name = "PB6",
.channel = FuriHalAdcChannelNone,
.number = 13,
.debug = true},
{.pin = &gpio_usart_rx,
.name = "PB7",
.channel = FuriHalAdcChannelNone,
.number = 14,
.debug = true},
{.pin = &gpio_ext_pc1,
.name = "PC1",
.channel = FuriHalAdcChannel2,
.number = 15,
.debug = false},
{.pin = &gpio_ext_pc0,
.name = "PC0",
.channel = FuriHalAdcChannel1,
.number = 16,
.debug = false},
{.pin = &gpio_ibutton,
.name = "PB14",
.channel = FuriHalAdcChannelNone,
.number = 17,
.debug = true},
// GND: 18
// 2nd column
// 5V: 19
{.pin = &gpio_ext_pc5, .name = "PC5", .number = 20, .debug = false},
{.pin = &gpio_ext_pc4, .name = "PC4", .number = 21, .debug = false},
{.pin = &gpio_ext_pa5, .name = "PA5", .number = 22, .debug = false},
{.pin = &gpio_ext_pb9, .name = "PB9", .number = 23, .debug = false},
{.pin = &gpio_ext_pa0, .name = "PA0", .number = 24, .debug = false},
{.pin = &gpio_ext_pa1, .name = "PA1", .number = 25, .debug = false},
{.pin = &gpio_ext_pc5,
.name = "PC5",
.channel = FuriHalAdcChannel14,
.number = 20,
.debug = false},
{.pin = &gpio_ext_pc4,
.name = "PC4",
.channel = FuriHalAdcChannel13,
.number = 21,
.debug = false},
{.pin = &gpio_ext_pa5,
.name = "PA5",
.channel = FuriHalAdcChannel10,
.number = 22,
.debug = false},
{.pin = &gpio_ext_pb9,
.name = "PB9",
.channel = FuriHalAdcChannelNone,
.number = 23,
.debug = false},
{.pin = &gpio_ext_pa0,
.name = "PA0",
.channel = FuriHalAdcChannel5,
.number = 24,
.debug = false},
{.pin = &gpio_ext_pa1,
.name = "PA1",
.channel = FuriHalAdcChannel6,
.number = 25,
.debug = false},
// KEY: 26
// Space
// 3v3: 27
{.pin = &gpio_ext_pa15, .name = "PA15", .number = 28, .debug = false},
{.pin = &gpio_ext_pa15,
.name = "PA15",
.channel = FuriHalAdcChannelNone,
.number = 28,
.debug = false},
// GND: 29
{.pin = &gpio_ext_pe4, .name = "PE4", .number = 30, .debug = false},
{.pin = &gpio_ext_pa2, .name = "PA2", .number = 31, .debug = false},
{.pin = &gpio_ext_pb4, .name = "PB4", .number = 32, .debug = false},
{.pin = &gpio_ext_pb5, .name = "PB5", .number = 33, .debug = false},
{.pin = &gpio_ext_pd0, .name = "PD0", .number = 34, .debug = false},
{.pin = &gpio_ext_pb13, .name = "PB13", .number = 35, .debug = false},
{.pin = &gpio_ext_pe4,
.name = "PE4",
.channel = FuriHalAdcChannelNone,
.number = 30,
.debug = false},
{.pin = &gpio_ext_pa2,
.name = "PA2",
.channel = FuriHalAdcChannel7,
.number = 31,
.debug = false},
{.pin = &gpio_ext_pb4,
.name = "PB4",
.channel = FuriHalAdcChannelNone,
.number = 32,
.debug = false},
{.pin = &gpio_ext_pb5,
.name = "PB5",
.channel = FuriHalAdcChannelNone,
.number = 33,
.debug = false},
{.pin = &gpio_ext_pd0,
.name = "PD0",
.channel = FuriHalAdcChannelNone,
.number = 34,
.debug = false},
{.pin = &gpio_ext_pb13,
.name = "PB13",
.channel = FuriHalAdcChannelNone,
.number = 35,
.debug = false},
// GND: 36
/* Dangerous pins, may damage hardware */
{.pin = &gpio_usart_rx, .name = "PB7", .number = 0, .debug = true},
{.pin = &gpio_speaker, .name = "PB8", .number = 0, .debug = true},
{.pin = &gpio_usart_rx,
.name = "PB7",
.channel = FuriHalAdcChannelNone,
.number = 0,
.debug = true},
{.pin = &gpio_speaker,
.name = "PB8",
.channel = FuriHalAdcChannelNone,
.number = 0,
.debug = true},
};
const size_t gpio_pins_count = COUNT_OF(gpio_pins);

View file

@ -1,9 +1,7 @@
#pragma once
#include <furi.h>
#include <stm32wbxx.h>
#include <stm32wbxx_ll_gpio.h>
#include <furi_hal_adc.h>
#ifdef __cplusplus
extern "C" {
@ -41,6 +39,7 @@ typedef struct {
typedef struct {
const GpioPin* pin;
const char* name;
const FuriHalAdcChannel channel;
const uint8_t number;
const bool debug;
} GpioPinRecord;

View file

@ -1,5 +1,5 @@
entry,status,name,type,params
Version,+,60.7,,
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,,
@ -272,6 +272,7 @@ Header,+,targets/f7/platform_specific/cxx_virtual_stub.h,,
Header,+,targets/f7/platform_specific/intrinsic_export.h,,
Header,+,targets/f7/platform_specific/math_wrapper.h,,
Header,+,targets/furi_hal_include/furi_hal.h,,
Header,+,targets/furi_hal_include/furi_hal_adc.h,,
Header,+,targets/furi_hal_include/furi_hal_bt.h,,
Header,+,targets/furi_hal_include/furi_hal_cortex.h,,
Header,+,targets/furi_hal_include/furi_hal_crypto.h,,
@ -1215,6 +1216,16 @@ Function,+,furi_event_flag_get,uint32_t,FuriEventFlag*
Function,+,furi_event_flag_set,uint32_t,"FuriEventFlag*, uint32_t"
Function,+,furi_event_flag_wait,uint32_t,"FuriEventFlag*, uint32_t, uint32_t, uint32_t"
Function,+,furi_get_tick,uint32_t,
Function,+,furi_hal_adc_acquire,FuriHalAdcHandle*,
Function,+,furi_hal_adc_configure,void,FuriHalAdcHandle*
Function,+,furi_hal_adc_configure_ex,void,"FuriHalAdcHandle*, FuriHalAdcScale, FuriHalAdcClock, FuriHalAdcOversample, FuriHalAdcSamplingTime"
Function,+,furi_hal_adc_convert_temp,float,"FuriHalAdcHandle*, uint16_t"
Function,+,furi_hal_adc_convert_to_voltage,float,"FuriHalAdcHandle*, uint16_t"
Function,+,furi_hal_adc_convert_vbat,float,"FuriHalAdcHandle*, uint16_t"
Function,+,furi_hal_adc_convert_vref,float,"FuriHalAdcHandle*, uint16_t"
Function,+,furi_hal_adc_init,void,
Function,+,furi_hal_adc_read,uint16_t,"FuriHalAdcHandle*, FuriHalAdcChannel"
Function,+,furi_hal_adc_release,void,FuriHalAdcHandle*
Function,+,furi_hal_bt_change_app,FuriHalBleProfileBase*,"const FuriHalBleProfileTemplate*, FuriHalBleProfileParams, GapEventCallback, void*"
Function,+,furi_hal_bt_check_profile_type,_Bool,"FuriHalBleProfileBase*, const FuriHalBleProfileTemplate*"
Function,+,furi_hal_bt_clear_white_list,_Bool,
@ -1925,6 +1936,7 @@ Function,+,ibutton_protocols_read,_Bool,"iButtonProtocols*, iButtonKey*"
Function,+,ibutton_protocols_render_brief_data,void,"iButtonProtocols*, const iButtonKey*, FuriString*"
Function,+,ibutton_protocols_render_data,void,"iButtonProtocols*, const iButtonKey*, FuriString*"
Function,+,ibutton_protocols_render_error,void,"iButtonProtocols*, const iButtonKey*, FuriString*"
Function,+,ibutton_protocols_render_uid,void,"iButtonProtocols*, const iButtonKey*, FuriString*"
Function,+,ibutton_protocols_save,_Bool,"iButtonProtocols*, const iButtonKey*, const char*"
Function,+,ibutton_protocols_write_blank,_Bool,"iButtonProtocols*, iButtonKey*"
Function,+,ibutton_protocols_write_copy,_Bool,"iButtonProtocols*, iButtonKey*"
@ -2479,6 +2491,10 @@ Function,+,mf_classic_poller_get_nt,MfClassicError,"MfClassicPoller*, uint8_t, M
Function,+,mf_classic_poller_get_nt_nested,MfClassicError,"MfClassicPoller*, uint8_t, MfClassicKeyType, MfClassicNt*"
Function,+,mf_classic_poller_halt,MfClassicError,MfClassicPoller*
Function,+,mf_classic_poller_read_block,MfClassicError,"MfClassicPoller*, uint8_t, MfClassicBlock*"
Function,+,mf_classic_poller_send_custom_parity_frame,MfClassicError,"MfClassicPoller*, const BitBuffer*, BitBuffer*, uint32_t"
Function,+,mf_classic_poller_send_encrypted_frame,MfClassicError,"MfClassicPoller*, const BitBuffer*, BitBuffer*, uint32_t"
Function,+,mf_classic_poller_send_frame,MfClassicError,"MfClassicPoller*, const BitBuffer*, BitBuffer*, uint32_t"
Function,+,mf_classic_poller_send_standard_frame,MfClassicError,"MfClassicPoller*, const BitBuffer*, BitBuffer*, uint32_t"
Function,+,mf_classic_poller_sync_auth,MfClassicError,"Nfc*, uint8_t, MfClassicKey*, MfClassicKeyType, MfClassicAuthContext*"
Function,+,mf_classic_poller_sync_change_value,MfClassicError,"Nfc*, uint8_t, MfClassicKey*, MfClassicKeyType, int32_t, int32_t*"
Function,+,mf_classic_poller_sync_collect_nt,MfClassicError,"Nfc*, uint8_t, MfClassicKeyType, MfClassicNt*"
@ -2882,6 +2898,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 entry status name type params
2 Version + 60.7 61.2
3 Header + applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h
4 Header + applications/services/bt/bt_service/bt.h
5 Header + applications/services/cli/cli.h
272 Header + targets/f7/platform_specific/intrinsic_export.h
273 Header + targets/f7/platform_specific/math_wrapper.h
274 Header + targets/furi_hal_include/furi_hal.h
275 Header + targets/furi_hal_include/furi_hal_adc.h
276 Header + targets/furi_hal_include/furi_hal_bt.h
277 Header + targets/furi_hal_include/furi_hal_cortex.h
278 Header + targets/furi_hal_include/furi_hal_crypto.h
1216 Function + furi_event_flag_set uint32_t FuriEventFlag*, uint32_t
1217 Function + furi_event_flag_wait uint32_t FuriEventFlag*, uint32_t, uint32_t, uint32_t
1218 Function + furi_get_tick uint32_t
1219 Function + furi_hal_adc_acquire FuriHalAdcHandle*
1220 Function + furi_hal_adc_configure void FuriHalAdcHandle*
1221 Function + furi_hal_adc_configure_ex void FuriHalAdcHandle*, FuriHalAdcScale, FuriHalAdcClock, FuriHalAdcOversample, FuriHalAdcSamplingTime
1222 Function + furi_hal_adc_convert_temp float FuriHalAdcHandle*, uint16_t
1223 Function + furi_hal_adc_convert_to_voltage float FuriHalAdcHandle*, uint16_t
1224 Function + furi_hal_adc_convert_vbat float FuriHalAdcHandle*, uint16_t
1225 Function + furi_hal_adc_convert_vref float FuriHalAdcHandle*, uint16_t
1226 Function + furi_hal_adc_init void
1227 Function + furi_hal_adc_read uint16_t FuriHalAdcHandle*, FuriHalAdcChannel
1228 Function + furi_hal_adc_release void FuriHalAdcHandle*
1229 Function + furi_hal_bt_change_app FuriHalBleProfileBase* const FuriHalBleProfileTemplate*, FuriHalBleProfileParams, GapEventCallback, void*
1230 Function + furi_hal_bt_check_profile_type _Bool FuriHalBleProfileBase*, const FuriHalBleProfileTemplate*
1231 Function + furi_hal_bt_clear_white_list _Bool
1936 Function + ibutton_protocols_render_brief_data void iButtonProtocols*, const iButtonKey*, FuriString*
1937 Function + ibutton_protocols_render_data void iButtonProtocols*, const iButtonKey*, FuriString*
1938 Function + ibutton_protocols_render_error void iButtonProtocols*, const iButtonKey*, FuriString*
1939 Function + ibutton_protocols_render_uid void iButtonProtocols*, const iButtonKey*, FuriString*
1940 Function + ibutton_protocols_save _Bool iButtonProtocols*, const iButtonKey*, const char*
1941 Function + ibutton_protocols_write_blank _Bool iButtonProtocols*, iButtonKey*
1942 Function + ibutton_protocols_write_copy _Bool iButtonProtocols*, iButtonKey*
2491 Function + mf_classic_poller_get_nt_nested MfClassicError MfClassicPoller*, uint8_t, MfClassicKeyType, MfClassicNt*
2492 Function + mf_classic_poller_halt MfClassicError MfClassicPoller*
2493 Function + mf_classic_poller_read_block MfClassicError MfClassicPoller*, uint8_t, MfClassicBlock*
2494 Function + mf_classic_poller_send_custom_parity_frame MfClassicError MfClassicPoller*, const BitBuffer*, BitBuffer*, uint32_t
2495 Function + mf_classic_poller_send_encrypted_frame MfClassicError MfClassicPoller*, const BitBuffer*, BitBuffer*, uint32_t
2496 Function + mf_classic_poller_send_frame MfClassicError MfClassicPoller*, const BitBuffer*, BitBuffer*, uint32_t
2497 Function + mf_classic_poller_send_standard_frame MfClassicError MfClassicPoller*, const BitBuffer*, BitBuffer*, uint32_t
2498 Function + mf_classic_poller_sync_auth MfClassicError Nfc*, uint8_t, MfClassicKey*, MfClassicKeyType, MfClassicAuthContext*
2499 Function + mf_classic_poller_sync_change_value MfClassicError Nfc*, uint8_t, MfClassicKey*, MfClassicKeyType, int32_t, int32_t*
2500 Function + mf_classic_poller_sync_collect_nt MfClassicError Nfc*, uint8_t, MfClassicKeyType, MfClassicNt*
2898 Function + protocol_dict_get_write_data _Bool ProtocolDict*, size_t, void*
2899 Function + protocol_dict_render_brief_data void ProtocolDict*, FuriString*, size_t
2900 Function + protocol_dict_render_data void ProtocolDict*, FuriString*, size_t
2901 Function + protocol_dict_render_uid void ProtocolDict*, FuriString*, size_t
2902 Function + protocol_dict_set_data void ProtocolDict*, size_t, const uint8_t*, size_t
2903 Function - pulse_reader_alloc PulseReader* const GpioPin*, uint32_t
2904 Function - pulse_reader_free void PulseReader*

View file

@ -9,7 +9,6 @@
/**
* Define IO Authentication
*/
#define CFG_USED_FIXED_PIN USE_FIXED_PIN_FOR_PAIRING_FORBIDDEN
#define CFG_ENCRYPTION_KEY_SIZE_MAX (16)
#define CFG_ENCRYPTION_KEY_SIZE_MIN (8)
@ -18,11 +17,6 @@
*/
#define CFG_IO_CAPABILITY IO_CAP_DISPLAY_YES_NO
/**
* Define MITM modes
*/
#define CFG_MITM_PROTECTION MITM_PROTECTION_REQUIRED
/**
* Define Secure Connections Support
*/

View file

@ -64,7 +64,7 @@ static int32_t gap_app(void* context);
/** function for updating rssi informations in global Gap object
*
*/
static inline void fetch_rssi() {
static inline void fetch_rssi(void) {
uint8_t ret_rssi = 127;
if(hci_read_rssi(gap->service.connection_handle, &ret_rssi) == BLE_STATUS_SUCCESS) {
gap->conn_rssi = (int8_t)ret_rssi;
@ -372,9 +372,8 @@ static void gap_init_svc(Gap* gap) {
// Set default PHY
hci_le_set_default_phy(ALL_PHYS_PREFERENCE, TX_2M_PREFERRED, RX_2M_PREFERRED);
// Set I/O capability
bool bonding_mode = gap->config->bonding_mode;
uint8_t cfg_mitm_protection = CFG_MITM_PROTECTION;
uint8_t cfg_used_fixed_pin = CFG_USED_FIXED_PIN;
uint8_t auth_req_mitm_mode = MITM_PROTECTION_REQUIRED;
uint8_t auth_req_use_fixed_pin = USE_FIXED_PIN_FOR_PAIRING_FORBIDDEN;
bool keypress_supported = false;
if(gap->config->pairing_method == GapPairingPinCodeShow) {
aci_gap_set_io_capability(IO_CAP_DISPLAY_ONLY);
@ -383,22 +382,21 @@ static void gap_init_svc(Gap* gap) {
keypress_supported = true;
} else if(gap->config->pairing_method == GapPairingNone) {
// "Just works" pairing method (iOS accepts it, it seems Android and Linux don't)
bonding_mode = false;
cfg_mitm_protection = MITM_PROTECTION_NOT_REQUIRED;
cfg_used_fixed_pin = USE_FIXED_PIN_FOR_PAIRING_ALLOWED;
auth_req_mitm_mode = MITM_PROTECTION_NOT_REQUIRED;
auth_req_use_fixed_pin = USE_FIXED_PIN_FOR_PAIRING_ALLOWED;
// If "just works" isn't supported, we want the numeric comparaison method
aci_gap_set_io_capability(IO_CAP_DISPLAY_YES_NO);
keypress_supported = true;
}
// Setup authentication
aci_gap_set_authentication_requirement(
bonding_mode,
cfg_mitm_protection,
gap->config->bonding_mode,
auth_req_mitm_mode,
CFG_SC_SUPPORT,
keypress_supported,
CFG_ENCRYPTION_KEY_SIZE_MIN,
CFG_ENCRYPTION_KEY_SIZE_MAX,
cfg_used_fixed_pin,
auth_req_use_fixed_pin,
0,
CFG_IDENTITY_ADDRESS);
// Configure whitelist

View file

@ -52,6 +52,7 @@ typedef enum {
GapPairingNone,
GapPairingPinCodeShow,
GapPairingPinCodeVerifyYesNo,
GapPairingCount,
} GapPairing;
typedef struct {

View file

@ -31,6 +31,7 @@ void furi_hal_deinit_early(void) {
void furi_hal_init(void) {
furi_hal_mpu_init();
furi_hal_adc_init();
furi_hal_clock_init();
furi_hal_random_init();
furi_hal_serial_control_init();

View file

@ -0,0 +1,281 @@
#include <furi_hal_adc.h>
#include <furi_hal_bus.h>
#include <furi_hal_cortex.h>
#include <furi_hal_power.h>
#include <furi.h>
#include <stm32wbxx_ll_adc.h>
#include <stm32wbxx_ll_system.h>
struct FuriHalAdcHandle {
ADC_TypeDef* adc;
FuriMutex* mutex;
uint32_t full_scale;
};
static const uint32_t furi_hal_adc_clock[] = {
[FuriHalAdcClockSync16] = LL_ADC_CLOCK_SYNC_PCLK_DIV4,
[FuriHalAdcClockSync32] = LL_ADC_CLOCK_SYNC_PCLK_DIV2,
[FuriHalAdcClockSync64] = LL_ADC_CLOCK_SYNC_PCLK_DIV1,
};
static const uint8_t furi_hal_adc_clock_div[] = {
[FuriHalAdcClockSync16] = 4,
[FuriHalAdcClockSync32] = 2,
[FuriHalAdcClockSync64] = 1,
};
static const uint32_t furi_hal_adc_oversample_ratio[] = {
[FuriHalAdcOversample2] = LL_ADC_OVS_RATIO_2,
[FuriHalAdcOversample4] = LL_ADC_OVS_RATIO_4,
[FuriHalAdcOversample8] = LL_ADC_OVS_RATIO_8,
[FuriHalAdcOversample16] = LL_ADC_OVS_RATIO_16,
[FuriHalAdcOversample32] = LL_ADC_OVS_RATIO_32,
[FuriHalAdcOversample64] = LL_ADC_OVS_RATIO_64,
[FuriHalAdcOversample128] = LL_ADC_OVS_RATIO_128,
[FuriHalAdcOversample256] = LL_ADC_OVS_RATIO_256,
};
static const uint32_t furi_hal_adc_oversample_shift[] = {
[FuriHalAdcOversample2] = LL_ADC_OVS_SHIFT_RIGHT_1,
[FuriHalAdcOversample4] = LL_ADC_OVS_SHIFT_RIGHT_2,
[FuriHalAdcOversample8] = LL_ADC_OVS_SHIFT_RIGHT_3,
[FuriHalAdcOversample16] = LL_ADC_OVS_SHIFT_RIGHT_4,
[FuriHalAdcOversample32] = LL_ADC_OVS_SHIFT_RIGHT_5,
[FuriHalAdcOversample64] = LL_ADC_OVS_SHIFT_RIGHT_6,
[FuriHalAdcOversample128] = LL_ADC_OVS_SHIFT_RIGHT_7,
[FuriHalAdcOversample256] = LL_ADC_OVS_SHIFT_RIGHT_8,
};
static const uint32_t furi_hal_adc_sampling_time[] = {
[FuriHalAdcSamplingtime2_5] = LL_ADC_SAMPLINGTIME_2CYCLES_5,
[FuriHalAdcSamplingtime6_5] = LL_ADC_SAMPLINGTIME_6CYCLES_5,
[FuriHalAdcSamplingtime12_5] = LL_ADC_SAMPLINGTIME_12CYCLES_5,
[FuriHalAdcSamplingtime24_5] = LL_ADC_SAMPLINGTIME_24CYCLES_5,
[FuriHalAdcSamplingtime47_5] = LL_ADC_SAMPLINGTIME_47CYCLES_5,
[FuriHalAdcSamplingtime92_5] = LL_ADC_SAMPLINGTIME_92CYCLES_5,
[FuriHalAdcSamplingtime247_5] = LL_ADC_SAMPLINGTIME_247CYCLES_5,
[FuriHalAdcSamplingtime640_5] = LL_ADC_SAMPLINGTIME_640CYCLES_5,
};
static const uint32_t furi_hal_adc_channel_map[] = {
[FuriHalAdcChannel0] = LL_ADC_CHANNEL_0,
[FuriHalAdcChannel1] = LL_ADC_CHANNEL_1,
[FuriHalAdcChannel2] = LL_ADC_CHANNEL_2,
[FuriHalAdcChannel3] = LL_ADC_CHANNEL_3,
[FuriHalAdcChannel4] = LL_ADC_CHANNEL_4,
[FuriHalAdcChannel5] = LL_ADC_CHANNEL_5,
[FuriHalAdcChannel6] = LL_ADC_CHANNEL_6,
[FuriHalAdcChannel7] = LL_ADC_CHANNEL_7,
[FuriHalAdcChannel8] = LL_ADC_CHANNEL_8,
[FuriHalAdcChannel9] = LL_ADC_CHANNEL_9,
[FuriHalAdcChannel10] = LL_ADC_CHANNEL_10,
[FuriHalAdcChannel11] = LL_ADC_CHANNEL_11,
[FuriHalAdcChannel12] = LL_ADC_CHANNEL_12,
[FuriHalAdcChannel13] = LL_ADC_CHANNEL_13,
[FuriHalAdcChannel14] = LL_ADC_CHANNEL_14,
[FuriHalAdcChannel15] = LL_ADC_CHANNEL_15,
[FuriHalAdcChannel16] = LL_ADC_CHANNEL_16,
[FuriHalAdcChannel17] = LL_ADC_CHANNEL_17,
[FuriHalAdcChannel18] = LL_ADC_CHANNEL_18,
[FuriHalAdcChannelVREFINT] = LL_ADC_CHANNEL_VREFINT,
[FuriHalAdcChannelTEMPSENSOR] = LL_ADC_CHANNEL_TEMPSENSOR,
[FuriHalAdcChannelVBAT] = LL_ADC_CHANNEL_VBAT,
};
static FuriHalAdcHandle* furi_hal_adc_handle = NULL;
void furi_hal_adc_init(void) {
furi_hal_adc_handle = malloc(sizeof(FuriHalAdcHandle));
furi_hal_adc_handle->adc = ADC1;
furi_hal_adc_handle->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
}
FuriHalAdcHandle* furi_hal_adc_acquire(void) {
furi_check(furi_mutex_acquire(furi_hal_adc_handle->mutex, FuriWaitForever) == FuriStatusOk);
furi_hal_power_insomnia_enter();
furi_hal_bus_enable(FuriHalBusADC);
return furi_hal_adc_handle;
}
void furi_hal_adc_release(FuriHalAdcHandle* handle) {
furi_check(handle);
if(furi_hal_bus_is_enabled(FuriHalBusADC)) furi_hal_bus_disable(FuriHalBusADC);
LL_VREFBUF_Disable();
LL_VREFBUF_EnableHIZ();
furi_hal_power_insomnia_exit();
furi_check(furi_mutex_release(furi_hal_adc_handle->mutex) == FuriStatusOk);
}
void furi_hal_adc_configure(FuriHalAdcHandle* handle) {
furi_hal_adc_configure_ex(
handle,
FuriHalAdcScale2048,
FuriHalAdcClockSync64,
FuriHalAdcOversample64,
FuriHalAdcSamplingtime247_5);
}
void furi_hal_adc_configure_ex(
FuriHalAdcHandle* handle,
FuriHalAdcScale scale,
FuriHalAdcClock clock,
FuriHalAdcOversample oversample,
FuriHalAdcSamplingTime sampling_time) {
furi_check(handle);
furi_check(scale == FuriHalAdcScale2048 || scale == FuriHalAdcScale2500);
furi_check(clock <= FuriHalAdcClockSync64);
furi_check(oversample <= FuriHalAdcOversampleNone);
furi_check(sampling_time <= FuriHalAdcSamplingtime640_5);
FuriHalCortexTimer timer;
if(furi_hal_bus_is_enabled(FuriHalBusADC)) furi_hal_bus_disable(FuriHalBusADC);
uint32_t trim_value = 0;
switch(scale) {
case FuriHalAdcScale2048:
LL_VREFBUF_SetVoltageScaling(LL_VREFBUF_VOLTAGE_SCALE0);
trim_value = LL_VREFBUF_SC0_GetCalibration() & 0x3FU;
handle->full_scale = 2048;
break;
case FuriHalAdcScale2500:
LL_VREFBUF_SetVoltageScaling(LL_VREFBUF_VOLTAGE_SCALE1);
trim_value = LL_VREFBUF_SC1_GetCalibration() & 0x3FU;
handle->full_scale = 2500;
break;
default:
furi_crash();
}
LL_VREFBUF_SetTrimming(trim_value);
LL_VREFBUF_Enable();
LL_VREFBUF_DisableHIZ();
timer = furi_hal_cortex_timer_get(500000); // 500ms to stabilize VREF
while(!LL_VREFBUF_IsVREFReady()) {
furi_check(!furi_hal_cortex_timer_is_expired(timer), "VREF fail");
};
furi_hal_bus_enable(FuriHalBusADC);
// ADC Common config
LL_ADC_CommonInitTypeDef ADC_CommonInitStruct = {0};
ADC_CommonInitStruct.CommonClock = furi_hal_adc_clock[clock];
furi_check(
LL_ADC_CommonInit(__LL_ADC_COMMON_INSTANCE(handle->adc), &ADC_CommonInitStruct) ==
SUCCESS);
LL_ADC_SetCommonPathInternalCh(
__LL_ADC_COMMON_INSTANCE(handle->adc),
LL_ADC_PATH_INTERNAL_VREFINT | LL_ADC_PATH_INTERNAL_TEMPSENSOR |
LL_ADC_PATH_INTERNAL_VBAT);
// ADC config part 1
LL_ADC_InitTypeDef ADC_InitStruct = {0};
ADC_InitStruct.Resolution = LL_ADC_RESOLUTION_12B; //-V1048
ADC_InitStruct.DataAlignment = LL_ADC_DATA_ALIGN_RIGHT;
ADC_InitStruct.LowPowerMode = LL_ADC_LP_MODE_NONE;
furi_check(LL_ADC_Init(handle->adc, &ADC_InitStruct) == SUCCESS);
// ADC config part 2: groups parameters
LL_ADC_REG_InitTypeDef ADC_REG_InitStruct = {0};
ADC_REG_InitStruct.TriggerSource = LL_ADC_REG_TRIG_SOFTWARE; //-V1048
ADC_REG_InitStruct.SequencerLength = LL_ADC_REG_SEQ_SCAN_DISABLE;
ADC_REG_InitStruct.SequencerDiscont = LL_ADC_REG_SEQ_DISCONT_DISABLE;
ADC_REG_InitStruct.ContinuousMode = LL_ADC_REG_CONV_SINGLE;
ADC_REG_InitStruct.DMATransfer = LL_ADC_REG_DMA_TRANSFER_NONE;
ADC_REG_InitStruct.Overrun = LL_ADC_REG_OVR_DATA_OVERWRITTEN;
furi_check(LL_ADC_REG_Init(handle->adc, &ADC_REG_InitStruct) == SUCCESS);
// ADC config part 3: sequencer and channels
if(oversample == FuriHalAdcOversampleNone) {
LL_ADC_SetOverSamplingScope(handle->adc, LL_ADC_OVS_DISABLE);
} else {
LL_ADC_SetOverSamplingScope(handle->adc, LL_ADC_OVS_GRP_REGULAR_CONTINUED);
LL_ADC_ConfigOverSamplingRatioShift(
handle->adc,
furi_hal_adc_oversample_ratio[oversample],
furi_hal_adc_oversample_shift[oversample]);
}
for(FuriHalAdcChannel channel = FuriHalAdcChannel0; channel < FuriHalAdcChannelNone;
channel++) {
// 47.5 cycles on 64MHz is first meaningful value for internal sources sampling
LL_ADC_SetChannelSamplingTime(
handle->adc,
furi_hal_adc_channel_map[channel],
furi_hal_adc_sampling_time[sampling_time]);
LL_ADC_SetChannelSingleDiff(
handle->adc, furi_hal_adc_channel_map[channel], LL_ADC_SINGLE_ENDED);
}
// Disable ADC deep power down (enabled by default after reset state)
LL_ADC_DisableDeepPowerDown(handle->adc);
// Enable ADC internal voltage regulator
LL_ADC_EnableInternalRegulator(handle->adc);
// Delay for ADC internal voltage regulator stabilization.
timer = furi_hal_cortex_timer_get(LL_ADC_DELAY_INTERNAL_REGUL_STAB_US);
while(!furi_hal_cortex_timer_is_expired(timer))
;
// Run ADC self calibration
LL_ADC_StartCalibration(handle->adc, LL_ADC_SINGLE_ENDED);
// Poll for ADC effectively calibrated
while(LL_ADC_IsCalibrationOnGoing(handle->adc) != 0)
;
// Delay between ADC end of calibration and ADC enable
size_t end =
DWT->CYCCNT + (LL_ADC_DELAY_CALIB_ENABLE_ADC_CYCLES * furi_hal_adc_clock_div[clock]);
while(DWT->CYCCNT < end)
;
// Enable ADC
LL_ADC_ClearFlag_ADRDY(handle->adc);
LL_ADC_Enable(handle->adc);
while(LL_ADC_IsActiveFlag_ADRDY(handle->adc) == 0)
;
}
uint16_t furi_hal_adc_read(FuriHalAdcHandle* handle, FuriHalAdcChannel channel) {
furi_check(handle);
furi_check(channel <= FuriHalAdcChannelVBAT);
furi_check(LL_ADC_IsEnabled(handle->adc) == 1);
furi_check(LL_ADC_IsDisableOngoing(handle->adc) == 0);
furi_check(LL_ADC_REG_IsConversionOngoing(handle->adc) == 0);
LL_ADC_REG_SetSequencerRanks(
handle->adc, LL_ADC_REG_RANK_1, furi_hal_adc_channel_map[channel]);
LL_ADC_REG_StartConversion(handle->adc);
while(LL_ADC_IsActiveFlag_EOC(handle->adc) == 0)
;
uint16_t value = LL_ADC_REG_ReadConversionData12(handle->adc);
return value;
}
float furi_hal_adc_convert_to_voltage(FuriHalAdcHandle* handle, uint16_t value) {
return (float)__LL_ADC_CALC_DATA_TO_VOLTAGE(handle->full_scale, value, LL_ADC_RESOLUTION_12B);
}
float furi_hal_adc_convert_vref(FuriHalAdcHandle* handle, uint16_t value) {
UNUSED(handle);
return (float)__LL_ADC_CALC_VREFANALOG_VOLTAGE(value, LL_ADC_RESOLUTION_12B);
}
float furi_hal_adc_convert_temp(FuriHalAdcHandle* handle, uint16_t value) {
return (float)__LL_ADC_CALC_TEMPERATURE(handle->full_scale, value, LL_ADC_RESOLUTION_12B);
}
float furi_hal_adc_convert_vbat(FuriHalAdcHandle* handle, uint16_t value) {
return furi_hal_adc_convert_to_voltage(handle, value) * 3;
}

View file

@ -121,6 +121,7 @@ void furi_hal_clock_init(void) {
LL_RCC_SetSMPSClockSource(LL_RCC_SMPS_CLKSOURCE_HSI);
LL_RCC_SetSMPSPrescaler(LL_RCC_SMPS_DIV_1);
LL_RCC_SetRFWKPClockSource(LL_RCC_RFWKP_CLKSOURCE_LSE);
LL_RCC_SetADCClockSource(LL_RCC_ADC_CLKSOURCE_SYSCLK);
FURI_LOG_I(TAG, "Init OK");
}

View file

@ -70,28 +70,88 @@ const GpioPin gpio_usb_dp = {.port = GPIOA, .pin = LL_GPIO_PIN_12};
const GpioPinRecord gpio_pins[] = {
// 5V: 1
{.pin = &gpio_ext_pa7, .name = "PA7", .number = 2, .debug = false},
{.pin = &gpio_ext_pa6, .name = "PA6", .number = 3, .debug = false},
{.pin = &gpio_ext_pa4, .name = "PA4", .number = 4, .debug = false},
{.pin = &gpio_ext_pb3, .name = "PB3", .number = 5, .debug = false},
{.pin = &gpio_ext_pb2, .name = "PB2", .number = 6, .debug = false},
{.pin = &gpio_ext_pc3, .name = "PC3", .number = 7, .debug = false},
{.pin = &gpio_ext_pa7,
.name = "PA7",
.channel = FuriHalAdcChannel12,
.number = 2,
.debug = false},
{.pin = &gpio_ext_pa6,
.name = "PA6",
.channel = FuriHalAdcChannel11,
.number = 3,
.debug = false},
{.pin = &gpio_ext_pa4,
.name = "PA4",
.channel = FuriHalAdcChannel9,
.number = 4,
.debug = false},
{.pin = &gpio_ext_pb3,
.name = "PB3",
.channel = FuriHalAdcChannelNone,
.number = 5,
.debug = false},
{.pin = &gpio_ext_pb2,
.name = "PB2",
.channel = FuriHalAdcChannelNone,
.number = 6,
.debug = false},
{.pin = &gpio_ext_pc3,
.name = "PC3",
.channel = FuriHalAdcChannel4,
.number = 7,
.debug = false},
// GND: 8
// Space
// 3v3: 9
{.pin = &gpio_swclk, .name = "PA14", .number = 10, .debug = true},
{.pin = &gpio_swclk,
.name = "PA14",
.channel = FuriHalAdcChannelNone,
.number = 10,
.debug = true},
// GND: 11
{.pin = &gpio_swdio, .name = "PA13", .number = 12, .debug = true},
{.pin = &gpio_usart_tx, .name = "PB6", .number = 13, .debug = true},
{.pin = &gpio_usart_rx, .name = "PB7", .number = 14, .debug = true},
{.pin = &gpio_ext_pc1, .name = "PC1", .number = 15, .debug = false},
{.pin = &gpio_ext_pc0, .name = "PC0", .number = 16, .debug = false},
{.pin = &gpio_ibutton, .name = "PB14", .number = 17, .debug = true},
{.pin = &gpio_swdio,
.name = "PA13",
.channel = FuriHalAdcChannelNone,
.number = 12,
.debug = true},
{.pin = &gpio_usart_tx,
.name = "PB6",
.channel = FuriHalAdcChannelNone,
.number = 13,
.debug = true},
{.pin = &gpio_usart_rx,
.name = "PB7",
.channel = FuriHalAdcChannelNone,
.number = 14,
.debug = true},
{.pin = &gpio_ext_pc1,
.name = "PC1",
.channel = FuriHalAdcChannel2,
.number = 15,
.debug = false},
{.pin = &gpio_ext_pc0,
.name = "PC0",
.channel = FuriHalAdcChannel1,
.number = 16,
.debug = false},
{.pin = &gpio_ibutton,
.name = "PB14",
.channel = FuriHalAdcChannelNone,
.number = 17,
.debug = true},
// GND: 18
/* Dangerous pins, may damage hardware */
{.pin = &gpio_speaker, .name = "PB8", .debug = true},
{.pin = &gpio_infrared_tx, .name = "PB9", .debug = true},
{.pin = &gpio_speaker,
.name = "PB8",
.channel = FuriHalAdcChannelNone,
.number = 0,
.debug = true},
{.pin = &gpio_infrared_tx,
.name = "PB9",
.channel = FuriHalAdcChannelNone,
.number = 0,
.debug = true},
};
const size_t gpio_pins_count = COUNT_OF(gpio_pins);

View file

@ -1,9 +1,7 @@
#pragma once
#include <furi.h>
#include <stm32wbxx.h>
#include <stm32wbxx_ll_gpio.h>
#include <furi_hal_adc.h>
#ifdef __cplusplus
extern "C" {
@ -41,6 +39,7 @@ typedef struct {
typedef struct {
const GpioPin* pin;
const char* name;
const FuriHalAdcChannel channel;
const uint8_t number;
const bool debug;
} GpioPinRecord;
@ -220,10 +219,11 @@ void furi_hal_resources_deinit_early(void);
void furi_hal_resources_init(void);
/**
* Get a corresponding external connector pin number for a gpio
* @param gpio GpioPin
* @return pin number or -1 if gpio is not on the external connector
/** Get a corresponding external connector pin number for a gpio
*
* @param gpio GpioPin
*
* @return pin number or -1 if gpio is not on the external connector
*/
int32_t furi_hal_resources_get_ext_pin_number(const GpioPin* gpio);

View file

@ -12,6 +12,7 @@ struct STOP_EXTERNING_ME {};
#include <furi_hal_cortex.h>
#include <furi_hal_clock.h>
#include <furi_hal_adc.h>
#include <furi_hal_bus.h>
#include <furi_hal_crypto.h>
#include <furi_hal_debug.h>

View file

@ -0,0 +1,229 @@
/**
* @file furi_hal_adc.h
* @brief ADC HAL API
*
* For the sake of simplicity this API implements only small subset
* of what ADC is actually capable of. Feel free to visit Reference
* Manual for STM32WB series and implement any other modes by your
* self.
*
* Couple things to keep in mind:
*
* - ADC resolution is 12 bits, but effective number of bits is ~10 at the best
* and further depends on how you use and configure it.
* - Analog domain is fed from SMPS which is quite noisy.
* - Because of that we use internal on-chip voltage reference for ADC.
* - It's capable of producing 2 voltages: 2.5V and 2.048V. This is the scale
* for your signal.
* - Only single ended mode is available. But you can implement differential one
* by using low level controls directly.
* - No DMA or interrupt API available at this point. But can be implemented
* with low level controls.
*
*
* How to use:
*
* - furi_hal_gpio_init - Configure your pins in `GpioModeAnalog`
* - furi_hal_adc_acquire - acquire ADC handle to work with
* - furi_hal_adc_configure - configure ADC block
* - furi_hal_adc_read - read value
* - furi_hal_adc_release - release ADC handle
*/
#pragma once
#include <furi.h>
#include <stdbool.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef struct FuriHalAdcHandle FuriHalAdcHandle;
typedef enum {
FuriHalAdcScale2048, /**< 2.048V scale */
FuriHalAdcScale2500, /**< 2.5V scale */
} FuriHalAdcScale;
typedef enum {
FuriHalAdcClockSync16, /**< 16MHZ, synchronous */
FuriHalAdcClockSync32, /**< 32MHZ, synchronous */
FuriHalAdcClockSync64, /**< 64MHz, synchronous */
} FuriHalAdcClock;
typedef enum {
FuriHalAdcOversample2, /**< ADC will take 2 samples per each value */
FuriHalAdcOversample4, /**< ADC will take 4 samples per each value */
FuriHalAdcOversample8, /**< ADC will take 8 samples per each value */
FuriHalAdcOversample16, /**< ADC will take 16 samples per each value */
FuriHalAdcOversample32, /**< ADC will take 32 samples per each value */
FuriHalAdcOversample64, /**< ADC will take 64 samples per each value */
FuriHalAdcOversample128, /**< ADC will take 128 samples per each value */
FuriHalAdcOversample256, /**< ADC will take 256 samples per each value */
FuriHalAdcOversampleNone, /**< disable oversampling */
} FuriHalAdcOversample;
typedef enum {
FuriHalAdcSamplingtime2_5, /**< Sampling time 2.5 ADC clock */
FuriHalAdcSamplingtime6_5, /**< Sampling time 6.5 ADC clock */
FuriHalAdcSamplingtime12_5, /**< Sampling time 12.5 ADC clock */
FuriHalAdcSamplingtime24_5, /**< Sampling time 24.5 ADC clock */
FuriHalAdcSamplingtime47_5, /**< Sampling time 47.5 ADC clock */
FuriHalAdcSamplingtime92_5, /**< Sampling time 92.5 ADC clock */
FuriHalAdcSamplingtime247_5, /**< Sampling time 247.5 ADC clock */
FuriHalAdcSamplingtime640_5, /**< Sampling time 640.5 ADC clock */
} FuriHalAdcSamplingTime;
typedef enum {
/* Channels 0 - 5 are fast channels */
FuriHalAdcChannel0, /**< Internal channel, see `FuriHalAdcChannelVREFINT`. */
FuriHalAdcChannel1, /**< Channel 1p */
FuriHalAdcChannel2, /**< Channel 2p or 1n */
FuriHalAdcChannel3, /**< Channel 3p or 2n */
FuriHalAdcChannel4, /**< Channel 4p or 3n */
FuriHalAdcChannel5, /**< Channel 5p or 4n */
/* Channels 6 - 18 are slow channels */
FuriHalAdcChannel6, /**< Channel 6p or 5n */
FuriHalAdcChannel7, /**< Channel 7p or 6n */
FuriHalAdcChannel8, /**< Channel 8p or 7n */
FuriHalAdcChannel9, /**< Channel 9p or 8n */
FuriHalAdcChannel10, /**< Channel 10p or 9n */
FuriHalAdcChannel11, /**< Channel 11p or 10n */
FuriHalAdcChannel12, /**< Channel 12p or 11n */
FuriHalAdcChannel13, /**< Channel 13p or 12n */
FuriHalAdcChannel14, /**< Channel 14p or 13n */
FuriHalAdcChannel15, /**< Channel 15p or 14n */
FuriHalAdcChannel16, /**< Channel 16p or 15n */
FuriHalAdcChannel17, /**< Internal channel, see `FuriHalAdcChannelTEMPSENSOR`. */
FuriHalAdcChannel18, /**< Internal channel, see `FuriHalAdcChannelVBAT`. */
/* Special Channels: combines one of the 0-18 channel and additional internal peripherals */
FuriHalAdcChannelVREFINT, /**< Special channel for VREFINT, used for calibration and self test */
FuriHalAdcChannelTEMPSENSOR, /**< Special channel for on-die temperature sensor, requires at least 5us of sampling time */
FuriHalAdcChannelVBAT, /**< Special channel for VBAT/3 voltage, requires at least 12us of sampling time */
/* Special value to indicate that pin is not connected to ADC */
FuriHalAdcChannelNone, /**< No channel */
} FuriHalAdcChannel;
/** Initialize ADC subsystem */
void furi_hal_adc_init(void);
/** Acquire ADC handle
*
* Enables appropriate power and clocking domains
*
* @return FuriHalAdcHandle pointer
*/
FuriHalAdcHandle* furi_hal_adc_acquire(void);
/** Release ADC handle
*
* @param handle The ADC handle
*/
void furi_hal_adc_release(FuriHalAdcHandle* handle);
/** Configure with default parameters and enable ADC
*
* Parameters used:
* - FuriHalAdcScale2048 - 2.048V VREF Scale. Your signal should be in 0 -
* 2.048V range.
* - FuriHalAdcClockSync64 - Clocked from sysclk bus at 64MHz in synchronous
* mode. Fast, no delay on data bus access.
* - FuriHalAdcOversample64 - Going to acquire and average 64 samples. For
* circuits with slowly or not changing signal. Total time per one read:
* (1/64)*(12.5+247.5)*64 = 260us. The best results you'll get if your signal
* will stay on the same level all this time.
* - FuriHalAdcSamplingtime247_5 - Sampling(transfer from source to internal
* sampling capacitor) time is 247.5 ADC clocks: (1/64)*247.5 = 3.8671875us.
* For relatively high impedance circuits.
*
* Also keep your measurement circuit impedance under 10KOhm or oversampling
* results will be compromised. Verify your signal with oscilloscope(you may
* need fast oscilloscope: 200MHz bandwidth, 125MS/s), ensure that signal is not
* distorted by sampling.
*
* Those parameters were optimized for 0 - 2.048 voltage measurement with ~0.1%
* precision. You can get more, but it will require some magic.
*
* @param handle The ADC handle
*/
void furi_hal_adc_configure(FuriHalAdcHandle* handle);
/** Configure with extended parameters and enable ADC
*
* General advice is to start with default parameters, figure out what exactly
* is not working for you and then tune it. Also in some cases changing
* circuit(adding caps, lowering impedance, adding opamp) may be better than changing
* parameters.
*
* @warning In general ADC is a world of magic: make sure that you understand
* how your circuit and ADC works. Then carefully read STM32WB
* series reference manual. Setting incorrect parameters leads to
* very poor results. Also internal channels require special
* settings.
*
* @param handle The ADC handle
* @param[in] scale The ADC voltage scale
* @param[in] clock The ADC clock
* @param[in] oversample The ADC oversample mode
* @param[in] sampling_time The ADC sampling time
*/
void furi_hal_adc_configure_ex(
FuriHalAdcHandle* handle,
FuriHalAdcScale scale,
FuriHalAdcClock clock,
FuriHalAdcOversample oversample,
FuriHalAdcSamplingTime sampling_time);
/** Read single ADC value
*
* @param handle The ADC handle
* @param[in] channel The channel to sample
*
* @return value, 12 bits
*/
uint16_t furi_hal_adc_read(FuriHalAdcHandle* handle, FuriHalAdcChannel channel);
/** Convert sampled value to voltage
*
* @param handle The ADC handle
* @param[in] value The value acquired with `furi_hal_adc_read`
*
* @return Voltage in mV
*/
float furi_hal_adc_convert_to_voltage(FuriHalAdcHandle* handle, uint16_t value);
/** Convert sampled VREFINT value to voltage
*
* @param handle The ADC handle
* @param[in] value The value acquired with `furi_hal_adc_read` for
* `FuriHalAdcChannelVREFINT` channel
*
* @return Voltage in mV
*/
float furi_hal_adc_convert_vref(FuriHalAdcHandle* handle, uint16_t value);
/** Convert sampled TEMPSENSOR value to temperature
*
* @param handle The ADC handle
* @param[in] value The value acquired with `furi_hal_adc_read` for
* `FuriHalAdcChannelTEMPSENSOR` channel
*
* @return temperature in degree C
*/
float furi_hal_adc_convert_temp(FuriHalAdcHandle* handle, uint16_t value);
/** Convert sampled VBAT value to voltage
*
* @param handle The ADC handle
* @param[in] value The value acquired with `furi_hal_adc_read` for
* `FuriHalAdcChannelVBAT` channel
*
* @return Voltage in mV
*/
float furi_hal_adc_convert_vbat(FuriHalAdcHandle* handle, uint16_t value);
#ifdef __cplusplus
}
#endif