mirror of
https://github.com/flipperdevices/flipperzero-firmware.git
synced 2025-12-12 20:59:50 +04:00
[FL-3928, FL-3929] CLI commands in fals and threads (#4116)
* feat: FuriThread stdin * ci: fix f18 * feat: stdio callback context * feat: FuriPipe * POTENTIALLY EXPLOSIVE pipe welding * fix: non-explosive welding * Revert welding * docs: furi_pipe * feat: pipe event loop integration * update f18 sdk * f18 * docs: make doxygen happy * fix: event loop not triggering when pipe attached to stdio * fix: partial stdout in pipe * allow simultaneous in and out subscription in event loop * feat: vcp i/o * feat: cli ansi stuffs and history * feat: more line editing * working but slow cli rewrite * restore previous speed after 4 days of debugging 🥲 * fix: cli_app_should_stop * fix: cli and event_loop memory leaks * style: remove commented out code * ci: fix pvs warnings * fix: unit tests, event_loop crash * ci: fix build * ci: silence pvs warning * feat: cli gpio * ci: fix formatting * Fix memory leak during event loop unsubscription * Event better memory leak fix * feat: cli completions * Merge remote-tracking branch 'origin/dev' into portasynthinca3/3928-cli-threads * merge fixups * temporarily exclude speaker_debug app * pvs and unit tests fixups * feat: commands in fals * move commands out of flash, code cleanup * ci: fix errors * fix: run commands in buffer when stopping session * speedup cli file transfer * fix f18 * separate cli_shell into modules * fix pvs warning * fix qflipper refusing to connect * remove temp debug logs * remove erroneous conclusion * Fix memory leak during event loop unsubscription * Event better memory leak fix * unit test for the fix * improve thread stdio callback signatures * pipe stdout timeout * update api symbols * fix f18, formatting * fix pvs warnings * increase stack size, hope to fix unit tests * cli completions * more key combos * commands in fals * move commands out of flash * ci: fix errors * speedup cli file transfer * merge fixups * fix f18 * cli: revert flag changes * cli: fix formatting * cli, fbt: loopback perf benchmark * thread, event_loop: subscribing to thread flags * cli: signal internal events using thread flags, improve performance * fix f18, formatting * event_loop: fix crash * storage_cli: increase write_chunk buffer size again * cli: explanation for order=0 * thread, event_loop: thread flags callback refactor * cli: increase stack size * cli: rename cli_app_should_stop -> cli_is_pipe_broken_or_is_etx_next_char * cli: use plain array instead of mlib for history * cli: prepend file name to static fns * cli: fix formatting * cli_shell: increase stack size * cli_shell: give up pipe to command thread * fix formatting * fix: format * fix merge * fix. merge. * cli_shell: fix detach ordering * desktop: record_cli -> record_cli_vcp * cli: fix spelling, reload/remove ext cmds on card mount/unmount * cli: fix race conditions and formatting * scripts: wait for CTS to go high before starting flipper * scripts: better race condition fix * REVERT THIS: test script race condition fix * Revert "REVERT THIS: test script race condition fix" This reverts commit3b028d29b0. * REVERT THIS: test script fix * scripts: sleep? * cli: updated oplist for CliCommandTree * Revert "REVERT THIS: test script fix" This reverts commite984631854. * cli: mention memory leak in FL ticket --------- Co-authored-by: Georgii Surkov <georgii.surkov@outlook.com> Co-authored-by: あく <alleteam@gmail.com> Co-authored-by: hedger <hedger@nanode.su>
This commit is contained in:
@@ -4,7 +4,7 @@ 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,
|
order=100,
|
||||||
|
|||||||
@@ -22,11 +22,5 @@ App(
|
|||||||
apptype=FlipperAppType.METAPACKAGE,
|
apptype=FlipperAppType.METAPACKAGE,
|
||||||
provides=[
|
provides=[
|
||||||
"cli",
|
"cli",
|
||||||
"ibutton_start",
|
|
||||||
"onewire_start",
|
|
||||||
"subghz_start",
|
|
||||||
"infrared_start",
|
|
||||||
"lfrfid_start",
|
|
||||||
"nfc_start",
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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,
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -216,8 +216,7 @@ void ibutton_cli_emulate(PipeSide* pipe, FuriString* args) {
|
|||||||
ibutton_protocols_free(protocols);
|
ibutton_protocols_free(protocols);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ibutton_cli(PipeSide* pipe, FuriString* args, void* context) {
|
static void execute(PipeSide* pipe, FuriString* args, void* context) {
|
||||||
UNUSED(pipe);
|
|
||||||
UNUSED(context);
|
UNUSED(context);
|
||||||
FuriString* cmd;
|
FuriString* cmd;
|
||||||
cmd = furi_string_alloc();
|
cmd = furi_string_alloc();
|
||||||
@@ -241,12 +240,4 @@ void ibutton_cli(PipeSide* pipe, FuriString* args, void* context) {
|
|||||||
furi_string_free(cmd);
|
furi_string_free(cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ibutton_on_system_start(void) {
|
CLI_COMMAND_INTERFACE(ikey, execute, CliCommandFlagDefault, 1024);
|
||||||
#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
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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,
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -526,7 +526,7 @@ static void infrared_cli_process_universal(PipeSide* pipe, FuriString* args) {
|
|||||||
furi_string_free(arg2);
|
furi_string_free(arg2);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void infrared_cli_start_ir(PipeSide* pipe, 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.");
|
||||||
@@ -553,12 +553,5 @@ static void infrared_cli_start_ir(PipeSide* pipe, FuriString* args, void* contex
|
|||||||
|
|
||||||
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* 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
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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,
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -536,7 +536,7 @@ static void lfrfid_cli_raw_emulate(PipeSide* pipe, FuriString* args) {
|
|||||||
furi_string_free(filepath);
|
furi_string_free(filepath);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void lfrfid_cli(PipeSide* pipe, 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();
|
||||||
@@ -566,8 +566,4 @@ static void lfrfid_cli(PipeSide* pipe, FuriString* args, void* context) {
|
|||||||
furi_string_free(cmd);
|
furi_string_free(cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
void lfrfid_on_system_start(void) {
|
CLI_COMMAND_INTERFACE(rfid, execute, CliCommandFlagDefault, 2048);
|
||||||
Cli* cli = furi_record_open(RECORD_CLI);
|
|
||||||
cli_add_command(cli, "rfid", CliCommandFlagDefault, lfrfid_cli, NULL);
|
|
||||||
furi_record_close(RECORD_CLI);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -258,10 +258,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,
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ static void nfc_cli_field(PipeSide* pipe, FuriString* args) {
|
|||||||
furi_hal_nfc_release();
|
furi_hal_nfc_release();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void nfc_cli(PipeSide* pipe, 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();
|
||||||
@@ -65,12 +65,4 @@ static void nfc_cli(PipeSide* pipe, 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);
|
||||||
#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
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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"],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include <furi.h>
|
#include <furi.h>
|
||||||
#include <furi_hal.h>
|
#include <furi_hal.h>
|
||||||
|
|
||||||
|
#include <cli/cli_commands.h>
|
||||||
#include <power/power_service/power.h>
|
#include <power/power_service/power.h>
|
||||||
#include <cli/cli_commands.h>
|
#include <cli/cli_commands.h>
|
||||||
#include <toolbox/args.h>
|
#include <toolbox/args.h>
|
||||||
@@ -45,7 +46,7 @@ static void onewire_cli_search(PipeSide* pipe) {
|
|||||||
furi_record_close(RECORD_POWER);
|
furi_record_close(RECORD_POWER);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void onewire_cli(PipeSide* pipe, 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();
|
||||||
@@ -63,12 +64,4 @@ static void onewire_cli(PipeSide* pipe, FuriString* args, void* context) {
|
|||||||
furi_string_free(cmd);
|
furi_string_free(cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
void onewire_on_system_start(void) {
|
CLI_COMMAND_INTERFACE(onewire, execute, CliCommandFlagDefault, 1024);
|
||||||
#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
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -20,10 +20,10 @@ 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=["cli"],
|
||||||
sources=["subghz_cli.c", "helpers/subghz_chat.c"],
|
sources=["subghz_cli.c", "helpers/subghz_chat.c"],
|
||||||
order=40,
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1116,7 +1116,7 @@ static void subghz_cli_command_chat(PipeSide* pipe, FuriString* args) {
|
|||||||
printf("\r\nExit chat\r\n");
|
printf("\r\nExit chat\r\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
static void subghz_cli_command(PipeSide* pipe, FuriString* args, void* context) {
|
static void execute(PipeSide* pipe, FuriString* args, void* context) {
|
||||||
FuriString* cmd;
|
FuriString* cmd;
|
||||||
cmd = furi_string_alloc();
|
cmd = furi_string_alloc();
|
||||||
|
|
||||||
@@ -1184,14 +1184,4 @@ static void subghz_cli_command(PipeSide* pipe, 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);
|
||||||
#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
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -33,3 +33,19 @@ App(
|
|||||||
sdk_headers=["cli_vcp.h"],
|
sdk_headers=["cli_vcp.h"],
|
||||||
sources=["cli_vcp.c"],
|
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"],
|
||||||
|
)
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ struct Cli {
|
|||||||
Cli* cli_alloc(void) {
|
Cli* cli_alloc(void) {
|
||||||
Cli* cli = malloc(sizeof(Cli));
|
Cli* cli = malloc(sizeof(Cli));
|
||||||
CliCommandTree_init(cli->commands);
|
CliCommandTree_init(cli->commands);
|
||||||
cli->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
cli->mutex = furi_mutex_alloc(FuriMutexTypeRecursive);
|
||||||
return cli;
|
return cli;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,6 +38,9 @@ void cli_add_command_ex(
|
|||||||
furi_check(name);
|
furi_check(name);
|
||||||
furi_check(callback);
|
furi_check(callback);
|
||||||
|
|
||||||
|
// the shell always attaches the pipe to the stdio, thus both flags can't be used at once
|
||||||
|
if(flags & CliCommandFlagUseShellThread) furi_check(!(flags & CliCommandFlagDontAttachStdio));
|
||||||
|
|
||||||
FuriString* name_str;
|
FuriString* name_str;
|
||||||
name_str = furi_string_alloc_set(name);
|
name_str = furi_string_alloc_set(name);
|
||||||
// command cannot contain spaces
|
// command cannot contain spaces
|
||||||
@@ -86,18 +89,75 @@ bool cli_get_command(Cli* cli, FuriString* command, CliCommand* result) {
|
|||||||
return !!data;
|
return !!data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void cli_remove_external_commands(Cli* cli) {
|
||||||
|
furi_check(cli);
|
||||||
|
furi_check(furi_mutex_acquire(cli->mutex, FuriWaitForever) == FuriStatusOk);
|
||||||
|
|
||||||
|
// FIXME FL-3977: memory leak somewhere within this function
|
||||||
|
|
||||||
|
CliCommandTree_t internal_cmds;
|
||||||
|
CliCommandTree_init(internal_cmds);
|
||||||
|
for
|
||||||
|
M_EACH(item, cli->commands, CliCommandTree_t) {
|
||||||
|
if(!(item->value_ptr->flags & CliCommandFlagExternal))
|
||||||
|
CliCommandTree_set_at(internal_cmds, *item->key_ptr, *item->value_ptr);
|
||||||
|
}
|
||||||
|
CliCommandTree_move(cli->commands, internal_cmds);
|
||||||
|
|
||||||
|
furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk);
|
||||||
|
}
|
||||||
|
|
||||||
|
void cli_enumerate_external_commands(Cli* cli) {
|
||||||
|
furi_check(cli);
|
||||||
|
furi_check(furi_mutex_acquire(cli->mutex, FuriWaitForever) == FuriStatusOk);
|
||||||
|
FURI_LOG_D(TAG, "Enumerating external commands");
|
||||||
|
|
||||||
|
cli_remove_external_commands(cli);
|
||||||
|
|
||||||
|
// iterate over files in plugin directory
|
||||||
|
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||||
|
File* plugin_dir = storage_file_alloc(storage);
|
||||||
|
|
||||||
|
if(storage_dir_open(plugin_dir, CLI_COMMANDS_PATH)) {
|
||||||
|
char plugin_filename[64];
|
||||||
|
FuriString* plugin_name = furi_string_alloc();
|
||||||
|
|
||||||
|
while(storage_dir_read(plugin_dir, NULL, plugin_filename, sizeof(plugin_filename))) {
|
||||||
|
FURI_LOG_T(TAG, "Plugin: %s", plugin_filename);
|
||||||
|
furi_string_set_str(plugin_name, plugin_filename);
|
||||||
|
furi_string_replace_all_str(plugin_name, ".fal", "");
|
||||||
|
furi_string_replace_at(plugin_name, 0, 4, ""); // remove "cli_" in the beginning
|
||||||
|
CliCommand command = {
|
||||||
|
.context = NULL,
|
||||||
|
.execute_callback = NULL,
|
||||||
|
.flags = CliCommandFlagExternal,
|
||||||
|
};
|
||||||
|
CliCommandTree_set_at(cli->commands, plugin_name, command);
|
||||||
|
}
|
||||||
|
|
||||||
|
furi_string_free(plugin_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
storage_file_free(plugin_dir);
|
||||||
|
furi_record_close(RECORD_STORAGE);
|
||||||
|
|
||||||
|
FURI_LOG_D(TAG, "Finished enumerating external commands");
|
||||||
|
furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk);
|
||||||
|
}
|
||||||
|
|
||||||
void cli_lock_commands(Cli* cli) {
|
void cli_lock_commands(Cli* cli) {
|
||||||
furi_assert(cli);
|
furi_check(cli);
|
||||||
furi_check(furi_mutex_acquire(cli->mutex, FuriWaitForever) == FuriStatusOk);
|
furi_check(furi_mutex_acquire(cli->mutex, FuriWaitForever) == FuriStatusOk);
|
||||||
}
|
}
|
||||||
|
|
||||||
void cli_unlock_commands(Cli* cli) {
|
void cli_unlock_commands(Cli* cli) {
|
||||||
furi_assert(cli);
|
furi_check(cli);
|
||||||
furi_mutex_release(cli->mutex);
|
furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk);
|
||||||
}
|
}
|
||||||
|
|
||||||
CliCommandTree_t* cli_get_commands(Cli* cli) {
|
CliCommandTree_t* cli_get_commands(Cli* cli) {
|
||||||
furi_assert(cli);
|
furi_check(cli);
|
||||||
|
furi_check(furi_mutex_get_owner(cli->mutex) == furi_thread_get_current_id());
|
||||||
return &cli->commands;
|
return &cli->commands;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,5 +179,6 @@ void cli_print_usage(const char* cmd, const char* usage, const char* arg) {
|
|||||||
void cli_on_system_start(void) {
|
void cli_on_system_start(void) {
|
||||||
Cli* cli = cli_alloc();
|
Cli* cli = cli_alloc();
|
||||||
cli_commands_init(cli);
|
cli_commands_init(cli);
|
||||||
|
cli_enumerate_external_commands(cli);
|
||||||
furi_record_create(RECORD_CLI, cli);
|
furi_record_create(RECORD_CLI, cli);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,13 @@ typedef enum {
|
|||||||
CliCommandFlagParallelSafe = (1 << 0), /**< Safe to run in parallel with other apps */
|
CliCommandFlagParallelSafe = (1 << 0), /**< Safe to run in parallel with other apps */
|
||||||
CliCommandFlagInsomniaSafe = (1 << 1), /**< Safe to run with insomnia mode on */
|
CliCommandFlagInsomniaSafe = (1 << 1), /**< Safe to run with insomnia mode on */
|
||||||
CliCommandFlagDontAttachStdio = (1 << 2), /**< Do no attach I/O pipe to thread stdio */
|
CliCommandFlagDontAttachStdio = (1 << 2), /**< Do no attach I/O pipe to thread stdio */
|
||||||
|
CliCommandFlagUseShellThread =
|
||||||
|
(1
|
||||||
|
<< 3), /**< Don't start a separate thread to run the command in. Incompatible with DontAttachStdio */
|
||||||
|
|
||||||
|
// internal flags (do not set them yourselves!)
|
||||||
|
|
||||||
|
CliCommandFlagExternal = (1 << 4), /**< The command comes from a .fal file */
|
||||||
} CliCommandFlag;
|
} CliCommandFlag;
|
||||||
|
|
||||||
/** Cli type anonymous structure */
|
/** Cli type anonymous structure */
|
||||||
@@ -87,6 +94,20 @@ void cli_add_command_ex(
|
|||||||
*/
|
*/
|
||||||
void cli_delete_command(Cli* cli, const char* name);
|
void cli_delete_command(Cli* cli, const char* name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Unregisters all external commands
|
||||||
|
*
|
||||||
|
* @param [in] cli pointer to the cli instance
|
||||||
|
*/
|
||||||
|
void cli_remove_external_commands(Cli* cli);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Reloads the list of externally available commands
|
||||||
|
*
|
||||||
|
* @param [in] cli pointer to cli instance
|
||||||
|
*/
|
||||||
|
void cli_enumerate_external_commands(Cli* cli);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Detects if Ctrl+C has been pressed or session has been terminated
|
* @brief Detects if Ctrl+C has been pressed or session has been terminated
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -91,6 +91,8 @@ void cli_command_help(PipeSide* pipe, FuriString* args, void* context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
printf(ANSI_RESET
|
||||||
|
"\r\nIf you just added a new command and can't see it above, run `reload_ext_cmds`");
|
||||||
printf(ANSI_RESET "\r\nFind out more: https://docs.flipper.net/development/cli");
|
printf(ANSI_RESET "\r\nFind out more: https://docs.flipper.net/development/cli");
|
||||||
|
|
||||||
cli_unlock_commands(cli);
|
cli_unlock_commands(cli);
|
||||||
@@ -512,6 +514,16 @@ void cli_command_i2c(PipeSide* pipe, FuriString* args, void* context) {
|
|||||||
furi_hal_i2c_release(&furi_hal_i2c_handle_external);
|
furi_hal_i2c_release(&furi_hal_i2c_handle_external);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void cli_command_reload_external(PipeSide* pipe, FuriString* args, void* context) {
|
||||||
|
UNUSED(pipe);
|
||||||
|
UNUSED(args);
|
||||||
|
UNUSED(context);
|
||||||
|
Cli* cli = furi_record_open(RECORD_CLI);
|
||||||
|
cli_enumerate_external_commands(cli);
|
||||||
|
furi_record_close(RECORD_CLI);
|
||||||
|
printf("OK!");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Echoes any bytes it receives except ASCII ETX (0x03, Ctrl+C)
|
* Echoes any bytes it receives except ASCII ETX (0x03, Ctrl+C)
|
||||||
*/
|
*/
|
||||||
@@ -537,6 +549,8 @@ void cli_commands_init(Cli* cli) {
|
|||||||
cli_add_command(cli, "!", CliCommandFlagParallelSafe, cli_command_info, (void*)true);
|
cli_add_command(cli, "!", CliCommandFlagParallelSafe, cli_command_info, (void*)true);
|
||||||
cli_add_command(cli, "info", CliCommandFlagParallelSafe, cli_command_info, NULL);
|
cli_add_command(cli, "info", CliCommandFlagParallelSafe, cli_command_info, NULL);
|
||||||
cli_add_command(cli, "device_info", CliCommandFlagParallelSafe, cli_command_info, (void*)true);
|
cli_add_command(cli, "device_info", CliCommandFlagParallelSafe, cli_command_info, (void*)true);
|
||||||
|
cli_add_command(
|
||||||
|
cli, "reload_ext_cmds", CliCommandFlagDefault, cli_command_reload_external, NULL);
|
||||||
|
|
||||||
cli_add_command(cli, "?", CliCommandFlagParallelSafe, cli_command_help, NULL);
|
cli_add_command(cli, "?", CliCommandFlagParallelSafe, cli_command_help, NULL);
|
||||||
cli_add_command(cli, "help", CliCommandFlagParallelSafe, cli_command_help, NULL);
|
cli_add_command(cli, "help", CliCommandFlagParallelSafe, cli_command_help, NULL);
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ extern "C" {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define CLI_BUILTIN_COMMAND_STACK_SIZE (3 * 1024U)
|
#define CLI_BUILTIN_COMMAND_STACK_SIZE (3 * 1024U)
|
||||||
|
#define CLI_COMMANDS_PATH "/ext/apps_data/cli/plugins"
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
void* context; //<! Context passed to callbacks
|
void* context; //<! Context passed to callbacks
|
||||||
@@ -33,7 +34,8 @@ BPTREE_DEF2(
|
|||||||
FURI_STRING_OPLIST,
|
FURI_STRING_OPLIST,
|
||||||
CliCommand,
|
CliCommand,
|
||||||
M_POD_OPLIST);
|
M_POD_OPLIST);
|
||||||
#define M_OPL_CliCommandTree_t() BPTREE_OPLIST(CliCommandTree, M_POD_OPLIST)
|
|
||||||
|
#define M_OPL_CliCommandTree_t() BPTREE_OPLIST2(CliCommandTree, FURI_STRING_OPLIST, M_POD_OPLIST)
|
||||||
|
|
||||||
bool cli_get_command(Cli* cli, FuriString* command, CliCommand* result);
|
bool cli_get_command(Cli* cli, FuriString* command, CliCommand* result);
|
||||||
|
|
||||||
|
|||||||
10
applications/services/cli/commands/hello_world.c
Normal file
10
applications/services/cli/commands/hello_world.c
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
#include "../cli_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, CliCommandFlagDefault, 768);
|
||||||
159
applications/services/cli/commands/neofetch.c
Normal file
159
applications/services/cli/commands/neofetch.c
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
#include "../cli_commands.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, CliCommandFlagDefault, 2048);
|
||||||
@@ -12,6 +12,7 @@
|
|||||||
#include <toolbox/pipe.h>
|
#include <toolbox/pipe.h>
|
||||||
#include <flipper_application/plugins/plugin_manager.h>
|
#include <flipper_application/plugins/plugin_manager.h>
|
||||||
#include <loader/firmware_api/firmware_api.h>
|
#include <loader/firmware_api/firmware_api.h>
|
||||||
|
#include <storage/storage.h>
|
||||||
|
|
||||||
#define TAG "CliShell"
|
#define TAG "CliShell"
|
||||||
|
|
||||||
@@ -29,6 +30,11 @@ CliShellKeyComboSet* component_key_combo_sets[] = {
|
|||||||
};
|
};
|
||||||
static_assert(CliShellComponentMAX == COUNT_OF(component_key_combo_sets));
|
static_assert(CliShellComponentMAX == COUNT_OF(component_key_combo_sets));
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
CliShellStorageEventMount,
|
||||||
|
CliShellStorageEventUnmount,
|
||||||
|
} CliShellStorageEvent;
|
||||||
|
|
||||||
struct CliShell {
|
struct CliShell {
|
||||||
Cli* cli;
|
Cli* cli;
|
||||||
FuriEventLoop* event_loop;
|
FuriEventLoop* event_loop;
|
||||||
@@ -37,6 +43,10 @@ struct CliShell {
|
|||||||
CliAnsiParser* ansi_parser;
|
CliAnsiParser* ansi_parser;
|
||||||
FuriEventLoopTimer* ansi_parsing_timer;
|
FuriEventLoopTimer* ansi_parsing_timer;
|
||||||
|
|
||||||
|
Storage* storage;
|
||||||
|
FuriPubSubSubscription* storage_subscription;
|
||||||
|
FuriMessageQueue* storage_event_queue;
|
||||||
|
|
||||||
void* components[CliShellComponentMAX];
|
void* components[CliShellComponentMAX];
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -46,10 +56,39 @@ typedef struct {
|
|||||||
FuriString* args;
|
FuriString* args;
|
||||||
} CliCommandThreadData;
|
} CliCommandThreadData;
|
||||||
|
|
||||||
|
static void cli_shell_data_available(PipeSide* pipe, void* context);
|
||||||
|
static void cli_shell_pipe_broken(PipeSide* pipe, void* context);
|
||||||
|
|
||||||
|
static void cli_shell_install_pipe(CliShell* cli_shell) {
|
||||||
|
pipe_install_as_stdio(cli_shell->pipe);
|
||||||
|
pipe_attach_to_event_loop(cli_shell->pipe, cli_shell->event_loop);
|
||||||
|
pipe_set_callback_context(cli_shell->pipe, cli_shell);
|
||||||
|
pipe_set_data_arrived_callback(cli_shell->pipe, cli_shell_data_available, 0);
|
||||||
|
pipe_set_broken_callback(cli_shell->pipe, cli_shell_pipe_broken, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cli_shell_detach_pipe(CliShell* cli_shell) {
|
||||||
|
pipe_detach_from_event_loop(cli_shell->pipe);
|
||||||
|
furi_thread_set_stdin_callback(NULL, NULL);
|
||||||
|
furi_thread_set_stdout_callback(NULL, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
// =========
|
// =========
|
||||||
// Execution
|
// Execution
|
||||||
// =========
|
// =========
|
||||||
|
|
||||||
|
static int32_t cli_command_thread(void* context) {
|
||||||
|
CliCommandThreadData* thread_data = context;
|
||||||
|
if(!(thread_data->command->flags & CliCommandFlagDontAttachStdio))
|
||||||
|
pipe_install_as_stdio(thread_data->pipe);
|
||||||
|
|
||||||
|
thread_data->command->execute_callback(
|
||||||
|
thread_data->pipe, thread_data->args, thread_data->command->context);
|
||||||
|
|
||||||
|
fflush(stdout);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
void cli_shell_execute_command(CliShell* cli_shell, FuriString* command) {
|
void cli_shell_execute_command(CliShell* cli_shell, FuriString* command) {
|
||||||
// split command into command and args
|
// split command into command and args
|
||||||
size_t space = furi_string_search_char(command, ' ');
|
size_t space = furi_string_search_char(command, ' ');
|
||||||
@@ -59,6 +98,7 @@ void cli_shell_execute_command(CliShell* cli_shell, FuriString* command) {
|
|||||||
FuriString* args = furi_string_alloc_set(command);
|
FuriString* args = furi_string_alloc_set(command);
|
||||||
furi_string_right(args, space + 1);
|
furi_string_right(args, space + 1);
|
||||||
|
|
||||||
|
PluginManager* plugin_manager = NULL;
|
||||||
Loader* loader = NULL;
|
Loader* loader = NULL;
|
||||||
CliCommand command_data;
|
CliCommand command_data;
|
||||||
|
|
||||||
@@ -71,6 +111,34 @@ void cli_shell_execute_command(CliShell* cli_shell, FuriString* command) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// load external command
|
||||||
|
if(command_data.flags & CliCommandFlagExternal) {
|
||||||
|
plugin_manager =
|
||||||
|
plugin_manager_alloc(PLUGIN_APP_ID, PLUGIN_API_VERSION, firmware_api_interface);
|
||||||
|
FuriString* path = furi_string_alloc_printf(
|
||||||
|
"%s/cli_%s.fal", CLI_COMMANDS_PATH, furi_string_get_cstr(command_name));
|
||||||
|
uint32_t plugin_cnt_last = plugin_manager_get_count(plugin_manager);
|
||||||
|
PluginManagerError error =
|
||||||
|
plugin_manager_load_single(plugin_manager, furi_string_get_cstr(path));
|
||||||
|
furi_string_free(path);
|
||||||
|
|
||||||
|
if(error != PluginManagerErrorNone) {
|
||||||
|
printf(ANSI_FG_RED "failed to load external command" ANSI_RESET);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CliCommandDescriptor* plugin =
|
||||||
|
plugin_manager_get_ep(plugin_manager, plugin_cnt_last);
|
||||||
|
furi_assert(plugin);
|
||||||
|
furi_check(furi_string_cmp_str(command_name, plugin->name) == 0);
|
||||||
|
command_data.execute_callback = plugin->execute_callback;
|
||||||
|
command_data.flags = plugin->flags | CliCommandFlagExternal;
|
||||||
|
command_data.stack_depth = plugin->stack_depth;
|
||||||
|
|
||||||
|
// external commands have to run in an external thread
|
||||||
|
furi_check(!(command_data.flags & CliCommandFlagUseShellThread));
|
||||||
|
}
|
||||||
|
|
||||||
// lock loader
|
// lock loader
|
||||||
if(!(command_data.flags & CliCommandFlagParallelSafe)) {
|
if(!(command_data.flags & CliCommandFlagParallelSafe)) {
|
||||||
loader = furi_record_open(RECORD_LOADER);
|
loader = furi_record_open(RECORD_LOADER);
|
||||||
@@ -82,7 +150,27 @@ void cli_shell_execute_command(CliShell* cli_shell, FuriString* command) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(command_data.flags & CliCommandFlagUseShellThread) {
|
||||||
|
// run command in this thread
|
||||||
command_data.execute_callback(cli_shell->pipe, args, command_data.context);
|
command_data.execute_callback(cli_shell->pipe, args, command_data.context);
|
||||||
|
} else {
|
||||||
|
// run command in separate thread
|
||||||
|
cli_shell_detach_pipe(cli_shell);
|
||||||
|
CliCommandThreadData thread_data = {
|
||||||
|
.command = &command_data,
|
||||||
|
.pipe = cli_shell->pipe,
|
||||||
|
.args = args,
|
||||||
|
};
|
||||||
|
FuriThread* thread = furi_thread_alloc_ex(
|
||||||
|
furi_string_get_cstr(command_name),
|
||||||
|
command_data.stack_depth,
|
||||||
|
cli_command_thread,
|
||||||
|
&thread_data);
|
||||||
|
furi_thread_start(thread);
|
||||||
|
furi_thread_join(thread);
|
||||||
|
furi_thread_free(thread);
|
||||||
|
cli_shell_install_pipe(cli_shell);
|
||||||
|
}
|
||||||
} while(0);
|
} while(0);
|
||||||
|
|
||||||
furi_string_free(command_name);
|
furi_string_free(command_name);
|
||||||
@@ -91,13 +179,51 @@ void cli_shell_execute_command(CliShell* cli_shell, FuriString* command) {
|
|||||||
// unlock loader
|
// unlock loader
|
||||||
if(loader) loader_unlock(loader);
|
if(loader) loader_unlock(loader);
|
||||||
furi_record_close(RECORD_LOADER);
|
furi_record_close(RECORD_LOADER);
|
||||||
|
|
||||||
|
// unload external command
|
||||||
|
if(plugin_manager) plugin_manager_free(plugin_manager);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==============
|
// ==============
|
||||||
// Event handlers
|
// Event handlers
|
||||||
// ==============
|
// ==============
|
||||||
|
|
||||||
static void cli_shell_process_key(CliShell* cli_shell, CliKeyCombo key_combo) {
|
static void cli_shell_storage_event(const void* message, void* context) {
|
||||||
|
CliShell* cli_shell = context;
|
||||||
|
const StorageEvent* event = message;
|
||||||
|
|
||||||
|
if(event->type == StorageEventTypeCardMount) {
|
||||||
|
CliShellStorageEvent cli_event = CliShellStorageEventMount;
|
||||||
|
furi_check(
|
||||||
|
furi_message_queue_put(cli_shell->storage_event_queue, &cli_event, 0) == FuriStatusOk);
|
||||||
|
} else if(event->type == StorageEventTypeCardUnmount) {
|
||||||
|
CliShellStorageEvent cli_event = CliShellStorageEventUnmount;
|
||||||
|
furi_check(
|
||||||
|
furi_message_queue_put(cli_shell->storage_event_queue, &cli_event, 0) == FuriStatusOk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cli_shell_storage_internal_event(FuriEventLoopObject* object, void* context) {
|
||||||
|
CliShell* cli_shell = context;
|
||||||
|
FuriMessageQueue* queue = object;
|
||||||
|
CliShellStorageEvent event;
|
||||||
|
furi_check(furi_message_queue_get(queue, &event, 0) == FuriStatusOk);
|
||||||
|
|
||||||
|
if(event == CliShellStorageEventMount) {
|
||||||
|
cli_enumerate_external_commands(cli_shell->cli);
|
||||||
|
} else if(event == CliShellStorageEventUnmount) {
|
||||||
|
cli_remove_external_commands(cli_shell->cli);
|
||||||
|
} else {
|
||||||
|
furi_crash();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
cli_shell_process_parser_result(CliShell* cli_shell, CliAnsiParserResult parse_result) {
|
||||||
|
if(!parse_result.is_done) return;
|
||||||
|
CliKeyCombo key_combo = parse_result.result;
|
||||||
|
if(key_combo.key == CliKeyUnrecognized) return;
|
||||||
|
|
||||||
for(size_t i = 0; i < CliShellComponentMAX; i++) { // -V1008
|
for(size_t i = 0; i < CliShellComponentMAX; i++) { // -V1008
|
||||||
CliShellKeyComboSet* set = component_key_combo_sets[i];
|
CliShellKeyComboSet* set = component_key_combo_sets[i];
|
||||||
void* component_context = cli_shell->components[i];
|
void* component_context = cli_shell->components[i];
|
||||||
@@ -130,22 +256,13 @@ static void cli_shell_data_available(PipeSide* pipe, void* context) {
|
|||||||
// process ANSI escape sequences
|
// process ANSI escape sequences
|
||||||
int c = getchar();
|
int c = getchar();
|
||||||
furi_assert(c >= 0);
|
furi_assert(c >= 0);
|
||||||
CliAnsiParserResult parse_result = cli_ansi_parser_feed(cli_shell->ansi_parser, c);
|
cli_shell_process_parser_result(cli_shell, cli_ansi_parser_feed(cli_shell->ansi_parser, c));
|
||||||
if(!parse_result.is_done) return;
|
|
||||||
CliKeyCombo key_combo = parse_result.result;
|
|
||||||
if(key_combo.key == CliKeyUnrecognized) return;
|
|
||||||
|
|
||||||
cli_shell_process_key(cli_shell, key_combo);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void cli_shell_timer_expired(void* context) {
|
static void cli_shell_timer_expired(void* context) {
|
||||||
CliShell* cli_shell = context;
|
CliShell* cli_shell = context;
|
||||||
CliAnsiParserResult parse_result = cli_ansi_parser_feed_timeout(cli_shell->ansi_parser);
|
cli_shell_process_parser_result(
|
||||||
if(!parse_result.is_done) return;
|
cli_shell, cli_ansi_parser_feed_timeout(cli_shell->ansi_parser));
|
||||||
CliKeyCombo key_combo = parse_result.result;
|
|
||||||
if(key_combo.key == CliKeyUnrecognized) return;
|
|
||||||
|
|
||||||
cli_shell_process_key(cli_shell, key_combo);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// =======
|
// =======
|
||||||
@@ -158,7 +275,6 @@ static CliShell* cli_shell_alloc(PipeSide* pipe) {
|
|||||||
cli_shell->cli = furi_record_open(RECORD_CLI);
|
cli_shell->cli = furi_record_open(RECORD_CLI);
|
||||||
cli_shell->ansi_parser = cli_ansi_parser_alloc();
|
cli_shell->ansi_parser = cli_ansi_parser_alloc();
|
||||||
cli_shell->pipe = pipe;
|
cli_shell->pipe = pipe;
|
||||||
pipe_install_as_stdio(cli_shell->pipe);
|
|
||||||
|
|
||||||
cli_shell->components[CliShellComponentLine] = cli_shell_line_alloc(cli_shell);
|
cli_shell->components[CliShellComponentLine] = cli_shell_line_alloc(cli_shell);
|
||||||
cli_shell->components[CliShellComponentCompletions] = cli_shell_completions_alloc(
|
cli_shell->components[CliShellComponentCompletions] = cli_shell_completions_alloc(
|
||||||
@@ -167,20 +283,34 @@ static CliShell* cli_shell_alloc(PipeSide* pipe) {
|
|||||||
cli_shell->event_loop = furi_event_loop_alloc();
|
cli_shell->event_loop = furi_event_loop_alloc();
|
||||||
cli_shell->ansi_parsing_timer = furi_event_loop_timer_alloc(
|
cli_shell->ansi_parsing_timer = furi_event_loop_timer_alloc(
|
||||||
cli_shell->event_loop, cli_shell_timer_expired, FuriEventLoopTimerTypeOnce, cli_shell);
|
cli_shell->event_loop, cli_shell_timer_expired, FuriEventLoopTimerTypeOnce, cli_shell);
|
||||||
pipe_attach_to_event_loop(cli_shell->pipe, cli_shell->event_loop);
|
|
||||||
|
|
||||||
pipe_set_callback_context(cli_shell->pipe, cli_shell);
|
cli_shell_install_pipe(cli_shell);
|
||||||
pipe_set_data_arrived_callback(cli_shell->pipe, cli_shell_data_available, 0);
|
|
||||||
pipe_set_broken_callback(cli_shell->pipe, cli_shell_pipe_broken, 0);
|
cli_shell->storage_event_queue = furi_message_queue_alloc(1, sizeof(CliShellStorageEvent));
|
||||||
|
furi_event_loop_subscribe_message_queue(
|
||||||
|
cli_shell->event_loop,
|
||||||
|
cli_shell->storage_event_queue,
|
||||||
|
FuriEventLoopEventIn,
|
||||||
|
cli_shell_storage_internal_event,
|
||||||
|
cli_shell);
|
||||||
|
cli_shell->storage = furi_record_open(RECORD_STORAGE);
|
||||||
|
cli_shell->storage_subscription = furi_pubsub_subscribe(
|
||||||
|
storage_get_pubsub(cli_shell->storage), cli_shell_storage_event, cli_shell);
|
||||||
|
|
||||||
return cli_shell;
|
return cli_shell;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void cli_shell_free(CliShell* cli_shell) {
|
static void cli_shell_free(CliShell* cli_shell) {
|
||||||
|
furi_pubsub_unsubscribe(
|
||||||
|
storage_get_pubsub(cli_shell->storage), cli_shell->storage_subscription);
|
||||||
|
furi_record_close(RECORD_STORAGE);
|
||||||
|
furi_event_loop_unsubscribe(cli_shell->event_loop, cli_shell->storage_event_queue);
|
||||||
|
furi_message_queue_free(cli_shell->storage_event_queue);
|
||||||
|
|
||||||
cli_shell_completions_free(cli_shell->components[CliShellComponentCompletions]);
|
cli_shell_completions_free(cli_shell->components[CliShellComponentCompletions]);
|
||||||
cli_shell_line_free(cli_shell->components[CliShellComponentLine]);
|
cli_shell_line_free(cli_shell->components[CliShellComponentLine]);
|
||||||
|
|
||||||
pipe_detach_from_event_loop(cli_shell->pipe);
|
cli_shell_detach_pipe(cli_shell);
|
||||||
furi_event_loop_timer_free(cli_shell->ansi_parsing_timer);
|
furi_event_loop_timer_free(cli_shell->ansi_parsing_timer);
|
||||||
furi_event_loop_free(cli_shell->event_loop);
|
furi_event_loop_free(cli_shell->event_loop);
|
||||||
pipe_free(cli_shell->pipe);
|
pipe_free(cli_shell->pipe);
|
||||||
|
|||||||
@@ -108,6 +108,7 @@ void cli_shell_completions_fill_variants(CliShellCompletions* completions) {
|
|||||||
furi_string_left(input, segment.length);
|
furi_string_left(input, segment.length);
|
||||||
|
|
||||||
if(segment.type == CliShellCompletionSegmentTypeCommand) {
|
if(segment.type == CliShellCompletionSegmentTypeCommand) {
|
||||||
|
cli_lock_commands(completions->cli);
|
||||||
CliCommandTree_t* commands = cli_get_commands(completions->cli);
|
CliCommandTree_t* commands = cli_get_commands(completions->cli);
|
||||||
for
|
for
|
||||||
M_EACH(registered_command, *commands, CliCommandTree_t) {
|
M_EACH(registered_command, *commands, CliCommandTree_t) {
|
||||||
@@ -116,6 +117,7 @@ void cli_shell_completions_fill_variants(CliShellCompletions* completions) {
|
|||||||
CommandCompletions_push_back(completions->variants, command_name);
|
CommandCompletions_push_back(completions->variants, command_name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
cli_unlock_commands(completions->cli);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// support removed, might reimplement in the future
|
// support removed, might reimplement in the future
|
||||||
|
|||||||
@@ -398,7 +398,7 @@ void desktop_lock(Desktop* desktop) {
|
|||||||
if(desktop_pin_code_is_set()) {
|
if(desktop_pin_code_is_set()) {
|
||||||
CliVcp* cli_vcp = furi_record_open(RECORD_CLI_VCP);
|
CliVcp* cli_vcp = furi_record_open(RECORD_CLI_VCP);
|
||||||
cli_vcp_disable(cli_vcp);
|
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);
|
||||||
@@ -428,7 +428,7 @@ void desktop_unlock(Desktop* desktop) {
|
|||||||
if(desktop_pin_code_is_set()) {
|
if(desktop_pin_code_is_set()) {
|
||||||
CliVcp* cli_vcp = furi_record_open(RECORD_CLI_VCP);
|
CliVcp* cli_vcp = furi_record_open(RECORD_CLI_VCP);
|
||||||
cli_vcp_enable(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};
|
||||||
@@ -528,7 +528,7 @@ int32_t desktop_srv(void* p) {
|
|||||||
} else {
|
} else {
|
||||||
CliVcp* cli_vcp = furi_record_open(RECORD_CLI_VCP);
|
CliVcp* cli_vcp = furi_record_open(RECORD_CLI_VCP);
|
||||||
cli_vcp_enable(cli_vcp);
|
cli_vcp_enable(cli_vcp);
|
||||||
furi_record_close(RECORD_CLI);
|
furi_record_close(RECORD_CLI_VCP);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(storage_file_exists(desktop->storage, SLIDESHOW_FS_PATH)) {
|
if(storage_file_exists(desktop->storage, SLIDESHOW_FS_PATH)) {
|
||||||
|
|||||||
@@ -696,7 +696,13 @@ static void storage_cli_factory_reset(PipeSide* pipe, FuriString* args, void* co
|
|||||||
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);
|
Cli* cli = furi_record_open(RECORD_CLI);
|
||||||
cli_add_command_ex(cli, "storage", CliCommandFlagParallelSafe, storage_cli, NULL, 512);
|
cli_add_command_ex(
|
||||||
|
cli,
|
||||||
|
"storage",
|
||||||
|
CliCommandFlagParallelSafe | CliCommandFlagUseShellThread,
|
||||||
|
storage_cli,
|
||||||
|
NULL,
|
||||||
|
512);
|
||||||
cli_add_command(
|
cli_add_command(
|
||||||
cli, "factory_reset", CliCommandFlagParallelSafe, storage_cli_factory_reset, NULL);
|
cli, "factory_reset", CliCommandFlagParallelSafe, storage_cli_factory_reset, NULL);
|
||||||
furi_record_close(RECORD_CLI);
|
furi_record_close(RECORD_CLI);
|
||||||
|
|||||||
@@ -109,6 +109,8 @@ class FlipperStorage:
|
|||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
self.port.open()
|
self.port.open()
|
||||||
|
time.sleep(0.5)
|
||||||
|
self.read.until(self.CLI_PROMPT)
|
||||||
self.port.reset_input_buffer()
|
self.port.reset_input_buffer()
|
||||||
# Send a command with a known syntax to make sure the buffer is flushed
|
# Send a command with a known syntax to make sure the buffer is flushed
|
||||||
self.send("device_info\r")
|
self.send("device_info\r")
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
entry,status,name,type,params
|
entry,status,name,type,params
|
||||||
Version,+,84.0,,
|
Version,+,84.1,,
|
||||||
Header,+,applications/services/bt/bt_service/bt.h,,
|
Header,+,applications/services/bt/bt_service/bt.h,,
|
||||||
Header,+,applications/services/bt/bt_service/bt_keys_storage.h,,
|
Header,+,applications/services/bt/bt_service/bt_keys_storage.h,,
|
||||||
Header,+,applications/services/cli/cli.h,,
|
Header,+,applications/services/cli/cli.h,,
|
||||||
@@ -786,8 +786,10 @@ Function,+,cli_ansi_parser_feed,CliAnsiParserResult,"CliAnsiParser*, char"
|
|||||||
Function,+,cli_ansi_parser_feed_timeout,CliAnsiParserResult,CliAnsiParser*
|
Function,+,cli_ansi_parser_feed_timeout,CliAnsiParserResult,CliAnsiParser*
|
||||||
Function,+,cli_ansi_parser_free,void,CliAnsiParser*
|
Function,+,cli_ansi_parser_free,void,CliAnsiParser*
|
||||||
Function,+,cli_delete_command,void,"Cli*, const char*"
|
Function,+,cli_delete_command,void,"Cli*, const char*"
|
||||||
|
Function,+,cli_enumerate_external_commands,void,Cli*
|
||||||
Function,+,cli_is_pipe_broken_or_is_etx_next_char,_Bool,PipeSide*
|
Function,+,cli_is_pipe_broken_or_is_etx_next_char,_Bool,PipeSide*
|
||||||
Function,+,cli_print_usage,void,"const char*, const char*, const char*"
|
Function,+,cli_print_usage,void,"const char*, const char*, const char*"
|
||||||
|
Function,+,cli_remove_external_commands,void,Cli*
|
||||||
Function,+,cli_vcp_disable,void,CliVcp*
|
Function,+,cli_vcp_disable,void,CliVcp*
|
||||||
Function,+,cli_vcp_enable,void,CliVcp*
|
Function,+,cli_vcp_enable,void,CliVcp*
|
||||||
Function,+,composite_api_resolver_add,void,"CompositeApiResolver*, const ElfApiInterface*"
|
Function,+,composite_api_resolver_add,void,"CompositeApiResolver*, const ElfApiInterface*"
|
||||||
|
|||||||
|
@@ -1,5 +1,5 @@
|
|||||||
entry,status,name,type,params
|
entry,status,name,type,params
|
||||||
Version,+,84.0,,
|
Version,+,84.1,,
|
||||||
Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,,
|
Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,,
|
||||||
Header,+,applications/services/bt/bt_service/bt.h,,
|
Header,+,applications/services/bt/bt_service/bt.h,,
|
||||||
Header,+,applications/services/bt/bt_service/bt_keys_storage.h,,
|
Header,+,applications/services/bt/bt_service/bt_keys_storage.h,,
|
||||||
@@ -863,8 +863,10 @@ Function,+,cli_ansi_parser_feed,CliAnsiParserResult,"CliAnsiParser*, char"
|
|||||||
Function,+,cli_ansi_parser_feed_timeout,CliAnsiParserResult,CliAnsiParser*
|
Function,+,cli_ansi_parser_feed_timeout,CliAnsiParserResult,CliAnsiParser*
|
||||||
Function,+,cli_ansi_parser_free,void,CliAnsiParser*
|
Function,+,cli_ansi_parser_free,void,CliAnsiParser*
|
||||||
Function,+,cli_delete_command,void,"Cli*, const char*"
|
Function,+,cli_delete_command,void,"Cli*, const char*"
|
||||||
|
Function,+,cli_enumerate_external_commands,void,Cli*
|
||||||
Function,+,cli_is_pipe_broken_or_is_etx_next_char,_Bool,PipeSide*
|
Function,+,cli_is_pipe_broken_or_is_etx_next_char,_Bool,PipeSide*
|
||||||
Function,+,cli_print_usage,void,"const char*, const char*, const char*"
|
Function,+,cli_print_usage,void,"const char*, const char*, const char*"
|
||||||
|
Function,+,cli_remove_external_commands,void,Cli*
|
||||||
Function,+,cli_vcp_disable,void,CliVcp*
|
Function,+,cli_vcp_disable,void,CliVcp*
|
||||||
Function,+,cli_vcp_enable,void,CliVcp*
|
Function,+,cli_vcp_enable,void,CliVcp*
|
||||||
Function,+,composite_api_resolver_add,void,"CompositeApiResolver*, const ElfApiInterface*"
|
Function,+,composite_api_resolver_add,void,"CompositeApiResolver*, const ElfApiInterface*"
|
||||||
@@ -3454,13 +3456,13 @@ Function,+,subghz_file_encoder_worker_get_level_duration,LevelDuration,void*
|
|||||||
Function,+,subghz_file_encoder_worker_is_running,_Bool,SubGhzFileEncoderWorker*
|
Function,+,subghz_file_encoder_worker_is_running,_Bool,SubGhzFileEncoderWorker*
|
||||||
Function,+,subghz_file_encoder_worker_start,_Bool,"SubGhzFileEncoderWorker*, const char*, const char*"
|
Function,+,subghz_file_encoder_worker_start,_Bool,"SubGhzFileEncoderWorker*, const char*, const char*"
|
||||||
Function,+,subghz_file_encoder_worker_stop,void,SubGhzFileEncoderWorker*
|
Function,+,subghz_file_encoder_worker_stop,void,SubGhzFileEncoderWorker*
|
||||||
Function,-,subghz_keystore_alloc,SubGhzKeystore*,
|
Function,+,subghz_keystore_alloc,SubGhzKeystore*,
|
||||||
Function,-,subghz_keystore_free,void,SubGhzKeystore*
|
Function,+,subghz_keystore_free,void,SubGhzKeystore*
|
||||||
Function,-,subghz_keystore_get_data,SubGhzKeyArray_t*,SubGhzKeystore*
|
Function,+,subghz_keystore_get_data,SubGhzKeyArray_t*,SubGhzKeystore*
|
||||||
Function,-,subghz_keystore_load,_Bool,"SubGhzKeystore*, const char*"
|
Function,+,subghz_keystore_load,_Bool,"SubGhzKeystore*, const char*"
|
||||||
Function,-,subghz_keystore_raw_encrypted_save,_Bool,"const char*, const char*, uint8_t*"
|
Function,+,subghz_keystore_raw_encrypted_save,_Bool,"const char*, const char*, uint8_t*"
|
||||||
Function,-,subghz_keystore_raw_get_data,_Bool,"const char*, size_t, uint8_t*, size_t"
|
Function,+,subghz_keystore_raw_get_data,_Bool,"const char*, size_t, uint8_t*, size_t"
|
||||||
Function,-,subghz_keystore_save,_Bool,"SubGhzKeystore*, const char*, uint8_t*"
|
Function,+,subghz_keystore_save,_Bool,"SubGhzKeystore*, const char*, uint8_t*"
|
||||||
Function,+,subghz_protocol_blocks_add_bit,void,"SubGhzBlockDecoder*, uint8_t"
|
Function,+,subghz_protocol_blocks_add_bit,void,"SubGhzBlockDecoder*, uint8_t"
|
||||||
Function,+,subghz_protocol_blocks_add_bytes,uint8_t,"const uint8_t[], size_t"
|
Function,+,subghz_protocol_blocks_add_bytes,uint8_t,"const uint8_t[], size_t"
|
||||||
Function,+,subghz_protocol_blocks_add_to_128_bit,void,"SubGhzBlockDecoder*, uint8_t, uint64_t*"
|
Function,+,subghz_protocol_blocks_add_to_128_bit,void,"SubGhzBlockDecoder*, uint8_t, uint64_t*"
|
||||||
|
|||||||
|
Reference in New Issue
Block a user