[FL-2627] Flipper applications: SDK, build and debug system (#1387)

* Added support for running applications from SD card (FAPs - Flipper Application Packages)
* Added plugin_dist target for fbt to build FAPs
* All apps of type FlipperAppType.EXTERNAL and FlipperAppType.PLUGIN are built as FAPs by default
* Updated VSCode configuration for new fbt features - re-deploy stock configuration to use them
* Added debugging support for FAPs with fbt debug & VSCode
* Added public firmware API with automated versioning

Co-authored-by: hedger <hedger@users.noreply.github.com>
Co-authored-by: SG <who.just.the.doctor@gmail.com>
Co-authored-by: あく <alleteam@gmail.com>
This commit is contained in:
SG
2022-09-15 02:11:38 +10:00
committed by Aleksandr Kutuzov
parent 0f6f9ad52e
commit b9a766d909
895 changed files with 8862 additions and 1465 deletions

View File

@@ -0,0 +1,10 @@
App(
appid="cli",
name="CliSrv",
apptype=FlipperAppType.SERVICE,
entry_point="cli_srv",
cdefines=["SRV_CLI"],
stack_size=4 * 1024,
order=30,
sdk_headers=["cli.h", "cli_vcp.h"],
)

View File

@@ -0,0 +1,492 @@
#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));
CliCommandTree_init(cli->commands);
string_init(cli->last_line);
string_init(cli->line);
cli->session = NULL;
cli->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
furi_check(cli->mutex);
cli->idle_sem = furi_semaphore_alloc(1, 0);
return cli;
}
void cli_putc(Cli* cli, char c) {
furi_assert(cli);
if(cli->session != NULL) {
cli->session->tx((uint8_t*)&c, 1);
}
}
char cli_getc(Cli* cli) {
furi_assert(cli);
char c = 0;
if(cli->session != NULL) {
if(cli->session->rx((uint8_t*)&c, 1, FuriWaitForever) == 0) {
cli_reset(cli);
}
} else {
cli_reset(cli);
}
return c;
}
void cli_write(Cli* cli, const uint8_t* buffer, size_t 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) {
furi_assert(cli);
if(cli->session != NULL) {
return cli->session->rx(buffer, size, FuriWaitForever);
} else {
return 0;
}
}
size_t cli_read_timeout(Cli* cli, uint8_t* buffer, size_t size, uint32_t timeout) {
furi_assert(cli);
if(cli->session != NULL) {
return cli->session->rx(buffer, size, timeout);
} else {
return 0;
}
}
bool cli_is_connected(Cli* cli) {
furi_assert(cli);
if(cli->session != NULL) {
return (cli->session->is_connected());
}
return false;
}
bool cli_cmd_interrupt_received(Cli* cli) {
furi_assert(cli);
char c = '\0';
if(cli_is_connected(cli)) {
if(cli->session->rx((uint8_t*)&c, 1, 0) == 1) {
return c == CliSymbolAsciiETX;
}
} else {
return true;
}
return false;
}
void cli_print_usage(const char* cmd, const char* usage, const char* arg) {
furi_assert(cmd);
furi_assert(arg);
furi_assert(usage);
printf("%s: illegal option -- %s\r\nusage: %s %s", cmd, arg, cmd, usage);
}
void cli_motd() {
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 Manual https://docs.flipperzero.one\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", 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
string_move(cli->last_line, cli->line);
// Reiniting cli->line
string_init(cli->line);
cli->cursor_position = 0;
}
static void cli_handle_backspace(Cli* cli) {
if(cli->cursor_position > 0) {
furi_assert(string_size(cli->line) > 0);
// Other side
printf("\e[D\e[1P");
fflush(stdout);
// Our side
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 - 1);
string_cat_str(temp, string_get_cstr(cli->line) + cli->cursor_position);
// cli->line is cleared and temp's buffer moved to cli->line
string_move(cli->line, temp);
// NO MEMORY LEAK, STOP REPORTING IT
cli->cursor_position--;
} else {
cli_putc(cli, CliSymbolAsciiBell);
}
}
static void cli_normalize_line(Cli* cli) {
string_strim(cli->line);
cli->cursor_position = string_size(cli->line);
}
static void cli_execute_command(Cli* cli, CliCommand* command, string_t 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(string_size(cli->line) == 0) {
cli_prompt(cli);
return;
}
// Command and args container
string_t command;
string_init(command);
string_t args;
string_init(args);
// Split command and args
size_t ws = string_search_char(cli->line, ' ');
if(ws == STRING_FAILURE) {
string_set(command, cli->line);
} else {
string_set_n(command, cli->line, 0, ws);
string_set_n(args, cli->line, ws, string_size(cli->line));
string_strim(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) {
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",
string_get_cstr(command));
cli_putc(cli, CliSymbolAsciiBell);
}
cli_reset(cli);
cli_prompt(cli);
// Cleanup command and args
string_clear(command);
string_clear(args);
}
static void cli_handle_autocomplete(Cli* cli) {
cli_normalize_line(cli);
if(string_size(cli->line) == 0) {
return;
}
cli_nl(cli);
// Prepare common base for autocomplete
string_t common;
string_init(common);
// Iterate throw commands
for
M_EACH(cli_command, cli->commands, CliCommandTree_t) {
// Process only if starts with line buffer
if(string_start_with_string_p(*cli_command->key_ptr, cli->line)) {
// Show autocomplete option
printf("%s\r\n", string_get_cstr(*cli_command->key_ptr));
// Process common base for autocomplete
if(string_size(common) > 0) {
// Choose shortest string
const size_t key_size = string_size(*cli_command->key_ptr);
const size_t common_size = 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(string_get_char(*cli_command->key_ptr, i) !=
string_get_char(common, i)) {
break;
}
i++;
}
// Cut right part if any
string_left(common, i);
} else {
// Start with something
string_set(common, *cli_command->key_ptr);
}
}
}
// Replace line buffer if autocomplete better
if(string_size(common) > string_size(cli->line)) {
string_set(cli->line, common);
cli->cursor_position = string_size(cli->line);
}
// Cleanup
string_clear(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(string_size(cli->line) == 0 && string_cmp(cli->line, cli->last_line) != 0) {
// Set line buffer and cursor position
string_set(cli->line, cli->last_line);
cli->cursor_position = string_size(cli->line);
// Show new line to user
printf("%s", string_get_cstr(cli->line));
}
} else if(c == 'B') {
} else if(c == 'C') {
if(cli->cursor_position < 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) {
if(cli->cursor_position == string_size(cli->line)) {
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, 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
string_move(cli->line, temp);
// NO MEMORY LEAK, STOP REPORTING IT
// Print character in replace mode
printf("\e[4h%c\e[4l", in_chr);
fflush(stdout);
}
cli->cursor_position++;
} else {
cli_putc(cli, CliSymbolAsciiBell);
}
}
void cli_add_command(
Cli* cli,
const char* name,
CliCommandFlag flags,
CliCallback callback,
void* context) {
string_t name_str;
string_init_set_str(name_str, name);
string_strim(name_str);
size_t name_replace;
do {
name_replace = string_replace_str(name_str, " ", "_");
} while(name_replace != STRING_FAILURE);
CliCommand c;
c.callback = callback;
c.context = context;
c.flags = flags;
furi_check(furi_mutex_acquire(cli->mutex, FuriWaitForever) == FuriStatusOk);
CliCommandTree_set_at(cli->commands, name_str, c);
furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk);
string_clear(name_str);
}
void cli_delete_command(Cli* cli, const char* name) {
string_t name_str;
string_init_set_str(name_str, name);
string_strim(name_str);
size_t name_replace;
do {
name_replace = string_replace_str(name_str, " ", "_");
} while(name_replace != STRING_FAILURE);
furi_check(furi_mutex_acquire(cli->mutex, FuriWaitForever) == FuriStatusOk);
CliCommandTree_erase(cli->commands, name_str);
furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk);
string_clear(name_str);
}
void cli_session_open(Cli* cli, void* session) {
furi_assert(cli);
furi_check(furi_mutex_acquire(cli->mutex, FuriWaitForever) == FuriStatusOk);
cli->session = session;
if(cli->session != NULL) {
cli->session->init();
furi_thread_set_stdout_callback(cli->session->tx_stdout);
} else {
furi_thread_set_stdout_callback(NULL);
}
furi_semaphore_release(cli->idle_sem);
furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk);
}
void cli_session_close(Cli* cli) {
furi_assert(cli);
furi_check(furi_mutex_acquire(cli->mutex, FuriWaitForever) == FuriStatusOk);
if(cli->session != NULL) {
cli->session->deinit();
}
cli->session = NULL;
furi_thread_set_stdout_callback(NULL);
furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk);
}
int32_t cli_srv(void* p) {
UNUSED(p);
Cli* cli = cli_alloc();
// Init basic cli commands
cli_commands_init(cli);
furi_record_create(RECORD_CLI, cli);
if(cli->session != NULL) {
furi_thread_set_stdout_callback(cli->session->tx_stdout);
} else {
furi_thread_set_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) {
if(cli->session != NULL) {
cli_process_input(cli);
} else {
furi_check(furi_semaphore_acquire(cli->idle_sem, FuriWaitForever) == FuriStatusOk);
}
}
return 0;
}

