Merge remote-tracking branch 'ofw/dev' into feat/nfc-type-4

This commit is contained in:
Willy-JL
2025-04-10 03:40:10 +01:00
388 changed files with 8000 additions and 3466 deletions

100
.github/CODEOWNERS vendored
View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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",

View File

@@ -6,6 +6,5 @@ App(
entry_point="accessor_app",
requires=["gui"],
stack_size=4 * 1024,
order=40,
fap_category="Debug",
)

View File

@@ -8,7 +8,6 @@ App(
"power",
],
stack_size=1 * 1024,
order=130,
fap_category="Debug",
fap_libs=["assets"],
)

View File

@@ -5,6 +5,5 @@ App(
entry_point="blink_test_app",
requires=["gui"],
stack_size=1 * 1024,
order=10,
fap_category="Debug",
)

View File

@@ -13,6 +13,5 @@ App(
"bt_debug",
],
stack_size=1 * 1024,
order=110,
fap_category="Debug",
)

View File

@@ -10,6 +10,5 @@ App(
"ccid_test",
],
stack_size=1 * 1024,
order=120,
fap_category="Debug",
)

View File

@@ -5,6 +5,5 @@ App(
entry_point="direct_draw_app",
requires=["gui", "input"],
stack_size=2 * 1024,
order=70,
fap_category="Debug",
)

View File

@@ -6,6 +6,5 @@ App(
requires=["gui"],
fap_libs=["u8g2"],
stack_size=1 * 1024,
order=120,
fap_category="Debug",
)

View File

@@ -5,6 +5,5 @@ App(
entry_point="event_loop_blink_test_app",
requires=["input"],
stack_size=1 * 1024,
order=20,
fap_category="Debug",
)

View File

@@ -6,7 +6,6 @@ App(
requires=["expansion_start"],
fap_libs=["assets"],
stack_size=1 * 1024,
order=20,
fap_category="Debug",
fap_file_assets="assets",
)

View File

@@ -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",
)

View File

@@ -5,6 +5,5 @@ App(
entry_point="keypad_test_app",
requires=["gui"],
stack_size=1 * 1024,
order=30,
fap_category="Debug",
)

View File

@@ -11,6 +11,5 @@ App(
"lfrfid_debug",
],
stack_size=1 * 1024,
order=100,
fap_category="Debug",
)

View 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",
)

View 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;
}

View 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",
)

View 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;
}

View File

@@ -5,6 +5,5 @@ App(
entry_point="locale_test_app",
requires=["gui", "locale"],
stack_size=2 * 1024,
order=70,
fap_category="Debug",
)

View File

@@ -5,6 +5,5 @@ App(
entry_point="rpc_debug_app",
requires=["gui", "rpc_start", "notification"],
stack_size=2 * 1024,
order=10,
fap_category="Debug",
)

View File

@@ -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"],
)

View File

@@ -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) {

View File

@@ -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",

View File

@@ -5,6 +5,5 @@ App(
entry_point="text_box_element_test_app",
requires=["gui"],
stack_size=1 * 1024,
order=140,
fap_category="Debug",
)

View File

@@ -5,6 +5,5 @@ App(
entry_point="text_box_view_test_app",
requires=["gui"],
stack_size=1 * 1024,
order=140,
fap_category="Debug",
)

View File

@@ -5,6 +5,5 @@ App(
entry_point="uart_echo_app",
requires=["gui"],
stack_size=2 * 1024,
order=70,
fap_category="Debug",
)

View File

@@ -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(

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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);

View File

@@ -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; \

View File

@@ -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));

View File

@@ -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>

View File

@@ -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);
}

View File

@@ -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)));

View File

@@ -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
}

View File

@@ -5,6 +5,5 @@ App(
entry_point="usb_mouse_app",
requires=["gui"],
stack_size=1 * 1024,
order=60,
fap_category="Debug",
)

View File

@@ -5,6 +5,5 @@ App(
entry_point="usb_test_app",
requires=["gui"],
stack_size=1 * 1024,
order=50,
fap_category="Debug",
)

View File

@@ -5,6 +5,5 @@ App(
entry_point="vibro_test_app",
requires=["gui"],
stack_size=1 * 1024,
order=20,
fap_category="Debug",
)

View File

@@ -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;

View File

@@ -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",
],
)

View File

@@ -7,5 +7,5 @@ App(
requires=["gui"],
stack_size=4 * 1024,
icon="A_FileManager_14",
order=0,
order=10,
)

View File

@@ -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) {

View File

@@ -4,12 +4,14 @@
typedef enum {
ArchiveAppTypeU2f,
ArchiveAppTypeSetting,
ArchiveAppTypeUnknown,
ArchiveAppsTotal,
} ArchiveAppTypeEnum;
static const ArchiveFileTypeEnum app_file_types[] = {
[ArchiveAppTypeU2f] = ArchiveFileTypeU2f,
[ArchiveAppTypeSetting] = ArchiveFileTypeSetting,
[ArchiveAppTypeUnknown] = ArchiveFileTypeUnknown,
};

View File

@@ -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[] = {

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -20,6 +20,7 @@ typedef enum {
ArchiveFileTypeFolder,
ArchiveFileTypeUnknown,
ArchiveFileTypeAppOrJs,
ArchiveFileTypeSetting,
ArchiveFileTypeLoading,
} ArchiveFileTypeEnum;

View File

@@ -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);

View File

@@ -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,

View File

@@ -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;

View File

@@ -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);

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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,
)

View File

@@ -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);

View File

@@ -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,
)

View File

@@ -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;

View File

@@ -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);

View File

@@ -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

View File

@@ -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,
)

View File

@@ -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);

View File

@@ -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>

View File

@@ -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,
)

View File

@@ -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);

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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);

View File

@@ -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);

View File

@@ -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
}
}

View File

@@ -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};

View File

@@ -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;
}
}

View File

@@ -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(

View File

@@ -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"],
)

View File

@@ -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);

View File

@@ -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,
)

View File

@@ -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);

View File

@@ -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,

View File

@@ -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);

View File

@@ -1,5 +1,3 @@
#pragma once
#include <cli/cli.h>
void subghz_on_system_start(void);

View File

@@ -3,6 +3,7 @@ App(
name="Basic services",
apptype=FlipperAppType.METAPACKAGE,
provides=[
"cli_vcp",
"crypto_start",
"rpc_start",
"expansion_start",

View File

@@ -21,5 +21,5 @@ App(
appid="bt_start",
apptype=FlipperAppType.STARTUP,
entry_point="bt_on_system_start",
order=70,
order=40,
)

View File

@@ -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);

View File

@@ -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"],
)

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -1,5 +0,0 @@
#pragma once
#include "cli_i.h"
void cli_commands_init(Cli* cli);

View File

@@ -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

View File

@@ -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);
}

View 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);

View 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,
};

View 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;

View File

@@ -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,
};

View File

@@ -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
}

View 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