1
mirror of https://github.com/flipperdevices/flipperzero-firmware.git synced 2025-12-12 04:41:26 +04:00

[FL-3954, FL-3955] New CLI architecture (#4111)

* 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: 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: fix rpc lockup

* cli: better lockup fix

* cli: fix f18

* fix merge

---------

Co-authored-by: Georgii Surkov <georgii.surkov@outlook.com>
Co-authored-by: あく <alleteam@gmail.com>
This commit is contained in:
Anna Antonenko
2025-04-02 22:10:10 +04:00
committed by GitHub
parent 5786066512
commit 13333edd30
60 changed files with 2074 additions and 1339 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

@@ -3,6 +3,7 @@
#include <music_worker/music_worker.h> #include <music_worker/music_worker.h>
#include <cli/cli.h> #include <cli/cli.h>
#include <toolbox/args.h> #include <toolbox/args.h>
#include <toolbox/pipe.h>
#define TAG "SpeakerDebug" #define TAG "SpeakerDebug"
@@ -37,8 +38,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;

View File

@@ -4,6 +4,7 @@
#include <cli/cli.h> #include <cli/cli.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,16 @@ 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) {
AncillaryThreadContext* ctx = context; AncillaryThreadContext* ctx = context;
ctx->flag |= TestFlagSpaceFreed; ctx->flag |= TestFlagSpaceFreed;
const char* message = "Hi!"; const char* message = "Hi!";
pipe_send(pipe, message, strlen(message), 0); pipe_send(pipe, message, strlen(message));
} }
static void on_became_broken(PipeSide* pipe, void* context) { static void on_became_broken(PipeSide* pipe, void* context) {
@@ -117,15 +113,15 @@ 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]; char buffer_2[16];
const char* expected_reply = "Hi!"; const char* expected_reply = "Hi!";
size = pipe_receive(alice, buffer_2, sizeof(buffer_2), FuriWaitForever); size = pipe_receive(alice, buffer_2, strlen(expected_reply));
buffer_2[size] = 0; buffer_2[size] = 0;
pipe_free(alice); pipe_free(alice);

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,13 +1,13 @@
#include <furi.h> #include <furi.h>
#include <cli/cli.h> #include <cli/cli.h>
#include <toolbox/pipe.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);
} }

View File

@@ -21,6 +21,7 @@ App(
name="On start hooks", name="On start hooks",
apptype=FlipperAppType.METAPACKAGE, apptype=FlipperAppType.METAPACKAGE,
provides=[ provides=[
"cli",
"ibutton_start", "ibutton_start",
"onewire_start", "onewire_start",
"subghz_start", "subghz_start",

View File

@@ -106,15 +106,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 +123,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 +309,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

@@ -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_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,8 @@ 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) { void ibutton_cli(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(cli); UNUSED(pipe);
UNUSED(context); UNUSED(context);
FuriString* cmd; FuriString* cmd;
cmd = furi_string_alloc(); cmd = furi_string_alloc();
@@ -241,14 +229,24 @@ 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);
} }
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
}

View File

@@ -1,11 +1,11 @@
#include <cli/cli.h> #include <cli/cli_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 infrared_cli_start_ir(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,7 +546,7 @@ 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();
} }

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_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 lfrfid_cli(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,26 @@ 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);
} }
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);
}

View File

@@ -1,8 +1,10 @@
#include <furi.h> #include <furi.h>
#include <furi_hal.h> #include <furi_hal.h>
#include <cli/cli.h> #include <cli/cli.h>
#include <cli/cli_commands.h>
#include <lib/toolbox/args.h> #include <lib/toolbox/args.h>
#include <lib/toolbox/hex.h> #include <lib/toolbox/hex.h>
#include <toolbox/pipe.h>
#include <furi_hal_nfc.h> #include <furi_hal_nfc.h>
@@ -17,7 +19,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) {
@@ -32,7 +34,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);
} }
@@ -40,7 +42,7 @@ static void nfc_cli_field(Cli* cli, FuriString* args) {
furi_hal_nfc_release(); furi_hal_nfc_release();
} }
static void nfc_cli(Cli* cli, FuriString* args, void* context) { static void nfc_cli(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(context); UNUSED(context);
FuriString* cmd; FuriString* cmd;
cmd = furi_string_alloc(); cmd = furi_string_alloc();
@@ -52,7 +54,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;
} }
} }

View File

@@ -2,31 +2,18 @@
#include <furi_hal.h> #include <furi_hal.h>
#include <power/power_service/power.h> #include <power/power_service/power.h>
#include <cli/cli_commands.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 +45,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 onewire_cli(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(context); UNUSED(context);
FuriString* cmd; FuriString* cmd;
cmd = furi_string_alloc(); cmd = furi_string_alloc();
@@ -70,8 +57,18 @@ 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);
} }
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
}

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

@@ -2,6 +2,7 @@
#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 <cli/cli.h>
#include <toolbox/pipe.h>
typedef struct SubGhzChatWorker SubGhzChatWorker; typedef struct SubGhzChatWorker SubGhzChatWorker;
@@ -19,7 +20,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,7 @@
#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_commands.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 +17,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"
@@ -61,7 +63,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;
@@ -91,7 +93,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 {
@@ -104,7 +106,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;
@@ -132,7 +134,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);
@@ -165,7 +167,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;
@@ -235,7 +237,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);
@@ -292,7 +296,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
@@ -348,7 +352,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)) {
@@ -381,7 +385,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;
@@ -419,7 +423,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) {
@@ -455,7 +459,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; FuriString* file_name;
file_name = furi_string_alloc(); file_name = furi_string_alloc();
@@ -525,7 +529,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)) {
@@ -570,7 +574,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();
@@ -774,7 +778,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);
@@ -788,11 +792,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);
@@ -837,8 +841,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; FuriString* source;
@@ -880,8 +884,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; FuriString* source;
@@ -917,7 +921,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
@@ -951,7 +955,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");
@@ -992,13 +996,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");
@@ -1020,7 +1023,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');
@@ -1034,7 +1037,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);
@@ -1088,7 +1091,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);
@@ -1113,7 +1116,7 @@ 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 subghz_cli_command(PipeSide* pipe, FuriString* args, void* context) {
FuriString* cmd; FuriString* cmd;
cmd = furi_string_alloc(); cmd = furi_string_alloc();
@@ -1124,53 +1127,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;
} }
} }

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

@@ -2,14 +2,15 @@
#include <furi_hal.h> #include <furi_hal.h>
#include <cli/cli.h> #include <cli/cli.h>
#include <lib/toolbox/args.h> #include <lib/toolbox/args.h>
#include <toolbox/pipe.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 +20,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 +42,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 +52,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 +70,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 +83,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 +120,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 +131,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 +153,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 +180,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 +195,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;
} }
} }

View File

@@ -1,10 +1,34 @@
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.c",
sdk_headers=["cli.h", "cli_vcp.h"], "shell/cli_shell.c",
"shell/cli_shell_line.c",
"cli_commands.c",
"cli_command_gpio.c",
"cli_ansi.c",
],
sdk_headers=[
"cli.h",
"cli_ansi.h",
],
# 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"],
) )

View File

