1
mirror of https://github.com/DarkFlippers/unleashed-firmware.git synced 2025-12-12 04:34:43 +04:00

Merge branch 'dev' into release

This commit is contained in:
MX
2024-05-25 00:40:07 +03:00
242 changed files with 4403 additions and 1828 deletions

View File

@@ -189,3 +189,10 @@ Max butthurt: 12
Min level: 3 Min level: 3
Max level: 3 Max level: 3
Weight: 5 Weight: 5
Name: L1_Akira_128x64
Min butthurt: 0
Max butthurt: 8
Min level: 1
Max level: 3
Weight: 5

View File

@@ -8,7 +8,8 @@
"amiralizadeh9480.cpp-helper", "amiralizadeh9480.cpp-helper",
"marus25.cortex-debug", "marus25.cortex-debug",
"zxh404.vscode-proto3", "zxh404.vscode-proto3",
"augustocdias.tasks-shell-input" "augustocdias.tasks-shell-input",
"rioj7.command-variable"
], ],
// List of extensions recommended by VS Code that should not be recommended for users of this workspace. // List of extensions recommended by VS Code that should not be recommended for users of this workspace.
"unwantedRecommendations": [ "unwantedRecommendations": [
@@ -16,4 +17,4 @@
"ms-vscode.cpptools", "ms-vscode.cpptools",
"ms-vscode.cmake-tools" "ms-vscode.cmake-tools"
] ]
} }

View File

