1
mirror of https://github.com/DarkFlippers/unleashed-firmware.git synced 2025-12-12 04:34:43 +04:00

Merge remote-tracking branch 'OFW/dev' into dev

This commit is contained in:
MX
2025-04-06 01:54:22 +03:00
135 changed files with 4630 additions and 2352 deletions

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 # Update WiFi devboard firmware with release channel
distenv.PhonyTarget( distenv.PhonyTarget(
"devboard_flash", "devboard_flash",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,7 +5,6 @@ App(
entry_point="file_browser_app", entry_point="file_browser_app",
requires=["gui"], requires=["gui"],
stack_size=2 * 1024, stack_size=2 * 1024,
order=150,
fap_category="Debug", fap_category="Debug",
fap_icon_assets="icons", fap_icon_assets="icons",
) )

View File

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

View File

@@ -11,6 +11,5 @@ App(
"lfrfid_debug", "lfrfid_debug",
], ],
stack_size=1 * 1024, stack_size=1 * 1024,
order=100,
fap_category="Debug", 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", entry_point="locale_test_app",
requires=["gui", "locale"], requires=["gui", "locale"],
stack_size=2 * 1024, stack_size=2 * 1024,
order=70,
fap_category="Debug", fap_category="Debug",
) )

View File

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

View File

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

View File

@@ -1,8 +1,10 @@
#include <furi.h> #include <furi.h>
#include <notification/notification.h> #include <notification/notification.h>
#include <music_worker/music_worker.h> #include <music_worker/music_worker.h>
#include <cli/cli.h>
#include <toolbox/args.h> #include <toolbox/args.h>
#include <toolbox/pipe.h>
#include <toolbox/cli/cli_registry.h>
#include <cli/cli_main_commands.h>
#define TAG "SpeakerDebug" #define TAG "SpeakerDebug"
@@ -19,14 +21,14 @@ typedef struct {
typedef struct { typedef struct {
MusicWorker* music_worker; MusicWorker* music_worker;
FuriMessageQueue* message_queue; FuriMessageQueue* message_queue;
Cli* cli; CliRegistry* cli_registry;
} SpeakerDebugApp; } SpeakerDebugApp;
static SpeakerDebugApp* speaker_app_alloc(void) { static SpeakerDebugApp* speaker_app_alloc(void) {
SpeakerDebugApp* app = (SpeakerDebugApp*)malloc(sizeof(SpeakerDebugApp)); SpeakerDebugApp* app = (SpeakerDebugApp*)malloc(sizeof(SpeakerDebugApp));
app->music_worker = music_worker_alloc(); app->music_worker = music_worker_alloc();
app->message_queue = furi_message_queue_alloc(8, sizeof(SpeakerDebugAppMessage)); 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; return app;
} }
@@ -37,8 +39,8 @@ static void speaker_app_free(SpeakerDebugApp* app) {
free(app); free(app);
} }
static void speaker_app_cli(Cli* cli, FuriString* args, void* context) { static void speaker_app_cli(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(cli); UNUSED(pipe);
SpeakerDebugApp* app = (SpeakerDebugApp*)context; SpeakerDebugApp* app = (SpeakerDebugApp*)context;
SpeakerDebugAppMessage message; SpeakerDebugAppMessage message;
@@ -95,7 +97,8 @@ static void speaker_app_run(SpeakerDebugApp* app, const char* arg) {
return; 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; SpeakerDebugAppMessage message;
FuriStatus status; 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) { int32_t speaker_debug_app(void* arg) {

View File

@@ -6,7 +6,6 @@ App(
entry_point="subghz_test_app", entry_point="subghz_test_app",
requires=["gui"], requires=["gui"],
stack_size=4 * 1024, stack_size=4 * 1024,
order=50,
fap_icon="subghz_test_10px.png", fap_icon="subghz_test_10px.png",
fap_category="Debug", fap_category="Debug",
fap_icon_assets="images", fap_icon_assets="images",

View File

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

View File

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

View File

@@ -4,10 +4,9 @@ App(
entry_point="unit_tests_on_system_start", entry_point="unit_tests_on_system_start",
sources=["unit_tests.c", "test_runner.c", "unit_test_api_table.cpp"], sources=["unit_tests.c", "test_runner.c", "unit_test_api_table.cpp"],
cdefines=["APP_UNIT_TESTS"], cdefines=["APP_UNIT_TESTS"],
requires=["system_settings", "subghz_start"], requires=["system_settings", "cli_subghz"],
provides=["delay_test"], provides=["delay_test"],
resources="resources", resources="resources",
order=100,
) )
App( App(

View File

@@ -2,8 +2,9 @@
#include "tests/test_api.h" #include "tests/test_api.h"
#include <cli/cli.h> #include <toolbox/cli/cli_command.h>
#include <toolbox/path.h> #include <toolbox/path.h>
#include <toolbox/pipe.h>
#include <loader/loader.h> #include <loader/loader.h>
#include <storage/storage.h> #include <storage/storage.h>
#include <notification/notification_messages.h> #include <notification/notification_messages.h>
@@ -25,7 +26,7 @@ struct TestRunner {
NotificationApp* notification; NotificationApp* notification;
// Temporary used things // Temporary used things
Cli* cli; PipeSide* pipe;
FuriString* args; FuriString* args;
// ELF related stuff // ELF related stuff
@@ -38,14 +39,14 @@ struct TestRunner {
int minunit_status; int minunit_status;
}; };
TestRunner* test_runner_alloc(Cli* cli, FuriString* args) { TestRunner* test_runner_alloc(PipeSide* pipe, FuriString* args) {
TestRunner* instance = malloc(sizeof(TestRunner)); TestRunner* instance = malloc(sizeof(TestRunner));
instance->storage = furi_record_open(RECORD_STORAGE); instance->storage = furi_record_open(RECORD_STORAGE);
instance->loader = furi_record_open(RECORD_LOADER); instance->loader = furi_record_open(RECORD_LOADER);
instance->notification = furi_record_open(RECORD_NOTIFICATION); instance->notification = furi_record_open(RECORD_NOTIFICATION);
instance->cli = cli; instance->pipe = pipe;
instance->args = args; instance->args = args;
instance->composite_resolver = composite_api_resolver_alloc(); instance->composite_resolver = composite_api_resolver_alloc();
@@ -147,7 +148,7 @@ static void test_runner_run_internal(TestRunner* instance) {
} }
while(true) { while(true) {
if(cli_cmd_interrupt_received(instance->cli)) { if(cli_is_pipe_broken_or_is_etx_next_char(instance->pipe)) {
break; break;
} }

View File

@@ -1,12 +1,12 @@
#pragma once #pragma once
#include <furi.h> #include <furi.h>
#include <toolbox/pipe.h>
typedef struct TestRunner TestRunner; 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

@@ -25,16 +25,13 @@ MU_TEST(pipe_test_trivial) {
mu_assert_int_eq(PIPE_SIZE - i, pipe_spaces_available(alice)); mu_assert_int_eq(PIPE_SIZE - i, pipe_spaces_available(alice));
mu_assert_int_eq(i, pipe_bytes_available(bob)); mu_assert_int_eq(i, pipe_bytes_available(bob));
if(pipe_send(alice, &i, sizeof(uint8_t), 0) != sizeof(uint8_t)) { if(pipe_spaces_available(alice) == 0) break;
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(PIPE_SIZE - i, pipe_spaces_available(bob));
mu_assert_int_eq(i, pipe_bytes_available(alice)); mu_assert_int_eq(i, pipe_bytes_available(alice));
if(pipe_send(bob, &i, sizeof(uint8_t), 0) != sizeof(uint8_t)) { furi_check(pipe_send(bob, &i, sizeof(uint8_t)) == sizeof(uint8_t));
break;
}
} }
pipe_free(alice); pipe_free(alice);
@@ -43,10 +40,9 @@ MU_TEST(pipe_test_trivial) {
for(uint8_t i = 0;; ++i) { for(uint8_t i = 0;; ++i) {
mu_assert_int_eq(PIPE_SIZE - i, pipe_bytes_available(bob)); mu_assert_int_eq(PIPE_SIZE - i, pipe_bytes_available(bob));
if(pipe_bytes_available(bob) == 0) break;
uint8_t value; uint8_t value;
if(pipe_receive(bob, &value, sizeof(uint8_t), 0) != sizeof(uint8_t)) { furi_check(pipe_receive(bob, &value, sizeof(uint8_t)) == sizeof(uint8_t));
break;
}
mu_assert_int_eq(i, value); mu_assert_int_eq(i, value);
} }
@@ -68,16 +64,15 @@ typedef struct {
static void on_data_arrived(PipeSide* pipe, void* context) { static void on_data_arrived(PipeSide* pipe, void* context) {
AncillaryThreadContext* ctx = context; AncillaryThreadContext* ctx = context;
ctx->flag |= TestFlagDataArrived; ctx->flag |= TestFlagDataArrived;
uint8_t buffer[PIPE_SIZE]; uint8_t input;
size_t size = pipe_receive(pipe, buffer, sizeof(buffer), 0); size_t size = pipe_receive(pipe, &input, sizeof(input));
pipe_send(pipe, buffer, size, 0); pipe_send(pipe, &input, size);
} }
static void on_space_freed(PipeSide* pipe, void* context) { static void on_space_freed(PipeSide* pipe, void* context) {
UNUSED(pipe);
AncillaryThreadContext* ctx = context; AncillaryThreadContext* ctx = context;
ctx->flag |= TestFlagSpaceFreed; ctx->flag |= TestFlagSpaceFreed;
const char* message = "Hi!";
pipe_send(pipe, message, strlen(message), 0);
} }
static void on_became_broken(PipeSide* pipe, void* context) { static void on_became_broken(PipeSide* pipe, void* context) {
@@ -117,22 +112,16 @@ MU_TEST(pipe_test_event_loop) {
furi_thread_start(thread); furi_thread_start(thread);
const char* message = "Hello!"; const char* message = "Hello!";
pipe_send(alice, message, strlen(message), FuriWaitForever); pipe_send(alice, message, strlen(message));
char buffer_1[16]; 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; 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); pipe_free(alice);
furi_thread_join(thread); furi_thread_join(thread);
mu_assert_string_eq(message, buffer_1); mu_assert_string_eq(message, buffer_1);
mu_assert_string_eq(expected_reply, buffer_2);
mu_assert_int_eq( mu_assert_int_eq(
TestFlagDataArrived | TestFlagSpaceFreed | TestFlagBecameBroken, TestFlagDataArrived | TestFlagSpaceFreed | TestFlagBecameBroken,
furi_thread_get_return_code(thread)); furi_thread_get_return_code(thread));

View File

@@ -3,7 +3,6 @@
#include <rpc/rpc.h> #include <rpc/rpc.h>
#include <rpc/rpc_i.h> #include <rpc/rpc_i.h>
#include <cli/cli.h>
#include <storage/storage.h> #include <storage/storage.h>
#include <loader/loader.h> #include <loader/loader.h>
#include <storage/filesystem_api_defines.h> #include <storage/filesystem_api_defines.h>

View File

@@ -521,11 +521,6 @@ MU_TEST(test_storage_data_path) {
// check that appsdata folder exists // check that appsdata folder exists
mu_check(storage_dir_exists(storage, APPS_DATA_PATH)); 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); furi_record_close(RECORD_STORAGE);
} }

View File

@@ -1,21 +1,24 @@
#include <furi.h> #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" #include "test_runner.h"
void unit_tests_cli(Cli* cli, FuriString* args, void* context) { void unit_tests_cli(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(cli);
UNUSED(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_run(test_runner);
test_runner_free(test_runner); test_runner_free(test_runner);
} }
void unit_tests_on_system_start(void) { void unit_tests_on_system_start(void) {
#ifdef SRV_CLI #ifdef SRV_CLI
Cli* cli = furi_record_open(RECORD_CLI); CliRegistry* registry = furi_record_open(RECORD_CLI);
cli_add_command(cli, "unit_tests", CliCommandFlagParallelSafe, unit_tests_cli, NULL); cli_registry_add_command(
registry, "unit_tests", CliCommandFlagParallelSafe, unit_tests_cli, NULL);
furi_record_close(RECORD_CLI); furi_record_close(RECORD_CLI);
#endif #endif
} }

View File

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

View File

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

View File

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

View File

@@ -23,11 +23,6 @@ App(
name="On start hooks", name="On start hooks",
apptype=FlipperAppType.METAPACKAGE, apptype=FlipperAppType.METAPACKAGE,
provides=[ provides=[
"ibutton_start", "cli",
"onewire_start",
"subghz_start",
"infrared_start",
"lfrfid_start",
"nfc_start",
], ],
) )

View File

@@ -44,7 +44,7 @@ static void archive_loader_callback(const void* message, void* context) {
const LoaderEvent* event = message; const LoaderEvent* event = message;
ArchiveApp* archive = (ArchiveApp*)context; ArchiveApp* archive = (ArchiveApp*)context;
if(event->type == LoaderEventTypeApplicationStopped) { if(event->type == LoaderEventTypeNoMoreAppsInQueue) {
view_dispatcher_send_custom_event( view_dispatcher_send_custom_event(
archive->view_dispatcher, ArchiveBrowserEventListRefresh); archive->view_dispatcher, ArchiveBrowserEventListRefresh);
} }

View File

@@ -1,7 +1,6 @@
#include "usb_uart_bridge.h" #include "usb_uart_bridge.h"
#include "usb_cdc.h" #include "usb_cdc.h"
#include <cli/cli_vcp.h> #include <cli/cli_vcp.h>
#include <cli/cli.h>
#include <toolbox/api_lock.h> #include <toolbox/api_lock.h>
#include <furi_hal.h> #include <furi_hal.h>
#include <furi_hal_usb_cdc.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) { static void usb_uart_vcp_init(UsbUartBridge* usb_uart, uint8_t vcp_ch) {
furi_hal_usb_unlock(); furi_hal_usb_unlock();
if(vcp_ch == 0) { if(vcp_ch == 0) {
Cli* cli = furi_record_open(RECORD_CLI); CliVcp* cli_vcp = furi_record_open(RECORD_CLI_VCP);
cli_session_close(cli); cli_vcp_disable(cli_vcp);
furi_record_close(RECORD_CLI); furi_record_close(RECORD_CLI_VCP);
furi_check(furi_hal_usb_set_config(&usb_cdc_single, NULL) == true); furi_check(furi_hal_usb_set_config(&usb_cdc_single, NULL) == true);
} else { } else {
furi_check(furi_hal_usb_set_config(&usb_cdc_dual, NULL) == true); furi_check(furi_hal_usb_set_config(&usb_cdc_dual, NULL) == true);
Cli* cli = furi_record_open(RECORD_CLI); CliVcp* cli_vcp = furi_record_open(RECORD_CLI_VCP);
cli_session_open(cli, &cli_vcp); cli_vcp_enable(cli_vcp);
furi_record_close(RECORD_CLI); furi_record_close(RECORD_CLI_VCP);
} }
furi_hal_cdc_set_callbacks(vcp_ch, (CdcCallbacks*)&cdc_cb, usb_uart); 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); UNUSED(usb_uart);
furi_hal_cdc_set_callbacks(vcp_ch, NULL, NULL); furi_hal_cdc_set_callbacks(vcp_ch, NULL, NULL);
if(vcp_ch != 0) { if(vcp_ch != 0) {
Cli* cli = furi_record_open(RECORD_CLI); CliVcp* cli_vcp = furi_record_open(RECORD_CLI_VCP);
cli_session_close(cli); cli_vcp_disable(cli_vcp);
furi_record_close(RECORD_CLI); furi_record_close(RECORD_CLI_VCP);
} }
} }
@@ -309,9 +308,9 @@ static int32_t usb_uart_worker(void* context) {
furi_hal_usb_unlock(); furi_hal_usb_unlock();
furi_check(furi_hal_usb_set_config(&usb_cdc_single, NULL) == true); furi_check(furi_hal_usb_set_config(&usb_cdc_single, NULL) == true);
Cli* cli = furi_record_open(RECORD_CLI); CliVcp* cli_vcp = furi_record_open(RECORD_CLI_VCP);
cli_session_open(cli, &cli_vcp); cli_vcp_enable(cli_vcp);
furi_record_close(RECORD_CLI); furi_record_close(RECORD_CLI_VCP);
return 0; return 0;
} }

View File

@@ -13,10 +13,10 @@ App(
) )
App( App(
appid="ibutton_start", appid="cli_ikey",
apptype=FlipperAppType.STARTUP,
targets=["f7"], targets=["f7"],
entry_point="ibutton_on_system_start", apptype=FlipperAppType.PLUGIN,
entry_point="cli_ikey_ep",
requires=["cli"],
sources=["ibutton_cli.c"], sources=["ibutton_cli.c"],
order=60,
) )

View File

@@ -1,26 +1,14 @@
#include <furi.h> #include <furi.h>
#include <furi_hal.h> #include <furi_hal.h>
#include <cli/cli.h> #include <cli/cli_main_commands.h>
#include <toolbox/args.h> #include <toolbox/args.h>
#include <toolbox/pipe.h>
#include <ibutton/ibutton_key.h> #include <ibutton/ibutton_key.h>
#include <ibutton/ibutton_worker.h> #include <ibutton/ibutton_worker.h>
#include <ibutton/ibutton_protocols.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) { static void ibutton_cli_print_usage(void) {
printf("Usage:\r\n"); printf("Usage:\r\n");
printf("ikey read\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); 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(); iButtonProtocols* protocols = ibutton_protocols_alloc();
iButtonWorker* worker = ibutton_worker_alloc(protocols); iButtonWorker* worker = ibutton_worker_alloc(protocols);
iButtonKey* key = ibutton_key_alloc(ibutton_protocols_get_max_data_size(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; break;
} }
if(cli_cmd_interrupt_received(cli)) break; if(cli_is_pipe_broken_or_is_etx_next_char(pipe)) break;
} }
ibutton_worker_stop(worker); 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); 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(); iButtonProtocols* protocols = ibutton_protocols_alloc();
iButtonWorker* worker = ibutton_worker_alloc(protocols); iButtonWorker* worker = ibutton_worker_alloc(protocols);
iButtonKey* key = ibutton_key_alloc(ibutton_protocols_get_max_data_size(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); } while(false);
@@ -195,7 +183,7 @@ void ibutton_cli_write(Cli* cli, FuriString* args) {
furi_event_flag_free(write_context.event); 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(); iButtonProtocols* protocols = ibutton_protocols_alloc();
iButtonWorker* worker = ibutton_worker_alloc(protocols); iButtonWorker* worker = ibutton_worker_alloc(protocols);
iButtonKey* key = ibutton_key_alloc(ibutton_protocols_get_max_data_size(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); 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); furi_delay_ms(100);
}; };
@@ -228,8 +216,7 @@ void ibutton_cli_emulate(Cli* cli, FuriString* args) {
ibutton_protocols_free(protocols); ibutton_protocols_free(protocols);
}; };
void ibutton_cli(Cli* cli, FuriString* args, void* context) { static void execute(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(cli);
UNUSED(context); UNUSED(context);
FuriString* cmd; FuriString* cmd;
cmd = furi_string_alloc(); 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) { 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) { } 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) { } else if(furi_string_cmp_str(cmd, "emulate") == 0) {
ibutton_cli_emulate(cli, args); ibutton_cli_emulate(pipe, args);
} else { } else {
ibutton_cli_print_usage(); ibutton_cli_print_usage();
} }
furi_string_free(cmd); furi_string_free(cmd);
} }
CLI_COMMAND_INTERFACE(ikey, execute, CliCommandFlagDefault, 1024, CLI_APPID);

View File

@@ -15,14 +15,14 @@ App(
) )
App( App(
appid="infrared_start", appid="cli_ir",
apptype=FlipperAppType.STARTUP,
targets=["f7"], targets=["f7"],
entry_point="infrared_on_system_start", apptype=FlipperAppType.PLUGIN,
entry_point="cli_ir_ep",
requires=["cli"],
sources=[ sources=[
"infrared_cli.c", "infrared_cli.c",
"infrared_brute_force.c", "infrared_brute_force.c",
"infrared_signal.c", "infrared_signal.c",
], ],
order=20,
) )

View File

@@ -1,11 +1,11 @@
#include <cli/cli.h> #include <cli/cli_main_commands.h>
#include <cli/cli_i.h>
#include <infrared.h> #include <infrared.h>
#include <infrared_worker.h> #include <infrared_worker.h>
#include <furi_hal_infrared.h> #include <furi_hal_infrared.h>
#include <flipper_format.h> #include <flipper_format.h>
#include <toolbox/args.h> #include <toolbox/args.h>
#include <toolbox/strint.h> #include <toolbox/strint.h>
#include <toolbox/pipe.h>
#include <m-dict.h> #include <m-dict.h>
#include "infrared_signal.h" #include "infrared_signal.h"
@@ -19,14 +19,14 @@
DICT_DEF2(dict_signals, FuriString*, FURI_STRING_OPLIST, int, M_DEFAULT_OPLIST) 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_rx(PipeSide* pipe, FuriString* args);
static void infrared_cli_start_ir_tx(Cli* cli, FuriString* args); static void infrared_cli_start_ir_tx(PipeSide* pipe, FuriString* args);
static void infrared_cli_process_decode(Cli* cli, FuriString* args); static void infrared_cli_process_decode(PipeSide* pipe, FuriString* args);
static void infrared_cli_process_universal(Cli* cli, FuriString* args); static void infrared_cli_process_universal(PipeSide* pipe, FuriString* args);
static const struct { static const struct {
const char* cmd; const char* cmd;
void (*process_function)(Cli* cli, FuriString* args); void (*process_function)(PipeSide* pipe, FuriString* args);
} infrared_cli_commands[] = { } infrared_cli_commands[] = {
{.cmd = "rx", .process_function = infrared_cli_start_ir_rx}, {.cmd = "rx", .process_function = infrared_cli_start_ir_rx},
{.cmd = "tx", .process_function = infrared_cli_start_ir_tx}, {.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); furi_assert(received_signal);
char buf[100]; char buf[100];
size_t buf_cnt; size_t buf_cnt;
Cli* cli = (Cli*)context; PipeSide* pipe = (PipeSide*)context;
if(infrared_worker_signal_is_decoded(received_signal)) { if(infrared_worker_signal_is_decoded(received_signal)) {
const InfraredMessage* message = infrared_worker_get_decoded_signal(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), ROUND_UP_TO(infrared_get_protocol_command_length(message->protocol), 4),
message->command, message->command,
message->repeat ? " R" : ""); message->repeat ? " R" : "");
cli_write(cli, (uint8_t*)buf, buf_cnt); pipe_send(pipe, buf, buf_cnt);
} else { } else {
const uint32_t* timings; const uint32_t* timings;
size_t timings_cnt; size_t timings_cnt;
infrared_worker_get_raw_signal(received_signal, &timings, &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); 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) { for(size_t i = 0; i < timings_cnt; ++i) {
buf_cnt = snprintf(buf, sizeof(buf), "%lu ", timings[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"); 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(); infrared_cli_print_universal_remotes();
} }
static void infrared_cli_start_ir_rx(Cli* cli, FuriString* args) { static void infrared_cli_start_ir_rx(PipeSide* pipe, FuriString* args) {
UNUSED(cli);
bool enable_decoding = true; bool enable_decoding = true;
if(!furi_string_empty(args)) { 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(); InfraredWorker* worker = infrared_worker_alloc();
infrared_worker_rx_enable_signal_decoding(worker, enable_decoding); infrared_worker_rx_enable_signal_decoding(worker, enable_decoding);
infrared_worker_rx_start(worker); 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"); 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); 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); return infrared_signal_is_valid(signal);
} }
static void infrared_cli_start_ir_tx(Cli* cli, FuriString* args) { static void infrared_cli_start_ir_tx(PipeSide* pipe, FuriString* args) {
UNUSED(cli); UNUSED(pipe);
const char* str = furi_string_get_cstr(args); const char* str = furi_string_get_cstr(args);
InfraredSignal* signal = infrared_signal_alloc(); InfraredSignal* signal = infrared_signal_alloc();
@@ -335,8 +333,8 @@ static bool infrared_cli_decode_file(FlipperFormat* input_file, FlipperFormat* o
return ret; return ret;
} }
static void infrared_cli_process_decode(Cli* cli, FuriString* args) { static void infrared_cli_process_decode(PipeSide* pipe, FuriString* args) {
UNUSED(cli); UNUSED(pipe);
Storage* storage = furi_record_open(RECORD_STORAGE); Storage* storage = furi_record_open(RECORD_STORAGE);
FlipperFormat* input_file = flipper_format_buffered_file_alloc(storage); FlipperFormat* input_file = flipper_format_buffered_file_alloc(storage);
FlipperFormat* output_file = NULL; FlipperFormat* output_file = NULL;
@@ -455,8 +453,10 @@ static void infrared_cli_list_remote_signals(FuriString* remote_name) {
furi_record_close(RECORD_STORAGE); furi_record_close(RECORD_STORAGE);
} }
static void static void infrared_cli_brute_force_signals(
infrared_cli_brute_force_signals(Cli* cli, FuriString* remote_name, FuriString* signal_name) { PipeSide* pipe,
FuriString* remote_name,
FuriString* signal_name) {
InfraredBruteForce* brute_force = infrared_brute_force_alloc(); InfraredBruteForce* brute_force = infrared_brute_force_alloc();
FuriString* remote_path = furi_string_alloc_printf( FuriString* remote_path = furi_string_alloc_printf(
"%s/%s.ir", INFRARED_ASSETS_FOLDER, furi_string_get_cstr(remote_name)); "%s/%s.ir", INFRARED_ASSETS_FOLDER, furi_string_get_cstr(remote_name));
@@ -490,7 +490,7 @@ static void
while(running) { while(running) {
running = infrared_brute_force_send(brute_force, current_signal); 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)); printf("\r%d%% complete.", (int)((float)current_signal++ / (float)signal_count * 100));
fflush(stdout); fflush(stdout);
@@ -504,7 +504,7 @@ static void
infrared_brute_force_free(brute_force); 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* arg1 = furi_string_alloc();
FuriString* arg2 = 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")) { } else if(furi_string_equal_str(arg1, "list")) {
infrared_cli_list_remote_signals(arg2); infrared_cli_list_remote_signals(arg2);
} else { } else {
infrared_cli_brute_force_signals(cli, arg1, arg2); infrared_cli_brute_force_signals(pipe, arg1, arg2);
} }
furi_string_free(arg1); furi_string_free(arg1);
furi_string_free(arg2); 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); UNUSED(context);
if(furi_hal_infrared_is_busy()) { if(furi_hal_infrared_is_busy()) {
printf("INFRARED is busy. Exiting."); 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)) { if(i < COUNT_OF(infrared_cli_commands)) {
infrared_cli_commands[i].process_function(cli, args); infrared_cli_commands[i].process_function(pipe, args);
} else { } else {
infrared_cli_print_usage(); infrared_cli_print_usage();
} }
furi_string_free(command); furi_string_free(command);
} }
void infrared_on_system_start(void) {
#ifdef SRV_CLI CLI_COMMAND_INTERFACE(ir, execute, CliCommandFlagDefault, 2048, CLI_APPID);
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
}

View File

@@ -13,10 +13,10 @@ App(
) )
App( App(
appid="lfrfid_start", appid="cli_rfid",
targets=["f7"], targets=["f7"],
apptype=FlipperAppType.STARTUP, apptype=FlipperAppType.PLUGIN,
entry_point="lfrfid_on_system_start", entry_point="cli_rfid_ep",
requires=["cli"],
sources=["lfrfid_cli.c"], sources=["lfrfid_cli.c"],
order=50,
) )

View File

@@ -1,11 +1,12 @@
#include <furi.h> #include <furi.h>
#include <furi_hal.h> #include <furi_hal.h>
#include <stdarg.h> #include <stdarg.h>
#include <cli/cli.h> #include <cli/cli_main_commands.h>
#include <lib/toolbox/args.h> #include <lib/toolbox/args.h>
#include <lib/lfrfid/lfrfid_worker.h> #include <lib/lfrfid/lfrfid_worker.h>
#include <storage/storage.h> #include <storage/storage.h>
#include <toolbox/stream/file_stream.h> #include <toolbox/stream/file_stream.h>
#include <toolbox/pipe.h>
#include <toolbox/varint.h> #include <toolbox/varint.h>
@@ -14,15 +15,6 @@
#include <lfrfid/lfrfid_raw_file.h> #include <lfrfid/lfrfid_raw_file.h>
#include <toolbox/pulse_protocols/pulse_glue.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) { static void lfrfid_cli_print_usage(void) {
printf("Usage:\r\n"); printf("Usage:\r\n");
printf("rfid read <optional: normal | indala> - read in ASK/PSK mode\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); 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; FuriString* type_string;
type_string = furi_string_alloc(); type_string = furi_string_alloc();
LFRFIDWorkerReadType type = LFRFIDWorkerReadTypeAuto; 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); 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); 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); ProtocolDict* dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax);
ProtocolId protocol; ProtocolId protocol;
@@ -212,7 +204,7 @@ static void lfrfid_cli_write(Cli* cli, FuriString* args) {
(1 << LFRFIDWorkerWriteProtocolCannotBeWritten) | (1 << LFRFIDWorkerWriteProtocolCannotBeWritten) |
(1 << LFRFIDWorkerWriteFobCannotBeWritten); (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); uint32_t flags = furi_event_flag_wait(event, available_flags, FuriFlagWaitAny, 100);
if(flags != (unsigned)FuriFlagErrorTimeout) { if(flags != (unsigned)FuriFlagErrorTimeout) {
if(FURI_BIT(flags, LFRFIDWorkerWriteOK)) { if(FURI_BIT(flags, LFRFIDWorkerWriteOK)) {
@@ -239,7 +231,7 @@ static void lfrfid_cli_write(Cli* cli, FuriString* args) {
furi_event_flag_free(event); 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); ProtocolDict* dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax);
ProtocolId protocol; ProtocolId protocol;
@@ -254,7 +246,7 @@ static void lfrfid_cli_emulate(Cli* cli, FuriString* args) {
lfrfid_worker_emulate_start(worker, protocol); lfrfid_worker_emulate_start(worker, protocol);
printf("Emulating RFID...\r\nPress Ctrl+C to abort\r\n"); 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); furi_delay_ms(100);
} }
printf("Emulation stopped\r\n"); printf("Emulation stopped\r\n");
@@ -265,8 +257,8 @@ static void lfrfid_cli_emulate(Cli* cli, FuriString* args) {
protocol_dict_free(dict); protocol_dict_free(dict);
} }
static void lfrfid_cli_raw_analyze(Cli* cli, FuriString* args) { static void lfrfid_cli_raw_analyze(PipeSide* pipe, FuriString* args) {
UNUSED(cli); UNUSED(pipe);
FuriString *filepath, *info_string; FuriString *filepath, *info_string;
filepath = furi_string_alloc(); filepath = furi_string_alloc();
info_string = 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); furi_event_flag_set(event, 1 << result);
} }
static void lfrfid_cli_raw_read(Cli* cli, FuriString* args) { static void lfrfid_cli_raw_read(PipeSide* pipe, FuriString* args) {
UNUSED(cli);
FuriString *filepath, *type_string; FuriString *filepath, *type_string;
filepath = furi_string_alloc(); filepath = furi_string_alloc();
type_string = 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) { if(overrun) {
@@ -479,9 +469,7 @@ static void lfrfid_cli_raw_emulate_callback(LFRFIDWorkerEmulateRawResult result,
furi_event_flag_set(event, 1 << result); furi_event_flag_set(event, 1 << result);
} }
static void lfrfid_cli_raw_emulate(Cli* cli, FuriString* args) { static void lfrfid_cli_raw_emulate(PipeSide* pipe, FuriString* args) {
UNUSED(cli);
FuriString* filepath; FuriString* filepath;
filepath = furi_string_alloc(); filepath = furi_string_alloc();
Storage* storage = furi_record_open(RECORD_STORAGE); 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) { if(overrun) {
@@ -548,7 +536,7 @@ static void lfrfid_cli_raw_emulate(Cli* cli, FuriString* args) {
furi_string_free(filepath); 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); UNUSED(context);
FuriString* cmd; FuriString* cmd;
cmd = furi_string_alloc(); 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) { 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) { } 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) { } 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) { } 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) { } 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) { } else if(furi_string_cmp_str(cmd, "raw_analyze") == 0) {
lfrfid_cli_raw_analyze(cli, args); lfrfid_cli_raw_analyze(pipe, args);
} else { } else {
lfrfid_cli_print_usage(); lfrfid_cli_print_usage();
} }
furi_string_free(cmd); furi_string_free(cmd);
} }
CLI_COMMAND_INTERFACE(rfid, execute, CliCommandFlagDefault, 2048, CLI_APPID);

