[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>
This commit is contained in:
Nikolay Minaylov
2022-04-29 15:02:17 +03:00
committed by GitHub
parent 76221021ec
commit 0eac917f91
20 changed files with 348 additions and 243 deletions

View File

@@ -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;

View File

@@ -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

View File

@@ -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);

319
applications/cli/cli_vcp.c Normal file
View File

@@ -0,0 +1,319 @@
#include <furi_hal_usb_cdc_i.h>
#include <furi_hal.h>
#include <furi.h>
#include <stream_buffer.h>
#include "cli_i.h"
#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_IF_NUM 0
typedef enum {
VcpEvtStop = (1 << 0),
VcpEvtConnect = (1 << 1),
VcpEvtDisconnect = (1 << 2),
VcpEvtStreamRx = (1 << 3),
VcpEvtRx = (1 << 4),
VcpEvtStreamTx = (1 << 5),
VcpEvtTx = (1 << 6),
} WorkerEvtFlags;
#define VCP_THREAD_FLAG_ALL \
(VcpEvtStop | VcpEvtConnect | VcpEvtDisconnect | VcpEvtRx | VcpEvtTx | VcpEvtStreamRx | \
VcpEvtStreamTx)
typedef struct {
FuriThread* thread;
StreamBufferHandle_t tx_stream;
StreamBufferHandle_t rx_stream;
volatile bool connected;
volatile bool running;
FuriHalUsbInterface* usb_if_prev;
uint8_t data_buffer[USB_CDC_PKT_LEN];
} CliVcp;
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,
};
static CliVcp* vcp = NULL;
static const uint8_t ascii_soh = 0x01;
static const uint8_t ascii_eot = 0x04;
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, "CliVcpWorker");
furi_thread_set_stack_size(vcp->thread, 1024);
furi_thread_set_callback(vcp->thread, vcp_worker);
furi_thread_start(vcp->thread);
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 tx_idle = true;
size_t missed_rx = 0;
uint8_t last_tx_pkt_len = 0;
// Switch USB to VCP mode (if it is not set yet)
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 session opened
if(flags & VcpEvtConnect) {
#ifdef CLI_VCP_DEBUG
FURI_LOG_D(TAG, "Connect");
#endif
if(vcp->connected == false) {
vcp->connected = true;
xStreamBufferSend(vcp->rx_stream, &ascii_soh, 1, osWaitForever);
}
}
// VCP session closed
if(flags & VcpEvtDisconnect) {
#ifdef CLI_VCP_DEBUG
FURI_LOG_D(TAG, "Disconnect");
#endif
if(vcp->connected == true) {
vcp->connected = false;
xStreamBufferReceive(vcp->tx_stream, vcp->data_buffer, USB_CDC_PKT_LEN, 0);
xStreamBufferSend(vcp->rx_stream, &ascii_eot, 1, osWaitForever);
}
}
// Rx buffer was read, maybe there is enough space for new data?
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) {
flags |= VcpEvtRx;
missed_rx--;
}
}
// New data received
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 CLI_VCP_DEBUG
FURI_LOG_D(TAG, "Rx %d", len);
#endif
if(len > 0) {
furi_check(
xStreamBufferSend(vcp->rx_stream, vcp->data_buffer, len, osWaitForever) ==
len);
}
} else {
#ifdef CLI_VCP_DEBUG
FURI_LOG_D(TAG, "Rx missed");
#endif
missed_rx++;
}
}
// New data in Tx buffer
if(flags & VcpEvtStreamTx) {
#ifdef CLI_VCP_DEBUG
FURI_LOG_D(TAG, "StreamTx");
#endif
if(tx_idle) {
flags |= VcpEvtTx;
}
}
// CDC write transfer done
if(flags & VcpEvtTx) {
size_t len =
xStreamBufferReceive(vcp->tx_stream, vcp->data_buffer, USB_CDC_PKT_LEN, 0);
#ifdef CLI_VCP_DEBUG
FURI_LOG_D(TAG, "Tx %d", len);
#endif
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_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;
}
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;
}
#ifdef CLI_VCP_DEBUG
FURI_LOG_D(TAG, "rx %u start", size);
#endif
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 = xStreamBufferReceive(vcp->rx_stream, buffer, batch_size, timeout);
#ifdef CLI_VCP_DEBUG
FURI_LOG_D(TAG, "rx %u ", batch_size);
#endif
if(len == 0) break;
osThreadFlagsSet(furi_thread_get_thread_id(vcp->thread), VcpEvtStreamRx);
size -= len;
buffer += len;
rx_cnt += len;
}
#ifdef CLI_VCP_DEBUG
FURI_LOG_D(TAG, "rx %u end", size);
#endif
return rx_cnt;
}
static void cli_vcp_tx(const uint8_t* buffer, size_t size) {
furi_assert(vcp);
furi_assert(buffer);
if(vcp->running == false) {
return;
}
#ifdef CLI_VCP_DEBUG
FURI_LOG_D(TAG, "tx %u start", size);
#endif
while(size > 0 && vcp->connected) {
size_t batch_size = size;
if(batch_size > USB_CDC_PKT_LEN) batch_size = USB_CDC_PKT_LEN;
xStreamBufferSend(vcp->tx_stream, buffer, batch_size, osWaitForever);
osThreadFlagsSet(furi_thread_get_thread_id(vcp->thread), VcpEvtStreamTx);
#ifdef CLI_VCP_DEBUG
FURI_LOG_D(TAG, "tx %u", batch_size);
#endif
size -= batch_size;
buffer += batch_size;
}
#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);
}
}
static void vcp_on_cdc_control_line(void* context, uint8_t state) {
// bit 0: DTR state, bit 1: RTS state
bool dtr = state & (1 << 0);
if(dtr == true) {
osThreadFlagsSet(furi_thread_get_thread_id(vcp->thread), VcpEvtConnect);
} else {
osThreadFlagsSet(furi_thread_get_thread_id(vcp->thread), VcpEvtDisconnect);
}
}
static void vcp_on_cdc_rx(void* context) {
uint32_t ret = osThreadFlagsSet(furi_thread_get_thread_id(vcp->thread), VcpEvtRx);
furi_check((ret & osFlagsError) == 0);
}
static void vcp_on_cdc_tx_complete(void* context) {
osThreadFlagsSet(furi_thread_get_thread_id(vcp->thread), VcpEvtTx);
}
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,
};

View File

@@ -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