@@ -2,7 +2,7 @@
"configurations": [ "configurations": [
{ {
"name": "Win32", "name": "Win32",
"compilerPath": "${workspaceFolder}/toolchain/x86_64-windows/bin/arm-none-eabi-gcc.exe", "compilerPath": "${workspaceFolder}/toolchain/current/bin/arm-none-eabi-gcc.exe",
"intelliSenseMode": "gcc-arm", "intelliSenseMode": "gcc-arm",
"compileCommands": "${workspaceFolder}/build/latest/compile_commands.json", "compileCommands": "${workspaceFolder}/build/latest/compile_commands.json",
"cStandard": "gnu23", "cStandard": "gnu23",
@@ -10,7 +10,7 @@
}, },
{ {
"name": "Linux", "name": "Linux",
"compilerPath": "${workspaceFolder}/toolchain/x86_64-linux/bin/arm-none-eabi-gcc", "compilerPath": "${workspaceFolder}/toolchain/current/bin/arm-none-eabi-gcc",
"intelliSenseMode": "gcc-arm", "intelliSenseMode": "gcc-arm",
"compileCommands": "${workspaceFolder}/build/latest/compile_commands.json", "compileCommands": "${workspaceFolder}/build/latest/compile_commands.json",
"cStandard": "gnu23", "cStandard": "gnu23",
@@ -18,7 +18,7 @@
}, },
{ {
"name": "Mac", "name": "Mac",
"compilerPath": "${workspaceFolder}/toolchain/x86_64-darwin/bin/arm-none-eabi-gcc", "compilerPath": "${workspaceFolder}/toolchain/current/bin/arm-none-eabi-gcc",
"intelliSenseMode": "gcc-arm", "intelliSenseMode": "gcc-arm",
"compileCommands": "${workspaceFolder}/build/latest/compile_commands.json", "compileCommands": "${workspaceFolder}/build/latest/compile_commands.json",
"cStandard": "gnu23", "cStandard": "gnu23",

View File

@@ -91,7 +91,7 @@
"label": "[Debug:unit_tests] Flash (USB)", "label": "[Debug:unit_tests] Flash (USB)",
"group": "build", "group": "build",
"type": "shell", "type": "shell",
"command": "./fbt FIRMWARE_APP_SET=unit_tests FORCE=1 flash_usb" "command": "./fbt FIRMWARE_APP_SET=unit_tests FORCE=1 flash_usb_full"
}, },
{ {
"label": "[Debug] Flash (USB, with resources)", "label": "[Debug] Flash (USB, with resources)",

View File

@@ -1,75 +1,42 @@
## New changes ## New changes
* LFRFID: **Electra intercom protocol support** (Romania) (by @Leptopt1los | PR #750) * Apps: **Mifare Nested - ported to latest API** using old nfc lib (by @xMasterX) (+ mem management fix by @Willy-JL)
* NFC: Temp fix for `iso14443_4_layer_decode_block` crash * LFRFID: **Electra fix** non-initialized encoded epilogue on render (by @Leptopt1los)
* NFC: CharlieCard parser (by @zacharyweiss) * JS: Move examples to subfolder `js_examples`
* SubGHz: FAAC RC XT - add 0xB button code on arrow buttons for programming mode * Apps: HID app improvements and fixes<br>
* SubGHz: Add Manually - Sommer FM fixes `- Move new mouse jiggler into mouse jiggler stealth and bring back previous version of mouse jiggler too`<br>
* SubGHz: Enabled tx-rx state on unused gpio pin by default (**external amp option was removed and is enabled by default now**) `- Set stealth jiggler max time default value to 4 min and min value to 1 min`<br>
* SubGHz: **Status output !TX/RX on the GDO2 CC1101 pin** (by @quen0n | PR #742) `- Merge OFW changes`<br>
* SubGHz: Reworked saved settings (by @xMasterX and @Willy-JL) `- More OFW merge fixes` (by @Willy-JL | PR #753)<br>
* Desktop: Fixes for animation unload (by @Willy-JL) * Apps: **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev)
* iButton: Updated DS1420 for latest ibutton changes * OFW (TLSF branch): SubGHz: fix memory corrupt in read raw view
* Misc: Allow no prefix usage of name_generator_make_detailed_datetime * OFW: **NFC App: fix changing UID**
* Misc: Allow setting view dispatcher callbacks to NULL * OFW: Replaced obsolete-format delay
* Misc: Added `void` due to `-Wstrict-prototypes` * OFW: **Archive: fix condition race on exit**
* Misc: Some code cleanup and proper log levels in nfc parsers * OFW: Text Box: fix displaying text with end text focus
* Infrared: Allow external apps to use infrared settings (by @Willy-JL) * OFW: FuriHal: add flash ops stats, workaround bug in SHCI_C2_SetSystemClock
* JS & HAL: Various fixes and FURI_HAL_RANDOM_MAX define added (by @Willy-JL) * OFW: Icons: compression fixes & larger dimension support
* JS: **BadUSB layout support** (by @Willy-JL) * OFW: **Text Box rework**
* JS: New Modules `widget`, `vgm` and path globals (by @jamisonderek) * OFW: Fix calling both `view_free_model()` and `view_free()`
* Apps: Enhance Random Interval and Movement Functionality in HID Mouse Jiggler for Improved Stealth and Human-Like Behavior (by @gushmazuko | PR #746) * OFW: JS: Add textbox module
* Apps: NFC Magic - **Gen2 writing support, Gen4 NTAG password and PACK fixes** (by @Astrrra) * OFW: JS: Add math module
* Apps: MFKey - **fixed crashes**, add more free ram (by @noproto & @Willy-JL) * OFW: **NFC: add Slix capabilities**
* Apps: **Check out Apps updates by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) * OFW: Settings refactor fixes
* OFW PR 3616: NFC: Mf Desfire fix reading big files (by gornekich) * OFW: JS: Add submenu module
* OFW: iButton: fix crash when deleting some keys * OFW: **Skylanders plugin**
* OFW: Desktop: cleanup error popups * OFW: Settings menu refactoring
* OFW: Troika parser visual fixes * OFW: NFC: Mf Desfire fix reading big files
* OFW: Fix the retry/exit confirmation prompts in iButton * OFW: Infrared: Add Toshiba RAS-2518D
* OFW: nfc app: add legacy keys for plantain cards * OFW: **vscode: config fixes**
* OFW: GUI: Fix array out of bounds in menu exit * OFW: Ble: new connection parameters negotiation scheme
* OFW: add support for S(WTX) request in iso14443_4a_poller * OFW: FuriHal: move version init to early stage
* OFW: Mosgortrans parser output fixes * OFW: Add support for R_ARM_REL32 relocations.
* OFW: BLE: Add GapPairingNone support * OFW: Remove unused DolphinWait_61x59 icon
* OFW: iButton new UI * OFW: Add the Akira animation
* OFW: FuriHal: add ADC API * OFW: **Desktop: fix crash on autolock after restart in locked state**
* OFW: Mf Desfire multiple file rights support
* OFW: **Felica poller** (NFC-F)
* OFW: Desktop/Loader: Unload animations before loading FAPs
* OFW: JS Documentation
* OFW: **Update radio stack to v1.19.0**
* OFW: **Move crypto1 to helpers, add it to the public API**
* OFW: Explain RNG differences, add FURI_HAL_RANDOM_MAX
* OFW: Furi: Add "out of memory" and "malloc(0)" crash messages
* OFW: IR: Fix crash on duty_cycle=1
* OFW: **Desktop: ensure that animation is unloaded before app start (fixes some out of memory crashes)**
* OFW: Hide unlock with reader for MFU-C
* OFW: fbt: fixed missing FBT_FAP_DEBUG_ELF_ROOT to dist env
* OFW: fbt: added -Wstrict-prototypes for main firmware
* OFW: Mifare Ultralight naming fix
* OFW: IR: Remember OTG state
* OFW: Bad USB: fix crash when selecting a keyboard layout
* OFW: L1_Mods animation update : adding VGM visual
* OFW: RFID Improvements
* OFW: Fixed plugins and UI
* OFW: **NFC: Fix mf desfire detect**
* OFW: infrared_transmit.h was missing `#pragma once`
* OFW: Show the wrong PIN Attempt count on the login screen
* OFW: SavedStruct: Introduce saved_struct_get_metadata
* OFW: JS CLI command
* OFW: Add ChromeOS Bad USB demo
* OFW: **Configurable Infrared TX output** (previous UL version is replaced with OFW version, new features added "AutoDetect" and saving settings)
* OFW: BadUSB: BLE, media keys, Fn/Globe key commands
* OFW: NFC: Slix privacy password reveal ->(was included in previous UL release) and **Desfire detect fix**
* OFW: github: additional pre-upload checks for doxygen workflow
* OFW: NFC UI fixes
* OFW: Gui: unicode support, new canvas API
* OFW: **Api Symbols: replace asserts with checks**
<br><br> <br><br>
#### Known NFC post-refactor regressions list: #### Known NFC post-refactor regressions list:
- Mifare Mini clones reading is broken (original mini working fine) (OFW) - Mifare Mini clones reading is broken (original mini working fine) (OFW)
- NFC CLI was removed with refactoring (OFW) (will be back soon) - NFC CLI was removed with refactoring (OFW) (will be back soon)
- Mifare Nested not ported to latest API yet, `unlshd-065` is the latest version on old NFC API that works with "nested app"
---- ----
@@ -95,7 +62,7 @@
|TON||`UQCOqcnYkvzOZUV_9bPE_8oTbOrOF03MnF-VcJyjisTZmsxa`| |TON||`UQCOqcnYkvzOZUV_9bPE_8oTbOrOF03MnF-VcJyjisTZmsxa`|
#### Thanks to our sponsors who supported project in the past and special thanks to sponsors who supports us on regular basis: #### Thanks to our sponsors who supported project in the past and special thanks to sponsors who supports us on regular basis:
ClaraCrazy, Pathfinder [Count Zero cDc], callmezimbra, Quen0n, MERRON, grvpvl (lvpvrg), art_col, ThurstonWaffles, Moneron, UterGrooll, LUCFER, Northpirate, zloepuzo, T.Rat, Alexey B., ionelife, ... @mishamyte, ClaraCrazy, Pathfinder [Count Zero cDc], callmezimbra, Quen0n, MERRON, grvpvl (lvpvrg), art_col, ThurstonWaffles, Moneron, UterGrooll, LUCFER, Northpirate, zloepuzo, T.Rat, Alexey B., ionelife, ...
and all other great people who supported our project and me (xMasterX), thanks to you all! and all other great people who supported our project and me (xMasterX), thanks to you all!

View File

@@ -0,0 +1,10 @@
App(
appid="text_box_element_test",
name="Text Box Element Test",
apptype=FlipperAppType.DEBUG,
entry_point="text_box_element_test_app",
requires=["gui"],
stack_size=1 * 1024,
order=140,
fap_category="Debug",
)

View File

@@ -71,7 +71,7 @@ static void text_box_test_input_callback(InputEvent* input_event, void* ctx) {
furi_message_queue_put(event_queue, input_event, FuriWaitForever); furi_message_queue_put(event_queue, input_event, FuriWaitForever);
} }
int32_t text_box_test_app(void* p) { int32_t text_box_element_test_app(void* p) {
UNUSED(p); UNUSED(p);
FuriMessageQueue* event_queue = furi_message_queue_alloc(32, sizeof(InputEvent)); FuriMessageQueue* event_queue = furi_message_queue_alloc(32, sizeof(InputEvent));
furi_check(event_queue); furi_check(event_queue);

View File

@@ -1,8 +1,8 @@
App( App(
appid="text_box_test", appid="text_box_view_test",
name="Text Box Test", name="Text Box View Test",
apptype=FlipperAppType.DEBUG, apptype=FlipperAppType.DEBUG,
entry_point="text_box_test_app", entry_point="text_box_view_test_app",
requires=["gui"], requires=["gui"],
stack_size=1 * 1024, stack_size=1 * 1024,
order=140, order=140,

View File

@@ -0,0 +1,161 @@
#include <furi.h>
#include <gui/gui.h>
#include <gui/view_dispatcher.h>
#include <gui/modules/text_box.h>
#include <gui/view_stack.h>
#define TAG "TextBoxViewTest"
typedef struct {
TextBoxFont font;
TextBoxFocus focus;
const char* text;
} TextBoxViewTestContent;
static const TextBoxViewTestContent text_box_view_test_content_arr[] = {
{
.font = TextBoxFontText,
.focus = TextBoxFocusStart,
.text = "Hello, let's test text box. Press Right and Left to switch content",
},
{
.font = TextBoxFontText,
.focus = TextBoxFocusEnd,
.text = "First test to add dynamically lines with EndFocus set\nLine 0",
},
{
.font = TextBoxFontText,
.focus = TextBoxFocusEnd,
.text = "First test to add dynamically lines with EndFocus set\nLine 0\nLine 1",
},
{
.font = TextBoxFontText,
.focus = TextBoxFocusEnd,
.text = "First test to add dynamically lines with EndFocus set\nLine 0\nLine 1\nLine 2",
},
{
.font = TextBoxFontText,
.focus = TextBoxFocusEnd,
.text =
"First test to add dynamically lines with EndFocus set\nLine 0\nLine 1\nLine 2\nLine 3",
},
{
.font = TextBoxFontText,
.focus = TextBoxFocusEnd,
.text =
"First test to add dynamically lines with EndFocus set\nLine 0\nLine 1\nLine 2\nLine 3\nLine 4",
},
{
.font = TextBoxFontText,
.focus = TextBoxFocusStart,
.text =
"Verify that symbols don't overlap borders: llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllend",
},
{
.font = TextBoxFontText,
.focus = TextBoxFocusStart,
.text =
"\n\n\n Start from several newline chars. Verify that scrolling doesn't break.\n\n\n\n\nThe end",
},
{
.font = TextBoxFontText,
.focus = TextBoxFocusStart,
.text =
"Let's test big text.\n\n The ARM Cortex-M is a group of 32-bit RISC ARM processor cores licensed by ARM Limited. These cores are optimized for low-cost and energy-efficient integrated circuits, which have been embedded in tens of billions of consumer devices.[1] Though they are most often the main component of microcontroller chips, sometimes they are embedded inside other types of chips too. The Cortex-M family consists of Cortex-M0,[2] Cortex-M0+,[3] Cortex-M1,[4] Cortex-M3,[5] Cortex-M4,[6] Cortex-M7,[7] Cortex-M23,[8] Cortex-M33,[9] Cortex-M35P,[10] Cortex-M52,[11] Cortex-M55,[12] Cortex-M85.[13] A floating-point unit (FPU) option is available for Cortex-M4 / M7 / M33 / M35P / M52 / M55 / M85 cores, and when included in the silicon these cores are sometimes known as \"Cortex-MxF\", where 'x' is the core variant.\n\nThe ARM Cortex-M family are ARM microprocessor cores that are designed for use in microcontrollers, ASICs, ASSPs, FPGAs, and SoCs. Cortex-M cores are commonly used as dedicated microcontroller chips, but also are hidden inside of SoC chips as power management controllers, I/O controllers, system controllers, touch screen controllers, smart battery controllers, and sensor controllers. The main difference from Cortex-A cores is that Cortex-M cores have no memory management unit (MMU) for virtual memory, considered essential for full-fledged operating systems. Cortex-M programs instead run bare metal or on one of the many real-time operating systems which support a Cortex-M.Though 8-bit microcontrollers were very popular in the past, Cortex-M has slowly been chipping away at the 8-bit market as the prices of low-end Cortex-M chips have moved downward. Cortex-M have become a popular replacements for 8-bit chips in applications that benefit from 32-bit math operations, and replacing older legacy ARM cores such as ARM7 and ARM9.",
},
{
.font = TextBoxFontText,
.focus = TextBoxFocusEnd,
.text =
"The same but with EndFocus\n\n The ARM Cortex-M is a group of 32-bit RISC ARM processor cores licensed by ARM Limited. These cores are optimized for low-cost and energy-efficient integrated circuits, which have been embedded in tens of billions of consumer devices.[1] Though they are most often the main component of microcontroller chips, sometimes they are embedded inside other types of chips too. The Cortex-M family consists of Cortex-M0,[2] Cortex-M0+,[3] Cortex-M1,[4] Cortex-M3,[5] Cortex-M4,[6] Cortex-M7,[7] Cortex-M23,[8] Cortex-M33,[9] Cortex-M35P,[10] Cortex-M52,[11] Cortex-M55,[12] Cortex-M85.[13] A floating-point unit (FPU) option is available for Cortex-M4 / M7 / M33 / M35P / M52 / M55 / M85 cores, and when included in the silicon these cores are sometimes known as \"Cortex-MxF\", where 'x' is the core variant.\n\nThe ARM Cortex-M family are ARM microprocessor cores that are designed for use in microcontrollers, ASICs, ASSPs, FPGAs, and SoCs. Cortex-M cores are commonly used as dedicated microcontroller chips, but also are hidden inside of SoC chips as power management controllers, I/O controllers, system controllers, touch screen controllers, smart battery controllers, and sensor controllers. The main difference from Cortex-A cores is that Cortex-M cores have no memory management unit (MMU) for virtual memory, considered essential for full-fledged operating systems. Cortex-M programs instead run bare metal or on one of the many real-time operating systems which support a Cortex-M.Though 8-bit microcontrollers were very popular in the past, Cortex-M has slowly been chipping away at the 8-bit market as the prices of low-end Cortex-M chips have moved downward. Cortex-M have become a popular replacements for 8-bit chips in applications that benefit from 32-bit math operations, and replacing older legacy ARM cores such as ARM7 and ARM9.",
},
{
.font = TextBoxFontHex,
.focus = TextBoxFocusEnd,
.text =
"0000 0000 0000 0000\n1111 1111 1111 1111\n2222 2222 2222 2222\n3333 3333 3333 3333\n4444 4444 4444 4444\n5555 5555 5555 5555\n6666 6666 6666 6666\n7777 7777 7777 7777\n8888 8888 8888 8888\n9999 9999 9999 9999\n0000 0000 0000 0000\n1111 1111 1111 1111\n2222 2222 2222 2222\n3333 3333 3333 3333\n4444 4444 4444 4444\n5555 5555 5555 5555\n6666 6666 6666 6666\n7777 7777 7777 7777\n8888 8888 8888 8888\n9999 9999 9999 9999\n0000 0000 0000 0000\n1111 1111 1111 1111\n2222 2222 2222 2222\n3333 3333 3333 3333\n4444 4444 4444 4444\n5555 5555 5555 5555\n6666 6666 6666 6666\n7777 7777 7777 7777\n8888 8888 8888 8888\n9999 9999 9999 9999\n0000 0000 0000 0000\n1111 1111 1111 1111\n2222 2222 2222 2222\n3333 3333 3333 3333\n4444 4444 4444 4444\n5555 5555 5555 5555\n6666 6666 6666 6666\n7777 7777 7777 7777\n8888 8888 8888 8888\n9999 9999 9999 9999\n0000 0000 0000 0000\n1111 1111 1111 1111\n2222 2222 2222 2222\n3333 3333 3333 3333\n4444 4444 4444 4444\n5555 5555 5555 5555\n6666 6666 6666 6666\n7777 7777 7777 7777\n8888 8888 8888 8888\n9999 9999 9999 9999\n0000 0000 0000 0000\n1111 1111 1111 1111\n2222 2222 2222 2222\n3333 3333 3333 3333\n4444 4444 4444 4444\n5555 5555 5555 5555\n6666 6666 6666 6666\n7777 7777 7777 7777\n8888 8888 8888 8888\n9999 9999 9999 9999\n0000 0000 0000 0000\n1111 1111 1111 1111\n2222 2222 2222 2222\n3333 3333 3333 3333\n4444 4444 4444 4444\n5555 5555 5555 5555\n6666 6666 6666 6666\n7777 7777 7777 7777\n8888 8888 8888 8888\n9999 9999 9999 9999\n0000 0000 0000 0000\n1111 1111 1111 1111\n2222 2222 2222 2222\n3333 3333 3333 3333\n4444 4444 4444 4444\n5555 5555 5555 5555\n6666 6666 6666 6666\n7777 7777 7777 7777\n8888 8888 8888 8888\n9999 9999 9999 9999\n0000 0000 0000 0000\n1111 1111 1111 1111\n2222 2222 2222 2222\n3333 3333 3333 3333\n4444 4444 4444 4444\n5555 5555 5555 5555\n6666 6666 6666 6666\n7777 7777 7777 7777\n8888 8888 8888 8888\n9999 9999 9999 9999\n0000 0000 0000 0000\n1111 1111 1111 1111\n2222 2222 2222 2222\n3333 3333 3333 3333\n4444 4444 4444 4444\n5555 5555 5555 5555\n6666 6666 6666 6666\n7777 7777 7777 7777\n8888 8888 8888 8888\n9999 9999 9999 9999\n0000 0000 0000 0000\n1111 1111 1111 1111\n2222 2222 2222 2222\n3333 3333 3333 3333\n4444 4444 4444 4444\n5555 5555 5555 5555\n6666 6666 6666 6666\n7777 7777 7777 7777\n8888 8888 8888 8888\n9999 9999 9999 9999",
},
};
typedef struct {
TextBox* text_box;
ViewDispatcher* view_dispatcher;
size_t current_content_i;
} TextBoxViewTest;
static void text_box_update_view(TextBoxViewTest* instance) {
// Intentional incorrect way to reset text box to verify that state resets if text changes
text_box_set_text(instance->text_box, "");
const TextBoxViewTestContent* content =
&text_box_view_test_content_arr[instance->current_content_i];
text_box_set_font(instance->text_box, content->font);
text_box_set_focus(instance->text_box, content->focus);
text_box_set_text(instance->text_box, content->text);
}
static bool text_box_switch_view_input_callback(InputEvent* event, void* context) {
bool consumed = false;
TextBoxViewTest* instance = context;
size_t contents_cnt = COUNT_OF(text_box_view_test_content_arr);
if(event->type == InputTypeShort) {
if(event->key == InputKeyRight) {
if(instance->current_content_i < contents_cnt - 1) {
instance->current_content_i++;
text_box_update_view(instance);
consumed = true;
}
} else if(event->key == InputKeyLeft) {
if(instance->current_content_i > 0) {
instance->current_content_i--;
text_box_update_view(instance);
consumed = true;
}
} else if(event->key == InputKeyBack) {
view_dispatcher_stop(instance->view_dispatcher);
}
}
return consumed;
}
int32_t text_box_view_test_app(void* p) {
UNUSED(p);
Gui* gui = furi_record_open(RECORD_GUI);
ViewDispatcher* view_dispatcher = view_dispatcher_alloc();
view_dispatcher_attach_to_gui(view_dispatcher, gui, ViewDispatcherTypeFullscreen);
view_dispatcher_enable_queue(view_dispatcher);
TextBoxViewTest instance = {
.text_box = text_box_alloc(),
.current_content_i = 0,
.view_dispatcher = view_dispatcher,
};
text_box_update_view(&instance);
View* text_box_switch_view = view_alloc();
view_set_input_callback(text_box_switch_view, text_box_switch_view_input_callback);
view_set_context(text_box_switch_view, &instance);
ViewStack* view_stack = view_stack_alloc();
view_stack_add_view(view_stack, text_box_switch_view);
view_stack_add_view(view_stack, text_box_get_view(instance.text_box));
view_dispatcher_add_view(view_dispatcher, 0, view_stack_get_view(view_stack));
view_dispatcher_switch_to_view(view_dispatcher, 0);
view_dispatcher_run(view_dispatcher);
view_dispatcher_remove_view(view_dispatcher, 0);
view_dispatcher_free(view_dispatcher);
view_stack_free(view_stack);
view_free(text_box_switch_view);
text_box_free(instance.text_box);
furi_record_close(RECORD_GUI);
return 0;
}

View File

@@ -2,8 +2,9 @@ App(
appid="unit_tests", appid="unit_tests",
apptype=FlipperAppType.STARTUP, apptype=FlipperAppType.STARTUP,
entry_point="unit_tests_on_system_start", entry_point="unit_tests_on_system_start",
sources=["unit_tests.c", "test_runner.c", "unit_test_api_table.cpp"],
cdefines=["APP_UNIT_TESTS"], cdefines=["APP_UNIT_TESTS"],
requires=["system_settings"], requires=["system_settings", "subghz_start"],
provides=["delay_test"], provides=["delay_test"],
resources="resources", resources="resources",
order=100, order=100,
@@ -12,9 +13,210 @@ App(
App( App(
appid="delay_test", appid="delay_test",
name="Delay Test", name="Delay Test",
sources=["tests/common/*.c", "tests/rpc/*.c"],
apptype=FlipperAppType.SYSTEM, apptype=FlipperAppType.SYSTEM,
entry_point="delay_test_app", entry_point="delay_test_app",
stack_size=1 * 1024, stack_size=1 * 1024,
requires=["unit_tests"], requires=["unit_tests"],
order=110, order=110,
) )
App(
appid="test_varint",
sources=["tests/common/*.c", "tests/varint/*.c"],
apptype=FlipperAppType.PLUGIN,
entry_point="get_api",
requires=["unit_tests"],
)
App(
appid="test_furi",
sources=["tests/common/*.c", "tests/furi/*.c"],
apptype=FlipperAppType.PLUGIN,
entry_point="get_api",
requires=["unit_tests"],
)
App(
appid="test_furi_hal",
sources=["tests/common/*.c", "tests/furi_hal/*.c"],
apptype=FlipperAppType.PLUGIN,
entry_point="get_api",
requires=["unit_tests"],
)
App(
appid="test_furi_hal_crypto",
sources=["tests/common/*.c", "tests/furi_hal_crypto/*.c"],
apptype=FlipperAppType.PLUGIN,
entry_point="get_api",
requires=["unit_tests"],
)
App(
appid="test_furi_string",
sources=["tests/common/*.c", "tests/furi_string/*.c"],
apptype=FlipperAppType.PLUGIN,
entry_point="get_api",
requires=["unit_tests"],
)
App(
appid="test_storage",
sources=["tests/common/*.c", "tests/storage/*.c"],
apptype=FlipperAppType.PLUGIN,
entry_point="get_api",
requires=["unit_tests"],
)
App(
appid="test_stream",
sources=["tests/common/*.c", "tests/stream/*.c"],
apptype=FlipperAppType.PLUGIN,
entry_point="get_api",
requires=["unit_tests"],
)
App(
appid="test_dirwalk",
sources=["tests/common/*.c", "tests/dirwalk/*.c"],
apptype=FlipperAppType.PLUGIN,
entry_point="get_api",
requires=["unit_tests"],
)
App(
appid="test_manifest",
sources=["tests/common/*.c", "tests/manifest/*.c"],
apptype=FlipperAppType.PLUGIN,
entry_point="get_api",
requires=["unit_tests"],
)
App(
appid="test_flipper_format",
sources=["tests/common/*.c", "tests/flipper_format/*.c"],
apptype=FlipperAppType.PLUGIN,
entry_point="get_api",
requires=["unit_tests"],
)
App(
appid="test_flipper_format_string",
sources=["tests/common/*.c", "tests/flipper_format_string/*.c"],
apptype=FlipperAppType.PLUGIN,
entry_point="get_api",
requires=["unit_tests"],
)
App(
appid="test_rpc",
sources=["tests/common/*.c", "tests/rpc/*.c"],
apptype=FlipperAppType.PLUGIN,
entry_point="get_api",
requires=["unit_tests"],
)
App(
appid="test_subghz",
sources=["tests/common/*.c", "tests/subghz/*.c"],
apptype=FlipperAppType.PLUGIN,
entry_point="get_api",
requires=["unit_tests"],
)
App(
appid="test_infrared",
sources=["tests/common/*.c", "tests/infrared/*.c"],
apptype=FlipperAppType.PLUGIN,
entry_point="get_api",
requires=["unit_tests"],
)
App(
appid="test_nfc",
sources=["tests/common/*.c", "tests/nfc/*.c"],
apptype=FlipperAppType.PLUGIN,
entry_point="get_api",
requires=["unit_tests"],
)
App(
appid="test_power",
sources=["tests/common/*.c", "tests/power/*.c"],
apptype=FlipperAppType.PLUGIN,
entry_point="get_api",
requires=["unit_tests"],
)
App(
appid="test_protocol_dict",
sources=["tests/common/*.c", "tests/protocol_dict/*.c"],
apptype=FlipperAppType.PLUGIN,
entry_point="get_api",
requires=["unit_tests"],
)
App(
appid="test_lfrfid",
sources=["tests/common/*.c", "tests/lfrfid/*.c"],
apptype=FlipperAppType.PLUGIN,
entry_point="get_api",
requires=["unit_tests"],
)
App(
appid="test_bit_lib",
sources=["tests/common/*.c", "tests/bit_lib/*.c"],
apptype=FlipperAppType.PLUGIN,
entry_point="get_api",
requires=["unit_tests"],
)
App(
appid="test_datetime",
sources=["tests/common/*.c", "tests/datetime/*.c"],
apptype=FlipperAppType.PLUGIN,
entry_point="get_api",
requires=["unit_tests"],
)
App(
appid="test_float_tools",
sources=["tests/common/*.c", "tests/float_tools/*.c"],
apptype=FlipperAppType.PLUGIN,
entry_point="get_api",
requires=["unit_tests"],
)
App(
appid="test_bt",
sources=["tests/common/*.c", "tests/bt/*.c"],
apptype=FlipperAppType.PLUGIN,
entry_point="get_api",
requires=["unit_tests"],
)
App(
appid="test_dialogs_file_browser_options",
sources=["tests/common/*.c", "tests/dialogs_file_browser_options/*.c"],
apptype=FlipperAppType.PLUGIN,
entry_point="get_api",
requires=["unit_tests"],
)
App(
appid="test_expansion",
sources=["tests/common/*.c", "tests/expansion/*.c"],
apptype=FlipperAppType.PLUGIN,
entry_point="get_api",
requires=["unit_tests"],
)
App(
appid="test_compress",
sources=["tests/common/*.c", "tests/compress/*.c"],
apptype=FlipperAppType.PLUGIN,
entry_point="get_api",
requires=["unit_tests"],
)

View File

@@ -0,0 +1,41 @@
Filetype: Flipper NFC device
Version: 4
# Device type can be ISO14443-3A, ISO14443-3B, ISO14443-4A, ISO14443-4B, ISO15693-3, FeliCa, NTAG/Ultralight, Mifare Classic, Mifare DESFire, SLIX, ST25TB
Device type: SLIX
# UID is common for all formats
UID: E0 04 01 08 49 D0 DC 81
# ISO15693-3 specific data
# Data Storage Format Identifier
DSFID: 01
# Application Family Identifier
AFI: 3D
# IC Reference - Vendor specific meaning
IC Reference: 01
# Lock Bits
Lock DSFID: true
Lock AFI: true
# Number of memory blocks, valid range = 1..256
Block Count: 80
# Size of a single memory block, valid range = 01...20 (hex)
Block Size: 04
Data Content: 03 0A 82 ED 86 39 61 D2 03 14 1E 32 B6 CA 00 3C 36 42 0C 33 53 30 37 32 32 34 30 30 00 00 00 00 00 FF 04 01 01 00 00 00 A3 03 1E 00 26 00 00 00 00 00 0F 00 76 03 65 01 00 00 00 00 85 01 34 00 75 09 05 00 01 00 00 00 00 00 00 00 00 00 00 00 D7 FA 00 1C 9E 1C 67 27 00 30 30 30 30 30 30 30 30 30 30 00 00 00 97 25 55 08 00 00 00 00 00 00 00 00 00 00 00 00 00 00 32 8C 00 30 53 48 80 DE 00 00 00 00 F4 C3 58 2B 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 11 F3 00 2C DD C3 3E 91 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E5 FF 00 01
# Block Security Status: 01 = locked, 00 = not locked
Security Status: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
# SLIX specific data
# SLIX capabilities field affects emulation modes. Possible options: Default, AcceptAllPasswords
Capabilities: AcceptAllPasswords
# Passwords are optional. If a password is omitted, a default value will be used
Password Read: 00 00 00 00
Password Write: 00 00 00 00
Password Privacy: 0F 0F 0F 0F
Password Destroy: 0F 0F 0F 0F
Password EAS: 00 00 00 00
# This is the card's secp128r1 elliptic curve signature. It can not be calculated without knowing NXP's private key.
Signature: A6 25 54 03 74 24 C4 38 36 F4 89 70 76 1A 72 27 54 D9 E7 3D 38 CB 4C 1B 3E FD 0E DF 8A F6 7E 3D
Privacy Mode: false
# Protection pointer configuration
Protection Pointer: 32
Protection Condition: 02
# SLIX Lock Bits
Lock EAS: true
Lock PPL: true

View File

@@ -0,0 +1,41 @@
Filetype: Flipper NFC device
Version: 4
# Device type can be ISO14443-3A, ISO14443-3B, ISO14443-4A, ISO14443-4B, ISO15693-3, FeliCa, NTAG/Ultralight, Mifare Classic, Mifare DESFire, SLIX, ST25TB
Device type: SLIX
# UID is common for all formats
UID: E0 04 01 08 49 D0 DC 81
# ISO15693-3 specific data
# Data Storage Format Identifier
DSFID: 01
# Application Family Identifier
AFI: 3D
# IC Reference - Vendor specific meaning
IC Reference: 01
# Lock Bits
Lock DSFID: true
Lock AFI: true
# Number of memory blocks, valid range = 1..256
Block Count: 80
# Size of a single memory block, valid range = 01...20 (hex)
Block Size: 04
Data Content: 03 0A 82 ED 86 39 61 D2 03 14 1E 32 B6 CA 00 3C 36 42 0C 33 53 30 37 32 32 34 30 30 00 00 00 00 00 FF 04 01 01 00 00 00 A3 03 1E 00 26 00 00 00 00 00 0F 00 76 03 65 01 00 00 00 00 85 01 34 00 75 09 05 00 01 00 00 00 00 00 00 00 00 00 00 00 D7 FA 00 1C 9E 1C 67 27 00 30 30 30 30 30 30 30 30 30 30 00 00 00 97 25 55 08 00 00 00 00 00 00 00 00 00 00 00 00 00 00 32 8C 00 30 53 48 80 DE 00 00 00 00 F4 C3 58 2B 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 11 F3 00 2C DD C3 3E 91 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E5 FF 00 01
# Block Security Status: 01 = locked, 00 = not locked
Security Status: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
# SLIX specific data
# SLIX capabilities field affects emulation modes. Possible options: Default, AcceptAllPasswords
Capabilities: Default
# Passwords are optional. If a password is omitted, a default value will be used
Password Read: 00 00 00 00
Password Write: 00 00 00 00
Password Privacy: 0F 0F 0F 0F
Password Destroy: 0F 0F 0F 0F
Password EAS: 00 00 00 00
# This is the card's secp128r1 elliptic curve signature. It can not be calculated without knowing NXP's private key.
Signature: A6 25 54 03 74 24 C4 38 36 F4 89 70 76 1A 72 27 54 D9 E7 3D 38 CB 4C 1B 3E FD 0E DF 8A F6 7E 3D
Privacy Mode: false
# Protection pointer configuration
Protection Pointer: 32
Protection Condition: 02
# SLIX Lock Bits
Lock EAS: true
Lock PPL: true

View File

@@ -0,0 +1,39 @@
Filetype: Flipper NFC device
Version: 4
# Device type can be ISO14443-3A, ISO14443-3B, ISO14443-4A, ISO14443-4B, ISO15693-3, FeliCa, NTAG/Ultralight, Mifare Classic, Mifare DESFire, SLIX, ST25TB
Device type: SLIX
# UID is common for all formats
UID: E0 04 01 08 49 D0 DC 81
# ISO15693-3 specific data
# Data Storage Format Identifier
DSFID: 01
# Application Family Identifier
AFI: 3D
# IC Reference - Vendor specific meaning
IC Reference: 01
# Lock Bits
Lock DSFID: true
Lock AFI: true
# Number of memory blocks, valid range = 1..256
Block Count: 80
# Size of a single memory block, valid range = 01...20 (hex)
Block Size: 04
Data Content: 03 0A 82 ED 86 39 61 D2 03 14 1E 32 B6 CA 00 3C 36 42 0C 33 53 30 37 32 32 34 30 30 00 00 00 00 00 FF 04 01 01 00 00 00 A3 03 1E 00 26 00 00 00 00 00 0F 00 76 03 65 01 00 00 00 00 85 01 34 00 75 09 05 00 01 00 00 00 00 00 00 00 00 00 00 00 D7 FA 00 1C 9E 1C 67 27 00 30 30 30 30 30 30 30 30 30 30 00 00 00 97 25 55 08 00 00 00 00 00 00 00 00 00 00 00 00 00 00 32 8C 00 30 53 48 80 DE 00 00 00 00 F4 C3 58 2B 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 11 F3 00 2C DD C3 3E 91 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E5 FF 00 01
# Block Security Status: 01 = locked, 00 = not locked
Security Status: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
# SLIX specific data
# Passwords are optional. If a password is omitted, a default value will be used
Password Read: 00 00 00 00
Password Write: 00 00 00 00
Password Privacy: 0F 0F 0F 0F
Password Destroy: 0F 0F 0F 0F
Password EAS: 00 00 00 00
# This is the card's secp128r1 elliptic curve signature. It can not be calculated without knowing NXP's private key.
Signature: A6 25 54 03 74 24 C4 38 36 F4 89 70 76 1A 72 27 54 D9 E7 3D 38 CB 4C 1B 3E FD 0E DF 8A F6 7E 3D
Privacy Mode: false
# Protection pointer configuration
Protection Pointer: 32
Protection Condition: 02
# SLIX Lock Bits
Lock EAS: true
Lock PPL: true

View File

@@ -1,168 +0,0 @@
#include <stdio.h>
#include <furi.h>
#include <furi_hal.h>
#include "minunit_vars.h"
#include <notification/notification_messages.h>
#include <cli/cli.h>
#include <loader/loader.h>
#define TAG "UnitTests"
int run_minunit_test_furi(void);
int run_minunit_test_furi_hal(void);
int run_minunit_test_furi_hal_crypto(void);
int run_minunit_test_furi_string(void);
int run_minunit_test_infrared(void);
int run_minunit_test_rpc(void);
int run_minunit_test_manifest(void);
int run_minunit_test_flipper_format(void);
int run_minunit_test_flipper_format_string(void);
int run_minunit_test_stream(void);
int run_minunit_test_storage(void);
int run_minunit_test_subghz(void);
int run_minunit_test_dirwalk(void);
int run_minunit_test_power(void);
int run_minunit_test_protocol_dict(void);
int run_minunit_test_lfrfid_protocols(void);
int run_minunit_test_nfc(void);
int run_minunit_test_bit_lib(void);
int run_minunit_test_datetime(void);
int run_minunit_test_float_tools(void);
int run_minunit_test_bt(void);
int run_minunit_test_dialogs_file_browser_options(void);
int run_minunit_test_expansion(void);
typedef int (*UnitTestEntry)(void);
typedef struct {
const char* name;
const UnitTestEntry entry;
} UnitTest;
const UnitTest unit_tests[] = {
{.name = "furi", .entry = run_minunit_test_furi},
{.name = "furi_hal", .entry = run_minunit_test_furi_hal},
{.name = "furi_hal_crypto", .entry = run_minunit_test_furi_hal_crypto},
{.name = "furi_string", .entry = run_minunit_test_furi_string},
{.name = "storage", .entry = run_minunit_test_storage},
{.name = "stream", .entry = run_minunit_test_stream},
{.name = "dirwalk", .entry = run_minunit_test_dirwalk},
{.name = "manifest", .entry = run_minunit_test_manifest},
{.name = "flipper_format", .entry = run_minunit_test_flipper_format},
{.name = "flipper_format_string", .entry = run_minunit_test_flipper_format_string},
{.name = "rpc", .entry = run_minunit_test_rpc},
{.name = "subghz", .entry = run_minunit_test_subghz},
{.name = "infrared", .entry = run_minunit_test_infrared},
{.name = "nfc", .entry = run_minunit_test_nfc},
{.name = "power", .entry = run_minunit_test_power},
{.name = "protocol_dict", .entry = run_minunit_test_protocol_dict},
{.name = "lfrfid", .entry = run_minunit_test_lfrfid_protocols},
{.name = "bit_lib", .entry = run_minunit_test_bit_lib},
{.name = "datetime", .entry = run_minunit_test_datetime},
{.name = "float_tools", .entry = run_minunit_test_float_tools},
{.name = "bt", .entry = run_minunit_test_bt},
{.name = "dialogs_file_browser_options",
.entry = run_minunit_test_dialogs_file_browser_options},
{.name = "expansion", .entry = run_minunit_test_expansion},
};
void minunit_print_progress(void) {
static const char progress[] = {'\\', '|', '/', '-'};
static uint8_t progress_counter = 0;
static uint32_t last_tick = 0;
uint32_t current_tick = furi_get_tick();
if(current_tick - last_tick > 20) {
last_tick = current_tick;
printf("[%c]\033[3D", progress[++progress_counter % COUNT_OF(progress)]);
fflush(stdout);
}
}
void minunit_print_fail(const char* str) {
printf(_FURI_LOG_CLR_E "%s\r\n" _FURI_LOG_CLR_RESET, str);
}
void minunit_printf_warning(const char* format, ...) {
FuriString* str = furi_string_alloc();
va_list args;
va_start(args, format);
furi_string_vprintf(str, format, args);
va_end(args);
printf(_FURI_LOG_CLR_W "%s\r\n" _FURI_LOG_CLR_RESET, furi_string_get_cstr(str));
furi_string_free(str);
}
void unit_tests_cli(Cli* cli, FuriString* args, void* context) {
UNUSED(cli);
UNUSED(args);
UNUSED(context);
minunit_run = 0;
minunit_assert = 0;
minunit_fail = 0;
minunit_status = 0;
Loader* loader = furi_record_open(RECORD_LOADER);
NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
// TODO FL-3491: lock device while test running
if(loader_is_locked(loader)) {
printf("RPC: stop all applications to run tests\r\n");
notification_message(notification, &sequence_blink_magenta_100);
} else {
notification_message_block(notification, &sequence_set_only_blue_255);
uint32_t heap_before = memmgr_get_free_heap();
uint32_t cycle_counter = furi_get_tick();
for(size_t i = 0; i < COUNT_OF(unit_tests); i++) {
if(cli_cmd_interrupt_received(cli)) {
break;
}
if(furi_string_size(args)) {
if(furi_string_cmp_str(args, unit_tests[i].name) == 0) {
unit_tests[i].entry();
} else {
printf("Skipping %s\r\n", unit_tests[i].name);
}
} else {
unit_tests[i].entry();
}
}
if(minunit_run != 0) {
printf("\r\nFailed tests: %u\r\n", minunit_fail);
// Time report
cycle_counter = (furi_get_tick() - cycle_counter);
printf("Consumed: %lu ms\r\n", cycle_counter);
// Wait for tested services and apps to deallocate memory
furi_delay_ms(200);
uint32_t heap_after = memmgr_get_free_heap();
printf("Leaked: %ld\r\n", heap_before - heap_after);
// Final Report
if(minunit_fail == 0) {
notification_message(notification, &sequence_success);
printf("Status: PASSED\r\n");
} else {
notification_message(notification, &sequence_error);
printf("Status: FAILED\r\n");
}
}
}
furi_record_close(RECORD_NOTIFICATION);
furi_record_close(RECORD_LOADER);
}
void unit_tests_on_system_start(void) {
#ifdef SRV_CLI
Cli* cli = furi_record_open(RECORD_CLI);
// We need to launch apps from tests, so we cannot lock loader
cli_add_command(cli, "unit_tests", CliCommandFlagParallelSafe, unit_tests_cli, NULL);
furi_record_close(RECORD_CLI);
#endif
}

View File

@@ -0,0 +1,216 @@
#include "test_runner.h"
#include "tests/test_api.h"
#include <cli/cli.h>
#include <toolbox/path.h>
#include <loader/loader.h>
#include <storage/storage.h>
#include <notification/notification_messages.h>
#include <loader/firmware_api/firmware_api.h>
#include <flipper_application/flipper_application.h>
#include <flipper_application/api_hashtable/api_hashtable.h>
#include <flipper_application/plugins/composite_resolver.h>
extern const ElfApiInterface* const unit_tests_api_interface;
#define TAG "TestRunner"
#define PLUGINS_PATH "/ext/apps_data/unit_tests/plugins"
struct TestRunner {
Storage* storage;
Loader* loader;
NotificationApp* notification;
// Temporary used things
Cli* cli;
FuriString* args;
// ELF related stuff
CompositeApiResolver* composite_resolver;
// Report data
int minunit_run;
int minunit_assert;
int minunit_fail;
int minunit_status;
};
TestRunner* test_runner_alloc(Cli* cli, FuriString* args) {
TestRunner* instance = malloc(sizeof(TestRunner));
instance->storage = furi_record_open(RECORD_STORAGE);
instance->loader = furi_record_open(RECORD_LOADER);
instance->notification = furi_record_open(RECORD_NOTIFICATION);
instance->cli = cli;
instance->args = args;
instance->composite_resolver = composite_api_resolver_alloc();
composite_api_resolver_add(instance->composite_resolver, firmware_api_interface);
composite_api_resolver_add(instance->composite_resolver, unit_tests_api_interface);
return instance;
}
void test_runner_free(TestRunner* instance) {
furi_assert(instance);
composite_api_resolver_free(instance->composite_resolver);
furi_record_close(RECORD_NOTIFICATION);
instance->notification = NULL;
furi_record_close(RECORD_LOADER);
instance->loader = NULL;
furi_record_close(RECORD_STORAGE);
instance->storage = NULL;
free(instance);
}
static bool test_runner_run_plugin(TestRunner* instance, const char* path) {
furi_assert(instance);
FURI_LOG_D(TAG, "Loading %s", path);
FlipperApplication* lib = flipper_application_alloc(
instance->storage, composite_api_resolver_get(instance->composite_resolver));
bool result = false;
instance->minunit_fail = -1;
do {
FlipperApplicationPreloadStatus preload_res = flipper_application_preload(lib, path);
if(preload_res != FlipperApplicationPreloadStatusSuccess) {
FURI_LOG_E(TAG, "Failed to preload %s, %d", path, preload_res);
break;
}
if(!flipper_application_is_plugin(lib)) {
FURI_LOG_E(TAG, "Not a plugin %s", path);
break;
}
FlipperApplicationLoadStatus load_status = flipper_application_map_to_memory(lib);
if(load_status != FlipperApplicationLoadStatusSuccess) {
FURI_LOG_E(TAG, "Failed to load %s", path);
break;
}
const FlipperAppPluginDescriptor* app_descriptor =
flipper_application_plugin_get_descriptor(lib);
const TestApi* test = app_descriptor->entry_point;
instance->minunit_fail = test->run();
instance->minunit_run += test->get_minunit_run();
instance->minunit_assert += test->get_minunit_assert();
instance->minunit_status += test->get_minunit_status();
result = (instance->minunit_fail == 0);
} while(false);
flipper_application_free(lib);
return result;
}
static void test_runner_run_internal(TestRunner* instance) {
furi_assert(instance);
char file_name_buffer[256];
FuriString* file_name = furi_string_alloc();
FuriString* file_basename = furi_string_alloc();
File* directory = storage_file_alloc(instance->storage);
do {
if(!storage_dir_open(directory, PLUGINS_PATH)) {
FURI_LOG_E(TAG, "Failed to open directory %s", PLUGINS_PATH);
break;
}
while(true) {
if(cli_cmd_interrupt_received(instance->cli)) {
break;
}
if(!storage_dir_read(directory, NULL, file_name_buffer, sizeof(file_name_buffer))) {
break;
}
furi_string_set(file_name, file_name_buffer);
if(!furi_string_end_with_str(file_name, ".fal")) {
continue;
}
path_concat(PLUGINS_PATH, file_name_buffer, file_name);
path_extract_filename(file_name, file_basename, true);
const char* file_basename_cstr = furi_string_get_cstr(file_basename);
bool result = true;
if(furi_string_size(instance->args)) {
if(furi_string_cmp_str(instance->args, file_basename_cstr) == 0) {
result = test_runner_run_plugin(instance, furi_string_get_cstr(file_name));
} else {
printf("Skipping %s\r\n", file_basename_cstr);
}
} else {
result = test_runner_run_plugin(instance, furi_string_get_cstr(file_name));
}
if(!result) {
printf("Failed to execute test: %s\r\n", file_basename_cstr);
break;
}
}
} while(false);
storage_dir_close(directory);
storage_file_free(directory);
furi_string_free(file_name);
furi_string_free(file_basename);
}
void test_runner_run(TestRunner* instance) {
furi_assert(instance);
// TODO FL-3491: lock device while test running
if(loader_is_locked(instance->loader)) {
printf("RPC: stop all applications to run tests\r\n");
notification_message(instance->notification, &sequence_blink_magenta_100);
} else {
notification_message_block(instance->notification, &sequence_set_only_blue_255);
uint32_t heap_before = memmgr_get_free_heap();
uint32_t cycle_counter = furi_get_tick();
test_runner_run_internal(instance);
if(instance->minunit_run != 0) {
printf("\r\nFailed tests: %d\r\n", instance->minunit_fail);
// Time report
cycle_counter = (furi_get_tick() - cycle_counter);
printf("Consumed: %lu ms\r\n", cycle_counter);
// Wait for tested services and apps to deallocate memory
furi_delay_ms(200);
uint32_t heap_after = memmgr_get_free_heap();
printf("Leaked: %ld\r\n", heap_before - heap_after);
// Final Report
if(instance->minunit_fail == 0) {
notification_message(instance->notification, &sequence_success);
printf("Status: PASSED\r\n");
} else {
notification_message(instance->notification, &sequence_error);
printf("Status: FAILED\r\n");
}
}
}
}

View File

@@ -0,0 +1,12 @@
#pragma once
#include <furi.h>
typedef struct TestRunner TestRunner;
typedef struct Cli Cli;
TestRunner* test_runner_alloc(Cli* cli, FuriString* args);
void test_runner_free(TestRunner* isntance);
void test_runner_run(TestRunner* isntance);

View File

@@ -1,5 +1,5 @@
#include <furi.h> #include <furi.h>
#include "../minunit.h" #include "../test.h"
#include <bit_lib/bit_lib.h> #include <bit_lib/bit_lib.h>
MU_TEST(test_bit_lib_increment_index) { MU_TEST(test_bit_lib_increment_index) {
@@ -737,4 +737,6 @@ MU_TEST_SUITE(test_bit_lib) {
int run_minunit_test_bit_lib(void) { int run_minunit_test_bit_lib(void) {
MU_RUN_SUITE(test_bit_lib); MU_RUN_SUITE(test_bit_lib);
return MU_EXIT_CODE; return MU_EXIT_CODE;
} }
TEST_API_DEFINE(run_minunit_test_bit_lib)

View File

@@ -1,6 +1,6 @@
#include <furi.h> #include <furi.h>
#include <furi_hal.h> #include <furi_hal.h>
#include "../minunit.h" #include "../test.h"
#include <bt/bt_service/bt_keys_storage.h> #include <bt/bt_service/bt_keys_storage.h>
#include <storage/storage.h> #include <storage/storage.h>
@@ -108,3 +108,5 @@ int run_minunit_test_bt(void) {
MU_RUN_SUITE(test_bt); MU_RUN_SUITE(test_bt);
return MU_EXIT_CODE; return MU_EXIT_CODE;
} }
TEST_API_DEFINE(run_minunit_test_bt)

View File

@@ -0,0 +1,42 @@
#include "../test.h"
#include "../minunit_vars.h"
#include <furi.h>
void minunit_print_progress(void) {
static const char progress[] = {'\\', '|', '/', '-'};
static uint8_t progress_counter = 0;
static uint32_t last_tick = 0;
uint32_t current_tick = furi_get_tick();
if(current_tick - last_tick > 20) {
last_tick = current_tick;
printf("[%c]\033[3D", progress[++progress_counter % COUNT_OF(progress)]);
fflush(stdout);
}
}
void minunit_print_fail(const char* str) {
printf(_FURI_LOG_CLR_E "%s\r\n" _FURI_LOG_CLR_RESET, str);
}
void minunit_printf_warning(const char* format, ...) {
FuriString* str = furi_string_alloc();
va_list args;
va_start(args, format);
furi_string_vprintf(str, format, args);
va_end(args);
printf(_FURI_LOG_CLR_W "%s\r\n" _FURI_LOG_CLR_RESET, furi_string_get_cstr(str));
furi_string_free(str);
}
int get_minunit_run(void) {
return minunit_run;
}
int get_minunit_assert(void) {
return minunit_assert;
}
int get_minunit_status(void) {
return minunit_status;
}

View File

@@ -0,0 +1,159 @@
#include "../test.h"
#include <toolbox/compress.h>
#include <furi.h>
#include <furi_hal.h>
#include <furi_hal_random.h>
#include <storage/storage.h>
#include <stdint.h>
#define COMPRESS_UNIT_TESTS_PATH(path) EXT_PATH("unit_tests/compress/" path)
static void compress_test_reference_comp_decomp() {
Storage* storage = furi_record_open(RECORD_STORAGE);
File* compressed_file = storage_file_alloc(storage);
File* decompressed_file = storage_file_alloc(storage);
mu_assert(
storage_file_open(
compressed_file,
COMPRESS_UNIT_TESTS_PATH("compressed.bin"),
FSAM_READ,
FSOM_OPEN_EXISTING),
"Failed to open compressed file");
mu_assert(
storage_file_open(
decompressed_file,
COMPRESS_UNIT_TESTS_PATH("uncompressed.bin"),
FSAM_READ,
FSOM_OPEN_EXISTING),
"Failed to open decompressed file");
uint64_t compressed_ref_size = storage_file_size(compressed_file);
uint64_t decompressed_ref_size = storage_file_size(decompressed_file);
mu_assert(compressed_ref_size > 0 && decompressed_ref_size > 0, "Invalid file sizes");
uint8_t* compressed_ref_buff = malloc(compressed_ref_size);
uint8_t* decompressed_ref_buff = malloc(decompressed_ref_size);
mu_assert(
storage_file_read(compressed_file, compressed_ref_buff, compressed_ref_size) ==
compressed_ref_size,
"Failed to read compressed file");
mu_assert(
storage_file_read(decompressed_file, decompressed_ref_buff, decompressed_ref_size) ==
decompressed_ref_size,
"Failed to read decompressed file");
storage_file_free(compressed_file);
storage_file_free(decompressed_file);
furi_record_close(RECORD_STORAGE);
uint8_t* temp_buffer = malloc(1024);
Compress* comp = compress_alloc(1024);
size_t encoded_size = 0;
mu_assert(
compress_encode(
comp, decompressed_ref_buff, decompressed_ref_size, temp_buffer, 1024, &encoded_size),
"Compress failed");
mu_assert(encoded_size == compressed_ref_size, "Encoded size is not equal to reference size");
mu_assert(
memcmp(temp_buffer, compressed_ref_buff, compressed_ref_size) == 0,
"Encoded buffer is not equal to reference");
size_t decoded_size = 0;
mu_assert(
compress_decode(
comp, compressed_ref_buff, compressed_ref_size, temp_buffer, 1024, &decoded_size),
"Decompress failed");
mu_assert(
decoded_size == decompressed_ref_size, "Decoded size is not equal to reference size");
mu_assert(
memcmp(temp_buffer, decompressed_ref_buff, decompressed_ref_size) == 0,
"Decoded buffer is not equal to reference");
compress_free(comp);
free(temp_buffer);
free(compressed_ref_buff);
free(decompressed_ref_buff);
}
static void compress_test_random_comp_decomp() {
static const size_t src_buffer_size = 1024;
static const size_t encoded_buffer_size = 1024;
static const size_t small_buffer_size = src_buffer_size / 32;
// We only fill half of the buffer with random data, so if anything goes wrong, there's no overflow
static const size_t src_data_size = src_buffer_size / 2;
Compress* comp = compress_alloc(src_buffer_size);
uint8_t* src_buff = malloc(src_buffer_size);
uint8_t* encoded_buff = malloc(encoded_buffer_size);
uint8_t* decoded_buff = malloc(src_buffer_size);
uint8_t* small_buff = malloc(small_buffer_size);
furi_hal_random_fill_buf(src_buff, src_data_size);
size_t encoded_size = 0;
mu_assert(
compress_encode(
comp, src_buff, src_data_size, encoded_buff, encoded_buffer_size, &encoded_size),
"Compress failed");
mu_assert(encoded_size > 0, "Encoded size is zero");
size_t small_enc_dec_size = 0;
mu_assert(
compress_encode(
comp, src_buff, src_data_size, small_buff, small_buffer_size, &small_enc_dec_size) ==
false,
"Compress to small buffer failed");
size_t decoded_size = 0;
mu_assert(
compress_decode(
comp, encoded_buff, encoded_size, decoded_buff, src_buffer_size, &decoded_size),
"Decompress failed");
mu_assert(decoded_size == src_data_size, "Decoded size is not equal to source size");
mu_assert(
memcmp(src_buff, decoded_buff, src_data_size) == 0,
"Decoded buffer is not equal to source");
mu_assert(
compress_decode(
comp, encoded_buff, encoded_size, small_buff, small_buffer_size, &small_enc_dec_size) ==
false,
"Decompress to small buffer failed");
free(small_buff);
free(src_buff);
free(encoded_buff);
free(decoded_buff);
compress_free(comp);
}
MU_TEST_SUITE(test_compress) {
MU_RUN_TEST(compress_test_random_comp_decomp);
MU_RUN_TEST(compress_test_reference_comp_decomp);
}
int run_minunit_test_compress(void) {
MU_RUN_SUITE(test_compress);
return MU_EXIT_CODE;
}
TEST_API_DEFINE(run_minunit_test_compress)

View File

@@ -1,5 +1,5 @@
#include <furi.h> #include <furi.h>
#include "../minunit.h" #include "../test.h"
#include <datetime/datetime.h> #include <datetime/datetime.h>
@@ -188,4 +188,6 @@ int run_minunit_test_datetime(void) {
MU_RUN_SUITE(test_datetime_validate_datetime); MU_RUN_SUITE(test_datetime_validate_datetime);
return MU_EXIT_CODE; return MU_EXIT_CODE;
} }
TEST_API_DEFINE(run_minunit_test_datetime)

View File

@@ -1,6 +1,6 @@
#include <dialogs/dialogs.h> #include <dialogs/dialogs.h>
#include "../minunit.h" #include "../test.h"
MU_TEST(test_dialog_file_browser_set_basic_options_should_init_all_fields) { MU_TEST(test_dialog_file_browser_set_basic_options_should_init_all_fields) {
mu_assert( mu_assert(
@@ -30,3 +30,5 @@ int run_minunit_test_dialogs_file_browser_options(void) {
return MU_EXIT_CODE; return MU_EXIT_CODE;
} }
TEST_API_DEFINE(run_minunit_test_dialogs_file_browser_options)

View File

@@ -1,4 +1,4 @@
#include "../minunit.h" #include "../test.h"
#include <furi.h> #include <furi.h>
#include <m-dict.h> #include <m-dict.h>
#include <toolbox/dir_walk.h> #include <toolbox/dir_walk.h>
@@ -269,4 +269,6 @@ MU_TEST_SUITE(test_dirwalk_suite) {
int run_minunit_test_dirwalk(void) { int run_minunit_test_dirwalk(void) {
MU_RUN_SUITE(test_dirwalk_suite); MU_RUN_SUITE(test_dirwalk_suite);
return MU_EXIT_CODE; return MU_EXIT_CODE;
} }
TEST_API_DEFINE(run_minunit_test_dirwalk)

View File

@@ -1,4 +1,4 @@
#include "../minunit.h" #include "../test.h"
#include <furi.h> #include <furi.h>
#include <furi_hal_random.h> #include <furi_hal_random.h>
@@ -198,3 +198,5 @@ int run_minunit_test_expansion(void) {
MU_RUN_SUITE(test_expansion_suite); MU_RUN_SUITE(test_expansion_suite);
return MU_EXIT_CODE; return MU_EXIT_CODE;
} }
TEST_API_DEFINE(run_minunit_test_expansion)

View File

@@ -2,7 +2,7 @@
#include <flipper_format/flipper_format.h> #include <flipper_format/flipper_format.h>
#include <flipper_format/flipper_format_i.h> #include <flipper_format/flipper_format_i.h>
#include <toolbox/stream/stream.h> #include <toolbox/stream/stream.h>
#include "../minunit.h" #include "../test.h"
#define TEST_DIR TEST_DIR_NAME "/" #define TEST_DIR TEST_DIR_NAME "/"
#define TEST_DIR_NAME EXT_PATH("unit_tests_tmp") #define TEST_DIR_NAME EXT_PATH("unit_tests_tmp")
@@ -549,3 +549,5 @@ int run_minunit_test_flipper_format(void) {
MU_RUN_SUITE(flipper_format); MU_RUN_SUITE(flipper_format);
return MU_EXIT_CODE; return MU_EXIT_CODE;
} }
TEST_API_DEFINE(run_minunit_test_flipper_format)

View File

@@ -3,7 +3,7 @@
#include <flipper_format/flipper_format_i.h> #include <flipper_format/flipper_format_i.h>
#include <toolbox/stream/stream.h> #include <toolbox/stream/stream.h>
#include <storage/storage.h> #include <storage/storage.h>
#include "../minunit.h" #include "../test.h"
static const char* test_filetype = "Flipper Format test"; static const char* test_filetype = "Flipper Format test";
static const uint32_t test_version = 666; static const uint32_t test_version = 666;
@@ -335,3 +335,5 @@ int run_minunit_test_flipper_format_string(void) {
MU_RUN_SUITE(flipper_format_string_suite); MU_RUN_SUITE(flipper_format_string_suite);
return MU_EXIT_CODE; return MU_EXIT_CODE;
} }
TEST_API_DEFINE(run_minunit_test_flipper_format_string)

View File

@@ -1,7 +1,7 @@
#include <float.h> #include <float.h>
#include <float_tools.h> #include <float_tools.h>
#include "../minunit.h" #include "../test.h"
MU_TEST(float_tools_equal_test) { MU_TEST(float_tools_equal_test) {
mu_check(float_is_equal(FLT_MAX, FLT_MAX)); mu_check(float_is_equal(FLT_MAX, FLT_MAX));
@@ -58,3 +58,5 @@ int run_minunit_test_float_tools(void) {
MU_RUN_SUITE(float_tools_suite); MU_RUN_SUITE(float_tools_suite);
return MU_EXIT_CODE; return MU_EXIT_CODE;
} }
TEST_API_DEFINE(run_minunit_test_float_tools)

View File

@@ -1,4 +1,4 @@
#include "../minunit.h" #include "../test.h"
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <stdbool.h> #include <stdbool.h>

View File

@@ -1,7 +1,7 @@
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include <furi.h> #include <furi.h>
#include "../minunit.h" #include "../test.h"
const uint32_t context_value = 0xdeadbeef; const uint32_t context_value = 0xdeadbeef;
const uint32_t notify_value_0 = 0x12345678; const uint32_t notify_value_0 = 0x12345678;

View File

@@ -1,7 +1,7 @@
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include <furi.h> #include <furi.h>
#include "../minunit.h" #include "../test.h"
#define TEST_RECORD_NAME "test/holding" #define TEST_RECORD_NAME "test/holding"

View File

@@ -1,7 +1,7 @@
#include <stdio.h> #include <stdio.h>
#include <furi.h> #include <furi.h>
#include <furi_hal.h> #include <furi_hal.h>
#include "../minunit.h" #include "../test.h"
// v2 tests // v2 tests
void test_furi_create_open(void); void test_furi_create_open(void);
@@ -55,3 +55,5 @@ int run_minunit_test_furi(void) {
return MU_EXIT_CODE; return MU_EXIT_CODE;
} }
TEST_API_DEFINE(run_minunit_test_furi)

View File

@@ -4,7 +4,7 @@
#include <furi.h> #include <furi.h>
#include <furi_hal.h> #include <furi_hal.h>
#include <lp5562_reg.h> #include <lp5562_reg.h>
#include "../minunit.h" #include "../test.h"
#include <stdlib.h> #include <stdlib.h>
#define DATA_SIZE 4 #define DATA_SIZE 4
@@ -232,3 +232,5 @@ int run_minunit_test_furi_hal(void) {
MU_RUN_SUITE(furi_hal_i2c_ext_suite); MU_RUN_SUITE(furi_hal_i2c_ext_suite);
return MU_EXIT_CODE; return MU_EXIT_CODE;
} }
TEST_API_DEFINE(run_minunit_test_furi_hal)

View File

@@ -1,7 +1,7 @@
#include <stdio.h> #include <stdio.h>
#include <furi.h> #include <furi.h>
#include <furi_hal.h> #include <furi_hal.h>
#include "../minunit.h" #include "../test.h"
static const uint8_t key_ctr_1[32] = { static const uint8_t key_ctr_1[32] = {
0x77, 0x6B, 0xEF, 0xF2, 0x85, 0x1D, 0xB0, 0x6F, 0x4C, 0x8A, 0x05, 0x42, 0xC8, 0x69, 0x6F, 0x6C, 0x77, 0x6B, 0xEF, 0xF2, 0x85, 0x1D, 0xB0, 0x6F, 0x4C, 0x8A, 0x05, 0x42, 0xC8, 0x69, 0x6F, 0x6C,
@@ -600,3 +600,5 @@ int run_minunit_test_furi_hal_crypto(void) {
MU_RUN_SUITE(furi_hal_crypto_gcm_test); MU_RUN_SUITE(furi_hal_crypto_gcm_test);
return MU_EXIT_CODE; return MU_EXIT_CODE;
} }
TEST_API_DEFINE(run_minunit_test_furi_hal_crypto)

View File

@@ -1,5 +1,5 @@
#include <furi.h> #include <furi.h>
#include "../minunit.h" #include "../test.h"
static void test_setup(void) { static void test_setup(void) {
} }
@@ -466,4 +466,6 @@ int run_minunit_test_furi_string(void) {
MU_RUN_SUITE(test_suite); MU_RUN_SUITE(test_suite);
return MU_EXIT_CODE; return MU_EXIT_CODE;
} }
TEST_API_DEFINE(run_minunit_test_furi_string)

View File

@@ -2,7 +2,7 @@
#include <flipper_format.h> #include <flipper_format.h>
#include <infrared.h> #include <infrared.h>
#include <common/infrared_common_i.h> #include <common/infrared_common_i.h>
#include "../minunit.h" #include "../test.h"
#define IR_TEST_FILES_DIR EXT_PATH("unit_tests/infrared/") #define IR_TEST_FILES_DIR EXT_PATH("unit_tests/infrared/")
#define IR_TEST_FILE_PREFIX "test_" #define IR_TEST_FILE_PREFIX "test_"
@@ -549,3 +549,5 @@ int run_minunit_test_infrared(void) {
MU_RUN_SUITE(infrared_test); MU_RUN_SUITE(infrared_test);
return MU_EXIT_CODE; return MU_EXIT_CODE;
} }
TEST_API_DEFINE(run_minunit_test_infrared)

View File

@@ -1,5 +1,5 @@
#include <furi.h> #include <furi.h>
#include "../minunit.h" #include "../test.h"
#include <toolbox/protocols/protocol_dict.h> #include <toolbox/protocols/protocol_dict.h>
#include <lfrfid/protocols/lfrfid_protocols.h> #include <lfrfid/protocols/lfrfid_protocols.h>
#include <toolbox/pulse_protocols/pulse_glue.h> #include <toolbox/pulse_protocols/pulse_glue.h>
@@ -550,4 +550,6 @@ MU_TEST_SUITE(test_lfrfid_protocols_suite) {
int run_minunit_test_lfrfid_protocols(void) { int run_minunit_test_lfrfid_protocols(void) {
MU_RUN_SUITE(test_lfrfid_protocols_suite); MU_RUN_SUITE(test_lfrfid_protocols_suite);
return MU_EXIT_CODE; return MU_EXIT_CODE;
} }
TEST_API_DEFINE(run_minunit_test_lfrfid_protocols)

View File

@@ -1,5 +1,5 @@
#include <furi.c> #include <furi.c>
#include "../minunit.h" #include "../test.h"
#include <update_util/resources/manifest.h> #include <update_util/resources/manifest.h>
#define TAG "Manifest" #define TAG "Manifest"
@@ -72,4 +72,6 @@ MU_TEST_SUITE(manifest_suite) {
int run_minunit_test_manifest(void) { int run_minunit_test_manifest(void) {
MU_RUN_SUITE(manifest_suite); MU_RUN_SUITE(manifest_suite);
return MU_EXIT_CODE; return MU_EXIT_CODE;
} }
TEST_API_DEFINE(run_minunit_test_manifest)

View File

@@ -13,12 +13,18 @@
#include <nfc/protocols/mf_ultralight/mf_ultralight_poller_sync.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_sync.h>
#include <nfc/protocols/mf_classic/mf_classic_poller.h> #include <nfc/protocols/mf_classic/mf_classic_poller.h>
#include <nfc/protocols/iso15693_3/iso15693_3_poller.h>
#include <nfc/protocols/slix/slix.h>
#include <nfc/protocols/slix/slix_i.h>
#include <nfc/protocols/slix/slix_poller.h>
#include <nfc/protocols/slix/slix_poller_i.h>
#include <nfc/nfc_poller.h> #include <nfc/nfc_poller.h>
#include <toolbox/keys_dict.h> #include <toolbox/keys_dict.h>
#include <nfc/nfc.h> #include <nfc/nfc.h>
#include "../minunit.h" #include "../test.h"
#define TAG "NfcTest" #define TAG "NfcTest"
@@ -42,6 +48,19 @@ typedef struct {
FuriThreadId thread_id; FuriThreadId thread_id;
} NfcTestMfClassicSendFrameTest; } NfcTestMfClassicSendFrameTest;
typedef enum {
NfcTestSlixPollerSetPasswordStateGetRandomNumber,
NfcTestSlixPollerSetPasswordStateSetPassword,
} NfcTestSlixPollerSetPasswordState;
typedef struct {
FuriThreadId thread_id;
NfcTestSlixPollerSetPasswordState state;
SlixRandomNumber random_number;
SlixPassword password;
SlixError error;
} NfcTestSlixPollerSetPasswordContext;
typedef struct { typedef struct {
Storage* storage; Storage* storage;
} NfcTest; } NfcTest;
@@ -627,6 +646,127 @@ MU_TEST(mf_classic_dict_test) {
"Remove test dict failed"); "Remove test dict failed");
} }
MU_TEST(slix_file_with_capabilities_test) {
NfcDevice* nfc_device_missed_cap = nfc_device_alloc();
mu_assert(
nfc_device_load(nfc_device_missed_cap, EXT_PATH("unit_tests/nfc/Slix_cap_missed.nfc")),
"nfc_device_load() failed\r\n");
NfcDevice* nfc_device_default_cap = nfc_device_alloc();
mu_assert(
nfc_device_load(nfc_device_default_cap, EXT_PATH("unit_tests/nfc/Slix_cap_default.nfc")),
"nfc_device_load() failed\r\n");
mu_assert(
nfc_device_is_equal(nfc_device_missed_cap, nfc_device_default_cap),
"nfc_device_is_equal() failed\r\n");
nfc_device_free(nfc_device_default_cap);
nfc_device_free(nfc_device_missed_cap);
}
NfcCommand slix_poller_set_password_callback(NfcGenericEventEx event, void* context) {
furi_check(event.poller);
furi_check(event.parent_event_data);
furi_check(context);
NfcCommand command = NfcCommandContinue;
Iso15693_3PollerEvent* iso15_event = event.parent_event_data;
SlixPoller* poller = event.poller;
NfcTestSlixPollerSetPasswordContext* slix_ctx = context;
if(iso15_event->type == Iso15693_3PollerEventTypeReady) {
iso15693_3_copy(
poller->data->iso15693_3_data, iso15693_3_poller_get_data(poller->iso15693_3_poller));
if(slix_ctx->state == NfcTestSlixPollerSetPasswordStateGetRandomNumber) {
slix_ctx->error = slix_poller_get_random_number(poller, &slix_ctx->random_number);
if(slix_ctx->error != SlixErrorNone) {
furi_thread_flags_set(slix_ctx->thread_id, NFC_TEST_FLAG_WORKER_DONE);
command = NfcCommandStop;
} else {
slix_ctx->state = NfcTestSlixPollerSetPasswordStateSetPassword;
}
} else if(slix_ctx->state == NfcTestSlixPollerSetPasswordStateSetPassword) {
slix_ctx->error = slix_poller_set_password(
poller, SlixPasswordTypeRead, slix_ctx->password, slix_ctx->random_number);
furi_thread_flags_set(slix_ctx->thread_id, NFC_TEST_FLAG_WORKER_DONE);
command = NfcCommandStop;
}
} else {
slix_ctx->error = slix_process_iso15693_3_error(iso15_event->data->error);
furi_thread_flags_set(slix_ctx->thread_id, NFC_TEST_FLAG_WORKER_DONE);
command = NfcCommandStop;
}
return command;
}
static void slix_set_password_test(const char* file_path, SlixPassword pass, bool correct_pass) {
FURI_LOG_I(TAG, "Testing file: %s", file_path);
Nfc* poller = nfc_alloc();
Nfc* listener = nfc_alloc();
NfcDevice* nfc_device = nfc_device_alloc();
mu_assert(nfc_device_load(nfc_device, file_path), "nfc_device_load() failed\r\n");
const SlixData* slix_data = nfc_device_get_data(nfc_device, NfcProtocolSlix);
NfcListener* slix_listener = nfc_listener_alloc(listener, NfcProtocolSlix, slix_data);
nfc_listener_start(slix_listener, NULL, NULL);
SlixCapabilities slix_capabilities = slix_data->capabilities;
NfcPoller* slix_poller = nfc_poller_alloc(poller, NfcProtocolSlix);
NfcTestSlixPollerSetPasswordContext slix_poller_context = {
.thread_id = furi_thread_get_current_id(),
.state = NfcTestSlixPollerSetPasswordStateGetRandomNumber,
.password = pass,
.error = SlixErrorNone,
};
nfc_poller_start_ex(slix_poller, slix_poller_set_password_callback, &slix_poller_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\r\n");
nfc_poller_stop(slix_poller);
nfc_poller_free(slix_poller);
nfc_listener_stop(slix_listener);
nfc_listener_free(slix_listener);
mu_assert(
slix_poller_context.state == NfcTestSlixPollerSetPasswordStateSetPassword,
"Poller failed before setting password\r\n");
if((slix_capabilities == SlixCapabilitiesAcceptAllPasswords) || (correct_pass)) {
mu_assert(slix_poller_context.error == SlixErrorNone, "Failed to set password\r\n");
} else {
mu_assert(
slix_poller_context.error == SlixErrorTimeout,
"Must have received SlixErrorTimeout\r\n");
}
nfc_device_free(nfc_device);
nfc_free(listener);
nfc_free(poller);
}
MU_TEST(slix_set_password_default_cap_correct_pass) {
slix_set_password_test(EXT_PATH("unit_tests/nfc/Slix_cap_default.nfc"), 0x00000000, true);
}
MU_TEST(slix_set_password_default_cap_incorrect_pass) {
slix_set_password_test(EXT_PATH("unit_tests/nfc/Slix_cap_default.nfc"), 0x12341234, false);
}
MU_TEST(slix_set_password_access_all_passwords_cap) {
slix_set_password_test(
EXT_PATH("unit_tests/nfc/Slix_cap_accept_all_pass.nfc"), 0x12341234, false);
}
MU_TEST_SUITE(nfc) { MU_TEST_SUITE(nfc) {
nfc_test_alloc(); nfc_test_alloc();
@@ -668,6 +808,11 @@ MU_TEST_SUITE(nfc) {
MU_RUN_TEST(mf_classic_send_frame_test); MU_RUN_TEST(mf_classic_send_frame_test);
MU_RUN_TEST(mf_classic_dict_test); MU_RUN_TEST(mf_classic_dict_test);
MU_RUN_TEST(slix_file_with_capabilities_test);
MU_RUN_TEST(slix_set_password_default_cap_correct_pass);
MU_RUN_TEST(slix_set_password_default_cap_incorrect_pass);
MU_RUN_TEST(slix_set_password_access_all_passwords_cap);
nfc_test_free(); nfc_test_free();
} }
@@ -675,3 +820,5 @@ int run_minunit_test_nfc(void) {
MU_RUN_SUITE(nfc); MU_RUN_SUITE(nfc);
return MU_EXIT_CODE; return MU_EXIT_CODE;
} }
TEST_API_DEFINE(run_minunit_test_nfc)

View File

@@ -1,6 +1,6 @@
#include <furi.h> #include <furi.h>
#include <furi_hal.h> #include <furi_hal.h>
#include "../minunit.h" #include "../test.h"
static void power_test_deinit(void) { static void power_test_deinit(void) {
// Try to reset to default charge voltage limit // Try to reset to default charge voltage limit
@@ -67,3 +67,5 @@ int run_minunit_test_power(void) {
MU_RUN_SUITE(test_power_suite); MU_RUN_SUITE(test_power_suite);
return MU_EXIT_CODE; return MU_EXIT_CODE;
} }
TEST_API_DEFINE(run_minunit_test_power)

View File

@@ -1,5 +1,5 @@
#include <furi.h> #include <furi.h>
#include "../minunit.h" #include "../test.h"
#include <toolbox/protocols/protocol_dict.h> #include <toolbox/protocols/protocol_dict.h>
typedef enum { typedef enum {
@@ -219,4 +219,6 @@ MU_TEST_SUITE(test_protocol_dict_suite) {
int run_minunit_test_protocol_dict(void) { int run_minunit_test_protocol_dict(void) {
MU_RUN_SUITE(test_protocol_dict_suite); MU_RUN_SUITE(test_protocol_dict_suite);
return MU_EXIT_CODE; return MU_EXIT_CODE;
} }
TEST_API_DEFINE(run_minunit_test_protocol_dict)

View File

@@ -17,7 +17,7 @@
#include <lib/toolbox/path.h> #include <lib/toolbox/path.h>
#include <m-list.h> #include <m-list.h>
#include "../minunit.h" #include "../test.h"
#include <protobuf_version.h> #include <protobuf_version.h>
#include <pb.h> #include <pb.h>
@@ -1864,3 +1864,5 @@ int32_t delay_test_app(void* p) {
return 0; return 0;
} }
TEST_API_DEFINE(run_minunit_test_rpc)

View File

@@ -1,4 +1,4 @@
#include "../minunit.h" #include "../test.h"
#include <furi.h> #include <furi.h>
#include <storage/storage.h> #include <storage/storage.h>
@@ -712,3 +712,5 @@ int run_minunit_test_storage(void) {
MU_RUN_SUITE(test_md5_calc_suite); MU_RUN_SUITE(test_md5_calc_suite);
return MU_EXIT_CODE; return MU_EXIT_CODE;
} }
TEST_API_DEFINE(run_minunit_test_storage)

View File

@@ -4,7 +4,7 @@
#include <toolbox/stream/file_stream.h> #include <toolbox/stream/file_stream.h>
#include <toolbox/stream/buffered_file_stream.h> #include <toolbox/stream/buffered_file_stream.h>
#include <storage/storage.h> #include <storage/storage.h>
#include "../minunit.h" #include "../test.h"
static const char* stream_test_data = "I write differently from what I speak, " static const char* stream_test_data = "I write differently from what I speak, "
"I speak differently from what I think, " "I speak differently from what I think, "
@@ -530,3 +530,5 @@ int run_minunit_test_stream(void) {
MU_RUN_SUITE(stream_suite); MU_RUN_SUITE(stream_suite);
return MU_EXIT_CODE; return MU_EXIT_CODE;
} }
TEST_API_DEFINE(run_minunit_test_stream)

View File

@@ -1,6 +1,6 @@
#include <furi.h> #include <furi.h>
#include <furi_hal.h> #include <furi_hal.h>
#include "../minunit.h" #include "../test.h"
#include <lib/subghz/receiver.h> #include <lib/subghz/receiver.h>
#include <lib/subghz/transmitter.h> #include <lib/subghz/transmitter.h>
#include <lib/subghz/subghz_keystore.h> #include <lib/subghz/subghz_keystore.h>
@@ -906,3 +906,5 @@ int run_minunit_test_subghz(void) {
MU_RUN_SUITE(subghz); MU_RUN_SUITE(subghz);
return MU_EXIT_CODE; return MU_EXIT_CODE;
} }
TEST_API_DEFINE(run_minunit_test_subghz)

View File

@@ -0,0 +1,12 @@
#pragma once
// Framework
#include "minunit.h"
#include "test_api.h"
int get_minunit_run(void);
int get_minunit_assert(void);
int get_minunit_status(void);

View File

@@ -0,0 +1,29 @@
#pragma once
#include <flipper_application/flipper_application.h>
#define APPID "UnitTest"
#define API_VERSION (0u)
typedef struct {
int (*run)(void);
int (*get_minunit_run)(void);
int (*get_minunit_assert)(void);
int (*get_minunit_status)(void);
} TestApi;
#define TEST_API_DEFINE(entrypoint) \
const TestApi test_api = { \
.run = entrypoint, \
.get_minunit_run = get_minunit_run, \
.get_minunit_assert = get_minunit_assert, \
.get_minunit_status = get_minunit_status, \
}; \
const FlipperAppPluginDescriptor app_descriptor = { \
.appid = APPID, \
.ep_api_version = API_VERSION, \
.entry_point = &test_api, \
}; \
const FlipperAppPluginDescriptor* get_api(void) { \
return &app_descriptor; \
}

View File

@@ -1,6 +1,8 @@
#include <furi.h> #include <furi.h>
#include <furi_hal.h> #include <furi_hal.h>
#include "../minunit.h"
#include "../test.h"
#include <toolbox/varint.h> #include <toolbox/varint.h>
#include <toolbox/profiler.h> #include <toolbox/profiler.h>
@@ -85,4 +87,6 @@ MU_TEST_SUITE(test_varint_suite) {
int run_minunit_test_varint(void) { int run_minunit_test_varint(void) {
MU_RUN_SUITE(test_varint_suite); MU_RUN_SUITE(test_varint_suite);
return MU_EXIT_CODE; return MU_EXIT_CODE;
} }
TEST_API_DEFINE(run_minunit_test_varint)

View File

@@ -0,0 +1,19 @@
#include <flipper_application/api_hashtable/api_hashtable.h>
#include <flipper_application/api_hashtable/compilesort.hpp>
#include "unit_test_api_table_i.h"
static_assert(!has_hash_collisions(unit_tests_api_table), "Detected API method hash collision!");
constexpr HashtableApiInterface unit_tests_hashtable_api_interface{
{
.api_version_major = 0,
.api_version_minor = 0,
.resolver_callback = &elf_resolve_from_hashtable,
},
unit_tests_api_table.cbegin(),
unit_tests_api_table.cend(),
};
extern "C" const ElfApiInterface* const unit_tests_api_interface =
&unit_tests_hashtable_api_interface;

View File

@@ -0,0 +1,29 @@
#include <update_util/resources/manifest.h>
#include <nfc/protocols/slix/slix_i.h>
#include <nfc/protocols/iso15693_3/iso15693_3_poller_i.h>
#include <FreeRTOS.h>
#include <FreeRTOS-Kernel/include/queue.h>
#include <rpc/rpc_i.h>
#include <flipper.pb.h>
static constexpr auto unit_tests_api_table = sort(create_array_t<sym_entry>(
API_METHOD(resource_manifest_reader_alloc, ResourceManifestReader*, (Storage*)),
API_METHOD(resource_manifest_reader_free, void, (ResourceManifestReader*)),
API_METHOD(resource_manifest_reader_open, bool, (ResourceManifestReader*, const char* filename)),
API_METHOD(resource_manifest_reader_next, ResourceManifestEntry*, (ResourceManifestReader*)),
API_METHOD(resource_manifest_reader_previous, ResourceManifestEntry*, (ResourceManifestReader*)),
API_METHOD(slix_process_iso15693_3_error, SlixError, (Iso15693_3Error)),
API_METHOD(iso15693_3_poller_get_data, const Iso15693_3Data*, (Iso15693_3Poller*)),
API_METHOD(rpc_system_storage_get_error, PB_CommandStatus, (FS_Error)),
API_METHOD(xQueueSemaphoreTake, BaseType_t, (QueueHandle_t, TickType_t)),
API_METHOD(vQueueDelete, void, (QueueHandle_t)),
API_METHOD(
xQueueGenericCreate,
QueueHandle_t,
(const UBaseType_t, const UBaseType_t, const uint8_t)),
API_METHOD(
xQueueGenericSend,
BaseType_t,
(QueueHandle_t, const void* const, TickType_t, const BaseType_t)),
API_VARIABLE(PB_Main_msg, PB_Main_msg_t)));

View File

@@ -0,0 +1,21 @@
#include <furi.h>
#include <cli/cli.h>
#include "test_runner.h"
void unit_tests_cli(Cli* cli, FuriString* args, void* context) {
UNUSED(cli);
UNUSED(context);
TestRunner* test_runner = test_runner_alloc(cli, args);
test_runner_run(test_runner);
test_runner_free(test_runner);
}
void unit_tests_on_system_start(void) {
#ifdef SRV_CLI
Cli* cli = furi_record_open(RECORD_CLI);
cli_add_command(cli, "unit_tests", CliCommandFlagParallelSafe, unit_tests_cli, NULL);
furi_record_close(RECORD_CLI);
#endif
}

View File

@@ -21,14 +21,14 @@ static void archive_tick_event_callback(void* context) {
ArchiveApp* archive_alloc(void) { ArchiveApp* archive_alloc(void) {
ArchiveApp* archive = malloc(sizeof(ArchiveApp)); ArchiveApp* archive = malloc(sizeof(ArchiveApp));
archive->gui = furi_record_open(RECORD_GUI);
archive->loader = furi_record_open(RECORD_LOADER);
archive->fav_move_str = furi_string_alloc(); archive->fav_move_str = furi_string_alloc();
archive->dst_path = furi_string_alloc(); archive->dst_path = furi_string_alloc();
archive->scene_manager = scene_manager_alloc(&archive_scene_handlers, archive); archive->scene_manager = scene_manager_alloc(&archive_scene_handlers, archive);
archive->view_dispatcher = view_dispatcher_alloc(); archive->view_dispatcher = view_dispatcher_alloc();
archive->gui = furi_record_open(RECORD_GUI);
ViewDispatcher* view_dispatcher = archive->view_dispatcher; ViewDispatcher* view_dispatcher = archive->view_dispatcher;
view_dispatcher_enable_queue(view_dispatcher); view_dispatcher_enable_queue(view_dispatcher);
view_dispatcher_set_event_callback_context(view_dispatcher, archive); view_dispatcher_set_event_callback_context(view_dispatcher, archive);
@@ -88,6 +88,8 @@ void archive_free(ArchiveApp* archive) {
furi_record_close(RECORD_DIALOGS); furi_record_close(RECORD_DIALOGS);
archive->dialogs = NULL; archive->dialogs = NULL;
furi_record_close(RECORD_LOADER);
archive->loader = NULL;
furi_record_close(RECORD_GUI); furi_record_close(RECORD_GUI);
archive->gui = NULL; archive->gui = NULL;

View File

@@ -26,6 +26,7 @@ typedef enum {
struct ArchiveApp { struct ArchiveApp {
Gui* gui; Gui* gui;
Loader* loader;
ViewDispatcher* view_dispatcher; ViewDispatcher* view_dispatcher;
ViewStack* view_stack; ViewStack* view_stack;
SceneManager* scene_manager; SceneManager* scene_manager;

View File

@@ -88,10 +88,8 @@ void archive_scene_browser_on_enter(void* context) {
archive_update_focus(browser, archive->text_store); archive_update_focus(browser, archive->text_store);
view_dispatcher_switch_to_view(archive->view_dispatcher, ArchiveViewBrowser); view_dispatcher_switch_to_view(archive->view_dispatcher, ArchiveViewBrowser);
Loader* loader = furi_record_open(RECORD_LOADER); archive->loader_stop_subscription = furi_pubsub_subscribe(
archive->loader_stop_subscription = loader_get_pubsub(archive->loader), archive_loader_callback, archive);
furi_pubsub_subscribe(loader_get_pubsub(loader), archive_loader_callback, archive);
furi_record_close(RECORD_LOADER);
uint32_t state = scene_manager_get_scene_state(archive->scene_manager, ArchiveAppSceneBrowser); uint32_t state = scene_manager_get_scene_state(archive->scene_manager, ArchiveAppSceneBrowser);
@@ -302,10 +300,11 @@ bool archive_scene_browser_on_event(void* context, SceneManagerEvent event) {
if(!archive_is_home(browser)) { if(!archive_is_home(browser)) {
archive_leave_dir(browser); archive_leave_dir(browser);
} else { } else {
Loader* loader = furi_record_open(RECORD_LOADER); if(archive->loader_stop_subscription) {
furi_pubsub_unsubscribe( furi_pubsub_unsubscribe(
loader_get_pubsub(loader), archive->loader_stop_subscription); loader_get_pubsub(archive->loader), archive->loader_stop_subscription);
furi_record_close(RECORD_LOADER); archive->loader_stop_subscription = NULL;
}
view_dispatcher_stop(archive->view_dispatcher); view_dispatcher_stop(archive->view_dispatcher);
} }
@@ -321,8 +320,9 @@ bool archive_scene_browser_on_event(void* context, SceneManagerEvent event) {
void archive_scene_browser_on_exit(void* context) { void archive_scene_browser_on_exit(void* context) {
ArchiveApp* archive = (ArchiveApp*)context; ArchiveApp* archive = (ArchiveApp*)context;
if(archive->loader_stop_subscription) {
Loader* loader = furi_record_open(RECORD_LOADER); furi_pubsub_unsubscribe(
furi_pubsub_unsubscribe(loader_get_pubsub(loader), archive->loader_stop_subscription); loader_get_pubsub(archive->loader), archive->loader_stop_subscription);
furi_record_close(RECORD_LOADER); archive->loader_stop_subscription = NULL;
}
} }

View File

@@ -1021,3 +1021,35 @@ type: raw
frequency: 38000 frequency: 38000
duty_cycle: 0.330000 duty_cycle: 0.330000
data: 3302 1640 404 423 407 420 410 1212 437 390 440 1234 405 395 435 392 438 415 415 1207 432 1242 407 420 410 391 439 414 405 1243 406 1241 408 392 438 415 415 386 433 393 437 390 440 414 405 396 434 419 411 389 441 412 407 420 410 390 440 387 432 1242 407 393 437 390 440 414 405 395 435 392 438 389 430 396 434 1240 409 417 413 414 405 395 435 419 411 1237 412 389 430 396 434 393 437 416 414 387 432 394 436 1212 437 389 441 1234 405 1217 432 1241 408 1213 436 1212 437 1210 439 data: 3302 1640 404 423 407 420 410 1212 437 390 440 1234 405 395 435 392 438 415 415 1207 432 1242 407 420 410 391 439 414 405 1243 406 1241 408 392 438 415 415 386 433 393 437 390 440 414 405 396 434 419 411 389 441 412 407 420 410 390 440 387 432 1242 407 393 437 390 440 414 405 395 435 392 438 389 430 396 434 1240 409 417 413 414 405 395 435 419 411 1237 412 389 430 396 434 393 437 416 414 387 432 394 436 1212 437 389 441 1234 405 1217 432 1241 408 1213 436 1212 437 1210 439
#
# Model: Toshiba RAS-2518D
#
name: Dh
type: raw
frequency: 38000
duty_cycle: 0.330000
data: 4349 4437 549 1615 551 1614 551 1614 551 1617 549 531 550 530 551 1615 550 531 550 532 549 530 551 531 550 530 551 1615 550 1614 551 531 550 1615 551 529 552 531 550 530 551 533 548 530 551 530 551 1616 549 1615 550 1616 550 1615 550 1614 551 1616 550 1615 551 1615 550 531 550 531 550 530 551 529 552 530 551 530 551 529 552 530 551 531 550 1615 550 532 549 1615 550 1616 550 531 550 531 550 530 551 530 551 529 552 532 549 530 551 530 551 531 550 529 552 531 550 1615 551 530 551 530 551 530 551 531 550 530 551 531 550 530 551 531 550 531 550 531 550 1616 550 1618 547 532 549 529 552 530 551 1615 551 1615 550 5379 4350 4436 550 1616 549 1615 551 1614 552 1615 550 529 552 530 551 1614 552 530 551 529 552 531 550 531 550 531 550 1614 552 1614 551 530 551 1615 550 530 551 530 551 530 551 530 551 531 550 532 549 1616 549 1615 551 1614 552 1615 550 1614 551 1616 550 1614 552 1615 550 529 552 530 551 530 551 530 551 531 550 531 550 530 551 530 551 531 550 1615 550 530 551 1615 550 1615 551 530 551 530 551 530 551 530 551 530 551 530 551 529 552 530 551 531 550 532 549 530 551 1615 551 531 550 530 551 530 551 530 551 530 551 531 550 531 550 531 550 530 551 531 550 1615 551 1615 551 532 549 531 550 531 550 1616 549 1614 552
#
name: Cool_hi
type: raw
frequency: 38000
duty_cycle: 0.330000
data: 4350 4438 549 1615 551 1614 552 1616 549 1616 550 530 551 531 550 1615 551 530 551 529 552 531 550 530 551 531 550 1614 551 1616 550 531 550 1616 549 530 551 531 550 530 551 529 552 530 551 531 550 1616 549 1616 550 1616 549 1616 550 1615 551 1614 551 1614 552 1615 551 530 551 531 550 530 551 531 550 531 550 529 552 532 549 531 550 530 551 1613 552 530 551 531 550 529 552 532 549 530 551 530 551 531 550 531 550 530 551 530 551 530 551 531 550 530 551 531 550 531 550 1615 551 529 552 530 551 530 551 530 551 530 551 530 551 530 551 530 551 532 549 531 550 531 550 532 549 531 550 531 550 530 551 530 551 5132 4351 4435 552 1616 550 1615 550 1615 551 1613 553 531 550 530 551 1615 550 530 551 531 550 531 550 530 551 532 549 1616 550 1616 549 530 551 1615 551 530 551 531 550 530 551 530 551 530 551 531 550 1615 551 1615 551 1614 551 1615 550 1615 551 1615 550 1615 550 1616 550 530 551 530 551 531 550 532 549 530 551 530 551 531 550 531 550 531 550 1615 550 530 551 530 551 530 551 529 552 531 550 530 551 531 550 531 550 530 551 530 551 531 550 530 551 530 551 530 551 531 550 1616 550 530 551 529 552 530 551 531 550 532 549 530 551 530 551 529 552 531 550 529 552 530 551 530 551 531 550 531 550 529 552 531 550
#
name: Cool_lo
type: raw
frequency: 38000
duty_cycle: 0.330000
data: 4350 4436 550 1617 549 1615 550 1615 550 1617 548 530 551 531 550 1615 551 531 550 531 550 530 551 530 551 531 550 1614 552 1615 550 530 551 1614 551 531 550 531 550 531 550 529 552 532 549 530 551 1617 549 1616 549 1615 551 1619 547 1615 550 1615 550 1616 549 1616 550 530 551 531 550 530 551 530 551 531 550 530 551 529 552 529 552 530 551 1617 548 533 548 1615 551 1613 552 530 551 531 550 531 550 530 551 530 551 532 549 531 550 531 550 530 551 531 550 531 550 531 550 1615 551 531 550 531 550 532 549 531 550 530 551 531 550 533 548 531 550 530 551 1617 548 1616 549 530 551 531 550 532 549 532 549 532 549 5200 4349 4436 550 1615 551 1615 551 1615 550 1616 550 531 550 530 551 1615 551 531 550 530 551 530 551 530 551 530 551 1616 549 1615 551 530 551 1615 551 531 550 531 550 530 551 531 550 531 550 531 550 1615 551 1616 550 1616 550 1615 550 1617 548 1616 549 1616 550 1615 550 531 550 530 551 531 550 531 550 532 549 530 551 531 550 531 550 532 549 1616 550 531 550 1616 550 1615 550 531 550 530 551 531 550 531 550 531 550 531 550 531 550 532 549 532 549 531 550 532 549 531 550 1616 550 531 550 530 551 532 549 532 549 530 551 532 549 531 550 532 549 531 550 1616 549 1617 549 531 550 530 551 531 550 532 549 532 549
#
name: Heat_hi
type: raw
frequency: 38000
duty_cycle: 0.330000
data: 4350 4437 547 1618 548 1620 546 1620 546 1619 547 534 547 535 546 1619 547 534 547 536 545 536 545 535 546 535 546 1619 547 1620 545 534 523 1644 546 535 522 559 546 535 546 534 547 535 546 535 545 1620 546 1620 546 1620 546 1619 547 1619 546 1619 547 1620 545 1620 546 535 546 534 547 537 520 558 523 558 547 534 547 536 521 559 522 559 522 1644 546 535 546 535 522 560 545 536 521 559 522 559 522 558 523 559 522 560 521 559 522 559 522 560 521 559 522 561 520 1644 521 1645 520 559 522 559 522 559 522 559 522 559 522 560 521 560 521 560 521 561 520 559 522 560 521 559 522 559 522 559 522 1644 522 559 522 5341 4349 4439 520 1645 521 1645 521 1646 519 1645 521 560 521 561 520 1645 521 560 521 560 521 559 522 560 521 561 520 1646 520 1645 521 561 520 1645 521 561 520 560 521 560 521 560 521 560 521 561 520 1644 522 1644 522 1645 520 1645 521 1645 521 1645 520 1646 520 1644 522 561 520 560 521 560 521 561 520 560 521 561 520 561 520 561 520 560 521 1646 520 562 519 561 520 561 520 562 519 560 521 560 521 561 520 561 520 560 521 560 521 561 520 560 521 560 521 562 519 1646 520 1645 521 561 520 561 520 561 520 560 521 560 521 561 520 560 521 559 522 560 521 561 520 561 520 560 521 562 519 559 522 1645 521 561 520
#
name: Heat_lo
type: raw
frequency: 38000
duty_cycle: 0.330000
data: 4348 4439 520 1646 520 1646 520 1646 519 1646 520 561 520 561 520 1646 519 561 520 561 520 562 519 562 519 561 520 1646 520 1647 518 563 518 1646 519 562 519 561 520 561 520 562 519 562 519 561 520 1648 517 1647 519 1646 519 1647 519 1646 520 1646 520 1645 520 1647 519 561 520 561 520 562 519 562 519 562 519 562 519 561 520 562 519 561 520 1646 520 562 519 1647 518 1646 520 562 519 560 521 561 520 561 520 561 520 562 519 562 519 560 521 562 519 562 519 560 521 1646 520 1646 520 561 520 562 519 561 520 562 519 561 520 561 520 561 520 561 520 561 520 1647 518 1646 520 562 519 562 519 561 520 1646 520 561 520 5409 4348 4440 519 1645 521 1646 519 1645 521 1645 521 561 520 561 520 1644 522 561 520 561 520 561 520 560 521 562 519 1646 520 1646 520 562 519 1644 522 561 520 561 520 561 520 561 520 561 520 561 520 1646 520 1645 520 1646 520 1645 521 1646 520 1646 520 1644 522 1645 521 560 521 560 521 561 520 561 520 560 521 560 521 561 520 561 520 561 520 1645 521 562 519 1645 521 1645 520 561 520 562 519 561 520 561 520 561 520 560 521 560 521 560 521 560 521 561 520 560 521 1646 520 1646 520 561 520 560 521 559 522 560 521 561 520 561 520 560 521 560 521 560 521 1646 520 1645 520 561 520 560 521 560 521 1645 521 561 520

View File

@@ -263,6 +263,15 @@ App(
sources=["plugins/supported_cards/itso.c"], sources=["plugins/supported_cards/itso.c"],
) )
App(
appid="skylanders_parser",
apptype=FlipperAppType.PLUGIN,
entry_point="skylanders_plugin_ep",
targets=["f7"],
requires=["nfc"],
sources=["plugins/supported_cards/skylanders.c"],
)
App( App(
appid="nfc_start", appid="nfc_start",
targets=["f7"], targets=["f7"],

View File

@@ -0,0 +1,865 @@
#include "nfc_supported_card_plugin.h"
#include <flipper_application/flipper_application.h>
#include <nfc/nfc_device.h>
#include <bit_lib/bit_lib.h>
#include <nfc/protocols/mf_classic/mf_classic_poller_sync.h>
#define TAG "Skylanders"
static const uint64_t skylanders_key = 0x4b0b20107ccb;
bool skylanders_verify(Nfc* nfc) {
bool verified = false;
do {
const uint8_t verify_sector = 0;
uint8_t block_num = mf_classic_get_first_block_num_of_sector(verify_sector);
FURI_LOG_D(TAG, "Verifying sector %u", verify_sector);
MfClassicKey key = {};
bit_lib_num_to_bytes_be(skylanders_key, COUNT_OF(key.data), key.data);
MfClassicAuthContext auth_ctx = {};
MfClassicError error =
mf_classic_poller_sync_auth(nfc, block_num, &key, MfClassicKeyTypeA, &auth_ctx);
if(error != MfClassicErrorNone) {
FURI_LOG_D(TAG, "Failed to read block %u: %d", block_num, error);
break;
}
verified = true;
} while(false);
return verified;
}
static bool skylanders_read(Nfc* nfc, NfcDevice* device) {
furi_assert(nfc);
furi_assert(device);
bool is_read = false;
MfClassicData* data = mf_classic_alloc();
nfc_device_copy_data(device, NfcProtocolMfClassic, data);
do {
MfClassicType type = MfClassicType1k;
MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type);
if(error != MfClassicErrorNone) break;
data->type = type;
MfClassicDeviceKeys keys = {};
for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) {
bit_lib_num_to_bytes_be(skylanders_key, sizeof(MfClassicKey), keys.key_a[i].data);
FURI_BIT_SET(keys.key_a_mask, i);
bit_lib_num_to_bytes_be(skylanders_key, sizeof(MfClassicKey), keys.key_b[i].data);
FURI_BIT_SET(keys.key_b_mask, i);
}
error = mf_classic_poller_sync_read(nfc, &keys, data);
if(error != MfClassicErrorNone) {
FURI_LOG_W(TAG, "Failed to read data");
break;
}
nfc_device_set_data(device, NfcProtocolMfClassic, data);
is_read = mf_classic_is_card_read(data);
} while(false);
mf_classic_free(data);
return is_read;
}
static uint8_t fill_name(const uint16_t id, FuriString* name) {
// USED RESEARCH FROM https://github.com/silicontrip/SkyReader/blob/master/toynames.cpp#L15C1-L163C1
// AND https://github.com/bettse/Solarbreeze/blob/master/Solarbreeze/ThePoster.swift#L438C1-L681C1
switch(id) {
case 0x0000:
furi_string_cat_printf(name, "Whirlwind");
break;
case 0x0001:
furi_string_cat_printf(name, "Sonic Boom");
break;
case 0x0002:
furi_string_cat_printf(name, "Warnado");
break;
case 0x0003:
furi_string_cat_printf(name, "Lightning Rod");
break;
case 0x0004:
case 0x0194:
furi_string_cat_printf(name, "Bash");
break;
case 0x0005:
furi_string_cat_printf(name, "Terrafin");
break;
case 0x0006:
furi_string_cat_printf(name, "Dino-Rang");
break;
case 0x0007:
furi_string_cat_printf(name, "Prism Break");
break;
case 0x0008:
furi_string_cat_printf(name, "Sunburn");
break;
case 0x0009:
furi_string_cat_printf(name, "Eruptor");
break;
case 0x000A:
furi_string_cat_printf(name, "Ignitor");
break;
case 0x000B:
furi_string_cat_printf(name, "Flameslinger");
break;
case 0x000C:
furi_string_cat_printf(name, "Zap");
break;
case 0x000D:
furi_string_cat_printf(name, "Wham-Shell");
break;
case 0x000E:
furi_string_cat_printf(name, "Gill Grunt");
break;
case 0x000F:
furi_string_cat_printf(name, "Slam Bam");
break;
case 0x0010:
case 0x01A0:
furi_string_cat_printf(name, "Spyro");
break;
case 0x0011:
furi_string_cat_printf(name, "Voodood");
break;
case 0x0012:
furi_string_cat_printf(name, "Double Trouble");
break;
case 0x0013:
case 0x01A3:
furi_string_cat_printf(name, "Trigger Happy");
break;
case 0x0014:
furi_string_cat_printf(name, "Drobot");
break;
case 0x0015:
furi_string_cat_printf(name, "Drill Sergeant");
break;
case 0x0016:
furi_string_cat_printf(name, "Boomer");
break;
case 0x0017:
furi_string_cat_printf(name, "Wrecking Ball");
break;
case 0x0018:
furi_string_cat_printf(name, "Camo");
break;
case 0x0019:
furi_string_cat_printf(name, "Zook");
break;
case 0x001A:
furi_string_cat_printf(name, "Stealth Elf");
break;
case 0x001B:
furi_string_cat_printf(name, "Stump Smash");
break;
case 0x001C:
furi_string_cat_printf(name, "Dark Spyro");
break;
case 0x001D:
furi_string_cat_printf(name, "Hex");
break;
case 0x001E:
case 0x01AE:
furi_string_cat_printf(name, "Chop Chop");
break;
case 0x001F:
furi_string_cat_printf(name, "Ghost Roaster");
break;
case 0x0020:
furi_string_cat_printf(name, "Cynder");
break;
case 0x0064:
furi_string_cat_printf(name, "Jet Vac");
break;
case 0x0065:
furi_string_cat_printf(name, "Swarm");
break;
case 0x0066:
furi_string_cat_printf(name, "Crusher");
break;
case 0x0067:
furi_string_cat_printf(name, "Flashwing");
break;
case 0x0068:
furi_string_cat_printf(name, "Hot Head");
break;
case 0x0069:
furi_string_cat_printf(name, "Hot Dog");
break;
case 0x006A:
furi_string_cat_printf(name, "Chill");
break;
case 0x006B:
furi_string_cat_printf(name, "Thumpback");
break;
case 0x006C:
furi_string_cat_printf(name, "Pop Fizz");
break;
case 0x006D:
furi_string_cat_printf(name, "Ninjini");
break;
case 0x006E:
furi_string_cat_printf(name, "Bouncer");
break;
case 0x006F:
furi_string_cat_printf(name, "Sprocket");
break;
case 0x0070:
furi_string_cat_printf(name, "Tree Rex");
break;
case 0x0071:
furi_string_cat_printf(name, "Shroomboom");
break;
case 0x0072:
furi_string_cat_printf(name, "Eye-Brawl");
break;
case 0x0073:
furi_string_cat_printf(name, "Fright Rider");
break;
case 0x00C8:
furi_string_cat_printf(name, "Anvil Rain");
break;
case 0x00C9:
furi_string_cat_printf(name, "Treasure Chest");
break;
case 0x00CA:
furi_string_cat_printf(name, "Healing Elixer");
break;
case 0x00CB:
furi_string_cat_printf(name, "Ghost Swords");
break;
case 0x00CC:
furi_string_cat_printf(name, "Time Twister");
break;
case 0x00CD:
furi_string_cat_printf(name, "Sky-Iron Shield");
break;
case 0x00CE:
furi_string_cat_printf(name, "Winged Boots");
break;
case 0x00CF:
furi_string_cat_printf(name, "Sparx Dragonfly");
break;
case 0x00D0:
furi_string_cat_printf(name, "Dragonfire Cannon");
break;
case 0x00D1:
furi_string_cat_printf(name, "Scorpion Striker Catapult");
break;
case 0x00D2:
furi_string_cat_printf(name, "Trap - Magic");
break;
case 0x00D3:
furi_string_cat_printf(name, "Trap - Water");
break;
case 0x00D4:
furi_string_cat_printf(name, "Trap - Air");
break;
case 0x00D5:
furi_string_cat_printf(name, "Trap - Undead");
break;
case 0x00D6:
furi_string_cat_printf(name, "Trap - Tech");
break;
case 0x00D7:
furi_string_cat_printf(name, "Trap - Fire");
break;
case 0x00D8:
furi_string_cat_printf(name, "Trap - Earth");
break;
case 0x00D9:
furi_string_cat_printf(name, "Trap - Life");
break;
case 0x00DA:
furi_string_cat_printf(name, "Trap - Light");
break;
case 0x00DB:
furi_string_cat_printf(name, "Trap - Dark");
break;
case 0x00DC:
furi_string_cat_printf(name, "Trap - Kaos");
break;
case 0x00E6:
furi_string_cat_printf(name, "Hand Of Fate");
break;
case 0x00E7:
furi_string_cat_printf(name, "Piggy Bank");
break;
case 0x00E8:
furi_string_cat_printf(name, "Rocket Ram");
break;
case 0x00E9:
furi_string_cat_printf(name, "Tiki Speaky");
break;
case 0x00EB:
furi_string_cat_printf(name, "Imaginite Mystery Chest");
break;
case 0x012C:
furi_string_cat_printf(name, "Dragons Peak");
break;
case 0x012D:
furi_string_cat_printf(name, "Empire of Ice");
break;
case 0x012E:
furi_string_cat_printf(name, "Pirate Seas");
break;
case 0x012F:
furi_string_cat_printf(name, "Darklight Crypt");
break;
case 0x0130:
furi_string_cat_printf(name, "Volcanic Vault");
break;
case 0x0131:
furi_string_cat_printf(name, "Mirror Of Mystery");
break;
case 0x0132:
furi_string_cat_printf(name, "Nightmare Express");
break;
case 0x0133:
furi_string_cat_printf(name, "Sunscraper Spire");
break;
case 0x0134:
furi_string_cat_printf(name, "Midnight Museum");
break;
case 0x01C2:
furi_string_cat_printf(name, "Gusto");
break;
case 0x01C3:
furi_string_cat_printf(name, "Thunderbolt");
break;
case 0x01C4:
furi_string_cat_printf(name, "Fling Kong");
break;
case 0x01C5:
furi_string_cat_printf(name, "Blades");
break;
case 0x01C6:
furi_string_cat_printf(name, "Wallop");
break;
case 0x01C7:
furi_string_cat_printf(name, "Head Rush");
break;
case 0x01C8:
furi_string_cat_printf(name, "Fist Bump");
break;
case 0x01C9:
furi_string_cat_printf(name, "Rocky Roll");
break;
case 0x01CA:
furi_string_cat_printf(name, "Wildfire");
break;
case 0x01CB:
furi_string_cat_printf(name, "Ka Boom");
break;
case 0x01CC:
furi_string_cat_printf(name, "Trail Blazer");
break;
case 0x01CD:
furi_string_cat_printf(name, "Torch");
break;
case 0x01CE:
furi_string_cat_printf(name, "Snap Shot");
break;
case 0x01CF:
furi_string_cat_printf(name, "Lob Star");
break;
case 0x01D0:
furi_string_cat_printf(name, "Flip Wreck");
break;
case 0x01D1:
furi_string_cat_printf(name, "Echo");
break;
case 0x01D2:
furi_string_cat_printf(name, "Blastermind");
break;
case 0x01D3:
furi_string_cat_printf(name, "Enigma");
break;
case 0x01D4:
furi_string_cat_printf(name, "Deja Vu");
break;
case 0x01D5:
furi_string_cat_printf(name, "Cobra Cadabra");
break;
case 0x01D6:
furi_string_cat_printf(name, "Jawbreaker");
break;
case 0x01D7:
furi_string_cat_printf(name, "Gearshift");
break;
case 0x01D8:
furi_string_cat_printf(name, "Chopper");
break;
case 0x01D9:
furi_string_cat_printf(name, "Tread Head");
break;
case 0x01DA:
furi_string_cat_printf(name, "Bushwhack");
break;
case 0x01DB:
furi_string_cat_printf(name, "Tuff Luck");
break;
case 0x01DC:
furi_string_cat_printf(name, "Food Fight");
break;
case 0x01DD:
furi_string_cat_printf(name, "High Five");
break;
case 0x01DE:
furi_string_cat_printf(name, "Krypt King");
break;
case 0x01DF:
furi_string_cat_printf(name, "Short Cut");
break;
case 0x01E0:
furi_string_cat_printf(name, "Bat Spin");
break;
case 0x01E1:
furi_string_cat_printf(name, "Funny Bone");
break;
case 0x01E2:
furi_string_cat_printf(name, "Knight light");
break;
case 0x01E3:
furi_string_cat_printf(name, "Spotlight");
break;
case 0x01E4:
furi_string_cat_printf(name, "Knight Mare");
break;
case 0x01E5:
furi_string_cat_printf(name, "Blackout");
break;
case 0x01F6:
furi_string_cat_printf(name, "Bop");
break;
case 0x01F7:
furi_string_cat_printf(name, "Spry");
break;
case 0x01F8:
furi_string_cat_printf(name, "Hijinx");
break;
case 0x01F9:
furi_string_cat_printf(name, "Terrabite");
break;
case 0x01FA:
furi_string_cat_printf(name, "Breeze");
break;
case 0x01FB:
furi_string_cat_printf(name, "Weeruptor");
break;
case 0x01FC:
furi_string_cat_printf(name, "Pet Vac");
break;
case 0x01FD:
furi_string_cat_printf(name, "Small Fry");
break;
case 0x01FE:
furi_string_cat_printf(name, "Drobit");
break;
case 0x0202:
furi_string_cat_printf(name, "Gill Runt");
break;
case 0x0207:
furi_string_cat_printf(name, "Trigger Snappy");
break;
case 0x020E:
furi_string_cat_printf(name, "Whisper Elf");
break;
case 0x021C:
furi_string_cat_printf(name, "Barkley");
break;
case 0x021D:
furi_string_cat_printf(name, "Thumpling");
break;
case 0x021E:
furi_string_cat_printf(name, "Mini Jini");
break;
case 0x021F:
furi_string_cat_printf(name, "Eye Small");
break;
case 0x0259:
furi_string_cat_printf(name, "King Pen");
break;
case 0x0265:
furi_string_cat_printf(name, "Golden Queen");
break;
case 0x02AD:
furi_string_cat_printf(name, "Fire Acorn");
break;
case 0x03E8:
furi_string_cat_printf(name, "(Boom) Jet");
break;
case 0x03E9:
furi_string_cat_printf(name, "(Free) Ranger");
break;
case 0x03EA:
furi_string_cat_printf(name, "(Rubble) Rouser");
break;
case 0x03EB:
furi_string_cat_printf(name, "(Doom) Stone");
break;
case 0x03EC:
furi_string_cat_printf(name, "Blast Zone");
break;
case 0x03ED:
furi_string_cat_printf(name, "(Fire) Kraken");
break;
case 0x03EE:
furi_string_cat_printf(name, "(Stink) Bomb");
break;
case 0x03EF:
furi_string_cat_printf(name, "(Grilla) Drilla");
break;
case 0x03F0:
furi_string_cat_printf(name, "(Hoot) Loop");
break;
case 0x03F1:
furi_string_cat_printf(name, "(Trap) Shadow");
break;
case 0x03F2:
furi_string_cat_printf(name, "(Magna) Charge");
break;
case 0x03F3:
furi_string_cat_printf(name, "(Spy) Rise");
break;
case 0x03F4:
furi_string_cat_printf(name, "(Night) Shift");
break;
case 0x03F5:
furi_string_cat_printf(name, "(Rattle) Shake");
break;
case 0x03F6:
furi_string_cat_printf(name, "(Freeze) Blade");
break;
case 0x03F7:
furi_string_cat_printf(name, "Wash Buckler");
break;
case 0x07D0:
furi_string_cat_printf(name, "Boom (Jet)");
break;
case 0x07D1:
furi_string_cat_printf(name, "Free (Ranger)");
break;
case 0x07D2:
furi_string_cat_printf(name, "Rubble (Rouser)");
break;
case 0x07D3:
furi_string_cat_printf(name, "Doom (Stone)");
break;
case 0x07D4:
furi_string_cat_printf(name, "Blast Zone (Head)");
break;
case 0x07D5:
furi_string_cat_printf(name, "Fire (Kraken)");
break;
case 0x07D6:
furi_string_cat_printf(name, "Stink (Bomb)");
break;
case 0x07D7:
furi_string_cat_printf(name, "Grilla (Drilla)");
break;
case 0x07D8:
furi_string_cat_printf(name, "Hoot (Loop)");
break;
case 0x07D9:
furi_string_cat_printf(name, "Trap (Shadow)");
break;
case 0x07DA:
furi_string_cat_printf(name, "Magna (Charge)");
break;
case 0x07DB:
furi_string_cat_printf(name, "Spy (Rise)");
break;
case 0x07DC:
furi_string_cat_printf(name, "Night (Shift)");
break;
case 0x07DD:
furi_string_cat_printf(name, "Rattle (Shake)");
break;
case 0x07DE:
furi_string_cat_printf(name, "Freeze (Blade)");
break;
case 0x07DF:
furi_string_cat_printf(name, "Wash Buckler (Head)");
break;
case 0x0BB8:
furi_string_cat_printf(name, "Scratch");
break;
case 0x0BB9:
furi_string_cat_printf(name, "Pop Thorn");
break;
case 0x0BBA:
furi_string_cat_printf(name, "Slobber Tooth");
break;
case 0x0BBB:
furi_string_cat_printf(name, "Scorp");
break;
case 0x0BBC:
furi_string_cat_printf(name, "Fryno");
break;
case 0x0BBD:
furi_string_cat_printf(name, "Smolderdash");
break;
case 0x0BBE:
furi_string_cat_printf(name, "Bumble Blast");
break;
case 0x0BBF:
furi_string_cat_printf(name, "Zoo Lou");
break;
case 0x0BC0:
furi_string_cat_printf(name, "Dune Bug");
break;
case 0x0BC1:
furi_string_cat_printf(name, "Star Strike");
break;
case 0x0BC2:
furi_string_cat_printf(name, "Countdown");
break;
case 0x0BC3:
furi_string_cat_printf(name, "Wind Up");
break;
case 0x0BC4:
furi_string_cat_printf(name, "Roller Brawl");
break;
case 0x0BC5:
furi_string_cat_printf(name, "Grim Creeper");
break;
case 0x0BC6:
furi_string_cat_printf(name, "Rip Tide");
break;
case 0x0BC7:
furi_string_cat_printf(name, "Punk Shock");
break;
case 0x0C80:
furi_string_cat_printf(name, "Battle Hammer");
break;
case 0x0C81:
furi_string_cat_printf(name, "Sky Diamond");
break;
case 0x0C82:
furi_string_cat_printf(name, "Platinum Sheep");
break;
case 0x0C83:
furi_string_cat_printf(name, "Groove Machine");
break;
case 0x0C84:
furi_string_cat_printf(name, "UFO Hat");
break;
case 0x0C94:
furi_string_cat_printf(name, "Jet Stream");
break;
case 0x0C95:
furi_string_cat_printf(name, "Tomb Buggy");
break;
case 0x0C96:
furi_string_cat_printf(name, "Reef Ripper");
break;
case 0x0C97:
furi_string_cat_printf(name, "Burn Cycle");
break;
case 0x0C98:
furi_string_cat_printf(name, "Hot Streak");
break;
case 0x0C99:
furi_string_cat_printf(name, "Shark Tank");
break;
case 0x0C9A:
furi_string_cat_printf(name, "Thump Truck");
break;
case 0x0C9B:
furi_string_cat_printf(name, "Crypt Crusher");
break;
case 0x0C9C:
furi_string_cat_printf(name, "Stealth Stinger");
break;
case 0x0C9F:
furi_string_cat_printf(name, "Dive Bomber");
break;
case 0x0CA0:
furi_string_cat_printf(name, "Sky Slicer");
break;
case 0x0CA1:
furi_string_cat_printf(name, "Clown Cruiser");
break;
case 0x0CA2:
furi_string_cat_printf(name, "Gold Rusher");
break;
case 0x0CA3:
furi_string_cat_printf(name, "Shield Striker");
break;
case 0x0CA4:
furi_string_cat_printf(name, "Sun Runner");
break;
case 0x0CA5:
furi_string_cat_printf(name, "Sea Shadow");
break;
case 0x0CA6:
furi_string_cat_printf(name, "Splatter Splasher");
break;
case 0x0CA7:
furi_string_cat_printf(name, "Soda Skimmer");
break;
case 0x0CA8:
furi_string_cat_printf(name, "Barrel Blaster");
break;
case 0x0CA9:
furi_string_cat_printf(name, "Buzz Wing");
break;
case 0x0CE4:
furi_string_cat_printf(name, "Sheep Wreck Island");
break;
case 0x0CE5:
furi_string_cat_printf(name, "Tower of Time");
break;
case 0x0CE6:
furi_string_cat_printf(name, "Fiery Forge");
break;
case 0x0CE7:
furi_string_cat_printf(name, "Arkeyan Crossbow");
break;
case 0x0D48:
furi_string_cat_printf(name, "Fiesta");
break;
case 0x0D49:
furi_string_cat_printf(name, "High Volt");
break;
case 0x0D4A:
furi_string_cat_printf(name, "Splat");
break;
case 0x0D4E:
furi_string_cat_printf(name, "Stormblade");
break;
case 0x0D53:
furi_string_cat_printf(name, "Smash It");
break;
case 0x0D54:
furi_string_cat_printf(name, "Spitfire");
break;
case 0x0D55:
furi_string_cat_printf(name, "Hurricane Jet-Vac");
break;
case 0x0D56:
furi_string_cat_printf(name, "Double Dare Trigger Happy");
break;
case 0x0D57:
furi_string_cat_printf(name, "Super Shot Stealth Elf");
break;
case 0x0D58:
furi_string_cat_printf(name, "Shark Shooter Terrafin");
break;
case 0x0D59:
furi_string_cat_printf(name, "Bone Bash Roller Brawl");
break;
case 0x0D5C:
furi_string_cat_printf(name, "Big Bubble Pop Fizz");
break;
case 0x0D5D:
furi_string_cat_printf(name, "Lava Lance Eruptor");
break;
case 0x0D5E:
furi_string_cat_printf(name, "Deep Dive Gill Grunt");
break;
case 0x0D5F:
furi_string_cat_printf(name, "Turbo Charge Donkey Kong");
break;
case 0x0D60:
furi_string_cat_printf(name, "Hammer Slam Bowser");
break;
case 0x0D61:
furi_string_cat_printf(name, "Dive-Clops");
break;
case 0x0D62:
furi_string_cat_printf(name, "Astroblast");
break;
case 0x0D63:
furi_string_cat_printf(name, "Nightfall");
break;
case 0x0D64:
furi_string_cat_printf(name, "Thrillipede");
break;
case 0x0DAC:
furi_string_cat_printf(name, "Sky Trophy");
break;
case 0x0DAD:
furi_string_cat_printf(name, "Land Trophy");
break;
case 0x0DAE:
furi_string_cat_printf(name, "Sea Trophy");
break;
case 0x0DAF:
furi_string_cat_printf(name, "Kaos Trophy");
break;
default:
furi_string_cat_printf(name, "Unknown");
break;
}
return true;
}
static bool skylanders_parse(const NfcDevice* device, FuriString* parsed_data) {
furi_assert(device);
const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic);
bool parsed = false;
FuriString* name = furi_string_alloc();
do {
// verify key
const uint8_t verify_sector = 0;
MfClassicSectorTrailer* sec_tr =
mf_classic_get_sector_trailer_by_sector(data, verify_sector);
uint64_t key = bit_lib_bytes_to_num_be(sec_tr->key_a.data, 6);
if(key != skylanders_key) break;
const uint16_t id = (uint16_t)*data->block[1].data;
if(id == 0) break;
bool success = fill_name(id, name);
if(!success) break;
furi_string_printf(parsed_data, "\e#Skylanders\n%s", furi_string_get_cstr(name));
parsed = true;
} while(false);
furi_string_free(name);
return parsed;
}
/* Actual implementation of app<>plugin interface */
static const NfcSupportedCardsPlugin skylanders_plugin = {
.protocol = NfcProtocolMfClassic,
.verify = skylanders_verify,
.read = skylanders_read,
.parse = skylanders_parse,
};
/* Plugin descriptor to comply with basic plugin specification */
static const FlipperAppPluginDescriptor skylanders_plugin_descriptor = {
.appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID,
.ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION,
.entry_point = &skylanders_plugin,
};
/* Plugin entry point - must return a pointer to const descriptor */
const FlipperAppPluginDescriptor* skylanders_plugin_ep(void) {
return &skylanders_plugin_descriptor;
}

View File

@@ -2,22 +2,12 @@
#include "../helpers/protocol_support/nfc_protocol_support_gui_common.h" #include "../helpers/protocol_support/nfc_protocol_support_gui_common.h"
static void nfc_scene_set_uid_byte_input_changed_callback(void* context) {
NfcApp* instance = context;
// Retrieve previously saved UID length
const size_t uid_len = scene_manager_get_scene_state(instance->scene_manager, NfcSceneSetUid);
nfc_device_set_uid(instance->nfc_device, instance->byte_input_store, uid_len);
}
void nfc_scene_set_uid_on_enter(void* context) { void nfc_scene_set_uid_on_enter(void* context) {
NfcApp* instance = context; NfcApp* instance = context;
size_t uid_len; size_t uid_len;
const uint8_t* uid = nfc_device_get_uid(instance->nfc_device, &uid_len); const uint8_t* uid = nfc_device_get_uid(instance->nfc_device, &uid_len);
memcpy(instance->byte_input_store, uid, uid_len); memcpy(instance->byte_input_store, uid, uid_len);
// Save UID length for use in callback
scene_manager_set_scene_state(instance->scene_manager, NfcSceneSetUid, uid_len);
// Setup view // Setup view
ByteInput* byte_input = instance->byte_input; ByteInput* byte_input = instance->byte_input;
@@ -25,7 +15,7 @@ void nfc_scene_set_uid_on_enter(void* context) {
byte_input_set_result_callback( byte_input_set_result_callback(
byte_input, byte_input,
nfc_protocol_support_common_byte_input_done_callback, nfc_protocol_support_common_byte_input_done_callback,
nfc_scene_set_uid_byte_input_changed_callback, NULL,
instance, instance,
instance->byte_input_store, instance->byte_input_store,
uid_len); uid_len);
@@ -39,6 +29,9 @@ bool nfc_scene_set_uid_on_event(void* context, SceneManagerEvent event) {
if(event.type == SceneManagerEventTypeCustom) { if(event.type == SceneManagerEventTypeCustom) {
if(event.event == NfcCustomEventByteInputDone) { if(event.event == NfcCustomEventByteInputDone) {
size_t uid_len = 0;
nfc_device_get_uid(instance->nfc_device, &uid_len);
nfc_device_set_uid(instance->nfc_device, instance->byte_input_store, uid_len);
if(scene_manager_has_previous_scene(instance->scene_manager, NfcSceneSavedMenu)) { if(scene_manager_has_previous_scene(instance->scene_manager, NfcSceneSavedMenu)) {
if(nfc_save(instance)) { if(nfc_save(instance)) {
scene_manager_next_scene(instance->scene_manager, NfcSceneSaveSuccess); scene_manager_next_scene(instance->scene_manager, NfcSceneSaveSuccess);

View File

@@ -88,7 +88,7 @@ void subghz_read_raw_add_data_rssi(SubGhzReadRAW* instance, float rssi, bool tra
model->rssi_history[model->ind_write] = u_rssi; model->rssi_history[model->ind_write] = u_rssi;
} }
if(model->ind_write > SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE) { if(model->ind_write >= SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE) {
model->rssi_history_end = true; model->rssi_history_end = true;
model->ind_write = 0; model->ind_write = 0;
} }

View File

@@ -14,7 +14,7 @@ App(
], ],
stack_size=1 * 1024, stack_size=1 * 1024,
order=20, order=20,
sdk_headers=["bt_service/bt.h"], sdk_headers=["bt_service/bt.h", "bt_service/bt_keys_storage.h"],
) )
App( App(

View File

@@ -3,6 +3,10 @@
#include <stdint.h> #include <stdint.h>
#include <stdbool.h> #include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef struct BtKeysStorage BtKeysStorage; typedef struct BtKeysStorage BtKeysStorage;
BtKeysStorage* bt_keys_storage_alloc(const char* keys_storage_path); BtKeysStorage* bt_keys_storage_alloc(const char* keys_storage_path);
@@ -18,3 +22,7 @@ bool bt_keys_storage_load(BtKeysStorage* instance);
bool bt_keys_storage_update(BtKeysStorage* instance, uint8_t* start_addr, uint32_t size); bool bt_keys_storage_update(BtKeysStorage* instance, uint8_t* start_addr, uint32_t size);
bool bt_keys_storage_delete(BtKeysStorage* instance); bool bt_keys_storage_delete(BtKeysStorage* instance);
#ifdef __cplusplus
}
#endif

View File

@@ -61,11 +61,11 @@ static void desktop_clock_update(Desktop* desktop) {
furi_hal_rtc_get_datetime(&curr_dt); furi_hal_rtc_get_datetime(&curr_dt);
bool time_format_12 = locale_get_time_format() == LocaleTimeFormat12h; bool time_format_12 = locale_get_time_format() == LocaleTimeFormat12h;
if(desktop->time_hour != curr_dt.hour || desktop->time_minute != curr_dt.minute || if(desktop->clock.hour != curr_dt.hour || desktop->clock.minute != curr_dt.minute ||
desktop->time_format_12 != time_format_12) { desktop->clock.format_12 != time_format_12) {
desktop->time_format_12 = time_format_12; desktop->clock.format_12 = time_format_12;
desktop->time_hour = curr_dt.hour; desktop->clock.hour = curr_dt.hour;
desktop->time_minute = curr_dt.minute; desktop->clock.minute = curr_dt.minute;
view_port_update(desktop->clock_viewport); view_port_update(desktop->clock_viewport);
} }
} }
@@ -92,8 +92,8 @@ static void desktop_clock_draw_callback(Canvas* canvas, void* context) {
canvas_set_font(canvas, FontPrimary); canvas_set_font(canvas, FontPrimary);
uint8_t hour = desktop->time_hour; uint8_t hour = desktop->clock.hour;
if(desktop->time_format_12) { if(desktop->clock.format_12) {
if(hour > 12) { if(hour > 12) {
hour -= 12; hour -= 12;
} }
@@ -103,11 +103,11 @@ static void desktop_clock_draw_callback(Canvas* canvas, void* context) {
} }
char buffer[20]; char buffer[20];
snprintf(buffer, sizeof(buffer), "%02u:%02u", hour, desktop->time_minute); snprintf(buffer, sizeof(buffer), "%02u:%02u", hour, desktop->clock.minute);
view_port_set_width( view_port_set_width(
desktop->clock_viewport, desktop->clock_viewport,
canvas_string_width(canvas, buffer) - 1 + (desktop->time_minute % 10 == 1)); canvas_string_width(canvas, buffer) - 1 + (desktop->clock.minute % 10 == 1));
canvas_draw_str_aligned(canvas, 0, 8, AlignLeft, AlignBottom, buffer); canvas_draw_str_aligned(canvas, 0, 8, AlignLeft, AlignBottom, buffer);
} }
@@ -140,7 +140,7 @@ static bool desktop_custom_event_callback(void* context, uint32_t event) {
} }
return true; return true;
case DesktopGlobalAutoLock: case DesktopGlobalAutoLock:
if(!loader_is_locked(desktop->loader)) { if(!loader_is_locked(desktop->loader) && !desktop->locked) {
desktop_lock(desktop); desktop_lock(desktop);
} }
return true; return true;
@@ -216,6 +216,8 @@ static void desktop_clock_timer_callback(void* context) {
} }
void desktop_lock(Desktop* desktop) { void desktop_lock(Desktop* desktop) {
furi_assert(!desktop->locked);
furi_hal_rtc_set_flag(FuriHalRtcFlagLock); furi_hal_rtc_set_flag(FuriHalRtcFlagLock);
if(desktop->settings.pin_code.length) { if(desktop->settings.pin_code.length) {
@@ -231,9 +233,13 @@ void desktop_lock(Desktop* desktop) {
DesktopStatus status = {.locked = true}; DesktopStatus status = {.locked = true};
furi_pubsub_publish(desktop->status_pubsub, &status); furi_pubsub_publish(desktop->status_pubsub, &status);
desktop->locked = true;
} }
void desktop_unlock(Desktop* desktop) { void desktop_unlock(Desktop* desktop) {
furi_assert(desktop->locked);
view_port_enabled_set(desktop->lock_icon_viewport, false); view_port_enabled_set(desktop->lock_icon_viewport, false);
Gui* gui = furi_record_open(RECORD_GUI); Gui* gui = furi_record_open(RECORD_GUI);
gui_set_lockdown(gui, false); gui_set_lockdown(gui, false);
@@ -252,6 +258,8 @@ void desktop_unlock(Desktop* desktop) {
DesktopStatus status = {.locked = false}; DesktopStatus status = {.locked = false};
furi_pubsub_publish(desktop->status_pubsub, &status); furi_pubsub_publish(desktop->status_pubsub, &status);
desktop->locked = false;
} }
void desktop_set_dummy_mode_state(Desktop* desktop, bool enabled) { void desktop_set_dummy_mode_state(Desktop* desktop, bool enabled) {

View File

@@ -35,6 +35,12 @@ typedef enum {
DesktopViewIdTotal, DesktopViewIdTotal,
} DesktopViewId; } DesktopViewId;
typedef struct {
uint8_t hour;
uint8_t minute;
bool format_12; // 1 - 12 hour, 0 - 24H
} DesktopClock;
struct Desktop { struct Desktop {
// Scene // Scene
FuriThread* scene_thread; FuriThread* scene_thread;
@@ -75,11 +81,10 @@ struct Desktop {
FuriPubSub* status_pubsub; FuriPubSub* status_pubsub;
uint8_t time_hour; DesktopClock clock;
uint8_t time_minute;
bool time_format_12 : 1; // 1 - 12 hour, 0 - 24H
bool in_transition : 1; bool in_transition : 1;
bool locked : 1;
FuriSemaphore* animation_semaphore; FuriSemaphore* animation_semaphore;
}; };

View File

@@ -1,6 +1,5 @@
#include "slideshow.h" #include "slideshow.h"
#include <stddef.h>
#include <storage/storage.h> #include <storage/storage.h>
#include <gui/icon.h> #include <gui/icon.h>
#include <gui/icon_i.h> #include <gui/icon_i.h>

View File

@@ -16,6 +16,7 @@ App(
"elements.h", "elements.h",
"view_dispatcher.h", "view_dispatcher.h",
"view_stack.h", "view_stack.h",
"view_holder.h",
"modules/button_menu.h", "modules/button_menu.h",
"modules/byte_input.h", "modules/byte_input.h",
"modules/popup.h", "modules/popup.h",

View File

@@ -16,7 +16,7 @@ const CanvasFontParameters canvas_font_params[FontTotalNumber] = {
Canvas* canvas_init(void) { Canvas* canvas_init(void) {
Canvas* canvas = malloc(sizeof(Canvas)); Canvas* canvas = malloc(sizeof(Canvas));
canvas->compress_icon = compress_icon_alloc(); canvas->compress_icon = compress_icon_alloc(ICON_DECOMPRESSOR_BUFFER_SIZE);
// Initialize mutex // Initialize mutex
canvas->mutex = furi_mutex_alloc(FuriMutexTypeNormal); canvas->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
@@ -398,7 +398,7 @@ void canvas_draw_icon_ex(
x += canvas->offset_x; x += canvas->offset_x;
y += canvas->offset_y; y += canvas->offset_y;
uint8_t* icon_data = NULL; uint8_t* icon_data = NULL;
compress_icon_decode(canvas->compress_icon, icon_get_data(icon), &icon_data); compress_icon_decode(canvas->compress_icon, icon_get_frame_data(icon, 0), &icon_data);
canvas_draw_u8g2_bitmap( canvas_draw_u8g2_bitmap(
&canvas->fb, x, y, icon_get_width(icon), icon_get_height(icon), icon_data, rotation); &canvas->fb, x, y, icon_get_width(icon), icon_get_height(icon), icon_data, rotation);
} }
@@ -410,7 +410,7 @@ void canvas_draw_icon(Canvas* canvas, int32_t x, int32_t y, const Icon* icon) {
x += canvas->offset_x; x += canvas->offset_x;
y += canvas->offset_y; y += canvas->offset_y;
uint8_t* icon_data = NULL; uint8_t* icon_data = NULL;
compress_icon_decode(canvas->compress_icon, icon_get_data(icon), &icon_data); compress_icon_decode(canvas->compress_icon, icon_get_frame_data(icon, 0), &icon_data);
canvas_draw_u8g2_bitmap( canvas_draw_u8g2_bitmap(
&canvas->fb, x, y, icon_get_width(icon), icon_get_height(icon), icon_data, IconRotation0); &canvas->fb, x, y, icon_get_width(icon), icon_get_height(icon), icon_data, IconRotation0);
} }

View File

@@ -12,6 +12,8 @@
#include <m-algo.h> #include <m-algo.h>
#include <furi.h> #include <furi.h>
#define ICON_DECOMPRESSOR_BUFFER_SIZE (128u * 64 / 8)
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif

View File

@@ -121,7 +121,8 @@ void elements_multiline_text_aligned(
/** Draw multiline text /** Draw multiline text
* *
* @param canvas Canvas instance * @param canvas Canvas instance
* @param x, y top left corner coordinates * @param x top left corner coordinates
* @param y top left corner coordinates
* @param text string (possible multiline) * @param text string (possible multiline)
*/ */
void elements_multiline_text(Canvas* canvas, int32_t x, int32_t y, const char* text); void elements_multiline_text(Canvas* canvas, int32_t x, int32_t y, const char* text);
@@ -129,7 +130,8 @@ void elements_multiline_text(Canvas* canvas, int32_t x, int32_t y, const char* t
/** Draw framed multiline text /** Draw framed multiline text
* *
* @param canvas Canvas instance * @param canvas Canvas instance
* @param x, y top left corner coordinates * @param x top left corner coordinates
* @param y top left corner coordinates
* @param text string (possible multiline) * @param text string (possible multiline)
*/ */
void elements_multiline_text_framed(Canvas* canvas, int32_t x, int32_t y, const char* text); void elements_multiline_text_framed(Canvas* canvas, int32_t x, int32_t y, const char* text);
@@ -137,8 +139,10 @@ void elements_multiline_text_framed(Canvas* canvas, int32_t x, int32_t y, const
/** Draw slightly rounded frame /** Draw slightly rounded frame
* *
* @param canvas Canvas instance * @param canvas Canvas instance
* @param x, y top left corner coordinates * @param x top left corner coordinates
* @param width, height size of frame * @param y top left corner coordinates
* @param width width of frame
* @param height height of frame
*/ */
void elements_slightly_rounded_frame( void elements_slightly_rounded_frame(
Canvas* canvas, Canvas* canvas,
@@ -150,8 +154,10 @@ void elements_slightly_rounded_frame(
/** Draw slightly rounded box /** Draw slightly rounded box
* *
* @param canvas Canvas instance * @param canvas Canvas instance
* @param x, y top left corner coordinates * @param x top left corner coordinates
* @param width, height size of box * @param y top left corner coordinates
* @param width height of box
* @param height height of box
*/ */
void elements_slightly_rounded_box( void elements_slightly_rounded_box(
Canvas* canvas, Canvas* canvas,
@@ -163,8 +169,10 @@ void elements_slightly_rounded_box(
/** Draw bold rounded frame /** Draw bold rounded frame
* *
* @param canvas Canvas instance * @param canvas Canvas instance
* @param x, y top left corner coordinates * @param x top left corner coordinates
* @param width, height size of frame * @param y top left corner coordinates
* @param width width of frame
* @param height height of frame
*/ */
void elements_bold_rounded_frame(Canvas* canvas, int32_t x, int32_t y, size_t width, size_t height); void elements_bold_rounded_frame(Canvas* canvas, int32_t x, int32_t y, size_t width, size_t height);

View File

@@ -1,13 +1,16 @@
#include "icon.h"
#include "icon_i.h" #include "icon_i.h"
#include <furi.h> #include <furi.h>
uint8_t icon_get_width(const Icon* instance) { #include <furi.h>
uint16_t icon_get_width(const Icon* instance) {
furi_check(instance); furi_check(instance);
return instance->width; return instance->width;
} }
uint8_t icon_get_height(const Icon* instance) { uint16_t icon_get_height(const Icon* instance) {
furi_check(instance); furi_check(instance);
return instance->height; return instance->height;
@@ -16,5 +19,14 @@ uint8_t icon_get_height(const Icon* instance) {
const uint8_t* icon_get_data(const Icon* instance) { const uint8_t* icon_get_data(const Icon* instance) {
furi_check(instance); furi_check(instance);
return instance->frames[0]; return icon_get_frame_data(instance, 0);
}
uint32_t icon_get_frame_count(const Icon* instance) {
return instance->frame_count;
}
const uint8_t* icon_get_frame_data(const Icon* instance, uint32_t frame) {
furi_check(frame < instance->frame_count);
return instance->frames[frame];
} }

View File

@@ -6,6 +6,7 @@
#pragma once #pragma once
#include <stdint.h> #include <stdint.h>
#include <core/common_defines.h>
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
@@ -19,7 +20,7 @@ typedef struct Icon Icon;
* *
* @return width in pixels * @return width in pixels
*/ */
uint8_t icon_get_width(const Icon* instance); uint16_t icon_get_width(const Icon* instance);
/** Get icon height /** Get icon height
* *
@@ -27,15 +28,32 @@ uint8_t icon_get_width(const Icon* instance);
* *
* @return height in pixels * @return height in pixels
*/ */
uint8_t icon_get_height(const Icon* instance); uint16_t icon_get_height(const Icon* instance);
/** Get Icon XBM bitmap data /** Get Icon XBM bitmap data for the first frame
* *
* @param[in] instance pointer to Icon data * @param[in] instance pointer to Icon data
* *
* @return pointer to XBM bitmap data * @return pointer to compressed XBM bitmap data
*/ */
const uint8_t* icon_get_data(const Icon* instance); FURI_DEPRECATED const uint8_t* icon_get_data(const Icon* instance);
/** Get Icon frame count
*
* @param[in] instance pointer to Icon data
*
* @return frame count
*/
uint32_t icon_get_frame_count(const Icon* instance);
/** Get Icon XBM bitmap data for a particular frame
*
* @param[in] instance pointer to Icon data
* @param[in] frame frame index
*
* @return pointer to compressed XBM bitmap data
*/
const uint8_t* icon_get_frame_data(const Icon* instance, uint32_t frame);
#ifdef __cplusplus #ifdef __cplusplus
} }

View File

@@ -4,11 +4,11 @@
*/ */
#pragma once #pragma once
#include "icon.h" #include <stdint.h>
struct Icon { struct Icon {
const uint8_t width; const uint16_t width;
const uint8_t height; const uint16_t height;
const uint8_t frame_count; const uint8_t frame_count;
const uint8_t frame_rate; const uint8_t frame_rate;
const uint8_t* const* frames; const uint8_t* const* frames;

View File

@@ -318,6 +318,26 @@ void submenu_add_lockable_item(
true); true);
} }
void submenu_change_item_label(Submenu* submenu, uint32_t index, const char* label) {
furi_check(submenu);
furi_check(label);
with_view_model(
submenu->view,
SubmenuModel * model,
{
SubmenuItemArray_it_t it;
for(SubmenuItemArray_it(it, model->items); !SubmenuItemArray_end_p(it);
SubmenuItemArray_next(it)) {
if(index == SubmenuItemArray_cref(it)->index) {
furi_string_set_str(SubmenuItemArray_cref(it)->label, label);
break;
}
}
},
true);
}
void submenu_reset(Submenu* submenu) { void submenu_reset(Submenu* submenu) {
furi_check(submenu); furi_check(submenu);
view_set_orientation(submenu->view, ViewOrientationHorizontal); view_set_orientation(submenu->view, ViewOrientationHorizontal);
@@ -335,6 +355,25 @@ void submenu_reset(Submenu* submenu) {
true); true);
} }
uint32_t submenu_get_selected_item(Submenu* submenu) {
furi_check(submenu);
uint32_t selected_item_index = 0;
with_view_model(
submenu->view,
SubmenuModel * model,
{
if(model->position < SubmenuItemArray_size(model->items)) {
const SubmenuItem* item = SubmenuItemArray_cget(model->items, model->position);
selected_item_index = item->index;
}
},
false);
return selected_item_index;
}
void submenu_set_selected_item(Submenu* submenu, uint32_t index) { void submenu_set_selected_item(Submenu* submenu, uint32_t index) {
furi_check(submenu); furi_check(submenu);
with_view_model( with_view_model(

View File

@@ -73,16 +73,32 @@ void submenu_add_lockable_item(
bool locked, bool locked,
const char* locked_message); const char* locked_message);
/** Change label of an existing item
*
* @param submenu Submenu instance
* @param index The index of the item
* @param label The new label
*/
void submenu_change_item_label(Submenu* submenu, uint32_t index, const char* label);
/** Remove all items from submenu /** Remove all items from submenu
* *
* @param submenu Submenu instance * @param submenu Submenu instance
*/ */
void submenu_reset(Submenu* submenu); void submenu_reset(Submenu* submenu);
/** Set submenu item selector /** Get submenu selected item index
* *
* @param submenu Submenu instance * @param submenu Submenu instance
* @param index The index *
* @return Index of the selected item
*/
uint32_t submenu_get_selected_item(Submenu* submenu);
/** Set submenu selected item by index
*
* @param submenu Submenu instance
* @param index The index of the selected item
*/ */
void submenu_set_selected_item(Submenu* submenu, uint32_t index); void submenu_set_selected_item(Submenu* submenu, uint32_t index);

View File

@@ -4,8 +4,13 @@
#include <furi.h> #include <furi.h>
#include <stdint.h> #include <stdint.h>
#define TEXT_BOX_MAX_SYMBOL_WIDTH (10) #define TEXT_BOX_TEXT_WIDTH (120)
#define TEXT_BOX_LINE_WIDTH (120) #define TEXT_BOX_TEXT_HEIGHT (56)
#define TEXT_BOX_MAX_LINES_PER_SCREEN (10)
#define TEXT_BOX_LINES_SCROLL_SPEED_MEDIUM (3)
#define TEXT_BOX_LINES_SCROLL_SPEED_FAST (5)
#define TEXT_BOX_LINES_SCROLL_SPEED_SATURATION (9)
struct TextBox { struct TextBox {
View* view; View* view;
@@ -14,13 +19,19 @@ struct TextBox {
}; };
typedef struct { typedef struct {
const char* text;
char* text_pos;
FuriString* text_formatted;
int32_t scroll_pos;
int32_t scroll_num;
TextBoxFont font; TextBoxFont font;
TextBoxFocus focus; TextBoxFocus focus;
const char* text;
int32_t scroll_pos;
int32_t scroll_num;
int32_t lines_on_screen;
int32_t line_offset;
int32_t text_offset;
FuriString* text_on_screen;
FuriString* text_line;
bool formatted; bool formatted;
} TextBoxModel; } TextBoxModel;
@@ -29,20 +40,11 @@ static void text_box_process_down(TextBox* text_box, uint8_t lines) {
text_box->view, text_box->view,
TextBoxModel * model, TextBoxModel * model,
{ {
if(model->scroll_pos < model->scroll_num - lines) { if(model->scroll_pos + lines < model->scroll_num) {
model->scroll_pos += lines; model->scroll_pos += lines;
for(uint8_t i = 0; i < lines; i++) { } else {
// Search next line start if(model->scroll_num > 0) {
while(*model->text_pos++ != '\n') model->scroll_pos = model->scroll_num - 1;
;
}
} else if(lines > 1) {
lines = model->scroll_num - model->scroll_pos - 1;
model->scroll_pos = model->scroll_num - 1;
for(uint8_t i = 0; i < lines; i++) {
// Search next line start
while(*model->text_pos++ != '\n')
;
} }
} }
}, },
@@ -54,69 +56,195 @@ static void text_box_process_up(TextBox* text_box, uint8_t lines) {
text_box->view, text_box->view,
TextBoxModel * model, TextBoxModel * model,
{ {
if(model->scroll_pos > lines - 1) { if(model->scroll_pos - lines > 0) {
model->scroll_pos -= lines; model->scroll_pos -= lines;
for(uint8_t i = 0; i < lines; i++) { } else {
// Reach last symbol of previous line
model->text_pos--;
// Search previous line start
while((model->text_pos != model->text) && (*(--model->text_pos) != '\n'))
;
if(*model->text_pos == '\n') {
model->text_pos++;
}
}
} else if(lines > 1) {
lines = model->scroll_pos;
model->scroll_pos = 0; model->scroll_pos = 0;
model->text_pos = (char*)model->text;
} }
}, },
true); true);
} }
static void text_box_insert_endline(Canvas* canvas, TextBoxModel* model) { static bool text_box_view_input_callback(InputEvent* event, void* context) {
size_t i = 0; furi_assert(context);
size_t line_width = 0;
const char* str = model->text;
size_t line_num = 0;
while(str[i] != '\0') { TextBox* text_box = context;
char symb = str[i++]; bool consumed = false;
if(symb != '\n') {
if(event->type == InputTypeShort || event->type == InputTypeRepeat) {
int32_t scroll_speed = 1;
if(text_box->button_held_for_ticks > TEXT_BOX_LINES_SCROLL_SPEED_FAST) {
if(text_box->button_held_for_ticks % 2) {
scroll_speed = 0;
} else {
scroll_speed =
(text_box->button_held_for_ticks > TEXT_BOX_LINES_SCROLL_SPEED_SATURATION) ?
TEXT_BOX_LINES_SCROLL_SPEED_FAST :
TEXT_BOX_LINES_SCROLL_SPEED_MEDIUM;
}
}
if(event->key == InputKeyDown) {
text_box_process_down(text_box, scroll_speed);
consumed = true;
} else if(event->key == InputKeyUp) {
text_box_process_up(text_box, scroll_speed);
consumed = true;
}
text_box->button_held_for_ticks++;
} else if(event->type == InputTypeRelease) {
text_box->button_held_for_ticks = 0;
consumed = true;
}
return consumed;
}
static bool text_box_end_of_text_reached(TextBoxModel* model) {
return model->text[model->text_offset] == '\0';
}
static bool text_box_start_of_text_reached(TextBoxModel* model) {
return model->text_offset == 0;
}
static void text_box_seek_next_line(Canvas* canvas, TextBoxModel* model) {
size_t line_width = 0;
while(!text_box_end_of_text_reached(model)) {
char symb = model->text[model->text_offset];
if(symb == '\n') {
model->text_offset++;
break;
} else {
size_t glyph_width = canvas_glyph_width(canvas, symb); size_t glyph_width = canvas_glyph_width(canvas, symb);
if(line_width + glyph_width > TEXT_BOX_LINE_WIDTH) { if(line_width + glyph_width > TEXT_BOX_TEXT_WIDTH) {
line_num++; break;
line_width = 0;
furi_string_push_back(model->text_formatted, '\n');
} }
line_width += glyph_width; line_width += glyph_width;
} else { model->text_offset++;
line_num++;
line_width = 0;
} }
furi_string_push_back(model->text_formatted, symb);
} }
line_num++; }
model->text = furi_string_get_cstr(model->text_formatted);
model->text_pos = (char*)model->text; static void text_box_seek_end_of_prev_line(TextBoxModel* model) {
size_t lines_on_screen = 56 / canvas_current_font_height(canvas); do {
if(model->focus == TextBoxFocusEnd && line_num > lines_on_screen) { if(text_box_start_of_text_reached(model)) break;
// Set text position to 5th line from the end model->text_offset--;
const char* end = model->text + furi_string_size(model->text_formatted); if(text_box_start_of_text_reached(model)) break;
for(size_t i = 0; i < line_num - lines_on_screen; i++) { if(model->text[model->text_offset] == '\n') {
while(model->text_pos < end) { model->text_offset--;
if(*model->text_pos++ == '\n') break; }
} } while(false);
}
static void text_box_seek_prev_paragraph(TextBoxModel* model) {
while(!text_box_start_of_text_reached(model)) {
if(model->text[model->text_offset] == '\n') {
model->text_offset++;
break;
}
model->text_offset--;
}
}
static void text_box_seek_prev_line(Canvas* canvas, TextBoxModel* model) {
int32_t start_text_offset = model->text_offset;
text_box_seek_end_of_prev_line(model);
text_box_seek_prev_paragraph(model);
int32_t current_text_offset = model->text_offset;
while(true) {
text_box_seek_next_line(canvas, model);
if(model->text_offset == start_text_offset) {
break;
}
current_text_offset = model->text_offset;
}
model->text_offset = current_text_offset;
}
static void text_box_move_line_offset(Canvas* canvas, TextBoxModel* model, int32_t line_offset) {
if(line_offset >= 0) {
for(int32_t i = 0; i < line_offset; i++) {
text_box_seek_next_line(canvas, model);
} }
model->scroll_num = line_num - (lines_on_screen - 1);
model->scroll_pos = line_num - lines_on_screen;
} else { } else {
model->scroll_num = MAX(line_num - (lines_on_screen - 1), 0u); for(int32_t i = 0; i < (-line_offset); i++) {
model->scroll_pos = 0; text_box_seek_prev_line(canvas, model);
}
} }
} }
static void text_box_update_screen_text(Canvas* canvas, TextBoxModel* model) {
furi_string_reset(model->text_on_screen);
furi_string_reset(model->text_line);
int32_t start_text_offset = model->text_offset;
for(int32_t i = 0; i < model->lines_on_screen; i++) {
int32_t current_line_text_offset = model->text_offset;
text_box_seek_next_line(canvas, model);
int32_t next_line_text_offset = model->text_offset;
furi_string_set_strn(
model->text_line,
&model->text[current_line_text_offset],
next_line_text_offset - current_line_text_offset);
size_t str_len = furi_string_size(model->text_line);
if(furi_string_get_char(model->text_line, str_len - 1) != '\n') {
furi_string_push_back(model->text_line, '\n');
}
furi_string_cat(model->text_on_screen, model->text_line);
if(text_box_end_of_text_reached(model)) break;
current_line_text_offset = next_line_text_offset;
}
model->text_offset = start_text_offset;
}
static void text_box_update_text_on_screen(Canvas* canvas, TextBoxModel* model) {
int32_t line_offset = model->scroll_pos - model->line_offset;
text_box_move_line_offset(canvas, model, line_offset);
text_box_update_screen_text(canvas, model);
model->line_offset = model->scroll_pos;
}
static void text_box_prepare_model(Canvas* canvas, TextBoxModel* model) {
int32_t lines_num = 0;
model->text_offset = 0;
model->scroll_num = 0;
model->scroll_pos = 0;
model->line_offset = 0;
model->lines_on_screen = TEXT_BOX_TEXT_HEIGHT / canvas_current_font_height(canvas);
// Cache text offset to quick final text offset update if TextBoxFocusEnd is set
int32_t window_offset[TEXT_BOX_MAX_LINES_PER_SCREEN] = {};
do {
window_offset[lines_num % model->lines_on_screen] = model->text_offset;
text_box_seek_next_line(canvas, model);
lines_num++;
} while(!text_box_end_of_text_reached(model));
model->text_offset = 0;
lines_num++;
if(model->focus == TextBoxFocusEnd) {
if(lines_num > model->lines_on_screen) {
model->text_offset = window_offset[(lines_num - 1) % model->lines_on_screen];
}
}
if(lines_num > model->lines_on_screen) {
model->scroll_num = lines_num - model->lines_on_screen;
model->scroll_pos = (model->focus == TextBoxFocusEnd) ? model->scroll_num - 1 : 0;
}
text_box_update_screen_text(canvas, model);
model->line_offset = model->scroll_pos;
}
static void text_box_view_draw_callback(Canvas* canvas, void* _model) { static void text_box_view_draw_callback(Canvas* canvas, void* _model) {
TextBoxModel* model = _model; TextBoxModel* model = _model;
@@ -132,44 +260,17 @@ static void text_box_view_draw_callback(Canvas* canvas, void* _model) {
} }
if(!model->formatted) { if(!model->formatted) {
text_box_insert_endline(canvas, model); text_box_prepare_model(canvas, model);
model->formatted = true; model->formatted = true;
} }
elements_slightly_rounded_frame(canvas, 0, 0, 124, 64); elements_slightly_rounded_frame(canvas, 0, 0, 124, 64);
elements_multiline_text(canvas, 3, 11, model->text_pos);
elements_scrollbar(canvas, model->scroll_pos, model->scroll_num); elements_scrollbar(canvas, model->scroll_pos, model->scroll_num);
}
static bool text_box_view_input_callback(InputEvent* event, void* context) { if(model->line_offset != model->scroll_pos) {
furi_assert(context); text_box_update_text_on_screen(canvas, model);
TextBox* text_box = context;
bool consumed = false;
if(event->type == InputTypeShort || event->type == InputTypeRepeat) {
int32_t scroll_speed = 1;
if(text_box->button_held_for_ticks > 5) {
if(text_box->button_held_for_ticks % 2) {
scroll_speed = 0;
} else {
scroll_speed = text_box->button_held_for_ticks > 9 ? 5 : 3;
}
}
if(event->key == InputKeyDown) {
text_box_process_down(text_box, scroll_speed);
consumed = true;
} else if(event->key == InputKeyUp) {
text_box_process_up(text_box, scroll_speed);
consumed = true;
}
text_box->button_held_for_ticks++;
} else if(event->type == InputTypeRelease) {
text_box->button_held_for_ticks = 0;
consumed = true;
} }
return consumed; elements_multiline_text(canvas, 3, 11, furi_string_get_cstr(model->text_on_screen));
} }
TextBox* text_box_alloc(void) { TextBox* text_box_alloc(void) {
@@ -185,7 +286,8 @@ TextBox* text_box_alloc(void) {
TextBoxModel * model, TextBoxModel * model,
{ {
model->text = NULL; model->text = NULL;
model->text_formatted = furi_string_alloc_set(""); model->text_on_screen = furi_string_alloc();
model->text_line = furi_string_alloc();
model->formatted = false; model->formatted = false;
model->font = TextBoxFontText; model->font = TextBoxFontText;
}, },
@@ -198,7 +300,13 @@ void text_box_free(TextBox* text_box) {
furi_check(text_box); furi_check(text_box);
with_view_model( with_view_model(
text_box->view, TextBoxModel * model, { furi_string_free(model->text_formatted); }, true); text_box->view,
TextBoxModel * model,
{
furi_string_free(model->text_on_screen);
furi_string_free(model->text_line);
},
true);
view_free(text_box->view); view_free(text_box->view);
free(text_box); free(text_box);
} }
@@ -216,9 +324,15 @@ void text_box_reset(TextBox* text_box) {
TextBoxModel * model, TextBoxModel * model,
{ {
model->text = NULL; model->text = NULL;
furi_string_set(model->text_formatted, "");
model->font = TextBoxFontText; model->font = TextBoxFontText;
model->focus = TextBoxFocusStart; model->focus = TextBoxFocusStart;
furi_string_reset(model->text_line);
furi_string_reset(model->text_on_screen);
model->line_offset = 0;
model->text_offset = 0;
model->lines_on_screen = 0;
model->scroll_num = 0;
model->scroll_pos = 0;
model->formatted = false; model->formatted = false;
}, },
true); true);
@@ -227,16 +341,12 @@ void text_box_reset(TextBox* text_box) {
void text_box_set_text(TextBox* text_box, const char* text) { void text_box_set_text(TextBox* text_box, const char* text) {
furi_check(text_box); furi_check(text_box);
furi_check(text); furi_check(text);
size_t str_length = strlen(text);
size_t formating_margin = str_length * TEXT_BOX_MAX_SYMBOL_WIDTH / TEXT_BOX_LINE_WIDTH;
with_view_model( with_view_model(
text_box->view, text_box->view,
TextBoxModel * model, TextBoxModel * model,
{ {
model->text = text; model->text = text;
furi_string_reset(model->text_formatted);
furi_string_reserve(model->text_formatted, str_length + formating_margin);
model->formatted = false; model->formatted = false;
}, },
true); true);

View File

@@ -97,10 +97,11 @@ void view_free_model(View* view) {
furi_mutex_free(model->mutex); furi_mutex_free(model->mutex);
free(model->data); free(model->data);
free(model); free(model);
view->model = NULL;
} else { } else {
furi_crash(); furi_crash();
} }
view->model = NULL;
view->model_type = ViewModelTypeNone;
} }
void* view_get_model(View* view) { void* view_get_model(View* view) {

View File

@@ -10,19 +10,6 @@
static_assert(!has_hash_collisions(elf_api_table), "Detected API method hash collision!"); static_assert(!has_hash_collisions(elf_api_table), "Detected API method hash collision!");
#ifdef APP_UNIT_TESTS
constexpr HashtableApiInterface mock_elf_api_interface{
{
.api_version_major = 0,
.api_version_minor = 0,
.resolver_callback = &elf_resolve_from_hashtable,
},
nullptr,
nullptr,
};
const ElfApiInterface* const firmware_api_interface = &mock_elf_api_interface;
#else
constexpr HashtableApiInterface elf_api_interface{ constexpr HashtableApiInterface elf_api_interface{
{ {
.api_version_major = (elf_api_version >> 16), .api_version_major = (elf_api_version >> 16),
@@ -33,7 +20,6 @@ constexpr HashtableApiInterface elf_api_interface{
elf_api_table.cend(), elf_api_table.cend(),
}; };
const ElfApiInterface* const firmware_api_interface = &elf_api_interface; const ElfApiInterface* const firmware_api_interface = &elf_api_interface;
#endif
extern "C" void furi_hal_info_get_api_version(uint16_t* major, uint16_t* minor) { extern "C" void furi_hal_info_get_api_version(uint16_t* major, uint16_t* minor) {
*major = firmware_api_interface->api_version_major; *major = firmware_api_interface->api_version_major;

View File

@@ -388,6 +388,12 @@ static LoaderStatus loader_start_external_app(
FURI_LOG_I(TAG, "Loaded in %zums", (size_t)(furi_get_tick() - start)); FURI_LOG_I(TAG, "Loaded in %zums", (size_t)(furi_get_tick() - start));
if(flipper_application_is_plugin(loader->app.fap)) {
status = loader_make_status_error(
LoaderStatusErrorInternal, error_message, "Plugin %s is not runnable", path);
break;
}
loader->app.thread = flipper_application_alloc_thread(loader->app.fap, args); loader->app.thread = flipper_application_alloc_thread(loader->app.fap, args);
FuriString* app_name = furi_string_alloc(); FuriString* app_name = furi_string_alloc();
path_extract_filename_no_ext(path, app_name); path_extract_filename_no_ext(path, app_name);

View File

@@ -92,7 +92,7 @@ static bool loader_applications_item_callback(
path, loader_applications_app->storage, icon_ptr, item_name); path, loader_applications_app->storage, icon_ptr, item_name);
} else { } else {
path_extract_filename(path, item_name, false); path_extract_filename(path, item_name, false);
memcpy(*icon_ptr, icon_get_data(&I_js_script_10px), FAP_MANIFEST_MAX_ICON_SIZE); memcpy(*icon_ptr, icon_get_frame_data(&I_js_script_10px, 0), FAP_MANIFEST_MAX_ICON_SIZE);
return true; return true;
} }
} }

View File

@@ -7,6 +7,10 @@
#include <flipper.pb.h> #include <flipper.pb.h>
#include <cli/cli.h> #include <cli/cli.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef void* (*RpcSystemAlloc)(RpcSession* session); typedef void* (*RpcSystemAlloc)(RpcSession* session);
typedef void (*RpcSystemFree)(void* context); typedef void (*RpcSystemFree)(void* context);
typedef void (*PBMessageHandler)(const PB_Main* msg_request, void* context); typedef void (*PBMessageHandler)(const PB_Main* msg_request, void* context);
@@ -45,3 +49,7 @@ void rpc_debug_print_data(const char* prefix, uint8_t* buffer, size_t size);
void rpc_cli_command_start_session(Cli* cli, FuriString* args, void* context); void rpc_cli_command_start_session(Cli* cli, FuriString* args, void* context);
PB_CommandStatus rpc_system_storage_get_error(FS_Error fs_error); PB_CommandStatus rpc_system_storage_get_error(FS_Error fs_error);
#ifdef __cplusplus
}
#endif

View File

@@ -43,7 +43,7 @@ static DialogMessageButton product_screen(DialogsApp* dialogs, DialogMessage* me
static DialogMessageButton address_screen(DialogsApp* dialogs, DialogMessage* message) { static DialogMessageButton address_screen(DialogsApp* dialogs, DialogMessage* message) {
DialogMessageButton result; DialogMessageButton result;
const char* screen_text = "Flipper Devices Inc\n" const char* screen_text = "Flipper Devices Inc.\n"
"Suite B #551, 2803\n" "Suite B #551, 2803\n"
"Philadelphia Pike, Claymont\n" "Philadelphia Pike, Claymont\n"
"DE, USA 19703\n"; "DE, USA 19703\n";
@@ -59,7 +59,7 @@ static DialogMessageButton compliance_screen(DialogsApp* dialogs, DialogMessage*
DialogMessageButton result; DialogMessageButton result;
const char* screen_text = "For all compliance\n" const char* screen_text = "For all compliance\n"
"certificates please visit:\n" "certificates, please visit:\n"
"www.flipp.dev/compliance"; "www.flipp.dev/compliance";
dialog_message_set_text(message, screen_text, 0, 0, AlignLeft, AlignTop); dialog_message_set_text(message, screen_text, 0, 0, AlignLeft, AlignTop);
@@ -226,9 +226,11 @@ int32_t about_settings_app(void* p) {
while(1) { while(1) {
if(screen_index >= COUNT_OF(about_screens) - 1) { if(screen_index >= COUNT_OF(about_screens) - 1) {
dialog_message_set_buttons(message, "Back", NULL, NULL); dialog_message_set_buttons(message, "Prev.", NULL, NULL);
} else if(screen_index == 0) {
dialog_message_set_buttons(message, NULL, NULL, "Next");
} else { } else {
dialog_message_set_buttons(message, "Back", NULL, "Next"); dialog_message_set_buttons(message, "Prev.", NULL, "Next");
} }
screen_result = about_screens[screen_index](dialogs, message); screen_result = about_screens[screen_index](dialogs, message);

View File

@@ -11,10 +11,10 @@ void bt_settings_scene_forget_dev_confirm_dialog_callback(DialogExResult result,
void bt_settings_scene_forget_dev_confirm_on_enter(void* context) { void bt_settings_scene_forget_dev_confirm_on_enter(void* context) {
BtSettingsApp* app = context; BtSettingsApp* app = context;
DialogEx* dialog = app->dialog; DialogEx* dialog = app->dialog;
dialog_ex_set_header(dialog, "Unpair All Devices?", 64, 3, AlignCenter, AlignTop); dialog_ex_set_header(dialog, "Unpair All Devices?", 64, 0, AlignCenter, AlignTop);
dialog_ex_set_text( dialog_ex_set_text(
dialog, "All previous pairings\nwill be lost!", 64, 22, AlignCenter, AlignTop); dialog, "All previous pairings\nwill be lost!", 64, 14, AlignCenter, AlignTop);
dialog_ex_set_left_button_text(dialog, "Back"); dialog_ex_set_left_button_text(dialog, "Cancel");
dialog_ex_set_right_button_text(dialog, "Unpair"); dialog_ex_set_right_button_text(dialog, "Unpair");
dialog_ex_set_context(dialog, app); dialog_ex_set_context(dialog, app);
dialog_ex_set_result_callback(dialog, bt_settings_scene_forget_dev_confirm_dialog_callback); dialog_ex_set_result_callback(dialog, bt_settings_scene_forget_dev_confirm_dialog_callback);

View File

@@ -53,7 +53,7 @@ void bt_settings_scene_start_on_enter(void* context) {
variable_item_set_current_value_index(item, BtSettingOff); variable_item_set_current_value_index(item, BtSettingOff);
variable_item_set_current_value_text(item, bt_settings_text[BtSettingOff]); variable_item_set_current_value_text(item, bt_settings_text[BtSettingOff]);
} }
variable_item_list_add(var_item_list, "Forget All Paired Devices", 1, NULL, NULL); variable_item_list_add(var_item_list, "Unpair All Devices", 1, NULL, NULL);
variable_item_list_set_enter_callback( variable_item_list_set_enter_callback(
var_item_list, bt_settings_scene_start_var_list_enter_callback, app); var_item_list, bt_settings_scene_start_var_list_enter_callback, app);
} else { } else {

View File

@@ -133,6 +133,7 @@ void desktop_settings_app_free(DesktopSettingsApp* app) {
extern int32_t desktop_settings_app(void* p) { extern int32_t desktop_settings_app(void* p) {
DesktopSettingsApp* app = desktop_settings_app_alloc(); DesktopSettingsApp* app = desktop_settings_app_alloc();
DESKTOP_SETTINGS_LOAD(&app->settings); DESKTOP_SETTINGS_LOAD(&app->settings);
if(p && (strcmp(p, DESKTOP_SETTINGS_RUN_PIN_SETUP_ARG) == 0)) { if(p && (strcmp(p, DESKTOP_SETTINGS_RUN_PIN_SETUP_ARG) == 0)) {
scene_manager_next_scene(app->scene_manager, DesktopSettingsAppScenePinSetupHowto); scene_manager_next_scene(app->scene_manager, DesktopSettingsAppScenePinSetupHowto);
} else { } else {
@@ -140,6 +141,9 @@ extern int32_t desktop_settings_app(void* p) {
} }
view_dispatcher_run(app->view_dispatcher); view_dispatcher_run(app->view_dispatcher);
DESKTOP_SETTINGS_SAVE(&app->settings);
desktop_settings_app_free(app); desktop_settings_app_free(app);
return 0; return 0;
} }

View File

@@ -47,4 +47,5 @@ typedef struct {
char device_name[FURI_HAL_VERSION_ARRAY_NAME_LENGTH]; char device_name[FURI_HAL_VERSION_ARRAY_NAME_LENGTH];
uint8_t menu_idx; uint8_t menu_idx;
uint32_t pin_menu_idx;
} DesktopSettingsApp; } DesktopSettingsApp;

View File

@@ -25,7 +25,7 @@ void desktop_settings_scene_pin_disable_on_enter(void* context) {
popup_set_context(app->popup, app); popup_set_context(app->popup, app);
popup_set_callback(app->popup, pin_disable_back_callback); popup_set_callback(app->popup, pin_disable_back_callback);
popup_set_icon(app->popup, 0, 2, &I_DolphinMafia_119x62); popup_set_icon(app->popup, 0, 2, &I_DolphinMafia_119x62);
popup_set_header(app->popup, "PIN\nDeleted!", 100, 0, AlignCenter, AlignTop); popup_set_header(app->popup, "Removed", 100, 10, AlignCenter, AlignTop);
popup_set_timeout(app->popup, 1500); popup_set_timeout(app->popup, 1500);
popup_enable_timeout(app->popup); popup_enable_timeout(app->popup);
view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewIdPopup); view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewIdPopup);

View File

@@ -37,14 +37,14 @@ void desktop_settings_scene_pin_menu_on_enter(void* context) {
submenu_add_item( submenu_add_item(
submenu, submenu,
"Disable", "Remove PIN",
SCENE_EVENT_DISABLE_PIN, SCENE_EVENT_DISABLE_PIN,
desktop_settings_scene_pin_menu_submenu_callback, desktop_settings_scene_pin_menu_submenu_callback,
app); app);
} }
submenu_set_header(app->submenu, "PIN Code Settings"); submenu_set_header(app->submenu, "PIN Code Settings");
submenu_set_selected_item(app->submenu, app->menu_idx); submenu_set_selected_item(app->submenu, app->pin_menu_idx);
view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewMenu); view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewMenu);
} }
@@ -76,11 +76,16 @@ bool desktop_settings_scene_pin_menu_on_event(void* context, SceneManagerEvent e
consumed = true; consumed = true;
break; break;
} }
} else if(event.type == SceneManagerEventTypeBack) {
submenu_set_selected_item(app->submenu, 0);
} }
return consumed; return consumed;
} }
void desktop_settings_scene_pin_menu_on_exit(void* context) { void desktop_settings_scene_pin_menu_on_exit(void* context) {
DesktopSettingsApp* app = context; DesktopSettingsApp* app = context;
app->pin_menu_idx = submenu_get_selected_item(app->submenu);
submenu_reset(app->submenu); submenu_reset(app->submenu);
} }

View File

@@ -97,6 +97,7 @@ bool desktop_settings_scene_pin_setup_on_event(void* context, SceneManagerEvent
break; break;
} }
} }
return consumed; return consumed;
} }

View File

@@ -27,6 +27,7 @@ void desktop_settings_scene_pin_setup_done_on_enter(void* context) {
DESKTOP_SETTINGS_SAVE(&app->settings); DESKTOP_SETTINGS_SAVE(&app->settings);
NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
notification_message(notification, &sequence_single_vibro); notification_message(notification, &sequence_single_vibro);
notification_message(notification, &sequence_blink_green_10);
furi_record_close(RECORD_NOTIFICATION); furi_record_close(RECORD_NOTIFICATION);
desktop_view_pin_input_set_context(app->pin_input_view, app); desktop_view_pin_input_set_context(app->pin_input_view, app);

View File

@@ -24,9 +24,9 @@ static void desktop_settings_view_pin_setup_howto2_draw(Canvas* canvas, void* mo
elements_multiline_text_aligned( elements_multiline_text_aligned(
canvas, canvas,
64, 64,
24, 0,
AlignCenter,
AlignCenter, AlignCenter,
AlignTop,
"Forgotten PIN can only be\n" "Forgotten PIN can only be\n"
"reset with entire device.\n" "reset with entire device.\n"
"Read docs How to reset PIN."); "Read docs How to reset PIN.");

View File

@@ -30,3 +30,5 @@ typedef enum {
PowerSettingsAppViewSubmenu, PowerSettingsAppViewSubmenu,
PowerSettingsAppViewDialog, PowerSettingsAppViewDialog,
} PowerSettingsAppView; } PowerSettingsAppView;
typedef enum { RebootTypeDFU, RebootTypeNormal } RebootType;

View File

@@ -1,4 +1,5 @@
ADD_SCENE(power_settings, start, Start) ADD_SCENE(power_settings, start, Start)
ADD_SCENE(power_settings, battery_info, BatteryInfo) ADD_SCENE(power_settings, battery_info, BatteryInfo)
ADD_SCENE(power_settings, reboot, Reboot) ADD_SCENE(power_settings, reboot, Reboot)
ADD_SCENE(power_settings, reboot_confirm, RebootConfirm)
ADD_SCENE(power_settings, power_off, PowerOff) ADD_SCENE(power_settings, power_off, PowerOff)

View File

@@ -10,12 +10,12 @@ void power_settings_scene_power_off_on_enter(void* context) {
PowerSettingsApp* app = context; PowerSettingsApp* app = context;
DialogEx* dialog = app->dialog; DialogEx* dialog = app->dialog;
dialog_ex_set_header(dialog, "Turn OFF Device?", 64, 2, AlignCenter, AlignTop); dialog_ex_set_header(dialog, "Turn Off Device?", 64, 0, AlignCenter, AlignTop);
dialog_ex_set_text( dialog_ex_set_text(
dialog, " I will be\nwaiting for\n you here...", 78, 16, AlignLeft, AlignTop); dialog, " I will be\nwaiting for\n you here...", 78, 14, AlignLeft, AlignTop);
dialog_ex_set_icon(dialog, 21, 13, &I_Cry_dolph_55x52); dialog_ex_set_icon(dialog, 14, 10, &I_dolph_cry_49x54);
dialog_ex_set_left_button_text(dialog, "Back"); dialog_ex_set_left_button_text(dialog, "Cancel");
dialog_ex_set_right_button_text(dialog, "OFF"); dialog_ex_set_right_button_text(dialog, "Power Off");
dialog_ex_set_result_callback(dialog, power_settings_scene_power_off_dialog_callback); dialog_ex_set_result_callback(dialog, power_settings_scene_power_off_dialog_callback);
dialog_ex_set_context(dialog, app); dialog_ex_set_context(dialog, app);

View File

@@ -24,7 +24,7 @@ void power_settings_scene_reboot_on_enter(void* context) {
app); app);
submenu_add_item( submenu_add_item(
submenu, submenu,
"Flipper OS", "Reboot Flipper",
PowerSettingsRebootSubmenuIndexOs, PowerSettingsRebootSubmenuIndexOs,
power_settings_scene_reboot_submenu_callback, power_settings_scene_reboot_submenu_callback,
app); app);
@@ -33,14 +33,18 @@ void power_settings_scene_reboot_on_enter(void* context) {
} }
bool power_settings_scene_reboot_on_event(void* context, SceneManagerEvent event) { bool power_settings_scene_reboot_on_event(void* context, SceneManagerEvent event) {
UNUSED(context); PowerSettingsApp* app = context;
bool consumed = false; bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) { if(event.type == SceneManagerEventTypeCustom) {
if(event.event == PowerSettingsRebootSubmenuIndexDfu) { if(event.event == PowerSettingsRebootSubmenuIndexDfu) {
power_reboot(PowerBootModeDfu); scene_manager_set_scene_state(
app->scene_manager, PowerSettingsAppSceneRebootConfirm, RebootTypeDFU);
scene_manager_next_scene(app->scene_manager, PowerSettingsAppSceneRebootConfirm);
} else if(event.event == PowerSettingsRebootSubmenuIndexOs) { } else if(event.event == PowerSettingsRebootSubmenuIndexOs) {
power_reboot(PowerBootModeNormal); scene_manager_set_scene_state(
app->scene_manager, PowerSettingsAppSceneRebootConfirm, RebootTypeNormal);
scene_manager_next_scene(app->scene_manager, PowerSettingsAppSceneRebootConfirm);
} }
consumed = true; consumed = true;
} }

View File

@@ -0,0 +1,66 @@
#include "../power_settings_app.h"
void power_settings_scene_reboot_confirm_dialog_callback(DialogExResult result, void* context) {
furi_assert(context);
PowerSettingsApp* app = context;
view_dispatcher_send_custom_event(app->view_dispatcher, result);
}
void power_settings_scene_reboot_confirm_on_enter(void* context) {
PowerSettingsApp* app = context;
DialogEx* dialog = app->dialog;
RebootType reboot_type =
scene_manager_get_scene_state(app->scene_manager, PowerSettingsAppSceneRebootConfirm);
if(reboot_type == RebootTypeDFU) {
dialog_ex_set_header(dialog, "Reboot to DFU Mode?", 64, 0, AlignCenter, AlignTop);
dialog_ex_set_text(
dialog,
"Needed for device maintenance\nor firmware upgrades",
64,
14,
AlignCenter,
AlignTop);
} else if(reboot_type == RebootTypeNormal) {
dialog_ex_set_header(dialog, "Reboot Flipper?", 64, 0, AlignCenter, AlignTop);
dialog_ex_set_text(
dialog, "May help with some firmware\n issues", 64, 14, AlignCenter, AlignTop);
} else {
furi_crash("Invalid reboot type");
}
dialog_ex_set_left_button_text(dialog, "Cancel");
dialog_ex_set_right_button_text(dialog, "Reboot");
dialog_ex_set_result_callback(dialog, power_settings_scene_reboot_confirm_dialog_callback);
dialog_ex_set_context(dialog, app);
view_dispatcher_switch_to_view(app->view_dispatcher, PowerSettingsAppViewDialog);
}
bool power_settings_scene_reboot_confirm_on_event(void* context, SceneManagerEvent event) {
PowerSettingsApp* app = context;
bool consumed = false;
RebootType reboot_type =
scene_manager_get_scene_state(app->scene_manager, PowerSettingsAppSceneRebootConfirm);
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == DialogExResultLeft) {
scene_manager_previous_scene(app->scene_manager);
} else if(event.event == DialogExResultRight) {
if(reboot_type == RebootTypeDFU) {
power_reboot(PowerBootModeDfu);
} else {
power_reboot(PowerBootModeNormal);
}
}
consumed = true;
}
return consumed;
}
void power_settings_scene_reboot_confirm_on_exit(void* context) {
PowerSettingsApp* app = context;
dialog_ex_reset(app->dialog);
}

View File

@@ -1,5 +1,7 @@
#include "../storage_settings.h" #include "../storage_settings.h"
#include <furi_hal.h> #include <furi_hal.h>
#include <notification/notification.h>
#include <notification/notification_messages.h>
#define BENCH_DATA_SIZE 4096 #define BENCH_DATA_SIZE 4096
#define BENCH_COUNT 6 #define BENCH_COUNT 6
@@ -86,7 +88,8 @@ static void storage_settings_scene_benchmark(StorageSettings* app) {
uint32_t bench_w_speed[BENCH_COUNT] = {0, 0, 0, 0, 0, 0}; uint32_t bench_w_speed[BENCH_COUNT] = {0, 0, 0, 0, 0, 0};
uint32_t bench_r_speed[BENCH_COUNT] = {0, 0, 0, 0, 0, 0}; uint32_t bench_r_speed[BENCH_COUNT] = {0, 0, 0, 0, 0, 0};
dialog_ex_set_header(dialog_ex, "Benchmarking...", 64, 32, AlignCenter, AlignCenter); dialog_ex_set_header(dialog_ex, "Benchmarking...", 74, 32, AlignCenter, AlignCenter);
dialog_ex_set_icon(dialog_ex, 12, 20, &I_LoadingHourglass_24x24);
for(size_t i = 0; i < BENCH_COUNT; i++) { for(size_t i = 0; i < BENCH_COUNT; i++) {
if(!storage_settings_scene_bench_write( if(!storage_settings_scene_bench_write(
app->fs_api, bench_size[i], bench_data, &bench_w_speed[i])) app->fs_api, bench_size[i], bench_data, &bench_w_speed[i]))
@@ -95,6 +98,7 @@ static void storage_settings_scene_benchmark(StorageSettings* app) {
if(i > 0) furi_string_cat_printf(app->text_string, "\n"); if(i > 0) furi_string_cat_printf(app->text_string, "\n");
furi_string_cat_printf(app->text_string, "%ub : W %luK ", bench_size[i], bench_w_speed[i]); furi_string_cat_printf(app->text_string, "%ub : W %luK ", bench_size[i], bench_w_speed[i]);
dialog_ex_set_header(dialog_ex, NULL, 0, 0, AlignCenter, AlignCenter); dialog_ex_set_header(dialog_ex, NULL, 0, 0, AlignCenter, AlignCenter);
dialog_ex_set_icon(dialog_ex, 0, 0, NULL);
dialog_ex_set_text( dialog_ex_set_text(
dialog_ex, furi_string_get_cstr(app->text_string), 0, 32, AlignLeft, AlignCenter); dialog_ex, furi_string_get_cstr(app->text_string), 0, 32, AlignLeft, AlignCenter);
@@ -110,6 +114,12 @@ static void storage_settings_scene_benchmark(StorageSettings* app) {
dialog_ex, furi_string_get_cstr(app->text_string), 0, 32, AlignLeft, AlignCenter); dialog_ex, furi_string_get_cstr(app->text_string), 0, 32, AlignLeft, AlignCenter);
} }
NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
notification_message(notification, &sequence_single_vibro);
notification_message(notification, &sequence_set_green_255);
notification_message(notification, &sequence_success);
furi_record_close(RECORD_NOTIFICATION);
free(bench_data); free(bench_data);
} }
@@ -146,11 +156,17 @@ bool storage_settings_scene_benchmark_on_event(void* context, SceneManagerEvent
if(event.type == SceneManagerEventTypeCustom) { if(event.type == SceneManagerEventTypeCustom) {
switch(event.event) { switch(event.event) {
case DialogExResultCenter: case DialogExResultCenter:
consumed = scene_manager_previous_scene(app->scene_manager); consumed = scene_manager_search_and_switch_to_previous_scene(
app->scene_manager, StorageSettingsStart);
break; break;
} }
} else if(event.type == SceneManagerEventTypeBack && sd_status != FSE_OK) { } else if(event.type == SceneManagerEventTypeBack) {
consumed = true; if(sd_status == FSE_OK) {
consumed = scene_manager_search_and_switch_to_previous_scene(
app->scene_manager, StorageSettingsStart);
} else {
consumed = true;
}
} }
return consumed; return consumed;
@@ -160,6 +176,10 @@ void storage_settings_scene_benchmark_on_exit(void* context) {
StorageSettings* app = context; StorageSettings* app = context;
DialogEx* dialog_ex = app->dialog_ex; DialogEx* dialog_ex = app->dialog_ex;
NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
notification_message(notification, &sequence_reset_green);
furi_record_close(RECORD_NOTIFICATION);
dialog_ex_reset(dialog_ex); dialog_ex_reset(dialog_ex);
furi_string_reset(app->text_string); furi_string_reset(app->text_string);

View File

@@ -0,0 +1,70 @@
#include "../storage_settings.h"
static void
storage_settings_scene_benchmark_confirm_dialog_callback(DialogExResult result, void* context) {
StorageSettings* app = context;
view_dispatcher_send_custom_event(app->view_dispatcher, result);
}
void storage_settings_scene_benchmark_confirm_on_enter(void* context) {
StorageSettings* app = context;
DialogEx* dialog_ex = app->dialog_ex;
FS_Error sd_status = storage_sd_status(app->fs_api);
if(sd_status == FSE_NOT_READY) {
dialog_ex_set_icon(dialog_ex, 83, 22, &I_WarningDolphinFlip_45x42);
dialog_ex_set_header(dialog_ex, "SD Card Not Mounted", 64, 3, AlignCenter, AlignTop);
dialog_ex_set_text(
dialog_ex, "Try to reinsert\nor format SD\ncard.", 3, 19, AlignLeft, AlignTop);
dialog_ex_set_center_button_text(dialog_ex, "Ok");
} else {
dialog_ex_set_header(dialog_ex, "Benchmark SD Card?", 64, 0, AlignCenter, AlignTop);
dialog_ex_set_text(
dialog_ex,
"SD will be tested in SPI\nmode. Learn more:\nr.flipper.net/sd_test",
0,
12,
AlignLeft,
AlignTop);
dialog_ex_set_icon(dialog_ex, 103, 12, &I_qr_benchmark_25x25);
dialog_ex_set_left_button_text(dialog_ex, "Cancel");
dialog_ex_set_right_button_text(dialog_ex, "Benchmark");
}
dialog_ex_set_context(dialog_ex, app);
dialog_ex_set_result_callback(
dialog_ex, storage_settings_scene_benchmark_confirm_dialog_callback);
view_dispatcher_switch_to_view(app->view_dispatcher, StorageSettingsViewDialogEx);
}
bool storage_settings_scene_benchmark_confirm_on_event(void* context, SceneManagerEvent event) {
StorageSettings* app = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
switch(event.event) {
case DialogExResultLeft:
case DialogExResultCenter:
consumed = scene_manager_previous_scene(app->scene_manager);
break;
case DialogExResultRight:
scene_manager_next_scene(app->scene_manager, StorageSettingsBenchmark);
consumed = true;
break;
}
} else if(event.type == SceneManagerEventTypeBack) {
consumed = true;
}
return consumed;
}
void storage_settings_scene_benchmark_confirm_on_exit(void* context) {
StorageSettings* app = context;
DialogEx* dialog_ex = app->dialog_ex;
dialog_ex_reset(dialog_ex);
}

Some files were not shown because too many files have changed in this diff Show More