View File

@@ -8,7 +8,6 @@
#include <assets_icons.h> #include <assets_icons.h>
#include <gui/view_dispatcher.h> #include <gui/view_dispatcher.h>
#include <gui/scene_manager.h> #include <gui/scene_manager.h>
#include <cli/cli.h>
#include <notification/notification_messages.h> #include <notification/notification_messages.h>
#include <gui/modules/submenu.h> #include <gui/modules/submenu.h>

View File

@@ -321,10 +321,10 @@ App(
) )
App( App(
appid="nfc_start", appid="cli_nfc",
targets=["f7"], targets=["f7"],
apptype=FlipperAppType.STARTUP, apptype=FlipperAppType.PLUGIN,
entry_point="nfc_on_system_start", entry_point="cli_nfc_ep",
requires=["cli"],
sources=["nfc_cli.c"], sources=["nfc_cli.c"],
order=30,
) )

View File

@@ -10,7 +10,6 @@
#include <assets_icons.h> #include <assets_icons.h>
#include <gui/view_dispatcher.h> #include <gui/view_dispatcher.h>
#include <gui/scene_manager.h> #include <gui/scene_manager.h>
#include <cli/cli.h>
#include <notification/notification_messages.h> #include <notification/notification_messages.h>
#include <gui/modules/submenu.h> #include <gui/modules/submenu.h>

View File

@@ -1,6 +1,6 @@
#include <furi.h> #include <furi.h>
#include <furi_hal.h> #include <furi_hal.h>
#include <cli/cli.h> #include <cli/cli_main_commands.h>
#include <lib/toolbox/args.h> #include <lib/toolbox/args.h>
#include <lib/toolbox/hex.h> #include <lib/toolbox/hex.h>
#include <lib/toolbox/bit_buffer.h> #include <lib/toolbox/bit_buffer.h>
@@ -8,6 +8,7 @@
#include <lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.h> #include <lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.h>
#include <lib/nfc/protocols/iso14443_4b/iso14443_4b_poller.h> #include <lib/nfc/protocols/iso14443_4b/iso14443_4b_poller.h>
#include <lib/nfc/protocols/iso15693_3/iso15693_3_poller.h> #include <lib/nfc/protocols/iso15693_3/iso15693_3_poller.h>
#include <toolbox/pipe.h>
#include <furi_hal_nfc.h> #include <furi_hal_nfc.h>
@@ -38,7 +39,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); UNUSED(args);
// Check if nfc worker is not busy // Check if nfc worker is not busy
if(furi_hal_nfc_is_hal_ready() != FuriHalNfcErrorNone) { if(furi_hal_nfc_is_hal_ready() != FuriHalNfcErrorNone) {
@@ -53,7 +54,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("Field is on. Don't leave device in this mode for too long.\r\n");
printf("Press Ctrl+C to abort\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); furi_delay_ms(50);
} }
@@ -199,7 +200,7 @@ static void nfc_cli_apdu(Cli* cli, FuriString* args) {
furi_string_free(data); furi_string_free(data);
} }
static void nfc_cli(Cli* cli, FuriString* args, void* context) { static void execute(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(context); UNUSED(context);
FuriString* cmd; FuriString* cmd;
cmd = furi_string_alloc(); cmd = furi_string_alloc();
@@ -215,7 +216,7 @@ static void nfc_cli(Cli* cli, FuriString* args, void* context) {
} }
if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) {
if(furi_string_cmp_str(cmd, "field") == 0) { if(furi_string_cmp_str(cmd, "field") == 0) {
nfc_cli_field(cli, args); nfc_cli_field(pipe, args);
break; break;
} }
} }
@@ -226,12 +227,4 @@ static void nfc_cli(Cli* cli, FuriString* args, void* context) {
furi_string_free(cmd); furi_string_free(cmd);
} }
void nfc_on_system_start(void) { CLI_COMMAND_INTERFACE(nfc, execute, CliCommandFlagDefault, 1024, CLI_APPID);
#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
}

View File

@@ -1,6 +1,8 @@
App( App(
appid="onewire_start", appid="cli_onewire",
apptype=FlipperAppType.STARTUP, targets=["f7"],
entry_point="onewire_on_system_start", apptype=FlipperAppType.PLUGIN,
order=60, entry_point="cli_onewire_ep",
requires=["cli"],
sources=["onewire_cli.c"],
) )

View File

@@ -1,32 +1,20 @@
#include <furi.h> #include <furi.h>
#include <furi_hal.h> #include <furi_hal.h>
#include <cli/cli_main_commands.h>
#include <power/power_service/power.h> #include <power/power_service/power.h>
#include <toolbox/cli/cli_command.h>
#include <cli/cli.h>
#include <toolbox/args.h> #include <toolbox/args.h>
#include <one_wire/one_wire_host.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) { static void onewire_cli_print_usage(void) {
printf("Usage:\r\n"); printf("Usage:\r\n");
printf("onewire search\r\n"); printf("onewire search\r\n");
}; };
static void onewire_cli_search(Cli* cli) { static void onewire_cli_search(PipeSide* pipe) {
UNUSED(cli); UNUSED(pipe);
OneWireHost* onewire = onewire_host_alloc(&gpio_ibutton); OneWireHost* onewire = onewire_host_alloc(&gpio_ibutton);
Power* power = furi_record_open(RECORD_POWER); Power* power = furi_record_open(RECORD_POWER);
uint8_t address[8]; uint8_t address[8];
@@ -58,7 +46,7 @@ static void onewire_cli_search(Cli* cli) {
furi_record_close(RECORD_POWER); 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); UNUSED(context);
FuriString* cmd; FuriString* cmd;
cmd = furi_string_alloc(); 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) { if(furi_string_cmp_str(cmd, "search") == 0) {
onewire_cli_search(cli); onewire_cli_search(pipe);
} }
furi_string_free(cmd); furi_string_free(cmd);
} }
CLI_COMMAND_INTERFACE(onewire, execute, CliCommandFlagDefault, 1024, CLI_APPID);

View File

