1
mirror of https://github.com/DarkFlippers/unleashed-firmware.git synced 2025-12-12 12:42:30 +04:00

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

This commit is contained in:
MX
2025-04-01 22:55:16 +03:00
12 changed files with 912 additions and 34 deletions

View File

@@ -6,9 +6,12 @@
#include <storage/storage.h> #include <storage/storage.h>
#include <applications/system/js_app/js_thread.h> #include <applications/system/js_app/js_thread.h>
#include <applications/system/js_app/js_value.h>
#include <stdint.h> #include <stdint.h>
#define TAG "JsUnitTests"
#define JS_SCRIPT_PATH(name) EXT_PATH("unit_tests/js/" name ".js") #define JS_SCRIPT_PATH(name) EXT_PATH("unit_tests/js/" name ".js")
typedef enum { typedef enum {
@@ -73,7 +76,311 @@ MU_TEST(js_test_storage) {
js_test_run(JS_SCRIPT_PATH("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_TEST_SUITE(test_js) {
MU_RUN_TEST(js_value_test);
MU_RUN_TEST(js_test_basic); MU_RUN_TEST(js_test_basic);
MU_RUN_TEST(js_test_math); MU_RUN_TEST(js_test_math);
MU_RUN_TEST(js_test_event_loop); MU_RUN_TEST(js_test_event_loop);

View File

@@ -396,6 +396,8 @@ void minunit_printf_warning(const char* format, ...);
return; \ return; \
} else { minunit_print_progress(); }) } else { minunit_print_progress(); })
//-V:mu_assert_string_eq:526, 547
#define mu_assert_string_eq(expected, result) \ #define mu_assert_string_eq(expected, result) \
MU__SAFE_BLOCK( \ MU__SAFE_BLOCK( \
const char* minunit_tmp_e = expected; const char* minunit_tmp_r = result; \ const char* minunit_tmp_e = expected; const char* minunit_tmp_r = result; \
@@ -416,6 +418,8 @@ void minunit_printf_warning(const char* format, ...);
return; \ return; \
} else { minunit_print_progress(); }) } else { minunit_print_progress(); })
//-V:mu_assert_mem_eq:526
#define mu_assert_mem_eq(expected, result, size) \ #define mu_assert_mem_eq(expected, result, size) \
MU__SAFE_BLOCK( \ MU__SAFE_BLOCK( \
const void* minunit_tmp_e = expected; const void* minunit_tmp_r = result; \ const void* minunit_tmp_e = expected; const void* minunit_tmp_r = result; \

View File

@@ -8,6 +8,7 @@
#include <rpc/rpc_i.h> #include <rpc/rpc_i.h>
#include <flipper.pb.h> #include <flipper.pb.h>
#include <applications/system/js_app/js_thread.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>( static constexpr auto unit_tests_api_table = sort(create_array_t<sym_entry>(
API_METHOD(resource_manifest_reader_alloc, ResourceManifestReader*, (Storage*)), 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*, JsThread*,
(const char* script_path, JsThreadCallback callback, void* context)), (const char* script_path, JsThreadCallback callback, void* context)),
API_METHOD(js_thread_stop, void, (JsThread * worker)), 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))); API_VARIABLE(PB_Main_msg, PB_Main_msg_t)));

View File

@@ -11,6 +11,7 @@ App(
"js_app.c", "js_app.c",
"js_modules.c", "js_modules.c",
"js_thread.c", "js_thread.c",
"js_value.c",
"plugin_api/app_api_table.cpp", "plugin_api/app_api_table.cpp",
"views/console_view.c", "views/console_view.c",
"modules/js_flipper.c", "modules/js_flipper.c",

View File

@@ -2,6 +2,7 @@
#include <stdint.h> #include <stdint.h>
#include "js_thread_i.h" #include "js_thread_i.h"
#include "js_value.h"
#include <flipper_application/flipper_application.h> #include <flipper_application/flipper_application.h>
#include <flipper_application/plugins/plugin_manager.h> #include <flipper_application/plugins/plugin_manager.h>
#include <flipper_application/plugins/composite_resolver.h> #include <flipper_application/plugins/composite_resolver.h>

View 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;
}

View 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

View File

@@ -10,12 +10,15 @@ let gui = require("gui");
GUI module has several submodules: GUI module has several submodules:
- @subpage js_gui__submenu — Displays a scrollable list of clickable textual entries - @subpage js_gui__byte_input — Keyboard-like hex input
- @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__dialog — Dialog with up to 3 options - @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 - @subpage js_gui__widget — Displays a combination of custom elements on one screen
--- ---
@@ -39,13 +42,13 @@ 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 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): are available (not all of which are unfortunately currently implemented in JS):
| View | Has JS adapter? | | View | Has JS adapter? |
|----------------------|------------------| |----------------------|-----------------------|
| `button_menu` | ❌ | | `button_menu` | ❌ |
| `button_panel` | ❌ | | `button_panel` | ❌ |
| `byte_input` | | | `byte_input` | |
| `dialog_ex` | ✅ (as `dialog`) | | `dialog_ex` | ✅ (as `dialog`) |
| `empty_screen` | ✅ | | `empty_screen` | ✅ |
| `file_browser` | | | `file_browser` | ✅ (as `file_picker`) |
| `loading` | ✅ | | `loading` | ✅ |
| `menu` | ❌ | | `menu` | ❌ |
| `number_input` | ❌ | | `number_input` | ❌ |
@@ -54,7 +57,7 @@ are available (not all of which are unfortunately currently implemented in JS):
| `text_box` | ✅ | | `text_box` | ✅ |
| `text_input` | ✅ | | `text_input` | ✅ |
| `variable_item_list` | ❌ | | `variable_item_list` | ❌ |
| `widget` | | | `widget` | |
In JS, each view has its own set of properties (or just "props"). The programmer In JS, each view has its own set of properties (or just "props"). The programmer
can manipulate these properties in two ways: can manipulate these properties in two ways:

