diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 15b7f78647e..d3ebb9ad67a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -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: diff --git a/.gitmodules b/.gitmodules index 5846b70595e..b580a8c71a8 100644 --- a/.gitmodules +++ b/.gitmodules @@ -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 diff --git a/ReadMe.md b/ReadMe.md index 6a3eed377ff..e8b4daabcef 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -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 diff --git a/SConstruct b/SConstruct index 312b23a6470..33463cdcea4 100644 --- a/SConstruct +++ b/SConstruct @@ -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 - -debug_fw_elf = distenv.AddDebugTarget(firmware_out) -Alias("debug", debug_fw_elf) - +firmware_debug = distenv.PhonyTarget( + "debug", + "${GDBPYCOM}", + source=firmware_env["FW_ELF"], + GDBOPTS="${GDBOPTS_BASE}", + GDBREMOTE="${OPENOCD_GDB_PIPE}", +) +distenv.Depends(firmware_debug, firmware_flash) + +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.Pseudo("lint.check.pseudo") -AlwaysBuild(lint_check) -Alias("lint", lint_check) +distenv.PhonyTarget( + "format", + "${PYTHON3} scripts/lint.py format ${LINT_SOURCES}", + LINT_SOURCES=firmware_env["LINT_SOURCES"], +) -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) diff --git a/applications/archive/helpers/archive_browser.c b/applications/archive/helpers/archive_browser.c index b2f39c0778d..3e4d673110c 100644 --- a/applications/archive/helpers/archive_browser.c +++ b/applications/archive/helpers/archive_browser.c @@ -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); - 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 + 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)) { diff --git a/applications/bad_usb/bad_usb_script.c b/applications/bad_usb/bad_usb_script.c index 0266b51fd56..38b45acd229 100644 --- a/applications/bad_usb/bad_usb_script.c +++ b/applications/bad_usb/bad_usb_script.c @@ -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}, - - {"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}, - - {"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}, + {"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", 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", 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]; diff --git a/applications/bt/bt_hid_app/bt_hid.c b/applications/bt/bt_hid_app/bt_hid.c index 4b1037cdd73..47ee2268d81 100755 --- a/applications/bt/bt_hid_app/bt_hid.c +++ b/applications/bt/bt_hid_app/bt_hid.c @@ -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 diff --git a/applications/bt/bt_hid_app/bt_hid.h b/applications/bt/bt_hid_app/bt_hid.h index 875cac58945..81d092db4d0 100644 --- a/applications/bt/bt_hid_app/bt_hid.h +++ b/applications/bt/bt_hid_app/bt_hid.h @@ -10,7 +10,9 @@ #include #include #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; diff --git a/applications/bt/bt_hid_app/views/bt_hid_keyboard.c b/applications/bt/bt_hid_app/views/bt_hid_keyboard.c new file mode 100644 index 00000000000..1088e2959d6 --- /dev/null +++ b/applications/bt/bt_hid_app/views/bt_hid_keyboard.c @@ -0,0 +1,384 @@ +#include "bt_hid_keyboard.h" +#include +#include +#include +#include +#include + +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; + }); +} diff --git a/applications/bt/bt_hid_app/views/bt_hid_keyboard.h b/applications/bt/bt_hid_app/views/bt_hid_keyboard.h new file mode 100644 index 00000000000..b2cc928e2a9 --- /dev/null +++ b/applications/bt/bt_hid_app/views/bt_hid_keyboard.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +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); diff --git a/applications/bt/bt_hid_app/views/bt_hid_keynote.c b/applications/bt/bt_hid_app/views/bt_hid_keynote.c index c4225227ad2..60a1ebc0877 100755 --- a/applications/bt/bt_hid_app/views/bt_hid_keynote.c +++ b/applications/bt/bt_hid_app/views/bt_hid_keynote.c @@ -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 - canvas_set_font(canvas, FontPrimary); - elements_multiline_text_aligned(canvas, 9, 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"); + canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); } else { - canvas_draw_icon(canvas, 18, 18, &I_Ble_disconnected_24x34); - elements_multiline_text_aligned(canvas, 3, 60, AlignLeft, 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, "Keynote"); + canvas_set_font(canvas, FontSecondary); // 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, 47, 60, 13); + elements_slightly_rounded_box(canvas, 66, 27, 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, 74, 29, &I_Ok_btn_9x9); + elements_multiline_text_aligned(canvas, 91, 36, AlignLeft, AlignBottom, "Space"); + canvas_set_color(canvas, ColorBlack); -static void bt_hid_keynote_process_press(BtHidKeynote* bt_hid_keynote, InputEvent* event) { - with_view_model( - bt_hid_keynote->view, (BtHidKeynoteModel * model) { - if(event->key == InputKeyUp) { - model->up_pressed = true; - furi_hal_bt_hid_kb_press(KEY_UP_ARROW); - } else if(event->key == InputKeyDown) { - model->down_pressed = true; - furi_hal_bt_hid_kb_press(KEY_DOWN_ARROW); - } else if(event->key == InputKeyLeft) { - model->left_pressed = true; - furi_hal_bt_hid_kb_press(KEY_LEFT_ARROW); - } else if(event->key == InputKeyRight) { - model->right_pressed = true; - furi_hal_bt_hid_kb_press(KEY_RIGHT_ARROW); - } else if(event->key == InputKeyOk) { - model->ok_pressed = true; - furi_hal_bt_hid_kb_press(KEY_SPACE); - } - return true; - }); + // 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, 110, 49, &I_Ok_btn_9x9); + elements_multiline_text_aligned(canvas, 76, 56, AlignLeft, AlignBottom, "Back"); } -static void bt_hid_keynote_process_release(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->key == InputKeyUp) { - model->up_pressed = false; - furi_hal_bt_hid_kb_release(KEY_UP_ARROW); - } else if(event->key == InputKeyDown) { - model->down_pressed = false; - furi_hal_bt_hid_kb_release(KEY_DOWN_ARROW); - } else if(event->key == InputKeyLeft) { - model->left_pressed = false; - furi_hal_bt_hid_kb_release(KEY_LEFT_ARROW); - } else if(event->key == InputKeyRight) { - model->right_pressed = false; - furi_hal_bt_hid_kb_release(KEY_RIGHT_ARROW); - } else if(event->key == InputKeyOk) { - model->ok_pressed = false; - furi_hal_bt_hid_kb_release(KEY_SPACE); + if(event->type == InputTypePress) { + if(event->key == InputKeyUp) { + model->up_pressed = true; + 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(HID_KEYBOARD_DOWN_ARROW); + } else if(event->key == InputKeyLeft) { + model->left_pressed = true; + 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(HID_KEYBOARD_RIGHT_ARROW); + } else if(event->key == InputKeyOk) { + model->ok_pressed = true; + furi_hal_bt_hid_kb_press(HID_KEYBOARD_SPACEBAR); + } else if(event->key == InputKeyBack) { + model->back_pressed = true; + } + } else if(event->type == InputTypeRelease) { + if(event->key == InputKeyUp) { + model->up_pressed = false; + 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(HID_KEYBOARD_DOWN_ARROW); + } else if(event->key == InputKeyLeft) { + model->left_pressed = false; + 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(HID_KEYBOARD_RIGHT_ARROW); + } else if(event->key == InputKeyOk) { + model->ok_pressed = false; + 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); - consumed = true; - } else if(event->type == InputTypeRelease) { - bt_hid_keynote_process_release(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 == InputTypeShort) { - if(event->key == InputKeyBack) { - furi_hal_hid_kb_release_all(); - } } return consumed; diff --git a/applications/bt/bt_hid_app/views/bt_hid_media.c b/applications/bt/bt_hid_app/views/bt_hid_media.c index 695ce3c36da..b384f47cf18 100755 --- a/applications/bt/bt_hid_app/views/bt_hid_media.c +++ b/applications/bt/bt_hid_app/views/bt_hid_media.c @@ -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(); } } diff --git a/applications/bt/bt_hid_app/views/bt_hid_mouse.c b/applications/bt/bt_hid_app/views/bt_hid_mouse.c new file mode 100644 index 00000000000..fb1537a2c9d --- /dev/null +++ b/applications/bt/bt_hid_app/views/bt_hid_mouse.c @@ -0,0 +1,207 @@ +#include "bt_hid_mouse.h" +#include +#include +#include +#include + +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; + }); +} diff --git a/applications/bt/bt_hid_app/views/bt_hid_mouse.h b/applications/bt/bt_hid_app/views/bt_hid_mouse.h new file mode 100644 index 00000000000..a82971d7689 --- /dev/null +++ b/applications/bt/bt_hid_app/views/bt_hid_mouse.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +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); diff --git a/applications/cli/cli.c b/applications/cli/cli.c index aa48e93bfc3..35baad7ebec 100644 --- a/applications/cli/cli.c +++ b/applications/cli/cli.c @@ -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); diff --git a/applications/cli/cli_vcp.c b/applications/cli/cli_vcp.c index 7e23e4b14ec..332dd7cba87 100644 --- a/applications/cli/cli_vcp.c +++ b/applications/cli/cli_vcp.c @@ -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); diff --git a/applications/desktop/helpers/slideshow.c b/applications/desktop/helpers/slideshow.c index 37f66cf35d1..593b2854a10 100644 --- a/applications/desktop/helpers/slideshow.c +++ b/applications/desktop/helpers/slideshow.c @@ -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) { diff --git a/applications/desktop/helpers/slideshow.h b/applications/desktop/helpers/slideshow.h index db19779a327..eeaac0e8b40 100644 --- a/applications/desktop/helpers/slideshow.h +++ b/applications/desktop/helpers/slideshow.h @@ -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); diff --git a/applications/desktop/views/desktop_view_locked.c b/applications/desktop/views/desktop_view_locked.c index 4b544988ee3..1245c152901 100644 --- a/applications/desktop/views/desktop_view_locked.c +++ b/applications/desktop/views/desktop_view_locked.c @@ -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,10 +173,12 @@ static bool desktop_view_locked_input(InputEvent* event, void* context) { desktop_view_locked_update_hint_icon_timeout(locked_view); if(event->key == InputKeyBack) { - locked_view->lock_lastpress = press_time; - locked_view->lock_count++; - if(locked_view->lock_count == UNLOCK_CNT) { - locked_view->callback(DesktopLockedEventUnlocked, locked_view->context); + 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; diff --git a/applications/desktop/views/desktop_view_slideshow.c b/applications/desktop/views/desktop_view_slideshow.c index 97f779b5897..cd22b39de4e 100644 --- a/applications/desktop/views/desktop_view_slideshow.c +++ b/applications/desktop/views/desktop_view_slideshow.c @@ -21,7 +21,9 @@ static void desktop_view_slideshow_draw(Canvas* canvas, void* model) { DesktopSlideshowViewModel* m = model; canvas_clear(canvas); - slideshow_draw(m->slideshow, canvas, 0, 0); + if(slideshow_is_loaded(m->slideshow)) { + slideshow_draw(m->slideshow, canvas, 0, 0); + } } static bool desktop_view_slideshow_input(InputEvent* event, void* context) { diff --git a/applications/gpio/usb_uart_bridge.c b/applications/gpio/usb_uart_bridge.c index cf7e7687b66..9c6ba0a6b49 100644 --- a/applications/gpio/usb_uart_bridge.c +++ b/applications/gpio/usb_uart_bridge.c @@ -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); diff --git a/applications/ibutton/ibutton.c b/applications/ibutton/ibutton.c index ae93f9720fd..0f54dc3ee40 100644 --- a/applications/ibutton/ibutton.c +++ b/applications/ibutton/ibutton.c @@ -5,6 +5,9 @@ #include "m-string.h" #include #include +#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) { - string_set_str(ibutton->file_path, (const char*)p); - if(ibutton_load_key_data(ibutton, ibutton->file_path)) { - key_loaded = true; - // TODO: Display an error if the key from p could not be loaded + 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, 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; } diff --git a/applications/ibutton/ibutton_custom_event.h b/applications/ibutton/ibutton_custom_event.h index 2be42d66daf..1706e00fa39 100644 --- a/applications/ibutton/ibutton_custom_event.h +++ b/applications/ibutton/ibutton_custom_event.h @@ -9,4 +9,6 @@ enum iButtonCustomEvent { iButtonCustomEventByteEditResult, iButtonCustomEventWorkerEmulated, iButtonCustomEventWorkerRead, + + iButtonCustomEventRpcExit, }; diff --git a/applications/ibutton/ibutton_i.h b/applications/ibutton/ibutton_i.h index a85dd5f6d9f..889d5a67c0e 100644 --- a/applications/ibutton/ibutton_i.h +++ b/applications/ibutton/ibutton_i.h @@ -50,6 +50,8 @@ struct iButton { Popup* popup; Widget* widget; DialogEx* dialog_ex; + + void* rpc_ctx; }; typedef enum { diff --git a/applications/ibutton/scenes/ibutton_scene_config.h b/applications/ibutton/scenes/ibutton_scene_config.h index d30b43beb4c..87fa1a036a7 100644 --- a/applications/ibutton/scenes/ibutton_scene_config.h +++ b/applications/ibutton/scenes/ibutton_scene_config.h @@ -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) diff --git a/applications/ibutton/scenes/ibutton_scene_emulate.c b/applications/ibutton/scenes/ibutton_scene_emulate.c index 9551ea90cf5..0483b77aae1 100644 --- a/applications/ibutton/scenes/ibutton_scene_emulate.c +++ b/applications/ibutton/scenes/ibutton_scene_emulate.c @@ -3,6 +3,8 @@ #include #include +#define EMULATE_TIMEOUT_TICKS 10 + static void ibutton_scene_emulate_callback(void* context, bool emulated) { iButton* ibutton = context; if(emulated) { @@ -95,11 +97,23 @@ 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) { - ibutton_notification_message(ibutton, iButtonNotificationMessageYellowBlink); + 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); } } diff --git a/applications/ibutton/scenes/ibutton_scene_rpc.c b/applications/ibutton/scenes/ibutton_scene_rpc.c new file mode 100644 index 00000000000..ceeca017920 --- /dev/null +++ b/applications/ibutton/scenes/ibutton_scene_rpc.c @@ -0,0 +1,36 @@ +#include "../ibutton_i.h" +#include + +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); +} diff --git a/applications/infrared/infrared.c b/applications/infrared/infrared.c index e641302cd83..62206116540 100644 --- a/applications/infrared/infrared.c +++ b/applications/infrared/infrared.c @@ -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,18 +426,29 @@ int32_t infrared_app(void* p) { infrared_make_app_folder(infrared); bool is_remote_loaded = false; + bool is_rpc_mode = false; if(p) { - string_set_str(infrared->file_path, (const char*)p); - is_remote_loaded = infrared_remote_load(infrared->remote, infrared->file_path); - if(!is_remote_loaded) { - dialog_message_show_storage_error( - infrared->dialogs, "Failed to load\nselected remote"); - return -1; + 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) { + dialog_message_show_storage_error( + infrared->dialogs, "Failed to load\nselected remote"); + 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); diff --git a/applications/infrared/infrared_i.h b/applications/infrared/infrared_i.h index 5c447adc34f..c47753f82d8 100644 --- a/applications/infrared/infrared_i.h +++ b/applications/infrared/infrared_i.h @@ -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 { diff --git a/applications/infrared/infrared_remote.c b/applications/infrared/infrared_remote.c index ad75efe7fab..94658035ba8 100644 --- a/applications/infrared/infrared_remote.c +++ b/applications/infrared/infrared_remote.c @@ -1,5 +1,7 @@ #include "infrared_remote.h" +#include +#include #include #include #include @@ -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); diff --git a/applications/infrared/infrared_remote.h b/applications/infrared/infrared_remote.h index 1336383f219..b6f63a198cb 100644 --- a/applications/infrared/infrared_remote.h +++ b/applications/infrared/infrared_remote.h @@ -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); diff --git a/applications/infrared/scenes/infrared_scene_config.h b/applications/infrared/scenes/infrared_scene_config.h index c4e083c1772..8ff3b167b23 100644 --- a/applications/infrared/scenes/infrared_scene_config.h +++ b/applications/infrared/scenes/infrared_scene_config.h @@ -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) diff --git a/applications/infrared/scenes/infrared_scene_rpc.c b/applications/infrared/scenes/infrared_scene_rpc.c new file mode 100644 index 00000000000..3cab9f80eb2 --- /dev/null +++ b/applications/infrared/scenes/infrared_scene_rpc.c @@ -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); +} diff --git a/applications/lfrfid/lfrfid_app.cpp b/applications/lfrfid/lfrfid_app.cpp index 4027a07ea19..d2c92d24861 100644 --- a/applications/lfrfid/lfrfid_app.cpp +++ b/applications/lfrfid/lfrfid_app.cpp @@ -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 #include +#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(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)) { - string_set_str(file_path, args); - load_key_data(file_path, &worker.key); - scene_controller.add_scene(SceneType::Emulate, new LfRfidAppSceneEmulate()); - scene_controller.process(100, SceneType::Emulate); + 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, 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"); } diff --git a/applications/lfrfid/lfrfid_app.h b/applications/lfrfid/lfrfid_app.h index 3f8209a1d8c..3372552fc8d 100644 --- a/applications/lfrfid/lfrfid_app.h +++ b/applications/lfrfid/lfrfid_app.h @@ -21,6 +21,7 @@ #include #include "helpers/rfid_worker.h" +#include "rpc/rpc_app.h" class LfRfidApp { public: @@ -30,6 +31,8 @@ class LfRfidApp { MenuSelected, Stay, Retry, + Exit, + EmulateStart, }; enum class SceneType : uint8_t { @@ -51,6 +54,7 @@ class LfRfidApp { SavedInfo, DeleteConfirm, DeleteSuccess, + Rpc, }; class Event { @@ -79,6 +83,8 @@ class LfRfidApp { string_t file_path; + RpcAppSystem* rpc_ctx; + void run(void* args); static const char* app_folder; @@ -89,8 +95,9 @@ class LfRfidApp { 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); }; diff --git a/applications/lfrfid/scene/lfrfid_app_scene_rpc.cpp b/applications/lfrfid/scene/lfrfid_app_scene_rpc.cpp new file mode 100644 index 00000000000..012c96ac15e --- /dev/null +++ b/applications/lfrfid/scene/lfrfid_app_scene_rpc.cpp @@ -0,0 +1,37 @@ +#include "lfrfid_app_scene_rpc.h" +#include "furi/common_defines.h" +#include + +void LfRfidAppSceneRpc::on_enter(LfRfidApp* app, bool /* need_restore */) { + auto popup = app->view_controller.get(); + + popup->set_header("RPC Mode", 64, 30, AlignCenter, AlignTop); + + app->view_controller.switch_to(); + + 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()->clean(); +} diff --git a/applications/lfrfid/scene/lfrfid_app_scene_rpc.h b/applications/lfrfid/scene/lfrfid_app_scene_rpc.h new file mode 100644 index 00000000000..f630dfd35e1 --- /dev/null +++ b/applications/lfrfid/scene/lfrfid_app_scene_rpc.h @@ -0,0 +1,12 @@ +#pragma once +#include "../lfrfid_app.h" + +class LfRfidAppSceneRpc : public GenericScene { +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; +}; diff --git a/applications/nfc/helpers/nfc_generators.c b/applications/nfc/helpers/nfc_generators.c new file mode 100644 index 00000000000..67d9be7a020 --- /dev/null +++ b/applications/nfc/helpers/nfc_generators.c @@ -0,0 +1,327 @@ +#include +#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, +}; diff --git a/applications/nfc/helpers/nfc_generators.h b/applications/nfc/helpers/nfc_generators.h new file mode 100644 index 00000000000..10a05591b1b --- /dev/null +++ b/applications/nfc/helpers/nfc_generators.h @@ -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[]; diff --git a/applications/nfc/nfc.c b/applications/nfc/nfc.c old mode 100755 new mode 100644 index adc3b149444..e98b5d9a516 --- a/applications/nfc/nfc.c +++ b/applications/nfc/nfc.c @@ -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) { diff --git a/applications/nfc/nfc_device.c b/applications/nfc/nfc_device.c index 092d0089617..155cc3f8435 100644 --- a/applications/nfc/nfc_device.c +++ b/applications/nfc/nfc_device.c @@ -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); diff --git a/applications/nfc/nfc_device.h b/applications/nfc/nfc_device.h index 400f6c36343..3b2875c09e7 100644 --- a/applications/nfc/nfc_device.h +++ b/applications/nfc/nfc_device.h @@ -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); diff --git a/applications/nfc/nfc_i.h b/applications/nfc/nfc_i.h index a640d957893..c42ddced206 100755 --- a/applications/nfc/nfc_i.h +++ b/applications/nfc/nfc_i.h @@ -29,10 +29,21 @@ #include #include +#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); diff --git a/applications/nfc/nfc_worker.c b/applications/nfc/nfc_worker.c index 176b15c6224..7d78fa74752 100644 --- a/applications/nfc/nfc_worker.c +++ b/applications/nfc/nfc_worker.c @@ -1,6 +1,8 @@ #include "nfc_worker_i.h" #include +#include + #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) { diff --git a/applications/nfc/scenes/nfc_scene_config.h b/applications/nfc/scenes/nfc_scene_config.h index e6351d4216e..ffd757de33f 100755 --- a/applications/nfc/scenes/nfc_scene_config.h +++ b/applications/nfc/scenes/nfc_scene_config.h @@ -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) diff --git a/applications/nfc/scenes/nfc_scene_generate_info.c b/applications/nfc/scenes/nfc_scene_generate_info.c new file mode 100644 index 00000000000..7fb7eb94207 --- /dev/null +++ b/applications/nfc/scenes/nfc_scene_generate_info.c @@ -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); +} diff --git a/applications/nfc/scenes/nfc_scene_rpc.c b/applications/nfc/scenes/nfc_scene_rpc.c new file mode 100644 index 00000000000..b94bf424edf --- /dev/null +++ b/applications/nfc/scenes/nfc_scene_rpc.c @@ -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); +} diff --git a/applications/nfc/scenes/nfc_scene_set_type.c b/applications/nfc/scenes/nfc_scene_set_type.c old mode 100755 new mode 100644 index 0fe63424fe4..ec6d11447ad --- a/applications/nfc/scenes/nfc_scene_set_type.c +++ b/applications/nfc/scenes/nfc_scene_set_type.c @@ -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; diff --git a/applications/notification/notification_app.c b/applications/notification/notification_app.c index 687672c9af6..9f362358ae1 100644 --- a/applications/notification/notification_app.c +++ b/applications/notification/notification_app.c @@ -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); } diff --git a/applications/picopass/application.fam b/applications/picopass/application.fam new file mode 100644 index 00000000000..223094250db --- /dev/null +++ b/applications/picopass/application.fam @@ -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, +) diff --git a/applications/picopass/picopass.c b/applications/picopass/picopass.c new file mode 100644 index 00000000000..191895482ba --- /dev/null +++ b/applications/picopass/picopass.c @@ -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; +} diff --git a/applications/picopass/picopass.h b/applications/picopass/picopass.h new file mode 100644 index 00000000000..a1a87d7f869 --- /dev/null +++ b/applications/picopass/picopass.h @@ -0,0 +1,3 @@ +#pragma once + +typedef struct Picopass Picopass; diff --git a/applications/picopass/picopass_device.c b/applications/picopass/picopass_device.c new file mode 100644 index 00000000000..8cce528805f --- /dev/null +++ b/applications/picopass/picopass_device.c @@ -0,0 +1,146 @@ +#include "picopass_device.h" + +#include +#include + +#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)); +} diff --git a/applications/picopass/picopass_device.h b/applications/picopass/picopass_device.h new file mode 100644 index 00000000000..a0f7a667e24 --- /dev/null +++ b/applications/picopass/picopass_device.h @@ -0,0 +1,70 @@ +#pragma once + +#include +#include +#include +#include + +#include + +#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); diff --git a/applications/picopass/picopass_i.h b/applications/picopass/picopass_i.h new file mode 100644 index 00000000000..dbf4f8be582 --- /dev/null +++ b/applications/picopass/picopass_i.h @@ -0,0 +1,77 @@ +#pragma once + +#include "picopass.h" +#include "picopass_worker.h" +#include "picopass_device.h" + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include + +#include +#include + +#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); diff --git a/applications/picopass/picopass_worker.c b/applications/picopass/picopass_worker.c new file mode 100644 index 00000000000..40fe44489a1 --- /dev/null +++ b/applications/picopass/picopass_worker.c @@ -0,0 +1,310 @@ +#include "picopass_worker_i.h" +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#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); + } +} diff --git a/applications/picopass/picopass_worker.h b/applications/picopass/picopass_worker.h new file mode 100755 index 00000000000..9035f1c8951 --- /dev/null +++ b/applications/picopass/picopass_worker.h @@ -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); diff --git a/applications/picopass/picopass_worker_i.h b/applications/picopass/picopass_worker_i.h new file mode 100644 index 00000000000..2610d5e7f06 --- /dev/null +++ b/applications/picopass/picopass_worker_i.h @@ -0,0 +1,24 @@ +#pragma once + +#include "picopass_worker.h" +#include "picopass_i.h" + +#include +#include + +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); diff --git a/applications/picopass/scenes/picopass_scene.c b/applications/picopass/scenes/picopass_scene.c new file mode 100755 index 00000000000..61bd5e8fea4 --- /dev/null +++ b/applications/picopass/scenes/picopass_scene.c @@ -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, +}; diff --git a/applications/picopass/scenes/picopass_scene.h b/applications/picopass/scenes/picopass_scene.h new file mode 100644 index 00000000000..2faa80b122f --- /dev/null +++ b/applications/picopass/scenes/picopass_scene.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +// 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 diff --git a/applications/picopass/scenes/picopass_scene_card_menu.c b/applications/picopass/scenes/picopass_scene_card_menu.c new file mode 100644 index 00000000000..a424b919a74 --- /dev/null +++ b/applications/picopass/scenes/picopass_scene_card_menu.c @@ -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); +} diff --git a/applications/picopass/scenes/picopass_scene_config.h b/applications/picopass/scenes/picopass_scene_config.h new file mode 100755 index 00000000000..0a3e73f2979 --- /dev/null +++ b/applications/picopass/scenes/picopass_scene_config.h @@ -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) diff --git a/applications/picopass/scenes/picopass_scene_read_card.c b/applications/picopass/scenes/picopass_scene_read_card.c new file mode 100644 index 00000000000..add05e4796b --- /dev/null +++ b/applications/picopass/scenes/picopass_scene_read_card.c @@ -0,0 +1,55 @@ +#include "../picopass_i.h" +#include + +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); +} diff --git a/applications/picopass/scenes/picopass_scene_read_card_success.c b/applications/picopass/scenes/picopass_scene_read_card_success.c new file mode 100644 index 00000000000..96a0803101b --- /dev/null +++ b/applications/picopass/scenes/picopass_scene_read_card_success.c @@ -0,0 +1,91 @@ +#include "../picopass_i.h" +#include + +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); +} diff --git a/applications/picopass/scenes/picopass_scene_save_name.c b/applications/picopass/scenes/picopass_scene_save_name.c new file mode 100644 index 00000000000..c5fa7dd1f1a --- /dev/null +++ b/applications/picopass/scenes/picopass_scene_save_name.c @@ -0,0 +1,84 @@ +#include "../picopass_i.h" +#include "m-string.h" +#include +#include +#include + +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); +} diff --git a/applications/picopass/scenes/picopass_scene_save_success.c b/applications/picopass/scenes/picopass_scene_save_success.c new file mode 100644 index 00000000000..e92d91fb440 --- /dev/null +++ b/applications/picopass/scenes/picopass_scene_save_success.c @@ -0,0 +1,47 @@ +#include "../picopass_i.h" +#include + +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); +} diff --git a/applications/picopass/scenes/picopass_scene_saved_menu.c b/applications/picopass/scenes/picopass_scene_saved_menu.c new file mode 100644 index 00000000000..232cf26a976 --- /dev/null +++ b/applications/picopass/scenes/picopass_scene_saved_menu.c @@ -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); +} diff --git a/applications/picopass/scenes/picopass_scene_start.c b/applications/picopass/scenes/picopass_scene_start.c new file mode 100644 index 00000000000..7f42fb13309 --- /dev/null +++ b/applications/picopass/scenes/picopass_scene_start.c @@ -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); +} diff --git a/applications/rpc/rpc.c b/applications/rpc/rpc.c index c974a0e51d5..ce063b8e40e 100644 --- a/applications/rpc/rpc.c +++ b/applications/rpc/rpc.c @@ -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; diff --git a/applications/rpc/rpc_app.c b/applications/rpc/rpc_app.c index 728c20528d3..f6678c3b657 100644 --- a/applications/rpc/rpc_app.c +++ b/applications/rpc/rpc_app.c @@ -1,16 +1,39 @@ +#include "cmsis_os2.h" #include "flipper.pb.h" #include "furi/record.h" #include "rpc_i.h" #include #include +#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); } diff --git a/applications/rpc/rpc_app.h b/applications/rpc/rpc_app.h new file mode 100644 index 00000000000..396eef1a341 --- /dev/null +++ b/applications/rpc/rpc_app.h @@ -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 diff --git a/applications/rpc/rpc_gpio.c b/applications/rpc/rpc_gpio.c new file mode 100644 index 00000000000..614e775a1c5 --- /dev/null +++ b/applications/rpc/rpc_gpio.c @@ -0,0 +1,221 @@ +#include "flipper.pb.h" +#include "rpc_i.h" +#include "gpio.pb.h" +#include +#include + +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; +} diff --git a/applications/rpc/rpc_i.h b/applications/rpc/rpc_i.h index f84cc991e1c..e512ad39768 100644 --- a/applications/rpc/rpc_i.h +++ b/applications/rpc/rpc_i.h @@ -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); diff --git a/applications/rpc/rpc_system.c b/applications/rpc/rpc_system.c index 45ae2e5a010..350602fd8f7 100644 --- a/applications/rpc/rpc_system.c +++ b/applications/rpc/rpc_system.c @@ -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; diff --git a/applications/snake_game/snake_game.c b/applications/snake_game/snake_game.c index bb9e207de25..1a4bf812629 100644 --- a/applications/snake_game/snake_game.c +++ b/applications/snake_game/snake_game.c @@ -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) { diff --git a/applications/storage/storages/storage_int.c b/applications/storage/storages/storage_int.c index 291fb426bb5..bdd78172efa 100644 --- a/applications/storage/storages/storage_int.c +++ b/applications/storage/storages/storage_int.c @@ -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); - *total_space = lfs_data->config.block_size * lfs_data->config.block_count; + 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); diff --git a/applications/subghz/helpers/subghz_custom_event.h b/applications/subghz/helpers/subghz_custom_event.h index a2e5ee3e6ea..05f0f4924ab 100644 --- a/applications/subghz/helpers/subghz_custom_event.h +++ b/applications/subghz/helpers/subghz_custom_event.h @@ -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, diff --git a/applications/subghz/helpers/subghz_types.h b/applications/subghz/helpers/subghz_types.h new file mode 100644 index 00000000000..8d2dcf17cb3 --- /dev/null +++ b/applications/subghz/helpers/subghz_types.h @@ -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; diff --git a/applications/subghz/scenes/subghz_scene_config.h b/applications/subghz/scenes/subghz_scene_config.h index 1cb217adc11..fd1271c8c73 100644 --- a/applications/subghz/scenes/subghz_scene_config.h +++ b/applications/subghz/scenes/subghz_scene_config.h @@ -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) diff --git a/applications/subghz/scenes/subghz_scene_read_raw.c b/applications/subghz/scenes/subghz_scene_read_raw.c index 45bb8a50bdb..598f235e35f 100644 --- a/applications/subghz/scenes/subghz_scene_read_raw.c +++ b/applications/subghz/scenes/subghz_scene_read_raw.c @@ -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) { diff --git a/applications/subghz/scenes/subghz_scene_receiver.c b/applications/subghz/scenes/subghz_scene_receiver.c index 01005148b33..55302b01f07 100644 --- a/applications/subghz/scenes/subghz_scene_receiver.c +++ b/applications/subghz/scenes/subghz_scene_receiver.c @@ -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: - notification_message(subghz->notifications, &subghs_sequence_rx); + 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) { diff --git a/applications/subghz/scenes/subghz_scene_receiver_config.c b/applications/subghz/scenes/subghz_scene_receiver_config.c index 8789a2434a5..cf31c1e921e 100644 --- a/applications/subghz/scenes/subghz_scene_receiver_config.c +++ b/applications/subghz/scenes/subghz_scene_receiver_config.c @@ -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) { diff --git a/applications/subghz/scenes/subghz_scene_rpc.c b/applications/subghz/scenes/subghz_scene_rpc.c new file mode 100644 index 00000000000..c7573fda13d --- /dev/null +++ b/applications/subghz/scenes/subghz_scene_rpc.c @@ -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); +} diff --git a/applications/subghz/subghz.c b/applications/subghz/subghz.c index d9dc19f409d..984ce472872 100644 --- a/applications/subghz/subghz.c +++ b/applications/subghz/subghz.c @@ -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"))) { diff --git a/applications/subghz/subghz_i.c b/applications/subghz/subghz_i.c index 33ceb9bbb93..0593cc6f78a 100644 --- a/applications/subghz/subghz_i.c +++ b/applications/subghz/subghz_i.c @@ -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: - dialog_message_show_storage_error(subghz->dialogs, "Cannot parse\nfile"); + if(show_dialog) { + dialog_message_show_storage_error(subghz->dialogs, "Cannot parse\nfile"); + } return false; case SubGhzLoadKeyStateOnlyRx: - subghz_dialog_message_show_only_rx(subghz); + 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); diff --git a/applications/subghz/subghz_i.h b/applications/subghz/subghz_i.h index 5d1b06993aa..0a4bcf0b4c7 100644 --- a/applications/subghz/subghz_i.h +++ b/applications/subghz/subghz_i.h @@ -1,5 +1,6 @@ #pragma once +#include "helpers/subghz_types.h" #include "subghz.h" #include "views/receiver.h" #include "views/transmitter.h" @@ -35,53 +36,9 @@ #include #include -#define SUBGHZ_MAX_LEN_NAME 64 +#include "rpc/rpc_app.h" -/** 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; +#define SUBGHZ_MAX_LEN_NAME 64 struct SubGhzTxRx { SubGhzWorker* worker; @@ -135,23 +92,10 @@ struct SubGhz { SubGhzTestPacket* subghz_test_packet; string_t error_str; SubGhzSetting* setting; -}; + SubGhzLock lock; -typedef enum { - SubGhzViewIdMenu, - SubGhzViewIdReceiver, - SubGhzViewIdPopup, - SubGhzViewIdTextInput, - SubGhzViewIdWidget, - SubGhzViewIdTransmitter, - SubGhzViewIdVariableItemList, - SubGhzViewIdFrequencyAnalyzer, - SubGhzViewIdReadRAW, - - SubGhzViewIdStatic, - SubGhzViewIdTestCarrier, - SubGhzViewIdTestPacket, -} SubGhzViewId; + void* rpc_ctx; +}; bool subghz_set_preset(SubGhz* subghz, const char* preset); void subghz_get_frequency_modulation(SubGhz* subghz, string_t frequency, string_t modulation); @@ -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, diff --git a/applications/subghz/views/receiver.c b/applications/subghz/views/receiver.c index 866d827267d..bb4a8f16d4b 100644 --- a/applications/subghz/views/receiver.c +++ b/applications/subghz/views/receiver.c @@ -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); } diff --git a/applications/subghz/views/receiver.h b/applications/subghz/views/receiver.h index 87065ef08ac..aab7a76c545 100644 --- a/applications/subghz/views/receiver.h +++ b/applications/subghz/views/receiver.h @@ -1,12 +1,15 @@ #pragma once #include +#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, diff --git a/applications/unit_tests/nfc/nfc_test.c b/applications/unit_tests/nfc/nfc_test.c new file mode 100644 index 00000000000..a2262735c3b --- /dev/null +++ b/applications/unit_tests/nfc/nfc_test.c @@ -0,0 +1,184 @@ +#include +#include +#include +#include +#include +#include + +#include +#include + +#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; +} diff --git a/applications/unit_tests/test_index.c b/applications/unit_tests/test_index.c index d8a8174c4b5..8ff27b4d17f 100644 --- a/applications/unit_tests/test_index.c +++ b/applications/unit_tests/test_index.c @@ -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); diff --git a/applications/updater/util/update_task.c b/applications/updater/util/update_task.c index 316fe6802e5..47bb7ae5ac3 100644 --- a/applications/updater/util/update_task.c +++ b/applications/updater/util/update_task.c @@ -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(); diff --git a/applications/updater/util/update_task_i.h b/applications/updater/util/update_task_i.h index fb6b4ac114a..95c63f64432 100644 --- a/applications/updater/util/update_task_i.h +++ b/applications/updater/util/update_task_i.h @@ -1,6 +1,7 @@ #pragma once #include +#include #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); diff --git a/applications/updater/util/update_task_worker_backup.c b/applications/updater/util/update_task_worker_backup.c index f1ce5281799..1e8c2f09c1b 100644 --- a/applications/updater/util/update_task_worker_backup.c +++ b/applications/updater/util/update_task_worker_backup.c @@ -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,28 +116,32 @@ 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; } - if(!update_task_parse_manifest(update_task)) { - return UPDATE_TASK_FAILED; - } + bool success = false; + do { + if(!update_task_parse_manifest(update_task)) { + break; + } - /* Waiting for BT service to 'start', so we don't race for boot mode flag */ - furi_record_open("bt"); - furi_record_close("bt"); + /* Waiting for BT service to 'start', so we don't race for boot mode flag */ + furi_record_open("bt"); + furi_record_close("bt"); - if(boot_mode == FuriHalRtcBootModePreUpdate) { - success = update_task_pre_update(update_task); - } else if(boot_mode == FuriHalRtcBootModePostUpdate) { - success = update_task_post_update(update_task); - } + if(boot_mode == FuriHalRtcBootModePreUpdate) { + 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); diff --git a/applications/updater/util/update_task_worker_flasher.c b/applications/updater/util/update_task_worker_flasher.c index 1f22e2f7201..d02858bc612 100644 --- a/applications/updater/util/update_task_worker_flasher.c +++ b/applications/updater/util/update_task_worker_flasher.c @@ -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; diff --git a/assets/SConscript b/assets/SConscript index e70208ab919..d5465534df2 100644 --- a/assets/SConscript +++ b/assets/SConscript @@ -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") diff --git a/assets/icons/BLE/BLE_HID/Ble_connected_15x15.png b/assets/icons/BLE/BLE_HID/Ble_connected_15x15.png new file mode 100644 index 00000000000..58776828ed4 Binary files /dev/null and b/assets/icons/BLE/BLE_HID/Ble_connected_15x15.png differ diff --git a/assets/icons/BLE/BLE_HID/Ble_disconnected_15x15.png b/assets/icons/BLE/BLE_HID/Ble_disconnected_15x15.png new file mode 100644 index 00000000000..bfc1e7d7f18 Binary files /dev/null and b/assets/icons/BLE/BLE_HID/Ble_disconnected_15x15.png differ diff --git a/assets/protobuf b/assets/protobuf index ffa62429f3c..d9e343661dd 160000 --- a/assets/protobuf +++ b/assets/protobuf @@ -1 +1 @@ -Subproject commit ffa62429f3c678537e0e883a3a8c3ae5f1398ed4 +Subproject commit d9e343661dd36cfab792b78be1dea4e5950cb4dd diff --git a/assets/unit_tests/nfc/nfc_nfca_signal_long.nfc b/assets/unit_tests/nfc/nfc_nfca_signal_long.nfc new file mode 100644 index 00000000000..fae69cb5cc4 --- /dev/null +++ b/assets/unit_tests/nfc/nfc_nfca_signal_long.nfc @@ -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 diff --git a/assets/unit_tests/nfc/nfc_nfca_signal_short.nfc b/assets/unit_tests/nfc/nfc_nfca_signal_short.nfc new file mode 100644 index 00000000000..3b7e2d9e973 --- /dev/null +++ b/assets/unit_tests/nfc/nfc_nfca_signal_short.nfc @@ -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 diff --git a/core/furi/thread.c b/core/furi/thread.c index 692e5e0563c..266a3855f23 100644 --- a/core/furi/thread.c +++ b/core/furi/thread.c @@ -7,6 +7,8 @@ #include #include +#define THREAD_NOTIFY_INDEX 1 // Index 0 is used for stream buffers + struct FuriThread { FuriThreadState state; int32_t ret; @@ -221,13 +223,14 @@ uint32_t furi_thread_flags_set(FuriThreadId thread_id, uint32_t flags) { if(FURI_IS_IRQ_MODE()) { yield = pdFALSE; - (void)xTaskNotifyFromISR(hTask, flags, eSetBits, &yield); - (void)xTaskNotifyAndQueryFromISR(hTask, 0, eNoAction, &rflags, NULL); + (void)xTaskNotifyIndexedFromISR(hTask, THREAD_NOTIFY_INDEX, flags, eSetBits, &yield); + (void)xTaskNotifyAndQueryIndexedFromISR( + hTask, THREAD_NOTIFY_INDEX, 0, eNoAction, &rflags, NULL); portYIELD_FROM_ISR(yield); } else { - (void)xTaskNotify(hTask, flags, eSetBits); - (void)xTaskNotifyAndQuery(hTask, 0, eNoAction, &rflags); + (void)xTaskNotifyIndexed(hTask, THREAD_NOTIFY_INDEX, flags, eSetBits); + (void)xTaskNotifyAndQueryIndexed(hTask, THREAD_NOTIFY_INDEX, 0, eNoAction, &rflags); } } /* Return flags after setting */ @@ -245,11 +248,13 @@ uint32_t furi_thread_flags_clear(uint32_t flags) { } else { hTask = xTaskGetCurrentTaskHandle(); - if(xTaskNotifyAndQuery(hTask, 0, eNoAction, &cflags) == pdPASS) { + if(xTaskNotifyAndQueryIndexed(hTask, THREAD_NOTIFY_INDEX, 0, eNoAction, &cflags) == + pdPASS) { rflags = cflags; cflags &= ~flags; - if(xTaskNotify(hTask, cflags, eSetValueWithOverwrite) != pdPASS) { + if(xTaskNotifyIndexed(hTask, THREAD_NOTIFY_INDEX, cflags, eSetValueWithOverwrite) != + pdPASS) { rflags = (uint32_t)osError; } } else { @@ -270,7 +275,8 @@ uint32_t furi_thread_flags_get(void) { } else { hTask = xTaskGetCurrentTaskHandle(); - if(xTaskNotifyAndQuery(hTask, 0, eNoAction, &rflags) != pdPASS) { + if(xTaskNotifyAndQueryIndexed(hTask, THREAD_NOTIFY_INDEX, 0, eNoAction, &rflags) != + pdPASS) { rflags = (uint32_t)osError; } } @@ -300,7 +306,7 @@ uint32_t furi_thread_flags_wait(uint32_t flags, uint32_t options, uint32_t timeo t0 = xTaskGetTickCount(); do { - rval = xTaskNotifyWait(0, clear, &nval, tout); + rval = xTaskNotifyWaitIndexed(THREAD_NOTIFY_INDEX, 0, clear, &nval, tout); if(rval == pdPASS) { rflags &= flags; diff --git a/documentation/fbt.md b/documentation/fbt.md index 061339da4e3..575b02654d9 100644 --- a/documentation/fbt.md +++ b/documentation/fbt.md @@ -1,15 +1,20 @@ # Flipper Build Tool -FBT is the entry point for most firmware-related commands and utilities. +FBT is the entry point for firmware-related commands and utilities. It is invoked by `./fbt` in firmware project root directory. Internally, it is a wrapper around [scons](https://scons.org/) build system. ## Requirements Please install Python packages required by assets build scripts: `pip3 install -r scripts/requirements.txt` +Make sure that `gcc-arm-none-eabi` toolchain & OpenOCD executables are in system's PATH. ## NB -FBT constructs all referenced environments & their targets' dependency trees on startup. So, to keep startup time as low as possible, we're hiding construction of certain targets behind command-line options. +* `fbt` constructs all referenced environments & their targets' dependency trees on startup. So, to keep startup time as low as possible, we're hiding construction of certain targets behind command-line options. +* `fbt` always performs `git submodule update --init` on start, unless you set `FBT_NO_SYNC=1` in environment: + * On Windows, that's `set "FBT_NO_SYNC=1"` in the shell you're running `fbt` from + * On \*nix, it's `$ FBT_NO_SYNC=1 ./fbt ...` +* `fbt` builds updater & firmware in separate subdirectories in `build`, with their names depending on optimization settings (`COMPACT` & `DEBUG` options). However, for ease of integration with IDEs, latest built variant's directory is always linked as `built/latest`. Additionally, `compile_commands.json` is generated in that folder, which is used for code completion support in IDE. ## Invoking FBT @@ -17,23 +22,25 @@ To build with FBT, call it specifying configuration options & targets to build. `./fbt --with-updater COMPACT=1 DEBUG=0 VERBOSE=1 updater_package copro_dist` -To run cleanup (think of `make clean`) for specified targets, all `-c` option. +To run cleanup (think of `make clean`) for specified targets, add `-c` option. ## FBT targets -FBT keeps track of internal dependencies, so you only need to build the highest-level target you need, and FBT will make sure everything it needs is up-to-date. +FBT keeps track of internal dependencies, so you only need to build the highest-level target you need, and FBT will make sure everything they depend on is up-to-date. ### High-level (what you most likely need) -- `fw_dist` - build & publish firmware to `dist` folder +- `fw_dist` - build & publish firmware to `dist` folder. This is a default target, when no other are specified - `updater_package` - build self-update package. _Requires `--with-updater` option_ - `copro_dist` - bundle Core2 FUS+stack binaries for qFlipper - `flash` - flash attached device with OpenOCD over ST-Link - `flash_usb` - build, upload and install update package to device over USB. _Requires `--with-updater` option_ - `debug` - build and flash firmware, then attach with gdb with firmware's .elf loaded - `debug_updater` - attach gdb with updater's .elf loaded. _Requires `--with-updater` option_ -- `debug_other` - attach gdb without loading built elf. Allows to manually add external elf files with `add-symbol-file` in gdb. +- `debug_other` - attach gdb without loading any .elf. Allows to manually add external elf files with `add-symbol-file` in gdb. +- `blackmagic` - debug firmware with Blackmagic probe (WiFi dev board) - `openocd` - just start OpenOCD +- `get_blackmagic` - output blackmagic address in gdb remote format. Useful for IDE integration ### Firmware targets @@ -42,7 +49,7 @@ FBT keeps track of internal dependencies, so you only need to build the highest- - Check out `--extra-ext-apps` for force adding extra apps to external build - `firmware_snake_game_list`, etc - generate source + assembler listing for app's .elf - `flash`, `firmware_flash` - flash current version to attached device with OpenOCD over ST-Link -- `firmware_cdb` - generate compilation database +- `flash_blackmagic` - flash current version to attached device with Blackmagic probe - `firmware_all`, `updater_all` - build basic set of binaries - `firmware_list`, `updater_list` - generate source + assembler listing diff --git a/fbt b/fbt index 81193a46897..b3d2ca354ee 100755 --- a/fbt +++ b/fbt @@ -2,11 +2,17 @@ set -e -if [[ -d .git ]]; then - echo "Updating git submodules" - git submodule update --init +SCRIPTDIR="$( dirname -- "$0"; )"; +SCONS_EP=${SCRIPTDIR}/lib/scons/scripts/scons.py + +if [[ -z "${FBT_NO_SYNC:-}" ]] ; then + if [[ -d .git ]]; then + git submodule update --init + else + echo Not in a git repo, please clone with git clone --recursive + exit 1 + fi fi -SCRIPTDIR="$( dirname -- "$0"; )"; SCONS_DEFAULT_FLAGS="-Q --warn=target-not-built" -python3 ${SCRIPTDIR}/lib/scons/scripts/scons.py ${SCONS_DEFAULT_FLAGS} "$@" +python3 ${SCONS_EP} ${SCONS_DEFAULT_FLAGS} "$@" diff --git a/fbt.cmd b/fbt.cmd index 7711e44b594..5edecd210d6 100644 --- a/fbt.cmd +++ b/fbt.cmd @@ -1,8 +1,15 @@ @echo off -if exist ".git" ( - echo Prepairing git submodules - git submodule update --init + +set SCONS_EP=%~dp0\lib\scons\scripts\scons.py + +if [%FBT_NO_SYNC%] == [] ( + if exist ".git" ( + git submodule update --init + ) else ( + echo Not in a git repo, please clone with git clone --recursive + exit /b 1 + ) ) set "SCONS_DEFAULT_FLAGS=-Q --warn=target-not-built" -python lib/scons/scripts/scons.py %SCONS_DEFAULT_FLAGS% %* +python %SCONS_EP% %SCONS_DEFAULT_FLAGS% %* diff --git a/fbt_options.py b/fbt_options.py index ddeff048144..1c9f9c82b52 100644 --- a/fbt_options.py +++ b/fbt_options.py @@ -41,12 +41,26 @@ # Supported toolchain versions FBT_TOOLCHAIN_VERSIONS = (" 10.3.",) -OPENOCD_OPTS = '-f interface/stlink.cfg -c "transport select hla_swd" -f debug/stm32wbx.cfg -c "stm32wbx.cpu configure -rtos auto" -c "init"' +OPENOCD_OPTS = [ + "-f", + "interface/stlink.cfg", + "-c", + "transport select hla_swd", + "-f", + "debug/stm32wbx.cfg", + "-c", + "stm32wbx.cpu configure -rtos auto", + "-c", + "init", +] SVD_FILE = "debug/STM32WB55_CM4.svd" +# Look for blackmagic probe on serial ports +BLACKMAGIC = "auto" + FIRMWARE_APPS = { - "default": ( + "default": [ "crypto_start", # Svc "basic_services", @@ -62,11 +76,11 @@ "basic_plugins", # Debug "debug_apps", - ), - "unit_tests": ( + ], + "unit_tests": [ "basic_services", "unit_tests", - ), + ], } FIRMWARE_APP_SET = "default" diff --git a/firmware.scons b/firmware.scons index 890be5f8742..7cc67f436e1 100644 --- a/firmware.scons +++ b/firmware.scons @@ -2,11 +2,15 @@ Import("ENV", "fw_build_meta") import os +from fbt.util import ( + should_gen_cdb_and_link_dir, + link_elf_dir_as_latest, +) # Building initial C environment for libs env = ENV.Clone( - tools=["compilation_db", "fwbin", "openocd", "fbt_apps"], - COMPILATIONDB_USE_ABSPATH=True, + tools=["compilation_db", "fwbin", "fbt_apps"], + COMPILATIONDB_USE_ABSPATH=False, BUILD_DIR=fw_build_meta["build_dir"], IS_BASE_FIRMWARE=fw_build_meta["type"] == "firmware", FW_FLAVOR=fw_build_meta["flavor"], @@ -72,11 +76,16 @@ if not env["VERBOSE"]: HEXCOMSTR="\tHEX\t${TARGET}", BINCOMSTR="\tBIN\t${TARGET}", DFUCOMSTR="\tDFU\t${TARGET}", - OOCDCOMSTR="\tFLASH\t${SOURCE}", + OPENOCDCOMSTR="\tFLASH\t${SOURCE}", ) -if fw_build_meta["type"] == "updater": +if env["IS_BASE_FIRMWARE"]: + env.Append( + FIRMWARE_BUILD_CFG="firmware", + RAM_EXEC=False, + ) +else: env.Append( FIRMWARE_BUILD_CFG="updater", RAM_EXEC=True, @@ -84,13 +93,6 @@ if fw_build_meta["type"] == "updater": "FURI_RAM_EXEC", ], ) -else: - env.Append( - FIRMWARE_BUILD_CFG="firmware", - RAM_EXEC=False, - ) -# print(env.Dump()) - # Invoke child SCopscripts to populate global `env` + build their own part of the code lib_targets = env.BuildModules( @@ -116,8 +118,7 @@ else: fwenv.Append(APPS=["updater"]) if extra_int_apps := GetOption("extra_int_apps"): - for extra_int_app in extra_int_apps.split(","): - fwenv.Append(APPS=[extra_int_app]) + fwenv.Append(APPS=extra_int_apps.split(",")) fwenv.LoadApplicationManifests() fwenv.PrepareApplicationsBuild() @@ -131,17 +132,17 @@ fwenv.AppendUnique( CPPDEFINES=fwenv["APPBUILD"].get_apps_cdefs(), ) - # Build applications.c for selected services & apps - # Depends on virtual value-only node, so it only gets rebuilt when set of apps changes apps_c = fwenv.ApplicationsC( "applications/applications.c", Value(fwenv["APPS"]), ) +# Adding dependency on manifest files so apps.c is rebuilt when any manifest is changed +fwenv.Depends(apps_c, fwenv.GlobRecursive("*.fam", "applications")) sources = [apps_c] -# Gather sources only from app folders from current configuration +# Gather sources only from app folders in current configuration for app_folder in fwenv["APPBUILD"].get_builtin_app_folders(): sources += fwenv.GlobRecursive("*.c*", os.path.join("applications", app_folder)) @@ -192,47 +193,59 @@ fwelf = fwenv["FW_ELF"] = fwenv.Program( "appframe", "assets", "misc", + "mbedtls", + "loclass", # 2nd round "flipperformat", "toolbox", ], ) -# Make it depend on everything child builders returned + +# Firmware depends on everything child builders returned Depends(fwelf, lib_targets) +# Output extra details after building firmware AddPostAction(fwelf, fwenv["APPBUILD_DUMP"]) AddPostAction(fwelf, Action("@$SIZECOM")) - +# Produce extra firmware files fwhex = fwenv["FW_HEX"] = fwenv.HEXBuilder("${FIRMWARE_BUILD_CFG}") fwbin = fwenv["FW_BIN"] = fwenv.BINBuilder("${FIRMWARE_BUILD_CFG}") fwdfu = fwenv["FW_DFU"] = fwenv.DFUBuilder("${FIRMWARE_BUILD_CFG}") -# Default(dfu) Alias(fwenv["FIRMWARE_BUILD_CFG"] + "_dfu", fwdfu) fwdump = fwenv.ObjDump("${FIRMWARE_BUILD_CFG}") Alias(fwenv["FIRMWARE_BUILD_CFG"] + "_list", fwdump) -# Additional FW-related pseudotargets -flash = fwenv["FW_FLASH"] = fwenv.OOCDFlashCommand( - "${FIRMWARE_BUILD_CFG}", - OPENOCD_COMMAND='-c "program ${SOURCE.posix} reset exit ${IMAGE_BASE_ADDRESS}"', -) -if fwenv["FORCE"]: - AlwaysBuild(flash) -Alias(fwenv["FIRMWARE_BUILD_CFG"] + "_flash", flash) -if fwenv["IS_BASE_FIRMWARE"]: - Alias("flash", flash) - - -# Compile DB generation -fwcdb = fwenv["FW_CDB"] = fwenv.CompilationDatabase("compile_commands.json") -Alias(fwenv["FIRMWARE_BUILD_CFG"] + "_cdb", fwcdb) +fw_artifacts = fwenv["FW_ARTIFACTS"] = [ + fwhex, + fwbin, + fwdfu, + fwenv["FW_VERSION_JSON"], +] + +# If current configuration was explicitly requested, generate compilation database +# and link its directory as build/latest +if should_gen_cdb_and_link_dir(fwenv, BUILD_TARGETS): + fwcdb = fwenv.CompilationDatabase() + # without filtering, both updater & firmware commands would be generated + fwenv.Replace(COMPILATIONDB_PATH_FILTER=fwenv.subst("*${FW_FLAVOR}*")) + Depends(fwcdb, fwelf) + fw_artifacts.append(fwcdb) + + # Adding as a phony target, so folder link is updated even if elf didn't change + link_dir_command = fwenv.PhonyTarget( + fwenv.subst("${FIRMWARE_BUILD_CFG}_latest"), + Action( + lambda source, target, env: link_elf_dir_as_latest(env, source[0]), + None, + ), + source=fwelf, + ) + fw_artifacts.append(link_dir_command) -artifacts = [fwhex, fwbin, fwdfu, env["FW_VERSION_JSON"]] -fwenv["FW_ARTIFACTS"] = artifacts +Alias(fwenv["FIRMWARE_BUILD_CFG"] + "_all", fw_artifacts) -Alias(fwenv["FIRMWARE_BUILD_CFG"] + "_all", artifacts) Return("fwenv") diff --git a/firmware/targets/f7/ble_glue/hid_service.c b/firmware/targets/f7/ble_glue/hid_service.c index e30cdcb01b3..0efe1747b63 100644 --- a/firmware/targets/f7/ble_glue/hid_service.c +++ b/firmware/targets/f7/ble_glue/hid_service.c @@ -9,10 +9,9 @@ typedef struct { uint16_t svc_handle; uint16_t protocol_mode_char_handle; - uint16_t report_char_handle; - uint16_t report_ref_desc_handle; + uint16_t report_char_handle[HID_SVC_REPORT_COUNT]; + uint16_t report_ref_desc_handle[HID_SVC_REPORT_COUNT]; uint16_t report_map_char_handle; - uint16_t keyboard_boot_char_handle; uint16_t info_char_handle; uint16_t ctrl_point_char_handle; } HIDSvc; @@ -47,12 +46,22 @@ void hid_svc_start() { SVCCTL_RegisterSvcHandler(hid_svc_event_handler); // Add service svc_uuid.Service_UUID_16 = HUMAN_INTERFACE_DEVICE_SERVICE_UUID; - status = - aci_gatt_add_service(UUID_TYPE_16, &svc_uuid, PRIMARY_SERVICE, 30, &hid_svc->svc_handle); + /** + * Add Human Interface Device Service + */ + status = aci_gatt_add_service( + UUID_TYPE_16, + &svc_uuid, + PRIMARY_SERVICE, + 2 + /* protocol mode */ + (4 * HID_SVC_INPUT_REPORT_COUNT) + (3 * HID_SVC_OUTPUT_REPORT_COUNT) + + (3 * HID_SVC_FEATURE_REPORT_COUNT) + 1 + 2 + 2 + + 2, /* Service + Report Map + HID Information + HID Control Point */ + &hid_svc->svc_handle); if(status) { FURI_LOG_E(TAG, "Failed to add HID service: %d", status); } - // Add Protocol mode characterstics + // Add Protocol mode characteristics char_uuid.Char_UUID_16 = PROTOCOL_MODE_CHAR_UUID; status = aci_gatt_add_char( hid_svc->svc_handle, @@ -75,42 +84,120 @@ void hid_svc_start() { if(status) { FURI_LOG_E(TAG, "Failed to update protocol mode characteristic: %d", status); } - // Add Report characterstics - char_uuid.Char_UUID_16 = REPORT_CHAR_UUID; - status = aci_gatt_add_char( - hid_svc->svc_handle, - UUID_TYPE_16, - &char_uuid, - HID_SVC_REPORT_MAX_LEN, - CHAR_PROP_READ | CHAR_PROP_NOTIFY, - ATTR_PERMISSION_NONE, - GATT_DONT_NOTIFY_EVENTS, - 10, - CHAR_VALUE_LEN_VARIABLE, - &hid_svc->report_char_handle); - if(status) { - FURI_LOG_E(TAG, "Failed to add report characteristic: %d", status); - } - // Add Report descriptor - uint8_t desc_val[] = {0x00, 0x01}; - desc_uuid.Char_UUID_16 = REPORT_REFERENCE_DESCRIPTOR_UUID; - status = aci_gatt_add_char_desc( - hid_svc->svc_handle, - hid_svc->report_char_handle, - UUID_TYPE_16, - &desc_uuid, - HID_SVC_REPORT_REF_LEN, - HID_SVC_REPORT_REF_LEN, - desc_val, - ATTR_PERMISSION_NONE, - ATTR_ACCESS_READ_ONLY, - GATT_DONT_NOTIFY_EVENTS, - MIN_ENCRY_KEY_SIZE, - CHAR_VALUE_LEN_CONSTANT, - &hid_svc->report_ref_desc_handle); - if(status) { - FURI_LOG_E(TAG, "Failed to add report reference descriptor: %d", status); + +#if(HID_SVC_REPORT_COUNT != 0) + for(uint8_t i = 0; i < HID_SVC_REPORT_COUNT; i++) { + if(i < HID_SVC_INPUT_REPORT_COUNT) { + uint8_t buf[2] = {i + 1, 1}; // 1 input + char_uuid.Char_UUID_16 = REPORT_CHAR_UUID; + status = aci_gatt_add_char( + hid_svc->svc_handle, + UUID_TYPE_16, + &char_uuid, + HID_SVC_REPORT_MAX_LEN, + CHAR_PROP_READ | CHAR_PROP_NOTIFY, + ATTR_PERMISSION_NONE, + GATT_DONT_NOTIFY_EVENTS, + 10, + CHAR_VALUE_LEN_VARIABLE, + &(hid_svc->report_char_handle[i])); + if(status) { + FURI_LOG_E(TAG, "Failed to add report characteristic: %d", status); + } + + desc_uuid.Char_UUID_16 = REPORT_REFERENCE_DESCRIPTOR_UUID; + status = aci_gatt_add_char_desc( + hid_svc->svc_handle, + hid_svc->report_char_handle[i], + UUID_TYPE_16, + &desc_uuid, + HID_SVC_REPORT_REF_LEN, + HID_SVC_REPORT_REF_LEN, + buf, + ATTR_PERMISSION_NONE, + ATTR_ACCESS_READ_WRITE, + GATT_DONT_NOTIFY_EVENTS, + MIN_ENCRY_KEY_SIZE, + CHAR_VALUE_LEN_CONSTANT, + &(hid_svc->report_ref_desc_handle[i])); + if(status) { + FURI_LOG_E(TAG, "Failed to add report reference descriptor: %d", status); + } + } else if((i - HID_SVC_INPUT_REPORT_COUNT) < HID_SVC_OUTPUT_REPORT_COUNT) { + uint8_t buf[2] = {i + 1, 2}; // 2 output + char_uuid.Char_UUID_16 = REPORT_CHAR_UUID; + status = aci_gatt_add_char( + hid_svc->svc_handle, + UUID_TYPE_16, + &char_uuid, + HID_SVC_REPORT_MAX_LEN, + CHAR_PROP_READ | CHAR_PROP_NOTIFY, + ATTR_PERMISSION_NONE, + GATT_DONT_NOTIFY_EVENTS, + 10, + CHAR_VALUE_LEN_VARIABLE, + &(hid_svc->report_char_handle[i])); + if(status) { + FURI_LOG_E(TAG, "Failed to add report characteristic: %d", status); + } + + desc_uuid.Char_UUID_16 = REPORT_REFERENCE_DESCRIPTOR_UUID; + status = aci_gatt_add_char_desc( + hid_svc->svc_handle, + hid_svc->report_char_handle[i], + UUID_TYPE_16, + &desc_uuid, + HID_SVC_REPORT_REF_LEN, + HID_SVC_REPORT_REF_LEN, + buf, + ATTR_PERMISSION_NONE, + ATTR_ACCESS_READ_WRITE, + GATT_DONT_NOTIFY_EVENTS, + MIN_ENCRY_KEY_SIZE, + CHAR_VALUE_LEN_CONSTANT, + &(hid_svc->report_ref_desc_handle[i])); + if(status) { + FURI_LOG_E(TAG, "Failed to add report reference descriptor: %d", status); + } + } else { + uint8_t buf[2] = {i + 1, 3}; // 3 feature + char_uuid.Char_UUID_16 = REPORT_CHAR_UUID; + status = aci_gatt_add_char( + hid_svc->svc_handle, + UUID_TYPE_16, + &char_uuid, + HID_SVC_REPORT_MAX_LEN, + CHAR_PROP_READ | CHAR_PROP_NOTIFY, + ATTR_PERMISSION_NONE, + GATT_DONT_NOTIFY_EVENTS, + 10, + CHAR_VALUE_LEN_VARIABLE, + &(hid_svc->report_char_handle[i])); + if(status) { + FURI_LOG_E(TAG, "Failed to add report characteristic: %d", status); + } + + desc_uuid.Char_UUID_16 = REPORT_REFERENCE_DESCRIPTOR_UUID; + status = aci_gatt_add_char_desc( + hid_svc->svc_handle, + hid_svc->report_char_handle[i], + UUID_TYPE_16, + &desc_uuid, + HID_SVC_REPORT_REF_LEN, + HID_SVC_REPORT_REF_LEN, + buf, + ATTR_PERMISSION_NONE, + ATTR_ACCESS_READ_WRITE, + GATT_DONT_NOTIFY_EVENTS, + MIN_ENCRY_KEY_SIZE, + CHAR_VALUE_LEN_CONSTANT, + &(hid_svc->report_ref_desc_handle[i])); + if(status) { + FURI_LOG_E(TAG, "Failed to add report reference descriptor: %d", status); + } + } } +#endif // Add Report Map characteristic char_uuid.Char_UUID_16 = REPORT_MAP_CHAR_UUID; status = aci_gatt_add_char( @@ -127,22 +214,7 @@ void hid_svc_start() { if(status) { FURI_LOG_E(TAG, "Failed to add report map characteristic: %d", status); } - // Add Boot Keyboard characteristic - char_uuid.Char_UUID_16 = BOOT_KEYBOARD_INPUT_REPORT_CHAR_UUID; - status = aci_gatt_add_char( - hid_svc->svc_handle, - UUID_TYPE_16, - &char_uuid, - HID_SVC_BOOT_KEYBOARD_INPUT_REPORT_MAX_LEN, - CHAR_PROP_READ | CHAR_PROP_NOTIFY, - ATTR_PERMISSION_NONE, - GATT_NOTIFY_WRITE_REQ_AND_WAIT_FOR_APPL_RESP, - 10, - CHAR_VALUE_LEN_VARIABLE, - &hid_svc->keyboard_boot_char_handle); - if(status) { - FURI_LOG_E(TAG, "Failed to add report map characteristic: %d", status); - } + // Add Information characteristic char_uuid.Char_UUID_16 = HID_INFORMATION_CHAR_UUID; status = aci_gatt_add_char( @@ -177,27 +249,27 @@ void hid_svc_start() { } } -bool hid_svc_update_report_map(uint8_t* data, uint16_t len) { +bool hid_svc_update_report_map(const uint8_t* data, uint16_t len) { furi_assert(data); furi_assert(hid_svc); tBleStatus status = aci_gatt_update_char_value( hid_svc->svc_handle, hid_svc->report_map_char_handle, 0, len, data); if(status) { - FURI_LOG_E(TAG, "Failed updating report map characteristic"); + FURI_LOG_E(TAG, "Failed updating report map characteristic: %d", status); return false; } return true; } -bool hid_svc_update_input_report(uint8_t* data, uint16_t len) { +bool hid_svc_update_input_report(uint8_t input_report_num, uint8_t* data, uint16_t len) { furi_assert(data); furi_assert(hid_svc); - tBleStatus status = - aci_gatt_update_char_value(hid_svc->svc_handle, hid_svc->report_char_handle, 0, len, data); + tBleStatus status = aci_gatt_update_char_value( + hid_svc->svc_handle, hid_svc->report_char_handle[input_report_num], 0, len, data); if(status) { - FURI_LOG_E(TAG, "Failed updating report characteristic"); + FURI_LOG_E(TAG, "Failed updating report characteristic: %d", status); return false; } return true; @@ -210,7 +282,7 @@ bool hid_svc_update_info(uint8_t* data, uint16_t len) { tBleStatus status = aci_gatt_update_char_value(hid_svc->svc_handle, hid_svc->info_char_handle, 0, len, data); if(status) { - FURI_LOG_E(TAG, "Failed updating info characteristic"); + FURI_LOG_E(TAG, "Failed updating info characteristic: %d", status); return false; } return true; @@ -228,18 +300,18 @@ void hid_svc_stop() { if(status) { FURI_LOG_E(TAG, "Failed to delete Report Map characteristic: %d", status); } - status = aci_gatt_del_char(hid_svc->svc_handle, hid_svc->report_char_handle); - if(status) { - FURI_LOG_E(TAG, "Failed to delete Report characteristic: %d", status); +#if(HID_SVC_INPUT_REPORT_COUNT != 0) + for(uint8_t i = 0; i < HID_SVC_REPORT_COUNT; i++) { + status = aci_gatt_del_char(hid_svc->svc_handle, hid_svc->report_char_handle[i]); + if(status) { + FURI_LOG_E(TAG, "Failed to delete Report characteristic: %d", status); + } } +#endif status = aci_gatt_del_char(hid_svc->svc_handle, hid_svc->protocol_mode_char_handle); if(status) { FURI_LOG_E(TAG, "Failed to delete Protocol Mode characteristic: %d", status); } - status = aci_gatt_del_char(hid_svc->svc_handle, hid_svc->keyboard_boot_char_handle); - if(status) { - FURI_LOG_E(TAG, "Failed to delete Keyboard Boot characteristic: %d", status); - } status = aci_gatt_del_char(hid_svc->svc_handle, hid_svc->info_char_handle); if(status) { FURI_LOG_E(TAG, "Failed to delete Information characteristic: %d", status); diff --git a/firmware/targets/f7/ble_glue/hid_service.h b/firmware/targets/f7/ble_glue/hid_service.h index ed1394beb6b..723460d496a 100644 --- a/firmware/targets/f7/ble_glue/hid_service.h +++ b/firmware/targets/f7/ble_glue/hid_service.h @@ -3,21 +3,26 @@ #include #include -#define HID_SVC_REPORT_MAP_MAX_LEN (120) -#define HID_SVC_REPORT_MAX_LEN (9) -#define HID_SVC_BOOT_KEYBOARD_INPUT_REPORT_MAX_LEN (8) +#define HID_SVC_REPORT_MAP_MAX_LEN (255) +#define HID_SVC_REPORT_MAX_LEN (255) #define HID_SVC_REPORT_REF_LEN (2) #define HID_SVC_INFO_LEN (4) #define HID_SVC_CONTROL_POINT_LEN (1) +#define HID_SVC_INPUT_REPORT_COUNT (3) +#define HID_SVC_OUTPUT_REPORT_COUNT (0) +#define HID_SVC_FEATURE_REPORT_COUNT (0) +#define HID_SVC_REPORT_COUNT \ + (HID_SVC_INPUT_REPORT_COUNT + HID_SVC_OUTPUT_REPORT_COUNT + HID_SVC_FEATURE_REPORT_COUNT) + void hid_svc_start(); void hid_svc_stop(); bool hid_svc_is_started(); -bool hid_svc_update_report_map(uint8_t* data, uint16_t len); +bool hid_svc_update_report_map(const uint8_t* data, uint16_t len); -bool hid_svc_update_input_report(uint8_t* data, uint16_t len); +bool hid_svc_update_input_report(uint8_t input_report_num, uint8_t* data, uint16_t len); bool hid_svc_update_info(uint8_t* data, uint16_t len); diff --git a/firmware/targets/f7/furi_hal/furi_hal_bt.c b/firmware/targets/f7/furi_hal/furi_hal_bt.c index ad12d773f80..dde3842d0af 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_bt.c +++ b/firmware/targets/f7/furi_hal/furi_hal_bt.c @@ -218,8 +218,8 @@ bool furi_hal_bt_start_app(FuriHalBtProfile profile, GapEventCallback event_cb, } else if(profile == FuriHalBtProfileHidKeyboard) { // Change MAC address for HID profile config->mac_address[2]++; - // Change name Flipper -> Keynote - const char* clicker_str = "Keynote"; + // Change name Flipper -> Control + const char* clicker_str = "Control"; memcpy(&config->adv_name[1], clicker_str, strlen(clicker_str)); } if(!gap_init(config, event_cb, context)) { diff --git a/firmware/targets/f7/furi_hal/furi_hal_bt_hid.c b/firmware/targets/f7/furi_hal/furi_hal_bt_hid.c index 507dedfb260..22415199cbd 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_bt_hid.c +++ b/firmware/targets/f7/furi_hal/furi_hal_bt_hid.c @@ -1,4 +1,6 @@ #include "furi_hal_bt_hid.h" +#include "furi_hal_usb_hid.h" +#include "usb_hid.h" #include "dev_info_service.h" #include "battery_service.h" #include "hid_service.h" @@ -10,128 +12,118 @@ #define FURI_HAL_BT_HID_INFO_FLAG_REMOTE_WAKE_MSK (0x01) #define FURI_HAL_BT_HID_INFO_FLAG_NORMALLY_CONNECTABLE_MSK (0x02) -#define FURI_HAL_BT_HID_KB_KEYS_MAX (6) +#define FURI_HAL_BT_HID_KB_MAX_KEYS 6 +#define FURI_HAL_BT_HID_CONSUMER_MAX_KEYS 1 + +// Report ids cant be 0 +enum HidReportId { + ReportIdKeyboard = 1, + ReportIdMouse = 2, + ReportIdConsumer = 3, +}; +// Report numbers corresponded to the report id with an offset of 1 +enum HidInputNumber { + ReportNumberKeyboard = 0, + ReportNumberMouse = 1, + ReportNumberConsumer = 2, +}; typedef struct { - // uint8_t report_id; uint8_t mods; uint8_t reserved; - uint8_t key[FURI_HAL_BT_HID_KB_KEYS_MAX]; -} FuriHalBtHidKbReport; + uint8_t key[FURI_HAL_BT_HID_KB_MAX_KEYS]; +} __attribute__((__packed__)) FuriHalBtHidKbReport; typedef struct { - uint8_t report_id; - uint8_t key; -} FuriHalBtHidMediaReport; - -// TODO make composite HID device -static uint8_t furi_hal_bt_hid_report_map_data[] = { - 0x05, - 0x01, // Usage Page (Generic Desktop) - 0x09, - 0x06, // Usage (Keyboard) - 0xA1, - 0x01, // Collection (Application) - // 0x85, 0x01, // Report ID (1) - 0x05, - 0x07, // Usage Page (Key Codes) - 0x19, - 0xe0, // Usage Minimum (224) - 0x29, - 0xe7, // Usage Maximum (231) - 0x15, - 0x00, // Logical Minimum (0) - 0x25, - 0x01, // Logical Maximum (1) - 0x75, - 0x01, // Report Size (1) - 0x95, - 0x08, // Report Count (8) - 0x81, - 0x02, // Input (Data, Variable, Absolute) - - 0x95, - 0x01, // Report Count (1) - 0x75, - 0x08, // Report Size (8) - 0x81, - 0x01, // Input (Constant) reserved byte(1) - - 0x95, - 0x05, // Report Count (5) - 0x75, - 0x01, // Report Size (1) - 0x05, - 0x08, // Usage Page (Page# for LEDs) - 0x19, - 0x01, // Usage Minimum (1) - 0x29, - 0x05, // Usage Maximum (5) - 0x91, - 0x02, // Output (Data, Variable, Absolute), Led report - 0x95, - 0x01, // Report Count (1) - 0x75, - 0x03, // Report Size (3) - 0x91, - 0x01, // Output (Data, Variable, Absolute), Led report padding - - 0x95, - 0x06, // Report Count (6) - 0x75, - 0x08, // Report Size (8) - 0x15, - 0x00, // Logical Minimum (0) - 0x25, - 0x65, // Logical Maximum (101) - 0x05, - 0x07, // Usage Page (Key codes) - 0x19, - 0x00, // Usage Minimum (0) - 0x29, - 0x65, // Usage Maximum (101) - 0x81, - 0x00, // Input (Data, Array) Key array(6 bytes) - - 0x09, - 0x05, // Usage (Vendor Defined) - 0x15, - 0x00, // Logical Minimum (0) - 0x26, - 0xFF, - 0x00, // Logical Maximum (255) - 0x75, - 0x08, // Report Size (8 bit) - 0x95, - 0x02, // Report Count (2) - 0xB1, - 0x02, // Feature (Data, Variable, Absolute) - - 0xC0, // End Collection (Application) - - // 0x05, 0x0C, // Usage Page (Consumer) - // 0x09, 0x01, // Usage (Consumer Control) - // 0xA1, 0x01, // Collection (Application) - // 0x85, 0x02, // Report ID (2) - // 0x05, 0x0C, // Usage Page (Consumer) - // 0x15, 0x00, // Logical Minimum (0) - // 0x25, 0x01, // Logical Maximum (1) - // 0x75, 0x01, // Report Size (1) - // 0x95, 0x07, // Report Count (7) - // 0x09, 0xB5, // Usage (Scan Next Track) - // 0x09, 0xB6, // Usage (Scan Previous Track) - // 0x09, 0xB7, // Usage (Stop) - // 0x09, 0xB8, // Usage (Eject) - // 0x09, 0xCD, // Usage (Play/Pause) - // 0x09, 0xE2, // Usage (Mute) - // 0x09, 0xE9, // Usage (Volume Increment) - // 0x09, 0xEA, // Usage (Volume Decrement) - // 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) - // 0xC0, // End Collection -}; + uint8_t btn; + int8_t x; + int8_t y; + int8_t wheel; +} __attribute__((__packed__)) FuriHalBtHidMouseReport; +typedef struct { + uint16_t key[FURI_HAL_BT_HID_CONSUMER_MAX_KEYS]; +} __attribute__((__packed__)) FuriHalBtHidConsumerReport; + +// keyboard+mouse+consumer hid report +static const uint8_t furi_hal_bt_hid_report_map_data[] = { + // Keyboard Report + HID_USAGE_PAGE(HID_PAGE_DESKTOP), + HID_USAGE(HID_DESKTOP_KEYBOARD), + HID_COLLECTION(HID_APPLICATION_COLLECTION), + HID_REPORT_ID(ReportIdKeyboard), + HID_USAGE_PAGE(HID_DESKTOP_KEYPAD), + HID_USAGE_MINIMUM(HID_KEYBOARD_L_CTRL), + HID_USAGE_MAXIMUM(HID_KEYBOARD_R_GUI), + HID_LOGICAL_MINIMUM(0), + HID_LOGICAL_MAXIMUM(1), + HID_REPORT_SIZE(1), + HID_REPORT_COUNT(8), + HID_INPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), + HID_REPORT_COUNT(1), + HID_REPORT_SIZE(8), + HID_INPUT(HID_IOF_CONSTANT | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), + HID_USAGE_PAGE(HID_PAGE_LED), + HID_REPORT_COUNT(8), + HID_REPORT_SIZE(1), + HID_USAGE_MINIMUM(1), + HID_USAGE_MAXIMUM(8), + HID_OUTPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), + HID_REPORT_COUNT(FURI_HAL_BT_HID_KB_MAX_KEYS), + HID_REPORT_SIZE(8), + HID_LOGICAL_MINIMUM(0), + HID_LOGICAL_MAXIMUM(101), + HID_USAGE_PAGE(HID_DESKTOP_KEYPAD), + HID_USAGE_MINIMUM(0), + HID_USAGE_MAXIMUM(101), + HID_INPUT(HID_IOF_DATA | HID_IOF_ARRAY | HID_IOF_ABSOLUTE), + HID_END_COLLECTION, + // Mouse Report + HID_USAGE_PAGE(HID_PAGE_DESKTOP), + HID_USAGE(HID_DESKTOP_MOUSE), + HID_COLLECTION(HID_APPLICATION_COLLECTION), + HID_USAGE(HID_DESKTOP_POINTER), + HID_COLLECTION(HID_PHYSICAL_COLLECTION), + HID_REPORT_ID(ReportIdMouse), + HID_USAGE_PAGE(HID_PAGE_BUTTON), + HID_USAGE_MINIMUM(1), + HID_USAGE_MAXIMUM(3), + HID_LOGICAL_MINIMUM(0), + HID_LOGICAL_MAXIMUM(1), + HID_REPORT_COUNT(3), + HID_REPORT_SIZE(1), + HID_INPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), + HID_REPORT_SIZE(1), + HID_REPORT_COUNT(5), + HID_INPUT(HID_IOF_CONSTANT | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), + HID_USAGE_PAGE(HID_PAGE_DESKTOP), + HID_USAGE(HID_DESKTOP_X), + HID_USAGE(HID_DESKTOP_Y), + HID_USAGE(HID_DESKTOP_WHEEL), + HID_LOGICAL_MINIMUM(-127), + HID_LOGICAL_MAXIMUM(127), + HID_REPORT_SIZE(8), + HID_REPORT_COUNT(3), + HID_INPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_RELATIVE), + HID_END_COLLECTION, + HID_END_COLLECTION, + // Consumer Report + HID_USAGE_PAGE(HID_PAGE_CONSUMER), + HID_USAGE(HID_CONSUMER_CONTROL), + HID_COLLECTION(HID_APPLICATION_COLLECTION), + HID_REPORT_ID(ReportIdConsumer), + HID_LOGICAL_MINIMUM(0), + HID_RI_LOGICAL_MAXIMUM(16, 0x3FF), + HID_USAGE_MINIMUM(0), + HID_RI_USAGE_MAXIMUM(16, 0x3FF), + HID_REPORT_COUNT(FURI_HAL_BT_HID_CONSUMER_MAX_KEYS), + HID_REPORT_SIZE(16), + HID_INPUT(HID_IOF_DATA | HID_IOF_ARRAY | HID_IOF_ABSOLUTE), + HID_END_COLLECTION, +}; FuriHalBtHidKbReport* kb_report = NULL; -FuriHalBtHidMediaReport* media_report = NULL; +FuriHalBtHidMouseReport* mouse_report = NULL; +FuriHalBtHidConsumerReport* consumer_report = NULL; void furi_hal_bt_hid_start() { // Start device info @@ -148,7 +140,8 @@ void furi_hal_bt_hid_start() { } // Configure HID Keyboard kb_report = malloc(sizeof(FuriHalBtHidKbReport)); - media_report = malloc(sizeof(FuriHalBtHidMediaReport)); + mouse_report = malloc(sizeof(FuriHalBtHidMouseReport)); + consumer_report = malloc(sizeof(FuriHalBtHidConsumerReport)); // Configure Report Map characteristic hid_svc_update_report_map( furi_hal_bt_hid_report_map_data, sizeof(furi_hal_bt_hid_report_map_data)); @@ -165,6 +158,8 @@ void furi_hal_bt_hid_start() { void furi_hal_bt_hid_stop() { furi_assert(kb_report); + furi_assert(mouse_report); + furi_assert(consumer_report); // Stop all services if(dev_info_svc_is_started()) { dev_info_svc_stop(); @@ -176,61 +171,119 @@ void furi_hal_bt_hid_stop() { hid_svc_stop(); } free(kb_report); - free(media_report); - media_report = NULL; + free(mouse_report); + free(consumer_report); kb_report = NULL; + mouse_report = NULL; + consumer_report = NULL; } bool furi_hal_bt_hid_kb_press(uint16_t button) { furi_assert(kb_report); - // kb_report->report_id = 0x01; - for(uint8_t i = 0; i < FURI_HAL_BT_HID_KB_KEYS_MAX; i++) { + for(uint8_t i = 0; i < FURI_HAL_BT_HID_KB_MAX_KEYS; i++) { if(kb_report->key[i] == 0) { kb_report->key[i] = button & 0xFF; break; } } kb_report->mods |= (button >> 8); - return hid_svc_update_input_report((uint8_t*)kb_report, sizeof(FuriHalBtHidKbReport)); + return hid_svc_update_input_report( + ReportNumberKeyboard, (uint8_t*)kb_report, sizeof(FuriHalBtHidKbReport)); } bool furi_hal_bt_hid_kb_release(uint16_t button) { furi_assert(kb_report); - // kb_report->report_id = 0x01; - for(uint8_t i = 0; i < FURI_HAL_BT_HID_KB_KEYS_MAX; i++) { + for(uint8_t i = 0; i < FURI_HAL_BT_HID_KB_MAX_KEYS; i++) { if(kb_report->key[i] == (button & 0xFF)) { kb_report->key[i] = 0; break; } } kb_report->mods &= ~(button >> 8); - return hid_svc_update_input_report((uint8_t*)kb_report, sizeof(FuriHalBtHidKbReport)); + return hid_svc_update_input_report( + ReportNumberKeyboard, (uint8_t*)kb_report, sizeof(FuriHalBtHidKbReport)); } bool furi_hal_bt_hid_kb_release_all() { furi_assert(kb_report); - // kb_report->report_id = 0x01; - memset(kb_report, 0, sizeof(FuriHalBtHidKbReport)); - return hid_svc_update_input_report((uint8_t*)kb_report, sizeof(FuriHalBtHidKbReport)); + for(uint8_t i = 0; i < FURI_HAL_BT_HID_KB_MAX_KEYS; i++) { + kb_report->key[i] = 0; + } + kb_report->mods = 0; + return hid_svc_update_input_report( + ReportNumberKeyboard, (uint8_t*)kb_report, sizeof(FuriHalBtHidKbReport)); +} + +bool furi_hal_bt_hid_consumer_key_press(uint16_t button) { + furi_assert(consumer_report); + for(uint8_t i = 0; i < FURI_HAL_BT_HID_CONSUMER_MAX_KEYS; i++) { + if(consumer_report->key[i] == 0) { + consumer_report->key[i] = button; + break; + } + } + return hid_svc_update_input_report( + ReportNumberConsumer, (uint8_t*)consumer_report, sizeof(FuriHalBtHidConsumerReport)); +} + +bool furi_hal_bt_hid_consumer_key_release(uint16_t button) { + furi_assert(consumer_report); + for(uint8_t i = 0; i < FURI_HAL_BT_HID_CONSUMER_MAX_KEYS; i++) { + if(consumer_report->key[i] == button) { + consumer_report->key[i] = 0; + break; + } + } + return hid_svc_update_input_report( + ReportNumberConsumer, (uint8_t*)consumer_report, sizeof(FuriHalBtHidConsumerReport)); +} + +bool furi_hal_bt_hid_consumer_key_release_all() { + furi_assert(consumer_report); + for(uint8_t i = 0; i < FURI_HAL_BT_HID_CONSUMER_MAX_KEYS; i++) { + consumer_report->key[i] = 0; + } + return hid_svc_update_input_report( + ReportNumberConsumer, (uint8_t*)consumer_report, sizeof(FuriHalBtHidConsumerReport)); +} + +bool furi_hal_bt_hid_mouse_move(int8_t dx, int8_t dy) { + furi_assert(mouse_report); + mouse_report->x = dx; + mouse_report->y = dy; + bool state = hid_svc_update_input_report( + ReportNumberMouse, (uint8_t*)mouse_report, sizeof(FuriHalBtHidMouseReport)); + mouse_report->x = 0; + mouse_report->y = 0; + return state; +} + +bool furi_hal_bt_hid_mouse_press(uint8_t button) { + furi_assert(mouse_report); + mouse_report->btn |= button; + return hid_svc_update_input_report( + ReportNumberMouse, (uint8_t*)mouse_report, sizeof(FuriHalBtHidMouseReport)); } -bool furi_hal_bt_hid_media_press(uint8_t button) { - furi_assert(media_report); - media_report->report_id = 0x02; - media_report->key |= (0x01 << button); - return hid_svc_update_input_report((uint8_t*)media_report, sizeof(FuriHalBtHidMediaReport)); +bool furi_hal_bt_hid_mouse_release(uint8_t button) { + furi_assert(mouse_report); + mouse_report->btn &= ~button; + return hid_svc_update_input_report( + ReportNumberMouse, (uint8_t*)mouse_report, sizeof(FuriHalBtHidMouseReport)); } -bool furi_hal_bt_hid_media_release(uint8_t button) { - furi_assert(media_report); - media_report->report_id = 0x02; - media_report->key &= ~(0x01 << button); - return hid_svc_update_input_report((uint8_t*)media_report, sizeof(FuriHalBtHidMediaReport)); +bool furi_hal_bt_hid_mouse_release_all() { + furi_assert(mouse_report); + mouse_report->btn = 0; + return hid_svc_update_input_report( + ReportNumberMouse, (uint8_t*)mouse_report, sizeof(FuriHalBtHidMouseReport)); } -bool furi_hal_bt_hid_media_release_all() { - furi_assert(media_report); - media_report->report_id = 0x02; - media_report->key = 0x00; - return hid_svc_update_input_report((uint8_t*)media_report, sizeof(FuriHalBtHidMediaReport)); +bool furi_hal_bt_hid_mouse_scroll(int8_t delta) { + furi_assert(mouse_report); + mouse_report->wheel = delta; + bool state = hid_svc_update_input_report( + ReportNumberMouse, (uint8_t*)mouse_report, sizeof(FuriHalBtHidMouseReport)); + mouse_report->wheel = 0; + return state; } diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc.c b/firmware/targets/f7/furi_hal/furi_hal_nfc.c index cba2a703097..73f83c5c912 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_nfc.c +++ b/firmware/targets/f7/furi_hal/furi_hal_nfc.c @@ -8,6 +8,9 @@ #include #include +#include +#include +#include #define TAG "FuriHalNfc" @@ -19,6 +22,8 @@ osEventFlagsId_t event = NULL; #define EVENT_FLAG_STOP (1UL << 2) #define EVENT_FLAG_ALL (EVENT_FLAG_INTERRUPT | EVENT_FLAG_STATE_CHANGED | EVENT_FLAG_STOP) +#define FURI_HAL_NFC_UID_INCOMPLETE (0x04) + void furi_hal_nfc_init() { ReturnCode ret = rfalNfcInitialize(); if(ret == ERR_NONE) { @@ -243,6 +248,109 @@ bool furi_hal_nfc_listen( return true; } +static void furi_hal_nfc_read_fifo(uint8_t* data, uint16_t* bits) { + uint8_t fifo_status[2]; + uint8_t rx_buff[64]; + + st25r3916ReadMultipleRegisters( + ST25R3916_REG_FIFO_STATUS1, fifo_status, ST25R3916_FIFO_STATUS_LEN); + uint16_t rx_bytes = + ((((uint16_t)fifo_status[1] & ST25R3916_REG_FIFO_STATUS2_fifo_b_mask) >> + ST25R3916_REG_FIFO_STATUS2_fifo_b_shift) + << 8); + rx_bytes |= (((uint16_t)fifo_status[0]) & 0x00FFU); + st25r3916ReadFifo(rx_buff, rx_bytes); + + memcpy(data, rx_buff, rx_bytes); + *bits = rx_bytes * 8; +} + +void furi_hal_nfc_listen_sleep() { + st25r3916ExecuteCommand(ST25R3916_CMD_GOTO_SLEEP); +} + +bool furi_hal_nfc_listen_rx(FuriHalNfcTxRxContext* tx_rx, uint32_t timeout_ms) { + furi_assert(tx_rx); + + // Wait for interrupts + uint32_t start = osKernelGetTickCount(); + bool data_received = false; + while(true) { + if(furi_hal_gpio_read(&gpio_nfc_irq_rfid_pull) == true) { + st25r3916CheckForReceivedInterrupts(); + if(st25r3916GetInterrupt(ST25R3916_IRQ_MASK_RXE)) { + furi_hal_nfc_read_fifo(tx_rx->rx_data, &tx_rx->rx_bits); + data_received = true; + break; + } + continue; + } + if(osKernelGetTickCount() - start > timeout_ms) { + FURI_LOG_T(TAG, "Interrupt waiting timeout"); + osDelay(1); + break; + } + } + + return data_received; +} + +void furi_hal_nfc_listen_start(FuriHalNfcDevData* nfc_data) { + furi_assert(nfc_data); + + furi_hal_gpio_init(&gpio_nfc_irq_rfid_pull, GpioModeInput, GpioPullDown, GpioSpeedVeryHigh); + // Clear interrupts + st25r3916ClearInterrupts(); + // Mask all interrupts + st25r3916DisableInterrupts(ST25R3916_IRQ_MASK_ALL); + // RESET + st25r3916ExecuteCommand(ST25R3916_CMD_STOP); + // Setup registers + st25r3916WriteRegister( + ST25R3916_REG_OP_CONTROL, + ST25R3916_REG_OP_CONTROL_en | ST25R3916_REG_OP_CONTROL_rx_en | + ST25R3916_REG_OP_CONTROL_en_fd_auto_efd); + st25r3916WriteRegister( + ST25R3916_REG_MODE, + ST25R3916_REG_MODE_targ_targ | ST25R3916_REG_MODE_om3 | ST25R3916_REG_MODE_om0); + st25r3916WriteRegister( + ST25R3916_REG_PASSIVE_TARGET, + ST25R3916_REG_PASSIVE_TARGET_fdel_2 | ST25R3916_REG_PASSIVE_TARGET_fdel_0 | + ST25R3916_REG_PASSIVE_TARGET_d_ac_ap2p | ST25R3916_REG_PASSIVE_TARGET_d_212_424_1r); + st25r3916WriteRegister(ST25R3916_REG_MASK_RX_TIMER, 0x02); + + // Mask interrupts + uint32_t clear_irq_mask = + (ST25R3916_IRQ_MASK_RXE | ST25R3916_IRQ_MASK_RXE_PTA | ST25R3916_IRQ_MASK_WU_A_X | + ST25R3916_IRQ_MASK_WU_A); + st25r3916EnableInterrupts(clear_irq_mask); + + // Set 4 or 7 bytes UID + if(nfc_data->uid_len == 4) { + st25r3916ChangeRegisterBits( + ST25R3916_REG_AUX, ST25R3916_REG_AUX_nfc_id_mask, ST25R3916_REG_AUX_nfc_id_4bytes); + } else { + st25r3916ChangeRegisterBits( + ST25R3916_REG_AUX, ST25R3916_REG_AUX_nfc_id_mask, ST25R3916_REG_AUX_nfc_id_7bytes); + } + // Write PT Memory + uint8_t pt_memory[15] = {}; + memcpy(pt_memory, nfc_data->uid, nfc_data->uid_len); + pt_memory[10] = nfc_data->atqa[0]; + pt_memory[11] = nfc_data->atqa[1]; + if(nfc_data->uid_len == 4) { + pt_memory[12] = nfc_data->sak & ~FURI_HAL_NFC_UID_INCOMPLETE; + } else { + pt_memory[12] = nfc_data->sak | FURI_HAL_NFC_UID_INCOMPLETE; + } + pt_memory[13] = nfc_data->sak & ~FURI_HAL_NFC_UID_INCOMPLETE; + pt_memory[14] = nfc_data->sak & ~FURI_HAL_NFC_UID_INCOMPLETE; + + st25r3916WritePTMem(pt_memory, sizeof(pt_memory)); + // Go to sence + st25r3916ExecuteCommand(ST25R3916_CMD_GOTO_SENSE); +} + void rfal_interrupt_callback_handler() { osEventFlagsSet(event, EVENT_FLAG_INTERRUPT); } @@ -369,23 +477,18 @@ bool furi_hal_nfc_emulate_nfca( static bool furi_hal_nfc_transparent_tx_rx(FuriHalNfcTxRxContext* tx_rx, uint16_t timeout_ms) { furi_assert(tx_rx->nfca_signal); - platformDisableIrqCallback(); - bool ret = false; // Start transparent mode st25r3916ExecuteCommand(ST25R3916_CMD_TRANSPARENT_MODE); - // Reconfigure gpio + // Reconfigure gpio for Transparent mode furi_hal_spi_bus_handle_deinit(&furi_hal_spi_bus_handle_nfc); - furi_hal_gpio_init(&gpio_spi_r_sck, GpioModeInput, GpioPullUp, GpioSpeedLow); - furi_hal_gpio_init(&gpio_spi_r_miso, GpioModeInput, GpioPullUp, GpioSpeedLow); - furi_hal_gpio_init(&gpio_nfc_cs, GpioModeInput, GpioPullUp, GpioSpeedLow); - furi_hal_gpio_init(&gpio_spi_r_mosi, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); - furi_hal_gpio_write(&gpio_spi_r_mosi, false); // Send signal + FURI_CRITICAL_ENTER(); nfca_signal_encode(tx_rx->nfca_signal, tx_rx->tx_data, tx_rx->tx_bits, tx_rx->tx_parity); digital_signal_send(tx_rx->nfca_signal->tx_signal, &gpio_spi_r_mosi); + FURI_CRITICAL_EXIT(); furi_hal_gpio_write(&gpio_spi_r_mosi, false); // Configure gpio back to SPI and exit transparent @@ -443,7 +546,6 @@ static bool furi_hal_nfc_transparent_tx_rx(FuriHalNfcTxRxContext* tx_rx, uint16_ } st25r3916ClearInterrupts(); - platformEnableIrqCallback(); return ret; } diff --git a/firmware/targets/f7/furi_hal/furi_hal_power.c b/firmware/targets/f7/furi_hal/furi_hal_power.c index 52bb7bcfe31..28e6cb051f9 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_power.c +++ b/firmware/targets/f7/furi_hal/furi_hal_power.c @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -299,6 +300,10 @@ void furi_hal_power_shutdown() { } void furi_hal_power_off() { + // Crutch: shutting down with ext 3V3 off is causing LSE to stop + furi_hal_power_enable_external_3_3v(); + furi_hal_delay_us(1000); + // Send poweroff to charger furi_hal_i2c_acquire(&furi_hal_i2c_handle_power); bq25896_poweroff(&furi_hal_i2c_handle_power); furi_hal_i2c_release(&furi_hal_i2c_handle_power); @@ -482,11 +487,11 @@ void furi_hal_power_dump_state() { } void furi_hal_power_enable_external_3_3v() { - LL_GPIO_SetOutputPin(PERIPH_POWER_GPIO_Port, PERIPH_POWER_Pin); + furi_hal_gpio_write(&periph_power, 1); } void furi_hal_power_disable_external_3_3v() { - LL_GPIO_ResetOutputPin(PERIPH_POWER_GPIO_Port, PERIPH_POWER_Pin); + furi_hal_gpio_write(&periph_power, 0); } void furi_hal_power_suppress_charge_enter() { diff --git a/firmware/targets/f7/furi_hal/furi_hal_resources.c b/firmware/targets/f7/furi_hal/furi_hal_resources.c index 21fac8346d1..c1238212a37 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_resources.c +++ b/firmware/targets/f7/furi_hal/furi_hal_resources.c @@ -58,7 +58,7 @@ const GpioPin gpio_i2c_power_scl = {.port = GPIOA, .pin = LL_GPIO_PIN_9}; const GpioPin gpio_speaker = {.port = GPIOB, .pin = LL_GPIO_PIN_8}; -const GpioPin periph_power = {.port = PERIPH_POWER_GPIO_Port, .pin = PERIPH_POWER_Pin}; +const GpioPin periph_power = {.port = GPIOA, .pin = LL_GPIO_PIN_3}; const GpioPin gpio_usb_dm = {.port = GPIOA, .pin = LL_GPIO_PIN_11}; const GpioPin gpio_usb_dp = {.port = GPIOA, .pin = LL_GPIO_PIN_12}; @@ -77,6 +77,10 @@ const size_t input_pins_count = sizeof(input_pins) / sizeof(InputPin); void furi_hal_resources_init_early() { furi_hal_gpio_init(&gpio_button_left, GpioModeInput, GpioPullUp, GpioSpeedLow); + // SD Card stepdown control + furi_hal_gpio_write(&periph_power, 1); + furi_hal_gpio_init(&periph_power, GpioModeOutputOpenDrain, GpioPullNo, GpioSpeedLow); + // Display pins furi_hal_gpio_write(&gpio_display_rst_n, 1); furi_hal_gpio_init_simple(&gpio_display_rst_n, GpioModeOutputPushPull); @@ -142,8 +146,6 @@ void furi_hal_resources_init() { furi_hal_gpio_init(&gpio_rf_sw_0, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); - furi_hal_gpio_init(&periph_power, GpioModeOutputOpenDrain, GpioPullNo, GpioSpeedLow); - NVIC_SetPriority(EXTI0_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 5, 0)); NVIC_EnableIRQ(EXTI0_IRQn); diff --git a/firmware/targets/f7/furi_hal/furi_hal_resources.h b/firmware/targets/f7/furi_hal/furi_hal_resources.h index 820760a164c..d16c567e628 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_resources.h +++ b/firmware/targets/f7/furi_hal/furi_hal_resources.h @@ -147,9 +147,6 @@ extern const GpioPin gpio_usb_dp; #define PC3_GPIO_Port GPIOC #define PC3_Pin LL_GPIO_PIN_3 -#define PERIPH_POWER_GPIO_Port GPIOA -#define PERIPH_POWER_Pin LL_GPIO_PIN_3 - #define QUARTZ_32MHZ_IN_GPIO_Port GPIOC #define QUARTZ_32MHZ_IN_Pin LL_GPIO_PIN_14 #define QUARTZ_32MHZ_OUT_GPIO_Port GPIOC diff --git a/firmware/targets/f7/furi_hal/furi_hal_rtc.c b/firmware/targets/f7/furi_hal/furi_hal_rtc.c index df410a9f4cf..24dad38fa02 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_rtc.c +++ b/firmware/targets/f7/furi_hal/furi_hal_rtc.c @@ -12,7 +12,9 @@ #define TAG "FuriHalRtc" -#define RTC_CLOCK_IS_READY() (LL_RCC_LSE_IsReady() && LL_RCC_LSI1_IsReady()) +#define FURI_HAL_RTC_LSE_STARTUP_TIME 300 + +#define FURI_HAL_RTC_CLOCK_IS_READY() (LL_RCC_LSE_IsReady() && LL_RCC_LSI1_IsReady()) #define FURI_HAL_RTC_HEADER_MAGIC 0x10F1 #define FURI_HAL_RTC_HEADER_VERSION 0 @@ -47,33 +49,83 @@ static const uint8_t furi_hal_rtc_days_per_month[][FURI_HAL_RTC_MONTHS_COUNT] = static const uint16_t furi_hal_rtc_days_per_year[] = {365, 366}; -void furi_hal_rtc_init_early() { - // LSE and RTC +static void furi_hal_rtc_reset() { + LL_RCC_ForceBackupDomainReset(); + LL_RCC_ReleaseBackupDomainReset(); +} + +static bool furi_hal_rtc_start_clock_and_switch() { + // Clock operation require access to Backup Domain LL_PWR_EnableBkUpAccess(); - if(!RTC_CLOCK_IS_READY()) { - LL_RCC_LSI1_Enable(); - // Try to start LSE normal way - LL_RCC_LSE_SetDriveCapability(LL_RCC_LSEDRIVE_HIGH); - LL_RCC_LSE_Enable(); - uint32_t c = 0; - while(!RTC_CLOCK_IS_READY() && c < 200) { - LL_mDelay(10); - c++; - } - // Plan B: reset backup domain - if(!RTC_CLOCK_IS_READY()) { - furi_hal_light_sequence("rgb R.r.R.r.R"); - LL_RCC_ForceBackupDomainReset(); - LL_RCC_ReleaseBackupDomainReset(); - NVIC_SystemReset(); - } - // Set RTC domain clock to LSE + + // Enable LSI and LSE + LL_RCC_LSI1_Enable(); + LL_RCC_LSE_SetDriveCapability(LL_RCC_LSEDRIVE_HIGH); + LL_RCC_LSE_Enable(); + + // Wait for LSI and LSE startup + uint32_t c = 0; + while(!FURI_HAL_RTC_CLOCK_IS_READY() && c < FURI_HAL_RTC_LSE_STARTUP_TIME) { + LL_mDelay(1); + c++; + } + + if(FURI_HAL_RTC_CLOCK_IS_READY()) { LL_RCC_SetRTCClockSource(LL_RCC_RTC_CLKSOURCE_LSE); + LL_RCC_EnableRTC(); + return LL_RCC_GetRTCClockSource() == LL_RCC_RTC_CLKSOURCE_LSE; + } else { + return false; + } +} + +static void furi_hal_rtc_recover() { + FuriHalRtcDateTime datetime = {0}; + + // Handle fixable LSE failure + if(LL_RCC_LSE_IsCSSDetected()) { + furi_hal_light_sequence("rgb B"); + // Shutdown LSE and LSECSS + LL_RCC_LSE_DisableCSS(); + LL_RCC_LSE_Disable(); + } else { + furi_hal_light_sequence("rgb R"); } - // Enable clocking - LL_RCC_EnableRTC(); + + // Temporary switch to LSI + LL_RCC_SetRTCClockSource(LL_RCC_RTC_CLKSOURCE_LSI); + if(LL_RCC_GetRTCClockSource() == LL_RCC_RTC_CLKSOURCE_LSI) { + // Get datetime before RTC Domain reset + furi_hal_rtc_get_datetime(&datetime); + } + + // Reset RTC Domain + furi_hal_rtc_reset(); + + // Start Clock + if(!furi_hal_rtc_start_clock_and_switch()) { + // Plan C: reset RTC and restart + furi_hal_light_sequence("rgb R.r.R.r.R.r"); + furi_hal_rtc_reset(); + NVIC_SystemReset(); + } + + // Set date if it valid + if(datetime.year != 0) { + furi_hal_rtc_set_datetime(&datetime); + } +} + +void furi_hal_rtc_init_early() { + // Enable RTCAPB clock LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_RTCAPB); + // Prepare clock + if(!furi_hal_rtc_start_clock_and_switch()) { + // Plan B: try to recover + furi_hal_rtc_recover(); + } + // Verify header register uint32_t data_reg = furi_hal_rtc_get_register(FuriHalRtcRegisterHeader); FuriHalRtcHeader* data = (FuriHalRtcHeader*)&data_reg; @@ -98,14 +150,6 @@ void furi_hal_rtc_deinit_early() { } void furi_hal_rtc_init() { - if(LL_RCC_GetRTCClockSource() != LL_RCC_RTC_CLKSOURCE_LSE) { - LL_RCC_ForceBackupDomainReset(); - LL_RCC_ReleaseBackupDomainReset(); - LL_RCC_SetRTCClockSource(LL_RCC_RTC_CLKSOURCE_LSE); - } - - LL_RCC_EnableRTC(); - LL_RTC_InitTypeDef RTC_InitStruct = {0}; RTC_InitStruct.HourFormat = LL_RTC_HOURFORMAT_24HOUR; RTC_InitStruct.AsynchPrescaler = 127; diff --git a/firmware/targets/f7/furi_hal/furi_hal_spi_config.c b/firmware/targets/f7/furi_hal/furi_hal_spi_config.c index 0bdb25082a1..e01f132e640 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_spi_config.c +++ b/firmware/targets/f7/furi_hal/furi_hal_spi_config.c @@ -188,6 +188,72 @@ inline static void furi_hal_spi_bus_r_handle_event_callback( } } +inline static void furi_hal_spi_bus_nfc_handle_event_callback( + FuriHalSpiBusHandle* handle, + FuriHalSpiBusHandleEvent event, + const LL_SPI_InitTypeDef* preset) { + if(event == FuriHalSpiBusHandleEventInit) { + // Configure GPIOs in normal SPI mode + furi_hal_gpio_init_ex( + handle->miso, + GpioModeAltFunctionPushPull, + GpioPullNo, + GpioSpeedVeryHigh, + GpioAltFn5SPI1); + furi_hal_gpio_init_ex( + handle->mosi, + GpioModeAltFunctionPushPull, + GpioPullNo, + GpioSpeedVeryHigh, + GpioAltFn5SPI1); + furi_hal_gpio_init_ex( + handle->sck, + GpioModeAltFunctionPushPull, + GpioPullNo, + GpioSpeedVeryHigh, + GpioAltFn5SPI1); + furi_hal_gpio_write(handle->cs, true); + furi_hal_gpio_init(handle->cs, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); + } else if(event == FuriHalSpiBusHandleEventDeinit) { + // Configure GPIOs for st25r3916 Transparent mode + furi_hal_gpio_init(handle->sck, GpioModeInput, GpioPullUp, GpioSpeedLow); + furi_hal_gpio_init(handle->miso, GpioModeInput, GpioPullUp, GpioSpeedLow); + furi_hal_gpio_init(handle->cs, GpioModeInput, GpioPullUp, GpioSpeedLow); + furi_hal_gpio_write(handle->mosi, false); + furi_hal_gpio_init(handle->mosi, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); + } else if(event == FuriHalSpiBusHandleEventActivate) { + LL_SPI_Init(handle->bus->spi, (LL_SPI_InitTypeDef*)preset); + LL_SPI_SetRxFIFOThreshold(handle->bus->spi, LL_SPI_RX_FIFO_TH_QUARTER); + LL_SPI_Enable(handle->bus->spi); + + furi_hal_gpio_init_ex( + handle->miso, + GpioModeAltFunctionPushPull, + GpioPullNo, + GpioSpeedVeryHigh, + GpioAltFn5SPI1); + furi_hal_gpio_init_ex( + handle->mosi, + GpioModeAltFunctionPushPull, + GpioPullNo, + GpioSpeedVeryHigh, + GpioAltFn5SPI1); + furi_hal_gpio_init_ex( + handle->sck, + GpioModeAltFunctionPushPull, + GpioPullNo, + GpioSpeedVeryHigh, + GpioAltFn5SPI1); + + } else if(event == FuriHalSpiBusHandleEventDeactivate) { + furi_hal_gpio_init(handle->miso, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_init(handle->mosi, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_init(handle->sck, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + + LL_SPI_Disable(handle->bus->spi); + } +} + static void furi_hal_spi_bus_handle_subghz_event_callback( FuriHalSpiBusHandle* handle, FuriHalSpiBusHandleEvent event) { @@ -206,7 +272,7 @@ FuriHalSpiBusHandle furi_hal_spi_bus_handle_subghz = { static void furi_hal_spi_bus_handle_nfc_event_callback( FuriHalSpiBusHandle* handle, FuriHalSpiBusHandleEvent event) { - furi_hal_spi_bus_r_handle_event_callback(handle, event, &furi_hal_spi_preset_2edge_low_8m); + furi_hal_spi_bus_nfc_handle_event_callback(handle, event, &furi_hal_spi_preset_2edge_low_8m); } FuriHalSpiBusHandle furi_hal_spi_bus_handle_nfc = { diff --git a/firmware/targets/f7/furi_hal/furi_hal_usb_hid.c b/firmware/targets/f7/furi_hal/furi_hal_usb_hid.c index aae4dd4fb6b..06f3f231f1a 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_usb_hid.c +++ b/firmware/targets/f7/furi_hal/furi_hal_usb_hid.c @@ -6,11 +6,6 @@ #include "usb.h" #include "usb_hid.h" -#include "hid_usage_desktop.h" -#include "hid_usage_button.h" -#include "hid_usage_keyboard.h" -#include "hid_usage_consumer.h" -#include "hid_usage_led.h" #define HID_EP_IN 0x81 #define HID_EP_OUT 0x01 diff --git a/firmware/targets/furi_hal_include/furi_hal_bt_hid.h b/firmware/targets/furi_hal_include/furi_hal_bt_hid.h index 4faeebaeb3f..e35edd01c06 100644 --- a/firmware/targets/furi_hal_include/furi_hal_bt_hid.h +++ b/firmware/targets/furi_hal_include/furi_hal_bt_hid.h @@ -3,17 +3,6 @@ #include #include -enum FuriHalBtHidMediKeys { - FuriHalBtHidMediaScanNext, - FuriHalBtHidMediaScanPrevious, - FuriHalBtHidMediaStop, - FuriHalBtHidMediaEject, - FuriHalBtHidMediaPlayPause, - FuriHalBtHidMediaMute, - FuriHalBtHidMediaVolumeUp, - FuriHalBtHidMediaVolumeDown, -}; - /** Start Hid Keyboard Profile */ void furi_hal_bt_hid_start(); @@ -44,20 +33,51 @@ bool furi_hal_bt_hid_kb_release(uint16_t button); */ bool furi_hal_bt_hid_kb_release_all(); -/** Release all media buttons +/** Set mouse movement and send HID report * - * @return true on success + * @param dx x coordinate delta + * @param dy y coordinate delta */ -bool furi_hal_bt_hid_media_press(uint8_t button); +bool furi_hal_bt_hid_mouse_move(int8_t dx, int8_t dy); -/** Release all media buttons +/** Set mouse button to pressed state and send HID report * - * @return true on success + * @param button key code */ -bool furi_hal_bt_hid_media_release(uint8_t button); +bool furi_hal_bt_hid_mouse_press(uint8_t button); -/** Release all media buttons +/** Set mouse button to released state and send HID report * - * @return true on success + * @param button key code + */ +bool furi_hal_bt_hid_mouse_release(uint8_t button); + +/** Set mouse button to released state and send HID report + * + * @param button key code + */ +bool furi_hal_bt_hid_mouse_release_all(); + +/** Set mouse wheel position and send HID report + * + * @param delta number of scroll steps + */ +bool furi_hal_bt_hid_mouse_scroll(int8_t delta); + +/** Set the following consumer key to pressed state and send HID report + * + * @param button key code + */ +bool furi_hal_bt_hid_consumer_key_press(uint16_t button); + +/** Set the following consumer key to released state and send HID report + * + * @param button key code + */ +bool furi_hal_bt_hid_consumer_key_release(uint16_t button); + +/** Set consumer key to released state and send HID report + * + * @param button key code */ -bool furi_hal_bt_hid_media_release_all(); +bool furi_hal_bt_hid_consumer_key_release_all(); diff --git a/firmware/targets/furi_hal_include/furi_hal_nfc.h b/firmware/targets/furi_hal_include/furi_hal_nfc.h index 6e438367b93..a25e7c6d36e 100644 --- a/firmware/targets/furi_hal_include/furi_hal_nfc.h +++ b/firmware/targets/furi_hal_include/furi_hal_nfc.h @@ -163,6 +163,39 @@ bool furi_hal_nfc_listen( bool activate_after_sak, uint32_t timeout); +/** Start Target Listen mode + * @note RFAL free implementation + * + * @param nfc_data FuriHalNfcDevData instance + */ +void furi_hal_nfc_listen_start(FuriHalNfcDevData* nfc_data); + +/** Read data in Target Listen mode + * @note Must be called only after furi_hal_nfc_listen_start() + * + * @param tx_rx FuriHalNfcTxRxContext instance + * @param timeout_ms timeout im ms + * + * @return true on not empty receive + */ +bool furi_hal_nfc_listen_rx(FuriHalNfcTxRxContext* tx_rx, uint32_t timeout_ms); + +/** Set Target in Sleep state */ +void furi_hal_nfc_listen_sleep(); + +/** Emulate NFC-A Target + * @note RFAL based implementation + * + * @param uid NFC-A UID + * @param uid_len NFC-A UID length + * @param atqa NFC-A ATQA + * @param sak NFC-A SAK + * @param callback FuriHalNfcEmulateCallback instance + * @param context pointer to context for callback + * @param timeout timeout in ms + * + * @return true on success + */ bool furi_hal_nfc_emulate_nfca( uint8_t* uid, uint8_t uid_len, diff --git a/firmware/targets/furi_hal_include/furi_hal_usb_hid.h b/firmware/targets/furi_hal_include/furi_hal_usb_hid.h index 20c76223e99..97bac7f049f 100644 --- a/firmware/targets/furi_hal_include/furi_hal_usb_hid.h +++ b/firmware/targets/furi_hal_include/furi_hal_usb_hid.h @@ -1,110 +1,13 @@ #pragma once +#include "hid_usage_desktop.h" +#include "hid_usage_button.h" +#include "hid_usage_keyboard.h" +#include "hid_usage_consumer.h" +#include "hid_usage_led.h" -/** HID keyboard key codes */ -enum HidKeyboardKeys { - KEY_NONE = 0x00, - KEY_ERROR_ROLLOVER = 0x01, - KEY_POST_FAIL = 0x02, - KEY_ERROR_UNDEFINED = 0x03, - KEY_A = 0x04, - KEY_B = 0x05, - KEY_C = 0x06, - KEY_D = 0x07, - KEY_E = 0x08, - KEY_F = 0x09, - KEY_G = 0x0A, - KEY_H = 0x0B, - KEY_I = 0x0C, - KEY_J = 0x0D, - KEY_K = 0x0E, - KEY_L = 0x0F, - KEY_M = 0x10, - KEY_N = 0x11, - KEY_O = 0x12, - KEY_P = 0x13, - KEY_Q = 0x14, - KEY_R = 0x15, - KEY_S = 0x16, - KEY_T = 0x17, - KEY_U = 0x18, - KEY_V = 0x19, - KEY_W = 0x1A, - KEY_X = 0x1B, - KEY_Y = 0x1C, - KEY_Z = 0x1D, - KEY_1 = 0x1E, - KEY_2 = 0x1F, - KEY_3 = 0x20, - KEY_4 = 0x21, - KEY_5 = 0x22, - KEY_6 = 0x23, - KEY_7 = 0x24, - KEY_8 = 0x25, - KEY_9 = 0x26, - KEY_0 = 0x27, - KEY_ENTER = 0x28, - KEY_ESC = 0x29, - KEY_BACKSPACE = 0x2A, - KEY_TAB = 0x2B, - KEY_SPACE = 0x2C, - KEY_MINUS = 0x2D, - KEY_EQUAL = 0x2E, - KEY_LEFT_BRACE = 0x2F, - KEY_RIGHT_BRACE = 0x30, - KEY_BACKSLASH = 0x31, - KEY_NON_US_NUM = 0x32, - KEY_SEMICOLON = 0x33, - KEY_QUOTE = 0x34, - KEY_TILDE = 0x35, - KEY_COMMA = 0x36, - KEY_PERIOD = 0x37, - KEY_SLASH = 0x38, - KEY_CAPS_LOCK = 0x39, - KEY_F1 = 0x3A, - KEY_F2 = 0x3B, - KEY_F3 = 0x3C, - KEY_F4 = 0x3D, - KEY_F5 = 0x3E, - KEY_F6 = 0x3F, - KEY_F7 = 0x40, - KEY_F8 = 0x41, - KEY_F9 = 0x42, - KEY_F10 = 0x43, - KEY_F11 = 0x44, - KEY_F12 = 0x45, - KEY_PRINT = 0x46, - KEY_SCROLL_LOCK = 0x47, - KEY_PAUSE = 0x48, - KEY_INSERT = 0x49, - KEY_HOME = 0x4A, - KEY_PAGE_UP = 0x4B, - KEY_DELETE = 0x4C, - KEY_END = 0x4D, - KEY_PAGE_DOWN = 0x4E, - KEY_RIGHT_ARROW = 0x4F, - KEY_LEFT_ARROW = 0x50, - KEY_DOWN_ARROW = 0x51, - KEY_UP_ARROW = 0x52, - KEY_NUM_LOCK = 0x53, - KEYPAD_DIVIDE = 0x54, - KEYPAD_MULTIPLY = 0x55, - KEYPAD_SUBTRACT = 0x56, - KEYPAD_ADD = 0x57, - KEYPAD_ENTER = 0x58, - KEYPAD_1 = 0x59, - KEYPAD_2 = 0x5A, - KEYPAD_3 = 0x5B, - KEYPAD_4 = 0x5C, - KEYPAD_5 = 0x5D, - KEYPAD_6 = 0x5E, - KEYPAD_7 = 0x5F, - KEYPAD_8 = 0x60, - KEYPAD_9 = 0x61, - KEYPAD_0 = 0x62, - KEYPAD_DOT = 0x63, - KEY_NON_US = 0x64, - KEY_APPLICATION = 0x65, -}; +#define HID_KEYBOARD_NONE 0x00 +// Remapping the colon key which is shift + ; to comma +#define HID_KEYBOARD_COMMA HID_KEYBOARD_COLON /** HID keyboard modifier keys */ enum HidKeyboardMods { @@ -120,134 +23,134 @@ enum HidKeyboardMods { /** ASCII to keycode conversion table */ static const uint16_t hid_asciimap[] = { - KEY_NONE, // NUL - KEY_NONE, // SOH - KEY_NONE, // STX - KEY_NONE, // ETX - KEY_NONE, // EOT - KEY_NONE, // ENQ - KEY_NONE, // ACK - KEY_NONE, // BEL - KEY_BACKSPACE, // BS Backspace - KEY_TAB, // TAB Tab - KEY_ENTER, // LF Enter - KEY_NONE, // VT - KEY_NONE, // FF - KEY_NONE, // CR - KEY_NONE, // SO - KEY_NONE, // SI - KEY_NONE, // DEL - KEY_NONE, // DC1 - KEY_NONE, // DC2 - KEY_NONE, // DC3 - KEY_NONE, // DC4 - KEY_NONE, // NAK - KEY_NONE, // SYN - KEY_NONE, // ETB - KEY_NONE, // CAN - KEY_NONE, // EM - KEY_NONE, // SUB - KEY_NONE, // ESC - KEY_NONE, // FS - KEY_NONE, // GS - KEY_NONE, // RS - KEY_NONE, // US - KEY_SPACE, // ' ' Space - KEY_1 | KEY_MOD_LEFT_SHIFT, // ! - KEY_QUOTE | KEY_MOD_LEFT_SHIFT, // " - KEY_3 | KEY_MOD_LEFT_SHIFT, // # - KEY_4 | KEY_MOD_LEFT_SHIFT, // $ - KEY_5 | KEY_MOD_LEFT_SHIFT, // % - KEY_7 | KEY_MOD_LEFT_SHIFT, // & - KEY_QUOTE, // ' - KEY_9 | KEY_MOD_LEFT_SHIFT, // ( - KEY_0 | KEY_MOD_LEFT_SHIFT, // ) - KEY_8 | KEY_MOD_LEFT_SHIFT, // * - KEY_EQUAL | KEY_MOD_LEFT_SHIFT, // + - KEY_COMMA, // , - KEY_MINUS, // - - KEY_PERIOD, // . - KEY_SLASH, // / - KEY_0, // 0 - KEY_1, // 1 - KEY_2, // 2 - KEY_3, // 3 - KEY_4, // 4 - KEY_5, // 5 - KEY_6, // 6 - KEY_7, // 7 - KEY_8, // 8 - KEY_9, // 9 - KEY_SEMICOLON | KEY_MOD_LEFT_SHIFT, // : - KEY_SEMICOLON, // ; - KEY_COMMA | KEY_MOD_LEFT_SHIFT, // < - KEY_EQUAL, // = - KEY_PERIOD | KEY_MOD_LEFT_SHIFT, // > - KEY_SLASH | KEY_MOD_LEFT_SHIFT, // ? - KEY_2 | KEY_MOD_LEFT_SHIFT, // @ - KEY_A | KEY_MOD_LEFT_SHIFT, // A - KEY_B | KEY_MOD_LEFT_SHIFT, // B - KEY_C | KEY_MOD_LEFT_SHIFT, // C - KEY_D | KEY_MOD_LEFT_SHIFT, // D - KEY_E | KEY_MOD_LEFT_SHIFT, // E - KEY_F | KEY_MOD_LEFT_SHIFT, // F - KEY_G | KEY_MOD_LEFT_SHIFT, // G - KEY_H | KEY_MOD_LEFT_SHIFT, // H - KEY_I | KEY_MOD_LEFT_SHIFT, // I - KEY_J | KEY_MOD_LEFT_SHIFT, // J - KEY_K | KEY_MOD_LEFT_SHIFT, // K - KEY_L | KEY_MOD_LEFT_SHIFT, // L - KEY_M | KEY_MOD_LEFT_SHIFT, // M - KEY_N | KEY_MOD_LEFT_SHIFT, // N - KEY_O | KEY_MOD_LEFT_SHIFT, // O - KEY_P | KEY_MOD_LEFT_SHIFT, // P - KEY_Q | KEY_MOD_LEFT_SHIFT, // Q - KEY_R | KEY_MOD_LEFT_SHIFT, // R - KEY_S | KEY_MOD_LEFT_SHIFT, // S - KEY_T | KEY_MOD_LEFT_SHIFT, // T - KEY_U | KEY_MOD_LEFT_SHIFT, // U - KEY_V | KEY_MOD_LEFT_SHIFT, // V - KEY_W | KEY_MOD_LEFT_SHIFT, // W - KEY_X | KEY_MOD_LEFT_SHIFT, // X - KEY_Y | KEY_MOD_LEFT_SHIFT, // Y - KEY_Z | KEY_MOD_LEFT_SHIFT, // Z - KEY_LEFT_BRACE, // [ - KEY_BACKSLASH, // bslash - KEY_RIGHT_BRACE, // ] - KEY_6 | KEY_MOD_LEFT_SHIFT, // ^ - KEY_MINUS | KEY_MOD_LEFT_SHIFT, // _ - KEY_TILDE, // ` - KEY_A, // a - KEY_B, // b - KEY_C, // c - KEY_D, // d - KEY_E, // e - KEY_F, // f - KEY_G, // g - KEY_H, // h - KEY_I, // i - KEY_J, // j - KEY_K, // k - KEY_L, // l - KEY_M, // m - KEY_N, // n - KEY_O, // o - KEY_P, // p - KEY_Q, // q - KEY_R, // r - KEY_S, // s - KEY_T, // t - KEY_U, // u - KEY_V, // v - KEY_W, // w - KEY_X, // x - KEY_Y, // y - KEY_Z, // z - KEY_LEFT_BRACE | KEY_MOD_LEFT_SHIFT, // { - KEY_BACKSLASH | KEY_MOD_LEFT_SHIFT, // | - KEY_RIGHT_BRACE | KEY_MOD_LEFT_SHIFT, // } - KEY_TILDE | KEY_MOD_LEFT_SHIFT, // ~ - KEY_NONE, // DEL + HID_KEYBOARD_NONE, // NUL + HID_KEYBOARD_NONE, // SOH + HID_KEYBOARD_NONE, // STX + HID_KEYBOARD_NONE, // ETX + HID_KEYBOARD_NONE, // EOT + HID_KEYBOARD_NONE, // ENQ + HID_KEYBOARD_NONE, // ACK + HID_KEYBOARD_NONE, // BEL + HID_KEYBOARD_DELETE, // BS Backspace + HID_KEYBOARD_TAB, // TAB Tab + HID_KEYBOARD_RETURN, // LF Enter + HID_KEYBOARD_NONE, // VT + HID_KEYBOARD_NONE, // FF + HID_KEYBOARD_NONE, // CR + HID_KEYBOARD_NONE, // SO + HID_KEYBOARD_NONE, // SI + HID_KEYBOARD_NONE, // DEL + HID_KEYBOARD_NONE, // DC1 + HID_KEYBOARD_NONE, // DC2 + HID_KEYBOARD_NONE, // DC3 + HID_KEYBOARD_NONE, // DC4 + HID_KEYBOARD_NONE, // NAK + HID_KEYBOARD_NONE, // SYN + HID_KEYBOARD_NONE, // ETB + HID_KEYBOARD_NONE, // CAN + HID_KEYBOARD_NONE, // EM + HID_KEYBOARD_NONE, // SUB + HID_KEYBOARD_NONE, // ESC + HID_KEYBOARD_NONE, // FS + HID_KEYBOARD_NONE, // GS + HID_KEYBOARD_NONE, // RS + HID_KEYBOARD_NONE, // US + HID_KEYBOARD_SPACEBAR, // ' ' Space + HID_KEYBOARD_1 | KEY_MOD_LEFT_SHIFT, // ! + HID_KEYBOARD_APOSTROPHE | KEY_MOD_LEFT_SHIFT, // " + HID_KEYBOARD_3 | KEY_MOD_LEFT_SHIFT, // # + HID_KEYBOARD_4 | KEY_MOD_LEFT_SHIFT, // $ + HID_KEYBOARD_5 | KEY_MOD_LEFT_SHIFT, // % + HID_KEYBOARD_7 | KEY_MOD_LEFT_SHIFT, // & + HID_KEYBOARD_APOSTROPHE, // ' + HID_KEYBOARD_9 | KEY_MOD_LEFT_SHIFT, // ( + HID_KEYBOARD_0 | KEY_MOD_LEFT_SHIFT, // ) + HID_KEYBOARD_8 | KEY_MOD_LEFT_SHIFT, // * + HID_KEYBOARD_EQUAL_SIGN | KEY_MOD_LEFT_SHIFT, // + + HID_KEYBOARD_COMMA, // , + HID_KEYBOARD_MINUS, // - + HID_KEYBOARD_DOT, // . + HID_KEYBOARD_SLASH, // / + HID_KEYBOARD_0, // 0 + HID_KEYBOARD_1, // 1 + HID_KEYBOARD_2, // 2 + HID_KEYBOARD_3, // 3 + HID_KEYBOARD_4, // 4 + HID_KEYBOARD_5, // 5 + HID_KEYBOARD_6, // 6 + HID_KEYBOARD_7, // 7 + HID_KEYBOARD_8, // 8 + HID_KEYBOARD_9, // 9 + HID_KEYBOARD_SEMICOLON | KEY_MOD_LEFT_SHIFT, // : + HID_KEYBOARD_SEMICOLON, // ; + HID_KEYBOARD_COMMA | KEY_MOD_LEFT_SHIFT, // < + HID_KEYBOARD_EQUAL_SIGN, // = + HID_KEYBOARD_DOT | KEY_MOD_LEFT_SHIFT, // > + HID_KEYBOARD_SLASH | KEY_MOD_LEFT_SHIFT, // ? + HID_KEYBOARD_2 | KEY_MOD_LEFT_SHIFT, // @ + HID_KEYBOARD_A | KEY_MOD_LEFT_SHIFT, // A + HID_KEYBOARD_B | KEY_MOD_LEFT_SHIFT, // B + HID_KEYBOARD_C | KEY_MOD_LEFT_SHIFT, // C + HID_KEYBOARD_D | KEY_MOD_LEFT_SHIFT, // D + HID_KEYBOARD_E | KEY_MOD_LEFT_SHIFT, // E + HID_KEYBOARD_F | KEY_MOD_LEFT_SHIFT, // F + HID_KEYBOARD_G | KEY_MOD_LEFT_SHIFT, // G + HID_KEYBOARD_H | KEY_MOD_LEFT_SHIFT, // H + HID_KEYBOARD_I | KEY_MOD_LEFT_SHIFT, // I + HID_KEYBOARD_J | KEY_MOD_LEFT_SHIFT, // J + HID_KEYBOARD_K | KEY_MOD_LEFT_SHIFT, // K + HID_KEYBOARD_L | KEY_MOD_LEFT_SHIFT, // L + HID_KEYBOARD_M | KEY_MOD_LEFT_SHIFT, // M + HID_KEYBOARD_N | KEY_MOD_LEFT_SHIFT, // N + HID_KEYBOARD_O | KEY_MOD_LEFT_SHIFT, // O + HID_KEYBOARD_P | KEY_MOD_LEFT_SHIFT, // P + HID_KEYBOARD_Q | KEY_MOD_LEFT_SHIFT, // Q + HID_KEYBOARD_R | KEY_MOD_LEFT_SHIFT, // R + HID_KEYBOARD_S | KEY_MOD_LEFT_SHIFT, // S + HID_KEYBOARD_T | KEY_MOD_LEFT_SHIFT, // T + HID_KEYBOARD_U | KEY_MOD_LEFT_SHIFT, // U + HID_KEYBOARD_V | KEY_MOD_LEFT_SHIFT, // V + HID_KEYBOARD_W | KEY_MOD_LEFT_SHIFT, // W + HID_KEYBOARD_X | KEY_MOD_LEFT_SHIFT, // X + HID_KEYBOARD_Y | KEY_MOD_LEFT_SHIFT, // Y + HID_KEYBOARD_Z | KEY_MOD_LEFT_SHIFT, // Z + HID_KEYBOARD_OPEN_BRACKET, // [ + HID_KEYBOARD_BACKSLASH, // bslash + HID_KEYBOARD_CLOSE_BRACKET, // ] + HID_KEYBOARD_6 | KEY_MOD_LEFT_SHIFT, // ^ + HID_KEYBOARD_MINUS | KEY_MOD_LEFT_SHIFT, // _ + HID_KEYBOARD_GRAVE_ACCENT, // ` + HID_KEYBOARD_A, // a + HID_KEYBOARD_B, // b + HID_KEYBOARD_C, // c + HID_KEYBOARD_D, // d + HID_KEYBOARD_E, // e + HID_KEYBOARD_F, // f + HID_KEYBOARD_G, // g + HID_KEYBOARD_H, // h + HID_KEYBOARD_I, // i + HID_KEYBOARD_J, // j + HID_KEYBOARD_K, // k + HID_KEYBOARD_L, // l + HID_KEYBOARD_M, // m + HID_KEYBOARD_N, // n + HID_KEYBOARD_O, // o + HID_KEYBOARD_P, // p + HID_KEYBOARD_Q, // q + HID_KEYBOARD_R, // r + HID_KEYBOARD_S, // s + HID_KEYBOARD_T, // t + HID_KEYBOARD_U, // u + HID_KEYBOARD_V, // v + HID_KEYBOARD_W, // w + HID_KEYBOARD_X, // x + HID_KEYBOARD_Y, // y + HID_KEYBOARD_Z, // z + HID_KEYBOARD_OPEN_BRACKET | KEY_MOD_LEFT_SHIFT, // { + HID_KEYBOARD_BACKSLASH | KEY_MOD_LEFT_SHIFT, // | + HID_KEYBOARD_CLOSE_BRACKET | KEY_MOD_LEFT_SHIFT, // } + HID_KEYBOARD_GRAVE_ACCENT | KEY_MOD_LEFT_SHIFT, // ~ + HID_KEYBOARD_NONE, // DEL }; typedef struct { @@ -260,7 +163,7 @@ typedef struct { typedef void (*HidStateCallback)(bool state, void* context); /** ASCII to keycode conversion macro */ -#define HID_ASCII_TO_KEY(x) (((uint8_t)x < 128) ? (hid_asciimap[(uint8_t)x]) : KEY_NONE) +#define HID_ASCII_TO_KEY(x) (((uint8_t)x < 128) ? (hid_asciimap[(uint8_t)x]) : HID_KEYBOARD_NONE) /** HID keyboard leds */ enum HidKeyboardLeds { diff --git a/lib/SConscript b/lib/SConscript index ee8b8356646..a3617c5d14d 100644 --- a/lib/SConscript +++ b/lib/SConscript @@ -72,6 +72,8 @@ libs = env.BuildModules( "subghz", "appframe", "misc", + "mbedtls", + "loclass", ], ) diff --git a/lib/ST25RFAL002/include/rfal_picopass.h b/lib/ST25RFAL002/include/rfal_picopass.h new file mode 100644 index 00000000000..c1b07981443 --- /dev/null +++ b/lib/ST25RFAL002/include/rfal_picopass.h @@ -0,0 +1,65 @@ + +#ifndef RFAL_PICOPASS_H +#define RFAL_PICOPASS_H + +/* + ****************************************************************************** + * INCLUDES + ****************************************************************************** + */ +#include "platform.h" +#include "rfal_rf.h" +#include "st_errno.h" + +#define RFAL_PICOPASS_UID_LEN 8 +#define RFAL_PICOPASS_MAX_BLOCK_LEN 8 + +#define RFAL_PICOPASS_TXRX_FLAGS \ + (RFAL_TXRX_FLAGS_CRC_TX_MANUAL | RFAL_TXRX_FLAGS_AGC_ON | RFAL_TXRX_FLAGS_PAR_RX_REMV | \ + RFAL_TXRX_FLAGS_CRC_RX_KEEP) + +enum { + RFAL_PICOPASS_CMD_ACTALL = 0x0A, + RFAL_PICOPASS_CMD_IDENTIFY = 0x0C, + RFAL_PICOPASS_CMD_SELECT = 0x81, + RFAL_PICOPASS_CMD_READCHECK = 0x88, + RFAL_PICOPASS_CMD_CHECK = 0x05, + RFAL_PICOPASS_CMD_READ = 0x0C, +}; + +typedef struct { + uint8_t CSN[RFAL_PICOPASS_UID_LEN]; // Anti-collision CSN + uint8_t crc[2]; +} rfalPicoPassIdentifyRes; + +typedef struct { + uint8_t CSN[RFAL_PICOPASS_UID_LEN]; // Real CSN + uint8_t crc[2]; +} rfalPicoPassSelectRes; + +typedef struct { + uint8_t CCNR[8]; +} rfalPicoPassReadCheckRes; + +typedef struct { + uint8_t mac[4]; +} rfalPicoPassCheckRes; + +typedef struct { + uint8_t data[RFAL_PICOPASS_MAX_BLOCK_LEN]; + uint8_t crc[2]; +} rfalPicoPassReadBlockRes; + +typedef struct { + rfalPicoPassReadBlockRes block[4]; +} ApplicationArea; + +ReturnCode rfalPicoPassPollerInitialize(void); +ReturnCode rfalPicoPassPollerCheckPresence(void); +ReturnCode rfalPicoPassPollerIdentify(rfalPicoPassIdentifyRes* idRes); +ReturnCode rfalPicoPassPollerSelect(uint8_t* csn, rfalPicoPassSelectRes* selRes); +ReturnCode rfalPicoPassPollerReadCheck(rfalPicoPassReadCheckRes* rcRes); +ReturnCode rfalPicoPassPollerCheck(uint8_t* mac, rfalPicoPassCheckRes* chkRes); +ReturnCode rfalPicoPassPollerReadBlock(uint8_t blockNum, rfalPicoPassReadBlockRes* readRes); + +#endif /* RFAL_PICOPASS_H */ diff --git a/lib/ST25RFAL002/platform.c b/lib/ST25RFAL002/platform.c index c688bd591c4..1facfa2ad42 100644 --- a/lib/ST25RFAL002/platform.c +++ b/lib/ST25RFAL002/platform.c @@ -6,11 +6,13 @@ typedef struct { FuriThread* thread; volatile PlatformIrqCallback callback; + bool need_spi_lock; } RfalPlatform; static volatile RfalPlatform rfal_platform = { .thread = NULL, .callback = NULL, + .need_spi_lock = true, }; void nfc_isr(void* _ctx) { @@ -71,10 +73,30 @@ bool platformSpiTxRx(const uint8_t* txBuf, uint8_t* rxBuf, uint16_t len) { return ret; } -void platformProtectST25RComm() { +// Until we completely remove RFAL, NFC works with SPI from rfal_platform_irq_thread and nfc_worker +// threads. Some nfc features already stop using RFAL and work with SPI from nfc_worker only. +// rfal_platform_spi_acquire() and rfal_platform_spi_release() functions are used to lock SPI for a +// long term without locking it for each SPI transaction. This is needed for time critical communications. +void rfal_platform_spi_acquire() { + platformDisableIrqCallback(); + rfal_platform.need_spi_lock = false; furi_hal_spi_acquire(&furi_hal_spi_bus_handle_nfc); } -void platformUnprotectST25RComm() { +void rfal_platform_spi_release() { furi_hal_spi_release(&furi_hal_spi_bus_handle_nfc); + rfal_platform.need_spi_lock = true; + platformEnableIrqCallback(); +} + +void platformProtectST25RComm() { + if(rfal_platform.need_spi_lock) { + furi_hal_spi_acquire(&furi_hal_spi_bus_handle_nfc); + } +} + +void platformUnprotectST25RComm() { + if(rfal_platform.need_spi_lock) { + furi_hal_spi_release(&furi_hal_spi_bus_handle_nfc); + } } diff --git a/lib/ST25RFAL002/platform.h b/lib/ST25RFAL002/platform.h index 99f97ace22a..832e034ecee 100644 --- a/lib/ST25RFAL002/platform.h +++ b/lib/ST25RFAL002/platform.h @@ -19,6 +19,8 @@ void platformDisableIrqCallback(); bool platformSpiTxRx(const uint8_t* txBuf, uint8_t* rxBuf, uint16_t len); void platformProtectST25RComm(); void platformUnprotectST25RComm(); +void rfal_platform_spi_acquire(); +void rfal_platform_spi_release(); #define ST25R_SS_PIN NFC_CS_Pin #define ST25R_SS_PORT NFC_CS_GPIO_Port diff --git a/lib/ST25RFAL002/source/rfal_picopass.c b/lib/ST25RFAL002/source/rfal_picopass.c new file mode 100644 index 00000000000..c99fa2241da --- /dev/null +++ b/lib/ST25RFAL002/source/rfal_picopass.c @@ -0,0 +1,172 @@ + +#include "rfal_picopass.h" +#include "utils.h" + +typedef struct { + uint8_t CMD; + uint8_t CSN[RFAL_PICOPASS_UID_LEN]; +} rfalPicoPassSelectReq; + +typedef struct { + uint8_t CMD; + uint8_t null[4]; + uint8_t mac[4]; +} rfalPicoPassCheckReq; + +ReturnCode rfalPicoPassPollerInitialize(void) { + ReturnCode ret; + + EXIT_ON_ERR(ret, rfalSetMode(RFAL_MODE_POLL_PICOPASS, RFAL_BR_26p48, RFAL_BR_26p48)); + rfalSetErrorHandling(RFAL_ERRORHANDLING_NFC); + + rfalSetGT(RFAL_GT_PICOPASS); + rfalSetFDTListen(RFAL_FDT_LISTEN_PICOPASS_POLLER); + rfalSetFDTPoll(RFAL_FDT_POLL_PICOPASS_POLLER); + + return ERR_NONE; +} + +ReturnCode rfalPicoPassPollerCheckPresence(void) { + ReturnCode ret; + uint8_t txBuf[1] = {RFAL_PICOPASS_CMD_ACTALL}; + uint8_t rxBuf[32] = {0}; + uint16_t recvLen = 0; + uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS; + uint32_t fwt = rfalConvMsTo1fc(20); + + ret = rfalTransceiveBlockingTxRx(txBuf, 1, rxBuf, 32, &recvLen, flags, fwt); + return ret; +} + +ReturnCode rfalPicoPassPollerIdentify(rfalPicoPassIdentifyRes* idRes) { + ReturnCode ret; + + uint8_t txBuf[1] = {RFAL_PICOPASS_CMD_IDENTIFY}; + uint16_t recvLen = 0; + uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS; + uint32_t fwt = rfalConvMsTo1fc(20); + + ret = rfalTransceiveBlockingTxRx( + txBuf, + sizeof(txBuf), + (uint8_t*)idRes, + sizeof(rfalPicoPassIdentifyRes), + &recvLen, + flags, + fwt); + // printf("identify rx: %d %s\n", recvLen, hex2Str(idRes->CSN, RFAL_PICOPASS_UID_LEN)); + + return ret; +} + +ReturnCode rfalPicoPassPollerSelect(uint8_t* csn, rfalPicoPassSelectRes* selRes) { + ReturnCode ret; + + rfalPicoPassSelectReq selReq; + selReq.CMD = RFAL_PICOPASS_CMD_SELECT; + ST_MEMCPY(selReq.CSN, csn, RFAL_PICOPASS_UID_LEN); + uint16_t recvLen = 0; + uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS; + uint32_t fwt = rfalConvMsTo1fc(20); + + ret = rfalTransceiveBlockingTxRx( + (uint8_t*)&selReq, + sizeof(rfalPicoPassSelectReq), + (uint8_t*)selRes, + sizeof(rfalPicoPassSelectRes), + &recvLen, + flags, + fwt); + // printf("select rx: %d %s\n", recvLen, hex2Str(selRes->CSN, RFAL_PICOPASS_UID_LEN)); + if(ret == ERR_TIMEOUT) { + return ERR_NONE; + } + + return ret; +} + +ReturnCode rfalPicoPassPollerReadCheck(rfalPicoPassReadCheckRes* rcRes) { + ReturnCode ret; + uint8_t txBuf[2] = {RFAL_PICOPASS_CMD_READCHECK, 0x02}; + uint16_t recvLen = 0; + uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS; + uint32_t fwt = rfalConvMsTo1fc(20); + + ret = rfalTransceiveBlockingTxRx( + txBuf, + sizeof(txBuf), + (uint8_t*)rcRes, + sizeof(rfalPicoPassReadCheckRes), + &recvLen, + flags, + fwt); + // printf("readcheck rx: %d %s\n", recvLen, hex2Str(rcRes->CCNR, 8)); + + if(ret == ERR_CRC) { + return ERR_NONE; + } + + return ret; +} + +ReturnCode rfalPicoPassPollerCheck(uint8_t* mac, rfalPicoPassCheckRes* chkRes) { + ReturnCode ret; + rfalPicoPassCheckReq chkReq; + chkReq.CMD = RFAL_PICOPASS_CMD_CHECK; + ST_MEMCPY(chkReq.mac, mac, 4); + ST_MEMSET(chkReq.null, 0, 4); + uint16_t recvLen = 0; + uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS; + uint32_t fwt = rfalConvMsTo1fc(20); + + // printf("check tx: %s\n", hex2Str((uint8_t *)&chkReq, sizeof(rfalPicoPassCheckReq))); + ret = rfalTransceiveBlockingTxRx( + (uint8_t*)&chkReq, + sizeof(rfalPicoPassCheckReq), + (uint8_t*)chkRes, + sizeof(rfalPicoPassCheckRes), + &recvLen, + flags, + fwt); + // printf("check rx: %d %s\n", recvLen, hex2Str(chkRes->mac, 4)); + if(ret == ERR_CRC) { + return ERR_NONE; + } + + return ret; +} + +ReturnCode rfalPicoPassPollerReadBlock(uint8_t blockNum, rfalPicoPassReadBlockRes* readRes) { + ReturnCode ret; + /* + * ./reveng -w 16 -s 0c07cc47 0c064556 0c083bbf 0c09b2ae + width=16 poly=0x1021 init=0xd924 refin=true refout=true xorout=0x0000 check=0x1329 residue=0x0000 name=(none) +0c 06 45 56 +0c 07 cc 47 +0c 08 3b bf +0c 09 b2 ae + */ + + uint8_t readCmds[4][4] = { + {RFAL_PICOPASS_CMD_READ, 6, 0x45, 0x56}, + {RFAL_PICOPASS_CMD_READ, 7, 0xcc, 0x47}, + {RFAL_PICOPASS_CMD_READ, 8, 0x3b, 0xbf}, + {RFAL_PICOPASS_CMD_READ, 9, 0xb2, 0xae}}; + + uint8_t* txBuf = readCmds[blockNum - 6]; + uint16_t recvLen = 0; + uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS; + uint32_t fwt = rfalConvMsTo1fc(20); + + ret = rfalTransceiveBlockingTxRx( + txBuf, + sizeof(txBuf), + (uint8_t*)readRes, + sizeof(rfalPicoPassReadBlockRes), + &recvLen, + flags, + fwt); + // printf("check rx: %d %s\n", recvLen, hex2Str(readRes->data, RFAL_PICOPASS_MAX_BLOCK_LEN)); + + return ret; +} diff --git a/lib/digital_signal/digital_signal.c b/lib/digital_signal/digital_signal.c index 23ddaf90920..46ca307a7fb 100644 --- a/lib/digital_signal/digital_signal.c +++ b/lib/digital_signal/digital_signal.c @@ -5,14 +5,17 @@ #include #include +#pragma GCC optimize("O3,unroll-loops,Ofast") + #define F_TIM (64000000.0) -#define T_TIM (1.0 / F_TIM) +#define T_TIM 1562 //15.625 ns *100 +#define T_TIM_DIV2 781 //15.625 ns / 2 *100 DigitalSignal* digital_signal_alloc(uint32_t max_edges_cnt) { DigitalSignal* signal = malloc(sizeof(DigitalSignal)); signal->start_level = true; signal->edges_max_cnt = max_edges_cnt; - signal->edge_timings = malloc(max_edges_cnt * sizeof(float)); + signal->edge_timings = malloc(max_edges_cnt * sizeof(uint32_t)); signal->reload_reg_buff = malloc(max_edges_cnt * sizeof(uint32_t)); signal->edge_cnt = 0; @@ -48,10 +51,10 @@ bool digital_signal_append(DigitalSignal* signal_a, DigitalSignal* signal_b) { signal_a->edge_timings[signal_a->edge_cnt] += signal_b->edge_timings[0]; } } - memcpy( - &signal_a->edge_timings[signal_a->edge_cnt], - &signal_b->edge_timings[start_copy], - (signal_b->edge_cnt - start_copy) * sizeof(float)); + + for(size_t i = 0; i < signal_b->edge_cnt - start_copy; i++) { + signal_a->edge_timings[signal_a->edge_cnt + i] = signal_b->edge_timings[start_copy + i]; + } signal_a->edge_cnt += signal_b->edge_cnt - start_copy; return true; @@ -69,34 +72,33 @@ uint32_t digital_signal_get_edges_cnt(DigitalSignal* signal) { return signal->edge_cnt; } -float digital_signal_get_edge(DigitalSignal* signal, uint32_t edge_num) { +uint32_t digital_signal_get_edge(DigitalSignal* signal, uint32_t edge_num) { furi_assert(signal); furi_assert(edge_num < signal->edge_cnt); return signal->edge_timings[edge_num]; } -static void digital_signal_prepare_arr(DigitalSignal* signal) { - float t_signal = 0; - float t_current = 0; - float r = 0; - float r_int = 0; - float r_dec = 0; +void digital_signal_prepare_arr(DigitalSignal* signal) { + uint32_t t_signal_rest = signal->edge_timings[0]; + uint32_t r_count_tick_arr = 0; + uint32_t r_rest_div = 0; for(size_t i = 0; i < signal->edge_cnt - 1; i++) { - t_signal += signal->edge_timings[i]; - r = (t_signal - t_current) / T_TIM; - r_dec = modff(r, &r_int); - if(r_dec < 0.5f) { - signal->reload_reg_buff[i] = (uint32_t)r_int - 1; + r_count_tick_arr = t_signal_rest / T_TIM; + r_rest_div = t_signal_rest % T_TIM; + t_signal_rest = signal->edge_timings[i + 1] + r_rest_div; + + if(r_rest_div < T_TIM_DIV2) { + signal->reload_reg_buff[i] = r_count_tick_arr - 1; } else { - signal->reload_reg_buff[i] = (uint32_t)r_int; + signal->reload_reg_buff[i] = r_count_tick_arr; + t_signal_rest -= T_TIM; } - t_current += (signal->reload_reg_buff[i] + 1) * T_TIM; } } -bool digital_signal_send(DigitalSignal* signal, const GpioPin* gpio) { +void digital_signal_send(DigitalSignal* signal, const GpioPin* gpio) { furi_assert(signal); furi_assert(gpio); @@ -168,6 +170,4 @@ bool digital_signal_send(DigitalSignal* signal, const GpioPin* gpio) { LL_TIM_SetCounter(TIM2, 0); LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_1); LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_2); - - return true; } diff --git a/lib/digital_signal/digital_signal.h b/lib/digital_signal/digital_signal.h index 5e20e73322b..d828444ca18 100644 --- a/lib/digital_signal/digital_signal.h +++ b/lib/digital_signal/digital_signal.h @@ -10,7 +10,7 @@ typedef struct { bool start_level; uint32_t edge_cnt; uint32_t edges_max_cnt; - float* edge_timings; + uint32_t* edge_timings; uint32_t* reload_reg_buff; } DigitalSignal; @@ -20,10 +20,12 @@ void digital_signal_free(DigitalSignal* signal); bool digital_signal_append(DigitalSignal* signal_a, DigitalSignal* signal_b); +void digital_signal_prepare_arr(DigitalSignal* signal); + bool digital_signal_get_start_level(DigitalSignal* signal); uint32_t digital_signal_get_edges_cnt(DigitalSignal* signal); -float digital_signal_get_edge(DigitalSignal* signal, uint32_t edge_num); +uint32_t digital_signal_get_edge(DigitalSignal* signal, uint32_t edge_num); -bool digital_signal_send(DigitalSignal* signal, const GpioPin* gpio); +void digital_signal_send(DigitalSignal* signal, const GpioPin* gpio); diff --git a/lib/loclass.scons b/lib/loclass.scons new file mode 100644 index 00000000000..ba43dd6e79e --- /dev/null +++ b/lib/loclass.scons @@ -0,0 +1,19 @@ +Import("env") + +env.Append( + CPPPATH=[ + "#/lib/loclass", + ], + CPPDEFINES=[ + ], +) + + +libenv = env.Clone(FW_LIB_NAME="loclass") +libenv.ApplyLibFlags() + +sources = Glob("loclass/*.c", source=True) + +lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources) +libenv.Install("${LIB_DIST_DIR}", lib) +Return("lib") diff --git a/lib/loclass/optimized_cipher.c b/lib/loclass/optimized_cipher.c new file mode 100644 index 00000000000..e4f6a58c355 --- /dev/null +++ b/lib/loclass/optimized_cipher.c @@ -0,0 +1,313 @@ +//----------------------------------------------------------------------------- +// Borrowed initially from https://github.com/holiman/loclass +// Copyright (C) 2014 Martin Holst Swende +// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// See LICENSE.txt for the text of the license. +//----------------------------------------------------------------------------- +// WARNING +// +// THIS CODE IS CREATED FOR EXPERIMENTATION AND EDUCATIONAL USE ONLY. +// +// USAGE OF THIS CODE IN OTHER WAYS MAY INFRINGE UPON THE INTELLECTUAL +// PROPERTY OF OTHER PARTIES, SUCH AS INSIDE SECURE AND HID GLOBAL, +// AND MAY EXPOSE YOU TO AN INFRINGEMENT ACTION FROM THOSE PARTIES. +// +// THIS CODE SHOULD NEVER BE USED TO INFRINGE PATENTS OR INTELLECTUAL PROPERTY RIGHTS. +//----------------------------------------------------------------------------- +// It is a reconstruction of the cipher engine used in iClass, and RFID techology. +// +// The implementation is based on the work performed by +// Flavio D. Garcia, Gerhard de Koning Gans, Roel Verdult and +// Milosch Meriac in the paper "Dismantling IClass". +//----------------------------------------------------------------------------- +/* + This file contains an optimized version of the MAC-calculation algorithm. Some measurements on + a std laptop showed it runs in about 1/3 of the time: + + Std: 0.428962 + Opt: 0.151609 + + Additionally, it is self-reliant, not requiring e.g. bitstreams from the cipherutils, thus can + be easily dropped into a code base. + + The optimizations have been performed in the following steps: + * Parameters passed by reference instead of by value. + * Iteration instead of recursion, un-nesting recursive loops into for-loops. + * Handling of bytes instead of individual bits, for less shuffling and masking + * Less creation of "objects", structs, and instead reuse of alloc:ed memory + * Inlining some functions via #define:s + + As a consequence, this implementation is less generic. Also, I haven't bothered documenting this. + For a thorough documentation, check out the MAC-calculation within cipher.c instead. + + -- MHS 2015 +**/ + +/** + + The runtime of opt_doTagMAC_2() with the MHS optimized version was 403 microseconds on Proxmark3. + This was still to slow for some newer readers which didn't want to wait that long. + + Further optimizations to speedup the MAC calculations: + * Optimized opt_Tt logic + * Look up table for opt_select + * Removing many unnecessary bit maskings (& 0x1) + * updating state in place instead of alternating use of a second state structure + * remove the necessity to reverse bits of input and output bytes + + opt_doTagMAC_2() now completes in 270 microseconds. + + -- piwi 2019 +**/ + +/** + add the possibility to do iCLASS on device only + -- iceman 2020 +**/ + +#include "optimized_cipher.h" +#include "optimized_elite.h" +#include "optimized_ikeys.h" +#include "optimized_cipherutils.h" + +static const uint8_t loclass_opt_select_LUT[256] = { + 00, 03, 02, 01, 02, 03, 00, 01, 04, 07, 07, 04, 06, 07, 05, 04, + 01, 02, 03, 00, 02, 03, 00, 01, 05, 06, 06, 05, 06, 07, 05, 04, + 06, 05, 04, 07, 04, 05, 06, 07, 06, 05, 05, 06, 04, 05, 07, 06, + 07, 04, 05, 06, 04, 05, 06, 07, 07, 04, 04, 07, 04, 05, 07, 06, + 06, 05, 04, 07, 04, 05, 06, 07, 02, 01, 01, 02, 00, 01, 03, 02, + 03, 00, 01, 02, 00, 01, 02, 03, 07, 04, 04, 07, 04, 05, 07, 06, + 00, 03, 02, 01, 02, 03, 00, 01, 00, 03, 03, 00, 02, 03, 01, 00, + 05, 06, 07, 04, 06, 07, 04, 05, 05, 06, 06, 05, 06, 07, 05, 04, + 02, 01, 00, 03, 00, 01, 02, 03, 06, 05, 05, 06, 04, 05, 07, 06, + 03, 00, 01, 02, 00, 01, 02, 03, 07, 04, 04, 07, 04, 05, 07, 06, + 02, 01, 00, 03, 00, 01, 02, 03, 02, 01, 01, 02, 00, 01, 03, 02, + 03, 00, 01, 02, 00, 01, 02, 03, 03, 00, 00, 03, 00, 01, 03, 02, + 04, 07, 06, 05, 06, 07, 04, 05, 00, 03, 03, 00, 02, 03, 01, 00, + 01, 02, 03, 00, 02, 03, 00, 01, 05, 06, 06, 05, 06, 07, 05, 04, + 04, 07, 06, 05, 06, 07, 04, 05, 04, 07, 07, 04, 06, 07, 05, 04, + 01, 02, 03, 00, 02, 03, 00, 01, 01, 02, 02, 01, 02, 03, 01, 00 +}; + +/********************** the table above has been generated with this code: ******** +#include "util.h" +static void init_opt_select_LUT(void) { + for (int r = 0; r < 256; r++) { + uint8_t r_ls2 = r << 2; + uint8_t r_and_ls2 = r & r_ls2; + uint8_t r_or_ls2 = r | r_ls2; + uint8_t z0 = (r_and_ls2 >> 5) ^ ((r & ~r_ls2) >> 4) ^ ( r_or_ls2 >> 3); + uint8_t z1 = (r_or_ls2 >> 6) ^ ( r_or_ls2 >> 1) ^ (r >> 5) ^ r; + uint8_t z2 = ((r & ~r_ls2) >> 4) ^ (r_and_ls2 >> 3) ^ r; + loclass_opt_select_LUT[r] = (z0 & 4) | (z1 & 2) | (z2 & 1); + } + print_result("", loclass_opt_select_LUT, 256); +} +***********************************************************************************/ + +#define loclass_opt__select(x,y,r) (4 & (((r & (r << 2)) >> 5) ^ ((r & ~(r << 2)) >> 4) ^ ( (r | r << 2) >> 3)))\ + |(2 & (((r | r << 2) >> 6) ^ ( (r | r << 2) >> 1) ^ (r >> 5) ^ r ^ ((x^y) << 1)))\ + |(1 & (((r & ~(r << 2)) >> 4) ^ ((r & (r << 2)) >> 3) ^ r ^ x)) + + +static void loclass_opt_successor(const uint8_t *k, LoclassState_t *s, uint8_t y) { + uint16_t Tt = s->t & 0xc533; + Tt = Tt ^ (Tt >> 1); + Tt = Tt ^ (Tt >> 4); + Tt = Tt ^ (Tt >> 10); + Tt = Tt ^ (Tt >> 8); + + s->t = (s->t >> 1); + s->t |= (Tt ^ (s->r >> 7) ^ (s->r >> 3)) << 15; + + uint8_t opt_B = s->b; + opt_B ^= s->b >> 6; + opt_B ^= s->b >> 5; + opt_B ^= s->b >> 4; + + s->b = s->b >> 1; + s->b |= (opt_B ^ s->r) << 7; + + uint8_t opt_select = loclass_opt_select_LUT[s->r] & 0x04; + opt_select |= (loclass_opt_select_LUT[s->r] ^ ((Tt ^ y) << 1)) & 0x02; + opt_select |= (loclass_opt_select_LUT[s->r] ^ Tt) & 0x01; + + uint8_t r = s->r; + s->r = (k[opt_select] ^ s->b) + s->l ; + s->l = s->r + r; +} + +static void loclass_opt_suc(const uint8_t *k, LoclassState_t *s, const uint8_t *in, uint8_t length, bool add32Zeroes) { + for (int i = 0; i < length; i++) { + uint8_t head; + head = in[i]; + loclass_opt_successor(k, s, head); + + head >>= 1; + loclass_opt_successor(k, s, head); + + head >>= 1; + loclass_opt_successor(k, s, head); + + head >>= 1; + loclass_opt_successor(k, s, head); + + head >>= 1; + loclass_opt_successor(k, s, head); + + head >>= 1; + loclass_opt_successor(k, s, head); + + head >>= 1; + loclass_opt_successor(k, s, head); + + head >>= 1; + loclass_opt_successor(k, s, head); + } + //For tag MAC, an additional 32 zeroes + if (add32Zeroes) { + for (int i = 0; i < 16; i++) { + loclass_opt_successor(k, s, 0); + loclass_opt_successor(k, s, 0); + } + } +} + +static void loclass_opt_output(const uint8_t *k, LoclassState_t *s, uint8_t *buffer) { + for (uint8_t times = 0; times < 4; times++) { + uint8_t bout = 0; + bout |= (s->r & 0x4) >> 2; + loclass_opt_successor(k, s, 0); + bout |= (s->r & 0x4) >> 1; + loclass_opt_successor(k, s, 0); + bout |= (s->r & 0x4); + loclass_opt_successor(k, s, 0); + bout |= (s->r & 0x4) << 1; + loclass_opt_successor(k, s, 0); + bout |= (s->r & 0x4) << 2; + loclass_opt_successor(k, s, 0); + bout |= (s->r & 0x4) << 3; + loclass_opt_successor(k, s, 0); + bout |= (s->r & 0x4) << 4; + loclass_opt_successor(k, s, 0); + bout |= (s->r & 0x4) << 5; + loclass_opt_successor(k, s, 0); + buffer[times] = bout; + } +} + +static void loclass_opt_MAC(uint8_t *k, uint8_t *input, uint8_t *out) { + LoclassState_t _init = { + ((k[0] ^ 0x4c) + 0xEC) & 0xFF,// l + ((k[0] ^ 0x4c) + 0x21) & 0xFF,// r + 0x4c, // b + 0xE012 // t + }; + + loclass_opt_suc(k, &_init, input, 12, false); + loclass_opt_output(k, &_init, out); +} + +static void loclass_opt_MAC_N(uint8_t *k, uint8_t *input, uint8_t in_size, uint8_t *out) { + LoclassState_t _init = { + ((k[0] ^ 0x4c) + 0xEC) & 0xFF,// l + ((k[0] ^ 0x4c) + 0x21) & 0xFF,// r + 0x4c, // b + 0xE012 // t + }; + + loclass_opt_suc(k, &_init, input, in_size, false); + loclass_opt_output(k, &_init, out); +} + +void loclass_opt_doReaderMAC(uint8_t *cc_nr_p, uint8_t *div_key_p, uint8_t mac[4]) { + uint8_t dest [] = {0, 0, 0, 0, 0, 0, 0, 0}; + loclass_opt_MAC(div_key_p, cc_nr_p, dest); + memcpy(mac, dest, 4); +} + +void loclass_opt_doReaderMAC_2(LoclassState_t _init, uint8_t *nr, uint8_t mac[4], const uint8_t *div_key_p) { + loclass_opt_suc(div_key_p, &_init, nr, 4, false); + loclass_opt_output(div_key_p, &_init, mac); +} + + +void loclass_doMAC_N(uint8_t *in_p, uint8_t in_size, uint8_t *div_key_p, uint8_t mac[4]) { + uint8_t dest [] = {0, 0, 0, 0, 0, 0, 0, 0}; + loclass_opt_MAC_N(div_key_p, in_p, in_size, dest); + memcpy(mac, dest, 4); +} + +void loclass_opt_doTagMAC(uint8_t *cc_p, const uint8_t *div_key_p, uint8_t mac[4]) { + LoclassState_t _init = { + ((div_key_p[0] ^ 0x4c) + 0xEC) & 0xFF,// l + ((div_key_p[0] ^ 0x4c) + 0x21) & 0xFF,// r + 0x4c, // b + 0xE012 // t + }; + loclass_opt_suc(div_key_p, &_init, cc_p, 12, true); + loclass_opt_output(div_key_p, &_init, mac); +} + +/** + * The tag MAC can be divided (both can, but no point in dividing the reader mac) into + * two functions, since the first 8 bytes are known, we can pre-calculate the state + * reached after feeding CC to the cipher. + * @param cc_p + * @param div_key_p + * @return the cipher state + */ +LoclassState_t loclass_opt_doTagMAC_1(uint8_t *cc_p, const uint8_t *div_key_p) { + LoclassState_t _init = { + ((div_key_p[0] ^ 0x4c) + 0xEC) & 0xFF,// l + ((div_key_p[0] ^ 0x4c) + 0x21) & 0xFF,// r + 0x4c, // b + 0xE012 // t + }; + loclass_opt_suc(div_key_p, &_init, cc_p, 8, false); + return _init; +} + +/** + * The second part of the tag MAC calculation, since the CC is already calculated into the state, + * this function is fed only the NR, and internally feeds the remaining 32 0-bits to generate the tag + * MAC response. + * @param _init - precalculated cipher state + * @param nr - the reader challenge + * @param mac - where to store the MAC + * @param div_key_p - the key to use + */ +void loclass_opt_doTagMAC_2(LoclassState_t _init, uint8_t *nr, uint8_t mac[4], const uint8_t *div_key_p) { + loclass_opt_suc(div_key_p, &_init, nr, 4, true); + loclass_opt_output(div_key_p, &_init, mac); +} + +void loclass_iclass_calc_div_key(uint8_t *csn, uint8_t *key, uint8_t *div_key, bool elite) { + if (elite) { + uint8_t keytable[128] = {0}; + uint8_t key_index[8] = {0}; + uint8_t key_sel[8] = { 0 }; + uint8_t key_sel_p[8] = { 0 }; + loclass_hash2(key, keytable); + loclass_hash1(csn, key_index); + for (uint8_t i = 0; i < 8 ; i++) + key_sel[i] = keytable[key_index[i]]; + + //Permute from iclass format to standard format + loclass_permutekey_rev(key_sel, key_sel_p); + loclass_diversifyKey(csn, key_sel_p, div_key); + } else { + loclass_diversifyKey(csn, key, div_key); + } +} diff --git a/lib/loclass/optimized_cipher.h b/lib/loclass/optimized_cipher.h new file mode 100644 index 00000000000..e7b8cbd67ff --- /dev/null +++ b/lib/loclass/optimized_cipher.h @@ -0,0 +1,90 @@ +//----------------------------------------------------------------------------- +// Borrowed initially from https://github.com/holiman/loclass +// More recently from https://github.com/RfidResearchGroup/proxmark3 +// Copyright (C) 2014 Martin Holst Swende +// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// See LICENSE.txt for the text of the license. +//----------------------------------------------------------------------------- +// WARNING +// +// THIS CODE IS CREATED FOR EXPERIMENTATION AND EDUCATIONAL USE ONLY. +// +// USAGE OF THIS CODE IN OTHER WAYS MAY INFRINGE UPON THE INTELLECTUAL +// PROPERTY OF OTHER PARTIES, SUCH AS INSIDE SECURE AND HID GLOBAL, +// AND MAY EXPOSE YOU TO AN INFRINGEMENT ACTION FROM THOSE PARTIES. +// +// THIS CODE SHOULD NEVER BE USED TO INFRINGE PATENTS OR INTELLECTUAL PROPERTY RIGHTS. +//----------------------------------------------------------------------------- +// It is a reconstruction of the cipher engine used in iClass, and RFID techology. +// +// The implementation is based on the work performed by +// Flavio D. Garcia, Gerhard de Koning Gans, Roel Verdult and +// Milosch Meriac in the paper "Dismantling IClass". +//----------------------------------------------------------------------------- +#ifndef OPTIMIZED_CIPHER_H +#define OPTIMIZED_CIPHER_H +#include +#include +#include +#include + +/** +* Definition 1 (Cipher state). A cipher state of iClass s is an element of F 40/2 +* consisting of the following four components: +* 1. the left register l = (l 0 . . . l 7 ) ∈ F 8/2 ; +* 2. the right register r = (r 0 . . . r 7 ) ∈ F 8/2 ; +* 3. the top register t = (t 0 . . . t 15 ) ∈ F 16/2 . +* 4. the bottom register b = (b 0 . . . b 7 ) ∈ F 8/2 . +**/ +typedef struct { + uint8_t l; + uint8_t r; + uint8_t b; + uint16_t t; +} LoclassState_t; + +/** The reader MAC is MAC(key, CC * NR ) + **/ +void loclass_opt_doReaderMAC(uint8_t *cc_nr_p, uint8_t *div_key_p, uint8_t mac[4]); + +void loclass_opt_doReaderMAC_2(LoclassState_t _init, uint8_t *nr, uint8_t mac[4], const uint8_t *div_key_p); + +/** + * The tag MAC is MAC(key, CC * NR * 32x0)) + */ +void loclass_opt_doTagMAC(uint8_t *cc_p, const uint8_t *div_key_p, uint8_t mac[4]); + +/** + * The tag MAC can be divided (both can, but no point in dividing the reader mac) into + * two functions, since the first 8 bytes are known, we can pre-calculate the state + * reached after feeding CC to the cipher. + * @param cc_p + * @param div_key_p + * @return the cipher state + */ +LoclassState_t loclass_opt_doTagMAC_1(uint8_t *cc_p, const uint8_t *div_key_p); +/** + * The second part of the tag MAC calculation, since the CC is already calculated into the state, + * this function is fed only the NR, and internally feeds the remaining 32 0-bits to generate the tag + * MAC response. + * @param _init - precalculated cipher state + * @param nr - the reader challenge + * @param mac - where to store the MAC + * @param div_key_p - the key to use + */ +void loclass_opt_doTagMAC_2(LoclassState_t _init, uint8_t *nr, uint8_t mac[4], const uint8_t *div_key_p); + +void loclass_doMAC_N(uint8_t *in_p, uint8_t in_size, uint8_t *div_key_p, uint8_t mac[4]); +void loclass_iclass_calc_div_key(uint8_t *csn, uint8_t *key, uint8_t *div_key, bool elite); +#endif // OPTIMIZED_CIPHER_H diff --git a/lib/loclass/optimized_cipherutils.c b/lib/loclass/optimized_cipherutils.c new file mode 100644 index 00000000000..c5bcbaccdad --- /dev/null +++ b/lib/loclass/optimized_cipherutils.c @@ -0,0 +1,137 @@ +//----------------------------------------------------------------------------- +// Borrowed initially from https://github.com/holiman/loclass +// Copyright (C) 2014 Martin Holst Swende +// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// See LICENSE.txt for the text of the license. +//----------------------------------------------------------------------------- +// WARNING +// +// THIS CODE IS CREATED FOR EXPERIMENTATION AND EDUCATIONAL USE ONLY. +// +// USAGE OF THIS CODE IN OTHER WAYS MAY INFRINGE UPON THE INTELLECTUAL +// PROPERTY OF OTHER PARTIES, SUCH AS INSIDE SECURE AND HID GLOBAL, +// AND MAY EXPOSE YOU TO AN INFRINGEMENT ACTION FROM THOSE PARTIES. +// +// THIS CODE SHOULD NEVER BE USED TO INFRINGE PATENTS OR INTELLECTUAL PROPERTY RIGHTS. +//----------------------------------------------------------------------------- +// It is a reconstruction of the cipher engine used in iClass, and RFID techology. +// +// The implementation is based on the work performed by +// Flavio D. Garcia, Gerhard de Koning Gans, Roel Verdult and +// Milosch Meriac in the paper "Dismantling IClass". +//----------------------------------------------------------------------------- +#include "optimized_cipherutils.h" +#include + +/** + * + * @brief Return and remove the first bit (x0) in the stream : + * @param stream + * @return + */ +bool loclass_headBit(LoclassBitstreamIn_t *stream) { + int bytepos = stream->position >> 3; // divide by 8 + int bitpos = (stream->position++) & 7; // mask out 00000111 + return (*(stream->buffer + bytepos) >> (7 - bitpos)) & 1; +} +/** + * @brief Return and remove the last bit (xn) in the stream: + * @param stream + * @return + */ +bool loclass_tailBit(LoclassBitstreamIn_t *stream) { + int bitpos = stream->numbits - 1 - (stream->position++); + + int bytepos = bitpos >> 3; + bitpos &= 7; + return (*(stream->buffer + bytepos) >> (7 - bitpos)) & 1; +} +/** + * @brief Pushes bit onto the stream + * @param stream + * @param bit + */ +void loclass_pushBit(LoclassBitstreamOut_t *stream, bool bit) { + int bytepos = stream->position >> 3; // divide by 8 + int bitpos = stream->position & 7; + *(stream->buffer + bytepos) |= (bit) << (7 - bitpos); + stream->position++; + stream->numbits++; +} + +/** + * @brief Pushes the lower six bits onto the stream + * as b0 b1 b2 b3 b4 b5 b6 + * @param stream + * @param bits + */ +void loclass_push6bits(LoclassBitstreamOut_t *stream, uint8_t bits) { + loclass_pushBit(stream, bits & 0x20); + loclass_pushBit(stream, bits & 0x10); + loclass_pushBit(stream, bits & 0x08); + loclass_pushBit(stream, bits & 0x04); + loclass_pushBit(stream, bits & 0x02); + loclass_pushBit(stream, bits & 0x01); +} + +/** + * @brief loclass_bitsLeft + * @param stream + * @return number of bits left in stream + */ +int loclass_bitsLeft(LoclassBitstreamIn_t *stream) { + return stream->numbits - stream->position; +} +/** + * @brief numBits + * @param stream + * @return Number of bits stored in stream + */ +void loclass_x_num_to_bytes(uint64_t n, size_t len, uint8_t *dest) { + while (len--) { + dest[len] = (uint8_t) n; + n >>= 8; + } +} + +uint64_t loclass_x_bytes_to_num(uint8_t *src, size_t len) { + uint64_t num = 0; + while (len--) { + num = (num << 8) | (*src); + src++; + } + return num; +} + +uint8_t loclass_reversebytes(uint8_t b) { + b = (b & 0xF0) >> 4 | (b & 0x0F) << 4; + b = (b & 0xCC) >> 2 | (b & 0x33) << 2; + b = (b & 0xAA) >> 1 | (b & 0x55) << 1; + return b; +} + +void loclass_reverse_arraybytes(uint8_t *arr, size_t len) { + uint8_t i; + for (i = 0; i < len ; i++) { + arr[i] = loclass_reversebytes(arr[i]); + } +} + +void loclass_reverse_arraycopy(uint8_t *arr, uint8_t *dest, size_t len) { + uint8_t i; + for (i = 0; i < len ; i++) { + dest[i] = loclass_reversebytes(arr[i]); + } +} + diff --git a/lib/loclass/optimized_cipherutils.h b/lib/loclass/optimized_cipherutils.h new file mode 100644 index 00000000000..cb9d2724ae0 --- /dev/null +++ b/lib/loclass/optimized_cipherutils.h @@ -0,0 +1,64 @@ +//----------------------------------------------------------------------------- +// Borrowed initially from https://github.com/holiman/loclass +// More recently from https://github.com/RfidResearchGroup/proxmark3 +// Copyright (C) 2014 Martin Holst Swende +// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// See LICENSE.txt for the text of the license. +//----------------------------------------------------------------------------- +// WARNING +// +// THIS CODE IS CREATED FOR EXPERIMENTATION AND EDUCATIONAL USE ONLY. +// +// USAGE OF THIS CODE IN OTHER WAYS MAY INFRINGE UPON THE INTELLECTUAL +// PROPERTY OF OTHER PARTIES, SUCH AS INSIDE SECURE AND HID GLOBAL, +// AND MAY EXPOSE YOU TO AN INFRINGEMENT ACTION FROM THOSE PARTIES. +// +// THIS CODE SHOULD NEVER BE USED TO INFRINGE PATENTS OR INTELLECTUAL PROPERTY RIGHTS. +//----------------------------------------------------------------------------- +// It is a reconstruction of the cipher engine used in iClass, and RFID techology. +// +// The implementation is based on the work performed by +// Flavio D. Garcia, Gerhard de Koning Gans, Roel Verdult and +// Milosch Meriac in the paper "Dismantling IClass". +//----------------------------------------------------------------------------- +#ifndef CIPHERUTILS_H +#define CIPHERUTILS_H +#include +#include +#include + +typedef struct { + uint8_t *buffer; + uint8_t numbits; + uint8_t position; +} LoclassBitstreamIn_t; + +typedef struct { + uint8_t *buffer; + uint8_t numbits; + uint8_t position; +} LoclassBitstreamOut_t; + +bool loclass_headBit(LoclassBitstreamIn_t *stream); +bool loclass_tailBit(LoclassBitstreamIn_t *stream); +void loclass_pushBit(LoclassBitstreamOut_t *stream, bool bit); +int loclass_bitsLeft(LoclassBitstreamIn_t *stream); + +void loclass_push6bits(LoclassBitstreamOut_t *stream, uint8_t bits); +void loclass_x_num_to_bytes(uint64_t n, size_t len, uint8_t *dest); +uint64_t loclass_x_bytes_to_num(uint8_t *src, size_t len); +uint8_t loclass_reversebytes(uint8_t b); +void loclass_reverse_arraybytes(uint8_t *arr, size_t len); +void loclass_reverse_arraycopy(uint8_t *arr, uint8_t *dest, size_t len); +#endif // CIPHERUTILS_H diff --git a/lib/loclass/optimized_elite.c b/lib/loclass/optimized_elite.c new file mode 100644 index 00000000000..fc1e5d7484e --- /dev/null +++ b/lib/loclass/optimized_elite.c @@ -0,0 +1,234 @@ +//----------------------------------------------------------------------------- +// Borrowed initially from https://github.com/holiman/loclass +// Copyright (C) 2014 Martin Holst Swende +// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// See LICENSE.txt for the text of the license. +//----------------------------------------------------------------------------- +// WARNING +// +// THIS CODE IS CREATED FOR EXPERIMENTATION AND EDUCATIONAL USE ONLY. +// +// USAGE OF THIS CODE IN OTHER WAYS MAY INFRINGE UPON THE INTELLECTUAL +// PROPERTY OF OTHER PARTIES, SUCH AS INSIDE SECURE AND HID GLOBAL, +// AND MAY EXPOSE YOU TO AN INFRINGEMENT ACTION FROM THOSE PARTIES. +// +// THIS CODE SHOULD NEVER BE USED TO INFRINGE PATENTS OR INTELLECTUAL PROPERTY RIGHTS. +//----------------------------------------------------------------------------- +// It is a reconstruction of the cipher engine used in iClass, and RFID techology. +// +// The implementation is based on the work performed by +// Flavio D. Garcia, Gerhard de Koning Gans, Roel Verdult and +// Milosch Meriac in the paper "Dismantling IClass". +//----------------------------------------------------------------------------- +#include "optimized_elite.h" + +#include +#include +#include +#include +#include "optimized_ikeys.h" + +/** + * @brief Permutes a key from standard NIST format to Iclass specific format + * from http://www.proxmark.org/forum/viewtopic.php?pid=11220#p11220 + * + * If you loclass_permute [6c 8d 44 f9 2a 2d 01 bf] you get [8a 0d b9 88 bb a7 90 ea] as shown below. + * + * 1 0 1 1 1 1 1 1 bf + * 0 0 0 0 0 0 0 1 01 + * 0 0 1 0 1 1 0 1 2d + * 0 0 1 0 1 0 1 0 2a + * 1 1 1 1 1 0 0 1 f9 + * 0 1 0 0 0 1 0 0 44 + * 1 0 0 0 1 1 0 1 8d + * 0 1 1 0 1 1 0 0 6c + * + * 8 0 b 8 b a 9 e + * a d 9 8 b 7 0 a + * + * @param key + * @param dest + */ +void loclass_permutekey(const uint8_t key[8], uint8_t dest[8]) { + int i; + for (i = 0 ; i < 8 ; i++) { + dest[i] = (((key[7] & (0x80 >> i)) >> (7 - i)) << 7) | + (((key[6] & (0x80 >> i)) >> (7 - i)) << 6) | + (((key[5] & (0x80 >> i)) >> (7 - i)) << 5) | + (((key[4] & (0x80 >> i)) >> (7 - i)) << 4) | + (((key[3] & (0x80 >> i)) >> (7 - i)) << 3) | + (((key[2] & (0x80 >> i)) >> (7 - i)) << 2) | + (((key[1] & (0x80 >> i)) >> (7 - i)) << 1) | + (((key[0] & (0x80 >> i)) >> (7 - i)) << 0); + } +} +/** + * Permutes a key from iclass specific format to NIST format + * @brief loclass_permutekey_rev + * @param key + * @param dest + */ +void loclass_permutekey_rev(const uint8_t key[8], uint8_t dest[8]) { + int i; + for (i = 0 ; i < 8 ; i++) { + dest[7 - i] = (((key[0] & (0x80 >> i)) >> (7 - i)) << 7) | + (((key[1] & (0x80 >> i)) >> (7 - i)) << 6) | + (((key[2] & (0x80 >> i)) >> (7 - i)) << 5) | + (((key[3] & (0x80 >> i)) >> (7 - i)) << 4) | + (((key[4] & (0x80 >> i)) >> (7 - i)) << 3) | + (((key[5] & (0x80 >> i)) >> (7 - i)) << 2) | + (((key[6] & (0x80 >> i)) >> (7 - i)) << 1) | + (((key[7] & (0x80 >> i)) >> (7 - i)) << 0); + } +} + +/** + * Helper function for loclass_hash1 + * @brief loclass_rr + * @param val + * @return + */ +static uint8_t loclass_rr(uint8_t val) { + return val >> 1 | ((val & 1) << 7); +} + +/** + * Helper function for loclass_hash1 + * @brief rl + * @param val + * @return + */ +static uint8_t loclass_rl(uint8_t val) { + return val << 1 | ((val & 0x80) >> 7); +} + +/** + * Helper function for loclass_hash1 + * @brief loclass_swap + * @param val + * @return + */ +static uint8_t loclass_swap(uint8_t val) { + return ((val >> 4) & 0xFF) | ((val & 0xFF) << 4); +} + +/** + * Hash1 takes CSN as input, and determines what bytes in the keytable will be used + * when constructing the K_sel. + * @param csn the CSN used + * @param k output + */ +void loclass_hash1(const uint8_t csn[], uint8_t k[]) { + k[0] = csn[0] ^ csn[1] ^ csn[2] ^ csn[3] ^ csn[4] ^ csn[5] ^ csn[6] ^ csn[7]; + k[1] = csn[0] + csn[1] + csn[2] + csn[3] + csn[4] + csn[5] + csn[6] + csn[7]; + k[2] = loclass_rr(loclass_swap(csn[2] + k[1])); + k[3] = loclass_rl(loclass_swap(csn[3] + k[0])); + k[4] = ~loclass_rr(csn[4] + k[2]) + 1; + k[5] = ~loclass_rl(csn[5] + k[3]) + 1; + k[6] = loclass_rr(csn[6] + (k[4] ^ 0x3c)); + k[7] = loclass_rl(csn[7] + (k[5] ^ 0xc3)); + + k[7] &= 0x7F; + k[6] &= 0x7F; + k[5] &= 0x7F; + k[4] &= 0x7F; + k[3] &= 0x7F; + k[2] &= 0x7F; + k[1] &= 0x7F; + k[0] &= 0x7F; +} +/** +Definition 14. Define the rotate key function loclass_rk : (F 82 ) 8 Γ— N β†’ (F 82 ) 8 as +loclass_rk(x [0] . . . x [7] , 0) = x [0] . . . x [7] +loclass_rk(x [0] . . . x [7] , n + 1) = loclass_rk(loclass_rl(x [0] ) . . . loclass_rl(x [7] ), n) +**/ +static void loclass_rk(uint8_t *key, uint8_t n, uint8_t *outp_key) { + memcpy(outp_key, key, 8); + uint8_t j; + while (n-- > 0) { + for (j = 0; j < 8 ; j++) + outp_key[j] = loclass_rl(outp_key[j]); + } + return; +} + +static mbedtls_des_context loclass_ctx_enc; +static mbedtls_des_context loclass_ctx_dec; + +static void loclass_desdecrypt_iclass(uint8_t *iclass_key, uint8_t *input, uint8_t *output) { + uint8_t key_std_format[8] = {0}; + loclass_permutekey_rev(iclass_key, key_std_format); + mbedtls_des_setkey_dec(&loclass_ctx_dec, key_std_format); + mbedtls_des_crypt_ecb(&loclass_ctx_dec, input, output); +} + +static void loclass_desencrypt_iclass(uint8_t *iclass_key, uint8_t *input, uint8_t *output) { + uint8_t key_std_format[8] = {0}; + loclass_permutekey_rev(iclass_key, key_std_format); + mbedtls_des_setkey_enc(&loclass_ctx_enc, key_std_format); + mbedtls_des_crypt_ecb(&loclass_ctx_enc, input, output); +} + +/** + * @brief Insert uint8_t[8] custom master key to calculate hash2 and return key_select. + * @param key unpermuted custom key + * @param loclass_hash1 loclass_hash1 + * @param key_sel output key_sel=h[loclass_hash1[i]] + */ +void hash2(uint8_t *key64, uint8_t *outp_keytable) { + /** + *Expected: + * High Security Key Table + + 00 F1 35 59 A1 0D 5A 26 7F 18 60 0B 96 8A C0 25 C1 + 10 BF A1 3B B0 FF 85 28 75 F2 1F C6 8F 0E 74 8F 21 + 20 14 7A 55 16 C8 A9 7D B3 13 0C 5D C9 31 8D A9 B2 + 30 A3 56 83 0F 55 7E DE 45 71 21 D2 6D C1 57 1C 9C + 40 78 2F 64 51 42 7B 64 30 FA 26 51 76 D3 E0 FB B6 + 50 31 9F BF 2F 7E 4F 94 B4 BD 4F 75 91 E3 1B EB 42 + 60 3F 88 6F B8 6C 2C 93 0D 69 2C D5 20 3C C1 61 95 + 70 43 08 A0 2F FE B3 26 D7 98 0B 34 7B 47 70 A0 AB + + **** The 64-bit HS Custom Key Value = 5B7C62C491C11B39 ******/ + uint8_t key64_negated[8] = {0}; + uint8_t z[8][8] = {{0}, {0}}; + uint8_t temp_output[8] = {0}; + + //calculate complement of key + int i; + for (i = 0; i < 8; i++) + key64_negated[i] = ~key64[i]; + + // Once again, key is on iclass-format + loclass_desencrypt_iclass(key64, key64_negated, z[0]); + + uint8_t y[8][8] = {{0}, {0}}; + + // y[0]=DES_dec(z[0],~key) + // Once again, key is on iclass-format + loclass_desdecrypt_iclass(z[0], key64_negated, y[0]); + + for (i = 1; i < 8; i++) { + loclass_rk(key64, i, temp_output); + loclass_desdecrypt_iclass(temp_output, z[i - 1], z[i]); + loclass_desencrypt_iclass(temp_output, y[i - 1], y[i]); + } + + if (outp_keytable != NULL) { + for (i = 0 ; i < 8 ; i++) { + memcpy(outp_keytable + i * 16, y[i], 8); + memcpy(outp_keytable + 8 + i * 16, z[i], 8); + } + } +} diff --git a/lib/loclass/optimized_elite.h b/lib/loclass/optimized_elite.h new file mode 100644 index 00000000000..9bc30e575f8 --- /dev/null +++ b/lib/loclass/optimized_elite.h @@ -0,0 +1,58 @@ +//----------------------------------------------------------------------------- +// Borrowed initially from https://github.com/holiman/loclass +// More recently from https://github.com/RfidResearchGroup/proxmark3 +// Copyright (C) 2014 Martin Holst Swende +// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// See LICENSE.txt for the text of the license. +//----------------------------------------------------------------------------- +// WARNING +// +// THIS CODE IS CREATED FOR EXPERIMENTATION AND EDUCATIONAL USE ONLY. +// +// USAGE OF THIS CODE IN OTHER WAYS MAY INFRINGE UPON THE INTELLECTUAL +// PROPERTY OF OTHER PARTIES, SUCH AS INSIDE SECURE AND HID GLOBAL, +// AND MAY EXPOSE YOU TO AN INFRINGEMENT ACTION FROM THOSE PARTIES. +// +// THIS CODE SHOULD NEVER BE USED TO INFRINGE PATENTS OR INTELLECTUAL PROPERTY RIGHTS. +//----------------------------------------------------------------------------- +// It is a reconstruction of the cipher engine used in iClass, and RFID techology. +// +// The implementation is based on the work performed by +// Flavio D. Garcia, Gerhard de Koning Gans, Roel Verdult and +// Milosch Meriac in the paper "Dismantling IClass". +//----------------------------------------------------------------------------- +#ifndef ELITE_CRACK_H +#define ELITE_CRACK_H + +#include +#include + +void loclass_permutekey(const uint8_t key[8], uint8_t dest[8]); +/** + * Permutes a key from iclass specific format to NIST format + * @brief loclass_permutekey_rev + * @param key + * @param dest + */ +void loclass_permutekey_rev(const uint8_t key[8], uint8_t dest[8]); +/** + * Hash1 takes CSN as input, and determines what bytes in the keytable will be used + * when constructing the K_sel. + * @param csn the CSN used + * @param k output + */ +void loclass_hash1(const uint8_t *csn, uint8_t *k); +void loclass_hash2(uint8_t *key64, uint8_t *outp_keytable); + +#endif diff --git a/lib/loclass/optimized_ikeys.c b/lib/loclass/optimized_ikeys.c new file mode 100644 index 00000000000..9531c16c281 --- /dev/null +++ b/lib/loclass/optimized_ikeys.c @@ -0,0 +1,321 @@ +//----------------------------------------------------------------------------- +// Borrowed initially from https://github.com/holiman/loclass +// Copyright (C) 2014 Martin Holst Swende +// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// See LICENSE.txt for the text of the license. +//----------------------------------------------------------------------------- +// WARNING +// +// THIS CODE IS CREATED FOR EXPERIMENTATION AND EDUCATIONAL USE ONLY. +// +// USAGE OF THIS CODE IN OTHER WAYS MAY INFRINGE UPON THE INTELLECTUAL +// PROPERTY OF OTHER PARTIES, SUCH AS INSIDE SECURE AND HID GLOBAL, +// AND MAY EXPOSE YOU TO AN INFRINGEMENT ACTION FROM THOSE PARTIES. +// +// THIS CODE SHOULD NEVER BE USED TO INFRINGE PATENTS OR INTELLECTUAL PROPERTY RIGHTS. +//----------------------------------------------------------------------------- +// It is a reconstruction of the cipher engine used in iClass, and RFID techology. +// +// The implementation is based on the work performed by +// Flavio D. Garcia, Gerhard de Koning Gans, Roel Verdult and +// Milosch Meriac in the paper "Dismantling IClass". +//----------------------------------------------------------------------------- + +/** +From "Dismantling iclass": + This section describes in detail the built-in key diversification algorithm of iClass. + Besides the obvious purpose of deriving a card key from a master key, this + algorithm intends to circumvent weaknesses in the cipher by preventing the + usage of certain β€˜weak’ keys. In order to compute a diversified key, the iClass + reader first encrypts the card identity id with the master key K, using single + DES. The resulting ciphertext is then input to a function called loclass_hash0 which + outputs the diversified key k. + + k = loclass_hash0(DES enc (id, K)) + + Here the DES encryption of id with master key K outputs a cryptogram c + of 64 bits. These 64 bits are divided as c = x, y, z [0] , . . . , z [7] ∈ F 82 Γ— F 82 Γ— (F 62 ) 8 + which is used as input to the loclass_hash0 function. This function introduces some + obfuscation by performing a number of permutations, complement and modulo + operations, see Figure 2.5. Besides that, it checks for and removes patterns like + similar key bytes, which could produce a strong bias in the cipher. Finally, the + output of loclass_hash0 is the diversified card key k = k [0] , . . . , k [7] ∈ (F 82 ) 8 . + +**/ +#include "optimized_ikeys.h" + +#include +#include +#include +#include +#include "optimized_cipherutils.h" + +static const uint8_t loclass_pi[35] = { + 0x0F, 0x17, 0x1B, 0x1D, 0x1E, 0x27, 0x2B, 0x2D, + 0x2E, 0x33, 0x35, 0x39, 0x36, 0x3A, 0x3C, 0x47, + 0x4B, 0x4D, 0x4E, 0x53, 0x55, 0x56, 0x59, 0x5A, + 0x5C, 0x63, 0x65, 0x66, 0x69, 0x6A, 0x6C, 0x71, + 0x72, 0x74, 0x78 +}; + +/** + * @brief The key diversification algorithm uses 6-bit bytes. + * This implementation uses 64 bit uint to pack seven of them into one + * variable. When they are there, they are placed as follows: + * XXXX XXXX N0 .... N7, occupying the last 48 bits. + * + * This function picks out one from such a collection + * @param all + * @param n bitnumber + * @return + */ +static uint8_t loclass_getSixBitByte(uint64_t c, int n) { + return (c >> (42 - 6 * n)) & 0x3F; +} + +/** + * @brief Puts back a six-bit 'byte' into a uint64_t. + * @param c buffer + * @param z the value to place there + * @param n bitnumber. + */ +static void loclass_pushbackSixBitByte(uint64_t *c, uint8_t z, int n) { + //0x XXXX YYYY ZZZZ ZZZZ ZZZZ + // ^z0 ^z7 + //z0: 1111 1100 0000 0000 + + uint64_t masked = z & 0x3F; + uint64_t eraser = 0x3F; + masked <<= 42 - 6 * n; + eraser <<= 42 - 6 * n; + + //masked <<= 6*n; + //eraser <<= 6*n; + + eraser = ~eraser; + (*c) &= eraser; + (*c) |= masked; + +} +/** + * @brief Swaps the z-values. + * If the input value has format XYZ0Z1...Z7, the output will have the format + * XYZ7Z6...Z0 instead + * @param c + * @return + */ +static uint64_t loclass_swapZvalues(uint64_t c) { + uint64_t newz = 0; + loclass_pushbackSixBitByte(&newz, loclass_getSixBitByte(c, 0), 7); + loclass_pushbackSixBitByte(&newz, loclass_getSixBitByte(c, 1), 6); + loclass_pushbackSixBitByte(&newz, loclass_getSixBitByte(c, 2), 5); + loclass_pushbackSixBitByte(&newz, loclass_getSixBitByte(c, 3), 4); + loclass_pushbackSixBitByte(&newz, loclass_getSixBitByte(c, 4), 3); + loclass_pushbackSixBitByte(&newz, loclass_getSixBitByte(c, 5), 2); + loclass_pushbackSixBitByte(&newz, loclass_getSixBitByte(c, 6), 1); + loclass_pushbackSixBitByte(&newz, loclass_getSixBitByte(c, 7), 0); + newz |= (c & 0xFFFF000000000000); + return newz; +} + +/** +* @return 4 six-bit bytes chunked into a uint64_t,as 00..00a0a1a2a3 +*/ +static uint64_t loclass_ck(int i, int j, uint64_t z) { + if (i == 1 && j == -1) { + // loclass_ck(1, βˆ’1, z [0] . . . z [3] ) = z [0] . . . z [3] + return z; + } else if (j == -1) { + // loclass_ck(i, βˆ’1, z [0] . . . z [3] ) = loclass_ck(i βˆ’ 1, i βˆ’ 2, z [0] . . . z [3] ) + return loclass_ck(i - 1, i - 2, z); + } + + if (loclass_getSixBitByte(z, i) == loclass_getSixBitByte(z, j)) { + //loclass_ck(i, j βˆ’ 1, z [0] . . . z [i] ← j . . . z [3] ) + uint64_t newz = 0; + int c; + for (c = 0; c < 4; c++) { + uint8_t val = loclass_getSixBitByte(z, c); + if (c == i) + loclass_pushbackSixBitByte(&newz, j, c); + else + loclass_pushbackSixBitByte(&newz, val, c); + } + return loclass_ck(i, j - 1, newz); + } else { + return loclass_ck(i, j - 1, z); + } +} +/** + + Definition 8. + Let the function check : (F 62 ) 8 β†’ (F 62 ) 8 be defined as + check(z [0] . . . z [7] ) = loclass_ck(3, 2, z [0] . . . z [3] ) Β· loclass_ck(3, 2, z [4] . . . z [7] ) + + where loclass_ck : N Γ— N Γ— (F 62 ) 4 β†’ (F 62 ) 4 is defined as + + loclass_ck(1, βˆ’1, z [0] . . . z [3] ) = z [0] . . . z [3] + loclass_ck(i, βˆ’1, z [0] . . . z [3] ) = loclass_ck(i βˆ’ 1, i βˆ’ 2, z [0] . . . z [3] ) + loclass_ck(i, j, z [0] . . . z [3] ) = + loclass_ck(i, j βˆ’ 1, z [0] . . . z [i] ← j . . . z [3] ), if z [i] = z [j] ; + loclass_ck(i, j βˆ’ 1, z [0] . . . z [3] ), otherwise + + otherwise. +**/ + +static uint64_t loclass_check(uint64_t z) { + //These 64 bits are divided as c = x, y, z [0] , . . . , z [7] + + // loclass_ck(3, 2, z [0] . . . z [3] ) + uint64_t ck1 = loclass_ck(3, 2, z); + + // loclass_ck(3, 2, z [4] . . . z [7] ) + uint64_t ck2 = loclass_ck(3, 2, z << 24); + + //The loclass_ck function will place the values + // in the middle of z. + ck1 &= 0x00000000FFFFFF000000; + ck2 &= 0x00000000FFFFFF000000; + + return ck1 | ck2 >> 24; +} + +static void loclass_permute(LoclassBitstreamIn_t *p_in, uint64_t z, int l, int r, LoclassBitstreamOut_t *out) { + if (loclass_bitsLeft(p_in) == 0) + return; + + bool pn = loclass_tailBit(p_in); + if (pn) { // pn = 1 + uint8_t zl = loclass_getSixBitByte(z, l); + + loclass_push6bits(out, zl + 1); + loclass_permute(p_in, z, l + 1, r, out); + } else { // otherwise + uint8_t zr = loclass_getSixBitByte(z, r); + + loclass_push6bits(out, zr); + loclass_permute(p_in, z, l, r + 1, out); + } +} + +/** + * @brief + *Definition 11. Let the function loclass_hash0 : F 82 Γ— F 82 Γ— (F 62 ) 8 β†’ (F 82 ) 8 be defined as + * loclass_hash0(x, y, z [0] . . . z [7] ) = k [0] . . . k [7] where + * z'[i] = (z[i] mod (63-i)) + i i = 0...3 + * z'[i+4] = (z[i+4] mod (64-i)) + i i = 0...3 + * αΊ‘ = check(z'); + * @param c + * @param k this is where the diversified key is put (should be 8 bytes) + * @return + */ +void loclass_hash0(uint64_t c, uint8_t k[8]) { + c = loclass_swapZvalues(c); + + //These 64 bits are divided as c = x, y, z [0] , . . . , z [7] + // x = 8 bits + // y = 8 bits + // z0-z7 6 bits each : 48 bits + uint8_t x = (c & 0xFF00000000000000) >> 56; + uint8_t y = (c & 0x00FF000000000000) >> 48; + uint64_t zP = 0; + + for (int n = 0; n < 4 ; n++) { + uint8_t zn = loclass_getSixBitByte(c, n); + uint8_t zn4 = loclass_getSixBitByte(c, n + 4); + uint8_t _zn = (zn % (63 - n)) + n; + uint8_t _zn4 = (zn4 % (64 - n)) + n; + loclass_pushbackSixBitByte(&zP, _zn, n); + loclass_pushbackSixBitByte(&zP, _zn4, n + 4); + } + + uint64_t zCaret = loclass_check(zP); + uint8_t p = loclass_pi[x % 35]; + + if (x & 1) //Check if x7 is 1 + p = ~p; + + LoclassBitstreamIn_t p_in = { &p, 8, 0 }; + uint8_t outbuffer[] = {0, 0, 0, 0, 0, 0, 0, 0}; + LoclassBitstreamOut_t out = {outbuffer, 0, 0}; + loclass_permute(&p_in, zCaret, 0, 4, &out); //returns 48 bits? or 6 8-bytes + + //Out is now a buffer containing six-bit bytes, should be 48 bits + // if all went well + //Shift z-values down onto the lower segment + + uint64_t zTilde = loclass_x_bytes_to_num(outbuffer, sizeof(outbuffer)); + + zTilde >>= 16; + + for (int i = 0; i < 8; i++) { + // the key on index i is first a bit from y + // then six bits from z, + // then a bit from p + + // Init with zeroes + k[i] = 0; + // First, place yi leftmost in k + //k[i] |= (y << i) & 0x80 ; + + // First, place y(7-i) leftmost in k + k[i] |= (y << (7 - i)) & 0x80 ; + + uint8_t zTilde_i = loclass_getSixBitByte(zTilde, i); + // zTildeI is now on the form 00XXXXXX + // with one leftshift, it'll be + // 0XXXXXX0 + // So after leftshift, we can OR it into k + // However, when doing complement, we need to + // again MASK 0XXXXXX0 (0x7E) + zTilde_i <<= 1; + + //Finally, add bit from p or p-mod + //Shift bit i into rightmost location (mask only after complement) + uint8_t p_i = p >> i & 0x1; + + if (k[i]) { // yi = 1 + k[i] |= ~zTilde_i & 0x7E; + k[i] |= p_i & 1; + k[i] += 1; + + } else { // otherwise + k[i] |= zTilde_i & 0x7E; + k[i] |= (~p_i) & 1; + } + } +} +/** + * @brief Performs Elite-class key diversification + * @param csn + * @param key + * @param div_key + */ +void loclass_diversifyKey(uint8_t *csn, const uint8_t *key, uint8_t *div_key) { + mbedtls_des_context loclass_ctx_enc; + + // Prepare the DES key + mbedtls_des_setkey_enc(&loclass_ctx_enc, key); + + uint8_t crypted_csn[8] = {0}; + + // Calculate DES(CSN, KEY) + mbedtls_des_crypt_ecb(&loclass_ctx_enc, csn, crypted_csn); + + //Calculate HASH0(DES)) + uint64_t c_csn = loclass_x_bytes_to_num(crypted_csn, sizeof(crypted_csn)); + + loclass_hash0(c_csn, div_key); +} + diff --git a/lib/loclass/optimized_ikeys.h b/lib/loclass/optimized_ikeys.h new file mode 100644 index 00000000000..e960b8be279 --- /dev/null +++ b/lib/loclass/optimized_ikeys.h @@ -0,0 +1,66 @@ +//----------------------------------------------------------------------------- +// Borrowed initially from https://github.com/holiman/loclass +// More recently from https://github.com/RfidResearchGroup/proxmark3 +// Copyright (C) 2014 Martin Holst Swende +// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// See LICENSE.txt for the text of the license. +//----------------------------------------------------------------------------- +// WARNING +// +// THIS CODE IS CREATED FOR EXPERIMENTATION AND EDUCATIONAL USE ONLY. +// +// USAGE OF THIS CODE IN OTHER WAYS MAY INFRINGE UPON THE INTELLECTUAL +// PROPERTY OF OTHER PARTIES, SUCH AS INSIDE SECURE AND HID GLOBAL, +// AND MAY EXPOSE YOU TO AN INFRINGEMENT ACTION FROM THOSE PARTIES. +// +// THIS CODE SHOULD NEVER BE USED TO INFRINGE PATENTS OR INTELLECTUAL PROPERTY RIGHTS. +//----------------------------------------------------------------------------- +// It is a reconstruction of the cipher engine used in iClass, and RFID techology. +// +// The implementation is based on the work performed by +// Flavio D. Garcia, Gerhard de Koning Gans, Roel Verdult and +// Milosch Meriac in the paper "Dismantling IClass". +//----------------------------------------------------------------------------- +#ifndef IKEYS_H +#define IKEYS_H + +#include + +/** + * @brief + *Definition 11. Let the function loclass_hash0 : F 82 Γ— F 82 Γ— (F 62 ) 8 β†’ (F 82 ) 8 be defined as + * loclass_hash0(x, y, z [0] . . . z [7] ) = k [0] . . . k [7] where + * z'[i] = (z[i] mod (63-i)) + i i = 0...3 + * z'[i+4] = (z[i+4] mod (64-i)) + i i = 0...3 + * αΊ‘ = check(z'); + * @param c + * @param k this is where the diversified key is put (should be 8 bytes) + * @return + */ +void loclass_hash0(uint64_t c, uint8_t k[8]); +/** + * @brief Performs Elite-class key diversification + * @param csn + * @param key + * @param div_key + */ + +void loclass_diversifyKey(uint8_t *csn, const uint8_t *key, uint8_t *div_key); +/** + * @brief Permutes a key from standard NIST format to Iclass specific format + * @param key + * @param dest + */ + +#endif // IKEYS_H diff --git a/lib/mbedtls b/lib/mbedtls new file mode 160000 index 00000000000..d65aeb37349 --- /dev/null +++ b/lib/mbedtls @@ -0,0 +1 @@ +Subproject commit d65aeb37349ad1a50e0f6c9b694d4b5290d60e49 diff --git a/lib/mbedtls.scons b/lib/mbedtls.scons new file mode 100644 index 00000000000..35de7e6fc75 --- /dev/null +++ b/lib/mbedtls.scons @@ -0,0 +1,20 @@ +Import("env") + +env.Append( + CPPPATH=[ + "#/lib/mbedtls", + "#/lib/mbedtls/include", + ], + CPPDEFINES=[ + ], +) + + +libenv = env.Clone(FW_LIB_NAME="mbedtls") +libenv.ApplyLibFlags() + +sources = ["mbedtls/library/des.c", "mbedtls/library/platform_util.c"] + +lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources) +libenv.Install("${LIB_DIST_DIR}", lib) +Return("lib") diff --git a/lib/nfc_protocols/mifare_classic.c b/lib/nfc_protocols/mifare_classic.c index b44f77cb8d8..21d470bcabf 100644 --- a/lib/nfc_protocols/mifare_classic.c +++ b/lib/nfc_protocols/mifare_classic.c @@ -198,7 +198,7 @@ static bool mf_classic_is_allowed_access( bool mf_classic_check_card_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK) { UNUSED(ATQA1); - if((ATQA0 == 0x44 || ATQA0 == 0x04) && (SAK == 0x08)) { + if((ATQA0 == 0x44 || ATQA0 == 0x04) && (SAK == 0x08 || SAK == 0x88 || SAK == 0x09)) { return true; } else if((ATQA0 == 0x42 || ATQA0 == 0x02) && (SAK == 0x18)) { return true; @@ -219,7 +219,7 @@ bool mf_classic_get_type( furi_assert(reader); memset(reader, 0, sizeof(MfClassicReader)); - if((ATQA0 == 0x44 || ATQA0 == 0x04) && (SAK == 0x08)) { + if((ATQA0 == 0x44 || ATQA0 == 0x04) && (SAK == 0x08 || SAK == 0x88 || SAK == 0x09)) { reader->type = MfClassicType1k; } else if((ATQA0 == 0x42 || ATQA0 == 0x02) && (SAK == 0x18)) { reader->type = MfClassicType4k; @@ -386,11 +386,25 @@ bool mf_classic_read_block( tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRaw; if(furi_hal_nfc_tx_rx(tx_rx, 50)) { - if(tx_rx->rx_bits == 8 * 18) { - for(uint8_t i = 0; i < 18; i++) { - block->value[i] = crypto1_byte(crypto, 0, 0) ^ tx_rx->rx_data[i]; + if(tx_rx->rx_bits == 8 * (MF_CLASSIC_BLOCK_SIZE + 2)) { + uint8_t block_received[MF_CLASSIC_BLOCK_SIZE + 2]; + for(uint8_t i = 0; i < MF_CLASSIC_BLOCK_SIZE + 2; i++) { + block_received[i] = crypto1_byte(crypto, 0, 0) ^ tx_rx->rx_data[i]; + } + uint16_t crc_calc = nfca_get_crc16(block_received, MF_CLASSIC_BLOCK_SIZE); + uint16_t crc_received = (block_received[MF_CLASSIC_BLOCK_SIZE + 1] << 8) | + block_received[MF_CLASSIC_BLOCK_SIZE]; + if(crc_received != crc_calc) { + FURI_LOG_E( + TAG, + "Incorrect CRC while reading block %d. Expected %04X, Received %04X", + block_num, + crc_received, + crc_calc); + } else { + memcpy(block->value, block_received, MF_CLASSIC_BLOCK_SIZE); + read_block_success = true; } - read_block_success = true; } } return read_block_success; @@ -537,25 +551,23 @@ bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_ // Read command while(!command_processed) { - if(!is_encrypted) { - // Read first frame - tx_rx->tx_bits = 0; - tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; - } - if(!furi_hal_nfc_tx_rx(tx_rx, 300)) { - FURI_LOG_D( - TAG, "Error in tx rx. Tx :%d bits, Rx: %d bits", tx_rx->tx_bits, tx_rx->rx_bits); - break; - } if(!is_encrypted) { memcpy(plain_data, tx_rx->rx_data, tx_rx->rx_bits / 8); } else { + if(!furi_hal_nfc_tx_rx(tx_rx, 300)) { + FURI_LOG_D( + TAG, + "Error in tx rx. Tx :%d bits, Rx: %d bits", + tx_rx->tx_bits, + tx_rx->rx_bits); + break; + } mf_crypto1_decrypt(&emulator->crypto, tx_rx->rx_data, tx_rx->rx_bits, plain_data); } - // TODO Check crc - if(plain_data[0] == 0x50 && plain_data[1] == 00) { + if(plain_data[0] == 0x50 && plain_data[1] == 0x00) { FURI_LOG_T(TAG, "Halt received"); + furi_hal_nfc_listen_sleep(); command_processed = true; break; } else if(plain_data[0] == 0x60 || plain_data[0] == 0x61) { @@ -564,11 +576,11 @@ bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_ uint8_t sector_trailer_block = mf_classic_get_sector_trailer(block); MfClassicSectorTrailer* sector_trailer = (MfClassicSectorTrailer*)emulator->data.block[sector_trailer_block].value; - if(plain_data[0] == 0x61) { - key = nfc_util_bytes2num(sector_trailer->key_b, 6); + if(plain_data[0] == 0x60) { + key = nfc_util_bytes2num(sector_trailer->key_a, 6); access_key = MfClassicKeyA; } else { - key = nfc_util_bytes2num(sector_trailer->key_a, 6); + key = nfc_util_bytes2num(sector_trailer->key_b, 6); access_key = MfClassicKeyB; } @@ -581,8 +593,12 @@ bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_ if(!is_encrypted) { crypto1_word(&emulator->crypto, emulator->cuid ^ nonce, 0); memcpy(tx_rx->tx_data, nt, sizeof(nt)); + tx_rx->tx_parity[0] = 0; + for(size_t i = 0; i < sizeof(nt); i++) { + tx_rx->tx_parity[0] |= nfc_util_odd_parity8(nt[i]) << (7 - i); + } tx_rx->tx_bits = sizeof(nt) * 8; - tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRxRaw; + tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; } else { mf_crypto1_encrypt( &emulator->crypto, diff --git a/lib/nfc_protocols/mifare_common.c b/lib/nfc_protocols/mifare_common.c index 0be24b512c0..fd622765ed6 100644 --- a/lib/nfc_protocols/mifare_common.c +++ b/lib/nfc_protocols/mifare_common.c @@ -6,7 +6,7 @@ MifareType mifare_common_get_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK) { if((ATQA0 == 0x44) && (ATQA1 == 0x00) && (SAK == 0x00)) { type = MifareTypeUltralight; } else if( - ((ATQA0 == 0x44 || ATQA0 == 0x04) && (SAK == 0x08)) || + ((ATQA0 == 0x44 || ATQA0 == 0x04) && (SAK == 0x08 || SAK == 0x88 || SAK == 0x09)) || ((ATQA0 == 0x42 || ATQA0 == 0x02) && (SAK == 0x18))) { type = MifareTypeClassic; } else if(ATQA0 == 0x44 && ATQA1 == 0x03 && SAK == 0x20) { diff --git a/lib/nfc_protocols/nfca.c b/lib/nfc_protocols/nfca.c index e1cbdafe1e6..c401f8cc587 100755 --- a/lib/nfc_protocols/nfca.c +++ b/lib/nfc_protocols/nfca.c @@ -8,7 +8,10 @@ #define NFCA_CRC_INIT (0x6363) #define NFCA_F_SIG (13560000.0) -#define NFCA_T_SIG (1.0 / NFCA_F_SIG) +#define T_SIG 7374 //73.746ns*100 +#define T_SIG_x8 58992 //T_SIG*8 +#define T_SIG_x8_x8 471936 //T_SIG*8*8 +#define T_SIG_x8_x9 530928 //T_SIG*8*9 #define NFCA_SIGNAL_MAX_EDGES (1350) @@ -64,15 +67,15 @@ static void nfca_add_bit(DigitalSignal* signal, bool bit) { if(bit) { signal->start_level = true; for(size_t i = 0; i < 7; i++) { - signal->edge_timings[i] = 8 * NFCA_T_SIG; + signal->edge_timings[i] = T_SIG_x8; } - signal->edge_timings[7] = 9 * 8 * NFCA_T_SIG; + signal->edge_timings[7] = T_SIG_x8_x9; signal->edge_cnt = 8; } else { signal->start_level = false; - signal->edge_timings[0] = 8 * 8 * NFCA_T_SIG; + signal->edge_timings[0] = T_SIG_x8_x8; for(size_t i = 1; i < 9; i++) { - signal->edge_timings[i] = 8 * NFCA_T_SIG; + signal->edge_timings[i] = T_SIG_x8; } signal->edge_cnt = 9; } diff --git a/lib/subghz/subghz_tx_rx_worker.c b/lib/subghz/subghz_tx_rx_worker.c index 863aaa31e1f..0f687f15380 100644 --- a/lib/subghz/subghz_tx_rx_worker.c +++ b/lib/subghz/subghz_tx_rx_worker.c @@ -237,13 +237,13 @@ bool subghz_tx_rx_worker_start(SubGhzTxRxWorker* instance, uint32_t frequency) { instance->worker_running = true; - furi_thread_start(instance->thread); - if(furi_hal_subghz_is_tx_allowed(frequency)) { instance->frequency = frequency; res = true; } + furi_thread_start(instance->thread); + return res; } diff --git a/lib/update_util/update_operation.c b/lib/update_util/update_operation.c index 142dcaaaabc..9082d262544 100644 --- a/lib/update_util/update_operation.c +++ b/lib/update_util/update_operation.c @@ -12,6 +12,8 @@ #define UPDATE_ROOT_DIR "/ext" UPDATE_DIR_DEFAULT_REL_PATH #define UPDATE_PREFIX "/ext" UPDATE_DIR_DEFAULT_REL_PATH "/" #define UPDATE_SUFFIX "/" UPDATE_MANIFEST_DEFAULT_NAME +/* Need at least 4 free LFS pages before update */ +#define UPDATE_MIN_INT_FREE_SPACE 4 * 4 * 1024 static const char* update_prepare_result_descr[] = { [UpdatePrepareResultOK] = "OK", @@ -22,6 +24,7 @@ static const char* update_prepare_result_descr[] = { [UpdatePrepareResultStageIntegrityError] = "Corrupted Stage2 loader", [UpdatePrepareResultManifestPointerError] = "Failed to create update pointer file", [UpdatePrepareResultOutdatedManifestVersion] = "Update package is too old", + [UpdatePrepareResultIntFull] = "Need more free space in internal storage", }; const char* update_operation_describe_preparation_result(const UpdatePrepareResult value) { @@ -133,15 +136,22 @@ static bool update_operation_persist_manifest_path(Storage* storage, const char* } UpdatePrepareResult update_operation_prepare(const char* manifest_file_path) { - UpdatePrepareResult result = UpdatePrepareResultManifestFolderNotFound; + UpdatePrepareResult result = UpdatePrepareResultIntFull; Storage* storage = furi_record_open("storage"); UpdateManifest* manifest = update_manifest_alloc(); File* file = storage_file_alloc(storage); + uint64_t free_int_space; string_t stage_path; string_init(stage_path); do { + if((storage_common_fs_info(storage, "/int", NULL, &free_int_space) != FSE_OK) || + (free_int_space < UPDATE_MIN_INT_FREE_SPACE)) { + break; + } + if(storage_common_stat(storage, manifest_file_path, NULL) != FSE_OK) { + result = UpdatePrepareResultManifestFolderNotFound; break; } diff --git a/lib/update_util/update_operation.h b/lib/update_util/update_operation.h index 39d58028354..056520e1147 100644 --- a/lib/update_util/update_operation.h +++ b/lib/update_util/update_operation.h @@ -32,6 +32,8 @@ typedef enum { UpdatePrepareResultManifestPointerError, UpdatePrepareResultTargetMismatch, UpdatePrepareResultOutdatedManifestVersion, + UpdatePrepareResultIntFull, + UpdatePrepareResultUnspecifiedError, } UpdatePrepareResult; const char* update_operation_describe_preparation_result(const UpdatePrepareResult value); diff --git a/scripts/flipper/app.py b/scripts/flipper/app.py index a75a8a9e84d..9583560212c 100644 --- a/scripts/flipper/app.py +++ b/scripts/flipper/app.py @@ -1,7 +1,6 @@ import logging import argparse import sys -import os class App: diff --git a/scripts/flipper/utils/cdc.py b/scripts/flipper/utils/cdc.py new file mode 100644 index 00000000000..081705cc257 --- /dev/null +++ b/scripts/flipper/utils/cdc.py @@ -0,0 +1,17 @@ +import serial.tools.list_ports as list_ports + +# Returns a valid port or None, if it cannot be found +def resolve_port(logger, portname: str = "auto"): + if portname != "auto": + return portname + # Try guessing + flippers = list(list_ports.grep("flip")) + if len(flippers) == 1: + flipper = flippers[0] + logger.info(f"Using {flipper.serial_number} on {flipper.device}") + return flipper.device + elif len(flippers) == 0: + logger.error("Failed to find connected Flipper") + elif len(flippers) > 1: + logger.error("More than one Flipper is attached") + logger.error("Failed to guess which port to use. Specify --port") diff --git a/scripts/selfupdate.py b/scripts/selfupdate.py index 538ecdb98b1..6d70574744d 100644 --- a/scripts/selfupdate.py +++ b/scripts/selfupdate.py @@ -1,20 +1,18 @@ #!/usr/bin/env python3 +from typing import final +from flipper.app import App from flipper.storage import FlipperStorage +from flipper.utils.cdc import resolve_port import logging -import argparse import os -import sys import pathlib import serial.tools.list_ports as list_ports -class Main: - def __init__(self): - # command args - self.parser = argparse.ArgumentParser() - self.parser.add_argument("-d", "--debug", action="store_true", help="Debug") +class Main(App): + def init(self): self.parser.add_argument("-p", "--port", help="CDC Port", default="auto") self.parser.add_argument( "-b", @@ -25,35 +23,15 @@ def __init__(self): type=int, ) - self.subparsers = self.parser.add_subparsers(help="sub-command help") - - self.parser_install = self.subparsers.add_parser( - "install", help="Install OTA package" - ) - self.parser_install.add_argument("manifest_path", help="Manifest path") - self.parser_install.add_argument( + self.parser.add_argument("manifest_path", help="Manifest path") + self.parser.add_argument( "--pkg_dir_name", help="Update dir name", default="pcbundle", required=False ) - self.parser_install.set_defaults(func=self.install) + self.parser.set_defaults(func=self.install) # logging self.logger = logging.getLogger() - def __call__(self): - self.args = self.parser.parse_args() - if "func" not in self.args: - self.parser.error("Choose something to do") - # configure log output - self.log_level = logging.DEBUG if self.args.debug else logging.INFO - self.logger.setLevel(self.log_level) - self.handler = logging.StreamHandler(sys.stdout) - self.handler.setLevel(self.log_level) - self.formatter = logging.Formatter("%(asctime)s [%(levelname)s] %(message)s") - self.handler.setFormatter(self.formatter) - self.logger.addHandler(self.handler) - # execute requested function - self.args.func() - # make directory with exist check def mkdir_on_storage(self, storage, flipper_dir_path): if not storage.exist_dir(flipper_dir_path): @@ -82,61 +60,60 @@ def send_file_to_storage(self, storage, flipper_file_path, local_file_path, forc return False return True - def _get_port(self): - if self.args.port != "auto": - return self.args.port - # Try guessing - flippers = list(list_ports.grep("flip")) - if len(flippers) == 1: - flipper = flippers[0] - self.logger.info(f"Using {flipper.serial_number} on {flipper.device}") - return flipper.device - elif len(flippers) == 0: - self.logger.error("Failed to find connected Flipper") - elif len(flippers) > 1: - self.logger.error("More than one Flipper is attached") - self.logger.error("Failed to guess which port to use. Specify --port") - def install(self): - if not (port := self._get_port()): + if not (port := resolve_port(self.logger, self.args.port)): return 1 storage = FlipperStorage(port, self.args.baud) storage.start() - if not os.path.isfile(self.args.manifest_path): - self.logger.error("Error: manifest not found") - return 2 - - manifest_path = pathlib.Path(os.path.abspath(self.args.manifest_path)) - manifest_name, pkg_name = manifest_path.parts[-1], manifest_path.parts[-2] - - pkg_dir_name = self.args.pkg_dir_name or pkg_name - flipper_update_path = f"/ext/update/{pkg_dir_name}" - - self.logger.info(f'Installing "{pkg_name}" from {flipper_update_path}') - # if not os.path.exists(self.args.manifest_path): - # self.logger.error("Error: package not found") - if not self.mkdir_on_storage(storage, flipper_update_path): - self.logger.error(f"Error: cannot create {storage.last_error}") - return -2 - - for dirpath, dirnames, filenames in os.walk(manifest_path.parents[0]): - for fname in filenames: - self.logger.debug(f"Uploading {fname}") - local_file_path = os.path.join(dirpath, fname) - flipper_file_path = f"{flipper_update_path}/{fname}" - if not self.send_file_to_storage( - storage, flipper_file_path, local_file_path, False - ): - self.logger.error(f"Error: {storage.last_error}") - return -3 - - storage.send_and_wait_eol( - f"update install {flipper_update_path}/{manifest_name}\r" - ) - break - storage.stop() + try: + if not os.path.isfile(self.args.manifest_path): + self.logger.error("Error: manifest not found") + return 2 + + manifest_path = pathlib.Path(os.path.abspath(self.args.manifest_path)) + manifest_name, pkg_name = manifest_path.parts[-1], manifest_path.parts[-2] + + pkg_dir_name = self.args.pkg_dir_name or pkg_name + update_root = "/ext/update" + flipper_update_path = f"{update_root}/{pkg_dir_name}" + + self.logger.info(f'Installing "{pkg_name}" from {flipper_update_path}') + # if not os.path.exists(self.args.manifest_path): + # self.logger.error("Error: package not found") + if not self.mkdir_on_storage( + storage, update_root + ) or not self.mkdir_on_storage(storage, flipper_update_path): + self.logger.error(f"Error: cannot create {storage.last_error}") + return -2 + + for dirpath, dirnames, filenames in os.walk(manifest_path.parents[0]): + for fname in filenames: + self.logger.debug(f"Uploading {fname}") + local_file_path = os.path.join(dirpath, fname) + flipper_file_path = f"{flipper_update_path}/{fname}" + if not self.send_file_to_storage( + storage, flipper_file_path, local_file_path, False + ): + self.logger.error(f"Error: {storage.last_error}") + return -3 + + storage.send_and_wait_eol( + f"update install {flipper_update_path}/{manifest_name}\r" + ) + result = storage.read.until(storage.CLI_EOL) + if not b"Verifying" in result: + self.logger.error(f"Unexpected response: {result.decode('ascii')}") + return -4 + result = storage.read.until(storage.CLI_EOL) + if not result.startswith(b"OK"): + self.logger.error(result.decode("ascii")) + return -5 + break + return 0 + finally: + storage.stop() if __name__ == "__main__": diff --git a/scripts/storage.py b/scripts/storage.py index 1281253bc43..0ddc2fc0c99 100755 --- a/scripts/storage.py +++ b/scripts/storage.py @@ -1,23 +1,19 @@ #!/usr/bin/env python3 +from flipper.app import App from flipper.storage import FlipperStorage +from flipper.utils.cdc import resolve_port import logging -import argparse import os -import sys import binascii -import posixpath import filecmp import tempfile -class Main: - def __init__(self): - # command args - self.parser = argparse.ArgumentParser() - self.parser.add_argument("-d", "--debug", action="store_true", help="Debug") - self.parser.add_argument("-p", "--port", help="CDC Port", required=True) +class Main(App): + def init(self): + self.parser.add_argument("-p", "--port", help="CDC Port", default="auto") self.parser.add_argument( "-b", "--baud", @@ -77,43 +73,37 @@ def __init__(self): ) self.parser_stress.set_defaults(func=self.stress) - # logging - self.logger = logging.getLogger() - - def __call__(self): - self.args = self.parser.parse_args() - if "func" not in self.args: - self.parser.error("Choose something to do") - # configure log output - self.log_level = logging.DEBUG if self.args.debug else logging.INFO - self.logger.setLevel(self.log_level) - self.handler = logging.StreamHandler(sys.stdout) - self.handler.setLevel(self.log_level) - self.formatter = logging.Formatter("%(asctime)s [%(levelname)s] %(message)s") - self.handler.setFormatter(self.formatter) - self.logger.addHandler(self.handler) - # execute requested function - self.args.func() + def _get_storage(self): + if not (port := resolve_port(self.logger, self.args.port)): + return None - def mkdir(self): - storage = FlipperStorage(self.args.port) + storage = FlipperStorage(port, self.args.baud) storage.start() + return storage + + def mkdir(self): + if not (storage := self._get_storage()): + return 1 + self.logger.debug(f'Creating "{self.args.flipper_path}"') if not storage.mkdir(self.args.flipper_path): self.logger.error(f"Error: {storage.last_error}") storage.stop() + return 0 def remove(self): - storage = FlipperStorage(self.args.port) - storage.start() + if not (storage := self._get_storage()): + return 1 + self.logger.debug(f'Removing "{self.args.flipper_path}"') if not storage.remove(self.args.flipper_path): self.logger.error(f"Error: {storage.last_error}") storage.stop() + return 0 def receive(self): - storage = FlipperStorage(self.args.port) - storage.start() + if not (storage := self._get_storage()): + return 1 if storage.exist_dir(self.args.flipper_path): for dirpath, dirnames, filenames in storage.walk(self.args.flipper_path): @@ -155,14 +145,17 @@ def receive(self): if not storage.receive_file(self.args.flipper_path, self.args.local_path): self.logger.error(f"Error: {storage.last_error}") storage.stop() + return 0 def send(self): - storage = FlipperStorage(self.args.port) - storage.start() + if not (storage := self._get_storage()): + return 1 + self.send_to_storage( storage, self.args.flipper_path, self.args.local_path, self.args.force ) storage.stop() + return 0 # send file or folder recursively def send_to_storage(self, storage, flipper_path, local_path, force): @@ -250,8 +243,9 @@ def send_file_to_storage(self, storage, flipper_file_path, local_file_path, forc self.logger.error(f"Error: {storage.last_error}") def read(self): - storage = FlipperStorage(self.args.port, self.args.baud) - storage.start() + if not (storage := self._get_storage()): + return 1 + self.logger.debug(f'Reading "{self.args.flipper_path}"') data = storage.read_file(self.args.flipper_path) if not data: @@ -264,10 +258,12 @@ def read(self): print("Binary hexadecimal data:") print(binascii.hexlify(data).decode()) storage.stop() + return 0 def size(self): - storage = FlipperStorage(self.args.port) - storage.start() + if not (storage := self._get_storage()): + return 1 + self.logger.debug(f'Getting size of "{self.args.flipper_path}"') size = storage.size(self.args.flipper_path) if size < 0: @@ -275,13 +271,16 @@ def size(self): else: print(size) storage.stop() + return 0 def list(self): - storage = FlipperStorage(self.args.port) - storage.start() + if not (storage := self._get_storage()): + return 1 + self.logger.debug(f'Listing "{self.args.flipper_path}"') storage.list_tree(self.args.flipper_path) storage.stop() + return 0 def stress(self): self.logger.error("This test is wearing out flash memory.") @@ -293,18 +292,21 @@ def stress(self): self.logger.error("Stop at this point or device warranty will be void") say = input("Anything to say? ").strip().lower() if say != "void": - return + return 2 say = input("Why, Mr. Anderson? ").strip().lower() if say != "because": - return + return 3 with tempfile.TemporaryDirectory() as tmpdirname: send_file_name = os.path.join(tmpdirname, "send") receive_file_name = os.path.join(tmpdirname, "receive") with open(send_file_name, "w") as fout: fout.write("A" * self.args.file_size) - storage = FlipperStorage(self.args.port) - storage.start() + + storage = self._get_storage() + if not storage: + return 1 + if storage.exist_file(self.args.flipper_path): self.logger.error("File exists, remove it first") return @@ -318,6 +320,7 @@ def stress(self): os.unlink(receive_file_name) self.args.count -= 1 storage.stop() + return 0 if __name__ == "__main__": diff --git a/site_scons/commandline.scons b/site_scons/commandline.scons index 06e836578fc..5a7a0dd2d55 100644 --- a/site_scons/commandline.scons +++ b/site_scons/commandline.scons @@ -162,6 +162,12 @@ vars.Add( default="", ) +vars.Add( + "BLACKMAGIC", + help="Blackmagic probe location", + default="auto", +) + vars.Add( "UPDATE_SPLASH", help="Directory name with slideshow frames to render after installing update package", diff --git a/site_scons/environ.scons b/site_scons/environ.scons index 9e49e8b601d..99d4cc0b562 100644 --- a/site_scons/environ.scons +++ b/site_scons/environ.scons @@ -78,5 +78,6 @@ coreenv["TEMPFILEARGESCFUNC"] = util.tempfile_arg_esc_func util.wrap_tempfile(coreenv, "LINKCOM") util.wrap_tempfile(coreenv, "ARCOM") +coreenv["SINGLEQUOTEFUNC"] = util.single_quote Return("coreenv") diff --git a/site_scons/fbt/util.py b/site_scons/fbt/util.py index 9de24a9aa52..a6bc4c063b8 100644 --- a/site_scons/fbt/util.py +++ b/site_scons/fbt/util.py @@ -2,7 +2,9 @@ from SCons.Subst import quote_spaces import re - +import os +import random +import string WINPATHSEP_RE = re.compile(r"\\([^\"'\\]|$)") @@ -17,3 +19,46 @@ def tempfile_arg_esc_func(arg): def wrap_tempfile(env, command): env[command] = '${TEMPFILE("' + env[command] + '","$' + command + 'STR")}' + + +def link_dir(target_path, source_path, is_windows): + # print(f"link_dir: {target_path} -> {source_path}") + if os.path.lexists(target_path) or os.path.exists(target_path): + os.unlink(target_path) + if is_windows: + # Crete junction + import _winapi + + if not os.path.isdir(source_path): + raise Exception(f"Source directory {source_path} is not a directory") + + if not os.path.exists(target_path): + _winapi.CreateJunction(source_path, target_path) + else: + os.symlink(source_path, target_path) + + +def single_quote(arg_list): + return " ".join(f"'{arg}'" if " " in arg else str(arg) for arg in arg_list) + + +def link_elf_dir_as_latest(env, elf_node): + elf_dir = elf_node.Dir(".") + latest_dir = env.Dir("#build/latest") + print(f"Setting {elf_dir} as latest built dir (./build/latest/)") + return link_dir(latest_dir.abspath, elf_dir.abspath, env["PLATFORM"] == "win32") + + +def should_gen_cdb_and_link_dir(env, requested_targets): + explicitly_building_updater = False + # Hacky way to check if updater-related targets were requested + for build_target in requested_targets: + if "updater" in str(build_target): + explicitly_building_updater = True + + is_updater = not env["IS_BASE_FIRMWARE"] + # If updater is explicitly requested, link to the latest updater + # Otherwise, link to firmware + return (is_updater and explicitly_building_updater) or ( + not is_updater and not explicitly_building_updater + ) diff --git a/site_scons/site_tools/blackmagic.py b/site_scons/site_tools/blackmagic.py new file mode 100644 index 00000000000..ec48c15fd08 --- /dev/null +++ b/site_scons/site_tools/blackmagic.py @@ -0,0 +1,74 @@ +from SCons.Errors import StopError + + +class BlackmagicResolver: + BLACKMAGIC_HOSTNAME = "blackmagic.local" + + def __init__(self, env): + self.env = env + + # On Win: + # 'location': '1-5:x.0', 'name': 'COM4', + # 'location': '1-5:x.2', 'name': 'COM13', + # On Linux: + # 'location': '1-1.2:1.0', 'name': 'ttyACM0', + # 'location': '1-1.2:1.2', 'name': 'ttyACM1', + # On MacOS: + # 'location': '0-1.3', 'name': 'cu.usbmodemblackmagic1', + # 'location': '0-1.3', 'name': 'cu.usbmodemblackmagic3', + def _find_probe(self): + import serial.tools.list_ports as list_ports + + ports = list(list_ports.grep("blackmagic")) + if len(ports) == 0: + # Blackmagic probe serial port not found, will be handled later + pass + elif len(ports) > 2: + raise StopError("More than one Blackmagic probe found") + else: + # If you're getting any issues with auto lookup, uncomment this + # print("\n".join([f"{p.device} {vars(p)}" for p in ports])) + return sorted(ports, key=lambda p: f"{p.location}_{p.name}")[0] + + # Look up blackmagic probe hostname with dns + def _resolve_hostname(self): + import socket + + try: + return socket.gethostbyname(self.BLACKMAGIC_HOSTNAME) + except socket.gaierror: + print("Failed to resolve Blackmagic hostname") + return None + + def get_serial(self): + if not (probe := self._find_probe()): + return None + # print(f"Found Blackmagic probe on {probe.device}") + if self.env.subst("$PLATFORM") == "win32": + return f"\\\\.\\{probe.device}" + return probe.device + + def get_networked(self): + if not (probe := self._resolve_hostname()): + return None + + return f"tcp:{probe}:2345" + + def __str__(self): + # print("distenv blackmagic", self.env.subst("$BLACKMAGIC")) + if (blackmagic := self.env.subst("$BLACKMAGIC")) != "auto": + return blackmagic + + # print("Looking for Blackmagic...") + if probe := self.get_serial() or self.get_networked(): + return probe + + raise Exception("Please specify BLACKMAGIC=...") + + +def generate(env): + env.SetDefault(BLACKMAGIC_ADDR=BlackmagicResolver(env)) + + +def exists(env): + return True diff --git a/site_scons/site_tools/fbt_apps.py b/site_scons/site_tools/fbt_apps.py index cbeae2d0a62..bdccccf7b5c 100644 --- a/site_scons/site_tools/fbt_apps.py +++ b/site_scons/site_tools/fbt_apps.py @@ -51,7 +51,10 @@ def generate(env): env.Append( BUILDERS={ "ApplicationsC": Builder( - action=Action(build_apps_c, "${APPSCOMSTR}"), + action=Action( + build_apps_c, + "${APPSCOMSTR}", + ), ), } ) diff --git a/site_scons/site_tools/fbt_dist.py b/site_scons/site_tools/fbt_dist.py index 8bfb40684ca..15a653a6097 100644 --- a/site_scons/site_tools/fbt_dist.py +++ b/site_scons/site_tools/fbt_dist.py @@ -48,44 +48,55 @@ def AddFwProject(env, base_env, fw_type, fw_env_key): project_env["FW_ARTIFACTS"], ], ) + env.Replace(DIST_DIR=get_variant_dirname(env)) return project_env -def AddDebugTarget(env, targetenv, force_flash=True): - pseudo_name = f"debug.{targetenv.subst('$FIRMWARE_BUILD_CFG')}.pseudo" - debug_target = env.GDBPy( - pseudo_name, - targetenv["FW_ELF"], - GDBPYOPTS='-ex "source debug/FreeRTOS/FreeRTOS.py" ' - '-ex "source debug/PyCortexMDebug/PyCortexMDebug.py" ' - '-ex "svd_load ${SVD_FILE}" ' - '-ex "compare-sections"', +def AddOpenOCDFlashTarget(env, targetenv, **kw): + openocd_target = env.OpenOCDFlash( + "#build/oocd-${BUILD_CFG}-flash.flag", + targetenv["FW_BIN"], + OPENOCD_COMMAND=[ + "-c", + "program ${SOURCE.posix} reset exit ${BASE_ADDRESS}", + ], + BUILD_CFG=targetenv.subst("$FIRMWARE_BUILD_CFG"), + BASE_ADDRESS=targetenv.subst("$IMAGE_BASE_ADDRESS"), + **kw, + ) + env.Alias(targetenv.subst("${FIRMWARE_BUILD_CFG}_flash"), openocd_target) + return openocd_target + + +def DistCommand(env, name, source, **kw): + target = f"dist_{name}" + command = env.Command( + target, + source, + '@${PYTHON3} ${ROOT_DIR.abspath}/scripts/sconsdist.py copy -p ${DIST_PROJECTS} -s "${DIST_SUFFIX}" ${DIST_EXTRA}', + **kw, ) - if force_flash: - env.Depends(debug_target, targetenv["FW_FLASH"]) - env.Pseudo(pseudo_name) - env.AlwaysBuild(debug_target) - return debug_target + env.Pseudo(target) + env.Alias(name, command) + return command def generate(env): env.AddMethod(AddFwProject) - env.AddMethod(AddDebugTarget) + env.AddMethod(DistCommand) + env.AddMethod(AddOpenOCDFlashTarget) + env.SetDefault( COPRO_MCU_FAMILY="STM32WB5x", ) + env.Append( BUILDERS={ - "DistBuilder": Builder( - action=Action( - '@${PYTHON3} ${ROOT_DIR.abspath}/scripts/sconsdist.py copy -p ${DIST_PROJECTS} -s "${DIST_SUFFIX}" ${DIST_EXTRA}', - ), - ), "UsbInstall": Builder( action=[ Action( - "${PYTHON3} ${ROOT_DIR.abspath}/scripts/selfupdate.py install dist/${DIST_DIR}/f${TARGET_HW}-update-${DIST_SUFFIX}/update.fuf" + "${PYTHON3} ${ROOT_DIR.abspath}/scripts/selfupdate.py dist/${DIST_DIR}/f${TARGET_HW}-update-${DIST_SUFFIX}/update.fuf" ), Touch("${TARGET}"), ] diff --git a/site_scons/site_tools/gdb.py b/site_scons/site_tools/gdb.py index e7b6bdd6875..94ea9fbe9be 100644 --- a/site_scons/site_tools/gdb.py +++ b/site_scons/site_tools/gdb.py @@ -11,22 +11,6 @@ def generate(env): GDBCOM="$GDB $GDBOPTS $SOURCES", # no $TARGET GDBPYCOM="$GDBPY $GDBOPTS $GDBPYOPTS $SOURCES", # no $TARGET ) - env.Append( - BUILDERS={ - "GDB": Builder( - action=Action( - "${GDBCOM}", - "${GDBCOMSTR}", - ), - ), - "GDBPy": Builder( - action=Action( - "${GDBPYCOM}", - "${GDBPYCOMSTR}", - ), - ), - } - ) def exists(env): diff --git a/site_scons/site_tools/openocd.py b/site_scons/site_tools/openocd.py index 135e1100a9e..dcf0bf925aa 100644 --- a/site_scons/site_tools/openocd.py +++ b/site_scons/site_tools/openocd.py @@ -7,7 +7,7 @@ _oocd_action = Action( "${OPENOCD} ${OPENOCD_OPTS} ${OPENOCD_COMMAND}", - "${OOCDCOMSTR}", + "${OPENOCDCOMSTR}", ) @@ -16,12 +16,13 @@ def generate(env): OPENOCD=__OPENOCD_BIN, OPENOCD_OPTS="", OPENOCD_COMMAND="", - OOCDCOMSTR="", + OPENOCDCOM="${OPENOCD} ${OPENOCD_OPTS} ${OPENOCD_COMMAND}", + OPENOCDCOMSTR="", ) env.Append( BUILDERS={ - "OOCDFlashCommand": Builder( + "OpenOCDFlash": Builder( action=[ _oocd_action, Touch("${TARGET}"), @@ -29,9 +30,6 @@ def generate(env): suffix=".flash", src_suffix=".bin", ), - "OOCDCommand": Builder( - action=_oocd_action, - ), } ) diff --git a/site_scons/site_tools/sconsmodular.py b/site_scons/site_tools/sconsmodular.py index eeb900229c9..db0cb8f350b 100644 --- a/site_scons/site_tools/sconsmodular.py +++ b/site_scons/site_tools/sconsmodular.py @@ -29,9 +29,20 @@ def BuildModules(env, modules): return result +def PhonyTarget(env, name, action, source=None, **kw): + if not source: + source = [] + phony_name = "phony_" + name + env.Pseudo(phony_name) + command = env.Command(phony_name, source, action, **kw) + env.AlwaysBuild(env.Alias(name, command)) + return command + + def generate(env): env.AddMethod(BuildModule) env.AddMethod(BuildModules) + env.AddMethod(PhonyTarget) def exists(env):