@@ -11,7 +11,6 @@ App(
"dialogs", "dialogs",
], ],
provides=[ provides=[
"subghz_start",
"subghz_load_dangerous_settings", "subghz_load_dangerous_settings",
], ],
icon="A_Sub1ghz_14", icon="A_Sub1ghz_14",
@@ -24,12 +23,12 @@ App(
) )
App( App(
appid="subghz_start", appid="cli_subghz",
targets=["f7"], targets=["f7"],
apptype=FlipperAppType.STARTUP, apptype=FlipperAppType.PLUGIN,
entry_point="subghz_on_system_start", entry_point="cli_subghz_ep",
requires=["subghz"], requires=["cli"],
order=40, sources=["subghz_cli.c", "helpers/subghz_chat.c"],
) )
App( App(

View File

@@ -1,5 +1,6 @@
#include "subghz_chat.h" #include "subghz_chat.h"
#include <lib/subghz/subghz_tx_rx_worker.h> #include <lib/subghz/subghz_tx_rx_worker.h>
#include <toolbox/pipe.h>
#define TAG "SubGhzChat" #define TAG "SubGhzChat"
@@ -14,7 +15,7 @@ struct SubGhzChatWorker {
FuriMessageQueue* event_queue; FuriMessageQueue* event_queue;
uint32_t last_time_rx_data; uint32_t last_time_rx_data;
Cli* cli; PipeSide* pipe;
}; };
/** Worker thread /** Worker thread
@@ -30,7 +31,7 @@ static int32_t subghz_chat_worker_thread(void* context) {
event.event = SubGhzChatEventUserEntrance; event.event = SubGhzChatEventUserEntrance;
furi_message_queue_put(instance->event_queue, &event, 0); furi_message_queue_put(instance->event_queue, &event, 0);
while(instance->worker_running) { 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.event = SubGhzChatEventInputData;
event.c = c; event.c = c;
furi_message_queue_put(instance->event_queue, &event, FuriWaitForever); 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); 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)); SubGhzChatWorker* instance = malloc(sizeof(SubGhzChatWorker));
instance->cli = cli; instance->pipe = pipe;
instance->thread = instance->thread =
furi_thread_alloc_ex("SubGhzChat", 2048, subghz_chat_worker_thread, instance); furi_thread_alloc_ex("SubGhzChat", 2048, subghz_chat_worker_thread, instance);

View File

@@ -1,7 +1,7 @@
#pragma once #pragma once
#include "../subghz_i.h" #include "../subghz_i.h"
#include <lib/subghz/devices/devices.h> #include <lib/subghz/devices/devices.h>
#include <cli/cli.h> #include <toolbox/pipe.h>
typedef struct SubGhzChatWorker SubGhzChatWorker; typedef struct SubGhzChatWorker SubGhzChatWorker;
@@ -19,7 +19,7 @@ typedef struct {
char c; char c;
} SubGhzChatEvent; } SubGhzChatEvent;
SubGhzChatWorker* subghz_chat_worker_alloc(Cli* cli); SubGhzChatWorker* subghz_chat_worker_alloc(PipeSide* pipe);
void subghz_chat_worker_free(SubGhzChatWorker* instance); void subghz_chat_worker_free(SubGhzChatWorker* instance);
bool subghz_chat_worker_start( bool subghz_chat_worker_start(
SubGhzChatWorker* instance, SubGhzChatWorker* instance,

View File

@@ -4,6 +4,8 @@
#include <furi_hal.h> #include <furi_hal.h>
#include <applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.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/subghz_keystore.h>
#include <lib/subghz/receiver.h> #include <lib/subghz/receiver.h>
@@ -16,6 +18,7 @@
#include <lib/toolbox/args.h> #include <lib/toolbox/args.h>
#include <lib/toolbox/strint.h> #include <lib/toolbox/strint.h>
#include <toolbox/pipe.h>
#include "helpers/subghz_chat.h" #include "helpers/subghz_chat.h"
@@ -73,7 +76,7 @@ static SubGhzEnvironment* subghz_cli_environment_init(void) {
return environment; 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); UNUSED(context);
uint32_t frequency = 433920000; uint32_t frequency = 433920000;
@@ -103,7 +106,7 @@ void subghz_cli_command_tx_carrier(Cli* cli, FuriString* args, void* context) {
if(furi_hal_subghz_tx()) { if(furi_hal_subghz_tx()) {
printf("Transmitting at frequency %lu Hz\r\n", frequency); printf("Transmitting at frequency %lu Hz\r\n", frequency);
printf("Press CTRL+C to stop\r\n"); 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); furi_delay_ms(250);
} }
} else { } else {
@@ -116,7 +119,7 @@ void subghz_cli_command_tx_carrier(Cli* cli, FuriString* args, void* context) {
furi_hal_power_suppress_charge_exit(); 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); UNUSED(context);
uint32_t frequency = 433920000; uint32_t frequency = 433920000;
@@ -144,7 +147,7 @@ void subghz_cli_command_rx_carrier(Cli* cli, FuriString* args, void* context) {
furi_hal_subghz_rx(); 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); furi_delay_ms(250);
printf("RSSI: %03.1fdbm\r", (double)furi_hal_subghz_get_rssi()); printf("RSSI: %03.1fdbm\r", (double)furi_hal_subghz_get_rssi());
fflush(stdout); fflush(stdout);
@@ -177,7 +180,7 @@ static const SubGhzDevice* subghz_cli_command_get_device(uint32_t* device_ind) {
return device; 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); UNUSED(context);
uint32_t frequency = 433920000; uint32_t frequency = 433920000;
uint32_t key = 0x0074BADE; uint32_t key = 0x0074BADE;
@@ -247,7 +250,9 @@ void subghz_cli_command_tx(Cli* cli, FuriString* args, void* context) {
furi_hal_power_suppress_charge_enter(); furi_hal_power_suppress_charge_enter();
if(subghz_devices_start_async_tx(device, subghz_transmitter_yield, transmitter)) { 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("."); printf(".");
fflush(stdout); fflush(stdout);
furi_delay_ms(333); furi_delay_ms(333);
@@ -303,7 +308,7 @@ static void subghz_cli_command_rx_callback(
furi_string_free(text); 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); UNUSED(context);
uint32_t frequency = 433920000; uint32_t frequency = 433920000;
uint32_t device_ind = 0; // 0 - CC1101_INT, 1 - CC1101_EXT uint32_t device_ind = 0; // 0 - CC1101_INT, 1 - CC1101_EXT
@@ -359,7 +364,7 @@ void subghz_cli_command_rx(Cli* cli, FuriString* args, void* context) {
frequency, frequency,
device_ind); device_ind);
LevelDuration level_duration; 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( int ret = furi_stream_buffer_receive(
instance->stream, &level_duration, sizeof(LevelDuration), 10); instance->stream, &level_duration, sizeof(LevelDuration), 10);
if(ret == sizeof(LevelDuration)) { if(ret == sizeof(LevelDuration)) {
@@ -392,7 +397,7 @@ void subghz_cli_command_rx(Cli* cli, FuriString* args, void* context) {
free(instance); 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); UNUSED(context);
uint32_t frequency = 433920000; uint32_t frequency = 433920000;
@@ -430,7 +435,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); printf("Listening at %lu. Press CTRL+C to stop\r\n", frequency);
LevelDuration level_duration; LevelDuration level_duration;
size_t counter = 0; 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( int ret = furi_stream_buffer_receive(
instance->stream, &level_duration, sizeof(LevelDuration), 10); instance->stream, &level_duration, sizeof(LevelDuration), 10);
if(ret == 0) { if(ret == 0) {
@@ -466,7 +471,7 @@ void subghz_cli_command_rx_raw(Cli* cli, FuriString* args, void* context) {
free(instance); 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); UNUSED(context);
FuriString* file_name = furi_string_alloc(); FuriString* file_name = furi_string_alloc();
furi_string_set(file_name, EXT_PATH("subghz/test.sub")); furi_string_set(file_name, EXT_PATH("subghz/test.sub"));
@@ -534,7 +539,7 @@ void subghz_cli_command_decode_raw(Cli* cli, FuriString* args, void* context) {
furi_string_get_cstr(file_name)); furi_string_get_cstr(file_name));
LevelDuration level_duration; 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 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); level_duration = subghz_file_encoder_worker_get_level_duration(file_worker_encoder);
if(!level_duration_is_reset(level_duration)) { if(!level_duration_is_reset(level_duration)) {
@@ -579,7 +584,7 @@ static FuriHalSubGhzPreset subghz_cli_get_preset_name(const char* preset_name) {
return preset; 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); UNUSED(context);
FuriString* file_name; FuriString* file_name;
file_name = furi_string_alloc(); file_name = furi_string_alloc();
@@ -783,7 +788,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)) { if(subghz_devices_start_async_tx(device, subghz_transmitter_yield, transmitter)) {
while( while(
!(subghz_devices_is_async_complete_tx(device) || !(subghz_devices_is_async_complete_tx(device) ||
cli_cmd_interrupt_received(cli))) { cli_is_pipe_broken_or_is_etx_next_char(pipe))) {
printf("."); printf(".");
fflush(stdout); fflush(stdout);
furi_delay_ms(333); furi_delay_ms(333);
@@ -797,11 +802,11 @@ void subghz_cli_command_tx_from_file(Cli* cli, FuriString* args, void* context)
if(!strcmp(furi_string_get_cstr(temp_str), "RAW")) { if(!strcmp(furi_string_get_cstr(temp_str), "RAW")) {
subghz_transmitter_stop(transmitter); subghz_transmitter_stop(transmitter);
repeat--; 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); 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"))); (repeat && !strcmp(furi_string_get_cstr(temp_str), "RAW")));
subghz_devices_sleep(device); subghz_devices_sleep(device);
@@ -851,8 +856,8 @@ static void subghz_cli_command_print_usage(void) {
} }
} }
static void subghz_cli_command_encrypt_keeloq(Cli* cli, FuriString* args) { static void subghz_cli_command_encrypt_keeloq(PipeSide* pipe, FuriString* args) {
UNUSED(cli); UNUSED(pipe);
uint8_t iv[16]; uint8_t iv[16];
FuriString* source = furi_string_alloc(); FuriString* source = furi_string_alloc();
@@ -892,8 +897,8 @@ static void subghz_cli_command_encrypt_keeloq(Cli* cli, FuriString* args) {
furi_string_free(source); furi_string_free(source);
} }
static void subghz_cli_command_encrypt_raw(Cli* cli, FuriString* args) { static void subghz_cli_command_encrypt_raw(PipeSide* pipe, FuriString* args) {
UNUSED(cli); UNUSED(pipe);
uint8_t iv[16]; uint8_t iv[16];
FuriString* source = furi_string_alloc(); FuriString* source = furi_string_alloc();
@@ -927,7 +932,7 @@ static void subghz_cli_command_encrypt_raw(Cli* cli, FuriString* args) {
furi_string_free(source); 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 frequency = 433920000;
uint32_t device_ind = 0; // 0 - CC1101_INT, 1 - CC1101_EXT uint32_t device_ind = 0; // 0 - CC1101_INT, 1 - CC1101_EXT
@@ -963,7 +968,7 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args) {
return; 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)) { if(!subghz_chat_worker_start(subghz_chat, device, frequency)) {
printf("Startup error SubGhzChatWorker\r\n"); printf("Startup error SubGhzChatWorker\r\n");
@@ -1000,13 +1005,12 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args) {
chat_event = subghz_chat_worker_get_event_chat(subghz_chat); chat_event = subghz_chat_worker_get_event_chat(subghz_chat);
switch(chat_event.event) { switch(chat_event.event) {
case SubGhzChatEventInputData: case SubGhzChatEventInputData:
if(chat_event.c == CliSymbolAsciiETX) { if(chat_event.c == CliKeyETX) {
printf("\r\n"); printf("\r\n");
chat_event.event = SubGhzChatEventUserExit; chat_event.event = SubGhzChatEventUserExit;
subghz_chat_worker_put_event_chat(subghz_chat, &chat_event); subghz_chat_worker_put_event_chat(subghz_chat, &chat_event);
break; break;
} else if( } else if((chat_event.c == CliKeyBackspace) || (chat_event.c == CliKeyDEL)) {
(chat_event.c == CliSymbolAsciiBackspace) || (chat_event.c == CliSymbolAsciiDel)) {
size_t len = furi_string_utf8_length(input); size_t len = furi_string_utf8_length(input);
if(len > furi_string_utf8_length(name)) { if(len > furi_string_utf8_length(name)) {
printf("%s", "\e[D\e[1P"); printf("%s", "\e[D\e[1P");
@@ -1028,7 +1032,7 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args) {
} }
furi_string_set(input, sysmsg); furi_string_set(input, sysmsg);
} }
} else if(chat_event.c == CliSymbolAsciiCR) { } else if(chat_event.c == CliKeyCR) {
printf("\r\n"); printf("\r\n");
furi_string_push_back(input, '\r'); furi_string_push_back(input, '\r');
furi_string_push_back(input, '\n'); furi_string_push_back(input, '\n');
@@ -1042,7 +1046,7 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args) {
furi_string_printf(input, "%s", furi_string_get_cstr(name)); furi_string_printf(input, "%s", furi_string_get_cstr(name));
printf("%s", furi_string_get_cstr(input)); printf("%s", furi_string_get_cstr(input));
fflush(stdout); fflush(stdout);
} else if(chat_event.c == CliSymbolAsciiLF) { } else if(chat_event.c == CliKeyLF) {
//cut out the symbol \n //cut out the symbol \n
} else { } else {
putc(chat_event.c, stdout); putc(chat_event.c, stdout);
@@ -1096,7 +1100,7 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args) {
break; break;
} }
} }
if(!cli_is_connected(cli)) { if(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) {
printf("\r\n"); printf("\r\n");
chat_event.event = SubGhzChatEventUserExit; chat_event.event = SubGhzChatEventUserExit;
subghz_chat_worker_put_event_chat(subghz_chat, &chat_event); subghz_chat_worker_put_event_chat(subghz_chat, &chat_event);
@@ -1121,8 +1125,9 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args) {
printf("\r\nExit chat\r\n"); 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 = furi_string_alloc(); FuriString* cmd;
cmd = furi_string_alloc();
do { do {
if(!args_read_string_and_trim(args, cmd)) { if(!args_read_string_and_trim(args, cmd)) {
@@ -1131,53 +1136,53 @@ static void subghz_cli_command(Cli* cli, FuriString* args, void* context) {
} }
if(furi_string_cmp_str(cmd, "chat") == 0) { if(furi_string_cmp_str(cmd, "chat") == 0) {
subghz_cli_command_chat(cli, args); subghz_cli_command_chat(pipe, args);
break; break;
} }
if(furi_string_cmp_str(cmd, "tx") == 0) { if(furi_string_cmp_str(cmd, "tx") == 0) {
subghz_cli_command_tx(cli, args, context); subghz_cli_command_tx(pipe, args, context);
break; break;
} }
if(furi_string_cmp_str(cmd, "rx") == 0) { if(furi_string_cmp_str(cmd, "rx") == 0) {
subghz_cli_command_rx(cli, args, context); subghz_cli_command_rx(pipe, args, context);
break; break;
} }
if(furi_string_cmp_str(cmd, "rx_raw") == 0) { 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; break;
} }
if(furi_string_cmp_str(cmd, "decode_raw") == 0) { 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; break;
} }
if(furi_string_cmp_str(cmd, "tx_from_file") == 0) { 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; break;
} }
if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) {
if(furi_string_cmp_str(cmd, "encrypt_keeloq") == 0) { if(furi_string_cmp_str(cmd, "encrypt_keeloq") == 0) {
subghz_cli_command_encrypt_keeloq(cli, args); subghz_cli_command_encrypt_keeloq(pipe, args);
break; break;
} }
if(furi_string_cmp_str(cmd, "encrypt_raw") == 0) { if(furi_string_cmp_str(cmd, "encrypt_raw") == 0) {
subghz_cli_command_encrypt_raw(cli, args); subghz_cli_command_encrypt_raw(pipe, args);
break; break;
} }
if(furi_string_cmp_str(cmd, "tx_carrier") == 0) { 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; break;
} }
if(furi_string_cmp_str(cmd, "rx_carrier") == 0) { 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; break;
} }
} }
@@ -1188,14 +1193,4 @@ static void subghz_cli_command(Cli* cli, FuriString* args, void* context) {
furi_string_free(cmd); furi_string_free(cmd);
} }
void subghz_on_system_start(void) { CLI_COMMAND_INTERFACE(subghz, execute, CliCommandFlagDefault, 2048, CLI_APPID);
#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
}

View File

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

View File

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

View File

@@ -1,15 +1,17 @@
#include <furi.h> #include <furi.h>
#include <furi_hal.h> #include <furi_hal.h>
#include <cli/cli.h>
#include <lib/toolbox/args.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 <ble/ble.h>
#include "bt_settings.h" #include "bt_settings.h"
#include "bt_service/bt.h" #include "bt_service/bt.h"
#include <profiles/serial_profile.h> #include <profiles/serial_profile.h>
static void bt_cli_command_hci_info(Cli* cli, FuriString* args, void* context) { static void bt_cli_command_hci_info(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(cli); UNUSED(pipe);
UNUSED(args); UNUSED(args);
UNUSED(context); UNUSED(context);
FuriString* buffer; FuriString* buffer;
@@ -19,7 +21,7 @@ static void bt_cli_command_hci_info(Cli* cli, FuriString* args, void* context) {
furi_string_free(buffer); 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); UNUSED(context);
int channel = 0; int channel = 0;
int power = 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"); printf("Press CTRL+C to stop\r\n");
furi_hal_bt_start_tone_tx(channel, 0x19 + power); 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_delay_ms(250);
} }
furi_hal_bt_stop_tone_tx(); 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); } 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); UNUSED(context);
int channel = 0; 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); 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); furi_delay_ms(250);
printf("RSSI: %6.1f dB\r", (double)furi_hal_bt_get_rssi()); printf("RSSI: %6.1f dB\r", (double)furi_hal_bt_get_rssi());
fflush(stdout); fflush(stdout);
@@ -82,7 +84,7 @@ static void bt_cli_command_carrier_rx(Cli* cli, FuriString* args, void* context)
} while(false); } 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); UNUSED(context);
int channel = 0; int channel = 0;
int pattern = 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"); printf("Press CTRL+C to stop\r\n");
furi_hal_bt_start_packet_tx(channel, pattern, datarate); 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_delay_ms(250);
} }
furi_hal_bt_stop_packet_test(); 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); } 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); UNUSED(context);
int channel = 0; int channel = 0;
int datarate = 1; 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"); printf("Press CTRL+C to stop\r\n");
furi_hal_bt_start_packet_rx(channel, datarate); 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); furi_delay_ms(250);
printf("RSSI: %03.1f dB\r", (double)furi_hal_bt_get_rssi()); printf("RSSI: %03.1f dB\r", (double)furi_hal_bt_get_rssi());
fflush(stdout); 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); UNUSED(context);
furi_record_open(RECORD_BT); furi_record_open(RECORD_BT);
@@ -194,24 +196,24 @@ static void bt_cli(Cli* cli, FuriString* args, void* context) {
break; break;
} }
if(furi_string_cmp_str(cmd, "hci_info") == 0) { 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; break;
} }
if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug) && furi_hal_bt_is_testing_supported()) { if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug) && furi_hal_bt_is_testing_supported()) {
if(furi_string_cmp_str(cmd, "tx_carrier") == 0) { 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; break;
} }
if(furi_string_cmp_str(cmd, "rx_carrier") == 0) { 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; break;
} }
if(furi_string_cmp_str(cmd, "tx_packet") == 0) { 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; break;
} }
if(furi_string_cmp_str(cmd, "rx_packet") == 0) { 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; break;
} }
} }
@@ -229,8 +231,8 @@ static void bt_cli(Cli* cli, FuriString* args, void* context) {
void bt_on_system_start(void) { void bt_on_system_start(void) {
#ifdef SRV_CLI #ifdef SRV_CLI
Cli* cli = furi_record_open(RECORD_CLI); CliRegistry* registry = furi_record_open(RECORD_CLI);
cli_add_command(cli, RECORD_BT, CliCommandFlagDefault, bt_cli, NULL); cli_registry_add_command(registry, "bt", CliCommandFlagDefault, bt_cli, NULL);
furi_record_close(RECORD_CLI); furi_record_close(RECORD_CLI);
#else #else
UNUSED(bt_cli); UNUSED(bt_cli);

View File

@@ -1,10 +1,51 @@
App( App(
appid="cli", appid="cli",
name="CliSrv", apptype=FlipperAppType.STARTUP,
apptype=FlipperAppType.SERVICE, entry_point="cli_on_system_start",
entry_point="cli_srv",
cdefines=["SRV_CLI"], cdefines=["SRV_CLI"],
stack_size=4 * 1024, sources=[
order=30, "cli_command_gpio.c",
sdk_headers=["cli.h", "cli_vcp.h"], "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=40,
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, const 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, const 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.h>
#include <furi_hal.h> #include <furi_hal.h>
#include <lib/toolbox/args.h> #include <lib/toolbox/args.h>
#include <toolbox/pipe.h>
#include <toolbox/cli/cli_command.h>
void cli_command_gpio_print_usage(void) { void cli_command_gpio_print_usage(void) {
printf("Usage:\r\n"); printf("Usage:\r\n");
@@ -70,8 +72,8 @@ static GpioParseReturn gpio_command_parse(FuriString* args, size_t* pin_num, uin
return ret; return ret;
} }
void cli_command_gpio_mode(Cli* cli, FuriString* args, void* context) { void cli_command_gpio_mode(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(cli); UNUSED(pipe);
UNUSED(context); UNUSED(context);
size_t num = 0; 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 if(gpio_pins[num].debug) { //-V779
printf( printf(
"Changing this pin mode may damage hardware. Are you sure you want to continue? (y/n)?\r\n"); "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') { if(c != 'y' && c != 'Y') {
printf("Cancelled.\r\n"); printf("Cancelled.\r\n");
return; 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) { void cli_command_gpio_read(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(cli); UNUSED(pipe);
UNUSED(context); UNUSED(context);
size_t num = 0; 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); 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); UNUSED(context);
size_t num = 0; size_t num = 0;
@@ -159,7 +162,7 @@ void cli_command_gpio_set(Cli* cli, FuriString* args, void* context) {
if(gpio_pins[num].debug) { if(gpio_pins[num].debug) {
printf( printf(
"Setting this pin may damage hardware. Are you sure you want to continue? (y/n)?\r\n"); "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') { if(c != 'y' && c != 'Y') {
printf("Cancelled.\r\n"); printf("Cancelled.\r\n");
return; 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); 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; FuriString* cmd;
cmd = furi_string_alloc(); 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) { if(furi_string_cmp_str(cmd, "mode") == 0) {
cli_command_gpio_mode(cli, args, context); cli_command_gpio_mode(pipe, args, context);
break; break;
} }
if(furi_string_cmp_str(cmd, "set") == 0) { if(furi_string_cmp_str(cmd, "set") == 0) {
cli_command_gpio_set(cli, args, context); cli_command_gpio_set(pipe, args, context);
break; break;
} }
if(furi_string_cmp_str(cmd, "read") == 0) { if(furi_string_cmp_str(cmd, "read") == 0) {
cli_command_gpio_read(cli, args, context); cli_command_gpio_read(pipe, args, context);
break; break;
} }

View File

@@ -1,5 +1,5 @@
#pragma once #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;
const 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 "cli_command_gpio.h"
#include <toolbox/cli/cli_ansi.h>
#include <core/thread.h> #include <core/thread.h>
#include <furi_hal.h> #include <furi_hal.h>
@@ -11,6 +12,7 @@
#include <loader/loader.h> #include <loader/loader.h>
#include <lib/toolbox/args.h> #include <lib/toolbox/args.h>
#include <lib/toolbox/strint.h> #include <lib/toolbox/strint.h>
#include <toolbox/pipe.h>
// Close to ISO, `date +'%Y-%m-%d %H:%M:%S %u'` // Close to ISO, `date +'%Y-%m-%d %H:%M:%S %u'`
#define CLI_DATE_FORMAT "%.4d-%.2d-%.2d %.2d:%.2d:%.2d %d" #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 args The arguments
* @param context The context * @param context The context
*/ */
void cli_command_info(Cli* cli, FuriString* args, void* context) { void cli_command_info(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(cli); UNUSED(pipe);
if(context) { if(context) {
furi_hal_info_get(cli_command_info_callback, '_', NULL); 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) { void cli_command_uptime(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(args); UNUSED(pipe);
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);
UNUSED(args); UNUSED(args);
UNUSED(context); UNUSED(context);
uint32_t uptime = furi_get_tick() / furi_kernel_get_tick_frequency(); uint32_t uptime = furi_get_tick() / furi_kernel_get_tick_frequency();
printf("Uptime: %luh%lum%lus", uptime / 60 / 60, uptime / 60 % 60, uptime % 60); printf("Uptime: %luh%lum%lus", uptime / 60 / 60, uptime / 60 % 60, uptime % 60);
} }
void cli_command_date(Cli* cli, FuriString* args, void* context) { void cli_command_date(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(cli); UNUSED(pipe);
UNUSED(context); UNUSED(context);
DateTime datetime = {0}; DateTime datetime = {0};
@@ -170,21 +132,12 @@ void cli_command_date(Cli* cli, FuriString* args, void* context) {
} }
} }
void cli_command_src(Cli* cli, FuriString* args, void* context) {
// Quality of life feature for people exploring CLI on lab.flipper.net/cli
// By Yousef AK
UNUSED(cli);
UNUSED(args);
UNUSED(context);
printf("https://github.com/DarkFlippers/unleashed-firmware");
}
#define CLI_COMMAND_LOG_RING_SIZE 2048 #define CLI_COMMAND_LOG_RING_SIZE 2048
#define CLI_COMMAND_LOG_BUFFER_SIZE 64 #define CLI_COMMAND_LOG_BUFFER_SIZE 64
void cli_command_log_tx_callback(const uint8_t* buffer, size_t size, void* context) { 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) { bool cli_command_log_level_set_from_string(FuriString* level) {
@@ -206,16 +159,13 @@ bool cli_command_log_level_set_from_string(FuriString* level) {
return false; return false;
} }
void cli_command_log(Cli* cli, FuriString* args, void* context) { void cli_command_log(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(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(); FuriLogLevel previous_level = furi_log_get_level();
bool restore_log_level = false; bool restore_log_level = false;
if(furi_string_size(args) > 0) { if(furi_string_size(args) > 0) {
if(!cli_command_log_level_set_from_string(args)) { if(!cli_command_log_level_set_from_string(args)) {
furi_stream_buffer_free(ring);
return; return;
} }
restore_log_level = true; restore_log_level = true;
@@ -227,16 +177,15 @@ void cli_command_log(Cli* cli, FuriString* args, void* context) {
FuriLogHandler log_handler = { FuriLogHandler log_handler = {
.callback = cli_command_log_tx_callback, .callback = cli_command_log_tx_callback,
.context = ring, .context = pipe,
}; };
furi_log_add_handler(log_handler); furi_log_add_handler(log_handler);
printf("Use <log ?> to list available log levels\r\n"); printf("Use <log ?> to list available log levels\r\n");
printf("Press CTRL+C to stop...\r\n"); 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)) {
size_t ret = furi_stream_buffer_receive(ring, buffer, CLI_COMMAND_LOG_BUFFER_SIZE, 50); furi_delay_ms(100);
cli_write(cli, buffer, ret);
} }
furi_log_remove_handler(log_handler); furi_log_remove_handler(log_handler);
@@ -245,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 // There will be strange behaviour if log level is set from settings while log command is running
furi_log_set_level(previous_level); furi_log_set_level(previous_level);
} }
furi_stream_buffer_free(ring);
} }
void cli_command_sysctl_debug(Cli* cli, FuriString* args, void* context) { void cli_command_sysctl_debug(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(cli); UNUSED(pipe);
UNUSED(context); UNUSED(context);
if(!furi_string_cmp(args, "0")) { if(!furi_string_cmp(args, "0")) {
furi_hal_rtc_reset_flag(FuriHalRtcFlagDebug); furi_hal_rtc_reset_flag(FuriHalRtcFlagDebug);
@@ -263,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) { void cli_command_sysctl_heap_track(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(cli); UNUSED(pipe);
UNUSED(context); UNUSED(context);
if(!furi_string_cmp(args, "none")) { if(!furi_string_cmp(args, "none")) {
furi_hal_rtc_set_heap_track_mode(FuriHalRtcHeapTrackModeNone); furi_hal_rtc_set_heap_track_mode(FuriHalRtcHeapTrackModeNone);
@@ -298,7 +245,7 @@ void cli_command_sysctl_print_usage(void) {
#endif #endif
} }
void cli_command_sysctl(Cli* cli, FuriString* args, void* context) { void cli_command_sysctl(PipeSide* pipe, FuriString* args, void* context) {
FuriString* cmd; FuriString* cmd;
cmd = furi_string_alloc(); cmd = furi_string_alloc();
@@ -309,12 +256,12 @@ void cli_command_sysctl(Cli* cli, FuriString* args, void* context) {
} }
if(furi_string_cmp_str(cmd, "debug") == 0) { if(furi_string_cmp_str(cmd, "debug") == 0) {
cli_command_sysctl_debug(cli, args, context); cli_command_sysctl_debug(pipe, args, context);
break; break;
} }
if(furi_string_cmp_str(cmd, "heap_track") == 0) { 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; break;
} }
@@ -324,8 +271,8 @@ void cli_command_sysctl(Cli* cli, FuriString* args, void* context) {
furi_string_free(cmd); furi_string_free(cmd);
} }
void cli_command_vibro(Cli* cli, FuriString* args, void* context) { void cli_command_vibro(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(cli); UNUSED(pipe);
UNUSED(context); UNUSED(context);
if(!furi_string_cmp(args, "0")) { if(!furi_string_cmp(args, "0")) {
@@ -351,8 +298,8 @@ void cli_command_vibro(Cli* cli, FuriString* args, void* context) {
} }
} }
void cli_command_led(Cli* cli, FuriString* args, void* context) { void cli_command_led(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(cli); UNUSED(pipe);
UNUSED(context); UNUSED(context);
// Get first word as light name // Get first word as light name
NotificationMessage notification_led_message; NotificationMessage notification_led_message;
@@ -406,23 +353,23 @@ void cli_command_led(Cli* cli, FuriString* args, void* context) {
furi_record_close(RECORD_NOTIFICATION); furi_record_close(RECORD_NOTIFICATION);
} }
static void cli_command_top(Cli* cli, FuriString* args, void* context) { static void cli_command_top(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(cli);
UNUSED(context); UNUSED(context);
int interval = 1000; int interval = 1000;
args_read_int_and_trim(args, &interval); args_read_int_and_trim(args, &interval);
FuriThreadList* thread_list = furi_thread_list_alloc(); 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(); uint32_t tick = furi_get_tick();
furi_thread_enumerate(thread_list); 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(); uint32_t uptime = tick / furi_kernel_get_tick_frequency();
printf( 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), furi_thread_list_size(thread_list),
(double)furi_thread_list_get_isr_time(thread_list), (double)furi_thread_list_get_isr_time(thread_list),
uptime / 60 / 60, uptime / 60 / 60,
@@ -430,14 +377,16 @@ static void cli_command_top(Cli* cli, FuriString* args, void* context) {
uptime % 60); uptime % 60);
printf( 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_total_heap(),
memmgr_get_free_heap(), memmgr_get_free_heap(),
memmgr_get_minimum_free_heap(), memmgr_get_minimum_free_heap(),
memmgr_heap_get_max_free_block()); memmgr_heap_get_max_free_block());
printf( 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", "AppID",
"Name", "Name",
"State", "State",
@@ -446,12 +395,13 @@ static void cli_command_top(Cli* cli, FuriString* args, void* context) {
"Stack", "Stack",
"Stack Min", "Stack Min",
"Heap", "Heap",
"CPU"); "%CPU");
for(size_t i = 0; i < furi_thread_list_size(thread_list); i++) { for(size_t i = 0; i < furi_thread_list_size(thread_list); i++) {
const FuriThreadListItem* item = furi_thread_list_get_at(thread_list, i); const FuriThreadListItem* item = furi_thread_list_get_at(thread_list, i);
printf( 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->app_id,
item->name, item->name,
item->state, item->state,
@@ -463,6 +413,9 @@ static void cli_command_top(Cli* cli, FuriString* args, void* context) {
(double)item->cpu); (double)item->cpu);
} }
printf(ANSI_ERASE_DISPLAY(ANSI_ERASE_FROM_CURSOR_TO_END));
fflush(stdout);
if(interval > 0) { if(interval > 0) {
furi_delay_ms(interval); furi_delay_ms(interval);
} else { } else {
@@ -472,8 +425,8 @@ static void cli_command_top(Cli* cli, FuriString* args, void* context) {
furi_thread_list_free(thread_list); furi_thread_list_free(thread_list);
} }
void cli_command_free(Cli* cli, FuriString* args, void* context) { void cli_command_free(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(cli); UNUSED(pipe);
UNUSED(args); UNUSED(args);
UNUSED(context); UNUSED(context);
@@ -486,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()); printf("Maximum pool block: %zu\r\n", memmgr_pool_get_max_block());
} }
void cli_command_free_blocks(Cli* cli, FuriString* args, void* context) { void cli_command_free_blocks(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(cli); UNUSED(pipe);
UNUSED(args); UNUSED(args);
UNUSED(context); UNUSED(context);
memmgr_heap_printf_free_blocks(); memmgr_heap_printf_free_blocks();
} }
void cli_command_i2c(Cli* cli, FuriString* args, void* context) { void cli_command_i2c(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(cli); UNUSED(pipe);
UNUSED(args); UNUSED(args);
UNUSED(context); UNUSED(context);
@@ -517,26 +470,53 @@ void cli_command_i2c(Cli* cli, FuriString* args, void* context) {
furi_hal_i2c_release(&furi_hal_i2c_handle_external); 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); * Echoes any bytes it receives except ASCII ETX (0x03, Ctrl+C)
cli_add_command(cli, "info", CliCommandFlagParallelSafe, cli_command_info, NULL); */
cli_add_command(cli, "device_info", CliCommandFlagParallelSafe, cli_command_info, (void*)true); void cli_command_echo(PipeSide* pipe, FuriString* args, void* context) {
cli_add_command(cli, "source", CliCommandFlagParallelSafe, cli_command_src, NULL); UNUSED(args);
UNUSED(context);
cli_add_command(cli, "?", CliCommandFlagParallelSafe, cli_command_help, NULL); uint8_t buffer[256];
cli_add_command(cli, "help", CliCommandFlagParallelSafe, cli_command_help, NULL);
cli_add_command(cli, "uptime", CliCommandFlagDefault, cli_command_uptime, NULL); while(true) {
cli_add_command(cli, "date", CliCommandFlagParallelSafe, cli_command_date, NULL); size_t to_read = CLAMP(pipe_bytes_available(pipe), sizeof(buffer), 1UL);
cli_add_command(cli, "log", CliCommandFlagParallelSafe, cli_command_log, NULL); size_t read = pipe_receive(pipe, buffer, to_read);
cli_add_command(cli, "l", CliCommandFlagParallelSafe, cli_command_log, NULL); if(read < to_read) break;
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);
cli_add_command(cli, "vibro", CliCommandFlagDefault, cli_command_vibro, NULL); if(memchr(buffer, CliKeyETX, read)) break;
cli_add_command(cli, "led", CliCommandFlagDefault, cli_command_led, NULL);
cli_add_command(cli, "gpio", CliCommandFlagDefault, cli_command_gpio, NULL); size_t written = pipe_send(pipe, buffer, read);
cli_add_command(cli, "i2c", CliCommandFlagDefault, cli_command_i2c, NULL); 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_usb_cdc.h>
#include <furi_hal.h> #include <furi_hal.h>
#include <furi.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 TAG "CliVcp"
#define USB_CDC_PKT_LEN CDC_DATA_SZ #define USB_CDC_PKT_LEN CDC_DATA_SZ
#define VCP_RX_BUF_SIZE (USB_CDC_PKT_LEN * 3) #define VCP_BUF_SIZE (USB_CDC_PKT_LEN * 3)
#define VCP_TX_BUF_SIZE (USB_CDC_PKT_LEN * 3)
#define VCP_IF_NUM 0 #define VCP_IF_NUM 0
#define VCP_MESSAGE_Q_LEN 8
#ifdef CLI_VCP_DEBUG #ifdef CLI_VCP_TRACE
#define VCP_DEBUG(...) FURI_LOG_D(TAG, __VA_ARGS__) #define VCP_TRACE(...) FURI_LOG_T(__VA_ARGS__)
#else #else
#define VCP_DEBUG(...) #define VCP_TRACE(...)
#endif #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 { typedef struct {
FuriThread* thread; enum {
CliVcpMessageTypeEnable,
CliVcpMessageTypeDisable,
} type;
union {};
} CliVcpMessage;
FuriStreamBuffer* tx_stream; typedef enum {
FuriStreamBuffer* rx_stream; CliVcpInternalEventConnected = (1 << 0),
CliVcpInternalEventDisconnected = (1 << 1),
CliVcpInternalEventTxDone = (1 << 2),
CliVcpInternalEventRx = (1 << 3),
} CliVcpInternalEvent;
volatile bool connected; #define CliVcpInternalEventAll \
volatile bool running; (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]; bool is_enabled, is_connected;
} CliVcp; FuriHalUsbInterface* previous_interface;
static int32_t vcp_worker(void* context); PipeSide* own_pipe;
static void vcp_on_cdc_tx_complete(void* context); PipeSide* shell_pipe;
static void vcp_on_cdc_rx(void* context); bool is_currently_transmitting;
static void vcp_state_callback(void* context, uint8_t state); size_t previous_tx_length;
static void vcp_on_cdc_control_line(void* context, uint8_t state);
static CdcCallbacks cdc_cb = { CliRegistry* main_registry;
vcp_on_cdc_tx_complete, CliShell* shell;
vcp_on_cdc_rx,
vcp_state_callback,
vcp_on_cdc_control_line,
NULL,
}; };
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) { uint8_t buf[USB_CDC_PKT_LEN];
if(vcp == NULL) { size_t to_receive_from_pipe = MIN(sizeof(buf), pipe_bytes_available(cli_vcp->own_pipe));
vcp = malloc(sizeof(CliVcp)); size_t length = pipe_receive(cli_vcp->own_pipe, buf, to_receive_from_pipe);
vcp->tx_stream = furi_stream_buffer_alloc(VCP_TX_BUF_SIZE, 1); if(length > 0 || cli_vcp->previous_tx_length == USB_CDC_PKT_LEN) {
vcp->rx_stream = furi_stream_buffer_alloc(VCP_RX_BUF_SIZE, 1); 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); cli_vcp->previous_tx_length = length;
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");
} }
static void cli_vcp_deinit(void) { /**
furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtStop); * Called in the following cases:
furi_thread_join(vcp->thread); * - new data arrived at the endpoint;
furi_thread_free(vcp->thread); * - data was read out of the pipe.
vcp->thread = NULL; */
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); // CDC callbacks
bool tx_idle = true; // =============
size_t missed_rx = 0;
uint8_t last_tx_pkt_len = 0;
// Switch USB to VCP mode (if it is not set yet) static void cli_vcp_signal_internal_event(CliVcp* cli_vcp, CliVcpInternalEvent event) {
vcp->usb_if_prev = furi_hal_usb_get_config(); furi_thread_flags_set(cli_vcp->thread_id, event);
if((vcp->usb_if_prev != &usb_cdc_single) && (vcp->usb_if_prev != &usb_cdc_dual)) { }
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_usb_set_config(&usb_cdc_single, NULL);
} furi_hal_cdc_set_callbacks(VCP_IF_NUM, &cdc_callbacks, cli_vcp);
furi_hal_cdc_set_callbacks(VCP_IF_NUM, &cdc_cb, NULL); break;
FURI_LOG_D(TAG, "Start"); case CliVcpMessageTypeDisable:
vcp->running = true; if(!cli_vcp->is_enabled) return;
FURI_LOG_D(TAG, "Disabling");
cli_vcp->is_enabled = false;
while(1) { // restore usb mode
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); furi_hal_cdc_set_callbacks(VCP_IF_NUM, NULL, NULL);
// Restore previous USB mode (if it was set during init) furi_hal_usb_set_config(cli_vcp->previous_interface, NULL);
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; break;
} }
} }
FURI_LOG_D(TAG, "End");
/**
* 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(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; return 0;
} }
static size_t cli_vcp_rx(uint8_t* buffer, size_t size, uint32_t timeout) { CliVcp* cli_vcp = cli_vcp_alloc();
furi_assert(vcp); furi_record_create(RECORD_CLI_VCP, cli_vcp);
furi_assert(buffer); furi_event_loop_run(cli_vcp->event_loop);
if(vcp->running == false) {
return 0; return 0;
} }
VCP_DEBUG("rx %u start", size); // ==========
// Public API
// ==========
size_t rx_cnt = 0; void cli_vcp_enable(CliVcp* cli_vcp) {
CliVcpMessage message = {
while(size > 0) { .type = CliVcpMessageTypeEnable,
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;
}
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);
}
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);
}
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;
}
const 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,
}; };
furi_message_queue_put(cli_vcp->message_queue, &message, FuriWaitForever);
}
void cli_vcp_disable(CliVcp* cli_vcp) {
CliVcpMessage message = {
.type = CliVcpMessageTypeDisable,
};
furi_message_queue_put(cli_vcp->message_queue, &message, FuriWaitForever);
}

View File

@@ -9,9 +9,12 @@
extern "C" { extern "C" {
#endif #endif
typedef struct CliSession CliSession; #define RECORD_CLI_VCP "cli_vcp"
extern const CliSession cli_vcp; typedef struct CliVcp CliVcp;
void cli_vcp_enable(CliVcp* cli_vcp);
void cli_vcp_disable(CliVcp* cli_vcp);
#ifdef __cplusplus #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);

View File

@@ -0,0 +1,160 @@
#include "../cli_main_commands.h"
#include <toolbox/cli/cli_ansi.h>
#include <toolbox/version.h>
#include <furi_hal.h>
#include <furi_hal_info.h>
#include <FreeRTOS.h>
#include <FreeRTOS-Kernel/include/task.h>
static void execute(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(pipe);
UNUSED(args);
UNUSED(context);
static const char* const neofetch_logo[] = {
" _.-------.._ -,",
" .-\"```\"--..,,_/ /`-, -, \\ ",
" .:\" /:/ /'\\ \\ ,_..., `. | |",
" / ,----/:/ /`\\ _\\~`_-\"` _;",
" ' / /`\"\"\"'\\ \\ \\.~`_-' ,-\"'/ ",
" | | | 0 | | .-' ,/` /",
" | ,..\\ \\ ,.-\"` ,/` /",
"; : `/`\"\"\\` ,/--==,/-----,",
"| `-...| -.___-Z:_______J...---;",
": ` _-'",
};
#define NEOFETCH_COLOR ANSI_FLIPPER_BRAND_ORANGE
// Determine logo parameters
size_t logo_height = COUNT_OF(neofetch_logo), logo_width = 0;
for(size_t i = 0; i < logo_height; i++)
logo_width = MAX(logo_width, strlen(neofetch_logo[i]));
logo_width += 4; // space between logo and info
// Format hostname delimiter
const size_t size_of_hostname = 4 + strlen(furi_hal_version_get_name_ptr());
char delimiter[64];
memset(delimiter, '-', size_of_hostname);
delimiter[size_of_hostname] = '\0';
// Get heap info
size_t heap_total = memmgr_get_total_heap();
size_t heap_used = heap_total - memmgr_get_free_heap();
uint16_t heap_percent = (100 * heap_used) / heap_total;
// Get storage info
Storage* storage = furi_record_open(RECORD_STORAGE);
uint64_t ext_total, ext_free, ext_used, ext_percent;
storage_common_fs_info(storage, "/ext", &ext_total, &ext_free);
ext_used = ext_total - ext_free;
ext_percent = (100 * ext_used) / ext_total;
ext_used /= 1024 * 1024;
ext_total /= 1024 * 1024;
furi_record_close(RECORD_STORAGE);
// Get battery info
uint16_t charge_percent = furi_hal_power_get_pct();
const char* charge_state;
if(furi_hal_power_is_charging()) {
if((charge_percent < 100) && (!furi_hal_power_is_charging_done())) {
charge_state = "charging";
} else {
charge_state = "charged";
}
} else {
charge_state = "discharging";
}
// Get misc info
uint32_t uptime = furi_get_tick() / furi_kernel_get_tick_frequency();
const Version* version = version_get();
uint16_t major, minor;
furi_hal_info_get_api_version(&major, &minor);
// Print ASCII art with info
const size_t info_height = 16;
for(size_t i = 0; i < MAX(logo_height, info_height); i++) {
printf(NEOFETCH_COLOR "%-*s", logo_width, (i < logo_height) ? neofetch_logo[i] : "");
switch(i) {
case 0: // you@<hostname>
printf("you" ANSI_RESET "@" NEOFETCH_COLOR "%s", furi_hal_version_get_name_ptr());
break;
case 1: // delimiter
printf(ANSI_RESET "%s", delimiter);
break;
case 2: // OS: FURI <edition> <branch> <version> <commit> (SDK <maj>.<min>)
printf(
"OS" ANSI_RESET ": FURI %s %s %s %s (SDK %hu.%hu)",
version_get_version(version),
version_get_gitbranch(version),
version_get_version(version),
version_get_githash(version),
major,
minor);
break;
case 3: // Host: <model> <hostname>
printf(
"Host" ANSI_RESET ": %s %s",
furi_hal_version_get_model_code(),
furi_hal_version_get_device_name_ptr());
break;
case 4: // Kernel: FreeRTOS <maj>.<min>.<build>
printf(
"Kernel" ANSI_RESET ": FreeRTOS %d.%d.%d",
tskKERNEL_VERSION_MAJOR,
tskKERNEL_VERSION_MINOR,
tskKERNEL_VERSION_BUILD);
break;
case 5: // Uptime: ?h?m?s
printf(
"Uptime" ANSI_RESET ": %luh%lum%lus",
uptime / 60 / 60,
uptime / 60 % 60,
uptime % 60);
break;
case 6: // ST7567 128x64 @ 1 bpp in 1.4"
printf("Display" ANSI_RESET ": ST7567 128x64 @ 1 bpp in 1.4\"");
break;
case 7: // DE: GuiSrv
printf("DE" ANSI_RESET ": GuiSrv");
break;
case 8: // Shell: CliSrv
printf("Shell" ANSI_RESET ": CliShell");
break;
case 9: // CPU: STM32WB55RG @ 64 MHz
printf("CPU" ANSI_RESET ": STM32WB55RG @ 64 MHz");
break;
case 10: // Memory: <used> / <total> B (??%)
printf(
"Memory" ANSI_RESET ": %zu / %zu B (%hu%%)", heap_used, heap_total, heap_percent);
break;
case 11: // Disk (/ext): <used> / <total> MiB (??%)
printf(
"Disk (/ext)" ANSI_RESET ": %llu / %llu MiB (%llu%%)",
ext_used,
ext_total,
ext_percent);
break;
case 12: // Battery: ??% (<state>)
printf("Battery" ANSI_RESET ": %hu%% (%s)" ANSI_RESET, charge_percent, charge_state);
break;
case 13: // empty space
break;
case 14: // Colors (line 1)
for(size_t j = 30; j <= 37; j++)
printf("\e[%dm███", j);
break;
case 15: // Colors (line 2)
for(size_t j = 90; j <= 97; j++)
printf("\e[%dm███", j);
break;
default:
break;
}
printf("\r\n");
}
printf(ANSI_RESET);
#undef NEOFETCH_COLOR
}
CLI_COMMAND_INTERFACE(neofetch, execute, CliCommandFlagParallelSafe, 2048, CLI_APPID);

View File

@@ -0,0 +1,43 @@
#include "../cli_main_commands.h"
#include <toolbox/cli/cli_registry.h>
#include <toolbox/cli/shell/cli_shell.h>
#include <toolbox/cli/cli_ansi.h>
#define RAINBOW_SUBCOMMAND \
ANSI_FG_RED "s" ANSI_FG_YELLOW "u" ANSI_FG_BLUE "b" ANSI_FG_GREEN "c" ANSI_FG_MAGENTA \
"o" ANSI_FG_RED "m" ANSI_FG_YELLOW "m" ANSI_FG_BLUE "a" ANSI_FG_GREEN \
"n" ANSI_FG_MAGENTA "d"
static void subcommand(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(pipe);
UNUSED(args);
UNUSED(context);
printf("This is a ✨" RAINBOW_SUBCOMMAND ANSI_RESET "✨!");
}
static void motd(void* context) {
UNUSED(context);
printf("\r\n");
printf("+------------------------------------+\r\n");
printf("| Hello world! |\r\n");
printf("| This is the " ANSI_FG_GREEN "MOTD" ANSI_RESET " for our " ANSI_FG_BLUE
"subshell" ANSI_RESET "! |\r\n");
printf("+------------------------------------+\r\n");
}
static void execute(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(args);
UNUSED(context);
CliRegistry* registry = cli_registry_alloc();
cli_registry_add_command(registry, "subcommand", CliCommandFlagParallelSafe, subcommand, NULL);
CliShell* shell = cli_shell_alloc(motd, NULL, pipe, registry, NULL);
cli_shell_set_prompt(shell, "subshell");
cli_shell_start(shell);
cli_shell_join(shell);
cli_shell_free(shell);
cli_registry_free(registry);
}
CLI_COMMAND_INTERFACE(subshell_demo, execute, CliCommandFlagParallelSafe, 2048, CLI_APPID);

View File

@@ -2,7 +2,10 @@
#include <furi.h> #include <furi.h>
#include <lib/toolbox/args.h> #include <lib/toolbox/args.h>
#include <cli/cli.h> #include <toolbox/pipe.h>
#include <cli/cli_main_commands.h>
#include <toolbox/cli/cli_registry.h>
#include <toolbox/cli/cli_ansi.h>
void crypto_cli_print_usage(void) { void crypto_cli_print_usage(void) {
printf("Usage:\r\n"); printf("Usage:\r\n");
@@ -17,7 +20,7 @@ void crypto_cli_print_usage(void) {
"\tstore_key <key_slot:int> <key_type:str> <key_size:int> <key_data:hex>\t - Store key in secure enclave. !!! NON-REVERSIBLE OPERATION - READ MANUAL FIRST !!!\r\n"); "\tstore_key <key_slot:int> <key_type:str> <key_size:int> <key_data:hex>\t - Store key in secure enclave. !!! NON-REVERSIBLE OPERATION - READ MANUAL FIRST !!!\r\n");
} }
void crypto_cli_encrypt(Cli* cli, FuriString* args) { void crypto_cli_encrypt(PipeSide* pipe, FuriString* args) {
int key_slot = 0; int key_slot = 0;
bool key_loaded = false; bool key_loaded = false;
uint8_t iv[16]; uint8_t iv[16];
@@ -44,15 +47,15 @@ void crypto_cli_encrypt(Cli* cli, FuriString* args) {
FuriString* input; FuriString* input;
input = furi_string_alloc(); input = furi_string_alloc();
char c; char c;
while(cli_read(cli, (uint8_t*)&c, 1) == 1) { while(pipe_receive(pipe, (uint8_t*)&c, 1) == 1) {
if(c == CliSymbolAsciiETX) { if(c == CliKeyETX) {
printf("\r\n"); printf("\r\n");
break; break;
} else if(c >= 0x20 && c < 0x7F) { } else if(c >= 0x20 && c < 0x7F) {
putc(c, stdout); putc(c, stdout);
fflush(stdout); fflush(stdout);
furi_string_push_back(input, c); furi_string_push_back(input, c);
} else if(c == CliSymbolAsciiCR) { } else if(c == CliKeyCR) {
printf("\r\n"); printf("\r\n");
furi_string_cat(input, "\r\n"); furi_string_cat(input, "\r\n");
} }
@@ -92,7 +95,7 @@ void crypto_cli_encrypt(Cli* cli, FuriString* args) {
} }
} }
void crypto_cli_decrypt(Cli* cli, FuriString* args) { void crypto_cli_decrypt(PipeSide* pipe, FuriString* args) {
int key_slot = 0; int key_slot = 0;
bool key_loaded = false; bool key_loaded = false;
uint8_t iv[16]; uint8_t iv[16];
@@ -119,15 +122,15 @@ void crypto_cli_decrypt(Cli* cli, FuriString* args) {
FuriString* hex_input; FuriString* hex_input;
hex_input = furi_string_alloc(); hex_input = furi_string_alloc();
char c; char c;
while(cli_read(cli, (uint8_t*)&c, 1) == 1) { while(pipe_receive(pipe, (uint8_t*)&c, 1) == 1) {
if(c == CliSymbolAsciiETX) { if(c == CliKeyETX) {
printf("\r\n"); printf("\r\n");
break; break;
} else if(c >= 0x20 && c < 0x7F) { } else if(c >= 0x20 && c < 0x7F) {
putc(c, stdout); putc(c, stdout);
fflush(stdout); fflush(stdout);
furi_string_push_back(hex_input, c); furi_string_push_back(hex_input, c);
} else if(c == CliSymbolAsciiCR) { } else if(c == CliKeyCR) {
printf("\r\n"); printf("\r\n");
} }
} }
@@ -164,8 +167,8 @@ void crypto_cli_decrypt(Cli* cli, FuriString* args) {
} }
} }
void crypto_cli_has_key(Cli* cli, FuriString* args) { void crypto_cli_has_key(PipeSide* pipe, FuriString* args) {
UNUSED(cli); UNUSED(pipe);
int key_slot = 0; int key_slot = 0;
uint8_t iv[16] = {0}; uint8_t iv[16] = {0};
@@ -186,8 +189,8 @@ void crypto_cli_has_key(Cli* cli, FuriString* args) {
} while(0); } while(0);
} }
void crypto_cli_store_key(Cli* cli, FuriString* args) { void crypto_cli_store_key(PipeSide* pipe, FuriString* args) {
UNUSED(cli); UNUSED(pipe);
int key_slot = 0; int key_slot = 0;
int key_size = 0; int key_size = 0;
FuriString* key_type; FuriString* key_type;
@@ -279,7 +282,7 @@ void crypto_cli_store_key(Cli* cli, FuriString* args) {
furi_string_free(key_type); furi_string_free(key_type);
} }
static void crypto_cli(Cli* cli, FuriString* args, void* context) { static void crypto_cli(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(context); UNUSED(context);
FuriString* cmd; FuriString* cmd;
cmd = furi_string_alloc(); cmd = furi_string_alloc();
@@ -291,22 +294,22 @@ static void crypto_cli(Cli* cli, FuriString* args, void* context) {
} }
if(furi_string_cmp_str(cmd, "encrypt") == 0) { if(furi_string_cmp_str(cmd, "encrypt") == 0) {
crypto_cli_encrypt(cli, args); crypto_cli_encrypt(pipe, args);
break; break;
} }
if(furi_string_cmp_str(cmd, "decrypt") == 0) { if(furi_string_cmp_str(cmd, "decrypt") == 0) {
crypto_cli_decrypt(cli, args); crypto_cli_decrypt(pipe, args);
break; break;
} }
if(furi_string_cmp_str(cmd, "has_key") == 0) { if(furi_string_cmp_str(cmd, "has_key") == 0) {
crypto_cli_has_key(cli, args); crypto_cli_has_key(pipe, args);
break; break;
} }
if(furi_string_cmp_str(cmd, "store_key") == 0) { if(furi_string_cmp_str(cmd, "store_key") == 0) {
crypto_cli_store_key(cli, args); crypto_cli_store_key(pipe, args);
break; break;
} }
@@ -318,8 +321,8 @@ static void crypto_cli(Cli* cli, FuriString* args, void* context) {
void crypto_on_system_start(void) { void crypto_on_system_start(void) {
#ifdef SRV_CLI #ifdef SRV_CLI
Cli* cli = furi_record_open(RECORD_CLI); CliRegistry* registry = furi_record_open(RECORD_CLI);
cli_add_command(cli, "crypto", CliCommandFlagDefault, crypto_cli, NULL); cli_registry_add_command(registry, "crypto", CliCommandFlagDefault, crypto_cli, NULL);
furi_record_close(RECORD_CLI); furi_record_close(RECORD_CLI);
#else #else
UNUSED(crypto_cli); UNUSED(crypto_cli);

View File

@@ -1,6 +1,5 @@
#include "desktop_i.h" #include "desktop_i.h"
#include <cli/cli.h>
#include <cli/cli_vcp.h> #include <cli/cli_vcp.h>
#include <gui/gui_i.h> #include <gui/gui_i.h>
@@ -30,9 +29,7 @@ static void desktop_loader_callback(const void* message, void* context) {
if(event->type == LoaderEventTypeApplicationBeforeLoad) { if(event->type == LoaderEventTypeApplicationBeforeLoad) {
view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopGlobalBeforeAppStarted); view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopGlobalBeforeAppStarted);
furi_check(furi_semaphore_acquire(desktop->animation_semaphore, 3000) == FuriStatusOk); furi_check(furi_semaphore_acquire(desktop->animation_semaphore, 3000) == FuriStatusOk);
} else if( } else if(event->type == LoaderEventTypeNoMoreAppsInQueue) {
event->type == LoaderEventTypeApplicationLoadFailed ||
event->type == LoaderEventTypeApplicationStopped) {
view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopGlobalAfterAppFinished); view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopGlobalAfterAppFinished);
} }
} }
@@ -405,9 +402,9 @@ void desktop_lock(Desktop* desktop) {
furi_hal_rtc_set_flag(FuriHalRtcFlagLock); furi_hal_rtc_set_flag(FuriHalRtcFlagLock);
if(desktop_pin_code_is_set()) { if(desktop_pin_code_is_set()) {
Cli* cli = furi_record_open(RECORD_CLI); CliVcp* cli_vcp = furi_record_open(RECORD_CLI_VCP);
cli_session_close(cli); cli_vcp_disable(cli_vcp);
furi_record_close(RECORD_CLI); furi_record_close(RECORD_CLI_VCP);
} }
desktop_auto_lock_inhibit(desktop); desktop_auto_lock_inhibit(desktop);
@@ -435,9 +432,9 @@ void desktop_unlock(Desktop* desktop) {
furi_hal_rtc_set_pin_fails(0); furi_hal_rtc_set_pin_fails(0);
if(desktop_pin_code_is_set()) { if(desktop_pin_code_is_set()) {
Cli* cli = furi_record_open(RECORD_CLI); CliVcp* cli_vcp = furi_record_open(RECORD_CLI_VCP);
cli_session_open(cli, &cli_vcp); cli_vcp_enable(cli_vcp);
furi_record_close(RECORD_CLI); furi_record_close(RECORD_CLI_VCP);
} }
DesktopStatus status = {.locked = false}; DesktopStatus status = {.locked = false};
@@ -534,6 +531,10 @@ int32_t desktop_srv(void* p) {
if(desktop_pin_code_is_set()) { if(desktop_pin_code_is_set()) {
desktop_lock(desktop); desktop_lock(desktop);
} else {
CliVcp* cli_vcp = furi_record_open(RECORD_CLI_VCP);
cli_vcp_enable(cli_vcp);
furi_record_close(RECORD_CLI_VCP);
} }
if(storage_file_exists(desktop->storage, SLIDESHOW_FS_PATH)) { if(storage_file_exists(desktop->storage, SLIDESHOW_FS_PATH)) {

View File

@@ -4,9 +4,11 @@
#include <stdint.h> #include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <furi.h> #include <furi.h>
#include <cli/cli.h>
#include <furi_hal_gpio.h> #include <furi_hal_gpio.h>
#include <furi_hal_vibro.h> #include <furi_hal_vibro.h>
#include <toolbox/cli/cli_command.h>
#include <cli/cli_main_commands.h>
#include <toolbox/pipe.h>
#define INPUT_DEBOUNCE_TICKS_HALF (INPUT_DEBOUNCE_TICKS / 2) #define INPUT_DEBOUNCE_TICKS_HALF (INPUT_DEBOUNCE_TICKS / 2)
#define INPUT_PRESS_TICKS 150 #define INPUT_PRESS_TICKS 150
@@ -28,7 +30,7 @@ typedef struct {
} InputPinState; } InputPinState;
/** Input CLI command handler */ /** Input CLI command handler */
void input_cli(Cli* cli, FuriString* args, void* context); void input_cli(PipeSide* pipe, FuriString* args, void* context);
// #define INPUT_DEBUG // #define INPUT_DEBUG
@@ -100,8 +102,10 @@ int32_t input_srv(void* p) {
#endif #endif
#ifdef SRV_CLI #ifdef SRV_CLI
Cli* cli = furi_record_open(RECORD_CLI); CliRegistry* registry = furi_record_open(RECORD_CLI);
cli_add_command(cli, "input", CliCommandFlagParallelSafe, input_cli, event_pubsub); cli_registry_add_command(
registry, "input", CliCommandFlagParallelSafe, input_cli, event_pubsub);
furi_record_close(RECORD_CLI);
#endif #endif
InputPinState pin_states[input_pins_count]; InputPinState pin_states[input_pins_count];

View File

@@ -1,8 +1,9 @@
#include "input.h" #include "input.h"
#include <furi.h> #include <furi.h>
#include <cli/cli.h> #include <toolbox/cli/cli_command.h>
#include <toolbox/args.h> #include <toolbox/args.h>
#include <toolbox/pipe.h>
static void input_cli_usage(void) { static void input_cli_usage(void) {
printf("Usage:\r\n"); printf("Usage:\r\n");
@@ -19,7 +20,7 @@ static void input_cli_dump_events_callback(const void* value, void* ctx) {
furi_message_queue_put(input_queue, value, FuriWaitForever); furi_message_queue_put(input_queue, value, FuriWaitForever);
} }
static void input_cli_dump(Cli* cli, FuriString* args, FuriPubSub* event_pubsub) { static void input_cli_dump(PipeSide* pipe, FuriString* args, FuriPubSub* event_pubsub) {
UNUSED(args); UNUSED(args);
FuriMessageQueue* input_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); FuriMessageQueue* input_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
FuriPubSubSubscription* input_subscription = FuriPubSubSubscription* input_subscription =
@@ -27,7 +28,7 @@ static void input_cli_dump(Cli* cli, FuriString* args, FuriPubSub* event_pubsub)
InputEvent input_event; InputEvent input_event;
printf("Press CTRL+C to stop\r\n"); 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)) {
if(furi_message_queue_get(input_queue, &input_event, 100) == FuriStatusOk) { if(furi_message_queue_get(input_queue, &input_event, 100) == FuriStatusOk) {
printf( printf(
"key: %s type: %s\r\n", "key: %s type: %s\r\n",
@@ -47,8 +48,8 @@ static void input_cli_send_print_usage(void) {
printf("\t\t <type>\t - one of 'press', 'release', 'short', 'long'\r\n"); printf("\t\t <type>\t - one of 'press', 'release', 'short', 'long'\r\n");
} }
static void input_cli_send(Cli* cli, FuriString* args, FuriPubSub* event_pubsub) { static void input_cli_send(PipeSide* pipe, FuriString* args, FuriPubSub* event_pubsub) {
UNUSED(cli); UNUSED(pipe);
InputEvent event; InputEvent event;
FuriString* key_str; FuriString* key_str;
key_str = furi_string_alloc(); key_str = furi_string_alloc();
@@ -97,8 +98,7 @@ static void input_cli_send(Cli* cli, FuriString* args, FuriPubSub* event_pubsub)
furi_string_free(key_str); furi_string_free(key_str);
} }
void input_cli(Cli* cli, FuriString* args, void* context) { void input_cli(PipeSide* pipe, FuriString* args, void* context) {
furi_assert(cli);
furi_assert(context); furi_assert(context);
FuriPubSub* event_pubsub = context; FuriPubSub* event_pubsub = context;
FuriString* cmd; FuriString* cmd;
@@ -110,11 +110,11 @@ void input_cli(Cli* cli, FuriString* args, void* context) {
break; break;
} }
if(furi_string_cmp_str(cmd, "dump") == 0) { if(furi_string_cmp_str(cmd, "dump") == 0) {
input_cli_dump(cli, args, event_pubsub); input_cli_dump(pipe, args, event_pubsub);
break; break;
} }
if(furi_string_cmp_str(cmd, "send") == 0) { if(furi_string_cmp_str(cmd, "send") == 0) {
input_cli_send(cli, args, event_pubsub); input_cli_send(pipe, args, event_pubsub);
break; break;
} }

View File

@@ -168,6 +168,13 @@ static void loader_show_gui_error(
furi_record_close(RECORD_DIALOGS); furi_record_close(RECORD_DIALOGS);
} }
static void loader_generic_synchronous_request(Loader* loader, LoaderMessage* message) {
furi_check(loader);
message->api_lock = api_lock_alloc_locked();
furi_message_queue_put(loader->queue, message, FuriWaitForever);
api_lock_wait_unlock_and_free(message->api_lock);
}
LoaderStatus LoaderStatus
loader_start(Loader* loader, const char* name, const char* args, FuriString* error_message) { loader_start(Loader* loader, const char* name, const char* args, FuriString* error_message) {
furi_check(loader); furi_check(loader);
@@ -203,16 +210,12 @@ void loader_start_detached_with_gui_error(Loader* loader, const char* name, cons
} }
bool loader_lock(Loader* loader) { bool loader_lock(Loader* loader) {
furi_check(loader);
LoaderMessage message;
LoaderMessageBoolResult result; LoaderMessageBoolResult result;
message.type = LoaderMessageTypeLock; LoaderMessage message = {
message.api_lock = api_lock_alloc_locked(); .type = LoaderMessageTypeLock,
message.bool_value = &result; .bool_value = &result,
furi_message_queue_put(loader->queue, &message, FuriWaitForever); };
api_lock_wait_unlock_and_free(message.api_lock); loader_generic_synchronous_request(loader, &message);
return result.value; return result.value;
} }
@@ -226,16 +229,12 @@ void loader_unlock(Loader* loader) {
} }
bool loader_is_locked(Loader* loader) { bool loader_is_locked(Loader* loader) {
furi_check(loader);
LoaderMessage message;
LoaderMessageBoolResult result; LoaderMessageBoolResult result;
message.type = LoaderMessageTypeIsLocked; LoaderMessage message = {
message.api_lock = api_lock_alloc_locked(); .type = LoaderMessageTypeIsLocked,
message.bool_value = &result; .bool_value = &result,
furi_message_queue_put(loader->queue, &message, FuriWaitForever); };
api_lock_wait_unlock_and_free(message.api_lock); loader_generic_synchronous_request(loader, &message);
return result.value; return result.value;
} }
@@ -257,42 +256,63 @@ FuriPubSub* loader_get_pubsub(Loader* loader) {
} }
bool loader_signal(Loader* loader, uint32_t signal, void* arg) { bool loader_signal(Loader* loader, uint32_t signal, void* arg) {
furi_check(loader);
LoaderMessageBoolResult result; LoaderMessageBoolResult result;
LoaderMessage message = { LoaderMessage message = {
.type = LoaderMessageTypeSignal, .type = LoaderMessageTypeSignal,
.api_lock = api_lock_alloc_locked(),
.signal.signal = signal, .signal.signal = signal,
.signal.arg = arg, .signal.arg = arg,
.bool_value = &result, .bool_value = &result,
}; };
loader_generic_synchronous_request(loader, &message);
furi_message_queue_put(loader->queue, &message, FuriWaitForever);
api_lock_wait_unlock_and_free(message.api_lock);
return result.value; return result.value;
} }
bool loader_get_application_name(Loader* loader, FuriString* name) { bool loader_get_application_name(Loader* loader, FuriString* name) {
furi_check(loader);
LoaderMessageBoolResult result; LoaderMessageBoolResult result;
LoaderMessage message = { LoaderMessage message = {
.type = LoaderMessageTypeGetApplicationName, .type = LoaderMessageTypeGetApplicationName,
.api_lock = api_lock_alloc_locked(),
.application_name = name, .application_name = name,
.bool_value = &result, .bool_value = &result,
}; };
loader_generic_synchronous_request(loader, &message);
furi_message_queue_put(loader->queue, &message, FuriWaitForever);
api_lock_wait_unlock_and_free(message.api_lock);
return result.value; return result.value;
} }
bool loader_get_application_launch_path(Loader* loader, FuriString* name) {
LoaderMessageBoolResult result;
LoaderMessage message = {
.type = LoaderMessageTypeGetApplicationLaunchPath,
.application_name = name,
.bool_value = &result,
};
loader_generic_synchronous_request(loader, &message);
return result.value;
}
void loader_enqueue_launch(
Loader* loader,
const char* name,
const char* args,
LoaderDeferredLaunchFlag flags) {
LoaderMessage message = {
.type = LoaderMessageTypeEnqueueLaunch,
.defer_start =
{
.name_or_path = strdup(name),
.args = args ? strdup(args) : NULL,
.flags = flags,
},
};
loader_generic_synchronous_request(loader, &message);
}
void loader_clear_launch_queue(Loader* loader) {
LoaderMessage message = {
.type = LoaderMessageTypeClearLaunchQueue,
};
loader_generic_synchronous_request(loader, &message);
}
// callbacks // callbacks
static void loader_menu_closed_callback(void* context) { static void loader_menu_closed_callback(void* context) {
@@ -329,12 +349,10 @@ static Loader* loader_alloc(void) {
Loader* loader = malloc(sizeof(Loader)); Loader* loader = malloc(sizeof(Loader));
loader->pubsub = furi_pubsub_alloc(); loader->pubsub = furi_pubsub_alloc();
loader->queue = furi_message_queue_alloc(1, sizeof(LoaderMessage)); loader->queue = furi_message_queue_alloc(1, sizeof(LoaderMessage));
loader->loader_menu = NULL; loader->gui = furi_record_open(RECORD_GUI);
loader->loader_applications = NULL; loader->view_holder = view_holder_alloc();
loader->app.args = NULL; loader->loading = loading_alloc();
loader->app.thread = NULL; view_holder_attach_to_gui(loader->view_holder, loader->gui);
loader->app.insomniac = false;
loader->app.fap = NULL;
return loader; return loader;
} }
@@ -706,6 +724,10 @@ static LoaderMessageLoaderStatusResult loader_do_start_by_name(
LoaderStatusErrorUnknownApp, error_message, "Application \"%s\" not found", name); LoaderStatusErrorUnknownApp, error_message, "Application \"%s\" not found", name);
} while(false); } while(false);
if(status.value == LoaderStatusOk) {
loader->app.launch_path = furi_string_alloc_set_str(name);
}
return status; return status;
} }
@@ -723,6 +745,57 @@ static void loader_do_unlock(Loader* loader) {
loader->app.thread = NULL; loader->app.thread = NULL;
} }
static void loader_do_emit_queue_empty_event(Loader* loader) {
FURI_LOG_I(TAG, "Launch queue empty");
LoaderEvent event;
event.type = LoaderEventTypeNoMoreAppsInQueue;
furi_pubsub_publish(loader->pubsub, &event);
}
static bool loader_do_deferred_launch(Loader* loader, LoaderDeferredLaunchRecord* record);
static void loader_do_next_deferred_launch_if_available(Loader* loader) {
LoaderDeferredLaunchRecord record;
if(loader_queue_pop(&loader->launch_queue, &record)) {
loader_do_deferred_launch(loader, &record);
loader_queue_item_clear(&record);
} else {
loader_do_emit_queue_empty_event(loader);
}
}
static bool loader_do_deferred_launch(Loader* loader, LoaderDeferredLaunchRecord* record) {
furi_assert(loader);
furi_assert(record);
bool is_successful = false;
FuriString* error_message = furi_string_alloc();
view_holder_set_view(loader->view_holder, loading_get_view(loader->loading));
view_holder_send_to_front(loader->view_holder);
do {
const char* app_name_str = record->name_or_path;
const char* app_args = record->args;
FURI_LOG_I(TAG, "Deferred launch: %s", app_name_str);
LoaderMessageLoaderStatusResult result =
loader_do_start_by_name(loader, app_name_str, app_args, error_message);
if(result.value == LoaderStatusOk) {
is_successful = true;
break;
}
if(record->flags & LoaderDeferredLaunchFlagGui)
loader_show_gui_error(result, app_name_str, error_message);
loader_do_next_deferred_launch_if_available(loader);
} while(false);
view_holder_set_view(loader->view_holder, NULL);
furi_string_free(error_message);
return is_successful;
}
static void loader_do_app_closed(Loader* loader) { static void loader_do_app_closed(Loader* loader) {
furi_assert(loader->app.thread); furi_assert(loader->app.thread);
@@ -747,11 +820,15 @@ static void loader_do_app_closed(Loader* loader) {
loader->app.thread = NULL; loader->app.thread = NULL;
} }
furi_string_free(loader->app.launch_path);
FURI_LOG_I(TAG, "Application stopped. Free heap: %zu", memmgr_get_free_heap()); FURI_LOG_I(TAG, "Application stopped. Free heap: %zu", memmgr_get_free_heap());
LoaderEvent event; LoaderEvent event;
event.type = LoaderEventTypeApplicationStopped; event.type = LoaderEventTypeApplicationStopped;
furi_pubsub_publish(loader->pubsub, &event); furi_pubsub_publish(loader->pubsub, &event);
loader_do_next_deferred_launch_if_available(loader);
} }
static bool loader_is_application_running(Loader* loader) { static bool loader_is_application_running(Loader* loader) {
@@ -776,6 +853,15 @@ static bool loader_do_get_application_name(Loader* loader, FuriString* name) {
return false; return false;
} }
static bool loader_do_get_application_launch_path(Loader* loader, FuriString* path) {
if(loader_is_application_running(loader)) {
furi_string_set(path, loader->app.launch_path);
return true;
}
return false;
}
// app // app
int32_t loader_srv(void* p) { int32_t loader_srv(void* p) {
@@ -798,16 +884,20 @@ int32_t loader_srv(void* p) {
while(true) { while(true) {
if(furi_message_queue_get(loader->queue, &message, FuriWaitForever) == FuriStatusOk) { if(furi_message_queue_get(loader->queue, &message, FuriWaitForever) == FuriStatusOk) {
switch(message.type) { switch(message.type) {
case LoaderMessageTypeStartByName: case LoaderMessageTypeStartByName: {
*(message.status_value) = loader_do_start_by_name( LoaderMessageLoaderStatusResult status = loader_do_start_by_name(
loader, message.start.name, message.start.args, message.start.error_message); loader, message.start.name, message.start.args, message.start.error_message);
*(message.status_value) = status;
if(status.value != LoaderStatusOk) loader_do_emit_queue_empty_event(loader);
api_lock_unlock(message.api_lock); api_lock_unlock(message.api_lock);
break; break;
}
case LoaderMessageTypeStartByNameDetachedWithGuiError: { case LoaderMessageTypeStartByNameDetachedWithGuiError: {
FuriString* error_message = furi_string_alloc(); FuriString* error_message = furi_string_alloc();
LoaderMessageLoaderStatusResult status = loader_do_start_by_name( LoaderMessageLoaderStatusResult status = loader_do_start_by_name(
loader, message.start.name, message.start.args, error_message); loader, message.start.name, message.start.args, error_message);
loader_show_gui_error(status, message.start.name, error_message); loader_show_gui_error(status, message.start.name, error_message);
if(status.value != LoaderStatusOk) loader_do_emit_queue_empty_event(loader);
if(message.start.name) free((void*)message.start.name); if(message.start.name) free((void*)message.start.name);
if(message.start.args) free((void*)message.start.args); if(message.start.args) free((void*)message.start.args);
furi_string_free(error_message); furi_string_free(error_message);
@@ -846,6 +936,19 @@ int32_t loader_srv(void* p) {
loader_do_get_application_name(loader, message.application_name); loader_do_get_application_name(loader, message.application_name);
api_lock_unlock(message.api_lock); api_lock_unlock(message.api_lock);
break; break;
case LoaderMessageTypeGetApplicationLaunchPath:
message.bool_value->value =
loader_do_get_application_launch_path(loader, message.application_name);
api_lock_unlock(message.api_lock);
break;
case LoaderMessageTypeEnqueueLaunch:
furi_check(loader_queue_push(&loader->launch_queue, &message.defer_start));
api_lock_unlock(message.api_lock);
break;
case LoaderMessageTypeClearLaunchQueue:
loader_queue_clear(&loader->launch_queue);
api_lock_unlock(message.api_lock);
break;
} }
} }
} }

View File

@@ -22,13 +22,19 @@ typedef enum {
typedef enum { typedef enum {
LoaderEventTypeApplicationBeforeLoad, LoaderEventTypeApplicationBeforeLoad,
LoaderEventTypeApplicationLoadFailed, LoaderEventTypeApplicationLoadFailed,
LoaderEventTypeApplicationStopped LoaderEventTypeApplicationStopped,
LoaderEventTypeNoMoreAppsInQueue, //<! The normal `Stopped` event still fires before this one
} LoaderEventType; } LoaderEventType;
typedef struct { typedef struct {
LoaderEventType type; LoaderEventType type;
} LoaderEvent; } LoaderEvent;
typedef enum {
LoaderDeferredLaunchFlagNone = 0,
LoaderDeferredLaunchFlagGui = (1 << 1), //<! Report launch to the user via a dialog
} LoaderDeferredLaunchFlag;
/** /**
* @brief Start application * @brief Start application
* @param[in] instance loader instance * @param[in] instance loader instance
@@ -95,7 +101,7 @@ FuriPubSub* loader_get_pubsub(Loader* instance);
* *
* @param[in] instance pointer to the loader instance * @param[in] instance pointer to the loader instance
* @param[in] signal signal value to be sent * @param[in] signal signal value to be sent
* @param[in,out] arg optional argument (can be of any value, including NULL) * @param[inout] arg optional argument (can be of any value, including NULL)
* *
* @return true if the signal was handled by the application, false otherwise * @return true if the signal was handled by the application, false otherwise
*/ */
@@ -105,11 +111,49 @@ bool loader_signal(Loader* instance, uint32_t signal, void* arg);
* @brief Get the name of the currently running application * @brief Get the name of the currently running application
* *
* @param[in] instance pointer to the loader instance * @param[in] instance pointer to the loader instance
* @param[in,out] name pointer to the string to contain the name (must be allocated) * @param[inout] name pointer to the string to contain the name (must be allocated)
* @return true if it was possible to get an application name, false otherwise * @return true if it was possible to get an application name, false otherwise
*/ */
bool loader_get_application_name(Loader* instance, FuriString* name); bool loader_get_application_name(Loader* instance, FuriString* name);
/**
* @brief Get the launch path or name of the currently running application
*
* This is the string that was supplied to `loader_start` such that the current
* app is running now. It might be a name (in the case of internal apps) or a
* path (in the case of external apps). This value can be used to launch the
* same app again.
*
* @param[in] instance pointer to the loader instance
* @param[inout] name pointer to the string to contain the path or name
* (must be allocated)
* @return true if it was possible to get an application path, false otherwise
*/
bool loader_get_application_launch_path(Loader* instance, FuriString* name);
/**
* @brief Enqueues a request to launch an application after the current one
*
* @param[in] instance pointer to the loader instance
* @param[in] name pointer to the name or path of the application, or NULL to
* cancel a previous request
* @param[in] args pointer to argument to provide to the next app
* @param[in] flags additional flags. see enum documentation for more info
*/
void loader_enqueue_launch(
Loader* instance,
const char* name,
const char* args,
LoaderDeferredLaunchFlag flags);
/**
* @brief Removes all requests to launch applications after the current one
* exits
*
* @param[in] instance pointer to the loader instance
*/
void loader_clear_launch_queue(Loader* instance);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View File

@@ -120,7 +120,7 @@ static void loader_pubsub_callback(const void* message, void* context) {
const LoaderEvent* event = message; const LoaderEvent* event = message;
const FuriThreadId thread_id = (FuriThreadId)context; const FuriThreadId thread_id = (FuriThreadId)context;
if(event->type == LoaderEventTypeApplicationStopped) { if(event->type == LoaderEventTypeNoMoreAppsInQueue) {
furi_thread_flags_set(thread_id, APPLICATION_STOP_EVENT); furi_thread_flags_set(thread_id, APPLICATION_STOP_EVENT);
} }
} }

View File

@@ -1,11 +1,13 @@
#include "loader.h" #include "loader.h"
#include <furi.h> #include <furi.h>
#include <cli/cli.h> #include <toolbox/cli/cli_command.h>
#include <cli/cli_main_commands.h>
#include <applications.h> #include <applications.h>
#include <lib/toolbox/args.h> #include <lib/toolbox/args.h>
#include <lib/toolbox/strint.h> #include <lib/toolbox/strint.h>
#include <notification/notification_messages.h> #include <notification/notification_messages.h>
#include <toolbox/pipe.h>
static void loader_cli_print_usage(void) { static void loader_cli_print_usage(void) {
printf("Usage:\r\n"); printf("Usage:\r\n");
@@ -110,8 +112,8 @@ static void loader_cli_signal(FuriString* args, Loader* loader) {
} }
} }
static void loader_cli(Cli* cli, FuriString* args, void* context) { static void loader_cli(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(cli); UNUSED(pipe);
UNUSED(context); UNUSED(context);
Loader* loader = furi_record_open(RECORD_LOADER); Loader* loader = furi_record_open(RECORD_LOADER);
@@ -140,8 +142,9 @@ static void loader_cli(Cli* cli, FuriString* args, void* context) {
void loader_on_system_start(void) { void loader_on_system_start(void) {
#ifdef SRV_CLI #ifdef SRV_CLI
Cli* cli = furi_record_open(RECORD_CLI); CliRegistry* registry = furi_record_open(RECORD_CLI);
cli_add_command(cli, RECORD_LOADER, CliCommandFlagParallelSafe, loader_cli, NULL); cli_registry_add_command(
registry, RECORD_LOADER, CliCommandFlagParallelSafe, loader_cli, NULL);
furi_record_close(RECORD_CLI); furi_record_close(RECORD_CLI);
#else #else
UNUSED(loader_cli); UNUSED(loader_cli);

View File

@@ -2,11 +2,20 @@
#include <furi.h> #include <furi.h>
#include <toolbox/api_lock.h> #include <toolbox/api_lock.h>
#include <flipper_application/flipper_application.h> #include <flipper_application/flipper_application.h>
#include <gui/gui.h>
#include <gui/view_holder.h>
#include <gui/modules/loading.h>
#include <m-array.h>
#include "loader.h" #include "loader.h"
#include "loader_menu.h" #include "loader_menu.h"
#include "loader_applications.h" #include "loader_applications.h"
#include "loader_queue.h"
typedef struct { typedef struct {
FuriString* launch_path;
char* args; char* args;
FuriThread* thread; FuriThread* thread;
bool insomniac; bool insomniac;
@@ -19,6 +28,12 @@ struct Loader {
LoaderMenu* loader_menu; LoaderMenu* loader_menu;
LoaderApplications* loader_applications; LoaderApplications* loader_applications;
LoaderAppData app; LoaderAppData app;
LoaderLaunchQueue launch_queue;
Gui* gui;
ViewHolder* view_holder;
Loading* loading;
}; };
typedef enum { typedef enum {
@@ -33,6 +48,9 @@ typedef enum {
LoaderMessageTypeStartByNameDetachedWithGuiError, LoaderMessageTypeStartByNameDetachedWithGuiError,
LoaderMessageTypeSignal, LoaderMessageTypeSignal,
LoaderMessageTypeGetApplicationName, LoaderMessageTypeGetApplicationName,
LoaderMessageTypeGetApplicationLaunchPath,
LoaderMessageTypeEnqueueLaunch,
LoaderMessageTypeClearLaunchQueue,
} LoaderMessageType; } LoaderMessageType;
typedef struct { typedef struct {
@@ -72,6 +90,7 @@ typedef struct {
union { union {
LoaderMessageStartByName start; LoaderMessageStartByName start;
LoaderDeferredLaunchRecord defer_start;
LoaderMessageSignal signal; LoaderMessageSignal signal;
FuriString* application_name; FuriString* application_name;
}; };

View File

@@ -0,0 +1,32 @@
#include "loader_queue.h"
void loader_queue_item_clear(LoaderDeferredLaunchRecord* item) {
free(item->args);
free(item->name_or_path);
}
bool loader_queue_pop(LoaderLaunchQueue* queue, LoaderDeferredLaunchRecord* item) {
if(!queue->item_cnt) return false;
*item = queue->items[0];
queue->item_cnt--;
memmove(
&queue->items[0], &queue->items[1], queue->item_cnt * sizeof(LoaderDeferredLaunchRecord));
return true;
}
bool loader_queue_push(LoaderLaunchQueue* queue, LoaderDeferredLaunchRecord* item) {
if(queue->item_cnt == LOADER_QUEUE_MAX_SIZE) return false;
queue->items[queue->item_cnt] = *item;
queue->item_cnt++;
return true;
}
void loader_queue_clear(LoaderLaunchQueue* queue) {
for(size_t i = 0; i < queue->item_cnt; i++)
loader_queue_item_clear(&queue->items[i]);
queue->item_cnt = 0;
}

View File

@@ -0,0 +1,53 @@
#pragma once
#include <furi.h>
#include "loader.h"
#define LOADER_QUEUE_MAX_SIZE 4
typedef struct {
char* name_or_path;
char* args;
LoaderDeferredLaunchFlag flags;
} LoaderDeferredLaunchRecord;
typedef struct {
LoaderDeferredLaunchRecord items[LOADER_QUEUE_MAX_SIZE];
size_t item_cnt;
} LoaderLaunchQueue;
/**
* @brief Frees internal data in a `DeferredLaunchRecord`
*
* @param[out] item Record to clear
*/
void loader_queue_item_clear(LoaderDeferredLaunchRecord* item);
/**
* @brief Fetches the next item from the launch queue
*
* @param[inout] queue Queue instance
* @param[out] item Item output
*
* @return `true` if `item` was populated, `false` if queue is empty
*/
bool loader_queue_pop(LoaderLaunchQueue* queue, LoaderDeferredLaunchRecord* item);
/**
* @brief Puts an item into the launch queue
*
* @param[inout] queue Queue instance
* @param[in] item Item to put in the queue
*
* @return `true` if the item was put into the queue, `false` if there's no more
* space left
*/
bool loader_queue_push(LoaderLaunchQueue* queue, LoaderDeferredLaunchRecord* item);
/**
* @brief Clears the launch queue
*
* @param[inout] queue Queue instance
*/
void loader_queue_clear(LoaderLaunchQueue* queue);

View File

@@ -1,7 +1,6 @@
#include "namechanger.h" #include "namechanger.h"
#include <furi_hal.h> #include <furi_hal.h>
#include <furi_hal_version.h> #include <furi_hal_version.h>
#include <cli/cli.h>
#include <cli/cli_vcp.h> #include <cli/cli_vcp.h>
#include <bt/bt_service/bt.h> #include <bt/bt_service/bt.h>
#include <storage/storage.h> #include <storage/storage.h>
@@ -79,7 +78,7 @@ int32_t namechanger_on_system_start(void* p) {
// Wait for all required services to start and create their records // Wait for all required services to start and create their records
uint8_t timeout = 0; uint8_t timeout = 0;
while(!furi_record_exists(RECORD_CLI) || !furi_record_exists(RECORD_BT) || while(!furi_record_exists(RECORD_CLI_VCP) || !furi_record_exists(RECORD_BT) ||
!furi_record_exists(RECORD_STORAGE)) { !furi_record_exists(RECORD_STORAGE)) {
timeout++; timeout++;
if(timeout > 250) { if(timeout > 250) {
@@ -91,11 +90,11 @@ int32_t namechanger_on_system_start(void* p) {
// Hehe bad code now here, bad bad bad, very bad, bad example, dont take it, make it better // Hehe bad code now here, bad bad bad, very bad, bad example, dont take it, make it better
if(namechanger_init()) { if(namechanger_init()) {
Cli* cli = furi_record_open(RECORD_CLI); CliVcp* cli = furi_record_open(RECORD_CLI_VCP);
cli_session_close(cli); cli_vcp_disable(cli);
furi_delay_ms(2); // why i added delays here furi_delay_ms(2); // why i added delays here
cli_session_open(cli, &cli_vcp); cli_vcp_enable(cli);
furi_record_close(RECORD_CLI); furi_record_close(RECORD_CLI_VCP);
furi_delay_ms(3); furi_delay_ms(3);
Bt* bt = furi_record_open(RECORD_BT); Bt* bt = furi_record_open(RECORD_BT);

View File

@@ -1,12 +1,14 @@
#include "power_cli.h" #include "power_cli.h"
#include <furi_hal.h> #include <furi_hal.h>
#include <cli/cli.h> #include <toolbox/cli/cli_command.h>
#include <cli/cli_main_commands.h>
#include <lib/toolbox/args.h> #include <lib/toolbox/args.h>
#include <power/power_service/power.h> #include <power/power_service/power.h>
#include <toolbox/pipe.h>
void power_cli_off(Cli* cli, FuriString* args) { void power_cli_off(PipeSide* pipe, FuriString* args) {
UNUSED(cli); UNUSED(pipe);
UNUSED(args); UNUSED(args);
Power* power = furi_record_open(RECORD_POWER); Power* power = furi_record_open(RECORD_POWER);
printf("It's now safe to disconnect USB from your flipper\r\n"); printf("It's now safe to disconnect USB from your flipper\r\n");
@@ -14,22 +16,22 @@ void power_cli_off(Cli* cli, FuriString* args) {
power_off(power); power_off(power);
} }
void power_cli_reboot(Cli* cli, FuriString* args) { void power_cli_reboot(PipeSide* pipe, FuriString* args) {
UNUSED(cli); UNUSED(pipe);
UNUSED(args); UNUSED(args);
Power* power = furi_record_open(RECORD_POWER); Power* power = furi_record_open(RECORD_POWER);
power_reboot(power, PowerBootModeNormal); power_reboot(power, PowerBootModeNormal);
} }
void power_cli_reboot2dfu(Cli* cli, FuriString* args) { void power_cli_reboot2dfu(PipeSide* pipe, FuriString* args) {
UNUSED(cli); UNUSED(pipe);
UNUSED(args); UNUSED(args);
Power* power = furi_record_open(RECORD_POWER); Power* power = furi_record_open(RECORD_POWER);
power_reboot(power, PowerBootModeDfu); power_reboot(power, PowerBootModeDfu);
} }
void power_cli_5v(Cli* cli, FuriString* args) { void power_cli_5v(PipeSide* pipe, FuriString* args) {
UNUSED(cli); UNUSED(pipe);
Power* power = furi_record_open(RECORD_POWER); Power* power = furi_record_open(RECORD_POWER);
if(!furi_string_cmp(args, "0")) { if(!furi_string_cmp(args, "0")) {
power_enable_otg(power, false); power_enable_otg(power, false);
@@ -42,8 +44,8 @@ void power_cli_5v(Cli* cli, FuriString* args) {
furi_record_close(RECORD_POWER); furi_record_close(RECORD_POWER);
} }
void power_cli_3v3(Cli* cli, FuriString* args) { void power_cli_3v3(PipeSide* pipe, FuriString* args) {
UNUSED(cli); UNUSED(pipe);
if(!furi_string_cmp(args, "0")) { if(!furi_string_cmp(args, "0")) {
furi_hal_power_disable_external_3_3v(); furi_hal_power_disable_external_3_3v();
} else if(!furi_string_cmp(args, "1")) { } else if(!furi_string_cmp(args, "1")) {
@@ -67,7 +69,7 @@ static void power_cli_command_print_usage(void) {
} }
} }
void power_cli(Cli* cli, FuriString* args, void* context) { void power_cli(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(context); UNUSED(context);
FuriString* cmd; FuriString* cmd;
cmd = furi_string_alloc(); cmd = furi_string_alloc();
@@ -79,28 +81,28 @@ void power_cli(Cli* cli, FuriString* args, void* context) {
} }
if(furi_string_cmp_str(cmd, "off") == 0) { if(furi_string_cmp_str(cmd, "off") == 0) {
power_cli_off(cli, args); power_cli_off(pipe, args);
break; break;
} }
if(furi_string_cmp_str(cmd, "reboot") == 0) { if(furi_string_cmp_str(cmd, "reboot") == 0) {
power_cli_reboot(cli, args); power_cli_reboot(pipe, args);
break; break;
} }
if(furi_string_cmp_str(cmd, "reboot2dfu") == 0) { if(furi_string_cmp_str(cmd, "reboot2dfu") == 0) {
power_cli_reboot2dfu(cli, args); power_cli_reboot2dfu(pipe, args);
break; break;
} }
if(furi_string_cmp_str(cmd, "5v") == 0) { if(furi_string_cmp_str(cmd, "5v") == 0) {
power_cli_5v(cli, args); power_cli_5v(pipe, args);
break; break;
} }
if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) {
if(furi_string_cmp_str(cmd, "3v3") == 0) { if(furi_string_cmp_str(cmd, "3v3") == 0) {
power_cli_3v3(cli, args); power_cli_3v3(pipe, args);
break; break;
} }
} }
@@ -113,10 +115,8 @@ void power_cli(Cli* cli, FuriString* args, void* context) {
void power_on_system_start(void) { void power_on_system_start(void) {
#ifdef SRV_CLI #ifdef SRV_CLI
Cli* cli = furi_record_open(RECORD_CLI); CliRegistry* registry = furi_record_open(RECORD_CLI);
cli_registry_add_command(registry, "power", CliCommandFlagParallelSafe, power_cli, NULL);
cli_add_command(cli, "power", CliCommandFlagParallelSafe, power_cli, NULL);
furi_record_close(RECORD_CLI); furi_record_close(RECORD_CLI);
#else #else
UNUSED(power_cli); UNUSED(power_cli);

View File

@@ -9,7 +9,8 @@
#include <furi.h> #include <furi.h>
#include <cli/cli.h> #include <toolbox/cli/cli_command.h>
#include <cli/cli_main_commands.h>
#include <stdint.h> #include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <m-dict.h> #include <m-dict.h>
@@ -435,9 +436,14 @@ void rpc_on_system_start(void* p) {
rpc->busy_mutex = furi_mutex_alloc(FuriMutexTypeNormal); rpc->busy_mutex = furi_mutex_alloc(FuriMutexTypeNormal);
Cli* cli = furi_record_open(RECORD_CLI); CliRegistry* registry = furi_record_open(RECORD_CLI);
cli_add_command( cli_registry_add_command(
cli, "start_rpc_session", CliCommandFlagParallelSafe, rpc_cli_command_start_session, rpc); registry,
"start_rpc_session",
CliCommandFlagParallelSafe,
rpc_cli_command_start_session,
rpc);
furi_record_close(RECORD_CLI);
furi_record_create(RECORD_RPC, rpc); furi_record_create(RECORD_RPC, rpc);
} }

View File

@@ -1,25 +1,26 @@
#include <cli/cli.h> #include <toolbox/cli/cli_command.h>
#include <cli/cli_main_commands.h>
#include <furi.h> #include <furi.h>
#include <rpc/rpc.h> #include <rpc/rpc.h>
#include <furi_hal.h> #include <furi_hal.h>
#include <toolbox/pipe.h>
#define TAG "RpcCli" #define TAG "RpcCli"
typedef struct { typedef struct {
Cli* cli; PipeSide* pipe;
bool session_close_request; bool session_close_request;
FuriSemaphore* terminate_semaphore; FuriSemaphore* terminate_semaphore;
} CliRpc; } CliRpc;
#define CLI_READ_BUFFER_SIZE 64 #define CLI_READ_BUFFER_SIZE 64UL
static void rpc_cli_send_bytes_callback(void* context, uint8_t* bytes, size_t bytes_len) { static void rpc_cli_send_bytes_callback(void* context, uint8_t* bytes, size_t bytes_len) {
furi_assert(context); furi_assert(context);
furi_assert(bytes); furi_assert(bytes);
furi_assert(bytes_len > 0); furi_assert(bytes_len > 0);
CliRpc* cli_rpc = context; CliRpc* cli_rpc = context;
pipe_send(cli_rpc->pipe, bytes, bytes_len);
cli_write(cli_rpc->cli, bytes, bytes_len);
} }
static void rpc_cli_session_close_callback(void* context) { static void rpc_cli_session_close_callback(void* context) {
@@ -36,9 +37,9 @@ static void rpc_cli_session_terminated_callback(void* context) {
furi_semaphore_release(cli_rpc->terminate_semaphore); furi_semaphore_release(cli_rpc->terminate_semaphore);
} }
void rpc_cli_command_start_session(Cli* cli, FuriString* args, void* context) { void rpc_cli_command_start_session(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(args); UNUSED(args);
furi_assert(cli); furi_assert(pipe);
furi_assert(context); furi_assert(context);
Rpc* rpc = context; Rpc* rpc = context;
@@ -53,7 +54,7 @@ void rpc_cli_command_start_session(Cli* cli, FuriString* args, void* context) {
return; return;
} }
CliRpc cli_rpc = {.cli = cli, .session_close_request = false}; CliRpc cli_rpc = {.pipe = pipe, .session_close_request = false};
cli_rpc.terminate_semaphore = furi_semaphore_alloc(1, 0); cli_rpc.terminate_semaphore = furi_semaphore_alloc(1, 0);
rpc_session_set_context(rpc_session, &cli_rpc); rpc_session_set_context(rpc_session, &cli_rpc);
rpc_session_set_send_bytes_callback(rpc_session, rpc_cli_send_bytes_callback); rpc_session_set_send_bytes_callback(rpc_session, rpc_cli_send_bytes_callback);
@@ -64,8 +65,9 @@ void rpc_cli_command_start_session(Cli* cli, FuriString* args, void* context) {
size_t size_received = 0; size_t size_received = 0;
while(1) { while(1) {
size_received = cli_read_timeout(cli_rpc.cli, buffer, CLI_READ_BUFFER_SIZE, 50); size_t to_receive = CLAMP(pipe_bytes_available(cli_rpc.pipe), CLI_READ_BUFFER_SIZE, 1UL);
if(!cli_is_connected(cli_rpc.cli) || cli_rpc.session_close_request) { size_received = pipe_receive(cli_rpc.pipe, buffer, to_receive);
if(size_received < to_receive || cli_rpc.session_close_request) {
break; break;
} }

View File

@@ -5,7 +5,7 @@
#include <pb_decode.h> #include <pb_decode.h>
#include <pb_encode.h> #include <pb_encode.h>
#include <flipper.pb.h> #include <flipper.pb.h>
#include <cli/cli.h> #include <toolbox/pipe.h>
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
@@ -46,7 +46,7 @@ void rpc_desktop_free(void* ctx);
void rpc_debug_print_message(const PB_Main* message); void rpc_debug_print_message(const PB_Main* message);
void rpc_debug_print_data(const char* prefix, uint8_t* buffer, size_t size); void rpc_debug_print_data(const char* prefix, uint8_t* buffer, size_t size);
void rpc_cli_command_start_session(Cli* cli, FuriString* args, void* context); void rpc_cli_command_start_session(PipeSide* pipe, FuriString* args, void* context);
PB_CommandStatus rpc_system_storage_get_error(FS_Error fs_error); PB_CommandStatus rpc_system_storage_get_error(FS_Error fs_error);

View File

@@ -1,7 +1,9 @@
#include <furi.h> #include <furi.h>
#include <furi_hal.h> #include <furi_hal.h>
#include <cli/cli.h> #include <toolbox/cli/cli_command.h>
#include <toolbox/cli/cli_ansi.h>
#include <cli/cli_main_commands.h>
#include <lib/toolbox/args.h> #include <lib/toolbox/args.h>
#include <lib/toolbox/dir_walk.h> #include <lib/toolbox/dir_walk.h>
#include <lib/toolbox/md5_calc.h> #include <lib/toolbox/md5_calc.h>
@@ -10,6 +12,7 @@
#include <storage/storage.h> #include <storage/storage.h>
#include <storage/storage_sd_api.h> #include <storage/storage_sd_api.h>
#include <power/power_service/power.h> #include <power/power_service/power.h>
#include <toolbox/pipe.h>
#define MAX_NAME_LENGTH 255 #define MAX_NAME_LENGTH 255
@@ -19,8 +22,8 @@ static void storage_cli_print_error(FS_Error error) {
printf("Storage error: %s\r\n", storage_error_get_desc(error)); printf("Storage error: %s\r\n", storage_error_get_desc(error));
} }
static void storage_cli_info(Cli* cli, FuriString* path, FuriString* args) { static void storage_cli_info(PipeSide* pipe, FuriString* path, FuriString* args) {
UNUSED(cli); UNUSED(pipe);
UNUSED(args); UNUSED(args);
Storage* api = furi_record_open(RECORD_STORAGE); Storage* api = furi_record_open(RECORD_STORAGE);
@@ -69,13 +72,14 @@ static void storage_cli_info(Cli* cli, FuriString* path, FuriString* args) {
furi_record_close(RECORD_STORAGE); furi_record_close(RECORD_STORAGE);
} }
static void storage_cli_format(Cli* cli, FuriString* path, FuriString* args) { static void storage_cli_format(PipeSide* pipe, FuriString* path, FuriString* args) {
UNUSED(pipe);
UNUSED(args); UNUSED(args);
if(furi_string_cmp_str(path, STORAGE_INT_PATH_PREFIX) == 0) { if(furi_string_cmp_str(path, STORAGE_INT_PATH_PREFIX) == 0) {
storage_cli_print_error(FSE_NOT_IMPLEMENTED); storage_cli_print_error(FSE_NOT_IMPLEMENTED);
} else if(furi_string_cmp_str(path, STORAGE_EXT_PATH_PREFIX) == 0) { } else if(furi_string_cmp_str(path, STORAGE_EXT_PATH_PREFIX) == 0) {
printf("Formatting SD card, All data will be lost! Are you sure (y/n)?\r\n"); printf("Formatting SD card, All data will be lost! Are you sure (y/n)?\r\n");
char answer = cli_getc(cli); char answer = getchar();
if(answer == 'y' || answer == 'Y') { if(answer == 'y' || answer == 'Y') {
Storage* api = furi_record_open(RECORD_STORAGE); Storage* api = furi_record_open(RECORD_STORAGE);
printf("Formatting, please wait...\r\n"); printf("Formatting, please wait...\r\n");
@@ -96,8 +100,8 @@ static void storage_cli_format(Cli* cli, FuriString* path, FuriString* args) {
} }
} }
static void storage_cli_list(Cli* cli, FuriString* path, FuriString* args) { static void storage_cli_list(PipeSide* pipe, FuriString* path, FuriString* args) {
UNUSED(cli); UNUSED(pipe);
UNUSED(args); UNUSED(args);
if(furi_string_cmp_str(path, "/") == 0) { if(furi_string_cmp_str(path, "/") == 0) {
printf("\t[D] int\r\n"); printf("\t[D] int\r\n");
@@ -134,13 +138,13 @@ static void storage_cli_list(Cli* cli, FuriString* path, FuriString* args) {
} }
} }
static void storage_cli_tree(Cli* cli, FuriString* path, FuriString* args) { static void storage_cli_tree(PipeSide* pipe, FuriString* path, FuriString* args) {
UNUSED(args); UNUSED(args);
if(furi_string_cmp_str(path, "/") == 0) { if(furi_string_cmp_str(path, "/") == 0) {
furi_string_set(path, STORAGE_INT_PATH_PREFIX); furi_string_set(path, STORAGE_INT_PATH_PREFIX);
storage_cli_tree(cli, path, NULL); storage_cli_tree(pipe, path, NULL);
furi_string_set(path, STORAGE_EXT_PATH_PREFIX); furi_string_set(path, STORAGE_EXT_PATH_PREFIX);
storage_cli_tree(cli, path, NULL); storage_cli_tree(pipe, path, NULL);
} else { } else {
Storage* api = furi_record_open(RECORD_STORAGE); Storage* api = furi_record_open(RECORD_STORAGE);
DirWalk* dir_walk = dir_walk_alloc(api); DirWalk* dir_walk = dir_walk_alloc(api);
@@ -176,8 +180,8 @@ static void storage_cli_tree(Cli* cli, FuriString* path, FuriString* args) {
} }
} }
static void storage_cli_read(Cli* cli, FuriString* path, FuriString* args) { static void storage_cli_read(PipeSide* pipe, FuriString* path, FuriString* args) {
UNUSED(cli); UNUSED(pipe);
UNUSED(args); UNUSED(args);
Storage* api = furi_record_open(RECORD_STORAGE); Storage* api = furi_record_open(RECORD_STORAGE);
File* file = storage_file_alloc(api); File* file = storage_file_alloc(api);
@@ -208,7 +212,8 @@ static void storage_cli_read(Cli* cli, FuriString* path, FuriString* args) {
furi_record_close(RECORD_STORAGE); furi_record_close(RECORD_STORAGE);
} }
static void storage_cli_write(Cli* cli, FuriString* path, FuriString* args) { static void storage_cli_write(PipeSide* pipe, FuriString* path, FuriString* args) {
UNUSED(pipe);
UNUSED(args); UNUSED(args);
Storage* api = furi_record_open(RECORD_STORAGE); Storage* api = furi_record_open(RECORD_STORAGE);
File* file = storage_file_alloc(api); File* file = storage_file_alloc(api);
@@ -222,9 +227,9 @@ static void storage_cli_write(Cli* cli, FuriString* path, FuriString* args) {
uint32_t read_index = 0; uint32_t read_index = 0;
while(true) { while(true) {
uint8_t symbol = cli_getc(cli); uint8_t symbol = getchar();
if(symbol == CliSymbolAsciiETX) { if(symbol == CliKeyETX) {
size_t write_size = read_index % buffer_size; size_t write_size = read_index % buffer_size;
if(write_size > 0) { if(write_size > 0) {
@@ -263,7 +268,8 @@ static void storage_cli_write(Cli* cli, FuriString* path, FuriString* args) {
furi_record_close(RECORD_STORAGE); furi_record_close(RECORD_STORAGE);
} }
static void storage_cli_read_chunks(Cli* cli, FuriString* path, FuriString* args) { static void storage_cli_read_chunks(PipeSide* pipe, FuriString* path, FuriString* args) {
UNUSED(pipe);
Storage* api = furi_record_open(RECORD_STORAGE); Storage* api = furi_record_open(RECORD_STORAGE);
File* file = storage_file_alloc(api); File* file = storage_file_alloc(api);
@@ -280,7 +286,7 @@ static void storage_cli_read_chunks(Cli* cli, FuriString* path, FuriString* args
uint8_t* data = malloc(buffer_size); uint8_t* data = malloc(buffer_size);
while(file_size > 0) { while(file_size > 0) {
printf("\r\nReady?\r\n"); printf("\r\nReady?\r\n");
cli_getc(cli); getchar();
size_t read_size = storage_file_read(file, data, buffer_size); size_t read_size = storage_file_read(file, data, buffer_size);
for(size_t i = 0; i < read_size; i++) { for(size_t i = 0; i < read_size; i++) {
@@ -302,31 +308,34 @@ static void storage_cli_read_chunks(Cli* cli, FuriString* path, FuriString* args
furi_record_close(RECORD_STORAGE); furi_record_close(RECORD_STORAGE);
} }
static void storage_cli_write_chunk(Cli* cli, FuriString* path, FuriString* args) { static void storage_cli_write_chunk(PipeSide* pipe, FuriString* path, FuriString* args) {
Storage* api = furi_record_open(RECORD_STORAGE); Storage* api = furi_record_open(RECORD_STORAGE);
File* file = storage_file_alloc(api); File* file = storage_file_alloc(api);
uint32_t buffer_size; uint32_t need_to_read;
if(strint_to_uint32(furi_string_get_cstr(args), NULL, &buffer_size, 10) != if(strint_to_uint32(furi_string_get_cstr(args), NULL, &need_to_read, 10) !=
StrintParseNoError) { StrintParseNoError) {
storage_cli_print_usage(); storage_cli_print_usage();
} else { } else {
if(storage_file_open(file, furi_string_get_cstr(path), FSAM_WRITE, FSOM_OPEN_APPEND)) { if(storage_file_open(file, furi_string_get_cstr(path), FSAM_WRITE, FSOM_OPEN_APPEND)) {
printf("Ready\r\n"); printf("Ready\r\n");
const size_t buffer_size = 1024;
if(buffer_size) {
uint8_t* buffer = malloc(buffer_size); uint8_t* buffer = malloc(buffer_size);
size_t read_bytes = cli_read(cli, buffer, buffer_size); while(need_to_read) {
size_t to_read_this_time = MIN(buffer_size, need_to_read);
size_t read_this_time = pipe_receive(pipe, buffer, to_read_this_time);
if(read_this_time != to_read_this_time) break;
size_t written_size = storage_file_write(file, buffer, read_bytes); size_t wrote_this_time = storage_file_write(file, buffer, read_this_time);
if(wrote_this_time != read_this_time) {
if(written_size != buffer_size) {
storage_cli_print_error(storage_file_get_error(file)); storage_cli_print_error(storage_file_get_error(file));
break;
}
need_to_read -= read_this_time;
} }
free(buffer); free(buffer);
}
} else { } else {
storage_cli_print_error(storage_file_get_error(file)); storage_cli_print_error(storage_file_get_error(file));
} }
@@ -337,8 +346,8 @@ static void storage_cli_write_chunk(Cli* cli, FuriString* path, FuriString* args
furi_record_close(RECORD_STORAGE); furi_record_close(RECORD_STORAGE);
} }
static void storage_cli_stat(Cli* cli, FuriString* path, FuriString* args) { static void storage_cli_stat(PipeSide* pipe, FuriString* path, FuriString* args) {
UNUSED(cli); UNUSED(pipe);
UNUSED(args); UNUSED(args);
Storage* api = furi_record_open(RECORD_STORAGE); Storage* api = furi_record_open(RECORD_STORAGE);
@@ -379,8 +388,8 @@ static void storage_cli_stat(Cli* cli, FuriString* path, FuriString* args) {
furi_record_close(RECORD_STORAGE); furi_record_close(RECORD_STORAGE);
} }
static void storage_cli_timestamp(Cli* cli, FuriString* path, FuriString* args) { static void storage_cli_timestamp(PipeSide* pipe, FuriString* path, FuriString* args) {
UNUSED(cli); UNUSED(pipe);
UNUSED(args); UNUSED(args);
Storage* api = furi_record_open(RECORD_STORAGE); Storage* api = furi_record_open(RECORD_STORAGE);
@@ -396,8 +405,8 @@ static void storage_cli_timestamp(Cli* cli, FuriString* path, FuriString* args)
furi_record_close(RECORD_STORAGE); furi_record_close(RECORD_STORAGE);
} }
static void storage_cli_copy(Cli* cli, FuriString* old_path, FuriString* args) { static void storage_cli_copy(PipeSide* pipe, FuriString* old_path, FuriString* args) {
UNUSED(cli); UNUSED(pipe);
Storage* api = furi_record_open(RECORD_STORAGE); Storage* api = furi_record_open(RECORD_STORAGE);
FuriString* new_path; FuriString* new_path;
new_path = furi_string_alloc(); new_path = furi_string_alloc();
@@ -417,8 +426,8 @@ static void storage_cli_copy(Cli* cli, FuriString* old_path, FuriString* args) {
furi_record_close(RECORD_STORAGE); furi_record_close(RECORD_STORAGE);
} }
static void storage_cli_remove(Cli* cli, FuriString* path, FuriString* args) { static void storage_cli_remove(PipeSide* pipe, FuriString* path, FuriString* args) {
UNUSED(cli); UNUSED(pipe);
UNUSED(args); UNUSED(args);
Storage* api = furi_record_open(RECORD_STORAGE); Storage* api = furi_record_open(RECORD_STORAGE);
FS_Error error = storage_common_remove(api, furi_string_get_cstr(path)); FS_Error error = storage_common_remove(api, furi_string_get_cstr(path));
@@ -430,8 +439,8 @@ static void storage_cli_remove(Cli* cli, FuriString* path, FuriString* args) {
furi_record_close(RECORD_STORAGE); furi_record_close(RECORD_STORAGE);
} }
static void storage_cli_rename(Cli* cli, FuriString* old_path, FuriString* args) { static void storage_cli_rename(PipeSide* pipe, FuriString* old_path, FuriString* args) {
UNUSED(cli); UNUSED(pipe);
Storage* api = furi_record_open(RECORD_STORAGE); Storage* api = furi_record_open(RECORD_STORAGE);
FuriString* new_path; FuriString* new_path;
new_path = furi_string_alloc(); new_path = furi_string_alloc();
@@ -451,8 +460,8 @@ static void storage_cli_rename(Cli* cli, FuriString* old_path, FuriString* args)
furi_record_close(RECORD_STORAGE); furi_record_close(RECORD_STORAGE);
} }
static void storage_cli_mkdir(Cli* cli, FuriString* path, FuriString* args) { static void storage_cli_mkdir(PipeSide* pipe, FuriString* path, FuriString* args) {
UNUSED(cli); UNUSED(pipe);
UNUSED(args); UNUSED(args);
Storage* api = furi_record_open(RECORD_STORAGE); Storage* api = furi_record_open(RECORD_STORAGE);
FS_Error error = storage_common_mkdir(api, furi_string_get_cstr(path)); FS_Error error = storage_common_mkdir(api, furi_string_get_cstr(path));
@@ -464,8 +473,8 @@ static void storage_cli_mkdir(Cli* cli, FuriString* path, FuriString* args) {
furi_record_close(RECORD_STORAGE); furi_record_close(RECORD_STORAGE);
} }
static void storage_cli_md5(Cli* cli, FuriString* path, FuriString* args) { static void storage_cli_md5(PipeSide* pipe, FuriString* path, FuriString* args) {
UNUSED(cli); UNUSED(pipe);
UNUSED(args); UNUSED(args);
Storage* api = furi_record_open(RECORD_STORAGE); Storage* api = furi_record_open(RECORD_STORAGE);
File* file = storage_file_alloc(api); File* file = storage_file_alloc(api);
@@ -491,8 +500,8 @@ static bool tar_extract_file_callback(const char* name, bool is_directory, void*
return true; return true;
} }
static void storage_cli_extract(Cli* cli, FuriString* old_path, FuriString* args) { static void storage_cli_extract(PipeSide* pipe, FuriString* old_path, FuriString* args) {
UNUSED(cli); UNUSED(pipe);
FuriString* new_path = furi_string_alloc(); FuriString* new_path = furi_string_alloc();
if(!args_read_probably_quoted_string_and_trim(args, new_path)) { if(!args_read_probably_quoted_string_and_trim(args, new_path)) {
@@ -526,7 +535,7 @@ static void storage_cli_extract(Cli* cli, FuriString* old_path, FuriString* args
furi_record_close(RECORD_STORAGE); furi_record_close(RECORD_STORAGE);
} }
typedef void (*StorageCliCommandCallback)(Cli* cli, FuriString* path, FuriString* args); typedef void (*StorageCliCommandCallback)(PipeSide* pipe, FuriString* path, FuriString* args);
typedef struct { typedef struct {
const char* command; const char* command;
@@ -631,7 +640,7 @@ static void storage_cli_print_usage(void) {
} }
} }
void storage_cli(Cli* cli, FuriString* args, void* context) { void storage_cli(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(context); UNUSED(context);
FuriString* cmd; FuriString* cmd;
FuriString* path; FuriString* path;
@@ -653,7 +662,7 @@ void storage_cli(Cli* cli, FuriString* args, void* context) {
for(; i < COUNT_OF(storage_cli_commands); ++i) { for(; i < COUNT_OF(storage_cli_commands); ++i) {
const StorageCliCommand* command_descr = &storage_cli_commands[i]; const StorageCliCommand* command_descr = &storage_cli_commands[i];
if(furi_string_cmp_str(cmd, command_descr->command) == 0) { if(furi_string_cmp_str(cmd, command_descr->command) == 0) {
command_descr->impl(cli, path, args); command_descr->impl(pipe, path, args);
break; break;
} }
} }
@@ -667,11 +676,12 @@ void storage_cli(Cli* cli, FuriString* args, void* context) {
furi_string_free(cmd); furi_string_free(cmd);
} }
static void storage_cli_factory_reset(Cli* cli, FuriString* args, void* context) { static void storage_cli_factory_reset(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(pipe);
UNUSED(args); UNUSED(args);
UNUSED(context); UNUSED(context);
printf("All data will be lost! Are you sure (y/n)?\r\n"); printf("All data will be lost! Are you sure (y/n)?\r\n");
char c = cli_getc(cli); char c = getchar();
if(c == 'y' || c == 'Y') { if(c == 'y' || c == 'Y') {
printf("Data will be wiped after reboot.\r\n"); printf("Data will be wiped after reboot.\r\n");
@@ -687,10 +697,15 @@ static void storage_cli_factory_reset(Cli* cli, FuriString* args, void* context)
void storage_on_system_start(void) { void storage_on_system_start(void) {
#ifdef SRV_CLI #ifdef SRV_CLI
Cli* cli = furi_record_open(RECORD_CLI); CliRegistry* registry = furi_record_open(RECORD_CLI);
cli_add_command(cli, RECORD_STORAGE, CliCommandFlagParallelSafe, storage_cli, NULL); cli_registry_add_command(
cli_add_command( registry,
cli, "factory_reset", CliCommandFlagParallelSafe, storage_cli_factory_reset, NULL); "storage",
CliCommandFlagParallelSafe | CliCommandFlagUseShellThread,
storage_cli,
NULL);
cli_registry_add_command(
registry, "factory_reset", CliCommandFlagParallelSafe, storage_cli_factory_reset, NULL);
furi_record_close(RECORD_CLI); furi_record_close(RECORD_CLI);
#else #else
UNUSED(storage_cli_factory_reset); UNUSED(storage_cli_factory_reset);

View File

@@ -4,7 +4,9 @@
#include "js_app_i.h" #include "js_app_i.h"
#include <toolbox/path.h> #include <toolbox/path.h>
#include <assets_icons.h> #include <assets_icons.h>
#include <cli/cli.h> #include <toolbox/cli/cli_command.h>
#include <cli/cli_main_commands.h>
#include <toolbox/pipe.h>
#define TAG "JS app" #define TAG "JS app"
@@ -131,12 +133,14 @@ int32_t js_app(void* arg) {
} //-V773 } //-V773
typedef struct { typedef struct {
Cli* cli; PipeSide* pipe;
FuriSemaphore* exit_sem; FuriSemaphore* exit_sem;
} JsCliContext; } JsCliContext;
static void js_cli_print(JsCliContext* ctx, const char* msg) { static void js_cli_print(JsCliContext* ctx, const char* msg) {
cli_write(ctx->cli, (uint8_t*)msg, strlen(msg)); UNUSED(ctx);
UNUSED(msg);
pipe_send(ctx->pipe, msg, strlen(msg));
} }
static void js_cli_exit(JsCliContext* ctx) { static void js_cli_exit(JsCliContext* ctx) {
@@ -170,7 +174,7 @@ static void js_cli_callback(JsThreadEvent event, const char* msg, void* context)
} }
} }
void js_cli_execute(Cli* cli, FuriString* args, void* context) { void js_cli_execute(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(context); UNUSED(context);
const char* path = furi_string_get_cstr(args); const char* path = furi_string_get_cstr(args);
@@ -187,14 +191,14 @@ void js_cli_execute(Cli* cli, FuriString* args, void* context) {
break; break;
} }
JsCliContext ctx = {.cli = cli}; JsCliContext ctx = {.pipe = pipe};
ctx.exit_sem = furi_semaphore_alloc(1, 0); ctx.exit_sem = furi_semaphore_alloc(1, 0);
printf("Running script %s, press CTRL+C to stop\r\n", path); printf("Running script %s, press CTRL+C to stop\r\n", path);
JsThread* js_thread = js_thread_run(path, js_cli_callback, &ctx); JsThread* js_thread = js_thread_run(path, js_cli_callback, &ctx);
while(furi_semaphore_acquire(ctx.exit_sem, 100) != FuriStatusOk) { while(furi_semaphore_acquire(ctx.exit_sem, 100) != FuriStatusOk) {
if(cli_cmd_interrupt_received(cli)) break; if(cli_is_pipe_broken_or_is_etx_next_char(pipe)) break;
} }
js_thread_stop(js_thread); js_thread_stop(js_thread);
@@ -206,8 +210,8 @@ void js_cli_execute(Cli* cli, FuriString* args, void* context) {
void js_app_on_system_start(void) { void js_app_on_system_start(void) {
#ifdef SRV_CLI #ifdef SRV_CLI
Cli* cli = furi_record_open(RECORD_CLI); CliRegistry* registry = furi_record_open(RECORD_CLI);
cli_add_command(cli, "js", CliCommandFlagDefault, js_cli_execute, NULL); cli_registry_add_command(registry, "js", CliCommandFlagDefault, js_cli_execute, NULL);
furi_record_close(RECORD_CLI); furi_record_close(RECORD_CLI);
#endif #endif
} }

View File

@@ -202,12 +202,15 @@ static JsSdkCompatStatus
return JsSdkCompatStatusCompatible; return JsSdkCompatStatusCompatible;
} }
#define JS_SDK_COMPAT_ARGS \ static const JsValueDeclaration js_sdk_version_arg_list[] = {
int32_t major, minor; \ JS_VALUE_SIMPLE(JsValueTypeInt32),
JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_INT32(&major), JS_ARG_INT32(&minor)); JS_VALUE_SIMPLE(JsValueTypeInt32),
};
static const JsValueArguments js_sdk_version_args = JS_VALUE_ARGS(js_sdk_version_arg_list);
void js_sdk_compatibility_status(struct mjs* mjs) { void js_sdk_compatibility_status(struct mjs* mjs) {
JS_SDK_COMPAT_ARGS; int32_t major, minor;
JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_sdk_version_args, &major, &minor);
JsSdkCompatStatus status = js_internal_sdk_compatibility_status(major, minor); JsSdkCompatStatus status = js_internal_sdk_compatibility_status(major, minor);
switch(status) { switch(status) {
case JsSdkCompatStatusCompatible: case JsSdkCompatStatusCompatible:
@@ -223,7 +226,8 @@ void js_sdk_compatibility_status(struct mjs* mjs) {
} }
void js_is_sdk_compatible(struct mjs* mjs) { void js_is_sdk_compatible(struct mjs* mjs) {
JS_SDK_COMPAT_ARGS; int32_t major, minor;
JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_sdk_version_args, &major, &minor);
JsSdkCompatStatus status = js_internal_sdk_compatibility_status(major, minor); JsSdkCompatStatus status = js_internal_sdk_compatibility_status(major, minor);
mjs_return(mjs, mjs_mk_boolean(mjs, status == JsSdkCompatStatusCompatible)); mjs_return(mjs, mjs_mk_boolean(mjs, status == JsSdkCompatStatusCompatible));
} }
@@ -246,7 +250,8 @@ static bool js_internal_compat_ask_user(const char* message) {
} }
void js_check_sdk_compatibility(struct mjs* mjs) { void js_check_sdk_compatibility(struct mjs* mjs) {
JS_SDK_COMPAT_ARGS; int32_t major, minor;
JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_sdk_version_args, &major, &minor);
JsSdkCompatStatus status = js_internal_sdk_compatibility_status(major, minor); JsSdkCompatStatus status = js_internal_sdk_compatibility_status(major, minor);
if(status != JsSdkCompatStatusCompatible) { if(status != JsSdkCompatStatusCompatible) {
FURI_LOG_E( FURI_LOG_E(
@@ -308,15 +313,20 @@ static bool js_internal_supports_all_of(struct mjs* mjs, mjs_val_t feature_arr)
return true; return true;
} }
static const JsValueDeclaration js_sdk_features_arg_list[] = {
JS_VALUE_SIMPLE(JsValueTypeAnyArray),
};
static const JsValueArguments js_sdk_features_args = JS_VALUE_ARGS(js_sdk_features_arg_list);
void js_does_sdk_support(struct mjs* mjs) { void js_does_sdk_support(struct mjs* mjs) {
mjs_val_t features; mjs_val_t features;
JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_ARR(&features)); JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_sdk_features_args, &features);
mjs_return(mjs, mjs_mk_boolean(mjs, js_internal_supports_all_of(mjs, features))); mjs_return(mjs, mjs_mk_boolean(mjs, js_internal_supports_all_of(mjs, features)));
} }
void js_check_sdk_features(struct mjs* mjs) { void js_check_sdk_features(struct mjs* mjs) {
mjs_val_t features; mjs_val_t features;
JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_ARR(&features)); JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_sdk_features_args, &features);
if(!js_internal_supports_all_of(mjs, features)) { if(!js_internal_supports_all_of(mjs, features)) {
FURI_LOG_E(TAG, "Script requests unsupported features"); FURI_LOG_E(TAG, "Script requests unsupported features");

View File

@@ -7,6 +7,10 @@
#include <flipper_application/plugins/plugin_manager.h> #include <flipper_application/plugins/plugin_manager.h>
#include <flipper_application/plugins/composite_resolver.h> #include <flipper_application/plugins/composite_resolver.h>
#ifdef __cplusplus
extern "C" {
#endif
#define PLUGIN_APP_ID "js" #define PLUGIN_APP_ID "js"
#define PLUGIN_API_VERSION 1 #define PLUGIN_API_VERSION 1
@@ -65,226 +69,6 @@ typedef enum {
JsForeignMagic_JsEventLoopContract, JsForeignMagic_JsEventLoopContract,
} JsForeignMagic; } JsForeignMagic;
// Are you tired of your silly little JS+C glue code functions being 75%
// argument validation code and 25% actual logic? Introducing: ASS (Argument
// Schema for Scripts)! ASS is a set of macros that reduce the typical
// boilerplate code of "check argument count, get arguments, validate arguments,
// extract C values from arguments" down to just one line!
/**
* When passed as the second argument to `JS_FETCH_ARGS_OR_RETURN`, signifies
* that the function requires exactly as many arguments as were specified.
*/
#define JS_EXACTLY ==
/**
* When passed as the second argument to `JS_FETCH_ARGS_OR_RETURN`, signifies
* that the function requires at least as many arguments as were specified.
*/
#define JS_AT_LEAST >=
typedef struct {
const char* name;
size_t value;
} JsEnumMapping;
#define JS_ENUM_MAP(var_name, ...) \
static const JsEnumMapping var_name##_mapping[] = { \
{NULL, sizeof(var_name)}, \
__VA_ARGS__, \
{NULL, 0}, \
};
typedef struct {
const char* name;
size_t offset;
} JsObjectMapping;
#define JS_OBJ_MAP(var_name, ...) \
static const JsObjectMapping var_name##_mapping[] = { \
__VA_ARGS__, \
{NULL, 0}, \
};
typedef struct {
void* out;
int (*validator)(mjs_val_t);
void (*converter)(struct mjs*, mjs_val_t*, void* out, const void* extra);
const char* expected_type;
bool (*extended_validator)(struct mjs*, mjs_val_t, const void* extra);
const void* extra_data;
} _js_arg_decl;
static inline void _js_to_int32(struct mjs* mjs, mjs_val_t* in, void* out, const void* extra) {
UNUSED(extra);
*(int32_t*)out = mjs_get_int32(mjs, *in);
}
#define JS_ARG_INT32(out) ((_js_arg_decl){out, mjs_is_number, _js_to_int32, "number", NULL, NULL})
static inline void _js_to_ptr(struct mjs* mjs, mjs_val_t* in, void* out, const void* extra) {
UNUSED(extra);
*(void**)out = mjs_get_ptr(mjs, *in);
}
#define JS_ARG_PTR(out) \
((_js_arg_decl){out, mjs_is_foreign, _js_to_ptr, "opaque pointer", NULL, NULL})
static inline void _js_to_string(struct mjs* mjs, mjs_val_t* in, void* out, const void* extra) {
UNUSED(extra);
*(const char**)out = mjs_get_string(mjs, in, NULL);
}
#define JS_ARG_STR(out) ((_js_arg_decl){out, mjs_is_string, _js_to_string, "string", NULL, NULL})
static inline void _js_to_bool(struct mjs* mjs, mjs_val_t* in, void* out, const void* extra) {
UNUSED(extra);
*(bool*)out = !!mjs_get_bool(mjs, *in);
}
#define JS_ARG_BOOL(out) ((_js_arg_decl){out, mjs_is_boolean, _js_to_bool, "boolean", NULL, NULL})
static inline void _js_passthrough(struct mjs* mjs, mjs_val_t* in, void* out, const void* extra) {
UNUSED(extra);
UNUSED(mjs);
*(mjs_val_t*)out = *in;
}
#define JS_ARG_ANY(out) ((_js_arg_decl){out, NULL, _js_passthrough, "any", NULL, NULL})
#define JS_ARG_OBJ(out) ((_js_arg_decl){out, mjs_is_object, _js_passthrough, "any", NULL, NULL})
#define JS_ARG_FN(out) \
((_js_arg_decl){out, mjs_is_function, _js_passthrough, "function", NULL, NULL})
#define JS_ARG_ARR(out) ((_js_arg_decl){out, mjs_is_array, _js_passthrough, "array", NULL, NULL})
static inline bool _js_validate_struct(struct mjs* mjs, mjs_val_t val, const void* extra) {
JsForeignMagic expected_magic = (JsForeignMagic)(size_t)extra;
JsForeignMagic struct_magic = *(JsForeignMagic*)mjs_get_ptr(mjs, val);
return struct_magic == expected_magic;
}
#define JS_ARG_STRUCT(type, out) \
((_js_arg_decl){ \
out, \
mjs_is_foreign, \
_js_to_ptr, \
#type, \
_js_validate_struct, \
(void*)JsForeignMagic##_##type})
static inline bool _js_validate_obj_w_struct(struct mjs* mjs, mjs_val_t val, const void* extra) {
JsForeignMagic expected_magic = (JsForeignMagic)(size_t)extra;
JsForeignMagic struct_magic = *(JsForeignMagic*)JS_GET_INST(mjs, val);
return struct_magic == expected_magic;
}
#define JS_ARG_OBJ_WITH_STRUCT(type, out) \
((_js_arg_decl){ \
out, \
mjs_is_object, \
_js_passthrough, \
#type, \
_js_validate_obj_w_struct, \
(void*)JsForeignMagic##_##type})
static inline bool _js_validate_enum(struct mjs* mjs, mjs_val_t val, const void* extra) {
for(const JsEnumMapping* mapping = (JsEnumMapping*)extra + 1; mapping->name; mapping++)
if(strcmp(mapping->name, mjs_get_string(mjs, &val, NULL)) == 0) return true;
return false;
}
static inline void
_js_convert_enum(struct mjs* mjs, mjs_val_t* val, void* out, const void* extra) {
const JsEnumMapping* mapping = (JsEnumMapping*)extra;
size_t size = mapping->value; // get enum size from first entry
for(mapping++; mapping->name; mapping++) {
if(strcmp(mapping->name, mjs_get_string(mjs, val, NULL)) == 0) {
if(size == 1)
*(uint8_t*)out = mapping->value;
else if(size == 2)
*(uint16_t*)out = mapping->value;
else if(size == 4)
*(uint32_t*)out = mapping->value;
else if(size == 8)
*(uint64_t*)out = mapping->value;
return;
}
}
// unreachable, thanks to _js_validate_enum
}
#define JS_ARG_ENUM(var_name, name) \
((_js_arg_decl){ \
&var_name, \
mjs_is_string, \
_js_convert_enum, \
name " enum", \
_js_validate_enum, \
var_name##_mapping})
static inline bool _js_validate_object(struct mjs* mjs, mjs_val_t val, const void* extra) {
for(const JsObjectMapping* mapping = (JsObjectMapping*)extra; mapping->name; mapping++)
if(mjs_get(mjs, val, mapping->name, ~0) == MJS_UNDEFINED) return false;
return true;
}
static inline void
_js_convert_object(struct mjs* mjs, mjs_val_t* val, void* out, const void* extra) {
const JsObjectMapping* mapping = (JsObjectMapping*)extra;
for(; mapping->name; mapping++) {
mjs_val_t field_val = mjs_get(mjs, *val, mapping->name, ~0);
*(mjs_val_t*)((uint8_t*)out + mapping->offset) = field_val;
}
}
#define JS_ARG_OBJECT(var_name, name) \
((_js_arg_decl){ \
&var_name, \
mjs_is_object, \
_js_convert_object, \
name " object", \
_js_validate_object, \
var_name##_mapping})
/**
* @brief Validates and converts a JS value with a declarative interface
*
* Example: `int32_t my_value; JS_CONVERT_OR_RETURN(mjs, &mjs_val, JS_ARG_INT32(&my_value), "value source");`
*
* @warning This macro executes `return;` by design in case of a validation failure
*/
#define JS_CONVERT_OR_RETURN(mjs, value, decl, source, ...) \
if(decl.validator) \
if(!decl.validator(*value)) \
JS_ERROR_AND_RETURN( \
mjs, \
MJS_BAD_ARGS_ERROR, \
source ": expected %s", \
##__VA_ARGS__, \
decl.expected_type); \
if(decl.extended_validator) \
if(!decl.extended_validator(mjs, *value, decl.extra_data)) \
JS_ERROR_AND_RETURN( \
mjs, \
MJS_BAD_ARGS_ERROR, \
source ": expected %s", \
##__VA_ARGS__, \
decl.expected_type); \
decl.converter(mjs, value, decl.out, decl.extra_data);
//-V:JS_FETCH_ARGS_OR_RETURN:1008
/**
* @brief Fetches and validates the arguments passed to a JS function
*
* Example: `int32_t my_arg; JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_INT32(&my_arg));`
*
* @warning This macro executes `return;` by design in case of an argument count
* mismatch or a validation failure
*/
#define JS_FETCH_ARGS_OR_RETURN(mjs, arg_operator, ...) \
_js_arg_decl _js_args[] = {__VA_ARGS__}; \
int _js_arg_cnt = COUNT_OF(_js_args); \
mjs_val_t _js_arg_vals[_js_arg_cnt]; \
if(!(mjs_nargs(mjs) arg_operator _js_arg_cnt)) \
JS_ERROR_AND_RETURN( \
mjs, \
MJS_BAD_ARGS_ERROR, \
"expected %s%d arguments, got %d", \
#arg_operator, \
_js_arg_cnt, \
mjs_nargs(mjs)); \
for(int _i = 0; _i < _js_arg_cnt; _i++) { \
_js_arg_vals[_i] = mjs_arg(mjs, _i); \
JS_CONVERT_OR_RETURN(mjs, &_js_arg_vals[_i], _js_args[_i], "argument %d", _i); \
}
/** /**
* @brief Prepends an error, sets the JS return value to `undefined` and returns * @brief Prepends an error, sets the JS return value to `undefined` and returns
* from the C function * from the C function
@@ -359,3 +143,7 @@ void js_does_sdk_support(struct mjs* mjs);
* @brief `checkSdkFeatures` function * @brief `checkSdkFeatures` function
*/ */
void js_check_sdk_features(struct mjs* mjs); void js_check_sdk_features(struct mjs* mjs);
#ifdef __cplusplus
}
#endif

View File

@@ -198,18 +198,15 @@ static void js_require(struct mjs* mjs) {
} }
static void js_parse_int(struct mjs* mjs) { static void js_parse_int(struct mjs* mjs) {
const char* str; static const JsValueDeclaration js_parse_int_arg_list[] = {
JS_FETCH_ARGS_OR_RETURN(mjs, JS_AT_LEAST, JS_ARG_STR(&str)); JS_VALUE_SIMPLE(JsValueTypeString),
JS_VALUE_SIMPLE_W_DEFAULT(JsValueTypeInt32, int32_val, 10),
};
static const JsValueArguments js_parse_int_args = JS_VALUE_ARGS(js_parse_int_arg_list);
int32_t base = 10; const char* str;
if(mjs_nargs(mjs) >= 2) { int32_t base;
mjs_val_t base_arg = mjs_arg(mjs, 1); JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_parse_int_args, &str, &base);
if(!mjs_is_number(base_arg)) {
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "Base must be a number");
mjs_return(mjs, MJS_UNDEFINED);
}
base = mjs_get_int(mjs, base_arg);
}
int32_t num; int32_t num;
if(strint_to_int32(str, NULL, &num, base) != StrintParseNoError) { if(strint_to_int32(str, NULL, &num, base) != StrintParseNoError) {

View File

@@ -11,6 +11,10 @@
#include <mjs_primitive_public.h> #include <mjs_primitive_public.h>
#include <mjs_array_buf_public.h> #include <mjs_array_buf_public.h>
#ifdef __cplusplus
extern "C" {
#endif
#define INST_PROP_NAME "_" #define INST_PROP_NAME "_"
typedef enum { typedef enum {
@@ -23,3 +27,7 @@ bool js_delay_with_flags(struct mjs* mjs, uint32_t time);
void js_flags_set(struct mjs* mjs, uint32_t flags); void js_flags_set(struct mjs* mjs, uint32_t flags);
uint32_t js_flags_wait(struct mjs* mjs, uint32_t flags, uint32_t timeout); uint32_t js_flags_wait(struct mjs* mjs, uint32_t flags, uint32_t timeout);
#ifdef __cplusplus
}
#endif

View File

@@ -144,10 +144,16 @@ static void js_event_loop_subscribe(struct mjs* mjs) {
JsEventLoop* module = JS_GET_CONTEXT(mjs); JsEventLoop* module = JS_GET_CONTEXT(mjs);
// get arguments // get arguments
static const JsValueDeclaration js_loop_subscribe_arg_list[] = {
JS_VALUE_SIMPLE(JsValueTypeRawPointer),
JS_VALUE_SIMPLE(JsValueTypeFunction),
};
static const JsValueArguments js_loop_subscribe_args =
JS_VALUE_ARGS(js_loop_subscribe_arg_list);
JsEventLoopContract* contract; JsEventLoopContract* contract;
mjs_val_t callback; mjs_val_t callback;
JS_FETCH_ARGS_OR_RETURN( JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_loop_subscribe_args, &contract, &callback);
mjs, JS_AT_LEAST, JS_ARG_STRUCT(JsEventLoopContract, &contract), JS_ARG_FN(&callback));
// create subscription object // create subscription object
JsEventLoopSubscription* subscription = malloc(sizeof(JsEventLoopSubscription)); JsEventLoopSubscription* subscription = malloc(sizeof(JsEventLoopSubscription));
@@ -242,20 +248,22 @@ static void js_event_loop_stop(struct mjs* mjs) {
* event * event
*/ */
static void js_event_loop_timer(struct mjs* mjs) { static void js_event_loop_timer(struct mjs* mjs) {
// get arguments static const JsValueEnumVariant js_loop_timer_mode_variants[] = {
const char* mode_str; {"periodic", FuriEventLoopTimerTypePeriodic},
int32_t interval; {"oneshot", FuriEventLoopTimerTypeOnce},
JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&mode_str), JS_ARG_INT32(&interval)); };
JsEventLoop* module = JS_GET_CONTEXT(mjs);
static const JsValueDeclaration js_loop_timer_arg_list[] = {
JS_VALUE_ENUM(FuriEventLoopTimerType, js_loop_timer_mode_variants),
JS_VALUE_SIMPLE(JsValueTypeInt32),
};
static const JsValueArguments js_loop_timer_args = JS_VALUE_ARGS(js_loop_timer_arg_list);
FuriEventLoopTimerType mode; FuriEventLoopTimerType mode;
if(strcasecmp(mode_str, "periodic") == 0) { int32_t interval;
mode = FuriEventLoopTimerTypePeriodic; JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_loop_timer_args, &mode, &interval);
} else if(strcasecmp(mode_str, "oneshot") == 0) {
mode = FuriEventLoopTimerTypeOnce; JsEventLoop* module = JS_GET_CONTEXT(mjs);
} else {
JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "argument 0: unknown mode");
}
// make timer contract // make timer contract
JsEventLoopContract* contract = malloc(sizeof(JsEventLoopContract)); JsEventLoopContract* contract = malloc(sizeof(JsEventLoopContract));
@@ -293,8 +301,14 @@ static mjs_val_t
*/ */
static void js_event_loop_queue_send(struct mjs* mjs) { static void js_event_loop_queue_send(struct mjs* mjs) {
// get arguments // get arguments
static const JsValueDeclaration js_loop_q_send_arg_list[] = {
JS_VALUE_SIMPLE(JsValueTypeAny),
};
static const JsValueArguments js_loop_q_send_args = JS_VALUE_ARGS(js_loop_q_send_arg_list);
mjs_val_t message; mjs_val_t message;
JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_ANY(&message)); JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_loop_q_send_args, &message);
JsEventLoopContract* contract = JS_GET_CONTEXT(mjs); JsEventLoopContract* contract = JS_GET_CONTEXT(mjs);
// send message // send message
@@ -311,8 +325,14 @@ static void js_event_loop_queue_send(struct mjs* mjs) {
*/ */
static void js_event_loop_queue(struct mjs* mjs) { static void js_event_loop_queue(struct mjs* mjs) {
// get arguments // get arguments
static const JsValueDeclaration js_loop_q_arg_list[] = {
JS_VALUE_SIMPLE(JsValueTypeInt32),
};
static const JsValueArguments js_loop_q_args = JS_VALUE_ARGS(js_loop_q_arg_list);
int32_t length; int32_t length;
JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_INT32(&length)); JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_loop_q_args, &length);
JsEventLoop* module = JS_GET_CONTEXT(mjs); JsEventLoop* module = JS_GET_CONTEXT(mjs);
// make queue contract // make queue contract

View File

@@ -54,83 +54,114 @@ static void js_gpio_int_cb(void* arg) {
* ``` * ```
*/ */
static void js_gpio_init(struct mjs* mjs) { static void js_gpio_init(struct mjs* mjs) {
// deconstruct mode object // direction variants
mjs_val_t mode_arg; typedef enum {
JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_OBJ(&mode_arg)); JsGpioDirectionIn,
mjs_val_t direction_arg = mjs_get(mjs, mode_arg, "direction", ~0); JsGpioDirectionOut,
mjs_val_t out_mode_arg = mjs_get(mjs, mode_arg, "outMode", ~0); } JsGpioDirection;
mjs_val_t in_mode_arg = mjs_get(mjs, mode_arg, "inMode", ~0); static const JsValueEnumVariant js_gpio_direction_variants[] = {
mjs_val_t edge_arg = mjs_get(mjs, mode_arg, "edge", ~0); {"in", JsGpioDirectionIn},
mjs_val_t pull_arg = mjs_get(mjs, mode_arg, "pull", ~0); {"out", JsGpioDirectionOut},
};
static const JsValueDeclaration js_gpio_direction =
JS_VALUE_ENUM(JsGpioDirection, js_gpio_direction_variants);
// get strings // inMode variants
const char* direction = mjs_get_string(mjs, &direction_arg, NULL); typedef enum {
const char* out_mode = mjs_get_string(mjs, &out_mode_arg, NULL); JsGpioInModeAnalog = (0 << 0),
const char* in_mode = mjs_get_string(mjs, &in_mode_arg, NULL); JsGpioInModePlainDigital = (1 << 0),
const char* edge = mjs_get_string(mjs, &edge_arg, NULL); JsGpioInModeInterrupt = (2 << 0),
const char* pull = mjs_get_string(mjs, &pull_arg, NULL); JsGpioInModeEvent = (3 << 0),
if(!direction) } JsGpioInMode;
JS_ERROR_AND_RETURN( static const JsValueEnumVariant js_gpio_in_mode_variants[] = {
mjs, MJS_BAD_ARGS_ERROR, "Expected string in \"direction\" field of mode object"); {"analog", JsGpioInModeAnalog},
if(!out_mode) out_mode = "open_drain"; {"plain_digital", JsGpioInModePlainDigital},
if(!in_mode) in_mode = "plain_digital"; {"interrupt", JsGpioInModeInterrupt},
if(!edge) edge = "rising"; {"event", JsGpioInModeEvent},
};
static const JsValueDeclaration js_gpio_in_mode =
JS_VALUE_ENUM_W_DEFAULT(JsGpioInMode, js_gpio_in_mode_variants, JsGpioInModePlainDigital);
// outMode variants
typedef enum {
JsGpioOutModePushPull,
JsGpioOutModeOpenDrain,
} JsGpioOutMode;
static const JsValueEnumVariant js_gpio_out_mode_variants[] = {
{"push_pull", JsGpioOutModePushPull},
{"open_drain", JsGpioOutModeOpenDrain},
};
static const JsValueDeclaration js_gpio_out_mode =
JS_VALUE_ENUM_W_DEFAULT(JsGpioOutMode, js_gpio_out_mode_variants, JsGpioOutModeOpenDrain);
// edge variants
typedef enum {
JsGpioEdgeRising = (0 << 2),
JsGpioEdgeFalling = (1 << 2),
JsGpioEdgeBoth = (2 << 2),
} JsGpioEdge;
static const JsValueEnumVariant js_gpio_edge_variants[] = {
{"rising", JsGpioEdgeRising},
{"falling", JsGpioEdgeFalling},
{"both", JsGpioEdgeBoth},
};
static const JsValueDeclaration js_gpio_edge =
JS_VALUE_ENUM_W_DEFAULT(JsGpioEdge, js_gpio_edge_variants, JsGpioEdgeRising);
// pull variants
static const JsValueEnumVariant js_gpio_pull_variants[] = {
{"up", GpioPullUp},
{"down", GpioPullDown},
};
static const JsValueDeclaration js_gpio_pull =
JS_VALUE_ENUM_W_DEFAULT(GpioPull, js_gpio_pull_variants, GpioPullNo);
// complete mode object
static const JsValueObjectField js_gpio_mode_object_fields[] = {
{"direction", &js_gpio_direction},
{"inMode", &js_gpio_in_mode},
{"outMode", &js_gpio_out_mode},
{"edge", &js_gpio_edge},
{"pull", &js_gpio_pull},
};
// function args
static const JsValueDeclaration js_gpio_init_arg_list[] = {
JS_VALUE_OBJECT_W_DEFAULTS(js_gpio_mode_object_fields),
};
static const JsValueArguments js_gpio_init_args = JS_VALUE_ARGS(js_gpio_init_arg_list);
JsGpioDirection direction;
JsGpioInMode in_mode;
JsGpioOutMode out_mode;
JsGpioEdge edge;
GpioPull pull;
JS_VALUE_PARSE_ARGS_OR_RETURN(
mjs, &js_gpio_init_args, &direction, &in_mode, &out_mode, &edge, &pull);
// convert strings to mode
GpioMode mode; GpioMode mode;
if(strcmp(direction, "out") == 0) { if(direction == JsGpioDirectionOut) {
if(strcmp(out_mode, "push_pull") == 0) static const GpioMode js_gpio_out_mode_lut[] = {
mode = GpioModeOutputPushPull; [JsGpioOutModePushPull] = GpioModeOutputPushPull,
else if(strcmp(out_mode, "open_drain") == 0) [JsGpioOutModeOpenDrain] = GpioModeOutputOpenDrain,
mode = GpioModeOutputOpenDrain; };
else mode = js_gpio_out_mode_lut[out_mode];
JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "invalid outMode");
} else if(strcmp(direction, "in") == 0) {
if(strcmp(in_mode, "analog") == 0) {
mode = GpioModeAnalog;
} else if(strcmp(in_mode, "plain_digital") == 0) {
mode = GpioModeInput;
} else if(strcmp(in_mode, "interrupt") == 0) {
if(strcmp(edge, "rising") == 0)
mode = GpioModeInterruptRise;
else if(strcmp(edge, "falling") == 0)
mode = GpioModeInterruptFall;
else if(strcmp(edge, "both") == 0)
mode = GpioModeInterruptRiseFall;
else
JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "invalid edge");
} else if(strcmp(in_mode, "event") == 0) {
if(strcmp(edge, "rising") == 0)
mode = GpioModeEventRise;
else if(strcmp(edge, "falling") == 0)
mode = GpioModeEventFall;
else if(strcmp(edge, "both") == 0)
mode = GpioModeEventRiseFall;
else
JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "invalid edge");
} else { } else {
JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "invalid inMode"); static const GpioMode js_gpio_in_mode_lut[] = {
} [JsGpioInModeAnalog] = GpioModeAnalog,
} else { [JsGpioInModePlainDigital] = GpioModeInput,
JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "invalid direction"); [JsGpioInModeInterrupt | JsGpioEdgeRising] = GpioModeInterruptRise,
[JsGpioInModeInterrupt | JsGpioEdgeFalling] = GpioModeInterruptFall,
[JsGpioInModeInterrupt | JsGpioEdgeBoth] = GpioModeInterruptRiseFall,
[JsGpioInModeEvent | JsGpioEdgeRising] = GpioModeEventRise,
[JsGpioInModeEvent | JsGpioEdgeFalling] = GpioModeEventFall,
[JsGpioInModeEvent | JsGpioEdgeBoth] = GpioModeEventRiseFall,
};
mode = js_gpio_in_mode_lut[in_mode | edge];
} }
// convert pull
GpioPull pull_mode;
if(!pull) {
pull_mode = GpioPullNo;
} else if(strcmp(pull, "up") == 0) {
pull_mode = GpioPullUp;
} else if(strcmp(pull, "down") == 0) {
pull_mode = GpioPullDown;
} else {
JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "invalid pull");
}
// init GPIO
JsGpioPinInst* manager_data = JS_GET_CONTEXT(mjs); JsGpioPinInst* manager_data = JS_GET_CONTEXT(mjs);
furi_hal_gpio_init(manager_data->pin, mode, pull_mode, GpioSpeedVeryHigh); furi_hal_gpio_init(manager_data->pin, mode, pull, GpioSpeedVeryHigh);
mjs_return(mjs, MJS_UNDEFINED);
} }
/** /**
@@ -146,8 +177,13 @@ static void js_gpio_init(struct mjs* mjs) {
* ``` * ```
*/ */
static void js_gpio_write(struct mjs* mjs) { static void js_gpio_write(struct mjs* mjs) {
static const JsValueDeclaration js_gpio_write_arg_list[] = {
JS_VALUE_SIMPLE(JsValueTypeBool),
};
static const JsValueArguments js_gpio_write_args = JS_VALUE_ARGS(js_gpio_write_arg_list);
bool level; bool level;
JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_BOOL(&level)); JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_gpio_write_args, &level);
JsGpioPinInst* manager_data = JS_GET_CONTEXT(mjs); JsGpioPinInst* manager_data = JS_GET_CONTEXT(mjs);
furi_hal_gpio_write(manager_data->pin, level); furi_hal_gpio_write(manager_data->pin, level);
mjs_return(mjs, MJS_UNDEFINED); mjs_return(mjs, MJS_UNDEFINED);
@@ -261,9 +297,16 @@ static void js_gpio_is_pwm_supported(struct mjs* mjs) {
* ``` * ```
*/ */
static void js_gpio_pwm_write(struct mjs* mjs) { static void js_gpio_pwm_write(struct mjs* mjs) {
JsGpioPinInst* manager_data = JS_GET_CONTEXT(mjs); static const JsValueDeclaration js_gpio_pwm_write_arg_list[] = {
JS_VALUE_SIMPLE(JsValueTypeInt32),
JS_VALUE_SIMPLE(JsValueTypeInt32),
};
static const JsValueArguments js_gpio_pwm_write_args =
JS_VALUE_ARGS(js_gpio_pwm_write_arg_list);
int32_t frequency, duty; int32_t frequency, duty;
JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_INT32(&frequency), JS_ARG_INT32(&duty)); JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_gpio_pwm_write_args, &frequency, &duty);
JsGpioPinInst* manager_data = JS_GET_CONTEXT(mjs);
if(manager_data->pwm_output == FuriHalPwmOutputIdNone) { if(manager_data->pwm_output == FuriHalPwmOutputIdNone) {
JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "PWM is not supported on this pin"); JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "PWM is not supported on this pin");
} }
@@ -326,8 +369,13 @@ static void js_gpio_pwm_stop(struct mjs* mjs) {
* ``` * ```
*/ */
static void js_gpio_get(struct mjs* mjs) { static void js_gpio_get(struct mjs* mjs) {
static const JsValueDeclaration js_gpio_get_arg_list[] = {
JS_VALUE_SIMPLE(JsValueTypeAny),
};
static const JsValueArguments js_gpio_get_args = JS_VALUE_ARGS(js_gpio_get_arg_list);
mjs_val_t name_arg; mjs_val_t name_arg;
JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_ANY(&name_arg)); JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_gpio_get_args, &name_arg);
const char* name_string = mjs_get_string(mjs, &name_arg, NULL); const char* name_string = mjs_get_string(mjs, &name_arg, NULL);
const GpioPinRecord* pin_record = NULL; const GpioPinRecord* pin_record = NULL;

View File

@@ -3,8 +3,14 @@
#include <assets_icons.h> #include <assets_icons.h>
static void js_gui_file_picker_pick_file(struct mjs* mjs) { static void js_gui_file_picker_pick_file(struct mjs* mjs) {
static const JsValueDeclaration js_picker_arg_list[] = {
JS_VALUE_SIMPLE(JsValueTypeString),
JS_VALUE_SIMPLE(JsValueTypeString),
};
static const JsValueArguments js_picker_args = JS_VALUE_ARGS(js_picker_arg_list);
const char *base_path, *extension; const char *base_path, *extension;
JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&base_path), JS_ARG_STR(&extension)); JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_picker_args, &base_path, &extension);
DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
const DialogsFileBrowserOptions browser_options = { const DialogsFileBrowserOptions browser_options = {

View File

@@ -39,9 +39,14 @@ typedef struct {
FxbmIconWrapperList_t fxbm_list; FxbmIconWrapperList_t fxbm_list;
} JsGuiIconInst; } JsGuiIconInst;
static const JsValueDeclaration js_icon_get_arg_list[] = {
JS_VALUE_SIMPLE(JsValueTypeString),
};
static const JsValueArguments js_icon_get_args = JS_VALUE_ARGS(js_icon_get_arg_list);
static void js_gui_icon_get_builtin(struct mjs* mjs) { static void js_gui_icon_get_builtin(struct mjs* mjs) {
const char* icon_name; const char* icon_name;
JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&icon_name)); JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_icon_get_args, &icon_name);
for(size_t i = 0; i < COUNT_OF(builtin_icons); i++) { for(size_t i = 0; i < COUNT_OF(builtin_icons); i++) {
if(strcmp(icon_name, builtin_icons[i].name) == 0) { if(strcmp(icon_name, builtin_icons[i].name) == 0) {
@@ -55,7 +60,7 @@ static void js_gui_icon_get_builtin(struct mjs* mjs) {
static void js_gui_icon_load_fxbm(struct mjs* mjs) { static void js_gui_icon_load_fxbm(struct mjs* mjs) {
const char* fxbm_path; const char* fxbm_path;
JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&fxbm_path)); JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_icon_get_args, &fxbm_path);
Storage* storage = furi_record_open(RECORD_STORAGE); Storage* storage = furi_record_open(RECORD_STORAGE);
File* file = storage_file_alloc(storage); File* file = storage_file_alloc(storage);

View File

@@ -68,8 +68,14 @@ static bool js_gui_vd_nav_callback(void* context) {
* @brief `viewDispatcher.sendCustom` * @brief `viewDispatcher.sendCustom`
*/ */
static void js_gui_vd_send_custom(struct mjs* mjs) { static void js_gui_vd_send_custom(struct mjs* mjs) {
static const JsValueDeclaration js_gui_vd_send_custom_arg_list[] = {
JS_VALUE_SIMPLE(JsValueTypeInt32),
};
static const JsValueArguments js_gui_vd_send_custom_args =
JS_VALUE_ARGS(js_gui_vd_send_custom_arg_list);
int32_t event; int32_t event;
JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_INT32(&event)); JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_gui_vd_send_custom_args, &event);
JsGui* module = JS_GET_CONTEXT(mjs); JsGui* module = JS_GET_CONTEXT(mjs);
view_dispatcher_send_custom_event(module->dispatcher, (uint32_t)event); view_dispatcher_send_custom_event(module->dispatcher, (uint32_t)event);
@@ -79,15 +85,25 @@ static void js_gui_vd_send_custom(struct mjs* mjs) {
* @brief `viewDispatcher.sendTo` * @brief `viewDispatcher.sendTo`
*/ */
static void js_gui_vd_send_to(struct mjs* mjs) { static void js_gui_vd_send_to(struct mjs* mjs) {
enum { typedef enum {
SendDirToFront, JsSendDirToFront,
SendDirToBack, JsSendDirToBack,
} send_direction; } JsSendDir;
JS_ENUM_MAP(send_direction, {"front", SendDirToFront}, {"back", SendDirToBack}); static const JsValueEnumVariant js_send_dir_variants[] = {
JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_ENUM(send_direction, "SendDirection")); {"front", JsSendDirToFront},
{"back", JsSendDirToBack},
};
static const JsValueDeclaration js_gui_vd_send_to_arg_list[] = {
JS_VALUE_ENUM(JsSendDir, js_send_dir_variants),
};
static const JsValueArguments js_gui_vd_send_to_args =
JS_VALUE_ARGS(js_gui_vd_send_to_arg_list);
JsSendDir send_direction;
JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_gui_vd_send_to_args, &send_direction);
JsGui* module = JS_GET_CONTEXT(mjs); JsGui* module = JS_GET_CONTEXT(mjs);
if(send_direction == SendDirToBack) { if(send_direction == JsSendDirToBack) {
view_dispatcher_send_to_back(module->dispatcher); view_dispatcher_send_to_back(module->dispatcher);
} else { } else {
view_dispatcher_send_to_front(module->dispatcher); view_dispatcher_send_to_front(module->dispatcher);
@@ -98,8 +114,15 @@ static void js_gui_vd_send_to(struct mjs* mjs) {
* @brief `viewDispatcher.switchTo` * @brief `viewDispatcher.switchTo`
*/ */
static void js_gui_vd_switch_to(struct mjs* mjs) { static void js_gui_vd_switch_to(struct mjs* mjs) {
static const JsValueDeclaration js_gui_vd_switch_to_arg_list[] = {
JS_VALUE_SIMPLE(JsValueTypeAny),
};
static const JsValueArguments js_gui_vd_switch_to_args =
JS_VALUE_ARGS(js_gui_vd_switch_to_arg_list);
mjs_val_t view; mjs_val_t view;
JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_OBJ(&view)); JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_gui_vd_switch_to_args, &view);
JsGuiViewData* view_data = JS_GET_INST(mjs, view); JsGuiViewData* view_data = JS_GET_INST(mjs, view);
mjs_val_t vd_obj = mjs_get_this(mjs); mjs_val_t vd_obj = mjs_get_this(mjs);
JsGui* module = JS_GET_INST(mjs, vd_obj); JsGui* module = JS_GET_INST(mjs, vd_obj);
@@ -267,9 +290,16 @@ static bool
* @brief `View.set` * @brief `View.set`
*/ */
static void js_gui_view_set(struct mjs* mjs) { static void js_gui_view_set(struct mjs* mjs) {
static const JsValueDeclaration js_gui_view_set_arg_list[] = {
JS_VALUE_SIMPLE(JsValueTypeString),
JS_VALUE_SIMPLE(JsValueTypeAny),
};
static const JsValueArguments js_gui_view_set_args = JS_VALUE_ARGS(js_gui_view_set_arg_list);
const char* name; const char* name;
mjs_val_t value; mjs_val_t value;
JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&name), JS_ARG_ANY(&value)); JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_gui_view_set_args, &name, &value);
JsGuiViewData* data = JS_GET_CONTEXT(mjs); JsGuiViewData* data = JS_GET_CONTEXT(mjs);
bool success = js_gui_view_assign(mjs, name, value, data); bool success = js_gui_view_assign(mjs, name, value, data);
UNUSED(success); UNUSED(success);
@@ -280,12 +310,19 @@ static void js_gui_view_set(struct mjs* mjs) {
* @brief `View.addChild` * @brief `View.addChild`
*/ */
static void js_gui_view_add_child(struct mjs* mjs) { static void js_gui_view_add_child(struct mjs* mjs) {
static const JsValueDeclaration js_gui_view_add_child_arg_list[] = {
JS_VALUE_SIMPLE(JsValueTypeAny),
};
static const JsValueArguments js_gui_view_add_child_args =
JS_VALUE_ARGS(js_gui_view_add_child_arg_list);
mjs_val_t child;
JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_gui_view_add_child_args, &child);
JsGuiViewData* data = JS_GET_CONTEXT(mjs); JsGuiViewData* data = JS_GET_CONTEXT(mjs);
if(!data->descriptor->add_child || !data->descriptor->reset_children) if(!data->descriptor->add_child || !data->descriptor->reset_children)
JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "this View can't have children"); JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "this View can't have children");
mjs_val_t child;
JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_ANY(&child));
bool success = data->descriptor->add_child(mjs, data->specific_view, data->custom_data, child); bool success = data->descriptor->add_child(mjs, data->specific_view, data->custom_data, child);
UNUSED(success); UNUSED(success);
mjs_return(mjs, MJS_UNDEFINED); mjs_return(mjs, MJS_UNDEFINED);
@@ -307,12 +344,19 @@ static void js_gui_view_reset_children(struct mjs* mjs) {
* @brief `View.setChildren` * @brief `View.setChildren`
*/ */
static void js_gui_view_set_children(struct mjs* mjs) { static void js_gui_view_set_children(struct mjs* mjs) {
static const JsValueDeclaration js_gui_view_set_children_arg_list[] = {
JS_VALUE_SIMPLE(JsValueTypeAnyArray),
};
static const JsValueArguments js_gui_view_set_children_args =
JS_VALUE_ARGS(js_gui_view_set_children_arg_list);
mjs_val_t children;
JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_gui_view_set_children_args, &children);
JsGuiViewData* data = JS_GET_CONTEXT(mjs); JsGuiViewData* data = JS_GET_CONTEXT(mjs);
if(!data->descriptor->add_child || !data->descriptor->reset_children) if(!data->descriptor->add_child || !data->descriptor->reset_children)
JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "this View can't have children"); JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "this View can't have children");
mjs_val_t children;
JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_ARR(&children));
js_gui_view_internal_set_children(mjs, children, data); js_gui_view_internal_set_children(mjs, children, data);
} }
@@ -365,7 +409,6 @@ static mjs_val_t js_gui_make_view(struct mjs* mjs, const JsViewDescriptor* descr
* @brief `ViewFactory.make` * @brief `ViewFactory.make`
*/ */
static void js_gui_vf_make(struct mjs* mjs) { static void js_gui_vf_make(struct mjs* mjs) {
JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY); // 0 args
const JsViewDescriptor* descriptor = JS_GET_CONTEXT(mjs); const JsViewDescriptor* descriptor = JS_GET_CONTEXT(mjs);
mjs_return(mjs, js_gui_make_view(mjs, descriptor)); mjs_return(mjs, js_gui_make_view(mjs, descriptor));
} }
@@ -374,8 +417,15 @@ static void js_gui_vf_make(struct mjs* mjs) {
* @brief `ViewFactory.makeWith` * @brief `ViewFactory.makeWith`
*/ */
static void js_gui_vf_make_with(struct mjs* mjs) { static void js_gui_vf_make_with(struct mjs* mjs) {
mjs_val_t props; static const JsValueDeclaration js_gui_vf_make_with_arg_list[] = {
JS_FETCH_ARGS_OR_RETURN(mjs, JS_AT_LEAST, JS_ARG_OBJ(&props)); JS_VALUE_SIMPLE(JsValueTypeAnyObject),
JS_VALUE_SIMPLE(JsValueTypeAny),
};
static const JsValueArguments js_gui_vf_make_with_args =
JS_VALUE_ARGS(js_gui_vf_make_with_arg_list);
mjs_val_t props, children;
JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_gui_vf_make_with_args, &props, &children);
const JsViewDescriptor* descriptor = JS_GET_CONTEXT(mjs); const JsViewDescriptor* descriptor = JS_GET_CONTEXT(mjs);
// make the object like normal // make the object like normal
@@ -396,14 +446,10 @@ static void js_gui_vf_make_with(struct mjs* mjs) {
} }
// assign children // assign children
if(mjs_nargs(mjs) >= 2) { if(mjs_is_array(children)) {
if(!data->descriptor->add_child || !data->descriptor->reset_children) if(!data->descriptor->add_child || !data->descriptor->reset_children)
JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "this View can't have children"); JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "this View can't have children");
mjs_val_t children = mjs_arg(mjs, 1);
if(!mjs_is_array(children))
JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "argument 1: expected array");
if(!js_gui_view_internal_set_children(mjs, children, data)) return; if(!js_gui_view_internal_set_children(mjs, children, data)) return;
} }

View File

@@ -35,60 +35,62 @@ static void
} }
static void js_serial_setup(struct mjs* mjs) { static void js_serial_setup(struct mjs* mjs) {
FuriHalSerialId serial_id; static const JsValueEnumVariant js_serial_id_variants[] = {
int32_t baudrate; {"lpuart", FuriHalSerialIdLpuart},
JS_ENUM_MAP(serial_id, {"lpuart", FuriHalSerialIdLpuart}, {"usart", FuriHalSerialIdUsart}); {"usart", FuriHalSerialIdUsart},
JS_FETCH_ARGS_OR_RETURN( };
mjs, JS_AT_LEAST, JS_ARG_ENUM(serial_id, "SerialId"), JS_ARG_INT32(&baudrate));
FuriHalSerialDataBits data_bits = FuriHalSerialDataBits8; static const JsValueEnumVariant js_serial_data_bit_variants[] = {
FuriHalSerialParity parity = FuriHalSerialParityNone;
FuriHalSerialStopBits stop_bits = FuriHalSerialStopBits1;
if(mjs_nargs(mjs) > 2) {
struct framing {
mjs_val_t data_bits;
mjs_val_t parity;
mjs_val_t stop_bits;
} framing;
JS_OBJ_MAP(
framing,
{"dataBits", offsetof(struct framing, data_bits)},
{"parity", offsetof(struct framing, parity)},
{"stopBits", offsetof(struct framing, stop_bits)});
JS_ENUM_MAP(
data_bits,
{"6", FuriHalSerialDataBits6}, {"6", FuriHalSerialDataBits6},
{"7", FuriHalSerialDataBits7}, {"7", FuriHalSerialDataBits7},
{"8", FuriHalSerialDataBits8}, {"8", FuriHalSerialDataBits8},
{"9", FuriHalSerialDataBits9}); {"9", FuriHalSerialDataBits9},
JS_ENUM_MAP( };
parity, static const JsValueDeclaration js_serial_data_bits = JS_VALUE_ENUM_W_DEFAULT(
FuriHalSerialDataBits, js_serial_data_bit_variants, FuriHalSerialDataBits8);
static const JsValueEnumVariant js_serial_parity_variants[] = {
{"none", FuriHalSerialParityNone}, {"none", FuriHalSerialParityNone},
{"even", FuriHalSerialParityEven}, {"even", FuriHalSerialParityEven},
{"odd", FuriHalSerialParityOdd}); {"odd", FuriHalSerialParityOdd},
JS_ENUM_MAP( };
stop_bits, static const JsValueDeclaration js_serial_parity = JS_VALUE_ENUM_W_DEFAULT(
FuriHalSerialParity, js_serial_parity_variants, FuriHalSerialParityNone);
static const JsValueEnumVariant js_serial_stop_bit_variants[] = {
{"0.5", FuriHalSerialStopBits0_5}, {"0.5", FuriHalSerialStopBits0_5},
{"1", FuriHalSerialStopBits1}, {"1", FuriHalSerialStopBits1},
{"1.5", FuriHalSerialStopBits1_5}, {"1.5", FuriHalSerialStopBits1_5},
{"2", FuriHalSerialStopBits2}); {"2", FuriHalSerialStopBits2},
mjs_val_t framing_obj = mjs_arg(mjs, 2); };
JS_CONVERT_OR_RETURN(mjs, &framing_obj, JS_ARG_OBJECT(framing, "Framing"), "argument 2"); static const JsValueDeclaration js_serial_stop_bits = JS_VALUE_ENUM_W_DEFAULT(
JS_CONVERT_OR_RETURN( FuriHalSerialStopBits, js_serial_stop_bit_variants, FuriHalSerialStopBits1);
mjs, &framing.data_bits, JS_ARG_ENUM(data_bits, "DataBits"), "argument 2: dataBits");
JS_CONVERT_OR_RETURN( static const JsValueObjectField js_serial_framing_fields[] = {
mjs, &framing.parity, JS_ARG_ENUM(parity, "Parity"), "argument 2: parity"); {"dataBits", &js_serial_data_bits},
JS_CONVERT_OR_RETURN( {"parity", &js_serial_parity},
mjs, &framing.stop_bits, JS_ARG_ENUM(stop_bits, "StopBits"), "argument 2: stopBits"); {"stopBits", &js_serial_stop_bits},
} };
static const JsValueDeclaration js_serial_setup_arg_list[] = {
JS_VALUE_ENUM(FuriHalSerialId, js_serial_id_variants),
JS_VALUE_SIMPLE(JsValueTypeInt32),
JS_VALUE_OBJECT_W_DEFAULTS(js_serial_framing_fields),
};
static const JsValueArguments js_serial_setup_args = JS_VALUE_ARGS(js_serial_setup_arg_list);
FuriHalSerialId serial_id;
int32_t baudrate;
FuriHalSerialDataBits data_bits = FuriHalSerialDataBits8;
FuriHalSerialParity parity = FuriHalSerialParityNone;
FuriHalSerialStopBits stop_bits = FuriHalSerialStopBits1;
JS_VALUE_PARSE_ARGS_OR_RETURN(
mjs, &js_serial_setup_args, &serial_id, &baudrate, &data_bits, &parity, &stop_bits);
JsSerialInst* serial = JS_GET_CONTEXT(mjs); JsSerialInst* serial = JS_GET_CONTEXT(mjs);
if(serial->setup_done) { if(serial->setup_done)
mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Serial is already configured"); JS_ERROR_AND_RETURN(mjs, MJS_INTERNAL_ERROR, "Serial is already configured");
mjs_return(mjs, MJS_UNDEFINED);
return;
}
expansion_disable(furi_record_open(RECORD_EXPANSION)); expansion_disable(furi_record_open(RECORD_EXPANSION));
furi_record_close(RECORD_EXPANSION); furi_record_close(RECORD_EXPANSION);
@@ -123,28 +125,20 @@ static void js_serial_deinit(JsSerialInst* js_serial) {
} }
static void js_serial_end(struct mjs* mjs) { static void js_serial_end(struct mjs* mjs) {
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); JsSerialInst* serial = JS_GET_CONTEXT(mjs);
JsSerialInst* serial = mjs_get_ptr(mjs, obj_inst);
furi_assert(serial); furi_assert(serial);
if(!serial->setup_done) { if(!serial->setup_done)
mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Serial is not configured"); JS_ERROR_AND_RETURN(mjs, MJS_INTERNAL_ERROR, "Serial is not configured");
mjs_return(mjs, MJS_UNDEFINED);
return;
}
js_serial_deinit(serial); js_serial_deinit(serial);
} }
static void js_serial_write(struct mjs* mjs) { static void js_serial_write(struct mjs* mjs) {
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); JsSerialInst* serial = JS_GET_CONTEXT(mjs);
JsSerialInst* serial = mjs_get_ptr(mjs, obj_inst);
furi_assert(serial); furi_assert(serial);
if(!serial->setup_done) { if(!serial->setup_done)
mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Serial is not configured"); JS_ERROR_AND_RETURN(mjs, MJS_INTERNAL_ERROR, "Serial is not configured");
mjs_return(mjs, MJS_UNDEFINED);
return;
}
bool args_correct = true; bool args_correct = true;
@@ -228,43 +222,20 @@ static size_t js_serial_receive(JsSerialInst* serial, char* buf, size_t len, uin
return bytes_read; return bytes_read;
} }
static const JsValueDeclaration js_serial_read_arg_list[] = {
JS_VALUE_SIMPLE(JsValueTypeInt32),
JS_VALUE_SIMPLE_W_DEFAULT(JsValueTypeInt32, int32_val, INT32_MAX),
};
static const JsValueArguments js_serial_read_args = JS_VALUE_ARGS(js_serial_read_arg_list);
static void js_serial_read(struct mjs* mjs) { static void js_serial_read(struct mjs* mjs) {
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); JsSerialInst* serial = JS_GET_CONTEXT(mjs);
JsSerialInst* serial = mjs_get_ptr(mjs, obj_inst);
furi_assert(serial); furi_assert(serial);
if(!serial->setup_done) { if(!serial->setup_done)
mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Serial is not configured"); JS_ERROR_AND_RETURN(mjs, MJS_INTERNAL_ERROR, "Serial is not configured");
mjs_return(mjs, MJS_UNDEFINED);
return;
}
size_t read_len = 0; int32_t read_len, timeout;
uint32_t timeout = FuriWaitForever; JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_serial_read_args, &read_len, &timeout);
do {
size_t num_args = mjs_nargs(mjs);
if(num_args == 1) {
mjs_val_t arg = mjs_arg(mjs, 0);
if(!mjs_is_number(arg)) {
break;
}
read_len = mjs_get_int32(mjs, arg);
} else if(num_args == 2) {
mjs_val_t len_arg = mjs_arg(mjs, 0);
mjs_val_t timeout_arg = mjs_arg(mjs, 1);
if((!mjs_is_number(len_arg)) || (!mjs_is_number(timeout_arg))) {
break;
}
read_len = mjs_get_int32(mjs, len_arg);
timeout = mjs_get_int32(mjs, timeout_arg);
}
} while(0);
if(read_len == 0) {
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "");
mjs_return(mjs, MJS_UNDEFINED);
return;
}
char* read_buf = malloc(read_len); char* read_buf = malloc(read_len);
size_t bytes_read = js_serial_receive(serial, read_buf, read_len, timeout); size_t bytes_read = js_serial_receive(serial, read_buf, read_len, timeout);
@@ -278,37 +249,19 @@ static void js_serial_read(struct mjs* mjs) {
} }
static void js_serial_readln(struct mjs* mjs) { static void js_serial_readln(struct mjs* mjs) {
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); JsSerialInst* serial = JS_GET_CONTEXT(mjs);
JsSerialInst* serial = mjs_get_ptr(mjs, obj_inst);
furi_assert(serial); furi_assert(serial);
if(!serial->setup_done) { if(!serial->setup_done)
mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Serial is not configured"); JS_ERROR_AND_RETURN(mjs, MJS_INTERNAL_ERROR, "Serial is not configured");
mjs_return(mjs, MJS_UNDEFINED);
return;
}
bool args_correct = false; static const JsValueDeclaration js_serial_readln_arg_list[] = {
uint32_t timeout = FuriWaitForever; JS_VALUE_SIMPLE(JsValueTypeInt32),
};
static const JsValueArguments js_serial_readln_args = JS_VALUE_ARGS(js_serial_readln_arg_list);
do { int32_t timeout;
size_t num_args = mjs_nargs(mjs); JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_serial_readln_args, &timeout);
if(num_args > 1) {
break;
} else if(num_args == 1) {
mjs_val_t arg = mjs_arg(mjs, 0);
if(!mjs_is_number(arg)) {
break;
}
timeout = mjs_get_int32(mjs, arg);
}
args_correct = true;
} while(0);
if(!args_correct) {
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "");
mjs_return(mjs, MJS_UNDEFINED);
return;
}
FuriString* rx_buf = furi_string_alloc(); FuriString* rx_buf = furi_string_alloc();
size_t bytes_read = 0; size_t bytes_read = 0;
char read_char = 0; char read_char = 0;
@@ -335,42 +288,13 @@ static void js_serial_readln(struct mjs* mjs) {
} }
static void js_serial_read_bytes(struct mjs* mjs) { static void js_serial_read_bytes(struct mjs* mjs) {
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); JsSerialInst* serial = JS_GET_CONTEXT(mjs);
JsSerialInst* serial = mjs_get_ptr(mjs, obj_inst);
furi_assert(serial); furi_assert(serial);
if(!serial->setup_done) { if(!serial->setup_done)
mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Serial is not configured"); JS_ERROR_AND_RETURN(mjs, MJS_INTERNAL_ERROR, "Serial is not configured");
mjs_return(mjs, MJS_UNDEFINED);
return;
}
size_t read_len = 0; int32_t read_len, timeout;
uint32_t timeout = FuriWaitForever; JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_serial_read_args, &read_len, &timeout);
do {
size_t num_args = mjs_nargs(mjs);
if(num_args == 1) {
mjs_val_t arg = mjs_arg(mjs, 0);
if(!mjs_is_number(arg)) {
break;
}
read_len = mjs_get_int32(mjs, arg);
} else if(num_args == 2) {
mjs_val_t len_arg = mjs_arg(mjs, 0);
mjs_val_t timeout_arg = mjs_arg(mjs, 1);
if((!mjs_is_number(len_arg)) || (!mjs_is_number(timeout_arg))) {
break;
}
read_len = mjs_get_int32(mjs, len_arg);
timeout = mjs_get_int32(mjs, timeout_arg);
}
} while(0);
if(read_len == 0) {
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "");
mjs_return(mjs, MJS_UNDEFINED);
return;
}
char* read_buf = malloc(read_len); char* read_buf = malloc(read_len);
size_t bytes_read = js_serial_receive(serial, read_buf, read_len, timeout); size_t bytes_read = js_serial_receive(serial, read_buf, read_len, timeout);
@@ -399,27 +323,19 @@ static char* js_serial_receive_any(JsSerialInst* serial, size_t* len, uint32_t t
} }
static void js_serial_read_any(struct mjs* mjs) { static void js_serial_read_any(struct mjs* mjs) {
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); JsSerialInst* serial = JS_GET_CONTEXT(mjs);
JsSerialInst* serial = mjs_get_ptr(mjs, obj_inst);
furi_assert(serial); furi_assert(serial);
if(!serial->setup_done) { if(!serial->setup_done)
mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Serial is not configured"); JS_ERROR_AND_RETURN(mjs, MJS_INTERNAL_ERROR, "Serial is not configured");
mjs_return(mjs, MJS_UNDEFINED);
return;
}
uint32_t timeout = FuriWaitForever; static const JsValueDeclaration js_serial_read_any_arg_list[] = {
JS_VALUE_SIMPLE_W_DEFAULT(JsValueTypeInt32, int32_val, INT32_MAX),
};
static const JsValueArguments js_serial_read_any_args =
JS_VALUE_ARGS(js_serial_read_any_arg_list);
do { int32_t timeout;
size_t num_args = mjs_nargs(mjs); JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_serial_read_any_args, &timeout);
if(num_args == 1) {
mjs_val_t timeout_arg = mjs_arg(mjs, 0);
if(!mjs_is_number(timeout_arg)) {
break;
}
timeout = mjs_get_int32(mjs, timeout_arg);
}
} while(0);
size_t bytes_read = 0; size_t bytes_read = 0;
char* read_buf = js_serial_receive_any(serial, &bytes_read, timeout); char* read_buf = js_serial_receive_any(serial, &bytes_read, timeout);
@@ -663,16 +579,19 @@ static void* js_serial_create(struct mjs* mjs, mjs_val_t* object, JsModules* mod
UNUSED(modules); UNUSED(modules);
JsSerialInst* js_serial = malloc(sizeof(JsSerialInst)); JsSerialInst* js_serial = malloc(sizeof(JsSerialInst));
js_serial->mjs = mjs; js_serial->mjs = mjs;
mjs_val_t serial_obj = mjs_mk_object(mjs); mjs_val_t serial_obj = mjs_mk_object(mjs);
mjs_set(mjs, serial_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, js_serial)); JS_ASSIGN_MULTI(mjs, serial_obj) {
mjs_set(mjs, serial_obj, "setup", ~0, MJS_MK_FN(js_serial_setup)); JS_FIELD(INST_PROP_NAME, mjs_mk_foreign(mjs, js_serial));
mjs_set(mjs, serial_obj, "end", ~0, MJS_MK_FN(js_serial_end)); JS_FIELD("setup", MJS_MK_FN(js_serial_setup));
mjs_set(mjs, serial_obj, "write", ~0, MJS_MK_FN(js_serial_write)); JS_FIELD("end", MJS_MK_FN(js_serial_end));
mjs_set(mjs, serial_obj, "read", ~0, MJS_MK_FN(js_serial_read)); JS_FIELD("write", MJS_MK_FN(js_serial_write));
mjs_set(mjs, serial_obj, "readln", ~0, MJS_MK_FN(js_serial_readln)); JS_FIELD("read", MJS_MK_FN(js_serial_read));
mjs_set(mjs, serial_obj, "readBytes", ~0, MJS_MK_FN(js_serial_read_bytes)); JS_FIELD("readln", MJS_MK_FN(js_serial_readln));
mjs_set(mjs, serial_obj, "readAny", ~0, MJS_MK_FN(js_serial_read_any)); JS_FIELD("readBytes", MJS_MK_FN(js_serial_read_bytes));
mjs_set(mjs, serial_obj, "expect", ~0, MJS_MK_FN(js_serial_expect)); JS_FIELD("readAny", MJS_MK_FN(js_serial_read_any));
JS_FIELD("expect", MJS_MK_FN(js_serial_expect));
}
*object = serial_obj; *object = serial_obj;
return js_serial; return js_serial;

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