View File

@@ -380,7 +380,10 @@ bool ble_profile_hid_mouse_scroll(FuriHalBleProfileBase* profile, int8_t delta)
#define CONNECTION_INTERVAL_MAX (0x24) #define CONNECTION_INTERVAL_MAX (0x24)
static GapConfig template_config = { 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, .appearance_char = GAP_APPEARANCE_KEYBOARD,
.bonding_mode = true, .bonding_mode = true,
.pairing_method = GapPairingPinCodeVerifyYesNo, .pairing_method = GapPairingPinCodeVerifyYesNo,

View File

@@ -23,6 +23,8 @@ typedef struct {
uint16_t connection_handle; uint16_t connection_handle;
uint8_t adv_svc_uuid_len; uint8_t adv_svc_uuid_len;
uint8_t adv_svc_uuid[20]; uint8_t adv_svc_uuid[20];
uint8_t mfg_data_len;
uint8_t mfg_data[20];
char* adv_name; char* adv_name;
} GapSvc; } GapSvc;
@@ -216,11 +218,12 @@ BleEventFlowStatus ble_event_app_notification(void* pckt) {
gap->service.connection_handle = event->Connection_Handle; gap->service.connection_handle = event->Connection_Handle;
gap_verify_connection_parameters(gap); gap_verify_connection_parameters(gap);
// Save rssi for current connection // Save rssi for current connection
fetch_rssi(); fetch_rssi();
if(gap->config->pairing_method != GapPairingNone) {
// Start pairing by sending security request // Start pairing by sending security request
aci_gap_slave_security_req(event->Connection_Handle); aci_gap_slave_security_req(event->Connection_Handle);
}
} break; } break;
default: default:
@@ -345,6 +348,14 @@ static void set_advertisment_service_uid(uint8_t* uid, uint8_t uid_len) {
gap->service.adv_svc_uuid_len += 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) { static void gap_init_svc(Gap* gap) {
tBleStatus status; tBleStatus status;
uint32_t srd_bd_addr[2]; uint32_t srd_bd_addr[2];
@@ -464,6 +475,11 @@ static void gap_advertise_start(GapState new_state) {
FURI_LOG_D(TAG, "set_non_discoverable success"); 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 // Configure advertising
status = aci_gap_set_discoverable( status = aci_gap_set_discoverable(
ADV_IND, ADV_IND,
@@ -577,11 +593,26 @@ bool gap_init(GapConfig* config, GapEventCallback on_event_cb, void* context) {
gap->is_secure = false; gap->is_secure = false;
gap->negotiation_round = 0; gap->negotiation_round = 0;
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]; uint8_t adv_service_uid[2];
gap->service.adv_svc_uuid_len = 1; gap->service.adv_svc_uuid_len = 1;
adv_service_uid[0] = gap->config->adv_service_uuid & 0xff; adv_service_uid[0] = gap->config->adv_service.Service_UUID_16 & 0xff;
adv_service_uid[1] = gap->config->adv_service_uuid >> 8; adv_service_uid[1] = gap->config->adv_service.Service_UUID_16 >> 8;
set_advertisment_service_uid(adv_service_uid, sizeof(adv_service_uid)); 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 // Set callback
gap->on_event_cb = on_event_cb; gap->on_event_cb = on_event_cb;

View File

@@ -69,7 +69,13 @@ typedef struct {
} GapConnectionParamsRequest; } GapConnectionParamsRequest;
typedef struct { 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; uint16_t appearance_char;
bool bonding_mode; bool bonding_mode;
GapPairing pairing_method; GapPairing pairing_method;

View File

@@ -6,6 +6,7 @@
#include <services/battery_service.h> #include <services/battery_service.h>
#include <services/serial_service.h> #include <services/serial_service.h>
#include <furi.h> #include <furi.h>
#include <ble/core/ble_defs.h>
typedef struct { typedef struct {
FuriHalBleProfileBase base; FuriHalBleProfileBase base;
@@ -47,7 +48,11 @@ static void ble_profile_serial_stop(FuriHalBleProfileBase* profile) {
#define CONNECTION_INTERVAL_MAX (0x24) #define CONNECTION_INTERVAL_MAX (0x24)
static const GapConfig serial_template_config = { static const GapConfig serial_template_config = {
.adv_service_uuid = 0x3080, .adv_service =
{
.UUID_Type = UUID_TYPE_16,
.Service_UUID_16 = 0x3080,
},
.appearance_char = 0x8600, .appearance_char = 0x8600,
.bonding_mode = true, .bonding_mode = true,
.pairing_method = GapPairingPinCodeShow, .pairing_method = GapPairingPinCodeShow,
@@ -71,7 +76,8 @@ static void
config->adv_name, config->adv_name,
furi_hal_version_get_ble_local_device_name_ptr(), furi_hal_version_get_ble_local_device_name_ptr(),
FURI_HAL_VERSION_DEVICE_NAME_LENGTH); 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 = { static const FuriHalBleProfileTemplate profile_callbacks = {