diff --git a/SConstruct b/SConstruct index 1279679688..44aa3bc174 100644 --- a/SConstruct +++ b/SConstruct @@ -412,6 +412,21 @@ distenv.PhonyTarget( ], ) + +# Measure CLI loopback performance +distenv.PhonyTarget( + "cli_perf", + [ + [ + "${PYTHON3}", + "${FBT_SCRIPT_DIR}/serial_cli_perf.py", + "-p", + "${FLIP_PORT}", + "${ARGS}", + ] + ], +) + # Update WiFi devboard firmware with release channel distenv.PhonyTarget( "devboard_flash", diff --git a/applications/debug/speaker_debug/speaker_debug.c b/applications/debug/speaker_debug/speaker_debug.c index 3f685ab30d..d2a40a2a48 100644 --- a/applications/debug/speaker_debug/speaker_debug.c +++ b/applications/debug/speaker_debug/speaker_debug.c @@ -3,6 +3,7 @@ #include #include #include +#include #define TAG "SpeakerDebug" @@ -37,8 +38,8 @@ static void speaker_app_free(SpeakerDebugApp* app) { free(app); } -static void speaker_app_cli(Cli* cli, FuriString* args, void* context) { - UNUSED(cli); +static void speaker_app_cli(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); SpeakerDebugApp* app = (SpeakerDebugApp*)context; SpeakerDebugAppMessage message; diff --git a/applications/debug/unit_tests/test_runner.c b/applications/debug/unit_tests/test_runner.c index de29e91b39..4de0511258 100644 --- a/applications/debug/unit_tests/test_runner.c +++ b/applications/debug/unit_tests/test_runner.c @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -25,7 +26,7 @@ struct TestRunner { NotificationApp* notification; // Temporary used things - Cli* cli; + PipeSide* pipe; FuriString* args; // ELF related stuff @@ -38,14 +39,14 @@ struct TestRunner { int minunit_status; }; -TestRunner* test_runner_alloc(Cli* cli, FuriString* args) { +TestRunner* test_runner_alloc(PipeSide* pipe, FuriString* args) { TestRunner* instance = malloc(sizeof(TestRunner)); instance->storage = furi_record_open(RECORD_STORAGE); instance->loader = furi_record_open(RECORD_LOADER); instance->notification = furi_record_open(RECORD_NOTIFICATION); - instance->cli = cli; + instance->pipe = pipe; instance->args = args; instance->composite_resolver = composite_api_resolver_alloc(); @@ -147,7 +148,7 @@ static void test_runner_run_internal(TestRunner* instance) { } while(true) { - if(cli_cmd_interrupt_received(instance->cli)) { + if(cli_is_pipe_broken_or_is_etx_next_char(instance->pipe)) { break; } diff --git a/applications/debug/unit_tests/test_runner.h b/applications/debug/unit_tests/test_runner.h index 43aba8bb15..0e9495263d 100644 --- a/applications/debug/unit_tests/test_runner.h +++ b/applications/debug/unit_tests/test_runner.h @@ -1,12 +1,12 @@ #pragma once #include +#include 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); diff --git a/applications/debug/unit_tests/tests/pipe/pipe_test.c b/applications/debug/unit_tests/tests/pipe/pipe_test.c index d440a04eec..4eae396361 100644 --- a/applications/debug/unit_tests/tests/pipe/pipe_test.c +++ b/applications/debug/unit_tests/tests/pipe/pipe_test.c @@ -25,16 +25,13 @@ MU_TEST(pipe_test_trivial) { mu_assert_int_eq(PIPE_SIZE - i, pipe_spaces_available(alice)); mu_assert_int_eq(i, pipe_bytes_available(bob)); - if(pipe_send(alice, &i, sizeof(uint8_t), 0) != sizeof(uint8_t)) { - break; - } + if(pipe_spaces_available(alice) == 0) break; + furi_check(pipe_send(alice, &i, sizeof(uint8_t)) == sizeof(uint8_t)); mu_assert_int_eq(PIPE_SIZE - i, pipe_spaces_available(bob)); mu_assert_int_eq(i, pipe_bytes_available(alice)); - if(pipe_send(bob, &i, sizeof(uint8_t), 0) != sizeof(uint8_t)) { - break; - } + furi_check(pipe_send(bob, &i, sizeof(uint8_t)) == sizeof(uint8_t)); } pipe_free(alice); @@ -43,10 +40,9 @@ MU_TEST(pipe_test_trivial) { for(uint8_t i = 0;; ++i) { mu_assert_int_eq(PIPE_SIZE - i, pipe_bytes_available(bob)); + if(pipe_bytes_available(bob) == 0) break; uint8_t value; - if(pipe_receive(bob, &value, sizeof(uint8_t), 0) != sizeof(uint8_t)) { - break; - } + furi_check(pipe_receive(bob, &value, sizeof(uint8_t)) == sizeof(uint8_t)); mu_assert_int_eq(i, value); } @@ -68,16 +64,16 @@ typedef struct { static void on_data_arrived(PipeSide* pipe, void* context) { AncillaryThreadContext* ctx = context; ctx->flag |= TestFlagDataArrived; - uint8_t buffer[PIPE_SIZE]; - size_t size = pipe_receive(pipe, buffer, sizeof(buffer), 0); - pipe_send(pipe, buffer, size, 0); + uint8_t input; + size_t size = pipe_receive(pipe, &input, sizeof(input)); + pipe_send(pipe, &input, size); } static void on_space_freed(PipeSide* pipe, void* context) { AncillaryThreadContext* ctx = context; ctx->flag |= TestFlagSpaceFreed; 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) { @@ -117,15 +113,15 @@ MU_TEST(pipe_test_event_loop) { furi_thread_start(thread); const char* message = "Hello!"; - pipe_send(alice, message, strlen(message), FuriWaitForever); + pipe_send(alice, message, strlen(message)); char buffer_1[16]; - size_t size = pipe_receive(alice, buffer_1, sizeof(buffer_1), FuriWaitForever); + size_t size = pipe_receive(alice, buffer_1, strlen(message)); buffer_1[size] = 0; char buffer_2[16]; const char* expected_reply = "Hi!"; - size = pipe_receive(alice, buffer_2, sizeof(buffer_2), FuriWaitForever); + size = pipe_receive(alice, buffer_2, strlen(expected_reply)); buffer_2[size] = 0; pipe_free(alice); diff --git a/applications/debug/unit_tests/tests/storage/storage_test.c b/applications/debug/unit_tests/tests/storage/storage_test.c index 75c52ef9a7..9d5c68a448 100644 --- a/applications/debug/unit_tests/tests/storage/storage_test.c +++ b/applications/debug/unit_tests/tests/storage/storage_test.c @@ -521,11 +521,6 @@ MU_TEST(test_storage_data_path) { // check that appsdata folder exists mu_check(storage_dir_exists(storage, APPS_DATA_PATH)); - // check that cli folder exists - mu_check(storage_dir_exists(storage, APPSDATA_APP_PATH("cli"))); - - storage_simply_remove(storage, APPSDATA_APP_PATH("cli")); - furi_record_close(RECORD_STORAGE); } diff --git a/applications/debug/unit_tests/unit_tests.c b/applications/debug/unit_tests/unit_tests.c index 237cb9080e..db78d8cedc 100644 --- a/applications/debug/unit_tests/unit_tests.c +++ b/applications/debug/unit_tests/unit_tests.c @@ -1,13 +1,13 @@ #include #include +#include #include "test_runner.h" -void unit_tests_cli(Cli* cli, FuriString* args, void* context) { - UNUSED(cli); +void unit_tests_cli(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); - TestRunner* test_runner = test_runner_alloc(cli, args); + TestRunner* test_runner = test_runner_alloc(pipe, args); test_runner_run(test_runner); test_runner_free(test_runner); } diff --git a/applications/main/application.fam b/applications/main/application.fam index 0a90ee2243..4d31623373 100644 --- a/applications/main/application.fam +++ b/applications/main/application.fam @@ -21,6 +21,7 @@ App( name="On start hooks", apptype=FlipperAppType.METAPACKAGE, provides=[ + "cli", "ibutton_start", "onewire_start", "subghz_start", diff --git a/applications/main/gpio/usb_uart_bridge.c b/applications/main/gpio/usb_uart_bridge.c index e6b71cb34f..15170f0d02 100644 --- a/applications/main/gpio/usb_uart_bridge.c +++ b/applications/main/gpio/usb_uart_bridge.c @@ -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) { furi_hal_usb_unlock(); if(vcp_ch == 0) { - Cli* cli = furi_record_open(RECORD_CLI); - cli_session_close(cli); - furi_record_close(RECORD_CLI); + CliVcp* cli_vcp = furi_record_open(RECORD_CLI_VCP); + cli_vcp_disable(cli_vcp); + furi_record_close(RECORD_CLI_VCP); furi_check(furi_hal_usb_set_config(&usb_cdc_single, NULL) == true); } else { furi_check(furi_hal_usb_set_config(&usb_cdc_dual, NULL) == true); - Cli* cli = furi_record_open(RECORD_CLI); - cli_session_open(cli, &cli_vcp); - furi_record_close(RECORD_CLI); + CliVcp* cli_vcp = furi_record_open(RECORD_CLI_VCP); + cli_vcp_enable(cli_vcp); + furi_record_close(RECORD_CLI_VCP); } furi_hal_cdc_set_callbacks(vcp_ch, (CdcCallbacks*)&cdc_cb, usb_uart); } @@ -123,9 +123,9 @@ static void usb_uart_vcp_deinit(UsbUartBridge* usb_uart, uint8_t vcp_ch) { UNUSED(usb_uart); furi_hal_cdc_set_callbacks(vcp_ch, NULL, NULL); if(vcp_ch != 0) { - Cli* cli = furi_record_open(RECORD_CLI); - cli_session_close(cli); - furi_record_close(RECORD_CLI); + CliVcp* cli_vcp = furi_record_open(RECORD_CLI_VCP); + cli_vcp_disable(cli_vcp); + furi_record_close(RECORD_CLI_VCP); } } @@ -309,9 +309,9 @@ static int32_t usb_uart_worker(void* context) { furi_hal_usb_unlock(); furi_check(furi_hal_usb_set_config(&usb_cdc_single, NULL) == true); - Cli* cli = furi_record_open(RECORD_CLI); - cli_session_open(cli, &cli_vcp); - furi_record_close(RECORD_CLI); + CliVcp* cli_vcp = furi_record_open(RECORD_CLI_VCP); + cli_vcp_enable(cli_vcp); + furi_record_close(RECORD_CLI_VCP); return 0; } diff --git a/applications/main/ibutton/ibutton_cli.c b/applications/main/ibutton/ibutton_cli.c index 2338ca3c3d..e11ace1d0f 100644 --- a/applications/main/ibutton/ibutton_cli.c +++ b/applications/main/ibutton/ibutton_cli.c @@ -1,26 +1,14 @@ #include #include -#include +#include #include +#include #include #include #include -static void ibutton_cli(Cli* cli, FuriString* args, void* context); - -// app cli function -void ibutton_on_system_start(void) { -#ifdef SRV_CLI - Cli* cli = furi_record_open(RECORD_CLI); - cli_add_command(cli, "ikey", CliCommandFlagDefault, ibutton_cli, cli); - furi_record_close(RECORD_CLI); -#else - UNUSED(ibutton_cli); -#endif -} - static void ibutton_cli_print_usage(void) { printf("Usage:\r\n"); printf("ikey read\r\n"); @@ -92,7 +80,7 @@ static void ibutton_cli_worker_read_cb(void* context) { furi_event_flag_set(event, EVENT_FLAG_IBUTTON_COMPLETE); } -static void ibutton_cli_read(Cli* cli) { +static void ibutton_cli_read(PipeSide* pipe) { iButtonProtocols* protocols = ibutton_protocols_alloc(); iButtonWorker* worker = ibutton_worker_alloc(protocols); iButtonKey* key = ibutton_key_alloc(ibutton_protocols_get_max_data_size(protocols)); @@ -113,7 +101,7 @@ static void ibutton_cli_read(Cli* cli) { break; } - if(cli_cmd_interrupt_received(cli)) break; + if(cli_is_pipe_broken_or_is_etx_next_char(pipe)) break; } ibutton_worker_stop(worker); @@ -138,7 +126,7 @@ static void ibutton_cli_worker_write_cb(void* context, iButtonWorkerWriteResult furi_event_flag_set(write_context->event, EVENT_FLAG_IBUTTON_COMPLETE); } -void ibutton_cli_write(Cli* cli, FuriString* args) { +void ibutton_cli_write(PipeSide* pipe, FuriString* args) { iButtonProtocols* protocols = ibutton_protocols_alloc(); iButtonWorker* worker = ibutton_worker_alloc(protocols); iButtonKey* key = ibutton_key_alloc(ibutton_protocols_get_max_data_size(protocols)); @@ -181,7 +169,7 @@ void ibutton_cli_write(Cli* cli, FuriString* args) { } } - if(cli_cmd_interrupt_received(cli)) break; + if(cli_is_pipe_broken_or_is_etx_next_char(pipe)) break; } } while(false); @@ -195,7 +183,7 @@ void ibutton_cli_write(Cli* cli, FuriString* args) { furi_event_flag_free(write_context.event); } -void ibutton_cli_emulate(Cli* cli, FuriString* args) { +void ibutton_cli_emulate(PipeSide* pipe, FuriString* args) { iButtonProtocols* protocols = ibutton_protocols_alloc(); iButtonWorker* worker = ibutton_worker_alloc(protocols); iButtonKey* key = ibutton_key_alloc(ibutton_protocols_get_max_data_size(protocols)); @@ -214,7 +202,7 @@ void ibutton_cli_emulate(Cli* cli, FuriString* args) { ibutton_worker_emulate_start(worker, key); - while(!cli_cmd_interrupt_received(cli)) { + while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) { furi_delay_ms(100); }; @@ -228,8 +216,8 @@ void ibutton_cli_emulate(Cli* cli, FuriString* args) { ibutton_protocols_free(protocols); } -void ibutton_cli(Cli* cli, FuriString* args, void* context) { - UNUSED(cli); +void ibutton_cli(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); UNUSED(context); FuriString* cmd; 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) { - ibutton_cli_read(cli); + ibutton_cli_read(pipe); } else if(furi_string_cmp_str(cmd, "write") == 0) { - ibutton_cli_write(cli, args); + ibutton_cli_write(pipe, args); } else if(furi_string_cmp_str(cmd, "emulate") == 0) { - ibutton_cli_emulate(cli, args); + ibutton_cli_emulate(pipe, args); } else { ibutton_cli_print_usage(); } furi_string_free(cmd); } + +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 +} diff --git a/applications/main/infrared/infrared_cli.c b/applications/main/infrared/infrared_cli.c index 85ae956583..e62da5fd28 100644 --- a/applications/main/infrared/infrared_cli.c +++ b/applications/main/infrared/infrared_cli.c @@ -1,11 +1,11 @@ -#include -#include +#include #include #include #include #include #include #include +#include #include #include "infrared_signal.h" @@ -19,14 +19,14 @@ DICT_DEF2(dict_signals, FuriString*, FURI_STRING_OPLIST, int, M_DEFAULT_OPLIST) -static void infrared_cli_start_ir_rx(Cli* cli, FuriString* args); -static void infrared_cli_start_ir_tx(Cli* cli, FuriString* args); -static void infrared_cli_process_decode(Cli* cli, FuriString* args); -static void infrared_cli_process_universal(Cli* cli, FuriString* args); +static void infrared_cli_start_ir_rx(PipeSide* pipe, FuriString* args); +static void infrared_cli_start_ir_tx(PipeSide* pipe, FuriString* args); +static void infrared_cli_process_decode(PipeSide* pipe, FuriString* args); +static void infrared_cli_process_universal(PipeSide* pipe, FuriString* args); static const struct { const char* cmd; - void (*process_function)(Cli* cli, FuriString* args); + void (*process_function)(PipeSide* pipe, FuriString* args); } infrared_cli_commands[] = { {.cmd = "rx", .process_function = infrared_cli_start_ir_rx}, {.cmd = "tx", .process_function = infrared_cli_start_ir_tx}, @@ -38,7 +38,7 @@ static void signal_received_callback(void* context, InfraredWorkerSignal* receiv furi_assert(received_signal); char buf[100]; size_t buf_cnt; - Cli* cli = (Cli*)context; + PipeSide* pipe = (PipeSide*)context; if(infrared_worker_signal_is_decoded(received_signal)) { const InfraredMessage* message = infrared_worker_get_decoded_signal(received_signal); @@ -52,20 +52,20 @@ static void signal_received_callback(void* context, InfraredWorkerSignal* receiv ROUND_UP_TO(infrared_get_protocol_command_length(message->protocol), 4), message->command, message->repeat ? " R" : ""); - cli_write(cli, (uint8_t*)buf, buf_cnt); + pipe_send(pipe, buf, buf_cnt); } else { const uint32_t* timings; size_t timings_cnt; infrared_worker_get_raw_signal(received_signal, &timings, &timings_cnt); buf_cnt = snprintf(buf, sizeof(buf), "RAW, %zu samples:\r\n", timings_cnt); - cli_write(cli, (uint8_t*)buf, buf_cnt); + pipe_send(pipe, buf, buf_cnt); for(size_t i = 0; i < timings_cnt; ++i) { buf_cnt = snprintf(buf, sizeof(buf), "%lu ", timings[i]); - cli_write(cli, (uint8_t*)buf, buf_cnt); + pipe_send(pipe, buf, buf_cnt); } buf_cnt = snprintf(buf, sizeof(buf), "\r\n"); - cli_write(cli, (uint8_t*)buf, buf_cnt); + pipe_send(pipe, buf, buf_cnt); } } @@ -124,9 +124,7 @@ static void infrared_cli_print_usage(void) { infrared_cli_print_universal_remotes(); } -static void infrared_cli_start_ir_rx(Cli* cli, FuriString* args) { - UNUSED(cli); - +static void infrared_cli_start_ir_rx(PipeSide* pipe, FuriString* args) { bool enable_decoding = true; if(!furi_string_empty(args)) { @@ -142,10 +140,10 @@ static void infrared_cli_start_ir_rx(Cli* cli, FuriString* args) { InfraredWorker* worker = infrared_worker_alloc(); infrared_worker_rx_enable_signal_decoding(worker, enable_decoding); infrared_worker_rx_start(worker); - infrared_worker_rx_set_received_signal_callback(worker, signal_received_callback, cli); + infrared_worker_rx_set_received_signal_callback(worker, signal_received_callback, pipe); printf("Receiving %s INFRARED...\r\nPress Ctrl+C to abort\r\n", enable_decoding ? "" : "RAW"); - while(!cli_cmd_interrupt_received(cli)) { + while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) { furi_delay_ms(50); } @@ -214,8 +212,8 @@ static bool infrared_cli_parse_raw(const char* str, InfraredSignal* signal) { return infrared_signal_is_valid(signal); } -static void infrared_cli_start_ir_tx(Cli* cli, FuriString* args) { - UNUSED(cli); +static void infrared_cli_start_ir_tx(PipeSide* pipe, FuriString* args) { + UNUSED(pipe); const char* str = furi_string_get_cstr(args); InfraredSignal* signal = infrared_signal_alloc(); @@ -335,8 +333,8 @@ static bool infrared_cli_decode_file(FlipperFormat* input_file, FlipperFormat* o return ret; } -static void infrared_cli_process_decode(Cli* cli, FuriString* args) { - UNUSED(cli); +static void infrared_cli_process_decode(PipeSide* pipe, FuriString* args) { + UNUSED(pipe); Storage* storage = furi_record_open(RECORD_STORAGE); FlipperFormat* input_file = flipper_format_buffered_file_alloc(storage); FlipperFormat* output_file = NULL; @@ -455,8 +453,10 @@ static void infrared_cli_list_remote_signals(FuriString* remote_name) { furi_record_close(RECORD_STORAGE); } -static void - infrared_cli_brute_force_signals(Cli* cli, FuriString* remote_name, FuriString* signal_name) { +static void infrared_cli_brute_force_signals( + PipeSide* pipe, + FuriString* remote_name, + FuriString* signal_name) { InfraredBruteForce* brute_force = infrared_brute_force_alloc(); FuriString* remote_path = furi_string_alloc_printf( "%s/%s.ir", INFRARED_ASSETS_FOLDER, furi_string_get_cstr(remote_name)); @@ -490,7 +490,7 @@ static void while(running) { running = infrared_brute_force_send(brute_force, current_signal); - if(cli_cmd_interrupt_received(cli)) break; + if(cli_is_pipe_broken_or_is_etx_next_char(pipe)) break; printf("\r%d%% complete.", (int)((float)current_signal++ / (float)signal_count * 100)); fflush(stdout); @@ -504,7 +504,7 @@ static void infrared_brute_force_free(brute_force); } -static void infrared_cli_process_universal(Cli* cli, FuriString* args) { +static void infrared_cli_process_universal(PipeSide* pipe, FuriString* args) { FuriString* arg1 = furi_string_alloc(); FuriString* arg2 = furi_string_alloc(); @@ -519,14 +519,14 @@ static void infrared_cli_process_universal(Cli* cli, FuriString* args) { } else if(furi_string_equal_str(arg1, "list")) { infrared_cli_list_remote_signals(arg2); } else { - infrared_cli_brute_force_signals(cli, arg1, arg2); + infrared_cli_brute_force_signals(pipe, arg1, arg2); } furi_string_free(arg1); furi_string_free(arg2); } -static void infrared_cli_start_ir(Cli* cli, FuriString* args, void* context) { +static void infrared_cli_start_ir(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); if(furi_hal_infrared_is_busy()) { 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)) { - infrared_cli_commands[i].process_function(cli, args); + infrared_cli_commands[i].process_function(pipe, args); } else { infrared_cli_print_usage(); } diff --git a/applications/main/lfrfid/lfrfid_cli.c b/applications/main/lfrfid/lfrfid_cli.c index a25032d6af..fa74906c07 100644 --- a/applications/main/lfrfid/lfrfid_cli.c +++ b/applications/main/lfrfid/lfrfid_cli.c @@ -1,11 +1,12 @@ #include #include #include -#include +#include #include #include #include #include +#include #include @@ -14,15 +15,6 @@ #include #include -static void lfrfid_cli(Cli* cli, FuriString* args, void* context); - -// app cli function -void lfrfid_on_system_start(void) { - Cli* cli = furi_record_open(RECORD_CLI); - cli_add_command(cli, "rfid", CliCommandFlagDefault, lfrfid_cli, NULL); - furi_record_close(RECORD_CLI); -} - static void lfrfid_cli_print_usage(void) { printf("Usage:\r\n"); printf("rfid read - read in ASK/PSK mode\r\n"); @@ -49,7 +41,7 @@ static void lfrfid_cli_read_callback(LFRFIDWorkerReadResult result, ProtocolId p furi_event_flag_set(context->event, 1 << result); } -static void lfrfid_cli_read(Cli* cli, FuriString* args) { +static void lfrfid_cli_read(PipeSide* pipe, FuriString* args) { FuriString* type_string; type_string = furi_string_alloc(); LFRFIDWorkerReadType type = LFRFIDWorkerReadTypeAuto; @@ -96,7 +88,7 @@ static void lfrfid_cli_read(Cli* cli, FuriString* args) { } } - if(cli_cmd_interrupt_received(cli)) break; + if(cli_is_pipe_broken_or_is_etx_next_char(pipe)) break; } lfrfid_worker_stop(worker); @@ -192,7 +184,7 @@ static void lfrfid_cli_write_callback(LFRFIDWorkerWriteResult result, void* ctx) furi_event_flag_set(events, 1 << result); } -static void lfrfid_cli_write(Cli* cli, FuriString* args) { +static void lfrfid_cli_write(PipeSide* pipe, FuriString* args) { ProtocolDict* dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax); ProtocolId protocol; @@ -212,7 +204,7 @@ static void lfrfid_cli_write(Cli* cli, FuriString* args) { (1 << LFRFIDWorkerWriteProtocolCannotBeWritten) | (1 << LFRFIDWorkerWriteFobCannotBeWritten); - while(!cli_cmd_interrupt_received(cli)) { + while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) { uint32_t flags = furi_event_flag_wait(event, available_flags, FuriFlagWaitAny, 100); if(flags != (unsigned)FuriFlagErrorTimeout) { if(FURI_BIT(flags, LFRFIDWorkerWriteOK)) { @@ -239,7 +231,7 @@ static void lfrfid_cli_write(Cli* cli, FuriString* args) { furi_event_flag_free(event); } -static void lfrfid_cli_emulate(Cli* cli, FuriString* args) { +static void lfrfid_cli_emulate(PipeSide* pipe, FuriString* args) { ProtocolDict* dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax); ProtocolId protocol; @@ -254,7 +246,7 @@ static void lfrfid_cli_emulate(Cli* cli, FuriString* args) { lfrfid_worker_emulate_start(worker, protocol); printf("Emulating RFID...\r\nPress Ctrl+C to abort\r\n"); - while(!cli_cmd_interrupt_received(cli)) { + while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) { furi_delay_ms(100); } printf("Emulation stopped\r\n"); @@ -265,8 +257,8 @@ static void lfrfid_cli_emulate(Cli* cli, FuriString* args) { protocol_dict_free(dict); } -static void lfrfid_cli_raw_analyze(Cli* cli, FuriString* args) { - UNUSED(cli); +static void lfrfid_cli_raw_analyze(PipeSide* pipe, FuriString* args) { + UNUSED(pipe); FuriString *filepath, *info_string; filepath = furi_string_alloc(); info_string = furi_string_alloc(); @@ -392,9 +384,7 @@ static void lfrfid_cli_raw_read_callback(LFRFIDWorkerReadRawResult result, void* furi_event_flag_set(event, 1 << result); } -static void lfrfid_cli_raw_read(Cli* cli, FuriString* args) { - UNUSED(cli); - +static void lfrfid_cli_raw_read(PipeSide* pipe, FuriString* args) { FuriString *filepath, *type_string; filepath = furi_string_alloc(); type_string = furi_string_alloc(); @@ -452,7 +442,7 @@ static void lfrfid_cli_raw_read(Cli* cli, FuriString* args) { } } - if(cli_cmd_interrupt_received(cli)) break; + if(cli_is_pipe_broken_or_is_etx_next_char(pipe)) break; } if(overrun) { @@ -479,9 +469,7 @@ static void lfrfid_cli_raw_emulate_callback(LFRFIDWorkerEmulateRawResult result, furi_event_flag_set(event, 1 << result); } -static void lfrfid_cli_raw_emulate(Cli* cli, FuriString* args) { - UNUSED(cli); - +static void lfrfid_cli_raw_emulate(PipeSide* pipe, FuriString* args) { FuriString* filepath; filepath = furi_string_alloc(); Storage* storage = furi_record_open(RECORD_STORAGE); @@ -527,7 +515,7 @@ static void lfrfid_cli_raw_emulate(Cli* cli, FuriString* args) { } } - if(cli_cmd_interrupt_received(cli)) break; + if(cli_is_pipe_broken_or_is_etx_next_char(pipe)) break; } if(overrun) { @@ -548,7 +536,7 @@ static void lfrfid_cli_raw_emulate(Cli* cli, FuriString* args) { furi_string_free(filepath); } -static void lfrfid_cli(Cli* cli, FuriString* args, void* context) { +static void lfrfid_cli(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); FuriString* cmd; 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) { - lfrfid_cli_read(cli, args); + lfrfid_cli_read(pipe, args); } else if(furi_string_cmp_str(cmd, "write") == 0) { - lfrfid_cli_write(cli, args); + lfrfid_cli_write(pipe, args); } else if(furi_string_cmp_str(cmd, "emulate") == 0) { - lfrfid_cli_emulate(cli, args); + lfrfid_cli_emulate(pipe, args); } else if(furi_string_cmp_str(cmd, "raw_read") == 0) { - lfrfid_cli_raw_read(cli, args); + lfrfid_cli_raw_read(pipe, args); } else if(furi_string_cmp_str(cmd, "raw_emulate") == 0) { - lfrfid_cli_raw_emulate(cli, args); + lfrfid_cli_raw_emulate(pipe, args); } else if(furi_string_cmp_str(cmd, "raw_analyze") == 0) { - lfrfid_cli_raw_analyze(cli, args); + lfrfid_cli_raw_analyze(pipe, args); } else { lfrfid_cli_print_usage(); } furi_string_free(cmd); } + +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); +} diff --git a/applications/main/nfc/nfc_cli.c b/applications/main/nfc/nfc_cli.c index 90ac26d7c2..8a9b1fec4d 100644 --- a/applications/main/nfc/nfc_cli.c +++ b/applications/main/nfc/nfc_cli.c @@ -1,8 +1,10 @@ #include #include #include +#include #include #include +#include #include @@ -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); // Check if nfc worker is not busy 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("Press Ctrl+C to abort\r\n"); - while(!cli_cmd_interrupt_received(cli)) { + while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) { furi_delay_ms(50); } @@ -40,7 +42,7 @@ static void nfc_cli_field(Cli* cli, FuriString* args) { furi_hal_nfc_release(); } -static void nfc_cli(Cli* cli, FuriString* args, void* context) { +static void nfc_cli(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); FuriString* cmd; 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_string_cmp_str(cmd, "field") == 0) { - nfc_cli_field(cli, args); + nfc_cli_field(pipe, args); break; } } diff --git a/applications/main/onewire/onewire_cli.c b/applications/main/onewire/onewire_cli.c index 74ca6bc1f8..63e3d696f7 100644 --- a/applications/main/onewire/onewire_cli.c +++ b/applications/main/onewire/onewire_cli.c @@ -2,31 +2,18 @@ #include #include - -#include +#include #include #include -static void onewire_cli(Cli* cli, FuriString* args, void* context); - -void onewire_on_system_start(void) { -#ifdef SRV_CLI - Cli* cli = furi_record_open(RECORD_CLI); - cli_add_command(cli, "onewire", CliCommandFlagDefault, onewire_cli, cli); - furi_record_close(RECORD_CLI); -#else - UNUSED(onewire_cli); -#endif -} - static void onewire_cli_print_usage(void) { printf("Usage:\r\n"); printf("onewire search\r\n"); } -static void onewire_cli_search(Cli* cli) { - UNUSED(cli); +static void onewire_cli_search(PipeSide* pipe) { + UNUSED(pipe); OneWireHost* onewire = onewire_host_alloc(&gpio_ibutton); Power* power = furi_record_open(RECORD_POWER); uint8_t address[8]; @@ -58,7 +45,7 @@ static void onewire_cli_search(Cli* cli) { 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); FuriString* cmd; 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) { - onewire_cli_search(cli); + onewire_cli_search(pipe); } 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 +} diff --git a/applications/main/subghz/helpers/subghz_chat.c b/applications/main/subghz/helpers/subghz_chat.c index 9945b69c8b..5c55aedeb8 100644 --- a/applications/main/subghz/helpers/subghz_chat.c +++ b/applications/main/subghz/helpers/subghz_chat.c @@ -1,5 +1,6 @@ #include "subghz_chat.h" #include +#include #define TAG "SubGhzChat" @@ -14,7 +15,7 @@ struct SubGhzChatWorker { FuriMessageQueue* event_queue; uint32_t last_time_rx_data; - Cli* cli; + PipeSide* pipe; }; /** Worker thread @@ -30,7 +31,7 @@ static int32_t subghz_chat_worker_thread(void* context) { event.event = SubGhzChatEventUserEntrance; furi_message_queue_put(instance->event_queue, &event, 0); while(instance->worker_running) { - if(cli_read_timeout(instance->cli, (uint8_t*)&c, 1, 1000) == 1) { + if(pipe_receive(instance->pipe, (uint8_t*)&c, 1) == 1) { event.event = SubGhzChatEventInputData; event.c = c; furi_message_queue_put(instance->event_queue, &event, FuriWaitForever); @@ -55,10 +56,10 @@ static void subghz_chat_worker_update_rx_event_chat(void* context) { furi_message_queue_put(instance->event_queue, &event, FuriWaitForever); } -SubGhzChatWorker* subghz_chat_worker_alloc(Cli* cli) { +SubGhzChatWorker* subghz_chat_worker_alloc(PipeSide* pipe) { SubGhzChatWorker* instance = malloc(sizeof(SubGhzChatWorker)); - instance->cli = cli; + instance->pipe = pipe; instance->thread = furi_thread_alloc_ex("SubGhzChat", 2048, subghz_chat_worker_thread, instance); diff --git a/applications/main/subghz/helpers/subghz_chat.h b/applications/main/subghz/helpers/subghz_chat.h index 2c454b75d9..0d14975064 100644 --- a/applications/main/subghz/helpers/subghz_chat.h +++ b/applications/main/subghz/helpers/subghz_chat.h @@ -2,6 +2,7 @@ #include "../subghz_i.h" #include #include +#include typedef struct SubGhzChatWorker SubGhzChatWorker; @@ -19,7 +20,7 @@ typedef struct { char c; } SubGhzChatEvent; -SubGhzChatWorker* subghz_chat_worker_alloc(Cli* cli); +SubGhzChatWorker* subghz_chat_worker_alloc(PipeSide* pipe); void subghz_chat_worker_free(SubGhzChatWorker* instance); bool subghz_chat_worker_start( SubGhzChatWorker* instance, diff --git a/applications/main/subghz/subghz_cli.c b/applications/main/subghz/subghz_cli.c index 54e02196d9..a07ea5a7e0 100644 --- a/applications/main/subghz/subghz_cli.c +++ b/applications/main/subghz/subghz_cli.c @@ -4,6 +4,7 @@ #include #include +#include #include #include @@ -16,6 +17,7 @@ #include #include +#include #include "helpers/subghz_chat.h" @@ -61,7 +63,7 @@ static SubGhzEnvironment* subghz_cli_environment_init(void) { return environment; } -void subghz_cli_command_tx_carrier(Cli* cli, FuriString* args, void* context) { +void subghz_cli_command_tx_carrier(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); uint32_t frequency = 433920000; @@ -91,7 +93,7 @@ void subghz_cli_command_tx_carrier(Cli* cli, FuriString* args, void* context) { if(furi_hal_subghz_tx()) { printf("Transmitting at frequency %lu Hz\r\n", frequency); printf("Press CTRL+C to stop\r\n"); - while(!cli_cmd_interrupt_received(cli)) { + while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) { furi_delay_ms(250); } } else { @@ -104,7 +106,7 @@ void subghz_cli_command_tx_carrier(Cli* cli, FuriString* args, void* context) { furi_hal_power_suppress_charge_exit(); } -void subghz_cli_command_rx_carrier(Cli* cli, FuriString* args, void* context) { +void subghz_cli_command_rx_carrier(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); uint32_t frequency = 433920000; @@ -132,7 +134,7 @@ void subghz_cli_command_rx_carrier(Cli* cli, FuriString* args, void* context) { furi_hal_subghz_rx(); - while(!cli_cmd_interrupt_received(cli)) { + while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) { furi_delay_ms(250); printf("RSSI: %03.1fdbm\r", (double)furi_hal_subghz_get_rssi()); fflush(stdout); @@ -165,7 +167,7 @@ static const SubGhzDevice* subghz_cli_command_get_device(uint32_t* device_ind) { return device; } -void subghz_cli_command_tx(Cli* cli, FuriString* args, void* context) { +void subghz_cli_command_tx(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); uint32_t frequency = 433920000; uint32_t key = 0x0074BADE; @@ -235,7 +237,9 @@ void subghz_cli_command_tx(Cli* cli, FuriString* args, void* context) { furi_hal_power_suppress_charge_enter(); if(subghz_devices_start_async_tx(device, subghz_transmitter_yield, transmitter)) { - while(!(subghz_devices_is_async_complete_tx(device) || cli_cmd_interrupt_received(cli))) { + while( + !(subghz_devices_is_async_complete_tx(device) || + cli_is_pipe_broken_or_is_etx_next_char(pipe))) { printf("."); fflush(stdout); furi_delay_ms(333); @@ -292,7 +296,7 @@ static void subghz_cli_command_rx_callback( furi_string_free(text); } -void subghz_cli_command_rx(Cli* cli, FuriString* args, void* context) { +void subghz_cli_command_rx(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); uint32_t frequency = 433920000; uint32_t device_ind = 0; // 0 - CC1101_INT, 1 - CC1101_EXT @@ -348,7 +352,7 @@ void subghz_cli_command_rx(Cli* cli, FuriString* args, void* context) { frequency, device_ind); LevelDuration level_duration; - while(!cli_cmd_interrupt_received(cli)) { + while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) { int ret = furi_stream_buffer_receive( instance->stream, &level_duration, sizeof(LevelDuration), 10); if(ret == sizeof(LevelDuration)) { @@ -381,7 +385,7 @@ void subghz_cli_command_rx(Cli* cli, FuriString* args, void* context) { free(instance); } -void subghz_cli_command_rx_raw(Cli* cli, FuriString* args, void* context) { +void subghz_cli_command_rx_raw(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); uint32_t frequency = 433920000; @@ -419,7 +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); LevelDuration level_duration; size_t counter = 0; - while(!cli_cmd_interrupt_received(cli)) { + while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) { int ret = furi_stream_buffer_receive( instance->stream, &level_duration, sizeof(LevelDuration), 10); if(ret == 0) { @@ -455,7 +459,7 @@ void subghz_cli_command_rx_raw(Cli* cli, FuriString* args, void* context) { free(instance); } -void subghz_cli_command_decode_raw(Cli* cli, FuriString* args, void* context) { +void subghz_cli_command_decode_raw(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); FuriString* file_name; file_name = furi_string_alloc(); @@ -525,7 +529,7 @@ void subghz_cli_command_decode_raw(Cli* cli, FuriString* args, void* context) { furi_string_get_cstr(file_name)); LevelDuration level_duration; - while(!cli_cmd_interrupt_received(cli)) { + while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) { furi_delay_us(500); //you need to have time to read from the file from the SD card level_duration = subghz_file_encoder_worker_get_level_duration(file_worker_encoder); if(!level_duration_is_reset(level_duration)) { @@ -570,7 +574,7 @@ static FuriHalSubGhzPreset subghz_cli_get_preset_name(const char* preset_name) { return preset; } -void subghz_cli_command_tx_from_file(Cli* cli, FuriString* args, void* context) { // -V524 +void subghz_cli_command_tx_from_file(PipeSide* pipe, FuriString* args, void* context) { // -V524 UNUSED(context); FuriString* file_name; file_name = furi_string_alloc(); @@ -774,7 +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)) { while( !(subghz_devices_is_async_complete_tx(device) || - cli_cmd_interrupt_received(cli))) { + cli_is_pipe_broken_or_is_etx_next_char(pipe))) { printf("."); fflush(stdout); furi_delay_ms(333); @@ -788,11 +792,11 @@ void subghz_cli_command_tx_from_file(Cli* cli, FuriString* args, void* context) if(!strcmp(furi_string_get_cstr(temp_str), "RAW")) { subghz_transmitter_stop(transmitter); repeat--; - if(!cli_cmd_interrupt_received(cli) && repeat) + if(!cli_is_pipe_broken_or_is_etx_next_char(pipe) && repeat) subghz_transmitter_deserialize(transmitter, fff_data_raw); } - } while(!cli_cmd_interrupt_received(cli) && + } while(!cli_is_pipe_broken_or_is_etx_next_char(pipe) && (repeat && !strcmp(furi_string_get_cstr(temp_str), "RAW"))); subghz_devices_sleep(device); @@ -837,8 +841,8 @@ static void subghz_cli_command_print_usage(void) { } } -static void subghz_cli_command_encrypt_keeloq(Cli* cli, FuriString* args) { - UNUSED(cli); +static void subghz_cli_command_encrypt_keeloq(PipeSide* pipe, FuriString* args) { + UNUSED(pipe); uint8_t iv[16]; FuriString* source; @@ -880,8 +884,8 @@ static void subghz_cli_command_encrypt_keeloq(Cli* cli, FuriString* args) { furi_string_free(source); } -static void subghz_cli_command_encrypt_raw(Cli* cli, FuriString* args) { - UNUSED(cli); +static void subghz_cli_command_encrypt_raw(PipeSide* pipe, FuriString* args) { + UNUSED(pipe); uint8_t iv[16]; FuriString* source; @@ -917,7 +921,7 @@ static void subghz_cli_command_encrypt_raw(Cli* cli, FuriString* args) { furi_string_free(source); } -static void subghz_cli_command_chat(Cli* cli, FuriString* args) { +static void subghz_cli_command_chat(PipeSide* pipe, FuriString* args) { uint32_t frequency = 433920000; uint32_t device_ind = 0; // 0 - CC1101_INT, 1 - CC1101_EXT @@ -951,7 +955,7 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args) { return; } - SubGhzChatWorker* subghz_chat = subghz_chat_worker_alloc(cli); + SubGhzChatWorker* subghz_chat = subghz_chat_worker_alloc(pipe); if(!subghz_chat_worker_start(subghz_chat, device, frequency)) { printf("Startup error SubGhzChatWorker\r\n"); @@ -992,13 +996,12 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args) { chat_event = subghz_chat_worker_get_event_chat(subghz_chat); switch(chat_event.event) { case SubGhzChatEventInputData: - if(chat_event.c == CliSymbolAsciiETX) { + if(chat_event.c == CliKeyETX) { printf("\r\n"); chat_event.event = SubGhzChatEventUserExit; subghz_chat_worker_put_event_chat(subghz_chat, &chat_event); break; - } else if( - (chat_event.c == CliSymbolAsciiBackspace) || (chat_event.c == CliSymbolAsciiDel)) { + } else if((chat_event.c == CliKeyBackspace) || (chat_event.c == CliKeyDEL)) { size_t len = furi_string_utf8_length(input); if(len > furi_string_utf8_length(name)) { printf("%s", "\e[D\e[1P"); @@ -1020,7 +1023,7 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args) { } furi_string_set(input, sysmsg); } - } else if(chat_event.c == CliSymbolAsciiCR) { + } else if(chat_event.c == CliKeyCR) { printf("\r\n"); furi_string_push_back(input, '\r'); furi_string_push_back(input, '\n'); @@ -1034,7 +1037,7 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args) { furi_string_printf(input, "%s", furi_string_get_cstr(name)); printf("%s", furi_string_get_cstr(input)); fflush(stdout); - } else if(chat_event.c == CliSymbolAsciiLF) { + } else if(chat_event.c == CliKeyLF) { //cut out the symbol \n } else { putc(chat_event.c, stdout); @@ -1088,7 +1091,7 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args) { break; } } - if(!cli_is_connected(cli)) { + if(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) { printf("\r\n"); chat_event.event = SubGhzChatEventUserExit; subghz_chat_worker_put_event_chat(subghz_chat, &chat_event); @@ -1113,7 +1116,7 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args) { printf("\r\nExit chat\r\n"); } -static void subghz_cli_command(Cli* cli, FuriString* args, void* context) { +static void subghz_cli_command(PipeSide* pipe, FuriString* args, void* context) { FuriString* cmd; 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) { - subghz_cli_command_chat(cli, args); + subghz_cli_command_chat(pipe, args); break; } if(furi_string_cmp_str(cmd, "tx") == 0) { - subghz_cli_command_tx(cli, args, context); + subghz_cli_command_tx(pipe, args, context); break; } if(furi_string_cmp_str(cmd, "rx") == 0) { - subghz_cli_command_rx(cli, args, context); + subghz_cli_command_rx(pipe, args, context); break; } if(furi_string_cmp_str(cmd, "rx_raw") == 0) { - subghz_cli_command_rx_raw(cli, args, context); + subghz_cli_command_rx_raw(pipe, args, context); break; } if(furi_string_cmp_str(cmd, "decode_raw") == 0) { - subghz_cli_command_decode_raw(cli, args, context); + subghz_cli_command_decode_raw(pipe, args, context); break; } if(furi_string_cmp_str(cmd, "tx_from_file") == 0) { - subghz_cli_command_tx_from_file(cli, args, context); + subghz_cli_command_tx_from_file(pipe, args, context); break; } if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { if(furi_string_cmp_str(cmd, "encrypt_keeloq") == 0) { - subghz_cli_command_encrypt_keeloq(cli, args); + subghz_cli_command_encrypt_keeloq(pipe, args); break; } if(furi_string_cmp_str(cmd, "encrypt_raw") == 0) { - subghz_cli_command_encrypt_raw(cli, args); + subghz_cli_command_encrypt_raw(pipe, args); break; } if(furi_string_cmp_str(cmd, "tx_carrier") == 0) { - subghz_cli_command_tx_carrier(cli, args, context); + subghz_cli_command_tx_carrier(pipe, args, context); break; } if(furi_string_cmp_str(cmd, "rx_carrier") == 0) { - subghz_cli_command_rx_carrier(cli, args, context); + subghz_cli_command_rx_carrier(pipe, args, context); break; } } diff --git a/applications/services/application.fam b/applications/services/application.fam index 9ffb26dd6f..a1a0429fa1 100644 --- a/applications/services/application.fam +++ b/applications/services/application.fam @@ -3,6 +3,7 @@ App( name="Basic services", apptype=FlipperAppType.METAPACKAGE, provides=[ + "cli_vcp", "crypto_start", "rpc_start", "expansion_start", diff --git a/applications/services/bt/bt_cli.c b/applications/services/bt/bt_cli.c index 7505c424dd..9ef3ef8a26 100644 --- a/applications/services/bt/bt_cli.c +++ b/applications/services/bt/bt_cli.c @@ -2,14 +2,15 @@ #include #include #include +#include #include #include "bt_settings.h" #include "bt_service/bt.h" #include -static void bt_cli_command_hci_info(Cli* cli, FuriString* args, void* context) { - UNUSED(cli); +static void bt_cli_command_hci_info(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); UNUSED(args); UNUSED(context); FuriString* buffer; @@ -19,7 +20,7 @@ static void bt_cli_command_hci_info(Cli* cli, FuriString* args, void* context) { furi_string_free(buffer); } -static void bt_cli_command_carrier_tx(Cli* cli, FuriString* args, void* context) { +static void bt_cli_command_carrier_tx(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); int channel = 0; int power = 0; @@ -41,7 +42,7 @@ static void bt_cli_command_carrier_tx(Cli* cli, FuriString* args, void* context) printf("Press CTRL+C to stop\r\n"); furi_hal_bt_start_tone_tx(channel, 0x19 + power); - while(!cli_cmd_interrupt_received(cli)) { + while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) { furi_delay_ms(250); } furi_hal_bt_stop_tone_tx(); @@ -51,7 +52,7 @@ static void bt_cli_command_carrier_tx(Cli* cli, FuriString* args, void* context) } while(false); } -static void bt_cli_command_carrier_rx(Cli* cli, FuriString* args, void* context) { +static void bt_cli_command_carrier_rx(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); int channel = 0; @@ -69,7 +70,7 @@ static void bt_cli_command_carrier_rx(Cli* cli, FuriString* args, void* context) furi_hal_bt_start_packet_rx(channel, 1); - while(!cli_cmd_interrupt_received(cli)) { + while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) { furi_delay_ms(250); printf("RSSI: %6.1f dB\r", (double)furi_hal_bt_get_rssi()); fflush(stdout); @@ -82,7 +83,7 @@ static void bt_cli_command_carrier_rx(Cli* cli, FuriString* args, void* context) } while(false); } -static void bt_cli_command_packet_tx(Cli* cli, FuriString* args, void* context) { +static void bt_cli_command_packet_tx(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); int channel = 0; int pattern = 0; @@ -119,7 +120,7 @@ static void bt_cli_command_packet_tx(Cli* cli, FuriString* args, void* context) printf("Press CTRL+C to stop\r\n"); furi_hal_bt_start_packet_tx(channel, pattern, datarate); - while(!cli_cmd_interrupt_received(cli)) { + while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) { furi_delay_ms(250); } furi_hal_bt_stop_packet_test(); @@ -130,7 +131,7 @@ static void bt_cli_command_packet_tx(Cli* cli, FuriString* args, void* context) } while(false); } -static void bt_cli_command_packet_rx(Cli* cli, FuriString* args, void* context) { +static void bt_cli_command_packet_rx(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); int channel = 0; int datarate = 1; @@ -152,7 +153,7 @@ static void bt_cli_command_packet_rx(Cli* cli, FuriString* args, void* context) printf("Press CTRL+C to stop\r\n"); furi_hal_bt_start_packet_rx(channel, datarate); - while(!cli_cmd_interrupt_received(cli)) { + while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) { furi_delay_ms(250); printf("RSSI: %03.1f dB\r", (double)furi_hal_bt_get_rssi()); fflush(stdout); @@ -179,7 +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); furi_record_open(RECORD_BT); @@ -194,24 +195,24 @@ static void bt_cli(Cli* cli, FuriString* args, void* context) { break; } if(furi_string_cmp_str(cmd, "hci_info") == 0) { - bt_cli_command_hci_info(cli, args, NULL); + bt_cli_command_hci_info(pipe, args, NULL); break; } if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug) && furi_hal_bt_is_testing_supported()) { if(furi_string_cmp_str(cmd, "tx_carrier") == 0) { - bt_cli_command_carrier_tx(cli, args, NULL); + bt_cli_command_carrier_tx(pipe, args, NULL); break; } if(furi_string_cmp_str(cmd, "rx_carrier") == 0) { - bt_cli_command_carrier_rx(cli, args, NULL); + bt_cli_command_carrier_rx(pipe, args, NULL); break; } if(furi_string_cmp_str(cmd, "tx_packet") == 0) { - bt_cli_command_packet_tx(cli, args, NULL); + bt_cli_command_packet_tx(pipe, args, NULL); break; } if(furi_string_cmp_str(cmd, "rx_packet") == 0) { - bt_cli_command_packet_rx(cli, args, NULL); + bt_cli_command_packet_rx(pipe, args, NULL); break; } } diff --git a/applications/services/cli/application.fam b/applications/services/cli/application.fam index 7a57bb6076..60af336e59 100644 --- a/applications/services/cli/application.fam +++ b/applications/services/cli/application.fam @@ -1,10 +1,34 @@ App( appid="cli", - name="CliSrv", - apptype=FlipperAppType.SERVICE, - entry_point="cli_srv", + apptype=FlipperAppType.STARTUP, + entry_point="cli_on_system_start", cdefines=["SRV_CLI"], - stack_size=4 * 1024, - order=30, - sdk_headers=["cli.h", "cli_vcp.h"], + sources=[ + "cli.c", + "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"], ) diff --git a/applications/services/cli/cli.c b/applications/services/cli/cli.c index c2a0b9cb1b..b517156606 100644 --- a/applications/services/cli/cli.c +++ b/applications/services/cli/cli.c @@ -1,406 +1,57 @@ +#include "cli.h" #include "cli_i.h" #include "cli_commands.h" -#include "cli_vcp.h" -#include -#include +#include "cli_ansi.h" +#include -#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 = malloc(sizeof(Cli)); - CliCommandTree_init(cli->commands); - - cli->last_line = furi_string_alloc(); - cli->line = furi_string_alloc(); - - cli->session = NULL; - cli->mutex = furi_mutex_alloc(FuriMutexTypeNormal); - - cli->idle_sem = furi_semaphore_alloc(1, 0); - return cli; } -void cli_putc(Cli* cli, char c) { - furi_check(cli); - if(cli->session != NULL) { - cli->session->tx((uint8_t*)&c, 1); - } -} - -char cli_getc(Cli* cli) { - furi_check(cli); - char c = 0; - if(cli->session != NULL) { - if(cli->session->rx((uint8_t*)&c, 1, FuriWaitForever) == 0) { - cli_reset(cli); - furi_delay_tick(10); - } - } else { - cli_reset(cli); - furi_delay_tick(10); - } - return c; -} - -void cli_write(Cli* cli, const uint8_t* buffer, size_t size) { - furi_check(cli); - if(cli->session != NULL) { - cli->session->tx(buffer, size); - } -} - -size_t cli_read(Cli* cli, uint8_t* buffer, size_t size) { - furi_check(cli); - if(cli->session != NULL) { - return cli->session->rx(buffer, size, FuriWaitForever); - } else { - return 0; - } -} - -size_t cli_read_timeout(Cli* cli, uint8_t* buffer, size_t size, uint32_t timeout) { - furi_check(cli); - if(cli->session != NULL) { - return cli->session->rx(buffer, size, timeout); - } else { - return 0; - } -} - -bool cli_is_connected(Cli* cli) { - furi_check(cli); - if(cli->session != NULL) { - return cli->session->is_connected(); - } - return false; -} - -bool cli_cmd_interrupt_received(Cli* cli) { - furi_check(cli); - char c = '\0'; - if(cli_is_connected(cli)) { - if(cli->session->rx((uint8_t*)&c, 1, 0) == 1) { - return c == CliSymbolAsciiETX; - } - } else { - return true; - } - return false; -} - -void cli_print_usage(const char* cmd, const char* usage, const char* arg) { - furi_check(cmd); - furi_check(arg); - furi_check(usage); - - printf("%s: illegal option -- %s\r\nusage: %s %s", cmd, arg, cmd, usage); -} - -void cli_motd(void) { - printf("\r\n" - " _.-------.._ -,\r\n" - " .-\"```\"--..,,_/ /`-, -, \\ \r\n" - " .:\" /:/ /'\\ \\ ,_..., `. | |\r\n" - " / ,----/:/ /`\\ _\\~`_-\"` _;\r\n" - " ' / /`\"\"\"'\\ \\ \\.~`_-' ,-\"'/ \r\n" - " | | | 0 | | .-' ,/` /\r\n" - " | ,..\\ \\ ,.-\"` ,/` /\r\n" - " ; : `/`\"\"\\` ,/--==,/-----,\r\n" - " | `-...| -.___-Z:_______J...---;\r\n" - " : ` _-'\r\n" - " _L_ _ ___ ___ ___ ___ ____--\"`___ _ ___\r\n" - "| __|| | |_ _|| _ \\| _ \\| __|| _ \\ / __|| | |_ _|\r\n" - "| _| | |__ | | | _/| _/| _| | / | (__ | |__ | |\r\n" - "|_| |____||___||_| |_| |___||_|_\\ \\___||____||___|\r\n" - "\r\n" - "Welcome to Flipper Zero Command Line Interface!\r\n" - "Read the manual: https://docs.flipper.net/development/cli\r\n" - "Run `help` or `?` to list available commands\r\n" - "\r\n"); - - const Version* firmware_version = furi_hal_version_get_firmware_version(); - if(firmware_version) { - printf( - "Firmware version: %s %s (%s%s built on %s)\r\n", - version_get_gitbranch(firmware_version), - version_get_version(firmware_version), - version_get_githash(firmware_version), - version_get_dirty_flag(firmware_version) ? "-dirty" : "", - version_get_builddate(firmware_version)); - } -} - -void cli_nl(Cli* cli) { - UNUSED(cli); - printf("\r\n"); -} - -void cli_prompt(Cli* cli) { - UNUSED(cli); - printf("\r\n>: %s", furi_string_get_cstr(cli->line)); - fflush(stdout); -} - -void cli_reset(Cli* cli) { - // cli->last_line is cleared and cli->line's buffer moved to cli->last_line - furi_string_move(cli->last_line, cli->line); - // Reiniting cli->line - cli->line = furi_string_alloc(); - cli->cursor_position = 0; -} - -static void cli_handle_backspace(Cli* cli) { - if(cli->cursor_position > 0) { - furi_assert(furi_string_size(cli->line) > 0); - // Other side - printf("\e[D\e[1P"); - fflush(stdout); - // Our side - furi_string_replace_at(cli->line, cli->cursor_position - 1, 1, ""); - - cli->cursor_position--; - } else { - cli_putc(cli, CliSymbolAsciiBell); - } -} - -static void cli_normalize_line(Cli* cli) { - furi_string_trim(cli->line); - cli->cursor_position = furi_string_size(cli->line); -} - -static void cli_execute_command(Cli* cli, CliCommand* command, FuriString* args) { - if(!(command->flags & CliCommandFlagInsomniaSafe)) { - furi_hal_power_insomnia_enter(); - } - - // Ensure that we running alone - if(!(command->flags & CliCommandFlagParallelSafe)) { - Loader* loader = furi_record_open(RECORD_LOADER); - bool safety_lock = loader_lock(loader); - if(safety_lock) { - // Execute command - command->callback(cli, args, command->context); - loader_unlock(loader); - } else { - printf("Other application is running, close it first"); - } - furi_record_close(RECORD_LOADER); - } else { - // Execute command - command->callback(cli, args, command->context); - } - - if(!(command->flags & CliCommandFlagInsomniaSafe)) { - furi_hal_power_insomnia_exit(); - } -} - -static void cli_handle_enter(Cli* cli) { - cli_normalize_line(cli); - - if(furi_string_size(cli->line) == 0) { - cli_prompt(cli); - return; - } - - // Command and args container - FuriString* command; - command = furi_string_alloc(); - FuriString* args; - args = furi_string_alloc(); - - // Split command and args - size_t ws = furi_string_search_char(cli->line, ' '); - if(ws == FURI_STRING_FAILURE) { - furi_string_set(command, cli->line); - } else { - furi_string_set_n(command, cli->line, 0, ws); - furi_string_set_n(args, cli->line, ws, furi_string_size(cli->line)); - furi_string_trim(args); - } - - // Search for command - furi_check(furi_mutex_acquire(cli->mutex, FuriWaitForever) == FuriStatusOk); - CliCommand* cli_command_ptr = CliCommandTree_get(cli->commands, command); - - if(cli_command_ptr) { //-V547 - CliCommand cli_command; - memcpy(&cli_command, cli_command_ptr, sizeof(CliCommand)); - furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk); - cli_nl(cli); - cli_execute_command(cli, &cli_command, args); - } else { - furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk); - cli_nl(cli); - printf( - "`%s` command not found, use `help` or `?` to list all available commands", - furi_string_get_cstr(command)); - cli_putc(cli, CliSymbolAsciiBell); - } - - cli_reset(cli); - cli_prompt(cli); - - // Cleanup command and args - furi_string_free(command); - furi_string_free(args); -} - -static void cli_handle_autocomplete(Cli* cli) { - cli_normalize_line(cli); - - if(furi_string_size(cli->line) == 0) { - return; - } - - cli_nl(cli); - - // Prepare common base for autocomplete - FuriString* common; - common = furi_string_alloc(); - // Iterate throw commands - for - M_EACH(cli_command, cli->commands, CliCommandTree_t) { - // Process only if starts with line buffer - if(furi_string_start_with(*cli_command->key_ptr, cli->line)) { - // Show autocomplete option - printf("%s\r\n", furi_string_get_cstr(*cli_command->key_ptr)); - // Process common base for autocomplete - if(furi_string_size(common) > 0) { - // Choose shortest string - const size_t key_size = furi_string_size(*cli_command->key_ptr); - const size_t common_size = furi_string_size(common); - const size_t min_size = key_size > common_size ? common_size : key_size; - size_t i = 0; - while(i < min_size) { - // Stop when do not match - if(furi_string_get_char(*cli_command->key_ptr, i) != - furi_string_get_char(common, i)) { - break; - } - i++; - } - // Cut right part if any - furi_string_left(common, i); - } else { - // Start with something - furi_string_set(common, *cli_command->key_ptr); - } - } - } - // Replace line buffer if autocomplete better - if(furi_string_size(common) > furi_string_size(cli->line)) { - furi_string_set(cli->line, common); - cli->cursor_position = furi_string_size(cli->line); - } - // Cleanup - furi_string_free(common); - // Show prompt - cli_prompt(cli); -} - -static void cli_handle_escape(Cli* cli, char c) { - if(c == 'A') { - // Use previous command if line buffer is empty - if(furi_string_size(cli->line) == 0 && furi_string_cmp(cli->line, cli->last_line) != 0) { - // Set line buffer and cursor position - furi_string_set(cli->line, cli->last_line); - cli->cursor_position = furi_string_size(cli->line); - // Show new line to user - printf("%s", furi_string_get_cstr(cli->line)); - } - } else if(c == 'B') { - } else if(c == 'C') { - if(cli->cursor_position < furi_string_size(cli->line)) { - cli->cursor_position++; - printf("\e[C"); - } - } else if(c == 'D') { - if(cli->cursor_position > 0) { - cli->cursor_position--; - printf("\e[D"); - } - } - fflush(stdout); -} - -void cli_process_input(Cli* cli) { - char in_chr = cli_getc(cli); - size_t rx_len; - - if(in_chr == CliSymbolAsciiTab) { - cli_handle_autocomplete(cli); - } else if(in_chr == CliSymbolAsciiSOH) { - furi_delay_ms(33); // We are too fast, Minicom is not ready yet - cli_motd(); - cli_prompt(cli); - } else if(in_chr == CliSymbolAsciiETX) { - cli_reset(cli); - cli_prompt(cli); - } else if(in_chr == CliSymbolAsciiEOT) { - cli_reset(cli); - } else if(in_chr == CliSymbolAsciiEsc) { - rx_len = cli_read(cli, (uint8_t*)&in_chr, 1); - if((rx_len > 0) && (in_chr == '[')) { - cli_read(cli, (uint8_t*)&in_chr, 1); - cli_handle_escape(cli, in_chr); - } else { - cli_putc(cli, CliSymbolAsciiBell); - } - } else if(in_chr == CliSymbolAsciiBackspace || in_chr == CliSymbolAsciiDel) { - cli_handle_backspace(cli); - } else if(in_chr == CliSymbolAsciiCR) { - cli_handle_enter(cli); - } else if( - (in_chr >= 0x20 && in_chr < 0x7F) && //-V560 - (furi_string_size(cli->line) < CLI_INPUT_LEN_LIMIT)) { - if(cli->cursor_position == furi_string_size(cli->line)) { - furi_string_push_back(cli->line, in_chr); - cli_putc(cli, in_chr); - } else { - // Insert character to line buffer - const char in_str[2] = {in_chr, 0}; - furi_string_replace_at(cli->line, cli->cursor_position, 0, in_str); - - // Print character in replace mode - printf("\e[4h%c\e[4l", in_chr); - fflush(stdout); - } - cli->cursor_position++; - } else { - cli_putc(cli, CliSymbolAsciiBell); - } -} - void cli_add_command( Cli* cli, const char* name, CliCommandFlag flags, - CliCallback callback, + CliExecuteCallback callback, 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(name); + furi_check(callback); + FuriString* name_str; 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; - do { - name_replace = furi_string_replace(name_str, " ", "_"); - } while(name_replace != FURI_STRING_FAILURE); - - CliCommand c; - c.callback = callback; - c.context = context; - c.flags = flags; + CliCommand command = { + .context = context, + .execute_callback = callback, + .flags = flags, + .stack_depth = stack_size, + }; 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_string_free(name_str); @@ -424,61 +75,49 @@ void cli_delete_command(Cli* cli, const char* name) { furi_string_free(name_str); } -void cli_session_open(Cli* cli, const void* session) { - furi_check(cli); - +bool cli_get_command(Cli* cli, FuriString* command, CliCommand* result) { + furi_assert(cli); furi_check(furi_mutex_acquire(cli->mutex, FuriWaitForever) == FuriStatusOk); - cli->session = session; - if(cli->session != NULL) { - cli->session->init(); - furi_thread_set_stdout_callback(cli->session->tx_stdout, NULL); - } else { - furi_thread_set_stdout_callback(NULL, NULL); - } - furi_semaphore_release(cli->idle_sem); + CliCommand* data = CliCommandTree_get(cli->commands, command); + if(data) *result = *data; + furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk); + + return !!data; } -void cli_session_close(Cli* cli) { - furi_check(cli); - +void cli_lock_commands(Cli* cli) { + furi_assert(cli); furi_check(furi_mutex_acquire(cli->mutex, FuriWaitForever) == FuriStatusOk); - if(cli->session != NULL) { - cli->session->deinit(); - } - cli->session = NULL; - furi_thread_set_stdout_callback(NULL, NULL); - furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk); } -int32_t cli_srv(void* p) { - UNUSED(p); +void cli_unlock_commands(Cli* cli) { + 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(); - - // Init basic cli commands cli_commands_init(cli); - furi_record_create(RECORD_CLI, cli); - - if(cli->session != NULL) { - furi_thread_set_stdout_callback(cli->session->tx_stdout, NULL); - } else { - furi_thread_set_stdout_callback(NULL, NULL); - } - - if(furi_hal_rtc_get_boot_mode() == FuriHalRtcBootModeNormal) { - cli_session_open(cli, &cli_vcp); - } else { - FURI_LOG_W(TAG, "Skipping start in special boot mode"); - } - - while(1) { - if(cli->session != NULL) { - cli_process_input(cli); - } else { - furi_check(furi_semaphore_acquire(cli->idle_sem, FuriWaitForever) == FuriStatusOk); - } - } - - return 0; } diff --git a/applications/services/cli/cli.h b/applications/services/cli/cli.h index fe8f090325..211e89d88f 100644 --- a/applications/services/cli/cli.h +++ b/applications/services/cli/cli.h @@ -1,64 +1,102 @@ /** * @file cli.h - * Cli API + * API for registering commands with the CLI */ #pragma once #include +#include +#include "cli_ansi.h" +#include #ifdef __cplusplus extern "C" { #endif -typedef enum { - CliSymbolAsciiSOH = 0x01, - CliSymbolAsciiETX = 0x03, - CliSymbolAsciiEOT = 0x04, - CliSymbolAsciiBell = 0x07, - CliSymbolAsciiBackspace = 0x08, - CliSymbolAsciiTab = 0x09, - CliSymbolAsciiLF = 0x0A, - CliSymbolAsciiCR = 0x0D, - CliSymbolAsciiEsc = 0x1B, - CliSymbolAsciiUS = 0x1F, - CliSymbolAsciiSpace = 0x20, - CliSymbolAsciiDel = 0x7F, -} CliSymbols; - -typedef enum { - CliCommandFlagDefault = 0, /**< Default, loader lock is used */ - CliCommandFlagParallelSafe = - (1 << 0), /**< Safe to run in parallel with other apps, loader lock is not used */ - CliCommandFlagInsomniaSafe = (1 << 1), /**< Safe to run with insomnia mode on */ -} CliCommandFlag; - #define RECORD_CLI "cli" +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 */ typedef struct Cli Cli; -/** Cli callback function pointer. Implement this interface and use - * add_cli_command - * @param args string with what was passed after command - * @param context pointer to whatever you gave us on cli_add_command +/** + * @brief CLI execution callback pointer. Implement this interface and use + * `add_cli_command`. + * + * This callback will be called from a separate thread spawned just for your + * command. The pipe will be installed as the thread's stdio, so you can use + * `printf`, `getchar` and other standard functions to communicate with the + * user. + * + * @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 (*CliCallback)(Cli* cli, FuriString* args, void* context); +typedef void (*CliExecuteCallback)(PipeSide* pipe, FuriString* args, void* context); -/** Add cli command Registers you command callback +/** + * @brief Registers a command with the CLI. Provides less options than the `_ex` + * counterpart. * - * @param cli pointer to cli instance - * @param name command name - * @param flags CliCommandFlag - * @param callback callback function - * @param context pointer to whatever we need to pass to callback + * @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( Cli* cli, const char* name, CliCommandFlag flags, - CliCallback callback, + CliExecuteCallback callback, 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 * * @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); -/** Delete cli command - * - * @param cli pointer to cli instance - * @param name command name - */ -void cli_delete_command(Cli* cli, const char* name); - -/** Read from terminal - * - * @param cli Cli instance - * @param buffer pointer to buffer - * @param size size of buffer in bytes - * - * @return bytes read - */ -size_t cli_read(Cli* cli, uint8_t* buffer, size_t size); - -/** Non-blocking read from terminal - * - * @param cli Cli instance - * @param buffer pointer to buffer - * @param size size of buffer in bytes - * @param timeout timeout value in ms - * - * @return bytes read - */ -size_t cli_read_timeout(Cli* cli, uint8_t* buffer, size_t size, uint32_t timeout); - -/** Non-blocking check for interrupt command received - * - * @param cli Cli instance - * - * @return true if received - */ -bool cli_cmd_interrupt_received(Cli* cli); - -/** Write to terminal Do it only from inside of cli call. - * - * @param cli Cli instance - * @param buffer pointer to buffer - * @param size size of buffer in bytes - */ -void cli_write(Cli* cli, const uint8_t* buffer, size_t size); - -/** Read character - * - * @param cli Cli instance - * - * @return char - */ -char cli_getc(Cli* cli); - -/** New line Send new ine sequence - */ -void cli_nl(Cli* cli); - -void cli_session_open(Cli* cli, const void* session); - -void cli_session_close(Cli* cli); - -bool cli_is_connected(Cli* cli); - #ifdef __cplusplus } #endif diff --git a/applications/services/cli/cli_ansi.c b/applications/services/cli/cli_ansi.c new file mode 100644 index 0000000000..81167643b4 --- /dev/null +++ b/applications/services/cli/cli_ansi.c @@ -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: + // -> + if(c != CliKeyEsc) PARSER_RESET_AND_RETURN(parser, CliModKeyNo, c); // -V1048 + + // ... + parser->state = CliAnsiParserStateEscape; + break; + + case CliAnsiParserStateEscape: + // -> + if(c == CliKeyEsc) PARSER_RESET_AND_RETURN(parser, CliModKeyNo, c); + + // -> Alt + + if(c != '[') PARSER_RESET_AND_RETURN(parser, CliModKeyAlt, c); + + // [ ... + parser->state = CliAnsiParserStateEscapeBrace; + break; + + case CliAnsiParserStateEscapeBrace: + // [ -> + if(c != '1') PARSER_RESET_AND_RETURN(parser, CliModKeyNo, cli_ansi_key_from_mnemonic(c)); + + // [ 1 ... + parser->state = CliAnsiParserStateEscapeBraceOne; + break; + + case CliAnsiParserStateEscapeBraceOne: + // [ 1 -> error + if(c != ';') PARSER_RESET_AND_RETURN(parser, CliModKeyNo, CliKeyUnrecognized); + + // [ 1 ; ... + parser->state = CliAnsiParserStateEscapeBraceOneSemicolon; + break; + + case CliAnsiParserStateEscapeBraceOneSemicolon: + // [ 1 ; ... + parser->modifiers = (c - '0'); + parser->modifiers &= ~1; + parser->state = CliAnsiParserStateEscapeBraceOneSemicolonModifiers; + break; + + case CliAnsiParserStateEscapeBraceOneSemicolonModifiers: + // [ 1 ; -> + + 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; +} diff --git a/applications/services/cli/cli_ansi.h b/applications/services/cli/cli_ansi.h new file mode 100644 index 0000000000..20bf33d8e8 --- /dev/null +++ b/applications/services/cli/cli_ansi.h @@ -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 diff --git a/applications/services/cli/cli_command_gpio.c b/applications/services/cli/cli_command_gpio.c index 010a7dfbe5..f37e6387fb 100644 --- a/applications/services/cli/cli_command_gpio.c +++ b/applications/services/cli/cli_command_gpio.c @@ -3,6 +3,7 @@ #include #include #include +#include void cli_command_gpio_print_usage(void) { printf("Usage:\r\n"); @@ -70,8 +71,8 @@ static GpioParseReturn gpio_command_parse(FuriString* args, size_t* pin_num, uin return ret; } -void cli_command_gpio_mode(Cli* cli, FuriString* args, void* context) { - UNUSED(cli); +void cli_command_gpio_mode(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); UNUSED(context); size_t num = 0; @@ -93,7 +94,7 @@ void cli_command_gpio_mode(Cli* cli, FuriString* args, void* context) { if(gpio_pins[num].debug) { //-V779 printf( "Changing this pin mode may damage hardware. Are you sure you want to continue? (y/n)?\r\n"); - char c = cli_getc(cli); + char c = getchar(); if(c != 'y' && c != 'Y') { printf("Cancelled.\r\n"); return; @@ -110,8 +111,8 @@ void cli_command_gpio_mode(Cli* cli, FuriString* args, void* context) { } } -void cli_command_gpio_read(Cli* cli, FuriString* args, void* context) { - UNUSED(cli); +void cli_command_gpio_read(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); UNUSED(context); size_t num = 0; @@ -131,7 +132,8 @@ void cli_command_gpio_read(Cli* cli, FuriString* args, void* context) { printf("Pin %s <= %u", gpio_pins[num].name, val); } -void cli_command_gpio_set(Cli* cli, FuriString* args, void* context) { +void cli_command_gpio_set(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); UNUSED(context); size_t num = 0; @@ -159,7 +161,7 @@ void cli_command_gpio_set(Cli* cli, FuriString* args, void* context) { if(gpio_pins[num].debug) { printf( "Setting this pin may damage hardware. Are you sure you want to continue? (y/n)?\r\n"); - char c = cli_getc(cli); + char c = getchar(); if(c != 'y' && c != 'Y') { printf("Cancelled.\r\n"); return; @@ -170,7 +172,7 @@ void cli_command_gpio_set(Cli* cli, FuriString* args, void* context) { printf("Pin %s => %u", gpio_pins[num].name, !!value); } -void cli_command_gpio(Cli* cli, FuriString* args, void* context) { +void cli_command_gpio(PipeSide* pipe, FuriString* args, void* context) { FuriString* cmd; cmd = furi_string_alloc(); @@ -181,17 +183,17 @@ void cli_command_gpio(Cli* cli, FuriString* args, void* context) { } if(furi_string_cmp_str(cmd, "mode") == 0) { - cli_command_gpio_mode(cli, args, context); + cli_command_gpio_mode(pipe, args, context); break; } if(furi_string_cmp_str(cmd, "set") == 0) { - cli_command_gpio_set(cli, args, context); + cli_command_gpio_set(pipe, args, context); break; } if(furi_string_cmp_str(cmd, "read") == 0) { - cli_command_gpio_read(cli, args, context); + cli_command_gpio_read(pipe, args, context); break; } diff --git a/applications/services/cli/cli_command_gpio.h b/applications/services/cli/cli_command_gpio.h index 7ae5aa6259..0290949e04 100644 --- a/applications/services/cli/cli_command_gpio.h +++ b/applications/services/cli/cli_command_gpio.h @@ -1,5 +1,6 @@ #pragma once #include "cli_i.h" +#include -void cli_command_gpio(Cli* cli, FuriString* args, void* context); +void cli_command_gpio(PipeSide* pipe, FuriString* args, void* context); diff --git a/applications/services/cli/cli_commands.c b/applications/services/cli/cli_commands.c index e4503b2746..24917afa9e 100644 --- a/applications/services/cli/cli_commands.c +++ b/applications/services/cli/cli_commands.c @@ -1,5 +1,7 @@ #include "cli_commands.h" #include "cli_command_gpio.h" +#include "cli_ansi.h" +#include "cli.h" #include #include @@ -11,6 +13,7 @@ #include #include #include +#include // Close to ISO, `date +'%Y-%m-%d %H:%M:%S %u'` #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 context The context */ -void cli_command_info(Cli* cli, FuriString* args, void* context) { - UNUSED(cli); +void cli_command_info(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); if(context) { furi_hal_info_get(cli_command_info_callback, '_', NULL); @@ -53,56 +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(context); - printf("Commands available:"); + printf("Available commands:" ANSI_FG_GREEN); - // Command count - const size_t commands_count = CliCommandTree_size(cli->commands); - const size_t commands_count_mid = commands_count / 2 + commands_count % 2; + // count non-hidden commands + Cli* cli = furi_record_open(RECORD_CLI); + 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 - CliCommandTree_it_t it_left; - CliCommandTree_it(it_left, cli->commands); - CliCommandTree_it_t it_right; - CliCommandTree_it(it_right, cli->commands); - for(size_t i = 0; i < commands_count_mid; i++) - CliCommandTree_next(it_right); - - // Iterate throw tree - for(size_t i = 0; i < commands_count_mid; i++) { - printf("\r\n"); - // Left Column - if(!CliCommandTree_end_p(it_left)) { - printf("%-30s", furi_string_get_cstr(*CliCommandTree_ref(it_left)->key_ptr)); - CliCommandTree_next(it_left); - } - // Right Column - if(!CliCommandTree_end_p(it_right)) { - printf("%s", furi_string_get_cstr(*CliCommandTree_ref(it_right)->key_ptr)); - CliCommandTree_next(it_right); - } - }; - - if(furi_string_size(args) > 0) { - cli_nl(cli); - printf("`"); - printf("%s", furi_string_get_cstr(args)); - printf("` command not found"); + // create iterators starting at different positions + const size_t columns = 3; + const size_t commands_per_column = (commands_count / columns) + (commands_count % columns); + CliCommandTree_it_t iterators[columns]; + for(size_t c = 0; c < columns; c++) { + CliCommandTree_it(iterators[c], *commands); + for(size_t i = 0; i < c * commands_per_column; i++) + CliCommandTree_next(iterators[c]); } + + // 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) { - UNUSED(cli); +void cli_command_uptime(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); UNUSED(args); UNUSED(context); uint32_t uptime = furi_get_tick() / furi_kernel_get_tick_frequency(); printf("Uptime: %luh%lum%lus", uptime / 60 / 60, uptime / 60 % 60, uptime % 60); } -void cli_command_date(Cli* cli, FuriString* args, void* context) { - UNUSED(cli); +void cli_command_date(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); UNUSED(context); DateTime datetime = {0}; @@ -174,7 +178,8 @@ void cli_command_date(Cli* cli, FuriString* args, void* context) { #define CLI_COMMAND_LOG_BUFFER_SIZE 64 void cli_command_log_tx_callback(const uint8_t* buffer, size_t size, void* context) { - furi_stream_buffer_send(context, buffer, size, 0); + PipeSide* pipe = context; + pipe_send(pipe, buffer, size); } bool cli_command_log_level_set_from_string(FuriString* level) { @@ -196,16 +201,13 @@ bool cli_command_log_level_set_from_string(FuriString* level) { return false; } -void cli_command_log(Cli* cli, FuriString* args, void* context) { +void cli_command_log(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); - FuriStreamBuffer* ring = furi_stream_buffer_alloc(CLI_COMMAND_LOG_RING_SIZE, 1); - uint8_t buffer[CLI_COMMAND_LOG_BUFFER_SIZE]; FuriLogLevel previous_level = furi_log_get_level(); bool restore_log_level = false; if(furi_string_size(args) > 0) { if(!cli_command_log_level_set_from_string(args)) { - furi_stream_buffer_free(ring); return; } restore_log_level = true; @@ -217,16 +219,15 @@ void cli_command_log(Cli* cli, FuriString* args, void* context) { FuriLogHandler log_handler = { .callback = cli_command_log_tx_callback, - .context = ring, + .context = pipe, }; furi_log_add_handler(log_handler); printf("Use to list available log levels\r\n"); printf("Press CTRL+C to stop...\r\n"); - while(!cli_cmd_interrupt_received(cli)) { - size_t ret = furi_stream_buffer_receive(ring, buffer, CLI_COMMAND_LOG_BUFFER_SIZE, 50); - cli_write(cli, buffer, ret); + while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) { + furi_delay_ms(100); } furi_log_remove_handler(log_handler); @@ -235,12 +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 furi_log_set_level(previous_level); } - - furi_stream_buffer_free(ring); } -void cli_command_sysctl_debug(Cli* cli, FuriString* args, void* context) { - UNUSED(cli); +void cli_command_sysctl_debug(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); UNUSED(context); if(!furi_string_cmp(args, "0")) { furi_hal_rtc_reset_flag(FuriHalRtcFlagDebug); @@ -253,8 +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) { - UNUSED(cli); +void cli_command_sysctl_heap_track(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); UNUSED(context); if(!furi_string_cmp(args, "none")) { furi_hal_rtc_set_heap_track_mode(FuriHalRtcHeapTrackModeNone); @@ -288,7 +287,7 @@ void cli_command_sysctl_print_usage(void) { #endif } -void cli_command_sysctl(Cli* cli, FuriString* args, void* context) { +void cli_command_sysctl(PipeSide* pipe, FuriString* args, void* context) { FuriString* cmd; cmd = furi_string_alloc(); @@ -299,12 +298,12 @@ void cli_command_sysctl(Cli* cli, FuriString* args, void* context) { } if(furi_string_cmp_str(cmd, "debug") == 0) { - cli_command_sysctl_debug(cli, args, context); + cli_command_sysctl_debug(pipe, args, context); break; } if(furi_string_cmp_str(cmd, "heap_track") == 0) { - cli_command_sysctl_heap_track(cli, args, context); + cli_command_sysctl_heap_track(pipe, args, context); break; } @@ -314,8 +313,8 @@ void cli_command_sysctl(Cli* cli, FuriString* args, void* context) { furi_string_free(cmd); } -void cli_command_vibro(Cli* cli, FuriString* args, void* context) { - UNUSED(cli); +void cli_command_vibro(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); UNUSED(context); if(!furi_string_cmp(args, "0")) { @@ -341,8 +340,8 @@ void cli_command_vibro(Cli* cli, FuriString* args, void* context) { } } -void cli_command_led(Cli* cli, FuriString* args, void* context) { - UNUSED(cli); +void cli_command_led(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); UNUSED(context); // Get first word as light name NotificationMessage notification_led_message; @@ -396,23 +395,23 @@ void cli_command_led(Cli* cli, FuriString* args, void* context) { furi_record_close(RECORD_NOTIFICATION); } -static void cli_command_top(Cli* cli, FuriString* args, void* context) { - UNUSED(cli); +static void cli_command_top(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); int interval = 1000; args_read_int_and_trim(args, &interval); FuriThreadList* thread_list = furi_thread_list_alloc(); - while(!cli_cmd_interrupt_received(cli)) { + while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) { uint32_t tick = furi_get_tick(); furi_thread_enumerate(thread_list); - if(interval) printf("\e[2J\e[0;0f"); // Clear display and return to 0 + if(interval) printf(ANSI_CURSOR_POS("1", "1")); uint32_t uptime = tick / furi_kernel_get_tick_frequency(); printf( - "Threads: %zu, ISR Time: %0.2f%%, Uptime: %luh%lum%lus\r\n", + "Threads: %zu, ISR Time: %0.2f%%, Uptime: %luh%lum%lus" ANSI_ERASE_LINE( + ANSI_ERASE_FROM_CURSOR_TO_END) "\r\n", furi_thread_list_size(thread_list), (double)furi_thread_list_get_isr_time(thread_list), uptime / 60 / 60, @@ -420,14 +419,16 @@ static void cli_command_top(Cli* cli, FuriString* args, void* context) { uptime % 60); printf( - "Heap: total %zu, free %zu, minimum %zu, max block %zu\r\n\r\n", + "Heap: total %zu, free %zu, minimum %zu, max block %zu" ANSI_ERASE_LINE( + ANSI_ERASE_FROM_CURSOR_TO_END) "\r\n" ANSI_ERASE_LINE(ANSI_ERASE_FROM_CURSOR_TO_END) "\r\n", memmgr_get_total_heap(), memmgr_get_free_heap(), memmgr_get_minimum_free_heap(), memmgr_heap_get_max_free_block()); printf( - "%-17s %-20s %-10s %5s %12s %6s %10s %7s %5s\r\n", + "%-17s %-20s %-10s %5s %12s %6s %10s %7s %5s" ANSI_ERASE_LINE( + ANSI_ERASE_FROM_CURSOR_TO_END) "\r\n", "AppID", "Name", "State", @@ -436,12 +437,13 @@ static void cli_command_top(Cli* cli, FuriString* args, void* context) { "Stack", "Stack Min", "Heap", - "CPU"); + "%CPU"); for(size_t i = 0; i < furi_thread_list_size(thread_list); i++) { const FuriThreadListItem* item = furi_thread_list_get_at(thread_list, i); printf( - "%-17s %-20s %-10s %5d 0x%08lx %6lu %10lu %7zu %5.1f\r\n", + "%-17s %-20s %-10s %5d 0x%08lx %6lu %10lu %7zu %5.1f" ANSI_ERASE_LINE( + ANSI_ERASE_FROM_CURSOR_TO_END) "\r\n", item->app_id, item->name, item->state, @@ -453,6 +455,9 @@ static void cli_command_top(Cli* cli, FuriString* args, void* context) { (double)item->cpu); } + printf(ANSI_ERASE_DISPLAY(ANSI_ERASE_FROM_CURSOR_TO_END)); + fflush(stdout); + if(interval > 0) { furi_delay_ms(interval); } else { @@ -462,8 +467,8 @@ static void cli_command_top(Cli* cli, FuriString* args, void* context) { furi_thread_list_free(thread_list); } -void cli_command_free(Cli* cli, FuriString* args, void* context) { - UNUSED(cli); +void cli_command_free(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); UNUSED(args); UNUSED(context); @@ -476,16 +481,16 @@ void cli_command_free(Cli* cli, FuriString* args, void* context) { printf("Maximum pool block: %zu\r\n", memmgr_pool_get_max_block()); } -void cli_command_free_blocks(Cli* cli, FuriString* args, void* context) { - UNUSED(cli); +void cli_command_free_blocks(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); UNUSED(args); UNUSED(context); memmgr_heap_printf_free_blocks(); } -void cli_command_i2c(Cli* cli, FuriString* args, void* context) { - UNUSED(cli); +void cli_command_i2c(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); UNUSED(args); UNUSED(context); @@ -507,6 +512,27 @@ void cli_command_i2c(Cli* cli, FuriString* args, void* context) { 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) { cli_add_command(cli, "!", CliCommandFlagParallelSafe, cli_command_info, (void*)true); 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, "free", CliCommandFlagParallelSafe, cli_command_free, 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, "led", CliCommandFlagDefault, cli_command_led, NULL); diff --git a/applications/services/cli/cli_commands.h b/applications/services/cli/cli_commands.h index 184eeb3739..77d9930aff 100644 --- a/applications/services/cli/cli_commands.h +++ b/applications/services/cli/cli_commands.h @@ -1,5 +1,34 @@ #pragma once -#include "cli_i.h" +#include "cli.h" +#include 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; \ + } diff --git a/applications/services/cli/cli_i.h b/applications/services/cli/cli_i.h index ca126dacde..b990e99601 100644 --- a/applications/services/cli/cli_i.h +++ b/applications/services/cli/cli_i.h @@ -1,67 +1,50 @@ +/** + * @file cli_i.h + * Internal API for getting commands registered with the CLI + */ + #pragma once -#include "cli.h" - #include -#include - -#include #include -#include - -#include "cli_vcp.h" - -#define CLI_LINE_SIZE_MAX -#define CLI_COMMANDS_TREE_RANK 4 +#include "cli.h" #ifdef __cplusplus extern "C" { #endif +#define CLI_BUILTIN_COMMAND_STACK_SIZE (3 * 1024U) + typedef struct { - CliCallback callback; - void* context; - uint32_t flags; + void* context; // #include #include +#include +#include #define TAG "CliVcp" -#define USB_CDC_PKT_LEN CDC_DATA_SZ -#define VCP_RX_BUF_SIZE (USB_CDC_PKT_LEN * 3) -#define VCP_TX_BUF_SIZE (USB_CDC_PKT_LEN * 3) +#define USB_CDC_PKT_LEN CDC_DATA_SZ +#define VCP_BUF_SIZE (USB_CDC_PKT_LEN * 3) +#define VCP_IF_NUM 0 +#define VCP_MESSAGE_Q_LEN 8 -#define VCP_IF_NUM 0 - -#ifdef CLI_VCP_DEBUG -#define VCP_DEBUG(...) FURI_LOG_D(TAG, __VA_ARGS__) +#ifdef CLI_VCP_TRACE +#define VCP_TRACE(...) FURI_LOG_T(__VA_ARGS__) #else -#define VCP_DEBUG(...) +#define VCP_TRACE(...) #endif -typedef enum { - VcpEvtStop = (1 << 0), - VcpEvtConnect = (1 << 1), - VcpEvtDisconnect = (1 << 2), - VcpEvtStreamRx = (1 << 3), - VcpEvtRx = (1 << 4), - VcpEvtStreamTx = (1 << 5), - VcpEvtTx = (1 << 6), -} WorkerEvtFlags; - -#define VCP_THREAD_FLAG_ALL \ - (VcpEvtStop | VcpEvtConnect | VcpEvtDisconnect | VcpEvtRx | VcpEvtTx | VcpEvtStreamRx | \ - VcpEvtStreamTx) - typedef struct { - FuriThread* thread; + enum { + CliVcpMessageTypeEnable, + CliVcpMessageTypeDisable, + } type; + union {}; +} CliVcpMessage; - FuriStreamBuffer* tx_stream; - FuriStreamBuffer* rx_stream; +typedef enum { + CliVcpInternalEventConnected = (1 << 0), + CliVcpInternalEventDisconnected = (1 << 1), + CliVcpInternalEventTxDone = (1 << 2), + CliVcpInternalEventRx = (1 << 3), +} CliVcpInternalEvent; - volatile bool connected; - volatile bool running; +#define CliVcpInternalEventAll \ + (CliVcpInternalEventConnected | CliVcpInternalEventDisconnected | CliVcpInternalEventTxDone | \ + CliVcpInternalEventRx) - FuriHalUsbInterface* usb_if_prev; +struct CliVcp { + FuriEventLoop* event_loop; + FuriMessageQueue* message_queue; // is_currently_transmitting) return; + if(!cli_vcp->own_pipe) return; -static void cli_vcp_init(void) { - if(vcp == NULL) { - vcp = malloc(sizeof(CliVcp)); - vcp->tx_stream = furi_stream_buffer_alloc(VCP_TX_BUF_SIZE, 1); - vcp->rx_stream = furi_stream_buffer_alloc(VCP_RX_BUF_SIZE, 1); + uint8_t buf[USB_CDC_PKT_LEN]; + size_t to_receive_from_pipe = MIN(sizeof(buf), pipe_bytes_available(cli_vcp->own_pipe)); + size_t length = pipe_receive(cli_vcp->own_pipe, buf, to_receive_from_pipe); + if(length > 0 || cli_vcp->previous_tx_length == USB_CDC_PKT_LEN) { + VCP_TRACE(TAG, "cdc_send length=%zu", length); + cli_vcp->is_currently_transmitting = true; + furi_hal_cdc_send(VCP_IF_NUM, buf, length); } - furi_assert(vcp->thread == NULL); - - vcp->connected = false; - - vcp->thread = furi_thread_alloc_ex("CliVcpWorker", 1024, vcp_worker, NULL); - furi_thread_start(vcp->thread); - - FURI_LOG_I(TAG, "Init OK"); + cli_vcp->previous_tx_length = length; } -static void cli_vcp_deinit(void) { - furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtStop); - furi_thread_join(vcp->thread); - furi_thread_free(vcp->thread); - vcp->thread = NULL; +/** + * Called in the following cases: + * - new data arrived at the endpoint; + * - data was read out of the pipe. + */ +static void cli_vcp_maybe_receive_data(CliVcp* cli_vcp) { + if(!cli_vcp->own_pipe) return; + if(pipe_spaces_available(cli_vcp->own_pipe) < USB_CDC_PKT_LEN) return; + + uint8_t buf[USB_CDC_PKT_LEN]; + size_t length = furi_hal_cdc_receive(VCP_IF_NUM, buf, sizeof(buf)); + VCP_TRACE(TAG, "cdc_receive length=%zu", length); + furi_check(pipe_send(cli_vcp->own_pipe, buf, length) == length); } -static int32_t vcp_worker(void* context) { - UNUSED(context); - bool tx_idle = true; - size_t missed_rx = 0; - uint8_t last_tx_pkt_len = 0; +// ============= +// CDC callbacks +// ============= - // Switch USB to VCP mode (if it is not set yet) - vcp->usb_if_prev = furi_hal_usb_get_config(); - if((vcp->usb_if_prev != &usb_cdc_single) && (vcp->usb_if_prev != &usb_cdc_dual)) { +static void cli_vcp_signal_internal_event(CliVcp* cli_vcp, CliVcpInternalEvent event) { + furi_thread_flags_set(cli_vcp->thread_id, event); +} + +static void cli_vcp_cdc_tx_done(void* context) { + CliVcp* cli_vcp = context; + cli_vcp_signal_internal_event(cli_vcp, CliVcpInternalEventTxDone); +} + +static void cli_vcp_cdc_rx(void* context) { + CliVcp* cli_vcp = context; + cli_vcp_signal_internal_event(cli_vcp, CliVcpInternalEventRx); +} + +static void cli_vcp_cdc_state_callback(void* context, CdcState state) { + CliVcp* cli_vcp = context; + if(state == CdcStateDisconnected) { + cli_vcp_signal_internal_event(cli_vcp, CliVcpInternalEventDisconnected); + } + // `Connected` events are generated by DTR going active +} + +static void cli_vcp_cdc_ctrl_line_callback(void* context, CdcCtrlLine ctrl_lines) { + CliVcp* cli_vcp = context; + if(ctrl_lines & CdcCtrlLineDTR) { + cli_vcp_signal_internal_event(cli_vcp, CliVcpInternalEventConnected); + } else { + cli_vcp_signal_internal_event(cli_vcp, CliVcpInternalEventDisconnected); + } +} + +static CdcCallbacks cdc_callbacks = { + .tx_ep_callback = cli_vcp_cdc_tx_done, + .rx_ep_callback = cli_vcp_cdc_rx, + .state_callback = cli_vcp_cdc_state_callback, + .ctrl_line_callback = cli_vcp_cdc_ctrl_line_callback, + .config_callback = NULL, +}; + +// ====================== +// Pipe callback handlers +// ====================== + +static void cli_vcp_data_from_shell(PipeSide* pipe, void* context) { + UNUSED(pipe); + CliVcp* cli_vcp = context; + cli_vcp_maybe_send_data(cli_vcp); +} + +static void cli_vcp_shell_ready(PipeSide* pipe, void* context) { + UNUSED(pipe); + CliVcp* cli_vcp = context; + cli_vcp_maybe_receive_data(cli_vcp); +} + +/** + * Processes messages arriving from other threads + */ +static void cli_vcp_message_received(FuriEventLoopObject* object, void* context) { + CliVcp* cli_vcp = context; + CliVcpMessage message; + furi_check(furi_message_queue_get(object, &message, 0) == FuriStatusOk); + + switch(message.type) { + case CliVcpMessageTypeEnable: + if(cli_vcp->is_enabled) return; + FURI_LOG_D(TAG, "Enabling"); + cli_vcp->is_enabled = true; + + // switch usb mode + cli_vcp->previous_interface = furi_hal_usb_get_config(); furi_hal_usb_set_config(&usb_cdc_single, NULL); + furi_hal_cdc_set_callbacks(VCP_IF_NUM, &cdc_callbacks, cli_vcp); + break; + + case CliVcpMessageTypeDisable: + if(!cli_vcp->is_enabled) return; + FURI_LOG_D(TAG, "Disabling"); + cli_vcp->is_enabled = false; + + // restore usb mode + furi_hal_cdc_set_callbacks(VCP_IF_NUM, NULL, NULL); + furi_hal_usb_set_config(cli_vcp->previous_interface, NULL); + break; } - furi_hal_cdc_set_callbacks(VCP_IF_NUM, &cdc_cb, NULL); - - FURI_LOG_D(TAG, "Start"); - vcp->running = true; - - while(1) { - uint32_t flags = - furi_thread_flags_wait(VCP_THREAD_FLAG_ALL, FuriFlagWaitAny, FuriWaitForever); - furi_assert(!(flags & FuriFlagError)); - - // VCP session opened - if(flags & VcpEvtConnect) { - VCP_DEBUG("Connect"); - - if(vcp->connected == false) { - vcp->connected = true; - furi_stream_buffer_send(vcp->rx_stream, &ascii_soh, 1, FuriWaitForever); - } - } - - // VCP session closed - if(flags & VcpEvtDisconnect) { - VCP_DEBUG("Disconnect"); - - if(vcp->connected == true) { - vcp->connected = false; - furi_stream_buffer_receive(vcp->tx_stream, vcp->data_buffer, USB_CDC_PKT_LEN, 0); - furi_stream_buffer_send(vcp->rx_stream, &ascii_eot, 1, FuriWaitForever); - } - } - - // Rx buffer was read, maybe there is enough space for new data? - if((flags & VcpEvtStreamRx) && (missed_rx > 0)) { - VCP_DEBUG("StreamRx"); - - if(furi_stream_buffer_spaces_available(vcp->rx_stream) >= USB_CDC_PKT_LEN) { - flags |= VcpEvtRx; - missed_rx--; - } - } - - // New data received - if(flags & VcpEvtRx) { - if(furi_stream_buffer_spaces_available(vcp->rx_stream) >= USB_CDC_PKT_LEN) { - int32_t len = furi_hal_cdc_receive(VCP_IF_NUM, vcp->data_buffer, USB_CDC_PKT_LEN); - VCP_DEBUG("Rx %ld", len); - - if(len > 0) { - furi_check( - furi_stream_buffer_send( - vcp->rx_stream, vcp->data_buffer, len, FuriWaitForever) == - (size_t)len); - } - } else { - VCP_DEBUG("Rx missed"); - missed_rx++; - } - } - - // New data in Tx buffer - if(flags & VcpEvtStreamTx) { - VCP_DEBUG("StreamTx"); - - if(tx_idle) { - flags |= VcpEvtTx; - } - } - - // CDC write transfer done - if(flags & VcpEvtTx) { - size_t len = - furi_stream_buffer_receive(vcp->tx_stream, vcp->data_buffer, USB_CDC_PKT_LEN, 0); - - VCP_DEBUG("Tx %d", len); - - if(len > 0) { // Some data left in Tx buffer. Sending it now - tx_idle = false; - furi_hal_cdc_send(VCP_IF_NUM, vcp->data_buffer, len); - last_tx_pkt_len = len; - } else { // There is nothing to send. - if(last_tx_pkt_len == 64) { - // Send extra zero-length packet if last packet len is 64 to indicate transfer end - furi_hal_cdc_send(VCP_IF_NUM, NULL, 0); - } else { - // Set flag to start next transfer instantly - tx_idle = true; - } - last_tx_pkt_len = 0; - } - } - - if(flags & VcpEvtStop) { - vcp->connected = false; - vcp->running = false; - furi_hal_cdc_set_callbacks(VCP_IF_NUM, NULL, NULL); - // Restore previous USB mode (if it was set during init) - if((vcp->usb_if_prev != &usb_cdc_single) && (vcp->usb_if_prev != &usb_cdc_dual)) { - furi_hal_usb_unlock(); - furi_hal_usb_set_config(vcp->usb_if_prev, NULL); - } - furi_stream_buffer_receive(vcp->tx_stream, vcp->data_buffer, USB_CDC_PKT_LEN, 0); - furi_stream_buffer_send(vcp->rx_stream, &ascii_eot, 1, FuriWaitForever); - break; - } - } - FURI_LOG_D(TAG, "End"); - return 0; } -static size_t cli_vcp_rx(uint8_t* buffer, size_t size, uint32_t timeout) { - furi_assert(vcp); - furi_assert(buffer); +/** + * Processes messages arriving from CDC event callbacks + */ +static void cli_vcp_internal_event_happened(void* context) { + CliVcp* cli_vcp = context; + CliVcpInternalEvent event = furi_thread_flags_wait(CliVcpInternalEventAll, FuriFlagWaitAny, 0); + furi_check(!(event & FuriFlagError)); - if(vcp->running == false) { + if(event & CliVcpInternalEventDisconnected) { + if(!cli_vcp->is_connected) return; + FURI_LOG_D(TAG, "Disconnected"); + cli_vcp->is_connected = false; + + // disconnect our side of the pipe + pipe_detach_from_event_loop(cli_vcp->own_pipe); + pipe_free(cli_vcp->own_pipe); + cli_vcp->own_pipe = NULL; + } + + if(event & CliVcpInternalEventConnected) { + if(cli_vcp->is_connected) return; + FURI_LOG_D(TAG, "Connected"); + cli_vcp->is_connected = true; + + // wait for previous shell to stop + furi_check(!cli_vcp->own_pipe); + if(cli_vcp->shell) { + 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; } - VCP_DEBUG("rx %u start", size); + CliVcp* cli_vcp = cli_vcp_alloc(); + furi_record_create(RECORD_CLI_VCP, cli_vcp); + furi_event_loop_run(cli_vcp->event_loop); - size_t rx_cnt = 0; - - while(size > 0) { - size_t batch_size = size; - if(batch_size > VCP_RX_BUF_SIZE) batch_size = VCP_RX_BUF_SIZE; - - size_t len = furi_stream_buffer_receive(vcp->rx_stream, buffer, batch_size, timeout); - VCP_DEBUG("rx %u ", batch_size); - - if(len == 0) break; - if(vcp->running == false) { - // EOT command is received after VCP session close - rx_cnt += len; - break; - } - furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtStreamRx); - size -= len; - buffer += len; - rx_cnt += len; - } - - VCP_DEBUG("rx %u end", size); - return rx_cnt; + return 0; } -static size_t cli_vcp_rx_stdin(uint8_t* data, size_t size, uint32_t timeout, void* context) { - UNUSED(context); - return cli_vcp_rx(data, size, timeout); +// ========== +// Public API +// ========== + +void cli_vcp_enable(CliVcp* cli_vcp) { + CliVcpMessage message = { + .type = CliVcpMessageTypeEnable, + }; + furi_message_queue_put(cli_vcp->message_queue, &message, FuriWaitForever); } -static void cli_vcp_tx(const uint8_t* buffer, size_t size) { - furi_assert(vcp); - furi_assert(buffer); - - if(vcp->running == false) { - return; - } - - VCP_DEBUG("tx %u start", size); - - while(size > 0 && vcp->connected) { - size_t batch_size = size; - if(batch_size > USB_CDC_PKT_LEN) batch_size = USB_CDC_PKT_LEN; - - furi_stream_buffer_send(vcp->tx_stream, buffer, batch_size, FuriWaitForever); - furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtStreamTx); - VCP_DEBUG("tx %u", batch_size); - - size -= batch_size; - buffer += batch_size; - } - - VCP_DEBUG("tx %u end", size); +void cli_vcp_disable(CliVcp* cli_vcp) { + CliVcpMessage message = { + .type = CliVcpMessageTypeDisable, + }; + furi_message_queue_put(cli_vcp->message_queue, &message, FuriWaitForever); } - -static void cli_vcp_tx_stdout(const char* data, size_t size, void* context) { - UNUSED(context); - cli_vcp_tx((const uint8_t*)data, size); -} - -static void vcp_state_callback(void* context, uint8_t state) { - UNUSED(context); - if(state == 0) { - furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtDisconnect); - } -} - -static void vcp_on_cdc_control_line(void* context, uint8_t state) { - UNUSED(context); - // bit 0: DTR state, bit 1: RTS state - bool dtr = state & (1 << 0); - - if(dtr == true) { - furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtConnect); - } else { - furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtDisconnect); - } -} - -static void vcp_on_cdc_rx(void* context) { - UNUSED(context); - uint32_t ret = furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtRx); - furi_check(!(ret & FuriFlagError)); -} - -static void vcp_on_cdc_tx_complete(void* context) { - UNUSED(context); - furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtTx); -} - -static bool cli_vcp_is_connected(void) { - furi_assert(vcp); - return vcp->connected; -} - -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, -}; diff --git a/applications/services/cli/cli_vcp.h b/applications/services/cli/cli_vcp.h index f51625342f..10e286183e 100644 --- a/applications/services/cli/cli_vcp.h +++ b/applications/services/cli/cli_vcp.h @@ -9,9 +9,12 @@ extern "C" { #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 } diff --git a/applications/services/cli/shell/cli_shell.c b/applications/services/cli/shell/cli_shell.c new file mode 100644 index 0000000000..62cbbd4036 --- /dev/null +++ b/applications/services/cli/shell/cli_shell.c @@ -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 +#include +#include +#include +#include +#include +#include + +#define TAG "CliShell" + +#define ANSI_TIMEOUT_MS 10 + +typedef enum { + CliShellComponentLine, + CliShellComponentMAX, //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; +} diff --git a/applications/services/cli/shell/cli_shell.h b/applications/services/cli/shell/cli_shell.h new file mode 100644 index 0000000000..e60eefc778 --- /dev/null +++ b/applications/services/cli/shell/cli_shell.h @@ -0,0 +1,16 @@ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define CLI_SHELL_STACK_SIZE (4 * 1024U) + +FuriThread* cli_shell_start(PipeSide* pipe); + +#ifdef __cplusplus +} +#endif diff --git a/applications/services/cli/shell/cli_shell_i.h b/applications/services/cli/shell/cli_shell_i.h new file mode 100644 index 0000000000..e8eae92c6d --- /dev/null +++ b/applications/services/cli/shell/cli_shell_i.h @@ -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 diff --git a/applications/services/cli/shell/cli_shell_line.c b/applications/services/cli/shell/cli_shell_line.c new file mode 100644 index 0000000000..45bc19d9d7 --- /dev/null +++ b/applications/services/cli/shell/cli_shell_line.c @@ -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}, + }, +}; diff --git a/applications/services/cli/shell/cli_shell_line.h b/applications/services/cli/shell/cli_shell_line.h new file mode 100644 index 0000000000..c1c810ee49 --- /dev/null +++ b/applications/services/cli/shell/cli_shell_line.h @@ -0,0 +1,36 @@ +#pragma once + +#include + +#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 diff --git a/applications/services/crypto/crypto_cli.c b/applications/services/crypto/crypto_cli.c index 744fa7151d..bfefaf0f16 100644 --- a/applications/services/crypto/crypto_cli.c +++ b/applications/services/crypto/crypto_cli.c @@ -2,6 +2,7 @@ #include #include +#include #include void crypto_cli_print_usage(void) { @@ -17,7 +18,7 @@ void crypto_cli_print_usage(void) { "\tstore_key \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; bool key_loaded = false; uint8_t iv[16]; @@ -44,15 +45,15 @@ void crypto_cli_encrypt(Cli* cli, FuriString* args) { FuriString* input; input = furi_string_alloc(); char c; - while(cli_read(cli, (uint8_t*)&c, 1) == 1) { - if(c == CliSymbolAsciiETX) { + while(pipe_receive(pipe, (uint8_t*)&c, 1) == 1) { + if(c == CliKeyETX) { printf("\r\n"); break; } else if(c >= 0x20 && c < 0x7F) { putc(c, stdout); fflush(stdout); furi_string_push_back(input, c); - } else if(c == CliSymbolAsciiCR) { + } else if(c == CliKeyCR) { printf("\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; bool key_loaded = false; uint8_t iv[16]; @@ -119,15 +120,15 @@ void crypto_cli_decrypt(Cli* cli, FuriString* args) { FuriString* hex_input; hex_input = furi_string_alloc(); char c; - while(cli_read(cli, (uint8_t*)&c, 1) == 1) { - if(c == CliSymbolAsciiETX) { + while(pipe_receive(pipe, (uint8_t*)&c, 1) == 1) { + if(c == CliKeyETX) { printf("\r\n"); break; } else if(c >= 0x20 && c < 0x7F) { putc(c, stdout); fflush(stdout); furi_string_push_back(hex_input, c); - } else if(c == CliSymbolAsciiCR) { + } else if(c == CliKeyCR) { printf("\r\n"); } } @@ -164,8 +165,8 @@ void crypto_cli_decrypt(Cli* cli, FuriString* args) { } } -void crypto_cli_has_key(Cli* cli, FuriString* args) { - UNUSED(cli); +void crypto_cli_has_key(PipeSide* pipe, FuriString* args) { + UNUSED(pipe); int key_slot = 0; uint8_t iv[16] = {0}; @@ -186,8 +187,8 @@ void crypto_cli_has_key(Cli* cli, FuriString* args) { } while(0); } -void crypto_cli_store_key(Cli* cli, FuriString* args) { - UNUSED(cli); +void crypto_cli_store_key(PipeSide* pipe, FuriString* args) { + UNUSED(pipe); int key_slot = 0; int key_size = 0; FuriString* key_type; @@ -279,7 +280,7 @@ void crypto_cli_store_key(Cli* cli, FuriString* args) { 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); FuriString* cmd; 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) { - crypto_cli_encrypt(cli, args); + crypto_cli_encrypt(pipe, args); break; } if(furi_string_cmp_str(cmd, "decrypt") == 0) { - crypto_cli_decrypt(cli, args); + crypto_cli_decrypt(pipe, args); break; } if(furi_string_cmp_str(cmd, "has_key") == 0) { - crypto_cli_has_key(cli, args); + crypto_cli_has_key(pipe, args); break; } if(furi_string_cmp_str(cmd, "store_key") == 0) { - crypto_cli_store_key(cli, args); + crypto_cli_store_key(pipe, args); break; } diff --git a/applications/services/desktop/desktop.c b/applications/services/desktop/desktop.c index 1132760d55..185fb9c3b5 100644 --- a/applications/services/desktop/desktop.c +++ b/applications/services/desktop/desktop.c @@ -396,8 +396,8 @@ void desktop_lock(Desktop* desktop) { furi_hal_rtc_set_flag(FuriHalRtcFlagLock); if(desktop_pin_code_is_set()) { - Cli* cli = furi_record_open(RECORD_CLI); - cli_session_close(cli); + CliVcp* cli_vcp = furi_record_open(RECORD_CLI_VCP); + cli_vcp_disable(cli_vcp); furi_record_close(RECORD_CLI); } @@ -426,8 +426,8 @@ void desktop_unlock(Desktop* desktop) { furi_hal_rtc_set_pin_fails(0); if(desktop_pin_code_is_set()) { - Cli* cli = furi_record_open(RECORD_CLI); - cli_session_open(cli, &cli_vcp); + CliVcp* cli_vcp = furi_record_open(RECORD_CLI_VCP); + cli_vcp_enable(cli_vcp); furi_record_close(RECORD_CLI); } @@ -525,6 +525,10 @@ int32_t desktop_srv(void* p) { if(desktop_pin_code_is_set()) { 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)) { diff --git a/applications/services/input/input.c b/applications/services/input/input.c index 6cbafb7958..c77771f3e2 100644 --- a/applications/services/input/input.c +++ b/applications/services/input/input.c @@ -6,6 +6,7 @@ #include #include #include +#include #define INPUT_DEBOUNCE_TICKS_HALF (INPUT_DEBOUNCE_TICKS / 2) #define INPUT_PRESS_TICKS 150 @@ -25,7 +26,7 @@ typedef struct { } InputPinState; /** 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 diff --git a/applications/services/input/input_cli.c b/applications/services/input/input_cli.c index 8e711c8954..a34ec3c369 100644 --- a/applications/services/input/input_cli.c +++ b/applications/services/input/input_cli.c @@ -3,6 +3,7 @@ #include #include #include +#include static void input_cli_usage(void) { 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); } -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); FuriMessageQueue* input_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); FuriPubSubSubscription* input_subscription = @@ -27,7 +28,7 @@ static void input_cli_dump(Cli* cli, FuriString* args, FuriPubSub* event_pubsub) InputEvent input_event; 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) { printf( "key: %s type: %s\r\n", @@ -47,8 +48,8 @@ static void input_cli_send_print_usage(void) { printf("\t\t \t - one of 'press', 'release', 'short', 'long'\r\n"); } -static void input_cli_send(Cli* cli, FuriString* args, FuriPubSub* event_pubsub) { - UNUSED(cli); +static void input_cli_send(PipeSide* pipe, FuriString* args, FuriPubSub* event_pubsub) { + UNUSED(pipe); InputEvent event; FuriString* key_str; 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); } -void input_cli(Cli* cli, FuriString* args, void* context) { - furi_assert(cli); +void input_cli(PipeSide* pipe, FuriString* args, void* context) { furi_assert(context); FuriPubSub* event_pubsub = context; FuriString* cmd; @@ -110,11 +110,11 @@ void input_cli(Cli* cli, FuriString* args, void* context) { break; } if(furi_string_cmp_str(cmd, "dump") == 0) { - input_cli_dump(cli, args, event_pubsub); + input_cli_dump(pipe, args, event_pubsub); break; } if(furi_string_cmp_str(cmd, "send") == 0) { - input_cli_send(cli, args, event_pubsub); + input_cli_send(pipe, args, event_pubsub); break; } diff --git a/applications/services/loader/loader_cli.c b/applications/services/loader/loader_cli.c index f3ea30df2a..40312d8b36 100644 --- a/applications/services/loader/loader_cli.c +++ b/applications/services/loader/loader_cli.c @@ -6,6 +6,7 @@ #include #include #include +#include static void loader_cli_print_usage(void) { 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) { - UNUSED(cli); +static void loader_cli(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); UNUSED(context); Loader* loader = furi_record_open(RECORD_LOADER); diff --git a/applications/services/power/power_cli.c b/applications/services/power/power_cli.c index 121552768f..f1771d9f11 100644 --- a/applications/services/power/power_cli.c +++ b/applications/services/power/power_cli.c @@ -4,9 +4,10 @@ #include #include #include +#include -void power_cli_off(Cli* cli, FuriString* args) { - UNUSED(cli); +void power_cli_off(PipeSide* pipe, FuriString* args) { + UNUSED(pipe); UNUSED(args); Power* power = furi_record_open(RECORD_POWER); 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); } -void power_cli_reboot(Cli* cli, FuriString* args) { - UNUSED(cli); +void power_cli_reboot(PipeSide* pipe, FuriString* args) { + UNUSED(pipe); UNUSED(args); Power* power = furi_record_open(RECORD_POWER); power_reboot(power, PowerBootModeNormal); } -void power_cli_reboot2dfu(Cli* cli, FuriString* args) { - UNUSED(cli); +void power_cli_reboot2dfu(PipeSide* pipe, FuriString* args) { + UNUSED(pipe); UNUSED(args); Power* power = furi_record_open(RECORD_POWER); power_reboot(power, PowerBootModeDfu); } -void power_cli_5v(Cli* cli, FuriString* args) { - UNUSED(cli); +void power_cli_5v(PipeSide* pipe, FuriString* args) { + UNUSED(pipe); Power* power = furi_record_open(RECORD_POWER); if(!furi_string_cmp(args, "0")) { power_enable_otg(power, false); @@ -42,8 +43,8 @@ void power_cli_5v(Cli* cli, FuriString* args) { furi_record_close(RECORD_POWER); } -void power_cli_3v3(Cli* cli, FuriString* args) { - UNUSED(cli); +void power_cli_3v3(PipeSide* pipe, FuriString* args) { + UNUSED(pipe); if(!furi_string_cmp(args, "0")) { furi_hal_power_disable_external_3_3v(); } 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); FuriString* cmd; 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) { - power_cli_off(cli, args); + power_cli_off(pipe, args); break; } if(furi_string_cmp_str(cmd, "reboot") == 0) { - power_cli_reboot(cli, args); + power_cli_reboot(pipe, args); break; } if(furi_string_cmp_str(cmd, "reboot2dfu") == 0) { - power_cli_reboot2dfu(cli, args); + power_cli_reboot2dfu(pipe, args); break; } if(furi_string_cmp_str(cmd, "5v") == 0) { - power_cli_5v(cli, args); + power_cli_5v(pipe, args); break; } if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { if(furi_string_cmp_str(cmd, "3v3") == 0) { - power_cli_3v3(cli, args); + power_cli_3v3(pipe, args); break; } } diff --git a/applications/services/rpc/rpc_cli.c b/applications/services/rpc/rpc_cli.c index 4612752a83..3280fe7025 100644 --- a/applications/services/rpc/rpc_cli.c +++ b/applications/services/rpc/rpc_cli.c @@ -2,24 +2,24 @@ #include #include #include +#include #define TAG "RpcCli" typedef struct { - Cli* cli; + PipeSide* pipe; bool session_close_request; FuriSemaphore* terminate_semaphore; } 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) { furi_assert(context); furi_assert(bytes); furi_assert(bytes_len > 0); CliRpc* cli_rpc = context; - - cli_write(cli_rpc->cli, bytes, bytes_len); + pipe_send(cli_rpc->pipe, bytes, bytes_len); } 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); } -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); - furi_assert(cli); + furi_assert(pipe); furi_assert(context); Rpc* rpc = context; @@ -53,7 +53,7 @@ void rpc_cli_command_start_session(Cli* cli, FuriString* args, void* context) { 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); rpc_session_set_context(rpc_session, &cli_rpc); 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; while(1) { - size_received = cli_read_timeout(cli_rpc.cli, buffer, CLI_READ_BUFFER_SIZE, 50); - if(!cli_is_connected(cli_rpc.cli) || cli_rpc.session_close_request) { + size_t to_receive = CLAMP(pipe_bytes_available(cli_rpc.pipe), CLI_READ_BUFFER_SIZE, 1UL); + size_received = pipe_receive(cli_rpc.pipe, buffer, to_receive); + if(size_received < to_receive || cli_rpc.session_close_request) { break; } diff --git a/applications/services/rpc/rpc_i.h b/applications/services/rpc/rpc_i.h index 0342df2b65..51903a2764 100644 --- a/applications/services/rpc/rpc_i.h +++ b/applications/services/rpc/rpc_i.h @@ -6,6 +6,7 @@ #include #include #include +#include #ifdef __cplusplus 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_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); diff --git a/applications/services/storage/storage_cli.c b/applications/services/storage/storage_cli.c index 441b58da66..416ecce0eb 100644 --- a/applications/services/storage/storage_cli.c +++ b/applications/services/storage/storage_cli.c @@ -10,6 +10,7 @@ #include #include #include +#include #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)); } -static void storage_cli_info(Cli* cli, FuriString* path, FuriString* args) { - UNUSED(cli); +static void storage_cli_info(PipeSide* pipe, FuriString* path, FuriString* args) { + UNUSED(pipe); UNUSED(args); 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); } -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); if(furi_string_cmp_str(path, STORAGE_INT_PATH_PREFIX) == 0) { storage_cli_print_error(FSE_NOT_IMPLEMENTED); } 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"); - char answer = cli_getc(cli); + char answer = getchar(); if(answer == 'y' || answer == 'Y') { Storage* api = furi_record_open(RECORD_STORAGE); 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) { - UNUSED(cli); +static void storage_cli_list(PipeSide* pipe, FuriString* path, FuriString* args) { + UNUSED(pipe); UNUSED(args); if(furi_string_cmp_str(path, "/") == 0) { 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); if(furi_string_cmp_str(path, "/") == 0) { 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); - storage_cli_tree(cli, path, NULL); + storage_cli_tree(pipe, path, NULL); } else { Storage* api = furi_record_open(RECORD_STORAGE); 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) { - UNUSED(cli); +static void storage_cli_read(PipeSide* pipe, FuriString* path, FuriString* args) { + UNUSED(pipe); UNUSED(args); Storage* api = furi_record_open(RECORD_STORAGE); 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); } -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); Storage* api = furi_record_open(RECORD_STORAGE); 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; 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; if(write_size > 0) { @@ -263,7 +266,8 @@ static void storage_cli_write(Cli* cli, FuriString* path, FuriString* args) { 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); 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); while(file_size > 0) { printf("\r\nReady?\r\n"); - cli_getc(cli); + getchar(); size_t read_size = storage_file_read(file, data, buffer_size); 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); } -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); File* file = storage_file_alloc(api); - uint32_t buffer_size; - if(strint_to_uint32(furi_string_get_cstr(args), NULL, &buffer_size, 10) != + uint32_t need_to_read; + if(strint_to_uint32(furi_string_get_cstr(args), NULL, &need_to_read, 10) != StrintParseNoError) { storage_cli_print_usage(); } else { if(storage_file_open(file, furi_string_get_cstr(path), FSAM_WRITE, FSOM_OPEN_APPEND)) { printf("Ready\r\n"); + const size_t buffer_size = 1024; + uint8_t* buffer = malloc(buffer_size); - if(buffer_size) { - uint8_t* buffer = malloc(buffer_size); + while(need_to_read) { + 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); - - size_t written_size = storage_file_write(file, buffer, read_bytes); - - if(written_size != buffer_size) { + if(wrote_this_time != read_this_time) { storage_cli_print_error(storage_file_get_error(file)); + break; } - - free(buffer); + need_to_read -= read_this_time; } + + free(buffer); } else { 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); } -static void storage_cli_stat(Cli* cli, FuriString* path, FuriString* args) { - UNUSED(cli); +static void storage_cli_stat(PipeSide* pipe, FuriString* path, FuriString* args) { + UNUSED(pipe); UNUSED(args); 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); } -static void storage_cli_timestamp(Cli* cli, FuriString* path, FuriString* args) { - UNUSED(cli); +static void storage_cli_timestamp(PipeSide* pipe, FuriString* path, FuriString* args) { + UNUSED(pipe); UNUSED(args); 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); } -static void storage_cli_copy(Cli* cli, FuriString* old_path, FuriString* args) { - UNUSED(cli); +static void storage_cli_copy(PipeSide* pipe, FuriString* old_path, FuriString* args) { + UNUSED(pipe); Storage* api = furi_record_open(RECORD_STORAGE); FuriString* new_path; 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); } -static void storage_cli_remove(Cli* cli, FuriString* path, FuriString* args) { - UNUSED(cli); +static void storage_cli_remove(PipeSide* pipe, FuriString* path, FuriString* args) { + UNUSED(pipe); UNUSED(args); Storage* api = furi_record_open(RECORD_STORAGE); 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); } -static void storage_cli_rename(Cli* cli, FuriString* old_path, FuriString* args) { - UNUSED(cli); +static void storage_cli_rename(PipeSide* pipe, FuriString* old_path, FuriString* args) { + UNUSED(pipe); Storage* api = furi_record_open(RECORD_STORAGE); FuriString* new_path; 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); } -static void storage_cli_mkdir(Cli* cli, FuriString* path, FuriString* args) { - UNUSED(cli); +static void storage_cli_mkdir(PipeSide* pipe, FuriString* path, FuriString* args) { + UNUSED(pipe); UNUSED(args); Storage* api = furi_record_open(RECORD_STORAGE); 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); } -static void storage_cli_md5(Cli* cli, FuriString* path, FuriString* args) { - UNUSED(cli); +static void storage_cli_md5(PipeSide* pipe, FuriString* path, FuriString* args) { + UNUSED(pipe); UNUSED(args); Storage* api = furi_record_open(RECORD_STORAGE); 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; } -static void storage_cli_extract(Cli* cli, FuriString* old_path, FuriString* args) { - UNUSED(cli); +static void storage_cli_extract(PipeSide* pipe, FuriString* old_path, FuriString* args) { + UNUSED(pipe); FuriString* new_path = furi_string_alloc(); 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); } -typedef void (*StorageCliCommandCallback)(Cli* cli, FuriString* path, FuriString* args); +typedef void (*StorageCliCommandCallback)(PipeSide* pipe, FuriString* path, FuriString* args); typedef struct { 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); FuriString* cmd; FuriString* path; @@ -653,7 +658,7 @@ void storage_cli(Cli* cli, FuriString* args, void* context) { for(; i < COUNT_OF(storage_cli_commands); ++i) { const StorageCliCommand* command_descr = &storage_cli_commands[i]; if(furi_string_cmp_str(cmd, command_descr->command) == 0) { - command_descr->impl(cli, path, args); + command_descr->impl(pipe, path, args); break; } } @@ -667,11 +672,12 @@ void storage_cli(Cli* cli, FuriString* args, void* context) { 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(context); 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') { 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) { #ifdef SRV_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, "factory_reset", CliCommandFlagParallelSafe, storage_cli_factory_reset, NULL); furi_record_close(RECORD_CLI); diff --git a/applications/system/js_app/js_app.c b/applications/system/js_app/js_app.c index c321150df7..bdb2ccc281 100644 --- a/applications/system/js_app/js_app.c +++ b/applications/system/js_app/js_app.c @@ -5,6 +5,7 @@ #include #include #include +#include #define TAG "JS app" @@ -131,12 +132,14 @@ int32_t js_app(void* arg) { } //-V773 typedef struct { - Cli* cli; + PipeSide* pipe; FuriSemaphore* exit_sem; } JsCliContext; 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) { @@ -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); const char* path = furi_string_get_cstr(args); @@ -187,14 +190,14 @@ void js_cli_execute(Cli* cli, FuriString* args, void* context) { break; } - JsCliContext ctx = {.cli = cli}; + JsCliContext ctx = {.pipe = pipe}; ctx.exit_sem = furi_semaphore_alloc(1, 0); printf("Running script %s, press CTRL+C to stop\r\n", path); JsThread* js_thread = js_thread_run(path, js_cli_callback, &ctx); 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); diff --git a/applications/system/updater/cli/updater_cli.c b/applications/system/updater/cli/updater_cli.c index 56a16bd9d3..01949269f7 100644 --- a/applications/system/updater/cli/updater_cli.c +++ b/applications/system/updater/cli/updater_cli.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -63,8 +64,8 @@ static const CliSubcommand update_cli_subcommands[] = { {.command = "help", .handler = updater_cli_help}, }; -static void updater_cli_ep(Cli* cli, FuriString* args, void* context) { - UNUSED(cli); +static void updater_cli_ep(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); UNUSED(context); FuriString* subcommand; subcommand = furi_string_alloc(); diff --git a/furi/core/core_defines.h b/furi/core/core_defines.h index fa56150ce1..2d6ced0255 100644 --- a/furi/core/core_defines.h +++ b/furi/core/core_defines.h @@ -41,6 +41,16 @@ extern "C" { #define CLAMP(x, upper, lower) (MIN(upper, MAX(x, lower))) #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 #define COUNT_OF(x) (sizeof(x) / sizeof(x[0])) #endif diff --git a/furi/core/event_loop.c b/furi/core/event_loop.c index e09be0ca43..800fea86c4 100644 --- a/furi/core/event_loop.c +++ b/furi/core/event_loop.c @@ -78,6 +78,7 @@ void furi_event_loop_free(FuriEventLoop* instance) { furi_event_loop_process_timer_queue(instance); furi_check(TimerList_empty_p(instance->timer_list)); furi_check(WaitingList_empty_p(instance->waiting_list)); + furi_check(!instance->are_thread_flags_subscribed); FuriEventLoopTree_clear(instance->tree); PendingQueue_clear(instance->pending_queue); @@ -243,6 +244,10 @@ void furi_event_loop_run(FuriEventLoop* instance) { } else if(flags & FuriEventLoopFlagPending) { 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 { furi_crash(); } @@ -416,6 +421,24 @@ void furi_event_loop_subscribe_mutex( 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 */ @@ -538,6 +561,25 @@ static bool furi_event_loop_item_is_waiting(FuriEventLoopItem* instance) { 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 */ diff --git a/furi/core/event_loop.h b/furi/core/event_loop.h index d5e8710a69..63f020f78e 100644 --- a/furi/core/event_loop.h +++ b/furi/core/event_loop.h @@ -203,6 +203,12 @@ typedef void FuriEventLoopObject; */ 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 */ typedef struct FuriEventFlag FuriEventFlag; @@ -304,6 +310,23 @@ void furi_event_loop_subscribe_mutex( FuriEventLoopEventCallback callback, 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) * * @param instance The Event Loop instance diff --git a/furi/core/event_loop_i.h b/furi/core/event_loop_i.h index ef2774b97e..c2f04a3599 100644 --- a/furi/core/event_loop_i.h +++ b/furi/core/event_loop_i.h @@ -4,12 +4,14 @@ #include "event_loop_link_i.h" #include "event_loop_timer_i.h" #include "event_loop_tick_i.h" +#include "event_loop_thread_flag_interface.h" #include #include #include #include "thread.h" +#include "thread_i.h" struct FuriEventLoopItem { // Source @@ -50,11 +52,12 @@ typedef enum { FuriEventLoopFlagStop = (1 << 1), FuriEventLoopFlagTimer = (1 << 2), FuriEventLoopFlagPending = (1 << 3), + FuriEventLoopFlagThreadFlag = (1 << 4), } FuriEventLoopFlag; #define FuriEventLoopFlagAll \ (FuriEventLoopFlagEvent | FuriEventLoopFlagStop | FuriEventLoopFlagTimer | \ - FuriEventLoopFlagPending) + FuriEventLoopFlagPending | FuriEventLoopFlagThreadFlag) typedef enum { FuriEventLoopProcessStatusComplete, @@ -94,4 +97,9 @@ struct FuriEventLoop { PendingQueue_t pending_queue; // Tick event FuriEventLoopTick tick; + + // Thread flags callback + bool are_thread_flags_subscribed; + FuriEventLoopThreadFlagsCallback thread_flags_callback; + void* thread_flags_callback_context; }; diff --git a/furi/core/event_loop_thread_flag_interface.h b/furi/core/event_loop_thread_flag_interface.h new file mode 100644 index 0000000000..05fcd47dec --- /dev/null +++ b/furi/core/event_loop_thread_flag_interface.h @@ -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); diff --git a/furi/core/thread.c b/furi/core/thread.c index d6132cdc23..e29a8711e3 100644 --- a/furi/core/thread.c +++ b/furi/core/thread.c @@ -7,6 +7,7 @@ #include "check.h" #include "common_defines.h" #include "string.h" +#include "event_loop_thread_flag_interface.h" #include "log.h" #include @@ -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); } } + + furi_event_loop_thread_flag_callback(thread_id); + /* Return flags after setting */ return rflags; } diff --git a/lib/toolbox/pipe.c b/lib/toolbox/pipe.c index c6a45abf1d..59b2f63f44 100644 --- a/lib/toolbox/pipe.c +++ b/lib/toolbox/pipe.c @@ -1,6 +1,8 @@ #include "pipe.h" #include +#define PIPE_DEFAULT_STATE_CHECK_PERIOD furi_ms_to_ticks(100) + /** * Data shared between both sides. */ @@ -23,7 +25,7 @@ struct PipeSide { PipeSideDataArrivedCallback on_data_arrived; PipeSideSpaceFreedCallback on_space_freed; PipeSideBrokenCallback on_pipe_broken; - FuriWait stdout_timeout; + FuriWait state_check_period; }; PipeSideBundle pipe_alloc(size_t capacity, size_t trigger_level) { @@ -53,14 +55,14 @@ PipeSideBundle pipe_alloc_ex(PipeSideReceiveSettings alice, PipeSideReceiveSetti .shared = shared, .sending = alice_to_bob, .receiving = bob_to_alice, - .stdout_timeout = FuriWaitForever, + .state_check_period = PIPE_DEFAULT_STATE_CHECK_PERIOD, }; *bobs_side = (PipeSide){ .role = PipeRoleBob, .shared = shared, .sending = bob_to_alice, .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}; @@ -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); PipeSide* pipe = context; - while(size) { - size_t sent = pipe_send(pipe, data, size, pipe->stdout_timeout); - if(!sent) break; - data += sent; - size -= sent; - } + pipe_send(pipe, data, size); } -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); PipeSide* pipe = context; - return pipe_receive(pipe, data, size, timeout); + return pipe_receive(pipe, data, size); } void pipe_install_as_stdio(PipeSide* pipe) { furi_check(pipe); - furi_thread_set_stdout_callback(_pipe_stdout_cb, pipe); - furi_thread_set_stdin_callback(_pipe_stdin_cb, pipe); + furi_thread_set_stdout_callback(pipe_stdout_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); - 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); - 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); - 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) { @@ -151,14 +173,14 @@ static void pipe_receiving_buffer_callback(FuriEventLoopObject* buffer, void* co UNUSED(buffer); PipeSide* pipe = context; 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) { UNUSED(buffer); PipeSide* pipe = context; 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) { diff --git a/lib/toolbox/pipe.h b/lib/toolbox/pipe.h index c05e60d0c5..b071975f70 100644 --- a/lib/toolbox/pipe.h +++ b/lib/toolbox/pipe.h @@ -148,38 +148,48 @@ void pipe_free(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 - * @param [in] timeout Timeout value in ticks + * `send` and `receive` will check the state of the pipe if exactly 0 bytes were + * 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. * + * 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 [out] data The buffer to fill with data * @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 */ -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. * + * 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 [out] data The buffer to get data from * @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 */ -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. diff --git a/scripts/serial_cli_perf.py b/scripts/serial_cli_perf.py new file mode 100644 index 0000000000..0fed7c3934 --- /dev/null +++ b/scripts/serial_cli_perf.py @@ -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() diff --git a/scripts/update.py b/scripts/update.py index 992f756037..1c877c23e9 100755 --- a/scripts/update.py +++ b/scripts/update.py @@ -34,7 +34,7 @@ class Main(App): FLASH_BASE = 0x8000000 FLASH_PAGE_SIZE = 4 * 1024 - MIN_GAP_PAGES = 1 + MIN_GAP_PAGES = 0 # Update stage file larger than that is not loadable without fix # https://github.com/flipperdevices/flipperzero-firmware/pull/3676 diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 9813401389..bfe7afbcdf 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,8 +1,9 @@ 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_keys_storage.h,, Header,+,applications/services/cli/cli.h,, +Header,+,applications/services/cli/cli_ansi.h,, Header,+,applications/services/cli/cli_vcp.h,, Header,+,applications/services/dialogs/dialogs.h,, Header,+,applications/services/dolphin/dolphin.h,, @@ -778,18 +779,17 @@ Function,-,ceill,long double,long double Function,-,cfree,void,void* Function,-,clearerr,void,FILE* Function,-,clearerr_unlocked,void,FILE* -Function,+,cli_add_command,void,"Cli*, const char*, CliCommandFlag, CliCallback, void*" -Function,+,cli_cmd_interrupt_received,_Bool,Cli* +Function,+,cli_add_command,void,"Cli*, const char*, CliCommandFlag, CliExecuteCallback, void*" +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_getc,char,Cli* -Function,+,cli_is_connected,_Bool,Cli* -Function,+,cli_nl,void,Cli* +Function,+,cli_is_pipe_broken_or_is_etx_next_char,_Bool,PipeSide* Function,+,cli_print_usage,void,"const char*, const char*, const char*" -Function,+,cli_read,size_t,"Cli*, uint8_t*, size_t" -Function,+,cli_read_timeout,size_t,"Cli*, uint8_t*, size_t, uint32_t" -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,+,cli_vcp_disable,void,CliVcp* +Function,+,cli_vcp_enable,void,CliVcp* Function,+,composite_api_resolver_add,void,"CompositeApiResolver*, const ElfApiInterface*" Function,+,composite_api_resolver_alloc,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_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_thread_flags,void,"FuriEventLoop*, FuriEventLoopThreadFlagsCallback, 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_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_stop,void,FuriEventLoopTimer* 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_hal_adc_acquire,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_free,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_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_callback_context,void,"PipeSide*, void*" Function,+,pipe_set_data_arrived_callback,void,"PipeSide*, PipeSideDataArrivedCallback, 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_state,PipeState,PipeSide* 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,-,ble_profile_hid,const FuriHalBleProfileTemplate*, Variable,+,ble_profile_serial,const FuriHalBleProfileTemplate* const, -Variable,+,cli_vcp,const CliSession, Variable,+,compress_config_heatshrink_default,const CompressConfigHeatshrink, Variable,+,firmware_api_interface,const ElfApiInterface* const, Variable,+,furi_hal_i2c_bus_external,FuriHalI2cBus, diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index da275594b9..1a8c46f10c 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,9 +1,10 @@ entry,status,name,type,params -Version,+,83.0,, +Version,+,84.0,, 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_keys_storage.h,, Header,+,applications/services/cli/cli.h,, +Header,+,applications/services/cli/cli_ansi.h,, Header,+,applications/services/cli/cli_vcp.h,, Header,+,applications/services/dialogs/dialogs.h,, Header,+,applications/services/dolphin/dolphin.h,, @@ -855,18 +856,17 @@ Function,-,ceill,long double,long double Function,-,cfree,void,void* Function,-,clearerr,void,FILE* Function,-,clearerr_unlocked,void,FILE* -Function,+,cli_add_command,void,"Cli*, const char*, CliCommandFlag, CliCallback, void*" -Function,+,cli_cmd_interrupt_received,_Bool,Cli* +Function,+,cli_add_command,void,"Cli*, const char*, CliCommandFlag, CliExecuteCallback, void*" +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_getc,char,Cli* -Function,+,cli_is_connected,_Bool,Cli* -Function,+,cli_nl,void,Cli* +Function,+,cli_is_pipe_broken_or_is_etx_next_char,_Bool,PipeSide* Function,+,cli_print_usage,void,"const char*, const char*, const char*" -Function,+,cli_read,size_t,"Cli*, uint8_t*, size_t" -Function,+,cli_read_timeout,size_t,"Cli*, uint8_t*, size_t, uint32_t" -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,+,cli_vcp_disable,void,CliVcp* +Function,+,cli_vcp_enable,void,CliVcp* Function,+,composite_api_resolver_add,void,"CompositeApiResolver*, const ElfApiInterface*" Function,+,composite_api_resolver_alloc,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_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_thread_flags,void,"FuriEventLoop*, FuriEventLoopThreadFlagsCallback, 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_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_stop,void,FuriEventLoopTimer* 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_hal_adc_acquire,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_free,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_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_callback_context,void,"PipeSide*, void*" Function,+,pipe_set_data_arrived_callback,void,"PipeSide*, PipeSideDataArrivedCallback, 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_state,PipeState,PipeSide* 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,-,ble_profile_hid,const FuriHalBleProfileTemplate*, Variable,+,ble_profile_serial,const FuriHalBleProfileTemplate* const, -Variable,+,cli_vcp,const CliSession, Variable,+,compress_config_heatshrink_default,const CompressConfigHeatshrink, Variable,+,firmware_api_interface,const ElfApiInterface* const, Variable,+,furi_hal_i2c_bus_external,FuriHalI2cBus, diff --git a/targets/f7/furi_hal/furi_hal_usb_cdc.h b/targets/f7/furi_hal/furi_hal_usb_cdc.h index 89b68991b9..50d4566981 100644 --- a/targets/f7/furi_hal/furi_hal_usb_cdc.h +++ b/targets/f7/furi_hal/furi_hal_usb_cdc.h @@ -9,11 +9,21 @@ extern "C" { #endif +typedef enum { + CdcStateDisconnected, + CdcStateConnected, +} CdcState; + +typedef enum { + CdcCtrlLineDTR = (1 << 0), + CdcCtrlLineRTS = (1 << 1), +} CdcCtrlLine; + typedef struct { void (*tx_ep_callback)(void* context); void (*rx_ep_callback)(void* context); - void (*state_callback)(void* context, uint8_t state); - void (*ctrl_line_callback)(void* context, uint8_t state); + void (*state_callback)(void* context, CdcState state); + void (*ctrl_line_callback)(void* context, CdcCtrlLine ctrl_lines); void (*config_callback)(void* context, struct usb_cdc_line_coding* config); } CdcCallbacks;