View File

@@ -0,0 +1,135 @@
/**
* @file cli.h
* Cli API
*/
#pragma once
#include <m-string.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;
typedef enum {
CliCommandFlagDefault = 0, /**< Default, loader lock is used */
CliCommandFlagParallelSafe =
(1 << 0), /**< Safe to run in parallel with other apps, loader lock is not used */
CliCommandFlagInsomniaSafe = (1 << 1), /**< Safe to run with insomnia mode on */
} CliCommandFlag;
#define RECORD_CLI "cli"
/** 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
*/
typedef void (*CliCallback)(Cli* cli, string_t args, void* context);
/** Add cli command Registers you command callback
*
* @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
*/
void cli_add_command(
Cli* cli,
const char* name,
CliCommandFlag flags,
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
*
* @param cli pointer to cli instance
* @param name command name
*/
void cli_delete_command(Cli* cli, const char* name);
/** Read from terminal
*
* @param cli Cli instance
* @param buffer pointer to buffer
* @param size size of buffer in bytes
*
* @return bytes read
*/
size_t cli_read(Cli* cli, uint8_t* buffer, size_t size);
/** Non-blocking read from terminal
*
* @param cli Cli instance
* @param buffer pointer to buffer
* @param size size of buffer in bytes
* @param timeout timeout value in ms
*
* @return bytes read
*/
size_t cli_read_timeout(Cli* cli, uint8_t* buffer, size_t size, uint32_t timeout);
/** Non-blocking check for interrupt command received
*
* @param cli Cli instance
*
* @return true if received
*/
bool cli_cmd_interrupt_received(Cli* cli);
/** Write to terminal Do it only from inside of cli call.
*
* @param cli Cli instance
* @param buffer pointer to buffer
* @param size size of buffer in bytes
*/
void cli_write(Cli* cli, const uint8_t* buffer, size_t size);
/** Read character
*
* @param cli Cli instance
*
* @return char
*/
char cli_getc(Cli* cli);
/** New line Send new ine sequence
*/
void cli_nl();
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

@@ -0,0 +1,228 @@
#include "cli_command_gpio.h"
#include <furi.h>
#include <furi_hal.h>
#include <lib/toolbox/args.h>
typedef struct {
const GpioPin* pin;
const char* name;
const bool debug;
} CliCommandGpio;
const CliCommandGpio cli_command_gpio_pins[] = {
{.pin = &gpio_ext_pc0, .name = "PC0", .debug = false},
{.pin = &gpio_ext_pc1, .name = "PC1", .debug = false},
{.pin = &gpio_ext_pc3, .name = "PC3", .debug = false},
{.pin = &gpio_ext_pb2, .name = "PB2", .debug = false},
{.pin = &gpio_ext_pb3, .name = "PB3", .debug = false},
{.pin = &gpio_ext_pa4, .name = "PA4", .debug = false},
{.pin = &gpio_ext_pa6, .name = "PA6", .debug = false},
{.pin = &gpio_ext_pa7, .name = "PA7", .debug = false},
/* Dangerous pins, may damage hardware */
{.pin = &gpio_infrared_rx, .name = "PA0", .debug = true},
{.pin = &gpio_usart_rx, .name = "PB7", .debug = true},
{.pin = &gpio_speaker, .name = "PB8", .debug = true},
{.pin = &gpio_infrared_tx, .name = "PB9", .debug = true},
};
void cli_command_gpio_print_usage() {
printf("Usage:\r\n");
printf("gpio <cmd> <args>\r\n");
printf("Cmd list:\r\n");
printf("\tmode <pin_name> <0|1>\t - Set gpio mode: 0 - input, 1 - output\r\n");
printf("\tset <pin_name> <0|1>\t - Set gpio value\r\n");
printf("\tread <pin_name>\t - Read gpio value\r\n");
}
static bool pin_name_to_int(string_t pin_name, size_t* result) {
bool found = false;
bool debug = furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug);
for(size_t i = 0; i < COUNT_OF(cli_command_gpio_pins); i++) {
if(!string_cmp(pin_name, cli_command_gpio_pins[i].name)) {
if(!cli_command_gpio_pins[i].debug || (cli_command_gpio_pins[i].debug && debug)) {
*result = i;
found = true;
break;
}
}
}
return found;
}
static void gpio_print_pins(void) {
printf("Wrong pin name. Available pins: ");
bool debug = furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug);
for(size_t i = 0; i < COUNT_OF(cli_command_gpio_pins); i++) {
if(!cli_command_gpio_pins[i].debug || (cli_command_gpio_pins[i].debug && debug)) {
printf("%s ", cli_command_gpio_pins[i].name);
}
}
}
typedef enum { OK, ERR_CMD_SYNTAX, ERR_PIN, ERR_VALUE } GpioParseError;
static GpioParseError gpio_command_parse(string_t args, size_t* pin_num, uint8_t* value) {
string_t pin_name;
string_init(pin_name);
size_t ws = string_search_char(args, ' ');
if(ws == STRING_FAILURE) {
return ERR_CMD_SYNTAX;
}
string_set_n(pin_name, args, 0, ws);
string_right(args, ws);
string_strim(args);
if(!pin_name_to_int(pin_name, pin_num)) {
string_clear(pin_name);
return ERR_PIN;
}
string_clear(pin_name);
if(!string_cmp(args, "0")) {
*value = 0;
} else if(!string_cmp(args, "1")) {
*value = 1;
} else {
return ERR_VALUE;
}
return OK;
}
void cli_command_gpio_mode(Cli* cli, string_t args, void* context) {
UNUSED(cli);
UNUSED(context);
size_t num = 0;
uint8_t value = 255;
GpioParseError err = gpio_command_parse(args, &num, &value);
if(ERR_CMD_SYNTAX == err) {
cli_print_usage("gpio mode", "<pin_name> <0|1>", string_get_cstr(args));
return;
} else if(ERR_PIN == err) {
gpio_print_pins();
return;
} else if(ERR_VALUE == err) {
printf("Value is invalid. Enter 1 for input or 0 for output");
return;
}
if(cli_command_gpio_pins[num].debug) {
printf(
"Changeing this pin mode may damage hardware. Are you sure you want to continue? (y/n)?\r\n");
char c = cli_getc(cli);
if(c != 'y' && c != 'Y') {
printf("Cancelled.\r\n");
return;
}
}
if(value == 1) { // output
furi_hal_gpio_write(cli_command_gpio_pins[num].pin, false);
furi_hal_gpio_init_simple(cli_command_gpio_pins[num].pin, GpioModeOutputPushPull);
printf("Pin %s is now an output (low)", cli_command_gpio_pins[num].name);
} else { // input
furi_hal_gpio_init_simple(cli_command_gpio_pins[num].pin, GpioModeInput);
printf("Pin %s is now an input", cli_command_gpio_pins[num].name);
}
}
void cli_command_gpio_read(Cli* cli, string_t args, void* context) {
UNUSED(cli);
UNUSED(context);
size_t num = 0;
if(!pin_name_to_int(args, &num)) {
gpio_print_pins();
return;
}
if(LL_GPIO_MODE_INPUT !=
LL_GPIO_GetPinMode(
cli_command_gpio_pins[num].pin->port, cli_command_gpio_pins[num].pin->pin)) {
printf("Err: pin %s is not set as an input.", cli_command_gpio_pins[num].name);
return;
}
uint8_t val = !!furi_hal_gpio_read(cli_command_gpio_pins[num].pin);
printf("Pin %s <= %u", cli_command_gpio_pins[num].name, val);
}
void cli_command_gpio_set(Cli* cli, string_t args, void* context) {
UNUSED(context);
size_t num = 0;
uint8_t value = 0;
GpioParseError err = gpio_command_parse(args, &num, &value);
if(ERR_CMD_SYNTAX == err) {
cli_print_usage("gpio set", "<pin_name> <0|1>", string_get_cstr(args));
return;
} else if(ERR_PIN == err) {
gpio_print_pins();
return;
} else if(ERR_VALUE == err) {
printf("Value is invalid. Enter 1 for high or 0 for low");
return;
}
if(LL_GPIO_MODE_OUTPUT !=
LL_GPIO_GetPinMode(
cli_command_gpio_pins[num].pin->port, cli_command_gpio_pins[num].pin->pin)) {
printf("Err: pin %s is not set as an output.", cli_command_gpio_pins[num].name);
return;
}
// Extra check if debug pins used
if(cli_command_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);
if(c != 'y' && c != 'Y') {
printf("Cancelled.\r\n");
return;
}
}
furi_hal_gpio_write(cli_command_gpio_pins[num].pin, !!value);
printf("Pin %s => %u", cli_command_gpio_pins[num].name, !!value);
}
void cli_command_gpio(Cli* cli, string_t args, void* context) {
string_t cmd;
string_init(cmd);
do {
if(!args_read_string_and_trim(args, cmd)) {
cli_command_gpio_print_usage();
break;
}
if(string_cmp_str(cmd, "mode") == 0) {
cli_command_gpio_mode(cli, args, context);
break;
}
if(string_cmp_str(cmd, "set") == 0) {
cli_command_gpio_set(cli, args, context);
break;
}
if(string_cmp_str(cmd, "read") == 0) {
cli_command_gpio_read(cli, args, context);
break;
}
cli_command_gpio_print_usage();
} while(false);
string_clear(cmd);
}