@@ -1,406 +1,57 @@
#include "cli.h"
#include "cli_i.h" #include "cli_i.h"
#include "cli_commands.h" #include "cli_commands.h"
#include "cli_vcp.h" #include "cli_ansi.h"
#include <furi_hal_version.h> #include <toolbox/pipe.h>
#include <loader/loader.h>
#define TAG "CliSrv" #define TAG "cli"
#define CLI_INPUT_LEN_LIMIT 256 struct Cli {
CliCommandTree_t commands;
FuriMutex* mutex;
};
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->last_line = furi_string_alloc();
cli->line = furi_string_alloc();
cli->session = NULL;
cli->mutex = furi_mutex_alloc(FuriMutexTypeNormal); cli->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
cli->idle_sem = furi_semaphore_alloc(1, 0);
return cli; 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( void cli_add_command(
Cli* cli, Cli* cli,
const char* name, const char* name,
CliCommandFlag flags, CliCommandFlag flags,
CliCallback callback, CliExecuteCallback callback,
void* context) { void* context) {
cli_add_command_ex(cli, name, flags, callback, context, CLI_BUILTIN_COMMAND_STACK_SIZE);
}
void cli_add_command_ex(
Cli* cli,
const char* name,
CliCommandFlag flags,
CliExecuteCallback callback,
void* context,
size_t stack_size) {
furi_check(cli); furi_check(cli);
furi_check(name);
furi_check(callback);
FuriString* name_str; FuriString* name_str;
name_str = furi_string_alloc_set(name); name_str = furi_string_alloc_set(name);
furi_string_trim(name_str); // command cannot contain spaces
furi_check(furi_string_search_char(name_str, ' ') == FURI_STRING_FAILURE);
size_t name_replace; CliCommand command = {
do { .context = context,
name_replace = furi_string_replace(name_str, " ", "_"); .execute_callback = callback,
} while(name_replace != FURI_STRING_FAILURE); .flags = flags,
.stack_depth = stack_size,
CliCommand c; };
c.callback = callback;
c.context = context;
c.flags = flags;
furi_check(furi_mutex_acquire(cli->mutex, FuriWaitForever) == FuriStatusOk); furi_check(furi_mutex_acquire(cli->mutex, FuriWaitForever) == FuriStatusOk);
CliCommandTree_set_at(cli->commands, name_str, c); CliCommandTree_set_at(cli->commands, name_str, command);
furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk); furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk);
furi_string_free(name_str); furi_string_free(name_str);
@@ -424,61 +75,49 @@ void cli_delete_command(Cli* cli, const char* name) {
furi_string_free(name_str); furi_string_free(name_str);
} }
void cli_session_open(Cli* cli, const void* session) { bool cli_get_command(Cli* cli, FuriString* command, CliCommand* result) {
furi_check(cli); furi_assert(cli);
furi_check(furi_mutex_acquire(cli->mutex, FuriWaitForever) == FuriStatusOk); furi_check(furi_mutex_acquire(cli->mutex, FuriWaitForever) == FuriStatusOk);
cli->session = session; CliCommand* data = CliCommandTree_get(cli->commands, command);
if(cli->session != NULL) { if(data) *result = *data;
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); furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk);
return !!data;
} }
void cli_session_close(Cli* cli) { void cli_lock_commands(Cli* cli) {
furi_check(cli); furi_assert(cli);
furi_check(furi_mutex_acquire(cli->mutex, FuriWaitForever) == FuriStatusOk); 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) { void cli_unlock_commands(Cli* cli) {
UNUSED(p); furi_assert(cli);
furi_mutex_release(cli->mutex);
}
CliCommandTree_t* cli_get_commands(Cli* cli) {
furi_assert(cli);
return &cli->commands;
}
bool cli_is_pipe_broken_or_is_etx_next_char(PipeSide* side) {
if(pipe_state(side) == PipeStateBroken) return true;
if(!pipe_bytes_available(side)) return false;
char c = getchar();
return c == CliKeyETX;
}
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_on_system_start(void) {
Cli* cli = cli_alloc(); Cli* cli = cli_alloc();
// Init basic cli commands
cli_commands_init(cli); cli_commands_init(cli);
furi_record_create(RECORD_CLI, 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,64 +1,102 @@
/** /**
* @file cli.h * @file cli.h
* Cli API * API for registering commands with the CLI
*/ */
#pragma once #pragma once
#include <furi.h> #include <furi.h>
#include <m-array.h>
#include "cli_ansi.h"
#include <toolbox/pipe.h>
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #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" #define RECORD_CLI "cli"
typedef enum {
CliCommandFlagDefault = 0, /**< Default */
CliCommandFlagParallelSafe = (1 << 0), /**< Safe to run in parallel with other apps */
CliCommandFlagInsomniaSafe = (1 << 1), /**< Safe to run with insomnia mode on */
CliCommandFlagDontAttachStdio = (1 << 2), /**< Do no attach I/O pipe to thread stdio */
} CliCommandFlag;
/** Cli type anonymous structure */ /** Cli type anonymous structure */
typedef struct Cli Cli; typedef struct Cli Cli;
/** Cli callback function pointer. Implement this interface and use /**
* add_cli_command * @brief CLI execution callback pointer. Implement this interface and use
* @param args string with what was passed after command * `add_cli_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 * This callback will be called from a separate thread spawned just for your
* @param name command name * command. The pipe will be installed as the thread's stdio, so you can use
* @param flags CliCommandFlag * `printf`, `getchar` and other standard functions to communicate with the
* @param callback callback function * user.
* @param context pointer to whatever we need to pass to callback *
* @param [in] pipe Pipe that can be used to send and receive data. If
* `CliCommandFlagDontAttachStdio` was not set, you can
* also use standard C functions (printf, getc, etc.) to
* access this pipe.
* @param [in] args String with what was passed after the command
* @param [in] context Whatever you provided to `cli_add_command`
*/
typedef void (*CliExecuteCallback)(PipeSide* pipe, FuriString* args, void* context);
/**
* @brief Registers a command with the CLI. Provides less options than the `_ex`
* counterpart.
*
* @param [in] cli Pointer to CLI instance
* @param [in] name Command name
* @param [in] flags CliCommandFlag
* @param [in] callback Callback function
* @param [in] context Custom context
*/ */
void cli_add_command( void cli_add_command(
Cli* cli, Cli* cli,
const char* name, const char* name,
CliCommandFlag flags, CliCommandFlag flags,
CliCallback callback, CliExecuteCallback callback,
void* context); void* context);
/**
* @brief Registers a command with the CLI. Provides more options than the
* non-`_ex` counterpart.
*
* @param [in] cli Pointer to CLI instance
* @param [in] name Command name
* @param [in] flags CliCommandFlag
* @param [in] callback Callback function
* @param [in] context Custom context
* @param [in] stack_size Thread stack size
*/
void cli_add_command_ex(
Cli* cli,
const char* name,
CliCommandFlag flags,
CliExecuteCallback callback,
void* context,
size_t stack_size);
/**
* @brief Deletes a cli command
*
* @param [in] cli pointer to cli instance
* @param [in] name command name
*/
void cli_delete_command(Cli* cli, const char* name);
/**
* @brief Detects if Ctrl+C has been pressed or session has been terminated
*
* @param [in] side Pointer to pipe side given to the command thread
* @warning This function also assumes that the pipe is installed as the
* thread's stdio
* @warning This function will consume 1 byte from the pipe
*/
bool cli_is_pipe_broken_or_is_etx_next_char(PipeSide* side);
/** Print unified cmd usage tip /** Print unified cmd usage tip
* *
* @param cmd cmd name * @param cmd cmd name
@@ -67,68 +105,6 @@ void cli_add_command(
*/ */
void cli_print_usage(const char* cmd, const char* usage, const char* arg); 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 #ifdef __cplusplus
} }
#endif #endif

View File

@@ -0,0 +1,123 @@
#include "cli_ansi.h"
typedef enum {
CliAnsiParserStateInitial,
CliAnsiParserStateEscape,
CliAnsiParserStateEscapeBrace,
CliAnsiParserStateEscapeBraceOne,
CliAnsiParserStateEscapeBraceOneSemicolon,
CliAnsiParserStateEscapeBraceOneSemicolonModifiers,
} CliAnsiParserState;
struct CliAnsiParser {
CliAnsiParserState state;
CliModKey modifiers;
};
CliAnsiParser* cli_ansi_parser_alloc(void) {
CliAnsiParser* parser = malloc(sizeof(CliAnsiParser));
return parser;
}
void cli_ansi_parser_free(CliAnsiParser* parser) {
free(parser);
}
/**
* @brief Converts a single character representing a special key into the enum
* representation
*/
static CliKey cli_ansi_key_from_mnemonic(char c) {
switch(c) {
case 'A':
return CliKeyUp;
case 'B':
return CliKeyDown;
case 'C':
return CliKeyRight;
case 'D':
return CliKeyLeft;
case 'F':
return CliKeyEnd;
case 'H':
return CliKeyHome;
default:
return CliKeyUnrecognized;
}
}
#define PARSER_RESET_AND_RETURN(parser, modifiers_val, key_val) \
do { \
parser->state = CliAnsiParserStateInitial; \
return (CliAnsiParserResult){ \
.is_done = true, \
.result = (CliKeyCombo){ \
.modifiers = modifiers_val, \
.key = key_val, \
}}; \
} while(0);
CliAnsiParserResult cli_ansi_parser_feed(CliAnsiParser* parser, char c) {
switch(parser->state) {
case CliAnsiParserStateInitial:
// <key> -> <key>
if(c != CliKeyEsc) PARSER_RESET_AND_RETURN(parser, CliModKeyNo, c); // -V1048
// <ESC> ...
parser->state = CliAnsiParserStateEscape;
break;
case CliAnsiParserStateEscape:
// <ESC> <ESC> -> <ESC>
if(c == CliKeyEsc) PARSER_RESET_AND_RETURN(parser, CliModKeyNo, c);
// <ESC> <key> -> Alt + <key>
if(c != '[') PARSER_RESET_AND_RETURN(parser, CliModKeyAlt, c);
// <ESC> [ ...
parser->state = CliAnsiParserStateEscapeBrace;
break;
case CliAnsiParserStateEscapeBrace:
// <ESC> [ <key mnemonic> -> <key>
if(c != '1') PARSER_RESET_AND_RETURN(parser, CliModKeyNo, cli_ansi_key_from_mnemonic(c));
// <ESC> [ 1 ...
parser->state = CliAnsiParserStateEscapeBraceOne;
break;
case CliAnsiParserStateEscapeBraceOne:
// <ESC> [ 1 <non-;> -> error
if(c != ';') PARSER_RESET_AND_RETURN(parser, CliModKeyNo, CliKeyUnrecognized);
// <ESC> [ 1 ; ...
parser->state = CliAnsiParserStateEscapeBraceOneSemicolon;
break;
case CliAnsiParserStateEscapeBraceOneSemicolon:
// <ESC> [ 1 ; <modifiers> ...
parser->modifiers = (c - '0');
parser->modifiers &= ~1;
parser->state = CliAnsiParserStateEscapeBraceOneSemicolonModifiers;
break;
case CliAnsiParserStateEscapeBraceOneSemicolonModifiers:
// <ESC> [ 1 ; <modifiers> <key mnemonic> -> <modifiers> + <key>
PARSER_RESET_AND_RETURN(parser, parser->modifiers, cli_ansi_key_from_mnemonic(c));
}
return (CliAnsiParserResult){.is_done = false};
}
CliAnsiParserResult cli_ansi_parser_feed_timeout(CliAnsiParser* parser) {
CliAnsiParserResult result = {.is_done = false};
if(parser->state == CliAnsiParserStateEscape) {
result.is_done = true;
result.result.key = CliKeyEsc;
result.result.modifiers = CliModKeyNo;
}
parser->state = CliAnsiParserStateInitial;
return result;
}

View File

@@ -0,0 +1,153 @@
#pragma once
#include "cli.h"
#ifdef __cplusplus
extern "C" {
#endif
// text styling
#define ANSI_RESET "\e[0m"
#define ANSI_BOLD "\e[1m"
#define ANSI_FAINT "\e[2m"
#define ANSI_INVERT "\e[7m"
#define ANSI_FG_BLACK "\e[30m"
#define ANSI_FG_RED "\e[31m"
#define ANSI_FG_GREEN "\e[32m"
#define ANSI_FG_YELLOW "\e[33m"
#define ANSI_FG_BLUE "\e[34m"
#define ANSI_FG_MAGENTA "\e[35m"
#define ANSI_FG_CYAN "\e[36m"
#define ANSI_FG_WHITE "\e[37m"
#define ANSI_FG_BR_BLACK "\e[90m"
#define ANSI_FG_BR_RED "\e[91m"
#define ANSI_FG_BR_GREEN "\e[92m"
#define ANSI_FG_BR_YELLOW "\e[93m"
#define ANSI_FG_BR_BLUE "\e[94m"
#define ANSI_FG_BR_MAGENTA "\e[95m"
#define ANSI_FG_BR_CYAN "\e[96m"
#define ANSI_FG_BR_WHITE "\e[97m"
#define ANSI_BG_BLACK "\e[40m"
#define ANSI_BG_RED "\e[41m"
#define ANSI_BG_GREEN "\e[42m"
#define ANSI_BG_YELLOW "\e[43m"
#define ANSI_BG_BLUE "\e[44m"
#define ANSI_BG_MAGENTA "\e[45m"
#define ANSI_BG_CYAN "\e[46m"
#define ANSI_BG_WHITE "\e[47m"
#define ANSI_BG_BR_BLACK "\e[100m"
#define ANSI_BG_BR_RED "\e[101m"
#define ANSI_BG_BR_GREEN "\e[102m"
#define ANSI_BG_BR_YELLOW "\e[103m"
#define ANSI_BG_BR_BLUE "\e[104m"
#define ANSI_BG_BR_MAGENTA "\e[105m"
#define ANSI_BG_BR_CYAN "\e[106m"
#define ANSI_BG_BR_WHITE "\e[107m"
#define ANSI_FLIPPER_BRAND_ORANGE "\e[38;2;255;130;0m"
// cursor positioning
#define ANSI_CURSOR_UP_BY(rows) "\e[" rows "A"
#define ANSI_CURSOR_DOWN_BY(rows) "\e[" rows "B"
#define ANSI_CURSOR_RIGHT_BY(cols) "\e[" cols "C"
#define ANSI_CURSOR_LEFT_BY(cols) "\e[" cols "D"
#define ANSI_CURSOR_DOWN_BY_AND_FIRST_COLUMN(rows) "\e[" rows "E"
#define ANSI_CURSOR_UP_BY_AND_FIRST_COLUMN(rows) "\e[" rows "F"
#define ANSI_CURSOR_HOR_POS(pos) "\e[" pos "G"
#define ANSI_CURSOR_POS(row, col) "\e[" row ";" col "H"
// erasing
#define ANSI_ERASE_FROM_CURSOR_TO_END "0"
#define ANSI_ERASE_FROM_START_TO_CURSOR "1"
#define ANSI_ERASE_ENTIRE "2"
#define ANSI_ERASE_DISPLAY(portion) "\e[" portion "J"
#define ANSI_ERASE_LINE(portion) "\e[" portion "K"
#define ANSI_ERASE_SCROLLBACK_BUFFER ANSI_ERASE_DISPLAY("3")
// misc
#define ANSI_INSERT_MODE_ENABLE "\e[4h"
#define ANSI_INSERT_MODE_DISABLE "\e[4l"
typedef enum {
CliKeyUnrecognized = 0,
CliKeySOH = 0x01,
CliKeyETX = 0x03,
CliKeyEOT = 0x04,
CliKeyBell = 0x07,
CliKeyBackspace = 0x08,
CliKeyTab = 0x09,
CliKeyLF = 0x0A,
CliKeyFF = 0x0C,
CliKeyCR = 0x0D,
CliKeyETB = 0x17,
CliKeyEsc = 0x1B,
CliKeyUS = 0x1F,
CliKeySpace = 0x20,
CliKeyDEL = 0x7F,
CliKeySpecial = 0x80,
CliKeyLeft,
CliKeyRight,
CliKeyUp,
CliKeyDown,
CliKeyHome,
CliKeyEnd,
} CliKey;
typedef enum {
CliModKeyNo = 0,
CliModKeyAlt = 2,
CliModKeyCtrl = 4,
CliModKeyMeta = 8,
} CliModKey;
typedef struct {
CliModKey modifiers;
CliKey key;
} CliKeyCombo;
typedef struct CliAnsiParser CliAnsiParser;
typedef struct {
bool is_done;
CliKeyCombo result;
} CliAnsiParserResult;
/**
* @brief Allocates an ANSI parser
*/
CliAnsiParser* cli_ansi_parser_alloc(void);
/**
* @brief Frees an ANSI parser
*/
void cli_ansi_parser_free(CliAnsiParser* parser);
/**
* @brief Feeds an ANSI parser a character
*/
CliAnsiParserResult cli_ansi_parser_feed(CliAnsiParser* parser, char c);
/**
* @brief Feeds an ANSI parser a timeout event
*
* As a user of the ANSI parser API, you are responsible for calling this
* function some time after the last character was fed into the parser. The
* recommended timeout is about 10 ms. The exact value does not matter as long
* as it is small enough for the user not notice a delay, but big enough that
* when a terminal is sending an escape sequence, this function does not get
* called in between the characters of the sequence.
*/
CliAnsiParserResult cli_ansi_parser_feed_timeout(CliAnsiParser* parser);
#ifdef __cplusplus
}
#endif

View File

@@ -3,6 +3,7 @@
#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>
void cli_command_gpio_print_usage(void) { void cli_command_gpio_print_usage(void) {
printf("Usage:\r\n"); printf("Usage:\r\n");
@@ -70,8 +71,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 +94,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 +111,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 +132,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 +161,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 +172,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 +183,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,6 @@
#pragma once #pragma once
#include "cli_i.h" #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 +1,7 @@
#include "cli_commands.h" #include "cli_commands.h"
#include "cli_command_gpio.h" #include "cli_command_gpio.h"
#include "cli_ansi.h"
#include "cli.h"
#include <core/thread.h> #include <core/thread.h>
#include <furi_hal.h> #include <furi_hal.h>
@@ -11,6 +13,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 +37,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 +56,57 @@ void cli_command_info(Cli* cli, FuriString* args, void* context) {
} }
} }
void cli_command_help(Cli* cli, FuriString* args, void* context) { void cli_command_help(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(pipe);
UNUSED(args); UNUSED(args);
UNUSED(context); UNUSED(context);
printf("Commands available:"); printf("Available commands:" ANSI_FG_GREEN);
// Command count // count non-hidden commands
const size_t commands_count = CliCommandTree_size(cli->commands); Cli* cli = furi_record_open(RECORD_CLI);
const size_t commands_count_mid = commands_count / 2 + commands_count % 2; cli_lock_commands(cli);
CliCommandTree_t* commands = cli_get_commands(cli);
size_t commands_count = CliCommandTree_size(*commands);
// Use 2 iterators from start and middle to show 2 columns // create iterators starting at different positions
CliCommandTree_it_t it_left; const size_t columns = 3;
CliCommandTree_it(it_left, cli->commands); const size_t commands_per_column = (commands_count / columns) + (commands_count % columns);
CliCommandTree_it_t it_right; CliCommandTree_it_t iterators[columns];
CliCommandTree_it(it_right, cli->commands); for(size_t c = 0; c < columns; c++) {
for(size_t i = 0; i < commands_count_mid; i++) CliCommandTree_it(iterators[c], *commands);
CliCommandTree_next(it_right); for(size_t i = 0; i < c * commands_per_column; i++)
CliCommandTree_next(iterators[c]);
// 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");
} }
// print commands
for(size_t r = 0; r < commands_per_column; r++) {
printf("\r\n");
for(size_t c = 0; c < columns; c++) {
if(!CliCommandTree_end_p(iterators[c])) {
const CliCommandTree_itref_t* item = CliCommandTree_cref(iterators[c]);
printf("%-30s", furi_string_get_cstr(*item->key_ptr));
CliCommandTree_next(iterators[c]);
}
}
}
printf(ANSI_RESET "\r\nFind out more: https://docs.flipper.net/development/cli");
cli_unlock_commands(cli);
furi_record_close(RECORD_CLI);
} }
void cli_command_uptime(Cli* cli, FuriString* args, void* context) { void cli_command_uptime(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(cli); UNUSED(pipe);
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};
@@ -174,7 +178,8 @@ void cli_command_date(Cli* cli, FuriString* args, void* context) {
#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) {
@@ -196,16 +201,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;
@@ -217,16 +219,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);
@@ -235,12 +236,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);
@@ -253,8 +252,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);
@@ -288,7 +287,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();
@@ -299,12 +298,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;
} }
@@ -314,8 +313,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")) {
@@ -341,8 +340,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;
@@ -396,23 +395,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,
@@ -420,14 +419,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",
@@ -436,12 +437,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,
@@ -453,6 +455,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 {
@@ -462,8 +467,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);
@@ -476,16 +481,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);
@@ -507,6 +512,27 @@ 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);
} }
/**
* Echoes any bytes it receives except ASCII ETX (0x03, Ctrl+C)
*/
void cli_command_echo(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(args);
UNUSED(context);
uint8_t buffer[256];
while(true) {
size_t to_read = CLAMP(pipe_bytes_available(pipe), sizeof(buffer), 1UL);
size_t read = pipe_receive(pipe, buffer, to_read);
if(read < to_read) break;
if(memchr(buffer, CliKeyETX, read)) break;
size_t written = pipe_send(pipe, buffer, read);
if(written < read) break;
}
}
void cli_commands_init(Cli* cli) { 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);
@@ -522,6 +548,7 @@ void cli_commands_init(Cli* cli) {
cli_add_command(cli, "top", CliCommandFlagParallelSafe, cli_command_top, 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", CliCommandFlagParallelSafe, cli_command_free, NULL);
cli_add_command(cli, "free_blocks", CliCommandFlagParallelSafe, cli_command_free_blocks, NULL); cli_add_command(cli, "free_blocks", CliCommandFlagParallelSafe, cli_command_free_blocks, NULL);
cli_add_command(cli, "echo", CliCommandFlagParallelSafe, cli_command_echo, NULL);
cli_add_command(cli, "vibro", CliCommandFlagDefault, cli_command_vibro, NULL); cli_add_command(cli, "vibro", CliCommandFlagDefault, cli_command_vibro, NULL);
cli_add_command(cli, "led", CliCommandFlagDefault, cli_command_led, NULL); cli_add_command(cli, "led", CliCommandFlagDefault, cli_command_led, NULL);

View File

@@ -1,5 +1,34 @@
#pragma once #pragma once
#include "cli_i.h" #include "cli.h"
#include <flipper_application/flipper_application.h>
void cli_commands_init(Cli* cli); void cli_commands_init(Cli* cli);
#define PLUGIN_APP_ID "cli"
#define PLUGIN_API_VERSION 1
typedef struct {
char* name;
CliExecuteCallback execute_callback;
CliCommandFlag flags;
size_t stack_depth;
} CliCommandDescriptor;
#define CLI_COMMAND_INTERFACE(name, execute_callback, flags, stack_depth) \
static const CliCommandDescriptor cli_##name##_desc = { \
#name, \
&execute_callback, \
flags, \
stack_depth, \
}; \
\
static const FlipperAppPluginDescriptor plugin_descriptor = { \
.appid = PLUGIN_APP_ID, \
.ep_api_version = PLUGIN_API_VERSION, \
.entry_point = &cli_##name##_desc, \
}; \
\
const FlipperAppPluginDescriptor* cli_##name##_ep(void) { \
return &plugin_descriptor; \
}

