Merge branch 'release-candidate' into release
This commit is contained in:
commit
3e5d499b44
7
.github/workflows/build.yml
vendored
7
.github/workflows/build.yml
vendored
@ -144,7 +144,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
issue-number: ${{ github.event.pull_request.number }}
|
issue-number: ${{ github.event.pull_request.number }}
|
||||||
comment-author: 'github-actions[bot]'
|
comment-author: 'github-actions[bot]'
|
||||||
body-includes: 'Install with web updater'
|
body-includes: 'Compiled firmware for commit'
|
||||||
|
|
||||||
- name: 'Create or update comment'
|
- name: 'Create or update comment'
|
||||||
if: ${{ !github.event.pull_request.head.repo.fork && github.event.pull_request}}
|
if: ${{ !github.event.pull_request.head.repo.fork && github.event.pull_request}}
|
||||||
@ -153,7 +153,10 @@ jobs:
|
|||||||
comment-id: ${{ steps.fc.outputs.comment-id }}
|
comment-id: ${{ steps.fc.outputs.comment-id }}
|
||||||
issue-number: ${{ github.event.pull_request.number }}
|
issue-number: ${{ github.event.pull_request.number }}
|
||||||
body: |
|
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
|
edit-mode: replace
|
||||||
|
|
||||||
compact:
|
compact:
|
||||||
|
|||||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -25,3 +25,6 @@
|
|||||||
[submodule "lib/scons"]
|
[submodule "lib/scons"]
|
||||||
path = lib/scons
|
path = lib/scons
|
||||||
url = https://github.com/SCons/scons.git
|
url = https://github.com/SCons/scons.git
|
||||||
|
[submodule "lib/mbedtls"]
|
||||||
|
path = lib/mbedtls
|
||||||
|
url = https://github.com/Mbed-TLS/mbedtls.git
|
||||||
|
|||||||
@ -27,9 +27,15 @@ They both must be flashed in order described.
|
|||||||
|
|
||||||
## With offline update package
|
## With offline update package
|
||||||
|
|
||||||
|
With Flipper attached over USB:
|
||||||
|
|
||||||
|
`./fbt --with-updater flash_usb`
|
||||||
|
|
||||||
|
Just building the package:
|
||||||
|
|
||||||
`./fbt --with-updater updater_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
|
## With STLink
|
||||||
|
|
||||||
|
|||||||
181
SConstruct
181
SConstruct
@ -17,7 +17,6 @@ fbt_variables = SConscript("site_scons/commandline.scons")
|
|||||||
cmd_environment = Environment(tools=[], variables=fbt_variables)
|
cmd_environment = Environment(tools=[], variables=fbt_variables)
|
||||||
Help(fbt_variables.GenerateHelpText(cmd_environment))
|
Help(fbt_variables.GenerateHelpText(cmd_environment))
|
||||||
|
|
||||||
|
|
||||||
# Building basic environment - tools, utility methods, cross-compilation
|
# Building basic environment - tools, utility methods, cross-compilation
|
||||||
# settings, gcc flags for Cortex-M4, basic builders and more
|
# settings, gcc flags for Cortex-M4, basic builders and more
|
||||||
coreenv = SConscript(
|
coreenv = SConscript(
|
||||||
@ -29,24 +28,49 @@ SConscript("site_scons/cc.scons", exports={"ENV": coreenv})
|
|||||||
# Store root dir in environment for certain tools
|
# Store root dir in environment for certain tools
|
||||||
coreenv["ROOT_DIR"] = Dir(".")
|
coreenv["ROOT_DIR"] = Dir(".")
|
||||||
|
|
||||||
|
|
||||||
# Create a separate "dist" environment and add construction envs to it
|
# Create a separate "dist" environment and add construction envs to it
|
||||||
distenv = coreenv.Clone(
|
distenv = coreenv.Clone(
|
||||||
tools=["fbt_dist", "openocd"],
|
tools=["fbt_dist", "openocd", "blackmagic"],
|
||||||
GDBOPTS="-ex 'target extended-remote | ${OPENOCD} -c \"gdb_port pipe\" ${OPENOCD_OPTS}' "
|
OPENOCD_GDB_PIPE=["|openocd -c 'gdb_port pipe' ${[SINGLEQUOTEFUNC(OPENOCD_OPTS)]}"],
|
||||||
'-ex "set confirm off" ',
|
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,
|
ENV=os.environ,
|
||||||
)
|
)
|
||||||
|
|
||||||
firmware_out = distenv.AddFwProject(
|
firmware_env = distenv.AddFwProject(
|
||||||
base_env=coreenv,
|
base_env=coreenv,
|
||||||
fw_type="firmware",
|
fw_type="firmware",
|
||||||
fw_env_key="FW_ENV",
|
fw_env_key="FW_ENV",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# If enabled, initialize updater-related targets
|
# If enabled, initialize updater-related targets
|
||||||
if GetOption("fullenv"):
|
if GetOption("fullenv"):
|
||||||
updater_out = distenv.AddFwProject(
|
updater_env = distenv.AddFwProject(
|
||||||
base_env=coreenv,
|
base_env=coreenv,
|
||||||
fw_type="updater",
|
fw_type="updater",
|
||||||
fw_env_key="UPD_ENV",
|
fw_env_key="UPD_ENV",
|
||||||
@ -71,91 +95,120 @@ if GetOption("fullenv"):
|
|||||||
"--splash",
|
"--splash",
|
||||||
distenv.subst("assets/slideshow/$UPDATE_SPLASH"),
|
distenv.subst("assets/slideshow/$UPDATE_SPLASH"),
|
||||||
]
|
]
|
||||||
selfupdate_dist = distenv.DistBuilder(
|
|
||||||
"selfupdate.pseudo",
|
selfupdate_dist = distenv.DistCommand(
|
||||||
(distenv["DIST_DEPENDS"], firmware_out["FW_RESOURCES"]),
|
"updater_package",
|
||||||
|
(distenv["DIST_DEPENDS"], firmware_env["FW_RESOURCES"]),
|
||||||
DIST_EXTRA=dist_arguments,
|
DIST_EXTRA=dist_arguments,
|
||||||
)
|
)
|
||||||
distenv.Pseudo("selfupdate.pseudo")
|
|
||||||
AlwaysBuild(selfupdate_dist)
|
|
||||||
Alias("updater_package", selfupdate_dist)
|
|
||||||
|
|
||||||
# Updater debug
|
# Updater debug
|
||||||
debug_updater_elf = distenv.AddDebugTarget(updater_out, False)
|
distenv.PhonyTarget(
|
||||||
Alias("updater_debug", debug_updater_elf)
|
"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
|
# Installation over USB & CLI
|
||||||
usb_update_package = distenv.UsbInstall(
|
usb_update_package = distenv.UsbInstall(
|
||||||
"usbinstall.flag",
|
"#build/usbinstall.flag",
|
||||||
(distenv["DIST_DEPENDS"], firmware_out["FW_RESOURCES"], selfupdate_dist),
|
(
|
||||||
|
distenv["DIST_DEPENDS"],
|
||||||
|
firmware_env["FW_RESOURCES"],
|
||||||
|
selfupdate_dist,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
if distenv["FORCE"]:
|
if distenv["FORCE"]:
|
||||||
AlwaysBuild(usb_update_package)
|
distenv.AlwaysBuild(usb_update_package)
|
||||||
Depends(usb_update_package, selfupdate_dist)
|
distenv.Depends(usb_update_package, selfupdate_dist)
|
||||||
Alias("flash_usb", usb_update_package)
|
distenv.Alias("flash_usb", usb_update_package)
|
||||||
|
|
||||||
|
|
||||||
# Target for copying & renaming binaries to dist folder
|
# Target for copying & renaming binaries to dist folder
|
||||||
basic_dist = distenv.DistBuilder("dist.pseudo", distenv["DIST_DEPENDS"])
|
basic_dist = distenv.DistCommand("fw_dist", distenv["DIST_DEPENDS"])
|
||||||
distenv.Pseudo("dist.pseudo")
|
distenv.Default(basic_dist)
|
||||||
AlwaysBuild(basic_dist)
|
|
||||||
Alias("fw_dist", basic_dist)
|
|
||||||
Default(basic_dist)
|
|
||||||
|
|
||||||
|
|
||||||
# Target for bundling core2 package for qFlipper
|
# Target for bundling core2 package for qFlipper
|
||||||
copro_dist = distenv.CoproBuilder(
|
copro_dist = distenv.CoproBuilder(
|
||||||
Dir("assets/core2_firmware"),
|
distenv.Dir("assets/core2_firmware"),
|
||||||
[],
|
[],
|
||||||
)
|
)
|
||||||
AlwaysBuild(copro_dist)
|
distenv.Alias("copro_dist", copro_dist)
|
||||||
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
|
# Debugging firmware
|
||||||
|
firmware_debug = distenv.PhonyTarget(
|
||||||
|
"debug",
|
||||||
|
"${GDBPYCOM}",
|
||||||
|
source=firmware_env["FW_ELF"],
|
||||||
|
GDBOPTS="${GDBOPTS_BASE}",
|
||||||
|
GDBREMOTE="${OPENOCD_GDB_PIPE}",
|
||||||
|
)
|
||||||
|
distenv.Depends(firmware_debug, firmware_flash)
|
||||||
|
|
||||||
debug_fw_elf = distenv.AddDebugTarget(firmware_out)
|
distenv.PhonyTarget(
|
||||||
Alias("debug", debug_fw_elf)
|
"blackmagic",
|
||||||
|
"${GDBPYCOM}",
|
||||||
|
source=firmware_env["FW_ELF"],
|
||||||
|
GDBOPTS="${GDBOPTS_BASE} ${GDBOPTS_BLACKMAGIC}",
|
||||||
|
GDBREMOTE="${BLACKMAGIC_ADDR}",
|
||||||
|
)
|
||||||
|
|
||||||
# Debug alien elf
|
# Debug alien elf
|
||||||
debug_other = distenv.GDBPy(
|
distenv.PhonyTarget(
|
||||||
"debugother.pseudo",
|
"debug_other",
|
||||||
None,
|
"${GDBPYCOM}",
|
||||||
GDBPYOPTS=
|
GDBPYOPTS='-ex "source debug/PyCortexMDebug/PyCortexMDebug.py" ',
|
||||||
# '-ex "source ${ROOT_DIR.abspath}/debug/FreeRTOS/FreeRTOS.py" '
|
GDBREMOTE="${OPENOCD_GDB_PIPE}",
|
||||||
'-ex "source debug/PyCortexMDebug/PyCortexMDebug.py" ',
|
|
||||||
)
|
)
|
||||||
distenv.Pseudo("debugother.pseudo")
|
|
||||||
AlwaysBuild(debug_other)
|
|
||||||
Alias("debug_other", debug_other)
|
|
||||||
|
|
||||||
|
|
||||||
# Just start OpenOCD
|
# Just start OpenOCD
|
||||||
openocd = distenv.OOCDCommand("openocd.pseudo", [])
|
distenv.PhonyTarget(
|
||||||
distenv.Pseudo("openocd.pseudo")
|
"openocd",
|
||||||
AlwaysBuild(openocd)
|
"${OPENOCDCOM}",
|
||||||
Alias("openocd", openocd)
|
)
|
||||||
|
|
||||||
|
|
||||||
# Linter
|
# Linter
|
||||||
lint_check = distenv.Command(
|
distenv.PhonyTarget(
|
||||||
"lint.check.pseudo",
|
"lint",
|
||||||
[],
|
"${PYTHON3} scripts/lint.py check ${LINT_SOURCES}",
|
||||||
"${PYTHON3} scripts/lint.py check $LINT_SOURCES",
|
LINT_SOURCES=firmware_env["LINT_SOURCES"],
|
||||||
LINT_SOURCES=firmware_out["LINT_SOURCES"],
|
)
|
||||||
|
|
||||||
|
distenv.PhonyTarget(
|
||||||
|
"format",
|
||||||
|
"${PYTHON3} scripts/lint.py format ${LINT_SOURCES}",
|
||||||
|
LINT_SOURCES=firmware_env["LINT_SOURCES"],
|
||||||
)
|
)
|
||||||
distenv.Pseudo("lint.check.pseudo")
|
|
||||||
AlwaysBuild(lint_check)
|
|
||||||
Alias("lint", lint_check)
|
|
||||||
|
|
||||||
|
|
||||||
lint_format = distenv.Command(
|
# Find blackmagic probe
|
||||||
"lint.format.pseudo",
|
|
||||||
[],
|
distenv.PhonyTarget(
|
||||||
"${PYTHON3} scripts/lint.py format $LINT_SOURCES",
|
"get_blackmagic",
|
||||||
LINT_SOURCES=firmware_out["LINT_SOURCES"],
|
"@echo $( ${BLACKMAGIC_ADDR} $)",
|
||||||
)
|
)
|
||||||
distenv.Pseudo("lint.format.pseudo")
|
|
||||||
AlwaysBuild(lint_format)
|
|
||||||
Alias("format", lint_format)
|
|
||||||
|
|||||||
@ -15,8 +15,9 @@ static void
|
|||||||
|
|
||||||
int32_t load_offset = 0;
|
int32_t load_offset = 0;
|
||||||
browser->is_root = is_root;
|
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);
|
archive_switch_tab(browser, browser->last_tab_switch_dir);
|
||||||
} else if(!string_start_with_str_p(browser->path, "/app:")) {
|
} else if(!string_start_with_str_p(browser->path, "/app:")) {
|
||||||
with_view_model(
|
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) {
|
void archive_switch_tab(ArchiveBrowserView* browser, InputKey key) {
|
||||||
furi_assert(browser);
|
furi_assert(browser);
|
||||||
ArchiveTabEnum tab = archive_get_tab(browser);
|
ArchiveTabEnum tab = archive_get_tab(browser);
|
||||||
@ -418,11 +435,15 @@ void archive_switch_tab(ArchiveBrowserView* browser, InputKey key) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ArchiveTabEnum tab = archive_get_tab(browser);
|
tab = archive_get_tab(browser);
|
||||||
bool skip_assets = (strcmp(archive_get_tab_ext(tab), "*") == 0) ? false : true;
|
if(archive_is_dir_exists(browser->path)) {
|
||||||
file_browser_worker_set_config(
|
bool skip_assets = (strcmp(archive_get_tab_ext(tab), "*") == 0) ? false : true;
|
||||||
browser->worker, browser->path, archive_get_tab_ext(tab), skip_assets);
|
file_browser_worker_set_config(
|
||||||
tab_empty = false; // Empty check will be performed later
|
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)) {
|
if((tab_empty) && (tab != ArchiveTabBrowser)) {
|
||||||
|
|||||||
@ -49,6 +49,7 @@ static const DuckyKey ducky_keys[] = {
|
|||||||
{"CTRL-SHIFT", KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_SHIFT},
|
{"CTRL-SHIFT", KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_SHIFT},
|
||||||
{"ALT-SHIFT", KEY_MOD_LEFT_ALT | KEY_MOD_LEFT_SHIFT},
|
{"ALT-SHIFT", KEY_MOD_LEFT_ALT | KEY_MOD_LEFT_SHIFT},
|
||||||
{"ALT-GUI", KEY_MOD_LEFT_ALT | KEY_MOD_LEFT_GUI},
|
{"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},
|
{"CTRL", KEY_MOD_LEFT_CTRL},
|
||||||
{"CONTROL", KEY_MOD_LEFT_CTRL},
|
{"CONTROL", KEY_MOD_LEFT_CTRL},
|
||||||
@ -57,48 +58,48 @@ static const DuckyKey ducky_keys[] = {
|
|||||||
{"GUI", KEY_MOD_LEFT_GUI},
|
{"GUI", KEY_MOD_LEFT_GUI},
|
||||||
{"WINDOWS", KEY_MOD_LEFT_GUI},
|
{"WINDOWS", KEY_MOD_LEFT_GUI},
|
||||||
|
|
||||||
{"DOWNARROW", KEY_DOWN_ARROW},
|
{"DOWNARROW", HID_KEYBOARD_DOWN_ARROW},
|
||||||
{"DOWN", KEY_DOWN_ARROW},
|
{"DOWN", HID_KEYBOARD_DOWN_ARROW},
|
||||||
{"LEFTARROW", KEY_LEFT_ARROW},
|
{"LEFTARROW", HID_KEYBOARD_LEFT_ARROW},
|
||||||
{"LEFT", KEY_LEFT_ARROW},
|
{"LEFT", HID_KEYBOARD_LEFT_ARROW},
|
||||||
{"RIGHTARROW", KEY_RIGHT_ARROW},
|
{"RIGHTARROW", HID_KEYBOARD_RIGHT_ARROW},
|
||||||
{"RIGHT", KEY_RIGHT_ARROW},
|
{"RIGHT", HID_KEYBOARD_RIGHT_ARROW},
|
||||||
{"UPARROW", KEY_UP_ARROW},
|
{"UPARROW", HID_KEYBOARD_UP_ARROW},
|
||||||
{"UP", KEY_UP_ARROW},
|
{"UP", HID_KEYBOARD_UP_ARROW},
|
||||||
|
|
||||||
{"ENTER", KEY_ENTER},
|
{"ENTER", HID_KEYBOARD_RETURN},
|
||||||
{"BREAK", KEY_PAUSE},
|
{"BREAK", HID_KEYBOARD_PAUSE},
|
||||||
{"PAUSE", KEY_PAUSE},
|
{"PAUSE", HID_KEYBOARD_PAUSE},
|
||||||
{"CAPSLOCK", KEY_CAPS_LOCK},
|
{"CAPSLOCK", HID_KEYBOARD_CAPS_LOCK},
|
||||||
{"DELETE", KEY_DELETE},
|
{"DELETE", HID_KEYBOARD_DELETE},
|
||||||
{"BACKSPACE", KEY_BACKSPACE},
|
{"BACKSPACE", HID_KEYPAD_BACKSPACE},
|
||||||
{"END", KEY_END},
|
{"END", HID_KEYBOARD_END},
|
||||||
{"ESC", KEY_ESC},
|
{"ESC", HID_KEYBOARD_ESCAPE},
|
||||||
{"ESCAPE", KEY_ESC},
|
{"ESCAPE", HID_KEYBOARD_ESCAPE},
|
||||||
{"HOME", KEY_HOME},
|
{"HOME", HID_KEYBOARD_HOME},
|
||||||
{"INSERT", KEY_INSERT},
|
{"INSERT", HID_KEYBOARD_INSERT},
|
||||||
{"NUMLOCK", KEY_NUM_LOCK},
|
{"NUMLOCK", HID_KEYPAD_NUMLOCK},
|
||||||
{"PAGEUP", KEY_PAGE_UP},
|
{"PAGEUP", HID_KEYBOARD_PAGE_UP},
|
||||||
{"PAGEDOWN", KEY_PAGE_DOWN},
|
{"PAGEDOWN", HID_KEYBOARD_PAGE_DOWN},
|
||||||
{"PRINTSCREEN", KEY_PRINT},
|
{"PRINTSCREEN", HID_KEYBOARD_PRINT_SCREEN},
|
||||||
{"SCROLLOCK", KEY_SCROLL_LOCK},
|
{"SCROLLOCK", HID_KEYBOARD_SCROLL_LOCK},
|
||||||
{"SPACE", KEY_SPACE},
|
{"SPACE", HID_KEYBOARD_SPACEBAR},
|
||||||
{"TAB", KEY_TAB},
|
{"TAB", HID_KEYBOARD_TAB},
|
||||||
{"MENU", KEY_APPLICATION},
|
{"MENU", HID_KEYBOARD_APPLICATION},
|
||||||
{"APP", KEY_APPLICATION},
|
{"APP", HID_KEYBOARD_APPLICATION},
|
||||||
|
|
||||||
{"F1", KEY_F1},
|
{"F1", HID_KEYBOARD_F1},
|
||||||
{"F2", KEY_F2},
|
{"F2", HID_KEYBOARD_F2},
|
||||||
{"F3", KEY_F3},
|
{"F3", HID_KEYBOARD_F3},
|
||||||
{"F4", KEY_F4},
|
{"F4", HID_KEYBOARD_F4},
|
||||||
{"F5", KEY_F5},
|
{"F5", HID_KEYBOARD_F5},
|
||||||
{"F6", KEY_F6},
|
{"F6", HID_KEYBOARD_F6},
|
||||||
{"F7", KEY_F7},
|
{"F7", HID_KEYBOARD_F7},
|
||||||
{"F8", KEY_F8},
|
{"F8", HID_KEYBOARD_F8},
|
||||||
{"F9", KEY_F9},
|
{"F9", HID_KEYBOARD_F9},
|
||||||
{"F10", KEY_F10},
|
{"F10", HID_KEYBOARD_F10},
|
||||||
{"F11", KEY_F11},
|
{"F11", HID_KEYBOARD_F11},
|
||||||
{"F12", KEY_F12},
|
{"F12", HID_KEYBOARD_F12},
|
||||||
};
|
};
|
||||||
|
|
||||||
static const char ducky_cmd_comment[] = {"REM"};
|
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 char ducky_cmd_altstr_2[] = {"ALTCODE "};
|
||||||
|
|
||||||
static const uint8_t numpad_keys[10] = {
|
static const uint8_t numpad_keys[10] = {
|
||||||
KEYPAD_0,
|
HID_KEYPAD_0,
|
||||||
KEYPAD_1,
|
HID_KEYPAD_1,
|
||||||
KEYPAD_2,
|
HID_KEYPAD_2,
|
||||||
KEYPAD_3,
|
HID_KEYPAD_3,
|
||||||
KEYPAD_4,
|
HID_KEYPAD_4,
|
||||||
KEYPAD_5,
|
HID_KEYPAD_5,
|
||||||
KEYPAD_6,
|
HID_KEYPAD_6,
|
||||||
KEYPAD_7,
|
HID_KEYPAD_7,
|
||||||
KEYPAD_8,
|
HID_KEYPAD_8,
|
||||||
KEYPAD_9,
|
HID_KEYPAD_9,
|
||||||
};
|
};
|
||||||
|
|
||||||
static bool ducky_get_number(const char* param, uint32_t* val) {
|
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() {
|
static void ducky_numlock_on() {
|
||||||
if((furi_hal_hid_get_led_state() & HID_KB_LED_NUM) == 0) {
|
if((furi_hal_hid_get_led_state() & HID_KB_LED_NUM) == 0) {
|
||||||
furi_hal_hid_kb_press(KEY_NUM_LOCK);
|
furi_hal_hid_kb_press(HID_KEYBOARD_LOCK_NUM_LOCK);
|
||||||
furi_hal_hid_kb_release(KEY_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_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])) {
|
while(!ducky_is_line_end(charcode[i])) {
|
||||||
state = ducky_numpad_press(charcode[i]);
|
state = ducky_numpad_press(charcode[i]);
|
||||||
@ -178,7 +179,7 @@ static bool ducky_altchar(const char* charcode) {
|
|||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
|
|
||||||
furi_hal_hid_kb_release(KEY_MOD_LEFT_ALT);
|
furi_hal_hid_kb_release(HID_KEYBOARD_L_ALT);
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -206,7 +207,7 @@ static bool ducky_string(const char* param) {
|
|||||||
uint32_t i = 0;
|
uint32_t i = 0;
|
||||||
while(param[i] != '\0') {
|
while(param[i] != '\0') {
|
||||||
uint16_t keycode = HID_ASCII_TO_KEY(param[i]);
|
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_press(keycode);
|
||||||
furi_hal_hid_kb_release(keycode);
|
furi_hal_hid_kb_release(keycode);
|
||||||
}
|
}
|
||||||
@ -294,7 +295,7 @@ static int32_t ducky_parse_line(BadUsbScript* bad_usb, string_t line) {
|
|||||||
} else {
|
} else {
|
||||||
// Special keys + modifiers
|
// Special keys + modifiers
|
||||||
uint16_t key = ducky_get_keycode(line_tmp, false);
|
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) {
|
if((key & 0xFF00) != 0) {
|
||||||
// It's a modifier key
|
// It's a modifier key
|
||||||
line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
|
line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
|
||||||
|
|||||||
@ -6,7 +6,9 @@
|
|||||||
|
|
||||||
enum BtDebugSubmenuIndex {
|
enum BtDebugSubmenuIndex {
|
||||||
BtHidSubmenuIndexKeynote,
|
BtHidSubmenuIndexKeynote,
|
||||||
|
BtHidSubmenuIndexKeyboard,
|
||||||
BtHidSubmenuIndexMedia,
|
BtHidSubmenuIndexMedia,
|
||||||
|
BtHidSubmenuIndexMouse,
|
||||||
};
|
};
|
||||||
|
|
||||||
void bt_hid_submenu_callback(void* context, uint32_t index) {
|
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) {
|
if(index == BtHidSubmenuIndexKeynote) {
|
||||||
app->view_id = BtHidViewKeynote;
|
app->view_id = BtHidViewKeynote;
|
||||||
view_dispatcher_switch_to_view(app->view_dispatcher, 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) {
|
} else if(index == BtHidSubmenuIndexMedia) {
|
||||||
app->view_id = BtHidViewMedia;
|
app->view_id = BtHidViewMedia;
|
||||||
view_dispatcher_switch_to_view(app->view_dispatcher, 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);
|
furi_assert(context);
|
||||||
BtHid* app = context;
|
BtHid* app = context;
|
||||||
if(result == DialogExResultLeft) {
|
if(result == DialogExResultLeft) {
|
||||||
// TODO switch to Submenu after Media is done
|
|
||||||
view_dispatcher_stop(app->view_dispatcher);
|
view_dispatcher_stop(app->view_dispatcher);
|
||||||
} else if(result == DialogExResultRight) {
|
} 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);
|
notification_internal_message(bt_hid->notifications, &sequence_reset_blue);
|
||||||
}
|
}
|
||||||
bt_hid_keynote_set_connected_status(bt_hid->bt_hid_keynote, connected);
|
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_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() {
|
BtHid* bt_hid_app_alloc() {
|
||||||
@ -76,8 +87,11 @@ BtHid* bt_hid_app_alloc() {
|
|||||||
app->submenu = submenu_alloc();
|
app->submenu = submenu_alloc();
|
||||||
submenu_add_item(
|
submenu_add_item(
|
||||||
app->submenu, "Keynote", BtHidSubmenuIndexKeynote, bt_hid_submenu_callback, app);
|
app->submenu, "Keynote", BtHidSubmenuIndexKeynote, bt_hid_submenu_callback, app);
|
||||||
|
submenu_add_item(
|
||||||
|
app->submenu, "Keyboard", BtHidSubmenuIndexKeyboard, bt_hid_submenu_callback, app);
|
||||||
submenu_add_item(
|
submenu_add_item(
|
||||||
app->submenu, "Media player", BtHidSubmenuIndexMedia, bt_hid_submenu_callback, app);
|
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_set_previous_callback(submenu_get_view(app->submenu), bt_hid_exit);
|
||||||
view_dispatcher_add_view(
|
view_dispatcher_add_view(
|
||||||
app->view_dispatcher, BtHidViewSubmenu, submenu_get_view(app->submenu));
|
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_context(app->dialog, app);
|
||||||
dialog_ex_set_left_button_text(app->dialog, "Exit");
|
dialog_ex_set_left_button_text(app->dialog, "Exit");
|
||||||
dialog_ex_set_right_button_text(app->dialog, "Stay");
|
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);
|
dialog_ex_set_header(app->dialog, "Close current app?", 16, 12, AlignLeft, AlignTop);
|
||||||
view_dispatcher_add_view(
|
view_dispatcher_add_view(
|
||||||
app->view_dispatcher, BtHidViewExitConfirm, dialog_ex_get_view(app->dialog));
|
app->view_dispatcher, BtHidViewExitConfirm, dialog_ex_get_view(app->dialog));
|
||||||
@ -99,12 +114,25 @@ BtHid* bt_hid_app_alloc() {
|
|||||||
view_dispatcher_add_view(
|
view_dispatcher_add_view(
|
||||||
app->view_dispatcher, BtHidViewKeynote, bt_hid_keynote_get_view(app->bt_hid_keynote));
|
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
|
// Media view
|
||||||
app->bt_hid_media = bt_hid_media_alloc();
|
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_set_previous_callback(bt_hid_media_get_view(app->bt_hid_media), bt_hid_exit_confirm_view);
|
||||||
view_dispatcher_add_view(
|
view_dispatcher_add_view(
|
||||||
app->view_dispatcher, BtHidViewMedia, bt_hid_media_get_view(app->bt_hid_media));
|
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
|
// TODO switch to menu after Media is done
|
||||||
view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewKeynote);
|
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);
|
dialog_ex_free(app->dialog);
|
||||||
view_dispatcher_remove_view(app->view_dispatcher, BtHidViewKeynote);
|
view_dispatcher_remove_view(app->view_dispatcher, BtHidViewKeynote);
|
||||||
bt_hid_keynote_free(app->bt_hid_keynote);
|
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);
|
view_dispatcher_remove_view(app->view_dispatcher, BtHidViewMedia);
|
||||||
bt_hid_media_free(app->bt_hid_media);
|
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);
|
view_dispatcher_free(app->view_dispatcher);
|
||||||
|
|
||||||
// Close records
|
// Close records
|
||||||
|
|||||||
@ -10,7 +10,9 @@
|
|||||||
#include <gui/modules/submenu.h>
|
#include <gui/modules/submenu.h>
|
||||||
#include <gui/modules/dialog_ex.h>
|
#include <gui/modules/dialog_ex.h>
|
||||||
#include "views/bt_hid_keynote.h"
|
#include "views/bt_hid_keynote.h"
|
||||||
|
#include "views/bt_hid_keyboard.h"
|
||||||
#include "views/bt_hid_media.h"
|
#include "views/bt_hid_media.h"
|
||||||
|
#include "views/bt_hid_mouse.h"
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
Bt* bt;
|
Bt* bt;
|
||||||
@ -20,13 +22,17 @@ typedef struct {
|
|||||||
Submenu* submenu;
|
Submenu* submenu;
|
||||||
DialogEx* dialog;
|
DialogEx* dialog;
|
||||||
BtHidKeynote* bt_hid_keynote;
|
BtHidKeynote* bt_hid_keynote;
|
||||||
|
BtHidKeyboard* bt_hid_keyboard;
|
||||||
BtHidMedia* bt_hid_media;
|
BtHidMedia* bt_hid_media;
|
||||||
|
BtHidMouse* bt_hid_mouse;
|
||||||
uint32_t view_id;
|
uint32_t view_id;
|
||||||
} BtHid;
|
} BtHid;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
BtHidViewSubmenu,
|
BtHidViewSubmenu,
|
||||||
BtHidViewKeynote,
|
BtHidViewKeynote,
|
||||||
|
BtHidViewKeyboard,
|
||||||
BtHidViewMedia,
|
BtHidViewMedia,
|
||||||
|
BtHidViewMouse,
|
||||||
BtHidViewExitConfirm,
|
BtHidViewExitConfirm,
|
||||||
} BtHidView;
|
} BtHidView;
|
||||||
|
|||||||
384
applications/bt/bt_hid_app/views/bt_hid_keyboard.c
Normal file
384
applications/bt/bt_hid_app/views/bt_hid_keyboard.c
Normal file
@ -0,0 +1,384 @@
|
|||||||
|
#include "bt_hid_keyboard.h"
|
||||||
|
#include <furi.h>
|
||||||
|
#include <furi_hal_bt_hid.h>
|
||||||
|
#include <furi_hal_usb_hid.h>
|
||||||
|
#include <gui/elements.h>
|
||||||
|
#include <gui/icon_i.h>
|
||||||
|
|
||||||
|
struct BtHidKeyboard {
|
||||||
|
View* view;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
bool shift;
|
||||||
|
bool alt;
|
||||||
|
bool ctrl;
|
||||||
|
bool gui;
|
||||||
|
uint8_t x;
|
||||||
|
uint8_t y;
|
||||||
|
uint8_t last_key_code;
|
||||||
|
uint16_t modifier_code;
|
||||||
|
bool ok_pressed;
|
||||||
|
bool back_pressed;
|
||||||
|
bool connected;
|
||||||
|
char key_string[5];
|
||||||
|
} BtHidKeyboardModel;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint8_t width;
|
||||||
|
char* key;
|
||||||
|
const Icon* icon;
|
||||||
|
char* shift_key;
|
||||||
|
uint8_t value;
|
||||||
|
} BtHidKeyboardKey;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int8_t x;
|
||||||
|
int8_t y;
|
||||||
|
} BtHidKeyboardPoint;
|
||||||
|
|
||||||
|
// 4 BY 12
|
||||||
|
#define MARGIN_TOP 0
|
||||||
|
#define MARGIN_LEFT 4
|
||||||
|
#define KEY_WIDTH 9
|
||||||
|
#define KEY_HEIGHT 12
|
||||||
|
#define KEY_PADDING 1
|
||||||
|
#define ROW_COUNT 6
|
||||||
|
#define COLUMN_COUNT 12
|
||||||
|
|
||||||
|
// 0 width items are not drawn, but there value is used
|
||||||
|
const BtHidKeyboardKey bt_hid_keyboard_keyset[ROW_COUNT][COLUMN_COUNT] = {
|
||||||
|
{
|
||||||
|
{.width = 1, .icon = NULL, .key = "1", .shift_key = "!", .value = HID_KEYBOARD_1},
|
||||||
|
{.width = 1, .icon = NULL, .key = "2", .shift_key = "@", .value = HID_KEYBOARD_2},
|
||||||
|
{.width = 1, .icon = NULL, .key = "3", .shift_key = "#", .value = HID_KEYBOARD_3},
|
||||||
|
{.width = 1, .icon = NULL, .key = "4", .shift_key = "$", .value = HID_KEYBOARD_4},
|
||||||
|
{.width = 1, .icon = NULL, .key = "5", .shift_key = "%", .value = HID_KEYBOARD_5},
|
||||||
|
{.width = 1, .icon = NULL, .key = "6", .shift_key = "^", .value = HID_KEYBOARD_6},
|
||||||
|
{.width = 1, .icon = NULL, .key = "7", .shift_key = "&", .value = HID_KEYBOARD_7},
|
||||||
|
{.width = 1, .icon = NULL, .key = "8", .shift_key = "*", .value = HID_KEYBOARD_8},
|
||||||
|
{.width = 1, .icon = NULL, .key = "9", .shift_key = "(", .value = HID_KEYBOARD_9},
|
||||||
|
{.width = 1, .icon = NULL, .key = "0", .shift_key = ")", .value = HID_KEYBOARD_0},
|
||||||
|
{.width = 2, .icon = &I_Pin_arrow_left_9x7, .value = HID_KEYBOARD_DELETE},
|
||||||
|
{.width = 0, .value = HID_KEYBOARD_DELETE},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{.width = 1, .icon = NULL, .key = "q", .shift_key = "Q", .value = HID_KEYBOARD_Q},
|
||||||
|
{.width = 1, .icon = NULL, .key = "w", .shift_key = "W", .value = HID_KEYBOARD_W},
|
||||||
|
{.width = 1, .icon = NULL, .key = "e", .shift_key = "E", .value = HID_KEYBOARD_E},
|
||||||
|
{.width = 1, .icon = NULL, .key = "r", .shift_key = "R", .value = HID_KEYBOARD_R},
|
||||||
|
{.width = 1, .icon = NULL, .key = "t", .shift_key = "T", .value = HID_KEYBOARD_T},
|
||||||
|
{.width = 1, .icon = NULL, .key = "y", .shift_key = "Y", .value = HID_KEYBOARD_Y},
|
||||||
|
{.width = 1, .icon = NULL, .key = "u", .shift_key = "U", .value = HID_KEYBOARD_U},
|
||||||
|
{.width = 1, .icon = NULL, .key = "i", .shift_key = "I", .value = HID_KEYBOARD_I},
|
||||||
|
{.width = 1, .icon = NULL, .key = "o", .shift_key = "O", .value = HID_KEYBOARD_O},
|
||||||
|
{.width = 1, .icon = NULL, .key = "p", .shift_key = "P", .value = HID_KEYBOARD_P},
|
||||||
|
{.width = 1, .icon = NULL, .key = "[", .shift_key = "{", .value = HID_KEYBOARD_OPEN_BRACKET},
|
||||||
|
{.width = 1,
|
||||||
|
.icon = NULL,
|
||||||
|
.key = "]",
|
||||||
|
.shift_key = "}",
|
||||||
|
.value = HID_KEYBOARD_CLOSE_BRACKET},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{.width = 1, .icon = NULL, .key = "a", .shift_key = "A", .value = HID_KEYBOARD_A},
|
||||||
|
{.width = 1, .icon = NULL, .key = "s", .shift_key = "S", .value = HID_KEYBOARD_S},
|
||||||
|
{.width = 1, .icon = NULL, .key = "d", .shift_key = "D", .value = HID_KEYBOARD_D},
|
||||||
|
{.width = 1, .icon = NULL, .key = "f", .shift_key = "F", .value = HID_KEYBOARD_F},
|
||||||
|
{.width = 1, .icon = NULL, .key = "g", .shift_key = "G", .value = HID_KEYBOARD_G},
|
||||||
|
{.width = 1, .icon = NULL, .key = "h", .shift_key = "H", .value = HID_KEYBOARD_H},
|
||||||
|
{.width = 1, .icon = NULL, .key = "j", .shift_key = "J", .value = HID_KEYBOARD_J},
|
||||||
|
{.width = 1, .icon = NULL, .key = "k", .shift_key = "K", .value = HID_KEYBOARD_K},
|
||||||
|
{.width = 1, .icon = NULL, .key = "l", .shift_key = "L", .value = HID_KEYBOARD_L},
|
||||||
|
{.width = 1, .icon = NULL, .key = ";", .shift_key = ":", .value = HID_KEYBOARD_SEMICOLON},
|
||||||
|
{.width = 2, .icon = &I_Pin_arrow_right_9x7, .value = HID_KEYBOARD_RETURN},
|
||||||
|
{.width = 0, .value = HID_KEYBOARD_RETURN},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{.width = 1, .icon = NULL, .key = "z", .shift_key = "Z", .value = HID_KEYBOARD_Z},
|
||||||
|
{.width = 1, .icon = NULL, .key = "x", .shift_key = "X", .value = HID_KEYBOARD_X},
|
||||||
|
{.width = 1, .icon = NULL, .key = "c", .shift_key = "C", .value = HID_KEYBOARD_C},
|
||||||
|
{.width = 1, .icon = NULL, .key = "v", .shift_key = "V", .value = HID_KEYBOARD_V},
|
||||||
|
{.width = 1, .icon = NULL, .key = "b", .shift_key = "B", .value = HID_KEYBOARD_B},
|
||||||
|
{.width = 1, .icon = NULL, .key = "n", .shift_key = "N", .value = HID_KEYBOARD_N},
|
||||||
|
{.width = 1, .icon = NULL, .key = "m", .shift_key = "M", .value = HID_KEYBOARD_M},
|
||||||
|
{.width = 1, .icon = NULL, .key = "/", .shift_key = "?", .value = HID_KEYBOARD_SLASH},
|
||||||
|
{.width = 1, .icon = NULL, .key = "\\", .shift_key = "|", .value = HID_KEYBOARD_BACKSLASH},
|
||||||
|
{.width = 1, .icon = NULL, .key = "`", .shift_key = "~", .value = HID_KEYBOARD_GRAVE_ACCENT},
|
||||||
|
{.width = 1, .icon = &I_ButtonUp_7x4, .value = HID_KEYBOARD_UP_ARROW},
|
||||||
|
{.width = 1, .icon = NULL, .key = "-", .shift_key = "_", .value = HID_KEYBOARD_MINUS},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{.width = 1, .icon = &I_Pin_arrow_up7x9, .value = HID_KEYBOARD_L_SHIFT},
|
||||||
|
{.width = 1, .icon = NULL, .key = ",", .shift_key = "<", .value = HID_KEYPAD_COMMA},
|
||||||
|
{.width = 1, .icon = NULL, .key = ".", .shift_key = ">", .value = HID_KEYBOARD_DOT},
|
||||||
|
{.width = 4, .icon = NULL, .key = " ", .value = HID_KEYBOARD_SPACEBAR},
|
||||||
|
{.width = 0, .value = HID_KEYBOARD_SPACEBAR},
|
||||||
|
{.width = 0, .value = HID_KEYBOARD_SPACEBAR},
|
||||||
|
{.width = 0, .value = HID_KEYBOARD_SPACEBAR},
|
||||||
|
{.width = 1, .icon = NULL, .key = "'", .shift_key = "\"", .value = HID_KEYBOARD_APOSTROPHE},
|
||||||
|
{.width = 1, .icon = NULL, .key = "=", .shift_key = "+", .value = HID_KEYBOARD_EQUAL_SIGN},
|
||||||
|
{.width = 1, .icon = &I_ButtonLeft_4x7, .value = HID_KEYBOARD_LEFT_ARROW},
|
||||||
|
{.width = 1, .icon = &I_ButtonDown_7x4, .value = HID_KEYBOARD_DOWN_ARROW},
|
||||||
|
{.width = 1, .icon = &I_ButtonRight_4x7, .value = HID_KEYBOARD_RIGHT_ARROW},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{.width = 3, .icon = NULL, .key = "Ctrl", .value = HID_KEYBOARD_L_CTRL},
|
||||||
|
{.width = 0, .icon = NULL, .value = HID_KEYBOARD_L_CTRL},
|
||||||
|
{.width = 0, .icon = NULL, .value = HID_KEYBOARD_L_CTRL},
|
||||||
|
{.width = 3, .icon = NULL, .key = "Alt", .value = HID_KEYBOARD_L_ALT},
|
||||||
|
{.width = 0, .icon = NULL, .value = HID_KEYBOARD_L_ALT},
|
||||||
|
{.width = 0, .icon = NULL, .value = HID_KEYBOARD_L_ALT},
|
||||||
|
{.width = 3, .icon = NULL, .key = "Cmd", .value = HID_KEYBOARD_L_GUI},
|
||||||
|
{.width = 0, .icon = NULL, .value = HID_KEYBOARD_L_GUI},
|
||||||
|
{.width = 0, .icon = NULL, .value = HID_KEYBOARD_L_GUI},
|
||||||
|
{.width = 3, .icon = NULL, .key = "Tab", .value = HID_KEYBOARD_TAB},
|
||||||
|
{.width = 0, .icon = NULL, .value = HID_KEYBOARD_TAB},
|
||||||
|
{.width = 0, .icon = NULL, .value = HID_KEYBOARD_TAB},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
static void bt_hid_keyboard_to_upper(char* str) {
|
||||||
|
while(*str) {
|
||||||
|
*str = toupper((unsigned char)*str);
|
||||||
|
str++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void bt_hid_keyboard_draw_key(
|
||||||
|
Canvas* canvas,
|
||||||
|
BtHidKeyboardModel* model,
|
||||||
|
uint8_t x,
|
||||||
|
uint8_t y,
|
||||||
|
BtHidKeyboardKey key,
|
||||||
|
bool selected) {
|
||||||
|
if(!key.width) return;
|
||||||
|
|
||||||
|
canvas_set_color(canvas, ColorBlack);
|
||||||
|
uint8_t keyWidth = KEY_WIDTH * key.width + KEY_PADDING * (key.width - 1);
|
||||||
|
if(selected) {
|
||||||
|
// Draw a filled box
|
||||||
|
elements_slightly_rounded_box(
|
||||||
|
canvas,
|
||||||
|
MARGIN_LEFT + x * (KEY_WIDTH + KEY_PADDING),
|
||||||
|
MARGIN_TOP + y * (KEY_HEIGHT + KEY_PADDING),
|
||||||
|
keyWidth,
|
||||||
|
KEY_HEIGHT);
|
||||||
|
canvas_set_color(canvas, ColorWhite);
|
||||||
|
} else {
|
||||||
|
// Draw a framed box
|
||||||
|
elements_slightly_rounded_frame(
|
||||||
|
canvas,
|
||||||
|
MARGIN_LEFT + x * (KEY_WIDTH + KEY_PADDING),
|
||||||
|
MARGIN_TOP + y * (KEY_HEIGHT + KEY_PADDING),
|
||||||
|
keyWidth,
|
||||||
|
KEY_HEIGHT);
|
||||||
|
}
|
||||||
|
if(key.icon != NULL) {
|
||||||
|
// Draw the icon centered on the button
|
||||||
|
canvas_draw_icon(
|
||||||
|
canvas,
|
||||||
|
MARGIN_LEFT + x * (KEY_WIDTH + KEY_PADDING) + keyWidth / 2 - key.icon->width / 2,
|
||||||
|
MARGIN_TOP + y * (KEY_HEIGHT + KEY_PADDING) + KEY_HEIGHT / 2 - key.icon->height / 2,
|
||||||
|
key.icon);
|
||||||
|
} else {
|
||||||
|
// If shift is toggled use the shift key when available
|
||||||
|
strcpy(model->key_string, (model->shift && key.shift_key != 0) ? key.shift_key : key.key);
|
||||||
|
// Upper case if ctrl or alt was toggled true
|
||||||
|
if((model->ctrl && key.value == HID_KEYBOARD_L_CTRL) ||
|
||||||
|
(model->alt && key.value == HID_KEYBOARD_L_ALT) ||
|
||||||
|
(model->gui && key.value == HID_KEYBOARD_L_GUI)) {
|
||||||
|
bt_hid_keyboard_to_upper(model->key_string);
|
||||||
|
}
|
||||||
|
canvas_draw_str_aligned(
|
||||||
|
canvas,
|
||||||
|
MARGIN_LEFT + x * (KEY_WIDTH + KEY_PADDING) + keyWidth / 2 + 1,
|
||||||
|
MARGIN_TOP + y * (KEY_HEIGHT + KEY_PADDING) + KEY_HEIGHT / 2,
|
||||||
|
AlignCenter,
|
||||||
|
AlignCenter,
|
||||||
|
model->key_string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void bt_hid_keyboard_draw_callback(Canvas* canvas, void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
BtHidKeyboardModel* model = context;
|
||||||
|
|
||||||
|
// Header
|
||||||
|
if(!model->connected) {
|
||||||
|
canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15);
|
||||||
|
canvas_set_font(canvas, FontPrimary);
|
||||||
|
elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Keyboard");
|
||||||
|
elements_multiline_text_aligned(
|
||||||
|
canvas, 4, 60, AlignLeft, AlignBottom, "Waiting for Connection...");
|
||||||
|
return; // Dont render the keyboard if we are not yet connected
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas_set_font(canvas, FontKeyboard);
|
||||||
|
// Start shifting the all keys up if on the next row (Scrolling)
|
||||||
|
uint8_t initY = model->y - 4 > 0 ? model->y - 4 : 0;
|
||||||
|
for(uint8_t y = initY; y < ROW_COUNT; y++) {
|
||||||
|
const BtHidKeyboardKey* keyboardKeyRow = bt_hid_keyboard_keyset[y];
|
||||||
|
uint8_t x = 0;
|
||||||
|
for(uint8_t i = 0; i < COLUMN_COUNT; i++) {
|
||||||
|
BtHidKeyboardKey key = keyboardKeyRow[i];
|
||||||
|
// Select when the button is hovered
|
||||||
|
// Select if the button is hovered within its width
|
||||||
|
// Select if back is clicked and its the backspace key
|
||||||
|
// Deselect when the button clicked or not hovered
|
||||||
|
bool keySelected = (x <= model->x && model->x < (x + key.width)) && y == model->y;
|
||||||
|
bool backSelected = model->back_pressed && key.value == HID_KEYBOARD_DELETE;
|
||||||
|
bt_hid_keyboard_draw_key(
|
||||||
|
canvas,
|
||||||
|
model,
|
||||||
|
x,
|
||||||
|
y - initY,
|
||||||
|
key,
|
||||||
|
(!model->ok_pressed && keySelected) || backSelected);
|
||||||
|
x += key.width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t bt_hid_keyboard_get_selected_key(BtHidKeyboardModel* model) {
|
||||||
|
BtHidKeyboardKey key = bt_hid_keyboard_keyset[model->y][model->x];
|
||||||
|
// Use upper case if shift is toggled
|
||||||
|
bool useUppercase = model->shift;
|
||||||
|
// Check if the key has an upper case version
|
||||||
|
bool hasUppercase = key.shift_key != 0;
|
||||||
|
if(useUppercase && hasUppercase)
|
||||||
|
return key.value;
|
||||||
|
else
|
||||||
|
return key.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void bt_hid_keyboard_get_select_key(BtHidKeyboardModel* model, BtHidKeyboardPoint delta) {
|
||||||
|
// Keep going until a valid spot is found, this allows for nulls and zero width keys in the map
|
||||||
|
do {
|
||||||
|
if(((int8_t)model->y) + delta.y < 0)
|
||||||
|
model->y = ROW_COUNT - 1;
|
||||||
|
else
|
||||||
|
model->y = (model->y + delta.y) % ROW_COUNT;
|
||||||
|
} while(delta.y != 0 && bt_hid_keyboard_keyset[model->y][model->x].value == 0);
|
||||||
|
|
||||||
|
do {
|
||||||
|
if(((int8_t)model->x) + delta.x < 0)
|
||||||
|
model->x = COLUMN_COUNT - 1;
|
||||||
|
else
|
||||||
|
model->x = (model->x + delta.x) % COLUMN_COUNT;
|
||||||
|
} while(delta.x != 0 && bt_hid_keyboard_keyset[model->y][model->x].width ==
|
||||||
|
0); // Skip zero width keys, pretend they are one key
|
||||||
|
}
|
||||||
|
|
||||||
|
static void bt_hid_keyboard_process(BtHidKeyboard* bt_hid_keyboard, InputEvent* event) {
|
||||||
|
with_view_model(
|
||||||
|
bt_hid_keyboard->view, (BtHidKeyboardModel * model) {
|
||||||
|
if(event->key == InputKeyOk) {
|
||||||
|
if(event->type == InputTypePress) {
|
||||||
|
model->ok_pressed = true;
|
||||||
|
} else if(event->type == InputTypeLong || event->type == InputTypeShort) {
|
||||||
|
model->last_key_code = bt_hid_keyboard_get_selected_key(model);
|
||||||
|
|
||||||
|
// Toggle the modifier key when clicked, and click the key
|
||||||
|
if(model->last_key_code == HID_KEYBOARD_L_SHIFT) {
|
||||||
|
model->shift = !model->shift;
|
||||||
|
if(model->shift)
|
||||||
|
model->modifier_code |= KEY_MOD_LEFT_SHIFT;
|
||||||
|
else
|
||||||
|
model->modifier_code &= ~KEY_MOD_LEFT_SHIFT;
|
||||||
|
} else if(model->last_key_code == HID_KEYBOARD_L_ALT) {
|
||||||
|
model->alt = !model->alt;
|
||||||
|
if(model->alt)
|
||||||
|
model->modifier_code |= KEY_MOD_LEFT_ALT;
|
||||||
|
else
|
||||||
|
model->modifier_code &= ~KEY_MOD_LEFT_ALT;
|
||||||
|
} else if(model->last_key_code == HID_KEYBOARD_L_CTRL) {
|
||||||
|
model->ctrl = !model->ctrl;
|
||||||
|
if(model->ctrl)
|
||||||
|
model->modifier_code |= KEY_MOD_LEFT_CTRL;
|
||||||
|
else
|
||||||
|
model->modifier_code &= ~KEY_MOD_LEFT_CTRL;
|
||||||
|
} else if(model->last_key_code == HID_KEYBOARD_L_GUI) {
|
||||||
|
model->gui = !model->gui;
|
||||||
|
if(model->gui)
|
||||||
|
model->modifier_code |= KEY_MOD_LEFT_GUI;
|
||||||
|
else
|
||||||
|
model->modifier_code &= ~KEY_MOD_LEFT_GUI;
|
||||||
|
}
|
||||||
|
furi_hal_bt_hid_kb_press(model->modifier_code | model->last_key_code);
|
||||||
|
} else if(event->type == InputTypeRelease) {
|
||||||
|
// Release happens after short and long presses
|
||||||
|
furi_hal_bt_hid_kb_release(model->modifier_code | model->last_key_code);
|
||||||
|
model->ok_pressed = false;
|
||||||
|
}
|
||||||
|
} else if(event->key == InputKeyBack) {
|
||||||
|
// If back is pressed for a short time, backspace
|
||||||
|
if(event->type == InputTypePress) {
|
||||||
|
model->back_pressed = true;
|
||||||
|
} else if(event->type == InputTypeShort) {
|
||||||
|
furi_hal_bt_hid_kb_press(HID_KEYBOARD_DELETE);
|
||||||
|
furi_hal_bt_hid_kb_release(HID_KEYBOARD_DELETE);
|
||||||
|
} else if(event->type == InputTypeRelease) {
|
||||||
|
model->back_pressed = false;
|
||||||
|
}
|
||||||
|
} else if(event->type == InputTypePress || event->type == InputTypeRepeat) {
|
||||||
|
// Cycle the selected keys
|
||||||
|
if(event->key == InputKeyUp) {
|
||||||
|
bt_hid_keyboard_get_select_key(model, (BtHidKeyboardPoint){.x = 0, .y = -1});
|
||||||
|
} else if(event->key == InputKeyDown) {
|
||||||
|
bt_hid_keyboard_get_select_key(model, (BtHidKeyboardPoint){.x = 0, .y = 1});
|
||||||
|
} else if(event->key == InputKeyLeft) {
|
||||||
|
bt_hid_keyboard_get_select_key(model, (BtHidKeyboardPoint){.x = -1, .y = 0});
|
||||||
|
} else if(event->key == InputKeyRight) {
|
||||||
|
bt_hid_keyboard_get_select_key(model, (BtHidKeyboardPoint){.x = 1, .y = 0});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool bt_hid_keyboard_input_callback(InputEvent* event, void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
BtHidKeyboard* bt_hid_keyboard = context;
|
||||||
|
bool consumed = false;
|
||||||
|
|
||||||
|
if(event->type == InputTypeLong && event->key == InputKeyBack) {
|
||||||
|
furi_hal_bt_hid_kb_release_all();
|
||||||
|
} else {
|
||||||
|
bt_hid_keyboard_process(bt_hid_keyboard, event);
|
||||||
|
consumed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return consumed;
|
||||||
|
}
|
||||||
|
|
||||||
|
BtHidKeyboard* bt_hid_keyboard_alloc() {
|
||||||
|
BtHidKeyboard* bt_hid_keyboard = malloc(sizeof(BtHidKeyboard));
|
||||||
|
bt_hid_keyboard->view = view_alloc();
|
||||||
|
view_set_context(bt_hid_keyboard->view, bt_hid_keyboard);
|
||||||
|
view_allocate_model(bt_hid_keyboard->view, ViewModelTypeLocking, sizeof(BtHidKeyboardModel));
|
||||||
|
view_set_draw_callback(bt_hid_keyboard->view, bt_hid_keyboard_draw_callback);
|
||||||
|
view_set_input_callback(bt_hid_keyboard->view, bt_hid_keyboard_input_callback);
|
||||||
|
|
||||||
|
return bt_hid_keyboard;
|
||||||
|
}
|
||||||
|
|
||||||
|
void bt_hid_keyboard_free(BtHidKeyboard* bt_hid_keyboard) {
|
||||||
|
furi_assert(bt_hid_keyboard);
|
||||||
|
view_free(bt_hid_keyboard->view);
|
||||||
|
free(bt_hid_keyboard);
|
||||||
|
}
|
||||||
|
|
||||||
|
View* bt_hid_keyboard_get_view(BtHidKeyboard* bt_hid_keyboard) {
|
||||||
|
furi_assert(bt_hid_keyboard);
|
||||||
|
return bt_hid_keyboard->view;
|
||||||
|
}
|
||||||
|
|
||||||
|
void bt_hid_keyboard_set_connected_status(BtHidKeyboard* bt_hid_keyboard, bool connected) {
|
||||||
|
furi_assert(bt_hid_keyboard);
|
||||||
|
with_view_model(
|
||||||
|
bt_hid_keyboard->view, (BtHidKeyboardModel * model) {
|
||||||
|
model->connected = connected;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
13
applications/bt/bt_hid_app/views/bt_hid_keyboard.h
Normal file
13
applications/bt/bt_hid_app/views/bt_hid_keyboard.h
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <gui/view.h>
|
||||||
|
|
||||||
|
typedef struct BtHidKeyboard BtHidKeyboard;
|
||||||
|
|
||||||
|
BtHidKeyboard* bt_hid_keyboard_alloc();
|
||||||
|
|
||||||
|
void bt_hid_keyboard_free(BtHidKeyboard* bt_hid_keyboard);
|
||||||
|
|
||||||
|
View* bt_hid_keyboard_get_view(BtHidKeyboard* bt_hid_keyboard);
|
||||||
|
|
||||||
|
void bt_hid_keyboard_set_connected_status(BtHidKeyboard* bt_hid_keyboard, bool connected);
|
||||||
@ -14,6 +14,7 @@ typedef struct {
|
|||||||
bool right_pressed;
|
bool right_pressed;
|
||||||
bool down_pressed;
|
bool down_pressed;
|
||||||
bool ok_pressed;
|
bool ok_pressed;
|
||||||
|
bool back_pressed;
|
||||||
bool connected;
|
bool connected;
|
||||||
} BtHidKeynoteModel;
|
} BtHidKeynoteModel;
|
||||||
|
|
||||||
@ -35,106 +36,119 @@ static void bt_hid_keynote_draw_callback(Canvas* canvas, void* context) {
|
|||||||
BtHidKeynoteModel* model = context;
|
BtHidKeynoteModel* model = context;
|
||||||
|
|
||||||
// Header
|
// 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);
|
canvas_set_font(canvas, FontPrimary);
|
||||||
elements_multiline_text_aligned(canvas, 9, 3, AlignLeft, AlignTop, "Keynote");
|
elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Keynote");
|
||||||
canvas_set_font(canvas, FontSecondary);
|
canvas_set_font(canvas, FontSecondary);
|
||||||
|
|
||||||
// Connected status
|
|
||||||
if(model->connected) {
|
|
||||||
canvas_draw_icon(canvas, 18, 18, &I_Ble_connected_38x34);
|
|
||||||
elements_multiline_text_aligned(canvas, 9, 60, AlignLeft, AlignBottom, "Connected");
|
|
||||||
} else {
|
|
||||||
canvas_draw_icon(canvas, 18, 18, &I_Ble_disconnected_24x34);
|
|
||||||
elements_multiline_text_aligned(canvas, 3, 60, AlignLeft, AlignBottom, "Disconnected");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Up
|
// Up
|
||||||
canvas_draw_icon(canvas, 86, 4, &I_Button_18x18);
|
canvas_draw_icon(canvas, 21, 24, &I_Button_18x18);
|
||||||
if(model->up_pressed) {
|
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);
|
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);
|
canvas_set_color(canvas, ColorBlack);
|
||||||
|
|
||||||
// Down
|
// Down
|
||||||
canvas_draw_icon(canvas, 86, 25, &I_Button_18x18);
|
canvas_draw_icon(canvas, 21, 45, &I_Button_18x18);
|
||||||
if(model->down_pressed) {
|
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);
|
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);
|
canvas_set_color(canvas, ColorBlack);
|
||||||
|
|
||||||
// Left
|
// Left
|
||||||
canvas_draw_icon(canvas, 65, 25, &I_Button_18x18);
|
canvas_draw_icon(canvas, 0, 45, &I_Button_18x18);
|
||||||
if(model->left_pressed) {
|
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);
|
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);
|
canvas_set_color(canvas, ColorBlack);
|
||||||
|
|
||||||
// Right
|
// Right
|
||||||
canvas_draw_icon(canvas, 107, 25, &I_Button_18x18);
|
canvas_draw_icon(canvas, 42, 45, &I_Button_18x18);
|
||||||
if(model->right_pressed) {
|
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);
|
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);
|
canvas_set_color(canvas, ColorBlack);
|
||||||
|
|
||||||
// Ok
|
// Ok
|
||||||
canvas_draw_icon(canvas, 63, 45, &I_Space_65x18);
|
canvas_draw_icon(canvas, 63, 25, &I_Space_65x18);
|
||||||
if(model->ok_pressed) {
|
if(model->ok_pressed) {
|
||||||
|
elements_slightly_rounded_box(canvas, 66, 27, 60, 13);
|
||||||
|
canvas_set_color(canvas, ColorWhite);
|
||||||
|
}
|
||||||
|
canvas_draw_icon(canvas, 74, 29, &I_Ok_btn_9x9);
|
||||||
|
elements_multiline_text_aligned(canvas, 91, 36, AlignLeft, AlignBottom, "Space");
|
||||||
|
canvas_set_color(canvas, ColorBlack);
|
||||||
|
|
||||||
|
// Back
|
||||||
|
canvas_draw_icon(canvas, 63, 45, &I_Space_65x18);
|
||||||
|
if(model->back_pressed) {
|
||||||
elements_slightly_rounded_box(canvas, 66, 47, 60, 13);
|
elements_slightly_rounded_box(canvas, 66, 47, 60, 13);
|
||||||
canvas_set_color(canvas, ColorWhite);
|
canvas_set_color(canvas, ColorWhite);
|
||||||
}
|
}
|
||||||
canvas_draw_icon(canvas, 74, 49, &I_Ok_btn_9x9);
|
canvas_draw_icon(canvas, 110, 49, &I_Ok_btn_9x9);
|
||||||
elements_multiline_text_aligned(canvas, 91, 56, AlignLeft, AlignBottom, "Space");
|
elements_multiline_text_aligned(canvas, 76, 56, AlignLeft, AlignBottom, "Back");
|
||||||
}
|
}
|
||||||
|
|
||||||
static void bt_hid_keynote_process_press(BtHidKeynote* bt_hid_keynote, InputEvent* event) {
|
static void bt_hid_keynote_process(BtHidKeynote* bt_hid_keynote, InputEvent* event) {
|
||||||
with_view_model(
|
with_view_model(
|
||||||
bt_hid_keynote->view, (BtHidKeynoteModel * model) {
|
bt_hid_keynote->view, (BtHidKeynoteModel * model) {
|
||||||
if(event->key == InputKeyUp) {
|
if(event->type == InputTypePress) {
|
||||||
model->up_pressed = true;
|
if(event->key == InputKeyUp) {
|
||||||
furi_hal_bt_hid_kb_press(KEY_UP_ARROW);
|
model->up_pressed = true;
|
||||||
} else if(event->key == InputKeyDown) {
|
furi_hal_bt_hid_kb_press(HID_KEYBOARD_UP_ARROW);
|
||||||
model->down_pressed = true;
|
} else if(event->key == InputKeyDown) {
|
||||||
furi_hal_bt_hid_kb_press(KEY_DOWN_ARROW);
|
model->down_pressed = true;
|
||||||
} else if(event->key == InputKeyLeft) {
|
furi_hal_bt_hid_kb_press(HID_KEYBOARD_DOWN_ARROW);
|
||||||
model->left_pressed = true;
|
} else if(event->key == InputKeyLeft) {
|
||||||
furi_hal_bt_hid_kb_press(KEY_LEFT_ARROW);
|
model->left_pressed = true;
|
||||||
} else if(event->key == InputKeyRight) {
|
furi_hal_bt_hid_kb_press(HID_KEYBOARD_LEFT_ARROW);
|
||||||
model->right_pressed = true;
|
} else if(event->key == InputKeyRight) {
|
||||||
furi_hal_bt_hid_kb_press(KEY_RIGHT_ARROW);
|
model->right_pressed = true;
|
||||||
} else if(event->key == InputKeyOk) {
|
furi_hal_bt_hid_kb_press(HID_KEYBOARD_RIGHT_ARROW);
|
||||||
model->ok_pressed = true;
|
} else if(event->key == InputKeyOk) {
|
||||||
furi_hal_bt_hid_kb_press(KEY_SPACE);
|
model->ok_pressed = true;
|
||||||
}
|
furi_hal_bt_hid_kb_press(HID_KEYBOARD_SPACEBAR);
|
||||||
return true;
|
} else if(event->key == InputKeyBack) {
|
||||||
});
|
model->back_pressed = true;
|
||||||
}
|
}
|
||||||
|
} else if(event->type == InputTypeRelease) {
|
||||||
static void bt_hid_keynote_process_release(BtHidKeynote* bt_hid_keynote, InputEvent* event) {
|
if(event->key == InputKeyUp) {
|
||||||
with_view_model(
|
model->up_pressed = false;
|
||||||
bt_hid_keynote->view, (BtHidKeynoteModel * model) {
|
furi_hal_bt_hid_kb_release(HID_KEYBOARD_UP_ARROW);
|
||||||
if(event->key == InputKeyUp) {
|
} else if(event->key == InputKeyDown) {
|
||||||
model->up_pressed = false;
|
model->down_pressed = false;
|
||||||
furi_hal_bt_hid_kb_release(KEY_UP_ARROW);
|
furi_hal_bt_hid_kb_release(HID_KEYBOARD_DOWN_ARROW);
|
||||||
} else if(event->key == InputKeyDown) {
|
} else if(event->key == InputKeyLeft) {
|
||||||
model->down_pressed = false;
|
model->left_pressed = false;
|
||||||
furi_hal_bt_hid_kb_release(KEY_DOWN_ARROW);
|
furi_hal_bt_hid_kb_release(HID_KEYBOARD_LEFT_ARROW);
|
||||||
} else if(event->key == InputKeyLeft) {
|
} else if(event->key == InputKeyRight) {
|
||||||
model->left_pressed = false;
|
model->right_pressed = false;
|
||||||
furi_hal_bt_hid_kb_release(KEY_LEFT_ARROW);
|
furi_hal_bt_hid_kb_release(HID_KEYBOARD_RIGHT_ARROW);
|
||||||
} else if(event->key == InputKeyRight) {
|
} else if(event->key == InputKeyOk) {
|
||||||
model->right_pressed = false;
|
model->ok_pressed = false;
|
||||||
furi_hal_bt_hid_kb_release(KEY_RIGHT_ARROW);
|
furi_hal_bt_hid_kb_release(HID_KEYBOARD_SPACEBAR);
|
||||||
} else if(event->key == InputKeyOk) {
|
} else if(event->key == InputKeyBack) {
|
||||||
model->ok_pressed = false;
|
model->back_pressed = false;
|
||||||
furi_hal_bt_hid_kb_release(KEY_SPACE);
|
}
|
||||||
|
} 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;
|
return true;
|
||||||
});
|
});
|
||||||
@ -145,16 +159,11 @@ static bool bt_hid_keynote_input_callback(InputEvent* event, void* context) {
|
|||||||
BtHidKeynote* bt_hid_keynote = context;
|
BtHidKeynote* bt_hid_keynote = context;
|
||||||
bool consumed = false;
|
bool consumed = false;
|
||||||
|
|
||||||
if(event->type == InputTypePress) {
|
if(event->type == InputTypeLong && event->key == InputKeyBack) {
|
||||||
bt_hid_keynote_process_press(bt_hid_keynote, event);
|
furi_hal_bt_hid_kb_release_all();
|
||||||
|
} else {
|
||||||
|
bt_hid_keynote_process(bt_hid_keynote, event);
|
||||||
consumed = true;
|
consumed = true;
|
||||||
} else if(event->type == InputTypeRelease) {
|
|
||||||
bt_hid_keynote_process_release(bt_hid_keynote, event);
|
|
||||||
consumed = true;
|
|
||||||
} else if(event->type == InputTypeShort) {
|
|
||||||
if(event->key == InputKeyBack) {
|
|
||||||
furi_hal_hid_kb_release_all();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return consumed;
|
return consumed;
|
||||||
|
|||||||
@ -35,18 +35,14 @@ static void bt_hid_media_draw_callback(Canvas* canvas, void* context) {
|
|||||||
BtHidMediaModel* model = context;
|
BtHidMediaModel* model = context;
|
||||||
|
|
||||||
// Header
|
// 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) {
|
if(model->connected) {
|
||||||
canvas_draw_icon(canvas, 23, 17, &I_Ble_connected_38x34);
|
canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15);
|
||||||
elements_multiline_text_aligned(canvas, 35, 61, AlignCenter, AlignBottom, "Connected");
|
|
||||||
} else {
|
} else {
|
||||||
canvas_draw_icon(canvas, 23, 17, &I_Ble_disconnected_24x34);
|
canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15);
|
||||||
elements_multiline_text_aligned(canvas, 35, 61, AlignCenter, AlignBottom, "Disconnected");
|
|
||||||
}
|
}
|
||||||
|
canvas_set_font(canvas, FontPrimary);
|
||||||
|
elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Media");
|
||||||
|
canvas_set_font(canvas, FontSecondary);
|
||||||
|
|
||||||
// Keypad circles
|
// Keypad circles
|
||||||
canvas_draw_icon(canvas, 76, 8, &I_Circles_47x47);
|
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) {
|
bt_hid_media->view, (BtHidMediaModel * model) {
|
||||||
if(event->key == InputKeyUp) {
|
if(event->key == InputKeyUp) {
|
||||||
model->up_pressed = true;
|
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) {
|
} else if(event->key == InputKeyDown) {
|
||||||
model->down_pressed = true;
|
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) {
|
} else if(event->key == InputKeyLeft) {
|
||||||
model->left_pressed = true;
|
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) {
|
} else if(event->key == InputKeyRight) {
|
||||||
model->right_pressed = true;
|
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) {
|
} else if(event->key == InputKeyOk) {
|
||||||
model->ok_pressed = true;
|
model->ok_pressed = true;
|
||||||
furi_hal_bt_hid_media_press(FuriHalBtHidMediaPlayPause);
|
furi_hal_bt_hid_consumer_key_press(HID_CONSUMER_PLAY_PAUSE);
|
||||||
}
|
}
|
||||||
return true;
|
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) {
|
bt_hid_media->view, (BtHidMediaModel * model) {
|
||||||
if(event->key == InputKeyUp) {
|
if(event->key == InputKeyUp) {
|
||||||
model->up_pressed = false;
|
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) {
|
} else if(event->key == InputKeyDown) {
|
||||||
model->down_pressed = false;
|
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) {
|
} else if(event->key == InputKeyLeft) {
|
||||||
model->left_pressed = false;
|
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) {
|
} else if(event->key == InputKeyRight) {
|
||||||
model->right_pressed = false;
|
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) {
|
} else if(event->key == InputKeyOk) {
|
||||||
model->ok_pressed = false;
|
model->ok_pressed = false;
|
||||||
furi_hal_bt_hid_media_release(FuriHalBtHidMediaPlayPause);
|
furi_hal_bt_hid_consumer_key_release(HID_CONSUMER_PLAY_PAUSE);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
@ -154,7 +150,7 @@ static bool bt_hid_media_input_callback(InputEvent* event, void* context) {
|
|||||||
consumed = true;
|
consumed = true;
|
||||||
} else if(event->type == InputTypeShort) {
|
} else if(event->type == InputTypeShort) {
|
||||||
if(event->key == InputKeyBack) {
|
if(event->key == InputKeyBack) {
|
||||||
furi_hal_bt_hid_media_release_all();
|
furi_hal_bt_hid_consumer_key_release_all();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
207
applications/bt/bt_hid_app/views/bt_hid_mouse.c
Normal file
207
applications/bt/bt_hid_app/views/bt_hid_mouse.c
Normal file
@ -0,0 +1,207 @@
|
|||||||
|
#include "bt_hid_mouse.h"
|
||||||
|
#include <furi.h>
|
||||||
|
#include <furi_hal_bt_hid.h>
|
||||||
|
#include <furi_hal_usb_hid.h>
|
||||||
|
#include <gui/elements.h>
|
||||||
|
|
||||||
|
struct BtHidMouse {
|
||||||
|
View* view;
|
||||||
|
};
|
||||||
|
#define MOUSE_MOVE_SHORT 5
|
||||||
|
#define MOUSE_MOVE_LONG 20
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
bool left_pressed;
|
||||||
|
bool up_pressed;
|
||||||
|
bool right_pressed;
|
||||||
|
bool down_pressed;
|
||||||
|
bool left_mouse_pressed;
|
||||||
|
bool left_mouse_held;
|
||||||
|
bool right_mouse_pressed;
|
||||||
|
bool connected;
|
||||||
|
} BtHidMouseModel;
|
||||||
|
|
||||||
|
static void bt_hid_mouse_draw_callback(Canvas* canvas, void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
BtHidMouseModel* model = context;
|
||||||
|
|
||||||
|
// Header
|
||||||
|
if(model->connected) {
|
||||||
|
canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15);
|
||||||
|
} else {
|
||||||
|
canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15);
|
||||||
|
}
|
||||||
|
canvas_set_font(canvas, FontPrimary);
|
||||||
|
elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Mouse");
|
||||||
|
canvas_set_font(canvas, FontSecondary);
|
||||||
|
|
||||||
|
if(model->left_mouse_held == true) {
|
||||||
|
elements_multiline_text_aligned(canvas, 0, 60, AlignLeft, AlignBottom, "Selecting...");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keypad circles
|
||||||
|
canvas_draw_icon(canvas, 64, 8, &I_Circles_47x47);
|
||||||
|
|
||||||
|
// Up
|
||||||
|
if(model->up_pressed) {
|
||||||
|
canvas_draw_icon(canvas, 81, 9, &I_Pressed_Button_13x13);
|
||||||
|
canvas_set_color(canvas, ColorWhite);
|
||||||
|
}
|
||||||
|
canvas_draw_icon(canvas, 84, 10, &I_Pin_arrow_up7x9);
|
||||||
|
canvas_set_color(canvas, ColorBlack);
|
||||||
|
|
||||||
|
// Down
|
||||||
|
if(model->down_pressed) {
|
||||||
|
canvas_draw_icon(canvas, 81, 41, &I_Pressed_Button_13x13);
|
||||||
|
canvas_set_color(canvas, ColorWhite);
|
||||||
|
}
|
||||||
|
canvas_draw_icon(canvas, 84, 43, &I_Pin_arrow_down_7x9);
|
||||||
|
canvas_set_color(canvas, ColorBlack);
|
||||||
|
|
||||||
|
// Left
|
||||||
|
if(model->left_pressed) {
|
||||||
|
canvas_draw_icon(canvas, 65, 25, &I_Pressed_Button_13x13);
|
||||||
|
canvas_set_color(canvas, ColorWhite);
|
||||||
|
}
|
||||||
|
canvas_draw_icon(canvas, 67, 28, &I_Pin_arrow_left_9x7);
|
||||||
|
canvas_set_color(canvas, ColorBlack);
|
||||||
|
|
||||||
|
// Right
|
||||||
|
if(model->right_pressed) {
|
||||||
|
canvas_draw_icon(canvas, 97, 25, &I_Pressed_Button_13x13);
|
||||||
|
canvas_set_color(canvas, ColorWhite);
|
||||||
|
}
|
||||||
|
canvas_draw_icon(canvas, 99, 28, &I_Pin_arrow_right_9x7);
|
||||||
|
canvas_set_color(canvas, ColorBlack);
|
||||||
|
|
||||||
|
// Ok
|
||||||
|
if(model->left_mouse_pressed) {
|
||||||
|
canvas_draw_icon(canvas, 81, 25, &I_Pressed_Button_13x13);
|
||||||
|
canvas_set_color(canvas, ColorWhite);
|
||||||
|
}
|
||||||
|
canvas_draw_icon(canvas, 83, 27, &I_Ok_btn_9x9);
|
||||||
|
canvas_set_color(canvas, ColorBlack);
|
||||||
|
|
||||||
|
// Back
|
||||||
|
if(model->right_mouse_pressed) {
|
||||||
|
canvas_draw_icon(canvas, 108, 48, &I_Pressed_Button_13x13);
|
||||||
|
canvas_set_color(canvas, ColorWhite);
|
||||||
|
}
|
||||||
|
canvas_draw_icon(canvas, 110, 50, &I_Ok_btn_9x9);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void bt_hid_mouse_process(BtHidMouse* bt_hid_mouse, InputEvent* event) {
|
||||||
|
with_view_model(
|
||||||
|
bt_hid_mouse->view, (BtHidMouseModel * model) {
|
||||||
|
if(event->key == InputKeyBack) {
|
||||||
|
if(event->type == InputTypeShort) {
|
||||||
|
furi_hal_bt_hid_mouse_press(HID_MOUSE_BTN_RIGHT);
|
||||||
|
furi_hal_bt_hid_mouse_release(HID_MOUSE_BTN_RIGHT);
|
||||||
|
} else if(event->type == InputTypePress) {
|
||||||
|
model->right_mouse_pressed = true;
|
||||||
|
} else if(event->type == InputTypeRelease) {
|
||||||
|
model->right_mouse_pressed = false;
|
||||||
|
}
|
||||||
|
} else if(event->key == InputKeyOk) {
|
||||||
|
if(event->type == InputTypeShort) {
|
||||||
|
// Just release if it was being held before
|
||||||
|
if(!model->left_mouse_held) furi_hal_bt_hid_mouse_press(HID_MOUSE_BTN_LEFT);
|
||||||
|
furi_hal_bt_hid_mouse_release(HID_MOUSE_BTN_LEFT);
|
||||||
|
model->left_mouse_held = false;
|
||||||
|
} else if(event->type == InputTypeLong) {
|
||||||
|
furi_hal_bt_hid_mouse_press(HID_MOUSE_BTN_LEFT);
|
||||||
|
model->left_mouse_held = true;
|
||||||
|
model->left_mouse_pressed = true;
|
||||||
|
} else if(event->type == InputTypePress) {
|
||||||
|
model->left_mouse_pressed = true;
|
||||||
|
} else if(event->type == InputTypeRelease) {
|
||||||
|
// Only release if it wasn't a long press
|
||||||
|
if(!model->left_mouse_held) model->left_mouse_pressed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if(event->key == InputKeyRight) {
|
||||||
|
if(event->type == InputTypePress) {
|
||||||
|
model->right_pressed = true;
|
||||||
|
furi_hal_bt_hid_mouse_move(MOUSE_MOVE_SHORT, 0);
|
||||||
|
} else if(event->type == InputTypeRepeat) {
|
||||||
|
furi_hal_bt_hid_mouse_move(MOUSE_MOVE_LONG, 0);
|
||||||
|
} else if(event->type == InputTypeRelease) {
|
||||||
|
model->right_pressed = false;
|
||||||
|
}
|
||||||
|
} else if(event->key == InputKeyLeft) {
|
||||||
|
if(event->type == InputTypePress) {
|
||||||
|
model->left_pressed = true;
|
||||||
|
furi_hal_bt_hid_mouse_move(-MOUSE_MOVE_SHORT, 0);
|
||||||
|
} else if(event->type == InputTypeRepeat) {
|
||||||
|
furi_hal_bt_hid_mouse_move(-MOUSE_MOVE_LONG, 0);
|
||||||
|
} else if(event->type == InputTypeRelease) {
|
||||||
|
model->left_pressed = false;
|
||||||
|
}
|
||||||
|
} else if(event->key == InputKeyDown) {
|
||||||
|
if(event->type == InputTypePress) {
|
||||||
|
model->down_pressed = true;
|
||||||
|
furi_hal_bt_hid_mouse_move(0, MOUSE_MOVE_SHORT);
|
||||||
|
} else if(event->type == InputTypeRepeat) {
|
||||||
|
furi_hal_bt_hid_mouse_move(0, MOUSE_MOVE_LONG);
|
||||||
|
} else if(event->type == InputTypeRelease) {
|
||||||
|
model->down_pressed = false;
|
||||||
|
}
|
||||||
|
} else if(event->key == InputKeyUp) {
|
||||||
|
if(event->type == InputTypePress) {
|
||||||
|
model->up_pressed = true;
|
||||||
|
furi_hal_bt_hid_mouse_move(0, -MOUSE_MOVE_SHORT);
|
||||||
|
} else if(event->type == InputTypeRepeat) {
|
||||||
|
furi_hal_bt_hid_mouse_move(0, -MOUSE_MOVE_LONG);
|
||||||
|
} else if(event->type == InputTypeRelease) {
|
||||||
|
model->up_pressed = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool bt_hid_mouse_input_callback(InputEvent* event, void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
BtHidMouse* bt_hid_mouse = context;
|
||||||
|
bool consumed = false;
|
||||||
|
|
||||||
|
if(event->type == InputTypeLong && event->key == InputKeyBack) {
|
||||||
|
furi_hal_bt_hid_mouse_release_all();
|
||||||
|
} else {
|
||||||
|
bt_hid_mouse_process(bt_hid_mouse, event);
|
||||||
|
consumed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return consumed;
|
||||||
|
}
|
||||||
|
|
||||||
|
BtHidMouse* bt_hid_mouse_alloc() {
|
||||||
|
BtHidMouse* bt_hid_mouse = malloc(sizeof(BtHidMouse));
|
||||||
|
bt_hid_mouse->view = view_alloc();
|
||||||
|
view_set_context(bt_hid_mouse->view, bt_hid_mouse);
|
||||||
|
view_allocate_model(bt_hid_mouse->view, ViewModelTypeLocking, sizeof(BtHidMouseModel));
|
||||||
|
view_set_draw_callback(bt_hid_mouse->view, bt_hid_mouse_draw_callback);
|
||||||
|
view_set_input_callback(bt_hid_mouse->view, bt_hid_mouse_input_callback);
|
||||||
|
|
||||||
|
return bt_hid_mouse;
|
||||||
|
}
|
||||||
|
|
||||||
|
void bt_hid_mouse_free(BtHidMouse* bt_hid_mouse) {
|
||||||
|
furi_assert(bt_hid_mouse);
|
||||||
|
view_free(bt_hid_mouse->view);
|
||||||
|
free(bt_hid_mouse);
|
||||||
|
}
|
||||||
|
|
||||||
|
View* bt_hid_mouse_get_view(BtHidMouse* bt_hid_mouse) {
|
||||||
|
furi_assert(bt_hid_mouse);
|
||||||
|
return bt_hid_mouse->view;
|
||||||
|
}
|
||||||
|
|
||||||
|
void bt_hid_mouse_set_connected_status(BtHidMouse* bt_hid_mouse, bool connected) {
|
||||||
|
furi_assert(bt_hid_mouse);
|
||||||
|
with_view_model(
|
||||||
|
bt_hid_mouse->view, (BtHidMouseModel * model) {
|
||||||
|
model->connected = connected;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
13
applications/bt/bt_hid_app/views/bt_hid_mouse.h
Normal file
13
applications/bt/bt_hid_app/views/bt_hid_mouse.h
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <gui/view.h>
|
||||||
|
|
||||||
|
typedef struct BtHidMouse BtHidMouse;
|
||||||
|
|
||||||
|
BtHidMouse* bt_hid_mouse_alloc();
|
||||||
|
|
||||||
|
void bt_hid_mouse_free(BtHidMouse* bt_hid_mouse);
|
||||||
|
|
||||||
|
View* bt_hid_mouse_get_view(BtHidMouse* bt_hid_mouse);
|
||||||
|
|
||||||
|
void bt_hid_mouse_set_connected_status(BtHidMouse* bt_hid_mouse, bool connected);
|
||||||
@ -229,18 +229,22 @@ static void cli_handle_enter(Cli* cli) {
|
|||||||
|
|
||||||
// Search for command
|
// Search for command
|
||||||
furi_check(osMutexAcquire(cli->mutex, osWaitForever) == osOK);
|
furi_check(osMutexAcquire(cli->mutex, osWaitForever) == osOK);
|
||||||
CliCommand* cli_command = CliCommandTree_get(cli->commands, command);
|
CliCommand* cli_command_ptr = CliCommandTree_get(cli->commands, command);
|
||||||
if(cli_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_nl(cli);
|
||||||
cli_execute_command(cli, cli_command, args);
|
cli_execute_command(cli, &cli_command, args);
|
||||||
} else {
|
} else {
|
||||||
|
furi_check(osMutexRelease(cli->mutex) == osOK);
|
||||||
cli_nl(cli);
|
cli_nl(cli);
|
||||||
printf(
|
printf(
|
||||||
"`%s` command not found, use `help` or `?` to list all available commands",
|
"`%s` command not found, use `help` or `?` to list all available commands",
|
||||||
string_get_cstr(command));
|
string_get_cstr(command));
|
||||||
cli_putc(cli, CliSymbolAsciiBell);
|
cli_putc(cli, CliSymbolAsciiBell);
|
||||||
}
|
}
|
||||||
furi_check(osMutexRelease(cli->mutex) == osOK);
|
|
||||||
|
|
||||||
cli_reset(cli);
|
cli_reset(cli);
|
||||||
cli_prompt(cli);
|
cli_prompt(cli);
|
||||||
|
|||||||
@ -199,6 +199,7 @@ static int32_t vcp_worker(void* context) {
|
|||||||
furi_hal_cdc_set_callbacks(VCP_IF_NUM, NULL, NULL);
|
furi_hal_cdc_set_callbacks(VCP_IF_NUM, NULL, NULL);
|
||||||
// Restore previous USB mode (if it was set during init)
|
// 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)) {
|
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);
|
furi_hal_usb_set_config(vcp->usb_if_prev, NULL);
|
||||||
}
|
}
|
||||||
xStreamBufferReceive(vcp->tx_stream, vcp->data_buffer, USB_CDC_PKT_LEN, 0);
|
xStreamBufferReceive(vcp->tx_stream, vcp->data_buffer, USB_CDC_PKT_LEN, 0);
|
||||||
|
|||||||
@ -12,6 +12,7 @@
|
|||||||
struct Slideshow {
|
struct Slideshow {
|
||||||
Icon icon;
|
Icon icon;
|
||||||
uint32_t current_frame;
|
uint32_t current_frame;
|
||||||
|
bool loaded;
|
||||||
};
|
};
|
||||||
|
|
||||||
#pragma pack(push, 1)
|
#pragma pack(push, 1)
|
||||||
@ -34,6 +35,7 @@ _Static_assert(sizeof(SlideshowFrameHeader) == 2, "Incorrect SlideshowFrameHeade
|
|||||||
|
|
||||||
Slideshow* slideshow_alloc() {
|
Slideshow* slideshow_alloc() {
|
||||||
Slideshow* ret = malloc(sizeof(Slideshow));
|
Slideshow* ret = malloc(sizeof(Slideshow));
|
||||||
|
ret->loaded = false;
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,7 +54,7 @@ void slideshow_free(Slideshow* slideshow) {
|
|||||||
bool slideshow_load(Slideshow* slideshow, const char* fspath) {
|
bool slideshow_load(Slideshow* slideshow, const char* fspath) {
|
||||||
Storage* storage = furi_record_open("storage");
|
Storage* storage = furi_record_open("storage");
|
||||||
File* slideshow_file = storage_file_alloc(storage);
|
File* slideshow_file = storage_file_alloc(storage);
|
||||||
bool load_success = false;
|
slideshow->loaded = false;
|
||||||
do {
|
do {
|
||||||
if(!storage_file_open(slideshow_file, fspath, FSAM_READ, FSOM_OPEN_EXISTING)) {
|
if(!storage_file_open(slideshow_file, fspath, FSAM_READ, FSOM_OPEN_EXISTING)) {
|
||||||
break;
|
break;
|
||||||
@ -80,12 +82,16 @@ bool slideshow_load(Slideshow* slideshow, const char* fspath) {
|
|||||||
frame_header.size) {
|
frame_header.size) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
load_success = (frame_idx + 1) == header.frame_count;
|
slideshow->loaded = (frame_idx + 1) == header.frame_count;
|
||||||
}
|
}
|
||||||
} while(false);
|
} while(false);
|
||||||
storage_file_free(slideshow_file);
|
storage_file_free(slideshow_file);
|
||||||
furi_record_close("storage");
|
furi_record_close("storage");
|
||||||
return load_success;
|
return slideshow->loaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool slideshow_is_loaded(Slideshow* slideshow) {
|
||||||
|
return slideshow->loaded;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool slideshow_advance(Slideshow* slideshow) {
|
bool slideshow_advance(Slideshow* slideshow) {
|
||||||
|
|||||||
@ -8,6 +8,7 @@ Slideshow* slideshow_alloc();
|
|||||||
|
|
||||||
void slideshow_free(Slideshow* slideshow);
|
void slideshow_free(Slideshow* slideshow);
|
||||||
bool slideshow_load(Slideshow* slideshow, const char* fspath);
|
bool slideshow_load(Slideshow* slideshow, const char* fspath);
|
||||||
|
bool slideshow_is_loaded(Slideshow* slideshow);
|
||||||
void slideshow_goback(Slideshow* slideshow);
|
void slideshow_goback(Slideshow* slideshow);
|
||||||
bool slideshow_advance(Slideshow* slideshow);
|
bool slideshow_advance(Slideshow* slideshow);
|
||||||
void slideshow_draw(Slideshow* slideshow, Canvas* canvas, uint8_t x, uint8_t y);
|
void slideshow_draw(Slideshow* slideshow, Canvas* canvas, uint8_t x, uint8_t y);
|
||||||
|
|||||||
@ -158,7 +158,7 @@ static bool desktop_view_locked_input(InputEvent* event, void* context) {
|
|||||||
const bool pin_locked = model->pin_locked;
|
const bool pin_locked = model->pin_locked;
|
||||||
view_commit_model(locked_view->view, is_changed);
|
view_commit_model(locked_view->view, is_changed);
|
||||||
|
|
||||||
if(view_state == DesktopViewLockedStateUnlocked || event->type != InputTypeShort) {
|
if(view_state == DesktopViewLockedStateUnlocked) {
|
||||||
return view_state != DesktopViewLockedStateUnlocked;
|
return view_state != DesktopViewLockedStateUnlocked;
|
||||||
} else if(view_state == DesktopViewLockedStateLocked && pin_locked) {
|
} else if(view_state == DesktopViewLockedStateLocked && pin_locked) {
|
||||||
locked_view->callback(DesktopLockedEventShowPinInput, locked_view->context);
|
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);
|
desktop_view_locked_update_hint_icon_timeout(locked_view);
|
||||||
|
|
||||||
if(event->key == InputKeyBack) {
|
if(event->key == InputKeyBack) {
|
||||||
locked_view->lock_lastpress = press_time;
|
if(event->type == InputTypeShort) {
|
||||||
locked_view->lock_count++;
|
locked_view->lock_lastpress = press_time;
|
||||||
if(locked_view->lock_count == UNLOCK_CNT) {
|
locked_view->lock_count++;
|
||||||
locked_view->callback(DesktopLockedEventUnlocked, locked_view->context);
|
if(locked_view->lock_count == UNLOCK_CNT) {
|
||||||
|
locked_view->callback(DesktopLockedEventUnlocked, locked_view->context);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
locked_view->lock_count = 0;
|
locked_view->lock_count = 0;
|
||||||
|
|||||||
@ -21,7 +21,9 @@ static void desktop_view_slideshow_draw(Canvas* canvas, void* model) {
|
|||||||
DesktopSlideshowViewModel* m = model;
|
DesktopSlideshowViewModel* m = model;
|
||||||
|
|
||||||
canvas_clear(canvas);
|
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) {
|
static bool desktop_view_slideshow_input(InputEvent* event, void* context) {
|
||||||
|
|||||||
@ -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) {
|
static void usb_uart_vcp_init(UsbUartBridge* usb_uart, uint8_t vcp_ch) {
|
||||||
furi_hal_usb_unlock();
|
furi_hal_usb_unlock();
|
||||||
FURI_LOG_I("", "Init %d", vcp_ch);
|
|
||||||
if(vcp_ch == 0) {
|
if(vcp_ch == 0) {
|
||||||
Cli* cli = furi_record_open("cli");
|
Cli* cli = furi_record_open("cli");
|
||||||
cli_session_close(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) {
|
static void usb_uart_vcp_deinit(UsbUartBridge* usb_uart, uint8_t vcp_ch) {
|
||||||
UNUSED(usb_uart);
|
UNUSED(usb_uart);
|
||||||
furi_hal_cdc_set_callbacks(vcp_ch, NULL, NULL);
|
furi_hal_cdc_set_callbacks(vcp_ch, NULL, NULL);
|
||||||
FURI_LOG_I("", "Deinit %d", vcp_ch);
|
|
||||||
if(vcp_ch != 0) {
|
if(vcp_ch != 0) {
|
||||||
Cli* cli = furi_record_open("cli");
|
Cli* cli = furi_record_open("cli");
|
||||||
cli_session_close(cli);
|
cli_session_close(cli);
|
||||||
|
|||||||
@ -5,6 +5,9 @@
|
|||||||
#include "m-string.h"
|
#include "m-string.h"
|
||||||
#include <toolbox/path.h>
|
#include <toolbox/path.h>
|
||||||
#include <flipper_format/flipper_format.h>
|
#include <flipper_format/flipper_format.h>
|
||||||
|
#include "rpc/rpc_app.h"
|
||||||
|
|
||||||
|
#define TAG "iButtonApp"
|
||||||
|
|
||||||
static const NotificationSequence sequence_blink_start_cyan = {
|
static const NotificationSequence sequence_blink_start_cyan = {
|
||||||
&message_blink_start_10,
|
&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);
|
FlipperFormat* file = flipper_format_file_alloc(ibutton->storage);
|
||||||
bool result = false;
|
bool result = false;
|
||||||
string_t data;
|
string_t data;
|
||||||
@ -89,13 +92,40 @@ static bool ibutton_load_key_data(iButton* ibutton, string_t key_path) {
|
|||||||
flipper_format_free(file);
|
flipper_format_free(file);
|
||||||
string_clear(data);
|
string_clear(data);
|
||||||
|
|
||||||
if(!result) {
|
if((!result) && (show_dialog)) {
|
||||||
dialog_message_show_storage_error(ibutton->dialogs, "Cannot load\nkey file");
|
dialog_message_show_storage_error(ibutton->dialogs, "Cannot load\nkey file");
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
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) {
|
bool ibutton_custom_event_callback(void* context, uint32_t event) {
|
||||||
furi_assert(context);
|
furi_assert(context);
|
||||||
iButton* ibutton = context;
|
iButton* ibutton = context;
|
||||||
@ -226,7 +256,7 @@ bool ibutton_file_select(iButton* ibutton) {
|
|||||||
true);
|
true);
|
||||||
|
|
||||||
if(success) {
|
if(success) {
|
||||||
success = ibutton_load_key_data(ibutton, ibutton->file_path);
|
success = ibutton_load_key_data(ibutton, ibutton->file_path, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
return success;
|
return success;
|
||||||
@ -334,16 +364,27 @@ int32_t ibutton_app(void* p) {
|
|||||||
ibutton_make_app_folder(ibutton);
|
ibutton_make_app_folder(ibutton);
|
||||||
|
|
||||||
bool key_loaded = false;
|
bool key_loaded = false;
|
||||||
|
bool rpc_mode = false;
|
||||||
|
|
||||||
if(p) {
|
if(p) {
|
||||||
string_set_str(ibutton->file_path, (const char*)p);
|
uint32_t rpc_ctx = 0;
|
||||||
if(ibutton_load_key_data(ibutton, ibutton->file_path)) {
|
if(sscanf(p, "RPC %lX", &rpc_ctx) == 1) {
|
||||||
key_loaded = true;
|
FURI_LOG_D(TAG, "Running in RPC mode");
|
||||||
// TODO: Display an error if the key from p could not be loaded
|
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);
|
scene_manager_next_scene(ibutton->scene_manager, iButtonSceneEmulate);
|
||||||
} else {
|
} else {
|
||||||
scene_manager_next_scene(ibutton->scene_manager, iButtonSceneStart);
|
scene_manager_next_scene(ibutton->scene_manager, iButtonSceneStart);
|
||||||
@ -351,6 +392,9 @@ int32_t ibutton_app(void* p) {
|
|||||||
|
|
||||||
view_dispatcher_run(ibutton->view_dispatcher);
|
view_dispatcher_run(ibutton->view_dispatcher);
|
||||||
|
|
||||||
|
if(ibutton->rpc_ctx) {
|
||||||
|
rpc_system_app_set_callback(ibutton->rpc_ctx, NULL, NULL);
|
||||||
|
}
|
||||||
ibutton_free(ibutton);
|
ibutton_free(ibutton);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,4 +9,6 @@ enum iButtonCustomEvent {
|
|||||||
iButtonCustomEventByteEditResult,
|
iButtonCustomEventByteEditResult,
|
||||||
iButtonCustomEventWorkerEmulated,
|
iButtonCustomEventWorkerEmulated,
|
||||||
iButtonCustomEventWorkerRead,
|
iButtonCustomEventWorkerRead,
|
||||||
|
|
||||||
|
iButtonCustomEventRpcExit,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -50,6 +50,8 @@ struct iButton {
|
|||||||
Popup* popup;
|
Popup* popup;
|
||||||
Widget* widget;
|
Widget* widget;
|
||||||
DialogEx* dialog_ex;
|
DialogEx* dialog_ex;
|
||||||
|
|
||||||
|
void* rpc_ctx;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
|
|||||||
@ -18,3 +18,4 @@ ADD_SCENE(ibutton, delete_confirm, DeleteConfirm)
|
|||||||
ADD_SCENE(ibutton, delete_success, DeleteSuccess)
|
ADD_SCENE(ibutton, delete_success, DeleteSuccess)
|
||||||
ADD_SCENE(ibutton, retry_confirm, RetryConfirm)
|
ADD_SCENE(ibutton, retry_confirm, RetryConfirm)
|
||||||
ADD_SCENE(ibutton, exit_confirm, ExitConfirm)
|
ADD_SCENE(ibutton, exit_confirm, ExitConfirm)
|
||||||
|
ADD_SCENE(ibutton, rpc, Rpc)
|
||||||
|
|||||||
@ -3,6 +3,8 @@
|
|||||||
#include <dolphin/dolphin.h>
|
#include <dolphin/dolphin.h>
|
||||||
#include <toolbox/path.h>
|
#include <toolbox/path.h>
|
||||||
|
|
||||||
|
#define EMULATE_TIMEOUT_TICKS 10
|
||||||
|
|
||||||
static void ibutton_scene_emulate_callback(void* context, bool emulated) {
|
static void ibutton_scene_emulate_callback(void* context, bool emulated) {
|
||||||
iButton* ibutton = context;
|
iButton* ibutton = context;
|
||||||
if(emulated) {
|
if(emulated) {
|
||||||
@ -95,11 +97,23 @@ bool ibutton_scene_emulate_on_event(void* context, SceneManagerEvent event) {
|
|||||||
bool consumed = false;
|
bool consumed = false;
|
||||||
|
|
||||||
if(event.type == SceneManagerEventTypeTick) {
|
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;
|
consumed = true;
|
||||||
} else if(event.type == SceneManagerEventTypeCustom) {
|
} else if(event.type == SceneManagerEventTypeCustom) {
|
||||||
consumed = true;
|
consumed = true;
|
||||||
if(event.event == iButtonCustomEventWorkerEmulated) {
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
36
applications/ibutton/scenes/ibutton_scene_rpc.c
Normal file
36
applications/ibutton/scenes/ibutton_scene_rpc.c
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
#include "../ibutton_i.h"
|
||||||
|
#include <toolbox/path.h>
|
||||||
|
|
||||||
|
void ibutton_scene_rpc_on_enter(void* context) {
|
||||||
|
iButton* ibutton = context;
|
||||||
|
Widget* widget = ibutton->widget;
|
||||||
|
|
||||||
|
widget_add_text_box_element(
|
||||||
|
widget, 0, 0, 128, 28, AlignCenter, AlignCenter, "RPC mode", false);
|
||||||
|
|
||||||
|
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget);
|
||||||
|
|
||||||
|
notification_message(ibutton->notifications, &sequence_display_backlight_on);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ibutton_scene_rpc_on_event(void* context, SceneManagerEvent event) {
|
||||||
|
UNUSED(context);
|
||||||
|
UNUSED(event);
|
||||||
|
iButton* ibutton = context;
|
||||||
|
|
||||||
|
bool consumed = false;
|
||||||
|
|
||||||
|
if(event.type == SceneManagerEventTypeCustom) {
|
||||||
|
consumed = true;
|
||||||
|
if(event.event == iButtonCustomEventRpcExit) {
|
||||||
|
view_dispatcher_stop(ibutton->view_dispatcher);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return consumed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ibutton_scene_rpc_on_exit(void* context) {
|
||||||
|
iButton* ibutton = context;
|
||||||
|
widget_reset(ibutton->widget);
|
||||||
|
}
|
||||||
@ -36,6 +36,52 @@ static void infrared_tick_event_callback(void* context) {
|
|||||||
scene_manager_handle_tick_event(infrared->scene_manager);
|
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) {
|
static void infrared_find_vacant_remote_name(string_t name, const char* path) {
|
||||||
Storage* storage = furi_record_open("storage");
|
Storage* storage = furi_record_open("storage");
|
||||||
|
|
||||||
@ -154,6 +200,11 @@ static void infrared_free(Infrared* infrared) {
|
|||||||
ViewDispatcher* view_dispatcher = infrared->view_dispatcher;
|
ViewDispatcher* view_dispatcher = infrared->view_dispatcher;
|
||||||
InfraredAppState* app_state = &infrared->app_state;
|
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);
|
view_dispatcher_remove_view(view_dispatcher, InfraredViewSubmenu);
|
||||||
submenu_free(infrared->submenu);
|
submenu_free(infrared->submenu);
|
||||||
|
|
||||||
@ -375,18 +426,29 @@ int32_t infrared_app(void* p) {
|
|||||||
infrared_make_app_folder(infrared);
|
infrared_make_app_folder(infrared);
|
||||||
|
|
||||||
bool is_remote_loaded = false;
|
bool is_remote_loaded = false;
|
||||||
|
bool is_rpc_mode = false;
|
||||||
|
|
||||||
if(p) {
|
if(p) {
|
||||||
string_set_str(infrared->file_path, (const char*)p);
|
uint32_t rpc_ctx = 0;
|
||||||
is_remote_loaded = infrared_remote_load(infrared->remote, infrared->file_path);
|
if(sscanf(p, "RPC %lX", &rpc_ctx) == 1) {
|
||||||
if(!is_remote_loaded) {
|
infrared->rpc_ctx = (void*)rpc_ctx;
|
||||||
dialog_message_show_storage_error(
|
rpc_system_app_set_callback(
|
||||||
infrared->dialogs, "Failed to load\nselected remote");
|
infrared->rpc_ctx, infrared_rpc_command_callback, infrared);
|
||||||
return -1;
|
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);
|
scene_manager_next_scene(infrared->scene_manager, InfraredSceneRemote);
|
||||||
} else {
|
} else {
|
||||||
scene_manager_next_scene(infrared->scene_manager, InfraredSceneStart);
|
scene_manager_next_scene(infrared->scene_manager, InfraredSceneStart);
|
||||||
|
|||||||
@ -30,6 +30,8 @@
|
|||||||
#include "views/infrared_progress_view.h"
|
#include "views/infrared_progress_view.h"
|
||||||
#include "views/infrared_debug_view.h"
|
#include "views/infrared_debug_view.h"
|
||||||
|
|
||||||
|
#include "rpc/rpc_app.h"
|
||||||
|
|
||||||
#define INFRARED_FILE_NAME_SIZE 100
|
#define INFRARED_FILE_NAME_SIZE 100
|
||||||
#define INFRARED_TEXT_STORE_NUM 2
|
#define INFRARED_TEXT_STORE_NUM 2
|
||||||
#define INFRARED_TEXT_STORE_SIZE 128
|
#define INFRARED_TEXT_STORE_SIZE 128
|
||||||
@ -95,6 +97,8 @@ struct Infrared {
|
|||||||
string_t file_path;
|
string_t file_path;
|
||||||
char text_store[INFRARED_TEXT_STORE_NUM][INFRARED_TEXT_STORE_SIZE + 1];
|
char text_store[INFRARED_TEXT_STORE_NUM][INFRARED_TEXT_STORE_SIZE + 1];
|
||||||
InfraredAppState app_state;
|
InfraredAppState app_state;
|
||||||
|
|
||||||
|
void* rpc_ctx;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
#include "infrared_remote.h"
|
#include "infrared_remote.h"
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <m-string.h>
|
#include <m-string.h>
|
||||||
#include <m-array.h>
|
#include <m-array.h>
|
||||||
@ -73,6 +75,17 @@ InfraredRemoteButton* infrared_remote_get_button(InfraredRemote* remote, size_t
|
|||||||
return *InfraredButtonArray_get(remote->buttons, index);
|
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) {
|
bool infrared_remote_add_button(InfraredRemote* remote, const char* name, InfraredSignal* signal) {
|
||||||
InfraredRemoteButton* button = infrared_remote_button_alloc();
|
InfraredRemoteButton* button = infrared_remote_button_alloc();
|
||||||
infrared_remote_button_set_name(button, name);
|
infrared_remote_button_set_name(button, name);
|
||||||
|
|||||||
@ -18,6 +18,7 @@ const char* infrared_remote_get_path(InfraredRemote* remote);
|
|||||||
|
|
||||||
size_t infrared_remote_get_button_count(InfraredRemote* remote);
|
size_t infrared_remote_get_button_count(InfraredRemote* remote);
|
||||||
InfraredRemoteButton* infrared_remote_get_button(InfraredRemote* remote, size_t index);
|
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_add_button(InfraredRemote* remote, const char* name, InfraredSignal* signal);
|
||||||
bool infrared_remote_rename_button(InfraredRemote* remote, const char* new_name, size_t index);
|
bool infrared_remote_rename_button(InfraredRemote* remote, const char* new_name, size_t index);
|
||||||
|
|||||||
@ -16,3 +16,4 @@ ADD_SCENE(infrared, universal, Universal)
|
|||||||
ADD_SCENE(infrared, universal_tv, UniversalTV)
|
ADD_SCENE(infrared, universal_tv, UniversalTV)
|
||||||
ADD_SCENE(infrared, debug, Debug)
|
ADD_SCENE(infrared, debug, Debug)
|
||||||
ADD_SCENE(infrared, error_databases, ErrorDatabases)
|
ADD_SCENE(infrared, error_databases, ErrorDatabases)
|
||||||
|
ADD_SCENE(infrared, rpc, Rpc)
|
||||||
|
|||||||
37
applications/infrared/scenes/infrared_scene_rpc.c
Normal file
37
applications/infrared/scenes/infrared_scene_rpc.c
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
#include "../infrared_i.h"
|
||||||
|
#include "gui/canvas.h"
|
||||||
|
|
||||||
|
void infrared_scene_rpc_on_enter(void* context) {
|
||||||
|
Infrared* infrared = context;
|
||||||
|
Popup* popup = infrared->popup;
|
||||||
|
|
||||||
|
popup_set_text(popup, "Rpc mode", 64, 28, AlignCenter, AlignCenter);
|
||||||
|
|
||||||
|
popup_set_context(popup, context);
|
||||||
|
popup_set_callback(popup, infrared_popup_closed_callback);
|
||||||
|
|
||||||
|
infrared_play_notification_message(infrared, InfraredNotificationMessageYellowOn);
|
||||||
|
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewPopup);
|
||||||
|
|
||||||
|
notification_message(infrared->notifications, &sequence_display_backlight_on);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool infrared_scene_rpc_on_event(void* context, SceneManagerEvent event) {
|
||||||
|
Infrared* infrared = context;
|
||||||
|
bool consumed = false;
|
||||||
|
|
||||||
|
if(event.type == SceneManagerEventTypeCustom) {
|
||||||
|
consumed = true;
|
||||||
|
if(event.event == InfraredCustomEventTypeBackPressed) {
|
||||||
|
view_dispatcher_stop(infrared->view_dispatcher);
|
||||||
|
} else if(event.event == InfraredCustomEventTypePopupClosed) {
|
||||||
|
view_dispatcher_stop(infrared->view_dispatcher);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return consumed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void infrared_scene_rpc_on_exit(void* context) {
|
||||||
|
Infrared* infrared = context;
|
||||||
|
popup_reset(infrared->popup);
|
||||||
|
}
|
||||||
@ -20,10 +20,13 @@
|
|||||||
#include "scene/lfrfid_app_scene_saved_info.h"
|
#include "scene/lfrfid_app_scene_saved_info.h"
|
||||||
#include "scene/lfrfid_app_scene_delete_confirm.h"
|
#include "scene/lfrfid_app_scene_delete_confirm.h"
|
||||||
#include "scene/lfrfid_app_scene_delete_success.h"
|
#include "scene/lfrfid_app_scene_delete_success.h"
|
||||||
|
#include "scene/lfrfid_app_scene_rpc.h"
|
||||||
|
|
||||||
#include <toolbox/path.h>
|
#include <toolbox/path.h>
|
||||||
#include <flipper_format/flipper_format.h>
|
#include <flipper_format/flipper_format.h>
|
||||||
|
|
||||||
|
#include "rpc/rpc_app.h"
|
||||||
|
|
||||||
const char* LfRfidApp::app_folder = "/any/lfrfid";
|
const char* LfRfidApp::app_folder = "/any/lfrfid";
|
||||||
const char* LfRfidApp::app_extension = ".rfid";
|
const char* LfRfidApp::app_extension = ".rfid";
|
||||||
const char* LfRfidApp::app_filetype = "Flipper RFID key";
|
const char* LfRfidApp::app_filetype = "Flipper RFID key";
|
||||||
@ -39,6 +42,43 @@ LfRfidApp::LfRfidApp()
|
|||||||
|
|
||||||
LfRfidApp::~LfRfidApp() {
|
LfRfidApp::~LfRfidApp() {
|
||||||
string_clear(file_path);
|
string_clear(file_path);
|
||||||
|
if(rpc_ctx) {
|
||||||
|
rpc_system_app_set_callback(rpc_ctx, NULL, NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool rpc_command_callback(RpcAppSystemEvent event, const char* arg, void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
LfRfidApp* app = static_cast<LfRfidApp*>(context);
|
||||||
|
|
||||||
|
bool result = false;
|
||||||
|
|
||||||
|
if(event == RpcAppEventSessionClose) {
|
||||||
|
rpc_system_app_set_callback(app->rpc_ctx, NULL, NULL);
|
||||||
|
app->rpc_ctx = NULL;
|
||||||
|
LfRfidApp::Event event;
|
||||||
|
event.type = LfRfidApp::EventType::Exit;
|
||||||
|
app->view_controller.send_event(&event);
|
||||||
|
result = true;
|
||||||
|
} else if(event == RpcAppEventAppExit) {
|
||||||
|
LfRfidApp::Event event;
|
||||||
|
event.type = LfRfidApp::EventType::Exit;
|
||||||
|
app->view_controller.send_event(&event);
|
||||||
|
result = true;
|
||||||
|
} else if(event == RpcAppEventLoadFile) {
|
||||||
|
if(arg) {
|
||||||
|
string_set_str(app->file_path, arg);
|
||||||
|
if(app->load_key_data(app->file_path, &(app->worker.key), false)) {
|
||||||
|
LfRfidApp::Event event;
|
||||||
|
event.type = LfRfidApp::EventType::EmulateStart;
|
||||||
|
app->view_controller.send_event(&event);
|
||||||
|
app->worker.start_emulate();
|
||||||
|
result = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void LfRfidApp::run(void* _args) {
|
void LfRfidApp::run(void* _args) {
|
||||||
@ -47,10 +87,19 @@ void LfRfidApp::run(void* _args) {
|
|||||||
make_app_folder();
|
make_app_folder();
|
||||||
|
|
||||||
if(strlen(args)) {
|
if(strlen(args)) {
|
||||||
string_set_str(file_path, args);
|
uint32_t rpc_ctx_ptr = 0;
|
||||||
load_key_data(file_path, &worker.key);
|
if(sscanf(args, "RPC %lX", &rpc_ctx_ptr) == 1) {
|
||||||
scene_controller.add_scene(SceneType::Emulate, new LfRfidAppSceneEmulate());
|
rpc_ctx = (RpcAppSystem*)rpc_ctx_ptr;
|
||||||
scene_controller.process(100, SceneType::Emulate);
|
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 {
|
} else {
|
||||||
scene_controller.add_scene(SceneType::Start, new LfRfidAppSceneStart());
|
scene_controller.add_scene(SceneType::Start, new LfRfidAppSceneStart());
|
||||||
scene_controller.add_scene(SceneType::Read, new LfRfidAppSceneRead());
|
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);
|
dialogs, file_path, file_path, app_extension, true, &I_125_10px, true);
|
||||||
|
|
||||||
if(result) {
|
if(result) {
|
||||||
result = load_key_data(file_path, &worker.key);
|
result = load_key_data(file_path, &worker.key, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@ -110,7 +159,7 @@ bool LfRfidApp::delete_key(RfidKey* key) {
|
|||||||
return storage_simply_remove(storage, string_get_cstr(file_path));
|
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);
|
FlipperFormat* file = flipper_format_file_alloc(storage);
|
||||||
bool result = false;
|
bool result = false;
|
||||||
string_t str_result;
|
string_t str_result;
|
||||||
@ -149,7 +198,7 @@ bool LfRfidApp::load_key_data(string_t path, RfidKey* key) {
|
|||||||
flipper_format_free(file);
|
flipper_format_free(file);
|
||||||
string_clear(str_result);
|
string_clear(str_result);
|
||||||
|
|
||||||
if(!result) {
|
if((!result) && (show_dialog)) {
|
||||||
dialog_message_show_storage_error(dialogs, "Cannot load\nkey file");
|
dialog_message_show_storage_error(dialogs, "Cannot load\nkey file");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -21,6 +21,7 @@
|
|||||||
#include <dialogs/dialogs.h>
|
#include <dialogs/dialogs.h>
|
||||||
|
|
||||||
#include "helpers/rfid_worker.h"
|
#include "helpers/rfid_worker.h"
|
||||||
|
#include "rpc/rpc_app.h"
|
||||||
|
|
||||||
class LfRfidApp {
|
class LfRfidApp {
|
||||||
public:
|
public:
|
||||||
@ -30,6 +31,8 @@ public:
|
|||||||
MenuSelected,
|
MenuSelected,
|
||||||
Stay,
|
Stay,
|
||||||
Retry,
|
Retry,
|
||||||
|
Exit,
|
||||||
|
EmulateStart,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class SceneType : uint8_t {
|
enum class SceneType : uint8_t {
|
||||||
@ -51,6 +54,7 @@ public:
|
|||||||
SavedInfo,
|
SavedInfo,
|
||||||
DeleteConfirm,
|
DeleteConfirm,
|
||||||
DeleteSuccess,
|
DeleteSuccess,
|
||||||
|
Rpc,
|
||||||
};
|
};
|
||||||
|
|
||||||
class Event {
|
class Event {
|
||||||
@ -79,6 +83,8 @@ public:
|
|||||||
|
|
||||||
string_t file_path;
|
string_t file_path;
|
||||||
|
|
||||||
|
RpcAppSystem* rpc_ctx;
|
||||||
|
|
||||||
void run(void* args);
|
void run(void* args);
|
||||||
|
|
||||||
static const char* app_folder;
|
static const char* app_folder;
|
||||||
@ -89,8 +95,9 @@ public:
|
|||||||
bool load_key_from_file_select(bool need_restore);
|
bool load_key_from_file_select(bool need_restore);
|
||||||
bool delete_key(RfidKey* key);
|
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);
|
bool save_key_data(string_t path, RfidKey* key);
|
||||||
|
|
||||||
void make_app_folder();
|
void make_app_folder();
|
||||||
|
//bool rpc_command_callback(RpcAppSystemEvent event, const char* arg, void* context);
|
||||||
};
|
};
|
||||||
|
|||||||
37
applications/lfrfid/scene/lfrfid_app_scene_rpc.cpp
Normal file
37
applications/lfrfid/scene/lfrfid_app_scene_rpc.cpp
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
#include "lfrfid_app_scene_rpc.h"
|
||||||
|
#include "furi/common_defines.h"
|
||||||
|
#include <dolphin/dolphin.h>
|
||||||
|
|
||||||
|
void LfRfidAppSceneRpc::on_enter(LfRfidApp* app, bool /* need_restore */) {
|
||||||
|
auto popup = app->view_controller.get<PopupVM>();
|
||||||
|
|
||||||
|
popup->set_header("RPC Mode", 64, 30, AlignCenter, AlignTop);
|
||||||
|
|
||||||
|
app->view_controller.switch_to<PopupVM>();
|
||||||
|
|
||||||
|
notification_message(app->notification, &sequence_display_backlight_on);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LfRfidAppSceneRpc::on_event(LfRfidApp* app, LfRfidApp::Event* event) {
|
||||||
|
UNUSED(app);
|
||||||
|
UNUSED(event);
|
||||||
|
bool consumed = false;
|
||||||
|
|
||||||
|
if(event->type == LfRfidApp::EventType::Exit) {
|
||||||
|
consumed = true;
|
||||||
|
LfRfidApp::Event view_event;
|
||||||
|
view_event.type = LfRfidApp::EventType::Back;
|
||||||
|
app->view_controller.send_event(&view_event);
|
||||||
|
} else if(event->type == LfRfidApp::EventType::EmulateStart) {
|
||||||
|
consumed = true;
|
||||||
|
emulating = true;
|
||||||
|
}
|
||||||
|
return consumed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LfRfidAppSceneRpc::on_exit(LfRfidApp* app) {
|
||||||
|
if(emulating) {
|
||||||
|
app->worker.stop_emulate();
|
||||||
|
}
|
||||||
|
app->view_controller.get<PopupVM>()->clean();
|
||||||
|
}
|
||||||
12
applications/lfrfid/scene/lfrfid_app_scene_rpc.h
Normal file
12
applications/lfrfid/scene/lfrfid_app_scene_rpc.h
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "../lfrfid_app.h"
|
||||||
|
|
||||||
|
class LfRfidAppSceneRpc : public GenericScene<LfRfidApp> {
|
||||||
|
public:
|
||||||
|
void on_enter(LfRfidApp* app, bool need_restore) final;
|
||||||
|
bool on_event(LfRfidApp* app, LfRfidApp::Event* event) final;
|
||||||
|
void on_exit(LfRfidApp* app) final;
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool emulating = false;
|
||||||
|
};
|
||||||
327
applications/nfc/helpers/nfc_generators.c
Normal file
327
applications/nfc/helpers/nfc_generators.c
Normal file
@ -0,0 +1,327 @@
|
|||||||
|
#include <furi_hal_random.h>
|
||||||
|
#include "nfc_generators.h"
|
||||||
|
|
||||||
|
#define NXP_MANUFACTURER_ID (0x04)
|
||||||
|
|
||||||
|
static const uint8_t version_bytes_mf0ulx1[] = {0x00, 0x04, 0x03, 0x00, 0x01, 0x00, 0x00, 0x03};
|
||||||
|
static const uint8_t version_bytes_ntag21x[] = {0x00, 0x04, 0x04, 0x02, 0x01, 0x00, 0x00, 0x03};
|
||||||
|
static const uint8_t version_bytes_ntag_i2c[] = {0x00, 0x04, 0x04, 0x05, 0x02, 0x00, 0x00, 0x03};
|
||||||
|
static const uint8_t default_data_ntag213[] = {0x01, 0x03, 0xA0, 0x0C, 0x34, 0x03, 0x00, 0xFE};
|
||||||
|
static const uint8_t default_data_ntag215_216[] = {0x03, 0x00, 0xFE};
|
||||||
|
static const uint8_t default_data_ntag_i2c[] = {0xE1, 0x10, 0x00, 0x00, 0x03, 0x00, 0xFE};
|
||||||
|
static const uint8_t default_config_ntag_i2c[] = {0x01, 0x00, 0xF8, 0x48, 0x08, 0x01, 0x00, 0x00};
|
||||||
|
|
||||||
|
static void nfc_generate_common_start(NfcDeviceData* data) {
|
||||||
|
nfc_device_data_clear(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void nfc_generate_mf_ul_uid(uint8_t* uid) {
|
||||||
|
uid[0] = NXP_MANUFACTURER_ID;
|
||||||
|
furi_hal_random_fill_buf(&uid[1], 6);
|
||||||
|
// I'm not sure how this is generated, but the upper nybble always seems to be 8
|
||||||
|
uid[6] &= 0x0F;
|
||||||
|
uid[6] |= 0x80;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void nfc_generate_mf_ul_common(NfcDeviceData* data) {
|
||||||
|
data->nfc_data.type = FuriHalNfcTypeA;
|
||||||
|
data->nfc_data.interface = FuriHalNfcInterfaceRf;
|
||||||
|
data->nfc_data.uid_len = 7;
|
||||||
|
nfc_generate_mf_ul_uid(data->nfc_data.uid);
|
||||||
|
data->nfc_data.atqa[0] = 0x44;
|
||||||
|
data->nfc_data.atqa[1] = 0x00;
|
||||||
|
data->nfc_data.sak = 0x00;
|
||||||
|
data->protocol = NfcDeviceProtocolMifareUl;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void nfc_generate_calc_bcc(uint8_t* uid, uint8_t* bcc0, uint8_t* bcc1) {
|
||||||
|
*bcc0 = 0x88 ^ uid[0] ^ uid[1] ^ uid[2];
|
||||||
|
*bcc1 = uid[3] ^ uid[4] ^ uid[5] ^ uid[6];
|
||||||
|
}
|
||||||
|
|
||||||
|
static void nfc_generate_mf_ul_copy_uid_with_bcc(NfcDeviceData* data) {
|
||||||
|
MfUltralightData* mful = &data->mf_ul_data;
|
||||||
|
memcpy(mful->data, data->nfc_data.uid, 3);
|
||||||
|
memcpy(&mful->data[4], &data->nfc_data.uid[3], 4);
|
||||||
|
nfc_generate_calc_bcc(data->nfc_data.uid, &mful->data[3], &mful->data[8]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void nfc_generate_mf_ul_orig(NfcDeviceData* data) {
|
||||||
|
nfc_generate_common_start(data);
|
||||||
|
nfc_generate_mf_ul_common(data);
|
||||||
|
|
||||||
|
MfUltralightData* mful = &data->mf_ul_data;
|
||||||
|
mful->type = MfUltralightTypeUnknown;
|
||||||
|
mful->data_size = 16 * 4;
|
||||||
|
nfc_generate_mf_ul_copy_uid_with_bcc(data);
|
||||||
|
// TODO: what's internal byte on page 2?
|
||||||
|
memset(&mful->data[4 * 4], 0xFF, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void nfc_generate_mf_ul_with_config_common(NfcDeviceData* data, uint8_t num_pages) {
|
||||||
|
nfc_generate_common_start(data);
|
||||||
|
nfc_generate_mf_ul_common(data);
|
||||||
|
|
||||||
|
MfUltralightData* mful = &data->mf_ul_data;
|
||||||
|
mful->data_size = num_pages * 4;
|
||||||
|
nfc_generate_mf_ul_copy_uid_with_bcc(data);
|
||||||
|
uint16_t config_index = (num_pages - 4) * 4;
|
||||||
|
mful->data[config_index] = 0x04; // STRG_MOD_EN
|
||||||
|
mful->data[config_index + 3] = 0xFF; // AUTH0
|
||||||
|
mful->data[config_index + 5] = 0x05; // VCTID
|
||||||
|
memset(&mful->data[config_index + 8], 0xFF, 4); // Default PWD
|
||||||
|
if(num_pages > 20) mful->data[config_index - 1] = MF_UL_TEARING_FLAG_DEFAULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void nfc_generate_mf_ul_ev1_common(NfcDeviceData* data, uint8_t num_pages) {
|
||||||
|
nfc_generate_mf_ul_with_config_common(data, num_pages);
|
||||||
|
MfUltralightData* mful = &data->mf_ul_data;
|
||||||
|
memcpy(&mful->version, version_bytes_mf0ulx1, sizeof(version_bytes_mf0ulx1));
|
||||||
|
for(size_t i = 0; i < 3; ++i) {
|
||||||
|
mful->tearing[i] = MF_UL_TEARING_FLAG_DEFAULT;
|
||||||
|
}
|
||||||
|
// TODO: what's internal byte on page 2?
|
||||||
|
}
|
||||||
|
|
||||||
|
static void nfc_generate_mf_ul_11(NfcDeviceData* data) {
|
||||||
|
nfc_generate_mf_ul_ev1_common(data, 20);
|
||||||
|
MfUltralightData* mful = &data->mf_ul_data;
|
||||||
|
mful->type = MfUltralightTypeUL11;
|
||||||
|
mful->version.prod_subtype = 0x01;
|
||||||
|
mful->version.storage_size = 0x0B;
|
||||||
|
mful->data[16 * 4] = 0x00; // Low capacitance version does not have STRG_MOD_EN
|
||||||
|
}
|
||||||
|
|
||||||
|
static void nfc_generate_mf_ul_h11(NfcDeviceData* data) {
|
||||||
|
nfc_generate_mf_ul_ev1_common(data, 20);
|
||||||
|
MfUltralightData* mful = &data->mf_ul_data;
|
||||||
|
mful->type = MfUltralightTypeUL11;
|
||||||
|
mful->version.prod_subtype = 0x02;
|
||||||
|
mful->version.storage_size = 0x0B;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void nfc_generate_mf_ul_21(NfcDeviceData* data) {
|
||||||
|
nfc_generate_mf_ul_ev1_common(data, 41);
|
||||||
|
MfUltralightData* mful = &data->mf_ul_data;
|
||||||
|
mful->type = MfUltralightTypeUL21;
|
||||||
|
mful->version.prod_subtype = 0x01;
|
||||||
|
mful->version.storage_size = 0x0E;
|
||||||
|
mful->data[37 * 4] = 0x00; // Low capacitance version does not have STRG_MOD_EN
|
||||||
|
}
|
||||||
|
|
||||||
|
static void nfc_generate_mf_ul_h21(NfcDeviceData* data) {
|
||||||
|
nfc_generate_mf_ul_ev1_common(data, 41);
|
||||||
|
MfUltralightData* mful = &data->mf_ul_data;
|
||||||
|
mful->type = MfUltralightTypeUL21;
|
||||||
|
mful->version.prod_subtype = 0x02;
|
||||||
|
mful->version.storage_size = 0x0E;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void nfc_generate_ntag21x_common(NfcDeviceData* data, uint8_t num_pages) {
|
||||||
|
nfc_generate_mf_ul_with_config_common(data, num_pages);
|
||||||
|
MfUltralightData* mful = &data->mf_ul_data;
|
||||||
|
memcpy(&mful->version, version_bytes_ntag21x, sizeof(version_bytes_mf0ulx1));
|
||||||
|
mful->data[9] = 0x48; // Internal byte
|
||||||
|
// Capability container
|
||||||
|
mful->data[12] = 0xE1;
|
||||||
|
mful->data[13] = 0x10;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void nfc_generate_ntag213(NfcDeviceData* data) {
|
||||||
|
nfc_generate_ntag21x_common(data, 45);
|
||||||
|
MfUltralightData* mful = &data->mf_ul_data;
|
||||||
|
mful->type = MfUltralightTypeNTAG213;
|
||||||
|
mful->version.storage_size = 0x0F;
|
||||||
|
mful->data[14] = 0x12;
|
||||||
|
// Default contents
|
||||||
|
memcpy(&mful->data[16], default_data_ntag213, sizeof(default_data_ntag213));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void nfc_generate_ntag215(NfcDeviceData* data) {
|
||||||
|
nfc_generate_ntag21x_common(data, 135);
|
||||||
|
MfUltralightData* mful = &data->mf_ul_data;
|
||||||
|
mful->type = MfUltralightTypeNTAG215;
|
||||||
|
mful->version.storage_size = 0x11;
|
||||||
|
mful->data[14] = 0x3E;
|
||||||
|
// Default contents
|
||||||
|
memcpy(&mful->data[16], default_data_ntag215_216, sizeof(default_data_ntag215_216));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void nfc_generate_ntag216(NfcDeviceData* data) {
|
||||||
|
nfc_generate_ntag21x_common(data, 231);
|
||||||
|
MfUltralightData* mful = &data->mf_ul_data;
|
||||||
|
mful->type = MfUltralightTypeNTAG216;
|
||||||
|
mful->version.storage_size = 0x13;
|
||||||
|
mful->data[14] = 0x6D;
|
||||||
|
// Default contents
|
||||||
|
memcpy(&mful->data[16], default_data_ntag215_216, sizeof(default_data_ntag215_216));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
nfc_generate_ntag_i2c_common(NfcDeviceData* data, MfUltralightType type, uint16_t num_pages) {
|
||||||
|
nfc_generate_common_start(data);
|
||||||
|
nfc_generate_mf_ul_common(data);
|
||||||
|
|
||||||
|
MfUltralightData* mful = &data->mf_ul_data;
|
||||||
|
mful->type = type;
|
||||||
|
memcpy(&mful->version, version_bytes_ntag_i2c, sizeof(version_bytes_ntag_i2c));
|
||||||
|
mful->data_size = num_pages * 4;
|
||||||
|
memcpy(mful->data, data->nfc_data.uid, data->nfc_data.uid_len);
|
||||||
|
mful->data[7] = data->nfc_data.sak;
|
||||||
|
mful->data[8] = data->nfc_data.atqa[0];
|
||||||
|
mful->data[9] = data->nfc_data.atqa[1];
|
||||||
|
|
||||||
|
uint16_t config_register_page;
|
||||||
|
uint16_t session_register_page;
|
||||||
|
|
||||||
|
// Sync with mifare_ultralight.c
|
||||||
|
switch(type) {
|
||||||
|
case MfUltralightTypeNTAGI2C1K:
|
||||||
|
config_register_page = 227;
|
||||||
|
session_register_page = 229;
|
||||||
|
break;
|
||||||
|
case MfUltralightTypeNTAGI2C2K:
|
||||||
|
config_register_page = 481;
|
||||||
|
session_register_page = 483;
|
||||||
|
break;
|
||||||
|
case MfUltralightTypeNTAGI2CPlus1K:
|
||||||
|
case MfUltralightTypeNTAGI2CPlus2K:
|
||||||
|
config_register_page = 232;
|
||||||
|
session_register_page = 234;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
furi_assert(false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(
|
||||||
|
&mful->data[config_register_page * 4],
|
||||||
|
default_config_ntag_i2c,
|
||||||
|
sizeof(default_config_ntag_i2c));
|
||||||
|
memcpy(
|
||||||
|
&mful->data[session_register_page * 4],
|
||||||
|
default_config_ntag_i2c,
|
||||||
|
sizeof(default_config_ntag_i2c));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void nfc_generate_ntag_i2c_1k(NfcDeviceData* data) {
|
||||||
|
nfc_generate_ntag_i2c_common(data, MfUltralightTypeNTAGI2C1K, 231);
|
||||||
|
MfUltralightData* mful = &data->mf_ul_data;
|
||||||
|
mful->version.prod_ver_minor = 0x01;
|
||||||
|
mful->version.storage_size = 0x13;
|
||||||
|
|
||||||
|
memcpy(&mful->data[12], default_data_ntag_i2c, sizeof(default_data_ntag_i2c));
|
||||||
|
mful->data[14] = 0x6D; // Size of tag in CC
|
||||||
|
}
|
||||||
|
|
||||||
|
static void nfc_generate_ntag_i2c_2k(NfcDeviceData* data) {
|
||||||
|
nfc_generate_ntag_i2c_common(data, MfUltralightTypeNTAGI2C2K, 485);
|
||||||
|
MfUltralightData* mful = &data->mf_ul_data;
|
||||||
|
mful->version.prod_ver_minor = 0x01;
|
||||||
|
mful->version.storage_size = 0x15;
|
||||||
|
|
||||||
|
memcpy(&mful->data[12], default_data_ntag_i2c, sizeof(default_data_ntag_i2c));
|
||||||
|
mful->data[14] = 0xEA; // Size of tag in CC
|
||||||
|
}
|
||||||
|
|
||||||
|
static void nfc_generate_ntag_i2c_plus_common(
|
||||||
|
NfcDeviceData* data,
|
||||||
|
MfUltralightType type,
|
||||||
|
uint16_t num_pages) {
|
||||||
|
nfc_generate_ntag_i2c_common(data, type, num_pages);
|
||||||
|
|
||||||
|
MfUltralightData* mful = &data->mf_ul_data;
|
||||||
|
uint16_t config_index = 227 * 4;
|
||||||
|
mful->data[config_index + 3] = 0xFF; // AUTH0
|
||||||
|
memset(&mful->data[config_index + 8], 0xFF, 4); // Default PWD
|
||||||
|
}
|
||||||
|
|
||||||
|
static void nfc_generate_ntag_i2c_plus_1k(NfcDeviceData* data) {
|
||||||
|
nfc_generate_ntag_i2c_plus_common(data, MfUltralightTypeNTAGI2CPlus1K, 236);
|
||||||
|
MfUltralightData* mful = &data->mf_ul_data;
|
||||||
|
mful->version.prod_ver_minor = 0x02;
|
||||||
|
mful->version.storage_size = 0x13;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void nfc_generate_ntag_i2c_plus_2k(NfcDeviceData* data) {
|
||||||
|
nfc_generate_ntag_i2c_plus_common(data, MfUltralightTypeNTAGI2CPlus2K, 492);
|
||||||
|
MfUltralightData* mful = &data->mf_ul_data;
|
||||||
|
mful->version.prod_ver_minor = 0x02;
|
||||||
|
mful->version.storage_size = 0x15;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const NfcGenerator mf_ul_generator = {
|
||||||
|
.name = "Mifare Ultralight",
|
||||||
|
.generator_func = nfc_generate_mf_ul_orig,
|
||||||
|
.next_scene = NfcSceneMifareUlMenu};
|
||||||
|
|
||||||
|
static const NfcGenerator mf_ul_11_generator = {
|
||||||
|
.name = "Mifare Ultralight EV1 11",
|
||||||
|
.generator_func = nfc_generate_mf_ul_11,
|
||||||
|
.next_scene = NfcSceneMifareUlMenu};
|
||||||
|
|
||||||
|
static const NfcGenerator mf_ul_h11_generator = {
|
||||||
|
.name = "Mifare Ultralight EV1 H11",
|
||||||
|
.generator_func = nfc_generate_mf_ul_h11,
|
||||||
|
.next_scene = NfcSceneMifareUlMenu};
|
||||||
|
|
||||||
|
static const NfcGenerator mf_ul_21_generator = {
|
||||||
|
.name = "Mifare Ultralight EV1 21",
|
||||||
|
.generator_func = nfc_generate_mf_ul_21,
|
||||||
|
.next_scene = NfcSceneMifareUlMenu};
|
||||||
|
|
||||||
|
static const NfcGenerator mf_ul_h21_generator = {
|
||||||
|
.name = "Mifare Ultralight EV1 H21",
|
||||||
|
.generator_func = nfc_generate_mf_ul_h21,
|
||||||
|
.next_scene = NfcSceneMifareUlMenu};
|
||||||
|
|
||||||
|
static const NfcGenerator ntag213_generator = {
|
||||||
|
.name = "NTAG213",
|
||||||
|
.generator_func = nfc_generate_ntag213,
|
||||||
|
.next_scene = NfcSceneMifareUlMenu};
|
||||||
|
|
||||||
|
static const NfcGenerator ntag215_generator = {
|
||||||
|
.name = "NTAG215",
|
||||||
|
.generator_func = nfc_generate_ntag215,
|
||||||
|
.next_scene = NfcSceneMifareUlMenu};
|
||||||
|
|
||||||
|
static const NfcGenerator ntag216_generator = {
|
||||||
|
.name = "NTAG216",
|
||||||
|
.generator_func = nfc_generate_ntag216,
|
||||||
|
.next_scene = NfcSceneMifareUlMenu};
|
||||||
|
|
||||||
|
static const NfcGenerator ntag_i2c_1k_generator = {
|
||||||
|
.name = "NTAG I2C 1k",
|
||||||
|
.generator_func = nfc_generate_ntag_i2c_1k,
|
||||||
|
.next_scene = NfcSceneMifareUlMenu};
|
||||||
|
|
||||||
|
static const NfcGenerator ntag_i2c_2k_generator = {
|
||||||
|
.name = "NTAG I2C 2k",
|
||||||
|
.generator_func = nfc_generate_ntag_i2c_2k,
|
||||||
|
.next_scene = NfcSceneMifareUlMenu};
|
||||||
|
|
||||||
|
static const NfcGenerator ntag_i2c_plus_1k_generator = {
|
||||||
|
.name = "NTAG I2C Plus 1k",
|
||||||
|
.generator_func = nfc_generate_ntag_i2c_plus_1k,
|
||||||
|
.next_scene = NfcSceneMifareUlMenu};
|
||||||
|
|
||||||
|
static const NfcGenerator ntag_i2c_plus_2k_generator = {
|
||||||
|
.name = "NTAG I2C Plus 2k",
|
||||||
|
.generator_func = nfc_generate_ntag_i2c_plus_2k,
|
||||||
|
.next_scene = NfcSceneMifareUlMenu};
|
||||||
|
|
||||||
|
const NfcGenerator* const nfc_generators[] = {
|
||||||
|
&mf_ul_generator,
|
||||||
|
&mf_ul_11_generator,
|
||||||
|
&mf_ul_h11_generator,
|
||||||
|
&mf_ul_21_generator,
|
||||||
|
&mf_ul_h21_generator,
|
||||||
|
&ntag213_generator,
|
||||||
|
&ntag215_generator,
|
||||||
|
&ntag216_generator,
|
||||||
|
&ntag_i2c_1k_generator,
|
||||||
|
&ntag_i2c_2k_generator,
|
||||||
|
&ntag_i2c_plus_1k_generator,
|
||||||
|
&ntag_i2c_plus_2k_generator,
|
||||||
|
NULL,
|
||||||
|
};
|
||||||
13
applications/nfc/helpers/nfc_generators.h
Normal file
13
applications/nfc/helpers/nfc_generators.h
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../nfc_i.h"
|
||||||
|
|
||||||
|
typedef void (*NfcGeneratorFunc)(NfcDeviceData* data);
|
||||||
|
|
||||||
|
struct NfcGenerator {
|
||||||
|
const char* name;
|
||||||
|
NfcGeneratorFunc generator_func;
|
||||||
|
NfcScene next_scene;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern const NfcGenerator* const nfc_generators[];
|
||||||
81
applications/nfc/nfc.c
Executable file → Normal file
81
applications/nfc/nfc.c
Executable file → Normal file
@ -19,6 +19,77 @@ void nfc_tick_event_callback(void* context) {
|
|||||||
scene_manager_handle_tick_event(nfc->scene_manager);
|
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_alloc() {
|
||||||
Nfc* nfc = malloc(sizeof(Nfc));
|
Nfc* nfc = malloc(sizeof(Nfc));
|
||||||
|
|
||||||
@ -84,6 +155,9 @@ Nfc* nfc_alloc() {
|
|||||||
view_dispatcher_add_view(
|
view_dispatcher_add_view(
|
||||||
nfc->view_dispatcher, NfcViewDictAttack, dict_attack_get_view(nfc->dict_attack));
|
nfc->view_dispatcher, NfcViewDictAttack, dict_attack_get_view(nfc->dict_attack));
|
||||||
|
|
||||||
|
// Generator
|
||||||
|
nfc->generator = NULL;
|
||||||
|
|
||||||
return nfc;
|
return nfc;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -190,7 +264,12 @@ int32_t nfc_app(void* p) {
|
|||||||
|
|
||||||
// Check argument and run corresponding scene
|
// Check argument and run corresponding scene
|
||||||
if((*args != '\0')) {
|
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) {
|
if(nfc->dev->format == NfcDeviceSaveFormatMifareUl) {
|
||||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateMifareUl);
|
scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateMifareUl);
|
||||||
} else if(nfc->dev->format == NfcDeviceSaveFormatMifareClassic) {
|
} else if(nfc->dev->format == NfcDeviceSaveFormatMifareClassic) {
|
||||||
|
|||||||
@ -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);
|
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;
|
bool parsed = false;
|
||||||
FlipperFormat* file = flipper_format_file_alloc(dev->storage);
|
FlipperFormat* file = flipper_format_file_alloc(dev->storage);
|
||||||
FuriHalNfcDevData* data = &dev->dev_data.nfc_data;
|
FuriHalNfcDevData* data = &dev->dev_data.nfc_data;
|
||||||
@ -887,7 +887,7 @@ static bool nfc_device_load_data(NfcDevice* dev, string_t path) {
|
|||||||
parsed = true;
|
parsed = true;
|
||||||
} while(false);
|
} while(false);
|
||||||
|
|
||||||
if(!parsed) {
|
if((!parsed) && (show_dialog)) {
|
||||||
if(deprecated_version) {
|
if(deprecated_version) {
|
||||||
dialog_message_show_storage_error(dev->dialogs, "File format deprecated");
|
dialog_message_show_storage_error(dev->dialogs, "File format deprecated");
|
||||||
} else {
|
} else {
|
||||||
@ -900,13 +900,13 @@ static bool nfc_device_load_data(NfcDevice* dev, string_t path) {
|
|||||||
return parsed;
|
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(dev);
|
||||||
furi_assert(file_path);
|
furi_assert(file_path);
|
||||||
|
|
||||||
// Load device data
|
// Load device data
|
||||||
string_set_str(dev->load_path, file_path);
|
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) {
|
if(dev_load) {
|
||||||
// Set device name
|
// Set device name
|
||||||
string_t filename;
|
string_t filename;
|
||||||
@ -933,7 +933,7 @@ bool nfc_file_select(NfcDevice* dev) {
|
|||||||
string_init(filename);
|
string_init(filename);
|
||||||
path_extract_filename(dev->load_path, filename, true);
|
path_extract_filename(dev->load_path, filename, true);
|
||||||
strncpy(dev->dev_name, string_get_cstr(filename), NFC_DEV_NAME_MAX_LEN);
|
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) {
|
if(res) {
|
||||||
nfc_device_set_name(dev, dev->dev_name);
|
nfc_device_set_name(dev, dev->dev_name);
|
||||||
}
|
}
|
||||||
@ -1017,7 +1017,7 @@ bool nfc_device_restore(NfcDevice* dev, bool use_load_path) {
|
|||||||
} else {
|
} else {
|
||||||
string_printf(path, "%s/%s%s", NFC_APP_FOLDER, dev->dev_name, NFC_APP_EXTENSION);
|
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;
|
restored = true;
|
||||||
} while(0);
|
} while(0);
|
||||||
|
|
||||||
|
|||||||
@ -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_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);
|
bool nfc_file_select(NfcDevice* dev);
|
||||||
|
|
||||||
|
|||||||
@ -29,10 +29,21 @@
|
|||||||
#include <nfc/scenes/nfc_scene.h>
|
#include <nfc/scenes/nfc_scene.h>
|
||||||
#include <nfc/helpers/nfc_custom_event.h>
|
#include <nfc/helpers/nfc_custom_event.h>
|
||||||
|
|
||||||
|
#include "rpc/rpc_app.h"
|
||||||
|
|
||||||
#define NFC_SEND_NOTIFICATION_FALSE (0UL)
|
#define NFC_SEND_NOTIFICATION_FALSE (0UL)
|
||||||
#define NFC_SEND_NOTIFICATION_TRUE (1UL)
|
#define NFC_SEND_NOTIFICATION_TRUE (1UL)
|
||||||
#define NFC_TEXT_STORE_SIZE 128
|
#define NFC_TEXT_STORE_SIZE 128
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
NfcRpcStateIdle,
|
||||||
|
NfcRpcStateEmulating,
|
||||||
|
NfcRpcStateEmulated,
|
||||||
|
} NfcRpcState;
|
||||||
|
|
||||||
|
// Forward declaration due to circular dependency
|
||||||
|
typedef struct NfcGenerator NfcGenerator;
|
||||||
|
|
||||||
struct Nfc {
|
struct Nfc {
|
||||||
NfcWorker* worker;
|
NfcWorker* worker;
|
||||||
ViewDispatcher* view_dispatcher;
|
ViewDispatcher* view_dispatcher;
|
||||||
@ -45,6 +56,9 @@ struct Nfc {
|
|||||||
char text_store[NFC_TEXT_STORE_SIZE + 1];
|
char text_store[NFC_TEXT_STORE_SIZE + 1];
|
||||||
string_t text_box_store;
|
string_t text_box_store;
|
||||||
|
|
||||||
|
void* rpc_ctx;
|
||||||
|
NfcRpcState rpc_state;
|
||||||
|
|
||||||
// Common Views
|
// Common Views
|
||||||
Submenu* submenu;
|
Submenu* submenu;
|
||||||
DialogEx* dialog_ex;
|
DialogEx* dialog_ex;
|
||||||
@ -55,6 +69,8 @@ struct Nfc {
|
|||||||
Widget* widget;
|
Widget* widget;
|
||||||
BankCard* bank_card;
|
BankCard* bank_card;
|
||||||
DictAttack* dict_attack;
|
DictAttack* dict_attack;
|
||||||
|
|
||||||
|
const NfcGenerator* generator;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
@ -80,3 +96,5 @@ void nfc_text_store_clear(Nfc* nfc);
|
|||||||
void nfc_blink_start(Nfc* nfc);
|
void nfc_blink_start(Nfc* nfc);
|
||||||
|
|
||||||
void nfc_blink_stop(Nfc* nfc);
|
void nfc_blink_stop(Nfc* nfc);
|
||||||
|
|
||||||
|
void nfc_rpc_exit_callback(Nfc* nfc);
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
#include "nfc_worker_i.h"
|
#include "nfc_worker_i.h"
|
||||||
#include <furi_hal.h>
|
#include <furi_hal.h>
|
||||||
|
|
||||||
|
#include <platform.h>
|
||||||
|
|
||||||
#define TAG "NfcWorker"
|
#define TAG "NfcWorker"
|
||||||
|
|
||||||
/***************************** NFC Worker API *******************************/
|
/***************************** NFC Worker API *******************************/
|
||||||
@ -495,9 +497,11 @@ void nfc_worker_emulate_mifare_classic(NfcWorker* nfc_worker) {
|
|||||||
NfcaSignal* nfca_signal = nfca_signal_alloc();
|
NfcaSignal* nfca_signal = nfca_signal_alloc();
|
||||||
tx_rx.nfca_signal = nfca_signal;
|
tx_rx.nfca_signal = nfca_signal;
|
||||||
|
|
||||||
|
rfal_platform_spi_acquire();
|
||||||
|
|
||||||
|
furi_hal_nfc_listen_start(nfc_data);
|
||||||
while(nfc_worker->state == NfcWorkerStateEmulateMifareClassic) {
|
while(nfc_worker->state == NfcWorkerStateEmulateMifareClassic) {
|
||||||
if(furi_hal_nfc_listen(
|
if(furi_hal_nfc_listen_rx(&tx_rx, 300)) {
|
||||||
nfc_data->uid, nfc_data->uid_len, nfc_data->atqa, nfc_data->sak, true, 300)) {
|
|
||||||
mf_classic_emulator(&emulator, &tx_rx);
|
mf_classic_emulator(&emulator, &tx_rx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -510,6 +514,8 @@ void nfc_worker_emulate_mifare_classic(NfcWorker* nfc_worker) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
nfca_signal_free(nfca_signal);
|
nfca_signal_free(nfca_signal);
|
||||||
|
|
||||||
|
rfal_platform_spi_release();
|
||||||
}
|
}
|
||||||
|
|
||||||
void nfc_worker_read_mifare_desfire(NfcWorker* nfc_worker) {
|
void nfc_worker_read_mifare_desfire(NfcWorker* nfc_worker) {
|
||||||
|
|||||||
@ -37,3 +37,5 @@ ADD_SCENE(nfc, read_mifare_classic, ReadMifareClassic)
|
|||||||
ADD_SCENE(nfc, emulate_mifare_classic, EmulateMifareClassic)
|
ADD_SCENE(nfc, emulate_mifare_classic, EmulateMifareClassic)
|
||||||
ADD_SCENE(nfc, mifare_classic_menu, MifareClassicMenu)
|
ADD_SCENE(nfc, mifare_classic_menu, MifareClassicMenu)
|
||||||
ADD_SCENE(nfc, dict_not_found, DictNotFound)
|
ADD_SCENE(nfc, dict_not_found, DictNotFound)
|
||||||
|
ADD_SCENE(nfc, rpc, Rpc)
|
||||||
|
ADD_SCENE(nfc, generate_info, GenerateInfo)
|
||||||
|
|||||||
55
applications/nfc/scenes/nfc_scene_generate_info.c
Normal file
55
applications/nfc/scenes/nfc_scene_generate_info.c
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
#include "../nfc_i.h"
|
||||||
|
#include "../helpers/nfc_generators.h"
|
||||||
|
|
||||||
|
void nfc_scene_generate_info_dialog_callback(DialogExResult result, void* context) {
|
||||||
|
Nfc* nfc = context;
|
||||||
|
|
||||||
|
view_dispatcher_send_custom_event(nfc->view_dispatcher, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
void nfc_scene_generate_info_on_enter(void* context) {
|
||||||
|
Nfc* nfc = context;
|
||||||
|
|
||||||
|
// Setup dialog view
|
||||||
|
FuriHalNfcDevData* data = &nfc->dev->dev_data.nfc_data;
|
||||||
|
DialogEx* dialog_ex = nfc->dialog_ex;
|
||||||
|
dialog_ex_set_right_button_text(dialog_ex, "More");
|
||||||
|
|
||||||
|
// Create info text
|
||||||
|
string_t info_str;
|
||||||
|
string_init_printf(
|
||||||
|
info_str, "%s\n%s\nUID:", nfc->generator->name, nfc_get_dev_type(data->type));
|
||||||
|
// Append UID
|
||||||
|
for(int i = 0; i < data->uid_len; ++i) {
|
||||||
|
string_cat_printf(info_str, " %02X", data->uid[i]);
|
||||||
|
}
|
||||||
|
nfc_text_store_set(nfc, string_get_cstr(info_str));
|
||||||
|
string_clear(info_str);
|
||||||
|
|
||||||
|
dialog_ex_set_text(dialog_ex, nfc->text_store, 0, 0, AlignLeft, AlignTop);
|
||||||
|
dialog_ex_set_context(dialog_ex, nfc);
|
||||||
|
dialog_ex_set_result_callback(dialog_ex, nfc_scene_generate_info_dialog_callback);
|
||||||
|
|
||||||
|
view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewDialogEx);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool nfc_scene_generate_info_on_event(void* context, SceneManagerEvent event) {
|
||||||
|
Nfc* nfc = context;
|
||||||
|
bool consumed = false;
|
||||||
|
|
||||||
|
if(event.type == SceneManagerEventTypeCustom) {
|
||||||
|
if(event.event == DialogExResultRight) {
|
||||||
|
scene_manager_next_scene(nfc->scene_manager, nfc->generator->next_scene);
|
||||||
|
consumed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return consumed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void nfc_scene_generate_info_on_exit(void* context) {
|
||||||
|
Nfc* nfc = context;
|
||||||
|
|
||||||
|
// Clean views
|
||||||
|
dialog_ex_reset(nfc->dialog_ex);
|
||||||
|
}
|
||||||
34
applications/nfc/scenes/nfc_scene_rpc.c
Normal file
34
applications/nfc/scenes/nfc_scene_rpc.c
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
#include "../nfc_i.h"
|
||||||
|
|
||||||
|
void nfc_scene_rpc_on_enter(void* context) {
|
||||||
|
Nfc* nfc = context;
|
||||||
|
Widget* widget = nfc->widget;
|
||||||
|
|
||||||
|
widget_add_text_box_element(
|
||||||
|
widget, 0, 0, 128, 28, AlignCenter, AlignCenter, "RPC mode", false);
|
||||||
|
|
||||||
|
notification_message(nfc->notifications, &sequence_display_backlight_on);
|
||||||
|
|
||||||
|
view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool nfc_scene_rpc_on_event(void* context, SceneManagerEvent event) {
|
||||||
|
Nfc* nfc = context;
|
||||||
|
bool consumed = false;
|
||||||
|
|
||||||
|
if(event.type == SceneManagerEventTypeCustom) {
|
||||||
|
consumed = true;
|
||||||
|
if(event.event == NfcCustomEventViewExit) {
|
||||||
|
view_dispatcher_stop(nfc->view_dispatcher);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return consumed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void nfc_scene_rpc_on_exit(void* context) {
|
||||||
|
Nfc* nfc = context;
|
||||||
|
|
||||||
|
nfc_rpc_exit_callback(nfc);
|
||||||
|
|
||||||
|
widget_reset(nfc->widget);
|
||||||
|
}
|
||||||
17
applications/nfc/scenes/nfc_scene_set_type.c
Executable file → Normal file
17
applications/nfc/scenes/nfc_scene_set_type.c
Executable file → Normal file
@ -1,9 +1,11 @@
|
|||||||
#include "../nfc_i.h"
|
#include "../nfc_i.h"
|
||||||
#include "m-string.h"
|
#include "m-string.h"
|
||||||
|
#include "../helpers/nfc_generators.h"
|
||||||
|
|
||||||
enum SubmenuIndex {
|
enum SubmenuIndex {
|
||||||
SubmenuIndexNFCA4,
|
SubmenuIndexNFCA4,
|
||||||
SubmenuIndexNFCA7,
|
SubmenuIndexNFCA7,
|
||||||
|
SubmenuIndexGeneratorsStart,
|
||||||
};
|
};
|
||||||
|
|
||||||
void nfc_scene_set_type_submenu_callback(void* context, uint32_t index) {
|
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, "NFC-A 7-bytes UID", SubmenuIndexNFCA7, nfc_scene_set_type_submenu_callback, nfc);
|
||||||
submenu_add_item(
|
submenu_add_item(
|
||||||
submenu, "NFC-A 4-bytes UID", SubmenuIndexNFCA4, nfc_scene_set_type_submenu_callback, nfc);
|
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);
|
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;
|
nfc->dev->format = NfcDeviceSaveFormatUid;
|
||||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneSetSak);
|
scene_manager_next_scene(nfc->scene_manager, NfcSceneSetSak);
|
||||||
consumed = true;
|
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;
|
return consumed;
|
||||||
|
|||||||
@ -93,6 +93,9 @@ void notification_reset_notification_led_layer(NotificationLedLayer* layer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void notification_reset_notification_layer(NotificationApp* app, uint8_t reset_mask) {
|
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) {
|
if(reset_mask & reset_red_mask) {
|
||||||
notification_reset_notification_led_layer(&app->led[0]);
|
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) {
|
if(reset_mask & reset_blue_mask) {
|
||||||
notification_reset_notification_led_layer(&app->led[2]);
|
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) {
|
if(reset_mask & reset_vibro_mask) {
|
||||||
notification_vibro_off();
|
notification_vibro_off();
|
||||||
}
|
}
|
||||||
@ -243,6 +243,9 @@ void notification_process_notification_message(
|
|||||||
notification_message->data.led_blink.on_time,
|
notification_message->data.led_blink.on_time,
|
||||||
notification_message->data.led_blink.period);
|
notification_message->data.led_blink.period);
|
||||||
reset_mask |= reset_blink_mask;
|
reset_mask |= reset_blink_mask;
|
||||||
|
reset_mask |= reset_red_mask;
|
||||||
|
reset_mask |= reset_green_mask;
|
||||||
|
reset_mask |= reset_blue_mask;
|
||||||
break;
|
break;
|
||||||
case NotificationMessageTypeLedBlinkColor:
|
case NotificationMessageTypeLedBlinkColor:
|
||||||
led_active = true;
|
led_active = true;
|
||||||
@ -251,6 +254,9 @@ void notification_process_notification_message(
|
|||||||
case NotificationMessageTypeLedBlinkStop:
|
case NotificationMessageTypeLedBlinkStop:
|
||||||
furi_hal_light_blink_stop();
|
furi_hal_light_blink_stop();
|
||||||
reset_mask &= ~reset_blink_mask;
|
reset_mask &= ~reset_blink_mask;
|
||||||
|
reset_mask |= reset_red_mask;
|
||||||
|
reset_mask |= reset_green_mask;
|
||||||
|
reset_mask |= reset_blue_mask;
|
||||||
break;
|
break;
|
||||||
case NotificationMessageTypeVibro:
|
case NotificationMessageTypeVibro:
|
||||||
if(notification_message->data.vibro.on) {
|
if(notification_message->data.vibro.on) {
|
||||||
@ -326,7 +332,7 @@ void notification_process_notification_message(
|
|||||||
reset_mask |= reset_green_mask;
|
reset_mask |= reset_green_mask;
|
||||||
reset_mask |= reset_blue_mask;
|
reset_mask |= reset_blue_mask;
|
||||||
|
|
||||||
if(need_minimal_delay) {
|
if((need_minimal_delay) && (reset_notifications)) {
|
||||||
notification_apply_notification_leds(app, led_off_values);
|
notification_apply_notification_leds(app, led_off_values);
|
||||||
furi_hal_delay_ms(minimal_delay);
|
furi_hal_delay_ms(minimal_delay);
|
||||||
}
|
}
|
||||||
|
|||||||
11
applications/picopass/application.fam
Normal file
11
applications/picopass/application.fam
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
App(
|
||||||
|
appid="picopass",
|
||||||
|
name="PicoPass Reader",
|
||||||
|
apptype=FlipperAppType.PLUGIN,
|
||||||
|
entry_point="picopass_app",
|
||||||
|
cdefines=["APP_PICOPASS"],
|
||||||
|
requires=["storage", "gui"],
|
||||||
|
stack_size=1 * 1024,
|
||||||
|
icon="A_Plugins_14",
|
||||||
|
order=30,
|
||||||
|
)
|
||||||
162
applications/picopass/picopass.c
Normal file
162
applications/picopass/picopass.c
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
#include "picopass_i.h"
|
||||||
|
|
||||||
|
#define TAG "PicoPass"
|
||||||
|
|
||||||
|
bool picopass_custom_event_callback(void* context, uint32_t event) {
|
||||||
|
furi_assert(context);
|
||||||
|
Picopass* picopass = context;
|
||||||
|
return scene_manager_handle_custom_event(picopass->scene_manager, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool picopass_back_event_callback(void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
Picopass* picopass = context;
|
||||||
|
return scene_manager_handle_back_event(picopass->scene_manager);
|
||||||
|
}
|
||||||
|
|
||||||
|
void picopass_tick_event_callback(void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
Picopass* picopass = context;
|
||||||
|
scene_manager_handle_tick_event(picopass->scene_manager);
|
||||||
|
}
|
||||||
|
|
||||||
|
Picopass* picopass_alloc() {
|
||||||
|
Picopass* picopass = malloc(sizeof(Picopass));
|
||||||
|
|
||||||
|
picopass->worker = picopass_worker_alloc();
|
||||||
|
picopass->view_dispatcher = view_dispatcher_alloc();
|
||||||
|
picopass->scene_manager = scene_manager_alloc(&picopass_scene_handlers, picopass);
|
||||||
|
view_dispatcher_enable_queue(picopass->view_dispatcher);
|
||||||
|
view_dispatcher_set_event_callback_context(picopass->view_dispatcher, picopass);
|
||||||
|
view_dispatcher_set_custom_event_callback(
|
||||||
|
picopass->view_dispatcher, picopass_custom_event_callback);
|
||||||
|
view_dispatcher_set_navigation_event_callback(
|
||||||
|
picopass->view_dispatcher, picopass_back_event_callback);
|
||||||
|
view_dispatcher_set_tick_event_callback(
|
||||||
|
picopass->view_dispatcher, picopass_tick_event_callback, 100);
|
||||||
|
|
||||||
|
// Picopass device
|
||||||
|
picopass->dev = picopass_device_alloc();
|
||||||
|
|
||||||
|
// Open GUI record
|
||||||
|
picopass->gui = furi_record_open("gui");
|
||||||
|
view_dispatcher_attach_to_gui(
|
||||||
|
picopass->view_dispatcher, picopass->gui, ViewDispatcherTypeFullscreen);
|
||||||
|
|
||||||
|
// Open Notification record
|
||||||
|
picopass->notifications = furi_record_open("notification");
|
||||||
|
|
||||||
|
// Submenu
|
||||||
|
picopass->submenu = submenu_alloc();
|
||||||
|
view_dispatcher_add_view(
|
||||||
|
picopass->view_dispatcher, PicopassViewMenu, submenu_get_view(picopass->submenu));
|
||||||
|
|
||||||
|
// Popup
|
||||||
|
picopass->popup = popup_alloc();
|
||||||
|
view_dispatcher_add_view(
|
||||||
|
picopass->view_dispatcher, PicopassViewPopup, popup_get_view(picopass->popup));
|
||||||
|
|
||||||
|
// Text Input
|
||||||
|
picopass->text_input = text_input_alloc();
|
||||||
|
view_dispatcher_add_view(
|
||||||
|
picopass->view_dispatcher,
|
||||||
|
PicopassViewTextInput,
|
||||||
|
text_input_get_view(picopass->text_input));
|
||||||
|
|
||||||
|
// Custom Widget
|
||||||
|
picopass->widget = widget_alloc();
|
||||||
|
view_dispatcher_add_view(
|
||||||
|
picopass->view_dispatcher, PicopassViewWidget, widget_get_view(picopass->widget));
|
||||||
|
|
||||||
|
return picopass;
|
||||||
|
}
|
||||||
|
|
||||||
|
void picopass_free(Picopass* picopass) {
|
||||||
|
furi_assert(picopass);
|
||||||
|
|
||||||
|
// Picopass device
|
||||||
|
picopass_device_free(picopass->dev);
|
||||||
|
picopass->dev = NULL;
|
||||||
|
|
||||||
|
// Submenu
|
||||||
|
view_dispatcher_remove_view(picopass->view_dispatcher, PicopassViewMenu);
|
||||||
|
submenu_free(picopass->submenu);
|
||||||
|
|
||||||
|
// Popup
|
||||||
|
view_dispatcher_remove_view(picopass->view_dispatcher, PicopassViewPopup);
|
||||||
|
popup_free(picopass->popup);
|
||||||
|
|
||||||
|
// TextInput
|
||||||
|
view_dispatcher_remove_view(picopass->view_dispatcher, PicopassViewTextInput);
|
||||||
|
text_input_free(picopass->text_input);
|
||||||
|
|
||||||
|
// Custom Widget
|
||||||
|
view_dispatcher_remove_view(picopass->view_dispatcher, PicopassViewWidget);
|
||||||
|
widget_free(picopass->widget);
|
||||||
|
|
||||||
|
// Worker
|
||||||
|
picopass_worker_stop(picopass->worker);
|
||||||
|
picopass_worker_free(picopass->worker);
|
||||||
|
|
||||||
|
// View Dispatcher
|
||||||
|
view_dispatcher_free(picopass->view_dispatcher);
|
||||||
|
|
||||||
|
// Scene Manager
|
||||||
|
scene_manager_free(picopass->scene_manager);
|
||||||
|
|
||||||
|
// GUI
|
||||||
|
furi_record_close("gui");
|
||||||
|
picopass->gui = NULL;
|
||||||
|
|
||||||
|
// Notifications
|
||||||
|
furi_record_close("notification");
|
||||||
|
picopass->notifications = NULL;
|
||||||
|
|
||||||
|
free(picopass);
|
||||||
|
}
|
||||||
|
|
||||||
|
void picopass_text_store_set(Picopass* picopass, const char* text, ...) {
|
||||||
|
va_list args;
|
||||||
|
va_start(args, text);
|
||||||
|
|
||||||
|
vsnprintf(picopass->text_store, sizeof(picopass->text_store), text, args);
|
||||||
|
|
||||||
|
va_end(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
void picopass_text_store_clear(Picopass* picopass) {
|
||||||
|
memset(picopass->text_store, 0, sizeof(picopass->text_store));
|
||||||
|
}
|
||||||
|
|
||||||
|
static const NotificationSequence picopass_sequence_blink_start_blue = {
|
||||||
|
&message_blink_start_10,
|
||||||
|
&message_blink_set_color_blue,
|
||||||
|
&message_do_not_reset,
|
||||||
|
NULL,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const NotificationSequence picopass_sequence_blink_stop = {
|
||||||
|
&message_blink_stop,
|
||||||
|
NULL,
|
||||||
|
};
|
||||||
|
|
||||||
|
void picopass_blink_start(Picopass* picopass) {
|
||||||
|
notification_message(picopass->notifications, &picopass_sequence_blink_start_blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
void picopass_blink_stop(Picopass* picopass) {
|
||||||
|
notification_message(picopass->notifications, &picopass_sequence_blink_stop);
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t picopass_app(void* p) {
|
||||||
|
UNUSED(p);
|
||||||
|
Picopass* picopass = picopass_alloc();
|
||||||
|
|
||||||
|
scene_manager_next_scene(picopass->scene_manager, PicopassSceneStart);
|
||||||
|
|
||||||
|
view_dispatcher_run(picopass->view_dispatcher);
|
||||||
|
|
||||||
|
picopass_free(picopass);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
3
applications/picopass/picopass.h
Normal file
3
applications/picopass/picopass.h
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
typedef struct Picopass Picopass;
|
||||||
146
applications/picopass/picopass_device.c
Normal file
146
applications/picopass/picopass_device.c
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
#include "picopass_device.h"
|
||||||
|
|
||||||
|
#include <toolbox/path.h>
|
||||||
|
#include <flipper_format/flipper_format.h>
|
||||||
|
|
||||||
|
#define TAG "PicopassDevice"
|
||||||
|
|
||||||
|
static const char* picopass_file_header = "Flipper Picopass device";
|
||||||
|
static const uint32_t picopass_file_version = 1;
|
||||||
|
|
||||||
|
PicopassDevice* picopass_device_alloc() {
|
||||||
|
PicopassDevice* picopass_dev = malloc(sizeof(PicopassDevice));
|
||||||
|
picopass_dev->storage = furi_record_open("storage");
|
||||||
|
picopass_dev->dialogs = furi_record_open("dialogs");
|
||||||
|
return picopass_dev;
|
||||||
|
}
|
||||||
|
|
||||||
|
void picopass_device_set_name(PicopassDevice* dev, const char* name) {
|
||||||
|
furi_assert(dev);
|
||||||
|
|
||||||
|
strlcpy(dev->dev_name, name, PICOPASS_DEV_NAME_MAX_LEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool picopass_device_save_file(
|
||||||
|
PicopassDevice* dev,
|
||||||
|
const char* dev_name,
|
||||||
|
const char* folder,
|
||||||
|
const char* extension,
|
||||||
|
bool use_load_path) {
|
||||||
|
furi_assert(dev);
|
||||||
|
|
||||||
|
bool saved = false;
|
||||||
|
FlipperFormat* file = flipper_format_file_alloc(dev->storage);
|
||||||
|
PicopassPacs* pacs = &dev->dev_data.pacs;
|
||||||
|
ApplicationArea* AA1 = &dev->dev_data.AA1;
|
||||||
|
string_t temp_str;
|
||||||
|
string_init(temp_str);
|
||||||
|
|
||||||
|
do {
|
||||||
|
if(use_load_path && !string_empty_p(dev->load_path)) {
|
||||||
|
// Get directory name
|
||||||
|
path_extract_dirname(string_get_cstr(dev->load_path), temp_str);
|
||||||
|
// Create picopass directory if necessary
|
||||||
|
if(!storage_simply_mkdir(dev->storage, string_get_cstr(temp_str))) break;
|
||||||
|
// Make path to file to save
|
||||||
|
string_cat_printf(temp_str, "/%s%s", dev_name, extension);
|
||||||
|
} else {
|
||||||
|
// Create picopass directory if necessary
|
||||||
|
if(!storage_simply_mkdir(dev->storage, PICOPASS_APP_FOLDER)) break;
|
||||||
|
// First remove picopass device file if it was saved
|
||||||
|
string_printf(temp_str, "%s/%s%s", folder, dev_name, extension);
|
||||||
|
}
|
||||||
|
// Open file
|
||||||
|
if(!flipper_format_file_open_always(file, string_get_cstr(temp_str))) break;
|
||||||
|
|
||||||
|
if(dev->format == PicopassDeviceSaveFormatHF) {
|
||||||
|
// Write header
|
||||||
|
if(!flipper_format_write_header_cstr(file, picopass_file_header, picopass_file_version))
|
||||||
|
break;
|
||||||
|
if(pacs->record.valid) {
|
||||||
|
if(!flipper_format_write_uint32(
|
||||||
|
file, "Facility Code", (uint32_t*)&pacs->record.FacilityCode, 1))
|
||||||
|
break;
|
||||||
|
if(!flipper_format_write_uint32(
|
||||||
|
file, "Card Number", (uint32_t*)&pacs->record.CardNumber, 1))
|
||||||
|
break;
|
||||||
|
if(!flipper_format_write_hex(
|
||||||
|
file, "Credential", pacs->credential, PICOPASS_BLOCK_LEN))
|
||||||
|
break;
|
||||||
|
if(!flipper_format_write_hex(file, "PIN", pacs->pin0, PICOPASS_BLOCK_LEN)) break;
|
||||||
|
if(!flipper_format_write_hex(file, "PIN(cont.)", pacs->pin1, PICOPASS_BLOCK_LEN))
|
||||||
|
break;
|
||||||
|
|
||||||
|
if(!flipper_format_write_comment_cstr(file, "Picopass blocks")) break;
|
||||||
|
// TODO: Save CSN, CFG, AA1, etc
|
||||||
|
bool block_saved = true;
|
||||||
|
for(size_t i = 0; i < 4; i++) {
|
||||||
|
string_printf(temp_str, "Block %d", i + 6);
|
||||||
|
if(!flipper_format_write_hex(
|
||||||
|
file,
|
||||||
|
string_get_cstr(temp_str),
|
||||||
|
AA1->block[i].data,
|
||||||
|
PICOPASS_BLOCK_LEN)) {
|
||||||
|
block_saved = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!block_saved) break;
|
||||||
|
if(!flipper_format_write_comment_cstr(file, "This is currently incomplete")) break;
|
||||||
|
}
|
||||||
|
} else if(dev->format == PicopassDeviceSaveFormatLF) {
|
||||||
|
const char* lf_header = "Flipper RFID key";
|
||||||
|
// Write header
|
||||||
|
if(!flipper_format_write_header_cstr(file, lf_header, 1)) break;
|
||||||
|
if(!flipper_format_write_comment_cstr(
|
||||||
|
file,
|
||||||
|
"This was generated from the Picopass plugin and may not match current lfrfid"))
|
||||||
|
break;
|
||||||
|
// When lfrfid supports more formats, update this
|
||||||
|
if(!flipper_format_write_string_cstr(file, "Key type", "H10301")) break;
|
||||||
|
uint8_t H10301[3] = {0};
|
||||||
|
H10301[0] = pacs->record.FacilityCode;
|
||||||
|
H10301[1] = pacs->record.CardNumber >> 8;
|
||||||
|
H10301[2] = pacs->record.CardNumber & 0x00FF;
|
||||||
|
if(!flipper_format_write_hex(file, "Data", H10301, 3)) break;
|
||||||
|
}
|
||||||
|
saved = true;
|
||||||
|
} while(0);
|
||||||
|
|
||||||
|
if(!saved) {
|
||||||
|
dialog_message_show_storage_error(dev->dialogs, "Can not save\nkey file");
|
||||||
|
}
|
||||||
|
string_clear(temp_str);
|
||||||
|
flipper_format_free(file);
|
||||||
|
return saved;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool picopass_device_save(PicopassDevice* dev, const char* dev_name) {
|
||||||
|
if(dev->format == PicopassDeviceSaveFormatHF) {
|
||||||
|
return picopass_device_save_file(
|
||||||
|
dev, dev_name, PICOPASS_APP_FOLDER, PICOPASS_APP_EXTENSION, true);
|
||||||
|
} else if(dev->format == PicopassDeviceSaveFormatLF) {
|
||||||
|
return picopass_device_save_file(dev, dev_name, "/any/lfrfid", ".rfid", true);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void picopass_device_clear(PicopassDevice* dev) {
|
||||||
|
furi_assert(dev);
|
||||||
|
|
||||||
|
picopass_device_data_clear(&dev->dev_data);
|
||||||
|
memset(&dev->dev_data, 0, sizeof(dev->dev_data));
|
||||||
|
}
|
||||||
|
|
||||||
|
void picopass_device_free(PicopassDevice* picopass_dev) {
|
||||||
|
furi_assert(picopass_dev);
|
||||||
|
picopass_device_clear(picopass_dev);
|
||||||
|
furi_record_close("storage");
|
||||||
|
furi_record_close("dialogs");
|
||||||
|
string_clear(picopass_dev->load_path);
|
||||||
|
free(picopass_dev);
|
||||||
|
}
|
||||||
|
|
||||||
|
void picopass_device_data_clear(PicopassDeviceData* dev_data) {
|
||||||
|
memset(&dev_data->AA1, 0, sizeof(ApplicationArea));
|
||||||
|
}
|
||||||
70
applications/picopass/picopass_device.h
Normal file
70
applications/picopass/picopass_device.h
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <storage/storage.h>
|
||||||
|
#include <dialogs/dialogs.h>
|
||||||
|
|
||||||
|
#include <rfal_picopass.h>
|
||||||
|
|
||||||
|
#define PICOPASS_DEV_NAME_MAX_LEN 22
|
||||||
|
#define PICOPASS_READER_DATA_MAX_SIZE 64
|
||||||
|
#define PICOPASS_BLOCK_LEN 8
|
||||||
|
|
||||||
|
#define PICOPASS_APP_FOLDER "/any/picopass"
|
||||||
|
#define PICOPASS_APP_EXTENSION ".picopass"
|
||||||
|
#define PICOPASS_APP_SHADOW_EXTENSION ".pas"
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
PicopassDeviceEncryptionUnknown = 0,
|
||||||
|
PicopassDeviceEncryptionNone = 0x14,
|
||||||
|
PicopassDeviceEncryptionDES = 0x15,
|
||||||
|
PicopassDeviceEncryption3DES = 0x17,
|
||||||
|
} PicopassEncryption;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
PicopassDeviceSaveFormatHF,
|
||||||
|
PicopassDeviceSaveFormatLF,
|
||||||
|
} PicopassDeviceSaveFormat;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
bool valid;
|
||||||
|
uint8_t bitLength;
|
||||||
|
uint8_t FacilityCode;
|
||||||
|
uint16_t CardNumber;
|
||||||
|
} PicopassWiegandRecord;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
bool biometrics;
|
||||||
|
PicopassEncryption encryption;
|
||||||
|
uint8_t credential[8];
|
||||||
|
uint8_t pin0[8];
|
||||||
|
uint8_t pin1[8];
|
||||||
|
PicopassWiegandRecord record;
|
||||||
|
} PicopassPacs;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
ApplicationArea AA1;
|
||||||
|
PicopassPacs pacs;
|
||||||
|
} PicopassDeviceData;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
Storage* storage;
|
||||||
|
DialogsApp* dialogs;
|
||||||
|
PicopassDeviceData dev_data;
|
||||||
|
char dev_name[PICOPASS_DEV_NAME_MAX_LEN + 1];
|
||||||
|
string_t load_path;
|
||||||
|
PicopassDeviceSaveFormat format;
|
||||||
|
} PicopassDevice;
|
||||||
|
|
||||||
|
PicopassDevice* picopass_device_alloc();
|
||||||
|
|
||||||
|
void picopass_device_free(PicopassDevice* picopass_dev);
|
||||||
|
|
||||||
|
void picopass_device_set_name(PicopassDevice* dev, const char* name);
|
||||||
|
|
||||||
|
bool picopass_device_save(PicopassDevice* dev, const char* dev_name);
|
||||||
|
|
||||||
|
void picopass_device_data_clear(PicopassDeviceData* dev_data);
|
||||||
|
|
||||||
|
void picopass_device_clear(PicopassDevice* dev);
|
||||||
77
applications/picopass/picopass_i.h
Normal file
77
applications/picopass/picopass_i.h
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "picopass.h"
|
||||||
|
#include "picopass_worker.h"
|
||||||
|
#include "picopass_device.h"
|
||||||
|
|
||||||
|
#include <rfal_picopass.h>
|
||||||
|
|
||||||
|
#include <furi.h>
|
||||||
|
#include <gui/gui.h>
|
||||||
|
#include <gui/view_dispatcher.h>
|
||||||
|
#include <gui/scene_manager.h>
|
||||||
|
#include <notification/notification_messages.h>
|
||||||
|
|
||||||
|
#include <gui/modules/submenu.h>
|
||||||
|
#include <gui/modules/popup.h>
|
||||||
|
#include <gui/modules/text_input.h>
|
||||||
|
#include <gui/modules/widget.h>
|
||||||
|
|
||||||
|
#include <input/input.h>
|
||||||
|
|
||||||
|
#include <picopass/scenes/picopass_scene.h>
|
||||||
|
|
||||||
|
#include <storage/storage.h>
|
||||||
|
#include <lib/toolbox/path.h>
|
||||||
|
|
||||||
|
#define PICOPASS_TEXT_STORE_SIZE 128
|
||||||
|
|
||||||
|
enum PicopassCustomEvent {
|
||||||
|
// Reserve first 100 events for button types and indexes, starting from 0
|
||||||
|
PicopassCustomEventReserved = 100,
|
||||||
|
|
||||||
|
PicopassCustomEventViewExit,
|
||||||
|
PicopassCustomEventWorkerExit,
|
||||||
|
PicopassCustomEventByteInputDone,
|
||||||
|
PicopassCustomEventTextInputDone,
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
EventTypeTick,
|
||||||
|
EventTypeKey,
|
||||||
|
} EventType;
|
||||||
|
|
||||||
|
struct Picopass {
|
||||||
|
PicopassWorker* worker;
|
||||||
|
ViewDispatcher* view_dispatcher;
|
||||||
|
Gui* gui;
|
||||||
|
NotificationApp* notifications;
|
||||||
|
SceneManager* scene_manager;
|
||||||
|
PicopassDevice* dev;
|
||||||
|
|
||||||
|
char text_store[PICOPASS_TEXT_STORE_SIZE + 1];
|
||||||
|
string_t text_box_store;
|
||||||
|
|
||||||
|
// Common Views
|
||||||
|
Submenu* submenu;
|
||||||
|
Popup* popup;
|
||||||
|
TextInput* text_input;
|
||||||
|
Widget* widget;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
PicopassViewMenu,
|
||||||
|
PicopassViewPopup,
|
||||||
|
PicopassViewTextInput,
|
||||||
|
PicopassViewWidget,
|
||||||
|
} PicopassView;
|
||||||
|
|
||||||
|
Picopass* picopass_alloc();
|
||||||
|
|
||||||
|
void picopass_text_store_set(Picopass* picopass, const char* text, ...);
|
||||||
|
|
||||||
|
void picopass_text_store_clear(Picopass* picopass);
|
||||||
|
|
||||||
|
void picopass_blink_start(Picopass* picopass);
|
||||||
|
|
||||||
|
void picopass_blink_stop(Picopass* picopass);
|
||||||
310
applications/picopass/picopass_worker.c
Normal file
310
applications/picopass/picopass_worker.c
Normal file
@ -0,0 +1,310 @@
|
|||||||
|
#include "picopass_worker_i.h"
|
||||||
|
#include <furi_hal.h>
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <st25r3916.h>
|
||||||
|
#include <rfal_analogConfig.h>
|
||||||
|
#include <rfal_rf.h>
|
||||||
|
#include <rfal_nfc.h>
|
||||||
|
|
||||||
|
#include <mbedtls/des.h>
|
||||||
|
#include <loclass/optimized_ikeys.h>
|
||||||
|
#include <loclass/optimized_cipher.h>
|
||||||
|
|
||||||
|
#include <platform.h>
|
||||||
|
|
||||||
|
#define TAG "PicopassWorker"
|
||||||
|
|
||||||
|
const uint8_t picopass_iclass_key[] = {0xaf, 0xa7, 0x85, 0xa7, 0xda, 0xb3, 0x33, 0x78};
|
||||||
|
const uint8_t picopass_iclass_decryptionkey[] =
|
||||||
|
{0xb4, 0x21, 0x2c, 0xca, 0xb7, 0xed, 0x21, 0x0f, 0x7b, 0x93, 0xd4, 0x59, 0x39, 0xc7, 0xdd, 0x36};
|
||||||
|
|
||||||
|
static void picopass_worker_enable_field() {
|
||||||
|
st25r3916TxRxOn();
|
||||||
|
rfalLowPowerModeStop();
|
||||||
|
rfalWorker();
|
||||||
|
}
|
||||||
|
|
||||||
|
static ReturnCode picopass_worker_disable_field(ReturnCode rc) {
|
||||||
|
st25r3916TxRxOff();
|
||||||
|
rfalLowPowerModeStart();
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ReturnCode picopass_worker_decrypt(uint8_t* enc_data, uint8_t* dec_data) {
|
||||||
|
uint8_t key[32] = {0};
|
||||||
|
memcpy(key, picopass_iclass_decryptionkey, sizeof(picopass_iclass_decryptionkey));
|
||||||
|
mbedtls_des3_context ctx;
|
||||||
|
mbedtls_des3_init(&ctx);
|
||||||
|
mbedtls_des3_set2key_dec(&ctx, key);
|
||||||
|
mbedtls_des3_crypt_ecb(&ctx, enc_data, dec_data);
|
||||||
|
mbedtls_des3_free(&ctx);
|
||||||
|
return ERR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ReturnCode picopass_worker_parse_wiegand(uint8_t* data, PicopassWiegandRecord* record) {
|
||||||
|
uint32_t* halves = (uint32_t*)data;
|
||||||
|
if(halves[0] == 0) {
|
||||||
|
uint8_t leading0s = __builtin_clz(REVERSE_BYTES_U32(halves[1]));
|
||||||
|
record->bitLength = 31 - leading0s;
|
||||||
|
} else {
|
||||||
|
uint8_t leading0s = __builtin_clz(REVERSE_BYTES_U32(halves[0]));
|
||||||
|
record->bitLength = 63 - leading0s;
|
||||||
|
}
|
||||||
|
FURI_LOG_D(TAG, "bitLength: %d", record->bitLength);
|
||||||
|
|
||||||
|
if(record->bitLength == 26) {
|
||||||
|
uint8_t* v4 = data + 4;
|
||||||
|
v4[0] = 0;
|
||||||
|
|
||||||
|
uint32_t bot = v4[3] | (v4[2] << 8) | (v4[1] << 16) | (v4[0] << 24);
|
||||||
|
|
||||||
|
record->CardNumber = (bot >> 1) & 0xFFFF;
|
||||||
|
record->FacilityCode = (bot >> 17) & 0xFF;
|
||||||
|
record->valid = true;
|
||||||
|
} else {
|
||||||
|
record->CardNumber = 0;
|
||||||
|
record->FacilityCode = 0;
|
||||||
|
record->valid = false;
|
||||||
|
}
|
||||||
|
return ERR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/***************************** Picopass Worker API *******************************/
|
||||||
|
|
||||||
|
PicopassWorker* picopass_worker_alloc() {
|
||||||
|
PicopassWorker* picopass_worker = malloc(sizeof(PicopassWorker));
|
||||||
|
|
||||||
|
// Worker thread attributes
|
||||||
|
picopass_worker->thread = furi_thread_alloc();
|
||||||
|
furi_thread_set_name(picopass_worker->thread, "PicopassWorker");
|
||||||
|
furi_thread_set_stack_size(picopass_worker->thread, 8192);
|
||||||
|
furi_thread_set_callback(picopass_worker->thread, picopass_worker_task);
|
||||||
|
furi_thread_set_context(picopass_worker->thread, picopass_worker);
|
||||||
|
|
||||||
|
picopass_worker->callback = NULL;
|
||||||
|
picopass_worker->context = NULL;
|
||||||
|
picopass_worker->storage = furi_record_open("storage");
|
||||||
|
|
||||||
|
picopass_worker_change_state(picopass_worker, PicopassWorkerStateReady);
|
||||||
|
|
||||||
|
return picopass_worker;
|
||||||
|
}
|
||||||
|
|
||||||
|
void picopass_worker_free(PicopassWorker* picopass_worker) {
|
||||||
|
furi_assert(picopass_worker);
|
||||||
|
|
||||||
|
furi_thread_free(picopass_worker->thread);
|
||||||
|
|
||||||
|
furi_record_close("storage");
|
||||||
|
|
||||||
|
free(picopass_worker);
|
||||||
|
}
|
||||||
|
|
||||||
|
PicopassWorkerState picopass_worker_get_state(PicopassWorker* picopass_worker) {
|
||||||
|
return picopass_worker->state;
|
||||||
|
}
|
||||||
|
|
||||||
|
void picopass_worker_start(
|
||||||
|
PicopassWorker* picopass_worker,
|
||||||
|
PicopassWorkerState state,
|
||||||
|
PicopassDeviceData* dev_data,
|
||||||
|
PicopassWorkerCallback callback,
|
||||||
|
void* context) {
|
||||||
|
furi_assert(picopass_worker);
|
||||||
|
furi_assert(dev_data);
|
||||||
|
|
||||||
|
picopass_worker->callback = callback;
|
||||||
|
picopass_worker->context = context;
|
||||||
|
picopass_worker->dev_data = dev_data;
|
||||||
|
picopass_worker_change_state(picopass_worker, state);
|
||||||
|
furi_thread_start(picopass_worker->thread);
|
||||||
|
}
|
||||||
|
|
||||||
|
void picopass_worker_stop(PicopassWorker* picopass_worker) {
|
||||||
|
furi_assert(picopass_worker);
|
||||||
|
if(picopass_worker->state == PicopassWorkerStateBroken ||
|
||||||
|
picopass_worker->state == PicopassWorkerStateReady) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
picopass_worker_disable_field(ERR_NONE);
|
||||||
|
|
||||||
|
picopass_worker_change_state(picopass_worker, PicopassWorkerStateStop);
|
||||||
|
furi_thread_join(picopass_worker->thread);
|
||||||
|
}
|
||||||
|
|
||||||
|
void picopass_worker_change_state(PicopassWorker* picopass_worker, PicopassWorkerState state) {
|
||||||
|
picopass_worker->state = state;
|
||||||
|
}
|
||||||
|
|
||||||
|
/***************************** Picopass Worker Thread *******************************/
|
||||||
|
|
||||||
|
ReturnCode picopass_detect_card(int timeout) {
|
||||||
|
UNUSED(timeout);
|
||||||
|
|
||||||
|
ReturnCode err;
|
||||||
|
|
||||||
|
err = rfalPicoPassPollerInitialize();
|
||||||
|
if(err != ERR_NONE) {
|
||||||
|
FURI_LOG_E(TAG, "rfalPicoPassPollerInitialize error %d", err);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = rfalFieldOnAndStartGT();
|
||||||
|
if(err != ERR_NONE) {
|
||||||
|
FURI_LOG_E(TAG, "rfalFieldOnAndStartGT error %d", err);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = rfalPicoPassPollerCheckPresence();
|
||||||
|
if(err != ERR_RF_COLLISION) {
|
||||||
|
FURI_LOG_E(TAG, "rfalPicoPassPollerCheckPresence error %d", err);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ERR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
ReturnCode picopass_read_card(ApplicationArea* AA1) {
|
||||||
|
rfalPicoPassIdentifyRes idRes;
|
||||||
|
rfalPicoPassSelectRes selRes;
|
||||||
|
rfalPicoPassReadCheckRes rcRes;
|
||||||
|
rfalPicoPassCheckRes chkRes;
|
||||||
|
|
||||||
|
ReturnCode err;
|
||||||
|
|
||||||
|
uint8_t div_key[8] = {0};
|
||||||
|
uint8_t mac[4] = {0};
|
||||||
|
uint8_t ccnr[12] = {0};
|
||||||
|
|
||||||
|
err = rfalPicoPassPollerIdentify(&idRes);
|
||||||
|
if(err != ERR_NONE) {
|
||||||
|
FURI_LOG_E(TAG, "rfalPicoPassPollerIdentify error %d", err);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = rfalPicoPassPollerSelect(idRes.CSN, &selRes);
|
||||||
|
if(err != ERR_NONE) {
|
||||||
|
FURI_LOG_E(TAG, "rfalPicoPassPollerSelect error %d", err);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = rfalPicoPassPollerReadCheck(&rcRes);
|
||||||
|
if(err != ERR_NONE) {
|
||||||
|
FURI_LOG_E(TAG, "rfalPicoPassPollerReadCheck error %d", err);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
memcpy(ccnr, rcRes.CCNR, sizeof(rcRes.CCNR)); // last 4 bytes left 0
|
||||||
|
|
||||||
|
loclass_diversifyKey(selRes.CSN, picopass_iclass_key, div_key);
|
||||||
|
loclass_opt_doReaderMAC(ccnr, div_key, mac);
|
||||||
|
|
||||||
|
err = rfalPicoPassPollerCheck(mac, &chkRes);
|
||||||
|
if(err != ERR_NONE) {
|
||||||
|
FURI_LOG_E(TAG, "rfalPicoPassPollerCheck error %d", err);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(size_t i = 0; i < 4; i++) {
|
||||||
|
FURI_LOG_D(TAG, "rfalPicoPassPollerReadBlock block %d", i + 6);
|
||||||
|
rfalPicoPassReadBlockRes block;
|
||||||
|
err = rfalPicoPassPollerReadBlock(i + 6, &block);
|
||||||
|
if(err != ERR_NONE) {
|
||||||
|
FURI_LOG_E(TAG, "rfalPicoPassPollerReadBlock error %d", err);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
FURI_LOG_D(
|
||||||
|
TAG,
|
||||||
|
"rfalPicoPassPollerReadBlock %d %02x%02x%02x%02x%02x%02x%02x%02x",
|
||||||
|
i + 6,
|
||||||
|
block.data[0],
|
||||||
|
block.data[1],
|
||||||
|
block.data[2],
|
||||||
|
block.data[3],
|
||||||
|
block.data[4],
|
||||||
|
block.data[5],
|
||||||
|
block.data[6],
|
||||||
|
block.data[7]);
|
||||||
|
|
||||||
|
memcpy(&(AA1->block[i]), &block, sizeof(block));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ERR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t picopass_worker_task(void* context) {
|
||||||
|
PicopassWorker* picopass_worker = context;
|
||||||
|
|
||||||
|
picopass_worker_enable_field();
|
||||||
|
if(picopass_worker->state == PicopassWorkerStateDetect) {
|
||||||
|
picopass_worker_detect(picopass_worker);
|
||||||
|
}
|
||||||
|
picopass_worker_disable_field(ERR_NONE);
|
||||||
|
|
||||||
|
picopass_worker_change_state(picopass_worker, PicopassWorkerStateReady);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void picopass_worker_detect(PicopassWorker* picopass_worker) {
|
||||||
|
picopass_device_data_clear(picopass_worker->dev_data);
|
||||||
|
PicopassDeviceData* dev_data = picopass_worker->dev_data;
|
||||||
|
|
||||||
|
ApplicationArea* AA1 = &dev_data->AA1;
|
||||||
|
PicopassPacs* pacs = &dev_data->pacs;
|
||||||
|
ReturnCode err;
|
||||||
|
|
||||||
|
while(picopass_worker->state == PicopassWorkerStateDetect) {
|
||||||
|
if(picopass_detect_card(1000) == ERR_NONE) {
|
||||||
|
// Process first found device
|
||||||
|
err = picopass_read_card(AA1);
|
||||||
|
if(err != ERR_NONE) {
|
||||||
|
FURI_LOG_E(TAG, "picopass_read_card error %d", err);
|
||||||
|
}
|
||||||
|
|
||||||
|
pacs->biometrics = AA1->block[0].data[4];
|
||||||
|
pacs->encryption = AA1->block[0].data[7];
|
||||||
|
|
||||||
|
if(pacs->encryption == 0x17) {
|
||||||
|
FURI_LOG_D(TAG, "3DES Encrypted");
|
||||||
|
err = picopass_worker_decrypt(AA1->block[1].data, pacs->credential);
|
||||||
|
if(err != ERR_NONE) {
|
||||||
|
FURI_LOG_E(TAG, "decrypt error %d", err);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = picopass_worker_decrypt(AA1->block[2].data, pacs->pin0);
|
||||||
|
if(err != ERR_NONE) {
|
||||||
|
FURI_LOG_E(TAG, "decrypt error %d", err);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = picopass_worker_decrypt(AA1->block[3].data, pacs->pin1);
|
||||||
|
if(err != ERR_NONE) {
|
||||||
|
FURI_LOG_E(TAG, "decrypt error %d", err);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if(pacs->encryption == 0x14) {
|
||||||
|
FURI_LOG_D(TAG, "No Encryption");
|
||||||
|
memcpy(pacs->credential, AA1->block[1].data, RFAL_PICOPASS_MAX_BLOCK_LEN);
|
||||||
|
memcpy(pacs->pin0, AA1->block[2].data, RFAL_PICOPASS_MAX_BLOCK_LEN);
|
||||||
|
memcpy(pacs->pin1, AA1->block[3].data, RFAL_PICOPASS_MAX_BLOCK_LEN);
|
||||||
|
} else if(pacs->encryption == 0x15) {
|
||||||
|
FURI_LOG_D(TAG, "DES Encrypted");
|
||||||
|
} else {
|
||||||
|
FURI_LOG_D(TAG, "Unknown encryption");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
picopass_worker_parse_wiegand(pacs->credential, &pacs->record);
|
||||||
|
|
||||||
|
// Notify caller and exit
|
||||||
|
if(picopass_worker->callback) {
|
||||||
|
picopass_worker->callback(PicopassWorkerEventSuccess, picopass_worker->context);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
osDelay(100);
|
||||||
|
}
|
||||||
|
}
|
||||||
45
applications/picopass/picopass_worker.h
Executable file
45
applications/picopass/picopass_worker.h
Executable file
@ -0,0 +1,45 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "picopass_device.h"
|
||||||
|
|
||||||
|
typedef struct PicopassWorker PicopassWorker;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
// Init states
|
||||||
|
PicopassWorkerStateNone,
|
||||||
|
PicopassWorkerStateBroken,
|
||||||
|
PicopassWorkerStateReady,
|
||||||
|
// Main worker states
|
||||||
|
PicopassWorkerStateDetect,
|
||||||
|
// Transition
|
||||||
|
PicopassWorkerStateStop,
|
||||||
|
} PicopassWorkerState;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
// Reserve first 50 events for application events
|
||||||
|
PicopassWorkerEventReserved = 50,
|
||||||
|
|
||||||
|
// Picopass worker common events
|
||||||
|
PicopassWorkerEventSuccess,
|
||||||
|
PicopassWorkerEventFail,
|
||||||
|
PicopassWorkerEventNoCardDetected,
|
||||||
|
|
||||||
|
PicopassWorkerEventStartReading,
|
||||||
|
} PicopassWorkerEvent;
|
||||||
|
|
||||||
|
typedef void (*PicopassWorkerCallback)(PicopassWorkerEvent event, void* context);
|
||||||
|
|
||||||
|
PicopassWorker* picopass_worker_alloc();
|
||||||
|
|
||||||
|
PicopassWorkerState picopass_worker_get_state(PicopassWorker* picopass_worker);
|
||||||
|
|
||||||
|
void picopass_worker_free(PicopassWorker* picopass_worker);
|
||||||
|
|
||||||
|
void picopass_worker_start(
|
||||||
|
PicopassWorker* picopass_worker,
|
||||||
|
PicopassWorkerState state,
|
||||||
|
PicopassDeviceData* dev_data,
|
||||||
|
PicopassWorkerCallback callback,
|
||||||
|
void* context);
|
||||||
|
|
||||||
|
void picopass_worker_stop(PicopassWorker* picopass_worker);
|
||||||
24
applications/picopass/picopass_worker_i.h
Normal file
24
applications/picopass/picopass_worker_i.h
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "picopass_worker.h"
|
||||||
|
#include "picopass_i.h"
|
||||||
|
|
||||||
|
#include <furi.h>
|
||||||
|
#include <lib/toolbox/stream/file_stream.h>
|
||||||
|
|
||||||
|
struct PicopassWorker {
|
||||||
|
FuriThread* thread;
|
||||||
|
Storage* storage;
|
||||||
|
|
||||||
|
PicopassDeviceData* dev_data;
|
||||||
|
PicopassWorkerCallback callback;
|
||||||
|
void* context;
|
||||||
|
|
||||||
|
PicopassWorkerState state;
|
||||||
|
};
|
||||||
|
|
||||||
|
void picopass_worker_change_state(PicopassWorker* picopass_worker, PicopassWorkerState state);
|
||||||
|
|
||||||
|
int32_t picopass_worker_task(void* context);
|
||||||
|
|
||||||
|
void picopass_worker_detect(PicopassWorker* picopass_worker);
|
||||||
30
applications/picopass/scenes/picopass_scene.c
Executable file
30
applications/picopass/scenes/picopass_scene.c
Executable file
@ -0,0 +1,30 @@
|
|||||||
|
#include "picopass_scene.h"
|
||||||
|
|
||||||
|
// Generate scene on_enter handlers array
|
||||||
|
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
|
||||||
|
void (*const picopass_on_enter_handlers[])(void*) = {
|
||||||
|
#include "picopass_scene_config.h"
|
||||||
|
};
|
||||||
|
#undef ADD_SCENE
|
||||||
|
|
||||||
|
// Generate scene on_event handlers array
|
||||||
|
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event,
|
||||||
|
bool (*const picopass_on_event_handlers[])(void* context, SceneManagerEvent event) = {
|
||||||
|
#include "picopass_scene_config.h"
|
||||||
|
};
|
||||||
|
#undef ADD_SCENE
|
||||||
|
|
||||||
|
// Generate scene on_exit handlers array
|
||||||
|
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit,
|
||||||
|
void (*const picopass_on_exit_handlers[])(void* context) = {
|
||||||
|
#include "picopass_scene_config.h"
|
||||||
|
};
|
||||||
|
#undef ADD_SCENE
|
||||||
|
|
||||||
|
// Initialize scene handlers configuration structure
|
||||||
|
const SceneManagerHandlers picopass_scene_handlers = {
|
||||||
|
.on_enter_handlers = picopass_on_enter_handlers,
|
||||||
|
.on_event_handlers = picopass_on_event_handlers,
|
||||||
|
.on_exit_handlers = picopass_on_exit_handlers,
|
||||||
|
.scene_num = PicopassSceneNum,
|
||||||
|
};
|
||||||
29
applications/picopass/scenes/picopass_scene.h
Normal file
29
applications/picopass/scenes/picopass_scene.h
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <gui/scene_manager.h>
|
||||||
|
|
||||||
|
// Generate scene id and total number
|
||||||
|
#define ADD_SCENE(prefix, name, id) PicopassScene##id,
|
||||||
|
typedef enum {
|
||||||
|
#include "picopass_scene_config.h"
|
||||||
|
PicopassSceneNum,
|
||||||
|
} PicopassScene;
|
||||||
|
#undef ADD_SCENE
|
||||||
|
|
||||||
|
extern const SceneManagerHandlers picopass_scene_handlers;
|
||||||
|
|
||||||
|
// Generate scene on_enter handlers declaration
|
||||||
|
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*);
|
||||||
|
#include "picopass_scene_config.h"
|
||||||
|
#undef ADD_SCENE
|
||||||
|
|
||||||
|
// Generate scene on_event handlers declaration
|
||||||
|
#define ADD_SCENE(prefix, name, id) \
|
||||||
|
bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event);
|
||||||
|
#include "picopass_scene_config.h"
|
||||||
|
#undef ADD_SCENE
|
||||||
|
|
||||||
|
// Generate scene on_exit handlers declaration
|
||||||
|
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context);
|
||||||
|
#include "picopass_scene_config.h"
|
||||||
|
#undef ADD_SCENE
|
||||||
65
applications/picopass/scenes/picopass_scene_card_menu.c
Normal file
65
applications/picopass/scenes/picopass_scene_card_menu.c
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
#include "../picopass_i.h"
|
||||||
|
|
||||||
|
enum SubmenuIndex {
|
||||||
|
SubmenuIndexSave,
|
||||||
|
SubmenuIndexSaveAsLF,
|
||||||
|
};
|
||||||
|
|
||||||
|
void picopass_scene_card_menu_submenu_callback(void* context, uint32_t index) {
|
||||||
|
Picopass* picopass = context;
|
||||||
|
|
||||||
|
view_dispatcher_send_custom_event(picopass->view_dispatcher, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
void picopass_scene_card_menu_on_enter(void* context) {
|
||||||
|
Picopass* picopass = context;
|
||||||
|
Submenu* submenu = picopass->submenu;
|
||||||
|
|
||||||
|
submenu_add_item(
|
||||||
|
submenu, "Save", SubmenuIndexSave, picopass_scene_card_menu_submenu_callback, picopass);
|
||||||
|
if(picopass->dev->dev_data.pacs.record.valid) {
|
||||||
|
submenu_add_item(
|
||||||
|
submenu,
|
||||||
|
"Save as LF",
|
||||||
|
SubmenuIndexSaveAsLF,
|
||||||
|
picopass_scene_card_menu_submenu_callback,
|
||||||
|
picopass);
|
||||||
|
}
|
||||||
|
submenu_set_selected_item(
|
||||||
|
picopass->submenu,
|
||||||
|
scene_manager_get_scene_state(picopass->scene_manager, PicopassSceneCardMenu));
|
||||||
|
|
||||||
|
view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewMenu);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool picopass_scene_card_menu_on_event(void* context, SceneManagerEvent event) {
|
||||||
|
Picopass* picopass = context;
|
||||||
|
bool consumed = false;
|
||||||
|
|
||||||
|
if(event.type == SceneManagerEventTypeCustom) {
|
||||||
|
if(event.event == SubmenuIndexSave) {
|
||||||
|
scene_manager_set_scene_state(
|
||||||
|
picopass->scene_manager, PicopassSceneCardMenu, SubmenuIndexSave);
|
||||||
|
scene_manager_next_scene(picopass->scene_manager, PicopassSceneSaveName);
|
||||||
|
picopass->dev->format = PicopassDeviceSaveFormatHF;
|
||||||
|
consumed = true;
|
||||||
|
} else if(event.event == SubmenuIndexSaveAsLF) {
|
||||||
|
scene_manager_set_scene_state(
|
||||||
|
picopass->scene_manager, PicopassSceneCardMenu, SubmenuIndexSaveAsLF);
|
||||||
|
picopass->dev->format = PicopassDeviceSaveFormatLF;
|
||||||
|
scene_manager_next_scene(picopass->scene_manager, PicopassSceneSaveName);
|
||||||
|
consumed = true;
|
||||||
|
}
|
||||||
|
} else if(event.type == SceneManagerEventTypeBack) {
|
||||||
|
consumed = scene_manager_search_and_switch_to_previous_scene(
|
||||||
|
picopass->scene_manager, PicopassSceneStart);
|
||||||
|
}
|
||||||
|
|
||||||
|
return consumed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void picopass_scene_card_menu_on_exit(void* context) {
|
||||||
|
Picopass* picopass = context;
|
||||||
|
|
||||||
|
submenu_reset(picopass->submenu);
|
||||||
|
}
|
||||||
7
applications/picopass/scenes/picopass_scene_config.h
Executable file
7
applications/picopass/scenes/picopass_scene_config.h
Executable file
@ -0,0 +1,7 @@
|
|||||||
|
ADD_SCENE(picopass, start, Start)
|
||||||
|
ADD_SCENE(picopass, read_card, ReadCard)
|
||||||
|
ADD_SCENE(picopass, read_card_success, ReadCardSuccess)
|
||||||
|
ADD_SCENE(picopass, card_menu, CardMenu)
|
||||||
|
ADD_SCENE(picopass, save_name, SaveName)
|
||||||
|
ADD_SCENE(picopass, save_success, SaveSuccess)
|
||||||
|
ADD_SCENE(picopass, saved_menu, SavedMenu)
|
||||||
55
applications/picopass/scenes/picopass_scene_read_card.c
Normal file
55
applications/picopass/scenes/picopass_scene_read_card.c
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
#include "../picopass_i.h"
|
||||||
|
#include <dolphin/dolphin.h>
|
||||||
|
|
||||||
|
void picopass_read_card_worker_callback(PicopassWorkerEvent event, void* context) {
|
||||||
|
UNUSED(event);
|
||||||
|
Picopass* picopass = context;
|
||||||
|
view_dispatcher_send_custom_event(picopass->view_dispatcher, PicopassCustomEventWorkerExit);
|
||||||
|
}
|
||||||
|
|
||||||
|
void picopass_scene_read_card_on_enter(void* context) {
|
||||||
|
Picopass* picopass = context;
|
||||||
|
DOLPHIN_DEED(DolphinDeedNfcRead);
|
||||||
|
|
||||||
|
// Setup view
|
||||||
|
Popup* popup = picopass->popup;
|
||||||
|
popup_set_header(popup, "Detecting\npicopass card", 70, 34, AlignLeft, AlignTop);
|
||||||
|
popup_set_icon(popup, 0, 3, &I_RFIDDolphinReceive_97x61);
|
||||||
|
|
||||||
|
// Start worker
|
||||||
|
view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewPopup);
|
||||||
|
picopass_worker_start(
|
||||||
|
picopass->worker,
|
||||||
|
PicopassWorkerStateDetect,
|
||||||
|
&picopass->dev->dev_data,
|
||||||
|
picopass_read_card_worker_callback,
|
||||||
|
picopass);
|
||||||
|
|
||||||
|
picopass_blink_start(picopass);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool picopass_scene_read_card_on_event(void* context, SceneManagerEvent event) {
|
||||||
|
Picopass* picopass = context;
|
||||||
|
bool consumed = false;
|
||||||
|
|
||||||
|
if(event.type == SceneManagerEventTypeCustom) {
|
||||||
|
if(event.event == PicopassCustomEventWorkerExit) {
|
||||||
|
scene_manager_next_scene(picopass->scene_manager, PicopassSceneReadCardSuccess);
|
||||||
|
consumed = true;
|
||||||
|
}
|
||||||
|
} else if(event.type == SceneManagerEventTypeTick) {
|
||||||
|
consumed = true;
|
||||||
|
}
|
||||||
|
return consumed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void picopass_scene_read_card_on_exit(void* context) {
|
||||||
|
Picopass* picopass = context;
|
||||||
|
|
||||||
|
// Stop worker
|
||||||
|
picopass_worker_stop(picopass->worker);
|
||||||
|
// Clear view
|
||||||
|
popup_reset(picopass->popup);
|
||||||
|
|
||||||
|
picopass_blink_stop(picopass);
|
||||||
|
}
|
||||||
@ -0,0 +1,91 @@
|
|||||||
|
#include "../picopass_i.h"
|
||||||
|
#include <dolphin/dolphin.h>
|
||||||
|
|
||||||
|
void picopass_scene_read_card_success_widget_callback(
|
||||||
|
GuiButtonType result,
|
||||||
|
InputType type,
|
||||||
|
void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
Picopass* picopass = context;
|
||||||
|
|
||||||
|
if(type == InputTypeShort) {
|
||||||
|
view_dispatcher_send_custom_event(picopass->view_dispatcher, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void picopass_scene_read_card_success_on_enter(void* context) {
|
||||||
|
Picopass* picopass = context;
|
||||||
|
string_t credential_str;
|
||||||
|
string_t wiegand_str;
|
||||||
|
string_init(credential_str);
|
||||||
|
string_init(wiegand_str);
|
||||||
|
|
||||||
|
DOLPHIN_DEED(DolphinDeedNfcReadSuccess);
|
||||||
|
|
||||||
|
// Send notification
|
||||||
|
notification_message(picopass->notifications, &sequence_success);
|
||||||
|
|
||||||
|
// Setup view
|
||||||
|
PicopassPacs* pacs = &picopass->dev->dev_data.pacs;
|
||||||
|
Widget* widget = picopass->widget;
|
||||||
|
|
||||||
|
string_set_str(credential_str, "");
|
||||||
|
for(uint8_t i = 0; i < RFAL_PICOPASS_MAX_BLOCK_LEN; i++) {
|
||||||
|
string_cat_printf(credential_str, " %02X", pacs->credential[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(pacs->record.valid) {
|
||||||
|
string_cat_printf(
|
||||||
|
wiegand_str, "FC: %03u CN: %05u", pacs->record.FacilityCode, pacs->record.CardNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
widget_add_button_element(
|
||||||
|
widget,
|
||||||
|
GuiButtonTypeLeft,
|
||||||
|
"Retry",
|
||||||
|
picopass_scene_read_card_success_widget_callback,
|
||||||
|
picopass);
|
||||||
|
|
||||||
|
widget_add_button_element(
|
||||||
|
widget,
|
||||||
|
GuiButtonTypeRight,
|
||||||
|
"More",
|
||||||
|
picopass_scene_read_card_success_widget_callback,
|
||||||
|
picopass);
|
||||||
|
|
||||||
|
if(pacs->record.valid) {
|
||||||
|
widget_add_string_element(
|
||||||
|
widget, 64, 12, AlignCenter, AlignCenter, FontPrimary, string_get_cstr(wiegand_str));
|
||||||
|
}
|
||||||
|
widget_add_string_element(
|
||||||
|
widget, 64, 32, AlignCenter, AlignCenter, FontSecondary, string_get_cstr(credential_str));
|
||||||
|
|
||||||
|
string_clear(credential_str);
|
||||||
|
string_clear(wiegand_str);
|
||||||
|
|
||||||
|
view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewWidget);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool picopass_scene_read_card_success_on_event(void* context, SceneManagerEvent event) {
|
||||||
|
Picopass* picopass = context;
|
||||||
|
bool consumed = false;
|
||||||
|
|
||||||
|
if(event.type == SceneManagerEventTypeCustom) {
|
||||||
|
if(event.event == GuiButtonTypeLeft) {
|
||||||
|
consumed = scene_manager_previous_scene(picopass->scene_manager);
|
||||||
|
} else if(event.event == GuiButtonTypeRight) {
|
||||||
|
// Clear device name
|
||||||
|
picopass_device_set_name(picopass->dev, "");
|
||||||
|
scene_manager_next_scene(picopass->scene_manager, PicopassSceneCardMenu);
|
||||||
|
consumed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return consumed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void picopass_scene_read_card_success_on_exit(void* context) {
|
||||||
|
Picopass* picopass = context;
|
||||||
|
|
||||||
|
// Clear view
|
||||||
|
widget_reset(picopass->widget);
|
||||||
|
}
|
||||||
84
applications/picopass/scenes/picopass_scene_save_name.c
Normal file
84
applications/picopass/scenes/picopass_scene_save_name.c
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
#include "../picopass_i.h"
|
||||||
|
#include "m-string.h"
|
||||||
|
#include <lib/toolbox/random_name.h>
|
||||||
|
#include <gui/modules/validators.h>
|
||||||
|
#include <toolbox/path.h>
|
||||||
|
|
||||||
|
void picopass_scene_save_name_text_input_callback(void* context) {
|
||||||
|
Picopass* picopass = context;
|
||||||
|
|
||||||
|
view_dispatcher_send_custom_event(picopass->view_dispatcher, PicopassCustomEventTextInputDone);
|
||||||
|
}
|
||||||
|
|
||||||
|
void picopass_scene_save_name_on_enter(void* context) {
|
||||||
|
Picopass* picopass = context;
|
||||||
|
|
||||||
|
// Setup view
|
||||||
|
TextInput* text_input = picopass->text_input;
|
||||||
|
bool dev_name_empty = false;
|
||||||
|
if(!strcmp(picopass->dev->dev_name, "")) {
|
||||||
|
set_random_name(picopass->text_store, sizeof(picopass->text_store));
|
||||||
|
dev_name_empty = true;
|
||||||
|
} else {
|
||||||
|
picopass_text_store_set(picopass, picopass->dev->dev_name);
|
||||||
|
}
|
||||||
|
text_input_set_header_text(text_input, "Name the card");
|
||||||
|
text_input_set_result_callback(
|
||||||
|
text_input,
|
||||||
|
picopass_scene_save_name_text_input_callback,
|
||||||
|
picopass,
|
||||||
|
picopass->text_store,
|
||||||
|
PICOPASS_DEV_NAME_MAX_LEN,
|
||||||
|
dev_name_empty);
|
||||||
|
|
||||||
|
string_t folder_path;
|
||||||
|
string_init(folder_path);
|
||||||
|
|
||||||
|
if(string_end_with_str_p(picopass->dev->load_path, PICOPASS_APP_EXTENSION)) {
|
||||||
|
path_extract_dirname(string_get_cstr(picopass->dev->load_path), folder_path);
|
||||||
|
} else {
|
||||||
|
string_set_str(folder_path, PICOPASS_APP_FOLDER);
|
||||||
|
}
|
||||||
|
|
||||||
|
ValidatorIsFile* validator_is_file = validator_is_file_alloc_init(
|
||||||
|
string_get_cstr(folder_path), PICOPASS_APP_EXTENSION, picopass->dev->dev_name);
|
||||||
|
text_input_set_validator(text_input, validator_is_file_callback, validator_is_file);
|
||||||
|
|
||||||
|
view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewTextInput);
|
||||||
|
|
||||||
|
string_clear(folder_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool picopass_scene_save_name_on_event(void* context, SceneManagerEvent event) {
|
||||||
|
Picopass* picopass = context;
|
||||||
|
bool consumed = false;
|
||||||
|
|
||||||
|
if(event.type == SceneManagerEventTypeCustom) {
|
||||||
|
if(event.event == PicopassCustomEventTextInputDone) {
|
||||||
|
if(strcmp(picopass->dev->dev_name, "")) {
|
||||||
|
// picopass_device_delete(picopass->dev, true);
|
||||||
|
}
|
||||||
|
strlcpy(
|
||||||
|
picopass->dev->dev_name, picopass->text_store, strlen(picopass->text_store) + 1);
|
||||||
|
if(picopass_device_save(picopass->dev, picopass->text_store)) {
|
||||||
|
scene_manager_next_scene(picopass->scene_manager, PicopassSceneSaveSuccess);
|
||||||
|
consumed = true;
|
||||||
|
} else {
|
||||||
|
consumed = scene_manager_search_and_switch_to_previous_scene(
|
||||||
|
picopass->scene_manager, PicopassSceneStart);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return consumed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void picopass_scene_save_name_on_exit(void* context) {
|
||||||
|
Picopass* picopass = context;
|
||||||
|
|
||||||
|
// Clear view
|
||||||
|
void* validator_context = text_input_get_validator_callback_context(picopass->text_input);
|
||||||
|
text_input_set_validator(picopass->text_input, NULL, NULL);
|
||||||
|
validator_is_file_free(validator_context);
|
||||||
|
|
||||||
|
text_input_reset(picopass->text_input);
|
||||||
|
}
|
||||||
47
applications/picopass/scenes/picopass_scene_save_success.c
Normal file
47
applications/picopass/scenes/picopass_scene_save_success.c
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
#include "../picopass_i.h"
|
||||||
|
#include <dolphin/dolphin.h>
|
||||||
|
|
||||||
|
void picopass_scene_save_success_popup_callback(void* context) {
|
||||||
|
Picopass* picopass = context;
|
||||||
|
view_dispatcher_send_custom_event(picopass->view_dispatcher, PicopassCustomEventViewExit);
|
||||||
|
}
|
||||||
|
|
||||||
|
void picopass_scene_save_success_on_enter(void* context) {
|
||||||
|
Picopass* picopass = context;
|
||||||
|
DOLPHIN_DEED(DolphinDeedNfcSave);
|
||||||
|
|
||||||
|
// Setup view
|
||||||
|
Popup* popup = picopass->popup;
|
||||||
|
popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59);
|
||||||
|
popup_set_header(popup, "Saved!", 13, 22, AlignLeft, AlignBottom);
|
||||||
|
popup_set_timeout(popup, 1500);
|
||||||
|
popup_set_context(popup, picopass);
|
||||||
|
popup_set_callback(popup, picopass_scene_save_success_popup_callback);
|
||||||
|
popup_enable_timeout(popup);
|
||||||
|
view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewPopup);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool picopass_scene_save_success_on_event(void* context, SceneManagerEvent event) {
|
||||||
|
Picopass* picopass = context;
|
||||||
|
bool consumed = false;
|
||||||
|
|
||||||
|
if(event.type == SceneManagerEventTypeCustom) {
|
||||||
|
if(event.event == PicopassCustomEventViewExit) {
|
||||||
|
if(scene_manager_has_previous_scene(picopass->scene_manager, PicopassSceneCardMenu)) {
|
||||||
|
consumed = scene_manager_search_and_switch_to_previous_scene(
|
||||||
|
picopass->scene_manager, PicopassSceneCardMenu);
|
||||||
|
} else {
|
||||||
|
consumed = scene_manager_search_and_switch_to_previous_scene(
|
||||||
|
picopass->scene_manager, PicopassSceneStart);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return consumed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void picopass_scene_save_success_on_exit(void* context) {
|
||||||
|
Picopass* picopass = context;
|
||||||
|
|
||||||
|
// Clear view
|
||||||
|
popup_reset(picopass->popup);
|
||||||
|
}
|
||||||
35
applications/picopass/scenes/picopass_scene_saved_menu.c
Normal file
35
applications/picopass/scenes/picopass_scene_saved_menu.c
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
#include "../picopass_i.h"
|
||||||
|
|
||||||
|
void picopass_scene_saved_menu_submenu_callback(void* context, uint32_t index) {
|
||||||
|
Picopass* picopass = context;
|
||||||
|
|
||||||
|
view_dispatcher_send_custom_event(picopass->view_dispatcher, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
void picopass_scene_saved_menu_on_enter(void* context) {
|
||||||
|
Picopass* picopass = context;
|
||||||
|
|
||||||
|
submenu_set_selected_item(
|
||||||
|
picopass->submenu,
|
||||||
|
scene_manager_get_scene_state(picopass->scene_manager, PicopassSceneSavedMenu));
|
||||||
|
|
||||||
|
view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewMenu);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool picopass_scene_saved_menu_on_event(void* context, SceneManagerEvent event) {
|
||||||
|
Picopass* picopass = context;
|
||||||
|
bool consumed = false;
|
||||||
|
|
||||||
|
if(event.type == SceneManagerEventTypeCustom) {
|
||||||
|
scene_manager_set_scene_state(
|
||||||
|
picopass->scene_manager, PicopassSceneSavedMenu, event.event);
|
||||||
|
}
|
||||||
|
|
||||||
|
return consumed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void picopass_scene_saved_menu_on_exit(void* context) {
|
||||||
|
Picopass* picopass = context;
|
||||||
|
|
||||||
|
submenu_reset(picopass->submenu);
|
||||||
|
}
|
||||||
45
applications/picopass/scenes/picopass_scene_start.c
Normal file
45
applications/picopass/scenes/picopass_scene_start.c
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
#include "../picopass_i.h"
|
||||||
|
enum SubmenuIndex {
|
||||||
|
SubmenuIndexRead,
|
||||||
|
SubmenuIndexRunScript,
|
||||||
|
SubmenuIndexSaved,
|
||||||
|
SubmenuIndexAddManualy,
|
||||||
|
SubmenuIndexDebug,
|
||||||
|
};
|
||||||
|
|
||||||
|
void picopass_scene_start_submenu_callback(void* context, uint32_t index) {
|
||||||
|
Picopass* picopass = context;
|
||||||
|
view_dispatcher_send_custom_event(picopass->view_dispatcher, index);
|
||||||
|
}
|
||||||
|
void picopass_scene_start_on_enter(void* context) {
|
||||||
|
Picopass* picopass = context;
|
||||||
|
|
||||||
|
Submenu* submenu = picopass->submenu;
|
||||||
|
submenu_add_item(
|
||||||
|
submenu, "Read Card", SubmenuIndexRead, picopass_scene_start_submenu_callback, picopass);
|
||||||
|
|
||||||
|
submenu_set_selected_item(
|
||||||
|
submenu, scene_manager_get_scene_state(picopass->scene_manager, PicopassSceneStart));
|
||||||
|
picopass_device_clear(picopass->dev);
|
||||||
|
view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewMenu);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool picopass_scene_start_on_event(void* context, SceneManagerEvent event) {
|
||||||
|
Picopass* picopass = context;
|
||||||
|
bool consumed = false;
|
||||||
|
|
||||||
|
if(event.type == SceneManagerEventTypeCustom) {
|
||||||
|
if(event.event == SubmenuIndexRead) {
|
||||||
|
scene_manager_next_scene(picopass->scene_manager, PicopassSceneReadCard);
|
||||||
|
consumed = true;
|
||||||
|
}
|
||||||
|
scene_manager_set_scene_state(picopass->scene_manager, PicopassSceneStart, event.event);
|
||||||
|
}
|
||||||
|
|
||||||
|
return consumed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void picopass_scene_start_on_exit(void* context) {
|
||||||
|
Picopass* picopass = context;
|
||||||
|
submenu_reset(picopass->submenu);
|
||||||
|
}
|
||||||
@ -45,13 +45,16 @@ static RpcSystemCallbacks rpc_systems[] = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
.alloc = rpc_system_app_alloc,
|
.alloc = rpc_system_app_alloc,
|
||||||
.free = NULL,
|
.free = rpc_system_app_free,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.alloc = rpc_system_gui_alloc,
|
.alloc = rpc_system_gui_alloc,
|
||||||
.free = rpc_system_gui_free,
|
.free = rpc_system_gui_free,
|
||||||
},
|
},
|
||||||
};
|
{
|
||||||
|
.alloc = rpc_system_gpio_alloc,
|
||||||
|
.free = NULL,
|
||||||
|
}};
|
||||||
|
|
||||||
struct RpcSession {
|
struct RpcSession {
|
||||||
Rpc* rpc;
|
Rpc* rpc;
|
||||||
|
|||||||
@ -1,16 +1,39 @@
|
|||||||
|
#include "cmsis_os2.h"
|
||||||
#include "flipper.pb.h"
|
#include "flipper.pb.h"
|
||||||
#include "furi/record.h"
|
#include "furi/record.h"
|
||||||
#include "rpc_i.h"
|
#include "rpc_i.h"
|
||||||
#include <furi.h>
|
#include <furi.h>
|
||||||
#include <loader/loader.h>
|
#include <loader/loader.h>
|
||||||
|
#include "rpc_app.h"
|
||||||
|
|
||||||
#define TAG "RpcSystemApp"
|
#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) {
|
static void rpc_system_app_start_process(const PB_Main* request, void* context) {
|
||||||
furi_assert(request);
|
furi_assert(request);
|
||||||
|
furi_assert(context);
|
||||||
|
|
||||||
furi_assert(request->which_content == PB_Main_app_start_request_tag);
|
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);
|
furi_assert(session);
|
||||||
|
char args_temp[16];
|
||||||
|
|
||||||
FURI_LOG_D(TAG, "Start");
|
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;
|
const char* app_name = request->content.app_start_request.name;
|
||||||
if(app_name) {
|
if(app_name) {
|
||||||
const char* app_args = request->content.app_start_request.args;
|
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);
|
LoaderStatus status = loader_start(loader, app_name, app_args);
|
||||||
if(status == LoaderStatusErrorAppStarted) {
|
if(status == LoaderStatusErrorAppStarted) {
|
||||||
result = PB_CommandStatus_ERROR_APP_SYSTEM_LOCKED;
|
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) {
|
static void rpc_system_app_lock_status_process(const PB_Main* request, void* context) {
|
||||||
furi_assert(request);
|
furi_assert(request);
|
||||||
|
furi_assert(context);
|
||||||
|
|
||||||
furi_assert(request->which_content == PB_Main_app_lock_status_request_tag);
|
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_assert(session);
|
||||||
|
|
||||||
FURI_LOG_D(TAG, "LockStatus");
|
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);
|
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) {
|
void* rpc_system_app_alloc(RpcSession* session) {
|
||||||
furi_assert(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 = {
|
RpcHandler rpc_handler = {
|
||||||
.message_handler = NULL,
|
.message_handler = NULL,
|
||||||
.decode_submessage = NULL,
|
.decode_submessage = NULL,
|
||||||
.context = session,
|
.context = rpc_app,
|
||||||
};
|
};
|
||||||
|
|
||||||
rpc_handler.message_handler = rpc_system_app_start_process;
|
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_handler.message_handler = rpc_system_app_lock_status_process;
|
||||||
rpc_add_handler(session, PB_Main_app_lock_status_request_tag, &rpc_handler);
|
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);
|
||||||
}
|
}
|
||||||
|
|||||||
24
applications/rpc/rpc_app.h
Normal file
24
applications/rpc/rpc_app.h
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "rpc.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
RpcAppEventSessionClose,
|
||||||
|
RpcAppEventAppExit,
|
||||||
|
RpcAppEventLoadFile,
|
||||||
|
RpcAppEventButtonPress,
|
||||||
|
RpcAppEventButtonRelease,
|
||||||
|
} RpcAppSystemEvent;
|
||||||
|
|
||||||
|
typedef bool (*RpcAppSystemCallback)(RpcAppSystemEvent event, const char* arg, void* context);
|
||||||
|
|
||||||
|
typedef struct RpcAppSystem RpcAppSystem;
|
||||||
|
|
||||||
|
void rpc_system_app_set_callback(RpcAppSystem* rpc_app, RpcAppSystemCallback callback, void* ctx);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
221
applications/rpc/rpc_gpio.c
Normal file
221
applications/rpc/rpc_gpio.c
Normal file
@ -0,0 +1,221 @@
|
|||||||
|
#include "flipper.pb.h"
|
||||||
|
#include "rpc_i.h"
|
||||||
|
#include "gpio.pb.h"
|
||||||
|
#include <furi_hal_gpio.h>
|
||||||
|
#include <furi_hal_resources.h>
|
||||||
|
|
||||||
|
static const GpioPin* rpc_pin_to_hal_pin(PB_Gpio_GpioPin rpc_pin) {
|
||||||
|
switch(rpc_pin) {
|
||||||
|
case PB_Gpio_GpioPin_PC0:
|
||||||
|
return &gpio_ext_pc0;
|
||||||
|
case PB_Gpio_GpioPin_PC1:
|
||||||
|
return &gpio_ext_pc1;
|
||||||
|
case PB_Gpio_GpioPin_PC3:
|
||||||
|
return &gpio_ext_pc3;
|
||||||
|
case PB_Gpio_GpioPin_PB2:
|
||||||
|
return &gpio_ext_pb2;
|
||||||
|
case PB_Gpio_GpioPin_PB3:
|
||||||
|
return &gpio_ext_pb3;
|
||||||
|
case PB_Gpio_GpioPin_PA4:
|
||||||
|
return &gpio_ext_pa4;
|
||||||
|
case PB_Gpio_GpioPin_PA6:
|
||||||
|
return &gpio_ext_pa6;
|
||||||
|
case PB_Gpio_GpioPin_PA7:
|
||||||
|
return &gpio_ext_pa7;
|
||||||
|
}
|
||||||
|
|
||||||
|
__builtin_unreachable();
|
||||||
|
}
|
||||||
|
|
||||||
|
static GpioMode rpc_mode_to_hal_mode(PB_Gpio_GpioPinMode rpc_mode) {
|
||||||
|
switch(rpc_mode) {
|
||||||
|
case PB_Gpio_GpioPinMode_OUTPUT:
|
||||||
|
return GpioModeOutputPushPull;
|
||||||
|
case PB_Gpio_GpioPinMode_INPUT:
|
||||||
|
return GpioModeInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
__builtin_unreachable();
|
||||||
|
}
|
||||||
|
|
||||||
|
static GpioPull rpc_pull_mode_to_hall_pull_mode(PB_Gpio_GpioInputPull pull_mode) {
|
||||||
|
switch(pull_mode) {
|
||||||
|
case PB_Gpio_GpioInputPull_UP:
|
||||||
|
return GpioPullUp;
|
||||||
|
case PB_Gpio_GpioInputPull_DOWN:
|
||||||
|
return GpioPullDown;
|
||||||
|
case PB_Gpio_GpioInputPull_NO:
|
||||||
|
return GpioPullNo;
|
||||||
|
}
|
||||||
|
|
||||||
|
__builtin_unreachable();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void rpc_system_gpio_set_pin_mode(const PB_Main* request, void* context) {
|
||||||
|
furi_assert(request);
|
||||||
|
furi_assert(context);
|
||||||
|
furi_assert(request->which_content == PB_Main_gpio_set_pin_mode_tag);
|
||||||
|
|
||||||
|
RpcSession* session = context;
|
||||||
|
furi_assert(session);
|
||||||
|
|
||||||
|
PB_Gpio_SetPinMode cmd = request->content.gpio_set_pin_mode;
|
||||||
|
const GpioPin* pin = rpc_pin_to_hal_pin(cmd.pin);
|
||||||
|
GpioMode mode = rpc_mode_to_hal_mode(cmd.mode);
|
||||||
|
|
||||||
|
furi_hal_gpio_init_simple(pin, mode);
|
||||||
|
if(mode == GpioModeOutputPushPull) {
|
||||||
|
furi_hal_gpio_write(pin, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
rpc_send_and_release_empty(session, request->command_id, PB_CommandStatus_OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void rpc_system_gpio_write_pin(const PB_Main* request, void* context) {
|
||||||
|
furi_assert(request);
|
||||||
|
furi_assert(context);
|
||||||
|
furi_assert(request->which_content == PB_Main_gpio_write_pin_tag);
|
||||||
|
|
||||||
|
RpcSession* session = context;
|
||||||
|
furi_assert(session);
|
||||||
|
|
||||||
|
PB_Gpio_WritePin cmd = request->content.gpio_write_pin;
|
||||||
|
const GpioPin* pin = rpc_pin_to_hal_pin(cmd.pin);
|
||||||
|
uint8_t value = !!(cmd.value);
|
||||||
|
|
||||||
|
PB_Main* response = malloc(sizeof(PB_Main));
|
||||||
|
response->command_id = request->command_id;
|
||||||
|
response->has_next = false;
|
||||||
|
|
||||||
|
if(LL_GPIO_MODE_OUTPUT != LL_GPIO_GetPinMode(pin->port, pin->pin)) {
|
||||||
|
response->command_status = PB_CommandStatus_ERROR_GPIO_MODE_INCORRECT;
|
||||||
|
} else {
|
||||||
|
response->command_status = PB_CommandStatus_OK;
|
||||||
|
furi_hal_gpio_write(pin, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
rpc_send_and_release(session, response);
|
||||||
|
|
||||||
|
free(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void rpc_system_gpio_read_pin(const PB_Main* request, void* context) {
|
||||||
|
furi_assert(request);
|
||||||
|
furi_assert(context);
|
||||||
|
furi_assert(request->which_content == PB_Main_gpio_read_pin_tag);
|
||||||
|
|
||||||
|
RpcSession* session = context;
|
||||||
|
furi_assert(session);
|
||||||
|
|
||||||
|
PB_Gpio_ReadPin cmd = request->content.gpio_read_pin;
|
||||||
|
const GpioPin* pin = rpc_pin_to_hal_pin(cmd.pin);
|
||||||
|
|
||||||
|
PB_Main* response = malloc(sizeof(PB_Main));
|
||||||
|
response->command_id = request->command_id;
|
||||||
|
response->has_next = false;
|
||||||
|
|
||||||
|
if(LL_GPIO_MODE_INPUT != LL_GPIO_GetPinMode(pin->port, pin->pin)) {
|
||||||
|
response->command_status = PB_CommandStatus_ERROR_GPIO_MODE_INCORRECT;
|
||||||
|
} else {
|
||||||
|
response->command_status = PB_CommandStatus_OK;
|
||||||
|
response->which_content = PB_Main_gpio_read_pin_response_tag;
|
||||||
|
response->content.gpio_read_pin_response.value = !!furi_hal_gpio_read(pin);
|
||||||
|
}
|
||||||
|
|
||||||
|
rpc_send_and_release(session, response);
|
||||||
|
|
||||||
|
free(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
void rpc_system_gpio_get_pin_mode(const PB_Main* request, void* context) {
|
||||||
|
furi_assert(request);
|
||||||
|
furi_assert(context);
|
||||||
|
furi_assert(request->which_content == PB_Main_gpio_get_pin_mode_tag);
|
||||||
|
|
||||||
|
RpcSession* session = context;
|
||||||
|
furi_assert(session);
|
||||||
|
|
||||||
|
PB_Gpio_GetPinMode cmd = request->content.gpio_get_pin_mode;
|
||||||
|
const GpioPin* pin = rpc_pin_to_hal_pin(cmd.pin);
|
||||||
|
|
||||||
|
PB_Main* response = malloc(sizeof(PB_Main));
|
||||||
|
response->command_id = request->command_id;
|
||||||
|
response->has_next = false;
|
||||||
|
|
||||||
|
uint32_t raw_pin_mode = LL_GPIO_GetPinMode(pin->port, pin->pin);
|
||||||
|
|
||||||
|
PB_Gpio_GpioPinMode pin_mode;
|
||||||
|
if(LL_GPIO_MODE_INPUT == raw_pin_mode) {
|
||||||
|
pin_mode = PB_Gpio_GpioPinMode_INPUT;
|
||||||
|
response->command_status = PB_CommandStatus_OK;
|
||||||
|
} else if(LL_GPIO_MODE_OUTPUT == raw_pin_mode) {
|
||||||
|
pin_mode = PB_Gpio_GpioPinMode_OUTPUT;
|
||||||
|
response->command_status = PB_CommandStatus_OK;
|
||||||
|
} else {
|
||||||
|
pin_mode = PB_Gpio_GpioPinMode_INPUT;
|
||||||
|
response->command_status = PB_CommandStatus_ERROR_GPIO_UNKNOWN_PIN_MODE;
|
||||||
|
}
|
||||||
|
|
||||||
|
response->which_content = PB_Main_gpio_get_pin_mode_response_tag;
|
||||||
|
response->content.gpio_get_pin_mode_response.mode = pin_mode;
|
||||||
|
|
||||||
|
rpc_send_and_release(session, response);
|
||||||
|
|
||||||
|
free(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
void rpc_system_gpio_set_input_pull(const PB_Main* request, void* context) {
|
||||||
|
furi_assert(request);
|
||||||
|
furi_assert(context);
|
||||||
|
furi_assert(request->which_content == PB_Main_gpio_set_input_pull_tag);
|
||||||
|
|
||||||
|
RpcSession* session = context;
|
||||||
|
furi_assert(session);
|
||||||
|
|
||||||
|
PB_Gpio_SetInputPull cmd = request->content.gpio_set_input_pull;
|
||||||
|
const GpioPin* pin = rpc_pin_to_hal_pin(cmd.pin);
|
||||||
|
const GpioPull pull_mode = rpc_pull_mode_to_hall_pull_mode(cmd.pull_mode);
|
||||||
|
|
||||||
|
PB_Main* response = malloc(sizeof(PB_Main));
|
||||||
|
response->command_id = request->command_id;
|
||||||
|
response->has_next = false;
|
||||||
|
|
||||||
|
PB_CommandStatus status;
|
||||||
|
if(LL_GPIO_MODE_INPUT != LL_GPIO_GetPinMode(pin->port, pin->pin)) {
|
||||||
|
status = PB_CommandStatus_ERROR_GPIO_MODE_INCORRECT;
|
||||||
|
} else {
|
||||||
|
status = PB_CommandStatus_OK;
|
||||||
|
furi_hal_gpio_init(pin, GpioModeInput, pull_mode, GpioSpeedLow);
|
||||||
|
}
|
||||||
|
|
||||||
|
rpc_send_and_release_empty(session, request->command_id, status);
|
||||||
|
|
||||||
|
free(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
void* rpc_system_gpio_alloc(RpcSession* session) {
|
||||||
|
furi_assert(session);
|
||||||
|
|
||||||
|
RpcHandler rpc_handler = {
|
||||||
|
.message_handler = NULL,
|
||||||
|
.decode_submessage = NULL,
|
||||||
|
.context = session,
|
||||||
|
};
|
||||||
|
|
||||||
|
rpc_handler.message_handler = rpc_system_gpio_set_pin_mode;
|
||||||
|
rpc_add_handler(session, PB_Main_gpio_set_pin_mode_tag, &rpc_handler);
|
||||||
|
|
||||||
|
rpc_handler.message_handler = rpc_system_gpio_write_pin;
|
||||||
|
rpc_add_handler(session, PB_Main_gpio_write_pin_tag, &rpc_handler);
|
||||||
|
|
||||||
|
rpc_handler.message_handler = rpc_system_gpio_read_pin;
|
||||||
|
rpc_add_handler(session, PB_Main_gpio_read_pin_tag, &rpc_handler);
|
||||||
|
|
||||||
|
rpc_handler.message_handler = rpc_system_gpio_get_pin_mode;
|
||||||
|
rpc_add_handler(session, PB_Main_gpio_get_pin_mode_tag, &rpc_handler);
|
||||||
|
|
||||||
|
rpc_handler.message_handler = rpc_system_gpio_set_input_pull;
|
||||||
|
rpc_add_handler(session, PB_Main_gpio_set_input_pull_tag, &rpc_handler);
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
@ -29,8 +29,11 @@ void* rpc_system_system_alloc(RpcSession* session);
|
|||||||
void* rpc_system_storage_alloc(RpcSession* session);
|
void* rpc_system_storage_alloc(RpcSession* session);
|
||||||
void rpc_system_storage_free(void* ctx);
|
void rpc_system_storage_free(void* ctx);
|
||||||
void* rpc_system_app_alloc(RpcSession* session);
|
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_alloc(RpcSession* session);
|
||||||
void rpc_system_gui_free(void* ctx);
|
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_print_message(const PB_Main* message);
|
||||||
void rpc_cli_command_start_session(Cli* cli, string_t args, void* context);
|
void rpc_cli_command_start_session(Cli* cli, string_t args, void* context);
|
||||||
|
|||||||
@ -277,10 +277,6 @@ static void rpc_system_system_update_request_process(const PB_Main* request, voi
|
|||||||
|
|
||||||
UpdatePrepareResult update_prepare_result =
|
UpdatePrepareResult update_prepare_result =
|
||||||
update_operation_prepare(request->content.system_update_request.update_manifest);
|
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));
|
PB_Main* response = malloc(sizeof(PB_Main));
|
||||||
response->command_id = request->command_id;
|
response->command_id = request->command_id;
|
||||||
|
|||||||
@ -29,6 +29,8 @@ typedef enum {
|
|||||||
GameStateGameOver,
|
GameStateGameOver,
|
||||||
} GameState;
|
} GameState;
|
||||||
|
|
||||||
|
// Note: do not change without purpose. Current values are used in smart
|
||||||
|
// orthogonality calculation in `snake_game_get_turn_snake`.
|
||||||
typedef enum {
|
typedef enum {
|
||||||
DirectionUp,
|
DirectionUp,
|
||||||
DirectionRight,
|
DirectionRight,
|
||||||
@ -195,44 +197,9 @@ static bool
|
|||||||
}
|
}
|
||||||
|
|
||||||
static Direction snake_game_get_turn_snake(SnakeState const* const snake_state) {
|
static Direction snake_game_get_turn_snake(SnakeState const* const snake_state) {
|
||||||
switch(snake_state->currentMovement) {
|
// Sum of two `Direction` lies between 0 and 6, odd values indicate orthogonality.
|
||||||
case DirectionUp:
|
bool is_orthogonal = (snake_state->currentMovement + snake_state->nextMovement) % 2 == 1;
|
||||||
switch(snake_state->nextMovement) {
|
return is_orthogonal ? snake_state->nextMovement : snake_state->currentMovement;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static Point snake_game_get_next_step(SnakeState const* const snake_state) {
|
static Point snake_game_get_next_step(SnakeState const* const snake_state) {
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
#define TAG "StorageInt"
|
#define TAG "StorageInt"
|
||||||
#define STORAGE_PATH "/int"
|
#define STORAGE_PATH "/int"
|
||||||
|
#define LFS_CLEAN_FINGERPRINT 0
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
const size_t start_address;
|
const size_t start_address;
|
||||||
@ -162,8 +163,9 @@ static LFSData* storage_int_lfs_data_alloc() {
|
|||||||
return lfs_data;
|
return lfs_data;
|
||||||
};
|
};
|
||||||
|
|
||||||
static bool storage_int_is_fingerprint_valid(LFSData* lfs_data) {
|
// Returns true if fingerprint was invalid and LFS reformatting is needed
|
||||||
bool value = true;
|
static bool storage_int_check_and_set_fingerprint(LFSData* lfs_data) {
|
||||||
|
bool value = false;
|
||||||
|
|
||||||
uint32_t os_fingerprint = 0;
|
uint32_t os_fingerprint = 0;
|
||||||
os_fingerprint |= ((lfs_data->start_page & 0xFF) << 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);
|
os_fingerprint |= ((LFS_DISK_VERSION_MAJOR & 0xFFFF) << 16);
|
||||||
|
|
||||||
uint32_t rtc_fingerprint = furi_hal_rtc_get_register(FuriHalRtcRegisterLfsFingerprint);
|
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_LOG_I(TAG, "Storing LFS fingerprint in RTC");
|
||||||
furi_hal_rtc_set_register(FuriHalRtcRegisterLfsFingerprint, os_fingerprint);
|
furi_hal_rtc_set_register(FuriHalRtcRegisterLfsFingerprint, os_fingerprint);
|
||||||
} else if(rtc_fingerprint != os_fingerprint) {
|
} else if(rtc_fingerprint != os_fingerprint) {
|
||||||
FURI_LOG_E(TAG, "LFS fingerprint mismatch");
|
FURI_LOG_E(TAG, "LFS fingerprint mismatch");
|
||||||
furi_hal_rtc_set_register(FuriHalRtcRegisterLfsFingerprint, os_fingerprint);
|
furi_hal_rtc_set_register(FuriHalRtcRegisterLfsFingerprint, os_fingerprint);
|
||||||
value = false;
|
value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
@ -187,8 +189,9 @@ static void storage_int_lfs_mount(LFSData* lfs_data, StorageData* storage) {
|
|||||||
int err;
|
int err;
|
||||||
lfs_t* lfs = &lfs_data->lfs;
|
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) ||
|
bool need_format = furi_hal_rtc_is_flag_set(FuriHalRtcFlagFactoryReset) ||
|
||||||
!storage_int_is_fingerprint_valid(lfs_data);
|
was_fingerprint_outdated;
|
||||||
|
|
||||||
if(need_format) {
|
if(need_format) {
|
||||||
// Format storage
|
// Format storage
|
||||||
@ -655,11 +658,13 @@ static FS_Error storage_int_common_fs_info(
|
|||||||
lfs_t* lfs = lfs_get_from_storage(storage);
|
lfs_t* lfs = lfs_get_from_storage(storage);
|
||||||
LFSData* lfs_data = lfs_data_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);
|
lfs_ssize_t result = lfs_fs_size(lfs);
|
||||||
if(result >= 0) {
|
if(free_space && (result >= 0)) {
|
||||||
*free_space = *total_space - (result * lfs_data->config.block_size);
|
*free_space = (lfs_data->config.block_count - result) * lfs_data->config.block_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
return storage_int_parse_error(result);
|
return storage_int_parse_error(result);
|
||||||
|
|||||||
@ -41,6 +41,7 @@ typedef enum {
|
|||||||
SubGhzCustomEventSceneShowOnlyRX,
|
SubGhzCustomEventSceneShowOnlyRX,
|
||||||
SubGhzCustomEventSceneAnalyzerLock,
|
SubGhzCustomEventSceneAnalyzerLock,
|
||||||
SubGhzCustomEventSceneAnalyzerUnlock,
|
SubGhzCustomEventSceneAnalyzerUnlock,
|
||||||
|
SubGhzCustomEventSceneSettingLock,
|
||||||
|
|
||||||
SubGhzCustomEventSceneExit,
|
SubGhzCustomEventSceneExit,
|
||||||
SubGhzCustomEventSceneStay,
|
SubGhzCustomEventSceneStay,
|
||||||
@ -48,6 +49,8 @@ typedef enum {
|
|||||||
SubGhzCustomEventViewReceiverOK,
|
SubGhzCustomEventViewReceiverOK,
|
||||||
SubGhzCustomEventViewReceiverConfig,
|
SubGhzCustomEventViewReceiverConfig,
|
||||||
SubGhzCustomEventViewReceiverBack,
|
SubGhzCustomEventViewReceiverBack,
|
||||||
|
SubGhzCustomEventViewReceiverOffDisplay,
|
||||||
|
SubGhzCustomEventViewReceiverUnlock,
|
||||||
|
|
||||||
SubGhzCustomEventViewReadRAWBack,
|
SubGhzCustomEventViewReadRAWBack,
|
||||||
SubGhzCustomEventViewReadRAWIDLE,
|
SubGhzCustomEventViewReadRAWIDLE,
|
||||||
|
|||||||
69
applications/subghz/helpers/subghz_types.h
Normal file
69
applications/subghz/helpers/subghz_types.h
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
/** SubGhzNotification state */
|
||||||
|
typedef enum {
|
||||||
|
SubGhzNotificationStateStarting,
|
||||||
|
SubGhzNotificationStateIDLE,
|
||||||
|
SubGhzNotificationStateTx,
|
||||||
|
SubGhzNotificationStateRx,
|
||||||
|
SubGhzNotificationStateRxDone,
|
||||||
|
} SubGhzNotificationState;
|
||||||
|
|
||||||
|
/** SubGhzTxRx state */
|
||||||
|
typedef enum {
|
||||||
|
SubGhzTxRxStateIDLE,
|
||||||
|
SubGhzTxRxStateRx,
|
||||||
|
SubGhzTxRxStateTx,
|
||||||
|
SubGhzTxRxStateSleep,
|
||||||
|
} SubGhzTxRxState;
|
||||||
|
|
||||||
|
/** SubGhzHopperState state */
|
||||||
|
typedef enum {
|
||||||
|
SubGhzHopperStateOFF,
|
||||||
|
SubGhzHopperStateRunnig,
|
||||||
|
SubGhzHopperStatePause,
|
||||||
|
SubGhzHopperStateRSSITimeOut,
|
||||||
|
} SubGhzHopperState;
|
||||||
|
|
||||||
|
/** SubGhzRxKeyState state */
|
||||||
|
typedef enum {
|
||||||
|
SubGhzRxKeyStateIDLE,
|
||||||
|
SubGhzRxKeyStateNoSave,
|
||||||
|
SubGhzRxKeyStateNeedSave,
|
||||||
|
SubGhzRxKeyStateBack,
|
||||||
|
SubGhzRxKeyStateStart,
|
||||||
|
SubGhzRxKeyStateAddKey,
|
||||||
|
SubGhzRxKeyStateExit,
|
||||||
|
SubGhzRxKeyStateRAWLoad,
|
||||||
|
SubGhzRxKeyStateRAWSave,
|
||||||
|
} SubGhzRxKeyState;
|
||||||
|
|
||||||
|
/** SubGhzLoadKeyState state */
|
||||||
|
typedef enum {
|
||||||
|
SubGhzLoadKeyStateUnknown,
|
||||||
|
SubGhzLoadKeyStateOK,
|
||||||
|
SubGhzLoadKeyStateParseErr,
|
||||||
|
SubGhzLoadKeyStateOnlyRx,
|
||||||
|
} SubGhzLoadKeyState;
|
||||||
|
|
||||||
|
/** SubGhzLock */
|
||||||
|
typedef enum {
|
||||||
|
SubGhzLockOff,
|
||||||
|
SubGhzLockOn,
|
||||||
|
} SubGhzLock;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
SubGhzViewIdMenu,
|
||||||
|
SubGhzViewIdReceiver,
|
||||||
|
SubGhzViewIdPopup,
|
||||||
|
SubGhzViewIdTextInput,
|
||||||
|
SubGhzViewIdWidget,
|
||||||
|
SubGhzViewIdTransmitter,
|
||||||
|
SubGhzViewIdVariableItemList,
|
||||||
|
SubGhzViewIdFrequencyAnalyzer,
|
||||||
|
SubGhzViewIdReadRAW,
|
||||||
|
|
||||||
|
SubGhzViewIdStatic,
|
||||||
|
SubGhzViewIdTestCarrier,
|
||||||
|
SubGhzViewIdTestPacket,
|
||||||
|
} SubGhzViewId;
|
||||||
@ -22,3 +22,4 @@ ADD_SCENE(subghz, read_raw, ReadRAW)
|
|||||||
ADD_SCENE(subghz, more_raw, MoreRAW)
|
ADD_SCENE(subghz, more_raw, MoreRAW)
|
||||||
ADD_SCENE(subghz, delete_raw, DeleteRAW)
|
ADD_SCENE(subghz, delete_raw, DeleteRAW)
|
||||||
ADD_SCENE(subghz, need_saving, NeedSaving)
|
ADD_SCENE(subghz, need_saving, NeedSaving)
|
||||||
|
ADD_SCENE(subghz, rpc, Rpc)
|
||||||
|
|||||||
@ -106,6 +106,7 @@ void subghz_scene_read_raw_on_enter(void* context) {
|
|||||||
|
|
||||||
bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) {
|
bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) {
|
||||||
SubGhz* subghz = context;
|
SubGhz* subghz = context;
|
||||||
|
bool consumed = false;
|
||||||
if(event.type == SceneManagerEventTypeCustom) {
|
if(event.type == SceneManagerEventTypeCustom) {
|
||||||
switch(event.event) {
|
switch(event.event) {
|
||||||
case SubGhzCustomEventViewReadRAWBack:
|
case SubGhzCustomEventViewReadRAWBack:
|
||||||
@ -141,7 +142,7 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
consumed = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SubGhzCustomEventViewReadRAWTXRXStop:
|
case SubGhzCustomEventViewReadRAWTXRXStop:
|
||||||
@ -156,14 +157,14 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) {
|
|||||||
subghz_sleep(subghz);
|
subghz_sleep(subghz);
|
||||||
};
|
};
|
||||||
subghz->state_notifications = SubGhzNotificationStateIDLE;
|
subghz->state_notifications = SubGhzNotificationStateIDLE;
|
||||||
return true;
|
consumed = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SubGhzCustomEventViewReadRAWConfig:
|
case SubGhzCustomEventViewReadRAWConfig:
|
||||||
scene_manager_set_scene_state(
|
scene_manager_set_scene_state(
|
||||||
subghz->scene_manager, SubGhzSceneReadRAW, SubGhzCustomEventManagerSet);
|
subghz->scene_manager, SubGhzSceneReadRAW, SubGhzCustomEventManagerSet);
|
||||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReceiverConfig);
|
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReceiverConfig);
|
||||||
return true;
|
consumed = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SubGhzCustomEventViewReadRAWErase:
|
case SubGhzCustomEventViewReadRAWErase:
|
||||||
@ -175,7 +176,7 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) {
|
|||||||
}
|
}
|
||||||
subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE;
|
subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE;
|
||||||
notification_message(subghz->notifications, &sequence_reset_rgb);
|
notification_message(subghz->notifications, &sequence_reset_rgb);
|
||||||
return true;
|
consumed = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SubGhzCustomEventViewReadRAWMore:
|
case SubGhzCustomEventViewReadRAWMore:
|
||||||
@ -184,7 +185,7 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) {
|
|||||||
subghz->scene_manager, SubGhzSceneReadRAW, SubGhzCustomEventManagerSet);
|
subghz->scene_manager, SubGhzSceneReadRAW, SubGhzCustomEventManagerSet);
|
||||||
subghz->txrx->rx_key_state = SubGhzRxKeyStateRAWLoad;
|
subghz->txrx->rx_key_state = SubGhzRxKeyStateRAWLoad;
|
||||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneMoreRAW);
|
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneMoreRAW);
|
||||||
return true;
|
consumed = true;
|
||||||
} else {
|
} else {
|
||||||
furi_crash("SubGhz: RAW file name update error.");
|
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;
|
break;
|
||||||
|
|
||||||
case SubGhzCustomEventViewReadRAWSendStop:
|
case SubGhzCustomEventViewReadRAWSendStop:
|
||||||
@ -224,7 +225,7 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) {
|
|||||||
subghz_sleep(subghz);
|
subghz_sleep(subghz);
|
||||||
}
|
}
|
||||||
subghz_read_raw_stop_send(subghz->subghz_read_raw);
|
subghz_read_raw_stop_send(subghz->subghz_read_raw);
|
||||||
return true;
|
consumed = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SubGhzCustomEventViewReadRAWIDLE:
|
case SubGhzCustomEventViewReadRAWIDLE:
|
||||||
@ -255,7 +256,7 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) {
|
|||||||
subghz->state_notifications = SubGhzNotificationStateIDLE;
|
subghz->state_notifications = SubGhzNotificationStateIDLE;
|
||||||
subghz->txrx->rx_key_state = SubGhzRxKeyStateAddKey;
|
subghz->txrx->rx_key_state = SubGhzRxKeyStateAddKey;
|
||||||
|
|
||||||
return true;
|
consumed = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SubGhzCustomEventViewReadRAWREC:
|
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);
|
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
consumed = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SubGhzCustomEventViewReadRAWSave:
|
case SubGhzCustomEventViewReadRAWSave:
|
||||||
@ -291,7 +292,7 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) {
|
|||||||
subghz->txrx->rx_key_state = SubGhzRxKeyStateBack;
|
subghz->txrx->rx_key_state = SubGhzRxKeyStateBack;
|
||||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveName);
|
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveName);
|
||||||
}
|
}
|
||||||
return true;
|
consumed = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@ -315,7 +316,7 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return consumed;
|
||||||
}
|
}
|
||||||
|
|
||||||
void subghz_scene_read_raw_on_exit(void* context) {
|
void subghz_scene_read_raw_on_exit(void* context) {
|
||||||
|
|||||||
@ -14,6 +14,23 @@ static const NotificationSequence subghs_sequence_rx = {
|
|||||||
NULL,
|
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) {
|
static void subghz_scene_receiver_update_statusbar(void* context) {
|
||||||
SubGhz* subghz = context;
|
SubGhz* subghz = context;
|
||||||
string_t history_stat_str;
|
string_t history_stat_str;
|
||||||
@ -92,6 +109,8 @@ void subghz_scene_receiver_on_enter(void* context) {
|
|||||||
subghz->txrx->rx_key_state = SubGhzRxKeyStateStart;
|
subghz->txrx->rx_key_state = SubGhzRxKeyStateStart;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
subghz_view_receiver_set_lock(subghz->subghz_receiver, subghz->lock);
|
||||||
|
|
||||||
//Load history to receiver
|
//Load history to receiver
|
||||||
subghz_view_receiver_exit(subghz->subghz_receiver);
|
subghz_view_receiver_exit(subghz->subghz_receiver);
|
||||||
for(uint8_t i = 0; i < subghz_history_get_item(subghz->txrx->history); i++) {
|
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) {
|
bool subghz_scene_receiver_on_event(void* context, SceneManagerEvent event) {
|
||||||
SubGhz* subghz = context;
|
SubGhz* subghz = context;
|
||||||
|
bool consumed = false;
|
||||||
if(event.type == SceneManagerEventTypeCustom) {
|
if(event.type == SceneManagerEventTypeCustom) {
|
||||||
switch(event.event) {
|
switch(event.event) {
|
||||||
case SubGhzCustomEventViewReceiverBack:
|
case SubGhzCustomEventViewReceiverBack:
|
||||||
|
|
||||||
// Stop CC1101 Rx
|
// Stop CC1101 Rx
|
||||||
subghz->state_notifications = SubGhzNotificationStateIDLE;
|
subghz->state_notifications = SubGhzNotificationStateIDLE;
|
||||||
if(subghz->txrx->txrx_state == SubGhzTxRxStateRx) {
|
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(
|
scene_manager_search_and_switch_to_previous_scene(
|
||||||
subghz->scene_manager, SubGhzSceneStart);
|
subghz->scene_manager, SubGhzSceneStart);
|
||||||
}
|
}
|
||||||
return true;
|
consumed = true;
|
||||||
break;
|
break;
|
||||||
case SubGhzCustomEventViewReceiverOK:
|
case SubGhzCustomEventViewReceiverOK:
|
||||||
subghz->txrx->idx_menu_chosen =
|
subghz->txrx->idx_menu_chosen =
|
||||||
subghz_view_receiver_get_idx_menu(subghz->subghz_receiver);
|
subghz_view_receiver_get_idx_menu(subghz->subghz_receiver);
|
||||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReceiverInfo);
|
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReceiverInfo);
|
||||||
return true;
|
consumed = true;
|
||||||
break;
|
break;
|
||||||
case SubGhzCustomEventViewReceiverConfig:
|
case SubGhzCustomEventViewReceiverConfig:
|
||||||
subghz->state_notifications = SubGhzNotificationStateIDLE;
|
subghz->state_notifications = SubGhzNotificationStateIDLE;
|
||||||
subghz->txrx->idx_menu_chosen =
|
subghz->txrx->idx_menu_chosen =
|
||||||
subghz_view_receiver_get_idx_menu(subghz->subghz_receiver);
|
subghz_view_receiver_get_idx_menu(subghz->subghz_receiver);
|
||||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReceiverConfig);
|
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;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
@ -174,20 +200,23 @@ bool subghz_scene_receiver_on_event(void* context, SceneManagerEvent event) {
|
|||||||
subghz_hopper_update(subghz);
|
subghz_hopper_update(subghz);
|
||||||
subghz_scene_receiver_update_statusbar(subghz);
|
subghz_scene_receiver_update_statusbar(subghz);
|
||||||
}
|
}
|
||||||
|
|
||||||
switch(subghz->state_notifications) {
|
switch(subghz->state_notifications) {
|
||||||
case SubGhzNotificationStateRx:
|
case SubGhzNotificationStateRx:
|
||||||
notification_message(subghz->notifications, &sequence_blink_cyan_10);
|
notification_message(subghz->notifications, &sequence_blink_cyan_10);
|
||||||
break;
|
break;
|
||||||
case SubGhzNotificationStateRxDone:
|
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;
|
subghz->state_notifications = SubGhzNotificationStateRx;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return consumed;
|
||||||
}
|
}
|
||||||
|
|
||||||
void subghz_scene_receiver_on_exit(void* context) {
|
void subghz_scene_receiver_on_exit(void* context) {
|
||||||
|
|||||||
@ -1,5 +1,12 @@
|
|||||||
#include "../subghz_i.h"
|
#include "../subghz_i.h"
|
||||||
|
|
||||||
|
enum SubGhzSettingIndex {
|
||||||
|
SubGhzSettingIndexFrequency,
|
||||||
|
SubGhzSettingIndexHopping,
|
||||||
|
SubGhzSettingIndexModulation,
|
||||||
|
SubGhzSettingIndexLock,
|
||||||
|
};
|
||||||
|
|
||||||
#define PRESET_COUNT 4
|
#define PRESET_COUNT 4
|
||||||
const char* const preset_text[PRESET_COUNT] = {
|
const char* const preset_text[PRESET_COUNT] = {
|
||||||
"AM270",
|
"AM270",
|
||||||
@ -137,6 +144,15 @@ static void subghz_scene_receiver_config_set_hopping_runing(VariableItem* item)
|
|||||||
subghz->txrx->hopper_state = hopping_value[index];
|
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) {
|
void subghz_scene_receiver_config_on_enter(void* context) {
|
||||||
SubGhz* subghz = context;
|
SubGhz* subghz = context;
|
||||||
VariableItem* item;
|
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_index(item, value_index);
|
||||||
variable_item_set_current_value_text(item, preset_text[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);
|
view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdVariableItemList);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool subghz_scene_receiver_config_on_event(void* context, SceneManagerEvent event) {
|
bool subghz_scene_receiver_config_on_event(void* context, SceneManagerEvent event) {
|
||||||
UNUSED(context);
|
SubGhz* subghz = context;
|
||||||
UNUSED(event);
|
bool consumed = false;
|
||||||
return 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) {
|
void subghz_scene_receiver_config_on_exit(void* context) {
|
||||||
|
|||||||
34
applications/subghz/scenes/subghz_scene_rpc.c
Normal file
34
applications/subghz/scenes/subghz_scene_rpc.c
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
#include "../subghz_i.h"
|
||||||
|
|
||||||
|
void subghz_scene_rpc_on_enter(void* context) {
|
||||||
|
SubGhz* subghz = context;
|
||||||
|
Widget* widget = subghz->widget;
|
||||||
|
|
||||||
|
widget_add_text_box_element(
|
||||||
|
widget, 0, 0, 128, 28, AlignCenter, AlignCenter, "RPC mode", false);
|
||||||
|
|
||||||
|
notification_message(subghz->notifications, &sequence_display_backlight_on);
|
||||||
|
|
||||||
|
view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdWidget);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool subghz_scene_rpc_on_event(void* context, SceneManagerEvent event) {
|
||||||
|
SubGhz* subghz = context;
|
||||||
|
bool consumed = false;
|
||||||
|
|
||||||
|
if(event.type == SceneManagerEventTypeCustom) {
|
||||||
|
consumed = true;
|
||||||
|
if(event.event == SubGhzCustomEventSceneExit) {
|
||||||
|
view_dispatcher_stop(subghz->view_dispatcher);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return consumed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void subghz_scene_rpc_on_exit(void* context) {
|
||||||
|
SubGhz* subghz = context;
|
||||||
|
|
||||||
|
//subghz_rpc_exit_callback(subghz);
|
||||||
|
|
||||||
|
widget_reset(subghz->widget);
|
||||||
|
}
|
||||||
@ -23,6 +23,54 @@ void subghz_tick_event_callback(void* context) {
|
|||||||
scene_manager_handle_tick_event(subghz->scene_manager);
|
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_alloc() {
|
||||||
SubGhz* subghz = malloc(sizeof(SubGhz));
|
SubGhz* subghz = malloc(sizeof(SubGhz));
|
||||||
|
|
||||||
@ -133,7 +181,8 @@ SubGhz* subghz_alloc() {
|
|||||||
subghz->setting = subghz_setting_alloc();
|
subghz->setting = subghz_setting_alloc();
|
||||||
subghz_setting_load(subghz->setting, "/ext/subghz/assets/setting_user");
|
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 = malloc(sizeof(SubGhzTxRx));
|
||||||
subghz->txrx->frequency = subghz_setting_get_default_frequency(subghz->setting);
|
subghz->txrx->frequency = subghz_setting_get_default_frequency(subghz->setting);
|
||||||
subghz->txrx->preset = FuriHalSubGhzPresetOok650Async;
|
subghz->txrx->preset = FuriHalSubGhzPresetOok650Async;
|
||||||
@ -167,6 +216,11 @@ SubGhz* subghz_alloc() {
|
|||||||
void subghz_free(SubGhz* subghz) {
|
void subghz_free(SubGhz* subghz) {
|
||||||
furi_assert(subghz);
|
furi_assert(subghz);
|
||||||
|
|
||||||
|
if(subghz->rpc_ctx) {
|
||||||
|
rpc_system_app_set_callback(subghz->rpc_ctx, NULL, NULL);
|
||||||
|
subghz->rpc_ctx = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
// Packet Test
|
// Packet Test
|
||||||
view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdTestPacket);
|
view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdTestPacket);
|
||||||
subghz_test_packet_free(subghz->subghz_test_packet);
|
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");
|
subghz->txrx->environment, "/ext/subghz/assets/keeloq_mfcodes_user");
|
||||||
// Check argument and run corresponding scene
|
// Check argument and run corresponding scene
|
||||||
if(p) {
|
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);
|
string_set_str(subghz->file_path, p);
|
||||||
|
|
||||||
if((!strcmp(subghz->txrx->decoder_result->protocol->name, "RAW"))) {
|
if((!strcmp(subghz->txrx->decoder_result->protocol->name, "RAW"))) {
|
||||||
|
|||||||
@ -218,7 +218,7 @@ void subghz_dialog_message_show_only_rx(SubGhz* subghz) {
|
|||||||
dialog_message_free(message);
|
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(subghz);
|
||||||
furi_assert(file_path);
|
furi_assert(file_path);
|
||||||
|
|
||||||
@ -308,11 +308,15 @@ bool subghz_key_load(SubGhz* subghz, const char* file_path) {
|
|||||||
|
|
||||||
switch(load_key_state) {
|
switch(load_key_state) {
|
||||||
case SubGhzLoadKeyStateParseErr:
|
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;
|
return false;
|
||||||
|
|
||||||
case SubGhzLoadKeyStateOnlyRx:
|
case SubGhzLoadKeyStateOnlyRx:
|
||||||
subghz_dialog_message_show_only_rx(subghz);
|
if(show_dialog) {
|
||||||
|
subghz_dialog_message_show_only_rx(subghz);
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
case SubGhzLoadKeyStateOK:
|
case SubGhzLoadKeyStateOK:
|
||||||
@ -427,7 +431,7 @@ bool subghz_load_protocol_from_file(SubGhz* subghz) {
|
|||||||
true);
|
true);
|
||||||
|
|
||||||
if(res) {
|
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);
|
string_clear(file_path);
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "helpers/subghz_types.h"
|
||||||
#include "subghz.h"
|
#include "subghz.h"
|
||||||
#include "views/receiver.h"
|
#include "views/receiver.h"
|
||||||
#include "views/transmitter.h"
|
#include "views/transmitter.h"
|
||||||
@ -35,54 +36,10 @@
|
|||||||
#include <gui/modules/variable_item_list.h>
|
#include <gui/modules/variable_item_list.h>
|
||||||
#include <lib/toolbox/path.h>
|
#include <lib/toolbox/path.h>
|
||||||
|
|
||||||
|
#include "rpc/rpc_app.h"
|
||||||
|
|
||||||
#define SUBGHZ_MAX_LEN_NAME 64
|
#define SUBGHZ_MAX_LEN_NAME 64
|
||||||
|
|
||||||
/** SubGhzNotification state */
|
|
||||||
typedef enum {
|
|
||||||
SubGhzNotificationStateStarting,
|
|
||||||
SubGhzNotificationStateIDLE,
|
|
||||||
SubGhzNotificationStateTx,
|
|
||||||
SubGhzNotificationStateRx,
|
|
||||||
SubGhzNotificationStateRxDone,
|
|
||||||
} SubGhzNotificationState;
|
|
||||||
|
|
||||||
/** SubGhzTxRx state */
|
|
||||||
typedef enum {
|
|
||||||
SubGhzTxRxStateIDLE,
|
|
||||||
SubGhzTxRxStateRx,
|
|
||||||
SubGhzTxRxStateTx,
|
|
||||||
SubGhzTxRxStateSleep,
|
|
||||||
} SubGhzTxRxState;
|
|
||||||
|
|
||||||
/** SubGhzHopperState state */
|
|
||||||
typedef enum {
|
|
||||||
SubGhzHopperStateOFF,
|
|
||||||
SubGhzHopperStateRunnig,
|
|
||||||
SubGhzHopperStatePause,
|
|
||||||
SubGhzHopperStateRSSITimeOut,
|
|
||||||
} SubGhzHopperState;
|
|
||||||
|
|
||||||
/** SubGhzRxKeyState state */
|
|
||||||
typedef enum {
|
|
||||||
SubGhzRxKeyStateIDLE,
|
|
||||||
SubGhzRxKeyStateNoSave,
|
|
||||||
SubGhzRxKeyStateNeedSave,
|
|
||||||
SubGhzRxKeyStateBack,
|
|
||||||
SubGhzRxKeyStateStart,
|
|
||||||
SubGhzRxKeyStateAddKey,
|
|
||||||
SubGhzRxKeyStateExit,
|
|
||||||
SubGhzRxKeyStateRAWLoad,
|
|
||||||
SubGhzRxKeyStateRAWSave,
|
|
||||||
} SubGhzRxKeyState;
|
|
||||||
|
|
||||||
/** SubGhzLoadKeyState state */
|
|
||||||
typedef enum {
|
|
||||||
SubGhzLoadKeyStateUnknown,
|
|
||||||
SubGhzLoadKeyStateOK,
|
|
||||||
SubGhzLoadKeyStateParseErr,
|
|
||||||
SubGhzLoadKeyStateOnlyRx,
|
|
||||||
} SubGhzLoadKeyState;
|
|
||||||
|
|
||||||
struct SubGhzTxRx {
|
struct SubGhzTxRx {
|
||||||
SubGhzWorker* worker;
|
SubGhzWorker* worker;
|
||||||
|
|
||||||
@ -135,24 +92,11 @@ struct SubGhz {
|
|||||||
SubGhzTestPacket* subghz_test_packet;
|
SubGhzTestPacket* subghz_test_packet;
|
||||||
string_t error_str;
|
string_t error_str;
|
||||||
SubGhzSetting* setting;
|
SubGhzSetting* setting;
|
||||||
|
SubGhzLock lock;
|
||||||
|
|
||||||
|
void* rpc_ctx;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
SubGhzViewIdMenu,
|
|
||||||
SubGhzViewIdReceiver,
|
|
||||||
SubGhzViewIdPopup,
|
|
||||||
SubGhzViewIdTextInput,
|
|
||||||
SubGhzViewIdWidget,
|
|
||||||
SubGhzViewIdTransmitter,
|
|
||||||
SubGhzViewIdVariableItemList,
|
|
||||||
SubGhzViewIdFrequencyAnalyzer,
|
|
||||||
SubGhzViewIdReadRAW,
|
|
||||||
|
|
||||||
SubGhzViewIdStatic,
|
|
||||||
SubGhzViewIdTestCarrier,
|
|
||||||
SubGhzViewIdTestPacket,
|
|
||||||
} SubGhzViewId;
|
|
||||||
|
|
||||||
bool subghz_set_preset(SubGhz* subghz, const char* preset);
|
bool subghz_set_preset(SubGhz* subghz, const char* preset);
|
||||||
void subghz_get_frequency_modulation(SubGhz* subghz, string_t frequency, string_t modulation);
|
void subghz_get_frequency_modulation(SubGhz* subghz, string_t frequency, string_t modulation);
|
||||||
void subghz_begin(SubGhz* subghz, FuriHalSubGhzPreset preset);
|
void subghz_begin(SubGhz* subghz, FuriHalSubGhzPreset preset);
|
||||||
@ -162,7 +106,7 @@ void subghz_sleep(SubGhz* subghz);
|
|||||||
bool subghz_tx_start(SubGhz* subghz, FlipperFormat* flipper_format);
|
bool subghz_tx_start(SubGhz* subghz, FlipperFormat* flipper_format);
|
||||||
void subghz_tx_stop(SubGhz* subghz);
|
void subghz_tx_stop(SubGhz* subghz);
|
||||||
void subghz_dialog_message_show_only_rx(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_get_next_name_file(SubGhz* subghz, uint8_t max_len);
|
||||||
bool subghz_save_protocol_to_file(
|
bool subghz_save_protocol_to_file(
|
||||||
SubGhz* subghz,
|
SubGhz* subghz,
|
||||||
|
|||||||
@ -11,6 +11,7 @@
|
|||||||
#define FRAME_HEIGHT 12
|
#define FRAME_HEIGHT 12
|
||||||
#define MAX_LEN_PX 100
|
#define MAX_LEN_PX 100
|
||||||
#define MENU_ITEMS 4u
|
#define MENU_ITEMS 4u
|
||||||
|
#define UNLOCK_CNT 3
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
string_t item_str;
|
string_t item_str;
|
||||||
@ -34,7 +35,17 @@ static const Icon* ReceiverItemIcons[] = {
|
|||||||
[SubGhzProtocolTypeDynamic] = &I_Lock_7x8,
|
[SubGhzProtocolTypeDynamic] = &I_Lock_7x8,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
SubGhzViewReceiverBarShowDefault,
|
||||||
|
SubGhzViewReceiverBarShowLock,
|
||||||
|
SubGhzViewReceiverBarShowToUnlockPress,
|
||||||
|
SubGhzViewReceiverBarShowUnlock,
|
||||||
|
} SubGhzViewReceiverBarShow;
|
||||||
|
|
||||||
struct SubGhzViewReceiver {
|
struct SubGhzViewReceiver {
|
||||||
|
SubGhzLock lock;
|
||||||
|
uint8_t lock_count;
|
||||||
|
osTimerId_t timer;
|
||||||
View* view;
|
View* view;
|
||||||
SubGhzViewReceiverCallback callback;
|
SubGhzViewReceiverCallback callback;
|
||||||
void* context;
|
void* context;
|
||||||
@ -48,8 +59,29 @@ typedef struct {
|
|||||||
uint16_t idx;
|
uint16_t idx;
|
||||||
uint16_t list_offset;
|
uint16_t list_offset;
|
||||||
uint16_t history_item;
|
uint16_t history_item;
|
||||||
|
SubGhzViewReceiverBarShow bar_show;
|
||||||
} SubGhzViewReceiverModel;
|
} 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(
|
void subghz_view_receiver_set_callback(
|
||||||
SubGhzViewReceiver* subghz_receiver,
|
SubGhzViewReceiver* subghz_receiver,
|
||||||
SubGhzViewReceiverCallback callback,
|
SubGhzViewReceiverCallback callback,
|
||||||
@ -138,17 +170,6 @@ void subghz_view_receiver_draw(Canvas* canvas, SubGhzViewReceiverModel* model) {
|
|||||||
canvas_set_font(canvas, FontSecondary);
|
canvas_set_font(canvas, FontSecondary);
|
||||||
|
|
||||||
elements_button_left(canvas, "Config");
|
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);
|
canvas_draw_line(canvas, 46, 51, 125, 51);
|
||||||
|
|
||||||
bool scrollbar = model->history_item > 4;
|
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);
|
elements_scrollbar_pos(canvas, 128, 0, 49, model->idx, model->history_item);
|
||||||
}
|
}
|
||||||
string_clear(str_buff);
|
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) {
|
bool subghz_view_receiver_input(InputEvent* event, void* context) {
|
||||||
furi_assert(context);
|
furi_assert(context);
|
||||||
SubGhzViewReceiver* subghz_receiver = 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) {
|
if(event->key == InputKeyBack && event->type == InputTypeShort) {
|
||||||
subghz_receiver->callback(SubGhzCustomEventViewReceiverBack, subghz_receiver->context);
|
subghz_receiver->callback(SubGhzCustomEventViewReceiverBack, subghz_receiver->context);
|
||||||
} else if(
|
} else if(
|
||||||
@ -240,6 +345,7 @@ void subghz_view_receiver_exit(void* context) {
|
|||||||
model->history_item = 0;
|
model->history_item = 0;
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
osTimerStop(subghz_receiver->timer);
|
||||||
}
|
}
|
||||||
|
|
||||||
SubGhzViewReceiver* subghz_view_receiver_alloc() {
|
SubGhzViewReceiver* subghz_view_receiver_alloc() {
|
||||||
@ -247,6 +353,9 @@ SubGhzViewReceiver* subghz_view_receiver_alloc() {
|
|||||||
|
|
||||||
// View allocation and configuration
|
// View allocation and configuration
|
||||||
subghz_receiver->view = view_alloc();
|
subghz_receiver->view = view_alloc();
|
||||||
|
|
||||||
|
subghz_receiver->lock = SubGhzLockOff;
|
||||||
|
subghz_receiver->lock_count = 0;
|
||||||
view_allocate_model(
|
view_allocate_model(
|
||||||
subghz_receiver->view, ViewModelTypeLocking, sizeof(SubGhzViewReceiverModel));
|
subghz_receiver->view, ViewModelTypeLocking, sizeof(SubGhzViewReceiverModel));
|
||||||
view_set_context(subghz_receiver->view, subghz_receiver);
|
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->frequency_str);
|
||||||
string_init(model->preset_str);
|
string_init(model->preset_str);
|
||||||
string_init(model->history_stat_str);
|
string_init(model->history_stat_str);
|
||||||
|
model->bar_show = SubGhzViewReceiverBarShowDefault;
|
||||||
model->history = malloc(sizeof(SubGhzReceiverHistory));
|
model->history = malloc(sizeof(SubGhzReceiverHistory));
|
||||||
SubGhzReceiverMenuItemArray_init(model->history->data);
|
SubGhzReceiverMenuItemArray_init(model->history->data);
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
subghz_receiver->timer =
|
||||||
|
osTimerNew(subghz_view_receiver_timer_callback, osTimerOnce, subghz_receiver, NULL);
|
||||||
return subghz_receiver;
|
return subghz_receiver;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -285,6 +396,7 @@ void subghz_view_receiver_free(SubGhzViewReceiver* subghz_receiver) {
|
|||||||
free(model->history);
|
free(model->history);
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
osTimerDelete(subghz_receiver->timer);
|
||||||
view_free(subghz_receiver->view);
|
view_free(subghz_receiver->view);
|
||||||
free(subghz_receiver);
|
free(subghz_receiver);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +1,15 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <gui/view.h>
|
#include <gui/view.h>
|
||||||
|
#include "../helpers/subghz_types.h"
|
||||||
#include "../helpers/subghz_custom_event.h"
|
#include "../helpers/subghz_custom_event.h"
|
||||||
|
|
||||||
typedef struct SubGhzViewReceiver SubGhzViewReceiver;
|
typedef struct SubGhzViewReceiver SubGhzViewReceiver;
|
||||||
|
|
||||||
typedef void (*SubGhzViewReceiverCallback)(SubGhzCustomEvent event, void* context);
|
typedef void (*SubGhzViewReceiverCallback)(SubGhzCustomEvent event, void* context);
|
||||||
|
|
||||||
|
void subghz_view_receiver_set_lock(SubGhzViewReceiver* subghz_receiver, SubGhzLock keyboard);
|
||||||
|
|
||||||
void subghz_view_receiver_set_callback(
|
void subghz_view_receiver_set_callback(
|
||||||
SubGhzViewReceiver* subghz_receiver,
|
SubGhzViewReceiver* subghz_receiver,
|
||||||
SubGhzViewReceiverCallback callback,
|
SubGhzViewReceiverCallback callback,
|
||||||
|
|||||||
184
applications/unit_tests/nfc/nfc_test.c
Normal file
184
applications/unit_tests/nfc/nfc_test.c
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
#include <furi.h>
|
||||||
|
#include <furi_hal.h>
|
||||||
|
#include <applications/storage/storage.h>
|
||||||
|
#include <lib/flipper_format/flipper_format.h>
|
||||||
|
#include <lib/nfc_protocols/nfca.h>
|
||||||
|
#include <lib/digital_signal/digital_signal.h>
|
||||||
|
|
||||||
|
#include <lib/flipper_format/flipper_format_i.h>
|
||||||
|
#include <lib/toolbox/stream/file_stream.h>
|
||||||
|
|
||||||
|
#include "../minunit.h"
|
||||||
|
|
||||||
|
#define TAG "NfcTest"
|
||||||
|
|
||||||
|
#define NFC_TEST_RESOURCES_DIR "/ext/unit_tests/nfc/"
|
||||||
|
#define NFC_TEST_SIGNAL_SHORT_FILE "nfc_nfca_signal_short.nfc"
|
||||||
|
#define NFC_TEST_SIGNAL_LONG_FILE "nfc_nfca_signal_long.nfc"
|
||||||
|
|
||||||
|
static const char* nfc_test_file_type = "Flipper NFC test";
|
||||||
|
static const uint32_t nfc_test_file_version = 1;
|
||||||
|
|
||||||
|
#define NFC_TEST_DATA_MAX_LEN 18
|
||||||
|
#define NFC_TETS_TIMINGS_MAX_LEN 1350
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
Storage* storage;
|
||||||
|
NfcaSignal* signal;
|
||||||
|
uint32_t test_data_len;
|
||||||
|
uint8_t test_data[NFC_TEST_DATA_MAX_LEN];
|
||||||
|
uint32_t test_timings_len;
|
||||||
|
uint32_t test_timings[NFC_TETS_TIMINGS_MAX_LEN];
|
||||||
|
} NfcTest;
|
||||||
|
|
||||||
|
static NfcTest* nfc_test = NULL;
|
||||||
|
|
||||||
|
static void nfc_test_alloc() {
|
||||||
|
nfc_test = malloc(sizeof(NfcTest));
|
||||||
|
nfc_test->signal = nfca_signal_alloc();
|
||||||
|
nfc_test->storage = furi_record_open("storage");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void nfc_test_free() {
|
||||||
|
furi_assert(nfc_test);
|
||||||
|
|
||||||
|
furi_record_close("storage");
|
||||||
|
nfca_signal_free(nfc_test->signal);
|
||||||
|
free(nfc_test);
|
||||||
|
nfc_test = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool nfc_test_read_signal_from_file(const char* file_name) {
|
||||||
|
bool success = false;
|
||||||
|
|
||||||
|
FlipperFormat* file = flipper_format_file_alloc(nfc_test->storage);
|
||||||
|
string_t file_type;
|
||||||
|
string_init(file_type);
|
||||||
|
uint32_t file_version = 0;
|
||||||
|
|
||||||
|
do {
|
||||||
|
if(!flipper_format_file_open_existing(file, file_name)) break;
|
||||||
|
if(!flipper_format_read_header(file, file_type, &file_version)) break;
|
||||||
|
if(string_cmp_str(file_type, nfc_test_file_type) || file_version != nfc_test_file_version)
|
||||||
|
break;
|
||||||
|
if(!flipper_format_read_uint32(file, "Data length", &nfc_test->test_data_len, 1)) break;
|
||||||
|
if(nfc_test->test_data_len > NFC_TEST_DATA_MAX_LEN) break;
|
||||||
|
if(!flipper_format_read_hex(
|
||||||
|
file, "Plain data", nfc_test->test_data, nfc_test->test_data_len))
|
||||||
|
break;
|
||||||
|
if(!flipper_format_read_uint32(file, "Timings length", &nfc_test->test_timings_len, 1))
|
||||||
|
break;
|
||||||
|
if(nfc_test->test_timings_len > NFC_TETS_TIMINGS_MAX_LEN) break;
|
||||||
|
if(!flipper_format_read_uint32(
|
||||||
|
file, "Timings", nfc_test->test_timings, nfc_test->test_timings_len))
|
||||||
|
break;
|
||||||
|
success = true;
|
||||||
|
} while(false);
|
||||||
|
|
||||||
|
string_clear(file_type);
|
||||||
|
flipper_format_free(file);
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool nfc_test_digital_signal_test_encode(
|
||||||
|
const char* file_name,
|
||||||
|
uint32_t encode_max_time,
|
||||||
|
uint32_t timing_tolerance,
|
||||||
|
uint32_t timings_sum_tolerance) {
|
||||||
|
furi_assert(nfc_test);
|
||||||
|
|
||||||
|
bool success = false;
|
||||||
|
uint32_t time = 0;
|
||||||
|
uint32_t dut_timings_sum = 0;
|
||||||
|
uint32_t ref_timings_sum = 0;
|
||||||
|
uint8_t parity[10] = {};
|
||||||
|
|
||||||
|
do {
|
||||||
|
// Read test data
|
||||||
|
if(!nfc_test_read_signal_from_file(file_name)) break;
|
||||||
|
|
||||||
|
// Encode signal
|
||||||
|
FURI_CRITICAL_ENTER();
|
||||||
|
time = DWT->CYCCNT;
|
||||||
|
nfca_signal_encode(
|
||||||
|
nfc_test->signal, nfc_test->test_data, nfc_test->test_data_len * 8, parity);
|
||||||
|
digital_signal_prepare_arr(nfc_test->signal->tx_signal);
|
||||||
|
time = (DWT->CYCCNT - time) / furi_hal_delay_instructions_per_microsecond();
|
||||||
|
FURI_CRITICAL_EXIT();
|
||||||
|
|
||||||
|
// Check timings
|
||||||
|
if(time > encode_max_time) {
|
||||||
|
FURI_LOG_E(
|
||||||
|
TAG, "Encoding time: %d us while accepted value: %d us", time, encode_max_time);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check data
|
||||||
|
if(nfc_test->signal->tx_signal->edge_cnt != nfc_test->test_timings_len) {
|
||||||
|
FURI_LOG_E(TAG, "Not equal timings buffers length");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t timings_diff = 0;
|
||||||
|
uint32_t* ref = nfc_test->test_timings;
|
||||||
|
uint32_t* dut = nfc_test->signal->tx_signal->reload_reg_buff;
|
||||||
|
bool timing_check_success = true;
|
||||||
|
for(size_t i = 0; i < nfc_test->test_timings_len; i++) {
|
||||||
|
timings_diff = dut[i] > ref[i] ? dut[i] - ref[i] : ref[i] - dut[i];
|
||||||
|
dut_timings_sum += dut[i];
|
||||||
|
ref_timings_sum += ref[i];
|
||||||
|
if(timings_diff > timing_tolerance) {
|
||||||
|
FURI_LOG_E(
|
||||||
|
TAG, "Too big differece in %d timings. Ref: %d, DUT: %d", i, ref[i], dut[i]);
|
||||||
|
timing_check_success = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!timing_check_success) break;
|
||||||
|
uint32_t sum_diff = dut_timings_sum > ref_timings_sum ? dut_timings_sum - ref_timings_sum :
|
||||||
|
ref_timings_sum - dut_timings_sum;
|
||||||
|
if(sum_diff > timings_sum_tolerance) {
|
||||||
|
FURI_LOG_E(
|
||||||
|
TAG,
|
||||||
|
"Too big difference in timings sum. Ref: %d, DUT: %d",
|
||||||
|
ref_timings_sum,
|
||||||
|
dut_timings_sum);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
FURI_LOG_I(TAG, "Encoding time: %d us. Acceptable time: %d us", time, encode_max_time);
|
||||||
|
FURI_LOG_I(
|
||||||
|
TAG,
|
||||||
|
"Timings sum difference: %d [1/64MHZ]. Acceptable difference: %d [1/64MHz]",
|
||||||
|
sum_diff,
|
||||||
|
timings_sum_tolerance);
|
||||||
|
success = true;
|
||||||
|
} while(false);
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
MU_TEST(nfc_digital_signal_test) {
|
||||||
|
mu_assert(
|
||||||
|
nfc_test_digital_signal_test_encode(
|
||||||
|
NFC_TEST_RESOURCES_DIR NFC_TEST_SIGNAL_SHORT_FILE, 500, 1, 37),
|
||||||
|
"NFC short digital signal test failed\r\n");
|
||||||
|
mu_assert(
|
||||||
|
nfc_test_digital_signal_test_encode(
|
||||||
|
NFC_TEST_RESOURCES_DIR NFC_TEST_SIGNAL_LONG_FILE, 2000, 1, 37),
|
||||||
|
"NFC long digital signal test failed\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
MU_TEST_SUITE(nfc) {
|
||||||
|
nfc_test_alloc();
|
||||||
|
|
||||||
|
MU_RUN_TEST(nfc_digital_signal_test);
|
||||||
|
|
||||||
|
nfc_test_free();
|
||||||
|
}
|
||||||
|
|
||||||
|
int run_minunit_test_nfc() {
|
||||||
|
MU_RUN_SUITE(nfc);
|
||||||
|
return MU_EXIT_CODE;
|
||||||
|
}
|
||||||
@ -19,6 +19,7 @@ int run_minunit_test_stream();
|
|||||||
int run_minunit_test_storage();
|
int run_minunit_test_storage();
|
||||||
int run_minunit_test_subghz();
|
int run_minunit_test_subghz();
|
||||||
int run_minunit_test_dirwalk();
|
int run_minunit_test_dirwalk();
|
||||||
|
int run_minunit_test_nfc();
|
||||||
|
|
||||||
void minunit_print_progress(void) {
|
void minunit_print_progress(void) {
|
||||||
static char progress[] = {'\\', '|', '/', '-'};
|
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_infrared_decoder_encoder();
|
||||||
test_result |= run_minunit_test_rpc();
|
test_result |= run_minunit_test_rpc();
|
||||||
test_result |= run_minunit_test_subghz();
|
test_result |= run_minunit_test_subghz();
|
||||||
|
test_result |= run_minunit_test_nfc();
|
||||||
|
|
||||||
cycle_counter = (furi_hal_get_tick() - cycle_counter);
|
cycle_counter = (furi_hal_get_tick() - cycle_counter);
|
||||||
|
|
||||||
|
|||||||
@ -44,17 +44,17 @@ static const UpdateTaskStageGroupMap update_task_stage_progress[] = {
|
|||||||
[UpdateTaskStageReadManifest] = STAGE_DEF(UpdateTaskStageGroupPreUpdate, 5),
|
[UpdateTaskStageReadManifest] = STAGE_DEF(UpdateTaskStageGroupPreUpdate, 5),
|
||||||
[UpdateTaskStageLfsBackup] = STAGE_DEF(UpdateTaskStageGroupPreUpdate, 15),
|
[UpdateTaskStageLfsBackup] = STAGE_DEF(UpdateTaskStageGroupPreUpdate, 15),
|
||||||
|
|
||||||
[UpdateTaskStageRadioImageValidate] = STAGE_DEF(UpdateTaskStageGroupRadio, 10),
|
[UpdateTaskStageRadioImageValidate] = STAGE_DEF(UpdateTaskStageGroupRadio, 15),
|
||||||
[UpdateTaskStageRadioErase] = STAGE_DEF(UpdateTaskStageGroupRadio, 50),
|
[UpdateTaskStageRadioErase] = STAGE_DEF(UpdateTaskStageGroupRadio, 60),
|
||||||
[UpdateTaskStageRadioWrite] = STAGE_DEF(UpdateTaskStageGroupRadio, 90),
|
[UpdateTaskStageRadioWrite] = STAGE_DEF(UpdateTaskStageGroupRadio, 80),
|
||||||
[UpdateTaskStageRadioInstall] = STAGE_DEF(UpdateTaskStageGroupRadio, 15),
|
[UpdateTaskStageRadioInstall] = STAGE_DEF(UpdateTaskStageGroupRadio, 60),
|
||||||
[UpdateTaskStageRadioBusy] = STAGE_DEF(UpdateTaskStageGroupRadio, 60),
|
[UpdateTaskStageRadioBusy] = STAGE_DEF(UpdateTaskStageGroupRadio, 80),
|
||||||
|
|
||||||
[UpdateTaskStageOBValidation] = STAGE_DEF(UpdateTaskStageGroupOptionBytes, 10),
|
[UpdateTaskStageOBValidation] = STAGE_DEF(UpdateTaskStageGroupOptionBytes, 10),
|
||||||
|
|
||||||
[UpdateTaskStageValidateDFUImage] = STAGE_DEF(UpdateTaskStageGroupFirmware, 100),
|
[UpdateTaskStageValidateDFUImage] = STAGE_DEF(UpdateTaskStageGroupFirmware, 50),
|
||||||
[UpdateTaskStageFlashWrite] = STAGE_DEF(UpdateTaskStageGroupFirmware, 200),
|
[UpdateTaskStageFlashWrite] = STAGE_DEF(UpdateTaskStageGroupFirmware, 200),
|
||||||
[UpdateTaskStageFlashValidate] = STAGE_DEF(UpdateTaskStageGroupFirmware, 50),
|
[UpdateTaskStageFlashValidate] = STAGE_DEF(UpdateTaskStageGroupFirmware, 30),
|
||||||
|
|
||||||
[UpdateTaskStageLfsRestore] = STAGE_DEF(UpdateTaskStageGroupPostUpdate, 30),
|
[UpdateTaskStageLfsRestore] = STAGE_DEF(UpdateTaskStageGroupPostUpdate, 30),
|
||||||
|
|
||||||
@ -214,6 +214,7 @@ UpdateTask* update_task_alloc() {
|
|||||||
update_task->storage = furi_record_open("storage");
|
update_task->storage = furi_record_open("storage");
|
||||||
update_task->file = storage_file_alloc(update_task->storage);
|
update_task->file = storage_file_alloc(update_task->storage);
|
||||||
update_task->status_change_cb = NULL;
|
update_task->status_change_cb = NULL;
|
||||||
|
update_task->boot_mode = furi_hal_rtc_get_boot_mode();
|
||||||
string_init(update_task->update_path);
|
string_init(update_task->update_path);
|
||||||
|
|
||||||
FuriThread* thread = update_task->thread = furi_thread_alloc();
|
FuriThread* thread = update_task->thread = furi_thread_alloc();
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <storage/storage.h>
|
#include <storage/storage.h>
|
||||||
|
#include <furi_hal.h>
|
||||||
|
|
||||||
#define UPDATE_TASK_NOERR 0
|
#define UPDATE_TASK_NOERR 0
|
||||||
#define UPDATE_TASK_FAILED -1
|
#define UPDATE_TASK_FAILED -1
|
||||||
@ -14,6 +15,7 @@ typedef struct UpdateTask {
|
|||||||
File* file;
|
File* file;
|
||||||
updateProgressCb status_change_cb;
|
updateProgressCb status_change_cb;
|
||||||
void* status_change_cb_state;
|
void* status_change_cb_state;
|
||||||
|
FuriHalRtcBootMode boot_mode;
|
||||||
} UpdateTask;
|
} UpdateTask;
|
||||||
|
|
||||||
void update_task_set_progress(UpdateTask* update_task, UpdateTaskStage stage, uint8_t progress);
|
void update_task_set_progress(UpdateTask* update_task, UpdateTaskStage stage, uint8_t progress);
|
||||||
|
|||||||
@ -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);
|
string_get_cstr(update_task->update_path), LFS_BACKUP_DEFAULT_FILENAME, file_path);
|
||||||
|
|
||||||
update_task_set_progress(update_task, UpdateTaskStageLfsRestore, 0);
|
update_task_set_progress(update_task, UpdateTaskStageLfsRestore, 0);
|
||||||
update_operation_disarm();
|
|
||||||
|
|
||||||
CHECK_RESULT(lfs_backup_unpack(update_task->storage, string_get_cstr(file_path)));
|
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) {
|
int32_t update_task_worker_backup_restore(void* context) {
|
||||||
furi_assert(context);
|
furi_assert(context);
|
||||||
UpdateTask* update_task = 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)) {
|
if((boot_mode != FuriHalRtcBootModePreUpdate) && (boot_mode != FuriHalRtcBootModePostUpdate)) {
|
||||||
/* no idea how we got here. Clear to normal boot */
|
/* no idea how we got here. Do nothing */
|
||||||
update_operation_disarm();
|
|
||||||
return UPDATE_TASK_NOERR;
|
return UPDATE_TASK_NOERR;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!update_task_parse_manifest(update_task)) {
|
bool success = false;
|
||||||
return UPDATE_TASK_FAILED;
|
do {
|
||||||
}
|
if(!update_task_parse_manifest(update_task)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
/* Waiting for BT service to 'start', so we don't race for boot mode flag */
|
/* Waiting for BT service to 'start', so we don't race for boot mode flag */
|
||||||
furi_record_open("bt");
|
furi_record_open("bt");
|
||||||
furi_record_close("bt");
|
furi_record_close("bt");
|
||||||
|
|
||||||
if(boot_mode == FuriHalRtcBootModePreUpdate) {
|
if(boot_mode == FuriHalRtcBootModePreUpdate) {
|
||||||
success = update_task_pre_update(update_task);
|
success = update_task_pre_update(update_task);
|
||||||
} else if(boot_mode == FuriHalRtcBootModePostUpdate) {
|
} else if(boot_mode == FuriHalRtcBootModePostUpdate) {
|
||||||
success = update_task_post_update(update_task);
|
success = update_task_post_update(update_task);
|
||||||
}
|
if(success) {
|
||||||
|
update_operation_disarm();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while(false);
|
||||||
|
|
||||||
if(!success) {
|
if(!success) {
|
||||||
update_task_set_progress(update_task, UpdateTaskStageError, 0);
|
update_task_set_progress(update_task, UpdateTaskStageError, 0);
|
||||||
|
|||||||
@ -340,6 +340,8 @@ int32_t update_task_worker_flash_writer(void* context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
furi_hal_rtc_set_boot_mode(FuriHalRtcBootModePostUpdate);
|
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);
|
update_task_set_progress(update_task, UpdateTaskStageCompleted, 100);
|
||||||
success = true;
|
success = true;
|
||||||
|
|||||||
@ -28,38 +28,38 @@ icons_src = assetsenv.GlobRecursive("*.png", "icons")
|
|||||||
icons_src += assetsenv.GlobRecursive("frame_rate", "icons")
|
icons_src += assetsenv.GlobRecursive("frame_rate", "icons")
|
||||||
|
|
||||||
icons = assetsenv.IconBuilder(Dir("compiled"), Dir("#/assets/icons"))
|
icons = assetsenv.IconBuilder(Dir("compiled"), Dir("#/assets/icons"))
|
||||||
Depends(icons, icons_src)
|
assetsenv.Depends(icons, icons_src)
|
||||||
Alias("icons", icons)
|
assetsenv.Alias("icons", icons)
|
||||||
|
|
||||||
|
|
||||||
# Protobuf .proto -> .c + .h
|
# Protobuf .proto -> .c + .h
|
||||||
|
|
||||||
proto_src = Glob("protobuf/*.proto", source=True)
|
proto_src = assetsenv.Glob("protobuf/*.proto", source=True)
|
||||||
proto_options = Glob("protobuf/*.options", source=True)
|
proto_options = assetsenv.Glob("protobuf/*.options", source=True)
|
||||||
proto = assetsenv.ProtoBuilder(Dir("compiled"), proto_src)
|
proto = assetsenv.ProtoBuilder(assetsenv.Dir("compiled"), proto_src)
|
||||||
Depends(proto, proto_options)
|
assetsenv.Depends(proto, proto_options)
|
||||||
# Precious(proto)
|
# Precious(proto)
|
||||||
Alias("proto", proto)
|
assetsenv.Alias("proto", proto)
|
||||||
|
|
||||||
|
|
||||||
# Internal animations
|
# Internal animations
|
||||||
|
|
||||||
dolphin_internal = assetsenv.DolphinSymBuilder(
|
dolphin_internal = assetsenv.DolphinSymBuilder(
|
||||||
Dir("compiled"),
|
assetsenv.Dir("compiled"),
|
||||||
Dir("#/assets/dolphin"),
|
assetsenv.Dir("#/assets/dolphin"),
|
||||||
DOLPHIN_RES_TYPE="internal",
|
DOLPHIN_RES_TYPE="internal",
|
||||||
)
|
)
|
||||||
Alias("dolphin_internal", dolphin_internal)
|
assetsenv.Alias("dolphin_internal", dolphin_internal)
|
||||||
|
|
||||||
|
|
||||||
# Blocking animations
|
# Blocking animations
|
||||||
|
|
||||||
dolphin_blocking = assetsenv.DolphinSymBuilder(
|
dolphin_blocking = assetsenv.DolphinSymBuilder(
|
||||||
Dir("compiled"),
|
assetsenv.Dir("compiled"),
|
||||||
Dir("#/assets/dolphin"),
|
assetsenv.Dir("#/assets/dolphin"),
|
||||||
DOLPHIN_RES_TYPE="blocking",
|
DOLPHIN_RES_TYPE="blocking",
|
||||||
)
|
)
|
||||||
Alias("dolphin_blocking", dolphin_blocking)
|
assetsenv.Alias("dolphin_blocking", dolphin_blocking)
|
||||||
|
|
||||||
|
|
||||||
# Protobuf version meta
|
# Protobuf version meta
|
||||||
@ -67,8 +67,8 @@ proto_ver = assetsenv.ProtoVerBuilder(
|
|||||||
"compiled/protobuf_version.h",
|
"compiled/protobuf_version.h",
|
||||||
"#/assets/protobuf/Changelog",
|
"#/assets/protobuf/Changelog",
|
||||||
)
|
)
|
||||||
Depends(proto_ver, proto)
|
assetsenv.Depends(proto_ver, proto)
|
||||||
Alias("proto_ver", proto_ver)
|
assetsenv.Alias("proto_ver", proto_ver)
|
||||||
|
|
||||||
# Gather everything into a static lib
|
# Gather everything into a static lib
|
||||||
assets_parts = (icons, proto, dolphin_blocking, dolphin_internal, proto_ver)
|
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"]:
|
if assetsenv["IS_BASE_FIRMWARE"]:
|
||||||
# External dolphin animations
|
# External dolphin animations
|
||||||
dolphin_external = assetsenv.DolphinExtBuilder(
|
dolphin_external = assetsenv.DolphinExtBuilder(
|
||||||
Dir("#/assets/resources/dolphin"),
|
assetsenv.Dir("#/assets/resources/dolphin"),
|
||||||
Dir("#/assets/dolphin"),
|
assetsenv.Dir("#/assets/dolphin"),
|
||||||
DOLPHIN_RES_TYPE="external",
|
DOLPHIN_RES_TYPE="external",
|
||||||
)
|
)
|
||||||
NoClean(dolphin_external)
|
assetsenv.NoClean(dolphin_external)
|
||||||
if assetsenv["FORCE"]:
|
if assetsenv["FORCE"]:
|
||||||
AlwaysBuild(dolphin_external)
|
assetsenv.AlwaysBuild(dolphin_external)
|
||||||
Alias("dolphin_ext", dolphin_external)
|
assetsenv.Alias("dolphin_ext", dolphin_external)
|
||||||
|
|
||||||
# Resources manifest
|
# Resources manifest
|
||||||
|
|
||||||
@ -101,13 +101,13 @@ if assetsenv["IS_BASE_FIRMWARE"]:
|
|||||||
"${RESMANIFESTCOMSTR}",
|
"${RESMANIFESTCOMSTR}",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
Precious(resources)
|
assetsenv.Precious(resources)
|
||||||
NoClean(resources)
|
assetsenv.NoClean(resources)
|
||||||
if assetsenv["FORCE"]:
|
if assetsenv["FORCE"]:
|
||||||
AlwaysBuild(resources)
|
assetsenv.AlwaysBuild(resources)
|
||||||
|
|
||||||
# Exporting resources node to external environment
|
# Exporting resources node to external environment
|
||||||
env["FW_RESOURCES"] = resources
|
env["FW_RESOURCES"] = resources
|
||||||
Alias("resources", resources)
|
assetsenv.Alias("resources", resources)
|
||||||
|
|
||||||
Return("assetslib")
|
Return("assetslib")
|
||||||
|
|||||||
BIN
assets/icons/BLE/BLE_HID/Ble_connected_15x15.png
Normal file
BIN
assets/icons/BLE/BLE_HID/Ble_connected_15x15.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 177 B |
BIN
assets/icons/BLE/BLE_HID/Ble_disconnected_15x15.png
Normal file
BIN
assets/icons/BLE/BLE_HID/Ble_disconnected_15x15.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 178 B |
@ -1 +1 @@
|
|||||||
Subproject commit ffa62429f3c678537e0e883a3a8c3ae5f1398ed4
|
Subproject commit d9e343661dd36cfab792b78be1dea4e5950cb4dd
|
||||||
6
assets/unit_tests/nfc/nfc_nfca_signal_long.nfc
Normal file
6
assets/unit_tests/nfc/nfc_nfca_signal_long.nfc
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
Filetype: Flipper NFC test
|
||||||
|
Version: 1
|
||||||
|
Data length: 18
|
||||||
|
Plain data: f1 99 41 43 a1 2f 23 01 de f3 c5 8d 91 4b 1e 50 4a c9
|
||||||
|
Timings length: 1304
|
||||||
|
Timings: 37 37 36 37 37 37 36 339 37 37 36 37 37 37 36 641 37 37 37 36 37 37 37 338 37 37 37 36 37 37 37 339 36 37 37 37 36 37 37 37 36 37 37 37 36 37 37 339 37 36 37 37 37 36 37 339 37 36 37 37 37 36 37 339 37 37 36 37 37 37 36 641 37 37 37 36 37 37 37 36 37 37 37 36 37 37 37 640 37 37 37 37 36 37 37 339 36 37 37 37 36 37 37 37 36 37 37 37 36 37 37 339 37 36 37 37 37 36 37 641 37 37 36 37 37 37 36 339 37 37 36 37 37 37 36 37 37 37 36 37 37 37 37 640 37 37 37 36 37 37 37 36 37 37 37 36 37 37 37 641 36 37 37 37 36 37 37 339 37 36 37 37 37 36 37 339 37 36 37 37 37 36 37 339 37 37 36 37 37 37 36 339 37 37 36 37 37 37 36 37 37 37 37 36 37 37 37 640 37 37 37 36 37 37 37 339 36 37 37 37 36 37 37 37 36 37 37 37 36 37 37 339 37 36 37 37 37 36 37 641 37 36 37 37 37 37 36 339 37 37 36 37 37 37 36 339 37 37 36 37 37 37 37 338 37 37 37 36 37 37 37 36 37 37 37 36 37 37 37 641 36 37 37 37 36 37 37 339 36 37 37 37 37 36 37 37 37 36 37 37 37 36 37 641 37 37 36 37 37 37 36 339 37 37 36 37 37 37 36 339 37 37 37 36 37 37 37 338 37 37 37 36 37 37 37 36 37 37 37 36 37 37 37 641 36 37 37 37 36 37 37 37 36 37 37 37 37 36 37 641 37 36 37 37 37 36 37 37 37 36 37 37 37 37 36 339 37 37 36 37 37 37 36 339 37 37 37 36 37 37 37 338 37 37 37 36 37 37 37 641 36 37 37 37 36 37 37 37 36 37 37 37 36 37 37 641 37 36 37 37 37 36 37 339 37 36 37 37 37 36 37 339 37 37 36 37 37 37 36 37 37 37 36 37 37 37 36 339 37 37 37 36 37 37 37 640 37 37 37 36 37 37 37 339 36 37 37 37 36 37 37 339 36 37 37 37 37 36 37 37 37 36 37 37 37 36 37 641 37 36 37 37 37 37 36 339 37 37 36 37 37 37 36 339 37 37 37 36 37 37 37 36 37 37 37 36 37 37 37 641 36 37 37 37 36 37 37 339 36 37 37 37 36 37 37 339 37 36 37 37 37 36 37 339 37 36 37 37 37 36 37 339 37 37 36 37 37 37 36 339 37 37 36 37 37 37 36 339 37 37 37 36 37 37 37 338 37 37 37 36 37 37 37 339 36 37 37 37 36 37 37 37 36 37 37 37 36 37 37 339 36 37 37 37 37 36 37 339 37 36 37 37 37 36 37 339 37 37 36 37 37 37 36 641 37 37 36 37 37 37 36 37 37 37 37 36 37 37 37 338 37 37 37 36 37 37 37 641 36 37 37 37 36 37 37 37 36 37 37 37 36 37 37 339 37 36 37 37 37 36 37 641 37 36 37 37 37 37 36 339 37 37 36 37 37 37 36 37 37 37 36 37 37 37 36 339 37 37 37 36 37 37 37 338 37 37 37 36 37 37 37 339 36 37 37 37 36 37 37 641 37 36 37 37 37 36 37 37 37 36 37 37 37 36 37 641 37 37 36 37 37 37 36 37 37 37 36 37 37 37 36 641 37 37 37 36 37 37 37 338 37 37 37 36 37 37 37 339 36 37 37 37 36 37 37 37 36 37 37 37 36 37 37 339 36 37 37 37 37 36 37 641 37 36 37 37 37 36 37 37 37 36 37 37 37 37 36 641 37 37 36 37 37 37 36 37 37 37 37 36 37 37 37 338 37 37 37 36 37 37 37 641 36 37 37 37 36 37 37 339 36 37 37 37 36 37 37 339 37 36 37 37 37 36 37 37 37 36 37 37 37 36 37 641 37 37 36 37 37 37 36 37 37 37 36 37 37 37 36 641 37 37 37 36 37 37 37 338 37 37 37 37 36 37 37 339 36 37 37 37 36 37 37 37 36 37 37 37 36 37 37 641 37 36 37 37 37 36 37 339 37 37 36 37 37 37 36 37 37 37 36 37 37 37 36 641 37 37 37 36 37 37 37 36 37 37 37 36 37 37 37 338 37 37 37 36 37 37 37 641 36 37 37 37 36 37 37 37 36 37 37 37 36 37 37 641 37 36 37 37 37 36 37 339 37 37 36 37 37 37 36 37 37 37 36 37 37 37 36 641 37 37 37 36 37 37 37 338 37 37 37 36 37 37 37 339 36 37 37 37 36 37 37 37 36 37 37 37 36 37 37 339 37 36 37 37 37 36 37 339 37 36 37 37 37 36 37 339 37 37 36 37 37 37 36 641 37 37 36 37 37 37 37 338 37 37 37 36 37 37 37 338 37 37 37 36 37 37 37 339 36 37 37 37 36 37 37 339 36 37 37 37 37 36 37 339 37 36 37 37 37 36 37 339 37 36 37 37 37 37 36 339 37 37 36 37 37 37 36 37 37 37 36 37 37 37 36 641 37 37 37 36 37 37 37 36 37 37 37 36 37 37 37 641 36 37 37 37 36 37 37 339 37 36 37 37 37 36 37 339 37 36 37 37 37 36 37 37 37 36 37 37 37 36 37 641 37 37 36 37 37 37 36 37 37 37 36 37 37 37 37 640 37 37 37 36 37 37 37 339 36 37 37 37 36 37 37 37 36 37 37 37 36 37 37 641 37 36 37 37 37 36 37 339 37 36 37 37 37 36 37 37 37 37 36 37 37 37 36 641 37 37 36 37 37 37 36 339 37 37 37 36 37 37 37 36 37 37 37 36 37 37 37 641 36 37 37 37 36 37 37 339 36 37 37 37 36 37 37 37 37 36 37 37 37 36 37 339 37 36 37 37 37 36 37 641 37 37 36 37 37 37 36 0
|
||||||
6
assets/unit_tests/nfc/nfc_nfca_signal_short.nfc
Normal file
6
assets/unit_tests/nfc/nfc_nfca_signal_short.nfc
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
Filetype: Flipper NFC test
|
||||||
|
Version: 1
|
||||||
|
Data length: 4
|
||||||
|
Plain data: 14 d8 a0 c9
|
||||||
|
Timings length: 296
|
||||||
|
Timings: 37 37 36 37 37 37 36 641 37 37 36 37 37 37 37 338 37 37 37 36 37 37 37 36 37 37 37 36 37 37 37 641 36 37 37 37 36 37 37 37 36 37 37 37 36 37 37 641 37 36 37 37 37 36 37 339 37 36 37 37 37 37 36 339 37 37 36 37 37 37 36 339 37 37 37 36 37 37 37 338 37 37 37 36 37 37 37 338 37 37 37 37 36 37 37 339 36 37 37 37 36 37 37 37 36 37 37 37 36 37 37 339 37 36 37 37 37 36 37 641 37 37 36 37 37 37 36 37 37 37 36 37 37 37 36 339 37 37 36 37 37 37 37 640 37 37 37 36 37 37 37 339 36 37 37 37 36 37 37 339 36 37 37 37 36 37 37 339 37 36 37 37 37 36 37 339 37 36 37 37 37 36 37 339 37 37 36 37 37 37 36 37 37 37 36 37 37 37 36 641 37 37 37 36 37 37 37 36 37 37 37 36 37 37 37 641 36 37 37 37 36 37 37 37 36 37 37 37 36 37 37 641 37 36 37 37 37 36 37 339 37 36 37 37 37 37 36 37 37 37 36 37 37 37 36 641 37 37 36 37 37 37 37 338 37 37 37 36 37 37 37 36 37 37 37 36 37 37 37 339 36 37 37 37 36 37 37 641 36 37 37 37 37 36 37 0
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user