View File

@@ -0,0 +1,5 @@
#pragma once
#include "cli_i.h"
void cli_command_gpio(Cli* cli, string_t args, void* context);

View File

@@ -0,0 +1,338 @@
#include "cli_commands.h"
#include "cli_command_gpio.h"
#include <furi_hal.h>
#include <furi_hal_info.h>
#include <task_control_block.h>
#include <time.h>
#include <notification/notification_messages.h>
#include <loader/loader.h>
#include <stream_buffer.h>
// Close to ISO, `date +'%Y-%m-%d %H:%M:%S %u'`
#define CLI_DATE_FORMAT "%.4d-%.2d-%.2d %.2d:%.2d:%.2d %d"
void cli_command_device_info_callback(const char* key, const char* value, bool last, void* context) {
UNUSED(context);
UNUSED(last);
printf("%-30s: %s\r\n", key, value);
}
/*
* Device Info Command
* This command is intended to be used by humans
*/
void cli_command_device_info(Cli* cli, string_t args, void* context) {
UNUSED(cli);
UNUSED(args);
furi_hal_info_get(cli_command_device_info_callback, context);
}
void cli_command_help(Cli* cli, string_t args, void* context) {
UNUSED(args);
UNUSED(context);
printf("Commands we have:");
// 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("\r\n");
// Left Column
if(!CliCommandTree_end_p(it_left)) {
printf("%-30s", string_get_cstr(*CliCommandTree_ref(it_left)->key_ptr));
CliCommandTree_next(it_left);
}
// Right Column
if(!CliCommandTree_end_p(it_right)) {
printf("%s", string_get_cstr(*CliCommandTree_ref(it_right)->key_ptr));
CliCommandTree_next(it_right);
}
};
if(string_size(args) > 0) {
cli_nl();
printf("Also I have no clue what '");
printf("%s", string_get_cstr(args));
printf("' is.");
}
}
void cli_command_date(Cli* cli, string_t args, void* context) {
UNUSED(cli);
UNUSED(context);
FuriHalRtcDateTime datetime = {0};
if(string_size(args) > 0) {
uint16_t hours, minutes, seconds, month, day, year, weekday;
int ret = sscanf(
string_get_cstr(args),
"%hu-%hu-%hu %hu:%hu:%hu %hu",
&year,
&month,
&day,
&hours,
&minutes,
&seconds,
&weekday);
// Some variables are going to discard upper byte
// There will be some funky behaviour which is not breaking anything
datetime.hour = hours;
datetime.minute = minutes;
datetime.second = seconds;
datetime.weekday = weekday;
datetime.month = month;
datetime.day = day;
datetime.year = year;
if(ret != 7) {
printf(
"Invalid datetime format, use `%s`. sscanf %d %s",
"%Y-%m-%d %H:%M:%S %u",
ret,
string_get_cstr(args));
return;
}
if(!furi_hal_rtc_validate_datetime(&datetime)) {
printf("Invalid datetime data");
return;
}
furi_hal_rtc_set_datetime(&datetime);
// Verification
furi_hal_rtc_get_datetime(&datetime);
printf(
"New datetime is: " CLI_DATE_FORMAT,
datetime.year,
datetime.month,
datetime.day,
datetime.hour,
datetime.minute,
datetime.second,
datetime.weekday);
} else {
furi_hal_rtc_get_datetime(&datetime);
printf(
CLI_DATE_FORMAT,
datetime.year,
datetime.month,
datetime.day,
datetime.hour,
datetime.minute,
datetime.second,
datetime.weekday);
}
}
#define CLI_COMMAND_LOG_RING_SIZE 2048
#define CLI_COMMAND_LOG_BUFFER_SIZE 64
void cli_command_log_tx_callback(const uint8_t* buffer, size_t size, void* context) {
xStreamBufferSend(context, buffer, size, 0);
}
void cli_command_log(Cli* cli, string_t args, void* context) {
UNUSED(args);
UNUSED(context);
StreamBufferHandle_t ring = xStreamBufferCreate(CLI_COMMAND_LOG_RING_SIZE, 1);
uint8_t buffer[CLI_COMMAND_LOG_BUFFER_SIZE];
furi_hal_console_set_tx_callback(cli_command_log_tx_callback, ring);
printf("Press CTRL+C to stop...\r\n");
while(!cli_cmd_interrupt_received(cli)) {
size_t ret = xStreamBufferReceive(ring, buffer, CLI_COMMAND_LOG_BUFFER_SIZE, 50);
cli_write(cli, buffer, ret);
}
furi_hal_console_set_tx_callback(NULL, NULL);
vStreamBufferDelete(ring);
}
void cli_command_vibro(Cli* cli, string_t args, void* context) {
UNUSED(cli);
UNUSED(context);
if(!string_cmp(args, "0")) {
NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
notification_message_block(notification, &sequence_reset_vibro);
furi_record_close(RECORD_NOTIFICATION);
} else if(!string_cmp(args, "1")) {
NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
notification_message_block(notification, &sequence_set_vibro_on);
furi_record_close(RECORD_NOTIFICATION);
} else {
cli_print_usage("vibro", "<1|0>", string_get_cstr(args));
}
}
void cli_command_debug(Cli* cli, string_t args, void* context) {
UNUSED(cli);
UNUSED(context);
if(!string_cmp(args, "0")) {
furi_hal_rtc_reset_flag(FuriHalRtcFlagDebug);
loader_update_menu();
printf("Debug disabled.");
} else if(!string_cmp(args, "1")) {
furi_hal_rtc_set_flag(FuriHalRtcFlagDebug);
loader_update_menu();
printf("Debug enabled.");
} else {
cli_print_usage("debug", "<1|0>", string_get_cstr(args));
}
}
void cli_command_led(Cli* cli, string_t args, void* context) {
UNUSED(cli);
UNUSED(context);
// Get first word as light name
NotificationMessage notification_led_message;
string_t light_name;
string_init(light_name);
size_t ws = string_search_char(args, ' ');
if(ws == STRING_FAILURE) {
cli_print_usage("led", "<r|g|b|bl> <0-255>", string_get_cstr(args));
string_clear(light_name);
return;
} else {
string_set_n(light_name, args, 0, ws);
string_right(args, ws);
string_strim(args);
}
// Check light name
if(!string_cmp(light_name, "r")) {
notification_led_message.type = NotificationMessageTypeLedRed;
} else if(!string_cmp(light_name, "g")) {
notification_led_message.type = NotificationMessageTypeLedGreen;
} else if(!string_cmp(light_name, "b")) {
notification_led_message.type = NotificationMessageTypeLedBlue;
} else if(!string_cmp(light_name, "bl")) {
notification_led_message.type = NotificationMessageTypeLedDisplayBacklight;
} else {
cli_print_usage("led", "<r|g|b|bl> <0-255>", string_get_cstr(args));
string_clear(light_name);
return;
}
string_clear(light_name);
// Read light value from the rest of the string
char* end_ptr;
uint32_t value = strtoul(string_get_cstr(args), &end_ptr, 0);
if(!(value < 256 && *end_ptr == '\0')) {
cli_print_usage("led", "<r|g|b|bl> <0-255>", string_get_cstr(args));
return;
}
// Set led value
notification_led_message.data.led.value = value;
// Form notification sequence
const NotificationSequence notification_sequence = {
&notification_led_message,
NULL,
};
// Send notification
NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
notification_internal_message_block(notification, &notification_sequence);
furi_record_close(RECORD_NOTIFICATION);
}
void cli_command_ps(Cli* cli, string_t args, void* context) {
UNUSED(cli);
UNUSED(args);
UNUSED(context);
const uint8_t threads_num_max = 32;
FuriThreadId threads_ids[threads_num_max];
uint8_t thread_num = furi_thread_enumerate(threads_ids, threads_num_max);
printf(
"%-20s %-14s %-8s %-8s %s\r\n", "Name", "Stack start", "Heap", "Stack", "Stack min free");
for(uint8_t i = 0; i < thread_num; i++) {
TaskControlBlock* tcb = (TaskControlBlock*)threads_ids[i];
printf(
"%-20s 0x%-12lx %-8d %-8ld %-8ld\r\n",
furi_thread_get_name(threads_ids[i]),
(uint32_t)tcb->pxStack,
memmgr_heap_get_thread_memory(threads_ids[i]),
(uint32_t)(tcb->pxEndOfStack - tcb->pxStack + 1) * sizeof(StackType_t),
furi_thread_get_stack_space(threads_ids[i]));
}
printf("\r\nTotal: %d", thread_num);
}
void cli_command_free(Cli* cli, string_t args, void* context) {
UNUSED(cli);
UNUSED(args);
UNUSED(context);
printf("Free heap size: %d\r\n", memmgr_get_free_heap());
printf("Total heap size: %d\r\n", memmgr_get_total_heap());
printf("Minimum heap size: %d\r\n", memmgr_get_minimum_free_heap());
printf("Maximum heap block: %d\r\n", memmgr_heap_get_max_free_block());
printf("Pool free: %d\r\n", memmgr_pool_get_free());
printf("Maximum pool block: %d\r\n", memmgr_pool_get_max_block());
}
void cli_command_free_blocks(Cli* cli, string_t args, void* context) {
UNUSED(cli);
UNUSED(args);
UNUSED(context);
memmgr_heap_printf_free_blocks();
}
void cli_command_i2c(Cli* cli, string_t args, void* context) {
UNUSED(cli);
UNUSED(args);
UNUSED(context);
furi_hal_i2c_acquire(&furi_hal_i2c_handle_external);
printf("Scanning external i2c on PC0(SCL)/PC1(SDA)\r\n"
"Clock: 100khz, 7bit address\r\n"
"\r\n");
printf(" | 0 1 2 3 4 5 6 7 8 9 A B C D E F\r\n");
printf("--+--------------------------------\r\n");
for(uint8_t row = 0; row < 0x8; row++) {
printf("%x | ", row);
for(uint8_t column = 0; column <= 0xF; column++) {
bool ret = furi_hal_i2c_is_device_ready(
&furi_hal_i2c_handle_external, ((row << 4) + column) << 1, 2);
printf("%c ", ret ? '#' : '-');
}
printf("\r\n");
}
furi_hal_i2c_release(&furi_hal_i2c_handle_external);
}
void cli_commands_init(Cli* cli) {
cli_add_command(cli, "!", CliCommandFlagParallelSafe, cli_command_device_info, NULL);
cli_add_command(cli, "device_info", CliCommandFlagParallelSafe, cli_command_device_info, NULL);
cli_add_command(cli, "?", CliCommandFlagParallelSafe, cli_command_help, NULL);
cli_add_command(cli, "help", CliCommandFlagParallelSafe, cli_command_help, 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, "debug", CliCommandFlagDefault, cli_command_debug, NULL);
cli_add_command(cli, "ps", CliCommandFlagParallelSafe, cli_command_ps, 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);
}