View File

@@ -1,67 +1,50 @@
/**
* @file cli_i.h
* Internal API for getting commands registered with the CLI
*/
#pragma once #pragma once
#include "cli.h"
#include <furi.h> #include <furi.h>
#include <furi_hal.h>
#include <m-dict.h>
#include <m-bptree.h> #include <m-bptree.h>
#include <m-array.h> #include "cli.h"
#include "cli_vcp.h"
#define CLI_LINE_SIZE_MAX
#define CLI_COMMANDS_TREE_RANK 4
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
#define CLI_BUILTIN_COMMAND_STACK_SIZE (3 * 1024U)
typedef struct { typedef struct {
CliCallback callback; void* context; //<! Context passed to callbacks
void* context; CliExecuteCallback execute_callback; //<! Callback for command execution
uint32_t flags; CliCommandFlag flags;
size_t stack_depth;
} CliCommand; } CliCommand;
struct CliSession { #define CLI_COMMANDS_TREE_RANK 4
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);
};
// -V:BPTREE_DEF2:1103
// -V:BPTREE_DEF2:524
BPTREE_DEF2( BPTREE_DEF2(
CliCommandTree, CliCommandTree,
CLI_COMMANDS_TREE_RANK, CLI_COMMANDS_TREE_RANK,
FuriString*, FuriString*,
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_OPLIST(CliCommandTree, M_POD_OPLIST)
struct Cli { bool cli_get_command(Cli* cli, FuriString* command, CliCommand* result);
CliCommandTree_t commands;
FuriMutex* mutex;
FuriSemaphore* idle_sem;
FuriString* last_line;
FuriString* line;
const CliSession* session;
size_t cursor_position; void cli_lock_commands(Cli* cli);
};
Cli* cli_alloc(void); void cli_unlock_commands(Cli* cli);
void cli_reset(Cli* cli); /**
* @warning Surround calls to this function with `cli_[un]lock_commands`
void cli_putc(Cli* cli, char c); */
CliCommandTree_t* cli_get_commands(Cli* cli);
void cli_stdout_callback(void* _cookie, const char* data, size_t size);
#ifdef __cplusplus #ifdef __cplusplus
} }

View File

@@ -1,323 +1,298 @@
#include "cli_i.h" // IWYU pragma: keep #include "cli_vcp.h"
#include "shell/cli_shell.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>
#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_MESSAGE_Q_LEN 8
#define VCP_IF_NUM 0 #ifdef CLI_VCP_TRACE
#define VCP_TRACE(...) FURI_LOG_T(__VA_ARGS__)
#ifdef CLI_VCP_DEBUG
#define VCP_DEBUG(...) FURI_LOG_D(TAG, __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); bool is_currently_transmitting;
static void vcp_on_cdc_rx(void* context); size_t previous_tx_length;
static void vcp_state_callback(void* context, uint8_t state);
static void vcp_on_cdc_control_line(void* context, uint8_t state);
static CdcCallbacks cdc_cb = { FuriThread* shell;
vcp_on_cdc_tx_complete,
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);
break;
case CliVcpMessageTypeDisable:
if(!cli_vcp->is_enabled) return;
FURI_LOG_D(TAG, "Disabling");
cli_vcp->is_enabled = false;
// restore usb mode
furi_hal_cdc_set_callbacks(VCP_IF_NUM, NULL, NULL);
furi_hal_usb_set_config(cli_vcp->previous_interface, NULL);
break;
} }
furi_hal_cdc_set_callbacks(VCP_IF_NUM, &cdc_cb, NULL);
FURI_LOG_D(TAG, "Start");
vcp->running = true;
while(1) {
uint32_t flags =
furi_thread_flags_wait(VCP_THREAD_FLAG_ALL, FuriFlagWaitAny, FuriWaitForever);
furi_assert(!(flags & FuriFlagError));
// VCP session opened
if(flags & VcpEvtConnect) {
VCP_DEBUG("Connect");
if(vcp->connected == false) {
vcp->connected = true;
furi_stream_buffer_send(vcp->rx_stream, &ascii_soh, 1, FuriWaitForever);
}
}
// VCP session closed
if(flags & VcpEvtDisconnect) {
VCP_DEBUG("Disconnect");
if(vcp->connected == true) {
vcp->connected = false;
furi_stream_buffer_receive(vcp->tx_stream, vcp->data_buffer, USB_CDC_PKT_LEN, 0);
furi_stream_buffer_send(vcp->rx_stream, &ascii_eot, 1, FuriWaitForever);
}
}
// Rx buffer was read, maybe there is enough space for new data?
if((flags & VcpEvtStreamRx) && (missed_rx > 0)) {
VCP_DEBUG("StreamRx");
if(furi_stream_buffer_spaces_available(vcp->rx_stream) >= USB_CDC_PKT_LEN) {
flags |= VcpEvtRx;
missed_rx--;
}
}
// New data received
if(flags & VcpEvtRx) {
if(furi_stream_buffer_spaces_available(vcp->rx_stream) >= USB_CDC_PKT_LEN) {
int32_t len = furi_hal_cdc_receive(VCP_IF_NUM, vcp->data_buffer, USB_CDC_PKT_LEN);
VCP_DEBUG("Rx %ld", len);
if(len > 0) {
furi_check(
furi_stream_buffer_send(
vcp->rx_stream, vcp->data_buffer, len, FuriWaitForever) ==
(size_t)len);
}
} else {
VCP_DEBUG("Rx missed");
missed_rx++;
}
}
// New data in Tx buffer
if(flags & VcpEvtStreamTx) {
VCP_DEBUG("StreamTx");
if(tx_idle) {
flags |= VcpEvtTx;
}
}
// CDC write transfer done
if(flags & VcpEvtTx) {
size_t len =
furi_stream_buffer_receive(vcp->tx_stream, vcp->data_buffer, USB_CDC_PKT_LEN, 0);
VCP_DEBUG("Tx %d", len);
if(len > 0) { // Some data left in Tx buffer. Sending it now
tx_idle = false;
furi_hal_cdc_send(VCP_IF_NUM, vcp->data_buffer, len);
last_tx_pkt_len = len;
} else { // There is nothing to send.
if(last_tx_pkt_len == 64) {
// Send extra zero-length packet if last packet len is 64 to indicate transfer end
furi_hal_cdc_send(VCP_IF_NUM, NULL, 0);
} else {
// Set flag to start next transfer instantly
tx_idle = true;
}
last_tx_pkt_len = 0;
}
}
if(flags & VcpEvtStop) {
vcp->connected = false;
vcp->running = false;
furi_hal_cdc_set_callbacks(VCP_IF_NUM, NULL, NULL);
// Restore previous USB mode (if it was set during init)
if((vcp->usb_if_prev != &usb_cdc_single) && (vcp->usb_if_prev != &usb_cdc_dual)) {
furi_hal_usb_unlock();
furi_hal_usb_set_config(vcp->usb_if_prev, NULL);
}
furi_stream_buffer_receive(vcp->tx_stream, vcp->data_buffer, USB_CDC_PKT_LEN, 0);
furi_stream_buffer_send(vcp->rx_stream, &ascii_eot, 1, FuriWaitForever);
break;
}
}
FURI_LOG_D(TAG, "End");
return 0;
} }
static size_t cli_vcp_rx(uint8_t* buffer, size_t size, uint32_t timeout) { /**
furi_assert(vcp); * Processes messages arriving from CDC event callbacks
furi_assert(buffer); */
static void cli_vcp_internal_event_happened(void* context) {
CliVcp* cli_vcp = context;
CliVcpInternalEvent event = furi_thread_flags_wait(CliVcpInternalEventAll, FuriFlagWaitAny, 0);
furi_check(!(event & FuriFlagError));
if(vcp->running == false) { if(event & CliVcpInternalEventDisconnected) {
if(!cli_vcp->is_connected) return;
FURI_LOG_D(TAG, "Disconnected");
cli_vcp->is_connected = false;
// disconnect our side of the pipe
pipe_detach_from_event_loop(cli_vcp->own_pipe);
pipe_free(cli_vcp->own_pipe);
cli_vcp->own_pipe = NULL;
}
if(event & CliVcpInternalEventConnected) {
if(cli_vcp->is_connected) return;
FURI_LOG_D(TAG, "Connected");
cli_vcp->is_connected = true;
// wait for previous shell to stop
furi_check(!cli_vcp->own_pipe);
if(cli_vcp->shell) {
furi_thread_join(cli_vcp->shell);
furi_thread_free(cli_vcp->shell);
}
// start shell thread
PipeSideBundle bundle = pipe_alloc(VCP_BUF_SIZE, 1);
cli_vcp->own_pipe = bundle.alices_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_start(bundle.bobs_side);
}
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);
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;
} }
VCP_DEBUG("rx %u start", size); CliVcp* cli_vcp = cli_vcp_alloc();
furi_record_create(RECORD_CLI_VCP, cli_vcp);
furi_event_loop_run(cli_vcp->event_loop);
size_t rx_cnt = 0; return 0;
while(size > 0) {
size_t batch_size = size;
if(batch_size > VCP_RX_BUF_SIZE) batch_size = VCP_RX_BUF_SIZE;
size_t len = furi_stream_buffer_receive(vcp->rx_stream, buffer, batch_size, timeout);
VCP_DEBUG("rx %u ", batch_size);
if(len == 0) break;
if(vcp->running == false) {
// EOT command is received after VCP session close
rx_cnt += len;
break;
}
furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtStreamRx);
size -= len;
buffer += len;
rx_cnt += len;
}
VCP_DEBUG("rx %u end", size);
return rx_cnt;
} }
static size_t cli_vcp_rx_stdin(uint8_t* data, size_t size, uint32_t timeout, void* context) { // ==========
UNUSED(context); // Public API
return cli_vcp_rx(data, size, timeout); // ==========
void cli_vcp_enable(CliVcp* cli_vcp) {
CliVcpMessage message = {
.type = CliVcpMessageTypeEnable,
};
furi_message_queue_put(cli_vcp->message_queue, &message, FuriWaitForever);
} }
static void cli_vcp_tx(const uint8_t* buffer, size_t size) { void cli_vcp_disable(CliVcp* cli_vcp) {
furi_assert(vcp); CliVcpMessage message = {
furi_assert(buffer); .type = CliVcpMessageTypeDisable,
};
if(vcp->running == false) { furi_message_queue_put(cli_vcp->message_queue, &message, FuriWaitForever);
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,
};

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,251 @@
#include "cli_shell.h"
#include "cli_shell_i.h"
#include "../cli_ansi.h"
#include "../cli_i.h"
#include "../cli_commands.h"
#include "cli_shell_line.h"
#include <stdio.h>
#include <furi_hal_version.h>
#include <m-array.h>
#include <loader/loader.h>
#include <toolbox/pipe.h>
#include <flipper_application/plugins/plugin_manager.h>
#include <loader/firmware_api/firmware_api.h>
#define TAG "CliShell"
#define ANSI_TIMEOUT_MS 10
typedef enum {
CliShellComponentLine,
CliShellComponentMAX, //<! do not use
} CliShellComponent;
CliShellKeyComboSet* component_key_combo_sets[] = {
[CliShellComponentLine] = &cli_shell_line_key_combo_set,
};
static_assert(CliShellComponentMAX == COUNT_OF(component_key_combo_sets));
struct CliShell {
Cli* cli;
FuriEventLoop* event_loop;
PipeSide* pipe;
CliAnsiParser* ansi_parser;
FuriEventLoopTimer* ansi_parsing_timer;
void* components[CliShellComponentMAX];
};
typedef struct {
CliCommand* command;
PipeSide* pipe;
FuriString* args;
} CliCommandThreadData;
// =========
// Execution
// =========
void cli_shell_execute_command(CliShell* cli_shell, FuriString* command) {
// split command into command and args
size_t space = furi_string_search_char(command, ' ');
if(space == FURI_STRING_FAILURE) space = furi_string_size(command);
FuriString* command_name = furi_string_alloc_set(command);
furi_string_left(command_name, space);
FuriString* args = furi_string_alloc_set(command);
furi_string_right(args, space + 1);
Loader* loader = NULL;
CliCommand command_data;
do {
// find handler
if(!cli_get_command(cli_shell->cli, command_name, &command_data)) {
printf(
ANSI_FG_RED "could not find command `%s`, try `help`" ANSI_RESET,
furi_string_get_cstr(command_name));
break;
}
// lock loader
if(!(command_data.flags & CliCommandFlagParallelSafe)) {
loader = furi_record_open(RECORD_LOADER);
bool success = loader_lock(loader);
if(!success) {
printf(ANSI_FG_RED
"this command cannot be run while an application is open" ANSI_RESET);
break;
}
}
command_data.execute_callback(cli_shell->pipe, args, command_data.context);
} while(0);
furi_string_free(command_name);
furi_string_free(args);
// unlock loader
if(loader) loader_unlock(loader);
furi_record_close(RECORD_LOADER);
}
// ==============
// Event handlers
// ==============
static void cli_shell_process_key(CliShell* cli_shell, CliKeyCombo key_combo) {
for(size_t i = 0; i < CliShellComponentMAX; i++) { // -V1008
CliShellKeyComboSet* set = component_key_combo_sets[i];
void* component_context = cli_shell->components[i];
for(size_t j = 0; j < set->count; j++) {
if(set->records[j].combo.modifiers == key_combo.modifiers &&
set->records[j].combo.key == key_combo.key)
if(set->records[j].action(key_combo, component_context)) return;
}
if(set->fallback)
if(set->fallback(key_combo, component_context)) return;
}
}
static void cli_shell_pipe_broken(PipeSide* pipe, void* context) {
// allow commands to be processed before we stop the shell
if(pipe_bytes_available(pipe)) return;
CliShell* cli_shell = context;
furi_event_loop_stop(cli_shell->event_loop);
}
static void cli_shell_data_available(PipeSide* pipe, void* context) {
UNUSED(pipe);
CliShell* cli_shell = context;
furi_event_loop_timer_start(cli_shell->ansi_parsing_timer, furi_ms_to_ticks(ANSI_TIMEOUT_MS));
// process ANSI escape sequences
int c = getchar();
furi_assert(c >= 0);
CliAnsiParserResult parse_result = 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) {
CliShell* cli_shell = context;
CliAnsiParserResult parse_result = cli_ansi_parser_feed_timeout(cli_shell->ansi_parser);
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);
}
// =======
// Helpers
// =======
static CliShell* cli_shell_alloc(PipeSide* pipe) {
CliShell* cli_shell = malloc(sizeof(CliShell));
cli_shell->cli = furi_record_open(RECORD_CLI);
cli_shell->ansi_parser = cli_ansi_parser_alloc();
cli_shell->pipe = pipe;
pipe_install_as_stdio(cli_shell->pipe);
cli_shell->components[CliShellComponentLine] = cli_shell_line_alloc(cli_shell);
cli_shell->event_loop = furi_event_loop_alloc();
cli_shell->ansi_parsing_timer = furi_event_loop_timer_alloc(
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);
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);
return cli_shell;
}
static void cli_shell_free(CliShell* cli_shell) {
cli_shell_line_free(cli_shell->components[CliShellComponentLine]);
pipe_detach_from_event_loop(cli_shell->pipe);
furi_event_loop_timer_free(cli_shell->ansi_parsing_timer);
furi_event_loop_free(cli_shell->event_loop);
pipe_free(cli_shell->pipe);
cli_ansi_parser_free(cli_shell->ansi_parser);
furi_record_close(RECORD_CLI);
free(cli_shell);
}
static void cli_shell_motd(void) {
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));
}
}
static int32_t cli_shell_thread(void* context) {
PipeSide* pipe = context;
// Sometimes, the other side closes the pipe even before our thread is started. Although the
// rest of the code will eventually find this out if this check is removed, there's no point in
// wasting time.
if(pipe_state(pipe) == PipeStateBroken) return 0;
CliShell* cli_shell = cli_shell_alloc(pipe);
FURI_LOG_D(TAG, "Started");
cli_shell_motd();
cli_shell_line_prompt(cli_shell->components[CliShellComponentLine]);
furi_event_loop_run(cli_shell->event_loop);
FURI_LOG_D(TAG, "Stopped");
cli_shell_free(cli_shell);
return 0;
}
// ==========
// Public API
// ==========
FuriThread* cli_shell_start(PipeSide* pipe) {
FuriThread* thread =
furi_thread_alloc_ex("CliShell", CLI_SHELL_STACK_SIZE, cli_shell_thread, pipe);
furi_thread_start(thread);
return thread;
}

