Merge remote-tracking branch 'OFW/dev' into dev

This commit is contained in:
MX
2025-04-06 01:54:22 +03:00
135 changed files with 4630 additions and 2352 deletions

View File

@@ -3,6 +3,7 @@ App(
name="Basic services",
apptype=FlipperAppType.METAPACKAGE,
provides=[
"cli_vcp",
"crypto_start",
"rpc_start",
"expansion_start",

View File

@@ -1,15 +1,17 @@
#include <furi.h>
#include <furi_hal.h>
#include <cli/cli.h>
#include <lib/toolbox/args.h>
#include <toolbox/pipe.h>
#include <cli/cli_main_commands.h>
#include <toolbox/cli/cli_registry.h>
#include <ble/ble.h>
#include "bt_settings.h"
#include "bt_service/bt.h"
#include <profiles/serial_profile.h>
static void bt_cli_command_hci_info(Cli* cli, FuriString* args, void* context) {
UNUSED(cli);
static void bt_cli_command_hci_info(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(pipe);
UNUSED(args);
UNUSED(context);
FuriString* buffer;
@@ -19,7 +21,7 @@ static void bt_cli_command_hci_info(Cli* cli, FuriString* args, void* context) {
furi_string_free(buffer);
}
static void bt_cli_command_carrier_tx(Cli* cli, FuriString* args, void* context) {
static void bt_cli_command_carrier_tx(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(context);
int channel = 0;
int power = 0;
@@ -41,7 +43,7 @@ static void bt_cli_command_carrier_tx(Cli* cli, FuriString* args, void* context)
printf("Press CTRL+C to stop\r\n");
furi_hal_bt_start_tone_tx(channel, 0x19 + power);
while(!cli_cmd_interrupt_received(cli)) {
while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) {
furi_delay_ms(250);
}
furi_hal_bt_stop_tone_tx();
@@ -51,7 +53,7 @@ static void bt_cli_command_carrier_tx(Cli* cli, FuriString* args, void* context)
} while(false);
}
static void bt_cli_command_carrier_rx(Cli* cli, FuriString* args, void* context) {
static void bt_cli_command_carrier_rx(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(context);
int channel = 0;
@@ -69,7 +71,7 @@ static void bt_cli_command_carrier_rx(Cli* cli, FuriString* args, void* context)
furi_hal_bt_start_packet_rx(channel, 1);
while(!cli_cmd_interrupt_received(cli)) {
while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) {
furi_delay_ms(250);
printf("RSSI: %6.1f dB\r", (double)furi_hal_bt_get_rssi());
fflush(stdout);
@@ -82,7 +84,7 @@ static void bt_cli_command_carrier_rx(Cli* cli, FuriString* args, void* context)
} while(false);
}
static void bt_cli_command_packet_tx(Cli* cli, FuriString* args, void* context) {
static void bt_cli_command_packet_tx(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(context);
int channel = 0;
int pattern = 0;
@@ -119,7 +121,7 @@ static void bt_cli_command_packet_tx(Cli* cli, FuriString* args, void* context)
printf("Press CTRL+C to stop\r\n");
furi_hal_bt_start_packet_tx(channel, pattern, datarate);
while(!cli_cmd_interrupt_received(cli)) {
while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) {
furi_delay_ms(250);
}
furi_hal_bt_stop_packet_test();
@@ -130,7 +132,7 @@ static void bt_cli_command_packet_tx(Cli* cli, FuriString* args, void* context)
} while(false);
}
static void bt_cli_command_packet_rx(Cli* cli, FuriString* args, void* context) {
static void bt_cli_command_packet_rx(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(context);
int channel = 0;
int datarate = 1;
@@ -152,7 +154,7 @@ static void bt_cli_command_packet_rx(Cli* cli, FuriString* args, void* context)
printf("Press CTRL+C to stop\r\n");
furi_hal_bt_start_packet_rx(channel, datarate);
while(!cli_cmd_interrupt_received(cli)) {
while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) {
furi_delay_ms(250);
printf("RSSI: %03.1f dB\r", (double)furi_hal_bt_get_rssi());
fflush(stdout);
@@ -179,7 +181,7 @@ static void bt_cli_print_usage(void) {
}
}
static void bt_cli(Cli* cli, FuriString* args, void* context) {
static void bt_cli(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(context);
furi_record_open(RECORD_BT);
@@ -194,24 +196,24 @@ static void bt_cli(Cli* cli, FuriString* args, void* context) {
break;
}
if(furi_string_cmp_str(cmd, "hci_info") == 0) {
bt_cli_command_hci_info(cli, args, NULL);
bt_cli_command_hci_info(pipe, args, NULL);
break;
}
if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug) && furi_hal_bt_is_testing_supported()) {
if(furi_string_cmp_str(cmd, "tx_carrier") == 0) {
bt_cli_command_carrier_tx(cli, args, NULL);
bt_cli_command_carrier_tx(pipe, args, NULL);
break;
}
if(furi_string_cmp_str(cmd, "rx_carrier") == 0) {
bt_cli_command_carrier_rx(cli, args, NULL);
bt_cli_command_carrier_rx(pipe, args, NULL);
break;
}
if(furi_string_cmp_str(cmd, "tx_packet") == 0) {
bt_cli_command_packet_tx(cli, args, NULL);
bt_cli_command_packet_tx(pipe, args, NULL);
break;
}
if(furi_string_cmp_str(cmd, "rx_packet") == 0) {
bt_cli_command_packet_rx(cli, args, NULL);
bt_cli_command_packet_rx(pipe, args, NULL);
break;
}
}
@@ -229,8 +231,8 @@ static void bt_cli(Cli* cli, FuriString* args, void* context) {
void bt_on_system_start(void) {
#ifdef SRV_CLI
Cli* cli = furi_record_open(RECORD_CLI);
cli_add_command(cli, RECORD_BT, CliCommandFlagDefault, bt_cli, NULL);
CliRegistry* registry = furi_record_open(RECORD_CLI);
cli_registry_add_command(registry, "bt", CliCommandFlagDefault, bt_cli, NULL);
furi_record_close(RECORD_CLI);
#else
UNUSED(bt_cli);

View File

@@ -1,10 +1,51 @@
App(
appid="cli",
name="CliSrv",
apptype=FlipperAppType.SERVICE,
entry_point="cli_srv",
apptype=FlipperAppType.STARTUP,
entry_point="cli_on_system_start",
cdefines=["SRV_CLI"],
stack_size=4 * 1024,
order=30,
sdk_headers=["cli.h", "cli_vcp.h"],
sources=[
"cli_command_gpio.c",
"cli_main_commands.c",
"cli_main_shell.c",
],
# This STARTUP has to be processed before those that depend on the "cli" record.
# "cli" used to be a SERVICE, but it's been converted into a STARTUP in order to
# reduce RAM usage. The "block until record has been created" mechanism
# unfortunately leads to a deadlock if the STARTUPs are processed sequentially.
order=0,
)
App(
appid="cli_vcp",
name="CliVcpSrv",
apptype=FlipperAppType.SERVICE,
entry_point="cli_vcp_srv",
stack_size=1024,
order=40,
sdk_headers=["cli_vcp.h"],
sources=["cli_vcp.c"],
)
App(
appid="cli_hello_world",
apptype=FlipperAppType.PLUGIN,
entry_point="cli_hello_world_ep",
requires=["cli"],
sources=["commands/hello_world.c"],
)
App(
appid="cli_neofetch",
apptype=FlipperAppType.PLUGIN,
entry_point="cli_neofetch_ep",
requires=["cli"],
sources=["commands/neofetch.c"],
)
App(
appid="cli_subshell_demo",
apptype=FlipperAppType.PLUGIN,
entry_point="cli_subshell_demo_ep",
requires=["cli"],
sources=["commands/subshell_demo.c"],
)

View File

@@ -1,484 +0,0 @@
#include "cli_i.h"
#include "cli_commands.h"
#include "cli_vcp.h"
#include <furi_hal_version.h>
#include <loader/loader.h>
#define TAG "CliSrv"
#define CLI_INPUT_LEN_LIMIT 256
Cli* cli_alloc(void) {
Cli* cli = malloc(sizeof(Cli));
CliCommandTree_init(cli->commands);
cli->last_line = furi_string_alloc();
cli->line = furi_string_alloc();
cli->session = NULL;
cli->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
cli->idle_sem = furi_semaphore_alloc(1, 0);
return cli;
}
void cli_putc(Cli* cli, char c) {
furi_check(cli);
if(cli->session != NULL) {
cli->session->tx((uint8_t*)&c, 1);
}
}
char cli_getc(Cli* cli) {
furi_check(cli);
char c = 0;
if(cli->session != NULL) {
if(cli->session->rx((uint8_t*)&c, 1, FuriWaitForever) == 0) {
cli_reset(cli);
furi_delay_tick(10);
}
} else {
cli_reset(cli);
furi_delay_tick(10);
}
return c;
}
void cli_write(Cli* cli, const uint8_t* buffer, size_t size) {
furi_check(cli);
if(cli->session != NULL) {
cli->session->tx(buffer, size);
}
}
size_t cli_read(Cli* cli, uint8_t* buffer, size_t size) {
furi_check(cli);
if(cli->session != NULL) {
return cli->session->rx(buffer, size, FuriWaitForever);
} else {
return 0;
}
}
size_t cli_read_timeout(Cli* cli, uint8_t* buffer, size_t size, uint32_t timeout) {
furi_check(cli);
if(cli->session != NULL) {
return cli->session->rx(buffer, size, timeout);
} else {
return 0;
}
}
bool cli_is_connected(Cli* cli) {
furi_check(cli);
if(cli->session != NULL) {
return cli->session->is_connected();
}
return false;
}
bool cli_cmd_interrupt_received(Cli* cli) {
furi_check(cli);
char c = '\0';
if(cli_is_connected(cli)) {
if(cli->session->rx((uint8_t*)&c, 1, 0) == 1) {
return c == CliSymbolAsciiETX;
}
} else {
return true;
}
return false;
}
void cli_print_usage(const char* cmd, const char* usage, const char* arg) {
furi_check(cmd);
furi_check(arg);
furi_check(usage);
printf("%s: illegal option -- %s\r\nusage: %s %s", cmd, arg, cmd, usage);
}
void cli_motd(void) {
printf("\r\n"
" _.-------.._ -,\r\n"
" .-\"```\"--..,,_/ /`-, -, \\ \r\n"
" .:\" /:/ /'\\ \\ ,_..., `. | |\r\n"
" / ,----/:/ /`\\ _\\~`_-\"` _;\r\n"
" ' / /`\"\"\"'\\ \\ \\.~`_-' ,-\"'/ \r\n"
" | | | 0 | | .-' ,/` /\r\n"
" | ,..\\ \\ ,.-\"` ,/` /\r\n"
" ; : `/`\"\"\\` ,/--==,/-----,\r\n"
" | `-...| -.___-Z:_______J...---;\r\n"
" : ` _-'\r\n"
" _L_ _ ___ ___ ___ ___ ____--\"`___ _ ___\r\n"
"| __|| | |_ _|| _ \\| _ \\| __|| _ \\ / __|| | |_ _|\r\n"
"| _| | |__ | | | _/| _/| _| | / | (__ | |__ | |\r\n"
"|_| |____||___||_| |_| |___||_|_\\ \\___||____||___|\r\n"
"\r\n"
"Welcome to Flipper Zero Command Line Interface!\r\n"
"Read the manual: https://docs.flipper.net/development/cli\r\n"
"Run `help` or `?` to list available commands\r\n"
"\r\n");
const Version* firmware_version = furi_hal_version_get_firmware_version();
if(firmware_version) {
printf(
"Firmware version: %s %s (%s%s built on %s)\r\n",
version_get_gitbranch(firmware_version),
version_get_version(firmware_version),
version_get_githash(firmware_version),
version_get_dirty_flag(firmware_version) ? "-dirty" : "",
version_get_builddate(firmware_version));
}
}
void cli_nl(Cli* cli) {
UNUSED(cli);
printf("\r\n");
}
void cli_prompt(Cli* cli) {
UNUSED(cli);
printf("\r\n>: %s", furi_string_get_cstr(cli->line));
fflush(stdout);
}
void cli_reset(Cli* cli) {
// cli->last_line is cleared and cli->line's buffer moved to cli->last_line
furi_string_move(cli->last_line, cli->line);
// Reiniting cli->line
cli->line = furi_string_alloc();
cli->cursor_position = 0;
}
static void cli_handle_backspace(Cli* cli) {
if(cli->cursor_position > 0) {
furi_assert(furi_string_size(cli->line) > 0);
// Other side
printf("\e[D\e[1P");
fflush(stdout);
// Our side
furi_string_replace_at(cli->line, cli->cursor_position - 1, 1, "");
cli->cursor_position--;
} else {
cli_putc(cli, CliSymbolAsciiBell);
}
}
static void cli_normalize_line(Cli* cli) {
furi_string_trim(cli->line);
cli->cursor_position = furi_string_size(cli->line);
}
static void cli_execute_command(Cli* cli, CliCommand* command, FuriString* args) {
if(!(command->flags & CliCommandFlagInsomniaSafe)) {
furi_hal_power_insomnia_enter();
}
// Ensure that we running alone
if(!(command->flags & CliCommandFlagParallelSafe)) {
Loader* loader = furi_record_open(RECORD_LOADER);
bool safety_lock = loader_lock(loader);
if(safety_lock) {
// Execute command
command->callback(cli, args, command->context);
loader_unlock(loader);
} else {
printf("Other application is running, close it first");
}
furi_record_close(RECORD_LOADER);
} else {
// Execute command
command->callback(cli, args, command->context);
}
if(!(command->flags & CliCommandFlagInsomniaSafe)) {
furi_hal_power_insomnia_exit();
}
}
static void cli_handle_enter(Cli* cli) {
cli_normalize_line(cli);
if(furi_string_size(cli->line) == 0) {
cli_prompt(cli);
return;
}
// Command and args container
FuriString* command;
command = furi_string_alloc();
FuriString* args;
args = furi_string_alloc();
// Split command and args
size_t ws = furi_string_search_char(cli->line, ' ');
if(ws == FURI_STRING_FAILURE) {
furi_string_set(command, cli->line);
} else {
furi_string_set_n(command, cli->line, 0, ws);
furi_string_set_n(args, cli->line, ws, furi_string_size(cli->line));
furi_string_trim(args);
}
// Search for command
furi_check(furi_mutex_acquire(cli->mutex, FuriWaitForever) == FuriStatusOk);
CliCommand* cli_command_ptr = CliCommandTree_get(cli->commands, command);
if(cli_command_ptr) { //-V547
CliCommand cli_command;
memcpy(&cli_command, cli_command_ptr, sizeof(CliCommand));
furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk);
cli_nl(cli);
cli_execute_command(cli, &cli_command, args);
} else {
furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk);
cli_nl(cli);
printf(
"`%s` command not found, use `help` or `?` to list all available commands",
furi_string_get_cstr(command));
cli_putc(cli, CliSymbolAsciiBell);
}
cli_reset(cli);
cli_prompt(cli);
// Cleanup command and args
furi_string_free(command);
furi_string_free(args);
}
static void cli_handle_autocomplete(Cli* cli) {
cli_normalize_line(cli);
if(furi_string_size(cli->line) == 0) {
return;
}
cli_nl(cli);
// Prepare common base for autocomplete
FuriString* common;
common = furi_string_alloc();
// Iterate throw commands
for
M_EACH(cli_command, cli->commands, CliCommandTree_t) {
// Process only if starts with line buffer
if(furi_string_start_with(*cli_command->key_ptr, cli->line)) {
// Show autocomplete option
printf("%s\r\n", furi_string_get_cstr(*cli_command->key_ptr));
// Process common base for autocomplete
if(furi_string_size(common) > 0) {
// Choose shortest string
const size_t key_size = furi_string_size(*cli_command->key_ptr);
const size_t common_size = furi_string_size(common);
const size_t min_size = key_size > common_size ? common_size : key_size;
size_t i = 0;
while(i < min_size) {
// Stop when do not match
if(furi_string_get_char(*cli_command->key_ptr, i) !=
furi_string_get_char(common, i)) {
break;
}
i++;
}
// Cut right part if any
furi_string_left(common, i);
} else {
// Start with something
furi_string_set(common, *cli_command->key_ptr);
}
}
}
// Replace line buffer if autocomplete better
if(furi_string_size(common) > furi_string_size(cli->line)) {
furi_string_set(cli->line, common);
cli->cursor_position = furi_string_size(cli->line);
}
// Cleanup
furi_string_free(common);
// Show prompt
cli_prompt(cli);
}
static void cli_handle_escape(Cli* cli, char c) {
if(c == 'A') {
// Use previous command if line buffer is empty
if(furi_string_size(cli->line) == 0 && furi_string_cmp(cli->line, cli->last_line) != 0) {
// Set line buffer and cursor position
furi_string_set(cli->line, cli->last_line);
cli->cursor_position = furi_string_size(cli->line);
// Show new line to user
printf("%s", furi_string_get_cstr(cli->line));
}
} else if(c == 'B') {
} else if(c == 'C') {
if(cli->cursor_position < furi_string_size(cli->line)) {
cli->cursor_position++;
printf("\e[C");
}
} else if(c == 'D') {
if(cli->cursor_position > 0) {
cli->cursor_position--;
printf("\e[D");
}
}
fflush(stdout);
}
void cli_process_input(Cli* cli) {
char in_chr = cli_getc(cli);
size_t rx_len;
if(in_chr == CliSymbolAsciiTab) {
cli_handle_autocomplete(cli);
} else if(in_chr == CliSymbolAsciiSOH) {
furi_delay_ms(33); // We are too fast, Minicom is not ready yet
cli_motd();
cli_prompt(cli);
} else if(in_chr == CliSymbolAsciiETX) {
cli_reset(cli);
cli_prompt(cli);
} else if(in_chr == CliSymbolAsciiEOT) {
cli_reset(cli);
} else if(in_chr == CliSymbolAsciiEsc) {
rx_len = cli_read(cli, (uint8_t*)&in_chr, 1);
if((rx_len > 0) && (in_chr == '[')) {
cli_read(cli, (uint8_t*)&in_chr, 1);
cli_handle_escape(cli, in_chr);
} else {
cli_putc(cli, CliSymbolAsciiBell);
}
} else if(in_chr == CliSymbolAsciiBackspace || in_chr == CliSymbolAsciiDel) {
cli_handle_backspace(cli);
} else if(in_chr == CliSymbolAsciiCR) {
cli_handle_enter(cli);
} else if(
(in_chr >= 0x20 && in_chr < 0x7F) && //-V560
(furi_string_size(cli->line) < CLI_INPUT_LEN_LIMIT)) {
if(cli->cursor_position == furi_string_size(cli->line)) {
furi_string_push_back(cli->line, in_chr);
cli_putc(cli, in_chr);
} else {
// Insert character to line buffer
const char in_str[2] = {in_chr, 0};
furi_string_replace_at(cli->line, cli->cursor_position, 0, in_str);
// Print character in replace mode
printf("\e[4h%c\e[4l", in_chr);
fflush(stdout);
}
cli->cursor_position++;
} else {
cli_putc(cli, CliSymbolAsciiBell);
}
}
void cli_add_command(
Cli* cli,
const char* name,
CliCommandFlag flags,
CliCallback callback,
void* context) {
furi_check(cli);
FuriString* name_str;
name_str = furi_string_alloc_set(name);
furi_string_trim(name_str);
size_t name_replace;
do {
name_replace = furi_string_replace(name_str, " ", "_");
} while(name_replace != FURI_STRING_FAILURE);
CliCommand c;
c.callback = callback;
c.context = context;
c.flags = flags;
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);
furi_string_free(name_str);
}
void cli_delete_command(Cli* cli, const char* name) {
furi_check(cli);
FuriString* name_str;
name_str = furi_string_alloc_set(name);
furi_string_trim(name_str);
size_t name_replace;
do {
name_replace = furi_string_replace(name_str, " ", "_");
} while(name_replace != FURI_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);
furi_string_free(name_str);
}
void cli_session_open(Cli* cli, const void* session) {
furi_check(cli);
furi_check(furi_mutex_acquire(cli->mutex, FuriWaitForever) == FuriStatusOk);
cli->session = session;
if(cli->session != NULL) {
cli->session->init();
furi_thread_set_stdout_callback(cli->session->tx_stdout, NULL);
} else {
furi_thread_set_stdout_callback(NULL, NULL);
}
furi_semaphore_release(cli->idle_sem);
furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk);
}
void cli_session_close(Cli* cli) {
furi_check(cli);
furi_check(furi_mutex_acquire(cli->mutex, FuriWaitForever) == FuriStatusOk);
if(cli->session != NULL) {
cli->session->deinit();
}
cli->session = NULL;
furi_thread_set_stdout_callback(NULL, NULL);
furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk);
}
int32_t cli_srv(void* p) {
UNUSED(p);
Cli* cli = cli_alloc();
// Init basic cli commands
cli_commands_init(cli);
furi_record_create(RECORD_CLI, cli);
if(cli->session != NULL) {
furi_thread_set_stdout_callback(cli->session->tx_stdout, NULL);
} else {
furi_thread_set_stdout_callback(NULL, NULL);
}
if(furi_hal_rtc_get_boot_mode() == FuriHalRtcBootModeNormal) {
cli_session_open(cli, &cli_vcp);
} else {
FURI_LOG_W(TAG, "Skipping start in special boot mode");
}
while(1) {
if(cli->session != NULL) {
cli_process_input(cli);
} else {
furi_check(furi_semaphore_acquire(cli->idle_sem, FuriWaitForever) == FuriStatusOk);
}
}
return 0;
}

View File

@@ -1,134 +0,0 @@
/**
* @file cli.h
* Cli API
*/
#pragma once
#include <furi.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, FuriString* 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(Cli* cli);
void cli_session_open(Cli* cli, const void* session);
void cli_session_close(Cli* cli);
bool cli_is_connected(Cli* cli);
#ifdef __cplusplus
}
#endif

View File

@@ -3,6 +3,8 @@
#include <furi.h>
#include <furi_hal.h>
#include <lib/toolbox/args.h>
#include <toolbox/pipe.h>
#include <toolbox/cli/cli_command.h>
void cli_command_gpio_print_usage(void) {
printf("Usage:\r\n");
@@ -70,8 +72,8 @@ static GpioParseReturn gpio_command_parse(FuriString* args, size_t* pin_num, uin
return ret;
}
void cli_command_gpio_mode(Cli* cli, FuriString* args, void* context) {
UNUSED(cli);
void cli_command_gpio_mode(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(pipe);
UNUSED(context);
size_t num = 0;
@@ -93,7 +95,7 @@ void cli_command_gpio_mode(Cli* cli, FuriString* args, void* context) {
if(gpio_pins[num].debug) { //-V779
printf(
"Changing this pin mode may damage hardware. Are you sure you want to continue? (y/n)?\r\n");
char c = cli_getc(cli);
char c = getchar();
if(c != 'y' && c != 'Y') {
printf("Cancelled.\r\n");
return;
@@ -110,8 +112,8 @@ void cli_command_gpio_mode(Cli* cli, FuriString* args, void* context) {
}
}
void cli_command_gpio_read(Cli* cli, FuriString* args, void* context) {
UNUSED(cli);
void cli_command_gpio_read(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(pipe);
UNUSED(context);
size_t num = 0;
@@ -131,7 +133,8 @@ void cli_command_gpio_read(Cli* cli, FuriString* args, void* context) {
printf("Pin %s <= %u", gpio_pins[num].name, val);
}
void cli_command_gpio_set(Cli* cli, FuriString* args, void* context) {
void cli_command_gpio_set(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(pipe);
UNUSED(context);
size_t num = 0;
@@ -159,7 +162,7 @@ void cli_command_gpio_set(Cli* cli, FuriString* args, void* context) {
if(gpio_pins[num].debug) {
printf(
"Setting this pin may damage hardware. Are you sure you want to continue? (y/n)?\r\n");
char c = cli_getc(cli);
char c = getchar();
if(c != 'y' && c != 'Y') {
printf("Cancelled.\r\n");
return;
@@ -170,7 +173,7 @@ void cli_command_gpio_set(Cli* cli, FuriString* args, void* context) {
printf("Pin %s => %u", gpio_pins[num].name, !!value);
}
void cli_command_gpio(Cli* cli, FuriString* args, void* context) {
void cli_command_gpio(PipeSide* pipe, FuriString* args, void* context) {
FuriString* cmd;
cmd = furi_string_alloc();
@@ -181,17 +184,17 @@ void cli_command_gpio(Cli* cli, FuriString* args, void* context) {
}
if(furi_string_cmp_str(cmd, "mode") == 0) {
cli_command_gpio_mode(cli, args, context);
cli_command_gpio_mode(pipe, args, context);
break;
}
if(furi_string_cmp_str(cmd, "set") == 0) {
cli_command_gpio_set(cli, args, context);
cli_command_gpio_set(pipe, args, context);
break;
}
if(furi_string_cmp_str(cmd, "read") == 0) {
cli_command_gpio_read(cli, args, context);
cli_command_gpio_read(pipe, args, context);
break;
}

View File

@@ -1,5 +1,5 @@
#pragma once
#include "cli_i.h"
#include <toolbox/pipe.h>
void cli_command_gpio(Cli* cli, FuriString* args, void* context);
void cli_command_gpio(PipeSide* pipe, FuriString* args, void* context);

View File

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

View File

@@ -1,68 +0,0 @@
#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);
size_t (*rx_stdin)(uint8_t* buffer, size_t size, uint32_t timeout, void* context);
void (*tx)(const uint8_t* buffer, size_t size);
void (*tx_stdout)(const char* data, size_t size, void* context);
bool (*is_connected)(void);
};
BPTREE_DEF2(
CliCommandTree,
CLI_COMMANDS_TREE_RANK,
FuriString*,
FURI_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;
FuriString* last_line;
FuriString* line;
const CliSession* session;
size_t cursor_position;
};
Cli* cli_alloc(void);
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

@@ -1,5 +1,6 @@
#include "cli_commands.h"
#include "cli_main_commands.h"
#include "cli_command_gpio.h"
#include <toolbox/cli/cli_ansi.h>
#include <core/thread.h>
#include <furi_hal.h>
@@ -11,6 +12,7 @@
#include <loader/loader.h>
#include <lib/toolbox/args.h>
#include <lib/toolbox/strint.h>
#include <toolbox/pipe.h>
// Close to ISO, `date +'%Y-%m-%d %H:%M:%S %u'`
#define CLI_DATE_FORMAT "%.4d-%.2d-%.2d %.2d:%.2d:%.2d %d"
@@ -34,8 +36,8 @@ void cli_command_info_callback(const char* key, const char* value, bool last, vo
* @param args The arguments
* @param context The context
*/
void cli_command_info(Cli* cli, FuriString* args, void* context) {
UNUSED(cli);
void cli_command_info(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(pipe);
if(context) {
furi_hal_info_get(cli_command_info_callback, '_', NULL);
@@ -53,56 +55,16 @@ void cli_command_info(Cli* cli, FuriString* args, void* context) {
}
}
void cli_command_help(Cli* cli, FuriString* args, void* context) {
UNUSED(args);
UNUSED(context);
printf("Commands available:");
// Command count
const size_t commands_count = CliCommandTree_size(cli->commands);
const size_t commands_count_mid = commands_count / 2 + commands_count % 2;
// Use 2 iterators from start and middle to show 2 columns
CliCommandTree_it_t it_left;
CliCommandTree_it(it_left, cli->commands);
CliCommandTree_it_t it_right;
CliCommandTree_it(it_right, cli->commands);
for(size_t i = 0; i < commands_count_mid; i++)
CliCommandTree_next(it_right);
// Iterate throw tree
for(size_t i = 0; i < commands_count_mid; i++) {
printf("\r\n");
// Left Column
if(!CliCommandTree_end_p(it_left)) {
printf("%-30s", furi_string_get_cstr(*CliCommandTree_ref(it_left)->key_ptr));
CliCommandTree_next(it_left);
}
// Right Column
if(!CliCommandTree_end_p(it_right)) {
printf("%s", furi_string_get_cstr(*CliCommandTree_ref(it_right)->key_ptr));
CliCommandTree_next(it_right);
}
};
if(furi_string_size(args) > 0) {
cli_nl(cli);
printf("`");
printf("%s", furi_string_get_cstr(args));
printf("` command not found");
}
}
void cli_command_uptime(Cli* cli, FuriString* args, void* context) {
UNUSED(cli);
void cli_command_uptime(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(pipe);
UNUSED(args);
UNUSED(context);
uint32_t uptime = furi_get_tick() / furi_kernel_get_tick_frequency();
printf("Uptime: %luh%lum%lus", uptime / 60 / 60, uptime / 60 % 60, uptime % 60);
}
void cli_command_date(Cli* cli, FuriString* args, void* context) {
UNUSED(cli);
void cli_command_date(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(pipe);
UNUSED(context);
DateTime datetime = {0};
@@ -170,21 +132,12 @@ void cli_command_date(Cli* cli, FuriString* args, void* context) {
}
}
void cli_command_src(Cli* cli, FuriString* args, void* context) {
// Quality of life feature for people exploring CLI on lab.flipper.net/cli
// By Yousef AK
UNUSED(cli);
UNUSED(args);
UNUSED(context);
printf("https://github.com/DarkFlippers/unleashed-firmware");
}
#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) {
furi_stream_buffer_send(context, buffer, size, 0);
PipeSide* pipe = context;
pipe_send(pipe, buffer, size);
}
bool cli_command_log_level_set_from_string(FuriString* level) {
@@ -206,16 +159,13 @@ bool cli_command_log_level_set_from_string(FuriString* level) {
return false;
}
void cli_command_log(Cli* cli, FuriString* args, void* context) {
void cli_command_log(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(context);
FuriStreamBuffer* ring = furi_stream_buffer_alloc(CLI_COMMAND_LOG_RING_SIZE, 1);
uint8_t buffer[CLI_COMMAND_LOG_BUFFER_SIZE];
FuriLogLevel previous_level = furi_log_get_level();
bool restore_log_level = false;
if(furi_string_size(args) > 0) {
if(!cli_command_log_level_set_from_string(args)) {
furi_stream_buffer_free(ring);
return;
}
restore_log_level = true;
@@ -227,16 +177,15 @@ void cli_command_log(Cli* cli, FuriString* args, void* context) {
FuriLogHandler log_handler = {
.callback = cli_command_log_tx_callback,
.context = ring,
.context = pipe,
};
furi_log_add_handler(log_handler);
printf("Use <log ?> to list available log levels\r\n");
printf("Press CTRL+C to stop...\r\n");
while(!cli_cmd_interrupt_received(cli)) {
size_t ret = furi_stream_buffer_receive(ring, buffer, CLI_COMMAND_LOG_BUFFER_SIZE, 50);
cli_write(cli, buffer, ret);
while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) {
furi_delay_ms(100);
}
furi_log_remove_handler(log_handler);
@@ -245,12 +194,10 @@ void cli_command_log(Cli* cli, FuriString* args, void* context) {
// There will be strange behaviour if log level is set from settings while log command is running
furi_log_set_level(previous_level);
}
furi_stream_buffer_free(ring);
}
void cli_command_sysctl_debug(Cli* cli, FuriString* args, void* context) {
UNUSED(cli);
void cli_command_sysctl_debug(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(pipe);
UNUSED(context);
if(!furi_string_cmp(args, "0")) {
furi_hal_rtc_reset_flag(FuriHalRtcFlagDebug);
@@ -263,8 +210,8 @@ void cli_command_sysctl_debug(Cli* cli, FuriString* args, void* context) {
}
}
void cli_command_sysctl_heap_track(Cli* cli, FuriString* args, void* context) {
UNUSED(cli);
void cli_command_sysctl_heap_track(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(pipe);
UNUSED(context);
if(!furi_string_cmp(args, "none")) {
furi_hal_rtc_set_heap_track_mode(FuriHalRtcHeapTrackModeNone);
@@ -298,7 +245,7 @@ void cli_command_sysctl_print_usage(void) {
#endif
}
void cli_command_sysctl(Cli* cli, FuriString* args, void* context) {
void cli_command_sysctl(PipeSide* pipe, FuriString* args, void* context) {
FuriString* cmd;
cmd = furi_string_alloc();
@@ -309,12 +256,12 @@ void cli_command_sysctl(Cli* cli, FuriString* args, void* context) {
}
if(furi_string_cmp_str(cmd, "debug") == 0) {
cli_command_sysctl_debug(cli, args, context);
cli_command_sysctl_debug(pipe, args, context);
break;
}
if(furi_string_cmp_str(cmd, "heap_track") == 0) {
cli_command_sysctl_heap_track(cli, args, context);
cli_command_sysctl_heap_track(pipe, args, context);
break;
}
@@ -324,8 +271,8 @@ void cli_command_sysctl(Cli* cli, FuriString* args, void* context) {
furi_string_free(cmd);
}
void cli_command_vibro(Cli* cli, FuriString* args, void* context) {
UNUSED(cli);
void cli_command_vibro(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(pipe);
UNUSED(context);
if(!furi_string_cmp(args, "0")) {
@@ -351,8 +298,8 @@ void cli_command_vibro(Cli* cli, FuriString* args, void* context) {
}
}
void cli_command_led(Cli* cli, FuriString* args, void* context) {
UNUSED(cli);
void cli_command_led(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(pipe);
UNUSED(context);
// Get first word as light name
NotificationMessage notification_led_message;
@@ -406,23 +353,23 @@ void cli_command_led(Cli* cli, FuriString* args, void* context) {
furi_record_close(RECORD_NOTIFICATION);
}
static void cli_command_top(Cli* cli, FuriString* args, void* context) {
UNUSED(cli);
static void cli_command_top(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(context);
int interval = 1000;
args_read_int_and_trim(args, &interval);
FuriThreadList* thread_list = furi_thread_list_alloc();
while(!cli_cmd_interrupt_received(cli)) {
while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) {
uint32_t tick = furi_get_tick();
furi_thread_enumerate(thread_list);
if(interval) printf("\e[2J\e[0;0f"); // Clear display and return to 0
if(interval) printf(ANSI_CURSOR_POS("1", "1"));
uint32_t uptime = tick / furi_kernel_get_tick_frequency();
printf(
"Threads: %zu, ISR Time: %0.2f%%, Uptime: %luh%lum%lus\r\n",
"Threads: %zu, ISR Time: %0.2f%%, Uptime: %luh%lum%lus" ANSI_ERASE_LINE(
ANSI_ERASE_FROM_CURSOR_TO_END) "\r\n",
furi_thread_list_size(thread_list),
(double)furi_thread_list_get_isr_time(thread_list),
uptime / 60 / 60,
@@ -430,14 +377,16 @@ static void cli_command_top(Cli* cli, FuriString* args, void* context) {
uptime % 60);
printf(
"Heap: total %zu, free %zu, minimum %zu, max block %zu\r\n\r\n",
"Heap: total %zu, free %zu, minimum %zu, max block %zu" ANSI_ERASE_LINE(
ANSI_ERASE_FROM_CURSOR_TO_END) "\r\n" ANSI_ERASE_LINE(ANSI_ERASE_FROM_CURSOR_TO_END) "\r\n",
memmgr_get_total_heap(),
memmgr_get_free_heap(),
memmgr_get_minimum_free_heap(),
memmgr_heap_get_max_free_block());
printf(
"%-17s %-20s %-10s %5s %12s %6s %10s %7s %5s\r\n",
"%-17s %-20s %-10s %5s %12s %6s %10s %7s %5s" ANSI_ERASE_LINE(
ANSI_ERASE_FROM_CURSOR_TO_END) "\r\n",
"AppID",
"Name",
"State",
@@ -446,12 +395,13 @@ static void cli_command_top(Cli* cli, FuriString* args, void* context) {
"Stack",
"Stack Min",
"Heap",
"CPU");
"%CPU");
for(size_t i = 0; i < furi_thread_list_size(thread_list); i++) {
const FuriThreadListItem* item = furi_thread_list_get_at(thread_list, i);
printf(
"%-17s %-20s %-10s %5d 0x%08lx %6lu %10lu %7zu %5.1f\r\n",
"%-17s %-20s %-10s %5d 0x%08lx %6lu %10lu %7zu %5.1f" ANSI_ERASE_LINE(
ANSI_ERASE_FROM_CURSOR_TO_END) "\r\n",
item->app_id,
item->name,
item->state,
@@ -463,6 +413,9 @@ static void cli_command_top(Cli* cli, FuriString* args, void* context) {
(double)item->cpu);
}
printf(ANSI_ERASE_DISPLAY(ANSI_ERASE_FROM_CURSOR_TO_END));
fflush(stdout);
if(interval > 0) {
furi_delay_ms(interval);
} else {
@@ -472,8 +425,8 @@ static void cli_command_top(Cli* cli, FuriString* args, void* context) {
furi_thread_list_free(thread_list);
}
void cli_command_free(Cli* cli, FuriString* args, void* context) {
UNUSED(cli);
void cli_command_free(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(pipe);
UNUSED(args);
UNUSED(context);
@@ -486,16 +439,16 @@ void cli_command_free(Cli* cli, FuriString* args, void* context) {
printf("Maximum pool block: %zu\r\n", memmgr_pool_get_max_block());
}
void cli_command_free_blocks(Cli* cli, FuriString* args, void* context) {
UNUSED(cli);
void cli_command_free_blocks(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(pipe);
UNUSED(args);
UNUSED(context);
memmgr_heap_printf_free_blocks();
}
void cli_command_i2c(Cli* cli, FuriString* args, void* context) {
UNUSED(cli);
void cli_command_i2c(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(pipe);
UNUSED(args);
UNUSED(context);
@@ -517,26 +470,53 @@ void cli_command_i2c(Cli* cli, FuriString* args, void* context) {
furi_hal_i2c_release(&furi_hal_i2c_handle_external);
}
void cli_commands_init(Cli* cli) {
cli_add_command(cli, "!", CliCommandFlagParallelSafe, cli_command_info, (void*)true);
cli_add_command(cli, "info", CliCommandFlagParallelSafe, cli_command_info, NULL);
cli_add_command(cli, "device_info", CliCommandFlagParallelSafe, cli_command_info, (void*)true);
cli_add_command(cli, "source", CliCommandFlagParallelSafe, cli_command_src, NULL);
/**
* Echoes any bytes it receives except ASCII ETX (0x03, Ctrl+C)
*/
void cli_command_echo(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(args);
UNUSED(context);
cli_add_command(cli, "?", CliCommandFlagParallelSafe, cli_command_help, NULL);
cli_add_command(cli, "help", CliCommandFlagParallelSafe, cli_command_help, NULL);
uint8_t buffer[256];
cli_add_command(cli, "uptime", CliCommandFlagDefault, cli_command_uptime, NULL);
cli_add_command(cli, "date", CliCommandFlagParallelSafe, cli_command_date, NULL);
cli_add_command(cli, "log", CliCommandFlagParallelSafe, cli_command_log, NULL);
cli_add_command(cli, "l", CliCommandFlagParallelSafe, cli_command_log, NULL);
cli_add_command(cli, "sysctl", CliCommandFlagDefault, cli_command_sysctl, NULL);
cli_add_command(cli, "top", CliCommandFlagParallelSafe, cli_command_top, NULL);
cli_add_command(cli, "free", CliCommandFlagParallelSafe, cli_command_free, NULL);
cli_add_command(cli, "free_blocks", CliCommandFlagParallelSafe, cli_command_free_blocks, NULL);
while(true) {
size_t to_read = CLAMP(pipe_bytes_available(pipe), sizeof(buffer), 1UL);
size_t read = pipe_receive(pipe, buffer, to_read);
if(read < to_read) break;
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);
if(memchr(buffer, CliKeyETX, read)) break;
size_t written = pipe_send(pipe, buffer, read);
if(written < read) break;
}
}
void cli_main_commands_init(CliRegistry* registry) {
cli_registry_add_command(
registry, "!", CliCommandFlagParallelSafe, cli_command_info, (void*)true);
cli_registry_add_command(registry, "info", CliCommandFlagParallelSafe, cli_command_info, NULL);
cli_registry_add_command(
registry, "device_info", CliCommandFlagParallelSafe, cli_command_info, (void*)true);
cli_registry_add_command(
registry, "uptime", CliCommandFlagParallelSafe, cli_command_uptime, NULL);
cli_registry_add_command(registry, "date", CliCommandFlagParallelSafe, cli_command_date, NULL);
cli_registry_add_command(registry, "log", CliCommandFlagParallelSafe, cli_command_log, NULL);
cli_registry_add_command(registry, "sysctl", CliCommandFlagDefault, cli_command_sysctl, NULL);
cli_registry_add_command(registry, "top", CliCommandFlagParallelSafe, cli_command_top, NULL);
cli_registry_add_command(registry, "free", CliCommandFlagParallelSafe, cli_command_free, NULL);
cli_registry_add_command(
registry, "free_blocks", CliCommandFlagDefault, cli_command_free_blocks, NULL);
cli_registry_add_command(registry, "echo", CliCommandFlagParallelSafe, cli_command_echo, NULL);
cli_registry_add_command(registry, "vibro", CliCommandFlagDefault, cli_command_vibro, NULL);
cli_registry_add_command(registry, "led", CliCommandFlagDefault, cli_command_led, NULL);
cli_registry_add_command(registry, "gpio", CliCommandFlagDefault, cli_command_gpio, NULL);
cli_registry_add_command(registry, "i2c", CliCommandFlagDefault, cli_command_i2c, NULL);
}
void cli_on_system_start(void) {
CliRegistry* registry = cli_registry_alloc();
cli_main_commands_init(registry);
furi_record_create(RECORD_CLI, registry);
}

View File

@@ -0,0 +1,9 @@
#pragma once
#include <toolbox/cli/cli_command.h>
#include <toolbox/cli/cli_registry.h>
#define RECORD_CLI "cli"
#define CLI_APPID "cli"
void cli_main_commands_init(CliRegistry* registry);

View File

@@ -0,0 +1,46 @@
#include "cli_main_shell.h"
#include "cli_main_commands.h"
#include <toolbox/cli/cli_ansi.h>
#include <toolbox/cli/shell/cli_shell.h>
#include <furi_hal_version.h>
void cli_main_motd(void* context) {
UNUSED(context);
printf(ANSI_FLIPPER_BRAND_ORANGE
"\r\n"
" _.-------.._ -,\r\n"
" .-\"```\"--..,,_/ /`-, -, \\ \r\n"
" .:\" /:/ /'\\ \\ ,_..., `. | |\r\n"
" / ,----/:/ /`\\ _\\~`_-\"` _;\r\n"
" ' / /`\"\"\"'\\ \\ \\.~`_-' ,-\"'/ \r\n"
" | | | 0 | | .-' ,/` /\r\n"
" | ,..\\ \\ ,.-\"` ,/` /\r\n"
" ; : `/`\"\"\\` ,/--==,/-----,\r\n"
" | `-...| -.___-Z:_______J...---;\r\n"
" : ` _-'\r\n"
" _L_ _ ___ ___ ___ ___ ____--\"`___ _ ___\r\n"
"| __|| | |_ _|| _ \\| _ \\| __|| _ \\ / __|| | |_ _|\r\n"
"| _| | |__ | | | _/| _/| _| | / | (__ | |__ | |\r\n"
"|_| |____||___||_| |_| |___||_|_\\ \\___||____||___|\r\n"
"\r\n" ANSI_FG_BR_WHITE "Welcome to Flipper Zero Command Line Interface!\r\n"
"Read the manual: https://docs.flipper.net/development/cli\r\n"
"Run `help` or `?` to list available commands\r\n"
"\r\n" ANSI_RESET);
const Version* firmware_version = furi_hal_version_get_firmware_version();
if(firmware_version) {
printf(
"Firmware version: %s %s (%s%s built on %s)\r\n",
version_get_gitbranch(firmware_version),
version_get_version(firmware_version),
version_get_githash(firmware_version),
version_get_dirty_flag(firmware_version) ? "-dirty" : "",
version_get_builddate(firmware_version));
}
}
const CliCommandExternalConfig cli_main_ext_config = {
.search_directory = "/ext/apps_data/cli/plugins",
.fal_prefix = "cli_",
.appid = CLI_APPID,
};

View File

@@ -0,0 +1,7 @@
#pragma once
#include <toolbox/cli/cli_command.h>
void cli_main_motd(void* context);
extern const CliCommandExternalConfig cli_main_ext_config;

View File

@@ -1,323 +1,308 @@
#include "cli_i.h" // IWYU pragma: keep
#include "cli_vcp.h"
#include <furi_hal_usb_cdc.h>
#include <furi_hal.h>
#include <furi.h>
#include <stdint.h>
#include <toolbox/pipe.h>
#include <toolbox/cli/shell/cli_shell.h>
#include "cli_main_shell.h"
#include "cli_main_commands.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 USB_CDC_PKT_LEN CDC_DATA_SZ
#define VCP_BUF_SIZE (USB_CDC_PKT_LEN * 3)
#define VCP_IF_NUM 0
#define VCP_MESSAGE_Q_LEN 8
#define VCP_IF_NUM 0
#ifdef CLI_VCP_DEBUG
#define VCP_DEBUG(...) FURI_LOG_D(TAG, __VA_ARGS__)
#ifdef CLI_VCP_TRACE
#define VCP_TRACE(...) FURI_LOG_T(__VA_ARGS__)
#else
#define VCP_DEBUG(...)
#define VCP_TRACE(...)
#endif
typedef enum {
VcpEvtStop = (1 << 0),
VcpEvtConnect = (1 << 1),
VcpEvtDisconnect = (1 << 2),
VcpEvtStreamRx = (1 << 3),
VcpEvtRx = (1 << 4),
VcpEvtStreamTx = (1 << 5),
VcpEvtTx = (1 << 6),
} WorkerEvtFlags;
#define VCP_THREAD_FLAG_ALL \
(VcpEvtStop | VcpEvtConnect | VcpEvtDisconnect | VcpEvtRx | VcpEvtTx | VcpEvtStreamRx | \
VcpEvtStreamTx)
typedef struct {
FuriThread* thread;
enum {
CliVcpMessageTypeEnable,
CliVcpMessageTypeDisable,
} type;
union {};
} CliVcpMessage;
FuriStreamBuffer* tx_stream;
FuriStreamBuffer* rx_stream;
typedef enum {
CliVcpInternalEventConnected = (1 << 0),
CliVcpInternalEventDisconnected = (1 << 1),
CliVcpInternalEventTxDone = (1 << 2),
CliVcpInternalEventRx = (1 << 3),
} CliVcpInternalEvent;
volatile bool connected;
volatile bool running;
#define CliVcpInternalEventAll \
(CliVcpInternalEventConnected | CliVcpInternalEventDisconnected | CliVcpInternalEventTxDone | \
CliVcpInternalEventRx)
FuriHalUsbInterface* usb_if_prev;
struct CliVcp {
FuriEventLoop* event_loop;
FuriMessageQueue* message_queue; // <! external messages
FuriThreadId thread_id;
uint8_t data_buffer[USB_CDC_PKT_LEN];
} CliVcp;
bool is_enabled, is_connected;
FuriHalUsbInterface* previous_interface;
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);
PipeSide* own_pipe;
PipeSide* shell_pipe;
bool is_currently_transmitting;
size_t previous_tx_length;
static CdcCallbacks cdc_cb = {
vcp_on_cdc_tx_complete,
vcp_on_cdc_rx,
vcp_state_callback,
vcp_on_cdc_control_line,
NULL,
CliRegistry* main_registry;
CliShell* shell;
};
static CliVcp* vcp = NULL;
// ============
// Data copying
// ============
static const uint8_t ascii_soh = 0x01;
static const uint8_t ascii_eot = 0x04;
/**
* Called in the following cases:
* - previous transfer has finished;
* - new data became available to send.
*/
static void cli_vcp_maybe_send_data(CliVcp* cli_vcp) {
if(cli_vcp->is_currently_transmitting) return;
if(!cli_vcp->own_pipe) return;
static void cli_vcp_init(void) {
if(vcp == NULL) {
vcp = malloc(sizeof(CliVcp));
vcp->tx_stream = furi_stream_buffer_alloc(VCP_TX_BUF_SIZE, 1);
vcp->rx_stream = furi_stream_buffer_alloc(VCP_RX_BUF_SIZE, 1);
uint8_t buf[USB_CDC_PKT_LEN];
size_t to_receive_from_pipe = MIN(sizeof(buf), pipe_bytes_available(cli_vcp->own_pipe));
size_t length = pipe_receive(cli_vcp->own_pipe, buf, to_receive_from_pipe);
if(length > 0 || cli_vcp->previous_tx_length == USB_CDC_PKT_LEN) {
VCP_TRACE(TAG, "cdc_send length=%zu", length);
cli_vcp->is_currently_transmitting = true;
furi_hal_cdc_send(VCP_IF_NUM, buf, length);
}
furi_assert(vcp->thread == NULL);
vcp->connected = false;
vcp->thread = furi_thread_alloc_ex("CliVcpWorker", 1024, vcp_worker, NULL);
furi_thread_start(vcp->thread);
FURI_LOG_I(TAG, "Init OK");
cli_vcp->previous_tx_length = length;
}
static void cli_vcp_deinit(void) {
furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtStop);
furi_thread_join(vcp->thread);
furi_thread_free(vcp->thread);
vcp->thread = NULL;
/**
* Called in the following cases:
* - new data arrived at the endpoint;
* - data was read out of the pipe.
*/
static void cli_vcp_maybe_receive_data(CliVcp* cli_vcp) {
if(!cli_vcp->own_pipe) return;
if(pipe_spaces_available(cli_vcp->own_pipe) < USB_CDC_PKT_LEN) return;
uint8_t buf[USB_CDC_PKT_LEN];
size_t length = furi_hal_cdc_receive(VCP_IF_NUM, buf, sizeof(buf));
VCP_TRACE(TAG, "cdc_receive length=%zu", length);
furi_check(pipe_send(cli_vcp->own_pipe, buf, length) == length);
}
static int32_t vcp_worker(void* context) {
UNUSED(context);
bool tx_idle = true;
size_t missed_rx = 0;
uint8_t last_tx_pkt_len = 0;
// =============
// CDC callbacks
// =============
// Switch USB to VCP mode (if it is not set yet)
vcp->usb_if_prev = furi_hal_usb_get_config();
if((vcp->usb_if_prev != &usb_cdc_single) && (vcp->usb_if_prev != &usb_cdc_dual)) {
static void cli_vcp_signal_internal_event(CliVcp* cli_vcp, CliVcpInternalEvent event) {
furi_thread_flags_set(cli_vcp->thread_id, event);
}
static void cli_vcp_cdc_tx_done(void* context) {
CliVcp* cli_vcp = context;
cli_vcp_signal_internal_event(cli_vcp, CliVcpInternalEventTxDone);
}
static void cli_vcp_cdc_rx(void* context) {
CliVcp* cli_vcp = context;
cli_vcp_signal_internal_event(cli_vcp, CliVcpInternalEventRx);
}
static void cli_vcp_cdc_state_callback(void* context, CdcState state) {
CliVcp* cli_vcp = context;
if(state == CdcStateDisconnected) {
cli_vcp_signal_internal_event(cli_vcp, CliVcpInternalEventDisconnected);
}
// `Connected` events are generated by DTR going active
}
static void cli_vcp_cdc_ctrl_line_callback(void* context, CdcCtrlLine ctrl_lines) {
CliVcp* cli_vcp = context;
if(ctrl_lines & CdcCtrlLineDTR) {
cli_vcp_signal_internal_event(cli_vcp, CliVcpInternalEventConnected);
} else {
cli_vcp_signal_internal_event(cli_vcp, CliVcpInternalEventDisconnected);
}
}
static CdcCallbacks cdc_callbacks = {
.tx_ep_callback = cli_vcp_cdc_tx_done,
.rx_ep_callback = cli_vcp_cdc_rx,
.state_callback = cli_vcp_cdc_state_callback,
.ctrl_line_callback = cli_vcp_cdc_ctrl_line_callback,
.config_callback = NULL,
};
// ======================
// Pipe callback handlers
// ======================
static void cli_vcp_data_from_shell(PipeSide* pipe, void* context) {
UNUSED(pipe);
CliVcp* cli_vcp = context;
cli_vcp_maybe_send_data(cli_vcp);
}
static void cli_vcp_shell_ready(PipeSide* pipe, void* context) {
UNUSED(pipe);
CliVcp* cli_vcp = context;
cli_vcp_maybe_receive_data(cli_vcp);
}
/**
* Processes messages arriving from other threads
*/
static void cli_vcp_message_received(FuriEventLoopObject* object, void* context) {
CliVcp* cli_vcp = context;
CliVcpMessage message;
furi_check(furi_message_queue_get(object, &message, 0) == FuriStatusOk);
switch(message.type) {
case CliVcpMessageTypeEnable:
if(cli_vcp->is_enabled) return;
FURI_LOG_D(TAG, "Enabling");
cli_vcp->is_enabled = true;
// switch usb mode
cli_vcp->previous_interface = furi_hal_usb_get_config();
furi_hal_usb_set_config(&usb_cdc_single, NULL);
furi_hal_cdc_set_callbacks(VCP_IF_NUM, &cdc_callbacks, cli_vcp);
break;
case CliVcpMessageTypeDisable:
if(!cli_vcp->is_enabled) return;
FURI_LOG_D(TAG, "Disabling");
cli_vcp->is_enabled = false;
// restore usb mode
furi_hal_cdc_set_callbacks(VCP_IF_NUM, NULL, NULL);
furi_hal_usb_set_config(cli_vcp->previous_interface, NULL);
break;
}
furi_hal_cdc_set_callbacks(VCP_IF_NUM, &cdc_cb, NULL);
FURI_LOG_D(TAG, "Start");
vcp->running = true;
while(1) {
uint32_t flags =
furi_thread_flags_wait(VCP_THREAD_FLAG_ALL, FuriFlagWaitAny, FuriWaitForever);
furi_assert(!(flags & FuriFlagError));
// VCP session opened
if(flags & VcpEvtConnect) {
VCP_DEBUG("Connect");
if(vcp->connected == false) {
vcp->connected = true;
furi_stream_buffer_send(vcp->rx_stream, &ascii_soh, 1, FuriWaitForever);
}
}
// VCP session closed
if(flags & VcpEvtDisconnect) {
VCP_DEBUG("Disconnect");
if(vcp->connected == true) {
vcp->connected = false;
furi_stream_buffer_receive(vcp->tx_stream, vcp->data_buffer, USB_CDC_PKT_LEN, 0);
furi_stream_buffer_send(vcp->rx_stream, &ascii_eot, 1, FuriWaitForever);
}
}
// Rx buffer was read, maybe there is enough space for new data?
if((flags & VcpEvtStreamRx) && (missed_rx > 0)) {
VCP_DEBUG("StreamRx");
if(furi_stream_buffer_spaces_available(vcp->rx_stream) >= USB_CDC_PKT_LEN) {
flags |= VcpEvtRx;
missed_rx--;
}
}
// New data received
if(flags & VcpEvtRx) {
if(furi_stream_buffer_spaces_available(vcp->rx_stream) >= USB_CDC_PKT_LEN) {
int32_t len = furi_hal_cdc_receive(VCP_IF_NUM, vcp->data_buffer, USB_CDC_PKT_LEN);
VCP_DEBUG("Rx %ld", len);
if(len > 0) {
furi_check(
furi_stream_buffer_send(
vcp->rx_stream, vcp->data_buffer, len, FuriWaitForever) ==
(size_t)len);
}
} else {
VCP_DEBUG("Rx missed");
missed_rx++;
}
}
// New data in Tx buffer
if(flags & VcpEvtStreamTx) {
VCP_DEBUG("StreamTx");
if(tx_idle) {
flags |= VcpEvtTx;
}
}
// CDC write transfer done
if(flags & VcpEvtTx) {
size_t len =
furi_stream_buffer_receive(vcp->tx_stream, vcp->data_buffer, USB_CDC_PKT_LEN, 0);
VCP_DEBUG("Tx %d", len);
if(len > 0) { // Some data left in Tx buffer. Sending it now
tx_idle = false;
furi_hal_cdc_send(VCP_IF_NUM, vcp->data_buffer, len);
last_tx_pkt_len = len;
} else { // There is nothing to send.
if(last_tx_pkt_len == 64) {
// Send extra zero-length packet if last packet len is 64 to indicate transfer end
furi_hal_cdc_send(VCP_IF_NUM, NULL, 0);
} else {
// Set flag to start next transfer instantly
tx_idle = true;
}
last_tx_pkt_len = 0;
}
}
if(flags & VcpEvtStop) {
vcp->connected = false;
vcp->running = false;
furi_hal_cdc_set_callbacks(VCP_IF_NUM, NULL, NULL);
// Restore previous USB mode (if it was set during init)
if((vcp->usb_if_prev != &usb_cdc_single) && (vcp->usb_if_prev != &usb_cdc_dual)) {
furi_hal_usb_unlock();
furi_hal_usb_set_config(vcp->usb_if_prev, NULL);
}
furi_stream_buffer_receive(vcp->tx_stream, vcp->data_buffer, USB_CDC_PKT_LEN, 0);
furi_stream_buffer_send(vcp->rx_stream, &ascii_eot, 1, FuriWaitForever);
break;
}
}
FURI_LOG_D(TAG, "End");
return 0;
}
static size_t cli_vcp_rx(uint8_t* buffer, size_t size, uint32_t timeout) {
furi_assert(vcp);
furi_assert(buffer);
/**
* Processes messages arriving from CDC event callbacks
*/
static void cli_vcp_internal_event_happened(void* context) {
CliVcp* cli_vcp = context;
CliVcpInternalEvent event = furi_thread_flags_wait(CliVcpInternalEventAll, FuriFlagWaitAny, 0);
furi_check(!(event & FuriFlagError));
if(vcp->running == false) {
if(event & CliVcpInternalEventDisconnected) {
if(!cli_vcp->is_connected) return;
FURI_LOG_D(TAG, "Disconnected");
cli_vcp->is_connected = false;
// disconnect our side of the pipe
pipe_detach_from_event_loop(cli_vcp->own_pipe);
pipe_free(cli_vcp->own_pipe);
cli_vcp->own_pipe = NULL;
}
if(event & CliVcpInternalEventConnected) {
if(cli_vcp->is_connected) return;
FURI_LOG_D(TAG, "Connected");
cli_vcp->is_connected = true;
// wait for previous shell to stop
furi_check(!cli_vcp->own_pipe);
if(cli_vcp->shell) {
cli_shell_join(cli_vcp->shell);
cli_shell_free(cli_vcp->shell);
pipe_free(cli_vcp->shell_pipe);
}
// start shell thread
PipeSideBundle bundle = pipe_alloc(VCP_BUF_SIZE, 1);
cli_vcp->own_pipe = bundle.alices_side;
cli_vcp->shell_pipe = bundle.bobs_side;
pipe_attach_to_event_loop(cli_vcp->own_pipe, cli_vcp->event_loop);
pipe_set_callback_context(cli_vcp->own_pipe, cli_vcp);
pipe_set_data_arrived_callback(
cli_vcp->own_pipe, cli_vcp_data_from_shell, FuriEventLoopEventFlagEdge);
pipe_set_space_freed_callback(
cli_vcp->own_pipe, cli_vcp_shell_ready, FuriEventLoopEventFlagEdge);
furi_delay_ms(33); // we are too fast, minicom isn't ready yet
cli_vcp->shell = cli_shell_alloc(
cli_main_motd, NULL, cli_vcp->shell_pipe, cli_vcp->main_registry, &cli_main_ext_config);
cli_shell_start(cli_vcp->shell);
}
if(event & CliVcpInternalEventRx) {
VCP_TRACE(TAG, "Rx");
cli_vcp_maybe_receive_data(cli_vcp);
}
if(event & CliVcpInternalEventTxDone) {
VCP_TRACE(TAG, "TxDone");
cli_vcp->is_currently_transmitting = false;
cli_vcp_maybe_send_data(cli_vcp);
}
}
// ============
// Thread stuff
// ============
static CliVcp* cli_vcp_alloc(void) {
CliVcp* cli_vcp = malloc(sizeof(CliVcp));
cli_vcp->thread_id = furi_thread_get_current_id();
cli_vcp->event_loop = furi_event_loop_alloc();
cli_vcp->message_queue = furi_message_queue_alloc(VCP_MESSAGE_Q_LEN, sizeof(CliVcpMessage));
furi_event_loop_subscribe_message_queue(
cli_vcp->event_loop,
cli_vcp->message_queue,
FuriEventLoopEventIn,
cli_vcp_message_received,
cli_vcp);
furi_event_loop_subscribe_thread_flags(
cli_vcp->event_loop, cli_vcp_internal_event_happened, cli_vcp);
cli_vcp->main_registry = furi_record_open(RECORD_CLI);
return cli_vcp;
}
int32_t cli_vcp_srv(void* p) {
UNUSED(p);
if(furi_hal_rtc_get_boot_mode() != FuriHalRtcBootModeNormal) {
FURI_LOG_W(TAG, "Skipping start in special boot mode");
furi_thread_suspend(furi_thread_get_current_id());
return 0;
}
VCP_DEBUG("rx %u start", size);
CliVcp* cli_vcp = cli_vcp_alloc();
furi_record_create(RECORD_CLI_VCP, cli_vcp);
furi_event_loop_run(cli_vcp->event_loop);
size_t rx_cnt = 0;
while(size > 0) {
size_t batch_size = size;
if(batch_size > VCP_RX_BUF_SIZE) batch_size = VCP_RX_BUF_SIZE;
size_t len = furi_stream_buffer_receive(vcp->rx_stream, buffer, batch_size, timeout);
VCP_DEBUG("rx %u ", batch_size);
if(len == 0) break;
if(vcp->running == false) {
// EOT command is received after VCP session close
rx_cnt += len;
break;
}
furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtStreamRx);
size -= len;
buffer += len;
rx_cnt += len;
}
VCP_DEBUG("rx %u end", size);
return rx_cnt;
return 0;
}
static size_t cli_vcp_rx_stdin(uint8_t* data, size_t size, uint32_t timeout, void* context) {
UNUSED(context);
return cli_vcp_rx(data, size, timeout);
// ==========
// Public API
// ==========
void cli_vcp_enable(CliVcp* cli_vcp) {
CliVcpMessage message = {
.type = CliVcpMessageTypeEnable,
};
furi_message_queue_put(cli_vcp->message_queue, &message, FuriWaitForever);
}
static void cli_vcp_tx(const uint8_t* buffer, size_t size) {
furi_assert(vcp);
furi_assert(buffer);
if(vcp->running == false) {
return;
}
VCP_DEBUG("tx %u start", size);
while(size > 0 && vcp->connected) {
size_t batch_size = size;
if(batch_size > USB_CDC_PKT_LEN) batch_size = USB_CDC_PKT_LEN;
furi_stream_buffer_send(vcp->tx_stream, buffer, batch_size, FuriWaitForever);
furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtStreamTx);
VCP_DEBUG("tx %u", batch_size);
size -= batch_size;
buffer += batch_size;
}
VCP_DEBUG("tx %u end", size);
void cli_vcp_disable(CliVcp* cli_vcp) {
CliVcpMessage message = {
.type = CliVcpMessageTypeDisable,
};
furi_message_queue_put(cli_vcp->message_queue, &message, FuriWaitForever);
}
static void cli_vcp_tx_stdout(const char* data, size_t size, void* context) {
UNUSED(context);
cli_vcp_tx((const uint8_t*)data, size);
}
static void vcp_state_callback(void* context, uint8_t state) {
UNUSED(context);
if(state == 0) {
furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtDisconnect);
}
}
static void vcp_on_cdc_control_line(void* context, uint8_t state) {
UNUSED(context);
// bit 0: DTR state, bit 1: RTS state
bool dtr = state & (1 << 0);
if(dtr == true) {
furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtConnect);
} else {
furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtDisconnect);
}
}
static void vcp_on_cdc_rx(void* context) {
UNUSED(context);
uint32_t ret = furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtRx);
furi_check(!(ret & FuriFlagError));
}
static void vcp_on_cdc_tx_complete(void* context) {
UNUSED(context);
furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtTx);
}
static bool cli_vcp_is_connected(void) {
furi_assert(vcp);
return vcp->connected;
}
const CliSession cli_vcp = {
cli_vcp_init,
cli_vcp_deinit,
cli_vcp_rx,
cli_vcp_rx_stdin,
cli_vcp_tx,
cli_vcp_tx_stdout,
cli_vcp_is_connected,
};

View File

@@ -9,9 +9,12 @@
extern "C" {
#endif
typedef struct CliSession CliSession;
#define RECORD_CLI_VCP "cli_vcp"
extern const CliSession cli_vcp;
typedef struct CliVcp CliVcp;
void cli_vcp_enable(CliVcp* cli_vcp);
void cli_vcp_disable(CliVcp* cli_vcp);
#ifdef __cplusplus
}

View File

@@ -0,0 +1,10 @@
#include "../cli_main_commands.h"
static void execute(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(pipe);
UNUSED(args);
UNUSED(context);
puts("Hello, World!");
}
CLI_COMMAND_INTERFACE(hello_world, execute, CliCommandFlagParallelSafe, 768, CLI_APPID);

View File

@@ -0,0 +1,160 @@
#include "../cli_main_commands.h"
#include <toolbox/cli/cli_ansi.h>
#include <toolbox/version.h>
#include <furi_hal.h>
#include <furi_hal_info.h>
#include <FreeRTOS.h>
#include <FreeRTOS-Kernel/include/task.h>
static void execute(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(pipe);
UNUSED(args);
UNUSED(context);
static const char* const neofetch_logo[] = {
" _.-------.._ -,",
" .-\"```\"--..,,_/ /`-, -, \\ ",
" .:\" /:/ /'\\ \\ ,_..., `. | |",
" / ,----/:/ /`\\ _\\~`_-\"` _;",
" ' / /`\"\"\"'\\ \\ \\.~`_-' ,-\"'/ ",
" | | | 0 | | .-' ,/` /",
" | ,..\\ \\ ,.-\"` ,/` /",
"; : `/`\"\"\\` ,/--==,/-----,",
"| `-...| -.___-Z:_______J...---;",
": ` _-'",
};
#define NEOFETCH_COLOR ANSI_FLIPPER_BRAND_ORANGE
// Determine logo parameters
size_t logo_height = COUNT_OF(neofetch_logo), logo_width = 0;
for(size_t i = 0; i < logo_height; i++)
logo_width = MAX(logo_width, strlen(neofetch_logo[i]));
logo_width += 4; // space between logo and info
// Format hostname delimiter
const size_t size_of_hostname = 4 + strlen(furi_hal_version_get_name_ptr());
char delimiter[64];
memset(delimiter, '-', size_of_hostname);
delimiter[size_of_hostname] = '\0';
// Get heap info
size_t heap_total = memmgr_get_total_heap();
size_t heap_used = heap_total - memmgr_get_free_heap();
uint16_t heap_percent = (100 * heap_used) / heap_total;
// Get storage info
Storage* storage = furi_record_open(RECORD_STORAGE);
uint64_t ext_total, ext_free, ext_used, ext_percent;
storage_common_fs_info(storage, "/ext", &ext_total, &ext_free);
ext_used = ext_total - ext_free;
ext_percent = (100 * ext_used) / ext_total;
ext_used /= 1024 * 1024;
ext_total /= 1024 * 1024;
furi_record_close(RECORD_STORAGE);
// Get battery info
uint16_t charge_percent = furi_hal_power_get_pct();
const char* charge_state;
if(furi_hal_power_is_charging()) {
if((charge_percent < 100) && (!furi_hal_power_is_charging_done())) {
charge_state = "charging";
} else {
charge_state = "charged";
}
} else {
charge_state = "discharging";
}
// Get misc info
uint32_t uptime = furi_get_tick() / furi_kernel_get_tick_frequency();
const Version* version = version_get();
uint16_t major, minor;
furi_hal_info_get_api_version(&major, &minor);
// Print ASCII art with info
const size_t info_height = 16;
for(size_t i = 0; i < MAX(logo_height, info_height); i++) {
printf(NEOFETCH_COLOR "%-*s", logo_width, (i < logo_height) ? neofetch_logo[i] : "");
switch(i) {
case 0: // you@<hostname>
printf("you" ANSI_RESET "@" NEOFETCH_COLOR "%s", furi_hal_version_get_name_ptr());
break;
case 1: // delimiter
printf(ANSI_RESET "%s", delimiter);
break;
case 2: // OS: FURI <edition> <branch> <version> <commit> (SDK <maj>.<min>)
printf(
"OS" ANSI_RESET ": FURI %s %s %s %s (SDK %hu.%hu)",
version_get_version(version),
version_get_gitbranch(version),
version_get_version(version),
version_get_githash(version),
major,
minor);
break;
case 3: // Host: <model> <hostname>
printf(
"Host" ANSI_RESET ": %s %s",
furi_hal_version_get_model_code(),
furi_hal_version_get_device_name_ptr());
break;
case 4: // Kernel: FreeRTOS <maj>.<min>.<build>
printf(
"Kernel" ANSI_RESET ": FreeRTOS %d.%d.%d",
tskKERNEL_VERSION_MAJOR,
tskKERNEL_VERSION_MINOR,
tskKERNEL_VERSION_BUILD);
break;
case 5: // Uptime: ?h?m?s
printf(
"Uptime" ANSI_RESET ": %luh%lum%lus",
uptime / 60 / 60,
uptime / 60 % 60,
uptime % 60);
break;
case 6: // ST7567 128x64 @ 1 bpp in 1.4"
printf("Display" ANSI_RESET ": ST7567 128x64 @ 1 bpp in 1.4\"");
break;
case 7: // DE: GuiSrv
printf("DE" ANSI_RESET ": GuiSrv");
break;
case 8: // Shell: CliSrv
printf("Shell" ANSI_RESET ": CliShell");
break;
case 9: // CPU: STM32WB55RG @ 64 MHz
printf("CPU" ANSI_RESET ": STM32WB55RG @ 64 MHz");
break;
case 10: // Memory: <used> / <total> B (??%)
printf(
"Memory" ANSI_RESET ": %zu / %zu B (%hu%%)", heap_used, heap_total, heap_percent);
break;
case 11: // Disk (/ext): <used> / <total> MiB (??%)
printf(
"Disk (/ext)" ANSI_RESET ": %llu / %llu MiB (%llu%%)",
ext_used,
ext_total,
ext_percent);
break;
case 12: // Battery: ??% (<state>)
printf("Battery" ANSI_RESET ": %hu%% (%s)" ANSI_RESET, charge_percent, charge_state);
break;
case 13: // empty space
break;
case 14: // Colors (line 1)
for(size_t j = 30; j <= 37; j++)
printf("\e[%dm███", j);
break;
case 15: // Colors (line 2)
for(size_t j = 90; j <= 97; j++)
printf("\e[%dm███", j);
break;
default:
break;
}
printf("\r\n");
}
printf(ANSI_RESET);
#undef NEOFETCH_COLOR
}
CLI_COMMAND_INTERFACE(neofetch, execute, CliCommandFlagParallelSafe, 2048, CLI_APPID);

View File

@@ -0,0 +1,43 @@
#include "../cli_main_commands.h"
#include <toolbox/cli/cli_registry.h>
#include <toolbox/cli/shell/cli_shell.h>
#include <toolbox/cli/cli_ansi.h>
#define RAINBOW_SUBCOMMAND \
ANSI_FG_RED "s" ANSI_FG_YELLOW "u" ANSI_FG_BLUE "b" ANSI_FG_GREEN "c" ANSI_FG_MAGENTA \
"o" ANSI_FG_RED "m" ANSI_FG_YELLOW "m" ANSI_FG_BLUE "a" ANSI_FG_GREEN \
"n" ANSI_FG_MAGENTA "d"
static void subcommand(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(pipe);
UNUSED(args);
UNUSED(context);
printf("This is a ✨" RAINBOW_SUBCOMMAND ANSI_RESET "✨!");
}
static void motd(void* context) {
UNUSED(context);
printf("\r\n");
printf("+------------------------------------+\r\n");
printf("| Hello world! |\r\n");
printf("| This is the " ANSI_FG_GREEN "MOTD" ANSI_RESET " for our " ANSI_FG_BLUE
"subshell" ANSI_RESET "! |\r\n");
printf("+------------------------------------+\r\n");
}
static void execute(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(args);
UNUSED(context);
CliRegistry* registry = cli_registry_alloc();
cli_registry_add_command(registry, "subcommand", CliCommandFlagParallelSafe, subcommand, NULL);
CliShell* shell = cli_shell_alloc(motd, NULL, pipe, registry, NULL);
cli_shell_set_prompt(shell, "subshell");
cli_shell_start(shell);
cli_shell_join(shell);
cli_shell_free(shell);
cli_registry_free(registry);
}
CLI_COMMAND_INTERFACE(subshell_demo, execute, CliCommandFlagParallelSafe, 2048, CLI_APPID);

View File

@@ -2,7 +2,10 @@
#include <furi.h>
#include <lib/toolbox/args.h>
#include <cli/cli.h>
#include <toolbox/pipe.h>
#include <cli/cli_main_commands.h>
#include <toolbox/cli/cli_registry.h>
#include <toolbox/cli/cli_ansi.h>
void crypto_cli_print_usage(void) {
printf("Usage:\r\n");
@@ -17,7 +20,7 @@ void crypto_cli_print_usage(void) {
"\tstore_key <key_slot:int> <key_type:str> <key_size:int> <key_data:hex>\t - Store key in secure enclave. !!! NON-REVERSIBLE OPERATION - READ MANUAL FIRST !!!\r\n");
}
void crypto_cli_encrypt(Cli* cli, FuriString* args) {
void crypto_cli_encrypt(PipeSide* pipe, FuriString* args) {
int key_slot = 0;
bool key_loaded = false;
uint8_t iv[16];
@@ -44,15 +47,15 @@ void crypto_cli_encrypt(Cli* cli, FuriString* args) {
FuriString* input;
input = furi_string_alloc();
char c;
while(cli_read(cli, (uint8_t*)&c, 1) == 1) {
if(c == CliSymbolAsciiETX) {
while(pipe_receive(pipe, (uint8_t*)&c, 1) == 1) {
if(c == CliKeyETX) {
printf("\r\n");
break;
} else if(c >= 0x20 && c < 0x7F) {
putc(c, stdout);
fflush(stdout);
furi_string_push_back(input, c);
} else if(c == CliSymbolAsciiCR) {
} else if(c == CliKeyCR) {
printf("\r\n");
furi_string_cat(input, "\r\n");
}
@@ -92,7 +95,7 @@ void crypto_cli_encrypt(Cli* cli, FuriString* args) {
}
}
void crypto_cli_decrypt(Cli* cli, FuriString* args) {
void crypto_cli_decrypt(PipeSide* pipe, FuriString* args) {
int key_slot = 0;
bool key_loaded = false;
uint8_t iv[16];
@@ -119,15 +122,15 @@ void crypto_cli_decrypt(Cli* cli, FuriString* args) {
FuriString* hex_input;
hex_input = furi_string_alloc();
char c;
while(cli_read(cli, (uint8_t*)&c, 1) == 1) {
if(c == CliSymbolAsciiETX) {
while(pipe_receive(pipe, (uint8_t*)&c, 1) == 1) {
if(c == CliKeyETX) {
printf("\r\n");
break;
} else if(c >= 0x20 && c < 0x7F) {
putc(c, stdout);
fflush(stdout);
furi_string_push_back(hex_input, c);
} else if(c == CliSymbolAsciiCR) {
} else if(c == CliKeyCR) {
printf("\r\n");
}
}
@@ -164,8 +167,8 @@ void crypto_cli_decrypt(Cli* cli, FuriString* args) {
}
}
void crypto_cli_has_key(Cli* cli, FuriString* args) {
UNUSED(cli);
void crypto_cli_has_key(PipeSide* pipe, FuriString* args) {
UNUSED(pipe);
int key_slot = 0;
uint8_t iv[16] = {0};
@@ -186,8 +189,8 @@ void crypto_cli_has_key(Cli* cli, FuriString* args) {
} while(0);
}
void crypto_cli_store_key(Cli* cli, FuriString* args) {
UNUSED(cli);
void crypto_cli_store_key(PipeSide* pipe, FuriString* args) {
UNUSED(pipe);
int key_slot = 0;
int key_size = 0;
FuriString* key_type;
@@ -279,7 +282,7 @@ void crypto_cli_store_key(Cli* cli, FuriString* args) {
furi_string_free(key_type);
}
static void crypto_cli(Cli* cli, FuriString* args, void* context) {
static void crypto_cli(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(context);
FuriString* cmd;
cmd = furi_string_alloc();
@@ -291,22 +294,22 @@ static void crypto_cli(Cli* cli, FuriString* args, void* context) {
}
if(furi_string_cmp_str(cmd, "encrypt") == 0) {
crypto_cli_encrypt(cli, args);
crypto_cli_encrypt(pipe, args);
break;
}
if(furi_string_cmp_str(cmd, "decrypt") == 0) {
crypto_cli_decrypt(cli, args);
crypto_cli_decrypt(pipe, args);
break;
}
if(furi_string_cmp_str(cmd, "has_key") == 0) {
crypto_cli_has_key(cli, args);
crypto_cli_has_key(pipe, args);
break;
}
if(furi_string_cmp_str(cmd, "store_key") == 0) {
crypto_cli_store_key(cli, args);
crypto_cli_store_key(pipe, args);
break;
}
@@ -318,8 +321,8 @@ static void crypto_cli(Cli* cli, FuriString* args, void* context) {
void crypto_on_system_start(void) {
#ifdef SRV_CLI
Cli* cli = furi_record_open(RECORD_CLI);
cli_add_command(cli, "crypto", CliCommandFlagDefault, crypto_cli, NULL);
CliRegistry* registry = furi_record_open(RECORD_CLI);
cli_registry_add_command(registry, "crypto", CliCommandFlagDefault, crypto_cli, NULL);
furi_record_close(RECORD_CLI);
#else
UNUSED(crypto_cli);

View File

@@ -1,6 +1,5 @@
#include "desktop_i.h"
#include <cli/cli.h>
#include <cli/cli_vcp.h>
#include <gui/gui_i.h>
@@ -30,9 +29,7 @@ static void desktop_loader_callback(const void* message, void* context) {
if(event->type == LoaderEventTypeApplicationBeforeLoad) {
view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopGlobalBeforeAppStarted);
furi_check(furi_semaphore_acquire(desktop->animation_semaphore, 3000) == FuriStatusOk);
} else if(
event->type == LoaderEventTypeApplicationLoadFailed ||
event->type == LoaderEventTypeApplicationStopped) {
} else if(event->type == LoaderEventTypeNoMoreAppsInQueue) {
view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopGlobalAfterAppFinished);
}
}
@@ -405,9 +402,9 @@ void desktop_lock(Desktop* desktop) {
furi_hal_rtc_set_flag(FuriHalRtcFlagLock);
if(desktop_pin_code_is_set()) {
Cli* cli = furi_record_open(RECORD_CLI);
cli_session_close(cli);
furi_record_close(RECORD_CLI);
CliVcp* cli_vcp = furi_record_open(RECORD_CLI_VCP);
cli_vcp_disable(cli_vcp);
furi_record_close(RECORD_CLI_VCP);
}
desktop_auto_lock_inhibit(desktop);
@@ -435,9 +432,9 @@ void desktop_unlock(Desktop* desktop) {
furi_hal_rtc_set_pin_fails(0);
if(desktop_pin_code_is_set()) {
Cli* cli = furi_record_open(RECORD_CLI);
cli_session_open(cli, &cli_vcp);
furi_record_close(RECORD_CLI);
CliVcp* cli_vcp = furi_record_open(RECORD_CLI_VCP);
cli_vcp_enable(cli_vcp);
furi_record_close(RECORD_CLI_VCP);
}
DesktopStatus status = {.locked = false};
@@ -534,6 +531,10 @@ int32_t desktop_srv(void* p) {
if(desktop_pin_code_is_set()) {
desktop_lock(desktop);
} else {
CliVcp* cli_vcp = furi_record_open(RECORD_CLI_VCP);
cli_vcp_enable(cli_vcp);
furi_record_close(RECORD_CLI_VCP);
}
if(storage_file_exists(desktop->storage, SLIDESHOW_FS_PATH)) {

View File

@@ -4,9 +4,11 @@
#include <stdint.h>
#include <stdio.h>
#include <furi.h>
#include <cli/cli.h>
#include <furi_hal_gpio.h>
#include <furi_hal_vibro.h>
#include <toolbox/cli/cli_command.h>
#include <cli/cli_main_commands.h>
#include <toolbox/pipe.h>
#define INPUT_DEBOUNCE_TICKS_HALF (INPUT_DEBOUNCE_TICKS / 2)
#define INPUT_PRESS_TICKS 150
@@ -28,7 +30,7 @@ typedef struct {
} InputPinState;
/** Input CLI command handler */
void input_cli(Cli* cli, FuriString* args, void* context);
void input_cli(PipeSide* pipe, FuriString* args, void* context);
// #define INPUT_DEBUG
@@ -100,8 +102,10 @@ int32_t input_srv(void* p) {
#endif
#ifdef SRV_CLI
Cli* cli = furi_record_open(RECORD_CLI);
cli_add_command(cli, "input", CliCommandFlagParallelSafe, input_cli, event_pubsub);
CliRegistry* registry = furi_record_open(RECORD_CLI);
cli_registry_add_command(
registry, "input", CliCommandFlagParallelSafe, input_cli, event_pubsub);
furi_record_close(RECORD_CLI);
#endif
InputPinState pin_states[input_pins_count];

View File

@@ -1,8 +1,9 @@
#include "input.h"
#include <furi.h>
#include <cli/cli.h>
#include <toolbox/cli/cli_command.h>
#include <toolbox/args.h>
#include <toolbox/pipe.h>
static void input_cli_usage(void) {
printf("Usage:\r\n");
@@ -19,7 +20,7 @@ static void input_cli_dump_events_callback(const void* value, void* ctx) {
furi_message_queue_put(input_queue, value, FuriWaitForever);
}
static void input_cli_dump(Cli* cli, FuriString* args, FuriPubSub* event_pubsub) {
static void input_cli_dump(PipeSide* pipe, FuriString* args, FuriPubSub* event_pubsub) {
UNUSED(args);
FuriMessageQueue* input_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
FuriPubSubSubscription* input_subscription =
@@ -27,7 +28,7 @@ static void input_cli_dump(Cli* cli, FuriString* args, FuriPubSub* event_pubsub)
InputEvent input_event;
printf("Press CTRL+C to stop\r\n");
while(!cli_cmd_interrupt_received(cli)) {
while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) {
if(furi_message_queue_get(input_queue, &input_event, 100) == FuriStatusOk) {
printf(
"key: %s type: %s\r\n",
@@ -47,8 +48,8 @@ static void input_cli_send_print_usage(void) {
printf("\t\t <type>\t - one of 'press', 'release', 'short', 'long'\r\n");
}
static void input_cli_send(Cli* cli, FuriString* args, FuriPubSub* event_pubsub) {
UNUSED(cli);
static void input_cli_send(PipeSide* pipe, FuriString* args, FuriPubSub* event_pubsub) {
UNUSED(pipe);
InputEvent event;
FuriString* key_str;
key_str = furi_string_alloc();
@@ -97,8 +98,7 @@ static void input_cli_send(Cli* cli, FuriString* args, FuriPubSub* event_pubsub)
furi_string_free(key_str);
}
void input_cli(Cli* cli, FuriString* args, void* context) {
furi_assert(cli);
void input_cli(PipeSide* pipe, FuriString* args, void* context) {
furi_assert(context);
FuriPubSub* event_pubsub = context;
FuriString* cmd;
@@ -110,11 +110,11 @@ void input_cli(Cli* cli, FuriString* args, void* context) {
break;
}
if(furi_string_cmp_str(cmd, "dump") == 0) {
input_cli_dump(cli, args, event_pubsub);
input_cli_dump(pipe, args, event_pubsub);
break;
}
if(furi_string_cmp_str(cmd, "send") == 0) {
input_cli_send(cli, args, event_pubsub);
input_cli_send(pipe, args, event_pubsub);
break;
}

View File

@@ -168,6 +168,13 @@ static void loader_show_gui_error(
furi_record_close(RECORD_DIALOGS);
}
static void loader_generic_synchronous_request(Loader* loader, LoaderMessage* message) {
furi_check(loader);
message->api_lock = api_lock_alloc_locked();
furi_message_queue_put(loader->queue, message, FuriWaitForever);
api_lock_wait_unlock_and_free(message->api_lock);
}
LoaderStatus
loader_start(Loader* loader, const char* name, const char* args, FuriString* error_message) {
furi_check(loader);
@@ -203,16 +210,12 @@ void loader_start_detached_with_gui_error(Loader* loader, const char* name, cons
}
bool loader_lock(Loader* loader) {
furi_check(loader);
LoaderMessage message;
LoaderMessageBoolResult result;
message.type = LoaderMessageTypeLock;
message.api_lock = api_lock_alloc_locked();
message.bool_value = &result;
furi_message_queue_put(loader->queue, &message, FuriWaitForever);
api_lock_wait_unlock_and_free(message.api_lock);
LoaderMessage message = {
.type = LoaderMessageTypeLock,
.bool_value = &result,
};
loader_generic_synchronous_request(loader, &message);
return result.value;
}
@@ -226,16 +229,12 @@ void loader_unlock(Loader* loader) {
}
bool loader_is_locked(Loader* loader) {
furi_check(loader);
LoaderMessage message;
LoaderMessageBoolResult result;
message.type = LoaderMessageTypeIsLocked;
message.api_lock = api_lock_alloc_locked();
message.bool_value = &result;
furi_message_queue_put(loader->queue, &message, FuriWaitForever);
api_lock_wait_unlock_and_free(message.api_lock);
LoaderMessage message = {
.type = LoaderMessageTypeIsLocked,
.bool_value = &result,
};
loader_generic_synchronous_request(loader, &message);
return result.value;
}
@@ -257,42 +256,63 @@ FuriPubSub* loader_get_pubsub(Loader* loader) {
}
bool loader_signal(Loader* loader, uint32_t signal, void* arg) {
furi_check(loader);
LoaderMessageBoolResult result;
LoaderMessage message = {
.type = LoaderMessageTypeSignal,
.api_lock = api_lock_alloc_locked(),
.signal.signal = signal,
.signal.arg = arg,
.bool_value = &result,
};
furi_message_queue_put(loader->queue, &message, FuriWaitForever);
api_lock_wait_unlock_and_free(message.api_lock);
loader_generic_synchronous_request(loader, &message);
return result.value;
}
bool loader_get_application_name(Loader* loader, FuriString* name) {
furi_check(loader);
LoaderMessageBoolResult result;
LoaderMessage message = {
.type = LoaderMessageTypeGetApplicationName,
.api_lock = api_lock_alloc_locked(),
.application_name = name,
.bool_value = &result,
};
furi_message_queue_put(loader->queue, &message, FuriWaitForever);
api_lock_wait_unlock_and_free(message.api_lock);
loader_generic_synchronous_request(loader, &message);
return result.value;
}
bool loader_get_application_launch_path(Loader* loader, FuriString* name) {
LoaderMessageBoolResult result;
LoaderMessage message = {
.type = LoaderMessageTypeGetApplicationLaunchPath,
.application_name = name,
.bool_value = &result,
};
loader_generic_synchronous_request(loader, &message);
return result.value;
}
void loader_enqueue_launch(
Loader* loader,
const char* name,
const char* args,
LoaderDeferredLaunchFlag flags) {
LoaderMessage message = {
.type = LoaderMessageTypeEnqueueLaunch,
.defer_start =
{
.name_or_path = strdup(name),
.args = args ? strdup(args) : NULL,
.flags = flags,
},
};
loader_generic_synchronous_request(loader, &message);
}
void loader_clear_launch_queue(Loader* loader) {
LoaderMessage message = {
.type = LoaderMessageTypeClearLaunchQueue,
};
loader_generic_synchronous_request(loader, &message);
}
// callbacks
static void loader_menu_closed_callback(void* context) {
@@ -329,12 +349,10 @@ static Loader* loader_alloc(void) {
Loader* loader = malloc(sizeof(Loader));
loader->pubsub = furi_pubsub_alloc();
loader->queue = furi_message_queue_alloc(1, sizeof(LoaderMessage));
loader->loader_menu = NULL;
loader->loader_applications = NULL;
loader->app.args = NULL;
loader->app.thread = NULL;
loader->app.insomniac = false;
loader->app.fap = NULL;
loader->gui = furi_record_open(RECORD_GUI);
loader->view_holder = view_holder_alloc();
loader->loading = loading_alloc();
view_holder_attach_to_gui(loader->view_holder, loader->gui);
return loader;
}
@@ -706,6 +724,10 @@ static LoaderMessageLoaderStatusResult loader_do_start_by_name(
LoaderStatusErrorUnknownApp, error_message, "Application \"%s\" not found", name);
} while(false);
if(status.value == LoaderStatusOk) {
loader->app.launch_path = furi_string_alloc_set_str(name);
}
return status;
}
@@ -723,6 +745,57 @@ static void loader_do_unlock(Loader* loader) {
loader->app.thread = NULL;
}
static void loader_do_emit_queue_empty_event(Loader* loader) {
FURI_LOG_I(TAG, "Launch queue empty");
LoaderEvent event;
event.type = LoaderEventTypeNoMoreAppsInQueue;
furi_pubsub_publish(loader->pubsub, &event);
}
static bool loader_do_deferred_launch(Loader* loader, LoaderDeferredLaunchRecord* record);
static void loader_do_next_deferred_launch_if_available(Loader* loader) {
LoaderDeferredLaunchRecord record;
if(loader_queue_pop(&loader->launch_queue, &record)) {
loader_do_deferred_launch(loader, &record);
loader_queue_item_clear(&record);
} else {
loader_do_emit_queue_empty_event(loader);
}
}
static bool loader_do_deferred_launch(Loader* loader, LoaderDeferredLaunchRecord* record) {
furi_assert(loader);
furi_assert(record);
bool is_successful = false;
FuriString* error_message = furi_string_alloc();
view_holder_set_view(loader->view_holder, loading_get_view(loader->loading));
view_holder_send_to_front(loader->view_holder);
do {
const char* app_name_str = record->name_or_path;
const char* app_args = record->args;
FURI_LOG_I(TAG, "Deferred launch: %s", app_name_str);
LoaderMessageLoaderStatusResult result =
loader_do_start_by_name(loader, app_name_str, app_args, error_message);
if(result.value == LoaderStatusOk) {
is_successful = true;
break;
}
if(record->flags & LoaderDeferredLaunchFlagGui)
loader_show_gui_error(result, app_name_str, error_message);
loader_do_next_deferred_launch_if_available(loader);
} while(false);
view_holder_set_view(loader->view_holder, NULL);
furi_string_free(error_message);
return is_successful;
}
static void loader_do_app_closed(Loader* loader) {
furi_assert(loader->app.thread);
@@ -747,11 +820,15 @@ static void loader_do_app_closed(Loader* loader) {
loader->app.thread = NULL;
}
furi_string_free(loader->app.launch_path);
FURI_LOG_I(TAG, "Application stopped. Free heap: %zu", memmgr_get_free_heap());
LoaderEvent event;
event.type = LoaderEventTypeApplicationStopped;
furi_pubsub_publish(loader->pubsub, &event);
loader_do_next_deferred_launch_if_available(loader);
}
static bool loader_is_application_running(Loader* loader) {
@@ -776,6 +853,15 @@ static bool loader_do_get_application_name(Loader* loader, FuriString* name) {
return false;
}
static bool loader_do_get_application_launch_path(Loader* loader, FuriString* path) {
if(loader_is_application_running(loader)) {
furi_string_set(path, loader->app.launch_path);
return true;
}
return false;
}
// app
int32_t loader_srv(void* p) {
@@ -798,16 +884,20 @@ int32_t loader_srv(void* p) {
while(true) {
if(furi_message_queue_get(loader->queue, &message, FuriWaitForever) == FuriStatusOk) {
switch(message.type) {
case LoaderMessageTypeStartByName:
*(message.status_value) = loader_do_start_by_name(
case LoaderMessageTypeStartByName: {
LoaderMessageLoaderStatusResult status = loader_do_start_by_name(
loader, message.start.name, message.start.args, message.start.error_message);
*(message.status_value) = status;
if(status.value != LoaderStatusOk) loader_do_emit_queue_empty_event(loader);
api_lock_unlock(message.api_lock);
break;
}
case LoaderMessageTypeStartByNameDetachedWithGuiError: {
FuriString* error_message = furi_string_alloc();
LoaderMessageLoaderStatusResult status = loader_do_start_by_name(
loader, message.start.name, message.start.args, error_message);
loader_show_gui_error(status, message.start.name, error_message);
if(status.value != LoaderStatusOk) loader_do_emit_queue_empty_event(loader);
if(message.start.name) free((void*)message.start.name);
if(message.start.args) free((void*)message.start.args);
furi_string_free(error_message);
@@ -846,6 +936,19 @@ int32_t loader_srv(void* p) {
loader_do_get_application_name(loader, message.application_name);
api_lock_unlock(message.api_lock);
break;
case LoaderMessageTypeGetApplicationLaunchPath:
message.bool_value->value =
loader_do_get_application_launch_path(loader, message.application_name);
api_lock_unlock(message.api_lock);
break;
case LoaderMessageTypeEnqueueLaunch:
furi_check(loader_queue_push(&loader->launch_queue, &message.defer_start));
api_lock_unlock(message.api_lock);
break;
case LoaderMessageTypeClearLaunchQueue:
loader_queue_clear(&loader->launch_queue);
api_lock_unlock(message.api_lock);
break;
}
}
}

View File

@@ -22,13 +22,19 @@ typedef enum {
typedef enum {
LoaderEventTypeApplicationBeforeLoad,
LoaderEventTypeApplicationLoadFailed,
LoaderEventTypeApplicationStopped
LoaderEventTypeApplicationStopped,
LoaderEventTypeNoMoreAppsInQueue, //<! The normal `Stopped` event still fires before this one
} LoaderEventType;
typedef struct {
LoaderEventType type;
} LoaderEvent;
typedef enum {
LoaderDeferredLaunchFlagNone = 0,
LoaderDeferredLaunchFlagGui = (1 << 1), //<! Report launch to the user via a dialog
} LoaderDeferredLaunchFlag;
/**
* @brief Start application
* @param[in] instance loader instance
@@ -95,7 +101,7 @@ FuriPubSub* loader_get_pubsub(Loader* instance);
*
* @param[in] instance pointer to the loader instance
* @param[in] signal signal value to be sent
* @param[in,out] arg optional argument (can be of any value, including NULL)
* @param[inout] arg optional argument (can be of any value, including NULL)
*
* @return true if the signal was handled by the application, false otherwise
*/
@@ -105,11 +111,49 @@ bool loader_signal(Loader* instance, uint32_t signal, void* arg);
* @brief Get the name of the currently running application
*
* @param[in] instance pointer to the loader instance
* @param[in,out] name pointer to the string to contain the name (must be allocated)
* @param[inout] name pointer to the string to contain the name (must be allocated)
* @return true if it was possible to get an application name, false otherwise
*/
bool loader_get_application_name(Loader* instance, FuriString* name);
/**
* @brief Get the launch path or name of the currently running application
*
* This is the string that was supplied to `loader_start` such that the current
* app is running now. It might be a name (in the case of internal apps) or a
* path (in the case of external apps). This value can be used to launch the
* same app again.
*
* @param[in] instance pointer to the loader instance
* @param[inout] name pointer to the string to contain the path or name
* (must be allocated)
* @return true if it was possible to get an application path, false otherwise
*/
bool loader_get_application_launch_path(Loader* instance, FuriString* name);
/**
* @brief Enqueues a request to launch an application after the current one
*
* @param[in] instance pointer to the loader instance
* @param[in] name pointer to the name or path of the application, or NULL to
* cancel a previous request
* @param[in] args pointer to argument to provide to the next app
* @param[in] flags additional flags. see enum documentation for more info
*/
void loader_enqueue_launch(
Loader* instance,
const char* name,
const char* args,
LoaderDeferredLaunchFlag flags);
/**
* @brief Removes all requests to launch applications after the current one
* exits
*
* @param[in] instance pointer to the loader instance
*/
void loader_clear_launch_queue(Loader* instance);
#ifdef __cplusplus
}
#endif

View File

@@ -120,7 +120,7 @@ static void loader_pubsub_callback(const void* message, void* context) {
const LoaderEvent* event = message;
const FuriThreadId thread_id = (FuriThreadId)context;
if(event->type == LoaderEventTypeApplicationStopped) {
if(event->type == LoaderEventTypeNoMoreAppsInQueue) {
furi_thread_flags_set(thread_id, APPLICATION_STOP_EVENT);
}
}

View File

@@ -1,11 +1,13 @@
#include "loader.h"
#include <furi.h>
#include <cli/cli.h>
#include <toolbox/cli/cli_command.h>
#include <cli/cli_main_commands.h>
#include <applications.h>
#include <lib/toolbox/args.h>
#include <lib/toolbox/strint.h>
#include <notification/notification_messages.h>
#include <toolbox/pipe.h>
static void loader_cli_print_usage(void) {
printf("Usage:\r\n");
@@ -110,8 +112,8 @@ static void loader_cli_signal(FuriString* args, Loader* loader) {
}
}
static void loader_cli(Cli* cli, FuriString* args, void* context) {
UNUSED(cli);
static void loader_cli(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(pipe);
UNUSED(context);
Loader* loader = furi_record_open(RECORD_LOADER);
@@ -140,8 +142,9 @@ static void loader_cli(Cli* cli, FuriString* args, void* context) {
void loader_on_system_start(void) {
#ifdef SRV_CLI
Cli* cli = furi_record_open(RECORD_CLI);
cli_add_command(cli, RECORD_LOADER, CliCommandFlagParallelSafe, loader_cli, NULL);
CliRegistry* registry = furi_record_open(RECORD_CLI);
cli_registry_add_command(
registry, RECORD_LOADER, CliCommandFlagParallelSafe, loader_cli, NULL);
furi_record_close(RECORD_CLI);
#else
UNUSED(loader_cli);

View File

@@ -2,11 +2,20 @@
#include <furi.h>
#include <toolbox/api_lock.h>
#include <flipper_application/flipper_application.h>
#include <gui/gui.h>
#include <gui/view_holder.h>
#include <gui/modules/loading.h>
#include <m-array.h>
#include "loader.h"
#include "loader_menu.h"
#include "loader_applications.h"
#include "loader_queue.h"
typedef struct {
FuriString* launch_path;
char* args;
FuriThread* thread;
bool insomniac;
@@ -19,6 +28,12 @@ struct Loader {
LoaderMenu* loader_menu;
LoaderApplications* loader_applications;
LoaderAppData app;
LoaderLaunchQueue launch_queue;
Gui* gui;
ViewHolder* view_holder;
Loading* loading;
};
typedef enum {
@@ -33,6 +48,9 @@ typedef enum {
LoaderMessageTypeStartByNameDetachedWithGuiError,
LoaderMessageTypeSignal,
LoaderMessageTypeGetApplicationName,
LoaderMessageTypeGetApplicationLaunchPath,
LoaderMessageTypeEnqueueLaunch,
LoaderMessageTypeClearLaunchQueue,
} LoaderMessageType;
typedef struct {
@@ -72,6 +90,7 @@ typedef struct {
union {
LoaderMessageStartByName start;
LoaderDeferredLaunchRecord defer_start;
LoaderMessageSignal signal;
FuriString* application_name;
};

View File

@@ -0,0 +1,32 @@
#include "loader_queue.h"
void loader_queue_item_clear(LoaderDeferredLaunchRecord* item) {
free(item->args);
free(item->name_or_path);
}
bool loader_queue_pop(LoaderLaunchQueue* queue, LoaderDeferredLaunchRecord* item) {
if(!queue->item_cnt) return false;
*item = queue->items[0];
queue->item_cnt--;
memmove(
&queue->items[0], &queue->items[1], queue->item_cnt * sizeof(LoaderDeferredLaunchRecord));
return true;
}
bool loader_queue_push(LoaderLaunchQueue* queue, LoaderDeferredLaunchRecord* item) {
if(queue->item_cnt == LOADER_QUEUE_MAX_SIZE) return false;
queue->items[queue->item_cnt] = *item;
queue->item_cnt++;
return true;
}
void loader_queue_clear(LoaderLaunchQueue* queue) {
for(size_t i = 0; i < queue->item_cnt; i++)
loader_queue_item_clear(&queue->items[i]);
queue->item_cnt = 0;
}

View File

@@ -0,0 +1,53 @@
#pragma once
#include <furi.h>
#include "loader.h"
#define LOADER_QUEUE_MAX_SIZE 4
typedef struct {
char* name_or_path;
char* args;
LoaderDeferredLaunchFlag flags;
} LoaderDeferredLaunchRecord;
typedef struct {
LoaderDeferredLaunchRecord items[LOADER_QUEUE_MAX_SIZE];
size_t item_cnt;
} LoaderLaunchQueue;
/**
* @brief Frees internal data in a `DeferredLaunchRecord`
*
* @param[out] item Record to clear
*/
void loader_queue_item_clear(LoaderDeferredLaunchRecord* item);
/**
* @brief Fetches the next item from the launch queue
*
* @param[inout] queue Queue instance
* @param[out] item Item output
*
* @return `true` if `item` was populated, `false` if queue is empty
*/
bool loader_queue_pop(LoaderLaunchQueue* queue, LoaderDeferredLaunchRecord* item);
/**
* @brief Puts an item into the launch queue
*
* @param[inout] queue Queue instance
* @param[in] item Item to put in the queue
*
* @return `true` if the item was put into the queue, `false` if there's no more
* space left
*/
bool loader_queue_push(LoaderLaunchQueue* queue, LoaderDeferredLaunchRecord* item);
/**
* @brief Clears the launch queue
*
* @param[inout] queue Queue instance
*/
void loader_queue_clear(LoaderLaunchQueue* queue);

View File

@@ -1,7 +1,6 @@
#include "namechanger.h"
#include <furi_hal.h>
#include <furi_hal_version.h>
#include <cli/cli.h>
#include <cli/cli_vcp.h>
#include <bt/bt_service/bt.h>
#include <storage/storage.h>
@@ -79,7 +78,7 @@ int32_t namechanger_on_system_start(void* p) {
// Wait for all required services to start and create their records
uint8_t timeout = 0;
while(!furi_record_exists(RECORD_CLI) || !furi_record_exists(RECORD_BT) ||
while(!furi_record_exists(RECORD_CLI_VCP) || !furi_record_exists(RECORD_BT) ||
!furi_record_exists(RECORD_STORAGE)) {
timeout++;
if(timeout > 250) {
@@ -91,11 +90,11 @@ int32_t namechanger_on_system_start(void* p) {
// Hehe bad code now here, bad bad bad, very bad, bad example, dont take it, make it better
if(namechanger_init()) {
Cli* cli = furi_record_open(RECORD_CLI);
cli_session_close(cli);
CliVcp* cli = furi_record_open(RECORD_CLI_VCP);
cli_vcp_disable(cli);
furi_delay_ms(2); // why i added delays here
cli_session_open(cli, &cli_vcp);
furi_record_close(RECORD_CLI);
cli_vcp_enable(cli);
furi_record_close(RECORD_CLI_VCP);
furi_delay_ms(3);
Bt* bt = furi_record_open(RECORD_BT);

View File

@@ -1,12 +1,14 @@
#include "power_cli.h"
#include <furi_hal.h>
#include <cli/cli.h>
#include <toolbox/cli/cli_command.h>
#include <cli/cli_main_commands.h>
#include <lib/toolbox/args.h>
#include <power/power_service/power.h>
#include <toolbox/pipe.h>
void power_cli_off(Cli* cli, FuriString* args) {
UNUSED(cli);
void power_cli_off(PipeSide* pipe, FuriString* args) {
UNUSED(pipe);
UNUSED(args);
Power* power = furi_record_open(RECORD_POWER);
printf("It's now safe to disconnect USB from your flipper\r\n");
@@ -14,22 +16,22 @@ void power_cli_off(Cli* cli, FuriString* args) {
power_off(power);
}
void power_cli_reboot(Cli* cli, FuriString* args) {
UNUSED(cli);
void power_cli_reboot(PipeSide* pipe, FuriString* args) {
UNUSED(pipe);
UNUSED(args);
Power* power = furi_record_open(RECORD_POWER);
power_reboot(power, PowerBootModeNormal);
}
void power_cli_reboot2dfu(Cli* cli, FuriString* args) {
UNUSED(cli);
void power_cli_reboot2dfu(PipeSide* pipe, FuriString* args) {
UNUSED(pipe);
UNUSED(args);
Power* power = furi_record_open(RECORD_POWER);
power_reboot(power, PowerBootModeDfu);
}
void power_cli_5v(Cli* cli, FuriString* args) {
UNUSED(cli);
void power_cli_5v(PipeSide* pipe, FuriString* args) {
UNUSED(pipe);
Power* power = furi_record_open(RECORD_POWER);
if(!furi_string_cmp(args, "0")) {
power_enable_otg(power, false);
@@ -42,8 +44,8 @@ void power_cli_5v(Cli* cli, FuriString* args) {
furi_record_close(RECORD_POWER);
}
void power_cli_3v3(Cli* cli, FuriString* args) {
UNUSED(cli);
void power_cli_3v3(PipeSide* pipe, FuriString* args) {
UNUSED(pipe);
if(!furi_string_cmp(args, "0")) {
furi_hal_power_disable_external_3_3v();
} else if(!furi_string_cmp(args, "1")) {
@@ -67,7 +69,7 @@ static void power_cli_command_print_usage(void) {
}
}
void power_cli(Cli* cli, FuriString* args, void* context) {
void power_cli(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(context);
FuriString* cmd;
cmd = furi_string_alloc();
@@ -79,28 +81,28 @@ void power_cli(Cli* cli, FuriString* args, void* context) {
}
if(furi_string_cmp_str(cmd, "off") == 0) {
power_cli_off(cli, args);
power_cli_off(pipe, args);
break;
}
if(furi_string_cmp_str(cmd, "reboot") == 0) {
power_cli_reboot(cli, args);
power_cli_reboot(pipe, args);
break;
}
if(furi_string_cmp_str(cmd, "reboot2dfu") == 0) {
power_cli_reboot2dfu(cli, args);
power_cli_reboot2dfu(pipe, args);
break;
}
if(furi_string_cmp_str(cmd, "5v") == 0) {
power_cli_5v(cli, args);
power_cli_5v(pipe, args);
break;
}
if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) {
if(furi_string_cmp_str(cmd, "3v3") == 0) {
power_cli_3v3(cli, args);
power_cli_3v3(pipe, args);
break;
}
}
@@ -113,10 +115,8 @@ void power_cli(Cli* cli, FuriString* args, void* context) {
void power_on_system_start(void) {
#ifdef SRV_CLI
Cli* cli = furi_record_open(RECORD_CLI);
cli_add_command(cli, "power", CliCommandFlagParallelSafe, power_cli, NULL);
CliRegistry* registry = furi_record_open(RECORD_CLI);
cli_registry_add_command(registry, "power", CliCommandFlagParallelSafe, power_cli, NULL);
furi_record_close(RECORD_CLI);
#else
UNUSED(power_cli);

View File

@@ -9,7 +9,8 @@
#include <furi.h>
#include <cli/cli.h>
#include <toolbox/cli/cli_command.h>
#include <cli/cli_main_commands.h>
#include <stdint.h>
#include <stdio.h>
#include <m-dict.h>
@@ -435,9 +436,14 @@ void rpc_on_system_start(void* p) {
rpc->busy_mutex = furi_mutex_alloc(FuriMutexTypeNormal);
Cli* cli = furi_record_open(RECORD_CLI);
cli_add_command(
cli, "start_rpc_session", CliCommandFlagParallelSafe, rpc_cli_command_start_session, rpc);
CliRegistry* registry = furi_record_open(RECORD_CLI);
cli_registry_add_command(
registry,
"start_rpc_session",
CliCommandFlagParallelSafe,
rpc_cli_command_start_session,
rpc);
furi_record_close(RECORD_CLI);
furi_record_create(RECORD_RPC, rpc);
}

View File

@@ -1,25 +1,26 @@
#include <cli/cli.h>
#include <toolbox/cli/cli_command.h>
#include <cli/cli_main_commands.h>
#include <furi.h>
#include <rpc/rpc.h>
#include <furi_hal.h>
#include <toolbox/pipe.h>
#define TAG "RpcCli"
typedef struct {
Cli* cli;
PipeSide* pipe;
bool session_close_request;
FuriSemaphore* terminate_semaphore;
} CliRpc;
#define CLI_READ_BUFFER_SIZE 64
#define CLI_READ_BUFFER_SIZE 64UL
static void rpc_cli_send_bytes_callback(void* context, uint8_t* bytes, size_t bytes_len) {
furi_assert(context);
furi_assert(bytes);
furi_assert(bytes_len > 0);
CliRpc* cli_rpc = context;
cli_write(cli_rpc->cli, bytes, bytes_len);
pipe_send(cli_rpc->pipe, bytes, bytes_len);
}
static void rpc_cli_session_close_callback(void* context) {
@@ -36,9 +37,9 @@ static void rpc_cli_session_terminated_callback(void* context) {
furi_semaphore_release(cli_rpc->terminate_semaphore);
}
void rpc_cli_command_start_session(Cli* cli, FuriString* args, void* context) {
void rpc_cli_command_start_session(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(args);
furi_assert(cli);
furi_assert(pipe);
furi_assert(context);
Rpc* rpc = context;
@@ -53,7 +54,7 @@ void rpc_cli_command_start_session(Cli* cli, FuriString* args, void* context) {
return;
}
CliRpc cli_rpc = {.cli = cli, .session_close_request = false};
CliRpc cli_rpc = {.pipe = pipe, .session_close_request = false};
cli_rpc.terminate_semaphore = furi_semaphore_alloc(1, 0);
rpc_session_set_context(rpc_session, &cli_rpc);
rpc_session_set_send_bytes_callback(rpc_session, rpc_cli_send_bytes_callback);
@@ -64,8 +65,9 @@ void rpc_cli_command_start_session(Cli* cli, FuriString* args, void* context) {
size_t size_received = 0;
while(1) {
size_received = cli_read_timeout(cli_rpc.cli, buffer, CLI_READ_BUFFER_SIZE, 50);
if(!cli_is_connected(cli_rpc.cli) || cli_rpc.session_close_request) {
size_t to_receive = CLAMP(pipe_bytes_available(cli_rpc.pipe), CLI_READ_BUFFER_SIZE, 1UL);
size_received = pipe_receive(cli_rpc.pipe, buffer, to_receive);
if(size_received < to_receive || cli_rpc.session_close_request) {
break;
}

View File

@@ -5,7 +5,7 @@
#include <pb_decode.h>
#include <pb_encode.h>
#include <flipper.pb.h>
#include <cli/cli.h>
#include <toolbox/pipe.h>
#ifdef __cplusplus
extern "C" {
@@ -46,7 +46,7 @@ void rpc_desktop_free(void* ctx);
void rpc_debug_print_message(const PB_Main* message);
void rpc_debug_print_data(const char* prefix, uint8_t* buffer, size_t size);
void rpc_cli_command_start_session(Cli* cli, FuriString* args, void* context);
void rpc_cli_command_start_session(PipeSide* pipe, FuriString* args, void* context);
PB_CommandStatus rpc_system_storage_get_error(FS_Error fs_error);

View File

@@ -1,7 +1,9 @@
#include <furi.h>
#include <furi_hal.h>
#include <cli/cli.h>
#include <toolbox/cli/cli_command.h>
#include <toolbox/cli/cli_ansi.h>
#include <cli/cli_main_commands.h>
#include <lib/toolbox/args.h>
#include <lib/toolbox/dir_walk.h>
#include <lib/toolbox/md5_calc.h>
@@ -10,6 +12,7 @@
#include <storage/storage.h>
#include <storage/storage_sd_api.h>
#include <power/power_service/power.h>
#include <toolbox/pipe.h>
#define MAX_NAME_LENGTH 255
@@ -19,8 +22,8 @@ static void storage_cli_print_error(FS_Error error) {
printf("Storage error: %s\r\n", storage_error_get_desc(error));
}
static void storage_cli_info(Cli* cli, FuriString* path, FuriString* args) {
UNUSED(cli);
static void storage_cli_info(PipeSide* pipe, FuriString* path, FuriString* args) {
UNUSED(pipe);
UNUSED(args);
Storage* api = furi_record_open(RECORD_STORAGE);
@@ -69,13 +72,14 @@ static void storage_cli_info(Cli* cli, FuriString* path, FuriString* args) {
furi_record_close(RECORD_STORAGE);
}
static void storage_cli_format(Cli* cli, FuriString* path, FuriString* args) {
static void storage_cli_format(PipeSide* pipe, FuriString* path, FuriString* args) {
UNUSED(pipe);
UNUSED(args);
if(furi_string_cmp_str(path, STORAGE_INT_PATH_PREFIX) == 0) {
storage_cli_print_error(FSE_NOT_IMPLEMENTED);
} else if(furi_string_cmp_str(path, STORAGE_EXT_PATH_PREFIX) == 0) {
printf("Formatting SD card, All data will be lost! Are you sure (y/n)?\r\n");
char answer = cli_getc(cli);
char answer = getchar();
if(answer == 'y' || answer == 'Y') {
Storage* api = furi_record_open(RECORD_STORAGE);
printf("Formatting, please wait...\r\n");
@@ -96,8 +100,8 @@ static void storage_cli_format(Cli* cli, FuriString* path, FuriString* args) {
}
}
static void storage_cli_list(Cli* cli, FuriString* path, FuriString* args) {
UNUSED(cli);
static void storage_cli_list(PipeSide* pipe, FuriString* path, FuriString* args) {
UNUSED(pipe);
UNUSED(args);
if(furi_string_cmp_str(path, "/") == 0) {
printf("\t[D] int\r\n");
@@ -134,13 +138,13 @@ static void storage_cli_list(Cli* cli, FuriString* path, FuriString* args) {
}
}
static void storage_cli_tree(Cli* cli, FuriString* path, FuriString* args) {
static void storage_cli_tree(PipeSide* pipe, FuriString* path, FuriString* args) {
UNUSED(args);
if(furi_string_cmp_str(path, "/") == 0) {
furi_string_set(path, STORAGE_INT_PATH_PREFIX);
storage_cli_tree(cli, path, NULL);
storage_cli_tree(pipe, path, NULL);
furi_string_set(path, STORAGE_EXT_PATH_PREFIX);
storage_cli_tree(cli, path, NULL);
storage_cli_tree(pipe, path, NULL);
} else {
Storage* api = furi_record_open(RECORD_STORAGE);
DirWalk* dir_walk = dir_walk_alloc(api);
@@ -176,8 +180,8 @@ static void storage_cli_tree(Cli* cli, FuriString* path, FuriString* args) {
}
}
static void storage_cli_read(Cli* cli, FuriString* path, FuriString* args) {
UNUSED(cli);
static void storage_cli_read(PipeSide* pipe, FuriString* path, FuriString* args) {
UNUSED(pipe);
UNUSED(args);
Storage* api = furi_record_open(RECORD_STORAGE);
File* file = storage_file_alloc(api);
@@ -208,7 +212,8 @@ static void storage_cli_read(Cli* cli, FuriString* path, FuriString* args) {
furi_record_close(RECORD_STORAGE);
}
static void storage_cli_write(Cli* cli, FuriString* path, FuriString* args) {
static void storage_cli_write(PipeSide* pipe, FuriString* path, FuriString* args) {
UNUSED(pipe);
UNUSED(args);
Storage* api = furi_record_open(RECORD_STORAGE);
File* file = storage_file_alloc(api);
@@ -222,9 +227,9 @@ static void storage_cli_write(Cli* cli, FuriString* path, FuriString* args) {
uint32_t read_index = 0;
while(true) {
uint8_t symbol = cli_getc(cli);
uint8_t symbol = getchar();
if(symbol == CliSymbolAsciiETX) {
if(symbol == CliKeyETX) {
size_t write_size = read_index % buffer_size;
if(write_size > 0) {
@@ -263,7 +268,8 @@ static void storage_cli_write(Cli* cli, FuriString* path, FuriString* args) {
furi_record_close(RECORD_STORAGE);
}
static void storage_cli_read_chunks(Cli* cli, FuriString* path, FuriString* args) {
static void storage_cli_read_chunks(PipeSide* pipe, FuriString* path, FuriString* args) {
UNUSED(pipe);
Storage* api = furi_record_open(RECORD_STORAGE);
File* file = storage_file_alloc(api);
@@ -280,7 +286,7 @@ static void storage_cli_read_chunks(Cli* cli, FuriString* path, FuriString* args
uint8_t* data = malloc(buffer_size);
while(file_size > 0) {
printf("\r\nReady?\r\n");
cli_getc(cli);
getchar();
size_t read_size = storage_file_read(file, data, buffer_size);
for(size_t i = 0; i < read_size; i++) {
@@ -302,31 +308,34 @@ static void storage_cli_read_chunks(Cli* cli, FuriString* path, FuriString* args
furi_record_close(RECORD_STORAGE);
}
static void storage_cli_write_chunk(Cli* cli, FuriString* path, FuriString* args) {
static void storage_cli_write_chunk(PipeSide* pipe, FuriString* path, FuriString* args) {
Storage* api = furi_record_open(RECORD_STORAGE);
File* file = storage_file_alloc(api);
uint32_t buffer_size;
if(strint_to_uint32(furi_string_get_cstr(args), NULL, &buffer_size, 10) !=
uint32_t need_to_read;
if(strint_to_uint32(furi_string_get_cstr(args), NULL, &need_to_read, 10) !=
StrintParseNoError) {
storage_cli_print_usage();
} else {
if(storage_file_open(file, furi_string_get_cstr(path), FSAM_WRITE, FSOM_OPEN_APPEND)) {
printf("Ready\r\n");
const size_t buffer_size = 1024;
uint8_t* buffer = malloc(buffer_size);
if(buffer_size) {
uint8_t* buffer = malloc(buffer_size);
while(need_to_read) {
size_t to_read_this_time = MIN(buffer_size, need_to_read);
size_t read_this_time = pipe_receive(pipe, buffer, to_read_this_time);
if(read_this_time != to_read_this_time) break;
size_t read_bytes = cli_read(cli, buffer, buffer_size);
size_t written_size = storage_file_write(file, buffer, read_bytes);
if(written_size != buffer_size) {
size_t wrote_this_time = storage_file_write(file, buffer, read_this_time);
if(wrote_this_time != read_this_time) {
storage_cli_print_error(storage_file_get_error(file));
break;
}
free(buffer);
need_to_read -= read_this_time;
}
free(buffer);
} else {
storage_cli_print_error(storage_file_get_error(file));
}
@@ -337,8 +346,8 @@ static void storage_cli_write_chunk(Cli* cli, FuriString* path, FuriString* args
furi_record_close(RECORD_STORAGE);
}
static void storage_cli_stat(Cli* cli, FuriString* path, FuriString* args) {
UNUSED(cli);
static void storage_cli_stat(PipeSide* pipe, FuriString* path, FuriString* args) {
UNUSED(pipe);
UNUSED(args);
Storage* api = furi_record_open(RECORD_STORAGE);
@@ -379,8 +388,8 @@ static void storage_cli_stat(Cli* cli, FuriString* path, FuriString* args) {
furi_record_close(RECORD_STORAGE);
}
static void storage_cli_timestamp(Cli* cli, FuriString* path, FuriString* args) {
UNUSED(cli);
static void storage_cli_timestamp(PipeSide* pipe, FuriString* path, FuriString* args) {
UNUSED(pipe);
UNUSED(args);
Storage* api = furi_record_open(RECORD_STORAGE);
@@ -396,8 +405,8 @@ static void storage_cli_timestamp(Cli* cli, FuriString* path, FuriString* args)
furi_record_close(RECORD_STORAGE);
}
static void storage_cli_copy(Cli* cli, FuriString* old_path, FuriString* args) {
UNUSED(cli);
static void storage_cli_copy(PipeSide* pipe, FuriString* old_path, FuriString* args) {
UNUSED(pipe);
Storage* api = furi_record_open(RECORD_STORAGE);
FuriString* new_path;
new_path = furi_string_alloc();
@@ -417,8 +426,8 @@ static void storage_cli_copy(Cli* cli, FuriString* old_path, FuriString* args) {
furi_record_close(RECORD_STORAGE);
}
static void storage_cli_remove(Cli* cli, FuriString* path, FuriString* args) {
UNUSED(cli);
static void storage_cli_remove(PipeSide* pipe, FuriString* path, FuriString* args) {
UNUSED(pipe);
UNUSED(args);
Storage* api = furi_record_open(RECORD_STORAGE);
FS_Error error = storage_common_remove(api, furi_string_get_cstr(path));
@@ -430,8 +439,8 @@ static void storage_cli_remove(Cli* cli, FuriString* path, FuriString* args) {
furi_record_close(RECORD_STORAGE);
}
static void storage_cli_rename(Cli* cli, FuriString* old_path, FuriString* args) {
UNUSED(cli);
static void storage_cli_rename(PipeSide* pipe, FuriString* old_path, FuriString* args) {
UNUSED(pipe);
Storage* api = furi_record_open(RECORD_STORAGE);
FuriString* new_path;
new_path = furi_string_alloc();
@@ -451,8 +460,8 @@ static void storage_cli_rename(Cli* cli, FuriString* old_path, FuriString* args)
furi_record_close(RECORD_STORAGE);
}
static void storage_cli_mkdir(Cli* cli, FuriString* path, FuriString* args) {
UNUSED(cli);
static void storage_cli_mkdir(PipeSide* pipe, FuriString* path, FuriString* args) {
UNUSED(pipe);
UNUSED(args);
Storage* api = furi_record_open(RECORD_STORAGE);
FS_Error error = storage_common_mkdir(api, furi_string_get_cstr(path));
@@ -464,8 +473,8 @@ static void storage_cli_mkdir(Cli* cli, FuriString* path, FuriString* args) {
furi_record_close(RECORD_STORAGE);
}
static void storage_cli_md5(Cli* cli, FuriString* path, FuriString* args) {
UNUSED(cli);
static void storage_cli_md5(PipeSide* pipe, FuriString* path, FuriString* args) {
UNUSED(pipe);
UNUSED(args);
Storage* api = furi_record_open(RECORD_STORAGE);
File* file = storage_file_alloc(api);
@@ -491,8 +500,8 @@ static bool tar_extract_file_callback(const char* name, bool is_directory, void*
return true;
}
static void storage_cli_extract(Cli* cli, FuriString* old_path, FuriString* args) {
UNUSED(cli);
static void storage_cli_extract(PipeSide* pipe, FuriString* old_path, FuriString* args) {
UNUSED(pipe);
FuriString* new_path = furi_string_alloc();
if(!args_read_probably_quoted_string_and_trim(args, new_path)) {
@@ -526,7 +535,7 @@ static void storage_cli_extract(Cli* cli, FuriString* old_path, FuriString* args
furi_record_close(RECORD_STORAGE);
}
typedef void (*StorageCliCommandCallback)(Cli* cli, FuriString* path, FuriString* args);
typedef void (*StorageCliCommandCallback)(PipeSide* pipe, FuriString* path, FuriString* args);
typedef struct {
const char* command;
@@ -631,7 +640,7 @@ static void storage_cli_print_usage(void) {
}
}
void storage_cli(Cli* cli, FuriString* args, void* context) {
void storage_cli(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(context);
FuriString* cmd;
FuriString* path;
@@ -653,7 +662,7 @@ void storage_cli(Cli* cli, FuriString* args, void* context) {
for(; i < COUNT_OF(storage_cli_commands); ++i) {
const StorageCliCommand* command_descr = &storage_cli_commands[i];
if(furi_string_cmp_str(cmd, command_descr->command) == 0) {
command_descr->impl(cli, path, args);
command_descr->impl(pipe, path, args);
break;
}
}
@@ -667,11 +676,12 @@ void storage_cli(Cli* cli, FuriString* args, void* context) {
furi_string_free(cmd);
}
static void storage_cli_factory_reset(Cli* cli, FuriString* args, void* context) {
static void storage_cli_factory_reset(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(pipe);
UNUSED(args);
UNUSED(context);
printf("All data will be lost! Are you sure (y/n)?\r\n");
char c = cli_getc(cli);
char c = getchar();
if(c == 'y' || c == 'Y') {
printf("Data will be wiped after reboot.\r\n");
@@ -687,10 +697,15 @@ static void storage_cli_factory_reset(Cli* cli, FuriString* args, void* context)
void storage_on_system_start(void) {
#ifdef SRV_CLI
Cli* cli = furi_record_open(RECORD_CLI);
cli_add_command(cli, RECORD_STORAGE, CliCommandFlagParallelSafe, storage_cli, NULL);
cli_add_command(
cli, "factory_reset", CliCommandFlagParallelSafe, storage_cli_factory_reset, NULL);
CliRegistry* registry = furi_record_open(RECORD_CLI);
cli_registry_add_command(
registry,
"storage",
CliCommandFlagParallelSafe | CliCommandFlagUseShellThread,
storage_cli,
NULL);
cli_registry_add_command(
registry, "factory_reset", CliCommandFlagParallelSafe, storage_cli_factory_reset, NULL);
furi_record_close(RECORD_CLI);
#else
UNUSED(storage_cli_factory_reset);