Merge branch 'release-candidate' into release

This commit is contained in:
Aleksandr Kutuzov 2022-07-13 18:49:21 +09:00
commit 3e5d499b44
No known key found for this signature in database
GPG Key ID: 0D0011717914BBCD
157 changed files with 7259 additions and 1302 deletions

View File

@ -144,7 +144,7 @@ jobs:
with:
issue-number: ${{ github.event.pull_request.number }}
comment-author: 'github-actions[bot]'
body-includes: 'Install with web updater'
body-includes: 'Compiled firmware for commit'
- name: 'Create or update comment'
if: ${{ !github.event.pull_request.head.repo.fork && github.event.pull_request}}
@ -153,7 +153,10 @@ jobs:
comment-id: ${{ steps.fc.outputs.comment-id }}
issue-number: ${{ github.event.pull_request.number }}
body: |
[Install with web updater](https://my.flipp.dev/?url=https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.artifacts-path}}/flipper-z-${{steps.names.outputs.default-target}}-update-${{steps.names.outputs.suffix}}.tgz&channel=${{steps.names.outputs.artifacts-path}}&version=${{steps.names.outputs.short-hash}}).
**Compiled firmware for commit `${{steps.names.outputs.short-hash}}`:**
- [📦 Update package](https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.artifacts-path}}/flipper-z-${{steps.names.outputs.default-target}}-update-${{steps.names.outputs.suffix}}.tgz)
- [📥 DFU file](https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.artifacts-path}}/flipper-z-${{steps.names.outputs.default-target}}-full-${{steps.names.outputs.suffix}}.dfu)
- [☁️ Web updater](https://my.flipp.dev/?url=https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.artifacts-path}}/flipper-z-${{steps.names.outputs.default-target}}-update-${{steps.names.outputs.suffix}}.tgz&channel=${{steps.names.outputs.artifacts-path}}&version=${{steps.names.outputs.short-hash}})
edit-mode: replace
compact:

3
.gitmodules vendored
View File

@ -25,3 +25,6 @@
[submodule "lib/scons"]
path = lib/scons
url = https://github.com/SCons/scons.git
[submodule "lib/mbedtls"]
path = lib/mbedtls
url = https://github.com/Mbed-TLS/mbedtls.git

View File

@ -27,9 +27,15 @@ They both must be flashed in order described.
## With offline update package
With Flipper attached over USB:
`./fbt --with-updater flash_usb`
Just building the package:
`./fbt --with-updater updater_package`
Copy the resulting directory to Flipper's SD card and navigate to `update.fuf` file in Archive app.
To update, copy the resulting directory to Flipper's SD card and navigate to `update.fuf` file in Archive app.
## With STLink

View File

@ -17,7 +17,6 @@ fbt_variables = SConscript("site_scons/commandline.scons")
cmd_environment = Environment(tools=[], variables=fbt_variables)
Help(fbt_variables.GenerateHelpText(cmd_environment))
# Building basic environment - tools, utility methods, cross-compilation
# settings, gcc flags for Cortex-M4, basic builders and more
coreenv = SConscript(
@ -29,24 +28,49 @@ SConscript("site_scons/cc.scons", exports={"ENV": coreenv})
# Store root dir in environment for certain tools
coreenv["ROOT_DIR"] = Dir(".")
# Create a separate "dist" environment and add construction envs to it
distenv = coreenv.Clone(
tools=["fbt_dist", "openocd"],
GDBOPTS="-ex 'target extended-remote | ${OPENOCD} -c \"gdb_port pipe\" ${OPENOCD_OPTS}' "
'-ex "set confirm off" ',
tools=["fbt_dist", "openocd", "blackmagic"],
OPENOCD_GDB_PIPE=["|openocd -c 'gdb_port pipe' ${[SINGLEQUOTEFUNC(OPENOCD_OPTS)]}"],
GDBOPTS_BASE=[
"-ex",
"target extended-remote ${GDBREMOTE}",
"-ex",
"set confirm off",
],
GDBOPTS_BLACKMAGIC=[
"-ex",
"monitor swdp_scan",
"-ex",
"monitor debug_bmp enable",
"-ex",
"attach 1",
"-ex",
"set mem inaccessible-by-default off",
],
GDBPYOPTS=[
"-ex",
"source debug/FreeRTOS/FreeRTOS.py",
"-ex",
"source debug/PyCortexMDebug/PyCortexMDebug.py",
"-ex",
"svd_load ${SVD_FILE}",
"-ex",
"compare-sections",
],
ENV=os.environ,
)
firmware_out = distenv.AddFwProject(
firmware_env = distenv.AddFwProject(
base_env=coreenv,
fw_type="firmware",
fw_env_key="FW_ENV",
)
# If enabled, initialize updater-related targets
if GetOption("fullenv"):
updater_out = distenv.AddFwProject(
updater_env = distenv.AddFwProject(
base_env=coreenv,
fw_type="updater",
fw_env_key="UPD_ENV",
@ -71,91 +95,120 @@ if GetOption("fullenv"):
"--splash",
distenv.subst("assets/slideshow/$UPDATE_SPLASH"),
]
selfupdate_dist = distenv.DistBuilder(
"selfupdate.pseudo",
(distenv["DIST_DEPENDS"], firmware_out["FW_RESOURCES"]),
selfupdate_dist = distenv.DistCommand(
"updater_package",
(distenv["DIST_DEPENDS"], firmware_env["FW_RESOURCES"]),
DIST_EXTRA=dist_arguments,
)
distenv.Pseudo("selfupdate.pseudo")
AlwaysBuild(selfupdate_dist)
Alias("updater_package", selfupdate_dist)
# Updater debug
debug_updater_elf = distenv.AddDebugTarget(updater_out, False)
Alias("updater_debug", debug_updater_elf)
distenv.PhonyTarget(
"updater_debug",
"${GDBPYCOM}",
source=updater_env["FW_ELF"],
GDBREMOTE="${OPENOCD_GDB_PIPE}",
)
distenv.PhonyTarget(
"updater_blackmagic",
"${GDBPYCOM}",
source=updater_env["FW_ELF"],
GDBOPTS=distenv.subst("$GDBOPTS_BLACKMAGIC"),
GDBREMOTE="${BLACKMAGIC_ADDR}",
)
# Installation over USB & CLI
usb_update_package = distenv.UsbInstall(
"usbinstall.flag",
(distenv["DIST_DEPENDS"], firmware_out["FW_RESOURCES"], selfupdate_dist),
"#build/usbinstall.flag",
(
distenv["DIST_DEPENDS"],
firmware_env["FW_RESOURCES"],
selfupdate_dist,
),
)
if distenv["FORCE"]:
AlwaysBuild(usb_update_package)
Depends(usb_update_package, selfupdate_dist)
Alias("flash_usb", usb_update_package)
distenv.AlwaysBuild(usb_update_package)
distenv.Depends(usb_update_package, selfupdate_dist)
distenv.Alias("flash_usb", usb_update_package)
# Target for copying & renaming binaries to dist folder
basic_dist = distenv.DistBuilder("dist.pseudo", distenv["DIST_DEPENDS"])
distenv.Pseudo("dist.pseudo")
AlwaysBuild(basic_dist)
Alias("fw_dist", basic_dist)
Default(basic_dist)
basic_dist = distenv.DistCommand("fw_dist", distenv["DIST_DEPENDS"])
distenv.Default(basic_dist)
# Target for bundling core2 package for qFlipper
copro_dist = distenv.CoproBuilder(
Dir("assets/core2_firmware"),
distenv.Dir("assets/core2_firmware"),
[],
)
AlwaysBuild(copro_dist)
Alias("copro_dist", copro_dist)
distenv.Alias("copro_dist", copro_dist)
firmware_flash = distenv.AddOpenOCDFlashTarget(firmware_env)
distenv.Alias("flash", firmware_flash)
firmware_bm_flash = distenv.PhonyTarget(
"flash_blackmagic",
"$GDB $GDBOPTS $SOURCES $GDBFLASH",
source=firmware_env["FW_ELF"],
GDBOPTS="${GDBOPTS_BASE} ${GDBOPTS_BLACKMAGIC}",
GDBREMOTE="${BLACKMAGIC_ADDR}",
GDBFLASH=[
"-ex",
"load",
"-ex",
"quit",
],
)
# Debugging firmware
firmware_debug = distenv.PhonyTarget(
"debug",
"${GDBPYCOM}",
source=firmware_env["FW_ELF"],
GDBOPTS="${GDBOPTS_BASE}",
GDBREMOTE="${OPENOCD_GDB_PIPE}",
)
distenv.Depends(firmware_debug, firmware_flash)
debug_fw_elf = distenv.AddDebugTarget(firmware_out)
Alias("debug", debug_fw_elf)
distenv.PhonyTarget(
"blackmagic",
"${GDBPYCOM}",
source=firmware_env["FW_ELF"],
GDBOPTS="${GDBOPTS_BASE} ${GDBOPTS_BLACKMAGIC}",
GDBREMOTE="${BLACKMAGIC_ADDR}",
)
# Debug alien elf
debug_other = distenv.GDBPy(
"debugother.pseudo",
None,
GDBPYOPTS=
# '-ex "source ${ROOT_DIR.abspath}/debug/FreeRTOS/FreeRTOS.py" '
'-ex "source debug/PyCortexMDebug/PyCortexMDebug.py" ',
distenv.PhonyTarget(
"debug_other",
"${GDBPYCOM}",
GDBPYOPTS='-ex "source debug/PyCortexMDebug/PyCortexMDebug.py" ',
GDBREMOTE="${OPENOCD_GDB_PIPE}",
)
distenv.Pseudo("debugother.pseudo")
AlwaysBuild(debug_other)
Alias("debug_other", debug_other)
# Just start OpenOCD
openocd = distenv.OOCDCommand("openocd.pseudo", [])
distenv.Pseudo("openocd.pseudo")
AlwaysBuild(openocd)
Alias("openocd", openocd)
distenv.PhonyTarget(
"openocd",
"${OPENOCDCOM}",
)
# Linter
lint_check = distenv.Command(
"lint.check.pseudo",
[],
"${PYTHON3} scripts/lint.py check $LINT_SOURCES",
LINT_SOURCES=firmware_out["LINT_SOURCES"],
distenv.PhonyTarget(
"lint",
"${PYTHON3} scripts/lint.py check ${LINT_SOURCES}",
LINT_SOURCES=firmware_env["LINT_SOURCES"],
)
distenv.PhonyTarget(
"format",
"${PYTHON3} scripts/lint.py format ${LINT_SOURCES}",
LINT_SOURCES=firmware_env["LINT_SOURCES"],
)
distenv.Pseudo("lint.check.pseudo")
AlwaysBuild(lint_check)
Alias("lint", lint_check)
lint_format = distenv.Command(
"lint.format.pseudo",
[],
"${PYTHON3} scripts/lint.py format $LINT_SOURCES",
LINT_SOURCES=firmware_out["LINT_SOURCES"],
# Find blackmagic probe
distenv.PhonyTarget(
"get_blackmagic",
"@echo $( ${BLACKMAGIC_ADDR} $)",
)
distenv.Pseudo("lint.format.pseudo")
AlwaysBuild(lint_format)
Alias("format", lint_format)

View File

@ -15,8 +15,9 @@ static void
int32_t load_offset = 0;
browser->is_root = is_root;
ArchiveTabEnum tab = archive_get_tab(browser);
if((item_cnt == 0) && (archive_is_home(browser))) {
if((item_cnt == 0) && (archive_is_home(browser)) && (tab != ArchiveTabBrowser)) {
archive_switch_tab(browser, browser->last_tab_switch_dir);
} else if(!string_start_with_str_p(browser->path, "/app:")) {
with_view_model(
@ -389,6 +390,22 @@ void archive_favorites_move_mode(ArchiveBrowserView* browser, bool active) {
});
}
static bool archive_is_dir_exists(string_t path) {
if(string_equal_str_p(path, "/any")) {
return true;
}
bool state = false;
FileInfo file_info;
Storage* storage = furi_record_open("storage");
if(storage_common_stat(storage, string_get_cstr(path), &file_info) == FSE_OK) {
if(file_info.flags & FSF_DIRECTORY) {
state = true;
}
}
furi_record_close("storage");
return state;
}
void archive_switch_tab(ArchiveBrowserView* browser, InputKey key) {
furi_assert(browser);
ArchiveTabEnum tab = archive_get_tab(browser);
@ -418,11 +435,15 @@ void archive_switch_tab(ArchiveBrowserView* browser, InputKey key) {
}
}
} else {
ArchiveTabEnum tab = archive_get_tab(browser);
tab = archive_get_tab(browser);
if(archive_is_dir_exists(browser->path)) {
bool skip_assets = (strcmp(archive_get_tab_ext(tab), "*") == 0) ? false : true;
file_browser_worker_set_config(
browser->worker, browser->path, archive_get_tab_ext(tab), skip_assets);
tab_empty = false; // Empty check will be performed later
} else {
tab_empty = true;
}
}
if((tab_empty) && (tab != ArchiveTabBrowser)) {

View File

@ -49,6 +49,7 @@ static const DuckyKey ducky_keys[] = {
{"CTRL-SHIFT", KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_SHIFT},
{"ALT-SHIFT", KEY_MOD_LEFT_ALT | KEY_MOD_LEFT_SHIFT},
{"ALT-GUI", KEY_MOD_LEFT_ALT | KEY_MOD_LEFT_GUI},
{"GUI-SHIFT", KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT},
{"CTRL", KEY_MOD_LEFT_CTRL},
{"CONTROL", KEY_MOD_LEFT_CTRL},
@ -57,48 +58,48 @@ static const DuckyKey ducky_keys[] = {
{"GUI", KEY_MOD_LEFT_GUI},
{"WINDOWS", KEY_MOD_LEFT_GUI},
{"DOWNARROW", KEY_DOWN_ARROW},
{"DOWN", KEY_DOWN_ARROW},
{"LEFTARROW", KEY_LEFT_ARROW},
{"LEFT", KEY_LEFT_ARROW},
{"RIGHTARROW", KEY_RIGHT_ARROW},
{"RIGHT", KEY_RIGHT_ARROW},
{"UPARROW", KEY_UP_ARROW},
{"UP", KEY_UP_ARROW},
{"DOWNARROW", HID_KEYBOARD_DOWN_ARROW},
{"DOWN", HID_KEYBOARD_DOWN_ARROW},
{"LEFTARROW", HID_KEYBOARD_LEFT_ARROW},
{"LEFT", HID_KEYBOARD_LEFT_ARROW},
{"RIGHTARROW", HID_KEYBOARD_RIGHT_ARROW},
{"RIGHT", HID_KEYBOARD_RIGHT_ARROW},
{"UPARROW", HID_KEYBOARD_UP_ARROW},
{"UP", HID_KEYBOARD_UP_ARROW},
{"ENTER", KEY_ENTER},
{"BREAK", KEY_PAUSE},
{"PAUSE", KEY_PAUSE},
{"CAPSLOCK", KEY_CAPS_LOCK},
{"DELETE", KEY_DELETE},
{"BACKSPACE", KEY_BACKSPACE},
{"END", KEY_END},
{"ESC", KEY_ESC},
{"ESCAPE", KEY_ESC},
{"HOME", KEY_HOME},
{"INSERT", KEY_INSERT},
{"NUMLOCK", KEY_NUM_LOCK},
{"PAGEUP", KEY_PAGE_UP},
{"PAGEDOWN", KEY_PAGE_DOWN},
{"PRINTSCREEN", KEY_PRINT},
{"SCROLLOCK", KEY_SCROLL_LOCK},
{"SPACE", KEY_SPACE},
{"TAB", KEY_TAB},
{"MENU", KEY_APPLICATION},
{"APP", KEY_APPLICATION},
{"ENTER", HID_KEYBOARD_RETURN},
{"BREAK", HID_KEYBOARD_PAUSE},
{"PAUSE", HID_KEYBOARD_PAUSE},
{"CAPSLOCK", HID_KEYBOARD_CAPS_LOCK},
{"DELETE", HID_KEYBOARD_DELETE},
{"BACKSPACE", HID_KEYPAD_BACKSPACE},
{"END", HID_KEYBOARD_END},
{"ESC", HID_KEYBOARD_ESCAPE},
{"ESCAPE", HID_KEYBOARD_ESCAPE},
{"HOME", HID_KEYBOARD_HOME},
{"INSERT", HID_KEYBOARD_INSERT},
{"NUMLOCK", HID_KEYPAD_NUMLOCK},
{"PAGEUP", HID_KEYBOARD_PAGE_UP},
{"PAGEDOWN", HID_KEYBOARD_PAGE_DOWN},
{"PRINTSCREEN", HID_KEYBOARD_PRINT_SCREEN},
{"SCROLLOCK", HID_KEYBOARD_SCROLL_LOCK},
{"SPACE", HID_KEYBOARD_SPACEBAR},
{"TAB", HID_KEYBOARD_TAB},
{"MENU", HID_KEYBOARD_APPLICATION},
{"APP", HID_KEYBOARD_APPLICATION},
{"F1", KEY_F1},
{"F2", KEY_F2},
{"F3", KEY_F3},
{"F4", KEY_F4},
{"F5", KEY_F5},
{"F6", KEY_F6},
{"F7", KEY_F7},
{"F8", KEY_F8},
{"F9", KEY_F9},
{"F10", KEY_F10},
{"F11", KEY_F11},
{"F12", KEY_F12},
{"F1", HID_KEYBOARD_F1},
{"F2", HID_KEYBOARD_F2},
{"F3", HID_KEYBOARD_F3},
{"F4", HID_KEYBOARD_F4},
{"F5", HID_KEYBOARD_F5},
{"F6", HID_KEYBOARD_F6},
{"F7", HID_KEYBOARD_F7},
{"F8", HID_KEYBOARD_F8},
{"F9", HID_KEYBOARD_F9},
{"F10", HID_KEYBOARD_F10},
{"F11", HID_KEYBOARD_F11},
{"F12", HID_KEYBOARD_F12},
};
static const char ducky_cmd_comment[] = {"REM"};
@ -114,16 +115,16 @@ static const char ducky_cmd_altstr_1[] = {"ALTSTRING "};
static const char ducky_cmd_altstr_2[] = {"ALTCODE "};
static const uint8_t numpad_keys[10] = {
KEYPAD_0,
KEYPAD_1,
KEYPAD_2,
KEYPAD_3,
KEYPAD_4,
KEYPAD_5,
KEYPAD_6,
KEYPAD_7,
KEYPAD_8,
KEYPAD_9,
HID_KEYPAD_0,
HID_KEYPAD_1,
HID_KEYPAD_2,
HID_KEYPAD_3,
HID_KEYPAD_4,
HID_KEYPAD_5,
HID_KEYPAD_6,
HID_KEYPAD_7,
HID_KEYPAD_8,
HID_KEYPAD_9,
};
static bool ducky_get_number(const char* param, uint32_t* val) {
@ -149,8 +150,8 @@ static bool ducky_is_line_end(const char chr) {
static void ducky_numlock_on() {
if((furi_hal_hid_get_led_state() & HID_KB_LED_NUM) == 0) {
furi_hal_hid_kb_press(KEY_NUM_LOCK);
furi_hal_hid_kb_release(KEY_NUM_LOCK);
furi_hal_hid_kb_press(HID_KEYBOARD_LOCK_NUM_LOCK);
furi_hal_hid_kb_release(HID_KEYBOARD_LOCK_NUM_LOCK);
}
}
@ -170,7 +171,7 @@ static bool ducky_altchar(const char* charcode) {
FURI_LOG_I(WORKER_TAG, "char %s", charcode);
furi_hal_hid_kb_press(KEY_MOD_LEFT_ALT);
furi_hal_hid_kb_press(HID_KEYBOARD_L_ALT);
while(!ducky_is_line_end(charcode[i])) {
state = ducky_numpad_press(charcode[i]);
@ -178,7 +179,7 @@ static bool ducky_altchar(const char* charcode) {
i++;
}
furi_hal_hid_kb_release(KEY_MOD_LEFT_ALT);
furi_hal_hid_kb_release(HID_KEYBOARD_L_ALT);
return state;
}
@ -206,7 +207,7 @@ static bool ducky_string(const char* param) {
uint32_t i = 0;
while(param[i] != '\0') {
uint16_t keycode = HID_ASCII_TO_KEY(param[i]);
if(keycode != KEY_NONE) {
if(keycode != HID_KEYBOARD_NONE) {
furi_hal_hid_kb_press(keycode);
furi_hal_hid_kb_release(keycode);
}
@ -294,7 +295,7 @@ static int32_t ducky_parse_line(BadUsbScript* bad_usb, string_t line) {
} else {
// Special keys + modifiers
uint16_t key = ducky_get_keycode(line_tmp, false);
if(key == KEY_NONE) return SCRIPT_STATE_ERROR;
if(key == HID_KEYBOARD_NONE) return SCRIPT_STATE_ERROR;
if((key & 0xFF00) != 0) {
// It's a modifier key
line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];

View File

@ -6,7 +6,9 @@
enum BtDebugSubmenuIndex {
BtHidSubmenuIndexKeynote,
BtHidSubmenuIndexKeyboard,
BtHidSubmenuIndexMedia,
BtHidSubmenuIndexMouse,
};
void bt_hid_submenu_callback(void* context, uint32_t index) {
@ -15,9 +17,15 @@ void bt_hid_submenu_callback(void* context, uint32_t index) {
if(index == BtHidSubmenuIndexKeynote) {
app->view_id = BtHidViewKeynote;
view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewKeynote);
} else if(index == BtHidSubmenuIndexKeyboard) {
app->view_id = BtHidViewKeyboard;
view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewKeyboard);
} else if(index == BtHidSubmenuIndexMedia) {
app->view_id = BtHidViewMedia;
view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewMedia);
} else if(index == BtHidSubmenuIndexMouse) {
app->view_id = BtHidViewMouse;
view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewMouse);
}
}
@ -25,10 +33,11 @@ void bt_hid_dialog_callback(DialogExResult result, void* context) {
furi_assert(context);
BtHid* app = context;
if(result == DialogExResultLeft) {
// TODO switch to Submenu after Media is done
view_dispatcher_stop(app->view_dispatcher);
} else if(result == DialogExResultRight) {
view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewKeynote);
view_dispatcher_switch_to_view(app->view_dispatcher, app->view_id); // Show last view
} else if(result == DialogExResultCenter) {
view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewSubmenu);
}
}
@ -52,7 +61,9 @@ void bt_hid_connection_status_changed_callback(BtStatus status, void* context) {
notification_internal_message(bt_hid->notifications, &sequence_reset_blue);
}
bt_hid_keynote_set_connected_status(bt_hid->bt_hid_keynote, connected);
bt_hid_keyboard_set_connected_status(bt_hid->bt_hid_keyboard, connected);
bt_hid_media_set_connected_status(bt_hid->bt_hid_media, connected);
bt_hid_mouse_set_connected_status(bt_hid->bt_hid_mouse, connected);
}
BtHid* bt_hid_app_alloc() {
@ -76,8 +87,11 @@ BtHid* bt_hid_app_alloc() {
app->submenu = submenu_alloc();
submenu_add_item(
app->submenu, "Keynote", BtHidSubmenuIndexKeynote, bt_hid_submenu_callback, app);
submenu_add_item(
app->submenu, "Keyboard", BtHidSubmenuIndexKeyboard, bt_hid_submenu_callback, app);
submenu_add_item(
app->submenu, "Media player", BtHidSubmenuIndexMedia, bt_hid_submenu_callback, app);
submenu_add_item(app->submenu, "Mouse", BtHidSubmenuIndexMouse, bt_hid_submenu_callback, app);
view_set_previous_callback(submenu_get_view(app->submenu), bt_hid_exit);
view_dispatcher_add_view(
app->view_dispatcher, BtHidViewSubmenu, submenu_get_view(app->submenu));
@ -88,6 +102,7 @@ BtHid* bt_hid_app_alloc() {
dialog_ex_set_context(app->dialog, app);
dialog_ex_set_left_button_text(app->dialog, "Exit");
dialog_ex_set_right_button_text(app->dialog, "Stay");
dialog_ex_set_center_button_text(app->dialog, "Menu");
dialog_ex_set_header(app->dialog, "Close current app?", 16, 12, AlignLeft, AlignTop);
view_dispatcher_add_view(
app->view_dispatcher, BtHidViewExitConfirm, dialog_ex_get_view(app->dialog));
@ -99,12 +114,25 @@ BtHid* bt_hid_app_alloc() {
view_dispatcher_add_view(
app->view_dispatcher, BtHidViewKeynote, bt_hid_keynote_get_view(app->bt_hid_keynote));
// Keyboard view
app->bt_hid_keyboard = bt_hid_keyboard_alloc();
view_set_previous_callback(
bt_hid_keyboard_get_view(app->bt_hid_keyboard), bt_hid_exit_confirm_view);
view_dispatcher_add_view(
app->view_dispatcher, BtHidViewKeyboard, bt_hid_keyboard_get_view(app->bt_hid_keyboard));
// Media view
app->bt_hid_media = bt_hid_media_alloc();
view_set_previous_callback(bt_hid_media_get_view(app->bt_hid_media), bt_hid_exit_confirm_view);
view_dispatcher_add_view(
app->view_dispatcher, BtHidViewMedia, bt_hid_media_get_view(app->bt_hid_media));
// Mouse view
app->bt_hid_mouse = bt_hid_mouse_alloc();
view_set_previous_callback(bt_hid_mouse_get_view(app->bt_hid_mouse), bt_hid_exit_confirm_view);
view_dispatcher_add_view(
app->view_dispatcher, BtHidViewMouse, bt_hid_mouse_get_view(app->bt_hid_mouse));
// TODO switch to menu after Media is done
view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewKeynote);
@ -124,8 +152,12 @@ void bt_hid_app_free(BtHid* app) {
dialog_ex_free(app->dialog);
view_dispatcher_remove_view(app->view_dispatcher, BtHidViewKeynote);
bt_hid_keynote_free(app->bt_hid_keynote);
view_dispatcher_remove_view(app->view_dispatcher, BtHidViewKeyboard);
bt_hid_keyboard_free(app->bt_hid_keyboard);
view_dispatcher_remove_view(app->view_dispatcher, BtHidViewMedia);
bt_hid_media_free(app->bt_hid_media);
view_dispatcher_remove_view(app->view_dispatcher, BtHidViewMouse);
bt_hid_mouse_free(app->bt_hid_mouse);
view_dispatcher_free(app->view_dispatcher);
// Close records

View File

@ -10,7 +10,9 @@
#include <gui/modules/submenu.h>
#include <gui/modules/dialog_ex.h>
#include "views/bt_hid_keynote.h"
#include "views/bt_hid_keyboard.h"
#include "views/bt_hid_media.h"
#include "views/bt_hid_mouse.h"
typedef struct {
Bt* bt;
@ -20,13 +22,17 @@ typedef struct {
Submenu* submenu;
DialogEx* dialog;
BtHidKeynote* bt_hid_keynote;
BtHidKeyboard* bt_hid_keyboard;
BtHidMedia* bt_hid_media;
BtHidMouse* bt_hid_mouse;
uint32_t view_id;
} BtHid;
typedef enum {
BtHidViewSubmenu,
BtHidViewKeynote,
BtHidViewKeyboard,
BtHidViewMedia,
BtHidViewMouse,
BtHidViewExitConfirm,
} BtHidView;

View File

@ -0,0 +1,384 @@
#include "bt_hid_keyboard.h"
#include <furi.h>
#include <furi_hal_bt_hid.h>
#include <furi_hal_usb_hid.h>
#include <gui/elements.h>
#include <gui/icon_i.h>
struct BtHidKeyboard {
View* view;
};
typedef struct {
bool shift;
bool alt;
bool ctrl;
bool gui;
uint8_t x;
uint8_t y;
uint8_t last_key_code;
uint16_t modifier_code;
bool ok_pressed;
bool back_pressed;
bool connected;
char key_string[5];
} BtHidKeyboardModel;
typedef struct {
uint8_t width;
char* key;
const Icon* icon;
char* shift_key;
uint8_t value;
} BtHidKeyboardKey;
typedef struct {
int8_t x;
int8_t y;
} BtHidKeyboardPoint;
// 4 BY 12
#define MARGIN_TOP 0
#define MARGIN_LEFT 4
#define KEY_WIDTH 9
#define KEY_HEIGHT 12
#define KEY_PADDING 1
#define ROW_COUNT 6
#define COLUMN_COUNT 12
// 0 width items are not drawn, but there value is used
const BtHidKeyboardKey bt_hid_keyboard_keyset[ROW_COUNT][COLUMN_COUNT] = {
{
{.width = 1, .icon = NULL, .key = "1", .shift_key = "!", .value = HID_KEYBOARD_1},
{.width = 1, .icon = NULL, .key = "2", .shift_key = "@", .value = HID_KEYBOARD_2},
{.width = 1, .icon = NULL, .key = "3", .shift_key = "#", .value = HID_KEYBOARD_3},
{.width = 1, .icon = NULL, .key = "4", .shift_key = "$", .value = HID_KEYBOARD_4},
{.width = 1, .icon = NULL, .key = "5", .shift_key = "%", .value = HID_KEYBOARD_5},
{.width = 1, .icon = NULL, .key = "6", .shift_key = "^", .value = HID_KEYBOARD_6},
{.width = 1, .icon = NULL, .key = "7", .shift_key = "&", .value = HID_KEYBOARD_7},
{.width = 1, .icon = NULL, .key = "8", .shift_key = "*", .value = HID_KEYBOARD_8},
{.width = 1, .icon = NULL, .key = "9", .shift_key = "(", .value = HID_KEYBOARD_9},
{.width = 1, .icon = NULL, .key = "0", .shift_key = ")", .value = HID_KEYBOARD_0},
{.width = 2, .icon = &I_Pin_arrow_left_9x7, .value = HID_KEYBOARD_DELETE},
{.width = 0, .value = HID_KEYBOARD_DELETE},
},
{
{.width = 1, .icon = NULL, .key = "q", .shift_key = "Q", .value = HID_KEYBOARD_Q},
{.width = 1, .icon = NULL, .key = "w", .shift_key = "W", .value = HID_KEYBOARD_W},
{.width = 1, .icon = NULL, .key = "e", .shift_key = "E", .value = HID_KEYBOARD_E},
{.width = 1, .icon = NULL, .key = "r", .shift_key = "R", .value = HID_KEYBOARD_R},
{.width = 1, .icon = NULL, .key = "t", .shift_key = "T", .value = HID_KEYBOARD_T},
{.width = 1, .icon = NULL, .key = "y", .shift_key = "Y", .value = HID_KEYBOARD_Y},
{.width = 1, .icon = NULL, .key = "u", .shift_key = "U", .value = HID_KEYBOARD_U},
{.width = 1, .icon = NULL, .key = "i", .shift_key = "I", .value = HID_KEYBOARD_I},
{.width = 1, .icon = NULL, .key = "o", .shift_key = "O", .value = HID_KEYBOARD_O},
{.width = 1, .icon = NULL, .key = "p", .shift_key = "P", .value = HID_KEYBOARD_P},
{.width = 1, .icon = NULL, .key = "[", .shift_key = "{", .value = HID_KEYBOARD_OPEN_BRACKET},
{.width = 1,
.icon = NULL,
.key = "]",
.shift_key = "}",
.value = HID_KEYBOARD_CLOSE_BRACKET},
},
{
{.width = 1, .icon = NULL, .key = "a", .shift_key = "A", .value = HID_KEYBOARD_A},
{.width = 1, .icon = NULL, .key = "s", .shift_key = "S", .value = HID_KEYBOARD_S},
{.width = 1, .icon = NULL, .key = "d", .shift_key = "D", .value = HID_KEYBOARD_D},
{.width = 1, .icon = NULL, .key = "f", .shift_key = "F", .value = HID_KEYBOARD_F},
{.width = 1, .icon = NULL, .key = "g", .shift_key = "G", .value = HID_KEYBOARD_G},
{.width = 1, .icon = NULL, .key = "h", .shift_key = "H", .value = HID_KEYBOARD_H},
{.width = 1, .icon = NULL, .key = "j", .shift_key = "J", .value = HID_KEYBOARD_J},
{.width = 1, .icon = NULL, .key = "k", .shift_key = "K", .value = HID_KEYBOARD_K},
{.width = 1, .icon = NULL, .key = "l", .shift_key = "L", .value = HID_KEYBOARD_L},
{.width = 1, .icon = NULL, .key = ";", .shift_key = ":", .value = HID_KEYBOARD_SEMICOLON},
{.width = 2, .icon = &I_Pin_arrow_right_9x7, .value = HID_KEYBOARD_RETURN},
{.width = 0, .value = HID_KEYBOARD_RETURN},
},
{
{.width = 1, .icon = NULL, .key = "z", .shift_key = "Z", .value = HID_KEYBOARD_Z},
{.width = 1, .icon = NULL, .key = "x", .shift_key = "X", .value = HID_KEYBOARD_X},
{.width = 1, .icon = NULL, .key = "c", .shift_key = "C", .value = HID_KEYBOARD_C},
{.width = 1, .icon = NULL, .key = "v", .shift_key = "V", .value = HID_KEYBOARD_V},
{.width = 1, .icon = NULL, .key = "b", .shift_key = "B", .value = HID_KEYBOARD_B},
{.width = 1, .icon = NULL, .key = "n", .shift_key = "N", .value = HID_KEYBOARD_N},
{.width = 1, .icon = NULL, .key = "m", .shift_key = "M", .value = HID_KEYBOARD_M},
{.width = 1, .icon = NULL, .key = "/", .shift_key = "?", .value = HID_KEYBOARD_SLASH},
{.width = 1, .icon = NULL, .key = "\\", .shift_key = "|", .value = HID_KEYBOARD_BACKSLASH},
{.width = 1, .icon = NULL, .key = "`", .shift_key = "~", .value = HID_KEYBOARD_GRAVE_ACCENT},
{.width = 1, .icon = &I_ButtonUp_7x4, .value = HID_KEYBOARD_UP_ARROW},
{.width = 1, .icon = NULL, .key = "-", .shift_key = "_", .value = HID_KEYBOARD_MINUS},
},
{
{.width = 1, .icon = &I_Pin_arrow_up7x9, .value = HID_KEYBOARD_L_SHIFT},
{.width = 1, .icon = NULL, .key = ",", .shift_key = "<", .value = HID_KEYPAD_COMMA},
{.width = 1, .icon = NULL, .key = ".", .shift_key = ">", .value = HID_KEYBOARD_DOT},
{.width = 4, .icon = NULL, .key = " ", .value = HID_KEYBOARD_SPACEBAR},
{.width = 0, .value = HID_KEYBOARD_SPACEBAR},
{.width = 0, .value = HID_KEYBOARD_SPACEBAR},
{.width = 0, .value = HID_KEYBOARD_SPACEBAR},
{.width = 1, .icon = NULL, .key = "'", .shift_key = "\"", .value = HID_KEYBOARD_APOSTROPHE},
{.width = 1, .icon = NULL, .key = "=", .shift_key = "+", .value = HID_KEYBOARD_EQUAL_SIGN},
{.width = 1, .icon = &I_ButtonLeft_4x7, .value = HID_KEYBOARD_LEFT_ARROW},
{.width = 1, .icon = &I_ButtonDown_7x4, .value = HID_KEYBOARD_DOWN_ARROW},
{.width = 1, .icon = &I_ButtonRight_4x7, .value = HID_KEYBOARD_RIGHT_ARROW},
},
{
{.width = 3, .icon = NULL, .key = "Ctrl", .value = HID_KEYBOARD_L_CTRL},
{.width = 0, .icon = NULL, .value = HID_KEYBOARD_L_CTRL},
{.width = 0, .icon = NULL, .value = HID_KEYBOARD_L_CTRL},
{.width = 3, .icon = NULL, .key = "Alt", .value = HID_KEYBOARD_L_ALT},
{.width = 0, .icon = NULL, .value = HID_KEYBOARD_L_ALT},
{.width = 0, .icon = NULL, .value = HID_KEYBOARD_L_ALT},
{.width = 3, .icon = NULL, .key = "Cmd", .value = HID_KEYBOARD_L_GUI},
{.width = 0, .icon = NULL, .value = HID_KEYBOARD_L_GUI},
{.width = 0, .icon = NULL, .value = HID_KEYBOARD_L_GUI},
{.width = 3, .icon = NULL, .key = "Tab", .value = HID_KEYBOARD_TAB},
{.width = 0, .icon = NULL, .value = HID_KEYBOARD_TAB},
{.width = 0, .icon = NULL, .value = HID_KEYBOARD_TAB},
},
};
static void bt_hid_keyboard_to_upper(char* str) {
while(*str) {
*str = toupper((unsigned char)*str);
str++;
}
}
static void bt_hid_keyboard_draw_key(
Canvas* canvas,
BtHidKeyboardModel* model,
uint8_t x,
uint8_t y,
BtHidKeyboardKey key,
bool selected) {
if(!key.width) return;
canvas_set_color(canvas, ColorBlack);
uint8_t keyWidth = KEY_WIDTH * key.width + KEY_PADDING * (key.width - 1);
if(selected) {
// Draw a filled box
elements_slightly_rounded_box(
canvas,
MARGIN_LEFT + x * (KEY_WIDTH + KEY_PADDING),
MARGIN_TOP + y * (KEY_HEIGHT + KEY_PADDING),
keyWidth,
KEY_HEIGHT);
canvas_set_color(canvas, ColorWhite);
} else {
// Draw a framed box
elements_slightly_rounded_frame(
canvas,
MARGIN_LEFT + x * (KEY_WIDTH + KEY_PADDING),
MARGIN_TOP + y * (KEY_HEIGHT + KEY_PADDING),
keyWidth,
KEY_HEIGHT);
}
if(key.icon != NULL) {
// Draw the icon centered on the button
canvas_draw_icon(
canvas,
MARGIN_LEFT + x * (KEY_WIDTH + KEY_PADDING) + keyWidth / 2 - key.icon->width / 2,
MARGIN_TOP + y * (KEY_HEIGHT + KEY_PADDING) + KEY_HEIGHT / 2 - key.icon->height / 2,
key.icon);
} else {
// If shift is toggled use the shift key when available
strcpy(model->key_string, (model->shift && key.shift_key != 0) ? key.shift_key : key.key);
// Upper case if ctrl or alt was toggled true
if((model->ctrl && key.value == HID_KEYBOARD_L_CTRL) ||
(model->alt && key.value == HID_KEYBOARD_L_ALT) ||
(model->gui && key.value == HID_KEYBOARD_L_GUI)) {
bt_hid_keyboard_to_upper(model->key_string);
}
canvas_draw_str_aligned(
canvas,
MARGIN_LEFT + x * (KEY_WIDTH + KEY_PADDING) + keyWidth / 2 + 1,
MARGIN_TOP + y * (KEY_HEIGHT + KEY_PADDING) + KEY_HEIGHT / 2,
AlignCenter,
AlignCenter,
model->key_string);
}
}
static void bt_hid_keyboard_draw_callback(Canvas* canvas, void* context) {
furi_assert(context);
BtHidKeyboardModel* model = context;
// Header
if(!model->connected) {
canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15);
canvas_set_font(canvas, FontPrimary);
elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Keyboard");
elements_multiline_text_aligned(
canvas, 4, 60, AlignLeft, AlignBottom, "Waiting for Connection...");
return; // Dont render the keyboard if we are not yet connected
}
canvas_set_font(canvas, FontKeyboard);
// Start shifting the all keys up if on the next row (Scrolling)
uint8_t initY = model->y - 4 > 0 ? model->y - 4 : 0;
for(uint8_t y = initY; y < ROW_COUNT; y++) {
const BtHidKeyboardKey* keyboardKeyRow = bt_hid_keyboard_keyset[y];
uint8_t x = 0;
for(uint8_t i = 0; i < COLUMN_COUNT; i++) {
BtHidKeyboardKey key = keyboardKeyRow[i];
// Select when the button is hovered
// Select if the button is hovered within its width
// Select if back is clicked and its the backspace key
// Deselect when the button clicked or not hovered
bool keySelected = (x <= model->x && model->x < (x + key.width)) && y == model->y;
bool backSelected = model->back_pressed && key.value == HID_KEYBOARD_DELETE;
bt_hid_keyboard_draw_key(
canvas,
model,
x,
y - initY,
key,
(!model->ok_pressed && keySelected) || backSelected);
x += key.width;
}
}
}
static uint8_t bt_hid_keyboard_get_selected_key(BtHidKeyboardModel* model) {
BtHidKeyboardKey key = bt_hid_keyboard_keyset[model->y][model->x];
// Use upper case if shift is toggled
bool useUppercase = model->shift;
// Check if the key has an upper case version
bool hasUppercase = key.shift_key != 0;
if(useUppercase && hasUppercase)
return key.value;
else
return key.value;
}
static void bt_hid_keyboard_get_select_key(BtHidKeyboardModel* model, BtHidKeyboardPoint delta) {
// Keep going until a valid spot is found, this allows for nulls and zero width keys in the map
do {
if(((int8_t)model->y) + delta.y < 0)
model->y = ROW_COUNT - 1;
else
model->y = (model->y + delta.y) % ROW_COUNT;
} while(delta.y != 0 && bt_hid_keyboard_keyset[model->y][model->x].value == 0);
do {
if(((int8_t)model->x) + delta.x < 0)
model->x = COLUMN_COUNT - 1;
else
model->x = (model->x + delta.x) % COLUMN_COUNT;
} while(delta.x != 0 && bt_hid_keyboard_keyset[model->y][model->x].width ==
0); // Skip zero width keys, pretend they are one key
}
static void bt_hid_keyboard_process(BtHidKeyboard* bt_hid_keyboard, InputEvent* event) {
with_view_model(
bt_hid_keyboard->view, (BtHidKeyboardModel * model) {
if(event->key == InputKeyOk) {
if(event->type == InputTypePress) {
model->ok_pressed = true;
} else if(event->type == InputTypeLong || event->type == InputTypeShort) {
model->last_key_code = bt_hid_keyboard_get_selected_key(model);
// Toggle the modifier key when clicked, and click the key
if(model->last_key_code == HID_KEYBOARD_L_SHIFT) {
model->shift = !model->shift;
if(model->shift)
model->modifier_code |= KEY_MOD_LEFT_SHIFT;
else
model->modifier_code &= ~KEY_MOD_LEFT_SHIFT;
} else if(model->last_key_code == HID_KEYBOARD_L_ALT) {
model->alt = !model->alt;
if(model->alt)
model->modifier_code |= KEY_MOD_LEFT_ALT;
else
model->modifier_code &= ~KEY_MOD_LEFT_ALT;
} else if(model->last_key_code == HID_KEYBOARD_L_CTRL) {
model->ctrl = !model->ctrl;
if(model->ctrl)
model->modifier_code |= KEY_MOD_LEFT_CTRL;
else
model->modifier_code &= ~KEY_MOD_LEFT_CTRL;
} else if(model->last_key_code == HID_KEYBOARD_L_GUI) {
model->gui = !model->gui;
if(model->gui)
model->modifier_code |= KEY_MOD_LEFT_GUI;
else
model->modifier_code &= ~KEY_MOD_LEFT_GUI;
}
furi_hal_bt_hid_kb_press(model->modifier_code | model->last_key_code);
} else if(event->type == InputTypeRelease) {
// Release happens after short and long presses
furi_hal_bt_hid_kb_release(model->modifier_code | model->last_key_code);
model->ok_pressed = false;
}
} else if(event->key == InputKeyBack) {
// If back is pressed for a short time, backspace
if(event->type == InputTypePress) {
model->back_pressed = true;
} else if(event->type == InputTypeShort) {
furi_hal_bt_hid_kb_press(HID_KEYBOARD_DELETE);
furi_hal_bt_hid_kb_release(HID_KEYBOARD_DELETE);
} else if(event->type == InputTypeRelease) {
model->back_pressed = false;
}
} else if(event->type == InputTypePress || event->type == InputTypeRepeat) {
// Cycle the selected keys
if(event->key == InputKeyUp) {
bt_hid_keyboard_get_select_key(model, (BtHidKeyboardPoint){.x = 0, .y = -1});
} else if(event->key == InputKeyDown) {
bt_hid_keyboard_get_select_key(model, (BtHidKeyboardPoint){.x = 0, .y = 1});
} else if(event->key == InputKeyLeft) {
bt_hid_keyboard_get_select_key(model, (BtHidKeyboardPoint){.x = -1, .y = 0});
} else if(event->key == InputKeyRight) {
bt_hid_keyboard_get_select_key(model, (BtHidKeyboardPoint){.x = 1, .y = 0});
}
}
return true;
});
}
static bool bt_hid_keyboard_input_callback(InputEvent* event, void* context) {
furi_assert(context);
BtHidKeyboard* bt_hid_keyboard = context;
bool consumed = false;
if(event->type == InputTypeLong && event->key == InputKeyBack) {
furi_hal_bt_hid_kb_release_all();
} else {
bt_hid_keyboard_process(bt_hid_keyboard, event);
consumed = true;
}
return consumed;
}
BtHidKeyboard* bt_hid_keyboard_alloc() {
BtHidKeyboard* bt_hid_keyboard = malloc(sizeof(BtHidKeyboard));
bt_hid_keyboard->view = view_alloc();
view_set_context(bt_hid_keyboard->view, bt_hid_keyboard);
view_allocate_model(bt_hid_keyboard->view, ViewModelTypeLocking, sizeof(BtHidKeyboardModel));
view_set_draw_callback(bt_hid_keyboard->view, bt_hid_keyboard_draw_callback);
view_set_input_callback(bt_hid_keyboard->view, bt_hid_keyboard_input_callback);
return bt_hid_keyboard;
}
void bt_hid_keyboard_free(BtHidKeyboard* bt_hid_keyboard) {
furi_assert(bt_hid_keyboard);
view_free(bt_hid_keyboard->view);
free(bt_hid_keyboard);
}
View* bt_hid_keyboard_get_view(BtHidKeyboard* bt_hid_keyboard) {
furi_assert(bt_hid_keyboard);
return bt_hid_keyboard->view;
}
void bt_hid_keyboard_set_connected_status(BtHidKeyboard* bt_hid_keyboard, bool connected) {
furi_assert(bt_hid_keyboard);
with_view_model(
bt_hid_keyboard->view, (BtHidKeyboardModel * model) {
model->connected = connected;
return true;
});
}

View File

@ -0,0 +1,13 @@
#pragma once
#include <gui/view.h>
typedef struct BtHidKeyboard BtHidKeyboard;
BtHidKeyboard* bt_hid_keyboard_alloc();
void bt_hid_keyboard_free(BtHidKeyboard* bt_hid_keyboard);
View* bt_hid_keyboard_get_view(BtHidKeyboard* bt_hid_keyboard);
void bt_hid_keyboard_set_connected_status(BtHidKeyboard* bt_hid_keyboard, bool connected);

View File

@ -14,6 +14,7 @@ typedef struct {
bool right_pressed;
bool down_pressed;
bool ok_pressed;
bool back_pressed;
bool connected;
} BtHidKeynoteModel;
@ -35,106 +36,119 @@ static void bt_hid_keynote_draw_callback(Canvas* canvas, void* context) {
BtHidKeynoteModel* model = context;
// Header
if(model->connected) {
canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15);
} else {
canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15);
}
canvas_set_font(canvas, FontPrimary);
elements_multiline_text_aligned(canvas, 9, 3, AlignLeft, AlignTop, "Keynote");
elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Keynote");
canvas_set_font(canvas, FontSecondary);
// Connected status
if(model->connected) {
canvas_draw_icon(canvas, 18, 18, &I_Ble_connected_38x34);
elements_multiline_text_aligned(canvas, 9, 60, AlignLeft, AlignBottom, "Connected");
} else {
canvas_draw_icon(canvas, 18, 18, &I_Ble_disconnected_24x34);
elements_multiline_text_aligned(canvas, 3, 60, AlignLeft, AlignBottom, "Disconnected");
}
// Up
canvas_draw_icon(canvas, 86, 4, &I_Button_18x18);
canvas_draw_icon(canvas, 21, 24, &I_Button_18x18);
if(model->up_pressed) {
elements_slightly_rounded_box(canvas, 89, 6, 13, 13);
elements_slightly_rounded_box(canvas, 24, 26, 13, 13);
canvas_set_color(canvas, ColorWhite);
}
bt_hid_keynote_draw_arrow(canvas, 95, 10, CanvasDirectionBottomToTop);
bt_hid_keynote_draw_arrow(canvas, 30, 30, CanvasDirectionBottomToTop);
canvas_set_color(canvas, ColorBlack);
// Down
canvas_draw_icon(canvas, 86, 25, &I_Button_18x18);
canvas_draw_icon(canvas, 21, 45, &I_Button_18x18);
if(model->down_pressed) {
elements_slightly_rounded_box(canvas, 89, 27, 13, 13);
elements_slightly_rounded_box(canvas, 24, 47, 13, 13);
canvas_set_color(canvas, ColorWhite);
}
bt_hid_keynote_draw_arrow(canvas, 95, 35, CanvasDirectionTopToBottom);
bt_hid_keynote_draw_arrow(canvas, 30, 55, CanvasDirectionTopToBottom);
canvas_set_color(canvas, ColorBlack);
// Left
canvas_draw_icon(canvas, 65, 25, &I_Button_18x18);
canvas_draw_icon(canvas, 0, 45, &I_Button_18x18);
if(model->left_pressed) {
elements_slightly_rounded_box(canvas, 68, 27, 13, 13);
elements_slightly_rounded_box(canvas, 3, 47, 13, 13);
canvas_set_color(canvas, ColorWhite);
}
bt_hid_keynote_draw_arrow(canvas, 72, 33, CanvasDirectionRightToLeft);
bt_hid_keynote_draw_arrow(canvas, 7, 53, CanvasDirectionRightToLeft);
canvas_set_color(canvas, ColorBlack);
// Right
canvas_draw_icon(canvas, 107, 25, &I_Button_18x18);
canvas_draw_icon(canvas, 42, 45, &I_Button_18x18);
if(model->right_pressed) {
elements_slightly_rounded_box(canvas, 110, 27, 13, 13);
elements_slightly_rounded_box(canvas, 45, 47, 13, 13);
canvas_set_color(canvas, ColorWhite);
}
bt_hid_keynote_draw_arrow(canvas, 118, 33, CanvasDirectionLeftToRight);
bt_hid_keynote_draw_arrow(canvas, 53, 53, CanvasDirectionLeftToRight);
canvas_set_color(canvas, ColorBlack);
// Ok
canvas_draw_icon(canvas, 63, 45, &I_Space_65x18);
canvas_draw_icon(canvas, 63, 25, &I_Space_65x18);
if(model->ok_pressed) {
elements_slightly_rounded_box(canvas, 66, 27, 60, 13);
canvas_set_color(canvas, ColorWhite);
}
canvas_draw_icon(canvas, 74, 29, &I_Ok_btn_9x9);
elements_multiline_text_aligned(canvas, 91, 36, AlignLeft, AlignBottom, "Space");
canvas_set_color(canvas, ColorBlack);
// Back
canvas_draw_icon(canvas, 63, 45, &I_Space_65x18);
if(model->back_pressed) {
elements_slightly_rounded_box(canvas, 66, 47, 60, 13);
canvas_set_color(canvas, ColorWhite);
}
canvas_draw_icon(canvas, 74, 49, &I_Ok_btn_9x9);
elements_multiline_text_aligned(canvas, 91, 56, AlignLeft, AlignBottom, "Space");
canvas_draw_icon(canvas, 110, 49, &I_Ok_btn_9x9);
elements_multiline_text_aligned(canvas, 76, 56, AlignLeft, AlignBottom, "Back");
}
static void bt_hid_keynote_process_press(BtHidKeynote* bt_hid_keynote, InputEvent* event) {
static void bt_hid_keynote_process(BtHidKeynote* bt_hid_keynote, InputEvent* event) {
with_view_model(
bt_hid_keynote->view, (BtHidKeynoteModel * model) {
if(event->type == InputTypePress) {
if(event->key == InputKeyUp) {
model->up_pressed = true;
furi_hal_bt_hid_kb_press(KEY_UP_ARROW);
furi_hal_bt_hid_kb_press(HID_KEYBOARD_UP_ARROW);
} else if(event->key == InputKeyDown) {
model->down_pressed = true;
furi_hal_bt_hid_kb_press(KEY_DOWN_ARROW);
furi_hal_bt_hid_kb_press(HID_KEYBOARD_DOWN_ARROW);
} else if(event->key == InputKeyLeft) {
model->left_pressed = true;
furi_hal_bt_hid_kb_press(KEY_LEFT_ARROW);
furi_hal_bt_hid_kb_press(HID_KEYBOARD_LEFT_ARROW);
} else if(event->key == InputKeyRight) {
model->right_pressed = true;
furi_hal_bt_hid_kb_press(KEY_RIGHT_ARROW);
furi_hal_bt_hid_kb_press(HID_KEYBOARD_RIGHT_ARROW);
} else if(event->key == InputKeyOk) {
model->ok_pressed = true;
furi_hal_bt_hid_kb_press(KEY_SPACE);
furi_hal_bt_hid_kb_press(HID_KEYBOARD_SPACEBAR);
} else if(event->key == InputKeyBack) {
model->back_pressed = true;
}
return true;
});
}
static void bt_hid_keynote_process_release(BtHidKeynote* bt_hid_keynote, InputEvent* event) {
with_view_model(
bt_hid_keynote->view, (BtHidKeynoteModel * model) {
} else if(event->type == InputTypeRelease) {
if(event->key == InputKeyUp) {
model->up_pressed = false;
furi_hal_bt_hid_kb_release(KEY_UP_ARROW);
furi_hal_bt_hid_kb_release(HID_KEYBOARD_UP_ARROW);
} else if(event->key == InputKeyDown) {
model->down_pressed = false;
furi_hal_bt_hid_kb_release(KEY_DOWN_ARROW);
furi_hal_bt_hid_kb_release(HID_KEYBOARD_DOWN_ARROW);
} else if(event->key == InputKeyLeft) {
model->left_pressed = false;
furi_hal_bt_hid_kb_release(KEY_LEFT_ARROW);
furi_hal_bt_hid_kb_release(HID_KEYBOARD_LEFT_ARROW);
} else if(event->key == InputKeyRight) {
model->right_pressed = false;
furi_hal_bt_hid_kb_release(KEY_RIGHT_ARROW);
furi_hal_bt_hid_kb_release(HID_KEYBOARD_RIGHT_ARROW);
} else if(event->key == InputKeyOk) {
model->ok_pressed = false;
furi_hal_bt_hid_kb_release(KEY_SPACE);
furi_hal_bt_hid_kb_release(HID_KEYBOARD_SPACEBAR);
} else if(event->key == InputKeyBack) {
model->back_pressed = false;
}
} else if(event->type == InputTypeShort) {
if(event->key == InputKeyBack) {
furi_hal_bt_hid_kb_press(HID_KEYBOARD_DELETE);
furi_hal_bt_hid_kb_release(HID_KEYBOARD_DELETE);
furi_hal_bt_hid_consumer_key_press(HID_CONSUMER_AC_BACK);
furi_hal_bt_hid_consumer_key_release(HID_CONSUMER_AC_BACK);
}
}
return true;
});
@ -145,16 +159,11 @@ static bool bt_hid_keynote_input_callback(InputEvent* event, void* context) {
BtHidKeynote* bt_hid_keynote = context;
bool consumed = false;
if(event->type == InputTypePress) {
bt_hid_keynote_process_press(bt_hid_keynote, event);
if(event->type == InputTypeLong && event->key == InputKeyBack) {
furi_hal_bt_hid_kb_release_all();
} else {
bt_hid_keynote_process(bt_hid_keynote, event);
consumed = true;
} else if(event->type == InputTypeRelease) {
bt_hid_keynote_process_release(bt_hid_keynote, event);
consumed = true;
} else if(event->type == InputTypeShort) {
if(event->key == InputKeyBack) {
furi_hal_hid_kb_release_all();
}
}
return consumed;

View File

@ -35,18 +35,14 @@ static void bt_hid_media_draw_callback(Canvas* canvas, void* context) {
BtHidMediaModel* model = context;
// Header
canvas_set_font(canvas, FontPrimary);
elements_multiline_text_aligned(canvas, 9, 3, AlignLeft, AlignTop, "Media player");
canvas_set_font(canvas, FontSecondary);
// Connected status
if(model->connected) {
canvas_draw_icon(canvas, 23, 17, &I_Ble_connected_38x34);
elements_multiline_text_aligned(canvas, 35, 61, AlignCenter, AlignBottom, "Connected");
canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15);
} else {
canvas_draw_icon(canvas, 23, 17, &I_Ble_disconnected_24x34);
elements_multiline_text_aligned(canvas, 35, 61, AlignCenter, AlignBottom, "Disconnected");
canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15);
}
canvas_set_font(canvas, FontPrimary);
elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Media");
canvas_set_font(canvas, FontSecondary);
// Keypad circles
canvas_draw_icon(canvas, 76, 8, &I_Circles_47x47);
@ -100,19 +96,19 @@ static void bt_hid_media_process_press(BtHidMedia* bt_hid_media, InputEvent* eve
bt_hid_media->view, (BtHidMediaModel * model) {
if(event->key == InputKeyUp) {
model->up_pressed = true;
furi_hal_bt_hid_media_press(FuriHalBtHidMediaVolumeUp);
furi_hal_bt_hid_consumer_key_press(HID_CONSUMER_VOLUME_INCREMENT);
} else if(event->key == InputKeyDown) {
model->down_pressed = true;
furi_hal_bt_hid_media_press(FuriHalBtHidMediaVolumeDown);
furi_hal_bt_hid_consumer_key_press(HID_CONSUMER_VOLUME_DECREMENT);
} else if(event->key == InputKeyLeft) {
model->left_pressed = true;
furi_hal_bt_hid_media_press(FuriHalBtHidMediaScanPrevious);
furi_hal_bt_hid_consumer_key_press(HID_CONSUMER_SCAN_PREVIOUS_TRACK);
} else if(event->key == InputKeyRight) {
model->right_pressed = true;
furi_hal_bt_hid_media_press(FuriHalBtHidMediaScanNext);
furi_hal_bt_hid_consumer_key_press(HID_CONSUMER_SCAN_NEXT_TRACK);
} else if(event->key == InputKeyOk) {
model->ok_pressed = true;
furi_hal_bt_hid_media_press(FuriHalBtHidMediaPlayPause);
furi_hal_bt_hid_consumer_key_press(HID_CONSUMER_PLAY_PAUSE);
}
return true;
});
@ -123,19 +119,19 @@ static void bt_hid_media_process_release(BtHidMedia* bt_hid_media, InputEvent* e
bt_hid_media->view, (BtHidMediaModel * model) {
if(event->key == InputKeyUp) {
model->up_pressed = false;
furi_hal_bt_hid_media_release(FuriHalBtHidMediaVolumeUp);
furi_hal_bt_hid_consumer_key_release(HID_CONSUMER_VOLUME_INCREMENT);
} else if(event->key == InputKeyDown) {
model->down_pressed = false;
furi_hal_bt_hid_media_release(FuriHalBtHidMediaVolumeDown);
furi_hal_bt_hid_consumer_key_release(HID_CONSUMER_VOLUME_DECREMENT);
} else if(event->key == InputKeyLeft) {
model->left_pressed = false;
furi_hal_bt_hid_media_release(FuriHalBtHidMediaScanPrevious);
furi_hal_bt_hid_consumer_key_release(HID_CONSUMER_SCAN_PREVIOUS_TRACK);
} else if(event->key == InputKeyRight) {
model->right_pressed = false;
furi_hal_bt_hid_media_release(FuriHalBtHidMediaScanNext);
furi_hal_bt_hid_consumer_key_release(HID_CONSUMER_SCAN_NEXT_TRACK);
} else if(event->key == InputKeyOk) {
model->ok_pressed = false;
furi_hal_bt_hid_media_release(FuriHalBtHidMediaPlayPause);
furi_hal_bt_hid_consumer_key_release(HID_CONSUMER_PLAY_PAUSE);
}
return true;
});
@ -154,7 +150,7 @@ static bool bt_hid_media_input_callback(InputEvent* event, void* context) {
consumed = true;
} else if(event->type == InputTypeShort) {
if(event->key == InputKeyBack) {
furi_hal_bt_hid_media_release_all();
furi_hal_bt_hid_consumer_key_release_all();
}
}

View File

@ -0,0 +1,207 @@
#include "bt_hid_mouse.h"
#include <furi.h>
#include <furi_hal_bt_hid.h>
#include <furi_hal_usb_hid.h>
#include <gui/elements.h>
struct BtHidMouse {
View* view;
};
#define MOUSE_MOVE_SHORT 5
#define MOUSE_MOVE_LONG 20
typedef struct {
bool left_pressed;
bool up_pressed;
bool right_pressed;
bool down_pressed;
bool left_mouse_pressed;
bool left_mouse_held;
bool right_mouse_pressed;
bool connected;
} BtHidMouseModel;
static void bt_hid_mouse_draw_callback(Canvas* canvas, void* context) {
furi_assert(context);
BtHidMouseModel* model = context;
// Header
if(model->connected) {
canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15);
} else {
canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15);
}
canvas_set_font(canvas, FontPrimary);
elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Mouse");
canvas_set_font(canvas, FontSecondary);
if(model->left_mouse_held == true) {
elements_multiline_text_aligned(canvas, 0, 60, AlignLeft, AlignBottom, "Selecting...");
}
// Keypad circles
canvas_draw_icon(canvas, 64, 8, &I_Circles_47x47);
// Up
if(model->up_pressed) {
canvas_draw_icon(canvas, 81, 9, &I_Pressed_Button_13x13);
canvas_set_color(canvas, ColorWhite);
}
canvas_draw_icon(canvas, 84, 10, &I_Pin_arrow_up7x9);
canvas_set_color(canvas, ColorBlack);
// Down
if(model->down_pressed) {
canvas_draw_icon(canvas, 81, 41, &I_Pressed_Button_13x13);
canvas_set_color(canvas, ColorWhite);
}
canvas_draw_icon(canvas, 84, 43, &I_Pin_arrow_down_7x9);
canvas_set_color(canvas, ColorBlack);
// Left
if(model->left_pressed) {
canvas_draw_icon(canvas, 65, 25, &I_Pressed_Button_13x13);
canvas_set_color(canvas, ColorWhite);
}
canvas_draw_icon(canvas, 67, 28, &I_Pin_arrow_left_9x7);
canvas_set_color(canvas, ColorBlack);
// Right
if(model->right_pressed) {
canvas_draw_icon(canvas, 97, 25, &I_Pressed_Button_13x13);
canvas_set_color(canvas, ColorWhite);
}
canvas_draw_icon(canvas, 99, 28, &I_Pin_arrow_right_9x7);
canvas_set_color(canvas, ColorBlack);
// Ok
if(model->left_mouse_pressed) {
canvas_draw_icon(canvas, 81, 25, &I_Pressed_Button_13x13);
canvas_set_color(canvas, ColorWhite);
}
canvas_draw_icon(canvas, 83, 27, &I_Ok_btn_9x9);
canvas_set_color(canvas, ColorBlack);
// Back
if(model->right_mouse_pressed) {
canvas_draw_icon(canvas, 108, 48, &I_Pressed_Button_13x13);
canvas_set_color(canvas, ColorWhite);
}
canvas_draw_icon(canvas, 110, 50, &I_Ok_btn_9x9);
}
static void bt_hid_mouse_process(BtHidMouse* bt_hid_mouse, InputEvent* event) {
with_view_model(
bt_hid_mouse->view, (BtHidMouseModel * model) {
if(event->key == InputKeyBack) {
if(event->type == InputTypeShort) {
furi_hal_bt_hid_mouse_press(HID_MOUSE_BTN_RIGHT);
furi_hal_bt_hid_mouse_release(HID_MOUSE_BTN_RIGHT);
} else if(event->type == InputTypePress) {
model->right_mouse_pressed = true;
} else if(event->type == InputTypeRelease) {
model->right_mouse_pressed = false;
}
} else if(event->key == InputKeyOk) {
if(event->type == InputTypeShort) {
// Just release if it was being held before
if(!model->left_mouse_held) furi_hal_bt_hid_mouse_press(HID_MOUSE_BTN_LEFT);
furi_hal_bt_hid_mouse_release(HID_MOUSE_BTN_LEFT);
model->left_mouse_held = false;
} else if(event->type == InputTypeLong) {
furi_hal_bt_hid_mouse_press(HID_MOUSE_BTN_LEFT);
model->left_mouse_held = true;
model->left_mouse_pressed = true;
} else if(event->type == InputTypePress) {
model->left_mouse_pressed = true;
} else if(event->type == InputTypeRelease) {
// Only release if it wasn't a long press
if(!model->left_mouse_held) model->left_mouse_pressed = false;
}
} else if(event->key == InputKeyRight) {
if(event->type == InputTypePress) {
model->right_pressed = true;
furi_hal_bt_hid_mouse_move(MOUSE_MOVE_SHORT, 0);
} else if(event->type == InputTypeRepeat) {
furi_hal_bt_hid_mouse_move(MOUSE_MOVE_LONG, 0);
} else if(event->type == InputTypeRelease) {
model->right_pressed = false;
}
} else if(event->key == InputKeyLeft) {
if(event->type == InputTypePress) {
model->left_pressed = true;
furi_hal_bt_hid_mouse_move(-MOUSE_MOVE_SHORT, 0);
} else if(event->type == InputTypeRepeat) {
furi_hal_bt_hid_mouse_move(-MOUSE_MOVE_LONG, 0);
} else if(event->type == InputTypeRelease) {
model->left_pressed = false;
}
} else if(event->key == InputKeyDown) {
if(event->type == InputTypePress) {
model->down_pressed = true;
furi_hal_bt_hid_mouse_move(0, MOUSE_MOVE_SHORT);
} else if(event->type == InputTypeRepeat) {
furi_hal_bt_hid_mouse_move(0, MOUSE_MOVE_LONG);
} else if(event->type == InputTypeRelease) {
model->down_pressed = false;
}
} else if(event->key == InputKeyUp) {
if(event->type == InputTypePress) {
model->up_pressed = true;
furi_hal_bt_hid_mouse_move(0, -MOUSE_MOVE_SHORT);
} else if(event->type == InputTypeRepeat) {
furi_hal_bt_hid_mouse_move(0, -MOUSE_MOVE_LONG);
} else if(event->type == InputTypeRelease) {
model->up_pressed = false;
}
}
return true;
});
}
static bool bt_hid_mouse_input_callback(InputEvent* event, void* context) {
furi_assert(context);
BtHidMouse* bt_hid_mouse = context;
bool consumed = false;
if(event->type == InputTypeLong && event->key == InputKeyBack) {
furi_hal_bt_hid_mouse_release_all();
} else {
bt_hid_mouse_process(bt_hid_mouse, event);
consumed = true;
}
return consumed;
}
BtHidMouse* bt_hid_mouse_alloc() {
BtHidMouse* bt_hid_mouse = malloc(sizeof(BtHidMouse));
bt_hid_mouse->view = view_alloc();
view_set_context(bt_hid_mouse->view, bt_hid_mouse);
view_allocate_model(bt_hid_mouse->view, ViewModelTypeLocking, sizeof(BtHidMouseModel));
view_set_draw_callback(bt_hid_mouse->view, bt_hid_mouse_draw_callback);
view_set_input_callback(bt_hid_mouse->view, bt_hid_mouse_input_callback);
return bt_hid_mouse;
}
void bt_hid_mouse_free(BtHidMouse* bt_hid_mouse) {
furi_assert(bt_hid_mouse);
view_free(bt_hid_mouse->view);
free(bt_hid_mouse);
}
View* bt_hid_mouse_get_view(BtHidMouse* bt_hid_mouse) {
furi_assert(bt_hid_mouse);
return bt_hid_mouse->view;
}
void bt_hid_mouse_set_connected_status(BtHidMouse* bt_hid_mouse, bool connected) {
furi_assert(bt_hid_mouse);
with_view_model(
bt_hid_mouse->view, (BtHidMouseModel * model) {
model->connected = connected;
return true;
});
}

View File

@ -0,0 +1,13 @@
#pragma once
#include <gui/view.h>
typedef struct BtHidMouse BtHidMouse;
BtHidMouse* bt_hid_mouse_alloc();
void bt_hid_mouse_free(BtHidMouse* bt_hid_mouse);
View* bt_hid_mouse_get_view(BtHidMouse* bt_hid_mouse);
void bt_hid_mouse_set_connected_status(BtHidMouse* bt_hid_mouse, bool connected);

View File

@ -229,18 +229,22 @@ static void cli_handle_enter(Cli* cli) {
// Search for command
furi_check(osMutexAcquire(cli->mutex, osWaitForever) == osOK);
CliCommand* cli_command = CliCommandTree_get(cli->commands, command);
if(cli_command) {
CliCommand* cli_command_ptr = CliCommandTree_get(cli->commands, command);
if(cli_command_ptr) {
CliCommand cli_command;
memcpy(&cli_command, cli_command_ptr, sizeof(CliCommand));
furi_check(osMutexRelease(cli->mutex) == osOK);
cli_nl(cli);
cli_execute_command(cli, cli_command, args);
cli_execute_command(cli, &cli_command, args);
} else {
furi_check(osMutexRelease(cli->mutex) == osOK);
cli_nl(cli);
printf(
"`%s` command not found, use `help` or `?` to list all available commands",
string_get_cstr(command));
cli_putc(cli, CliSymbolAsciiBell);
}
furi_check(osMutexRelease(cli->mutex) == osOK);
cli_reset(cli);
cli_prompt(cli);

View File

@ -199,6 +199,7 @@ static int32_t vcp_worker(void* context) {
furi_hal_cdc_set_callbacks(VCP_IF_NUM, NULL, NULL);
// Restore previous USB mode (if it was set during init)
if((vcp->usb_if_prev != &usb_cdc_single) && (vcp->usb_if_prev != &usb_cdc_dual)) {
furi_hal_usb_unlock();
furi_hal_usb_set_config(vcp->usb_if_prev, NULL);
}
xStreamBufferReceive(vcp->tx_stream, vcp->data_buffer, USB_CDC_PKT_LEN, 0);

View File

@ -12,6 +12,7 @@
struct Slideshow {
Icon icon;
uint32_t current_frame;
bool loaded;
};
#pragma pack(push, 1)
@ -34,6 +35,7 @@ _Static_assert(sizeof(SlideshowFrameHeader) == 2, "Incorrect SlideshowFrameHeade
Slideshow* slideshow_alloc() {
Slideshow* ret = malloc(sizeof(Slideshow));
ret->loaded = false;
return ret;
}
@ -52,7 +54,7 @@ void slideshow_free(Slideshow* slideshow) {
bool slideshow_load(Slideshow* slideshow, const char* fspath) {
Storage* storage = furi_record_open("storage");
File* slideshow_file = storage_file_alloc(storage);
bool load_success = false;
slideshow->loaded = false;
do {
if(!storage_file_open(slideshow_file, fspath, FSAM_READ, FSOM_OPEN_EXISTING)) {
break;
@ -80,12 +82,16 @@ bool slideshow_load(Slideshow* slideshow, const char* fspath) {
frame_header.size) {
break;
}
load_success = (frame_idx + 1) == header.frame_count;
slideshow->loaded = (frame_idx + 1) == header.frame_count;
}
} while(false);
storage_file_free(slideshow_file);
furi_record_close("storage");
return load_success;
return slideshow->loaded;
}
bool slideshow_is_loaded(Slideshow* slideshow) {
return slideshow->loaded;
}
bool slideshow_advance(Slideshow* slideshow) {

View File

@ -8,6 +8,7 @@ Slideshow* slideshow_alloc();
void slideshow_free(Slideshow* slideshow);
bool slideshow_load(Slideshow* slideshow, const char* fspath);
bool slideshow_is_loaded(Slideshow* slideshow);
void slideshow_goback(Slideshow* slideshow);
bool slideshow_advance(Slideshow* slideshow);
void slideshow_draw(Slideshow* slideshow, Canvas* canvas, uint8_t x, uint8_t y);

View File

@ -158,7 +158,7 @@ static bool desktop_view_locked_input(InputEvent* event, void* context) {
const bool pin_locked = model->pin_locked;
view_commit_model(locked_view->view, is_changed);
if(view_state == DesktopViewLockedStateUnlocked || event->type != InputTypeShort) {
if(view_state == DesktopViewLockedStateUnlocked) {
return view_state != DesktopViewLockedStateUnlocked;
} else if(view_state == DesktopViewLockedStateLocked && pin_locked) {
locked_view->callback(DesktopLockedEventShowPinInput, locked_view->context);
@ -173,11 +173,13 @@ static bool desktop_view_locked_input(InputEvent* event, void* context) {
desktop_view_locked_update_hint_icon_timeout(locked_view);
if(event->key == InputKeyBack) {
if(event->type == InputTypeShort) {
locked_view->lock_lastpress = press_time;
locked_view->lock_count++;
if(locked_view->lock_count == UNLOCK_CNT) {
locked_view->callback(DesktopLockedEventUnlocked, locked_view->context);
}
}
} else {
locked_view->lock_count = 0;
}

View File

@ -21,8 +21,10 @@ static void desktop_view_slideshow_draw(Canvas* canvas, void* model) {
DesktopSlideshowViewModel* m = model;
canvas_clear(canvas);
if(slideshow_is_loaded(m->slideshow)) {
slideshow_draw(m->slideshow, canvas, 0, 0);
}
}
static bool desktop_view_slideshow_input(InputEvent* event, void* context) {
furi_assert(event);

View File

@ -85,7 +85,6 @@ static void usb_uart_on_irq_cb(UartIrqEvent ev, uint8_t data, void* context) {
static void usb_uart_vcp_init(UsbUartBridge* usb_uart, uint8_t vcp_ch) {
furi_hal_usb_unlock();
FURI_LOG_I("", "Init %d", vcp_ch);
if(vcp_ch == 0) {
Cli* cli = furi_record_open("cli");
cli_session_close(cli);
@ -103,7 +102,6 @@ static void usb_uart_vcp_init(UsbUartBridge* usb_uart, uint8_t vcp_ch) {
static void usb_uart_vcp_deinit(UsbUartBridge* usb_uart, uint8_t vcp_ch) {
UNUSED(usb_uart);
furi_hal_cdc_set_callbacks(vcp_ch, NULL, NULL);
FURI_LOG_I("", "Deinit %d", vcp_ch);
if(vcp_ch != 0) {
Cli* cli = furi_record_open("cli");
cli_session_close(cli);

View File

@ -5,6 +5,9 @@
#include "m-string.h"
#include <toolbox/path.h>
#include <flipper_format/flipper_format.h>
#include "rpc/rpc_app.h"
#define TAG "iButtonApp"
static const NotificationSequence sequence_blink_start_cyan = {
&message_blink_start_10,
@ -55,7 +58,7 @@ static void ibutton_make_app_folder(iButton* ibutton) {
}
}
static bool ibutton_load_key_data(iButton* ibutton, string_t key_path) {
static bool ibutton_load_key_data(iButton* ibutton, string_t key_path, bool show_dialog) {
FlipperFormat* file = flipper_format_file_alloc(ibutton->storage);
bool result = false;
string_t data;
@ -89,13 +92,40 @@ static bool ibutton_load_key_data(iButton* ibutton, string_t key_path) {
flipper_format_free(file);
string_clear(data);
if(!result) {
if((!result) && (show_dialog)) {
dialog_message_show_storage_error(ibutton->dialogs, "Cannot load\nkey file");
}
return result;
}
static bool ibutton_rpc_command_callback(RpcAppSystemEvent event, const char* arg, void* context) {
furi_assert(context);
iButton* ibutton = context;
bool result = false;
if(event == RpcAppEventSessionClose) {
rpc_system_app_set_callback(ibutton->rpc_ctx, NULL, NULL);
ibutton->rpc_ctx = NULL;
view_dispatcher_send_custom_event(ibutton->view_dispatcher, iButtonCustomEventRpcExit);
result = true;
} else if(event == RpcAppEventAppExit) {
view_dispatcher_send_custom_event(ibutton->view_dispatcher, iButtonCustomEventRpcExit);
result = true;
} else if(event == RpcAppEventLoadFile) {
if(arg) {
string_set_str(ibutton->file_path, arg);
if(ibutton_load_key_data(ibutton, ibutton->file_path, false)) {
ibutton_worker_emulate_start(ibutton->key_worker, ibutton->key);
result = true;
}
}
}
return result;
}
bool ibutton_custom_event_callback(void* context, uint32_t event) {
furi_assert(context);
iButton* ibutton = context;
@ -226,7 +256,7 @@ bool ibutton_file_select(iButton* ibutton) {
true);
if(success) {
success = ibutton_load_key_data(ibutton, ibutton->file_path);
success = ibutton_load_key_data(ibutton, ibutton->file_path, true);
}
return success;
@ -334,16 +364,27 @@ int32_t ibutton_app(void* p) {
ibutton_make_app_folder(ibutton);
bool key_loaded = false;
bool rpc_mode = false;
if(p) {
uint32_t rpc_ctx = 0;
if(sscanf(p, "RPC %lX", &rpc_ctx) == 1) {
FURI_LOG_D(TAG, "Running in RPC mode");
ibutton->rpc_ctx = (void*)rpc_ctx;
rpc_mode = true;
rpc_system_app_set_callback(ibutton->rpc_ctx, ibutton_rpc_command_callback, ibutton);
} else {
string_set_str(ibutton->file_path, (const char*)p);
if(ibutton_load_key_data(ibutton, ibutton->file_path)) {
if(ibutton_load_key_data(ibutton, ibutton->file_path, true)) {
key_loaded = true;
// TODO: Display an error if the key from p could not be loaded
}
}
}
if(key_loaded) {
if(rpc_mode) {
scene_manager_next_scene(ibutton->scene_manager, iButtonSceneRpc);
} else if(key_loaded) {
scene_manager_next_scene(ibutton->scene_manager, iButtonSceneEmulate);
} else {
scene_manager_next_scene(ibutton->scene_manager, iButtonSceneStart);
@ -351,6 +392,9 @@ int32_t ibutton_app(void* p) {
view_dispatcher_run(ibutton->view_dispatcher);
if(ibutton->rpc_ctx) {
rpc_system_app_set_callback(ibutton->rpc_ctx, NULL, NULL);
}
ibutton_free(ibutton);
return 0;
}

View File

@ -9,4 +9,6 @@ enum iButtonCustomEvent {
iButtonCustomEventByteEditResult,
iButtonCustomEventWorkerEmulated,
iButtonCustomEventWorkerRead,
iButtonCustomEventRpcExit,
};

View File

@ -50,6 +50,8 @@ struct iButton {
Popup* popup;
Widget* widget;
DialogEx* dialog_ex;
void* rpc_ctx;
};
typedef enum {

View File

@ -18,3 +18,4 @@ ADD_SCENE(ibutton, delete_confirm, DeleteConfirm)
ADD_SCENE(ibutton, delete_success, DeleteSuccess)
ADD_SCENE(ibutton, retry_confirm, RetryConfirm)
ADD_SCENE(ibutton, exit_confirm, ExitConfirm)
ADD_SCENE(ibutton, rpc, Rpc)

View File

@ -3,6 +3,8 @@
#include <dolphin/dolphin.h>
#include <toolbox/path.h>
#define EMULATE_TIMEOUT_TICKS 10
static void ibutton_scene_emulate_callback(void* context, bool emulated) {
iButton* ibutton = context;
if(emulated) {
@ -95,12 +97,24 @@ bool ibutton_scene_emulate_on_event(void* context, SceneManagerEvent event) {
bool consumed = false;
if(event.type == SceneManagerEventTypeTick) {
uint32_t cnt = scene_manager_get_scene_state(ibutton->scene_manager, iButtonSceneEmulate);
if(cnt > 0) {
cnt--;
if(cnt == 0) {
ibutton_notification_message(ibutton, iButtonNotificationMessageEmulateBlink);
}
scene_manager_set_scene_state(ibutton->scene_manager, iButtonSceneEmulate, cnt);
}
consumed = true;
} else if(event.type == SceneManagerEventTypeCustom) {
consumed = true;
if(event.event == iButtonCustomEventWorkerEmulated) {
if(scene_manager_get_scene_state(ibutton->scene_manager, iButtonSceneEmulate) == 0) {
ibutton_notification_message(ibutton, iButtonNotificationMessageYellowBlink);
}
scene_manager_set_scene_state(
ibutton->scene_manager, iButtonSceneEmulate, EMULATE_TIMEOUT_TICKS);
}
}
return consumed;

View File

@ -0,0 +1,36 @@
#include "../ibutton_i.h"
#include <toolbox/path.h>
void ibutton_scene_rpc_on_enter(void* context) {
iButton* ibutton = context;
Widget* widget = ibutton->widget;
widget_add_text_box_element(
widget, 0, 0, 128, 28, AlignCenter, AlignCenter, "RPC mode", false);
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget);
notification_message(ibutton->notifications, &sequence_display_backlight_on);
}
bool ibutton_scene_rpc_on_event(void* context, SceneManagerEvent event) {
UNUSED(context);
UNUSED(event);
iButton* ibutton = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
consumed = true;
if(event.event == iButtonCustomEventRpcExit) {
view_dispatcher_stop(ibutton->view_dispatcher);
}
}
return consumed;
}
void ibutton_scene_rpc_on_exit(void* context) {
iButton* ibutton = context;
widget_reset(ibutton->widget);
}

View File

@ -36,6 +36,52 @@ static void infrared_tick_event_callback(void* context) {
scene_manager_handle_tick_event(infrared->scene_manager);
}
static bool
infrared_rpc_command_callback(RpcAppSystemEvent event, const char* arg, void* context) {
furi_assert(context);
Infrared* infrared = context;
if(!infrared->rpc_ctx) {
return false;
}
bool result = false;
if(event == RpcAppEventSessionClose) {
rpc_system_app_set_callback(infrared->rpc_ctx, NULL, NULL);
infrared->rpc_ctx = NULL;
view_dispatcher_send_custom_event(
infrared->view_dispatcher, InfraredCustomEventTypeBackPressed);
result = true;
} else if(event == RpcAppEventAppExit) {
view_dispatcher_send_custom_event(
infrared->view_dispatcher, InfraredCustomEventTypeBackPressed);
result = true;
} else if(event == RpcAppEventLoadFile) {
if(arg) {
string_set_str(infrared->file_path, arg);
result = infrared_remote_load(infrared->remote, infrared->file_path);
infrared_worker_tx_set_get_signal_callback(
infrared->worker, infrared_worker_tx_get_signal_steady_callback, infrared);
infrared_worker_tx_set_signal_sent_callback(
infrared->worker, infrared_signal_sent_callback, infrared);
}
} else if(event == RpcAppEventButtonPress) {
if(arg) {
size_t button_index = 0;
if(infrared_remote_find_button_by_name(infrared->remote, arg, &button_index)) {
infrared_tx_start_button_index(infrared, button_index);
result = true;
}
}
} else if(event == RpcAppEventButtonRelease) {
infrared_tx_stop(infrared);
result = true;
}
return result;
}
static void infrared_find_vacant_remote_name(string_t name, const char* path) {
Storage* storage = furi_record_open("storage");
@ -154,6 +200,11 @@ static void infrared_free(Infrared* infrared) {
ViewDispatcher* view_dispatcher = infrared->view_dispatcher;
InfraredAppState* app_state = &infrared->app_state;
if(infrared->rpc_ctx) {
rpc_system_app_set_callback(infrared->rpc_ctx, NULL, NULL);
infrared->rpc_ctx = NULL;
}
view_dispatcher_remove_view(view_dispatcher, InfraredViewSubmenu);
submenu_free(infrared->submenu);
@ -375,8 +426,16 @@ int32_t infrared_app(void* p) {
infrared_make_app_folder(infrared);
bool is_remote_loaded = false;
bool is_rpc_mode = false;
if(p) {
uint32_t rpc_ctx = 0;
if(sscanf(p, "RPC %lX", &rpc_ctx) == 1) {
infrared->rpc_ctx = (void*)rpc_ctx;
rpc_system_app_set_callback(
infrared->rpc_ctx, infrared_rpc_command_callback, infrared);
is_rpc_mode = true;
} else {
string_set_str(infrared->file_path, (const char*)p);
is_remote_loaded = infrared_remote_load(infrared->remote, infrared->file_path);
if(!is_remote_loaded) {
@ -385,8 +444,11 @@ int32_t infrared_app(void* p) {
return -1;
}
}
}
if(is_remote_loaded) {
if(is_rpc_mode) {
scene_manager_next_scene(infrared->scene_manager, InfraredSceneRpc);
} else if(is_remote_loaded) {
scene_manager_next_scene(infrared->scene_manager, InfraredSceneRemote);
} else {
scene_manager_next_scene(infrared->scene_manager, InfraredSceneStart);

View File

@ -30,6 +30,8 @@
#include "views/infrared_progress_view.h"
#include "views/infrared_debug_view.h"
#include "rpc/rpc_app.h"
#define INFRARED_FILE_NAME_SIZE 100
#define INFRARED_TEXT_STORE_NUM 2
#define INFRARED_TEXT_STORE_SIZE 128
@ -95,6 +97,8 @@ struct Infrared {
string_t file_path;
char text_store[INFRARED_TEXT_STORE_NUM][INFRARED_TEXT_STORE_SIZE + 1];
InfraredAppState app_state;
void* rpc_ctx;
};
typedef enum {

View File

@ -1,5 +1,7 @@
#include "infrared_remote.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdlib.h>
#include <m-string.h>
#include <m-array.h>
@ -73,6 +75,17 @@ InfraredRemoteButton* infrared_remote_get_button(InfraredRemote* remote, size_t
return *InfraredButtonArray_get(remote->buttons, index);
}
bool infrared_remote_find_button_by_name(InfraredRemote* remote, const char* name, size_t* index) {
for(size_t i = 0; i < InfraredButtonArray_size(remote->buttons); i++) {
InfraredRemoteButton* button = *InfraredButtonArray_get(remote->buttons, i);
if(!strcmp(infrared_remote_button_get_name(button), name)) {
*index = i;
return true;
}
}
return false;
}
bool infrared_remote_add_button(InfraredRemote* remote, const char* name, InfraredSignal* signal) {
InfraredRemoteButton* button = infrared_remote_button_alloc();
infrared_remote_button_set_name(button, name);

View File

@ -18,6 +18,7 @@ const char* infrared_remote_get_path(InfraredRemote* remote);
size_t infrared_remote_get_button_count(InfraredRemote* remote);
InfraredRemoteButton* infrared_remote_get_button(InfraredRemote* remote, size_t index);
bool infrared_remote_find_button_by_name(InfraredRemote* remote, const char* name, size_t* index);
bool infrared_remote_add_button(InfraredRemote* remote, const char* name, InfraredSignal* signal);
bool infrared_remote_rename_button(InfraredRemote* remote, const char* new_name, size_t index);

View File

@ -16,3 +16,4 @@ ADD_SCENE(infrared, universal, Universal)
ADD_SCENE(infrared, universal_tv, UniversalTV)
ADD_SCENE(infrared, debug, Debug)
ADD_SCENE(infrared, error_databases, ErrorDatabases)
ADD_SCENE(infrared, rpc, Rpc)

View File

@ -0,0 +1,37 @@
#include "../infrared_i.h"
#include "gui/canvas.h"
void infrared_scene_rpc_on_enter(void* context) {
Infrared* infrared = context;
Popup* popup = infrared->popup;
popup_set_text(popup, "Rpc mode", 64, 28, AlignCenter, AlignCenter);
popup_set_context(popup, context);
popup_set_callback(popup, infrared_popup_closed_callback);
infrared_play_notification_message(infrared, InfraredNotificationMessageYellowOn);
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewPopup);
notification_message(infrared->notifications, &sequence_display_backlight_on);
}
bool infrared_scene_rpc_on_event(void* context, SceneManagerEvent event) {
Infrared* infrared = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
consumed = true;
if(event.event == InfraredCustomEventTypeBackPressed) {
view_dispatcher_stop(infrared->view_dispatcher);
} else if(event.event == InfraredCustomEventTypePopupClosed) {
view_dispatcher_stop(infrared->view_dispatcher);
}
}
return consumed;
}
void infrared_scene_rpc_on_exit(void* context) {
Infrared* infrared = context;
popup_reset(infrared->popup);
}

View File

@ -20,10 +20,13 @@
#include "scene/lfrfid_app_scene_saved_info.h"
#include "scene/lfrfid_app_scene_delete_confirm.h"
#include "scene/lfrfid_app_scene_delete_success.h"
#include "scene/lfrfid_app_scene_rpc.h"
#include <toolbox/path.h>
#include <flipper_format/flipper_format.h>
#include "rpc/rpc_app.h"
const char* LfRfidApp::app_folder = "/any/lfrfid";
const char* LfRfidApp::app_extension = ".rfid";
const char* LfRfidApp::app_filetype = "Flipper RFID key";
@ -39,6 +42,43 @@ LfRfidApp::LfRfidApp()
LfRfidApp::~LfRfidApp() {
string_clear(file_path);
if(rpc_ctx) {
rpc_system_app_set_callback(rpc_ctx, NULL, NULL);
}
}
static bool rpc_command_callback(RpcAppSystemEvent event, const char* arg, void* context) {
furi_assert(context);
LfRfidApp* app = static_cast<LfRfidApp*>(context);
bool result = false;
if(event == RpcAppEventSessionClose) {
rpc_system_app_set_callback(app->rpc_ctx, NULL, NULL);
app->rpc_ctx = NULL;
LfRfidApp::Event event;
event.type = LfRfidApp::EventType::Exit;
app->view_controller.send_event(&event);
result = true;
} else if(event == RpcAppEventAppExit) {
LfRfidApp::Event event;
event.type = LfRfidApp::EventType::Exit;
app->view_controller.send_event(&event);
result = true;
} else if(event == RpcAppEventLoadFile) {
if(arg) {
string_set_str(app->file_path, arg);
if(app->load_key_data(app->file_path, &(app->worker.key), false)) {
LfRfidApp::Event event;
event.type = LfRfidApp::EventType::EmulateStart;
app->view_controller.send_event(&event);
app->worker.start_emulate();
result = true;
}
}
}
return result;
}
void LfRfidApp::run(void* _args) {
@ -47,10 +87,19 @@ void LfRfidApp::run(void* _args) {
make_app_folder();
if(strlen(args)) {
uint32_t rpc_ctx_ptr = 0;
if(sscanf(args, "RPC %lX", &rpc_ctx_ptr) == 1) {
rpc_ctx = (RpcAppSystem*)rpc_ctx_ptr;
rpc_system_app_set_callback(rpc_ctx, rpc_command_callback, this);
scene_controller.add_scene(SceneType::Rpc, new LfRfidAppSceneRpc());
scene_controller.process(100, SceneType::Rpc);
} else {
string_set_str(file_path, args);
load_key_data(file_path, &worker.key);
load_key_data(file_path, &worker.key, true);
scene_controller.add_scene(SceneType::Emulate, new LfRfidAppSceneEmulate());
scene_controller.process(100, SceneType::Emulate);
}
} else {
scene_controller.add_scene(SceneType::Start, new LfRfidAppSceneStart());
scene_controller.add_scene(SceneType::Read, new LfRfidAppSceneRead());
@ -99,7 +148,7 @@ bool LfRfidApp::load_key_from_file_select(bool need_restore) {
dialogs, file_path, file_path, app_extension, true, &I_125_10px, true);
if(result) {
result = load_key_data(file_path, &worker.key);
result = load_key_data(file_path, &worker.key, true);
}
return result;
@ -110,7 +159,7 @@ bool LfRfidApp::delete_key(RfidKey* key) {
return storage_simply_remove(storage, string_get_cstr(file_path));
}
bool LfRfidApp::load_key_data(string_t path, RfidKey* key) {
bool LfRfidApp::load_key_data(string_t path, RfidKey* key, bool show_dialog) {
FlipperFormat* file = flipper_format_file_alloc(storage);
bool result = false;
string_t str_result;
@ -149,7 +198,7 @@ bool LfRfidApp::load_key_data(string_t path, RfidKey* key) {
flipper_format_free(file);
string_clear(str_result);
if(!result) {
if((!result) && (show_dialog)) {
dialog_message_show_storage_error(dialogs, "Cannot load\nkey file");
}

View File

@ -21,6 +21,7 @@
#include <dialogs/dialogs.h>
#include "helpers/rfid_worker.h"
#include "rpc/rpc_app.h"
class LfRfidApp {
public:
@ -30,6 +31,8 @@ public:
MenuSelected,
Stay,
Retry,
Exit,
EmulateStart,
};
enum class SceneType : uint8_t {
@ -51,6 +54,7 @@ public:
SavedInfo,
DeleteConfirm,
DeleteSuccess,
Rpc,
};
class Event {
@ -79,6 +83,8 @@ public:
string_t file_path;
RpcAppSystem* rpc_ctx;
void run(void* args);
static const char* app_folder;
@ -89,8 +95,9 @@ public:
bool load_key_from_file_select(bool need_restore);
bool delete_key(RfidKey* key);
bool load_key_data(string_t path, RfidKey* key);
bool load_key_data(string_t path, RfidKey* key, bool show_dialog);
bool save_key_data(string_t path, RfidKey* key);
void make_app_folder();
//bool rpc_command_callback(RpcAppSystemEvent event, const char* arg, void* context);
};

View File

@ -0,0 +1,37 @@
#include "lfrfid_app_scene_rpc.h"
#include "furi/common_defines.h"
#include <dolphin/dolphin.h>
void LfRfidAppSceneRpc::on_enter(LfRfidApp* app, bool /* need_restore */) {
auto popup = app->view_controller.get<PopupVM>();
popup->set_header("RPC Mode", 64, 30, AlignCenter, AlignTop);
app->view_controller.switch_to<PopupVM>();
notification_message(app->notification, &sequence_display_backlight_on);
}
bool LfRfidAppSceneRpc::on_event(LfRfidApp* app, LfRfidApp::Event* event) {
UNUSED(app);
UNUSED(event);
bool consumed = false;
if(event->type == LfRfidApp::EventType::Exit) {
consumed = true;
LfRfidApp::Event view_event;
view_event.type = LfRfidApp::EventType::Back;
app->view_controller.send_event(&view_event);
} else if(event->type == LfRfidApp::EventType::EmulateStart) {
consumed = true;
emulating = true;
}
return consumed;
}
void LfRfidAppSceneRpc::on_exit(LfRfidApp* app) {
if(emulating) {
app->worker.stop_emulate();
}
app->view_controller.get<PopupVM>()->clean();
}

View File

@ -0,0 +1,12 @@
#pragma once
#include "../lfrfid_app.h"
class LfRfidAppSceneRpc : public GenericScene<LfRfidApp> {
public:
void on_enter(LfRfidApp* app, bool need_restore) final;
bool on_event(LfRfidApp* app, LfRfidApp::Event* event) final;
void on_exit(LfRfidApp* app) final;
private:
bool emulating = false;
};

View File

@ -0,0 +1,327 @@
#include <furi_hal_random.h>
#include "nfc_generators.h"
#define NXP_MANUFACTURER_ID (0x04)
static const uint8_t version_bytes_mf0ulx1[] = {0x00, 0x04, 0x03, 0x00, 0x01, 0x00, 0x00, 0x03};
static const uint8_t version_bytes_ntag21x[] = {0x00, 0x04, 0x04, 0x02, 0x01, 0x00, 0x00, 0x03};
static const uint8_t version_bytes_ntag_i2c[] = {0x00, 0x04, 0x04, 0x05, 0x02, 0x00, 0x00, 0x03};
static const uint8_t default_data_ntag213[] = {0x01, 0x03, 0xA0, 0x0C, 0x34, 0x03, 0x00, 0xFE};
static const uint8_t default_data_ntag215_216[] = {0x03, 0x00, 0xFE};
static const uint8_t default_data_ntag_i2c[] = {0xE1, 0x10, 0x00, 0x00, 0x03, 0x00, 0xFE};
static const uint8_t default_config_ntag_i2c[] = {0x01, 0x00, 0xF8, 0x48, 0x08, 0x01, 0x00, 0x00};
static void nfc_generate_common_start(NfcDeviceData* data) {
nfc_device_data_clear(data);
}
static void nfc_generate_mf_ul_uid(uint8_t* uid) {
uid[0] = NXP_MANUFACTURER_ID;
furi_hal_random_fill_buf(&uid[1], 6);
// I'm not sure how this is generated, but the upper nybble always seems to be 8
uid[6] &= 0x0F;
uid[6] |= 0x80;
}
static void nfc_generate_mf_ul_common(NfcDeviceData* data) {
data->nfc_data.type = FuriHalNfcTypeA;
data->nfc_data.interface = FuriHalNfcInterfaceRf;
data->nfc_data.uid_len = 7;
nfc_generate_mf_ul_uid(data->nfc_data.uid);
data->nfc_data.atqa[0] = 0x44;
data->nfc_data.atqa[1] = 0x00;
data->nfc_data.sak = 0x00;
data->protocol = NfcDeviceProtocolMifareUl;
}
static void nfc_generate_calc_bcc(uint8_t* uid, uint8_t* bcc0, uint8_t* bcc1) {
*bcc0 = 0x88 ^ uid[0] ^ uid[1] ^ uid[2];
*bcc1 = uid[3] ^ uid[4] ^ uid[5] ^ uid[6];
}
static void nfc_generate_mf_ul_copy_uid_with_bcc(NfcDeviceData* data) {
MfUltralightData* mful = &data->mf_ul_data;
memcpy(mful->data, data->nfc_data.uid, 3);
memcpy(&mful->data[4], &data->nfc_data.uid[3], 4);
nfc_generate_calc_bcc(data->nfc_data.uid, &mful->data[3], &mful->data[8]);
}
static void nfc_generate_mf_ul_orig(NfcDeviceData* data) {
nfc_generate_common_start(data);
nfc_generate_mf_ul_common(data);
MfUltralightData* mful = &data->mf_ul_data;
mful->type = MfUltralightTypeUnknown;
mful->data_size = 16 * 4;
nfc_generate_mf_ul_copy_uid_with_bcc(data);
// TODO: what's internal byte on page 2?
memset(&mful->data[4 * 4], 0xFF, 4);
}
static void nfc_generate_mf_ul_with_config_common(NfcDeviceData* data, uint8_t num_pages) {
nfc_generate_common_start(data);
nfc_generate_mf_ul_common(data);
MfUltralightData* mful = &data->mf_ul_data;
mful->data_size = num_pages * 4;
nfc_generate_mf_ul_copy_uid_with_bcc(data);
uint16_t config_index = (num_pages - 4) * 4;
mful->data[config_index] = 0x04; // STRG_MOD_EN
mful->data[config_index + 3] = 0xFF; // AUTH0
mful->data[config_index + 5] = 0x05; // VCTID
memset(&mful->data[config_index + 8], 0xFF, 4); // Default PWD
if(num_pages > 20) mful->data[config_index - 1] = MF_UL_TEARING_FLAG_DEFAULT;
}
static void nfc_generate_mf_ul_ev1_common(NfcDeviceData* data, uint8_t num_pages) {
nfc_generate_mf_ul_with_config_common(data, num_pages);
MfUltralightData* mful = &data->mf_ul_data;
memcpy(&mful->version, version_bytes_mf0ulx1, sizeof(version_bytes_mf0ulx1));
for(size_t i = 0; i < 3; ++i) {
mful->tearing[i] = MF_UL_TEARING_FLAG_DEFAULT;
}
// TODO: what's internal byte on page 2?
}
static void nfc_generate_mf_ul_11(NfcDeviceData* data) {
nfc_generate_mf_ul_ev1_common(data, 20);
MfUltralightData* mful = &data->mf_ul_data;
mful->type = MfUltralightTypeUL11;
mful->version.prod_subtype = 0x01;
mful->version.storage_size = 0x0B;
mful->data[16 * 4] = 0x00; // Low capacitance version does not have STRG_MOD_EN
}
static void nfc_generate_mf_ul_h11(NfcDeviceData* data) {
nfc_generate_mf_ul_ev1_common(data, 20);
MfUltralightData* mful = &data->mf_ul_data;
mful->type = MfUltralightTypeUL11;
mful->version.prod_subtype = 0x02;
mful->version.storage_size = 0x0B;
}
static void nfc_generate_mf_ul_21(NfcDeviceData* data) {
nfc_generate_mf_ul_ev1_common(data, 41);
MfUltralightData* mful = &data->mf_ul_data;
mful->type = MfUltralightTypeUL21;
mful->version.prod_subtype = 0x01;
mful->version.storage_size = 0x0E;
mful->data[37 * 4] = 0x00; // Low capacitance version does not have STRG_MOD_EN
}
static void nfc_generate_mf_ul_h21(NfcDeviceData* data) {
nfc_generate_mf_ul_ev1_common(data, 41);
MfUltralightData* mful = &data->mf_ul_data;
mful->type = MfUltralightTypeUL21;
mful->version.prod_subtype = 0x02;
mful->version.storage_size = 0x0E;
}
static void nfc_generate_ntag21x_common(NfcDeviceData* data, uint8_t num_pages) {
nfc_generate_mf_ul_with_config_common(data, num_pages);
MfUltralightData* mful = &data->mf_ul_data;
memcpy(&mful->version, version_bytes_ntag21x, sizeof(version_bytes_mf0ulx1));
mful->data[9] = 0x48; // Internal byte
// Capability container
mful->data[12] = 0xE1;
mful->data[13] = 0x10;
}
static void nfc_generate_ntag213(NfcDeviceData* data) {
nfc_generate_ntag21x_common(data, 45);
MfUltralightData* mful = &data->mf_ul_data;
mful->type = MfUltralightTypeNTAG213;
mful->version.storage_size = 0x0F;
mful->data[14] = 0x12;
// Default contents
memcpy(&mful->data[16], default_data_ntag213, sizeof(default_data_ntag213));
}
static void nfc_generate_ntag215(NfcDeviceData* data) {
nfc_generate_ntag21x_common(data, 135);
MfUltralightData* mful = &data->mf_ul_data;
mful->type = MfUltralightTypeNTAG215;
mful->version.storage_size = 0x11;
mful->data[14] = 0x3E;
// Default contents
memcpy(&mful->data[16], default_data_ntag215_216, sizeof(default_data_ntag215_216));
}
static void nfc_generate_ntag216(NfcDeviceData* data) {
nfc_generate_ntag21x_common(data, 231);
MfUltralightData* mful = &data->mf_ul_data;
mful->type = MfUltralightTypeNTAG216;
mful->version.storage_size = 0x13;
mful->data[14] = 0x6D;
// Default contents
memcpy(&mful->data[16], default_data_ntag215_216, sizeof(default_data_ntag215_216));
}
static void
nfc_generate_ntag_i2c_common(NfcDeviceData* data, MfUltralightType type, uint16_t num_pages) {
nfc_generate_common_start(data);
nfc_generate_mf_ul_common(data);
MfUltralightData* mful = &data->mf_ul_data;
mful->type = type;
memcpy(&mful->version, version_bytes_ntag_i2c, sizeof(version_bytes_ntag_i2c));
mful->data_size = num_pages * 4;
memcpy(mful->data, data->nfc_data.uid, data->nfc_data.uid_len);
mful->data[7] = data->nfc_data.sak;
mful->data[8] = data->nfc_data.atqa[0];
mful->data[9] = data->nfc_data.atqa[1];
uint16_t config_register_page;
uint16_t session_register_page;
// Sync with mifare_ultralight.c
switch(type) {
case MfUltralightTypeNTAGI2C1K:
config_register_page = 227;
session_register_page = 229;
break;
case MfUltralightTypeNTAGI2C2K:
config_register_page = 481;
session_register_page = 483;
break;
case MfUltralightTypeNTAGI2CPlus1K:
case MfUltralightTypeNTAGI2CPlus2K:
config_register_page = 232;
session_register_page = 234;
break;
default:
furi_assert(false);
break;
}
memcpy(
&mful->data[config_register_page * 4],
default_config_ntag_i2c,
sizeof(default_config_ntag_i2c));
memcpy(
&mful->data[session_register_page * 4],
default_config_ntag_i2c,
sizeof(default_config_ntag_i2c));
}
static void nfc_generate_ntag_i2c_1k(NfcDeviceData* data) {
nfc_generate_ntag_i2c_common(data, MfUltralightTypeNTAGI2C1K, 231);
MfUltralightData* mful = &data->mf_ul_data;
mful->version.prod_ver_minor = 0x01;
mful->version.storage_size = 0x13;
memcpy(&mful->data[12], default_data_ntag_i2c, sizeof(default_data_ntag_i2c));
mful->data[14] = 0x6D; // Size of tag in CC
}
static void nfc_generate_ntag_i2c_2k(NfcDeviceData* data) {
nfc_generate_ntag_i2c_common(data, MfUltralightTypeNTAGI2C2K, 485);
MfUltralightData* mful = &data->mf_ul_data;
mful->version.prod_ver_minor = 0x01;
mful->version.storage_size = 0x15;
memcpy(&mful->data[12], default_data_ntag_i2c, sizeof(default_data_ntag_i2c));
mful->data[14] = 0xEA; // Size of tag in CC
}
static void nfc_generate_ntag_i2c_plus_common(
NfcDeviceData* data,
MfUltralightType type,
uint16_t num_pages) {
nfc_generate_ntag_i2c_common(data, type, num_pages);
MfUltralightData* mful = &data->mf_ul_data;
uint16_t config_index = 227 * 4;
mful->data[config_index + 3] = 0xFF; // AUTH0
memset(&mful->data[config_index + 8], 0xFF, 4); // Default PWD
}
static void nfc_generate_ntag_i2c_plus_1k(NfcDeviceData* data) {
nfc_generate_ntag_i2c_plus_common(data, MfUltralightTypeNTAGI2CPlus1K, 236);
MfUltralightData* mful = &data->mf_ul_data;
mful->version.prod_ver_minor = 0x02;
mful->version.storage_size = 0x13;
}
static void nfc_generate_ntag_i2c_plus_2k(NfcDeviceData* data) {
nfc_generate_ntag_i2c_plus_common(data, MfUltralightTypeNTAGI2CPlus2K, 492);
MfUltralightData* mful = &data->mf_ul_data;
mful->version.prod_ver_minor = 0x02;
mful->version.storage_size = 0x15;
}
static const NfcGenerator mf_ul_generator = {
.name = "Mifare Ultralight",
.generator_func = nfc_generate_mf_ul_orig,
.next_scene = NfcSceneMifareUlMenu};
static const NfcGenerator mf_ul_11_generator = {
.name = "Mifare Ultralight EV1 11",
.generator_func = nfc_generate_mf_ul_11,
.next_scene = NfcSceneMifareUlMenu};
static const NfcGenerator mf_ul_h11_generator = {
.name = "Mifare Ultralight EV1 H11",
.generator_func = nfc_generate_mf_ul_h11,
.next_scene = NfcSceneMifareUlMenu};
static const NfcGenerator mf_ul_21_generator = {
.name = "Mifare Ultralight EV1 21",
.generator_func = nfc_generate_mf_ul_21,
.next_scene = NfcSceneMifareUlMenu};
static const NfcGenerator mf_ul_h21_generator = {
.name = "Mifare Ultralight EV1 H21",
.generator_func = nfc_generate_mf_ul_h21,
.next_scene = NfcSceneMifareUlMenu};
static const NfcGenerator ntag213_generator = {
.name = "NTAG213",
.generator_func = nfc_generate_ntag213,
.next_scene = NfcSceneMifareUlMenu};
static const NfcGenerator ntag215_generator = {
.name = "NTAG215",
.generator_func = nfc_generate_ntag215,
.next_scene = NfcSceneMifareUlMenu};
static const NfcGenerator ntag216_generator = {
.name = "NTAG216",
.generator_func = nfc_generate_ntag216,
.next_scene = NfcSceneMifareUlMenu};
static const NfcGenerator ntag_i2c_1k_generator = {
.name = "NTAG I2C 1k",
.generator_func = nfc_generate_ntag_i2c_1k,
.next_scene = NfcSceneMifareUlMenu};
static const NfcGenerator ntag_i2c_2k_generator = {
.name = "NTAG I2C 2k",
.generator_func = nfc_generate_ntag_i2c_2k,
.next_scene = NfcSceneMifareUlMenu};
static const NfcGenerator ntag_i2c_plus_1k_generator = {
.name = "NTAG I2C Plus 1k",
.generator_func = nfc_generate_ntag_i2c_plus_1k,
.next_scene = NfcSceneMifareUlMenu};
static const NfcGenerator ntag_i2c_plus_2k_generator = {
.name = "NTAG I2C Plus 2k",
.generator_func = nfc_generate_ntag_i2c_plus_2k,
.next_scene = NfcSceneMifareUlMenu};
const NfcGenerator* const nfc_generators[] = {
&mf_ul_generator,
&mf_ul_11_generator,
&mf_ul_h11_generator,
&mf_ul_21_generator,
&mf_ul_h21_generator,
&ntag213_generator,
&ntag215_generator,
&ntag216_generator,
&ntag_i2c_1k_generator,
&ntag_i2c_2k_generator,
&ntag_i2c_plus_1k_generator,
&ntag_i2c_plus_2k_generator,
NULL,
};

View File

@ -0,0 +1,13 @@
#pragma once
#include "../nfc_i.h"
typedef void (*NfcGeneratorFunc)(NfcDeviceData* data);
struct NfcGenerator {
const char* name;
NfcGeneratorFunc generator_func;
NfcScene next_scene;
};
extern const NfcGenerator* const nfc_generators[];

81
applications/nfc/nfc.c Executable file → Normal file
View File

@ -19,6 +19,77 @@ void nfc_tick_event_callback(void* context) {
scene_manager_handle_tick_event(nfc->scene_manager);
}
void nfc_rpc_exit_callback(Nfc* nfc) {
if(nfc->rpc_state == NfcRpcStateEmulating) {
// Stop worker
nfc_worker_stop(nfc->worker);
} else if(nfc->rpc_state == NfcRpcStateEmulated) {
// Stop worker
nfc_worker_stop(nfc->worker);
// Save data in shadow file
nfc_device_save_shadow(nfc->dev, nfc->dev->dev_name);
}
if(nfc->rpc_ctx) {
rpc_system_app_set_callback(nfc->rpc_ctx, NULL, NULL);
nfc->rpc_ctx = NULL;
}
}
static void nfc_rpc_emulate_callback(NfcWorkerEvent event, void* context) {
UNUSED(event);
Nfc* nfc = context;
nfc->rpc_state = NfcRpcStateEmulated;
}
static bool nfc_rpc_command_callback(RpcAppSystemEvent event, const char* arg, void* context) {
furi_assert(context);
Nfc* nfc = context;
if(!nfc->rpc_ctx) {
return false;
}
bool result = false;
if(event == RpcAppEventSessionClose) {
rpc_system_app_set_callback(nfc->rpc_ctx, NULL, NULL);
nfc->rpc_ctx = NULL;
view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit);
result = true;
} else if(event == RpcAppEventAppExit) {
view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit);
result = true;
} else if(event == RpcAppEventLoadFile) {
if((arg) && (nfc->rpc_state == NfcRpcStateIdle)) {
if(nfc_device_load(nfc->dev, arg, false)) {
if(nfc->dev->format == NfcDeviceSaveFormatMifareUl) {
nfc_worker_start(
nfc->worker,
NfcWorkerStateEmulateMifareUltralight,
&nfc->dev->dev_data,
nfc_rpc_emulate_callback,
nfc);
} else if(nfc->dev->format == NfcDeviceSaveFormatMifareClassic) {
nfc_worker_start(
nfc->worker,
NfcWorkerStateEmulateMifareClassic,
&nfc->dev->dev_data,
nfc_rpc_emulate_callback,
nfc);
} else {
nfc_worker_start(
nfc->worker, NfcWorkerStateEmulate, &nfc->dev->dev_data, NULL, nfc);
}
nfc->rpc_state = NfcRpcStateEmulating;
result = true;
}
}
}
return result;
}
Nfc* nfc_alloc() {
Nfc* nfc = malloc(sizeof(Nfc));
@ -84,6 +155,9 @@ Nfc* nfc_alloc() {
view_dispatcher_add_view(
nfc->view_dispatcher, NfcViewDictAttack, dict_attack_get_view(nfc->dict_attack));
// Generator
nfc->generator = NULL;
return nfc;
}
@ -190,7 +264,12 @@ int32_t nfc_app(void* p) {
// Check argument and run corresponding scene
if((*args != '\0')) {
if(nfc_device_load(nfc->dev, p)) {
uint32_t rpc_ctx = 0;
if(sscanf(p, "RPC %lX", &rpc_ctx) == 1) {
nfc->rpc_ctx = (void*)rpc_ctx;
rpc_system_app_set_callback(nfc->rpc_ctx, nfc_rpc_command_callback, nfc);
scene_manager_next_scene(nfc->scene_manager, NfcSceneRpc);
} else if(nfc_device_load(nfc->dev, p, true)) {
if(nfc->dev->format == NfcDeviceSaveFormatMifareUl) {
scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateMifareUl);
} else if(nfc->dev->format == NfcDeviceSaveFormatMifareClassic) {

View File

@ -837,7 +837,7 @@ bool nfc_device_save_shadow(NfcDevice* dev, const char* dev_name) {
return nfc_device_save_file(dev, dev_name, NFC_APP_FOLDER, NFC_APP_SHADOW_EXTENSION, true);
}
static bool nfc_device_load_data(NfcDevice* dev, string_t path) {
static bool nfc_device_load_data(NfcDevice* dev, string_t path, bool show_dialog) {
bool parsed = false;
FlipperFormat* file = flipper_format_file_alloc(dev->storage);
FuriHalNfcDevData* data = &dev->dev_data.nfc_data;
@ -887,7 +887,7 @@ static bool nfc_device_load_data(NfcDevice* dev, string_t path) {
parsed = true;
} while(false);
if(!parsed) {
if((!parsed) && (show_dialog)) {
if(deprecated_version) {
dialog_message_show_storage_error(dev->dialogs, "File format deprecated");
} else {
@ -900,13 +900,13 @@ static bool nfc_device_load_data(NfcDevice* dev, string_t path) {
return parsed;
}
bool nfc_device_load(NfcDevice* dev, const char* file_path) {
bool nfc_device_load(NfcDevice* dev, const char* file_path, bool show_dialog) {
furi_assert(dev);
furi_assert(file_path);
// Load device data
string_set_str(dev->load_path, file_path);
bool dev_load = nfc_device_load_data(dev, dev->load_path);
bool dev_load = nfc_device_load_data(dev, dev->load_path, show_dialog);
if(dev_load) {
// Set device name
string_t filename;
@ -933,7 +933,7 @@ bool nfc_file_select(NfcDevice* dev) {
string_init(filename);
path_extract_filename(dev->load_path, filename, true);
strncpy(dev->dev_name, string_get_cstr(filename), NFC_DEV_NAME_MAX_LEN);
res = nfc_device_load_data(dev, dev->load_path);
res = nfc_device_load_data(dev, dev->load_path, true);
if(res) {
nfc_device_set_name(dev, dev->dev_name);
}
@ -1017,7 +1017,7 @@ bool nfc_device_restore(NfcDevice* dev, bool use_load_path) {
} else {
string_printf(path, "%s/%s%s", NFC_APP_FOLDER, dev->dev_name, NFC_APP_EXTENSION);
}
if(!nfc_device_load_data(dev, path)) break;
if(!nfc_device_load_data(dev, path, true)) break;
restored = true;
} while(0);

View File

@ -71,7 +71,7 @@ bool nfc_device_save(NfcDevice* dev, const char* dev_name);
bool nfc_device_save_shadow(NfcDevice* dev, const char* dev_name);
bool nfc_device_load(NfcDevice* dev, const char* file_path);
bool nfc_device_load(NfcDevice* dev, const char* file_path, bool show_dialog);
bool nfc_file_select(NfcDevice* dev);

View File

@ -29,10 +29,21 @@
#include <nfc/scenes/nfc_scene.h>
#include <nfc/helpers/nfc_custom_event.h>
#include "rpc/rpc_app.h"
#define NFC_SEND_NOTIFICATION_FALSE (0UL)
#define NFC_SEND_NOTIFICATION_TRUE (1UL)
#define NFC_TEXT_STORE_SIZE 128
typedef enum {
NfcRpcStateIdle,
NfcRpcStateEmulating,
NfcRpcStateEmulated,
} NfcRpcState;
// Forward declaration due to circular dependency
typedef struct NfcGenerator NfcGenerator;
struct Nfc {
NfcWorker* worker;
ViewDispatcher* view_dispatcher;
@ -45,6 +56,9 @@ struct Nfc {
char text_store[NFC_TEXT_STORE_SIZE + 1];
string_t text_box_store;
void* rpc_ctx;
NfcRpcState rpc_state;
// Common Views
Submenu* submenu;
DialogEx* dialog_ex;
@ -55,6 +69,8 @@ struct Nfc {
Widget* widget;
BankCard* bank_card;
DictAttack* dict_attack;
const NfcGenerator* generator;
};
typedef enum {
@ -80,3 +96,5 @@ void nfc_text_store_clear(Nfc* nfc);
void nfc_blink_start(Nfc* nfc);
void nfc_blink_stop(Nfc* nfc);
void nfc_rpc_exit_callback(Nfc* nfc);

View File

@ -1,6 +1,8 @@
#include "nfc_worker_i.h"
#include <furi_hal.h>
#include <platform.h>
#define TAG "NfcWorker"
/***************************** NFC Worker API *******************************/
@ -495,9 +497,11 @@ void nfc_worker_emulate_mifare_classic(NfcWorker* nfc_worker) {
NfcaSignal* nfca_signal = nfca_signal_alloc();
tx_rx.nfca_signal = nfca_signal;
rfal_platform_spi_acquire();
furi_hal_nfc_listen_start(nfc_data);
while(nfc_worker->state == NfcWorkerStateEmulateMifareClassic) {
if(furi_hal_nfc_listen(
nfc_data->uid, nfc_data->uid_len, nfc_data->atqa, nfc_data->sak, true, 300)) {
if(furi_hal_nfc_listen_rx(&tx_rx, 300)) {
mf_classic_emulator(&emulator, &tx_rx);
}
}
@ -510,6 +514,8 @@ void nfc_worker_emulate_mifare_classic(NfcWorker* nfc_worker) {
}
nfca_signal_free(nfca_signal);
rfal_platform_spi_release();
}
void nfc_worker_read_mifare_desfire(NfcWorker* nfc_worker) {

View File

@ -37,3 +37,5 @@ ADD_SCENE(nfc, read_mifare_classic, ReadMifareClassic)
ADD_SCENE(nfc, emulate_mifare_classic, EmulateMifareClassic)
ADD_SCENE(nfc, mifare_classic_menu, MifareClassicMenu)
ADD_SCENE(nfc, dict_not_found, DictNotFound)
ADD_SCENE(nfc, rpc, Rpc)
ADD_SCENE(nfc, generate_info, GenerateInfo)

View File

@ -0,0 +1,55 @@
#include "../nfc_i.h"
#include "../helpers/nfc_generators.h"
void nfc_scene_generate_info_dialog_callback(DialogExResult result, void* context) {
Nfc* nfc = context;
view_dispatcher_send_custom_event(nfc->view_dispatcher, result);
}
void nfc_scene_generate_info_on_enter(void* context) {
Nfc* nfc = context;
// Setup dialog view
FuriHalNfcDevData* data = &nfc->dev->dev_data.nfc_data;
DialogEx* dialog_ex = nfc->dialog_ex;
dialog_ex_set_right_button_text(dialog_ex, "More");
// Create info text
string_t info_str;
string_init_printf(
info_str, "%s\n%s\nUID:", nfc->generator->name, nfc_get_dev_type(data->type));
// Append UID
for(int i = 0; i < data->uid_len; ++i) {
string_cat_printf(info_str, " %02X", data->uid[i]);
}
nfc_text_store_set(nfc, string_get_cstr(info_str));
string_clear(info_str);
dialog_ex_set_text(dialog_ex, nfc->text_store, 0, 0, AlignLeft, AlignTop);
dialog_ex_set_context(dialog_ex, nfc);
dialog_ex_set_result_callback(dialog_ex, nfc_scene_generate_info_dialog_callback);
view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewDialogEx);
}
bool nfc_scene_generate_info_on_event(void* context, SceneManagerEvent event) {
Nfc* nfc = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == DialogExResultRight) {
scene_manager_next_scene(nfc->scene_manager, nfc->generator->next_scene);
consumed = true;
}
}
return consumed;
}
void nfc_scene_generate_info_on_exit(void* context) {
Nfc* nfc = context;
// Clean views
dialog_ex_reset(nfc->dialog_ex);
}

View File

@ -0,0 +1,34 @@
#include "../nfc_i.h"
void nfc_scene_rpc_on_enter(void* context) {
Nfc* nfc = context;
Widget* widget = nfc->widget;
widget_add_text_box_element(
widget, 0, 0, 128, 28, AlignCenter, AlignCenter, "RPC mode", false);
notification_message(nfc->notifications, &sequence_display_backlight_on);
view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget);
}
bool nfc_scene_rpc_on_event(void* context, SceneManagerEvent event) {
Nfc* nfc = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
consumed = true;
if(event.event == NfcCustomEventViewExit) {
view_dispatcher_stop(nfc->view_dispatcher);
}
}
return consumed;
}
void nfc_scene_rpc_on_exit(void* context) {
Nfc* nfc = context;
nfc_rpc_exit_callback(nfc);
widget_reset(nfc->widget);
}

17
applications/nfc/scenes/nfc_scene_set_type.c Executable file → Normal file
View File

@ -1,9 +1,11 @@
#include "../nfc_i.h"
#include "m-string.h"
#include "../helpers/nfc_generators.h"
enum SubmenuIndex {
SubmenuIndexNFCA4,
SubmenuIndexNFCA7,
SubmenuIndexGeneratorsStart,
};
void nfc_scene_set_type_submenu_callback(void* context, uint32_t index) {
@ -22,6 +24,14 @@ void nfc_scene_set_type_on_enter(void* context) {
submenu, "NFC-A 7-bytes UID", SubmenuIndexNFCA7, nfc_scene_set_type_submenu_callback, nfc);
submenu_add_item(
submenu, "NFC-A 4-bytes UID", SubmenuIndexNFCA4, nfc_scene_set_type_submenu_callback, nfc);
// Generators
int i = SubmenuIndexGeneratorsStart;
for(const NfcGenerator* const* generator = nfc_generators; *generator != NULL;
++generator, ++i) {
submenu_add_item(submenu, (*generator)->name, i, nfc_scene_set_type_submenu_callback, nfc);
}
view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu);
}
@ -40,6 +50,13 @@ bool nfc_scene_set_type_on_event(void* context, SceneManagerEvent event) {
nfc->dev->format = NfcDeviceSaveFormatUid;
scene_manager_next_scene(nfc->scene_manager, NfcSceneSetSak);
consumed = true;
} else {
nfc_device_clear(nfc->dev);
nfc->generator = nfc_generators[event.event - SubmenuIndexGeneratorsStart];
nfc->generator->generator_func(&nfc->dev->dev_data);
scene_manager_next_scene(nfc->scene_manager, NfcSceneGenerateInfo);
consumed = true;
}
}
return consumed;

View File

@ -93,6 +93,9 @@ void notification_reset_notification_led_layer(NotificationLedLayer* layer) {
}
void notification_reset_notification_layer(NotificationApp* app, uint8_t reset_mask) {
if(reset_mask & reset_blink_mask) {
furi_hal_light_blink_stop();
}
if(reset_mask & reset_red_mask) {
notification_reset_notification_led_layer(&app->led[0]);
}
@ -102,9 +105,6 @@ void notification_reset_notification_layer(NotificationApp* app, uint8_t reset_m
if(reset_mask & reset_blue_mask) {
notification_reset_notification_led_layer(&app->led[2]);
}
if(reset_mask & reset_blink_mask) {
furi_hal_light_blink_stop();
}
if(reset_mask & reset_vibro_mask) {
notification_vibro_off();
}
@ -243,6 +243,9 @@ void notification_process_notification_message(
notification_message->data.led_blink.on_time,
notification_message->data.led_blink.period);
reset_mask |= reset_blink_mask;
reset_mask |= reset_red_mask;
reset_mask |= reset_green_mask;
reset_mask |= reset_blue_mask;
break;
case NotificationMessageTypeLedBlinkColor:
led_active = true;
@ -251,6 +254,9 @@ void notification_process_notification_message(
case NotificationMessageTypeLedBlinkStop:
furi_hal_light_blink_stop();
reset_mask &= ~reset_blink_mask;
reset_mask |= reset_red_mask;
reset_mask |= reset_green_mask;
reset_mask |= reset_blue_mask;
break;
case NotificationMessageTypeVibro:
if(notification_message->data.vibro.on) {
@ -326,7 +332,7 @@ void notification_process_notification_message(
reset_mask |= reset_green_mask;
reset_mask |= reset_blue_mask;
if(need_minimal_delay) {
if((need_minimal_delay) && (reset_notifications)) {
notification_apply_notification_leds(app, led_off_values);
furi_hal_delay_ms(minimal_delay);
}

View File

@ -0,0 +1,11 @@
App(
appid="picopass",
name="PicoPass Reader",
apptype=FlipperAppType.PLUGIN,
entry_point="picopass_app",
cdefines=["APP_PICOPASS"],
requires=["storage", "gui"],
stack_size=1 * 1024,
icon="A_Plugins_14",
order=30,
)

View File

@ -0,0 +1,162 @@
#include "picopass_i.h"
#define TAG "PicoPass"
bool picopass_custom_event_callback(void* context, uint32_t event) {
furi_assert(context);
Picopass* picopass = context;
return scene_manager_handle_custom_event(picopass->scene_manager, event);
}
bool picopass_back_event_callback(void* context) {
furi_assert(context);
Picopass* picopass = context;
return scene_manager_handle_back_event(picopass->scene_manager);
}
void picopass_tick_event_callback(void* context) {
furi_assert(context);
Picopass* picopass = context;
scene_manager_handle_tick_event(picopass->scene_manager);
}
Picopass* picopass_alloc() {
Picopass* picopass = malloc(sizeof(Picopass));
picopass->worker = picopass_worker_alloc();
picopass->view_dispatcher = view_dispatcher_alloc();
picopass->scene_manager = scene_manager_alloc(&picopass_scene_handlers, picopass);
view_dispatcher_enable_queue(picopass->view_dispatcher);
view_dispatcher_set_event_callback_context(picopass->view_dispatcher, picopass);
view_dispatcher_set_custom_event_callback(
picopass->view_dispatcher, picopass_custom_event_callback);
view_dispatcher_set_navigation_event_callback(
picopass->view_dispatcher, picopass_back_event_callback);
view_dispatcher_set_tick_event_callback(
picopass->view_dispatcher, picopass_tick_event_callback, 100);
// Picopass device
picopass->dev = picopass_device_alloc();
// Open GUI record
picopass->gui = furi_record_open("gui");
view_dispatcher_attach_to_gui(
picopass->view_dispatcher, picopass->gui, ViewDispatcherTypeFullscreen);
// Open Notification record
picopass->notifications = furi_record_open("notification");
// Submenu
picopass->submenu = submenu_alloc();
view_dispatcher_add_view(
picopass->view_dispatcher, PicopassViewMenu, submenu_get_view(picopass->submenu));
// Popup
picopass->popup = popup_alloc();
view_dispatcher_add_view(
picopass->view_dispatcher, PicopassViewPopup, popup_get_view(picopass->popup));
// Text Input
picopass->text_input = text_input_alloc();
view_dispatcher_add_view(
picopass->view_dispatcher,
PicopassViewTextInput,
text_input_get_view(picopass->text_input));
// Custom Widget
picopass->widget = widget_alloc();
view_dispatcher_add_view(
picopass->view_dispatcher, PicopassViewWidget, widget_get_view(picopass->widget));
return picopass;
}
void picopass_free(Picopass* picopass) {
furi_assert(picopass);
// Picopass device
picopass_device_free(picopass->dev);
picopass->dev = NULL;
// Submenu
view_dispatcher_remove_view(picopass->view_dispatcher, PicopassViewMenu);
submenu_free(picopass->submenu);
// Popup
view_dispatcher_remove_view(picopass->view_dispatcher, PicopassViewPopup);
popup_free(picopass->popup);
// TextInput
view_dispatcher_remove_view(picopass->view_dispatcher, PicopassViewTextInput);
text_input_free(picopass->text_input);
// Custom Widget
view_dispatcher_remove_view(picopass->view_dispatcher, PicopassViewWidget);
widget_free(picopass->widget);
// Worker
picopass_worker_stop(picopass->worker);
picopass_worker_free(picopass->worker);
// View Dispatcher
view_dispatcher_free(picopass->view_dispatcher);
// Scene Manager
scene_manager_free(picopass->scene_manager);
// GUI
furi_record_close("gui");
picopass->gui = NULL;
// Notifications
furi_record_close("notification");
picopass->notifications = NULL;
free(picopass);
}
void picopass_text_store_set(Picopass* picopass, const char* text, ...) {
va_list args;
va_start(args, text);
vsnprintf(picopass->text_store, sizeof(picopass->text_store), text, args);
va_end(args);
}
void picopass_text_store_clear(Picopass* picopass) {
memset(picopass->text_store, 0, sizeof(picopass->text_store));
}
static const NotificationSequence picopass_sequence_blink_start_blue = {
&message_blink_start_10,
&message_blink_set_color_blue,
&message_do_not_reset,
NULL,
};
static const NotificationSequence picopass_sequence_blink_stop = {
&message_blink_stop,
NULL,
};
void picopass_blink_start(Picopass* picopass) {
notification_message(picopass->notifications, &picopass_sequence_blink_start_blue);
}
void picopass_blink_stop(Picopass* picopass) {
notification_message(picopass->notifications, &picopass_sequence_blink_stop);
}
int32_t picopass_app(void* p) {
UNUSED(p);
Picopass* picopass = picopass_alloc();
scene_manager_next_scene(picopass->scene_manager, PicopassSceneStart);
view_dispatcher_run(picopass->view_dispatcher);
picopass_free(picopass);
return 0;
}

View File

@ -0,0 +1,3 @@
#pragma once
typedef struct Picopass Picopass;

View File

@ -0,0 +1,146 @@
#include "picopass_device.h"
#include <toolbox/path.h>
#include <flipper_format/flipper_format.h>
#define TAG "PicopassDevice"
static const char* picopass_file_header = "Flipper Picopass device";
static const uint32_t picopass_file_version = 1;
PicopassDevice* picopass_device_alloc() {
PicopassDevice* picopass_dev = malloc(sizeof(PicopassDevice));
picopass_dev->storage = furi_record_open("storage");
picopass_dev->dialogs = furi_record_open("dialogs");
return picopass_dev;
}
void picopass_device_set_name(PicopassDevice* dev, const char* name) {
furi_assert(dev);
strlcpy(dev->dev_name, name, PICOPASS_DEV_NAME_MAX_LEN);
}
static bool picopass_device_save_file(
PicopassDevice* dev,
const char* dev_name,
const char* folder,
const char* extension,
bool use_load_path) {
furi_assert(dev);
bool saved = false;
FlipperFormat* file = flipper_format_file_alloc(dev->storage);
PicopassPacs* pacs = &dev->dev_data.pacs;
ApplicationArea* AA1 = &dev->dev_data.AA1;
string_t temp_str;
string_init(temp_str);
do {
if(use_load_path && !string_empty_p(dev->load_path)) {
// Get directory name
path_extract_dirname(string_get_cstr(dev->load_path), temp_str);
// Create picopass directory if necessary
if(!storage_simply_mkdir(dev->storage, string_get_cstr(temp_str))) break;
// Make path to file to save
string_cat_printf(temp_str, "/%s%s", dev_name, extension);
} else {
// Create picopass directory if necessary
if(!storage_simply_mkdir(dev->storage, PICOPASS_APP_FOLDER)) break;
// First remove picopass device file if it was saved
string_printf(temp_str, "%s/%s%s", folder, dev_name, extension);
}
// Open file
if(!flipper_format_file_open_always(file, string_get_cstr(temp_str))) break;
if(dev->format == PicopassDeviceSaveFormatHF) {
// Write header
if(!flipper_format_write_header_cstr(file, picopass_file_header, picopass_file_version))
break;
if(pacs->record.valid) {
if(!flipper_format_write_uint32(
file, "Facility Code", (uint32_t*)&pacs->record.FacilityCode, 1))
break;
if(!flipper_format_write_uint32(
file, "Card Number", (uint32_t*)&pacs->record.CardNumber, 1))
break;
if(!flipper_format_write_hex(
file, "Credential", pacs->credential, PICOPASS_BLOCK_LEN))
break;
if(!flipper_format_write_hex(file, "PIN", pacs->pin0, PICOPASS_BLOCK_LEN)) break;
if(!flipper_format_write_hex(file, "PIN(cont.)", pacs->pin1, PICOPASS_BLOCK_LEN))
break;
if(!flipper_format_write_comment_cstr(file, "Picopass blocks")) break;
// TODO: Save CSN, CFG, AA1, etc
bool block_saved = true;
for(size_t i = 0; i < 4; i++) {
string_printf(temp_str, "Block %d", i + 6);
if(!flipper_format_write_hex(
file,
string_get_cstr(temp_str),
AA1->block[i].data,
PICOPASS_BLOCK_LEN)) {
block_saved = false;
break;
}
}
if(!block_saved) break;
if(!flipper_format_write_comment_cstr(file, "This is currently incomplete")) break;
}
} else if(dev->format == PicopassDeviceSaveFormatLF) {
const char* lf_header = "Flipper RFID key";
// Write header
if(!flipper_format_write_header_cstr(file, lf_header, 1)) break;
if(!flipper_format_write_comment_cstr(
file,
"This was generated from the Picopass plugin and may not match current lfrfid"))
break;
// When lfrfid supports more formats, update this
if(!flipper_format_write_string_cstr(file, "Key type", "H10301")) break;
uint8_t H10301[3] = {0};
H10301[0] = pacs->record.FacilityCode;
H10301[1] = pacs->record.CardNumber >> 8;
H10301[2] = pacs->record.CardNumber & 0x00FF;
if(!flipper_format_write_hex(file, "Data", H10301, 3)) break;
}
saved = true;
} while(0);
if(!saved) {
dialog_message_show_storage_error(dev->dialogs, "Can not save\nkey file");
}
string_clear(temp_str);
flipper_format_free(file);
return saved;
}
bool picopass_device_save(PicopassDevice* dev, const char* dev_name) {
if(dev->format == PicopassDeviceSaveFormatHF) {
return picopass_device_save_file(
dev, dev_name, PICOPASS_APP_FOLDER, PICOPASS_APP_EXTENSION, true);
} else if(dev->format == PicopassDeviceSaveFormatLF) {
return picopass_device_save_file(dev, dev_name, "/any/lfrfid", ".rfid", true);
}
return false;
}
void picopass_device_clear(PicopassDevice* dev) {
furi_assert(dev);
picopass_device_data_clear(&dev->dev_data);
memset(&dev->dev_data, 0, sizeof(dev->dev_data));
}
void picopass_device_free(PicopassDevice* picopass_dev) {
furi_assert(picopass_dev);
picopass_device_clear(picopass_dev);
furi_record_close("storage");
furi_record_close("dialogs");
string_clear(picopass_dev->load_path);
free(picopass_dev);
}
void picopass_device_data_clear(PicopassDeviceData* dev_data) {
memset(&dev_data->AA1, 0, sizeof(ApplicationArea));
}

View File

@ -0,0 +1,70 @@
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include <storage/storage.h>
#include <dialogs/dialogs.h>
#include <rfal_picopass.h>
#define PICOPASS_DEV_NAME_MAX_LEN 22
#define PICOPASS_READER_DATA_MAX_SIZE 64
#define PICOPASS_BLOCK_LEN 8
#define PICOPASS_APP_FOLDER "/any/picopass"
#define PICOPASS_APP_EXTENSION ".picopass"
#define PICOPASS_APP_SHADOW_EXTENSION ".pas"
typedef enum {
PicopassDeviceEncryptionUnknown = 0,
PicopassDeviceEncryptionNone = 0x14,
PicopassDeviceEncryptionDES = 0x15,
PicopassDeviceEncryption3DES = 0x17,
} PicopassEncryption;
typedef enum {
PicopassDeviceSaveFormatHF,
PicopassDeviceSaveFormatLF,
} PicopassDeviceSaveFormat;
typedef struct {
bool valid;
uint8_t bitLength;
uint8_t FacilityCode;
uint16_t CardNumber;
} PicopassWiegandRecord;
typedef struct {
bool biometrics;
PicopassEncryption encryption;
uint8_t credential[8];
uint8_t pin0[8];
uint8_t pin1[8];
PicopassWiegandRecord record;
} PicopassPacs;
typedef struct {
ApplicationArea AA1;
PicopassPacs pacs;
} PicopassDeviceData;
typedef struct {
Storage* storage;
DialogsApp* dialogs;
PicopassDeviceData dev_data;
char dev_name[PICOPASS_DEV_NAME_MAX_LEN + 1];
string_t load_path;
PicopassDeviceSaveFormat format;
} PicopassDevice;
PicopassDevice* picopass_device_alloc();
void picopass_device_free(PicopassDevice* picopass_dev);
void picopass_device_set_name(PicopassDevice* dev, const char* name);
bool picopass_device_save(PicopassDevice* dev, const char* dev_name);
void picopass_device_data_clear(PicopassDeviceData* dev_data);
void picopass_device_clear(PicopassDevice* dev);

View File

@ -0,0 +1,77 @@
#pragma once
#include "picopass.h"
#include "picopass_worker.h"
#include "picopass_device.h"
#include <rfal_picopass.h>
#include <furi.h>
#include <gui/gui.h>
#include <gui/view_dispatcher.h>
#include <gui/scene_manager.h>
#include <notification/notification_messages.h>
#include <gui/modules/submenu.h>
#include <gui/modules/popup.h>
#include <gui/modules/text_input.h>
#include <gui/modules/widget.h>
#include <input/input.h>
#include <picopass/scenes/picopass_scene.h>
#include <storage/storage.h>
#include <lib/toolbox/path.h>
#define PICOPASS_TEXT_STORE_SIZE 128
enum PicopassCustomEvent {
// Reserve first 100 events for button types and indexes, starting from 0
PicopassCustomEventReserved = 100,
PicopassCustomEventViewExit,
PicopassCustomEventWorkerExit,
PicopassCustomEventByteInputDone,
PicopassCustomEventTextInputDone,
};
typedef enum {
EventTypeTick,
EventTypeKey,
} EventType;
struct Picopass {
PicopassWorker* worker;
ViewDispatcher* view_dispatcher;
Gui* gui;
NotificationApp* notifications;
SceneManager* scene_manager;
PicopassDevice* dev;
char text_store[PICOPASS_TEXT_STORE_SIZE + 1];
string_t text_box_store;
// Common Views
Submenu* submenu;
Popup* popup;
TextInput* text_input;
Widget* widget;
};
typedef enum {
PicopassViewMenu,
PicopassViewPopup,
PicopassViewTextInput,
PicopassViewWidget,
} PicopassView;
Picopass* picopass_alloc();
void picopass_text_store_set(Picopass* picopass, const char* text, ...);
void picopass_text_store_clear(Picopass* picopass);
void picopass_blink_start(Picopass* picopass);
void picopass_blink_stop(Picopass* picopass);

View File

@ -0,0 +1,310 @@
#include "picopass_worker_i.h"
#include <furi_hal.h>
#include <stdlib.h>
#include <st25r3916.h>
#include <rfal_analogConfig.h>
#include <rfal_rf.h>
#include <rfal_nfc.h>
#include <mbedtls/des.h>
#include <loclass/optimized_ikeys.h>
#include <loclass/optimized_cipher.h>
#include <platform.h>
#define TAG "PicopassWorker"
const uint8_t picopass_iclass_key[] = {0xaf, 0xa7, 0x85, 0xa7, 0xda, 0xb3, 0x33, 0x78};
const uint8_t picopass_iclass_decryptionkey[] =
{0xb4, 0x21, 0x2c, 0xca, 0xb7, 0xed, 0x21, 0x0f, 0x7b, 0x93, 0xd4, 0x59, 0x39, 0xc7, 0xdd, 0x36};
static void picopass_worker_enable_field() {
st25r3916TxRxOn();
rfalLowPowerModeStop();
rfalWorker();
}
static ReturnCode picopass_worker_disable_field(ReturnCode rc) {
st25r3916TxRxOff();
rfalLowPowerModeStart();
return rc;
}
static ReturnCode picopass_worker_decrypt(uint8_t* enc_data, uint8_t* dec_data) {
uint8_t key[32] = {0};
memcpy(key, picopass_iclass_decryptionkey, sizeof(picopass_iclass_decryptionkey));
mbedtls_des3_context ctx;
mbedtls_des3_init(&ctx);
mbedtls_des3_set2key_dec(&ctx, key);
mbedtls_des3_crypt_ecb(&ctx, enc_data, dec_data);
mbedtls_des3_free(&ctx);
return ERR_NONE;
}
static ReturnCode picopass_worker_parse_wiegand(uint8_t* data, PicopassWiegandRecord* record) {
uint32_t* halves = (uint32_t*)data;
if(halves[0] == 0) {
uint8_t leading0s = __builtin_clz(REVERSE_BYTES_U32(halves[1]));
record->bitLength = 31 - leading0s;
} else {
uint8_t leading0s = __builtin_clz(REVERSE_BYTES_U32(halves[0]));
record->bitLength = 63 - leading0s;
}
FURI_LOG_D(TAG, "bitLength: %d", record->bitLength);
if(record->bitLength == 26) {
uint8_t* v4 = data + 4;
v4[0] = 0;
uint32_t bot = v4[3] | (v4[2] << 8) | (v4[1] << 16) | (v4[0] << 24);
record->CardNumber = (bot >> 1) & 0xFFFF;
record->FacilityCode = (bot >> 17) & 0xFF;
record->valid = true;
} else {
record->CardNumber = 0;
record->FacilityCode = 0;
record->valid = false;
}
return ERR_NONE;
}
/***************************** Picopass Worker API *******************************/
PicopassWorker* picopass_worker_alloc() {
PicopassWorker* picopass_worker = malloc(sizeof(PicopassWorker));
// Worker thread attributes
picopass_worker->thread = furi_thread_alloc();
furi_thread_set_name(picopass_worker->thread, "PicopassWorker");
furi_thread_set_stack_size(picopass_worker->thread, 8192);
furi_thread_set_callback(picopass_worker->thread, picopass_worker_task);
furi_thread_set_context(picopass_worker->thread, picopass_worker);
picopass_worker->callback = NULL;
picopass_worker->context = NULL;
picopass_worker->storage = furi_record_open("storage");
picopass_worker_change_state(picopass_worker, PicopassWorkerStateReady);
return picopass_worker;
}
void picopass_worker_free(PicopassWorker* picopass_worker) {
furi_assert(picopass_worker);
furi_thread_free(picopass_worker->thread);
furi_record_close("storage");
free(picopass_worker);
}
PicopassWorkerState picopass_worker_get_state(PicopassWorker* picopass_worker) {
return picopass_worker->state;
}
void picopass_worker_start(
PicopassWorker* picopass_worker,
PicopassWorkerState state,
PicopassDeviceData* dev_data,
PicopassWorkerCallback callback,
void* context) {
furi_assert(picopass_worker);
furi_assert(dev_data);
picopass_worker->callback = callback;
picopass_worker->context = context;
picopass_worker->dev_data = dev_data;
picopass_worker_change_state(picopass_worker, state);
furi_thread_start(picopass_worker->thread);
}
void picopass_worker_stop(PicopassWorker* picopass_worker) {
furi_assert(picopass_worker);
if(picopass_worker->state == PicopassWorkerStateBroken ||
picopass_worker->state == PicopassWorkerStateReady) {
return;
}
picopass_worker_disable_field(ERR_NONE);
picopass_worker_change_state(picopass_worker, PicopassWorkerStateStop);
furi_thread_join(picopass_worker->thread);
}
void picopass_worker_change_state(PicopassWorker* picopass_worker, PicopassWorkerState state) {
picopass_worker->state = state;
}
/***************************** Picopass Worker Thread *******************************/
ReturnCode picopass_detect_card(int timeout) {
UNUSED(timeout);
ReturnCode err;
err = rfalPicoPassPollerInitialize();
if(err != ERR_NONE) {
FURI_LOG_E(TAG, "rfalPicoPassPollerInitialize error %d", err);
return err;
}
err = rfalFieldOnAndStartGT();
if(err != ERR_NONE) {
FURI_LOG_E(TAG, "rfalFieldOnAndStartGT error %d", err);
return err;
}
err = rfalPicoPassPollerCheckPresence();
if(err != ERR_RF_COLLISION) {
FURI_LOG_E(TAG, "rfalPicoPassPollerCheckPresence error %d", err);
return err;
}
return ERR_NONE;
}
ReturnCode picopass_read_card(ApplicationArea* AA1) {
rfalPicoPassIdentifyRes idRes;
rfalPicoPassSelectRes selRes;
rfalPicoPassReadCheckRes rcRes;
rfalPicoPassCheckRes chkRes;
ReturnCode err;
uint8_t div_key[8] = {0};
uint8_t mac[4] = {0};
uint8_t ccnr[12] = {0};
err = rfalPicoPassPollerIdentify(&idRes);
if(err != ERR_NONE) {
FURI_LOG_E(TAG, "rfalPicoPassPollerIdentify error %d", err);
return err;
}
err = rfalPicoPassPollerSelect(idRes.CSN, &selRes);
if(err != ERR_NONE) {
FURI_LOG_E(TAG, "rfalPicoPassPollerSelect error %d", err);
return err;
}
err = rfalPicoPassPollerReadCheck(&rcRes);
if(err != ERR_NONE) {
FURI_LOG_E(TAG, "rfalPicoPassPollerReadCheck error %d", err);
return err;
}
memcpy(ccnr, rcRes.CCNR, sizeof(rcRes.CCNR)); // last 4 bytes left 0
loclass_diversifyKey(selRes.CSN, picopass_iclass_key, div_key);
loclass_opt_doReaderMAC(ccnr, div_key, mac);
err = rfalPicoPassPollerCheck(mac, &chkRes);
if(err != ERR_NONE) {
FURI_LOG_E(TAG, "rfalPicoPassPollerCheck error %d", err);
return err;
}
for(size_t i = 0; i < 4; i++) {
FURI_LOG_D(TAG, "rfalPicoPassPollerReadBlock block %d", i + 6);
rfalPicoPassReadBlockRes block;
err = rfalPicoPassPollerReadBlock(i + 6, &block);
if(err != ERR_NONE) {
FURI_LOG_E(TAG, "rfalPicoPassPollerReadBlock error %d", err);
return err;
}
FURI_LOG_D(
TAG,
"rfalPicoPassPollerReadBlock %d %02x%02x%02x%02x%02x%02x%02x%02x",
i + 6,
block.data[0],
block.data[1],
block.data[2],
block.data[3],
block.data[4],
block.data[5],
block.data[6],
block.data[7]);
memcpy(&(AA1->block[i]), &block, sizeof(block));
}
return ERR_NONE;
}
int32_t picopass_worker_task(void* context) {
PicopassWorker* picopass_worker = context;
picopass_worker_enable_field();
if(picopass_worker->state == PicopassWorkerStateDetect) {
picopass_worker_detect(picopass_worker);
}
picopass_worker_disable_field(ERR_NONE);
picopass_worker_change_state(picopass_worker, PicopassWorkerStateReady);
return 0;
}
void picopass_worker_detect(PicopassWorker* picopass_worker) {
picopass_device_data_clear(picopass_worker->dev_data);
PicopassDeviceData* dev_data = picopass_worker->dev_data;
ApplicationArea* AA1 = &dev_data->AA1;
PicopassPacs* pacs = &dev_data->pacs;
ReturnCode err;
while(picopass_worker->state == PicopassWorkerStateDetect) {
if(picopass_detect_card(1000) == ERR_NONE) {
// Process first found device
err = picopass_read_card(AA1);
if(err != ERR_NONE) {
FURI_LOG_E(TAG, "picopass_read_card error %d", err);
}
pacs->biometrics = AA1->block[0].data[4];
pacs->encryption = AA1->block[0].data[7];
if(pacs->encryption == 0x17) {
FURI_LOG_D(TAG, "3DES Encrypted");
err = picopass_worker_decrypt(AA1->block[1].data, pacs->credential);
if(err != ERR_NONE) {
FURI_LOG_E(TAG, "decrypt error %d", err);
break;
}
err = picopass_worker_decrypt(AA1->block[2].data, pacs->pin0);
if(err != ERR_NONE) {
FURI_LOG_E(TAG, "decrypt error %d", err);
break;
}
err = picopass_worker_decrypt(AA1->block[3].data, pacs->pin1);
if(err != ERR_NONE) {
FURI_LOG_E(TAG, "decrypt error %d", err);
break;
}
} else if(pacs->encryption == 0x14) {
FURI_LOG_D(TAG, "No Encryption");
memcpy(pacs->credential, AA1->block[1].data, RFAL_PICOPASS_MAX_BLOCK_LEN);
memcpy(pacs->pin0, AA1->block[2].data, RFAL_PICOPASS_MAX_BLOCK_LEN);
memcpy(pacs->pin1, AA1->block[3].data, RFAL_PICOPASS_MAX_BLOCK_LEN);
} else if(pacs->encryption == 0x15) {
FURI_LOG_D(TAG, "DES Encrypted");
} else {
FURI_LOG_D(TAG, "Unknown encryption");
break;
}
picopass_worker_parse_wiegand(pacs->credential, &pacs->record);
// Notify caller and exit
if(picopass_worker->callback) {
picopass_worker->callback(PicopassWorkerEventSuccess, picopass_worker->context);
}
break;
}
osDelay(100);
}
}

View File

@ -0,0 +1,45 @@
#pragma once
#include "picopass_device.h"
typedef struct PicopassWorker PicopassWorker;
typedef enum {
// Init states
PicopassWorkerStateNone,
PicopassWorkerStateBroken,
PicopassWorkerStateReady,
// Main worker states
PicopassWorkerStateDetect,
// Transition
PicopassWorkerStateStop,
} PicopassWorkerState;
typedef enum {
// Reserve first 50 events for application events
PicopassWorkerEventReserved = 50,
// Picopass worker common events
PicopassWorkerEventSuccess,
PicopassWorkerEventFail,
PicopassWorkerEventNoCardDetected,
PicopassWorkerEventStartReading,
} PicopassWorkerEvent;
typedef void (*PicopassWorkerCallback)(PicopassWorkerEvent event, void* context);
PicopassWorker* picopass_worker_alloc();
PicopassWorkerState picopass_worker_get_state(PicopassWorker* picopass_worker);
void picopass_worker_free(PicopassWorker* picopass_worker);
void picopass_worker_start(
PicopassWorker* picopass_worker,
PicopassWorkerState state,
PicopassDeviceData* dev_data,
PicopassWorkerCallback callback,
void* context);
void picopass_worker_stop(PicopassWorker* picopass_worker);

View File

@ -0,0 +1,24 @@
#pragma once
#include "picopass_worker.h"
#include "picopass_i.h"
#include <furi.h>
#include <lib/toolbox/stream/file_stream.h>
struct PicopassWorker {
FuriThread* thread;
Storage* storage;
PicopassDeviceData* dev_data;
PicopassWorkerCallback callback;
void* context;
PicopassWorkerState state;
};
void picopass_worker_change_state(PicopassWorker* picopass_worker, PicopassWorkerState state);
int32_t picopass_worker_task(void* context);
void picopass_worker_detect(PicopassWorker* picopass_worker);

View File

@ -0,0 +1,30 @@
#include "picopass_scene.h"
// Generate scene on_enter handlers array
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
void (*const picopass_on_enter_handlers[])(void*) = {
#include "picopass_scene_config.h"
};
#undef ADD_SCENE
// Generate scene on_event handlers array
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event,
bool (*const picopass_on_event_handlers[])(void* context, SceneManagerEvent event) = {
#include "picopass_scene_config.h"
};
#undef ADD_SCENE
// Generate scene on_exit handlers array
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit,
void (*const picopass_on_exit_handlers[])(void* context) = {
#include "picopass_scene_config.h"
};
#undef ADD_SCENE
// Initialize scene handlers configuration structure
const SceneManagerHandlers picopass_scene_handlers = {
.on_enter_handlers = picopass_on_enter_handlers,
.on_event_handlers = picopass_on_event_handlers,
.on_exit_handlers = picopass_on_exit_handlers,
.scene_num = PicopassSceneNum,
};

View File

@ -0,0 +1,29 @@
#pragma once
#include <gui/scene_manager.h>
// Generate scene id and total number
#define ADD_SCENE(prefix, name, id) PicopassScene##id,
typedef enum {
#include "picopass_scene_config.h"
PicopassSceneNum,
} PicopassScene;
#undef ADD_SCENE
extern const SceneManagerHandlers picopass_scene_handlers;
// Generate scene on_enter handlers declaration
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*);
#include "picopass_scene_config.h"
#undef ADD_SCENE
// Generate scene on_event handlers declaration
#define ADD_SCENE(prefix, name, id) \
bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event);
#include "picopass_scene_config.h"
#undef ADD_SCENE
// Generate scene on_exit handlers declaration
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context);
#include "picopass_scene_config.h"
#undef ADD_SCENE

View File

@ -0,0 +1,65 @@
#include "../picopass_i.h"
enum SubmenuIndex {
SubmenuIndexSave,
SubmenuIndexSaveAsLF,
};
void picopass_scene_card_menu_submenu_callback(void* context, uint32_t index) {
Picopass* picopass = context;
view_dispatcher_send_custom_event(picopass->view_dispatcher, index);
}
void picopass_scene_card_menu_on_enter(void* context) {
Picopass* picopass = context;
Submenu* submenu = picopass->submenu;
submenu_add_item(
submenu, "Save", SubmenuIndexSave, picopass_scene_card_menu_submenu_callback, picopass);
if(picopass->dev->dev_data.pacs.record.valid) {
submenu_add_item(
submenu,
"Save as LF",
SubmenuIndexSaveAsLF,
picopass_scene_card_menu_submenu_callback,
picopass);
}
submenu_set_selected_item(
picopass->submenu,
scene_manager_get_scene_state(picopass->scene_manager, PicopassSceneCardMenu));
view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewMenu);
}
bool picopass_scene_card_menu_on_event(void* context, SceneManagerEvent event) {
Picopass* picopass = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == SubmenuIndexSave) {
scene_manager_set_scene_state(
picopass->scene_manager, PicopassSceneCardMenu, SubmenuIndexSave);
scene_manager_next_scene(picopass->scene_manager, PicopassSceneSaveName);
picopass->dev->format = PicopassDeviceSaveFormatHF;
consumed = true;
} else if(event.event == SubmenuIndexSaveAsLF) {
scene_manager_set_scene_state(
picopass->scene_manager, PicopassSceneCardMenu, SubmenuIndexSaveAsLF);
picopass->dev->format = PicopassDeviceSaveFormatLF;
scene_manager_next_scene(picopass->scene_manager, PicopassSceneSaveName);
consumed = true;
}
} else if(event.type == SceneManagerEventTypeBack) {
consumed = scene_manager_search_and_switch_to_previous_scene(
picopass->scene_manager, PicopassSceneStart);
}
return consumed;
}
void picopass_scene_card_menu_on_exit(void* context) {
Picopass* picopass = context;
submenu_reset(picopass->submenu);
}

View File

@ -0,0 +1,7 @@
ADD_SCENE(picopass, start, Start)
ADD_SCENE(picopass, read_card, ReadCard)
ADD_SCENE(picopass, read_card_success, ReadCardSuccess)
ADD_SCENE(picopass, card_menu, CardMenu)
ADD_SCENE(picopass, save_name, SaveName)
ADD_SCENE(picopass, save_success, SaveSuccess)
ADD_SCENE(picopass, saved_menu, SavedMenu)

View File

@ -0,0 +1,55 @@
#include "../picopass_i.h"
#include <dolphin/dolphin.h>
void picopass_read_card_worker_callback(PicopassWorkerEvent event, void* context) {
UNUSED(event);
Picopass* picopass = context;
view_dispatcher_send_custom_event(picopass->view_dispatcher, PicopassCustomEventWorkerExit);
}
void picopass_scene_read_card_on_enter(void* context) {
Picopass* picopass = context;
DOLPHIN_DEED(DolphinDeedNfcRead);
// Setup view
Popup* popup = picopass->popup;
popup_set_header(popup, "Detecting\npicopass card", 70, 34, AlignLeft, AlignTop);
popup_set_icon(popup, 0, 3, &I_RFIDDolphinReceive_97x61);
// Start worker
view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewPopup);
picopass_worker_start(
picopass->worker,
PicopassWorkerStateDetect,
&picopass->dev->dev_data,
picopass_read_card_worker_callback,
picopass);
picopass_blink_start(picopass);
}
bool picopass_scene_read_card_on_event(void* context, SceneManagerEvent event) {
Picopass* picopass = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == PicopassCustomEventWorkerExit) {
scene_manager_next_scene(picopass->scene_manager, PicopassSceneReadCardSuccess);
consumed = true;
}
} else if(event.type == SceneManagerEventTypeTick) {
consumed = true;
}
return consumed;
}
void picopass_scene_read_card_on_exit(void* context) {
Picopass* picopass = context;
// Stop worker
picopass_worker_stop(picopass->worker);
// Clear view
popup_reset(picopass->popup);
picopass_blink_stop(picopass);
}

View File

@ -0,0 +1,91 @@
#include "../picopass_i.h"
#include <dolphin/dolphin.h>
void picopass_scene_read_card_success_widget_callback(
GuiButtonType result,
InputType type,
void* context) {
furi_assert(context);
Picopass* picopass = context;
if(type == InputTypeShort) {
view_dispatcher_send_custom_event(picopass->view_dispatcher, result);
}
}
void picopass_scene_read_card_success_on_enter(void* context) {
Picopass* picopass = context;
string_t credential_str;
string_t wiegand_str;
string_init(credential_str);
string_init(wiegand_str);
DOLPHIN_DEED(DolphinDeedNfcReadSuccess);
// Send notification
notification_message(picopass->notifications, &sequence_success);
// Setup view
PicopassPacs* pacs = &picopass->dev->dev_data.pacs;
Widget* widget = picopass->widget;
string_set_str(credential_str, "");
for(uint8_t i = 0; i < RFAL_PICOPASS_MAX_BLOCK_LEN; i++) {
string_cat_printf(credential_str, " %02X", pacs->credential[i]);
}
if(pacs->record.valid) {
string_cat_printf(
wiegand_str, "FC: %03u CN: %05u", pacs->record.FacilityCode, pacs->record.CardNumber);
}
widget_add_button_element(
widget,
GuiButtonTypeLeft,
"Retry",
picopass_scene_read_card_success_widget_callback,
picopass);
widget_add_button_element(
widget,
GuiButtonTypeRight,
"More",
picopass_scene_read_card_success_widget_callback,
picopass);
if(pacs->record.valid) {
widget_add_string_element(
widget, 64, 12, AlignCenter, AlignCenter, FontPrimary, string_get_cstr(wiegand_str));
}
widget_add_string_element(
widget, 64, 32, AlignCenter, AlignCenter, FontSecondary, string_get_cstr(credential_str));
string_clear(credential_str);
string_clear(wiegand_str);
view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewWidget);
}
bool picopass_scene_read_card_success_on_event(void* context, SceneManagerEvent event) {
Picopass* picopass = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == GuiButtonTypeLeft) {
consumed = scene_manager_previous_scene(picopass->scene_manager);
} else if(event.event == GuiButtonTypeRight) {
// Clear device name
picopass_device_set_name(picopass->dev, "");
scene_manager_next_scene(picopass->scene_manager, PicopassSceneCardMenu);
consumed = true;
}
}
return consumed;
}
void picopass_scene_read_card_success_on_exit(void* context) {
Picopass* picopass = context;
// Clear view
widget_reset(picopass->widget);
}

View File

@ -0,0 +1,84 @@
#include "../picopass_i.h"
#include "m-string.h"
#include <lib/toolbox/random_name.h>
#include <gui/modules/validators.h>
#include <toolbox/path.h>
void picopass_scene_save_name_text_input_callback(void* context) {
Picopass* picopass = context;
view_dispatcher_send_custom_event(picopass->view_dispatcher, PicopassCustomEventTextInputDone);
}
void picopass_scene_save_name_on_enter(void* context) {
Picopass* picopass = context;
// Setup view
TextInput* text_input = picopass->text_input;
bool dev_name_empty = false;
if(!strcmp(picopass->dev->dev_name, "")) {
set_random_name(picopass->text_store, sizeof(picopass->text_store));
dev_name_empty = true;
} else {
picopass_text_store_set(picopass, picopass->dev->dev_name);
}
text_input_set_header_text(text_input, "Name the card");
text_input_set_result_callback(
text_input,
picopass_scene_save_name_text_input_callback,
picopass,
picopass->text_store,
PICOPASS_DEV_NAME_MAX_LEN,
dev_name_empty);
string_t folder_path;
string_init(folder_path);
if(string_end_with_str_p(picopass->dev->load_path, PICOPASS_APP_EXTENSION)) {
path_extract_dirname(string_get_cstr(picopass->dev->load_path), folder_path);
} else {
string_set_str(folder_path, PICOPASS_APP_FOLDER);
}
ValidatorIsFile* validator_is_file = validator_is_file_alloc_init(
string_get_cstr(folder_path), PICOPASS_APP_EXTENSION, picopass->dev->dev_name);
text_input_set_validator(text_input, validator_is_file_callback, validator_is_file);
view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewTextInput);
string_clear(folder_path);
}
bool picopass_scene_save_name_on_event(void* context, SceneManagerEvent event) {
Picopass* picopass = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == PicopassCustomEventTextInputDone) {
if(strcmp(picopass->dev->dev_name, "")) {
// picopass_device_delete(picopass->dev, true);
}
strlcpy(
picopass->dev->dev_name, picopass->text_store, strlen(picopass->text_store) + 1);
if(picopass_device_save(picopass->dev, picopass->text_store)) {
scene_manager_next_scene(picopass->scene_manager, PicopassSceneSaveSuccess);
consumed = true;
} else {
consumed = scene_manager_search_and_switch_to_previous_scene(
picopass->scene_manager, PicopassSceneStart);
}
}
}
return consumed;
}
void picopass_scene_save_name_on_exit(void* context) {
Picopass* picopass = context;
// Clear view
void* validator_context = text_input_get_validator_callback_context(picopass->text_input);
text_input_set_validator(picopass->text_input, NULL, NULL);
validator_is_file_free(validator_context);
text_input_reset(picopass->text_input);
}

View File

@ -0,0 +1,47 @@
#include "../picopass_i.h"
#include <dolphin/dolphin.h>
void picopass_scene_save_success_popup_callback(void* context) {
Picopass* picopass = context;
view_dispatcher_send_custom_event(picopass->view_dispatcher, PicopassCustomEventViewExit);
}
void picopass_scene_save_success_on_enter(void* context) {
Picopass* picopass = context;
DOLPHIN_DEED(DolphinDeedNfcSave);
// Setup view
Popup* popup = picopass->popup;
popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59);
popup_set_header(popup, "Saved!", 13, 22, AlignLeft, AlignBottom);
popup_set_timeout(popup, 1500);
popup_set_context(popup, picopass);
popup_set_callback(popup, picopass_scene_save_success_popup_callback);
popup_enable_timeout(popup);
view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewPopup);
}
bool picopass_scene_save_success_on_event(void* context, SceneManagerEvent event) {
Picopass* picopass = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == PicopassCustomEventViewExit) {
if(scene_manager_has_previous_scene(picopass->scene_manager, PicopassSceneCardMenu)) {
consumed = scene_manager_search_and_switch_to_previous_scene(
picopass->scene_manager, PicopassSceneCardMenu);
} else {
consumed = scene_manager_search_and_switch_to_previous_scene(
picopass->scene_manager, PicopassSceneStart);
}
}
}
return consumed;
}
void picopass_scene_save_success_on_exit(void* context) {
Picopass* picopass = context;
// Clear view
popup_reset(picopass->popup);
}

View File

@ -0,0 +1,35 @@
#include "../picopass_i.h"
void picopass_scene_saved_menu_submenu_callback(void* context, uint32_t index) {
Picopass* picopass = context;
view_dispatcher_send_custom_event(picopass->view_dispatcher, index);
}
void picopass_scene_saved_menu_on_enter(void* context) {
Picopass* picopass = context;
submenu_set_selected_item(
picopass->submenu,
scene_manager_get_scene_state(picopass->scene_manager, PicopassSceneSavedMenu));
view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewMenu);
}
bool picopass_scene_saved_menu_on_event(void* context, SceneManagerEvent event) {
Picopass* picopass = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
scene_manager_set_scene_state(
picopass->scene_manager, PicopassSceneSavedMenu, event.event);
}
return consumed;
}
void picopass_scene_saved_menu_on_exit(void* context) {
Picopass* picopass = context;
submenu_reset(picopass->submenu);
}

View File

@ -0,0 +1,45 @@
#include "../picopass_i.h"
enum SubmenuIndex {
SubmenuIndexRead,
SubmenuIndexRunScript,
SubmenuIndexSaved,
SubmenuIndexAddManualy,
SubmenuIndexDebug,
};
void picopass_scene_start_submenu_callback(void* context, uint32_t index) {
Picopass* picopass = context;
view_dispatcher_send_custom_event(picopass->view_dispatcher, index);
}
void picopass_scene_start_on_enter(void* context) {
Picopass* picopass = context;
Submenu* submenu = picopass->submenu;
submenu_add_item(
submenu, "Read Card", SubmenuIndexRead, picopass_scene_start_submenu_callback, picopass);
submenu_set_selected_item(
submenu, scene_manager_get_scene_state(picopass->scene_manager, PicopassSceneStart));
picopass_device_clear(picopass->dev);
view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewMenu);
}
bool picopass_scene_start_on_event(void* context, SceneManagerEvent event) {
Picopass* picopass = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == SubmenuIndexRead) {
scene_manager_next_scene(picopass->scene_manager, PicopassSceneReadCard);
consumed = true;
}
scene_manager_set_scene_state(picopass->scene_manager, PicopassSceneStart, event.event);
}
return consumed;
}
void picopass_scene_start_on_exit(void* context) {
Picopass* picopass = context;
submenu_reset(picopass->submenu);
}

View File

@ -45,13 +45,16 @@ static RpcSystemCallbacks rpc_systems[] = {
},
{
.alloc = rpc_system_app_alloc,
.free = NULL,
.free = rpc_system_app_free,
},
{
.alloc = rpc_system_gui_alloc,
.free = rpc_system_gui_free,
},
};
{
.alloc = rpc_system_gpio_alloc,
.free = NULL,
}};
struct RpcSession {
Rpc* rpc;

View File

@ -1,16 +1,39 @@
#include "cmsis_os2.h"
#include "flipper.pb.h"
#include "furi/record.h"
#include "rpc_i.h"
#include <furi.h>
#include <loader/loader.h>
#include "rpc_app.h"
#define TAG "RpcSystemApp"
#define APP_BUTTON_TIMEOUT 1000
struct RpcAppSystem {
RpcSession* session;
RpcAppSystemCallback app_callback;
void* app_context;
osTimerId_t timer;
};
static void rpc_system_app_timer_callback(void* context) {
furi_assert(context);
RpcAppSystem* rpc_app = context;
if(rpc_app->app_callback) {
rpc_app->app_callback(RpcAppEventButtonRelease, NULL, rpc_app->app_context);
}
}
static void rpc_system_app_start_process(const PB_Main* request, void* context) {
furi_assert(request);
furi_assert(context);
furi_assert(request->which_content == PB_Main_app_start_request_tag);
RpcSession* session = (RpcSession*)context;
RpcAppSystem* rpc_app = context;
RpcSession* session = rpc_app->session;
furi_assert(session);
char args_temp[16];
FURI_LOG_D(TAG, "Start");
@ -20,6 +43,11 @@ static void rpc_system_app_start_process(const PB_Main* request, void* context)
const char* app_name = request->content.app_start_request.name;
if(app_name) {
const char* app_args = request->content.app_start_request.args;
if(strcmp(app_args, "RPC") == 0) {
// If app is being started in RPC mode - pass RPC context via args string
snprintf(args_temp, 16, "RPC %08lX", (uint32_t)rpc_app);
app_args = args_temp;
}
LoaderStatus status = loader_start(loader, app_name, app_args);
if(status == LoaderStatusErrorAppStarted) {
result = PB_CommandStatus_ERROR_APP_SYSTEM_LOCKED;
@ -43,8 +71,11 @@ static void rpc_system_app_start_process(const PB_Main* request, void* context)
static void rpc_system_app_lock_status_process(const PB_Main* request, void* context) {
furi_assert(request);
furi_assert(context);
furi_assert(request->which_content == PB_Main_app_lock_status_request_tag);
RpcSession* session = (RpcSession*)context;
RpcAppSystem* rpc_app = context;
RpcSession* session = rpc_app->session;
furi_assert(session);
FURI_LOG_D(TAG, "LockStatus");
@ -66,13 +97,123 @@ static void rpc_system_app_lock_status_process(const PB_Main* request, void* con
pb_release(&PB_Main_msg, &response);
}
static void rpc_system_app_exit(const PB_Main* request, void* context) {
furi_assert(request);
furi_assert(context);
furi_assert(request->which_content == PB_Main_app_exit_request_tag);
RpcAppSystem* rpc_app = context;
RpcSession* session = rpc_app->session;
furi_assert(session);
PB_CommandStatus status;
if(rpc_app->app_callback) {
if(rpc_app->app_callback(RpcAppEventAppExit, NULL, rpc_app->app_context)) {
status = PB_CommandStatus_OK;
osTimerStop(rpc_app->timer);
} else {
status = PB_CommandStatus_ERROR_APP_CMD_ERROR;
}
} else {
status = PB_CommandStatus_ERROR_APP_NOT_RUNNING;
}
rpc_send_and_release_empty(session, request->command_id, status);
}
static void rpc_system_app_load_file(const PB_Main* request, void* context) {
furi_assert(request);
furi_assert(context);
furi_assert(request->which_content == PB_Main_app_load_file_request_tag);
RpcAppSystem* rpc_app = context;
RpcSession* session = rpc_app->session;
furi_assert(session);
PB_CommandStatus status;
if(rpc_app->app_callback) {
const char* file_path = request->content.app_load_file_request.path;
if(rpc_app->app_callback(RpcAppEventLoadFile, file_path, rpc_app->app_context)) {
status = PB_CommandStatus_OK;
} else {
status = PB_CommandStatus_ERROR_APP_CMD_ERROR;
}
} else {
status = PB_CommandStatus_ERROR_APP_NOT_RUNNING;
}
rpc_send_and_release_empty(session, request->command_id, status);
}
static void rpc_system_app_button_press(const PB_Main* request, void* context) {
furi_assert(request);
furi_assert(context);
furi_assert(request->which_content == PB_Main_app_button_press_request_tag);
RpcAppSystem* rpc_app = context;
RpcSession* session = rpc_app->session;
furi_assert(session);
PB_CommandStatus status;
if(rpc_app->app_callback) {
const char* args = request->content.app_button_press_request.args;
if(rpc_app->app_callback(RpcAppEventButtonPress, args, rpc_app->app_context)) {
status = PB_CommandStatus_OK;
osTimerStart(rpc_app->timer, APP_BUTTON_TIMEOUT);
} else {
status = PB_CommandStatus_ERROR_APP_CMD_ERROR;
}
} else {
status = PB_CommandStatus_ERROR_APP_NOT_RUNNING;
}
rpc_send_and_release_empty(session, request->command_id, status);
}
static void rpc_system_app_button_release(const PB_Main* request, void* context) {
furi_assert(request);
furi_assert(request->which_content == PB_Main_app_button_release_request_tag);
furi_assert(context);
RpcAppSystem* rpc_app = context;
RpcSession* session = rpc_app->session;
furi_assert(session);
PB_CommandStatus status;
if(rpc_app->app_callback) {
if(rpc_app->app_callback(RpcAppEventButtonRelease, NULL, rpc_app->app_context)) {
status = PB_CommandStatus_OK;
osTimerStop(rpc_app->timer);
} else {
status = PB_CommandStatus_ERROR_APP_CMD_ERROR;
}
} else {
status = PB_CommandStatus_ERROR_APP_NOT_RUNNING;
}
rpc_send_and_release_empty(session, request->command_id, status);
}
void rpc_system_app_set_callback(RpcAppSystem* rpc_app, RpcAppSystemCallback callback, void* ctx) {
furi_assert(rpc_app);
rpc_app->app_callback = callback;
rpc_app->app_context = ctx;
}
void* rpc_system_app_alloc(RpcSession* session) {
furi_assert(session);
RpcAppSystem* rpc_app = malloc(sizeof(RpcAppSystem));
rpc_app->session = session;
rpc_app->timer = osTimerNew(rpc_system_app_timer_callback, osTimerOnce, rpc_app, NULL);
RpcHandler rpc_handler = {
.message_handler = NULL,
.decode_submessage = NULL,
.context = session,
.context = rpc_app,
};
rpc_handler.message_handler = rpc_system_app_start_process;
@ -81,5 +222,31 @@ void* rpc_system_app_alloc(RpcSession* session) {
rpc_handler.message_handler = rpc_system_app_lock_status_process;
rpc_add_handler(session, PB_Main_app_lock_status_request_tag, &rpc_handler);
return NULL;
rpc_handler.message_handler = rpc_system_app_exit;
rpc_add_handler(session, PB_Main_app_exit_request_tag, &rpc_handler);
rpc_handler.message_handler = rpc_system_app_load_file;
rpc_add_handler(session, PB_Main_app_load_file_request_tag, &rpc_handler);
rpc_handler.message_handler = rpc_system_app_button_press;
rpc_add_handler(session, PB_Main_app_button_press_request_tag, &rpc_handler);
rpc_handler.message_handler = rpc_system_app_button_release;
rpc_add_handler(session, PB_Main_app_button_release_request_tag, &rpc_handler);
return rpc_app;
}
void rpc_system_app_free(void* context) {
RpcAppSystem* rpc_app = context;
RpcSession* session = rpc_app->session;
furi_assert(session);
osTimerDelete(rpc_app->timer);
if(rpc_app->app_callback) {
rpc_app->app_callback(RpcAppEventSessionClose, NULL, rpc_app->app_context);
}
free(rpc_app);
}

View File

@ -0,0 +1,24 @@
#pragma once
#include "rpc.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef enum {
RpcAppEventSessionClose,
RpcAppEventAppExit,
RpcAppEventLoadFile,
RpcAppEventButtonPress,
RpcAppEventButtonRelease,
} RpcAppSystemEvent;
typedef bool (*RpcAppSystemCallback)(RpcAppSystemEvent event, const char* arg, void* context);
typedef struct RpcAppSystem RpcAppSystem;
void rpc_system_app_set_callback(RpcAppSystem* rpc_app, RpcAppSystemCallback callback, void* ctx);
#ifdef __cplusplus
}
#endif

221
applications/rpc/rpc_gpio.c Normal file
View File

@ -0,0 +1,221 @@
#include "flipper.pb.h"
#include "rpc_i.h"
#include "gpio.pb.h"
#include <furi_hal_gpio.h>
#include <furi_hal_resources.h>
static const GpioPin* rpc_pin_to_hal_pin(PB_Gpio_GpioPin rpc_pin) {
switch(rpc_pin) {
case PB_Gpio_GpioPin_PC0:
return &gpio_ext_pc0;
case PB_Gpio_GpioPin_PC1:
return &gpio_ext_pc1;
case PB_Gpio_GpioPin_PC3:
return &gpio_ext_pc3;
case PB_Gpio_GpioPin_PB2:
return &gpio_ext_pb2;
case PB_Gpio_GpioPin_PB3:
return &gpio_ext_pb3;
case PB_Gpio_GpioPin_PA4:
return &gpio_ext_pa4;
case PB_Gpio_GpioPin_PA6:
return &gpio_ext_pa6;
case PB_Gpio_GpioPin_PA7:
return &gpio_ext_pa7;
}
__builtin_unreachable();
}
static GpioMode rpc_mode_to_hal_mode(PB_Gpio_GpioPinMode rpc_mode) {
switch(rpc_mode) {
case PB_Gpio_GpioPinMode_OUTPUT:
return GpioModeOutputPushPull;
case PB_Gpio_GpioPinMode_INPUT:
return GpioModeInput;
}
__builtin_unreachable();
}
static GpioPull rpc_pull_mode_to_hall_pull_mode(PB_Gpio_GpioInputPull pull_mode) {
switch(pull_mode) {
case PB_Gpio_GpioInputPull_UP:
return GpioPullUp;
case PB_Gpio_GpioInputPull_DOWN:
return GpioPullDown;
case PB_Gpio_GpioInputPull_NO:
return GpioPullNo;
}
__builtin_unreachable();
}
static void rpc_system_gpio_set_pin_mode(const PB_Main* request, void* context) {
furi_assert(request);
furi_assert(context);
furi_assert(request->which_content == PB_Main_gpio_set_pin_mode_tag);
RpcSession* session = context;
furi_assert(session);
PB_Gpio_SetPinMode cmd = request->content.gpio_set_pin_mode;
const GpioPin* pin = rpc_pin_to_hal_pin(cmd.pin);
GpioMode mode = rpc_mode_to_hal_mode(cmd.mode);
furi_hal_gpio_init_simple(pin, mode);
if(mode == GpioModeOutputPushPull) {
furi_hal_gpio_write(pin, false);
}
rpc_send_and_release_empty(session, request->command_id, PB_CommandStatus_OK);
}
static void rpc_system_gpio_write_pin(const PB_Main* request, void* context) {
furi_assert(request);
furi_assert(context);
furi_assert(request->which_content == PB_Main_gpio_write_pin_tag);
RpcSession* session = context;
furi_assert(session);
PB_Gpio_WritePin cmd = request->content.gpio_write_pin;
const GpioPin* pin = rpc_pin_to_hal_pin(cmd.pin);
uint8_t value = !!(cmd.value);
PB_Main* response = malloc(sizeof(PB_Main));
response->command_id = request->command_id;
response->has_next = false;
if(LL_GPIO_MODE_OUTPUT != LL_GPIO_GetPinMode(pin->port, pin->pin)) {
response->command_status = PB_CommandStatus_ERROR_GPIO_MODE_INCORRECT;
} else {
response->command_status = PB_CommandStatus_OK;
furi_hal_gpio_write(pin, value);
}
rpc_send_and_release(session, response);
free(response);
}
static void rpc_system_gpio_read_pin(const PB_Main* request, void* context) {
furi_assert(request);
furi_assert(context);
furi_assert(request->which_content == PB_Main_gpio_read_pin_tag);
RpcSession* session = context;
furi_assert(session);
PB_Gpio_ReadPin cmd = request->content.gpio_read_pin;
const GpioPin* pin = rpc_pin_to_hal_pin(cmd.pin);
PB_Main* response = malloc(sizeof(PB_Main));
response->command_id = request->command_id;
response->has_next = false;
if(LL_GPIO_MODE_INPUT != LL_GPIO_GetPinMode(pin->port, pin->pin)) {
response->command_status = PB_CommandStatus_ERROR_GPIO_MODE_INCORRECT;
} else {
response->command_status = PB_CommandStatus_OK;
response->which_content = PB_Main_gpio_read_pin_response_tag;
response->content.gpio_read_pin_response.value = !!furi_hal_gpio_read(pin);
}
rpc_send_and_release(session, response);
free(response);
}
void rpc_system_gpio_get_pin_mode(const PB_Main* request, void* context) {
furi_assert(request);
furi_assert(context);
furi_assert(request->which_content == PB_Main_gpio_get_pin_mode_tag);
RpcSession* session = context;
furi_assert(session);
PB_Gpio_GetPinMode cmd = request->content.gpio_get_pin_mode;
const GpioPin* pin = rpc_pin_to_hal_pin(cmd.pin);
PB_Main* response = malloc(sizeof(PB_Main));
response->command_id = request->command_id;
response->has_next = false;
uint32_t raw_pin_mode = LL_GPIO_GetPinMode(pin->port, pin->pin);
PB_Gpio_GpioPinMode pin_mode;
if(LL_GPIO_MODE_INPUT == raw_pin_mode) {
pin_mode = PB_Gpio_GpioPinMode_INPUT;
response->command_status = PB_CommandStatus_OK;
} else if(LL_GPIO_MODE_OUTPUT == raw_pin_mode) {
pin_mode = PB_Gpio_GpioPinMode_OUTPUT;
response->command_status = PB_CommandStatus_OK;
} else {
pin_mode = PB_Gpio_GpioPinMode_INPUT;
response->command_status = PB_CommandStatus_ERROR_GPIO_UNKNOWN_PIN_MODE;
}
response->which_content = PB_Main_gpio_get_pin_mode_response_tag;
response->content.gpio_get_pin_mode_response.mode = pin_mode;
rpc_send_and_release(session, response);
free(response);
}
void rpc_system_gpio_set_input_pull(const PB_Main* request, void* context) {
furi_assert(request);
furi_assert(context);
furi_assert(request->which_content == PB_Main_gpio_set_input_pull_tag);
RpcSession* session = context;
furi_assert(session);
PB_Gpio_SetInputPull cmd = request->content.gpio_set_input_pull;
const GpioPin* pin = rpc_pin_to_hal_pin(cmd.pin);
const GpioPull pull_mode = rpc_pull_mode_to_hall_pull_mode(cmd.pull_mode);
PB_Main* response = malloc(sizeof(PB_Main));
response->command_id = request->command_id;
response->has_next = false;
PB_CommandStatus status;
if(LL_GPIO_MODE_INPUT != LL_GPIO_GetPinMode(pin->port, pin->pin)) {
status = PB_CommandStatus_ERROR_GPIO_MODE_INCORRECT;
} else {
status = PB_CommandStatus_OK;
furi_hal_gpio_init(pin, GpioModeInput, pull_mode, GpioSpeedLow);
}
rpc_send_and_release_empty(session, request->command_id, status);
free(response);
}
void* rpc_system_gpio_alloc(RpcSession* session) {
furi_assert(session);
RpcHandler rpc_handler = {
.message_handler = NULL,
.decode_submessage = NULL,
.context = session,
};
rpc_handler.message_handler = rpc_system_gpio_set_pin_mode;
rpc_add_handler(session, PB_Main_gpio_set_pin_mode_tag, &rpc_handler);
rpc_handler.message_handler = rpc_system_gpio_write_pin;
rpc_add_handler(session, PB_Main_gpio_write_pin_tag, &rpc_handler);
rpc_handler.message_handler = rpc_system_gpio_read_pin;
rpc_add_handler(session, PB_Main_gpio_read_pin_tag, &rpc_handler);
rpc_handler.message_handler = rpc_system_gpio_get_pin_mode;
rpc_add_handler(session, PB_Main_gpio_get_pin_mode_tag, &rpc_handler);
rpc_handler.message_handler = rpc_system_gpio_set_input_pull;
rpc_add_handler(session, PB_Main_gpio_set_input_pull_tag, &rpc_handler);
return NULL;
}

View File

@ -29,8 +29,11 @@ void* rpc_system_system_alloc(RpcSession* session);
void* rpc_system_storage_alloc(RpcSession* session);
void rpc_system_storage_free(void* ctx);
void* rpc_system_app_alloc(RpcSession* session);
void rpc_system_app_free(void* ctx);
void* rpc_system_gui_alloc(RpcSession* session);
void rpc_system_gui_free(void* ctx);
void* rpc_system_gpio_alloc(RpcSession* session);
void rpc_system_gpio_free(void* ctx);
void rpc_print_message(const PB_Main* message);
void rpc_cli_command_start_session(Cli* cli, string_t args, void* context);

View File

@ -277,10 +277,6 @@ static void rpc_system_system_update_request_process(const PB_Main* request, voi
UpdatePrepareResult update_prepare_result =
update_operation_prepare(request->content.system_update_request.update_manifest);
/* RPC enum does not have such entry; setting to closest one */
if(update_prepare_result == UpdatePrepareResultOutdatedManifestVersion) {
update_prepare_result = UpdatePrepareResultManifestInvalid;
}
PB_Main* response = malloc(sizeof(PB_Main));
response->command_id = request->command_id;

View File

@ -29,6 +29,8 @@ typedef enum {
GameStateGameOver,
} GameState;
// Note: do not change without purpose. Current values are used in smart
// orthogonality calculation in `snake_game_get_turn_snake`.
typedef enum {
DirectionUp,
DirectionRight,
@ -195,44 +197,9 @@ static bool
}
static Direction snake_game_get_turn_snake(SnakeState const* const snake_state) {
switch(snake_state->currentMovement) {
case DirectionUp:
switch(snake_state->nextMovement) {
case DirectionRight:
return DirectionRight;
case DirectionLeft:
return DirectionLeft;
default:
return snake_state->currentMovement;
}
case DirectionRight:
switch(snake_state->nextMovement) {
case DirectionUp:
return DirectionUp;
case DirectionDown:
return DirectionDown;
default:
return snake_state->currentMovement;
}
case DirectionDown:
switch(snake_state->nextMovement) {
case DirectionRight:
return DirectionRight;
case DirectionLeft:
return DirectionLeft;
default:
return snake_state->currentMovement;
}
default: // case DirectionLeft:
switch(snake_state->nextMovement) {
case DirectionUp:
return DirectionUp;
case DirectionDown:
return DirectionDown;
default:
return snake_state->currentMovement;
}
}
// Sum of two `Direction` lies between 0 and 6, odd values indicate orthogonality.
bool is_orthogonal = (snake_state->currentMovement + snake_state->nextMovement) % 2 == 1;
return is_orthogonal ? snake_state->nextMovement : snake_state->currentMovement;
}
static Point snake_game_get_next_step(SnakeState const* const snake_state) {

View File

@ -4,6 +4,7 @@
#define TAG "StorageInt"
#define STORAGE_PATH "/int"
#define LFS_CLEAN_FINGERPRINT 0
typedef struct {
const size_t start_address;
@ -162,8 +163,9 @@ static LFSData* storage_int_lfs_data_alloc() {
return lfs_data;
};
static bool storage_int_is_fingerprint_valid(LFSData* lfs_data) {
bool value = true;
// Returns true if fingerprint was invalid and LFS reformatting is needed
static bool storage_int_check_and_set_fingerprint(LFSData* lfs_data) {
bool value = false;
uint32_t os_fingerprint = 0;
os_fingerprint |= ((lfs_data->start_page & 0xFF) << 0);
@ -171,13 +173,13 @@ static bool storage_int_is_fingerprint_valid(LFSData* lfs_data) {
os_fingerprint |= ((LFS_DISK_VERSION_MAJOR & 0xFFFF) << 16);
uint32_t rtc_fingerprint = furi_hal_rtc_get_register(FuriHalRtcRegisterLfsFingerprint);
if(rtc_fingerprint == 0) {
if(rtc_fingerprint == LFS_CLEAN_FINGERPRINT) {
FURI_LOG_I(TAG, "Storing LFS fingerprint in RTC");
furi_hal_rtc_set_register(FuriHalRtcRegisterLfsFingerprint, os_fingerprint);
} else if(rtc_fingerprint != os_fingerprint) {
FURI_LOG_E(TAG, "LFS fingerprint mismatch");
furi_hal_rtc_set_register(FuriHalRtcRegisterLfsFingerprint, os_fingerprint);
value = false;
value = true;
}
return value;
@ -187,8 +189,9 @@ static void storage_int_lfs_mount(LFSData* lfs_data, StorageData* storage) {
int err;
lfs_t* lfs = &lfs_data->lfs;
bool was_fingerprint_outdated = storage_int_check_and_set_fingerprint(lfs_data);
bool need_format = furi_hal_rtc_is_flag_set(FuriHalRtcFlagFactoryReset) ||
!storage_int_is_fingerprint_valid(lfs_data);
was_fingerprint_outdated;
if(need_format) {
// Format storage
@ -655,11 +658,13 @@ static FS_Error storage_int_common_fs_info(
lfs_t* lfs = lfs_get_from_storage(storage);
LFSData* lfs_data = lfs_data_get_from_storage(storage);
if(total_space) {
*total_space = lfs_data->config.block_size * lfs_data->config.block_count;
}
lfs_ssize_t result = lfs_fs_size(lfs);
if(result >= 0) {
*free_space = *total_space - (result * lfs_data->config.block_size);
if(free_space && (result >= 0)) {
*free_space = (lfs_data->config.block_count - result) * lfs_data->config.block_size;
}
return storage_int_parse_error(result);

View File

@ -41,6 +41,7 @@ typedef enum {
SubGhzCustomEventSceneShowOnlyRX,
SubGhzCustomEventSceneAnalyzerLock,
SubGhzCustomEventSceneAnalyzerUnlock,
SubGhzCustomEventSceneSettingLock,
SubGhzCustomEventSceneExit,
SubGhzCustomEventSceneStay,
@ -48,6 +49,8 @@ typedef enum {
SubGhzCustomEventViewReceiverOK,
SubGhzCustomEventViewReceiverConfig,
SubGhzCustomEventViewReceiverBack,
SubGhzCustomEventViewReceiverOffDisplay,
SubGhzCustomEventViewReceiverUnlock,
SubGhzCustomEventViewReadRAWBack,
SubGhzCustomEventViewReadRAWIDLE,

View File

@ -0,0 +1,69 @@
#pragma once
/** SubGhzNotification state */
typedef enum {
SubGhzNotificationStateStarting,
SubGhzNotificationStateIDLE,
SubGhzNotificationStateTx,
SubGhzNotificationStateRx,
SubGhzNotificationStateRxDone,
} SubGhzNotificationState;
/** SubGhzTxRx state */
typedef enum {
SubGhzTxRxStateIDLE,
SubGhzTxRxStateRx,
SubGhzTxRxStateTx,
SubGhzTxRxStateSleep,
} SubGhzTxRxState;
/** SubGhzHopperState state */
typedef enum {
SubGhzHopperStateOFF,
SubGhzHopperStateRunnig,
SubGhzHopperStatePause,
SubGhzHopperStateRSSITimeOut,
} SubGhzHopperState;
/** SubGhzRxKeyState state */
typedef enum {
SubGhzRxKeyStateIDLE,
SubGhzRxKeyStateNoSave,
SubGhzRxKeyStateNeedSave,
SubGhzRxKeyStateBack,
SubGhzRxKeyStateStart,
SubGhzRxKeyStateAddKey,
SubGhzRxKeyStateExit,
SubGhzRxKeyStateRAWLoad,
SubGhzRxKeyStateRAWSave,
} SubGhzRxKeyState;
/** SubGhzLoadKeyState state */
typedef enum {
SubGhzLoadKeyStateUnknown,
SubGhzLoadKeyStateOK,
SubGhzLoadKeyStateParseErr,
SubGhzLoadKeyStateOnlyRx,
} SubGhzLoadKeyState;
/** SubGhzLock */
typedef enum {
SubGhzLockOff,
SubGhzLockOn,
} SubGhzLock;
typedef enum {
SubGhzViewIdMenu,
SubGhzViewIdReceiver,
SubGhzViewIdPopup,
SubGhzViewIdTextInput,
SubGhzViewIdWidget,
SubGhzViewIdTransmitter,
SubGhzViewIdVariableItemList,
SubGhzViewIdFrequencyAnalyzer,
SubGhzViewIdReadRAW,
SubGhzViewIdStatic,
SubGhzViewIdTestCarrier,
SubGhzViewIdTestPacket,
} SubGhzViewId;

View File

@ -22,3 +22,4 @@ ADD_SCENE(subghz, read_raw, ReadRAW)
ADD_SCENE(subghz, more_raw, MoreRAW)
ADD_SCENE(subghz, delete_raw, DeleteRAW)
ADD_SCENE(subghz, need_saving, NeedSaving)
ADD_SCENE(subghz, rpc, Rpc)

View File

@ -106,6 +106,7 @@ void subghz_scene_read_raw_on_enter(void* context) {
bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) {
SubGhz* subghz = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
switch(event.event) {
case SubGhzCustomEventViewReadRAWBack:
@ -141,7 +142,7 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) {
}
}
}
return true;
consumed = true;
break;
case SubGhzCustomEventViewReadRAWTXRXStop:
@ -156,14 +157,14 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) {
subghz_sleep(subghz);
};
subghz->state_notifications = SubGhzNotificationStateIDLE;
return true;
consumed = true;
break;
case SubGhzCustomEventViewReadRAWConfig:
scene_manager_set_scene_state(
subghz->scene_manager, SubGhzSceneReadRAW, SubGhzCustomEventManagerSet);
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReceiverConfig);
return true;
consumed = true;
break;
case SubGhzCustomEventViewReadRAWErase:
@ -175,7 +176,7 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) {
}
subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE;
notification_message(subghz->notifications, &sequence_reset_rgb);
return true;
consumed = true;
break;
case SubGhzCustomEventViewReadRAWMore:
@ -184,7 +185,7 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) {
subghz->scene_manager, SubGhzSceneReadRAW, SubGhzCustomEventManagerSet);
subghz->txrx->rx_key_state = SubGhzRxKeyStateRAWLoad;
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneMoreRAW);
return true;
consumed = true;
} else {
furi_crash("SubGhz: RAW file name update error.");
}
@ -214,7 +215,7 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) {
}
}
}
return true;
consumed = true;
break;
case SubGhzCustomEventViewReadRAWSendStop:
@ -224,7 +225,7 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) {
subghz_sleep(subghz);
}
subghz_read_raw_stop_send(subghz->subghz_read_raw);
return true;
consumed = true;
break;
case SubGhzCustomEventViewReadRAWIDLE:
@ -255,7 +256,7 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) {
subghz->state_notifications = SubGhzNotificationStateIDLE;
subghz->txrx->rx_key_state = SubGhzRxKeyStateAddKey;
return true;
consumed = true;
break;
case SubGhzCustomEventViewReadRAWREC:
@ -281,7 +282,7 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) {
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError);
}
}
return true;
consumed = true;
break;
case SubGhzCustomEventViewReadRAWSave:
@ -291,7 +292,7 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) {
subghz->txrx->rx_key_state = SubGhzRxKeyStateBack;
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveName);
}
return true;
consumed = true;
break;
default:
@ -315,7 +316,7 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) {
break;
}
}
return false;
return consumed;
}
void subghz_scene_read_raw_on_exit(void* context) {

View File

@ -14,6 +14,23 @@ static const NotificationSequence subghs_sequence_rx = {
NULL,
};
static const NotificationSequence subghs_sequence_rx_locked = {
&message_green_255,
&message_display_backlight_on,
&message_vibro_on,
&message_note_c6,
&message_delay_50,
&message_sound_off,
&message_vibro_off,
&message_delay_500,
&message_display_backlight_off,
NULL,
};
static void subghz_scene_receiver_update_statusbar(void* context) {
SubGhz* subghz = context;
string_t history_stat_str;
@ -92,6 +109,8 @@ void subghz_scene_receiver_on_enter(void* context) {
subghz->txrx->rx_key_state = SubGhzRxKeyStateStart;
}
subghz_view_receiver_set_lock(subghz->subghz_receiver, subghz->lock);
//Load history to receiver
subghz_view_receiver_exit(subghz->subghz_receiver);
for(uint8_t i = 0; i < subghz_history_get_item(subghz->txrx->history); i++) {
@ -126,11 +145,10 @@ void subghz_scene_receiver_on_enter(void* context) {
bool subghz_scene_receiver_on_event(void* context, SceneManagerEvent event) {
SubGhz* subghz = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
switch(event.event) {
case SubGhzCustomEventViewReceiverBack:
// Stop CC1101 Rx
subghz->state_notifications = SubGhzNotificationStateIDLE;
if(subghz->txrx->txrx_state == SubGhzTxRxStateRx) {
@ -151,20 +169,28 @@ bool subghz_scene_receiver_on_event(void* context, SceneManagerEvent event) {
scene_manager_search_and_switch_to_previous_scene(
subghz->scene_manager, SubGhzSceneStart);
}
return true;
consumed = true;
break;
case SubGhzCustomEventViewReceiverOK:
subghz->txrx->idx_menu_chosen =
subghz_view_receiver_get_idx_menu(subghz->subghz_receiver);
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReceiverInfo);
return true;
consumed = true;
break;
case SubGhzCustomEventViewReceiverConfig:
subghz->state_notifications = SubGhzNotificationStateIDLE;
subghz->txrx->idx_menu_chosen =
subghz_view_receiver_get_idx_menu(subghz->subghz_receiver);
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReceiverConfig);
return true;
consumed = true;
break;
case SubGhzCustomEventViewReceiverOffDisplay:
notification_message(subghz->notifications, &sequence_display_backlight_off);
consumed = true;
break;
case SubGhzCustomEventViewReceiverUnlock:
subghz->lock = SubGhzLockOff;
consumed = true;
break;
default:
break;
@ -174,20 +200,23 @@ bool subghz_scene_receiver_on_event(void* context, SceneManagerEvent event) {
subghz_hopper_update(subghz);
subghz_scene_receiver_update_statusbar(subghz);
}
switch(subghz->state_notifications) {
case SubGhzNotificationStateRx:
notification_message(subghz->notifications, &sequence_blink_cyan_10);
break;
case SubGhzNotificationStateRxDone:
if(subghz->lock != SubGhzLockOn) {
notification_message(subghz->notifications, &subghs_sequence_rx);
} else {
notification_message(subghz->notifications, &subghs_sequence_rx_locked);
}
subghz->state_notifications = SubGhzNotificationStateRx;
break;
default:
break;
}
}
return false;
return consumed;
}
void subghz_scene_receiver_on_exit(void* context) {

View File

@ -1,5 +1,12 @@
#include "../subghz_i.h"
enum SubGhzSettingIndex {
SubGhzSettingIndexFrequency,
SubGhzSettingIndexHopping,
SubGhzSettingIndexModulation,
SubGhzSettingIndexLock,
};
#define PRESET_COUNT 4
const char* const preset_text[PRESET_COUNT] = {
"AM270",
@ -137,6 +144,15 @@ static void subghz_scene_receiver_config_set_hopping_runing(VariableItem* item)
subghz->txrx->hopper_state = hopping_value[index];
}
static void subghz_scene_receiver_config_var_list_enter_callback(void* context, uint32_t index) {
furi_assert(context);
SubGhz* subghz = context;
if(index == SubGhzSettingIndexLock) {
view_dispatcher_send_custom_event(
subghz->view_dispatcher, SubGhzCustomEventSceneSettingLock);
}
}
void subghz_scene_receiver_config_on_enter(void* context) {
SubGhz* subghz = context;
VariableItem* item;
@ -185,13 +201,29 @@ void subghz_scene_receiver_config_on_enter(void* context) {
variable_item_set_current_value_index(item, value_index);
variable_item_set_current_value_text(item, preset_text[value_index]);
if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneReadRAW) !=
SubGhzCustomEventManagerSet) {
variable_item_list_add(subghz->variable_item_list, "Lock Keyboard", 1, NULL, NULL);
variable_item_list_set_enter_callback(
subghz->variable_item_list,
subghz_scene_receiver_config_var_list_enter_callback,
subghz);
}
view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdVariableItemList);
}
bool subghz_scene_receiver_config_on_event(void* context, SceneManagerEvent event) {
UNUSED(context);
UNUSED(event);
return false;
SubGhz* subghz = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == SubGhzCustomEventSceneSettingLock) {
subghz->lock = SubGhzLockOn;
scene_manager_previous_scene(subghz->scene_manager);
consumed = true;
}
}
return consumed;
}
void subghz_scene_receiver_config_on_exit(void* context) {

View File

@ -0,0 +1,34 @@
#include "../subghz_i.h"
void subghz_scene_rpc_on_enter(void* context) {
SubGhz* subghz = context;
Widget* widget = subghz->widget;
widget_add_text_box_element(
widget, 0, 0, 128, 28, AlignCenter, AlignCenter, "RPC mode", false);
notification_message(subghz->notifications, &sequence_display_backlight_on);
view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdWidget);
}
bool subghz_scene_rpc_on_event(void* context, SceneManagerEvent event) {
SubGhz* subghz = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
consumed = true;
if(event.event == SubGhzCustomEventSceneExit) {
view_dispatcher_stop(subghz->view_dispatcher);
}
}
return consumed;
}
void subghz_scene_rpc_on_exit(void* context) {
SubGhz* subghz = context;
//subghz_rpc_exit_callback(subghz);
widget_reset(subghz->widget);
}

View File

@ -23,6 +23,54 @@ void subghz_tick_event_callback(void* context) {
scene_manager_handle_tick_event(subghz->scene_manager);
}
static bool subghz_rpc_command_callback(RpcAppSystemEvent event, const char* arg, void* context) {
furi_assert(context);
SubGhz* subghz = context;
if(!subghz->rpc_ctx) {
return false;
}
bool result = false;
if(event == RpcAppEventSessionClose) {
rpc_system_app_set_callback(subghz->rpc_ctx, NULL, NULL);
subghz->rpc_ctx = NULL;
view_dispatcher_send_custom_event(subghz->view_dispatcher, SubGhzCustomEventSceneExit);
if(subghz->txrx->txrx_state == SubGhzTxRxStateTx) {
subghz_tx_stop(subghz);
subghz_sleep(subghz);
}
result = true;
} else if(event == RpcAppEventAppExit) {
view_dispatcher_send_custom_event(subghz->view_dispatcher, SubGhzCustomEventSceneExit);
if(subghz->txrx->txrx_state == SubGhzTxRxStateTx) {
subghz_tx_stop(subghz);
subghz_sleep(subghz);
}
result = true;
} else if(event == RpcAppEventLoadFile) {
if(arg) {
if(subghz_key_load(subghz, arg, false)) {
string_set_str(subghz->file_path, arg);
result = true;
}
}
} else if(event == RpcAppEventButtonPress) {
if(subghz->txrx->txrx_state == SubGhzTxRxStateSleep) {
result = subghz_tx_start(subghz, subghz->txrx->fff_data);
}
} else if(event == RpcAppEventButtonRelease) {
if(subghz->txrx->txrx_state == SubGhzTxRxStateTx) {
subghz_tx_stop(subghz);
subghz_sleep(subghz);
result = true;
}
}
return result;
}
SubGhz* subghz_alloc() {
SubGhz* subghz = malloc(sizeof(SubGhz));
@ -133,7 +181,8 @@ SubGhz* subghz_alloc() {
subghz->setting = subghz_setting_alloc();
subghz_setting_load(subghz->setting, "/ext/subghz/assets/setting_user");
//init Worker & Protocol & History
//init Worker & Protocol & History & KeyBoard
subghz->lock = SubGhzLockOff;
subghz->txrx = malloc(sizeof(SubGhzTxRx));
subghz->txrx->frequency = subghz_setting_get_default_frequency(subghz->setting);
subghz->txrx->preset = FuriHalSubGhzPresetOok650Async;
@ -167,6 +216,11 @@ SubGhz* subghz_alloc() {
void subghz_free(SubGhz* subghz) {
furi_assert(subghz);
if(subghz->rpc_ctx) {
rpc_system_app_set_callback(subghz->rpc_ctx, NULL, NULL);
subghz->rpc_ctx = NULL;
}
// Packet Test
view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdTestPacket);
subghz_test_packet_free(subghz->subghz_test_packet);
@ -264,7 +318,12 @@ int32_t subghz_app(void* p) {
subghz->txrx->environment, "/ext/subghz/assets/keeloq_mfcodes_user");
// Check argument and run corresponding scene
if(p) {
if(subghz_key_load(subghz, p)) {
uint32_t rpc_ctx = 0;
if(sscanf(p, "RPC %lX", &rpc_ctx) == 1) {
subghz->rpc_ctx = (void*)rpc_ctx;
rpc_system_app_set_callback(subghz->rpc_ctx, subghz_rpc_command_callback, subghz);
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneRpc);
} else if(subghz_key_load(subghz, p, true)) {
string_set_str(subghz->file_path, p);
if((!strcmp(subghz->txrx->decoder_result->protocol->name, "RAW"))) {

View File

@ -218,7 +218,7 @@ void subghz_dialog_message_show_only_rx(SubGhz* subghz) {
dialog_message_free(message);
}
bool subghz_key_load(SubGhz* subghz, const char* file_path) {
bool subghz_key_load(SubGhz* subghz, const char* file_path, bool show_dialog) {
furi_assert(subghz);
furi_assert(file_path);
@ -308,11 +308,15 @@ bool subghz_key_load(SubGhz* subghz, const char* file_path) {
switch(load_key_state) {
case SubGhzLoadKeyStateParseErr:
if(show_dialog) {
dialog_message_show_storage_error(subghz->dialogs, "Cannot parse\nfile");
}
return false;
case SubGhzLoadKeyStateOnlyRx:
if(show_dialog) {
subghz_dialog_message_show_only_rx(subghz);
}
return false;
case SubGhzLoadKeyStateOK:
@ -427,7 +431,7 @@ bool subghz_load_protocol_from_file(SubGhz* subghz) {
true);
if(res) {
res = subghz_key_load(subghz, string_get_cstr(subghz->file_path));
res = subghz_key_load(subghz, string_get_cstr(subghz->file_path), true);
}
string_clear(file_path);

View File

@ -1,5 +1,6 @@
#pragma once
#include "helpers/subghz_types.h"
#include "subghz.h"
#include "views/receiver.h"
#include "views/transmitter.h"
@ -35,54 +36,10 @@
#include <gui/modules/variable_item_list.h>
#include <lib/toolbox/path.h>
#include "rpc/rpc_app.h"
#define SUBGHZ_MAX_LEN_NAME 64
/** SubGhzNotification state */
typedef enum {
SubGhzNotificationStateStarting,
SubGhzNotificationStateIDLE,
SubGhzNotificationStateTx,
SubGhzNotificationStateRx,
SubGhzNotificationStateRxDone,
} SubGhzNotificationState;
/** SubGhzTxRx state */
typedef enum {
SubGhzTxRxStateIDLE,
SubGhzTxRxStateRx,
SubGhzTxRxStateTx,
SubGhzTxRxStateSleep,
} SubGhzTxRxState;
/** SubGhzHopperState state */
typedef enum {
SubGhzHopperStateOFF,
SubGhzHopperStateRunnig,
SubGhzHopperStatePause,
SubGhzHopperStateRSSITimeOut,
} SubGhzHopperState;
/** SubGhzRxKeyState state */
typedef enum {
SubGhzRxKeyStateIDLE,
SubGhzRxKeyStateNoSave,
SubGhzRxKeyStateNeedSave,
SubGhzRxKeyStateBack,
SubGhzRxKeyStateStart,
SubGhzRxKeyStateAddKey,
SubGhzRxKeyStateExit,
SubGhzRxKeyStateRAWLoad,
SubGhzRxKeyStateRAWSave,
} SubGhzRxKeyState;
/** SubGhzLoadKeyState state */
typedef enum {
SubGhzLoadKeyStateUnknown,
SubGhzLoadKeyStateOK,
SubGhzLoadKeyStateParseErr,
SubGhzLoadKeyStateOnlyRx,
} SubGhzLoadKeyState;
struct SubGhzTxRx {
SubGhzWorker* worker;
@ -135,24 +92,11 @@ struct SubGhz {
SubGhzTestPacket* subghz_test_packet;
string_t error_str;
SubGhzSetting* setting;
SubGhzLock lock;
void* rpc_ctx;
};
typedef enum {
SubGhzViewIdMenu,
SubGhzViewIdReceiver,
SubGhzViewIdPopup,
SubGhzViewIdTextInput,
SubGhzViewIdWidget,
SubGhzViewIdTransmitter,
SubGhzViewIdVariableItemList,
SubGhzViewIdFrequencyAnalyzer,
SubGhzViewIdReadRAW,
SubGhzViewIdStatic,
SubGhzViewIdTestCarrier,
SubGhzViewIdTestPacket,
} SubGhzViewId;
bool subghz_set_preset(SubGhz* subghz, const char* preset);
void subghz_get_frequency_modulation(SubGhz* subghz, string_t frequency, string_t modulation);
void subghz_begin(SubGhz* subghz, FuriHalSubGhzPreset preset);
@ -162,7 +106,7 @@ void subghz_sleep(SubGhz* subghz);
bool subghz_tx_start(SubGhz* subghz, FlipperFormat* flipper_format);
void subghz_tx_stop(SubGhz* subghz);
void subghz_dialog_message_show_only_rx(SubGhz* subghz);
bool subghz_key_load(SubGhz* subghz, const char* file_path);
bool subghz_key_load(SubGhz* subghz, const char* file_path, bool show_dialog);
bool subghz_get_next_name_file(SubGhz* subghz, uint8_t max_len);
bool subghz_save_protocol_to_file(
SubGhz* subghz,

View File

@ -11,6 +11,7 @@
#define FRAME_HEIGHT 12
#define MAX_LEN_PX 100
#define MENU_ITEMS 4u
#define UNLOCK_CNT 3
typedef struct {
string_t item_str;
@ -34,7 +35,17 @@ static const Icon* ReceiverItemIcons[] = {
[SubGhzProtocolTypeDynamic] = &I_Lock_7x8,
};
typedef enum {
SubGhzViewReceiverBarShowDefault,
SubGhzViewReceiverBarShowLock,
SubGhzViewReceiverBarShowToUnlockPress,
SubGhzViewReceiverBarShowUnlock,
} SubGhzViewReceiverBarShow;
struct SubGhzViewReceiver {
SubGhzLock lock;
uint8_t lock_count;
osTimerId_t timer;
View* view;
SubGhzViewReceiverCallback callback;
void* context;
@ -48,8 +59,29 @@ typedef struct {
uint16_t idx;
uint16_t list_offset;
uint16_t history_item;
SubGhzViewReceiverBarShow bar_show;
} SubGhzViewReceiverModel;
void subghz_view_receiver_set_lock(SubGhzViewReceiver* subghz_receiver, SubGhzLock lock) {
furi_assert(subghz_receiver);
subghz_receiver->lock_count = 0;
if(lock == SubGhzLockOn) {
subghz_receiver->lock = lock;
with_view_model(
subghz_receiver->view, (SubGhzViewReceiverModel * model) {
model->bar_show = SubGhzViewReceiverBarShowLock;
return true;
});
osTimerStart(subghz_receiver->timer, pdMS_TO_TICKS(1000));
} else {
with_view_model(
subghz_receiver->view, (SubGhzViewReceiverModel * model) {
model->bar_show = SubGhzViewReceiverBarShowDefault;
return true;
});
}
}
void subghz_view_receiver_set_callback(
SubGhzViewReceiver* subghz_receiver,
SubGhzViewReceiverCallback callback,
@ -138,17 +170,6 @@ void subghz_view_receiver_draw(Canvas* canvas, SubGhzViewReceiverModel* model) {
canvas_set_font(canvas, FontSecondary);
elements_button_left(canvas, "Config");
canvas_draw_str(canvas, 44, 62, string_get_cstr(model->frequency_str));
canvas_draw_str(canvas, 79, 62, string_get_cstr(model->preset_str));
canvas_draw_str(canvas, 96, 62, string_get_cstr(model->history_stat_str));
if(model->history_item == 0) {
canvas_draw_icon(canvas, 0, 0, &I_Scanning_123x52);
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 63, 46, "Scanning...");
canvas_draw_line(canvas, 46, 51, 125, 51);
return;
}
canvas_draw_line(canvas, 46, 51, 125, 51);
bool scrollbar = model->history_item > 4;
@ -175,12 +196,96 @@ void subghz_view_receiver_draw(Canvas* canvas, SubGhzViewReceiverModel* model) {
elements_scrollbar_pos(canvas, 128, 0, 49, model->idx, model->history_item);
}
string_clear(str_buff);
canvas_set_color(canvas, ColorBlack);
if(model->history_item == 0) {
canvas_draw_icon(canvas, 0, 0, &I_Scanning_123x52);
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 63, 46, "Scanning...");
canvas_draw_line(canvas, 46, 51, 125, 51);
canvas_set_font(canvas, FontSecondary);
}
switch(model->bar_show) {
case SubGhzViewReceiverBarShowLock:
canvas_draw_icon(canvas, 64, 55, &I_Lock_7x8);
canvas_draw_str(canvas, 74, 62, "Locked");
break;
case SubGhzViewReceiverBarShowToUnlockPress:
canvas_draw_str(canvas, 44, 62, string_get_cstr(model->frequency_str));
canvas_draw_str(canvas, 79, 62, string_get_cstr(model->preset_str));
canvas_draw_str(canvas, 96, 62, string_get_cstr(model->history_stat_str));
canvas_set_font(canvas, FontSecondary);
elements_bold_rounded_frame(canvas, 14, 8, 99, 48);
elements_multiline_text(canvas, 65, 26, "To unlock\npress:");
canvas_draw_icon(canvas, 65, 42, &I_Pin_back_arrow_10x8);
canvas_draw_icon(canvas, 80, 42, &I_Pin_back_arrow_10x8);
canvas_draw_icon(canvas, 95, 42, &I_Pin_back_arrow_10x8);
canvas_draw_icon(canvas, 16, 13, &I_WarningDolphin_45x42);
canvas_draw_dot(canvas, 17, 61);
break;
case SubGhzViewReceiverBarShowUnlock:
canvas_draw_icon(canvas, 64, 55, &I_Unlock_7x8);
canvas_draw_str(canvas, 74, 62, "Unlocked");
break;
default:
canvas_draw_str(canvas, 44, 62, string_get_cstr(model->frequency_str));
canvas_draw_str(canvas, 79, 62, string_get_cstr(model->preset_str));
canvas_draw_str(canvas, 96, 62, string_get_cstr(model->history_stat_str));
break;
}
}
static void subghz_view_receiver_timer_callback(void* context) {
furi_assert(context);
SubGhzViewReceiver* subghz_receiver = context;
with_view_model(
subghz_receiver->view, (SubGhzViewReceiverModel * model) {
model->bar_show = SubGhzViewReceiverBarShowDefault;
return true;
});
if(subghz_receiver->lock_count < UNLOCK_CNT) {
subghz_receiver->callback(
SubGhzCustomEventViewReceiverOffDisplay, subghz_receiver->context);
} else {
subghz_receiver->lock = SubGhzLockOff;
subghz_receiver->callback(SubGhzCustomEventViewReceiverUnlock, subghz_receiver->context);
}
subghz_receiver->lock_count = 0;
}
bool subghz_view_receiver_input(InputEvent* event, void* context) {
furi_assert(context);
SubGhzViewReceiver* subghz_receiver = context;
if(subghz_receiver->lock == SubGhzLockOn) {
with_view_model(
subghz_receiver->view, (SubGhzViewReceiverModel * model) {
model->bar_show = SubGhzViewReceiverBarShowToUnlockPress;
return true;
});
if(subghz_receiver->lock_count == 0) {
osTimerStart(subghz_receiver->timer, pdMS_TO_TICKS(1000));
}
if(event->key == InputKeyBack && event->type == InputTypeShort) {
subghz_receiver->lock_count++;
}
if(subghz_receiver->lock_count >= UNLOCK_CNT) {
// subghz_receiver->callback(
// SubGhzCustomEventViewReceiverUnlock, subghz_receiver->context);
with_view_model(
subghz_receiver->view, (SubGhzViewReceiverModel * model) {
model->bar_show = SubGhzViewReceiverBarShowUnlock;
return true;
});
//subghz_receiver->lock = SubGhzLockOff;
osTimerStart(subghz_receiver->timer, pdMS_TO_TICKS(650));
}
return true;
}
if(event->key == InputKeyBack && event->type == InputTypeShort) {
subghz_receiver->callback(SubGhzCustomEventViewReceiverBack, subghz_receiver->context);
} else if(
@ -240,6 +345,7 @@ void subghz_view_receiver_exit(void* context) {
model->history_item = 0;
return false;
});
osTimerStop(subghz_receiver->timer);
}
SubGhzViewReceiver* subghz_view_receiver_alloc() {
@ -247,6 +353,9 @@ SubGhzViewReceiver* subghz_view_receiver_alloc() {
// View allocation and configuration
subghz_receiver->view = view_alloc();
subghz_receiver->lock = SubGhzLockOff;
subghz_receiver->lock_count = 0;
view_allocate_model(
subghz_receiver->view, ViewModelTypeLocking, sizeof(SubGhzViewReceiverModel));
view_set_context(subghz_receiver->view, subghz_receiver);
@ -260,11 +369,13 @@ SubGhzViewReceiver* subghz_view_receiver_alloc() {
string_init(model->frequency_str);
string_init(model->preset_str);
string_init(model->history_stat_str);
model->bar_show = SubGhzViewReceiverBarShowDefault;
model->history = malloc(sizeof(SubGhzReceiverHistory));
SubGhzReceiverMenuItemArray_init(model->history->data);
return true;
});
subghz_receiver->timer =
osTimerNew(subghz_view_receiver_timer_callback, osTimerOnce, subghz_receiver, NULL);
return subghz_receiver;
}
@ -285,6 +396,7 @@ void subghz_view_receiver_free(SubGhzViewReceiver* subghz_receiver) {
free(model->history);
return false;
});
osTimerDelete(subghz_receiver->timer);
view_free(subghz_receiver->view);
free(subghz_receiver);
}

View File

@ -1,12 +1,15 @@
#pragma once
#include <gui/view.h>
#include "../helpers/subghz_types.h"
#include "../helpers/subghz_custom_event.h"
typedef struct SubGhzViewReceiver SubGhzViewReceiver;
typedef void (*SubGhzViewReceiverCallback)(SubGhzCustomEvent event, void* context);
void subghz_view_receiver_set_lock(SubGhzViewReceiver* subghz_receiver, SubGhzLock keyboard);
void subghz_view_receiver_set_callback(
SubGhzViewReceiver* subghz_receiver,
SubGhzViewReceiverCallback callback,

View File

@ -0,0 +1,184 @@
#include <furi.h>
#include <furi_hal.h>
#include <applications/storage/storage.h>
#include <lib/flipper_format/flipper_format.h>
#include <lib/nfc_protocols/nfca.h>
#include <lib/digital_signal/digital_signal.h>
#include <lib/flipper_format/flipper_format_i.h>
#include <lib/toolbox/stream/file_stream.h>
#include "../minunit.h"
#define TAG "NfcTest"
#define NFC_TEST_RESOURCES_DIR "/ext/unit_tests/nfc/"
#define NFC_TEST_SIGNAL_SHORT_FILE "nfc_nfca_signal_short.nfc"
#define NFC_TEST_SIGNAL_LONG_FILE "nfc_nfca_signal_long.nfc"
static const char* nfc_test_file_type = "Flipper NFC test";
static const uint32_t nfc_test_file_version = 1;
#define NFC_TEST_DATA_MAX_LEN 18
#define NFC_TETS_TIMINGS_MAX_LEN 1350
typedef struct {
Storage* storage;
NfcaSignal* signal;
uint32_t test_data_len;
uint8_t test_data[NFC_TEST_DATA_MAX_LEN];
uint32_t test_timings_len;
uint32_t test_timings[NFC_TETS_TIMINGS_MAX_LEN];
} NfcTest;
static NfcTest* nfc_test = NULL;
static void nfc_test_alloc() {
nfc_test = malloc(sizeof(NfcTest));
nfc_test->signal = nfca_signal_alloc();
nfc_test->storage = furi_record_open("storage");
}
static void nfc_test_free() {
furi_assert(nfc_test);
furi_record_close("storage");
nfca_signal_free(nfc_test->signal);
free(nfc_test);
nfc_test = NULL;
}
static bool nfc_test_read_signal_from_file(const char* file_name) {
bool success = false;
FlipperFormat* file = flipper_format_file_alloc(nfc_test->storage);
string_t file_type;
string_init(file_type);
uint32_t file_version = 0;
do {
if(!flipper_format_file_open_existing(file, file_name)) break;
if(!flipper_format_read_header(file, file_type, &file_version)) break;
if(string_cmp_str(file_type, nfc_test_file_type) || file_version != nfc_test_file_version)
break;
if(!flipper_format_read_uint32(file, "Data length", &nfc_test->test_data_len, 1)) break;
if(nfc_test->test_data_len > NFC_TEST_DATA_MAX_LEN) break;
if(!flipper_format_read_hex(
file, "Plain data", nfc_test->test_data, nfc_test->test_data_len))
break;
if(!flipper_format_read_uint32(file, "Timings length", &nfc_test->test_timings_len, 1))
break;
if(nfc_test->test_timings_len > NFC_TETS_TIMINGS_MAX_LEN) break;
if(!flipper_format_read_uint32(
file, "Timings", nfc_test->test_timings, nfc_test->test_timings_len))
break;
success = true;
} while(false);
string_clear(file_type);
flipper_format_free(file);
return success;
}
static bool nfc_test_digital_signal_test_encode(
const char* file_name,
uint32_t encode_max_time,
uint32_t timing_tolerance,
uint32_t timings_sum_tolerance) {
furi_assert(nfc_test);
bool success = false;
uint32_t time = 0;
uint32_t dut_timings_sum = 0;
uint32_t ref_timings_sum = 0;
uint8_t parity[10] = {};
do {
// Read test data
if(!nfc_test_read_signal_from_file(file_name)) break;
// Encode signal
FURI_CRITICAL_ENTER();
time = DWT->CYCCNT;
nfca_signal_encode(
nfc_test->signal, nfc_test->test_data, nfc_test->test_data_len * 8, parity);
digital_signal_prepare_arr(nfc_test->signal->tx_signal);
time = (DWT->CYCCNT - time) / furi_hal_delay_instructions_per_microsecond();
FURI_CRITICAL_EXIT();
// Check timings
if(time > encode_max_time) {
FURI_LOG_E(
TAG, "Encoding time: %d us while accepted value: %d us", time, encode_max_time);
break;
}
// Check data
if(nfc_test->signal->tx_signal->edge_cnt != nfc_test->test_timings_len) {
FURI_LOG_E(TAG, "Not equal timings buffers length");
break;
}
uint32_t timings_diff = 0;
uint32_t* ref = nfc_test->test_timings;
uint32_t* dut = nfc_test->signal->tx_signal->reload_reg_buff;
bool timing_check_success = true;
for(size_t i = 0; i < nfc_test->test_timings_len; i++) {
timings_diff = dut[i] > ref[i] ? dut[i] - ref[i] : ref[i] - dut[i];
dut_timings_sum += dut[i];
ref_timings_sum += ref[i];
if(timings_diff > timing_tolerance) {
FURI_LOG_E(
TAG, "Too big differece in %d timings. Ref: %d, DUT: %d", i, ref[i], dut[i]);
timing_check_success = false;
break;
}
}
if(!timing_check_success) break;
uint32_t sum_diff = dut_timings_sum > ref_timings_sum ? dut_timings_sum - ref_timings_sum :
ref_timings_sum - dut_timings_sum;
if(sum_diff > timings_sum_tolerance) {
FURI_LOG_E(
TAG,
"Too big difference in timings sum. Ref: %d, DUT: %d",
ref_timings_sum,
dut_timings_sum);
break;
}
FURI_LOG_I(TAG, "Encoding time: %d us. Acceptable time: %d us", time, encode_max_time);
FURI_LOG_I(
TAG,
"Timings sum difference: %d [1/64MHZ]. Acceptable difference: %d [1/64MHz]",
sum_diff,
timings_sum_tolerance);
success = true;
} while(false);
return success;
}
MU_TEST(nfc_digital_signal_test) {
mu_assert(
nfc_test_digital_signal_test_encode(
NFC_TEST_RESOURCES_DIR NFC_TEST_SIGNAL_SHORT_FILE, 500, 1, 37),
"NFC short digital signal test failed\r\n");
mu_assert(
nfc_test_digital_signal_test_encode(
NFC_TEST_RESOURCES_DIR NFC_TEST_SIGNAL_LONG_FILE, 2000, 1, 37),
"NFC long digital signal test failed\r\n");
}
MU_TEST_SUITE(nfc) {
nfc_test_alloc();
MU_RUN_TEST(nfc_digital_signal_test);
nfc_test_free();
}
int run_minunit_test_nfc() {
MU_RUN_SUITE(nfc);
return MU_EXIT_CODE;
}

View File

@ -19,6 +19,7 @@ int run_minunit_test_stream();
int run_minunit_test_storage();
int run_minunit_test_subghz();
int run_minunit_test_dirwalk();
int run_minunit_test_nfc();
void minunit_print_progress(void) {
static char progress[] = {'\\', '|', '/', '-'};
@ -67,6 +68,7 @@ void unit_tests_cli(Cli* cli, string_t args, void* context) {
test_result |= run_minunit_test_infrared_decoder_encoder();
test_result |= run_minunit_test_rpc();
test_result |= run_minunit_test_subghz();
test_result |= run_minunit_test_nfc();
cycle_counter = (furi_hal_get_tick() - cycle_counter);

View File

@ -44,17 +44,17 @@ static const UpdateTaskStageGroupMap update_task_stage_progress[] = {
[UpdateTaskStageReadManifest] = STAGE_DEF(UpdateTaskStageGroupPreUpdate, 5),
[UpdateTaskStageLfsBackup] = STAGE_DEF(UpdateTaskStageGroupPreUpdate, 15),
[UpdateTaskStageRadioImageValidate] = STAGE_DEF(UpdateTaskStageGroupRadio, 10),
[UpdateTaskStageRadioErase] = STAGE_DEF(UpdateTaskStageGroupRadio, 50),
[UpdateTaskStageRadioWrite] = STAGE_DEF(UpdateTaskStageGroupRadio, 90),
[UpdateTaskStageRadioInstall] = STAGE_DEF(UpdateTaskStageGroupRadio, 15),
[UpdateTaskStageRadioBusy] = STAGE_DEF(UpdateTaskStageGroupRadio, 60),
[UpdateTaskStageRadioImageValidate] = STAGE_DEF(UpdateTaskStageGroupRadio, 15),
[UpdateTaskStageRadioErase] = STAGE_DEF(UpdateTaskStageGroupRadio, 60),
[UpdateTaskStageRadioWrite] = STAGE_DEF(UpdateTaskStageGroupRadio, 80),
[UpdateTaskStageRadioInstall] = STAGE_DEF(UpdateTaskStageGroupRadio, 60),
[UpdateTaskStageRadioBusy] = STAGE_DEF(UpdateTaskStageGroupRadio, 80),
[UpdateTaskStageOBValidation] = STAGE_DEF(UpdateTaskStageGroupOptionBytes, 10),
[UpdateTaskStageValidateDFUImage] = STAGE_DEF(UpdateTaskStageGroupFirmware, 100),
[UpdateTaskStageValidateDFUImage] = STAGE_DEF(UpdateTaskStageGroupFirmware, 50),
[UpdateTaskStageFlashWrite] = STAGE_DEF(UpdateTaskStageGroupFirmware, 200),
[UpdateTaskStageFlashValidate] = STAGE_DEF(UpdateTaskStageGroupFirmware, 50),
[UpdateTaskStageFlashValidate] = STAGE_DEF(UpdateTaskStageGroupFirmware, 30),
[UpdateTaskStageLfsRestore] = STAGE_DEF(UpdateTaskStageGroupPostUpdate, 30),
@ -214,6 +214,7 @@ UpdateTask* update_task_alloc() {
update_task->storage = furi_record_open("storage");
update_task->file = storage_file_alloc(update_task->storage);
update_task->status_change_cb = NULL;
update_task->boot_mode = furi_hal_rtc_get_boot_mode();
string_init(update_task->update_path);
FuriThread* thread = update_task->thread = furi_thread_alloc();

View File

@ -1,6 +1,7 @@
#pragma once
#include <storage/storage.h>
#include <furi_hal.h>
#define UPDATE_TASK_NOERR 0
#define UPDATE_TASK_FAILED -1
@ -14,6 +15,7 @@ typedef struct UpdateTask {
File* file;
updateProgressCb status_change_cb;
void* status_change_cb_state;
FuriHalRtcBootMode boot_mode;
} UpdateTask;
void update_task_set_progress(UpdateTask* update_task, UpdateTaskStage stage, uint8_t progress);

View File

@ -67,7 +67,6 @@ static bool update_task_post_update(UpdateTask* update_task) {
string_get_cstr(update_task->update_path), LFS_BACKUP_DEFAULT_FILENAME, file_path);
update_task_set_progress(update_task, UpdateTaskStageLfsRestore, 0);
update_operation_disarm();
CHECK_RESULT(lfs_backup_unpack(update_task->storage, string_get_cstr(file_path)));
@ -117,17 +116,17 @@ static bool update_task_post_update(UpdateTask* update_task) {
int32_t update_task_worker_backup_restore(void* context) {
furi_assert(context);
UpdateTask* update_task = context;
bool success = false;
FuriHalRtcBootMode boot_mode = furi_hal_rtc_get_boot_mode();
FuriHalRtcBootMode boot_mode = update_task->boot_mode;
if((boot_mode != FuriHalRtcBootModePreUpdate) && (boot_mode != FuriHalRtcBootModePostUpdate)) {
/* no idea how we got here. Clear to normal boot */
update_operation_disarm();
/* no idea how we got here. Do nothing */
return UPDATE_TASK_NOERR;
}
bool success = false;
do {
if(!update_task_parse_manifest(update_task)) {
return UPDATE_TASK_FAILED;
break;
}
/* Waiting for BT service to 'start', so we don't race for boot mode flag */
@ -138,7 +137,11 @@ int32_t update_task_worker_backup_restore(void* context) {
success = update_task_pre_update(update_task);
} else if(boot_mode == FuriHalRtcBootModePostUpdate) {
success = update_task_post_update(update_task);
if(success) {
update_operation_disarm();
}
}
} while(false);
if(!success) {
update_task_set_progress(update_task, UpdateTaskStageError, 0);

View File

@ -340,6 +340,8 @@ int32_t update_task_worker_flash_writer(void* context) {
}
furi_hal_rtc_set_boot_mode(FuriHalRtcBootModePostUpdate);
// Format LFS before restoring backup on next boot
furi_hal_rtc_set_flag(FuriHalRtcFlagFactoryReset);
update_task_set_progress(update_task, UpdateTaskStageCompleted, 100);
success = true;

View File

@ -28,38 +28,38 @@ icons_src = assetsenv.GlobRecursive("*.png", "icons")
icons_src += assetsenv.GlobRecursive("frame_rate", "icons")
icons = assetsenv.IconBuilder(Dir("compiled"), Dir("#/assets/icons"))
Depends(icons, icons_src)
Alias("icons", icons)
assetsenv.Depends(icons, icons_src)
assetsenv.Alias("icons", icons)
# Protobuf .proto -> .c + .h
proto_src = Glob("protobuf/*.proto", source=True)
proto_options = Glob("protobuf/*.options", source=True)
proto = assetsenv.ProtoBuilder(Dir("compiled"), proto_src)
Depends(proto, proto_options)
proto_src = assetsenv.Glob("protobuf/*.proto", source=True)
proto_options = assetsenv.Glob("protobuf/*.options", source=True)
proto = assetsenv.ProtoBuilder(assetsenv.Dir("compiled"), proto_src)
assetsenv.Depends(proto, proto_options)
# Precious(proto)
Alias("proto", proto)
assetsenv.Alias("proto", proto)
# Internal animations
dolphin_internal = assetsenv.DolphinSymBuilder(
Dir("compiled"),
Dir("#/assets/dolphin"),
assetsenv.Dir("compiled"),
assetsenv.Dir("#/assets/dolphin"),
DOLPHIN_RES_TYPE="internal",
)
Alias("dolphin_internal", dolphin_internal)
assetsenv.Alias("dolphin_internal", dolphin_internal)
# Blocking animations
dolphin_blocking = assetsenv.DolphinSymBuilder(
Dir("compiled"),
Dir("#/assets/dolphin"),
assetsenv.Dir("compiled"),
assetsenv.Dir("#/assets/dolphin"),
DOLPHIN_RES_TYPE="blocking",
)
Alias("dolphin_blocking", dolphin_blocking)
assetsenv.Alias("dolphin_blocking", dolphin_blocking)
# Protobuf version meta
@ -67,8 +67,8 @@ proto_ver = assetsenv.ProtoVerBuilder(
"compiled/protobuf_version.h",
"#/assets/protobuf/Changelog",
)
Depends(proto_ver, proto)
Alias("proto_ver", proto_ver)
assetsenv.Depends(proto_ver, proto)
assetsenv.Alias("proto_ver", proto_ver)
# Gather everything into a static lib
assets_parts = (icons, proto, dolphin_blocking, dolphin_internal, proto_ver)
@ -82,14 +82,14 @@ assetsenv.Install("${LIB_DIST_DIR}", assetslib)
if assetsenv["IS_BASE_FIRMWARE"]:
# External dolphin animations
dolphin_external = assetsenv.DolphinExtBuilder(
Dir("#/assets/resources/dolphin"),
Dir("#/assets/dolphin"),
assetsenv.Dir("#/assets/resources/dolphin"),
assetsenv.Dir("#/assets/dolphin"),
DOLPHIN_RES_TYPE="external",
)
NoClean(dolphin_external)
assetsenv.NoClean(dolphin_external)
if assetsenv["FORCE"]:
AlwaysBuild(dolphin_external)
Alias("dolphin_ext", dolphin_external)
assetsenv.AlwaysBuild(dolphin_external)
assetsenv.Alias("dolphin_ext", dolphin_external)
# Resources manifest
@ -101,13 +101,13 @@ if assetsenv["IS_BASE_FIRMWARE"]:
"${RESMANIFESTCOMSTR}",
),
)
Precious(resources)
NoClean(resources)
assetsenv.Precious(resources)
assetsenv.NoClean(resources)
if assetsenv["FORCE"]:
AlwaysBuild(resources)
assetsenv.AlwaysBuild(resources)
# Exporting resources node to external environment
env["FW_RESOURCES"] = resources
Alias("resources", resources)
assetsenv.Alias("resources", resources)
Return("assetslib")

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 B

@ -1 +1 @@
Subproject commit ffa62429f3c678537e0e883a3a8c3ae5f1398ed4
Subproject commit d9e343661dd36cfab792b78be1dea4e5950cb4dd

View File

@ -0,0 +1,6 @@
Filetype: Flipper NFC test
Version: 1
Data length: 18
Plain data: f1 99 41 43 a1 2f 23 01 de f3 c5 8d 91 4b 1e 50 4a c9
Timings length: 1304
Timings: 37 37 36 37 37 37 36 339 37 37 36 37 37 37 36 641 37 37 37 36 37 37 37 338 37 37 37 36 37 37 37 339 36 37 37 37 36 37 37 37 36 37 37 37 36 37 37 339 37 36 37 37 37 36 37 339 37 36 37 37 37 36 37 339 37 37 36 37 37 37 36 641 37 37 37 36 37 37 37 36 37 37 37 36 37 37 37 640 37 37 37 37 36 37 37 339 36 37 37 37 36 37 37 37 36 37 37 37 36 37 37 339 37 36 37 37 37 36 37 641 37 37 36 37 37 37 36 339 37 37 36 37 37 37 36 37 37 37 36 37 37 37 37 640 37 37 37 36 37 37 37 36 37 37 37 36 37 37 37 641 36 37 37 37 36 37 37 339 37 36 37 37 37 36 37 339 37 36 37 37 37 36 37 339 37 37 36 37 37 37 36 339 37 37 36 37 37 37 36 37 37 37 37 36 37 37 37 640 37 37 37 36 37 37 37 339 36 37 37 37 36 37 37 37 36 37 37 37 36 37 37 339 37 36 37 37 37 36 37 641 37 36 37 37 37 37 36 339 37 37 36 37 37 37 36 339 37 37 36 37 37 37 37 338 37 37 37 36 37 37 37 36 37 37 37 36 37 37 37 641 36 37 37 37 36 37 37 339 36 37 37 37 37 36 37 37 37 36 37 37 37 36 37 641 37 37 36 37 37 37 36 339 37 37 36 37 37 37 36 339 37 37 37 36 37 37 37 338 37 37 37 36 37 37 37 36 37 37 37 36 37 37 37 641 36 37 37 37 36 37 37 37 36 37 37 37 37 36 37 641 37 36 37 37 37 36 37 37 37 36 37 37 37 37 36 339 37 37 36 37 37 37 36 339 37 37 37 36 37 37 37 338 37 37 37 36 37 37 37 641 36 37 37 37 36 37 37 37 36 37 37 37 36 37 37 641 37 36 37 37 37 36 37 339 37 36 37 37 37 36 37 339 37 37 36 37 37 37 36 37 37 37 36 37 37 37 36 339 37 37 37 36 37 37 37 640 37 37 37 36 37 37 37 339 36 37 37 37 36 37 37 339 36 37 37 37 37 36 37 37 37 36 37 37 37 36 37 641 37 36 37 37 37 37 36 339 37 37 36 37 37 37 36 339 37 37 37 36 37 37 37 36 37 37 37 36 37 37 37 641 36 37 37 37 36 37 37 339 36 37 37 37 36 37 37 339 37 36 37 37 37 36 37 339 37 36 37 37 37 36 37 339 37 37 36 37 37 37 36 339 37 37 36 37 37 37 36 339 37 37 37 36 37 37 37 338 37 37 37 36 37 37 37 339 36 37 37 37 36 37 37 37 36 37 37 37 36 37 37 339 36 37 37 37 37 36 37 339 37 36 37 37 37 36 37 339 37 37 36 37 37 37 36 641 37 37 36 37 37 37 36 37 37 37 37 36 37 37 37 338 37 37 37 36 37 37 37 641 36 37 37 37 36 37 37 37 36 37 37 37 36 37 37 339 37 36 37 37 37 36 37 641 37 36 37 37 37 37 36 339 37 37 36 37 37 37 36 37 37 37 36 37 37 37 36 339 37 37 37 36 37 37 37 338 37 37 37 36 37 37 37 339 36 37 37 37 36 37 37 641 37 36 37 37 37 36 37 37 37 36 37 37 37 36 37 641 37 37 36 37 37 37 36 37 37 37 36 37 37 37 36 641 37 37 37 36 37 37 37 338 37 37 37 36 37 37 37 339 36 37 37 37 36 37 37 37 36 37 37 37 36 37 37 339 36 37 37 37 37 36 37 641 37 36 37 37 37 36 37 37 37 36 37 37 37 37 36 641 37 37 36 37 37 37 36 37 37 37 37 36 37 37 37 338 37 37 37 36 37 37 37 641 36 37 37 37 36 37 37 339 36 37 37 37 36 37 37 339 37 36 37 37 37 36 37 37 37 36 37 37 37 36 37 641 37 37 36 37 37 37 36 37 37 37 36 37 37 37 36 641 37 37 37 36 37 37 37 338 37 37 37 37 36 37 37 339 36 37 37 37 36 37 37 37 36 37 37 37 36 37 37 641 37 36 37 37 37 36 37 339 37 37 36 37 37 37 36 37 37 37 36 37 37 37 36 641 37 37 37 36 37 37 37 36 37 37 37 36 37 37 37 338 37 37 37 36 37 37 37 641 36 37 37 37 36 37 37 37 36 37 37 37 36 37 37 641 37 36 37 37 37 36 37 339 37 37 36 37 37 37 36 37 37 37 36 37 37 37 36 641 37 37 37 36 37 37 37 338 37 37 37 36 37 37 37 339 36 37 37 37 36 37 37 37 36 37 37 37 36 37 37 339 37 36 37 37 37 36 37 339 37 36 37 37 37 36 37 339 37 37 36 37 37 37 36 641 37 37 36 37 37 37 37 338 37 37 37 36 37 37 37 338 37 37 37 36 37 37 37 339 36 37 37 37 36 37 37 339 36 37 37 37 37 36 37 339 37 36 37 37 37 36 37 339 37 36 37 37 37 37 36 339 37 37 36 37 37 37 36 37 37 37 36 37 37 37 36 641 37 37 37 36 37 37 37 36 37 37 37 36 37 37 37 641 36 37 37 37 36 37 37 339 37 36 37 37 37 36 37 339 37 36 37 37 37 36 37 37 37 36 37 37 37 36 37 641 37 37 36 37 37 37 36 37 37 37 36 37 37 37 37 640 37 37 37 36 37 37 37 339 36 37 37 37 36 37 37 37 36 37 37 37 36 37 37 641 37 36 37 37 37 36 37 339 37 36 37 37 37 36 37 37 37 37 36 37 37 37 36 641 37 37 36 37 37 37 36 339 37 37 37 36 37 37 37 36 37 37 37 36 37 37 37 641 36 37 37 37 36 37 37 339 36 37 37 37 36 37 37 37 37 36 37 37 37 36 37 339 37 36 37 37 37 36 37 641 37 37 36 37 37 37 36 0

View File

@ -0,0 +1,6 @@
Filetype: Flipper NFC test
Version: 1
Data length: 4
Plain data: 14 d8 a0 c9
Timings length: 296
Timings: 37 37 36 37 37 37 36 641 37 37 36 37 37 37 37 338 37 37 37 36 37 37 37 36 37 37 37 36 37 37 37 641 36 37 37 37 36 37 37 37 36 37 37 37 36 37 37 641 37 36 37 37 37 36 37 339 37 36 37 37 37 37 36 339 37 37 36 37 37 37 36 339 37 37 37 36 37 37 37 338 37 37 37 36 37 37 37 338 37 37 37 37 36 37 37 339 36 37 37 37 36 37 37 37 36 37 37 37 36 37 37 339 37 36 37 37 37 36 37 641 37 37 36 37 37 37 36 37 37 37 36 37 37 37 36 339 37 37 36 37 37 37 37 640 37 37 37 36 37 37 37 339 36 37 37 37 36 37 37 339 36 37 37 37 36 37 37 339 37 36 37 37 37 36 37 339 37 36 37 37 37 36 37 339 37 37 36 37 37 37 36 37 37 37 36 37 37 37 36 641 37 37 37 36 37 37 37 36 37 37 37 36 37 37 37 641 36 37 37 37 36 37 37 37 36 37 37 37 36 37 37 641 37 36 37 37 37 36 37 339 37 36 37 37 37 37 36 37 37 37 36 37 37 37 36 641 37 37 36 37 37 37 37 338 37 37 37 36 37 37 37 36 37 37 37 36 37 37 37 339 36 37 37 37 36 37 37 641 36 37 37 37 37 36 37 0

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