View File

@@ -0,0 +1,16 @@
#pragma once
#include <furi.h>
#include <toolbox/pipe.h>
#ifdef __cplusplus
extern "C" {
#endif
#define CLI_SHELL_STACK_SIZE (4 * 1024U)
FuriThread* cli_shell_start(PipeSide* pipe);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,32 @@
#pragma once
#include "../cli_ansi.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct CliShell CliShell;
/**
* @brief Key combo handler
* @return true if the event was handled, false otherwise
*/
typedef bool (*CliShellKeyComboAction)(CliKeyCombo combo, void* context);
typedef struct {
CliKeyCombo combo;
CliShellKeyComboAction action;
} CliShellKeyComboRecord;
typedef struct {
CliShellKeyComboAction fallback;
size_t count;
CliShellKeyComboRecord records[];
} CliShellKeyComboSet;
void cli_shell_execute_command(CliShell* cli_shell, FuriString* command);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,238 @@
#include "cli_shell_line.h"
#define HISTORY_DEPTH 10
struct CliShellLine {
size_t history_position;
size_t line_position;
FuriString* history[HISTORY_DEPTH];
size_t history_entries;
CliShell* shell;
};
// ==========
// Public API
// ==========
CliShellLine* cli_shell_line_alloc(CliShell* shell) {
CliShellLine* line = malloc(sizeof(CliShellLine));
line->shell = shell;
line->history[0] = furi_string_alloc();
line->history_entries = 1;
return line;
}
void cli_shell_line_free(CliShellLine* line) {
for(size_t i = 0; i < line->history_entries; i++)
furi_string_free(line->history[i]);
free(line);
}
FuriString* cli_shell_line_get_selected(CliShellLine* line) {
return line->history[line->history_position];
}
FuriString* cli_shell_line_get_editing(CliShellLine* line) {
return line->history[0];
}
size_t cli_shell_line_prompt_length(CliShellLine* line) {
UNUSED(line);
return strlen(">: ");
}
void cli_shell_line_format_prompt(CliShellLine* line, char* buf, size_t length) {
UNUSED(line);
snprintf(buf, length - 1, ">: ");
}
void cli_shell_line_prompt(CliShellLine* line) {
char buffer[32];
cli_shell_line_format_prompt(line, buffer, sizeof(buffer));
printf("\r\n%s", buffer);
fflush(stdout);
}
void cli_shell_line_ensure_not_overwriting_history(CliShellLine* line) {
if(line->history_position > 0) {
FuriString* source = cli_shell_line_get_selected(line);
FuriString* destination = cli_shell_line_get_editing(line);
furi_string_set(destination, source);
line->history_position = 0;
}
}
// ==============
// Input handlers
// ==============
static bool cli_shell_line_input_ctrl_c(CliKeyCombo combo, void* context) {
UNUSED(combo);
CliShellLine* line = context;
// reset input
furi_string_reset(cli_shell_line_get_editing(line));
line->line_position = 0;
line->history_position = 0;
printf("^C");
cli_shell_line_prompt(line);
return true;
}
static bool cli_shell_line_input_cr(CliKeyCombo combo, void* context) {
UNUSED(combo);
CliShellLine* line = context;
FuriString* command = cli_shell_line_get_selected(line);
furi_string_trim(command);
FuriString* command_copy = furi_string_alloc_set(command);
if(line->history_position > 0) {
// move selected command to the front
memmove(
&line->history[1], &line->history[0], line->history_position * sizeof(FuriString*));
line->history[0] = command;
}
// insert empty command
if(line->history_entries == HISTORY_DEPTH) {
furi_string_free(line->history[HISTORY_DEPTH - 1]);
line->history_entries--;
}
memmove(&line->history[1], &line->history[0], line->history_entries * sizeof(FuriString*));
line->history[0] = furi_string_alloc();
line->history_entries++;
line->line_position = 0;
line->history_position = 0;
// execute command
printf("\r\n");
if(!furi_string_empty(command_copy)) cli_shell_execute_command(line->shell, command_copy);
furi_string_free(command_copy);
cli_shell_line_prompt(line);
return true;
}
static bool cli_shell_line_input_up_down(CliKeyCombo combo, void* context) {
CliShellLine* line = context;
// go up and down in history
int increment = (combo.key == CliKeyUp) ? 1 : -1;
size_t new_pos =
CLAMP((int)line->history_position + increment, (int)line->history_entries - 1, 0);
// print prompt with selected command
if(new_pos != line->history_position) {
line->history_position = new_pos;
FuriString* command = cli_shell_line_get_selected(line);
printf(
ANSI_CURSOR_HOR_POS("1") ">: %s" ANSI_ERASE_LINE(ANSI_ERASE_FROM_CURSOR_TO_END),
furi_string_get_cstr(command));
fflush(stdout);
line->line_position = furi_string_size(command);
}
return true;
}
static bool cli_shell_line_input_left_right(CliKeyCombo combo, void* context) {
CliShellLine* line = context;
// go left and right in the current line
FuriString* command = cli_shell_line_get_selected(line);
int increment = (combo.key == CliKeyRight) ? 1 : -1;
size_t new_pos =
CLAMP((int)line->line_position + increment, (int)furi_string_size(command), 0);
// move cursor
if(new_pos != line->line_position) {
line->line_position = new_pos;
printf("%s", (increment == 1) ? ANSI_CURSOR_RIGHT_BY("1") : ANSI_CURSOR_LEFT_BY("1"));
fflush(stdout);
}
return true;
}
static bool cli_shell_line_input_home(CliKeyCombo combo, void* context) {
UNUSED(combo);
CliShellLine* line = context;
// go to the start
line->line_position = 0;
printf(ANSI_CURSOR_HOR_POS("%zu"), cli_shell_line_prompt_length(line) + 1);
fflush(stdout);
return true;
}
static bool cli_shell_line_input_end(CliKeyCombo combo, void* context) {
UNUSED(combo);
CliShellLine* line = context;
// go to the end
line->line_position = furi_string_size(cli_shell_line_get_selected(line));
printf(
ANSI_CURSOR_HOR_POS("%zu"), cli_shell_line_prompt_length(line) + line->line_position + 1);
fflush(stdout);
return true;
}
static bool cli_shell_line_input_bksp(CliKeyCombo combo, void* context) {
UNUSED(combo);
CliShellLine* line = context;
// erase one character
cli_shell_line_ensure_not_overwriting_history(line);
FuriString* editing_line = cli_shell_line_get_editing(line);
if(line->line_position == 0) {
putc(CliKeyBell, stdout);
fflush(stdout);
return true;
}
line->line_position--;
furi_string_replace_at(editing_line, line->line_position, 1, "");
// move cursor, print the rest of the line, restore cursor
printf(
ANSI_CURSOR_LEFT_BY("1") "%s" ANSI_ERASE_LINE(ANSI_ERASE_FROM_CURSOR_TO_END),
furi_string_get_cstr(editing_line) + line->line_position);
size_t left_by = furi_string_size(editing_line) - line->line_position;
if(left_by) // apparently LEFT_BY("0") still shifts left by one ._ .
printf(ANSI_CURSOR_LEFT_BY("%zu"), left_by);
fflush(stdout);
return true;
}
static bool cli_shell_line_input_fallback(CliKeyCombo combo, void* context) {
CliShellLine* line = context;
if(combo.modifiers != CliModKeyNo) return false;
if(combo.key < CliKeySpace || combo.key >= CliKeyDEL) return false;
// insert character
cli_shell_line_ensure_not_overwriting_history(line);
FuriString* editing_line = cli_shell_line_get_editing(line);
if(line->line_position == furi_string_size(editing_line)) {
furi_string_push_back(editing_line, combo.key);
printf("%c", combo.key);
} else {
const char in_str[2] = {combo.key, 0};
furi_string_replace_at(editing_line, line->line_position, 0, in_str);
printf(ANSI_INSERT_MODE_ENABLE "%c" ANSI_INSERT_MODE_DISABLE, combo.key);
}
fflush(stdout);
line->line_position++;
return true;
}
CliShellKeyComboSet cli_shell_line_key_combo_set = {
.fallback = cli_shell_line_input_fallback,
.count = 10,
.records =
{
{{CliModKeyNo, CliKeyETX}, cli_shell_line_input_ctrl_c},
{{CliModKeyNo, CliKeyCR}, cli_shell_line_input_cr},
{{CliModKeyNo, CliKeyUp}, cli_shell_line_input_up_down},
{{CliModKeyNo, CliKeyDown}, cli_shell_line_input_up_down},
{{CliModKeyNo, CliKeyLeft}, cli_shell_line_input_left_right},
{{CliModKeyNo, CliKeyRight}, cli_shell_line_input_left_right},
{{CliModKeyNo, CliKeyHome}, cli_shell_line_input_home},
{{CliModKeyNo, CliKeyEnd}, cli_shell_line_input_end},
{{CliModKeyNo, CliKeyBackspace}, cli_shell_line_input_bksp},
{{CliModKeyNo, CliKeyDEL}, cli_shell_line_input_bksp},
},
};

View File

@@ -0,0 +1,36 @@
#pragma once
#include <furi.h>
#include "cli_shell_i.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct CliShellLine CliShellLine;
CliShellLine* cli_shell_line_alloc(CliShell* shell);
void cli_shell_line_free(CliShellLine* line);
FuriString* cli_shell_line_get_selected(CliShellLine* line);
FuriString* cli_shell_line_get_editing(CliShellLine* line);
size_t cli_shell_line_prompt_length(CliShellLine* line);
void cli_shell_line_format_prompt(CliShellLine* line, char* buf, size_t length);
void cli_shell_line_prompt(CliShellLine* line);
/**
* @brief If a line from history has been selected, moves it into the active line
*/
void cli_shell_line_ensure_not_overwriting_history(CliShellLine* line);
extern CliShellKeyComboSet cli_shell_line_key_combo_set;
#ifdef __cplusplus
}
#endif

