From bf7fefcc731371c9716eaed4177ee44447bcc2c1 Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Thu, 31 Oct 2024 18:19:26 +0300 Subject: [PATCH 01/47] feat: FuriThread stdin --- .../unit_tests/tests/furi/furi_stdio_test.c | 104 ++++++++++++++++++ .../debug/unit_tests/tests/furi/furi_test.c | 8 ++ furi/core/string.h | 8 +- furi/core/thread.c | 64 ++++++++++- furi/core/thread.h | 50 +++++++++ lib/print/SConscript | 20 ++-- lib/print/wrappers.c | 47 +++++++- lib/print/wrappers.h | 6 + targets/f7/api_symbols.csv | 11 +- 9 files changed, 298 insertions(+), 20 deletions(-) create mode 100644 applications/debug/unit_tests/tests/furi/furi_stdio_test.c diff --git a/applications/debug/unit_tests/tests/furi/furi_stdio_test.c b/applications/debug/unit_tests/tests/furi/furi_stdio_test.c new file mode 100644 index 00000000000..a42973900a4 --- /dev/null +++ b/applications/debug/unit_tests/tests/furi/furi_stdio_test.c @@ -0,0 +1,104 @@ +#include +#include +#include +#include "../test.h" // IWYU pragma: keep + +#define TAG "StdioTest" + +// stdin + +static char mock_in[256]; +static size_t mock_in_len, mock_in_pos; + +static void set_mock_in(const char* str) { + size_t len = strlen(str); + strcpy(mock_in, str); + mock_in_len = len; + mock_in_pos = 0; +} + +static size_t mock_in_cb(char* buffer, size_t size, FuriWait wait) { + UNUSED(wait); + size_t remaining = mock_in_len - mock_in_pos; + size = MIN(remaining, size); + memcpy(buffer, mock_in + mock_in_pos, size); + mock_in_pos += size; + return size; +} + +void test_stdin(void) { + FuriThreadStdinReadCallback in_cb = furi_thread_get_stdin_callback(); + furi_thread_set_stdin_callback(mock_in_cb); + char buf[256]; + + // plain in + set_mock_in("Hello, World!\n"); + fgets(buf, sizeof(buf), stdin); + mu_assert_string_eq("Hello, World!\n", buf); + mu_assert_int_eq(EOF, getchar()); + + // ungetc + ungetc('i', stdin); + ungetc('H', stdin); + fgets(buf, sizeof(buf), stdin); + mu_assert_string_eq("Hi", buf); + mu_assert_int_eq(EOF, getchar()); + + // ungetc + plain in + set_mock_in(" World"); + ungetc('i', stdin); + ungetc('H', stdin); + fgets(buf, sizeof(buf), stdin); + mu_assert_string_eq("Hi World", buf); + mu_assert_int_eq(EOF, getchar()); + + // partial plain in + set_mock_in("Hello, World!\n"); + fgets(buf, strlen("Hello") + 1, stdin); + mu_assert_string_eq("Hello", buf); + mu_assert_int_eq(',', getchar()); + fgets(buf, sizeof(buf), stdin); + mu_assert_string_eq(" World!\n", buf); + + furi_thread_set_stdin_callback(in_cb); +} + +// stdout + +static FuriString* mock_out; +FuriThreadStdoutWriteCallback original_out_cb; + +static void mock_out_cb(const char* data, size_t size) { + // there's no furi_string_cat_strn :( + for(size_t i = 0; i < size; i++) { + furi_string_push_back(mock_out, data[i]); + } +} + +static void assert_and_clear_mock_out(const char* expected) { + // return the original stdout callback for the duration of the check + // if the check fails, we don't want the error to end up in our buffer, + // we want to be able to see it! + furi_thread_set_stdout_callback(original_out_cb); + mu_assert_string_eq(expected, furi_string_get_cstr(mock_out)); + furi_thread_set_stdout_callback(mock_out_cb); + + furi_string_reset(mock_out); +} + +void test_stdout(void) { + original_out_cb = furi_thread_get_stdout_callback(); + furi_thread_set_stdout_callback(mock_out_cb); + mock_out = furi_string_alloc(); + + puts("Hello, World!"); + assert_and_clear_mock_out("Hello, World!\n"); + + printf("He"); + printf("llo!"); + fflush(stdout); + assert_and_clear_mock_out("Hello!"); + + furi_string_free(mock_out); + furi_thread_set_stdout_callback(original_out_cb); +} diff --git a/applications/debug/unit_tests/tests/furi/furi_test.c b/applications/debug/unit_tests/tests/furi/furi_test.c index 2a76d5184c4..722718ac52d 100644 --- a/applications/debug/unit_tests/tests/furi/furi_test.c +++ b/applications/debug/unit_tests/tests/furi/furi_test.c @@ -9,6 +9,8 @@ void test_furi_pubsub(void); void test_furi_memmgr(void); void test_furi_event_loop(void); void test_errno_saving(void); +void test_stdin(void); +void test_stdout(void); static int foo = 0; @@ -47,6 +49,11 @@ MU_TEST(mu_test_errno_saving) { test_errno_saving(); } +MU_TEST(mu_test_stdio) { + test_stdin(); + test_stdout(); +} + MU_TEST_SUITE(test_suite) { MU_SUITE_CONFIGURE(&test_setup, &test_teardown); MU_RUN_TEST(test_check); @@ -56,6 +63,7 @@ MU_TEST_SUITE(test_suite) { MU_RUN_TEST(mu_test_furi_pubsub); MU_RUN_TEST(mu_test_furi_memmgr); MU_RUN_TEST(mu_test_furi_event_loop); + MU_RUN_TEST(mu_test_stdio); MU_RUN_TEST(mu_test_errno_saving); } diff --git a/furi/core/string.h b/furi/core/string.h index 84b8c6a2405..0d407356bc6 100644 --- a/furi/core/string.h +++ b/furi/core/string.h @@ -129,12 +129,12 @@ void furi_string_swap(FuriString* string_1, FuriString* string_2); /** Move string_2 content to string_1. * - * Set the string to the other one, and destroy the other one. + * Copy data from one string to another and destroy the source. * - * @param string_1 The FuriString instance 1 - * @param string_2 The FuriString instance 2 + * @param destination The destination FuriString + * @param source The source FuriString */ -void furi_string_move(FuriString* string_1, FuriString* string_2); +void furi_string_move(FuriString* destination, FuriString* source); /** Compute a hash for the string. * diff --git a/furi/core/thread.c b/furi/core/thread.c index fd576ea72bb..12b9e61887c 100644 --- a/furi/core/thread.c +++ b/furi/core/thread.c @@ -23,12 +23,15 @@ #define THREAD_MAX_STACK_SIZE (UINT16_MAX * sizeof(StackType_t)) -typedef struct FuriThreadStdout FuriThreadStdout; - -struct FuriThreadStdout { +typedef struct { FuriThreadStdoutWriteCallback write_callback; FuriString* buffer; -}; +} FuriThreadStdout; + +typedef struct { + FuriThreadStdinReadCallback read_callback; + FuriString* unread_buffer; // output.buffer = furi_string_alloc(); + thread->input.unread_buffer = furi_string_alloc(); FuriThread* parent = NULL; if(xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED) { @@ -245,6 +250,7 @@ void furi_thread_free(FuriThread* thread) { } furi_string_free(thread->output.buffer); + furi_string_free(thread->input.unread_buffer); free(thread); } @@ -717,6 +723,15 @@ static size_t __furi_thread_stdout_write(FuriThread* thread, const char* data, s return size; } +static size_t + __furi_thread_stdin_read(FuriThread* thread, char* data, size_t size, FuriWait timeout) { + if(thread->input.read_callback != NULL) { + return thread->input.read_callback(data, size, timeout); + } else { + return 0; + } +} + static int32_t __furi_thread_stdout_flush(FuriThread* thread) { FuriString* buffer = thread->output.buffer; size_t size = furi_string_size(buffer); @@ -727,6 +742,18 @@ static int32_t __furi_thread_stdout_flush(FuriThread* thread) { return 0; } +FuriThreadStdoutWriteCallback furi_thread_get_stdout_callback(void) { + FuriThread* thread = furi_thread_get_current(); + furi_check(thread); + return thread->output.write_callback; +} + +FuriThreadStdinReadCallback furi_thread_get_stdin_callback(void) { + FuriThread* thread = furi_thread_get_current(); + furi_check(thread); + return thread->input.read_callback; +} + void furi_thread_set_stdout_callback(FuriThreadStdoutWriteCallback callback) { FuriThread* thread = furi_thread_get_current(); furi_check(thread); @@ -734,10 +761,10 @@ void furi_thread_set_stdout_callback(FuriThreadStdoutWriteCallback callback) { thread->output.write_callback = callback; } -FuriThreadStdoutWriteCallback furi_thread_get_stdout_callback(void) { +void furi_thread_set_stdin_callback(FuriThreadStdinReadCallback callback) { FuriThread* thread = furi_thread_get_current(); furi_check(thread); - return thread->output.write_callback; + thread->input.read_callback = callback; } size_t furi_thread_stdout_write(const char* data, size_t size) { @@ -772,6 +799,31 @@ int32_t furi_thread_stdout_flush(void) { return __furi_thread_stdout_flush(thread); } +size_t furi_thread_stdin_read(char* buffer, size_t size, FuriWait timeout) { + FuriThread* thread = furi_thread_get_current(); + furi_check(thread); + + size_t from_buffer = MIN(furi_string_size(thread->input.unread_buffer), size); + size_t from_input = size - from_buffer; + size_t from_input_actual = + __furi_thread_stdin_read(thread, buffer + from_buffer, from_input, timeout); + memcpy(buffer, furi_string_get_cstr(thread->input.unread_buffer), from_buffer); + furi_string_right(thread->input.unread_buffer, from_buffer); + + return from_buffer + from_input_actual; +} + +void furi_thread_stdin_unread(char* buffer, size_t size) { + FuriThread* thread = furi_thread_get_current(); + furi_check(thread); + + FuriString* new_buf = furi_string_alloc(); // there's no furi_string_alloc_set_strn :( + furi_string_set_strn(new_buf, buffer, size); + furi_string_cat(new_buf, thread->input.unread_buffer); + furi_string_free(thread->input.unread_buffer); + thread->input.unread_buffer = new_buf; +} + void furi_thread_suspend(FuriThreadId thread_id) { furi_check(thread_id); diff --git a/furi/core/thread.h b/furi/core/thread.h index ed7aa4553b0..219d839ee17 100644 --- a/furi/core/thread.h +++ b/furi/core/thread.h @@ -77,6 +77,18 @@ typedef int32_t (*FuriThreadCallback)(void* context); */ typedef void (*FuriThreadStdoutWriteCallback)(const char* data, size_t size); +/** + * @brief Standard input callback function pointer type + * + * The function to be used as a standard input callback MUST follow this signature. + * + * @param[out] buffer buffer to read data into + * @param[in] size maximum number of bytes to read into the buffer + * @param[in] timeout how long to wait for (in ticks) before giving up + * @returns number of bytes that was actually read into the buffer + */ +typedef size_t (*FuriThreadStdinReadCallback)(char* buffer, size_t size, FuriWait timeout); + /** * @brief State change callback function pointer type. * @@ -468,13 +480,28 @@ uint32_t furi_thread_get_stack_space(FuriThreadId thread_id); */ FuriThreadStdoutWriteCallback furi_thread_get_stdout_callback(void); +/** + * @brief Get the standard input callback for the current thead. + * + * @return pointer to the standard in callback function + */ +FuriThreadStdinReadCallback furi_thread_get_stdin_callback(void); + /** Set standard output callback for the current thread. * * @param[in] callback pointer to the callback function or NULL to clear */ void furi_thread_set_stdout_callback(FuriThreadStdoutWriteCallback callback); +/** Set standard input callback for the current thread. + * + * @param[in] callback pointer to the callback function or NULL to clear + */ +void furi_thread_set_stdin_callback(FuriThreadStdinReadCallback callback); + /** Write data to buffered standard output. + * + * @note You can also use the standard C `putc`, `puts`, `printf` and friends. * * @param[in] data pointer to the data to be written * @param[in] size data size in bytes @@ -489,6 +516,29 @@ size_t furi_thread_stdout_write(const char* data, size_t size); */ int32_t furi_thread_stdout_flush(void); +/** Read data from the standard input + * + * @note You can also use the standard C `getc`, `gets` and friends. + * + * @param[in] buffer pointer to the buffer to read data into + * @param[in] size how many bytes to read into the buffer + * @param[in] timeout how long to wait for (in ticks) before giving up + * @return number of bytes that was actually read + */ +size_t furi_thread_stdin_read(char* buffer, size_t size, FuriWait timeout); + +/** Puts data back into the standard input buffer + * + * `furi_thread_stdin_read` will return the bytes in the same order that they + * were supplied to this function. + * + * @note You can also use the standard C `ungetc`. + * + * @param[in] buffer pointer to the buffer to get data from + * @param[in] size how many bytes to read from the buffer + */ +void furi_thread_stdin_unread(char* buffer, size_t size); + /** * @brief Suspend a thread. * diff --git a/lib/print/SConscript b/lib/print/SConscript index 07be8d890e1..90028cf06a1 100644 --- a/lib/print/SConscript +++ b/lib/print/SConscript @@ -44,18 +44,24 @@ wrapped_fn_list = [ "vsiprintf", "vsniprintf", # - # Scanf is not implemented 4 now + # standard input + # + "fgetc", + "getc", + "getchar", + "fgets", + "ungetc", + # + # standard input, but unimplemented + # + "gets", + # + # scanf, not implemented for now # # "fscanf", # "scanf", # "sscanf", # "vsprintf", - # "fgetc", - # "fgets", - # "getc", - # "getchar", - # "gets", - # "ungetc", # "vfscanf", # "vscanf", # "vsscanf", diff --git a/lib/print/wrappers.c b/lib/print/wrappers.c index c8d72d19285..18df92def8c 100644 --- a/lib/print/wrappers.c +++ b/lib/print/wrappers.c @@ -51,11 +51,54 @@ int __wrap_snprintf(char* str, size_t size, const char* format, ...) { } int __wrap_fflush(FILE* stream) { - UNUSED(stream); - furi_thread_stdout_flush(); + if(stream == stdout) furi_thread_stdout_flush(); return 0; } +int __wrap_fgetc(FILE* stream) { + if(stream != stdin) return EOF; + char c; + if(furi_thread_stdin_read(&c, 1, FuriWaitForever) == 0) return EOF; + return c; +} + +int __wrap_getc(FILE* stream) { + return __wrap_fgetc(stream); +} + +int __wrap_getchar(void) { + return __wrap_fgetc(stdin); +} + +char* __wrap_fgets(char* str, size_t n, FILE* stream) { + // leave space for the zero terminator + furi_check(n >= 1); + n--; + + if(stream != stdin) { + *str = '\0'; + return str; + } + + // read characters + int c; + do { + c = __wrap_fgetc(stdin); + if(c > 0) *(str++) = c; + } while(c != EOF && c != '\n' && --n); + + // place zero terminator + *str = '\0'; + return str; +} + +int __wrap_ungetc(int ch, FILE* stream) { + char c = ch; + if(stream != stdin) return EOF; + furi_thread_stdin_unread(&c, 1); + return ch; +} + __attribute__((__noreturn__)) void __wrap___assert(const char* file, int line, const char* e) { UNUSED(file); UNUSED(line); diff --git a/lib/print/wrappers.h b/lib/print/wrappers.h index 3cec88249a2..8a4599b4174 100644 --- a/lib/print/wrappers.h +++ b/lib/print/wrappers.h @@ -16,6 +16,12 @@ int __wrap_putc(int ch, FILE* stream); int __wrap_snprintf(char* str, size_t size, const char* format, ...); int __wrap_fflush(FILE* stream); +int __wrap_fgetc(FILE* stream); +int __wrap_getc(FILE* stream); +int __wrap_getchar(void); +char* __wrap_fgets(char* str, size_t n, FILE* stream); +int __wrap_ungetc(int ch, FILE* stream); + __attribute__((__noreturn__)) void __wrap___assert(const char* file, int line, const char* e); __attribute__((__noreturn__)) void diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index c121fc71681..0b2d28e8bba 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,77.2,, +Version,+,77.3,, 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,, @@ -448,11 +448,16 @@ Function,-,__utoa,char*,"unsigned, char*, int" Function,+,__wrap___assert,void,"const char*, int, const char*" Function,+,__wrap___assert_func,void,"const char*, int, const char*, const char*" Function,+,__wrap_fflush,int,FILE* +Function,+,__wrap_fgetc,int,FILE* +Function,+,__wrap_fgets,char*,"char*, size_t, FILE*" +Function,+,__wrap_getc,int,FILE* +Function,+,__wrap_getchar,int, Function,+,__wrap_printf,int,"const char*, ..." Function,+,__wrap_putc,int,"int, FILE*" Function,+,__wrap_putchar,int,int Function,+,__wrap_puts,int,const char* Function,+,__wrap_snprintf,int,"char*, size_t, const char*, ..." +Function,+,__wrap_ungetc,int,"int, FILE*" Function,+,__wrap_vsnprintf,int,"char*, size_t, const char*, va_list" Function,-,_asiprintf_r,int,"_reent*, char**, const char*, ..." Function,-,_asniprintf_r,char*,"_reent*, char*, size_t*, const char*, ..." @@ -1863,6 +1868,7 @@ Function,+,furi_thread_get_return_code,int32_t,FuriThread* Function,+,furi_thread_get_signal_callback,FuriThreadSignalCallback,const FuriThread* Function,+,furi_thread_get_stack_space,uint32_t,FuriThreadId Function,+,furi_thread_get_state,FuriThreadState,FuriThread* +Function,+,furi_thread_get_stdin_callback,FuriThreadStdinReadCallback, Function,+,furi_thread_get_stdout_callback,FuriThreadStdoutWriteCallback, Function,+,furi_thread_is_suspended,_Bool,FuriThreadId Function,+,furi_thread_join,_Bool,FuriThread* @@ -1883,9 +1889,12 @@ Function,+,furi_thread_set_signal_callback,void,"FuriThread*, FuriThreadSignalCa Function,+,furi_thread_set_stack_size,void,"FuriThread*, size_t" Function,+,furi_thread_set_state_callback,void,"FuriThread*, FuriThreadStateCallback" Function,+,furi_thread_set_state_context,void,"FuriThread*, void*" +Function,+,furi_thread_set_stdin_callback,void,FuriThreadStdinReadCallback Function,+,furi_thread_set_stdout_callback,void,FuriThreadStdoutWriteCallback Function,+,furi_thread_signal,_Bool,"const FuriThread*, uint32_t, void*" Function,+,furi_thread_start,void,FuriThread* +Function,+,furi_thread_stdin_read,size_t,"char*, size_t, FuriWait" +Function,+,furi_thread_stdin_unread,void,"char*, size_t" Function,+,furi_thread_stdout_flush,int32_t, Function,+,furi_thread_stdout_write,size_t,"const char*, size_t" Function,+,furi_thread_suspend,void,FuriThreadId From 4c592439a7ab228d9e15b464954865900749be7a Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Thu, 31 Oct 2024 18:29:47 +0300 Subject: [PATCH 02/47] ci: fix f18 --- targets/f18/api_symbols.csv | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index b5d51a0dd5c..2b247d6f30c 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,78.1,, +Version,+,78.2,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, Header,+,applications/services/cli/cli.h,, @@ -371,11 +371,16 @@ Function,-,__utoa,char*,"unsigned, char*, int" Function,+,__wrap___assert,void,"const char*, int, const char*" Function,+,__wrap___assert_func,void,"const char*, int, const char*, const char*" Function,+,__wrap_fflush,int,FILE* +Function,+,__wrap_fgetc,int,FILE* +Function,+,__wrap_fgets,char*,"char*, size_t, FILE*" +Function,+,__wrap_getc,int,FILE* +Function,+,__wrap_getchar,int, Function,+,__wrap_printf,int,"const char*, ..." Function,+,__wrap_putc,int,"int, FILE*" Function,+,__wrap_putchar,int,int Function,+,__wrap_puts,int,const char* Function,+,__wrap_snprintf,int,"char*, size_t, const char*, ..." +Function,+,__wrap_ungetc,int,"int, FILE*" Function,+,__wrap_vsnprintf,int,"char*, size_t, const char*, va_list" Function,-,_asiprintf_r,int,"_reent*, char**, const char*, ..." Function,-,_asniprintf_r,char*,"_reent*, char*, size_t*, const char*, ..." @@ -1654,6 +1659,7 @@ Function,+,furi_thread_get_return_code,int32_t,FuriThread* Function,+,furi_thread_get_signal_callback,FuriThreadSignalCallback,const FuriThread* Function,+,furi_thread_get_stack_space,uint32_t,FuriThreadId Function,+,furi_thread_get_state,FuriThreadState,FuriThread* +Function,+,furi_thread_get_stdin_callback,FuriThreadStdinReadCallback, Function,+,furi_thread_get_stdout_callback,FuriThreadStdoutWriteCallback, Function,+,furi_thread_is_suspended,_Bool,FuriThreadId Function,+,furi_thread_join,_Bool,FuriThread* @@ -1674,9 +1680,12 @@ Function,+,furi_thread_set_signal_callback,void,"FuriThread*, FuriThreadSignalCa Function,+,furi_thread_set_stack_size,void,"FuriThread*, size_t" Function,+,furi_thread_set_state_callback,void,"FuriThread*, FuriThreadStateCallback" Function,+,furi_thread_set_state_context,void,"FuriThread*, void*" +Function,+,furi_thread_set_stdin_callback,void,FuriThreadStdinReadCallback Function,+,furi_thread_set_stdout_callback,void,FuriThreadStdoutWriteCallback Function,+,furi_thread_signal,_Bool,"const FuriThread*, uint32_t, void*" Function,+,furi_thread_start,void,FuriThread* +Function,+,furi_thread_stdin_read,size_t,"char*, size_t, FuriWait" +Function,+,furi_thread_stdin_unread,void,"char*, size_t" Function,+,furi_thread_stdout_flush,int32_t, Function,+,furi_thread_stdout_write,size_t,"const char*, size_t" Function,+,furi_thread_suspend,void,FuriThreadId From f1eb60b36021df376f8cf46e6d2eadedfe5fb8be Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Thu, 7 Nov 2024 17:38:06 +0300 Subject: [PATCH 03/47] feat: stdio callback context --- .../unit_tests/tests/furi/furi_stdio_test.c | 20 +++++++++++-------- applications/services/cli/cli.c | 10 +++++----- applications/services/cli/cli_i.h | 3 ++- applications/services/cli/cli_vcp.c | 9 ++++++++- furi/core/thread.c | 12 +++++++---- furi/core/thread.h | 13 ++++++++---- targets/f18/api_symbols.csv | 6 +++--- targets/f7/api_symbols.csv | 6 +++--- 8 files changed, 50 insertions(+), 29 deletions(-) diff --git a/applications/debug/unit_tests/tests/furi/furi_stdio_test.c b/applications/debug/unit_tests/tests/furi/furi_stdio_test.c index a42973900a4..94e2f613b6a 100644 --- a/applications/debug/unit_tests/tests/furi/furi_stdio_test.c +++ b/applications/debug/unit_tests/tests/furi/furi_stdio_test.c @@ -5,6 +5,8 @@ #define TAG "StdioTest" +#define CONTEXT_MAGIC ((void*)0xDEADBEEF) + // stdin static char mock_in[256]; @@ -17,8 +19,9 @@ static void set_mock_in(const char* str) { mock_in_pos = 0; } -static size_t mock_in_cb(char* buffer, size_t size, FuriWait wait) { +static size_t mock_in_cb(char* buffer, size_t size, FuriWait wait, void* context) { UNUSED(wait); + furi_check(context == CONTEXT_MAGIC); size_t remaining = mock_in_len - mock_in_pos; size = MIN(remaining, size); memcpy(buffer, mock_in + mock_in_pos, size); @@ -28,7 +31,7 @@ static size_t mock_in_cb(char* buffer, size_t size, FuriWait wait) { void test_stdin(void) { FuriThreadStdinReadCallback in_cb = furi_thread_get_stdin_callback(); - furi_thread_set_stdin_callback(mock_in_cb); + furi_thread_set_stdin_callback(mock_in_cb, CONTEXT_MAGIC); char buf[256]; // plain in @@ -60,7 +63,7 @@ void test_stdin(void) { fgets(buf, sizeof(buf), stdin); mu_assert_string_eq(" World!\n", buf); - furi_thread_set_stdin_callback(in_cb); + furi_thread_set_stdin_callback(in_cb, CONTEXT_MAGIC); } // stdout @@ -68,7 +71,8 @@ void test_stdin(void) { static FuriString* mock_out; FuriThreadStdoutWriteCallback original_out_cb; -static void mock_out_cb(const char* data, size_t size) { +static void mock_out_cb(const char* data, size_t size, void* context) { + furi_check(context == CONTEXT_MAGIC); // there's no furi_string_cat_strn :( for(size_t i = 0; i < size; i++) { furi_string_push_back(mock_out, data[i]); @@ -79,16 +83,16 @@ static void assert_and_clear_mock_out(const char* expected) { // return the original stdout callback for the duration of the check // if the check fails, we don't want the error to end up in our buffer, // we want to be able to see it! - furi_thread_set_stdout_callback(original_out_cb); + furi_thread_set_stdout_callback(original_out_cb, CONTEXT_MAGIC); mu_assert_string_eq(expected, furi_string_get_cstr(mock_out)); - furi_thread_set_stdout_callback(mock_out_cb); + furi_thread_set_stdout_callback(mock_out_cb, CONTEXT_MAGIC); furi_string_reset(mock_out); } void test_stdout(void) { original_out_cb = furi_thread_get_stdout_callback(); - furi_thread_set_stdout_callback(mock_out_cb); + furi_thread_set_stdout_callback(mock_out_cb, CONTEXT_MAGIC); mock_out = furi_string_alloc(); puts("Hello, World!"); @@ -100,5 +104,5 @@ void test_stdout(void) { assert_and_clear_mock_out("Hello!"); furi_string_free(mock_out); - furi_thread_set_stdout_callback(original_out_cb); + furi_thread_set_stdout_callback(original_out_cb, CONTEXT_MAGIC); } diff --git a/applications/services/cli/cli.c b/applications/services/cli/cli.c index 0d8f52c04ec..28ba417c261 100644 --- a/applications/services/cli/cli.c +++ b/applications/services/cli/cli.c @@ -431,9 +431,9 @@ void cli_session_open(Cli* cli, void* session) { cli->session = session; if(cli->session != NULL) { cli->session->init(); - furi_thread_set_stdout_callback(cli->session->tx_stdout); + furi_thread_set_stdout_callback(cli->session->tx_stdout, NULL); } else { - furi_thread_set_stdout_callback(NULL); + furi_thread_set_stdout_callback(NULL, NULL); } furi_semaphore_release(cli->idle_sem); furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk); @@ -447,7 +447,7 @@ void cli_session_close(Cli* cli) { cli->session->deinit(); } cli->session = NULL; - furi_thread_set_stdout_callback(NULL); + furi_thread_set_stdout_callback(NULL, NULL); furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk); } @@ -461,9 +461,9 @@ int32_t cli_srv(void* p) { furi_record_create(RECORD_CLI, cli); if(cli->session != NULL) { - furi_thread_set_stdout_callback(cli->session->tx_stdout); + furi_thread_set_stdout_callback(cli->session->tx_stdout, NULL); } else { - furi_thread_set_stdout_callback(NULL); + furi_thread_set_stdout_callback(NULL, NULL); } if(furi_hal_rtc_get_boot_mode() == FuriHalRtcBootModeNormal) { diff --git a/applications/services/cli/cli_i.h b/applications/services/cli/cli_i.h index d4cac6e7d92..d7351b9ffc3 100644 --- a/applications/services/cli/cli_i.h +++ b/applications/services/cli/cli_i.h @@ -28,8 +28,9 @@ struct CliSession { void (*init)(void); void (*deinit)(void); size_t (*rx)(uint8_t* buffer, size_t size, uint32_t timeout); + size_t (*rx_stdin)(uint8_t* buffer, size_t size, uint32_t timeout, void* context); void (*tx)(const uint8_t* buffer, size_t size); - void (*tx_stdout)(const char* data, size_t size); + void (*tx_stdout)(const char* data, size_t size, void* context); bool (*is_connected)(void); }; diff --git a/applications/services/cli/cli_vcp.c b/applications/services/cli/cli_vcp.c index cdabaaa0544..aa399e78a29 100644 --- a/applications/services/cli/cli_vcp.c +++ b/applications/services/cli/cli_vcp.c @@ -242,6 +242,11 @@ static size_t cli_vcp_rx(uint8_t* buffer, size_t size, uint32_t timeout) { return rx_cnt; } +static size_t cli_vcp_rx_stdin(uint8_t* data, size_t size, uint32_t timeout, void* context) { + UNUSED(context); + return cli_vcp_rx(data, size, timeout); +} + static void cli_vcp_tx(const uint8_t* buffer, size_t size) { furi_assert(vcp); furi_assert(buffer); @@ -267,7 +272,8 @@ static void cli_vcp_tx(const uint8_t* buffer, size_t size) { VCP_DEBUG("tx %u end", size); } -static void cli_vcp_tx_stdout(const char* data, size_t size) { +static void cli_vcp_tx_stdout(const char* data, size_t size, void* context) { + UNUSED(context); cli_vcp_tx((const uint8_t*)data, size); } @@ -310,6 +316,7 @@ 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/furi/core/thread.c b/furi/core/thread.c index 12b9e61887c..6e515795775 100644 --- a/furi/core/thread.c +++ b/furi/core/thread.c @@ -26,11 +26,13 @@ typedef struct { FuriThreadStdoutWriteCallback write_callback; FuriString* buffer; + void* context; } FuriThreadStdout; typedef struct { FuriThreadStdinReadCallback read_callback; FuriString* unread_buffer; // output.write_callback != NULL) { - thread->output.write_callback(data, size); + thread->output.write_callback(data, size, thread->output.context); } else { furi_log_tx((const uint8_t*)data, size); } @@ -726,7 +728,7 @@ static size_t __furi_thread_stdout_write(FuriThread* thread, const char* data, s static size_t __furi_thread_stdin_read(FuriThread* thread, char* data, size_t size, FuriWait timeout) { if(thread->input.read_callback != NULL) { - return thread->input.read_callback(data, size, timeout); + return thread->input.read_callback(data, size, timeout, thread->input.context); } else { return 0; } @@ -754,17 +756,19 @@ FuriThreadStdinReadCallback furi_thread_get_stdin_callback(void) { return thread->input.read_callback; } -void furi_thread_set_stdout_callback(FuriThreadStdoutWriteCallback callback) { +void furi_thread_set_stdout_callback(FuriThreadStdoutWriteCallback callback, void* context) { FuriThread* thread = furi_thread_get_current(); furi_check(thread); __furi_thread_stdout_flush(thread); thread->output.write_callback = callback; + thread->output.context = context; } -void furi_thread_set_stdin_callback(FuriThreadStdinReadCallback callback) { +void furi_thread_set_stdin_callback(FuriThreadStdinReadCallback callback, void* context) { FuriThread* thread = furi_thread_get_current(); furi_check(thread); thread->input.read_callback = callback; + thread->input.context = context; } size_t furi_thread_stdout_write(const char* data, size_t size) { diff --git a/furi/core/thread.h b/furi/core/thread.h index 219d839ee17..9abfde5cd85 100644 --- a/furi/core/thread.h +++ b/furi/core/thread.h @@ -74,8 +74,9 @@ typedef int32_t (*FuriThreadCallback)(void* context); * * @param[in] data pointer to the data to be written to the standard out * @param[in] size size of the data in bytes + * @param[in] context optional context */ -typedef void (*FuriThreadStdoutWriteCallback)(const char* data, size_t size); +typedef void (*FuriThreadStdoutWriteCallback)(const char* data, size_t size, void* context); /** * @brief Standard input callback function pointer type @@ -85,9 +86,11 @@ typedef void (*FuriThreadStdoutWriteCallback)(const char* data, size_t size); * @param[out] buffer buffer to read data into * @param[in] size maximum number of bytes to read into the buffer * @param[in] timeout how long to wait for (in ticks) before giving up + * @param[in] context optional context * @returns number of bytes that was actually read into the buffer */ -typedef size_t (*FuriThreadStdinReadCallback)(char* buffer, size_t size, FuriWait timeout); +typedef size_t ( + *FuriThreadStdinReadCallback)(char* buffer, size_t size, FuriWait timeout, void* context); /** * @brief State change callback function pointer type. @@ -490,14 +493,16 @@ FuriThreadStdinReadCallback furi_thread_get_stdin_callback(void); /** Set standard output callback for the current thread. * * @param[in] callback pointer to the callback function or NULL to clear + * @param[in] context context to be passed to the callback */ -void furi_thread_set_stdout_callback(FuriThreadStdoutWriteCallback callback); +void furi_thread_set_stdout_callback(FuriThreadStdoutWriteCallback callback, void* context); /** Set standard input callback for the current thread. * * @param[in] callback pointer to the callback function or NULL to clear + * @param[in] context context to be passed to the callback */ -void furi_thread_set_stdin_callback(FuriThreadStdinReadCallback callback); +void furi_thread_set_stdin_callback(FuriThreadStdinReadCallback callback, void* context); /** Write data to buffered standard output. * diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 2b247d6f30c..23421712d01 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,78.2,, +Version,+,79.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,, @@ -1680,8 +1680,8 @@ Function,+,furi_thread_set_signal_callback,void,"FuriThread*, FuriThreadSignalCa Function,+,furi_thread_set_stack_size,void,"FuriThread*, size_t" Function,+,furi_thread_set_state_callback,void,"FuriThread*, FuriThreadStateCallback" Function,+,furi_thread_set_state_context,void,"FuriThread*, void*" -Function,+,furi_thread_set_stdin_callback,void,FuriThreadStdinReadCallback -Function,+,furi_thread_set_stdout_callback,void,FuriThreadStdoutWriteCallback +Function,+,furi_thread_set_stdin_callback,void,"FuriThreadStdinReadCallback, void*" +Function,+,furi_thread_set_stdout_callback,void,"FuriThreadStdoutWriteCallback, void*" Function,+,furi_thread_signal,_Bool,"const FuriThread*, uint32_t, void*" Function,+,furi_thread_start,void,FuriThread* Function,+,furi_thread_stdin_read,size_t,"char*, size_t, FuriWait" diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 8418b6e0bcc..6f9fc5466e6 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,78.2,, +Version,+,79.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,, @@ -1899,8 +1899,8 @@ Function,+,furi_thread_set_signal_callback,void,"FuriThread*, FuriThreadSignalCa Function,+,furi_thread_set_stack_size,void,"FuriThread*, size_t" Function,+,furi_thread_set_state_callback,void,"FuriThread*, FuriThreadStateCallback" Function,+,furi_thread_set_state_context,void,"FuriThread*, void*" -Function,+,furi_thread_set_stdin_callback,void,FuriThreadStdinReadCallback -Function,+,furi_thread_set_stdout_callback,void,FuriThreadStdoutWriteCallback +Function,+,furi_thread_set_stdin_callback,void,"FuriThreadStdinReadCallback, void*" +Function,+,furi_thread_set_stdout_callback,void,"FuriThreadStdoutWriteCallback, void*" Function,+,furi_thread_signal,_Bool,"const FuriThread*, uint32_t, void*" Function,+,furi_thread_start,void,FuriThread* Function,+,furi_thread_stdin_read,size_t,"char*, size_t, FuriWait" From a4a7a41e310e8a17145605979fce16c481916605 Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Thu, 7 Nov 2024 18:33:53 +0300 Subject: [PATCH 04/47] feat: FuriPipe --- .../tests/furi/furi_primitives_test.c | 53 ++++++ furi/core/pipe.c | 122 ++++++++++++++ furi/core/pipe.h | 152 ++++++++++++++++++ furi/furi.h | 1 + targets/f18/api_symbols.csv | 12 +- targets/f7/api_symbols.csv | 12 +- 6 files changed, 350 insertions(+), 2 deletions(-) create mode 100644 furi/core/pipe.c create mode 100644 furi/core/pipe.h diff --git a/applications/debug/unit_tests/tests/furi/furi_primitives_test.c b/applications/debug/unit_tests/tests/furi/furi_primitives_test.c index d9ad0303955..64c7c2af311 100644 --- a/applications/debug/unit_tests/tests/furi/furi_primitives_test.c +++ b/applications/debug/unit_tests/tests/furi/furi_primitives_test.c @@ -7,9 +7,13 @@ #define STREAM_BUFFER_SIZE (32U) #define STREAM_BUFFER_TRG_LEVEL (STREAM_BUFFER_SIZE / 2U) +#define PIPE_SIZE (128U) +#define PIPE_TRG_LEVEL (1U) + typedef struct { FuriMessageQueue* message_queue; FuriStreamBuffer* stream_buffer; + FuriPipe pipe; } TestFuriPrimitivesData; static void test_furi_message_queue(TestFuriPrimitivesData* data) { @@ -87,16 +91,65 @@ static void test_furi_stream_buffer(TestFuriPrimitivesData* data) { } } +static void test_furi_pipe(TestFuriPrimitivesData* data) { + FuriPipeSide* alice = data->pipe.alices_side; + FuriPipeSide* bob = data->pipe.bobs_side; + + mu_assert_int_eq(FuriPipeRoleAlice, furi_pipe_role(alice)); + mu_assert_int_eq(FuriPipeRoleBob, furi_pipe_role(bob)); + mu_assert_int_eq(FuriPipeStateOpen, furi_pipe_state(alice)); + mu_assert_int_eq(FuriPipeStateOpen, furi_pipe_state(bob)); + + mu_assert_int_eq(PIPE_SIZE, furi_pipe_spaces_available(alice)); + mu_assert_int_eq(PIPE_SIZE, furi_pipe_spaces_available(bob)); + mu_assert_int_eq(0, furi_pipe_bytes_available(alice)); + mu_assert_int_eq(0, furi_pipe_bytes_available(bob)); + + for(uint8_t i = 0;; ++i) { + mu_assert_int_eq(PIPE_SIZE - i, furi_pipe_spaces_available(alice)); + mu_assert_int_eq(i, furi_pipe_bytes_available(bob)); + + if(furi_pipe_send(alice, &i, sizeof(uint8_t), 0) != sizeof(uint8_t)) { + break; + } + + mu_assert_int_eq(PIPE_SIZE - i, furi_pipe_spaces_available(bob)); + mu_assert_int_eq(i, furi_pipe_bytes_available(alice)); + + if(furi_pipe_send(bob, &i, sizeof(uint8_t), 0) != sizeof(uint8_t)) { + break; + } + } + + furi_pipe_free(alice); + mu_assert_int_eq(FuriPipeStateBroken, furi_pipe_state(bob)); + + for(uint8_t i = 0;; ++i) { + mu_assert_int_eq(PIPE_SIZE - i, furi_pipe_bytes_available(bob)); + + uint8_t value; + if(furi_pipe_receive(bob, &value, sizeof(uint8_t), 0) != sizeof(uint8_t)) { + break; + } + + mu_assert_int_eq(i, value); + } + + furi_pipe_free(bob); +} + // This is a stub that needs expanding void test_furi_primitives(void) { TestFuriPrimitivesData data = { .message_queue = furi_message_queue_alloc(MESSAGE_QUEUE_CAPACITY, MESSAGE_QUEUE_ELEMENT_SIZE), .stream_buffer = furi_stream_buffer_alloc(STREAM_BUFFER_SIZE, STREAM_BUFFER_TRG_LEVEL), + .pipe = furi_pipe_alloc(PIPE_SIZE, PIPE_TRG_LEVEL), }; test_furi_message_queue(&data); test_furi_stream_buffer(&data); + test_furi_pipe(&data); furi_message_queue_free(data.message_queue); furi_stream_buffer_free(data.stream_buffer); diff --git a/furi/core/pipe.c b/furi/core/pipe.c new file mode 100644 index 00000000000..e343c7f41a3 --- /dev/null +++ b/furi/core/pipe.c @@ -0,0 +1,122 @@ +#include "pipe.h" +#include "stream_buffer.h" +#include "semaphore.h" +#include "mutex.h" +#include "check.h" +#include "memmgr.h" + +/** + * There are two PipeSides, both of which point to the same primitives + */ +struct FuriPipeSide { + FuriPipeRole role; + FuriStreamBuffer* sending; + FuriStreamBuffer* receiving; + FuriSemaphore* instance_count; // role; +} + +FuriPipeState furi_pipe_state(FuriPipeSide* pipe) { + furi_check(pipe); + uint32_t count = furi_semaphore_get_count(pipe->instance_count); + return (count == 1) ? FuriPipeStateOpen : FuriPipeStateBroken; +} + +void furi_pipe_free(FuriPipeSide* pipe) { + furi_check(pipe); + + furi_mutex_acquire(pipe->state_transition, FuriWaitForever); + FuriStatus status = furi_semaphore_acquire(pipe->instance_count, 0); + + if(status == FuriStatusOk) { + // the other side is still intact + furi_mutex_release(pipe->state_transition); + free(pipe); + } else { + // the other side is gone too + furi_stream_buffer_free(pipe->sending); + furi_stream_buffer_free(pipe->receiving); + furi_semaphore_free(pipe->instance_count); + furi_mutex_free(pipe->state_transition); + free(pipe); + } +} + +static void _furi_pipe_stdout_cb(const char* data, size_t size, void* context) { + furi_assert(context); + FuriPipeSide* pipe = context; + furi_check(furi_stream_buffer_send(pipe->sending, data, size, FuriWaitForever) == size); +} + +static size_t _furi_pipe_stdin_cb(char* data, size_t size, FuriWait timeout, void* context) { + furi_assert(context); + FuriPipeSide* pipe = context; + return furi_stream_buffer_receive(pipe->sending, data, size, timeout); +} + +void furi_pipe_install_as_stdio(FuriPipeSide* pipe) { + furi_check(pipe); + furi_thread_set_stdout_callback(_furi_pipe_stdout_cb, pipe); + furi_thread_set_stdin_callback(_furi_pipe_stdin_cb, pipe); +} + +size_t furi_pipe_receive(FuriPipeSide* pipe, void* data, size_t length, FuriWait timeout) { + furi_check(pipe); + return furi_stream_buffer_receive(pipe->receiving, data, length, timeout); +} + +size_t furi_pipe_send(FuriPipeSide* pipe, const void* data, size_t length, FuriWait timeout) { + furi_check(pipe); + return furi_stream_buffer_send(pipe->sending, data, length, timeout); +} + +size_t furi_pipe_bytes_available(FuriPipeSide* pipe) { + furi_check(pipe); + return furi_stream_buffer_bytes_available(pipe->receiving); +} + +size_t furi_pipe_spaces_available(FuriPipeSide* pipe) { + furi_check(pipe); + return furi_stream_buffer_spaces_available(pipe->sending); +} diff --git a/furi/core/pipe.h b/furi/core/pipe.h new file mode 100644 index 00000000000..8a7ee38f664 --- /dev/null +++ b/furi/core/pipe.h @@ -0,0 +1,152 @@ +/** + * @file pipe.h + * Furi pipe primitive + * + * Pipes are used to send bytes between two threads in both directions. The two + * threads are referred to as Alice and Bob and their abilities regarding what + * they can do with the pipe are equal. + * + * It is also possible to use both sides of the pipe within one thread. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "base.h" +#include + +/** + * @brief The role of a pipe side + * + * Both roles are equal, as they can both read and write the data. This status + * might be helpful in determining the role of a thread w.r.t. another thread in + * an application that builds on the pipe. + */ +typedef enum { + FuriPipeRoleAlice, + FuriPipeRoleBob, +} FuriPipeRole; + +/** + * @brief The state of a pipe + * + * - `FuriPipeStateOpen`: Both pipe sides are in place, meaning data that is + * sent down the pipe _might_ be read by the peer, and new data sent by the + * peer _might_ arrive. + * - `FuriPipeStateBroken`: The other side of the pipe has been freed, meaning + * data that is written will never reach its destination, and no new data + * will appear in the buffer. + * + * A broken pipe can never become open again, because there's no way to connect + * a side of a pipe to another side of a pipe. + */ +typedef enum { + FuriPipeStateOpen, + FuriPipeStateBroken, +} FuriPipeState; + +typedef struct FuriPipeSide FuriPipeSide; + +typedef struct { + FuriPipeSide* alices_side; + FuriPipeSide* bobs_side; +} FuriPipe; + +typedef struct { + size_t capacity; + size_t trigger_level; +} FuriPipeSideReceiveSettings; + +/** + * @brief Allocates two connected sides of one pipe. + * + * Creating a pair of sides using this function is the only way to connect two + * pipe sides together. Two unrelated orphaned sides may never be connected back + * together. + * + * The capacity and trigger level for both directions are the same when the pipe + * is created using this function. Use `furi_pipe_alloc_ex` if you want more + * control. + */ +FuriPipe furi_pipe_alloc(size_t capacity, size_t trigger_level); + +/** + * @brief Allocates two connected sides of one pipe. + * + * Creating a pair of sides using this function is the only way to connect two + * pipe sides together. Two unrelated orphaned sides may never be connected back + * together. + * + * The capacity and trigger level may be different for the two directions when + * the pipe is created using this function. Use `furi_pipe_alloc` if you don't + * need control this fine. + */ +FuriPipe furi_pipe_alloc_ex(FuriPipeSideReceiveSettings alice, FuriPipeSideReceiveSettings bob); + +/** + * @brief Gets the role of a pipe side. + * + * The roles (Alice and Bob) are equal, as both can send and receive data. This + * status might be helpful in determining the role of a thread w.r.t. another + * thread. + */ +FuriPipeRole furi_pipe_role(FuriPipeSide* pipe); + +/** + * @brief Gets the state of a pipe. + * + * When the state is `FuriPipeStateOpen`, both sides are active and may send or + * receive data. When the state is `FuriPipeStateBroken`, only one side is + * active (the one that this method has been called on). If you find yourself in + * that state, the data that you send will never be heard by anyone, and the + * data you receive are leftovers in the buffer. + */ +FuriPipeState furi_pipe_state(FuriPipeSide* pipe); + +/** + * @brief Frees a side of a pipe. + * + * When only one of the sides is freed, the pipe is transitioned from the "Open" + * state into the "Broken" state. When both sides are freed, the underlying data + * structures are freed too. + */ +void furi_pipe_free(FuriPipeSide* pipe); + +/** + * @brief Connects the pipe to the `stdin` and `stdout` of the current thread. + * + * After performing this operation, you can use `getc`, `puts`, etc. to send and + * receive data to and from the pipe. If the pipe becomes broken, C stdlib calls + * will return `EOF` wherever possible. + * + * You can disconnect the pipe by manually calling + * `furi_thread_set_stdout_callback` and `furi_thread_set_stdin_callback` with + * `NULL`. + */ +void furi_pipe_install_as_stdio(FuriPipeSide* pipe); + +/** + * @brief Receives data from the pipe. + */ +size_t furi_pipe_receive(FuriPipeSide* pipe, void* data, size_t length, FuriWait timeout); + +/** + * @brief Sends data into the pipe. + */ +size_t furi_pipe_send(FuriPipeSide* pipe, const void* data, size_t length, FuriWait timeout); + +/** + * @brief Determines how many bytes there are in the pipe available to be read. + */ +size_t furi_pipe_bytes_available(FuriPipeSide* pipe); + +/** + * @brief Determines how many space there is in the pipe for data to be written + * into. + */ +size_t furi_pipe_spaces_available(FuriPipeSide* pipe); + +#ifdef __cplusplus +} +#endif diff --git a/furi/furi.h b/furi/furi.h index 6ddf2857757..4afcb8f4697 100644 --- a/furi/furi.h +++ b/furi/furi.h @@ -21,6 +21,7 @@ #include "core/timer.h" #include "core/string.h" #include "core/stream_buffer.h" +#include "core/pipe.h" #include diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 23421712d01..40e83094561 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,79.0,, +Version,+,79.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, Header,+,applications/services/cli/cli.h,, @@ -1552,6 +1552,16 @@ Function,+,furi_mutex_alloc,FuriMutex*,FuriMutexType Function,+,furi_mutex_free,void,FuriMutex* Function,+,furi_mutex_get_owner,FuriThreadId,FuriMutex* Function,+,furi_mutex_release,FuriStatus,FuriMutex* +Function,+,furi_pipe_alloc,FuriPipe,"size_t, size_t" +Function,+,furi_pipe_alloc_ex,FuriPipe,"FuriPipeSideReceiveSettings, FuriPipeSideReceiveSettings" +Function,+,furi_pipe_bytes_available,size_t,FuriPipeSide* +Function,+,furi_pipe_free,void,FuriPipeSide* +Function,+,furi_pipe_install_as_stdio,void,FuriPipeSide* +Function,+,furi_pipe_receive,size_t,"FuriPipeSide*, void*, size_t, FuriWait" +Function,+,furi_pipe_role,FuriPipeRole,FuriPipeSide* +Function,+,furi_pipe_send,size_t,"FuriPipeSide*, const void*, size_t, FuriWait" +Function,+,furi_pipe_spaces_available,size_t,FuriPipeSide* +Function,+,furi_pipe_state,FuriPipeState,FuriPipeSide* Function,+,furi_pubsub_alloc,FuriPubSub*, Function,+,furi_pubsub_free,void,FuriPubSub* Function,+,furi_pubsub_publish,void,"FuriPubSub*, void*" diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 6f9fc5466e6..1eb34efa8c3 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,79.0,, +Version,+,79.1,, 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,, @@ -1771,6 +1771,16 @@ Function,+,furi_mutex_alloc,FuriMutex*,FuriMutexType Function,+,furi_mutex_free,void,FuriMutex* Function,+,furi_mutex_get_owner,FuriThreadId,FuriMutex* Function,+,furi_mutex_release,FuriStatus,FuriMutex* +Function,+,furi_pipe_alloc,FuriPipe,"size_t, size_t" +Function,+,furi_pipe_alloc_ex,FuriPipe,"FuriPipeSideReceiveSettings, FuriPipeSideReceiveSettings" +Function,+,furi_pipe_bytes_available,size_t,FuriPipeSide* +Function,+,furi_pipe_free,void,FuriPipeSide* +Function,+,furi_pipe_install_as_stdio,void,FuriPipeSide* +Function,+,furi_pipe_receive,size_t,"FuriPipeSide*, void*, size_t, FuriWait" +Function,+,furi_pipe_role,FuriPipeRole,FuriPipeSide* +Function,+,furi_pipe_send,size_t,"FuriPipeSide*, const void*, size_t, FuriWait" +Function,+,furi_pipe_spaces_available,size_t,FuriPipeSide* +Function,+,furi_pipe_state,FuriPipeState,FuriPipeSide* Function,+,furi_pubsub_alloc,FuriPubSub*, Function,+,furi_pubsub_free,void,FuriPubSub* Function,+,furi_pubsub_publish,void,"FuriPubSub*, void*" From 004e3a62d3c7a8d9e146f93fe6b76fa060dbd602 Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Fri, 8 Nov 2024 18:14:27 +0300 Subject: [PATCH 05/47] POTENTIALLY EXPLOSIVE pipe welding --- .../tests/furi/furi_primitives_test.c | 55 +++++ .../debug/unit_tests/tests/furi/furi_test.c | 2 +- furi/core/pipe.c | 214 +++++++++++++++--- furi/core/pipe.h | 41 +++- targets/f7/api_symbols.csv | 4 +- 5 files changed, 280 insertions(+), 36 deletions(-) diff --git a/applications/debug/unit_tests/tests/furi/furi_primitives_test.c b/applications/debug/unit_tests/tests/furi/furi_primitives_test.c index 64c7c2af311..6246078c2ff 100644 --- a/applications/debug/unit_tests/tests/furi/furi_primitives_test.c +++ b/applications/debug/unit_tests/tests/furi/furi_primitives_test.c @@ -138,6 +138,59 @@ static void test_furi_pipe(TestFuriPrimitivesData* data) { furi_pipe_free(bob); } +static void test_furi_pipe_welding(void) { + FuriPipe pipes[10]; + for(size_t i = 0; i < COUNT_OF(pipes); i++) { + pipes[i] = furi_pipe_alloc(PIPE_SIZE, PIPE_TRG_LEVEL); + mu_assert_int_eq(FuriPipeRoleAlice, furi_pipe_role(pipes[i].alices_side)); + mu_assert_int_eq(FuriPipeRoleBob, furi_pipe_role(pipes[i].bobs_side)); + } + + FuriPipeSide* alice = pipes[0].alices_side; + FuriPipeSide* bob = pipes[COUNT_OF(pipes) - 1].bobs_side; + const uint8_t src_buf[4] = {0xDE, 0xAD, 0xBE, 0xEF}; + furi_pipe_send(alice, src_buf, sizeof(src_buf), FuriWaitForever); + + for(size_t i = 0; i < COUNT_OF(pipes) - 1; i++) { + furi_pipe_weld(pipes[i].bobs_side, pipes[i + 1].alices_side); // lots of sparks! + mu_assert_int_eq(FuriPipeRoleJoint, furi_pipe_role(pipes[i].bobs_side)); + mu_assert_int_eq(FuriPipeRoleJoint, furi_pipe_role(pipes[i + 1].alices_side)); + } + mu_assert_int_eq(FuriPipeRoleAlice, furi_pipe_role(alice)); + mu_assert_int_eq(FuriPipeRoleBob, furi_pipe_role(bob)); + + uint8_t buf[sizeof(src_buf)]; + furi_pipe_receive(bob, buf, sizeof(buf), FuriWaitForever); + mu_assert_mem_eq(src_buf, buf, sizeof(buf)); + + for(uint8_t i = 0;; ++i) { + mu_assert_int_eq(PIPE_SIZE - i, furi_pipe_spaces_available(alice)); + mu_assert_int_eq(i, furi_pipe_bytes_available(bob)); + + if(furi_pipe_send(alice, &i, sizeof(uint8_t), 0) != sizeof(uint8_t)) { + break; + } + + mu_assert_int_eq(PIPE_SIZE - i, furi_pipe_spaces_available(bob)); + mu_assert_int_eq(i, furi_pipe_bytes_available(alice)); + + if(furi_pipe_send(bob, &i, sizeof(uint8_t), 0) != sizeof(uint8_t)) { + break; + } + } + + for(uint8_t i = 0;; ++i) { + mu_assert_int_eq(PIPE_SIZE - i, furi_pipe_bytes_available(bob)); + + uint8_t value; + if(furi_pipe_receive(bob, &value, sizeof(uint8_t), 0) != sizeof(uint8_t)) { + break; + } + + mu_assert_int_eq(i, value); + } +} + // This is a stub that needs expanding void test_furi_primitives(void) { TestFuriPrimitivesData data = { @@ -153,4 +206,6 @@ void test_furi_primitives(void) { furi_message_queue_free(data.message_queue); furi_stream_buffer_free(data.stream_buffer); + + test_furi_pipe_welding(); } diff --git a/applications/debug/unit_tests/tests/furi/furi_test.c b/applications/debug/unit_tests/tests/furi/furi_test.c index f23be37a974..0a18ada8bbe 100644 --- a/applications/debug/unit_tests/tests/furi/furi_test.c +++ b/applications/debug/unit_tests/tests/furi/furi_test.c @@ -43,7 +43,7 @@ MU_TEST(mu_test_furi_memmgr) { } MU_TEST(mu_test_furi_event_loop) { - test_furi_event_loop(); + // test_furi_event_loop(); } MU_TEST(mu_test_errno_saving) { diff --git a/furi/core/pipe.c b/furi/core/pipe.c index e343c7f41a3..3669833070e 100644 --- a/furi/core/pipe.c +++ b/furi/core/pipe.c @@ -4,49 +4,68 @@ #include "mutex.h" #include "check.h" #include "memmgr.h" +#include + +ARRAY_DEF(PipeSideArray, FuriPipeSide*, M_PTR_OPLIST); +#define M_OPL_PipeSideArray_t() ARRAY_OPLIST(PipeSideArray) /** - * There are two PipeSides, both of which point to the same primitives + * A chain of pipes that have been welded together. Initially, pipes consist of + * a chain of length 1. */ +typedef struct { + FuriStreamBuffer* alice_to_bob; + FuriStreamBuffer* bob_to_alice; + PipeSideArray_t pipe_sides; // pipe_sides); FuriPipeSide* alices_side = malloc(sizeof(FuriPipeSide)); FuriPipeSide* bobs_side = malloc(sizeof(FuriPipeSide)); + PipeSideArray_push_back(chain->pipe_sides, alices_side); + PipeSideArray_push_back(chain->pipe_sides, bobs_side); *alices_side = (FuriPipeSide){ .role = FuriPipeRoleAlice, + .chain = chain, .sending = alice_to_bob, .receiving = bob_to_alice, - .instance_count = instance_count, - .state_transition = state_transition, + .send_settings = to_bob, }; *bobs_side = (FuriPipeSide){ .role = FuriPipeRoleBob, + .chain = chain, .sending = bob_to_alice, .receiving = alice_to_bob, - .instance_count = instance_count, - .state_transition = state_transition, + .send_settings = to_alice, }; return (FuriPipe){.alices_side = alices_side, .bobs_side = bobs_side}; @@ -59,28 +78,42 @@ FuriPipeRole furi_pipe_role(FuriPipeSide* pipe) { FuriPipeState furi_pipe_state(FuriPipeSide* pipe) { furi_check(pipe); - uint32_t count = furi_semaphore_get_count(pipe->instance_count); - return (count == 1) ? FuriPipeStateOpen : FuriPipeStateBroken; + FURI_CRITICAL_ENTER(); + size_t sides = PipeSideArray_size(pipe->chain->pipe_sides); + FURI_CRITICAL_EXIT(); + return (sides % 2) ? FuriPipeStateBroken : FuriPipeStateOpen; } void furi_pipe_free(FuriPipeSide* pipe) { furi_check(pipe); - furi_mutex_acquire(pipe->state_transition, FuriWaitForever); - FuriStatus status = furi_semaphore_acquire(pipe->instance_count, 0); + FURI_CRITICAL_ENTER(); - if(status == FuriStatusOk) { - // the other side is still intact - furi_mutex_release(pipe->state_transition); - free(pipe); - } else { + furi_check(pipe->role != FuriPipeRoleJoint); // unweld first + + size_t sides = PipeSideArray_size(pipe->chain->pipe_sides); + furi_check(sides <= 2); // TODO: support chains! + + if(sides == 1) { // the other side is gone too furi_stream_buffer_free(pipe->sending); furi_stream_buffer_free(pipe->receiving); - furi_semaphore_free(pipe->instance_count); - furi_mutex_free(pipe->state_transition); + PipeSideArray_clear(pipe->chain->pipe_sides); + free(pipe->chain); + free(pipe); + } else { + // the other side is still intact + PipeSideArray_it_t it; + for(PipeSideArray_it(it, pipe->chain->pipe_sides); !PipeSideArray_end_p(it); + PipeSideArray_next(it)) { + if(*PipeSideArray_cref(it) == pipe) break; + } + furi_check(!PipeSideArray_end_p(it)); + PipeSideArray_remove(pipe->chain->pipe_sides, it); free(pipe); } + + FURI_CRITICAL_EXIT(); } static void _furi_pipe_stdout_cb(const char* data, size_t size, void* context) { @@ -103,20 +136,143 @@ void furi_pipe_install_as_stdio(FuriPipeSide* pipe) { size_t furi_pipe_receive(FuriPipeSide* pipe, void* data, size_t length, FuriWait timeout) { furi_check(pipe); - return furi_stream_buffer_receive(pipe->receiving, data, length, timeout); + FuriStreamBuffer* buffer = pipe->receiving; + if(!buffer) return 0; + return furi_stream_buffer_receive(buffer, data, length, timeout); } size_t furi_pipe_send(FuriPipeSide* pipe, const void* data, size_t length, FuriWait timeout) { furi_check(pipe); - return furi_stream_buffer_send(pipe->sending, data, length, timeout); + FuriStreamBuffer* buffer = pipe->sending; + if(!buffer) return 0; + return furi_stream_buffer_send(buffer, data, length, timeout); } size_t furi_pipe_bytes_available(FuriPipeSide* pipe) { furi_check(pipe); - return furi_stream_buffer_bytes_available(pipe->receiving); + FuriStreamBuffer* buffer = pipe->receiving; + if(!buffer) return 0; + return furi_stream_buffer_bytes_available(buffer); } size_t furi_pipe_spaces_available(FuriPipeSide* pipe) { furi_check(pipe); - return furi_stream_buffer_spaces_available(pipe->sending); + FuriStreamBuffer* buffer = pipe->sending; + if(!buffer) return 0; + return furi_stream_buffer_spaces_available(buffer); +} + +void furi_pipe_weld(FuriPipeSide* side_1, FuriPipeSide* side_2) { + // Here's a pipe: + // + // | | + // s |=========| r + // ----|---->----|---- + // ----|----<----|---- + // r |=========| s + // | | + // A B + // + // It's got two sides (_A_lice and _B_ob) and two StreamBuffers backing it (A to B and B to A). + // From Alice's perspective, A>B is the _s_ending stream, and A----|---- ----|---->----|---- + // ----|----<----|---- ----|----<----|---- + // r |=========| s r |=========| s + // | | | | + // A B A B + // + // We want to "weld" faces iB and iA ("intermediate" Alice and Bob), forming a new "pipe chain" + // with the ends cA and cB ("chain" Alice and Bob): + // + // | | | | + // s |=========|=====|=========| r + // ----|------------>------------|---- + // ----|------------<------------|---- + // r |=========|=====|=========| s + // | | | | + // cA iB iA cB + // + // By only using one StreamBuffer per direction for two welded pipes, we can avoid having to + // copy data around and increase performance. At the cost, of course, of no longer being able + // to inspect, inject, modify or otherwise do anything with the data at faces iB and iA. + + furi_check(side_1); + furi_check(side_2); + + FURI_CRITICAL_ENTER(); + + // cannot weld an already welded side + furi_check(side_1->role != FuriPipeRoleJoint); + furi_check(side_2->role != FuriPipeRoleJoint); + + // can only weld an Alice to a Bob + furi_check(side_1->role != side_2->role); + + FuriPipeSide* intermediate_alice = (side_1->role == FuriPipeRoleAlice) ? side_1 : side_2; + FuriPipeSide* intermediate_bob = (side_2->role == FuriPipeRoleBob) ? side_2 : side_1; + + // cannot weld two ends of the same chain + furi_check(intermediate_alice->chain != intermediate_bob->chain); + + FuriPipeChain* left_chain = intermediate_bob->chain; + FuriPipeChain* right_chain = intermediate_alice->chain; + + // copy residual data + do { + size_t buf_size = + MAX(furi_stream_buffer_bytes_available(left_chain->alice_to_bob), + furi_stream_buffer_bytes_available(right_chain->bob_to_alice)); + uint8_t buf[buf_size]; + + size_t to_copy = furi_stream_buffer_receive(left_chain->alice_to_bob, buf, buf_size, 0); + furi_stream_buffer_send(right_chain->alice_to_bob, buf, to_copy, 0); + furi_check( + furi_stream_buffer_bytes_available(left_chain->alice_to_bob) == 0); // all data copied + + to_copy = furi_stream_buffer_receive(right_chain->bob_to_alice, buf, buf_size, 0); + furi_stream_buffer_send(left_chain->bob_to_alice, buf, to_copy, 0); + furi_check( + furi_stream_buffer_bytes_available(right_chain->bob_to_alice) == 0); // all data copied + } while(0); + + // concat right chain to left chain + for + M_EACH(side, right_chain->pipe_sides, PipeSideArray_t) { + (*side)->chain = left_chain; + PipeSideArray_push_back(left_chain->pipe_sides, *side); + } + + // free unneeded things + furi_stream_buffer_free(left_chain->alice_to_bob); + furi_stream_buffer_free(right_chain->bob_to_alice); + left_chain->alice_to_bob = right_chain->alice_to_bob; + PipeSideArray_clear(right_chain->pipe_sides); + free(right_chain); + + // update intermediate sides + intermediate_bob->role = FuriPipeRoleJoint; + intermediate_bob->sending = NULL; + intermediate_bob->receiving = NULL; + intermediate_alice->role = FuriPipeRoleJoint; + intermediate_alice->sending = NULL; + intermediate_alice->receiving = NULL; + + // update endpoint (chain) sides + FuriPipeSide* chain_alice = *PipeSideArray_front(left_chain->pipe_sides); + FuriPipeSide* chain_bob = *PipeSideArray_back(left_chain->pipe_sides); + chain_alice->sending = left_chain->alice_to_bob; + chain_bob->sending = left_chain->bob_to_alice; + + FURI_CRITICAL_EXIT(); +} + +void furi_pipe_unweld(FuriPipeSide* side) { + UNUSED(side); + furi_crash("unimplemented"); // TODO: } diff --git a/furi/core/pipe.h b/furi/core/pipe.h index 8a7ee38f664..25b4b8ccf4a 100644 --- a/furi/core/pipe.h +++ b/furi/core/pipe.h @@ -19,13 +19,17 @@ extern "C" { /** * @brief The role of a pipe side * - * Both roles are equal, as they can both read and write the data. This status - * might be helpful in determining the role of a thread w.r.t. another thread in - * an application that builds on the pipe. + * Alice and Bob are equal, as they can both read and write the data. This + * status might be helpful in determining the role of a thread w.r.t. another + * thread in an application that builds on the pipe. + * + * Joints only allow the `unweld` operation. For more info, see + * `furi_pipe_weld`. */ typedef enum { FuriPipeRoleAlice, FuriPipeRoleBob, + FuriPipeRoleJoint, } FuriPipeRole; /** @@ -37,6 +41,8 @@ typedef enum { * - `FuriPipeStateBroken`: The other side of the pipe has been freed, meaning * data that is written will never reach its destination, and no new data * will appear in the buffer. + * - `FuriPipeStateWelded`: The side of the pipe functions as a joint between + * two pipes. For more info, see `furi_pipe_weld`. * * A broken pipe can never become open again, because there's no way to connect * a side of a pipe to another side of a pipe. @@ -44,6 +50,7 @@ typedef enum { typedef enum { FuriPipeStateOpen, FuriPipeStateBroken, + FuriPipeStateWelded, } FuriPipeState; typedef struct FuriPipeSide FuriPipeSide; @@ -56,7 +63,7 @@ typedef struct { typedef struct { size_t capacity; size_t trigger_level; -} FuriPipeSideReceiveSettings; +} FuriPipeDirectionSettings; /** * @brief Allocates two connected sides of one pipe. @@ -82,7 +89,7 @@ FuriPipe furi_pipe_alloc(size_t capacity, size_t trigger_level); * the pipe is created using this function. Use `furi_pipe_alloc` if you don't * need control this fine. */ -FuriPipe furi_pipe_alloc_ex(FuriPipeSideReceiveSettings alice, FuriPipeSideReceiveSettings bob); +FuriPipe furi_pipe_alloc_ex(FuriPipeDirectionSettings to_alice, FuriPipeDirectionSettings to_bob); /** * @brief Gets the role of a pipe side. @@ -147,6 +154,30 @@ size_t furi_pipe_bytes_available(FuriPipeSide* pipe); */ size_t furi_pipe_spaces_available(FuriPipeSide* pipe); +/** + * @brief Welds two sides of different pipes together. + * + * When two sides of a pipe are welded together, data that appears at `side_1` + * is automatically pushed into `side_2` and vice versa. This connection may be + * undone using `furi_pipe_unweld`. + * + * While a side of a pipe is welded to another, it is impossible to use any of + * the methods to inspect and/or modify the data flowing through the joint: + * - `send` and `receive` become no-ops and return 0, + * - `bytes_available` and `spaces_available` return 0. + * + * You cannot weld an Alice to an Alice or a Bob to a Bob. You can only weld an + * Alice to a Bob. + */ +void furi_pipe_weld(FuriPipeSide* side_1, FuriPipeSide* side_2); + +/** + * @brief Undoes a `weld` operation. + * + * See `furi_pipe_weld`. + */ +void furi_pipe_unweld(FuriPipeSide* side); + #ifdef __cplusplus } #endif diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 1eb34efa8c3..613f2e2f3ad 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1772,7 +1772,7 @@ Function,+,furi_mutex_free,void,FuriMutex* Function,+,furi_mutex_get_owner,FuriThreadId,FuriMutex* Function,+,furi_mutex_release,FuriStatus,FuriMutex* Function,+,furi_pipe_alloc,FuriPipe,"size_t, size_t" -Function,+,furi_pipe_alloc_ex,FuriPipe,"FuriPipeSideReceiveSettings, FuriPipeSideReceiveSettings" +Function,+,furi_pipe_alloc_ex,FuriPipe,"FuriPipeDirectionSettings, FuriPipeDirectionSettings" Function,+,furi_pipe_bytes_available,size_t,FuriPipeSide* Function,+,furi_pipe_free,void,FuriPipeSide* Function,+,furi_pipe_install_as_stdio,void,FuriPipeSide* @@ -1781,6 +1781,8 @@ Function,+,furi_pipe_role,FuriPipeRole,FuriPipeSide* Function,+,furi_pipe_send,size_t,"FuriPipeSide*, const void*, size_t, FuriWait" Function,+,furi_pipe_spaces_available,size_t,FuriPipeSide* Function,+,furi_pipe_state,FuriPipeState,FuriPipeSide* +Function,+,furi_pipe_unweld,void,FuriPipeSide* +Function,+,furi_pipe_weld,void,"FuriPipeSide*, FuriPipeSide*" Function,+,furi_pubsub_alloc,FuriPubSub*, Function,+,furi_pubsub_free,void,FuriPubSub* Function,+,furi_pubsub_publish,void,"FuriPubSub*, void*" From 920a3e99fe17797ca67f81b1902857a1d3dc4733 Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Mon, 11 Nov 2024 19:39:07 +0300 Subject: [PATCH 06/47] fix: non-explosive welding --- .../tests/furi/furi_primitives_test.c | 2 + furi/core/pipe.c | 94 ++++++++++++++----- furi/core/pipe.h | 28 +++++- targets/f7/api_symbols.csv | 2 +- 4 files changed, 99 insertions(+), 27 deletions(-) diff --git a/applications/debug/unit_tests/tests/furi/furi_primitives_test.c b/applications/debug/unit_tests/tests/furi/furi_primitives_test.c index 6246078c2ff..3bd7e6145dc 100644 --- a/applications/debug/unit_tests/tests/furi/furi_primitives_test.c +++ b/applications/debug/unit_tests/tests/furi/furi_primitives_test.c @@ -159,6 +159,7 @@ static void test_furi_pipe_welding(void) { mu_assert_int_eq(FuriPipeRoleAlice, furi_pipe_role(alice)); mu_assert_int_eq(FuriPipeRoleBob, furi_pipe_role(bob)); + // make sure that residual data is copied over uint8_t buf[sizeof(src_buf)]; furi_pipe_receive(bob, buf, sizeof(buf), FuriWaitForever); mu_assert_mem_eq(src_buf, buf, sizeof(buf)); @@ -179,6 +180,7 @@ static void test_furi_pipe_welding(void) { } } + // make sure that new data reaches its destination for(uint8_t i = 0;; ++i) { mu_assert_int_eq(PIPE_SIZE - i, furi_pipe_bytes_available(bob)); diff --git a/furi/core/pipe.c b/furi/core/pipe.c index 3669833070e..35093a17fdf 100644 --- a/furi/core/pipe.c +++ b/furi/core/pipe.c @@ -1,6 +1,5 @@ #include "pipe.h" #include "stream_buffer.h" -#include "semaphore.h" #include "mutex.h" #include "check.h" #include "memmgr.h" @@ -20,6 +19,7 @@ typedef struct { } FuriPipeChain; struct FuriPipeSide { + FuriMutex* mutex; FuriPipeRole role; FuriPipeChain* chain; FuriStreamBuffer* sending; @@ -32,10 +32,13 @@ FuriPipe furi_pipe_alloc(size_t capacity, size_t trigger_level) { .capacity = capacity, .trigger_level = trigger_level, }; - return furi_pipe_alloc_ex(settings, settings); + return furi_pipe_alloc_ex(FuriPipeWeldingCapEnabled, settings, settings); } -FuriPipe furi_pipe_alloc_ex(FuriPipeDirectionSettings to_alice, FuriPipeDirectionSettings to_bob) { +FuriPipe furi_pipe_alloc_ex( + FuriPipeWeldingCap welding_cap, + FuriPipeDirectionSettings to_alice, + FuriPipeDirectionSettings to_bob) { FuriStreamBuffer* alice_to_bob = furi_stream_buffer_alloc(to_bob.capacity, to_bob.trigger_level); FuriStreamBuffer* bob_to_alice = @@ -54,6 +57,9 @@ FuriPipe furi_pipe_alloc_ex(FuriPipeDirectionSettings to_alice, FuriPipeDirectio PipeSideArray_push_back(chain->pipe_sides, bobs_side); *alices_side = (FuriPipeSide){ + .mutex = (welding_cap == FuriPipeWeldingCapEnabled) ? + furi_mutex_alloc(FuriMutexTypeRecursive) : + NULL, .role = FuriPipeRoleAlice, .chain = chain, .sending = alice_to_bob, @@ -61,6 +67,9 @@ FuriPipe furi_pipe_alloc_ex(FuriPipeDirectionSettings to_alice, FuriPipeDirectio .send_settings = to_bob, }; *bobs_side = (FuriPipeSide){ + .mutex = (welding_cap == FuriPipeWeldingCapEnabled) ? + furi_mutex_alloc(FuriMutexTypeNormal) : + NULL, .role = FuriPipeRoleBob, .chain = chain, .sending = bob_to_alice, @@ -71,24 +80,34 @@ FuriPipe furi_pipe_alloc_ex(FuriPipeDirectionSettings to_alice, FuriPipeDirectio return (FuriPipe){.alices_side = alices_side, .bobs_side = bobs_side}; } +static FURI_ALWAYS_INLINE void furi_pipe_side_lock(FuriPipeSide* pipe) { + if(pipe->mutex) furi_mutex_acquire(pipe->mutex, FuriWaitForever); +} + +static FURI_ALWAYS_INLINE void furi_pipe_side_unlock(FuriPipeSide* pipe) { + if(pipe->mutex) furi_mutex_release(pipe->mutex); +} + FuriPipeRole furi_pipe_role(FuriPipeSide* pipe) { furi_check(pipe); - return pipe->role; + furi_pipe_side_lock(pipe); + FuriPipeRole role = pipe->role; + furi_pipe_side_unlock(pipe); + return role; } FuriPipeState furi_pipe_state(FuriPipeSide* pipe) { furi_check(pipe); - FURI_CRITICAL_ENTER(); + furi_pipe_side_lock(pipe); size_t sides = PipeSideArray_size(pipe->chain->pipe_sides); - FURI_CRITICAL_EXIT(); + furi_pipe_side_unlock(pipe); return (sides % 2) ? FuriPipeStateBroken : FuriPipeStateOpen; } void furi_pipe_free(FuriPipeSide* pipe) { furi_check(pipe); - FURI_CRITICAL_ENTER(); - + furi_pipe_side_lock(pipe); furi_check(pipe->role != FuriPipeRoleJoint); // unweld first size_t sides = PipeSideArray_size(pipe->chain->pipe_sides); @@ -99,6 +118,7 @@ void furi_pipe_free(FuriPipeSide* pipe) { furi_stream_buffer_free(pipe->sending); furi_stream_buffer_free(pipe->receiving); PipeSideArray_clear(pipe->chain->pipe_sides); + if(pipe->mutex) furi_mutex_free(pipe->mutex); free(pipe->chain); free(pipe); } else { @@ -110,10 +130,9 @@ void furi_pipe_free(FuriPipeSide* pipe) { } furi_check(!PipeSideArray_end_p(it)); PipeSideArray_remove(pipe->chain->pipe_sides, it); + if(pipe->mutex) furi_mutex_free(pipe->mutex); free(pipe); } - - FURI_CRITICAL_EXIT(); } static void _furi_pipe_stdout_cb(const char* data, size_t size, void* context) { @@ -125,7 +144,7 @@ static void _furi_pipe_stdout_cb(const char* data, size_t size, void* context) { static size_t _furi_pipe_stdin_cb(char* data, size_t size, FuriWait timeout, void* context) { furi_assert(context); FuriPipeSide* pipe = context; - return furi_stream_buffer_receive(pipe->sending, data, size, timeout); + return furi_stream_buffer_receive(pipe->receiving, data, size, timeout); } void furi_pipe_install_as_stdio(FuriPipeSide* pipe) { @@ -136,30 +155,42 @@ void furi_pipe_install_as_stdio(FuriPipeSide* pipe) { size_t furi_pipe_receive(FuriPipeSide* pipe, void* data, size_t length, FuriWait timeout) { furi_check(pipe); + furi_pipe_side_lock(pipe); FuriStreamBuffer* buffer = pipe->receiving; - if(!buffer) return 0; - return furi_stream_buffer_receive(buffer, data, length, timeout); + size_t received = 0; + if(buffer) received = furi_stream_buffer_receive(buffer, data, length, timeout); + furi_pipe_side_unlock(pipe); + return received; } size_t furi_pipe_send(FuriPipeSide* pipe, const void* data, size_t length, FuriWait timeout) { furi_check(pipe); + furi_pipe_side_lock(pipe); FuriStreamBuffer* buffer = pipe->sending; - if(!buffer) return 0; - return furi_stream_buffer_send(buffer, data, length, timeout); + size_t sent = 0; + if(buffer) sent = furi_stream_buffer_send(buffer, data, length, timeout); + furi_pipe_side_unlock(pipe); + return sent; } size_t furi_pipe_bytes_available(FuriPipeSide* pipe) { furi_check(pipe); + furi_pipe_side_lock(pipe); FuriStreamBuffer* buffer = pipe->receiving; - if(!buffer) return 0; - return furi_stream_buffer_bytes_available(buffer); + size_t available = 0; + if(buffer) available = furi_stream_buffer_bytes_available(buffer); + furi_pipe_side_unlock(pipe); + return available; } size_t furi_pipe_spaces_available(FuriPipeSide* pipe) { furi_check(pipe); + furi_pipe_side_lock(pipe); FuriStreamBuffer* buffer = pipe->sending; - if(!buffer) return 0; - return furi_stream_buffer_spaces_available(buffer); + size_t available = 0; + if(buffer) available = furi_stream_buffer_spaces_available(buffer); + furi_pipe_side_unlock(pipe); + return available; } void furi_pipe_weld(FuriPipeSide* side_1, FuriPipeSide* side_2) { @@ -205,7 +236,12 @@ void furi_pipe_weld(FuriPipeSide* side_1, FuriPipeSide* side_2) { furi_check(side_1); furi_check(side_2); - FURI_CRITICAL_ENTER(); + // both sides must be weldable + furi_check(side_1->mutex); + furi_check(side_2->mutex); + + furi_pipe_side_lock(side_1); + furi_pipe_side_lock(side_2); // cannot weld an already welded side furi_check(side_1->role != FuriPipeRoleJoint); @@ -223,6 +259,18 @@ void furi_pipe_weld(FuriPipeSide* side_1, FuriPipeSide* side_2) { FuriPipeChain* left_chain = intermediate_bob->chain; FuriPipeChain* right_chain = intermediate_alice->chain; + // lock all sides in both chains + for + M_EACH(side, left_chain->pipe_sides, PipeSideArray_t) { + // already locked Bob near the beginning + if(*side != intermediate_bob) furi_pipe_side_lock(*side); + } + for + M_EACH(side, right_chain->pipe_sides, PipeSideArray_t) { + // already locked Alice near the beginning + if(*side != intermediate_alice) furi_pipe_side_lock(*side); + } + // copy residual data do { size_t buf_size = @@ -269,7 +317,11 @@ void furi_pipe_weld(FuriPipeSide* side_1, FuriPipeSide* side_2) { chain_alice->sending = left_chain->alice_to_bob; chain_bob->sending = left_chain->bob_to_alice; - FURI_CRITICAL_EXIT(); + // unlock all sides + for + M_EACH(side, left_chain->pipe_sides, PipeSideArray_t) { + furi_pipe_side_unlock(*side); + } } void furi_pipe_unweld(FuriPipeSide* side) { diff --git a/furi/core/pipe.h b/furi/core/pipe.h index 25b4b8ccf4a..f48db6fb83d 100644 --- a/furi/core/pipe.h +++ b/furi/core/pipe.h @@ -65,6 +65,20 @@ typedef struct { size_t trigger_level; } FuriPipeDirectionSettings; +/** + * Controls whether the pipe should support welding or not. This decision should + * depend on your use case for the pipes: + * - If you never want to weld pipes, use non-weldable pipes, as they will be + * faster. + * - If you want to copy data between pipes, use weldable pipes and weld them + * together, as that is faster and more memory efficient than manually + * copying data around. + */ +typedef enum { + FuriPipeWeldingCapEnabled, + FuriPipeWeldingCapDisabled, +} FuriPipeWeldingCap; + /** * @brief Allocates two connected sides of one pipe. * @@ -73,8 +87,8 @@ typedef struct { * together. * * The capacity and trigger level for both directions are the same when the pipe - * is created using this function. Use `furi_pipe_alloc_ex` if you want more - * control. + * is created using this function. Welding support is enabled, which might be + * undesirable. Use `furi_pipe_alloc_ex` if you want more control. */ FuriPipe furi_pipe_alloc(size_t capacity, size_t trigger_level); @@ -86,10 +100,14 @@ FuriPipe furi_pipe_alloc(size_t capacity, size_t trigger_level); * together. * * The capacity and trigger level may be different for the two directions when - * the pipe is created using this function. Use `furi_pipe_alloc` if you don't - * need control this fine. + * the pipe is created using this function. You can enable or disable welding + * support, optimizing performance for your exact use case. Use + * `furi_pipe_alloc` if you don't need control this fine. */ -FuriPipe furi_pipe_alloc_ex(FuriPipeDirectionSettings to_alice, FuriPipeDirectionSettings to_bob); +FuriPipe furi_pipe_alloc_ex( + FuriPipeWeldingCap welding_cap, + FuriPipeDirectionSettings to_alice, + FuriPipeDirectionSettings to_bob); /** * @brief Gets the role of a pipe side. diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 613f2e2f3ad..35506b23479 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1772,7 +1772,7 @@ Function,+,furi_mutex_free,void,FuriMutex* Function,+,furi_mutex_get_owner,FuriThreadId,FuriMutex* Function,+,furi_mutex_release,FuriStatus,FuriMutex* Function,+,furi_pipe_alloc,FuriPipe,"size_t, size_t" -Function,+,furi_pipe_alloc_ex,FuriPipe,"FuriPipeDirectionSettings, FuriPipeDirectionSettings" +Function,+,furi_pipe_alloc_ex,FuriPipe,"FuriPipeWeldingCap, FuriPipeDirectionSettings, FuriPipeDirectionSettings" Function,+,furi_pipe_bytes_available,size_t,FuriPipeSide* Function,+,furi_pipe_free,void,FuriPipeSide* Function,+,furi_pipe_install_as_stdio,void,FuriPipeSide* From f792fdec1bd08fe07df408b59b3d7c6453033dbe Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Tue, 12 Nov 2024 14:54:53 +0300 Subject: [PATCH 07/47] Revert welding --- .../tests/furi/furi_primitives_test.c | 57 ---- .../debug/unit_tests/tests/furi/furi_test.c | 2 +- furi/core/pipe.c | 274 +++--------------- furi/core/pipe.h | 67 +---- targets/f7/api_symbols.csv | 4 +- 5 files changed, 44 insertions(+), 360 deletions(-) diff --git a/applications/debug/unit_tests/tests/furi/furi_primitives_test.c b/applications/debug/unit_tests/tests/furi/furi_primitives_test.c index 3bd7e6145dc..64c7c2af311 100644 --- a/applications/debug/unit_tests/tests/furi/furi_primitives_test.c +++ b/applications/debug/unit_tests/tests/furi/furi_primitives_test.c @@ -138,61 +138,6 @@ static void test_furi_pipe(TestFuriPrimitivesData* data) { furi_pipe_free(bob); } -static void test_furi_pipe_welding(void) { - FuriPipe pipes[10]; - for(size_t i = 0; i < COUNT_OF(pipes); i++) { - pipes[i] = furi_pipe_alloc(PIPE_SIZE, PIPE_TRG_LEVEL); - mu_assert_int_eq(FuriPipeRoleAlice, furi_pipe_role(pipes[i].alices_side)); - mu_assert_int_eq(FuriPipeRoleBob, furi_pipe_role(pipes[i].bobs_side)); - } - - FuriPipeSide* alice = pipes[0].alices_side; - FuriPipeSide* bob = pipes[COUNT_OF(pipes) - 1].bobs_side; - const uint8_t src_buf[4] = {0xDE, 0xAD, 0xBE, 0xEF}; - furi_pipe_send(alice, src_buf, sizeof(src_buf), FuriWaitForever); - - for(size_t i = 0; i < COUNT_OF(pipes) - 1; i++) { - furi_pipe_weld(pipes[i].bobs_side, pipes[i + 1].alices_side); // lots of sparks! - mu_assert_int_eq(FuriPipeRoleJoint, furi_pipe_role(pipes[i].bobs_side)); - mu_assert_int_eq(FuriPipeRoleJoint, furi_pipe_role(pipes[i + 1].alices_side)); - } - mu_assert_int_eq(FuriPipeRoleAlice, furi_pipe_role(alice)); - mu_assert_int_eq(FuriPipeRoleBob, furi_pipe_role(bob)); - - // make sure that residual data is copied over - uint8_t buf[sizeof(src_buf)]; - furi_pipe_receive(bob, buf, sizeof(buf), FuriWaitForever); - mu_assert_mem_eq(src_buf, buf, sizeof(buf)); - - for(uint8_t i = 0;; ++i) { - mu_assert_int_eq(PIPE_SIZE - i, furi_pipe_spaces_available(alice)); - mu_assert_int_eq(i, furi_pipe_bytes_available(bob)); - - if(furi_pipe_send(alice, &i, sizeof(uint8_t), 0) != sizeof(uint8_t)) { - break; - } - - mu_assert_int_eq(PIPE_SIZE - i, furi_pipe_spaces_available(bob)); - mu_assert_int_eq(i, furi_pipe_bytes_available(alice)); - - if(furi_pipe_send(bob, &i, sizeof(uint8_t), 0) != sizeof(uint8_t)) { - break; - } - } - - // make sure that new data reaches its destination - for(uint8_t i = 0;; ++i) { - mu_assert_int_eq(PIPE_SIZE - i, furi_pipe_bytes_available(bob)); - - uint8_t value; - if(furi_pipe_receive(bob, &value, sizeof(uint8_t), 0) != sizeof(uint8_t)) { - break; - } - - mu_assert_int_eq(i, value); - } -} - // This is a stub that needs expanding void test_furi_primitives(void) { TestFuriPrimitivesData data = { @@ -208,6 +153,4 @@ void test_furi_primitives(void) { furi_message_queue_free(data.message_queue); furi_stream_buffer_free(data.stream_buffer); - - test_furi_pipe_welding(); } diff --git a/applications/debug/unit_tests/tests/furi/furi_test.c b/applications/debug/unit_tests/tests/furi/furi_test.c index 0a18ada8bbe..f23be37a974 100644 --- a/applications/debug/unit_tests/tests/furi/furi_test.c +++ b/applications/debug/unit_tests/tests/furi/furi_test.c @@ -43,7 +43,7 @@ MU_TEST(mu_test_furi_memmgr) { } MU_TEST(mu_test_furi_event_loop) { - // test_furi_event_loop(); + test_furi_event_loop(); } MU_TEST(mu_test_errno_saving) { diff --git a/furi/core/pipe.c b/furi/core/pipe.c index 35093a17fdf..e343c7f41a3 100644 --- a/furi/core/pipe.c +++ b/furi/core/pipe.c @@ -1,136 +1,84 @@ #include "pipe.h" #include "stream_buffer.h" +#include "semaphore.h" #include "mutex.h" #include "check.h" #include "memmgr.h" -#include - -ARRAY_DEF(PipeSideArray, FuriPipeSide*, M_PTR_OPLIST); -#define M_OPL_PipeSideArray_t() ARRAY_OPLIST(PipeSideArray) /** - * A chain of pipes that have been welded together. Initially, pipes consist of - * a chain of length 1. + * There are two PipeSides, both of which point to the same primitives */ -typedef struct { - FuriStreamBuffer* alice_to_bob; - FuriStreamBuffer* bob_to_alice; - PipeSideArray_t pipe_sides; // pipe_sides); +FuriPipe furi_pipe_alloc_ex(FuriPipeSideReceiveSettings alice, FuriPipeSideReceiveSettings bob) { + // the underlying primitives are shared + FuriStreamBuffer* alice_to_bob = furi_stream_buffer_alloc(bob.capacity, bob.trigger_level); + FuriStreamBuffer* bob_to_alice = furi_stream_buffer_alloc(alice.capacity, alice.trigger_level); + FuriSemaphore* instance_count = furi_semaphore_alloc(1, 1); + FuriMutex* state_transition = furi_mutex_alloc(FuriMutexTypeNormal); FuriPipeSide* alices_side = malloc(sizeof(FuriPipeSide)); FuriPipeSide* bobs_side = malloc(sizeof(FuriPipeSide)); - PipeSideArray_push_back(chain->pipe_sides, alices_side); - PipeSideArray_push_back(chain->pipe_sides, bobs_side); *alices_side = (FuriPipeSide){ - .mutex = (welding_cap == FuriPipeWeldingCapEnabled) ? - furi_mutex_alloc(FuriMutexTypeRecursive) : - NULL, .role = FuriPipeRoleAlice, - .chain = chain, .sending = alice_to_bob, .receiving = bob_to_alice, - .send_settings = to_bob, + .instance_count = instance_count, + .state_transition = state_transition, }; *bobs_side = (FuriPipeSide){ - .mutex = (welding_cap == FuriPipeWeldingCapEnabled) ? - furi_mutex_alloc(FuriMutexTypeNormal) : - NULL, .role = FuriPipeRoleBob, - .chain = chain, .sending = bob_to_alice, .receiving = alice_to_bob, - .send_settings = to_alice, + .instance_count = instance_count, + .state_transition = state_transition, }; return (FuriPipe){.alices_side = alices_side, .bobs_side = bobs_side}; } -static FURI_ALWAYS_INLINE void furi_pipe_side_lock(FuriPipeSide* pipe) { - if(pipe->mutex) furi_mutex_acquire(pipe->mutex, FuriWaitForever); -} - -static FURI_ALWAYS_INLINE void furi_pipe_side_unlock(FuriPipeSide* pipe) { - if(pipe->mutex) furi_mutex_release(pipe->mutex); -} - FuriPipeRole furi_pipe_role(FuriPipeSide* pipe) { furi_check(pipe); - furi_pipe_side_lock(pipe); - FuriPipeRole role = pipe->role; - furi_pipe_side_unlock(pipe); - return role; + return pipe->role; } FuriPipeState furi_pipe_state(FuriPipeSide* pipe) { furi_check(pipe); - furi_pipe_side_lock(pipe); - size_t sides = PipeSideArray_size(pipe->chain->pipe_sides); - furi_pipe_side_unlock(pipe); - return (sides % 2) ? FuriPipeStateBroken : FuriPipeStateOpen; + uint32_t count = furi_semaphore_get_count(pipe->instance_count); + return (count == 1) ? FuriPipeStateOpen : FuriPipeStateBroken; } void furi_pipe_free(FuriPipeSide* pipe) { furi_check(pipe); - furi_pipe_side_lock(pipe); - furi_check(pipe->role != FuriPipeRoleJoint); // unweld first - - size_t sides = PipeSideArray_size(pipe->chain->pipe_sides); - furi_check(sides <= 2); // TODO: support chains! + furi_mutex_acquire(pipe->state_transition, FuriWaitForever); + FuriStatus status = furi_semaphore_acquire(pipe->instance_count, 0); - if(sides == 1) { + if(status == FuriStatusOk) { + // the other side is still intact + furi_mutex_release(pipe->state_transition); + free(pipe); + } else { // the other side is gone too furi_stream_buffer_free(pipe->sending); furi_stream_buffer_free(pipe->receiving); - PipeSideArray_clear(pipe->chain->pipe_sides); - if(pipe->mutex) furi_mutex_free(pipe->mutex); - free(pipe->chain); - free(pipe); - } else { - // the other side is still intact - PipeSideArray_it_t it; - for(PipeSideArray_it(it, pipe->chain->pipe_sides); !PipeSideArray_end_p(it); - PipeSideArray_next(it)) { - if(*PipeSideArray_cref(it) == pipe) break; - } - furi_check(!PipeSideArray_end_p(it)); - PipeSideArray_remove(pipe->chain->pipe_sides, it); - if(pipe->mutex) furi_mutex_free(pipe->mutex); + furi_semaphore_free(pipe->instance_count); + furi_mutex_free(pipe->state_transition); free(pipe); } } @@ -144,7 +92,7 @@ static void _furi_pipe_stdout_cb(const char* data, size_t size, void* context) { static size_t _furi_pipe_stdin_cb(char* data, size_t size, FuriWait timeout, void* context) { furi_assert(context); FuriPipeSide* pipe = context; - return furi_stream_buffer_receive(pipe->receiving, data, size, timeout); + return furi_stream_buffer_receive(pipe->sending, data, size, timeout); } void furi_pipe_install_as_stdio(FuriPipeSide* pipe) { @@ -155,176 +103,20 @@ void furi_pipe_install_as_stdio(FuriPipeSide* pipe) { size_t furi_pipe_receive(FuriPipeSide* pipe, void* data, size_t length, FuriWait timeout) { furi_check(pipe); - furi_pipe_side_lock(pipe); - FuriStreamBuffer* buffer = pipe->receiving; - size_t received = 0; - if(buffer) received = furi_stream_buffer_receive(buffer, data, length, timeout); - furi_pipe_side_unlock(pipe); - return received; + return furi_stream_buffer_receive(pipe->receiving, data, length, timeout); } size_t furi_pipe_send(FuriPipeSide* pipe, const void* data, size_t length, FuriWait timeout) { furi_check(pipe); - furi_pipe_side_lock(pipe); - FuriStreamBuffer* buffer = pipe->sending; - size_t sent = 0; - if(buffer) sent = furi_stream_buffer_send(buffer, data, length, timeout); - furi_pipe_side_unlock(pipe); - return sent; + return furi_stream_buffer_send(pipe->sending, data, length, timeout); } size_t furi_pipe_bytes_available(FuriPipeSide* pipe) { furi_check(pipe); - furi_pipe_side_lock(pipe); - FuriStreamBuffer* buffer = pipe->receiving; - size_t available = 0; - if(buffer) available = furi_stream_buffer_bytes_available(buffer); - furi_pipe_side_unlock(pipe); - return available; + return furi_stream_buffer_bytes_available(pipe->receiving); } size_t furi_pipe_spaces_available(FuriPipeSide* pipe) { furi_check(pipe); - furi_pipe_side_lock(pipe); - FuriStreamBuffer* buffer = pipe->sending; - size_t available = 0; - if(buffer) available = furi_stream_buffer_spaces_available(buffer); - furi_pipe_side_unlock(pipe); - return available; -} - -void furi_pipe_weld(FuriPipeSide* side_1, FuriPipeSide* side_2) { - // Here's a pipe: - // - // | | - // s |=========| r - // ----|---->----|---- - // ----|----<----|---- - // r |=========| s - // | | - // A B - // - // It's got two sides (_A_lice and _B_ob) and two StreamBuffers backing it (A to B and B to A). - // From Alice's perspective, A>B is the _s_ending stream, and A----|---- ----|---->----|---- - // ----|----<----|---- ----|----<----|---- - // r |=========| s r |=========| s - // | | | | - // A B A B - // - // We want to "weld" faces iB and iA ("intermediate" Alice and Bob), forming a new "pipe chain" - // with the ends cA and cB ("chain" Alice and Bob): - // - // | | | | - // s |=========|=====|=========| r - // ----|------------>------------|---- - // ----|------------<------------|---- - // r |=========|=====|=========| s - // | | | | - // cA iB iA cB - // - // By only using one StreamBuffer per direction for two welded pipes, we can avoid having to - // copy data around and increase performance. At the cost, of course, of no longer being able - // to inspect, inject, modify or otherwise do anything with the data at faces iB and iA. - - furi_check(side_1); - furi_check(side_2); - - // both sides must be weldable - furi_check(side_1->mutex); - furi_check(side_2->mutex); - - furi_pipe_side_lock(side_1); - furi_pipe_side_lock(side_2); - - // cannot weld an already welded side - furi_check(side_1->role != FuriPipeRoleJoint); - furi_check(side_2->role != FuriPipeRoleJoint); - - // can only weld an Alice to a Bob - furi_check(side_1->role != side_2->role); - - FuriPipeSide* intermediate_alice = (side_1->role == FuriPipeRoleAlice) ? side_1 : side_2; - FuriPipeSide* intermediate_bob = (side_2->role == FuriPipeRoleBob) ? side_2 : side_1; - - // cannot weld two ends of the same chain - furi_check(intermediate_alice->chain != intermediate_bob->chain); - - FuriPipeChain* left_chain = intermediate_bob->chain; - FuriPipeChain* right_chain = intermediate_alice->chain; - - // lock all sides in both chains - for - M_EACH(side, left_chain->pipe_sides, PipeSideArray_t) { - // already locked Bob near the beginning - if(*side != intermediate_bob) furi_pipe_side_lock(*side); - } - for - M_EACH(side, right_chain->pipe_sides, PipeSideArray_t) { - // already locked Alice near the beginning - if(*side != intermediate_alice) furi_pipe_side_lock(*side); - } - - // copy residual data - do { - size_t buf_size = - MAX(furi_stream_buffer_bytes_available(left_chain->alice_to_bob), - furi_stream_buffer_bytes_available(right_chain->bob_to_alice)); - uint8_t buf[buf_size]; - - size_t to_copy = furi_stream_buffer_receive(left_chain->alice_to_bob, buf, buf_size, 0); - furi_stream_buffer_send(right_chain->alice_to_bob, buf, to_copy, 0); - furi_check( - furi_stream_buffer_bytes_available(left_chain->alice_to_bob) == 0); // all data copied - - to_copy = furi_stream_buffer_receive(right_chain->bob_to_alice, buf, buf_size, 0); - furi_stream_buffer_send(left_chain->bob_to_alice, buf, to_copy, 0); - furi_check( - furi_stream_buffer_bytes_available(right_chain->bob_to_alice) == 0); // all data copied - } while(0); - - // concat right chain to left chain - for - M_EACH(side, right_chain->pipe_sides, PipeSideArray_t) { - (*side)->chain = left_chain; - PipeSideArray_push_back(left_chain->pipe_sides, *side); - } - - // free unneeded things - furi_stream_buffer_free(left_chain->alice_to_bob); - furi_stream_buffer_free(right_chain->bob_to_alice); - left_chain->alice_to_bob = right_chain->alice_to_bob; - PipeSideArray_clear(right_chain->pipe_sides); - free(right_chain); - - // update intermediate sides - intermediate_bob->role = FuriPipeRoleJoint; - intermediate_bob->sending = NULL; - intermediate_bob->receiving = NULL; - intermediate_alice->role = FuriPipeRoleJoint; - intermediate_alice->sending = NULL; - intermediate_alice->receiving = NULL; - - // update endpoint (chain) sides - FuriPipeSide* chain_alice = *PipeSideArray_front(left_chain->pipe_sides); - FuriPipeSide* chain_bob = *PipeSideArray_back(left_chain->pipe_sides); - chain_alice->sending = left_chain->alice_to_bob; - chain_bob->sending = left_chain->bob_to_alice; - - // unlock all sides - for - M_EACH(side, left_chain->pipe_sides, PipeSideArray_t) { - furi_pipe_side_unlock(*side); - } -} - -void furi_pipe_unweld(FuriPipeSide* side) { - UNUSED(side); - furi_crash("unimplemented"); // TODO: + return furi_stream_buffer_spaces_available(pipe->sending); } diff --git a/furi/core/pipe.h b/furi/core/pipe.h index f48db6fb83d..8a7ee38f664 100644 --- a/furi/core/pipe.h +++ b/furi/core/pipe.h @@ -19,17 +19,13 @@ extern "C" { /** * @brief The role of a pipe side * - * Alice and Bob are equal, as they can both read and write the data. This - * status might be helpful in determining the role of a thread w.r.t. another - * thread in an application that builds on the pipe. - * - * Joints only allow the `unweld` operation. For more info, see - * `furi_pipe_weld`. + * Both roles are equal, as they can both read and write the data. This status + * might be helpful in determining the role of a thread w.r.t. another thread in + * an application that builds on the pipe. */ typedef enum { FuriPipeRoleAlice, FuriPipeRoleBob, - FuriPipeRoleJoint, } FuriPipeRole; /** @@ -41,8 +37,6 @@ typedef enum { * - `FuriPipeStateBroken`: The other side of the pipe has been freed, meaning * data that is written will never reach its destination, and no new data * will appear in the buffer. - * - `FuriPipeStateWelded`: The side of the pipe functions as a joint between - * two pipes. For more info, see `furi_pipe_weld`. * * A broken pipe can never become open again, because there's no way to connect * a side of a pipe to another side of a pipe. @@ -50,7 +44,6 @@ typedef enum { typedef enum { FuriPipeStateOpen, FuriPipeStateBroken, - FuriPipeStateWelded, } FuriPipeState; typedef struct FuriPipeSide FuriPipeSide; @@ -63,21 +56,7 @@ typedef struct { typedef struct { size_t capacity; size_t trigger_level; -} FuriPipeDirectionSettings; - -/** - * Controls whether the pipe should support welding or not. This decision should - * depend on your use case for the pipes: - * - If you never want to weld pipes, use non-weldable pipes, as they will be - * faster. - * - If you want to copy data between pipes, use weldable pipes and weld them - * together, as that is faster and more memory efficient than manually - * copying data around. - */ -typedef enum { - FuriPipeWeldingCapEnabled, - FuriPipeWeldingCapDisabled, -} FuriPipeWeldingCap; +} FuriPipeSideReceiveSettings; /** * @brief Allocates two connected sides of one pipe. @@ -87,8 +66,8 @@ typedef enum { * together. * * The capacity and trigger level for both directions are the same when the pipe - * is created using this function. Welding support is enabled, which might be - * undesirable. Use `furi_pipe_alloc_ex` if you want more control. + * is created using this function. Use `furi_pipe_alloc_ex` if you want more + * control. */ FuriPipe furi_pipe_alloc(size_t capacity, size_t trigger_level); @@ -100,14 +79,10 @@ FuriPipe furi_pipe_alloc(size_t capacity, size_t trigger_level); * together. * * The capacity and trigger level may be different for the two directions when - * the pipe is created using this function. You can enable or disable welding - * support, optimizing performance for your exact use case. Use - * `furi_pipe_alloc` if you don't need control this fine. + * the pipe is created using this function. Use `furi_pipe_alloc` if you don't + * need control this fine. */ -FuriPipe furi_pipe_alloc_ex( - FuriPipeWeldingCap welding_cap, - FuriPipeDirectionSettings to_alice, - FuriPipeDirectionSettings to_bob); +FuriPipe furi_pipe_alloc_ex(FuriPipeSideReceiveSettings alice, FuriPipeSideReceiveSettings bob); /** * @brief Gets the role of a pipe side. @@ -172,30 +147,6 @@ size_t furi_pipe_bytes_available(FuriPipeSide* pipe); */ size_t furi_pipe_spaces_available(FuriPipeSide* pipe); -/** - * @brief Welds two sides of different pipes together. - * - * When two sides of a pipe are welded together, data that appears at `side_1` - * is automatically pushed into `side_2` and vice versa. This connection may be - * undone using `furi_pipe_unweld`. - * - * While a side of a pipe is welded to another, it is impossible to use any of - * the methods to inspect and/or modify the data flowing through the joint: - * - `send` and `receive` become no-ops and return 0, - * - `bytes_available` and `spaces_available` return 0. - * - * You cannot weld an Alice to an Alice or a Bob to a Bob. You can only weld an - * Alice to a Bob. - */ -void furi_pipe_weld(FuriPipeSide* side_1, FuriPipeSide* side_2); - -/** - * @brief Undoes a `weld` operation. - * - * See `furi_pipe_weld`. - */ -void furi_pipe_unweld(FuriPipeSide* side); - #ifdef __cplusplus } #endif diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 35506b23479..1eb34efa8c3 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1772,7 +1772,7 @@ Function,+,furi_mutex_free,void,FuriMutex* Function,+,furi_mutex_get_owner,FuriThreadId,FuriMutex* Function,+,furi_mutex_release,FuriStatus,FuriMutex* Function,+,furi_pipe_alloc,FuriPipe,"size_t, size_t" -Function,+,furi_pipe_alloc_ex,FuriPipe,"FuriPipeWeldingCap, FuriPipeDirectionSettings, FuriPipeDirectionSettings" +Function,+,furi_pipe_alloc_ex,FuriPipe,"FuriPipeSideReceiveSettings, FuriPipeSideReceiveSettings" Function,+,furi_pipe_bytes_available,size_t,FuriPipeSide* Function,+,furi_pipe_free,void,FuriPipeSide* Function,+,furi_pipe_install_as_stdio,void,FuriPipeSide* @@ -1781,8 +1781,6 @@ Function,+,furi_pipe_role,FuriPipeRole,FuriPipeSide* Function,+,furi_pipe_send,size_t,"FuriPipeSide*, const void*, size_t, FuriWait" Function,+,furi_pipe_spaces_available,size_t,FuriPipeSide* Function,+,furi_pipe_state,FuriPipeState,FuriPipeSide* -Function,+,furi_pipe_unweld,void,FuriPipeSide* -Function,+,furi_pipe_weld,void,"FuriPipeSide*, FuriPipeSide*" Function,+,furi_pubsub_alloc,FuriPubSub*, Function,+,furi_pubsub_free,void,FuriPubSub* Function,+,furi_pubsub_publish,void,"FuriPubSub*, void*" From 1c9dbb3635ee77f6dfd56b31922ce214fed0ff83 Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Tue, 12 Nov 2024 15:13:23 +0300 Subject: [PATCH 08/47] docs: furi_pipe --- furi/core/pipe.h | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/furi/core/pipe.h b/furi/core/pipe.h index 8a7ee38f664..5e212a90b5e 100644 --- a/furi/core/pipe.h +++ b/furi/core/pipe.h @@ -68,6 +68,11 @@ typedef struct { * The capacity and trigger level for both directions are the same when the pipe * is created using this function. Use `furi_pipe_alloc_ex` if you want more * control. + * + * @param capacity Maximum number of bytes buffered in one direction + * @param trigger_level Number of bytes that need to be available in the buffer + * in order for a blocked thread to unblock + * @returns Bundle with both sides of the pipe */ FuriPipe furi_pipe_alloc(size_t capacity, size_t trigger_level); @@ -81,6 +86,11 @@ FuriPipe furi_pipe_alloc(size_t capacity, size_t trigger_level); * The capacity and trigger level may be different for the two directions when * the pipe is created using this function. Use `furi_pipe_alloc` if you don't * need control this fine. + * + * @param alice `capacity` and `trigger_level` settings for Alice's receiving + * buffer + * @param bob `capacity` and `trigger_level` settings for Bob's receiving buffer + * @returns Bundle with both sides of the pipe */ FuriPipe furi_pipe_alloc_ex(FuriPipeSideReceiveSettings alice, FuriPipeSideReceiveSettings bob); @@ -90,6 +100,9 @@ FuriPipe furi_pipe_alloc_ex(FuriPipeSideReceiveSettings alice, FuriPipeSideRecei * The roles (Alice and Bob) are equal, as both can send and receive data. This * status might be helpful in determining the role of a thread w.r.t. another * thread. + * + * @param [in] pipe Pipe side to query + * @returns Role of provided pipe side */ FuriPipeRole furi_pipe_role(FuriPipeSide* pipe); @@ -101,6 +114,9 @@ FuriPipeRole furi_pipe_role(FuriPipeSide* pipe); * active (the one that this method has been called on). If you find yourself in * that state, the data that you send will never be heard by anyone, and the * data you receive are leftovers in the buffer. + * + * @param [in] pipe Pipe side to query + * @returns State of the pipe */ FuriPipeState furi_pipe_state(FuriPipeSide* pipe); @@ -110,6 +126,8 @@ FuriPipeState furi_pipe_state(FuriPipeSide* pipe); * When only one of the sides is freed, the pipe is transitioned from the "Open" * state into the "Broken" state. When both sides are freed, the underlying data * structures are freed too. + * + * @param [in] pipe Pipe side to free */ void furi_pipe_free(FuriPipeSide* pipe); @@ -123,27 +141,49 @@ void furi_pipe_free(FuriPipeSide* pipe); * You can disconnect the pipe by manually calling * `furi_thread_set_stdout_callback` and `furi_thread_set_stdin_callback` with * `NULL`. + * + * @param [in] pipe Pipe side to connect to the stdio */ void furi_pipe_install_as_stdio(FuriPipeSide* pipe); /** * @brief Receives data from the pipe. + * + * @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 furi_pipe_receive(FuriPipeSide* pipe, void* data, size_t length, FuriWait timeout); /** * @brief Sends data into the pipe. + * + * @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 furi_pipe_send(FuriPipeSide* pipe, const void* data, size_t length, FuriWait timeout); /** * @brief Determines how many bytes there are in the pipe available to be read. + * + * @param [in] Pipe side to query + * @returns Number of bytes available to be read out from that side of the pipe */ size_t furi_pipe_bytes_available(FuriPipeSide* pipe); /** * @brief Determines how many space there is in the pipe for data to be written * into. + * + * @param [in] Pipe side to query + * @returns Number of bytes available to be written into that side of the pipe */ size_t furi_pipe_spaces_available(FuriPipeSide* pipe); From bd283874bbb343122a9fd1ec7e75bcaddb13ea24 Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Tue, 12 Nov 2024 16:13:13 +0300 Subject: [PATCH 09/47] feat: pipe event loop integration --- .../tests/furi/furi_event_loop_test.c | 90 ++++++++++++++++++- .../tests/furi/furi_primitives_test.c | 1 + furi/core/event_loop.c | 12 +++ furi/core/event_loop.h | 27 +++++- furi/core/pipe.c | 84 +++++++++++++---- furi/core/stream_buffer.c | 5 ++ furi/core/stream_buffer.h | 11 +++ targets/f7/api_symbols.csv | 2 + 8 files changed, 213 insertions(+), 19 deletions(-) diff --git a/applications/debug/unit_tests/tests/furi/furi_event_loop_test.c b/applications/debug/unit_tests/tests/furi/furi_event_loop_test.c index 73f38ab77f8..493c6942ba5 100644 --- a/applications/debug/unit_tests/tests/furi/furi_event_loop_test.c +++ b/applications/debug/unit_tests/tests/furi/furi_event_loop_test.c @@ -9,7 +9,7 @@ #define MESSAGE_COUNT (256UL) #define EVENT_FLAG_COUNT (23UL) -#define PRIMITIVE_COUNT (4UL) +#define PRIMITIVE_COUNT (5UL) #define RUN_COUNT (2UL) typedef struct { @@ -18,6 +18,7 @@ typedef struct { uint32_t stream_buffer_count; uint32_t event_flag_count; uint32_t semaphore_count; + uint32_t pipe_count; uint32_t primitives_tested; } TestFuriEventLoopThread; @@ -26,6 +27,7 @@ typedef struct { FuriStreamBuffer* stream_buffer; FuriEventFlag* event_flag; FuriSemaphore* semaphore; + FuriPipe pipe; TestFuriEventLoopThread producer; TestFuriEventLoopThread consumer; @@ -209,6 +211,43 @@ static void furi_delay_us(furi_hal_random_get() % 100); } +static void + test_furi_event_loop_producer_pipe_callback(FuriEventLoopObject* object, void* context) { + furi_check(context); + + TestFuriEventLoopData* data = context; + furi_check(data->pipe.alices_side == object); + + TestFuriEventLoopThread* producer = &data->producer; + TestFuriEventLoopThread* consumer = &data->consumer; + + FURI_LOG_I(TAG, "producer Pipe: %lu %lu", producer->pipe_count, consumer->pipe_count); + + if(producer->pipe_count == MESSAGE_COUNT / 2) { + furi_event_loop_unsubscribe(producer->event_loop, data->pipe.alices_side); + furi_event_loop_subscribe_pipe( + producer->event_loop, + data->pipe.alices_side, + FuriEventLoopEventOut, + test_furi_event_loop_producer_pipe_callback, + data); + + } else if(producer->pipe_count == MESSAGE_COUNT) { + furi_event_loop_unsubscribe(producer->event_loop, data->pipe.alices_side); + furi_event_loop_pend_callback( + producer->event_loop, test_furi_event_loop_pending_callback, producer); + return; + } + + producer->pipe_count++; + + furi_check( + furi_pipe_send(data->pipe.alices_side, &producer->pipe_count, sizeof(uint32_t), 0) == + sizeof(uint32_t)); + + furi_delay_us(furi_hal_random_get() % 100); +} + static int32_t test_furi_event_loop_producer(void* p) { furi_check(p); @@ -244,6 +283,12 @@ static int32_t test_furi_event_loop_producer(void* p) { FuriEventLoopEventOut, test_furi_event_loop_producer_semaphore_callback, data); + furi_event_loop_subscribe_pipe( + producer->event_loop, + data->pipe.alices_side, + FuriEventLoopEventOut, + test_furi_event_loop_producer_pipe_callback, + data); test_furi_event_loop_thread_run_and_cleanup(producer); } @@ -402,6 +447,40 @@ static void data->consumer.semaphore_count++; } +static void + test_furi_event_loop_consumer_pipe_callback(FuriEventLoopObject* object, void* context) { + furi_check(context); + + TestFuriEventLoopData* data = context; + furi_check(data->pipe.bobs_side == object); + + TestFuriEventLoopThread* producer = &data->producer; + TestFuriEventLoopThread* consumer = &data->consumer; + + furi_delay_us(furi_hal_random_get() % 100); + + furi_check( + furi_pipe_receive(data->pipe.bobs_side, &consumer->pipe_count, sizeof(uint32_t), 0) == + sizeof(uint32_t)); + + FURI_LOG_I(TAG, "consumer Pipe: %lu %lu", producer->pipe_count, consumer->pipe_count); + + if(consumer->pipe_count == MESSAGE_COUNT / 2) { + furi_event_loop_unsubscribe(consumer->event_loop, data->pipe.bobs_side); + furi_event_loop_subscribe_pipe( + consumer->event_loop, + data->pipe.bobs_side, + FuriEventLoopEventIn, + test_furi_event_loop_consumer_pipe_callback, + data); + + } else if(consumer->pipe_count == MESSAGE_COUNT) { + furi_event_loop_unsubscribe(data->consumer.event_loop, data->pipe.bobs_side); + furi_event_loop_pend_callback( + consumer->event_loop, test_furi_event_loop_pending_callback, consumer); + } +} + static int32_t test_furi_event_loop_consumer(void* p) { furi_check(p); @@ -437,6 +516,12 @@ static int32_t test_furi_event_loop_consumer(void* p) { FuriEventLoopEventIn, test_furi_event_loop_consumer_semaphore_callback, data); + furi_event_loop_subscribe_pipe( + consumer->event_loop, + data->pipe.bobs_side, + FuriEventLoopEventIn, + test_furi_event_loop_consumer_pipe_callback, + data); test_furi_event_loop_thread_run_and_cleanup(consumer); } @@ -453,6 +538,7 @@ void test_furi_event_loop(void) { data.stream_buffer = furi_stream_buffer_alloc(16, sizeof(uint32_t)); data.event_flag = furi_event_flag_alloc(); data.semaphore = furi_semaphore_alloc(8, 0); + data.pipe = furi_pipe_alloc(16, sizeof(uint32_t)); FuriThread* producer_thread = furi_thread_alloc_ex("producer_thread", 1 * 1024, test_furi_event_loop_producer, &data); @@ -487,4 +573,6 @@ void test_furi_event_loop(void) { furi_stream_buffer_free(data.stream_buffer); furi_event_flag_free(data.event_flag); furi_semaphore_free(data.semaphore); + furi_pipe_free(data.pipe.alices_side); + furi_pipe_free(data.pipe.bobs_side); } diff --git a/applications/debug/unit_tests/tests/furi/furi_primitives_test.c b/applications/debug/unit_tests/tests/furi/furi_primitives_test.c index 64c7c2af311..2a0a4df5874 100644 --- a/applications/debug/unit_tests/tests/furi/furi_primitives_test.c +++ b/applications/debug/unit_tests/tests/furi/furi_primitives_test.c @@ -153,4 +153,5 @@ void test_furi_primitives(void) { furi_message_queue_free(data.message_queue); furi_stream_buffer_free(data.stream_buffer); + // the pipe is freed by the test } diff --git a/furi/core/event_loop.c b/furi/core/event_loop.c index c0998ea902e..8ca6e0ca7c5 100644 --- a/furi/core/event_loop.c +++ b/furi/core/event_loop.c @@ -415,6 +415,18 @@ void furi_event_loop_subscribe_mutex( instance, mutex, &furi_mutex_event_loop_contract, event, callback, context); } +void furi_event_loop_subscribe_pipe( + FuriEventLoop* instance, + FuriPipeSide* pipe, + FuriEventLoopEvent event, + FuriEventLoopEventCallback callback, + void* context) { + extern const FuriEventLoopContract furi_pipe_event_loop_contract; + + furi_event_loop_object_subscribe( + instance, pipe, &furi_pipe_event_loop_contract, event, callback, context); +} + /** * Public generic unsubscription API */ diff --git a/furi/core/event_loop.h b/furi/core/event_loop.h index d5e8710a69e..b00637b2328 100644 --- a/furi/core/event_loop.h +++ b/furi/core/event_loop.h @@ -40,7 +40,8 @@ typedef enum { * - One or more items were inserted into a FuriMessageQueue, * - Enough data has been written to a FuriStreamBuffer, * - A FuriSemaphore has been released at least once, - * - A FuriMutex has been released. + * - A FuriMutex has been released, + * - Enough data is available to be read out from a FuriPipeSide. */ FuriEventLoopEventIn = 0x00000001U, /** @@ -50,7 +51,8 @@ typedef enum { * - One or more items were removed from a FuriMessageQueue, * - Any amount of data has been read out of a FuriStreamBuffer, * - A FuriSemaphore has been acquired at least once, - * - A FuriMutex has been acquired. + * - A FuriMutex has been acquired, + * - Any amount of data has been read out of a FuriPipeSide. */ FuriEventLoopEventOut = 0x00000002U, /** @@ -304,6 +306,27 @@ void furi_event_loop_subscribe_mutex( FuriEventLoopEventCallback callback, void* context); +/** Opaque pipe side type */ +typedef struct FuriPipeSide FuriPipeSide; + +/** + * Subscribe to pipe events + * + * @warning you can only have one subscription for one event type. + * + * @param instance The Event Loop instance + * @param pipe The Pipe to add + * @param[in] event The Event Loop event to trigger on + * @param[in] callback The callback to call on event + * @param context The context for callback + */ +void furi_event_loop_subscribe_pipe( + FuriEventLoop* instance, + FuriPipeSide* pipe, + FuriEventLoopEvent event, + FuriEventLoopEventCallback callback, + void* context); + /** Unsubscribe from events (common) * * @param instance The Event Loop instance diff --git a/furi/core/pipe.c b/furi/core/pipe.c index e343c7f41a3..264b015b1d0 100644 --- a/furi/core/pipe.c +++ b/furi/core/pipe.c @@ -4,16 +4,28 @@ #include "mutex.h" #include "check.h" #include "memmgr.h" +#include "event_loop_link_i.h" /** - * There are two PipeSides, both of which point to the same primitives + * Data shared between both sides. + */ +typedef struct { + FuriSemaphore* instance_count; // alice_event_loop_link, + .peer_event_loop_link = &shared->bob_event_loop_link, }; *bobs_side = (FuriPipeSide){ .role = FuriPipeRoleBob, + .shared = shared, .sending = bob_to_alice, .receiving = alice_to_bob, - .instance_count = instance_count, - .state_transition = state_transition, + .self_event_loop_link = &shared->bob_event_loop_link, + .peer_event_loop_link = &shared->alice_event_loop_link, }; return (FuriPipe){.alices_side = alices_side, .bobs_side = bobs_side}; @@ -59,26 +77,31 @@ FuriPipeRole furi_pipe_role(FuriPipeSide* pipe) { FuriPipeState furi_pipe_state(FuriPipeSide* pipe) { furi_check(pipe); - uint32_t count = furi_semaphore_get_count(pipe->instance_count); + uint32_t count = furi_semaphore_get_count(pipe->shared->instance_count); return (count == 1) ? FuriPipeStateOpen : FuriPipeStateBroken; } void furi_pipe_free(FuriPipeSide* pipe) { furi_check(pipe); - furi_mutex_acquire(pipe->state_transition, FuriWaitForever); - FuriStatus status = furi_semaphore_acquire(pipe->instance_count, 0); + // Event Loop must be disconnected + furi_check(!pipe->self_event_loop_link->item_in); + furi_check(!pipe->self_event_loop_link->item_out); + + furi_mutex_acquire(pipe->shared->state_transition, FuriWaitForever); + FuriStatus status = furi_semaphore_acquire(pipe->shared->instance_count, 0); if(status == FuriStatusOk) { // the other side is still intact - furi_mutex_release(pipe->state_transition); + furi_mutex_release(pipe->shared->state_transition); free(pipe); } else { // the other side is gone too furi_stream_buffer_free(pipe->sending); furi_stream_buffer_free(pipe->receiving); - furi_semaphore_free(pipe->instance_count); - furi_mutex_free(pipe->state_transition); + furi_semaphore_free(pipe->shared->instance_count); + furi_mutex_free(pipe->shared->state_transition); + free(pipe->shared); free(pipe); } } @@ -103,12 +126,17 @@ void furi_pipe_install_as_stdio(FuriPipeSide* pipe) { size_t furi_pipe_receive(FuriPipeSide* pipe, void* data, size_t length, FuriWait timeout) { furi_check(pipe); + furi_event_loop_link_notify(pipe->peer_event_loop_link, FuriEventLoopEventOut); return furi_stream_buffer_receive(pipe->receiving, data, length, timeout); } size_t furi_pipe_send(FuriPipeSide* pipe, const void* data, size_t length, FuriWait timeout) { furi_check(pipe); - return furi_stream_buffer_send(pipe->sending, data, length, timeout); + size_t sent = furi_stream_buffer_send(pipe->sending, data, length, timeout); + if(furi_stream_buffer_bytes_available(pipe->sending) >= + furi_stream_get_trigger_level(pipe->sending)) + furi_event_loop_link_notify(pipe->peer_event_loop_link, FuriEventLoopEventIn); + return sent; } size_t furi_pipe_bytes_available(FuriPipeSide* pipe) { @@ -120,3 +148,27 @@ size_t furi_pipe_spaces_available(FuriPipeSide* pipe) { furi_check(pipe); return furi_stream_buffer_spaces_available(pipe->sending); } + +static FuriEventLoopLink* furi_pipe_event_loop_get_link(FuriEventLoopObject* object) { + FuriPipeSide* instance = object; + furi_assert(instance); + return instance->self_event_loop_link; +} + +static bool furi_pipe_event_loop_get_level(FuriEventLoopObject* object, FuriEventLoopEvent event) { + FuriPipeSide* instance = object; + furi_assert(instance); + + if(event == FuriEventLoopEventIn) { + return furi_stream_buffer_bytes_available(instance->receiving); + } else if(event == FuriEventLoopEventOut) { + return furi_stream_buffer_spaces_available(instance->sending); + } else { + furi_crash(); + } +} + +const FuriEventLoopContract furi_pipe_event_loop_contract = { + .get_link = furi_pipe_event_loop_get_link, + .get_level = furi_pipe_event_loop_get_level, +}; diff --git a/furi/core/stream_buffer.c b/furi/core/stream_buffer.c index 783b2d7413d..902ec931c33 100644 --- a/furi/core/stream_buffer.c +++ b/furi/core/stream_buffer.c @@ -54,6 +54,11 @@ bool furi_stream_set_trigger_level(FuriStreamBuffer* stream_buffer, size_t trigg pdTRUE; } +size_t furi_stream_get_trigger_level(FuriStreamBuffer* stream_buffer) { + furi_check(stream_buffer); + return ((StaticStreamBuffer_t*)stream_buffer)->xTriggerLevelBytes; +} + size_t furi_stream_buffer_send( FuriStreamBuffer* stream_buffer, const void* data, diff --git a/furi/core/stream_buffer.h b/furi/core/stream_buffer.h index eef8ee51073..deca813c7e2 100644 --- a/furi/core/stream_buffer.h +++ b/furi/core/stream_buffer.h @@ -54,6 +54,17 @@ void furi_stream_buffer_free(FuriStreamBuffer* stream_buffer); */ bool furi_stream_set_trigger_level(FuriStreamBuffer* stream_buffer, size_t trigger_level); +/** + * @brief Get trigger level for stream buffer. + * A stream buffer's trigger level is the number of bytes that must be in the + * stream buffer before a task that is blocked on the stream buffer to + * wait for data is moved out of the blocked state. + * + * @param stream_buffer The stream buffer instance + * @return The trigger level for the stream buffer + */ +size_t furi_stream_get_trigger_level(FuriStreamBuffer* stream_buffer); + /** * @brief Sends bytes to a stream buffer. The bytes are copied into the stream buffer. * Wakes up task waiting for data to become available if called from ISR. diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 1eb34efa8c3..9e6269ada2f 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1238,6 +1238,7 @@ Function,+,furi_event_loop_stop,void,FuriEventLoop* Function,+,furi_event_loop_subscribe_event_flag,void,"FuriEventLoop*, FuriEventFlag*, FuriEventLoopEvent, FuriEventLoopEventCallback, void*" Function,+,furi_event_loop_subscribe_message_queue,void,"FuriEventLoop*, FuriMessageQueue*, FuriEventLoopEvent, FuriEventLoopEventCallback, void*" Function,+,furi_event_loop_subscribe_mutex,void,"FuriEventLoop*, FuriMutex*, FuriEventLoopEvent, FuriEventLoopEventCallback, void*" +Function,+,furi_event_loop_subscribe_pipe,void,"FuriEventLoop*, FuriPipeSide*, 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_tick_set,void,"FuriEventLoop*, uint32_t, FuriEventLoopTickCallback, void*" @@ -1808,6 +1809,7 @@ Function,+,furi_stream_buffer_receive,size_t,"FuriStreamBuffer*, void*, size_t, Function,+,furi_stream_buffer_reset,FuriStatus,FuriStreamBuffer* Function,+,furi_stream_buffer_send,size_t,"FuriStreamBuffer*, const void*, size_t, uint32_t" Function,+,furi_stream_buffer_spaces_available,size_t,FuriStreamBuffer* +Function,+,furi_stream_get_trigger_level,size_t,FuriStreamBuffer* Function,+,furi_stream_set_trigger_level,_Bool,"FuriStreamBuffer*, size_t" Function,+,furi_string_alloc,FuriString*, Function,+,furi_string_alloc_move,FuriString*,FuriString* From acf307d05bab93f99e57a97115992b1e76aa1e43 Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Tue, 12 Nov 2024 16:14:56 +0300 Subject: [PATCH 10/47] update f18 sdk --- targets/f18/api_symbols.csv | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 40e83094561..b02a78b92fe 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,79.1,, +Version,+,79.2,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, Header,+,applications/services/cli/cli.h,, @@ -1128,6 +1128,7 @@ Function,+,furi_event_loop_stop,void,FuriEventLoop* Function,+,furi_event_loop_subscribe_event_flag,void,"FuriEventLoop*, FuriEventFlag*, FuriEventLoopEvent, FuriEventLoopEventCallback, void*" Function,+,furi_event_loop_subscribe_message_queue,void,"FuriEventLoop*, FuriMessageQueue*, FuriEventLoopEvent, FuriEventLoopEventCallback, void*" Function,+,furi_event_loop_subscribe_mutex,void,"FuriEventLoop*, FuriMutex*, FuriEventLoopEvent, FuriEventLoopEventCallback, void*" +Function,+,furi_event_loop_subscribe_pipe,void,"FuriEventLoop*, FuriPipeSide*, 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_tick_set,void,"FuriEventLoop*, uint32_t, FuriEventLoopTickCallback, void*" @@ -1589,6 +1590,7 @@ Function,+,furi_stream_buffer_receive,size_t,"FuriStreamBuffer*, void*, size_t, Function,+,furi_stream_buffer_reset,FuriStatus,FuriStreamBuffer* Function,+,furi_stream_buffer_send,size_t,"FuriStreamBuffer*, const void*, size_t, uint32_t" Function,+,furi_stream_buffer_spaces_available,size_t,FuriStreamBuffer* +Function,+,furi_stream_get_trigger_level,size_t,FuriStreamBuffer* Function,+,furi_stream_set_trigger_level,_Bool,"FuriStreamBuffer*, size_t" Function,+,furi_string_alloc,FuriString*, Function,+,furi_string_alloc_move,FuriString*,FuriString* From 2bcc3c408a98801a46b7d233a028c61e1d94a2f2 Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Tue, 12 Nov 2024 16:19:19 +0300 Subject: [PATCH 11/47] f18 --- targets/f18/api_symbols.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index b02a78b92fe..f62be777a77 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,79.2,, +Version,+,79.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, Header,+,applications/services/cli/cli.h,, From af7ff8151f7c9071026d0ac34a4079e56217300a Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Tue, 12 Nov 2024 16:24:51 +0300 Subject: [PATCH 12/47] docs: make doxygen happy --- furi/core/pipe.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/furi/core/pipe.h b/furi/core/pipe.h index 5e212a90b5e..82c1a2d10c1 100644 --- a/furi/core/pipe.h +++ b/furi/core/pipe.h @@ -173,7 +173,7 @@ size_t furi_pipe_send(FuriPipeSide* pipe, const void* data, size_t length, FuriW /** * @brief Determines how many bytes there are in the pipe available to be read. * - * @param [in] Pipe side to query + * @param [in] pipe Pipe side to query * @returns Number of bytes available to be read out from that side of the pipe */ size_t furi_pipe_bytes_available(FuriPipeSide* pipe); @@ -182,7 +182,7 @@ size_t furi_pipe_bytes_available(FuriPipeSide* pipe); * @brief Determines how many space there is in the pipe for data to be written * into. * - * @param [in] Pipe side to query + * @param [in] pipe Pipe side to query * @returns Number of bytes available to be written into that side of the pipe */ size_t furi_pipe_spaces_available(FuriPipeSide* pipe); From 857ad33e1a5146befb1973c5e920d9b6cbf9fac6 Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Wed, 13 Nov 2024 15:52:14 +0300 Subject: [PATCH 13/47] fix: event loop not triggering when pipe attached to stdio --- furi/core/pipe.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/furi/core/pipe.c b/furi/core/pipe.c index 264b015b1d0..992861785d3 100644 --- a/furi/core/pipe.c +++ b/furi/core/pipe.c @@ -109,13 +109,13 @@ void furi_pipe_free(FuriPipeSide* pipe) { static void _furi_pipe_stdout_cb(const char* data, size_t size, void* context) { furi_assert(context); FuriPipeSide* pipe = context; - furi_check(furi_stream_buffer_send(pipe->sending, data, size, FuriWaitForever) == size); + furi_check(furi_pipe_send(pipe, data, size, FuriWaitForever) == size); } static size_t _furi_pipe_stdin_cb(char* data, size_t size, FuriWait timeout, void* context) { furi_assert(context); FuriPipeSide* pipe = context; - return furi_stream_buffer_receive(pipe->sending, data, size, timeout); + return furi_pipe_receive(pipe, data, size, timeout); } void furi_pipe_install_as_stdio(FuriPipeSide* pipe) { From bc9223aa2dca5caa6054ff78f9448fa079257a1c Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Wed, 13 Nov 2024 16:41:00 +0300 Subject: [PATCH 14/47] fix: partial stdout in pipe --- furi/core/pipe.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/furi/core/pipe.c b/furi/core/pipe.c index 992861785d3..6159e9a7dde 100644 --- a/furi/core/pipe.c +++ b/furi/core/pipe.c @@ -109,7 +109,11 @@ void furi_pipe_free(FuriPipeSide* pipe) { static void _furi_pipe_stdout_cb(const char* data, size_t size, void* context) { furi_assert(context); FuriPipeSide* pipe = context; - furi_check(furi_pipe_send(pipe, data, size, FuriWaitForever) == size); + while(size) { + size_t sent = furi_pipe_send(pipe, data, size, FuriWaitForever); + data += sent; + size -= sent; + } } static size_t _furi_pipe_stdin_cb(char* data, size_t size, FuriWait timeout, void* context) { From dac9c008db1425ae7e9740a16fe4960015e55e41 Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Wed, 13 Nov 2024 18:33:46 +0300 Subject: [PATCH 15/47] allow simultaneous in and out subscription in event loop --- furi/core/event_loop.c | 44 +++++++++++++++++++++++++++------------- furi/core/event_loop_i.h | 9 ++++++-- 2 files changed, 37 insertions(+), 16 deletions(-) diff --git a/furi/core/event_loop.c b/furi/core/event_loop.c index 8ca6e0ca7c5..23acd89a125 100644 --- a/furi/core/event_loop.c +++ b/furi/core/event_loop.c @@ -322,27 +322,34 @@ static void furi_event_loop_object_subscribe( FURI_CRITICAL_ENTER(); - furi_check(FuriEventLoopTree_get(instance->tree, object) == NULL); + // Get or create item pair + FuriEventLoopItemPair* item_pair_ptr = FuriEventLoopTree_get(instance->tree, object); + FuriEventLoopItemPair item_pair = item_pair_ptr ? *item_pair_ptr : + (FuriEventLoopItemPair){NULL, NULL}; // Allocate and setup item FuriEventLoopItem* item = furi_event_loop_item_alloc(instance, contract, object, event); furi_event_loop_item_set_callback(item, callback, context); - FuriEventLoopTree_set_at(instance->tree, object, item); - FuriEventLoopLink* link = item->contract->get_link(object); FuriEventLoopEvent event_noflags = item->event & FuriEventLoopEventMask; if(event_noflags == FuriEventLoopEventIn) { furi_check(link->item_in == NULL); + furi_check(item_pair.in == NULL); link->item_in = item; + item_pair.in = item; } else if(event_noflags == FuriEventLoopEventOut) { furi_check(link->item_out == NULL); + furi_check(item_pair.out == NULL); link->item_out = item; + item_pair.out = item; } else { furi_crash(); } + FuriEventLoopTree_set_at(instance->tree, object, item_pair); + if(!(item->event & FuriEventLoopEventFlagEdge)) { if(item->contract->get_level(item->object, event_noflags)) { furi_event_loop_item_notify(item); @@ -431,15 +438,10 @@ void furi_event_loop_subscribe_pipe( * Public generic unsubscription API */ -void furi_event_loop_unsubscribe(FuriEventLoop* instance, FuriEventLoopObject* object) { - furi_check(instance); - furi_check(instance->thread_id == furi_thread_get_current_id()); - - FURI_CRITICAL_ENTER(); - - FuriEventLoopItem* item = NULL; - furi_check(FuriEventLoopTree_pop_at(&item, instance->tree, object)); - +static void furi_event_loop_unsubscribe_item( + FuriEventLoop* instance, + FuriEventLoopObject* object, + FuriEventLoopItem* item) { furi_check(item); furi_check(item->owner == instance); @@ -465,6 +467,20 @@ void furi_event_loop_unsubscribe(FuriEventLoop* instance, FuriEventLoopObject* o } else { furi_event_loop_item_free(item); } +} + +void furi_event_loop_unsubscribe(FuriEventLoop* instance, FuriEventLoopObject* object) { + furi_check(instance); + furi_check(instance->thread_id == furi_thread_get_current_id()); + + FURI_CRITICAL_ENTER(); + + FuriEventLoopItemPair item_pair; + furi_check(FuriEventLoopTree_pop_at(&item_pair, instance->tree, object)); + furi_check(item_pair.in || item_pair.out); + + if(item_pair.in) furi_event_loop_unsubscribe_item(instance, object, item_pair.in); + if(item_pair.out) furi_event_loop_unsubscribe_item(instance, object, item_pair.out); FURI_CRITICAL_EXIT(); } @@ -474,8 +490,8 @@ bool furi_event_loop_is_subscribed(FuriEventLoop* instance, FuriEventLoopObject* furi_check(instance->thread_id == furi_thread_get_current_id()); FURI_CRITICAL_ENTER(); - FuriEventLoopItem* const* item = FuriEventLoopTree_cget(instance->tree, object); - bool result = !!item; + const FuriEventLoopItemPair* item_pair = FuriEventLoopTree_cget(instance->tree, object); + bool result = !!item_pair; FURI_CRITICAL_EXIT(); return result; diff --git a/furi/core/event_loop_i.h b/furi/core/event_loop_i.h index 7016e1e1bea..099bc42232b 100644 --- a/furi/core/event_loop_i.h +++ b/furi/core/event_loop_i.h @@ -33,13 +33,18 @@ ILIST_DEF(WaitingList, FuriEventLoopItem, M_POD_OPLIST) /* Event Loop RB tree */ #define FURI_EVENT_LOOP_TREE_RANK (4) +typedef struct { + FuriEventLoopItem* in; + FuriEventLoopItem* out; +} FuriEventLoopItemPair; + BPTREE_DEF2( // NOLINT FuriEventLoopTree, FURI_EVENT_LOOP_TREE_RANK, FuriEventLoopObject*, /* pointer to object we track */ M_PTR_OPLIST, - FuriEventLoopItem*, /* pointer to the FuriEventLoopItem */ - M_PTR_OPLIST) + FuriEventLoopItemPair, /* pointers to the two FuriEventLoopItem */ + M_POD_OPLIST) #define M_OPL_FuriEventLoopTree_t() BPTREE_OPLIST(FuriEventLoopTree, M_POD_OPLIST) From 8837c8ba6cd72fd3f317b914c4bff69ba6c9cc15 Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Wed, 13 Nov 2024 18:44:19 +0300 Subject: [PATCH 16/47] feat: vcp i/o --- applications/main/gpio/usb_uart_bridge.c | 18 +- applications/services/application.fam | 1 + applications/services/cli/application.fam | 14 +- applications/services/cli/cli.c | 82 +--- applications/services/cli/cli_i.h | 1 - applications/services/cli/cli_shell.c | 78 ++++ applications/services/cli/cli_shell.h | 15 + applications/services/cli/cli_vcp.c | 512 ++++++++++------------ applications/services/cli/cli_vcp.h | 7 +- applications/services/desktop/desktop.c | 12 +- targets/f18/api_symbols.csv | 5 +- targets/f7/api_symbols.csv | 5 +- targets/f7/furi_hal/furi_hal_usb_cdc.h | 14 +- 13 files changed, 399 insertions(+), 365 deletions(-) create mode 100644 applications/services/cli/cli_shell.c create mode 100644 applications/services/cli/cli_shell.h diff --git a/applications/main/gpio/usb_uart_bridge.c b/applications/main/gpio/usb_uart_bridge.c index f6e68b10966..b24a4977251 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); } @@ -308,9 +308,9 @@ static int32_t usb_uart_worker(void* context) { furi_hal_usb_unlock(); furi_check(furi_hal_usb_set_config(&usb_cdc_single, NULL) == true); - Cli* cli = furi_record_open(RECORD_CLI); - cli_session_open(cli, &cli_vcp); - furi_record_close(RECORD_CLI); + CliVcp* cli_vcp = furi_record_open(RECORD_CLI_VCP); + cli_vcp_enable(cli_vcp); + furi_record_close(RECORD_CLI_VCP); return 0; } diff --git a/applications/services/application.fam b/applications/services/application.fam index 9ffb26dd6fd..b1bafe5d6e5 100644 --- a/applications/services/application.fam +++ b/applications/services/application.fam @@ -10,5 +10,6 @@ App( "desktop", "loader", "power", + "cli_vcp", ], ) diff --git a/applications/services/cli/application.fam b/applications/services/cli/application.fam index 7a57bb6076b..6739360efe2 100644 --- a/applications/services/cli/application.fam +++ b/applications/services/cli/application.fam @@ -4,7 +4,17 @@ App( apptype=FlipperAppType.SERVICE, entry_point="cli_srv", cdefines=["SRV_CLI"], - stack_size=4 * 1024, + stack_size=1 * 1024, order=30, - sdk_headers=["cli.h", "cli_vcp.h"], + sdk_headers=["cli.h"], +) + +App( + appid="cli_vcp", + name="CliVcpSrv", + apptype=FlipperAppType.SERVICE, + entry_point="cli_vcp_srv", + stack_size=1 * 1024, + order=40, + sdk_headers=["cli_vcp.h"], ) diff --git a/applications/services/cli/cli.c b/applications/services/cli/cli.c index 28ba417c261..7329e1a0221 100644 --- a/applications/services/cli/cli.c +++ b/applications/services/cli/cli.c @@ -16,8 +16,6 @@ Cli* cli_alloc(void) { 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); @@ -27,69 +25,43 @@ Cli* cli_alloc(void) { void cli_putc(Cli* cli, char c) { furi_check(cli); - if(cli->session != NULL) { - cli->session->tx((uint8_t*)&c, 1); - } + UNUSED(c); } 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); - } + UNUSED(buffer); + UNUSED(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; - } + UNUSED(buffer); + UNUSED(size); + 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; - } + UNUSED(buffer); + UNUSED(size); + UNUSED(timeout); + 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; } @@ -426,15 +398,9 @@ void cli_delete_command(Cli* cli, const char* name) { void cli_session_open(Cli* cli, void* session) { furi_check(cli); + UNUSED(session); furi_check(furi_mutex_acquire(cli->mutex, FuriWaitForever) == FuriStatusOk); - cli->session = session; - if(cli->session != NULL) { - cli->session->init(); - furi_thread_set_stdout_callback(cli->session->tx_stdout, NULL); - } else { - furi_thread_set_stdout_callback(NULL, NULL); - } furi_semaphore_release(cli->idle_sem); furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk); } @@ -443,16 +409,13 @@ void cli_session_close(Cli* cli) { furi_check(cli); furi_check(furi_mutex_acquire(cli->mutex, FuriWaitForever) == FuriStatusOk); - if(cli->session != NULL) { - cli->session->deinit(); - } - cli->session = NULL; furi_thread_set_stdout_callback(NULL, NULL); furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk); } int32_t cli_srv(void* p) { UNUSED(p); + Cli* cli = cli_alloc(); // Init basic cli commands @@ -460,25 +423,8 @@ int32_t cli_srv(void* p) { 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); - } - } + while(1) + furi_delay_tick(FuriWaitForever); return 0; } diff --git a/applications/services/cli/cli_i.h b/applications/services/cli/cli_i.h index d7351b9ffc3..b9e41054f51 100644 --- a/applications/services/cli/cli_i.h +++ b/applications/services/cli/cli_i.h @@ -50,7 +50,6 @@ struct Cli { FuriSemaphore* idle_sem; FuriString* last_line; FuriString* line; - CliSession* session; size_t cursor_position; }; diff --git a/applications/services/cli/cli_shell.c b/applications/services/cli/cli_shell.c new file mode 100644 index 00000000000..c499517b50e --- /dev/null +++ b/applications/services/cli/cli_shell.c @@ -0,0 +1,78 @@ +#include "cli_shell.h" +#include + +#define TAG "CliShell" + +typedef struct { + FuriEventLoop* event_loop; + FuriPipeSide* pipe; +} CliShell; + +static void cli_shell_tick(void* context) { + CliShell* cli_shell = context; + if(furi_pipe_state(cli_shell->pipe) == FuriPipeStateBroken) { + furi_event_loop_stop(cli_shell->event_loop); + } +} + +static void cli_shell_data_available(FuriEventLoopObject* object, void* context) { + UNUSED(object); + CliShell* cli_shell = context; + UNUSED(cli_shell); + + int c = getchar(); + printf("You typed: %c\n", c); +} + +static CliShell* cli_shell_alloc(FuriPipeSide* pipe) { + CliShell* cli_shell = malloc(sizeof(CliShell)); + cli_shell->event_loop = furi_event_loop_alloc(); + + cli_shell->pipe = pipe; + furi_pipe_install_as_stdio(cli_shell->pipe); + furi_event_loop_subscribe_pipe( + cli_shell->event_loop, + cli_shell->pipe, + FuriEventLoopEventIn, + cli_shell_data_available, + cli_shell); + + furi_event_loop_pend_callback(cli_shell->event_loop, cli_shell_tick, cli_shell); + + return cli_shell; +} + +static void cli_shell_free(CliShell* cli_shell) { + furi_event_loop_unsubscribe(cli_shell->event_loop, cli_shell->pipe); + furi_event_loop_free(cli_shell->event_loop); + furi_pipe_free(cli_shell->pipe); + free(cli_shell); +} + +static int32_t cli_shell_thread(void* context) { + FuriPipeSide* pipe = context; + CliShell* cli_shell = cli_shell_alloc(pipe); + + FURI_LOG_T(TAG, "Started"); + + const char* long_str = + "Hello, World! This is a very long string to test out how my new VCP service handles oddly-sized blocks. In addition to this, I'm going to make it longer than the pipe buffer, just to see how things work out. This string should be plenty long already, so I'm going to wrap this up!"; + char buf[strlen(long_str) + 3]; + for(size_t s = 1; s <= strlen(long_str); s++) { + memcpy(buf, long_str, s); + memcpy(buf + s, "|", 2); + puts(buf); + } + + furi_event_loop_run(cli_shell->event_loop); + FURI_LOG_T(TAG, "Stopped"); + + cli_shell_free(cli_shell); + return 0; +} + +void cli_shell_start(FuriPipeSide* pipe) { + FuriThread* thread = + furi_thread_alloc_ex("CliShell", CLI_SHELL_STACK_SIZE, cli_shell_thread, pipe); + furi_thread_start(thread); +} diff --git a/applications/services/cli/cli_shell.h b/applications/services/cli/cli_shell.h new file mode 100644 index 00000000000..4cfe06ce960 --- /dev/null +++ b/applications/services/cli/cli_shell.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define CLI_SHELL_STACK_SIZE (1 * 1024U) + +void cli_shell_start(FuriPipeSide* pipe); + +#ifdef __cplusplus +} +#endif diff --git a/applications/services/cli/cli_vcp.c b/applications/services/cli/cli_vcp.c index aa399e78a29..4c49ca57d07 100644 --- a/applications/services/cli/cli_vcp.c +++ b/applications/services/cli/cli_vcp.c @@ -1,4 +1,5 @@ -#include "cli_i.h" // IWYU pragma: keep +#include "cli_vcp.h" +#include "cli_shell.h" #include #include #include @@ -6,318 +7,283 @@ #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 VCP_BUF_SIZE (USB_CDC_PKT_LEN * 3) #define VCP_IF_NUM 0 -#ifdef CLI_VCP_DEBUG -#define VCP_DEBUG(...) FURI_LOG_D(TAG, __VA_ARGS__) -#else -#define VCP_DEBUG(...) -#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) +#define VCP_MESSAGE_Q_LEN 4 typedef struct { - FuriThread* thread; - - FuriStreamBuffer* tx_stream; - FuriStreamBuffer* rx_stream; - - volatile bool connected; - volatile bool running; - - FuriHalUsbInterface* usb_if_prev; - - uint8_t data_buffer[USB_CDC_PKT_LEN]; -} CliVcp; + enum { + CliVcpMessageTypeEnable, + CliVcpMessageTypeDisable, + } type; + union {}; +} CliVcpMessage; -static int32_t vcp_worker(void* context); -static void vcp_on_cdc_tx_complete(void* context); -static void vcp_on_cdc_rx(void* context); -static void vcp_state_callback(void* context, uint8_t state); -static void vcp_on_cdc_control_line(void* context, uint8_t state); - -static CdcCallbacks cdc_cb = { - vcp_on_cdc_tx_complete, - vcp_on_cdc_rx, - vcp_state_callback, - vcp_on_cdc_control_line, - NULL, +typedef enum { + CliVcpInternalMessageConnected, + CliVcpInternalMessageDisconnected, + CliVcpInternalMessageTxDone, + CliVcpInternalMessageRx, +} CliVcpInternalMessage; + +struct CliVcp { + FuriEventLoop* event_loop; + FuriMessageQueue* message_queue; // tx_stream = furi_stream_buffer_alloc(VCP_TX_BUF_SIZE, 1); - vcp->rx_stream = furi_stream_buffer_alloc(VCP_RX_BUF_SIZE, 1); - } - furi_assert(vcp->thread == NULL); +// ============= +// CDC callbacks +// ============= - vcp->connected = false; - - vcp->thread = furi_thread_alloc_ex("CliVcpWorker", 1024, vcp_worker, NULL); - furi_thread_start(vcp->thread); - - FURI_LOG_I(TAG, "Init OK"); +static void + cli_vcp_send_internal_message(CliVcp* cli_vcp, CliVcpInternalMessage message, bool check) { + FuriStatus status = furi_message_queue_put(cli_vcp->internal_message_queue, &message, 0); + if(check) furi_check(status == FuriStatusOk); } -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; +static void cli_vcp_cdc_tx_done(void* context) { + CliVcp* cli_vcp = context; + cli_vcp_send_internal_message(cli_vcp, CliVcpInternalMessageTxDone, true); } -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; +static void cli_vcp_cdc_rx(void* context) { + CliVcp* cli_vcp = context; + cli_vcp_send_internal_message(cli_vcp, CliVcpInternalMessageRx, false); +} - // 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)) { - furi_hal_usb_set_config(&usb_cdc_single, NULL); - } - 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; - } +static void cli_vcp_cdc_state_callback(void* context, CdcState state) { + CliVcp* cli_vcp = context; + if(state == CdcStateDisconnected) { + cli_vcp_send_internal_message(cli_vcp, CliVcpInternalMessageDisconnected, true); } - FURI_LOG_D(TAG, "End"); - return 0; + // `Connected` events are generated by DTR going active } -static size_t cli_vcp_rx(uint8_t* buffer, size_t size, uint32_t timeout) { - furi_assert(vcp); - furi_assert(buffer); - - if(vcp->running == false) { - return 0; +static void cli_vcp_cdc_ctrl_line_callback(void* context, CdcCtrlLine ctrl_lines) { + CliVcp* cli_vcp = context; + if(ctrl_lines & CdcCtrlLineDTR) { + cli_vcp_send_internal_message(cli_vcp, CliVcpInternalMessageConnected, true); + } else { + cli_vcp_send_internal_message(cli_vcp, CliVcpInternalMessageDisconnected, true); } +} - VCP_DEBUG("rx %u start", size); - - 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); +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, +}; - 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; +// ================== +// EventLoop handlers +// ================== + +/** + * Called in the following cases: + * - previous transfer has finished; + * - new data became available to send. + */ +static void cli_vcp_maybe_send_data(CliVcp* cli_vcp) { + if(cli_vcp->is_currently_transmitting) return; + if(!cli_vcp->own_pipe) return; + + uint8_t buf[USB_CDC_PKT_LEN]; + size_t length = furi_pipe_receive(cli_vcp->own_pipe, buf, sizeof(buf), 0); + if(length > 0 || cli_vcp->previous_tx_length == USB_CDC_PKT_LEN) { + FURI_LOG_T(TAG, "cdc_send length=%u", length); + cli_vcp->is_currently_transmitting = true; + furi_hal_cdc_send(VCP_IF_NUM, buf, length); } + cli_vcp->previous_tx_length = length; +} - VCP_DEBUG("rx %u end", size); - return rx_cnt; +/** + * 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(furi_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)); + FURI_LOG_T(TAG, "cdc_receive length=%u", length); + furi_check(furi_pipe_send(cli_vcp->own_pipe, buf, length, FuriWaitForever) == length); } -static size_t cli_vcp_rx_stdin(uint8_t* data, size_t size, uint32_t timeout, void* context) { - UNUSED(context); - return cli_vcp_rx(data, size, timeout); +static void cli_vcp_data_from_shell(FuriEventLoopObject* object, void* context) { + UNUSED(object); + CliVcp* cli_vcp = context; + cli_vcp_maybe_send_data(cli_vcp); } -static void cli_vcp_tx(const uint8_t* buffer, size_t size) { - furi_assert(vcp); - furi_assert(buffer); +static void cli_vcp_shell_ready(FuriEventLoopObject* object, void* context) { + UNUSED(object); + CliVcp* cli_vcp = context; + FURI_LOG_T(TAG, "shell_ready"); + cli_vcp_maybe_receive_data(cli_vcp); +} - if(vcp->running == false) { - return; +/** + * 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; } +} - 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; +/** + * Processes messages arriving from CDC event callbacks + */ +static void cli_vcp_internal_message_received(FuriEventLoopObject* object, void* context) { + CliVcp* cli_vcp = context; + CliVcpInternalMessage message; + furi_check(furi_message_queue_get(object, &message, 0) == FuriStatusOk); + + switch(message) { + case CliVcpInternalMessageConnected: + if(cli_vcp->is_connected) return; + FURI_LOG_D(TAG, "Connected"); + cli_vcp->is_connected = true; + + // start shell thread + FuriPipe pipe = furi_pipe_alloc(VCP_BUF_SIZE, 1); + cli_vcp->own_pipe = pipe.alices_side; + furi_event_loop_subscribe_pipe( + cli_vcp->event_loop, + cli_vcp->own_pipe, + FuriEventLoopEventIn | FuriEventLoopEventFlagEdge, + cli_vcp_data_from_shell, + cli_vcp); + furi_event_loop_subscribe_pipe( + cli_vcp->event_loop, + cli_vcp->own_pipe, + FuriEventLoopEventOut | FuriEventLoopEventFlagEdge, + cli_vcp_shell_ready, + cli_vcp); + cli_shell_start(pipe.bobs_side); + break; + + case CliVcpInternalMessageDisconnected: + if(!cli_vcp->is_connected) return; + FURI_LOG_D(TAG, "Disconnected"); + cli_vcp->is_connected = false; + + // disconnect our side of the pipe + furi_event_loop_unsubscribe(cli_vcp->event_loop, cli_vcp->own_pipe); + furi_pipe_free(cli_vcp->own_pipe); + cli_vcp->own_pipe = NULL; + break; + + case CliVcpInternalMessageTxDone: + FURI_LOG_T(TAG, "TxDone"); + cli_vcp->is_currently_transmitting = false; + cli_vcp_maybe_send_data(cli_vcp); + break; + + case CliVcpInternalMessageRx: + FURI_LOG_T(TAG, "Rx"); + cli_vcp_maybe_receive_data(cli_vcp); + break; } - - VCP_DEBUG("tx %u end", size); } -static void cli_vcp_tx_stdout(const char* data, size_t size, void* context) { - UNUSED(context); - cli_vcp_tx((const uint8_t*)data, size); +// ============ +// Thread stuff +// ============ + +static CliVcp* cli_vcp_alloc(void) { + CliVcp* cli_vcp = malloc(sizeof(CliVcp)); + + 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); + + cli_vcp->internal_message_queue = + furi_message_queue_alloc(VCP_MESSAGE_Q_LEN, sizeof(CliVcpInternalMessage)); + furi_event_loop_subscribe_message_queue( + cli_vcp->event_loop, + cli_vcp->internal_message_queue, + FuriEventLoopEventIn, + cli_vcp_internal_message_received, + cli_vcp); + + return cli_vcp; } -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); +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; } -} -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); + CliVcp* cli_vcp = cli_vcp_alloc(); + furi_record_create(RECORD_CLI_VCP, cli_vcp); + furi_event_loop_run(cli_vcp->event_loop); - 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); - } + return 0; } -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)); -} +// ========== +// Public API +// ========== -static void vcp_on_cdc_tx_complete(void* context) { - UNUSED(context); - furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtTx); +void cli_vcp_enable(CliVcp* cli_vcp) { + CliVcpMessage message = { + .type = CliVcpMessageTypeEnable, + }; + furi_message_queue_put(cli_vcp->message_queue, &message, FuriWaitForever); } -static bool cli_vcp_is_connected(void) { - furi_assert(vcp); - return vcp->connected; +void cli_vcp_disable(CliVcp* cli_vcp) { + CliVcpMessage message = { + .type = CliVcpMessageTypeDisable, + }; + furi_message_queue_put(cli_vcp->message_queue, &message, FuriWaitForever); } - -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 3aef2ef7083..10e286183e1 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 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/desktop/desktop.c b/applications/services/desktop/desktop.c index 1132760d555..185fb9c3b52 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/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index f62be777a77..c020c1d09ed 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,79.1,, +Version,+,80.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,, @@ -784,6 +784,8 @@ 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*, void*" +Function,+,cli_vcp_disable,void,CliVcp* +Function,+,cli_vcp_enable,void,CliVcp* Function,+,cli_write,void,"Cli*, const uint8_t*, size_t" Function,+,composite_api_resolver_add,void,"CompositeApiResolver*, const ElfApiInterface*" Function,+,composite_api_resolver_alloc,CompositeApiResolver*, @@ -2896,7 +2898,6 @@ Variable,-,_sys_errlist,const char*[], Variable,-,_sys_nerr,int, Variable,-,ble_profile_hid,const FuriHalBleProfileTemplate*, Variable,-,ble_profile_serial,const FuriHalBleProfileTemplate*, -Variable,+,cli_vcp,CliSession, Variable,+,compress_config_heatshrink_default,const CompressConfigHeatshrink, Variable,+,firmware_api_interface,const ElfApiInterface*, Variable,+,furi_hal_i2c_bus_external,FuriHalI2cBus, diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 9e6269ada2f..da03440492e 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,79.1,, +Version,+,80.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,, @@ -861,6 +861,8 @@ 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*, void*" +Function,+,cli_vcp_disable,void,CliVcp* +Function,+,cli_vcp_enable,void,CliVcp* Function,+,cli_write,void,"Cli*, const uint8_t*, size_t" Function,+,composite_api_resolver_add,void,"CompositeApiResolver*, const ElfApiInterface*" Function,+,composite_api_resolver_alloc,CompositeApiResolver*, @@ -3746,7 +3748,6 @@ Variable,-,_sys_errlist,const char*[], Variable,-,_sys_nerr,int, Variable,-,ble_profile_hid,const FuriHalBleProfileTemplate*, Variable,-,ble_profile_serial,const FuriHalBleProfileTemplate*, -Variable,+,cli_vcp,CliSession, Variable,+,compress_config_heatshrink_default,const CompressConfigHeatshrink, Variable,+,firmware_api_interface,const ElfApiInterface*, 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 89b68991b95..50d45669812 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; From 20ce18fc19c3ef98e0bef3a92c83e886017b6d8b Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Thu, 14 Nov 2024 23:46:50 +0300 Subject: [PATCH 17/47] feat: cli ansi stuffs and history --- applications/services/cli/cli_ansi.c | 144 ++++++++++++++++++++++ applications/services/cli/cli_ansi.h | 134 +++++++++++++++++++++ applications/services/cli/cli_shell.c | 166 +++++++++++++++++++++++--- 3 files changed, 430 insertions(+), 14 deletions(-) create mode 100644 applications/services/cli/cli_ansi.c create mode 100644 applications/services/cli/cli_ansi.h diff --git a/applications/services/cli/cli_ansi.c b/applications/services/cli/cli_ansi.c new file mode 100644 index 00000000000..35448c1d3aa --- /dev/null +++ b/applications/services/cli/cli_ansi.c @@ -0,0 +1,144 @@ +#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; + } +} + +CliAnsiParserResult cli_ansi_parser_feed(CliAnsiParser* parser, char c) { + switch(parser->state) { + case CliAnsiParserStateInitial: + // -> + if(c != CliKeyEsc) { + parser->state = CliAnsiParserStateInitial; + return (CliAnsiParserResult){ + .is_done = true, + .result = (CliKeyCombo){ + .modifiers = CliModKeyNo, + .key = c, + }}; + } + + // ... + parser->state = CliAnsiParserStateEscape; + break; + + case CliAnsiParserStateEscape: + // -> + if(c == CliKeyEsc) { + parser->state = CliAnsiParserStateInitial; + return (CliAnsiParserResult){ + .is_done = true, + .result = (CliKeyCombo){ + .modifiers = CliModKeyNo, + .key = c, + }}; + } + + // -> Alt + + if(c != '[') { + parser->state = CliAnsiParserStateInitial; + return (CliAnsiParserResult){ + .is_done = true, + .result = (CliKeyCombo){ + .modifiers = CliModKeyAlt, + .key = c, + }}; + } + + // [ ... + parser->state = CliAnsiParserStateEscapeBrace; + break; + + case CliAnsiParserStateEscapeBrace: + // [ -> + if(c != '1') { + parser->state = CliAnsiParserStateInitial; + return (CliAnsiParserResult){ + .is_done = true, + .result = (CliKeyCombo){ + .modifiers = CliModKeyNo, + .key = cli_ansi_key_from_mnemonic(c), + }}; + } + + // [ 1 ... + parser->state = CliAnsiParserStateEscapeBraceOne; + break; + + case CliAnsiParserStateEscapeBraceOne: + // [ 1 -> error + if(c != ';') { + parser->state = CliAnsiParserStateInitial; + return (CliAnsiParserResult){ + .is_done = true, + .result = (CliKeyCombo){ + .key = CliKeyUnrecognized, + }}; + } + + // [ 1 ; ... + parser->state = CliAnsiParserStateEscapeBraceOneSemicolon; + break; + + case CliAnsiParserStateEscapeBraceOneSemicolon: + // [ 1 ; ... + parser->modifiers = (c - '0'); + parser->modifiers &= ~1; + parser->state = CliAnsiParserStateEscapeBraceOneSemicolonModifiers; + break; + + case CliAnsiParserStateEscapeBraceOneSemicolonModifiers: + // [ 1 ; -> + + parser->state = CliAnsiParserStateInitial; + return (CliAnsiParserResult){ + .is_done = true, + .result = (CliKeyCombo){ + .modifiers = parser->modifiers, + .key = cli_ansi_key_from_mnemonic(c), + }}; + } + + return (CliAnsiParserResult){.is_done = false}; +} diff --git a/applications/services/cli/cli_ansi.h b/applications/services/cli/cli_ansi.h new file mode 100644 index 00000000000..ce692599739 --- /dev/null +++ b/applications/services/cli/cli_ansi.h @@ -0,0 +1,134 @@ +#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_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_ENTIRE_DISPLAY_AND_SCROLLBACK_BUFFER ANSI_ERASE_DISPLAY("3") + +typedef enum { + CliKeyUnrecognized = 0, + + CliKeySOH = 0x01, + CliKeyETX = 0x03, + CliKeyEOT = 0x04, + CliKeyBell = 0x07, + CliKeyBackspace = 0x08, + CliKeyTab = 0x09, + CliKeyLF = 0x0A, + 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); + +#ifdef __cplusplus +} +#endif diff --git a/applications/services/cli/cli_shell.c b/applications/services/cli/cli_shell.c index c499517b50e..6e1a46d0941 100644 --- a/applications/services/cli/cli_shell.c +++ b/applications/services/cli/cli_shell.c @@ -1,11 +1,24 @@ #include "cli_shell.h" +#include "cli_ansi.h" #include +#include +#include #define TAG "CliShell" +#define HISTORY_DEPTH 10 + +ARRAY_DEF(ShellHistory, FuriString*, FURI_STRING_OPLIST); +#define M_OPL_ShellHistory_t() ARRAY_OPLIST(ShellHistory) + typedef struct { FuriEventLoop* event_loop; FuriPipeSide* pipe; + CliAnsiParser* ansi_parser; + + size_t history_position; + size_t line_position; + ShellHistory_t history; } CliShell; static void cli_shell_tick(void* context) { @@ -15,18 +28,115 @@ static void cli_shell_tick(void* context) { } } +static void cli_shell_prompt(CliShell* cli_shell) { + UNUSED(cli_shell); + printf("\r\n>: "); + fflush(stdout); +} + +static void cli_shell_dump_history(CliShell* cli_shell) { + FURI_LOG_T(TAG, "history depth=%d, entries:", ShellHistory_size(cli_shell->history)); + for + M_EACH(entry, cli_shell->history, ShellHistory_t) { + FURI_LOG_T(TAG, " \"%s\"", furi_string_get_cstr(*entry)); + } +} + +/** + * If a line from history has been selected, moves it into the active line + */ +static void cli_shell_ensure_not_overwriting_history(CliShell* cli_shell) { + if(cli_shell->history_position > 0) { + FuriString* source = *ShellHistory_cget(cli_shell->history, cli_shell->history_position); + FuriString* destination = *ShellHistory_front(cli_shell->history); + furi_string_set(destination, source); + cli_shell->history_position = 0; + } +} + static void cli_shell_data_available(FuriEventLoopObject* object, void* context) { UNUSED(object); CliShell* cli_shell = context; UNUSED(cli_shell); + // process ANSI escape sequences int c = getchar(); - printf("You typed: %c\n", c); + 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; + FURI_LOG_T(TAG, "mod=%d, key='%c'=%d", key_combo.modifiers, key_combo.key, key_combo.key); + + // do things the user requests + if(key_combo.modifiers == 0 && key_combo.key == CliKeyETX) { // usually Ctrl+C + // reset input + furi_string_reset(*ShellHistory_front(cli_shell->history)); + cli_shell->line_position = 0; + cli_shell->history_position = 0; + printf("^C"); + cli_shell_prompt(cli_shell); + + } else if(key_combo.modifiers == 0 && key_combo.key == CliKeyLF) { + // get command and update history + cli_shell_dump_history(cli_shell); + FuriString* command = furi_string_alloc(); + ShellHistory_pop_at(&command, cli_shell->history, cli_shell->history_position); + if(cli_shell->history_position > 0) ShellHistory_pop_at(NULL, cli_shell->history, 0); + if(!furi_string_empty(command)) ShellHistory_push_at(cli_shell->history, 0, command); + ShellHistory_push_at(cli_shell->history, 0, furi_string_alloc()); + if(ShellHistory_size(cli_shell->history) > HISTORY_DEPTH) { + ShellHistory_pop_back(NULL, cli_shell->history); + } + cli_shell_dump_history(cli_shell); + + // execute command + cli_shell->line_position = 0; + cli_shell->history_position = 0; + printf("\r\ncommand input: \"%s\"", furi_string_get_cstr(command)); + cli_shell_prompt(cli_shell); + + } else if(key_combo.modifiers == 0 && (key_combo.key == CliKeyUp || key_combo.key == CliKeyDown)) { + // go up and down in history + int increment = (key_combo.key == CliKeyUp) ? 1 : -1; + cli_shell->history_position = CLAMP( + (int)cli_shell->history_position + increment, + (int)ShellHistory_size(cli_shell->history) - 1, + 0); + + // print prompt with selected command + FuriString* command = *ShellHistory_cget(cli_shell->history, cli_shell->history_position); + printf( + ANSI_CURSOR_HOR_POS("1") ">: %s" ANSI_ERASE_LINE(ANSI_ERASE_FROM_CURSOR_TO_END), + furi_string_get_cstr(command)); + fflush(stdout); + cli_shell->line_position = furi_string_size(command); + + } else if(key_combo.modifiers == 0 && key_combo.key >= CliKeySpace && key_combo.key < CliKeyDEL) { + cli_shell_ensure_not_overwriting_history(cli_shell); + + // insert character + FuriString* line = *ShellHistory_front(cli_shell->history); + if(cli_shell->line_position == furi_string_size(line)) { + furi_string_push_back(line, key_combo.key); + putc(key_combo.key, stdout); + fflush(stdout); + } else { + const char in_str[2] = {key_combo.key, 0}; + furi_string_replace_at(line, cli_shell->line_position, 0, in_str); + printf("\e[4h%c\e[4l", key_combo.key); + fflush(stdout); + } + cli_shell->line_position++; + } } static CliShell* cli_shell_alloc(FuriPipeSide* pipe) { CliShell* cli_shell = malloc(sizeof(CliShell)); + cli_shell->ansi_parser = cli_ansi_parser_alloc(); cli_shell->event_loop = furi_event_loop_alloc(); + ShellHistory_init(cli_shell->history); + ShellHistory_push_at(cli_shell->history, 0, furi_string_alloc()); cli_shell->pipe = pipe; furi_pipe_install_as_stdio(cli_shell->pipe); @@ -37,7 +147,7 @@ static CliShell* cli_shell_alloc(FuriPipeSide* pipe) { cli_shell_data_available, cli_shell); - furi_event_loop_pend_callback(cli_shell->event_loop, cli_shell_tick, cli_shell); + furi_event_loop_tick_set(cli_shell->event_loop, 1, cli_shell_tick, cli_shell); return cli_shell; } @@ -46,26 +156,54 @@ static void cli_shell_free(CliShell* cli_shell) { furi_event_loop_unsubscribe(cli_shell->event_loop, cli_shell->pipe); furi_event_loop_free(cli_shell->event_loop); furi_pipe_free(cli_shell->pipe); + ShellHistory_clear(cli_shell->history); + cli_ansi_parser_free(cli_shell->ansi_parser); 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) { FuriPipeSide* pipe = context; CliShell* cli_shell = cli_shell_alloc(pipe); - FURI_LOG_T(TAG, "Started"); - - const char* long_str = - "Hello, World! This is a very long string to test out how my new VCP service handles oddly-sized blocks. In addition to this, I'm going to make it longer than the pipe buffer, just to see how things work out. This string should be plenty long already, so I'm going to wrap this up!"; - char buf[strlen(long_str) + 3]; - for(size_t s = 1; s <= strlen(long_str); s++) { - memcpy(buf, long_str, s); - memcpy(buf + s, "|", 2); - puts(buf); - } - + FURI_LOG_D(TAG, "Started"); + cli_shell_motd(); + cli_shell_prompt(cli_shell); furi_event_loop_run(cli_shell->event_loop); - FURI_LOG_T(TAG, "Stopped"); + FURI_LOG_D(TAG, "Stopped"); cli_shell_free(cli_shell); return 0; From 1b31e705d7065406db26d620d8495309df58b4dd Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Fri, 15 Nov 2024 16:56:37 +0300 Subject: [PATCH 18/47] feat: more line editing --- applications/services/cli/cli_ansi.h | 12 +- applications/services/cli/cli_shell.c | 192 ++++++++++++++++++++++++-- applications/services/cli/cli_vcp.c | 1 - 3 files changed, 188 insertions(+), 17 deletions(-) diff --git a/applications/services/cli/cli_ansi.h b/applications/services/cli/cli_ansi.h index ce692599739..f3b626d1a03 100644 --- a/applications/services/cli/cli_ansi.h +++ b/applications/services/cli/cli_ansi.h @@ -65,9 +65,14 @@ extern "C" { #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_ENTIRE_DISPLAY_AND_SCROLLBACK_BUFFER ANSI_ERASE_DISPLAY("3") +#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, @@ -79,6 +84,7 @@ typedef enum { CliKeyBackspace = 0x08, CliKeyTab = 0x09, CliKeyLF = 0x0A, + CliKeyFF = 0x0C, CliKeyCR = 0x0D, CliKeyETB = 0x17, CliKeyEsc = 0x1B, diff --git a/applications/services/cli/cli_shell.c b/applications/services/cli/cli_shell.c index 6e1a46d0941..2d8f09a4a48 100644 --- a/applications/services/cli/cli_shell.c +++ b/applications/services/cli/cli_shell.c @@ -28,9 +28,20 @@ static void cli_shell_tick(void* context) { } } -static void cli_shell_prompt(CliShell* cli_shell) { +static size_t cli_shell_prompt_length(CliShell* cli_shell) { + UNUSED(cli_shell); + return strlen(">: "); +} + +static void cli_shell_format_prompt(CliShell* cli_shell, char* buf, size_t length) { UNUSED(cli_shell); - printf("\r\n>: "); + snprintf(buf, length - 1, ">: "); +} + +static void cli_shell_prompt(CliShell* cli_shell) { + char buffer[128]; + cli_shell_format_prompt(cli_shell, buffer, sizeof(buffer)); + printf("\r\n%s", buffer); fflush(stdout); } @@ -38,7 +49,7 @@ static void cli_shell_dump_history(CliShell* cli_shell) { FURI_LOG_T(TAG, "history depth=%d, entries:", ShellHistory_size(cli_shell->history)); for M_EACH(entry, cli_shell->history, ShellHistory_t) { - FURI_LOG_T(TAG, " \"%s\"", furi_string_get_cstr(*entry)); + FURI_LOG_T(TAG, " \"%s\"", furi_string_get_cstr(*entry)); } } @@ -54,6 +65,65 @@ static void cli_shell_ensure_not_overwriting_history(CliShell* cli_shell) { } } +typedef enum { + CliCharClassWord, + CliCharClassSpace, + CliCharClassOther, +} CliCharClass; + +/** + * @brief Determines the class that a character belongs to + * + * The return value of this function should not be used on its own; it should + * only be used for comparing it with other values returned by this function. + * This function is used internally in `cli_skip_run`. + */ +static CliCharClass cli_char_class(char c) { + if((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_') { + return CliCharClassWord; + } else if(c == ' ') { + return CliCharClassSpace; + } else { + return CliCharClassOther; + } +} + +typedef enum { + CliSkipDirectionLeft, + CliSkipDirectionRight, +} CliSkipDirection; + +/** + * @brief Skips a run of a class of characters + * + * @param string Input string + * @param original_pos Position to start the search at + * @param direction Direction in which to perform the search + * @returns The position at which the run ends + */ +static size_t cli_skip_run(FuriString* string, size_t original_pos, CliSkipDirection direction) { + if(furi_string_size(string) == 0) return original_pos; + if(direction == CliSkipDirectionLeft && original_pos == 0) return original_pos; + if(direction == CliSkipDirectionRight && original_pos == furi_string_size(string)) + return original_pos; + + int8_t look_offset = (direction == CliSkipDirectionLeft) ? -1 : 0; + int8_t increment = (direction == CliSkipDirectionLeft) ? -1 : 1; + int32_t position = original_pos; + CliCharClass start_class = + cli_char_class(furi_string_get_char(string, position + look_offset)); + + while(true) { + position += increment; + if(position < 0) break; + if(position >= (int32_t)furi_string_size(string)) break; + if(cli_char_class(furi_string_get_char(string, position + look_offset)) != start_class) + break; + } + + return MAX(0, position); +} + static void cli_shell_data_available(FuriEventLoopObject* object, void* context) { UNUSED(object); CliShell* cli_shell = context; @@ -66,7 +136,7 @@ static void cli_shell_data_available(FuriEventLoopObject* object, void* context) if(!parse_result.is_done) return; CliKeyCombo key_combo = parse_result.result; if(key_combo.key == CliKeyUnrecognized) return; - FURI_LOG_T(TAG, "mod=%d, key='%c'=%d", key_combo.modifiers, key_combo.key, key_combo.key); + FURI_LOG_T(TAG, "mod=%d, key=%d='%c'", key_combo.modifiers, key_combo.key, key_combo.key); // do things the user requests if(key_combo.modifiers == 0 && key_combo.key == CliKeyETX) { // usually Ctrl+C @@ -77,6 +147,19 @@ static void cli_shell_data_available(FuriEventLoopObject* object, void* context) printf("^C"); cli_shell_prompt(cli_shell); + } else if(key_combo.modifiers == 0 && key_combo.key == CliKeyFF) { // usually Ctrl+L + // clear screen + FuriString* command = *ShellHistory_cget(cli_shell->history, cli_shell->history_position); + char prompt[128]; + cli_shell_format_prompt(cli_shell, prompt, sizeof(prompt)); + printf( + ANSI_ERASE_DISPLAY(ANSI_ERASE_ENTIRE) ANSI_ERASE_SCROLLBACK_BUFFER ANSI_CURSOR_POS( + "1", "1") "%s%s" ANSI_CURSOR_HOR_POS("%d"), + prompt, + furi_string_get_cstr(command), + strlen(prompt) + cli_shell->line_position + 1 /* 1-based column indexing */); + fflush(stdout); + } else if(key_combo.modifiers == 0 && key_combo.key == CliKeyLF) { // get command and update history cli_shell_dump_history(cli_shell); @@ -99,34 +182,117 @@ static void cli_shell_data_available(FuriEventLoopObject* object, void* context) } else if(key_combo.modifiers == 0 && (key_combo.key == CliKeyUp || key_combo.key == CliKeyDown)) { // go up and down in history int increment = (key_combo.key == CliKeyUp) ? 1 : -1; - cli_shell->history_position = CLAMP( + size_t new_pos = CLAMP( (int)cli_shell->history_position + increment, (int)ShellHistory_size(cli_shell->history) - 1, 0); // print prompt with selected command + if(new_pos != cli_shell->history_position) { + cli_shell->history_position = new_pos; + FuriString* command = + *ShellHistory_cget(cli_shell->history, cli_shell->history_position); + printf( + ANSI_CURSOR_HOR_POS("1") ">: %s" ANSI_ERASE_LINE(ANSI_ERASE_FROM_CURSOR_TO_END), + furi_string_get_cstr(command)); + fflush(stdout); + cli_shell->line_position = furi_string_size(command); + } + + } else if( + key_combo.modifiers == 0 && + (key_combo.key == CliKeyLeft || key_combo.key == CliKeyRight)) { + // go left and right in the current line FuriString* command = *ShellHistory_cget(cli_shell->history, cli_shell->history_position); + int increment = (key_combo.key == CliKeyRight) ? 1 : -1; + size_t new_pos = + CLAMP((int)cli_shell->line_position + increment, (int)furi_string_size(command), 0); + + // move cursor + if(new_pos != cli_shell->line_position) { + cli_shell->line_position = new_pos; + printf("%s", (increment == 1) ? ANSI_CURSOR_RIGHT_BY("1") : ANSI_CURSOR_LEFT_BY("1")); + fflush(stdout); + } + + } else if(key_combo.modifiers == 0 && key_combo.key == CliKeyHome) { + // go to the start + cli_shell->line_position = 0; + printf(ANSI_CURSOR_HOR_POS("%d"), cli_shell_prompt_length(cli_shell) + 1); + fflush(stdout); + + } else if(key_combo.modifiers == 0 && key_combo.key == CliKeyEnd) { + // go to the end + FuriString* line = *ShellHistory_cget(cli_shell->history, cli_shell->history_position); + cli_shell->line_position = furi_string_size(line); printf( - ANSI_CURSOR_HOR_POS("1") ">: %s" ANSI_ERASE_LINE(ANSI_ERASE_FROM_CURSOR_TO_END), - furi_string_get_cstr(command)); + ANSI_CURSOR_HOR_POS("%d"), + cli_shell_prompt_length(cli_shell) + cli_shell->line_position + 1); fflush(stdout); - cli_shell->line_position = furi_string_size(command); - } else if(key_combo.modifiers == 0 && key_combo.key >= CliKeySpace && key_combo.key < CliKeyDEL) { + } else if( + key_combo.modifiers == 0 && + (key_combo.key == CliKeyBackspace || key_combo.key == CliKeyDEL)) { + // erase one character cli_shell_ensure_not_overwriting_history(cli_shell); + FuriString* line = *ShellHistory_front(cli_shell->history); + if(cli_shell->line_position == 0) { + putc(CliKeyBell, stdout); + fflush(stdout); + return; + } + cli_shell->line_position--; + furi_string_replace_at(line, cli_shell->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(line) + cli_shell->line_position); + size_t left_by = furi_string_size(line) - cli_shell->line_position; + if(left_by) // apparently LEFT_BY("0") shift left by one ._ . + printf(ANSI_CURSOR_LEFT_BY("%d"), left_by); + fflush(stdout); + + } else if( + key_combo.modifiers == CliModKeyCtrl && + (key_combo.key == CliKeyLeft || key_combo.key == CliKeyRight)) { + // skip run of similar chars to the left or right + FuriString* line = *ShellHistory_cget(cli_shell->history, cli_shell->history_position); + CliSkipDirection direction = (key_combo.key == CliKeyLeft) ? CliSkipDirectionLeft : + CliSkipDirectionRight; + cli_shell->line_position = cli_skip_run(line, cli_shell->line_position, direction); + printf( + ANSI_CURSOR_HOR_POS("%d"), + cli_shell_prompt_length(cli_shell) + cli_shell->line_position + 1); + fflush(stdout); + + } else if(key_combo.modifiers == 0 && key_combo.key == CliKeyETB) { + // delete run of similar chars to the left + cli_shell_ensure_not_overwriting_history(cli_shell); + FuriString* line = *ShellHistory_cget(cli_shell->history, cli_shell->history_position); + size_t run_start = cli_skip_run(line, cli_shell->line_position, CliSkipDirectionLeft); + furi_string_replace_at(line, run_start, cli_shell->line_position - run_start, ""); + cli_shell->line_position = run_start; + printf( + ANSI_CURSOR_HOR_POS("%zu") "%s" ANSI_ERASE_LINE(ANSI_ERASE_FROM_CURSOR_TO_END) + ANSI_CURSOR_HOR_POS("%zu"), + cli_shell_prompt_length(cli_shell) + cli_shell->line_position + 1, + furi_string_get_cstr(line) + run_start, + cli_shell_prompt_length(cli_shell) + run_start + 1); + fflush(stdout); + + } else if(key_combo.modifiers == 0 && key_combo.key >= CliKeySpace && key_combo.key < CliKeyDEL) { // insert character + cli_shell_ensure_not_overwriting_history(cli_shell); FuriString* line = *ShellHistory_front(cli_shell->history); if(cli_shell->line_position == furi_string_size(line)) { furi_string_push_back(line, key_combo.key); - putc(key_combo.key, stdout); - fflush(stdout); } else { const char in_str[2] = {key_combo.key, 0}; furi_string_replace_at(line, cli_shell->line_position, 0, in_str); - printf("\e[4h%c\e[4l", key_combo.key); - fflush(stdout); } + printf(ANSI_INSERT_MODE_ENABLE "%c" ANSI_INSERT_MODE_DISABLE, key_combo.key); + fflush(stdout); cli_shell->line_position++; } } diff --git a/applications/services/cli/cli_vcp.c b/applications/services/cli/cli_vcp.c index 4c49ca57d07..78686a28bf6 100644 --- a/applications/services/cli/cli_vcp.c +++ b/applications/services/cli/cli_vcp.c @@ -133,7 +133,6 @@ static void cli_vcp_data_from_shell(FuriEventLoopObject* object, void* context) static void cli_vcp_shell_ready(FuriEventLoopObject* object, void* context) { UNUSED(object); CliVcp* cli_vcp = context; - FURI_LOG_T(TAG, "shell_ready"); cli_vcp_maybe_receive_data(cli_vcp); } From 82169a79ca40279a9de946a1e8faef775070aa98 Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Thu, 28 Nov 2024 21:04:55 +0400 Subject: [PATCH 19/47] working but slow cli rewrite --- .../debug/bt_debug_app/application.fam | 36 +- applications/main/application.fam | 3 +- applications/main/gpio/usb_uart_bridge.c | 6 +- applications/main/ibutton/ibutton_cli.c | 25 +- applications/main/infrared/infrared_cli.c | 52 +- applications/main/lfrfid/lfrfid_cli.c | 44 +- applications/main/nfc/nfc_cli.c | 8 +- .../main/subghz/helpers/subghz_chat.c | 8 +- .../main/subghz/helpers/subghz_chat.h | 2 +- applications/main/subghz/subghz_cli.c | 81 ++-- applications/services/application.fam | 2 +- applications/services/bt/application.fam | 2 +- applications/services/bt/bt_cli.c | 32 +- applications/services/cli/application.fam | 15 +- applications/services/cli/cli.c | 456 ++++-------------- applications/services/cli/cli.h | 133 ++--- applications/services/cli/cli_commands.c | 194 ++++---- applications/services/cli/cli_i.h | 52 +- applications/services/cli/cli_shell.c | 99 +++- applications/services/cli/cli_shell.h | 2 +- applications/services/cli/cli_vcp.c | 163 ++++--- applications/services/crypto/crypto_cli.c | 34 +- applications/services/input/input.c | 4 +- applications/services/input/input_cli.c | 15 +- applications/services/loader/loader_cli.c | 6 +- applications/services/power/application.fam | 2 +- applications/services/power/power_cli.c | 34 +- applications/services/rpc/application.fam | 2 +- applications/services/rpc/rpc.c | 2 +- applications/services/rpc/rpc_cli.c | 20 +- applications/services/rpc/rpc_i.h | 2 +- applications/services/storage/storage_cli.c | 106 ++-- applications/system/js_app/js_app.c | 14 +- applications/system/updater/cli/updater_cli.c | 4 +- furi/core/event_loop.c | 38 +- furi/core/event_loop.h | 22 + furi/core/event_loop_i.h | 10 +- furi/core/pipe.c | 6 +- targets/f7/api_symbols.csv | 15 +- 39 files changed, 785 insertions(+), 966 deletions(-) diff --git a/applications/debug/bt_debug_app/application.fam b/applications/debug/bt_debug_app/application.fam index 8ed1ccc054f..99803368660 100644 --- a/applications/debug/bt_debug_app/application.fam +++ b/applications/debug/bt_debug_app/application.fam @@ -1,18 +1,18 @@ -App( - appid="bt_debug", - name="Bluetooth Debug", - apptype=FlipperAppType.DEBUG, - entry_point="bt_debug_app", - cdefines=["SRV_BT"], - requires=[ - "bt", - "gui", - "dialogs", - ], - provides=[ - "bt_debug", - ], - stack_size=1 * 1024, - order=110, - fap_category="Debug", -) +# App( +# appid="bt_debug", +# name="Bluetooth Debug", +# apptype=FlipperAppType.DEBUG, +# entry_point="bt_debug_app", +# cdefines=["SRV_BT"], +# requires=[ +# "bt", +# "gui", +# "dialogs", +# ], +# provides=[ +# "bt_debug", +# ], +# stack_size=1 * 1024, +# order=110, +# fap_category="Debug", +# ) diff --git a/applications/main/application.fam b/applications/main/application.fam index 0a90ee2243f..c488ae7d46b 100644 --- a/applications/main/application.fam +++ b/applications/main/application.fam @@ -21,8 +21,9 @@ App( name="On start hooks", apptype=FlipperAppType.METAPACKAGE, provides=[ + "cli_start", "ibutton_start", - "onewire_start", + # "onewire_start", "subghz_start", "infrared_start", "lfrfid_start", diff --git a/applications/main/gpio/usb_uart_bridge.c b/applications/main/gpio/usb_uart_bridge.c index b24a4977251..3efc7e01dba 100644 --- a/applications/main/gpio/usb_uart_bridge.c +++ b/applications/main/gpio/usb_uart_bridge.c @@ -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); } } diff --git a/applications/main/ibutton/ibutton_cli.c b/applications/main/ibutton/ibutton_cli.c index 2338ca3c3d7..5d748459ad7 100644 --- a/applications/main/ibutton/ibutton_cli.c +++ b/applications/main/ibutton/ibutton_cli.c @@ -8,13 +8,13 @@ #include #include -static void ibutton_cli(Cli* cli, FuriString* args, void* context); +static void ibutton_cli(FuriPipeSide* pipe, 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); + cli_add_command(cli, "ikey", CliCommandFlagParallelUnsafe, ibutton_cli, cli); furi_record_close(RECORD_CLI); #else UNUSED(ibutton_cli); @@ -92,7 +92,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(FuriPipeSide* 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 +113,7 @@ static void ibutton_cli_read(Cli* cli) { break; } - if(cli_cmd_interrupt_received(cli)) break; + if(cli_app_should_stop(pipe)) break; } ibutton_worker_stop(worker); @@ -138,7 +138,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(FuriPipeSide* 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 +181,7 @@ void ibutton_cli_write(Cli* cli, FuriString* args) { } } - if(cli_cmd_interrupt_received(cli)) break; + if(cli_app_should_stop(pipe)) break; } } while(false); @@ -195,7 +195,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(FuriPipeSide* 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 +214,7 @@ void ibutton_cli_emulate(Cli* cli, FuriString* args) { ibutton_worker_emulate_start(worker, key); - while(!cli_cmd_interrupt_received(cli)) { + while(!cli_app_should_stop(pipe)) { furi_delay_ms(100); }; @@ -228,8 +228,7 @@ 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(FuriPipeSide* pipe, FuriString* args, void* context) { UNUSED(context); FuriString* cmd; cmd = furi_string_alloc(); @@ -241,11 +240,11 @@ 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(); } diff --git a/applications/main/infrared/infrared_cli.c b/applications/main/infrared/infrared_cli.c index b700cf12150..e6bee100560 100644 --- a/applications/main/infrared/infrared_cli.c +++ b/applications/main/infrared/infrared_cli.c @@ -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(FuriPipeSide* pipe, FuriString* args); +static void infrared_cli_start_ir_tx(FuriPipeSide* pipe, FuriString* args); +static void infrared_cli_process_decode(FuriPipeSide* pipe, FuriString* args); +static void infrared_cli_process_universal(FuriPipeSide* pipe, FuriString* args); static const struct { const char* cmd; - void (*process_function)(Cli* cli, FuriString* args); + void (*process_function)(FuriPipeSide* 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; + FuriPipeSide* pipe = (FuriPipeSide*)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); + furi_pipe_send(pipe, buf, buf_cnt, FuriWaitForever); } 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); + furi_pipe_send(pipe, buf, buf_cnt, FuriWaitForever); 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); + furi_pipe_send(pipe, buf, buf_cnt, FuriWaitForever); } buf_cnt = snprintf(buf, sizeof(buf), "\r\n"); - cli_write(cli, (uint8_t*)buf, buf_cnt); + furi_pipe_send(pipe, buf, buf_cnt, FuriWaitForever); } } @@ -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(FuriPipeSide* 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_app_should_stop(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(FuriPipeSide* 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(FuriPipeSide* 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( + FuriPipeSide* 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)); @@ -491,7 +491,7 @@ static void while(running) { running = infrared_brute_force_send_next(brute_force); - if(cli_cmd_interrupt_received(cli)) break; + if(cli_app_should_stop(pipe)) break; printf("\r%d%% complete.", (int)((float)records_sent++ / (float)record_count * 100)); fflush(stdout); @@ -505,7 +505,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(FuriPipeSide* pipe, FuriString* args) { FuriString* arg1 = furi_string_alloc(); FuriString* arg2 = furi_string_alloc(); @@ -520,14 +520,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(FuriPipeSide* pipe, FuriString* args, void* context) { UNUSED(context); if(furi_hal_infrared_is_busy()) { printf("INFRARED is busy. Exiting."); @@ -547,7 +547,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 a25032d6af9..c4330a5316f 100644 --- a/applications/main/lfrfid/lfrfid_cli.c +++ b/applications/main/lfrfid/lfrfid_cli.c @@ -14,7 +14,7 @@ #include #include -static void lfrfid_cli(Cli* cli, FuriString* args, void* context); +static void lfrfid_cli(FuriPipeSide* pipe, FuriString* args, void* context); // app cli function void lfrfid_on_system_start(void) { @@ -49,7 +49,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(FuriPipeSide* pipe, FuriString* args) { FuriString* type_string; type_string = furi_string_alloc(); LFRFIDWorkerReadType type = LFRFIDWorkerReadTypeAuto; @@ -96,7 +96,7 @@ static void lfrfid_cli_read(Cli* cli, FuriString* args) { } } - if(cli_cmd_interrupt_received(cli)) break; + if(cli_app_should_stop(pipe)) break; } lfrfid_worker_stop(worker); @@ -192,7 +192,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(FuriPipeSide* pipe, FuriString* args) { ProtocolDict* dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax); ProtocolId protocol; @@ -212,7 +212,7 @@ static void lfrfid_cli_write(Cli* cli, FuriString* args) { (1 << LFRFIDWorkerWriteProtocolCannotBeWritten) | (1 << LFRFIDWorkerWriteFobCannotBeWritten); - while(!cli_cmd_interrupt_received(cli)) { + while(!cli_app_should_stop(pipe)) { uint32_t flags = furi_event_flag_wait(event, available_flags, FuriFlagWaitAny, 100); if(flags != (unsigned)FuriFlagErrorTimeout) { if(FURI_BIT(flags, LFRFIDWorkerWriteOK)) { @@ -239,7 +239,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(FuriPipeSide* pipe, FuriString* args) { ProtocolDict* dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax); ProtocolId protocol; @@ -254,7 +254,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_app_should_stop(pipe)) { furi_delay_ms(100); } printf("Emulation stopped\r\n"); @@ -265,8 +265,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(FuriPipeSide* pipe, FuriString* args) { + UNUSED(pipe); FuriString *filepath, *info_string; filepath = furi_string_alloc(); info_string = furi_string_alloc(); @@ -392,9 +392,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(FuriPipeSide* pipe, FuriString* args) { FuriString *filepath, *type_string; filepath = furi_string_alloc(); type_string = furi_string_alloc(); @@ -452,7 +450,7 @@ static void lfrfid_cli_raw_read(Cli* cli, FuriString* args) { } } - if(cli_cmd_interrupt_received(cli)) break; + if(cli_app_should_stop(pipe)) break; } if(overrun) { @@ -479,9 +477,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(FuriPipeSide* pipe, FuriString* args) { FuriString* filepath; filepath = furi_string_alloc(); Storage* storage = furi_record_open(RECORD_STORAGE); @@ -527,7 +523,7 @@ static void lfrfid_cli_raw_emulate(Cli* cli, FuriString* args) { } } - if(cli_cmd_interrupt_received(cli)) break; + if(cli_app_should_stop(pipe)) break; } if(overrun) { @@ -548,7 +544,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(FuriPipeSide* pipe, FuriString* args, void* context) { UNUSED(context); FuriString* cmd; cmd = furi_string_alloc(); @@ -560,17 +556,17 @@ 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(); } diff --git a/applications/main/nfc/nfc_cli.c b/applications/main/nfc/nfc_cli.c index 90ac26d7c23..3a817578ac0 100644 --- a/applications/main/nfc/nfc_cli.c +++ b/applications/main/nfc/nfc_cli.c @@ -17,7 +17,7 @@ static void nfc_cli_print_usage(void) { } } -static void nfc_cli_field(Cli* cli, FuriString* args) { +static void nfc_cli_field(FuriPipeSide* pipe, FuriString* args) { UNUSED(args); // Check if nfc worker is not busy if(furi_hal_nfc_is_hal_ready() != FuriHalNfcErrorNone) { @@ -32,7 +32,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_app_should_stop(pipe)) { furi_delay_ms(50); } @@ -40,7 +40,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(FuriPipeSide* pipe, FuriString* args, void* context) { UNUSED(context); FuriString* cmd; cmd = furi_string_alloc(); @@ -52,7 +52,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/subghz/helpers/subghz_chat.c b/applications/main/subghz/helpers/subghz_chat.c index 9945b69c8b7..60dd8aa87d2 100644 --- a/applications/main/subghz/helpers/subghz_chat.c +++ b/applications/main/subghz/helpers/subghz_chat.c @@ -14,7 +14,7 @@ struct SubGhzChatWorker { FuriMessageQueue* event_queue; uint32_t last_time_rx_data; - Cli* cli; + FuriPipeSide* pipe; }; /** Worker thread @@ -30,7 +30,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(furi_pipe_receive(instance->pipe, (uint8_t*)&c, 1, furi_ms_to_ticks(1000)) == 1) { event.event = SubGhzChatEventInputData; event.c = c; furi_message_queue_put(instance->event_queue, &event, FuriWaitForever); @@ -55,10 +55,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(FuriPipeSide* 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 2c454b75d98..ef6a91f0e8e 100644 --- a/applications/main/subghz/helpers/subghz_chat.h +++ b/applications/main/subghz/helpers/subghz_chat.h @@ -19,7 +19,7 @@ typedef struct { char c; } SubGhzChatEvent; -SubGhzChatWorker* subghz_chat_worker_alloc(Cli* cli); +SubGhzChatWorker* subghz_chat_worker_alloc(FuriPipeSide* 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 6375f2eee4d..8e1b8db0a89 100644 --- a/applications/main/subghz/subghz_cli.c +++ b/applications/main/subghz/subghz_cli.c @@ -4,6 +4,7 @@ #include #include +#include #include #include @@ -68,7 +69,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(FuriPipeSide* pipe, FuriString* args, void* context) { UNUSED(context); uint32_t frequency = 433920000; @@ -98,7 +99,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_app_should_stop(pipe)) { furi_delay_ms(250); } } else { @@ -111,7 +112,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(FuriPipeSide* pipe, FuriString* args, void* context) { UNUSED(context); uint32_t frequency = 433920000; @@ -139,7 +140,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_app_should_stop(pipe)) { furi_delay_ms(250); printf("RSSI: %03.1fdbm\r", (double)furi_hal_subghz_get_rssi()); fflush(stdout); @@ -172,7 +173,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(FuriPipeSide* pipe, FuriString* args, void* context) { UNUSED(context); uint32_t frequency = 433920000; uint32_t key = 0x0074BADE; @@ -242,7 +243,7 @@ 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_app_should_stop(pipe))) { printf("."); fflush(stdout); furi_delay_ms(333); @@ -299,7 +300,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(FuriPipeSide* pipe, FuriString* args, void* context) { UNUSED(context); uint32_t frequency = 433920000; uint32_t device_ind = 0; // 0 - CC1101_INT, 1 - CC1101_EXT @@ -355,7 +356,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_app_should_stop(pipe)) { int ret = furi_stream_buffer_receive( instance->stream, &level_duration, sizeof(LevelDuration), 10); if(ret == sizeof(LevelDuration)) { @@ -388,7 +389,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(FuriPipeSide* pipe, FuriString* args, void* context) { UNUSED(context); uint32_t frequency = 433920000; @@ -426,7 +427,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_app_should_stop(pipe)) { int ret = furi_stream_buffer_receive( instance->stream, &level_duration, sizeof(LevelDuration), 10); if(ret == 0) { @@ -462,7 +463,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(FuriPipeSide* pipe, FuriString* args, void* context) { UNUSED(context); FuriString* file_name; file_name = furi_string_alloc(); @@ -532,7 +533,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_app_should_stop(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)) { @@ -577,7 +578,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(FuriPipeSide* pipe, FuriString* args, void* context) { // -V524 UNUSED(context); FuriString* file_name; file_name = furi_string_alloc(); @@ -780,8 +781,7 @@ void subghz_cli_command_tx_from_file(Cli* cli, FuriString* args, void* context) furi_delay_ms(200); if(subghz_devices_start_async_tx(device, subghz_transmitter_yield, transmitter)) { while( - !(subghz_devices_is_async_complete_tx(device) || - cli_cmd_interrupt_received(cli))) { + !(subghz_devices_is_async_complete_tx(device) || cli_app_should_stop(pipe))) { printf("."); fflush(stdout); furi_delay_ms(333); @@ -795,11 +795,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_app_should_stop(pipe) && repeat) subghz_transmitter_deserialize(transmitter, fff_data_raw); } - } while(!cli_cmd_interrupt_received(cli) && + } while(!cli_app_should_stop(pipe) && (repeat && !strcmp(furi_string_get_cstr(temp_str), "RAW"))); subghz_devices_sleep(device); @@ -844,8 +844,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(FuriPipeSide* pipe, FuriString* args) { + UNUSED(pipe); uint8_t iv[16]; FuriString* source; @@ -887,8 +887,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(FuriPipeSide* pipe, FuriString* args) { + UNUSED(pipe); uint8_t iv[16]; FuriString* source; @@ -924,7 +924,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(FuriPipeSide* pipe, FuriString* args) { uint32_t frequency = 433920000; uint32_t device_ind = 0; // 0 - CC1101_INT, 1 - CC1101_EXT @@ -958,7 +958,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"); @@ -999,13 +999,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"); @@ -1027,7 +1026,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'); @@ -1041,7 +1040,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); @@ -1095,7 +1094,7 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args) { break; } } - if(!cli_is_connected(cli)) { + if(!cli_app_should_stop(pipe)) { printf("\r\n"); chat_event.event = SubGhzChatEventUserExit; subghz_chat_worker_put_event_chat(subghz_chat, &chat_event); @@ -1120,7 +1119,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(FuriPipeSide* pipe, FuriString* args, void* context) { FuriString* cmd; cmd = furi_string_alloc(); @@ -1131,53 +1130,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; } } @@ -1191,9 +1190,7 @@ static void subghz_cli_command(Cli* cli, FuriString* args, void* context) { void subghz_on_system_start(void) { #ifdef SRV_CLI Cli* cli = furi_record_open(RECORD_CLI); - cli_add_command(cli, "subghz", CliCommandFlagDefault, subghz_cli_command, NULL); - furi_record_close(RECORD_CLI); #else UNUSED(subghz_cli_command); diff --git a/applications/services/application.fam b/applications/services/application.fam index b1bafe5d6e5..a1a0429fa11 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", @@ -10,6 +11,5 @@ App( "desktop", "loader", "power", - "cli_vcp", ], ) diff --git a/applications/services/bt/application.fam b/applications/services/bt/application.fam index 2d2840e3a5d..60627756fc2 100644 --- a/applications/services/bt/application.fam +++ b/applications/services/bt/application.fam @@ -5,7 +5,7 @@ App( entry_point="bt_srv", cdefines=["SRV_BT"], requires=[ - "cli", + "cli_start", "dialogs", ], provides=[ diff --git a/applications/services/bt/bt_cli.c b/applications/services/bt/bt_cli.c index 7505c424dde..ec820cc9c30 100644 --- a/applications/services/bt/bt_cli.c +++ b/applications/services/bt/bt_cli.c @@ -8,8 +8,8 @@ #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(FuriPipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); UNUSED(args); UNUSED(context); FuriString* buffer; @@ -19,7 +19,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(FuriPipeSide* pipe, FuriString* args, void* context) { UNUSED(context); int channel = 0; int power = 0; @@ -41,7 +41,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_app_should_stop(pipe)) { furi_delay_ms(250); } furi_hal_bt_stop_tone_tx(); @@ -51,7 +51,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(FuriPipeSide* pipe, FuriString* args, void* context) { UNUSED(context); int channel = 0; @@ -69,7 +69,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_app_should_stop(pipe)) { furi_delay_ms(250); printf("RSSI: %6.1f dB\r", (double)furi_hal_bt_get_rssi()); fflush(stdout); @@ -82,7 +82,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(FuriPipeSide* pipe, FuriString* args, void* context) { UNUSED(context); int channel = 0; int pattern = 0; @@ -119,7 +119,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_app_should_stop(pipe)) { furi_delay_ms(250); } furi_hal_bt_stop_packet_test(); @@ -130,7 +130,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(FuriPipeSide* pipe, FuriString* args, void* context) { UNUSED(context); int channel = 0; int datarate = 1; @@ -152,7 +152,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_app_should_stop(pipe)) { furi_delay_ms(250); printf("RSSI: %03.1f dB\r", (double)furi_hal_bt_get_rssi()); fflush(stdout); @@ -179,7 +179,7 @@ static void bt_cli_print_usage(void) { } } -static void bt_cli(Cli* cli, FuriString* args, void* context) { +static void bt_cli(FuriPipeSide* pipe, FuriString* args, void* context) { UNUSED(context); furi_record_open(RECORD_BT); @@ -194,24 +194,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 6739360efe2..99e90980994 100644 --- a/applications/services/cli/application.fam +++ b/applications/services/cli/application.fam @@ -1,12 +1,10 @@ App( - appid="cli", - name="CliSrv", - apptype=FlipperAppType.SERVICE, - entry_point="cli_srv", + appid="cli_start", + apptype=FlipperAppType.STARTUP, + entry_point="cli_on_system_start", cdefines=["SRV_CLI"], - stack_size=1 * 1024, - order=30, - sdk_headers=["cli.h"], + sources=["cli.c"], + order=0, ) App( @@ -14,7 +12,8 @@ App( name="CliVcpSrv", apptype=FlipperAppType.SERVICE, entry_point="cli_vcp_srv", - stack_size=1 * 1024, + stack_size=768, order=40, sdk_headers=["cli_vcp.h"], + sources=["cli.c", "cli_vcp.c", "cli_shell.c", "cli_commands.c", "cli_ansi.c"], ) diff --git a/applications/services/cli/cli.c b/applications/services/cli/cli.c index 7329e1a0221..1391d8a716a 100644 --- a/applications/services/cli/cli.c +++ b/applications/services/cli/cli.c @@ -1,354 +1,72 @@ +#include "cli.h" #include "cli_i.h" #include "cli_commands.h" -#include "cli_vcp.h" -#include -#include +#include "cli_ansi.h" -#define TAG "CliSrv" - -#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->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); - UNUSED(c); -} - -char cli_getc(Cli* cli) { - furi_check(cli); - char c = 0; - return c; -} - -void cli_write(Cli* cli, const uint8_t* buffer, size_t size) { - furi_check(cli); - UNUSED(buffer); - UNUSED(size); -} - -size_t cli_read(Cli* cli, uint8_t* buffer, size_t size) { - furi_check(cli); - UNUSED(buffer); - UNUSED(size); - return 0; -} - -size_t cli_read_timeout(Cli* cli, uint8_t* buffer, size_t size, uint32_t timeout) { - furi_check(cli); - UNUSED(buffer); - UNUSED(size); - UNUSED(timeout); - return 0; -} - -bool cli_is_connected(Cli* cli) { - furi_check(cli); - return false; -} - -bool cli_cmd_interrupt_received(Cli* cli) { - furi_check(cli); - 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); - } -} +// 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); +// } void cli_add_command( Cli* cli, @@ -396,35 +114,55 @@ void cli_delete_command(Cli* cli, const char* name) { furi_string_free(name_str); } -void cli_session_open(Cli* cli, void* session) { - furi_check(cli); - UNUSED(session); - +bool cli_get_command(Cli* cli, FuriString* command, CliCommand* result) { + furi_assert(cli); furi_check(furi_mutex_acquire(cli->mutex, FuriWaitForever) == FuriStatusOk); - 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); -} -void cli_session_close(Cli* cli) { - furi_check(cli); + return !!data; +} +void cli_lock_commands(Cli* cli) { + furi_assert(cli); furi_check(furi_mutex_acquire(cli->mutex, FuriWaitForever) == FuriStatusOk); - 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); +} - Cli* cli = cli_alloc(); +CliCommandTree_t* cli_get_commands(Cli* cli) { + furi_assert(cli); + return &cli->commands; +} - // Init basic cli commands - cli_commands_init(cli); +bool cli_app_should_stop(FuriPipeSide* side) { + if(furi_pipe_state(side) == FuriPipeStateBroken) return true; + if(!furi_pipe_bytes_available(side)) return false; + char c = getchar(); + if(c == CliKeyETX) { + return true; + } else { + ungetc(c, stdin); + return false; + } +} - furi_record_create(RECORD_CLI, cli); +void cli_print_usage(const char* cmd, const char* usage, const char* arg) { + furi_check(cmd); + furi_check(arg); + furi_check(usage); - while(1) - furi_delay_tick(FuriWaitForever); + printf("%s: illegal option -- %s\r\nusage: %s %s", cmd, arg, cmd, usage); +} - return 0; +void cli_on_system_start(void) { + Cli* cli = cli_alloc(); + cli_commands_init(cli); + furi_record_create(RECORD_CLI, cli); } diff --git a/applications/services/cli/cli.h b/applications/services/cli/cli.h index bb84670a739..3d8bd3f7c92 100644 --- a/applications/services/cli/cli.h +++ b/applications/services/cli/cli.h @@ -1,56 +1,50 @@ /** * @file cli.h - * Cli API + * API for registering commands with the CLI */ #pragma once #include +#include +#include "cli_ansi.h" #ifdef __cplusplus extern "C" { #endif -typedef enum { - CliSymbolAsciiSOH = 0x01, - CliSymbolAsciiETX = 0x03, - CliSymbolAsciiEOT = 0x04, - CliSymbolAsciiBell = 0x07, - CliSymbolAsciiBackspace = 0x08, - CliSymbolAsciiTab = 0x09, - CliSymbolAsciiLF = 0x0A, - CliSymbolAsciiCR = 0x0D, - CliSymbolAsciiEsc = 0x1B, - CliSymbolAsciiUS = 0x1F, - CliSymbolAsciiSpace = 0x20, - CliSymbolAsciiDel = 0x7F, -} CliSymbols; +#define RECORD_CLI "cli" 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 */ + CliCommandFlagDefault = 0, /**< Default */ + CliCommandFlagParallelUnsafe = (1 << 0), /**< Unsafe 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; -#define RECORD_CLI "cli" - /** Cli type anonymous structure */ typedef struct Cli Cli; -/** Cli callback function pointer. Implement this interface and use - * add_cli_command - * @param args string with what was passed after command - * @param context pointer to whatever you gave us on cli_add_command +/** + * @brief CLI callback function pointer. Implement this interface and use + * `add_cli_command`. + * + * @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 (*CliCallback)(FuriPipeSide* pipe, FuriString* args, void* context); -/** Add cli command Registers you command callback +/** + * @brief Registers a command with the CLI * - * @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, @@ -59,75 +53,30 @@ void cli_add_command( CliCallback callback, void* context); -/** Print unified cmd usage tip - * - * @param cmd cmd name - * @param usage usage tip - * @param arg arg passed by user - */ -void cli_print_usage(const char* cmd, const char* usage, const char* arg); - -/** Delete cli command +/** + * @brief Deletes a cli command * - * @param cli pointer to cli instance - * @param name command name + * @param [in] cli pointer to cli instance + * @param [in] 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 +/** + * @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 */ -void cli_write(Cli* cli, const uint8_t* buffer, size_t size); +bool cli_app_should_stop(FuriPipeSide* side); -/** Read character - * - * @param cli Cli instance +/** Print unified cmd usage tip * - * @return char - */ -char cli_getc(Cli* cli); - -/** New line Send new ine sequence + * @param cmd cmd name + * @param usage usage tip + * @param arg arg passed by user */ -void cli_nl(Cli* cli); - -void cli_session_open(Cli* cli, void* session); - -void cli_session_close(Cli* cli); - -bool cli_is_connected(Cli* cli); +void cli_print_usage(const char* cmd, const char* usage, const char* arg); #ifdef __cplusplus } diff --git a/applications/services/cli/cli_commands.c b/applications/services/cli/cli_commands.c index e4503b27462..3de3ab59ccd 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 @@ -34,8 +36,8 @@ void cli_command_info_callback(const char* key, const char* value, bool last, vo * @param args The arguments * @param context The context */ -void cli_command_info(Cli* cli, FuriString* args, void* context) { - UNUSED(cli); +void cli_command_info(FuriPipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); if(context) { furi_hal_info_get(cli_command_info_callback, '_', NULL); @@ -53,56 +55,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(FuriPipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); UNUSED(args); UNUSED(context); - printf("Commands available:"); - - // Command count - const size_t commands_count = CliCommandTree_size(cli->commands); - const size_t commands_count_mid = commands_count / 2 + commands_count % 2; - - // Use 2 iterators from start and middle to show 2 columns - CliCommandTree_it_t it_left; - CliCommandTree_it(it_left, cli->commands); - CliCommandTree_it_t it_right; - CliCommandTree_it(it_right, cli->commands); - for(size_t i = 0; i < commands_count_mid; i++) - CliCommandTree_next(it_right); - - // Iterate throw tree - for(size_t i = 0; i < commands_count_mid; i++) { + printf("Built-in shell commands:" ANSI_FG_GREEN); + + // 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); + + // 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"); - // 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"); + 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(FuriPipeSide* 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(FuriPipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); UNUSED(context); DateTime datetime = {0}; @@ -174,7 +177,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); + FuriPipeSide* pipe = context; + furi_pipe_send(pipe, buffer, size, FuriWaitForever); } bool cli_command_log_level_set_from_string(FuriString* level) { @@ -196,16 +200,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(FuriPipeSide* 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 +218,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_app_should_stop(pipe)) { + furi_delay_ms(100); } furi_log_remove_handler(log_handler); @@ -235,12 +235,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(FuriPipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); UNUSED(context); if(!furi_string_cmp(args, "0")) { furi_hal_rtc_reset_flag(FuriHalRtcFlagDebug); @@ -253,8 +251,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(FuriPipeSide* 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 +286,7 @@ void cli_command_sysctl_print_usage(void) { #endif } -void cli_command_sysctl(Cli* cli, FuriString* args, void* context) { +void cli_command_sysctl(FuriPipeSide* pipe, FuriString* args, void* context) { FuriString* cmd; cmd = furi_string_alloc(); @@ -299,12 +297,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 +312,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(FuriPipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); UNUSED(context); if(!furi_string_cmp(args, "0")) { @@ -341,8 +339,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(FuriPipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); UNUSED(context); // Get first word as light name NotificationMessage notification_led_message; @@ -396,23 +394,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(FuriPipeSide* 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_app_should_stop(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 +418,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 +436,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 +454,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 +466,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(FuriPipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); UNUSED(args); UNUSED(context); @@ -476,16 +480,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(FuriPipeSide* 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(FuriPipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); UNUSED(args); UNUSED(context); @@ -508,23 +512,23 @@ void cli_command_i2c(Cli* cli, FuriString* args, void* context) { } void cli_commands_init(Cli* cli) { - cli_add_command(cli, "!", CliCommandFlagParallelSafe, cli_command_info, (void*)true); - cli_add_command(cli, "info", CliCommandFlagParallelSafe, cli_command_info, NULL); - cli_add_command(cli, "device_info", CliCommandFlagParallelSafe, cli_command_info, (void*)true); - - cli_add_command(cli, "?", CliCommandFlagParallelSafe, cli_command_help, NULL); - cli_add_command(cli, "help", CliCommandFlagParallelSafe, cli_command_help, NULL); - - cli_add_command(cli, "uptime", CliCommandFlagDefault, cli_command_uptime, NULL); - cli_add_command(cli, "date", CliCommandFlagParallelSafe, cli_command_date, NULL); - cli_add_command(cli, "log", CliCommandFlagParallelSafe, cli_command_log, NULL); - cli_add_command(cli, "sysctl", CliCommandFlagDefault, cli_command_sysctl, NULL); - cli_add_command(cli, "top", CliCommandFlagParallelSafe, cli_command_top, NULL); - cli_add_command(cli, "free", CliCommandFlagParallelSafe, cli_command_free, NULL); - cli_add_command(cli, "free_blocks", CliCommandFlagParallelSafe, cli_command_free_blocks, NULL); - - cli_add_command(cli, "vibro", CliCommandFlagDefault, cli_command_vibro, NULL); - cli_add_command(cli, "led", CliCommandFlagDefault, cli_command_led, NULL); - cli_add_command(cli, "gpio", CliCommandFlagDefault, cli_command_gpio, NULL); - cli_add_command(cli, "i2c", CliCommandFlagDefault, cli_command_i2c, NULL); + cli_add_command(cli, "!", CliCommandFlagDefault, cli_command_info, (void*)true); + cli_add_command(cli, "info", CliCommandFlagDefault, cli_command_info, NULL); + cli_add_command(cli, "device_info", CliCommandFlagDefault, cli_command_info, (void*)true); + + cli_add_command(cli, "?", CliCommandFlagDefault, cli_command_help, NULL); + cli_add_command(cli, "help", CliCommandFlagDefault, cli_command_help, NULL); + + cli_add_command(cli, "uptime", CliCommandFlagParallelUnsafe, cli_command_uptime, NULL); + cli_add_command(cli, "date", CliCommandFlagDefault, cli_command_date, NULL); + cli_add_command(cli, "log", CliCommandFlagDefault, cli_command_log, NULL); + cli_add_command(cli, "sysctl", CliCommandFlagParallelUnsafe, cli_command_sysctl, NULL); + cli_add_command(cli, "top", CliCommandFlagDefault, cli_command_top, NULL); + cli_add_command(cli, "free", CliCommandFlagDefault, cli_command_free, NULL); + cli_add_command(cli, "free_blocks", CliCommandFlagDefault, cli_command_free_blocks, NULL); + + cli_add_command(cli, "vibro", CliCommandFlagParallelUnsafe, cli_command_vibro, NULL); + cli_add_command(cli, "led", CliCommandFlagParallelUnsafe, cli_command_led, NULL); + // cli_add_command(cli, "gpio", CliCommandFlagParallelUnsafe, cli_command_gpio, NULL); + cli_add_command(cli, "i2c", CliCommandFlagParallelUnsafe, cli_command_i2c, NULL); } diff --git a/applications/services/cli/cli_i.h b/applications/services/cli/cli_i.h index b9e41054f51..28c3d60d4e2 100644 --- a/applications/services/cli/cli_i.h +++ b/applications/services/cli/cli_i.h @@ -1,18 +1,13 @@ -#pragma once +/** + * @file cli_i.h + * Internal API for getting commands registered with the CLI + */ -#include "cli.h" +#pragma once #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" { @@ -21,18 +16,10 @@ extern "C" { typedef struct { CliCallback callback; void* context; - uint32_t flags; + CliCommandFlag flags; } CliCommand; -struct CliSession { - void (*init)(void); - void (*deinit)(void); - size_t (*rx)(uint8_t* buffer, size_t size, uint32_t timeout); - size_t (*rx_stdin)(uint8_t* buffer, size_t size, uint32_t timeout, void* context); - void (*tx)(const uint8_t* buffer, size_t size); - void (*tx_stdout)(const char* data, size_t size, void* context); - bool (*is_connected)(void); -}; +#define CLI_COMMANDS_TREE_RANK 4 BPTREE_DEF2( CliCommandTree, @@ -40,27 +27,20 @@ BPTREE_DEF2( FuriString*, FURI_STRING_OPLIST, CliCommand, - M_POD_OPLIST) + M_POD_OPLIST); #define M_OPL_CliCommandTree_t() BPTREE_OPLIST(CliCommandTree, M_POD_OPLIST) -struct Cli { - CliCommandTree_t commands; - FuriMutex* mutex; - FuriSemaphore* idle_sem; - FuriString* last_line; - FuriString* line; - - size_t cursor_position; -}; - -Cli* cli_alloc(void); +bool cli_get_command(Cli* cli, FuriString* command, CliCommand* result); -void cli_reset(Cli* cli); +void cli_lock_commands(Cli* cli); -void cli_putc(Cli* cli, char c); +void cli_unlock_commands(Cli* cli); -void cli_stdout_callback(void* _cookie, const char* data, size_t size); +/** + * @warning Surround calls to this function with `cli_[un]lock_commands` + */ +CliCommandTree_t* cli_get_commands(Cli* cli); #ifdef __cplusplus } diff --git a/applications/services/cli/cli_shell.c b/applications/services/cli/cli_shell.c index 2d8f09a4a48..535b89fa4cb 100644 --- a/applications/services/cli/cli_shell.c +++ b/applications/services/cli/cli_shell.c @@ -1,8 +1,10 @@ #include "cli_shell.h" #include "cli_ansi.h" +#include "cli_i.h" #include #include #include +#include #define TAG "CliShell" @@ -12,6 +14,8 @@ ARRAY_DEF(ShellHistory, FuriString*, FURI_STRING_OPLIST); #define M_OPL_ShellHistory_t() ARRAY_OPLIST(ShellHistory) typedef struct { + Cli* cli; + FuriEventLoop* event_loop; FuriPipeSide* pipe; CliAnsiParser* ansi_parser; @@ -21,6 +25,72 @@ typedef struct { ShellHistory_t history; } CliShell; +typedef struct { + CliCommand* command; + FuriPipeSide* pipe; + FuriString* args; +} CliCommandThreadData; + +static int32_t cli_command_thread(void* context) { + CliCommandThreadData* thread_data = context; + if(!(thread_data->command->flags & CliCommandFlagDontAttachStdio)) + furi_pipe_install_as_stdio(thread_data->pipe); + + thread_data->command->callback( + thread_data->pipe, thread_data->args, thread_data->command->context); + + fflush(stdout); + return 0; +} + +static void cli_shell_execute_command(CliShell* cli_shell, FuriString* command) { + // split command into command and args + size_t space = furi_string_search_char(command, ' '); + if(space == FURI_STRING_FAILURE) space = furi_string_size(command); + FuriString* command_name = furi_string_alloc_set(command); + furi_string_left(command_name, space); + FuriString* args = furi_string_alloc_set(command); + furi_string_right(args, space + 1); // FIXME: + + // find handler + CliCommand command_data; + if(!cli_get_command(cli_shell->cli, command_name, &command_data)) { + printf( + ANSI_FG_RED "could not find command `%s`" ANSI_RESET, + furi_string_get_cstr(command_name)); + return; + } + + // lock loader + Loader* loader = furi_record_open(RECORD_LOADER); + if(command_data.flags & CliCommandFlagParallelUnsafe) { + bool success = loader_lock(loader); + if(!success) { + printf(ANSI_FG_RED + "this command cannot be run while an application is open" ANSI_RESET); + return; + } + } + + // run command in separate thread + CliCommandThreadData thread_data = { + .command = &command_data, + .pipe = cli_shell->pipe, + .args = args, + }; + FuriThread* thread = furi_thread_alloc_ex( + furi_string_get_cstr(command_name), CLI_SHELL_STACK_SIZE, cli_command_thread, &thread_data); + furi_thread_start(thread); + furi_thread_join(thread); + furi_thread_free(thread); + + furi_string_free(command_name); + furi_string_free(args); + + // unlock loader + if(command_data.flags & CliCommandFlagParallelUnsafe) loader_unlock(loader); +} + static void cli_shell_tick(void* context) { CliShell* cli_shell = context; if(furi_pipe_state(cli_shell->pipe) == FuriPipeStateBroken) { @@ -45,14 +115,6 @@ static void cli_shell_prompt(CliShell* cli_shell) { fflush(stdout); } -static void cli_shell_dump_history(CliShell* cli_shell) { - FURI_LOG_T(TAG, "history depth=%d, entries:", ShellHistory_size(cli_shell->history)); - for - M_EACH(entry, cli_shell->history, ShellHistory_t) { - FURI_LOG_T(TAG, " \"%s\"", furi_string_get_cstr(*entry)); - } -} - /** * If a line from history has been selected, moves it into the active line */ @@ -160,23 +222,26 @@ static void cli_shell_data_available(FuriEventLoopObject* object, void* context) strlen(prompt) + cli_shell->line_position + 1 /* 1-based column indexing */); fflush(stdout); - } else if(key_combo.modifiers == 0 && key_combo.key == CliKeyLF) { + } else if(key_combo.modifiers == 0 && key_combo.key == CliKeyCR) { // get command and update history - cli_shell_dump_history(cli_shell); FuriString* command = furi_string_alloc(); ShellHistory_pop_at(&command, cli_shell->history, cli_shell->history_position); + furi_string_trim(command); if(cli_shell->history_position > 0) ShellHistory_pop_at(NULL, cli_shell->history, 0); if(!furi_string_empty(command)) ShellHistory_push_at(cli_shell->history, 0, command); - ShellHistory_push_at(cli_shell->history, 0, furi_string_alloc()); + FuriString* new_command = furi_string_alloc(); + ShellHistory_push_at(cli_shell->history, 0, new_command); + furi_string_free(new_command); if(ShellHistory_size(cli_shell->history) > HISTORY_DEPTH) { ShellHistory_pop_back(NULL, cli_shell->history); } - cli_shell_dump_history(cli_shell); // execute command cli_shell->line_position = 0; cli_shell->history_position = 0; - printf("\r\ncommand input: \"%s\"", furi_string_get_cstr(command)); + printf("\r\n"); + cli_shell_execute_command(cli_shell, command); + furi_string_free(command); cli_shell_prompt(cli_shell); } else if(key_combo.modifiers == 0 && (key_combo.key == CliKeyUp || key_combo.key == CliKeyDown)) { @@ -287,11 +352,12 @@ static void cli_shell_data_available(FuriEventLoopObject* object, void* context) FuriString* line = *ShellHistory_front(cli_shell->history); if(cli_shell->line_position == furi_string_size(line)) { furi_string_push_back(line, key_combo.key); + printf("%c", key_combo.key); } else { const char in_str[2] = {key_combo.key, 0}; furi_string_replace_at(line, cli_shell->line_position, 0, in_str); + printf(ANSI_INSERT_MODE_ENABLE "%c" ANSI_INSERT_MODE_DISABLE, key_combo.key); } - printf(ANSI_INSERT_MODE_ENABLE "%c" ANSI_INSERT_MODE_DISABLE, key_combo.key); fflush(stdout); cli_shell->line_position++; } @@ -299,6 +365,7 @@ static void cli_shell_data_available(FuriEventLoopObject* object, void* context) static CliShell* cli_shell_alloc(FuriPipeSide* 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->event_loop = furi_event_loop_alloc(); ShellHistory_init(cli_shell->history); @@ -324,6 +391,7 @@ static void cli_shell_free(CliShell* cli_shell) { furi_pipe_free(cli_shell->pipe); ShellHistory_clear(cli_shell->history); cli_ansi_parser_free(cli_shell->ansi_parser); + furi_record_close(RECORD_CLI); free(cli_shell); } @@ -375,8 +443,9 @@ static int32_t cli_shell_thread(void* context) { return 0; } -void cli_shell_start(FuriPipeSide* pipe) { +FuriThread* cli_shell_start(FuriPipeSide* 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/cli_shell.h b/applications/services/cli/cli_shell.h index 4cfe06ce960..3594b6029ca 100644 --- a/applications/services/cli/cli_shell.h +++ b/applications/services/cli/cli_shell.h @@ -8,7 +8,7 @@ extern "C" { #define CLI_SHELL_STACK_SIZE (1 * 1024U) -void cli_shell_start(FuriPipeSide* pipe); +FuriThread* cli_shell_start(FuriPipeSide* pipe); #ifdef __cplusplus } diff --git a/applications/services/cli/cli_vcp.c b/applications/services/cli/cli_vcp.c index 78686a28bf6..7f671a94337 100644 --- a/applications/services/cli/cli_vcp.c +++ b/applications/services/cli/cli_vcp.c @@ -3,6 +3,7 @@ #include #include #include +#include #define TAG "CliVcp" @@ -11,7 +12,13 @@ #define VCP_IF_NUM 0 -#define VCP_MESSAGE_Q_LEN 4 +#define VCP_MESSAGE_Q_LEN 8 + +// #ifdef VCP_TRACE +#define VCP_LOG_T(...) FURI_LOG_T(__VA_ARGS__) +// #else +// #define VCP_LOG_T(...) +// #endif typedef struct { enum { @@ -22,10 +29,10 @@ typedef struct { } CliVcpMessage; typedef enum { - CliVcpInternalMessageConnected, - CliVcpInternalMessageDisconnected, - CliVcpInternalMessageTxDone, - CliVcpInternalMessageRx, + CliVcpInternalMessageConnected = 1U << 0, + CliVcpInternalMessageDisconnected = 1U << 1, + CliVcpInternalMessageTxDone = 1U << 2, + CliVcpInternalMessageRx = 1U << 3, } CliVcpInternalMessage; struct CliVcp { @@ -39,32 +46,71 @@ struct CliVcp { FuriPipeSide* own_pipe; bool is_currently_transmitting; size_t previous_tx_length; + + FuriThread* shell; }; +// ============ +// Data copying +// ============ + +/** + * Called in the following cases: + * - previous transfer has finished; + * - new data became available to send. + */ +static void cli_vcp_maybe_send_data(CliVcp* cli_vcp) { + if(cli_vcp->is_currently_transmitting) return; + if(!cli_vcp->own_pipe) return; + + uint8_t buf[USB_CDC_PKT_LEN]; + size_t length = furi_pipe_receive(cli_vcp->own_pipe, buf, sizeof(buf), 0); + if(length > 0 || cli_vcp->previous_tx_length == USB_CDC_PKT_LEN) { + VCP_LOG_T(TAG, "cdc_send length=%u", length); + cli_vcp->is_currently_transmitting = true; + furi_hal_cdc_send(VCP_IF_NUM, buf, length); + } + cli_vcp->previous_tx_length = length; +} + +/** + * 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(furi_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_LOG_T(TAG, "cdc_receive length=%u", length); + furi_check(furi_pipe_send(cli_vcp->own_pipe, buf, length, 0) == length); +} + // ============= // CDC callbacks // ============= -static void - cli_vcp_send_internal_message(CliVcp* cli_vcp, CliVcpInternalMessage message, bool check) { - FuriStatus status = furi_message_queue_put(cli_vcp->internal_message_queue, &message, 0); - if(check) furi_check(status == FuriStatusOk); +static void cli_vcp_send_internal_message(CliVcp* cli_vcp, CliVcpInternalMessage message) { + furi_check( + furi_message_queue_put(cli_vcp->internal_message_queue, &message, 0) == FuriStatusOk); } static void cli_vcp_cdc_tx_done(void* context) { CliVcp* cli_vcp = context; - cli_vcp_send_internal_message(cli_vcp, CliVcpInternalMessageTxDone, true); + cli_vcp_send_internal_message(cli_vcp, CliVcpInternalMessageTxDone); } static void cli_vcp_cdc_rx(void* context) { CliVcp* cli_vcp = context; - cli_vcp_send_internal_message(cli_vcp, CliVcpInternalMessageRx, false); + cli_vcp_send_internal_message(cli_vcp, CliVcpInternalMessageRx); } static void cli_vcp_cdc_state_callback(void* context, CdcState state) { CliVcp* cli_vcp = context; if(state == CdcStateDisconnected) { - cli_vcp_send_internal_message(cli_vcp, CliVcpInternalMessageDisconnected, true); + cli_vcp_send_internal_message(cli_vcp, CliVcpInternalMessageDisconnected); } // `Connected` events are generated by DTR going active } @@ -72,9 +118,9 @@ static void cli_vcp_cdc_state_callback(void* context, CdcState state) { static void cli_vcp_cdc_ctrl_line_callback(void* context, CdcCtrlLine ctrl_lines) { CliVcp* cli_vcp = context; if(ctrl_lines & CdcCtrlLineDTR) { - cli_vcp_send_internal_message(cli_vcp, CliVcpInternalMessageConnected, true); + cli_vcp_send_internal_message(cli_vcp, CliVcpInternalMessageConnected); } else { - cli_vcp_send_internal_message(cli_vcp, CliVcpInternalMessageDisconnected, true); + cli_vcp_send_internal_message(cli_vcp, CliVcpInternalMessageDisconnected); } } @@ -90,40 +136,6 @@ static CdcCallbacks cdc_callbacks = { // EventLoop handlers // ================== -/** - * Called in the following cases: - * - previous transfer has finished; - * - new data became available to send. - */ -static void cli_vcp_maybe_send_data(CliVcp* cli_vcp) { - if(cli_vcp->is_currently_transmitting) return; - if(!cli_vcp->own_pipe) return; - - uint8_t buf[USB_CDC_PKT_LEN]; - size_t length = furi_pipe_receive(cli_vcp->own_pipe, buf, sizeof(buf), 0); - if(length > 0 || cli_vcp->previous_tx_length == USB_CDC_PKT_LEN) { - FURI_LOG_T(TAG, "cdc_send length=%u", length); - cli_vcp->is_currently_transmitting = true; - furi_hal_cdc_send(VCP_IF_NUM, buf, length); - } - cli_vcp->previous_tx_length = length; -} - -/** - * 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(furi_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)); - FURI_LOG_T(TAG, "cdc_receive length=%u", length); - furi_check(furi_pipe_send(cli_vcp->own_pipe, buf, length, FuriWaitForever) == length); -} - static void cli_vcp_data_from_shell(FuriEventLoopObject* object, void* context) { UNUSED(object); CliVcp* cli_vcp = context; @@ -133,6 +145,7 @@ static void cli_vcp_data_from_shell(FuriEventLoopObject* object, void* context) static void cli_vcp_shell_ready(FuriEventLoopObject* object, void* context) { UNUSED(object); CliVcp* cli_vcp = context; + VCP_LOG_T(TAG, "shell_ready"); cli_vcp_maybe_receive_data(cli_vcp); } @@ -177,11 +190,40 @@ static void cli_vcp_internal_message_received(FuriEventLoopObject* object, void* furi_check(furi_message_queue_get(object, &message, 0) == FuriStatusOk); switch(message) { + case CliVcpInternalMessageTxDone: + VCP_LOG_T(TAG, "TxDone"); + cli_vcp->is_currently_transmitting = false; + cli_vcp_maybe_send_data(cli_vcp); + break; + + case CliVcpInternalMessageRx: + VCP_LOG_T(TAG, "Rx"); + cli_vcp_maybe_receive_data(cli_vcp); + break; + + case CliVcpInternalMessageDisconnected: + if(!cli_vcp->is_connected) return; + FURI_LOG_D(TAG, "Disconnected"); + cli_vcp->is_connected = false; + + // disconnect our side of the pipe + furi_event_loop_unsubscribe(cli_vcp->event_loop, cli_vcp->own_pipe); + furi_pipe_free(cli_vcp->own_pipe); + cli_vcp->own_pipe = NULL; + break; + case CliVcpInternalMessageConnected: 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 FuriPipe pipe = furi_pipe_alloc(VCP_BUF_SIZE, 1); cli_vcp->own_pipe = pipe.alices_side; @@ -197,29 +239,8 @@ static void cli_vcp_internal_message_received(FuriEventLoopObject* object, void* FuriEventLoopEventOut | FuriEventLoopEventFlagEdge, cli_vcp_shell_ready, cli_vcp); - cli_shell_start(pipe.bobs_side); - break; - - case CliVcpInternalMessageDisconnected: - if(!cli_vcp->is_connected) return; - FURI_LOG_D(TAG, "Disconnected"); - cli_vcp->is_connected = false; - - // disconnect our side of the pipe - furi_event_loop_unsubscribe(cli_vcp->event_loop, cli_vcp->own_pipe); - furi_pipe_free(cli_vcp->own_pipe); - cli_vcp->own_pipe = NULL; - break; - - case CliVcpInternalMessageTxDone: - FURI_LOG_T(TAG, "TxDone"); - cli_vcp->is_currently_transmitting = false; - cli_vcp_maybe_send_data(cli_vcp); - break; - - case CliVcpInternalMessageRx: - FURI_LOG_T(TAG, "Rx"); - cli_vcp_maybe_receive_data(cli_vcp); + furi_delay_ms(33); // we are too fast, minicom isn't ready yet + cli_vcp->shell = cli_shell_start(pipe.bobs_side); break; } } diff --git a/applications/services/crypto/crypto_cli.c b/applications/services/crypto/crypto_cli.c index 744fa7151d1..91b6e94dbf9 100644 --- a/applications/services/crypto/crypto_cli.c +++ b/applications/services/crypto/crypto_cli.c @@ -17,7 +17,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(FuriPipeSide* pipe, FuriString* args) { int key_slot = 0; bool key_loaded = false; uint8_t iv[16]; @@ -44,15 +44,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(furi_pipe_receive(pipe, (uint8_t*)&c, 1, FuriWaitForever) == 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 +92,7 @@ void crypto_cli_encrypt(Cli* cli, FuriString* args) { } } -void crypto_cli_decrypt(Cli* cli, FuriString* args) { +void crypto_cli_decrypt(FuriPipeSide* pipe, FuriString* args) { int key_slot = 0; bool key_loaded = false; uint8_t iv[16]; @@ -119,15 +119,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(furi_pipe_receive(pipe, (uint8_t*)&c, 1, FuriWaitForever) == 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 +164,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(FuriPipeSide* pipe, FuriString* args) { + UNUSED(pipe); int key_slot = 0; uint8_t iv[16] = {0}; @@ -186,8 +186,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(FuriPipeSide* pipe, FuriString* args) { + UNUSED(pipe); int key_slot = 0; int key_size = 0; FuriString* key_type; @@ -279,7 +279,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(FuriPipeSide* pipe, FuriString* args, void* context) { UNUSED(context); FuriString* cmd; cmd = furi_string_alloc(); @@ -291,22 +291,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/input/input.c b/applications/services/input/input.c index 6cbafb79583..3ad3170de84 100644 --- a/applications/services/input/input.c +++ b/applications/services/input/input.c @@ -25,7 +25,7 @@ typedef struct { } InputPinState; /** Input CLI command handler */ -void input_cli(Cli* cli, FuriString* args, void* context); +void input_cli(FuriPipeSide* pipe, FuriString* args, void* context); // #define INPUT_DEBUG @@ -93,7 +93,7 @@ int32_t input_srv(void* p) { #ifdef SRV_CLI Cli* cli = furi_record_open(RECORD_CLI); - cli_add_command(cli, "input", CliCommandFlagParallelSafe, input_cli, event_pubsub); + cli_add_command(cli, "input", CliCommandFlagDefault, input_cli, event_pubsub); #endif InputPinState pin_states[input_pins_count]; diff --git a/applications/services/input/input_cli.c b/applications/services/input/input_cli.c index 8e711c8954a..e2f0ca29ece 100644 --- a/applications/services/input/input_cli.c +++ b/applications/services/input/input_cli.c @@ -19,7 +19,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(FuriPipeSide* pipe, FuriString* args, FuriPubSub* event_pubsub) { UNUSED(args); FuriMessageQueue* input_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); FuriPubSubSubscription* input_subscription = @@ -27,7 +27,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_app_should_stop(pipe)) { if(furi_message_queue_get(input_queue, &input_event, 100) == FuriStatusOk) { printf( "key: %s type: %s\r\n", @@ -47,8 +47,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(FuriPipeSide* pipe, FuriString* args, FuriPubSub* event_pubsub) { + UNUSED(pipe); InputEvent event; FuriString* key_str; key_str = furi_string_alloc(); @@ -97,8 +97,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(FuriPipeSide* pipe, FuriString* args, void* context) { furi_assert(context); FuriPubSub* event_pubsub = context; FuriString* cmd; @@ -110,11 +109,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 f3ea30df2ab..efd83cc87cc 100644 --- a/applications/services/loader/loader_cli.c +++ b/applications/services/loader/loader_cli.c @@ -110,8 +110,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(FuriPipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); UNUSED(context); Loader* loader = furi_record_open(RECORD_LOADER); @@ -141,7 +141,7 @@ static void loader_cli(Cli* cli, FuriString* args, void* context) { void loader_on_system_start(void) { #ifdef SRV_CLI Cli* cli = furi_record_open(RECORD_CLI); - cli_add_command(cli, RECORD_LOADER, CliCommandFlagParallelSafe, loader_cli, NULL); + cli_add_command(cli, RECORD_LOADER, CliCommandFlagDefault, loader_cli, NULL); furi_record_close(RECORD_CLI); #else UNUSED(loader_cli); diff --git a/applications/services/power/application.fam b/applications/services/power/application.fam index f14d88c5426..059c570c40d 100644 --- a/applications/services/power/application.fam +++ b/applications/services/power/application.fam @@ -6,7 +6,7 @@ App( cdefines=["SRV_POWER"], requires=[ "gui", - "cli", + "cli_start", ], provides=[ "power_settings", diff --git a/applications/services/power/power_cli.c b/applications/services/power/power_cli.c index 93d0f232ac8..e74add6369d 100644 --- a/applications/services/power/power_cli.c +++ b/applications/services/power/power_cli.c @@ -5,8 +5,8 @@ #include #include -void power_cli_off(Cli* cli, FuriString* args) { - UNUSED(cli); +void power_cli_off(FuriPipeSide* 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 +14,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(FuriPipeSide* 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(FuriPipeSide* 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(FuriPipeSide* pipe, FuriString* args) { + UNUSED(pipe); if(!furi_string_cmp(args, "0")) { furi_hal_power_disable_otg(); } else if(!furi_string_cmp(args, "1")) { @@ -39,8 +39,8 @@ void power_cli_5v(Cli* cli, FuriString* args) { } } -void power_cli_3v3(Cli* cli, FuriString* args) { - UNUSED(cli); +void power_cli_3v3(FuriPipeSide* 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")) { @@ -64,7 +64,7 @@ static void power_cli_command_print_usage(void) { } } -void power_cli(Cli* cli, FuriString* args, void* context) { +void power_cli(FuriPipeSide* pipe, FuriString* args, void* context) { UNUSED(context); FuriString* cmd; cmd = furi_string_alloc(); @@ -76,28 +76,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; } } @@ -112,7 +112,7 @@ void power_on_system_start(void) { #ifdef SRV_CLI Cli* cli = furi_record_open(RECORD_CLI); - cli_add_command(cli, "power", CliCommandFlagParallelSafe, power_cli, NULL); + cli_add_command(cli, "power", CliCommandFlagDefault, power_cli, NULL); furi_record_close(RECORD_CLI); #else diff --git a/applications/services/rpc/application.fam b/applications/services/rpc/application.fam index 7c0b6813369..c8e26e0446f 100644 --- a/applications/services/rpc/application.fam +++ b/applications/services/rpc/application.fam @@ -3,7 +3,7 @@ App( apptype=FlipperAppType.STARTUP, entry_point="rpc_on_system_start", cdefines=["SRV_RPC"], - requires=["cli"], + requires=["cli_start"], order=10, sdk_headers=["rpc_app.h"], ) diff --git a/applications/services/rpc/rpc.c b/applications/services/rpc/rpc.c index 41d55841ef0..6d66bbe8637 100644 --- a/applications/services/rpc/rpc.c +++ b/applications/services/rpc/rpc.c @@ -437,7 +437,7 @@ void rpc_on_system_start(void* p) { Cli* cli = furi_record_open(RECORD_CLI); cli_add_command( - cli, "start_rpc_session", CliCommandFlagParallelSafe, rpc_cli_command_start_session, rpc); + cli, "start_rpc_session", CliCommandFlagDefault, rpc_cli_command_start_session, rpc); furi_record_create(RECORD_RPC, rpc); } diff --git a/applications/services/rpc/rpc_cli.c b/applications/services/rpc/rpc_cli.c index 4612752a83c..3b30730bd07 100644 --- a/applications/services/rpc/rpc_cli.c +++ b/applications/services/rpc/rpc_cli.c @@ -6,7 +6,7 @@ #define TAG "RpcCli" typedef struct { - Cli* cli; + FuriPipeSide* pipe; bool session_close_request; FuriSemaphore* terminate_semaphore; } CliRpc; @@ -19,7 +19,11 @@ static void rpc_cli_send_bytes_callback(void* context, uint8_t* bytes, size_t by furi_assert(bytes_len > 0); CliRpc* cli_rpc = context; - cli_write(cli_rpc->cli, bytes, bytes_len); + while(bytes_len) { + size_t sent = furi_pipe_send(cli_rpc->pipe, bytes, bytes_len, FuriWaitForever); + bytes += sent; + bytes_len -= sent; + } } static void rpc_cli_session_close_callback(void* context) { @@ -36,9 +40,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(FuriPipeSide* pipe, FuriString* args, void* context) { UNUSED(args); - furi_assert(cli); + furi_assert(pipe); furi_assert(context); Rpc* rpc = context; @@ -53,7 +57,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 +68,10 @@ 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_received = + furi_pipe_receive(cli_rpc.pipe, buffer, CLI_READ_BUFFER_SIZE, furi_ms_to_ticks(50)); + if((furi_pipe_state(cli_rpc.pipe) == FuriPipeStateBroken) || + cli_rpc.session_close_request) { break; } diff --git a/applications/services/rpc/rpc_i.h b/applications/services/rpc/rpc_i.h index 0342df2b656..ef65283f7d8 100644 --- a/applications/services/rpc/rpc_i.h +++ b/applications/services/rpc/rpc_i.h @@ -46,7 +46,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(FuriPipeSide* 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 441b58da66b..f78580d75d8 100644 --- a/applications/services/storage/storage_cli.c +++ b/applications/services/storage/storage_cli.c @@ -19,8 +19,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(FuriPipeSide* pipe, FuriString* path, FuriString* args) { + UNUSED(pipe); UNUSED(args); Storage* api = furi_record_open(RECORD_STORAGE); @@ -69,13 +69,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(FuriPipeSide* 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 +97,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(FuriPipeSide* pipe, FuriString* path, FuriString* args) { + UNUSED(pipe); UNUSED(args); if(furi_string_cmp_str(path, "/") == 0) { printf("\t[D] int\r\n"); @@ -134,13 +135,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(FuriPipeSide* 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 +177,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(FuriPipeSide* pipe, FuriString* path, FuriString* args) { + UNUSED(pipe); UNUSED(args); Storage* api = furi_record_open(RECORD_STORAGE); File* file = storage_file_alloc(api); @@ -208,7 +209,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(FuriPipeSide* pipe, FuriString* path, FuriString* args) { + UNUSED(pipe); UNUSED(args); Storage* api = furi_record_open(RECORD_STORAGE); File* file = storage_file_alloc(api); @@ -222,9 +224,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 +265,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(FuriPipeSide* pipe, FuriString* path, FuriString* args) { + UNUSED(pipe); Storage* api = furi_record_open(RECORD_STORAGE); File* file = storage_file_alloc(api); @@ -280,7 +283,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,30 +305,29 @@ 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(FuriPipeSide* 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"); + char buffer[32]; - if(buffer_size) { - uint8_t* buffer = malloc(buffer_size); - - size_t read_bytes = cli_read(cli, buffer, buffer_size); - - size_t written_size = storage_file_write(file, buffer, read_bytes); + while(need_to_read) { + size_t read_this_time = furi_pipe_receive( + pipe, buffer, MIN(sizeof(buffer), need_to_read), FuriWaitForever); + size_t wrote_this_time = storage_file_write(file, buffer, read_this_time); - 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; } } else { storage_cli_print_error(storage_file_get_error(file)); @@ -337,8 +339,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(FuriPipeSide* pipe, FuriString* path, FuriString* args) { + UNUSED(pipe); UNUSED(args); Storage* api = furi_record_open(RECORD_STORAGE); @@ -379,8 +381,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(FuriPipeSide* pipe, FuriString* path, FuriString* args) { + UNUSED(pipe); UNUSED(args); Storage* api = furi_record_open(RECORD_STORAGE); @@ -396,8 +398,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(FuriPipeSide* 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 +419,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(FuriPipeSide* 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 +432,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(FuriPipeSide* 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 +453,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(FuriPipeSide* 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 +466,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(FuriPipeSide* pipe, FuriString* path, FuriString* args) { + UNUSED(pipe); UNUSED(args); Storage* api = furi_record_open(RECORD_STORAGE); File* file = storage_file_alloc(api); @@ -491,8 +493,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(FuriPipeSide* 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 +528,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)(FuriPipeSide* pipe, FuriString* path, FuriString* args); typedef struct { const char* command; @@ -631,7 +633,7 @@ static void storage_cli_print_usage(void) { } } -void storage_cli(Cli* cli, FuriString* args, void* context) { +void storage_cli(FuriPipeSide* pipe, FuriString* args, void* context) { UNUSED(context); FuriString* cmd; FuriString* path; @@ -653,7 +655,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 +669,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(FuriPipeSide* 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,9 +691,8 @@ 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( - cli, "factory_reset", CliCommandFlagParallelSafe, storage_cli_factory_reset, NULL); + cli_add_command(cli, RECORD_STORAGE, CliCommandFlagDefault, storage_cli, NULL); + cli_add_command(cli, "factory_reset", CliCommandFlagDefault, storage_cli_factory_reset, NULL); furi_record_close(RECORD_CLI); #else UNUSED(storage_cli_factory_reset); diff --git a/applications/system/js_app/js_app.c b/applications/system/js_app/js_app.c index c321150df7b..0d6c3231c98 100644 --- a/applications/system/js_app/js_app.c +++ b/applications/system/js_app/js_app.c @@ -131,12 +131,14 @@ int32_t js_app(void* arg) { } //-V773 typedef struct { - Cli* cli; + FuriPipeSide* 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); + furi_pipe_send(ctx->pipe, msg, strlen(msg), FuriWaitForever); } static void js_cli_exit(JsCliContext* ctx) { @@ -170,7 +172,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(FuriPipeSide* pipe, FuriString* args, void* context) { UNUSED(context); const char* path = furi_string_get_cstr(args); @@ -187,14 +189,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_app_should_stop(pipe)) break; } js_thread_stop(js_thread); @@ -207,7 +209,7 @@ void js_cli_execute(Cli* cli, FuriString* args, void* context) { void js_app_on_system_start(void) { #ifdef SRV_CLI Cli* cli = furi_record_open(RECORD_CLI); - cli_add_command(cli, "js", CliCommandFlagDefault, js_cli_execute, NULL); + cli_add_command(cli, "js", CliCommandFlagParallelUnsafe, js_cli_execute, NULL); furi_record_close(RECORD_CLI); #endif } diff --git a/applications/system/updater/cli/updater_cli.c b/applications/system/updater/cli/updater_cli.c index 56a16bd9d31..80314af4681 100644 --- a/applications/system/updater/cli/updater_cli.c +++ b/applications/system/updater/cli/updater_cli.c @@ -63,8 +63,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(FuriPipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); UNUSED(context); FuriString* subcommand; subcommand = furi_string_alloc(); diff --git a/furi/core/event_loop.c b/furi/core/event_loop.c index 23acd89a125..33d992946d4 100644 --- a/furi/core/event_loop.c +++ b/furi/core/event_loop.c @@ -40,6 +40,13 @@ static void furi_event_loop_process_pending_callbacks(FuriEventLoop* instance) { } } +static inline void furi_event_loop_process_custom_events(FuriEventLoop* instance) { + if(instance->custom.callback) { + uint32_t events = furi_thread_flags_wait(0x00FFFFFFUL, FuriFlagWaitAny, 0); + instance->custom.callback(events, instance->custom.context); + } +} + static bool furi_event_loop_signal_callback(uint32_t signal, void* arg, void* context) { furi_assert(context); FuriEventLoop* instance = context; @@ -242,10 +249,12 @@ void furi_event_loop_run(FuriEventLoop* instance) { } else if(flags & FuriEventLoopFlagPending) { furi_event_loop_process_pending_callbacks(instance); + } else if(flags & FuriEventLoopFlagCustom) { + furi_event_loop_process_custom_events(instance); + } else { furi_crash(); } - } else if(!furi_event_loop_process_expired_timers(instance)) { furi_event_loop_process_tick(instance); } @@ -281,6 +290,33 @@ void furi_event_loop_stop(FuriEventLoop* instance) { furi_event_loop_notify(instance, FuriEventLoopFlagStop); } +/* + * Public direct thread notification API + */ + +void furi_event_loop_set_custom_event_callback( + FuriEventLoop* instance, + FuriEventLoopCustomCallback callback, + void* context) { + furi_check(instance); + furi_check(instance->thread_id == furi_thread_get_current_id()); + furi_check(callback); + + instance->custom.callback = callback; + instance->custom.context = context; + + if(furi_thread_flags_get()) { + furi_event_loop_notify(instance, FuriEventLoopFlagCustom); + } +} + +void furi_event_loop_set_custom_event(FuriEventLoop* instance, uint32_t events) { + furi_check(instance); + + furi_thread_flags_set(instance->thread_id, events); + furi_event_loop_notify(instance, FuriEventLoopFlagCustom); +} + /* * Public deferred function call API */ diff --git a/furi/core/event_loop.h b/furi/core/event_loop.h index b00637b2328..3617c78c180 100644 --- a/furi/core/event_loop.h +++ b/furi/core/event_loop.h @@ -138,6 +138,28 @@ void furi_event_loop_run(FuriEventLoop* instance); */ void furi_event_loop_stop(FuriEventLoop* instance); +/* + * Direct notifications + */ + +/** + * @brief Custom event callback type + */ +typedef void (*FuriEventLoopCustomCallback)(uint32_t events, void* context); + +/** + * @brief Sets direct thread notification callback + */ +void furi_event_loop_set_custom_event_callback( + FuriEventLoop* instance, + FuriEventLoopCustomCallback callback, + void* context); + +/** + * @brief Sends a direct thread notification + */ +void furi_event_loop_set_custom_event(FuriEventLoop* instance, uint32_t events); + /* * Tick related API */ diff --git a/furi/core/event_loop_i.h b/furi/core/event_loop_i.h index 099bc42232b..9d6a257ff16 100644 --- a/furi/core/event_loop_i.h +++ b/furi/core/event_loop_i.h @@ -55,11 +55,12 @@ typedef enum { FuriEventLoopFlagStop = (1 << 1), FuriEventLoopFlagTimer = (1 << 2), FuriEventLoopFlagPending = (1 << 3), + FuriEventLoopFlagCustom = (1 << 4), } FuriEventLoopFlag; #define FuriEventLoopFlagAll \ (FuriEventLoopFlagEvent | FuriEventLoopFlagStop | FuriEventLoopFlagTimer | \ - FuriEventLoopFlagPending) + FuriEventLoopFlagPending | FuriEventLoopFlagCustom) typedef enum { FuriEventLoopProcessStatusComplete, @@ -80,6 +81,11 @@ typedef struct { LIST_DUAL_PUSH_DEF(PendingQueue, FuriEventLoopPendingQueueItem, M_POD_OPLIST) +typedef struct { + FuriEventLoopCustomCallback callback; + void* context; +} FuriEventLoopCustom; + struct FuriEventLoop { // Only works if all operations are done from the same thread FuriThreadId thread_id; @@ -99,4 +105,6 @@ struct FuriEventLoop { PendingQueue_t pending_queue; // Tick event FuriEventLoopTick tick; + // Custom event + FuriEventLoopCustom custom; }; diff --git a/furi/core/pipe.c b/furi/core/pipe.c index 6159e9a7dde..d62ad0e61b6 100644 --- a/furi/core/pipe.c +++ b/furi/core/pipe.c @@ -110,6 +110,7 @@ static void _furi_pipe_stdout_cb(const char* data, size_t size, void* context) { furi_assert(context); FuriPipeSide* pipe = context; while(size) { + if(furi_pipe_state(pipe) == FuriPipeStateBroken) return; size_t sent = furi_pipe_send(pipe, data, size, FuriWaitForever); data += sent; size -= sent; @@ -130,8 +131,9 @@ void furi_pipe_install_as_stdio(FuriPipeSide* pipe) { size_t furi_pipe_receive(FuriPipeSide* pipe, void* data, size_t length, FuriWait timeout) { furi_check(pipe); - furi_event_loop_link_notify(pipe->peer_event_loop_link, FuriEventLoopEventOut); - return furi_stream_buffer_receive(pipe->receiving, data, length, timeout); + size_t received = furi_stream_buffer_receive(pipe->receiving, data, length, timeout); + if(received) furi_event_loop_link_notify(pipe->peer_event_loop_link, FuriEventLoopEventOut); + return received; } size_t furi_pipe_send(FuriPipeSide* pipe, const void* data, size_t length, FuriWait timeout) { diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index da03440492e..0d3ccd09a6d 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -3,7 +3,6 @@ Version,+,80.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_vcp.h,, Header,+,applications/services/dialogs/dialogs.h,, Header,+,applications/services/dolphin/dolphin.h,, @@ -850,20 +849,8 @@ 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_delete_command,void,"Cli*, const char*" -Function,+,cli_getc,char,Cli* -Function,+,cli_is_connected,_Bool,Cli* -Function,+,cli_nl,void,Cli* -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*, void*" Function,+,cli_vcp_disable,void,CliVcp* Function,+,cli_vcp_enable,void,CliVcp* -Function,+,cli_write,void,"Cli*, const uint8_t*, size_t" Function,+,composite_api_resolver_add,void,"CompositeApiResolver*, const ElfApiInterface*" Function,+,composite_api_resolver_alloc,CompositeApiResolver*, Function,+,composite_api_resolver_free,void,CompositeApiResolver* @@ -1236,6 +1223,8 @@ Function,+,furi_event_loop_free,void,FuriEventLoop* Function,+,furi_event_loop_is_subscribed,_Bool,"FuriEventLoop*, FuriEventLoopObject*" Function,+,furi_event_loop_pend_callback,void,"FuriEventLoop*, FuriEventLoopPendingCallback, void*" Function,+,furi_event_loop_run,void,FuriEventLoop* +Function,+,furi_event_loop_set_custom_event,void,"FuriEventLoop*, uint32_t" +Function,+,furi_event_loop_set_custom_event_callback,void,"FuriEventLoop*, FuriEventLoopCustomCallback, void*" Function,+,furi_event_loop_stop,void,FuriEventLoop* Function,+,furi_event_loop_subscribe_event_flag,void,"FuriEventLoop*, FuriEventFlag*, FuriEventLoopEvent, FuriEventLoopEventCallback, void*" Function,+,furi_event_loop_subscribe_message_queue,void,"FuriEventLoop*, FuriMessageQueue*, FuriEventLoopEvent, FuriEventLoopEventCallback, void*" From fa1919c62ff9c70806545452a7f534ee70000050 Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Thu, 28 Nov 2024 21:44:12 +0400 Subject: [PATCH 20/47] =?UTF-8?q?restore=20previous=20speed=20after=204=20?= =?UTF-8?q?days=20of=20debugging=20=F0=9F=A5=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- applications/services/storage/storage_cli.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/services/storage/storage_cli.c b/applications/services/storage/storage_cli.c index f78580d75d8..9458420b05a 100644 --- a/applications/services/storage/storage_cli.c +++ b/applications/services/storage/storage_cli.c @@ -316,7 +316,7 @@ static void storage_cli_write_chunk(FuriPipeSide* pipe, FuriString* path, FuriSt } else { if(storage_file_open(file, furi_string_get_cstr(path), FSAM_WRITE, FSOM_OPEN_APPEND)) { printf("Ready\r\n"); - char buffer[32]; + char buffer[256]; while(need_to_read) { size_t read_this_time = furi_pipe_receive( From bd826711c453d2faf505dc3e5dbe1a08c4a2cef3 Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Thu, 28 Nov 2024 22:14:04 +0400 Subject: [PATCH 21/47] fix: cli_app_should_stop --- applications/services/cli/cli.c | 7 +------ applications/services/cli/cli.h | 1 + 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/applications/services/cli/cli.c b/applications/services/cli/cli.c index 1391d8a716a..a98acff995f 100644 --- a/applications/services/cli/cli.c +++ b/applications/services/cli/cli.c @@ -145,12 +145,7 @@ bool cli_app_should_stop(FuriPipeSide* side) { if(furi_pipe_state(side) == FuriPipeStateBroken) return true; if(!furi_pipe_bytes_available(side)) return false; char c = getchar(); - if(c == CliKeyETX) { - return true; - } else { - ungetc(c, stdin); - return false; - } + return c == CliKeyETX; } void cli_print_usage(const char* cmd, const char* usage, const char* arg) { diff --git a/applications/services/cli/cli.h b/applications/services/cli/cli.h index 3d8bd3f7c92..6b7f99754b8 100644 --- a/applications/services/cli/cli.h +++ b/applications/services/cli/cli.h @@ -67,6 +67,7 @@ void cli_delete_command(Cli* cli, const char* name); * @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 0 or 1 bytes from the pipe */ bool cli_app_should_stop(FuriPipeSide* side); From 34c65660b6cb3d5406d8f8b5033dc02ede9f5849 Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Fri, 29 Nov 2024 00:11:21 +0400 Subject: [PATCH 22/47] fix: cli and event_loop memory leaks --- applications/services/cli/cli_shell.c | 8 +++++--- applications/services/cli/cli_vcp.c | 8 ++++---- furi/core/event_loop.c | 6 ++++++ 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/applications/services/cli/cli_shell.c b/applications/services/cli/cli_shell.c index 535b89fa4cb..200d11d23d3 100644 --- a/applications/services/cli/cli_shell.c +++ b/applications/services/cli/cli_shell.c @@ -314,7 +314,7 @@ static void cli_shell_data_available(FuriEventLoopObject* object, void* context) ANSI_CURSOR_LEFT_BY("1") "%s" ANSI_ERASE_LINE(ANSI_ERASE_FROM_CURSOR_TO_END), furi_string_get_cstr(line) + cli_shell->line_position); size_t left_by = furi_string_size(line) - cli_shell->line_position; - if(left_by) // apparently LEFT_BY("0") shift left by one ._ . + if(left_by) // apparently LEFT_BY("0") still shifts left by one ._ . printf(ANSI_CURSOR_LEFT_BY("%d"), left_by); fflush(stdout); @@ -369,7 +369,9 @@ static CliShell* cli_shell_alloc(FuriPipeSide* pipe) { cli_shell->ansi_parser = cli_ansi_parser_alloc(); cli_shell->event_loop = furi_event_loop_alloc(); ShellHistory_init(cli_shell->history); - ShellHistory_push_at(cli_shell->history, 0, furi_string_alloc()); + FuriString* new_command = furi_string_alloc(); + ShellHistory_push_at(cli_shell->history, 0, new_command); + furi_string_free(new_command); cli_shell->pipe = pipe; furi_pipe_install_as_stdio(cli_shell->pipe); @@ -387,9 +389,9 @@ static CliShell* cli_shell_alloc(FuriPipeSide* pipe) { static void cli_shell_free(CliShell* cli_shell) { furi_event_loop_unsubscribe(cli_shell->event_loop, cli_shell->pipe); - furi_event_loop_free(cli_shell->event_loop); furi_pipe_free(cli_shell->pipe); ShellHistory_clear(cli_shell->history); + furi_event_loop_free(cli_shell->event_loop); cli_ansi_parser_free(cli_shell->ansi_parser); furi_record_close(RECORD_CLI); free(cli_shell); diff --git a/applications/services/cli/cli_vcp.c b/applications/services/cli/cli_vcp.c index 7f671a94337..ed628596fb0 100644 --- a/applications/services/cli/cli_vcp.c +++ b/applications/services/cli/cli_vcp.c @@ -29,10 +29,10 @@ typedef struct { } CliVcpMessage; typedef enum { - CliVcpInternalMessageConnected = 1U << 0, - CliVcpInternalMessageDisconnected = 1U << 1, - CliVcpInternalMessageTxDone = 1U << 2, - CliVcpInternalMessageRx = 1U << 3, + CliVcpInternalMessageConnected, + CliVcpInternalMessageDisconnected, + CliVcpInternalMessageTxDone, + CliVcpInternalMessageRx, } CliVcpInternalMessage; struct CliVcp { diff --git a/furi/core/event_loop.c b/furi/core/event_loop.c index 33d992946d4..1825fb70dff 100644 --- a/furi/core/event_loop.c +++ b/furi/core/event_loop.c @@ -566,7 +566,13 @@ static void furi_event_loop_item_free(FuriEventLoopItem* instance) { static void furi_event_loop_item_free_later(FuriEventLoopItem* instance) { furi_assert(instance); furi_assert(!furi_event_loop_item_is_waiting(instance)); + FuriEventLoop* owner = instance->owner; + + // by erasing the owner and forcefully adding the item into the waiting list, + // we're asking the event loop to free us instance->owner = NULL; + WaitingList_push_back(owner->waiting_list, instance); + furi_event_loop_notify(owner, FuriEventLoopFlagEvent); } static void furi_event_loop_item_set_callback( From a346d8c8d97705509c1a38b05a4fd6b15a75500b Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Fri, 29 Nov 2024 22:31:34 +0400 Subject: [PATCH 23/47] style: remove commented out code --- applications/services/cli/cli.c | 53 --------------------------------- 1 file changed, 53 deletions(-) diff --git a/applications/services/cli/cli.c b/applications/services/cli/cli.c index a98acff995f..63fd11a9912 100644 --- a/applications/services/cli/cli.c +++ b/applications/services/cli/cli.c @@ -15,59 +15,6 @@ Cli* cli_alloc(void) { return cli; } -// 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); -// } - void cli_add_command( Cli* cli, const char* name, From c3f7825e398b035f61e5021924d8a976f5b1f86a Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Fri, 29 Nov 2024 23:13:29 +0400 Subject: [PATCH 24/47] ci: fix pvs warnings --- applications/services/cli/cli_ansi.c | 1 - applications/services/cli/cli_i.h | 2 +- applications/services/cli/cli_shell.c | 13 +++++++------ applications/services/cli/cli_vcp.c | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/applications/services/cli/cli_ansi.c b/applications/services/cli/cli_ansi.c index 35448c1d3aa..b8551e3967b 100644 --- a/applications/services/cli/cli_ansi.c +++ b/applications/services/cli/cli_ansi.c @@ -51,7 +51,6 @@ CliAnsiParserResult cli_ansi_parser_feed(CliAnsiParser* parser, char c) { case CliAnsiParserStateInitial: // -> if(c != CliKeyEsc) { - parser->state = CliAnsiParserStateInitial; return (CliAnsiParserResult){ .is_done = true, .result = (CliKeyCombo){ diff --git a/applications/services/cli/cli_i.h b/applications/services/cli/cli_i.h index 28c3d60d4e2..f0413dd4d10 100644 --- a/applications/services/cli/cli_i.h +++ b/applications/services/cli/cli_i.h @@ -27,7 +27,7 @@ BPTREE_DEF2( FuriString*, FURI_STRING_OPLIST, CliCommand, - M_POD_OPLIST); + M_POD_OPLIST); // -V1103 #define M_OPL_CliCommandTree_t() BPTREE_OPLIST(CliCommandTree, M_POD_OPLIST) diff --git a/applications/services/cli/cli_shell.c b/applications/services/cli/cli_shell.c index 200d11d23d3..d02532712ea 100644 --- a/applications/services/cli/cli_shell.c +++ b/applications/services/cli/cli_shell.c @@ -10,7 +10,7 @@ #define HISTORY_DEPTH 10 -ARRAY_DEF(ShellHistory, FuriString*, FURI_STRING_OPLIST); +ARRAY_DEF(ShellHistory, FuriString*, FURI_STRING_OPLIST); // -V524 #define M_OPL_ShellHistory_t() ARRAY_OPLIST(ShellHistory) typedef struct { @@ -198,7 +198,8 @@ static void cli_shell_data_available(FuriEventLoopObject* object, void* context) if(!parse_result.is_done) return; CliKeyCombo key_combo = parse_result.result; if(key_combo.key == CliKeyUnrecognized) return; - FURI_LOG_T(TAG, "mod=%d, key=%d='%c'", key_combo.modifiers, key_combo.key, key_combo.key); + FURI_LOG_T( + TAG, "mod=%d, key=%d='%c'", key_combo.modifiers, key_combo.key, (char)key_combo.key); // do things the user requests if(key_combo.modifiers == 0 && key_combo.key == CliKeyETX) { // usually Ctrl+C @@ -216,7 +217,7 @@ static void cli_shell_data_available(FuriEventLoopObject* object, void* context) cli_shell_format_prompt(cli_shell, prompt, sizeof(prompt)); printf( ANSI_ERASE_DISPLAY(ANSI_ERASE_ENTIRE) ANSI_ERASE_SCROLLBACK_BUFFER ANSI_CURSOR_POS( - "1", "1") "%s%s" ANSI_CURSOR_HOR_POS("%d"), + "1", "1") "%s%s" ANSI_CURSOR_HOR_POS("%zu"), prompt, furi_string_get_cstr(command), strlen(prompt) + cli_shell->line_position + 1 /* 1-based column indexing */); @@ -291,7 +292,7 @@ static void cli_shell_data_available(FuriEventLoopObject* object, void* context) FuriString* line = *ShellHistory_cget(cli_shell->history, cli_shell->history_position); cli_shell->line_position = furi_string_size(line); printf( - ANSI_CURSOR_HOR_POS("%d"), + ANSI_CURSOR_HOR_POS("%zu"), cli_shell_prompt_length(cli_shell) + cli_shell->line_position + 1); fflush(stdout); @@ -315,7 +316,7 @@ static void cli_shell_data_available(FuriEventLoopObject* object, void* context) furi_string_get_cstr(line) + cli_shell->line_position); size_t left_by = furi_string_size(line) - cli_shell->line_position; if(left_by) // apparently LEFT_BY("0") still shifts left by one ._ . - printf(ANSI_CURSOR_LEFT_BY("%d"), left_by); + printf(ANSI_CURSOR_LEFT_BY("%zu"), left_by); fflush(stdout); } else if( @@ -327,7 +328,7 @@ static void cli_shell_data_available(FuriEventLoopObject* object, void* context) CliSkipDirectionRight; cli_shell->line_position = cli_skip_run(line, cli_shell->line_position, direction); printf( - ANSI_CURSOR_HOR_POS("%d"), + ANSI_CURSOR_HOR_POS("%zu"), cli_shell_prompt_length(cli_shell) + cli_shell->line_position + 1); fflush(stdout); diff --git a/applications/services/cli/cli_vcp.c b/applications/services/cli/cli_vcp.c index ed628596fb0..5181b3a47e1 100644 --- a/applications/services/cli/cli_vcp.c +++ b/applications/services/cli/cli_vcp.c @@ -66,7 +66,7 @@ static void cli_vcp_maybe_send_data(CliVcp* cli_vcp) { uint8_t buf[USB_CDC_PKT_LEN]; size_t length = furi_pipe_receive(cli_vcp->own_pipe, buf, sizeof(buf), 0); if(length > 0 || cli_vcp->previous_tx_length == USB_CDC_PKT_LEN) { - VCP_LOG_T(TAG, "cdc_send length=%u", length); + VCP_LOG_T(TAG, "cdc_send length=%zu", length); cli_vcp->is_currently_transmitting = true; furi_hal_cdc_send(VCP_IF_NUM, buf, length); } @@ -84,7 +84,7 @@ static void cli_vcp_maybe_receive_data(CliVcp* cli_vcp) { uint8_t buf[USB_CDC_PKT_LEN]; size_t length = furi_hal_cdc_receive(VCP_IF_NUM, buf, sizeof(buf)); - VCP_LOG_T(TAG, "cdc_receive length=%u", length); + VCP_LOG_T(TAG, "cdc_receive length=%zu", length); furi_check(furi_pipe_send(cli_vcp->own_pipe, buf, length, 0) == length); } From b0b73c0ec9c2570d6cb1bc8649457c5d96b37be7 Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Mon, 2 Dec 2024 20:12:12 +0400 Subject: [PATCH 25/47] fix: unit tests, event_loop crash --- applications/debug/unit_tests/test_runner.c | 8 ++-- applications/debug/unit_tests/test_runner.h | 7 ++- .../unit_tests/tests/furi/furi_stdio_test.c | 15 ++++--- .../unit_tests/tests/storage/storage_test.c | 5 --- applications/debug/unit_tests/unit_tests.c | 7 ++- applications/services/cli/cli_shell.c | 5 ++- applications/services/cli/cli_shell.h | 3 +- furi/core/event_loop.c | 44 +------------------ furi/core/event_loop.h | 22 ---------- furi/core/event_loop_i.h | 10 +---- furi/core/thread.c | 14 ++++-- furi/core/thread.h | 10 +++-- targets/f7/api_symbols.csv | 6 +-- 13 files changed, 45 insertions(+), 111 deletions(-) diff --git a/applications/debug/unit_tests/test_runner.c b/applications/debug/unit_tests/test_runner.c index de29e91b39a..0fddee6cb0b 100644 --- a/applications/debug/unit_tests/test_runner.c +++ b/applications/debug/unit_tests/test_runner.c @@ -25,7 +25,7 @@ struct TestRunner { NotificationApp* notification; // Temporary used things - Cli* cli; + FuriPipeSide* pipe; FuriString* args; // ELF related stuff @@ -38,14 +38,14 @@ struct TestRunner { int minunit_status; }; -TestRunner* test_runner_alloc(Cli* cli, FuriString* args) { +TestRunner* test_runner_alloc(FuriPipeSide* 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 +147,7 @@ static void test_runner_run_internal(TestRunner* instance) { } while(true) { - if(cli_cmd_interrupt_received(instance->cli)) { + if(cli_app_should_stop(instance->pipe)) { break; } diff --git a/applications/debug/unit_tests/test_runner.h b/applications/debug/unit_tests/test_runner.h index 43aba8bb15b..d91dec83d4c 100644 --- a/applications/debug/unit_tests/test_runner.h +++ b/applications/debug/unit_tests/test_runner.h @@ -3,10 +3,9 @@ #include typedef struct TestRunner TestRunner; -typedef struct Cli Cli; -TestRunner* test_runner_alloc(Cli* cli, FuriString* args); +TestRunner* test_runner_alloc(FuriPipeSide* 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/furi/furi_stdio_test.c b/applications/debug/unit_tests/tests/furi/furi_stdio_test.c index 94e2f613b6a..d80bd7bd576 100644 --- a/applications/debug/unit_tests/tests/furi/furi_stdio_test.c +++ b/applications/debug/unit_tests/tests/furi/furi_stdio_test.c @@ -30,7 +30,9 @@ static size_t mock_in_cb(char* buffer, size_t size, FuriWait wait, void* context } void test_stdin(void) { - FuriThreadStdinReadCallback in_cb = furi_thread_get_stdin_callback(); + FuriThreadStdinReadCallback in_cb; + void* in_ctx; + furi_thread_get_stdin_callback(&in_cb, &in_ctx); furi_thread_set_stdin_callback(mock_in_cb, CONTEXT_MAGIC); char buf[256]; @@ -63,13 +65,14 @@ void test_stdin(void) { fgets(buf, sizeof(buf), stdin); mu_assert_string_eq(" World!\n", buf); - furi_thread_set_stdin_callback(in_cb, CONTEXT_MAGIC); + furi_thread_set_stdin_callback(in_cb, in_ctx); } // stdout static FuriString* mock_out; -FuriThreadStdoutWriteCallback original_out_cb; +static FuriThreadStdoutWriteCallback original_out_cb; +static void* original_out_ctx; static void mock_out_cb(const char* data, size_t size, void* context) { furi_check(context == CONTEXT_MAGIC); @@ -83,7 +86,7 @@ static void assert_and_clear_mock_out(const char* expected) { // return the original stdout callback for the duration of the check // if the check fails, we don't want the error to end up in our buffer, // we want to be able to see it! - furi_thread_set_stdout_callback(original_out_cb, CONTEXT_MAGIC); + furi_thread_set_stdout_callback(original_out_cb, original_out_ctx); mu_assert_string_eq(expected, furi_string_get_cstr(mock_out)); furi_thread_set_stdout_callback(mock_out_cb, CONTEXT_MAGIC); @@ -91,7 +94,7 @@ static void assert_and_clear_mock_out(const char* expected) { } void test_stdout(void) { - original_out_cb = furi_thread_get_stdout_callback(); + furi_thread_get_stdout_callback(&original_out_cb, &original_out_ctx); furi_thread_set_stdout_callback(mock_out_cb, CONTEXT_MAGIC); mock_out = furi_string_alloc(); @@ -104,5 +107,5 @@ void test_stdout(void) { assert_and_clear_mock_out("Hello!"); furi_string_free(mock_out); - furi_thread_set_stdout_callback(original_out_cb, CONTEXT_MAGIC); + furi_thread_set_stdout_callback(original_out_cb, original_out_ctx); } diff --git a/applications/debug/unit_tests/tests/storage/storage_test.c b/applications/debug/unit_tests/tests/storage/storage_test.c index 75c52ef9a7a..9d5c68a448a 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 237cb9080e0..457530054a7 100644 --- a/applications/debug/unit_tests/unit_tests.c +++ b/applications/debug/unit_tests/unit_tests.c @@ -3,11 +3,10 @@ #include "test_runner.h" -void unit_tests_cli(Cli* cli, FuriString* args, void* context) { - UNUSED(cli); +void unit_tests_cli(FuriPipeSide* 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); } @@ -15,7 +14,7 @@ void unit_tests_cli(Cli* cli, FuriString* args, void* context) { void unit_tests_on_system_start(void) { #ifdef SRV_CLI Cli* cli = furi_record_open(RECORD_CLI); - cli_add_command(cli, "unit_tests", CliCommandFlagParallelSafe, unit_tests_cli, NULL); + cli_add_command(cli, "unit_tests", CliCommandFlagDefault, unit_tests_cli, NULL); furi_record_close(RECORD_CLI); #endif } diff --git a/applications/services/cli/cli_shell.c b/applications/services/cli/cli_shell.c index d02532712ea..ea9c6c9e531 100644 --- a/applications/services/cli/cli_shell.c +++ b/applications/services/cli/cli_shell.c @@ -79,7 +79,10 @@ static void cli_shell_execute_command(CliShell* cli_shell, FuriString* command) .args = args, }; FuriThread* thread = furi_thread_alloc_ex( - furi_string_get_cstr(command_name), CLI_SHELL_STACK_SIZE, cli_command_thread, &thread_data); + furi_string_get_cstr(command_name), + CLI_COMMAND_STACK_SIZE, + cli_command_thread, + &thread_data); furi_thread_start(thread); furi_thread_join(thread); furi_thread_free(thread); diff --git a/applications/services/cli/cli_shell.h b/applications/services/cli/cli_shell.h index 3594b6029ca..a50f6a01ee2 100644 --- a/applications/services/cli/cli_shell.h +++ b/applications/services/cli/cli_shell.h @@ -6,7 +6,8 @@ extern "C" { #endif -#define CLI_SHELL_STACK_SIZE (1 * 1024U) +#define CLI_SHELL_STACK_SIZE (1 * 1024U) +#define CLI_COMMAND_STACK_SIZE (3 * 1024U) FuriThread* cli_shell_start(FuriPipeSide* pipe); diff --git a/furi/core/event_loop.c b/furi/core/event_loop.c index 1825fb70dff..23acd89a125 100644 --- a/furi/core/event_loop.c +++ b/furi/core/event_loop.c @@ -40,13 +40,6 @@ static void furi_event_loop_process_pending_callbacks(FuriEventLoop* instance) { } } -static inline void furi_event_loop_process_custom_events(FuriEventLoop* instance) { - if(instance->custom.callback) { - uint32_t events = furi_thread_flags_wait(0x00FFFFFFUL, FuriFlagWaitAny, 0); - instance->custom.callback(events, instance->custom.context); - } -} - static bool furi_event_loop_signal_callback(uint32_t signal, void* arg, void* context) { furi_assert(context); FuriEventLoop* instance = context; @@ -249,12 +242,10 @@ void furi_event_loop_run(FuriEventLoop* instance) { } else if(flags & FuriEventLoopFlagPending) { furi_event_loop_process_pending_callbacks(instance); - } else if(flags & FuriEventLoopFlagCustom) { - furi_event_loop_process_custom_events(instance); - } else { furi_crash(); } + } else if(!furi_event_loop_process_expired_timers(instance)) { furi_event_loop_process_tick(instance); } @@ -290,33 +281,6 @@ void furi_event_loop_stop(FuriEventLoop* instance) { furi_event_loop_notify(instance, FuriEventLoopFlagStop); } -/* - * Public direct thread notification API - */ - -void furi_event_loop_set_custom_event_callback( - FuriEventLoop* instance, - FuriEventLoopCustomCallback callback, - void* context) { - furi_check(instance); - furi_check(instance->thread_id == furi_thread_get_current_id()); - furi_check(callback); - - instance->custom.callback = callback; - instance->custom.context = context; - - if(furi_thread_flags_get()) { - furi_event_loop_notify(instance, FuriEventLoopFlagCustom); - } -} - -void furi_event_loop_set_custom_event(FuriEventLoop* instance, uint32_t events) { - furi_check(instance); - - furi_thread_flags_set(instance->thread_id, events); - furi_event_loop_notify(instance, FuriEventLoopFlagCustom); -} - /* * Public deferred function call API */ @@ -566,13 +530,7 @@ static void furi_event_loop_item_free(FuriEventLoopItem* instance) { static void furi_event_loop_item_free_later(FuriEventLoopItem* instance) { furi_assert(instance); furi_assert(!furi_event_loop_item_is_waiting(instance)); - FuriEventLoop* owner = instance->owner; - - // by erasing the owner and forcefully adding the item into the waiting list, - // we're asking the event loop to free us instance->owner = NULL; - WaitingList_push_back(owner->waiting_list, instance); - furi_event_loop_notify(owner, FuriEventLoopFlagEvent); } static void furi_event_loop_item_set_callback( diff --git a/furi/core/event_loop.h b/furi/core/event_loop.h index 3617c78c180..b00637b2328 100644 --- a/furi/core/event_loop.h +++ b/furi/core/event_loop.h @@ -138,28 +138,6 @@ void furi_event_loop_run(FuriEventLoop* instance); */ void furi_event_loop_stop(FuriEventLoop* instance); -/* - * Direct notifications - */ - -/** - * @brief Custom event callback type - */ -typedef void (*FuriEventLoopCustomCallback)(uint32_t events, void* context); - -/** - * @brief Sets direct thread notification callback - */ -void furi_event_loop_set_custom_event_callback( - FuriEventLoop* instance, - FuriEventLoopCustomCallback callback, - void* context); - -/** - * @brief Sends a direct thread notification - */ -void furi_event_loop_set_custom_event(FuriEventLoop* instance, uint32_t events); - /* * Tick related API */ diff --git a/furi/core/event_loop_i.h b/furi/core/event_loop_i.h index 9d6a257ff16..099bc42232b 100644 --- a/furi/core/event_loop_i.h +++ b/furi/core/event_loop_i.h @@ -55,12 +55,11 @@ typedef enum { FuriEventLoopFlagStop = (1 << 1), FuriEventLoopFlagTimer = (1 << 2), FuriEventLoopFlagPending = (1 << 3), - FuriEventLoopFlagCustom = (1 << 4), } FuriEventLoopFlag; #define FuriEventLoopFlagAll \ (FuriEventLoopFlagEvent | FuriEventLoopFlagStop | FuriEventLoopFlagTimer | \ - FuriEventLoopFlagPending | FuriEventLoopFlagCustom) + FuriEventLoopFlagPending) typedef enum { FuriEventLoopProcessStatusComplete, @@ -81,11 +80,6 @@ typedef struct { LIST_DUAL_PUSH_DEF(PendingQueue, FuriEventLoopPendingQueueItem, M_POD_OPLIST) -typedef struct { - FuriEventLoopCustomCallback callback; - void* context; -} FuriEventLoopCustom; - struct FuriEventLoop { // Only works if all operations are done from the same thread FuriThreadId thread_id; @@ -105,6 +99,4 @@ struct FuriEventLoop { PendingQueue_t pending_queue; // Tick event FuriEventLoopTick tick; - // Custom event - FuriEventLoopCustom custom; }; diff --git a/furi/core/thread.c b/furi/core/thread.c index 6e515795775..f6f1427f022 100644 --- a/furi/core/thread.c +++ b/furi/core/thread.c @@ -744,16 +744,22 @@ static int32_t __furi_thread_stdout_flush(FuriThread* thread) { return 0; } -FuriThreadStdoutWriteCallback furi_thread_get_stdout_callback(void) { +void furi_thread_get_stdout_callback(FuriThreadStdoutWriteCallback* callback, void** context) { FuriThread* thread = furi_thread_get_current(); furi_check(thread); - return thread->output.write_callback; + furi_check(callback); + furi_check(context); + *callback = thread->output.write_callback; + *context = thread->output.context; } -FuriThreadStdinReadCallback furi_thread_get_stdin_callback(void) { +void furi_thread_get_stdin_callback(FuriThreadStdinReadCallback* callback, void** context) { FuriThread* thread = furi_thread_get_current(); furi_check(thread); - return thread->input.read_callback; + furi_check(callback); + furi_check(context); + *callback = thread->input.read_callback; + *context = thread->input.context; } void furi_thread_set_stdout_callback(FuriThreadStdoutWriteCallback callback, void* context) { diff --git a/furi/core/thread.h b/furi/core/thread.h index 9abfde5cd85..c1f615d1648 100644 --- a/furi/core/thread.h +++ b/furi/core/thread.h @@ -479,16 +479,18 @@ uint32_t furi_thread_get_stack_space(FuriThreadId thread_id); /** * @brief Get the standard output callback for the current thead. * - * @return pointer to the standard out callback function + * @param[out] callback where to store the stdout callback + * @param[out] context where to store the context */ -FuriThreadStdoutWriteCallback furi_thread_get_stdout_callback(void); +void furi_thread_get_stdout_callback(FuriThreadStdoutWriteCallback* callback, void** context); /** * @brief Get the standard input callback for the current thead. * - * @return pointer to the standard in callback function + * @param[out] callback where to store the stdin callback + * @param[out] context where to store the context */ -FuriThreadStdinReadCallback furi_thread_get_stdin_callback(void); +void furi_thread_get_stdin_callback(FuriThreadStdinReadCallback* callback, void** context); /** Set standard output callback for the current thread. * diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 0d3ccd09a6d..5b1bc02d93f 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1223,8 +1223,6 @@ Function,+,furi_event_loop_free,void,FuriEventLoop* Function,+,furi_event_loop_is_subscribed,_Bool,"FuriEventLoop*, FuriEventLoopObject*" Function,+,furi_event_loop_pend_callback,void,"FuriEventLoop*, FuriEventLoopPendingCallback, void*" Function,+,furi_event_loop_run,void,FuriEventLoop* -Function,+,furi_event_loop_set_custom_event,void,"FuriEventLoop*, uint32_t" -Function,+,furi_event_loop_set_custom_event_callback,void,"FuriEventLoop*, FuriEventLoopCustomCallback, void*" Function,+,furi_event_loop_stop,void,FuriEventLoop* Function,+,furi_event_loop_subscribe_event_flag,void,"FuriEventLoop*, FuriEventFlag*, FuriEventLoopEvent, FuriEventLoopEventCallback, void*" Function,+,furi_event_loop_subscribe_message_queue,void,"FuriEventLoop*, FuriMessageQueue*, FuriEventLoopEvent, FuriEventLoopEventCallback, void*" @@ -1881,8 +1879,8 @@ Function,+,furi_thread_get_return_code,int32_t,FuriThread* Function,+,furi_thread_get_signal_callback,FuriThreadSignalCallback,const FuriThread* Function,+,furi_thread_get_stack_space,uint32_t,FuriThreadId Function,+,furi_thread_get_state,FuriThreadState,FuriThread* -Function,+,furi_thread_get_stdin_callback,FuriThreadStdinReadCallback, -Function,+,furi_thread_get_stdout_callback,FuriThreadStdoutWriteCallback, +Function,+,furi_thread_get_stdin_callback,void,"FuriThreadStdinReadCallback*, void**" +Function,+,furi_thread_get_stdout_callback,void,"FuriThreadStdoutWriteCallback*, void**" Function,+,furi_thread_is_suspended,_Bool,FuriThreadId Function,+,furi_thread_join,_Bool,FuriThread* Function,+,furi_thread_list_alloc,FuriThreadList*, From 8e05df63ad8390641bf7af30087312204b3fbecf Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Tue, 3 Dec 2024 12:09:53 +0400 Subject: [PATCH 26/47] ci: fix build --- .../debug/speaker_debug/speaker_debug.c | 6 +++--- applications/services/cli/application.fam | 1 + targets/f18/api_symbols.csv | 17 ++++++----------- targets/f7/api_symbols.csv | 8 ++++++++ 4 files changed, 18 insertions(+), 14 deletions(-) diff --git a/applications/debug/speaker_debug/speaker_debug.c b/applications/debug/speaker_debug/speaker_debug.c index 3f685ab30d4..81f91c93d73 100644 --- a/applications/debug/speaker_debug/speaker_debug.c +++ b/applications/debug/speaker_debug/speaker_debug.c @@ -37,8 +37,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(FuriPipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); SpeakerDebugApp* app = (SpeakerDebugApp*)context; SpeakerDebugAppMessage message; @@ -95,7 +95,7 @@ static void speaker_app_run(SpeakerDebugApp* app, const char* arg) { return; } - cli_add_command(app->cli, CLI_COMMAND, CliCommandFlagParallelSafe, speaker_app_cli, app); + cli_add_command(app->cli, CLI_COMMAND, CliCommandFlagDefault, speaker_app_cli, app); SpeakerDebugAppMessage message; FuriStatus status; diff --git a/applications/services/cli/application.fam b/applications/services/cli/application.fam index 99e90980994..dc53e4bed96 100644 --- a/applications/services/cli/application.fam +++ b/applications/services/cli/application.fam @@ -4,6 +4,7 @@ App( entry_point="cli_on_system_start", cdefines=["SRV_CLI"], sources=["cli.c"], + sdk_headers=["cli.h"], order=0, ) diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index c020c1d09ed..5c89a28aa58 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -774,19 +774,14 @@ 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_ansi_parser_alloc,CliAnsiParser*, +Function,-,cli_ansi_parser_feed,CliAnsiParserResult,"CliAnsiParser*, char" +Function,-,cli_ansi_parser_free,void,CliAnsiParser* +Function,+,cli_app_should_stop,_Bool,FuriPipeSide* 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_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*, void*" Function,+,cli_vcp_disable,void,CliVcp* Function,+,cli_vcp_enable,void,CliVcp* -Function,+,cli_write,void,"Cli*, const uint8_t*, size_t" Function,+,composite_api_resolver_add,void,"CompositeApiResolver*, const ElfApiInterface*" Function,+,composite_api_resolver_alloc,CompositeApiResolver*, Function,+,composite_api_resolver_free,void,CompositeApiResolver* @@ -1673,8 +1668,8 @@ Function,+,furi_thread_get_return_code,int32_t,FuriThread* Function,+,furi_thread_get_signal_callback,FuriThreadSignalCallback,const FuriThread* Function,+,furi_thread_get_stack_space,uint32_t,FuriThreadId Function,+,furi_thread_get_state,FuriThreadState,FuriThread* -Function,+,furi_thread_get_stdin_callback,FuriThreadStdinReadCallback, -Function,+,furi_thread_get_stdout_callback,FuriThreadStdoutWriteCallback, +Function,+,furi_thread_get_stdin_callback,void,"FuriThreadStdinReadCallback*, void**" +Function,+,furi_thread_get_stdout_callback,void,"FuriThreadStdoutWriteCallback*, void**" Function,+,furi_thread_is_suspended,_Bool,FuriThreadId Function,+,furi_thread_join,_Bool,FuriThread* Function,+,furi_thread_list_alloc,FuriThreadList*, diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 5b1bc02d93f..61e182e7c6b 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -3,6 +3,7 @@ Version,+,80.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_vcp.h,, Header,+,applications/services/dialogs/dialogs.h,, Header,+,applications/services/dolphin/dolphin.h,, @@ -849,6 +850,13 @@ 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_ansi_parser_alloc,CliAnsiParser*, +Function,-,cli_ansi_parser_feed,CliAnsiParserResult,"CliAnsiParser*, char" +Function,-,cli_ansi_parser_free,void,CliAnsiParser* +Function,+,cli_app_should_stop,_Bool,FuriPipeSide* +Function,+,cli_delete_command,void,"Cli*, const char*" +Function,+,cli_print_usage,void,"const char*, const char*, const char*" Function,+,cli_vcp_disable,void,CliVcp* Function,+,cli_vcp_enable,void,CliVcp* Function,+,composite_api_resolver_add,void,"CompositeApiResolver*, const ElfApiInterface*" From 789e3373e199ff659bc3586f2a43dfd6604a4b33 Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Tue, 3 Dec 2024 12:11:14 +0400 Subject: [PATCH 27/47] ci: silence pvs warning --- applications/services/cli/cli_i.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/applications/services/cli/cli_i.h b/applications/services/cli/cli_i.h index f0413dd4d10..eb7ed4cba4e 100644 --- a/applications/services/cli/cli_i.h +++ b/applications/services/cli/cli_i.h @@ -21,13 +21,14 @@ typedef struct { #define CLI_COMMANDS_TREE_RANK 4 +// -V:BPTREE_DEF2:1103 BPTREE_DEF2( CliCommandTree, CLI_COMMANDS_TREE_RANK, FuriString*, FURI_STRING_OPLIST, CliCommand, - M_POD_OPLIST); // -V1103 + M_POD_OPLIST); #define M_OPL_CliCommandTree_t() BPTREE_OPLIST(CliCommandTree, M_POD_OPLIST) From 2e1d236da9f2a57f4be96304a9f30a0185486cda Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Tue, 3 Dec 2024 12:23:14 +0400 Subject: [PATCH 28/47] feat: cli gpio --- applications/services/cli/application.fam | 4 ++-- applications/services/cli/cli_command_gpio.c | 23 ++++++++++---------- applications/services/cli/cli_command_gpio.h | 2 +- applications/services/cli/cli_commands.c | 2 +- 4 files changed, 16 insertions(+), 15 deletions(-) diff --git a/applications/services/cli/application.fam b/applications/services/cli/application.fam index dc53e4bed96..832ffc2e746 100644 --- a/applications/services/cli/application.fam +++ b/applications/services/cli/application.fam @@ -3,7 +3,7 @@ App( apptype=FlipperAppType.STARTUP, entry_point="cli_on_system_start", cdefines=["SRV_CLI"], - sources=["cli.c"], + sources=["cli.c", "cli_shell.c", "cli_commands.c", "cli_command_gpio.c", "cli_ansi.c"], sdk_headers=["cli.h"], order=0, ) @@ -16,5 +16,5 @@ App( stack_size=768, order=40, sdk_headers=["cli_vcp.h"], - sources=["cli.c", "cli_vcp.c", "cli_shell.c", "cli_commands.c", "cli_ansi.c"], + sources=["cli_vcp.c"], ) diff --git a/applications/services/cli/cli_command_gpio.c b/applications/services/cli/cli_command_gpio.c index 010a7dfbe59..0c375e7db94 100644 --- a/applications/services/cli/cli_command_gpio.c +++ b/applications/services/cli/cli_command_gpio.c @@ -70,8 +70,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(FuriPipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); UNUSED(context); size_t num = 0; @@ -93,7 +93,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 +110,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(FuriPipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); UNUSED(context); size_t num = 0; @@ -131,7 +131,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(FuriPipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); UNUSED(context); size_t num = 0; @@ -159,7 +160,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 +171,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(FuriPipeSide* pipe, FuriString* args, void* context) { FuriString* cmd; cmd = furi_string_alloc(); @@ -181,17 +182,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 7ae5aa62599..285a3a07819 100644 --- a/applications/services/cli/cli_command_gpio.h +++ b/applications/services/cli/cli_command_gpio.h @@ -2,4 +2,4 @@ #include "cli_i.h" -void cli_command_gpio(Cli* cli, FuriString* args, void* context); +void cli_command_gpio(FuriPipeSide* pipe, FuriString* args, void* context); diff --git a/applications/services/cli/cli_commands.c b/applications/services/cli/cli_commands.c index 3de3ab59ccd..324cbc9e5ac 100644 --- a/applications/services/cli/cli_commands.c +++ b/applications/services/cli/cli_commands.c @@ -529,6 +529,6 @@ void cli_commands_init(Cli* cli) { cli_add_command(cli, "vibro", CliCommandFlagParallelUnsafe, cli_command_vibro, NULL); cli_add_command(cli, "led", CliCommandFlagParallelUnsafe, cli_command_led, NULL); - // cli_add_command(cli, "gpio", CliCommandFlagParallelUnsafe, cli_command_gpio, NULL); + cli_add_command(cli, "gpio", CliCommandFlagParallelUnsafe, cli_command_gpio, NULL); cli_add_command(cli, "i2c", CliCommandFlagParallelUnsafe, cli_command_i2c, NULL); } From 1280a8ee4d29dc4f574af0fa2a1f40dfc8c2407a Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Tue, 3 Dec 2024 12:25:12 +0400 Subject: [PATCH 29/47] ci: fix formatting --- applications/services/cli/application.fam | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/applications/services/cli/application.fam b/applications/services/cli/application.fam index 832ffc2e746..e35c79ffe87 100644 --- a/applications/services/cli/application.fam +++ b/applications/services/cli/application.fam @@ -3,7 +3,13 @@ App( apptype=FlipperAppType.STARTUP, entry_point="cli_on_system_start", cdefines=["SRV_CLI"], - sources=["cli.c", "cli_shell.c", "cli_commands.c", "cli_command_gpio.c", "cli_ansi.c"], + sources=[ + "cli.c", + "cli_shell.c", + "cli_commands.c", + "cli_command_gpio.c", + "cli_ansi.c", + ], sdk_headers=["cli.h"], order=0, ) From 9d340695a36498cb14f547e58e8278580eb1342b Mon Sep 17 00:00:00 2001 From: Georgii Surkov Date: Wed, 4 Dec 2024 15:50:11 +0000 Subject: [PATCH 30/47] Fix memory leak during event loop unsubscription --- furi/core/event_loop.c | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/furi/core/event_loop.c b/furi/core/event_loop.c index 23acd89a125..73b9cf8865e 100644 --- a/furi/core/event_loop.c +++ b/furi/core/event_loop.c @@ -40,6 +40,14 @@ static void furi_event_loop_process_pending_callbacks(FuriEventLoop* instance) { } } +static void furi_event_loop_process_deleted_items(FuriEventLoop* instance) { + while(!WaitingList_empty_p(instance->waiting_list)) { + FuriEventLoopItem* item = WaitingList_pop_front(instance->waiting_list); + furi_check(!item->owner); + furi_event_loop_item_free(item); + } +} + static bool furi_event_loop_signal_callback(uint32_t signal, void* arg, void* context) { furi_assert(context); FuriEventLoop* instance = context; @@ -85,6 +93,7 @@ void furi_event_loop_free(FuriEventLoop* instance) { furi_event_loop_process_timer_queue(instance); furi_check(TimerList_empty_p(instance->timer_list)); + furi_event_loop_process_deleted_items(instance); furi_check(WaitingList_empty_p(instance->waiting_list)); FuriEventLoopTree_clear(instance->tree); @@ -124,23 +133,19 @@ static inline FuriEventLoopProcessStatus static inline FuriEventLoopProcessStatus furi_event_loop_process_event(FuriEventLoop* instance, FuriEventLoopItem* item) { - FuriEventLoopProcessStatus status; + if(item->owner == NULL) { + return FuriEventLoopProcessStatusFreeLater; + } if(item->event & FuriEventLoopEventFlagOnce) { furi_event_loop_unsubscribe(instance, item->object); } if(item->event & FuriEventLoopEventFlagEdge) { - status = furi_event_loop_process_edge_event(item); + return furi_event_loop_process_edge_event(item); } else { - status = furi_event_loop_process_level_event(item); + return furi_event_loop_process_level_event(item); } - - if(item->owner == NULL) { - status = FuriEventLoopProcessStatusFreeLater; - } - - return status; } static inline FuriEventLoopItem* furi_event_loop_get_waiting_item(FuriEventLoop* instance) { @@ -529,7 +534,9 @@ static void furi_event_loop_item_free(FuriEventLoopItem* instance) { static void furi_event_loop_item_free_later(FuriEventLoopItem* instance) { furi_assert(instance); + furi_assert(instance->owner); furi_assert(!furi_event_loop_item_is_waiting(instance)); + WaitingList_push_back(instance->owner->waiting_list, instance); instance->owner = NULL; } From 7a69000d914cb2fbc58cab678f8663f598b32b48 Mon Sep 17 00:00:00 2001 From: Georgii Surkov Date: Wed, 4 Dec 2024 17:43:15 +0000 Subject: [PATCH 31/47] Event better memory leak fix --- furi/core/event_loop.c | 54 ++++++++++++++++++---------------------- furi/core/event_loop_i.h | 4 +-- 2 files changed, 26 insertions(+), 32 deletions(-) diff --git a/furi/core/event_loop.c b/furi/core/event_loop.c index 73b9cf8865e..9a17ccc7884 100644 --- a/furi/core/event_loop.c +++ b/furi/core/event_loop.c @@ -32,22 +32,6 @@ static void furi_event_loop_item_notify(FuriEventLoopItem* instance); static bool furi_event_loop_item_is_waiting(FuriEventLoopItem* instance); -static void furi_event_loop_process_pending_callbacks(FuriEventLoop* instance) { - for(; !PendingQueue_empty_p(instance->pending_queue); - PendingQueue_pop_back(NULL, instance->pending_queue)) { - const FuriEventLoopPendingQueueItem* item = PendingQueue_back(instance->pending_queue); - item->callback(item->context); - } -} - -static void furi_event_loop_process_deleted_items(FuriEventLoop* instance) { - while(!WaitingList_empty_p(instance->waiting_list)) { - FuriEventLoopItem* item = WaitingList_pop_front(instance->waiting_list); - furi_check(!item->owner); - furi_event_loop_item_free(item); - } -} - static bool furi_event_loop_signal_callback(uint32_t signal, void* arg, void* context) { furi_assert(context); FuriEventLoop* instance = context; @@ -93,7 +77,6 @@ void furi_event_loop_free(FuriEventLoop* instance) { furi_event_loop_process_timer_queue(instance); furi_check(TimerList_empty_p(instance->timer_list)); - furi_event_loop_process_deleted_items(instance); furi_check(WaitingList_empty_p(instance->waiting_list)); FuriEventLoopTree_clear(instance->tree); @@ -133,19 +116,27 @@ static inline FuriEventLoopProcessStatus static inline FuriEventLoopProcessStatus furi_event_loop_process_event(FuriEventLoop* instance, FuriEventLoopItem* item) { - if(item->owner == NULL) { - return FuriEventLoopProcessStatusFreeLater; - } + FuriEventLoopProcessStatus status; if(item->event & FuriEventLoopEventFlagOnce) { furi_event_loop_unsubscribe(instance, item->object); } + instance->current_item = item; + if(item->event & FuriEventLoopEventFlagEdge) { - return furi_event_loop_process_edge_event(item); + status = furi_event_loop_process_edge_event(item); } else { - return furi_event_loop_process_level_event(item); + status = furi_event_loop_process_level_event(item); } + + instance->current_item = NULL; + + if(item->owner == NULL) { + status = FuriEventLoopProcessStatusFreeLater; + } + + return status; } static inline FuriEventLoopItem* furi_event_loop_get_waiting_item(FuriEventLoop* instance) { @@ -198,6 +189,14 @@ static void furi_event_loop_process_waiting_list(FuriEventLoop* instance) { furi_event_loop_sync_flags(instance); } +static void furi_event_loop_process_pending_callbacks(FuriEventLoop* instance) { + for(; !PendingQueue_empty_p(instance->pending_queue); + PendingQueue_pop_back(NULL, instance->pending_queue)) { + const FuriEventLoopPendingQueueItem* item = PendingQueue_back(instance->pending_queue); + item->callback(item->context); + } +} + static void furi_event_loop_restore_flags(FuriEventLoop* instance, uint32_t flags) { if(flags) { xTaskNotifyIndexed( @@ -208,7 +207,6 @@ static void furi_event_loop_restore_flags(FuriEventLoop* instance, uint32_t flag void furi_event_loop_run(FuriEventLoop* instance) { furi_check(instance); furi_check(instance->thread_id == furi_thread_get_current_id()); - FuriThread* thread = furi_thread_get_current(); // Set the default signal callback if none was previously set @@ -218,9 +216,9 @@ void furi_event_loop_run(FuriEventLoop* instance) { furi_event_loop_init_tick(instance); - while(true) { - instance->state = FuriEventLoopStateIdle; + instance->state = FuriEventLoopStateRunning; + while(true) { const TickType_t ticks_to_sleep = MIN(furi_event_loop_get_timer_wait_time(instance), furi_event_loop_get_tick_wait_time(instance)); @@ -229,8 +227,6 @@ void furi_event_loop_run(FuriEventLoop* instance) { BaseType_t ret = xTaskNotifyWaitIndexed( FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX, 0, FuriEventLoopFlagAll, &flags, ticks_to_sleep); - instance->state = FuriEventLoopStateProcessing; - if(ret == pdTRUE) { if(flags & FuriEventLoopFlagStop) { instance->state = FuriEventLoopStateStopped; @@ -467,7 +463,7 @@ static void furi_event_loop_unsubscribe_item( WaitingList_unlink(item); } - if(instance->state == FuriEventLoopStateProcessing) { + if(instance->current_item == item) { furi_event_loop_item_free_later(item); } else { furi_event_loop_item_free(item); @@ -534,9 +530,7 @@ static void furi_event_loop_item_free(FuriEventLoopItem* instance) { static void furi_event_loop_item_free_later(FuriEventLoopItem* instance) { furi_assert(instance); - furi_assert(instance->owner); furi_assert(!furi_event_loop_item_is_waiting(instance)); - WaitingList_push_back(instance->owner->waiting_list, instance); instance->owner = NULL; } diff --git a/furi/core/event_loop_i.h b/furi/core/event_loop_i.h index 099bc42232b..8b5a188b0b4 100644 --- a/furi/core/event_loop_i.h +++ b/furi/core/event_loop_i.h @@ -69,8 +69,7 @@ typedef enum { typedef enum { FuriEventLoopStateStopped, - FuriEventLoopStateIdle, - FuriEventLoopStateProcessing, + FuriEventLoopStateRunning, } FuriEventLoopState; typedef struct { @@ -86,6 +85,7 @@ struct FuriEventLoop { // Poller state volatile FuriEventLoopState state; + volatile FuriEventLoopItem* current_item; // Event handling FuriEventLoopTree_t tree; From da047a30beaf1b04560d232ad1ea4ec563410724 Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Sun, 8 Dec 2024 03:08:28 +0400 Subject: [PATCH 32/47] feat: cli completions --- applications/services/cli/application.fam | 1 - applications/services/cli/cli.c | 36 +- applications/services/cli/cli.h | 58 ++- applications/services/cli/cli_ansi.c | 13 + applications/services/cli/cli_ansi.h | 19 +- applications/services/cli/cli_i.h | 6 - applications/services/cli/cli_shell.c | 423 ++++++++++++++++++---- applications/services/cli/cli_vcp.c | 16 +- furi/core/core_defines.h | 10 + targets/f7/api_symbols.csv | 8 - 10 files changed, 477 insertions(+), 113 deletions(-) diff --git a/applications/services/cli/application.fam b/applications/services/cli/application.fam index e35c79ffe87..d0c20ad4ac9 100644 --- a/applications/services/cli/application.fam +++ b/applications/services/cli/application.fam @@ -10,7 +10,6 @@ App( "cli_command_gpio.c", "cli_ansi.c", ], - sdk_headers=["cli.h"], order=0, ) diff --git a/applications/services/cli/cli.c b/applications/services/cli/cli.c index 63fd11a9912..180c1054dcf 100644 --- a/applications/services/cli/cli.c +++ b/applications/services/cli/cli.c @@ -19,25 +19,33 @@ void cli_add_command( Cli* cli, const char* name, CliCommandFlag flags, - CliCallback callback, + CliExecuteCallback callback, void* context) { - furi_check(cli); - FuriString* name_str; - name_str = furi_string_alloc_set(name); - furi_string_trim(name_str); + // missing checks performed by cli_add_command_ex + + CliCommand cmd = { + .name = name, + .context = context, + .execute_callback = callback, + .complete_callback = NULL, + .flags = flags, + }; + cli_add_command_ex(cli, &cmd); +} - size_t name_replace; - do { - name_replace = furi_string_replace(name_str, " ", "_"); - } while(name_replace != FURI_STRING_FAILURE); +void cli_add_command_ex(Cli* cli, CliCommand* command) { + furi_check(cli); + furi_check(command); + furi_check(command->name); + furi_check(command->execute_callback); - CliCommand c; - c.callback = callback; - c.context = context; - c.flags = flags; + FuriString* name_str; + name_str = furi_string_alloc_set(command->name); + // command cannot contain spaces + furi_check(furi_string_search_char(name_str, ' ') == FURI_STRING_FAILURE); 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); diff --git a/applications/services/cli/cli.h b/applications/services/cli/cli.h index 6b7f99754b8..f98a1600b95 100644 --- a/applications/services/cli/cli.h +++ b/applications/services/cli/cli.h @@ -5,7 +5,7 @@ #pragma once #include -#include +#include #include "cli_ansi.h" #ifdef __cplusplus @@ -25,8 +25,13 @@ typedef enum { typedef struct Cli Cli; /** - * @brief CLI callback function pointer. Implement this interface and use - * `add_cli_command`. + * @brief CLI execution callbackpointer. Implement this interface and use + * `add_cli_command` or `cli_add_command_ex`. + * + * 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 @@ -35,10 +40,11 @@ typedef struct Cli Cli; * @param [in] args String with what was passed after the command * @param [in] context Whatever you provided to `cli_add_command` */ -typedef void (*CliCallback)(FuriPipeSide* pipe, FuriString* args, void* context); +typedef void (*CliExecuteCallback)(FuriPipeSide* pipe, FuriString* args, void* context); /** - * @brief Registers a command with the CLI + * @brief Registers a command with the CLI. Provides less options than + * `cli_add_command_ex` * * @param [in] cli Pointer to CLI instance * @param [in] name Command name @@ -50,9 +56,49 @@ void cli_add_command( Cli* cli, const char* name, CliCommandFlag flags, - CliCallback callback, + CliExecuteCallback callback, + void* context); + +ARRAY_DEF(CommandCompletions, FuriString*, FURI_STRING_OPLIST); // -V524 +#define M_OPL_CommandCompletions_t() ARRAY_OPLIST(CommandCompletions) + +/** + * @brief Command autocomplete callback. + * + * This callback will be called from the shell thread. + * + * @param [in] partial_args Input after the name of the command up to the point + * where TAB was pressed. + * @param [out] full_args An initialized empty array that you fill up with + * suggestions for the entire `args`. + */ +typedef void (*CliCompleteCallback)( + FuriPipeSide* pipe, + FuriString* partial_args, + CommandCompletions_t full_args, void* context); +/** + * @brief Extended command descriptor for `cli_add_command_ex` + */ +typedef struct { + const char* name; //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 index f3b626d1a03..0b850c5e30d 100644 --- a/applications/services/cli/cli_ansi.h +++ b/applications/services/cli/cli_ansi.h @@ -8,9 +8,10 @@ extern "C" { // text styling -#define ANSI_RESET "\e[0m" -#define ANSI_BOLD "\e[1m" -#define ANSI_FAINT "\e[2m" +#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" @@ -135,6 +136,18 @@ void cli_ansi_parser_free(CliAnsiParser* parser); */ CliAnsiParserResult cli_ansi_parser_feed(CliAnsiParser* parser, char c); +/** + * @brief Feeds an ANSI parser a timeout event + * + * As a user of the ANSI parses 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_i.h b/applications/services/cli/cli_i.h index eb7ed4cba4e..04b99997621 100644 --- a/applications/services/cli/cli_i.h +++ b/applications/services/cli/cli_i.h @@ -13,12 +13,6 @@ extern "C" { #endif -typedef struct { - CliCallback callback; - void* context; - CliCommandFlag flags; -} CliCommand; - #define CLI_COMMANDS_TREE_RANK 4 // -V:BPTREE_DEF2:1103 diff --git a/applications/services/cli/cli_shell.c b/applications/services/cli/cli_shell.c index ea9c6c9e531..7d46a293376 100644 --- a/applications/services/cli/cli_shell.c +++ b/applications/services/cli/cli_shell.c @@ -8,7 +8,11 @@ #define TAG "CliShell" -#define HISTORY_DEPTH 10 +#define HISTORY_DEPTH 10 +#define COMPLETION_COLUMNS 3 +#define COMPLETION_COLUMN_WIDTH "30" +#define COMPLETION_COLUMN_WIDTH_I 30 +#define ANSI_TIMEOUT_MS 10 ARRAY_DEF(ShellHistory, FuriString*, FURI_STRING_OPLIST); // -V524 #define M_OPL_ShellHistory_t() ARRAY_OPLIST(ShellHistory) @@ -19,10 +23,15 @@ typedef struct { FuriEventLoop* event_loop; FuriPipeSide* pipe; CliAnsiParser* ansi_parser; + FuriEventLoopTimer* ansi_parsing_timer; size_t history_position; size_t line_position; ShellHistory_t history; + + CommandCompletions_t completions; + size_t selected_completion; + bool is_displaying_completions; } CliShell; typedef struct { @@ -36,7 +45,7 @@ static int32_t cli_command_thread(void* context) { if(!(thread_data->command->flags & CliCommandFlagDontAttachStdio)) furi_pipe_install_as_stdio(thread_data->pipe); - thread_data->command->callback( + thread_data->command->execute_callback( thread_data->pipe, thread_data->args, thread_data->command->context); fflush(stdout); @@ -50,7 +59,7 @@ static void cli_shell_execute_command(CliShell* cli_shell, FuriString* command) FuriString* command_name = furi_string_alloc_set(command); furi_string_left(command_name, space); FuriString* args = furi_string_alloc_set(command); - furi_string_right(args, space + 1); // FIXME: + furi_string_right(args, space + 1); // find handler CliCommand command_data; @@ -159,7 +168,7 @@ typedef enum { } CliSkipDirection; /** - * @brief Skips a run of a class of characters + * @brief Skips a run of characters of the same class * * @param string Input string * @param original_pos Position to start the search at @@ -189,24 +198,224 @@ static size_t cli_skip_run(FuriString* string, size_t original_pos, CliSkipDirec return MAX(0, position); } -static void cli_shell_data_available(FuriEventLoopObject* object, void* context) { - UNUSED(object); - CliShell* cli_shell = context; - UNUSED(cli_shell); +/** + * @brief Update for the completions menu + */ +typedef enum { + CliShellCompletionsActionOpen, + CliShellCompletionsActionClose, + CliShellCompletionsActionUp, + CliShellCompletionsActionDown, + CliShellCompletionsActionLeft, + CliShellCompletionsActionRight, + CliShellCompletionsActionInput, + CliShellCompletionsActionSelect, +} CliShellCompletionsAction; - // 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; +typedef enum { + CliShellCompletionSegmentTypeCommand, + CliShellCompletionSegmentTypeArguments, +} CliShellCompletionSegmentType; + +typedef struct { + CliShellCompletionSegmentType type; + size_t start; + size_t length; +} CliShellCompletionSegment; + +static CliShellCompletionSegment cli_shell_completion_segment(CliShell* cli_shell) { + CliShellCompletionSegment segment; + + FuriString* input = furi_string_alloc_set(*ShellHistory_front(cli_shell->history)); + furi_string_left(input, cli_shell->line_position); + + // find index of first non-space character + size_t first_non_space = 0; + while(1) { + size_t ret = furi_string_search_char(input, ' ', first_non_space); + if(ret == FURI_STRING_FAILURE) break; + if(ret - first_non_space > 1) break; + first_non_space++; + } + + size_t first_space_in_command = furi_string_search_char(input, ' ', first_non_space); + + if(first_space_in_command == FURI_STRING_FAILURE) { + segment.type = CliShellCompletionSegmentTypeCommand; + segment.start = first_non_space; + segment.length = furi_string_size(input) - first_non_space; + } else { + segment.type = CliShellCompletionSegmentTypeArguments; + furi_crash("TODO:"); + } + + furi_string_free(input); + return segment; +} + +static void cli_shell_fill_completions(CliShell* cli_shell) { + CommandCompletions_reset(cli_shell->completions); + + CliShellCompletionSegment segment = cli_shell_completion_segment(cli_shell); + FuriString* input = furi_string_alloc_set(*ShellHistory_front(cli_shell->history)); + furi_string_right(input, segment.start); + furi_string_left(input, segment.length); + + if(segment.type == CliShellCompletionSegmentTypeCommand) { + CliCommandTree_t* commands = cli_get_commands(cli_shell->cli); + for + M_EACH(registered_command, *commands, CliCommandTree_t) { + FuriString* command_name = *registered_command->key_ptr; + if(furi_string_start_with(command_name, input)) { + CommandCompletions_push_back(cli_shell->completions, command_name); + } + } + + } else { + furi_crash("TODO:"); + } + + furi_string_free(input); +} + +static void cli_shell_completions_render(CliShell* cli_shell, CliShellCompletionsAction action) { + furi_assert(cli_shell); + if(action == CliShellCompletionsActionOpen) furi_check(!cli_shell->is_displaying_completions); + if(action == CliShellCompletionsActionInput) furi_check(cli_shell->is_displaying_completions); + if(action == CliShellCompletionsActionClose) furi_check(cli_shell->is_displaying_completions); + + char prompt[64]; + cli_shell_format_prompt(cli_shell, prompt, sizeof(prompt)); + + if(action == CliShellCompletionsActionOpen || action == CliShellCompletionsActionInput) { + // show or update completions menu (full re-render) + cli_shell_fill_completions(cli_shell); + cli_shell->selected_completion = 0; + + printf("\n\r"); + size_t position = 0; + for + M_EACH(completion, cli_shell->completions, CommandCompletions_t) { + if(position == cli_shell->selected_completion) printf(ANSI_INVERT); + printf("%-" COMPLETION_COLUMN_WIDTH "s", furi_string_get_cstr(*completion)); + if(position == cli_shell->selected_completion) printf(ANSI_RESET); + if((position % COMPLETION_COLUMNS == COMPLETION_COLUMNS - 1) && + position != CommandCompletions_size(cli_shell->completions)) { + printf("\r\n"); + } + position++; + } + + if(!position) { + printf(ANSI_FG_RED "no completions" ANSI_RESET); + } + + size_t total_rows = (position / COMPLETION_COLUMNS) + 1; + printf( + ANSI_ERASE_DISPLAY(ANSI_ERASE_FROM_CURSOR_TO_END) ANSI_CURSOR_UP_BY("%zu") + ANSI_CURSOR_HOR_POS("%zu"), + total_rows, + strlen(prompt) + cli_shell->line_position + 1); + + cli_shell->is_displaying_completions = true; + + } else if(action == CliShellCompletionsActionClose) { + // clear completions menu + printf( + ANSI_CURSOR_HOR_POS("%zu") ANSI_ERASE_DISPLAY(ANSI_ERASE_FROM_CURSOR_TO_END) + ANSI_CURSOR_HOR_POS("%zu"), + strlen(prompt) + + furi_string_size( + *ShellHistory_cget(cli_shell->history, cli_shell->history_position)) + + 1, + strlen(prompt) + cli_shell->line_position + 1); + cli_shell->is_displaying_completions = false; + + } else if( + action == CliShellCompletionsActionUp || action == CliShellCompletionsActionDown || + action == CliShellCompletionsActionLeft || action == CliShellCompletionsActionRight) { + if(CommandCompletions_empty_p(cli_shell->completions)) return; + + // move selection + size_t old_selection = cli_shell->selected_completion; + int new_selection_unclamped = cli_shell->selected_completion; + if(action == CliShellCompletionsActionUp) new_selection_unclamped -= COMPLETION_COLUMNS; + if(action == CliShellCompletionsActionDown) new_selection_unclamped += COMPLETION_COLUMNS; + if(action == CliShellCompletionsActionLeft) new_selection_unclamped--; + if(action == CliShellCompletionsActionRight) new_selection_unclamped++; + size_t new_selection = CLAMP_WRAPAROUND( + new_selection_unclamped, (int)CommandCompletions_size(cli_shell->completions) - 1, 0); + cli_shell->selected_completion = new_selection; + + if(new_selection != old_selection) { + // determine selection coordinates relative to top-left of suggestion menu + size_t old_x = (old_selection % COMPLETION_COLUMNS) * COMPLETION_COLUMN_WIDTH_I; + size_t old_y = old_selection / COMPLETION_COLUMNS; + size_t new_x = (new_selection % COMPLETION_COLUMNS) * COMPLETION_COLUMN_WIDTH_I; + size_t new_y = new_selection / COMPLETION_COLUMNS; + printf("\n\r"); + + // print old selection in normal colors + if(old_y) printf(ANSI_CURSOR_DOWN_BY("%zu"), old_y); + printf(ANSI_CURSOR_HOR_POS("%zu"), old_x + 1); + printf( + "%-" COMPLETION_COLUMN_WIDTH "s", + furi_string_get_cstr( + *CommandCompletions_cget(cli_shell->completions, old_selection))); + if(old_y) printf(ANSI_CURSOR_UP_BY("%zu"), old_y); + printf(ANSI_CURSOR_HOR_POS("1")); + + // print new selection in inverted colors + if(new_y) printf(ANSI_CURSOR_DOWN_BY("%zu"), new_y); + printf(ANSI_CURSOR_HOR_POS("%zu"), new_x + 1); + printf( + ANSI_INVERT "%-" COMPLETION_COLUMN_WIDTH "s" ANSI_RESET, + furi_string_get_cstr( + *CommandCompletions_cget(cli_shell->completions, new_selection))); + + // return cursor + printf(ANSI_CURSOR_UP_BY("%zu"), new_y + 1); + printf( + ANSI_CURSOR_HOR_POS("%zu"), + strlen(prompt) + + furi_string_size( + *ShellHistory_cget(cli_shell->history, cli_shell->history_position)) + + 1); + } + + } else if(action == CliShellCompletionsActionSelect) { + // insert selection into prompt + CliShellCompletionSegment segment = cli_shell_completion_segment(cli_shell); + FuriString* input = *ShellHistory_cget(cli_shell->history, cli_shell->history_position); + FuriString* completion = + *CommandCompletions_cget(cli_shell->completions, cli_shell->selected_completion); + furi_string_replace_at( + input, segment.start, segment.length, furi_string_get_cstr(completion)); + printf( + ANSI_CURSOR_HOR_POS("%zu") "%s" ANSI_ERASE_LINE(ANSI_ERASE_FROM_CURSOR_TO_END), + strlen(prompt) + 1, + furi_string_get_cstr(input)); + + int position_change = (int)furi_string_size(completion) - (int)segment.length; + cli_shell->line_position = MAX(0, (int)cli_shell->line_position + position_change); + + cli_shell_completions_render(cli_shell, CliShellCompletionsActionClose); + + } else { + furi_crash(); + } + + fflush(stdout); +} + +static void cli_shell_process_key(CliShell* cli_shell, CliKeyCombo key_combo) { FURI_LOG_T( TAG, "mod=%d, key=%d='%c'", key_combo.modifiers, key_combo.key, (char)key_combo.key); - // do things the user requests if(key_combo.modifiers == 0 && key_combo.key == CliKeyETX) { // usually Ctrl+C // reset input + if(cli_shell->is_displaying_completions) + cli_shell_completions_render(cli_shell, CliShellCompletionsActionClose); furi_string_reset(*ShellHistory_front(cli_shell->history)); cli_shell->line_position = 0; cli_shell->history_position = 0; @@ -216,7 +425,7 @@ static void cli_shell_data_available(FuriEventLoopObject* object, void* context) } else if(key_combo.modifiers == 0 && key_combo.key == CliKeyFF) { // usually Ctrl+L // clear screen FuriString* command = *ShellHistory_cget(cli_shell->history, cli_shell->history_position); - char prompt[128]; + char prompt[64]; cli_shell_format_prompt(cli_shell, prompt, sizeof(prompt)); printf( ANSI_ERASE_DISPLAY(ANSI_ERASE_ENTIRE) ANSI_ERASE_SCROLLBACK_BUFFER ANSI_CURSOR_POS( @@ -227,71 +436,98 @@ static void cli_shell_data_available(FuriEventLoopObject* object, void* context) fflush(stdout); } else if(key_combo.modifiers == 0 && key_combo.key == CliKeyCR) { - // get command and update history - FuriString* command = furi_string_alloc(); - ShellHistory_pop_at(&command, cli_shell->history, cli_shell->history_position); - furi_string_trim(command); - if(cli_shell->history_position > 0) ShellHistory_pop_at(NULL, cli_shell->history, 0); - if(!furi_string_empty(command)) ShellHistory_push_at(cli_shell->history, 0, command); - FuriString* new_command = furi_string_alloc(); - ShellHistory_push_at(cli_shell->history, 0, new_command); - furi_string_free(new_command); - if(ShellHistory_size(cli_shell->history) > HISTORY_DEPTH) { - ShellHistory_pop_back(NULL, cli_shell->history); + if(cli_shell->is_displaying_completions) { + // select completion + cli_shell_completions_render(cli_shell, CliShellCompletionsActionSelect); + } else { + // get command and update history + FuriString* command = furi_string_alloc(); + ShellHistory_pop_at(&command, cli_shell->history, cli_shell->history_position); + furi_string_trim(command); + if(cli_shell->history_position > 0) ShellHistory_pop_at(NULL, cli_shell->history, 0); + if(!furi_string_empty(command)) ShellHistory_push_at(cli_shell->history, 0, command); + FuriString* new_command = furi_string_alloc(); + ShellHistory_push_at(cli_shell->history, 0, new_command); + furi_string_free(new_command); + if(ShellHistory_size(cli_shell->history) > HISTORY_DEPTH) { + ShellHistory_pop_back(NULL, cli_shell->history); + } + + // execute command + cli_shell->line_position = 0; + cli_shell->history_position = 0; + printf("\r\n"); + if(!furi_string_empty(command)) cli_shell_execute_command(cli_shell, command); + furi_string_free(command); + cli_shell_prompt(cli_shell); } - // execute command - cli_shell->line_position = 0; - cli_shell->history_position = 0; - printf("\r\n"); - cli_shell_execute_command(cli_shell, command); - furi_string_free(command); - cli_shell_prompt(cli_shell); - } else if(key_combo.modifiers == 0 && (key_combo.key == CliKeyUp || key_combo.key == CliKeyDown)) { - // go up and down in history - int increment = (key_combo.key == CliKeyUp) ? 1 : -1; - size_t new_pos = CLAMP( - (int)cli_shell->history_position + increment, - (int)ShellHistory_size(cli_shell->history) - 1, - 0); - - // print prompt with selected command - if(new_pos != cli_shell->history_position) { - cli_shell->history_position = new_pos; - FuriString* command = - *ShellHistory_cget(cli_shell->history, cli_shell->history_position); - printf( - ANSI_CURSOR_HOR_POS("1") ">: %s" ANSI_ERASE_LINE(ANSI_ERASE_FROM_CURSOR_TO_END), - furi_string_get_cstr(command)); - fflush(stdout); - cli_shell->line_position = furi_string_size(command); + if(cli_shell->is_displaying_completions) { + cli_shell_completions_render( + cli_shell, + (key_combo.key == CliKeyUp) ? CliShellCompletionsActionUp : + CliShellCompletionsActionDown); + } else { + // go up and down in history + int increment = (key_combo.key == CliKeyUp) ? 1 : -1; + size_t new_pos = CLAMP( + (int)cli_shell->history_position + increment, + (int)ShellHistory_size(cli_shell->history) - 1, + 0); + + // print prompt with selected command + if(new_pos != cli_shell->history_position) { + cli_shell->history_position = new_pos; + FuriString* command = + *ShellHistory_cget(cli_shell->history, cli_shell->history_position); + printf( + ANSI_CURSOR_HOR_POS("1") ">: %s" ANSI_ERASE_LINE( + ANSI_ERASE_FROM_CURSOR_TO_END), + furi_string_get_cstr(command)); + fflush(stdout); + cli_shell->line_position = furi_string_size(command); + } } } else if( key_combo.modifiers == 0 && (key_combo.key == CliKeyLeft || key_combo.key == CliKeyRight)) { - // go left and right in the current line - FuriString* command = *ShellHistory_cget(cli_shell->history, cli_shell->history_position); - int increment = (key_combo.key == CliKeyRight) ? 1 : -1; - size_t new_pos = - CLAMP((int)cli_shell->line_position + increment, (int)furi_string_size(command), 0); - - // move cursor - if(new_pos != cli_shell->line_position) { - cli_shell->line_position = new_pos; - printf("%s", (increment == 1) ? ANSI_CURSOR_RIGHT_BY("1") : ANSI_CURSOR_LEFT_BY("1")); - fflush(stdout); + if(cli_shell->is_displaying_completions) { + cli_shell_completions_render( + cli_shell, + (key_combo.key == CliKeyLeft) ? CliShellCompletionsActionLeft : + CliShellCompletionsActionRight); + + } else { + // go left and right in the current line + FuriString* command = + *ShellHistory_cget(cli_shell->history, cli_shell->history_position); + int increment = (key_combo.key == CliKeyRight) ? 1 : -1; + size_t new_pos = CLAMP( + (int)cli_shell->line_position + increment, (int)furi_string_size(command), 0); + + // move cursor + if(new_pos != cli_shell->line_position) { + cli_shell->line_position = new_pos; + printf( + "%s", (increment == 1) ? ANSI_CURSOR_RIGHT_BY("1") : ANSI_CURSOR_LEFT_BY("1")); + fflush(stdout); + } } } else if(key_combo.modifiers == 0 && key_combo.key == CliKeyHome) { // go to the start + if(cli_shell->is_displaying_completions) + cli_shell_completions_render(cli_shell, CliShellCompletionsActionClose); cli_shell->line_position = 0; printf(ANSI_CURSOR_HOR_POS("%d"), cli_shell_prompt_length(cli_shell) + 1); fflush(stdout); } else if(key_combo.modifiers == 0 && key_combo.key == CliKeyEnd) { // go to the end + if(cli_shell->is_displaying_completions) + cli_shell_completions_render(cli_shell, CliShellCompletionsActionClose); FuriString* line = *ShellHistory_cget(cli_shell->history, cli_shell->history_position); cli_shell->line_position = furi_string_size(line); printf( @@ -322,10 +558,15 @@ static void cli_shell_data_available(FuriEventLoopObject* object, void* context) printf(ANSI_CURSOR_LEFT_BY("%zu"), left_by); fflush(stdout); + if(cli_shell->is_displaying_completions) + cli_shell_completions_render(cli_shell, CliShellCompletionsActionInput); + } else if( key_combo.modifiers == CliModKeyCtrl && (key_combo.key == CliKeyLeft || key_combo.key == CliKeyRight)) { // skip run of similar chars to the left or right + if(cli_shell->is_displaying_completions) + cli_shell_completions_render(cli_shell, CliShellCompletionsActionClose); FuriString* line = *ShellHistory_cget(cli_shell->history, cli_shell->history_position); CliSkipDirection direction = (key_combo.key == CliKeyLeft) ? CliSkipDirectionLeft : CliSkipDirectionRight; @@ -350,6 +591,22 @@ static void cli_shell_data_available(FuriEventLoopObject* object, void* context) cli_shell_prompt_length(cli_shell) + run_start + 1); fflush(stdout); + if(cli_shell->is_displaying_completions) + cli_shell_completions_render(cli_shell, CliShellCompletionsActionInput); + + } else if(key_combo.modifiers == 0 && key_combo.key == CliKeyTab) { + // display completions + cli_shell_ensure_not_overwriting_history(cli_shell); + cli_shell_completions_render( + cli_shell, + cli_shell->is_displaying_completions ? CliShellCompletionsActionRight : + CliShellCompletionsActionOpen); + + } else if(key_combo.modifiers == 0 && key_combo.key == CliKeyEsc) { + // close completions menu + if(cli_shell->is_displaying_completions) + cli_shell_completions_render(cli_shell, CliShellCompletionsActionClose); + } else if(key_combo.modifiers == 0 && key_combo.key >= CliKeySpace && key_combo.key < CliKeyDEL) { // insert character cli_shell_ensure_not_overwriting_history(cli_shell); @@ -364,9 +621,39 @@ static void cli_shell_data_available(FuriEventLoopObject* object, void* context) } fflush(stdout); cli_shell->line_position++; + + if(cli_shell->is_displaying_completions) + cli_shell_completions_render(cli_shell, CliShellCompletionsActionInput); } } +static void cli_shell_data_available(FuriEventLoopObject* object, void* context) { + UNUSED(object); + 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); +} + static CliShell* cli_shell_alloc(FuriPipeSide* pipe) { CliShell* cli_shell = malloc(sizeof(CliShell)); cli_shell->cli = furi_record_open(RECORD_CLI); @@ -386,12 +673,20 @@ static CliShell* cli_shell_alloc(FuriPipeSide* pipe) { cli_shell_data_available, cli_shell); + cli_shell->ansi_parsing_timer = furi_event_loop_timer_alloc( + cli_shell->event_loop, cli_shell_timer_expired, FuriEventLoopTimerTypeOnce, cli_shell); + furi_event_loop_tick_set(cli_shell->event_loop, 1, cli_shell_tick, cli_shell); + CommandCompletions_init(cli_shell->completions); + cli_shell->selected_completion = 0; + return cli_shell; } static void cli_shell_free(CliShell* cli_shell) { + CommandCompletions_clear(cli_shell->completions); + furi_event_loop_timer_free(cli_shell->ansi_parsing_timer); furi_event_loop_unsubscribe(cli_shell->event_loop, cli_shell->pipe); furi_pipe_free(cli_shell->pipe); ShellHistory_clear(cli_shell->history); diff --git a/applications/services/cli/cli_vcp.c b/applications/services/cli/cli_vcp.c index 5181b3a47e1..82df37151d4 100644 --- a/applications/services/cli/cli_vcp.c +++ b/applications/services/cli/cli_vcp.c @@ -14,12 +14,6 @@ #define VCP_MESSAGE_Q_LEN 8 -// #ifdef VCP_TRACE -#define VCP_LOG_T(...) FURI_LOG_T(__VA_ARGS__) -// #else -// #define VCP_LOG_T(...) -// #endif - typedef struct { enum { CliVcpMessageTypeEnable, @@ -66,7 +60,7 @@ static void cli_vcp_maybe_send_data(CliVcp* cli_vcp) { uint8_t buf[USB_CDC_PKT_LEN]; size_t length = furi_pipe_receive(cli_vcp->own_pipe, buf, sizeof(buf), 0); if(length > 0 || cli_vcp->previous_tx_length == USB_CDC_PKT_LEN) { - VCP_LOG_T(TAG, "cdc_send length=%zu", length); + FURI_LOG_T(TAG, "cdc_send length=%zu", length); cli_vcp->is_currently_transmitting = true; furi_hal_cdc_send(VCP_IF_NUM, buf, length); } @@ -84,7 +78,7 @@ static void cli_vcp_maybe_receive_data(CliVcp* cli_vcp) { uint8_t buf[USB_CDC_PKT_LEN]; size_t length = furi_hal_cdc_receive(VCP_IF_NUM, buf, sizeof(buf)); - VCP_LOG_T(TAG, "cdc_receive length=%zu", length); + FURI_LOG_T(TAG, "cdc_receive length=%zu", length); furi_check(furi_pipe_send(cli_vcp->own_pipe, buf, length, 0) == length); } @@ -145,7 +139,7 @@ static void cli_vcp_data_from_shell(FuriEventLoopObject* object, void* context) static void cli_vcp_shell_ready(FuriEventLoopObject* object, void* context) { UNUSED(object); CliVcp* cli_vcp = context; - VCP_LOG_T(TAG, "shell_ready"); + FURI_LOG_T(TAG, "shell_ready"); cli_vcp_maybe_receive_data(cli_vcp); } @@ -191,13 +185,13 @@ static void cli_vcp_internal_message_received(FuriEventLoopObject* object, void* switch(message) { case CliVcpInternalMessageTxDone: - VCP_LOG_T(TAG, "TxDone"); + FURI_LOG_T(TAG, "TxDone"); cli_vcp->is_currently_transmitting = false; cli_vcp_maybe_send_data(cli_vcp); break; case CliVcpInternalMessageRx: - VCP_LOG_T(TAG, "Rx"); + FURI_LOG_T(TAG, "Rx"); cli_vcp_maybe_receive_data(cli_vcp); break; diff --git a/furi/core/core_defines.h b/furi/core/core_defines.h index fa56150ce15..2d6ced02556 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/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 61e182e7c6b..5b1bc02d93f 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -3,7 +3,6 @@ Version,+,80.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_vcp.h,, Header,+,applications/services/dialogs/dialogs.h,, Header,+,applications/services/dolphin/dolphin.h,, @@ -850,13 +849,6 @@ 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_ansi_parser_alloc,CliAnsiParser*, -Function,-,cli_ansi_parser_feed,CliAnsiParserResult,"CliAnsiParser*, char" -Function,-,cli_ansi_parser_free,void,CliAnsiParser* -Function,+,cli_app_should_stop,_Bool,FuriPipeSide* -Function,+,cli_delete_command,void,"Cli*, const char*" -Function,+,cli_print_usage,void,"const char*, const char*, const char*" Function,+,cli_vcp_disable,void,CliVcp* Function,+,cli_vcp_enable,void,CliVcp* Function,+,composite_api_resolver_add,void,"CompositeApiResolver*, const ElfApiInterface*" From 824b64412209c029701e8cafc115196033654d7f Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Tue, 24 Dec 2024 13:42:00 +0400 Subject: [PATCH 33/47] Merge remote-tracking branch 'origin/dev' into portasynthinca3/3928-cli-threads --- .github/workflows/build.yml | 1 + .github/workflows/build_compact.yml | 1 + .github/workflows/docs.yml | 1 + .github/workflows/merge_report.yml | 1 + .github/workflows/pvs_studio.yml | 1 + .github/workflows/unit_tests.yml | 48 +- .github/workflows/updater_test.yml | 54 +- .../debug/speaker_debug/speaker_debug.c | 3 +- applications/debug/unit_tests/application.fam | 8 + applications/debug/unit_tests/test_runner.c | 5 +- applications/debug/unit_tests/test_runner.h | 3 +- .../flipper_format/flipper_format_test.c | 1 + .../tests/furi/furi_event_loop_test.c | 90 +- .../tests/furi/furi_primitives_test.c | 54 - .../debug/unit_tests/tests/pipe/pipe_test.c | 153 + applications/debug/unit_tests/unit_tests.c | 3 +- applications/main/ibutton/ibutton_cli.c | 11 +- applications/main/infrared/infrared_app.c | 33 + applications/main/infrared/infrared_app_i.h | 14 + applications/main/infrared/infrared_cli.c | 33 +- .../main/infrared/infrared_custom_event.h | 2 + .../resources/infrared/assets/audio.ir | 39 + .../main/infrared/scenes/infrared_scene_rpc.c | 43 + applications/main/lfrfid/lfrfid_cli.c | 17 +- .../main/nfc/helpers/mf_classic_key_cache.c | 2 +- .../main/nfc/helpers/mf_classic_key_cache.h | 2 +- .../protocol_support/mf_classic/mf_classic.c | 2 +- applications/main/nfc/nfc_cli.c | 5 +- .../nfc/plugins/supported_cards/clipper.c | 6 +- .../nfc/plugins/supported_cards/plantain.c | 16 +- .../nfc/plugins/supported_cards/skylanders.c | 49 +- .../resources/nfc/assets/mf_classic_dict.nfc | 3578 ++++++++++++----- .../nfc_scene_mf_classic_update_initial.c | 2 +- .../main/subghz/helpers/subghz_chat.c | 7 +- .../main/subghz/helpers/subghz_chat.h | 3 +- .../main/subghz/helpers/subghz_custom_event.h | 1 + .../main/subghz/scenes/subghz_scene_rpc.c | 37 + applications/main/subghz/subghz.c | 3 + applications/main/subghz/subghz_cli.c | 23 +- applications/services/bt/bt_cli.c | 13 +- applications/services/cli/cli.c | 7 +- applications/services/cli/cli.h | 7 +- applications/services/cli/cli_command_gpio.c | 9 +- applications/services/cli/cli_command_gpio.h | 3 +- applications/services/cli/cli_commands.c | 33 +- applications/services/cli/cli_shell.c | 54 +- applications/services/cli/cli_shell.h | 3 +- applications/services/cli/cli_vcp.c | 52 +- applications/services/crypto/crypto_cli.c | 15 +- applications/services/input/input.c | 3 +- applications/services/input/input_cli.c | 7 +- applications/services/loader/loader_cli.c | 3 +- applications/services/power/power_cli.c | 13 +- applications/services/rpc/rpc_app.c | 39 + applications/services/rpc/rpc_app.h | 8 + applications/services/rpc/rpc_cli.c | 12 +- applications/services/rpc/rpc_i.h | 3 +- applications/services/storage/storage_cli.c | 43 +- .../notification_settings_app.c | 54 +- applications/system/js_app/js_app.c | 7 +- .../packages/create-fz-app/pnpm-lock.yaml | 8 +- .../js_app/packages/fz-sdk/package.json | 2 +- .../js_app/packages/fz-sdk/pnpm-lock.yaml | 13 +- .../system/js_app/packages/fz-sdk/sdk.js | 16 +- applications/system/updater/cli/updater_cli.c | 3 +- .../L1_Happy_holidays_128x64/frame_0.png | Bin 0 -> 858 bytes .../L1_Happy_holidays_128x64/frame_1.png | Bin 0 -> 855 bytes .../L1_Happy_holidays_128x64/frame_10.png | Bin 0 -> 872 bytes .../L1_Happy_holidays_128x64/frame_11.png | Bin 0 -> 861 bytes .../L1_Happy_holidays_128x64/frame_12.png | Bin 0 -> 853 bytes .../L1_Happy_holidays_128x64/frame_2.png | Bin 0 -> 851 bytes .../L1_Happy_holidays_128x64/frame_3.png | Bin 0 -> 852 bytes .../L1_Happy_holidays_128x64/frame_4.png | Bin 0 -> 856 bytes .../L1_Happy_holidays_128x64/frame_5.png | Bin 0 -> 850 bytes .../L1_Happy_holidays_128x64/frame_6.png | Bin 0 -> 851 bytes .../L1_Happy_holidays_128x64/frame_7.png | Bin 0 -> 860 bytes .../L1_Happy_holidays_128x64/frame_8.png | Bin 0 -> 857 bytes .../L1_Happy_holidays_128x64/frame_9.png | Bin 0 -> 863 bytes .../L1_Happy_holidays_128x64/meta.txt | 23 + .../L1_Sleigh_ride_128x64/frame_0.png | Bin 0 -> 820 bytes .../L1_Sleigh_ride_128x64/frame_1.png | Bin 0 -> 881 bytes .../L1_Sleigh_ride_128x64/frame_10.png | Bin 0 -> 788 bytes .../L1_Sleigh_ride_128x64/frame_11.png | Bin 0 -> 816 bytes .../L1_Sleigh_ride_128x64/frame_12.png | Bin 0 -> 864 bytes .../L1_Sleigh_ride_128x64/frame_13.png | Bin 0 -> 798 bytes .../L1_Sleigh_ride_128x64/frame_14.png | Bin 0 -> 813 bytes .../L1_Sleigh_ride_128x64/frame_15.png | Bin 0 -> 879 bytes .../L1_Sleigh_ride_128x64/frame_16.png | Bin 0 -> 855 bytes .../L1_Sleigh_ride_128x64/frame_17.png | Bin 0 -> 772 bytes .../L1_Sleigh_ride_128x64/frame_18.png | Bin 0 -> 817 bytes .../L1_Sleigh_ride_128x64/frame_19.png | Bin 0 -> 867 bytes .../L1_Sleigh_ride_128x64/frame_2.png | Bin 0 -> 866 bytes .../L1_Sleigh_ride_128x64/frame_20.png | Bin 0 -> 809 bytes .../L1_Sleigh_ride_128x64/frame_21.png | Bin 0 -> 795 bytes .../L1_Sleigh_ride_128x64/frame_22.png | Bin 0 -> 870 bytes .../L1_Sleigh_ride_128x64/frame_23.png | Bin 0 -> 852 bytes .../L1_Sleigh_ride_128x64/frame_24.png | Bin 0 -> 805 bytes .../L1_Sleigh_ride_128x64/frame_25.png | Bin 0 -> 858 bytes .../L1_Sleigh_ride_128x64/frame_26.png | Bin 0 -> 830 bytes .../L1_Sleigh_ride_128x64/frame_27.png | Bin 0 -> 828 bytes .../L1_Sleigh_ride_128x64/frame_28.png | Bin 0 -> 585 bytes .../L1_Sleigh_ride_128x64/frame_29.png | Bin 0 -> 431 bytes .../L1_Sleigh_ride_128x64/frame_3.png | Bin 0 -> 812 bytes .../L1_Sleigh_ride_128x64/frame_30.png | Bin 0 -> 281 bytes .../L1_Sleigh_ride_128x64/frame_31.png | Bin 0 -> 270 bytes .../L1_Sleigh_ride_128x64/frame_32.png | Bin 0 -> 236 bytes .../L1_Sleigh_ride_128x64/frame_33.png | Bin 0 -> 485 bytes .../L1_Sleigh_ride_128x64/frame_34.png | Bin 0 -> 771 bytes .../L1_Sleigh_ride_128x64/frame_35.png | Bin 0 -> 887 bytes .../L1_Sleigh_ride_128x64/frame_36.png | Bin 0 -> 809 bytes .../L1_Sleigh_ride_128x64/frame_4.png | Bin 0 -> 890 bytes .../L1_Sleigh_ride_128x64/frame_5.png | Bin 0 -> 819 bytes .../L1_Sleigh_ride_128x64/frame_6.png | Bin 0 -> 799 bytes .../L1_Sleigh_ride_128x64/frame_7.png | Bin 0 -> 817 bytes .../L1_Sleigh_ride_128x64/frame_8.png | Bin 0 -> 875 bytes .../L1_Sleigh_ride_128x64/frame_9.png | Bin 0 -> 823 bytes .../external/L1_Sleigh_ride_128x64/meta.txt | 23 + assets/dolphin/external/manifest.txt | 20 +- assets/protobuf | 2 +- documentation/UnitTests.md | 2 +- documentation/doxygen/Doxyfile.cfg | 1 + firmware.scons | 2 + furi/core/event_loop.c | 60 +- furi/core/event_loop.h | 27 +- furi/core/event_loop_i.h | 9 +- furi/core/memmgr.c | 4 +- furi/core/pipe.c | 180 - furi/core/pipe.h | 192 - furi/furi.h | 1 - lib/ble_profile/extra_services/hid_service.c | 14 +- lib/flipper_application/elf/elf_file.c | 12 +- lib/flipper_format/flipper_format.c | 5 + lib/flipper_format/flipper_format.h | 8 + lib/nfc/protocols/mf_plus/mf_plus_poller.c | 4 +- .../parsers/iso15693/iso15693_parser.c | 2 + lib/subghz/protocols/bin_raw.c | 2 +- lib/toolbox/SConscript | 1 + lib/toolbox/pipe.c | 241 ++ lib/toolbox/pipe.h | 295 ++ scripts/get_env.py | 6 +- scripts/lint.py | 26 +- scripts/testops.py | 156 +- .../toolchain/windows-toolchain-download.ps1 | 13 +- targets/f18/api_symbols.csv | 37 +- targets/f7/api_symbols.csv | 31 +- targets/f7/ble_glue/furi_ble/gatt.c | 34 +- targets/f7/stm32wb55xx_flash.ld | 2 +- targets/f7/stm32wb55xx_ram_fw.ld | 2 +- 148 files changed, 4094 insertions(+), 2128 deletions(-) create mode 100644 applications/debug/unit_tests/tests/pipe/pipe_test.c create mode 100755 assets/dolphin/external/L1_Happy_holidays_128x64/frame_0.png create mode 100755 assets/dolphin/external/L1_Happy_holidays_128x64/frame_1.png create mode 100755 assets/dolphin/external/L1_Happy_holidays_128x64/frame_10.png create mode 100755 assets/dolphin/external/L1_Happy_holidays_128x64/frame_11.png create mode 100755 assets/dolphin/external/L1_Happy_holidays_128x64/frame_12.png create mode 100755 assets/dolphin/external/L1_Happy_holidays_128x64/frame_2.png create mode 100755 assets/dolphin/external/L1_Happy_holidays_128x64/frame_3.png create mode 100755 assets/dolphin/external/L1_Happy_holidays_128x64/frame_4.png create mode 100755 assets/dolphin/external/L1_Happy_holidays_128x64/frame_5.png create mode 100755 assets/dolphin/external/L1_Happy_holidays_128x64/frame_6.png create mode 100755 assets/dolphin/external/L1_Happy_holidays_128x64/frame_7.png create mode 100755 assets/dolphin/external/L1_Happy_holidays_128x64/frame_8.png create mode 100755 assets/dolphin/external/L1_Happy_holidays_128x64/frame_9.png create mode 100755 assets/dolphin/external/L1_Happy_holidays_128x64/meta.txt create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_0.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_1.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_10.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_11.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_12.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_13.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_14.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_15.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_16.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_17.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_18.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_19.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_2.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_20.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_21.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_22.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_23.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_24.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_25.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_26.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_27.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_28.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_29.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_3.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_30.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_31.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_32.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_33.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_34.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_35.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_36.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_4.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_5.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_6.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_7.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_8.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_9.png create mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/meta.txt delete mode 100644 furi/core/pipe.c delete mode 100644 furi/core/pipe.h create mode 100644 lib/toolbox/pipe.c create mode 100644 lib/toolbox/pipe.h diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2bc2178aea3..66a2bdf73ef 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,6 +12,7 @@ env: DEFAULT_TARGET: f7 FBT_TOOLCHAIN_PATH: /runner/_work FBT_GIT_SUBMODULE_SHALLOW: 1 + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} jobs: main: diff --git a/.github/workflows/build_compact.yml b/.github/workflows/build_compact.yml index f45275204a3..f98ab8b49a7 100644 --- a/.github/workflows/build_compact.yml +++ b/.github/workflows/build_compact.yml @@ -6,6 +6,7 @@ on: env: FBT_TOOLCHAIN_PATH: /runner/_work FBT_GIT_SUBMODULE_SHALLOW: 1 + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} jobs: compact: diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 1fa02508567..65af450cf30 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -9,6 +9,7 @@ on: env: TARGETS: f7 DEFAULT_TARGET: f7 + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} jobs: check-secret: diff --git a/.github/workflows/merge_report.yml b/.github/workflows/merge_report.yml index 90302ce1aa0..9ee7884c82c 100644 --- a/.github/workflows/merge_report.yml +++ b/.github/workflows/merge_report.yml @@ -7,6 +7,7 @@ on: env: FBT_TOOLCHAIN_PATH: /runner/_work + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} jobs: merge_report: diff --git a/.github/workflows/pvs_studio.yml b/.github/workflows/pvs_studio.yml index 8eb6fea482b..3f1a164bc94 100644 --- a/.github/workflows/pvs_studio.yml +++ b/.github/workflows/pvs_studio.yml @@ -11,6 +11,7 @@ env: DEFAULT_TARGET: f7 FBT_TOOLCHAIN_PATH: /runner/_work FBT_GIT_SUBMODULE_SHALLOW: 1 + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} jobs: analyse_c_cpp: diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index d37337452a9..309dd7ebd4b 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -5,54 +5,39 @@ on: env: TARGETS: f7 DEFAULT_TARGET: f7 - FBT_TOOLCHAIN_PATH: /opt + FBT_TOOLCHAIN_PATH: /opt/ FBT_GIT_SUBMODULE_SHALLOW: 1 jobs: run_units_on_bench: - runs-on: [self-hosted, FlipperZeroUnitTest] + runs-on: [ self-hosted, FlipperZeroTest ] steps: - - name: 'Wipe workspace' - run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; - - name: Checkout code uses: actions/checkout@v4 with: fetch-depth: 1 ref: ${{ github.event.pull_request.head.sha }} - - name: 'Get flipper from device manager (mock)' - id: device - run: | - echo "flipper=auto" >> $GITHUB_OUTPUT - - name: 'Flash unit tests firmware' id: flashing if: success() - timeout-minutes: 10 - run: | - ./fbt resources firmware_latest flash SWD_TRANSPORT_SERIAL=2A0906016415303030303032 LIB_DEBUG=1 FIRMWARE_APP_SET=unit_tests FORCE=1 - - - name: 'Wait for flipper and format ext' - id: format_ext - if: steps.flashing.outcome == 'success' - timeout-minutes: 5 + timeout-minutes: 20 run: | source scripts/toolchain/fbtenv.sh - python3 scripts/testops.py -p=${{steps.device.outputs.flipper}} -t=120 await_flipper - python3 scripts/storage.py -p ${{steps.device.outputs.flipper}} format_ext + ./fbt resources firmware_latest flash LIB_DEBUG=1 FIRMWARE_APP_SET=unit_tests FORCE=1 + - name: 'Copy assets and unit data, reboot and wait for flipper' id: copy - if: steps.format_ext.outcome == 'success' + if: steps.flashing.outcome == 'success' timeout-minutes: 7 run: | source scripts/toolchain/fbtenv.sh - python3 scripts/testops.py -p=${{steps.device.outputs.flipper}} -t=15 await_flipper - rm -rf build/latest/resources/dolphin - python3 scripts/storage.py -p ${{steps.device.outputs.flipper}} -f send build/latest/resources /ext - python3 scripts/power.py -p ${{steps.device.outputs.flipper}} reboot - python3 scripts/testops.py -p=${{steps.device.outputs.flipper}} -t=15 await_flipper + python3 scripts/testops.py -t=15 await_flipper + python3 scripts/storage.py -f send build/latest/resources /ext + python3 scripts/storage.py -f send /region_data /ext/.int/.region_data + python3 scripts/power.py reboot + python3 scripts/testops.py -t=30 await_flipper - name: 'Run units and validate results' id: run_units @@ -60,9 +45,16 @@ jobs: timeout-minutes: 7 run: | source scripts/toolchain/fbtenv.sh - python3 scripts/testops.py run_units -p ${{steps.device.outputs.flipper}} + python3 scripts/testops.py run_units + + - name: 'Upload test results' + if: failure() && steps.flashing.outcome == 'success' && steps.run_units.outcome != 'skipped' + uses: actions/upload-artifact@v4 + with: + name: unit-tests_output + path: unit_tests*.txt - name: 'Check GDB output' if: failure() && steps.flashing.outcome == 'success' run: | - ./fbt gdb_trace_all SWD_TRANSPORT_SERIAL=2A0906016415303030303032 LIB_DEBUG=1 FIRMWARE_APP_SET=unit_tests FORCE=1 + ./fbt gdb_trace_all LIB_DEBUG=1 FIRMWARE_APP_SET=unit_tests FORCE=1 diff --git a/.github/workflows/updater_test.yml b/.github/workflows/updater_test.yml index dbe5df883d6..59cc6e716be 100644 --- a/.github/workflows/updater_test.yml +++ b/.github/workflows/updater_test.yml @@ -1,39 +1,31 @@ name: 'Updater test' on: pull_request: + env: TARGETS: f7 DEFAULT_TARGET: f7 - FBT_TOOLCHAIN_PATH: /opt + FBT_TOOLCHAIN_PATH: /opt/ FBT_GIT_SUBMODULE_SHALLOW: 1 jobs: test_updater_on_bench: - runs-on: [self-hosted, FlipperZeroUpdaterTest] + runs-on: [self-hosted, FlipperZeroTest ] steps: - - name: 'Wipe workspace' - run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; - - name: Checkout code uses: actions/checkout@v4 with: fetch-depth: 1 - submodules: false ref: ${{ github.event.pull_request.head.sha }} - - name: 'Get flipper from device manager (mock)' - id: device - run: | - echo "flipper=auto" >> $GITHUB_OUTPUT - echo "stlink=0F020D026415303030303032" >> $GITHUB_OUTPUT - - name: 'Flashing target firmware' id: first_full_flash - timeout-minutes: 10 + timeout-minutes: 20 run: | source scripts/toolchain/fbtenv.sh - ./fbt flash_usb_full PORT=${{steps.device.outputs.flipper}} FORCE=1 - python3 scripts/testops.py -p=${{steps.device.outputs.flipper}} -t=180 await_flipper + python3 scripts/testops.py -t=180 await_flipper + ./fbt flash_usb_full FORCE=1 + - name: 'Validating updater' id: second_full_flash @@ -41,33 +33,7 @@ jobs: if: success() run: | source scripts/toolchain/fbtenv.sh - ./fbt flash_usb PORT=${{steps.device.outputs.flipper}} FORCE=1 - python3 scripts/testops.py -p=${{steps.device.outputs.flipper}} -t=180 await_flipper - - - name: 'Get last release tag' - id: release_tag - if: failure() - run: | - echo "tag=$(git tag -l --sort=-version:refname | grep -v "rc\|RC" | head -1)" >> $GITHUB_OUTPUT - - - name: 'Wipe workspace' - run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; + python3 scripts/testops.py -t=180 await_flipper + ./fbt flash_usb FORCE=1 + python3 scripts/testops.py -t=180 await_flipper - - name: 'Checkout latest release' - uses: actions/checkout@v4 - if: failure() - with: - fetch-depth: 1 - ref: ${{ steps.release_tag.outputs.tag }} - - - name: 'Flash last release' - if: failure() - run: | - ./fbt flash SWD_TRANSPORT_SERIAL=${{steps.device.outputs.stlink}} FORCE=1 - - - name: 'Wait for flipper and format ext' - if: failure() - run: | - source scripts/toolchain/fbtenv.sh - python3 scripts/testops.py -p=${{steps.device.outputs.flipper}} -t=180 await_flipper - python3 scripts/storage.py -p ${{steps.device.outputs.flipper}} format_ext diff --git a/applications/debug/speaker_debug/speaker_debug.c b/applications/debug/speaker_debug/speaker_debug.c index 81f91c93d73..527a2ce08cd 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,7 +38,7 @@ static void speaker_app_free(SpeakerDebugApp* app) { free(app); } -static void speaker_app_cli(FuriPipeSide* pipe, FuriString* args, void* context) { +static void speaker_app_cli(PipeSide* pipe, FuriString* args, void* context) { UNUSED(pipe); SpeakerDebugApp* app = (SpeakerDebugApp*)context; diff --git a/applications/debug/unit_tests/application.fam b/applications/debug/unit_tests/application.fam index dec3283e4e7..f92d7e66fb8 100644 --- a/applications/debug/unit_tests/application.fam +++ b/applications/debug/unit_tests/application.fam @@ -236,3 +236,11 @@ App( entry_point="get_api", requires=["unit_tests"], ) + +App( + appid="test_pipe", + sources=["tests/common/*.c", "tests/pipe/*.c"], + apptype=FlipperAppType.PLUGIN, + entry_point="get_api", + requires=["unit_tests"], +) diff --git a/applications/debug/unit_tests/test_runner.c b/applications/debug/unit_tests/test_runner.c index 0fddee6cb0b..3470e5ac752 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 - FuriPipeSide* pipe; + PipeSide* pipe; FuriString* args; // ELF related stuff @@ -38,7 +39,7 @@ struct TestRunner { int minunit_status; }; -TestRunner* test_runner_alloc(FuriPipeSide* pipe, FuriString* args) { +TestRunner* test_runner_alloc(PipeSide* pipe, FuriString* args) { TestRunner* instance = malloc(sizeof(TestRunner)); instance->storage = furi_record_open(RECORD_STORAGE); diff --git a/applications/debug/unit_tests/test_runner.h b/applications/debug/unit_tests/test_runner.h index d91dec83d4c..0e9495263d0 100644 --- a/applications/debug/unit_tests/test_runner.h +++ b/applications/debug/unit_tests/test_runner.h @@ -1,10 +1,11 @@ #pragma once #include +#include typedef struct TestRunner TestRunner; -TestRunner* test_runner_alloc(FuriPipeSide* pipe, FuriString* args); +TestRunner* test_runner_alloc(PipeSide* pipe, FuriString* args); void test_runner_free(TestRunner* instance); diff --git a/applications/debug/unit_tests/tests/flipper_format/flipper_format_test.c b/applications/debug/unit_tests/tests/flipper_format/flipper_format_test.c index 888a6644426..934634c7175 100644 --- a/applications/debug/unit_tests/tests/flipper_format/flipper_format_test.c +++ b/applications/debug/unit_tests/tests/flipper_format/flipper_format_test.c @@ -265,6 +265,7 @@ static bool test_write(const char* file_name) { if(!flipper_format_file_open_always(file, file_name)) break; if(!flipper_format_write_header_cstr(file, test_filetype, test_version)) break; if(!flipper_format_write_comment_cstr(file, "This is comment")) break; + if(!flipper_format_write_empty_line(file)) break; if(!flipper_format_write_string_cstr(file, test_string_key, test_string_data)) break; if(!flipper_format_write_int32(file, test_int_key, test_int_data, COUNT_OF(test_int_data))) break; diff --git a/applications/debug/unit_tests/tests/furi/furi_event_loop_test.c b/applications/debug/unit_tests/tests/furi/furi_event_loop_test.c index 493c6942ba5..73f38ab77f8 100644 --- a/applications/debug/unit_tests/tests/furi/furi_event_loop_test.c +++ b/applications/debug/unit_tests/tests/furi/furi_event_loop_test.c @@ -9,7 +9,7 @@ #define MESSAGE_COUNT (256UL) #define EVENT_FLAG_COUNT (23UL) -#define PRIMITIVE_COUNT (5UL) +#define PRIMITIVE_COUNT (4UL) #define RUN_COUNT (2UL) typedef struct { @@ -18,7 +18,6 @@ typedef struct { uint32_t stream_buffer_count; uint32_t event_flag_count; uint32_t semaphore_count; - uint32_t pipe_count; uint32_t primitives_tested; } TestFuriEventLoopThread; @@ -27,7 +26,6 @@ typedef struct { FuriStreamBuffer* stream_buffer; FuriEventFlag* event_flag; FuriSemaphore* semaphore; - FuriPipe pipe; TestFuriEventLoopThread producer; TestFuriEventLoopThread consumer; @@ -211,43 +209,6 @@ static void furi_delay_us(furi_hal_random_get() % 100); } -static void - test_furi_event_loop_producer_pipe_callback(FuriEventLoopObject* object, void* context) { - furi_check(context); - - TestFuriEventLoopData* data = context; - furi_check(data->pipe.alices_side == object); - - TestFuriEventLoopThread* producer = &data->producer; - TestFuriEventLoopThread* consumer = &data->consumer; - - FURI_LOG_I(TAG, "producer Pipe: %lu %lu", producer->pipe_count, consumer->pipe_count); - - if(producer->pipe_count == MESSAGE_COUNT / 2) { - furi_event_loop_unsubscribe(producer->event_loop, data->pipe.alices_side); - furi_event_loop_subscribe_pipe( - producer->event_loop, - data->pipe.alices_side, - FuriEventLoopEventOut, - test_furi_event_loop_producer_pipe_callback, - data); - - } else if(producer->pipe_count == MESSAGE_COUNT) { - furi_event_loop_unsubscribe(producer->event_loop, data->pipe.alices_side); - furi_event_loop_pend_callback( - producer->event_loop, test_furi_event_loop_pending_callback, producer); - return; - } - - producer->pipe_count++; - - furi_check( - furi_pipe_send(data->pipe.alices_side, &producer->pipe_count, sizeof(uint32_t), 0) == - sizeof(uint32_t)); - - furi_delay_us(furi_hal_random_get() % 100); -} - static int32_t test_furi_event_loop_producer(void* p) { furi_check(p); @@ -283,12 +244,6 @@ static int32_t test_furi_event_loop_producer(void* p) { FuriEventLoopEventOut, test_furi_event_loop_producer_semaphore_callback, data); - furi_event_loop_subscribe_pipe( - producer->event_loop, - data->pipe.alices_side, - FuriEventLoopEventOut, - test_furi_event_loop_producer_pipe_callback, - data); test_furi_event_loop_thread_run_and_cleanup(producer); } @@ -447,40 +402,6 @@ static void data->consumer.semaphore_count++; } -static void - test_furi_event_loop_consumer_pipe_callback(FuriEventLoopObject* object, void* context) { - furi_check(context); - - TestFuriEventLoopData* data = context; - furi_check(data->pipe.bobs_side == object); - - TestFuriEventLoopThread* producer = &data->producer; - TestFuriEventLoopThread* consumer = &data->consumer; - - furi_delay_us(furi_hal_random_get() % 100); - - furi_check( - furi_pipe_receive(data->pipe.bobs_side, &consumer->pipe_count, sizeof(uint32_t), 0) == - sizeof(uint32_t)); - - FURI_LOG_I(TAG, "consumer Pipe: %lu %lu", producer->pipe_count, consumer->pipe_count); - - if(consumer->pipe_count == MESSAGE_COUNT / 2) { - furi_event_loop_unsubscribe(consumer->event_loop, data->pipe.bobs_side); - furi_event_loop_subscribe_pipe( - consumer->event_loop, - data->pipe.bobs_side, - FuriEventLoopEventIn, - test_furi_event_loop_consumer_pipe_callback, - data); - - } else if(consumer->pipe_count == MESSAGE_COUNT) { - furi_event_loop_unsubscribe(data->consumer.event_loop, data->pipe.bobs_side); - furi_event_loop_pend_callback( - consumer->event_loop, test_furi_event_loop_pending_callback, consumer); - } -} - static int32_t test_furi_event_loop_consumer(void* p) { furi_check(p); @@ -516,12 +437,6 @@ static int32_t test_furi_event_loop_consumer(void* p) { FuriEventLoopEventIn, test_furi_event_loop_consumer_semaphore_callback, data); - furi_event_loop_subscribe_pipe( - consumer->event_loop, - data->pipe.bobs_side, - FuriEventLoopEventIn, - test_furi_event_loop_consumer_pipe_callback, - data); test_furi_event_loop_thread_run_and_cleanup(consumer); } @@ -538,7 +453,6 @@ void test_furi_event_loop(void) { data.stream_buffer = furi_stream_buffer_alloc(16, sizeof(uint32_t)); data.event_flag = furi_event_flag_alloc(); data.semaphore = furi_semaphore_alloc(8, 0); - data.pipe = furi_pipe_alloc(16, sizeof(uint32_t)); FuriThread* producer_thread = furi_thread_alloc_ex("producer_thread", 1 * 1024, test_furi_event_loop_producer, &data); @@ -573,6 +487,4 @@ void test_furi_event_loop(void) { furi_stream_buffer_free(data.stream_buffer); furi_event_flag_free(data.event_flag); furi_semaphore_free(data.semaphore); - furi_pipe_free(data.pipe.alices_side); - furi_pipe_free(data.pipe.bobs_side); } diff --git a/applications/debug/unit_tests/tests/furi/furi_primitives_test.c b/applications/debug/unit_tests/tests/furi/furi_primitives_test.c index 2a0a4df5874..d9ad0303955 100644 --- a/applications/debug/unit_tests/tests/furi/furi_primitives_test.c +++ b/applications/debug/unit_tests/tests/furi/furi_primitives_test.c @@ -7,13 +7,9 @@ #define STREAM_BUFFER_SIZE (32U) #define STREAM_BUFFER_TRG_LEVEL (STREAM_BUFFER_SIZE / 2U) -#define PIPE_SIZE (128U) -#define PIPE_TRG_LEVEL (1U) - typedef struct { FuriMessageQueue* message_queue; FuriStreamBuffer* stream_buffer; - FuriPipe pipe; } TestFuriPrimitivesData; static void test_furi_message_queue(TestFuriPrimitivesData* data) { @@ -91,67 +87,17 @@ static void test_furi_stream_buffer(TestFuriPrimitivesData* data) { } } -static void test_furi_pipe(TestFuriPrimitivesData* data) { - FuriPipeSide* alice = data->pipe.alices_side; - FuriPipeSide* bob = data->pipe.bobs_side; - - mu_assert_int_eq(FuriPipeRoleAlice, furi_pipe_role(alice)); - mu_assert_int_eq(FuriPipeRoleBob, furi_pipe_role(bob)); - mu_assert_int_eq(FuriPipeStateOpen, furi_pipe_state(alice)); - mu_assert_int_eq(FuriPipeStateOpen, furi_pipe_state(bob)); - - mu_assert_int_eq(PIPE_SIZE, furi_pipe_spaces_available(alice)); - mu_assert_int_eq(PIPE_SIZE, furi_pipe_spaces_available(bob)); - mu_assert_int_eq(0, furi_pipe_bytes_available(alice)); - mu_assert_int_eq(0, furi_pipe_bytes_available(bob)); - - for(uint8_t i = 0;; ++i) { - mu_assert_int_eq(PIPE_SIZE - i, furi_pipe_spaces_available(alice)); - mu_assert_int_eq(i, furi_pipe_bytes_available(bob)); - - if(furi_pipe_send(alice, &i, sizeof(uint8_t), 0) != sizeof(uint8_t)) { - break; - } - - mu_assert_int_eq(PIPE_SIZE - i, furi_pipe_spaces_available(bob)); - mu_assert_int_eq(i, furi_pipe_bytes_available(alice)); - - if(furi_pipe_send(bob, &i, sizeof(uint8_t), 0) != sizeof(uint8_t)) { - break; - } - } - - furi_pipe_free(alice); - mu_assert_int_eq(FuriPipeStateBroken, furi_pipe_state(bob)); - - for(uint8_t i = 0;; ++i) { - mu_assert_int_eq(PIPE_SIZE - i, furi_pipe_bytes_available(bob)); - - uint8_t value; - if(furi_pipe_receive(bob, &value, sizeof(uint8_t), 0) != sizeof(uint8_t)) { - break; - } - - mu_assert_int_eq(i, value); - } - - furi_pipe_free(bob); -} - // This is a stub that needs expanding void test_furi_primitives(void) { TestFuriPrimitivesData data = { .message_queue = furi_message_queue_alloc(MESSAGE_QUEUE_CAPACITY, MESSAGE_QUEUE_ELEMENT_SIZE), .stream_buffer = furi_stream_buffer_alloc(STREAM_BUFFER_SIZE, STREAM_BUFFER_TRG_LEVEL), - .pipe = furi_pipe_alloc(PIPE_SIZE, PIPE_TRG_LEVEL), }; test_furi_message_queue(&data); test_furi_stream_buffer(&data); - test_furi_pipe(&data); furi_message_queue_free(data.message_queue); furi_stream_buffer_free(data.stream_buffer); - // the pipe is freed by the test } diff --git a/applications/debug/unit_tests/tests/pipe/pipe_test.c b/applications/debug/unit_tests/tests/pipe/pipe_test.c new file mode 100644 index 00000000000..d440a04eecb --- /dev/null +++ b/applications/debug/unit_tests/tests/pipe/pipe_test.c @@ -0,0 +1,153 @@ +#include "../test.h" // IWYU pragma: keep + +#include +#include + +#define PIPE_SIZE 128U +#define PIPE_TRG_LEVEL 1U + +MU_TEST(pipe_test_trivial) { + PipeSideBundle bundle = pipe_alloc(PIPE_SIZE, PIPE_TRG_LEVEL); + PipeSide* alice = bundle.alices_side; + PipeSide* bob = bundle.bobs_side; + + mu_assert_int_eq(PipeRoleAlice, pipe_role(alice)); + mu_assert_int_eq(PipeRoleBob, pipe_role(bob)); + mu_assert_int_eq(PipeStateOpen, pipe_state(alice)); + mu_assert_int_eq(PipeStateOpen, pipe_state(bob)); + + mu_assert_int_eq(PIPE_SIZE, pipe_spaces_available(alice)); + mu_assert_int_eq(PIPE_SIZE, pipe_spaces_available(bob)); + mu_assert_int_eq(0, pipe_bytes_available(alice)); + mu_assert_int_eq(0, pipe_bytes_available(bob)); + + for(uint8_t i = 0;; ++i) { + 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; + } + + 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; + } + } + + pipe_free(alice); + mu_assert_int_eq(PipeStateBroken, pipe_state(bob)); + + for(uint8_t i = 0;; ++i) { + mu_assert_int_eq(PIPE_SIZE - i, pipe_bytes_available(bob)); + + uint8_t value; + if(pipe_receive(bob, &value, sizeof(uint8_t), 0) != sizeof(uint8_t)) { + break; + } + + mu_assert_int_eq(i, value); + } + + pipe_free(bob); +} + +typedef enum { + TestFlagDataArrived = 1 << 0, + TestFlagSpaceFreed = 1 << 1, + TestFlagBecameBroken = 1 << 2, +} TestFlag; + +typedef struct { + TestFlag flag; + FuriEventLoop* event_loop; +} AncillaryThreadContext; + +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); +} + +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); +} + +static void on_became_broken(PipeSide* pipe, void* context) { + UNUSED(pipe); + AncillaryThreadContext* ctx = context; + ctx->flag |= TestFlagBecameBroken; + furi_event_loop_stop(ctx->event_loop); +} + +static int32_t ancillary_thread(void* context) { + PipeSide* pipe = context; + AncillaryThreadContext thread_ctx = { + .flag = 0, + .event_loop = furi_event_loop_alloc(), + }; + + pipe_attach_to_event_loop(pipe, thread_ctx.event_loop); + pipe_set_callback_context(pipe, &thread_ctx); + pipe_set_data_arrived_callback(pipe, on_data_arrived, 0); + pipe_set_space_freed_callback(pipe, on_space_freed, FuriEventLoopEventFlagEdge); + pipe_set_broken_callback(pipe, on_became_broken, 0); + + furi_event_loop_run(thread_ctx.event_loop); + + pipe_detach_from_event_loop(pipe); + pipe_free(pipe); + furi_event_loop_free(thread_ctx.event_loop); + return thread_ctx.flag; +} + +MU_TEST(pipe_test_event_loop) { + PipeSideBundle bundle = pipe_alloc(PIPE_SIZE, PIPE_TRG_LEVEL); + PipeSide* alice = bundle.alices_side; + PipeSide* bob = bundle.bobs_side; + + FuriThread* thread = furi_thread_alloc_ex("PipeTestAnc", 2048, ancillary_thread, bob); + furi_thread_start(thread); + + const char* message = "Hello!"; + pipe_send(alice, message, strlen(message), FuriWaitForever); + + char buffer_1[16]; + size_t size = pipe_receive(alice, buffer_1, sizeof(buffer_1), FuriWaitForever); + buffer_1[size] = 0; + + char buffer_2[16]; + const char* expected_reply = "Hi!"; + size = pipe_receive(alice, buffer_2, sizeof(buffer_2), FuriWaitForever); + buffer_2[size] = 0; + + pipe_free(alice); + furi_thread_join(thread); + + mu_assert_string_eq(message, buffer_1); + mu_assert_string_eq(expected_reply, buffer_2); + mu_assert_int_eq( + TestFlagDataArrived | TestFlagSpaceFreed | TestFlagBecameBroken, + furi_thread_get_return_code(thread)); + + furi_thread_free(thread); +} + +MU_TEST_SUITE(test_pipe) { + MU_RUN_TEST(pipe_test_trivial); + MU_RUN_TEST(pipe_test_event_loop); +} + +int run_minunit_test_pipe(void) { + MU_RUN_SUITE(test_pipe); + return MU_EXIT_CODE; +} + +TEST_API_DEFINE(run_minunit_test_pipe) diff --git a/applications/debug/unit_tests/unit_tests.c b/applications/debug/unit_tests/unit_tests.c index 457530054a7..22aea82e241 100644 --- a/applications/debug/unit_tests/unit_tests.c +++ b/applications/debug/unit_tests/unit_tests.c @@ -1,9 +1,10 @@ #include #include +#include #include "test_runner.h" -void unit_tests_cli(FuriPipeSide* pipe, FuriString* args, void* context) { +void unit_tests_cli(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); TestRunner* test_runner = test_runner_alloc(pipe, args); diff --git a/applications/main/ibutton/ibutton_cli.c b/applications/main/ibutton/ibutton_cli.c index 5d748459ad7..d72c83ea74c 100644 --- a/applications/main/ibutton/ibutton_cli.c +++ b/applications/main/ibutton/ibutton_cli.c @@ -3,12 +3,13 @@ #include #include +#include #include #include #include -static void ibutton_cli(FuriPipeSide* pipe, FuriString* args, void* context); +static void ibutton_cli(PipeSide* pipe, FuriString* args, void* context); // app cli function void ibutton_on_system_start(void) { @@ -92,7 +93,7 @@ static void ibutton_cli_worker_read_cb(void* context) { furi_event_flag_set(event, EVENT_FLAG_IBUTTON_COMPLETE); } -static void ibutton_cli_read(FuriPipeSide* pipe) { +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)); @@ -138,7 +139,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(FuriPipeSide* pipe, 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)); @@ -195,7 +196,7 @@ void ibutton_cli_write(FuriPipeSide* pipe, FuriString* args) { furi_event_flag_free(write_context.event); } -void ibutton_cli_emulate(FuriPipeSide* pipe, 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)); @@ -228,7 +229,7 @@ void ibutton_cli_emulate(FuriPipeSide* pipe, FuriString* args) { ibutton_protocols_free(protocols); } -void ibutton_cli(FuriPipeSide* pipe, FuriString* args, void* context) { +void ibutton_cli(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); FuriString* cmd; cmd = furi_string_alloc(); diff --git a/applications/main/infrared/infrared_app.c b/applications/main/infrared/infrared_app.c index a93fd766df3..c50039760c3 100644 --- a/applications/main/infrared/infrared_app.c +++ b/applications/main/infrared/infrared_app.c @@ -88,6 +88,19 @@ static void infrared_rpc_command_callback(const RpcAppSystemEvent* event, void* view_dispatcher_send_custom_event( infrared->view_dispatcher, InfraredCustomEventTypeRpcButtonPressIndex); } + } else if(event->type == RpcAppEventTypeButtonPressRelease) { + furi_assert( + event->data.type == RpcAppSystemEventDataTypeString || + event->data.type == RpcAppSystemEventDataTypeInt32); + if(event->data.type == RpcAppSystemEventDataTypeString) { + furi_string_set(infrared->button_name, event->data.string); + view_dispatcher_send_custom_event( + infrared->view_dispatcher, InfraredCustomEventTypeRpcButtonPressReleaseName); + } else { + infrared->app_state.current_button_index = event->data.i32; + view_dispatcher_send_custom_event( + infrared->view_dispatcher, InfraredCustomEventTypeRpcButtonPressReleaseIndex); + } } else if(event->type == RpcAppEventTypeButtonRelease) { view_dispatcher_send_custom_event( infrared->view_dispatcher, InfraredCustomEventTypeRpcButtonRelease); @@ -411,6 +424,26 @@ void infrared_tx_stop(InfraredApp* infrared) { infrared->app_state.last_transmit_time = furi_get_tick(); } +void infrared_tx_send_once(InfraredApp* infrared) { + if(infrared->app_state.is_transmitting) { + return; + } + + dolphin_deed(DolphinDeedIrSend); + infrared_signal_transmit(infrared->current_signal); +} + +InfraredErrorCode infrared_tx_send_once_button_index(InfraredApp* infrared, size_t button_index) { + furi_assert(button_index < infrared_remote_get_signal_count(infrared->remote)); + + InfraredErrorCode error = infrared_remote_load_signal( + infrared->remote, infrared->current_signal, infrared->app_state.current_button_index); + if(!INFRARED_ERROR_PRESENT(error)) { + infrared_tx_send_once(infrared); + } + + return error; +} void infrared_blocking_task_start(InfraredApp* infrared, FuriThreadCallback callback) { view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewLoading); furi_thread_set_callback(infrared->task_thread, callback); diff --git a/applications/main/infrared/infrared_app_i.h b/applications/main/infrared/infrared_app_i.h index 692cc967157..75d8502f26e 100644 --- a/applications/main/infrared/infrared_app_i.h +++ b/applications/main/infrared/infrared_app_i.h @@ -218,6 +218,20 @@ InfraredErrorCode infrared_tx_start_button_index(InfraredApp* infrared, size_t b */ void infrared_tx_stop(InfraredApp* infrared); +/** + * @brief Transmit the currently loaded signal once. + * + * @param[in,out] infrared pointer to the application instance. + */ +void infrared_tx_send_once(InfraredApp* infrared); + +/** + * @brief Load the signal under the given index and transmit it once. + * + * @param[in,out] infrared pointer to the application instance. + */ +InfraredErrorCode infrared_tx_send_once_button_index(InfraredApp* infrared, size_t button_index); + /** * @brief Start a blocking task in a separate thread. * diff --git a/applications/main/infrared/infrared_cli.c b/applications/main/infrared/infrared_cli.c index e6bee100560..ab34c12682f 100644 --- a/applications/main/infrared/infrared_cli.c +++ b/applications/main/infrared/infrared_cli.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include "infrared_signal.h" @@ -19,14 +20,14 @@ DICT_DEF2(dict_signals, FuriString*, FURI_STRING_OPLIST, int, M_DEFAULT_OPLIST) -static void infrared_cli_start_ir_rx(FuriPipeSide* pipe, FuriString* args); -static void infrared_cli_start_ir_tx(FuriPipeSide* pipe, FuriString* args); -static void infrared_cli_process_decode(FuriPipeSide* pipe, FuriString* args); -static void infrared_cli_process_universal(FuriPipeSide* pipe, 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)(FuriPipeSide* pipe, 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 +39,7 @@ static void signal_received_callback(void* context, InfraredWorkerSignal* receiv furi_assert(received_signal); char buf[100]; size_t buf_cnt; - FuriPipeSide* pipe = (FuriPipeSide*)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 +53,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" : ""); - furi_pipe_send(pipe, buf, buf_cnt, FuriWaitForever); + pipe_send(pipe, buf, buf_cnt, FuriWaitForever); } 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); - furi_pipe_send(pipe, buf, buf_cnt, FuriWaitForever); + pipe_send(pipe, buf, buf_cnt, FuriWaitForever); for(size_t i = 0; i < timings_cnt; ++i) { buf_cnt = snprintf(buf, sizeof(buf), "%lu ", timings[i]); - furi_pipe_send(pipe, buf, buf_cnt, FuriWaitForever); + pipe_send(pipe, buf, buf_cnt, FuriWaitForever); } buf_cnt = snprintf(buf, sizeof(buf), "\r\n"); - furi_pipe_send(pipe, buf, buf_cnt, FuriWaitForever); + pipe_send(pipe, buf, buf_cnt, FuriWaitForever); } } @@ -124,7 +125,7 @@ static void infrared_cli_print_usage(void) { infrared_cli_print_universal_remotes(); } -static void infrared_cli_start_ir_rx(FuriPipeSide* pipe, FuriString* args) { +static void infrared_cli_start_ir_rx(PipeSide* pipe, FuriString* args) { bool enable_decoding = true; if(!furi_string_empty(args)) { @@ -212,7 +213,7 @@ static bool infrared_cli_parse_raw(const char* str, InfraredSignal* signal) { return infrared_signal_is_valid(signal); } -static void infrared_cli_start_ir_tx(FuriPipeSide* pipe, FuriString* args) { +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(); @@ -333,7 +334,7 @@ static bool infrared_cli_decode_file(FlipperFormat* input_file, FlipperFormat* o return ret; } -static void infrared_cli_process_decode(FuriPipeSide* pipe, FuriString* args) { +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); @@ -454,7 +455,7 @@ static void infrared_cli_list_remote_signals(FuriString* remote_name) { } static void infrared_cli_brute_force_signals( - FuriPipeSide* pipe, + PipeSide* pipe, FuriString* remote_name, FuriString* signal_name) { InfraredBruteForce* brute_force = infrared_brute_force_alloc(); @@ -505,7 +506,7 @@ static void infrared_cli_brute_force_signals( infrared_brute_force_free(brute_force); } -static void infrared_cli_process_universal(FuriPipeSide* pipe, FuriString* args) { +static void infrared_cli_process_universal(PipeSide* pipe, FuriString* args) { FuriString* arg1 = furi_string_alloc(); FuriString* arg2 = furi_string_alloc(); @@ -527,7 +528,7 @@ static void infrared_cli_process_universal(FuriPipeSide* pipe, FuriString* args) furi_string_free(arg2); } -static void infrared_cli_start_ir(FuriPipeSide* pipe, 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."); diff --git a/applications/main/infrared/infrared_custom_event.h b/applications/main/infrared/infrared_custom_event.h index 02d9a276f17..2efc99f4b50 100644 --- a/applications/main/infrared/infrared_custom_event.h +++ b/applications/main/infrared/infrared_custom_event.h @@ -21,6 +21,8 @@ enum InfraredCustomEventType { InfraredCustomEventTypeRpcButtonPressName, InfraredCustomEventTypeRpcButtonPressIndex, InfraredCustomEventTypeRpcButtonRelease, + InfraredCustomEventTypeRpcButtonPressReleaseName, + InfraredCustomEventTypeRpcButtonPressReleaseIndex, InfraredCustomEventTypeRpcSessionClose, InfraredCustomEventTypeGpioTxPinChanged, diff --git a/applications/main/infrared/resources/infrared/assets/audio.ir b/applications/main/infrared/resources/infrared/assets/audio.ir index 20070bbe06e..efd0382e3af 100644 --- a/applications/main/infrared/resources/infrared/assets/audio.ir +++ b/applications/main/infrared/resources/infrared/assets/audio.ir @@ -4650,3 +4650,42 @@ type: parsed protocol: NECext address: 7F 01 00 00 command: 69 96 00 00 +# +# Model : NAD DR2 remote for NAD D7050 and D3020 +# +name: Power +type: parsed +protocol: NECext +address: 87 7C 00 00 +command: 25 DA 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 87 7C 00 00 +command: 88 77 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 87 7C 00 00 +command: 8C 73 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 87 7C 00 00 +command: 94 6B 00 00 +# +name: Next +type: parsed +protocol: NECext +address: 87 7C 00 00 +command: 1A E5 00 00 +# +name: Prev +type: parsed +protocol: NECext +address: 87 7C 00 00 +command: 1D E2 00 00 +# diff --git a/applications/main/infrared/scenes/infrared_scene_rpc.c b/applications/main/infrared/scenes/infrared_scene_rpc.c index 8f9dc433881..35cd971d800 100644 --- a/applications/main/infrared/scenes/infrared_scene_rpc.c +++ b/applications/main/infrared/scenes/infrared_scene_rpc.c @@ -124,6 +124,49 @@ bool infrared_scene_rpc_on_event(void* context, SceneManagerEvent event) { rpc_system_app_confirm(infrared->rpc_ctx, result); + } else if( + event.event == InfraredCustomEventTypeRpcButtonPressReleaseName || + event.event == InfraredCustomEventTypeRpcButtonPressReleaseIndex) { + bool result = false; + + // Send the signal once and stop + if(rpc_state == InfraredRpcStateLoaded) { + if(event.event == InfraredCustomEventTypeRpcButtonPressReleaseName) { + const char* button_name = furi_string_get_cstr(infrared->button_name); + size_t index; + const bool index_found = + infrared_remote_get_signal_index(infrared->remote, button_name, &index); + app_state->current_button_index = index_found ? (signed)index : + InfraredButtonIndexNone; + FURI_LOG_D(TAG, "Sending signal with name \"%s\"", button_name); + } else { + FURI_LOG_D( + TAG, "Sending signal with index \"%ld\"", app_state->current_button_index); + } + if(infrared->app_state.current_button_index != InfraredButtonIndexNone) { + InfraredErrorCode error = infrared_tx_send_once_button_index( + infrared, app_state->current_button_index); + if(!INFRARED_ERROR_PRESENT(error)) { + const char* remote_name = infrared_remote_get_name(infrared->remote); + infrared_text_store_set(infrared, 0, "emulating\n%s", remote_name); + + infrared_scene_rpc_show(infrared); + result = true; + } else { + rpc_system_app_set_error_code( + infrared->rpc_ctx, RpcAppSystemErrorCodeInternalParse); + rpc_system_app_set_error_text( + infrared->rpc_ctx, "Cannot load button data"); + result = false; + } + } + } + + if(result) { + scene_manager_set_scene_state( + infrared->scene_manager, InfraredSceneRpc, InfraredRpcStateLoaded); + } + rpc_system_app_confirm(infrared->rpc_ctx, result); } else if( event.event == InfraredCustomEventTypeRpcExit || event.event == InfraredCustomEventTypeRpcSessionClose || diff --git a/applications/main/lfrfid/lfrfid_cli.c b/applications/main/lfrfid/lfrfid_cli.c index c4330a5316f..f3d381b4698 100644 --- a/applications/main/lfrfid/lfrfid_cli.c +++ b/applications/main/lfrfid/lfrfid_cli.c @@ -6,6 +6,7 @@ #include #include #include +#include #include @@ -14,7 +15,7 @@ #include #include -static void lfrfid_cli(FuriPipeSide* pipe, FuriString* args, void* context); +static void lfrfid_cli(PipeSide* pipe, FuriString* args, void* context); // app cli function void lfrfid_on_system_start(void) { @@ -49,7 +50,7 @@ static void lfrfid_cli_read_callback(LFRFIDWorkerReadResult result, ProtocolId p furi_event_flag_set(context->event, 1 << result); } -static void lfrfid_cli_read(FuriPipeSide* pipe, FuriString* args) { +static void lfrfid_cli_read(PipeSide* pipe, FuriString* args) { FuriString* type_string; type_string = furi_string_alloc(); LFRFIDWorkerReadType type = LFRFIDWorkerReadTypeAuto; @@ -192,7 +193,7 @@ static void lfrfid_cli_write_callback(LFRFIDWorkerWriteResult result, void* ctx) furi_event_flag_set(events, 1 << result); } -static void lfrfid_cli_write(FuriPipeSide* pipe, FuriString* args) { +static void lfrfid_cli_write(PipeSide* pipe, FuriString* args) { ProtocolDict* dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax); ProtocolId protocol; @@ -239,7 +240,7 @@ static void lfrfid_cli_write(FuriPipeSide* pipe, FuriString* args) { furi_event_flag_free(event); } -static void lfrfid_cli_emulate(FuriPipeSide* pipe, FuriString* args) { +static void lfrfid_cli_emulate(PipeSide* pipe, FuriString* args) { ProtocolDict* dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax); ProtocolId protocol; @@ -265,7 +266,7 @@ static void lfrfid_cli_emulate(FuriPipeSide* pipe, FuriString* args) { protocol_dict_free(dict); } -static void lfrfid_cli_raw_analyze(FuriPipeSide* pipe, FuriString* args) { +static void lfrfid_cli_raw_analyze(PipeSide* pipe, FuriString* args) { UNUSED(pipe); FuriString *filepath, *info_string; filepath = furi_string_alloc(); @@ -392,7 +393,7 @@ static void lfrfid_cli_raw_read_callback(LFRFIDWorkerReadRawResult result, void* furi_event_flag_set(event, 1 << result); } -static void lfrfid_cli_raw_read(FuriPipeSide* pipe, FuriString* args) { +static void lfrfid_cli_raw_read(PipeSide* pipe, FuriString* args) { FuriString *filepath, *type_string; filepath = furi_string_alloc(); type_string = furi_string_alloc(); @@ -477,7 +478,7 @@ static void lfrfid_cli_raw_emulate_callback(LFRFIDWorkerEmulateRawResult result, furi_event_flag_set(event, 1 << result); } -static void lfrfid_cli_raw_emulate(FuriPipeSide* pipe, FuriString* args) { +static void lfrfid_cli_raw_emulate(PipeSide* pipe, FuriString* args) { FuriString* filepath; filepath = furi_string_alloc(); Storage* storage = furi_record_open(RECORD_STORAGE); @@ -544,7 +545,7 @@ static void lfrfid_cli_raw_emulate(FuriPipeSide* pipe, FuriString* args) { furi_string_free(filepath); } -static void lfrfid_cli(FuriPipeSide* pipe, FuriString* args, void* context) { +static void lfrfid_cli(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); FuriString* cmd; cmd = furi_string_alloc(); diff --git a/applications/main/nfc/helpers/mf_classic_key_cache.c b/applications/main/nfc/helpers/mf_classic_key_cache.c index 1b945660c0a..763c4dea7e3 100644 --- a/applications/main/nfc/helpers/mf_classic_key_cache.c +++ b/applications/main/nfc/helpers/mf_classic_key_cache.c @@ -166,7 +166,7 @@ void mf_classic_key_cache_load_from_data(MfClassicKeyCache* instance, const MfCl } } -bool mf_classic_key_cahce_get_next_key( +bool mf_classic_key_cache_get_next_key( MfClassicKeyCache* instance, uint8_t* sector_num, MfClassicKey* key, diff --git a/applications/main/nfc/helpers/mf_classic_key_cache.h b/applications/main/nfc/helpers/mf_classic_key_cache.h index b09f4526bab..50a1f5c30f6 100644 --- a/applications/main/nfc/helpers/mf_classic_key_cache.h +++ b/applications/main/nfc/helpers/mf_classic_key_cache.h @@ -16,7 +16,7 @@ bool mf_classic_key_cache_load(MfClassicKeyCache* instance, const uint8_t* uid, void mf_classic_key_cache_load_from_data(MfClassicKeyCache* instance, const MfClassicData* data); -bool mf_classic_key_cahce_get_next_key( +bool mf_classic_key_cache_get_next_key( MfClassicKeyCache* instance, uint8_t* sector_num, MfClassicKey* key, diff --git a/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c b/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c index 4fece16be5a..6f7be7f4c45 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c +++ b/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c @@ -72,7 +72,7 @@ static NfcCommand nfc_scene_read_poller_callback_mf_classic(NfcGenericEvent even uint8_t sector_num = 0; MfClassicKey key = {}; MfClassicKeyType key_type = MfClassicKeyTypeA; - if(mf_classic_key_cahce_get_next_key( + if(mf_classic_key_cache_get_next_key( instance->mfc_key_cache, §or_num, &key, &key_type)) { mfc_event->data->read_sector_request_data.sector_num = sector_num; mfc_event->data->read_sector_request_data.key = key; diff --git a/applications/main/nfc/nfc_cli.c b/applications/main/nfc/nfc_cli.c index 3a817578ac0..5d0681462b2 100644 --- a/applications/main/nfc/nfc_cli.c +++ b/applications/main/nfc/nfc_cli.c @@ -3,6 +3,7 @@ #include #include #include +#include #include @@ -17,7 +18,7 @@ static void nfc_cli_print_usage(void) { } } -static void nfc_cli_field(FuriPipeSide* pipe, 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) { @@ -40,7 +41,7 @@ static void nfc_cli_field(FuriPipeSide* pipe, FuriString* args) { furi_hal_nfc_release(); } -static void nfc_cli(FuriPipeSide* pipe, FuriString* args, void* context) { +static void nfc_cli(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); FuriString* cmd; cmd = furi_string_alloc(); diff --git a/applications/main/nfc/plugins/supported_cards/clipper.c b/applications/main/nfc/plugins/supported_cards/clipper.c index 3eba82425d1..eb459d41951 100644 --- a/applications/main/nfc/plugins/supported_cards/clipper.c +++ b/applications/main/nfc/plugins/supported_cards/clipper.c @@ -101,7 +101,8 @@ static const IdMapping bart_zones[] = { {.id = 0x001d, .name = "Lake Merrit"}, {.id = 0x001e, .name = "Fruitvale"}, {.id = 0x001f, .name = "Coliseum"}, - {.id = 0x0021, .name = "San Leandro"}, + {.id = 0x0020, .name = "San Leandro"}, + {.id = 0x0021, .name = "Bay Fair"}, {.id = 0x0022, .name = "Hayward"}, {.id = 0x0023, .name = "South Hayward"}, {.id = 0x0024, .name = "Union City"}, @@ -131,6 +132,9 @@ static const IdMapping muni_zones[] = { {.id = 0x000b, .name = "Castro"}, {.id = 0x000c, .name = "Forest Hill"}, // Guessed {.id = 0x000d, .name = "West Portal"}, + {.id = 0x0019, .name = "Union Square/Market Street"}, + {.id = 0x001a, .name = "Chinatown - Rose Pak"}, + {.id = 0x001b, .name = "Yerba Buena/Moscone"}, }; static const size_t kNumMUNIZones = COUNT(muni_zones); diff --git a/applications/main/nfc/plugins/supported_cards/plantain.c b/applications/main/nfc/plugins/supported_cards/plantain.c index 9f2491691be..49bbaebe8ec 100644 --- a/applications/main/nfc/plugins/supported_cards/plantain.c +++ b/applications/main/nfc/plugins/supported_cards/plantain.c @@ -310,9 +310,11 @@ static bool plantain_parse(const NfcDevice* device, FuriString* parsed_data) { last_payment_date.year, last_payment_date.hour, last_payment_date.minute); - //payment amount. This needs to be investigated more, currently it shows incorrect amount on some cards. - uint16_t last_payment = (data->block[18].data[9] << 8) | data->block[18].data[8]; - furi_string_cat_printf(parsed_data, "Amount: %d rub", last_payment / 100); + //Last payment amount. + uint16_t last_payment = ((data->block[18].data[10] << 16) | + (data->block[18].data[9] << 8) | (data->block[18].data[8])) / + 100; + furi_string_cat_printf(parsed_data, "Amount: %d rub", last_payment); furi_string_free(card_number_s); furi_string_free(tmp_s); //This is for 4K Plantains. @@ -369,9 +371,11 @@ static bool plantain_parse(const NfcDevice* device, FuriString* parsed_data) { last_payment_date.year, last_payment_date.hour, last_payment_date.minute); - //payment amount - uint16_t last_payment = (data->block[18].data[9] << 8) | data->block[18].data[8]; - furi_string_cat_printf(parsed_data, "Amount: %d rub", last_payment / 100); + //Last payment amount + uint16_t last_payment = ((data->block[18].data[10] << 16) | + (data->block[18].data[9] << 8) | (data->block[18].data[8])) / + 100; + furi_string_cat_printf(parsed_data, "Amount: %d rub", last_payment); furi_string_free(card_number_s); furi_string_free(tmp_s); } diff --git a/applications/main/nfc/plugins/supported_cards/skylanders.c b/applications/main/nfc/plugins/supported_cards/skylanders.c index 6c199f11462..b5dc0ab86e0 100644 --- a/applications/main/nfc/plugins/supported_cards/skylanders.c +++ b/applications/main/nfc/plugins/supported_cards/skylanders.c @@ -7,13 +7,36 @@ #include #include -#define TAG "Skylanders" +#define TAG "Skylanders" +#define POLY UINT64_C(0x42f0e1eba9ea3693) +#define TOP UINT64_C(0x800000000000) +#define UID_LEN 4 +#define KEY_MASK 0xFFFFFFFFFFFF static const uint64_t skylanders_key = 0x4b0b20107ccb; static const char* nfc_resources_header = "Flipper NFC resources"; static const uint32_t nfc_resources_file_version = 1; +uint64_t crc64_like(uint64_t result, uint8_t sector) { + result ^= (uint64_t)sector << 40; + for(int i = 0; i < 8; i++) { + result = (result & TOP) ? (result << 1) ^ POLY : result << 1; + } + return result; +} + +uint64_t taghash(uint32_t uid) { + uint64_t result = 0x9AE903260CC4; + uint8_t uidBytes[UID_LEN] = {0}; + memcpy(uidBytes, &uid, UID_LEN); + + for(int i = 0; i < UID_LEN; i++) { + result = crc64_like(result, uidBytes[i]); + } + return result; +} + static bool skylanders_search_data( Storage* storage, const char* file_name, @@ -88,6 +111,12 @@ static bool skylanders_read(Nfc* nfc, NfcDevice* device) { MfClassicData* data = mf_classic_alloc(); nfc_device_copy_data(device, NfcProtocolMfClassic, data); + size_t* uid_len = 0; + const uint8_t* uid_bytes = mf_classic_get_uid(data, uid_len); + uint32_t uid = 0; + memcpy(&uid, uid_bytes, sizeof(uid)); + uint64_t hash = taghash(uid); + do { MfClassicType type = MfClassicType1k; MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type); @@ -96,10 +125,18 @@ static bool skylanders_read(Nfc* nfc, NfcDevice* device) { data->type = type; MfClassicDeviceKeys keys = {}; for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { - bit_lib_num_to_bytes_be(skylanders_key, sizeof(MfClassicKey), keys.key_a[i].data); - FURI_BIT_SET(keys.key_a_mask, i); - bit_lib_num_to_bytes_be(skylanders_key, sizeof(MfClassicKey), keys.key_b[i].data); - FURI_BIT_SET(keys.key_b_mask, i); + if(i == 0) { + bit_lib_num_to_bytes_be(skylanders_key, sizeof(MfClassicKey), keys.key_a[i].data); + FURI_BIT_SET(keys.key_a_mask, i); + } else { + uint64_t sectorhash = crc64_like(hash, i); + uint64_t key = sectorhash & KEY_MASK; + uint8_t* keyBytes = (uint8_t*)&key; + memcpy(keys.key_a[i].data, keyBytes, sizeof(MfClassicKey)); + FURI_BIT_SET(keys.key_a_mask, i); + memset(keys.key_b[i].data, 0, sizeof(MfClassicKey)); + FURI_BIT_SET(keys.key_b_mask, i); + } } error = mf_classic_poller_sync_read(nfc, &keys, data); @@ -134,7 +171,7 @@ static bool skylanders_parse(const NfcDevice* device, FuriString* parsed_data) { uint64_t key = bit_lib_bytes_to_num_be(sec_tr->key_a.data, 6); if(key != skylanders_key) break; - const uint16_t id = (uint16_t)*data->block[1].data; + const uint16_t id = data->block[1].data[1] << 8 | data->block[1].data[0]; if(id == 0) break; Storage* storage = furi_record_open(RECORD_STORAGE); diff --git a/applications/main/nfc/resources/nfc/assets/mf_classic_dict.nfc b/applications/main/nfc/resources/nfc/assets/mf_classic_dict.nfc index e2b74f8475d..0a3de18af2c 100644 --- a/applications/main/nfc/resources/nfc/assets/mf_classic_dict.nfc +++ b/applications/main/nfc/resources/nfc/assets/mf_classic_dict.nfc @@ -1,538 +1,1010 @@ -# Key dictionary from https://github.com/ikarus23/MifareClassicTool.git - -# More well known keys! -# Standard keys +# Key dictionary from https://github.com/RfidResearchGroup/proxmark3/ +# +# Mifare Default Keys +# -- iceman fork version -- +# -- contribute to this list, sharing is caring -- +# +# Default key FFFFFFFFFFFF -A0A1A2A3A4A5 -D3F7D3F7D3F7 +# +# Blank key 000000000000 - -# Keys from mfoc +# +# NFC Forum MADkey +A0A1A2A3A4A5 +# +# MAD access key A (reversed) +A5A4A3A2A1A0 +# +# MAD access key B +89ECA97F8C2A +# +# Mifare 1k EV1 (S50) hidden blocks, Signature data +# 16 A +5C8FF9990DA2 +# +# 17 A +75CCB59C9BED +# +# 16 B +D01AFEEB890A +# +# 17 B +4B791BEA7BCC +# +# QL88 keys +# 17 A/B +2612C6DE84CA +707B11FC1481 +# +# QL88 diversifed +03F9067646AE +2352C5B56D85 +# B0B1B2B3B4B5 +C0C1C2C3C4C5 +D0D1D2D3D4D5 +AABBCCDDEEFF 4D3A99C351DD 1A982C7E459A -AABBCCDDEEFF +# +# key A Wien +D3F7D3F7D3F7 +# +# key B Wien +5A1B85FCE20A +# +# 714C5C886E97 587EE5F9350F A0478CC39091 533CB6C723F6 8FD0A4F256E9 - -# Keys from: -# http://pastebin.com/wcTHXLZZ -A64598A77478 -26940B21FF5D +# +# iCopy-X +E00000000000 +# +# +E7D6064C5860 +B27CCAB30DBD +# +# lib / Nat Bieb +D2ECE8B9395E +# NSCP default key +1494E81663D7 +# +# NFC tools +7c9fb8474242 +# +# Kiev keys +569369C5A0E5 +632193BE1C3C +644672BD4AFE +8FE644038790 +9DE89E070277 +B5FF67CBA951 +EFF603E1EFE9 +F14EE7CAE863 +# +# ICT S14 A/B +9C28A60F7249 +C9826AF02794 +# +# RKF +# Västtrafiken KeyA, RKF ÖstgötaTrafiken KeyA FC00018778F7 +# +# Västtrafiken KeyA +0297927C0F77 +54726176656C +# +# Västtrafiken KeyB 00000FFE2488 +776974687573 +EE0042F88840 +# +# RKF SLKeyA +26940B21FF5D +A64598A77478 +# +# RKF SLKeyB 5C598C9C58B5 E4D2770A89BE - -# Keys from: -# http://pastebin.com/svGjN30Q -434F4D4D4F41 -434F4D4D4F42 -47524F555041 -47524F555042 -505249564141 -505249564142 - -# Keys from: -# http://pastebin.com/d7sSetef -0297927C0F77 -EE0042F88840 +# +# RKF Rejskort Danmark KeyA 722BFCC5375F +# +# RKF Rejskort Danmark KeyB F1D83F964314 - -# Keys from: -# http://pastebin.com/pvJX0xVS -54726176656C -776974687573 -4AF9D7ADEBE4 -2BA9621E0A36 - -# Keys from: -# http://pastebin.com/y3PDBWR1 +# +# RKF JOJOPRIVA KeyA +505249564141 +# +# RKF JOJOPRIVA KeyB +505249564142 +# +# RKF JOJOGROUP KeyA +47524F555041 +434F4D4D4F41 +# +# RKF JOJOGROUP KeyB +47524F555042 +434F4D4D4F42 +# +# TNP3xxx +4B0B20107CCB +# +# Access control system +605F5E5D5C5B +# +# NSP Global keys A and B (uk housing access control) +199404281970 +199404281998 +# +# Data from http://www.proxmark.org/forum/viewtopic.php?pid=25925#p25925 +# Tengo Cards Key A +FFF011223358 +FF9F11223358 +# +# Elevator system Kherson, Ukraine +AC37E76385F5 +576DCFFF2F25 +# +# Car wash system +1EE38419EF39 +26578719DCD9 +# +# more Keys from mfc_default_keys.lua 000000000001 +000000000002 +00000000000A +00000000000B +010203040506 +0123456789AB +100000000000 +111111111111 123456789ABC -B127C6F41436 12F2EE3478C1 -34D1DF9934C5 -55F5A5DD38C9 -F1A97341A9FC -33F974B42769 14D446E33363 -C934FE34D934 1999A3554A55 +200000000000 +222222222222 27DD91F1FCF1 -A94133013401 -99C636334433 +# +# Hotel system +505209016A1F +# +# Directory and eventlog KeyB +2BA9621E0A36 +# +# Directory and eventlog KeyA +4AF9D7ADEBE4 +# +# +333333333333 +33F974B42769 +34D1DF9934C5 43AB19EF5C31 -A053A292A4AF +444444444444 505249565441 505249565442 - -# Keys from: -# http://pastebin.com/TUXj17K3 -FC0001877BF7 - -# Keys from: -# http://0x9000.blogspot.com/2010/12/mifare-classic-default-keys.html -A0B0C0D0E0F0 -A1B1C1D1E1F1 - -# Keys from: -# https://code.google.com/p/mifare-key-cracker/downloads/list -BD493A3962B6 -010203040506 -111111111111 -222222222222 -333333333333 -444444444444 555555555555 +55F5A5DD38C9 666666666666 777777777777 888888888888 999999999999 +99C636334433 +A00000000000 +A053A292A4AF +A94133013401 AAAAAAAAAAAA +# +# Key from ladyada.net +ABCDEF123456 +# +# +B00000000000 +B127C6F41436 BBBBBBBBBBBB +BD493A3962B6 +C934FE34D934 CCCCCCCCCCCC DDDDDDDDDDDD EEEEEEEEEEEE -0123456789AB - -# Keys from: -# https://github.com/4ZM/mfterm/blob/master/dictionary.txt -000000000002 -00000000000A -00000000000B -100000000000 -200000000000 -A00000000000 -B00000000000 - -# Key from: -# ladyada.net -ABCDEF123456 - -# Key from: -# http://irq5.io/2013/04/13/decoding-bcard-conference-badges/ -F4A9EF2AFC6D - -# Keys from: -# https://github.com/iceman1001/proxmark -4B0B20107CCB -569369C5A0E5 -632193BE1C3C -644672BD4AFE -8FE644038790 -9DE89E070277 -B5FF67CBA951 -EFF603E1EFE9 -F14EE7CAE863 +# +# elevator +# data from forum +FFFFFF545846 +# +# +F1A97341A9FC +# +# hotel system 44AB09010845 85FED980EA5A -314B49474956 -564C505F4D41 -0263DE1278F3 -067DB45454A9 -15FC4C7613FE -16F21A82EC84 -16F3D5AB1139 -17758856B182 +# +# ARD (fr) key A +43454952534E +# ARD (fr) key B +4A2B29111213 +# +# +4143414F5250 +# +# Tehran Railway +A9B43414F585 +1FB235AC1388 +# +# Data from http://irq5.io/2013/04/13/decoding-bcard-conference-badges/ +# BCARD KeyB +F4A9EF2AFC6D +# +# +# S0 B +89EAC97F8C2A +# +# S4 A +43C7600DEE6B +# +# S6 A +0120BF672A64 +# +# S6 B +FB0B20DF1F34 +# +# +A9F953DEF0A3 +# +# Data from forum +74A386AD0A6D +3F7A5C2DBD81 +21EDF95E7433 +C121FF19F681 +3D5D9996359A +# +# Here be BIP keys... +3A42F33AF429 1FC235AC1309 -22C1BAE1AACD +6338A371C0ED 243F160918D1 -25094DF6F148 -2A3C347A1200 -324F5DF65310 +F124C2578AD0 +9AFC42372AF1 32AC3B90AC13 +682D401ABB09 +4AD1E273EAF1 +067DB45454A9 +E2C42591368A +15FC4C7613FE +2A3C347A1200 +68D30288910A +16F3D5AB1139 +F59A36A2546D +937A4FFF3011 +64E3C10394C2 35C3D2CAEE88 -3A42F33AF429 +B736412614AF +693143F10368 +324F5DF65310 +A3F97428DD01 +643FB6DE2217 +63F17A449AF0 +82F435DEDF01 +C4652C54261C +0263DE1278F3 +D49E2826664F +51284C3686A6 3DF14C8000A1 -3E3554AF0E12 +6A470D54127C +# +# Data from http://pastebin.com/AK9Bftpw +# Länstrafiken i Västerbotten +48FFE71294A0 +E3429281EFC1 +16F21A82EC84 +460722122510 +# +# 3dprinter +# EPI Envisionte +AAFB06045877 +# +# Gyms / Fitness Clubs / Health Clubs / Wellness Centres +# +# Fysiken A 3E65E4FB65B3 +# +# Fysiken B +25094DF6F148 +# +# +# https://mattionline.de/fitnessstudio-armband-reverse-engineering/ +# https://mattionline.de/milazycracker/ +# Gym Wristband A - Same as Fysiken A +# Gym Wristband B +81CC25EBBB6A +195DC63DB3A3 +# +# CleverFit +A05DBD98E0FC +# +# GoFit +AA4DDA458EBB +EAB8066C7479 +# +# Nordic Wellness A - Same as Fysiken A +# Nordic Wellness B +E5519E1CC92B +# +# Jett's 24 Hour Fitness S0 KA/B +# 049979614077 +# 829338771705 +# +# Hotel KeyCard +D3B595E9DD63 +AFBECD121004 +# +# SimonsVoss +6471A5EF2D1A +# +# ID06 +4E3552426B32 +22BDACF5A33F +6E7747394E63 +763958704B78 +# +# Onity S1 A/B +8A19D40CF2B5 +# +3961EA82C46D +# +# 24-7 +D21762B2DE3B +0E83A374B513 +1F1FFE000000 +A10F303FC879 +1322285230B8 +0C71BCFB7E72 +C3C88C6340B8 +F101622750B7 +1F107328DC8D +710732200D34 +7C335FB121B5 +B39AE17435DC +# +# key A 454841585443 -460722122510 -48FFE71294A0 -491CDCFB7752 -4AD1E273EAF1 -4B791BEA7BCC -51284C3686A6 +# +# Lift system +190819842023 +# +# Data from http://pastebin.com/gQ6nk38G +D39BB83F5297 +85675B200017 528C9DFFE28C -5EB8F884C8D1 +C82EC29E3235 +3E3554AF0E12 +491CDCFB7752 +22C1BAE1AACD 5F146716E373 -6338A371C0ED -63F17A449AF0 -643FB6DE2217 -64E3C10394C2 -682D401ABB09 -68D30288910A -693143F10368 -6A470D54127C 740E9A4F9AAF -75CCB59C9BED -75D8690F21B6 -75EDE6A84460 -82F435DEDF01 -85675B200017 -871B8C085997 -937A4FFF3011 +AC0E24C75527 97184D136233 -97D1101F18B0 -9AFC42372AF1 -A27D3804C259 -A3F97428DD01 +E444D53D359F +17758856B182 A8966C7CC54B -A9F953DEF0A3 -AAFB06045877 -AC0E24C75527 -AE3FF4EEA0DB -B0C9DD55DD4D -B736412614AF -C4652C54261C C6AD00254562 -C82EC29E3235 -D39BB83F5297 -D49E2826664F -DF27A8F1CB8E -E2C42591368A -E3429281EFC1 -E444D53D359F -F124C2578AD0 -F59A36A2546D +AE3FF4EEA0DB +5EB8F884C8D1 FEE470A4CB58 -0000000018DE -0000014B5C31 -003003003003 -003CC420001A -013889343891 -01FA3FC68349 -021209197591 -050908080008 -0A7932DC7E65 -0C669993C776 -0C71BCFB7E72 +75D8690F21B6 +871B8C085997 +97D1101F18B0 +75EDE6A84460 +DF27A8F1CB8E +B0C9DD55DD4D +# +# Data from http://bit.ly/1bdSbJl +A0B0C0D0E0F0 +A1B1C1D1E1F1 +# +# Data from msk social +2735FC181807 +2ABA9519F574 +84FD7F7A12B6 +186D8C4B93F9 +3A4BBA8ADAF0 +8765B17968A2 +40EAD80721CE +0DB5E6523F7C +51119DAE5216 +83E3549CE42D +136BDB246CAC +7DE02A7F6025 +BF23A53C1F63 +CB9A1F2D7368 +C7C0ADB3284F +9F131D8C2057 +67362D90F973 +6202A38F69E2 +100533B89331 +653A87594079 +D8A274B2E026 +B20B83CB145C +9AFA6CB4FC3D +A229E68AD9E5 +49C2B5296EF4 +# +# Data from http://pastebin.com/RRJUEDCM 0D258FE90296 -0E83A374B513 -0F230695923F -0FFBF65B5A14 -11428B5BCE06 -11428B5BCE07 -11428B5BCE08 -11428B5BCE09 -11428B5BCE0A -11428B5BCE0F -11496F97752A -123F8888F322 -1322285230B8 -1565A172770F -157B10D84C6B -157C9A513FA5 -15CAFD6159F6 -160A91D29A9C -16551D52FD20 -167A1BE102E0 -16DDCB6B3F24 -1717E34A7A8A +E55A3CA71826 +A4F204203F56 +EEB420209D0C +911E52FD7CE4 +752FBB5B7B45 +66B03ACA6EE9 +48734389EDC3 17193709ADF4 -185FA3438949 -1877ED29435A -18971D893494 -1AB23CD45EF6 1ACC3189578C -1F107328DC8D -1F1A0A111B5B -1F1FFE000000 -2031D1E57A3B -# HID Key B -204752454154 -21A600056CB0 -22729A9BD40F -2338B4913111 -2548A443DF28 -25D60050BF6E -26643965B16E +C2B7EC7D4EB1 +369A4663ACD2 +# +# Data from https://github.com/zhangjingye03/zxcardumper +# zxcard Key A/B +668770666644 +003003003003 +# +# Data from http://phreakerclub.com/forum/showthread.php?p=41266 26973EA74321 -27FBC86A00D0 -2A2C13CC242A -2A6D9205E7CA -2CB1A90071C8 -2DD39A54E1F3 -2ED3B15E7C0F -2EF720F2AF76 -2FC1F32F51B1 +71F3A315AD26 +51044EFB5AAB +AC70CA327A04 +EB0A8FF88ADE +# +# Transport system Metromoney +2803BCB0C7E1 +9C616585E26D +4FA9EB49F75E +2DADE48942C5 +A160FCD5EC4C +112233445566 +361A62F35BC9 +# +# Transport system Spain +83F3CB98C258 +070D486BC555 +A9B018868CC1 +9DCDB136110C +749934CC8ED3 +506DB955F161 +F088A85E71D7 +72B458D60363 +70C714869DC7 +B32464412EE3 +F253C30568C4 +1C68315674AC +CFE63749080A +C1E6F8AFC9EC +DD0DE3BA08A6 +3D923EB73534 +FF94F86B09A6 +D61707FFDFB1 +8223205047B6 +9951A273DEE7 +C9449301AF93 +66695A45C9FA +89AA9D743812 +C41514DEFC07 +C52876869800 +5353B3AECB53 +2E4169A5C79D +4BB747E48C2A +6285A1C8EB5C +5145C34DBA19 +25352912CD8D +81B20C274C3F +00B70875AF1D +04B787B2F3A5 +05412723F1B6 +05C301C8795A +066F5AF3CCEE +0A1B6C50E04E +0AD0956DF6EE +0AD6B7E37183 +0F3A4D48757B +1417E5671417 +18AB07270506 +18E887D625B4 +1ABC15934F5A +1AF66F83F5BE +260480290483 +2900AAC52BC3 +2910AFE15C99 +374521A38BCC +3A4C47757B07 +3A524B7A7B37 +3C4ABB877EAF +3F3A534B7B7B +4B787B273A50 +4B92DF1BF25D +4F0E4AE8051A +514B797B2F3A +529CF51F05C5 +52B26C199862 +57A18BFEC381 +5A7D87876EA8 +64CBADC7A313 +65B6C3200736 +67B1B3A4E497 +6B0454D5D3C3 +6B3B7AF45777 +6C273F431564 +702C1BF025DD +738385948494 +76E450094393 +777B1F3A4F4A +7B173A4E4976 +81504133B13C +826576A1AB68 +8A55194F6587 +8DFACF11E778 +8FD6D76742DC +9AFEE1F65742 +9D56D83658AC +9FAC23197904 +A1AB3A08712C +A514B797B373 +A58AB5619631 +A5BB18152EF1 +A777B233A4F4 +AB19BC885A29 +AB91BDA25F00 +AE98BA1E6F2C +B133A4D48757 +B3A4C47757B0 +B6803136F5AF +B793ADA6DB0C +B95BFDEBA7E4 +C0AA2BBD27CD +C27F5C1A9C2B +C9BE49675FE4 +CCCE24102003 +CDE668FDCDBA +D23A31A4AAB9 +DEDD7688BC38 +E9AE90885C39 +F0A3C5182007 +F3A524B7A7B3 +# +# Data from mall +# playland balikesir +ABBA1234FCB0 +# +# A trio bowling bahcelievler +314F495254FF +4152414B4E41 +# +# karinca park nigde +4E474434FFFF +# +# hotel system +537930363139 +# +# Data from https://github.com/RadioWar/NFCGUI +44DD5A385AAF +21A600056CB0 +B1ACA33180A5 +DD61EB6BCE22 +1565A172770F +3E84D2612E2A +F23442436765 +79674F96C771 +87DF99D496CB +C5132C8980BC +A21680C27773 +F26E21EDCEE2 +675557ECC92E +F4396E468114 +6DB17C16B35B +4186562A5BB2 2FEAE851C199 -3060206F5B0A -31646241686C -321958042333 +DB1A3338B2EB +157B10D84C6B +A643F952EA57 +DF37DCB6AFB3 +4C32BAF326E0 +91CE16C07AC5 +3C5D1C2BCD18 +C3F19EC592A2 +F72A29005459 +185FA3438949 321A695BD266 -340E40F81CD8 -345547514B4D -356D46474348 -369A4663ACD2 -36ABF5874ED7 -374BF468607F +D327083A60A7 +45635EF66EF3 +5481986D2D62 +CBA6AE869AD5 +645A166B1EEB +A7ABBC77CC9E +F792C4C76A5C +BFB6796A11DB +# +# Data from Salto A/B +6A1987C40A21 +7F33625BC129 +6BE9314930D8 +# +# Data from forum +2338B4913111 +# +# Data from stoye +CB779C50E1BD +A27D3804C259 +003CC420001A +F9861526130F 381ECE050FBD -386B4D634A65 -38FCF33072E0 -3A09594C8587 -3B7E4FD575AD -3C5D1C2BCD18 -3E84D2612E2A -3FA7217EC575 -410B9B40B872 +A57186BDD2B9 +48C739E21A04 +36ABF5874ED7 +649D2ABBBD20 +BBE8FFFCF363 +AB4E7045E97D +340E40F81CD8 +E4F65C0EF32C +D2A597D76936 +A920F32FE93A +86AFD95200F7 +9B832A9881FF +26643965B16E +0C669993C776 +B468D1991AF9 +D9A37831DCE5 +2FC1F32F51B1 +0FFBF65B5A14 +C5CFE06D9EA3 +C0DECE673829 +# +# +A56C2DF9A26D +# +# Data from https://pastebin.com/vbwast74 +68D3F7307C89 +# +# Smart Rider. Western Australian Public Transport Cards +568C9083F71C +117E5C165B10 +24BB421C7973 +3E3A546650EA +41F262D3AB66 +514956AB3142 +863933AE8388 +# +# Bangkok metro key +97F5DA640B18 +# +# Metro Valencia key +A8844B0BCA06 +# +# HTC Eindhoven key +857464D3AAD1 +# +# Vigik Keys +# Various sources : +# * https://github.com/DumpDos/Vigik +# * http://newffr.com/viewtopic.php?&forum=235&topic=11559 +# * Own dumps +# +# French VIGIK +# VIGIK1 A +314B49474956 +# +# VIGIK1 B +564C505F4D41 +BA5B895DA162 +# +# BTCINO UNDETERMINED SPREAKD 0x01->0x13 key +021209197591 +# +# +2EF720F2AF76 414C41524F4E -415A54454B4D -4186562A5BB2 424C41524F4E -425A73484166 -436A46587552 -447AB7FD5A6B -44DD5A385AAF -44F0B5FBE344 -45635EF66EF3 -476242304C53 -484558414354 -# HID Key A -484944204953 -484A57696F4A -48734389EDC3 -48C739E21A04 -49FAE4E3849F 4A6352684677 -4C32BAF326E0 -4C6B69723461 -4C961F23E6BE -4D3248735131 -4D5076656D58 -4E32336C6E38 -4E4175623670 -4F9F59C9C875 -509359F131B1 -51044EFB5AAB -5106CA7E4A69 -513C85D06CDE -52264716EFDE +BF1F4424AF76 536653644C65 -53C11F90822A -543B01B27A95 -5481986D2D62 -5544564E6E67 -564777315276 -568C9083F71C -57734F6F6974 -57784A533069 -584F66326877 -5A1B85FCE20A -5EC39B022F2B -623055724556 -62387B8D250D -6245E47352E6 -62CED42A6D87 +# +# Intratone Cogelec +# Data from http://bouzdeck.com/rfid/32-cloning-a-mifare-classic-1k-tag.html +484558414354 +A22AE129C013 +49FAE4E3849F +38FCF33072E0 +8AD5517B4B18 +509359F131B1 +6C78928E1317 +AA0720018738 +A6CAC2886412 62D0C424ED8E -62EFD80AB715 -645A166B1EEB -649D2ABBBD20 -666E564F4A44 -668770666644 -66B03ACA6EE9 +E64A986A5D94 +8FA1D601D0A2 +89347350BD36 66D2B7DC39EF -66F3ED00FED7 -67546972BC69 -675557ECC92E -686A736A356E -68D3F7307C89 -69FB7B7CD8EE -6A1987C40A21 -6A676C315142 -6A696B646631 -6B6579737472 6BC1E1AE547D -6C78928E1317 -6C94E1CED026 -6D44B5AAF464 +22729A9BD40F +# +# Data from https://dfir.lu/blog/cloning-a-mifare-classic-1k-tag.html +925B158F796F +FAD63ECB5891 +BBA840BA1C57 +CC6B3B3CD263 +6245E47352E6 +8ED41E8B8056 +2DD39A54E1F3 6D4C5B3658D2 -6D4E334B6C48 -6DB17C16B35B -6F4B6D644178 -6F506F493353 +1877ED29435A +52264716EFDE +961C0DB4A7ED 703140FD6D86 -70564650584F -710732200D34 -71F3A315AD26 -744E326B3441 -752FBB5B7B45 -756EF55E2507 -77494C526339 -77646B633657 +157C9A513FA5 +E2A5DC8E066F +# +# Data from forum, schlage 9691T fob +EF1232AB18A0 +# +# Data from a oyster card +374BF468607F +BFC8E353AF63 +15CAFD6159F6 +62EFD80AB715 +987A7F7F1A35 +C4104FA3C526 +4C961F23E6BE +67546972BC69 +F4CD5D4C13FF +94414C1A07DC +16551D52FD20 +9CB290282F7D 77A84170B574 -79674F96C771 +ED646C83A4F3 +E703589DB50B +513C85D06CDE +95093F0B2E22 +543B01B27A95 +C6D375B99972 +EE4CC572B40E +5106CA7E4A69 +C96BD1CE607F +167A1BE102E0 +A8D0D850A606 +A2ABB693CE34 7B296C40C486 +91F93A5564C9 +E10623E7A016 +B725F9CBF183 +# +# Data from FDi tag +8829DA9DAF76 +# +# Data from GitHub issue +0A7932DC7E65 +11428B5BCE06 +11428B5BCE07 +11428B5BCE08 +11428B5BCE09 +11428B5BCE0A +11428B5BCE0F +18971D893494 +25D60050BF6E +44F0B5FBE344 7B296F353C6B -7C335FB121B5 -7F33625BC129 8553263F4FF0 -8697389ACA26 -86AFD95200F7 -87DF99D496CB -8829DA9DAF76 -89347350BD36 -8AD5517B4B18 8E5D33A6ED51 -8ED41E8B8056 -8FA1D601D0A2 -911E52FD7CE4 -9189449EA24E -91CE16C07AC5 -91F93A5564C9 -925B158F796F -92EE4DC87191 -932B9CB730EF -94414C1A07DC -95093F0B2E22 -961C0DB4A7ED -987A7F7F1A35 -9B832A9881FF -9CB290282F7D -9DC282D46217 9F42971E8322 -A10F303FC879 -A21680C27773 -A22AE129C013 -A2ABB693CE34 -A4F204203F56 -A56C2DF9A26D -A57186BDD2B9 -A643F952EA57 -A6CAC2886412 -A7ABBC77CC9E -A8D0D850A606 -A920F32FE93A -AA0720018738 -AB4E7045E97D -AC70CA327A04 -AD4FB33388BF -AD9E0A1CA2F7 -AFD0BA94D624 -B1ACA33180A5 -B35A0E4ACC09 -B39AE17435DC -B468D1991AF9 -B578F38A5C61 -B725F9CBF183 -B7BF0C13066E -B8A1F613CF3D -BA5B895DA162 -BBA840BA1C57 -BBE8FFFCF363 -BEDB604CC9D1 -BF1F4424AF76 -BFB6796A11DB -BFC8E353AF63 -C0C1C2C3C4C5 -C0DECE673829 -C2B7EC7D4EB1 -C3C88C6340B8 -C3F19EC592A2 -C4104FA3C526 -C5132C8980BC -C5CFE06D9EA3 C620318EF179 -C6D375B99972 -C96BD1CE607F -CB779C50E1BD -CBA6AE869AD5 -CC6B3B3CD263 -D0D1D2D3D4D5 -D21762B2DE3B -D2A597D76936 -D327083A60A7 D4FE03CE5B06 D4FE03CE5B07 D4FE03CE5B08 D4FE03CE5B09 D4FE03CE5B0A D4FE03CE5B0F -D58023BA2BDC -D9A37831DCE5 -DB1A3338B2EB -DD61EB6BCE22 -DF37DCB6AFB3 -E10623E7A016 E241E8AFCBAF -E2A5DC8E066F -E4F65C0EF32C -E55A3CA71826 -E64A986A5D94 -E703589DB50B -EB0A8FF88ADE -EC0A9B1A9E06 -ED646C83A4F3 -EE4CC572B40E -EEB420209D0C -F101622750B7 -F1B9F5669CC8 -F23442436765 -F238D78FF48F -F26E21EDCEE2 -F4396E468114 -F4CD5D4C13FF -F662248E7E89 -F72A29005459 -F792C4C76A5C +# Transport system Argentina - SUBE +# Shared key - sec 3 blk 15 +3FA7217EC575 +# +# Data from forum post +123F8888F322 +050908080008 +# +# Data from hoist +4F9F59C9C875 +# +# Data from pastebin +66F3ED00FED7 F7A39753D018 -F9861526130F -FAD63ECB5891 - -# Some keys of https://w3bsit3-dns.com and https://ikey.ru -BC4580B7F20B -8E26E45E7D65 -A7141147D430 -18E3A02B5EFF -E328A1C7156D -8A8D88151A00 -7A86AA203788 -72F96BDD3714 -C76BF71A2509 -1B61B2E78C75 -045CECA15535 -6B07877E2C5C -0CE7CD2CC72B -EA0FD73CB149 -B81F2B0C2F66 -BB52F8CCE07F -46D78E850A7E -E4821A377B75 -8791B2CCB5C4 -D5524F591EED -BAFF3053B496 -0F318130ED18 -42E9B54E51AB -7413B599C4EA -9EA3387A63C1 -B27ADDFB64B0 -E56AC127DD45 -0BE5FAC8B06A -FD8705E721B0 -7259FA0197C6 -22052B480D11 -9D993C5D4EF4 -C65D4EAA645B -0EB23CC8110B -3A8A139C20B4 -19FC84A3784B -0F01CEFF2742 -A3FAA6DAFF67 -BC2D1791DEC1 -7A396F0D633D -ACFFFFFFFFFF -77DABC9825E1 -518DC6EEA089 -044CE1872BC3 -114D6BE9440C -AFCEF64C9913 - +# +# Data from https://pastebin.com/Z7pEeZif +386B4D634A65 +666E564F4A44 +564777315276 +476242304C53 +6A696B646631 +4D3248735131 +425A73484166 +57784A533069 +345547514B4D +4C6B69723461 +4E4175623670 +4D5076656D58 +686A736A356E +484A57696F4A +6F4B6D644178 +744E326B3441 +70564650584F +584F66326877 +6D4E334B6C48 +6A676C315142 +77494C526339 +623055724556 +356D46474348 +4E32336C6E38 +57734F6F6974 +436A46587552 +5544564E6E67 +6F506F493353 +31646241686C +77646B633657 +# +# Data from TransPert +2031D1E57A3B +53C11F90822A +9189449EA24E +# +# data from Github +410B9B40B872 +2CB1A90071C8 +# +# +8697389ACA26 +1AB23CD45EF6 +013889343891 +# +# +0000000018DE +16DDCB6B3F24 +# +# Data from https://pastebin.com/vwDRZW7d +# Vingcard Mifare 4k Staff card +EC0A9B1A9E06 +6C94E1CED026 +0F230695923F +0000014B5C31 +# +# +BEDB604CC9D1 +B8A1F613CF3D +B578F38A5C61 +B66AC040203A +6D0B6A2A0003 +2E641D99AD5B +AD4FB33388BF +69FB7B7CD8EE +# +# Hotel +2A6D9205E7CA +13B91C226E56 +# +# KABA Hotel Locks +2A2C13CC242A +# +# +27FBC86A00D0 +01FA3FC68349 +# +# Smart Rider. Western Australian Public Transport Cards +6D44B5AAF464 +1717E34A7A8A +# +# RFIDeas +6B6579737472 +# +# HID MIFARE Classic 1k Key +484944204953 +204752454154 +# HID MIFARE SO +3B7E4FD575AD +11496F97752A +# +# Luxeo/Aztek cashless vending +415A54454B4D +# +# BQT +321958042333 +# +# Aperio KEY_A Sector 1, 12, 13, 14, 15 Data Start 0 Length 48 +160A91D29A9C +# +# Gallagher +B7BF0C13066E +# +# PIK Comfort Moscow keys (ISBC Mifare Plus SE 1K) +009FB42D98ED +002E626E2820 +# +# Boston, MA, USA Transit - MBTA Charlie Card +3060206F5B0A +5EC39B022F2B +3A09594C8587 +F1B9F5669CC8 +F662248E7E89 +62387B8D250D +F238D78FF48F +9DC282D46217 +AFD0BA94D624 +92EE4DC87191 +B35A0E4ACC09 +756EF55E2507 +447AB7FD5A6B +932B9CB730EF +1F1A0A111B5B +AD9E0A1CA2F7 +D58023BA2BDC +62CED42A6D87 +2548A443DF28 +2ED3B15E7C0F +F66224EE1E89 +# +# +60012E9BA3FA +# +# +DE1FCBEC764B +81BFBE8CACBA +BFF123126C9B +2F47741062A0 +B4166B0A27EA +A170D9B59F95 +400BC9BE8976 +D80511FC2AB4 +1FCEF3005BCF +BB467463ACD6 +E67C8010502D +FF58BA1B4478 +# +# Data from https://pastebin.com/Kz8xp4ev +FBF225DC5D58 +# +# Data https://pastebin.com/BEm6bdAE +# vingcard.txt +# Note: most likely diversified +96A301BCE267 +4708111C8604 +3D50D902EA48 +6700F10FEC09 +7A09CC1DB70A +560F7CFF2D81 +66B31E64CA4B +9E53491F685B +3A09911D860C +8A036920AC0C +361F69D2C462 +D9BCDE7FC489 +0C03A720F208 +6018522FAC02 +# +# Data from https://pastebin.com/4t2yFMgt +# Mifare technische Universität Graz TUG +D58660D1ACDE +50A11381502C +C01FC822C6E5 +0854BF31111E +# +# More keys - Found 8A at Sebel Hotel in Canberra, Australia +AE8587108640 +# +# SafLock standalone door locks +135B88A94B8B +# # Russian Troika card +EC29806D9738 08B386463229 0E8F64340BA4 0F1C63013DBA @@ -587,693 +1059,698 @@ EAAC88E5DC99 F8493407799D 6B8BD9860763 D3A297DC2698 -FBF225DC5D58 -# Strelka extension +# +# Data from reddit +34635A313344 +593367486137 +# +# Keys from Mifare Classic Tool project +044CE1872BC3 +045CECA15535 +0BE5FAC8B06A +0CE7CD2CC72B +0EB23CC8110B +0F01CEFF2742 +0F318130ED18 +114D6BE9440C +18E3A02B5EFF +19FC84A3784B +1B61B2E78C75 +22052B480D11 3367BFAA91DB +3A8A139C20B4 +42E9B54E51AB +46D78E850A7E 4B609876BBA3 -5C83859F2224 -66B504430416 -70D1CF2C6843 +518DC6EEA089 +6B07877E2C5C +7259FA0197C6 +72F96BDD3714 +7413B599C4EA +77DABC9825E1 +7A396F0D633D +7A86AA203788 +8791B2CCB5C4 +8A8D88151A00 8C97CD7A0E56 +8E26E45E7D65 +9D993C5D4EF4 +9EA3387A63C1 +A3FAA6DAFF67 +A7141147D430 +ACFFFFFFFFFF +AFCEF64C9913 +B27ADDFB64B0 +B81F2B0C2F66 B9F8A7D83978 -C4B3BD0ED5F1 -C4D3911AD1B3 -CAD7D4A6A996 -DA898ACBB854 -FEA1295774F9 - -# Moscow public toilets card -807119F81418 -22C8BCD10AAA -0AAABA420191 -E51B4C22C8BC -DBF9F79AB7A2 -34EDE51B4C22 -C8BCD10AAABA -BCD10AAABA42 - -# Moscow social card -2735FC181807 -2ABA9519F574 -84FD7F7A12B6 -186D8C4B93F9 -3A4BBA8ADAF0 -8765B17968A2 -40EAD80721CE -0DB5E6523F7C -51119DAE5216 -83E3549CE42D -136BDB246CAC -2F87F74090D1 -E53EAEFE478F -CE2797E73070 -328A034B93DB -81E1529AE22B -FC55C50E579F -1A72E2337BC3 -5DB52676BE07 -F64FBF085098 -8FE758A8F039 -BB1484CC155D -41990A529AE2 -CD2E9EE62F77 -69C1327AC20B -3C9C0D559DE5 -67BF3880C811 -48A01159A1E9 -2B83FB448CD4 -F24BBB044C94 -7DE02A7F6025 -BF23A53C1F63 -CB9A1F2D7368 -C7C0ADB3284F -9F131D8C2057 -67362D90F973 -6202A38F69E2 -100533B89331 -653A87594079 -D8A274B2E026 -B20B83CB145C -9AFA6CB4FC3D -94F46DB5FD46 -C31C8CD41D65 -BB1684CC155D -CA2393DB246C -1D75E52E76BE -81D9529AE223 -0159C9125AA2 -52AA1B6BB3FB -97EF60A8F031 -6FC73888D011 -3A92FA438BD3 -74CC3D85CD0E -025ACA1B63A3 -AF0878C81151 -9BFB6CB4FC45 -F750C0095199 -075FCF1860A8 -2686EE3F87C7 -277FEF3880C0 -82DA4B93DB1C -9CF46DB5FD46 -93EB64ACF43D - -# Keys from RfidResearchGroup proxmark3 project -# https://github.com/RfidResearchGroup/proxmark3/blob/master/client/dictionaries/mfc_default_keys.dic -0854BF31111E -0C03A720F208 -135B88A94B8B -1FCEF3005BCF -2F47741062A0 -361F69D2C462 -3A09911D860C -3D50D902EA48 -400BC9BE8976 -4708111C8604 -50A11381502C -560F7CFF2D81 -60012E9BA3FA -6018522FAC02 -66B31E64CA4B -6700F10FEC09 -7A09CC1DB70A -81BFBE8CACBA -8A036920AC0C -8A19D40CF2B5 -96A301BCE267 -9E53491F685B -A170D9B59F95 -AE8587108640 -B4166B0A27EA -BB467463ACD6 -BFF123126C9B -C01FC822C6E5 -D58660D1ACDE -D80511FC2AB4 -D9BCDE7FC489 -DE1FCBEC764B -E67C8010502D -FF58BA1B4478 - -C1E51C63B8F5 -4143414F5250 -474249437569 +BAFF3053B496 +BB52F8CCE07F +BC2D1791DEC1 +BC4580B7F20B +C65D4EAA645B +C76BF71A2509 +D5524F591EED +E328A1C7156D +E4821A377B75 +E56AC127DD45 +EA0FD73CB149 +FC0001877BF7 +FD8705E721B0 +00ADA2CD516D +518108E061E2 +558AAD64EB5B +001122334455 +6CA761AB6CA7 +B1C4A8F7F6E3 +FF75AFDA5A3C +FCDDF7767C10 +A6B3F6C8F1D4 +# +# +237A4D0D9119 +0ED7846C2BC9 +FFFFD06F83E3 +FFFFAE82366C +F89C86B2A961 +F83466888612 +ED3A7EFBFF56 +E96246531342 +E1DD284379D4 +DFED39FFBB76 +DB5181C92CBE +CFC738403AB0 +BCFE01BCFE01 +BA28CFD15EE8 B0699AD03D17 -B18CDCDE52B7 -3864FCBA5937 -85A438F72A8A -C342F825B01B -C6A76CB2F3B5 -323334353637 -43C7600DEE6B -D01AFEEB890A -26396F2042E7 -F3F0172066B2 -BB320A757099 -00F0BD116D70 -25094DF2C1BD -E41E6199318F -F00DFEEDD0D0 -6D9B485A4845 -71171A82D951 -62711F1A83E9 -1711B1A82E96 -F3864FCCA693 -7B7E752B6A2D -2012053082AD -9AEDF9931EC1 -B9C874AE63D0 -83BAB5ACAD62 -A541538F1416 -4A2B29111213 -D31463A7AB6D -AD5645062534 -B069D0D03D17 -30FFB6B056F5 -D7744A1A0C44 -D1991E71E2C5 -1795902DBAF9 -4243414F5250 -C554EF6A6015 -A5524645CD91 -200306202033 -A00003000084 -CEE3632EEFF5 -F0F0172066B2 -B021669B44BB -3F1A87298691 +AABBCC660429 +A4EF6C3BB692 A2B2C9D187FB -4B511F4D28DD -E3AD9E9BA5D4 -B3630C9F11C8 -F83466888612 -857464D3AAD1 -A2A3CCA2A3CC -35D850D10A24 -B2F170172066 -0D8CA561BDF3 -05F89678CFCF -850984712F1A -21EDF95E7433 -172066B2F2F0 -6B2F1B017206 -363119000001 -23D4CDFF8DA3 -EF4C5A7AC6FC -123456ABCDEF +9B1DD7C030A1 +9AEDF9931EC1 8F9B229047AC -E96246531342 +872B71F9D15A +833FBD3CFE51 5D293AFC8D7E -AABBCC660429 -63FCA9492F38 -354A787087F1 +5554AAA96321 +474249437569 +435330666666 +1A2B3C4D5E6F +123456ABCDEF +83BAB5ACAD62 +64E2283FCF5E +64A2EE93B12B +46868F6D5677 40E5EA1EFC00 -675A32413770 -12FD3A94DF0E -C4F271F5F0B3 -4A306E62E9B6 -4CFF128FA3EF -0000085F0000 -0F385FFB6529 -FA38F70215AD -904735F00F9E -B66AC040203A -BCFE01BCFE01 -2FCA8492F386 -237A4D0D9119 -E6849FCC324B -0ED7846C2BC9 -1FB235AC1388 -EA1B88DF0A76 -C9739233861F -89EAC97F8C2A -D964406E67B4 -563A22C01FC8 -DB5181C92CBE -1E352F9E19E5 -6291B3860FC8 -A9A4045DCE77 -434456495243 -B5F454568271 -491CDC863104 -4D8B8B95FDEE -AFBECD121004 -4D48414C5648 -E1DD284379D4 -AFBECD120454 -CFC738403AB0 -0AF7DB99AEE4 -772219470B38 -EF61A3D48E2A -4A4C474F524D -0172066B2F03 -3D5D9996359A -66A163BA82B4 +37D4DCA92451 +2012053082AD 2011092119F1 -BB2C0007D022 -1494E81663D7 -590BD659CDD2 -A6C028A12FBB -BCF5A6B5E13F -A2F63A485632 -39CF885474DD -D2ECE8B9395E +200306202033 +1795902DBAF9 17505586EF02 -70172066B2F0 -B385EFA64290 -B7C344A36D88 -010000000000 -2F130172066B -DFED39FFBB76 -3F7A5C2DBD81 -DFE73BE48AC6 -FB0B20DF1F34 -913385FFB752 -2066B2F27017 -91FF18E63887 -0734BFB93DAB -97F5DA640B18 -B42C4DFD7A90 -C121FF19F681 -BE7C4F6C7A9A -FE04ECFE5577 -18F34C92A56E -0BB31DC123E5 -0E0E8C6D8EB6 -1CFA22DBDFC3 -0F1A81C95071 -AA4D051954AC -9F9D8EEDDCCE -863FCB959373 -98631ED2B229 -A23456789123 -833FBD3CFE51 -AB28A44AD5F5 -74A386AD0A6D -4C60F4B15BA8 022FE48B3072 -97271231A83F -385EFA542907 -066B2F230172 -BA729428E808 -BDF837787A71 -A05DBD98E0FC -395244733978 -9B1DD7C030A1 -2C9F3D45BA13 -1DB710648A65 -E65B66089AFC -A85198481331 -5A12F83326E7 -A7D71AC06DC2 -64E2283FCF5E -FFFFAE82366C -10F3BEBC01DF -00ADA2CD516D +013940233313 +# +# Hotel Adina +9EBC3EB37130 +# +# Misc. keys from hotels & library cards in Germany +914f57280ce3 +324a82200018 +370aee95cd69 +2e032ad6850d +1feda39d38ec +288b7a34dbf8 +0965e3193497 +18c628493f7f +064d9423938a +995fd2a2351e +7c7d672bc62e +217250fb7014 +ae7478ccaee7 +abbf6d116eaf +05862c58edfb +e43b7f185460 +6a59aa9a959b +b79e5b175227 +7bc9ebb8274b +b2afbf2331d4 +223e5847dd79 +640524d2a39b +aee297cb2fd6 +3da5dfa54604 +0cf1a2aa1f8d +# +# most likely diversifed individual keys. +# data from https://github.com/korsehindi/proxmark3/commit/24fdbfa9a1d5c996aaa5c192bc07e4ab28db4c5c +491CDC863104 +A2F63A485632 +98631ED2B229 +19F1FFE02563 +# +# Argentina +563A22C01FC8 +43CA22C13091 +25094DF2C1BD +# +# OMNITEC.ES HOTEL TIMECARD / MAINTENANCECARD +AFBECD120454 +# +# OMNITEC.ES HOTEL EMERGENCYCARD +842146108088 +# +# TAPCARD PUBLIC TRANSPORT LA +EA1B88DF0A76 +D1991E71E2C5 +05F89678CFCF +D31463A7AB6D +C38197C36420 +772219470B38 +1C1532A6F1BC +FA38F70215AD +E907470D31CC +160F4B7AB806 1D28C58BBE8A -6936C035AE1B -AC45AD2D620D -64A2EE93B12B -A9F95891F0A4 -E45230E7A9E8 -A7FB4824ACBF -223C3427108A -58AC17BF3629 -535F47D35E39 -10F2BBAA4D1C -A0004A000036 -3F3865FCCB69 -0B0172066B2F -4098653289D3 -BA28CFD15EE8 -A22647F422AE -99858A49C119 -29173860FC76 -1A80B93F7107 -1A2B3C4D5E6F +B3830B95CA34 +6A0E215D1EEB +E41E6199318F +C4F271F5F0B3 +1E352F9E19E5 +0E0E8C6D8EB6 +C342F825B01B +CB911A1A1929 +E65B66089AFC +B81846F06EDF +37FC71221B46 +880C09CFA23C +6476FA0746E7 +419A13811554 2C60E904539C +4ECCA6236400 +10F2BBAA4D1C +4857DD68ECD9 +C6A76CB2F3B5 +E3AD9E9BA5D4 6C9EC046C1A4 -FC9839273862 -1C1532A6F1BC -09800FF94AAF -FFFFD06F83E3 -0B83797A9C64 -13B91C226E56 -A4EF6C3BB692 -000000270000 -A00002000021 -872B71F9D15A +# +# ROC HIGHSCHOOL ACCESSCARD +B021669B44BB +B18CDCDE52B7 +A22647F422AE +B268F7C9CA63 A37A30004AC9 -B1A862985913 +B3630C9F11C8 A4CDFF3B1848 -2E641D99AD5B -827ED62B31A7 -CD212889C3ED -B1A80C94F710 -CB911A1A1929 +B42C4DFD7A90 +A541538F1416 +B5F454568271 +A6C028A12FBB +B6323F550F54 +A7D71AC06DC2 +B7C344A36D88 A844F4F52385 -C0BEEFEC850B -5C8FF9990DA2 -160F4B7AB806 -B8937130B6BA -66B2F1F01720 -82D58AA49CCB -A9B43414F585 -C38197C36420 -0172066B2F33 -434143445649 -34B16CD59FF8 -5A7A52D5E20D -6471A5EF2D1A -F57F410E18FF -3B0172066B2F -E907470D31CC B8457ACC5F5D -67CC03B7D577 -A8844B0BCA06 -435330666666 -B47058139187 -46868F6D5677 -C27D999912EA -37FC71221B46 -F97371271A84 +A9A4045DCE77 +B9B8B7B6B5B3 +AA4D051954AC +BA729428E808 +AB28A44AD5F5 +BB320A757099 +AC45AD2D620D +BCF5A6B5E13F +AD5645062534 +BDF837787A71 AE43F36C1A9A +BE7C4F6C7A9A 5EC7938F140A -AA734D2F40E0 -A5A4A3A2A1A0 -70172066B2F3 +82D58AA49CCB +# +# MELON CARD +323334353637 +# +# +CEE3632EEFF5 +827ED62B31A7 03EA4053C6ED +C0BEEFEC850B +F57F410E18FF +0AF7DB99AEE4 +A7FB4824ACBF +207FFED492FD +1CFA22DBDFC3 +30FFB6B056F5 +39CF885474DD +00F0BD116D70 +4CFF128FA3EF +10F3BEBC01DF +# +# Transportes Insular La Palma +0172066B2F03 +0000085F0000 +1A80B93F7107 +70172066B2F0 +B1A80C94F710 +0B0172066B2F +0F1A81C95071 +F0F0172066B2 +1131A81D9507 +2F130172066B +71171A82D951 +B2F170172066 +1711B1A82E96 +6B2F1B017206 +62711F1A83E9 +66B2F1F01720 +97271231A83F +066B2F230172 +F97371271A84 +2066B2F27017 +50983712B1A8 +72066B2F2B01 +850984712F1A +172066B2F2F0 +A85198481331 +0172066B2F33 +1A8619858137 +70172066B2F3 +B1A862985913 +3B0172066B2F +3F1A87298691 +F3F0172066B2 +# +# Tehran ezpay +38A88AEC1C43 +CBD2568BC7C6 +7BCB4774EC8F +22ECE9316461 +AE4B497A2527 +EEC0626B01A1 +2C71E22A32FE +91142568B22F +7D56759A974A +D3B1C7EA5C53 +41C82D231497 +0B8B21C692C2 +604AC8D87C7E +8E7B29460F12 +BB3D7B11D224 +# +# Chaco +B210CFA436D2 +B8B1CFA646A8 +A9F95891F0A4 +# +# Keys from APK application "Scan Badge" +4A4C474F524D +444156494442 +434143445649 +434456495243 +A00002000021 +EF61A3D48E2A +A23456789123 +010000000000 +363119000001 +A00003000084 +675A32413770 +395244733978 +A0004A000036 +2C9F3D45BA13 +4243414F5250 +DFE73BE48AC6 +# +# +B069D0D03D17 +000131B93F28 +# +# From the DFW Area, TX, USA +A506370E7C0F +26396F2042E7 +70758FDD31E0 +9F9D8EEDDCCE 06FF5F03AA1A -5554AAA96321 -0120BF672A64 -87291F3861FC -9EBC3EB37130 +4098653289D3 +904735F00F9E B4C36C79DA8D -43CA22C13091 -6D0B6A2A0003 -FB6C88B7E279 -013940233313 -7DD399D4E897 -ED3A7EFBFF56 68F9A1F0B424 -6476FA0746E7 -1A8619858137 -1131A81D9507 +5A85536395B3 +7DD399D4E897 +EF4C5A7AC6FC +B47058139187 8268046CD154 -4857DD68ECD9 +67CC03B7D577 +# +# From the HTL Mödling, NÖ, AT +A5524645CD91 +D964406E67B4 +99858A49C119 +7B7E752B6A2D +C27D999912EA +66A163BA82B4 +4C60F4B15BA8 +# +# CAFE + CO, AT +35D850D10A24 +4B511F4D28DD +E45230E7A9E8 +535F47D35E39 +FB6C88B7E279 +# +# Metro Card, AT +223C3427108A +# +# Unknown, AT +23D4CDFF8DA3 +E6849FCC324B +12FD3A94DF0E +# +# Unknown, AT +0B83797A9C64 +39AD2963D3D1 +# +# Hotel Berlin Classic room A KEY +34B16CD59FF8 +# +# Hotel Berlin Classic room B KEY +BB2C0007D022 +# +# Coinmatic laundry Smart card +# data from: https://pastebin.com/XZQiLtUf +0734BFB93DAB +85A438F72A8A +# +# Data from forum, Chinese hotel +58AC17BF3629 B62307B62307 -C6C866AA421E -F66224EE1E89 -4ECCA6236400 -72066B2F2B01 +# +# +A2A3CCA2A3CC +# +# Granada, ES Transport Card +000000270000 +0F385FFB6529 +29173860FC76 +2FCA8492F386 +385EFA542907 +3864FCBA5937 +3F3865FCCB69 +6291B3860FC8 +63FCA9492F38 +863FCB959373 +87291F3861FC +913385FFB752 +B385EFA64290 +C9739233861F +F3864FCCA693 +FC9839273862 +# +# various hotel keys 34D3C568B348 -000131B93F28 -419A13811554 -B6323F550F54 -F89C86B2A961 -B268F7C9CA63 -B8B1CFA646A8 -4D57414C5648 -6A0E215D1EEB -70758FDD31E0 -37D4DCA92451 -444156494442 -B210CFA436D2 -207FFED492FD +91FF18E63887 +4D8B8B95FDEE +354A787087F1 +4A306E62E9B6 +B9C874AE63D0 +# +# Data from official repo +F00DFEEDD0D0 +0BB31DC123E5 7578BF2C66A9 -50983712B1A8 -5A85536395B3 -B81846F06EDF -842146108088 -19F1FFE02563 -D3B595E9DD63 -B3830B95CA34 -A506370E7C0F -880C09CFA23C -43454952534E -39AD2963D3D1 -B9B8B7B6B5B3 +CD212889C3ED +6936C035AE1B +C6C866AA421E +590BD659CDD2 +AA734D2F40E0 +09800FF94AAF +5A12F83326E7 +C554EF6A6015 +0D8CA561BDF3 +B8937130B6BA +D7744A1A0C44 82908B57EF4F - -C67BEB41FFBF -2AFFD6F88B97 -E77952748484 -988ACDECDFB0 -605F5E5D5C5B -42EF7BF572AB -4087C6A75A96 -AADE86B1F9C1 -5EA088C824C9 -120D00FFFFFF +FE04ECFE5577 +# +# comfort inn hotel +4D57414C5648 +4D48414C5648 +# +# unknown hotel key +6D9B485A4845 +# +# Bosch Solution 6000 +5A7A52D5E20D +# +# Found in TagInfo app +# RATB key +C1E51C63B8F5 +1DB710648A65 +# E-GO card key +18F34C92A56E +# +# Library Card MFP - SL1 +4A832584637D CA679D6291B0 +30D9690FC5BC +5296C26109D4 +E77952748484 +91C2376005A1 +30B7680B2BC9 E2A9E88BFE16 -0A4600FF00FF 43B04995D234 -0602721E8F06 +AADE86B1F9C1 +5EA088C824C9 +C67BEB41FFBF +B84D52971107 +52B0D3F6116E +# +# Data from https://pastebin.com/cLSQQ9xN +CA3A24669D45 +4087C6A75A96 +403F09848B87 +D73438698EEA 5F31F6FCD3A0 -4AE23A562A80 A0974382C4C5 -91C2376005A1 -FEE2A3FBC5B6 -2602FFFFFFFF -CA3A24669D45 -A9F3F289B70C -B84D52971107 -274E6101FC5E -00DD300F4F10 -F7BA51A9434E -4A832584637D -B16B2E573235 A82045A10949 -FC0B50AF8700 -403F09848B87 +# +# Data from https://pastebin.com/2iV8h93h +# +# funnivarium +# forum ankara +2602FFFFFFFF +# +# macera adasi +# ankara kentpark +# INACTIVE +0A4600FF00FF DFF293979FA7 -4118D7EF0902 -30B7680B2BC9 -52B0D3F6116E -5296C26109D4 -DB6819558A25 4D6F62692E45 +4118D7EF0902 +# +# petrol ofisi +# positive card +# ode-gec 0406080A0C0E +# +# konya elkart +988ACDECDFB0 +120D00FFFFFF +# +# bowlingo +# serdivan avym +4AE23A562A80 +# +# kart 54 +2AFFD6F88B97 +A9F3F289B70C +DB6819558A25 6130DFA578A0 -30D9690FC5BC -D73438698EEA +B16B2E573235 +42EF7BF572AB +274E6101FC5E +# +# crazy park +# kizilay avm +00DD300F4F10 +# +# kartsistem B +FEE2A3FBC5B6 +# +# toru ent +# taurus avm 005078565703 - -7C87013A648A -9E7168064993 -45FEE09C1D06 -734EBE504CE8 -E592ED478E59 -C229CE5123D5 -240F0BB84681 -D8BA1AA9ABA0 -865B6472B1C0 -974A36E2B1BA -57D83754711D -C9CD8D7C65E5 -C197AE6D6990 -AABAFFCC7612 -C0AD1B72921A -AFAAFCC40DEC +# +# Ving? +0602721E8F06 +FC0B50AF8700 +F7BA51A9434E +# +# eskart +# eskisehir transport card E902395C1744 -DAC7E0CBA8FD -755D49191A78 -68D3263A8CD6 -2F8A867B06B4 +4051A85E7F2D 7357EBD483CC -ABCC1276FCB0 -26BF1A68B00F -704A81DDACED +D8BA1AA9ABA0 +76939DDD9E97 +3BF391815A8D +# +# muzekart +# museum card for turkey +7C87013A648A E8794FB14C63 +9F97C182585B EC070A52E539 -037F64F470AD -76939DDD9E97 -4D80A10649DF -89E00BC444EF -26107E7006A0 +C229CE5123D5 +E495D6E69D9C +26BF1A68B00F B1D3BC5A7CCA -ECC58C5D34CA -9F97C182585B -B2FE3B2875A6 +734EBE504CE8 +974A36E2B1BA +C197AE6D6990 +4D80A10649DF +037F64F470AD +C9CD8D7C65E5 B70B1957FE71 -E495D6E69D9C -0860318A3A89 -4051A85E7F2D -17D071403C20 -3BF391815A8D -1927A45A83D3 CE7712C5071D +C0AD1B72921A +45FEE09C1D06 +E592ED478E59 F3C1F1DB1D83 +704A81DDACED +89E00BC444EF +AFAAFCC40DEC +ECC58C5D34CA +57D83754711D D0DDDF2933EC - -# Iron Logic -A3A26EF4C6B0 -2C3FEAAE99FC -E85B73382E1F -F4ED24C2B998 -CB574C6D3B19 -E092081D724B -B38D82CF7B6C -8228D2AA6EFA -2C7E983588A3 -CF7A7B77E232 -32A7F5EAF87D -7453A687B5F0 -01A0C008A5B9 -DEC0CEB0CE24 -413BED2AE45B -D6261A9A4B3F -CB9D507CE56D - -# Tehran ezpay -38A88AEC1C43 -CBD2568BC7C6 -7BCB4774EC8F -22ECE9316461 -AE4B497A2527 -EEC0626B01A1 -2C71E22A32FE -91142568B22F -7D56759A974A -D3B1C7EA5C53 -41C82D231497 -0B8B21C692C2 -604AC8D87C7E -8E7B29460F12 -BB3D7B11D224 - -# More keys from the PM3 repo -DC018FC1D126 -C428C4550A75 -0C4233587119 -5B0C7EC83645 -540D5E6355CC -35C649004000 -CFE63749080A -6307417353C1 -411053C05273 -749934CC8ED3 -1C68315674AC -35D152154017 -D1417E431949 -26B85DCA4321 -D973D917A4C7 -3A471B2192BF -534F4C303232 -730956C72BC2 -C9449301AF93 -F678905568C3 -4578ABFEDC12 -075D1A4DD323 -43E69C28F08C -0F35D5660653 -F7FA2F629BB1 -5145C34DBA19 -124578ABFEDC -E2F14D0A0E28 -C8AACD7CF3D1 -9C616585E26D -4927C97F1D57 +240F0BB84681 +9E7168064993 +2F8A867B06B4 +# +# bursakart +# bursa transport card +755D49191A78 +DAC7E0CBA8FD +68D3263A8CD6 +865B6472B1C0 +0860318A3A89 +1927A45A83D3 +B2FE3B2875A6 +# +# playland +# maltepe park +ABCC1276FCB0 +AABAFFCC7612 +# +# lunasan +# kocaeli fair +26107E7006A0 +# +# gamefactory +# ozdilek +17D071403C20 +# +# +534F4C415249 +534F4C303232 +# +# Nespresso, smart card +# key-gen algo, these keys are for one card (keys diversified) +FF9A84635BD2 6F30126EE7E4 -155332417E00 -5353B3AECB53 -361A62F35BC9 -00460740D722 -A9B018868CC1 -2E71D3BD262A -4F75030AD12B -42454C4C4147 -D75971531042 -25352912CD8D -51E97FFF51E9 -1170553E4304 -D1F71E05AD9D -541C417E57C0 -AE76242931F1 6039ABB101BB -0E620691B9FE -4BF6DE347FB6 -10510049D725 -1F0128447C00 +F1A1239A4487 +# +# +B882FD4A9F78 +CD7FFFF81C4A +AA0857C641A3 +C8AACD7CF3D1 +9FFDA233B496 +26B85DCA4321 +D4B2D140CB2D +A7395CCB42A0 +541C417E57C0 D14E615E0545 -94B6A644DFF6 -81B20C274C3F -66695A45C9FA -130662240200 -DD0DE3BA08A6 -05F5EC05133C -4FA9EB49F75E -C1E6F8AFC9EC -28D70900734C -32CA52054416 +69D92108C8B5 703265497350 -3D923EB73534 +D75971531042 +10510049D725 +35C649004000 +5B0C7EC83645 +05F5EC05133C +521B517352C7 +94B6A644DFF6 +2CA4A4D68B8E +A7765C952DDF +E2F14D0A0E28 +DC018FC1D126 +4927C97F1D57 +046154274C11 +155332417E00 +6B13935CD550 C151D998C669 -534F4C415249 -70C714869DC7 -A7395CCB42A0 -89AA9D743812 -A160FCD5EC4C -9DCDB136110C -9951A273DEE7 -AA0857C641A3 -F1A1239A4487 -B882FD4A9F78 +D973D917A4C7 +130662240200 9386E2A48280 +52750A0E592A +075D1A4DD323 +32CA52054416 460661C93045 -EF1232AB18A0 -6285A1C8EB5C -C41514DEFC07 -ABFEDC124578 -046154274C11 5429D67E1F57 -# SMARTair Key B -E7316853E731 -CD7FFFF81C4A -F253C30568C4 -E7D6064C5860 -506DB955F161 -8223205047B6 -070D486BC555 -D4B2D140CB2D 0C734F230E13 -2E4169A5C79D -69D92108C8B5 -A297CEB7D34B -FF9A84635BD2 +1F0128447C00 +411053C05273 +42454C4C4147 +C428C4550A75 +730956C72BC2 +28D70900734C +4F75030AD12B +6307417353C1 +D65561530174 +D1F71E05AD9D +F7FA2F629BB1 +0E620691B9FE +43E69C28F08C 735175696421 -5D0762D13401 -D61707FFDFB1 -2803BCB0C7E1 -C52876869800 424C0FFBF657 -AF9E38D36582 -B32464412EE3 +51E97FFF51E9 +E7316853E731 +00460740D722 +35D152154017 +5D0762D13401 +0F35D5660653 +1170553E4304 +0C4233587119 +F678905568C3 50240A68D1D8 -6B13935CD550 -83F3CB98C258 -521B517352C7 -4BB747E48C2A +2E71D3BD262A +540D5E6355CC +D1417E431949 +4BF6DE347FB6 +# +# +3A471B2192BF +A297CEB7D34B +AE76242931F1 +# +# +124578ABFEDC +ABFEDC124578 +4578ABFEDC12 +# +# Data from +# premier inn hotel chain 5E594208EF02 -FFFFFF545846 -D65561530174 -52750A0E592A -112233445566 -2DADE48942C5 -A7765C952DDF -2CA4A4D68B8E -72B458D60363 -F088A85E71D7 -FF94F86B09A6 -B27CCAB30DBD -89ECA97F8C2A -E00000000000 -9FFDA233B496 +AF9E38D36582 +# +# Norwegian building site identication card. (HMS KORT) +# Key a 10DF4D1859C8 +# +# Key B B5244E79B0C8 +# +# Ukraine hotel F5C1C4C5DE34 - +# +# Data from Mifare Classic Tool repo # Rotterdam University of applied sciences campus card BB7923232725 A95BD5BB4FC5 @@ -1297,27 +1774,406 @@ B5ADEFCA46C4 BF3FE47637EC B290401B0CAD AD11006B0601 - -# Keys of Armenian underground ticket -A0A1A2A8A4A5 +# +# Data from Mifare Classic Tool repo +# Armenian Metro +E4410EF8ED2D +6A68A7D83E11 0D6057E8133B D3F3B958B8A3 -6A68A7D83E11 -7C469FE86855 -E4410EF8ED2D 3E120568A35C -CE99FBC8BD26 2196FAD8115B - -# PIK Comfort Moscow keys (ISBC Mifare Plus SE 1K) -009FB42D98ED -002E626E2820 - -# Volgograd (Russia) Volna transport cards keys -2B787A063D5D -D37C8F1793F7 - +7C469FE86855 +CE99FBC8BD26 +# +# keys from Eurothermes group (Switzerland) +D66D91829013 +75B691829013 +83E391829013 +A23C91829013 +E46A91829013 +D9E091829013 +FED791829013 +155F91829013 +06CC91829013 +8DDC91829013 +54AF91829013 +29A791829013 +668091829013 +00008627C10A +# +# easycard +310D51E539CA +2CCDA1358323 +03E0094CEDFE +562E6EF73DB6 +F53E9F4114A9 +AD38C17DE7D2 +# +# SUBE cards keys (new) +2DEB57A3EA8F +32C1BB023F87 +70E3AD3F2D29 +202ECDCCC642 +3686192D813F +24501C422387 +2C7813A721C3 +FFE04BE3D995 +D28F090677A1 +DE2D83E2DCCC +A66A478712EA +643232ADB2D5 +C7F4A4478415 +95C013B70D99 +3C383889362A +3C6D9C4A90FA +51BEDBA005E5 +74BF7363F354 +53B09DB89111 +E98075318085 +2F904641D75F +7F60AEF68136 +F5C1B3F62FDA +3E6E5713BA10 +8B75A29D4AB2 +7E6545076619 +# +# SUBE cards keys (old) +4C5A766DFE3A +32C6768847F5 +F68930789631 +8B42B6D64B02 +B627A3CB13F8 +562A4FB8260B +88DDC24E1671 +91CB7802A559 +7A3E0F5B63FC +8CA2C9DC8292 +5CCC6D50EAAC +DE4F5AA9A7F3 +52D0145E1AF5 +C10F92A4E57E +7D6E7AF43C97 +DE1E7D5F6DF1 +F4CB751B031A +C54474936B59 +2A1F900D4533 +6303CDCBB233 +F115E91357B3 +BFE25035B0C8 +62FF943EB069 +7C82EF592001 +D5C172325DD3 +992B152E834A +CE75D7EADEAF +# +# Russian Podorozhnik card (Saint-Petersburg transport) +# may be combined with Troika +038B5F9B5A2A +04DC35277635 +0C420A20E056 +152FD0C420A7 +296FC317A513 +29C35FA068FB +31BEC3D9E510 +462225CD34CF +4B7CB25354D3 +5583698DF085 +578A9ADA41E3 +6F95887A4FD3 +7600E889ADF9 +86120E488ABF +8818A9C5D406 +8C90C70CFF4A +8E65B3AF7D22 +9764FEC3154A +9BA241DB3F56 +AD2BDC097023 +B0A2AAF3A1BA +B69D40D1A439 +C956C3B80DA3 +CA96A487DE0B +D0A4131FB290 +D27058C6E2C7 +E19504C39461 +FA1FBB3F0F1F +FF16014FEFC7 +# +# Food GEM +6686FADE5566 +# +# Samsung Data Systems (SDS) — Electronic Locks +# Gen 1 S10 KA/KB is FFFFFFFFFFFF, incompatible with Gen 2 locks +# +# SDS Gen 2 S10 KB +C22E04247D9A +# +# Data from Discord, French pool +# SDS Gen 2 S10 KA +9B7C25052FC3 +494446555455 +# +# Data from Discord, seems to be related to ASSA +427553754D47 +# Keys found on Edith Cowan University Smart Riders +9A677289564D +186C59E6AFC9 +DDDAA35A9749 +9D0D0A829F49 +# Mercator Pika Card, Slovenia +97D77FAE77D3 +5AF445D2B87A +# +# Vilniečio/JUDU kortelė, Lithuania +# A +16901CB400BC +F0FE56621A42 +8C187E78EE9C +FE2A42E85CA8 +# B +6A6C80423226 +F4CE4AF888AE +307448829EBC +C2A0105EB028 +# +# Keys from Flipper Zero Community +# Last update: Aug 13, 2022 +# +# unknown if keys are diversified or static default +# +# Strelka Extension +5C83859F2224 +66B504430416 +70D1CF2C6843 +C4B3BD0ED5F1 +C4D3911AD1B3 +CAD7D4A6A996 +DA898ACBB854 +FEA1295774F9 +# +# Moscow Public Toilets Card +807119F81418 +22C8BCD10AAA +0AAABA420191 +E51B4C22C8BC +DBF9F79AB7A2 +34EDE51B4C22 +C8BCD10AAABA +BCD10AAABA42 +# +# Moscow Social Card +2F87F74090D1 +E53EAEFE478F +CE2797E73070 +328A034B93DB +81E1529AE22B +FC55C50E579F +1A72E2337BC3 +5DB52676BE07 +F64FBF085098 +8FE758A8F039 +BB1484CC155D +41990A529AE2 +CD2E9EE62F77 +69C1327AC20B +3C9C0D559DE5 +67BF3880C811 +48A01159A1E9 +2B83FB448CD4 +F24BBB044C94 +94F46DB5FD46 +C31C8CD41D65 +BB1684CC155D +CA2393DB246C +1D75E52E76BE +81D9529AE223 +0159C9125AA2 +52AA1B6BB3FB +97EF60A8F031 +6FC73888D011 +3A92FA438BD3 +74CC3D85CD0E +025ACA1B63A3 +AF0878C81151 +9BFB6CB4FC45 +F750C0095199 +075FCF1860A8 +2686EE3F87C7 +277FEF3880C0 +82DA4B93DB1C +9CF46DB5FD46 +93EB64ACF43D +# +# Iron Logic RU +A3A26EF4C6B0 +2C3FEAAE99FC +E85B73382E1F +F4ED24C2B998 +CB574C6D3B19 +E092081D724B +B38D82CF7B6C +8228D2AA6EFA +2C7E983588A3 +CF7A7B77E232 +32A7F5EAF87D +7453A687B5F0 +01A0C008A5B9 +DEC0CEB0CE24 +413BED2AE45B +D6261A9A4B3F +CB9D507CE56D +# +# Armenian Underground Ticket +A0A1A2A8A4A5 +# +# Badge Maker Leaked from https://github.com/UberGuidoZ +1A1B1C1D1E1F +1665FE2AE945 +158B51947A8E +E167EC67C7FF +D537320FF90E +5E56BFA9E2C9 +F81CED821B63 +C81584EF5EDF +9551F8F9259D +36E1765CE3E8 +509052C8E42E +776C9B03BE71 +C608E13ADD50 +BEE8B345B949 +ED0EC56EEFDD +9716D5241E28 +05D1FC14DC31 +3321FB75A356 +F22A78E29880 +EC211D12C98D +8CCA8F62A551 +B637E46AD674 +39605B3C8917 +3882719778A1 +9F27D36C4230 +DB32A6811327 +8AA8544A2207 +8C5819E780A4 +7549E90353A2 +2E52ABE0CE95 +E46210ED98AB +61D030C0D7A8 +18E20102821E +DA59354DFB88 +040047C12B75 +D10008074A6F +686E736F6E20 +446176696453 +6F6674776172 +6520446F7665 +# +# Apartment keyfobs (USA) (Corvette830) +E60F8387F0B9 +FFD46FF6C5EE +4F9661ED2E70 +576A798C9904 +1C5179C4A8A1 +16CA203B811B +11AC8C8F3AF2 +# +# The Westin Jakarta Indonesia (D4DB0D) +# Peppers Hotel Unknown location (D4D0D) +6E0DD4136B0A +141940E9B71B +3B1D3AAC866E +95E9EE4CCF8F +FEA6B332F04A +BE0EC5155806 +0500D6BFCC4F +FC5AC7678BE3 +F09BB8DD142D +B4B3FFEDBE0A +540E0D2D1D08 +# +# Schlage 9691T Keyfob (seasnaill) +7579B671051A +4F4553746B41 +# +# Vigik ScanBadge App (fr.badgevigik.scanbadge) +# Website https://badge-vigik.fr/ (Alex) +0000A2B3C86F +021200C20307 +021209197507 +1E34B127AF9C +303041534956 +4143532D494E +41454E521985 +43412D627400 +455249524345 +456666456666 +45B722C63319 +484585414354 +4D414C414741 +536563644C65 +57D27B730760 +593DD8FE167A +6472616E7265 +65626F726369 +680E95F3C287 +709BA7D4F920 +8829DAD9AF76 +92D0A0999CBA +948EE7CFC9DB +9EB7C8A6D4E3 +A22AE12C9013 +AFC984A3576E +# +# Vigik verified by quantum-x +# https://github.com/RfidResearchGroup/proxmark3/pull/1742#issuecomment-1206113976 +A00027000099 +A00016000028 +A00003000028 +A0000F000345 +A00001000030 +A00002000086 +A00002000036 +A00002000088 +A00000000058 +A00000000096 +A00000000008 +A00000043D79 +A00000000064 +A00025000030 +A00003000057 +# +# BH USA 2013 conference +012279BAD3E5 +# +# iGuard Simple (and reverse) keys +AAAAAAFFFFFF +FFFFFFAAAAAA +# +# Random Hotel A Key Sec 0 Blk 3 - KABA Lock (VideoMan) +3111A3A303EB +# Transport system Uruguay - STM +# Shared key - sec 0 blk 3 +D144BD193063 +# +# Data from http://www.proxmark.org/forum/viewtopic.php?pid=45659#p45659 +3515AE068CAD +# +# Keys Catering +6A0D531DA1A7 +4BB29463DC29 +# +# Keys Swim +8627C10A7014 +453857395635 +# +# Unknown hotel system Sec 0 / A +353038383134 +# +# Brazil transport Sec 8 / A +50d4c54fcdf5 +# # Bandai Namco Passport [fka Banapassport] / Sega Aime Card +# Dumped on the Flipper Devices Discord Server 6090D00632F5 019761AA8082 574343467632 @@ -1351,7 +2207,551 @@ E69DD9015A43 C8382A233993 7B304F2A12A6 FC9418BF788B +# +# Guest Cashless Prepaid Arcade Payment Cards +168168168168 +198407157610 +4E4F584D2101 +4E4F584D2105 +686B35333376 +861861861861 +# +# Transport System Cracow / Polen +B071A76BA2E9 +B3A181BCA5F2 +3225942F7717 +80D00703C5FB +6DD510E080B1 +87529F30FC58 +B75C4FA614AE +42DC568C64F4 +# Data from "the more the marriott" mifare project (colonelborkmundus) +# aka The Horde +# +# These keys seem to be from Vingcard / Saflok system which means they are diversified +# and not static default keys. To verify this, the UID from such a card is needed. +# +# 20230125-01, Elite Member Marriott Rewards +43012BD9EB87 +# 20230125-02, Elite Member Marriott Rewards +3119A70628EB +# 20230125-03, Elite Member Marriott Rewards +23C9FDD9A366 +# 20230125-04, Elite Member Marriott Rewards +7B4DFC6D6525 +# 20230125-05, Elite Member Marriott Rewards +1330824CD356 +# 20230125-06, Elite Member Marriott Rewards +30AAD6A711EF +# 20230125-07, Fairfield Inn & Suites Marriott +7B3B589A5525 +# 20230125-08, Moxy Hotels +20C166C00ADB +# 20230125-09, Westin Hotels & Resorts +7D0A1C277C05 +2058580A941F +8C29F8320617 +# 20230125-10, Westin Hotels & Resorts +C40964215509 +D44CFC178460 +5697519A8F02 +# 20230125-12, AC Hotels Marriott +7B56B2B38725 +# 20230125-13, AC Hotels Marriott +8EA8EC3F2320 +# 20230125-14, Waldorf Astoria Chicago +011C6CF459E8 +# 20230125-24, Aria Resort & Casino +A18D9F4E75AF +# 20230125-25, Aria Resort & Casino +316B8FAA12EF +# 20230125-26, Residence Inn Mariott +3122AE5341EB +# 20230125-27, Residence Inn Mariott +F72CD208FDF9 +# 20230125-28, Marriott +035C70558D7B +# 20230125-29, Marriott +12AB4C37BB8B +# 20230125-30, Marriott +9966588CB9A0 +# 20230125-31, Sheraton +42FC522DE987 +# 20230125-32, The Industrialist +2158E314C3DF +# 20230125-39, The Ritz-Carlton Balharbour +30FB20D0EFEF +# 20230125-40, The Ritz-Carlton Balharbour +66A3B064CC4B +# 20230125-41, The Ritz-Carlton Balharbour +D18296CD9E6E +# 20230125-42, The Ritz-Carlton Balharbour +D20289CD9E6E +# 20230125-44, Graduate Hotels +209A2B910545 +C49DAE1C6049 +# 20230125-46, AmericInn +8AC04C1A4A25 +# 20230129-53, Marriott Bonvoy +6E029927600D +3E173F64C01C +C670A9AD6066 +# 20230413-69, Westin +487339FA02E0 +# 20230413-70, Marriott Bonvoy +DBD5CA4EE467 +A0B1F234006C +180DE12B700E +# 20230413-71, Westin +1352C68F7A56 +# 20230413-76, Ritz Carlton +318BD98C1CEF +# 20230413-77, Marriott +D23C1CB1216E +# 20230413-78, Caesars +A1D92F808CAF +# 20230413-79, The Cosmopolitan, Vegas +# 20230413-80, Aria +1153C319B4F8 +# 20230413-81, Aria +110C819BBEF8 +# 20230413-82, Aria +1332117E8756 +# 20230413-83, Kimpton +500AE915F50A +5032E362B484 +8B63AB712753 +# 20230413-85, Kimpton +06106E187106 +2E45C23DC541 +D9FF8BEE7550 +# 20230413-87, Marriott +42F7A186BF87 +# 20230413-88, Meritage Resort +D213B093B79A +# 20230413-89, Meritage Resort +216024C49EDF +# 20230413-90, Gaylord Palms +D201DBB6AB6E +# 20230413-91, Residence Inn +9F4AD875BB30 +# 20230413-92, Marriott +3352DB1E8777 +# 20230413-94, Marriott +09074A146605 +151F3E85EC46 +# +# Travelodge by Wyndham Berkeley +0000FFFFFFFF +4663ACD2FFFF +EDC317193709 +# Hotel Santa Cruz +75FAB77E2E5B +# saflok brand HOTEL key +32F093536677 +# A WaterFront Hotel in Oakland +3351916B5A77 +# Ballys (2018) +336E34CC2177 +# Random Hawaiian Hotel +A1670589B2AF +# SF Hotel (SoMa area) +2E0F00700000 +# +# Unknown PACS from Western Australia +CA80E51FA52B +A71E80EA35E1 +05597810D63D +# +# Hotel Key from Las Vegas +EA0CA627FD06 +80BB8436024C +5044068C5183 +# +# Key from Hotel M Montreal (probably diversified) +7E5E05866ED6 +661ABF99AFAD +# +# Key from evo Montreal (probably diversified) +1064BA5D6DF8 +# Hotel key +CE0F4F15E909 +D60DE9436219 +# +# ATM Area de Girona, spanish transport card +A01000000000 +A02000000000 +A03000000000 +A04000000000 +A05000000000 +A06000000000 +A07000000000 +A08000000000 +A09000000000 +A10000000000 +A11000000000 +A12000000000 +A13000000000 +A14000000000 +A15000000000 +B01000000000 +B02000000000 +B03000000000 +B04000000000 +B05000000000 +B06000000000 +B07000000000 +B08000000000 +B09000000000 +B10000000000 +B11000000000 +B12000000000 +B13000000000 +B14000000000 +B15000000000 +# +# Pittsburgh, PA, USA - Pittsburgh Regional Transit ConnectCard +A7AE4A5A33DC +6B857B568C10 +E2CE9A674CBE +A4896B2EBA4E +0724DF9AEDE8 +0E368FB140C1 +874EB25C8721 +5C313F4539CD +C5498606E0A8 +79C69F7EC7C0 +DA7DD0044DA2 +1B8189BD966B +765584147990 +4B7C7C315E6E +46CAAD12C524 +53BD03DEA5C9 +D2D72CB60F59 +14D258786538 +E2E89A375B36 +B3FA87DB0C45 +44D3B1561B34 +2817C6E02F97 +A513FF1232E9 +BD454BD52792 +391771654DC8 +5162797F8E1C +F700BD8E042D +3973ABFD8B66 +CE8BFF3728EE +09938D05DA78 +EACDA4DBE420 +EC2B9FD483CA +# +# Hotel Intelier Orange - Benicasim, Spain +# block 1 - key A +04256CFE0425 +# +# InsideWash Membership Card - Portugal +C18063858BB9 +# +# An apartment building in Sydney Olympic Park +13254608D0AB +24A2971BC0B2 +14264709D1AC +25A3981CC1B3 +1527480AD2AD +26A4991DC2B4 +1628490BD3AE +27A59A1EC3B5 +17294A0CD4AF +28A69B1FC4B6 +182A4B0DD5B0 +29A79C20C5B7 +192B4C0ED6B1 +2AA89D21C6B8 +1A2C4D0FD7B2 +2BA99E22C7B9 +1B2D4E10D8B3 +2CAA9F23C8BA +1C2E4F11D9B4 +2DABA024C9BB +1D2F5012DAB5 +2EACA125CABC +1E305113DBB6 +2FADA226CBBD +1F315214DCB7 +30AEA327CCBE +20325315DDB8 +31AFA428CDBF +21335416DEB9 +32B0A529CEC0 +22345517DFBA +33B1A62ACFC1 +# +# Universidade de São Paulo (USP) student card +17B50E38F1B0 +24E311F594CE +3794FBFB1A54 +43B229069F6A +4531952F765F +4943F2F35E0A +4985E681EF88 +4F56C88E0337 +710070E92C79 +8A036C5C35D4 +A027BD830A06 +D33673C19243 +D89A506542F2 +E5813CD228F1 +FAB943906E9C +# +# R.A.T.T transport card key A/B +AA034F342A55 +456776908C48 +# BusFacil - Brazilian public transport card for some cities +7b296f353c6b +3fa7217ec575 +fae9b14365a9 +c567dd4a6004 +c567dd4a6005 +c567dd4a6006 +c567dd4a6007 +c567dd4a6008 +c567dd4a6009 +c567dd4a600a +c567dd4a600d +c567dd4a600e +c567dd4a600f +5ef014ec5d7f +5086052022ac +bd6af9754c18 +5d67d4732a7d +17fe45604a04 +17fe45604a05 +17fe45604a06 +17fe45604a07 +17fe45604a08 +17fe45604a09 +17fe45604a0a +17fe45604a0d +17fe45604a0e +17fe45604a0f +# keys for swimming pool cards in Reykjavík Iceland +28220F14BEF0 +# key for Orkan keyfobs +300724070486 +# key for Atlantsolía keyfobs +60FCB3C42ABF +# key for hotel in greece +722F24F0722F +# STS Hotel 2A +535453535453 +# Public transport in Sofia, Bulgaria (SKGT) +# upgraded to DESFire since January 2024 +# SKGT common +# Sector 15, key A +f618b3d7855a +# Sector 15, key B +f1afa4da949f +# SKGT multi-use ticket +# Sector 0 +67362dace527 +633a010fa3c3 +# Sector 1 +f93c98655b9c +67ec0a47b0fb +# Sector 2 +54a028818ac7 +b2e87e53c5a0 +# Sector 3 +3e93cf0644b6 +79e12280e219 +# Sector 4 +2204b9fbf033 +4537fd238c8e +# Sector 5 +d1b44a9df05f +cfa526835a1f +# Sector 6 +21cc007ad81c +c097d0a85446 +# Sector 7 +d2268262710f +730bb7b8b3de +# Sector 8 +9fe7c5be7dff +61ae2d920c79 +# Sector 9 +78fcd4470c50 +b638caf7357b +# Sector 10 +0dc1dd7c8ea2 +4c6a6866b934 +# Sector 11 +03de2ceb2ea1 +93e0118b21ed +# Sector 12 +8fbced387bf4 +f57ca95c6edd +# Sector 13 +ef24fe3b4cf7 +8b44d303d62f +# Sector 14 +b1ea40b2caa6 +3abf8431003b +# Sector 15 - see above +# SKGT personalised subscription card +# Sector 0, 2, 16, key A +a0a1a2a3a4a5 +# Sector 8-14, 17-39, key A +ffffffffffff +# Sector 1, key A +# blue +f1df0ca8948b +# yellow +7747b4912984 +# Sector 3, key A +# blue +09d556d57a4b +# yellow +3ed158c6934e +# Sector 4-7, key A +# blue +839dedbfec0d +# yellow +c694a9ed2f9e +# Sector 15 - see above +# Sector 0, 16, key B (blue) +81d55f4551b9 +# Sector 0, key B (yellow) +6e9a040c3c91 +# Sector 1, key B +# blue +5b72c63fb416 +# yellow +a3cdced46371 +# Sector 2, key B +# blue +87a61433d026 +# yellow +9cd3a81f11ab +# Sector 3, key B +# blue +7070d331360c +# yellow +836c790f6e2c +# Sector 4-7, key B +# blue +7fe057787c4f +# yellow +ff59c6d13f88 +# Sector 8-14, 17-39, key B +536f6669614d +# Sector 15 - see above +# End of SKGT +# Hanoi Bus Rapid Transit - 1/2/3 A +AAAFBA10FC37 +C61F2C28DADF +23AACA30CBF2 +# +# Keys dumped from student ID (AGH Cracow - Poland), may be diversified +# Need to be verified +833E4F32589E +432D02DA59F3 +5C161CA2716F +F60B5F9666B8 +98EAC5321D2F +CC945E3FE5C4 +70783C436CF4 +2D186C7149A9 +5D60AC0939FC +93A5CE63C873 +87174550E900 +45675B25A3DA +F91750E629D5 +A3E662ABCDC8 +33D99E9FFA6A +FF7AABA39C61 +A8248C049BEA +C2AF731771C4 +9263B2E0DD80 +CE7FCCBBA5D8 +F8E385E5A2A0 +B27678B5C4AE +D68D7EBB9551 +7AB63F082328 +# +# Payment cards used by Eurest on certain campuses +7E2BC58168EB +# +# Shower cards provided by Seijsener +291A65CBEA7B +344A359BBAD9 +476572726974 +4D696368656C +4F3748E6C826 +69D40AF8B353 +72DEA10F21DF +74845AA8E3F1 +8C3C43EDCC55 +ACD30DFFB434 +D1A27C8EC5DF +F14D329CBDBE +# +# Hotel cards from Austria +AB287B3B4903 +7B0DEDA7E162 +# +#Metro Q transit cards from Huston, Texas +373B72D34B80 +BF3FFC245C9b +7D1D9E7CF8A7 +6A917BF357E6 +B1D461EC62CA +C6BECABEBE8A +66026782D435 +4547E34E40D9 +753897b99AAE +1C36761E8ABD +6D8FBD8CC524 +5A3274779706 +23F13602CC1A +511C1C2C9804 +F8B2B926555E +2593E37D9B2E +41A1F17EE990 +64DD48AEDE88 +7915ED4D9903 +D139DD71DB92 +216D97D46E88 +D9D1C447E427 +911E789433CB +93B43D689F85 +525A869053F1 +69B25667E0B4 +6AACA2D97645 +# UK London Office +435DF6296EC4 +2338B4913222 +# Acces card of students, and more in Occitanie, France +E9A553102EA5 +F982E971CFED +1F42AB9159EE +BBFB836A48B8 +B5D170B2E8F5 +E76978A05F10 +0B1A995DD007 +650DB9CEDB6B +13E54B4448B7 +3E3540C2C273 +A76152840117 +066CCC7666BC +3C0B3AC3AFA3 +CCB541598D72 +1988B5D48EC3 +892EEF0D30FB +0FE5CE5CC640 +# Volgograd (Russia) Volna transport cards keys +2B787A063D5D +D37C8F1793F7 # H World Hotel Chain Room Keys 543071543071 5F01015F0101 diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_update_initial.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_update_initial.c index 7c76260b4fa..a477a08b925 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_update_initial.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_update_initial.c @@ -34,7 +34,7 @@ NfcCommand nfc_mf_classic_update_initial_worker_callback(NfcGenericEvent event, uint8_t sector_num = 0; MfClassicKey key = {}; MfClassicKeyType key_type = MfClassicKeyTypeA; - if(mf_classic_key_cahce_get_next_key( + if(mf_classic_key_cache_get_next_key( instance->mfc_key_cache, §or_num, &key, &key_type)) { mfc_event->data->read_sector_request_data.sector_num = sector_num; mfc_event->data->read_sector_request_data.key = key; diff --git a/applications/main/subghz/helpers/subghz_chat.c b/applications/main/subghz/helpers/subghz_chat.c index 60dd8aa87d2..cb142bb4655 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; - FuriPipeSide* pipe; + 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(furi_pipe_receive(instance->pipe, (uint8_t*)&c, 1, furi_ms_to_ticks(1000)) == 1) { + if(pipe_receive(instance->pipe, (uint8_t*)&c, 1, furi_ms_to_ticks(1000)) == 1) { event.event = SubGhzChatEventInputData; event.c = c; furi_message_queue_put(instance->event_queue, &event, FuriWaitForever); @@ -55,7 +56,7 @@ 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(FuriPipeSide* pipe) { +SubGhzChatWorker* subghz_chat_worker_alloc(PipeSide* pipe) { SubGhzChatWorker* instance = malloc(sizeof(SubGhzChatWorker)); instance->pipe = pipe; diff --git a/applications/main/subghz/helpers/subghz_chat.h b/applications/main/subghz/helpers/subghz_chat.h index ef6a91f0e8e..0d14975064c 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(FuriPipeSide* pipe); +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/helpers/subghz_custom_event.h b/applications/main/subghz/helpers/subghz_custom_event.h index fe2c08fc648..571f3feb9c9 100644 --- a/applications/main/subghz/helpers/subghz_custom_event.h +++ b/applications/main/subghz/helpers/subghz_custom_event.h @@ -49,6 +49,7 @@ typedef enum { SubGhzCustomEventSceneRpcLoad, SubGhzCustomEventSceneRpcButtonPress, SubGhzCustomEventSceneRpcButtonRelease, + SubGhzCustomEventSceneRpcButtonPressRelease, SubGhzCustomEventSceneRpcSessionClose, SubGhzCustomEventViewReceiverOK, diff --git a/applications/main/subghz/scenes/subghz_scene_rpc.c b/applications/main/subghz/scenes/subghz_scene_rpc.c index c1476746d4a..040a151140f 100644 --- a/applications/main/subghz/scenes/subghz_scene_rpc.c +++ b/applications/main/subghz/scenes/subghz_scene_rpc.c @@ -85,6 +85,43 @@ bool subghz_scene_rpc_on_event(void* context, SceneManagerEvent event) { scene_manager_set_scene_state( subghz->scene_manager, SubGhzSceneRpc, SubGhzRpcStateIdle); rpc_system_app_confirm(subghz->rpc_ctx, result); + } else if(event.event == SubGhzCustomEventSceneRpcButtonPressRelease) { + bool result = false; + if(state == SubGhzRpcStateLoaded) { + switch( + subghz_txrx_tx_start(subghz->txrx, subghz_txrx_get_fff_data(subghz->txrx))) { + case SubGhzTxRxStartTxStateErrorOnlyRx: + rpc_system_app_set_error_code( + subghz->rpc_ctx, RpcAppSystemErrorCodeRegionLock); + rpc_system_app_set_error_text( + subghz->rpc_ctx, + "Transmission on this frequency is restricted in your region"); + break; + case SubGhzTxRxStartTxStateErrorParserOthers: + rpc_system_app_set_error_code( + subghz->rpc_ctx, RpcAppSystemErrorCodeInternalParse); + rpc_system_app_set_error_text( + subghz->rpc_ctx, "Error in protocol parameters description"); + break; + + default: //if(SubGhzTxRxStartTxStateOk) + result = true; + subghz_blink_start(subghz); + scene_manager_set_scene_state( + subghz->scene_manager, SubGhzSceneRpc, SubGhzRpcStateTx); + break; + } + } + + // Stop transmission + if(state == SubGhzRpcStateTx) { + subghz_txrx_stop(subghz->txrx); + subghz_blink_stop(subghz); + result = true; + } + scene_manager_set_scene_state( + subghz->scene_manager, SubGhzSceneRpc, SubGhzRpcStateIdle); + rpc_system_app_confirm(subghz->rpc_ctx, result); } else if(event.event == SubGhzCustomEventSceneRpcLoad) { bool result = false; if(state == SubGhzRpcStateIdle) { diff --git a/applications/main/subghz/subghz.c b/applications/main/subghz/subghz.c index 22e81f2eb71..b17a232a893 100644 --- a/applications/main/subghz/subghz.c +++ b/applications/main/subghz/subghz.c @@ -43,6 +43,9 @@ static void subghz_rpc_command_callback(const RpcAppSystemEvent* event, void* co } else if(event->type == RpcAppEventTypeButtonRelease) { view_dispatcher_send_custom_event( subghz->view_dispatcher, SubGhzCustomEventSceneRpcButtonRelease); + } else if(event->type == RpcAppEventTypeButtonPressRelease) { + view_dispatcher_send_custom_event( + subghz->view_dispatcher, SubGhzCustomEventSceneRpcButtonPressRelease); } else { rpc_system_app_confirm(subghz->rpc_ctx, false); } diff --git a/applications/main/subghz/subghz_cli.c b/applications/main/subghz/subghz_cli.c index 8e1b8db0a89..fde18c7e07f 100644 --- a/applications/main/subghz/subghz_cli.c +++ b/applications/main/subghz/subghz_cli.c @@ -17,6 +17,7 @@ #include #include +#include #include "helpers/subghz_chat.h" @@ -69,7 +70,7 @@ static SubGhzEnvironment* subghz_cli_environment_init(void) { return environment; } -void subghz_cli_command_tx_carrier(FuriPipeSide* pipe, FuriString* args, void* context) { +void subghz_cli_command_tx_carrier(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); uint32_t frequency = 433920000; @@ -112,7 +113,7 @@ void subghz_cli_command_tx_carrier(FuriPipeSide* pipe, FuriString* args, void* c furi_hal_power_suppress_charge_exit(); } -void subghz_cli_command_rx_carrier(FuriPipeSide* pipe, FuriString* args, void* context) { +void subghz_cli_command_rx_carrier(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); uint32_t frequency = 433920000; @@ -173,7 +174,7 @@ static const SubGhzDevice* subghz_cli_command_get_device(uint32_t* device_ind) { return device; } -void subghz_cli_command_tx(FuriPipeSide* pipe, 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; @@ -300,7 +301,7 @@ static void subghz_cli_command_rx_callback( furi_string_free(text); } -void subghz_cli_command_rx(FuriPipeSide* pipe, 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 @@ -389,7 +390,7 @@ void subghz_cli_command_rx(FuriPipeSide* pipe, FuriString* args, void* context) free(instance); } -void subghz_cli_command_rx_raw(FuriPipeSide* pipe, FuriString* args, void* context) { +void subghz_cli_command_rx_raw(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); uint32_t frequency = 433920000; @@ -463,7 +464,7 @@ void subghz_cli_command_rx_raw(FuriPipeSide* pipe, FuriString* args, void* conte free(instance); } -void subghz_cli_command_decode_raw(FuriPipeSide* pipe, 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(); @@ -578,7 +579,7 @@ static FuriHalSubGhzPreset subghz_cli_get_preset_name(const char* preset_name) { return preset; } -void subghz_cli_command_tx_from_file(FuriPipeSide* pipe, 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(); @@ -844,7 +845,7 @@ static void subghz_cli_command_print_usage(void) { } } -static void subghz_cli_command_encrypt_keeloq(FuriPipeSide* pipe, FuriString* args) { +static void subghz_cli_command_encrypt_keeloq(PipeSide* pipe, FuriString* args) { UNUSED(pipe); uint8_t iv[16]; @@ -887,7 +888,7 @@ static void subghz_cli_command_encrypt_keeloq(FuriPipeSide* pipe, FuriString* ar furi_string_free(source); } -static void subghz_cli_command_encrypt_raw(FuriPipeSide* pipe, FuriString* args) { +static void subghz_cli_command_encrypt_raw(PipeSide* pipe, FuriString* args) { UNUSED(pipe); uint8_t iv[16]; @@ -924,7 +925,7 @@ static void subghz_cli_command_encrypt_raw(FuriPipeSide* pipe, FuriString* args) furi_string_free(source); } -static void subghz_cli_command_chat(FuriPipeSide* pipe, 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 @@ -1119,7 +1120,7 @@ static void subghz_cli_command_chat(FuriPipeSide* pipe, FuriString* args) { printf("\r\nExit chat\r\n"); } -static void subghz_cli_command(FuriPipeSide* pipe, FuriString* args, void* context) { +static void subghz_cli_command(PipeSide* pipe, FuriString* args, void* context) { FuriString* cmd; cmd = furi_string_alloc(); diff --git a/applications/services/bt/bt_cli.c b/applications/services/bt/bt_cli.c index ec820cc9c30..34a4e45b0ea 100644 --- a/applications/services/bt/bt_cli.c +++ b/applications/services/bt/bt_cli.c @@ -2,13 +2,14 @@ #include #include #include +#include #include #include "bt_settings.h" #include "bt_service/bt.h" #include -static void bt_cli_command_hci_info(FuriPipeSide* pipe, FuriString* args, void* context) { +static void bt_cli_command_hci_info(PipeSide* pipe, FuriString* args, void* context) { UNUSED(pipe); UNUSED(args); UNUSED(context); @@ -19,7 +20,7 @@ static void bt_cli_command_hci_info(FuriPipeSide* pipe, FuriString* args, void* furi_string_free(buffer); } -static void bt_cli_command_carrier_tx(FuriPipeSide* pipe, 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; @@ -51,7 +52,7 @@ static void bt_cli_command_carrier_tx(FuriPipeSide* pipe, FuriString* args, void } while(false); } -static void bt_cli_command_carrier_rx(FuriPipeSide* pipe, FuriString* args, void* context) { +static void bt_cli_command_carrier_rx(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); int channel = 0; @@ -82,7 +83,7 @@ static void bt_cli_command_carrier_rx(FuriPipeSide* pipe, FuriString* args, void } while(false); } -static void bt_cli_command_packet_tx(FuriPipeSide* pipe, 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; @@ -130,7 +131,7 @@ static void bt_cli_command_packet_tx(FuriPipeSide* pipe, FuriString* args, void* } while(false); } -static void bt_cli_command_packet_rx(FuriPipeSide* pipe, 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; @@ -179,7 +180,7 @@ static void bt_cli_print_usage(void) { } } -static void bt_cli(FuriPipeSide* pipe, FuriString* args, void* context) { +static void bt_cli(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); furi_record_open(RECORD_BT); diff --git a/applications/services/cli/cli.c b/applications/services/cli/cli.c index 180c1054dcf..746e95086fb 100644 --- a/applications/services/cli/cli.c +++ b/applications/services/cli/cli.c @@ -2,6 +2,7 @@ #include "cli_i.h" #include "cli_commands.h" #include "cli_ansi.h" +#include struct Cli { CliCommandTree_t commands; @@ -96,9 +97,9 @@ CliCommandTree_t* cli_get_commands(Cli* cli) { return &cli->commands; } -bool cli_app_should_stop(FuriPipeSide* side) { - if(furi_pipe_state(side) == FuriPipeStateBroken) return true; - if(!furi_pipe_bytes_available(side)) return false; +bool cli_app_should_stop(PipeSide* side) { + if(pipe_state(side) == PipeStateBroken) return true; + if(!pipe_bytes_available(side)) return false; char c = getchar(); return c == CliKeyETX; } diff --git a/applications/services/cli/cli.h b/applications/services/cli/cli.h index f98a1600b95..56490fada60 100644 --- a/applications/services/cli/cli.h +++ b/applications/services/cli/cli.h @@ -7,6 +7,7 @@ #include #include #include "cli_ansi.h" +#include #ifdef __cplusplus extern "C" { @@ -40,7 +41,7 @@ typedef struct Cli Cli; * @param [in] args String with what was passed after the command * @param [in] context Whatever you provided to `cli_add_command` */ -typedef void (*CliExecuteCallback)(FuriPipeSide* pipe, FuriString* args, void* context); +typedef void (*CliExecuteCallback)(PipeSide* pipe, FuriString* args, void* context); /** * @brief Registers a command with the CLI. Provides less options than @@ -73,7 +74,7 @@ ARRAY_DEF(CommandCompletions, FuriString*, FURI_STRING_OPLIST); // -V524 * suggestions for the entire `args`. */ typedef void (*CliCompleteCallback)( - FuriPipeSide* pipe, + PipeSide* pipe, FuriString* partial_args, CommandCompletions_t full_args, void* context); @@ -115,7 +116,7 @@ void cli_delete_command(Cli* cli, const char* name); * thread's stdio * @warning This function will consume 0 or 1 bytes from the pipe */ -bool cli_app_should_stop(FuriPipeSide* side); +bool cli_app_should_stop(PipeSide* side); /** Print unified cmd usage tip * diff --git a/applications/services/cli/cli_command_gpio.c b/applications/services/cli/cli_command_gpio.c index 0c375e7db94..f37e6387fb0 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,7 +71,7 @@ static GpioParseReturn gpio_command_parse(FuriString* args, size_t* pin_num, uin return ret; } -void cli_command_gpio_mode(FuriPipeSide* pipe, FuriString* args, void* context) { +void cli_command_gpio_mode(PipeSide* pipe, FuriString* args, void* context) { UNUSED(pipe); UNUSED(context); @@ -110,7 +111,7 @@ void cli_command_gpio_mode(FuriPipeSide* pipe, FuriString* args, void* context) } } -void cli_command_gpio_read(FuriPipeSide* pipe, FuriString* args, void* context) { +void cli_command_gpio_read(PipeSide* pipe, FuriString* args, void* context) { UNUSED(pipe); UNUSED(context); @@ -131,7 +132,7 @@ void cli_command_gpio_read(FuriPipeSide* pipe, FuriString* args, void* context) printf("Pin %s <= %u", gpio_pins[num].name, val); } -void cli_command_gpio_set(FuriPipeSide* pipe, FuriString* args, void* context) { +void cli_command_gpio_set(PipeSide* pipe, FuriString* args, void* context) { UNUSED(pipe); UNUSED(context); @@ -171,7 +172,7 @@ void cli_command_gpio_set(FuriPipeSide* pipe, FuriString* args, void* context) { printf("Pin %s => %u", gpio_pins[num].name, !!value); } -void cli_command_gpio(FuriPipeSide* pipe, FuriString* args, void* context) { +void cli_command_gpio(PipeSide* pipe, FuriString* args, void* context) { FuriString* cmd; cmd = furi_string_alloc(); diff --git a/applications/services/cli/cli_command_gpio.h b/applications/services/cli/cli_command_gpio.h index 285a3a07819..0290949e04d 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(FuriPipeSide* pipe, 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 324cbc9e5ac..d595a16a4f0 100644 --- a/applications/services/cli/cli_commands.c +++ b/applications/services/cli/cli_commands.c @@ -13,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" @@ -36,7 +37,7 @@ 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(FuriPipeSide* pipe, FuriString* args, void* context) { +void cli_command_info(PipeSide* pipe, FuriString* args, void* context) { UNUSED(pipe); if(context) { @@ -55,7 +56,7 @@ void cli_command_info(FuriPipeSide* pipe, FuriString* args, void* context) { } } -void cli_command_help(FuriPipeSide* pipe, FuriString* args, void* context) { +void cli_command_help(PipeSide* pipe, FuriString* args, void* context) { UNUSED(pipe); UNUSED(args); UNUSED(context); @@ -96,7 +97,7 @@ void cli_command_help(FuriPipeSide* pipe, FuriString* args, void* context) { furi_record_close(RECORD_CLI); } -void cli_command_uptime(FuriPipeSide* pipe, FuriString* args, void* context) { +void cli_command_uptime(PipeSide* pipe, FuriString* args, void* context) { UNUSED(pipe); UNUSED(args); UNUSED(context); @@ -104,7 +105,7 @@ void cli_command_uptime(FuriPipeSide* pipe, FuriString* args, void* context) { printf("Uptime: %luh%lum%lus", uptime / 60 / 60, uptime / 60 % 60, uptime % 60); } -void cli_command_date(FuriPipeSide* pipe, FuriString* args, void* context) { +void cli_command_date(PipeSide* pipe, FuriString* args, void* context) { UNUSED(pipe); UNUSED(context); @@ -177,8 +178,8 @@ void cli_command_date(FuriPipeSide* pipe, 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) { - FuriPipeSide* pipe = context; - furi_pipe_send(pipe, buffer, size, FuriWaitForever); + PipeSide* pipe = context; + pipe_send(pipe, buffer, size, FuriWaitForever); } bool cli_command_log_level_set_from_string(FuriString* level) { @@ -200,7 +201,7 @@ bool cli_command_log_level_set_from_string(FuriString* level) { return false; } -void cli_command_log(FuriPipeSide* pipe, FuriString* args, void* context) { +void cli_command_log(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); FuriLogLevel previous_level = furi_log_get_level(); bool restore_log_level = false; @@ -237,7 +238,7 @@ void cli_command_log(FuriPipeSide* pipe, FuriString* args, void* context) { } } -void cli_command_sysctl_debug(FuriPipeSide* pipe, FuriString* args, void* context) { +void cli_command_sysctl_debug(PipeSide* pipe, FuriString* args, void* context) { UNUSED(pipe); UNUSED(context); if(!furi_string_cmp(args, "0")) { @@ -251,7 +252,7 @@ void cli_command_sysctl_debug(FuriPipeSide* pipe, FuriString* args, void* contex } } -void cli_command_sysctl_heap_track(FuriPipeSide* pipe, FuriString* args, void* context) { +void cli_command_sysctl_heap_track(PipeSide* pipe, FuriString* args, void* context) { UNUSED(pipe); UNUSED(context); if(!furi_string_cmp(args, "none")) { @@ -286,7 +287,7 @@ void cli_command_sysctl_print_usage(void) { #endif } -void cli_command_sysctl(FuriPipeSide* pipe, FuriString* args, void* context) { +void cli_command_sysctl(PipeSide* pipe, FuriString* args, void* context) { FuriString* cmd; cmd = furi_string_alloc(); @@ -312,7 +313,7 @@ void cli_command_sysctl(FuriPipeSide* pipe, FuriString* args, void* context) { furi_string_free(cmd); } -void cli_command_vibro(FuriPipeSide* pipe, FuriString* args, void* context) { +void cli_command_vibro(PipeSide* pipe, FuriString* args, void* context) { UNUSED(pipe); UNUSED(context); @@ -339,7 +340,7 @@ void cli_command_vibro(FuriPipeSide* pipe, FuriString* args, void* context) { } } -void cli_command_led(FuriPipeSide* pipe, FuriString* args, void* context) { +void cli_command_led(PipeSide* pipe, FuriString* args, void* context) { UNUSED(pipe); UNUSED(context); // Get first word as light name @@ -394,7 +395,7 @@ void cli_command_led(FuriPipeSide* pipe, FuriString* args, void* context) { furi_record_close(RECORD_NOTIFICATION); } -static void cli_command_top(FuriPipeSide* pipe, FuriString* args, void* context) { +static void cli_command_top(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); int interval = 1000; @@ -466,7 +467,7 @@ static void cli_command_top(FuriPipeSide* pipe, FuriString* args, void* context) furi_thread_list_free(thread_list); } -void cli_command_free(FuriPipeSide* pipe, FuriString* args, void* context) { +void cli_command_free(PipeSide* pipe, FuriString* args, void* context) { UNUSED(pipe); UNUSED(args); UNUSED(context); @@ -480,7 +481,7 @@ void cli_command_free(FuriPipeSide* pipe, FuriString* args, void* context) { printf("Maximum pool block: %zu\r\n", memmgr_pool_get_max_block()); } -void cli_command_free_blocks(FuriPipeSide* pipe, FuriString* args, void* context) { +void cli_command_free_blocks(PipeSide* pipe, FuriString* args, void* context) { UNUSED(pipe); UNUSED(args); UNUSED(context); @@ -488,7 +489,7 @@ void cli_command_free_blocks(FuriPipeSide* pipe, FuriString* args, void* context memmgr_heap_printf_free_blocks(); } -void cli_command_i2c(FuriPipeSide* pipe, FuriString* args, void* context) { +void cli_command_i2c(PipeSide* pipe, FuriString* args, void* context) { UNUSED(pipe); UNUSED(args); UNUSED(context); diff --git a/applications/services/cli/cli_shell.c b/applications/services/cli/cli_shell.c index 7d46a293376..b397fc7c63b 100644 --- a/applications/services/cli/cli_shell.c +++ b/applications/services/cli/cli_shell.c @@ -5,6 +5,7 @@ #include #include #include +#include #define TAG "CliShell" @@ -21,7 +22,7 @@ typedef struct { Cli* cli; FuriEventLoop* event_loop; - FuriPipeSide* pipe; + PipeSide* pipe; CliAnsiParser* ansi_parser; FuriEventLoopTimer* ansi_parsing_timer; @@ -36,14 +37,14 @@ typedef struct { typedef struct { CliCommand* command; - FuriPipeSide* pipe; + PipeSide* pipe; FuriString* args; } CliCommandThreadData; static int32_t cli_command_thread(void* context) { CliCommandThreadData* thread_data = context; if(!(thread_data->command->flags & CliCommandFlagDontAttachStdio)) - furi_pipe_install_as_stdio(thread_data->pipe); + pipe_install_as_stdio(thread_data->pipe); thread_data->command->execute_callback( thread_data->pipe, thread_data->args, thread_data->command->context); @@ -103,13 +104,6 @@ static void cli_shell_execute_command(CliShell* cli_shell, FuriString* command) if(command_data.flags & CliCommandFlagParallelUnsafe) loader_unlock(loader); } -static void cli_shell_tick(void* context) { - CliShell* cli_shell = context; - if(furi_pipe_state(cli_shell->pipe) == FuriPipeStateBroken) { - furi_event_loop_stop(cli_shell->event_loop); - } -} - static size_t cli_shell_prompt_length(CliShell* cli_shell) { UNUSED(cli_shell); return strlen(">: "); @@ -627,8 +621,14 @@ static void cli_shell_process_key(CliShell* cli_shell, CliKeyCombo key_combo) { } } -static void cli_shell_data_available(FuriEventLoopObject* object, void* context) { - UNUSED(object); +static void cli_shell_pipe_broken(PipeSide* pipe, void* context) { + UNUSED(pipe); + 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)); @@ -654,7 +654,7 @@ static void cli_shell_timer_expired(void* context) { cli_shell_process_key(cli_shell, key_combo); } -static CliShell* cli_shell_alloc(FuriPipeSide* pipe) { +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(); @@ -664,22 +664,18 @@ static CliShell* cli_shell_alloc(FuriPipeSide* pipe) { ShellHistory_push_at(cli_shell->history, 0, new_command); furi_string_free(new_command); - cli_shell->pipe = pipe; - furi_pipe_install_as_stdio(cli_shell->pipe); - furi_event_loop_subscribe_pipe( - cli_shell->event_loop, - cli_shell->pipe, - FuriEventLoopEventIn, - cli_shell_data_available, - cli_shell); + CommandCompletions_init(cli_shell->completions); + cli_shell->selected_completion = 0; cli_shell->ansi_parsing_timer = furi_event_loop_timer_alloc( cli_shell->event_loop, cli_shell_timer_expired, FuriEventLoopTimerTypeOnce, cli_shell); - furi_event_loop_tick_set(cli_shell->event_loop, 1, cli_shell_tick, cli_shell); - - CommandCompletions_init(cli_shell->completions); - cli_shell->selected_completion = 0; + cli_shell->pipe = pipe; + pipe_install_as_stdio(cli_shell->pipe); + pipe_attach_to_event_loop(cli_shell->pipe, cli_shell->event_loop); + pipe_set_callback_context(cli_shell->pipe, cli_shell); + pipe_set_data_arrived_callback(cli_shell->pipe, cli_shell_data_available, 0); + pipe_set_broken_callback(cli_shell->pipe, cli_shell_pipe_broken, FuriEventLoopEventFlagEdge); return cli_shell; } @@ -687,8 +683,8 @@ static CliShell* cli_shell_alloc(FuriPipeSide* pipe) { static void cli_shell_free(CliShell* cli_shell) { CommandCompletions_clear(cli_shell->completions); furi_event_loop_timer_free(cli_shell->ansi_parsing_timer); - furi_event_loop_unsubscribe(cli_shell->event_loop, cli_shell->pipe); - furi_pipe_free(cli_shell->pipe); + pipe_detach_from_event_loop(cli_shell->pipe); + pipe_free(cli_shell->pipe); ShellHistory_clear(cli_shell->history); furi_event_loop_free(cli_shell->event_loop); cli_ansi_parser_free(cli_shell->ansi_parser); @@ -731,7 +727,7 @@ static void cli_shell_motd(void) { } static int32_t cli_shell_thread(void* context) { - FuriPipeSide* pipe = context; + PipeSide* pipe = context; CliShell* cli_shell = cli_shell_alloc(pipe); FURI_LOG_D(TAG, "Started"); @@ -744,7 +740,7 @@ static int32_t cli_shell_thread(void* context) { return 0; } -FuriThread* cli_shell_start(FuriPipeSide* pipe) { +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); diff --git a/applications/services/cli/cli_shell.h b/applications/services/cli/cli_shell.h index a50f6a01ee2..2737017deb9 100644 --- a/applications/services/cli/cli_shell.h +++ b/applications/services/cli/cli_shell.h @@ -1,6 +1,7 @@ #pragma once #include +#include #ifdef __cplusplus extern "C" { @@ -9,7 +10,7 @@ extern "C" { #define CLI_SHELL_STACK_SIZE (1 * 1024U) #define CLI_COMMAND_STACK_SIZE (3 * 1024U) -FuriThread* cli_shell_start(FuriPipeSide* pipe); +FuriThread* cli_shell_start(PipeSide* pipe); #ifdef __cplusplus } diff --git a/applications/services/cli/cli_vcp.c b/applications/services/cli/cli_vcp.c index 82df37151d4..3eb38963b4c 100644 --- a/applications/services/cli/cli_vcp.c +++ b/applications/services/cli/cli_vcp.c @@ -4,6 +4,7 @@ #include #include #include +#include #define TAG "CliVcp" @@ -37,7 +38,7 @@ struct CliVcp { bool is_enabled, is_connected; FuriHalUsbInterface* previous_interface; - FuriPipeSide* own_pipe; + PipeSide* own_pipe; bool is_currently_transmitting; size_t previous_tx_length; @@ -58,7 +59,7 @@ static void cli_vcp_maybe_send_data(CliVcp* cli_vcp) { if(!cli_vcp->own_pipe) return; uint8_t buf[USB_CDC_PKT_LEN]; - size_t length = furi_pipe_receive(cli_vcp->own_pipe, buf, sizeof(buf), 0); + size_t length = pipe_receive(cli_vcp->own_pipe, buf, sizeof(buf), 0); if(length > 0 || cli_vcp->previous_tx_length == USB_CDC_PKT_LEN) { FURI_LOG_T(TAG, "cdc_send length=%zu", length); cli_vcp->is_currently_transmitting = true; @@ -74,12 +75,12 @@ static void cli_vcp_maybe_send_data(CliVcp* cli_vcp) { */ static void cli_vcp_maybe_receive_data(CliVcp* cli_vcp) { if(!cli_vcp->own_pipe) return; - if(furi_pipe_spaces_available(cli_vcp->own_pipe) < USB_CDC_PKT_LEN) 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)); FURI_LOG_T(TAG, "cdc_receive length=%zu", length); - furi_check(furi_pipe_send(cli_vcp->own_pipe, buf, length, 0) == length); + furi_check(pipe_send(cli_vcp->own_pipe, buf, length, 0) == length); } // ============= @@ -126,20 +127,19 @@ static CdcCallbacks cdc_callbacks = { .config_callback = NULL, }; -// ================== -// EventLoop handlers -// ================== +// ====================== +// Pipe callback handlers +// ====================== -static void cli_vcp_data_from_shell(FuriEventLoopObject* object, void* context) { - UNUSED(object); +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(FuriEventLoopObject* object, void* context) { - UNUSED(object); +static void cli_vcp_shell_ready(PipeSide* pipe, void* context) { + UNUSED(pipe); CliVcp* cli_vcp = context; - FURI_LOG_T(TAG, "shell_ready"); cli_vcp_maybe_receive_data(cli_vcp); } @@ -201,8 +201,8 @@ static void cli_vcp_internal_message_received(FuriEventLoopObject* object, void* cli_vcp->is_connected = false; // disconnect our side of the pipe - furi_event_loop_unsubscribe(cli_vcp->event_loop, cli_vcp->own_pipe); - furi_pipe_free(cli_vcp->own_pipe); + pipe_detach_from_event_loop(cli_vcp->own_pipe); + pipe_free(cli_vcp->own_pipe); cli_vcp->own_pipe = NULL; break; @@ -219,22 +219,16 @@ static void cli_vcp_internal_message_received(FuriEventLoopObject* object, void* } // start shell thread - FuriPipe pipe = furi_pipe_alloc(VCP_BUF_SIZE, 1); - cli_vcp->own_pipe = pipe.alices_side; - furi_event_loop_subscribe_pipe( - cli_vcp->event_loop, - cli_vcp->own_pipe, - FuriEventLoopEventIn | FuriEventLoopEventFlagEdge, - cli_vcp_data_from_shell, - cli_vcp); - furi_event_loop_subscribe_pipe( - cli_vcp->event_loop, - cli_vcp->own_pipe, - FuriEventLoopEventOut | FuriEventLoopEventFlagEdge, - cli_vcp_shell_ready, - cli_vcp); + 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(pipe.bobs_side); + cli_vcp->shell = cli_shell_start(bundle.bobs_side); break; } } diff --git a/applications/services/crypto/crypto_cli.c b/applications/services/crypto/crypto_cli.c index 91b6e94dbf9..26e082b5606 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(FuriPipeSide* pipe, FuriString* args) { +void crypto_cli_encrypt(PipeSide* pipe, FuriString* args) { int key_slot = 0; bool key_loaded = false; uint8_t iv[16]; @@ -44,7 +45,7 @@ void crypto_cli_encrypt(FuriPipeSide* pipe, FuriString* args) { FuriString* input; input = furi_string_alloc(); char c; - while(furi_pipe_receive(pipe, (uint8_t*)&c, 1, FuriWaitForever) == 1) { + while(pipe_receive(pipe, (uint8_t*)&c, 1, FuriWaitForever) == 1) { if(c == CliKeyETX) { printf("\r\n"); break; @@ -92,7 +93,7 @@ void crypto_cli_encrypt(FuriPipeSide* pipe, FuriString* args) { } } -void crypto_cli_decrypt(FuriPipeSide* pipe, FuriString* args) { +void crypto_cli_decrypt(PipeSide* pipe, FuriString* args) { int key_slot = 0; bool key_loaded = false; uint8_t iv[16]; @@ -119,7 +120,7 @@ void crypto_cli_decrypt(FuriPipeSide* pipe, FuriString* args) { FuriString* hex_input; hex_input = furi_string_alloc(); char c; - while(furi_pipe_receive(pipe, (uint8_t*)&c, 1, FuriWaitForever) == 1) { + while(pipe_receive(pipe, (uint8_t*)&c, 1, FuriWaitForever) == 1) { if(c == CliKeyETX) { printf("\r\n"); break; @@ -164,7 +165,7 @@ void crypto_cli_decrypt(FuriPipeSide* pipe, FuriString* args) { } } -void crypto_cli_has_key(FuriPipeSide* pipe, FuriString* args) { +void crypto_cli_has_key(PipeSide* pipe, FuriString* args) { UNUSED(pipe); int key_slot = 0; uint8_t iv[16] = {0}; @@ -186,7 +187,7 @@ void crypto_cli_has_key(FuriPipeSide* pipe, FuriString* args) { } while(0); } -void crypto_cli_store_key(FuriPipeSide* pipe, FuriString* args) { +void crypto_cli_store_key(PipeSide* pipe, FuriString* args) { UNUSED(pipe); int key_slot = 0; int key_size = 0; @@ -279,7 +280,7 @@ void crypto_cli_store_key(FuriPipeSide* pipe, FuriString* args) { furi_string_free(key_type); } -static void crypto_cli(FuriPipeSide* pipe, FuriString* args, void* context) { +static void crypto_cli(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); FuriString* cmd; cmd = furi_string_alloc(); diff --git a/applications/services/input/input.c b/applications/services/input/input.c index 3ad3170de84..f74b355f1f2 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(FuriPipeSide* pipe, 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 e2f0ca29ece..f1450c26d41 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(FuriPipeSide* pipe, 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 = @@ -47,7 +48,7 @@ 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(FuriPipeSide* pipe, FuriString* args, FuriPubSub* event_pubsub) { +static void input_cli_send(PipeSide* pipe, FuriString* args, FuriPubSub* event_pubsub) { UNUSED(pipe); InputEvent event; FuriString* key_str; @@ -97,7 +98,7 @@ static void input_cli_send(FuriPipeSide* pipe, FuriString* args, FuriPubSub* eve furi_string_free(key_str); } -void input_cli(FuriPipeSide* pipe, FuriString* args, void* context) { +void input_cli(PipeSide* pipe, FuriString* args, void* context) { furi_assert(context); FuriPubSub* event_pubsub = context; FuriString* cmd; diff --git a/applications/services/loader/loader_cli.c b/applications/services/loader/loader_cli.c index efd83cc87cc..7d5a161c28a 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,7 +111,7 @@ static void loader_cli_signal(FuriString* args, Loader* loader) { } } -static void loader_cli(FuriPipeSide* pipe, FuriString* args, void* context) { +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 e74add6369d..9ed86eaab63 100644 --- a/applications/services/power/power_cli.c +++ b/applications/services/power/power_cli.c @@ -4,8 +4,9 @@ #include #include #include +#include -void power_cli_off(FuriPipeSide* pipe, FuriString* args) { +void power_cli_off(PipeSide* pipe, FuriString* args) { UNUSED(pipe); UNUSED(args); Power* power = furi_record_open(RECORD_POWER); @@ -14,21 +15,21 @@ void power_cli_off(FuriPipeSide* pipe, FuriString* args) { power_off(power); } -void power_cli_reboot(FuriPipeSide* pipe, FuriString* args) { +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(FuriPipeSide* pipe, FuriString* args) { +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(FuriPipeSide* pipe, FuriString* args) { +void power_cli_5v(PipeSide* pipe, FuriString* args) { UNUSED(pipe); if(!furi_string_cmp(args, "0")) { furi_hal_power_disable_otg(); @@ -39,7 +40,7 @@ void power_cli_5v(FuriPipeSide* pipe, FuriString* args) { } } -void power_cli_3v3(FuriPipeSide* pipe, FuriString* args) { +void power_cli_3v3(PipeSide* pipe, FuriString* args) { UNUSED(pipe); if(!furi_string_cmp(args, "0")) { furi_hal_power_disable_external_3_3v(); @@ -64,7 +65,7 @@ static void power_cli_command_print_usage(void) { } } -void power_cli(FuriPipeSide* pipe, FuriString* args, void* context) { +void power_cli(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); FuriString* cmd; cmd = furi_string_alloc(); diff --git a/applications/services/rpc/rpc_app.c b/applications/services/rpc/rpc_app.c index aa2a3f64fd6..2b9a6542d77 100644 --- a/applications/services/rpc/rpc_app.c +++ b/applications/services/rpc/rpc_app.c @@ -258,6 +258,41 @@ static void rpc_system_app_button_release(const PB_Main* request, void* context) } } +static void rpc_system_app_button_press_release(const PB_Main* request, void* context) { + furi_assert(request); + furi_assert(request->which_content == PB_Main_app_button_press_release_request_tag); + + RpcAppSystem* rpc_app = context; + furi_assert(rpc_app); + + if(rpc_app->callback) { + FURI_LOG_D(TAG, "ButtonPressRelease"); + + RpcAppSystemEvent event; + event.type = RpcAppEventTypeButtonPressRelease; + + if(strlen(request->content.app_button_press_release_request.args) != 0) { + event.data.type = RpcAppSystemEventDataTypeString; + event.data.string = request->content.app_button_press_release_request.args; + } else { + event.data.type = RpcAppSystemEventDataTypeInt32; + event.data.i32 = request->content.app_button_press_release_request.index; + } + + rpc_system_app_error_reset(rpc_app); + rpc_system_app_set_last_command(rpc_app, request->command_id, &event); + + rpc_app->callback(&event, rpc_app->callback_context); + + } else { + rpc_system_app_send_error_response( + rpc_app, + request->command_id, + PB_CommandStatus_ERROR_APP_NOT_RUNNING, + "ButtonPressRelease"); + } +} + static void rpc_system_app_get_error_process(const PB_Main* request, void* context) { furi_assert(request); furi_assert(request->which_content == PB_Main_app_get_error_request_tag); @@ -332,6 +367,7 @@ void rpc_system_app_confirm(RpcAppSystem* rpc_app, bool result) { rpc_app->last_event_type == RpcAppEventTypeLoadFile || rpc_app->last_event_type == RpcAppEventTypeButtonPress || rpc_app->last_event_type == RpcAppEventTypeButtonRelease || + rpc_app->last_event_type == RpcAppEventTypeButtonPressRelease || rpc_app->last_event_type == RpcAppEventTypeDataExchange); const uint32_t last_command_id = rpc_app->last_command_id; @@ -432,6 +468,9 @@ void* rpc_system_app_alloc(RpcSession* session) { rpc_handler.message_handler = rpc_system_app_button_release; rpc_add_handler(session, PB_Main_app_button_release_request_tag, &rpc_handler); + rpc_handler.message_handler = rpc_system_app_button_press_release; + rpc_add_handler(session, PB_Main_app_button_press_release_request_tag, &rpc_handler); + rpc_handler.message_handler = rpc_system_app_get_error_process; rpc_add_handler(session, PB_Main_app_get_error_request_tag, &rpc_handler); diff --git a/applications/services/rpc/rpc_app.h b/applications/services/rpc/rpc_app.h index aa6fd81cc81..377d9ccb345 100644 --- a/applications/services/rpc/rpc_app.h +++ b/applications/services/rpc/rpc_app.h @@ -90,6 +90,13 @@ typedef enum { * all activities to be conducted while a button is being pressed. */ RpcAppEventTypeButtonRelease, + /** + * @brief The client has informed the application that a button has been pressed and released. + * + * This command's meaning is application-specific, e.g. to perform an action + * once without repeating it. + */ + RpcAppEventTypeButtonPressRelease, /** * @brief The client has sent a byte array of arbitrary size. * @@ -162,6 +169,7 @@ void rpc_system_app_send_exited(RpcAppSystem* rpc_app); * - RpcAppEventTypeLoadFile * - RpcAppEventTypeButtonPress * - RpcAppEventTypeButtonRelease + * - RpcAppEventTypeButtonPressRelease * - RpcAppEventTypeDataExchange * * Not confirming these events will result in a client-side timeout. diff --git a/applications/services/rpc/rpc_cli.c b/applications/services/rpc/rpc_cli.c index 3b30730bd07..587f3212f3e 100644 --- a/applications/services/rpc/rpc_cli.c +++ b/applications/services/rpc/rpc_cli.c @@ -2,11 +2,12 @@ #include #include #include +#include #define TAG "RpcCli" typedef struct { - FuriPipeSide* pipe; + PipeSide* pipe; bool session_close_request; FuriSemaphore* terminate_semaphore; } CliRpc; @@ -20,7 +21,7 @@ static void rpc_cli_send_bytes_callback(void* context, uint8_t* bytes, size_t by CliRpc* cli_rpc = context; while(bytes_len) { - size_t sent = furi_pipe_send(cli_rpc->pipe, bytes, bytes_len, FuriWaitForever); + size_t sent = pipe_send(cli_rpc->pipe, bytes, bytes_len, FuriWaitForever); bytes += sent; bytes_len -= sent; } @@ -40,7 +41,7 @@ static void rpc_cli_session_terminated_callback(void* context) { furi_semaphore_release(cli_rpc->terminate_semaphore); } -void rpc_cli_command_start_session(FuriPipeSide* pipe, FuriString* args, void* context) { +void rpc_cli_command_start_session(PipeSide* pipe, FuriString* args, void* context) { UNUSED(args); furi_assert(pipe); furi_assert(context); @@ -69,9 +70,8 @@ void rpc_cli_command_start_session(FuriPipeSide* pipe, FuriString* args, void* c while(1) { size_received = - furi_pipe_receive(cli_rpc.pipe, buffer, CLI_READ_BUFFER_SIZE, furi_ms_to_ticks(50)); - if((furi_pipe_state(cli_rpc.pipe) == FuriPipeStateBroken) || - cli_rpc.session_close_request) { + pipe_receive(cli_rpc.pipe, buffer, CLI_READ_BUFFER_SIZE, furi_ms_to_ticks(50)); + if((pipe_state(cli_rpc.pipe) == PipeStateBroken) || cli_rpc.session_close_request) { break; } diff --git a/applications/services/rpc/rpc_i.h b/applications/services/rpc/rpc_i.h index ef65283f7d8..51903a2764d 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(FuriPipeSide* pipe, 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 9458420b05a..bb7a08cad79 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,7 +20,7 @@ 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(FuriPipeSide* pipe, FuriString* path, FuriString* args) { +static void storage_cli_info(PipeSide* pipe, FuriString* path, FuriString* args) { UNUSED(pipe); UNUSED(args); Storage* api = furi_record_open(RECORD_STORAGE); @@ -69,7 +70,7 @@ static void storage_cli_info(FuriPipeSide* pipe, FuriString* path, FuriString* a furi_record_close(RECORD_STORAGE); } -static void storage_cli_format(FuriPipeSide* pipe, 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) { @@ -97,7 +98,7 @@ static void storage_cli_format(FuriPipeSide* pipe, FuriString* path, FuriString* } } -static void storage_cli_list(FuriPipeSide* pipe, FuriString* path, FuriString* args) { +static void storage_cli_list(PipeSide* pipe, FuriString* path, FuriString* args) { UNUSED(pipe); UNUSED(args); if(furi_string_cmp_str(path, "/") == 0) { @@ -135,7 +136,7 @@ static void storage_cli_list(FuriPipeSide* pipe, FuriString* path, FuriString* a } } -static void storage_cli_tree(FuriPipeSide* pipe, 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); @@ -177,7 +178,7 @@ static void storage_cli_tree(FuriPipeSide* pipe, FuriString* path, FuriString* a } } -static void storage_cli_read(FuriPipeSide* pipe, FuriString* path, FuriString* args) { +static void storage_cli_read(PipeSide* pipe, FuriString* path, FuriString* args) { UNUSED(pipe); UNUSED(args); Storage* api = furi_record_open(RECORD_STORAGE); @@ -209,7 +210,7 @@ static void storage_cli_read(FuriPipeSide* pipe, FuriString* path, FuriString* a furi_record_close(RECORD_STORAGE); } -static void storage_cli_write(FuriPipeSide* pipe, 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); @@ -265,7 +266,7 @@ static void storage_cli_write(FuriPipeSide* pipe, FuriString* path, FuriString* furi_record_close(RECORD_STORAGE); } -static void storage_cli_read_chunks(FuriPipeSide* pipe, 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); @@ -305,7 +306,7 @@ static void storage_cli_read_chunks(FuriPipeSide* pipe, FuriString* path, FuriSt furi_record_close(RECORD_STORAGE); } -static void storage_cli_write_chunk(FuriPipeSide* pipe, 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); @@ -319,8 +320,8 @@ static void storage_cli_write_chunk(FuriPipeSide* pipe, FuriString* path, FuriSt char buffer[256]; while(need_to_read) { - size_t read_this_time = furi_pipe_receive( - pipe, buffer, MIN(sizeof(buffer), need_to_read), FuriWaitForever); + size_t read_this_time = + pipe_receive(pipe, buffer, MIN(sizeof(buffer), need_to_read), FuriWaitForever); size_t wrote_this_time = storage_file_write(file, buffer, read_this_time); if(wrote_this_time != read_this_time) { @@ -339,7 +340,7 @@ static void storage_cli_write_chunk(FuriPipeSide* pipe, FuriString* path, FuriSt furi_record_close(RECORD_STORAGE); } -static void storage_cli_stat(FuriPipeSide* pipe, FuriString* path, FuriString* args) { +static void storage_cli_stat(PipeSide* pipe, FuriString* path, FuriString* args) { UNUSED(pipe); UNUSED(args); Storage* api = furi_record_open(RECORD_STORAGE); @@ -381,7 +382,7 @@ static void storage_cli_stat(FuriPipeSide* pipe, FuriString* path, FuriString* a furi_record_close(RECORD_STORAGE); } -static void storage_cli_timestamp(FuriPipeSide* pipe, FuriString* path, FuriString* args) { +static void storage_cli_timestamp(PipeSide* pipe, FuriString* path, FuriString* args) { UNUSED(pipe); UNUSED(args); Storage* api = furi_record_open(RECORD_STORAGE); @@ -398,7 +399,7 @@ static void storage_cli_timestamp(FuriPipeSide* pipe, FuriString* path, FuriStri furi_record_close(RECORD_STORAGE); } -static void storage_cli_copy(FuriPipeSide* pipe, FuriString* old_path, FuriString* args) { +static void storage_cli_copy(PipeSide* pipe, FuriString* old_path, FuriString* args) { UNUSED(pipe); Storage* api = furi_record_open(RECORD_STORAGE); FuriString* new_path; @@ -419,7 +420,7 @@ static void storage_cli_copy(FuriPipeSide* pipe, FuriString* old_path, FuriStrin furi_record_close(RECORD_STORAGE); } -static void storage_cli_remove(FuriPipeSide* pipe, FuriString* path, FuriString* args) { +static void storage_cli_remove(PipeSide* pipe, FuriString* path, FuriString* args) { UNUSED(pipe); UNUSED(args); Storage* api = furi_record_open(RECORD_STORAGE); @@ -432,7 +433,7 @@ static void storage_cli_remove(FuriPipeSide* pipe, FuriString* path, FuriString* furi_record_close(RECORD_STORAGE); } -static void storage_cli_rename(FuriPipeSide* pipe, FuriString* old_path, FuriString* args) { +static void storage_cli_rename(PipeSide* pipe, FuriString* old_path, FuriString* args) { UNUSED(pipe); Storage* api = furi_record_open(RECORD_STORAGE); FuriString* new_path; @@ -453,7 +454,7 @@ static void storage_cli_rename(FuriPipeSide* pipe, FuriString* old_path, FuriStr furi_record_close(RECORD_STORAGE); } -static void storage_cli_mkdir(FuriPipeSide* pipe, FuriString* path, FuriString* args) { +static void storage_cli_mkdir(PipeSide* pipe, FuriString* path, FuriString* args) { UNUSED(pipe); UNUSED(args); Storage* api = furi_record_open(RECORD_STORAGE); @@ -466,7 +467,7 @@ static void storage_cli_mkdir(FuriPipeSide* pipe, FuriString* path, FuriString* furi_record_close(RECORD_STORAGE); } -static void storage_cli_md5(FuriPipeSide* pipe, FuriString* path, FuriString* args) { +static void storage_cli_md5(PipeSide* pipe, FuriString* path, FuriString* args) { UNUSED(pipe); UNUSED(args); Storage* api = furi_record_open(RECORD_STORAGE); @@ -493,7 +494,7 @@ static bool tar_extract_file_callback(const char* name, bool is_directory, void* return true; } -static void storage_cli_extract(FuriPipeSide* pipe, FuriString* old_path, FuriString* args) { +static void storage_cli_extract(PipeSide* pipe, FuriString* old_path, FuriString* args) { UNUSED(pipe); FuriString* new_path = furi_string_alloc(); @@ -528,7 +529,7 @@ static void storage_cli_extract(FuriPipeSide* pipe, FuriString* old_path, FuriSt furi_record_close(RECORD_STORAGE); } -typedef void (*StorageCliCommandCallback)(FuriPipeSide* pipe, FuriString* path, FuriString* args); +typedef void (*StorageCliCommandCallback)(PipeSide* pipe, FuriString* path, FuriString* args); typedef struct { const char* command; @@ -633,7 +634,7 @@ static void storage_cli_print_usage(void) { } } -void storage_cli(FuriPipeSide* pipe, FuriString* args, void* context) { +void storage_cli(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); FuriString* cmd; FuriString* path; @@ -669,7 +670,7 @@ void storage_cli(FuriPipeSide* pipe, FuriString* args, void* context) { furi_string_free(cmd); } -static void storage_cli_factory_reset(FuriPipeSide* pipe, FuriString* args, void* context) { +static void storage_cli_factory_reset(PipeSide* pipe, FuriString* args, void* context) { UNUSED(pipe); UNUSED(args); UNUSED(context); diff --git a/applications/settings/notification_settings/notification_settings_app.c b/applications/settings/notification_settings/notification_settings_app.c index ada2bfdd42c..2462b32bdeb 100644 --- a/applications/settings/notification_settings/notification_settings_app.c +++ b/applications/settings/notification_settings/notification_settings_app.c @@ -20,8 +20,11 @@ static const NotificationSequence sequence_note_c = { NULL, }; -#define CONTRAST_COUNT 11 +#define CONTRAST_COUNT 17 const char* const contrast_text[CONTRAST_COUNT] = { + "-8", + "-7", + "-6", "-5", "-4", "-3", @@ -33,8 +36,14 @@ const char* const contrast_text[CONTRAST_COUNT] = { "+3", "+4", "+5", + "+6", + "+7", + "+8", }; const int32_t contrast_value[CONTRAST_COUNT] = { + -8, + -7, + -6, -5, -4, -3, @@ -46,44 +55,47 @@ const int32_t contrast_value[CONTRAST_COUNT] = { 3, 4, 5, + 6, + 7, + 8, }; -#define BACKLIGHT_COUNT 5 +#define BACKLIGHT_COUNT 21 const char* const backlight_text[BACKLIGHT_COUNT] = { - "0%", - "25%", - "50%", - "75%", - "100%", + "0%", "5%", "10%", "15%", "20%", "25%", "30%", "35%", "40%", "45%", "50%", + "55%", "60%", "65%", "70%", "75%", "80%", "85%", "90%", "95%", "100%", }; const float backlight_value[BACKLIGHT_COUNT] = { - 0.0f, - 0.25f, - 0.5f, - 0.75f, - 1.0f, + 0.00f, 0.05f, 0.10f, 0.15f, 0.20f, 0.25f, 0.30f, 0.35f, 0.40f, 0.45f, 0.50f, + 0.55f, 0.60f, 0.65f, 0.70f, 0.75f, 0.80f, 0.85f, 0.90f, 0.95f, 1.00f, }; -#define VOLUME_COUNT 5 +#define VOLUME_COUNT 21 const char* const volume_text[VOLUME_COUNT] = { - "0%", - "25%", - "50%", - "75%", - "100%", + "0%", "5%", "10%", "15%", "20%", "25%", "30%", "35%", "40%", "45%", "50%", + "55%", "60%", "65%", "70%", "75%", "80%", "85%", "90%", "95%", "100%", +}; +const float volume_value[VOLUME_COUNT] = { + 0.00f, 0.05f, 0.10f, 0.15f, 0.20f, 0.25f, 0.30f, 0.35f, 0.40f, 0.45f, 0.50f, + 0.55f, 0.60f, 0.65f, 0.70f, 0.75f, 0.80f, 0.85f, 0.90f, 0.95f, 1.00f, }; -const float volume_value[VOLUME_COUNT] = {0.0f, 0.25f, 0.5f, 0.75f, 1.0f}; -#define DELAY_COUNT 6 +#define DELAY_COUNT 11 const char* const delay_text[DELAY_COUNT] = { "1s", "5s", + "10s", "15s", "30s", "60s", + "90s", "120s", + "5min", + "10min", + "30min", }; -const uint32_t delay_value[DELAY_COUNT] = {1000, 5000, 15000, 30000, 60000, 120000}; +const uint32_t delay_value[DELAY_COUNT] = + {1000, 5000, 10000, 15000, 30000, 60000, 90000, 120000, 300000, 600000, 1800000}; #define VIBRO_COUNT 2 const char* const vibro_text[VIBRO_COUNT] = { diff --git a/applications/system/js_app/js_app.c b/applications/system/js_app/js_app.c index 0d6c3231c98..37c5d6e0557 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,14 +132,14 @@ int32_t js_app(void* arg) { } //-V773 typedef struct { - FuriPipeSide* pipe; + PipeSide* pipe; FuriSemaphore* exit_sem; } JsCliContext; static void js_cli_print(JsCliContext* ctx, const char* msg) { UNUSED(ctx); UNUSED(msg); - furi_pipe_send(ctx->pipe, msg, strlen(msg), FuriWaitForever); + pipe_send(ctx->pipe, msg, strlen(msg), FuriWaitForever); } static void js_cli_exit(JsCliContext* ctx) { @@ -172,7 +173,7 @@ static void js_cli_callback(JsThreadEvent event, const char* msg, void* context) } } -void js_cli_execute(FuriPipeSide* pipe, FuriString* args, void* context) { +void js_cli_execute(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); const char* path = furi_string_get_cstr(args); diff --git a/applications/system/js_app/packages/create-fz-app/pnpm-lock.yaml b/applications/system/js_app/packages/create-fz-app/pnpm-lock.yaml index 58f20a385d2..3f753df15ad 100644 --- a/applications/system/js_app/packages/create-fz-app/pnpm-lock.yaml +++ b/applications/system/js_app/packages/create-fz-app/pnpm-lock.yaml @@ -62,8 +62,8 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - cross-spawn@7.0.3: - resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} eastasianwidth@0.2.0: @@ -240,7 +240,7 @@ snapshots: color-name@1.1.4: {} - cross-spawn@7.0.3: + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 shebang-command: 2.0.0 @@ -256,7 +256,7 @@ snapshots: foreground-child@3.3.0: dependencies: - cross-spawn: 7.0.3 + cross-spawn: 7.0.6 signal-exit: 4.1.0 get-caller-file@2.0.5: {} diff --git a/applications/system/js_app/packages/fz-sdk/package.json b/applications/system/js_app/packages/fz-sdk/package.json index 3c2954c9c4e..f500fae2b93 100644 --- a/applications/system/js_app/packages/fz-sdk/package.json +++ b/applications/system/js_app/packages/fz-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@flipperdevices/fz-sdk", - "version": "0.1.2", + "version": "0.1.3", "description": "Type declarations and documentation for native JS modules available on Flipper Zero", "keywords": [ "flipper", diff --git a/applications/system/js_app/packages/fz-sdk/pnpm-lock.yaml b/applications/system/js_app/packages/fz-sdk/pnpm-lock.yaml index 45944a8546b..67d3bde8240 100644 --- a/applications/system/js_app/packages/fz-sdk/pnpm-lock.yaml +++ b/applications/system/js_app/packages/fz-sdk/pnpm-lock.yaml @@ -8,13 +8,6 @@ importers: .: dependencies: - prompts: - specifier: ^2.4.2 - version: 2.4.2 - serialport: - specifier: ^12.0.0 - version: 12.0.0 - devDependencies: esbuild: specifier: ^0.24.0 version: 0.24.0 @@ -24,6 +17,12 @@ importers: json5: specifier: ^2.2.3 version: 2.2.3 + prompts: + specifier: ^2.4.2 + version: 2.4.2 + serialport: + specifier: ^12.0.0 + version: 12.0.0 typedoc: specifier: ^0.26.10 version: 0.26.10(typescript@5.6.3) diff --git a/applications/system/js_app/packages/fz-sdk/sdk.js b/applications/system/js_app/packages/fz-sdk/sdk.js index 2eecf032dcd..44663203d23 100644 --- a/applications/system/js_app/packages/fz-sdk/sdk.js +++ b/applications/system/js_app/packages/fz-sdk/sdk.js @@ -85,9 +85,21 @@ async function build(config) { async function upload(config) { const appFile = fs.readFileSync(config.input, "utf8"); - const flippers = (await SerialPort.list()).filter(x => x.serialNumber?.startsWith("flip_")); + const serialPorts = await SerialPort.list(); + + let flippers = serialPorts + .filter(x => x.serialNumber?.startsWith("flip_")) + .map(x => ({ path: x.path, name: x.serialNumber.replace("flip_", "") })); + + if (!flippers.length) { + // some Windows installations don't report the serial number correctly; + // filter by STM VCP VID:PID instead + flippers = serialPorts + .filter(x => x?.vendorId === "0483" && x?.productId === "5740") + .map(x => ({ path: x.path, name: x.path })); + } - if (!flippers) { + if (!flippers.length) { console.error("No Flippers found"); process.exit(1); } diff --git a/applications/system/updater/cli/updater_cli.c b/applications/system/updater/cli/updater_cli.c index 80314af4681..01949269f73 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,7 +64,7 @@ static const CliSubcommand update_cli_subcommands[] = { {.command = "help", .handler = updater_cli_help}, }; -static void updater_cli_ep(FuriPipeSide* pipe, FuriString* args, void* context) { +static void updater_cli_ep(PipeSide* pipe, FuriString* args, void* context) { UNUSED(pipe); UNUSED(context); FuriString* subcommand; diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_0.png b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_0.png new file mode 100755 index 0000000000000000000000000000000000000000..f1207ed14b35cd01eafa00342f2801d74b46c622 GIT binary patch literal 858 zcmV-g1Eu_lP)b3G}_zPLJOisK}_wXw<>5vl)fnyEcmLJ)0YM`-qsd9LG<*g zg3uHHfWB#8LN=zM6$A}^iEZj_uEAVvNj6Dq_mbTmAFd!cFTa@&W`4{t&64fs z1O6+|kM;zum_y0;LR;%E{4|RT5jh|65*n~`w!6HK#2L%5CukdP2q1}?sRvZR12EsS zP6BKKYE)f>e;Pi(R|nZq8>DGO9+1VXzqs;Y z>zzX{C5FwW0Jpr#S{j^JpU8;?=-uOueU6(!BS9RndoqvP1aOo&p}tH{j*$Y>F2CbY zJTRxpb}`rb=#NmV8-pBq^GnU*vUM}Lrs&3DVX2NWQRPbV9Eu_gHSp)RdG_xa2L$n& zAzo{&Ly7kt@}aaYyloeLFiehuLtK&CfvZv9i~0aRdxU@_`$?=4uDcSHyHB@B=3sjv z1Yp-C*-y?RIqd_Gb4_y*`NdA5GkU`(19o4%V_h1`c<^#Zb!*T(J_a>H!@b?Pw{we% z1dHI&$2X)22_qD0fIX;@)4QwO+?wU}hx4OTV?~Yn&m|Y9uV0s?!lVme6X@3g$i9tJ zAw<+xNi6@`UU{o9>1u#x3Q(N{@}pCiFtmK`&hbpo$P?pJF1bH9`LkVT#6U2cFbE**&wk(4~aXcues_UO>rxTx;jl=<04a40ce_veR#+-lBGlmLLRKtj8 k04biGE0u;J2K9h{0X->d_eOezlK=n!07*qoM6N<$g2W|{hX4Qo literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_1.png b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_1.png new file mode 100755 index 0000000000000000000000000000000000000000..9d9012281f0193620bc458455f6a93538fad2e96 GIT binary patch literal 855 zcmV-d1E~CoP);NOWB)|bSkT1aRXRBA7yY|TxF{FSfXu8*lUk&&qCXhO8F)FkoA>H2*0;2g2fv& z(d#Xs;?yt-?E~*0`D{sLX^yAU0@$ekWb2zI3N8X{Fs83orr0sQ#X|guwcpNP1=Z{G zSdat3yA1rXr`2nki#Nt*x!NSyKj4u+UwHlRdp$A`_S7#~2^ znalc2P!kjpqG|x})i68iBGTxNe8ACYahNl>a$BAkJMa8EWISSW9x^|7!7He$DqzaK zHH~UJtLg*3q0pI4GT^O->aCwBq?&T_iw|3e_BWDe59<_EmFTb&R`!3M<{$oTix6M< z>)gGM21gU)@>GCLe`>uG{-^#CUE~ZdT5WwA&`g90koSOKH57+|(uvFv@ zu5jrw5_rGj>oQ@wX!V1Y;}RF$?qIcGbR!}y4R^HsbN*(0SXHAg%c)Z)Sl#Bty=5=L zsgMqRmyS4iNFy-1b&V$Edv0=qawEuV>%O>0>(QCF8Rm}903b;}$!G~1tcE58ti4NG z#Li1EDY$>yq#tK|B{A92+mN-R_2;-oJ?~InHFlt@zLj6-(yP6H#HwvaCccFQEMv5L zf=vPm?`HFc&NPYoHp(3VcND9qw+G8F!`Z&vP|e+|N$-{9)>3?5mJ2g}w){{ph6J4J z%MI1syar5eO=xJuFUo-&*Dz1D2A`uKvLp&GC&Xeh15D$Dcrr0Le)4jNZU3t?tW+aRjFfo7> hF5f7XLU{2m{sTJ-XIlu4y4L^z002ovPDHLkV1fh!ld%8* literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_10.png b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_10.png new file mode 100755 index 0000000000000000000000000000000000000000..cb8f173b0bdd8ae661a0e3c000fa88fe256b3a30 GIT binary patch literal 872 zcmV-u1DE`XP)?*-2wV(L$SR8{9F5vK7gWF*K9z%sdYhFTTrr?}P9A_;@_Xs~B$& zdb=C`2i$J}PvkF<5Lip!C9yJmJk zyD)1^<~b8;r*9}UQQZePJA_5QLN`m13s@3OkFb+2eNEr3q|e{3Do1W(`LOHQ*{Ccc zB5$Q4jUTjXj@h}Yt9&X@PQ4@to~kt#+CNgxBxB&i*V>Ql>6XdOn;M1U`%k`JdYzb# zxvXjd@yA2gSI!^4_f;b4ZVha>V{2(}QKcwlYoN5(9sVe;;QMGaV0UFMDS=xLDQI_k zl`c30L1j1u&AC6(zViz%&P+gL(A6Pd>t%<3qVh2Bofy|h5cY3#mMO5li|Y^PWBb5vv#*6Jr_FPc<~!% zzI?osA0I4#%i;aLLUqKAW@~uXa`(Ts#~MgIadhi(T6M8oL-0000;RtW zYC;GgHWEVi?&N>ACW2t*m9`GWO9_C1mL9w88*tuE7R2n%(<4e*f~ghW9``l2w{(Idzmy zutB!vuh7(q6FumKEzuwW3j>H)yTC$Q)<$cl-5z|y-D(1|M{-+S4~zn(Om^kCIMPHS9NRn-wo zb-G{73WfIdhPR*xoUE3%!LeNDq5+&-b6)u9wbmmCRLA3PDHsgjUH*FU4P5pLwkgFV zmxg|AeEVE-xl4Bs3UJRKUC+WFO%Y=k1n4^CS3kxxCIG~RLNWKb&+NDt@uDzso1(=) ziO{b`1j1wkskuLG`gr?8T!vo%MDt|Wxs$H6RMRzvDm|*B)6Vsz{HJQVCiK<`7I%66 zpD+s`5q~AZZ&Y`vK%F#^#?`Zh(igxfh#;bQczHYO!l%F7Qcd2Iw2#!O6k`4kFx=;dmqLL2aI=r`kQ=1B2Is`ynS&V z<-m2T+}iw1!W-ao64<|YDyCoJj>kKX`oA%;cSfI$NThnHV{pp1@th#<3}a7jmBu`P zDqsa5kKKJg0*?!;XMmeM@@8qw(-6&dL7+AUbPP_-V{~{se>K-hd3t2Z!}?=`PrJ;# zQnh39NRQzpt(~lq3*y4nYpOBlET<#Dr92W2XR56aRW)_yVsXJ!W4uiuWfyz#sBd~p naeM(t04ZO&R;l#2#RBjz<}zf`VzpON00000NkvXXu0mjf*Yuj_ literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_12.png b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_12.png new file mode 100755 index 0000000000000000000000000000000000000000..5d4c7e7c55fb7012fa223c9084bfe754c5b50e4f GIT binary patch literal 853 zcmV-b1FHOqP)24#t z)$%{!Q45Ty-Up3=wft=c7FG4Oco@|*;BzJ3rR*z7I#_C0{0L^xB_+HdKGwmDt*hX6 zDH20Ibn!pi0Fyv8^LjZu2{ML(&ZIO+ZL(v#u~+Zi{QU%ZB`c)=A=;6(xnT!)w=IGt z=rYN(4d4fF8i~Na_14g#ID(kx5x^!L#~NS7XoQad>-6bk!-Etiv{*>*vHHuoDNw!k z6Bgu&=ryXNZat%G8DUk&U%K4Vh#e5;u5{tcO?gnuU;Vpo0uJJxMn=zn8dOzP6(9`7 zEDrjmYFK79AIVDKNJDk`d#Z&N9J_G3ad7`tJ->0As;ZJa<+8$t^j++69Ulc2wFZz}~l6 z2i~c<%n15&MiH}mc>V#t(I9v-Q%ybCJHJ&K+pAM;f5?b#dvfM$7{k&>F~d5MxP|iG zuzq5)*a~gZ@(lPVvHC{youmzh+FR<={yxpxF6ZCPB+GWTdbU|UpW2_RHsYTJ;tjyJJ$FNDVQx?~LS0I5z~ f@9uVx0t);KGZb(kKhPw)00000NkvXXu0mjfXi1u-?7W>nA=QF^N=Sn#Tt)k_1K?$nA#5M6H- z3SIFJs3+S?$SlbLxQwjlT}@4XMa_vL+g&bDiVrlO%h|I11tkAJR*h4?o`ocsAzjOk`v+05A z6S0AbLLD1$CbL%`nE^E%K>+(g<*^MDqF^JyR=fZHhu$#kT7e)w$r|myavG?9auyq; zKYZndksi}ZIGTPdw6*@?p1uO!w znz&u}0w4q@tEFA=Y*Y2x6`WjihCh3K<3L|C-CkX!q^iUR3udD4T<)wc*+BBe^$%O` z9DO;pT($(b6I9nTb45d9FA<=3UoiGLeh!Tk@xblL1OCRq3Fd|R3OxmeO3e8Dibr{1 zUeld&q4m*kk=8c%bL`D8HBUyJ+v!b3H;0AgI@Uyu>*@0-ig2hwxUj=>f6jU!NYIQ3 zT4Nn5ydO}Eq;>UexAYxw0t`u2rUq`rdtV~p$Dq)4{V=Ihabs3ZrTcVC0t^1*bx8)pZ36wySQ=&?|`G43`_qCmSb|uFJ9h)-AGPdfGG$gIuCG%s+c7RI1Xef zDMOT|0C&IEz1}^e&Fwi(K3p7`9xLnjeV5W}Gq-NZa%s{B7{ln-2ISGra}oGVt&uw6 z&4cn*Y0_7N<_b`o1d1cmKVj+UrGHN4dS0F!pY|#IzQs2U8)eLn$s>IRC)sFNgSj9c zT%A{qnQ&zVJj&x{G*jL9R6CvI^js_+_-a}HHpK^%`VRKOQ-O7;6vtCo$t)n{i}RJr dAS9px@GqP{Wg0BXhk5`2002ovPDHLkV1gwEk39eY literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_3.png b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_3.png new file mode 100755 index 0000000000000000000000000000000000000000..3a47dfc170224127ad04528accd7b47104c33ae9 GIT binary patch literal 852 zcmV-a1FQUrP)`u%7fF~_5srm=#DzMRcpHmH0U6ukyH3_+-tnN~JbCOQv8kQi2h4Vg#S0unXINGxg zZkHmf(E0H4f3_jULp=X_eme(@K&?>6D75yyarmQomH8?DIxB$n{l}YMtfCMgz$WARRBeK`i7Xb9N38#N{wk>6 zn8t$ai(jKW?KTqz)31beoqg$Au^EG>5F@0_K|5OoAV!jm2e98^DtSPu+D-&p6T7j? z22=T#GHT)qf}e{4E{@RF0Fg$IkAQ;u*%3|?$ZdIC>Ad>eun9>hhRF2Pg|Muus(`6R z)->i?xoVVmxigvOz>5vlTi;O5G~@UuZ#56>ZKQucT&Aq5_(KI(+xu}=dbnaMkX%?k zbML*uvD5@?3a}MTY;@e$8so!6fS&!)_@_xFgSiv|g03v0gM*XQC7OLe7Yx9Bg*ybo z<=>Gw{DDXpaM`TYH+n_1%^eCrrGy!?Ak{p{GzI z8~!pIEf6A&*y#3kR-v@#Cda7$LP?wVC3xSi3@2 z!A?tGJB8q9N&6|Bbg2;*OYx3%VpovwG3?>ipJCRCq5JN3X{KARhKwUBo|D-PFjKokHKSty^2^5?rk6=KdDEDqO$ zlE(C=O?Adb3-7cQG}Eo^pfh<6C=Srv}f|5b-c_7Lq9ky$8NSLWtCuN(*43ja_;nGG^5I8Gz^?q6nP)ZGpk{ZHis=6xRwwiRTHA8TKI}=fhUKOwbyp*p) z2p|>-q51KR|71-Kg38;iIutJ<0D2>80Fl&>=|L8@mlf0m2&d@4?5vamXlM>`E$uM& zLKMOdA7KG!y!qBs72u{$B7pt6@44MDK-D!^V87mRI{WLlLppT;;t_9C|0|_TN*h>^ zj^N5re|xwI8NVphb@q+h3wV$~8SxX!(74qyUqIrNE$9sE#={~=qPFP+W1vSt9(&E1 z0@w#M;|jvVascq_~JyI!tV!l;9SsuG>egr&l+Zp2-t5D{M= zxWD<`Yin~kj-C+Uz#rRbhI1ica{}Z${oyYtVl@D9A=jSz95#dEluuTe-J(51Z@~=S z7ayPQ^NX4scV{*~`TbBe`9Qia-9u8(wg>LXe{9TRN|XGS=5{@Zg<_dt<(Sv&+FJ*o ziN0^Nat=6q6b)+J;3_!3{A4WGN_o||LH+#NqgtL0dO9am(d!IKB4BSt_PHey6Isf7y-< z!wfa!9l9>;)g)X8pEG;~IUur8_8G`M#z14}0c@3I_-9^=i5){Fe2nIfdcshi9*ImQ~PmS^Z0g~t#yK>Pt#X=O#0ChmR iZ!Pxq^*{`ifqwzxpI*j|*hYB(00007mXC8pbK{fp*z8BBm>suB`z94 zFfN2z#O6;ZBKV8g7q#_QCCN@qXeOGrX(7q8#^xpS-n%X)ZoHd&?}2j;hXd^?#CwPA zZOi|FM=da>x(?a|HqzI5V_8*ymmEel_W3|*?omo@k`A{uEM5S!qfr}P5)T`~@!}eU zdlYFxI&$ehTOSiaaP7tRABvyQ0Q#b~$wP^DsbasocXJFd1{r#UZe(Mj5kjgVg2g{! zqNg^2ZK_BB-fyps9Bu~itIHvPje7exFE?RqvK{*YgMaUzt9`2BE?9`~vG&X8RZ#K) z7UV#1mhwdS08C|FSktM~pXWCN$lx48!Zft9CIB81XRJb3h^a7vAc@-4!PamLsmo(G z7;ooH(D5G)LNpx!ygxvW^j~~s^Wd{{%hC2_%Bo6qC>J(LcU4;-n|et6 z;lSJ~e$e#y#ZU2?PPWj?9s!L5~f{i={^$pKjX?pAQF9f0f{TU#lQ!M$*KdX4t-O&vGdPrS(ARtsZ%Cc+2!QQ{=VDb z$ZDTt^&CF36BRUR(S*Xj8=PdQQ7miHV(rM{c2K0j(_N#gNlTJmk~39ouyWD33IS_J z7XP|CXfrx*)k97CSv|YB^_+)F=h8LRI45=q`3b{5Jz4>f1FM)q(809%5-`|T2Vru@ zGKlX3frLxo*sC6#jq*=|cLuA^-vNb@?tE>=D{0cXn5jm8+S&5?R=a$R3xT(~^R*eT zpaBK_K|^ER?D>|0W*YN^<82^co0-IDeXEol(ku3ud+BR(w;t!C()#IT&PhZT>YIlj zV>2mDWvKY@{dLud2K0bP1Opc!9uJ{(@LR+Or^Zxv0OniRg=d^G^)Q+QXo8flT(4F` chyfh@3(5jmrgn0s#I%4<)Bp&osQ-~KgQQ%<@;Hi6Q z(k904#d!5(@qoOhMQKuPI7tL$L1LF^*jJ?G0dL=USP~QG@}2o6Gv9n?23nDej}kdT z%m0YSEikG2H%!)m?dFG!uBqxWM%Pu7wAs|rc~xB&@LEke)|w&YfSVIhi(VG60lZkK zLkJ)i38DG%)&FEo4TAZ%TXiUYLICte)Bqx>AJBu`+PbTtCO|kw2WESz9KfOG5zo>N zW6wt+Z1WKo$c;DOdU_tXrjrO@zvz3m5eBGw1`F)eJ08pa^3{+|?SlBkTh#wbsglwL z7NjG1bEv;P+<=TL33aW!apM*~Bv3_MLKP0Lx6BujIAs$$!@BXY2$HC6I$#WRKPX@? z=B5C40L^%U@TeL9Tz-aQyl;>-6FDHG4=Ro}JPG;MZ=V12InxGY0+11nx>Z$m^oHv9 zJ!00#x3=d1ACr6Z*?8-)XLd*YpYD=NHRbi`cN$$Ut<8r~2SrsSI*|#>#fGlOU8Wcj zUm3W!@y)9%vw8NP5@6SjZ8yW&5U_ax3Y~8Fb4pkZKs?B|r;dlspfu&k3YAUTGxQeC z;5~6Tx=$}?a?qXG_~f^Ts>ucEK6eL6ecK**NB^;2!IUTYHO)7Ch=o#>VCjI@>e^ce z$3)+kI)4&4u^$a;e8_u{`}qgpV!5CiA5uTPvR5nM=;@qLjSnR`MUv~wN8Fz_&NpST ztE3)r-lspDs^Lj;ii`tWV>=jR&xu_{?u0VL{y86*m5xnJE=-M|y$MLj$k!`-mlsi< zTxzKPaa_VxaGVFo0};g;Ab(sd-V^((KVFjIE7|@zXYq53SpZotj{5+^U>rC(`QTCw zd}g-JFvE8P_T8lqRy2G?Q;j+67zeWbbBh=q+%0OVSLKD#IiJk$J6tQSJ7w5Lq&0n+ zT-->gq=I;GdqFj#3?=X+f`LyVo(`eVbsO=(S7ZEtfFwG=E}eCzRE(k-pbkj)^@YB^ d9*BV|@Gpq#TX#gkE++s0002ovPDHLkV1lDnjsE}u literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_7.png b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_7.png new file mode 100755 index 0000000000000000000000000000000000000000..da3a78f4c7d2364f251efcf0d311cfeadf3693ab GIT binary patch literal 860 zcmV-i1Ec(jP)HGw^KWKzD1O2?a4gD9ViVO(ALi=Ao3~!3nM+a`evB?;V{tr$^<9Ht z@Ozx-`5N$}TShE+x8EB(Sg}yy2m+X>`*`iE3Tp8XV3Q$zVtka;2@D3}hg5HzodeaM zuV6r)vTsm07j7o>+()SD%qy4MY8GPL03l%(8fhil9EnrbU|)z+i~uB2(+ps1v~_mb zU^43xhMmAih-NLo^Z-r$6p@vVC;(iIW>)CMlbf<#Y`*@W-|d2p} z`R|OgGP|3p27rY^b0W!r7i;O6Uw)vFYRd8R@6`71T~K%Z4h2;uI^c%mdrNwLec366 z#9s{jc5kkyehI4U0&E4N8_n>0h2Q;HfR25^&}VTGcrHNzuRRTD68cmrOE9xWI~;@g zRa*h!!Xw1`zY<8^2~%tI?Zu-K7rhR99V&Q&Uq>%2}c?`l}k+I{g3Ek&naV}M(`v%`QS-6Vs37`vu}7BFU+ zw1_z;d5(bhha}y&C&I*$mHU1m6Kn? z6o#RHp1Ib6S_zl4d*i*e8be^Bwr9Z`#3bzima(q3{<4?X#IMh*M(>-2!l_2~{L;4& z!N*-~{betw7I$4WbWE^ts-d8%#zHc2259RqPvK~3t8?OkUX;Tl>$Z m(G*Yzq;TDI@s{A_#w=kO&avP_}B;zHLpOl zBas;Lp^N{;2ABlG>9-nlBtgb7(4I6xQk!hsPVA5OZ~bv>g;d?_qHs8 zCFn58i#6aoZwd*)zy03OfjESiml41wZAWWg#%PF-0BiK>lf#1)##t<+_gVSn>=jVG z{sR`|nea`@qtRMM#}dMdj=y@fsTM+t=MXX`pxCl2s58CN>+<(53EMTR1?@1s!UhNz zA0T1yGfqsHXe9d1ERt5*nnQEm^aDj_SFZivmu{xz3DwlI96M&h#chtg~lh@tevO^(pN5SMgwUHpS{$p^2~#jlQbK(aQno=$AI7%fJCweu7e?5MO* zfPHAQHoOzFnGy80j3Q>`;M^m8qe1X&rkZ-Ndv3Edwp*{X{2?Q{<;lBW!5EfaifPse zC7K5Zz}oT2V$<2A=>_n6u=;lW{iFqkTAQj<{vOR*uI87flO;P-K3Q)(pW^Z8`A<00 z+EktLOB!;xepw@9-OS0lf);uk82K1zs!okzwEJFuxzGuDX<*9X{k6sy6*H-vkc1-A z4-Y&=ZNpqEPzm7r4b_BUlYaz4c_8Y}RcoQDmkiA$5`a@kJquYuQ+46qht*5 j04Y!0=|GNY91Bxel{hfU)#i;bjeXFf0r)cg|m z+s-(^7BF=7szJD24*=ER!!^!mfoCz4qOTO&dXl>^D)ywH&zYgxW*M#RUNTZ zr~AaLQfyyu=r(kNlhx8TIFjpJ)Q^*E&T}8W*1G?Y>Udn1qQUU(#jh9Nz-7N^o35DT z!r-rsZ=OjmcIw`J0^IUPH?!~uQ^MFq0Xh%+!yn-p69D2uv6TD#!|b>h@q#e1M#-YT zi_jmA2!zQ7QgeUW_VEscxD3AjvF6E;b0b}CsiqqYR=ZV4r=9tv@`q}=A@tM<7Wa7e z?=TA>5q~wpZw&8Jg*s^>jmvKpyS^|s#~^};=EKW7Q72{kDlOIIJxO~>ohqL9R~WaS zZiU>X=tpQTfTX=xKIz$j{av76>=oq4FoWFzCqO2oyoPZ?PlCeq8MbyKj(@ehbAG|7 zcWB1v7=iJOBZEdWHjA-aY7b&xlmQc{Dndu)E{2@XnsnFVNDlS}PQBY`^8LX}% znuTQJqM~4B#zm-92_+Bf zi2p4g0OtWXwe8q|fXG%h&?xNYu0n&b4sL z^QO4X%7Pd6w}FAu%DJt*<9W?LZ}sJzDy)43;DZ5hUZWUqm44ih4)g{eVM}X9JCviI{E9d-bNa6iy*vm1^Sk%H{e(E@vBc# z-aP=|TlA@2q#79;JlIL1;H0w(ER>@j&yA3SNAp3&T#H2>$DO}UvOsY1@;7Fh)u(4; zt7iZxPyTM3-}yp`+0emQD`kSyQQMw0=@Wb0>0bbdoVK6z&aSpu)4j*~fWh82J(ahs zk^ZhEfYWDrc$Q;hgYH!iU><9-BSbx#yM_UN8nQ>e1vk4G9Zmr(R&7rJ9b9?yXnzv? z9;|Z+f{l5Tx$42x*?ZPK)0H*h3u1W~-EK=+bL4|;=wAanQcUf8|2Udo2oy+Cdv8L! zW>BpbXhV_;PA=Cmfr9i4`d*SM(!HKE(xp(jyCf-y=RThl2`wDk>F<@KOZp)req5=I zt7oqy&E?_}H9tt7zmbrnS|wGI<6b=68QGTva1ZnsG5TzOwD^4e+eWX)-a-qNT3C{T yjOM;xOQ2ei-mUtO+R?u!EKPKl0XTtM6#oJKCt7uy9%sA&0000O9aKtjFBFS<5rrbC%}m9M zUgRQo7XRX1b*D;^N?Ky=V?0!jd8EhM#x;n(iGKn8%i01^Q66Wj%mffr}U0-1Ob zAb^oTl>oMustNt26ldfm(WIn8cS^nw*cymV$9HzJdP{oZX z0DX}26@XXvB7mt|#QDeoiB>l%gnMp9<1RSsACR#L4*RrA4QTF}P@O3{P!=X&k|;Kw z{GG>YUjb)6wR^FoRzzG(K&R@4c1B+{Ck6u80E)uu#8e?{x_(D|nn3F|y%Owv`RPM< z0svYxdV1{gIr|r%6wR|rG z_Ipb9%K2T_WLJOdjU!Ia9LO0bFr9z*qjY7Kfbu;WBwM0Eytoa zv${bA+da&=CaBL3UV&D9ecr}20W+2Rq}Z_9H}LJJs7bb1)9v4MC!20Be7t`SouEjX zTsClwx2-l*f?XqA@%P8snc^`e*gjXTY;jv*Dp1Iz~P7fSP6>B%@=;M*NoN z&=$~kJR8Xvu~Q%XbJo)dAR^srD5aiZ-)UR74#8G8#=Rxu>vO8x(`%goMkdxKM;_5P zZ5{Tx07J%srCf@u9_7-oIH4>{e&LkJ13AL?UJoUdiY%}6iF|KsmFfb)$i%E)32)oT z?B9E2TqG~)Q3Yqo~o zxmYX{aZx*b>OB*Z?-*%R3J$iGAKipVIPm_Zv=l_ja&*?#pgOGkGbAD6?ryXZk;1r$ zgpSDY)KMFee7R0UWLTsi-{lZl4&zc)SuWa$RAgPDvy}TEA-!}aN+5?L00000NkvXX Hu0mjfdXb+( literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_10.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_10.png new file mode 100755 index 0000000000000000000000000000000000000000..5459ae27c3e0b2d87cd3a2b68996ceda57cd6f41 GIT binary patch literal 788 zcmV+v1MB>WP)EtRqp8xfd%lOk=O*D>zE{8R{hs%EA5QbX z69Qmufyo`u1_Y$GvzL2?9p9?;NLR-=tK3H!AMk?h0wr)JIMuBH8gL~tfxn}}F~9Hl z<$3d+?Z7x3h(1Z$TV(8#I-a(CS;x3zBfP(Wy?+u2f9ZjcsCemK z<&Wal6rHP)KJnQ2Yvtay%j@gj8B=M*sn}#EX+NR&Um>=qX!_pf8Yg>hEzIEY$uM!~ zuUHdl*=yJLoQZl3Qs7L?KGb=y3kHxh*8cX{mbzm{kTmB!c5`-UTn&J< zi;4=Jp|JJtQ2@uGxF8J={k*IYuw2(H9Nf*1CmUz4QUwRhM%rSr=%v%@1-VHpG zX>Y!S*k6sK>@W1)wyDj22^axEz0^)k#2Eth(ON0dDZdreV{iwM$xVfJ3=R;nmUjlM zkIP-KkC1@=DueNqUDFnguRnB%j=2~?_}!KM4^rcK@M}knfZv+b-#PpJX`*(@2BJ)} zaNeHDRUsAFK-0he>Q9Cj2YSXfuX=A1S-C3gx@!a32OlmiuH_RBGzvD5ICz5x0uoat z{>dS!r6pY}w)Cs(4!d3ZqOY|2)f=COOUEq0l(VRO$UN>m{f%P*rjW;&`K2T7Tmw8Z6lW$uEFf_3*{&fP*BdvUl3oztSDYF#=e*{#<6Y z@2PT-f}$j~78AujRW#=~%x_4F-kx6gRJYs_*EOv~0=S@wNBws2I@rmXS`A)kr86X- z{0xjPjE+M4?q1+r4UAUOdqB_spqcWYb+E}M%Vo@AOo3b#kW`T*X<5;T>;4B1IZ1|x S#su9y zXzB&EP@D+}ih{(@X_M%bTA~QHMVd@Y+lgp0)1+x;&YU^F4|6efz3jcey;y7gKP>j} zzX$+u{sV*8eH#$awvz2^UTou3wn^%BjQ2M!)KLP8Whk`@B*5FwGi6BBK^~5Vrc%Bx z!!zfWgB5Ly#h+_Mtr*Q?Q|*O~S36pyZ&YD)F=)-@3=BUtO)xW*`MN8{(m6=&19!`NGz28WWYy2D z+1q#;8#7%4>R+{grs+~ag<|;qeU-2dT)UpNLrnkp%Qp?vkksdoM7O&2zCB2q^rAsE zNbCd5P9!bNM#nGd%QDb@1W7?6sy8)N0=&nOq>e}!Nh$?S`fDTL)RSjdZ#ZYc!?#qd zxhr<#Lh17CddFkQqNHjmoZb7Atjo}DRGw6jONIUf+tP50#f}GqIZXdUSuX>36-<}@nO@B3Kfof;!_8)rw+(TC;ykfz3 z+Zs!+ik#i9-MjZss&8!74os3!|1P@JqO^C)hRgXd3V3~VaW*aQ(dK( zBVB#P?3!PZr114b5K-1ir#FsS0MmVwb-Aa;(!pa$k_xATeam5Q`irBg?^UPoj(3>X zyvk%>=v;R5m{&()cOXqk3LlW9 u3nR;Mq(w=3Ly{^F&W|AlYDO5H8~Yc`rGxxvN7noR0000kltwRJ zHqlzsn8JD1t4U*p9c#2VF|pAm3NBb#Y=vFe5@y-ic^+mVzRUaPd*A#M}wUV0b+eehAL4h6(YK+)S~90XEeon$0@jRSee z1**;&2U2LCpW?xeSzJ_+`+MV|XB|L)n`aCBzmD9N9DuHZ(CO(LZ-gH-r2tr3l^L;t zh=jz-8J6!X#34GaY(O_2UWzjVn5E+Yni{+@Dhu{LyZNn9T?(s@Y97h_%fF3;*|l64 z4QBKGnSYn#VJt@ClE2XvRIamHipXqg_Ee4_ZP<)L#To26V6Da&ME`Geo*HOF!ECLr_vT<^KPyZ&y? zf)c1S`fIsY%e9pgb_i6X!JW^z8JU}?A3#?OD*E0FdvVCq0_AEJVIuEarTvP0NgR%&>G&UbFoz$}i=uVA#~b z@Jo4rqi^uVLmy*$I?%p8W;R6omrW~_o$vgsqp~|Tvx_aeVN(MJ(5tCT&tl7lb->bL z&s5C(Vk+OL&9>t;O9{dBF{%qaEO$IV zv>!x*Ga}WfB&%{VxnU`R>F;g-aq}-PM6T`+1B%6<^tj6iUP-m5bf9+=RR4pYg2rr{ zr37mC`h49p*P9mG&Y}c37`tV9rq~y6ndm7OrU1`WT}F2X?v_Q$YsH{bq_CcF994-# zsQJ&G+jo{N?>y+n71xansHHc-xMG2>;u-iTC;$NOo|M2iWDSBIL?2C`f~p6notk`0000R1tyh>M^I#_qv{QP$XrggLvOd7m!M?9MzFFYo`y^Z8emhj(nv z&MH@wAp>JwRUy-zK)tsfn@+-UZl|( zT7umoary&@;Oto&cW3Bxp*q?DA-WPrA>Hx(-0YX#T9+ErYr6N+8=_3c0}@aG`layq zN0$!)qX#{0U+lCF<*WJUhC+sxnbha4L=+=ZR}5>LR2JCsG3^VgcA{F1 zl`I{k9ksc1ZQRVK zs9{9@mEvq55&=BuE5Rz;SH`(93Ah+Q>i_(-uGBK1U6f$$-P@7 z(E61dDTey6mx6n*{JdiTU@i-Y$m03`yu#^%@1Mr^mB8YP>BfrpKk}dvmX*M-&%Chl z^X>1_z%FK#(CW1d&HkQIfJ|&a3Ab}S3uDRBV(L7#cer#4ps%S;ym8{*ajG1z0Yr|u z{Xpp)Y_$i+0q*3CF-`)`7eyD~@B#Cz2I%*C_Lm^JS}}PwK;(S(miZxq>)*I$GhQa# zoxo!sB6A1#DPd~L8DH6BTi&P74SqA!tz1T%l3RUw>0QI)JDF~1od#ln~EgM8``UT%NteSRUuc||C zBnU9%Ym+e8N}%_NW1xh^KT1|xb;RS-5DZ*8Rsxg*uV%r(BToac4{`w@QomVk$$b$K cTk!q=0mCXct2Z>fF#rGn07*qoM6N<$g2`}%OaK4? literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_14.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_14.png new file mode 100755 index 0000000000000000000000000000000000000000..a5d6c1a7a59b8116e393bfade90b5891981070b4 GIT binary patch literal 813 zcmV+|1JeA7P)1v7b7Rs(o7gnOg*DS66-E$$HkCr=ieeTj1et54L@9!bl#=W!D4i)HB3-mD zBK|A}E3_q3jZ`XySZ7>F0;NuAp%t~swa`vAaemsAnaSKauZzi~iDz@Z?>R3o@Aq*S z=YJ;zz_|-X*F75$(7BfFY8CFvt!#@leT=`rD(Wo(#p+rE5BxZLniW6*8EdNR6~6g% zU6f*RCS2RQZ?Wy0b&8$SvTJiO;lI}MFaX>rG!-oiw{H~M8ml10#-{6slXoSKdF6}U zzj|z0Wy%){3Wx>Gtr(Y0qc}Bo9ARYw*+gAcqm|aUnEQ9r;m&Er6R9{RyRj9f&4}QK ztYe+X#{l%Xz{2$p7vpS50&I2w9r`r-`sFNud7MylDk&`m0A0t8yl*#Mc-|xO$9I6g zOe|Mc?Dup=A>d3-ncTx&#%>q^(%`dfZg;_ro*&JK{&~PT!5#NrtgZLNIfm#<)8&E* zFAn(aFOoeu0sTg(_Rd%F);6L>F!u6u#~{8~orUv8Fm>_F7J$%2U%G&0>g%ntfW-- zec$&Vk}J=g8cHOS-9Et0s%s*2!V{5TuLt1}uGy0fVQ`ky0sQvnlH2_0r6`XaLVs`e z9athzww-R@e{A1pB}yHO`%?`mq5hGW?z`%Al-tgfY^~QLeQIyIH=w4criPVln^##{ z8ajX-yn47^O?;G>jZPlTh*-XmYuriYYEnwSB*aYZ)|G?ji}N<2Ym-sQ+3|VzCzWvm z<1sM&XaM&8L{W{63FTzBrKg3Yq8j_8+tS3Y{qxj%ic;ePrNV@+ovg&j-+C{jM#a8u rQ>r~%IE&P{qDHY?HF?>=f6x90pDlLgl6o*(00000NkvXXu0mjf1J{uq literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_15.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_15.png new file mode 100755 index 0000000000000000000000000000000000000000..57b2c9bbee26fbc31ea9e384d8ca8c44936db89f GIT binary patch literal 879 zcmV-#1CacQP)~9or#-tqXwE=trZzs6*sn#Qc^H<9#z~Z znvGTpHdzRj*EHGvpJk|zVmyz3{er~ zRJ4A-qSzE22f*LtK!6Znr32yxpa5V8kOUDc9n>+g7yva{Xara*@Sg)>UsD<0H9F4%#7+c6@3i;;3lqVlFP?gN7EtTWC;%2# z#Rt^ml^V7kc?m2Jsjg(W_|fU|hYloy3n=+GP3T~(?GFi-licd6WZ5MW!Uk-bp0CnH zdV;G!!aw|$6jWZssSq8i``+Ff`{ua=Pdyq(|Kd!Z+7C|A0as;{qC>(E#(S*jb* zz%}pic+asr#b5aDQ=TphvIIU13}^V}&P1 z7AI2-Uz|81a{PEhru0d^T$|K(b8`s#qVomo%#_-Z(oa63UFf(JZk5-&^^##mem^L( zQkLlI9`QY+yJ@_a<)q6fgQ!Q1hAV%kJr^Dbe-7`Zln}2uGrz_1J!{)7|137+9bI#E zCp@g!Y1DQ{b8Xl6lSN-SjJjF%k+ErRSGv>zbOO`Mn$Eh=J2wY_`qSCMt47NYttvkR zpdL=MC505m65=&TyGYK@|CmHX65<^qCsLH7w@vUlBc2OY&dz#+Nb$0G;`s@YvCL6} z$U?t(;>nmuPA()h6rTVf4oyT-h~#c1JSmHa_=&s8e*u(ie_+i17}Nj&002ovPDHLk FV1imcqT>Jn literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_16.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_16.png new file mode 100755 index 0000000000000000000000000000000000000000..4be832c648910992ec977cd567913fc99db14545 GIT binary patch literal 855 zcmV-d1E~CoP)KUdworlw-BFPg1P$sm6{!i*7qQZo8dHS0D;13N z$%nr9vh_vEth9X*1EygLLIXh(icMPr*-6mG{t20q(4A~%=lHOj&8na9}5Wptd<+DbzR3cxt*Ewh4m34o@}VAfhVy@PsMiN2QcLsHlbZ=iN@H^J-wY%Wz~Wu%moQnV?E zl&<|PvNT)+NkmHdy&`3)&FwM(`N?Hs)&)@e2xVUUoXm*_(C8r9Wtr}g<)JKd+m=go)IkZXy)*G?=PZEl_9=ml8wVe`O|AGu6;sJtb0sn3 zXD+({W}vAs*>m!ZR}VcC)`OK3@o2q4QWn6DvXY99$wCKH9)OqDB;-05bL=?}a6g5J zoDs=M=s1q!WE>5ca3Jk{Zb5kKsFLcrV3e8m_9+kUWYCB;27VWN&La=c3~G!tUWc2d z$>vVxHyepoh6`1ZoFx0QlC(8y)c2!})K!XaUMePSME>aSm3*uuk44sOTM5x87pvc! zgYB|EAD9>x3mDb(;WpHey}MZCMl|x#w|}jaqMR%`U5;ZMlw(ppg{Lf1z59wq9nZh^ z*74}6(^f0-y`z8>UYaXZzYtj!xnSEivQ(%JRL@Bv%*mqTIGQ#KpD$%{AWf~ULk_;(y4AEptLo<`ScaMvi$)DcMJ@lu9EEl#Kq`+yXr&IxC;l5FY?FvWz zxh=}_@^8V`xBid=!`l>x#>H&S#Wm-2#X|w`_fknwvCws=AQEnX0FxYFA8RG8|$;q6XG%r=c zSh^nM%kI`Ly9DYGnU(x~U!>GGVZmPJIuh?c$pEM(ME;TFoG+3E*zGC7D6=b#Ju?b8 zXh3q$f8Li0HCPTxFy^m=?SKysXi&jayj!K^n|jMBZ*FwqCPN5yJ}7~{caIKLwUIgI zQ34}VV-1@-1Pmj;K>M@8!?NPh>g<~?5a=>^_UZoWMv@upqFVwhC+@vd3$PdiL}YE^ zUpH|p;d!_4JSDKUV=}+v^Q$i8GifF8;LwrA8^)Qxgn+#_u-)cCD&`4+d7Q(9e}YfUK>b?o8c{&#~h1?Em)xqyb(Ibn|#eaqF=P7ftn__;9!x;sMT`- z>J#Un?;|s3`X$^euZYZh3D48)nY7`I%0ax<_tDf3jzV1+K-KG)K?!TWri^9OC70_N z(D3|n3Q#tjjDd#YTLAPx+y_K*^ZlW$i-?$n=e+=t8zr75sa7BfZXZXg8NeK5LsQ!P!!h0x$q9-Z^pKELh6!9z9p95xv#B_CqZ&{nIMI zj`RjmE?4}-CoxyOig9>&u(XuqnK9NkPzYV6zGK^i3acPu zp+)?B{7}3KVixL3k%|63wxj`C9Wd`lu6u`n^8w6m0_ls7!(GxGK;yVk(!cV=KW`EL z{UYF2NKEN={nW0k0>JUUG*g{TH`^22feg4*%-u$(VX5J3)2mzU&ga~-O%!OJALksR zB}1bT_nL#zozVmM-5#_V5i}JeeH+*4vfx}Ys~4c}uhJ#BVnq0NOwYFgfkmh7$sv|w zMo-5RzB8OwXe;F?$X^`vU|S|ESSdY_{-CbSP5Oj~#=o*#(>)?2lF0zwum5UFqc0am zE*1{I8)KQU@{&=w^2@-i)7%3iKKfK=02nL}U7#U15`T3N!0EI4^QVs3 z?o+h505>pejJ&DsllyfL;PQ3WLZ!6wtkmUsp0~GX&6c?&W*AP7L9h>_%KXGT#YgfZ z$G(NDZ(B=r;KTHL)2s3N3|eY+EoK0iySabsIj-(J*^QK2NEQ7(Qd@6A5Bz~tUdR@e zmwd zO=w(I7>1v7@43m%Pdnq!XvEY+Ce^{zI;~I}qs$a4wzv{|W6%gOq!B?hJ4LH=t)SvU zLZL3S+5`%XorN2PqQ;q(;zAwh!XFwk{Yg8f+L?6H%)QCX{aoBh?YBAbocBDOZ}Bq8 z#@OLe2f(-ij8OoBCQu+Z?xJC081MjeQziKg=}rLj(3K|#+@>d50m@#anMnd^jENBd z(y$MhhFlN{U+_kl_~hA}yyT%={!zS}{;Vgsd*DO800ZZU^j28o*FPpUTV` z+p?e<=X;h5X}i=>ZT$O;3kFeeeQ)*omd<*AHk;Lo!Y##eY|F_`-hK|yYTr-A$u7M< zG{not0JXdnKiB0)%b{6Lonlo^R4Q*R z1cP((gY$&tXp{B`nXL=k>Xo7sykP5=sRw~M^PbJScD#@6X)dNf-Ct{cwPZVhLHkx0 z>PBi(>;CN5S&j8)6i`z|lJicnR|)yO^}4QmAMM%hPyT(U5(?J`>)Nx7cgywkJBJae zo=MfDCaKyNv8Nl35*mN3C+v&eg@oNdoB$DdXh%8dmg>43NL_a{FzqMDE~MuIwkN)E z5Rq!7NaiZ0@xpmBC(vNX1= zNV`=hEsPys#*@zakEsOspa6_lxRos51<*!}LiPTXXO&<0MwoGb=;KSfO_gLWeeVTt zZ%dGg#fl3vYfCN>05mLaPNlLRR8ewkSbL|{LL<))Zudw;3lINOLC4&7sJZ)zqe~f? zEhl0i%cJkFisbrBAckyC&t^r|r8GBbV63}G9Ij6}0O}b7BRetY@Ko|LK$VFEnU%X+ zlJDyVFioV8srzb6?uZWk z`sZl*J$|J+N`YWAqW3kbfs+m48*L*15l=S^zPt9QR!0s$URnwL*@=z^yCXLvq-p$%Z*QysNn4?nzjye9?zUuU)P znq>vWJ1R5g!t{5h4u1iv@)JL_JcQi;Fs)U>vO-&m)P%`&>zIk;z6m4{nUq*}r^HNT zVg)WSa zJB>!GF_F9>tW+9W?b{e9F%c4DB_Wh2tx{T$!VJ!x`sN)MGt#-6d(W41zVCitjvDWJ z)k(8?^m}&uI_OR0=*`vNH!B6r>Oa4_9U~M(@&K4N@UO^Bo33?@h}i$TlK_8=kl(1<4*5_WLJlW{%hR8cAHq-F0tCAlWY!ftXQZrk%tLa767O)Eux&PC|SJHNv)xg;&YW>F+0s!@}Zo>SK zUP@=D0k#&9oOP;=r+1&o0~8A6&Tptle|$wEK>NNa^*e7lcZP?a2Y9xOs%}%)dvD*j zFes~g^eHgioPS6iJo2rFu&WG8IQwXl>A?YlVZW7aKM+3i0u;)C64nwI4m-^=&)u+< z7I4$a&lwqk$h1aQB#iDJ=$;SsL=lwm^?`HaYRmfptnCIR%a{7njh^IeDj**9F>Ihr z%_}`P1`NNLS8`?QoxLAD9v=RIb>a=zu*3WCcu3%V0^nLD)}5)mnO@?92imD?ybAL$ zG7fC58xqAK)PVJ73qY7MDE7cgv*eV4>wO0D-@tlpf8rD{@tP89%bDoUL(9D$M5jlU z&|I26X{`Hm2p2Obfx+aAYu!6z$l7Pz?1Zr{Ivp=Nw(5;}?LqsCG zZ|XCVK>L2yH-Ph)`s-yl*yL`xtOWQ)XX`!4K+(F86On3l@ra0gENqMkozM9HtZM>Pv%4_IT4XK?fbq_sS;#70QGtql&oyq-dfnH;!*_+I}^ns nfJ)I<216I^jeL^CZ$d11@;kWVw|A_%ItK^W7EB z2jgA3l%Z1jQH?Ge_7uDKzug6#rBm-o<-5Lk-#Z@2?=%7|twr@NBbLC+Rx;hHQD*uD ze|wjxIr5P{yUD$wxhVH7j_J5H_964YtMPjLiaJwT z*7BuKEV*h6#E=>zi_P3y?IG+yQY_t^y_0KI>_uwCYQaJfxd3JyX~TrEbGqdNY&(PI zHcxB)u-+U4@Sd}*?3>sOyv>c01;^{LKq~gcuB(@i0nZmVjOOF_r#PQU=w`oDVBM1X z=@2{|9YO3UvdxHdhkkPtE88iLo`pUm_FWh|A75&BJ7mGF`jx(cLiX>*MYv?a)nvbI z9xW1D^s&+PT5o@l)EAku^Lkz*Mr${E_f)g95^L}I(p_Si#fUT zpLd~H1V&H%ZWJDP$8A2gP)boH+IZHsesm_51t4+468n4!v$y`G4FQ7@t6n*@&D}15 zQ?T&I7+}8k0sg{>@<7Y&!lSBjCxNLXhV6eXKv@8UepLL-izjJl_~_k)&GfiYsYKKjefYg<<;ilyvRvkXk?o z(^{5-Ti!w&T+AqtqDP9;YVGErOHUs2a_KuCX-<-)Dd}3NoLUANfIZOAa`EHXP`8`Pm&ZK4P(h<0c%ii$YU%_p4?zWA_o8@|Yp zf>ie)v-71k0U3(S(Mq!y6%{KunKE4OgKf~dCT+UqCb_xChopF3J~-cx!^h#{Bu$yI z=CtRUgALhN00=iY5D*0-`4;g!-~%`do859zHX z01xIuqcI@iHwpXUb5v9+jdv zfJ667{=}xp!B#6++WM!CtT!AOYZ(`}Zylub_fsBpfI)A1k#eo$-iGpQ0c#n)Cyd2L zW*Qyg)pki2z8TeR*CkvSkZL!irJK_#WwR*t ztyFJUs{V$nYMYDXXH+i+^NkaGHT!AS0^?9B^lsYx;B4 z%D?)F(^6{XS%nQ3DY)j-F3qn@N&Q(Q?K@PsE|n`I(pRnnOxC;qfmq~c!rLyA%4^~A zjFg&D_?v5iSkyd{fbqaH6Rrb9hT1;+19k&5%MQQ?wmS)37oOSqTGFP0`}-D;fq5WL zXO8d6QV=B0N-BhvAF@;fNka)qs_N}UmoSr(zE4R~nE8Ay>zL#?>`4QdG6Qa~idZU6uP07*qoM6N<$g5|EIEdT%j literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_23.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_23.png new file mode 100755 index 0000000000000000000000000000000000000000..639834612c846903283f6ed6628f2c79c3fd6a1e GIT binary patch literal 852 zcmV-a1FQUrP)1v7=A^kbt&`HmrW%{6C=M}V3a-o|chR6ILKaRZph%VYN2(T;23%w=byKKd zK^HE>R7EJ0`cp*v12MOv(9&vybP;K2rms`?;8z^xJ(e?|IKT&-o4q z@B1c3g{)W9{bqnFuJ?MtHb&?TwN`-~Tpm1}h}In@`<0F(W5C#|5=)dO83VeSkwxA^ z0AiW}<|UY-O=<})WOKnap1A5&g#sq)@+CKCYL0#A??S}?XT0X{;fJXei5lZOM^By& z#Y^51^Z{EI@fD`7jJ$QLB!Sc}0{W@27@up!0|YR7%L7EYCF|A}SfnTUVqx=`zaA>b z{**6f)TH{{s<zCKn4VKM5y(OT z;I6!Z;83FYU-Ksy0iGE(QJ5U4l$I;y69#k!)pWjThm*S3onjO+pl$~Hv8-?{oD?9zJ?wvK{&E&bT;3D@~|Gk$L#P#-@&bh7VEWB6))9A3Gw6c&P(Z4Y0$ z6`co^HEq9~JYdTGU=3h2j(UB;%HAGd4||~EL*8^-_}q9Fm~R0U&!mEFpYCbz^nK#~ zJVqTw-97??gFx@|eM(L}Ev!8>eJLa)E<1)DAuyJ>asvPgl;H2N*4UK+fZ6Q^n$H2YDFZOPUkOWv{imJCxyJ>2qF)J> z$+fg|qwQh=CetW^F8y!lf!t+j>Fbe}3?gF7 zGV-~Iuk-niyb_?h;drT?;@vF2gc9Hn-LaZPI=#pN5s}5kYp#fl$ZwepA|lqde=s64 zlkdn^Gw8WqxYrh~e3 zL!wPgT(s%}oi=XVV72Mnn3h<@1W^)!4j@#mcBUP$@6pFQE?x^$@8yn@(@;@U~vu zl%z^!OIHK1$LMHl>~L-zE6H!jWk!7SMVMqa0=TghsMH|gY&$-vd+~{g3zMNL4XPR$ zSoLA&-D_G{sultYmE={7<$=NGPqy>)NrH7Now(ffW^24*jRLcmx6_x_2TGyUl|6K@ zVU$7(XD4yABcFh6&e%#VY>GyHaOs2EtM~lC&3^`MLYv#!;AHFb@B<;4nrMZ`~U>1I` ze7g(Z?fYeXoXDb&t^e@-l)kRb9W3}xv834O_1w$uY%V|o{a#5ksPu1 z$ovi$!1>%_=76cxnF_#ljF|2v$^`!&1vqoXLR+as6t#K)e*_k~vH+>qT!?xw;?A#+ zMK-eESKz{{)(9Ry7ug`Qauogfl4pU>+|w3XJud8iZy%ECXIE8~a{UEuxrC%>xf(wx z`A)gKa|%h?lRr_BmZh6LMI>p?H#I5lE`K^L3X8Ri+H#o%@!mnerWs~S<`tEcDn%Pjfu`Kw6H8mQar4y6RQOj#={92Rru`E jl;u7;lYj0beNp@eu20-ipVE6~Uf(?YKCPa%s!3ChnPn!ZgKpNQZ z0|#=Ra)E8K8KD^jI`URfrBJvs4Lm*JSHnEY!*2u6Hf%KZ%y)&qfdgY3&7ZMrPg=08 zq)-BIYqc=uK&SXgO*1yACa|ooO*Opaw^D?$U?xb$2V~AgHo^YTswC@5#0mRg;;bHZ z3FY5+0tsLJx+hr>(+Nid5K29C?!Iv<*S8_6RIaRC+Ou%tvagGQ2(9UseRnTU0}wY@ z^Lm|fW3k-{K-pNk#Co%5YzI)gjb_;+UGmy;WrZgFz1tz%8sSAt>xS+?ku1Rh1i{)6k?NHteb%0MqdDo_0wVf z`n=X!xvPt>{TZ&CXH!%5N*t6l|6YF}v7A`FQ*yi`8I+u8`f|42>8jkSS`_U=%5&Qz z-#lT@e7M&e!K;OpnCmyXRW)$)vvqfN(g(a0wM#DQUEQ6{A|g_~UTP)-KeX5?XDMO0d1lUP{Bp(GzWlHIs((L zt8l}X3p25r+G6!ud_LN&O{7kr+LAGlNV3Tny1V}DG(ZFjKtD=X*ZLx0bgQ}T;nYTl zh=lKFTD(n7E2W%sB4Fn_J13V)$I3Y6wX3v9H{k8w)@c=;W#}?(|1^S2w>W2rTw+{c-BToST=D=u-gmks;^vVo6_VwQX?N4{byAa!t7k%0PfPv$S+D+;X?zot z!aGEs^rz=s;O3gq-t%u>_zd;Hd!s-(Z&beX%uB7%zpV&d*@n!?xPBJKNk`G`n@B%QTE8I-EEN`PPPN8WjZck^Ke6Q^JaxEG%3 z_-PoOa^Y4F+Hu>8FIbC=iCjM>$+c)KUbNOKsb)<4#~ztYHMi!6KuJuByML8S`(`;F zfD&ASiw8?8(aep45-!qHow`RSn&qcK300GcYfU;b$R3aX0WNV?X=dOLZ2$lO07*qo IM6N<$f@c1SEdT%j literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_27.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_27.png new file mode 100755 index 0000000000000000000000000000000000000000..962349bfdec5503429689825577af262921ac6c3 GIT binary patch literal 828 zcmV-C1H=4@P);MYf0)t1eLxntVNLyloX+DW?F4xn>Lx4kC|lVz2hP?>BPG^=lt&B&;Nhe z$Nydh0DT7xZunLpKw~}YuQjGxC8&`#9c5=&N6iIbu2e6O2D^vhwGJQ;rz2I`xM9`h z4OZsney!Bc=1tm9ZrFI?M~Uiv@ueNAX(#}uX4f`q5^7~o&di2&DB5-Eug~SqtX1DP z_x?f9>aIt8y%2v0r+v736~o;DEi^xOh-v}}MRKyT9$ND;fSLNc>P3gEkVkZT&0JYT zM56wYHIUNPsBVi}1ts_tc>iJmpgIjuT269gUOB)ft8$JgKlS47!h_?0g`9*kG0=KK zDuDJpm2)Cp87qz(e6XN$&Y7-CY44@1+-%XKU?6$C>!-d;eF*pXpmNUCzFW^uFOL#* zdw|Lnw*J5;@+}xLYJiw^qRKm&Htcw_)b33 zUNqEb3X6BW3)_NBrX2OtSTd4OJrK_;W?Lp)P+=G^$00KuI7fbI-c;0)xY~v9)6S5* zXSZ8bRxvLAY@zUQF?w^c$I(=<;fF$we>%ki@tEa4`EDLf|WcomHtkl$Pt=2S9cG-e) z5_8wBH44S%G)78~Q10~6%8j{+4DxU($qj3*IpoZ5j|~@^Ui)`TxV0C|wSjr>!qu-y zZkvx?IS1WaHM2&Rw-*#8yCq(l6|wakAriAN@4v~-jJ0000C6`P9 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_28.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_28.png new file mode 100755 index 0000000000000000000000000000000000000000..2eb4456cc780a288fda6b71ff3ca99b931c51b2d GIT binary patch literal 585 zcmV-P0=E5$P)>Nh!OB=PyYTx622!q|kdqdsU?O6C zY`-77ka~b3JP3L3LgIs!0Zd;Hk+vjd7p0MO?%AFTaRBBC085+0@+}?!{nZ#{lBAI| zk}kVDr6OOCFON@Iau4&u`Bx3c7{kY_)lM4#Ej_Q+6-Yh8Z=k>B*GUKAX*Ese$ICP0 z8L+!^i5ZYC-uYR90EnFzb<-`4*OguP)Ht&3V|Fb%C>5PO>Cx+2p|}NS-`J?gV=)ir zz70x62H<0TQ-CQh41ETce^co0FIr!Wq z)xD}&RR9ok+5ix`BC#9JkkfQycE+FiSLaaE>t?%f+t<`R>!|U-2%8EfD`o&RA>zGy z-TmLTSAiX{>=J&-=7C$F==OWd64&-!+h`ToJoNh)^6=0kLjn7IB#T*5XBMEG*2HBJ~;}B+3L_Rues8tiw8w|~g@?NH`D5Z=;plA# Z{Rey_OQicOEv5hf002ovPDHLkV1i=g!{7h_ literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_3.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_3.png new file mode 100755 index 0000000000000000000000000000000000000000..e5772a672f9a63d511a03cd14cba48df73df3662 GIT binary patch literal 812 zcmV+{1JnG8P)Nkl)On1-IBH1q<;PWKD5!)^K#Gm-NXO<@41Ze zzwiNY{sXUWyEY)8t(lEB61#OO-ymB$!zBLg?gY45Y!*ns=rxXRW&kDlp=4^?JLamh z+pLsx{?psQ;nd2H&9$@0>|@QooFtWY03Qs1^AczMhH1c3pIA$Wnni;CTdm=A6HvBD zGSA)yb9kU=Vo_p8H@9Leot2K0zwqQFTQ5l6j6QXzX0%lrC%R+u;nrjJ>a~VMdA&GS z4(?H~2|`92GnJ8N11I`10J~7|-FPYNg|#{NK?{Ww{z7T#*F1oE#NjSICp*rq_k97d za|n?Nb}N4A-5~(?5K6>r4=ISt2ORHP%UI$1<@7syMgR_H)=m8Lea!rPr*}v=q>$rkwl*0<*&ZC zU?yImHA4m%OwQOyZvL-(F;7H0sSqT)ZQ<>F^-XwX41l(z{m9Jo9PDVVMF`%KQW{s)bzRqu$o?Gz2qm&E!1bC94yDt;WaxMT`~j@f z9R;OVIa7k-8>=Q5U;fO!(|0web&Z8)^pVL4F?q0000P43(XaExcZC)XO5g_}>Ap4BQE&%nc>iptZDLR|u?6uJVm%UC@+kLF@2I$vP@~#&R zxZV{tB$?U`%t5S1;qJ5m9kDuJM}JpjA48EIcNKk+9(EOFv?MK_o~iwzsEP7#?DW_H f$=?+J;Lm^?$5Vh#T320I00000NkvXXu0mjfxI%Y8 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_31.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_31.png new file mode 100755 index 0000000000000000000000000000000000000000..ec47b899c994843115098e2b481d4be9e3545bd9 GIT binary patch literal 270 zcmV+p0rCEcP)g#Y&IE8RMH&I=ZT-N9#$G7Z;2?CV z!MD6x1bD!iYcNdCSF^9tY4hauHh`CC?40-Z-=cvn*3{YeK2>8 ze+2U>lk@GCfAh=B#t>UH24K-BvC9r%eZAQz=j37B8kTNI77Uy6BwY4=Pwc+n50F}o UZhAxmRsaA107*qoM6N<$g4SGi=l}o! literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_32.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_32.png new file mode 100755 index 0000000000000000000000000000000000000000..3f345b563e9eeed887691ea64fd476c58e9eade1 GIT binary patch literal 236 zcmVukXtLRdY<7&Yil?6l5smS!)4=ixyV{H|i zEVZ~qQTSxJvPvwAS+^ZYlHpRc4b{SFksE~%lY85BfJbk@bKKYrf4%(Tbt~+$){Jx@>H0 z9v!U*E>kPOV&QLk{US7P`x)X2@FjRl)L3A-;}s^}z6-}5I>m6R*COBYii7&ERd`uB zg+3&W`T8Obyggs*z8c*$1-S7%TAE9JE`yu<`gW@2bNRjVnA)?Z&*lEIf=!Xm246;w!wPWp`HmW)LcC6j8v((m0$X#-)(gTGH^FsQna8H2~_{aHwIgSCq bjE?>S{e=)!nb_>V00000NkvXXu0mjfi(20( literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_34.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_34.png new file mode 100755 index 0000000000000000000000000000000000000000..44c4d0d498b2ebfd6caf104f011430dfe5b1d362 GIT binary patch literal 771 zcmV+e1N{7nP)-)V`@Jcg?b! z8h15C(1l>d;+xnmB8bn0s0B?%Nll~j^76-I($3t^#mqxoUG9(XoO|xacaS8dU}7Ul zvH+yz0pJ4=0SEvKI~0J8h$~QMwbUk6iDv-FZCWX+W})l<4*=N!%mPs`Nk=`_H&U(RQN03dn~zOR4?0F^bcZ+3Jd|f@+2ZE!M+C?0$2hh zw{b3pwgYGh$>~DExEw%JKEP9k2~3drd00M~y~*R%-#{v~l9@dz#;0AHy!)E}^O+Rx z94I<&WVukn2$BWI?n_UP21jE!C^3x zDZI1qZ-3U!AASDMd$7RXZN&e z0fPM>xds;872w$+fB2Cr%~H`{g2wRTyVJ=QSO^(AJ6nsq`o(wFD^F{B%*=)68BWF} zXvTF8XWqhPlMHg|MfEYgan$}7+&OQ0>r%*vAZ@7aw5@LMqSO@9nv+wm?Y$}K6O~oM zi|WC5xD2E}p3qgPtJartKM85w>$fDS`}vAa$%oo~qjz(2j6=ZJSTHBHSJMXE^+B4= zU?IC%uSc~Qb0kp_Bdvd-<4w&=Yi?5=q~4jgms(nr_H|mausIPt6w!cMd<>9Q)~-r& zZE1c*ve0jw1WX8%DOgyo1(dtlNXk_={mO$H@Gr*&ZJJBPG>ZTL002ovPDHLkV1id3 BYq9_U literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_35.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_35.png new file mode 100755 index 0000000000000000000000000000000000000000..f70ecb3abe0a34f98debe1b004eb08b12c98673b GIT binary patch literal 887 zcmV--1Bm>IP)I|X#-uh;n6?-_SZ&e{l2{{$MlW29fAFB}G}ef6 z^We24CdMpP8)HLjLOaozka(ay2$(iyAm9RpvIS%ZcJ_NcEbH}`=Y5{{AKi!E42k#v zphU=jbn`Zm>HmS%9n|H~b!8xcSy~v6Rp-lqj970})rLVV1{%PFlE?{~Lcju=gp(qd z*%bni;C>+|5^&gTdAuyV+9n|%4%uZeCHnQFxLmLD%HZub4lT)5>VmBj+N=Ys2_K~^ zV1&lW)zNY+0M!g#3me@gLq(%!W}f@y-)j(p62OXcD-EhLnzo)IU-P&fxLXyWwT&xb z|HtPlrdaioIrn2=-l|Ag9$u{a^!EC)Ppq60V{voLo_a4%xm$}zC)9i+&-Yb4NaVNu zT}A86)|x5aNgZC!$fT(ViYpi2T@%Uml)-}(Hzy{iL<&;QO|N1JE$x+v{x@M(!=v#?_jqm~nttmr=t z7iljxDh0nY&TkKQ|yw=xuTui4JdN{KB-Euw&K_M zu{BbrM#JrEfIY|m{;WB-CK-FYS(nvM?GAtt1i*TU3t>YvU|1~Pc4N&XpYNDWq}(Rp z%1DKNpNATlm3g=#xh%PX6<18Wv~9zwZ|N?vGQrSH_Q|xNkpJuzI{_f%wM-vl7TrJv3XRa0I{>wWXqt=#1!scW6rfAj(5QHzka zQyVTW<<9`DcMJh@nWN{PdHpKD^JPY&Lac7bwMEwITfBfU#P$ zpzZ?at5EsKk!a|~d!n)&`)d%L`FKEdSYMII!X7zGr3=u>>pS#Q)ytacEojC;7Z+o- zf~u--d0yT_qhcE=ol>1wU2q&x&iL;z`(Ogy;s!}joq9G5+oR*Er}dKKpt^WLd)oE1 z>NPL#d7`Mhy!(hh-O5rh2nM8MqRZx9h;M!aL}7YtJLn0QcYQGMO@3zpl7-zF5M|-c ndusK)F7<&Z%d5Xbx48TZM|mrzzUzGF00000NkvXXu0mjf3B8d` literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_4.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_4.png new file mode 100755 index 0000000000000000000000000000000000000000..7d970234f57cdecc7e45bf765ed313ad92c3161b GIT binary patch literal 890 zcmV-=1BLvFP)6U8pcMp@S4V4`@4ocyo`72k70Lo9~?a<@?V4 z7-HkNVF>`RHUVn@0J9D#kj-8!oah5e0M0sEsTMByP~%y4 zifHE_S9;&`9T#56Ak&f!I@>Q6lLt#!o~PvQmWV0$Q@q{!jiW({!Iqm>)!xxJ?AFuZ zU`dwo>wNprw)yQDKtzs+Bql2VEZ#_Xp67&V7#vKE*?+fZ!Vmso8&H_w08_NvAH?=jCdf)!o1&-%i|OKByIox zqfF}OBHD?>C(oWM%kkR4&uP=|I_j5b%BsXW8h&_k_ouieho*b(@fTn6Bc%mb16K9q z*3XMWKdF`T9i{?#J9BX{Js+F+dx0w|yy5+vx-q!{q<+4=$2V`|kdXo3F66A36uoXB} z6B%s-2h^jkvB2)S^h~NLg^1+yYf==Mu3qsHo)V~txG|rV+P@lK2N5wJ)=f>1h#6iy z<#|>LREs*&0;(mlE0f{!s>rQ;P#O`*uOvKAm2H~}_jk`hrE~}|BS|sCby*QHA9$YU zsQ3v~3!Q-4l>sFSHQSiqlS7WB^FM<_LnNJkFb*ot#_xYEA|jF(lkmKM0Xq7HHc@&K QWdHyG07*qoM6N<$g4?&KI{*Lx literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_5.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_5.png new file mode 100755 index 0000000000000000000000000000000000000000..70eab1f3e8c945560bd8aac429f4ae7e15401456 GIT binary patch literal 819 zcmV-31I+x1P) zUuYav6vlt|=B93H!FAKbeW-EU1Z_*GEfkSfldZI*f?}ae8v4g9A}AVcAQbvgb_ad1 z_+L=a2Qj|sjFwP*$U~(g*ir=9^q~+?$RMFRB|@f9lG)Ad93N&k+xu|ge4Ou`?|%0j zHPQd_kqn)qmV5r}K8jA-fW8>+@Sz(>OpOMw#lnx|~>+7u6}GfM*!KBRqQW$9C?c4A!&7q&C) zZR#>l`I#;9n;`}zfIja1F(kDxeFzxcYip8?Y`a8Eaw+*|eJ)F*YtkW27#2cQ3fD9xxO7i8G3U60x|3hHSFj?51%w@g*Fe6W3Pen(-lWthqH zIKkiFo>@#YdUcaWUK;ygeb245ZyzF3y)D)2H^w|bSsEA$o9Abri#(vmLPTOE(QB~) zSe5~l5WL-02egF&)bGsRKK>VQ(gpckWJY9Pc_ctPj$;TQ18@D9pQ6_VgMkYJ99z_qrZHz^_5-O!*)p0$SA@8>``u9&5i?P zA|=hi@vU}}3!lBz`j-#=wU1y@SuW7CZiv^m0KM@Q0KcH*jeYOjEG9ac0J!&rVWd>M zTm-QI%2S3C^+SMqw+Qi_2_+D0d{@?S)rZC17$6rG63sIoBP%l{B_LDHuT0c^g~i>i z$@@l4X0l=b;Z?7{h=@pS`7uw#(~h&(K}4h;O*1O@MIKm|5nrOJfLwFl+f(jocm#${{bV`LSb#O+VcPa002ovPDHLkV1lqyd=>xz literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_6.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_6.png new file mode 100755 index 0000000000000000000000000000000000000000..8169e0766fd617850ef249e6919e2c7715e8cd9e GIT binary patch literal 799 zcmV+)1K|9LP)@2%14c(S3tq0sqQ7jEH5Dt=*W+!+tAtp8Hr$`!T0+9nA>H+_Nu>*%{ z!r3O^VVig%GbQn&7sQSz>BdX-(8LRNqlAUFbXQ8s>@Yj8hn=OZ-{pJr``+()pXX(q z|D6y3V*`wCc~&4mdNX^kU6}DLUYoRZlwRfq%GQCJzgfTnJ;GS40;s@jr2P7p4z2EO z`BkgUH8%sL>5Hx;%?&bmOPV#=@MWC5ermDatP_F);K8ay`L*yx0CU$U5dPW*AyM(x z^U`0%jVUtakvX+@=#1S?TU}kPT~eh=ycLt~QG(ax)Axz(Ni^|%eTCC)w+3qW$n?oI2)|Li?GX$SGl8*dSc?ud<`f8{IV6KN8%j`Qq+U;K{fgcF08uYQ&u>V>*Dtc2!ZX}?$gX!6H# zq9Cn==(qf}v5Vg(raVadCdiQojiEb=w{x+vg#B^nIU=LSh3(Hwpb#Cg{#fZvXs8rS zpx-R8%_ni*;!%$Q97uyP7Vy2+OWFi7VFxK|`I`@}h6^VQKwSwCd!Kq$AG)axK=pN@ z)ZD^x=k{?|2~f>$uM}>t@@GFFBGOowS((iZ7bT}nfaD|=o_p+uo70GhNU3w8F8_)= z)mjO_!)u>sms1C9KaF7lba$l=XVnG0*L9g&6OrikiTN+(Z)d=9R3ni9re$PLJ@C(h znY`?I@Kz&}CAt4AP%?e+AOz1kfhiA^Jk0C_dGjZ^Z2waRlkTK#qmH2p^tdhSB@q#k dszd{h^B;3hM?PLq!U_NY002ovPDHLkV1lI=dW!%6 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_7.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_7.png new file mode 100755 index 0000000000000000000000000000000000000000..adee1a63dac8b87d57862f233107c1f961f39cac GIT binary patch literal 817 zcmV-11J3-3P)RhGNuC&1^3lbT18NI;gl(OH8CIa|J4-6 z(9S=ILoqD~ih^##tZOy%4^>3nkhO7aYed^*?YbskzI=}tNn_`GbI$pl!}Gk)3x{3& zuL1y^|G>Z{*9HW%EN2fcZERyzc8Sz=jQclD6e|J6GL)JEDR8&*NEuRfkcY2BQz>8a z!ZoKBgJrhG;k3$Z5YIixT?A2j=N`iCBxBp4$A1Hidih4IN`Gk{$) z08G7~Xn=q)d8oQ~xAB^ZOz%*CwyJlUMANgg7yseme0?g$(l$VCe`M3^GyRY*IssWhRbTRzmwo2GAv1_?vkjcY8eA*Zql6w4+_~uC6_XZ@5yYZkJ zq@DvzFOud8@ndK8K^bWM5J^EQt`{{?0^IwMq*yeJrsaW?`NRnL@qv@8)}6B8;#(@# z+?ZG~S2|Z%>$ogflvd5d*|{gpnqgXv%Hs-hUg(dpH3L^!?6F`Vhv~a3>kdP=Q7sal z+%a0YJGrx*gI|q6zmGkh+uTcF(E%j+XHF=YZ_SK!XFi^>K()=g_RFq6cF{f@p10sT zky|(I&Ck1peII>hq4v@}eWf8H!?1Pm7bD8w_(u0hj%`@qWPBZb26TCpn%6&SC_-{V4gwZyxYn-U8Fd&v>xl0)?^# zuHM(>!OCF>?!W@(WZ7Za!0EEuiE^`mY8|g zU7rO>QutJQ;uW3FbW3%h_uie0s&d|)QeA^W<+}BGZFnta8^B+T0{cf3+mI$Cg?CEQ v>5;|Tk>(}o1xc!GnLUCOXsN>Z%vb*cYgu*d%gmGW00000NkvXXu0mjfmz0(k literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_8.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_8.png new file mode 100755 index 0000000000000000000000000000000000000000..db6e667fb77d119f4150c6a1ec875be023846c19 GIT binary patch literal 875 zcmV-x1C;!UP)~KgG zR}iEKYkd&s5>XHzgbo{2);@?U76v}7jm|c0SDM(WxlQlAUmue0ynNw%zURyLoI^kL zGTAy>AJ&1*viblBH+3K&3PcJG#WBDGup1&ZI*~%d$8{V4nu?GFD0|!@zybGp`T!?U z0Vu+FXuJxL@-`K&x$fN}fO2^v@PK`OBbv*7;lLJ~Gm(Khvu{j6SulUGk@WbdCub`` z#Z=lc0L-r_Os)Cmk-CR%xAg-4&)Qm_@CkdyHb!T9$TTis^htr($(@hgP-8ijR#wVq z%mpcm4H(sWsqG$YTgG7ffWpkif6KfeWw8MZbNlgLKhph95n^DFO6A(*_O}BS*bbHr zEidX={A}`M!Uli0op_nh zy7S_t!gp1q>P8?#z*R_CPxQ`+cX9UxkHv&I6 zXB;Pcc;Wu^DaQzc_;96iITfyx{hJFr)~AcC`;s&|vYODkUY~ObGd+?@ML(r5^Yv=ZHt8XtwHhg6(hr?2qSD`z z!Y34FRMNLOi`H#fFb6PcT4~E8X;k$v3Nx#@{{d={ku%1$CRzXh002ovPDHLkV1k;- BrV{`F literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_9.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_9.png new file mode 100755 index 0000000000000000000000000000000000000000..a6a37fe29c7f17cec2d4824961e1acfe37f0d353 GIT binary patch literal 823 zcmV-71IYY|P)de9(=SZW1EY1mdVOhDM_c6MG5vwyzZ`^%H}ee=A}$LQ95 zF=jyHg=34CF#l@Q#%R2kI}nFKWUgz1neW&VR+pM! zBwEhe<@Uy2y7&2nVtj@elmP1LJezOG3#aM=kNA}QW9}aFhB*PvWR_uB5A7lEkYhOWwzPxbk_-U#y zg(S~e8g@uwO?$n#9ByJQ zGvG#6dKXzeCxaQ8l3B+|Bi?fK&dC{e`mUQ!fzM=bPgsdwY<$iqS6e%2d0>LNqEgNQ z9~D6fzibBC`8{xD4m8{csK)>{_^E|vBliO?{{st?Q~gh3V.irtest` in the [assets](https://github.com/flipperdevices/flipperzero-firmware/tree/dev/applications/debug/unit_tests/resources/unit_tests/infrared) directory. 2. Fill it with the test data (more on it below). -3. Add the test code to [infrared_test.c](https://github.com/flipperdevices/flipperzero-firmware/blob/dev/applications/debug/unit_tests/infrared/infrared_test.c). +3. Add the test code to [infrared_test.c](https://github.com/flipperdevices/flipperzero-firmware/blob/dev/applications/debug/unit_tests/tests/infrared/infrared_test.c). 4. Build and install firmware with resources, install it on your Flipper and run the tests to see if they pass. ##### Test data format diff --git a/documentation/doxygen/Doxyfile.cfg b/documentation/doxygen/Doxyfile.cfg index e016317494f..3df12f08f0a 100644 --- a/documentation/doxygen/Doxyfile.cfg +++ b/documentation/doxygen/Doxyfile.cfg @@ -1042,6 +1042,7 @@ EXCLUDE = $(DOXY_SRC_ROOT)/lib/mlib \ $(DOXY_SRC_ROOT)/applications/plugins/dap_link/lib/free-dap \ $(DOXY_SRC_ROOT)/applications/debug \ $(DOXY_SRC_ROOT)/applications/main \ + $(DOXY_SRC_ROOT)/applications/system/js_app/packages \ $(DOXY_SRC_ROOT)/applications/settings \ $(DOXY_SRC_ROOT)/lib/micro-ecc \ $(DOXY_SRC_ROOT)/lib/ReadMe.md \ diff --git a/firmware.scons b/firmware.scons index 4c5e058739d..e7378f957ed 100644 --- a/firmware.scons +++ b/firmware.scons @@ -29,6 +29,8 @@ env = ENV.Clone( TARGETS_ROOT=Dir("#/targets"), LINT_SOURCES=[ Dir("applications"), + # Not C code + Dir("!applications/system/js_app/packages"), ], LIBPATH=[ "${LIB_DIST_DIR}", diff --git a/furi/core/event_loop.c b/furi/core/event_loop.c index 9a17ccc7884..c9e6ece8fa1 100644 --- a/furi/core/event_loop.c +++ b/furi/core/event_loop.c @@ -130,12 +130,12 @@ static inline FuriEventLoopProcessStatus status = furi_event_loop_process_level_event(item); } - instance->current_item = NULL; - if(item->owner == NULL) { status = FuriEventLoopProcessStatusFreeLater; } + instance->current_item = NULL; + return status; } @@ -323,34 +323,27 @@ static void furi_event_loop_object_subscribe( FURI_CRITICAL_ENTER(); - // Get or create item pair - FuriEventLoopItemPair* item_pair_ptr = FuriEventLoopTree_get(instance->tree, object); - FuriEventLoopItemPair item_pair = item_pair_ptr ? *item_pair_ptr : - (FuriEventLoopItemPair){NULL, NULL}; + furi_check(FuriEventLoopTree_get(instance->tree, object) == NULL); // Allocate and setup item FuriEventLoopItem* item = furi_event_loop_item_alloc(instance, contract, object, event); furi_event_loop_item_set_callback(item, callback, context); + FuriEventLoopTree_set_at(instance->tree, object, item); + FuriEventLoopLink* link = item->contract->get_link(object); FuriEventLoopEvent event_noflags = item->event & FuriEventLoopEventMask; if(event_noflags == FuriEventLoopEventIn) { furi_check(link->item_in == NULL); - furi_check(item_pair.in == NULL); link->item_in = item; - item_pair.in = item; } else if(event_noflags == FuriEventLoopEventOut) { furi_check(link->item_out == NULL); - furi_check(item_pair.out == NULL); link->item_out = item; - item_pair.out = item; } else { furi_crash(); } - FuriEventLoopTree_set_at(instance->tree, object, item_pair); - if(!(item->event & FuriEventLoopEventFlagEdge)) { if(item->contract->get_level(item->object, event_noflags)) { furi_event_loop_item_notify(item); @@ -423,26 +416,19 @@ void furi_event_loop_subscribe_mutex( instance, mutex, &furi_mutex_event_loop_contract, event, callback, context); } -void furi_event_loop_subscribe_pipe( - FuriEventLoop* instance, - FuriPipeSide* pipe, - FuriEventLoopEvent event, - FuriEventLoopEventCallback callback, - void* context) { - extern const FuriEventLoopContract furi_pipe_event_loop_contract; - - furi_event_loop_object_subscribe( - instance, pipe, &furi_pipe_event_loop_contract, event, callback, context); -} - /** * Public generic unsubscription API */ -static void furi_event_loop_unsubscribe_item( - FuriEventLoop* instance, - FuriEventLoopObject* object, - FuriEventLoopItem* item) { +void furi_event_loop_unsubscribe(FuriEventLoop* instance, FuriEventLoopObject* object) { + furi_check(instance); + furi_check(instance->thread_id == furi_thread_get_current_id()); + + FURI_CRITICAL_ENTER(); + + FuriEventLoopItem* item = NULL; + furi_check(FuriEventLoopTree_pop_at(&item, instance->tree, object)); + furi_check(item); furi_check(item->owner == instance); @@ -468,20 +454,6 @@ static void furi_event_loop_unsubscribe_item( } else { furi_event_loop_item_free(item); } -} - -void furi_event_loop_unsubscribe(FuriEventLoop* instance, FuriEventLoopObject* object) { - furi_check(instance); - furi_check(instance->thread_id == furi_thread_get_current_id()); - - FURI_CRITICAL_ENTER(); - - FuriEventLoopItemPair item_pair; - furi_check(FuriEventLoopTree_pop_at(&item_pair, instance->tree, object)); - furi_check(item_pair.in || item_pair.out); - - if(item_pair.in) furi_event_loop_unsubscribe_item(instance, object, item_pair.in); - if(item_pair.out) furi_event_loop_unsubscribe_item(instance, object, item_pair.out); FURI_CRITICAL_EXIT(); } @@ -491,8 +463,8 @@ bool furi_event_loop_is_subscribed(FuriEventLoop* instance, FuriEventLoopObject* furi_check(instance->thread_id == furi_thread_get_current_id()); FURI_CRITICAL_ENTER(); - const FuriEventLoopItemPair* item_pair = FuriEventLoopTree_cget(instance->tree, object); - bool result = !!item_pair; + FuriEventLoopItem* const* item = FuriEventLoopTree_cget(instance->tree, object); + bool result = !!item; FURI_CRITICAL_EXIT(); return result; diff --git a/furi/core/event_loop.h b/furi/core/event_loop.h index b00637b2328..d5e8710a69e 100644 --- a/furi/core/event_loop.h +++ b/furi/core/event_loop.h @@ -40,8 +40,7 @@ typedef enum { * - One or more items were inserted into a FuriMessageQueue, * - Enough data has been written to a FuriStreamBuffer, * - A FuriSemaphore has been released at least once, - * - A FuriMutex has been released, - * - Enough data is available to be read out from a FuriPipeSide. + * - A FuriMutex has been released. */ FuriEventLoopEventIn = 0x00000001U, /** @@ -51,8 +50,7 @@ typedef enum { * - One or more items were removed from a FuriMessageQueue, * - Any amount of data has been read out of a FuriStreamBuffer, * - A FuriSemaphore has been acquired at least once, - * - A FuriMutex has been acquired, - * - Any amount of data has been read out of a FuriPipeSide. + * - A FuriMutex has been acquired. */ FuriEventLoopEventOut = 0x00000002U, /** @@ -306,27 +304,6 @@ void furi_event_loop_subscribe_mutex( FuriEventLoopEventCallback callback, void* context); -/** Opaque pipe side type */ -typedef struct FuriPipeSide FuriPipeSide; - -/** - * Subscribe to pipe events - * - * @warning you can only have one subscription for one event type. - * - * @param instance The Event Loop instance - * @param pipe The Pipe to add - * @param[in] event The Event Loop event to trigger on - * @param[in] callback The callback to call on event - * @param context The context for callback - */ -void furi_event_loop_subscribe_pipe( - FuriEventLoop* instance, - FuriPipeSide* pipe, - FuriEventLoopEvent event, - FuriEventLoopEventCallback callback, - void* context); - /** 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 8b5a188b0b4..ef2774b97eb 100644 --- a/furi/core/event_loop_i.h +++ b/furi/core/event_loop_i.h @@ -33,18 +33,13 @@ ILIST_DEF(WaitingList, FuriEventLoopItem, M_POD_OPLIST) /* Event Loop RB tree */ #define FURI_EVENT_LOOP_TREE_RANK (4) -typedef struct { - FuriEventLoopItem* in; - FuriEventLoopItem* out; -} FuriEventLoopItemPair; - BPTREE_DEF2( // NOLINT FuriEventLoopTree, FURI_EVENT_LOOP_TREE_RANK, FuriEventLoopObject*, /* pointer to object we track */ M_PTR_OPLIST, - FuriEventLoopItemPair, /* pointers to the two FuriEventLoopItem */ - M_POD_OPLIST) + FuriEventLoopItem*, /* pointer to the FuriEventLoopItem */ + M_PTR_OPLIST) #define M_OPL_FuriEventLoopTree_t() BPTREE_OPLIST(FuriEventLoopTree, M_POD_OPLIST) diff --git a/furi/core/memmgr.c b/furi/core/memmgr.c index d3ff873ae50..8ee0d1723b7 100644 --- a/furi/core/memmgr.c +++ b/furi/core/memmgr.c @@ -106,5 +106,7 @@ void* aligned_malloc(size_t size, size_t alignment) { } void aligned_free(void* p) { - free(((void**)p)[-1]); + if(p) { + free(((void**)p)[-1]); + } } diff --git a/furi/core/pipe.c b/furi/core/pipe.c deleted file mode 100644 index d62ad0e61b6..00000000000 --- a/furi/core/pipe.c +++ /dev/null @@ -1,180 +0,0 @@ -#include "pipe.h" -#include "stream_buffer.h" -#include "semaphore.h" -#include "mutex.h" -#include "check.h" -#include "memmgr.h" -#include "event_loop_link_i.h" - -/** - * Data shared between both sides. - */ -typedef struct { - FuriSemaphore* instance_count; // alice_event_loop_link, - .peer_event_loop_link = &shared->bob_event_loop_link, - }; - *bobs_side = (FuriPipeSide){ - .role = FuriPipeRoleBob, - .shared = shared, - .sending = bob_to_alice, - .receiving = alice_to_bob, - .self_event_loop_link = &shared->bob_event_loop_link, - .peer_event_loop_link = &shared->alice_event_loop_link, - }; - - return (FuriPipe){.alices_side = alices_side, .bobs_side = bobs_side}; -} - -FuriPipeRole furi_pipe_role(FuriPipeSide* pipe) { - furi_check(pipe); - return pipe->role; -} - -FuriPipeState furi_pipe_state(FuriPipeSide* pipe) { - furi_check(pipe); - uint32_t count = furi_semaphore_get_count(pipe->shared->instance_count); - return (count == 1) ? FuriPipeStateOpen : FuriPipeStateBroken; -} - -void furi_pipe_free(FuriPipeSide* pipe) { - furi_check(pipe); - - // Event Loop must be disconnected - furi_check(!pipe->self_event_loop_link->item_in); - furi_check(!pipe->self_event_loop_link->item_out); - - furi_mutex_acquire(pipe->shared->state_transition, FuriWaitForever); - FuriStatus status = furi_semaphore_acquire(pipe->shared->instance_count, 0); - - if(status == FuriStatusOk) { - // the other side is still intact - furi_mutex_release(pipe->shared->state_transition); - free(pipe); - } else { - // the other side is gone too - furi_stream_buffer_free(pipe->sending); - furi_stream_buffer_free(pipe->receiving); - furi_semaphore_free(pipe->shared->instance_count); - furi_mutex_free(pipe->shared->state_transition); - free(pipe->shared); - free(pipe); - } -} - -static void _furi_pipe_stdout_cb(const char* data, size_t size, void* context) { - furi_assert(context); - FuriPipeSide* pipe = context; - while(size) { - if(furi_pipe_state(pipe) == FuriPipeStateBroken) return; - size_t sent = furi_pipe_send(pipe, data, size, FuriWaitForever); - data += sent; - size -= sent; - } -} - -static size_t _furi_pipe_stdin_cb(char* data, size_t size, FuriWait timeout, void* context) { - furi_assert(context); - FuriPipeSide* pipe = context; - return furi_pipe_receive(pipe, data, size, timeout); -} - -void furi_pipe_install_as_stdio(FuriPipeSide* pipe) { - furi_check(pipe); - furi_thread_set_stdout_callback(_furi_pipe_stdout_cb, pipe); - furi_thread_set_stdin_callback(_furi_pipe_stdin_cb, pipe); -} - -size_t furi_pipe_receive(FuriPipeSide* pipe, void* data, size_t length, FuriWait timeout) { - furi_check(pipe); - size_t received = furi_stream_buffer_receive(pipe->receiving, data, length, timeout); - if(received) furi_event_loop_link_notify(pipe->peer_event_loop_link, FuriEventLoopEventOut); - return received; -} - -size_t furi_pipe_send(FuriPipeSide* pipe, const void* data, size_t length, FuriWait timeout) { - furi_check(pipe); - size_t sent = furi_stream_buffer_send(pipe->sending, data, length, timeout); - if(furi_stream_buffer_bytes_available(pipe->sending) >= - furi_stream_get_trigger_level(pipe->sending)) - furi_event_loop_link_notify(pipe->peer_event_loop_link, FuriEventLoopEventIn); - return sent; -} - -size_t furi_pipe_bytes_available(FuriPipeSide* pipe) { - furi_check(pipe); - return furi_stream_buffer_bytes_available(pipe->receiving); -} - -size_t furi_pipe_spaces_available(FuriPipeSide* pipe) { - furi_check(pipe); - return furi_stream_buffer_spaces_available(pipe->sending); -} - -static FuriEventLoopLink* furi_pipe_event_loop_get_link(FuriEventLoopObject* object) { - FuriPipeSide* instance = object; - furi_assert(instance); - return instance->self_event_loop_link; -} - -static bool furi_pipe_event_loop_get_level(FuriEventLoopObject* object, FuriEventLoopEvent event) { - FuriPipeSide* instance = object; - furi_assert(instance); - - if(event == FuriEventLoopEventIn) { - return furi_stream_buffer_bytes_available(instance->receiving); - } else if(event == FuriEventLoopEventOut) { - return furi_stream_buffer_spaces_available(instance->sending); - } else { - furi_crash(); - } -} - -const FuriEventLoopContract furi_pipe_event_loop_contract = { - .get_link = furi_pipe_event_loop_get_link, - .get_level = furi_pipe_event_loop_get_level, -}; diff --git a/furi/core/pipe.h b/furi/core/pipe.h deleted file mode 100644 index 82c1a2d10c1..00000000000 --- a/furi/core/pipe.h +++ /dev/null @@ -1,192 +0,0 @@ -/** - * @file pipe.h - * Furi pipe primitive - * - * Pipes are used to send bytes between two threads in both directions. The two - * threads are referred to as Alice and Bob and their abilities regarding what - * they can do with the pipe are equal. - * - * It is also possible to use both sides of the pipe within one thread. - */ - -#ifdef __cplusplus -extern "C" { -#endif - -#include "base.h" -#include - -/** - * @brief The role of a pipe side - * - * Both roles are equal, as they can both read and write the data. This status - * might be helpful in determining the role of a thread w.r.t. another thread in - * an application that builds on the pipe. - */ -typedef enum { - FuriPipeRoleAlice, - FuriPipeRoleBob, -} FuriPipeRole; - -/** - * @brief The state of a pipe - * - * - `FuriPipeStateOpen`: Both pipe sides are in place, meaning data that is - * sent down the pipe _might_ be read by the peer, and new data sent by the - * peer _might_ arrive. - * - `FuriPipeStateBroken`: The other side of the pipe has been freed, meaning - * data that is written will never reach its destination, and no new data - * will appear in the buffer. - * - * A broken pipe can never become open again, because there's no way to connect - * a side of a pipe to another side of a pipe. - */ -typedef enum { - FuriPipeStateOpen, - FuriPipeStateBroken, -} FuriPipeState; - -typedef struct FuriPipeSide FuriPipeSide; - -typedef struct { - FuriPipeSide* alices_side; - FuriPipeSide* bobs_side; -} FuriPipe; - -typedef struct { - size_t capacity; - size_t trigger_level; -} FuriPipeSideReceiveSettings; - -/** - * @brief Allocates two connected sides of one pipe. - * - * Creating a pair of sides using this function is the only way to connect two - * pipe sides together. Two unrelated orphaned sides may never be connected back - * together. - * - * The capacity and trigger level for both directions are the same when the pipe - * is created using this function. Use `furi_pipe_alloc_ex` if you want more - * control. - * - * @param capacity Maximum number of bytes buffered in one direction - * @param trigger_level Number of bytes that need to be available in the buffer - * in order for a blocked thread to unblock - * @returns Bundle with both sides of the pipe - */ -FuriPipe furi_pipe_alloc(size_t capacity, size_t trigger_level); - -/** - * @brief Allocates two connected sides of one pipe. - * - * Creating a pair of sides using this function is the only way to connect two - * pipe sides together. Two unrelated orphaned sides may never be connected back - * together. - * - * The capacity and trigger level may be different for the two directions when - * the pipe is created using this function. Use `furi_pipe_alloc` if you don't - * need control this fine. - * - * @param alice `capacity` and `trigger_level` settings for Alice's receiving - * buffer - * @param bob `capacity` and `trigger_level` settings for Bob's receiving buffer - * @returns Bundle with both sides of the pipe - */ -FuriPipe furi_pipe_alloc_ex(FuriPipeSideReceiveSettings alice, FuriPipeSideReceiveSettings bob); - -/** - * @brief Gets the role of a pipe side. - * - * The roles (Alice and Bob) are equal, as both can send and receive data. This - * status might be helpful in determining the role of a thread w.r.t. another - * thread. - * - * @param [in] pipe Pipe side to query - * @returns Role of provided pipe side - */ -FuriPipeRole furi_pipe_role(FuriPipeSide* pipe); - -/** - * @brief Gets the state of a pipe. - * - * When the state is `FuriPipeStateOpen`, both sides are active and may send or - * receive data. When the state is `FuriPipeStateBroken`, only one side is - * active (the one that this method has been called on). If you find yourself in - * that state, the data that you send will never be heard by anyone, and the - * data you receive are leftovers in the buffer. - * - * @param [in] pipe Pipe side to query - * @returns State of the pipe - */ -FuriPipeState furi_pipe_state(FuriPipeSide* pipe); - -/** - * @brief Frees a side of a pipe. - * - * When only one of the sides is freed, the pipe is transitioned from the "Open" - * state into the "Broken" state. When both sides are freed, the underlying data - * structures are freed too. - * - * @param [in] pipe Pipe side to free - */ -void furi_pipe_free(FuriPipeSide* pipe); - -/** - * @brief Connects the pipe to the `stdin` and `stdout` of the current thread. - * - * After performing this operation, you can use `getc`, `puts`, etc. to send and - * receive data to and from the pipe. If the pipe becomes broken, C stdlib calls - * will return `EOF` wherever possible. - * - * You can disconnect the pipe by manually calling - * `furi_thread_set_stdout_callback` and `furi_thread_set_stdin_callback` with - * `NULL`. - * - * @param [in] pipe Pipe side to connect to the stdio - */ -void furi_pipe_install_as_stdio(FuriPipeSide* pipe); - -/** - * @brief Receives data from the pipe. - * - * @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 furi_pipe_receive(FuriPipeSide* pipe, void* data, size_t length, FuriWait timeout); - -/** - * @brief Sends data into the pipe. - * - * @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 furi_pipe_send(FuriPipeSide* pipe, const void* data, size_t length, FuriWait timeout); - -/** - * @brief Determines how many bytes there are in the pipe available to be read. - * - * @param [in] pipe Pipe side to query - * @returns Number of bytes available to be read out from that side of the pipe - */ -size_t furi_pipe_bytes_available(FuriPipeSide* pipe); - -/** - * @brief Determines how many space there is in the pipe for data to be written - * into. - * - * @param [in] pipe Pipe side to query - * @returns Number of bytes available to be written into that side of the pipe - */ -size_t furi_pipe_spaces_available(FuriPipeSide* pipe); - -#ifdef __cplusplus -} -#endif diff --git a/furi/furi.h b/furi/furi.h index 4afcb8f4697..6ddf2857757 100644 --- a/furi/furi.h +++ b/furi/furi.h @@ -21,7 +21,6 @@ #include "core/timer.h" #include "core/string.h" #include "core/stream_buffer.h" -#include "core/pipe.h" #include diff --git a/lib/ble_profile/extra_services/hid_service.c b/lib/ble_profile/extra_services/hid_service.c index e46d2010c52..9f9a0f7520e 100644 --- a/lib/ble_profile/extra_services/hid_service.c +++ b/lib/ble_profile/extra_services/hid_service.c @@ -10,13 +10,13 @@ #define TAG "BleHid" #define BLE_SVC_HID_REPORT_MAP_MAX_LEN (255) -#define BLE_SVC_HID_REPORT_MAX_LEN (255) -#define BLE_SVC_HID_REPORT_REF_LEN (2) -#define BLE_SVC_HID_INFO_LEN (4) -#define BLE_SVC_HID_CONTROL_POINT_LEN (1) +#define BLE_SVC_HID_REPORT_MAX_LEN (255) +#define BLE_SVC_HID_REPORT_REF_LEN (2) +#define BLE_SVC_HID_INFO_LEN (4) +#define BLE_SVC_HID_CONTROL_POINT_LEN (1) -#define BLE_SVC_HID_INPUT_REPORT_COUNT (3) -#define BLE_SVC_HID_OUTPUT_REPORT_COUNT (0) +#define BLE_SVC_HID_INPUT_REPORT_COUNT (3) +#define BLE_SVC_HID_OUTPUT_REPORT_COUNT (0) #define BLE_SVC_HID_FEATURE_REPORT_COUNT (0) #define BLE_SVC_HID_REPORT_COUNT \ (BLE_SVC_HID_INPUT_REPORT_COUNT + BLE_SVC_HID_OUTPUT_REPORT_COUNT + \ @@ -157,6 +157,7 @@ static BleEventAckStatus ble_svc_hid_event_handler(void* event, void* context) { hci_event_pckt* event_pckt = (hci_event_pckt*)(((hci_uart_pckt*)event)->data); evt_blecore_aci* blecore_evt = (evt_blecore_aci*)event_pckt->data; // aci_gatt_attribute_modified_event_rp0* attribute_modified; + if(event_pckt->evt == HCI_VENDOR_SPECIFIC_DEBUG_EVT_CODE) { if(blecore_evt->ecode == ACI_GATT_ATTRIBUTE_MODIFIED_VSEVT_CODE) { // Process modification events @@ -274,6 +275,7 @@ bool ble_svc_hid_update_input_report( .data_ptr = data, .data_len = len, }; + return ble_gatt_characteristic_update( hid_svc->svc_handle, &hid_svc->input_report_chars[input_report_num], &report_data); } diff --git a/lib/flipper_application/elf/elf_file.c b/lib/flipper_application/elf/elf_file.c index d0c4f52fb1f..bd8ecdf7ef3 100644 --- a/lib/flipper_application/elf/elf_file.c +++ b/lib/flipper_application/elf/elf_file.c @@ -8,11 +8,11 @@ #define TAG "Elf" -#define ELF_NAME_BUFFER_LEN 32 -#define SECTION_OFFSET(e, n) ((e)->section_table + (n) * sizeof(Elf32_Shdr)) -#define IS_FLAGS_SET(v, m) (((v) & (m)) == (m)) +#define ELF_NAME_BUFFER_LEN 32 +#define SECTION_OFFSET(e, n) ((e)->section_table + (n) * sizeof(Elf32_Shdr)) +#define IS_FLAGS_SET(v, m) (((v) & (m)) == (m)) #define RESOLVER_THREAD_YIELD_STEP 30 -#define FAST_RELOCATION_VERSION 1 +#define FAST_RELOCATION_VERSION 1 // #define ELF_DEBUG_LOG 1 @@ -830,9 +830,7 @@ void elf_file_free(ELFFile* elf) { for(ELFSectionDict_it(it, elf->sections); !ELFSectionDict_end_p(it); ELFSectionDict_next(it)) { const ELFSectionDict_itref_t* itref = ELFSectionDict_cref(it); - if(itref->value.data) { - aligned_free(itref->value.data); - } + aligned_free(itref->value.data); if(itref->value.fast_rel) { aligned_free(itref->value.fast_rel->data); free(itref->value.fast_rel); diff --git a/lib/flipper_format/flipper_format.c b/lib/flipper_format/flipper_format.c index 8992247d1fb..d07022e122e 100644 --- a/lib/flipper_format/flipper_format.c +++ b/lib/flipper_format/flipper_format.c @@ -403,6 +403,11 @@ bool flipper_format_write_comment_cstr(FlipperFormat* flipper_format, const char return flipper_format_stream_write_comment_cstr(flipper_format->stream, data); } +bool flipper_format_write_empty_line(FlipperFormat* flipper_format) { + furi_check(flipper_format); + return flipper_format_stream_write_eol(flipper_format->stream); +} + bool flipper_format_delete_key(FlipperFormat* flipper_format, const char* key) { furi_check(flipper_format); FlipperStreamWriteData write_data = { diff --git a/lib/flipper_format/flipper_format.h b/lib/flipper_format/flipper_format.h index 46f78e25549..4a1bb767bb3 100644 --- a/lib/flipper_format/flipper_format.h +++ b/lib/flipper_format/flipper_format.h @@ -518,6 +518,14 @@ bool flipper_format_write_comment(FlipperFormat* flipper_format, FuriString* dat */ bool flipper_format_write_comment_cstr(FlipperFormat* flipper_format, const char* data); +/** Write empty line (Improves readability for human based parsing) + * + * @param flipper_format Pointer to a FlipperFormat instance + * + * @return True on success + */ +bool flipper_format_write_empty_line(FlipperFormat* flipper_format); + /** Removes the first matching key and its value. Sets the RW pointer to a * position of deleted data. * diff --git a/lib/nfc/protocols/mf_plus/mf_plus_poller.c b/lib/nfc/protocols/mf_plus/mf_plus_poller.c index 8d1cc1c99a2..57a26a9cf7b 100644 --- a/lib/nfc/protocols/mf_plus/mf_plus_poller.c +++ b/lib/nfc/protocols/mf_plus/mf_plus_poller.c @@ -146,7 +146,7 @@ static void mf_plus_poller_set_callback( static NfcCommand mf_plus_poller_run(NfcGenericEvent event, void* context) { furi_assert(context); - furi_assert(event.protocol = NfcProtocolIso14443_4a); + furi_assert(event.protocol == NfcProtocolIso14443_4a); furi_assert(event.event_data); MfPlusPoller* instance = context; @@ -178,7 +178,7 @@ void mf_plus_poller_free(MfPlusPoller* instance) { static bool mf_plus_poller_detect(NfcGenericEvent event, void* context) { furi_assert(context); - furi_assert(event.protocol = NfcProtocolIso14443_4a); + furi_assert(event.protocol == NfcProtocolIso14443_4a); furi_assert(event.event_data); MfPlusPoller* instance = context; diff --git a/lib/signal_reader/parsers/iso15693/iso15693_parser.c b/lib/signal_reader/parsers/iso15693/iso15693_parser.c index a2c6912e637..e47c734a279 100644 --- a/lib/signal_reader/parsers/iso15693/iso15693_parser.c +++ b/lib/signal_reader/parsers/iso15693/iso15693_parser.c @@ -243,6 +243,8 @@ static Iso15693ParserCommand iso15693_parser_parse_1_out_of_256(Iso15693Parser* instance->parsed_frame, instance->next_byte_part * 4 + j / 2); } } + } else { + instance->zero_found = true; } } instance->next_byte_part = (instance->next_byte_part + 1) % 64; diff --git a/lib/subghz/protocols/bin_raw.c b/lib/subghz/protocols/bin_raw.c index 8298bce6b06..c2aebb6aba5 100644 --- a/lib/subghz/protocols/bin_raw.c +++ b/lib/subghz/protocols/bin_raw.c @@ -314,8 +314,8 @@ SubGhzProtocolStatus flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); if(!subghz_protocol_encoder_bin_raw_get_upload(instance)) { - break; res = SubGhzProtocolStatusErrorEncoderGetUpload; + break; } instance->encoder.is_running = true; diff --git a/lib/toolbox/SConscript b/lib/toolbox/SConscript index 03b8999c4d3..8a1c4a8c5d3 100644 --- a/lib/toolbox/SConscript +++ b/lib/toolbox/SConscript @@ -30,6 +30,7 @@ env.Append( File("stream/string_stream.h"), File("stream/buffered_file_stream.h"), File("strint.h"), + File("pipe.h"), File("protocols/protocol_dict.h"), File("pretty_format.h"), File("hex.h"), diff --git a/lib/toolbox/pipe.c b/lib/toolbox/pipe.c new file mode 100644 index 00000000000..7112b5a4538 --- /dev/null +++ b/lib/toolbox/pipe.c @@ -0,0 +1,241 @@ +#include "pipe.h" +#include + +/** + * Data shared between both sides. + */ +typedef struct { + FuriSemaphore* instance_count; // role; +} + +PipeState pipe_state(PipeSide* pipe) { + furi_check(pipe); + uint32_t count = furi_semaphore_get_count(pipe->shared->instance_count); + return (count == 1) ? PipeStateOpen : PipeStateBroken; +} + +void pipe_free(PipeSide* pipe) { + furi_check(pipe); + furi_check(!pipe->event_loop); + + furi_mutex_acquire(pipe->shared->state_transition, FuriWaitForever); + FuriStatus status = furi_semaphore_acquire(pipe->shared->instance_count, 0); + + if(status == FuriStatusOk) { + // the other side is still intact + furi_mutex_release(pipe->shared->state_transition); + free(pipe); + } else { + // the other side is gone too + furi_stream_buffer_free(pipe->sending); + furi_stream_buffer_free(pipe->receiving); + furi_semaphore_free(pipe->shared->instance_count); + furi_mutex_free(pipe->shared->state_transition); + free(pipe->shared); + free(pipe); + } +} + +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, FuriWaitForever); + data += sent; + size -= sent; + } +} + +static size_t _pipe_stdin_cb(char* data, size_t size, FuriWait timeout, void* context) { + furi_assert(context); + PipeSide* pipe = context; + return pipe_receive(pipe, data, size, timeout); +} + +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); +} + +size_t pipe_receive(PipeSide* pipe, void* data, size_t length, FuriWait timeout) { + furi_check(pipe); + return furi_stream_buffer_receive(pipe->receiving, data, length, timeout); +} + +size_t pipe_send(PipeSide* pipe, const void* data, size_t length, FuriWait timeout) { + furi_check(pipe); + return furi_stream_buffer_send(pipe->sending, data, length, timeout); +} + +size_t pipe_bytes_available(PipeSide* pipe) { + furi_check(pipe); + return furi_stream_buffer_bytes_available(pipe->receiving); +} + +size_t pipe_spaces_available(PipeSide* pipe) { + furi_check(pipe); + return furi_stream_buffer_spaces_available(pipe->sending); +} + +static void pipe_receiving_buffer_callback(FuriEventLoopObject* buffer, void* context) { + UNUSED(buffer); + PipeSide* pipe = context; + furi_assert(pipe); + 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_space_freed) pipe->on_space_freed(pipe, pipe->callback_context); +} + +static void pipe_semaphore_callback(FuriEventLoopObject* semaphore, void* context) { + UNUSED(semaphore); + PipeSide* pipe = context; + furi_assert(pipe); + if(pipe->on_pipe_broken) pipe->on_pipe_broken(pipe, pipe->callback_context); +} + +void pipe_attach_to_event_loop(PipeSide* pipe, FuriEventLoop* event_loop) { + furi_check(pipe); + furi_check(event_loop); + furi_check(!pipe->event_loop); + + pipe->event_loop = event_loop; +} + +void pipe_detach_from_event_loop(PipeSide* pipe) { + furi_check(pipe); + furi_check(pipe->event_loop); + + furi_event_loop_maybe_unsubscribe(pipe->event_loop, pipe->receiving); + furi_event_loop_maybe_unsubscribe(pipe->event_loop, pipe->sending); + furi_event_loop_maybe_unsubscribe(pipe->event_loop, pipe->shared->instance_count); + + pipe->event_loop = NULL; +} + +void pipe_set_callback_context(PipeSide* pipe, void* context) { + furi_check(pipe); + pipe->callback_context = context; +} + +void pipe_set_data_arrived_callback( + PipeSide* pipe, + PipeSideDataArrivedCallback callback, + FuriEventLoopEvent event) { + furi_check(pipe); + furi_check(pipe->event_loop); + furi_check((event & FuriEventLoopEventMask) == 0); + + furi_event_loop_maybe_unsubscribe(pipe->event_loop, pipe->receiving); + pipe->on_data_arrived = callback; + if(callback) + furi_event_loop_subscribe_stream_buffer( + pipe->event_loop, + pipe->receiving, + FuriEventLoopEventIn | event, + pipe_receiving_buffer_callback, + pipe); +} + +void pipe_set_space_freed_callback( + PipeSide* pipe, + PipeSideSpaceFreedCallback callback, + FuriEventLoopEvent event) { + furi_check(pipe); + furi_check(pipe->event_loop); + furi_check((event & FuriEventLoopEventMask) == 0); + + furi_event_loop_maybe_unsubscribe(pipe->event_loop, pipe->sending); + pipe->on_space_freed = callback; + if(callback) + furi_event_loop_subscribe_stream_buffer( + pipe->event_loop, + pipe->sending, + FuriEventLoopEventOut | event, + pipe_sending_buffer_callback, + pipe); +} + +void pipe_set_broken_callback( + PipeSide* pipe, + PipeSideBrokenCallback callback, + FuriEventLoopEvent event) { + furi_check(pipe); + furi_check(pipe->event_loop); + furi_check((event & FuriEventLoopEventMask) == 0); + + furi_event_loop_maybe_unsubscribe(pipe->event_loop, pipe->shared->instance_count); + pipe->on_pipe_broken = callback; + if(callback) + furi_event_loop_subscribe_semaphore( + pipe->event_loop, + pipe->shared->instance_count, + FuriEventLoopEventOut | event, + pipe_semaphore_callback, + pipe); +} diff --git a/lib/toolbox/pipe.h b/lib/toolbox/pipe.h new file mode 100644 index 00000000000..df75f4c48de --- /dev/null +++ b/lib/toolbox/pipe.h @@ -0,0 +1,295 @@ +/** + * @file pipe.h + * Pipe convenience module + * + * Pipes are used to send bytes between two threads in both directions. The two + * threads are referred to as Alice and Bob and their abilities regarding what + * they can do with the pipe are equal. + * + * It is also possible to use both sides of the pipe within one thread. + */ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +/** + * @brief The role of a pipe side + * + * Both roles are equal, as they can both read and write the data. This status + * might be helpful in determining the role of a thread w.r.t. another thread in + * an application that builds on the pipe. + */ +typedef enum { + PipeRoleAlice, + PipeRoleBob, +} PipeRole; + +/** + * @brief The state of a pipe + * + * - `PipeStateOpen`: Both pipe sides are in place, meaning data that is sent + * down the pipe _might_ be read by the peer, and new data sent by the peer + * _might_ arrive. + * - `PipeStateBroken`: The other side of the pipe has been freed, meaning + * data that is written will never reach its destination, and no new data + * will appear in the buffer. + * + * A broken pipe can never become open again, because there's no way to connect + * a side of a pipe to another side of a pipe. + */ +typedef enum { + PipeStateOpen, + PipeStateBroken, +} PipeState; + +typedef struct PipeSide PipeSide; + +typedef struct { + PipeSide* alices_side; + PipeSide* bobs_side; +} PipeSideBundle; + +typedef struct { + size_t capacity; + size_t trigger_level; +} PipeSideReceiveSettings; + +/** + * @brief Allocates two connected sides of one pipe. + * + * Creating a pair of sides using this function is the only way to connect two + * pipe sides together. Two unrelated orphaned sides may never be connected back + * together. + * + * The capacity and trigger level for both directions are the same when the pipe + * is created using this function. Use `pipe_alloc_ex` if you want more + * control. + * + * @param capacity Maximum number of bytes buffered in one direction + * @param trigger_level Number of bytes that need to be available in the buffer + * in order for a blocked thread to unblock + * @returns Bundle with both sides of the pipe + */ +PipeSideBundle pipe_alloc(size_t capacity, size_t trigger_level); + +/** + * @brief Allocates two connected sides of one pipe. + * + * Creating a pair of sides using this function is the only way to connect two + * pipe sides together. Two unrelated orphaned sides may never be connected back + * together. + * + * The capacity and trigger level may be different for the two directions when + * the pipe is created using this function. Use `pipe_alloc` if you don't + * need control this fine. + * + * @param alice `capacity` and `trigger_level` settings for Alice's receiving + * buffer + * @param bob `capacity` and `trigger_level` settings for Bob's receiving buffer + * @returns Bundle with both sides of the pipe + */ +PipeSideBundle pipe_alloc_ex(PipeSideReceiveSettings alice, PipeSideReceiveSettings bob); + +/** + * @brief Gets the role of a pipe side. + * + * The roles (Alice and Bob) are equal, as both can send and receive data. This + * status might be helpful in determining the role of a thread w.r.t. another + * thread. + * + * @param [in] pipe Pipe side to query + * @returns Role of provided pipe side + */ +PipeRole pipe_role(PipeSide* pipe); + +/** + * @brief Gets the state of a pipe. + * + * When the state is `PipeStateOpen`, both sides are active and may send or + * receive data. When the state is `PipeStateBroken`, only one side is active + * (the one that this method has been called on). If you find yourself in that + * state, the data that you send will never be heard by anyone, and the data you + * receive are leftovers in the buffer. + * + * @param [in] pipe Pipe side to query + * @returns State of the pipe + */ +PipeState pipe_state(PipeSide* pipe); + +/** + * @brief Frees a side of a pipe. + * + * When only one of the sides is freed, the pipe is transitioned from the "Open" + * state into the "Broken" state. When both sides are freed, the underlying data + * structures are freed too. + * + * @param [in] pipe Pipe side to free + */ +void pipe_free(PipeSide* pipe); + +/** + * @brief Connects the pipe to the `stdin` and `stdout` of the current thread. + * + * After performing this operation, you can use `getc`, `puts`, etc. to send and + * receive data to and from the pipe. If the pipe becomes broken, C stdlib calls + * will return `EOF` wherever possible. + * + * You can disconnect the pipe by manually calling + * `furi_thread_set_stdout_callback` and `furi_thread_set_stdin_callback` with + * `NULL`. + * + * @param [in] pipe Pipe side to connect to the stdio + */ +void pipe_install_as_stdio(PipeSide* pipe); + +/** + * @brief Receives data from the pipe. + * + * @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); + +/** + * @brief Sends data into the pipe. + * + * @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); + +/** + * @brief Determines how many bytes there are in the pipe available to be read. + * + * @param [in] pipe Pipe side to query + * @returns Number of bytes available to be read out from that side of the pipe + */ +size_t pipe_bytes_available(PipeSide* pipe); + +/** + * @brief Determines how many space there is in the pipe for data to be written + * into. + * + * @param [in] pipe Pipe side to query + * @returns Number of bytes available to be written into that side of the pipe + */ +size_t pipe_spaces_available(PipeSide* pipe); + +/** + * @brief Attaches a `PipeSide` to a `FuriEventLoop`, allowing to attach + * callbacks to the PipeSide. + * + * @param [in] pipe Pipe side to attach to the event loop + * @param [in] event_loop Event loop to attach the pipe side to + */ +void pipe_attach_to_event_loop(PipeSide* pipe, FuriEventLoop* event_loop); + +/** + * @brief Detaches a `PipeSide` from the `FuriEventLoop` that it was previously + * attached to. + * + * @param [in] pipe Pipe side to detach to the event loop + */ +void pipe_detach_from_event_loop(PipeSide* pipe); + +/** + * @brief Callback for when data arrives to a `PipeSide`. + * + * @param [in] pipe Pipe side that called the callback + * @param [inout] context Custom context + */ +typedef void (*PipeSideDataArrivedCallback)(PipeSide* pipe, void* context); + +/** + * @brief Callback for when data is read out of the opposite `PipeSide`. + * + * @param [in] pipe Pipe side that called the callback + * @param [inout] context Custom context + */ +typedef void (*PipeSideSpaceFreedCallback)(PipeSide* pipe, void* context); + +/** + * @brief Callback for when the opposite `PipeSide` is freed, making the pipe + * broken. + * + * @param [in] pipe Pipe side that called the callback + * @param [inout] context Custom context + */ +typedef void (*PipeSideBrokenCallback)(PipeSide* pipe, void* context); + +/** + * @brief Sets the custom context for all callbacks. + * + * @param [in] pipe Pipe side to set the context of + * @param [inout] context Custom context that will be passed to callbacks + */ +void pipe_set_callback_context(PipeSide* pipe, void* context); + +/** + * @brief Sets the callback for when data arrives. + * + * @param [in] pipe Pipe side to assign the callback to + * @param [in] callback Callback to assign to the pipe side. Set to NULL to + * unsubscribe. + * @param [in] event Additional event loop flags (e.g. `Edge`, `Once`, etc.). + * Non-flag values of the enum are not allowed. + * + * @warning Attach the pipe side to an event loop first using + * `pipe_attach_to_event_loop`. + */ +void pipe_set_data_arrived_callback( + PipeSide* pipe, + PipeSideDataArrivedCallback callback, + FuriEventLoopEvent event); + +/** + * @brief Sets the callback for when data is read out of the opposite `PipeSide`. + * + * @param [in] pipe Pipe side to assign the callback to + * @param [in] callback Callback to assign to the pipe side. Set to NULL to + * unsubscribe. + * @param [in] event Additional event loop flags (e.g. `Edge`, `Once`, etc.). + * Non-flag values of the enum are not allowed. + * + * @warning Attach the pipe side to an event loop first using + * `pipe_attach_to_event_loop`. + */ +void pipe_set_space_freed_callback( + PipeSide* pipe, + PipeSideSpaceFreedCallback callback, + FuriEventLoopEvent event); + +/** + * @brief Sets the callback for when the opposite `PipeSide` is freed, making + * the pipe broken. + * + * @param [in] pipe Pipe side to assign the callback to + * @param [in] callback Callback to assign to the pipe side. Set to NULL to + * unsubscribe. + * @param [in] event Additional event loop flags (e.g. `Edge`, `Once`, etc.). + * Non-flag values of the enum are not allowed. + * + * @warning Attach the pipe side to an event loop first using + * `pipe_attach_to_event_loop`. + */ +void pipe_set_broken_callback( + PipeSide* pipe, + PipeSideBrokenCallback callback, + FuriEventLoopEvent event); + +#ifdef __cplusplus +} +#endif diff --git a/scripts/get_env.py b/scripts/get_env.py index e8f6b3b77fd..6d6b3cce57a 100755 --- a/scripts/get_env.py +++ b/scripts/get_env.py @@ -34,7 +34,11 @@ def get_commit_json(event): commit_url = event["pull_request"]["base"]["repo"]["commits_url"].replace( "{/sha}", f"/{event['pull_request']['head']['sha']}" ) - with urllib.request.urlopen(commit_url, context=context) as commit_file: + request = urllib.request.Request(commit_url) + if "GH_TOKEN" in os.environ: + request.add_header("Authorization", "Bearer %s" % (os.environ["GH_TOKEN"])) + + with urllib.request.urlopen(request, context=context) as commit_file: commit_json = json.loads(commit_file.read().decode("utf-8")) return commit_json diff --git a/scripts/lint.py b/scripts/lint.py index 8530209bec0..896cd381285 100755 --- a/scripts/lint.py +++ b/scripts/lint.py @@ -35,21 +35,25 @@ def init(self): self.parser_format.set_defaults(func=self.format) @staticmethod - def _filter_lint_directories(dirnames: list[str]): + def _filter_lint_directories( + dirpath: str, dirnames: list[str], excludes: tuple[str] + ): # Skipping 3rd-party code - usually resides in subfolder "lib" if "lib" in dirnames: dirnames.remove("lib") - # Skipping hidden folders + # Skipping hidden and excluded folders for dirname in dirnames.copy(): if dirname.startswith("."): dirnames.remove(dirname) + if os.path.join(dirpath, dirname).startswith(excludes): + dirnames.remove(dirname) - def _check_folders(self, folders: list): + def _check_folders(self, folders: list, excludes: tuple[str]): show_message = False pattern = re.compile(SOURCE_CODE_DIR_PATTERN) for folder in folders: for dirpath, dirnames, filenames in os.walk(folder): - self._filter_lint_directories(dirnames) + self._filter_lint_directories(dirpath, dirnames, excludes) for dirname in dirnames: if not pattern.match(dirname): @@ -61,11 +65,11 @@ def _check_folders(self, folders: list): "Folders are not renamed automatically, please fix it by yourself" ) - def _find_sources(self, folders: list): + def _find_sources(self, folders: list, excludes: tuple[str]): output = [] for folder in folders: for dirpath, dirnames, filenames in os.walk(folder): - self._filter_lint_directories(dirnames) + self._filter_lint_directories(dirpath, dirnames, excludes) for filename in filenames: ext = os.path.splitext(filename.lower())[1] @@ -168,14 +172,20 @@ def _apply_file_permissions(self, sources: list, dry_run: bool = False): def _perform(self, dry_run: bool): result = 0 - sources = self._find_sources(self.args.input) + excludes = [] + for folder in self.args.input.copy(): + if folder.startswith("!"): + excludes.append(folder.removeprefix("!")) + self.args.input.remove(folder) + excludes = tuple(excludes) + sources = self._find_sources(self.args.input, excludes) if not self._format_sources(sources, dry_run=dry_run): result |= 0b001 if not self._apply_file_naming_convention(sources, dry_run=dry_run): result |= 0b010 if not self._apply_file_permissions(sources, dry_run=dry_run): result |= 0b100 - self._check_folders(self.args.input) + self._check_folders(self.args.input, excludes) return result def check(self): diff --git a/scripts/testops.py b/scripts/testops.py index 4ae10c7f4ae..3100a9b7fe7 100644 --- a/scripts/testops.py +++ b/scripts/testops.py @@ -1,8 +1,7 @@ #!/usr/bin/env python3 - import re -import sys import time +from datetime import datetime from typing import Optional from flipper.app import App @@ -11,7 +10,10 @@ class Main(App): - # this is basic use without sub-commands, simply to reboot flipper / power it off, not meant as a full CLI wrapper + def __init__(self, no_exit=False): + super().__init__(no_exit) + self.test_results = None + def init(self): self.parser.add_argument("-p", "--port", help="CDC Port", default="auto") self.parser.add_argument( @@ -67,64 +69,108 @@ def run_units(self): self.logger.info("Running unit tests") flipper.send("unit_tests" + "\r") self.logger.info("Waiting for unit tests to complete") - data = flipper.read.until(">: ") - self.logger.info("Parsing result") - - lines = data.decode().split("\r\n") - - tests_re = r"Failed tests: \d{0,}" - time_re = r"Consumed: \d{0,}" - leak_re = r"Leaked: \d{0,}" - status_re = r"Status: \w{3,}" - - tests_pattern = re.compile(tests_re) - time_pattern = re.compile(time_re) - leak_pattern = re.compile(leak_re) - status_pattern = re.compile(status_re) tests, elapsed_time, leak, status = None, None, None, None total = 0 - - for line in lines: - self.logger.info(line) - if "()" in line: - total += 1 - - if not tests: - tests = re.match(tests_pattern, line) - if not elapsed_time: - elapsed_time = re.match(time_pattern, line) - if not leak: - leak = re.match(leak_pattern, line) - if not status: - status = re.match(status_pattern, line) - - if None in (tests, elapsed_time, leak, status): - self.logger.error( - f"Failed to parse output: {tests} {elapsed_time} {leak} {status}" + all_required_found = False + + full_output = [] + + tests_pattern = re.compile(r"Failed tests: \d{0,}") + time_pattern = re.compile(r"Consumed: \d{0,}") + leak_pattern = re.compile(r"Leaked: \d{0,}") + status_pattern = re.compile(r"Status: \w{3,}") + + try: + while not all_required_found: + try: + line = flipper.read.until("\r\n", cut_eol=True).decode() + self.logger.info(line) + if "command not found," in line: + self.logger.error(f"Command not found: {line}") + return 1 + + if "()" in line: + total += 1 + self.logger.debug(f"Test completed: {line}") + + if not tests: + tests = tests_pattern.match(line) + if not elapsed_time: + elapsed_time = time_pattern.match(line) + if not leak: + leak = leak_pattern.match(line) + if not status: + status = status_pattern.match(line) + + pattern = re.compile( + r"(\[-]|\[\\]|\[\|]|\[/-]|\[[^\]]*\]|\x1b\[\d+D)" + ) + line_to_append = pattern.sub("", line) + pattern = re.compile(r"\[3D[^\]]*") + line_to_append = pattern.sub("", line_to_append) + line_to_append = f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S,%f')} {line_to_append}" + + full_output.append(line_to_append) + + if tests and elapsed_time and leak and status: + all_required_found = True + try: + remaining = flipper.read.until(">: ", cut_eol=True).decode() + if remaining.strip(): + full_output.append(remaining) + except: + pass + break + + except Exception as e: + self.logger.error(f"Error reading output: {e}") + raise + + if None in (tests, elapsed_time, leak, status): + raise RuntimeError( + f"Failed to parse output: {tests} {elapsed_time} {leak} {status}" + ) + + leak = int(re.findall(r"[- ]\d+", leak.group(0))[0]) + status = re.findall(r"\w+", status.group(0))[1] + tests = int(re.findall(r"\d+", tests.group(0))[0]) + elapsed_time = int(re.findall(r"\d+", elapsed_time.group(0))[0]) + + test_results = { + "full_output": "\n".join(full_output), + "total_tests": total, + "failed_tests": tests, + "elapsed_time_ms": elapsed_time, + "memory_leak_bytes": leak, + "status": status, + } + + self.test_results = test_results + + output_file = "unit_tests_output.txt" + with open(output_file, "w") as f: + f.write(test_results["full_output"]) + + print( + f"::notice:: Total tests: {total} Failed tests: {tests} Status: {status} Elapsed time: {elapsed_time / 1000} s Memory leak: {leak} bytes" ) - sys.exit(1) - - leak = int(re.findall(r"[- ]\d+", leak.group(0))[0]) - status = re.findall(r"\w+", status.group(0))[1] - tests = int(re.findall(r"\d+", tests.group(0))[0]) - elapsed_time = int(re.findall(r"\d+", elapsed_time.group(0))[0]) - - if tests > 0 or status != "PASSED": - self.logger.error(f"Got {tests} failed tests.") - self.logger.error(f"Leaked (not failing on this stat): {leak}") - self.logger.error(f"Status: {status}") - self.logger.error(f"Time: {elapsed_time/1000} seconds") - flipper.stop() - return 1 - self.logger.info(f"Leaked (not failing on this stat): {leak}") - self.logger.info( - f"Tests ran successfully! Time elapsed {elapsed_time/1000} seconds. Passed {total} tests." - ) + if tests > 0 or status != "PASSED": + self.logger.error(f"Got {tests} failed tests.") + self.logger.error(f"Leaked (not failing on this stat): {leak}") + self.logger.error(f"Status: {status}") + self.logger.error(f"Time: {elapsed_time / 1000} seconds") + return 1 - flipper.stop() - return 0 + self.logger.info(f"Leaked (not failing on this stat): {leak}") + self.logger.info( + f"Tests ran successfully! Time elapsed {elapsed_time / 1000} seconds. Passed {total} tests." + ) + return 0 + + finally: + flipper.stop() if __name__ == "__main__": diff --git a/scripts/toolchain/windows-toolchain-download.ps1 b/scripts/toolchain/windows-toolchain-download.ps1 index 025f8341f79..85a5ab7247a 100644 --- a/scripts/toolchain/windows-toolchain-download.ps1 +++ b/scripts/toolchain/windows-toolchain-download.ps1 @@ -16,15 +16,15 @@ $toolchain_dist_temp_path = "$download_dir\$toolchain_dist_folder" try { if (Test-Path -LiteralPath "$toolchain_target_path") { - Write-Host -NoNewline "Removing old Windows toolchain.." - Remove-Item -LiteralPath "$toolchain_target_path" -Force -Recurse - Write-Host "done!" + Write-Host -NoNewline "Removing old Windows toolchain.." + Remove-Item -LiteralPath "$toolchain_target_path" -Force -Recurse + Write-Host "done!" } if (Test-path -LiteralPath "$toolchain_target_path\..\current") { - Write-Host -NoNewline "Unlinking 'current'.." + Write-Host -NoNewline "Unlinking 'current'.." Remove-Item -LiteralPath "$toolchain_target_path\..\current" -Force - Write-Host "done!" + Write-Host "done!" } if (!(Test-Path -LiteralPath "$toolchain_zip_temp_path" -PathType Leaf)) { @@ -46,7 +46,8 @@ if (Test-Path -LiteralPath "$toolchain_dist_temp_path") { Write-Host -NoNewline "Extracting Windows toolchain.." # This is faster than Expand-Archive Add-Type -Assembly "System.IO.Compression.Filesystem" -[System.IO.Compression.ZipFile]::ExtractToDirectory("$toolchain_zip_temp_path", "$download_dir") +Add-Type -Assembly "System.Text.Encoding" +[System.IO.Compression.ZipFile]::ExtractToDirectory("$toolchain_zip_temp_path", "$download_dir", [System.Text.Encoding]::UTF8) # Expand-Archive -LiteralPath "$toolchain_zip_temp_path" -DestinationPath "$download_dir" Write-Host -NoNewline "moving.." diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 5c89a28aa58..ca156bbaf8b 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -2,7 +2,6 @@ entry,status,name,type,params Version,+,80.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_vcp.h,, Header,+,applications/services/dialogs/dialogs.h,, Header,+,applications/services/dolphin/dolphin.h,, @@ -161,6 +160,7 @@ Header,+,lib/toolbox/manchester_encoder.h,, Header,+,lib/toolbox/md5_calc.h,, Header,+,lib/toolbox/name_generator.h,, Header,+,lib/toolbox/path.h,, +Header,+,lib/toolbox/pipe.h,, Header,+,lib/toolbox/pretty_format.h,, Header,+,lib/toolbox/protocols/protocol_dict.h,, Header,+,lib/toolbox/pulse_protocols/pulse_glue.h,, @@ -773,13 +773,6 @@ 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_ansi_parser_alloc,CliAnsiParser*, -Function,-,cli_ansi_parser_feed,CliAnsiParserResult,"CliAnsiParser*, char" -Function,-,cli_ansi_parser_free,void,CliAnsiParser* -Function,+,cli_app_should_stop,_Bool,FuriPipeSide* -Function,+,cli_delete_command,void,"Cli*, const char*" -Function,+,cli_print_usage,void,"const char*, const char*, const char*" Function,+,cli_vcp_disable,void,CliVcp* Function,+,cli_vcp_enable,void,CliVcp* Function,+,composite_api_resolver_add,void,"CompositeApiResolver*, const ElfApiInterface*" @@ -1051,6 +1044,7 @@ Function,+,flipper_format_update_uint32,_Bool,"FlipperFormat*, const char*, cons Function,+,flipper_format_write_bool,_Bool,"FlipperFormat*, const char*, const _Bool*, const uint16_t" Function,+,flipper_format_write_comment,_Bool,"FlipperFormat*, FuriString*" Function,+,flipper_format_write_comment_cstr,_Bool,"FlipperFormat*, const char*" +Function,+,flipper_format_write_empty_line,_Bool,FlipperFormat* Function,+,flipper_format_write_float,_Bool,"FlipperFormat*, const char*, const float*, const uint16_t" Function,+,flipper_format_write_header,_Bool,"FlipperFormat*, FuriString*, const uint32_t" Function,+,flipper_format_write_header_cstr,_Bool,"FlipperFormat*, const char*, const uint32_t" @@ -1125,7 +1119,6 @@ Function,+,furi_event_loop_stop,void,FuriEventLoop* Function,+,furi_event_loop_subscribe_event_flag,void,"FuriEventLoop*, FuriEventFlag*, FuriEventLoopEvent, FuriEventLoopEventCallback, void*" Function,+,furi_event_loop_subscribe_message_queue,void,"FuriEventLoop*, FuriMessageQueue*, FuriEventLoopEvent, FuriEventLoopEventCallback, void*" Function,+,furi_event_loop_subscribe_mutex,void,"FuriEventLoop*, FuriMutex*, FuriEventLoopEvent, FuriEventLoopEventCallback, void*" -Function,+,furi_event_loop_subscribe_pipe,void,"FuriEventLoop*, FuriPipeSide*, 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_tick_set,void,"FuriEventLoop*, uint32_t, FuriEventLoopTickCallback, void*" @@ -1550,16 +1543,6 @@ Function,+,furi_mutex_alloc,FuriMutex*,FuriMutexType Function,+,furi_mutex_free,void,FuriMutex* Function,+,furi_mutex_get_owner,FuriThreadId,FuriMutex* Function,+,furi_mutex_release,FuriStatus,FuriMutex* -Function,+,furi_pipe_alloc,FuriPipe,"size_t, size_t" -Function,+,furi_pipe_alloc_ex,FuriPipe,"FuriPipeSideReceiveSettings, FuriPipeSideReceiveSettings" -Function,+,furi_pipe_bytes_available,size_t,FuriPipeSide* -Function,+,furi_pipe_free,void,FuriPipeSide* -Function,+,furi_pipe_install_as_stdio,void,FuriPipeSide* -Function,+,furi_pipe_receive,size_t,"FuriPipeSide*, void*, size_t, FuriWait" -Function,+,furi_pipe_role,FuriPipeRole,FuriPipeSide* -Function,+,furi_pipe_send,size_t,"FuriPipeSide*, const void*, size_t, FuriWait" -Function,+,furi_pipe_spaces_available,size_t,FuriPipeSide* -Function,+,furi_pipe_state,FuriPipeState,FuriPipeSide* Function,+,furi_pubsub_alloc,FuriPubSub*, Function,+,furi_pubsub_free,void,FuriPubSub* Function,+,furi_pubsub_publish,void,"FuriPubSub*, void*" @@ -2298,6 +2281,22 @@ Function,+,pb_skip_field,_Bool,"pb_istream_t*, pb_wire_type_t" Function,+,pb_write,_Bool,"pb_ostream_t*, const pb_byte_t*, size_t" Function,-,pclose,int,FILE* Function,-,perror,void,const char* +Function,+,pipe_alloc,PipeSideBundle,"size_t, size_t" +Function,+,pipe_alloc_ex,PipeSideBundle,"PipeSideReceiveSettings, PipeSideReceiveSettings" +Function,+,pipe_attach_to_event_loop,void,"PipeSide*, FuriEventLoop*" +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_role,PipeRole,PipeSide* +Function,+,pipe_send,size_t,"PipeSide*, const void*, size_t, FuriWait" +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_spaces_available,size_t,PipeSide* +Function,+,pipe_state,PipeState,PipeSide* Function,+,plugin_manager_alloc,PluginManager*,"const char*, uint32_t, const ElfApiInterface*" Function,+,plugin_manager_free,void,PluginManager* Function,+,plugin_manager_get,const FlipperAppPluginDescriptor*,"PluginManager*, uint32_t" diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 5b1bc02d93f..6bbd6501754 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -232,6 +232,7 @@ Header,+,lib/toolbox/manchester_encoder.h,, Header,+,lib/toolbox/md5_calc.h,, Header,+,lib/toolbox/name_generator.h,, Header,+,lib/toolbox/path.h,, +Header,+,lib/toolbox/pipe.h,, Header,+,lib/toolbox/pretty_format.h,, Header,+,lib/toolbox/protocols/protocol_dict.h,, Header,+,lib/toolbox/pulse_protocols/pulse_glue.h,, @@ -1153,6 +1154,7 @@ Function,+,flipper_format_update_uint32,_Bool,"FlipperFormat*, const char*, cons Function,+,flipper_format_write_bool,_Bool,"FlipperFormat*, const char*, const _Bool*, const uint16_t" Function,+,flipper_format_write_comment,_Bool,"FlipperFormat*, FuriString*" Function,+,flipper_format_write_comment_cstr,_Bool,"FlipperFormat*, const char*" +Function,+,flipper_format_write_empty_line,_Bool,FlipperFormat* Function,+,flipper_format_write_float,_Bool,"FlipperFormat*, const char*, const float*, const uint16_t" Function,+,flipper_format_write_header,_Bool,"FlipperFormat*, FuriString*, const uint32_t" Function,+,flipper_format_write_header_cstr,_Bool,"FlipperFormat*, const char*, const uint32_t" @@ -1227,7 +1229,6 @@ Function,+,furi_event_loop_stop,void,FuriEventLoop* Function,+,furi_event_loop_subscribe_event_flag,void,"FuriEventLoop*, FuriEventFlag*, FuriEventLoopEvent, FuriEventLoopEventCallback, void*" Function,+,furi_event_loop_subscribe_message_queue,void,"FuriEventLoop*, FuriMessageQueue*, FuriEventLoopEvent, FuriEventLoopEventCallback, void*" Function,+,furi_event_loop_subscribe_mutex,void,"FuriEventLoop*, FuriMutex*, FuriEventLoopEvent, FuriEventLoopEventCallback, void*" -Function,+,furi_event_loop_subscribe_pipe,void,"FuriEventLoop*, FuriPipeSide*, 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_tick_set,void,"FuriEventLoop*, uint32_t, FuriEventLoopTickCallback, void*" @@ -1761,16 +1762,6 @@ Function,+,furi_mutex_alloc,FuriMutex*,FuriMutexType Function,+,furi_mutex_free,void,FuriMutex* Function,+,furi_mutex_get_owner,FuriThreadId,FuriMutex* Function,+,furi_mutex_release,FuriStatus,FuriMutex* -Function,+,furi_pipe_alloc,FuriPipe,"size_t, size_t" -Function,+,furi_pipe_alloc_ex,FuriPipe,"FuriPipeSideReceiveSettings, FuriPipeSideReceiveSettings" -Function,+,furi_pipe_bytes_available,size_t,FuriPipeSide* -Function,+,furi_pipe_free,void,FuriPipeSide* -Function,+,furi_pipe_install_as_stdio,void,FuriPipeSide* -Function,+,furi_pipe_receive,size_t,"FuriPipeSide*, void*, size_t, FuriWait" -Function,+,furi_pipe_role,FuriPipeRole,FuriPipeSide* -Function,+,furi_pipe_send,size_t,"FuriPipeSide*, const void*, size_t, FuriWait" -Function,+,furi_pipe_spaces_available,size_t,FuriPipeSide* -Function,+,furi_pipe_state,FuriPipeState,FuriPipeSide* Function,+,furi_pubsub_alloc,FuriPubSub*, Function,+,furi_pubsub_free,void,FuriPubSub* Function,+,furi_pubsub_publish,void,"FuriPubSub*, void*" @@ -1780,7 +1771,7 @@ Function,+,furi_record_close,void,const char* Function,+,furi_record_create,void,"const char*, void*" Function,+,furi_record_destroy,_Bool,const char* Function,+,furi_record_exists,_Bool,const char* -Function,-,furi_record_init,void, +Function,+,furi_record_init,void, Function,+,furi_record_open,void*,const char* Function,+,furi_run,void, Function,+,furi_semaphore_acquire,FuriStatus,"FuriSemaphore*, uint32_t" @@ -2926,6 +2917,22 @@ Function,+,pb_skip_field,_Bool,"pb_istream_t*, pb_wire_type_t" Function,+,pb_write,_Bool,"pb_ostream_t*, const pb_byte_t*, size_t" Function,-,pclose,int,FILE* Function,-,perror,void,const char* +Function,+,pipe_alloc,PipeSideBundle,"size_t, size_t" +Function,+,pipe_alloc_ex,PipeSideBundle,"PipeSideReceiveSettings, PipeSideReceiveSettings" +Function,+,pipe_attach_to_event_loop,void,"PipeSide*, FuriEventLoop*" +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_role,PipeRole,PipeSide* +Function,+,pipe_send,size_t,"PipeSide*, const void*, size_t, FuriWait" +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_spaces_available,size_t,PipeSide* +Function,+,pipe_state,PipeState,PipeSide* Function,+,plugin_manager_alloc,PluginManager*,"const char*, uint32_t, const ElfApiInterface*" Function,+,plugin_manager_free,void,PluginManager* Function,+,plugin_manager_get,const FlipperAppPluginDescriptor*,"PluginManager*, uint32_t" diff --git a/targets/f7/ble_glue/furi_ble/gatt.c b/targets/f7/ble_glue/furi_ble/gatt.c index e407865833c..b8ab094f96c 100644 --- a/targets/f7/ble_glue/furi_ble/gatt.c +++ b/targets/f7/ble_glue/furi_ble/gatt.c @@ -7,6 +7,12 @@ #define GATT_MIN_READ_KEY_SIZE (10) +#ifdef BLE_GATT_STRICT +#define ble_gatt_strict_crash(message) furi_crash(message) +#else +#define ble_gatt_strict_crash(message) +#endif + void ble_gatt_characteristic_init( uint16_t svc_handle, const BleGattCharacteristicParams* char_descriptor, @@ -42,6 +48,7 @@ void ble_gatt_characteristic_init( &char_instance->handle); if(status) { FURI_LOG_E(TAG, "Failed to add %s char: %d", char_descriptor->name, status); + ble_gatt_strict_crash("Failed to add characteristic"); } char_instance->descriptor_handle = 0; @@ -68,6 +75,7 @@ void ble_gatt_characteristic_init( &char_instance->descriptor_handle); if(status) { FURI_LOG_E(TAG, "Failed to add %s char descriptor: %d", char_descriptor->name, status); + ble_gatt_strict_crash("Failed to add characteristic descriptor"); } if(release_data) { free((void*)char_data); @@ -82,6 +90,7 @@ void ble_gatt_characteristic_delete( if(status) { FURI_LOG_E( TAG, "Failed to delete %s char: %d", char_instance->characteristic->name, status); + ble_gatt_strict_crash("Failed to delete characteristic"); } free((void*)char_instance->characteristic); } @@ -111,14 +120,27 @@ bool ble_gatt_characteristic_update( release_data = char_descriptor->data.callback.fn(context, &char_data, &char_data_size); } - tBleStatus result = aci_gatt_update_char_value( - svc_handle, char_instance->handle, 0, char_data_size, char_data); - if(result) { - FURI_LOG_E(TAG, "Failed updating %s characteristic: %d", char_descriptor->name, result); - } + tBleStatus result; + size_t retries_left = 1000; + do { + retries_left--; + result = aci_gatt_update_char_value( + svc_handle, char_instance->handle, 0, char_data_size, char_data); + if(result == BLE_STATUS_INSUFFICIENT_RESOURCES) { + FURI_LOG_W(TAG, "Insufficient resources for %s characteristic", char_descriptor->name); + furi_delay_ms(1); + } + } while(result == BLE_STATUS_INSUFFICIENT_RESOURCES && retries_left); + if(release_data) { free((void*)char_data); } + + if(result != BLE_STATUS_SUCCESS) { + FURI_LOG_E(TAG, "Failed updating %s characteristic: %d", char_descriptor->name, result); + ble_gatt_strict_crash("Failed to update characteristic"); + } + return result != BLE_STATUS_SUCCESS; } @@ -132,6 +154,7 @@ bool ble_gatt_service_add( Service_UUID_Type, Service_UUID, Service_Type, Max_Attribute_Records, Service_Handle); if(result) { FURI_LOG_E(TAG, "Failed to add service: %x", result); + ble_gatt_strict_crash("Failed to add service"); } return result == BLE_STATUS_SUCCESS; @@ -141,6 +164,7 @@ bool ble_gatt_service_delete(uint16_t svc_handle) { tBleStatus result = aci_gatt_del_service(svc_handle); if(result) { FURI_LOG_E(TAG, "Failed to delete service: %x", result); + ble_gatt_strict_crash("Failed to delete service"); } return result == BLE_STATUS_SUCCESS; diff --git a/targets/f7/stm32wb55xx_flash.ld b/targets/f7/stm32wb55xx_flash.ld index 524da6fc327..ef61bb23887 100644 --- a/targets/f7/stm32wb55xx_flash.ld +++ b/targets/f7/stm32wb55xx_flash.ld @@ -3,7 +3,7 @@ ENTRY(Reset_Handler) /* Highest address of the user mode stack */ _stack_end = 0x20030000; /* end of RAM */ /* Generate a link error if heap and stack don't fit into RAM */ -_stack_size = 0x200; /* required amount of stack */ +_stack_size = 0x400; /* required amount of stack */ MEMORY { FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K diff --git a/targets/f7/stm32wb55xx_ram_fw.ld b/targets/f7/stm32wb55xx_ram_fw.ld index f0e8ad678cd..93579788d00 100644 --- a/targets/f7/stm32wb55xx_ram_fw.ld +++ b/targets/f7/stm32wb55xx_ram_fw.ld @@ -3,7 +3,7 @@ ENTRY(Reset_Handler) /* Highest address of the user mode stack */ _stack_end = 0x20030000; /* end of RAM */ /* Generate a link error if heap and stack don't fit into RAM */ -_stack_size = 0x200; /* required amount of stack */ +_stack_size = 0x400; /* required amount of stack */ MEMORY { FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K From daf8ad7341495d9ad5b389c5c21c1f0841dde1e7 Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Tue, 24 Dec 2024 13:52:08 +0400 Subject: [PATCH 34/47] merge fixups --- furi/core/thread.h | 7 ------- 1 file changed, 7 deletions(-) diff --git a/furi/core/thread.h b/furi/core/thread.h index dcf28f38f6a..c1f615d1648 100644 --- a/furi/core/thread.h +++ b/furi/core/thread.h @@ -492,13 +492,6 @@ void furi_thread_get_stdout_callback(FuriThreadStdoutWriteCallback* callback, vo */ void furi_thread_get_stdin_callback(FuriThreadStdinReadCallback* callback, void** context); -/** - * @brief Get the standard input callback for the current thead. - * - * @return pointer to the standard in callback function - */ -FuriThreadStdinReadCallback furi_thread_get_stdin_callback(void); - /** Set standard output callback for the current thread. * * @param[in] callback pointer to the callback function or NULL to clear From 58bd664e0e172e2deca0c09908edf0418f4e32c6 Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Tue, 24 Dec 2024 14:03:21 +0400 Subject: [PATCH 35/47] temporarily exclude speaker_debug app --- applications/debug/application.fam | 2 +- .../debug/speaker_debug/application.fam | 22 +++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/applications/debug/application.fam b/applications/debug/application.fam index cdbf8fe18b6..557e90ac5d8 100644 --- a/applications/debug/application.fam +++ b/applications/debug/application.fam @@ -12,6 +12,6 @@ App( "display_test", "text_box_test", "file_browser_test", - "speaker_debug", + # "speaker_debug", # TODO: external fap support ], ) diff --git a/applications/debug/speaker_debug/application.fam b/applications/debug/speaker_debug/application.fam index 68d8b188bdd..2c319836d34 100644 --- a/applications/debug/speaker_debug/application.fam +++ b/applications/debug/speaker_debug/application.fam @@ -1,11 +1,11 @@ -App( - appid="speaker_debug", - name="Speaker Debug", - apptype=FlipperAppType.DEBUG, - entry_point="speaker_debug_app", - requires=["gui", "notification"], - stack_size=2 * 1024, - order=10, - fap_category="Debug", - fap_libs=["music_worker"], -) +# App( +# appid="speaker_debug", +# name="Speaker Debug", +# apptype=FlipperAppType.DEBUG, +# entry_point="speaker_debug_app", +# requires=["gui", "notification"], +# stack_size=2 * 1024, +# order=10, +# fap_category="Debug", +# fap_libs=["music_worker"], +# ) From 0916aeb150381cb6cde9fc03a3ece0dca9848d5e Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Tue, 24 Dec 2024 14:19:55 +0400 Subject: [PATCH 36/47] pvs and unit tests fixups --- applications/services/cli/cli.c | 29 +++++++++------------- applications/services/cli/cli.h | 40 ------------------------------- applications/services/cli/cli_i.h | 12 +++++++++- 3 files changed, 22 insertions(+), 59 deletions(-) diff --git a/applications/services/cli/cli.c b/applications/services/cli/cli.c index fdbf631a820..a344eb2ea9f 100644 --- a/applications/services/cli/cli.c +++ b/applications/services/cli/cli.c @@ -22,31 +22,24 @@ void cli_add_command( CliCommandFlag flags, CliExecuteCallback callback, void* context) { - // missing checks performed by cli_add_command_ex + furi_check(cli); + furi_check(name); + furi_check(callback); - CliCommand cmd = { + FuriString* name_str; + name_str = furi_string_alloc_set(name); + // command cannot contain spaces + furi_check(furi_string_search_char(name_str, ' ') == FURI_STRING_FAILURE); + + CliCommand command = { .name = name, .context = context, .execute_callback = callback, - .complete_callback = NULL, .flags = flags, }; - cli_add_command_ex(cli, &cmd); -} - -void cli_add_command_ex(Cli* cli, CliCommand* command) { - furi_check(cli); - furi_check(command); - furi_check(command->name); - furi_check(command->execute_callback); - - FuriString* name_str; - name_str = furi_string_alloc_set(command->name); - // command cannot contain spaces - furi_check(furi_string_search_char(name_str, ' ') == FURI_STRING_FAILURE); furi_check(furi_mutex_acquire(cli->mutex, FuriWaitForever) == FuriStatusOk); - CliCommandTree_set_at(cli->commands, name_str, *command); + CliCommandTree_set_at(cli->commands, name_str, command); furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk); furi_string_free(name_str); @@ -75,7 +68,7 @@ bool cli_get_command(Cli* cli, FuriString* command, CliCommand* result) { furi_check(furi_mutex_acquire(cli->mutex, FuriWaitForever) == FuriStatusOk); CliCommand* data = CliCommandTree_get(cli->commands, command); if(data) *result = *data; - + furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk); return !!data; diff --git a/applications/services/cli/cli.h b/applications/services/cli/cli.h index 56490fada60..166194181f5 100644 --- a/applications/services/cli/cli.h +++ b/applications/services/cli/cli.h @@ -60,46 +60,6 @@ void cli_add_command( CliExecuteCallback callback, void* context); -ARRAY_DEF(CommandCompletions, FuriString*, FURI_STRING_OPLIST); // -V524 -#define M_OPL_CommandCompletions_t() ARRAY_OPLIST(CommandCompletions) - -/** - * @brief Command autocomplete callback. - * - * This callback will be called from the shell thread. - * - * @param [in] partial_args Input after the name of the command up to the point - * where TAB was pressed. - * @param [out] full_args An initialized empty array that you fill up with - * suggestions for the entire `args`. - */ -typedef void (*CliCompleteCallback)( - PipeSide* pipe, - FuriString* partial_args, - CommandCompletions_t full_args, - void* context); - -/** - * @brief Extended command descriptor for `cli_add_command_ex` - */ -typedef struct { - const char* name; // Date: Wed, 25 Dec 2024 06:34:32 +0400 Subject: [PATCH 37/47] feat: commands in fals --- applications/main/application.fam | 4 +- applications/main/onewire/onewire_cli.c | 10 +- applications/services/bt/application.fam | 2 +- applications/services/cli/application.fam | 18 +- applications/services/cli/cli.c | 51 +++++- applications/services/cli/cli.h | 18 +- applications/services/cli/cli_commands.c | 16 +- applications/services/cli/cli_commands.h | 31 +++- applications/services/cli/cli_i.h | 5 +- applications/services/cli/cli_shell.c | 100 +++++++---- applications/services/cli/cli_shell.h | 3 +- .../services/cli/commands/hello_world.c | 10 ++ applications/services/cli/commands/neofetch.c | 159 ++++++++++++++++++ applications/services/power/application.fam | 2 +- applications/services/rpc/application.fam | 2 +- 15 files changed, 379 insertions(+), 52 deletions(-) create mode 100644 applications/services/cli/commands/hello_world.c create mode 100644 applications/services/cli/commands/neofetch.c diff --git a/applications/main/application.fam b/applications/main/application.fam index c488ae7d46b..4d316233731 100644 --- a/applications/main/application.fam +++ b/applications/main/application.fam @@ -21,9 +21,9 @@ App( name="On start hooks", apptype=FlipperAppType.METAPACKAGE, provides=[ - "cli_start", + "cli", "ibutton_start", - # "onewire_start", + "onewire_start", "subghz_start", "infrared_start", "lfrfid_start", diff --git a/applications/main/onewire/onewire_cli.c b/applications/main/onewire/onewire_cli.c index af3d4e8035c..e61b8c3064d 100644 --- a/applications/main/onewire/onewire_cli.c +++ b/applications/main/onewire/onewire_cli.c @@ -6,7 +6,7 @@ #include -static void onewire_cli(Cli* cli, FuriString* args, void* context); +static void onewire_cli(PipeSide* pipe, FuriString* args, void* context); void onewire_on_system_start(void) { #ifdef SRV_CLI @@ -23,8 +23,8 @@ static void onewire_cli_print_usage(void) { 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); uint8_t address[8]; bool done = false; @@ -53,7 +53,7 @@ static void onewire_cli_search(Cli* cli) { onewire_host_free(onewire); } -void onewire_cli(Cli* cli, FuriString* args, void* context) { +void onewire_cli(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); FuriString* cmd; cmd = furi_string_alloc(); @@ -65,7 +65,7 @@ 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); diff --git a/applications/services/bt/application.fam b/applications/services/bt/application.fam index 60627756fc2..2d2840e3a5d 100644 --- a/applications/services/bt/application.fam +++ b/applications/services/bt/application.fam @@ -5,7 +5,7 @@ App( entry_point="bt_srv", cdefines=["SRV_BT"], requires=[ - "cli_start", + "cli", "dialogs", ], provides=[ diff --git a/applications/services/cli/application.fam b/applications/services/cli/application.fam index d0c20ad4ac9..578f0410dce 100644 --- a/applications/services/cli/application.fam +++ b/applications/services/cli/application.fam @@ -1,5 +1,5 @@ App( - appid="cli_start", + appid="cli", apptype=FlipperAppType.STARTUP, entry_point="cli_on_system_start", cdefines=["SRV_CLI"], @@ -23,3 +23,19 @@ App( sdk_headers=["cli_vcp.h"], sources=["cli_vcp.c"], ) + +App( + appid="cli_hello_world", + apptype=FlipperAppType.PLUGIN, + entry_point="cli_hello_world_ep", + requires=["cli"], + sources=["commands/hello_world.c"], +) + +App( + appid="cli_neofetch", + apptype=FlipperAppType.PLUGIN, + entry_point="cli_neofetch_ep", + requires=["cli"], + sources=["commands/neofetch.c"], +) diff --git a/applications/services/cli/cli.c b/applications/services/cli/cli.c index a344eb2ea9f..3999ef80142 100644 --- a/applications/services/cli/cli.c +++ b/applications/services/cli/cli.c @@ -4,6 +4,8 @@ #include "cli_ansi.h" #include +#define TAG "cli" + struct Cli { CliCommandTree_t commands; FuriMutex* mutex; @@ -32,10 +34,10 @@ void cli_add_command( furi_check(furi_string_search_char(name_str, ' ') == FURI_STRING_FAILURE); CliCommand command = { - .name = name, .context = context, .execute_callback = callback, .flags = flags, + .stack_depth = CLI_BUILTIN_COMMAND_STACK_SIZE, }; furi_check(furi_mutex_acquire(cli->mutex, FuriWaitForever) == FuriStatusOk); @@ -74,6 +76,53 @@ bool cli_get_command(Cli* cli, FuriString* command, CliCommand* result) { return !!data; } +void cli_enumerate_external_commands(Cli* cli) { + furi_check(cli); + furi_check(furi_mutex_acquire(cli->mutex, FuriWaitForever) == FuriStatusOk); + FURI_LOG_D(TAG, "Enumerating external commands"); + + // remove external commands + CliCommandTree_t internal_cmds; + CliCommandTree_init(internal_cmds); + for + M_EACH(item, cli->commands, CliCommandTree_t) { + if(!(item->value_ptr->flags & CliCommandFlagExternal)) + CliCommandTree_set_at(internal_cmds, *item->key_ptr, *item->value_ptr); + } + CliCommandTree_move(cli->commands, internal_cmds); + + // iterate over files in plugin directory + Storage* storage = furi_record_open(RECORD_STORAGE); + File* plogin_dir = storage_file_alloc(storage); + + if(storage_dir_open(plogin_dir, CLI_COMMANDS_PATH)) { + char plugin_filename[64]; + FuriString* plugin_name = furi_string_alloc(); + + while(storage_dir_read(plogin_dir, NULL, plugin_filename, sizeof(plugin_filename))) { + FURI_LOG_T(TAG, "Plugin: %s", plugin_filename); + furi_string_set_str(plugin_name, plugin_filename); + furi_string_replace_all_str(plugin_name, ".fal", ""); + furi_string_replace_at(plugin_name, 0, 4, ""); // remove "cli_" in the beginning + CliCommand command = { + .context = NULL, + .execute_callback = NULL, + .flags = CliCommandFlagExternal, + }; + CliCommandTree_set_at(cli->commands, plugin_name, command); + } + + furi_string_free(plugin_name); + } + + storage_dir_close(plogin_dir); + storage_file_free(plogin_dir); + furi_record_close(RECORD_STORAGE); + + FURI_LOG_D(TAG, "Finished enumerating external commands"); + furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk); +} + void cli_lock_commands(Cli* cli) { furi_assert(cli); furi_check(furi_mutex_acquire(cli->mutex, FuriWaitForever) == FuriStatusOk); diff --git a/applications/services/cli/cli.h b/applications/services/cli/cli.h index 166194181f5..61524104b0e 100644 --- a/applications/services/cli/cli.h +++ b/applications/services/cli/cli.h @@ -20,14 +20,18 @@ typedef enum { CliCommandFlagParallelUnsafe = (1 << 0), /**< Unsafe 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 */ + + // internal flags (do not set them yourselves!) + + CliCommandFlagExternal = (1 << 3), /**< The command comes from a .fal file */ } CliCommandFlag; /** Cli type anonymous structure */ typedef struct Cli Cli; /** - * @brief CLI execution callbackpointer. Implement this interface and use - * `add_cli_command` or `cli_add_command_ex`. + * @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 @@ -44,8 +48,7 @@ typedef struct Cli Cli; typedef void (*CliExecuteCallback)(PipeSide* pipe, FuriString* args, void* context); /** - * @brief Registers a command with the CLI. Provides less options than - * `cli_add_command_ex` + * @brief Registers a command with the CLI. * * @param [in] cli Pointer to CLI instance * @param [in] name Command name @@ -68,6 +71,13 @@ void cli_add_command( */ void cli_delete_command(Cli* cli, const char* name); +/** + * @brief Reloads the list of externally available commands + * + * @param [in] cli pointer to cli instance + */ +void cli_enumerate_external_commands(Cli* cli); + /** * @brief Detects if Ctrl+C has been pressed or session has been terminated * diff --git a/applications/services/cli/cli_commands.c b/applications/services/cli/cli_commands.c index d595a16a4f0..7aee94e2f0d 100644 --- a/applications/services/cli/cli_commands.c +++ b/applications/services/cli/cli_commands.c @@ -60,7 +60,7 @@ void cli_command_help(PipeSide* pipe, FuriString* args, void* context) { UNUSED(pipe); UNUSED(args); UNUSED(context); - printf("Built-in shell commands:" ANSI_FG_GREEN); + printf("Available commands:" ANSI_FG_GREEN); // count non-hidden commands Cli* cli = furi_record_open(RECORD_CLI); @@ -91,6 +91,8 @@ void cli_command_help(PipeSide* pipe, FuriString* args, void* context) { } } + printf(ANSI_RESET + "\r\nIf you just added a new command and can't see it above, run `reload_ext_cmds`"); printf(ANSI_RESET "\r\nFind out more: https://docs.flipper.net/development/cli"); cli_unlock_commands(cli); @@ -512,10 +514,22 @@ void cli_command_i2c(PipeSide* pipe, FuriString* args, void* context) { furi_hal_i2c_release(&furi_hal_i2c_handle_external); } +void cli_command_reload_external(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); + UNUSED(args); + UNUSED(context); + Cli* cli = furi_record_open(RECORD_CLI); + cli_enumerate_external_commands(cli); + furi_record_close(RECORD_CLI); + printf("OK!"); +} + void cli_commands_init(Cli* cli) { cli_add_command(cli, "!", CliCommandFlagDefault, cli_command_info, (void*)true); cli_add_command(cli, "info", CliCommandFlagDefault, cli_command_info, NULL); cli_add_command(cli, "device_info", CliCommandFlagDefault, cli_command_info, (void*)true); + cli_add_command( + cli, "reload_ext_cmds", CliCommandFlagDefault, cli_command_reload_external, NULL); cli_add_command(cli, "?", CliCommandFlagDefault, cli_command_help, NULL); cli_add_command(cli, "help", CliCommandFlagDefault, cli_command_help, NULL); diff --git a/applications/services/cli/cli_commands.h b/applications/services/cli/cli_commands.h index 184eeb3739e..77d9930aff2 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 fd565f6dc38..ec7867f4fe2 100644 --- a/applications/services/cli/cli_i.h +++ b/applications/services/cli/cli_i.h @@ -13,11 +13,14 @@ extern "C" { #endif +#define CLI_BUILTIN_COMMAND_STACK_SIZE (3 * 1024U) +#define CLI_COMMANDS_PATH "/ext/apps_data/cli/plugins" + typedef struct { - const char* name; // #include #include #include #include +#include +#include #define TAG "CliShell" @@ -62,46 +65,80 @@ static void cli_shell_execute_command(CliShell* cli_shell, FuriString* command) FuriString* args = furi_string_alloc_set(command); furi_string_right(args, space + 1); - // find handler + PluginManager* plugin_manager = NULL; + Loader* loader = NULL; CliCommand command_data; - if(!cli_get_command(cli_shell->cli, command_name, &command_data)) { - printf( - ANSI_FG_RED "could not find command `%s`" ANSI_RESET, - furi_string_get_cstr(command_name)); - return; - } - // lock loader - Loader* loader = furi_record_open(RECORD_LOADER); - if(command_data.flags & CliCommandFlagParallelUnsafe) { - bool success = loader_lock(loader); - if(!success) { - printf(ANSI_FG_RED - "this command cannot be run while an application is open" ANSI_RESET); - return; + do { + // find handler + if(!cli_get_command(cli_shell->cli, command_name, &command_data)) { + printf( + ANSI_FG_RED "could not find command `%s`, try `help`" ANSI_RESET, + furi_string_get_cstr(command_name)); + break; } - } - // run command in separate thread - CliCommandThreadData thread_data = { - .command = &command_data, - .pipe = cli_shell->pipe, - .args = args, - }; - FuriThread* thread = furi_thread_alloc_ex( - furi_string_get_cstr(command_name), - CLI_COMMAND_STACK_SIZE, - cli_command_thread, - &thread_data); - furi_thread_start(thread); - furi_thread_join(thread); - furi_thread_free(thread); + // load external command + if(command_data.flags & CliCommandFlagExternal) { + plugin_manager = + plugin_manager_alloc(PLUGIN_APP_ID, PLUGIN_API_VERSION, firmware_api_interface); + FuriString* path = furi_string_alloc_printf( + "%s/cli_%s.fal", CLI_COMMANDS_PATH, furi_string_get_cstr(command_name)); + uint32_t plugin_cnt_last = plugin_manager_get_count(plugin_manager); + PluginManagerError error = + plugin_manager_load_single(plugin_manager, furi_string_get_cstr(path)); + furi_string_free(path); + + if(error != PluginManagerErrorNone) { + printf(ANSI_FG_RED "failed to load external command" ANSI_RESET); + break; + } + + const CliCommandDescriptor* plugin = + plugin_manager_get_ep(plugin_manager, plugin_cnt_last); + furi_assert(plugin); + furi_check(furi_string_cmp_str(command_name, plugin->name) == 0); + command_data.execute_callback = plugin->execute_callback; + command_data.flags = plugin->flags | CliCommandFlagExternal; + command_data.stack_depth = plugin->stack_depth; + } + + // lock loader + if(command_data.flags & CliCommandFlagParallelUnsafe) { + 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; + } + } + + // run command in separate thread + CliCommandThreadData thread_data = { + .command = &command_data, + .pipe = cli_shell->pipe, + .args = args, + }; + FuriThread* thread = furi_thread_alloc_ex( + furi_string_get_cstr(command_name), + command_data.stack_depth, + cli_command_thread, + &thread_data); + furi_thread_start(thread); + furi_thread_join(thread); + furi_thread_free(thread); + } while(0); furi_string_free(command_name); furi_string_free(args); // unlock loader - if(command_data.flags & CliCommandFlagParallelUnsafe) loader_unlock(loader); + if(loader) loader_unlock(loader); + furi_record_close(RECORD_LOADER); + + // unload external command + if(plugin_manager) plugin_manager_free(plugin_manager); } static size_t cli_shell_prompt_length(CliShell* cli_shell) { @@ -731,6 +768,7 @@ static int32_t cli_shell_thread(void* context) { CliShell* cli_shell = cli_shell_alloc(pipe); FURI_LOG_D(TAG, "Started"); + cli_enumerate_external_commands(cli_shell->cli); cli_shell_motd(); cli_shell_prompt(cli_shell); furi_event_loop_run(cli_shell->event_loop); diff --git a/applications/services/cli/cli_shell.h b/applications/services/cli/cli_shell.h index 2737017deb9..fdcabab8bff 100644 --- a/applications/services/cli/cli_shell.h +++ b/applications/services/cli/cli_shell.h @@ -7,8 +7,7 @@ extern "C" { #endif -#define CLI_SHELL_STACK_SIZE (1 * 1024U) -#define CLI_COMMAND_STACK_SIZE (3 * 1024U) +#define CLI_SHELL_STACK_SIZE (1 * 1024U) FuriThread* cli_shell_start(PipeSide* pipe); diff --git a/applications/services/cli/commands/hello_world.c b/applications/services/cli/commands/hello_world.c new file mode 100644 index 00000000000..81be97298fb --- /dev/null +++ b/applications/services/cli/commands/hello_world.c @@ -0,0 +1,10 @@ +#include "../cli_commands.h" + +static void execute(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); + UNUSED(args); + UNUSED(context); + puts("Hello, World!"); +} + +CLI_COMMAND_INTERFACE(hello_world, execute, CliCommandFlagDefault, 768); diff --git a/applications/services/cli/commands/neofetch.c b/applications/services/cli/commands/neofetch.c new file mode 100644 index 00000000000..e652212eb7d --- /dev/null +++ b/applications/services/cli/commands/neofetch.c @@ -0,0 +1,159 @@ +#include "../cli_commands.h" +#include +#include +#include +#include +#include + +static void execute(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); + UNUSED(args); + UNUSED(context); + + static const char* const neofetch_logo[] = { + " _.-------.._ -,", + " .-\"```\"--..,,_/ /`-, -, \\ ", + " .:\" /:/ /'\\ \\ ,_..., `. | |", + " / ,----/:/ /`\\ _\\~`_-\"` _;", + " ' / /`\"\"\"'\\ \\ \\.~`_-' ,-\"'/ ", + " | | | 0 | | .-' ,/` /", + " | ,..\\ \\ ,.-\"` ,/` /", + "; : `/`\"\"\\` ,/--==,/-----,", + "| `-...| -.___-Z:_______J...---;", + ": ` _-'", + }; +#define NEOFETCH_COLOR ANSI_FLIPPER_BRAND_ORANGE + + // Determine logo parameters + size_t logo_height = COUNT_OF(neofetch_logo), logo_width = 0; + for(size_t i = 0; i < logo_height; i++) + logo_width = MAX(logo_width, strlen(neofetch_logo[i])); + logo_width += 4; // space between logo and info + + // Format hostname delimiter + const size_t size_of_hostname = 4 + strlen(furi_hal_version_get_name_ptr()); + char delimiter[64]; + memset(delimiter, '-', size_of_hostname); + delimiter[size_of_hostname] = '\0'; + + // Get heap info + size_t heap_total = memmgr_get_total_heap(); + size_t heap_used = heap_total - memmgr_get_free_heap(); + uint16_t heap_percent = (100 * heap_used) / heap_total; + + // Get storage info + Storage* storage = furi_record_open(RECORD_STORAGE); + uint64_t ext_total, ext_free, ext_used, ext_percent; + storage_common_fs_info(storage, "/ext", &ext_total, &ext_free); + ext_used = ext_total - ext_free; + ext_percent = (100 * ext_used) / ext_total; + ext_used /= 1024 * 1024; + ext_total /= 1024 * 1024; + furi_record_close(RECORD_STORAGE); + + // Get battery info + uint16_t charge_percent = furi_hal_power_get_pct(); + const char* charge_state; + if(furi_hal_power_is_charging()) { + if((charge_percent < 100) && (!furi_hal_power_is_charging_done())) { + charge_state = "charging"; + } else { + charge_state = "charged"; + } + } else { + charge_state = "discharging"; + } + + // Get misc info + uint32_t uptime = furi_get_tick() / furi_kernel_get_tick_frequency(); + const Version* version = version_get(); + uint16_t major, minor; + furi_hal_info_get_api_version(&major, &minor); + + // Print ASCII art with info + const size_t info_height = 16; + for(size_t i = 0; i < MAX(logo_height, info_height); i++) { + printf(NEOFETCH_COLOR "%-*s", logo_width, (i < logo_height) ? neofetch_logo[i] : ""); + switch(i) { + case 0: // you@ + printf("you" ANSI_RESET "@" NEOFETCH_COLOR "%s", furi_hal_version_get_name_ptr()); + break; + case 1: // delimiter + printf(ANSI_RESET "%s", delimiter); + break; + case 2: // OS: FURI (SDK .) + printf( + "OS" ANSI_RESET ": FURI %s %s %s %s (SDK %hu.%hu)", + version_get_version(version), + version_get_gitbranch(version), + version_get_version(version), + version_get_githash(version), + major, + minor); + break; + case 3: // Host: + printf( + "Host" ANSI_RESET ": %s %s", + furi_hal_version_get_model_code(), + furi_hal_version_get_device_name_ptr()); + break; + case 4: // Kernel: FreeRTOS .. + printf( + "Kernel" ANSI_RESET ": FreeRTOS %d.%d.%d", + tskKERNEL_VERSION_MAJOR, + tskKERNEL_VERSION_MINOR, + tskKERNEL_VERSION_BUILD); + break; + case 5: // Uptime: ?h?m?s + printf( + "Uptime" ANSI_RESET ": %luh%lum%lus", + uptime / 60 / 60, + uptime / 60 % 60, + uptime % 60); + break; + case 6: // ST7567 128x64 @ 1 bpp in 1.4" + printf("Display" ANSI_RESET ": ST7567 128x64 @ 1 bpp in 1.4\""); + break; + case 7: // DE: GuiSrv + printf("DE" ANSI_RESET ": GuiSrv"); + break; + case 8: // Shell: CliSrv + printf("Shell" ANSI_RESET ": CliShell"); + break; + case 9: // CPU: STM32WB55RG @ 64 MHz + printf("CPU" ANSI_RESET ": STM32WB55RG @ 64 MHz"); + break; + case 10: // Memory: / B (??%) + printf( + "Memory" ANSI_RESET ": %zu / %zu B (%hu%%)", heap_used, heap_total, heap_percent); + break; + case 11: // Disk (/ext): / MiB (??%) + printf( + "Disk (/ext)" ANSI_RESET ": %llu / %llu MiB (%llu%%)", + ext_used, + ext_total, + ext_percent); + break; + case 12: // Battery: ??% () + printf("Battery" ANSI_RESET ": %hu%% (%s)" ANSI_RESET, charge_percent, charge_state); + break; + case 13: // empty space + break; + case 14: // Colors (line 1) + for(size_t j = 30; j <= 37; j++) + printf("\e[%dm███", j); + break; + case 15: // Colors (line 2) + for(size_t j = 90; j <= 97; j++) + printf("\e[%dm███", j); + break; + default: + break; + } + printf("\r\n"); + } + printf(ANSI_RESET); +#undef NEOFETCH_COLOR +} + +CLI_COMMAND_INTERFACE(neofetch, execute, CliCommandFlagDefault, 2048); diff --git a/applications/services/power/application.fam b/applications/services/power/application.fam index 059c570c40d..f14d88c5426 100644 --- a/applications/services/power/application.fam +++ b/applications/services/power/application.fam @@ -6,7 +6,7 @@ App( cdefines=["SRV_POWER"], requires=[ "gui", - "cli_start", + "cli", ], provides=[ "power_settings", diff --git a/applications/services/rpc/application.fam b/applications/services/rpc/application.fam index c8e26e0446f..7c0b6813369 100644 --- a/applications/services/rpc/application.fam +++ b/applications/services/rpc/application.fam @@ -3,7 +3,7 @@ App( apptype=FlipperAppType.STARTUP, entry_point="rpc_on_system_start", cdefines=["SRV_RPC"], - requires=["cli_start"], + requires=["cli"], order=10, sdk_headers=["rpc_app.h"], ) From c88891e3c6fc908265cec09a6b7c416b55212239 Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Thu, 9 Jan 2025 14:17:31 +0400 Subject: [PATCH 38/47] move commands out of flash, code cleanup --- applications/debug/application.fam | 2 +- .../debug/bt_debug_app/application.fam | 36 ++++----- .../debug/speaker_debug/application.fam | 22 +++--- applications/main/application.fam | 6 -- applications/main/ibutton/application.fam | 9 ++- applications/main/ibutton/ibutton_cli.c | 19 +---- applications/main/infrared/application.fam | 8 +- applications/main/infrared/infrared_cli.c | 16 +--- applications/main/lfrfid/application.fam | 8 +- applications/main/lfrfid/lfrfid_cli.c | 15 +--- applications/main/nfc/application.fam | 8 +- applications/main/nfc/nfc_cli.c | 13 +--- applications/main/onewire/application.fam | 10 ++- applications/main/onewire/onewire_cli.c | 18 +---- applications/main/subghz/application.fam | 8 +- applications/main/subghz/subghz_cli.c | 14 +--- applications/services/cli/application.fam | 4 + applications/services/cli/cli_shell.c | 74 ++++++++++++------- targets/f7/api_symbols.csv | 25 +++++-- 19 files changed, 149 insertions(+), 166 deletions(-) diff --git a/applications/debug/application.fam b/applications/debug/application.fam index 557e90ac5d8..cdbf8fe18b6 100644 --- a/applications/debug/application.fam +++ b/applications/debug/application.fam @@ -12,6 +12,6 @@ App( "display_test", "text_box_test", "file_browser_test", - # "speaker_debug", # TODO: external fap support + "speaker_debug", ], ) diff --git a/applications/debug/bt_debug_app/application.fam b/applications/debug/bt_debug_app/application.fam index 99803368660..8ed1ccc054f 100644 --- a/applications/debug/bt_debug_app/application.fam +++ b/applications/debug/bt_debug_app/application.fam @@ -1,18 +1,18 @@ -# App( -# appid="bt_debug", -# name="Bluetooth Debug", -# apptype=FlipperAppType.DEBUG, -# entry_point="bt_debug_app", -# cdefines=["SRV_BT"], -# requires=[ -# "bt", -# "gui", -# "dialogs", -# ], -# provides=[ -# "bt_debug", -# ], -# stack_size=1 * 1024, -# order=110, -# fap_category="Debug", -# ) +App( + appid="bt_debug", + name="Bluetooth Debug", + apptype=FlipperAppType.DEBUG, + entry_point="bt_debug_app", + cdefines=["SRV_BT"], + requires=[ + "bt", + "gui", + "dialogs", + ], + provides=[ + "bt_debug", + ], + stack_size=1 * 1024, + order=110, + fap_category="Debug", +) diff --git a/applications/debug/speaker_debug/application.fam b/applications/debug/speaker_debug/application.fam index 2c319836d34..68d8b188bdd 100644 --- a/applications/debug/speaker_debug/application.fam +++ b/applications/debug/speaker_debug/application.fam @@ -1,11 +1,11 @@ -# App( -# appid="speaker_debug", -# name="Speaker Debug", -# apptype=FlipperAppType.DEBUG, -# entry_point="speaker_debug_app", -# requires=["gui", "notification"], -# stack_size=2 * 1024, -# order=10, -# fap_category="Debug", -# fap_libs=["music_worker"], -# ) +App( + appid="speaker_debug", + name="Speaker Debug", + apptype=FlipperAppType.DEBUG, + entry_point="speaker_debug_app", + requires=["gui", "notification"], + stack_size=2 * 1024, + order=10, + fap_category="Debug", + fap_libs=["music_worker"], +) diff --git a/applications/main/application.fam b/applications/main/application.fam index 4d316233731..9d8604206c8 100644 --- a/applications/main/application.fam +++ b/applications/main/application.fam @@ -22,11 +22,5 @@ App( apptype=FlipperAppType.METAPACKAGE, provides=[ "cli", - "ibutton_start", - "onewire_start", - "subghz_start", - "infrared_start", - "lfrfid_start", - "nfc_start", ], ) diff --git a/applications/main/ibutton/application.fam b/applications/main/ibutton/application.fam index 01c02ec23d6..7ef735caf56 100644 --- a/applications/main/ibutton/application.fam +++ b/applications/main/ibutton/application.fam @@ -13,10 +13,11 @@ App( ) App( - appid="ibutton_start", - apptype=FlipperAppType.STARTUP, + appid="cli_ikey", targets=["f7"], - entry_point="ibutton_on_system_start", + apptype=FlipperAppType.PLUGIN, + entry_point="cli_ikey_ep", + requires=["cli"], sources=["ibutton_cli.c"], - order=60, ) + diff --git a/applications/main/ibutton/ibutton_cli.c b/applications/main/ibutton/ibutton_cli.c index d72c83ea74c..0fd36d31e1b 100644 --- a/applications/main/ibutton/ibutton_cli.c +++ b/applications/main/ibutton/ibutton_cli.c @@ -1,7 +1,7 @@ #include #include -#include +#include #include #include @@ -9,19 +9,6 @@ #include #include -static void ibutton_cli(PipeSide* pipe, 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", CliCommandFlagParallelUnsafe, 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"); @@ -229,7 +216,7 @@ void ibutton_cli_emulate(PipeSide* pipe, FuriString* args) { ibutton_protocols_free(protocols); } -void ibutton_cli(PipeSide* pipe, FuriString* args, void* context) { +static void execute(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); FuriString* cmd; cmd = furi_string_alloc(); @@ -252,3 +239,5 @@ void ibutton_cli(PipeSide* pipe, FuriString* args, void* context) { furi_string_free(cmd); } + +CLI_COMMAND_INTERFACE(ikey, execute, CliCommandFlagDefault, 1024); diff --git a/applications/main/infrared/application.fam b/applications/main/infrared/application.fam index 575bebbe481..79b3fdbfad3 100644 --- a/applications/main/infrared/application.fam +++ b/applications/main/infrared/application.fam @@ -15,14 +15,14 @@ App( ) App( - appid="infrared_start", - apptype=FlipperAppType.STARTUP, + appid="cli_ir", targets=["f7"], - entry_point="infrared_on_system_start", + apptype=FlipperAppType.PLUGIN, + entry_point="cli_ir_ep", + requires=["cli"], sources=[ "infrared_cli.c", "infrared_brute_force.c", "infrared_signal.c", ], - order=20, ) diff --git a/applications/main/infrared/infrared_cli.c b/applications/main/infrared/infrared_cli.c index ab34c12682f..aeab32c4ad7 100644 --- a/applications/main/infrared/infrared_cli.c +++ b/applications/main/infrared/infrared_cli.c @@ -1,5 +1,4 @@ -#include -#include +#include #include #include #include @@ -528,7 +527,7 @@ static void infrared_cli_process_universal(PipeSide* pipe, FuriString* args) { furi_string_free(arg2); } -static void infrared_cli_start_ir(PipeSide* pipe, FuriString* args, void* context) { +static void execute(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); if(furi_hal_infrared_is_busy()) { printf("INFRARED is busy. Exiting."); @@ -555,12 +554,5 @@ static void infrared_cli_start_ir(PipeSide* pipe, FuriString* args, void* contex furi_string_free(command); } -void infrared_on_system_start(void) { -#ifdef SRV_CLI - Cli* cli = (Cli*)furi_record_open(RECORD_CLI); - cli_add_command(cli, "ir", CliCommandFlagDefault, infrared_cli_start_ir, NULL); - furi_record_close(RECORD_CLI); -#else - UNUSED(infrared_cli_start_ir); -#endif -} + +CLI_COMMAND_INTERFACE(ir, execute, CliCommandFlagDefault, 2048); diff --git a/applications/main/lfrfid/application.fam b/applications/main/lfrfid/application.fam index c067d786fc4..d6fca74f48c 100644 --- a/applications/main/lfrfid/application.fam +++ b/applications/main/lfrfid/application.fam @@ -13,10 +13,10 @@ App( ) App( - appid="lfrfid_start", + appid="cli_rfid", targets=["f7"], - apptype=FlipperAppType.STARTUP, - entry_point="lfrfid_on_system_start", + apptype=FlipperAppType.PLUGIN, + entry_point="cli_rfid_ep", + requires=["cli"], sources=["lfrfid_cli.c"], - order=50, ) diff --git a/applications/main/lfrfid/lfrfid_cli.c b/applications/main/lfrfid/lfrfid_cli.c index f3d381b4698..1a77eed048f 100644 --- a/applications/main/lfrfid/lfrfid_cli.c +++ b/applications/main/lfrfid/lfrfid_cli.c @@ -1,7 +1,7 @@ #include #include #include -#include +#include #include #include #include @@ -15,15 +15,6 @@ #include #include -static void lfrfid_cli(PipeSide* pipe, 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"); @@ -545,7 +536,7 @@ static void lfrfid_cli_raw_emulate(PipeSide* pipe, FuriString* args) { furi_string_free(filepath); } -static void lfrfid_cli(PipeSide* pipe, FuriString* args, void* context) { +static void execute(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); FuriString* cmd; cmd = furi_string_alloc(); @@ -574,3 +565,5 @@ static void lfrfid_cli(PipeSide* pipe, FuriString* args, void* context) { furi_string_free(cmd); } + +CLI_COMMAND_INTERFACE(rfid, execute, CliCommandFlagDefault, 2048); diff --git a/applications/main/nfc/application.fam b/applications/main/nfc/application.fam index 6dbde7c3718..26057e735e2 100644 --- a/applications/main/nfc/application.fam +++ b/applications/main/nfc/application.fam @@ -248,10 +248,10 @@ App( ) App( - appid="nfc_start", + appid="cli_nfc", targets=["f7"], - apptype=FlipperAppType.STARTUP, - entry_point="nfc_on_system_start", + apptype=FlipperAppType.PLUGIN, + entry_point="cli_nfc_ep", + requires=["cli"], sources=["nfc_cli.c"], - order=30, ) diff --git a/applications/main/nfc/nfc_cli.c b/applications/main/nfc/nfc_cli.c index 5d0681462b2..7391437e8a9 100644 --- a/applications/main/nfc/nfc_cli.c +++ b/applications/main/nfc/nfc_cli.c @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -41,7 +42,7 @@ static void nfc_cli_field(PipeSide* pipe, FuriString* args) { furi_hal_nfc_release(); } -static void nfc_cli(PipeSide* pipe, FuriString* args, void* context) { +static void execute(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); FuriString* cmd; cmd = furi_string_alloc(); @@ -64,12 +65,4 @@ static void nfc_cli(PipeSide* pipe, FuriString* args, void* context) { furi_string_free(cmd); } -void nfc_on_system_start(void) { -#ifdef SRV_CLI - Cli* cli = furi_record_open(RECORD_CLI); - cli_add_command(cli, "nfc", CliCommandFlagDefault, nfc_cli, NULL); - furi_record_close(RECORD_CLI); -#else - UNUSED(nfc_cli); -#endif -} +CLI_COMMAND_INTERFACE(nfc, execute, CliCommandFlagDefault, 1024); diff --git a/applications/main/onewire/application.fam b/applications/main/onewire/application.fam index 3d35abce948..e38bcdfefde 100644 --- a/applications/main/onewire/application.fam +++ b/applications/main/onewire/application.fam @@ -1,6 +1,8 @@ App( - appid="onewire_start", - apptype=FlipperAppType.STARTUP, - entry_point="onewire_on_system_start", - order=60, + appid="cli_onewire", + targets=["f7"], + apptype=FlipperAppType.PLUGIN, + entry_point="cli_onewire_ep", + requires=["cli"], + sources=["onewire_cli.c"], ) diff --git a/applications/main/onewire/onewire_cli.c b/applications/main/onewire/onewire_cli.c index e61b8c3064d..d1525d1e8fd 100644 --- a/applications/main/onewire/onewire_cli.c +++ b/applications/main/onewire/onewire_cli.c @@ -1,23 +1,11 @@ #include #include -#include +#include #include #include -static void onewire_cli(PipeSide* pipe, 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"); @@ -53,7 +41,7 @@ static void onewire_cli_search(PipeSide* pipe) { onewire_host_free(onewire); } -void onewire_cli(PipeSide* pipe, FuriString* args, void* context) { +static void execute(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); FuriString* cmd; cmd = furi_string_alloc(); @@ -70,3 +58,5 @@ void onewire_cli(PipeSide* pipe, FuriString* args, void* context) { furi_string_free(cmd); } + +CLI_COMMAND_INTERFACE(onewire, execute, CliCommandFlagDefault, 1024); diff --git a/applications/main/subghz/application.fam b/applications/main/subghz/application.fam index 1abcf7f5487..fe7b07b1ebd 100644 --- a/applications/main/subghz/application.fam +++ b/applications/main/subghz/application.fam @@ -20,10 +20,10 @@ App( ) App( - appid="subghz_start", + appid="cli_subghz", targets=["f7"], - apptype=FlipperAppType.STARTUP, - entry_point="subghz_on_system_start", + apptype=FlipperAppType.PLUGIN, + entry_point="cli_subghz_ep", + requires=["cli"], sources=["subghz_cli.c", "helpers/subghz_chat.c"], - order=40, ) diff --git a/applications/main/subghz/subghz_cli.c b/applications/main/subghz/subghz_cli.c index fde18c7e07f..b9095ade845 100644 --- a/applications/main/subghz/subghz_cli.c +++ b/applications/main/subghz/subghz_cli.c @@ -4,7 +4,7 @@ #include #include -#include +#include #include #include @@ -1120,7 +1120,7 @@ static void subghz_cli_command_chat(PipeSide* pipe, FuriString* args) { printf("\r\nExit chat\r\n"); } -static void subghz_cli_command(PipeSide* pipe, FuriString* args, void* context) { +static void execute(PipeSide* pipe, FuriString* args, void* context) { FuriString* cmd; cmd = furi_string_alloc(); @@ -1188,12 +1188,4 @@ static void subghz_cli_command(PipeSide* pipe, FuriString* args, void* context) furi_string_free(cmd); } -void subghz_on_system_start(void) { -#ifdef SRV_CLI - Cli* cli = furi_record_open(RECORD_CLI); - cli_add_command(cli, "subghz", CliCommandFlagDefault, subghz_cli_command, NULL); - furi_record_close(RECORD_CLI); -#else - UNUSED(subghz_cli_command); -#endif -} +CLI_COMMAND_INTERFACE(subghz, execute, CliCommandFlagDefault, 2048); diff --git a/applications/services/cli/application.fam b/applications/services/cli/application.fam index 578f0410dce..e951f7a53ef 100644 --- a/applications/services/cli/application.fam +++ b/applications/services/cli/application.fam @@ -10,6 +10,10 @@ App( "cli_command_gpio.c", "cli_ansi.c", ], + sdk_headers=[ + "cli.h", + "cli_ansi.h", + ], order=0, ) diff --git a/applications/services/cli/cli_shell.c b/applications/services/cli/cli_shell.c index 3323fe75d44..1b011bbe3ff 100644 --- a/applications/services/cli/cli_shell.c +++ b/applications/services/cli/cli_shell.c @@ -44,6 +44,22 @@ typedef struct { FuriString* args; } CliCommandThreadData; +// =============== +// History helpers +// =============== + +static FuriString* cli_shell_history_selected_line(CliShell* cli_shell) { + return *ShellHistory_cget(cli_shell->history, cli_shell->history_position); +} + +static FuriString* cli_shell_history_editing_line(CliShell* cli_shell) { + return *ShellHistory_front(cli_shell->history); +} + +// ========= +// Execution +// ========= + static int32_t cli_command_thread(void* context) { CliCommandThreadData* thread_data = context; if(!(thread_data->command->flags & CliCommandFlagDontAttachStdio)) @@ -141,6 +157,10 @@ static void cli_shell_execute_command(CliShell* cli_shell, FuriString* command) if(plugin_manager) plugin_manager_free(plugin_manager); } +// ==================== +// Line editing helpers +// ==================== + static size_t cli_shell_prompt_length(CliShell* cli_shell) { UNUSED(cli_shell); return strlen(">: "); @@ -163,8 +183,8 @@ static void cli_shell_prompt(CliShell* cli_shell) { */ static void cli_shell_ensure_not_overwriting_history(CliShell* cli_shell) { if(cli_shell->history_position > 0) { - FuriString* source = *ShellHistory_cget(cli_shell->history, cli_shell->history_position); - FuriString* destination = *ShellHistory_front(cli_shell->history); + FuriString* source = cli_shell_history_selected_line(cli_shell); + FuriString* destination = cli_shell_history_editing_line(cli_shell); furi_string_set(destination, source); cli_shell->history_position = 0; } @@ -229,6 +249,10 @@ static size_t cli_skip_run(FuriString* string, size_t original_pos, CliSkipDirec return MAX(0, position); } +// ============ +// Autocomplete +// ============ + /** * @brief Update for the completions menu */ @@ -257,7 +281,7 @@ typedef struct { static CliShellCompletionSegment cli_shell_completion_segment(CliShell* cli_shell) { CliShellCompletionSegment segment; - FuriString* input = furi_string_alloc_set(*ShellHistory_front(cli_shell->history)); + FuriString* input = furi_string_alloc_set(cli_shell_history_editing_line(cli_shell)); furi_string_left(input, cli_shell->line_position); // find index of first non-space character @@ -277,7 +301,9 @@ static CliShellCompletionSegment cli_shell_completion_segment(CliShell* cli_shel segment.length = furi_string_size(input) - first_non_space; } else { segment.type = CliShellCompletionSegmentTypeArguments; - furi_crash("TODO:"); + segment.start = 0; + segment.length = 0; + // support removed, might reimplement in the future } furi_string_free(input); @@ -288,7 +314,7 @@ static void cli_shell_fill_completions(CliShell* cli_shell) { CommandCompletions_reset(cli_shell->completions); CliShellCompletionSegment segment = cli_shell_completion_segment(cli_shell); - FuriString* input = furi_string_alloc_set(*ShellHistory_front(cli_shell->history)); + FuriString* input = furi_string_alloc_set(cli_shell_history_editing_line(cli_shell)); furi_string_right(input, segment.start); furi_string_left(input, segment.length); @@ -303,7 +329,7 @@ static void cli_shell_fill_completions(CliShell* cli_shell) { } } else { - furi_crash("TODO:"); + // support removed, might reimplement in the future } furi_string_free(input); @@ -355,10 +381,7 @@ static void cli_shell_completions_render(CliShell* cli_shell, CliShellCompletion printf( ANSI_CURSOR_HOR_POS("%zu") ANSI_ERASE_DISPLAY(ANSI_ERASE_FROM_CURSOR_TO_END) ANSI_CURSOR_HOR_POS("%zu"), - strlen(prompt) + - furi_string_size( - *ShellHistory_cget(cli_shell->history, cli_shell->history_position)) + - 1, + strlen(prompt) + furi_string_size(cli_shell_history_selected_line(cli_shell)) + 1, strlen(prompt) + cli_shell->line_position + 1); cli_shell->is_displaying_completions = false; @@ -408,16 +431,13 @@ static void cli_shell_completions_render(CliShell* cli_shell, CliShellCompletion printf(ANSI_CURSOR_UP_BY("%zu"), new_y + 1); printf( ANSI_CURSOR_HOR_POS("%zu"), - strlen(prompt) + - furi_string_size( - *ShellHistory_cget(cli_shell->history, cli_shell->history_position)) + - 1); + strlen(prompt) + furi_string_size(cli_shell_history_selected_line(cli_shell)) + 1); } } else if(action == CliShellCompletionsActionSelect) { // insert selection into prompt CliShellCompletionSegment segment = cli_shell_completion_segment(cli_shell); - FuriString* input = *ShellHistory_cget(cli_shell->history, cli_shell->history_position); + FuriString* input = cli_shell_history_selected_line(cli_shell); FuriString* completion = *CommandCompletions_cget(cli_shell->completions, cli_shell->selected_completion); furi_string_replace_at( @@ -439,6 +459,10 @@ static void cli_shell_completions_render(CliShell* cli_shell, CliShellCompletion fflush(stdout); } +// ============= +// Input handler +// ============= + static void cli_shell_process_key(CliShell* cli_shell, CliKeyCombo key_combo) { FURI_LOG_T( TAG, "mod=%d, key=%d='%c'", key_combo.modifiers, key_combo.key, (char)key_combo.key); @@ -447,7 +471,7 @@ static void cli_shell_process_key(CliShell* cli_shell, CliKeyCombo key_combo) { // reset input if(cli_shell->is_displaying_completions) cli_shell_completions_render(cli_shell, CliShellCompletionsActionClose); - furi_string_reset(*ShellHistory_front(cli_shell->history)); + furi_string_reset(cli_shell_history_editing_line(cli_shell)); cli_shell->line_position = 0; cli_shell->history_position = 0; printf("^C"); @@ -455,7 +479,7 @@ static void cli_shell_process_key(CliShell* cli_shell, CliKeyCombo key_combo) { } else if(key_combo.modifiers == 0 && key_combo.key == CliKeyFF) { // usually Ctrl+L // clear screen - FuriString* command = *ShellHistory_cget(cli_shell->history, cli_shell->history_position); + FuriString* command = cli_shell_history_selected_line(cli_shell); char prompt[64]; cli_shell_format_prompt(cli_shell, prompt, sizeof(prompt)); printf( @@ -510,8 +534,7 @@ static void cli_shell_process_key(CliShell* cli_shell, CliKeyCombo key_combo) { // print prompt with selected command if(new_pos != cli_shell->history_position) { cli_shell->history_position = new_pos; - FuriString* command = - *ShellHistory_cget(cli_shell->history, cli_shell->history_position); + FuriString* command = cli_shell_history_selected_line(cli_shell); printf( ANSI_CURSOR_HOR_POS("1") ">: %s" ANSI_ERASE_LINE( ANSI_ERASE_FROM_CURSOR_TO_END), @@ -532,8 +555,7 @@ static void cli_shell_process_key(CliShell* cli_shell, CliKeyCombo key_combo) { } else { // go left and right in the current line - FuriString* command = - *ShellHistory_cget(cli_shell->history, cli_shell->history_position); + FuriString* command = cli_shell_history_selected_line(cli_shell); int increment = (key_combo.key == CliKeyRight) ? 1 : -1; size_t new_pos = CLAMP( (int)cli_shell->line_position + increment, (int)furi_string_size(command), 0); @@ -559,7 +581,7 @@ static void cli_shell_process_key(CliShell* cli_shell, CliKeyCombo key_combo) { // go to the end if(cli_shell->is_displaying_completions) cli_shell_completions_render(cli_shell, CliShellCompletionsActionClose); - FuriString* line = *ShellHistory_cget(cli_shell->history, cli_shell->history_position); + FuriString* line = cli_shell_history_selected_line(cli_shell); cli_shell->line_position = furi_string_size(line); printf( ANSI_CURSOR_HOR_POS("%zu"), @@ -571,7 +593,7 @@ static void cli_shell_process_key(CliShell* cli_shell, CliKeyCombo key_combo) { (key_combo.key == CliKeyBackspace || key_combo.key == CliKeyDEL)) { // erase one character cli_shell_ensure_not_overwriting_history(cli_shell); - FuriString* line = *ShellHistory_front(cli_shell->history); + FuriString* line = cli_shell_history_editing_line(cli_shell); if(cli_shell->line_position == 0) { putc(CliKeyBell, stdout); fflush(stdout); @@ -598,7 +620,7 @@ static void cli_shell_process_key(CliShell* cli_shell, CliKeyCombo key_combo) { // skip run of similar chars to the left or right if(cli_shell->is_displaying_completions) cli_shell_completions_render(cli_shell, CliShellCompletionsActionClose); - FuriString* line = *ShellHistory_cget(cli_shell->history, cli_shell->history_position); + FuriString* line = cli_shell_history_selected_line(cli_shell); CliSkipDirection direction = (key_combo.key == CliKeyLeft) ? CliSkipDirectionLeft : CliSkipDirectionRight; cli_shell->line_position = cli_skip_run(line, cli_shell->line_position, direction); @@ -610,7 +632,7 @@ static void cli_shell_process_key(CliShell* cli_shell, CliKeyCombo key_combo) { } else if(key_combo.modifiers == 0 && key_combo.key == CliKeyETB) { // delete run of similar chars to the left cli_shell_ensure_not_overwriting_history(cli_shell); - FuriString* line = *ShellHistory_cget(cli_shell->history, cli_shell->history_position); + FuriString* line = cli_shell_history_selected_line(cli_shell); size_t run_start = cli_skip_run(line, cli_shell->line_position, CliSkipDirectionLeft); furi_string_replace_at(line, run_start, cli_shell->line_position - run_start, ""); cli_shell->line_position = run_start; @@ -641,7 +663,7 @@ static void cli_shell_process_key(CliShell* cli_shell, CliKeyCombo key_combo) { } else if(key_combo.modifiers == 0 && key_combo.key >= CliKeySpace && key_combo.key < CliKeyDEL) { // insert character cli_shell_ensure_not_overwriting_history(cli_shell); - FuriString* line = *ShellHistory_front(cli_shell->history); + FuriString* line = cli_shell_history_editing_line(cli_shell); if(cli_shell->line_position == furi_string_size(line)) { furi_string_push_back(line, key_combo.key); printf("%c", key_combo.key); diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 6bbd6501754..1826aa0611a 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -3,6 +3,8 @@ Version,+,80.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,, @@ -850,6 +852,15 @@ 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, CliExecuteCallback, void*" +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_app_should_stop,_Bool,PipeSide* +Function,+,cli_delete_command,void,"Cli*, const char*" +Function,+,cli_enumerate_external_commands,void,Cli* +Function,+,cli_print_usage,void,"const char*, const char*, const char*" Function,+,cli_vcp_disable,void,CliVcp* Function,+,cli_vcp_enable,void,CliVcp* Function,+,composite_api_resolver_add,void,"CompositeApiResolver*, const ElfApiInterface*" @@ -3405,13 +3416,13 @@ Function,+,subghz_file_encoder_worker_get_level_duration,LevelDuration,void* Function,+,subghz_file_encoder_worker_is_running,_Bool,SubGhzFileEncoderWorker* Function,+,subghz_file_encoder_worker_start,_Bool,"SubGhzFileEncoderWorker*, const char*, const char*" Function,+,subghz_file_encoder_worker_stop,void,SubGhzFileEncoderWorker* -Function,-,subghz_keystore_alloc,SubGhzKeystore*, -Function,-,subghz_keystore_free,void,SubGhzKeystore* -Function,-,subghz_keystore_get_data,SubGhzKeyArray_t*,SubGhzKeystore* -Function,-,subghz_keystore_load,_Bool,"SubGhzKeystore*, const char*" -Function,-,subghz_keystore_raw_encrypted_save,_Bool,"const char*, const char*, uint8_t*" -Function,-,subghz_keystore_raw_get_data,_Bool,"const char*, size_t, uint8_t*, size_t" -Function,-,subghz_keystore_save,_Bool,"SubGhzKeystore*, const char*, uint8_t*" +Function,+,subghz_keystore_alloc,SubGhzKeystore*, +Function,+,subghz_keystore_free,void,SubGhzKeystore* +Function,+,subghz_keystore_get_data,SubGhzKeyArray_t*,SubGhzKeystore* +Function,+,subghz_keystore_load,_Bool,"SubGhzKeystore*, const char*" +Function,+,subghz_keystore_raw_encrypted_save,_Bool,"const char*, const char*, uint8_t*" +Function,+,subghz_keystore_raw_get_data,_Bool,"const char*, size_t, uint8_t*, size_t" +Function,+,subghz_keystore_save,_Bool,"SubGhzKeystore*, const char*, uint8_t*" Function,+,subghz_protocol_blocks_add_bit,void,"SubGhzBlockDecoder*, uint8_t" Function,+,subghz_protocol_blocks_add_bytes,uint8_t,"const uint8_t[], size_t" Function,+,subghz_protocol_blocks_add_to_128_bit,void,"SubGhzBlockDecoder*, uint8_t, uint64_t*" From 1a54a005ca6dce9fe3b5eaab9cf39ebe70c1ac6b Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Thu, 9 Jan 2025 14:23:17 +0400 Subject: [PATCH 39/47] ci: fix errors --- applications/debug/unit_tests/application.fam | 2 +- applications/main/ibutton/application.fam | 1 - targets/f18/api_symbols.csv | 11 +++++++++++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/applications/debug/unit_tests/application.fam b/applications/debug/unit_tests/application.fam index f92d7e66fb8..05e83440284 100644 --- a/applications/debug/unit_tests/application.fam +++ b/applications/debug/unit_tests/application.fam @@ -4,7 +4,7 @@ App( entry_point="unit_tests_on_system_start", sources=["unit_tests.c", "test_runner.c", "unit_test_api_table.cpp"], cdefines=["APP_UNIT_TESTS"], - requires=["system_settings", "subghz_start"], + requires=["system_settings", "cli_subghz"], provides=["delay_test"], resources="resources", order=100, diff --git a/applications/main/ibutton/application.fam b/applications/main/ibutton/application.fam index 7ef735caf56..84afe0f021e 100644 --- a/applications/main/ibutton/application.fam +++ b/applications/main/ibutton/application.fam @@ -20,4 +20,3 @@ App( requires=["cli"], sources=["ibutton_cli.c"], ) - diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index ca156bbaf8b..212330d8ba8 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -2,6 +2,8 @@ entry,status,name,type,params Version,+,80.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,, @@ -773,6 +775,15 @@ 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, CliExecuteCallback, void*" +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_app_should_stop,_Bool,PipeSide* +Function,+,cli_delete_command,void,"Cli*, const char*" +Function,+,cli_enumerate_external_commands,void,Cli* +Function,+,cli_print_usage,void,"const char*, const char*, const char*" Function,+,cli_vcp_disable,void,CliVcp* Function,+,cli_vcp_enable,void,CliVcp* Function,+,composite_api_resolver_add,void,"CompositeApiResolver*, const ElfApiInterface*" From c316a9fbf7fc7a6fd60091f0fa49472608524b01 Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Thu, 9 Jan 2025 16:08:14 +0400 Subject: [PATCH 40/47] fix: run commands in buffer when stopping session --- applications/services/cli/cli_shell.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/applications/services/cli/cli_shell.c b/applications/services/cli/cli_shell.c index 1b011bbe3ff..dda8abcedbe 100644 --- a/applications/services/cli/cli_shell.c +++ b/applications/services/cli/cli_shell.c @@ -681,7 +681,9 @@ static void cli_shell_process_key(CliShell* cli_shell, CliKeyCombo key_combo) { } static void cli_shell_pipe_broken(PipeSide* pipe, void* context) { - UNUSED(pipe); + // 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); } @@ -734,7 +736,7 @@ static CliShell* cli_shell_alloc(PipeSide* pipe) { pipe_attach_to_event_loop(cli_shell->pipe, cli_shell->event_loop); pipe_set_callback_context(cli_shell->pipe, cli_shell); pipe_set_data_arrived_callback(cli_shell->pipe, cli_shell_data_available, 0); - pipe_set_broken_callback(cli_shell->pipe, cli_shell_pipe_broken, FuriEventLoopEventFlagEdge); + pipe_set_broken_callback(cli_shell->pipe, cli_shell_pipe_broken, 0); return cli_shell; } From a1d523986373b477e51b1da11366cae78e9cfcfd Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Tue, 4 Feb 2025 17:28:38 +0400 Subject: [PATCH 41/47] speedup cli file transfer --- applications/services/cli/cli.c | 15 ++++++++- applications/services/cli/cli.h | 27 ++++++++++++++-- applications/services/cli/cli_shell.c | 36 +++++++++++++-------- applications/services/cli/cli_vcp.c | 22 +++++++------ applications/services/storage/storage_cli.c | 2 +- targets/f7/api_symbols.csv | 3 +- 6 files changed, 77 insertions(+), 28 deletions(-) diff --git a/applications/services/cli/cli.c b/applications/services/cli/cli.c index 3999ef80142..17a5e6d507a 100644 --- a/applications/services/cli/cli.c +++ b/applications/services/cli/cli.c @@ -24,10 +24,23 @@ void cli_add_command( CliCommandFlag flags, 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); + // the shell always attaches the pipe to the stdio, thus both flags can't be used at once + if(flags & CliCommandFlagUseShellThread) furi_check(!(flags & CliCommandFlagDontAttachStdio)); + FuriString* name_str; name_str = furi_string_alloc_set(name); // command cannot contain spaces @@ -37,7 +50,7 @@ void cli_add_command( .context = context, .execute_callback = callback, .flags = flags, - .stack_depth = CLI_BUILTIN_COMMAND_STACK_SIZE, + .stack_depth = stack_size, }; furi_check(furi_mutex_acquire(cli->mutex, FuriWaitForever) == FuriStatusOk); diff --git a/applications/services/cli/cli.h b/applications/services/cli/cli.h index 61524104b0e..43e631c3cee 100644 --- a/applications/services/cli/cli.h +++ b/applications/services/cli/cli.h @@ -20,10 +20,13 @@ typedef enum { CliCommandFlagParallelUnsafe = (1 << 0), /**< Unsafe 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 */ + CliCommandFlagUseShellThread = + (1 + << 3), /**< Don't start a separate thread to run the command in. Incompatible with DontAttachStdio */ // internal flags (do not set them yourselves!) - CliCommandFlagExternal = (1 << 3), /**< The command comes from a .fal file */ + CliCommandFlagExternal = (1 << 4), /**< The command comes from a .fal file */ } CliCommandFlag; /** Cli type anonymous structure */ @@ -48,7 +51,8 @@ typedef struct Cli Cli; typedef void (*CliExecuteCallback)(PipeSide* pipe, FuriString* args, void* context); /** - * @brief Registers a command with the CLI. + * @brief Registers a command with the CLI. Provides less options than the `_ex` + * counterpart. * * @param [in] cli Pointer to CLI instance * @param [in] name Command name @@ -63,6 +67,25 @@ void cli_add_command( 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 * diff --git a/applications/services/cli/cli_shell.c b/applications/services/cli/cli_shell.c index dda8abcedbe..c5c68d49282 100644 --- a/applications/services/cli/cli_shell.c +++ b/applications/services/cli/cli_shell.c @@ -117,6 +117,9 @@ static void cli_shell_execute_command(CliShell* cli_shell, FuriString* command) command_data.execute_callback = plugin->execute_callback; command_data.flags = plugin->flags | CliCommandFlagExternal; command_data.stack_depth = plugin->stack_depth; + + // external commands have to run in an external thread + furi_check(!(command_data.flags & CliCommandFlagUseShellThread)); } // lock loader @@ -130,20 +133,25 @@ static void cli_shell_execute_command(CliShell* cli_shell, FuriString* command) } } - // run command in separate thread - CliCommandThreadData thread_data = { - .command = &command_data, - .pipe = cli_shell->pipe, - .args = args, - }; - FuriThread* thread = furi_thread_alloc_ex( - furi_string_get_cstr(command_name), - command_data.stack_depth, - cli_command_thread, - &thread_data); - furi_thread_start(thread); - furi_thread_join(thread); - furi_thread_free(thread); + if(command_data.flags & CliCommandFlagUseShellThread) { + // run command in this thread + command_data.execute_callback(cli_shell->pipe, args, command_data.context); + } else { + // run command in separate thread + CliCommandThreadData thread_data = { + .command = &command_data, + .pipe = cli_shell->pipe, + .args = args, + }; + FuriThread* thread = furi_thread_alloc_ex( + furi_string_get_cstr(command_name), + command_data.stack_depth, + cli_command_thread, + &thread_data); + furi_thread_start(thread); + furi_thread_join(thread); + furi_thread_free(thread); + } } while(0); furi_string_free(command_name); diff --git a/applications/services/cli/cli_vcp.c b/applications/services/cli/cli_vcp.c index 3eb38963b4c..87ac0cbcc16 100644 --- a/applications/services/cli/cli_vcp.c +++ b/applications/services/cli/cli_vcp.c @@ -8,13 +8,17 @@ #define TAG "CliVcp" -#define USB_CDC_PKT_LEN CDC_DATA_SZ -#define VCP_BUF_SIZE (USB_CDC_PKT_LEN * 3) - -#define VCP_IF_NUM 0 - +#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 +#ifdef CLI_VCP_TRACE +#define VCP_TRACE(...) FURI_LOG_T(TAG, __VA_ARGS__) +#else +#define VCP_TRACE(...) +#endif + typedef struct { enum { CliVcpMessageTypeEnable, @@ -61,7 +65,7 @@ static void cli_vcp_maybe_send_data(CliVcp* cli_vcp) { uint8_t buf[USB_CDC_PKT_LEN]; size_t length = pipe_receive(cli_vcp->own_pipe, buf, sizeof(buf), 0); if(length > 0 || cli_vcp->previous_tx_length == USB_CDC_PKT_LEN) { - FURI_LOG_T(TAG, "cdc_send length=%zu", length); + VCP_TRACE(TAG, "cdc_send length=%zu", length); cli_vcp->is_currently_transmitting = true; furi_hal_cdc_send(VCP_IF_NUM, buf, length); } @@ -79,7 +83,7 @@ static void cli_vcp_maybe_receive_data(CliVcp* cli_vcp) { uint8_t buf[USB_CDC_PKT_LEN]; size_t length = furi_hal_cdc_receive(VCP_IF_NUM, buf, sizeof(buf)); - FURI_LOG_T(TAG, "cdc_receive length=%zu", length); + VCP_TRACE(TAG, "cdc_receive length=%zu", length); furi_check(pipe_send(cli_vcp->own_pipe, buf, length, 0) == length); } @@ -185,13 +189,13 @@ static void cli_vcp_internal_message_received(FuriEventLoopObject* object, void* switch(message) { case CliVcpInternalMessageTxDone: - FURI_LOG_T(TAG, "TxDone"); + VCP_TRACE(TAG, "TxDone"); cli_vcp->is_currently_transmitting = false; cli_vcp_maybe_send_data(cli_vcp); break; case CliVcpInternalMessageRx: - FURI_LOG_T(TAG, "Rx"); + VCP_TRACE(TAG, "Rx"); cli_vcp_maybe_receive_data(cli_vcp); break; diff --git a/applications/services/storage/storage_cli.c b/applications/services/storage/storage_cli.c index bb7a08cad79..fce4ec51b2b 100644 --- a/applications/services/storage/storage_cli.c +++ b/applications/services/storage/storage_cli.c @@ -692,7 +692,7 @@ static void storage_cli_factory_reset(PipeSide* pipe, FuriString* args, void* co void storage_on_system_start(void) { #ifdef SRV_CLI Cli* cli = furi_record_open(RECORD_CLI); - cli_add_command(cli, RECORD_STORAGE, CliCommandFlagDefault, storage_cli, NULL); + cli_add_command_ex(cli, RECORD_STORAGE, CliCommandFlagUseShellThread, storage_cli, NULL, 512); cli_add_command(cli, "factory_reset", CliCommandFlagDefault, storage_cli_factory_reset, NULL); furi_record_close(RECORD_CLI); #else diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 1826aa0611a..da4bcfea61a 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,80.0,, +Version,+,80.1,, 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,, @@ -853,6 +853,7 @@ Function,-,cfree,void,void* Function,-,clearerr,void,FILE* Function,-,clearerr_unlocked,void,FILE* 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* From 1dce56976f41192c2f0b673f3a16ef22e54abeae Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Tue, 4 Feb 2025 18:37:57 +0400 Subject: [PATCH 42/47] fix f18 --- targets/f18/api_symbols.csv | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 212330d8ba8..7aa664d99d7 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,80.0,, +Version,+,80.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, Header,+,applications/services/cli/cli.h,, @@ -776,6 +776,7 @@ Function,-,cfree,void,void* Function,-,clearerr,void,FILE* Function,-,clearerr_unlocked,void,FILE* 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* From 3722940d9f6793a56949a8caaeec7677fcd49b40 Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Tue, 11 Feb 2025 02:28:45 +0400 Subject: [PATCH 43/47] separate cli_shell into modules --- applications/services/cli/application.fam | 4 +- applications/services/cli/cli_ansi.h | 2 +- applications/services/cli/cli_i.h | 3 - applications/services/cli/cli_shell.c | 818 ------------------ applications/services/cli/cli_vcp.c | 2 +- applications/services/cli/shell/cli_shell.c | 547 ++++++++++++ .../services/cli/{ => shell}/cli_shell.h | 2 +- .../cli/shell/cli_shell_completions.c | 199 +++++ .../cli/shell/cli_shell_completions.h | 67 ++ .../services/cli/shell/cli_shell_line.c | 70 ++ .../services/cli/shell/cli_shell_line.h | 67 ++ 11 files changed, 956 insertions(+), 825 deletions(-) delete mode 100644 applications/services/cli/cli_shell.c create mode 100644 applications/services/cli/shell/cli_shell.c rename applications/services/cli/{ => shell}/cli_shell.h (80%) create mode 100644 applications/services/cli/shell/cli_shell_completions.c create mode 100644 applications/services/cli/shell/cli_shell_completions.h create mode 100644 applications/services/cli/shell/cli_shell_line.c create mode 100644 applications/services/cli/shell/cli_shell_line.h diff --git a/applications/services/cli/application.fam b/applications/services/cli/application.fam index e951f7a53ef..f775553dd07 100644 --- a/applications/services/cli/application.fam +++ b/applications/services/cli/application.fam @@ -5,7 +5,9 @@ App( cdefines=["SRV_CLI"], sources=[ "cli.c", - "cli_shell.c", + "shell/cli_shell.c", + "shell/cli_shell_completions.c", + "shell/cli_shell_line.c", "cli_commands.c", "cli_command_gpio.c", "cli_ansi.c", diff --git a/applications/services/cli/cli_ansi.h b/applications/services/cli/cli_ansi.h index 0b850c5e30d..20bf33d8e8a 100644 --- a/applications/services/cli/cli_ansi.h +++ b/applications/services/cli/cli_ansi.h @@ -139,7 +139,7 @@ CliAnsiParserResult cli_ansi_parser_feed(CliAnsiParser* parser, char c); /** * @brief Feeds an ANSI parser a timeout event * - * As a user of the ANSI parses API, you are responsible for calling this + * 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 diff --git a/applications/services/cli/cli_i.h b/applications/services/cli/cli_i.h index ec7867f4fe2..bc14ec657a9 100644 --- a/applications/services/cli/cli_i.h +++ b/applications/services/cli/cli_i.h @@ -36,9 +36,6 @@ BPTREE_DEF2( M_POD_OPLIST); #define M_OPL_CliCommandTree_t() BPTREE_OPLIST(CliCommandTree, M_POD_OPLIST) -ARRAY_DEF(CommandCompletions, FuriString*, FURI_STRING_OPLIST); // -V524 -#define M_OPL_CommandCompletions_t() ARRAY_OPLIST(CommandCompletions) - bool cli_get_command(Cli* cli, FuriString* command, CliCommand* result); void cli_lock_commands(Cli* cli); diff --git a/applications/services/cli/cli_shell.c b/applications/services/cli/cli_shell.c deleted file mode 100644 index c5c68d49282..00000000000 --- a/applications/services/cli/cli_shell.c +++ /dev/null @@ -1,818 +0,0 @@ -#include "cli_shell.h" -#include "cli_ansi.h" -#include "cli_i.h" -#include "cli_commands.h" -#include -#include -#include -#include -#include -#include -#include - -#define TAG "CliShell" - -#define HISTORY_DEPTH 10 -#define COMPLETION_COLUMNS 3 -#define COMPLETION_COLUMN_WIDTH "30" -#define COMPLETION_COLUMN_WIDTH_I 30 -#define ANSI_TIMEOUT_MS 10 - -ARRAY_DEF(ShellHistory, FuriString*, FURI_STRING_OPLIST); // -V524 -#define M_OPL_ShellHistory_t() ARRAY_OPLIST(ShellHistory) - -typedef struct { - Cli* cli; - - FuriEventLoop* event_loop; - PipeSide* pipe; - CliAnsiParser* ansi_parser; - FuriEventLoopTimer* ansi_parsing_timer; - - size_t history_position; - size_t line_position; - ShellHistory_t history; - - CommandCompletions_t completions; - size_t selected_completion; - bool is_displaying_completions; -} CliShell; - -typedef struct { - CliCommand* command; - PipeSide* pipe; - FuriString* args; -} CliCommandThreadData; - -// =============== -// History helpers -// =============== - -static FuriString* cli_shell_history_selected_line(CliShell* cli_shell) { - return *ShellHistory_cget(cli_shell->history, cli_shell->history_position); -} - -static FuriString* cli_shell_history_editing_line(CliShell* cli_shell) { - return *ShellHistory_front(cli_shell->history); -} - -// ========= -// Execution -// ========= - -static int32_t cli_command_thread(void* context) { - CliCommandThreadData* thread_data = context; - if(!(thread_data->command->flags & CliCommandFlagDontAttachStdio)) - pipe_install_as_stdio(thread_data->pipe); - - thread_data->command->execute_callback( - thread_data->pipe, thread_data->args, thread_data->command->context); - - fflush(stdout); - return 0; -} - -static void cli_shell_execute_command(CliShell* cli_shell, FuriString* command) { - // split command into command and args - size_t space = furi_string_search_char(command, ' '); - if(space == FURI_STRING_FAILURE) space = furi_string_size(command); - FuriString* command_name = furi_string_alloc_set(command); - furi_string_left(command_name, space); - FuriString* args = furi_string_alloc_set(command); - furi_string_right(args, space + 1); - - PluginManager* plugin_manager = NULL; - Loader* loader = NULL; - CliCommand command_data; - - do { - // find handler - if(!cli_get_command(cli_shell->cli, command_name, &command_data)) { - printf( - ANSI_FG_RED "could not find command `%s`, try `help`" ANSI_RESET, - furi_string_get_cstr(command_name)); - break; - } - - // load external command - if(command_data.flags & CliCommandFlagExternal) { - plugin_manager = - plugin_manager_alloc(PLUGIN_APP_ID, PLUGIN_API_VERSION, firmware_api_interface); - FuriString* path = furi_string_alloc_printf( - "%s/cli_%s.fal", CLI_COMMANDS_PATH, furi_string_get_cstr(command_name)); - uint32_t plugin_cnt_last = plugin_manager_get_count(plugin_manager); - PluginManagerError error = - plugin_manager_load_single(plugin_manager, furi_string_get_cstr(path)); - furi_string_free(path); - - if(error != PluginManagerErrorNone) { - printf(ANSI_FG_RED "failed to load external command" ANSI_RESET); - break; - } - - const CliCommandDescriptor* plugin = - plugin_manager_get_ep(plugin_manager, plugin_cnt_last); - furi_assert(plugin); - furi_check(furi_string_cmp_str(command_name, plugin->name) == 0); - command_data.execute_callback = plugin->execute_callback; - command_data.flags = plugin->flags | CliCommandFlagExternal; - command_data.stack_depth = plugin->stack_depth; - - // external commands have to run in an external thread - furi_check(!(command_data.flags & CliCommandFlagUseShellThread)); - } - - // lock loader - if(command_data.flags & CliCommandFlagParallelUnsafe) { - 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; - } - } - - if(command_data.flags & CliCommandFlagUseShellThread) { - // run command in this thread - command_data.execute_callback(cli_shell->pipe, args, command_data.context); - } else { - // run command in separate thread - CliCommandThreadData thread_data = { - .command = &command_data, - .pipe = cli_shell->pipe, - .args = args, - }; - FuriThread* thread = furi_thread_alloc_ex( - furi_string_get_cstr(command_name), - command_data.stack_depth, - cli_command_thread, - &thread_data); - furi_thread_start(thread); - furi_thread_join(thread); - furi_thread_free(thread); - } - } while(0); - - furi_string_free(command_name); - furi_string_free(args); - - // unlock loader - if(loader) loader_unlock(loader); - furi_record_close(RECORD_LOADER); - - // unload external command - if(plugin_manager) plugin_manager_free(plugin_manager); -} - -// ==================== -// Line editing helpers -// ==================== - -static size_t cli_shell_prompt_length(CliShell* cli_shell) { - UNUSED(cli_shell); - return strlen(">: "); -} - -static void cli_shell_format_prompt(CliShell* cli_shell, char* buf, size_t length) { - UNUSED(cli_shell); - snprintf(buf, length - 1, ">: "); -} - -static void cli_shell_prompt(CliShell* cli_shell) { - char buffer[128]; - cli_shell_format_prompt(cli_shell, buffer, sizeof(buffer)); - printf("\r\n%s", buffer); - fflush(stdout); -} - -/** - * If a line from history has been selected, moves it into the active line - */ -static void cli_shell_ensure_not_overwriting_history(CliShell* cli_shell) { - if(cli_shell->history_position > 0) { - FuriString* source = cli_shell_history_selected_line(cli_shell); - FuriString* destination = cli_shell_history_editing_line(cli_shell); - furi_string_set(destination, source); - cli_shell->history_position = 0; - } -} - -typedef enum { - CliCharClassWord, - CliCharClassSpace, - CliCharClassOther, -} CliCharClass; - -/** - * @brief Determines the class that a character belongs to - * - * The return value of this function should not be used on its own; it should - * only be used for comparing it with other values returned by this function. - * This function is used internally in `cli_skip_run`. - */ -static CliCharClass cli_char_class(char c) { - if((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_') { - return CliCharClassWord; - } else if(c == ' ') { - return CliCharClassSpace; - } else { - return CliCharClassOther; - } -} - -typedef enum { - CliSkipDirectionLeft, - CliSkipDirectionRight, -} CliSkipDirection; - -/** - * @brief Skips a run of characters of the same class - * - * @param string Input string - * @param original_pos Position to start the search at - * @param direction Direction in which to perform the search - * @returns The position at which the run ends - */ -static size_t cli_skip_run(FuriString* string, size_t original_pos, CliSkipDirection direction) { - if(furi_string_size(string) == 0) return original_pos; - if(direction == CliSkipDirectionLeft && original_pos == 0) return original_pos; - if(direction == CliSkipDirectionRight && original_pos == furi_string_size(string)) - return original_pos; - - int8_t look_offset = (direction == CliSkipDirectionLeft) ? -1 : 0; - int8_t increment = (direction == CliSkipDirectionLeft) ? -1 : 1; - int32_t position = original_pos; - CliCharClass start_class = - cli_char_class(furi_string_get_char(string, position + look_offset)); - - while(true) { - position += increment; - if(position < 0) break; - if(position >= (int32_t)furi_string_size(string)) break; - if(cli_char_class(furi_string_get_char(string, position + look_offset)) != start_class) - break; - } - - return MAX(0, position); -} - -// ============ -// Autocomplete -// ============ - -/** - * @brief Update for the completions menu - */ -typedef enum { - CliShellCompletionsActionOpen, - CliShellCompletionsActionClose, - CliShellCompletionsActionUp, - CliShellCompletionsActionDown, - CliShellCompletionsActionLeft, - CliShellCompletionsActionRight, - CliShellCompletionsActionInput, - CliShellCompletionsActionSelect, -} CliShellCompletionsAction; - -typedef enum { - CliShellCompletionSegmentTypeCommand, - CliShellCompletionSegmentTypeArguments, -} CliShellCompletionSegmentType; - -typedef struct { - CliShellCompletionSegmentType type; - size_t start; - size_t length; -} CliShellCompletionSegment; - -static CliShellCompletionSegment cli_shell_completion_segment(CliShell* cli_shell) { - CliShellCompletionSegment segment; - - FuriString* input = furi_string_alloc_set(cli_shell_history_editing_line(cli_shell)); - furi_string_left(input, cli_shell->line_position); - - // find index of first non-space character - size_t first_non_space = 0; - while(1) { - size_t ret = furi_string_search_char(input, ' ', first_non_space); - if(ret == FURI_STRING_FAILURE) break; - if(ret - first_non_space > 1) break; - first_non_space++; - } - - size_t first_space_in_command = furi_string_search_char(input, ' ', first_non_space); - - if(first_space_in_command == FURI_STRING_FAILURE) { - segment.type = CliShellCompletionSegmentTypeCommand; - segment.start = first_non_space; - segment.length = furi_string_size(input) - first_non_space; - } else { - segment.type = CliShellCompletionSegmentTypeArguments; - segment.start = 0; - segment.length = 0; - // support removed, might reimplement in the future - } - - furi_string_free(input); - return segment; -} - -static void cli_shell_fill_completions(CliShell* cli_shell) { - CommandCompletions_reset(cli_shell->completions); - - CliShellCompletionSegment segment = cli_shell_completion_segment(cli_shell); - FuriString* input = furi_string_alloc_set(cli_shell_history_editing_line(cli_shell)); - furi_string_right(input, segment.start); - furi_string_left(input, segment.length); - - if(segment.type == CliShellCompletionSegmentTypeCommand) { - CliCommandTree_t* commands = cli_get_commands(cli_shell->cli); - for - M_EACH(registered_command, *commands, CliCommandTree_t) { - FuriString* command_name = *registered_command->key_ptr; - if(furi_string_start_with(command_name, input)) { - CommandCompletions_push_back(cli_shell->completions, command_name); - } - } - - } else { - // support removed, might reimplement in the future - } - - furi_string_free(input); -} - -static void cli_shell_completions_render(CliShell* cli_shell, CliShellCompletionsAction action) { - furi_assert(cli_shell); - if(action == CliShellCompletionsActionOpen) furi_check(!cli_shell->is_displaying_completions); - if(action == CliShellCompletionsActionInput) furi_check(cli_shell->is_displaying_completions); - if(action == CliShellCompletionsActionClose) furi_check(cli_shell->is_displaying_completions); - - char prompt[64]; - cli_shell_format_prompt(cli_shell, prompt, sizeof(prompt)); - - if(action == CliShellCompletionsActionOpen || action == CliShellCompletionsActionInput) { - // show or update completions menu (full re-render) - cli_shell_fill_completions(cli_shell); - cli_shell->selected_completion = 0; - - printf("\n\r"); - size_t position = 0; - for - M_EACH(completion, cli_shell->completions, CommandCompletions_t) { - if(position == cli_shell->selected_completion) printf(ANSI_INVERT); - printf("%-" COMPLETION_COLUMN_WIDTH "s", furi_string_get_cstr(*completion)); - if(position == cli_shell->selected_completion) printf(ANSI_RESET); - if((position % COMPLETION_COLUMNS == COMPLETION_COLUMNS - 1) && - position != CommandCompletions_size(cli_shell->completions)) { - printf("\r\n"); - } - position++; - } - - if(!position) { - printf(ANSI_FG_RED "no completions" ANSI_RESET); - } - - size_t total_rows = (position / COMPLETION_COLUMNS) + 1; - printf( - ANSI_ERASE_DISPLAY(ANSI_ERASE_FROM_CURSOR_TO_END) ANSI_CURSOR_UP_BY("%zu") - ANSI_CURSOR_HOR_POS("%zu"), - total_rows, - strlen(prompt) + cli_shell->line_position + 1); - - cli_shell->is_displaying_completions = true; - - } else if(action == CliShellCompletionsActionClose) { - // clear completions menu - printf( - ANSI_CURSOR_HOR_POS("%zu") ANSI_ERASE_DISPLAY(ANSI_ERASE_FROM_CURSOR_TO_END) - ANSI_CURSOR_HOR_POS("%zu"), - strlen(prompt) + furi_string_size(cli_shell_history_selected_line(cli_shell)) + 1, - strlen(prompt) + cli_shell->line_position + 1); - cli_shell->is_displaying_completions = false; - - } else if( - action == CliShellCompletionsActionUp || action == CliShellCompletionsActionDown || - action == CliShellCompletionsActionLeft || action == CliShellCompletionsActionRight) { - if(CommandCompletions_empty_p(cli_shell->completions)) return; - - // move selection - size_t old_selection = cli_shell->selected_completion; - int new_selection_unclamped = cli_shell->selected_completion; - if(action == CliShellCompletionsActionUp) new_selection_unclamped -= COMPLETION_COLUMNS; - if(action == CliShellCompletionsActionDown) new_selection_unclamped += COMPLETION_COLUMNS; - if(action == CliShellCompletionsActionLeft) new_selection_unclamped--; - if(action == CliShellCompletionsActionRight) new_selection_unclamped++; - size_t new_selection = CLAMP_WRAPAROUND( - new_selection_unclamped, (int)CommandCompletions_size(cli_shell->completions) - 1, 0); - cli_shell->selected_completion = new_selection; - - if(new_selection != old_selection) { - // determine selection coordinates relative to top-left of suggestion menu - size_t old_x = (old_selection % COMPLETION_COLUMNS) * COMPLETION_COLUMN_WIDTH_I; - size_t old_y = old_selection / COMPLETION_COLUMNS; - size_t new_x = (new_selection % COMPLETION_COLUMNS) * COMPLETION_COLUMN_WIDTH_I; - size_t new_y = new_selection / COMPLETION_COLUMNS; - printf("\n\r"); - - // print old selection in normal colors - if(old_y) printf(ANSI_CURSOR_DOWN_BY("%zu"), old_y); - printf(ANSI_CURSOR_HOR_POS("%zu"), old_x + 1); - printf( - "%-" COMPLETION_COLUMN_WIDTH "s", - furi_string_get_cstr( - *CommandCompletions_cget(cli_shell->completions, old_selection))); - if(old_y) printf(ANSI_CURSOR_UP_BY("%zu"), old_y); - printf(ANSI_CURSOR_HOR_POS("1")); - - // print new selection in inverted colors - if(new_y) printf(ANSI_CURSOR_DOWN_BY("%zu"), new_y); - printf(ANSI_CURSOR_HOR_POS("%zu"), new_x + 1); - printf( - ANSI_INVERT "%-" COMPLETION_COLUMN_WIDTH "s" ANSI_RESET, - furi_string_get_cstr( - *CommandCompletions_cget(cli_shell->completions, new_selection))); - - // return cursor - printf(ANSI_CURSOR_UP_BY("%zu"), new_y + 1); - printf( - ANSI_CURSOR_HOR_POS("%zu"), - strlen(prompt) + furi_string_size(cli_shell_history_selected_line(cli_shell)) + 1); - } - - } else if(action == CliShellCompletionsActionSelect) { - // insert selection into prompt - CliShellCompletionSegment segment = cli_shell_completion_segment(cli_shell); - FuriString* input = cli_shell_history_selected_line(cli_shell); - FuriString* completion = - *CommandCompletions_cget(cli_shell->completions, cli_shell->selected_completion); - furi_string_replace_at( - input, segment.start, segment.length, furi_string_get_cstr(completion)); - printf( - ANSI_CURSOR_HOR_POS("%zu") "%s" ANSI_ERASE_LINE(ANSI_ERASE_FROM_CURSOR_TO_END), - strlen(prompt) + 1, - furi_string_get_cstr(input)); - - int position_change = (int)furi_string_size(completion) - (int)segment.length; - cli_shell->line_position = MAX(0, (int)cli_shell->line_position + position_change); - - cli_shell_completions_render(cli_shell, CliShellCompletionsActionClose); - - } else { - furi_crash(); - } - - fflush(stdout); -} - -// ============= -// Input handler -// ============= - -static void cli_shell_process_key(CliShell* cli_shell, CliKeyCombo key_combo) { - FURI_LOG_T( - TAG, "mod=%d, key=%d='%c'", key_combo.modifiers, key_combo.key, (char)key_combo.key); - - if(key_combo.modifiers == 0 && key_combo.key == CliKeyETX) { // usually Ctrl+C - // reset input - if(cli_shell->is_displaying_completions) - cli_shell_completions_render(cli_shell, CliShellCompletionsActionClose); - furi_string_reset(cli_shell_history_editing_line(cli_shell)); - cli_shell->line_position = 0; - cli_shell->history_position = 0; - printf("^C"); - cli_shell_prompt(cli_shell); - - } else if(key_combo.modifiers == 0 && key_combo.key == CliKeyFF) { // usually Ctrl+L - // clear screen - FuriString* command = cli_shell_history_selected_line(cli_shell); - char prompt[64]; - cli_shell_format_prompt(cli_shell, prompt, sizeof(prompt)); - printf( - ANSI_ERASE_DISPLAY(ANSI_ERASE_ENTIRE) ANSI_ERASE_SCROLLBACK_BUFFER ANSI_CURSOR_POS( - "1", "1") "%s%s" ANSI_CURSOR_HOR_POS("%zu"), - prompt, - furi_string_get_cstr(command), - strlen(prompt) + cli_shell->line_position + 1 /* 1-based column indexing */); - fflush(stdout); - - } else if(key_combo.modifiers == 0 && key_combo.key == CliKeyCR) { - if(cli_shell->is_displaying_completions) { - // select completion - cli_shell_completions_render(cli_shell, CliShellCompletionsActionSelect); - } else { - // get command and update history - FuriString* command = furi_string_alloc(); - ShellHistory_pop_at(&command, cli_shell->history, cli_shell->history_position); - furi_string_trim(command); - if(cli_shell->history_position > 0) ShellHistory_pop_at(NULL, cli_shell->history, 0); - if(!furi_string_empty(command)) ShellHistory_push_at(cli_shell->history, 0, command); - FuriString* new_command = furi_string_alloc(); - ShellHistory_push_at(cli_shell->history, 0, new_command); - furi_string_free(new_command); - if(ShellHistory_size(cli_shell->history) > HISTORY_DEPTH) { - ShellHistory_pop_back(NULL, cli_shell->history); - } - - // execute command - cli_shell->line_position = 0; - cli_shell->history_position = 0; - printf("\r\n"); - if(!furi_string_empty(command)) cli_shell_execute_command(cli_shell, command); - furi_string_free(command); - cli_shell_prompt(cli_shell); - } - - } else if(key_combo.modifiers == 0 && (key_combo.key == CliKeyUp || key_combo.key == CliKeyDown)) { - if(cli_shell->is_displaying_completions) { - cli_shell_completions_render( - cli_shell, - (key_combo.key == CliKeyUp) ? CliShellCompletionsActionUp : - CliShellCompletionsActionDown); - } else { - // go up and down in history - int increment = (key_combo.key == CliKeyUp) ? 1 : -1; - size_t new_pos = CLAMP( - (int)cli_shell->history_position + increment, - (int)ShellHistory_size(cli_shell->history) - 1, - 0); - - // print prompt with selected command - if(new_pos != cli_shell->history_position) { - cli_shell->history_position = new_pos; - FuriString* command = cli_shell_history_selected_line(cli_shell); - printf( - ANSI_CURSOR_HOR_POS("1") ">: %s" ANSI_ERASE_LINE( - ANSI_ERASE_FROM_CURSOR_TO_END), - furi_string_get_cstr(command)); - fflush(stdout); - cli_shell->line_position = furi_string_size(command); - } - } - - } else if( - key_combo.modifiers == 0 && - (key_combo.key == CliKeyLeft || key_combo.key == CliKeyRight)) { - if(cli_shell->is_displaying_completions) { - cli_shell_completions_render( - cli_shell, - (key_combo.key == CliKeyLeft) ? CliShellCompletionsActionLeft : - CliShellCompletionsActionRight); - - } else { - // go left and right in the current line - FuriString* command = cli_shell_history_selected_line(cli_shell); - int increment = (key_combo.key == CliKeyRight) ? 1 : -1; - size_t new_pos = CLAMP( - (int)cli_shell->line_position + increment, (int)furi_string_size(command), 0); - - // move cursor - if(new_pos != cli_shell->line_position) { - cli_shell->line_position = new_pos; - printf( - "%s", (increment == 1) ? ANSI_CURSOR_RIGHT_BY("1") : ANSI_CURSOR_LEFT_BY("1")); - fflush(stdout); - } - } - - } else if(key_combo.modifiers == 0 && key_combo.key == CliKeyHome) { - // go to the start - if(cli_shell->is_displaying_completions) - cli_shell_completions_render(cli_shell, CliShellCompletionsActionClose); - cli_shell->line_position = 0; - printf(ANSI_CURSOR_HOR_POS("%d"), cli_shell_prompt_length(cli_shell) + 1); - fflush(stdout); - - } else if(key_combo.modifiers == 0 && key_combo.key == CliKeyEnd) { - // go to the end - if(cli_shell->is_displaying_completions) - cli_shell_completions_render(cli_shell, CliShellCompletionsActionClose); - FuriString* line = cli_shell_history_selected_line(cli_shell); - cli_shell->line_position = furi_string_size(line); - printf( - ANSI_CURSOR_HOR_POS("%zu"), - cli_shell_prompt_length(cli_shell) + cli_shell->line_position + 1); - fflush(stdout); - - } else if( - key_combo.modifiers == 0 && - (key_combo.key == CliKeyBackspace || key_combo.key == CliKeyDEL)) { - // erase one character - cli_shell_ensure_not_overwriting_history(cli_shell); - FuriString* line = cli_shell_history_editing_line(cli_shell); - if(cli_shell->line_position == 0) { - putc(CliKeyBell, stdout); - fflush(stdout); - return; - } - cli_shell->line_position--; - furi_string_replace_at(line, cli_shell->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(line) + cli_shell->line_position); - size_t left_by = furi_string_size(line) - cli_shell->line_position; - if(left_by) // apparently LEFT_BY("0") still shifts left by one ._ . - printf(ANSI_CURSOR_LEFT_BY("%zu"), left_by); - fflush(stdout); - - if(cli_shell->is_displaying_completions) - cli_shell_completions_render(cli_shell, CliShellCompletionsActionInput); - - } else if( - key_combo.modifiers == CliModKeyCtrl && - (key_combo.key == CliKeyLeft || key_combo.key == CliKeyRight)) { - // skip run of similar chars to the left or right - if(cli_shell->is_displaying_completions) - cli_shell_completions_render(cli_shell, CliShellCompletionsActionClose); - FuriString* line = cli_shell_history_selected_line(cli_shell); - CliSkipDirection direction = (key_combo.key == CliKeyLeft) ? CliSkipDirectionLeft : - CliSkipDirectionRight; - cli_shell->line_position = cli_skip_run(line, cli_shell->line_position, direction); - printf( - ANSI_CURSOR_HOR_POS("%zu"), - cli_shell_prompt_length(cli_shell) + cli_shell->line_position + 1); - fflush(stdout); - - } else if(key_combo.modifiers == 0 && key_combo.key == CliKeyETB) { - // delete run of similar chars to the left - cli_shell_ensure_not_overwriting_history(cli_shell); - FuriString* line = cli_shell_history_selected_line(cli_shell); - size_t run_start = cli_skip_run(line, cli_shell->line_position, CliSkipDirectionLeft); - furi_string_replace_at(line, run_start, cli_shell->line_position - run_start, ""); - cli_shell->line_position = run_start; - printf( - ANSI_CURSOR_HOR_POS("%zu") "%s" ANSI_ERASE_LINE(ANSI_ERASE_FROM_CURSOR_TO_END) - ANSI_CURSOR_HOR_POS("%zu"), - cli_shell_prompt_length(cli_shell) + cli_shell->line_position + 1, - furi_string_get_cstr(line) + run_start, - cli_shell_prompt_length(cli_shell) + run_start + 1); - fflush(stdout); - - if(cli_shell->is_displaying_completions) - cli_shell_completions_render(cli_shell, CliShellCompletionsActionInput); - - } else if(key_combo.modifiers == 0 && key_combo.key == CliKeyTab) { - // display completions - cli_shell_ensure_not_overwriting_history(cli_shell); - cli_shell_completions_render( - cli_shell, - cli_shell->is_displaying_completions ? CliShellCompletionsActionRight : - CliShellCompletionsActionOpen); - - } else if(key_combo.modifiers == 0 && key_combo.key == CliKeyEsc) { - // close completions menu - if(cli_shell->is_displaying_completions) - cli_shell_completions_render(cli_shell, CliShellCompletionsActionClose); - - } else if(key_combo.modifiers == 0 && key_combo.key >= CliKeySpace && key_combo.key < CliKeyDEL) { - // insert character - cli_shell_ensure_not_overwriting_history(cli_shell); - FuriString* line = cli_shell_history_editing_line(cli_shell); - if(cli_shell->line_position == furi_string_size(line)) { - furi_string_push_back(line, key_combo.key); - printf("%c", key_combo.key); - } else { - const char in_str[2] = {key_combo.key, 0}; - furi_string_replace_at(line, cli_shell->line_position, 0, in_str); - printf(ANSI_INSERT_MODE_ENABLE "%c" ANSI_INSERT_MODE_DISABLE, key_combo.key); - } - fflush(stdout); - cli_shell->line_position++; - - if(cli_shell->is_displaying_completions) - cli_shell_completions_render(cli_shell, CliShellCompletionsActionInput); - } -} - -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); -} - -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->event_loop = furi_event_loop_alloc(); - ShellHistory_init(cli_shell->history); - FuriString* new_command = furi_string_alloc(); - ShellHistory_push_at(cli_shell->history, 0, new_command); - furi_string_free(new_command); - - CommandCompletions_init(cli_shell->completions); - cli_shell->selected_completion = 0; - - cli_shell->ansi_parsing_timer = furi_event_loop_timer_alloc( - cli_shell->event_loop, cli_shell_timer_expired, FuriEventLoopTimerTypeOnce, cli_shell); - - cli_shell->pipe = pipe; - pipe_install_as_stdio(cli_shell->pipe); - pipe_attach_to_event_loop(cli_shell->pipe, cli_shell->event_loop); - pipe_set_callback_context(cli_shell->pipe, cli_shell); - pipe_set_data_arrived_callback(cli_shell->pipe, cli_shell_data_available, 0); - pipe_set_broken_callback(cli_shell->pipe, cli_shell_pipe_broken, 0); - - return cli_shell; -} - -static void cli_shell_free(CliShell* cli_shell) { - CommandCompletions_clear(cli_shell->completions); - furi_event_loop_timer_free(cli_shell->ansi_parsing_timer); - pipe_detach_from_event_loop(cli_shell->pipe); - pipe_free(cli_shell->pipe); - ShellHistory_clear(cli_shell->history); - furi_event_loop_free(cli_shell->event_loop); - 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; - CliShell* cli_shell = cli_shell_alloc(pipe); - - FURI_LOG_D(TAG, "Started"); - cli_enumerate_external_commands(cli_shell->cli); - cli_shell_motd(); - cli_shell_prompt(cli_shell); - furi_event_loop_run(cli_shell->event_loop); - FURI_LOG_D(TAG, "Stopped"); - - cli_shell_free(cli_shell); - return 0; -} - -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/cli_vcp.c b/applications/services/cli/cli_vcp.c index 87ac0cbcc16..346b4d40f08 100644 --- a/applications/services/cli/cli_vcp.c +++ b/applications/services/cli/cli_vcp.c @@ -1,5 +1,5 @@ #include "cli_vcp.h" -#include "cli_shell.h" +#include "shell/cli_shell.h" #include #include #include diff --git a/applications/services/cli/shell/cli_shell.c b/applications/services/cli/shell/cli_shell.c new file mode 100644 index 00000000000..7ad4c5c7784 --- /dev/null +++ b/applications/services/cli/shell/cli_shell.c @@ -0,0 +1,547 @@ +#include "cli_shell.h" +#include "../cli_ansi.h" +#include "../cli_i.h" +#include "../cli_commands.h" +#include "cli_shell_completions.h" +#include "cli_shell_line.h" +#include +#include +#include +#include +#include +#include +#include + +#define TAG "CliShell" + +#define HISTORY_DEPTH 10 +#define ANSI_TIMEOUT_MS 10 + +typedef struct { + Cli* cli; + + FuriEventLoop* event_loop; + PipeSide* pipe; + CliAnsiParser* ansi_parser; + FuriEventLoopTimer* ansi_parsing_timer; + + CliShellLine line; + + CliShellCompletions completions; +} CliShell; + +typedef struct { + CliCommand* command; + PipeSide* pipe; + FuriString* args; +} CliCommandThreadData; + +// =============== +// History helpers +// =============== + +// ========= +// Execution +// ========= + +static int32_t cli_command_thread(void* context) { + CliCommandThreadData* thread_data = context; + if(!(thread_data->command->flags & CliCommandFlagDontAttachStdio)) + pipe_install_as_stdio(thread_data->pipe); + + thread_data->command->execute_callback( + thread_data->pipe, thread_data->args, thread_data->command->context); + + fflush(stdout); + return 0; +} + +static void cli_shell_execute_command(CliShell* cli_shell, FuriString* command) { + // split command into command and args + size_t space = furi_string_search_char(command, ' '); + if(space == FURI_STRING_FAILURE) space = furi_string_size(command); + FuriString* command_name = furi_string_alloc_set(command); + furi_string_left(command_name, space); + FuriString* args = furi_string_alloc_set(command); + furi_string_right(args, space + 1); + + PluginManager* plugin_manager = NULL; + Loader* loader = NULL; + CliCommand command_data; + + do { + // find handler + if(!cli_get_command(cli_shell->cli, command_name, &command_data)) { + printf( + ANSI_FG_RED "could not find command `%s`, try `help`" ANSI_RESET, + furi_string_get_cstr(command_name)); + break; + } + + // load external command + if(command_data.flags & CliCommandFlagExternal) { + plugin_manager = + plugin_manager_alloc(PLUGIN_APP_ID, PLUGIN_API_VERSION, firmware_api_interface); + FuriString* path = furi_string_alloc_printf( + "%s/cli_%s.fal", CLI_COMMANDS_PATH, furi_string_get_cstr(command_name)); + uint32_t plugin_cnt_last = plugin_manager_get_count(plugin_manager); + PluginManagerError error = + plugin_manager_load_single(plugin_manager, furi_string_get_cstr(path)); + furi_string_free(path); + + if(error != PluginManagerErrorNone) { + printf(ANSI_FG_RED "failed to load external command" ANSI_RESET); + break; + } + + const CliCommandDescriptor* plugin = + plugin_manager_get_ep(plugin_manager, plugin_cnt_last); + furi_assert(plugin); + furi_check(furi_string_cmp_str(command_name, plugin->name) == 0); + command_data.execute_callback = plugin->execute_callback; + command_data.flags = plugin->flags | CliCommandFlagExternal; + command_data.stack_depth = plugin->stack_depth; + + // external commands have to run in an external thread + furi_check(!(command_data.flags & CliCommandFlagUseShellThread)); + } + + // lock loader + if(command_data.flags & CliCommandFlagParallelUnsafe) { + 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; + } + } + + if(command_data.flags & CliCommandFlagUseShellThread) { + // run command in this thread + command_data.execute_callback(cli_shell->pipe, args, command_data.context); + } else { + // run command in separate thread + CliCommandThreadData thread_data = { + .command = &command_data, + .pipe = cli_shell->pipe, + .args = args, + }; + FuriThread* thread = furi_thread_alloc_ex( + furi_string_get_cstr(command_name), + command_data.stack_depth, + cli_command_thread, + &thread_data); + furi_thread_start(thread); + furi_thread_join(thread); + furi_thread_free(thread); + } + } while(0); + + furi_string_free(command_name); + furi_string_free(args); + + // unlock loader + if(loader) loader_unlock(loader); + furi_record_close(RECORD_LOADER); + + // unload external command + if(plugin_manager) plugin_manager_free(plugin_manager); +} + +// ============= +// Input handler +// ============= + +static void cli_shell_process_key(CliShell* cli_shell, CliKeyCombo key_combo) { + FURI_LOG_T( + TAG, "mod=%d, key=%d='%c'", key_combo.modifiers, key_combo.key, (char)key_combo.key); + + if(key_combo.modifiers == 0 && key_combo.key == CliKeyETX) { // usually Ctrl+C + // reset input + if(cli_shell->completions.is_displaying) + cli_shell_completions_render( + &cli_shell->completions, + &cli_shell->line, + cli_shell->cli, + CliShellCompletionsActionClose); + furi_string_reset(cli_shell_line_get_editing(&cli_shell->line)); + cli_shell->line.line_position = 0; + cli_shell->line.history_position = 0; + printf("^C"); + cli_shell_line_prompt(&cli_shell->line); + + } else if(key_combo.modifiers == 0 && key_combo.key == CliKeyFF) { // usually Ctrl+L + // clear screen + FuriString* command = cli_shell_line_get_selected(&cli_shell->line); + char prompt[64]; + cli_shell_line_format_prompt(&cli_shell->line, prompt, sizeof(prompt)); + printf( + ANSI_ERASE_DISPLAY(ANSI_ERASE_ENTIRE) ANSI_ERASE_SCROLLBACK_BUFFER ANSI_CURSOR_POS( + "1", "1") "%s%s" ANSI_CURSOR_HOR_POS("%zu"), + prompt, + furi_string_get_cstr(command), + strlen(prompt) + cli_shell->line.line_position + 1 /* 1-based column indexing */); + fflush(stdout); + + } else if(key_combo.modifiers == 0 && key_combo.key == CliKeyCR) { + if(cli_shell->completions.is_displaying) { + // select completion + cli_shell_completions_render( + &cli_shell->completions, + &cli_shell->line, + cli_shell->cli, + CliShellCompletionsActionSelect); + } else { + // get command and update history + FuriString* command = furi_string_alloc(); + ShellHistory_pop_at( + &command, cli_shell->line.history, cli_shell->line.history_position); + furi_string_trim(command); + if(cli_shell->line.history_position > 0) + ShellHistory_pop_at(NULL, cli_shell->line.history, 0); + if(!furi_string_empty(command)) + ShellHistory_push_at(cli_shell->line.history, 0, command); + FuriString* new_command = furi_string_alloc(); + ShellHistory_push_at(cli_shell->line.history, 0, new_command); + furi_string_free(new_command); + if(ShellHistory_size(cli_shell->line.history) > HISTORY_DEPTH) { + ShellHistory_pop_back(NULL, cli_shell->line.history); + } + + // execute command + cli_shell->line.line_position = 0; + cli_shell->line.history_position = 0; + printf("\r\n"); + if(!furi_string_empty(command)) cli_shell_execute_command(cli_shell, command); + furi_string_free(command); + cli_shell_line_prompt(&cli_shell->line); + } + + } else if(key_combo.modifiers == 0 && (key_combo.key == CliKeyUp || key_combo.key == CliKeyDown)) { + if(cli_shell->completions.is_displaying) { + cli_shell_completions_render( + &cli_shell->completions, + &cli_shell->line, + cli_shell->cli, + (key_combo.key == CliKeyUp) ? CliShellCompletionsActionUp : + CliShellCompletionsActionDown); + } else { + // go up and down in history + int increment = (key_combo.key == CliKeyUp) ? 1 : -1; + size_t new_pos = CLAMP( + (int)cli_shell->line.history_position + increment, + (int)ShellHistory_size(cli_shell->line.history) - 1, + 0); + + // print prompt with selected command + if(new_pos != cli_shell->line.history_position) { + cli_shell->line.history_position = new_pos; + FuriString* command = cli_shell_line_get_selected(&cli_shell->line); + printf( + ANSI_CURSOR_HOR_POS("1") ">: %s" ANSI_ERASE_LINE( + ANSI_ERASE_FROM_CURSOR_TO_END), + furi_string_get_cstr(command)); + fflush(stdout); + cli_shell->line.line_position = furi_string_size(command); + } + } + + } else if( + key_combo.modifiers == 0 && + (key_combo.key == CliKeyLeft || key_combo.key == CliKeyRight)) { + if(cli_shell->completions.is_displaying) { + cli_shell_completions_render( + &cli_shell->completions, + &cli_shell->line, + cli_shell->cli, + (key_combo.key == CliKeyLeft) ? CliShellCompletionsActionLeft : + CliShellCompletionsActionRight); + + } else { + // go left and right in the current line + FuriString* command = cli_shell_line_get_selected(&cli_shell->line); + int increment = (key_combo.key == CliKeyRight) ? 1 : -1; + size_t new_pos = CLAMP( + (int)cli_shell->line.line_position + increment, (int)furi_string_size(command), 0); + + // move cursor + if(new_pos != cli_shell->line.line_position) { + cli_shell->line.line_position = new_pos; + printf( + "%s", (increment == 1) ? ANSI_CURSOR_RIGHT_BY("1") : ANSI_CURSOR_LEFT_BY("1")); + fflush(stdout); + } + } + + } else if(key_combo.modifiers == 0 && key_combo.key == CliKeyHome) { + // go to the start + if(cli_shell->completions.is_displaying) + cli_shell_completions_render( + &cli_shell->completions, + &cli_shell->line, + cli_shell->cli, + CliShellCompletionsActionClose); + cli_shell->line.line_position = 0; + printf(ANSI_CURSOR_HOR_POS("%d"), cli_shell_line_prompt_length(&cli_shell->line) + 1); + fflush(stdout); + + } else if(key_combo.modifiers == 0 && key_combo.key == CliKeyEnd) { + // go to the end + if(cli_shell->completions.is_displaying) + cli_shell_completions_render( + &cli_shell->completions, + &cli_shell->line, + cli_shell->cli, + CliShellCompletionsActionClose); + FuriString* line = cli_shell_line_get_selected(&cli_shell->line); + cli_shell->line.line_position = furi_string_size(line); + printf( + ANSI_CURSOR_HOR_POS("%zu"), + cli_shell_line_prompt_length(&cli_shell->line) + cli_shell->line.line_position + 1); + fflush(stdout); + + } else if( + key_combo.modifiers == 0 && + (key_combo.key == CliKeyBackspace || key_combo.key == CliKeyDEL)) { + // erase one character + cli_shell_line_ensure_not_overwriting_history(&cli_shell->line); + FuriString* line = cli_shell_line_get_editing(&cli_shell->line); + if(cli_shell->line.line_position == 0) { + putc(CliKeyBell, stdout); + fflush(stdout); + return; + } + cli_shell->line.line_position--; + furi_string_replace_at(line, cli_shell->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(line) + cli_shell->line.line_position); + size_t left_by = furi_string_size(line) - cli_shell->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); + + if(cli_shell->completions.is_displaying) + cli_shell_completions_render( + &cli_shell->completions, + &cli_shell->line, + cli_shell->cli, + CliShellCompletionsActionInput); + + } else if( + key_combo.modifiers == CliModKeyCtrl && + (key_combo.key == CliKeyLeft || key_combo.key == CliKeyRight)) { + // skip run of similar chars to the left or right + if(cli_shell->completions.is_displaying) + cli_shell_completions_render( + &cli_shell->completions, + &cli_shell->line, + cli_shell->cli, + CliShellCompletionsActionClose); + FuriString* line = cli_shell_line_get_selected(&cli_shell->line); + CliSkipDirection direction = (key_combo.key == CliKeyLeft) ? CliSkipDirectionLeft : + CliSkipDirectionRight; + cli_shell->line.line_position = + cli_shell_line_skip_run(line, cli_shell->line.line_position, direction); + printf( + ANSI_CURSOR_HOR_POS("%zu"), + cli_shell_line_prompt_length(&cli_shell->line) + cli_shell->line.line_position + 1); + fflush(stdout); + + } else if(key_combo.modifiers == 0 && key_combo.key == CliKeyETB) { + // delete run of similar chars to the left + cli_shell_line_ensure_not_overwriting_history(&cli_shell->line); + FuriString* line = cli_shell_line_get_selected(&cli_shell->line); + size_t run_start = + cli_shell_line_skip_run(line, cli_shell->line.line_position, CliSkipDirectionLeft); + furi_string_replace_at(line, run_start, cli_shell->line.line_position - run_start, ""); + cli_shell->line.line_position = run_start; + printf( + ANSI_CURSOR_HOR_POS("%zu") "%s" ANSI_ERASE_LINE(ANSI_ERASE_FROM_CURSOR_TO_END) + ANSI_CURSOR_HOR_POS("%zu"), + cli_shell_line_prompt_length(&cli_shell->line) + cli_shell->line.line_position + 1, + furi_string_get_cstr(line) + run_start, + cli_shell_line_prompt_length(&cli_shell->line) + run_start + 1); + fflush(stdout); + + if(cli_shell->completions.is_displaying) + cli_shell_completions_render( + &cli_shell->completions, + &cli_shell->line, + cli_shell->cli, + CliShellCompletionsActionInput); + + } else if(key_combo.modifiers == 0 && key_combo.key == CliKeyTab) { + // display completions + cli_shell_line_ensure_not_overwriting_history(&cli_shell->line); + cli_shell_completions_render( + &cli_shell->completions, + &cli_shell->line, + cli_shell->cli, + cli_shell->completions.is_displaying ? CliShellCompletionsActionRight : + CliShellCompletionsActionOpen); + + } else if(key_combo.modifiers == 0 && key_combo.key == CliKeyEsc) { + // close completions menu + if(cli_shell->completions.is_displaying) + cli_shell_completions_render( + &cli_shell->completions, + &cli_shell->line, + cli_shell->cli, + CliShellCompletionsActionClose); + + } else if(key_combo.modifiers == 0 && key_combo.key >= CliKeySpace && key_combo.key < CliKeyDEL) { + // insert character + cli_shell_line_ensure_not_overwriting_history(&cli_shell->line); + FuriString* line = cli_shell_line_get_editing(&cli_shell->line); + if(cli_shell->line.line_position == furi_string_size(line)) { + furi_string_push_back(line, key_combo.key); + printf("%c", key_combo.key); + } else { + const char in_str[2] = {key_combo.key, 0}; + furi_string_replace_at(line, cli_shell->line.line_position, 0, in_str); + printf(ANSI_INSERT_MODE_ENABLE "%c" ANSI_INSERT_MODE_DISABLE, key_combo.key); + } + fflush(stdout); + cli_shell->line.line_position++; + + if(cli_shell->completions.is_displaying) + cli_shell_completions_render( + &cli_shell->completions, + &cli_shell->line, + cli_shell->cli, + CliShellCompletionsActionInput); + } +} + +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); +} + +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->event_loop = furi_event_loop_alloc(); + ShellHistory_init(cli_shell->line.history); + FuriString* new_command = furi_string_alloc(); + ShellHistory_push_at(cli_shell->line.history, 0, new_command); + furi_string_free(new_command); + + CommandCompletions_init(cli_shell->completions.variants); + cli_shell->completions.selected = 0; + + cli_shell->ansi_parsing_timer = furi_event_loop_timer_alloc( + cli_shell->event_loop, cli_shell_timer_expired, FuriEventLoopTimerTypeOnce, cli_shell); + + cli_shell->pipe = pipe; + pipe_install_as_stdio(cli_shell->pipe); + pipe_attach_to_event_loop(cli_shell->pipe, cli_shell->event_loop); + pipe_set_callback_context(cli_shell->pipe, cli_shell); + pipe_set_data_arrived_callback(cli_shell->pipe, cli_shell_data_available, 0); + pipe_set_broken_callback(cli_shell->pipe, cli_shell_pipe_broken, 0); + + return cli_shell; +} + +static void cli_shell_free(CliShell* cli_shell) { + CommandCompletions_clear(cli_shell->completions.variants); + furi_event_loop_timer_free(cli_shell->ansi_parsing_timer); + pipe_detach_from_event_loop(cli_shell->pipe); + pipe_free(cli_shell->pipe); + ShellHistory_clear(cli_shell->line.history); + furi_event_loop_free(cli_shell->event_loop); + 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; + CliShell* cli_shell = cli_shell_alloc(pipe); + + FURI_LOG_D(TAG, "Started"); + cli_enumerate_external_commands(cli_shell->cli); + cli_shell_motd(); + cli_shell_line_prompt(&cli_shell->line); + furi_event_loop_run(cli_shell->event_loop); + FURI_LOG_D(TAG, "Stopped"); + + cli_shell_free(cli_shell); + return 0; +} + +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/cli_shell.h b/applications/services/cli/shell/cli_shell.h similarity index 80% rename from applications/services/cli/cli_shell.h rename to applications/services/cli/shell/cli_shell.h index fdcabab8bff..a61097ef0d6 100644 --- a/applications/services/cli/cli_shell.h +++ b/applications/services/cli/shell/cli_shell.h @@ -7,7 +7,7 @@ extern "C" { #endif -#define CLI_SHELL_STACK_SIZE (1 * 1024U) +#define CLI_SHELL_STACK_SIZE (2 * 1024U) FuriThread* cli_shell_start(PipeSide* pipe); diff --git a/applications/services/cli/shell/cli_shell_completions.c b/applications/services/cli/shell/cli_shell_completions.c new file mode 100644 index 00000000000..f320a354b9d --- /dev/null +++ b/applications/services/cli/shell/cli_shell_completions.c @@ -0,0 +1,199 @@ +#include "cli_shell_completions.h" + +CliShellCompletionSegment + cli_shell_completions_segment(CliShellCompletions* completions, CliShellLine* line) { + furi_assert(completions); + furi_assert(line); + CliShellCompletionSegment segment; + + FuriString* input = furi_string_alloc_set(cli_shell_line_get_editing(line)); + furi_string_left(input, line->line_position); + + // find index of first non-space character + size_t first_non_space = 0; + while(1) { + size_t ret = furi_string_search_char(input, ' ', first_non_space); + if(ret == FURI_STRING_FAILURE) break; + if(ret - first_non_space > 1) break; + first_non_space++; + } + + size_t first_space_in_command = furi_string_search_char(input, ' ', first_non_space); + + if(first_space_in_command == FURI_STRING_FAILURE) { + segment.type = CliShellCompletionSegmentTypeCommand; + segment.start = first_non_space; + segment.length = furi_string_size(input) - first_non_space; + } else { + segment.type = CliShellCompletionSegmentTypeArguments; + segment.start = 0; + segment.length = 0; + // support removed, might reimplement in the future + } + + furi_string_free(input); + return segment; +} + +void cli_shell_completions_fill_variants( + CliShellCompletions* completions, + CliShellLine* line, + Cli* cli) { + furi_assert(completions); + furi_assert(line); + CommandCompletions_reset(completions->variants); + + CliShellCompletionSegment segment = cli_shell_completions_segment(completions, line); + FuriString* input = furi_string_alloc_set(cli_shell_line_get_editing(line)); + furi_string_right(input, segment.start); + furi_string_left(input, segment.length); + + if(segment.type == CliShellCompletionSegmentTypeCommand) { + CliCommandTree_t* commands = cli_get_commands(cli); + for + M_EACH(registered_command, *commands, CliCommandTree_t) { + FuriString* command_name = *registered_command->key_ptr; + if(furi_string_start_with(command_name, input)) { + CommandCompletions_push_back(completions->variants, command_name); + } + } + + } else { + // support removed, might reimplement in the future + } + + furi_string_free(input); +} + +void cli_shell_completions_render( + CliShellCompletions* completions, + CliShellLine* line, + Cli* cli, + CliShellCompletionsAction action) { + furi_assert(completions); + furi_assert(line); + furi_assert(cli); + if(action == CliShellCompletionsActionOpen) furi_check(!completions->is_displaying); + if(action == CliShellCompletionsActionInput) furi_check(completions->is_displaying); + if(action == CliShellCompletionsActionClose) furi_check(completions->is_displaying); + + char prompt[64]; + cli_shell_line_format_prompt(line, prompt, sizeof(prompt)); + + if(action == CliShellCompletionsActionOpen || action == CliShellCompletionsActionInput) { + // show or update completions menu (full re-render) + cli_shell_completions_fill_variants(completions, line, cli); + completions->selected = 0; + + printf("\n\r"); + size_t position = 0; + for + M_EACH(completion, completions->variants, CommandCompletions_t) { + if(position == completions->selected) printf(ANSI_INVERT); + printf("%-" COMPLETION_COLUMN_WIDTH "s", furi_string_get_cstr(*completion)); + if(position == completions->selected) printf(ANSI_RESET); + if((position % COMPLETION_COLUMNS == COMPLETION_COLUMNS - 1) && + position != CommandCompletions_size(completions->variants)) { + printf("\r\n"); + } + position++; + } + + if(!position) { + printf(ANSI_FG_RED "no completions" ANSI_RESET); + } + + size_t total_rows = (position / COMPLETION_COLUMNS) + 1; + printf( + ANSI_ERASE_DISPLAY(ANSI_ERASE_FROM_CURSOR_TO_END) ANSI_CURSOR_UP_BY("%zu") + ANSI_CURSOR_HOR_POS("%zu"), + total_rows, + strlen(prompt) + line->line_position + 1); + + completions->is_displaying = true; + + } else if(action == CliShellCompletionsActionClose) { + // clear completions menu + printf( + ANSI_CURSOR_HOR_POS("%zu") ANSI_ERASE_DISPLAY(ANSI_ERASE_FROM_CURSOR_TO_END) + ANSI_CURSOR_HOR_POS("%zu"), + strlen(prompt) + furi_string_size(cli_shell_line_get_selected(line)) + 1, + strlen(prompt) + line->line_position + 1); + completions->is_displaying = false; + + } else if( + action == CliShellCompletionsActionUp || action == CliShellCompletionsActionDown || + action == CliShellCompletionsActionLeft || action == CliShellCompletionsActionRight) { + if(CommandCompletions_empty_p(completions->variants)) return; + + // move selection + size_t old_selection = completions->selected; + int new_selection_unclamped = completions->selected; + if(action == CliShellCompletionsActionUp) new_selection_unclamped -= COMPLETION_COLUMNS; + if(action == CliShellCompletionsActionDown) new_selection_unclamped += COMPLETION_COLUMNS; + if(action == CliShellCompletionsActionLeft) new_selection_unclamped--; + if(action == CliShellCompletionsActionRight) new_selection_unclamped++; + size_t new_selection = CLAMP_WRAPAROUND( + new_selection_unclamped, (int)CommandCompletions_size(completions->variants) - 1, 0); + completions->selected = new_selection; + + if(new_selection != old_selection) { + // determine selection coordinates relative to top-left of suggestion menu + size_t old_x = (old_selection % COMPLETION_COLUMNS) * COMPLETION_COLUMN_WIDTH_I; + size_t old_y = old_selection / COMPLETION_COLUMNS; + size_t new_x = (new_selection % COMPLETION_COLUMNS) * COMPLETION_COLUMN_WIDTH_I; + size_t new_y = new_selection / COMPLETION_COLUMNS; + printf("\n\r"); + + // print old selection in normal colors + if(old_y) printf(ANSI_CURSOR_DOWN_BY("%zu"), old_y); + printf(ANSI_CURSOR_HOR_POS("%zu"), old_x + 1); + printf( + "%-" COMPLETION_COLUMN_WIDTH "s", + furi_string_get_cstr( + *CommandCompletions_cget(completions->variants, old_selection))); + if(old_y) printf(ANSI_CURSOR_UP_BY("%zu"), old_y); + printf(ANSI_CURSOR_HOR_POS("1")); + + // print new selection in inverted colors + if(new_y) printf(ANSI_CURSOR_DOWN_BY("%zu"), new_y); + printf(ANSI_CURSOR_HOR_POS("%zu"), new_x + 1); + printf( + ANSI_INVERT "%-" COMPLETION_COLUMN_WIDTH "s" ANSI_RESET, + furi_string_get_cstr( + *CommandCompletions_cget(completions->variants, new_selection))); + + // return cursor + printf(ANSI_CURSOR_UP_BY("%zu"), new_y + 1); + printf( + ANSI_CURSOR_HOR_POS("%zu"), + strlen(prompt) + furi_string_size(cli_shell_line_get_selected(line)) + 1); + } else { + // there's only one option + cli_shell_completions_render(completions, line, cli, CliShellCompletionsActionSelect); + } + + } else if(action == CliShellCompletionsActionSelect) { + // insert selection into prompt + CliShellCompletionSegment segment = cli_shell_completions_segment(completions, line); + FuriString* input = cli_shell_line_get_selected(line); + FuriString* completion = + *CommandCompletions_cget(completions->variants, completions->selected); + furi_string_replace_at( + input, segment.start, segment.length, furi_string_get_cstr(completion)); + printf( + ANSI_CURSOR_HOR_POS("%zu") "%s" ANSI_ERASE_LINE(ANSI_ERASE_FROM_CURSOR_TO_END), + strlen(prompt) + 1, + furi_string_get_cstr(input)); + + int position_change = (int)furi_string_size(completion) - (int)segment.length; + line->line_position = MAX(0, (int)line->line_position + position_change); + + cli_shell_completions_render(completions, line, cli, CliShellCompletionsActionClose); + + } else { + furi_crash(); + } + + fflush(stdout); +} diff --git a/applications/services/cli/shell/cli_shell_completions.h b/applications/services/cli/shell/cli_shell_completions.h new file mode 100644 index 00000000000..f6bc3c949e6 --- /dev/null +++ b/applications/services/cli/shell/cli_shell_completions.h @@ -0,0 +1,67 @@ +#pragma once + +#include +#include +#include "cli_shell_line.h" +#include "../cli.h" +#include "../cli_i.h" + +#ifdef __cplusplus +extern "C" { +#endif + +ARRAY_DEF(CommandCompletions, FuriString*, FURI_STRING_OPLIST); // -V524 +#define M_OPL_CommandCompletions_t() ARRAY_OPLIST(CommandCompletions) + +#define COMPLETION_COLUMNS 3 +#define COMPLETION_COLUMN_WIDTH "30" +#define COMPLETION_COLUMN_WIDTH_I 30 + +typedef struct { + CommandCompletions_t variants; + size_t selected; + bool is_displaying; +} CliShellCompletions; + +/** + * @brief Update for the completions menu + */ +typedef enum { + CliShellCompletionsActionOpen, + CliShellCompletionsActionClose, + CliShellCompletionsActionUp, + CliShellCompletionsActionDown, + CliShellCompletionsActionLeft, + CliShellCompletionsActionRight, + CliShellCompletionsActionInput, + CliShellCompletionsActionSelect, +} CliShellCompletionsAction; + +typedef enum { + CliShellCompletionSegmentTypeCommand, + CliShellCompletionSegmentTypeArguments, +} CliShellCompletionSegmentType; + +typedef struct { + CliShellCompletionSegmentType type; + size_t start; + size_t length; +} CliShellCompletionSegment; + +CliShellCompletionSegment + cli_shell_completions_segment(CliShellCompletions* completions, CliShellLine* line); + +void cli_shell_completions_fill_variants( + CliShellCompletions* completions, + CliShellLine* line, + Cli* cli); + +void cli_shell_completions_render( + CliShellCompletions* completions, + CliShellLine* line, + Cli* cli, + CliShellCompletionsAction action); + +#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 00000000000..10131a9557f --- /dev/null +++ b/applications/services/cli/shell/cli_shell_line.c @@ -0,0 +1,70 @@ +#include "cli_shell_line.h" + +FuriString* cli_shell_line_get_selected(CliShellLine* line) { + return *ShellHistory_cget(line->history, line->history_position); +} + +FuriString* cli_shell_line_get_editing(CliShellLine* line) { + return *ShellHistory_front(line->history); +} + +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[128]; + 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; + } +} + +CliCharClass cli_shell_line_char_class(char c) { + if((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_') { + return CliCharClassWord; + } else if(c == ' ') { + return CliCharClassSpace; + } else { + return CliCharClassOther; + } +} + +size_t + cli_shell_line_skip_run(FuriString* string, size_t original_pos, CliSkipDirection direction) { + if(furi_string_size(string) == 0) return original_pos; + if(direction == CliSkipDirectionLeft && original_pos == 0) return original_pos; + if(direction == CliSkipDirectionRight && original_pos == furi_string_size(string)) + return original_pos; + + int8_t look_offset = (direction == CliSkipDirectionLeft) ? -1 : 0; + int8_t increment = (direction == CliSkipDirectionLeft) ? -1 : 1; + int32_t position = original_pos; + CliCharClass start_class = + cli_shell_line_char_class(furi_string_get_char(string, position + look_offset)); + + while(true) { + position += increment; + if(position < 0) break; + if(position >= (int32_t)furi_string_size(string)) break; + if(cli_shell_line_char_class(furi_string_get_char(string, position + look_offset)) != + start_class) + break; + } + + return MAX(0, position); +} 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 00000000000..eb8d0356c80 --- /dev/null +++ b/applications/services/cli/shell/cli_shell_line.h @@ -0,0 +1,67 @@ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +ARRAY_DEF(ShellHistory, FuriString*, FURI_STRING_OPLIST); // -V524 +#define M_OPL_ShellHistory_t() ARRAY_OPLIST(ShellHistory) + +typedef struct { + size_t history_position; + size_t line_position; + ShellHistory_t history; +} CliShellLine; + +typedef enum { + CliCharClassWord, + CliCharClassSpace, + CliCharClassOther, +} CliCharClass; + +typedef enum { + CliSkipDirectionLeft, + CliSkipDirectionRight, +} CliSkipDirection; + +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); + +/** + * @brief Determines the class that a character belongs to + * + * The return value of this function should not be used on its own; it should + * only be used for comparing it with other values returned by this function. + * This function is used internally in `cli_skip_run`. + */ +CliCharClass cli_shell_line_char_class(char c); + +/** + * @brief Skips a run of characters of the same class + * + * @param string Input string + * @param original_pos Position to start the search at + * @param direction Direction in which to perform the search + * @returns The position at which the run ends + */ +size_t + cli_shell_line_skip_run(FuriString* string, size_t original_pos, CliSkipDirection direction); + +#ifdef __cplusplus +} +#endif From e4a82f3a13bccf1313e375e29acd03985d76066d Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Tue, 11 Feb 2025 02:33:31 +0400 Subject: [PATCH 44/47] fix pvs warning --- applications/services/cli/shell/cli_shell.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/services/cli/shell/cli_shell.c b/applications/services/cli/shell/cli_shell.c index 7ad4c5c7784..4f67ae742d3 100644 --- a/applications/services/cli/shell/cli_shell.c +++ b/applications/services/cli/shell/cli_shell.c @@ -283,7 +283,7 @@ static void cli_shell_process_key(CliShell* cli_shell, CliKeyCombo key_combo) { cli_shell->cli, CliShellCompletionsActionClose); cli_shell->line.line_position = 0; - printf(ANSI_CURSOR_HOR_POS("%d"), cli_shell_line_prompt_length(&cli_shell->line) + 1); + printf(ANSI_CURSOR_HOR_POS("%zu"), cli_shell_line_prompt_length(&cli_shell->line) + 1); fflush(stdout); } else if(key_combo.modifiers == 0 && key_combo.key == CliKeyEnd) { From 687bbe7119776726600a88fd9f976ca4c7981255 Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Thu, 13 Feb 2025 02:09:58 +0400 Subject: [PATCH 45/47] fix qflipper refusing to connect --- applications/services/cli/shell/cli_shell.c | 12 +++++++++++- lib/toolbox/pipe.c | 14 +++++++++++++- lib/toolbox/pipe.h | 10 ++++++++++ targets/f18/api_symbols.csv | 1 + targets/f7/api_symbols.csv | 1 + 5 files changed, 36 insertions(+), 2 deletions(-) diff --git a/applications/services/cli/shell/cli_shell.c b/applications/services/cli/shell/cli_shell.c index 4f67ae742d3..e4e1478a18d 100644 --- a/applications/services/cli/shell/cli_shell.c +++ b/applications/services/cli/shell/cli_shell.c @@ -474,6 +474,7 @@ static CliShell* cli_shell_alloc(PipeSide* pipe) { 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); + pipe_set_stdout_timeout(cli_shell->pipe, furi_ms_to_ticks(50)); return cli_shell; } @@ -526,13 +527,22 @@ static void cli_shell_motd(void) { 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_enumerate_external_commands(cli_shell->cli); cli_shell_motd(); cli_shell_line_prompt(&cli_shell->line); - furi_event_loop_run(cli_shell->event_loop); + + // FIXME: we shouldn't have to do this check. Talk with @gsurkov about `Out` event race conditions + if(pipe_state(pipe) == PipeStateOpen) furi_event_loop_run(cli_shell->event_loop); + FURI_LOG_D(TAG, "Stopped"); cli_shell_free(cli_shell); diff --git a/lib/toolbox/pipe.c b/lib/toolbox/pipe.c index 7112b5a4538..87278bc3025 100644 --- a/lib/toolbox/pipe.c +++ b/lib/toolbox/pipe.c @@ -23,6 +23,7 @@ struct PipeSide { PipeSideDataArrivedCallback on_data_arrived; PipeSideSpaceFreedCallback on_space_freed; PipeSideBrokenCallback on_pipe_broken; + FuriWait stdout_timeout; }; PipeSideBundle pipe_alloc(size_t capacity, size_t trigger_level) { @@ -52,12 +53,14 @@ PipeSideBundle pipe_alloc_ex(PipeSideReceiveSettings alice, PipeSideReceiveSetti .shared = shared, .sending = alice_to_bob, .receiving = bob_to_alice, + .stdout_timeout = FuriWaitForever, }; *bobs_side = (PipeSide){ .role = PipeRoleBob, .shared = shared, .sending = bob_to_alice, .receiving = alice_to_bob, + .stdout_timeout = FuriWaitForever, }; return (PipeSideBundle){.alices_side = alices_side, .bobs_side = bobs_side}; @@ -71,6 +74,7 @@ PipeRole pipe_role(PipeSide* pipe) { PipeState pipe_state(PipeSide* pipe) { furi_check(pipe); uint32_t count = furi_semaphore_get_count(pipe->shared->instance_count); + FURI_LOG_I("pipe", "cnt=%ld", count); return (count == 1) ? PipeStateOpen : PipeStateBroken; } @@ -82,6 +86,8 @@ void pipe_free(PipeSide* pipe) { FuriStatus status = furi_semaphore_acquire(pipe->shared->instance_count, 0); if(status == FuriStatusOk) { + FURI_LOG_I( + "pipe", "i am gone, cnt=%ld", furi_semaphore_get_count(pipe->shared->instance_count)); // the other side is still intact furi_mutex_release(pipe->shared->state_transition); free(pipe); @@ -100,7 +106,8 @@ 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, FuriWaitForever); + size_t sent = pipe_send(pipe, data, size, pipe->stdout_timeout); + if(!sent) break; data += sent; size -= sent; } @@ -118,6 +125,11 @@ void pipe_install_as_stdio(PipeSide* pipe) { furi_thread_set_stdin_callback(_pipe_stdin_cb, pipe); } +void pipe_set_stdout_timeout(PipeSide* pipe, FuriWait timeout) { + furi_check(pipe); + pipe->stdout_timeout = timeout; +} + size_t pipe_receive(PipeSide* pipe, void* data, size_t length, FuriWait timeout) { furi_check(pipe); return furi_stream_buffer_receive(pipe->receiving, data, length, timeout); diff --git a/lib/toolbox/pipe.h b/lib/toolbox/pipe.h index df75f4c48de..c05e60d0c52 100644 --- a/lib/toolbox/pipe.h +++ b/lib/toolbox/pipe.h @@ -147,6 +147,16 @@ void pipe_free(PipeSide* pipe); */ void pipe_install_as_stdio(PipeSide* pipe); +/** + * @brief Sets the timeout for `stdout` write operations + * + * @note This value is set to `FuriWaitForever` when the pipe is created + * + * @param [in] pipe Pipe side to set the timeout of + * @param [in] timeout Timeout value in ticks + */ +void pipe_set_stdout_timeout(PipeSide* pipe, FuriWait timeout); + /** * @brief Receives data from the pipe. * diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 01cf7d560cc..9a0c971033b 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -2331,6 +2331,7 @@ Function,+,pipe_set_broken_callback,void,"PipeSide*, PipeSideBrokenCallback, Fur 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_spaces_available,size_t,PipeSide* Function,+,pipe_state,PipeState,PipeSide* Function,+,plugin_manager_alloc,PluginManager*,"const char*, uint32_t, const ElfApiInterface*" diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index e0e9652636a..631943f20f4 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -2968,6 +2968,7 @@ Function,+,pipe_set_broken_callback,void,"PipeSide*, PipeSideBrokenCallback, Fur 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_spaces_available,size_t,PipeSide* Function,+,pipe_state,PipeState,PipeSide* Function,+,plugin_manager_alloc,PluginManager*,"const char*, uint32_t, const ElfApiInterface*" From c150a42b184b5ba5431d43a204910a64fd632808 Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Thu, 13 Feb 2025 02:39:17 +0400 Subject: [PATCH 46/47] remove temp debug logs --- lib/toolbox/pipe.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/toolbox/pipe.c b/lib/toolbox/pipe.c index 87278bc3025..845372fd5c5 100644 --- a/lib/toolbox/pipe.c +++ b/lib/toolbox/pipe.c @@ -74,7 +74,6 @@ PipeRole pipe_role(PipeSide* pipe) { PipeState pipe_state(PipeSide* pipe) { furi_check(pipe); uint32_t count = furi_semaphore_get_count(pipe->shared->instance_count); - FURI_LOG_I("pipe", "cnt=%ld", count); return (count == 1) ? PipeStateOpen : PipeStateBroken; } @@ -86,8 +85,6 @@ void pipe_free(PipeSide* pipe) { FuriStatus status = furi_semaphore_acquire(pipe->shared->instance_count, 0); if(status == FuriStatusOk) { - FURI_LOG_I( - "pipe", "i am gone, cnt=%ld", furi_semaphore_get_count(pipe->shared->instance_count)); // the other side is still intact furi_mutex_release(pipe->shared->state_transition); free(pipe); From 18b55cf4d548e03a52d5f5707f3dd2f2ff809385 Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Thu, 13 Feb 2025 19:42:37 +0400 Subject: [PATCH 47/47] remove erroneous conclusion --- applications/services/cli/shell/cli_shell.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/applications/services/cli/shell/cli_shell.c b/applications/services/cli/shell/cli_shell.c index e4e1478a18d..8aa2c741a0f 100644 --- a/applications/services/cli/shell/cli_shell.c +++ b/applications/services/cli/shell/cli_shell.c @@ -540,8 +540,7 @@ static int32_t cli_shell_thread(void* context) { cli_shell_motd(); cli_shell_line_prompt(&cli_shell->line); - // FIXME: we shouldn't have to do this check. Talk with @gsurkov about `Out` event race conditions - if(pipe_state(pipe) == PipeStateOpen) furi_event_loop_run(cli_shell->event_loop); + furi_event_loop_run(cli_shell->event_loop); FURI_LOG_D(TAG, "Stopped");