mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-04-24 03:29:57 -07:00
Merge remote-tracking branch 'ofw/dev' into mntm-dev --nobuild
This commit is contained in:
@@ -45,7 +45,9 @@
|
||||
- OFW: SDK 0.3
|
||||
- Backport of missing features to new `gui/widget` (by @Willy-JL)
|
||||
- UART framing data/stop/parity bits options in `serial` module (by @portasynthinca3)
|
||||
- OFW: New JS value destructuring (by @portasynthinca3)
|
||||
- OFW: Alarm: Snooze, timeouts, and dismissing from the locked state (by @Astrrra)
|
||||
- OFW: BLE: Advertising improvements, 128bit service UUID support, manufacturer scan response data support (by @bettse)
|
||||
- OFW: Furi: UART framing mode selection, support for different data/stop/parity bits (by @portasynthinca3)
|
||||
- OFW: GUI: Widget elements for line, rect and circle with fill options (by @Willy-JL)
|
||||
|
||||
@@ -90,6 +92,7 @@
|
||||
- Infrared:
|
||||
- OFW: Add Fujitsu ASTG12LVCC to AC Universal Remote (by @KereruA0i)
|
||||
- OFW: Increase max carrier limit to 1000000 (by @skotopes)
|
||||
- OFW: CLI: New CLI architecture, some text formatting, better stability and less RAM usage (by @portasynthinca3)
|
||||
- OFW: Power: Added OTG controls to Power service, remembers OTG when unplugging USB (by @Astrrra & @skotopes)
|
||||
- OFW: GUI: Updated Button Panel with more options for button handling (by @Akiva-Cohen)
|
||||
- Furi:
|
||||
@@ -125,5 +128,6 @@
|
||||
- OFW: uFBT: Bumped action version in example github workflow for project template (by @hedger)
|
||||
|
||||
### Removed:
|
||||
- Apps: CLI-GUI Bridge: Temporarily removed due to breakage after OFW API changes
|
||||
- JS: Removed old `widget` module, replaced by new `gui/widget` view
|
||||
- MNTM: Removed Charge Cap option, replaced by Charge Limit in Power Settings
|
||||
|
||||
15
SConstruct
15
SConstruct
@@ -417,6 +417,21 @@ distenv.PhonyTarget(
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
# Measure CLI loopback performance
|
||||
distenv.PhonyTarget(
|
||||
"cli_perf",
|
||||
[
|
||||
[
|
||||
"${PYTHON3}",
|
||||
"${FBT_SCRIPT_DIR}/serial_cli_perf.py",
|
||||
"-p",
|
||||
"${FLIP_PORT}",
|
||||
"${ARGS}",
|
||||
]
|
||||
],
|
||||
)
|
||||
|
||||
# Update WiFi devboard firmware with release channel
|
||||
distenv.PhonyTarget(
|
||||
"devboard_flash",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <music_worker/music_worker.h>
|
||||
#include <cli/cli.h>
|
||||
#include <toolbox/args.h>
|
||||
#include <toolbox/pipe.h>
|
||||
|
||||
#define TAG "SpeakerDebug"
|
||||
|
||||
@@ -37,8 +38,8 @@ static void speaker_app_free(SpeakerDebugApp* app) {
|
||||
free(app);
|
||||
}
|
||||
|
||||
static void speaker_app_cli(Cli* cli, FuriString* args, void* context) {
|
||||
UNUSED(cli);
|
||||
static void speaker_app_cli(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(pipe);
|
||||
|
||||
SpeakerDebugApp* app = (SpeakerDebugApp*)context;
|
||||
SpeakerDebugAppMessage message;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include <cli/cli.h>
|
||||
#include <toolbox/path.h>
|
||||
#include <toolbox/pipe.h>
|
||||
#include <loader/loader.h>
|
||||
#include <storage/storage.h>
|
||||
#include <dialogs/dialogs.h>
|
||||
@@ -26,7 +27,7 @@ struct TestRunner {
|
||||
NotificationApp* notification;
|
||||
|
||||
// Temporary used things
|
||||
Cli* cli;
|
||||
PipeSide* pipe;
|
||||
FuriString* args;
|
||||
|
||||
// ELF related stuff
|
||||
@@ -42,14 +43,14 @@ struct TestRunner {
|
||||
size_t total_failed;
|
||||
};
|
||||
|
||||
TestRunner* test_runner_alloc(Cli* cli, FuriString* args) {
|
||||
TestRunner* test_runner_alloc(PipeSide* pipe, FuriString* args) {
|
||||
TestRunner* instance = malloc(sizeof(TestRunner));
|
||||
|
||||
instance->storage = furi_record_open(RECORD_STORAGE);
|
||||
instance->loader = furi_record_open(RECORD_LOADER);
|
||||
instance->notification = furi_record_open(RECORD_NOTIFICATION);
|
||||
|
||||
instance->cli = cli;
|
||||
instance->pipe = pipe;
|
||||
instance->args = args;
|
||||
|
||||
instance->composite_resolver = composite_api_resolver_alloc();
|
||||
@@ -153,7 +154,7 @@ static void test_runner_run_internal(TestRunner* instance) {
|
||||
}
|
||||
|
||||
while(true) {
|
||||
if(instance->cli && cli_cmd_interrupt_received(instance->cli)) {
|
||||
if(instance->cli && cli_is_pipe_broken_or_is_etx_next_char(instance->pipe)) {
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <furi.h>
|
||||
#include <toolbox/pipe.h>
|
||||
|
||||
typedef struct TestRunner TestRunner;
|
||||
typedef struct Cli Cli;
|
||||
|
||||
TestRunner* test_runner_alloc(Cli* cli, FuriString* args);
|
||||
TestRunner* test_runner_alloc(PipeSide* pipe, FuriString* args);
|
||||
|
||||
void test_runner_free(TestRunner* isntance);
|
||||
void test_runner_free(TestRunner* instance);
|
||||
|
||||
void test_runner_run(TestRunner* isntance);
|
||||
void test_runner_run(TestRunner* instance);
|
||||
|
||||
@@ -6,9 +6,12 @@
|
||||
|
||||
#include <storage/storage.h>
|
||||
#include <applications/system/js_app/js_thread.h>
|
||||
#include <applications/system/js_app/js_value.h>
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define TAG "JsUnitTests"
|
||||
|
||||
#define JS_SCRIPT_PATH(name) EXT_PATH("unit_tests/js/" name ".js")
|
||||
|
||||
typedef enum {
|
||||
@@ -73,7 +76,311 @@ MU_TEST(js_test_storage) {
|
||||
js_test_run(JS_SCRIPT_PATH("storage"));
|
||||
}
|
||||
|
||||
static void js_value_test_compatibility_matrix(struct mjs* mjs) {
|
||||
static const JsValueType types[] = {
|
||||
JsValueTypeAny,
|
||||
JsValueTypeAnyArray,
|
||||
JsValueTypeAnyObject,
|
||||
JsValueTypeFunction,
|
||||
JsValueTypeRawPointer,
|
||||
JsValueTypeInt32,
|
||||
JsValueTypeDouble,
|
||||
JsValueTypeString,
|
||||
JsValueTypeBool,
|
||||
};
|
||||
|
||||
mjs_val_t values[] = {
|
||||
mjs_mk_undefined(),
|
||||
mjs_mk_foreign(mjs, (void*)0xDEADBEEF),
|
||||
mjs_mk_array(mjs),
|
||||
mjs_mk_object(mjs),
|
||||
mjs_mk_number(mjs, 123.456),
|
||||
mjs_mk_string(mjs, "test", ~0, false),
|
||||
mjs_mk_boolean(mjs, true),
|
||||
};
|
||||
|
||||
// for proper matrix formatting and better readability
|
||||
#define YES true
|
||||
#define NO_ false
|
||||
static const bool success_matrix[COUNT_OF(types)][COUNT_OF(values)] = {
|
||||
// types:
|
||||
{YES, YES, YES, YES, YES, YES, YES}, // any
|
||||
{NO_, NO_, YES, NO_, NO_, NO_, NO_}, // array
|
||||
{NO_, NO_, YES, YES, NO_, NO_, NO_}, // obj
|
||||
{NO_, NO_, NO_, NO_, NO_, NO_, NO_}, // fn
|
||||
{NO_, YES, NO_, NO_, NO_, NO_, NO_}, // ptr
|
||||
{NO_, NO_, NO_, NO_, YES, NO_, NO_}, // int32
|
||||
{NO_, NO_, NO_, NO_, YES, NO_, NO_}, // double
|
||||
{NO_, NO_, NO_, NO_, NO_, YES, NO_}, // str
|
||||
{NO_, NO_, NO_, NO_, NO_, NO_, YES}, // bool
|
||||
//
|
||||
//und ptr arr obj num str bool <- values
|
||||
};
|
||||
#undef NO_
|
||||
#undef YES
|
||||
|
||||
for(size_t i = 0; i < COUNT_OF(types); i++) {
|
||||
for(size_t j = 0; j < COUNT_OF(values); j++) {
|
||||
const JsValueDeclaration declaration = {
|
||||
.type = types[i],
|
||||
.n_children = 0,
|
||||
};
|
||||
// we only care about the status, not the result. double has the largest size out of
|
||||
// all the results
|
||||
uint8_t result[sizeof(double)];
|
||||
JsValueParseStatus status;
|
||||
JS_VALUE_PARSE(
|
||||
mjs,
|
||||
JS_VALUE_PARSE_SOURCE_VALUE(&declaration),
|
||||
JsValueParseFlagNone,
|
||||
&status,
|
||||
&values[j],
|
||||
result);
|
||||
if((status == JsValueParseStatusOk) != success_matrix[i][j]) {
|
||||
FURI_LOG_E(TAG, "type %zu, value %zu", i, j);
|
||||
mu_fail("see serial logs");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void js_value_test_literal(struct mjs* mjs) {
|
||||
static const JsValueType types[] = {
|
||||
JsValueTypeAny,
|
||||
JsValueTypeAnyArray,
|
||||
JsValueTypeAnyObject,
|
||||
};
|
||||
|
||||
mjs_val_t values[] = {
|
||||
mjs_mk_undefined(),
|
||||
mjs_mk_array(mjs),
|
||||
mjs_mk_object(mjs),
|
||||
};
|
||||
|
||||
mu_assert_int_eq(COUNT_OF(types), COUNT_OF(values));
|
||||
for(size_t i = 0; i < COUNT_OF(types); i++) {
|
||||
const JsValueDeclaration declaration = {
|
||||
.type = types[i],
|
||||
.n_children = 0,
|
||||
};
|
||||
mjs_val_t result;
|
||||
JsValueParseStatus status;
|
||||
JS_VALUE_PARSE(
|
||||
mjs,
|
||||
JS_VALUE_PARSE_SOURCE_VALUE(&declaration),
|
||||
JsValueParseFlagNone,
|
||||
&status,
|
||||
&values[i],
|
||||
&result);
|
||||
mu_assert_int_eq(JsValueParseStatusOk, status);
|
||||
mu_assert(result == values[i], "wrong result");
|
||||
}
|
||||
}
|
||||
|
||||
static void js_value_test_primitive(
|
||||
struct mjs* mjs,
|
||||
JsValueType type,
|
||||
const void* c_value,
|
||||
size_t c_value_size,
|
||||
mjs_val_t js_val) {
|
||||
const JsValueDeclaration declaration = {
|
||||
.type = type,
|
||||
.n_children = 0,
|
||||
};
|
||||
uint8_t result[c_value_size];
|
||||
JsValueParseStatus status;
|
||||
JS_VALUE_PARSE(
|
||||
mjs,
|
||||
JS_VALUE_PARSE_SOURCE_VALUE(&declaration),
|
||||
JsValueParseFlagNone,
|
||||
&status,
|
||||
&js_val,
|
||||
result);
|
||||
mu_assert_int_eq(JsValueParseStatusOk, status);
|
||||
if(type == JsValueTypeString) {
|
||||
const char* result_str = *(const char**)&result;
|
||||
mu_assert_string_eq(c_value, result_str);
|
||||
} else {
|
||||
mu_assert_mem_eq(c_value, result, c_value_size);
|
||||
}
|
||||
}
|
||||
|
||||
static void js_value_test_primitives(struct mjs* mjs) {
|
||||
int32_t i32 = 123;
|
||||
js_value_test_primitive(mjs, JsValueTypeInt32, &i32, sizeof(i32), mjs_mk_number(mjs, i32));
|
||||
|
||||
double dbl = 123.456;
|
||||
js_value_test_primitive(mjs, JsValueTypeDouble, &dbl, sizeof(dbl), mjs_mk_number(mjs, dbl));
|
||||
|
||||
const char* str = "test";
|
||||
js_value_test_primitive(
|
||||
mjs, JsValueTypeString, str, strlen(str) + 1, mjs_mk_string(mjs, str, ~0, false));
|
||||
|
||||
bool boolean = true;
|
||||
js_value_test_primitive(
|
||||
mjs, JsValueTypeBool, &boolean, sizeof(boolean), mjs_mk_boolean(mjs, boolean));
|
||||
}
|
||||
|
||||
static uint32_t
|
||||
js_value_test_enum(struct mjs* mjs, const JsValueDeclaration* decl, const char* value) {
|
||||
mjs_val_t str = mjs_mk_string(mjs, value, ~0, false);
|
||||
uint32_t result;
|
||||
JsValueParseStatus status;
|
||||
JS_VALUE_PARSE(
|
||||
mjs, JS_VALUE_PARSE_SOURCE_VALUE(decl), JsValueParseFlagNone, &status, &str, &result);
|
||||
if(status != JsValueParseStatusOk) return 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
static void js_value_test_enums(struct mjs* mjs) {
|
||||
static const JsValueEnumVariant enum_1_variants[] = {
|
||||
{"variant 1", 1},
|
||||
{"variant 2", 2},
|
||||
{"variant 3", 3},
|
||||
};
|
||||
static const JsValueDeclaration enum_1 = JS_VALUE_ENUM(uint32_t, enum_1_variants);
|
||||
|
||||
static const JsValueEnumVariant enum_2_variants[] = {
|
||||
{"read", 4},
|
||||
{"write", 8},
|
||||
};
|
||||
static const JsValueDeclaration enum_2 = JS_VALUE_ENUM(uint32_t, enum_2_variants);
|
||||
|
||||
mu_assert_int_eq(1, js_value_test_enum(mjs, &enum_1, "variant 1"));
|
||||
mu_assert_int_eq(2, js_value_test_enum(mjs, &enum_1, "variant 2"));
|
||||
mu_assert_int_eq(3, js_value_test_enum(mjs, &enum_1, "variant 3"));
|
||||
mu_assert_int_eq(0, js_value_test_enum(mjs, &enum_1, "not a thing"));
|
||||
|
||||
mu_assert_int_eq(0, js_value_test_enum(mjs, &enum_2, "variant 1"));
|
||||
mu_assert_int_eq(0, js_value_test_enum(mjs, &enum_2, "variant 2"));
|
||||
mu_assert_int_eq(0, js_value_test_enum(mjs, &enum_2, "variant 3"));
|
||||
mu_assert_int_eq(0, js_value_test_enum(mjs, &enum_2, "not a thing"));
|
||||
mu_assert_int_eq(4, js_value_test_enum(mjs, &enum_2, "read"));
|
||||
mu_assert_int_eq(8, js_value_test_enum(mjs, &enum_2, "write"));
|
||||
}
|
||||
|
||||
static void js_value_test_object(struct mjs* mjs) {
|
||||
static const JsValueDeclaration int_decl = JS_VALUE_SIMPLE(JsValueTypeInt32);
|
||||
|
||||
static const JsValueDeclaration str_decl = JS_VALUE_SIMPLE(JsValueTypeString);
|
||||
|
||||
static const JsValueEnumVariant enum_variants[] = {
|
||||
{"variant 1", 1},
|
||||
{"variant 2", 2},
|
||||
{"variant 3", 3},
|
||||
};
|
||||
static const JsValueDeclaration enum_decl = JS_VALUE_ENUM(uint32_t, enum_variants);
|
||||
|
||||
static const JsValueObjectField fields[] = {
|
||||
{"int", &int_decl},
|
||||
{"str", &str_decl},
|
||||
{"enum", &enum_decl},
|
||||
};
|
||||
static const JsValueDeclaration object_decl = JS_VALUE_OBJECT(fields);
|
||||
|
||||
mjs_val_t object = mjs_mk_object(mjs);
|
||||
JS_ASSIGN_MULTI(mjs, object) {
|
||||
JS_FIELD("str", mjs_mk_string(mjs, "Helloooo!", ~0, false));
|
||||
JS_FIELD("int", mjs_mk_number(mjs, 123));
|
||||
JS_FIELD("enum", mjs_mk_string(mjs, "variant 2", ~0, false));
|
||||
}
|
||||
|
||||
const char* result_str;
|
||||
int32_t result_int;
|
||||
uint32_t result_enum;
|
||||
JsValueParseStatus status;
|
||||
JS_VALUE_PARSE(
|
||||
mjs,
|
||||
JS_VALUE_PARSE_SOURCE_VALUE(&object_decl),
|
||||
JsValueParseFlagNone,
|
||||
&status,
|
||||
&object,
|
||||
&result_int,
|
||||
&result_str,
|
||||
&result_enum);
|
||||
mu_assert_int_eq(JsValueParseStatusOk, status);
|
||||
mu_assert_string_eq("Helloooo!", result_str);
|
||||
mu_assert_int_eq(123, result_int);
|
||||
mu_assert_int_eq(2, result_enum);
|
||||
}
|
||||
|
||||
static void js_value_test_default(struct mjs* mjs) {
|
||||
static const JsValueDeclaration int_decl =
|
||||
JS_VALUE_SIMPLE_W_DEFAULT(JsValueTypeInt32, int32_val, 123);
|
||||
static const JsValueDeclaration str_decl = JS_VALUE_SIMPLE(JsValueTypeString);
|
||||
|
||||
static const JsValueObjectField fields[] = {
|
||||
{"int", &int_decl},
|
||||
{"str", &str_decl},
|
||||
};
|
||||
static const JsValueDeclaration object_decl = JS_VALUE_OBJECT(fields);
|
||||
|
||||
mjs_val_t object = mjs_mk_object(mjs);
|
||||
JS_ASSIGN_MULTI(mjs, object) {
|
||||
JS_FIELD("str", mjs_mk_string(mjs, "Helloooo!", ~0, false));
|
||||
JS_FIELD("int", mjs_mk_undefined());
|
||||
}
|
||||
|
||||
const char* result_str;
|
||||
int32_t result_int;
|
||||
JsValueParseStatus status;
|
||||
JS_VALUE_PARSE(
|
||||
mjs,
|
||||
JS_VALUE_PARSE_SOURCE_VALUE(&object_decl),
|
||||
JsValueParseFlagNone,
|
||||
&status,
|
||||
&object,
|
||||
&result_int,
|
||||
&result_str);
|
||||
mu_assert_string_eq("Helloooo!", result_str);
|
||||
mu_assert_int_eq(123, result_int);
|
||||
}
|
||||
|
||||
static void js_value_test_args_fn(struct mjs* mjs) {
|
||||
static const JsValueDeclaration arg_list[] = {
|
||||
JS_VALUE_SIMPLE(JsValueTypeInt32),
|
||||
JS_VALUE_SIMPLE(JsValueTypeInt32),
|
||||
JS_VALUE_SIMPLE(JsValueTypeInt32),
|
||||
};
|
||||
static const JsValueArguments args = JS_VALUE_ARGS(arg_list);
|
||||
|
||||
int32_t a, b, c;
|
||||
JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &args, &a, &b, &c);
|
||||
|
||||
mu_assert_int_eq(123, a);
|
||||
mu_assert_int_eq(456, b);
|
||||
mu_assert_int_eq(-420, c);
|
||||
}
|
||||
|
||||
static void js_value_test_args(struct mjs* mjs) {
|
||||
mjs_val_t function = MJS_MK_FN(js_value_test_args_fn);
|
||||
|
||||
mjs_val_t result;
|
||||
mjs_val_t args[] = {
|
||||
mjs_mk_number(mjs, 123),
|
||||
mjs_mk_number(mjs, 456),
|
||||
mjs_mk_number(mjs, -420),
|
||||
};
|
||||
mu_assert_int_eq(
|
||||
MJS_OK, mjs_apply(mjs, &result, function, MJS_UNDEFINED, COUNT_OF(args), args));
|
||||
}
|
||||
|
||||
MU_TEST(js_value_test) {
|
||||
struct mjs* mjs = mjs_create(NULL);
|
||||
|
||||
js_value_test_compatibility_matrix(mjs);
|
||||
js_value_test_literal(mjs);
|
||||
js_value_test_primitives(mjs);
|
||||
js_value_test_enums(mjs);
|
||||
js_value_test_object(mjs);
|
||||
js_value_test_default(mjs);
|
||||
js_value_test_args(mjs);
|
||||
|
||||
mjs_destroy(mjs);
|
||||
}
|
||||
|
||||
MU_TEST_SUITE(test_js) {
|
||||
MU_RUN_TEST(js_value_test);
|
||||
MU_RUN_TEST(js_test_basic);
|
||||
MU_RUN_TEST(js_test_math);
|
||||
MU_RUN_TEST(js_test_event_loop);
|
||||
|
||||
@@ -396,6 +396,8 @@ void minunit_printf_warning(const char* format, ...);
|
||||
return; \
|
||||
} else { minunit_print_progress(); })
|
||||
|
||||
//-V:mu_assert_string_eq:526, 547
|
||||
|
||||
#define mu_assert_string_eq(expected, result) \
|
||||
MU__SAFE_BLOCK( \
|
||||
const char* minunit_tmp_e = expected; const char* minunit_tmp_r = result; \
|
||||
@@ -416,6 +418,8 @@ void minunit_printf_warning(const char* format, ...);
|
||||
return; \
|
||||
} else { minunit_print_progress(); })
|
||||
|
||||
//-V:mu_assert_mem_eq:526
|
||||
|
||||
#define mu_assert_mem_eq(expected, result, size) \
|
||||
MU__SAFE_BLOCK( \
|
||||
const void* minunit_tmp_e = expected; const void* minunit_tmp_r = result; \
|
||||
|
||||
@@ -25,16 +25,13 @@ MU_TEST(pipe_test_trivial) {
|
||||
mu_assert_int_eq(PIPE_SIZE - i, pipe_spaces_available(alice));
|
||||
mu_assert_int_eq(i, pipe_bytes_available(bob));
|
||||
|
||||
if(pipe_send(alice, &i, sizeof(uint8_t), 0) != sizeof(uint8_t)) {
|
||||
break;
|
||||
}
|
||||
if(pipe_spaces_available(alice) == 0) break;
|
||||
furi_check(pipe_send(alice, &i, sizeof(uint8_t)) == sizeof(uint8_t));
|
||||
|
||||
mu_assert_int_eq(PIPE_SIZE - i, pipe_spaces_available(bob));
|
||||
mu_assert_int_eq(i, pipe_bytes_available(alice));
|
||||
|
||||
if(pipe_send(bob, &i, sizeof(uint8_t), 0) != sizeof(uint8_t)) {
|
||||
break;
|
||||
}
|
||||
furi_check(pipe_send(bob, &i, sizeof(uint8_t)) == sizeof(uint8_t));
|
||||
}
|
||||
|
||||
pipe_free(alice);
|
||||
@@ -43,10 +40,9 @@ MU_TEST(pipe_test_trivial) {
|
||||
for(uint8_t i = 0;; ++i) {
|
||||
mu_assert_int_eq(PIPE_SIZE - i, pipe_bytes_available(bob));
|
||||
|
||||
if(pipe_bytes_available(bob) == 0) break;
|
||||
uint8_t value;
|
||||
if(pipe_receive(bob, &value, sizeof(uint8_t), 0) != sizeof(uint8_t)) {
|
||||
break;
|
||||
}
|
||||
furi_check(pipe_receive(bob, &value, sizeof(uint8_t)) == sizeof(uint8_t));
|
||||
|
||||
mu_assert_int_eq(i, value);
|
||||
}
|
||||
@@ -68,16 +64,16 @@ typedef struct {
|
||||
static void on_data_arrived(PipeSide* pipe, void* context) {
|
||||
AncillaryThreadContext* ctx = context;
|
||||
ctx->flag |= TestFlagDataArrived;
|
||||
uint8_t buffer[PIPE_SIZE];
|
||||
size_t size = pipe_receive(pipe, buffer, sizeof(buffer), 0);
|
||||
pipe_send(pipe, buffer, size, 0);
|
||||
uint8_t input;
|
||||
size_t size = pipe_receive(pipe, &input, sizeof(input));
|
||||
pipe_send(pipe, &input, size);
|
||||
}
|
||||
|
||||
static void on_space_freed(PipeSide* pipe, void* context) {
|
||||
AncillaryThreadContext* ctx = context;
|
||||
ctx->flag |= TestFlagSpaceFreed;
|
||||
const char* message = "Hi!";
|
||||
pipe_send(pipe, message, strlen(message), 0);
|
||||
pipe_send(pipe, message, strlen(message));
|
||||
}
|
||||
|
||||
static void on_became_broken(PipeSide* pipe, void* context) {
|
||||
@@ -117,15 +113,15 @@ MU_TEST(pipe_test_event_loop) {
|
||||
furi_thread_start(thread);
|
||||
|
||||
const char* message = "Hello!";
|
||||
pipe_send(alice, message, strlen(message), FuriWaitForever);
|
||||
pipe_send(alice, message, strlen(message));
|
||||
|
||||
char buffer_1[16];
|
||||
size_t size = pipe_receive(alice, buffer_1, sizeof(buffer_1), FuriWaitForever);
|
||||
size_t size = pipe_receive(alice, buffer_1, strlen(message));
|
||||
buffer_1[size] = 0;
|
||||
|
||||
char buffer_2[16];
|
||||
const char* expected_reply = "Hi!";
|
||||
size = pipe_receive(alice, buffer_2, sizeof(buffer_2), FuriWaitForever);
|
||||
size = pipe_receive(alice, buffer_2, strlen(expected_reply));
|
||||
buffer_2[size] = 0;
|
||||
|
||||
pipe_free(alice);
|
||||
|
||||
@@ -521,11 +521,6 @@ MU_TEST(test_storage_data_path) {
|
||||
// check that appsdata folder exists
|
||||
mu_check(storage_dir_exists(storage, APPS_DATA_PATH));
|
||||
|
||||
// check that cli folder exists
|
||||
mu_check(storage_dir_exists(storage, APPSDATA_APP_PATH("cli")));
|
||||
|
||||
storage_simply_remove(storage, APPSDATA_APP_PATH("cli"));
|
||||
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <rpc/rpc_i.h>
|
||||
#include <flipper.pb.h>
|
||||
#include <applications/system/js_app/js_thread.h>
|
||||
#include <applications/system/js_app/js_value.h>
|
||||
|
||||
static constexpr auto unit_tests_api_table = sort(create_array_t<sym_entry>(
|
||||
API_METHOD(resource_manifest_reader_alloc, ResourceManifestReader*, (Storage*)),
|
||||
@@ -38,4 +39,16 @@ static constexpr auto unit_tests_api_table = sort(create_array_t<sym_entry>(
|
||||
JsThread*,
|
||||
(const char* script_path, JsThreadCallback callback, void* context)),
|
||||
API_METHOD(js_thread_stop, void, (JsThread * worker)),
|
||||
API_METHOD(js_value_buffer_size, size_t, (const JsValueParseDeclaration declaration)),
|
||||
API_METHOD(
|
||||
js_value_parse,
|
||||
JsValueParseStatus,
|
||||
(struct mjs * mjs,
|
||||
const JsValueParseDeclaration declaration,
|
||||
JsValueParseFlag flags,
|
||||
mjs_val_t* buffer,
|
||||
size_t buf_size,
|
||||
mjs_val_t* source,
|
||||
size_t n_c_vals,
|
||||
...)),
|
||||
API_VARIABLE(PB_Main_msg, PB_Main_msg_t)));
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <cli/cli.h>
|
||||
#include <toolbox/pipe.h>
|
||||
#include <toolbox/run_parallel.h>
|
||||
|
||||
#include "test_runner.h"
|
||||
|
||||
void unit_tests_cli(Cli* cli, FuriString* args, void* context) {
|
||||
UNUSED(cli);
|
||||
void unit_tests_cli(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(context);
|
||||
|
||||
TestRunner* test_runner = test_runner_alloc(cli, args);
|
||||
TestRunner* test_runner = test_runner_alloc(pipe, args);
|
||||
test_runner_run(test_runner);
|
||||
test_runner_free(test_runner);
|
||||
}
|
||||
|
||||
Submodule applications/external updated: 42e62d1740...352fd71a7f
@@ -22,6 +22,7 @@ App(
|
||||
name="On start hooks",
|
||||
apptype=FlipperAppType.METAPACKAGE,
|
||||
provides=[
|
||||
"cli",
|
||||
"ibutton_start",
|
||||
"onewire_start",
|
||||
"subghz_start",
|
||||
|
||||
@@ -382,7 +382,11 @@ bool ble_profile_hid_mouse_scroll(FuriHalBleProfileBase* profile, int8_t delta)
|
||||
#define CONNECTION_INTERVAL_MAX (0x24)
|
||||
|
||||
static GapConfig template_config = {
|
||||
.adv_service_uuid = HUMAN_INTERFACE_DEVICE_SERVICE_UUID,
|
||||
.adv_service =
|
||||
{
|
||||
.UUID_Type = UUID_TYPE_16,
|
||||
.Service_UUID_16 = HUMAN_INTERFACE_DEVICE_SERVICE_UUID,
|
||||
},
|
||||
.appearance_char = GAP_APPEARANCE_KEYBOARD,
|
||||
.bonding_mode = true,
|
||||
.pairing_method = GapPairingPinCodeVerifyYesNo,
|
||||
|
||||
@@ -108,15 +108,15 @@ static void usb_uart_on_irq_rx_dma_cb(
|
||||
static void usb_uart_vcp_init(UsbUartBridge* usb_uart, uint8_t vcp_ch) {
|
||||
furi_hal_usb_unlock();
|
||||
if(vcp_ch == 0) {
|
||||
Cli* cli = furi_record_open(RECORD_CLI);
|
||||
cli_session_close(cli);
|
||||
furi_record_close(RECORD_CLI);
|
||||
CliVcp* cli_vcp = furi_record_open(RECORD_CLI_VCP);
|
||||
cli_vcp_disable(cli_vcp);
|
||||
furi_record_close(RECORD_CLI_VCP);
|
||||
furi_check(furi_hal_usb_set_config(&usb_cdc_single, NULL) == true);
|
||||
} else {
|
||||
furi_check(furi_hal_usb_set_config(&usb_cdc_dual, NULL) == true);
|
||||
Cli* cli = furi_record_open(RECORD_CLI);
|
||||
cli_session_open(cli, &cli_vcp);
|
||||
furi_record_close(RECORD_CLI);
|
||||
CliVcp* cli_vcp = furi_record_open(RECORD_CLI_VCP);
|
||||
cli_vcp_enable(cli_vcp);
|
||||
furi_record_close(RECORD_CLI_VCP);
|
||||
}
|
||||
furi_hal_cdc_set_callbacks(vcp_ch, (CdcCallbacks*)&cdc_cb, usb_uart);
|
||||
}
|
||||
@@ -125,9 +125,9 @@ static void usb_uart_vcp_deinit(UsbUartBridge* usb_uart, uint8_t vcp_ch) {
|
||||
UNUSED(usb_uart);
|
||||
furi_hal_cdc_set_callbacks(vcp_ch, NULL, NULL);
|
||||
if(vcp_ch != 0) {
|
||||
Cli* cli = furi_record_open(RECORD_CLI);
|
||||
cli_session_close(cli);
|
||||
furi_record_close(RECORD_CLI);
|
||||
CliVcp* cli_vcp = furi_record_open(RECORD_CLI_VCP);
|
||||
cli_vcp_disable(cli_vcp);
|
||||
furi_record_close(RECORD_CLI_VCP);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -314,9 +314,9 @@ static int32_t usb_uart_worker(void* context) {
|
||||
|
||||
furi_hal_usb_unlock();
|
||||
furi_check(furi_hal_usb_set_config(&usb_cdc_single, NULL) == true);
|
||||
Cli* cli = furi_record_open(RECORD_CLI);
|
||||
cli_session_open(cli, &cli_vcp);
|
||||
furi_record_close(RECORD_CLI);
|
||||
CliVcp* cli_vcp = furi_record_open(RECORD_CLI_VCP);
|
||||
cli_vcp_enable(cli_vcp);
|
||||
furi_record_close(RECORD_CLI_VCP);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1,29 +1,14 @@
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
|
||||
#include <cli/cli.h>
|
||||
#include <cli/cli_commands.h>
|
||||
#include <toolbox/args.h>
|
||||
#include <toolbox/pipe.h>
|
||||
|
||||
#include <ibutton/ibutton_key.h>
|
||||
#include <ibutton/ibutton_worker.h>
|
||||
#include <ibutton/ibutton_protocols.h>
|
||||
|
||||
static void ibutton_cli(Cli* cli, FuriString* args, void* context);
|
||||
|
||||
#include <cli/cli_i.h>
|
||||
CLI_PLUGIN_WRAPPER("ibutton", ibutton_cli)
|
||||
|
||||
// app cli function
|
||||
void ibutton_on_system_start(void) {
|
||||
#ifdef SRV_CLI
|
||||
Cli* cli = furi_record_open(RECORD_CLI);
|
||||
cli_add_command(cli, "ikey", CliCommandFlagDefault, ibutton_cli_wrapper, cli);
|
||||
furi_record_close(RECORD_CLI);
|
||||
#else
|
||||
UNUSED(ibutton_cli);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void ibutton_cli_print_usage(void) {
|
||||
printf("Usage:\r\n");
|
||||
printf("ikey read\r\n");
|
||||
@@ -95,7 +80,7 @@ static void ibutton_cli_worker_read_cb(void* context) {
|
||||
furi_event_flag_set(event, EVENT_FLAG_IBUTTON_COMPLETE);
|
||||
}
|
||||
|
||||
static void ibutton_cli_read(Cli* cli) {
|
||||
static void ibutton_cli_read(PipeSide* pipe) {
|
||||
iButtonProtocols* protocols = ibutton_protocols_alloc();
|
||||
iButtonWorker* worker = ibutton_worker_alloc(protocols);
|
||||
iButtonKey* key = ibutton_key_alloc(ibutton_protocols_get_max_data_size(protocols));
|
||||
@@ -116,7 +101,7 @@ static void ibutton_cli_read(Cli* cli) {
|
||||
break;
|
||||
}
|
||||
|
||||
if(cli_cmd_interrupt_received(cli)) break;
|
||||
if(cli_is_pipe_broken_or_is_etx_next_char(pipe)) break;
|
||||
}
|
||||
|
||||
ibutton_worker_stop(worker);
|
||||
@@ -141,7 +126,7 @@ static void ibutton_cli_worker_write_cb(void* context, iButtonWorkerWriteResult
|
||||
furi_event_flag_set(write_context->event, EVENT_FLAG_IBUTTON_COMPLETE);
|
||||
}
|
||||
|
||||
void ibutton_cli_write(Cli* cli, FuriString* args) {
|
||||
void ibutton_cli_write(PipeSide* pipe, FuriString* args) {
|
||||
iButtonProtocols* protocols = ibutton_protocols_alloc();
|
||||
iButtonWorker* worker = ibutton_worker_alloc(protocols);
|
||||
iButtonKey* key = ibutton_key_alloc(ibutton_protocols_get_max_data_size(protocols));
|
||||
@@ -184,7 +169,7 @@ void ibutton_cli_write(Cli* cli, FuriString* args) {
|
||||
}
|
||||
}
|
||||
|
||||
if(cli_cmd_interrupt_received(cli)) break;
|
||||
if(cli_is_pipe_broken_or_is_etx_next_char(pipe)) break;
|
||||
}
|
||||
} while(false);
|
||||
|
||||
@@ -198,7 +183,7 @@ void ibutton_cli_write(Cli* cli, FuriString* args) {
|
||||
furi_event_flag_free(write_context.event);
|
||||
}
|
||||
|
||||
void ibutton_cli_emulate(Cli* cli, FuriString* args) {
|
||||
void ibutton_cli_emulate(PipeSide* pipe, FuriString* args) {
|
||||
iButtonProtocols* protocols = ibutton_protocols_alloc();
|
||||
iButtonWorker* worker = ibutton_worker_alloc(protocols);
|
||||
iButtonKey* key = ibutton_key_alloc(ibutton_protocols_get_max_data_size(protocols));
|
||||
@@ -217,7 +202,7 @@ void ibutton_cli_emulate(Cli* cli, FuriString* args) {
|
||||
|
||||
ibutton_worker_emulate_start(worker, key);
|
||||
|
||||
while(!cli_cmd_interrupt_received(cli)) {
|
||||
while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) {
|
||||
furi_delay_ms(100);
|
||||
}
|
||||
|
||||
@@ -231,8 +216,8 @@ void ibutton_cli_emulate(Cli* cli, FuriString* args) {
|
||||
ibutton_protocols_free(protocols);
|
||||
}
|
||||
|
||||
void ibutton_cli(Cli* cli, FuriString* args, void* context) {
|
||||
UNUSED(cli);
|
||||
void ibutton_cli(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(pipe);
|
||||
UNUSED(context);
|
||||
FuriString* cmd;
|
||||
cmd = furi_string_alloc();
|
||||
@@ -244,14 +229,27 @@ void ibutton_cli(Cli* cli, FuriString* args, void* context) {
|
||||
}
|
||||
|
||||
if(furi_string_cmp_str(cmd, "read") == 0) {
|
||||
ibutton_cli_read(cli);
|
||||
ibutton_cli_read(pipe);
|
||||
} else if(furi_string_cmp_str(cmd, "write") == 0) {
|
||||
ibutton_cli_write(cli, args);
|
||||
ibutton_cli_write(pipe, args);
|
||||
} else if(furi_string_cmp_str(cmd, "emulate") == 0) {
|
||||
ibutton_cli_emulate(cli, args);
|
||||
ibutton_cli_emulate(pipe, args);
|
||||
} else {
|
||||
ibutton_cli_print_usage();
|
||||
}
|
||||
|
||||
furi_string_free(cmd);
|
||||
}
|
||||
|
||||
#include <cli/cli_i.h>
|
||||
CLI_PLUGIN_WRAPPER("ibutton", ibutton_cli)
|
||||
|
||||
void ibutton_on_system_start(void) {
|
||||
#ifdef SRV_CLI
|
||||
Cli* cli = furi_record_open(RECORD_CLI);
|
||||
cli_add_command(cli, "ikey", CliCommandFlagDefault, ibutton_cli_wrapper, cli);
|
||||
furi_record_close(RECORD_CLI);
|
||||
#else
|
||||
UNUSED(ibutton_cli);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
#include <cli/cli.h>
|
||||
#include <cli/cli_i.h>
|
||||
#include <cli/cli_commands.h>
|
||||
#include <infrared.h>
|
||||
#include <infrared_worker.h>
|
||||
#include <furi_hal_infrared.h>
|
||||
#include <flipper_format.h>
|
||||
#include <toolbox/args.h>
|
||||
#include <toolbox/strint.h>
|
||||
#include <toolbox/pipe.h>
|
||||
#include <m-dict.h>
|
||||
|
||||
#include "infrared_signal.h"
|
||||
@@ -19,14 +19,14 @@
|
||||
|
||||
DICT_DEF2(dict_signals, FuriString*, FURI_STRING_OPLIST, int, M_DEFAULT_OPLIST)
|
||||
|
||||
static void infrared_cli_start_ir_rx(Cli* cli, FuriString* args);
|
||||
static void infrared_cli_start_ir_tx(Cli* cli, FuriString* args);
|
||||
static void infrared_cli_process_decode(Cli* cli, FuriString* args);
|
||||
static void infrared_cli_process_universal(Cli* cli, FuriString* args);
|
||||
static void infrared_cli_start_ir_rx(PipeSide* pipe, FuriString* args);
|
||||
static void infrared_cli_start_ir_tx(PipeSide* pipe, FuriString* args);
|
||||
static void infrared_cli_process_decode(PipeSide* pipe, FuriString* args);
|
||||
static void infrared_cli_process_universal(PipeSide* pipe, FuriString* args);
|
||||
|
||||
static const struct {
|
||||
const char* cmd;
|
||||
void (*process_function)(Cli* cli, FuriString* args);
|
||||
void (*process_function)(PipeSide* pipe, FuriString* args);
|
||||
} infrared_cli_commands[] = {
|
||||
{.cmd = "rx", .process_function = infrared_cli_start_ir_rx},
|
||||
{.cmd = "tx", .process_function = infrared_cli_start_ir_tx},
|
||||
@@ -38,7 +38,7 @@ static void signal_received_callback(void* context, InfraredWorkerSignal* receiv
|
||||
furi_assert(received_signal);
|
||||
char buf[100];
|
||||
size_t buf_cnt;
|
||||
Cli* cli = (Cli*)context;
|
||||
PipeSide* pipe = (PipeSide*)context;
|
||||
|
||||
if(infrared_worker_signal_is_decoded(received_signal)) {
|
||||
const InfraredMessage* message = infrared_worker_get_decoded_signal(received_signal);
|
||||
@@ -52,20 +52,20 @@ static void signal_received_callback(void* context, InfraredWorkerSignal* receiv
|
||||
ROUND_UP_TO(infrared_get_protocol_command_length(message->protocol), 4),
|
||||
message->command,
|
||||
message->repeat ? " R" : "");
|
||||
cli_write(cli, (uint8_t*)buf, buf_cnt);
|
||||
pipe_send(pipe, buf, buf_cnt);
|
||||
} else {
|
||||
const uint32_t* timings;
|
||||
size_t timings_cnt;
|
||||
infrared_worker_get_raw_signal(received_signal, &timings, &timings_cnt);
|
||||
|
||||
buf_cnt = snprintf(buf, sizeof(buf), "RAW, %zu samples:\r\n", timings_cnt);
|
||||
cli_write(cli, (uint8_t*)buf, buf_cnt);
|
||||
pipe_send(pipe, buf, buf_cnt);
|
||||
for(size_t i = 0; i < timings_cnt; ++i) {
|
||||
buf_cnt = snprintf(buf, sizeof(buf), "%lu ", timings[i]);
|
||||
cli_write(cli, (uint8_t*)buf, buf_cnt);
|
||||
pipe_send(pipe, buf, buf_cnt);
|
||||
}
|
||||
buf_cnt = snprintf(buf, sizeof(buf), "\r\n");
|
||||
cli_write(cli, (uint8_t*)buf, buf_cnt);
|
||||
pipe_send(pipe, buf, buf_cnt);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,9 +124,7 @@ static void infrared_cli_print_usage(void) {
|
||||
infrared_cli_print_universal_remotes();
|
||||
}
|
||||
|
||||
static void infrared_cli_start_ir_rx(Cli* cli, FuriString* args) {
|
||||
UNUSED(cli);
|
||||
|
||||
static void infrared_cli_start_ir_rx(PipeSide* pipe, FuriString* args) {
|
||||
bool enable_decoding = true;
|
||||
|
||||
if(!furi_string_empty(args)) {
|
||||
@@ -142,10 +140,10 @@ static void infrared_cli_start_ir_rx(Cli* cli, FuriString* args) {
|
||||
InfraredWorker* worker = infrared_worker_alloc();
|
||||
infrared_worker_rx_enable_signal_decoding(worker, enable_decoding);
|
||||
infrared_worker_rx_start(worker);
|
||||
infrared_worker_rx_set_received_signal_callback(worker, signal_received_callback, cli);
|
||||
infrared_worker_rx_set_received_signal_callback(worker, signal_received_callback, pipe);
|
||||
|
||||
printf("Receiving %s INFRARED...\r\nPress Ctrl+C to abort\r\n", enable_decoding ? "" : "RAW");
|
||||
while(!cli_cmd_interrupt_received(cli)) {
|
||||
while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) {
|
||||
furi_delay_ms(50);
|
||||
}
|
||||
|
||||
@@ -214,8 +212,8 @@ static bool infrared_cli_parse_raw(const char* str, InfraredSignal* signal) {
|
||||
return infrared_signal_is_valid(signal);
|
||||
}
|
||||
|
||||
static void infrared_cli_start_ir_tx(Cli* cli, FuriString* args) {
|
||||
UNUSED(cli);
|
||||
static void infrared_cli_start_ir_tx(PipeSide* pipe, FuriString* args) {
|
||||
UNUSED(pipe);
|
||||
const char* str = furi_string_get_cstr(args);
|
||||
InfraredSignal* signal = infrared_signal_alloc();
|
||||
|
||||
@@ -335,8 +333,8 @@ static bool infrared_cli_decode_file(FlipperFormat* input_file, FlipperFormat* o
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void infrared_cli_process_decode(Cli* cli, FuriString* args) {
|
||||
UNUSED(cli);
|
||||
static void infrared_cli_process_decode(PipeSide* pipe, FuriString* args) {
|
||||
UNUSED(pipe);
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
FlipperFormat* input_file = flipper_format_buffered_file_alloc(storage);
|
||||
FlipperFormat* output_file = NULL;
|
||||
@@ -455,8 +453,10 @@ static void infrared_cli_list_remote_signals(FuriString* remote_name) {
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
}
|
||||
|
||||
static void
|
||||
infrared_cli_brute_force_signals(Cli* cli, FuriString* remote_name, FuriString* signal_name) {
|
||||
static void infrared_cli_brute_force_signals(
|
||||
PipeSide* pipe,
|
||||
FuriString* remote_name,
|
||||
FuriString* signal_name) {
|
||||
InfraredBruteForce* brute_force = infrared_brute_force_alloc();
|
||||
FuriString* remote_path = furi_string_alloc_printf(
|
||||
"%s/%s.ir", INFRARED_ASSETS_FOLDER, furi_string_get_cstr(remote_name));
|
||||
@@ -490,7 +490,7 @@ static void
|
||||
while(running) {
|
||||
running = infrared_brute_force_send(brute_force, current_signal);
|
||||
|
||||
if(cli_cmd_interrupt_received(cli)) break;
|
||||
if(cli_is_pipe_broken_or_is_etx_next_char(pipe)) break;
|
||||
|
||||
printf("\r%d%% complete.", (int)((float)current_signal++ / (float)signal_count * 100));
|
||||
fflush(stdout);
|
||||
@@ -504,7 +504,7 @@ static void
|
||||
infrared_brute_force_free(brute_force);
|
||||
}
|
||||
|
||||
static void infrared_cli_process_universal(Cli* cli, FuriString* args) {
|
||||
static void infrared_cli_process_universal(PipeSide* pipe, FuriString* args) {
|
||||
FuriString* arg1 = furi_string_alloc();
|
||||
FuriString* arg2 = furi_string_alloc();
|
||||
|
||||
@@ -519,14 +519,14 @@ static void infrared_cli_process_universal(Cli* cli, FuriString* args) {
|
||||
} else if(furi_string_equal_str(arg1, "list")) {
|
||||
infrared_cli_list_remote_signals(arg2);
|
||||
} else {
|
||||
infrared_cli_brute_force_signals(cli, arg1, arg2);
|
||||
infrared_cli_brute_force_signals(pipe, arg1, arg2);
|
||||
}
|
||||
|
||||
furi_string_free(arg1);
|
||||
furi_string_free(arg2);
|
||||
}
|
||||
|
||||
static void infrared_cli_start_ir(Cli* cli, FuriString* args, void* context) {
|
||||
static void infrared_cli_start_ir(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(context);
|
||||
if(furi_hal_infrared_is_busy()) {
|
||||
printf("INFRARED is busy. Exiting.");
|
||||
@@ -546,7 +546,7 @@ static void infrared_cli_start_ir(Cli* cli, FuriString* args, void* context) {
|
||||
}
|
||||
|
||||
if(i < COUNT_OF(infrared_cli_commands)) {
|
||||
infrared_cli_commands[i].process_function(cli, args);
|
||||
infrared_cli_commands[i].process_function(pipe, args);
|
||||
} else {
|
||||
infrared_cli_print_usage();
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <stdarg.h>
|
||||
#include <cli/cli.h>
|
||||
#include <cli/cli_commands.h>
|
||||
#include <lib/toolbox/args.h>
|
||||
#include <lib/lfrfid/lfrfid_worker.h>
|
||||
#include <storage/storage.h>
|
||||
#include <toolbox/stream/file_stream.h>
|
||||
#include <toolbox/pipe.h>
|
||||
|
||||
#include <toolbox/varint.h>
|
||||
|
||||
@@ -14,18 +15,6 @@
|
||||
#include <lfrfid/lfrfid_raw_file.h>
|
||||
#include <toolbox/pulse_protocols/pulse_glue.h>
|
||||
|
||||
static void lfrfid_cli(Cli* cli, FuriString* args, void* context);
|
||||
|
||||
#include <cli/cli_i.h>
|
||||
CLI_PLUGIN_WRAPPER("lfrfid", lfrfid_cli)
|
||||
|
||||
// app cli function
|
||||
void lfrfid_on_system_start(void) {
|
||||
Cli* cli = furi_record_open(RECORD_CLI);
|
||||
cli_add_command(cli, "rfid", CliCommandFlagDefault, lfrfid_cli_wrapper, NULL);
|
||||
furi_record_close(RECORD_CLI);
|
||||
}
|
||||
|
||||
static void lfrfid_cli_print_usage(void) {
|
||||
printf("Usage:\r\n");
|
||||
printf("rfid read <optional: normal | indala> - read in ASK/PSK mode\r\n");
|
||||
@@ -52,7 +41,7 @@ static void lfrfid_cli_read_callback(LFRFIDWorkerReadResult result, ProtocolId p
|
||||
furi_event_flag_set(context->event, 1 << result);
|
||||
}
|
||||
|
||||
static void lfrfid_cli_read(Cli* cli, FuriString* args) {
|
||||
static void lfrfid_cli_read(PipeSide* pipe, FuriString* args) {
|
||||
FuriString* type_string;
|
||||
type_string = furi_string_alloc();
|
||||
LFRFIDWorkerReadType type = LFRFIDWorkerReadTypeAuto;
|
||||
@@ -99,7 +88,7 @@ static void lfrfid_cli_read(Cli* cli, FuriString* args) {
|
||||
}
|
||||
}
|
||||
|
||||
if(cli_cmd_interrupt_received(cli)) break;
|
||||
if(cli_is_pipe_broken_or_is_etx_next_char(pipe)) break;
|
||||
}
|
||||
|
||||
lfrfid_worker_stop(worker);
|
||||
@@ -195,7 +184,7 @@ static void lfrfid_cli_write_callback(LFRFIDWorkerWriteResult result, void* ctx)
|
||||
furi_event_flag_set(events, 1 << result);
|
||||
}
|
||||
|
||||
static void lfrfid_cli_write(Cli* cli, FuriString* args) {
|
||||
static void lfrfid_cli_write(PipeSide* pipe, FuriString* args) {
|
||||
ProtocolDict* dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax);
|
||||
ProtocolId protocol;
|
||||
|
||||
@@ -215,7 +204,7 @@ static void lfrfid_cli_write(Cli* cli, FuriString* args) {
|
||||
(1 << LFRFIDWorkerWriteProtocolCannotBeWritten) |
|
||||
(1 << LFRFIDWorkerWriteFobCannotBeWritten);
|
||||
|
||||
while(!cli_cmd_interrupt_received(cli)) {
|
||||
while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) {
|
||||
uint32_t flags = furi_event_flag_wait(event, available_flags, FuriFlagWaitAny, 100);
|
||||
if(flags != (unsigned)FuriFlagErrorTimeout) {
|
||||
if(FURI_BIT(flags, LFRFIDWorkerWriteOK)) {
|
||||
@@ -242,7 +231,7 @@ static void lfrfid_cli_write(Cli* cli, FuriString* args) {
|
||||
furi_event_flag_free(event);
|
||||
}
|
||||
|
||||
static void lfrfid_cli_emulate(Cli* cli, FuriString* args) {
|
||||
static void lfrfid_cli_emulate(PipeSide* pipe, FuriString* args) {
|
||||
ProtocolDict* dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax);
|
||||
ProtocolId protocol;
|
||||
|
||||
@@ -257,7 +246,7 @@ static void lfrfid_cli_emulate(Cli* cli, FuriString* args) {
|
||||
lfrfid_worker_emulate_start(worker, protocol);
|
||||
|
||||
printf("Emulating RFID...\r\nPress Ctrl+C to abort\r\n");
|
||||
while(!cli_cmd_interrupt_received(cli)) {
|
||||
while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) {
|
||||
furi_delay_ms(100);
|
||||
}
|
||||
printf("Emulation stopped\r\n");
|
||||
@@ -268,8 +257,8 @@ static void lfrfid_cli_emulate(Cli* cli, FuriString* args) {
|
||||
protocol_dict_free(dict);
|
||||
}
|
||||
|
||||
static void lfrfid_cli_raw_analyze(Cli* cli, FuriString* args) {
|
||||
UNUSED(cli);
|
||||
static void lfrfid_cli_raw_analyze(PipeSide* pipe, FuriString* args) {
|
||||
UNUSED(pipe);
|
||||
FuriString *filepath, *info_string;
|
||||
filepath = furi_string_alloc();
|
||||
info_string = furi_string_alloc();
|
||||
@@ -395,9 +384,7 @@ static void lfrfid_cli_raw_read_callback(LFRFIDWorkerReadRawResult result, void*
|
||||
furi_event_flag_set(event, 1 << result);
|
||||
}
|
||||
|
||||
static void lfrfid_cli_raw_read(Cli* cli, FuriString* args) {
|
||||
UNUSED(cli);
|
||||
|
||||
static void lfrfid_cli_raw_read(PipeSide* pipe, FuriString* args) {
|
||||
FuriString *filepath, *type_string;
|
||||
filepath = furi_string_alloc();
|
||||
type_string = furi_string_alloc();
|
||||
@@ -455,7 +442,7 @@ static void lfrfid_cli_raw_read(Cli* cli, FuriString* args) {
|
||||
}
|
||||
}
|
||||
|
||||
if(cli_cmd_interrupt_received(cli)) break;
|
||||
if(cli_is_pipe_broken_or_is_etx_next_char(pipe)) break;
|
||||
}
|
||||
|
||||
if(overrun) {
|
||||
@@ -482,9 +469,7 @@ static void lfrfid_cli_raw_emulate_callback(LFRFIDWorkerEmulateRawResult result,
|
||||
furi_event_flag_set(event, 1 << result);
|
||||
}
|
||||
|
||||
static void lfrfid_cli_raw_emulate(Cli* cli, FuriString* args) {
|
||||
UNUSED(cli);
|
||||
|
||||
static void lfrfid_cli_raw_emulate(PipeSide* pipe, FuriString* args) {
|
||||
FuriString* filepath;
|
||||
filepath = furi_string_alloc();
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
@@ -530,7 +515,7 @@ static void lfrfid_cli_raw_emulate(Cli* cli, FuriString* args) {
|
||||
}
|
||||
}
|
||||
|
||||
if(cli_cmd_interrupt_received(cli)) break;
|
||||
if(cli_is_pipe_broken_or_is_etx_next_char(pipe)) break;
|
||||
}
|
||||
|
||||
if(overrun) {
|
||||
@@ -551,7 +536,7 @@ static void lfrfid_cli_raw_emulate(Cli* cli, FuriString* args) {
|
||||
furi_string_free(filepath);
|
||||
}
|
||||
|
||||
static void lfrfid_cli(Cli* cli, FuriString* args, void* context) {
|
||||
static void lfrfid_cli(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(context);
|
||||
FuriString* cmd;
|
||||
cmd = furi_string_alloc();
|
||||
@@ -563,20 +548,29 @@ static void lfrfid_cli(Cli* cli, FuriString* args, void* context) {
|
||||
}
|
||||
|
||||
if(furi_string_cmp_str(cmd, "read") == 0) {
|
||||
lfrfid_cli_read(cli, args);
|
||||
lfrfid_cli_read(pipe, args);
|
||||
} else if(furi_string_cmp_str(cmd, "write") == 0) {
|
||||
lfrfid_cli_write(cli, args);
|
||||
lfrfid_cli_write(pipe, args);
|
||||
} else if(furi_string_cmp_str(cmd, "emulate") == 0) {
|
||||
lfrfid_cli_emulate(cli, args);
|
||||
lfrfid_cli_emulate(pipe, args);
|
||||
} else if(furi_string_cmp_str(cmd, "raw_read") == 0) {
|
||||
lfrfid_cli_raw_read(cli, args);
|
||||
lfrfid_cli_raw_read(pipe, args);
|
||||
} else if(furi_string_cmp_str(cmd, "raw_emulate") == 0) {
|
||||
lfrfid_cli_raw_emulate(cli, args);
|
||||
lfrfid_cli_raw_emulate(pipe, args);
|
||||
} else if(furi_string_cmp_str(cmd, "raw_analyze") == 0) {
|
||||
lfrfid_cli_raw_analyze(cli, args);
|
||||
lfrfid_cli_raw_analyze(pipe, args);
|
||||
} else {
|
||||
lfrfid_cli_print_usage();
|
||||
}
|
||||
|
||||
furi_string_free(cmd);
|
||||
}
|
||||
|
||||
#include <cli/cli_i.h>
|
||||
CLI_PLUGIN_WRAPPER("lfrfid", lfrfid_cli)
|
||||
|
||||
void lfrfid_on_system_start(void) {
|
||||
Cli* cli = furi_record_open(RECORD_CLI);
|
||||
cli_add_command(cli, "rfid", CliCommandFlagDefault, lfrfid_cli_wrapper, NULL);
|
||||
furi_record_close(RECORD_CLI);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <cli/cli.h>
|
||||
#include <cli/cli_commands.h>
|
||||
#include <lib/toolbox/args.h>
|
||||
#include <lib/toolbox/hex.h>
|
||||
#include <toolbox/pipe.h>
|
||||
|
||||
#include <furi_hal_nfc.h>
|
||||
|
||||
@@ -17,7 +19,7 @@ static void nfc_cli_print_usage(void) {
|
||||
}
|
||||
}
|
||||
|
||||
static void nfc_cli_field(Cli* cli, FuriString* args) {
|
||||
static void nfc_cli_field(PipeSide* pipe, FuriString* args) {
|
||||
UNUSED(args);
|
||||
// Check if nfc worker is not busy
|
||||
if(furi_hal_nfc_is_hal_ready() != FuriHalNfcErrorNone) {
|
||||
@@ -32,7 +34,7 @@ static void nfc_cli_field(Cli* cli, FuriString* args) {
|
||||
printf("Field is on. Don't leave device in this mode for too long.\r\n");
|
||||
printf("Press Ctrl+C to abort\r\n");
|
||||
|
||||
while(!cli_cmd_interrupt_received(cli)) {
|
||||
while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) {
|
||||
furi_delay_ms(50);
|
||||
}
|
||||
|
||||
@@ -40,7 +42,7 @@ static void nfc_cli_field(Cli* cli, FuriString* args) {
|
||||
furi_hal_nfc_release();
|
||||
}
|
||||
|
||||
static void nfc_cli(Cli* cli, FuriString* args, void* context) {
|
||||
static void nfc_cli(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(context);
|
||||
FuriString* cmd;
|
||||
cmd = furi_string_alloc();
|
||||
@@ -52,7 +54,7 @@ static void nfc_cli(Cli* cli, FuriString* args, void* context) {
|
||||
}
|
||||
if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) {
|
||||
if(furi_string_cmp_str(cmd, "field") == 0) {
|
||||
nfc_cli_field(cli, args);
|
||||
nfc_cli_field(pipe, args);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,34 +2,18 @@
|
||||
#include <furi_hal.h>
|
||||
|
||||
#include <power/power_service/power.h>
|
||||
|
||||
#include <cli/cli.h>
|
||||
#include <cli/cli_commands.h>
|
||||
#include <toolbox/args.h>
|
||||
|
||||
#include <one_wire/one_wire_host.h>
|
||||
|
||||
static void onewire_cli(Cli* cli, FuriString* args, void* context);
|
||||
|
||||
#include <cli/cli_i.h>
|
||||
CLI_PLUGIN_WRAPPER("onewire", onewire_cli)
|
||||
|
||||
void onewire_on_system_start(void) {
|
||||
#ifdef SRV_CLI
|
||||
Cli* cli = furi_record_open(RECORD_CLI);
|
||||
cli_add_command(cli, "onewire", CliCommandFlagDefault, onewire_cli_wrapper, cli);
|
||||
furi_record_close(RECORD_CLI);
|
||||
#else
|
||||
UNUSED(onewire_cli);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void onewire_cli_print_usage(void) {
|
||||
printf("Usage:\r\n");
|
||||
printf("onewire search\r\n");
|
||||
}
|
||||
|
||||
static void onewire_cli_search(Cli* cli) {
|
||||
UNUSED(cli);
|
||||
static void onewire_cli_search(PipeSide* pipe) {
|
||||
UNUSED(pipe);
|
||||
OneWireHost* onewire = onewire_host_alloc(&gpio_ibutton);
|
||||
Power* power = furi_record_open(RECORD_POWER);
|
||||
uint8_t address[8];
|
||||
@@ -61,7 +45,7 @@ static void onewire_cli_search(Cli* cli) {
|
||||
furi_record_close(RECORD_POWER);
|
||||
}
|
||||
|
||||
void onewire_cli(Cli* cli, FuriString* args, void* context) {
|
||||
static void onewire_cli(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(context);
|
||||
FuriString* cmd;
|
||||
cmd = furi_string_alloc();
|
||||
@@ -73,8 +57,21 @@ void onewire_cli(Cli* cli, FuriString* args, void* context) {
|
||||
}
|
||||
|
||||
if(furi_string_cmp_str(cmd, "search") == 0) {
|
||||
onewire_cli_search(cli);
|
||||
onewire_cli_search(pipe);
|
||||
}
|
||||
|
||||
furi_string_free(cmd);
|
||||
}
|
||||
|
||||
#include <cli/cli_i.h>
|
||||
CLI_PLUGIN_WRAPPER("onewire", onewire_cli)
|
||||
|
||||
void onewire_on_system_start(void) {
|
||||
#ifdef SRV_CLI
|
||||
Cli* cli = furi_record_open(RECORD_CLI);
|
||||
cli_add_command(cli, "onewire", CliCommandFlagDefault, onewire_cli_wrapper, cli);
|
||||
furi_record_close(RECORD_CLI);
|
||||
#else
|
||||
UNUSED(onewire_cli);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "subghz_chat.h"
|
||||
#include <lib/subghz/subghz_tx_rx_worker.h>
|
||||
#include <toolbox/pipe.h>
|
||||
|
||||
#define TAG "SubGhzChat"
|
||||
|
||||
@@ -14,7 +15,7 @@ struct SubGhzChatWorker {
|
||||
FuriMessageQueue* event_queue;
|
||||
uint32_t last_time_rx_data;
|
||||
|
||||
Cli* cli;
|
||||
PipeSide* pipe;
|
||||
};
|
||||
|
||||
/** Worker thread
|
||||
@@ -30,7 +31,7 @@ static int32_t subghz_chat_worker_thread(void* context) {
|
||||
event.event = SubGhzChatEventUserEntrance;
|
||||
furi_message_queue_put(instance->event_queue, &event, 0);
|
||||
while(instance->worker_running) {
|
||||
if(cli_read_timeout(instance->cli, (uint8_t*)&c, 1, 1000) == 1) {
|
||||
if(pipe_receive(instance->pipe, (uint8_t*)&c, 1) == 1) {
|
||||
event.event = SubGhzChatEventInputData;
|
||||
event.c = c;
|
||||
furi_message_queue_put(instance->event_queue, &event, FuriWaitForever);
|
||||
@@ -55,10 +56,10 @@ static void subghz_chat_worker_update_rx_event_chat(void* context) {
|
||||
furi_message_queue_put(instance->event_queue, &event, FuriWaitForever);
|
||||
}
|
||||
|
||||
SubGhzChatWorker* subghz_chat_worker_alloc(Cli* cli) {
|
||||
SubGhzChatWorker* subghz_chat_worker_alloc(PipeSide* pipe) {
|
||||
SubGhzChatWorker* instance = malloc(sizeof(SubGhzChatWorker));
|
||||
|
||||
instance->cli = cli;
|
||||
instance->pipe = pipe;
|
||||
|
||||
instance->thread =
|
||||
furi_thread_alloc_ex("SubGhzChat", 2048, subghz_chat_worker_thread, instance);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "../subghz_i.h"
|
||||
#include <lib/subghz/devices/devices.h>
|
||||
#include <cli/cli.h>
|
||||
#include <toolbox/pipe.h>
|
||||
|
||||
typedef struct SubGhzChatWorker SubGhzChatWorker;
|
||||
|
||||
@@ -19,7 +20,7 @@ typedef struct {
|
||||
char c;
|
||||
} SubGhzChatEvent;
|
||||
|
||||
SubGhzChatWorker* subghz_chat_worker_alloc(Cli* cli);
|
||||
SubGhzChatWorker* subghz_chat_worker_alloc(PipeSide* pipe);
|
||||
void subghz_chat_worker_free(SubGhzChatWorker* instance);
|
||||
bool subghz_chat_worker_start(
|
||||
SubGhzChatWorker* instance,
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <furi_hal.h>
|
||||
|
||||
#include <applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h>
|
||||
#include <cli/cli_commands.h>
|
||||
|
||||
#include <lib/subghz/subghz_keystore.h>
|
||||
#include <lib/subghz/receiver.h>
|
||||
@@ -16,6 +17,7 @@
|
||||
|
||||
#include <lib/toolbox/args.h>
|
||||
#include <lib/toolbox/strint.h>
|
||||
#include <toolbox/pipe.h>
|
||||
|
||||
#include "helpers/subghz_chat.h"
|
||||
|
||||
@@ -66,7 +68,7 @@ static SubGhzEnvironment* subghz_cli_environment_init(void) {
|
||||
return environment;
|
||||
}
|
||||
|
||||
void subghz_cli_command_tx_carrier(Cli* cli, FuriString* args, void* context) {
|
||||
void subghz_cli_command_tx_carrier(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(context);
|
||||
uint32_t frequency = 433920000;
|
||||
|
||||
@@ -96,7 +98,7 @@ void subghz_cli_command_tx_carrier(Cli* cli, FuriString* args, void* context) {
|
||||
if(furi_hal_subghz_tx()) {
|
||||
printf("Transmitting at frequency %lu Hz\r\n", frequency);
|
||||
printf("Press CTRL+C to stop\r\n");
|
||||
while(!cli_cmd_interrupt_received(cli)) {
|
||||
while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) {
|
||||
furi_delay_ms(250);
|
||||
}
|
||||
} else {
|
||||
@@ -109,7 +111,7 @@ void subghz_cli_command_tx_carrier(Cli* cli, FuriString* args, void* context) {
|
||||
furi_hal_power_suppress_charge_exit();
|
||||
}
|
||||
|
||||
void subghz_cli_command_rx_carrier(Cli* cli, FuriString* args, void* context) {
|
||||
void subghz_cli_command_rx_carrier(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(context);
|
||||
uint32_t frequency = 433920000;
|
||||
|
||||
@@ -137,7 +139,7 @@ void subghz_cli_command_rx_carrier(Cli* cli, FuriString* args, void* context) {
|
||||
|
||||
furi_hal_subghz_rx();
|
||||
|
||||
while(!cli_cmd_interrupt_received(cli)) {
|
||||
while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) {
|
||||
furi_delay_ms(250);
|
||||
printf("RSSI: %03.1fdbm\r", (double)furi_hal_subghz_get_rssi());
|
||||
fflush(stdout);
|
||||
@@ -170,7 +172,7 @@ static const SubGhzDevice* subghz_cli_command_get_device(uint32_t* device_ind) {
|
||||
return device;
|
||||
}
|
||||
|
||||
void subghz_cli_command_tx(Cli* cli, FuriString* args, void* context) {
|
||||
void subghz_cli_command_tx(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(context);
|
||||
uint32_t frequency = 433920000;
|
||||
uint32_t key = 0x0074BADE;
|
||||
@@ -240,7 +242,9 @@ void subghz_cli_command_tx(Cli* cli, FuriString* args, void* context) {
|
||||
|
||||
furi_hal_power_suppress_charge_enter();
|
||||
if(subghz_devices_start_async_tx(device, subghz_transmitter_yield, transmitter)) {
|
||||
while(!(subghz_devices_is_async_complete_tx(device) || cli_cmd_interrupt_received(cli))) {
|
||||
while(
|
||||
!(subghz_devices_is_async_complete_tx(device) ||
|
||||
cli_is_pipe_broken_or_is_etx_next_char(pipe))) {
|
||||
printf(".");
|
||||
fflush(stdout);
|
||||
furi_delay_ms(333);
|
||||
@@ -296,7 +300,7 @@ static void subghz_cli_command_rx_callback(
|
||||
furi_string_free(text);
|
||||
}
|
||||
|
||||
void subghz_cli_command_rx(Cli* cli, FuriString* args, void* context) {
|
||||
void subghz_cli_command_rx(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(context);
|
||||
uint32_t frequency = 433920000;
|
||||
uint32_t device_ind = 0; // 0 - CC1101_INT, 1 - CC1101_EXT
|
||||
@@ -352,7 +356,7 @@ void subghz_cli_command_rx(Cli* cli, FuriString* args, void* context) {
|
||||
frequency,
|
||||
device_ind);
|
||||
LevelDuration level_duration;
|
||||
while(!cli_cmd_interrupt_received(cli)) {
|
||||
while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) {
|
||||
int ret = furi_stream_buffer_receive(
|
||||
instance->stream, &level_duration, sizeof(LevelDuration), 10);
|
||||
if(ret == sizeof(LevelDuration)) {
|
||||
@@ -385,7 +389,7 @@ void subghz_cli_command_rx(Cli* cli, FuriString* args, void* context) {
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void subghz_cli_command_rx_raw(Cli* cli, FuriString* args, void* context) {
|
||||
void subghz_cli_command_rx_raw(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(context);
|
||||
uint32_t frequency = 433920000;
|
||||
|
||||
@@ -423,7 +427,7 @@ void subghz_cli_command_rx_raw(Cli* cli, FuriString* args, void* context) {
|
||||
printf("Listening at %lu. Press CTRL+C to stop\r\n", frequency);
|
||||
LevelDuration level_duration;
|
||||
size_t counter = 0;
|
||||
while(!cli_cmd_interrupt_received(cli)) {
|
||||
while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) {
|
||||
int ret = furi_stream_buffer_receive(
|
||||
instance->stream, &level_duration, sizeof(LevelDuration), 10);
|
||||
if(ret == 0) {
|
||||
@@ -459,7 +463,7 @@ void subghz_cli_command_rx_raw(Cli* cli, FuriString* args, void* context) {
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void subghz_cli_command_decode_raw(Cli* cli, FuriString* args, void* context) {
|
||||
void subghz_cli_command_decode_raw(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(context);
|
||||
FuriString* file_name = furi_string_alloc();
|
||||
furi_string_set(file_name, EXT_PATH("subghz/test.sub"));
|
||||
@@ -527,7 +531,7 @@ void subghz_cli_command_decode_raw(Cli* cli, FuriString* args, void* context) {
|
||||
furi_string_get_cstr(file_name));
|
||||
|
||||
LevelDuration level_duration;
|
||||
while(!cli_cmd_interrupt_received(cli)) {
|
||||
while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) {
|
||||
furi_delay_us(500); //you need to have time to read from the file from the SD card
|
||||
level_duration = subghz_file_encoder_worker_get_level_duration(file_worker_encoder);
|
||||
if(!level_duration_is_reset(level_duration)) {
|
||||
@@ -572,7 +576,7 @@ static FuriHalSubGhzPreset subghz_cli_get_preset_name(const char* preset_name) {
|
||||
return preset;
|
||||
}
|
||||
|
||||
void subghz_cli_command_tx_from_file(Cli* cli, FuriString* args, void* context) { // -V524
|
||||
void subghz_cli_command_tx_from_file(PipeSide* pipe, FuriString* args, void* context) { // -V524
|
||||
UNUSED(context);
|
||||
FuriString* file_name;
|
||||
file_name = furi_string_alloc();
|
||||
@@ -776,7 +780,7 @@ void subghz_cli_command_tx_from_file(Cli* cli, FuriString* args, void* context)
|
||||
if(subghz_devices_start_async_tx(device, subghz_transmitter_yield, transmitter)) {
|
||||
while(
|
||||
!(subghz_devices_is_async_complete_tx(device) ||
|
||||
cli_cmd_interrupt_received(cli))) {
|
||||
cli_is_pipe_broken_or_is_etx_next_char(pipe))) {
|
||||
printf(".");
|
||||
fflush(stdout);
|
||||
furi_delay_ms(333);
|
||||
@@ -790,11 +794,11 @@ void subghz_cli_command_tx_from_file(Cli* cli, FuriString* args, void* context)
|
||||
if(!strcmp(furi_string_get_cstr(temp_str), "RAW")) {
|
||||
subghz_transmitter_stop(transmitter);
|
||||
repeat--;
|
||||
if(!cli_cmd_interrupt_received(cli) && repeat)
|
||||
if(!cli_is_pipe_broken_or_is_etx_next_char(pipe) && repeat)
|
||||
subghz_transmitter_deserialize(transmitter, fff_data_raw);
|
||||
}
|
||||
|
||||
} while(!cli_cmd_interrupt_received(cli) &&
|
||||
} while(!cli_is_pipe_broken_or_is_etx_next_char(pipe) &&
|
||||
(repeat && !strcmp(furi_string_get_cstr(temp_str), "RAW")));
|
||||
|
||||
subghz_devices_sleep(device);
|
||||
@@ -844,8 +848,8 @@ static void subghz_cli_command_print_usage(void) {
|
||||
}
|
||||
}
|
||||
|
||||
static void subghz_cli_command_encrypt_keeloq(Cli* cli, FuriString* args) {
|
||||
UNUSED(cli);
|
||||
static void subghz_cli_command_encrypt_keeloq(PipeSide* pipe, FuriString* args) {
|
||||
UNUSED(pipe);
|
||||
uint8_t iv[16];
|
||||
|
||||
FuriString* source = furi_string_alloc();
|
||||
@@ -885,8 +889,8 @@ static void subghz_cli_command_encrypt_keeloq(Cli* cli, FuriString* args) {
|
||||
furi_string_free(source);
|
||||
}
|
||||
|
||||
static void subghz_cli_command_encrypt_raw(Cli* cli, FuriString* args) {
|
||||
UNUSED(cli);
|
||||
static void subghz_cli_command_encrypt_raw(PipeSide* pipe, FuriString* args) {
|
||||
UNUSED(pipe);
|
||||
uint8_t iv[16];
|
||||
|
||||
FuriString* source = furi_string_alloc();
|
||||
@@ -920,7 +924,7 @@ static void subghz_cli_command_encrypt_raw(Cli* cli, FuriString* args) {
|
||||
furi_string_free(source);
|
||||
}
|
||||
|
||||
static void subghz_cli_command_chat(Cli* cli, FuriString* args) {
|
||||
static void subghz_cli_command_chat(PipeSide* pipe, FuriString* args) {
|
||||
uint32_t frequency = 433920000;
|
||||
uint32_t device_ind = 0; // 0 - CC1101_INT, 1 - CC1101_EXT
|
||||
|
||||
@@ -954,7 +958,7 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args) {
|
||||
return;
|
||||
}
|
||||
|
||||
SubGhzChatWorker* subghz_chat = subghz_chat_worker_alloc(cli);
|
||||
SubGhzChatWorker* subghz_chat = subghz_chat_worker_alloc(pipe);
|
||||
|
||||
if(!subghz_chat_worker_start(subghz_chat, device, frequency)) {
|
||||
printf("Startup error SubGhzChatWorker\r\n");
|
||||
@@ -991,13 +995,12 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args) {
|
||||
chat_event = subghz_chat_worker_get_event_chat(subghz_chat);
|
||||
switch(chat_event.event) {
|
||||
case SubGhzChatEventInputData:
|
||||
if(chat_event.c == CliSymbolAsciiETX) {
|
||||
if(chat_event.c == CliKeyETX) {
|
||||
printf("\r\n");
|
||||
chat_event.event = SubGhzChatEventUserExit;
|
||||
subghz_chat_worker_put_event_chat(subghz_chat, &chat_event);
|
||||
break;
|
||||
} else if(
|
||||
(chat_event.c == CliSymbolAsciiBackspace) || (chat_event.c == CliSymbolAsciiDel)) {
|
||||
} else if((chat_event.c == CliKeyBackspace) || (chat_event.c == CliKeyDEL)) {
|
||||
size_t len = furi_string_utf8_length(input);
|
||||
if(len > furi_string_utf8_length(name)) {
|
||||
printf("%s", "\e[D\e[1P");
|
||||
@@ -1019,7 +1022,7 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args) {
|
||||
}
|
||||
furi_string_set(input, sysmsg);
|
||||
}
|
||||
} else if(chat_event.c == CliSymbolAsciiCR) {
|
||||
} else if(chat_event.c == CliKeyCR) {
|
||||
printf("\r\n");
|
||||
furi_string_push_back(input, '\r');
|
||||
furi_string_push_back(input, '\n');
|
||||
@@ -1033,7 +1036,7 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args) {
|
||||
furi_string_printf(input, "%s", furi_string_get_cstr(name));
|
||||
printf("%s", furi_string_get_cstr(input));
|
||||
fflush(stdout);
|
||||
} else if(chat_event.c == CliSymbolAsciiLF) {
|
||||
} else if(chat_event.c == CliKeyLF) {
|
||||
//cut out the symbol \n
|
||||
} else {
|
||||
putc(chat_event.c, stdout);
|
||||
@@ -1087,7 +1090,7 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!cli_is_connected(cli)) {
|
||||
if(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) {
|
||||
printf("\r\n");
|
||||
chat_event.event = SubGhzChatEventUserExit;
|
||||
subghz_chat_worker_put_event_chat(subghz_chat, &chat_event);
|
||||
@@ -1112,7 +1115,7 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args) {
|
||||
printf("\r\nExit chat\r\n");
|
||||
}
|
||||
|
||||
static void subghz_cli_command(Cli* cli, FuriString* args, void* context) {
|
||||
static void subghz_cli_command(PipeSide* pipe, FuriString* args, void* context) {
|
||||
FuriString* cmd = furi_string_alloc();
|
||||
|
||||
do {
|
||||
@@ -1122,53 +1125,53 @@ static void subghz_cli_command(Cli* cli, FuriString* args, void* context) {
|
||||
}
|
||||
|
||||
if(furi_string_cmp_str(cmd, "chat") == 0) {
|
||||
subghz_cli_command_chat(cli, args);
|
||||
subghz_cli_command_chat(pipe, args);
|
||||
break;
|
||||
}
|
||||
|
||||
if(furi_string_cmp_str(cmd, "tx") == 0) {
|
||||
subghz_cli_command_tx(cli, args, context);
|
||||
subghz_cli_command_tx(pipe, args, context);
|
||||
break;
|
||||
}
|
||||
|
||||
if(furi_string_cmp_str(cmd, "rx") == 0) {
|
||||
subghz_cli_command_rx(cli, args, context);
|
||||
subghz_cli_command_rx(pipe, args, context);
|
||||
break;
|
||||
}
|
||||
|
||||
if(furi_string_cmp_str(cmd, "rx_raw") == 0) {
|
||||
subghz_cli_command_rx_raw(cli, args, context);
|
||||
subghz_cli_command_rx_raw(pipe, args, context);
|
||||
break;
|
||||
}
|
||||
|
||||
if(furi_string_cmp_str(cmd, "decode_raw") == 0) {
|
||||
subghz_cli_command_decode_raw(cli, args, context);
|
||||
subghz_cli_command_decode_raw(pipe, args, context);
|
||||
break;
|
||||
}
|
||||
|
||||
if(furi_string_cmp_str(cmd, "tx_from_file") == 0) {
|
||||
subghz_cli_command_tx_from_file(cli, args, context);
|
||||
subghz_cli_command_tx_from_file(pipe, args, context);
|
||||
break;
|
||||
}
|
||||
|
||||
if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) {
|
||||
if(furi_string_cmp_str(cmd, "encrypt_keeloq") == 0) {
|
||||
subghz_cli_command_encrypt_keeloq(cli, args);
|
||||
subghz_cli_command_encrypt_keeloq(pipe, args);
|
||||
break;
|
||||
}
|
||||
|
||||
if(furi_string_cmp_str(cmd, "encrypt_raw") == 0) {
|
||||
subghz_cli_command_encrypt_raw(cli, args);
|
||||
subghz_cli_command_encrypt_raw(pipe, args);
|
||||
break;
|
||||
}
|
||||
|
||||
if(furi_string_cmp_str(cmd, "tx_carrier") == 0) {
|
||||
subghz_cli_command_tx_carrier(cli, args, context);
|
||||
subghz_cli_command_tx_carrier(pipe, args, context);
|
||||
break;
|
||||
}
|
||||
|
||||
if(furi_string_cmp_str(cmd, "rx_carrier") == 0) {
|
||||
subghz_cli_command_rx_carrier(cli, args, context);
|
||||
subghz_cli_command_rx_carrier(pipe, args, context);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1182,9 +1185,9 @@ static void subghz_cli_command(Cli* cli, FuriString* args, void* context) {
|
||||
#include <cli/cli_i.h>
|
||||
CLI_PLUGIN_WRAPPER("subghz", subghz_cli_command)
|
||||
|
||||
static void subghz_cli_command_chat_wrapper(Cli* cli, FuriString* args, void* context) {
|
||||
static void subghz_cli_command_chat_wrapper(PipeSide* pipe, FuriString* args, void* context) {
|
||||
furi_string_replace_at(args, 0, 0, "chat ");
|
||||
subghz_cli_command_wrapper(cli, args, context);
|
||||
subghz_cli_command_wrapper(pipe, args, context);
|
||||
}
|
||||
|
||||
void subghz_on_system_start(void) {
|
||||
|
||||
@@ -3,6 +3,7 @@ App(
|
||||
name="Basic services",
|
||||
apptype=FlipperAppType.METAPACKAGE,
|
||||
provides=[
|
||||
"cli_vcp",
|
||||
"crypto_start",
|
||||
"rpc_start",
|
||||
"expansion_start",
|
||||
|
||||
@@ -2,14 +2,15 @@
|
||||
#include <furi_hal.h>
|
||||
#include <cli/cli.h>
|
||||
#include <lib/toolbox/args.h>
|
||||
#include <toolbox/pipe.h>
|
||||
|
||||
#include <ble/ble.h>
|
||||
#include "bt_service/bt.h"
|
||||
#include "bt_service/bt_settings_api_i.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 +20,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 +42,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 +52,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 +70,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 +83,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 +120,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 +131,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 +153,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 +180,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);
|
||||
Bt* bt = furi_record_open(RECORD_BT);
|
||||
|
||||
@@ -194,24 +195,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,36 @@
|
||||
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.c",
|
||||
"shell/cli_shell.c",
|
||||
"shell/cli_shell_line.c",
|
||||
"cli_commands.c",
|
||||
"cli_command_gpio.c",
|
||||
"cli_ansi.c",
|
||||
],
|
||||
sdk_headers=[
|
||||
"cli.h",
|
||||
"cli_ansi.h",
|
||||
],
|
||||
# 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(
|
||||
@@ -27,15 +51,6 @@ App(
|
||||
sources=["cli_commands.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="help_cli",
|
||||
targets=["f7"],
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="cli_command_help_plugin_ep",
|
||||
requires=["cli"],
|
||||
sources=["cli_commands.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="uptime_cli",
|
||||
targets=["f7"],
|
||||
|
||||
@@ -1,453 +1,62 @@
|
||||
#include "cli.h"
|
||||
#include "cli_i.h"
|
||||
#include "cli_commands.h"
|
||||
#include "cli_vcp.h"
|
||||
#include <furi_hal_version.h>
|
||||
#include <loader/loader.h>
|
||||
#include "cli_ansi.h"
|
||||
#include <toolbox/pipe.h>
|
||||
|
||||
#include <flipper_application/plugins/plugin_manager.h>
|
||||
#include <loader/firmware_api/firmware_api.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define TAG "CliSrv"
|
||||
#define TAG "cli"
|
||||
|
||||
#define CLI_INPUT_LEN_LIMIT 256
|
||||
struct Cli {
|
||||
CliCommandTree_t commands;
|
||||
FuriMutex* mutex;
|
||||
};
|
||||
|
||||
Cli* cli_alloc(void) {
|
||||
Cli* cli = malloc(sizeof(Cli));
|
||||
|
||||
CliCommandTree_init(cli->commands);
|
||||
|
||||
cli->last_line = furi_string_alloc();
|
||||
cli->line = furi_string_alloc();
|
||||
|
||||
cli->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 size_t cli_string_distance(const char* s1, const char* s2) {
|
||||
size_t distance = 0;
|
||||
|
||||
while(*s1 && *s2) {
|
||||
if(*s1++ != *s2++) distance++;
|
||||
}
|
||||
while(*s1++)
|
||||
distance++;
|
||||
while(*s2++)
|
||||
distance++;
|
||||
|
||||
return distance;
|
||||
}
|
||||
|
||||
static void cli_find_similar_command(Cli* cli, const char* input, FuriString* suggestion) {
|
||||
size_t min_distance = (size_t)-1;
|
||||
size_t max_allowed = (strlen(input) + 1) / 2;
|
||||
furi_string_reset(suggestion);
|
||||
|
||||
CliCommandTree_it_t it;
|
||||
for(CliCommandTree_it(it, cli->commands); !CliCommandTree_end_p(it); CliCommandTree_next(it)) {
|
||||
const char* cmd_name = furi_string_get_cstr(*CliCommandTree_ref(it)->key_ptr);
|
||||
size_t distance = cli_string_distance(input, cmd_name);
|
||||
if(distance < min_distance && distance <= max_allowed) {
|
||||
min_distance = distance;
|
||||
furi_string_set(suggestion, cmd_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
FuriString* suggestion = furi_string_alloc();
|
||||
cli_find_similar_command(cli, furi_string_get_cstr(command), suggestion);
|
||||
|
||||
if(furi_string_empty(suggestion)) {
|
||||
printf(
|
||||
"`%s` command not found, use `help` or `?` to list all available commands",
|
||||
furi_string_get_cstr(command));
|
||||
} else {
|
||||
printf(
|
||||
"`%s` command not found, did you mean `%s`? Use `help` or `?` to list all available commands",
|
||||
furi_string_get_cstr(command),
|
||||
furi_string_get_cstr(suggestion));
|
||||
}
|
||||
|
||||
furi_string_free(suggestion);
|
||||
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,
|
||||
CliExecuteCallback callback,
|
||||
void* context) {
|
||||
cli_add_command_ex(cli, name, flags, callback, context, CLI_BUILTIN_COMMAND_STACK_SIZE);
|
||||
}
|
||||
|
||||
void cli_add_command_ex(
|
||||
Cli* cli,
|
||||
const char* name,
|
||||
CliCommandFlag flags,
|
||||
CliExecuteCallback callback,
|
||||
void* context,
|
||||
size_t stack_size) {
|
||||
furi_check(cli);
|
||||
furi_check(name);
|
||||
furi_check(callback);
|
||||
|
||||
FuriString* name_str;
|
||||
name_str = furi_string_alloc_set(name);
|
||||
furi_string_trim(name_str);
|
||||
// command cannot contain spaces
|
||||
furi_check(furi_string_search_char(name_str, ' ') == FURI_STRING_FAILURE);
|
||||
|
||||
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;
|
||||
CliCommand command = {
|
||||
.context = context,
|
||||
.execute_callback = callback,
|
||||
.flags = flags,
|
||||
.stack_depth = stack_size,
|
||||
};
|
||||
|
||||
furi_check(furi_mutex_acquire(cli->mutex, FuriWaitForever) == FuriStatusOk);
|
||||
CliCommandTree_set_at(cli->commands, name_str, c);
|
||||
CliCommandTree_set_at(cli->commands, name_str, command);
|
||||
furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk);
|
||||
|
||||
furi_string_free(name_str);
|
||||
@@ -471,74 +80,62 @@ void cli_delete_command(Cli* cli, const char* name) {
|
||||
furi_string_free(name_str);
|
||||
}
|
||||
|
||||
void cli_session_open(Cli* cli, const void* session) {
|
||||
furi_check(cli);
|
||||
|
||||
bool cli_get_command(Cli* cli, FuriString* command, CliCommand* result) {
|
||||
furi_assert(cli);
|
||||
furi_check(furi_mutex_acquire(cli->mutex, FuriWaitForever) == FuriStatusOk);
|
||||
cli->session = session;
|
||||
if(cli->session != NULL) {
|
||||
cli->session->init();
|
||||
furi_thread_set_stdout_callback(cli->session->tx_stdout, NULL);
|
||||
} else {
|
||||
furi_thread_set_stdout_callback(NULL, NULL);
|
||||
}
|
||||
furi_semaphore_release(cli->idle_sem);
|
||||
CliCommand* data = CliCommandTree_get(cli->commands, command);
|
||||
if(data) *result = *data;
|
||||
|
||||
furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk);
|
||||
|
||||
return !!data;
|
||||
}
|
||||
|
||||
void cli_session_close(Cli* cli) {
|
||||
furi_check(cli);
|
||||
|
||||
void cli_lock_commands(Cli* cli) {
|
||||
furi_assert(cli);
|
||||
furi_check(furi_mutex_acquire(cli->mutex, FuriWaitForever) == FuriStatusOk);
|
||||
if(cli->session != NULL) {
|
||||
cli->session->deinit();
|
||||
}
|
||||
cli->session = NULL;
|
||||
furi_thread_set_stdout_callback(NULL, NULL);
|
||||
furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk);
|
||||
}
|
||||
|
||||
int32_t cli_srv(void* p) {
|
||||
UNUSED(p);
|
||||
void cli_unlock_commands(Cli* cli) {
|
||||
furi_assert(cli);
|
||||
furi_mutex_release(cli->mutex);
|
||||
}
|
||||
|
||||
CliCommandTree_t* cli_get_commands(Cli* cli) {
|
||||
furi_assert(cli);
|
||||
return &cli->commands;
|
||||
}
|
||||
|
||||
bool cli_is_pipe_broken_or_is_etx_next_char(PipeSide* side) {
|
||||
if(pipe_state(side) == PipeStateBroken) return true;
|
||||
if(!pipe_bytes_available(side)) return false;
|
||||
char c = getchar();
|
||||
return c == CliKeyETX;
|
||||
}
|
||||
|
||||
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_on_system_start(void) {
|
||||
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_is_normal_boot()) {
|
||||
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;
|
||||
}
|
||||
|
||||
void cli_plugin_wrapper(const char* name, Cli* cli, FuriString* args, void* context) {
|
||||
void cli_plugin_wrapper(const char* name, PipeSide* pipe, FuriString* args, void* context) {
|
||||
PluginManager* manager =
|
||||
plugin_manager_alloc(CLI_PLUGIN_APP_ID, CLI_PLUGIN_API_VERSION, firmware_api_interface);
|
||||
FuriString* path =
|
||||
furi_string_alloc_printf(EXT_PATH("apps_data/cli/plugins/%s_cli.fal"), name);
|
||||
PluginManagerError error = plugin_manager_load_single(manager, furi_string_get_cstr(path));
|
||||
if(error == PluginManagerErrorNone) {
|
||||
const CliCallback handler = plugin_manager_get_ep(manager, 0);
|
||||
handler(cli, args, context);
|
||||
const CliExecuteCallback handler = plugin_manager_get_ep(manager, 0);
|
||||
handler(pipe, args, context);
|
||||
} else {
|
||||
printf(
|
||||
"CLI plugin '%s' failed (code %" PRIu16 "), reinstall firmware or check logs\r\n",
|
||||
|
||||
@@ -1,64 +1,102 @@
|
||||
/**
|
||||
* @file cli.h
|
||||
* Cli API
|
||||
* API for registering commands with the CLI
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <furi.h>
|
||||
#include <m-array.h>
|
||||
#include "cli_ansi.h"
|
||||
#include <toolbox/pipe.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"
|
||||
|
||||
typedef enum {
|
||||
CliCommandFlagDefault = 0, /**< Default */
|
||||
CliCommandFlagParallelSafe = (1 << 0), /**< Safe to run in parallel with other apps */
|
||||
CliCommandFlagInsomniaSafe = (1 << 1), /**< Safe to run with insomnia mode on */
|
||||
CliCommandFlagDontAttachStdio = (1 << 2), /**< Do no attach I/O pipe to thread stdio */
|
||||
} CliCommandFlag;
|
||||
|
||||
/** 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
|
||||
/**
|
||||
* @brief CLI execution callback pointer. Implement this interface and use
|
||||
* `add_cli_command`.
|
||||
*
|
||||
* @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
|
||||
* This callback will be called from a separate thread spawned just for your
|
||||
* command. The pipe will be installed as the thread's stdio, so you can use
|
||||
* `printf`, `getchar` and other standard functions to communicate with the
|
||||
* user.
|
||||
*
|
||||
* @param [in] pipe Pipe that can be used to send and receive data. If
|
||||
* `CliCommandFlagDontAttachStdio` was not set, you can
|
||||
* also use standard C functions (printf, getc, etc.) to
|
||||
* access this pipe.
|
||||
* @param [in] args String with what was passed after the command
|
||||
* @param [in] context Whatever you provided to `cli_add_command`
|
||||
*/
|
||||
typedef void (*CliExecuteCallback)(PipeSide* pipe, FuriString* args, void* context);
|
||||
|
||||
/**
|
||||
* @brief Registers a command with the CLI. Provides less options than the `_ex`
|
||||
* counterpart.
|
||||
*
|
||||
* @param [in] cli Pointer to CLI instance
|
||||
* @param [in] name Command name
|
||||
* @param [in] flags CliCommandFlag
|
||||
* @param [in] callback Callback function
|
||||
* @param [in] context Custom context
|
||||
*/
|
||||
void cli_add_command(
|
||||
Cli* cli,
|
||||
const char* name,
|
||||
CliCommandFlag flags,
|
||||
CliCallback callback,
|
||||
CliExecuteCallback callback,
|
||||
void* context);
|
||||
|
||||
/**
|
||||
* @brief Registers a command with the CLI. Provides more options than the
|
||||
* non-`_ex` counterpart.
|
||||
*
|
||||
* @param [in] cli Pointer to CLI instance
|
||||
* @param [in] name Command name
|
||||
* @param [in] flags CliCommandFlag
|
||||
* @param [in] callback Callback function
|
||||
* @param [in] context Custom context
|
||||
* @param [in] stack_size Thread stack size
|
||||
*/
|
||||
void cli_add_command_ex(
|
||||
Cli* cli,
|
||||
const char* name,
|
||||
CliCommandFlag flags,
|
||||
CliExecuteCallback callback,
|
||||
void* context,
|
||||
size_t stack_size);
|
||||
|
||||
/**
|
||||
* @brief Deletes a cli command
|
||||
*
|
||||
* @param [in] cli pointer to cli instance
|
||||
* @param [in] name command name
|
||||
*/
|
||||
void cli_delete_command(Cli* cli, const char* name);
|
||||
|
||||
/**
|
||||
* @brief Detects if Ctrl+C has been pressed or session has been terminated
|
||||
*
|
||||
* @param [in] side Pointer to pipe side given to the command thread
|
||||
* @warning This function also assumes that the pipe is installed as the
|
||||
* thread's stdio
|
||||
* @warning This function will consume 1 byte from the pipe
|
||||
*/
|
||||
bool cli_is_pipe_broken_or_is_etx_next_char(PipeSide* side);
|
||||
|
||||
/** Print unified cmd usage tip
|
||||
*
|
||||
* @param cmd cmd name
|
||||
@@ -67,68 +105,6 @@ void cli_add_command(
|
||||
*/
|
||||
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
|
||||
|
||||
123
applications/services/cli/cli_ansi.c
Normal file
123
applications/services/cli/cli_ansi.c
Normal file
@@ -0,0 +1,123 @@
|
||||
#include "cli_ansi.h"
|
||||
|
||||
typedef enum {
|
||||
CliAnsiParserStateInitial,
|
||||
CliAnsiParserStateEscape,
|
||||
CliAnsiParserStateEscapeBrace,
|
||||
CliAnsiParserStateEscapeBraceOne,
|
||||
CliAnsiParserStateEscapeBraceOneSemicolon,
|
||||
CliAnsiParserStateEscapeBraceOneSemicolonModifiers,
|
||||
} CliAnsiParserState;
|
||||
|
||||
struct CliAnsiParser {
|
||||
CliAnsiParserState state;
|
||||
CliModKey modifiers;
|
||||
};
|
||||
|
||||
CliAnsiParser* cli_ansi_parser_alloc(void) {
|
||||
CliAnsiParser* parser = malloc(sizeof(CliAnsiParser));
|
||||
return parser;
|
||||
}
|
||||
|
||||
void cli_ansi_parser_free(CliAnsiParser* parser) {
|
||||
free(parser);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Converts a single character representing a special key into the enum
|
||||
* representation
|
||||
*/
|
||||
static CliKey cli_ansi_key_from_mnemonic(char c) {
|
||||
switch(c) {
|
||||
case 'A':
|
||||
return CliKeyUp;
|
||||
case 'B':
|
||||
return CliKeyDown;
|
||||
case 'C':
|
||||
return CliKeyRight;
|
||||
case 'D':
|
||||
return CliKeyLeft;
|
||||
case 'F':
|
||||
return CliKeyEnd;
|
||||
case 'H':
|
||||
return CliKeyHome;
|
||||
default:
|
||||
return CliKeyUnrecognized;
|
||||
}
|
||||
}
|
||||
|
||||
#define PARSER_RESET_AND_RETURN(parser, modifiers_val, key_val) \
|
||||
do { \
|
||||
parser->state = CliAnsiParserStateInitial; \
|
||||
return (CliAnsiParserResult){ \
|
||||
.is_done = true, \
|
||||
.result = (CliKeyCombo){ \
|
||||
.modifiers = modifiers_val, \
|
||||
.key = key_val, \
|
||||
}}; \
|
||||
} while(0);
|
||||
|
||||
CliAnsiParserResult cli_ansi_parser_feed(CliAnsiParser* parser, char c) {
|
||||
switch(parser->state) {
|
||||
case CliAnsiParserStateInitial:
|
||||
// <key> -> <key>
|
||||
if(c != CliKeyEsc) PARSER_RESET_AND_RETURN(parser, CliModKeyNo, c); // -V1048
|
||||
|
||||
// <ESC> ...
|
||||
parser->state = CliAnsiParserStateEscape;
|
||||
break;
|
||||
|
||||
case CliAnsiParserStateEscape:
|
||||
// <ESC> <ESC> -> <ESC>
|
||||
if(c == CliKeyEsc) PARSER_RESET_AND_RETURN(parser, CliModKeyNo, c);
|
||||
|
||||
// <ESC> <key> -> Alt + <key>
|
||||
if(c != '[') PARSER_RESET_AND_RETURN(parser, CliModKeyAlt, c);
|
||||
|
||||
// <ESC> [ ...
|
||||
parser->state = CliAnsiParserStateEscapeBrace;
|
||||
break;
|
||||
|
||||
case CliAnsiParserStateEscapeBrace:
|
||||
// <ESC> [ <key mnemonic> -> <key>
|
||||
if(c != '1') PARSER_RESET_AND_RETURN(parser, CliModKeyNo, cli_ansi_key_from_mnemonic(c));
|
||||
|
||||
// <ESC> [ 1 ...
|
||||
parser->state = CliAnsiParserStateEscapeBraceOne;
|
||||
break;
|
||||
|
||||
case CliAnsiParserStateEscapeBraceOne:
|
||||
// <ESC> [ 1 <non-;> -> error
|
||||
if(c != ';') PARSER_RESET_AND_RETURN(parser, CliModKeyNo, CliKeyUnrecognized);
|
||||
|
||||
// <ESC> [ 1 ; ...
|
||||
parser->state = CliAnsiParserStateEscapeBraceOneSemicolon;
|
||||
break;
|
||||
|
||||
case CliAnsiParserStateEscapeBraceOneSemicolon:
|
||||
// <ESC> [ 1 ; <modifiers> ...
|
||||
parser->modifiers = (c - '0');
|
||||
parser->modifiers &= ~1;
|
||||
parser->state = CliAnsiParserStateEscapeBraceOneSemicolonModifiers;
|
||||
break;
|
||||
|
||||
case CliAnsiParserStateEscapeBraceOneSemicolonModifiers:
|
||||
// <ESC> [ 1 ; <modifiers> <key mnemonic> -> <modifiers> + <key>
|
||||
PARSER_RESET_AND_RETURN(parser, parser->modifiers, cli_ansi_key_from_mnemonic(c));
|
||||
}
|
||||
|
||||
return (CliAnsiParserResult){.is_done = false};
|
||||
}
|
||||
|
||||
CliAnsiParserResult cli_ansi_parser_feed_timeout(CliAnsiParser* parser) {
|
||||
CliAnsiParserResult result = {.is_done = false};
|
||||
|
||||
if(parser->state == CliAnsiParserStateEscape) {
|
||||
result.is_done = true;
|
||||
result.result.key = CliKeyEsc;
|
||||
result.result.modifiers = CliModKeyNo;
|
||||
}
|
||||
|
||||
parser->state = CliAnsiParserStateInitial;
|
||||
return result;
|
||||
}
|
||||
153
applications/services/cli/cli_ansi.h
Normal file
153
applications/services/cli/cli_ansi.h
Normal file
@@ -0,0 +1,153 @@
|
||||
#pragma once
|
||||
|
||||
#include "cli.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// text styling
|
||||
|
||||
#define ANSI_RESET "\e[0m"
|
||||
#define ANSI_BOLD "\e[1m"
|
||||
#define ANSI_FAINT "\e[2m"
|
||||
#define ANSI_INVERT "\e[7m"
|
||||
|
||||
#define ANSI_FG_BLACK "\e[30m"
|
||||
#define ANSI_FG_RED "\e[31m"
|
||||
#define ANSI_FG_GREEN "\e[32m"
|
||||
#define ANSI_FG_YELLOW "\e[33m"
|
||||
#define ANSI_FG_BLUE "\e[34m"
|
||||
#define ANSI_FG_MAGENTA "\e[35m"
|
||||
#define ANSI_FG_CYAN "\e[36m"
|
||||
#define ANSI_FG_WHITE "\e[37m"
|
||||
#define ANSI_FG_BR_BLACK "\e[90m"
|
||||
#define ANSI_FG_BR_RED "\e[91m"
|
||||
#define ANSI_FG_BR_GREEN "\e[92m"
|
||||
#define ANSI_FG_BR_YELLOW "\e[93m"
|
||||
#define ANSI_FG_BR_BLUE "\e[94m"
|
||||
#define ANSI_FG_BR_MAGENTA "\e[95m"
|
||||
#define ANSI_FG_BR_CYAN "\e[96m"
|
||||
#define ANSI_FG_BR_WHITE "\e[97m"
|
||||
|
||||
#define ANSI_BG_BLACK "\e[40m"
|
||||
#define ANSI_BG_RED "\e[41m"
|
||||
#define ANSI_BG_GREEN "\e[42m"
|
||||
#define ANSI_BG_YELLOW "\e[43m"
|
||||
#define ANSI_BG_BLUE "\e[44m"
|
||||
#define ANSI_BG_MAGENTA "\e[45m"
|
||||
#define ANSI_BG_CYAN "\e[46m"
|
||||
#define ANSI_BG_WHITE "\e[47m"
|
||||
#define ANSI_BG_BR_BLACK "\e[100m"
|
||||
#define ANSI_BG_BR_RED "\e[101m"
|
||||
#define ANSI_BG_BR_GREEN "\e[102m"
|
||||
#define ANSI_BG_BR_YELLOW "\e[103m"
|
||||
#define ANSI_BG_BR_BLUE "\e[104m"
|
||||
#define ANSI_BG_BR_MAGENTA "\e[105m"
|
||||
#define ANSI_BG_BR_CYAN "\e[106m"
|
||||
#define ANSI_BG_BR_WHITE "\e[107m"
|
||||
|
||||
#define ANSI_FLIPPER_BRAND_ORANGE "\e[38;2;255;130;0m"
|
||||
|
||||
// cursor positioning
|
||||
|
||||
#define ANSI_CURSOR_UP_BY(rows) "\e[" rows "A"
|
||||
#define ANSI_CURSOR_DOWN_BY(rows) "\e[" rows "B"
|
||||
#define ANSI_CURSOR_RIGHT_BY(cols) "\e[" cols "C"
|
||||
#define ANSI_CURSOR_LEFT_BY(cols) "\e[" cols "D"
|
||||
#define ANSI_CURSOR_DOWN_BY_AND_FIRST_COLUMN(rows) "\e[" rows "E"
|
||||
#define ANSI_CURSOR_UP_BY_AND_FIRST_COLUMN(rows) "\e[" rows "F"
|
||||
#define ANSI_CURSOR_HOR_POS(pos) "\e[" pos "G"
|
||||
#define ANSI_CURSOR_POS(row, col) "\e[" row ";" col "H"
|
||||
|
||||
// erasing
|
||||
|
||||
#define ANSI_ERASE_FROM_CURSOR_TO_END "0"
|
||||
#define ANSI_ERASE_FROM_START_TO_CURSOR "1"
|
||||
#define ANSI_ERASE_ENTIRE "2"
|
||||
|
||||
#define ANSI_ERASE_DISPLAY(portion) "\e[" portion "J"
|
||||
#define ANSI_ERASE_LINE(portion) "\e[" portion "K"
|
||||
#define ANSI_ERASE_SCROLLBACK_BUFFER ANSI_ERASE_DISPLAY("3")
|
||||
|
||||
// misc
|
||||
|
||||
#define ANSI_INSERT_MODE_ENABLE "\e[4h"
|
||||
#define ANSI_INSERT_MODE_DISABLE "\e[4l"
|
||||
|
||||
typedef enum {
|
||||
CliKeyUnrecognized = 0,
|
||||
|
||||
CliKeySOH = 0x01,
|
||||
CliKeyETX = 0x03,
|
||||
CliKeyEOT = 0x04,
|
||||
CliKeyBell = 0x07,
|
||||
CliKeyBackspace = 0x08,
|
||||
CliKeyTab = 0x09,
|
||||
CliKeyLF = 0x0A,
|
||||
CliKeyFF = 0x0C,
|
||||
CliKeyCR = 0x0D,
|
||||
CliKeyETB = 0x17,
|
||||
CliKeyEsc = 0x1B,
|
||||
CliKeyUS = 0x1F,
|
||||
CliKeySpace = 0x20,
|
||||
CliKeyDEL = 0x7F,
|
||||
|
||||
CliKeySpecial = 0x80,
|
||||
CliKeyLeft,
|
||||
CliKeyRight,
|
||||
CliKeyUp,
|
||||
CliKeyDown,
|
||||
CliKeyHome,
|
||||
CliKeyEnd,
|
||||
} CliKey;
|
||||
|
||||
typedef enum {
|
||||
CliModKeyNo = 0,
|
||||
CliModKeyAlt = 2,
|
||||
CliModKeyCtrl = 4,
|
||||
CliModKeyMeta = 8,
|
||||
} CliModKey;
|
||||
|
||||
typedef struct {
|
||||
CliModKey modifiers;
|
||||
CliKey key;
|
||||
} CliKeyCombo;
|
||||
|
||||
typedef struct CliAnsiParser CliAnsiParser;
|
||||
|
||||
typedef struct {
|
||||
bool is_done;
|
||||
CliKeyCombo result;
|
||||
} CliAnsiParserResult;
|
||||
|
||||
/**
|
||||
* @brief Allocates an ANSI parser
|
||||
*/
|
||||
CliAnsiParser* cli_ansi_parser_alloc(void);
|
||||
|
||||
/**
|
||||
* @brief Frees an ANSI parser
|
||||
*/
|
||||
void cli_ansi_parser_free(CliAnsiParser* parser);
|
||||
|
||||
/**
|
||||
* @brief Feeds an ANSI parser a character
|
||||
*/
|
||||
CliAnsiParserResult cli_ansi_parser_feed(CliAnsiParser* parser, char c);
|
||||
|
||||
/**
|
||||
* @brief Feeds an ANSI parser a timeout event
|
||||
*
|
||||
* As a user of the ANSI parser API, you are responsible for calling this
|
||||
* function some time after the last character was fed into the parser. The
|
||||
* recommended timeout is about 10 ms. The exact value does not matter as long
|
||||
* as it is small enough for the user not notice a delay, but big enough that
|
||||
* when a terminal is sending an escape sequence, this function does not get
|
||||
* called in between the characters of the sequence.
|
||||
*/
|
||||
CliAnsiParserResult cli_ansi_parser_feed_timeout(CliAnsiParser* parser);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <lib/toolbox/args.h>
|
||||
#include <toolbox/pipe.h>
|
||||
|
||||
void cli_command_gpio_print_usage(void) {
|
||||
printf("Usage:\r\n");
|
||||
@@ -70,8 +71,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 +94,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 +111,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 +132,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 +161,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 +172,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 +183,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;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#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);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "cli_commands.h"
|
||||
#include "cli_command_gpio.h"
|
||||
#include "cli_ansi.h"
|
||||
#include "cli.h"
|
||||
|
||||
#include <core/thread.h>
|
||||
#include <furi_hal.h>
|
||||
@@ -11,6 +13,7 @@
|
||||
#include <loader/loader.h>
|
||||
#include <lib/toolbox/args.h>
|
||||
#include <lib/toolbox/strint.h>
|
||||
#include <toolbox/pipe.h>
|
||||
#include <storage/storage.h>
|
||||
|
||||
// Close to ISO, `date +'%Y-%m-%d %H:%M:%S %u'`
|
||||
@@ -35,8 +38,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);
|
||||
@@ -55,8 +58,8 @@ void cli_command_info(Cli* cli, FuriString* args, void* context) {
|
||||
}
|
||||
|
||||
// Lil Easter egg :>
|
||||
void cli_command_neofetch(Cli* cli, FuriString* args, void* context) {
|
||||
UNUSED(cli);
|
||||
void cli_command_neofetch(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(pipe);
|
||||
UNUSED(args);
|
||||
UNUSED(context);
|
||||
|
||||
@@ -72,9 +75,7 @@ void cli_command_neofetch(Cli* cli, FuriString* args, void* context) {
|
||||
"| `-...| -.___-Z:_______J...---;",
|
||||
": ` _-'",
|
||||
};
|
||||
#define ANSI_RESET "\e[0m"
|
||||
#define ANSI_FLIPPER_BRAND_ORANGE "\e[38;2;255;130;0m"
|
||||
#define NEOFETCH_COLOR ANSI_FLIPPER_BRAND_ORANGE
|
||||
#define NEOFETCH_COLOR ANSI_FLIPPER_BRAND_ORANGE
|
||||
|
||||
// Determine logo parameters
|
||||
size_t logo_height = COUNT_OF(neofetch_logo), logo_width = 0;
|
||||
@@ -206,60 +207,59 @@ void cli_command_neofetch(Cli* cli, FuriString* args, void* context) {
|
||||
}
|
||||
printf(ANSI_RESET);
|
||||
#undef NEOFETCH_COLOR
|
||||
#undef ANSI_FLIPPER_BRAND_ORANGE
|
||||
#undef ANSI_RESET
|
||||
}
|
||||
|
||||
void cli_command_help(Cli* cli, FuriString* args, void* context) {
|
||||
void cli_command_help(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(pipe);
|
||||
UNUSED(args);
|
||||
UNUSED(context);
|
||||
printf("Commands available:");
|
||||
printf("Available commands:" ANSI_FG_GREEN);
|
||||
|
||||
// Command count
|
||||
const size_t commands_count = CliCommandTree_size(cli->commands);
|
||||
const size_t commands_count_mid = commands_count / 2 + commands_count % 2;
|
||||
// count non-hidden commands
|
||||
Cli* cli = furi_record_open(RECORD_CLI);
|
||||
cli_lock_commands(cli);
|
||||
CliCommandTree_t* commands = cli_get_commands(cli);
|
||||
size_t commands_count = CliCommandTree_size(*commands);
|
||||
|
||||
// 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");
|
||||
// create iterators starting at different positions
|
||||
const size_t columns = 3;
|
||||
const size_t commands_per_column = (commands_count / columns) + (commands_count % columns);
|
||||
CliCommandTree_it_t iterators[columns];
|
||||
for(size_t c = 0; c < columns; c++) {
|
||||
CliCommandTree_it(iterators[c], *commands);
|
||||
for(size_t i = 0; i < c * commands_per_column; i++)
|
||||
CliCommandTree_next(iterators[c]);
|
||||
}
|
||||
|
||||
// print commands
|
||||
for(size_t r = 0; r < commands_per_column; r++) {
|
||||
printf("\r\n");
|
||||
|
||||
for(size_t c = 0; c < columns; c++) {
|
||||
if(!CliCommandTree_end_p(iterators[c])) {
|
||||
const CliCommandTree_itref_t* item = CliCommandTree_cref(iterators[c]);
|
||||
printf("%-30s", furi_string_get_cstr(*item->key_ptr));
|
||||
CliCommandTree_next(iterators[c]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
printf(ANSI_RESET "\r\nFind out more: https://docs.flipper.net/development/cli");
|
||||
|
||||
cli_unlock_commands(cli);
|
||||
furi_record_close(RECORD_CLI);
|
||||
}
|
||||
|
||||
void cli_command_uptime(Cli* cli, FuriString* args, void* context) {
|
||||
UNUSED(cli);
|
||||
void cli_command_uptime(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};
|
||||
@@ -327,10 +327,10 @@ void cli_command_date(Cli* cli, FuriString* args, void* context) {
|
||||
}
|
||||
}
|
||||
|
||||
void cli_command_src(Cli* cli, FuriString* args, void* context) {
|
||||
void cli_command_src(PipeSide* pipe, FuriString* args, void* context) {
|
||||
// Quality of life feature for people exploring CLI on lab.flipper.net/cli
|
||||
// By Yousef AK
|
||||
UNUSED(cli);
|
||||
UNUSED(pipe);
|
||||
UNUSED(args);
|
||||
UNUSED(context);
|
||||
|
||||
@@ -341,7 +341,8 @@ void cli_command_src(Cli* cli, FuriString* args, void* context) {
|
||||
#define CLI_COMMAND_LOG_BUFFER_SIZE 64
|
||||
|
||||
void cli_command_log_tx_callback(const uint8_t* buffer, size_t size, void* context) {
|
||||
furi_stream_buffer_send(context, buffer, size, 0);
|
||||
PipeSide* pipe = context;
|
||||
pipe_send(pipe, buffer, size);
|
||||
}
|
||||
|
||||
bool cli_command_log_level_set_from_string(FuriString* level) {
|
||||
@@ -363,16 +364,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;
|
||||
@@ -384,16 +382,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);
|
||||
@@ -402,12 +399,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);
|
||||
@@ -420,8 +415,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);
|
||||
@@ -442,8 +437,8 @@ void cli_command_sysctl_heap_track(Cli* cli, FuriString* args, void* context) {
|
||||
}
|
||||
}
|
||||
|
||||
void cli_command_sysctl_sleep_mode(Cli* cli, FuriString* args, void* context) {
|
||||
UNUSED(cli);
|
||||
void cli_command_sysctl_sleep_mode(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(pipe);
|
||||
UNUSED(context);
|
||||
if(!furi_string_cmp(args, "default")) {
|
||||
furi_hal_rtc_reset_flag(FuriHalRtcFlagLegacySleep);
|
||||
@@ -456,8 +451,8 @@ void cli_command_sysctl_sleep_mode(Cli* cli, FuriString* args, void* context) {
|
||||
}
|
||||
}
|
||||
|
||||
void cli_command_sysctl_log_level(Cli* cli, FuriString* args, void* context) {
|
||||
UNUSED(cli);
|
||||
void cli_command_sysctl_log_level(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(pipe);
|
||||
UNUSED(context);
|
||||
FuriLogLevel log_level;
|
||||
if(!furi_log_level_from_string(furi_string_get_cstr(args), &log_level)) {
|
||||
@@ -488,7 +483,7 @@ void cli_command_sysctl_print_usage(void) {
|
||||
printf("\tlog_level <error|warn|info|default|debug|trace>\t - Set system log level\r\n");
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
@@ -499,22 +494,22 @@ 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;
|
||||
}
|
||||
|
||||
if(furi_string_cmp_str(cmd, "sleep_mode") == 0) {
|
||||
cli_command_sysctl_sleep_mode(cli, args, context);
|
||||
cli_command_sysctl_sleep_mode(pipe, args, context);
|
||||
break;
|
||||
}
|
||||
|
||||
if(furi_string_cmp_str(cmd, "log_level") == 0) {
|
||||
cli_command_sysctl_log_level(cli, args, context);
|
||||
cli_command_sysctl_log_level(pipe, args, context);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -524,8 +519,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")) {
|
||||
@@ -551,8 +546,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;
|
||||
@@ -606,23 +601,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,
|
||||
@@ -630,14 +625,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",
|
||||
@@ -646,12 +643,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,
|
||||
@@ -663,6 +661,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 {
|
||||
@@ -672,8 +673,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);
|
||||
|
||||
@@ -686,16 +687,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);
|
||||
|
||||
@@ -717,8 +718,29 @@ void cli_command_i2c(Cli* cli, FuriString* args, void* context) {
|
||||
furi_hal_i2c_release(&furi_hal_i2c_handle_external);
|
||||
}
|
||||
|
||||
void cli_command_clear(Cli* cli, FuriString* args, void* context) {
|
||||
UNUSED(cli);
|
||||
/**
|
||||
* 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);
|
||||
|
||||
uint8_t buffer[256];
|
||||
|
||||
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;
|
||||
|
||||
if(memchr(buffer, CliKeyETX, read)) break;
|
||||
|
||||
size_t written = pipe_send(pipe, buffer, read);
|
||||
if(written < read) break;
|
||||
}
|
||||
}
|
||||
|
||||
void cli_command_clear(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(pipe);
|
||||
UNUSED(args);
|
||||
UNUSED(context);
|
||||
printf("\e[2J\e[H");
|
||||
@@ -726,7 +748,6 @@ void cli_command_clear(Cli* cli, FuriString* args, void* context) {
|
||||
|
||||
CLI_PLUGIN_WRAPPER("src", cli_command_src)
|
||||
CLI_PLUGIN_WRAPPER("neofetch", cli_command_neofetch)
|
||||
CLI_PLUGIN_WRAPPER("help", cli_command_help)
|
||||
CLI_PLUGIN_WRAPPER("uptime", cli_command_uptime)
|
||||
CLI_PLUGIN_WRAPPER("date", cli_command_date)
|
||||
CLI_PLUGIN_WRAPPER("sysctl", cli_command_sysctl)
|
||||
@@ -745,8 +766,8 @@ void cli_commands_init(Cli* cli) {
|
||||
cli_add_command(
|
||||
cli, "neofetch", CliCommandFlagParallelSafe, cli_command_neofetch_wrapper, NULL);
|
||||
|
||||
cli_add_command(cli, "?", CliCommandFlagParallelSafe, cli_command_help_wrapper, NULL);
|
||||
cli_add_command(cli, "help", CliCommandFlagParallelSafe, cli_command_help_wrapper, NULL);
|
||||
cli_add_command(cli, "?", CliCommandFlagParallelSafe, cli_command_help, NULL);
|
||||
cli_add_command(cli, "help", CliCommandFlagParallelSafe, cli_command_help, NULL);
|
||||
|
||||
cli_add_command(cli, "uptime", CliCommandFlagDefault, cli_command_uptime_wrapper, NULL);
|
||||
cli_add_command(cli, "date", CliCommandFlagParallelSafe, cli_command_date_wrapper, NULL);
|
||||
@@ -756,6 +777,7 @@ void cli_commands_init(Cli* cli) {
|
||||
cli_add_command(cli, "top", CliCommandFlagParallelSafe, cli_command_top, NULL);
|
||||
cli_add_command(cli, "free", CliCommandFlagParallelSafe, cli_command_free, NULL);
|
||||
cli_add_command(cli, "free_blocks", CliCommandFlagParallelSafe, cli_command_free_blocks, NULL);
|
||||
cli_add_command(cli, "echo", CliCommandFlagParallelSafe, cli_command_echo, NULL);
|
||||
|
||||
cli_add_command(cli, "vibro", CliCommandFlagDefault, cli_command_vibro_wrapper, NULL);
|
||||
cli_add_command(cli, "led", CliCommandFlagDefault, cli_command_led_wrapper, NULL);
|
||||
|
||||
@@ -1,5 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include "cli_i.h"
|
||||
#include "cli.h"
|
||||
#include <flipper_application/flipper_application.h>
|
||||
|
||||
void cli_commands_init(Cli* cli);
|
||||
|
||||
#define PLUGIN_APP_ID "cli"
|
||||
#define PLUGIN_API_VERSION 1
|
||||
|
||||
typedef struct {
|
||||
char* name;
|
||||
CliExecuteCallback execute_callback;
|
||||
CliCommandFlag flags;
|
||||
size_t stack_depth;
|
||||
} CliCommandDescriptor;
|
||||
|
||||
#define CLI_COMMAND_INTERFACE(name, execute_callback, flags, stack_depth) \
|
||||
static const CliCommandDescriptor cli_##name##_desc = { \
|
||||
#name, \
|
||||
&execute_callback, \
|
||||
flags, \
|
||||
stack_depth, \
|
||||
}; \
|
||||
\
|
||||
static const FlipperAppPluginDescriptor plugin_descriptor = { \
|
||||
.appid = PLUGIN_APP_ID, \
|
||||
.ep_api_version = PLUGIN_API_VERSION, \
|
||||
.entry_point = &cli_##name##_desc, \
|
||||
}; \
|
||||
\
|
||||
const FlipperAppPluginDescriptor* cli_##name##_ep(void) { \
|
||||
return &plugin_descriptor; \
|
||||
}
|
||||
|
||||
@@ -1,89 +1,72 @@
|
||||
/**
|
||||
* @file cli_i.h
|
||||
* Internal API for getting commands registered with the CLI
|
||||
*/
|
||||
|
||||
#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
|
||||
#include "cli.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define CLI_BUILTIN_COMMAND_STACK_SIZE (3 * 1024U)
|
||||
|
||||
typedef struct {
|
||||
CliCallback callback;
|
||||
void* context;
|
||||
uint32_t flags;
|
||||
void* context; //<! Context passed to callbacks
|
||||
CliExecuteCallback execute_callback; //<! Callback for command execution
|
||||
CliCommandFlag flags;
|
||||
size_t stack_depth;
|
||||
} CliCommand;
|
||||
|
||||
struct CliSession {
|
||||
void (*init)(void);
|
||||
void (*deinit)(void);
|
||||
size_t (*rx)(uint8_t* buffer, size_t size, uint32_t timeout);
|
||||
size_t (*rx_stdin)(uint8_t* buffer, size_t size, uint32_t timeout, void* context);
|
||||
void (*tx)(const uint8_t* buffer, size_t size);
|
||||
void (*tx_stdout)(const char* data, size_t size, void* context);
|
||||
bool (*is_connected)(void);
|
||||
};
|
||||
#define CLI_COMMANDS_TREE_RANK 4
|
||||
|
||||
// -V:BPTREE_DEF2:1103
|
||||
// -V:BPTREE_DEF2:524
|
||||
BPTREE_DEF2(
|
||||
CliCommandTree,
|
||||
CLI_COMMANDS_TREE_RANK,
|
||||
FuriString*,
|
||||
FURI_STRING_OPLIST,
|
||||
CliCommand,
|
||||
M_POD_OPLIST)
|
||||
|
||||
M_POD_OPLIST);
|
||||
#define M_OPL_CliCommandTree_t() BPTREE_OPLIST(CliCommandTree, M_POD_OPLIST)
|
||||
|
||||
struct Cli {
|
||||
CliCommandTree_t commands;
|
||||
FuriMutex* mutex;
|
||||
FuriSemaphore* idle_sem;
|
||||
FuriString* last_line;
|
||||
FuriString* line;
|
||||
const CliSession* session;
|
||||
bool cli_get_command(Cli* cli, FuriString* command, CliCommand* result);
|
||||
|
||||
size_t cursor_position;
|
||||
};
|
||||
void cli_lock_commands(Cli* cli);
|
||||
|
||||
Cli* cli_alloc(void);
|
||||
void cli_unlock_commands(Cli* cli);
|
||||
|
||||
void cli_reset(Cli* cli);
|
||||
|
||||
void cli_putc(Cli* cli, char c);
|
||||
|
||||
void cli_stdout_callback(void* _cookie, const char* data, size_t size);
|
||||
/**
|
||||
* @warning Surround calls to this function with `cli_[un]lock_commands`
|
||||
*/
|
||||
CliCommandTree_t* cli_get_commands(Cli* cli);
|
||||
|
||||
// CLI command wrapping to load from plugin file on SD card
|
||||
// Just need to:
|
||||
// - Use CLI_PLUGIN_WRAPPER("name", cmd_callback)
|
||||
// - Replace callback usages with cmd_callback_wrapper
|
||||
// - Add "name_cli" entry in app manifest to build as plugin
|
||||
void cli_plugin_wrapper(const char* name, Cli* cli, FuriString* args, void* context);
|
||||
void cli_plugin_wrapper(const char* name, PipeSide* pipe, FuriString* args, void* context);
|
||||
#include <flipper_application/flipper_application.h>
|
||||
#define CLI_PLUGIN_APP_ID "cli"
|
||||
#define CLI_PLUGIN_API_VERSION 1
|
||||
#define CLI_PLUGIN_WRAPPER(plugin_name_without_cli_suffix, cli_command_callback) \
|
||||
void cli_command_callback##_wrapper(Cli* cli, FuriString* args, void* context) { \
|
||||
cli_plugin_wrapper(plugin_name_without_cli_suffix, cli, args, context); \
|
||||
} \
|
||||
static const FlipperAppPluginDescriptor cli_command_callback##_plugin_descriptor = { \
|
||||
.appid = CLI_PLUGIN_APP_ID, \
|
||||
.ep_api_version = CLI_PLUGIN_API_VERSION, \
|
||||
.entry_point = &cli_command_callback, \
|
||||
}; \
|
||||
const FlipperAppPluginDescriptor* cli_command_callback##_plugin_ep(void) { \
|
||||
UNUSED(cli_command_callback##_wrapper); \
|
||||
return &cli_command_callback##_plugin_descriptor; \
|
||||
#define CLI_PLUGIN_WRAPPER(plugin_name_without_cli_suffix, cli_command_callback) \
|
||||
void cli_command_callback##_wrapper(PipeSide* pipe, FuriString* args, void* context) { \
|
||||
cli_plugin_wrapper(plugin_name_without_cli_suffix, pipe, args, context); \
|
||||
} \
|
||||
static const FlipperAppPluginDescriptor cli_command_callback##_plugin_descriptor = { \
|
||||
.appid = CLI_PLUGIN_APP_ID, \
|
||||
.ep_api_version = CLI_PLUGIN_API_VERSION, \
|
||||
.entry_point = &cli_command_callback, \
|
||||
}; \
|
||||
const FlipperAppPluginDescriptor* cli_command_callback##_plugin_ep(void) { \
|
||||
UNUSED(cli_command_callback##_wrapper); \
|
||||
return &cli_command_callback##_plugin_descriptor; \
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@@ -1,324 +1,298 @@
|
||||
#include "cli_i.h" // IWYU pragma: keep
|
||||
#include "cli_vcp.h"
|
||||
#include "shell/cli_shell.h"
|
||||
#include <furi_hal_usb_cdc.h>
|
||||
#include <furi_hal.h>
|
||||
#include <furi.h>
|
||||
#include <stdint.h>
|
||||
#include <toolbox/pipe.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;
|
||||
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,
|
||||
NULL,
|
||||
FuriThread* 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) {
|
||||
furi_thread_join(cli_vcp->shell);
|
||||
furi_thread_free(cli_vcp->shell);
|
||||
}
|
||||
|
||||
// start shell thread
|
||||
PipeSideBundle bundle = pipe_alloc(VCP_BUF_SIZE, 1);
|
||||
cli_vcp->own_pipe = bundle.alices_side;
|
||||
pipe_attach_to_event_loop(cli_vcp->own_pipe, cli_vcp->event_loop);
|
||||
pipe_set_callback_context(cli_vcp->own_pipe, cli_vcp);
|
||||
pipe_set_data_arrived_callback(
|
||||
cli_vcp->own_pipe, cli_vcp_data_from_shell, FuriEventLoopEventFlagEdge);
|
||||
pipe_set_space_freed_callback(
|
||||
cli_vcp->own_pipe, cli_vcp_shell_ready, FuriEventLoopEventFlagEdge);
|
||||
furi_delay_ms(33); // we are too fast, minicom isn't ready yet
|
||||
cli_vcp->shell = cli_shell_start(bundle.bobs_side);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
return cli_vcp;
|
||||
}
|
||||
|
||||
int32_t cli_vcp_srv(void* p) {
|
||||
UNUSED(p);
|
||||
|
||||
if(!furi_hal_is_normal_boot()) {
|
||||
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,
|
||||
};
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
251
applications/services/cli/shell/cli_shell.c
Normal file
251
applications/services/cli/shell/cli_shell.c
Normal file
@@ -0,0 +1,251 @@
|
||||
#include "cli_shell.h"
|
||||
#include "cli_shell_i.h"
|
||||
#include "../cli_ansi.h"
|
||||
#include "../cli_i.h"
|
||||
#include "../cli_commands.h"
|
||||
#include "cli_shell_line.h"
|
||||
#include <stdio.h>
|
||||
#include <furi_hal_version.h>
|
||||
#include <m-array.h>
|
||||
#include <loader/loader.h>
|
||||
#include <toolbox/pipe.h>
|
||||
#include <flipper_application/plugins/plugin_manager.h>
|
||||
#include <loader/firmware_api/firmware_api.h>
|
||||
|
||||
#define TAG "CliShell"
|
||||
|
||||
#define ANSI_TIMEOUT_MS 10
|
||||
|
||||
typedef enum {
|
||||
CliShellComponentLine,
|
||||
CliShellComponentMAX, //<! do not use
|
||||
} CliShellComponent;
|
||||
|
||||
CliShellKeyComboSet* component_key_combo_sets[] = {
|
||||
[CliShellComponentLine] = &cli_shell_line_key_combo_set,
|
||||
};
|
||||
static_assert(CliShellComponentMAX == COUNT_OF(component_key_combo_sets));
|
||||
|
||||
struct CliShell {
|
||||
Cli* cli;
|
||||
FuriEventLoop* event_loop;
|
||||
PipeSide* pipe;
|
||||
|
||||
CliAnsiParser* ansi_parser;
|
||||
FuriEventLoopTimer* ansi_parsing_timer;
|
||||
|
||||
void* components[CliShellComponentMAX];
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
CliCommand* command;
|
||||
PipeSide* pipe;
|
||||
FuriString* args;
|
||||
} CliCommandThreadData;
|
||||
|
||||
// =========
|
||||
// Execution
|
||||
// =========
|
||||
|
||||
void cli_shell_execute_command(CliShell* cli_shell, FuriString* command) {
|
||||
// split command into command and args
|
||||
size_t space = furi_string_search_char(command, ' ');
|
||||
if(space == FURI_STRING_FAILURE) space = furi_string_size(command);
|
||||
FuriString* command_name = furi_string_alloc_set(command);
|
||||
furi_string_left(command_name, space);
|
||||
FuriString* args = furi_string_alloc_set(command);
|
||||
furi_string_right(args, space + 1);
|
||||
|
||||
Loader* loader = NULL;
|
||||
CliCommand command_data;
|
||||
|
||||
do {
|
||||
// find handler
|
||||
if(!cli_get_command(cli_shell->cli, command_name, &command_data)) {
|
||||
printf(
|
||||
ANSI_FG_RED "could not find command `%s`, try `help`" ANSI_RESET,
|
||||
furi_string_get_cstr(command_name));
|
||||
break;
|
||||
}
|
||||
|
||||
// lock loader
|
||||
if(!(command_data.flags & CliCommandFlagParallelSafe)) {
|
||||
loader = furi_record_open(RECORD_LOADER);
|
||||
bool success = loader_lock(loader);
|
||||
if(!success) {
|
||||
printf(ANSI_FG_RED
|
||||
"this command cannot be run while an application is open" ANSI_RESET);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
command_data.execute_callback(cli_shell->pipe, args, command_data.context);
|
||||
} while(0);
|
||||
|
||||
furi_string_free(command_name);
|
||||
furi_string_free(args);
|
||||
|
||||
// unlock loader
|
||||
if(loader) loader_unlock(loader);
|
||||
furi_record_close(RECORD_LOADER);
|
||||
}
|
||||
|
||||
// ==============
|
||||
// Event handlers
|
||||
// ==============
|
||||
|
||||
static void cli_shell_process_key(CliShell* cli_shell, CliKeyCombo key_combo) {
|
||||
for(size_t i = 0; i < CliShellComponentMAX; i++) { // -V1008
|
||||
CliShellKeyComboSet* set = component_key_combo_sets[i];
|
||||
void* component_context = cli_shell->components[i];
|
||||
|
||||
for(size_t j = 0; j < set->count; j++) {
|
||||
if(set->records[j].combo.modifiers == key_combo.modifiers &&
|
||||
set->records[j].combo.key == key_combo.key)
|
||||
if(set->records[j].action(key_combo, component_context)) return;
|
||||
}
|
||||
|
||||
if(set->fallback)
|
||||
if(set->fallback(key_combo, component_context)) return;
|
||||
}
|
||||
}
|
||||
|
||||
static void cli_shell_pipe_broken(PipeSide* pipe, void* context) {
|
||||
// allow commands to be processed before we stop the shell
|
||||
if(pipe_bytes_available(pipe)) return;
|
||||
|
||||
CliShell* cli_shell = context;
|
||||
furi_event_loop_stop(cli_shell->event_loop);
|
||||
}
|
||||
|
||||
static void cli_shell_data_available(PipeSide* pipe, void* context) {
|
||||
UNUSED(pipe);
|
||||
CliShell* cli_shell = context;
|
||||
|
||||
furi_event_loop_timer_start(cli_shell->ansi_parsing_timer, furi_ms_to_ticks(ANSI_TIMEOUT_MS));
|
||||
|
||||
// process ANSI escape sequences
|
||||
int c = getchar();
|
||||
furi_assert(c >= 0);
|
||||
CliAnsiParserResult parse_result = cli_ansi_parser_feed(cli_shell->ansi_parser, c);
|
||||
if(!parse_result.is_done) return;
|
||||
CliKeyCombo key_combo = parse_result.result;
|
||||
if(key_combo.key == CliKeyUnrecognized) return;
|
||||
|
||||
cli_shell_process_key(cli_shell, key_combo);
|
||||
}
|
||||
|
||||
static void cli_shell_timer_expired(void* context) {
|
||||
CliShell* cli_shell = context;
|
||||
CliAnsiParserResult parse_result = cli_ansi_parser_feed_timeout(cli_shell->ansi_parser);
|
||||
if(!parse_result.is_done) return;
|
||||
CliKeyCombo key_combo = parse_result.result;
|
||||
if(key_combo.key == CliKeyUnrecognized) return;
|
||||
|
||||
cli_shell_process_key(cli_shell, key_combo);
|
||||
}
|
||||
|
||||
// =======
|
||||
// Helpers
|
||||
// =======
|
||||
|
||||
static CliShell* cli_shell_alloc(PipeSide* pipe) {
|
||||
CliShell* cli_shell = malloc(sizeof(CliShell));
|
||||
|
||||
cli_shell->cli = furi_record_open(RECORD_CLI);
|
||||
cli_shell->ansi_parser = cli_ansi_parser_alloc();
|
||||
cli_shell->pipe = pipe;
|
||||
pipe_install_as_stdio(cli_shell->pipe);
|
||||
|
||||
cli_shell->components[CliShellComponentLine] = cli_shell_line_alloc(cli_shell);
|
||||
|
||||
cli_shell->event_loop = furi_event_loop_alloc();
|
||||
cli_shell->ansi_parsing_timer = furi_event_loop_timer_alloc(
|
||||
cli_shell->event_loop, cli_shell_timer_expired, FuriEventLoopTimerTypeOnce, cli_shell);
|
||||
pipe_attach_to_event_loop(cli_shell->pipe, cli_shell->event_loop);
|
||||
|
||||
pipe_set_callback_context(cli_shell->pipe, cli_shell);
|
||||
pipe_set_data_arrived_callback(cli_shell->pipe, cli_shell_data_available, 0);
|
||||
pipe_set_broken_callback(cli_shell->pipe, cli_shell_pipe_broken, 0);
|
||||
|
||||
return cli_shell;
|
||||
}
|
||||
|
||||
static void cli_shell_free(CliShell* cli_shell) {
|
||||
cli_shell_line_free(cli_shell->components[CliShellComponentLine]);
|
||||
|
||||
pipe_detach_from_event_loop(cli_shell->pipe);
|
||||
furi_event_loop_timer_free(cli_shell->ansi_parsing_timer);
|
||||
furi_event_loop_free(cli_shell->event_loop);
|
||||
pipe_free(cli_shell->pipe);
|
||||
cli_ansi_parser_free(cli_shell->ansi_parser);
|
||||
furi_record_close(RECORD_CLI);
|
||||
free(cli_shell);
|
||||
}
|
||||
|
||||
static void cli_shell_motd(void) {
|
||||
printf(ANSI_FLIPPER_BRAND_ORANGE
|
||||
"\r\n"
|
||||
" _.-------.._ -,\r\n"
|
||||
" .-\"```\"--..,,_/ /`-, -, \\ \r\n"
|
||||
" .:\" /:/ /'\\ \\ ,_..., `. | |\r\n"
|
||||
" / ,----/:/ /`\\ _\\~`_-\"` _;\r\n"
|
||||
" ' / /`\"\"\"'\\ \\ \\.~`_-' ,-\"'/ \r\n"
|
||||
" | | | 0 | | .-' ,/` /\r\n"
|
||||
" | ,..\\ \\ ,.-\"` ,/` /\r\n"
|
||||
" ; : `/`\"\"\\` ,/--==,/-----,\r\n"
|
||||
" | `-...| -.___-Z:_______J...---;\r\n"
|
||||
" : ` _-'\r\n"
|
||||
" _L_ _ ___ ___ ___ ___ ____--\"`___ _ ___\r\n"
|
||||
"| __|| | |_ _|| _ \\| _ \\| __|| _ \\ / __|| | |_ _|\r\n"
|
||||
"| _| | |__ | | | _/| _/| _| | / | (__ | |__ | |\r\n"
|
||||
"|_| |____||___||_| |_| |___||_|_\\ \\___||____||___|\r\n"
|
||||
"\r\n" ANSI_FG_BR_WHITE "Welcome to Flipper Zero Command Line Interface!\r\n"
|
||||
"Read the manual: https://docs.flipper.net/development/cli\r\n"
|
||||
"Run `help` or `?` to list available commands\r\n"
|
||||
"\r\n" ANSI_RESET);
|
||||
|
||||
const Version* firmware_version = furi_hal_version_get_firmware_version();
|
||||
if(firmware_version) {
|
||||
printf(
|
||||
"Firmware version: %s %s (%s%s built on %s)\r\n",
|
||||
version_get_gitbranch(firmware_version),
|
||||
version_get_version(firmware_version),
|
||||
version_get_githash(firmware_version),
|
||||
version_get_dirty_flag(firmware_version) ? "-dirty" : "",
|
||||
version_get_builddate(firmware_version));
|
||||
}
|
||||
}
|
||||
|
||||
static int32_t cli_shell_thread(void* context) {
|
||||
PipeSide* pipe = context;
|
||||
|
||||
// Sometimes, the other side closes the pipe even before our thread is started. Although the
|
||||
// rest of the code will eventually find this out if this check is removed, there's no point in
|
||||
// wasting time.
|
||||
if(pipe_state(pipe) == PipeStateBroken) return 0;
|
||||
|
||||
CliShell* cli_shell = cli_shell_alloc(pipe);
|
||||
|
||||
FURI_LOG_D(TAG, "Started");
|
||||
cli_shell_motd();
|
||||
cli_shell_line_prompt(cli_shell->components[CliShellComponentLine]);
|
||||
|
||||
furi_event_loop_run(cli_shell->event_loop);
|
||||
|
||||
FURI_LOG_D(TAG, "Stopped");
|
||||
|
||||
cli_shell_free(cli_shell);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ==========
|
||||
// Public API
|
||||
// ==========
|
||||
|
||||
FuriThread* cli_shell_start(PipeSide* pipe) {
|
||||
FuriThread* thread =
|
||||
furi_thread_alloc_ex("CliShell", CLI_SHELL_STACK_SIZE, cli_shell_thread, pipe);
|
||||
furi_thread_start(thread);
|
||||
return thread;
|
||||
}
|
||||
16
applications/services/cli/shell/cli_shell.h
Normal file
16
applications/services/cli/shell/cli_shell.h
Normal file
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include <furi.h>
|
||||
#include <toolbox/pipe.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define CLI_SHELL_STACK_SIZE (4 * 1024U)
|
||||
|
||||
FuriThread* cli_shell_start(PipeSide* pipe);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
32
applications/services/cli/shell/cli_shell_i.h
Normal file
32
applications/services/cli/shell/cli_shell_i.h
Normal file
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include "../cli_ansi.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct CliShell CliShell;
|
||||
|
||||
/**
|
||||
* @brief Key combo handler
|
||||
* @return true if the event was handled, false otherwise
|
||||
*/
|
||||
typedef bool (*CliShellKeyComboAction)(CliKeyCombo combo, void* context);
|
||||
|
||||
typedef struct {
|
||||
CliKeyCombo combo;
|
||||
CliShellKeyComboAction action;
|
||||
} CliShellKeyComboRecord;
|
||||
|
||||
typedef struct {
|
||||
CliShellKeyComboAction fallback;
|
||||
size_t count;
|
||||
CliShellKeyComboRecord records[];
|
||||
} CliShellKeyComboSet;
|
||||
|
||||
void cli_shell_execute_command(CliShell* cli_shell, FuriString* command);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
238
applications/services/cli/shell/cli_shell_line.c
Normal file
238
applications/services/cli/shell/cli_shell_line.c
Normal file
@@ -0,0 +1,238 @@
|
||||
#include "cli_shell_line.h"
|
||||
|
||||
#define HISTORY_DEPTH 10
|
||||
|
||||
struct CliShellLine {
|
||||
size_t history_position;
|
||||
size_t line_position;
|
||||
FuriString* history[HISTORY_DEPTH];
|
||||
size_t history_entries;
|
||||
CliShell* shell;
|
||||
};
|
||||
|
||||
// ==========
|
||||
// Public API
|
||||
// ==========
|
||||
|
||||
CliShellLine* cli_shell_line_alloc(CliShell* shell) {
|
||||
CliShellLine* line = malloc(sizeof(CliShellLine));
|
||||
line->shell = shell;
|
||||
|
||||
line->history[0] = furi_string_alloc();
|
||||
line->history_entries = 1;
|
||||
|
||||
return line;
|
||||
}
|
||||
|
||||
void cli_shell_line_free(CliShellLine* line) {
|
||||
for(size_t i = 0; i < line->history_entries; i++)
|
||||
furi_string_free(line->history[i]);
|
||||
|
||||
free(line);
|
||||
}
|
||||
|
||||
FuriString* cli_shell_line_get_selected(CliShellLine* line) {
|
||||
return line->history[line->history_position];
|
||||
}
|
||||
|
||||
FuriString* cli_shell_line_get_editing(CliShellLine* line) {
|
||||
return line->history[0];
|
||||
}
|
||||
|
||||
size_t cli_shell_line_prompt_length(CliShellLine* line) {
|
||||
UNUSED(line);
|
||||
return strlen(">: ");
|
||||
}
|
||||
|
||||
void cli_shell_line_format_prompt(CliShellLine* line, char* buf, size_t length) {
|
||||
UNUSED(line);
|
||||
snprintf(buf, length - 1, ">: ");
|
||||
}
|
||||
|
||||
void cli_shell_line_prompt(CliShellLine* line) {
|
||||
char buffer[32];
|
||||
cli_shell_line_format_prompt(line, buffer, sizeof(buffer));
|
||||
printf("\r\n%s", buffer);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
void cli_shell_line_ensure_not_overwriting_history(CliShellLine* line) {
|
||||
if(line->history_position > 0) {
|
||||
FuriString* source = cli_shell_line_get_selected(line);
|
||||
FuriString* destination = cli_shell_line_get_editing(line);
|
||||
furi_string_set(destination, source);
|
||||
line->history_position = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// ==============
|
||||
// Input handlers
|
||||
// ==============
|
||||
|
||||
static bool cli_shell_line_input_ctrl_c(CliKeyCombo combo, void* context) {
|
||||
UNUSED(combo);
|
||||
CliShellLine* line = context;
|
||||
// reset input
|
||||
furi_string_reset(cli_shell_line_get_editing(line));
|
||||
line->line_position = 0;
|
||||
line->history_position = 0;
|
||||
printf("^C");
|
||||
cli_shell_line_prompt(line);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool cli_shell_line_input_cr(CliKeyCombo combo, void* context) {
|
||||
UNUSED(combo);
|
||||
CliShellLine* line = context;
|
||||
|
||||
FuriString* command = cli_shell_line_get_selected(line);
|
||||
furi_string_trim(command);
|
||||
FuriString* command_copy = furi_string_alloc_set(command);
|
||||
|
||||
if(line->history_position > 0) {
|
||||
// move selected command to the front
|
||||
memmove(
|
||||
&line->history[1], &line->history[0], line->history_position * sizeof(FuriString*));
|
||||
line->history[0] = command;
|
||||
}
|
||||
|
||||
// insert empty command
|
||||
if(line->history_entries == HISTORY_DEPTH) {
|
||||
furi_string_free(line->history[HISTORY_DEPTH - 1]);
|
||||
line->history_entries--;
|
||||
}
|
||||
memmove(&line->history[1], &line->history[0], line->history_entries * sizeof(FuriString*));
|
||||
line->history[0] = furi_string_alloc();
|
||||
line->history_entries++;
|
||||
line->line_position = 0;
|
||||
line->history_position = 0;
|
||||
|
||||
// execute command
|
||||
printf("\r\n");
|
||||
if(!furi_string_empty(command_copy)) cli_shell_execute_command(line->shell, command_copy);
|
||||
furi_string_free(command_copy);
|
||||
|
||||
cli_shell_line_prompt(line);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool cli_shell_line_input_up_down(CliKeyCombo combo, void* context) {
|
||||
CliShellLine* line = context;
|
||||
// go up and down in history
|
||||
int increment = (combo.key == CliKeyUp) ? 1 : -1;
|
||||
size_t new_pos =
|
||||
CLAMP((int)line->history_position + increment, (int)line->history_entries - 1, 0);
|
||||
|
||||
// print prompt with selected command
|
||||
if(new_pos != line->history_position) {
|
||||
line->history_position = new_pos;
|
||||
FuriString* command = cli_shell_line_get_selected(line);
|
||||
printf(
|
||||
ANSI_CURSOR_HOR_POS("1") ">: %s" ANSI_ERASE_LINE(ANSI_ERASE_FROM_CURSOR_TO_END),
|
||||
furi_string_get_cstr(command));
|
||||
fflush(stdout);
|
||||
line->line_position = furi_string_size(command);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool cli_shell_line_input_left_right(CliKeyCombo combo, void* context) {
|
||||
CliShellLine* line = context;
|
||||
// go left and right in the current line
|
||||
FuriString* command = cli_shell_line_get_selected(line);
|
||||
int increment = (combo.key == CliKeyRight) ? 1 : -1;
|
||||
size_t new_pos =
|
||||
CLAMP((int)line->line_position + increment, (int)furi_string_size(command), 0);
|
||||
|
||||
// move cursor
|
||||
if(new_pos != line->line_position) {
|
||||
line->line_position = new_pos;
|
||||
printf("%s", (increment == 1) ? ANSI_CURSOR_RIGHT_BY("1") : ANSI_CURSOR_LEFT_BY("1"));
|
||||
fflush(stdout);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool cli_shell_line_input_home(CliKeyCombo combo, void* context) {
|
||||
UNUSED(combo);
|
||||
CliShellLine* line = context;
|
||||
// go to the start
|
||||
line->line_position = 0;
|
||||
printf(ANSI_CURSOR_HOR_POS("%zu"), cli_shell_line_prompt_length(line) + 1);
|
||||
fflush(stdout);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool cli_shell_line_input_end(CliKeyCombo combo, void* context) {
|
||||
UNUSED(combo);
|
||||
CliShellLine* line = context;
|
||||
// go to the end
|
||||
line->line_position = furi_string_size(cli_shell_line_get_selected(line));
|
||||
printf(
|
||||
ANSI_CURSOR_HOR_POS("%zu"), cli_shell_line_prompt_length(line) + line->line_position + 1);
|
||||
fflush(stdout);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool cli_shell_line_input_bksp(CliKeyCombo combo, void* context) {
|
||||
UNUSED(combo);
|
||||
CliShellLine* line = context;
|
||||
// erase one character
|
||||
cli_shell_line_ensure_not_overwriting_history(line);
|
||||
FuriString* editing_line = cli_shell_line_get_editing(line);
|
||||
if(line->line_position == 0) {
|
||||
putc(CliKeyBell, stdout);
|
||||
fflush(stdout);
|
||||
return true;
|
||||
}
|
||||
line->line_position--;
|
||||
furi_string_replace_at(editing_line, line->line_position, 1, "");
|
||||
|
||||
// move cursor, print the rest of the line, restore cursor
|
||||
printf(
|
||||
ANSI_CURSOR_LEFT_BY("1") "%s" ANSI_ERASE_LINE(ANSI_ERASE_FROM_CURSOR_TO_END),
|
||||
furi_string_get_cstr(editing_line) + line->line_position);
|
||||
size_t left_by = furi_string_size(editing_line) - line->line_position;
|
||||
if(left_by) // apparently LEFT_BY("0") still shifts left by one ._ .
|
||||
printf(ANSI_CURSOR_LEFT_BY("%zu"), left_by);
|
||||
fflush(stdout);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool cli_shell_line_input_fallback(CliKeyCombo combo, void* context) {
|
||||
CliShellLine* line = context;
|
||||
if(combo.modifiers != CliModKeyNo) return false;
|
||||
if(combo.key < CliKeySpace || combo.key >= CliKeyDEL) return false;
|
||||
// insert character
|
||||
cli_shell_line_ensure_not_overwriting_history(line);
|
||||
FuriString* editing_line = cli_shell_line_get_editing(line);
|
||||
if(line->line_position == furi_string_size(editing_line)) {
|
||||
furi_string_push_back(editing_line, combo.key);
|
||||
printf("%c", combo.key);
|
||||
} else {
|
||||
const char in_str[2] = {combo.key, 0};
|
||||
furi_string_replace_at(editing_line, line->line_position, 0, in_str);
|
||||
printf(ANSI_INSERT_MODE_ENABLE "%c" ANSI_INSERT_MODE_DISABLE, combo.key);
|
||||
}
|
||||
fflush(stdout);
|
||||
line->line_position++;
|
||||
return true;
|
||||
}
|
||||
|
||||
CliShellKeyComboSet cli_shell_line_key_combo_set = {
|
||||
.fallback = cli_shell_line_input_fallback,
|
||||
.count = 10,
|
||||
.records =
|
||||
{
|
||||
{{CliModKeyNo, CliKeyETX}, cli_shell_line_input_ctrl_c},
|
||||
{{CliModKeyNo, CliKeyCR}, cli_shell_line_input_cr},
|
||||
{{CliModKeyNo, CliKeyUp}, cli_shell_line_input_up_down},
|
||||
{{CliModKeyNo, CliKeyDown}, cli_shell_line_input_up_down},
|
||||
{{CliModKeyNo, CliKeyLeft}, cli_shell_line_input_left_right},
|
||||
{{CliModKeyNo, CliKeyRight}, cli_shell_line_input_left_right},
|
||||
{{CliModKeyNo, CliKeyHome}, cli_shell_line_input_home},
|
||||
{{CliModKeyNo, CliKeyEnd}, cli_shell_line_input_end},
|
||||
{{CliModKeyNo, CliKeyBackspace}, cli_shell_line_input_bksp},
|
||||
{{CliModKeyNo, CliKeyDEL}, cli_shell_line_input_bksp},
|
||||
},
|
||||
};
|
||||
36
applications/services/cli/shell/cli_shell_line.h
Normal file
36
applications/services/cli/shell/cli_shell_line.h
Normal file
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include <furi.h>
|
||||
|
||||
#include "cli_shell_i.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct CliShellLine CliShellLine;
|
||||
|
||||
CliShellLine* cli_shell_line_alloc(CliShell* shell);
|
||||
|
||||
void cli_shell_line_free(CliShellLine* line);
|
||||
|
||||
FuriString* cli_shell_line_get_selected(CliShellLine* line);
|
||||
|
||||
FuriString* cli_shell_line_get_editing(CliShellLine* line);
|
||||
|
||||
size_t cli_shell_line_prompt_length(CliShellLine* line);
|
||||
|
||||
void cli_shell_line_format_prompt(CliShellLine* line, char* buf, size_t length);
|
||||
|
||||
void cli_shell_line_prompt(CliShellLine* line);
|
||||
|
||||
/**
|
||||
* @brief If a line from history has been selected, moves it into the active line
|
||||
*/
|
||||
void cli_shell_line_ensure_not_overwriting_history(CliShellLine* line);
|
||||
|
||||
extern CliShellKeyComboSet cli_shell_line_key_combo_set;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -2,6 +2,7 @@
|
||||
#include <furi.h>
|
||||
|
||||
#include <lib/toolbox/args.h>
|
||||
#include <toolbox/pipe.h>
|
||||
#include <cli/cli.h>
|
||||
|
||||
void crypto_cli_print_usage(void) {
|
||||
@@ -17,7 +18,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 +45,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 +93,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 +120,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 +165,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 +187,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 +280,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 +292,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;
|
||||
}
|
||||
|
||||
|
||||
@@ -402,9 +402,9 @@ void desktop_lock(Desktop* desktop, bool with_pin) {
|
||||
|
||||
if(with_pin) {
|
||||
if(!momentum_settings.allow_locked_rpc_usb) {
|
||||
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);
|
||||
}
|
||||
if(!momentum_settings.allow_locked_rpc_ble) {
|
||||
Bt* bt = furi_record_open(RECORD_BT);
|
||||
@@ -440,9 +440,9 @@ void desktop_unlock(Desktop* desktop) {
|
||||
|
||||
if(with_pin) {
|
||||
if(!momentum_settings.allow_locked_rpc_usb) {
|
||||
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);
|
||||
}
|
||||
if(!momentum_settings.allow_locked_rpc_ble) {
|
||||
Bt* bt = furi_record_open(RECORD_BT);
|
||||
@@ -559,6 +559,10 @@ int32_t desktop_srv(void* p) {
|
||||
if(desktop_pin_code_is_set() &&
|
||||
(momentum_settings.lock_on_boot || furi_hal_rtc_is_flag_set(FuriHalRtcFlagLock))) {
|
||||
desktop_lock(desktop, true);
|
||||
} 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)) {
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <furi.h>
|
||||
#include <cli/cli.h>
|
||||
#include <furi_hal_gpio.h>
|
||||
#include <toolbox/pipe.h>
|
||||
#include <furi_hal_vibro.h>
|
||||
|
||||
#define INPUT_DEBOUNCE_TICKS_HALF (INPUT_DEBOUNCE_TICKS / 2)
|
||||
@@ -30,7 +31,7 @@ typedef struct {
|
||||
} InputPinState;
|
||||
|
||||
/** Input CLI command handler */
|
||||
void input_cli_wrapper(Cli* cli, FuriString* args, void* context);
|
||||
void input_cli_wrapper(PipeSide* pipe, FuriString* args, void* context);
|
||||
|
||||
// #define INPUT_DEBUG
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <furi.h>
|
||||
#include <cli/cli.h>
|
||||
#include <toolbox/args.h>
|
||||
#include <toolbox/pipe.h>
|
||||
|
||||
static void input_cli_usage(void) {
|
||||
printf("Usage:\r\n");
|
||||
@@ -20,7 +21,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 =
|
||||
@@ -28,7 +29,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",
|
||||
@@ -58,7 +59,7 @@ static void fake_input(FuriPubSub* event_pubsub, InputKey key, InputType type) {
|
||||
}
|
||||
}
|
||||
|
||||
static void input_cli_keyboard(Cli* cli, FuriString* args, FuriPubSub* event_pubsub) {
|
||||
static void input_cli_keyboard(PipeSide* pipe, FuriString* args, FuriPubSub* event_pubsub) {
|
||||
UNUSED(args);
|
||||
printf("Using console keyboard feedback for flipper input\r\n");
|
||||
|
||||
@@ -71,16 +72,16 @@ static void input_cli_keyboard(Cli* cli, FuriString* args, FuriPubSub* event_pub
|
||||
printf("\r\nPress CTRL+C to stop\r\n");
|
||||
bool hold = false;
|
||||
FuriPubSub* ascii_pubsub = furi_record_open(RECORD_ASCII_EVENTS);
|
||||
while(cli_is_connected(cli)) {
|
||||
char in_chr = cli_getc(cli);
|
||||
if(in_chr == CliSymbolAsciiETX) break;
|
||||
while(pipe_state(pipe) == PipeStateOpen) {
|
||||
char in_chr = getchar();
|
||||
if(in_chr == CliKeyETX) break;
|
||||
InputKey send_key = InputKeyMAX;
|
||||
uint8_t send_ascii = AsciiValueNUL;
|
||||
|
||||
switch(in_chr) {
|
||||
case CliSymbolAsciiEsc: // Escape code for arrows
|
||||
if(!cli_read(cli, (uint8_t*)&in_chr, 1) || in_chr != '[') break;
|
||||
if(!cli_read(cli, (uint8_t*)&in_chr, 1)) break;
|
||||
case CliKeyEsc: // Escape code for arrows
|
||||
if(!pipe_receive(pipe, &in_chr, 1) || in_chr != '[') break;
|
||||
if(!pipe_receive(pipe, &in_chr, 1)) break;
|
||||
if(in_chr >= 'A' && in_chr <= 'D') { // Arrows = Dpad
|
||||
if(hold) {
|
||||
send_key = InputKeyUp + (in_chr - 'A'); // Same order as InputKey
|
||||
@@ -89,8 +90,8 @@ static void input_cli_keyboard(Cli* cli, FuriString* args, FuriPubSub* event_pub
|
||||
}
|
||||
}
|
||||
break;
|
||||
case CliSymbolAsciiBackspace: // (minicom) Backspace = Back
|
||||
case CliSymbolAsciiDel: // (putty/picocom) Backspace = Back
|
||||
case CliKeyBackspace: // (minicom) Backspace = Back
|
||||
case CliKeyDEL: // (putty/picocom) Backspace = Back
|
||||
if(hold) {
|
||||
send_key = InputKeyBack;
|
||||
} else {
|
||||
@@ -104,14 +105,14 @@ static void input_cli_keyboard(Cli* cli, FuriString* args, FuriPubSub* event_pub
|
||||
send_ascii = AsciiValueESC;
|
||||
}
|
||||
break;
|
||||
case CliSymbolAsciiCR: // Enter = Ok
|
||||
case CliKeyCR: // Enter = Ok
|
||||
if(hold) {
|
||||
send_key = InputKeyOk;
|
||||
} else {
|
||||
send_ascii = AsciiValueCR;
|
||||
}
|
||||
break;
|
||||
case CliSymbolAsciiSpace: // Space = Toggle hold next key
|
||||
case CliKeySpace: // Space = Toggle hold next key
|
||||
if(hold) {
|
||||
send_ascii = ' ';
|
||||
} else {
|
||||
@@ -143,8 +144,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);
|
||||
InputKey key;
|
||||
InputType type;
|
||||
FuriString* key_str;
|
||||
@@ -194,8 +195,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;
|
||||
@@ -207,15 +207,15 @@ 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, "keyboard") == 0) {
|
||||
input_cli_keyboard(cli, args, event_pubsub);
|
||||
input_cli_keyboard(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;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#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");
|
||||
@@ -113,8 +114,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);
|
||||
|
||||
|
||||
@@ -4,9 +4,10 @@
|
||||
#include <cli/cli.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 +15,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 +43,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 +68,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 +80,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,24 +2,24 @@
|
||||
#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 +36,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 +53,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 +64,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;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <pb_encode.h>
|
||||
#include <flipper.pb.h>
|
||||
#include <cli/cli.h>
|
||||
#include <toolbox/pipe.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
@@ -46,7 +47,7 @@ void rpc_desktop_free(void* ctx);
|
||||
void rpc_debug_print_message(const PB_Main* message);
|
||||
void rpc_debug_print_data(const char* prefix, uint8_t* buffer, size_t size);
|
||||
|
||||
void rpc_cli_command_start_session(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);
|
||||
|
||||
|
||||
@@ -10,6 +10,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 254
|
||||
|
||||
@@ -19,8 +20,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 +70,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 +98,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 +136,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 +178,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 +210,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 +225,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 +266,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 +284,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 +306,32 @@ 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 read_this_time = pipe_receive(pipe, buffer, MIN(buffer_size, need_to_read));
|
||||
size_t wrote_this_time = storage_file_write(file, buffer, read_this_time);
|
||||
|
||||
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) {
|
||||
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 +342,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 +384,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 +401,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 +422,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 +435,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 +456,8 @@ static void storage_cli_rename(Cli* cli, FuriString* old_path, FuriString* args)
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
}
|
||||
|
||||
static void storage_cli_migrate(Cli* cli, FuriString* old_path, FuriString* args) {
|
||||
UNUSED(cli);
|
||||
static void storage_cli_migrate(PipeSide* pipe, FuriString* old_path, FuriString* args) {
|
||||
UNUSED(pipe);
|
||||
Storage* api = furi_record_open(RECORD_STORAGE);
|
||||
FuriString* new_path;
|
||||
new_path = furi_string_alloc();
|
||||
@@ -472,8 +477,8 @@ static void storage_cli_migrate(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));
|
||||
@@ -485,8 +490,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);
|
||||
@@ -512,8 +517,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)) {
|
||||
@@ -547,7 +552,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;
|
||||
@@ -657,7 +662,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;
|
||||
@@ -679,7 +684,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;
|
||||
}
|
||||
}
|
||||
@@ -693,11 +698,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");
|
||||
|
||||
@@ -714,7 +720,7 @@ 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_ex(cli, "storage", CliCommandFlagParallelSafe, storage_cli, NULL, 512);
|
||||
cli_add_command(
|
||||
cli, "factory_reset", CliCommandFlagParallelSafe, storage_cli_factory_reset, NULL);
|
||||
furi_record_close(RECORD_CLI);
|
||||
|
||||
@@ -15,6 +15,7 @@ App(
|
||||
"js_app.c",
|
||||
"js_modules.c",
|
||||
"js_thread.c",
|
||||
"js_value.c",
|
||||
"plugin_api/app_api_table.cpp",
|
||||
"views/console_view.c",
|
||||
"modules/js_flipper.c",
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <toolbox/path.h>
|
||||
#include <assets_icons.h>
|
||||
#include <cli/cli.h>
|
||||
#include <toolbox/pipe.h>
|
||||
|
||||
#define TAG "JS app"
|
||||
|
||||
@@ -131,12 +132,14 @@ int32_t js_app(void* arg) {
|
||||
} //-V773
|
||||
|
||||
typedef struct {
|
||||
Cli* cli;
|
||||
PipeSide* pipe;
|
||||
FuriSemaphore* exit_sem;
|
||||
} JsCliContext;
|
||||
|
||||
static void js_cli_print(JsCliContext* ctx, const char* msg) {
|
||||
cli_write(ctx->cli, (uint8_t*)msg, strlen(msg));
|
||||
UNUSED(ctx);
|
||||
UNUSED(msg);
|
||||
pipe_send(ctx->pipe, msg, strlen(msg));
|
||||
}
|
||||
|
||||
static void js_cli_exit(JsCliContext* ctx) {
|
||||
@@ -170,7 +173,7 @@ static void js_cli_callback(JsThreadEvent event, const char* msg, void* context)
|
||||
}
|
||||
}
|
||||
|
||||
void js_cli_execute(Cli* cli, FuriString* args, void* context) {
|
||||
void js_cli_execute(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(context);
|
||||
|
||||
const char* path = furi_string_get_cstr(args);
|
||||
@@ -187,14 +190,14 @@ void js_cli_execute(Cli* cli, FuriString* args, void* context) {
|
||||
break;
|
||||
}
|
||||
|
||||
JsCliContext ctx = {.cli = cli};
|
||||
JsCliContext ctx = {.pipe = pipe};
|
||||
ctx.exit_sem = furi_semaphore_alloc(1, 0);
|
||||
|
||||
printf("Running script %s, press CTRL+C to stop\r\n", path);
|
||||
JsThread* js_thread = js_thread_run(path, js_cli_callback, &ctx);
|
||||
|
||||
while(furi_semaphore_acquire(ctx.exit_sem, 100) != FuriStatusOk) {
|
||||
if(cli_cmd_interrupt_received(cli)) break;
|
||||
if(cli_is_pipe_broken_or_is_etx_next_char(pipe)) break;
|
||||
}
|
||||
|
||||
js_thread_stop(js_thread);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <stdint.h>
|
||||
#include "js_thread_i.h"
|
||||
#include "js_value.h"
|
||||
#include <flipper_application/flipper_application.h>
|
||||
#include <flipper_application/plugins/plugin_manager.h>
|
||||
#include <flipper_application/plugins/composite_resolver.h>
|
||||
|
||||
291
applications/system/js_app/js_value.c
Normal file
291
applications/system/js_app/js_value.c
Normal file
@@ -0,0 +1,291 @@
|
||||
#include "js_value.h"
|
||||
#include <stdarg.h>
|
||||
|
||||
#ifdef APP_UNIT_TESTS
|
||||
#define JS_VAL_DEBUG
|
||||
#endif
|
||||
|
||||
size_t js_value_buffer_size(const JsValueParseDeclaration declaration) {
|
||||
if(declaration.source == JsValueParseSourceValue) {
|
||||
const JsValueDeclaration* value_decl = declaration.value_decl;
|
||||
JsValueType type = value_decl->type & JsValueTypeMask;
|
||||
|
||||
if(type == JsValueTypeString) return 1;
|
||||
|
||||
if(type == JsValueTypeObject) {
|
||||
size_t total = 0;
|
||||
for(size_t i = 0; i < value_decl->n_children; i++)
|
||||
total += js_value_buffer_size(
|
||||
JS_VALUE_PARSE_SOURCE_VALUE(value_decl->object_fields[i].value));
|
||||
return total;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
} else {
|
||||
const JsValueArguments* arg_decl = declaration.argument_decl;
|
||||
size_t total = 0;
|
||||
for(size_t i = 0; i < arg_decl->n_children; i++)
|
||||
total += js_value_buffer_size(JS_VALUE_PARSE_SOURCE_VALUE(&arg_decl->arguments[i]));
|
||||
return total;
|
||||
}
|
||||
}
|
||||
|
||||
static size_t js_value_resulting_c_values_count(const JsValueParseDeclaration declaration) {
|
||||
if(declaration.source == JsValueParseSourceValue) {
|
||||
const JsValueDeclaration* value_decl = declaration.value_decl;
|
||||
JsValueType type = value_decl->type & JsValueTypeMask;
|
||||
|
||||
if(type == JsValueTypeObject) {
|
||||
size_t total = 0;
|
||||
for(size_t i = 0; i < value_decl->n_children; i++)
|
||||
total += js_value_resulting_c_values_count(
|
||||
JS_VALUE_PARSE_SOURCE_VALUE(value_decl->object_fields[i].value));
|
||||
return total;
|
||||
}
|
||||
|
||||
return 1;
|
||||
|
||||
} else {
|
||||
const JsValueArguments* arg_decl = declaration.argument_decl;
|
||||
size_t total = 0;
|
||||
for(size_t i = 0; i < arg_decl->n_children; i++)
|
||||
total += js_value_resulting_c_values_count(
|
||||
JS_VALUE_PARSE_SOURCE_VALUE(&arg_decl->arguments[i]));
|
||||
return total;
|
||||
}
|
||||
}
|
||||
|
||||
#define PREPEND_JS_ERROR_AND_RETURN(mjs, flags, ...) \
|
||||
do { \
|
||||
if((flags) & JsValueParseFlagReturnOnError) \
|
||||
mjs_prepend_errorf((mjs), MJS_BAD_ARGS_ERROR, __VA_ARGS__); \
|
||||
return JsValueParseStatusJsError; \
|
||||
} while(0)
|
||||
|
||||
#define PREPEND_JS_EXPECTED_ERROR_AND_RETURN(mjs, flags, type) \
|
||||
PREPEND_JS_ERROR_AND_RETURN(mjs, flags, "expected %s", type)
|
||||
|
||||
static void js_value_assign_enum_val(void* destination, JsValueType type_w_flags, uint32_t value) {
|
||||
if(type_w_flags & JsValueTypeEnumSize1) {
|
||||
*(uint8_t*)destination = value;
|
||||
} else if(type_w_flags & JsValueTypeEnumSize2) {
|
||||
*(uint16_t*)destination = value;
|
||||
} else if(type_w_flags & JsValueTypeEnumSize4) {
|
||||
*(uint32_t*)destination = value;
|
||||
}
|
||||
}
|
||||
|
||||
static bool js_value_is_null_or_undefined(mjs_val_t* val_ptr) {
|
||||
return mjs_is_null(*val_ptr) || mjs_is_undefined(*val_ptr);
|
||||
}
|
||||
|
||||
static bool js_value_maybe_assign_default(
|
||||
const JsValueDeclaration* declaration,
|
||||
mjs_val_t* val_ptr,
|
||||
void* destination,
|
||||
size_t size) {
|
||||
if((declaration->type & JsValueTypePermitNull) && js_value_is_null_or_undefined(val_ptr)) {
|
||||
memcpy(destination, &declaration->default_value, size);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
typedef int (*MjsTypecheckFn)(mjs_val_t value);
|
||||
|
||||
static JsValueParseStatus js_value_parse_literal(
|
||||
struct mjs* mjs,
|
||||
JsValueParseFlag flags,
|
||||
mjs_val_t* destination,
|
||||
mjs_val_t* source,
|
||||
MjsTypecheckFn typecheck,
|
||||
const char* type_name) {
|
||||
if(!typecheck(*source)) PREPEND_JS_EXPECTED_ERROR_AND_RETURN(mjs, flags, type_name);
|
||||
*destination = *source;
|
||||
return JsValueParseStatusOk;
|
||||
}
|
||||
|
||||
static JsValueParseStatus js_value_parse_va(
|
||||
struct mjs* mjs,
|
||||
const JsValueParseDeclaration declaration,
|
||||
JsValueParseFlag flags,
|
||||
mjs_val_t* source,
|
||||
mjs_val_t* buffer,
|
||||
size_t* buffer_index,
|
||||
va_list* out_pointers) {
|
||||
if(declaration.source == JsValueParseSourceArguments) {
|
||||
const JsValueArguments* arg_decl = declaration.argument_decl;
|
||||
|
||||
for(size_t i = 0; i < arg_decl->n_children; i++) {
|
||||
mjs_val_t arg_val = mjs_arg(mjs, i);
|
||||
JsValueParseStatus status = js_value_parse_va(
|
||||
mjs,
|
||||
JS_VALUE_PARSE_SOURCE_VALUE(&arg_decl->arguments[i]),
|
||||
flags,
|
||||
&arg_val,
|
||||
buffer,
|
||||
buffer_index,
|
||||
out_pointers);
|
||||
if(status != JsValueParseStatusOk) return status;
|
||||
}
|
||||
|
||||
return JsValueParseStatusOk;
|
||||
}
|
||||
|
||||
const JsValueDeclaration* value_decl = declaration.value_decl;
|
||||
JsValueType type_w_flags = value_decl->type;
|
||||
JsValueType type_noflags = type_w_flags & JsValueTypeMask;
|
||||
bool is_null_but_allowed = (type_w_flags & JsValueTypePermitNull) &&
|
||||
js_value_is_null_or_undefined(source);
|
||||
|
||||
void* destination = NULL;
|
||||
if(type_noflags != JsValueTypeObject) destination = va_arg(*out_pointers, void*);
|
||||
|
||||
switch(type_noflags) {
|
||||
// Literal terms
|
||||
case JsValueTypeAny:
|
||||
*(mjs_val_t*)destination = *source;
|
||||
break;
|
||||
case JsValueTypeAnyArray:
|
||||
return js_value_parse_literal(mjs, flags, destination, source, mjs_is_array, "array");
|
||||
case JsValueTypeAnyObject:
|
||||
return js_value_parse_literal(mjs, flags, destination, source, mjs_is_object, "array");
|
||||
case JsValueTypeFunction:
|
||||
return js_value_parse_literal(
|
||||
mjs, flags, destination, source, mjs_is_function, "function");
|
||||
|
||||
// Primitive types
|
||||
case JsValueTypeRawPointer: {
|
||||
if(js_value_maybe_assign_default(value_decl, source, destination, sizeof(void*))) break;
|
||||
if(!mjs_is_foreign(*source)) PREPEND_JS_EXPECTED_ERROR_AND_RETURN(mjs, flags, "pointer");
|
||||
*(void**)destination = mjs_get_ptr(mjs, *source);
|
||||
break;
|
||||
}
|
||||
case JsValueTypeInt32: {
|
||||
if(js_value_maybe_assign_default(value_decl, source, destination, sizeof(int32_t))) break;
|
||||
if(!mjs_is_number(*source)) PREPEND_JS_EXPECTED_ERROR_AND_RETURN(mjs, flags, "number");
|
||||
*(int32_t*)destination = mjs_get_int32(mjs, *source);
|
||||
break;
|
||||
}
|
||||
case JsValueTypeDouble: {
|
||||
if(js_value_maybe_assign_default(value_decl, source, destination, sizeof(double))) break;
|
||||
if(!mjs_is_number(*source)) PREPEND_JS_EXPECTED_ERROR_AND_RETURN(mjs, flags, "number");
|
||||
*(double*)destination = mjs_get_double(mjs, *source);
|
||||
break;
|
||||
}
|
||||
case JsValueTypeBool: {
|
||||
if(js_value_maybe_assign_default(value_decl, source, destination, sizeof(bool))) break;
|
||||
if(!mjs_is_boolean(*source)) PREPEND_JS_EXPECTED_ERROR_AND_RETURN(mjs, flags, "bool");
|
||||
*(bool*)destination = mjs_get_bool(mjs, *source);
|
||||
break;
|
||||
}
|
||||
case JsValueTypeString: {
|
||||
if(js_value_maybe_assign_default(value_decl, source, destination, sizeof(const char*)))
|
||||
break;
|
||||
if(!mjs_is_string(*source)) PREPEND_JS_EXPECTED_ERROR_AND_RETURN(mjs, flags, "string");
|
||||
buffer[*buffer_index] = *source;
|
||||
*(const char**)destination = mjs_get_string(mjs, &buffer[*buffer_index], NULL);
|
||||
(*buffer_index)++;
|
||||
break;
|
||||
}
|
||||
|
||||
// Types with children
|
||||
case JsValueTypeEnum: {
|
||||
if(is_null_but_allowed) {
|
||||
js_value_assign_enum_val(
|
||||
destination, type_w_flags, value_decl->default_value.enum_val);
|
||||
|
||||
} else if(mjs_is_string(*source)) {
|
||||
const char* str = mjs_get_string(mjs, source, NULL);
|
||||
furi_check(str);
|
||||
|
||||
bool match_found = false;
|
||||
for(size_t i = 0; i < value_decl->n_children; i++) {
|
||||
const JsValueEnumVariant* variant = &value_decl->enum_variants[i];
|
||||
if(strcmp(str, variant->string_value) == 0) {
|
||||
js_value_assign_enum_val(destination, type_w_flags, variant->num_value);
|
||||
match_found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!match_found)
|
||||
PREPEND_JS_EXPECTED_ERROR_AND_RETURN(mjs, flags, "one of permitted strings");
|
||||
|
||||
} else {
|
||||
PREPEND_JS_EXPECTED_ERROR_AND_RETURN(mjs, flags, "string");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case JsValueTypeObject: {
|
||||
if(!(is_null_but_allowed || mjs_is_object(*source)))
|
||||
PREPEND_JS_EXPECTED_ERROR_AND_RETURN(mjs, flags, "object");
|
||||
for(size_t i = 0; i < value_decl->n_children; i++) {
|
||||
const JsValueObjectField* field = &value_decl->object_fields[i];
|
||||
mjs_val_t field_val = mjs_get(mjs, *source, field->field_name, ~0);
|
||||
JsValueParseStatus status = js_value_parse_va(
|
||||
mjs,
|
||||
JS_VALUE_PARSE_SOURCE_VALUE(field->value),
|
||||
flags,
|
||||
&field_val,
|
||||
buffer,
|
||||
buffer_index,
|
||||
out_pointers);
|
||||
if(status != JsValueParseStatusOk)
|
||||
PREPEND_JS_ERROR_AND_RETURN(mjs, flags, "field %s: ", field->field_name);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case JsValueTypeMask:
|
||||
case JsValueTypeEnumSize1:
|
||||
case JsValueTypeEnumSize2:
|
||||
case JsValueTypeEnumSize4:
|
||||
case JsValueTypePermitNull:
|
||||
furi_crash();
|
||||
}
|
||||
|
||||
return JsValueParseStatusOk;
|
||||
}
|
||||
|
||||
JsValueParseStatus js_value_parse(
|
||||
struct mjs* mjs,
|
||||
const JsValueParseDeclaration declaration,
|
||||
JsValueParseFlag flags,
|
||||
mjs_val_t* buffer,
|
||||
size_t buf_size,
|
||||
mjs_val_t* source,
|
||||
size_t n_c_vals,
|
||||
...) {
|
||||
furi_check(mjs);
|
||||
furi_check(buffer);
|
||||
|
||||
if(declaration.source == JsValueParseSourceValue) {
|
||||
furi_check(source);
|
||||
furi_check(declaration.value_decl);
|
||||
} else {
|
||||
furi_check(source == NULL);
|
||||
furi_check(declaration.argument_decl);
|
||||
}
|
||||
|
||||
#ifdef JS_VAL_DEBUG
|
||||
furi_check(buf_size == js_value_buffer_size(declaration));
|
||||
furi_check(n_c_vals == js_value_resulting_c_values_count(declaration));
|
||||
#else
|
||||
UNUSED(js_value_resulting_c_values_count);
|
||||
#endif
|
||||
|
||||
va_list out_pointers;
|
||||
va_start(out_pointers, n_c_vals);
|
||||
|
||||
size_t buffer_index = 0;
|
||||
JsValueParseStatus status =
|
||||
js_value_parse_va(mjs, declaration, flags, source, buffer, &buffer_index, &out_pointers);
|
||||
furi_check(buffer_index <= buf_size);
|
||||
|
||||
va_end(out_pointers);
|
||||
|
||||
return status;
|
||||
}
|
||||
212
applications/system/js_app/js_value.h
Normal file
212
applications/system/js_app/js_value.h
Normal file
@@ -0,0 +1,212 @@
|
||||
#pragma once
|
||||
|
||||
#include <furi.h>
|
||||
#include "js_modules.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
// literal types
|
||||
JsValueTypeAny, //<! Literal term
|
||||
JsValueTypeAnyArray, //<! Literal term, after ensuring that it's an array
|
||||
JsValueTypeAnyObject, //<! Literal term, after ensuring that it's an object
|
||||
JsValueTypeFunction, //<! Literal term, after ensuring that it's a function
|
||||
|
||||
// primitive types
|
||||
JsValueTypeRawPointer, //<! Unchecked `void*`
|
||||
JsValueTypeInt32, //<! Number cast to `int32_t`
|
||||
JsValueTypeDouble, //<! Number cast to `double`
|
||||
JsValueTypeString, //<! Any string cast to `const char*`
|
||||
JsValueTypeBool, //<! Bool cast to `bool`
|
||||
|
||||
// types with children
|
||||
JsValueTypeEnum, //<! String with predefined possible values cast to a C enum via a mapping
|
||||
JsValueTypeObject, //<! Object with predefined recursive fields cast to several C values
|
||||
|
||||
JsValueTypeMask = 0xff,
|
||||
|
||||
// enum sizes
|
||||
JsValueTypeEnumSize1 = (1 << 8),
|
||||
JsValueTypeEnumSize2 = (2 << 8),
|
||||
JsValueTypeEnumSize4 = (4 << 8),
|
||||
|
||||
// flags
|
||||
JsValueTypePermitNull = (1 << 16), //<! If the value is absent, assign default value
|
||||
} JsValueType;
|
||||
|
||||
#define JS_VALUE_TYPE_ENUM_SIZE(x) ((x) << 8)
|
||||
|
||||
typedef struct {
|
||||
const char* string_value;
|
||||
size_t num_value;
|
||||
} JsValueEnumVariant;
|
||||
|
||||
typedef union {
|
||||
void* ptr_val;
|
||||
int32_t int32_val;
|
||||
double double_val;
|
||||
const char* str_val;
|
||||
size_t enum_val;
|
||||
bool bool_val;
|
||||
} JsValueDefaultValue;
|
||||
|
||||
typedef struct JsValueObjectField JsValueObjectField;
|
||||
|
||||
typedef struct {
|
||||
JsValueType type;
|
||||
JsValueDefaultValue default_value;
|
||||
|
||||
size_t n_children;
|
||||
union {
|
||||
const JsValueEnumVariant* enum_variants;
|
||||
const JsValueObjectField* object_fields;
|
||||
};
|
||||
} JsValueDeclaration;
|
||||
|
||||
struct JsValueObjectField {
|
||||
const char* field_name;
|
||||
const JsValueDeclaration* value;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
size_t n_children;
|
||||
const JsValueDeclaration* arguments;
|
||||
} JsValueArguments;
|
||||
|
||||
#define JS_VALUE_ENUM(c_type, variants) \
|
||||
{ \
|
||||
.type = JsValueTypeEnum | JS_VALUE_TYPE_ENUM_SIZE(sizeof(c_type)), \
|
||||
.n_children = COUNT_OF(variants), \
|
||||
.enum_variants = variants, \
|
||||
}
|
||||
|
||||
#define JS_VALUE_ENUM_W_DEFAULT(c_type, variants, default) \
|
||||
{ \
|
||||
.type = JsValueTypeEnum | JsValueTypePermitNull | \
|
||||
JS_VALUE_TYPE_ENUM_SIZE(sizeof(c_type)), \
|
||||
.default_value.enum_val = default, \
|
||||
.n_children = COUNT_OF(variants), \
|
||||
.enum_variants = variants, \
|
||||
}
|
||||
|
||||
#define JS_VALUE_OBJECT(fields) \
|
||||
{ \
|
||||
.type = JsValueTypeObject, \
|
||||
.n_children = COUNT_OF(fields), \
|
||||
.object_fields = fields, \
|
||||
}
|
||||
|
||||
#define JS_VALUE_OBJECT_W_DEFAULTS(fields) \
|
||||
{ \
|
||||
.type = JsValueTypeObject | JsValueTypePermitNull, \
|
||||
.n_children = COUNT_OF(fields), \
|
||||
.object_fields = fields, \
|
||||
}
|
||||
|
||||
#define JS_VALUE_SIMPLE(t) {.type = t}
|
||||
|
||||
#define JS_VALUE_SIMPLE_W_DEFAULT(t, name, val) \
|
||||
{.type = (t) | JsValueTypePermitNull, .default_value.name = (val)}
|
||||
|
||||
#define JS_VALUE_ARGS(args) \
|
||||
{ \
|
||||
.n_children = COUNT_OF(args), \
|
||||
.arguments = args, \
|
||||
}
|
||||
|
||||
typedef enum {
|
||||
JsValueParseFlagNone = 0,
|
||||
JsValueParseFlagReturnOnError =
|
||||
(1
|
||||
<< 0), //<! Sets mjs error string to a description of the parsing error and returns from the JS function
|
||||
} JsValueParseFlag;
|
||||
|
||||
typedef enum {
|
||||
JsValueParseStatusOk, //<! Parsing completed successfully
|
||||
JsValueParseStatusJsError, //<! Parsing failed due to incorrect JS input
|
||||
} JsValueParseStatus;
|
||||
|
||||
typedef enum {
|
||||
JsValueParseSourceValue,
|
||||
JsValueParseSourceArguments,
|
||||
} JsValueParseSource;
|
||||
|
||||
typedef struct {
|
||||
JsValueParseSource source;
|
||||
union {
|
||||
const JsValueDeclaration* value_decl;
|
||||
const JsValueArguments* argument_decl;
|
||||
};
|
||||
} JsValueParseDeclaration;
|
||||
|
||||
#define JS_VALUE_PARSE_SOURCE_VALUE(declaration) \
|
||||
((JsValueParseDeclaration){.source = JsValueParseSourceValue, .value_decl = declaration})
|
||||
#define JS_VALUE_PARSE_SOURCE_ARGS(declaration) \
|
||||
((JsValueParseDeclaration){ \
|
||||
.source = JsValueParseSourceArguments, .argument_decl = declaration})
|
||||
|
||||
/**
|
||||
* @brief Determines the size of the buffer array of `mjs_val_t`s that needs to
|
||||
* be passed to `js_value_parse`.
|
||||
*/
|
||||
size_t js_value_buffer_size(const JsValueParseDeclaration declaration);
|
||||
|
||||
/**
|
||||
* @brief Converts a JS value into a series of C values.
|
||||
*
|
||||
* @param[in] mjs mJS instance pointer
|
||||
* @param[in] declaration Declaration for the input value. Chooses where the
|
||||
* values are to be fetched from (an `mjs_val_t` or
|
||||
* function arguments)
|
||||
* @param[in] flags See the corresponding enum.
|
||||
* @param[out] buffer Temporary buffer for values that need to live
|
||||
* longer than the function call. To determine the
|
||||
* size of the buffer, use `js_value_buffer_size`.
|
||||
* Values parsed by this function will become invalid
|
||||
* when this buffer goes out of scope.
|
||||
* @param[in] buf_size Number of entries in the temporary buffer (i.e.
|
||||
* `COUNT_OF`, not `sizeof`).
|
||||
* @param[in] source Source JS value that needs to be converted. May be
|
||||
* NULL if `declaration.source` is
|
||||
* `JsValueParseSourceArguments`.
|
||||
* @param[in] n_c_vals Number of output C values
|
||||
* @param[out] ... Pointers to output C values. The order in which
|
||||
* these values are populated corresponds to the order
|
||||
* in which the values are defined in the declaration.
|
||||
*
|
||||
* @returns Parsing status
|
||||
*/
|
||||
JsValueParseStatus js_value_parse(
|
||||
struct mjs* mjs,
|
||||
const JsValueParseDeclaration declaration,
|
||||
JsValueParseFlag flags,
|
||||
mjs_val_t* buffer,
|
||||
size_t buf_size,
|
||||
mjs_val_t* source,
|
||||
size_t n_c_vals,
|
||||
...);
|
||||
|
||||
#define JS_VALUE_PARSE(mjs, declaration, flags, status_ptr, value_ptr, ...) \
|
||||
void* _args[] = {__VA_ARGS__}; \
|
||||
size_t _n_args = COUNT_OF(_args); \
|
||||
size_t _temp_buf_len = js_value_buffer_size(declaration); \
|
||||
mjs_val_t _temp_buffer[_temp_buf_len]; \
|
||||
*(status_ptr) = js_value_parse( \
|
||||
mjs, declaration, flags, _temp_buffer, _temp_buf_len, value_ptr, _n_args, __VA_ARGS__);
|
||||
|
||||
#define JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, declaration, ...) \
|
||||
JsValueParseStatus _status; \
|
||||
JS_VALUE_PARSE( \
|
||||
mjs, \
|
||||
JS_VALUE_PARSE_SOURCE_ARGS(declaration), \
|
||||
JsValueParseFlagReturnOnError, \
|
||||
&_status, \
|
||||
NULL, \
|
||||
__VA_ARGS__); \
|
||||
if(_status != JsValueParseStatusOk) return;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <toolbox/path.h>
|
||||
#include <toolbox/tar/tar_archive.h>
|
||||
#include <toolbox/args.h>
|
||||
#include <toolbox/pipe.h>
|
||||
#include <update_util/update_manifest.h>
|
||||
#include <update_util/int_backup.h>
|
||||
#include <update_util/update_operation.h>
|
||||
@@ -63,8 +64,8 @@ static const CliSubcommand update_cli_subcommands[] = {
|
||||
{.command = "help", .handler = updater_cli_help},
|
||||
};
|
||||
|
||||
static void updater_cli_ep(Cli* cli, FuriString* args, void* context) {
|
||||
UNUSED(cli);
|
||||
static void updater_cli_ep(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(pipe);
|
||||
UNUSED(context);
|
||||
FuriString* subcommand;
|
||||
subcommand = furi_string_alloc();
|
||||
|
||||
@@ -17,6 +17,7 @@ Flipper Zero's built-in JavaScript engine enables you to run lightweight scripts
|
||||
|
||||
- @subpage js_badusb — This module allows you to emulate a standard USB keyboard
|
||||
- @subpage js_event_loop — The module for easy event-based developing
|
||||
- @subpage js_flipper — This module allows to query device information
|
||||
- @subpage js_gpio — This module allows you to control GPIO pins
|
||||
- @subpage js_gui — This module allows you to use GUI (graphical user interface)
|
||||
- @subpage js_math — This module contains mathematical methods and constants
|
||||
|
||||
@@ -10,12 +10,15 @@ let gui = require("gui");
|
||||
|
||||
GUI module has several submodules:
|
||||
|
||||
- @subpage js_gui__submenu — Displays a scrollable list of clickable textual entries
|
||||
- @subpage js_gui__loading — Displays an animated hourglass icon
|
||||
- @subpage js_gui__empty_screen — Just empty screen
|
||||
- @subpage js_gui__text_input — Keyboard-like text input
|
||||
- @subpage js_gui__text_box — Simple multiline text box
|
||||
- @subpage js_gui__byte_input — Keyboard-like hex input
|
||||
- @subpage js_gui__dialog — Dialog with up to 3 options
|
||||
- @subpage js_gui__empty_screen — Just empty screen
|
||||
- @subpage js_gui__file_picker — Displays a file selection prompt
|
||||
- @subpage js_gui__icon — Retrieves and loads icons for use in GUI
|
||||
- @subpage js_gui__loading — Displays an animated hourglass icon
|
||||
- @subpage js_gui__submenu — Displays a scrollable list of clickable textual entries
|
||||
- @subpage js_gui__text_box — Simple multiline text box
|
||||
- @subpage js_gui__text_input — Keyboard-like text input
|
||||
- @subpage js_gui__widget — Displays a combination of custom elements on one screen
|
||||
|
||||
---
|
||||
@@ -38,23 +41,23 @@ always access the canvas through a viewport.
|
||||
In Flipper's terminology, a "View" is a fullscreen design element that assumes
|
||||
control over the entire viewport and all input events. Different types of views
|
||||
are available (not all of which are unfortunately currently implemented in JS):
|
||||
| View | Has JS adapter? |
|
||||
|----------------------|------------------|
|
||||
| `button_menu` | ❌ |
|
||||
| `button_panel` | ❌ |
|
||||
| `byte_input` | ❌ |
|
||||
| `dialog_ex` | ✅ (as `dialog`) |
|
||||
| `empty_screen` | ✅ |
|
||||
| `file_browser` | ❌ |
|
||||
| `loading` | ✅ |
|
||||
| `menu` | ❌ |
|
||||
| `number_input` | ❌ |
|
||||
| `popup` | ❌ |
|
||||
| `submenu` | ✅ |
|
||||
| `text_box` | ✅ |
|
||||
| `text_input` | ✅ |
|
||||
| `variable_item_list` | ❌ |
|
||||
| `widget` | ❌ |
|
||||
| View | Has JS adapter? |
|
||||
|----------------------|-----------------------|
|
||||
| `button_menu` | ❌ |
|
||||
| `button_panel` | ❌ |
|
||||
| `byte_input` | ✅ |
|
||||
| `dialog_ex` | ✅ (as `dialog`) |
|
||||
| `empty_screen` | ✅ |
|
||||
| `file_browser` | ✅ (as `file_picker`) |
|
||||
| `loading` | ✅ |
|
||||
| `menu` | ❌ |
|
||||
| `number_input` | ❌ |
|
||||
| `popup` | ❌ |
|
||||
| `submenu` | ✅ |
|
||||
| `text_box` | ✅ |
|
||||
| `text_input` | ✅ |
|
||||
| `variable_item_list` | ❌ |
|
||||
| `widget` | ✅ |
|
||||
|
||||
In JS, each view has its own set of properties (or just "props"). The programmer
|
||||
can manipulate these properties in two ways:
|
||||
|
||||
@@ -41,6 +41,16 @@ extern "C" {
|
||||
#define CLAMP(x, upper, lower) (MIN(upper, MAX(x, lower)))
|
||||
#endif
|
||||
|
||||
#ifndef CLAMP_WRAPAROUND
|
||||
#define CLAMP_WRAPAROUND(x, upper, lower) \
|
||||
({ \
|
||||
__typeof__(x) _x = (x); \
|
||||
__typeof__(upper) _upper = (upper); \
|
||||
__typeof__(lower) _lower = (lower); \
|
||||
(_x > _upper) ? _lower : ((_x < _lower) ? _upper : _x); \
|
||||
})
|
||||
#endif
|
||||
|
||||
#ifndef COUNT_OF
|
||||
#define COUNT_OF(x) (sizeof(x) / sizeof(x[0]))
|
||||
#endif
|
||||
|
||||
@@ -78,6 +78,7 @@ void furi_event_loop_free(FuriEventLoop* instance) {
|
||||
furi_event_loop_process_timer_queue(instance);
|
||||
furi_check(TimerList_empty_p(instance->timer_list));
|
||||
furi_check(WaitingList_empty_p(instance->waiting_list));
|
||||
furi_check(!instance->are_thread_flags_subscribed);
|
||||
|
||||
FuriEventLoopTree_clear(instance->tree);
|
||||
PendingQueue_clear(instance->pending_queue);
|
||||
@@ -243,6 +244,10 @@ void furi_event_loop_run(FuriEventLoop* instance) {
|
||||
} else if(flags & FuriEventLoopFlagPending) {
|
||||
furi_event_loop_process_pending_callbacks(instance);
|
||||
|
||||
} else if(flags & FuriEventLoopFlagThreadFlag) {
|
||||
if(instance->are_thread_flags_subscribed)
|
||||
instance->thread_flags_callback(instance->thread_flags_callback_context);
|
||||
|
||||
} else {
|
||||
furi_crash();
|
||||
}
|
||||
@@ -416,6 +421,24 @@ void furi_event_loop_subscribe_mutex(
|
||||
instance, mutex, &furi_mutex_event_loop_contract, event, callback, context);
|
||||
}
|
||||
|
||||
void furi_event_loop_subscribe_thread_flags(
|
||||
FuriEventLoop* instance,
|
||||
FuriEventLoopThreadFlagsCallback callback,
|
||||
void* context) {
|
||||
furi_check(instance);
|
||||
furi_check(callback);
|
||||
furi_check(!instance->are_thread_flags_subscribed);
|
||||
instance->are_thread_flags_subscribed = true;
|
||||
instance->thread_flags_callback = callback;
|
||||
instance->thread_flags_callback_context = context;
|
||||
}
|
||||
|
||||
void furi_event_loop_unsubscribe_thread_flags(FuriEventLoop* instance) {
|
||||
furi_check(instance);
|
||||
furi_check(instance->are_thread_flags_subscribed);
|
||||
instance->are_thread_flags_subscribed = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public generic unsubscription API
|
||||
*/
|
||||
@@ -538,6 +561,25 @@ static bool furi_event_loop_item_is_waiting(FuriEventLoopItem* instance) {
|
||||
return instance->WaitingList.prev || instance->WaitingList.next;
|
||||
}
|
||||
|
||||
void furi_event_loop_thread_flag_callback(FuriThreadId thread_id) {
|
||||
TaskHandle_t hTask = (TaskHandle_t)thread_id;
|
||||
BaseType_t yield;
|
||||
|
||||
if(FURI_IS_IRQ_MODE()) {
|
||||
yield = pdFALSE;
|
||||
(void)xTaskNotifyIndexedFromISR(
|
||||
hTask,
|
||||
FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX,
|
||||
FuriEventLoopFlagThreadFlag,
|
||||
eSetBits,
|
||||
&yield);
|
||||
portYIELD_FROM_ISR(yield);
|
||||
} else {
|
||||
(void)xTaskNotifyIndexed(
|
||||
hTask, FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX, FuriEventLoopFlagThreadFlag, eSetBits);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Internal event loop link API, used by supported primitives
|
||||
*/
|
||||
|
||||
@@ -203,6 +203,12 @@ typedef void FuriEventLoopObject;
|
||||
*/
|
||||
typedef void (*FuriEventLoopEventCallback)(FuriEventLoopObject* object, void* context);
|
||||
|
||||
/** Callback type for event loop thread flag events
|
||||
*
|
||||
* @param context The context that was provided upon subscription
|
||||
*/
|
||||
typedef void (*FuriEventLoopThreadFlagsCallback)(void* context);
|
||||
|
||||
/** Opaque event flag type */
|
||||
typedef struct FuriEventFlag FuriEventFlag;
|
||||
|
||||
@@ -304,6 +310,23 @@ void furi_event_loop_subscribe_mutex(
|
||||
FuriEventLoopEventCallback callback,
|
||||
void* context);
|
||||
|
||||
/** Subscribe to thread flag events of the current thread
|
||||
*
|
||||
* @param instance The Event Loop instance
|
||||
* @param callback The callback to call when a flag has been set
|
||||
* @param context The context for callback
|
||||
*/
|
||||
void furi_event_loop_subscribe_thread_flags(
|
||||
FuriEventLoop* instance,
|
||||
FuriEventLoopThreadFlagsCallback callback,
|
||||
void* context);
|
||||
|
||||
/** Unsubscribe from thread flag events of the current thread
|
||||
*
|
||||
* @param instance The Event Loop instance
|
||||
*/
|
||||
void furi_event_loop_unsubscribe_thread_flags(FuriEventLoop* instance);
|
||||
|
||||
/** Unsubscribe from events (common)
|
||||
*
|
||||
* @param instance The Event Loop instance
|
||||
|
||||
@@ -4,12 +4,14 @@
|
||||
#include "event_loop_link_i.h"
|
||||
#include "event_loop_timer_i.h"
|
||||
#include "event_loop_tick_i.h"
|
||||
#include "event_loop_thread_flag_interface.h"
|
||||
|
||||
#include <m-list.h>
|
||||
#include <m-bptree.h>
|
||||
#include <m-i-list.h>
|
||||
|
||||
#include "thread.h"
|
||||
#include "thread_i.h"
|
||||
|
||||
struct FuriEventLoopItem {
|
||||
// Source
|
||||
@@ -50,11 +52,12 @@ typedef enum {
|
||||
FuriEventLoopFlagStop = (1 << 1),
|
||||
FuriEventLoopFlagTimer = (1 << 2),
|
||||
FuriEventLoopFlagPending = (1 << 3),
|
||||
FuriEventLoopFlagThreadFlag = (1 << 4),
|
||||
} FuriEventLoopFlag;
|
||||
|
||||
#define FuriEventLoopFlagAll \
|
||||
(FuriEventLoopFlagEvent | FuriEventLoopFlagStop | FuriEventLoopFlagTimer | \
|
||||
FuriEventLoopFlagPending)
|
||||
FuriEventLoopFlagPending | FuriEventLoopFlagThreadFlag)
|
||||
|
||||
typedef enum {
|
||||
FuriEventLoopProcessStatusComplete,
|
||||
@@ -94,4 +97,9 @@ struct FuriEventLoop {
|
||||
PendingQueue_t pending_queue;
|
||||
// Tick event
|
||||
FuriEventLoopTick tick;
|
||||
|
||||
// Thread flags callback
|
||||
bool are_thread_flags_subscribed;
|
||||
FuriEventLoopThreadFlagsCallback thread_flags_callback;
|
||||
void* thread_flags_callback_context;
|
||||
};
|
||||
|
||||
10
furi/core/event_loop_thread_flag_interface.h
Normal file
10
furi/core/event_loop_thread_flag_interface.h
Normal file
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include "thread.h"
|
||||
|
||||
/**
|
||||
* @brief Notify `FuriEventLoop` that `furi_thread_flags_set` has been called
|
||||
*
|
||||
* @param thread_id Thread id
|
||||
*/
|
||||
extern void furi_event_loop_thread_flag_callback(FuriThreadId thread_id);
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "check.h"
|
||||
#include "common_defines.h"
|
||||
#include "string.h"
|
||||
#include "event_loop_thread_flag_interface.h"
|
||||
|
||||
#include "log.h"
|
||||
#include <furi_hal_rtc.h>
|
||||
@@ -503,6 +504,9 @@ uint32_t furi_thread_flags_set(FuriThreadId thread_id, uint32_t flags) {
|
||||
(void)xTaskNotifyAndQueryIndexed(hTask, THREAD_NOTIFY_INDEX, 0, eNoAction, &rflags);
|
||||
}
|
||||
}
|
||||
|
||||
furi_event_loop_thread_flag_callback(thread_id);
|
||||
|
||||
/* Return flags after setting */
|
||||
return rflags;
|
||||
}
|
||||
|
||||
@@ -380,7 +380,10 @@ bool ble_profile_hid_mouse_scroll(FuriHalBleProfileBase* profile, int8_t delta)
|
||||
#define CONNECTION_INTERVAL_MAX (0x24)
|
||||
|
||||
static GapConfig template_config = {
|
||||
.adv_service_uuid = HUMAN_INTERFACE_DEVICE_SERVICE_UUID,
|
||||
.adv_service = {
|
||||
.UUID_Type = UUID_TYPE_16,
|
||||
.Service_UUID_16 = HUMAN_INTERFACE_DEVICE_SERVICE_UUID,
|
||||
},
|
||||
.appearance_char = GAP_APPEARANCE_KEYBOARD,
|
||||
.bonding_mode = true,
|
||||
.pairing_method = GapPairingPinCodeVerifyYesNo,
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#include "pipe.h"
|
||||
#include <furi.h>
|
||||
|
||||
#define PIPE_DEFAULT_STATE_CHECK_PERIOD furi_ms_to_ticks(100)
|
||||
|
||||
/**
|
||||
* Data shared between both sides.
|
||||
*/
|
||||
@@ -23,7 +25,7 @@ struct PipeSide {
|
||||
PipeSideDataArrivedCallback on_data_arrived;
|
||||
PipeSideSpaceFreedCallback on_space_freed;
|
||||
PipeSideBrokenCallback on_pipe_broken;
|
||||
FuriWait stdout_timeout;
|
||||
FuriWait state_check_period;
|
||||
};
|
||||
|
||||
PipeSideBundle pipe_alloc(size_t capacity, size_t trigger_level) {
|
||||
@@ -53,14 +55,14 @@ PipeSideBundle pipe_alloc_ex(PipeSideReceiveSettings alice, PipeSideReceiveSetti
|
||||
.shared = shared,
|
||||
.sending = alice_to_bob,
|
||||
.receiving = bob_to_alice,
|
||||
.stdout_timeout = FuriWaitForever,
|
||||
.state_check_period = PIPE_DEFAULT_STATE_CHECK_PERIOD,
|
||||
};
|
||||
*bobs_side = (PipeSide){
|
||||
.role = PipeRoleBob,
|
||||
.shared = shared,
|
||||
.sending = bob_to_alice,
|
||||
.receiving = alice_to_bob,
|
||||
.stdout_timeout = FuriWaitForever,
|
||||
.state_check_period = PIPE_DEFAULT_STATE_CHECK_PERIOD,
|
||||
};
|
||||
|
||||
return (PipeSideBundle){.alices_side = alices_side, .bobs_side = bobs_side};
|
||||
@@ -99,42 +101,62 @@ void pipe_free(PipeSide* pipe) {
|
||||
}
|
||||
}
|
||||
|
||||
static void _pipe_stdout_cb(const char* data, size_t size, void* context) {
|
||||
static void pipe_stdout_cb(const char* data, size_t size, void* context) {
|
||||
furi_assert(context);
|
||||
PipeSide* pipe = context;
|
||||
while(size) {
|
||||
size_t sent = pipe_send(pipe, data, size, pipe->stdout_timeout);
|
||||
if(!sent) break;
|
||||
data += sent;
|
||||
size -= sent;
|
||||
}
|
||||
pipe_send(pipe, data, size);
|
||||
}
|
||||
|
||||
static size_t _pipe_stdin_cb(char* data, size_t size, FuriWait timeout, void* context) {
|
||||
static size_t pipe_stdin_cb(char* data, size_t size, FuriWait timeout, void* context) {
|
||||
UNUSED(timeout);
|
||||
furi_assert(context);
|
||||
PipeSide* pipe = context;
|
||||
return pipe_receive(pipe, data, size, timeout);
|
||||
return pipe_receive(pipe, data, size);
|
||||
}
|
||||
|
||||
void pipe_install_as_stdio(PipeSide* pipe) {
|
||||
furi_check(pipe);
|
||||
furi_thread_set_stdout_callback(_pipe_stdout_cb, pipe);
|
||||
furi_thread_set_stdin_callback(_pipe_stdin_cb, pipe);
|
||||
furi_thread_set_stdout_callback(pipe_stdout_cb, pipe);
|
||||
furi_thread_set_stdin_callback(pipe_stdin_cb, pipe);
|
||||
}
|
||||
|
||||
void pipe_set_stdout_timeout(PipeSide* pipe, FuriWait timeout) {
|
||||
void pipe_set_state_check_period(PipeSide* pipe, FuriWait check_period) {
|
||||
furi_check(pipe);
|
||||
pipe->stdout_timeout = timeout;
|
||||
pipe->state_check_period = check_period;
|
||||
}
|
||||
|
||||
size_t pipe_receive(PipeSide* pipe, void* data, size_t length, FuriWait timeout) {
|
||||
size_t pipe_receive(PipeSide* pipe, void* data, size_t length) {
|
||||
furi_check(pipe);
|
||||
return furi_stream_buffer_receive(pipe->receiving, data, length, timeout);
|
||||
|
||||
size_t received = 0;
|
||||
while(length) {
|
||||
size_t received_this_time =
|
||||
furi_stream_buffer_receive(pipe->receiving, data, length, pipe->state_check_period);
|
||||
if(!received_this_time && pipe_state(pipe) == PipeStateBroken) break;
|
||||
|
||||
received += received_this_time;
|
||||
length -= received_this_time;
|
||||
data += received_this_time;
|
||||
}
|
||||
|
||||
return received;
|
||||
}
|
||||
|
||||
size_t pipe_send(PipeSide* pipe, const void* data, size_t length, FuriWait timeout) {
|
||||
size_t pipe_send(PipeSide* pipe, const void* data, size_t length) {
|
||||
furi_check(pipe);
|
||||
return furi_stream_buffer_send(pipe->sending, data, length, timeout);
|
||||
|
||||
size_t sent = 0;
|
||||
while(length) {
|
||||
size_t sent_this_time =
|
||||
furi_stream_buffer_send(pipe->sending, data, length, pipe->state_check_period);
|
||||
if(!sent_this_time && pipe_state(pipe) == PipeStateBroken) break;
|
||||
|
||||
sent += sent_this_time;
|
||||
length -= sent_this_time;
|
||||
data += sent_this_time;
|
||||
}
|
||||
|
||||
return sent;
|
||||
}
|
||||
|
||||
size_t pipe_bytes_available(PipeSide* pipe) {
|
||||
@@ -151,14 +173,14 @@ static void pipe_receiving_buffer_callback(FuriEventLoopObject* buffer, void* co
|
||||
UNUSED(buffer);
|
||||
PipeSide* pipe = context;
|
||||
furi_assert(pipe);
|
||||
if(pipe->on_space_freed) pipe->on_data_arrived(pipe, pipe->callback_context);
|
||||
if(pipe->on_data_arrived) pipe->on_data_arrived(pipe, pipe->callback_context);
|
||||
}
|
||||
|
||||
static void pipe_sending_buffer_callback(FuriEventLoopObject* buffer, void* context) {
|
||||
UNUSED(buffer);
|
||||
PipeSide* pipe = context;
|
||||
furi_assert(pipe);
|
||||
if(pipe->on_data_arrived) pipe->on_space_freed(pipe, pipe->callback_context);
|
||||
if(pipe->on_space_freed) pipe->on_space_freed(pipe, pipe->callback_context);
|
||||
}
|
||||
|
||||
static void pipe_semaphore_callback(FuriEventLoopObject* semaphore, void* context) {
|
||||
|
||||
@@ -148,38 +148,48 @@ void pipe_free(PipeSide* pipe);
|
||||
void pipe_install_as_stdio(PipeSide* pipe);
|
||||
|
||||
/**
|
||||
* @brief Sets the timeout for `stdout` write operations
|
||||
* @brief Sets the state check period for `send` and `receive` operations
|
||||
*
|
||||
* @note This value is set to `FuriWaitForever` when the pipe is created
|
||||
* @note This value is set to 100 ms when the pipe is created
|
||||
*
|
||||
* @param [in] pipe Pipe side to set the timeout of
|
||||
* @param [in] timeout Timeout value in ticks
|
||||
* `send` and `receive` will check the state of the pipe if exactly 0 bytes were
|
||||
* sent or received during any given `check_period`. Read the documentation for
|
||||
* `pipe_send` and `pipe_receive` for more info.
|
||||
*
|
||||
* @param [in] pipe Pipe side to set the check period of
|
||||
* @param [in] check_period Period in ticks
|
||||
*/
|
||||
void pipe_set_stdout_timeout(PipeSide* pipe, FuriWait timeout);
|
||||
void pipe_set_state_check_period(PipeSide* pipe, FuriWait check_period);
|
||||
|
||||
/**
|
||||
* @brief Receives data from the pipe.
|
||||
*
|
||||
* This function will try to receive all of the requested bytes from the pipe.
|
||||
* If at some point during the operation the pipe becomes broken, this function
|
||||
* will return prematurely, in which case the return value will be less than the
|
||||
* requested `length`.
|
||||
*
|
||||
* @param [in] pipe The pipe side to read data out of
|
||||
* @param [out] data The buffer to fill with data
|
||||
* @param length Maximum length of data to read
|
||||
* @param timeout The timeout (in ticks) after which the read operation is
|
||||
* interrupted
|
||||
* @returns The number of bytes actually written into the provided buffer
|
||||
*/
|
||||
size_t pipe_receive(PipeSide* pipe, void* data, size_t length, FuriWait timeout);
|
||||
size_t pipe_receive(PipeSide* pipe, void* data, size_t length);
|
||||
|
||||
/**
|
||||
* @brief Sends data into the pipe.
|
||||
*
|
||||
* This function will try to send all of the requested bytes to the pipe.
|
||||
* If at some point during the operation the pipe becomes broken, this function
|
||||
* will return prematurely, in which case the return value will be less than the
|
||||
* requested `length`.
|
||||
*
|
||||
* @param [in] pipe The pipe side to send data into
|
||||
* @param [out] data The buffer to get data from
|
||||
* @param length Maximum length of data to send
|
||||
* @param timeout The timeout (in ticks) after which the write operation is
|
||||
* interrupted
|
||||
* @returns The number of bytes actually read from the provided buffer
|
||||
*/
|
||||
size_t pipe_send(PipeSide* pipe, const void* data, size_t length, FuriWait timeout);
|
||||
size_t pipe_send(PipeSide* pipe, const void* data, size_t length);
|
||||
|
||||
/**
|
||||
* @brief Determines how many bytes there are in the pipe available to be read.
|
||||
|
||||
@@ -296,63 +296,65 @@ def _validate_app_imports(target, source, env):
|
||||
for sym in unresolved_syms
|
||||
if sym.startswith(
|
||||
(
|
||||
# advanced_plugin
|
||||
# example_advanced_plugins app_api_table
|
||||
"app_api_accumulator_",
|
||||
# gallagher
|
||||
"GALLAGHER_CARDAX_ASCII",
|
||||
"gallagher_deobfuscate_and_parse_credential",
|
||||
# js_
|
||||
# js_app app_api_table
|
||||
"js_delay_with_flags",
|
||||
"js_event_loop_get_loop",
|
||||
"js_flags_set",
|
||||
"js_flags_wait",
|
||||
"js_gui_make_view_factory",
|
||||
"js_module_get",
|
||||
# test_js
|
||||
"js_thread_run",
|
||||
"js_thread_stop",
|
||||
# totp_
|
||||
"totp_",
|
||||
"token_info_",
|
||||
"memset_s",
|
||||
# social_moscow, troika
|
||||
"mosgortrans_parse_transport_block",
|
||||
"render_section_header",
|
||||
# metroflip
|
||||
# js_event_loop_api_table
|
||||
"js_event_loop_get_loop",
|
||||
# js_gui_api_table
|
||||
"js_gui_make_view_factory",
|
||||
# metroflip_api_table
|
||||
"metroflip_",
|
||||
"apdu_success",
|
||||
"bit_slice_to_dec",
|
||||
"byte_to_binary",
|
||||
"free_calypso_",
|
||||
"get_calypso_",
|
||||
"get_intercode_",
|
||||
"get_network_",
|
||||
"get_opus_",
|
||||
"get_ravkav_",
|
||||
"guess_card_type",
|
||||
"handle_keyfile_case",
|
||||
"is_calypso_",
|
||||
"manage_keyfiles",
|
||||
"mf_classic_key_cache_",
|
||||
"read_file",
|
||||
"apdu_success",
|
||||
"select_app",
|
||||
"show_navigo_",
|
||||
"show_opus_",
|
||||
"show_ravkav_",
|
||||
"mf_classic_key_cache_",
|
||||
"manage_keyfiles",
|
||||
"uid_to_string",
|
||||
"handle_keyfile_case",
|
||||
"get_calypso_",
|
||||
"get_network_",
|
||||
"is_calypso_",
|
||||
"free_calypso_",
|
||||
"guess_card_type",
|
||||
"get_intercode_",
|
||||
"show_navigo_",
|
||||
"get_opus_",
|
||||
"show_opus_",
|
||||
"get_ravkav_",
|
||||
"show_ravkav_",
|
||||
"mosgortrans_parse_transport_block",
|
||||
"render_section_header",
|
||||
# nfc_app_api_table
|
||||
"gallagher_deobfuscate_and_parse_credential",
|
||||
"GALLAGHER_CARDAX_ASCII",
|
||||
"mosgortrans_parse_transport_block",
|
||||
"render_section_header",
|
||||
# totp app_api_table
|
||||
"totp_",
|
||||
"memset_s",
|
||||
"token_info_",
|
||||
# unit_tests_api_table
|
||||
"js_thread_run",
|
||||
"js_thread_stop",
|
||||
"js_value_buffer_size",
|
||||
"js_value_parse",
|
||||
)
|
||||
)
|
||||
and any(
|
||||
prefix in source[0].path
|
||||
for prefix in [
|
||||
# example_advanced_plugins app_api_table
|
||||
"advanced_plugin",
|
||||
"gallagher",
|
||||
# js_app app_api_table, js_event_loop_api_table, js_gui_api_table
|
||||
"js_", # js_app and all js_ modules
|
||||
"social_moscow",
|
||||
"test_js",
|
||||
"totp_",
|
||||
"troika",
|
||||
# metroflip
|
||||
# metroflip_api_table
|
||||
"bip_plugin",
|
||||
"calypso_plugin",
|
||||
"charliecard_plugin",
|
||||
@@ -364,6 +366,14 @@ def _validate_app_imports(target, source, env):
|
||||
"opal_plugin",
|
||||
"smartrider_plugin",
|
||||
"troika_plugin",
|
||||
# nfc_app_api_table
|
||||
"gallagher",
|
||||
"social_moscow",
|
||||
"troika",
|
||||
# totp app_api_table
|
||||
"totp_",
|
||||
# unit_tests_api_table
|
||||
"test_js",
|
||||
]
|
||||
)
|
||||
]
|
||||
|
||||
59
scripts/serial_cli_perf.py
Normal file
59
scripts/serial_cli_perf.py
Normal file
@@ -0,0 +1,59 @@
|
||||
import argparse
|
||||
import logging
|
||||
from serial import Serial
|
||||
from random import randint
|
||||
from time import time
|
||||
|
||||
from flipper.utils.cdc import resolve_port
|
||||
|
||||
|
||||
def main():
|
||||
logger = logging.getLogger()
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("-p", "--port", help="CDC Port", default="auto")
|
||||
parser.add_argument(
|
||||
"-l", "--length", type=int, help="Number of bytes to send", default=1024**2
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
if not (port := resolve_port(logger, args.port)):
|
||||
logger.error("Is Flipper connected via USB and not in DFU mode?")
|
||||
return 1
|
||||
port = Serial(port, 230400)
|
||||
port.timeout = 2
|
||||
|
||||
port.read_until(b">: ")
|
||||
port.write(b"echo\r")
|
||||
port.read_until(b">: ")
|
||||
|
||||
print(f"Transferring {args.length} bytes. Hang tight...")
|
||||
|
||||
start_time = time()
|
||||
|
||||
bytes_to_send = args.length
|
||||
block_size = 1024
|
||||
while bytes_to_send:
|
||||
actual_size = min(block_size, bytes_to_send)
|
||||
# can't use 0x03 because that's ASCII ETX, or Ctrl+C
|
||||
block = bytes([randint(4, 255) for _ in range(actual_size)])
|
||||
|
||||
port.write(block)
|
||||
return_block = port.read(actual_size)
|
||||
|
||||
if return_block != block:
|
||||
logger.error("Incorrect block received")
|
||||
break
|
||||
|
||||
bytes_to_send -= actual_size
|
||||
|
||||
end_time = time()
|
||||
delta = end_time - start_time
|
||||
speed = args.length / delta
|
||||
print(f"Speed: {speed/1024:.2f} KiB/s")
|
||||
|
||||
port.write(b"\x03") # Ctrl+C
|
||||
port.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -35,7 +35,7 @@ class Main(App):
|
||||
|
||||
FLASH_BASE = 0x8000000
|
||||
FLASH_PAGE_SIZE = 4 * 1024
|
||||
MIN_GAP_PAGES = 1
|
||||
MIN_GAP_PAGES = 0
|
||||
|
||||
# Update stage file larger than that is not loadable without fix
|
||||
# https://github.com/flipperdevices/flipperzero-firmware/pull/3676
|
||||
|
||||
@@ -3,6 +3,7 @@ Version,+,79.2,,
|
||||
Header,+,applications/services/bt/bt_service/bt.h,,
|
||||
Header,+,applications/services/bt/bt_service/bt_keys_storage.h,,
|
||||
Header,+,applications/services/cli/cli.h,,
|
||||
Header,+,applications/services/cli/cli_ansi.h,,
|
||||
Header,+,applications/services/cli/cli_vcp.h,,
|
||||
Header,+,applications/services/dialogs/dialogs.h,,
|
||||
Header,+,applications/services/dolphin/dolphin.h,,
|
||||
@@ -778,18 +779,17 @@ Function,-,ceill,long double,long double
|
||||
Function,-,cfree,void,void*
|
||||
Function,-,clearerr,void,FILE*
|
||||
Function,-,clearerr_unlocked,void,FILE*
|
||||
Function,+,cli_add_command,void,"Cli*, const char*, CliCommandFlag, CliCallback, void*"
|
||||
Function,+,cli_cmd_interrupt_received,_Bool,Cli*
|
||||
Function,+,cli_add_command,void,"Cli*, const char*, CliCommandFlag, CliExecuteCallback, void*"
|
||||
Function,+,cli_add_command_ex,void,"Cli*, const char*, CliCommandFlag, CliExecuteCallback, void*, size_t"
|
||||
Function,+,cli_ansi_parser_alloc,CliAnsiParser*,
|
||||
Function,+,cli_ansi_parser_feed,CliAnsiParserResult,"CliAnsiParser*, char"
|
||||
Function,+,cli_ansi_parser_feed_timeout,CliAnsiParserResult,CliAnsiParser*
|
||||
Function,+,cli_ansi_parser_free,void,CliAnsiParser*
|
||||
Function,+,cli_delete_command,void,"Cli*, const char*"
|
||||
Function,+,cli_getc,char,Cli*
|
||||
Function,+,cli_is_connected,_Bool,Cli*
|
||||
Function,+,cli_nl,void,Cli*
|
||||
Function,+,cli_is_pipe_broken_or_is_etx_next_char,_Bool,PipeSide*
|
||||
Function,+,cli_print_usage,void,"const char*, const char*, const char*"
|
||||
Function,+,cli_read,size_t,"Cli*, uint8_t*, size_t"
|
||||
Function,+,cli_read_timeout,size_t,"Cli*, uint8_t*, size_t, uint32_t"
|
||||
Function,+,cli_session_close,void,Cli*
|
||||
Function,+,cli_session_open,void,"Cli*, const void*"
|
||||
Function,+,cli_write,void,"Cli*, const uint8_t*, size_t"
|
||||
Function,+,cli_vcp_disable,void,CliVcp*
|
||||
Function,+,cli_vcp_enable,void,CliVcp*
|
||||
Function,+,composite_api_resolver_add,void,"CompositeApiResolver*, const ElfApiInterface*"
|
||||
Function,+,composite_api_resolver_alloc,CompositeApiResolver*,
|
||||
Function,+,composite_api_resolver_free,void,CompositeApiResolver*
|
||||
@@ -1138,6 +1138,7 @@ Function,+,furi_event_loop_subscribe_message_queue,void,"FuriEventLoop*, FuriMes
|
||||
Function,+,furi_event_loop_subscribe_mutex,void,"FuriEventLoop*, FuriMutex*, FuriEventLoopEvent, FuriEventLoopEventCallback, void*"
|
||||
Function,+,furi_event_loop_subscribe_semaphore,void,"FuriEventLoop*, FuriSemaphore*, FuriEventLoopEvent, FuriEventLoopEventCallback, void*"
|
||||
Function,+,furi_event_loop_subscribe_stream_buffer,void,"FuriEventLoop*, FuriStreamBuffer*, FuriEventLoopEvent, FuriEventLoopEventCallback, void*"
|
||||
Function,+,furi_event_loop_subscribe_thread_flags,void,"FuriEventLoop*, FuriEventLoopThreadFlagsCallback, void*"
|
||||
Function,+,furi_event_loop_tick_set,void,"FuriEventLoop*, uint32_t, FuriEventLoopTickCallback, void*"
|
||||
Function,+,furi_event_loop_timer_alloc,FuriEventLoopTimer*,"FuriEventLoop*, FuriEventLoopTimerCallback, FuriEventLoopTimerType, void*"
|
||||
Function,+,furi_event_loop_timer_free,void,FuriEventLoopTimer*
|
||||
@@ -1148,6 +1149,7 @@ Function,+,furi_event_loop_timer_restart,void,FuriEventLoopTimer*
|
||||
Function,+,furi_event_loop_timer_start,void,"FuriEventLoopTimer*, uint32_t"
|
||||
Function,+,furi_event_loop_timer_stop,void,FuriEventLoopTimer*
|
||||
Function,+,furi_event_loop_unsubscribe,void,"FuriEventLoop*, FuriEventLoopObject*"
|
||||
Function,+,furi_event_loop_unsubscribe_thread_flags,void,FuriEventLoop*
|
||||
Function,+,furi_get_tick,uint32_t,
|
||||
Function,+,furi_hal_adc_acquire,FuriHalAdcHandle*,
|
||||
Function,+,furi_hal_adc_configure,void,FuriHalAdcHandle*
|
||||
@@ -2332,14 +2334,14 @@ Function,+,pipe_bytes_available,size_t,PipeSide*
|
||||
Function,+,pipe_detach_from_event_loop,void,PipeSide*
|
||||
Function,+,pipe_free,void,PipeSide*
|
||||
Function,+,pipe_install_as_stdio,void,PipeSide*
|
||||
Function,+,pipe_receive,size_t,"PipeSide*, void*, size_t, FuriWait"
|
||||
Function,+,pipe_receive,size_t,"PipeSide*, void*, size_t"
|
||||
Function,+,pipe_role,PipeRole,PipeSide*
|
||||
Function,+,pipe_send,size_t,"PipeSide*, const void*, size_t, FuriWait"
|
||||
Function,+,pipe_send,size_t,"PipeSide*, const void*, size_t"
|
||||
Function,+,pipe_set_broken_callback,void,"PipeSide*, PipeSideBrokenCallback, FuriEventLoopEvent"
|
||||
Function,+,pipe_set_callback_context,void,"PipeSide*, void*"
|
||||
Function,+,pipe_set_data_arrived_callback,void,"PipeSide*, PipeSideDataArrivedCallback, FuriEventLoopEvent"
|
||||
Function,+,pipe_set_space_freed_callback,void,"PipeSide*, PipeSideSpaceFreedCallback, FuriEventLoopEvent"
|
||||
Function,+,pipe_set_stdout_timeout,void,"PipeSide*, FuriWait"
|
||||
Function,+,pipe_set_state_check_period,void,"PipeSide*, FuriWait"
|
||||
Function,+,pipe_spaces_available,size_t,PipeSide*
|
||||
Function,+,pipe_state,PipeState,PipeSide*
|
||||
Function,+,plugin_manager_alloc,PluginManager*,"const char*, uint32_t, const ElfApiInterface*"
|
||||
@@ -2942,7 +2944,6 @@ Variable,-,_sys_errlist,const char* const[],
|
||||
Variable,-,_sys_nerr,int,
|
||||
Variable,-,ble_profile_hid,const FuriHalBleProfileTemplate*,
|
||||
Variable,+,ble_profile_serial,const FuriHalBleProfileTemplate* const,
|
||||
Variable,+,cli_vcp,const CliSession,
|
||||
Variable,+,compress_config_heatshrink_default,const CompressConfigHeatshrink,
|
||||
Variable,+,firmware_api_interface,const ElfApiInterface* const,
|
||||
Variable,+,furi_hal_i2c_bus_external,FuriHalI2cBus,
|
||||
|
||||
|
@@ -7,6 +7,7 @@ Header,+,applications/services/applications.h,,
|
||||
Header,+,applications/services/bt/bt_service/bt.h,,
|
||||
Header,+,applications/services/bt/bt_service/bt_keys_storage.h,,
|
||||
Header,+,applications/services/cli/cli.h,,
|
||||
Header,+,applications/services/cli/cli_ansi.h,,
|
||||
Header,+,applications/services/cli/cli_vcp.h,,
|
||||
Header,+,applications/services/dialogs/dialogs.h,,
|
||||
Header,+,applications/services/dolphin/dolphin.h,,
|
||||
@@ -875,18 +876,17 @@ Function,-,ceill,long double,long double
|
||||
Function,-,cfree,void,void*
|
||||
Function,-,clearerr,void,FILE*
|
||||
Function,-,clearerr_unlocked,void,FILE*
|
||||
Function,+,cli_add_command,void,"Cli*, const char*, CliCommandFlag, CliCallback, void*"
|
||||
Function,+,cli_cmd_interrupt_received,_Bool,Cli*
|
||||
Function,+,cli_add_command,void,"Cli*, const char*, CliCommandFlag, CliExecuteCallback, void*"
|
||||
Function,+,cli_add_command_ex,void,"Cli*, const char*, CliCommandFlag, CliExecuteCallback, void*, size_t"
|
||||
Function,+,cli_ansi_parser_alloc,CliAnsiParser*,
|
||||
Function,+,cli_ansi_parser_feed,CliAnsiParserResult,"CliAnsiParser*, char"
|
||||
Function,+,cli_ansi_parser_feed_timeout,CliAnsiParserResult,CliAnsiParser*
|
||||
Function,+,cli_ansi_parser_free,void,CliAnsiParser*
|
||||
Function,+,cli_delete_command,void,"Cli*, const char*"
|
||||
Function,+,cli_getc,char,Cli*
|
||||
Function,+,cli_is_connected,_Bool,Cli*
|
||||
Function,+,cli_nl,void,Cli*
|
||||
Function,+,cli_is_pipe_broken_or_is_etx_next_char,_Bool,PipeSide*
|
||||
Function,+,cli_print_usage,void,"const char*, const char*, const char*"
|
||||
Function,+,cli_read,size_t,"Cli*, uint8_t*, size_t"
|
||||
Function,+,cli_read_timeout,size_t,"Cli*, uint8_t*, size_t, uint32_t"
|
||||
Function,+,cli_session_close,void,Cli*
|
||||
Function,+,cli_session_open,void,"Cli*, const void*"
|
||||
Function,+,cli_write,void,"Cli*, const uint8_t*, size_t"
|
||||
Function,+,cli_vcp_disable,void,CliVcp*
|
||||
Function,+,cli_vcp_enable,void,CliVcp*
|
||||
Function,+,composite_api_resolver_add,void,"CompositeApiResolver*, const ElfApiInterface*"
|
||||
Function,+,composite_api_resolver_alloc,CompositeApiResolver*,
|
||||
Function,+,composite_api_resolver_free,void,CompositeApiResolver*
|
||||
@@ -1301,6 +1301,7 @@ Function,+,furi_event_loop_subscribe_message_queue,void,"FuriEventLoop*, FuriMes
|
||||
Function,+,furi_event_loop_subscribe_mutex,void,"FuriEventLoop*, FuriMutex*, FuriEventLoopEvent, FuriEventLoopEventCallback, void*"
|
||||
Function,+,furi_event_loop_subscribe_semaphore,void,"FuriEventLoop*, FuriSemaphore*, FuriEventLoopEvent, FuriEventLoopEventCallback, void*"
|
||||
Function,+,furi_event_loop_subscribe_stream_buffer,void,"FuriEventLoop*, FuriStreamBuffer*, FuriEventLoopEvent, FuriEventLoopEventCallback, void*"
|
||||
Function,+,furi_event_loop_subscribe_thread_flags,void,"FuriEventLoop*, FuriEventLoopThreadFlagsCallback, void*"
|
||||
Function,+,furi_event_loop_tick_set,void,"FuriEventLoop*, uint32_t, FuriEventLoopTickCallback, void*"
|
||||
Function,+,furi_event_loop_timer_alloc,FuriEventLoopTimer*,"FuriEventLoop*, FuriEventLoopTimerCallback, FuriEventLoopTimerType, void*"
|
||||
Function,+,furi_event_loop_timer_free,void,FuriEventLoopTimer*
|
||||
@@ -1311,6 +1312,7 @@ Function,+,furi_event_loop_timer_restart,void,FuriEventLoopTimer*
|
||||
Function,+,furi_event_loop_timer_start,void,"FuriEventLoopTimer*, uint32_t"
|
||||
Function,+,furi_event_loop_timer_stop,void,FuriEventLoopTimer*
|
||||
Function,+,furi_event_loop_unsubscribe,void,"FuriEventLoop*, FuriEventLoopObject*"
|
||||
Function,+,furi_event_loop_unsubscribe_thread_flags,void,FuriEventLoop*
|
||||
Function,+,furi_get_tick,uint32_t,
|
||||
Function,+,furi_hal_adc_acquire,FuriHalAdcHandle*,
|
||||
Function,+,furi_hal_adc_configure,void,FuriHalAdcHandle*
|
||||
@@ -3048,14 +3050,14 @@ Function,+,pipe_bytes_available,size_t,PipeSide*
|
||||
Function,+,pipe_detach_from_event_loop,void,PipeSide*
|
||||
Function,+,pipe_free,void,PipeSide*
|
||||
Function,+,pipe_install_as_stdio,void,PipeSide*
|
||||
Function,+,pipe_receive,size_t,"PipeSide*, void*, size_t, FuriWait"
|
||||
Function,+,pipe_receive,size_t,"PipeSide*, void*, size_t"
|
||||
Function,+,pipe_role,PipeRole,PipeSide*
|
||||
Function,+,pipe_send,size_t,"PipeSide*, const void*, size_t, FuriWait"
|
||||
Function,+,pipe_send,size_t,"PipeSide*, const void*, size_t"
|
||||
Function,+,pipe_set_broken_callback,void,"PipeSide*, PipeSideBrokenCallback, FuriEventLoopEvent"
|
||||
Function,+,pipe_set_callback_context,void,"PipeSide*, void*"
|
||||
Function,+,pipe_set_data_arrived_callback,void,"PipeSide*, PipeSideDataArrivedCallback, FuriEventLoopEvent"
|
||||
Function,+,pipe_set_space_freed_callback,void,"PipeSide*, PipeSideSpaceFreedCallback, FuriEventLoopEvent"
|
||||
Function,+,pipe_set_stdout_timeout,void,"PipeSide*, FuriWait"
|
||||
Function,+,pipe_set_state_check_period,void,"PipeSide*, FuriWait"
|
||||
Function,+,pipe_spaces_available,size_t,PipeSide*
|
||||
Function,+,pipe_state,PipeState,PipeSide*
|
||||
Function,+,plugin_manager_alloc,PluginManager*,"const char*, uint32_t, const ElfApiInterface*"
|
||||
@@ -4237,7 +4239,6 @@ Variable,-,_sys_errlist,const char* const[],
|
||||
Variable,-,_sys_nerr,int,
|
||||
Variable,-,ble_profile_hid,const FuriHalBleProfileTemplate*,
|
||||
Variable,+,ble_profile_serial,const FuriHalBleProfileTemplate* const,
|
||||
Variable,+,cli_vcp,const CliSession,
|
||||
Variable,+,compress_config_heatshrink_default,const CompressConfigHeatshrink,
|
||||
Variable,+,firmware_api_interface,const ElfApiInterface* const,
|
||||
Variable,+,furi_hal_i2c_bus_external,FuriHalI2cBus,
|
||||
|
||||
|
@@ -23,6 +23,8 @@ typedef struct {
|
||||
uint16_t connection_handle;
|
||||
uint8_t adv_svc_uuid_len;
|
||||
uint8_t adv_svc_uuid[20];
|
||||
uint8_t mfg_data_len;
|
||||
uint8_t mfg_data[20];
|
||||
char* adv_name;
|
||||
} GapSvc;
|
||||
|
||||
@@ -216,11 +218,10 @@ BleEventFlowStatus ble_event_app_notification(void* pckt) {
|
||||
gap->service.connection_handle = event->Connection_Handle;
|
||||
|
||||
gap_verify_connection_parameters(gap);
|
||||
|
||||
// Save rssi for current connection
|
||||
fetch_rssi();
|
||||
// Start pairing by sending security request
|
||||
aci_gap_slave_security_req(event->Connection_Handle);
|
||||
if(gap->config->pairing_method != GapPairingNone) {
|
||||
// Start pairing by sending security request
|
||||
aci_gap_slave_security_req(event->Connection_Handle);
|
||||
}
|
||||
} break;
|
||||
|
||||
default:
|
||||
@@ -345,6 +346,14 @@ static void set_advertisment_service_uid(uint8_t* uid, uint8_t uid_len) {
|
||||
gap->service.adv_svc_uuid_len += uid_len;
|
||||
}
|
||||
|
||||
static void set_manufacturer_data(uint8_t* mfg_data, uint8_t mfg_data_len) {
|
||||
furi_check(mfg_data_len < sizeof(gap->service.mfg_data) - 2);
|
||||
gap->service.mfg_data[0] = mfg_data_len + 1;
|
||||
gap->service.mfg_data[1] = AD_TYPE_MANUFACTURER_SPECIFIC_DATA;
|
||||
memcpy(&gap->service.mfg_data[gap->service.mfg_data_len], mfg_data, mfg_data_len);
|
||||
gap->service.mfg_data_len += mfg_data_len;
|
||||
}
|
||||
|
||||
static void gap_init_svc(Gap* gap) {
|
||||
tBleStatus status;
|
||||
uint32_t srd_bd_addr[2];
|
||||
@@ -464,6 +473,11 @@ static void gap_advertise_start(GapState new_state) {
|
||||
FURI_LOG_D(TAG, "set_non_discoverable success");
|
||||
}
|
||||
}
|
||||
|
||||
if(gap->service.mfg_data_len > 0) {
|
||||
hci_le_set_scan_response_data(gap->service.mfg_data_len, gap->service.mfg_data);
|
||||
}
|
||||
|
||||
// Configure advertising
|
||||
status = aci_gap_set_discoverable(
|
||||
ADV_IND,
|
||||
@@ -577,11 +591,26 @@ bool gap_init(GapConfig* config, GapEventCallback on_event_cb, void* context) {
|
||||
gap->is_secure = false;
|
||||
gap->negotiation_round = 0;
|
||||
|
||||
uint8_t adv_service_uid[2];
|
||||
gap->service.adv_svc_uuid_len = 1;
|
||||
adv_service_uid[0] = gap->config->adv_service_uuid & 0xff;
|
||||
adv_service_uid[1] = gap->config->adv_service_uuid >> 8;
|
||||
set_advertisment_service_uid(adv_service_uid, sizeof(adv_service_uid));
|
||||
if(gap->config->mfg_data_len > 0) {
|
||||
// Offset by 2 for length + AD_TYPE_MANUFACTURER_SPECIFIC_DATA
|
||||
gap->service.mfg_data_len = 2;
|
||||
set_manufacturer_data(gap->config->mfg_data, gap->config->mfg_data_len);
|
||||
}
|
||||
|
||||
if(gap->config->adv_service.UUID_Type == UUID_TYPE_16) {
|
||||
uint8_t adv_service_uid[2];
|
||||
gap->service.adv_svc_uuid_len = 1;
|
||||
adv_service_uid[0] = gap->config->adv_service.Service_UUID_16 & 0xff;
|
||||
adv_service_uid[1] = gap->config->adv_service.Service_UUID_16 >> 8;
|
||||
set_advertisment_service_uid(adv_service_uid, sizeof(adv_service_uid));
|
||||
} else if(gap->config->adv_service.UUID_Type == UUID_TYPE_128) {
|
||||
gap->service.adv_svc_uuid_len = 1;
|
||||
set_advertisment_service_uid(
|
||||
gap->config->adv_service.Service_UUID_128,
|
||||
sizeof(gap->config->adv_service.Service_UUID_128));
|
||||
} else {
|
||||
furi_crash("Invalid UUID type");
|
||||
}
|
||||
|
||||
// Set callback
|
||||
gap->on_event_cb = on_event_cb;
|
||||
|
||||
@@ -69,7 +69,13 @@ typedef struct {
|
||||
} GapConnectionParamsRequest;
|
||||
|
||||
typedef struct {
|
||||
uint16_t adv_service_uuid;
|
||||
struct {
|
||||
uint8_t UUID_Type;
|
||||
uint16_t Service_UUID_16;
|
||||
uint8_t Service_UUID_128[16];
|
||||
} adv_service;
|
||||
uint8_t mfg_data[20];
|
||||
uint8_t mfg_data_len;
|
||||
uint16_t appearance_char;
|
||||
bool bonding_mode;
|
||||
GapPairing pairing_method;
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <services/battery_service.h>
|
||||
#include <services/serial_service.h>
|
||||
#include <furi.h>
|
||||
#include <ble/core/ble_defs.h>
|
||||
|
||||
typedef struct {
|
||||
FuriHalBleProfileBase base;
|
||||
@@ -47,7 +48,11 @@ static void ble_profile_serial_stop(FuriHalBleProfileBase* profile) {
|
||||
#define CONNECTION_INTERVAL_MAX (0x24)
|
||||
|
||||
static const GapConfig serial_template_config = {
|
||||
.adv_service_uuid = 0x3080,
|
||||
.adv_service =
|
||||
{
|
||||
.UUID_Type = UUID_TYPE_16,
|
||||
.Service_UUID_16 = 0x3080,
|
||||
},
|
||||
.appearance_char = 0x8600,
|
||||
.bonding_mode = true,
|
||||
.pairing_method = GapPairingPinCodeShow,
|
||||
@@ -71,7 +76,8 @@ static void
|
||||
config->adv_name,
|
||||
furi_hal_version_get_ble_local_device_name_ptr(),
|
||||
FURI_HAL_VERSION_DEVICE_NAME_LENGTH);
|
||||
config->adv_service_uuid |= furi_hal_version_get_hw_color();
|
||||
config->adv_service.UUID_Type = UUID_TYPE_16;
|
||||
config->adv_service.Service_UUID_16 |= furi_hal_version_get_hw_color();
|
||||
}
|
||||
|
||||
static const FuriHalBleProfileTemplate profile_callbacks = {
|
||||
|
||||
@@ -9,11 +9,21 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
CdcStateDisconnected,
|
||||
CdcStateConnected,
|
||||
} CdcState;
|
||||
|
||||
typedef enum {
|
||||
CdcCtrlLineDTR = (1 << 0),
|
||||
CdcCtrlLineRTS = (1 << 1),
|
||||
} CdcCtrlLine;
|
||||
|
||||
typedef struct {
|
||||
void (*tx_ep_callback)(void* context);
|
||||
void (*rx_ep_callback)(void* context);
|
||||
void (*state_callback)(void* context, uint8_t state);
|
||||
void (*ctrl_line_callback)(void* context, uint8_t state);
|
||||
void (*state_callback)(void* context, CdcState state);
|
||||
void (*ctrl_line_callback)(void* context, CdcCtrlLine ctrl_lines);
|
||||
void (*config_callback)(void* context, struct usb_cdc_line_coding* config);
|
||||
void (*break_callback)(void* context, uint16_t duration);
|
||||
} CdcCallbacks;
|
||||
|
||||
Reference in New Issue
Block a user