View File

@@ -2,6 +2,7 @@
#include <furi.h> #include <furi.h>
#include <lib/toolbox/args.h> #include <lib/toolbox/args.h>
#include <toolbox/pipe.h>
#include <cli/cli.h> #include <cli/cli.h>
void crypto_cli_print_usage(void) { void crypto_cli_print_usage(void) {
@@ -17,7 +18,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-REVERSABLE 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-REVERSABLE 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 +45,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 +93,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 +120,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 +165,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 +187,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 +280,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 +292,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;
} }

View File

@@ -396,8 +396,8 @@ 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);
} }
@@ -426,8 +426,8 @@ 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);
} }
@@ -525,6 +525,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);
} }
if(storage_file_exists(desktop->storage, SLIDESHOW_FS_PATH)) { if(storage_file_exists(desktop->storage, SLIDESHOW_FS_PATH)) {

View File

@@ -6,6 +6,7 @@
#include <furi.h> #include <furi.h>
#include <cli/cli.h> #include <cli/cli.h>
#include <furi_hal_gpio.h> #include <furi_hal_gpio.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
@@ -25,7 +26,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

View File

@@ -3,6 +3,7 @@
#include <furi.h> #include <furi.h>
#include <cli/cli.h> #include <cli/cli.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

@@ -6,6 +6,7 @@
#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 +111,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);

View File

@@ -4,9 +4,10 @@
#include <cli/cli.h> #include <cli/cli.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 +15,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 +43,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 +68,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 +80,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;
} }
} }

View File

@@ -2,24 +2,24 @@
#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 +36,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 +53,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 +64,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

@@ -6,6 +6,7 @@
#include <pb_encode.h> #include <pb_encode.h>
#include <flipper.pb.h> #include <flipper.pb.h>
#include <cli/cli.h> #include <cli/cli.h>
#include <toolbox/pipe.h>
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
@@ -46,7 +47,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

@@ -10,6 +10,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 +20,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 +70,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 +98,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 +136,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 +178,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 +210,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 +225,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 +266,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 +284,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 +306,32 @@ 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;
uint8_t* buffer = malloc(buffer_size);
if(buffer_size) { while(need_to_read) {
uint8_t* buffer = malloc(buffer_size); size_t read_this_time = pipe_receive(pipe, buffer, MIN(buffer_size, need_to_read));
size_t wrote_this_time = storage_file_write(file, buffer, read_this_time);
size_t read_bytes = cli_read(cli, buffer, buffer_size); if(wrote_this_time != read_this_time) {
size_t written_size = storage_file_write(file, buffer, read_bytes);
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 +342,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 +384,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 +401,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 +422,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 +435,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 +456,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 +469,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 +496,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 +531,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 +636,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 +658,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 +672,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");
@@ -688,7 +694,7 @@ 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); Cli* cli = furi_record_open(RECORD_CLI);
cli_add_command(cli, RECORD_STORAGE, CliCommandFlagParallelSafe, storage_cli, NULL); cli_add_command_ex(cli, "storage", CliCommandFlagParallelSafe, 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);

View File

@@ -5,6 +5,7 @@
#include <toolbox/path.h> #include <toolbox/path.h>
#include <assets_icons.h> #include <assets_icons.h>
#include <cli/cli.h> #include <cli/cli.h>
#include <toolbox/pipe.h>
#define TAG "JS app" #define TAG "JS app"
@@ -131,12 +132,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 +173,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 +190,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);

View File

@@ -7,6 +7,7 @@
#include <toolbox/path.h> #include <toolbox/path.h>
#include <toolbox/tar/tar_archive.h> #include <toolbox/tar/tar_archive.h>
#include <toolbox/args.h> #include <toolbox/args.h>
#include <toolbox/pipe.h>
#include <update_util/update_manifest.h> #include <update_util/update_manifest.h>
#include <update_util/int_backup.h> #include <update_util/int_backup.h>
#include <update_util/update_operation.h> #include <update_util/update_operation.h>
@@ -63,8 +64,8 @@ static const CliSubcommand update_cli_subcommands[] = {
{.command = "help", .handler = updater_cli_help}, {.command = "help", .handler = updater_cli_help},
}; };
static void updater_cli_ep(Cli* cli, FuriString* args, void* context) { static void updater_cli_ep(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(cli); UNUSED(pipe);
UNUSED(context); UNUSED(context);
FuriString* subcommand; FuriString* subcommand;
subcommand = furi_string_alloc(); subcommand = furi_string_alloc();

View File

@@ -41,6 +41,16 @@ extern "C" {
#define CLAMP(x, upper, lower) (MIN(upper, MAX(x, lower))) #define CLAMP(x, upper, lower) (MIN(upper, MAX(x, lower)))
#endif #endif
#ifndef CLAMP_WRAPAROUND
#define CLAMP_WRAPAROUND(x, upper, lower) \
({ \
__typeof__(x) _x = (x); \
__typeof__(upper) _upper = (upper); \
__typeof__(lower) _lower = (lower); \
(_x > _upper) ? _lower : ((_x < _lower) ? _upper : _x); \
})
#endif
#ifndef COUNT_OF #ifndef COUNT_OF
#define COUNT_OF(x) (sizeof(x) / sizeof(x[0])) #define COUNT_OF(x) (sizeof(x) / sizeof(x[0]))
#endif #endif

View File

@@ -78,6 +78,7 @@ void furi_event_loop_free(FuriEventLoop* instance) {
furi_event_loop_process_timer_queue(instance); furi_event_loop_process_timer_queue(instance);
furi_check(TimerList_empty_p(instance->timer_list)); furi_check(TimerList_empty_p(instance->timer_list));
furi_check(WaitingList_empty_p(instance->waiting_list)); furi_check(WaitingList_empty_p(instance->waiting_list));
furi_check(!instance->are_thread_flags_subscribed);
FuriEventLoopTree_clear(instance->tree); FuriEventLoopTree_clear(instance->tree);
PendingQueue_clear(instance->pending_queue); PendingQueue_clear(instance->pending_queue);
@@ -243,6 +244,10 @@ void furi_event_loop_run(FuriEventLoop* instance) {
} else if(flags & FuriEventLoopFlagPending) { } else if(flags & FuriEventLoopFlagPending) {
furi_event_loop_process_pending_callbacks(instance); furi_event_loop_process_pending_callbacks(instance);
} else if(flags & FuriEventLoopFlagThreadFlag) {
if(instance->are_thread_flags_subscribed)
instance->thread_flags_callback(instance->thread_flags_callback_context);
} else { } else {
furi_crash(); furi_crash();
} }
@@ -416,6 +421,24 @@ void furi_event_loop_subscribe_mutex(
instance, mutex, &furi_mutex_event_loop_contract, event, callback, context); instance, mutex, &furi_mutex_event_loop_contract, event, callback, context);
} }
void furi_event_loop_subscribe_thread_flags(
FuriEventLoop* instance,
FuriEventLoopThreadFlagsCallback callback,
void* context) {
furi_check(instance);
furi_check(callback);
furi_check(!instance->are_thread_flags_subscribed);
instance->are_thread_flags_subscribed = true;
instance->thread_flags_callback = callback;
instance->thread_flags_callback_context = context;
}
void furi_event_loop_unsubscribe_thread_flags(FuriEventLoop* instance) {
furi_check(instance);
furi_check(instance->are_thread_flags_subscribed);
instance->are_thread_flags_subscribed = false;
}
/** /**
* Public generic unsubscription API * Public generic unsubscription API
*/ */
@@ -538,6 +561,25 @@ static bool furi_event_loop_item_is_waiting(FuriEventLoopItem* instance) {
return instance->WaitingList.prev || instance->WaitingList.next; return instance->WaitingList.prev || instance->WaitingList.next;
} }
void furi_event_loop_thread_flag_callback(FuriThreadId thread_id) {
TaskHandle_t hTask = (TaskHandle_t)thread_id;
BaseType_t yield;
if(FURI_IS_IRQ_MODE()) {
yield = pdFALSE;
(void)xTaskNotifyIndexedFromISR(
hTask,
FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX,
FuriEventLoopFlagThreadFlag,
eSetBits,
&yield);
portYIELD_FROM_ISR(yield);
} else {
(void)xTaskNotifyIndexed(
hTask, FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX, FuriEventLoopFlagThreadFlag, eSetBits);
}
}
/* /*
* Internal event loop link API, used by supported primitives * Internal event loop link API, used by supported primitives
*/ */

View File

@@ -203,6 +203,12 @@ typedef void FuriEventLoopObject;
*/ */
typedef void (*FuriEventLoopEventCallback)(FuriEventLoopObject* object, void* context); typedef void (*FuriEventLoopEventCallback)(FuriEventLoopObject* object, void* context);
/** Callback type for event loop thread flag events
*
* @param context The context that was provided upon subscription
*/
typedef void (*FuriEventLoopThreadFlagsCallback)(void* context);
/** Opaque event flag type */ /** Opaque event flag type */
typedef struct FuriEventFlag FuriEventFlag; typedef struct FuriEventFlag FuriEventFlag;
@@ -304,6 +310,23 @@ void furi_event_loop_subscribe_mutex(
FuriEventLoopEventCallback callback, FuriEventLoopEventCallback callback,
void* context); void* context);
/** Subscribe to thread flag events of the current thread
*
* @param instance The Event Loop instance
* @param callback The callback to call when a flag has been set
* @param context The context for callback
*/
void furi_event_loop_subscribe_thread_flags(
FuriEventLoop* instance,
FuriEventLoopThreadFlagsCallback callback,
void* context);
/** Unsubscribe from thread flag events of the current thread
*
* @param instance The Event Loop instance
*/
void furi_event_loop_unsubscribe_thread_flags(FuriEventLoop* instance);
/** Unsubscribe from events (common) /** Unsubscribe from events (common)
* *
* @param instance The Event Loop instance * @param instance The Event Loop instance

View File

@@ -4,12 +4,14 @@
#include "event_loop_link_i.h" #include "event_loop_link_i.h"
#include "event_loop_timer_i.h" #include "event_loop_timer_i.h"
#include "event_loop_tick_i.h" #include "event_loop_tick_i.h"
#include "event_loop_thread_flag_interface.h"
#include <m-list.h> #include <m-list.h>
#include <m-bptree.h> #include <m-bptree.h>
#include <m-i-list.h> #include <m-i-list.h>
#include "thread.h" #include "thread.h"
#include "thread_i.h"
struct FuriEventLoopItem { struct FuriEventLoopItem {
// Source // Source
@@ -50,11 +52,12 @@ typedef enum {
FuriEventLoopFlagStop = (1 << 1), FuriEventLoopFlagStop = (1 << 1),
FuriEventLoopFlagTimer = (1 << 2), FuriEventLoopFlagTimer = (1 << 2),
FuriEventLoopFlagPending = (1 << 3), FuriEventLoopFlagPending = (1 << 3),
FuriEventLoopFlagThreadFlag = (1 << 4),
} FuriEventLoopFlag; } FuriEventLoopFlag;
#define FuriEventLoopFlagAll \ #define FuriEventLoopFlagAll \
(FuriEventLoopFlagEvent | FuriEventLoopFlagStop | FuriEventLoopFlagTimer | \ (FuriEventLoopFlagEvent | FuriEventLoopFlagStop | FuriEventLoopFlagTimer | \
FuriEventLoopFlagPending) FuriEventLoopFlagPending | FuriEventLoopFlagThreadFlag)
typedef enum { typedef enum {
FuriEventLoopProcessStatusComplete, FuriEventLoopProcessStatusComplete,
@@ -94,4 +97,9 @@ struct FuriEventLoop {
PendingQueue_t pending_queue; PendingQueue_t pending_queue;
// Tick event // Tick event
FuriEventLoopTick tick; FuriEventLoopTick tick;
// Thread flags callback
bool are_thread_flags_subscribed;
FuriEventLoopThreadFlagsCallback thread_flags_callback;
void* thread_flags_callback_context;
}; };

View File

@@ -0,0 +1,10 @@
#pragma once
#include "thread.h"
/**
* @brief Notify `FuriEventLoop` that `furi_thread_flags_set` has been called
*
* @param thread_id Thread id
*/
extern void furi_event_loop_thread_flag_callback(FuriThreadId thread_id);

View File

@@ -7,6 +7,7 @@
#include "check.h" #include "check.h"
#include "common_defines.h" #include "common_defines.h"
#include "string.h" #include "string.h"
#include "event_loop_thread_flag_interface.h"
#include "log.h" #include "log.h"
#include <furi_hal_rtc.h> #include <furi_hal_rtc.h>
@@ -501,6 +502,9 @@ uint32_t furi_thread_flags_set(FuriThreadId thread_id, uint32_t flags) {
(void)xTaskNotifyAndQueryIndexed(hTask, THREAD_NOTIFY_INDEX, 0, eNoAction, &rflags); (void)xTaskNotifyAndQueryIndexed(hTask, THREAD_NOTIFY_INDEX, 0, eNoAction, &rflags);
} }
} }
furi_event_loop_thread_flag_callback(thread_id);
/* Return flags after setting */ /* Return flags after setting */
return rflags; return rflags;
} }

View File

