Просмотр исходного кода

[FL-2163] CLI: Separate session from CLI service (#1130)

* CLI: session refactoring
* Added forgotten define
* Desktop lock state save
* Dolphin: use proper type for value returned by dolphin_state_xp_to_levelup

Co-authored-by: Aleksandr Kutuzov <alleteam@gmail.com>
Nikolay Minaylov 3 лет назад
Родитель
Сommit
0eac917f91

+ 116 - 51
applications/cli/cli.c

@@ -1,9 +1,11 @@
 #include "cli_i.h"
 #include "cli_commands.h"
-
+#include "cli_vcp.h"
 #include <furi_hal_version.h>
 #include <loader/loader.h>
 
+#define TAG "CliSrv"
+
 Cli* cli_alloc() {
     Cli* cli = malloc(sizeof(Cli));
 
@@ -12,55 +14,78 @@ Cli* cli_alloc() {
     string_init(cli->last_line);
     string_init(cli->line);
 
+    cli->session = NULL;
+
     cli->mutex = osMutexNew(NULL);
     furi_check(cli->mutex);
 
+    cli->idle_sem = osSemaphoreNew(1, 0, NULL);
+
     return cli;
 }
 
-void cli_free(Cli* cli) {
+void cli_putc(Cli* cli, char c) {
     furi_assert(cli);
-
-    string_clear(cli->last_line);
-    string_clear(cli->line);
-
-    CliCommandTree_clear(cli->commands);
-
-    free(cli);
-}
-
-void cli_putc(char c) {
-    furi_hal_vcp_tx((uint8_t*)&c, 1);
+    if(cli->session != NULL) {
+        cli->session->tx((uint8_t*)&c, 1);
+    }
 }
 
 char cli_getc(Cli* cli) {
     furi_assert(cli);
-    char c;
-    if(furi_hal_vcp_rx((uint8_t*)&c, 1) == 0) {
+    char c = 0;
+    if(cli->session != NULL) {
+        if(cli->session->rx((uint8_t*)&c, 1, osWaitForever) == 0) {
+            cli_reset(cli);
+        }
+    } else {
         cli_reset(cli);
     }
     return c;
 }
 
-void cli_stdout_callback(void* _cookie, const char* data, size_t size) {
-    furi_hal_vcp_tx((const uint8_t*)data, size);
-}
-
 void cli_write(Cli* cli, const uint8_t* buffer, size_t size) {
-    return furi_hal_vcp_tx(buffer, size);
+    furi_assert(cli);
+    if(cli->session != NULL) {
+        cli->session->tx(buffer, size);
+    }
 }
 
 size_t cli_read(Cli* cli, uint8_t* buffer, size_t size) {
-    return furi_hal_vcp_rx(buffer, size);
+    furi_assert(cli);
+    if(cli->session != NULL) {
+        return cli->session->rx(buffer, size, osWaitForever);
+    } else {
+        return 0;
+    }
+}
+
+size_t cli_read_timeout(Cli* cli, uint8_t* buffer, size_t size, uint32_t timeout) {
+    furi_assert(cli);
+    if(cli->session != NULL) {
+        return cli->session->rx(buffer, size, timeout);
+    } else {
+        return 0;
+    }
 }
 
 bool cli_cmd_interrupt_received(Cli* cli) {
+    furi_assert(cli);
     char c = '\0';
-    if(furi_hal_vcp_rx_with_timeout((uint8_t*)&c, 1, 0) == 1) {
-        return c == CliSymbolAsciiETX;
-    } else {
-        return false;
+    if(cli->session != NULL) {
+        if(cli->session->rx((uint8_t*)&c, 1, 0) == 1) {
+            return c == CliSymbolAsciiETX;
+        }
     }
+    return false;
+}
+
+bool cli_is_connected(Cli* cli) {
+    furi_assert(cli);
+    if(cli->session != NULL) {
+        return (cli->session->is_connected());
+    }
+    return false;
 }
 
 void cli_print_usage(const char* cmd, const char* usage, const char* arg) {
@@ -139,7 +164,7 @@ static void cli_handle_backspace(Cli* cli) {
 
         cli->cursor_position--;
     } else {
-        cli_putc(CliSymbolAsciiBell);
+        cli_putc(cli, CliSymbolAsciiBell);
     }
 }
 
@@ -210,7 +235,7 @@ static void cli_handle_enter(Cli* cli) {
         printf(
             "`%s` command not found, use `help` or `?` to list all available commands",
             string_get_cstr(command));
-        cli_putc(CliSymbolAsciiBell);
+        cli_putc(cli, CliSymbolAsciiBell);
     }
     furi_check(osMutexRelease(cli->mutex) == osOK);
 
@@ -301,43 +326,43 @@ static void cli_handle_escape(Cli* cli, char c) {
 }
 
 void cli_process_input(Cli* cli) {
-    char c = cli_getc(cli);
-    size_t r;
+    char in_chr = cli_getc(cli);
+    size_t rx_len;
 
-    if(c == CliSymbolAsciiTab) {
+    if(in_chr == CliSymbolAsciiTab) {
         cli_handle_autocomplete(cli);
-    } else if(c == CliSymbolAsciiSOH) {
+    } else if(in_chr == CliSymbolAsciiSOH) {
         osDelay(33); // We are too fast, Minicom is not ready yet
         cli_motd();
         cli_prompt(cli);
-    } else if(c == CliSymbolAsciiETX) {
+    } else if(in_chr == CliSymbolAsciiETX) {
         cli_reset(cli);
         cli_prompt(cli);
-    } else if(c == CliSymbolAsciiEOT) {
+    } else if(in_chr == CliSymbolAsciiEOT) {
         cli_reset(cli);
-    } else if(c == CliSymbolAsciiEsc) {
-        r = furi_hal_vcp_rx((uint8_t*)&c, 1);
-        if(r && c == '[') {
-            furi_hal_vcp_rx((uint8_t*)&c, 1);
-            cli_handle_escape(cli, c);
+    } 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(CliSymbolAsciiBell);
+            cli_putc(cli, CliSymbolAsciiBell);
         }
-    } else if(c == CliSymbolAsciiBackspace || c == CliSymbolAsciiDel) {
+    } else if(in_chr == CliSymbolAsciiBackspace || in_chr == CliSymbolAsciiDel) {
         cli_handle_backspace(cli);
-    } else if(c == CliSymbolAsciiCR) {
+    } else if(in_chr == CliSymbolAsciiCR) {
         cli_handle_enter(cli);
-    } else if(c >= 0x20 && c < 0x7F) {
+    } else if(in_chr >= 0x20 && in_chr < 0x7F) {
         if(cli->cursor_position == string_size(cli->line)) {
-            string_push_back(cli->line, c);
-            cli_putc(c);
+            string_push_back(cli->line, in_chr);
+            cli_putc(cli, in_chr);
         } else {
             // ToDo: better way?
             string_t temp;
             string_init(temp);
             string_reserve(temp, string_size(cli->line) + 1);
             string_set_strn(temp, string_get_cstr(cli->line), cli->cursor_position);
-            string_push_back(temp, c);
+            string_push_back(temp, in_chr);
             string_cat_str(temp, string_get_cstr(cli->line) + cli->cursor_position);
 
             // cli->line is cleared and temp's buffer moved to cli->line
@@ -345,12 +370,12 @@ void cli_process_input(Cli* cli) {
             // NO MEMORY LEAK, STOP REPORTING IT
 
             // Print character in replace mode
-            printf("\e[4h%c\e[4l", c);
+            printf("\e[4h%c\e[4l", in_chr);
             fflush(stdout);
         }
         cli->cursor_position++;
     } else {
-        cli_putc(CliSymbolAsciiBell);
+        cli_putc(cli, CliSymbolAsciiBell);
     }
 }
 
@@ -398,19 +423,59 @@ void cli_delete_command(Cli* cli, const char* name) {
     string_clear(name_str);
 }
 
+void cli_session_open(Cli* cli, void* session) {
+    furi_assert(cli);
+
+    furi_check(osMutexAcquire(cli->mutex, osWaitForever) == osOK);
+    cli->session = session;
+    if(cli->session != NULL) {
+        cli->session->init();
+        furi_stdglue_set_thread_stdout_callback(cli->session->tx_stdout);
+    } else {
+        furi_stdglue_set_thread_stdout_callback(NULL);
+    }
+    osSemaphoreRelease(cli->idle_sem);
+    furi_check(osMutexRelease(cli->mutex) == osOK);
+}
+
+void cli_session_close(Cli* cli) {
+    furi_assert(cli);
+
+    furi_check(osMutexAcquire(cli->mutex, osWaitForever) == osOK);
+    if(cli->session != NULL) {
+        cli->session->deinit();
+    }
+    cli->session = NULL;
+    furi_stdglue_set_thread_stdout_callback(NULL);
+    furi_check(osMutexRelease(cli->mutex) == osOK);
+}
+
 int32_t cli_srv(void* p) {
     Cli* cli = cli_alloc();
 
-    furi_hal_vcp_init();
-
     // Init basic cli commands
     cli_commands_init(cli);
 
     furi_record_create("cli", cli);
 
-    furi_stdglue_set_thread_stdout_callback(cli_stdout_callback);
+    if(cli->session != NULL) {
+        furi_stdglue_set_thread_stdout_callback(cli->session->tx_stdout);
+    } else {
+        furi_stdglue_set_thread_stdout_callback(NULL);
+    }
+
+    if(furi_hal_rtc_get_boot_mode() == FuriHalRtcBootModeNormal) {
+        cli_session_open(cli, &cli_vcp);
+    } else {
+        FURI_LOG_W(TAG, "Skipped CLI session open: device in special startup mode");
+    }
+
     while(1) {
-        cli_process_input(cli);
+        if(cli->session != NULL) {
+            cli_process_input(cli);
+        } else {
+            furi_check(osSemaphoreAcquire(cli->idle_sem, osWaitForever) == osOK);
+        }
     }
 
     return 0;

+ 20 - 3
applications/cli/cli.h

@@ -73,17 +73,28 @@ void cli_print_usage(const char* cmd, const char* usage, const char* arg);
  */
 void cli_delete_command(Cli* cli, const char* name);
 
-/** Read from terminal Do it only from inside of cli call.
+/** Read from terminal
  *
  * @param      cli     Cli instance
  * @param      buffer  pointer to buffer
  * @param      size    size of buffer in bytes
  *
- * @return     bytes written
+ * @return     bytes read
  */
 size_t cli_read(Cli* cli, uint8_t* buffer, size_t size);
 
-/** Not blocking check for interrupt command received
+/** 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
  *
@@ -111,6 +122,12 @@ char cli_getc(Cli* cli);
  */
 void cli_nl();
 
+void cli_session_open(Cli* cli, void* session);
+
+void cli_session_close(Cli* cli);
+
+bool cli_is_connected(Cli* cli);
+
 #ifdef __cplusplus
 }
 #endif

+ 14 - 3
applications/cli/cli_i.h

@@ -18,6 +18,17 @@ typedef struct {
     uint32_t flags;
 } CliCommand;
 
+typedef struct CliSession CliSession;
+
+struct CliSession {
+    void (*init)(void);
+    void (*deinit)(void);
+    size_t (*rx)(uint8_t* buffer, size_t size, uint32_t timeout);
+    void (*tx)(const uint8_t* buffer, size_t size);
+    void (*tx_stdout)(void* _cookie, const char* data, size_t size);
+    bool (*is_connected)(void);
+};
+
 BPTREE_DEF2(
     CliCommandTree,
     CLI_COMMANDS_TREE_RANK,
@@ -31,18 +42,18 @@ BPTREE_DEF2(
 struct Cli {
     CliCommandTree_t commands;
     osMutexId_t mutex;
+    osSemaphoreId_t idle_sem;
     string_t last_line;
     string_t line;
+    CliSession* session;
 
     size_t cursor_position;
 };
 
 Cli* cli_alloc();
 
-void cli_free(Cli* cli);
-
 void cli_reset(Cli* cli);
 
-void cli_putc(char c);
+void cli_putc(Cli* cli, char c);
 
 void cli_stdout_callback(void* _cookie, const char* data, size_t size);

+ 99 - 89
firmware/targets/f7/furi_hal/furi_hal_vcp.c → applications/cli/cli_vcp.c

@@ -2,8 +2,9 @@
 #include <furi_hal.h>
 #include <furi.h>
 #include <stream_buffer.h>
+#include "cli_i.h"
 
-#define TAG "FuriHalVcp"
+#define TAG "CliVcp"
 
 #define USB_CDC_PKT_LEN CDC_DATA_SZ
 #define VCP_RX_BUF_SIZE (USB_CDC_PKT_LEN * 3)
@@ -12,19 +13,18 @@
 #define VCP_IF_NUM 0
 
 typedef enum {
-    VcpEvtEnable = (1 << 0),
-    VcpEvtDisable = (1 << 1),
-    VcpEvtConnect = (1 << 2),
-    VcpEvtDisconnect = (1 << 3),
-    VcpEvtStreamRx = (1 << 4),
-    VcpEvtRx = (1 << 5),
-    VcpEvtStreamTx = (1 << 6),
-    VcpEvtTx = (1 << 7),
+    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                                                                  \
-    (VcpEvtEnable | VcpEvtDisable | VcpEvtConnect | VcpEvtDisconnect | VcpEvtRx | VcpEvtTx | \
-     VcpEvtStreamRx | VcpEvtStreamTx)
+#define VCP_THREAD_FLAG_ALL                                                                 \
+    (VcpEvtStop | VcpEvtConnect | VcpEvtDisconnect | VcpEvtRx | VcpEvtTx | VcpEvtStreamRx | \
+     VcpEvtStreamTx)
 
 typedef struct {
     FuriThread* thread;
@@ -33,9 +33,12 @@ typedef struct {
     StreamBufferHandle_t rx_stream;
 
     volatile bool connected;
+    volatile bool running;
+
+    FuriHalUsbInterface* usb_if_prev;
 
     uint8_t data_buffer[USB_CDC_PKT_LEN];
-} FuriHalVcp;
+} CliVcp;
 
 static int32_t vcp_worker(void* context);
 static void vcp_on_cdc_tx_complete(void* context);
@@ -51,25 +54,23 @@ static CdcCallbacks cdc_cb = {
     NULL,
 };
 
-static FuriHalVcp* vcp = NULL;
+static CliVcp* vcp = NULL;
 
 static const uint8_t ascii_soh = 0x01;
 static const uint8_t ascii_eot = 0x04;
 
-void furi_hal_vcp_init() {
-    vcp = malloc(sizeof(FuriHalVcp));
-    vcp->connected = false;
-
-    vcp->tx_stream = xStreamBufferCreate(VCP_TX_BUF_SIZE, 1);
-    vcp->rx_stream = xStreamBufferCreate(VCP_RX_BUF_SIZE, 1);
-
-    if(furi_hal_rtc_get_boot_mode() != FuriHalRtcBootModeNormal) {
-        FURI_LOG_W(TAG, "Skipped worker init: device in special startup mode");
-        return;
+static void cli_vcp_init() {
+    if(vcp == NULL) {
+        vcp = malloc(sizeof(CliVcp));
+        vcp->tx_stream = xStreamBufferCreate(VCP_TX_BUF_SIZE, 1);
+        vcp->rx_stream = xStreamBufferCreate(VCP_RX_BUF_SIZE, 1);
     }
+    furi_assert(vcp->thread == NULL);
+
+    vcp->connected = false;
 
     vcp->thread = furi_thread_alloc();
-    furi_thread_set_name(vcp->thread, "VcpDriver");
+    furi_thread_set_name(vcp->thread, "CliVcpWorker");
     furi_thread_set_stack_size(vcp->thread, 1024);
     furi_thread_set_callback(vcp->thread, vcp_worker);
     furi_thread_start(vcp->thread);
@@ -77,48 +78,35 @@ void furi_hal_vcp_init() {
     FURI_LOG_I(TAG, "Init OK");
 }
 
+static void cli_vcp_deinit() {
+    osThreadFlagsSet(furi_thread_get_thread_id(vcp->thread), VcpEvtStop);
+    furi_thread_join(vcp->thread);
+    furi_thread_free(vcp->thread);
+    vcp->thread = NULL;
+}
+
 static int32_t vcp_worker(void* context) {
-    bool enabled = true;
-    bool tx_idle = false;
+    bool tx_idle = true;
     size_t missed_rx = 0;
     uint8_t last_tx_pkt_len = 0;
 
-    furi_hal_usb_set_config(&usb_cdc_single, NULL);
+    // 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 = osThreadFlagsWait(VCP_THREAD_FLAG_ALL, osFlagsWaitAny, osWaitForever);
         furi_assert((flags & osFlagsError) == 0);
 
-        // VCP enabled
-        if((flags & VcpEvtEnable) && !enabled) {
-#ifdef FURI_HAL_USB_VCP_DEBUG
-            FURI_LOG_D(TAG, "Enable");
-#endif
-            flags |= VcpEvtTx;
-            furi_hal_cdc_set_callbacks(VCP_IF_NUM, &cdc_cb, NULL);
-            enabled = true;
-            furi_hal_cdc_receive(VCP_IF_NUM, vcp->data_buffer, USB_CDC_PKT_LEN); // flush Rx buffer
-            if(furi_hal_cdc_get_ctrl_line_state(VCP_IF_NUM) & (1 << 0)) {
-                vcp->connected = true;
-                xStreamBufferSend(vcp->rx_stream, &ascii_soh, 1, osWaitForever);
-            }
-        }
-
-        // VCP disabled
-        if((flags & VcpEvtDisable) && enabled) {
-#ifdef FURI_HAL_USB_VCP_DEBUG
-            FURI_LOG_D(TAG, "Disable");
-#endif
-            enabled = false;
-            vcp->connected = false;
-            xStreamBufferReceive(vcp->tx_stream, vcp->data_buffer, USB_CDC_PKT_LEN, 0);
-            xStreamBufferSend(vcp->rx_stream, &ascii_eot, 1, osWaitForever);
-        }
-
         // VCP session opened
-        if((flags & VcpEvtConnect) && enabled) {
-#ifdef FURI_HAL_USB_VCP_DEBUG
+        if(flags & VcpEvtConnect) {
+#ifdef CLI_VCP_DEBUG
             FURI_LOG_D(TAG, "Connect");
 #endif
             if(vcp->connected == false) {
@@ -128,8 +116,8 @@ static int32_t vcp_worker(void* context) {
         }
 
         // VCP session closed
-        if((flags & VcpEvtDisconnect) && enabled) {
-#ifdef FURI_HAL_USB_VCP_DEBUG
+        if(flags & VcpEvtDisconnect) {
+#ifdef CLI_VCP_DEBUG
             FURI_LOG_D(TAG, "Disconnect");
 #endif
             if(vcp->connected == true) {
@@ -140,8 +128,8 @@ static int32_t vcp_worker(void* context) {
         }
 
         // Rx buffer was read, maybe there is enough space for new data?
-        if((flags & VcpEvtStreamRx) && enabled && missed_rx > 0) {
-#ifdef FURI_HAL_USB_VCP_DEBUG
+        if((flags & VcpEvtStreamRx) && (missed_rx > 0)) {
+#ifdef CLI_VCP_DEBUG
             FURI_LOG_D(TAG, "StreamRx");
 #endif
             if(xStreamBufferSpacesAvailable(vcp->rx_stream) >= USB_CDC_PKT_LEN) {
@@ -151,10 +139,10 @@ static int32_t vcp_worker(void* context) {
         }
 
         // New data received
-        if((flags & VcpEvtRx)) {
+        if(flags & VcpEvtRx) {
             if(xStreamBufferSpacesAvailable(vcp->rx_stream) >= USB_CDC_PKT_LEN) {
                 int32_t len = furi_hal_cdc_receive(VCP_IF_NUM, vcp->data_buffer, USB_CDC_PKT_LEN);
-#ifdef FURI_HAL_USB_VCP_DEBUG
+#ifdef CLI_VCP_DEBUG
                 FURI_LOG_D(TAG, "Rx %d", len);
 #endif
                 if(len > 0) {
@@ -163,7 +151,7 @@ static int32_t vcp_worker(void* context) {
                         len);
                 }
             } else {
-#ifdef FURI_HAL_USB_VCP_DEBUG
+#ifdef CLI_VCP_DEBUG
                 FURI_LOG_D(TAG, "Rx missed");
 #endif
                 missed_rx++;
@@ -171,8 +159,8 @@ static int32_t vcp_worker(void* context) {
         }
 
         // New data in Tx buffer
-        if((flags & VcpEvtStreamTx) && enabled) {
-#ifdef FURI_HAL_USB_VCP_DEBUG
+        if(flags & VcpEvtStreamTx) {
+#ifdef CLI_VCP_DEBUG
             FURI_LOG_D(TAG, "StreamTx");
 #endif
             if(tx_idle) {
@@ -181,10 +169,10 @@ static int32_t vcp_worker(void* context) {
         }
 
         // CDC write transfer done
-        if((flags & VcpEvtTx) && enabled) {
+        if(flags & VcpEvtTx) {
             size_t len =
                 xStreamBufferReceive(vcp->tx_stream, vcp->data_buffer, USB_CDC_PKT_LEN, 0);
-#ifdef FURI_HAL_USB_VCP_DEBUG
+#ifdef CLI_VCP_DEBUG
             FURI_LOG_D(TAG, "Tx %d", len);
 #endif
             if(len > 0) { // Some data left in Tx buffer. Sending it now
@@ -202,23 +190,33 @@ static int32_t vcp_worker(void* context) {
                 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_set_config(vcp->usb_if_prev, NULL);
+            }
+            xStreamBufferReceive(vcp->tx_stream, vcp->data_buffer, USB_CDC_PKT_LEN, 0);
+            xStreamBufferSend(vcp->rx_stream, &ascii_eot, 1, osWaitForever);
+            break;
+        }
     }
+    FURI_LOG_D(TAG, "End");
     return 0;
 }
 
-void furi_hal_vcp_enable() {
-    osThreadFlagsSet(furi_thread_get_thread_id(vcp->thread), VcpEvtEnable);
-}
-
-void furi_hal_vcp_disable() {
-    osThreadFlagsSet(furi_thread_get_thread_id(vcp->thread), VcpEvtDisable);
-}
-
-size_t furi_hal_vcp_rx_with_timeout(uint8_t* buffer, size_t size, uint32_t timeout) {
+static size_t cli_vcp_rx(uint8_t* buffer, size_t size, uint32_t timeout) {
     furi_assert(vcp);
     furi_assert(buffer);
 
-#ifdef FURI_HAL_USB_VCP_DEBUG
+    if(vcp->running == false) {
+        return 0;
+    }
+
+#ifdef CLI_VCP_DEBUG
     FURI_LOG_D(TAG, "rx %u start", size);
 #endif
 
@@ -229,7 +227,7 @@ size_t furi_hal_vcp_rx_with_timeout(uint8_t* buffer, size_t size, uint32_t timeo
         if(batch_size > VCP_RX_BUF_SIZE) batch_size = VCP_RX_BUF_SIZE;
 
         size_t len = xStreamBufferReceive(vcp->rx_stream, buffer, batch_size, timeout);
-#ifdef FURI_HAL_USB_VCP_DEBUG
+#ifdef CLI_VCP_DEBUG
         FURI_LOG_D(TAG, "rx %u ", batch_size);
 #endif
         if(len == 0) break;
@@ -239,22 +237,21 @@ size_t furi_hal_vcp_rx_with_timeout(uint8_t* buffer, size_t size, uint32_t timeo
         rx_cnt += len;
     }
 
-#ifdef FURI_HAL_USB_VCP_DEBUG
+#ifdef CLI_VCP_DEBUG
     FURI_LOG_D(TAG, "rx %u end", size);
 #endif
     return rx_cnt;
 }
 
-size_t furi_hal_vcp_rx(uint8_t* buffer, size_t size) {
-    furi_assert(vcp);
-    return furi_hal_vcp_rx_with_timeout(buffer, size, osWaitForever);
-}
-
-void furi_hal_vcp_tx(const uint8_t* buffer, size_t size) {
+static void cli_vcp_tx(const uint8_t* buffer, size_t size) {
     furi_assert(vcp);
     furi_assert(buffer);
 
-#ifdef FURI_HAL_USB_VCP_DEBUG
+    if(vcp->running == false) {
+        return;
+    }
+
+#ifdef CLI_VCP_DEBUG
     FURI_LOG_D(TAG, "tx %u start", size);
 #endif
 
@@ -264,7 +261,7 @@ void furi_hal_vcp_tx(const uint8_t* buffer, size_t size) {
 
         xStreamBufferSend(vcp->tx_stream, buffer, batch_size, osWaitForever);
         osThreadFlagsSet(furi_thread_get_thread_id(vcp->thread), VcpEvtStreamTx);
-#ifdef FURI_HAL_USB_VCP_DEBUG
+#ifdef CLI_VCP_DEBUG
         FURI_LOG_D(TAG, "tx %u", batch_size);
 #endif
 
@@ -272,11 +269,15 @@ void furi_hal_vcp_tx(const uint8_t* buffer, size_t size) {
         buffer += batch_size;
     }
 
-#ifdef FURI_HAL_USB_VCP_DEBUG
+#ifdef CLI_VCP_DEBUG
     FURI_LOG_D(TAG, "tx %u end", size);
 #endif
 }
 
+static void cli_vcp_tx_stdout(void* _cookie, const char* data, size_t size) {
+    cli_vcp_tx((const uint8_t*)data, size);
+}
+
 static void vcp_state_callback(void* context, uint8_t state) {
     if(state == 0) {
         osThreadFlagsSet(furi_thread_get_thread_id(vcp->thread), VcpEvtDisconnect);
@@ -303,7 +304,16 @@ static void vcp_on_cdc_tx_complete(void* context) {
     osThreadFlagsSet(furi_thread_get_thread_id(vcp->thread), VcpEvtTx);
 }
 
-bool furi_hal_vcp_is_connected(void) {
+static bool cli_vcp_is_connected(void) {
     furi_assert(vcp);
     return vcp->connected;
 }
+
+CliSession cli_vcp = {
+    cli_vcp_init,
+    cli_vcp_deinit,
+    cli_vcp_rx,
+    cli_vcp_tx,
+    cli_vcp_tx_stdout,
+    cli_vcp_is_connected,
+};

+ 18 - 0
applications/cli/cli_vcp.h

@@ -0,0 +1,18 @@
+/**
+ * @file cli_vcp.h
+ * VCP HAL API
+ */
+
+#pragma once
+
+#include "cli_i.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern CliSession cli_vcp;
+
+#ifdef __cplusplus
+}
+#endif

+ 2 - 1
applications/desktop/desktop_settings/desktop_settings.h

@@ -5,7 +5,7 @@
 #include <stdbool.h>
 #include <toolbox/saved_struct.h>
 
-#define DESKTOP_SETTINGS_VER (2)
+#define DESKTOP_SETTINGS_VER (3)
 #define DESKTOP_SETTINGS_PATH "/int/desktop.settings"
 #define DESKTOP_SETTINGS_MAGIC (0x17)
 #define PIN_MAX_LENGTH 12
@@ -39,5 +39,6 @@ typedef struct {
 typedef struct {
     uint16_t favorite;
     PinCode pin_code;
+    uint8_t is_locked;
     uint32_t auto_lock_delay_ms;
 } DesktopSettings;

+ 31 - 6
applications/desktop/helpers/pin_lock.c

@@ -8,6 +8,7 @@
 
 #include "../helpers/pin_lock.h"
 #include "../desktop_i.h"
+#include <cli/cli_vcp.h>
 
 static const NotificationSequence sequence_pin_fail = {
     &message_display_on,
@@ -61,26 +62,50 @@ uint32_t desktop_pin_lock_get_fail_timeout() {
     return pin_timeout;
 }
 
-void desktop_pin_lock() {
+void desktop_pin_lock(DesktopSettings* settings) {
+    furi_assert(settings);
+
     furi_hal_rtc_set_pin_fails(0);
     furi_hal_rtc_set_flag(FuriHalRtcFlagLock);
-    furi_hal_usb_disable();
+    Cli* cli = furi_record_open("cli");
+    cli_session_close(cli);
+    furi_record_close("cli");
+    settings->is_locked = 1;
+    SAVE_DESKTOP_SETTINGS(settings);
 }
 
-void desktop_pin_unlock() {
+void desktop_pin_unlock(DesktopSettings* settings) {
+    furi_assert(settings);
+
     furi_hal_rtc_reset_flag(FuriHalRtcFlagLock);
-    furi_hal_usb_enable();
+    Cli* cli = furi_record_open("cli");
+    cli_session_open(cli, &cli_vcp);
+    furi_record_close("cli");
+    settings->is_locked = 0;
+    SAVE_DESKTOP_SETTINGS(settings);
 }
 
 void desktop_pin_lock_init(DesktopSettings* settings) {
+    furi_assert(settings);
+
     if(settings->pin_code.length > 0) {
-        furi_hal_rtc_set_flag(FuriHalRtcFlagLock);
-        furi_hal_usb_disable();
+        if(settings->is_locked == 1) {
+            furi_hal_rtc_set_flag(FuriHalRtcFlagLock);
+        } else {
+            if(desktop_pin_lock_is_locked()) {
+                settings->is_locked = 1;
+                SAVE_DESKTOP_SETTINGS(settings);
+            }
+        }
     } else {
         furi_hal_rtc_set_pin_fails(0);
         furi_hal_rtc_reset_flag(FuriHalRtcFlagLock);
         furi_hal_usb_enable();
     }
+
+    if(desktop_pin_lock_is_locked()) {
+        furi_hal_usb_disable();
+    }
 }
 
 bool desktop_pin_lock_verify(const PinCode* pin_set, const PinCode* pin_entered) {

+ 2 - 2
applications/desktop/helpers/pin_lock.h

@@ -8,9 +8,9 @@ void desktop_pin_lock_error_notify();
 
 uint32_t desktop_pin_lock_get_fail_timeout();
 
-void desktop_pin_lock();
+void desktop_pin_lock(DesktopSettings* settings);
 
-void desktop_pin_unlock();
+void desktop_pin_unlock(DesktopSettings* settings);
 
 bool desktop_pin_lock_is_locked();
 

+ 1 - 1
applications/desktop/scenes/desktop_scene_lock_menu.c

@@ -54,7 +54,7 @@ bool desktop_scene_lock_menu_on_event(void* context, SceneManagerEvent event) {
             break;
         case DesktopLockMenuEventPinLock:
             if(desktop->settings.pin_code.length > 0) {
-                desktop_pin_lock();
+                desktop_pin_lock(&desktop->settings);
                 desktop_lock(desktop);
             } else {
                 LoaderStatus status =

+ 1 - 1
applications/desktop/scenes/desktop_scene_pin_input.c

@@ -126,7 +126,7 @@ bool desktop_scene_pin_input_on_event(void* context, SceneManagerEvent event) {
             consumed = true;
             break;
         case DesktopPinInputEventUnlocked:
-            desktop_pin_unlock();
+            desktop_pin_unlock(&desktop->settings);
             desktop_unlock(desktop);
             consumed = true;
             break;

+ 1 - 1
applications/dolphin/helpers/dolphin_state.c

@@ -148,7 +148,7 @@ void dolphin_state_on_deed(DolphinState* dolphin_state, DolphinDeed deed) {
         dolphin_deed_get_app_limit(app) - dolphin_state->data.icounter_daily_limit[app];
     uint8_t deed_weight = CLAMP(dolphin_deed_get_weight(deed), weight_limit, 0);
 
-    uint8_t xp_to_levelup = dolphin_state_xp_to_levelup(dolphin_state->data.icounter);
+    uint32_t xp_to_levelup = dolphin_state_xp_to_levelup(dolphin_state->data.icounter);
     if(xp_to_levelup) {
         deed_weight = MIN(xp_to_levelup, deed_weight);
         dolphin_state->data.icounter += deed_weight;

+ 29 - 14
applications/gpio/usb_uart_bridge.c

@@ -3,6 +3,8 @@
 #include <stream_buffer.h>
 #include <furi_hal_usb_cdc_i.h>
 #include "usb_cdc.h"
+#include "cli/cli_vcp.h"
+#include "cli/cli.h"
 
 #define USB_CDC_PKT_LEN CDC_DATA_SZ
 #define USB_UART_RX_BUF_SIZE (USB_CDC_PKT_LEN * 5)
@@ -16,17 +18,16 @@ static const GpioPin* flow_pins[][2] = {
 };
 
 typedef enum {
-    WorkerEvtReserved = (1 << 0), // Reserved for StreamBuffer internal event
-    WorkerEvtStop = (1 << 1),
-    WorkerEvtRxDone = (1 << 2),
+    WorkerEvtStop = (1 << 0),
+    WorkerEvtRxDone = (1 << 1),
 
-    WorkerEvtTxStop = (1 << 3),
-    WorkerEvtCdcRx = (1 << 4),
+    WorkerEvtTxStop = (1 << 2),
+    WorkerEvtCdcRx = (1 << 3),
 
-    WorkerEvtCfgChange = (1 << 5),
+    WorkerEvtCfgChange = (1 << 4),
 
-    WorkerEvtLineCfgSet = (1 << 6),
-    WorkerEvtCtrlLineSet = (1 << 7),
+    WorkerEvtLineCfgSet = (1 << 5),
+    WorkerEvtCtrlLineSet = (1 << 6),
 
 } WorkerEvtFlags;
 
@@ -84,18 +85,29 @@ static void usb_uart_on_irq_cb(UartIrqEvent ev, uint8_t data, void* context) {
 
 static void usb_uart_vcp_init(UsbUartBridge* usb_uart, uint8_t vcp_ch) {
     furi_hal_usb_unlock();
+    FURI_LOG_I("", "Init %d", vcp_ch);
     if(vcp_ch == 0) {
+        Cli* cli = furi_record_open("cli");
+        cli_session_close(cli);
+        furi_record_close("cli");
         furi_check(furi_hal_usb_set_config(&usb_cdc_single, NULL) == true);
-        furi_hal_vcp_disable();
     } else {
         furi_check(furi_hal_usb_set_config(&usb_cdc_dual, NULL) == true);
+        Cli* cli = furi_record_open("cli");
+        cli_session_open(cli, &cli_vcp);
+        furi_record_close("cli");
     }
     furi_hal_cdc_set_callbacks(vcp_ch, (CdcCallbacks*)&cdc_cb, usb_uart);
 }
 
 static void usb_uart_vcp_deinit(UsbUartBridge* usb_uart, uint8_t vcp_ch) {
     furi_hal_cdc_set_callbacks(vcp_ch, NULL, NULL);
-    if(vcp_ch == 0) furi_hal_vcp_enable();
+    FURI_LOG_I("", "Deinit %d", vcp_ch);
+    if(vcp_ch != 0) {
+        Cli* cli = furi_record_open("cli");
+        cli_session_close(cli);
+        furi_record_close("cli");
+    }
 }
 
 static void usb_uart_serial_init(UsbUartBridge* usb_uart, uint8_t uart_ch) {
@@ -155,7 +167,6 @@ static int32_t usb_uart_worker(void* context) {
     furi_thread_set_context(usb_uart->tx_thread, usb_uart);
     furi_thread_set_callback(usb_uart->tx_thread, usb_uart_tx_thread);
 
-    FuriHalUsbInterface* usb_mode_prev = furi_hal_usb_get_config();
     usb_uart_vcp_init(usb_uart, usb_uart->cfg.vcp_ch);
     usb_uart_serial_init(usb_uart, usb_uart->cfg.uart_ch);
     usb_uart_set_baudrate(usb_uart, usb_uart->cfg.baudrate);
@@ -247,11 +258,9 @@ static int32_t usb_uart_worker(void* context) {
             usb_uart_update_ctrl_lines(usb_uart);
         }
     }
-
     usb_uart_vcp_deinit(usb_uart, usb_uart->cfg.vcp_ch);
     usb_uart_serial_deinit(usb_uart, usb_uart->cfg.uart_ch);
-    furi_hal_usb_unlock();
-    furi_hal_usb_set_config(usb_mode_prev, NULL);
+
     if(usb_uart->cfg.flow_pins != 0) {
         furi_hal_gpio_init_simple(flow_pins[usb_uart->cfg.flow_pins - 1][0], GpioModeAnalog);
         furi_hal_gpio_init_simple(flow_pins[usb_uart->cfg.flow_pins - 1][1], GpioModeAnalog);
@@ -265,6 +274,12 @@ static int32_t usb_uart_worker(void* context) {
     osMutexDelete(usb_uart->usb_mutex);
     osSemaphoreDelete(usb_uart->tx_sem);
 
+    furi_hal_usb_unlock();
+    furi_check(furi_hal_usb_set_config(&usb_cdc_single, NULL) == true);
+    Cli* cli = furi_record_open("cli");
+    cli_session_open(cli, &cli_vcp);
+    furi_record_close("cli");
+
     return 0;
 }
 

+ 2 - 2
applications/rpc/rpc_cli.c

@@ -62,8 +62,8 @@ void rpc_cli_command_start_session(Cli* cli, string_t args, void* context) {
     size_t size_received = 0;
 
     while(1) {
-        size_received = furi_hal_vcp_rx_with_timeout(buffer, CLI_READ_BUFFER_SIZE, 50);
-        if(!furi_hal_vcp_is_connected() || cli_rpc.session_close_request) {
+        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) {
             break;
         }
 

+ 6 - 2
applications/subghz/helpers/subghz_chat.c

@@ -12,6 +12,8 @@ struct SubGhzChatWorker {
     volatile bool worker_stoping;
     osMessageQueueId_t event_queue;
     uint32_t last_time_rx_data;
+
+    Cli* cli;
 };
 
 /** Worker thread
@@ -27,7 +29,7 @@ static int32_t subghz_chat_worker_thread(void* context) {
     event.event = SubGhzChatEventUserEntrance;
     osMessageQueuePut(instance->event_queue, &event, 0, 0);
     while(instance->worker_running) {
-        if(furi_hal_vcp_rx_with_timeout((uint8_t*)&c, 1, 1000) == 1) {
+        if(cli_read_timeout(instance->cli, (uint8_t*)&c, 1, 1000) == 1) {
             event.event = SubGhzChatEventInputData;
             event.c = c;
             osMessageQueuePut(instance->event_queue, &event, 0, osWaitForever);
@@ -52,9 +54,11 @@ static void subghz_chat_worker_update_rx_event_chat(void* context) {
     osMessageQueuePut(instance->event_queue, &event, 0, osWaitForever);
 }
 
-SubGhzChatWorker* subghz_chat_worker_alloc() {
+SubGhzChatWorker* subghz_chat_worker_alloc(Cli* cli) {
     SubGhzChatWorker* instance = malloc(sizeof(SubGhzChatWorker));
 
+    instance->cli = cli;
+
     instance->thread = furi_thread_alloc();
     furi_thread_set_name(instance->thread, "SubGhzChat");
     furi_thread_set_stack_size(instance->thread, 2048);

+ 2 - 1
applications/subghz/helpers/subghz_chat.h

@@ -1,5 +1,6 @@
 #pragma once
 #include "../subghz_i.h"
+#include <cli/cli.h>
 
 typedef struct SubGhzChatWorker SubGhzChatWorker;
 
@@ -17,7 +18,7 @@ typedef struct {
     char c;
 } SubGhzChatEvent;
 
-SubGhzChatWorker* subghz_chat_worker_alloc();
+SubGhzChatWorker* subghz_chat_worker_alloc(Cli* cli);
 void subghz_chat_worker_free(SubGhzChatWorker* instance);
 bool subghz_chat_worker_start(SubGhzChatWorker* instance, uint32_t frequency);
 void subghz_chat_worker_stop(SubGhzChatWorker* instance);

+ 1 - 1
applications/subghz/subghz_cli.c

@@ -525,7 +525,7 @@ static void subghz_cli_command_chat(Cli* cli, string_t args) {
         return;
     }
 
-    SubGhzChatWorker* subghz_chat = subghz_chat_worker_alloc();
+    SubGhzChatWorker* subghz_chat = subghz_chat_worker_alloc(cli);
     if(!subghz_chat_worker_start(subghz_chat, frequency)) {
         printf("Startup error SubGhzChatWorker\r\n");
 

+ 0 - 1
firmware/targets/f7/furi_hal/furi_hal_usb.c

@@ -1,7 +1,6 @@
 #include "furi_hal_version.h"
 #include "furi_hal_usb_i.h"
 #include "furi_hal_usb.h"
-#include "furi_hal_vcp.h"
 #include <furi_hal_power.h>
 #include <stm32wbxx_ll_pwr.h>
 #include <furi.h>

+ 3 - 0
firmware/targets/f7/furi_hal/furi_hal_usb_cdc.c

@@ -482,6 +482,9 @@ void furi_hal_cdc_set_callbacks(uint8_t if_num, CdcCallbacks* cb, void* context)
         if(callbacks[if_num]->state_callback != NULL) {
             if(connected == true) callbacks[if_num]->state_callback(cb_ctx[if_num], 1);
         }
+        if(callbacks[if_num]->ctrl_line_callback != NULL) {
+            callbacks[if_num]->ctrl_line_callback(cb_ctx[if_num], cdc_ctrl_line_state[if_num]);
+        }
     }
 }
 

+ 0 - 1
firmware/targets/furi_hal_include/furi_hal.h

@@ -22,7 +22,6 @@ template <unsigned int N> struct STOP_EXTERNING_ME {};
 #include "furi_hal_light.h"
 #include "furi_hal_delay.h"
 #include "furi_hal_power.h"
-#include "furi_hal_vcp.h"
 #include "furi_hal_interrupt.h"
 #include "furi_hal_version.h"
 #include "furi_hal_bt.h"

+ 0 - 63
firmware/targets/furi_hal_include/furi_hal_vcp.h

@@ -1,63 +0,0 @@
-/**
- * @file furi_hal_vcp.h
- * VCP HAL API
- */
-
-#pragma once
-
-#include <stdbool.h>
-#include <stdint.h>
-#include <string.h>
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-/** Init VCP HAL Allocates ring buffer and initializes state
- */
-void furi_hal_vcp_init();
-
-/** Disable VCP to make CDC interface usable by other application
- */
-void furi_hal_vcp_disable();
-
-/** Enable VCP
- */
-void furi_hal_vcp_enable();
-
-/** Recieve data from VCP Waits till some data arrives, never returns 0
- *
- * @param      buffer  pointer to buffer
- * @param      size    buffer size
- *
- * @return     items copied in buffer, 0 if channel closed
- */
-size_t furi_hal_vcp_rx(uint8_t* buffer, size_t size);
-
-/** Recieve data from VCP with timeout Waits till some data arrives during
- * timeout
- *
- * @param      buffer   pointer to buffer
- * @param      size     buffer size
- * @param      timeout  rx timeout in ms
- *
- * @return     items copied in buffer, 0 if channel closed or timeout occurs
- */
-size_t furi_hal_vcp_rx_with_timeout(uint8_t* buffer, size_t size, uint32_t timeout);
-
-/** Transmit data to VCP
- *
- * @param      buffer  pointer to buffer
- * @param      size    buffer size
- */
-void furi_hal_vcp_tx(const uint8_t* buffer, size_t size);
-
-/** Check whether VCP is connected
- *
- * @return     true if connected
- */
-bool furi_hal_vcp_is_connected(void);
-
-#ifdef __cplusplus
-}
-#endif