mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-05-23 05:24:46 -07:00
Merge remote-tracking branch 'ofw/dev' into feat/nfc-type-4
This commit is contained in:
100
.github/CODEOWNERS
vendored
100
.github/CODEOWNERS
vendored
@@ -1,69 +1,69 @@
|
||||
# Who owns all the fish by default
|
||||
* @skotopes @DrZlo13 @hedger @gsurkov
|
||||
* @DrZlo13 @hedger @gsurkov
|
||||
|
||||
# Apps
|
||||
/applications/debug/bt_debug_app/ @skotopes @DrZlo13 @hedger @gsurkov @gornekich
|
||||
/applications/debug/accessor/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov
|
||||
/applications/debug/battery_test_app/ @skotopes @DrZlo13 @hedger @gsurkov @gornekich
|
||||
/applications/debug/bt_debug_app/ @skotopes @DrZlo13 @hedger @gsurkov @gornekich
|
||||
/applications/debug/file_browser_test/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov
|
||||
/applications/debug/lfrfid_debug/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov
|
||||
/applications/debug/text_box_test/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov
|
||||
/applications/debug/uart_echo/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov
|
||||
/applications/debug/usb_mouse/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov
|
||||
/applications/debug/usb_test/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov
|
||||
/applications/debug/bt_debug_app/ @DrZlo13 @hedger @gsurkov @gornekich
|
||||
/applications/debug/accessor/ @DrZlo13 @hedger @gsurkov @nminaylov
|
||||
/applications/debug/battery_test_app/ @DrZlo13 @hedger @gsurkov @gornekich
|
||||
/applications/debug/bt_debug_app/ @DrZlo13 @hedger @gsurkov @gornekich
|
||||
/applications/debug/file_browser_test/ @DrZlo13 @hedger @gsurkov @nminaylov
|
||||
/applications/debug/lfrfid_debug/ @DrZlo13 @hedger @gsurkov @nminaylov
|
||||
/applications/debug/text_box_test/ @DrZlo13 @hedger @gsurkov @nminaylov
|
||||
/applications/debug/uart_echo/ @DrZlo13 @hedger @gsurkov @nminaylov
|
||||
/applications/debug/usb_mouse/ @DrZlo13 @hedger @gsurkov @nminaylov
|
||||
/applications/debug/usb_test/ @DrZlo13 @hedger @gsurkov @nminaylov
|
||||
|
||||
/applications/main/archive/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov
|
||||
/applications/main/bad_usb/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov
|
||||
/applications/main/gpio/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov
|
||||
/applications/main/ibutton/ @skotopes @DrZlo13 @hedger @gsurkov
|
||||
/applications/main/infrared/ @skotopes @DrZlo13 @hedger @gsurkov
|
||||
/applications/main/nfc/ @skotopes @DrZlo13 @hedger @gsurkov @gornekich @Astrrra
|
||||
/applications/main/subghz/ @skotopes @DrZlo13 @hedger @gsurkov @Skorpionm
|
||||
/applications/main/u2f/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov
|
||||
/applications/main/archive/ @DrZlo13 @hedger @gsurkov @nminaylov
|
||||
/applications/main/bad_usb/ @DrZlo13 @hedger @gsurkov @nminaylov
|
||||
/applications/main/gpio/ @DrZlo13 @hedger @gsurkov @nminaylov
|
||||
/applications/main/ibutton/ @DrZlo13 @hedger @gsurkov
|
||||
/applications/main/infrared/ @DrZlo13 @hedger @gsurkov
|
||||
/applications/main/nfc/ @DrZlo13 @hedger @gsurkov @gornekich
|
||||
/applications/main/subghz/ @DrZlo13 @hedger @gsurkov @Skorpionm
|
||||
/applications/main/u2f/ @DrZlo13 @hedger @gsurkov @nminaylov
|
||||
|
||||
/applications/services/bt/ @skotopes @DrZlo13 @hedger @gsurkov @gornekich
|
||||
/applications/services/cli/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov
|
||||
/applications/services/crypto/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov
|
||||
/applications/services/desktop/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov
|
||||
/applications/services/dolphin/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov
|
||||
/applications/services/power/ @skotopes @DrZlo13 @hedger @gsurkov @gornekich
|
||||
/applications/services/rpc/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov
|
||||
/applications/services/bt/ @DrZlo13 @hedger @gsurkov @gornekich
|
||||
/applications/services/cli/ @DrZlo13 @hedger @gsurkov @nminaylov
|
||||
/applications/services/crypto/ @DrZlo13 @hedger @gsurkov @nminaylov
|
||||
/applications/services/desktop/ @DrZlo13 @hedger @gsurkov @nminaylov
|
||||
/applications/services/dolphin/ @DrZlo13 @hedger @gsurkov @nminaylov
|
||||
/applications/services/power/ @DrZlo13 @hedger @gsurkov @gornekich
|
||||
/applications/services/rpc/ @DrZlo13 @hedger @gsurkov @nminaylov
|
||||
|
||||
/applications/services/bt_settings_app/ @skotopes @DrZlo13 @hedger @gsurkov @gornekich
|
||||
/applications/services/desktop_settings/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov
|
||||
/applications/services/dolphin_passport/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov
|
||||
/applications/services/power_settings_app/ @skotopes @DrZlo13 @hedger @gsurkov @gornekich
|
||||
/applications/services/bt_settings_app/ @DrZlo13 @hedger @gsurkov @gornekich
|
||||
/applications/services/desktop_settings/ @DrZlo13 @hedger @gsurkov @nminaylov
|
||||
/applications/services/dolphin_passport/ @DrZlo13 @hedger @gsurkov @nminaylov
|
||||
/applications/services/power_settings_app/ @DrZlo13 @hedger @gsurkov @gornekich
|
||||
|
||||
/applications/system/storage_move_to_sd/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov
|
||||
/applications/system/js_app/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov @portasynthinca3
|
||||
/applications/system/storage_move_to_sd/ @DrZlo13 @hedger @gsurkov @nminaylov
|
||||
/applications/system/js_app/ @DrZlo13 @hedger @gsurkov @nminaylov @portasynthinca3
|
||||
|
||||
/applications/debug/unit_tests/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov @gornekich @Astrrra @Skorpionm
|
||||
/applications/debug/unit_tests/ @DrZlo13 @hedger @gsurkov @nminaylov @gornekich @Skorpionm
|
||||
|
||||
/applications/examples/example_thermo/ @skotopes @DrZlo13 @hedger @gsurkov
|
||||
/applications/examples/example_thermo/ @DrZlo13 @hedger @gsurkov
|
||||
|
||||
# Firmware targets
|
||||
/targets/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov
|
||||
/targets/ @DrZlo13 @hedger @gsurkov @nminaylov
|
||||
|
||||
# Assets
|
||||
/applications/main/infrared/resources/ @skotopes @DrZlo13 @hedger @gsurkov
|
||||
/applications/main/infrared/resources/ @DrZlo13 @hedger @gsurkov
|
||||
|
||||
# Documentation
|
||||
/documentation/ @skotopes @DrZlo13 @hedger @gsurkov
|
||||
/scripts/toolchain/ @skotopes @DrZlo13 @hedger @gsurkov
|
||||
/documentation/ @DrZlo13 @hedger @gsurkov @portasynthinca3
|
||||
/scripts/toolchain/ @DrZlo13 @hedger @gsurkov
|
||||
|
||||
# Lib
|
||||
/lib/stm32wb_copro/ @skotopes @DrZlo13 @hedger @gsurkov @gornekich
|
||||
/lib/digital_signal/ @skotopes @DrZlo13 @hedger @gsurkov @gornekich
|
||||
/lib/infrared/ @skotopes @DrZlo13 @hedger @gsurkov
|
||||
/lib/lfrfid/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov
|
||||
/lib/libusb_stm32/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov
|
||||
/lib/mbedtls/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov
|
||||
/lib/mjs/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov @portasynthinca3
|
||||
/lib/nanopb/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov
|
||||
/lib/nfc/ @skotopes @DrZlo13 @hedger @gsurkov @gornekich @Astrrra
|
||||
/lib/one_wire/ @skotopes @DrZlo13 @hedger @gsurkov
|
||||
/lib/subghz/ @skotopes @DrZlo13 @hedger @gsurkov @Skorpionm
|
||||
/lib/stm32wb_copro/ @DrZlo13 @hedger @gsurkov @gornekich
|
||||
/lib/digital_signal/ @DrZlo13 @hedger @gsurkov @gornekich
|
||||
/lib/infrared/ @DrZlo13 @hedger @gsurkov
|
||||
/lib/lfrfid/ @DrZlo13 @hedger @gsurkov @nminaylov
|
||||
/lib/libusb_stm32/ @DrZlo13 @hedger @gsurkov @nminaylov
|
||||
/lib/mbedtls/ @DrZlo13 @hedger @gsurkov @nminaylov
|
||||
/lib/mjs/ @DrZlo13 @hedger @gsurkov @nminaylov @portasynthinca3
|
||||
/lib/nanopb/ @DrZlo13 @hedger @gsurkov @nminaylov
|
||||
/lib/nfc/ @DrZlo13 @hedger @gsurkov @gornekich
|
||||
/lib/one_wire/ @DrZlo13 @hedger @gsurkov
|
||||
/lib/subghz/ @DrZlo13 @hedger @gsurkov @Skorpionm
|
||||
|
||||
# CI/CD
|
||||
/.github/workflows/ @skotopes @DrZlo13 @hedger @gsurkov
|
||||
/.github/workflows/ @DrZlo13 @hedger @gsurkov
|
||||
|
||||
6
.github/workflows/unit_tests.yml
vendored
6
.github/workflows/unit_tests.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
- name: 'Flash unit tests firmware'
|
||||
id: flashing
|
||||
if: success()
|
||||
timeout-minutes: 10
|
||||
timeout-minutes: 5
|
||||
run: |
|
||||
source scripts/toolchain/fbtenv.sh
|
||||
./fbt resources firmware_latest flash LIB_DEBUG=1 FIRMWARE_APP_SET=unit_tests FORCE=1
|
||||
@@ -30,7 +30,7 @@ jobs:
|
||||
- name: 'Copy assets and unit data, reboot and wait for flipper'
|
||||
id: copy
|
||||
if: steps.flashing.outcome == 'success'
|
||||
timeout-minutes: 7
|
||||
timeout-minutes: 5
|
||||
run: |
|
||||
source scripts/toolchain/fbtenv.sh
|
||||
python3 scripts/testops.py -t=15 await_flipper
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
- name: 'Run units and validate results'
|
||||
id: run_units
|
||||
if: steps.copy.outcome == 'success'
|
||||
timeout-minutes: 7
|
||||
timeout-minutes: 5
|
||||
run: |
|
||||
source scripts/toolchain/fbtenv.sh
|
||||
python3 scripts/testops.py run_units
|
||||
|
||||
4
.github/workflows/updater_test.yml
vendored
4
.github/workflows/updater_test.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
||||
|
||||
- name: 'Flashing target firmware'
|
||||
id: first_full_flash
|
||||
timeout-minutes: 10
|
||||
timeout-minutes: 5
|
||||
run: |
|
||||
source scripts/toolchain/fbtenv.sh
|
||||
python3 scripts/testops.py -t=180 await_flipper
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
|
||||
- name: 'Validating updater'
|
||||
id: second_full_flash
|
||||
timeout-minutes: 10
|
||||
timeout-minutes: 5
|
||||
if: success()
|
||||
run: |
|
||||
source scripts/toolchain/fbtenv.sh
|
||||
|
||||
15
SConstruct
15
SConstruct
@@ -412,6 +412,21 @@ distenv.PhonyTarget(
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
# Measure CLI loopback performance
|
||||
distenv.PhonyTarget(
|
||||
"cli_perf",
|
||||
[
|
||||
[
|
||||
"${PYTHON3}",
|
||||
"${FBT_SCRIPT_DIR}/serial_cli_perf.py",
|
||||
"-p",
|
||||
"${FLIP_PORT}",
|
||||
"${ARGS}",
|
||||
]
|
||||
],
|
||||
)
|
||||
|
||||
# Update WiFi devboard firmware with release channel
|
||||
distenv.PhonyTarget(
|
||||
"devboard_flash",
|
||||
|
||||
@@ -6,6 +6,5 @@ App(
|
||||
entry_point="accessor_app",
|
||||
requires=["gui"],
|
||||
stack_size=4 * 1024,
|
||||
order=40,
|
||||
fap_category="Debug",
|
||||
)
|
||||
|
||||
@@ -8,7 +8,6 @@ App(
|
||||
"power",
|
||||
],
|
||||
stack_size=1 * 1024,
|
||||
order=130,
|
||||
fap_category="Debug",
|
||||
fap_libs=["assets"],
|
||||
)
|
||||
|
||||
@@ -5,6 +5,5 @@ App(
|
||||
entry_point="blink_test_app",
|
||||
requires=["gui"],
|
||||
stack_size=1 * 1024,
|
||||
order=10,
|
||||
fap_category="Debug",
|
||||
)
|
||||
|
||||
@@ -13,6 +13,5 @@ App(
|
||||
"bt_debug",
|
||||
],
|
||||
stack_size=1 * 1024,
|
||||
order=110,
|
||||
fap_category="Debug",
|
||||
)
|
||||
|
||||
@@ -10,6 +10,5 @@ App(
|
||||
"ccid_test",
|
||||
],
|
||||
stack_size=1 * 1024,
|
||||
order=120,
|
||||
fap_category="Debug",
|
||||
)
|
||||
|
||||
@@ -5,6 +5,5 @@ App(
|
||||
entry_point="direct_draw_app",
|
||||
requires=["gui", "input"],
|
||||
stack_size=2 * 1024,
|
||||
order=70,
|
||||
fap_category="Debug",
|
||||
)
|
||||
|
||||
@@ -6,6 +6,5 @@ App(
|
||||
requires=["gui"],
|
||||
fap_libs=["u8g2"],
|
||||
stack_size=1 * 1024,
|
||||
order=120,
|
||||
fap_category="Debug",
|
||||
)
|
||||
|
||||
@@ -5,6 +5,5 @@ App(
|
||||
entry_point="event_loop_blink_test_app",
|
||||
requires=["input"],
|
||||
stack_size=1 * 1024,
|
||||
order=20,
|
||||
fap_category="Debug",
|
||||
)
|
||||
|
||||
@@ -6,7 +6,6 @@ App(
|
||||
requires=["expansion_start"],
|
||||
fap_libs=["assets"],
|
||||
stack_size=1 * 1024,
|
||||
order=20,
|
||||
fap_category="Debug",
|
||||
fap_file_assets="assets",
|
||||
)
|
||||
|
||||
@@ -5,7 +5,6 @@ App(
|
||||
entry_point="file_browser_app",
|
||||
requires=["gui"],
|
||||
stack_size=2 * 1024,
|
||||
order=150,
|
||||
fap_category="Debug",
|
||||
fap_icon_assets="icons",
|
||||
)
|
||||
|
||||
@@ -5,6 +5,5 @@ App(
|
||||
entry_point="keypad_test_app",
|
||||
requires=["gui"],
|
||||
stack_size=1 * 1024,
|
||||
order=30,
|
||||
fap_category="Debug",
|
||||
)
|
||||
|
||||
@@ -11,6 +11,5 @@ App(
|
||||
"lfrfid_debug",
|
||||
],
|
||||
stack_size=1 * 1024,
|
||||
order=100,
|
||||
fap_category="Debug",
|
||||
)
|
||||
|
||||
8
applications/debug/loader_chaining_a/application.fam
Normal file
8
applications/debug/loader_chaining_a/application.fam
Normal file
@@ -0,0 +1,8 @@
|
||||
App(
|
||||
appid="loader_chaining_a",
|
||||
name="Loader Chaining Test: App A",
|
||||
apptype=FlipperAppType.DEBUG,
|
||||
entry_point="chaining_test_app_a",
|
||||
stack_size=1 * 1024,
|
||||
fap_category="Debug",
|
||||
)
|
||||
164
applications/debug/loader_chaining_a/loader_chaining_a.c
Normal file
164
applications/debug/loader_chaining_a/loader_chaining_a.c
Normal file
@@ -0,0 +1,164 @@
|
||||
#include <furi.h>
|
||||
#include <gui/gui.h>
|
||||
#include <gui/view_dispatcher.h>
|
||||
#include <gui/modules/submenu.h>
|
||||
#include <loader/loader.h>
|
||||
#include <dialogs/dialogs.h>
|
||||
|
||||
#define TAG "LoaderChainingA"
|
||||
#define CHAINING_TEST_B "/ext/apps/Debug/loader_chaining_b.fap"
|
||||
#define NONEXISTENT_APP "Some nonexistent app"
|
||||
|
||||
typedef struct {
|
||||
Gui* gui;
|
||||
ViewDispatcher* view_dispatcher;
|
||||
Submenu* submenu;
|
||||
|
||||
Loader* loader;
|
||||
|
||||
DialogsApp* dialogs;
|
||||
} LoaderChainingA;
|
||||
|
||||
typedef enum {
|
||||
LoaderChainingASubmenuLaunchB,
|
||||
LoaderChainingASubmenuLaunchBThenA,
|
||||
LoaderChainingASubmenuLaunchNonexistentSilent,
|
||||
LoaderChainingASubmenuLaunchNonexistentGui,
|
||||
LoaderChainingASubmenuLaunchNonexistentGuiThenA,
|
||||
} LoaderChainingASubmenu;
|
||||
|
||||
static void loader_chaining_a_submenu_callback(void* context, uint32_t index) {
|
||||
LoaderChainingA* app = context;
|
||||
|
||||
FuriString* self_path = furi_string_alloc();
|
||||
furi_check(loader_get_application_launch_path(app->loader, self_path));
|
||||
|
||||
switch(index) {
|
||||
case LoaderChainingASubmenuLaunchB:
|
||||
loader_enqueue_launch(app->loader, CHAINING_TEST_B, "Hello", LoaderDeferredLaunchFlagGui);
|
||||
view_dispatcher_stop(app->view_dispatcher);
|
||||
break;
|
||||
|
||||
case LoaderChainingASubmenuLaunchBThenA:
|
||||
loader_enqueue_launch(app->loader, CHAINING_TEST_B, "Hello", LoaderDeferredLaunchFlagGui);
|
||||
loader_enqueue_launch(
|
||||
app->loader,
|
||||
furi_string_get_cstr(self_path),
|
||||
"Hello to you from the future",
|
||||
LoaderDeferredLaunchFlagGui);
|
||||
|
||||
break;
|
||||
|
||||
case LoaderChainingASubmenuLaunchNonexistentSilent:
|
||||
loader_enqueue_launch(app->loader, NONEXISTENT_APP, NULL, LoaderDeferredLaunchFlagNone);
|
||||
break;
|
||||
|
||||
case LoaderChainingASubmenuLaunchNonexistentGui:
|
||||
loader_enqueue_launch(app->loader, NONEXISTENT_APP, NULL, LoaderDeferredLaunchFlagGui);
|
||||
break;
|
||||
|
||||
case LoaderChainingASubmenuLaunchNonexistentGuiThenA:
|
||||
loader_enqueue_launch(app->loader, NONEXISTENT_APP, NULL, LoaderDeferredLaunchFlagGui);
|
||||
loader_enqueue_launch(
|
||||
app->loader,
|
||||
furi_string_get_cstr(self_path),
|
||||
"Hello to you from the future",
|
||||
LoaderDeferredLaunchFlagGui);
|
||||
break;
|
||||
}
|
||||
|
||||
furi_string_free(self_path);
|
||||
view_dispatcher_stop(app->view_dispatcher);
|
||||
}
|
||||
|
||||
static bool loader_chaining_a_nav_callback(void* context) {
|
||||
LoaderChainingA* app = context;
|
||||
view_dispatcher_stop(app->view_dispatcher);
|
||||
return true;
|
||||
}
|
||||
|
||||
LoaderChainingA* loader_chaining_a_alloc(void) {
|
||||
LoaderChainingA* app = malloc(sizeof(LoaderChainingA));
|
||||
app->gui = furi_record_open(RECORD_GUI);
|
||||
app->loader = furi_record_open(RECORD_LOADER);
|
||||
app->dialogs = furi_record_open(RECORD_DIALOGS);
|
||||
app->view_dispatcher = view_dispatcher_alloc();
|
||||
app->submenu = submenu_alloc();
|
||||
|
||||
submenu_add_item(
|
||||
app->submenu,
|
||||
"Launch B",
|
||||
LoaderChainingASubmenuLaunchB,
|
||||
loader_chaining_a_submenu_callback,
|
||||
app);
|
||||
submenu_add_item(
|
||||
app->submenu,
|
||||
"Launch B, then A",
|
||||
LoaderChainingASubmenuLaunchBThenA,
|
||||
loader_chaining_a_submenu_callback,
|
||||
app);
|
||||
submenu_add_item(
|
||||
app->submenu,
|
||||
"Trigger error: silent",
|
||||
LoaderChainingASubmenuLaunchNonexistentSilent,
|
||||
loader_chaining_a_submenu_callback,
|
||||
app);
|
||||
submenu_add_item(
|
||||
app->submenu,
|
||||
"Trigger error: GUI",
|
||||
LoaderChainingASubmenuLaunchNonexistentGui,
|
||||
loader_chaining_a_submenu_callback,
|
||||
app);
|
||||
submenu_add_item(
|
||||
app->submenu,
|
||||
"Error, then launch A",
|
||||
LoaderChainingASubmenuLaunchNonexistentGuiThenA,
|
||||
loader_chaining_a_submenu_callback,
|
||||
app);
|
||||
|
||||
view_dispatcher_add_view(app->view_dispatcher, 0, submenu_get_view(app->submenu));
|
||||
view_dispatcher_set_navigation_event_callback(
|
||||
app->view_dispatcher, loader_chaining_a_nav_callback);
|
||||
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
|
||||
|
||||
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, 0);
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
void loader_chaining_a_free(LoaderChainingA* app) {
|
||||
furi_record_close(RECORD_DIALOGS);
|
||||
furi_record_close(RECORD_LOADER);
|
||||
furi_record_close(RECORD_GUI);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, 0);
|
||||
submenu_free(app->submenu);
|
||||
view_dispatcher_free(app->view_dispatcher);
|
||||
free(app);
|
||||
}
|
||||
|
||||
int32_t chaining_test_app_a(const char* arg) {
|
||||
LoaderChainingA* app = loader_chaining_a_alloc();
|
||||
|
||||
if(arg) {
|
||||
if(strlen(arg)) {
|
||||
DialogMessage* message = dialog_message_alloc();
|
||||
FuriString* text;
|
||||
|
||||
dialog_message_set_header(message, "Hi, I am A", 64, 0, AlignCenter, AlignTop);
|
||||
text = furi_string_alloc_printf("Me from the past says:\n%s", arg);
|
||||
dialog_message_set_buttons(message, NULL, "ok!", NULL);
|
||||
|
||||
dialog_message_set_text(
|
||||
message, furi_string_get_cstr(text), 64, 32, AlignCenter, AlignCenter);
|
||||
dialog_message_show(app->dialogs, message);
|
||||
dialog_message_free(message);
|
||||
furi_string_free(text);
|
||||
}
|
||||
}
|
||||
|
||||
view_dispatcher_run(app->view_dispatcher);
|
||||
|
||||
loader_chaining_a_free(app);
|
||||
return 0;
|
||||
}
|
||||
8
applications/debug/loader_chaining_b/application.fam
Normal file
8
applications/debug/loader_chaining_b/application.fam
Normal file
@@ -0,0 +1,8 @@
|
||||
App(
|
||||
appid="loader_chaining_b",
|
||||
name="Loader Chaining Test: App B",
|
||||
apptype=FlipperAppType.DEBUG,
|
||||
entry_point="chaining_test_app_b",
|
||||
stack_size=1 * 1024,
|
||||
fap_category="Debug",
|
||||
)
|
||||
27
applications/debug/loader_chaining_b/loader_chaining_b.c
Normal file
27
applications/debug/loader_chaining_b/loader_chaining_b.c
Normal file
@@ -0,0 +1,27 @@
|
||||
#include <furi.h>
|
||||
#include <dialogs/dialogs.h>
|
||||
#include <loader/loader.h>
|
||||
|
||||
int32_t chaining_test_app_b(const char* arg) {
|
||||
if(!arg) return 0;
|
||||
|
||||
Loader* loader = furi_record_open(RECORD_LOADER);
|
||||
DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
|
||||
|
||||
DialogMessage* message = dialog_message_alloc();
|
||||
dialog_message_set_header(message, "Hi, I am B", 64, 0, AlignCenter, AlignTop);
|
||||
FuriString* text = furi_string_alloc_printf("And A told me:\n%s", arg);
|
||||
dialog_message_set_text(message, furi_string_get_cstr(text), 64, 32, AlignCenter, AlignCenter);
|
||||
dialog_message_set_buttons(message, "Just quit", NULL, "Launch A");
|
||||
DialogMessageButton result = dialog_message_show(dialogs, message);
|
||||
dialog_message_free(message);
|
||||
furi_string_free(text);
|
||||
|
||||
if(result == DialogMessageButtonRight)
|
||||
loader_enqueue_launch(
|
||||
loader, "/ext/apps/Debug/loader_chaining_a.fap", NULL, LoaderDeferredLaunchFlagGui);
|
||||
|
||||
furi_record_close(RECORD_LOADER);
|
||||
furi_record_close(RECORD_DIALOGS);
|
||||
return 0;
|
||||
}
|
||||
@@ -5,6 +5,5 @@ App(
|
||||
entry_point="locale_test_app",
|
||||
requires=["gui", "locale"],
|
||||
stack_size=2 * 1024,
|
||||
order=70,
|
||||
fap_category="Debug",
|
||||
)
|
||||
|
||||
@@ -5,6 +5,5 @@ App(
|
||||
entry_point="rpc_debug_app",
|
||||
requires=["gui", "rpc_start", "notification"],
|
||||
stack_size=2 * 1024,
|
||||
order=10,
|
||||
fap_category="Debug",
|
||||
)
|
||||
|
||||
@@ -5,7 +5,6 @@ App(
|
||||
entry_point="speaker_debug_app",
|
||||
requires=["gui", "notification"],
|
||||
stack_size=2 * 1024,
|
||||
order=10,
|
||||
fap_category="Debug",
|
||||
fap_libs=["music_worker"],
|
||||
)
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
#include <furi.h>
|
||||
#include <notification/notification.h>
|
||||
#include <music_worker/music_worker.h>
|
||||
#include <cli/cli.h>
|
||||
#include <toolbox/args.h>
|
||||
#include <toolbox/pipe.h>
|
||||
#include <toolbox/cli/cli_registry.h>
|
||||
#include <cli/cli_main_commands.h>
|
||||
|
||||
#define TAG "SpeakerDebug"
|
||||
|
||||
@@ -19,14 +21,14 @@ typedef struct {
|
||||
typedef struct {
|
||||
MusicWorker* music_worker;
|
||||
FuriMessageQueue* message_queue;
|
||||
Cli* cli;
|
||||
CliRegistry* cli_registry;
|
||||
} SpeakerDebugApp;
|
||||
|
||||
static SpeakerDebugApp* speaker_app_alloc(void) {
|
||||
SpeakerDebugApp* app = (SpeakerDebugApp*)malloc(sizeof(SpeakerDebugApp));
|
||||
app->music_worker = music_worker_alloc();
|
||||
app->message_queue = furi_message_queue_alloc(8, sizeof(SpeakerDebugAppMessage));
|
||||
app->cli = furi_record_open(RECORD_CLI);
|
||||
app->cli_registry = furi_record_open(RECORD_CLI);
|
||||
return app;
|
||||
}
|
||||
|
||||
@@ -37,8 +39,8 @@ static void speaker_app_free(SpeakerDebugApp* app) {
|
||||
free(app);
|
||||
}
|
||||
|
||||
static void speaker_app_cli(Cli* cli, FuriString* args, void* context) {
|
||||
UNUSED(cli);
|
||||
static void speaker_app_cli(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(pipe);
|
||||
|
||||
SpeakerDebugApp* app = (SpeakerDebugApp*)context;
|
||||
SpeakerDebugAppMessage message;
|
||||
@@ -95,7 +97,8 @@ static void speaker_app_run(SpeakerDebugApp* app, const char* arg) {
|
||||
return;
|
||||
}
|
||||
|
||||
cli_add_command(app->cli, CLI_COMMAND, CliCommandFlagParallelSafe, speaker_app_cli, app);
|
||||
cli_registry_add_command(
|
||||
app->cli_registry, CLI_COMMAND, CliCommandFlagParallelSafe, speaker_app_cli, app);
|
||||
|
||||
SpeakerDebugAppMessage message;
|
||||
FuriStatus status;
|
||||
@@ -110,7 +113,7 @@ static void speaker_app_run(SpeakerDebugApp* app, const char* arg) {
|
||||
}
|
||||
}
|
||||
|
||||
cli_delete_command(app->cli, CLI_COMMAND);
|
||||
cli_registry_delete_command(app->cli_registry, CLI_COMMAND);
|
||||
}
|
||||
|
||||
int32_t speaker_debug_app(void* arg) {
|
||||
|
||||
@@ -6,7 +6,6 @@ App(
|
||||
entry_point="subghz_test_app",
|
||||
requires=["gui"],
|
||||
stack_size=4 * 1024,
|
||||
order=50,
|
||||
fap_icon="subghz_test_10px.png",
|
||||
fap_category="Debug",
|
||||
fap_icon_assets="images",
|
||||
|
||||
@@ -5,6 +5,5 @@ App(
|
||||
entry_point="text_box_element_test_app",
|
||||
requires=["gui"],
|
||||
stack_size=1 * 1024,
|
||||
order=140,
|
||||
fap_category="Debug",
|
||||
)
|
||||
|
||||
@@ -5,6 +5,5 @@ App(
|
||||
entry_point="text_box_view_test_app",
|
||||
requires=["gui"],
|
||||
stack_size=1 * 1024,
|
||||
order=140,
|
||||
fap_category="Debug",
|
||||
)
|
||||
|
||||
@@ -5,6 +5,5 @@ App(
|
||||
entry_point="uart_echo_app",
|
||||
requires=["gui"],
|
||||
stack_size=2 * 1024,
|
||||
order=70,
|
||||
fap_category="Debug",
|
||||
)
|
||||
|
||||
@@ -4,10 +4,10 @@ App(
|
||||
entry_point="unit_tests_on_system_start",
|
||||
sources=["unit_tests.c", "test_runner.c", "unit_test_api_table.cpp"],
|
||||
cdefines=["APP_UNIT_TESTS"],
|
||||
requires=["system_settings", "subghz_start"],
|
||||
requires=["system_settings", "cli_subghz"],
|
||||
provides=["delay_test"],
|
||||
resources="resources",
|
||||
order=100,
|
||||
order=30,
|
||||
)
|
||||
|
||||
App(
|
||||
@@ -18,7 +18,7 @@ App(
|
||||
entry_point="delay_test_app",
|
||||
stack_size=1 * 1024,
|
||||
requires=["unit_tests"],
|
||||
order=110,
|
||||
order=30,
|
||||
)
|
||||
|
||||
App(
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
|
||||
#include "tests/test_api.h"
|
||||
|
||||
#include <cli/cli.h>
|
||||
#include <toolbox/cli/cli_command.h>
|
||||
#include <toolbox/path.h>
|
||||
#include <toolbox/pipe.h>
|
||||
#include <loader/loader.h>
|
||||
#include <storage/storage.h>
|
||||
#include <notification/notification_messages.h>
|
||||
@@ -25,7 +26,7 @@ struct TestRunner {
|
||||
NotificationApp* notification;
|
||||
|
||||
// Temporary used things
|
||||
Cli* cli;
|
||||
PipeSide* pipe;
|
||||
FuriString* args;
|
||||
|
||||
// ELF related stuff
|
||||
@@ -38,14 +39,14 @@ struct TestRunner {
|
||||
int minunit_status;
|
||||
};
|
||||
|
||||
TestRunner* test_runner_alloc(Cli* cli, FuriString* args) {
|
||||
TestRunner* test_runner_alloc(PipeSide* pipe, FuriString* args) {
|
||||
TestRunner* instance = malloc(sizeof(TestRunner));
|
||||
|
||||
instance->storage = furi_record_open(RECORD_STORAGE);
|
||||
instance->loader = furi_record_open(RECORD_LOADER);
|
||||
instance->notification = furi_record_open(RECORD_NOTIFICATION);
|
||||
|
||||
instance->cli = cli;
|
||||
instance->pipe = pipe;
|
||||
instance->args = args;
|
||||
|
||||
instance->composite_resolver = composite_api_resolver_alloc();
|
||||
@@ -147,7 +148,7 @@ static void test_runner_run_internal(TestRunner* instance) {
|
||||
}
|
||||
|
||||
while(true) {
|
||||
if(cli_cmd_interrupt_received(instance->cli)) {
|
||||
if(cli_is_pipe_broken_or_is_etx_next_char(instance->pipe)) {
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <furi.h>
|
||||
#include <toolbox/pipe.h>
|
||||
|
||||
typedef struct TestRunner TestRunner;
|
||||
typedef struct Cli Cli;
|
||||
|
||||
TestRunner* test_runner_alloc(Cli* cli, FuriString* args);
|
||||
TestRunner* test_runner_alloc(PipeSide* pipe, FuriString* args);
|
||||
|
||||
void test_runner_free(TestRunner* isntance);
|
||||
void test_runner_free(TestRunner* instance);
|
||||
|
||||
void test_runner_run(TestRunner* isntance);
|
||||
void test_runner_run(TestRunner* instance);
|
||||
|
||||
@@ -6,9 +6,12 @@
|
||||
|
||||
#include <storage/storage.h>
|
||||
#include <applications/system/js_app/js_thread.h>
|
||||
#include <applications/system/js_app/js_value.h>
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define TAG "JsUnitTests"
|
||||
|
||||
#define JS_SCRIPT_PATH(name) EXT_PATH("unit_tests/js/" name ".js")
|
||||
|
||||
typedef enum {
|
||||
@@ -73,7 +76,311 @@ MU_TEST(js_test_storage) {
|
||||
js_test_run(JS_SCRIPT_PATH("storage"));
|
||||
}
|
||||
|
||||
static void js_value_test_compatibility_matrix(struct mjs* mjs) {
|
||||
static const JsValueType types[] = {
|
||||
JsValueTypeAny,
|
||||
JsValueTypeAnyArray,
|
||||
JsValueTypeAnyObject,
|
||||
JsValueTypeFunction,
|
||||
JsValueTypeRawPointer,
|
||||
JsValueTypeInt32,
|
||||
JsValueTypeDouble,
|
||||
JsValueTypeString,
|
||||
JsValueTypeBool,
|
||||
};
|
||||
|
||||
mjs_val_t values[] = {
|
||||
mjs_mk_undefined(),
|
||||
mjs_mk_foreign(mjs, (void*)0xDEADBEEF),
|
||||
mjs_mk_array(mjs),
|
||||
mjs_mk_object(mjs),
|
||||
mjs_mk_number(mjs, 123.456),
|
||||
mjs_mk_string(mjs, "test", ~0, false),
|
||||
mjs_mk_boolean(mjs, true),
|
||||
};
|
||||
|
||||
// for proper matrix formatting and better readability
|
||||
#define YES true
|
||||
#define NO_ false
|
||||
static const bool success_matrix[COUNT_OF(types)][COUNT_OF(values)] = {
|
||||
// types:
|
||||
{YES, YES, YES, YES, YES, YES, YES}, // any
|
||||
{NO_, NO_, YES, NO_, NO_, NO_, NO_}, // array
|
||||
{NO_, NO_, YES, YES, NO_, NO_, NO_}, // obj
|
||||
{NO_, NO_, NO_, NO_, NO_, NO_, NO_}, // fn
|
||||
{NO_, YES, NO_, NO_, NO_, NO_, NO_}, // ptr
|
||||
{NO_, NO_, NO_, NO_, YES, NO_, NO_}, // int32
|
||||
{NO_, NO_, NO_, NO_, YES, NO_, NO_}, // double
|
||||
{NO_, NO_, NO_, NO_, NO_, YES, NO_}, // str
|
||||
{NO_, NO_, NO_, NO_, NO_, NO_, YES}, // bool
|
||||
//
|
||||
//und ptr arr obj num str bool <- values
|
||||
};
|
||||
#undef NO_
|
||||
#undef YES
|
||||
|
||||
for(size_t i = 0; i < COUNT_OF(types); i++) {
|
||||
for(size_t j = 0; j < COUNT_OF(values); j++) {
|
||||
const JsValueDeclaration declaration = {
|
||||
.type = types[i],
|
||||
.n_children = 0,
|
||||
};
|
||||
// we only care about the status, not the result. double has the largest size out of
|
||||
// all the results
|
||||
uint8_t result[sizeof(double)];
|
||||
JsValueParseStatus status;
|
||||
JS_VALUE_PARSE(
|
||||
mjs,
|
||||
JS_VALUE_PARSE_SOURCE_VALUE(&declaration),
|
||||
JsValueParseFlagNone,
|
||||
&status,
|
||||
&values[j],
|
||||
result);
|
||||
if((status == JsValueParseStatusOk) != success_matrix[i][j]) {
|
||||
FURI_LOG_E(TAG, "type %zu, value %zu", i, j);
|
||||
mu_fail("see serial logs");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void js_value_test_literal(struct mjs* mjs) {
|
||||
static const JsValueType types[] = {
|
||||
JsValueTypeAny,
|
||||
JsValueTypeAnyArray,
|
||||
JsValueTypeAnyObject,
|
||||
};
|
||||
|
||||
mjs_val_t values[] = {
|
||||
mjs_mk_undefined(),
|
||||
mjs_mk_array(mjs),
|
||||
mjs_mk_object(mjs),
|
||||
};
|
||||
|
||||
mu_assert_int_eq(COUNT_OF(types), COUNT_OF(values));
|
||||
for(size_t i = 0; i < COUNT_OF(types); i++) {
|
||||
const JsValueDeclaration declaration = {
|
||||
.type = types[i],
|
||||
.n_children = 0,
|
||||
};
|
||||
mjs_val_t result;
|
||||
JsValueParseStatus status;
|
||||
JS_VALUE_PARSE(
|
||||
mjs,
|
||||
JS_VALUE_PARSE_SOURCE_VALUE(&declaration),
|
||||
JsValueParseFlagNone,
|
||||
&status,
|
||||
&values[i],
|
||||
&result);
|
||||
mu_assert_int_eq(JsValueParseStatusOk, status);
|
||||
mu_assert(result == values[i], "wrong result");
|
||||
}
|
||||
}
|
||||
|
||||
static void js_value_test_primitive(
|
||||
struct mjs* mjs,
|
||||
JsValueType type,
|
||||
const void* c_value,
|
||||
size_t c_value_size,
|
||||
mjs_val_t js_val) {
|
||||
const JsValueDeclaration declaration = {
|
||||
.type = type,
|
||||
.n_children = 0,
|
||||
};
|
||||
uint8_t result[c_value_size];
|
||||
JsValueParseStatus status;
|
||||
JS_VALUE_PARSE(
|
||||
mjs,
|
||||
JS_VALUE_PARSE_SOURCE_VALUE(&declaration),
|
||||
JsValueParseFlagNone,
|
||||
&status,
|
||||
&js_val,
|
||||
result);
|
||||
mu_assert_int_eq(JsValueParseStatusOk, status);
|
||||
if(type == JsValueTypeString) {
|
||||
const char* result_str = *(const char**)&result;
|
||||
mu_assert_string_eq(c_value, result_str);
|
||||
} else {
|
||||
mu_assert_mem_eq(c_value, result, c_value_size);
|
||||
}
|
||||
}
|
||||
|
||||
static void js_value_test_primitives(struct mjs* mjs) {
|
||||
int32_t i32 = 123;
|
||||
js_value_test_primitive(mjs, JsValueTypeInt32, &i32, sizeof(i32), mjs_mk_number(mjs, i32));
|
||||
|
||||
double dbl = 123.456;
|
||||
js_value_test_primitive(mjs, JsValueTypeDouble, &dbl, sizeof(dbl), mjs_mk_number(mjs, dbl));
|
||||
|
||||
const char* str = "test";
|
||||
js_value_test_primitive(
|
||||
mjs, JsValueTypeString, str, strlen(str) + 1, mjs_mk_string(mjs, str, ~0, false));
|
||||
|
||||
bool boolean = true;
|
||||
js_value_test_primitive(
|
||||
mjs, JsValueTypeBool, &boolean, sizeof(boolean), mjs_mk_boolean(mjs, boolean));
|
||||
}
|
||||
|
||||
static uint32_t
|
||||
js_value_test_enum(struct mjs* mjs, const JsValueDeclaration* decl, const char* value) {
|
||||
mjs_val_t str = mjs_mk_string(mjs, value, ~0, false);
|
||||
uint32_t result;
|
||||
JsValueParseStatus status;
|
||||
JS_VALUE_PARSE(
|
||||
mjs, JS_VALUE_PARSE_SOURCE_VALUE(decl), JsValueParseFlagNone, &status, &str, &result);
|
||||
if(status != JsValueParseStatusOk) return 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
static void js_value_test_enums(struct mjs* mjs) {
|
||||
static const JsValueEnumVariant enum_1_variants[] = {
|
||||
{"variant 1", 1},
|
||||
{"variant 2", 2},
|
||||
{"variant 3", 3},
|
||||
};
|
||||
static const JsValueDeclaration enum_1 = JS_VALUE_ENUM(uint32_t, enum_1_variants);
|
||||
|
||||
static const JsValueEnumVariant enum_2_variants[] = {
|
||||
{"read", 4},
|
||||
{"write", 8},
|
||||
};
|
||||
static const JsValueDeclaration enum_2 = JS_VALUE_ENUM(uint32_t, enum_2_variants);
|
||||
|
||||
mu_assert_int_eq(1, js_value_test_enum(mjs, &enum_1, "variant 1"));
|
||||
mu_assert_int_eq(2, js_value_test_enum(mjs, &enum_1, "variant 2"));
|
||||
mu_assert_int_eq(3, js_value_test_enum(mjs, &enum_1, "variant 3"));
|
||||
mu_assert_int_eq(0, js_value_test_enum(mjs, &enum_1, "not a thing"));
|
||||
|
||||
mu_assert_int_eq(0, js_value_test_enum(mjs, &enum_2, "variant 1"));
|
||||
mu_assert_int_eq(0, js_value_test_enum(mjs, &enum_2, "variant 2"));
|
||||
mu_assert_int_eq(0, js_value_test_enum(mjs, &enum_2, "variant 3"));
|
||||
mu_assert_int_eq(0, js_value_test_enum(mjs, &enum_2, "not a thing"));
|
||||
mu_assert_int_eq(4, js_value_test_enum(mjs, &enum_2, "read"));
|
||||
mu_assert_int_eq(8, js_value_test_enum(mjs, &enum_2, "write"));
|
||||
}
|
||||
|
||||
static void js_value_test_object(struct mjs* mjs) {
|
||||
static const JsValueDeclaration int_decl = JS_VALUE_SIMPLE(JsValueTypeInt32);
|
||||
|
||||
static const JsValueDeclaration str_decl = JS_VALUE_SIMPLE(JsValueTypeString);
|
||||
|
||||
static const JsValueEnumVariant enum_variants[] = {
|
||||
{"variant 1", 1},
|
||||
{"variant 2", 2},
|
||||
{"variant 3", 3},
|
||||
};
|
||||
static const JsValueDeclaration enum_decl = JS_VALUE_ENUM(uint32_t, enum_variants);
|
||||
|
||||
static const JsValueObjectField fields[] = {
|
||||
{"int", &int_decl},
|
||||
{"str", &str_decl},
|
||||
{"enum", &enum_decl},
|
||||
};
|
||||
static const JsValueDeclaration object_decl = JS_VALUE_OBJECT(fields);
|
||||
|
||||
mjs_val_t object = mjs_mk_object(mjs);
|
||||
JS_ASSIGN_MULTI(mjs, object) {
|
||||
JS_FIELD("str", mjs_mk_string(mjs, "Helloooo!", ~0, false));
|
||||
JS_FIELD("int", mjs_mk_number(mjs, 123));
|
||||
JS_FIELD("enum", mjs_mk_string(mjs, "variant 2", ~0, false));
|
||||
}
|
||||
|
||||
const char* result_str;
|
||||
int32_t result_int;
|
||||
uint32_t result_enum;
|
||||
JsValueParseStatus status;
|
||||
JS_VALUE_PARSE(
|
||||
mjs,
|
||||
JS_VALUE_PARSE_SOURCE_VALUE(&object_decl),
|
||||
JsValueParseFlagNone,
|
||||
&status,
|
||||
&object,
|
||||
&result_int,
|
||||
&result_str,
|
||||
&result_enum);
|
||||
mu_assert_int_eq(JsValueParseStatusOk, status);
|
||||
mu_assert_string_eq("Helloooo!", result_str);
|
||||
mu_assert_int_eq(123, result_int);
|
||||
mu_assert_int_eq(2, result_enum);
|
||||
}
|
||||
|
||||
static void js_value_test_default(struct mjs* mjs) {
|
||||
static const JsValueDeclaration int_decl =
|
||||
JS_VALUE_SIMPLE_W_DEFAULT(JsValueTypeInt32, int32_val, 123);
|
||||
static const JsValueDeclaration str_decl = JS_VALUE_SIMPLE(JsValueTypeString);
|
||||
|
||||
static const JsValueObjectField fields[] = {
|
||||
{"int", &int_decl},
|
||||
{"str", &str_decl},
|
||||
};
|
||||
static const JsValueDeclaration object_decl = JS_VALUE_OBJECT(fields);
|
||||
|
||||
mjs_val_t object = mjs_mk_object(mjs);
|
||||
JS_ASSIGN_MULTI(mjs, object) {
|
||||
JS_FIELD("str", mjs_mk_string(mjs, "Helloooo!", ~0, false));
|
||||
JS_FIELD("int", mjs_mk_undefined());
|
||||
}
|
||||
|
||||
const char* result_str;
|
||||
int32_t result_int;
|
||||
JsValueParseStatus status;
|
||||
JS_VALUE_PARSE(
|
||||
mjs,
|
||||
JS_VALUE_PARSE_SOURCE_VALUE(&object_decl),
|
||||
JsValueParseFlagNone,
|
||||
&status,
|
||||
&object,
|
||||
&result_int,
|
||||
&result_str);
|
||||
mu_assert_string_eq("Helloooo!", result_str);
|
||||
mu_assert_int_eq(123, result_int);
|
||||
}
|
||||
|
||||
static void js_value_test_args_fn(struct mjs* mjs) {
|
||||
static const JsValueDeclaration arg_list[] = {
|
||||
JS_VALUE_SIMPLE(JsValueTypeInt32),
|
||||
JS_VALUE_SIMPLE(JsValueTypeInt32),
|
||||
JS_VALUE_SIMPLE(JsValueTypeInt32),
|
||||
};
|
||||
static const JsValueArguments args = JS_VALUE_ARGS(arg_list);
|
||||
|
||||
int32_t a, b, c;
|
||||
JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &args, &a, &b, &c);
|
||||
|
||||
mu_assert_int_eq(123, a);
|
||||
mu_assert_int_eq(456, b);
|
||||
mu_assert_int_eq(-420, c);
|
||||
}
|
||||
|
||||
static void js_value_test_args(struct mjs* mjs) {
|
||||
mjs_val_t function = MJS_MK_FN(js_value_test_args_fn);
|
||||
|
||||
mjs_val_t result;
|
||||
mjs_val_t args[] = {
|
||||
mjs_mk_number(mjs, 123),
|
||||
mjs_mk_number(mjs, 456),
|
||||
mjs_mk_number(mjs, -420),
|
||||
};
|
||||
mu_assert_int_eq(
|
||||
MJS_OK, mjs_apply(mjs, &result, function, MJS_UNDEFINED, COUNT_OF(args), args));
|
||||
}
|
||||
|
||||
MU_TEST(js_value_test) {
|
||||
struct mjs* mjs = mjs_create(NULL);
|
||||
|
||||
js_value_test_compatibility_matrix(mjs);
|
||||
js_value_test_literal(mjs);
|
||||
js_value_test_primitives(mjs);
|
||||
js_value_test_enums(mjs);
|
||||
js_value_test_object(mjs);
|
||||
js_value_test_default(mjs);
|
||||
js_value_test_args(mjs);
|
||||
|
||||
mjs_destroy(mjs);
|
||||
}
|
||||
|
||||
MU_TEST_SUITE(test_js) {
|
||||
MU_RUN_TEST(js_value_test);
|
||||
MU_RUN_TEST(js_test_basic);
|
||||
MU_RUN_TEST(js_test_math);
|
||||
MU_RUN_TEST(js_test_event_loop);
|
||||
|
||||
@@ -396,6 +396,8 @@ void minunit_printf_warning(const char* format, ...);
|
||||
return; \
|
||||
} else { minunit_print_progress(); })
|
||||
|
||||
//-V:mu_assert_string_eq:526, 547
|
||||
|
||||
#define mu_assert_string_eq(expected, result) \
|
||||
MU__SAFE_BLOCK( \
|
||||
const char* minunit_tmp_e = expected; const char* minunit_tmp_r = result; \
|
||||
@@ -416,6 +418,8 @@ void minunit_printf_warning(const char* format, ...);
|
||||
return; \
|
||||
} else { minunit_print_progress(); })
|
||||
|
||||
//-V:mu_assert_mem_eq:526
|
||||
|
||||
#define mu_assert_mem_eq(expected, result, size) \
|
||||
MU__SAFE_BLOCK( \
|
||||
const void* minunit_tmp_e = expected; const void* minunit_tmp_r = result; \
|
||||
|
||||
@@ -25,16 +25,13 @@ MU_TEST(pipe_test_trivial) {
|
||||
mu_assert_int_eq(PIPE_SIZE - i, pipe_spaces_available(alice));
|
||||
mu_assert_int_eq(i, pipe_bytes_available(bob));
|
||||
|
||||
if(pipe_send(alice, &i, sizeof(uint8_t), 0) != sizeof(uint8_t)) {
|
||||
break;
|
||||
}
|
||||
if(pipe_spaces_available(alice) == 0) break;
|
||||
furi_check(pipe_send(alice, &i, sizeof(uint8_t)) == sizeof(uint8_t));
|
||||
|
||||
mu_assert_int_eq(PIPE_SIZE - i, pipe_spaces_available(bob));
|
||||
mu_assert_int_eq(i, pipe_bytes_available(alice));
|
||||
|
||||
if(pipe_send(bob, &i, sizeof(uint8_t), 0) != sizeof(uint8_t)) {
|
||||
break;
|
||||
}
|
||||
furi_check(pipe_send(bob, &i, sizeof(uint8_t)) == sizeof(uint8_t));
|
||||
}
|
||||
|
||||
pipe_free(alice);
|
||||
@@ -43,10 +40,9 @@ MU_TEST(pipe_test_trivial) {
|
||||
for(uint8_t i = 0;; ++i) {
|
||||
mu_assert_int_eq(PIPE_SIZE - i, pipe_bytes_available(bob));
|
||||
|
||||
if(pipe_bytes_available(bob) == 0) break;
|
||||
uint8_t value;
|
||||
if(pipe_receive(bob, &value, sizeof(uint8_t), 0) != sizeof(uint8_t)) {
|
||||
break;
|
||||
}
|
||||
furi_check(pipe_receive(bob, &value, sizeof(uint8_t)) == sizeof(uint8_t));
|
||||
|
||||
mu_assert_int_eq(i, value);
|
||||
}
|
||||
@@ -68,16 +64,15 @@ typedef struct {
|
||||
static void on_data_arrived(PipeSide* pipe, void* context) {
|
||||
AncillaryThreadContext* ctx = context;
|
||||
ctx->flag |= TestFlagDataArrived;
|
||||
uint8_t buffer[PIPE_SIZE];
|
||||
size_t size = pipe_receive(pipe, buffer, sizeof(buffer), 0);
|
||||
pipe_send(pipe, buffer, size, 0);
|
||||
uint8_t input;
|
||||
size_t size = pipe_receive(pipe, &input, sizeof(input));
|
||||
pipe_send(pipe, &input, size);
|
||||
}
|
||||
|
||||
static void on_space_freed(PipeSide* pipe, void* context) {
|
||||
UNUSED(pipe);
|
||||
AncillaryThreadContext* ctx = context;
|
||||
ctx->flag |= TestFlagSpaceFreed;
|
||||
const char* message = "Hi!";
|
||||
pipe_send(pipe, message, strlen(message), 0);
|
||||
}
|
||||
|
||||
static void on_became_broken(PipeSide* pipe, void* context) {
|
||||
@@ -117,22 +112,16 @@ MU_TEST(pipe_test_event_loop) {
|
||||
furi_thread_start(thread);
|
||||
|
||||
const char* message = "Hello!";
|
||||
pipe_send(alice, message, strlen(message), FuriWaitForever);
|
||||
pipe_send(alice, message, strlen(message));
|
||||
|
||||
char buffer_1[16];
|
||||
size_t size = pipe_receive(alice, buffer_1, sizeof(buffer_1), FuriWaitForever);
|
||||
size_t size = pipe_receive(alice, buffer_1, strlen(message));
|
||||
buffer_1[size] = 0;
|
||||
|
||||
char buffer_2[16];
|
||||
const char* expected_reply = "Hi!";
|
||||
size = pipe_receive(alice, buffer_2, sizeof(buffer_2), FuriWaitForever);
|
||||
buffer_2[size] = 0;
|
||||
|
||||
pipe_free(alice);
|
||||
furi_thread_join(thread);
|
||||
|
||||
mu_assert_string_eq(message, buffer_1);
|
||||
mu_assert_string_eq(expected_reply, buffer_2);
|
||||
mu_assert_int_eq(
|
||||
TestFlagDataArrived | TestFlagSpaceFreed | TestFlagBecameBroken,
|
||||
furi_thread_get_return_code(thread));
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
#include <rpc/rpc.h>
|
||||
#include <rpc/rpc_i.h>
|
||||
#include <cli/cli.h>
|
||||
#include <storage/storage.h>
|
||||
#include <loader/loader.h>
|
||||
#include <storage/filesystem_api_defines.h>
|
||||
|
||||
@@ -521,11 +521,6 @@ MU_TEST(test_storage_data_path) {
|
||||
// check that appsdata folder exists
|
||||
mu_check(storage_dir_exists(storage, APPS_DATA_PATH));
|
||||
|
||||
// check that cli folder exists
|
||||
mu_check(storage_dir_exists(storage, APPSDATA_APP_PATH("cli")));
|
||||
|
||||
storage_simply_remove(storage, APPSDATA_APP_PATH("cli"));
|
||||
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <rpc/rpc_i.h>
|
||||
#include <flipper.pb.h>
|
||||
#include <applications/system/js_app/js_thread.h>
|
||||
#include <applications/system/js_app/js_value.h>
|
||||
|
||||
static constexpr auto unit_tests_api_table = sort(create_array_t<sym_entry>(
|
||||
API_METHOD(resource_manifest_reader_alloc, ResourceManifestReader*, (Storage*)),
|
||||
@@ -38,4 +39,16 @@ static constexpr auto unit_tests_api_table = sort(create_array_t<sym_entry>(
|
||||
JsThread*,
|
||||
(const char* script_path, JsThreadCallback callback, void* context)),
|
||||
API_METHOD(js_thread_stop, void, (JsThread * worker)),
|
||||
API_METHOD(js_value_buffer_size, size_t, (const JsValueParseDeclaration declaration)),
|
||||
API_METHOD(
|
||||
js_value_parse,
|
||||
JsValueParseStatus,
|
||||
(struct mjs * mjs,
|
||||
const JsValueParseDeclaration declaration,
|
||||
JsValueParseFlag flags,
|
||||
mjs_val_t* buffer,
|
||||
size_t buf_size,
|
||||
mjs_val_t* source,
|
||||
size_t n_c_vals,
|
||||
...)),
|
||||
API_VARIABLE(PB_Main_msg, PB_Main_msg_t)));
|
||||
|
||||
@@ -1,21 +1,24 @@
|
||||
#include <furi.h>
|
||||
#include <cli/cli.h>
|
||||
#include <toolbox/pipe.h>
|
||||
#include <toolbox/cli/cli_command.h>
|
||||
#include <toolbox/cli/cli_registry.h>
|
||||
#include <cli/cli_main_commands.h>
|
||||
|
||||
#include "test_runner.h"
|
||||
|
||||
void unit_tests_cli(Cli* cli, FuriString* args, void* context) {
|
||||
UNUSED(cli);
|
||||
void unit_tests_cli(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(context);
|
||||
|
||||
TestRunner* test_runner = test_runner_alloc(cli, args);
|
||||
TestRunner* test_runner = test_runner_alloc(pipe, args);
|
||||
test_runner_run(test_runner);
|
||||
test_runner_free(test_runner);
|
||||
}
|
||||
|
||||
void unit_tests_on_system_start(void) {
|
||||
#ifdef SRV_CLI
|
||||
Cli* cli = furi_record_open(RECORD_CLI);
|
||||
cli_add_command(cli, "unit_tests", CliCommandFlagParallelSafe, unit_tests_cli, NULL);
|
||||
CliRegistry* registry = furi_record_open(RECORD_CLI);
|
||||
cli_registry_add_command(
|
||||
registry, "unit_tests", CliCommandFlagParallelSafe, unit_tests_cli, NULL);
|
||||
furi_record_close(RECORD_CLI);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -5,6 +5,5 @@ App(
|
||||
entry_point="usb_mouse_app",
|
||||
requires=["gui"],
|
||||
stack_size=1 * 1024,
|
||||
order=60,
|
||||
fap_category="Debug",
|
||||
)
|
||||
|
||||
@@ -5,6 +5,5 @@ App(
|
||||
entry_point="usb_test_app",
|
||||
requires=["gui"],
|
||||
stack_size=1 * 1024,
|
||||
order=50,
|
||||
fap_category="Debug",
|
||||
)
|
||||
|
||||
@@ -5,6 +5,5 @@ App(
|
||||
entry_point="vibro_test_app",
|
||||
requires=["gui"],
|
||||
stack_size=1 * 1024,
|
||||
order=20,
|
||||
fap_category="Debug",
|
||||
)
|
||||
|
||||
@@ -85,7 +85,7 @@ typedef struct {
|
||||
volatile SubGhzDeviceCC1101ExtState state;
|
||||
volatile SubGhzDeviceCC1101ExtRegulation regulation;
|
||||
const GpioPin* async_mirror_pin;
|
||||
FuriHalSpiBusHandle* spi_bus_handle;
|
||||
const FuriHalSpiBusHandle* spi_bus_handle;
|
||||
const GpioPin* g0_pin;
|
||||
SubGhzDeviceCC1101ExtAsyncTx async_tx;
|
||||
SubGhzDeviceCC1101ExtAsyncRx async_rx;
|
||||
|
||||
@@ -21,11 +21,6 @@ App(
|
||||
name="On start hooks",
|
||||
apptype=FlipperAppType.METAPACKAGE,
|
||||
provides=[
|
||||
"ibutton_start",
|
||||
"onewire_start",
|
||||
"subghz_start",
|
||||
"infrared_start",
|
||||
"lfrfid_start",
|
||||
"nfc_start",
|
||||
"cli",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -7,5 +7,5 @@ App(
|
||||
requires=["gui"],
|
||||
stack_size=4 * 1024,
|
||||
icon="A_FileManager_14",
|
||||
order=0,
|
||||
order=10,
|
||||
)
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
#include "archive_apps.h"
|
||||
#include "archive_browser.h"
|
||||
|
||||
static const char* known_apps[] = {
|
||||
static const char* const known_apps[] = {
|
||||
[ArchiveAppTypeU2f] = "u2f",
|
||||
[ArchiveAppTypeSetting] = "setting",
|
||||
};
|
||||
|
||||
ArchiveAppTypeEnum archive_get_app_type(const char* path) {
|
||||
@@ -36,6 +37,8 @@ bool archive_app_is_available(void* context, const char* path) {
|
||||
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
return file_exists;
|
||||
} else if(app == ArchiveAppTypeSetting) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
@@ -53,6 +56,9 @@ bool archive_app_read_dir(void* context, const char* path) {
|
||||
if(app == ArchiveAppTypeU2f) {
|
||||
archive_add_app_item(browser, "/app:u2f/U2F Token");
|
||||
return true;
|
||||
} else if(app == ArchiveAppTypeSetting) {
|
||||
archive_add_app_item(browser, path);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
@@ -75,6 +81,8 @@ void archive_app_delete_file(void* context, const char* path) {
|
||||
if(archive_is_favorite("/app:u2f/U2F Token")) {
|
||||
archive_favorites_delete("/app:u2f/U2F Token");
|
||||
}
|
||||
} else if(app == ArchiveAppTypeSetting) {
|
||||
// can't delete a setting!
|
||||
}
|
||||
|
||||
if(res) {
|
||||
|
||||
@@ -4,12 +4,14 @@
|
||||
|
||||
typedef enum {
|
||||
ArchiveAppTypeU2f,
|
||||
ArchiveAppTypeSetting,
|
||||
ArchiveAppTypeUnknown,
|
||||
ArchiveAppsTotal,
|
||||
} ArchiveAppTypeEnum;
|
||||
|
||||
static const ArchiveFileTypeEnum app_file_types[] = {
|
||||
[ArchiveAppTypeU2f] = ArchiveFileTypeU2f,
|
||||
[ArchiveAppTypeSetting] = ArchiveFileTypeSetting,
|
||||
[ArchiveAppTypeUnknown] = ArchiveFileTypeUnknown,
|
||||
};
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#define TAB_DEFAULT ArchiveTabFavorites // Start tab
|
||||
#define FILE_LIST_BUF_LEN 50
|
||||
|
||||
static const char* tab_default_paths[] = {
|
||||
static const char* const tab_default_paths[] = {
|
||||
[ArchiveTabFavorites] = "/app:favorites",
|
||||
[ArchiveTabIButton] = EXT_PATH("ibutton"),
|
||||
[ArchiveTabNFC] = EXT_PATH("nfc"),
|
||||
@@ -20,7 +20,7 @@ static const char* tab_default_paths[] = {
|
||||
[ArchiveTabBrowser] = STORAGE_EXT_PATH_PREFIX,
|
||||
};
|
||||
|
||||
static const char* known_ext[] = {
|
||||
static const char* const known_ext[] = {
|
||||
[ArchiveFileTypeIButton] = ".ibtn",
|
||||
[ArchiveFileTypeNFC] = ".nfc",
|
||||
[ArchiveFileTypeSubGhz] = ".sub",
|
||||
@@ -34,6 +34,7 @@ static const char* known_ext[] = {
|
||||
[ArchiveFileTypeFolder] = "?",
|
||||
[ArchiveFileTypeUnknown] = "*",
|
||||
[ArchiveFileTypeAppOrJs] = ".fap|.js",
|
||||
[ArchiveFileTypeSetting] = "?",
|
||||
};
|
||||
|
||||
static const ArchiveFileTypeEnum known_type[] = {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
|
||||
#include "archive_favorites.h"
|
||||
#include "archive_files.h"
|
||||
#include "archive_apps.h"
|
||||
#include "archive_browser.h"
|
||||
|
||||
#include <dialogs/dialogs.h>
|
||||
|
||||
#define ARCHIVE_FAV_FILE_BUF_LEN 32
|
||||
|
||||
static bool archive_favorites_read_line(File* file, FuriString* str_result) {
|
||||
@@ -337,3 +338,46 @@ void archive_favorites_save(void* context) {
|
||||
storage_file_free(file);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
}
|
||||
|
||||
void archive_favorites_handle_setting_pin_unpin(const char* app_name, const char* setting) {
|
||||
DialogMessage* message = dialog_message_alloc();
|
||||
|
||||
FuriString* setting_path = furi_string_alloc_set_str(app_name);
|
||||
if(setting) {
|
||||
furi_string_push_back(setting_path, '/');
|
||||
furi_string_cat_str(setting_path, setting);
|
||||
}
|
||||
const char* setting_path_str = furi_string_get_cstr(setting_path);
|
||||
|
||||
bool is_favorite = archive_is_favorite("/app:setting/%s", setting_path_str);
|
||||
dialog_message_set_header(
|
||||
message,
|
||||
is_favorite ? "Unpin This Setting?" : "Pin This Setting?",
|
||||
64,
|
||||
0,
|
||||
AlignCenter,
|
||||
AlignTop);
|
||||
dialog_message_set_text(
|
||||
message,
|
||||
is_favorite ? "It will no longer be\naccessible from the\nFavorites menu" :
|
||||
"It will be accessible from the\nFavorites menu",
|
||||
64,
|
||||
32,
|
||||
AlignCenter,
|
||||
AlignCenter);
|
||||
dialog_message_set_buttons(
|
||||
message, is_favorite ? "Unpin" : "Go back", NULL, is_favorite ? "Keep pinned" : "Pin");
|
||||
|
||||
DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
|
||||
DialogMessageButton button = dialog_message_show(dialogs, message);
|
||||
furi_record_close(RECORD_DIALOGS);
|
||||
|
||||
if(is_favorite && button == DialogMessageButtonLeft) {
|
||||
archive_favorites_delete("/app:setting/%s", setting_path_str);
|
||||
} else if(!is_favorite && button == DialogMessageButtonRight) {
|
||||
archive_file_append(ARCHIVE_FAV_PATH, "/app:setting/%s\n", setting_path_str);
|
||||
}
|
||||
|
||||
furi_string_free(setting_path);
|
||||
dialog_message_free(message);
|
||||
}
|
||||
|
||||
@@ -12,3 +12,13 @@ bool archive_is_favorite(const char* format, ...) _ATTRIBUTE((__format__(__print
|
||||
bool archive_favorites_rename(const char* src, const char* dst);
|
||||
void archive_add_to_favorites(const char* file_path);
|
||||
void archive_favorites_save(void* context);
|
||||
|
||||
/**
|
||||
* Intended to be called by settings apps to handle long presses, as well as
|
||||
* internally from within the archive
|
||||
*
|
||||
* @param app_name name of the referring application
|
||||
* @param setting name of the setting, which will be both displayed to the user
|
||||
* and passed to the application as an argument upon recall
|
||||
*/
|
||||
void archive_favorites_handle_setting_pin_unpin(const char* app_name, const char* setting);
|
||||
|
||||
@@ -20,6 +20,7 @@ typedef enum {
|
||||
ArchiveFileTypeFolder,
|
||||
ArchiveFileTypeUnknown,
|
||||
ArchiveFileTypeAppOrJs,
|
||||
ArchiveFileTypeSetting,
|
||||
ArchiveFileTypeLoading,
|
||||
} ArchiveFileTypeEnum;
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ static void archive_loader_callback(const void* message, void* context) {
|
||||
const LoaderEvent* event = message;
|
||||
ArchiveApp* archive = (ArchiveApp*)context;
|
||||
|
||||
if(event->type == LoaderEventTypeApplicationStopped) {
|
||||
if(event->type == LoaderEventTypeNoMoreAppsInQueue) {
|
||||
view_dispatcher_send_custom_event(
|
||||
archive->view_dispatcher, ArchiveBrowserEventListRefresh);
|
||||
}
|
||||
@@ -52,20 +52,37 @@ static void archive_run_in_app(ArchiveBrowserView* browser, ArchiveFile_t* selec
|
||||
UNUSED(browser);
|
||||
Loader* loader = furi_record_open(RECORD_LOADER);
|
||||
|
||||
const char* app_name = archive_get_flipper_app_name(selected->type);
|
||||
|
||||
if(app_name) {
|
||||
if(selected->is_app) {
|
||||
char* param = strrchr(furi_string_get_cstr(selected->path), '/');
|
||||
if(param != NULL) {
|
||||
param++;
|
||||
}
|
||||
loader_start_with_gui_error(loader, app_name, param);
|
||||
if(selected->type == ArchiveFileTypeSetting) {
|
||||
FuriString* app_name = furi_string_alloc_set(selected->path);
|
||||
furi_string_right(app_name, furi_string_search_char(app_name, '/', 1) + 1);
|
||||
size_t slash = furi_string_search_char(app_name, '/', 1);
|
||||
if(slash != FURI_STRING_FAILURE) {
|
||||
furi_string_left(app_name, slash);
|
||||
FuriString* app_args =
|
||||
furi_string_alloc_set_str(furi_string_get_cstr(app_name) + slash + 1);
|
||||
loader_start_with_gui_error(
|
||||
loader, furi_string_get_cstr(app_name), furi_string_get_cstr(app_args));
|
||||
furi_string_free(app_args);
|
||||
} else {
|
||||
loader_start_with_gui_error(loader, app_name, furi_string_get_cstr(selected->path));
|
||||
loader_start_with_gui_error(loader, furi_string_get_cstr(app_name), NULL);
|
||||
}
|
||||
furi_string_free(app_name);
|
||||
} else {
|
||||
loader_start_with_gui_error(loader, furi_string_get_cstr(selected->path), NULL);
|
||||
const char* app_name = archive_get_flipper_app_name(selected->type);
|
||||
if(app_name) {
|
||||
if(selected->is_app) {
|
||||
char* param = strrchr(furi_string_get_cstr(selected->path), '/');
|
||||
if(param != NULL) {
|
||||
param++;
|
||||
}
|
||||
loader_start_with_gui_error(loader, app_name, param);
|
||||
} else {
|
||||
loader_start_with_gui_error(
|
||||
loader, app_name, furi_string_get_cstr(selected->path));
|
||||
}
|
||||
} else {
|
||||
loader_start_with_gui_error(loader, furi_string_get_cstr(selected->path), NULL);
|
||||
}
|
||||
}
|
||||
|
||||
furi_record_close(RECORD_LOADER);
|
||||
|
||||
@@ -28,6 +28,7 @@ static const Icon* ArchiveItemIcons[] = {
|
||||
[ArchiveFileTypeInfrared] = &I_ir_10px,
|
||||
[ArchiveFileTypeBadUsb] = &I_badusb_10px,
|
||||
[ArchiveFileTypeU2f] = &I_u2f_10px,
|
||||
[ArchiveFileTypeSetting] = &I_settings_10px,
|
||||
[ArchiveFileTypeUpdateManifest] = &I_update_10px,
|
||||
[ArchiveFileTypeFolder] = &I_dir_10px,
|
||||
[ArchiveFileTypeUnknown] = &I_unknown_10px,
|
||||
|
||||
@@ -40,11 +40,8 @@ static const uint8_t numpad_keys[10] = {
|
||||
};
|
||||
|
||||
uint32_t ducky_get_command_len(const char* line) {
|
||||
uint32_t len = strlen(line);
|
||||
for(uint32_t i = 0; i < len; i++) {
|
||||
if(line[i] == ' ') return i;
|
||||
}
|
||||
return 0;
|
||||
char* first_space = strchr(line, ' ');
|
||||
return first_space ? (first_space - line) : 0;
|
||||
}
|
||||
|
||||
bool ducky_is_line_end(const char chr) {
|
||||
@@ -180,37 +177,46 @@ static bool ducky_string_next(BadUsbScript* bad_usb) {
|
||||
|
||||
static int32_t ducky_parse_line(BadUsbScript* bad_usb, FuriString* line) {
|
||||
uint32_t line_len = furi_string_size(line);
|
||||
const char* line_tmp = furi_string_get_cstr(line);
|
||||
const char* line_cstr = furi_string_get_cstr(line);
|
||||
|
||||
if(line_len == 0) {
|
||||
return SCRIPT_STATE_NEXT_LINE; // Skip empty lines
|
||||
}
|
||||
FURI_LOG_D(WORKER_TAG, "line:%s", line_tmp);
|
||||
FURI_LOG_D(WORKER_TAG, "line:%s", line_cstr);
|
||||
|
||||
// Ducky Lang Functions
|
||||
int32_t cmd_result = ducky_execute_cmd(bad_usb, line_tmp);
|
||||
int32_t cmd_result = ducky_execute_cmd(bad_usb, line_cstr);
|
||||
if(cmd_result != SCRIPT_STATE_CMD_UNKNOWN) {
|
||||
return cmd_result;
|
||||
}
|
||||
|
||||
// Mouse Keys
|
||||
uint16_t key = ducky_get_mouse_keycode_by_name(line_tmp);
|
||||
uint16_t key = ducky_get_mouse_keycode_by_name(line_cstr);
|
||||
if(key != HID_MOUSE_INVALID) {
|
||||
bad_usb->hid->mouse_press(bad_usb->hid_inst, key);
|
||||
bad_usb->hid->mouse_release(bad_usb->hid_inst, key);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Special keys + modifiers
|
||||
key = ducky_get_keycode(bad_usb, line_tmp, false);
|
||||
if(key == HID_KEYBOARD_NONE) {
|
||||
return ducky_error(bad_usb, "No keycode defined for %s", line_tmp);
|
||||
}
|
||||
if((key & 0xFF00) != 0) {
|
||||
// It's a modifier key
|
||||
line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
|
||||
key |= ducky_get_keycode(bad_usb, line_tmp, true);
|
||||
// Parse chain of modifiers linked by spaces and hyphens
|
||||
uint16_t modifiers = 0;
|
||||
while(1) {
|
||||
key = ducky_get_next_modifier_keycode_by_name(&line_cstr);
|
||||
if(key == HID_KEYBOARD_NONE) break;
|
||||
|
||||
modifiers |= key;
|
||||
char next_char = *line_cstr;
|
||||
if(next_char == ' ' || next_char == '-') line_cstr++;
|
||||
}
|
||||
|
||||
// Main key
|
||||
char next_char = *line_cstr;
|
||||
uint16_t main_key = ducky_get_keycode_by_name(line_cstr);
|
||||
if(!main_key && next_char) main_key = BADUSB_ASCII_TO_KEY(bad_usb, next_char);
|
||||
key = modifiers | main_key;
|
||||
|
||||
if(key == 0 && next_char) ducky_error(bad_usb, "No keycode defined for %s", line_cstr);
|
||||
|
||||
bad_usb->hid->kb_press(bad_usb->hid_inst, key);
|
||||
bad_usb->hid->kb_release(bad_usb->hid_inst, key);
|
||||
return 0;
|
||||
|
||||
@@ -54,6 +54,8 @@ uint32_t ducky_get_command_len(const char* line);
|
||||
|
||||
bool ducky_is_line_end(const char chr);
|
||||
|
||||
uint16_t ducky_get_next_modifier_keycode_by_name(const char** param);
|
||||
|
||||
uint16_t ducky_get_keycode_by_name(const char* param);
|
||||
|
||||
uint16_t ducky_get_media_keycode_by_name(const char* param);
|
||||
|
||||
@@ -6,21 +6,16 @@ typedef struct {
|
||||
uint16_t keycode;
|
||||
} DuckyKey;
|
||||
|
||||
static const DuckyKey ducky_keys[] = {
|
||||
{"CTRL-ALT", KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_ALT},
|
||||
{"CTRL-SHIFT", KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_SHIFT},
|
||||
{"ALT-SHIFT", KEY_MOD_LEFT_ALT | KEY_MOD_LEFT_SHIFT},
|
||||
{"ALT-GUI", KEY_MOD_LEFT_ALT | KEY_MOD_LEFT_GUI},
|
||||
{"GUI-SHIFT", KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT},
|
||||
{"GUI-CTRL", KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_CTRL},
|
||||
|
||||
static const DuckyKey ducky_modifier_keys[] = {
|
||||
{"CTRL", KEY_MOD_LEFT_CTRL},
|
||||
{"CONTROL", KEY_MOD_LEFT_CTRL},
|
||||
{"SHIFT", KEY_MOD_LEFT_SHIFT},
|
||||
{"ALT", KEY_MOD_LEFT_ALT},
|
||||
{"GUI", KEY_MOD_LEFT_GUI},
|
||||
{"WINDOWS", KEY_MOD_LEFT_GUI},
|
||||
};
|
||||
|
||||
static const DuckyKey ducky_keys[] = {
|
||||
{"DOWNARROW", HID_KEYBOARD_DOWN_ARROW},
|
||||
{"DOWN", HID_KEYBOARD_DOWN_ARROW},
|
||||
{"LEFTARROW", HID_KEYBOARD_LEFT_ARROW},
|
||||
@@ -119,6 +114,23 @@ static const DuckyKey ducky_mouse_keys[] = {
|
||||
{"WHEEL_CLICK", HID_MOUSE_BTN_WHEEL},
|
||||
};
|
||||
|
||||
uint16_t ducky_get_next_modifier_keycode_by_name(const char** param) {
|
||||
const char* input_str = *param;
|
||||
|
||||
for(size_t i = 0; i < COUNT_OF(ducky_modifier_keys); i++) {
|
||||
size_t key_cmd_len = strlen(ducky_modifier_keys[i].name);
|
||||
if((strncmp(input_str, ducky_modifier_keys[i].name, key_cmd_len) == 0)) {
|
||||
char next_char_after_key = input_str[key_cmd_len];
|
||||
if(ducky_is_line_end(next_char_after_key) || (next_char_after_key == '-')) {
|
||||
*param = &input_str[key_cmd_len];
|
||||
return ducky_modifier_keys[i].keycode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return HID_KEYBOARD_NONE;
|
||||
}
|
||||
|
||||
uint16_t ducky_get_keycode_by_name(const char* param) {
|
||||
for(size_t i = 0; i < COUNT_OF(ducky_keys); i++) {
|
||||
size_t key_cmd_len = strlen(ducky_keys[i].name);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "usb_uart_bridge.h"
|
||||
#include "usb_cdc.h"
|
||||
#include <cli/cli_vcp.h>
|
||||
#include <cli/cli.h>
|
||||
#include <toolbox/api_lock.h>
|
||||
#include <furi_hal.h>
|
||||
#include <furi_hal_usb_cdc.h>
|
||||
@@ -106,15 +105,15 @@ static void usb_uart_on_irq_rx_dma_cb(
|
||||
static void usb_uart_vcp_init(UsbUartBridge* usb_uart, uint8_t vcp_ch) {
|
||||
furi_hal_usb_unlock();
|
||||
if(vcp_ch == 0) {
|
||||
Cli* cli = furi_record_open(RECORD_CLI);
|
||||
cli_session_close(cli);
|
||||
furi_record_close(RECORD_CLI);
|
||||
CliVcp* cli_vcp = furi_record_open(RECORD_CLI_VCP);
|
||||
cli_vcp_disable(cli_vcp);
|
||||
furi_record_close(RECORD_CLI_VCP);
|
||||
furi_check(furi_hal_usb_set_config(&usb_cdc_single, NULL) == true);
|
||||
} else {
|
||||
furi_check(furi_hal_usb_set_config(&usb_cdc_dual, NULL) == true);
|
||||
Cli* cli = furi_record_open(RECORD_CLI);
|
||||
cli_session_open(cli, &cli_vcp);
|
||||
furi_record_close(RECORD_CLI);
|
||||
CliVcp* cli_vcp = furi_record_open(RECORD_CLI_VCP);
|
||||
cli_vcp_enable(cli_vcp);
|
||||
furi_record_close(RECORD_CLI_VCP);
|
||||
}
|
||||
furi_hal_cdc_set_callbacks(vcp_ch, (CdcCallbacks*)&cdc_cb, usb_uart);
|
||||
}
|
||||
@@ -123,9 +122,9 @@ static void usb_uart_vcp_deinit(UsbUartBridge* usb_uart, uint8_t vcp_ch) {
|
||||
UNUSED(usb_uart);
|
||||
furi_hal_cdc_set_callbacks(vcp_ch, NULL, NULL);
|
||||
if(vcp_ch != 0) {
|
||||
Cli* cli = furi_record_open(RECORD_CLI);
|
||||
cli_session_close(cli);
|
||||
furi_record_close(RECORD_CLI);
|
||||
CliVcp* cli_vcp = furi_record_open(RECORD_CLI_VCP);
|
||||
cli_vcp_disable(cli_vcp);
|
||||
furi_record_close(RECORD_CLI_VCP);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -309,9 +308,9 @@ static int32_t usb_uart_worker(void* context) {
|
||||
|
||||
furi_hal_usb_unlock();
|
||||
furi_check(furi_hal_usb_set_config(&usb_cdc_single, NULL) == true);
|
||||
Cli* cli = furi_record_open(RECORD_CLI);
|
||||
cli_session_open(cli, &cli_vcp);
|
||||
furi_record_close(RECORD_CLI);
|
||||
CliVcp* cli_vcp = furi_record_open(RECORD_CLI_VCP);
|
||||
cli_vcp_enable(cli_vcp);
|
||||
furi_record_close(RECORD_CLI_VCP);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -13,10 +13,10 @@ App(
|
||||
)
|
||||
|
||||
App(
|
||||
appid="ibutton_start",
|
||||
apptype=FlipperAppType.STARTUP,
|
||||
appid="cli_ikey",
|
||||
targets=["f7"],
|
||||
entry_point="ibutton_on_system_start",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="cli_ikey_ep",
|
||||
requires=["cli"],
|
||||
sources=["ibutton_cli.c"],
|
||||
order=60,
|
||||
)
|
||||
|
||||
@@ -1,26 +1,14 @@
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
|
||||
#include <cli/cli.h>
|
||||
#include <cli/cli_main_commands.h>
|
||||
#include <toolbox/args.h>
|
||||
#include <toolbox/pipe.h>
|
||||
|
||||
#include <ibutton/ibutton_key.h>
|
||||
#include <ibutton/ibutton_worker.h>
|
||||
#include <ibutton/ibutton_protocols.h>
|
||||
|
||||
static void ibutton_cli(Cli* cli, FuriString* args, void* context);
|
||||
|
||||
// app cli function
|
||||
void ibutton_on_system_start(void) {
|
||||
#ifdef SRV_CLI
|
||||
Cli* cli = furi_record_open(RECORD_CLI);
|
||||
cli_add_command(cli, "ikey", CliCommandFlagDefault, ibutton_cli, cli);
|
||||
furi_record_close(RECORD_CLI);
|
||||
#else
|
||||
UNUSED(ibutton_cli);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void ibutton_cli_print_usage(void) {
|
||||
printf("Usage:\r\n");
|
||||
printf("ikey read\r\n");
|
||||
@@ -92,7 +80,7 @@ static void ibutton_cli_worker_read_cb(void* context) {
|
||||
furi_event_flag_set(event, EVENT_FLAG_IBUTTON_COMPLETE);
|
||||
}
|
||||
|
||||
static void ibutton_cli_read(Cli* cli) {
|
||||
static void ibutton_cli_read(PipeSide* pipe) {
|
||||
iButtonProtocols* protocols = ibutton_protocols_alloc();
|
||||
iButtonWorker* worker = ibutton_worker_alloc(protocols);
|
||||
iButtonKey* key = ibutton_key_alloc(ibutton_protocols_get_max_data_size(protocols));
|
||||
@@ -113,7 +101,7 @@ static void ibutton_cli_read(Cli* cli) {
|
||||
break;
|
||||
}
|
||||
|
||||
if(cli_cmd_interrupt_received(cli)) break;
|
||||
if(cli_is_pipe_broken_or_is_etx_next_char(pipe)) break;
|
||||
}
|
||||
|
||||
ibutton_worker_stop(worker);
|
||||
@@ -138,7 +126,7 @@ static void ibutton_cli_worker_write_cb(void* context, iButtonWorkerWriteResult
|
||||
furi_event_flag_set(write_context->event, EVENT_FLAG_IBUTTON_COMPLETE);
|
||||
}
|
||||
|
||||
void ibutton_cli_write(Cli* cli, FuriString* args) {
|
||||
void ibutton_cli_write(PipeSide* pipe, FuriString* args) {
|
||||
iButtonProtocols* protocols = ibutton_protocols_alloc();
|
||||
iButtonWorker* worker = ibutton_worker_alloc(protocols);
|
||||
iButtonKey* key = ibutton_key_alloc(ibutton_protocols_get_max_data_size(protocols));
|
||||
@@ -181,7 +169,7 @@ void ibutton_cli_write(Cli* cli, FuriString* args) {
|
||||
}
|
||||
}
|
||||
|
||||
if(cli_cmd_interrupt_received(cli)) break;
|
||||
if(cli_is_pipe_broken_or_is_etx_next_char(pipe)) break;
|
||||
}
|
||||
} while(false);
|
||||
|
||||
@@ -195,7 +183,7 @@ void ibutton_cli_write(Cli* cli, FuriString* args) {
|
||||
furi_event_flag_free(write_context.event);
|
||||
}
|
||||
|
||||
void ibutton_cli_emulate(Cli* cli, FuriString* args) {
|
||||
void ibutton_cli_emulate(PipeSide* pipe, FuriString* args) {
|
||||
iButtonProtocols* protocols = ibutton_protocols_alloc();
|
||||
iButtonWorker* worker = ibutton_worker_alloc(protocols);
|
||||
iButtonKey* key = ibutton_key_alloc(ibutton_protocols_get_max_data_size(protocols));
|
||||
@@ -214,7 +202,7 @@ void ibutton_cli_emulate(Cli* cli, FuriString* args) {
|
||||
|
||||
ibutton_worker_emulate_start(worker, key);
|
||||
|
||||
while(!cli_cmd_interrupt_received(cli)) {
|
||||
while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) {
|
||||
furi_delay_ms(100);
|
||||
};
|
||||
|
||||
@@ -228,8 +216,7 @@ void ibutton_cli_emulate(Cli* cli, FuriString* args) {
|
||||
ibutton_protocols_free(protocols);
|
||||
}
|
||||
|
||||
void ibutton_cli(Cli* cli, FuriString* args, void* context) {
|
||||
UNUSED(cli);
|
||||
static void execute(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(context);
|
||||
FuriString* cmd;
|
||||
cmd = furi_string_alloc();
|
||||
@@ -241,14 +228,16 @@ void ibutton_cli(Cli* cli, FuriString* args, void* context) {
|
||||
}
|
||||
|
||||
if(furi_string_cmp_str(cmd, "read") == 0) {
|
||||
ibutton_cli_read(cli);
|
||||
ibutton_cli_read(pipe);
|
||||
} else if(furi_string_cmp_str(cmd, "write") == 0) {
|
||||
ibutton_cli_write(cli, args);
|
||||
ibutton_cli_write(pipe, args);
|
||||
} else if(furi_string_cmp_str(cmd, "emulate") == 0) {
|
||||
ibutton_cli_emulate(cli, args);
|
||||
ibutton_cli_emulate(pipe, args);
|
||||
} else {
|
||||
ibutton_cli_print_usage();
|
||||
}
|
||||
|
||||
furi_string_free(cmd);
|
||||
}
|
||||
|
||||
CLI_COMMAND_INTERFACE(ikey, execute, CliCommandFlagDefault, 1024, CLI_APPID);
|
||||
|
||||
@@ -15,14 +15,14 @@ App(
|
||||
)
|
||||
|
||||
App(
|
||||
appid="infrared_start",
|
||||
apptype=FlipperAppType.STARTUP,
|
||||
appid="cli_ir",
|
||||
targets=["f7"],
|
||||
entry_point="infrared_on_system_start",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="cli_ir_ep",
|
||||
requires=["cli"],
|
||||
sources=[
|
||||
"infrared_cli.c",
|
||||
"infrared_brute_force.c",
|
||||
"infrared_signal.c",
|
||||
],
|
||||
order=20,
|
||||
)
|
||||
|
||||
@@ -104,9 +104,9 @@ InfraredErrorCode infrared_brute_force_calculate_messages(InfraredBruteForce* br
|
||||
break;
|
||||
}
|
||||
|
||||
size_t signal_start = flipper_format_tell(ff);
|
||||
bool signal_valid = false;
|
||||
while(infrared_signal_read_name(ff, signal_name) == InfraredErrorCodeNone) {
|
||||
size_t signal_start = flipper_format_tell(ff);
|
||||
error = infrared_signal_read_body(signal, ff);
|
||||
signal_valid = (!INFRARED_ERROR_PRESENT(error)) && infrared_signal_is_valid(signal);
|
||||
if(!signal_valid) break;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
#include <cli/cli.h>
|
||||
#include <cli/cli_i.h>
|
||||
#include <cli/cli_main_commands.h>
|
||||
#include <infrared.h>
|
||||
#include <infrared_worker.h>
|
||||
#include <furi_hal_infrared.h>
|
||||
#include <flipper_format.h>
|
||||
#include <toolbox/args.h>
|
||||
#include <toolbox/strint.h>
|
||||
#include <toolbox/pipe.h>
|
||||
#include <m-dict.h>
|
||||
|
||||
#include "infrared_signal.h"
|
||||
@@ -19,14 +19,14 @@
|
||||
|
||||
DICT_DEF2(dict_signals, FuriString*, FURI_STRING_OPLIST, int, M_DEFAULT_OPLIST)
|
||||
|
||||
static void infrared_cli_start_ir_rx(Cli* cli, FuriString* args);
|
||||
static void infrared_cli_start_ir_tx(Cli* cli, FuriString* args);
|
||||
static void infrared_cli_process_decode(Cli* cli, FuriString* args);
|
||||
static void infrared_cli_process_universal(Cli* cli, FuriString* args);
|
||||
static void infrared_cli_start_ir_rx(PipeSide* pipe, FuriString* args);
|
||||
static void infrared_cli_start_ir_tx(PipeSide* pipe, FuriString* args);
|
||||
static void infrared_cli_process_decode(PipeSide* pipe, FuriString* args);
|
||||
static void infrared_cli_process_universal(PipeSide* pipe, FuriString* args);
|
||||
|
||||
static const struct {
|
||||
const char* cmd;
|
||||
void (*process_function)(Cli* cli, FuriString* args);
|
||||
void (*process_function)(PipeSide* pipe, FuriString* args);
|
||||
} infrared_cli_commands[] = {
|
||||
{.cmd = "rx", .process_function = infrared_cli_start_ir_rx},
|
||||
{.cmd = "tx", .process_function = infrared_cli_start_ir_tx},
|
||||
@@ -38,7 +38,7 @@ static void signal_received_callback(void* context, InfraredWorkerSignal* receiv
|
||||
furi_assert(received_signal);
|
||||
char buf[100];
|
||||
size_t buf_cnt;
|
||||
Cli* cli = (Cli*)context;
|
||||
PipeSide* pipe = (PipeSide*)context;
|
||||
|
||||
if(infrared_worker_signal_is_decoded(received_signal)) {
|
||||
const InfraredMessage* message = infrared_worker_get_decoded_signal(received_signal);
|
||||
@@ -52,20 +52,20 @@ static void signal_received_callback(void* context, InfraredWorkerSignal* receiv
|
||||
ROUND_UP_TO(infrared_get_protocol_command_length(message->protocol), 4),
|
||||
message->command,
|
||||
message->repeat ? " R" : "");
|
||||
cli_write(cli, (uint8_t*)buf, buf_cnt);
|
||||
pipe_send(pipe, buf, buf_cnt);
|
||||
} else {
|
||||
const uint32_t* timings;
|
||||
size_t timings_cnt;
|
||||
infrared_worker_get_raw_signal(received_signal, &timings, &timings_cnt);
|
||||
|
||||
buf_cnt = snprintf(buf, sizeof(buf), "RAW, %zu samples:\r\n", timings_cnt);
|
||||
cli_write(cli, (uint8_t*)buf, buf_cnt);
|
||||
pipe_send(pipe, buf, buf_cnt);
|
||||
for(size_t i = 0; i < timings_cnt; ++i) {
|
||||
buf_cnt = snprintf(buf, sizeof(buf), "%lu ", timings[i]);
|
||||
cli_write(cli, (uint8_t*)buf, buf_cnt);
|
||||
pipe_send(pipe, buf, buf_cnt);
|
||||
}
|
||||
buf_cnt = snprintf(buf, sizeof(buf), "\r\n");
|
||||
cli_write(cli, (uint8_t*)buf, buf_cnt);
|
||||
pipe_send(pipe, buf, buf_cnt);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,9 +124,7 @@ static void infrared_cli_print_usage(void) {
|
||||
infrared_cli_print_universal_remotes();
|
||||
}
|
||||
|
||||
static void infrared_cli_start_ir_rx(Cli* cli, FuriString* args) {
|
||||
UNUSED(cli);
|
||||
|
||||
static void infrared_cli_start_ir_rx(PipeSide* pipe, FuriString* args) {
|
||||
bool enable_decoding = true;
|
||||
|
||||
if(!furi_string_empty(args)) {
|
||||
@@ -142,10 +140,10 @@ static void infrared_cli_start_ir_rx(Cli* cli, FuriString* args) {
|
||||
InfraredWorker* worker = infrared_worker_alloc();
|
||||
infrared_worker_rx_enable_signal_decoding(worker, enable_decoding);
|
||||
infrared_worker_rx_start(worker);
|
||||
infrared_worker_rx_set_received_signal_callback(worker, signal_received_callback, cli);
|
||||
infrared_worker_rx_set_received_signal_callback(worker, signal_received_callback, pipe);
|
||||
|
||||
printf("Receiving %s INFRARED...\r\nPress Ctrl+C to abort\r\n", enable_decoding ? "" : "RAW");
|
||||
while(!cli_cmd_interrupt_received(cli)) {
|
||||
while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) {
|
||||
furi_delay_ms(50);
|
||||
}
|
||||
|
||||
@@ -214,8 +212,8 @@ static bool infrared_cli_parse_raw(const char* str, InfraredSignal* signal) {
|
||||
return infrared_signal_is_valid(signal);
|
||||
}
|
||||
|
||||
static void infrared_cli_start_ir_tx(Cli* cli, FuriString* args) {
|
||||
UNUSED(cli);
|
||||
static void infrared_cli_start_ir_tx(PipeSide* pipe, FuriString* args) {
|
||||
UNUSED(pipe);
|
||||
const char* str = furi_string_get_cstr(args);
|
||||
InfraredSignal* signal = infrared_signal_alloc();
|
||||
|
||||
@@ -335,8 +333,8 @@ static bool infrared_cli_decode_file(FlipperFormat* input_file, FlipperFormat* o
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void infrared_cli_process_decode(Cli* cli, FuriString* args) {
|
||||
UNUSED(cli);
|
||||
static void infrared_cli_process_decode(PipeSide* pipe, FuriString* args) {
|
||||
UNUSED(pipe);
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
FlipperFormat* input_file = flipper_format_buffered_file_alloc(storage);
|
||||
FlipperFormat* output_file = NULL;
|
||||
@@ -455,8 +453,10 @@ static void infrared_cli_list_remote_signals(FuriString* remote_name) {
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
}
|
||||
|
||||
static void
|
||||
infrared_cli_brute_force_signals(Cli* cli, FuriString* remote_name, FuriString* signal_name) {
|
||||
static void infrared_cli_brute_force_signals(
|
||||
PipeSide* pipe,
|
||||
FuriString* remote_name,
|
||||
FuriString* signal_name) {
|
||||
InfraredBruteForce* brute_force = infrared_brute_force_alloc();
|
||||
FuriString* remote_path = furi_string_alloc_printf(
|
||||
"%s/%s.ir", INFRARED_ASSETS_FOLDER, furi_string_get_cstr(remote_name));
|
||||
@@ -490,7 +490,7 @@ static void
|
||||
while(running) {
|
||||
running = infrared_brute_force_send(brute_force, current_signal);
|
||||
|
||||
if(cli_cmd_interrupt_received(cli)) break;
|
||||
if(cli_is_pipe_broken_or_is_etx_next_char(pipe)) break;
|
||||
|
||||
printf("\r%d%% complete.", (int)((float)current_signal++ / (float)signal_count * 100));
|
||||
fflush(stdout);
|
||||
@@ -504,7 +504,7 @@ static void
|
||||
infrared_brute_force_free(brute_force);
|
||||
}
|
||||
|
||||
static void infrared_cli_process_universal(Cli* cli, FuriString* args) {
|
||||
static void infrared_cli_process_universal(PipeSide* pipe, FuriString* args) {
|
||||
FuriString* arg1 = furi_string_alloc();
|
||||
FuriString* arg2 = furi_string_alloc();
|
||||
|
||||
@@ -519,14 +519,14 @@ static void infrared_cli_process_universal(Cli* cli, FuriString* args) {
|
||||
} else if(furi_string_equal_str(arg1, "list")) {
|
||||
infrared_cli_list_remote_signals(arg2);
|
||||
} else {
|
||||
infrared_cli_brute_force_signals(cli, arg1, arg2);
|
||||
infrared_cli_brute_force_signals(pipe, arg1, arg2);
|
||||
}
|
||||
|
||||
furi_string_free(arg1);
|
||||
furi_string_free(arg2);
|
||||
}
|
||||
|
||||
static void infrared_cli_start_ir(Cli* cli, FuriString* args, void* context) {
|
||||
static void execute(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(context);
|
||||
if(furi_hal_infrared_is_busy()) {
|
||||
printf("INFRARED is busy. Exiting.");
|
||||
@@ -546,19 +546,12 @@ static void infrared_cli_start_ir(Cli* cli, FuriString* args, void* context) {
|
||||
}
|
||||
|
||||
if(i < COUNT_OF(infrared_cli_commands)) {
|
||||
infrared_cli_commands[i].process_function(cli, args);
|
||||
infrared_cli_commands[i].process_function(pipe, args);
|
||||
} else {
|
||||
infrared_cli_print_usage();
|
||||
}
|
||||
|
||||
furi_string_free(command);
|
||||
}
|
||||
void infrared_on_system_start(void) {
|
||||
#ifdef SRV_CLI
|
||||
Cli* cli = (Cli*)furi_record_open(RECORD_CLI);
|
||||
cli_add_command(cli, "ir", CliCommandFlagDefault, infrared_cli_start_ir, NULL);
|
||||
furi_record_close(RECORD_CLI);
|
||||
#else
|
||||
UNUSED(infrared_cli_start_ir);
|
||||
#endif
|
||||
}
|
||||
|
||||
CLI_COMMAND_INTERFACE(ir, execute, CliCommandFlagDefault, 2048, CLI_APPID);
|
||||
|
||||
@@ -2474,7 +2474,7 @@ protocol: RC5
|
||||
address: 01 00 00 00
|
||||
command: 14 00 00 00
|
||||
#
|
||||
# Model: Elitelux L32HD1000
|
||||
# Model: Elitelux L32HD1000 / Vivax TV-32LE114T2S2SM / Sansui
|
||||
#
|
||||
name: Power
|
||||
type: parsed
|
||||
|
||||
@@ -13,10 +13,10 @@ App(
|
||||
)
|
||||
|
||||
App(
|
||||
appid="lfrfid_start",
|
||||
appid="cli_rfid",
|
||||
targets=["f7"],
|
||||
apptype=FlipperAppType.STARTUP,
|
||||
entry_point="lfrfid_on_system_start",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="cli_rfid_ep",
|
||||
requires=["cli"],
|
||||
sources=["lfrfid_cli.c"],
|
||||
order=50,
|
||||
)
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <stdarg.h>
|
||||
#include <cli/cli.h>
|
||||
#include <cli/cli_main_commands.h>
|
||||
#include <lib/toolbox/args.h>
|
||||
#include <lib/lfrfid/lfrfid_worker.h>
|
||||
#include <storage/storage.h>
|
||||
#include <toolbox/stream/file_stream.h>
|
||||
#include <toolbox/pipe.h>
|
||||
|
||||
#include <toolbox/varint.h>
|
||||
|
||||
@@ -14,15 +15,6 @@
|
||||
#include <lfrfid/lfrfid_raw_file.h>
|
||||
#include <toolbox/pulse_protocols/pulse_glue.h>
|
||||
|
||||
static void lfrfid_cli(Cli* cli, FuriString* args, void* context);
|
||||
|
||||
// app cli function
|
||||
void lfrfid_on_system_start(void) {
|
||||
Cli* cli = furi_record_open(RECORD_CLI);
|
||||
cli_add_command(cli, "rfid", CliCommandFlagDefault, lfrfid_cli, NULL);
|
||||
furi_record_close(RECORD_CLI);
|
||||
}
|
||||
|
||||
static void lfrfid_cli_print_usage(void) {
|
||||
printf("Usage:\r\n");
|
||||
printf("rfid read <optional: normal | indala> - read in ASK/PSK mode\r\n");
|
||||
@@ -49,7 +41,7 @@ static void lfrfid_cli_read_callback(LFRFIDWorkerReadResult result, ProtocolId p
|
||||
furi_event_flag_set(context->event, 1 << result);
|
||||
}
|
||||
|
||||
static void lfrfid_cli_read(Cli* cli, FuriString* args) {
|
||||
static void lfrfid_cli_read(PipeSide* pipe, FuriString* args) {
|
||||
FuriString* type_string;
|
||||
type_string = furi_string_alloc();
|
||||
LFRFIDWorkerReadType type = LFRFIDWorkerReadTypeAuto;
|
||||
@@ -96,7 +88,7 @@ static void lfrfid_cli_read(Cli* cli, FuriString* args) {
|
||||
}
|
||||
}
|
||||
|
||||
if(cli_cmd_interrupt_received(cli)) break;
|
||||
if(cli_is_pipe_broken_or_is_etx_next_char(pipe)) break;
|
||||
}
|
||||
|
||||
lfrfid_worker_stop(worker);
|
||||
@@ -192,7 +184,7 @@ static void lfrfid_cli_write_callback(LFRFIDWorkerWriteResult result, void* ctx)
|
||||
furi_event_flag_set(events, 1 << result);
|
||||
}
|
||||
|
||||
static void lfrfid_cli_write(Cli* cli, FuriString* args) {
|
||||
static void lfrfid_cli_write(PipeSide* pipe, FuriString* args) {
|
||||
ProtocolDict* dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax);
|
||||
ProtocolId protocol;
|
||||
|
||||
@@ -212,7 +204,7 @@ static void lfrfid_cli_write(Cli* cli, FuriString* args) {
|
||||
(1 << LFRFIDWorkerWriteProtocolCannotBeWritten) |
|
||||
(1 << LFRFIDWorkerWriteFobCannotBeWritten);
|
||||
|
||||
while(!cli_cmd_interrupt_received(cli)) {
|
||||
while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) {
|
||||
uint32_t flags = furi_event_flag_wait(event, available_flags, FuriFlagWaitAny, 100);
|
||||
if(flags != (unsigned)FuriFlagErrorTimeout) {
|
||||
if(FURI_BIT(flags, LFRFIDWorkerWriteOK)) {
|
||||
@@ -239,7 +231,7 @@ static void lfrfid_cli_write(Cli* cli, FuriString* args) {
|
||||
furi_event_flag_free(event);
|
||||
}
|
||||
|
||||
static void lfrfid_cli_emulate(Cli* cli, FuriString* args) {
|
||||
static void lfrfid_cli_emulate(PipeSide* pipe, FuriString* args) {
|
||||
ProtocolDict* dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax);
|
||||
ProtocolId protocol;
|
||||
|
||||
@@ -254,7 +246,7 @@ static void lfrfid_cli_emulate(Cli* cli, FuriString* args) {
|
||||
lfrfid_worker_emulate_start(worker, protocol);
|
||||
|
||||
printf("Emulating RFID...\r\nPress Ctrl+C to abort\r\n");
|
||||
while(!cli_cmd_interrupt_received(cli)) {
|
||||
while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) {
|
||||
furi_delay_ms(100);
|
||||
}
|
||||
printf("Emulation stopped\r\n");
|
||||
@@ -265,8 +257,8 @@ static void lfrfid_cli_emulate(Cli* cli, FuriString* args) {
|
||||
protocol_dict_free(dict);
|
||||
}
|
||||
|
||||
static void lfrfid_cli_raw_analyze(Cli* cli, FuriString* args) {
|
||||
UNUSED(cli);
|
||||
static void lfrfid_cli_raw_analyze(PipeSide* pipe, FuriString* args) {
|
||||
UNUSED(pipe);
|
||||
FuriString *filepath, *info_string;
|
||||
filepath = furi_string_alloc();
|
||||
info_string = furi_string_alloc();
|
||||
@@ -392,9 +384,7 @@ static void lfrfid_cli_raw_read_callback(LFRFIDWorkerReadRawResult result, void*
|
||||
furi_event_flag_set(event, 1 << result);
|
||||
}
|
||||
|
||||
static void lfrfid_cli_raw_read(Cli* cli, FuriString* args) {
|
||||
UNUSED(cli);
|
||||
|
||||
static void lfrfid_cli_raw_read(PipeSide* pipe, FuriString* args) {
|
||||
FuriString *filepath, *type_string;
|
||||
filepath = furi_string_alloc();
|
||||
type_string = furi_string_alloc();
|
||||
@@ -452,7 +442,7 @@ static void lfrfid_cli_raw_read(Cli* cli, FuriString* args) {
|
||||
}
|
||||
}
|
||||
|
||||
if(cli_cmd_interrupt_received(cli)) break;
|
||||
if(cli_is_pipe_broken_or_is_etx_next_char(pipe)) break;
|
||||
}
|
||||
|
||||
if(overrun) {
|
||||
@@ -479,9 +469,7 @@ static void lfrfid_cli_raw_emulate_callback(LFRFIDWorkerEmulateRawResult result,
|
||||
furi_event_flag_set(event, 1 << result);
|
||||
}
|
||||
|
||||
static void lfrfid_cli_raw_emulate(Cli* cli, FuriString* args) {
|
||||
UNUSED(cli);
|
||||
|
||||
static void lfrfid_cli_raw_emulate(PipeSide* pipe, FuriString* args) {
|
||||
FuriString* filepath;
|
||||
filepath = furi_string_alloc();
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
@@ -527,7 +515,7 @@ static void lfrfid_cli_raw_emulate(Cli* cli, FuriString* args) {
|
||||
}
|
||||
}
|
||||
|
||||
if(cli_cmd_interrupt_received(cli)) break;
|
||||
if(cli_is_pipe_broken_or_is_etx_next_char(pipe)) break;
|
||||
}
|
||||
|
||||
if(overrun) {
|
||||
@@ -548,7 +536,7 @@ static void lfrfid_cli_raw_emulate(Cli* cli, FuriString* args) {
|
||||
furi_string_free(filepath);
|
||||
}
|
||||
|
||||
static void lfrfid_cli(Cli* cli, FuriString* args, void* context) {
|
||||
static void execute(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(context);
|
||||
FuriString* cmd;
|
||||
cmd = furi_string_alloc();
|
||||
@@ -560,20 +548,22 @@ static void lfrfid_cli(Cli* cli, FuriString* args, void* context) {
|
||||
}
|
||||
|
||||
if(furi_string_cmp_str(cmd, "read") == 0) {
|
||||
lfrfid_cli_read(cli, args);
|
||||
lfrfid_cli_read(pipe, args);
|
||||
} else if(furi_string_cmp_str(cmd, "write") == 0) {
|
||||
lfrfid_cli_write(cli, args);
|
||||
lfrfid_cli_write(pipe, args);
|
||||
} else if(furi_string_cmp_str(cmd, "emulate") == 0) {
|
||||
lfrfid_cli_emulate(cli, args);
|
||||
lfrfid_cli_emulate(pipe, args);
|
||||
} else if(furi_string_cmp_str(cmd, "raw_read") == 0) {
|
||||
lfrfid_cli_raw_read(cli, args);
|
||||
lfrfid_cli_raw_read(pipe, args);
|
||||
} else if(furi_string_cmp_str(cmd, "raw_emulate") == 0) {
|
||||
lfrfid_cli_raw_emulate(cli, args);
|
||||
lfrfid_cli_raw_emulate(pipe, args);
|
||||
} else if(furi_string_cmp_str(cmd, "raw_analyze") == 0) {
|
||||
lfrfid_cli_raw_analyze(cli, args);
|
||||
lfrfid_cli_raw_analyze(pipe, args);
|
||||
} else {
|
||||
lfrfid_cli_print_usage();
|
||||
}
|
||||
|
||||
furi_string_free(cmd);
|
||||
}
|
||||
|
||||
CLI_COMMAND_INTERFACE(rfid, execute, CliCommandFlagDefault, 2048, CLI_APPID);
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
#include <assets_icons.h>
|
||||
#include <gui/view_dispatcher.h>
|
||||
#include <gui/scene_manager.h>
|
||||
#include <cli/cli.h>
|
||||
#include <notification/notification_messages.h>
|
||||
|
||||
#include <gui/modules/submenu.h>
|
||||
|
||||
@@ -268,10 +268,10 @@ App(
|
||||
)
|
||||
|
||||
App(
|
||||
appid="nfc_start",
|
||||
appid="cli_nfc",
|
||||
targets=["f7"],
|
||||
apptype=FlipperAppType.STARTUP,
|
||||
entry_point="nfc_on_system_start",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="cli_nfc_ep",
|
||||
requires=["cli"],
|
||||
sources=["nfc_cli.c"],
|
||||
order=30,
|
||||
)
|
||||
|
||||
@@ -37,11 +37,13 @@ void nfc_render_iso15693_3_brief(const Iso15693_3Data* data, FuriString* str) {
|
||||
}
|
||||
|
||||
void nfc_render_iso15693_3_system_info(const Iso15693_3Data* data, FuriString* str) {
|
||||
if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_MEMORY) {
|
||||
const uint16_t block_count = iso15693_3_get_block_count(data);
|
||||
const uint8_t block_size = iso15693_3_get_block_size(data);
|
||||
|
||||
if((data->system_info.flags & ISO15693_3_SYSINFO_FLAG_MEMORY) &&
|
||||
(block_count > 0 && block_size > 0)) {
|
||||
furi_string_cat(str, "\e#Memory data\n\e*--------------------\n");
|
||||
|
||||
const uint16_t block_count = iso15693_3_get_block_count(data);
|
||||
const uint8_t block_size = iso15693_3_get_block_size(data);
|
||||
const uint16_t display_block_count =
|
||||
MIN(NFC_RENDER_ISO15693_3_MAX_BYTES / block_size, block_count);
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
enum {
|
||||
SubmenuIndexDetectReader = SubmenuIndexCommonMax,
|
||||
SubmenuIndexDictAttack,
|
||||
SubmenuIndexCrackNonces,
|
||||
SubmenuIndexUpdate,
|
||||
};
|
||||
|
||||
@@ -130,6 +131,13 @@ static void nfc_scene_read_menu_on_enter_mf_classic(NfcApp* instance) {
|
||||
SubmenuIndexDictAttack,
|
||||
nfc_protocol_support_common_submenu_callback,
|
||||
instance);
|
||||
|
||||
submenu_add_item(
|
||||
submenu,
|
||||
"Crack nonces in MFKey32",
|
||||
SubmenuIndexCrackNonces,
|
||||
nfc_protocol_support_common_submenu_callback,
|
||||
instance);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,6 +199,11 @@ static bool nfc_scene_read_menu_on_event_mf_classic(NfcApp* instance, SceneManag
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == SubmenuIndexDetectReader) {
|
||||
scene_manager_set_scene_state(
|
||||
instance->scene_manager,
|
||||
NfcSceneSaveConfirm,
|
||||
NfcSceneSaveConfirmStateDetectReader);
|
||||
|
||||
scene_manager_next_scene(instance->scene_manager, NfcSceneSaveConfirm);
|
||||
dolphin_deed(DolphinDeedNfcDetectReader);
|
||||
consumed = true;
|
||||
@@ -200,6 +213,11 @@ static bool nfc_scene_read_menu_on_event_mf_classic(NfcApp* instance, SceneManag
|
||||
scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicDictAttack);
|
||||
}
|
||||
consumed = true;
|
||||
} else if(event.event == SubmenuIndexCrackNonces) {
|
||||
scene_manager_set_scene_state(
|
||||
instance->scene_manager, NfcSceneSaveConfirm, NfcSceneSaveConfirmStateCrackNonces);
|
||||
scene_manager_next_scene(instance->scene_manager, NfcSceneSaveConfirm);
|
||||
consumed = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -180,6 +180,9 @@ void nfc_render_mf_desfire_file_settings_data(
|
||||
case MfDesfireFileTypeCyclicRecord:
|
||||
type = "cyclic";
|
||||
break;
|
||||
case MfDesfireFileTypeTransactionMac:
|
||||
type = "txn-mac";
|
||||
break;
|
||||
default:
|
||||
type = "unknown";
|
||||
}
|
||||
@@ -237,6 +240,15 @@ void nfc_render_mf_desfire_file_settings_data(
|
||||
furi_string_cat_printf(str, "size %lu\n", record_size);
|
||||
furi_string_cat_printf(str, "num %lu max %lu\n", record_count, settings->record.max);
|
||||
break;
|
||||
case MfDesfireFileTypeTransactionMac:
|
||||
record_count = 0;
|
||||
furi_string_cat_printf(
|
||||
str,
|
||||
"key opt %02X ver %02X\n",
|
||||
settings->transaction_mac.key_option,
|
||||
settings->transaction_mac.key_version);
|
||||
furi_string_cat_printf(str, "cnt limit %lu\n", settings->transaction_mac.counter_limit);
|
||||
break;
|
||||
}
|
||||
|
||||
bool is_auth_required = true;
|
||||
|
||||
@@ -470,6 +470,26 @@ static void nfc_show_initial_scene_for_device(NfcApp* nfc) {
|
||||
scene_manager_next_scene(nfc->scene_manager, scene);
|
||||
}
|
||||
|
||||
void nfc_app_run_external(NfcApp* nfc, const char* app_path) {
|
||||
furi_assert(nfc);
|
||||
furi_assert(app_path);
|
||||
|
||||
Loader* loader = furi_record_open(RECORD_LOADER);
|
||||
|
||||
loader_enqueue_launch(loader, app_path, NULL, LoaderDeferredLaunchFlagGui);
|
||||
|
||||
FuriString* self_path = furi_string_alloc();
|
||||
furi_check(loader_get_application_launch_path(loader, self_path));
|
||||
|
||||
loader_enqueue_launch(
|
||||
loader, furi_string_get_cstr(self_path), NULL, LoaderDeferredLaunchFlagGui);
|
||||
furi_string_free(self_path);
|
||||
|
||||
furi_record_close(RECORD_LOADER);
|
||||
|
||||
view_dispatcher_stop(nfc->view_dispatcher);
|
||||
}
|
||||
|
||||
int32_t nfc_app(void* p) {
|
||||
if(!nfc_is_hal_ready()) return 0;
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#include <assets_icons.h>
|
||||
#include <gui/view_dispatcher.h>
|
||||
#include <gui/scene_manager.h>
|
||||
#include <cli/cli.h>
|
||||
#include <notification/notification_messages.h>
|
||||
|
||||
#include <gui/modules/submenu.h>
|
||||
@@ -36,6 +35,7 @@
|
||||
#include "helpers/felica_auth.h"
|
||||
#include "helpers/slix_unlock.h"
|
||||
|
||||
#include <loader/loader.h>
|
||||
#include <dialogs/dialogs.h>
|
||||
#include <storage/storage.h>
|
||||
#include <toolbox/path.h>
|
||||
@@ -81,6 +81,8 @@
|
||||
#define NFC_APP_MF_CLASSIC_DICT_SYSTEM_NESTED_PATH \
|
||||
(NFC_APP_FOLDER "/assets/mf_classic_dict_nested.nfc")
|
||||
|
||||
#define NFC_MFKEY32_APP_PATH (EXT_PATH("apps/NFC/mfkey.fap"))
|
||||
|
||||
typedef enum {
|
||||
NfcRpcStateIdle,
|
||||
NfcRpcStateEmulating,
|
||||
@@ -168,6 +170,11 @@ typedef enum {
|
||||
NfcViewDetectReader,
|
||||
} NfcView;
|
||||
|
||||
typedef enum {
|
||||
NfcSceneSaveConfirmStateDetectReader,
|
||||
NfcSceneSaveConfirmStateCrackNonces,
|
||||
} NfcSceneSaveConfirmState;
|
||||
|
||||
int32_t nfc_task(void* p);
|
||||
|
||||
void nfc_text_store_set(NfcApp* nfc, const char* text, ...);
|
||||
@@ -203,3 +210,5 @@ bool nfc_save_file(NfcApp* instance, FuriString* path);
|
||||
void nfc_make_app_folder(NfcApp* instance);
|
||||
|
||||
void nfc_append_filename_string_when_present(NfcApp* instance, FuriString* string);
|
||||
|
||||
void nfc_app_run_external(NfcApp* nfc, const char* app_path);
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <cli/cli.h>
|
||||
#include <cli/cli_main_commands.h>
|
||||
#include <lib/toolbox/args.h>
|
||||
#include <lib/toolbox/hex.h>
|
||||
#include <toolbox/pipe.h>
|
||||
|
||||
#include <furi_hal_nfc.h>
|
||||
|
||||
@@ -17,7 +18,7 @@ static void nfc_cli_print_usage(void) {
|
||||
}
|
||||
}
|
||||
|
||||
static void nfc_cli_field(Cli* cli, FuriString* args) {
|
||||
static void nfc_cli_field(PipeSide* pipe, FuriString* args) {
|
||||
UNUSED(args);
|
||||
// Check if nfc worker is not busy
|
||||
if(furi_hal_nfc_is_hal_ready() != FuriHalNfcErrorNone) {
|
||||
@@ -32,7 +33,7 @@ static void nfc_cli_field(Cli* cli, FuriString* args) {
|
||||
printf("Field is on. Don't leave device in this mode for too long.\r\n");
|
||||
printf("Press Ctrl+C to abort\r\n");
|
||||
|
||||
while(!cli_cmd_interrupt_received(cli)) {
|
||||
while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) {
|
||||
furi_delay_ms(50);
|
||||
}
|
||||
|
||||
@@ -40,7 +41,7 @@ static void nfc_cli_field(Cli* cli, FuriString* args) {
|
||||
furi_hal_nfc_release();
|
||||
}
|
||||
|
||||
static void nfc_cli(Cli* cli, FuriString* args, void* context) {
|
||||
static void execute(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(context);
|
||||
FuriString* cmd;
|
||||
cmd = furi_string_alloc();
|
||||
@@ -52,7 +53,7 @@ static void nfc_cli(Cli* cli, FuriString* args, void* context) {
|
||||
}
|
||||
if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) {
|
||||
if(furi_string_cmp_str(cmd, "field") == 0) {
|
||||
nfc_cli_field(cli, args);
|
||||
nfc_cli_field(pipe, args);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -63,12 +64,4 @@ static void nfc_cli(Cli* cli, FuriString* args, void* context) {
|
||||
furi_string_free(cmd);
|
||||
}
|
||||
|
||||
void nfc_on_system_start(void) {
|
||||
#ifdef SRV_CLI
|
||||
Cli* cli = furi_record_open(RECORD_CLI);
|
||||
cli_add_command(cli, "nfc", CliCommandFlagDefault, nfc_cli, NULL);
|
||||
furi_record_close(RECORD_CLI);
|
||||
#else
|
||||
UNUSED(nfc_cli);
|
||||
#endif
|
||||
}
|
||||
CLI_COMMAND_INTERFACE(nfc, execute, CliCommandFlagDefault, 1024, CLI_APPID);
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include <nfc/protocols/type_4_tag/type_4_tag.h>
|
||||
|
||||
#include <bit_lib.h>
|
||||
#include <toolbox/pretty_format.h>
|
||||
|
||||
#define TAG "NDEF"
|
||||
|
||||
@@ -188,30 +189,34 @@ static bool ndef_get(Ndef* ndef, size_t pos, size_t len, void* buf) {
|
||||
// So the first 93 (31*3) data blocks correspond to 128 real blocks.
|
||||
// Last 128 blocks are 8 sectors: 15 data blocks, 1 sector trailer.
|
||||
// So the last 120 (8*15) data blocks correspond to 128 real blocks.
|
||||
div_t small_sector_data_blocks = div(pos, MF_CLASSIC_BLOCK_SIZE);
|
||||
const size_t real_block_data_offset = pos % MF_CLASSIC_BLOCK_SIZE;
|
||||
size_t small_sector_data_blocks = pos / MF_CLASSIC_BLOCK_SIZE;
|
||||
size_t large_sector_data_blocks = 0;
|
||||
if(small_sector_data_blocks.quot > 93) {
|
||||
large_sector_data_blocks = small_sector_data_blocks.quot - 93;
|
||||
small_sector_data_blocks.quot = 93;
|
||||
if(small_sector_data_blocks > 93) {
|
||||
large_sector_data_blocks = small_sector_data_blocks - 93;
|
||||
small_sector_data_blocks = 93;
|
||||
}
|
||||
|
||||
div_t small_sectors = div(small_sector_data_blocks.quot, 3);
|
||||
size_t real_block = small_sectors.quot * 4 + small_sectors.rem;
|
||||
if(small_sectors.quot >= 16) {
|
||||
const size_t small_sector_block_offset = small_sector_data_blocks % 3;
|
||||
const size_t small_sectors = small_sector_data_blocks / 3;
|
||||
size_t real_block = small_sectors * 4 + small_sector_block_offset;
|
||||
if(small_sectors >= 16) {
|
||||
real_block += 4; // Skip MAD2
|
||||
}
|
||||
if(large_sector_data_blocks) {
|
||||
div_t large_sectors = div(large_sector_data_blocks, 15);
|
||||
real_block += large_sectors.quot * 16 + large_sectors.rem;
|
||||
const size_t large_sector_block_offset = large_sector_data_blocks % 15;
|
||||
const size_t large_sectors = large_sector_data_blocks / 15;
|
||||
real_block += large_sectors * 16 + large_sector_block_offset;
|
||||
}
|
||||
|
||||
const uint8_t* cur = &ndef->mfc.blocks[real_block].data[small_sector_data_blocks.rem];
|
||||
const uint8_t* cur = &ndef->mfc.blocks[real_block].data[real_block_data_offset];
|
||||
while(len) {
|
||||
size_t sector_trailer = mf_classic_get_sector_trailer_num_by_block(real_block);
|
||||
const uint8_t* end = &ndef->mfc.blocks[sector_trailer].data[0];
|
||||
|
||||
size_t chunk_len = MIN((size_t)(end - cur), len);
|
||||
const size_t chunk_len = MIN((size_t)(end - cur), len);
|
||||
memcpy(buf, cur, chunk_len);
|
||||
buf += chunk_len;
|
||||
len -= chunk_len;
|
||||
|
||||
if(len) {
|
||||
@@ -258,7 +263,9 @@ static inline bool is_printable(char c) {
|
||||
|
||||
static bool is_text(const uint8_t* buf, size_t len) {
|
||||
for(size_t i = 0; i < len; i++) {
|
||||
if(!is_printable(buf[i])) return false;
|
||||
if(!is_printable(buf[i]) && !(buf[i] == '\0' && i == len - 1)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -274,7 +281,7 @@ static bool ndef_dump(Ndef* ndef, const char* prefix, size_t pos, size_t len, bo
|
||||
for(size_t i = 0; i < len; i++) {
|
||||
char c;
|
||||
if(!ndef_get(ndef, pos + i, 1, &c)) return false;
|
||||
if(!is_printable(c)) {
|
||||
if(!is_printable(c) && !(c == '\0' && i == len - 1)) {
|
||||
furi_string_left(ndef->output, string_prev);
|
||||
force_hex = true;
|
||||
break;
|
||||
@@ -282,14 +289,18 @@ static bool ndef_dump(Ndef* ndef, const char* prefix, size_t pos, size_t len, bo
|
||||
furi_string_push_back(ndef->output, c);
|
||||
}
|
||||
}
|
||||
if(force_hex) {
|
||||
for(size_t i = 0; i < len; i++) {
|
||||
uint8_t b;
|
||||
if(!ndef_get(ndef, pos + i, 1, &b)) return false;
|
||||
furi_string_cat_printf(ndef->output, "%02X ", b);
|
||||
if(!force_hex) {
|
||||
furi_string_cat(ndef->output, "\n");
|
||||
} else {
|
||||
uint8_t buf[4];
|
||||
for(size_t i = 0; i < len; i += sizeof(buf)) {
|
||||
uint8_t buf_len = MIN(sizeof(buf), len - i);
|
||||
if(!ndef_get(ndef, pos + i, buf_len, &buf)) return false;
|
||||
pretty_format_bytes_hex_canonical(
|
||||
ndef->output, 4, PRETTY_FORMAT_FONT_MONOSPACE, buf, buf_len);
|
||||
furi_string_cat(ndef->output, "\n");
|
||||
}
|
||||
}
|
||||
furi_string_cat(ndef->output, "\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -299,9 +310,7 @@ static void
|
||||
if(!force_hex && is_text(buf, len)) {
|
||||
furi_string_cat_printf(ndef->output, "%.*s", len, (const char*)buf);
|
||||
} else {
|
||||
for(size_t i = 0; i < len; i++) {
|
||||
furi_string_cat_printf(ndef->output, "%02X ", ((const uint8_t*)buf)[i]);
|
||||
}
|
||||
pretty_format_bytes_hex_canonical(ndef->output, 4, PRETTY_FORMAT_FONT_MONOSPACE, buf, len);
|
||||
}
|
||||
furi_string_cat(ndef->output, "\n");
|
||||
}
|
||||
@@ -596,7 +605,7 @@ bool ndef_parse_record(
|
||||
NdefTnf tnf,
|
||||
const char* type,
|
||||
uint8_t type_len) {
|
||||
FURI_LOG_D(TAG, "payload type: %.*s len: %hu", type_len, type, len);
|
||||
FURI_LOG_D(TAG, "payload type: %.*s len: %hu pos: %zu", type_len, type, len, pos);
|
||||
if(!len) {
|
||||
furi_string_cat(ndef->output, "Empty\n");
|
||||
return true;
|
||||
@@ -901,13 +910,13 @@ static bool ndef_mfc_parse(const NfcDevice* device, FuriString* parsed_data) {
|
||||
for(uint8_t mad = 0; mad < COUNT_OF(mads); mad++) {
|
||||
const size_t block = mads[mad].block;
|
||||
const size_t sector = mf_classic_get_sector_by_block(block);
|
||||
if(sector_count <= sector) break; // Skip this MAD if not present
|
||||
if(sector_count <= sector) continue; // Skip this MAD if not present
|
||||
// Check MAD key
|
||||
const MfClassicSectorTrailer* sector_trailer =
|
||||
mf_classic_get_sector_trailer_by_sector(data, sector);
|
||||
const uint64_t sector_key_a = bit_lib_bytes_to_num_be(
|
||||
sector_trailer->key_a.data, COUNT_OF(sector_trailer->key_a.data));
|
||||
if(sector_key_a != mad_key) return false;
|
||||
if(sector_key_a != mad_key) continue;
|
||||
// Find NDEF AIDs
|
||||
for(uint8_t aid_index = 0; aid_index < mads[mad].aid_count; aid_index++) {
|
||||
const uint8_t* aid = &data->block[block].data[2 + aid_index * AID_SIZE];
|
||||
@@ -931,7 +940,7 @@ static bool ndef_mfc_parse(const NfcDevice* device, FuriString* parsed_data) {
|
||||
data_size = 93 + (sector_count - 32) * 15;
|
||||
} else {
|
||||
data_size = sector_count * 3;
|
||||
if(sector_count >= 16) {
|
||||
if(sector_count > 16) {
|
||||
data_size -= 3; // Skip MAD2
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
#include "../nfc_app_i.h"
|
||||
#include "loader/loader.h"
|
||||
|
||||
typedef enum {
|
||||
NfcSceneMfClassicMfKeyCompleteStateAppMissing,
|
||||
NfcSceneMfClassicMfKeyCompleteStateAppPresent,
|
||||
} NfcSceneMfClassicMfKeyCompleteState;
|
||||
|
||||
void nfc_scene_mf_classic_mfkey_complete_callback(
|
||||
GuiButtonType result,
|
||||
@@ -15,22 +21,47 @@ void nfc_scene_mf_classic_mfkey_complete_on_enter(void* context) {
|
||||
|
||||
widget_add_string_element(
|
||||
instance->widget, 64, 0, AlignCenter, AlignTop, FontPrimary, "Completed!");
|
||||
widget_add_string_multiline_element(
|
||||
instance->widget,
|
||||
64,
|
||||
13,
|
||||
AlignCenter,
|
||||
AlignTop,
|
||||
FontSecondary,
|
||||
"Now use Mfkey32 to extract \nkeys: r.flipper.net/nfc-tools");
|
||||
widget_add_icon_element(instance->widget, 50, 39, &I_MFKey_qr_25x25);
|
||||
widget_add_button_element(
|
||||
instance->widget,
|
||||
GuiButtonTypeRight,
|
||||
"Finish",
|
||||
nfc_scene_mf_classic_mfkey_complete_callback,
|
||||
instance);
|
||||
|
||||
NfcSceneMfClassicMfKeyCompleteState scene_state =
|
||||
storage_common_exists(instance->storage, NFC_MFKEY32_APP_PATH) ?
|
||||
NfcSceneMfClassicMfKeyCompleteStateAppPresent :
|
||||
NfcSceneMfClassicMfKeyCompleteStateAppMissing;
|
||||
scene_manager_set_scene_state(
|
||||
instance->scene_manager, NfcSceneMfClassicMfkeyComplete, scene_state);
|
||||
|
||||
if(scene_state == NfcSceneMfClassicMfKeyCompleteStateAppMissing) {
|
||||
widget_add_string_multiline_element(
|
||||
instance->widget,
|
||||
64,
|
||||
13,
|
||||
AlignCenter,
|
||||
AlignTop,
|
||||
FontSecondary,
|
||||
"Now use Mfkey32 to extract \nkeys: r.flipper.net/nfc-tools");
|
||||
widget_add_icon_element(instance->widget, 50, 39, &I_MFKey_qr_25x25);
|
||||
widget_add_button_element(
|
||||
instance->widget,
|
||||
GuiButtonTypeRight,
|
||||
"Finish",
|
||||
nfc_scene_mf_classic_mfkey_complete_callback,
|
||||
instance);
|
||||
} else {
|
||||
widget_add_string_multiline_element(
|
||||
instance->widget,
|
||||
60,
|
||||
16,
|
||||
AlignLeft,
|
||||
AlignTop,
|
||||
FontSecondary,
|
||||
"Now run Mfkey32\n to extract \nkeys");
|
||||
widget_add_icon_element(instance->widget, 5, 18, &I_WarningDolphin_45x42);
|
||||
widget_add_button_element(
|
||||
instance->widget,
|
||||
GuiButtonTypeRight,
|
||||
"Run",
|
||||
nfc_scene_mf_classic_mfkey_complete_callback,
|
||||
instance);
|
||||
}
|
||||
view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget);
|
||||
}
|
||||
|
||||
@@ -40,8 +71,14 @@ bool nfc_scene_mf_classic_mfkey_complete_on_event(void* context, SceneManagerEve
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == GuiButtonTypeRight) {
|
||||
consumed = scene_manager_search_and_switch_to_previous_scene(
|
||||
instance->scene_manager, NfcSceneStart);
|
||||
NfcSceneMfClassicMfKeyCompleteState scene_state = scene_manager_get_scene_state(
|
||||
instance->scene_manager, NfcSceneMfClassicMfkeyComplete);
|
||||
if(scene_state == NfcSceneMfClassicMfKeyCompleteStateAppMissing) {
|
||||
consumed = scene_manager_search_and_switch_to_previous_scene(
|
||||
instance->scene_manager, NfcSceneStart);
|
||||
} else {
|
||||
nfc_app_run_external(instance, NFC_MFKEY32_APP_PATH);
|
||||
}
|
||||
}
|
||||
} else if(event.type == SceneManagerEventTypeBack) {
|
||||
const uint32_t prev_scenes[] = {NfcSceneSavedMenu, NfcSceneStart};
|
||||
|
||||
@@ -29,7 +29,14 @@ bool nfc_scene_save_confirm_on_event(void* context, SceneManagerEvent event) {
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveName);
|
||||
consumed = true;
|
||||
} else if(event.event == DialogExResultLeft) {
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicDetectReader);
|
||||
NfcSceneSaveConfirmState scene_state =
|
||||
scene_manager_get_scene_state(nfc->scene_manager, NfcSceneSaveConfirm);
|
||||
|
||||
NfcScene scene = scene_state == NfcSceneSaveConfirmStateCrackNonces ?
|
||||
NfcSceneMfClassicMfkeyComplete :
|
||||
NfcSceneMfClassicDetectReader;
|
||||
|
||||
scene_manager_next_scene(nfc->scene_manager, scene);
|
||||
consumed = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,13 @@ bool nfc_scene_save_success_on_event(void* context, SceneManagerEvent event) {
|
||||
consumed = scene_manager_search_and_switch_to_previous_scene(
|
||||
nfc->scene_manager, NfcSceneMfClassicKeys);
|
||||
} else if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSaveConfirm)) {
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicDetectReader);
|
||||
NfcSceneSaveConfirmState scene_state =
|
||||
scene_manager_get_scene_state(nfc->scene_manager, NfcSceneSaveConfirm);
|
||||
|
||||
NfcScene scene = scene_state == NfcSceneSaveConfirmStateCrackNonces ?
|
||||
NfcSceneMfClassicMfkeyComplete :
|
||||
NfcSceneMfClassicDetectReader;
|
||||
scene_manager_next_scene(nfc->scene_manager, scene);
|
||||
consumed = true;
|
||||
} else if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSetType)) {
|
||||
consumed = scene_manager_search_and_switch_to_another_scene(
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
App(
|
||||
appid="onewire_start",
|
||||
apptype=FlipperAppType.STARTUP,
|
||||
entry_point="onewire_on_system_start",
|
||||
order=60,
|
||||
appid="cli_onewire",
|
||||
targets=["f7"],
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="cli_onewire_ep",
|
||||
requires=["cli"],
|
||||
sources=["onewire_cli.c"],
|
||||
)
|
||||
|
||||
@@ -1,32 +1,20 @@
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
|
||||
#include <cli/cli_main_commands.h>
|
||||
#include <power/power_service/power.h>
|
||||
|
||||
#include <cli/cli.h>
|
||||
#include <toolbox/cli/cli_command.h>
|
||||
#include <toolbox/args.h>
|
||||
|
||||
#include <one_wire/one_wire_host.h>
|
||||
|
||||
static void onewire_cli(Cli* cli, FuriString* args, void* context);
|
||||
|
||||
void onewire_on_system_start(void) {
|
||||
#ifdef SRV_CLI
|
||||
Cli* cli = furi_record_open(RECORD_CLI);
|
||||
cli_add_command(cli, "onewire", CliCommandFlagDefault, onewire_cli, cli);
|
||||
furi_record_close(RECORD_CLI);
|
||||
#else
|
||||
UNUSED(onewire_cli);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void onewire_cli_print_usage(void) {
|
||||
printf("Usage:\r\n");
|
||||
printf("onewire search\r\n");
|
||||
}
|
||||
|
||||
static void onewire_cli_search(Cli* cli) {
|
||||
UNUSED(cli);
|
||||
static void onewire_cli_search(PipeSide* pipe) {
|
||||
UNUSED(pipe);
|
||||
OneWireHost* onewire = onewire_host_alloc(&gpio_ibutton);
|
||||
Power* power = furi_record_open(RECORD_POWER);
|
||||
uint8_t address[8];
|
||||
@@ -58,7 +46,7 @@ static void onewire_cli_search(Cli* cli) {
|
||||
furi_record_close(RECORD_POWER);
|
||||
}
|
||||
|
||||
void onewire_cli(Cli* cli, FuriString* args, void* context) {
|
||||
static void execute(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(context);
|
||||
FuriString* cmd;
|
||||
cmd = furi_string_alloc();
|
||||
@@ -70,8 +58,10 @@ void onewire_cli(Cli* cli, FuriString* args, void* context) {
|
||||
}
|
||||
|
||||
if(furi_string_cmp_str(cmd, "search") == 0) {
|
||||
onewire_cli_search(cli);
|
||||
onewire_cli_search(pipe);
|
||||
}
|
||||
|
||||
furi_string_free(cmd);
|
||||
}
|
||||
|
||||
CLI_COMMAND_INTERFACE(onewire, execute, CliCommandFlagDefault, 1024, CLI_APPID);
|
||||
|
||||
@@ -20,10 +20,10 @@ App(
|
||||
)
|
||||
|
||||
App(
|
||||
appid="subghz_start",
|
||||
appid="cli_subghz",
|
||||
targets=["f7"],
|
||||
apptype=FlipperAppType.STARTUP,
|
||||
entry_point="subghz_on_system_start",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="cli_subghz_ep",
|
||||
requires=["cli"],
|
||||
sources=["subghz_cli.c", "helpers/subghz_chat.c"],
|
||||
order=40,
|
||||
)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "subghz_chat.h"
|
||||
#include <lib/subghz/subghz_tx_rx_worker.h>
|
||||
#include <toolbox/pipe.h>
|
||||
|
||||
#define TAG "SubGhzChat"
|
||||
|
||||
@@ -14,7 +15,7 @@ struct SubGhzChatWorker {
|
||||
FuriMessageQueue* event_queue;
|
||||
uint32_t last_time_rx_data;
|
||||
|
||||
Cli* cli;
|
||||
PipeSide* pipe;
|
||||
};
|
||||
|
||||
/** Worker thread
|
||||
@@ -30,7 +31,7 @@ static int32_t subghz_chat_worker_thread(void* context) {
|
||||
event.event = SubGhzChatEventUserEntrance;
|
||||
furi_message_queue_put(instance->event_queue, &event, 0);
|
||||
while(instance->worker_running) {
|
||||
if(cli_read_timeout(instance->cli, (uint8_t*)&c, 1, 1000) == 1) {
|
||||
if(pipe_receive(instance->pipe, (uint8_t*)&c, 1) == 1) {
|
||||
event.event = SubGhzChatEventInputData;
|
||||
event.c = c;
|
||||
furi_message_queue_put(instance->event_queue, &event, FuriWaitForever);
|
||||
@@ -55,10 +56,10 @@ static void subghz_chat_worker_update_rx_event_chat(void* context) {
|
||||
furi_message_queue_put(instance->event_queue, &event, FuriWaitForever);
|
||||
}
|
||||
|
||||
SubGhzChatWorker* subghz_chat_worker_alloc(Cli* cli) {
|
||||
SubGhzChatWorker* subghz_chat_worker_alloc(PipeSide* pipe) {
|
||||
SubGhzChatWorker* instance = malloc(sizeof(SubGhzChatWorker));
|
||||
|
||||
instance->cli = cli;
|
||||
instance->pipe = pipe;
|
||||
|
||||
instance->thread =
|
||||
furi_thread_alloc_ex("SubGhzChat", 2048, subghz_chat_worker_thread, instance);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
#include "../subghz_i.h"
|
||||
#include <lib/subghz/devices/devices.h>
|
||||
#include <cli/cli.h>
|
||||
#include <toolbox/pipe.h>
|
||||
|
||||
typedef struct SubGhzChatWorker SubGhzChatWorker;
|
||||
|
||||
@@ -19,7 +19,7 @@ typedef struct {
|
||||
char c;
|
||||
} SubGhzChatEvent;
|
||||
|
||||
SubGhzChatWorker* subghz_chat_worker_alloc(Cli* cli);
|
||||
SubGhzChatWorker* subghz_chat_worker_alloc(PipeSide* pipe);
|
||||
void subghz_chat_worker_free(SubGhzChatWorker* instance);
|
||||
bool subghz_chat_worker_start(
|
||||
SubGhzChatWorker* instance,
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
#include <furi_hal.h>
|
||||
|
||||
#include <applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h>
|
||||
#include <cli/cli_main_commands.h>
|
||||
#include <toolbox/cli/cli_ansi.h>
|
||||
|
||||
#include <lib/subghz/subghz_keystore.h>
|
||||
#include <lib/subghz/receiver.h>
|
||||
@@ -16,6 +18,7 @@
|
||||
|
||||
#include <lib/toolbox/args.h>
|
||||
#include <lib/toolbox/strint.h>
|
||||
#include <toolbox/pipe.h>
|
||||
|
||||
#include "helpers/subghz_chat.h"
|
||||
|
||||
@@ -61,7 +64,7 @@ static SubGhzEnvironment* subghz_cli_environment_init(void) {
|
||||
return environment;
|
||||
}
|
||||
|
||||
void subghz_cli_command_tx_carrier(Cli* cli, FuriString* args, void* context) {
|
||||
void subghz_cli_command_tx_carrier(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(context);
|
||||
uint32_t frequency = 433920000;
|
||||
|
||||
@@ -91,7 +94,7 @@ void subghz_cli_command_tx_carrier(Cli* cli, FuriString* args, void* context) {
|
||||
if(furi_hal_subghz_tx()) {
|
||||
printf("Transmitting at frequency %lu Hz\r\n", frequency);
|
||||
printf("Press CTRL+C to stop\r\n");
|
||||
while(!cli_cmd_interrupt_received(cli)) {
|
||||
while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) {
|
||||
furi_delay_ms(250);
|
||||
}
|
||||
} else {
|
||||
@@ -104,7 +107,7 @@ void subghz_cli_command_tx_carrier(Cli* cli, FuriString* args, void* context) {
|
||||
furi_hal_power_suppress_charge_exit();
|
||||
}
|
||||
|
||||
void subghz_cli_command_rx_carrier(Cli* cli, FuriString* args, void* context) {
|
||||
void subghz_cli_command_rx_carrier(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(context);
|
||||
uint32_t frequency = 433920000;
|
||||
|
||||
@@ -132,7 +135,7 @@ void subghz_cli_command_rx_carrier(Cli* cli, FuriString* args, void* context) {
|
||||
|
||||
furi_hal_subghz_rx();
|
||||
|
||||
while(!cli_cmd_interrupt_received(cli)) {
|
||||
while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) {
|
||||
furi_delay_ms(250);
|
||||
printf("RSSI: %03.1fdbm\r", (double)furi_hal_subghz_get_rssi());
|
||||
fflush(stdout);
|
||||
@@ -165,7 +168,7 @@ static const SubGhzDevice* subghz_cli_command_get_device(uint32_t* device_ind) {
|
||||
return device;
|
||||
}
|
||||
|
||||
void subghz_cli_command_tx(Cli* cli, FuriString* args, void* context) {
|
||||
void subghz_cli_command_tx(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(context);
|
||||
uint32_t frequency = 433920000;
|
||||
uint32_t key = 0x0074BADE;
|
||||
@@ -235,7 +238,9 @@ void subghz_cli_command_tx(Cli* cli, FuriString* args, void* context) {
|
||||
|
||||
furi_hal_power_suppress_charge_enter();
|
||||
if(subghz_devices_start_async_tx(device, subghz_transmitter_yield, transmitter)) {
|
||||
while(!(subghz_devices_is_async_complete_tx(device) || cli_cmd_interrupt_received(cli))) {
|
||||
while(
|
||||
!(subghz_devices_is_async_complete_tx(device) ||
|
||||
cli_is_pipe_broken_or_is_etx_next_char(pipe))) {
|
||||
printf(".");
|
||||
fflush(stdout);
|
||||
furi_delay_ms(333);
|
||||
@@ -292,7 +297,7 @@ static void subghz_cli_command_rx_callback(
|
||||
furi_string_free(text);
|
||||
}
|
||||
|
||||
void subghz_cli_command_rx(Cli* cli, FuriString* args, void* context) {
|
||||
void subghz_cli_command_rx(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(context);
|
||||
uint32_t frequency = 433920000;
|
||||
uint32_t device_ind = 0; // 0 - CC1101_INT, 1 - CC1101_EXT
|
||||
@@ -348,7 +353,7 @@ void subghz_cli_command_rx(Cli* cli, FuriString* args, void* context) {
|
||||
frequency,
|
||||
device_ind);
|
||||
LevelDuration level_duration;
|
||||
while(!cli_cmd_interrupt_received(cli)) {
|
||||
while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) {
|
||||
int ret = furi_stream_buffer_receive(
|
||||
instance->stream, &level_duration, sizeof(LevelDuration), 10);
|
||||
if(ret == sizeof(LevelDuration)) {
|
||||
@@ -381,7 +386,7 @@ void subghz_cli_command_rx(Cli* cli, FuriString* args, void* context) {
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void subghz_cli_command_rx_raw(Cli* cli, FuriString* args, void* context) {
|
||||
void subghz_cli_command_rx_raw(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(context);
|
||||
uint32_t frequency = 433920000;
|
||||
|
||||
@@ -419,7 +424,7 @@ void subghz_cli_command_rx_raw(Cli* cli, FuriString* args, void* context) {
|
||||
printf("Listening at %lu. Press CTRL+C to stop\r\n", frequency);
|
||||
LevelDuration level_duration;
|
||||
size_t counter = 0;
|
||||
while(!cli_cmd_interrupt_received(cli)) {
|
||||
while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) {
|
||||
int ret = furi_stream_buffer_receive(
|
||||
instance->stream, &level_duration, sizeof(LevelDuration), 10);
|
||||
if(ret == 0) {
|
||||
@@ -455,7 +460,7 @@ void subghz_cli_command_rx_raw(Cli* cli, FuriString* args, void* context) {
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void subghz_cli_command_decode_raw(Cli* cli, FuriString* args, void* context) {
|
||||
void subghz_cli_command_decode_raw(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(context);
|
||||
FuriString* file_name;
|
||||
file_name = furi_string_alloc();
|
||||
@@ -525,7 +530,7 @@ void subghz_cli_command_decode_raw(Cli* cli, FuriString* args, void* context) {
|
||||
furi_string_get_cstr(file_name));
|
||||
|
||||
LevelDuration level_duration;
|
||||
while(!cli_cmd_interrupt_received(cli)) {
|
||||
while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) {
|
||||
furi_delay_us(500); //you need to have time to read from the file from the SD card
|
||||
level_duration = subghz_file_encoder_worker_get_level_duration(file_worker_encoder);
|
||||
if(!level_duration_is_reset(level_duration)) {
|
||||
@@ -570,7 +575,7 @@ static FuriHalSubGhzPreset subghz_cli_get_preset_name(const char* preset_name) {
|
||||
return preset;
|
||||
}
|
||||
|
||||
void subghz_cli_command_tx_from_file(Cli* cli, FuriString* args, void* context) { // -V524
|
||||
void subghz_cli_command_tx_from_file(PipeSide* pipe, FuriString* args, void* context) { // -V524
|
||||
UNUSED(context);
|
||||
FuriString* file_name;
|
||||
file_name = furi_string_alloc();
|
||||
@@ -774,7 +779,7 @@ void subghz_cli_command_tx_from_file(Cli* cli, FuriString* args, void* context)
|
||||
if(subghz_devices_start_async_tx(device, subghz_transmitter_yield, transmitter)) {
|
||||
while(
|
||||
!(subghz_devices_is_async_complete_tx(device) ||
|
||||
cli_cmd_interrupt_received(cli))) {
|
||||
cli_is_pipe_broken_or_is_etx_next_char(pipe))) {
|
||||
printf(".");
|
||||
fflush(stdout);
|
||||
furi_delay_ms(333);
|
||||
@@ -788,11 +793,11 @@ void subghz_cli_command_tx_from_file(Cli* cli, FuriString* args, void* context)
|
||||
if(!strcmp(furi_string_get_cstr(temp_str), "RAW")) {
|
||||
subghz_transmitter_stop(transmitter);
|
||||
repeat--;
|
||||
if(!cli_cmd_interrupt_received(cli) && repeat)
|
||||
if(!cli_is_pipe_broken_or_is_etx_next_char(pipe) && repeat)
|
||||
subghz_transmitter_deserialize(transmitter, fff_data_raw);
|
||||
}
|
||||
|
||||
} while(!cli_cmd_interrupt_received(cli) &&
|
||||
} while(!cli_is_pipe_broken_or_is_etx_next_char(pipe) &&
|
||||
(repeat && !strcmp(furi_string_get_cstr(temp_str), "RAW")));
|
||||
|
||||
subghz_devices_sleep(device);
|
||||
@@ -837,8 +842,8 @@ static void subghz_cli_command_print_usage(void) {
|
||||
}
|
||||
}
|
||||
|
||||
static void subghz_cli_command_encrypt_keeloq(Cli* cli, FuriString* args) {
|
||||
UNUSED(cli);
|
||||
static void subghz_cli_command_encrypt_keeloq(PipeSide* pipe, FuriString* args) {
|
||||
UNUSED(pipe);
|
||||
uint8_t iv[16];
|
||||
|
||||
FuriString* source;
|
||||
@@ -880,8 +885,8 @@ static void subghz_cli_command_encrypt_keeloq(Cli* cli, FuriString* args) {
|
||||
furi_string_free(source);
|
||||
}
|
||||
|
||||
static void subghz_cli_command_encrypt_raw(Cli* cli, FuriString* args) {
|
||||
UNUSED(cli);
|
||||
static void subghz_cli_command_encrypt_raw(PipeSide* pipe, FuriString* args) {
|
||||
UNUSED(pipe);
|
||||
uint8_t iv[16];
|
||||
|
||||
FuriString* source;
|
||||
@@ -917,7 +922,7 @@ static void subghz_cli_command_encrypt_raw(Cli* cli, FuriString* args) {
|
||||
furi_string_free(source);
|
||||
}
|
||||
|
||||
static void subghz_cli_command_chat(Cli* cli, FuriString* args) {
|
||||
static void subghz_cli_command_chat(PipeSide* pipe, FuriString* args) {
|
||||
uint32_t frequency = 433920000;
|
||||
uint32_t device_ind = 0; // 0 - CC1101_INT, 1 - CC1101_EXT
|
||||
|
||||
@@ -951,7 +956,7 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args) {
|
||||
return;
|
||||
}
|
||||
|
||||
SubGhzChatWorker* subghz_chat = subghz_chat_worker_alloc(cli);
|
||||
SubGhzChatWorker* subghz_chat = subghz_chat_worker_alloc(pipe);
|
||||
|
||||
if(!subghz_chat_worker_start(subghz_chat, device, frequency)) {
|
||||
printf("Startup error SubGhzChatWorker\r\n");
|
||||
@@ -992,13 +997,12 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args) {
|
||||
chat_event = subghz_chat_worker_get_event_chat(subghz_chat);
|
||||
switch(chat_event.event) {
|
||||
case SubGhzChatEventInputData:
|
||||
if(chat_event.c == CliSymbolAsciiETX) {
|
||||
if(chat_event.c == CliKeyETX) {
|
||||
printf("\r\n");
|
||||
chat_event.event = SubGhzChatEventUserExit;
|
||||
subghz_chat_worker_put_event_chat(subghz_chat, &chat_event);
|
||||
break;
|
||||
} else if(
|
||||
(chat_event.c == CliSymbolAsciiBackspace) || (chat_event.c == CliSymbolAsciiDel)) {
|
||||
} else if((chat_event.c == CliKeyBackspace) || (chat_event.c == CliKeyDEL)) {
|
||||
size_t len = furi_string_utf8_length(input);
|
||||
if(len > furi_string_utf8_length(name)) {
|
||||
printf("%s", "\e[D\e[1P");
|
||||
@@ -1020,7 +1024,7 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args) {
|
||||
}
|
||||
furi_string_set(input, sysmsg);
|
||||
}
|
||||
} else if(chat_event.c == CliSymbolAsciiCR) {
|
||||
} else if(chat_event.c == CliKeyCR) {
|
||||
printf("\r\n");
|
||||
furi_string_push_back(input, '\r');
|
||||
furi_string_push_back(input, '\n');
|
||||
@@ -1034,7 +1038,7 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args) {
|
||||
furi_string_printf(input, "%s", furi_string_get_cstr(name));
|
||||
printf("%s", furi_string_get_cstr(input));
|
||||
fflush(stdout);
|
||||
} else if(chat_event.c == CliSymbolAsciiLF) {
|
||||
} else if(chat_event.c == CliKeyLF) {
|
||||
//cut out the symbol \n
|
||||
} else {
|
||||
putc(chat_event.c, stdout);
|
||||
@@ -1088,7 +1092,7 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!cli_is_connected(cli)) {
|
||||
if(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) {
|
||||
printf("\r\n");
|
||||
chat_event.event = SubGhzChatEventUserExit;
|
||||
subghz_chat_worker_put_event_chat(subghz_chat, &chat_event);
|
||||
@@ -1113,7 +1117,7 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args) {
|
||||
printf("\r\nExit chat\r\n");
|
||||
}
|
||||
|
||||
static void subghz_cli_command(Cli* cli, FuriString* args, void* context) {
|
||||
static void execute(PipeSide* pipe, FuriString* args, void* context) {
|
||||
FuriString* cmd;
|
||||
cmd = furi_string_alloc();
|
||||
|
||||
@@ -1124,53 +1128,53 @@ static void subghz_cli_command(Cli* cli, FuriString* args, void* context) {
|
||||
}
|
||||
|
||||
if(furi_string_cmp_str(cmd, "chat") == 0) {
|
||||
subghz_cli_command_chat(cli, args);
|
||||
subghz_cli_command_chat(pipe, args);
|
||||
break;
|
||||
}
|
||||
|
||||
if(furi_string_cmp_str(cmd, "tx") == 0) {
|
||||
subghz_cli_command_tx(cli, args, context);
|
||||
subghz_cli_command_tx(pipe, args, context);
|
||||
break;
|
||||
}
|
||||
|
||||
if(furi_string_cmp_str(cmd, "rx") == 0) {
|
||||
subghz_cli_command_rx(cli, args, context);
|
||||
subghz_cli_command_rx(pipe, args, context);
|
||||
break;
|
||||
}
|
||||
|
||||
if(furi_string_cmp_str(cmd, "rx_raw") == 0) {
|
||||
subghz_cli_command_rx_raw(cli, args, context);
|
||||
subghz_cli_command_rx_raw(pipe, args, context);
|
||||
break;
|
||||
}
|
||||
|
||||
if(furi_string_cmp_str(cmd, "decode_raw") == 0) {
|
||||
subghz_cli_command_decode_raw(cli, args, context);
|
||||
subghz_cli_command_decode_raw(pipe, args, context);
|
||||
break;
|
||||
}
|
||||
|
||||
if(furi_string_cmp_str(cmd, "tx_from_file") == 0) {
|
||||
subghz_cli_command_tx_from_file(cli, args, context);
|
||||
subghz_cli_command_tx_from_file(pipe, args, context);
|
||||
break;
|
||||
}
|
||||
|
||||
if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) {
|
||||
if(furi_string_cmp_str(cmd, "encrypt_keeloq") == 0) {
|
||||
subghz_cli_command_encrypt_keeloq(cli, args);
|
||||
subghz_cli_command_encrypt_keeloq(pipe, args);
|
||||
break;
|
||||
}
|
||||
|
||||
if(furi_string_cmp_str(cmd, "encrypt_raw") == 0) {
|
||||
subghz_cli_command_encrypt_raw(cli, args);
|
||||
subghz_cli_command_encrypt_raw(pipe, args);
|
||||
break;
|
||||
}
|
||||
|
||||
if(furi_string_cmp_str(cmd, "tx_carrier") == 0) {
|
||||
subghz_cli_command_tx_carrier(cli, args, context);
|
||||
subghz_cli_command_tx_carrier(pipe, args, context);
|
||||
break;
|
||||
}
|
||||
|
||||
if(furi_string_cmp_str(cmd, "rx_carrier") == 0) {
|
||||
subghz_cli_command_rx_carrier(cli, args, context);
|
||||
subghz_cli_command_rx_carrier(pipe, args, context);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1181,14 +1185,4 @@ static void subghz_cli_command(Cli* cli, FuriString* args, void* context) {
|
||||
furi_string_free(cmd);
|
||||
}
|
||||
|
||||
void subghz_on_system_start(void) {
|
||||
#ifdef SRV_CLI
|
||||
Cli* cli = furi_record_open(RECORD_CLI);
|
||||
|
||||
cli_add_command(cli, "subghz", CliCommandFlagDefault, subghz_cli_command, NULL);
|
||||
|
||||
furi_record_close(RECORD_CLI);
|
||||
#else
|
||||
UNUSED(subghz_cli_command);
|
||||
#endif
|
||||
}
|
||||
CLI_COMMAND_INTERFACE(subghz, execute, CliCommandFlagDefault, 2048, CLI_APPID);
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
#include <cli/cli.h>
|
||||
|
||||
void subghz_on_system_start(void);
|
||||
|
||||
@@ -3,6 +3,7 @@ App(
|
||||
name="Basic services",
|
||||
apptype=FlipperAppType.METAPACKAGE,
|
||||
provides=[
|
||||
"cli_vcp",
|
||||
"crypto_start",
|
||||
"rpc_start",
|
||||
"expansion_start",
|
||||
|
||||
@@ -21,5 +21,5 @@ App(
|
||||
appid="bt_start",
|
||||
apptype=FlipperAppType.STARTUP,
|
||||
entry_point="bt_on_system_start",
|
||||
order=70,
|
||||
order=40,
|
||||
)
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <cli/cli.h>
|
||||
#include <lib/toolbox/args.h>
|
||||
#include <toolbox/pipe.h>
|
||||
#include <cli/cli_main_commands.h>
|
||||
#include <toolbox/cli/cli_registry.h>
|
||||
|
||||
#include <ble/ble.h>
|
||||
#include "bt_settings.h"
|
||||
#include "bt_service/bt.h"
|
||||
#include <profiles/serial_profile.h>
|
||||
|
||||
static void bt_cli_command_hci_info(Cli* cli, FuriString* args, void* context) {
|
||||
UNUSED(cli);
|
||||
static void bt_cli_command_hci_info(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(pipe);
|
||||
UNUSED(args);
|
||||
UNUSED(context);
|
||||
FuriString* buffer;
|
||||
@@ -19,7 +21,7 @@ static void bt_cli_command_hci_info(Cli* cli, FuriString* args, void* context) {
|
||||
furi_string_free(buffer);
|
||||
}
|
||||
|
||||
static void bt_cli_command_carrier_tx(Cli* cli, FuriString* args, void* context) {
|
||||
static void bt_cli_command_carrier_tx(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(context);
|
||||
int channel = 0;
|
||||
int power = 0;
|
||||
@@ -41,7 +43,7 @@ static void bt_cli_command_carrier_tx(Cli* cli, FuriString* args, void* context)
|
||||
printf("Press CTRL+C to stop\r\n");
|
||||
furi_hal_bt_start_tone_tx(channel, 0x19 + power);
|
||||
|
||||
while(!cli_cmd_interrupt_received(cli)) {
|
||||
while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) {
|
||||
furi_delay_ms(250);
|
||||
}
|
||||
furi_hal_bt_stop_tone_tx();
|
||||
@@ -51,7 +53,7 @@ static void bt_cli_command_carrier_tx(Cli* cli, FuriString* args, void* context)
|
||||
} while(false);
|
||||
}
|
||||
|
||||
static void bt_cli_command_carrier_rx(Cli* cli, FuriString* args, void* context) {
|
||||
static void bt_cli_command_carrier_rx(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(context);
|
||||
int channel = 0;
|
||||
|
||||
@@ -69,7 +71,7 @@ static void bt_cli_command_carrier_rx(Cli* cli, FuriString* args, void* context)
|
||||
|
||||
furi_hal_bt_start_packet_rx(channel, 1);
|
||||
|
||||
while(!cli_cmd_interrupt_received(cli)) {
|
||||
while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) {
|
||||
furi_delay_ms(250);
|
||||
printf("RSSI: %6.1f dB\r", (double)furi_hal_bt_get_rssi());
|
||||
fflush(stdout);
|
||||
@@ -82,7 +84,7 @@ static void bt_cli_command_carrier_rx(Cli* cli, FuriString* args, void* context)
|
||||
} while(false);
|
||||
}
|
||||
|
||||
static void bt_cli_command_packet_tx(Cli* cli, FuriString* args, void* context) {
|
||||
static void bt_cli_command_packet_tx(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(context);
|
||||
int channel = 0;
|
||||
int pattern = 0;
|
||||
@@ -119,7 +121,7 @@ static void bt_cli_command_packet_tx(Cli* cli, FuriString* args, void* context)
|
||||
printf("Press CTRL+C to stop\r\n");
|
||||
furi_hal_bt_start_packet_tx(channel, pattern, datarate);
|
||||
|
||||
while(!cli_cmd_interrupt_received(cli)) {
|
||||
while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) {
|
||||
furi_delay_ms(250);
|
||||
}
|
||||
furi_hal_bt_stop_packet_test();
|
||||
@@ -130,7 +132,7 @@ static void bt_cli_command_packet_tx(Cli* cli, FuriString* args, void* context)
|
||||
} while(false);
|
||||
}
|
||||
|
||||
static void bt_cli_command_packet_rx(Cli* cli, FuriString* args, void* context) {
|
||||
static void bt_cli_command_packet_rx(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(context);
|
||||
int channel = 0;
|
||||
int datarate = 1;
|
||||
@@ -152,7 +154,7 @@ static void bt_cli_command_packet_rx(Cli* cli, FuriString* args, void* context)
|
||||
printf("Press CTRL+C to stop\r\n");
|
||||
furi_hal_bt_start_packet_rx(channel, datarate);
|
||||
|
||||
while(!cli_cmd_interrupt_received(cli)) {
|
||||
while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) {
|
||||
furi_delay_ms(250);
|
||||
printf("RSSI: %03.1f dB\r", (double)furi_hal_bt_get_rssi());
|
||||
fflush(stdout);
|
||||
@@ -179,7 +181,7 @@ static void bt_cli_print_usage(void) {
|
||||
}
|
||||
}
|
||||
|
||||
static void bt_cli(Cli* cli, FuriString* args, void* context) {
|
||||
static void bt_cli(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(context);
|
||||
furi_record_open(RECORD_BT);
|
||||
|
||||
@@ -194,24 +196,24 @@ static void bt_cli(Cli* cli, FuriString* args, void* context) {
|
||||
break;
|
||||
}
|
||||
if(furi_string_cmp_str(cmd, "hci_info") == 0) {
|
||||
bt_cli_command_hci_info(cli, args, NULL);
|
||||
bt_cli_command_hci_info(pipe, args, NULL);
|
||||
break;
|
||||
}
|
||||
if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug) && furi_hal_bt_is_testing_supported()) {
|
||||
if(furi_string_cmp_str(cmd, "tx_carrier") == 0) {
|
||||
bt_cli_command_carrier_tx(cli, args, NULL);
|
||||
bt_cli_command_carrier_tx(pipe, args, NULL);
|
||||
break;
|
||||
}
|
||||
if(furi_string_cmp_str(cmd, "rx_carrier") == 0) {
|
||||
bt_cli_command_carrier_rx(cli, args, NULL);
|
||||
bt_cli_command_carrier_rx(pipe, args, NULL);
|
||||
break;
|
||||
}
|
||||
if(furi_string_cmp_str(cmd, "tx_packet") == 0) {
|
||||
bt_cli_command_packet_tx(cli, args, NULL);
|
||||
bt_cli_command_packet_tx(pipe, args, NULL);
|
||||
break;
|
||||
}
|
||||
if(furi_string_cmp_str(cmd, "rx_packet") == 0) {
|
||||
bt_cli_command_packet_rx(cli, args, NULL);
|
||||
bt_cli_command_packet_rx(pipe, args, NULL);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -229,8 +231,8 @@ static void bt_cli(Cli* cli, FuriString* args, void* context) {
|
||||
|
||||
void bt_on_system_start(void) {
|
||||
#ifdef SRV_CLI
|
||||
Cli* cli = furi_record_open(RECORD_CLI);
|
||||
cli_add_command(cli, RECORD_BT, CliCommandFlagDefault, bt_cli, NULL);
|
||||
CliRegistry* registry = furi_record_open(RECORD_CLI);
|
||||
cli_registry_add_command(registry, "bt", CliCommandFlagDefault, bt_cli, NULL);
|
||||
furi_record_close(RECORD_CLI);
|
||||
#else
|
||||
UNUSED(bt_cli);
|
||||
|
||||
@@ -1,10 +1,51 @@
|
||||
App(
|
||||
appid="cli",
|
||||
name="CliSrv",
|
||||
apptype=FlipperAppType.SERVICE,
|
||||
entry_point="cli_srv",
|
||||
apptype=FlipperAppType.STARTUP,
|
||||
entry_point="cli_on_system_start",
|
||||
cdefines=["SRV_CLI"],
|
||||
stack_size=4 * 1024,
|
||||
order=30,
|
||||
sdk_headers=["cli.h", "cli_vcp.h"],
|
||||
sources=[
|
||||
"cli_command_gpio.c",
|
||||
"cli_main_commands.c",
|
||||
"cli_main_shell.c",
|
||||
],
|
||||
# This STARTUP has to be processed before those that depend on the "cli" record.
|
||||
# "cli" used to be a SERVICE, but it's been converted into a STARTUP in order to
|
||||
# reduce RAM usage. The "block until record has been created" mechanism
|
||||
# unfortunately leads to a deadlock if the STARTUPs are processed sequentially.
|
||||
order=0,
|
||||
)
|
||||
|
||||
App(
|
||||
appid="cli_vcp",
|
||||
name="CliVcpSrv",
|
||||
apptype=FlipperAppType.SERVICE,
|
||||
entry_point="cli_vcp_srv",
|
||||
stack_size=1024,
|
||||
order=10,
|
||||
sdk_headers=["cli_vcp.h"],
|
||||
sources=["cli_vcp.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="cli_hello_world",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="cli_hello_world_ep",
|
||||
requires=["cli"],
|
||||
sources=["commands/hello_world.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="cli_neofetch",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="cli_neofetch_ep",
|
||||
requires=["cli"],
|
||||
sources=["commands/neofetch.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="cli_subshell_demo",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="cli_subshell_demo_ep",
|
||||
requires=["cli"],
|
||||
sources=["commands/subshell_demo.c"],
|
||||
)
|
||||
|
||||
@@ -1,484 +0,0 @@
|
||||
#include "cli_i.h"
|
||||
#include "cli_commands.h"
|
||||
#include "cli_vcp.h"
|
||||
#include <furi_hal_version.h>
|
||||
#include <loader/loader.h>
|
||||
|
||||
#define TAG "CliSrv"
|
||||
|
||||
#define CLI_INPUT_LEN_LIMIT 256
|
||||
|
||||
Cli* cli_alloc(void) {
|
||||
Cli* cli = malloc(sizeof(Cli));
|
||||
|
||||
CliCommandTree_init(cli->commands);
|
||||
|
||||
cli->last_line = furi_string_alloc();
|
||||
cli->line = furi_string_alloc();
|
||||
|
||||
cli->session = NULL;
|
||||
|
||||
cli->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
||||
|
||||
cli->idle_sem = furi_semaphore_alloc(1, 0);
|
||||
|
||||
return cli;
|
||||
}
|
||||
|
||||
void cli_putc(Cli* cli, char c) {
|
||||
furi_check(cli);
|
||||
if(cli->session != NULL) {
|
||||
cli->session->tx((uint8_t*)&c, 1);
|
||||
}
|
||||
}
|
||||
|
||||
char cli_getc(Cli* cli) {
|
||||
furi_check(cli);
|
||||
char c = 0;
|
||||
if(cli->session != NULL) {
|
||||
if(cli->session->rx((uint8_t*)&c, 1, FuriWaitForever) == 0) {
|
||||
cli_reset(cli);
|
||||
furi_delay_tick(10);
|
||||
}
|
||||
} else {
|
||||
cli_reset(cli);
|
||||
furi_delay_tick(10);
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
void cli_write(Cli* cli, const uint8_t* buffer, size_t size) {
|
||||
furi_check(cli);
|
||||
if(cli->session != NULL) {
|
||||
cli->session->tx(buffer, size);
|
||||
}
|
||||
}
|
||||
|
||||
size_t cli_read(Cli* cli, uint8_t* buffer, size_t size) {
|
||||
furi_check(cli);
|
||||
if(cli->session != NULL) {
|
||||
return cli->session->rx(buffer, size, FuriWaitForever);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
size_t cli_read_timeout(Cli* cli, uint8_t* buffer, size_t size, uint32_t timeout) {
|
||||
furi_check(cli);
|
||||
if(cli->session != NULL) {
|
||||
return cli->session->rx(buffer, size, timeout);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool cli_is_connected(Cli* cli) {
|
||||
furi_check(cli);
|
||||
if(cli->session != NULL) {
|
||||
return cli->session->is_connected();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool cli_cmd_interrupt_received(Cli* cli) {
|
||||
furi_check(cli);
|
||||
char c = '\0';
|
||||
if(cli_is_connected(cli)) {
|
||||
if(cli->session->rx((uint8_t*)&c, 1, 0) == 1) {
|
||||
return c == CliSymbolAsciiETX;
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void cli_print_usage(const char* cmd, const char* usage, const char* arg) {
|
||||
furi_check(cmd);
|
||||
furi_check(arg);
|
||||
furi_check(usage);
|
||||
|
||||
printf("%s: illegal option -- %s\r\nusage: %s %s", cmd, arg, cmd, usage);
|
||||
}
|
||||
|
||||
void cli_motd(void) {
|
||||
printf("\r\n"
|
||||
" _.-------.._ -,\r\n"
|
||||
" .-\"```\"--..,,_/ /`-, -, \\ \r\n"
|
||||
" .:\" /:/ /'\\ \\ ,_..., `. | |\r\n"
|
||||
" / ,----/:/ /`\\ _\\~`_-\"` _;\r\n"
|
||||
" ' / /`\"\"\"'\\ \\ \\.~`_-' ,-\"'/ \r\n"
|
||||
" | | | 0 | | .-' ,/` /\r\n"
|
||||
" | ,..\\ \\ ,.-\"` ,/` /\r\n"
|
||||
" ; : `/`\"\"\\` ,/--==,/-----,\r\n"
|
||||
" | `-...| -.___-Z:_______J...---;\r\n"
|
||||
" : ` _-'\r\n"
|
||||
" _L_ _ ___ ___ ___ ___ ____--\"`___ _ ___\r\n"
|
||||
"| __|| | |_ _|| _ \\| _ \\| __|| _ \\ / __|| | |_ _|\r\n"
|
||||
"| _| | |__ | | | _/| _/| _| | / | (__ | |__ | |\r\n"
|
||||
"|_| |____||___||_| |_| |___||_|_\\ \\___||____||___|\r\n"
|
||||
"\r\n"
|
||||
"Welcome to Flipper Zero Command Line Interface!\r\n"
|
||||
"Read the manual: https://docs.flipper.net/development/cli\r\n"
|
||||
"Run `help` or `?` to list available commands\r\n"
|
||||
"\r\n");
|
||||
|
||||
const Version* firmware_version = furi_hal_version_get_firmware_version();
|
||||
if(firmware_version) {
|
||||
printf(
|
||||
"Firmware version: %s %s (%s%s built on %s)\r\n",
|
||||
version_get_gitbranch(firmware_version),
|
||||
version_get_version(firmware_version),
|
||||
version_get_githash(firmware_version),
|
||||
version_get_dirty_flag(firmware_version) ? "-dirty" : "",
|
||||
version_get_builddate(firmware_version));
|
||||
}
|
||||
}
|
||||
|
||||
void cli_nl(Cli* cli) {
|
||||
UNUSED(cli);
|
||||
printf("\r\n");
|
||||
}
|
||||
|
||||
void cli_prompt(Cli* cli) {
|
||||
UNUSED(cli);
|
||||
printf("\r\n>: %s", furi_string_get_cstr(cli->line));
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
void cli_reset(Cli* cli) {
|
||||
// cli->last_line is cleared and cli->line's buffer moved to cli->last_line
|
||||
furi_string_move(cli->last_line, cli->line);
|
||||
// Reiniting cli->line
|
||||
cli->line = furi_string_alloc();
|
||||
cli->cursor_position = 0;
|
||||
}
|
||||
|
||||
static void cli_handle_backspace(Cli* cli) {
|
||||
if(cli->cursor_position > 0) {
|
||||
furi_assert(furi_string_size(cli->line) > 0);
|
||||
// Other side
|
||||
printf("\e[D\e[1P");
|
||||
fflush(stdout);
|
||||
// Our side
|
||||
furi_string_replace_at(cli->line, cli->cursor_position - 1, 1, "");
|
||||
|
||||
cli->cursor_position--;
|
||||
} else {
|
||||
cli_putc(cli, CliSymbolAsciiBell);
|
||||
}
|
||||
}
|
||||
|
||||
static void cli_normalize_line(Cli* cli) {
|
||||
furi_string_trim(cli->line);
|
||||
cli->cursor_position = furi_string_size(cli->line);
|
||||
}
|
||||
|
||||
static void cli_execute_command(Cli* cli, CliCommand* command, FuriString* args) {
|
||||
if(!(command->flags & CliCommandFlagInsomniaSafe)) {
|
||||
furi_hal_power_insomnia_enter();
|
||||
}
|
||||
|
||||
// Ensure that we running alone
|
||||
if(!(command->flags & CliCommandFlagParallelSafe)) {
|
||||
Loader* loader = furi_record_open(RECORD_LOADER);
|
||||
bool safety_lock = loader_lock(loader);
|
||||
if(safety_lock) {
|
||||
// Execute command
|
||||
command->callback(cli, args, command->context);
|
||||
loader_unlock(loader);
|
||||
} else {
|
||||
printf("Other application is running, close it first");
|
||||
}
|
||||
furi_record_close(RECORD_LOADER);
|
||||
} else {
|
||||
// Execute command
|
||||
command->callback(cli, args, command->context);
|
||||
}
|
||||
|
||||
if(!(command->flags & CliCommandFlagInsomniaSafe)) {
|
||||
furi_hal_power_insomnia_exit();
|
||||
}
|
||||
}
|
||||
|
||||
static void cli_handle_enter(Cli* cli) {
|
||||
cli_normalize_line(cli);
|
||||
|
||||
if(furi_string_size(cli->line) == 0) {
|
||||
cli_prompt(cli);
|
||||
return;
|
||||
}
|
||||
|
||||
// Command and args container
|
||||
FuriString* command;
|
||||
command = furi_string_alloc();
|
||||
FuriString* args;
|
||||
args = furi_string_alloc();
|
||||
|
||||
// Split command and args
|
||||
size_t ws = furi_string_search_char(cli->line, ' ');
|
||||
if(ws == FURI_STRING_FAILURE) {
|
||||
furi_string_set(command, cli->line);
|
||||
} else {
|
||||
furi_string_set_n(command, cli->line, 0, ws);
|
||||
furi_string_set_n(args, cli->line, ws, furi_string_size(cli->line));
|
||||
furi_string_trim(args);
|
||||
}
|
||||
|
||||
// Search for command
|
||||
furi_check(furi_mutex_acquire(cli->mutex, FuriWaitForever) == FuriStatusOk);
|
||||
CliCommand* cli_command_ptr = CliCommandTree_get(cli->commands, command);
|
||||
|
||||
if(cli_command_ptr) { //-V547
|
||||
CliCommand cli_command;
|
||||
memcpy(&cli_command, cli_command_ptr, sizeof(CliCommand));
|
||||
furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk);
|
||||
cli_nl(cli);
|
||||
cli_execute_command(cli, &cli_command, args);
|
||||
} else {
|
||||
furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk);
|
||||
cli_nl(cli);
|
||||
printf(
|
||||
"`%s` command not found, use `help` or `?` to list all available commands",
|
||||
furi_string_get_cstr(command));
|
||||
cli_putc(cli, CliSymbolAsciiBell);
|
||||
}
|
||||
|
||||
cli_reset(cli);
|
||||
cli_prompt(cli);
|
||||
|
||||
// Cleanup command and args
|
||||
furi_string_free(command);
|
||||
furi_string_free(args);
|
||||
}
|
||||
|
||||
static void cli_handle_autocomplete(Cli* cli) {
|
||||
cli_normalize_line(cli);
|
||||
|
||||
if(furi_string_size(cli->line) == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
cli_nl(cli);
|
||||
|
||||
// Prepare common base for autocomplete
|
||||
FuriString* common;
|
||||
common = furi_string_alloc();
|
||||
// Iterate throw commands
|
||||
for
|
||||
M_EACH(cli_command, cli->commands, CliCommandTree_t) {
|
||||
// Process only if starts with line buffer
|
||||
if(furi_string_start_with(*cli_command->key_ptr, cli->line)) {
|
||||
// Show autocomplete option
|
||||
printf("%s\r\n", furi_string_get_cstr(*cli_command->key_ptr));
|
||||
// Process common base for autocomplete
|
||||
if(furi_string_size(common) > 0) {
|
||||
// Choose shortest string
|
||||
const size_t key_size = furi_string_size(*cli_command->key_ptr);
|
||||
const size_t common_size = furi_string_size(common);
|
||||
const size_t min_size = key_size > common_size ? common_size : key_size;
|
||||
size_t i = 0;
|
||||
while(i < min_size) {
|
||||
// Stop when do not match
|
||||
if(furi_string_get_char(*cli_command->key_ptr, i) !=
|
||||
furi_string_get_char(common, i)) {
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
// Cut right part if any
|
||||
furi_string_left(common, i);
|
||||
} else {
|
||||
// Start with something
|
||||
furi_string_set(common, *cli_command->key_ptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Replace line buffer if autocomplete better
|
||||
if(furi_string_size(common) > furi_string_size(cli->line)) {
|
||||
furi_string_set(cli->line, common);
|
||||
cli->cursor_position = furi_string_size(cli->line);
|
||||
}
|
||||
// Cleanup
|
||||
furi_string_free(common);
|
||||
// Show prompt
|
||||
cli_prompt(cli);
|
||||
}
|
||||
|
||||
static void cli_handle_escape(Cli* cli, char c) {
|
||||
if(c == 'A') {
|
||||
// Use previous command if line buffer is empty
|
||||
if(furi_string_size(cli->line) == 0 && furi_string_cmp(cli->line, cli->last_line) != 0) {
|
||||
// Set line buffer and cursor position
|
||||
furi_string_set(cli->line, cli->last_line);
|
||||
cli->cursor_position = furi_string_size(cli->line);
|
||||
// Show new line to user
|
||||
printf("%s", furi_string_get_cstr(cli->line));
|
||||
}
|
||||
} else if(c == 'B') {
|
||||
} else if(c == 'C') {
|
||||
if(cli->cursor_position < furi_string_size(cli->line)) {
|
||||
cli->cursor_position++;
|
||||
printf("\e[C");
|
||||
}
|
||||
} else if(c == 'D') {
|
||||
if(cli->cursor_position > 0) {
|
||||
cli->cursor_position--;
|
||||
printf("\e[D");
|
||||
}
|
||||
}
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
void cli_process_input(Cli* cli) {
|
||||
char in_chr = cli_getc(cli);
|
||||
size_t rx_len;
|
||||
|
||||
if(in_chr == CliSymbolAsciiTab) {
|
||||
cli_handle_autocomplete(cli);
|
||||
} else if(in_chr == CliSymbolAsciiSOH) {
|
||||
furi_delay_ms(33); // We are too fast, Minicom is not ready yet
|
||||
cli_motd();
|
||||
cli_prompt(cli);
|
||||
} else if(in_chr == CliSymbolAsciiETX) {
|
||||
cli_reset(cli);
|
||||
cli_prompt(cli);
|
||||
} else if(in_chr == CliSymbolAsciiEOT) {
|
||||
cli_reset(cli);
|
||||
} else if(in_chr == CliSymbolAsciiEsc) {
|
||||
rx_len = cli_read(cli, (uint8_t*)&in_chr, 1);
|
||||
if((rx_len > 0) && (in_chr == '[')) {
|
||||
cli_read(cli, (uint8_t*)&in_chr, 1);
|
||||
cli_handle_escape(cli, in_chr);
|
||||
} else {
|
||||
cli_putc(cli, CliSymbolAsciiBell);
|
||||
}
|
||||
} else if(in_chr == CliSymbolAsciiBackspace || in_chr == CliSymbolAsciiDel) {
|
||||
cli_handle_backspace(cli);
|
||||
} else if(in_chr == CliSymbolAsciiCR) {
|
||||
cli_handle_enter(cli);
|
||||
} else if(
|
||||
(in_chr >= 0x20 && in_chr < 0x7F) && //-V560
|
||||
(furi_string_size(cli->line) < CLI_INPUT_LEN_LIMIT)) {
|
||||
if(cli->cursor_position == furi_string_size(cli->line)) {
|
||||
furi_string_push_back(cli->line, in_chr);
|
||||
cli_putc(cli, in_chr);
|
||||
} else {
|
||||
// Insert character to line buffer
|
||||
const char in_str[2] = {in_chr, 0};
|
||||
furi_string_replace_at(cli->line, cli->cursor_position, 0, in_str);
|
||||
|
||||
// Print character in replace mode
|
||||
printf("\e[4h%c\e[4l", in_chr);
|
||||
fflush(stdout);
|
||||
}
|
||||
cli->cursor_position++;
|
||||
} else {
|
||||
cli_putc(cli, CliSymbolAsciiBell);
|
||||
}
|
||||
}
|
||||
|
||||
void cli_add_command(
|
||||
Cli* cli,
|
||||
const char* name,
|
||||
CliCommandFlag flags,
|
||||
CliCallback callback,
|
||||
void* context) {
|
||||
furi_check(cli);
|
||||
FuriString* name_str;
|
||||
name_str = furi_string_alloc_set(name);
|
||||
furi_string_trim(name_str);
|
||||
|
||||
size_t name_replace;
|
||||
do {
|
||||
name_replace = furi_string_replace(name_str, " ", "_");
|
||||
} while(name_replace != FURI_STRING_FAILURE);
|
||||
|
||||
CliCommand c;
|
||||
c.callback = callback;
|
||||
c.context = context;
|
||||
c.flags = flags;
|
||||
|
||||
furi_check(furi_mutex_acquire(cli->mutex, FuriWaitForever) == FuriStatusOk);
|
||||
CliCommandTree_set_at(cli->commands, name_str, c);
|
||||
furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk);
|
||||
|
||||
furi_string_free(name_str);
|
||||
}
|
||||
|
||||
void cli_delete_command(Cli* cli, const char* name) {
|
||||
furi_check(cli);
|
||||
FuriString* name_str;
|
||||
name_str = furi_string_alloc_set(name);
|
||||
furi_string_trim(name_str);
|
||||
|
||||
size_t name_replace;
|
||||
do {
|
||||
name_replace = furi_string_replace(name_str, " ", "_");
|
||||
} while(name_replace != FURI_STRING_FAILURE);
|
||||
|
||||
furi_check(furi_mutex_acquire(cli->mutex, FuriWaitForever) == FuriStatusOk);
|
||||
CliCommandTree_erase(cli->commands, name_str);
|
||||
furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk);
|
||||
|
||||
furi_string_free(name_str);
|
||||
}
|
||||
|
||||
void cli_session_open(Cli* cli, void* session) {
|
||||
furi_check(cli);
|
||||
|
||||
furi_check(furi_mutex_acquire(cli->mutex, FuriWaitForever) == FuriStatusOk);
|
||||
cli->session = session;
|
||||
if(cli->session != NULL) {
|
||||
cli->session->init();
|
||||
furi_thread_set_stdout_callback(cli->session->tx_stdout, NULL);
|
||||
} else {
|
||||
furi_thread_set_stdout_callback(NULL, NULL);
|
||||
}
|
||||
furi_semaphore_release(cli->idle_sem);
|
||||
furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk);
|
||||
}
|
||||
|
||||
void cli_session_close(Cli* cli) {
|
||||
furi_check(cli);
|
||||
|
||||
furi_check(furi_mutex_acquire(cli->mutex, FuriWaitForever) == FuriStatusOk);
|
||||
if(cli->session != NULL) {
|
||||
cli->session->deinit();
|
||||
}
|
||||
cli->session = NULL;
|
||||
furi_thread_set_stdout_callback(NULL, NULL);
|
||||
furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk);
|
||||
}
|
||||
|
||||
int32_t cli_srv(void* p) {
|
||||
UNUSED(p);
|
||||
Cli* cli = cli_alloc();
|
||||
|
||||
// Init basic cli commands
|
||||
cli_commands_init(cli);
|
||||
|
||||
furi_record_create(RECORD_CLI, cli);
|
||||
|
||||
if(cli->session != NULL) {
|
||||
furi_thread_set_stdout_callback(cli->session->tx_stdout, NULL);
|
||||
} else {
|
||||
furi_thread_set_stdout_callback(NULL, NULL);
|
||||
}
|
||||
|
||||
if(furi_hal_rtc_get_boot_mode() == FuriHalRtcBootModeNormal) {
|
||||
cli_session_open(cli, &cli_vcp);
|
||||
} else {
|
||||
FURI_LOG_W(TAG, "Skipping start in special boot mode");
|
||||
}
|
||||
|
||||
while(1) {
|
||||
if(cli->session != NULL) {
|
||||
cli_process_input(cli);
|
||||
} else {
|
||||
furi_check(furi_semaphore_acquire(cli->idle_sem, FuriWaitForever) == FuriStatusOk);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,134 +0,0 @@
|
||||
/**
|
||||
* @file cli.h
|
||||
* Cli API
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <furi.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
CliSymbolAsciiSOH = 0x01,
|
||||
CliSymbolAsciiETX = 0x03,
|
||||
CliSymbolAsciiEOT = 0x04,
|
||||
CliSymbolAsciiBell = 0x07,
|
||||
CliSymbolAsciiBackspace = 0x08,
|
||||
CliSymbolAsciiTab = 0x09,
|
||||
CliSymbolAsciiLF = 0x0A,
|
||||
CliSymbolAsciiCR = 0x0D,
|
||||
CliSymbolAsciiEsc = 0x1B,
|
||||
CliSymbolAsciiUS = 0x1F,
|
||||
CliSymbolAsciiSpace = 0x20,
|
||||
CliSymbolAsciiDel = 0x7F,
|
||||
} CliSymbols;
|
||||
|
||||
typedef enum {
|
||||
CliCommandFlagDefault = 0, /**< Default, loader lock is used */
|
||||
CliCommandFlagParallelSafe =
|
||||
(1 << 0), /**< Safe to run in parallel with other apps, loader lock is not used */
|
||||
CliCommandFlagInsomniaSafe = (1 << 1), /**< Safe to run with insomnia mode on */
|
||||
} CliCommandFlag;
|
||||
|
||||
#define RECORD_CLI "cli"
|
||||
|
||||
/** Cli type anonymous structure */
|
||||
typedef struct Cli Cli;
|
||||
|
||||
/** Cli callback function pointer. Implement this interface and use
|
||||
* add_cli_command
|
||||
* @param args string with what was passed after command
|
||||
* @param context pointer to whatever you gave us on cli_add_command
|
||||
*/
|
||||
typedef void (*CliCallback)(Cli* cli, FuriString* args, void* context);
|
||||
|
||||
/** Add cli command Registers you command callback
|
||||
*
|
||||
* @param cli pointer to cli instance
|
||||
* @param name command name
|
||||
* @param flags CliCommandFlag
|
||||
* @param callback callback function
|
||||
* @param context pointer to whatever we need to pass to callback
|
||||
*/
|
||||
void cli_add_command(
|
||||
Cli* cli,
|
||||
const char* name,
|
||||
CliCommandFlag flags,
|
||||
CliCallback callback,
|
||||
void* context);
|
||||
|
||||
/** Print unified cmd usage tip
|
||||
*
|
||||
* @param cmd cmd name
|
||||
* @param usage usage tip
|
||||
* @param arg arg passed by user
|
||||
*/
|
||||
void cli_print_usage(const char* cmd, const char* usage, const char* arg);
|
||||
|
||||
/** Delete cli command
|
||||
*
|
||||
* @param cli pointer to cli instance
|
||||
* @param name command name
|
||||
*/
|
||||
void cli_delete_command(Cli* cli, const char* name);
|
||||
|
||||
/** Read from terminal
|
||||
*
|
||||
* @param cli Cli instance
|
||||
* @param buffer pointer to buffer
|
||||
* @param size size of buffer in bytes
|
||||
*
|
||||
* @return bytes read
|
||||
*/
|
||||
size_t cli_read(Cli* cli, uint8_t* buffer, size_t size);
|
||||
|
||||
/** Non-blocking read from terminal
|
||||
*
|
||||
* @param cli Cli instance
|
||||
* @param buffer pointer to buffer
|
||||
* @param size size of buffer in bytes
|
||||
* @param timeout timeout value in ms
|
||||
*
|
||||
* @return bytes read
|
||||
*/
|
||||
size_t cli_read_timeout(Cli* cli, uint8_t* buffer, size_t size, uint32_t timeout);
|
||||
|
||||
/** Non-blocking check for interrupt command received
|
||||
*
|
||||
* @param cli Cli instance
|
||||
*
|
||||
* @return true if received
|
||||
*/
|
||||
bool cli_cmd_interrupt_received(Cli* cli);
|
||||
|
||||
/** Write to terminal Do it only from inside of cli call.
|
||||
*
|
||||
* @param cli Cli instance
|
||||
* @param buffer pointer to buffer
|
||||
* @param size size of buffer in bytes
|
||||
*/
|
||||
void cli_write(Cli* cli, const uint8_t* buffer, size_t size);
|
||||
|
||||
/** Read character
|
||||
*
|
||||
* @param cli Cli instance
|
||||
*
|
||||
* @return char
|
||||
*/
|
||||
char cli_getc(Cli* cli);
|
||||
|
||||
/** New line Send new ine sequence
|
||||
*/
|
||||
void cli_nl(Cli* cli);
|
||||
|
||||
void cli_session_open(Cli* cli, void* session);
|
||||
|
||||
void cli_session_close(Cli* cli);
|
||||
|
||||
bool cli_is_connected(Cli* cli);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -3,6 +3,8 @@
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <lib/toolbox/args.h>
|
||||
#include <toolbox/pipe.h>
|
||||
#include <toolbox/cli/cli_command.h>
|
||||
|
||||
void cli_command_gpio_print_usage(void) {
|
||||
printf("Usage:\r\n");
|
||||
@@ -70,8 +72,8 @@ static GpioParseReturn gpio_command_parse(FuriString* args, size_t* pin_num, uin
|
||||
return ret;
|
||||
}
|
||||
|
||||
void cli_command_gpio_mode(Cli* cli, FuriString* args, void* context) {
|
||||
UNUSED(cli);
|
||||
void cli_command_gpio_mode(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(pipe);
|
||||
UNUSED(context);
|
||||
|
||||
size_t num = 0;
|
||||
@@ -93,7 +95,7 @@ void cli_command_gpio_mode(Cli* cli, FuriString* args, void* context) {
|
||||
if(gpio_pins[num].debug) { //-V779
|
||||
printf(
|
||||
"Changing this pin mode may damage hardware. Are you sure you want to continue? (y/n)?\r\n");
|
||||
char c = cli_getc(cli);
|
||||
char c = getchar();
|
||||
if(c != 'y' && c != 'Y') {
|
||||
printf("Cancelled.\r\n");
|
||||
return;
|
||||
@@ -110,8 +112,8 @@ void cli_command_gpio_mode(Cli* cli, FuriString* args, void* context) {
|
||||
}
|
||||
}
|
||||
|
||||
void cli_command_gpio_read(Cli* cli, FuriString* args, void* context) {
|
||||
UNUSED(cli);
|
||||
void cli_command_gpio_read(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(pipe);
|
||||
UNUSED(context);
|
||||
|
||||
size_t num = 0;
|
||||
@@ -131,7 +133,8 @@ void cli_command_gpio_read(Cli* cli, FuriString* args, void* context) {
|
||||
printf("Pin %s <= %u", gpio_pins[num].name, val);
|
||||
}
|
||||
|
||||
void cli_command_gpio_set(Cli* cli, FuriString* args, void* context) {
|
||||
void cli_command_gpio_set(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(pipe);
|
||||
UNUSED(context);
|
||||
|
||||
size_t num = 0;
|
||||
@@ -159,7 +162,7 @@ void cli_command_gpio_set(Cli* cli, FuriString* args, void* context) {
|
||||
if(gpio_pins[num].debug) {
|
||||
printf(
|
||||
"Setting this pin may damage hardware. Are you sure you want to continue? (y/n)?\r\n");
|
||||
char c = cli_getc(cli);
|
||||
char c = getchar();
|
||||
if(c != 'y' && c != 'Y') {
|
||||
printf("Cancelled.\r\n");
|
||||
return;
|
||||
@@ -170,7 +173,7 @@ void cli_command_gpio_set(Cli* cli, FuriString* args, void* context) {
|
||||
printf("Pin %s => %u", gpio_pins[num].name, !!value);
|
||||
}
|
||||
|
||||
void cli_command_gpio(Cli* cli, FuriString* args, void* context) {
|
||||
void cli_command_gpio(PipeSide* pipe, FuriString* args, void* context) {
|
||||
FuriString* cmd;
|
||||
cmd = furi_string_alloc();
|
||||
|
||||
@@ -181,17 +184,17 @@ void cli_command_gpio(Cli* cli, FuriString* args, void* context) {
|
||||
}
|
||||
|
||||
if(furi_string_cmp_str(cmd, "mode") == 0) {
|
||||
cli_command_gpio_mode(cli, args, context);
|
||||
cli_command_gpio_mode(pipe, args, context);
|
||||
break;
|
||||
}
|
||||
|
||||
if(furi_string_cmp_str(cmd, "set") == 0) {
|
||||
cli_command_gpio_set(cli, args, context);
|
||||
cli_command_gpio_set(pipe, args, context);
|
||||
break;
|
||||
}
|
||||
|
||||
if(furi_string_cmp_str(cmd, "read") == 0) {
|
||||
cli_command_gpio_read(cli, args, context);
|
||||
cli_command_gpio_read(pipe, args, context);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include "cli_i.h"
|
||||
#include <toolbox/pipe.h>
|
||||
|
||||
void cli_command_gpio(Cli* cli, FuriString* args, void* context);
|
||||
void cli_command_gpio(PipeSide* pipe, FuriString* args, void* context);
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "cli_i.h"
|
||||
|
||||
void cli_commands_init(Cli* cli);
|
||||
@@ -1,68 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "cli.h"
|
||||
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
|
||||
#include <m-dict.h>
|
||||
#include <m-bptree.h>
|
||||
#include <m-array.h>
|
||||
|
||||
#include "cli_vcp.h"
|
||||
|
||||
#define CLI_LINE_SIZE_MAX
|
||||
#define CLI_COMMANDS_TREE_RANK 4
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
CliCallback callback;
|
||||
void* context;
|
||||
uint32_t flags;
|
||||
} CliCommand;
|
||||
|
||||
struct CliSession {
|
||||
void (*init)(void);
|
||||
void (*deinit)(void);
|
||||
size_t (*rx)(uint8_t* buffer, size_t size, uint32_t timeout);
|
||||
size_t (*rx_stdin)(uint8_t* buffer, size_t size, uint32_t timeout, void* context);
|
||||
void (*tx)(const uint8_t* buffer, size_t size);
|
||||
void (*tx_stdout)(const char* data, size_t size, void* context);
|
||||
bool (*is_connected)(void);
|
||||
};
|
||||
|
||||
BPTREE_DEF2(
|
||||
CliCommandTree,
|
||||
CLI_COMMANDS_TREE_RANK,
|
||||
FuriString*,
|
||||
FURI_STRING_OPLIST,
|
||||
CliCommand,
|
||||
M_POD_OPLIST)
|
||||
|
||||
#define M_OPL_CliCommandTree_t() BPTREE_OPLIST(CliCommandTree, M_POD_OPLIST)
|
||||
|
||||
struct Cli {
|
||||
CliCommandTree_t commands;
|
||||
FuriMutex* mutex;
|
||||
FuriSemaphore* idle_sem;
|
||||
FuriString* last_line;
|
||||
FuriString* line;
|
||||
CliSession* session;
|
||||
|
||||
size_t cursor_position;
|
||||
};
|
||||
|
||||
Cli* cli_alloc(void);
|
||||
|
||||
void cli_reset(Cli* cli);
|
||||
|
||||
void cli_putc(Cli* cli, char c);
|
||||
|
||||
void cli_stdout_callback(void* _cookie, const char* data, size_t size);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "cli_commands.h"
|
||||
#include "cli_main_commands.h"
|
||||
#include "cli_command_gpio.h"
|
||||
#include <toolbox/cli/cli_ansi.h>
|
||||
|
||||
#include <core/thread.h>
|
||||
#include <furi_hal.h>
|
||||
@@ -11,6 +12,7 @@
|
||||
#include <loader/loader.h>
|
||||
#include <lib/toolbox/args.h>
|
||||
#include <lib/toolbox/strint.h>
|
||||
#include <toolbox/pipe.h>
|
||||
|
||||
// Close to ISO, `date +'%Y-%m-%d %H:%M:%S %u'`
|
||||
#define CLI_DATE_FORMAT "%.4d-%.2d-%.2d %.2d:%.2d:%.2d %d"
|
||||
@@ -34,8 +36,8 @@ void cli_command_info_callback(const char* key, const char* value, bool last, vo
|
||||
* @param args The arguments
|
||||
* @param context The context
|
||||
*/
|
||||
void cli_command_info(Cli* cli, FuriString* args, void* context) {
|
||||
UNUSED(cli);
|
||||
void cli_command_info(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(pipe);
|
||||
|
||||
if(context) {
|
||||
furi_hal_info_get(cli_command_info_callback, '_', NULL);
|
||||
@@ -53,56 +55,16 @@ void cli_command_info(Cli* cli, FuriString* args, void* context) {
|
||||
}
|
||||
}
|
||||
|
||||
void cli_command_help(Cli* cli, FuriString* args, void* context) {
|
||||
UNUSED(args);
|
||||
UNUSED(context);
|
||||
printf("Commands available:");
|
||||
|
||||
// Command count
|
||||
const size_t commands_count = CliCommandTree_size(cli->commands);
|
||||
const size_t commands_count_mid = commands_count / 2 + commands_count % 2;
|
||||
|
||||
// Use 2 iterators from start and middle to show 2 columns
|
||||
CliCommandTree_it_t it_left;
|
||||
CliCommandTree_it(it_left, cli->commands);
|
||||
CliCommandTree_it_t it_right;
|
||||
CliCommandTree_it(it_right, cli->commands);
|
||||
for(size_t i = 0; i < commands_count_mid; i++)
|
||||
CliCommandTree_next(it_right);
|
||||
|
||||
// Iterate throw tree
|
||||
for(size_t i = 0; i < commands_count_mid; i++) {
|
||||
printf("\r\n");
|
||||
// Left Column
|
||||
if(!CliCommandTree_end_p(it_left)) {
|
||||
printf("%-30s", furi_string_get_cstr(*CliCommandTree_ref(it_left)->key_ptr));
|
||||
CliCommandTree_next(it_left);
|
||||
}
|
||||
// Right Column
|
||||
if(!CliCommandTree_end_p(it_right)) {
|
||||
printf("%s", furi_string_get_cstr(*CliCommandTree_ref(it_right)->key_ptr));
|
||||
CliCommandTree_next(it_right);
|
||||
}
|
||||
};
|
||||
|
||||
if(furi_string_size(args) > 0) {
|
||||
cli_nl(cli);
|
||||
printf("`");
|
||||
printf("%s", furi_string_get_cstr(args));
|
||||
printf("` command not found");
|
||||
}
|
||||
}
|
||||
|
||||
void cli_command_uptime(Cli* cli, FuriString* args, void* context) {
|
||||
UNUSED(cli);
|
||||
void cli_command_uptime(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(pipe);
|
||||
UNUSED(args);
|
||||
UNUSED(context);
|
||||
uint32_t uptime = furi_get_tick() / furi_kernel_get_tick_frequency();
|
||||
printf("Uptime: %luh%lum%lus", uptime / 60 / 60, uptime / 60 % 60, uptime % 60);
|
||||
}
|
||||
|
||||
void cli_command_date(Cli* cli, FuriString* args, void* context) {
|
||||
UNUSED(cli);
|
||||
void cli_command_date(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(pipe);
|
||||
UNUSED(context);
|
||||
|
||||
DateTime datetime = {0};
|
||||
@@ -174,7 +136,8 @@ void cli_command_date(Cli* cli, FuriString* args, void* context) {
|
||||
#define CLI_COMMAND_LOG_BUFFER_SIZE 64
|
||||
|
||||
void cli_command_log_tx_callback(const uint8_t* buffer, size_t size, void* context) {
|
||||
furi_stream_buffer_send(context, buffer, size, 0);
|
||||
PipeSide* pipe = context;
|
||||
pipe_send(pipe, buffer, size);
|
||||
}
|
||||
|
||||
bool cli_command_log_level_set_from_string(FuriString* level) {
|
||||
@@ -196,16 +159,13 @@ bool cli_command_log_level_set_from_string(FuriString* level) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void cli_command_log(Cli* cli, FuriString* args, void* context) {
|
||||
void cli_command_log(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(context);
|
||||
FuriStreamBuffer* ring = furi_stream_buffer_alloc(CLI_COMMAND_LOG_RING_SIZE, 1);
|
||||
uint8_t buffer[CLI_COMMAND_LOG_BUFFER_SIZE];
|
||||
FuriLogLevel previous_level = furi_log_get_level();
|
||||
bool restore_log_level = false;
|
||||
|
||||
if(furi_string_size(args) > 0) {
|
||||
if(!cli_command_log_level_set_from_string(args)) {
|
||||
furi_stream_buffer_free(ring);
|
||||
return;
|
||||
}
|
||||
restore_log_level = true;
|
||||
@@ -217,16 +177,15 @@ void cli_command_log(Cli* cli, FuriString* args, void* context) {
|
||||
|
||||
FuriLogHandler log_handler = {
|
||||
.callback = cli_command_log_tx_callback,
|
||||
.context = ring,
|
||||
.context = pipe,
|
||||
};
|
||||
|
||||
furi_log_add_handler(log_handler);
|
||||
|
||||
printf("Use <log ?> to list available log levels\r\n");
|
||||
printf("Press CTRL+C to stop...\r\n");
|
||||
while(!cli_cmd_interrupt_received(cli)) {
|
||||
size_t ret = furi_stream_buffer_receive(ring, buffer, CLI_COMMAND_LOG_BUFFER_SIZE, 50);
|
||||
cli_write(cli, buffer, ret);
|
||||
while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) {
|
||||
furi_delay_ms(100);
|
||||
}
|
||||
|
||||
furi_log_remove_handler(log_handler);
|
||||
@@ -235,12 +194,10 @@ void cli_command_log(Cli* cli, FuriString* args, void* context) {
|
||||
// There will be strange behaviour if log level is set from settings while log command is running
|
||||
furi_log_set_level(previous_level);
|
||||
}
|
||||
|
||||
furi_stream_buffer_free(ring);
|
||||
}
|
||||
|
||||
void cli_command_sysctl_debug(Cli* cli, FuriString* args, void* context) {
|
||||
UNUSED(cli);
|
||||
void cli_command_sysctl_debug(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(pipe);
|
||||
UNUSED(context);
|
||||
if(!furi_string_cmp(args, "0")) {
|
||||
furi_hal_rtc_reset_flag(FuriHalRtcFlagDebug);
|
||||
@@ -253,8 +210,8 @@ void cli_command_sysctl_debug(Cli* cli, FuriString* args, void* context) {
|
||||
}
|
||||
}
|
||||
|
||||
void cli_command_sysctl_heap_track(Cli* cli, FuriString* args, void* context) {
|
||||
UNUSED(cli);
|
||||
void cli_command_sysctl_heap_track(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(pipe);
|
||||
UNUSED(context);
|
||||
if(!furi_string_cmp(args, "none")) {
|
||||
furi_hal_rtc_set_heap_track_mode(FuriHalRtcHeapTrackModeNone);
|
||||
@@ -288,7 +245,7 @@ void cli_command_sysctl_print_usage(void) {
|
||||
#endif
|
||||
}
|
||||
|
||||
void cli_command_sysctl(Cli* cli, FuriString* args, void* context) {
|
||||
void cli_command_sysctl(PipeSide* pipe, FuriString* args, void* context) {
|
||||
FuriString* cmd;
|
||||
cmd = furi_string_alloc();
|
||||
|
||||
@@ -299,12 +256,12 @@ void cli_command_sysctl(Cli* cli, FuriString* args, void* context) {
|
||||
}
|
||||
|
||||
if(furi_string_cmp_str(cmd, "debug") == 0) {
|
||||
cli_command_sysctl_debug(cli, args, context);
|
||||
cli_command_sysctl_debug(pipe, args, context);
|
||||
break;
|
||||
}
|
||||
|
||||
if(furi_string_cmp_str(cmd, "heap_track") == 0) {
|
||||
cli_command_sysctl_heap_track(cli, args, context);
|
||||
cli_command_sysctl_heap_track(pipe, args, context);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -314,8 +271,8 @@ void cli_command_sysctl(Cli* cli, FuriString* args, void* context) {
|
||||
furi_string_free(cmd);
|
||||
}
|
||||
|
||||
void cli_command_vibro(Cli* cli, FuriString* args, void* context) {
|
||||
UNUSED(cli);
|
||||
void cli_command_vibro(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(pipe);
|
||||
UNUSED(context);
|
||||
|
||||
if(!furi_string_cmp(args, "0")) {
|
||||
@@ -341,8 +298,8 @@ void cli_command_vibro(Cli* cli, FuriString* args, void* context) {
|
||||
}
|
||||
}
|
||||
|
||||
void cli_command_led(Cli* cli, FuriString* args, void* context) {
|
||||
UNUSED(cli);
|
||||
void cli_command_led(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(pipe);
|
||||
UNUSED(context);
|
||||
// Get first word as light name
|
||||
NotificationMessage notification_led_message;
|
||||
@@ -396,23 +353,23 @@ void cli_command_led(Cli* cli, FuriString* args, void* context) {
|
||||
furi_record_close(RECORD_NOTIFICATION);
|
||||
}
|
||||
|
||||
static void cli_command_top(Cli* cli, FuriString* args, void* context) {
|
||||
UNUSED(cli);
|
||||
static void cli_command_top(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(context);
|
||||
|
||||
int interval = 1000;
|
||||
args_read_int_and_trim(args, &interval);
|
||||
|
||||
FuriThreadList* thread_list = furi_thread_list_alloc();
|
||||
while(!cli_cmd_interrupt_received(cli)) {
|
||||
while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) {
|
||||
uint32_t tick = furi_get_tick();
|
||||
furi_thread_enumerate(thread_list);
|
||||
|
||||
if(interval) printf("\e[2J\e[0;0f"); // Clear display and return to 0
|
||||
if(interval) printf(ANSI_CURSOR_POS("1", "1"));
|
||||
|
||||
uint32_t uptime = tick / furi_kernel_get_tick_frequency();
|
||||
printf(
|
||||
"Threads: %zu, ISR Time: %0.2f%%, Uptime: %luh%lum%lus\r\n",
|
||||
"Threads: %zu, ISR Time: %0.2f%%, Uptime: %luh%lum%lus" ANSI_ERASE_LINE(
|
||||
ANSI_ERASE_FROM_CURSOR_TO_END) "\r\n",
|
||||
furi_thread_list_size(thread_list),
|
||||
(double)furi_thread_list_get_isr_time(thread_list),
|
||||
uptime / 60 / 60,
|
||||
@@ -420,14 +377,16 @@ static void cli_command_top(Cli* cli, FuriString* args, void* context) {
|
||||
uptime % 60);
|
||||
|
||||
printf(
|
||||
"Heap: total %zu, free %zu, minimum %zu, max block %zu\r\n\r\n",
|
||||
"Heap: total %zu, free %zu, minimum %zu, max block %zu" ANSI_ERASE_LINE(
|
||||
ANSI_ERASE_FROM_CURSOR_TO_END) "\r\n" ANSI_ERASE_LINE(ANSI_ERASE_FROM_CURSOR_TO_END) "\r\n",
|
||||
memmgr_get_total_heap(),
|
||||
memmgr_get_free_heap(),
|
||||
memmgr_get_minimum_free_heap(),
|
||||
memmgr_heap_get_max_free_block());
|
||||
|
||||
printf(
|
||||
"%-17s %-20s %-10s %5s %12s %6s %10s %7s %5s\r\n",
|
||||
"%-17s %-20s %-10s %5s %12s %6s %10s %7s %5s" ANSI_ERASE_LINE(
|
||||
ANSI_ERASE_FROM_CURSOR_TO_END) "\r\n",
|
||||
"AppID",
|
||||
"Name",
|
||||
"State",
|
||||
@@ -436,12 +395,13 @@ static void cli_command_top(Cli* cli, FuriString* args, void* context) {
|
||||
"Stack",
|
||||
"Stack Min",
|
||||
"Heap",
|
||||
"CPU");
|
||||
"%CPU");
|
||||
|
||||
for(size_t i = 0; i < furi_thread_list_size(thread_list); i++) {
|
||||
const FuriThreadListItem* item = furi_thread_list_get_at(thread_list, i);
|
||||
printf(
|
||||
"%-17s %-20s %-10s %5d 0x%08lx %6lu %10lu %7zu %5.1f\r\n",
|
||||
"%-17s %-20s %-10s %5d 0x%08lx %6lu %10lu %7zu %5.1f" ANSI_ERASE_LINE(
|
||||
ANSI_ERASE_FROM_CURSOR_TO_END) "\r\n",
|
||||
item->app_id,
|
||||
item->name,
|
||||
item->state,
|
||||
@@ -453,6 +413,9 @@ static void cli_command_top(Cli* cli, FuriString* args, void* context) {
|
||||
(double)item->cpu);
|
||||
}
|
||||
|
||||
printf(ANSI_ERASE_DISPLAY(ANSI_ERASE_FROM_CURSOR_TO_END));
|
||||
fflush(stdout);
|
||||
|
||||
if(interval > 0) {
|
||||
furi_delay_ms(interval);
|
||||
} else {
|
||||
@@ -462,8 +425,8 @@ static void cli_command_top(Cli* cli, FuriString* args, void* context) {
|
||||
furi_thread_list_free(thread_list);
|
||||
}
|
||||
|
||||
void cli_command_free(Cli* cli, FuriString* args, void* context) {
|
||||
UNUSED(cli);
|
||||
void cli_command_free(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(pipe);
|
||||
UNUSED(args);
|
||||
UNUSED(context);
|
||||
|
||||
@@ -476,16 +439,16 @@ void cli_command_free(Cli* cli, FuriString* args, void* context) {
|
||||
printf("Maximum pool block: %zu\r\n", memmgr_pool_get_max_block());
|
||||
}
|
||||
|
||||
void cli_command_free_blocks(Cli* cli, FuriString* args, void* context) {
|
||||
UNUSED(cli);
|
||||
void cli_command_free_blocks(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(pipe);
|
||||
UNUSED(args);
|
||||
UNUSED(context);
|
||||
|
||||
memmgr_heap_printf_free_blocks();
|
||||
}
|
||||
|
||||
void cli_command_i2c(Cli* cli, FuriString* args, void* context) {
|
||||
UNUSED(cli);
|
||||
void cli_command_i2c(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(pipe);
|
||||
UNUSED(args);
|
||||
UNUSED(context);
|
||||
|
||||
@@ -507,24 +470,53 @@ void cli_command_i2c(Cli* cli, FuriString* args, void* context) {
|
||||
furi_hal_i2c_release(&furi_hal_i2c_handle_external);
|
||||
}
|
||||
|
||||
void cli_commands_init(Cli* cli) {
|
||||
cli_add_command(cli, "!", CliCommandFlagParallelSafe, cli_command_info, (void*)true);
|
||||
cli_add_command(cli, "info", CliCommandFlagParallelSafe, cli_command_info, NULL);
|
||||
cli_add_command(cli, "device_info", CliCommandFlagParallelSafe, cli_command_info, (void*)true);
|
||||
/**
|
||||
* Echoes any bytes it receives except ASCII ETX (0x03, Ctrl+C)
|
||||
*/
|
||||
void cli_command_echo(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(args);
|
||||
UNUSED(context);
|
||||
|
||||
cli_add_command(cli, "?", CliCommandFlagParallelSafe, cli_command_help, NULL);
|
||||
cli_add_command(cli, "help", CliCommandFlagParallelSafe, cli_command_help, NULL);
|
||||
uint8_t buffer[256];
|
||||
|
||||
cli_add_command(cli, "uptime", CliCommandFlagDefault, cli_command_uptime, NULL);
|
||||
cli_add_command(cli, "date", CliCommandFlagParallelSafe, cli_command_date, NULL);
|
||||
cli_add_command(cli, "log", CliCommandFlagParallelSafe, cli_command_log, NULL);
|
||||
cli_add_command(cli, "sysctl", CliCommandFlagDefault, cli_command_sysctl, NULL);
|
||||
cli_add_command(cli, "top", CliCommandFlagParallelSafe, cli_command_top, NULL);
|
||||
cli_add_command(cli, "free", CliCommandFlagParallelSafe, cli_command_free, NULL);
|
||||
cli_add_command(cli, "free_blocks", CliCommandFlagParallelSafe, cli_command_free_blocks, NULL);
|
||||
while(true) {
|
||||
size_t to_read = CLAMP(pipe_bytes_available(pipe), sizeof(buffer), 1UL);
|
||||
size_t read = pipe_receive(pipe, buffer, to_read);
|
||||
if(read < to_read) break;
|
||||
|
||||
cli_add_command(cli, "vibro", CliCommandFlagDefault, cli_command_vibro, NULL);
|
||||
cli_add_command(cli, "led", CliCommandFlagDefault, cli_command_led, NULL);
|
||||
cli_add_command(cli, "gpio", CliCommandFlagDefault, cli_command_gpio, NULL);
|
||||
cli_add_command(cli, "i2c", CliCommandFlagDefault, cli_command_i2c, NULL);
|
||||
if(memchr(buffer, CliKeyETX, read)) break;
|
||||
|
||||
size_t written = pipe_send(pipe, buffer, read);
|
||||
if(written < read) break;
|
||||
}
|
||||
}
|
||||
|
||||
void cli_main_commands_init(CliRegistry* registry) {
|
||||
cli_registry_add_command(
|
||||
registry, "!", CliCommandFlagParallelSafe, cli_command_info, (void*)true);
|
||||
cli_registry_add_command(registry, "info", CliCommandFlagParallelSafe, cli_command_info, NULL);
|
||||
cli_registry_add_command(
|
||||
registry, "device_info", CliCommandFlagParallelSafe, cli_command_info, (void*)true);
|
||||
|
||||
cli_registry_add_command(
|
||||
registry, "uptime", CliCommandFlagParallelSafe, cli_command_uptime, NULL);
|
||||
cli_registry_add_command(registry, "date", CliCommandFlagParallelSafe, cli_command_date, NULL);
|
||||
cli_registry_add_command(registry, "log", CliCommandFlagParallelSafe, cli_command_log, NULL);
|
||||
cli_registry_add_command(registry, "sysctl", CliCommandFlagDefault, cli_command_sysctl, NULL);
|
||||
cli_registry_add_command(registry, "top", CliCommandFlagParallelSafe, cli_command_top, NULL);
|
||||
cli_registry_add_command(registry, "free", CliCommandFlagParallelSafe, cli_command_free, NULL);
|
||||
cli_registry_add_command(
|
||||
registry, "free_blocks", CliCommandFlagDefault, cli_command_free_blocks, NULL);
|
||||
cli_registry_add_command(registry, "echo", CliCommandFlagParallelSafe, cli_command_echo, NULL);
|
||||
|
||||
cli_registry_add_command(registry, "vibro", CliCommandFlagDefault, cli_command_vibro, NULL);
|
||||
cli_registry_add_command(registry, "led", CliCommandFlagDefault, cli_command_led, NULL);
|
||||
cli_registry_add_command(registry, "gpio", CliCommandFlagDefault, cli_command_gpio, NULL);
|
||||
cli_registry_add_command(registry, "i2c", CliCommandFlagDefault, cli_command_i2c, NULL);
|
||||
}
|
||||
|
||||
void cli_on_system_start(void) {
|
||||
CliRegistry* registry = cli_registry_alloc();
|
||||
cli_main_commands_init(registry);
|
||||
furi_record_create(RECORD_CLI, registry);
|
||||
}
|
||||
9
applications/services/cli/cli_main_commands.h
Normal file
9
applications/services/cli/cli_main_commands.h
Normal file
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <toolbox/cli/cli_command.h>
|
||||
#include <toolbox/cli/cli_registry.h>
|
||||
|
||||
#define RECORD_CLI "cli"
|
||||
#define CLI_APPID "cli"
|
||||
|
||||
void cli_main_commands_init(CliRegistry* registry);
|
||||
46
applications/services/cli/cli_main_shell.c
Normal file
46
applications/services/cli/cli_main_shell.c
Normal file
@@ -0,0 +1,46 @@
|
||||
#include "cli_main_shell.h"
|
||||
#include "cli_main_commands.h"
|
||||
#include <toolbox/cli/cli_ansi.h>
|
||||
#include <toolbox/cli/shell/cli_shell.h>
|
||||
#include <furi_hal_version.h>
|
||||
|
||||
void cli_main_motd(void* context) {
|
||||
UNUSED(context);
|
||||
printf(ANSI_FLIPPER_BRAND_ORANGE
|
||||
"\r\n"
|
||||
" _.-------.._ -,\r\n"
|
||||
" .-\"```\"--..,,_/ /`-, -, \\ \r\n"
|
||||
" .:\" /:/ /'\\ \\ ,_..., `. | |\r\n"
|
||||
" / ,----/:/ /`\\ _\\~`_-\"` _;\r\n"
|
||||
" ' / /`\"\"\"'\\ \\ \\.~`_-' ,-\"'/ \r\n"
|
||||
" | | | 0 | | .-' ,/` /\r\n"
|
||||
" | ,..\\ \\ ,.-\"` ,/` /\r\n"
|
||||
" ; : `/`\"\"\\` ,/--==,/-----,\r\n"
|
||||
" | `-...| -.___-Z:_______J...---;\r\n"
|
||||
" : ` _-'\r\n"
|
||||
" _L_ _ ___ ___ ___ ___ ____--\"`___ _ ___\r\n"
|
||||
"| __|| | |_ _|| _ \\| _ \\| __|| _ \\ / __|| | |_ _|\r\n"
|
||||
"| _| | |__ | | | _/| _/| _| | / | (__ | |__ | |\r\n"
|
||||
"|_| |____||___||_| |_| |___||_|_\\ \\___||____||___|\r\n"
|
||||
"\r\n" ANSI_FG_BR_WHITE "Welcome to Flipper Zero Command Line Interface!\r\n"
|
||||
"Read the manual: https://docs.flipper.net/development/cli\r\n"
|
||||
"Run `help` or `?` to list available commands\r\n"
|
||||
"\r\n" ANSI_RESET);
|
||||
|
||||
const Version* firmware_version = furi_hal_version_get_firmware_version();
|
||||
if(firmware_version) {
|
||||
printf(
|
||||
"Firmware version: %s %s (%s%s built on %s)\r\n",
|
||||
version_get_gitbranch(firmware_version),
|
||||
version_get_version(firmware_version),
|
||||
version_get_githash(firmware_version),
|
||||
version_get_dirty_flag(firmware_version) ? "-dirty" : "",
|
||||
version_get_builddate(firmware_version));
|
||||
}
|
||||
}
|
||||
|
||||
const CliCommandExternalConfig cli_main_ext_config = {
|
||||
.search_directory = "/ext/apps_data/cli/plugins",
|
||||
.fal_prefix = "cli_",
|
||||
.appid = CLI_APPID,
|
||||
};
|
||||
7
applications/services/cli/cli_main_shell.h
Normal file
7
applications/services/cli/cli_main_shell.h
Normal file
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <toolbox/cli/cli_command.h>
|
||||
|
||||
void cli_main_motd(void* context);
|
||||
|
||||
extern const CliCommandExternalConfig cli_main_ext_config;
|
||||
@@ -1,323 +1,308 @@
|
||||
#include "cli_i.h" // IWYU pragma: keep
|
||||
#include "cli_vcp.h"
|
||||
#include <furi_hal_usb_cdc.h>
|
||||
#include <furi_hal.h>
|
||||
#include <furi.h>
|
||||
#include <stdint.h>
|
||||
#include <toolbox/pipe.h>
|
||||
#include <toolbox/cli/shell/cli_shell.h>
|
||||
#include "cli_main_shell.h"
|
||||
#include "cli_main_commands.h"
|
||||
|
||||
#define TAG "CliVcp"
|
||||
|
||||
#define USB_CDC_PKT_LEN CDC_DATA_SZ
|
||||
#define VCP_RX_BUF_SIZE (USB_CDC_PKT_LEN * 3)
|
||||
#define VCP_TX_BUF_SIZE (USB_CDC_PKT_LEN * 3)
|
||||
#define USB_CDC_PKT_LEN CDC_DATA_SZ
|
||||
#define VCP_BUF_SIZE (USB_CDC_PKT_LEN * 3)
|
||||
#define VCP_IF_NUM 0
|
||||
#define VCP_MESSAGE_Q_LEN 8
|
||||
|
||||
#define VCP_IF_NUM 0
|
||||
|
||||
#ifdef CLI_VCP_DEBUG
|
||||
#define VCP_DEBUG(...) FURI_LOG_D(TAG, __VA_ARGS__)
|
||||
#ifdef CLI_VCP_TRACE
|
||||
#define VCP_TRACE(...) FURI_LOG_T(__VA_ARGS__)
|
||||
#else
|
||||
#define VCP_DEBUG(...)
|
||||
#define VCP_TRACE(...)
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
VcpEvtStop = (1 << 0),
|
||||
VcpEvtConnect = (1 << 1),
|
||||
VcpEvtDisconnect = (1 << 2),
|
||||
VcpEvtStreamRx = (1 << 3),
|
||||
VcpEvtRx = (1 << 4),
|
||||
VcpEvtStreamTx = (1 << 5),
|
||||
VcpEvtTx = (1 << 6),
|
||||
} WorkerEvtFlags;
|
||||
|
||||
#define VCP_THREAD_FLAG_ALL \
|
||||
(VcpEvtStop | VcpEvtConnect | VcpEvtDisconnect | VcpEvtRx | VcpEvtTx | VcpEvtStreamRx | \
|
||||
VcpEvtStreamTx)
|
||||
|
||||
typedef struct {
|
||||
FuriThread* thread;
|
||||
enum {
|
||||
CliVcpMessageTypeEnable,
|
||||
CliVcpMessageTypeDisable,
|
||||
} type;
|
||||
union {};
|
||||
} CliVcpMessage;
|
||||
|
||||
FuriStreamBuffer* tx_stream;
|
||||
FuriStreamBuffer* rx_stream;
|
||||
typedef enum {
|
||||
CliVcpInternalEventConnected = (1 << 0),
|
||||
CliVcpInternalEventDisconnected = (1 << 1),
|
||||
CliVcpInternalEventTxDone = (1 << 2),
|
||||
CliVcpInternalEventRx = (1 << 3),
|
||||
} CliVcpInternalEvent;
|
||||
|
||||
volatile bool connected;
|
||||
volatile bool running;
|
||||
#define CliVcpInternalEventAll \
|
||||
(CliVcpInternalEventConnected | CliVcpInternalEventDisconnected | CliVcpInternalEventTxDone | \
|
||||
CliVcpInternalEventRx)
|
||||
|
||||
FuriHalUsbInterface* usb_if_prev;
|
||||
struct CliVcp {
|
||||
FuriEventLoop* event_loop;
|
||||
FuriMessageQueue* message_queue; // <! external messages
|
||||
FuriThreadId thread_id;
|
||||
|
||||
uint8_t data_buffer[USB_CDC_PKT_LEN];
|
||||
} CliVcp;
|
||||
bool is_enabled, is_connected;
|
||||
FuriHalUsbInterface* previous_interface;
|
||||
|
||||
static int32_t vcp_worker(void* context);
|
||||
static void vcp_on_cdc_tx_complete(void* context);
|
||||
static void vcp_on_cdc_rx(void* context);
|
||||
static void vcp_state_callback(void* context, uint8_t state);
|
||||
static void vcp_on_cdc_control_line(void* context, uint8_t state);
|
||||
PipeSide* own_pipe;
|
||||
PipeSide* shell_pipe;
|
||||
bool is_currently_transmitting;
|
||||
size_t previous_tx_length;
|
||||
|
||||
static CdcCallbacks cdc_cb = {
|
||||
vcp_on_cdc_tx_complete,
|
||||
vcp_on_cdc_rx,
|
||||
vcp_state_callback,
|
||||
vcp_on_cdc_control_line,
|
||||
NULL,
|
||||
CliRegistry* main_registry;
|
||||
CliShell* shell;
|
||||
};
|
||||
|
||||
static CliVcp* vcp = NULL;
|
||||
// ============
|
||||
// Data copying
|
||||
// ============
|
||||
|
||||
static const uint8_t ascii_soh = 0x01;
|
||||
static const uint8_t ascii_eot = 0x04;
|
||||
/**
|
||||
* Called in the following cases:
|
||||
* - previous transfer has finished;
|
||||
* - new data became available to send.
|
||||
*/
|
||||
static void cli_vcp_maybe_send_data(CliVcp* cli_vcp) {
|
||||
if(cli_vcp->is_currently_transmitting) return;
|
||||
if(!cli_vcp->own_pipe) return;
|
||||
|
||||
static void cli_vcp_init(void) {
|
||||
if(vcp == NULL) {
|
||||
vcp = malloc(sizeof(CliVcp));
|
||||
vcp->tx_stream = furi_stream_buffer_alloc(VCP_TX_BUF_SIZE, 1);
|
||||
vcp->rx_stream = furi_stream_buffer_alloc(VCP_RX_BUF_SIZE, 1);
|
||||
uint8_t buf[USB_CDC_PKT_LEN];
|
||||
size_t to_receive_from_pipe = MIN(sizeof(buf), pipe_bytes_available(cli_vcp->own_pipe));
|
||||
size_t length = pipe_receive(cli_vcp->own_pipe, buf, to_receive_from_pipe);
|
||||
if(length > 0 || cli_vcp->previous_tx_length == USB_CDC_PKT_LEN) {
|
||||
VCP_TRACE(TAG, "cdc_send length=%zu", length);
|
||||
cli_vcp->is_currently_transmitting = true;
|
||||
furi_hal_cdc_send(VCP_IF_NUM, buf, length);
|
||||
}
|
||||
furi_assert(vcp->thread == NULL);
|
||||
|
||||
vcp->connected = false;
|
||||
|
||||
vcp->thread = furi_thread_alloc_ex("CliVcpWorker", 1024, vcp_worker, NULL);
|
||||
furi_thread_start(vcp->thread);
|
||||
|
||||
FURI_LOG_I(TAG, "Init OK");
|
||||
cli_vcp->previous_tx_length = length;
|
||||
}
|
||||
|
||||
static void cli_vcp_deinit(void) {
|
||||
furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtStop);
|
||||
furi_thread_join(vcp->thread);
|
||||
furi_thread_free(vcp->thread);
|
||||
vcp->thread = NULL;
|
||||
/**
|
||||
* Called in the following cases:
|
||||
* - new data arrived at the endpoint;
|
||||
* - data was read out of the pipe.
|
||||
*/
|
||||
static void cli_vcp_maybe_receive_data(CliVcp* cli_vcp) {
|
||||
if(!cli_vcp->own_pipe) return;
|
||||
if(pipe_spaces_available(cli_vcp->own_pipe) < USB_CDC_PKT_LEN) return;
|
||||
|
||||
uint8_t buf[USB_CDC_PKT_LEN];
|
||||
size_t length = furi_hal_cdc_receive(VCP_IF_NUM, buf, sizeof(buf));
|
||||
VCP_TRACE(TAG, "cdc_receive length=%zu", length);
|
||||
furi_check(pipe_send(cli_vcp->own_pipe, buf, length) == length);
|
||||
}
|
||||
|
||||
static int32_t vcp_worker(void* context) {
|
||||
UNUSED(context);
|
||||
bool tx_idle = true;
|
||||
size_t missed_rx = 0;
|
||||
uint8_t last_tx_pkt_len = 0;
|
||||
// =============
|
||||
// CDC callbacks
|
||||
// =============
|
||||
|
||||
// Switch USB to VCP mode (if it is not set yet)
|
||||
vcp->usb_if_prev = furi_hal_usb_get_config();
|
||||
if((vcp->usb_if_prev != &usb_cdc_single) && (vcp->usb_if_prev != &usb_cdc_dual)) {
|
||||
static void cli_vcp_signal_internal_event(CliVcp* cli_vcp, CliVcpInternalEvent event) {
|
||||
furi_thread_flags_set(cli_vcp->thread_id, event);
|
||||
}
|
||||
|
||||
static void cli_vcp_cdc_tx_done(void* context) {
|
||||
CliVcp* cli_vcp = context;
|
||||
cli_vcp_signal_internal_event(cli_vcp, CliVcpInternalEventTxDone);
|
||||
}
|
||||
|
||||
static void cli_vcp_cdc_rx(void* context) {
|
||||
CliVcp* cli_vcp = context;
|
||||
cli_vcp_signal_internal_event(cli_vcp, CliVcpInternalEventRx);
|
||||
}
|
||||
|
||||
static void cli_vcp_cdc_state_callback(void* context, CdcState state) {
|
||||
CliVcp* cli_vcp = context;
|
||||
if(state == CdcStateDisconnected) {
|
||||
cli_vcp_signal_internal_event(cli_vcp, CliVcpInternalEventDisconnected);
|
||||
}
|
||||
// `Connected` events are generated by DTR going active
|
||||
}
|
||||
|
||||
static void cli_vcp_cdc_ctrl_line_callback(void* context, CdcCtrlLine ctrl_lines) {
|
||||
CliVcp* cli_vcp = context;
|
||||
if(ctrl_lines & CdcCtrlLineDTR) {
|
||||
cli_vcp_signal_internal_event(cli_vcp, CliVcpInternalEventConnected);
|
||||
} else {
|
||||
cli_vcp_signal_internal_event(cli_vcp, CliVcpInternalEventDisconnected);
|
||||
}
|
||||
}
|
||||
|
||||
static CdcCallbacks cdc_callbacks = {
|
||||
.tx_ep_callback = cli_vcp_cdc_tx_done,
|
||||
.rx_ep_callback = cli_vcp_cdc_rx,
|
||||
.state_callback = cli_vcp_cdc_state_callback,
|
||||
.ctrl_line_callback = cli_vcp_cdc_ctrl_line_callback,
|
||||
.config_callback = NULL,
|
||||
};
|
||||
|
||||
// ======================
|
||||
// Pipe callback handlers
|
||||
// ======================
|
||||
|
||||
static void cli_vcp_data_from_shell(PipeSide* pipe, void* context) {
|
||||
UNUSED(pipe);
|
||||
CliVcp* cli_vcp = context;
|
||||
cli_vcp_maybe_send_data(cli_vcp);
|
||||
}
|
||||
|
||||
static void cli_vcp_shell_ready(PipeSide* pipe, void* context) {
|
||||
UNUSED(pipe);
|
||||
CliVcp* cli_vcp = context;
|
||||
cli_vcp_maybe_receive_data(cli_vcp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes messages arriving from other threads
|
||||
*/
|
||||
static void cli_vcp_message_received(FuriEventLoopObject* object, void* context) {
|
||||
CliVcp* cli_vcp = context;
|
||||
CliVcpMessage message;
|
||||
furi_check(furi_message_queue_get(object, &message, 0) == FuriStatusOk);
|
||||
|
||||
switch(message.type) {
|
||||
case CliVcpMessageTypeEnable:
|
||||
if(cli_vcp->is_enabled) return;
|
||||
FURI_LOG_D(TAG, "Enabling");
|
||||
cli_vcp->is_enabled = true;
|
||||
|
||||
// switch usb mode
|
||||
cli_vcp->previous_interface = furi_hal_usb_get_config();
|
||||
furi_hal_usb_set_config(&usb_cdc_single, NULL);
|
||||
furi_hal_cdc_set_callbacks(VCP_IF_NUM, &cdc_callbacks, cli_vcp);
|
||||
break;
|
||||
|
||||
case CliVcpMessageTypeDisable:
|
||||
if(!cli_vcp->is_enabled) return;
|
||||
FURI_LOG_D(TAG, "Disabling");
|
||||
cli_vcp->is_enabled = false;
|
||||
|
||||
// restore usb mode
|
||||
furi_hal_cdc_set_callbacks(VCP_IF_NUM, NULL, NULL);
|
||||
furi_hal_usb_set_config(cli_vcp->previous_interface, NULL);
|
||||
break;
|
||||
}
|
||||
furi_hal_cdc_set_callbacks(VCP_IF_NUM, &cdc_cb, NULL);
|
||||
|
||||
FURI_LOG_D(TAG, "Start");
|
||||
vcp->running = true;
|
||||
|
||||
while(1) {
|
||||
uint32_t flags =
|
||||
furi_thread_flags_wait(VCP_THREAD_FLAG_ALL, FuriFlagWaitAny, FuriWaitForever);
|
||||
furi_assert(!(flags & FuriFlagError));
|
||||
|
||||
// VCP session opened
|
||||
if(flags & VcpEvtConnect) {
|
||||
VCP_DEBUG("Connect");
|
||||
|
||||
if(vcp->connected == false) {
|
||||
vcp->connected = true;
|
||||
furi_stream_buffer_send(vcp->rx_stream, &ascii_soh, 1, FuriWaitForever);
|
||||
}
|
||||
}
|
||||
|
||||
// VCP session closed
|
||||
if(flags & VcpEvtDisconnect) {
|
||||
VCP_DEBUG("Disconnect");
|
||||
|
||||
if(vcp->connected == true) {
|
||||
vcp->connected = false;
|
||||
furi_stream_buffer_receive(vcp->tx_stream, vcp->data_buffer, USB_CDC_PKT_LEN, 0);
|
||||
furi_stream_buffer_send(vcp->rx_stream, &ascii_eot, 1, FuriWaitForever);
|
||||
}
|
||||
}
|
||||
|
||||
// Rx buffer was read, maybe there is enough space for new data?
|
||||
if((flags & VcpEvtStreamRx) && (missed_rx > 0)) {
|
||||
VCP_DEBUG("StreamRx");
|
||||
|
||||
if(furi_stream_buffer_spaces_available(vcp->rx_stream) >= USB_CDC_PKT_LEN) {
|
||||
flags |= VcpEvtRx;
|
||||
missed_rx--;
|
||||
}
|
||||
}
|
||||
|
||||
// New data received
|
||||
if(flags & VcpEvtRx) {
|
||||
if(furi_stream_buffer_spaces_available(vcp->rx_stream) >= USB_CDC_PKT_LEN) {
|
||||
int32_t len = furi_hal_cdc_receive(VCP_IF_NUM, vcp->data_buffer, USB_CDC_PKT_LEN);
|
||||
VCP_DEBUG("Rx %ld", len);
|
||||
|
||||
if(len > 0) {
|
||||
furi_check(
|
||||
furi_stream_buffer_send(
|
||||
vcp->rx_stream, vcp->data_buffer, len, FuriWaitForever) ==
|
||||
(size_t)len);
|
||||
}
|
||||
} else {
|
||||
VCP_DEBUG("Rx missed");
|
||||
missed_rx++;
|
||||
}
|
||||
}
|
||||
|
||||
// New data in Tx buffer
|
||||
if(flags & VcpEvtStreamTx) {
|
||||
VCP_DEBUG("StreamTx");
|
||||
|
||||
if(tx_idle) {
|
||||
flags |= VcpEvtTx;
|
||||
}
|
||||
}
|
||||
|
||||
// CDC write transfer done
|
||||
if(flags & VcpEvtTx) {
|
||||
size_t len =
|
||||
furi_stream_buffer_receive(vcp->tx_stream, vcp->data_buffer, USB_CDC_PKT_LEN, 0);
|
||||
|
||||
VCP_DEBUG("Tx %d", len);
|
||||
|
||||
if(len > 0) { // Some data left in Tx buffer. Sending it now
|
||||
tx_idle = false;
|
||||
furi_hal_cdc_send(VCP_IF_NUM, vcp->data_buffer, len);
|
||||
last_tx_pkt_len = len;
|
||||
} else { // There is nothing to send.
|
||||
if(last_tx_pkt_len == 64) {
|
||||
// Send extra zero-length packet if last packet len is 64 to indicate transfer end
|
||||
furi_hal_cdc_send(VCP_IF_NUM, NULL, 0);
|
||||
} else {
|
||||
// Set flag to start next transfer instantly
|
||||
tx_idle = true;
|
||||
}
|
||||
last_tx_pkt_len = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if(flags & VcpEvtStop) {
|
||||
vcp->connected = false;
|
||||
vcp->running = false;
|
||||
furi_hal_cdc_set_callbacks(VCP_IF_NUM, NULL, NULL);
|
||||
// Restore previous USB mode (if it was set during init)
|
||||
if((vcp->usb_if_prev != &usb_cdc_single) && (vcp->usb_if_prev != &usb_cdc_dual)) {
|
||||
furi_hal_usb_unlock();
|
||||
furi_hal_usb_set_config(vcp->usb_if_prev, NULL);
|
||||
}
|
||||
furi_stream_buffer_receive(vcp->tx_stream, vcp->data_buffer, USB_CDC_PKT_LEN, 0);
|
||||
furi_stream_buffer_send(vcp->rx_stream, &ascii_eot, 1, FuriWaitForever);
|
||||
break;
|
||||
}
|
||||
}
|
||||
FURI_LOG_D(TAG, "End");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static size_t cli_vcp_rx(uint8_t* buffer, size_t size, uint32_t timeout) {
|
||||
furi_assert(vcp);
|
||||
furi_assert(buffer);
|
||||
/**
|
||||
* Processes messages arriving from CDC event callbacks
|
||||
*/
|
||||
static void cli_vcp_internal_event_happened(void* context) {
|
||||
CliVcp* cli_vcp = context;
|
||||
CliVcpInternalEvent event = furi_thread_flags_wait(CliVcpInternalEventAll, FuriFlagWaitAny, 0);
|
||||
furi_check(!(event & FuriFlagError));
|
||||
|
||||
if(vcp->running == false) {
|
||||
if(event & CliVcpInternalEventDisconnected) {
|
||||
if(!cli_vcp->is_connected) return;
|
||||
FURI_LOG_D(TAG, "Disconnected");
|
||||
cli_vcp->is_connected = false;
|
||||
|
||||
// disconnect our side of the pipe
|
||||
pipe_detach_from_event_loop(cli_vcp->own_pipe);
|
||||
pipe_free(cli_vcp->own_pipe);
|
||||
cli_vcp->own_pipe = NULL;
|
||||
}
|
||||
|
||||
if(event & CliVcpInternalEventConnected) {
|
||||
if(cli_vcp->is_connected) return;
|
||||
FURI_LOG_D(TAG, "Connected");
|
||||
cli_vcp->is_connected = true;
|
||||
|
||||
// wait for previous shell to stop
|
||||
furi_check(!cli_vcp->own_pipe);
|
||||
if(cli_vcp->shell) {
|
||||
cli_shell_join(cli_vcp->shell);
|
||||
cli_shell_free(cli_vcp->shell);
|
||||
pipe_free(cli_vcp->shell_pipe);
|
||||
}
|
||||
|
||||
// start shell thread
|
||||
PipeSideBundle bundle = pipe_alloc(VCP_BUF_SIZE, 1);
|
||||
cli_vcp->own_pipe = bundle.alices_side;
|
||||
cli_vcp->shell_pipe = bundle.bobs_side;
|
||||
pipe_attach_to_event_loop(cli_vcp->own_pipe, cli_vcp->event_loop);
|
||||
pipe_set_callback_context(cli_vcp->own_pipe, cli_vcp);
|
||||
pipe_set_data_arrived_callback(
|
||||
cli_vcp->own_pipe, cli_vcp_data_from_shell, FuriEventLoopEventFlagEdge);
|
||||
pipe_set_space_freed_callback(
|
||||
cli_vcp->own_pipe, cli_vcp_shell_ready, FuriEventLoopEventFlagEdge);
|
||||
furi_delay_ms(33); // we are too fast, minicom isn't ready yet
|
||||
cli_vcp->shell = cli_shell_alloc(
|
||||
cli_main_motd, NULL, cli_vcp->shell_pipe, cli_vcp->main_registry, &cli_main_ext_config);
|
||||
cli_shell_start(cli_vcp->shell);
|
||||
}
|
||||
|
||||
if(event & CliVcpInternalEventRx) {
|
||||
VCP_TRACE(TAG, "Rx");
|
||||
cli_vcp_maybe_receive_data(cli_vcp);
|
||||
}
|
||||
|
||||
if(event & CliVcpInternalEventTxDone) {
|
||||
VCP_TRACE(TAG, "TxDone");
|
||||
cli_vcp->is_currently_transmitting = false;
|
||||
cli_vcp_maybe_send_data(cli_vcp);
|
||||
}
|
||||
}
|
||||
|
||||
// ============
|
||||
// Thread stuff
|
||||
// ============
|
||||
|
||||
static CliVcp* cli_vcp_alloc(void) {
|
||||
CliVcp* cli_vcp = malloc(sizeof(CliVcp));
|
||||
cli_vcp->thread_id = furi_thread_get_current_id();
|
||||
|
||||
cli_vcp->event_loop = furi_event_loop_alloc();
|
||||
|
||||
cli_vcp->message_queue = furi_message_queue_alloc(VCP_MESSAGE_Q_LEN, sizeof(CliVcpMessage));
|
||||
furi_event_loop_subscribe_message_queue(
|
||||
cli_vcp->event_loop,
|
||||
cli_vcp->message_queue,
|
||||
FuriEventLoopEventIn,
|
||||
cli_vcp_message_received,
|
||||
cli_vcp);
|
||||
|
||||
furi_event_loop_subscribe_thread_flags(
|
||||
cli_vcp->event_loop, cli_vcp_internal_event_happened, cli_vcp);
|
||||
|
||||
cli_vcp->main_registry = furi_record_open(RECORD_CLI);
|
||||
|
||||
return cli_vcp;
|
||||
}
|
||||
|
||||
int32_t cli_vcp_srv(void* p) {
|
||||
UNUSED(p);
|
||||
|
||||
if(furi_hal_rtc_get_boot_mode() != FuriHalRtcBootModeNormal) {
|
||||
FURI_LOG_W(TAG, "Skipping start in special boot mode");
|
||||
furi_thread_suspend(furi_thread_get_current_id());
|
||||
return 0;
|
||||
}
|
||||
|
||||
VCP_DEBUG("rx %u start", size);
|
||||
CliVcp* cli_vcp = cli_vcp_alloc();
|
||||
furi_record_create(RECORD_CLI_VCP, cli_vcp);
|
||||
furi_event_loop_run(cli_vcp->event_loop);
|
||||
|
||||
size_t rx_cnt = 0;
|
||||
|
||||
while(size > 0) {
|
||||
size_t batch_size = size;
|
||||
if(batch_size > VCP_RX_BUF_SIZE) batch_size = VCP_RX_BUF_SIZE;
|
||||
|
||||
size_t len = furi_stream_buffer_receive(vcp->rx_stream, buffer, batch_size, timeout);
|
||||
VCP_DEBUG("rx %u ", batch_size);
|
||||
|
||||
if(len == 0) break;
|
||||
if(vcp->running == false) {
|
||||
// EOT command is received after VCP session close
|
||||
rx_cnt += len;
|
||||
break;
|
||||
}
|
||||
furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtStreamRx);
|
||||
size -= len;
|
||||
buffer += len;
|
||||
rx_cnt += len;
|
||||
}
|
||||
|
||||
VCP_DEBUG("rx %u end", size);
|
||||
return rx_cnt;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static size_t cli_vcp_rx_stdin(uint8_t* data, size_t size, uint32_t timeout, void* context) {
|
||||
UNUSED(context);
|
||||
return cli_vcp_rx(data, size, timeout);
|
||||
// ==========
|
||||
// Public API
|
||||
// ==========
|
||||
|
||||
void cli_vcp_enable(CliVcp* cli_vcp) {
|
||||
CliVcpMessage message = {
|
||||
.type = CliVcpMessageTypeEnable,
|
||||
};
|
||||
furi_message_queue_put(cli_vcp->message_queue, &message, FuriWaitForever);
|
||||
}
|
||||
|
||||
static void cli_vcp_tx(const uint8_t* buffer, size_t size) {
|
||||
furi_assert(vcp);
|
||||
furi_assert(buffer);
|
||||
|
||||
if(vcp->running == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
VCP_DEBUG("tx %u start", size);
|
||||
|
||||
while(size > 0 && vcp->connected) {
|
||||
size_t batch_size = size;
|
||||
if(batch_size > USB_CDC_PKT_LEN) batch_size = USB_CDC_PKT_LEN;
|
||||
|
||||
furi_stream_buffer_send(vcp->tx_stream, buffer, batch_size, FuriWaitForever);
|
||||
furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtStreamTx);
|
||||
VCP_DEBUG("tx %u", batch_size);
|
||||
|
||||
size -= batch_size;
|
||||
buffer += batch_size;
|
||||
}
|
||||
|
||||
VCP_DEBUG("tx %u end", size);
|
||||
void cli_vcp_disable(CliVcp* cli_vcp) {
|
||||
CliVcpMessage message = {
|
||||
.type = CliVcpMessageTypeDisable,
|
||||
};
|
||||
furi_message_queue_put(cli_vcp->message_queue, &message, FuriWaitForever);
|
||||
}
|
||||
|
||||
static void cli_vcp_tx_stdout(const char* data, size_t size, void* context) {
|
||||
UNUSED(context);
|
||||
cli_vcp_tx((const uint8_t*)data, size);
|
||||
}
|
||||
|
||||
static void vcp_state_callback(void* context, uint8_t state) {
|
||||
UNUSED(context);
|
||||
if(state == 0) {
|
||||
furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtDisconnect);
|
||||
}
|
||||
}
|
||||
|
||||
static void vcp_on_cdc_control_line(void* context, uint8_t state) {
|
||||
UNUSED(context);
|
||||
// bit 0: DTR state, bit 1: RTS state
|
||||
bool dtr = state & (1 << 0);
|
||||
|
||||
if(dtr == true) {
|
||||
furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtConnect);
|
||||
} else {
|
||||
furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtDisconnect);
|
||||
}
|
||||
}
|
||||
|
||||
static void vcp_on_cdc_rx(void* context) {
|
||||
UNUSED(context);
|
||||
uint32_t ret = furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtRx);
|
||||
furi_check(!(ret & FuriFlagError));
|
||||
}
|
||||
|
||||
static void vcp_on_cdc_tx_complete(void* context) {
|
||||
UNUSED(context);
|
||||
furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtTx);
|
||||
}
|
||||
|
||||
static bool cli_vcp_is_connected(void) {
|
||||
furi_assert(vcp);
|
||||
return vcp->connected;
|
||||
}
|
||||
|
||||
CliSession cli_vcp = {
|
||||
cli_vcp_init,
|
||||
cli_vcp_deinit,
|
||||
cli_vcp_rx,
|
||||
cli_vcp_rx_stdin,
|
||||
cli_vcp_tx,
|
||||
cli_vcp_tx_stdout,
|
||||
cli_vcp_is_connected,
|
||||
};
|
||||
|
||||
@@ -9,9 +9,12 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct CliSession CliSession;
|
||||
#define RECORD_CLI_VCP "cli_vcp"
|
||||
|
||||
extern CliSession cli_vcp;
|
||||
typedef struct CliVcp CliVcp;
|
||||
|
||||
void cli_vcp_enable(CliVcp* cli_vcp);
|
||||
void cli_vcp_disable(CliVcp* cli_vcp);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
10
applications/services/cli/commands/hello_world.c
Normal file
10
applications/services/cli/commands/hello_world.c
Normal file
@@ -0,0 +1,10 @@
|
||||
#include "../cli_main_commands.h"
|
||||
|
||||
static void execute(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(pipe);
|
||||
UNUSED(args);
|
||||
UNUSED(context);
|
||||
puts("Hello, World!");
|
||||
}
|
||||
|
||||
CLI_COMMAND_INTERFACE(hello_world, execute, CliCommandFlagParallelSafe, 768, CLI_APPID);
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user