@@ -1,6 +1,8 @@
#include "pipe.h" #include "pipe.h"
#include <furi.h> #include <furi.h>
#define PIPE_DEFAULT_STATE_CHECK_PERIOD furi_ms_to_ticks(100)
/** /**
* Data shared between both sides. * Data shared between both sides.
*/ */
@@ -23,7 +25,7 @@ struct PipeSide {
PipeSideDataArrivedCallback on_data_arrived; PipeSideDataArrivedCallback on_data_arrived;
PipeSideSpaceFreedCallback on_space_freed; PipeSideSpaceFreedCallback on_space_freed;
PipeSideBrokenCallback on_pipe_broken; PipeSideBrokenCallback on_pipe_broken;
FuriWait stdout_timeout; FuriWait state_check_period;
}; };
PipeSideBundle pipe_alloc(size_t capacity, size_t trigger_level) { PipeSideBundle pipe_alloc(size_t capacity, size_t trigger_level) {
@@ -53,14 +55,14 @@ PipeSideBundle pipe_alloc_ex(PipeSideReceiveSettings alice, PipeSideReceiveSetti
.shared = shared, .shared = shared,
.sending = alice_to_bob, .sending = alice_to_bob,
.receiving = bob_to_alice, .receiving = bob_to_alice,
.stdout_timeout = FuriWaitForever, .state_check_period = PIPE_DEFAULT_STATE_CHECK_PERIOD,
}; };
*bobs_side = (PipeSide){ *bobs_side = (PipeSide){
.role = PipeRoleBob, .role = PipeRoleBob,
.shared = shared, .shared = shared,
.sending = bob_to_alice, .sending = bob_to_alice,
.receiving = alice_to_bob, .receiving = alice_to_bob,
.stdout_timeout = FuriWaitForever, .state_check_period = PIPE_DEFAULT_STATE_CHECK_PERIOD,
}; };
return (PipeSideBundle){.alices_side = alices_side, .bobs_side = bobs_side}; return (PipeSideBundle){.alices_side = alices_side, .bobs_side = bobs_side};
@@ -99,42 +101,62 @@ void pipe_free(PipeSide* pipe) {
} }
} }
static void _pipe_stdout_cb(const char* data, size_t size, void* context) { static void pipe_stdout_cb(const char* data, size_t size, void* context) {
furi_assert(context); furi_assert(context);
PipeSide* pipe = context; PipeSide* pipe = context;
while(size) { pipe_send(pipe, data, size);
size_t sent = pipe_send(pipe, data, size, pipe->stdout_timeout);
if(!sent) break;
data += sent;
size -= sent;
}
} }
static size_t _pipe_stdin_cb(char* data, size_t size, FuriWait timeout, void* context) { static size_t pipe_stdin_cb(char* data, size_t size, FuriWait timeout, void* context) {
UNUSED(timeout);
furi_assert(context); furi_assert(context);
PipeSide* pipe = context; PipeSide* pipe = context;
return pipe_receive(pipe, data, size, timeout); return pipe_receive(pipe, data, size);
} }
void pipe_install_as_stdio(PipeSide* pipe) { void pipe_install_as_stdio(PipeSide* pipe) {
furi_check(pipe); furi_check(pipe);
furi_thread_set_stdout_callback(_pipe_stdout_cb, pipe); furi_thread_set_stdout_callback(pipe_stdout_cb, pipe);
furi_thread_set_stdin_callback(_pipe_stdin_cb, pipe); furi_thread_set_stdin_callback(pipe_stdin_cb, pipe);
} }
void pipe_set_stdout_timeout(PipeSide* pipe, FuriWait timeout) { void pipe_set_state_check_period(PipeSide* pipe, FuriWait check_period) {
furi_check(pipe); furi_check(pipe);
pipe->stdout_timeout = timeout; pipe->state_check_period = check_period;
} }
size_t pipe_receive(PipeSide* pipe, void* data, size_t length, FuriWait timeout) { size_t pipe_receive(PipeSide* pipe, void* data, size_t length) {
furi_check(pipe); furi_check(pipe);
return furi_stream_buffer_receive(pipe->receiving, data, length, timeout);
size_t received = 0;
while(length) {
size_t received_this_time =
furi_stream_buffer_receive(pipe->receiving, data, length, pipe->state_check_period);
if(!received_this_time && pipe_state(pipe) == PipeStateBroken) break;
received += received_this_time;
length -= received_this_time;
data += received_this_time;
}
return received;
} }
size_t pipe_send(PipeSide* pipe, const void* data, size_t length, FuriWait timeout) { size_t pipe_send(PipeSide* pipe, const void* data, size_t length) {
furi_check(pipe); furi_check(pipe);
return furi_stream_buffer_send(pipe->sending, data, length, timeout);
size_t sent = 0;
while(length) {
size_t sent_this_time =
furi_stream_buffer_send(pipe->sending, data, length, pipe->state_check_period);
if(!sent_this_time && pipe_state(pipe) == PipeStateBroken) break;
sent += sent_this_time;
length -= sent_this_time;
data += sent_this_time;
}
return sent;
} }
size_t pipe_bytes_available(PipeSide* pipe) { size_t pipe_bytes_available(PipeSide* pipe) {
@@ -151,14 +173,14 @@ static void pipe_receiving_buffer_callback(FuriEventLoopObject* buffer, void* co
UNUSED(buffer); UNUSED(buffer);
PipeSide* pipe = context; PipeSide* pipe = context;
furi_assert(pipe); furi_assert(pipe);
if(pipe->on_space_freed) pipe->on_data_arrived(pipe, pipe->callback_context); if(pipe->on_data_arrived) pipe->on_data_arrived(pipe, pipe->callback_context);
} }
static void pipe_sending_buffer_callback(FuriEventLoopObject* buffer, void* context) { static void pipe_sending_buffer_callback(FuriEventLoopObject* buffer, void* context) {
UNUSED(buffer); UNUSED(buffer);
PipeSide* pipe = context; PipeSide* pipe = context;
furi_assert(pipe); furi_assert(pipe);
if(pipe->on_data_arrived) pipe->on_space_freed(pipe, pipe->callback_context); if(pipe->on_space_freed) pipe->on_space_freed(pipe, pipe->callback_context);
} }
static void pipe_semaphore_callback(FuriEventLoopObject* semaphore, void* context) { static void pipe_semaphore_callback(FuriEventLoopObject* semaphore, void* context) {

View File

@@ -148,38 +148,48 @@ void pipe_free(PipeSide* pipe);
void pipe_install_as_stdio(PipeSide* pipe); void pipe_install_as_stdio(PipeSide* pipe);
/** /**
* @brief Sets the timeout for `stdout` write operations * @brief Sets the state check period for `send` and `receive` operations
* *
* @note This value is set to `FuriWaitForever` when the pipe is created * @note This value is set to 100 ms when the pipe is created
* *
* @param [in] pipe Pipe side to set the timeout of * `send` and `receive` will check the state of the pipe if exactly 0 bytes were
* @param [in] timeout Timeout value in ticks * sent or received during any given `check_period`. Read the documentation for
* `pipe_send` and `pipe_receive` for more info.
*
* @param [in] pipe Pipe side to set the check period of
* @param [in] check_period Period in ticks
*/ */
void pipe_set_stdout_timeout(PipeSide* pipe, FuriWait timeout); void pipe_set_state_check_period(PipeSide* pipe, FuriWait check_period);
/** /**
* @brief Receives data from the pipe. * @brief Receives data from the pipe.
* *
* This function will try to receive all of the requested bytes from the pipe.
* If at some point during the operation the pipe becomes broken, this function
* will return prematurely, in which case the return value will be less than the
* requested `length`.
*
* @param [in] pipe The pipe side to read data out of * @param [in] pipe The pipe side to read data out of
* @param [out] data The buffer to fill with data * @param [out] data The buffer to fill with data
* @param length Maximum length of data to read * @param length Maximum length of data to read
* @param timeout The timeout (in ticks) after which the read operation is
* interrupted
* @returns The number of bytes actually written into the provided buffer * @returns The number of bytes actually written into the provided buffer
*/ */
size_t pipe_receive(PipeSide* pipe, void* data, size_t length, FuriWait timeout); size_t pipe_receive(PipeSide* pipe, void* data, size_t length);
/** /**
* @brief Sends data into the pipe. * @brief Sends data into the pipe.
* *
* This function will try to send all of the requested bytes to the pipe.
* If at some point during the operation the pipe becomes broken, this function
* will return prematurely, in which case the return value will be less than the
* requested `length`.
*
* @param [in] pipe The pipe side to send data into * @param [in] pipe The pipe side to send data into
* @param [out] data The buffer to get data from * @param [out] data The buffer to get data from
* @param length Maximum length of data to send * @param length Maximum length of data to send
* @param timeout The timeout (in ticks) after which the write operation is
* interrupted
* @returns The number of bytes actually read from the provided buffer * @returns The number of bytes actually read from the provided buffer
*/ */
size_t pipe_send(PipeSide* pipe, const void* data, size_t length, FuriWait timeout); size_t pipe_send(PipeSide* pipe, const void* data, size_t length);
/** /**
* @brief Determines how many bytes there are in the pipe available to be read. * @brief Determines how many bytes there are in the pipe available to be read.

View File

@@ -0,0 +1,59 @@
import argparse
import logging
from serial import Serial
from random import randint
from time import time
from flipper.utils.cdc import resolve_port
def main():
logger = logging.getLogger()
parser = argparse.ArgumentParser()
parser.add_argument("-p", "--port", help="CDC Port", default="auto")
parser.add_argument(
"-l", "--length", type=int, help="Number of bytes to send", default=1024**2
)
args = parser.parse_args()
if not (port := resolve_port(logger, args.port)):
logger.error("Is Flipper connected via USB and not in DFU mode?")
return 1
port = Serial(port, 230400)
port.timeout = 2
port.read_until(b">: ")
port.write(b"echo\r")
port.read_until(b">: ")
print(f"Transferring {args.length} bytes. Hang tight...")
start_time = time()
bytes_to_send = args.length
block_size = 1024
while bytes_to_send:
actual_size = min(block_size, bytes_to_send)
# can't use 0x03 because that's ASCII ETX, or Ctrl+C
block = bytes([randint(4, 255) for _ in range(actual_size)])
port.write(block)
return_block = port.read(actual_size)
if return_block != block:
logger.error("Incorrect block received")
break
bytes_to_send -= actual_size
end_time = time()
delta = end_time - start_time
speed = args.length / delta
print(f"Speed: {speed/1024:.2f} KiB/s")
port.write(b"\x03") # Ctrl+C
port.close()
if __name__ == "__main__":
main()

View File

@@ -34,7 +34,7 @@ class Main(App):
FLASH_BASE = 0x8000000 FLASH_BASE = 0x8000000
FLASH_PAGE_SIZE = 4 * 1024 FLASH_PAGE_SIZE = 4 * 1024
MIN_GAP_PAGES = 1 MIN_GAP_PAGES = 0
# Update stage file larger than that is not loadable without fix # Update stage file larger than that is not loadable without fix
# https://github.com/flipperdevices/flipperzero-firmware/pull/3676 # https://github.com/flipperdevices/flipperzero-firmware/pull/3676

View File

@@ -1,8 +1,9 @@
entry,status,name,type,params entry,status,name,type,params
Version,+,83.0,, Version,+,84.0,,
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,,
Header,+,applications/services/cli/cli_ansi.h,,
Header,+,applications/services/cli/cli_vcp.h,, Header,+,applications/services/cli/cli_vcp.h,,
Header,+,applications/services/dialogs/dialogs.h,, Header,+,applications/services/dialogs/dialogs.h,,
Header,+,applications/services/dolphin/dolphin.h,, Header,+,applications/services/dolphin/dolphin.h,,
@@ -778,18 +779,17 @@ Function,-,ceill,long double,long double
Function,-,cfree,void,void* Function,-,cfree,void,void*
Function,-,clearerr,void,FILE* Function,-,clearerr,void,FILE*
Function,-,clearerr_unlocked,void,FILE* Function,-,clearerr_unlocked,void,FILE*
Function,+,cli_add_command,void,"Cli*, const char*, CliCommandFlag, CliCallback, void*" Function,+,cli_add_command,void,"Cli*, const char*, CliCommandFlag, CliExecuteCallback, void*"
Function,+,cli_cmd_interrupt_received,_Bool,Cli* Function,+,cli_add_command_ex,void,"Cli*, const char*, CliCommandFlag, CliExecuteCallback, void*, size_t"
Function,+,cli_ansi_parser_alloc,CliAnsiParser*,
Function,+,cli_ansi_parser_feed,CliAnsiParserResult,"CliAnsiParser*, char"
Function,+,cli_ansi_parser_feed_timeout,CliAnsiParserResult,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_getc,char,Cli* Function,+,cli_is_pipe_broken_or_is_etx_next_char,_Bool,PipeSide*
Function,+,cli_is_connected,_Bool,Cli*
Function,+,cli_nl,void,Cli*
Function,+,cli_print_usage,void,"const char*, const char*, const char*" Function,+,cli_print_usage,void,"const char*, const char*, const char*"
Function,+,cli_read,size_t,"Cli*, uint8_t*, size_t" Function,+,cli_vcp_disable,void,CliVcp*
Function,+,cli_read_timeout,size_t,"Cli*, uint8_t*, size_t, uint32_t" Function,+,cli_vcp_enable,void,CliVcp*
Function,+,cli_session_close,void,Cli*
Function,+,cli_session_open,void,"Cli*, const void*"
Function,+,cli_write,void,"Cli*, const uint8_t*, size_t"
Function,+,composite_api_resolver_add,void,"CompositeApiResolver*, const ElfApiInterface*" Function,+,composite_api_resolver_add,void,"CompositeApiResolver*, const ElfApiInterface*"
Function,+,composite_api_resolver_alloc,CompositeApiResolver*, Function,+,composite_api_resolver_alloc,CompositeApiResolver*,
Function,+,composite_api_resolver_free,void,CompositeApiResolver* Function,+,composite_api_resolver_free,void,CompositeApiResolver*
@@ -1138,6 +1138,7 @@ Function,+,furi_event_loop_subscribe_message_queue,void,"FuriEventLoop*, FuriMes
Function,+,furi_event_loop_subscribe_mutex,void,"FuriEventLoop*, FuriMutex*, FuriEventLoopEvent, FuriEventLoopEventCallback, void*" Function,+,furi_event_loop_subscribe_mutex,void,"FuriEventLoop*, FuriMutex*, FuriEventLoopEvent, FuriEventLoopEventCallback, void*"
Function,+,furi_event_loop_subscribe_semaphore,void,"FuriEventLoop*, FuriSemaphore*, FuriEventLoopEvent, FuriEventLoopEventCallback, void*" Function,+,furi_event_loop_subscribe_semaphore,void,"FuriEventLoop*, FuriSemaphore*, FuriEventLoopEvent, FuriEventLoopEventCallback, void*"
Function,+,furi_event_loop_subscribe_stream_buffer,void,"FuriEventLoop*, FuriStreamBuffer*, FuriEventLoopEvent, FuriEventLoopEventCallback, void*" Function,+,furi_event_loop_subscribe_stream_buffer,void,"FuriEventLoop*, FuriStreamBuffer*, FuriEventLoopEvent, FuriEventLoopEventCallback, void*"
Function,+,furi_event_loop_subscribe_thread_flags,void,"FuriEventLoop*, FuriEventLoopThreadFlagsCallback, void*"
Function,+,furi_event_loop_tick_set,void,"FuriEventLoop*, uint32_t, FuriEventLoopTickCallback, void*" Function,+,furi_event_loop_tick_set,void,"FuriEventLoop*, uint32_t, FuriEventLoopTickCallback, void*"
Function,+,furi_event_loop_timer_alloc,FuriEventLoopTimer*,"FuriEventLoop*, FuriEventLoopTimerCallback, FuriEventLoopTimerType, void*" Function,+,furi_event_loop_timer_alloc,FuriEventLoopTimer*,"FuriEventLoop*, FuriEventLoopTimerCallback, FuriEventLoopTimerType, void*"
Function,+,furi_event_loop_timer_free,void,FuriEventLoopTimer* Function,+,furi_event_loop_timer_free,void,FuriEventLoopTimer*
@@ -1148,6 +1149,7 @@ Function,+,furi_event_loop_timer_restart,void,FuriEventLoopTimer*
Function,+,furi_event_loop_timer_start,void,"FuriEventLoopTimer*, uint32_t" Function,+,furi_event_loop_timer_start,void,"FuriEventLoopTimer*, uint32_t"
Function,+,furi_event_loop_timer_stop,void,FuriEventLoopTimer* Function,+,furi_event_loop_timer_stop,void,FuriEventLoopTimer*
Function,+,furi_event_loop_unsubscribe,void,"FuriEventLoop*, FuriEventLoopObject*" Function,+,furi_event_loop_unsubscribe,void,"FuriEventLoop*, FuriEventLoopObject*"
Function,+,furi_event_loop_unsubscribe_thread_flags,void,FuriEventLoop*
Function,+,furi_get_tick,uint32_t, Function,+,furi_get_tick,uint32_t,
Function,+,furi_hal_adc_acquire,FuriHalAdcHandle*, Function,+,furi_hal_adc_acquire,FuriHalAdcHandle*,
Function,+,furi_hal_adc_configure,void,FuriHalAdcHandle* Function,+,furi_hal_adc_configure,void,FuriHalAdcHandle*
@@ -2329,14 +2331,14 @@ Function,+,pipe_bytes_available,size_t,PipeSide*
Function,+,pipe_detach_from_event_loop,void,PipeSide* Function,+,pipe_detach_from_event_loop,void,PipeSide*
Function,+,pipe_free,void,PipeSide* Function,+,pipe_free,void,PipeSide*
Function,+,pipe_install_as_stdio,void,PipeSide* Function,+,pipe_install_as_stdio,void,PipeSide*
Function,+,pipe_receive,size_t,"PipeSide*, void*, size_t, FuriWait" Function,+,pipe_receive,size_t,"PipeSide*, void*, size_t"
Function,+,pipe_role,PipeRole,PipeSide* Function,+,pipe_role,PipeRole,PipeSide*
Function,+,pipe_send,size_t,"PipeSide*, const void*, size_t, FuriWait" Function,+,pipe_send,size_t,"PipeSide*, const void*, size_t"
Function,+,pipe_set_broken_callback,void,"PipeSide*, PipeSideBrokenCallback, FuriEventLoopEvent" Function,+,pipe_set_broken_callback,void,"PipeSide*, PipeSideBrokenCallback, FuriEventLoopEvent"
Function,+,pipe_set_callback_context,void,"PipeSide*, void*" Function,+,pipe_set_callback_context,void,"PipeSide*, void*"
Function,+,pipe_set_data_arrived_callback,void,"PipeSide*, PipeSideDataArrivedCallback, FuriEventLoopEvent" Function,+,pipe_set_data_arrived_callback,void,"PipeSide*, PipeSideDataArrivedCallback, FuriEventLoopEvent"
Function,+,pipe_set_space_freed_callback,void,"PipeSide*, PipeSideSpaceFreedCallback, FuriEventLoopEvent" Function,+,pipe_set_space_freed_callback,void,"PipeSide*, PipeSideSpaceFreedCallback, FuriEventLoopEvent"
Function,+,pipe_set_stdout_timeout,void,"PipeSide*, FuriWait" Function,+,pipe_set_state_check_period,void,"PipeSide*, FuriWait"
Function,+,pipe_spaces_available,size_t,PipeSide* Function,+,pipe_spaces_available,size_t,PipeSide*
Function,+,pipe_state,PipeState,PipeSide* Function,+,pipe_state,PipeState,PipeSide*
Function,+,plugin_manager_alloc,PluginManager*,"const char*, uint32_t, const ElfApiInterface*" Function,+,plugin_manager_alloc,PluginManager*,"const char*, uint32_t, const ElfApiInterface*"
@@ -2939,7 +2941,6 @@ Variable,-,_sys_errlist,const char* const[],
Variable,-,_sys_nerr,int, Variable,-,_sys_nerr,int,
Variable,-,ble_profile_hid,const FuriHalBleProfileTemplate*, Variable,-,ble_profile_hid,const FuriHalBleProfileTemplate*,
Variable,+,ble_profile_serial,const FuriHalBleProfileTemplate* const, Variable,+,ble_profile_serial,const FuriHalBleProfileTemplate* const,
Variable,+,cli_vcp,const CliSession,
Variable,+,compress_config_heatshrink_default,const CompressConfigHeatshrink, Variable,+,compress_config_heatshrink_default,const CompressConfigHeatshrink,
Variable,+,firmware_api_interface,const ElfApiInterface* const, Variable,+,firmware_api_interface,const ElfApiInterface* const,
Variable,+,furi_hal_i2c_bus_external,FuriHalI2cBus, Variable,+,furi_hal_i2c_bus_external,FuriHalI2cBus,
1 entry status name type params
2 Version + 83.0 84.0
3 Header + applications/services/bt/bt_service/bt.h
4 Header + applications/services/bt/bt_service/bt_keys_storage.h
5 Header + applications/services/cli/cli.h
6 Header + applications/services/cli/cli_ansi.h
7 Header + applications/services/cli/cli_vcp.h
8 Header + applications/services/dialogs/dialogs.h
9 Header + applications/services/dolphin/dolphin.h
779 Function - cfree void void*
780 Function - clearerr void FILE*
781 Function - clearerr_unlocked void FILE*
782 Function + cli_add_command void Cli*, const char*, CliCommandFlag, CliCallback, void* Cli*, const char*, CliCommandFlag, CliExecuteCallback, void*
783 Function + cli_cmd_interrupt_received cli_add_command_ex _Bool void Cli* Cli*, const char*, CliCommandFlag, CliExecuteCallback, void*, size_t
784 Function + cli_ansi_parser_alloc CliAnsiParser*
785 Function + cli_ansi_parser_feed CliAnsiParserResult CliAnsiParser*, char
786 Function + cli_ansi_parser_feed_timeout CliAnsiParserResult CliAnsiParser*
787 Function + cli_ansi_parser_free void CliAnsiParser*
788 Function + cli_delete_command void Cli*, const char*
789 Function + cli_getc cli_is_pipe_broken_or_is_etx_next_char char _Bool Cli* PipeSide*
Function + cli_is_connected _Bool Cli*
Function + cli_nl void Cli*
790 Function + cli_print_usage void const char*, const char*, const char*
791 Function + cli_read cli_vcp_disable size_t void Cli*, uint8_t*, size_t CliVcp*
792 Function + cli_read_timeout cli_vcp_enable size_t void Cli*, uint8_t*, size_t, uint32_t CliVcp*
Function + cli_session_close void Cli*
Function + cli_session_open void Cli*, const void*
Function + cli_write void Cli*, const uint8_t*, size_t
793 Function + composite_api_resolver_add void CompositeApiResolver*, const ElfApiInterface*
794 Function + composite_api_resolver_alloc CompositeApiResolver*
795 Function + composite_api_resolver_free void CompositeApiResolver*
1138 Function + furi_event_loop_subscribe_mutex void FuriEventLoop*, FuriMutex*, FuriEventLoopEvent, FuriEventLoopEventCallback, void*
1139 Function + furi_event_loop_subscribe_semaphore void FuriEventLoop*, FuriSemaphore*, FuriEventLoopEvent, FuriEventLoopEventCallback, void*
1140 Function + furi_event_loop_subscribe_stream_buffer void FuriEventLoop*, FuriStreamBuffer*, FuriEventLoopEvent, FuriEventLoopEventCallback, void*
1141 Function + furi_event_loop_subscribe_thread_flags void FuriEventLoop*, FuriEventLoopThreadFlagsCallback, void*
1142 Function + furi_event_loop_tick_set void FuriEventLoop*, uint32_t, FuriEventLoopTickCallback, void*
1143 Function + furi_event_loop_timer_alloc FuriEventLoopTimer* FuriEventLoop*, FuriEventLoopTimerCallback, FuriEventLoopTimerType, void*
1144 Function + furi_event_loop_timer_free void FuriEventLoopTimer*
1149 Function + furi_event_loop_timer_start void FuriEventLoopTimer*, uint32_t
1150 Function + furi_event_loop_timer_stop void FuriEventLoopTimer*
1151 Function + furi_event_loop_unsubscribe void FuriEventLoop*, FuriEventLoopObject*
1152 Function + furi_event_loop_unsubscribe_thread_flags void FuriEventLoop*
1153 Function + furi_get_tick uint32_t
1154 Function + furi_hal_adc_acquire FuriHalAdcHandle*
1155 Function + furi_hal_adc_configure void FuriHalAdcHandle*
2331 Function + pipe_detach_from_event_loop void PipeSide*
2332 Function + pipe_free void PipeSide*
2333 Function + pipe_install_as_stdio void PipeSide*
2334 Function + pipe_receive size_t PipeSide*, void*, size_t, FuriWait PipeSide*, void*, size_t
2335 Function + pipe_role PipeRole PipeSide*
2336 Function + pipe_send size_t PipeSide*, const void*, size_t, FuriWait PipeSide*, const void*, size_t
2337 Function + pipe_set_broken_callback void PipeSide*, PipeSideBrokenCallback, FuriEventLoopEvent
2338 Function + pipe_set_callback_context void PipeSide*, void*
2339 Function + pipe_set_data_arrived_callback void PipeSide*, PipeSideDataArrivedCallback, FuriEventLoopEvent
2340 Function + pipe_set_space_freed_callback void PipeSide*, PipeSideSpaceFreedCallback, FuriEventLoopEvent
2341 Function + pipe_set_stdout_timeout pipe_set_state_check_period void PipeSide*, FuriWait
2342 Function + pipe_spaces_available size_t PipeSide*
2343 Function + pipe_state PipeState PipeSide*
2344 Function + plugin_manager_alloc PluginManager* const char*, uint32_t, const ElfApiInterface*
2941 Variable - _sys_nerr int
2942 Variable - ble_profile_hid const FuriHalBleProfileTemplate*
2943 Variable + ble_profile_serial const FuriHalBleProfileTemplate* const
Variable + cli_vcp const CliSession
2944 Variable + compress_config_heatshrink_default const CompressConfigHeatshrink
2945 Variable + firmware_api_interface const ElfApiInterface* const
2946 Variable + furi_hal_i2c_bus_external FuriHalI2cBus

View File

@@ -1,9 +1,10 @@
entry,status,name,type,params entry,status,name,type,params
Version,+,83.0,, Version,+,84.0,,
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,,
Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli.h,,
Header,+,applications/services/cli/cli_ansi.h,,
Header,+,applications/services/cli/cli_vcp.h,, Header,+,applications/services/cli/cli_vcp.h,,
Header,+,applications/services/dialogs/dialogs.h,, Header,+,applications/services/dialogs/dialogs.h,,
Header,+,applications/services/dolphin/dolphin.h,, Header,+,applications/services/dolphin/dolphin.h,,
@@ -855,18 +856,17 @@ Function,-,ceill,long double,long double
Function,-,cfree,void,void* Function,-,cfree,void,void*
Function,-,clearerr,void,FILE* Function,-,clearerr,void,FILE*
Function,-,clearerr_unlocked,void,FILE* Function,-,clearerr_unlocked,void,FILE*
Function,+,cli_add_command,void,"Cli*, const char*, CliCommandFlag, CliCallback, void*" Function,+,cli_add_command,void,"Cli*, const char*, CliCommandFlag, CliExecuteCallback, void*"
Function,+,cli_cmd_interrupt_received,_Bool,Cli* Function,+,cli_add_command_ex,void,"Cli*, const char*, CliCommandFlag, CliExecuteCallback, void*, size_t"
Function,+,cli_ansi_parser_alloc,CliAnsiParser*,
Function,+,cli_ansi_parser_feed,CliAnsiParserResult,"CliAnsiParser*, char"
Function,+,cli_ansi_parser_feed_timeout,CliAnsiParserResult,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_getc,char,Cli* Function,+,cli_is_pipe_broken_or_is_etx_next_char,_Bool,PipeSide*
Function,+,cli_is_connected,_Bool,Cli*
Function,+,cli_nl,void,Cli*
Function,+,cli_print_usage,void,"const char*, const char*, const char*" Function,+,cli_print_usage,void,"const char*, const char*, const char*"
Function,+,cli_read,size_t,"Cli*, uint8_t*, size_t" Function,+,cli_vcp_disable,void,CliVcp*
Function,+,cli_read_timeout,size_t,"Cli*, uint8_t*, size_t, uint32_t" Function,+,cli_vcp_enable,void,CliVcp*
Function,+,cli_session_close,void,Cli*
Function,+,cli_session_open,void,"Cli*, const void*"
Function,+,cli_write,void,"Cli*, const uint8_t*, size_t"
Function,+,composite_api_resolver_add,void,"CompositeApiResolver*, const ElfApiInterface*" Function,+,composite_api_resolver_add,void,"CompositeApiResolver*, const ElfApiInterface*"
Function,+,composite_api_resolver_alloc,CompositeApiResolver*, Function,+,composite_api_resolver_alloc,CompositeApiResolver*,
Function,+,composite_api_resolver_free,void,CompositeApiResolver* Function,+,composite_api_resolver_free,void,CompositeApiResolver*
@@ -1250,6 +1250,7 @@ Function,+,furi_event_loop_subscribe_message_queue,void,"FuriEventLoop*, FuriMes
Function,+,furi_event_loop_subscribe_mutex,void,"FuriEventLoop*, FuriMutex*, FuriEventLoopEvent, FuriEventLoopEventCallback, void*" Function,+,furi_event_loop_subscribe_mutex,void,"FuriEventLoop*, FuriMutex*, FuriEventLoopEvent, FuriEventLoopEventCallback, void*"
Function,+,furi_event_loop_subscribe_semaphore,void,"FuriEventLoop*, FuriSemaphore*, FuriEventLoopEvent, FuriEventLoopEventCallback, void*" Function,+,furi_event_loop_subscribe_semaphore,void,"FuriEventLoop*, FuriSemaphore*, FuriEventLoopEvent, FuriEventLoopEventCallback, void*"
Function,+,furi_event_loop_subscribe_stream_buffer,void,"FuriEventLoop*, FuriStreamBuffer*, FuriEventLoopEvent, FuriEventLoopEventCallback, void*" Function,+,furi_event_loop_subscribe_stream_buffer,void,"FuriEventLoop*, FuriStreamBuffer*, FuriEventLoopEvent, FuriEventLoopEventCallback, void*"
Function,+,furi_event_loop_subscribe_thread_flags,void,"FuriEventLoop*, FuriEventLoopThreadFlagsCallback, void*"
Function,+,furi_event_loop_tick_set,void,"FuriEventLoop*, uint32_t, FuriEventLoopTickCallback, void*" Function,+,furi_event_loop_tick_set,void,"FuriEventLoop*, uint32_t, FuriEventLoopTickCallback, void*"
Function,+,furi_event_loop_timer_alloc,FuriEventLoopTimer*,"FuriEventLoop*, FuriEventLoopTimerCallback, FuriEventLoopTimerType, void*" Function,+,furi_event_loop_timer_alloc,FuriEventLoopTimer*,"FuriEventLoop*, FuriEventLoopTimerCallback, FuriEventLoopTimerType, void*"
Function,+,furi_event_loop_timer_free,void,FuriEventLoopTimer* Function,+,furi_event_loop_timer_free,void,FuriEventLoopTimer*
@@ -1260,6 +1261,7 @@ Function,+,furi_event_loop_timer_restart,void,FuriEventLoopTimer*
Function,+,furi_event_loop_timer_start,void,"FuriEventLoopTimer*, uint32_t" Function,+,furi_event_loop_timer_start,void,"FuriEventLoopTimer*, uint32_t"
Function,+,furi_event_loop_timer_stop,void,FuriEventLoopTimer* Function,+,furi_event_loop_timer_stop,void,FuriEventLoopTimer*
Function,+,furi_event_loop_unsubscribe,void,"FuriEventLoop*, FuriEventLoopObject*" Function,+,furi_event_loop_unsubscribe,void,"FuriEventLoop*, FuriEventLoopObject*"
Function,+,furi_event_loop_unsubscribe_thread_flags,void,FuriEventLoop*
Function,+,furi_get_tick,uint32_t, Function,+,furi_get_tick,uint32_t,
Function,+,furi_hal_adc_acquire,FuriHalAdcHandle*, Function,+,furi_hal_adc_acquire,FuriHalAdcHandle*,
Function,+,furi_hal_adc_configure,void,FuriHalAdcHandle* Function,+,furi_hal_adc_configure,void,FuriHalAdcHandle*
@@ -2967,14 +2969,14 @@ Function,+,pipe_bytes_available,size_t,PipeSide*
Function,+,pipe_detach_from_event_loop,void,PipeSide* Function,+,pipe_detach_from_event_loop,void,PipeSide*
Function,+,pipe_free,void,PipeSide* Function,+,pipe_free,void,PipeSide*
Function,+,pipe_install_as_stdio,void,PipeSide* Function,+,pipe_install_as_stdio,void,PipeSide*
Function,+,pipe_receive,size_t,"PipeSide*, void*, size_t, FuriWait" Function,+,pipe_receive,size_t,"PipeSide*, void*, size_t"
Function,+,pipe_role,PipeRole,PipeSide* Function,+,pipe_role,PipeRole,PipeSide*
Function,+,pipe_send,size_t,"PipeSide*, const void*, size_t, FuriWait" Function,+,pipe_send,size_t,"PipeSide*, const void*, size_t"
Function,+,pipe_set_broken_callback,void,"PipeSide*, PipeSideBrokenCallback, FuriEventLoopEvent" Function,+,pipe_set_broken_callback,void,"PipeSide*, PipeSideBrokenCallback, FuriEventLoopEvent"
Function,+,pipe_set_callback_context,void,"PipeSide*, void*" Function,+,pipe_set_callback_context,void,"PipeSide*, void*"
Function,+,pipe_set_data_arrived_callback,void,"PipeSide*, PipeSideDataArrivedCallback, FuriEventLoopEvent" Function,+,pipe_set_data_arrived_callback,void,"PipeSide*, PipeSideDataArrivedCallback, FuriEventLoopEvent"
Function,+,pipe_set_space_freed_callback,void,"PipeSide*, PipeSideSpaceFreedCallback, FuriEventLoopEvent" Function,+,pipe_set_space_freed_callback,void,"PipeSide*, PipeSideSpaceFreedCallback, FuriEventLoopEvent"
Function,+,pipe_set_stdout_timeout,void,"PipeSide*, FuriWait" Function,+,pipe_set_state_check_period,void,"PipeSide*, FuriWait"
Function,+,pipe_spaces_available,size_t,PipeSide* Function,+,pipe_spaces_available,size_t,PipeSide*
Function,+,pipe_state,PipeState,PipeSide* Function,+,pipe_state,PipeState,PipeSide*
Function,+,plugin_manager_alloc,PluginManager*,"const char*, uint32_t, const ElfApiInterface*" Function,+,plugin_manager_alloc,PluginManager*,"const char*, uint32_t, const ElfApiInterface*"
@@ -3791,7 +3793,6 @@ Variable,-,_sys_errlist,const char* const[],
Variable,-,_sys_nerr,int, Variable,-,_sys_nerr,int,
Variable,-,ble_profile_hid,const FuriHalBleProfileTemplate*, Variable,-,ble_profile_hid,const FuriHalBleProfileTemplate*,
Variable,+,ble_profile_serial,const FuriHalBleProfileTemplate* const, Variable,+,ble_profile_serial,const FuriHalBleProfileTemplate* const,
Variable,+,cli_vcp,const CliSession,
Variable,+,compress_config_heatshrink_default,const CompressConfigHeatshrink, Variable,+,compress_config_heatshrink_default,const CompressConfigHeatshrink,
Variable,+,firmware_api_interface,const ElfApiInterface* const, Variable,+,firmware_api_interface,const ElfApiInterface* const,
Variable,+,furi_hal_i2c_bus_external,FuriHalI2cBus, Variable,+,furi_hal_i2c_bus_external,FuriHalI2cBus,
1 entry status name type params
2 Version + 83.0 84.0
3 Header + applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h
4 Header + applications/services/bt/bt_service/bt.h
5 Header + applications/services/bt/bt_service/bt_keys_storage.h
6 Header + applications/services/cli/cli.h
7 Header + applications/services/cli/cli_ansi.h
8 Header + applications/services/cli/cli_vcp.h
9 Header + applications/services/dialogs/dialogs.h
10 Header + applications/services/dolphin/dolphin.h
856 Function - cfree void void*
857 Function - clearerr void FILE*
858 Function - clearerr_unlocked void FILE*
859 Function + cli_add_command void Cli*, const char*, CliCommandFlag, CliCallback, void* Cli*, const char*, CliCommandFlag, CliExecuteCallback, void*
860 Function + cli_cmd_interrupt_received cli_add_command_ex _Bool void Cli* Cli*, const char*, CliCommandFlag, CliExecuteCallback, void*, size_t
861 Function + cli_ansi_parser_alloc CliAnsiParser*
862 Function + cli_ansi_parser_feed CliAnsiParserResult CliAnsiParser*, char
863 Function + cli_ansi_parser_feed_timeout CliAnsiParserResult CliAnsiParser*
864 Function + cli_ansi_parser_free void CliAnsiParser*
865 Function + cli_delete_command void Cli*, const char*
866 Function + cli_getc cli_is_pipe_broken_or_is_etx_next_char char _Bool Cli* PipeSide*
Function + cli_is_connected _Bool Cli*
Function + cli_nl void Cli*
867 Function + cli_print_usage void const char*, const char*, const char*
868 Function + cli_read cli_vcp_disable size_t void Cli*, uint8_t*, size_t CliVcp*
869 Function + cli_read_timeout cli_vcp_enable size_t void Cli*, uint8_t*, size_t, uint32_t CliVcp*
Function + cli_session_close void Cli*
Function + cli_session_open void Cli*, const void*
Function + cli_write void Cli*, const uint8_t*, size_t
870 Function + composite_api_resolver_add void CompositeApiResolver*, const ElfApiInterface*
871 Function + composite_api_resolver_alloc CompositeApiResolver*
872 Function + composite_api_resolver_free void CompositeApiResolver*
1250 Function + furi_event_loop_subscribe_mutex void FuriEventLoop*, FuriMutex*, FuriEventLoopEvent, FuriEventLoopEventCallback, void*
1251 Function + furi_event_loop_subscribe_semaphore void FuriEventLoop*, FuriSemaphore*, FuriEventLoopEvent, FuriEventLoopEventCallback, void*
1252 Function + furi_event_loop_subscribe_stream_buffer void FuriEventLoop*, FuriStreamBuffer*, FuriEventLoopEvent, FuriEventLoopEventCallback, void*
1253 Function + furi_event_loop_subscribe_thread_flags void FuriEventLoop*, FuriEventLoopThreadFlagsCallback, void*
1254 Function + furi_event_loop_tick_set void FuriEventLoop*, uint32_t, FuriEventLoopTickCallback, void*
1255 Function + furi_event_loop_timer_alloc FuriEventLoopTimer* FuriEventLoop*, FuriEventLoopTimerCallback, FuriEventLoopTimerType, void*
1256 Function + furi_event_loop_timer_free void FuriEventLoopTimer*
1261 Function + furi_event_loop_timer_start void FuriEventLoopTimer*, uint32_t
1262 Function + furi_event_loop_timer_stop void FuriEventLoopTimer*
1263 Function + furi_event_loop_unsubscribe void FuriEventLoop*, FuriEventLoopObject*
1264 Function + furi_event_loop_unsubscribe_thread_flags void FuriEventLoop*
1265 Function + furi_get_tick uint32_t
1266 Function + furi_hal_adc_acquire FuriHalAdcHandle*
1267 Function + furi_hal_adc_configure void FuriHalAdcHandle*
2969 Function + pipe_detach_from_event_loop void PipeSide*
2970 Function + pipe_free void PipeSide*
2971 Function + pipe_install_as_stdio void PipeSide*
2972 Function + pipe_receive size_t PipeSide*, void*, size_t, FuriWait PipeSide*, void*, size_t
2973 Function + pipe_role PipeRole PipeSide*
2974 Function + pipe_send size_t PipeSide*, const void*, size_t, FuriWait PipeSide*, const void*, size_t
2975 Function + pipe_set_broken_callback void PipeSide*, PipeSideBrokenCallback, FuriEventLoopEvent
2976 Function + pipe_set_callback_context void PipeSide*, void*
2977 Function + pipe_set_data_arrived_callback void PipeSide*, PipeSideDataArrivedCallback, FuriEventLoopEvent
2978 Function + pipe_set_space_freed_callback void PipeSide*, PipeSideSpaceFreedCallback, FuriEventLoopEvent
2979 Function + pipe_set_stdout_timeout pipe_set_state_check_period void PipeSide*, FuriWait
2980 Function + pipe_spaces_available size_t PipeSide*
2981 Function + pipe_state PipeState PipeSide*
2982 Function + plugin_manager_alloc PluginManager* const char*, uint32_t, const ElfApiInterface*
3793 Variable - _sys_nerr int
3794 Variable - ble_profile_hid const FuriHalBleProfileTemplate*
3795 Variable + ble_profile_serial const FuriHalBleProfileTemplate* const
Variable + cli_vcp const CliSession
3796 Variable + compress_config_heatshrink_default const CompressConfigHeatshrink
3797 Variable + firmware_api_interface const ElfApiInterface* const
3798 Variable + furi_hal_i2c_bus_external FuriHalI2cBus

View File

@@ -9,11 +9,21 @@
extern "C" { extern "C" {
#endif #endif
typedef enum {
CdcStateDisconnected,
CdcStateConnected,
} CdcState;
typedef enum {
CdcCtrlLineDTR = (1 << 0),
CdcCtrlLineRTS = (1 << 1),
} CdcCtrlLine;
typedef struct { typedef struct {
void (*tx_ep_callback)(void* context); void (*tx_ep_callback)(void* context);
void (*rx_ep_callback)(void* context); void (*rx_ep_callback)(void* context);
void (*state_callback)(void* context, uint8_t state); void (*state_callback)(void* context, CdcState state);
void (*ctrl_line_callback)(void* context, uint8_t state); void (*ctrl_line_callback)(void* context, CdcCtrlLine ctrl_lines);
void (*config_callback)(void* context, struct usb_cdc_line_coding* config); void (*config_callback)(void* context, struct usb_cdc_line_coding* config);
} CdcCallbacks; } CdcCallbacks;