View File

@@ -0,0 +1,5 @@
#pragma once
#include "cli_i.h"
void cli_commands_init(Cli* cli);

View File

@@ -0,0 +1,67 @@
#pragma once
#include "cli.h"
#include <furi.h>
#include <furi_hal.h>
#include <m-dict.h>
#include <m-bptree.h>
#include <m-array.h>
#include "cli_vcp.h"
#define CLI_LINE_SIZE_MAX
#define CLI_COMMANDS_TREE_RANK 4
#ifdef __cplusplus
extern "C" {
#endif
typedef struct {
CliCallback callback;
void* context;
uint32_t flags;
} CliCommand;
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)(const char* data, size_t size);
bool (*is_connected)(void);
};
BPTREE_DEF2(
CliCommandTree,
CLI_COMMANDS_TREE_RANK,
string_t,
STRING_OPLIST,
CliCommand,
M_POD_OPLIST)
#define M_OPL_CliCommandTree_t() BPTREE_OPLIST(CliCommandTree, M_POD_OPLIST)
struct Cli {
CliCommandTree_t commands;
FuriMutex* mutex;
FuriSemaphore* idle_sem;
string_t last_line;
string_t line;
CliSession* session;
size_t cursor_position;
};
Cli* cli_alloc();
void cli_reset(Cli* cli);
void cli_putc(Cli* cli, char c);
void cli_stdout_callback(void* _cookie, const char* data, size_t size);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,326 @@
#include <furi_hal_usb_cdc.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() {
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 int32_t vcp_worker(void* context) {
UNUSED(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 =
furi_thread_flags_wait(VCP_THREAD_FLAG_ALL, FuriFlagWaitAny, FuriWaitForever);
furi_assert((flags & FuriFlagError) == 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, FuriWaitForever);
}
}
// 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, FuriWaitForever);
}
}
// 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, FuriWaitForever) ==
(size_t)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_unlock();
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, FuriWaitForever);
break;
}
}
FURI_LOG_D(TAG, "End");
return 0;
}
static size_t cli_vcp_rx(uint8_t* buffer, size_t size, uint32_t timeout) {
furi_assert(vcp);
furi_assert(buffer);
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;
furi_thread_flags_set(furi_thread_get_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, FuriWaitForever);
furi_thread_flags_set(furi_thread_get_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(const char* data, size_t size) {
cli_vcp_tx((const uint8_t*)data, size);
}
static void vcp_state_callback(void* context, uint8_t state) {
UNUSED(context);
if(state == 0) {
furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtDisconnect);
}
}
static void vcp_on_cdc_control_line(void* context, uint8_t state) {
UNUSED(context);
// bit 0: DTR state, bit 1: RTS state
bool dtr = state & (1 << 0);
if(dtr == true) {
furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtConnect);
} else {
furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtDisconnect);
}
}
static void vcp_on_cdc_rx(void* context) {
UNUSED(context);
uint32_t ret = furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtRx);
furi_check((ret & FuriFlagError) == 0);
}
static void vcp_on_cdc_tx_complete(void* context) {
UNUSED(context);
furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtTx);
}
static bool cli_vcp_is_connected(void) {
furi_assert(vcp);
return vcp->connected;
}
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
#ifdef __cplusplus
extern "C" {
#endif
typedef struct CliSession CliSession;
extern CliSession cli_vcp;
#ifdef __cplusplus
}
#endif