mirror of
https://github.com/DarkFlippers/unleashed-firmware.git
synced 2024-12-22 12:51:39 +03:00
Merge remote-tracking branch 'OFW/dev' into dev
This commit is contained in:
parent
2f102e61a9
commit
4b9b1769f7
@ -221,6 +221,14 @@ App(
|
||||
requires=["unit_tests"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="test_js",
|
||||
sources=["tests/common/*.c", "tests/js/*.c"],
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="get_api",
|
||||
requires=["unit_tests", "js_app"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="test_strint",
|
||||
sources=["tests/common/*.c", "tests/strint/*.c"],
|
||||
|
@ -0,0 +1,4 @@
|
||||
let tests = require("tests");
|
||||
|
||||
tests.assert_eq(1337, 1337);
|
||||
tests.assert_eq("hello", "hello");
|
@ -0,0 +1,30 @@
|
||||
let tests = require("tests");
|
||||
let event_loop = require("event_loop");
|
||||
|
||||
let ext = {
|
||||
i: 0,
|
||||
received: false,
|
||||
};
|
||||
|
||||
let queue = event_loop.queue(16);
|
||||
|
||||
event_loop.subscribe(queue.input, function (_, item, tests, ext) {
|
||||
tests.assert_eq(123, item);
|
||||
ext.received = true;
|
||||
}, tests, ext);
|
||||
|
||||
event_loop.subscribe(event_loop.timer("periodic", 1), function (_, _item, queue, counter, ext) {
|
||||
ext.i++;
|
||||
queue.send(123);
|
||||
if (counter === 10)
|
||||
event_loop.stop();
|
||||
return [queue, counter + 1, ext];
|
||||
}, queue, 1, ext);
|
||||
|
||||
event_loop.subscribe(event_loop.timer("oneshot", 1000), function (_, _item, tests) {
|
||||
tests.fail("event loop was not stopped");
|
||||
}, tests);
|
||||
|
||||
event_loop.run();
|
||||
tests.assert_eq(10, ext.i);
|
||||
tests.assert_eq(true, ext.received);
|
@ -0,0 +1,34 @@
|
||||
let tests = require("tests");
|
||||
let math = require("math");
|
||||
|
||||
// math.EPSILON on Flipper Zero is 2.22044604925031308085e-16
|
||||
|
||||
// basics
|
||||
tests.assert_float_close(5, math.abs(-5), math.EPSILON);
|
||||
tests.assert_float_close(0.5, math.abs(-0.5), math.EPSILON);
|
||||
tests.assert_float_close(5, math.abs(5), math.EPSILON);
|
||||
tests.assert_float_close(0.5, math.abs(0.5), math.EPSILON);
|
||||
tests.assert_float_close(3, math.cbrt(27), math.EPSILON);
|
||||
tests.assert_float_close(6, math.ceil(5.3), math.EPSILON);
|
||||
tests.assert_float_close(31, math.clz32(1), math.EPSILON);
|
||||
tests.assert_float_close(5, math.floor(5.7), math.EPSILON);
|
||||
tests.assert_float_close(5, math.max(3, 5), math.EPSILON);
|
||||
tests.assert_float_close(3, math.min(3, 5), math.EPSILON);
|
||||
tests.assert_float_close(-1, math.sign(-5), math.EPSILON);
|
||||
tests.assert_float_close(5, math.trunc(5.7), math.EPSILON);
|
||||
|
||||
// trig
|
||||
tests.assert_float_close(1.0471975511965976, math.acos(0.5), math.EPSILON);
|
||||
tests.assert_float_close(1.3169578969248166, math.acosh(2), math.EPSILON);
|
||||
tests.assert_float_close(0.5235987755982988, math.asin(0.5), math.EPSILON);
|
||||
tests.assert_float_close(1.4436354751788103, math.asinh(2), math.EPSILON);
|
||||
tests.assert_float_close(0.7853981633974483, math.atan(1), math.EPSILON);
|
||||
tests.assert_float_close(0.7853981633974483, math.atan2(1, 1), math.EPSILON);
|
||||
tests.assert_float_close(0.5493061443340549, math.atanh(0.5), math.EPSILON);
|
||||
tests.assert_float_close(-1, math.cos(math.PI), math.EPSILON * 18); // Error 3.77475828372553223744e-15
|
||||
tests.assert_float_close(1, math.sin(math.PI / 2), math.EPSILON * 4.5); // Error 9.99200722162640886381e-16
|
||||
|
||||
// powers
|
||||
tests.assert_float_close(5, math.sqrt(25), math.EPSILON);
|
||||
tests.assert_float_close(8, math.pow(2, 3), math.EPSILON);
|
||||
tests.assert_float_close(2.718281828459045, math.exp(1), math.EPSILON * 2); // Error 4.44089209850062616169e-16
|
136
applications/debug/unit_tests/resources/unit_tests/js/storage.js
Normal file
136
applications/debug/unit_tests/resources/unit_tests/js/storage.js
Normal file
@ -0,0 +1,136 @@
|
||||
let storage = require("storage");
|
||||
let tests = require("tests");
|
||||
|
||||
let baseDir = "/ext/.tmp/unit_tests";
|
||||
|
||||
tests.assert_eq(true, storage.rmrf(baseDir));
|
||||
tests.assert_eq(true, storage.makeDirectory(baseDir));
|
||||
|
||||
// write
|
||||
let file = storage.openFile(baseDir + "/helloworld", "w", "create_always");
|
||||
tests.assert_eq(true, !!file);
|
||||
tests.assert_eq(true, file.isOpen());
|
||||
tests.assert_eq(13, file.write("Hello, World!"));
|
||||
tests.assert_eq(true, file.close());
|
||||
tests.assert_eq(false, file.isOpen());
|
||||
|
||||
// read
|
||||
file = storage.openFile(baseDir + "/helloworld", "r", "open_existing");
|
||||
tests.assert_eq(true, !!file);
|
||||
tests.assert_eq(true, file.isOpen());
|
||||
tests.assert_eq(13, file.size());
|
||||
tests.assert_eq("Hello, World!", file.read("ascii", 128));
|
||||
tests.assert_eq(true, file.close());
|
||||
tests.assert_eq(false, file.isOpen());
|
||||
|
||||
// seek
|
||||
file = storage.openFile(baseDir + "/helloworld", "r", "open_existing");
|
||||
tests.assert_eq(true, !!file);
|
||||
tests.assert_eq(true, file.isOpen());
|
||||
tests.assert_eq(13, file.size());
|
||||
tests.assert_eq("Hello, World!", file.read("ascii", 128));
|
||||
tests.assert_eq(true, file.seekAbsolute(1));
|
||||
tests.assert_eq(true, file.seekRelative(2));
|
||||
tests.assert_eq(3, file.tell());
|
||||
tests.assert_eq(false, file.eof());
|
||||
tests.assert_eq("lo, World!", file.read("ascii", 128));
|
||||
tests.assert_eq(true, file.eof());
|
||||
tests.assert_eq(true, file.close());
|
||||
tests.assert_eq(false, file.isOpen());
|
||||
|
||||
// byte-level copy
|
||||
let src = storage.openFile(baseDir + "/helloworld", "r", "open_existing");
|
||||
let dst = storage.openFile(baseDir + "/helloworld2", "rw", "create_always");
|
||||
tests.assert_eq(true, !!src);
|
||||
tests.assert_eq(true, src.isOpen());
|
||||
tests.assert_eq(true, !!dst);
|
||||
tests.assert_eq(true, dst.isOpen());
|
||||
tests.assert_eq(true, src.copyTo(dst, 10));
|
||||
tests.assert_eq(true, dst.seekAbsolute(0));
|
||||
tests.assert_eq("Hello, Wor", dst.read("ascii", 128));
|
||||
tests.assert_eq(true, src.copyTo(dst, 3));
|
||||
tests.assert_eq(true, dst.seekAbsolute(0));
|
||||
tests.assert_eq("Hello, World!", dst.read("ascii", 128));
|
||||
tests.assert_eq(true, src.eof());
|
||||
tests.assert_eq(true, src.close());
|
||||
tests.assert_eq(false, src.isOpen());
|
||||
tests.assert_eq(true, dst.eof());
|
||||
tests.assert_eq(true, dst.close());
|
||||
tests.assert_eq(false, dst.isOpen());
|
||||
|
||||
// truncate
|
||||
tests.assert_eq(true, storage.copy(baseDir + "/helloworld", baseDir + "/helloworld2"));
|
||||
file = storage.openFile(baseDir + "/helloworld2", "w", "open_existing");
|
||||
tests.assert_eq(true, !!file);
|
||||
tests.assert_eq(true, file.seekAbsolute(5));
|
||||
tests.assert_eq(true, file.truncate());
|
||||
tests.assert_eq(true, file.close());
|
||||
file = storage.openFile(baseDir + "/helloworld2", "r", "open_existing");
|
||||
tests.assert_eq(true, !!file);
|
||||
tests.assert_eq("Hello", file.read("ascii", 128));
|
||||
tests.assert_eq(true, file.close());
|
||||
|
||||
// existence
|
||||
tests.assert_eq(true, storage.fileExists(baseDir + "/helloworld"));
|
||||
tests.assert_eq(true, storage.fileExists(baseDir + "/helloworld2"));
|
||||
tests.assert_eq(false, storage.fileExists(baseDir + "/sus_amogus_123"));
|
||||
tests.assert_eq(false, storage.directoryExists(baseDir + "/helloworld"));
|
||||
tests.assert_eq(false, storage.fileExists(baseDir));
|
||||
tests.assert_eq(true, storage.directoryExists(baseDir));
|
||||
tests.assert_eq(true, storage.fileOrDirExists(baseDir));
|
||||
tests.assert_eq(true, storage.remove(baseDir + "/helloworld2"));
|
||||
tests.assert_eq(false, storage.fileExists(baseDir + "/helloworld2"));
|
||||
|
||||
// stat
|
||||
let stat = storage.stat(baseDir + "/helloworld");
|
||||
tests.assert_eq(true, !!stat);
|
||||
tests.assert_eq(baseDir + "/helloworld", stat.path);
|
||||
tests.assert_eq(false, stat.isDirectory);
|
||||
tests.assert_eq(13, stat.size);
|
||||
|
||||
// rename
|
||||
tests.assert_eq(true, storage.fileExists(baseDir + "/helloworld"));
|
||||
tests.assert_eq(false, storage.fileExists(baseDir + "/helloworld123"));
|
||||
tests.assert_eq(true, storage.rename(baseDir + "/helloworld", baseDir + "/helloworld123"));
|
||||
tests.assert_eq(false, storage.fileExists(baseDir + "/helloworld"));
|
||||
tests.assert_eq(true, storage.fileExists(baseDir + "/helloworld123"));
|
||||
tests.assert_eq(true, storage.rename(baseDir + "/helloworld123", baseDir + "/helloworld"));
|
||||
tests.assert_eq(true, storage.fileExists(baseDir + "/helloworld"));
|
||||
tests.assert_eq(false, storage.fileExists(baseDir + "/helloworld123"));
|
||||
|
||||
// copy
|
||||
tests.assert_eq(true, storage.fileExists(baseDir + "/helloworld"));
|
||||
tests.assert_eq(false, storage.fileExists(baseDir + "/helloworld123"));
|
||||
tests.assert_eq(true, storage.copy(baseDir + "/helloworld", baseDir + "/helloworld123"));
|
||||
tests.assert_eq(true, storage.fileExists(baseDir + "/helloworld"));
|
||||
tests.assert_eq(true, storage.fileExists(baseDir + "/helloworld123"));
|
||||
|
||||
// next avail
|
||||
tests.assert_eq("helloworld1", storage.nextAvailableFilename(baseDir, "helloworld", "", 20));
|
||||
|
||||
// fs info
|
||||
let fsInfo = storage.fsInfo("/ext");
|
||||
tests.assert_eq(true, !!fsInfo);
|
||||
tests.assert_eq(true, fsInfo.freeSpace < fsInfo.totalSpace); // idk \(-_-)/
|
||||
fsInfo = storage.fsInfo("/int");
|
||||
tests.assert_eq(true, !!fsInfo);
|
||||
tests.assert_eq(true, fsInfo.freeSpace < fsInfo.totalSpace);
|
||||
|
||||
// path operations
|
||||
tests.assert_eq(true, storage.arePathsEqual("/ext/test", "/ext/Test"));
|
||||
tests.assert_eq(false, storage.arePathsEqual("/ext/test", "/ext/Testttt"));
|
||||
tests.assert_eq(true, storage.isSubpathOf("/ext/test", "/ext/test/sub"));
|
||||
tests.assert_eq(false, storage.isSubpathOf("/ext/test/sub", "/ext/test"));
|
||||
|
||||
// dir
|
||||
let entries = storage.readDirectory(baseDir);
|
||||
tests.assert_eq(true, !!entries);
|
||||
// FIXME: (-nofl) this test suite assumes that files are listed by
|
||||
// `readDirectory` in the exact order that they were created, which is not
|
||||
// something that is actually guaranteed.
|
||||
// Possible solution: sort and compare the array.
|
||||
tests.assert_eq("helloworld", entries[0].path);
|
||||
tests.assert_eq("helloworld123", entries[1].path);
|
||||
|
||||
tests.assert_eq(true, storage.rmrf(baseDir));
|
||||
tests.assert_eq(true, storage.makeDirectory(baseDir));
|
88
applications/debug/unit_tests/tests/js/js_test.c
Normal file
88
applications/debug/unit_tests/tests/js/js_test.c
Normal file
@ -0,0 +1,88 @@
|
||||
#include "../test.h" // IWYU pragma: keep
|
||||
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <furi_hal_random.h>
|
||||
|
||||
#include <storage/storage.h>
|
||||
#include <applications/system/js_app/js_thread.h>
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define JS_SCRIPT_PATH(name) EXT_PATH("unit_tests/js/" name ".js")
|
||||
|
||||
typedef enum {
|
||||
JsTestsFinished = 1,
|
||||
JsTestsError = 2,
|
||||
} JsTestFlag;
|
||||
|
||||
typedef struct {
|
||||
FuriEventFlag* event_flags;
|
||||
FuriString* error_string;
|
||||
} JsTestCallbackContext;
|
||||
|
||||
static void js_test_callback(JsThreadEvent event, const char* msg, void* param) {
|
||||
JsTestCallbackContext* context = param;
|
||||
if(event == JsThreadEventPrint) {
|
||||
FURI_LOG_I("js_test", "%s", msg);
|
||||
} else if(event == JsThreadEventError || event == JsThreadEventErrorTrace) {
|
||||
context->error_string = furi_string_alloc_set_str(msg);
|
||||
furi_event_flag_set(context->event_flags, JsTestsFinished | JsTestsError);
|
||||
} else if(event == JsThreadEventDone) {
|
||||
furi_event_flag_set(context->event_flags, JsTestsFinished);
|
||||
}
|
||||
}
|
||||
|
||||
static void js_test_run(const char* script_path) {
|
||||
JsTestCallbackContext* context = malloc(sizeof(JsTestCallbackContext));
|
||||
context->event_flags = furi_event_flag_alloc();
|
||||
|
||||
JsThread* thread = js_thread_run(script_path, js_test_callback, context);
|
||||
uint32_t flags = furi_event_flag_wait(
|
||||
context->event_flags, JsTestsFinished, FuriFlagWaitAny, FuriWaitForever);
|
||||
if(flags & FuriFlagError) {
|
||||
// getting the flags themselves should not fail
|
||||
furi_crash();
|
||||
}
|
||||
|
||||
FuriString* error_string = context->error_string;
|
||||
|
||||
js_thread_stop(thread);
|
||||
furi_event_flag_free(context->event_flags);
|
||||
free(context);
|
||||
|
||||
if(flags & JsTestsError) {
|
||||
// memory leak: not freeing the FuriString if the tests fail,
|
||||
// because mu_fail executes a return
|
||||
//
|
||||
// who cares tho?
|
||||
mu_fail(furi_string_get_cstr(error_string));
|
||||
}
|
||||
}
|
||||
|
||||
MU_TEST(js_test_basic) {
|
||||
js_test_run(JS_SCRIPT_PATH("basic"));
|
||||
}
|
||||
MU_TEST(js_test_math) {
|
||||
js_test_run(JS_SCRIPT_PATH("math"));
|
||||
}
|
||||
MU_TEST(js_test_event_loop) {
|
||||
js_test_run(JS_SCRIPT_PATH("event_loop"));
|
||||
}
|
||||
MU_TEST(js_test_storage) {
|
||||
js_test_run(JS_SCRIPT_PATH("storage"));
|
||||
}
|
||||
|
||||
MU_TEST_SUITE(test_js) {
|
||||
MU_RUN_TEST(js_test_basic);
|
||||
MU_RUN_TEST(js_test_math);
|
||||
MU_RUN_TEST(js_test_event_loop);
|
||||
MU_RUN_TEST(js_test_storage);
|
||||
}
|
||||
|
||||
int run_minunit_test_js(void) {
|
||||
MU_RUN_SUITE(test_js);
|
||||
return MU_EXIT_CODE;
|
||||
}
|
||||
|
||||
TEST_API_DEFINE(run_minunit_test_js)
|
@ -31,7 +31,7 @@ extern "C" {
|
||||
#include <Windows.h>
|
||||
#if defined(_MSC_VER) && _MSC_VER < 1900
|
||||
#define snprintf _snprintf
|
||||
#define __func__ __FUNCTION__
|
||||
#define __func__ __FUNCTION__ //-V1059
|
||||
#endif
|
||||
|
||||
#elif defined(__unix__) || defined(__unix) || defined(unix) || \
|
||||
@ -56,7 +56,7 @@ extern "C" {
|
||||
#endif
|
||||
|
||||
#if __GNUC__ >= 5 && !defined(__STDC_VERSION__)
|
||||
#define __func__ __extension__ __FUNCTION__
|
||||
#define __func__ __extension__ __FUNCTION__ //-V1059
|
||||
#endif
|
||||
|
||||
#else
|
||||
@ -102,6 +102,7 @@ void minunit_printf_warning(const char* format, ...);
|
||||
MU__SAFE_BLOCK(minunit_setup = setup_fun; minunit_teardown = teardown_fun;)
|
||||
|
||||
/* Test runner */
|
||||
//-V:MU_RUN_TEST:550
|
||||
#define MU_RUN_TEST(test) \
|
||||
MU__SAFE_BLOCK( \
|
||||
if(minunit_real_timer == 0 && minunit_proc_timer == 0) { \
|
||||
|
@ -7,7 +7,7 @@
|
||||
|
||||
#include <rpc/rpc_i.h>
|
||||
#include <flipper.pb.h>
|
||||
#include <core/event_loop.h>
|
||||
#include <applications/system/js_app/js_thread.h>
|
||||
|
||||
static constexpr auto unit_tests_api_table = sort(create_array_t<sym_entry>(
|
||||
API_METHOD(resource_manifest_reader_alloc, ResourceManifestReader*, (Storage*)),
|
||||
@ -33,13 +33,9 @@ static constexpr auto unit_tests_api_table = sort(create_array_t<sym_entry>(
|
||||
xQueueGenericSend,
|
||||
BaseType_t,
|
||||
(QueueHandle_t, const void* const, TickType_t, const BaseType_t)),
|
||||
API_METHOD(furi_event_loop_alloc, FuriEventLoop*, (void)),
|
||||
API_METHOD(furi_event_loop_free, void, (FuriEventLoop*)),
|
||||
API_METHOD(
|
||||
furi_event_loop_subscribe_message_queue,
|
||||
void,
|
||||
(FuriEventLoop*, FuriMessageQueue*, FuriEventLoopEvent, FuriEventLoopEventCallback, void*)),
|
||||
API_METHOD(furi_event_loop_unsubscribe, void, (FuriEventLoop*, FuriEventLoopObject*)),
|
||||
API_METHOD(furi_event_loop_run, void, (FuriEventLoop*)),
|
||||
API_METHOD(furi_event_loop_stop, void, (FuriEventLoop*)),
|
||||
js_thread_run,
|
||||
JsThread*,
|
||||
(const char* script_path, JsThreadCallback callback, void* context)),
|
||||
API_METHOD(js_thread_stop, void, (JsThread * worker)),
|
||||
API_VARIABLE(PB_Main_msg, PB_Main_msg_t)));
|
||||
|
@ -272,6 +272,15 @@ App(
|
||||
sources=["plugins/supported_cards/skylanders.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="hworld_parser",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="hworld_plugin_ep",
|
||||
targets=["f7"],
|
||||
requires=["nfc"],
|
||||
sources=["plugins/supported_cards/hworld.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="nfc_cli",
|
||||
targets=["f7"],
|
||||
|
243
applications/main/nfc/plugins/supported_cards/hworld.c
Normal file
243
applications/main/nfc/plugins/supported_cards/hworld.c
Normal file
@ -0,0 +1,243 @@
|
||||
// Flipper Zero parser for H World Hotel Key Cards
|
||||
// H World operates around 10,000 hotels, most of which in mainland China
|
||||
// Reverse engineering and parser written by @Torron (Github: @zinongli)
|
||||
#include "nfc_supported_card_plugin.h"
|
||||
#include <flipper_application.h>
|
||||
#include <nfc/protocols/mf_classic/mf_classic_poller_sync.h>
|
||||
#include <bit_lib.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define TAG "H World"
|
||||
#define ROOM_SECTOR 1
|
||||
#define VIP_SECTOR 5
|
||||
#define ROOM_SECTOR_KEY_BLOCK 7
|
||||
#define VIP_SECTOR_KEY_BLOCK 23
|
||||
#define ACCESS_INFO_BLOCK 5
|
||||
#define ROOM_NUM_DECIMAL_BLOCK 6
|
||||
#define H_WORLD_YEAR_OFFSET 2000
|
||||
|
||||
typedef struct {
|
||||
uint64_t a;
|
||||
uint64_t b;
|
||||
} MfClassicKeyPair;
|
||||
|
||||
static MfClassicKeyPair hworld_standard_keys[] = {
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 000
|
||||
{.a = 0x543071543071, .b = 0x5F01015F0101}, // 001
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 002
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 003
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 004
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 005
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 006
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 007
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 008
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 009
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 010
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 011
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 012
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 013
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 014
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 015
|
||||
};
|
||||
|
||||
static MfClassicKeyPair hworld_vip_keys[] = {
|
||||
{.a = 0x000000000000, .b = 0xFFFFFFFFFFFF}, // 000
|
||||
{.a = 0x543071543071, .b = 0x5F01015F0101}, // 001
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 002
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 003
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 004
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0x200510241234}, // 005
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0x200510241234}, // 006
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0x200510241234}, // 007
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0x200510241234}, // 008
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0x200510241234}, // 009
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0x200510241234}, // 010
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0x200510241234}, // 011
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0x200510241234}, // 012
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 013
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 014
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 015
|
||||
};
|
||||
|
||||
static bool hworld_verify(Nfc* nfc) {
|
||||
bool verified = false;
|
||||
|
||||
do {
|
||||
const uint8_t block_num = mf_classic_get_first_block_num_of_sector(ROOM_SECTOR);
|
||||
|
||||
MfClassicKey standard_key = {0};
|
||||
bit_lib_num_to_bytes_be(
|
||||
hworld_standard_keys[ROOM_SECTOR].a, COUNT_OF(standard_key.data), standard_key.data);
|
||||
|
||||
MfClassicAuthContext auth_context;
|
||||
MfClassicError standard_error = mf_classic_poller_sync_auth(
|
||||
nfc, block_num, &standard_key, MfClassicKeyTypeA, &auth_context);
|
||||
|
||||
if(standard_error != MfClassicErrorNone) {
|
||||
FURI_LOG_D(TAG, "Failed static key check for block %u", block_num);
|
||||
break;
|
||||
}
|
||||
|
||||
MfClassicKey vip_key = {0};
|
||||
bit_lib_num_to_bytes_be(
|
||||
hworld_vip_keys[VIP_SECTOR].b, COUNT_OF(vip_key.data), vip_key.data);
|
||||
|
||||
MfClassicError vip_error = mf_classic_poller_sync_auth(
|
||||
nfc, block_num, &vip_key, MfClassicKeyTypeB, &auth_context);
|
||||
|
||||
if(vip_error == MfClassicErrorNone) {
|
||||
FURI_LOG_D(TAG, "VIP card detected");
|
||||
} else {
|
||||
FURI_LOG_D(TAG, "Standard card detected");
|
||||
}
|
||||
|
||||
verified = true;
|
||||
} while(false);
|
||||
|
||||
return verified;
|
||||
}
|
||||
|
||||
static bool hworld_read(Nfc* nfc, NfcDevice* device) {
|
||||
furi_assert(nfc);
|
||||
furi_assert(device);
|
||||
|
||||
bool is_read = false;
|
||||
|
||||
MfClassicData* data = mf_classic_alloc();
|
||||
nfc_device_copy_data(device, NfcProtocolMfClassic, data);
|
||||
|
||||
do {
|
||||
MfClassicType type = MfClassicType1k;
|
||||
MfClassicError standard_error = mf_classic_poller_sync_detect_type(nfc, &type);
|
||||
MfClassicError vip_error = MfClassicErrorNotPresent;
|
||||
if(standard_error != MfClassicErrorNone) break;
|
||||
data->type = type;
|
||||
|
||||
MfClassicDeviceKeys standard_keys = {};
|
||||
for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) {
|
||||
bit_lib_num_to_bytes_be(
|
||||
hworld_standard_keys[i].a, sizeof(MfClassicKey), standard_keys.key_a[i].data);
|
||||
FURI_BIT_SET(standard_keys.key_a_mask, i);
|
||||
bit_lib_num_to_bytes_be(
|
||||
hworld_standard_keys[i].b, sizeof(MfClassicKey), standard_keys.key_b[i].data);
|
||||
FURI_BIT_SET(standard_keys.key_b_mask, i);
|
||||
}
|
||||
|
||||
standard_error = mf_classic_poller_sync_read(nfc, &standard_keys, data);
|
||||
if(standard_error == MfClassicErrorNone) {
|
||||
FURI_LOG_I(TAG, "Standard card successfully read");
|
||||
} else {
|
||||
MfClassicDeviceKeys vip_keys = {};
|
||||
for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) {
|
||||
bit_lib_num_to_bytes_be(
|
||||
hworld_vip_keys[i].a, sizeof(MfClassicKey), vip_keys.key_a[i].data);
|
||||
FURI_BIT_SET(vip_keys.key_a_mask, i);
|
||||
bit_lib_num_to_bytes_be(
|
||||
hworld_vip_keys[i].b, sizeof(MfClassicKey), vip_keys.key_b[i].data);
|
||||
FURI_BIT_SET(vip_keys.key_b_mask, i);
|
||||
}
|
||||
|
||||
vip_error = mf_classic_poller_sync_read(nfc, &vip_keys, data);
|
||||
|
||||
if(vip_error == MfClassicErrorNone) {
|
||||
FURI_LOG_I(TAG, "VIP card successfully read");
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
nfc_device_set_data(device, NfcProtocolMfClassic, data);
|
||||
|
||||
is_read = (standard_error == MfClassicErrorNone) | (vip_error == MfClassicErrorNone);
|
||||
} while(false);
|
||||
|
||||
mf_classic_free(data);
|
||||
|
||||
return is_read;
|
||||
}
|
||||
|
||||
bool hworld_parse(const NfcDevice* device, FuriString* parsed_data) {
|
||||
furi_assert(device);
|
||||
const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic);
|
||||
bool parsed = false;
|
||||
|
||||
do {
|
||||
// Check card type
|
||||
if(data->type != MfClassicType1k) break;
|
||||
|
||||
// Check static key for verificaiton
|
||||
const uint8_t* data_room_sec_key_a_ptr = &data->block[ROOM_SECTOR_KEY_BLOCK].data[0];
|
||||
const uint8_t* data_room_sec_key_b_ptr = &data->block[ROOM_SECTOR_KEY_BLOCK].data[10];
|
||||
uint64_t data_room_sec_key_a = bit_lib_get_bits_64(data_room_sec_key_a_ptr, 0, 48);
|
||||
uint64_t data_room_sec_key_b = bit_lib_get_bits_64(data_room_sec_key_b_ptr, 0, 48);
|
||||
if((data_room_sec_key_a != hworld_standard_keys[ROOM_SECTOR].a) |
|
||||
(data_room_sec_key_b != hworld_standard_keys[ROOM_SECTOR].b))
|
||||
break;
|
||||
|
||||
// Check whether this card is VIP
|
||||
const uint8_t* data_vip_sec_key_b_ptr = &data->block[VIP_SECTOR_KEY_BLOCK].data[10];
|
||||
uint64_t data_vip_sec_key_b = bit_lib_get_bits_64(data_vip_sec_key_b_ptr, 0, 48);
|
||||
bool is_hworld_vip = (data_vip_sec_key_b == hworld_vip_keys[VIP_SECTOR].b);
|
||||
uint8_t room_floor = data->block[ACCESS_INFO_BLOCK].data[13];
|
||||
uint8_t room_num = data->block[ACCESS_INFO_BLOCK].data[14];
|
||||
|
||||
// Check in date & time
|
||||
uint16_t check_in_year = data->block[ACCESS_INFO_BLOCK].data[2] + H_WORLD_YEAR_OFFSET;
|
||||
uint8_t check_in_month = data->block[ACCESS_INFO_BLOCK].data[3];
|
||||
uint8_t check_in_day = data->block[ACCESS_INFO_BLOCK].data[4];
|
||||
uint8_t check_in_hour = data->block[ACCESS_INFO_BLOCK].data[5];
|
||||
uint8_t check_in_minute = data->block[ACCESS_INFO_BLOCK].data[6];
|
||||
|
||||
// Expire date & time
|
||||
uint16_t expire_year = data->block[ACCESS_INFO_BLOCK].data[7] + H_WORLD_YEAR_OFFSET;
|
||||
uint8_t expire_month = data->block[ACCESS_INFO_BLOCK].data[8];
|
||||
uint8_t expire_day = data->block[ACCESS_INFO_BLOCK].data[9];
|
||||
uint8_t expire_hour = data->block[ACCESS_INFO_BLOCK].data[10];
|
||||
uint8_t expire_minute = data->block[ACCESS_INFO_BLOCK].data[11];
|
||||
|
||||
furi_string_cat_printf(parsed_data, "\e#H World Card\n");
|
||||
furi_string_cat_printf(
|
||||
parsed_data, "%s\n", is_hworld_vip ? "VIP card" : "Standard room key");
|
||||
furi_string_cat_printf(parsed_data, "Room Num: %u%02u\n", room_floor, room_num);
|
||||
furi_string_cat_printf(
|
||||
parsed_data,
|
||||
"Check-in Date: \n%04u-%02d-%02d\n%02d:%02d:00\n",
|
||||
check_in_year,
|
||||
check_in_month,
|
||||
check_in_day,
|
||||
check_in_hour,
|
||||
check_in_minute);
|
||||
furi_string_cat_printf(
|
||||
parsed_data,
|
||||
"Expiration Date: \n%04u-%02d-%02d\n%02d:%02d:00",
|
||||
expire_year,
|
||||
expire_month,
|
||||
expire_day,
|
||||
expire_hour,
|
||||
expire_minute);
|
||||
parsed = true;
|
||||
} while(false);
|
||||
return parsed;
|
||||
}
|
||||
|
||||
/* Actual implementation of app<>plugin interface */
|
||||
static const NfcSupportedCardsPlugin hworld_plugin = {
|
||||
.protocol = NfcProtocolMfClassic,
|
||||
.verify = hworld_verify,
|
||||
.read = hworld_read,
|
||||
.parse = hworld_parse,
|
||||
};
|
||||
|
||||
/* Plugin descriptor to comply with basic plugin specification */
|
||||
static const FlipperAppPluginDescriptor hworld_plugin_descriptor = {
|
||||
.appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID,
|
||||
.ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION,
|
||||
.entry_point = &hworld_plugin,
|
||||
};
|
||||
|
||||
/* Plugin entry point - must return a pointer to const descriptor */
|
||||
const FlipperAppPluginDescriptor* hworld_plugin_ep(void) {
|
||||
return &hworld_plugin_descriptor;
|
||||
}
|
@ -298,15 +298,14 @@ void canvas_draw_xbm(
|
||||
|
||||
/** Draw rotated XBM bitmap
|
||||
*
|
||||
* @param canvas Canvas instance
|
||||
* @param x x coordinate
|
||||
* @param y y coordinate
|
||||
* @param[in] width bitmap width
|
||||
* @param[in] height bitmap height
|
||||
* @param[in] rotation bitmap rotation
|
||||
* @param bitmap pointer to XBM bitmap data
|
||||
* @param canvas Canvas instance
|
||||
* @param x x coordinate
|
||||
* @param y y coordinate
|
||||
* @param[in] width bitmap width
|
||||
* @param[in] height bitmap height
|
||||
* @param[in] rotation bitmap rotation
|
||||
* @param bitmap_data pointer to XBM bitmap data
|
||||
*/
|
||||
|
||||
void canvas_draw_xbm_ex(
|
||||
Canvas* canvas,
|
||||
int32_t x,
|
||||
|
@ -65,6 +65,13 @@ void text_input_set_result_callback(
|
||||
size_t text_buffer_size,
|
||||
bool clear_default_text);
|
||||
|
||||
/**
|
||||
* @brief Sets the minimum length of a TextInput
|
||||
* @param [in] text_input TextInput
|
||||
* @param [in] minimum_length Minimum input length
|
||||
*/
|
||||
void text_input_set_minimum_length(TextInput* text_input, size_t minimum_length);
|
||||
|
||||
void text_input_set_validator(
|
||||
TextInput* text_input,
|
||||
TextInputValidatorCallback callback,
|
||||
|
@ -5,6 +5,12 @@
|
||||
#define VIEW_DISPATCHER_QUEUE_LEN (16U)
|
||||
|
||||
ViewDispatcher* view_dispatcher_alloc(void) {
|
||||
ViewDispatcher* dispatcher = view_dispatcher_alloc_ex(furi_event_loop_alloc());
|
||||
dispatcher->is_event_loop_owned = true;
|
||||
return dispatcher;
|
||||
}
|
||||
|
||||
ViewDispatcher* view_dispatcher_alloc_ex(FuriEventLoop* loop) {
|
||||
ViewDispatcher* view_dispatcher = malloc(sizeof(ViewDispatcher));
|
||||
|
||||
view_dispatcher->view_port = view_port_alloc();
|
||||
@ -16,7 +22,7 @@ ViewDispatcher* view_dispatcher_alloc(void) {
|
||||
|
||||
ViewDict_init(view_dispatcher->views);
|
||||
|
||||
view_dispatcher->event_loop = furi_event_loop_alloc();
|
||||
view_dispatcher->event_loop = loop;
|
||||
|
||||
view_dispatcher->input_queue =
|
||||
furi_message_queue_alloc(VIEW_DISPATCHER_QUEUE_LEN, sizeof(InputEvent));
|
||||
@ -57,7 +63,7 @@ void view_dispatcher_free(ViewDispatcher* view_dispatcher) {
|
||||
furi_message_queue_free(view_dispatcher->input_queue);
|
||||
furi_message_queue_free(view_dispatcher->event_queue);
|
||||
|
||||
furi_event_loop_free(view_dispatcher->event_loop);
|
||||
if(view_dispatcher->is_event_loop_owned) furi_event_loop_free(view_dispatcher->event_loop);
|
||||
// Free dispatcher
|
||||
free(view_dispatcher);
|
||||
}
|
||||
@ -85,6 +91,7 @@ void view_dispatcher_set_tick_event_callback(
|
||||
ViewDispatcherTickEventCallback callback,
|
||||
uint32_t tick_period) {
|
||||
furi_check(view_dispatcher);
|
||||
furi_check(view_dispatcher->is_event_loop_owned);
|
||||
view_dispatcher->tick_event_callback = callback;
|
||||
view_dispatcher->tick_period = tick_period;
|
||||
}
|
||||
@ -106,11 +113,12 @@ void view_dispatcher_run(ViewDispatcher* view_dispatcher) {
|
||||
uint32_t tick_period = view_dispatcher->tick_period == 0 ? FuriWaitForever :
|
||||
view_dispatcher->tick_period;
|
||||
|
||||
furi_event_loop_tick_set(
|
||||
view_dispatcher->event_loop,
|
||||
tick_period,
|
||||
view_dispatcher_handle_tick_event,
|
||||
view_dispatcher);
|
||||
if(view_dispatcher->is_event_loop_owned)
|
||||
furi_event_loop_tick_set(
|
||||
view_dispatcher->event_loop,
|
||||
tick_period,
|
||||
view_dispatcher_handle_tick_event,
|
||||
view_dispatcher);
|
||||
|
||||
furi_event_loop_run(view_dispatcher->event_loop);
|
||||
|
||||
|
@ -47,6 +47,15 @@ typedef void (*ViewDispatcherTickEventCallback)(void* context);
|
||||
*/
|
||||
ViewDispatcher* view_dispatcher_alloc(void);
|
||||
|
||||
/** Allocate ViewDispatcher instance with an externally owned event loop. If
|
||||
* this constructor is used instead of `view_dispatcher_alloc`, the burden of
|
||||
* freeing the event loop is placed on the caller.
|
||||
*
|
||||
* @param loop pointer to FuriEventLoop instance
|
||||
* @return pointer to ViewDispatcher instance
|
||||
*/
|
||||
ViewDispatcher* view_dispatcher_alloc_ex(FuriEventLoop* loop);
|
||||
|
||||
/** Free ViewDispatcher instance
|
||||
*
|
||||
* @warning All added views MUST be removed using view_dispatcher_remove_view()
|
||||
@ -97,6 +106,10 @@ void view_dispatcher_set_navigation_event_callback(
|
||||
|
||||
/** Set tick event handler
|
||||
*
|
||||
* @warning Requires the event loop to be owned by the view dispatcher, i.e.
|
||||
* it should have been instantiated with `view_dispatcher_alloc`, not
|
||||
* `view_dispatcher_alloc_ex`.
|
||||
*
|
||||
* @param view_dispatcher ViewDispatcher instance
|
||||
* @param callback ViewDispatcherTickEventCallback
|
||||
* @param tick_period callback call period
|
||||
|
@ -14,6 +14,7 @@
|
||||
DICT_DEF2(ViewDict, uint32_t, M_DEFAULT_OPLIST, View*, M_PTR_OPLIST) // NOLINT
|
||||
|
||||
struct ViewDispatcher {
|
||||
bool is_event_loop_owned;
|
||||
FuriEventLoop* event_loop;
|
||||
FuriMessageQueue* input_queue;
|
||||
FuriMessageQueue* event_queue;
|
||||
|
@ -377,7 +377,7 @@ void storage_common_resolve_path_and_ensure_app_directory(Storage* storage, Furi
|
||||
* @param storage pointer to a storage API instance.
|
||||
* @param source pointer to a zero-terminated string containing the source path.
|
||||
* @param dest pointer to a zero-terminated string containing the destination path.
|
||||
* @return FSE_OK if the migration was successfull completed, any other error code on failure.
|
||||
* @return FSE_OK if the migration was successfully completed, any other error code on failure.
|
||||
*/
|
||||
FS_Error storage_common_migrate(Storage* storage, const char* source, const char* dest);
|
||||
|
||||
@ -425,7 +425,7 @@ bool storage_common_is_subdir(Storage* storage, const char* parent, const char*
|
||||
/******************* Error Functions *******************/
|
||||
|
||||
/**
|
||||
* @brief Get the textual description of a numeric error identifer.
|
||||
* @brief Get the textual description of a numeric error identifier.
|
||||
*
|
||||
* @param error_id numeric identifier of the error in question.
|
||||
* @return pointer to a statically allocated zero-terminated string containing the respective error text.
|
||||
|
@ -1106,7 +1106,7 @@ EXAMPLE_RECURSIVE = NO
|
||||
# that contain images that are to be included in the documentation (see the
|
||||
# \image command).
|
||||
|
||||
IMAGE_PATH =
|
||||
IMAGE_PATH = $(DOXY_SRC_ROOT)/documentation/images
|
||||
|
||||
# The INPUT_FILTER tag can be used to specify a program that doxygen should
|
||||
# invoke to filter for each input file. Doxygen will invoke the filter program
|
||||
|
BIN
documentation/images/dialog.png
Normal file
BIN
documentation/images/dialog.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
BIN
documentation/images/empty.png
Normal file
BIN
documentation/images/empty.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1005 B |
BIN
documentation/images/loading.png
Normal file
BIN
documentation/images/loading.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
BIN
documentation/images/submenu.png
Normal file
BIN
documentation/images/submenu.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.7 KiB |
BIN
documentation/images/text_box.png
Normal file
BIN
documentation/images/text_box.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.3 KiB |
BIN
documentation/images/text_input.png
Normal file
BIN
documentation/images/text_input.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.0 KiB |
@ -41,16 +41,10 @@ print("string1", "string2", 123);
|
||||
Same as `print`, but output to serial console only, with corresponding log level.
|
||||
|
||||
## to_string
|
||||
Convert a number to string.
|
||||
Convert a number to string with an optional base.
|
||||
|
||||
### Examples:
|
||||
```js
|
||||
to_string(123)
|
||||
```
|
||||
## to_hex_string
|
||||
Convert a number to string(hex format).
|
||||
|
||||
### Examples:
|
||||
```js
|
||||
to_hex_string(0xFF)
|
||||
to_string(123) // "123"
|
||||
to_string(123, 16) // "0x7b"
|
||||
```
|
||||
|
@ -1,49 +0,0 @@
|
||||
# js_dialog {#js_dialog}
|
||||
|
||||
# Dialog module
|
||||
```js
|
||||
let dialog = require("dialog");
|
||||
```
|
||||
# Methods
|
||||
|
||||
## message
|
||||
Show a simple message dialog with header, text and "OK" button.
|
||||
|
||||
### Parameters
|
||||
- Dialog header text
|
||||
- Dialog text
|
||||
|
||||
### Returns
|
||||
true if central button was pressed, false if the dialog was closed by back key press
|
||||
|
||||
### Examples:
|
||||
```js
|
||||
dialog.message("Dialog demo", "Press OK to start");
|
||||
```
|
||||
|
||||
## custom
|
||||
More complex dialog with configurable buttons
|
||||
|
||||
### Parameters
|
||||
Configuration object with the following fields:
|
||||
- header: Dialog header text
|
||||
- text: Dialog text
|
||||
- button_left: (optional) left button name
|
||||
- button_right: (optional) right button name
|
||||
- button_center: (optional) central button name
|
||||
|
||||
### Returns
|
||||
Name of pressed button or empty string if the dialog was closed by back key press
|
||||
|
||||
### Examples:
|
||||
```js
|
||||
let dialog_params = ({
|
||||
header: "Dialog header",
|
||||
text: "Dialog text",
|
||||
button_left: "Left",
|
||||
button_right: "Right",
|
||||
button_center: "OK"
|
||||
});
|
||||
|
||||
dialog.custom(dialog_params);
|
||||
```
|
144
documentation/js/js_event_loop.md
Normal file
144
documentation/js/js_event_loop.md
Normal file
@ -0,0 +1,144 @@
|
||||
# js_event_loop {#js_event_loop}
|
||||
|
||||
# Event Loop module
|
||||
```js
|
||||
let eventLoop = require("event_loop");
|
||||
```
|
||||
|
||||
The event loop is central to event-based programming in many frameworks, and our
|
||||
JS subsystem is no exception. It is a good idea to familiarize yourself with the
|
||||
event loop first before using any of the advanced modules (e.g. GPIO and GUI).
|
||||
|
||||
## Conceptualizing the event loop
|
||||
If you ever wrote JavaScript before, you have definitely seen callbacks. It's
|
||||
when a function accepts another function (usually an anonymous one) as one of
|
||||
the arguments, which it will call later on, e.g. when an event happens or when
|
||||
data becomes ready:
|
||||
```js
|
||||
setTimeout(function() { console.log("Hello, World!") }, 1000);
|
||||
```
|
||||
|
||||
Many JavaScript engines employ a queue that the runtime fetches events from as
|
||||
they occur, subsequently calling the corresponding callbacks. This is done in a
|
||||
long-running loop, hence the name "event loop". Here's the pseudocode for a
|
||||
typical event loop:
|
||||
```js
|
||||
while(loop_is_running()) {
|
||||
if(event_available_in_queue()) {
|
||||
let event = fetch_event_from_queue();
|
||||
let callback = get_callback_associated_with(event);
|
||||
if(callback)
|
||||
callback(get_extra_data_for(event));
|
||||
} else {
|
||||
// avoid wasting CPU time
|
||||
sleep_until_any_event_becomes_available();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Most JS runtimes enclose the event loop within themselves, so that most JS
|
||||
programmers does not even need to be aware of its existence. This is not the
|
||||
case with our JS subsystem.
|
||||
|
||||
# Example
|
||||
This is how one would write something similar to the `setTimeout` example above:
|
||||
```js
|
||||
// import module
|
||||
let eventLoop = require("event_loop");
|
||||
|
||||
// create an event source that will fire once 1 second after it has been created
|
||||
let timer = eventLoop.timer("oneshot", 1000);
|
||||
|
||||
// subscribe a callback to the event source
|
||||
eventLoop.subscribe(timer, function(_subscription, _item, eventLoop) {
|
||||
print("Hello, World!");
|
||||
eventLoop.stop();
|
||||
}, eventLoop); // notice this extra argument. we'll come back to this later
|
||||
|
||||
// run the loop until it is stopped
|
||||
eventLoop.run();
|
||||
|
||||
// the previous line will only finish executing once `.stop()` is called, hence
|
||||
// the following line will execute only after "Hello, World!" is printed
|
||||
print("Stopped");
|
||||
```
|
||||
|
||||
I promised you that we'll come back to the extra argument after the callback
|
||||
function. Our JavaScript engine does not support closures (anonymous functions
|
||||
that access values outside of their arguments), so we ask `subscribe` to pass an
|
||||
outside value (namely, `eventLoop`) as an argument to the callback so that we
|
||||
can access it. We can modify this extra state:
|
||||
```js
|
||||
// this timer will fire every second
|
||||
let timer = eventLoop.timer("periodic", 1000);
|
||||
eventLoop.subscribe(timer, function(_subscription, _item, counter, eventLoop) {
|
||||
print("Counter is at:", counter);
|
||||
if(counter === 10)
|
||||
eventLoop.stop();
|
||||
// modify the extra arguments that will be passed to us the next time
|
||||
return [counter + 1, eventLoop];
|
||||
}, 0, eventLoop);
|
||||
```
|
||||
|
||||
Because we have two extra arguments, if we return anything other than an array
|
||||
of length 2, the arguments will be kept as-is for the next call.
|
||||
|
||||
The first two arguments that get passed to our callback are:
|
||||
- The subscription manager that lets us `.cancel()` our subscription
|
||||
- The event item, used for events that have extra data. Timer events do not,
|
||||
they just produce `undefined`.
|
||||
|
||||
# API reference
|
||||
## `run`
|
||||
Runs the event loop until it is stopped with `stop`.
|
||||
|
||||
## `subscribe`
|
||||
Subscribes a function to an event.
|
||||
|
||||
### Parameters
|
||||
- `contract`: an event source identifier
|
||||
- `callback`: the function to call when the event happens
|
||||
- extra arguments: will be passed as extra arguments to the callback
|
||||
|
||||
The callback will be called with at least two arguments, plus however many were
|
||||
passed as extra arguments to `subscribe`. The first argument is the subscription
|
||||
manager (the same one that `subscribe` itself returns). The second argument is
|
||||
the event item for events that produce extra data; the ones that don't set this
|
||||
to `undefined`. The callback may return an array of the same length as the count
|
||||
of the extra arguments to modify them for the next time that the event handler
|
||||
is called. Any other returns values are discarded.
|
||||
|
||||
### Returns
|
||||
A `SubscriptionManager` object:
|
||||
- `SubscriptionManager.cancel()`: unsubscribes the callback from the event
|
||||
|
||||
### Warning
|
||||
Each event source may only have one callback associated with it.
|
||||
|
||||
## `stop`
|
||||
Stops the event loop.
|
||||
|
||||
## `timer`
|
||||
Produces an event source that fires with a constant interval either once or
|
||||
indefinitely.
|
||||
|
||||
### Parameters
|
||||
- `mode`: either `"oneshot"` or `"periodic"`
|
||||
- `interval`: the timeout (for `"oneshot"`) timers or the period (for
|
||||
`"periodic"` timers)
|
||||
|
||||
### Returns
|
||||
A `Contract` object, as expected by `subscribe`'s first parameter.
|
||||
|
||||
## `queue`
|
||||
Produces a queue that can be used to exchange messages.
|
||||
|
||||
### Parameters
|
||||
- `length`: the maximum number of items that the queue may contain
|
||||
|
||||
### Returns
|
||||
A `Queue` object:
|
||||
- `Queue.send(message)`:
|
||||
- `message`: a value of any type that will be placed at the end of the queue
|
||||
- `input`: a `Contract` (event source) that pops items from the front of the
|
||||
queue
|
77
documentation/js/js_gpio.md
Normal file
77
documentation/js/js_gpio.md
Normal file
@ -0,0 +1,77 @@
|
||||
# js_gpio {#js_gpio}
|
||||
|
||||
# GPIO module
|
||||
```js
|
||||
let eventLoop = require("event_loop");
|
||||
let gpio = require("gpio");
|
||||
```
|
||||
|
||||
This module depends on the `event_loop` module, so it _must_ only be imported
|
||||
after `event_loop` is imported.
|
||||
|
||||
# Example
|
||||
```js
|
||||
let eventLoop = require("event_loop");
|
||||
let gpio = require("gpio");
|
||||
|
||||
let led = gpio.get("pc3");
|
||||
led.init({ direction: "out", outMode: "push_pull" });
|
||||
|
||||
led.write(true);
|
||||
delay(1000);
|
||||
led.write(false);
|
||||
delay(1000);
|
||||
```
|
||||
|
||||
# API reference
|
||||
## `get`
|
||||
Gets a `Pin` object that can be used to manage a pin.
|
||||
|
||||
### Parameters
|
||||
- `pin`: pin identifier (examples: `"pc3"`, `7`, `"pa6"`, `3`)
|
||||
|
||||
### Returns
|
||||
A `Pin` object
|
||||
|
||||
## `Pin` object
|
||||
### `Pin.init()`
|
||||
Configures a pin
|
||||
|
||||
#### Parameters
|
||||
- `mode`: `Mode` object:
|
||||
- `direction` (required): either `"in"` or `"out"`
|
||||
- `outMode` (required for `direction: "out"`): either `"open_drain"` or
|
||||
`"push_pull"`
|
||||
- `inMode` (required for `direction: "in"`): either `"analog"`,
|
||||
`"plain_digital"`, `"interrupt"` or `"event"`
|
||||
- `edge` (required for `inMode: "interrupt"` or `"event"`): either
|
||||
`"rising"`, `"falling"` or `"both"`
|
||||
- `pull` (optional): either `"up"`, `"down"` or unset
|
||||
|
||||
### `Pin.write()`
|
||||
Writes a digital value to a pin configured with `direction: "out"`
|
||||
|
||||
#### Parameters
|
||||
- `value`: boolean logic level to write
|
||||
|
||||
### `Pin.read()`
|
||||
Reads a digital value from a pin configured with `direction: "in"` and any
|
||||
`inMode` except `"analog"`
|
||||
|
||||
#### Returns
|
||||
Boolean logic level
|
||||
|
||||
### `Pin.read_analog()`
|
||||
Reads an analog voltage level in millivolts from a pin configured with
|
||||
`direction: "in"` and `inMode: "analog"`
|
||||
|
||||
#### Returns
|
||||
Voltage on pin in millivolts
|
||||
|
||||
### `Pin.interrupt()`
|
||||
Attaches an interrupt to a pin configured with `direction: "in"` and
|
||||
`inMode: "interrupt"` or `"event"`
|
||||
|
||||
#### Returns
|
||||
An event loop `Contract` object that identifies the interrupt event source. The
|
||||
event does not produce any extra data.
|
161
documentation/js/js_gui.md
Normal file
161
documentation/js/js_gui.md
Normal file
@ -0,0 +1,161 @@
|
||||
# js_gui {#js_gui}
|
||||
|
||||
# GUI module
|
||||
```js
|
||||
let eventLoop = require("event_loop");
|
||||
let gui = require("gui");
|
||||
```
|
||||
|
||||
This module depends on the `event_loop` module, so it _must_ only be imported
|
||||
after `event_loop` is imported.
|
||||
|
||||
## Conceptualizing GUI
|
||||
### Event loop
|
||||
It is highly recommended to familiarize yourself with the event loop first
|
||||
before doing GUI-related things.
|
||||
|
||||
### Canvas
|
||||
The canvas is just a drawing area with no abstractions over it. Drawing on the
|
||||
canvas directly (i.e. not through a viewport) is useful in case you want to
|
||||
implement a custom design element, but this is rather uncommon.
|
||||
|
||||
### Viewport
|
||||
A viewport is a window into a rectangular portion of the canvas. Applications
|
||||
always access the canvas through a viewport.
|
||||
|
||||
### View
|
||||
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` | ❌ |
|
||||
|
||||
In JS, each view has its own set of properties (or just "props"). The programmer
|
||||
can manipulate these properties in two ways:
|
||||
- Instantiate a `View` using the `makeWith(props)` method, passing an object
|
||||
with the initial properties
|
||||
- Call `set(name, value)` to modify a property of an existing `View`
|
||||
|
||||
### View Dispatcher
|
||||
The view dispatcher holds references to all the views that an application needs
|
||||
and switches between them as the application makes requests to do so.
|
||||
|
||||
### Scene Manager
|
||||
The scene manager is an optional add-on to the view dispatcher that makes
|
||||
managing applications with complex navigation flows easier. It is currently
|
||||
inaccessible from JS.
|
||||
|
||||
### Approaches
|
||||
In total, there are three different approaches that you may take when writing
|
||||
a GUI application:
|
||||
| Approach | Use cases | Available from JS |
|
||||
|----------------|------------------------------------------------------------------------------|-------------------|
|
||||
| ViewPort only | Accessing the graphics API directly, without any of the nice UI abstractions | ❌ |
|
||||
| ViewDispatcher | Common UI elements that fit with the overall look of the system | ✅ |
|
||||
| SceneManager | Additional navigation flow management for complex applications | ❌ |
|
||||
|
||||
# Example
|
||||
An example with three different views using the ViewDispatcher approach:
|
||||
```js
|
||||
let eventLoop = require("event_loop");
|
||||
let gui = require("gui");
|
||||
let loadingView = require("gui/loading");
|
||||
let submenuView = require("gui/submenu");
|
||||
let emptyView = require("gui/empty_screen");
|
||||
|
||||
// Common pattern: declare all the views in an object. This is absolutely not
|
||||
// required, but adds clarity to the script.
|
||||
let views = {
|
||||
// the view dispatcher auto-✨magically✨ remembers views as they are created
|
||||
loading: loadingView.make(),
|
||||
empty: emptyView.make(),
|
||||
demos: submenuView.makeWith({
|
||||
items: [
|
||||
"Hourglass screen",
|
||||
"Empty screen",
|
||||
"Exit app",
|
||||
],
|
||||
}),
|
||||
};
|
||||
|
||||
// go to different screens depending on what was selected
|
||||
eventLoop.subscribe(views.demos.chosen, function (_sub, index, gui, eventLoop, views) {
|
||||
if (index === 0) {
|
||||
gui.viewDispatcher.switchTo(views.loading);
|
||||
} else if (index === 1) {
|
||||
gui.viewDispatcher.switchTo(views.empty);
|
||||
} else if (index === 2) {
|
||||
eventLoop.stop();
|
||||
}
|
||||
}, gui, eventLoop, views);
|
||||
|
||||
// go to the demo chooser screen when the back key is pressed
|
||||
eventLoop.subscribe(gui.viewDispatcher.navigation, function (_sub, _, gui, views) {
|
||||
gui.viewDispatcher.switchTo(views.demos);
|
||||
}, gui, views);
|
||||
|
||||
// run UI
|
||||
gui.viewDispatcher.switchTo(views.demos);
|
||||
eventLoop.run();
|
||||
```
|
||||
|
||||
# API reference
|
||||
## `viewDispatcher`
|
||||
The `viewDispatcher` constant holds the `ViewDispatcher` singleton.
|
||||
|
||||
### `viewDispatcher.switchTo(view)`
|
||||
Switches to a view, giving it control over the display and input
|
||||
|
||||
#### Parameters
|
||||
- `view`: the `View` to switch to
|
||||
|
||||
### `viewDispatcher.sendTo(direction)`
|
||||
Sends the viewport that the dispatcher manages to the front of the stackup
|
||||
(effectively making it visible), or to the back (effectively making it
|
||||
invisible)
|
||||
|
||||
#### Parameters
|
||||
- `direction`: either `"front"` or `"back"`
|
||||
|
||||
### `viewDispatcher.sendCustom(event)`
|
||||
Sends a custom number to the `custom` event handler
|
||||
|
||||
#### Parameters
|
||||
- `event`: number to send
|
||||
|
||||
### `viewDispatcher.custom`
|
||||
An event loop `Contract` object that identifies the custom event source,
|
||||
triggered by `ViewDispatcher.sendCustom(event)`
|
||||
|
||||
### `viewDispatcher.navigation`
|
||||
An event loop `Contract` object that identifies the navigation event source,
|
||||
triggered when the back key is pressed
|
||||
|
||||
## `ViewFactory`
|
||||
When you import a module implementing a view, a `ViewFactory` is instantiated.
|
||||
For example, in the example above, `loadingView`, `submenuView` and `emptyView`
|
||||
are view factories.
|
||||
|
||||
### `ViewFactory.make()`
|
||||
Creates an instance of a `View`
|
||||
|
||||
### `ViewFactory.make(props)`
|
||||
Creates an instance of a `View` and assigns initial properties from `props`
|
||||
|
||||
#### Parameters
|
||||
- `props`: simple key-value object, e.g. `{ header: "Header" }`
|
53
documentation/js/js_gui__dialog.md
Normal file
53
documentation/js/js_gui__dialog.md
Normal file
@ -0,0 +1,53 @@
|
||||
# js_gui__dialog {#js_gui__dialog}
|
||||
|
||||
# Dialog GUI view
|
||||
Displays a dialog with up to three options.
|
||||
|
||||
<img src="dialog.png" width="200" alt="Sample screenshot of the view" />
|
||||
|
||||
```js
|
||||
let eventLoop = require("event_loop");
|
||||
let gui = require("gui");
|
||||
let dialogView = require("gui/dialog");
|
||||
```
|
||||
|
||||
This module depends on the `gui` module, which in turn depends on the
|
||||
`event_loop` module, so they _must_ be imported in this order. It is also
|
||||
recommended to conceptualize these modules first before using this one.
|
||||
|
||||
# Example
|
||||
For an example refer to the `gui.js` example script.
|
||||
|
||||
# View props
|
||||
## `header`
|
||||
Text that appears in bold at the top of the screen
|
||||
|
||||
Type: `string`
|
||||
|
||||
## `text`
|
||||
Text that appears in the middle of the screen
|
||||
|
||||
Type: `string`
|
||||
|
||||
## `left`
|
||||
Text for the left button. If unset, the left button does not show up.
|
||||
|
||||
Type: `string`
|
||||
|
||||
## `center`
|
||||
Text for the center button. If unset, the center button does not show up.
|
||||
|
||||
Type: `string`
|
||||
|
||||
## `right`
|
||||
Text for the right button. If unset, the right button does not show up.
|
||||
|
||||
Type: `string`
|
||||
|
||||
# View events
|
||||
## `input`
|
||||
Fires when the user presses on either of the three possible buttons. The item
|
||||
contains one of the strings `"left"`, `"center"` or `"right"` depending on the
|
||||
button.
|
||||
|
||||
Item type: `string`
|
22
documentation/js/js_gui__empty_screen.md
Normal file
22
documentation/js/js_gui__empty_screen.md
Normal file
@ -0,0 +1,22 @@
|
||||
# js_gui__empty_screen {#js_gui__empty_screen}
|
||||
|
||||
# Empty Screen GUI View
|
||||
Displays nothing.
|
||||
|
||||
<img src="empty.png" width="200" alt="Sample screenshot of the view" />
|
||||
|
||||
```js
|
||||
let eventLoop = require("event_loop");
|
||||
let gui = require("gui");
|
||||
let emptyView = require("gui/empty_screen");
|
||||
```
|
||||
|
||||
This module depends on the `gui` module, which in turn depends on the
|
||||
`event_loop` module, so they _must_ be imported in this order. It is also
|
||||
recommended to conceptualize these modules first before using this one.
|
||||
|
||||
# Example
|
||||
For an example refer to the GUI example.
|
||||
|
||||
# View props
|
||||
This view does not have any props.
|
23
documentation/js/js_gui__loading.md
Normal file
23
documentation/js/js_gui__loading.md
Normal file
@ -0,0 +1,23 @@
|
||||
# js_gui__loading {#js_gui__loading}
|
||||
|
||||
# Loading GUI View
|
||||
Displays an animated hourglass icon. Suppresses all `navigation` events, making
|
||||
it impossible for the user to exit the view by pressing the back key.
|
||||
|
||||
<img src="loading.png" width="200" alt="Sample screenshot of the view" />
|
||||
|
||||
```js
|
||||
let eventLoop = require("event_loop");
|
||||
let gui = require("gui");
|
||||
let loadingView = require("gui/loading");
|
||||
```
|
||||
|
||||
This module depends on the `gui` module, which in turn depends on the
|
||||
`event_loop` module, so they _must_ be imported in this order. It is also
|
||||
recommended to conceptualize these modules first before using this one.
|
||||
|
||||
# Example
|
||||
For an example refer to the GUI example.
|
||||
|
||||
# View props
|
||||
This view does not have any props.
|
37
documentation/js/js_gui__submenu.md
Normal file
37
documentation/js/js_gui__submenu.md
Normal file
@ -0,0 +1,37 @@
|
||||
# js_gui__submenu {#js_gui__submenu}
|
||||
|
||||
# Submenu GUI view
|
||||
Displays a scrollable list of clickable textual entries.
|
||||
|
||||
<img src="submenu.png" width="200" alt="Sample screenshot of the view" />
|
||||
|
||||
```js
|
||||
let eventLoop = require("event_loop");
|
||||
let gui = require("gui");
|
||||
let submenuView = require("gui/submenu");
|
||||
```
|
||||
|
||||
This module depends on the `gui` module, which in turn depends on the
|
||||
`event_loop` module, so they _must_ be imported in this order. It is also
|
||||
recommended to conceptualize these modules first before using this one.
|
||||
|
||||
# Example
|
||||
For an example refer to the GUI example.
|
||||
|
||||
# View props
|
||||
## `header`
|
||||
Single line of text that appears above the list
|
||||
|
||||
Type: `string`
|
||||
|
||||
## `items`
|
||||
The list of options
|
||||
|
||||
Type: `string[]`
|
||||
|
||||
# View events
|
||||
## `chosen`
|
||||
Fires when an entry has been chosen by the user. The item contains the index of
|
||||
the entry.
|
||||
|
||||
Item type: `number`
|
25
documentation/js/js_gui__text_box.md
Normal file
25
documentation/js/js_gui__text_box.md
Normal file
@ -0,0 +1,25 @@
|
||||
# js_gui__text_box {#js_gui__text_box}
|
||||
|
||||
# Text box GUI view
|
||||
Displays a scrollable read-only text field.
|
||||
|
||||
<img src="text_box.png" width="200" alt="Sample screenshot of the view" />
|
||||
|
||||
```js
|
||||
let eventLoop = require("event_loop");
|
||||
let gui = require("gui");
|
||||
let textBoxView = require("gui/text_box");
|
||||
```
|
||||
|
||||
This module depends on the `gui` module, which in turn depends on the
|
||||
`event_loop` module, so they _must_ be imported in this order. It is also
|
||||
recommended to conceptualize these modules first before using this one.
|
||||
|
||||
# Example
|
||||
For an example refer to the `gui.js` example script.
|
||||
|
||||
# View props
|
||||
## `text`
|
||||
Text to show in the text box.
|
||||
|
||||
Type: `string`
|
44
documentation/js/js_gui__text_input.md
Normal file
44
documentation/js/js_gui__text_input.md
Normal file
@ -0,0 +1,44 @@
|
||||
# js_gui__text_input {#js_gui__text_input}
|
||||
|
||||
# Text input GUI view
|
||||
Displays a keyboard.
|
||||
|
||||
<img src="text_input.png" width="200" alt="Sample screenshot of the view" />
|
||||
|
||||
```js
|
||||
let eventLoop = require("event_loop");
|
||||
let gui = require("gui");
|
||||
let textInputView = require("gui/text_input");
|
||||
```
|
||||
|
||||
This module depends on the `gui` module, which in turn depends on the
|
||||
`event_loop` module, so they _must_ be imported in this order. It is also
|
||||
recommended to conceptualize these modules first before using this one.
|
||||
|
||||
# Example
|
||||
For an example refer to the `gui.js` example script.
|
||||
|
||||
# View props
|
||||
## `minLength`
|
||||
Smallest allowed text length
|
||||
|
||||
Type: `number`
|
||||
|
||||
## `maxLength`
|
||||
Biggest allowed text length
|
||||
|
||||
Type: `number`
|
||||
|
||||
Default: `32`
|
||||
|
||||
## `header`
|
||||
Single line of text that appears above the keyboard
|
||||
|
||||
Type: `string`
|
||||
|
||||
# View events
|
||||
## `input`
|
||||
Fires when the user selects the "save" button and the text matches the length
|
||||
constrained by `minLength` and `maxLength`.
|
||||
|
||||
Item type: `string`
|
@ -1,48 +0,0 @@
|
||||
# js_submenu {#js_submenu}
|
||||
|
||||
# Submenu module
|
||||
```js
|
||||
let submenu = require("submenu");
|
||||
```
|
||||
# Methods
|
||||
|
||||
## setHeader
|
||||
Set the submenu header text.
|
||||
|
||||
### Parameters
|
||||
- header (string): The submenu header text
|
||||
|
||||
### Example
|
||||
```js
|
||||
submenu.setHeader("Select an option:");
|
||||
```
|
||||
|
||||
## addItem
|
||||
Add a new submenu item.
|
||||
|
||||
### Parameters
|
||||
- label (string): The submenu item label text
|
||||
- id (number): The submenu item ID, must be a Uint32 number
|
||||
|
||||
### Example
|
||||
```js
|
||||
submenu.addItem("Option 1", 1);
|
||||
submenu.addItem("Option 2", 2);
|
||||
submenu.addItem("Option 3", 3);
|
||||
```
|
||||
|
||||
## show
|
||||
Show a submenu that was previously configured using `setHeader()` and `addItem()` methods.
|
||||
|
||||
### Returns
|
||||
The ID of the submenu item that was selected, or `undefined` if the BACK button was pressed.
|
||||
|
||||
### Example
|
||||
```js
|
||||
let selected = submenu.show();
|
||||
if (selected === undefined) {
|
||||
// if BACK button was pressed
|
||||
} else if (selected === 1) {
|
||||
// if item with ID 1 was selected
|
||||
}
|
||||
```
|
@ -1,69 +0,0 @@
|
||||
# js_textbox {#js_textbox}
|
||||
|
||||
# Textbox module
|
||||
```js
|
||||
let textbox = require("textbox");
|
||||
```
|
||||
# Methods
|
||||
|
||||
## setConfig
|
||||
Set focus and font for the textbox.
|
||||
|
||||
### Parameters
|
||||
- focus: "start" to focus on the beginning of the text, or "end" to focus on the end of the text
|
||||
- font: "text" to use the default proportional font, or "hex" to use a monospaced font, which is convenient for aligned array output in HEX
|
||||
|
||||
### Example
|
||||
```js
|
||||
textbox.setConfig("start", "text");
|
||||
textbox.addText("Hello world");
|
||||
textbox.show();
|
||||
```
|
||||
|
||||
## addText
|
||||
Add text to the end of the textbox.
|
||||
|
||||
### Parameters
|
||||
- text (string): The text to add to the end of the textbox
|
||||
|
||||
### Example
|
||||
```js
|
||||
textbox.addText("New text 1\nNew text 2");
|
||||
```
|
||||
|
||||
## clearText
|
||||
Clear the textbox.
|
||||
|
||||
### Example
|
||||
```js
|
||||
textbox.clearText();
|
||||
```
|
||||
|
||||
## isOpen
|
||||
Return true if the textbox is open.
|
||||
|
||||
### Returns
|
||||
True if the textbox is open, false otherwise.
|
||||
|
||||
### Example
|
||||
```js
|
||||
let isOpen = textbox.isOpen();
|
||||
```
|
||||
|
||||
## show
|
||||
Show the textbox. You can add text to it using the `addText()` method before or after calling the `show()` method.
|
||||
|
||||
### Example
|
||||
```js
|
||||
textbox.show();
|
||||
```
|
||||
|
||||
## close
|
||||
Close the textbox.
|
||||
|
||||
### Example
|
||||
```js
|
||||
if (textbox.isOpen()) {
|
||||
textbox.close();
|
||||
}
|
||||
```
|
@ -75,6 +75,7 @@ FIRMWARE_APPS = {
|
||||
"updater_app",
|
||||
"radio_device_cc1101_ext",
|
||||
"unit_tests",
|
||||
"js_app",
|
||||
],
|
||||
}
|
||||
|
||||
|
@ -216,6 +216,7 @@ fwelf = fwenv["FW_ELF"] = fwenv.Program(
|
||||
sources,
|
||||
LIBS=fwenv["TARGET_CFG"].linker_dependencies,
|
||||
)
|
||||
Depends(fwelf, fwenv["LINKER_SCRIPT_PATH"])
|
||||
|
||||
# Firmware depends on everything child builders returned
|
||||
# Depends(fwelf, lib_targets)
|
||||
|
@ -418,6 +418,18 @@ void furi_event_loop_unsubscribe(FuriEventLoop* instance, FuriEventLoopObject* o
|
||||
FURI_CRITICAL_EXIT();
|
||||
}
|
||||
|
||||
bool furi_event_loop_is_subscribed(FuriEventLoop* instance, FuriEventLoopObject* object) {
|
||||
furi_check(instance);
|
||||
furi_check(instance->thread_id == furi_thread_get_current_id());
|
||||
FURI_CRITICAL_ENTER();
|
||||
|
||||
FuriEventLoopItem* const* item = FuriEventLoopTree_cget(instance->tree, object);
|
||||
bool result = !!item;
|
||||
|
||||
FURI_CRITICAL_EXIT();
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Private Event Loop Item functions
|
||||
*/
|
||||
|
@ -289,6 +289,23 @@ void furi_event_loop_subscribe_mutex(
|
||||
*/
|
||||
void furi_event_loop_unsubscribe(FuriEventLoop* instance, FuriEventLoopObject* object);
|
||||
|
||||
/**
|
||||
* @brief Checks if the loop is subscribed to an object of any kind
|
||||
*
|
||||
* @param instance Event Loop instance
|
||||
* @param object Object to check
|
||||
*/
|
||||
bool furi_event_loop_is_subscribed(FuriEventLoop* instance, FuriEventLoopObject* object);
|
||||
|
||||
/**
|
||||
* @brief Convenience function for `if(is_subscribed()) unsubscribe()`
|
||||
*/
|
||||
static inline void
|
||||
furi_event_loop_maybe_unsubscribe(FuriEventLoop* instance, FuriEventLoopObject* object) {
|
||||
if(furi_event_loop_is_subscribed(instance, object))
|
||||
furi_event_loop_unsubscribe(instance, object);
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
@ -103,6 +103,7 @@ struct mjs* mjs_create(void* context) {
|
||||
sizeof(struct mjs_object),
|
||||
MJS_OBJECT_ARENA_SIZE,
|
||||
MJS_OBJECT_ARENA_INC_SIZE);
|
||||
mjs->object_arena.destructor = mjs_obj_destructor;
|
||||
gc_arena_init(
|
||||
&mjs->property_arena,
|
||||
sizeof(struct mjs_property),
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include "mjs_primitive.h"
|
||||
#include "mjs_string.h"
|
||||
#include "mjs_util.h"
|
||||
#include "furi.h"
|
||||
|
||||
#include "common/mg_str.h"
|
||||
|
||||
@ -20,6 +21,19 @@ MJS_PRIVATE mjs_val_t mjs_object_to_value(struct mjs_object* o) {
|
||||
}
|
||||
}
|
||||
|
||||
MJS_PRIVATE void mjs_obj_destructor(struct mjs* mjs, void* cell) {
|
||||
struct mjs_object* obj = cell;
|
||||
mjs_val_t obj_val = mjs_object_to_value(obj);
|
||||
|
||||
struct mjs_property* destructor = mjs_get_own_property(
|
||||
mjs, obj_val, MJS_DESTRUCTOR_PROP_NAME, strlen(MJS_DESTRUCTOR_PROP_NAME));
|
||||
if(!destructor) return;
|
||||
if(!mjs_is_foreign(destructor->value)) return;
|
||||
|
||||
mjs_custom_obj_destructor_t destructor_fn = mjs_get_ptr(mjs, destructor->value);
|
||||
if(destructor_fn) destructor_fn(mjs, obj_val);
|
||||
}
|
||||
|
||||
MJS_PRIVATE struct mjs_object* get_object_struct(mjs_val_t v) {
|
||||
struct mjs_object* ret = NULL;
|
||||
if(mjs_is_null(v)) {
|
||||
@ -293,7 +307,8 @@ mjs_val_t
|
||||
* start from the end so the constructed object more closely resembles
|
||||
* the definition.
|
||||
*/
|
||||
while(def->name != NULL) def++;
|
||||
while(def->name != NULL)
|
||||
def++;
|
||||
for(def--; def >= defs; def--) {
|
||||
mjs_val_t v = MJS_UNDEFINED;
|
||||
const char* ptr = (const char*)base + def->offset;
|
||||
|
@ -50,6 +50,11 @@ MJS_PRIVATE mjs_err_t mjs_set_internal(
|
||||
*/
|
||||
MJS_PRIVATE void mjs_op_create_object(struct mjs* mjs);
|
||||
|
||||
/*
|
||||
* Cell destructor for object arena
|
||||
*/
|
||||
MJS_PRIVATE void mjs_obj_destructor(struct mjs* mjs, void* cell);
|
||||
|
||||
#define MJS_PROTO_PROP_NAME "__p" /* Make it < 5 chars */
|
||||
|
||||
#if defined(__cplusplus)
|
||||
|
@ -119,6 +119,14 @@ int mjs_del(struct mjs* mjs, mjs_val_t obj, const char* name, size_t len);
|
||||
*/
|
||||
mjs_val_t mjs_next(struct mjs* mjs, mjs_val_t obj, mjs_val_t* iterator);
|
||||
|
||||
typedef void (*mjs_custom_obj_destructor_t)(struct mjs* mjs, mjs_val_t object);
|
||||
|
||||
/*
|
||||
* Destructor property name. If set, must be a foreign pointer to a function
|
||||
* that will be called just before the object is freed.
|
||||
*/
|
||||
#define MJS_DESTRUCTOR_PROP_NAME "__d"
|
||||
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
@ -65,10 +65,8 @@ static NfcCommand iso14443_4a_listener_run(NfcGenericEvent event, void* context)
|
||||
if(instance->state == Iso14443_4aListenerStateIdle) {
|
||||
if(bit_buffer_get_size_bytes(rx_buffer) == 2 &&
|
||||
bit_buffer_get_byte(rx_buffer, 0) == ISO14443_4A_CMD_READ_ATS) {
|
||||
if(iso14443_4a_listener_send_ats(instance, &instance->data->ats_data) !=
|
||||
if(iso14443_4a_listener_send_ats(instance, &instance->data->ats_data) ==
|
||||
Iso14443_4aErrorNone) {
|
||||
command = NfcCommandContinue;
|
||||
} else {
|
||||
instance->state = Iso14443_4aListenerStateActive;
|
||||
}
|
||||
}
|
||||
@ -93,7 +91,6 @@ static NfcCommand iso14443_4a_listener_run(NfcGenericEvent event, void* context)
|
||||
if(instance->callback) {
|
||||
command = instance->callback(instance->generic_event, instance->context);
|
||||
}
|
||||
command = NfcCommandContinue;
|
||||
}
|
||||
|
||||
return command;
|
||||
|
@ -1,5 +1,5 @@
|
||||
entry,status,name,type,params
|
||||
Version,+,78.0,,
|
||||
Version,+,77.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,,
|
||||
@ -1116,6 +1116,7 @@ Function,+,furi_event_flag_set,uint32_t,"FuriEventFlag*, uint32_t"
|
||||
Function,+,furi_event_flag_wait,uint32_t,"FuriEventFlag*, uint32_t, uint32_t, uint32_t"
|
||||
Function,+,furi_event_loop_alloc,FuriEventLoop*,
|
||||
Function,+,furi_event_loop_free,void,FuriEventLoop*
|
||||
Function,+,furi_event_loop_is_subscribed,_Bool,"FuriEventLoop*, FuriEventLoopObject*"
|
||||
Function,+,furi_event_loop_pend_callback,void,"FuriEventLoop*, FuriEventLoopPendingCallback, void*"
|
||||
Function,+,furi_event_loop_run,void,FuriEventLoop*
|
||||
Function,+,furi_event_loop_stop,void,FuriEventLoop*
|
||||
@ -1372,6 +1373,8 @@ Function,-,furi_hal_resources_deinit_early,void,
|
||||
Function,+,furi_hal_resources_get_ext_pin_number,int32_t,const GpioPin*
|
||||
Function,-,furi_hal_resources_init,void,
|
||||
Function,-,furi_hal_resources_init_early,void,
|
||||
Function,+,furi_hal_resources_pin_by_name,const GpioPinRecord*,const char*
|
||||
Function,+,furi_hal_resources_pin_by_number,const GpioPinRecord*,uint8_t
|
||||
Function,-,furi_hal_rtc_deinit_early,void,
|
||||
Function,+,furi_hal_rtc_get_boot_mode,FuriHalRtcBootMode,
|
||||
Function,+,furi_hal_rtc_get_datetime,void,DateTime*
|
||||
@ -2687,6 +2690,7 @@ Function,+,text_input_get_validator_callback_context,void*,TextInput*
|
||||
Function,+,text_input_get_view,View*,TextInput*
|
||||
Function,+,text_input_reset,void,TextInput*
|
||||
Function,+,text_input_set_header_text,void,"TextInput*, const char*"
|
||||
Function,+,text_input_set_minimum_length,void,"TextInput*, size_t"
|
||||
Function,+,text_input_set_result_callback,void,"TextInput*, TextInputCallback, void*, char*, size_t, _Bool"
|
||||
Function,+,text_input_set_validator,void,"TextInput*, TextInputValidatorCallback, void*"
|
||||
Function,-,tgamma,double,double
|
||||
@ -2761,6 +2765,7 @@ Function,+,view_allocate_model,void,"View*, ViewModelType, size_t"
|
||||
Function,+,view_commit_model,void,"View*, _Bool"
|
||||
Function,+,view_dispatcher_add_view,void,"ViewDispatcher*, uint32_t, View*"
|
||||
Function,+,view_dispatcher_alloc,ViewDispatcher*,
|
||||
Function,+,view_dispatcher_alloc_ex,ViewDispatcher*,FuriEventLoop*
|
||||
Function,+,view_dispatcher_attach_to_gui,void,"ViewDispatcher*, Gui*, ViewDispatcherType"
|
||||
Function,+,view_dispatcher_enable_queue,void,ViewDispatcher*
|
||||
Function,+,view_dispatcher_free,void,ViewDispatcher*
|
||||
|
|
@ -354,3 +354,19 @@ int32_t furi_hal_resources_get_ext_pin_number(const GpioPin* gpio) {
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
const GpioPinRecord* furi_hal_resources_pin_by_name(const char* name) {
|
||||
for(size_t i = 0; i < gpio_pins_count; i++) {
|
||||
const GpioPinRecord* record = &gpio_pins[i];
|
||||
if(strcasecmp(name, record->name) == 0) return record;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const GpioPinRecord* furi_hal_resources_pin_by_number(uint8_t number) {
|
||||
for(size_t i = 0; i < gpio_pins_count; i++) {
|
||||
const GpioPinRecord* record = &gpio_pins[i];
|
||||
if(record->number == number) return record;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
@ -121,6 +121,26 @@ void furi_hal_resources_init(void);
|
||||
*/
|
||||
int32_t furi_hal_resources_get_ext_pin_number(const GpioPin* gpio);
|
||||
|
||||
/**
|
||||
* @brief Finds a pin by its name
|
||||
*
|
||||
* @param name case-insensitive pin name to look for (e.g. `"Pc3"`, `"pA4"`)
|
||||
*
|
||||
* @return a pointer to the corresponding `GpioPinRecord` if such a pin exists,
|
||||
* `NULL` otherwise.
|
||||
*/
|
||||
const GpioPinRecord* furi_hal_resources_pin_by_name(const char* name);
|
||||
|
||||
/**
|
||||
* @brief Finds a pin by its number
|
||||
*
|
||||
* @param name pin number to look for (e.g. `7`, `4`)
|
||||
*
|
||||
* @return a pointer to the corresponding `GpioPinRecord` if such a pin exists,
|
||||
* `NULL` otherwise.
|
||||
*/
|
||||
const GpioPinRecord* furi_hal_resources_pin_by_number(uint8_t number);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
@ -1,5 +1,5 @@
|
||||
entry,status,name,type,params
|
||||
Version,+,78.0,,
|
||||
Version,+,77.2,,
|
||||
Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,,
|
||||
Header,+,applications/services/bt/bt_service/bt.h,,
|
||||
Header,+,applications/services/bt/bt_service/bt_keys_storage.h,,
|
||||
@ -1256,6 +1256,7 @@ Function,+,furi_event_flag_set,uint32_t,"FuriEventFlag*, uint32_t"
|
||||
Function,+,furi_event_flag_wait,uint32_t,"FuriEventFlag*, uint32_t, uint32_t, uint32_t"
|
||||
Function,+,furi_event_loop_alloc,FuriEventLoop*,
|
||||
Function,+,furi_event_loop_free,void,FuriEventLoop*
|
||||
Function,+,furi_event_loop_is_subscribed,_Bool,"FuriEventLoop*, FuriEventLoopObject*"
|
||||
Function,+,furi_event_loop_pend_callback,void,"FuriEventLoop*, FuriEventLoopPendingCallback, void*"
|
||||
Function,+,furi_event_loop_run,void,FuriEventLoop*
|
||||
Function,+,furi_event_loop_stop,void,FuriEventLoop*
|
||||
@ -1576,6 +1577,8 @@ Function,-,furi_hal_resources_deinit_early,void,
|
||||
Function,+,furi_hal_resources_get_ext_pin_number,int32_t,const GpioPin*
|
||||
Function,-,furi_hal_resources_init,void,
|
||||
Function,-,furi_hal_resources_init_early,void,
|
||||
Function,+,furi_hal_resources_pin_by_name,const GpioPinRecord*,const char*
|
||||
Function,+,furi_hal_resources_pin_by_number,const GpioPinRecord*,uint8_t
|
||||
Function,+,furi_hal_rfid_comp_set_callback,void,"FuriHalRfidCompCallback, void*"
|
||||
Function,+,furi_hal_rfid_comp_start,void,
|
||||
Function,+,furi_hal_rfid_comp_stop,void,
|
||||
@ -3691,6 +3694,7 @@ Function,+,view_allocate_model,void,"View*, ViewModelType, size_t"
|
||||
Function,+,view_commit_model,void,"View*, _Bool"
|
||||
Function,+,view_dispatcher_add_view,void,"ViewDispatcher*, uint32_t, View*"
|
||||
Function,+,view_dispatcher_alloc,ViewDispatcher*,
|
||||
Function,+,view_dispatcher_alloc_ex,ViewDispatcher*,FuriEventLoop*
|
||||
Function,+,view_dispatcher_attach_to_gui,void,"ViewDispatcher*, Gui*, ViewDispatcherType"
|
||||
Function,+,view_dispatcher_enable_queue,void,ViewDispatcher*
|
||||
Function,+,view_dispatcher_free,void,ViewDispatcher*
|
||||
|
|
@ -288,3 +288,19 @@ int32_t furi_hal_resources_get_ext_pin_number(const GpioPin* gpio) {
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
const GpioPinRecord* furi_hal_resources_pin_by_name(const char* name) {
|
||||
for(size_t i = 0; i < gpio_pins_count; i++) {
|
||||
const GpioPinRecord* record = &gpio_pins[i];
|
||||
if(strcasecmp(name, record->name) == 0) return record;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const GpioPinRecord* furi_hal_resources_pin_by_number(uint8_t number) {
|
||||
for(size_t i = 0; i < gpio_pins_count; i++) {
|
||||
const GpioPinRecord* record = &gpio_pins[i];
|
||||
if(record->number == number) return record;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
@ -227,6 +227,26 @@ void furi_hal_resources_init(void);
|
||||
*/
|
||||
int32_t furi_hal_resources_get_ext_pin_number(const GpioPin* gpio);
|
||||
|
||||
/**
|
||||
* @brief Finds a pin by its name
|
||||
*
|
||||
* @param name case-insensitive pin name to look for (e.g. `"Pc3"`, `"pA4"`)
|
||||
*
|
||||
* @return a pointer to the corresponding `GpioPinRecord` if such a pin exists,
|
||||
* `NULL` otherwise.
|
||||
*/
|
||||
const GpioPinRecord* furi_hal_resources_pin_by_name(const char* name);
|
||||
|
||||
/**
|
||||
* @brief Finds a pin by its number
|
||||
*
|
||||
* @param name pin number to look for (e.g. `7`, `4`)
|
||||
*
|
||||
* @return a pointer to the corresponding `GpioPinRecord` if such a pin exists,
|
||||
* `NULL` otherwise.
|
||||
*/
|
||||
const GpioPinRecord* furi_hal_resources_pin_by_number(uint8_t number);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
@ -3,7 +3,7 @@ ENTRY(Reset_Handler)
|
||||
/* Highest address of the user mode stack */
|
||||
_stack_end = 0x20030000; /* end of RAM */
|
||||
/* Generate a link error if heap and stack don't fit into RAM */
|
||||
_stack_size = 0x1000; /* required amount of stack */
|
||||
_stack_size = 0x200; /* required amount of stack */
|
||||
|
||||
MEMORY {
|
||||
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K
|
||||
|
@ -3,7 +3,7 @@ ENTRY(Reset_Handler)
|
||||
/* Highest address of the user mode stack */
|
||||
_stack_end = 0x20030000; /* end of RAM */
|
||||
/* Generate a link error if heap and stack don't fit into RAM */
|
||||
_stack_size = 0x1000; /* required amount of stack */
|
||||
_stack_size = 0x200; /* required amount of stack */
|
||||
|
||||
MEMORY {
|
||||
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K
|
||||
|
15
tsconfig.json
Normal file
15
tsconfig.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"checkJs": true,
|
||||
"module": "CommonJS",
|
||||
"typeRoots": [
|
||||
"./applications/system/js_app/types"
|
||||
],
|
||||
"noLib": true,
|
||||
},
|
||||
"include": [
|
||||
"./applications/system/js_app/examples/apps/Scripts",
|
||||
"./applications/debug/unit_tests/resources/unit_tests/js",
|
||||
"./applications/system/js_app/types/global.d.ts",
|
||||
]
|
||||
}
|
Loading…
Reference in New Issue
Block a user