mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-05-22 05:14:46 -07:00
Merge remote-tracking branch 'noproto/dev' into ulcdict
This commit is contained in:
5
.gitignore
vendored
5
.gitignore
vendored
@@ -63,4 +63,7 @@ PVS-Studio.log
|
|||||||
|
|
||||||
.gdbinit
|
.gdbinit
|
||||||
|
|
||||||
/fbt_options_local.py
|
/fbt_options_local.py
|
||||||
|
|
||||||
|
# JS packages
|
||||||
|
node_modules/
|
||||||
|
|||||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -35,3 +35,6 @@
|
|||||||
[submodule "documentation/doxygen/doxygen-awesome-css"]
|
[submodule "documentation/doxygen/doxygen-awesome-css"]
|
||||||
path = documentation/doxygen/doxygen-awesome-css
|
path = documentation/doxygen/doxygen-awesome-css
|
||||||
url = https://github.com/jothepro/doxygen-awesome-css.git
|
url = https://github.com/jothepro/doxygen-awesome-css.git
|
||||||
|
[submodule "applications/system/picopass"]
|
||||||
|
path = applications/system/picopass
|
||||||
|
url = https://gitlab.com/bettse/picopass.git
|
||||||
|
|||||||
@@ -80,3 +80,4 @@ Utility apps not visible in other menus, plus few external apps pre-packaged wit
|
|||||||
- `storage_move_to_sd` - Data migration tool for internal storage
|
- `storage_move_to_sd` - Data migration tool for internal storage
|
||||||
- `updater` - Update service & application
|
- `updater` - Update service & application
|
||||||
- `mfkey` - MIFARE Classic key recovery tool
|
- `mfkey` - MIFARE Classic key recovery tool
|
||||||
|
- `picopass` - Picopass tool
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ static void view_port_input_callback(InputEvent* input_event, void* context) {
|
|||||||
furi_message_queue_put(app->input_queue, input_event, 0);
|
furi_message_queue_put(app->input_queue, input_event, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool input_queue_callback(FuriEventLoopObject* object, void* context) {
|
static void input_queue_callback(FuriEventLoopObject* object, void* context) {
|
||||||
FuriMessageQueue* queue = object;
|
FuriMessageQueue* queue = object;
|
||||||
EventLoopBlinkTestApp* app = context;
|
EventLoopBlinkTestApp* app = context;
|
||||||
|
|
||||||
@@ -107,8 +107,6 @@ static bool input_queue_callback(FuriEventLoopObject* object, void* context) {
|
|||||||
furi_event_loop_stop(app->event_loop);
|
furi_event_loop_stop(app->event_loop);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void blink_timer_callback(void* context) {
|
static void blink_timer_callback(void* context) {
|
||||||
|
|||||||
@@ -1,4 +1,15 @@
|
|||||||
let tests = require("tests");
|
let tests = require("tests");
|
||||||
|
let flipper = require("flipper");
|
||||||
|
|
||||||
tests.assert_eq(1337, 1337);
|
tests.assert_eq(1337, 1337);
|
||||||
tests.assert_eq("hello", "hello");
|
tests.assert_eq("hello", "hello");
|
||||||
|
|
||||||
|
tests.assert_eq("compatible", sdkCompatibilityStatus(0, 1));
|
||||||
|
tests.assert_eq("firmwareTooOld", sdkCompatibilityStatus(100500, 0));
|
||||||
|
tests.assert_eq("firmwareTooNew", sdkCompatibilityStatus(-100500, 0));
|
||||||
|
tests.assert_eq(true, doesSdkSupport(["baseline"]));
|
||||||
|
tests.assert_eq(false, doesSdkSupport(["abobus", "other-nonexistent-feature"]));
|
||||||
|
|
||||||
|
tests.assert_eq("flipperdevices", flipper.firmwareVendor);
|
||||||
|
tests.assert_eq(0, flipper.jsSdkVersion[0]);
|
||||||
|
tests.assert_eq(1, flipper.jsSdkVersion[1]);
|
||||||
|
|||||||
@@ -1,205 +0,0 @@
|
|||||||
#include "../test.h"
|
|
||||||
#include <furi.h>
|
|
||||||
#include <furi_hal.h>
|
|
||||||
|
|
||||||
#include <FreeRTOS.h>
|
|
||||||
#include <task.h>
|
|
||||||
|
|
||||||
#define TAG "TestFuriEventLoop"
|
|
||||||
|
|
||||||
#define EVENT_LOOP_EVENT_COUNT (256u)
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
FuriMessageQueue* mq;
|
|
||||||
|
|
||||||
FuriEventLoop* producer_event_loop;
|
|
||||||
uint32_t producer_counter;
|
|
||||||
|
|
||||||
FuriEventLoop* consumer_event_loop;
|
|
||||||
uint32_t consumer_counter;
|
|
||||||
} TestFuriData;
|
|
||||||
|
|
||||||
bool test_furi_event_loop_producer_mq_callback(FuriEventLoopObject* object, void* context) {
|
|
||||||
furi_check(context);
|
|
||||||
|
|
||||||
TestFuriData* data = context;
|
|
||||||
furi_check(data->mq == object, "Invalid queue");
|
|
||||||
|
|
||||||
FURI_LOG_I(
|
|
||||||
TAG, "producer_mq_callback: %lu %lu", data->producer_counter, data->consumer_counter);
|
|
||||||
|
|
||||||
if(data->producer_counter == EVENT_LOOP_EVENT_COUNT / 2) {
|
|
||||||
furi_event_loop_unsubscribe(data->producer_event_loop, data->mq);
|
|
||||||
furi_event_loop_subscribe_message_queue(
|
|
||||||
data->producer_event_loop,
|
|
||||||
data->mq,
|
|
||||||
FuriEventLoopEventOut,
|
|
||||||
test_furi_event_loop_producer_mq_callback,
|
|
||||||
data);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(data->producer_counter == EVENT_LOOP_EVENT_COUNT) {
|
|
||||||
furi_event_loop_stop(data->producer_event_loop);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
data->producer_counter++;
|
|
||||||
furi_check(
|
|
||||||
furi_message_queue_put(data->mq, &data->producer_counter, 0) == FuriStatusOk,
|
|
||||||
"furi_message_queue_put failed");
|
|
||||||
furi_delay_us(furi_hal_random_get() % 1000);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t test_furi_event_loop_producer(void* p) {
|
|
||||||
furi_check(p);
|
|
||||||
|
|
||||||
TestFuriData* data = p;
|
|
||||||
|
|
||||||
FURI_LOG_I(TAG, "producer start 1st run");
|
|
||||||
|
|
||||||
data->producer_event_loop = furi_event_loop_alloc();
|
|
||||||
furi_event_loop_subscribe_message_queue(
|
|
||||||
data->producer_event_loop,
|
|
||||||
data->mq,
|
|
||||||
FuriEventLoopEventOut,
|
|
||||||
test_furi_event_loop_producer_mq_callback,
|
|
||||||
data);
|
|
||||||
|
|
||||||
furi_event_loop_run(data->producer_event_loop);
|
|
||||||
|
|
||||||
// 2 EventLoop index, 0xFFFFFFFF - all possible flags, emulate uncleared flags
|
|
||||||
xTaskNotifyIndexed(xTaskGetCurrentTaskHandle(), 2, 0xFFFFFFFF, eSetBits);
|
|
||||||
|
|
||||||
furi_event_loop_unsubscribe(data->producer_event_loop, data->mq);
|
|
||||||
furi_event_loop_free(data->producer_event_loop);
|
|
||||||
|
|
||||||
FURI_LOG_I(TAG, "producer start 2nd run");
|
|
||||||
|
|
||||||
data->producer_counter = 0;
|
|
||||||
data->producer_event_loop = furi_event_loop_alloc();
|
|
||||||
|
|
||||||
furi_event_loop_subscribe_message_queue(
|
|
||||||
data->producer_event_loop,
|
|
||||||
data->mq,
|
|
||||||
FuriEventLoopEventOut,
|
|
||||||
test_furi_event_loop_producer_mq_callback,
|
|
||||||
data);
|
|
||||||
|
|
||||||
furi_event_loop_run(data->producer_event_loop);
|
|
||||||
|
|
||||||
furi_event_loop_unsubscribe(data->producer_event_loop, data->mq);
|
|
||||||
furi_event_loop_free(data->producer_event_loop);
|
|
||||||
|
|
||||||
FURI_LOG_I(TAG, "producer end");
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool test_furi_event_loop_consumer_mq_callback(FuriEventLoopObject* object, void* context) {
|
|
||||||
furi_check(context);
|
|
||||||
|
|
||||||
TestFuriData* data = context;
|
|
||||||
furi_check(data->mq == object);
|
|
||||||
|
|
||||||
furi_delay_us(furi_hal_random_get() % 1000);
|
|
||||||
furi_check(furi_message_queue_get(data->mq, &data->consumer_counter, 0) == FuriStatusOk);
|
|
||||||
|
|
||||||
FURI_LOG_I(
|
|
||||||
TAG, "consumer_mq_callback: %lu %lu", data->producer_counter, data->consumer_counter);
|
|
||||||
|
|
||||||
if(data->consumer_counter == EVENT_LOOP_EVENT_COUNT / 2) {
|
|
||||||
furi_event_loop_unsubscribe(data->consumer_event_loop, data->mq);
|
|
||||||
furi_event_loop_subscribe_message_queue(
|
|
||||||
data->consumer_event_loop,
|
|
||||||
data->mq,
|
|
||||||
FuriEventLoopEventIn,
|
|
||||||
test_furi_event_loop_consumer_mq_callback,
|
|
||||||
data);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(data->consumer_counter == EVENT_LOOP_EVENT_COUNT) {
|
|
||||||
furi_event_loop_stop(data->consumer_event_loop);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t test_furi_event_loop_consumer(void* p) {
|
|
||||||
furi_check(p);
|
|
||||||
|
|
||||||
TestFuriData* data = p;
|
|
||||||
|
|
||||||
FURI_LOG_I(TAG, "consumer start 1st run");
|
|
||||||
|
|
||||||
data->consumer_event_loop = furi_event_loop_alloc();
|
|
||||||
furi_event_loop_subscribe_message_queue(
|
|
||||||
data->consumer_event_loop,
|
|
||||||
data->mq,
|
|
||||||
FuriEventLoopEventIn,
|
|
||||||
test_furi_event_loop_consumer_mq_callback,
|
|
||||||
data);
|
|
||||||
|
|
||||||
furi_event_loop_run(data->consumer_event_loop);
|
|
||||||
|
|
||||||
// 2 EventLoop index, 0xFFFFFFFF - all possible flags, emulate uncleared flags
|
|
||||||
xTaskNotifyIndexed(xTaskGetCurrentTaskHandle(), 2, 0xFFFFFFFF, eSetBits);
|
|
||||||
|
|
||||||
furi_event_loop_unsubscribe(data->consumer_event_loop, data->mq);
|
|
||||||
furi_event_loop_free(data->consumer_event_loop);
|
|
||||||
|
|
||||||
FURI_LOG_I(TAG, "consumer start 2nd run");
|
|
||||||
|
|
||||||
data->consumer_counter = 0;
|
|
||||||
data->consumer_event_loop = furi_event_loop_alloc();
|
|
||||||
furi_event_loop_subscribe_message_queue(
|
|
||||||
data->consumer_event_loop,
|
|
||||||
data->mq,
|
|
||||||
FuriEventLoopEventIn,
|
|
||||||
test_furi_event_loop_consumer_mq_callback,
|
|
||||||
data);
|
|
||||||
|
|
||||||
furi_event_loop_run(data->consumer_event_loop);
|
|
||||||
|
|
||||||
furi_event_loop_unsubscribe(data->consumer_event_loop, data->mq);
|
|
||||||
furi_event_loop_free(data->consumer_event_loop);
|
|
||||||
|
|
||||||
FURI_LOG_I(TAG, "consumer end");
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void test_furi_event_loop(void) {
|
|
||||||
TestFuriData data = {};
|
|
||||||
|
|
||||||
data.mq = furi_message_queue_alloc(16, sizeof(uint32_t));
|
|
||||||
|
|
||||||
FuriThread* producer_thread = furi_thread_alloc();
|
|
||||||
furi_thread_set_name(producer_thread, "producer_thread");
|
|
||||||
furi_thread_set_stack_size(producer_thread, 1 * 1024);
|
|
||||||
furi_thread_set_callback(producer_thread, test_furi_event_loop_producer);
|
|
||||||
furi_thread_set_context(producer_thread, &data);
|
|
||||||
furi_thread_start(producer_thread);
|
|
||||||
|
|
||||||
FuriThread* consumer_thread = furi_thread_alloc();
|
|
||||||
furi_thread_set_name(consumer_thread, "consumer_thread");
|
|
||||||
furi_thread_set_stack_size(consumer_thread, 1 * 1024);
|
|
||||||
furi_thread_set_callback(consumer_thread, test_furi_event_loop_consumer);
|
|
||||||
furi_thread_set_context(consumer_thread, &data);
|
|
||||||
furi_thread_start(consumer_thread);
|
|
||||||
|
|
||||||
// Wait for thread to complete their tasks
|
|
||||||
furi_thread_join(producer_thread);
|
|
||||||
furi_thread_join(consumer_thread);
|
|
||||||
|
|
||||||
// The test itself
|
|
||||||
mu_assert_int_eq(data.producer_counter, data.consumer_counter);
|
|
||||||
mu_assert_int_eq(data.producer_counter, EVENT_LOOP_EVENT_COUNT);
|
|
||||||
|
|
||||||
// Release memory
|
|
||||||
furi_thread_free(consumer_thread);
|
|
||||||
furi_thread_free(producer_thread);
|
|
||||||
furi_message_queue_free(data.mq);
|
|
||||||
}
|
|
||||||
490
applications/debug/unit_tests/tests/furi/furi_event_loop_test.c
Normal file
490
applications/debug/unit_tests/tests/furi/furi_event_loop_test.c
Normal file
@@ -0,0 +1,490 @@
|
|||||||
|
#include "../test.h"
|
||||||
|
#include <furi.h>
|
||||||
|
#include <furi_hal.h>
|
||||||
|
|
||||||
|
#include <FreeRTOS.h>
|
||||||
|
#include <task.h>
|
||||||
|
|
||||||
|
#define TAG "TestFuriEventLoop"
|
||||||
|
|
||||||
|
#define MESSAGE_COUNT (256UL)
|
||||||
|
#define EVENT_FLAG_COUNT (23UL)
|
||||||
|
#define PRIMITIVE_COUNT (4UL)
|
||||||
|
#define RUN_COUNT (2UL)
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
FuriEventLoop* event_loop;
|
||||||
|
uint32_t message_queue_count;
|
||||||
|
uint32_t stream_buffer_count;
|
||||||
|
uint32_t event_flag_count;
|
||||||
|
uint32_t semaphore_count;
|
||||||
|
uint32_t primitives_tested;
|
||||||
|
} TestFuriEventLoopThread;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
FuriMessageQueue* message_queue;
|
||||||
|
FuriStreamBuffer* stream_buffer;
|
||||||
|
FuriEventFlag* event_flag;
|
||||||
|
FuriSemaphore* semaphore;
|
||||||
|
|
||||||
|
TestFuriEventLoopThread producer;
|
||||||
|
TestFuriEventLoopThread consumer;
|
||||||
|
} TestFuriEventLoopData;
|
||||||
|
|
||||||
|
static void test_furi_event_loop_pending_callback(void* context) {
|
||||||
|
furi_check(context);
|
||||||
|
|
||||||
|
TestFuriEventLoopThread* test_thread = context;
|
||||||
|
furi_check(test_thread->primitives_tested < PRIMITIVE_COUNT);
|
||||||
|
|
||||||
|
test_thread->primitives_tested++;
|
||||||
|
FURI_LOG_I(TAG, "primitives tested: %lu", test_thread->primitives_tested);
|
||||||
|
|
||||||
|
if(test_thread->primitives_tested == PRIMITIVE_COUNT) {
|
||||||
|
furi_event_loop_stop(test_thread->event_loop);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_furi_event_loop_thread_init(TestFuriEventLoopThread* test_thread) {
|
||||||
|
memset(test_thread, 0, sizeof(TestFuriEventLoopThread));
|
||||||
|
test_thread->event_loop = furi_event_loop_alloc();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_furi_event_loop_thread_run_and_cleanup(TestFuriEventLoopThread* test_thread) {
|
||||||
|
furi_event_loop_run(test_thread->event_loop);
|
||||||
|
// 2 EventLoop index, 0xFFFFFFFF - all possible flags, emulate uncleared flags
|
||||||
|
xTaskNotifyIndexed(xTaskGetCurrentTaskHandle(), 2, 0xFFFFFFFF, eSetBits);
|
||||||
|
furi_event_loop_free(test_thread->event_loop);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_furi_event_loop_producer_message_queue_callback(
|
||||||
|
FuriEventLoopObject* object,
|
||||||
|
void* context) {
|
||||||
|
furi_check(context);
|
||||||
|
|
||||||
|
TestFuriEventLoopData* data = context;
|
||||||
|
furi_check(data->message_queue == object);
|
||||||
|
|
||||||
|
FURI_LOG_I(
|
||||||
|
TAG,
|
||||||
|
"producer MessageQueue: %lu %lu",
|
||||||
|
data->producer.message_queue_count,
|
||||||
|
data->consumer.message_queue_count);
|
||||||
|
|
||||||
|
if(data->producer.message_queue_count == MESSAGE_COUNT / 2) {
|
||||||
|
furi_event_loop_unsubscribe(data->producer.event_loop, data->message_queue);
|
||||||
|
furi_event_loop_subscribe_message_queue(
|
||||||
|
data->producer.event_loop,
|
||||||
|
data->message_queue,
|
||||||
|
FuriEventLoopEventOut,
|
||||||
|
test_furi_event_loop_producer_message_queue_callback,
|
||||||
|
data);
|
||||||
|
|
||||||
|
} else if(data->producer.message_queue_count == MESSAGE_COUNT) {
|
||||||
|
furi_event_loop_unsubscribe(data->producer.event_loop, data->message_queue);
|
||||||
|
furi_event_loop_pend_callback(
|
||||||
|
data->producer.event_loop, test_furi_event_loop_pending_callback, &data->producer);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
data->producer.message_queue_count++;
|
||||||
|
|
||||||
|
furi_check(
|
||||||
|
furi_message_queue_put(data->message_queue, &data->producer.message_queue_count, 0) ==
|
||||||
|
FuriStatusOk);
|
||||||
|
|
||||||
|
furi_delay_us(furi_hal_random_get() % 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_furi_event_loop_producer_stream_buffer_callback(
|
||||||
|
FuriEventLoopObject* object,
|
||||||
|
void* context) {
|
||||||
|
furi_check(context);
|
||||||
|
|
||||||
|
TestFuriEventLoopData* data = context;
|
||||||
|
furi_check(data->stream_buffer == object);
|
||||||
|
|
||||||
|
TestFuriEventLoopThread* producer = &data->producer;
|
||||||
|
TestFuriEventLoopThread* consumer = &data->consumer;
|
||||||
|
|
||||||
|
FURI_LOG_I(
|
||||||
|
TAG,
|
||||||
|
"producer StreamBuffer: %lu %lu",
|
||||||
|
producer->stream_buffer_count,
|
||||||
|
consumer->stream_buffer_count);
|
||||||
|
|
||||||
|
if(producer->stream_buffer_count == MESSAGE_COUNT / 2) {
|
||||||
|
furi_event_loop_unsubscribe(producer->event_loop, data->stream_buffer);
|
||||||
|
furi_event_loop_subscribe_stream_buffer(
|
||||||
|
producer->event_loop,
|
||||||
|
data->stream_buffer,
|
||||||
|
FuriEventLoopEventOut,
|
||||||
|
test_furi_event_loop_producer_stream_buffer_callback,
|
||||||
|
data);
|
||||||
|
|
||||||
|
} else if(producer->stream_buffer_count == MESSAGE_COUNT) {
|
||||||
|
furi_event_loop_unsubscribe(producer->event_loop, data->stream_buffer);
|
||||||
|
furi_event_loop_pend_callback(
|
||||||
|
producer->event_loop, test_furi_event_loop_pending_callback, producer);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
producer->stream_buffer_count++;
|
||||||
|
|
||||||
|
furi_check(
|
||||||
|
furi_stream_buffer_send(
|
||||||
|
data->stream_buffer, &producer->stream_buffer_count, sizeof(uint32_t), 0) ==
|
||||||
|
sizeof(uint32_t));
|
||||||
|
|
||||||
|
furi_delay_us(furi_hal_random_get() % 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
test_furi_event_loop_producer_event_flag_callback(FuriEventLoopObject* object, void* context) {
|
||||||
|
furi_check(context);
|
||||||
|
|
||||||
|
TestFuriEventLoopData* data = context;
|
||||||
|
furi_check(data->event_flag == object);
|
||||||
|
|
||||||
|
const uint32_t producer_flags = (1UL << data->producer.event_flag_count);
|
||||||
|
const uint32_t consumer_flags = (1UL << data->consumer.event_flag_count);
|
||||||
|
|
||||||
|
FURI_LOG_I(TAG, "producer EventFlag: 0x%06lX 0x%06lX", producer_flags, consumer_flags);
|
||||||
|
|
||||||
|
furi_check(furi_event_flag_set(data->event_flag, producer_flags) & producer_flags);
|
||||||
|
|
||||||
|
if(data->producer.event_flag_count == EVENT_FLAG_COUNT / 2) {
|
||||||
|
furi_event_loop_unsubscribe(data->producer.event_loop, data->event_flag);
|
||||||
|
furi_event_loop_subscribe_event_flag(
|
||||||
|
data->producer.event_loop,
|
||||||
|
data->event_flag,
|
||||||
|
FuriEventLoopEventOut,
|
||||||
|
test_furi_event_loop_producer_event_flag_callback,
|
||||||
|
data);
|
||||||
|
|
||||||
|
} else if(data->producer.event_flag_count == EVENT_FLAG_COUNT) {
|
||||||
|
furi_event_loop_unsubscribe(data->producer.event_loop, data->event_flag);
|
||||||
|
furi_event_loop_pend_callback(
|
||||||
|
data->producer.event_loop, test_furi_event_loop_pending_callback, &data->producer);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
data->producer.event_flag_count++;
|
||||||
|
|
||||||
|
furi_delay_us(furi_hal_random_get() % 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
test_furi_event_loop_producer_semaphore_callback(FuriEventLoopObject* object, void* context) {
|
||||||
|
furi_check(context);
|
||||||
|
|
||||||
|
TestFuriEventLoopData* data = context;
|
||||||
|
furi_check(data->semaphore == object);
|
||||||
|
|
||||||
|
TestFuriEventLoopThread* producer = &data->producer;
|
||||||
|
TestFuriEventLoopThread* consumer = &data->consumer;
|
||||||
|
|
||||||
|
FURI_LOG_I(
|
||||||
|
TAG, "producer Semaphore: %lu %lu", producer->semaphore_count, consumer->semaphore_count);
|
||||||
|
furi_check(furi_semaphore_release(data->semaphore) == FuriStatusOk);
|
||||||
|
|
||||||
|
if(producer->semaphore_count == MESSAGE_COUNT / 2) {
|
||||||
|
furi_event_loop_unsubscribe(producer->event_loop, data->semaphore);
|
||||||
|
furi_event_loop_subscribe_semaphore(
|
||||||
|
producer->event_loop,
|
||||||
|
data->semaphore,
|
||||||
|
FuriEventLoopEventOut,
|
||||||
|
test_furi_event_loop_producer_semaphore_callback,
|
||||||
|
data);
|
||||||
|
|
||||||
|
} else if(producer->semaphore_count == MESSAGE_COUNT) {
|
||||||
|
furi_event_loop_unsubscribe(producer->event_loop, data->semaphore);
|
||||||
|
furi_event_loop_pend_callback(
|
||||||
|
producer->event_loop, test_furi_event_loop_pending_callback, producer);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
data->producer.semaphore_count++;
|
||||||
|
|
||||||
|
furi_delay_us(furi_hal_random_get() % 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int32_t test_furi_event_loop_producer(void* p) {
|
||||||
|
furi_check(p);
|
||||||
|
|
||||||
|
TestFuriEventLoopData* data = p;
|
||||||
|
TestFuriEventLoopThread* producer = &data->producer;
|
||||||
|
|
||||||
|
for(uint32_t i = 0; i < RUN_COUNT; ++i) {
|
||||||
|
FURI_LOG_I(TAG, "producer start run %lu", i);
|
||||||
|
|
||||||
|
test_furi_event_loop_thread_init(producer);
|
||||||
|
|
||||||
|
furi_event_loop_subscribe_message_queue(
|
||||||
|
producer->event_loop,
|
||||||
|
data->message_queue,
|
||||||
|
FuriEventLoopEventOut,
|
||||||
|
test_furi_event_loop_producer_message_queue_callback,
|
||||||
|
data);
|
||||||
|
furi_event_loop_subscribe_stream_buffer(
|
||||||
|
producer->event_loop,
|
||||||
|
data->stream_buffer,
|
||||||
|
FuriEventLoopEventOut,
|
||||||
|
test_furi_event_loop_producer_stream_buffer_callback,
|
||||||
|
data);
|
||||||
|
furi_event_loop_subscribe_event_flag(
|
||||||
|
producer->event_loop,
|
||||||
|
data->event_flag,
|
||||||
|
FuriEventLoopEventOut,
|
||||||
|
test_furi_event_loop_producer_event_flag_callback,
|
||||||
|
data);
|
||||||
|
furi_event_loop_subscribe_semaphore(
|
||||||
|
producer->event_loop,
|
||||||
|
data->semaphore,
|
||||||
|
FuriEventLoopEventOut,
|
||||||
|
test_furi_event_loop_producer_semaphore_callback,
|
||||||
|
data);
|
||||||
|
|
||||||
|
test_furi_event_loop_thread_run_and_cleanup(producer);
|
||||||
|
}
|
||||||
|
|
||||||
|
FURI_LOG_I(TAG, "producer end");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_furi_event_loop_consumer_message_queue_callback(
|
||||||
|
FuriEventLoopObject* object,
|
||||||
|
void* context) {
|
||||||
|
furi_check(context);
|
||||||
|
|
||||||
|
TestFuriEventLoopData* data = context;
|
||||||
|
furi_check(data->message_queue == object);
|
||||||
|
|
||||||
|
furi_delay_us(furi_hal_random_get() % 100);
|
||||||
|
|
||||||
|
furi_check(
|
||||||
|
furi_message_queue_get(data->message_queue, &data->consumer.message_queue_count, 0) ==
|
||||||
|
FuriStatusOk);
|
||||||
|
|
||||||
|
FURI_LOG_I(
|
||||||
|
TAG,
|
||||||
|
"consumer MessageQueue: %lu %lu",
|
||||||
|
data->producer.message_queue_count,
|
||||||
|
data->consumer.message_queue_count);
|
||||||
|
|
||||||
|
if(data->consumer.message_queue_count == MESSAGE_COUNT / 2) {
|
||||||
|
furi_event_loop_unsubscribe(data->consumer.event_loop, data->message_queue);
|
||||||
|
furi_event_loop_subscribe_message_queue(
|
||||||
|
data->consumer.event_loop,
|
||||||
|
data->message_queue,
|
||||||
|
FuriEventLoopEventIn,
|
||||||
|
test_furi_event_loop_consumer_message_queue_callback,
|
||||||
|
data);
|
||||||
|
|
||||||
|
} else if(data->consumer.message_queue_count == MESSAGE_COUNT) {
|
||||||
|
furi_event_loop_unsubscribe(data->consumer.event_loop, data->message_queue);
|
||||||
|
furi_event_loop_pend_callback(
|
||||||
|
data->consumer.event_loop, test_furi_event_loop_pending_callback, &data->consumer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_furi_event_loop_consumer_stream_buffer_callback(
|
||||||
|
FuriEventLoopObject* object,
|
||||||
|
void* context) {
|
||||||
|
furi_check(context);
|
||||||
|
|
||||||
|
TestFuriEventLoopData* data = context;
|
||||||
|
furi_check(data->stream_buffer == object);
|
||||||
|
|
||||||
|
TestFuriEventLoopThread* producer = &data->producer;
|
||||||
|
TestFuriEventLoopThread* consumer = &data->consumer;
|
||||||
|
|
||||||
|
furi_delay_us(furi_hal_random_get() % 100);
|
||||||
|
|
||||||
|
furi_check(
|
||||||
|
furi_stream_buffer_receive(
|
||||||
|
data->stream_buffer, &consumer->stream_buffer_count, sizeof(uint32_t), 0) ==
|
||||||
|
sizeof(uint32_t));
|
||||||
|
|
||||||
|
FURI_LOG_I(
|
||||||
|
TAG,
|
||||||
|
"consumer StreamBuffer: %lu %lu",
|
||||||
|
producer->stream_buffer_count,
|
||||||
|
consumer->stream_buffer_count);
|
||||||
|
|
||||||
|
if(consumer->stream_buffer_count == MESSAGE_COUNT / 2) {
|
||||||
|
furi_event_loop_unsubscribe(consumer->event_loop, data->stream_buffer);
|
||||||
|
furi_event_loop_subscribe_stream_buffer(
|
||||||
|
consumer->event_loop,
|
||||||
|
data->stream_buffer,
|
||||||
|
FuriEventLoopEventIn,
|
||||||
|
test_furi_event_loop_consumer_stream_buffer_callback,
|
||||||
|
data);
|
||||||
|
|
||||||
|
} else if(consumer->stream_buffer_count == MESSAGE_COUNT) {
|
||||||
|
furi_event_loop_unsubscribe(data->consumer.event_loop, data->stream_buffer);
|
||||||
|
furi_event_loop_pend_callback(
|
||||||
|
consumer->event_loop, test_furi_event_loop_pending_callback, consumer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
test_furi_event_loop_consumer_event_flag_callback(FuriEventLoopObject* object, void* context) {
|
||||||
|
furi_check(context);
|
||||||
|
|
||||||
|
TestFuriEventLoopData* data = context;
|
||||||
|
furi_check(data->event_flag == object);
|
||||||
|
|
||||||
|
furi_delay_us(furi_hal_random_get() % 100);
|
||||||
|
|
||||||
|
const uint32_t producer_flags = (1UL << data->producer.event_flag_count);
|
||||||
|
const uint32_t consumer_flags = (1UL << data->consumer.event_flag_count);
|
||||||
|
|
||||||
|
furi_check(
|
||||||
|
furi_event_flag_wait(data->event_flag, consumer_flags, FuriFlagWaitAny, 0) &
|
||||||
|
consumer_flags);
|
||||||
|
|
||||||
|
FURI_LOG_I(TAG, "consumer EventFlag: 0x%06lX 0x%06lX", producer_flags, consumer_flags);
|
||||||
|
|
||||||
|
if(data->consumer.event_flag_count == EVENT_FLAG_COUNT / 2) {
|
||||||
|
furi_event_loop_unsubscribe(data->consumer.event_loop, data->event_flag);
|
||||||
|
furi_event_loop_subscribe_event_flag(
|
||||||
|
data->consumer.event_loop,
|
||||||
|
data->event_flag,
|
||||||
|
FuriEventLoopEventIn,
|
||||||
|
test_furi_event_loop_consumer_event_flag_callback,
|
||||||
|
data);
|
||||||
|
|
||||||
|
} else if(data->consumer.event_flag_count == EVENT_FLAG_COUNT) {
|
||||||
|
furi_event_loop_unsubscribe(data->consumer.event_loop, data->event_flag);
|
||||||
|
furi_event_loop_pend_callback(
|
||||||
|
data->consumer.event_loop, test_furi_event_loop_pending_callback, &data->consumer);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
data->consumer.event_flag_count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
test_furi_event_loop_consumer_semaphore_callback(FuriEventLoopObject* object, void* context) {
|
||||||
|
furi_check(context);
|
||||||
|
|
||||||
|
TestFuriEventLoopData* data = context;
|
||||||
|
furi_check(data->semaphore == object);
|
||||||
|
|
||||||
|
furi_delay_us(furi_hal_random_get() % 100);
|
||||||
|
|
||||||
|
TestFuriEventLoopThread* producer = &data->producer;
|
||||||
|
TestFuriEventLoopThread* consumer = &data->consumer;
|
||||||
|
|
||||||
|
furi_check(furi_semaphore_acquire(data->semaphore, 0) == FuriStatusOk);
|
||||||
|
|
||||||
|
FURI_LOG_I(
|
||||||
|
TAG, "consumer Semaphore: %lu %lu", producer->semaphore_count, consumer->semaphore_count);
|
||||||
|
|
||||||
|
if(consumer->semaphore_count == MESSAGE_COUNT / 2) {
|
||||||
|
furi_event_loop_unsubscribe(consumer->event_loop, data->semaphore);
|
||||||
|
furi_event_loop_subscribe_semaphore(
|
||||||
|
consumer->event_loop,
|
||||||
|
data->semaphore,
|
||||||
|
FuriEventLoopEventIn,
|
||||||
|
test_furi_event_loop_consumer_semaphore_callback,
|
||||||
|
data);
|
||||||
|
|
||||||
|
} else if(consumer->semaphore_count == MESSAGE_COUNT) {
|
||||||
|
furi_event_loop_unsubscribe(consumer->event_loop, data->semaphore);
|
||||||
|
furi_event_loop_pend_callback(
|
||||||
|
consumer->event_loop, test_furi_event_loop_pending_callback, consumer);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
data->consumer.semaphore_count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int32_t test_furi_event_loop_consumer(void* p) {
|
||||||
|
furi_check(p);
|
||||||
|
|
||||||
|
TestFuriEventLoopData* data = p;
|
||||||
|
TestFuriEventLoopThread* consumer = &data->consumer;
|
||||||
|
|
||||||
|
for(uint32_t i = 0; i < RUN_COUNT; ++i) {
|
||||||
|
FURI_LOG_I(TAG, "consumer start run %lu", i);
|
||||||
|
|
||||||
|
test_furi_event_loop_thread_init(consumer);
|
||||||
|
|
||||||
|
furi_event_loop_subscribe_message_queue(
|
||||||
|
consumer->event_loop,
|
||||||
|
data->message_queue,
|
||||||
|
FuriEventLoopEventIn,
|
||||||
|
test_furi_event_loop_consumer_message_queue_callback,
|
||||||
|
data);
|
||||||
|
furi_event_loop_subscribe_stream_buffer(
|
||||||
|
consumer->event_loop,
|
||||||
|
data->stream_buffer,
|
||||||
|
FuriEventLoopEventIn,
|
||||||
|
test_furi_event_loop_consumer_stream_buffer_callback,
|
||||||
|
data);
|
||||||
|
furi_event_loop_subscribe_event_flag(
|
||||||
|
consumer->event_loop,
|
||||||
|
data->event_flag,
|
||||||
|
FuriEventLoopEventIn,
|
||||||
|
test_furi_event_loop_consumer_event_flag_callback,
|
||||||
|
data);
|
||||||
|
furi_event_loop_subscribe_semaphore(
|
||||||
|
consumer->event_loop,
|
||||||
|
data->semaphore,
|
||||||
|
FuriEventLoopEventIn,
|
||||||
|
test_furi_event_loop_consumer_semaphore_callback,
|
||||||
|
data);
|
||||||
|
|
||||||
|
test_furi_event_loop_thread_run_and_cleanup(consumer);
|
||||||
|
}
|
||||||
|
|
||||||
|
FURI_LOG_I(TAG, "consumer end");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_furi_event_loop(void) {
|
||||||
|
TestFuriEventLoopData data = {};
|
||||||
|
|
||||||
|
data.message_queue = furi_message_queue_alloc(16, sizeof(uint32_t));
|
||||||
|
data.stream_buffer = furi_stream_buffer_alloc(16, sizeof(uint32_t));
|
||||||
|
data.event_flag = furi_event_flag_alloc();
|
||||||
|
data.semaphore = furi_semaphore_alloc(8, 0);
|
||||||
|
|
||||||
|
FuriThread* producer_thread =
|
||||||
|
furi_thread_alloc_ex("producer_thread", 1 * 1024, test_furi_event_loop_producer, &data);
|
||||||
|
furi_thread_start(producer_thread);
|
||||||
|
|
||||||
|
FuriThread* consumer_thread =
|
||||||
|
furi_thread_alloc_ex("consumer_thread", 1 * 1024, test_furi_event_loop_consumer, &data);
|
||||||
|
furi_thread_start(consumer_thread);
|
||||||
|
|
||||||
|
// Wait for thread to complete their tasks
|
||||||
|
furi_thread_join(producer_thread);
|
||||||
|
furi_thread_join(consumer_thread);
|
||||||
|
|
||||||
|
TestFuriEventLoopThread* producer = &data.producer;
|
||||||
|
TestFuriEventLoopThread* consumer = &data.consumer;
|
||||||
|
|
||||||
|
// The test itself
|
||||||
|
mu_assert_int_eq(producer->message_queue_count, consumer->message_queue_count);
|
||||||
|
mu_assert_int_eq(producer->message_queue_count, MESSAGE_COUNT);
|
||||||
|
mu_assert_int_eq(producer->stream_buffer_count, consumer->stream_buffer_count);
|
||||||
|
mu_assert_int_eq(producer->stream_buffer_count, MESSAGE_COUNT);
|
||||||
|
mu_assert_int_eq(producer->event_flag_count, consumer->event_flag_count);
|
||||||
|
mu_assert_int_eq(producer->event_flag_count, EVENT_FLAG_COUNT);
|
||||||
|
mu_assert_int_eq(producer->semaphore_count, consumer->semaphore_count);
|
||||||
|
mu_assert_int_eq(producer->semaphore_count, MESSAGE_COUNT);
|
||||||
|
|
||||||
|
// Release memory
|
||||||
|
furi_thread_free(consumer_thread);
|
||||||
|
furi_thread_free(producer_thread);
|
||||||
|
|
||||||
|
furi_message_queue_free(data.message_queue);
|
||||||
|
furi_stream_buffer_free(data.stream_buffer);
|
||||||
|
furi_event_flag_free(data.event_flag);
|
||||||
|
furi_semaphore_free(data.semaphore);
|
||||||
|
}
|
||||||
103
applications/debug/unit_tests/tests/furi/furi_primitives_test.c
Normal file
103
applications/debug/unit_tests/tests/furi/furi_primitives_test.c
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
#include <furi.h>
|
||||||
|
#include "../test.h" // IWYU pragma: keep
|
||||||
|
|
||||||
|
#define MESSAGE_QUEUE_CAPACITY (16U)
|
||||||
|
#define MESSAGE_QUEUE_ELEMENT_SIZE (sizeof(uint32_t))
|
||||||
|
|
||||||
|
#define STREAM_BUFFER_SIZE (32U)
|
||||||
|
#define STREAM_BUFFER_TRG_LEVEL (STREAM_BUFFER_SIZE / 2U)
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
FuriMessageQueue* message_queue;
|
||||||
|
FuriStreamBuffer* stream_buffer;
|
||||||
|
} TestFuriPrimitivesData;
|
||||||
|
|
||||||
|
static void test_furi_message_queue(TestFuriPrimitivesData* data) {
|
||||||
|
FuriMessageQueue* message_queue = data->message_queue;
|
||||||
|
|
||||||
|
mu_assert_int_eq(0, furi_message_queue_get_count(message_queue));
|
||||||
|
mu_assert_int_eq(MESSAGE_QUEUE_CAPACITY, furi_message_queue_get_space(message_queue));
|
||||||
|
mu_assert_int_eq(MESSAGE_QUEUE_CAPACITY, furi_message_queue_get_capacity(message_queue));
|
||||||
|
mu_assert_int_eq(
|
||||||
|
MESSAGE_QUEUE_ELEMENT_SIZE, furi_message_queue_get_message_size(message_queue));
|
||||||
|
|
||||||
|
for(uint32_t i = 0;; ++i) {
|
||||||
|
mu_assert_int_eq(MESSAGE_QUEUE_CAPACITY - i, furi_message_queue_get_space(message_queue));
|
||||||
|
mu_assert_int_eq(i, furi_message_queue_get_count(message_queue));
|
||||||
|
|
||||||
|
if(furi_message_queue_put(message_queue, &i, 0) != FuriStatusOk) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mu_assert_int_eq(0, furi_message_queue_get_space(message_queue));
|
||||||
|
mu_assert_int_eq(MESSAGE_QUEUE_CAPACITY, furi_message_queue_get_count(message_queue));
|
||||||
|
|
||||||
|
for(uint32_t i = 0;; ++i) {
|
||||||
|
mu_assert_int_eq(i, furi_message_queue_get_space(message_queue));
|
||||||
|
mu_assert_int_eq(MESSAGE_QUEUE_CAPACITY - i, furi_message_queue_get_count(message_queue));
|
||||||
|
|
||||||
|
uint32_t value;
|
||||||
|
if(furi_message_queue_get(message_queue, &value, 0) != FuriStatusOk) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
mu_assert_int_eq(i, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
mu_assert_int_eq(0, furi_message_queue_get_count(message_queue));
|
||||||
|
mu_assert_int_eq(MESSAGE_QUEUE_CAPACITY, furi_message_queue_get_space(message_queue));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_furi_stream_buffer(TestFuriPrimitivesData* data) {
|
||||||
|
FuriStreamBuffer* stream_buffer = data->stream_buffer;
|
||||||
|
|
||||||
|
mu_assert(furi_stream_buffer_is_empty(stream_buffer), "Must be empty");
|
||||||
|
mu_assert(!furi_stream_buffer_is_full(stream_buffer), "Must be not full");
|
||||||
|
mu_assert_int_eq(0, furi_stream_buffer_bytes_available(stream_buffer));
|
||||||
|
mu_assert_int_eq(STREAM_BUFFER_SIZE, furi_stream_buffer_spaces_available(stream_buffer));
|
||||||
|
|
||||||
|
for(uint8_t i = 0;; ++i) {
|
||||||
|
mu_assert_int_eq(i, furi_stream_buffer_bytes_available(stream_buffer));
|
||||||
|
mu_assert_int_eq(
|
||||||
|
STREAM_BUFFER_SIZE - i, furi_stream_buffer_spaces_available(stream_buffer));
|
||||||
|
|
||||||
|
if(furi_stream_buffer_send(stream_buffer, &i, sizeof(uint8_t), 0) != sizeof(uint8_t)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mu_assert(!furi_stream_buffer_is_empty(stream_buffer), "Must be not empty");
|
||||||
|
mu_assert(furi_stream_buffer_is_full(stream_buffer), "Must be full");
|
||||||
|
mu_assert_int_eq(STREAM_BUFFER_SIZE, furi_stream_buffer_bytes_available(stream_buffer));
|
||||||
|
mu_assert_int_eq(0, furi_stream_buffer_spaces_available(stream_buffer));
|
||||||
|
|
||||||
|
for(uint8_t i = 0;; ++i) {
|
||||||
|
mu_assert_int_eq(
|
||||||
|
STREAM_BUFFER_SIZE - i, furi_stream_buffer_bytes_available(stream_buffer));
|
||||||
|
mu_assert_int_eq(i, furi_stream_buffer_spaces_available(stream_buffer));
|
||||||
|
|
||||||
|
uint8_t value;
|
||||||
|
if(furi_stream_buffer_receive(stream_buffer, &value, sizeof(uint8_t), 0) !=
|
||||||
|
sizeof(uint8_t)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
mu_assert_int_eq(i, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is a stub that needs expanding
|
||||||
|
void test_furi_primitives(void) {
|
||||||
|
TestFuriPrimitivesData data = {
|
||||||
|
.message_queue =
|
||||||
|
furi_message_queue_alloc(MESSAGE_QUEUE_CAPACITY, MESSAGE_QUEUE_ELEMENT_SIZE),
|
||||||
|
.stream_buffer = furi_stream_buffer_alloc(STREAM_BUFFER_SIZE, STREAM_BUFFER_TRG_LEVEL),
|
||||||
|
};
|
||||||
|
|
||||||
|
test_furi_message_queue(&data);
|
||||||
|
test_furi_stream_buffer(&data);
|
||||||
|
|
||||||
|
furi_message_queue_free(data.message_queue);
|
||||||
|
furi_stream_buffer_free(data.stream_buffer);
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ void test_furi_pubsub(void);
|
|||||||
void test_furi_memmgr(void);
|
void test_furi_memmgr(void);
|
||||||
void test_furi_event_loop(void);
|
void test_furi_event_loop(void);
|
||||||
void test_errno_saving(void);
|
void test_errno_saving(void);
|
||||||
|
void test_furi_primitives(void);
|
||||||
|
|
||||||
static int foo = 0;
|
static int foo = 0;
|
||||||
|
|
||||||
@@ -47,6 +48,10 @@ MU_TEST(mu_test_errno_saving) {
|
|||||||
test_errno_saving();
|
test_errno_saving();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MU_TEST(mu_test_furi_primitives) {
|
||||||
|
test_furi_primitives();
|
||||||
|
}
|
||||||
|
|
||||||
MU_TEST_SUITE(test_suite) {
|
MU_TEST_SUITE(test_suite) {
|
||||||
MU_SUITE_CONFIGURE(&test_setup, &test_teardown);
|
MU_SUITE_CONFIGURE(&test_setup, &test_teardown);
|
||||||
MU_RUN_TEST(test_check);
|
MU_RUN_TEST(test_check);
|
||||||
@@ -57,6 +62,7 @@ MU_TEST_SUITE(test_suite) {
|
|||||||
MU_RUN_TEST(mu_test_furi_memmgr);
|
MU_RUN_TEST(mu_test_furi_memmgr);
|
||||||
MU_RUN_TEST(mu_test_furi_event_loop);
|
MU_RUN_TEST(mu_test_furi_event_loop);
|
||||||
MU_RUN_TEST(mu_test_errno_saving);
|
MU_RUN_TEST(mu_test_errno_saving);
|
||||||
|
MU_RUN_TEST(mu_test_furi_primitives);
|
||||||
}
|
}
|
||||||
|
|
||||||
int run_minunit_test_furi(void) {
|
int run_minunit_test_furi(void) {
|
||||||
|
|||||||
@@ -1,3 +1,12 @@
|
|||||||
|
App(
|
||||||
|
appid="example_event_loop_event_flags",
|
||||||
|
name="Example: Event Loop Event Flags",
|
||||||
|
apptype=FlipperAppType.EXTERNAL,
|
||||||
|
sources=["example_event_loop_event_flags.c"],
|
||||||
|
entry_point="example_event_loop_event_flags_app",
|
||||||
|
fap_category="Examples",
|
||||||
|
)
|
||||||
|
|
||||||
App(
|
App(
|
||||||
appid="example_event_loop_timer",
|
appid="example_event_loop_timer",
|
||||||
name="Example: Event Loop Timer",
|
name="Example: Event Loop Timer",
|
||||||
|
|||||||
@@ -0,0 +1,173 @@
|
|||||||
|
/**
|
||||||
|
* @file example_event_loop_event_flags.c
|
||||||
|
* @brief Example application demonstrating the use of the FuriEventFlag primitive in FuriEventLoop instances.
|
||||||
|
*
|
||||||
|
* This application receives keystrokes from the input service and sets the appropriate flags,
|
||||||
|
* which are subsequently processed in the event loop
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <furi.h>
|
||||||
|
#include <gui/gui.h>
|
||||||
|
#include <gui/view_port.h>
|
||||||
|
|
||||||
|
#include <furi_hal_random.h>
|
||||||
|
|
||||||
|
#define TAG "ExampleEventLoopEventFlags"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
Gui* gui;
|
||||||
|
ViewPort* view_port;
|
||||||
|
FuriEventLoop* event_loop;
|
||||||
|
FuriEventFlag* event_flag;
|
||||||
|
} EventLoopEventFlagsApp;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
EventLoopEventFlagsOk = (1 << 0),
|
||||||
|
EventLoopEventFlagsUp = (1 << 1),
|
||||||
|
EventLoopEventFlagsDown = (1 << 2),
|
||||||
|
EventLoopEventFlagsLeft = (1 << 3),
|
||||||
|
EventLoopEventFlagsRight = (1 << 4),
|
||||||
|
EventLoopEventFlagsBack = (1 << 5),
|
||||||
|
EventLoopEventFlagsExit = (1 << 6),
|
||||||
|
} EventLoopEventFlags;
|
||||||
|
|
||||||
|
#define EVENT_LOOP_EVENT_FLAGS_MASK \
|
||||||
|
(EventLoopEventFlagsOk | EventLoopEventFlagsUp | EventLoopEventFlagsDown | \
|
||||||
|
EventLoopEventFlagsLeft | EventLoopEventFlagsRight | EventLoopEventFlagsBack | \
|
||||||
|
EventLoopEventFlagsExit)
|
||||||
|
|
||||||
|
// This function is executed in the GUI context each time an input event occurs (e.g. the user pressed a key)
|
||||||
|
static void event_loop_event_flags_app_input_callback(InputEvent* event, void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
EventLoopEventFlagsApp* app = context;
|
||||||
|
UNUSED(app);
|
||||||
|
|
||||||
|
if(event->type == InputTypePress) {
|
||||||
|
if(event->key == InputKeyOk) {
|
||||||
|
furi_event_flag_set(app->event_flag, EventLoopEventFlagsOk);
|
||||||
|
} else if(event->key == InputKeyUp) {
|
||||||
|
furi_event_flag_set(app->event_flag, EventLoopEventFlagsUp);
|
||||||
|
} else if(event->key == InputKeyDown) {
|
||||||
|
furi_event_flag_set(app->event_flag, EventLoopEventFlagsDown);
|
||||||
|
} else if(event->key == InputKeyLeft) {
|
||||||
|
furi_event_flag_set(app->event_flag, EventLoopEventFlagsLeft);
|
||||||
|
} else if(event->key == InputKeyRight) {
|
||||||
|
furi_event_flag_set(app->event_flag, EventLoopEventFlagsRight);
|
||||||
|
} else if(event->key == InputKeyBack) {
|
||||||
|
furi_event_flag_set(app->event_flag, EventLoopEventFlagsBack);
|
||||||
|
}
|
||||||
|
} else if(event->type == InputTypeLong) {
|
||||||
|
if(event->key == InputKeyBack) {
|
||||||
|
furi_event_flag_set(app->event_flag, EventLoopEventFlagsExit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function is executed each time a new event flag is inserted in the input event flag.
|
||||||
|
static void
|
||||||
|
event_loop_event_flags_app_event_flags_callback(FuriEventLoopObject* object, void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
EventLoopEventFlagsApp* app = context;
|
||||||
|
|
||||||
|
furi_assert(object == app->event_flag);
|
||||||
|
|
||||||
|
EventLoopEventFlags events =
|
||||||
|
furi_event_flag_wait(app->event_flag, EVENT_LOOP_EVENT_FLAGS_MASK, FuriFlagWaitAny, 0);
|
||||||
|
furi_check((events) != 0);
|
||||||
|
|
||||||
|
if(events & EventLoopEventFlagsOk) {
|
||||||
|
FURI_LOG_I(TAG, "Press \"Ok\"");
|
||||||
|
}
|
||||||
|
if(events & EventLoopEventFlagsUp) {
|
||||||
|
FURI_LOG_I(TAG, "Press \"Up\"");
|
||||||
|
}
|
||||||
|
if(events & EventLoopEventFlagsDown) {
|
||||||
|
FURI_LOG_I(TAG, "Press \"Down\"");
|
||||||
|
}
|
||||||
|
if(events & EventLoopEventFlagsLeft) {
|
||||||
|
FURI_LOG_I(TAG, "Press \"Left\"");
|
||||||
|
}
|
||||||
|
if(events & EventLoopEventFlagsRight) {
|
||||||
|
FURI_LOG_I(TAG, "Press \"Right\"");
|
||||||
|
}
|
||||||
|
if(events & EventLoopEventFlagsBack) {
|
||||||
|
FURI_LOG_I(TAG, "Press \"Back\"");
|
||||||
|
}
|
||||||
|
if(events & EventLoopEventFlagsExit) {
|
||||||
|
FURI_LOG_I(TAG, "Exit App");
|
||||||
|
furi_event_loop_stop(app->event_loop);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static EventLoopEventFlagsApp* event_loop_event_flags_app_alloc(void) {
|
||||||
|
EventLoopEventFlagsApp* app = malloc(sizeof(EventLoopEventFlagsApp));
|
||||||
|
|
||||||
|
// Create event loop instances.
|
||||||
|
app->event_loop = furi_event_loop_alloc();
|
||||||
|
// Create event flag instances.
|
||||||
|
app->event_flag = furi_event_flag_alloc();
|
||||||
|
|
||||||
|
// Create GUI instance.
|
||||||
|
app->gui = furi_record_open(RECORD_GUI);
|
||||||
|
app->view_port = view_port_alloc();
|
||||||
|
// Gain exclusive access to the input events
|
||||||
|
view_port_input_callback_set(app->view_port, event_loop_event_flags_app_input_callback, app);
|
||||||
|
gui_add_view_port(app->gui, app->view_port, GuiLayerFullscreen);
|
||||||
|
|
||||||
|
// Notify the event loop about incoming messages in the event flag
|
||||||
|
furi_event_loop_subscribe_event_flag(
|
||||||
|
app->event_loop,
|
||||||
|
app->event_flag,
|
||||||
|
FuriEventLoopEventIn | FuriEventLoopEventFlagEdge,
|
||||||
|
event_loop_event_flags_app_event_flags_callback,
|
||||||
|
app);
|
||||||
|
|
||||||
|
return app;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void event_loop_event_flags_app_free(EventLoopEventFlagsApp* app) {
|
||||||
|
gui_remove_view_port(app->gui, app->view_port);
|
||||||
|
|
||||||
|
furi_record_close(RECORD_GUI);
|
||||||
|
app->gui = NULL;
|
||||||
|
|
||||||
|
// Delete all instances
|
||||||
|
view_port_free(app->view_port);
|
||||||
|
app->view_port = NULL;
|
||||||
|
|
||||||
|
// IMPORTANT: The user code MUST unsubscribe from all events before deleting the event loop.
|
||||||
|
// Failure to do so will result in a crash.
|
||||||
|
furi_event_loop_unsubscribe(app->event_loop, app->event_flag);
|
||||||
|
|
||||||
|
furi_event_flag_free(app->event_flag);
|
||||||
|
app->event_flag = NULL;
|
||||||
|
|
||||||
|
furi_event_loop_free(app->event_loop);
|
||||||
|
app->event_loop = NULL;
|
||||||
|
|
||||||
|
free(app);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void event_loop_event_flags_app_run(EventLoopEventFlagsApp* app) {
|
||||||
|
FURI_LOG_I(TAG, "Press keys to see them printed here.");
|
||||||
|
FURI_LOG_I(TAG, "Quickly press different keys to generate events.");
|
||||||
|
FURI_LOG_I(TAG, "Long press \"Back\" to exit app.");
|
||||||
|
|
||||||
|
// Run the application event loop. This call will block until the application is about to exit.
|
||||||
|
furi_event_loop_run(app->event_loop);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*******************************************************************
|
||||||
|
* vvv START HERE vvv
|
||||||
|
*
|
||||||
|
* The application's entry point - referenced in application.fam
|
||||||
|
*******************************************************************/
|
||||||
|
int32_t example_event_loop_event_flags_app(void* arg) {
|
||||||
|
UNUSED(arg);
|
||||||
|
|
||||||
|
EventLoopEventFlagsApp* app = event_loop_event_flags_app_alloc();
|
||||||
|
event_loop_event_flags_app_run(app);
|
||||||
|
event_loop_event_flags_app_free(app);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -52,7 +52,7 @@ typedef struct {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// This function is executed each time the data is taken out of the stream buffer. It is used to restart the worker timer.
|
// This function is executed each time the data is taken out of the stream buffer. It is used to restart the worker timer.
|
||||||
static bool
|
static void
|
||||||
event_loop_multi_app_stream_buffer_worker_callback(FuriEventLoopObject* object, void* context) {
|
event_loop_multi_app_stream_buffer_worker_callback(FuriEventLoopObject* object, void* context) {
|
||||||
furi_assert(context);
|
furi_assert(context);
|
||||||
EventLoopMultiAppWorker* worker = context;
|
EventLoopMultiAppWorker* worker = context;
|
||||||
@@ -62,8 +62,6 @@ static bool
|
|||||||
FURI_LOG_I(TAG, "Data was removed from buffer");
|
FURI_LOG_I(TAG, "Data was removed from buffer");
|
||||||
// Restart the timer to generate another block of random data.
|
// Restart the timer to generate another block of random data.
|
||||||
furi_event_loop_timer_start(worker->timer, WORKER_DATA_INTERVAL_MS);
|
furi_event_loop_timer_start(worker->timer, WORKER_DATA_INTERVAL_MS);
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// This function is executed when the worker timer expires. The timer will NOT restart automatically
|
// This function is executed when the worker timer expires. The timer will NOT restart automatically
|
||||||
@@ -152,7 +150,7 @@ static void event_loop_multi_app_input_callback(InputEvent* event, void* context
|
|||||||
}
|
}
|
||||||
|
|
||||||
// This function is executed each time new data is available in the stream buffer.
|
// This function is executed each time new data is available in the stream buffer.
|
||||||
static bool
|
static void
|
||||||
event_loop_multi_app_stream_buffer_callback(FuriEventLoopObject* object, void* context) {
|
event_loop_multi_app_stream_buffer_callback(FuriEventLoopObject* object, void* context) {
|
||||||
furi_assert(context);
|
furi_assert(context);
|
||||||
EventLoopMultiApp* app = context;
|
EventLoopMultiApp* app = context;
|
||||||
@@ -172,12 +170,10 @@ static bool
|
|||||||
|
|
||||||
FURI_LOG_I(TAG, "Received data: %s", furi_string_get_cstr(tmp_str));
|
FURI_LOG_I(TAG, "Received data: %s", furi_string_get_cstr(tmp_str));
|
||||||
furi_string_free(tmp_str);
|
furi_string_free(tmp_str);
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// This function is executed each time a new message is inserted in the input queue.
|
// This function is executed each time a new message is inserted in the input queue.
|
||||||
static bool event_loop_multi_app_input_queue_callback(FuriEventLoopObject* object, void* context) {
|
static void event_loop_multi_app_input_queue_callback(FuriEventLoopObject* object, void* context) {
|
||||||
furi_assert(context);
|
furi_assert(context);
|
||||||
EventLoopMultiApp* app = context;
|
EventLoopMultiApp* app = context;
|
||||||
|
|
||||||
@@ -222,8 +218,6 @@ static bool event_loop_multi_app_input_queue_callback(FuriEventLoopObject* objec
|
|||||||
// Not a long press, just print the key's name.
|
// Not a long press, just print the key's name.
|
||||||
FURI_LOG_I(TAG, "Short press: %s", input_get_key_name(event.key));
|
FURI_LOG_I(TAG, "Short press: %s", input_get_key_name(event.key));
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// This function is executed each time the countdown timer expires.
|
// This function is executed each time the countdown timer expires.
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ static int32_t event_loop_mutex_app_worker_thread(void* context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// This function is being run each time when the mutex gets released
|
// This function is being run each time when the mutex gets released
|
||||||
static bool event_loop_mutex_app_event_callback(FuriEventLoopObject* object, void* context) {
|
static void event_loop_mutex_app_event_callback(FuriEventLoopObject* object, void* context) {
|
||||||
furi_assert(context);
|
furi_assert(context);
|
||||||
|
|
||||||
EventLoopMutexApp* app = context;
|
EventLoopMutexApp* app = context;
|
||||||
@@ -82,8 +82,6 @@ static bool event_loop_mutex_app_event_callback(FuriEventLoopObject* object, voi
|
|||||||
MUTEX_EVENT_AND_FLAGS,
|
MUTEX_EVENT_AND_FLAGS,
|
||||||
event_loop_mutex_app_event_callback,
|
event_loop_mutex_app_event_callback,
|
||||||
app);
|
app);
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static EventLoopMutexApp* event_loop_mutex_app_alloc(void) {
|
static EventLoopMutexApp* event_loop_mutex_app_alloc(void) {
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ static int32_t event_loop_stream_buffer_app_worker_thread(void* context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// This function is being run each time when the number of bytes in the buffer is above its trigger level.
|
// This function is being run each time when the number of bytes in the buffer is above its trigger level.
|
||||||
static bool
|
static void
|
||||||
event_loop_stream_buffer_app_event_callback(FuriEventLoopObject* object, void* context) {
|
event_loop_stream_buffer_app_event_callback(FuriEventLoopObject* object, void* context) {
|
||||||
furi_assert(context);
|
furi_assert(context);
|
||||||
EventLoopStreamBufferApp* app = context;
|
EventLoopStreamBufferApp* app = context;
|
||||||
@@ -76,8 +76,6 @@ static bool
|
|||||||
|
|
||||||
FURI_LOG_I(TAG, "Received data: %s", furi_string_get_cstr(tmp_str));
|
FURI_LOG_I(TAG, "Received data: %s", furi_string_get_cstr(tmp_str));
|
||||||
furi_string_free(tmp_str);
|
furi_string_free(tmp_str);
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static EventLoopStreamBufferApp* event_loop_stream_buffer_app_alloc(void) {
|
static EventLoopStreamBufferApp* event_loop_stream_buffer_app_alloc(void) {
|
||||||
|
|||||||
@@ -218,6 +218,35 @@ App(
|
|||||||
sources=["plugins/supported_cards/trt.c"],
|
sources=["plugins/supported_cards/trt.c"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
App(
|
||||||
|
appid="ndef_ul_parser",
|
||||||
|
apptype=FlipperAppType.PLUGIN,
|
||||||
|
cdefines=[("NDEF_PROTO", "NDEF_PROTO_UL")],
|
||||||
|
entry_point="ndef_plugin_ep",
|
||||||
|
targets=["f7"],
|
||||||
|
requires=["nfc"],
|
||||||
|
sources=["plugins/supported_cards/ndef.c"],
|
||||||
|
)
|
||||||
|
App(
|
||||||
|
appid="ndef_mfc_parser",
|
||||||
|
apptype=FlipperAppType.PLUGIN,
|
||||||
|
cdefines=[("NDEF_PROTO", "NDEF_PROTO_MFC")],
|
||||||
|
entry_point="ndef_plugin_ep",
|
||||||
|
targets=["f7"],
|
||||||
|
requires=["nfc"],
|
||||||
|
sources=["plugins/supported_cards/ndef.c"],
|
||||||
|
)
|
||||||
|
|
||||||
|
App(
|
||||||
|
appid="ndef_slix_parser",
|
||||||
|
apptype=FlipperAppType.PLUGIN,
|
||||||
|
cdefines=[("NDEF_PROTO", "NDEF_PROTO_SLIX")],
|
||||||
|
entry_point="ndef_plugin_ep",
|
||||||
|
targets=["f7"],
|
||||||
|
requires=["nfc"],
|
||||||
|
sources=["plugins/supported_cards/ndef.c"],
|
||||||
|
)
|
||||||
|
|
||||||
App(
|
App(
|
||||||
appid="nfc_start",
|
appid="nfc_start",
|
||||||
targets=["f7"],
|
targets=["f7"],
|
||||||
|
|||||||
@@ -139,6 +139,19 @@ static const IdMapping actransit_zones[] = {
|
|||||||
};
|
};
|
||||||
static const size_t kNumACTransitZones = COUNT(actransit_zones);
|
static const size_t kNumACTransitZones = COUNT(actransit_zones);
|
||||||
|
|
||||||
|
// Instead of persisting individual Station IDs, Caltrain saves Zone numbers.
|
||||||
|
// https://www.caltrain.com/stations-zones
|
||||||
|
static const IdMapping caltrain_zones[] = {
|
||||||
|
{.id = 0x0001, .name = "Zone 1"},
|
||||||
|
{.id = 0x0002, .name = "Zone 2"},
|
||||||
|
{.id = 0x0003, .name = "Zone 3"},
|
||||||
|
{.id = 0x0004, .name = "Zone 4"},
|
||||||
|
{.id = 0x0005, .name = "Zone 5"},
|
||||||
|
{.id = 0x0006, .name = "Zone 6"},
|
||||||
|
};
|
||||||
|
|
||||||
|
static const size_t kNumCaltrainZones = COUNT(caltrain_zones);
|
||||||
|
|
||||||
//
|
//
|
||||||
// Full agency+zone mapping.
|
// Full agency+zone mapping.
|
||||||
//
|
//
|
||||||
@@ -149,6 +162,7 @@ static const struct {
|
|||||||
} agency_zone_map[] = {
|
} agency_zone_map[] = {
|
||||||
{.agency_id = 0x0001, .zone_map = actransit_zones, .zone_count = kNumACTransitZones},
|
{.agency_id = 0x0001, .zone_map = actransit_zones, .zone_count = kNumACTransitZones},
|
||||||
{.agency_id = 0x0004, .zone_map = bart_zones, .zone_count = kNumBARTZones},
|
{.agency_id = 0x0004, .zone_map = bart_zones, .zone_count = kNumBARTZones},
|
||||||
|
{.agency_id = 0x0006, .zone_map = caltrain_zones, .zone_count = kNumCaltrainZones},
|
||||||
{.agency_id = 0x0012, .zone_map = muni_zones, .zone_count = kNumMUNIZones}};
|
{.agency_id = 0x0012, .zone_map = muni_zones, .zone_count = kNumMUNIZones}};
|
||||||
static const size_t kNumAgencyZoneMaps = COUNT(agency_zone_map);
|
static const size_t kNumAgencyZoneMaps = COUNT(agency_zone_map);
|
||||||
|
|
||||||
|
|||||||
1065
applications/main/nfc/plugins/supported_cards/ndef.c
Normal file
1065
applications/main/nfc/plugins/supported_cards/ndef.c
Normal file
File diff suppressed because it is too large
Load Diff
@@ -201,8 +201,9 @@ static bool plantain_read(Nfc* nfc, NfcDevice* device) {
|
|||||||
|
|
||||||
static bool plantain_parse(const NfcDevice* device, FuriString* parsed_data) {
|
static bool plantain_parse(const NfcDevice* device, FuriString* parsed_data) {
|
||||||
furi_assert(device);
|
furi_assert(device);
|
||||||
|
size_t uid_len = 0;
|
||||||
const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic);
|
const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic);
|
||||||
|
const uint8_t* uid = mf_classic_get_uid(data, &uid_len);
|
||||||
|
|
||||||
bool parsed = false;
|
bool parsed = false;
|
||||||
|
|
||||||
@@ -220,12 +221,30 @@ static bool plantain_parse(const NfcDevice* device, FuriString* parsed_data) {
|
|||||||
if(key != cfg.keys[cfg.data_sector].a) break;
|
if(key != cfg.keys[cfg.data_sector].a) break;
|
||||||
|
|
||||||
furi_string_printf(parsed_data, "\e#Plantain card\n");
|
furi_string_printf(parsed_data, "\e#Plantain card\n");
|
||||||
|
|
||||||
|
const uint8_t* temp_ptr = &uid[0];
|
||||||
|
|
||||||
|
// UID is read from last to first byte
|
||||||
|
uint8_t card_number_tmp[uid_len];
|
||||||
|
|
||||||
|
if(uid_len == 4) {
|
||||||
|
for(size_t i = 0; i < 4; i++) {
|
||||||
|
card_number_tmp[i] = temp_ptr[3 - i];
|
||||||
|
}
|
||||||
|
} else if(uid_len == 7) {
|
||||||
|
for(size_t i = 0; i < 7; i++) {
|
||||||
|
card_number_tmp[i] = temp_ptr[6 - i];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
//UID is converted to a card number
|
||||||
uint64_t card_number = 0;
|
uint64_t card_number = 0;
|
||||||
for(size_t i = 0; i < 7; i++) {
|
for(size_t i = 0; i < uid_len; i++) {
|
||||||
card_number = (card_number << 8) | data->block[0].data[6 - i];
|
card_number = (card_number << 8) | card_number_tmp[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print card number with 4-digit groups
|
// Print card number with 4-digit groups. "3" in "3078" denotes a ticket type "3 - full ticket", will differ on discounted cards.
|
||||||
furi_string_cat_printf(parsed_data, "Number: ");
|
furi_string_cat_printf(parsed_data, "Number: ");
|
||||||
FuriString* card_number_s = furi_string_alloc();
|
FuriString* card_number_s = furi_string_alloc();
|
||||||
furi_string_cat_printf(card_number_s, "%llu", card_number);
|
furi_string_cat_printf(card_number_s, "%llu", card_number);
|
||||||
@@ -237,6 +256,7 @@ static bool plantain_parse(const NfcDevice* device, FuriString* parsed_data) {
|
|||||||
furi_string_push_back(tmp_s, ' ');
|
furi_string_push_back(tmp_s, ' ');
|
||||||
}
|
}
|
||||||
furi_string_cat_printf(parsed_data, "%s\n", furi_string_get_cstr(tmp_s));
|
furi_string_cat_printf(parsed_data, "%s\n", furi_string_get_cstr(tmp_s));
|
||||||
|
// this works for 2K Plantain
|
||||||
if(data->type == MfClassicType1k) {
|
if(data->type == MfClassicType1k) {
|
||||||
//balance
|
//balance
|
||||||
uint32_t balance = 0;
|
uint32_t balance = 0;
|
||||||
@@ -290,20 +310,70 @@ static bool plantain_parse(const NfcDevice* device, FuriString* parsed_data) {
|
|||||||
last_payment_date.year,
|
last_payment_date.year,
|
||||||
last_payment_date.hour,
|
last_payment_date.hour,
|
||||||
last_payment_date.minute);
|
last_payment_date.minute);
|
||||||
//payment summ
|
//payment amount. This needs to be investigated more, currently it shows incorrect amount on some cards.
|
||||||
uint16_t last_payment = (data->block[18].data[9] << 8) | data->block[18].data[8];
|
uint16_t last_payment = (data->block[18].data[9] << 8) | data->block[18].data[8];
|
||||||
furi_string_cat_printf(parsed_data, "Amount: %d rub", last_payment / 100);
|
furi_string_cat_printf(parsed_data, "Amount: %d rub", last_payment / 100);
|
||||||
furi_string_free(card_number_s);
|
furi_string_free(card_number_s);
|
||||||
furi_string_free(tmp_s);
|
furi_string_free(tmp_s);
|
||||||
|
//This is for 4K Plantains.
|
||||||
} else if(data->type == MfClassicType4k) {
|
} else if(data->type == MfClassicType4k) {
|
||||||
|
//balance
|
||||||
|
uint32_t balance = 0;
|
||||||
|
for(uint8_t i = 0; i < 4; i++) {
|
||||||
|
balance = (balance << 8) | data->block[16].data[3 - i];
|
||||||
|
}
|
||||||
|
furi_string_cat_printf(parsed_data, "Balance: %ld rub\n", balance / 100);
|
||||||
|
|
||||||
//trips
|
//trips
|
||||||
uint8_t trips_metro = data->block[36].data[0];
|
uint8_t trips_metro = data->block[21].data[0];
|
||||||
uint8_t trips_ground = data->block[36].data[1];
|
uint8_t trips_ground = data->block[21].data[1];
|
||||||
furi_string_cat_printf(parsed_data, "Trips: %d\n", trips_metro + trips_ground);
|
furi_string_cat_printf(parsed_data, "Trips: %d\n", trips_metro + trips_ground);
|
||||||
|
//trip time
|
||||||
|
uint32_t last_trip_timestamp = 0;
|
||||||
|
for(uint8_t i = 0; i < 3; i++) {
|
||||||
|
last_trip_timestamp = (last_trip_timestamp << 8) | data->block[21].data[4 - i];
|
||||||
|
}
|
||||||
|
DateTime last_trip = {0};
|
||||||
|
from_minutes_to_datetime(last_trip_timestamp + 24 * 60, &last_trip, 2010);
|
||||||
|
furi_string_cat_printf(
|
||||||
|
parsed_data,
|
||||||
|
"Trip start: %02d.%02d.%04d %02d:%02d\n",
|
||||||
|
last_trip.day,
|
||||||
|
last_trip.month,
|
||||||
|
last_trip.year,
|
||||||
|
last_trip.hour,
|
||||||
|
last_trip.minute);
|
||||||
|
//validator
|
||||||
|
uint16_t validator = (data->block[20].data[5] << 8) | data->block[20].data[4];
|
||||||
|
furi_string_cat_printf(parsed_data, "Validator: %d\n", validator);
|
||||||
|
//tariff
|
||||||
|
uint16_t fare = (data->block[20].data[7] << 8) | data->block[20].data[6];
|
||||||
|
furi_string_cat_printf(parsed_data, "Tariff: %d rub\n", fare / 100);
|
||||||
//trips in metro
|
//trips in metro
|
||||||
furi_string_cat_printf(parsed_data, "Trips (Metro): %d\n", trips_metro);
|
furi_string_cat_printf(parsed_data, "Trips (Metro): %d\n", trips_metro);
|
||||||
//trips on ground
|
//trips on ground
|
||||||
furi_string_cat_printf(parsed_data, "Trips (Ground): %d\n", trips_ground);
|
furi_string_cat_printf(parsed_data, "Trips (Ground): %d\n", trips_ground);
|
||||||
|
//last payment
|
||||||
|
uint32_t last_payment_timestamp = 0;
|
||||||
|
for(uint8_t i = 0; i < 3; i++) {
|
||||||
|
last_payment_timestamp = (last_payment_timestamp << 8) |
|
||||||
|
data->block[18].data[4 - i];
|
||||||
|
}
|
||||||
|
DateTime last_payment_date = {0};
|
||||||
|
from_minutes_to_datetime(last_payment_timestamp + 24 * 60, &last_payment_date, 2010);
|
||||||
|
furi_string_cat_printf(
|
||||||
|
parsed_data,
|
||||||
|
"Last pay: %02d.%02d.%04d %02d:%02d\n",
|
||||||
|
last_payment_date.day,
|
||||||
|
last_payment_date.month,
|
||||||
|
last_payment_date.year,
|
||||||
|
last_payment_date.hour,
|
||||||
|
last_payment_date.minute);
|
||||||
|
//payment amount
|
||||||
|
uint16_t last_payment = (data->block[18].data[9] << 8) | data->block[18].data[8];
|
||||||
|
furi_string_cat_printf(parsed_data, "Amount: %d rub", last_payment / 100);
|
||||||
|
furi_string_free(card_number_s);
|
||||||
|
furi_string_free(tmp_s);
|
||||||
}
|
}
|
||||||
parsed = true;
|
parsed = true;
|
||||||
} while(false);
|
} while(false);
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -5,8 +5,8 @@
|
|||||||
|
|
||||||
#define TAG "NfcMfClassicDictAttack"
|
#define TAG "NfcMfClassicDictAttack"
|
||||||
|
|
||||||
// TODO: Fix lag when leaving the dictionary attack view after Hardnested
|
// TODO FL-3926: Fix lag when leaving the dictionary attack view after Hardnested
|
||||||
// TODO: Re-enters backdoor detection between user and system dictionary if no backdoor is found
|
// TODO FL-3926: Re-enters backdoor detection between user and system dictionary if no backdoor is found
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
DictAttackStateCUIDDictInProgress,
|
DictAttackStateCUIDDictInProgress,
|
||||||
|
|||||||
@@ -1,58 +1,59 @@
|
|||||||
Filetype: Flipper SubGhz Keystore File
|
Filetype: Flipper SubGhz Keystore File
|
||||||
Version: 0
|
Version: 0
|
||||||
Encryption: 1
|
Encryption: 1
|
||||||
IV: 59 65 73 2C 20 61 6E 64 20 79 6F 75 20 74 6F 6F
|
IV: 43 68 65 63 6B 20 70 61 73 73 65 64 20 4F 77 4F
|
||||||
25F6594BB3FA40C929F9CF43E5E6649A42047B727398F057589EAAC7430169E5
|
00A92D8AE07E4998E826AF5C89AD659BD8C2BC6A40DA78B1AC05CF5B066243ED
|
||||||
B2D2A6E64C3A13406610C086DBF2D17F0C5643E44CD276D4F8933942B964DAA2
|
A3C71FA36145D0EE56D78C05DDDDA97E487BCDCA6BDAC2C6F87402A0B20EE3CB
|
||||||
76EE2549A4F62499533856346B3AAF535F248E23802111380D44B70014581E09
|
B9BDFBD18A63503580C18ABF84101B33D7F720900201510086EB3F0C1F533564
|
||||||
482D1CD99F0F9940195777F482B6EF78B0895FDEAF721544A349B705FCFDF3DE
|
EAB736F80371447008CC3BE3CE952CE429E7BF743A70C7CC62FF415B9E38467B
|
||||||
A83ED14173781D96C7434892D9A092A8658EDDC105FC35FFB1D1C727E0A9FD13F8C07A79F6778D858C0002265C8D49ED
|
9C50D75C6E4A82F49AF285EC3545E58F8815FF4FCF5C9FFFCF0151FB693413FF13B594C8A28077450C8E561B0272C264
|
||||||
3675A174F916BDCBE056B5C9C7B6461AC885969A140DAB923137F80CEBBBF64D
|
A54C7E6DD1CAE0758F7A123B187C6EDA5BF3789969FE1E5F5A167E2DA7719671
|
||||||
EE55B1DE8D6632FF91C090641032A290C15271E59C6B9E75328B7463F9029B9F
|
1461891D4AB3500B5BC0859166377CE098C04AAF6FD721F9C58A155F23F8E75A
|
||||||
8A81B9B366DD0F00D1829B3DF62A3EF912B4F492E9D6E99BDC33519C53A9A24D
|
BD1FD5645367BA76D8A87C271FF71E71C407B276BB0B165AF8CF6317250B77A6
|
||||||
1C55C1195C5BFCC502448269211384EF13C979FABE39E4F3251E219F67C8DC4B
|
B18718EA6EB53CCDF9C26FC46E36D17234D93EF578376123B1F3F9953302CF62
|
||||||
11F0FFEB6B9B308DFA9CFA0CD87F5D4EB2D0E48CC9D026C4FAE2C3D06EDE64E4
|
B633458C1948BC65357904F901F6DD5CD9D795887C176C6AA48E477F0EB693DB
|
||||||
11B3CC20AAD78DE62E835B922006635F6BA8D6D72088AEE51D6B0BA4DF8A87AB9A900F7B9D2AD87F0D9DC091EFFCE41A
|
B1352AC0DE8EBDD838F5DF7E040B062CF8FBB73180F3E712C5B2BDBFE1257A2C18694C51F242BCEA62DF317708771AA1
|
||||||
5E57F7A3369B51636E91471C3873C4017B7694FC074E1620AD288548282201860304FA42804049A974E8D495C9DFDDC2
|
842294FA0BACCD5B7710F002E40E4BB6142BC7C3125B2E56D4431FD8D6DD1CCE3397A4EB502B9E4248FA68CE8013E93D
|
||||||
62E153886C36AD29DDFBE061800863C12DB1E04E10E082A4FED50BE36ACCF2D1
|
5379E56AD483C0F870D9446C9CDE65ACA40C3A699D0653EC2F356A076D72D6C2
|
||||||
B0699C0D88A3E91683D90D0B8E2BC78829E12691A9D44B771D0FCC6A8E6E5F34
|
8FD394F533CD2B03AE247B18F4A5214ED0AF64892F497362129FEF5837012BE4
|
||||||
5039B897208F5224F8147B443BF2E4AECBC4FF6BB8C5BA330094C7B1426E88EF
|
FA8BAB9CD11B306F9FCD924B3A66C678A0316F048A30312B5FA69B9E86EDD8C9
|
||||||
3F1B2AE46A6467BFE35EA4003788A2F437A7AEB45B3EB0B53D4236445165CF93
|
35D6E447F3B8BA11331805027BCA1D972E9B29E07F1D2993C974DCAF5EE1EB76
|
||||||
CFB07A55170A6CC9DD578E294087FCCE356A5C11D6BDC4728BC3CE28C1AD4E0AF8CE283915464C2BC2BD47CF93B2FAE2
|
5FBF06E449C471D0AE2DECD141B937666C30EE72273CF58A99E19DCF5D2F69A703F17880B5FB1873480AFA82E2CE7674
|
||||||
EB7F7622C0A1C32B630F923CAB2A8014868212F7B3CE2DA3A81BC0BA11FE26EB
|
06C1463057544BDB86BD91B3BCC507FD3A5939CFF3315AFD252511FE4F6109EC
|
||||||
ABEBD5C5EF32E24841BDAD3A412F4CF841CB0FBC515E47F73DB55A8E3ABE8854
|
9AB5C03053AF5836E71FE27D0CD74515C967273EB240B7C37825B94D9D9F08AC
|
||||||
099226A6C87F2E543BEF871491CBB18F4CB3AC96B0629F45D79021183EC35B34C48E03E8C8EA5E11171D6ACC233293D7
|
66EB1C65F32D5DBDC37B5FF7ACD7C4273B88298E8F0BED08F533F41F38DE651010686B79623804F67398B020E109B9A4
|
||||||
AE78F4F9F800E1A2F96C2A1ADC2655576B723773D533DF2B6A9D98004F607990675648FEA66470FE70E32CCD071D7DEB
|
7B1846759EDD928EF35B2E8FB7E3FFFE545C9A8B349476A9CE2BDDE55CE0E97F53002A543D0FB738CA67490501629296
|
||||||
AC4065F0A4CD5947DE081FEC2B35F3C1A30A3C2A12908F59B7B4A1C2AA5A1C56
|
EB61CB652F414A70441ADACA46DBCC78A5AAC7A2ED4527AA2A93995482985867
|
||||||
15791F43991B06D729EAD37E9990D6FCF20F356D03661B4B96A5777D9FE6A7EA
|
1E119E453C38C3242EFE3D9A3BBB3D257D91D15710C47811C1ADA934515DBD9E
|
||||||
78AADF788C3AE3E619078B0AF71AAB39E125A6046B4FC362D7C8D51EC8C32451
|
6D8B689C37F7CFD52123BA77B6A4FABB16C3D22BF66FB78B4387BDBC975A3EE5
|
||||||
8B9000CF70198026159D56F7B4A530BCE0E247C00CD4D81EEB59ED52C57FEA03
|
997A46848917B76A1D728DA8C3A072F16F020AF50070BAD91AC2D841FA9805C1
|
||||||
A41F06335E36DDEED8E77A6773E75C8BB9E255DB48A070DDB63CCA4D8DA00336
|
C51BFD0B93FECC7234D342F1DA785736A9229A21ACA8C9AA171906AA8916856F
|
||||||
33DB24D54DA53E295467FAB962D863C7015DB706E4AF6248B9B046FB7B9899C2
|
86188B0DF2C25CA48DC0ED0B4524C17D93585873877FAFBBB55EB24A1D6FDABC
|
||||||
3C4C7C892BAA22AAEF6C92962C67CA0B0F18D8C0A88FD45A4D0A5A166FF1C766
|
8178B7DFF235094BDCA1E5D72E48B113F2290C0FEBC95CF842013AB9CD84E0AD
|
||||||
9B0850A575A27BA23E82E52EAECC6766B0D5FB3CDCBC60CD970AB90742B5BCFA
|
25AEF7C490788DCC142CBC96FBCAB598B7EA1D15B7DE4ABBF75DE70832B519E4
|
||||||
9329F044763196FC029639758C29F4186F77AF3DAF237CA434427A712A6BA890
|
8C5FB85C16C5C833E1D8CE13A9432009D98B0D1639EB0C7C86D0AF4ACD7EB694
|
||||||
35F89F373EA9F7EBAF1DE48BC21E14D7D1B28967C8F65A5D0228CED120ABD06F
|
AC9422E946BB8482CFD808B8E17BE01D9F5D9AB49E1192174A04BB0F032F2182
|
||||||
319423ACE5A80A117C7521A1A0BF22B40181B62BEBDB800AEE139E94FE323298
|
793EEE939544D18C547E2198FA6F4A72D518C15C14146FC6CAD6AB642A3C9824
|
||||||
3C2D4F8700825C966004993A1191F1573CBF4A407CB7728937ED0E46320D4E9B
|
C910617B5DC2C3137E96AC1869B7C5E90A1585181CC1B585C4CACC2624B7A72A
|
||||||
C1A9BF45B7E5AFE2753D7C8E04C80301EF54FBD9377F1A879C224A6CD0C841C9B8DAFEF2E12B4D0418D36B3AF8AFCBB7
|
0AAEBC3463300A0391C2B2B14865A68EF44EAE8B1C2462B2730E28B25881B6462B3CCA631DC8A750F97EF5003E6B3060
|
||||||
062E33ADC270AF5111536CADF329D9C78E3CC3FB0538ACF38E9E2A61B5A3B49E
|
0861D4A3FE5970BB36BF8B525C69009212520A3B79F48ACEC6CB8F6DF96D913F
|
||||||
BC6E26CCC82DB4C4AD1970CDCED894E85F838EFF0701A36BCFCBFA463A7D0482
|
6327F56523F609D4ABC552912EF808E5919EA42104137AD206EF93AA4B34C097
|
||||||
CDBD96D21F2CD5457863AE80240A435E478FEE7F7130ED22253B9FA77BED9B67
|
F88B71DAE547754731CD1C45ACBC355E52FE6D7B984A27B454DD7E8BFF12A023
|
||||||
21956DAF6FE1C313DBF310EC05A6EAD9930B6571AAEAB968C26CA4348ACECFE4
|
824025D56ED8B11BB65722F8C04168767D059BEB156B183D718CFB6AA38B9D81
|
||||||
F05A6468310D49A770A4FFB960EDD2F985646C83C7A7F5160E61CE9BDC578D38
|
66E60820D0A6C452D9E209FB56FE3F49CAEFCBDC6116177063E0759FA11FDC8F
|
||||||
7F7BEC5B03AFE530746A0F2E8277D17FAA743D678D0FDB69626FFA4AB07504B4
|
52D6ACA1A6928C52462BABD9A76628B25A2B913FC5BE316A9248C90B6F952529
|
||||||
92204CD4721F426DE2870D47FDDB54771BF8BF766AE2B88C1A2AE1A27ADAEBB6B50BB0D2A64E50725A8613DC702B4645
|
6FF5E9DAC956EF849FB58C11948C6F00743E157A8C2C631769EFCD80BAA3C048E10E682437EB53ED906C3060CCC7FECC
|
||||||
A3EA461CED048BA13FEFD357C04EC1CFD8C3608D5B5C81577C36B8C7B9A80759
|
42757F22BDC1059154C41EDEE74E227E6D980F39939D3AA78FEC3F4DC303BE87
|
||||||
960FA991AC2669E25D03AF0B52D04691172A00EECE05DC6DD59D008CEF220780
|
689C2694447C4FF56C85B60E9FA66CD70A7EB9703A7C0202AD34374E9B290178
|
||||||
EFE29D8222D2C319C93A0601410A1ED88563CD38E3E4E621617AFB72E3C100F3
|
E1A8E0ACF1E30714A7E580CA6380879C48992BA93A1637F24A66E15A03509B96
|
||||||
C55ADFA945416FFBD013F6B97914B4A902FABBFB4EFBB2583AEE11D93DC87B4D
|
5E1176A42E6C0DCF80E7B445372E3C054875A39D5F7A8B9C35985EDB46F65BB0
|
||||||
6D462090B80489D07337E034F0F27C881066321C58C553EDCBCE593A50B96A31
|
12A6349CB3EC6D79B3C85052D6155730A036AF7F54BCD8CC9EC8F36557005D5E
|
||||||
C2DB9833D0810D023E548EF4BEC218D916DB005EF2BE1122AE4BC756EBE4AFBF
|
B6D3D039D8B6D6F298665B16EBCB908D5F64D37C31F583C4F660AC0535DEC2DD
|
||||||
D5E0EC7C772A9E39973EF35DA201C661BCD182DA2AAE3E3706781191666B7B4F
|
327BEE9B3E7A989B49787DE9D0C573C97343D79527FA6F442F8BDA0B9336E666
|
||||||
371463A5E41EEEA81D25217FDB97DACABC0B1892E0A5B36825B83330FEB900D3D89915D7B9AD5B3C3550C84384DF242E
|
1BDEDA4766FB3EFC1867836267E632B12020262B1EABA4BD31F9AB20DE49632A
|
||||||
BEE891192062A5AA254B6E3AD27D006A650D947EDF9010F6A78C78A4443F1D0C
|
8DE80C00CAD7F6E555713414DE8CE56B1C8603777F38F553F528907FA25E153EA5D3459739C66B46F6854A75F19A1511
|
||||||
D56C5498E65B3E2FE6B1A3C25B4D47650DD9E1BAE04AA9CD468026854D5B231B
|
3A07DD2876F794FDF920F5D7B50A6D15C618FCF02F064E5AD5871C5B098EE8EC
|
||||||
2CFF607EE43B721D9B6F564CF70034920716392BAD686A95518CD02ACFE6D232
|
66B7166156874006B35AF6997F61B84F016868FBAD283304B256F3DA065E65E3
|
||||||
2FABCA8EA65BF98CB8236AD4301B8D1C474FBB1F33B084C736C323A83B6DE336
|
1E6D841D11C065F88B3F52EF60862E579E717E6AEFABF6A79FCF90D0810B35BB
|
||||||
07AF12E594D474483BB6DB6CD2F7F722934DF2B9BECE2F0761FED2ECA2738C17FA8B03195A632D5395E9D2F4F8B84C91
|
B3B168E2532A6B659503736AA8933FB01A88DB9EA339C1C19EF7566600312B26
|
||||||
|
974628362FD1951C207BDC3F5363F2747BAFA14ED7155772ADAA2FE8D1F7DC702F8DDB8CB4C17DBA803EC0DB6CDA43AC
|
||||||
|
|||||||
@@ -212,7 +212,7 @@ static void dolphin_update_clear_limits_timer_period(void* context) {
|
|||||||
FURI_LOG_D(TAG, "Daily limits reset in %lu ms", time_to_clear_limits);
|
FURI_LOG_D(TAG, "Daily limits reset in %lu ms", time_to_clear_limits);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool dolphin_process_event(FuriEventLoopObject* object, void* context) {
|
static void dolphin_process_event(FuriEventLoopObject* object, void* context) {
|
||||||
UNUSED(object);
|
UNUSED(object);
|
||||||
|
|
||||||
Dolphin* dolphin = context;
|
Dolphin* dolphin = context;
|
||||||
@@ -264,8 +264,6 @@ static bool dolphin_process_event(FuriEventLoopObject* object, void* context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dolphin_event_release(&event);
|
dolphin_event_release(&event);
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void dolphin_storage_callback(const void* message, void* context) {
|
static void dolphin_storage_callback(const void* message, void* context) {
|
||||||
|
|||||||
@@ -376,7 +376,7 @@ void view_dispatcher_update(View* view, void* context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool view_dispatcher_run_event_callback(FuriEventLoopObject* object, void* context) {
|
void view_dispatcher_run_event_callback(FuriEventLoopObject* object, void* context) {
|
||||||
furi_assert(context);
|
furi_assert(context);
|
||||||
ViewDispatcher* instance = context;
|
ViewDispatcher* instance = context;
|
||||||
furi_assert(instance->event_queue == object);
|
furi_assert(instance->event_queue == object);
|
||||||
@@ -384,11 +384,9 @@ bool view_dispatcher_run_event_callback(FuriEventLoopObject* object, void* conte
|
|||||||
uint32_t event;
|
uint32_t event;
|
||||||
furi_check(furi_message_queue_get(instance->event_queue, &event, 0) == FuriStatusOk);
|
furi_check(furi_message_queue_get(instance->event_queue, &event, 0) == FuriStatusOk);
|
||||||
view_dispatcher_handle_custom_event(instance, event);
|
view_dispatcher_handle_custom_event(instance, event);
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool view_dispatcher_run_input_callback(FuriEventLoopObject* object, void* context) {
|
void view_dispatcher_run_input_callback(FuriEventLoopObject* object, void* context) {
|
||||||
furi_assert(context);
|
furi_assert(context);
|
||||||
ViewDispatcher* instance = context;
|
ViewDispatcher* instance = context;
|
||||||
furi_assert(instance->input_queue == object);
|
furi_assert(instance->input_queue == object);
|
||||||
@@ -396,6 +394,4 @@ bool view_dispatcher_run_input_callback(FuriEventLoopObject* object, void* conte
|
|||||||
InputEvent input;
|
InputEvent input;
|
||||||
furi_check(furi_message_queue_get(instance->input_queue, &input, 0) == FuriStatusOk);
|
furi_check(furi_message_queue_get(instance->input_queue, &input, 0) == FuriStatusOk);
|
||||||
view_dispatcher_handle_input(instance, &input);
|
view_dispatcher_handle_input(instance, &input);
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ void view_dispatcher_set_current_view(ViewDispatcher* view_dispatcher, View* vie
|
|||||||
void view_dispatcher_update(View* view, void* context);
|
void view_dispatcher_update(View* view, void* context);
|
||||||
|
|
||||||
/** ViewDispatcher run event loop event callback */
|
/** ViewDispatcher run event loop event callback */
|
||||||
bool view_dispatcher_run_event_callback(FuriEventLoopObject* object, void* context);
|
void view_dispatcher_run_event_callback(FuriEventLoopObject* object, void* context);
|
||||||
|
|
||||||
/** ViewDispatcher run event loop input callback */
|
/** ViewDispatcher run event loop input callback */
|
||||||
bool view_dispatcher_run_input_callback(FuriEventLoopObject* object, void* context);
|
void view_dispatcher_run_input_callback(FuriEventLoopObject* object, void* context);
|
||||||
|
|||||||
@@ -593,3 +593,7 @@ const NotificationSequence sequence_lcd_contrast_update = {
|
|||||||
&message_lcd_contrast_update,
|
&message_lcd_contrast_update,
|
||||||
NULL,
|
NULL,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const NotificationSequence sequence_empty = {
|
||||||
|
NULL,
|
||||||
|
};
|
||||||
|
|||||||
@@ -145,6 +145,9 @@ extern const NotificationSequence sequence_audiovisual_alert;
|
|||||||
// LCD
|
// LCD
|
||||||
extern const NotificationSequence sequence_lcd_contrast_update;
|
extern const NotificationSequence sequence_lcd_contrast_update;
|
||||||
|
|
||||||
|
// Wait for notification queue become empty
|
||||||
|
extern const NotificationSequence sequence_empty;
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -191,7 +191,7 @@ static void power_handle_reboot(PowerBootMode mode) {
|
|||||||
furi_hal_power_reset();
|
furi_hal_power_reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool power_message_callback(FuriEventLoopObject* object, void* context) {
|
static void power_message_callback(FuriEventLoopObject* object, void* context) {
|
||||||
furi_assert(context);
|
furi_assert(context);
|
||||||
Power* power = context;
|
Power* power = context;
|
||||||
|
|
||||||
@@ -223,8 +223,6 @@ static bool power_message_callback(FuriEventLoopObject* object, void* context) {
|
|||||||
if(msg.lock) {
|
if(msg.lock) {
|
||||||
api_lock_unlock(msg.lock);
|
api_lock_unlock(msg.lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void power_tick_callback(void* context) {
|
static void power_tick_callback(void* context) {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ App(
|
|||||||
provides=[
|
provides=[
|
||||||
"passport",
|
"passport",
|
||||||
"system_settings",
|
"system_settings",
|
||||||
|
"clock_settings",
|
||||||
"about",
|
"about",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|||||||
17
applications/settings/clock_settings/application.fam
Normal file
17
applications/settings/clock_settings/application.fam
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
App(
|
||||||
|
appid="clock_settings",
|
||||||
|
name="Clock & Alarm",
|
||||||
|
apptype=FlipperAppType.SETTINGS,
|
||||||
|
entry_point="clock_settings",
|
||||||
|
requires=["gui"],
|
||||||
|
provides=["clock_settings_start"],
|
||||||
|
stack_size=1 * 1024,
|
||||||
|
order=90,
|
||||||
|
)
|
||||||
|
|
||||||
|
App(
|
||||||
|
appid="clock_settings_start",
|
||||||
|
apptype=FlipperAppType.STARTUP,
|
||||||
|
entry_point="clock_settings_start",
|
||||||
|
order=1000,
|
||||||
|
)
|
||||||
71
applications/settings/clock_settings/clock_settings.c
Normal file
71
applications/settings/clock_settings/clock_settings.c
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
#include "clock_settings.h"
|
||||||
|
|
||||||
|
#include <furi.h>
|
||||||
|
#include <furi_hal.h>
|
||||||
|
|
||||||
|
static bool clock_settings_custom_event_callback(void* context, uint32_t event) {
|
||||||
|
furi_assert(context);
|
||||||
|
ClockSettings* app = context;
|
||||||
|
return scene_manager_handle_custom_event(app->scene_manager, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool clock_settings_back_event_callback(void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
ClockSettings* app = context;
|
||||||
|
return scene_manager_handle_back_event(app->scene_manager);
|
||||||
|
}
|
||||||
|
|
||||||
|
ClockSettings* clock_settings_alloc() {
|
||||||
|
ClockSettings* app = malloc(sizeof(ClockSettings));
|
||||||
|
|
||||||
|
app->gui = furi_record_open(RECORD_GUI);
|
||||||
|
|
||||||
|
app->view_dispatcher = view_dispatcher_alloc();
|
||||||
|
app->scene_manager = scene_manager_alloc(&clock_settings_scene_handlers, app);
|
||||||
|
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
|
||||||
|
|
||||||
|
view_dispatcher_set_custom_event_callback(
|
||||||
|
app->view_dispatcher, clock_settings_custom_event_callback);
|
||||||
|
view_dispatcher_set_navigation_event_callback(
|
||||||
|
app->view_dispatcher, clock_settings_back_event_callback);
|
||||||
|
|
||||||
|
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
|
||||||
|
|
||||||
|
app->pwm_view =
|
||||||
|
clock_settings_module_alloc(view_dispatcher_get_event_loop(app->view_dispatcher));
|
||||||
|
view_dispatcher_add_view(
|
||||||
|
app->view_dispatcher, ClockSettingsViewPwm, clock_settings_module_get_view(app->pwm_view));
|
||||||
|
|
||||||
|
scene_manager_next_scene(app->scene_manager, ClockSettingsSceneStart);
|
||||||
|
|
||||||
|
return app;
|
||||||
|
}
|
||||||
|
|
||||||
|
void clock_settings_free(ClockSettings* app) {
|
||||||
|
furi_assert(app);
|
||||||
|
|
||||||
|
// Views
|
||||||
|
view_dispatcher_remove_view(app->view_dispatcher, ClockSettingsViewPwm);
|
||||||
|
|
||||||
|
clock_settings_module_free(app->pwm_view);
|
||||||
|
|
||||||
|
// View dispatcher
|
||||||
|
view_dispatcher_free(app->view_dispatcher);
|
||||||
|
scene_manager_free(app->scene_manager);
|
||||||
|
|
||||||
|
// Close records
|
||||||
|
furi_record_close(RECORD_GUI);
|
||||||
|
|
||||||
|
free(app);
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t clock_settings(void* p) {
|
||||||
|
UNUSED(p);
|
||||||
|
ClockSettings* clock_settings = clock_settings_alloc();
|
||||||
|
|
||||||
|
view_dispatcher_run(clock_settings->view_dispatcher);
|
||||||
|
|
||||||
|
clock_settings_free(clock_settings);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
31
applications/settings/clock_settings/clock_settings.h
Normal file
31
applications/settings/clock_settings/clock_settings.h
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "scenes/clock_settings_scene.h"
|
||||||
|
|
||||||
|
#include <furi_hal_clock.h>
|
||||||
|
#include <furi_hal_pwm.h>
|
||||||
|
|
||||||
|
#include <gui/gui.h>
|
||||||
|
#include <gui/view_dispatcher.h>
|
||||||
|
#include <gui/scene_manager.h>
|
||||||
|
#include <gui/modules/submenu.h>
|
||||||
|
#include <gui/modules/variable_item_list.h>
|
||||||
|
#include <gui/modules/submenu.h>
|
||||||
|
#include "views/clock_settings_module.h"
|
||||||
|
|
||||||
|
typedef struct ClockSettings ClockSettings;
|
||||||
|
|
||||||
|
struct ClockSettings {
|
||||||
|
Gui* gui;
|
||||||
|
ViewDispatcher* view_dispatcher;
|
||||||
|
SceneManager* scene_manager;
|
||||||
|
ClockSettingsModule* pwm_view;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
ClockSettingsViewPwm,
|
||||||
|
} ClockSettingsView;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
ClockSettingsCustomEventNone,
|
||||||
|
} ClockSettingsCustomEvent;
|
||||||
177
applications/settings/clock_settings/clock_settings_alarm.c
Normal file
177
applications/settings/clock_settings/clock_settings_alarm.c
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
#include <furi.h>
|
||||||
|
#include <furi_hal.h>
|
||||||
|
|
||||||
|
#include <gui/gui.h>
|
||||||
|
#include <gui/view_port.h>
|
||||||
|
|
||||||
|
#include <notification/notification.h>
|
||||||
|
#include <notification/notification_messages.h>
|
||||||
|
|
||||||
|
#include <assets_icons.h>
|
||||||
|
|
||||||
|
#define TAG "ClockSettingsAlarm"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
DateTime now;
|
||||||
|
IconAnimation* icon;
|
||||||
|
} ClockSettingsAlramModel;
|
||||||
|
|
||||||
|
const NotificationSequence sequence_alarm = {
|
||||||
|
&message_force_speaker_volume_setting_1f,
|
||||||
|
&message_force_vibro_setting_on,
|
||||||
|
&message_force_display_brightness_setting_1f,
|
||||||
|
&message_vibro_on,
|
||||||
|
|
||||||
|
&message_display_backlight_on,
|
||||||
|
&message_note_c7,
|
||||||
|
&message_delay_250,
|
||||||
|
|
||||||
|
&message_display_backlight_off,
|
||||||
|
&message_note_c4,
|
||||||
|
&message_delay_250,
|
||||||
|
|
||||||
|
&message_display_backlight_on,
|
||||||
|
&message_note_c7,
|
||||||
|
&message_delay_250,
|
||||||
|
|
||||||
|
&message_display_backlight_off,
|
||||||
|
&message_note_c4,
|
||||||
|
&message_delay_250,
|
||||||
|
|
||||||
|
&message_sound_off,
|
||||||
|
&message_vibro_off,
|
||||||
|
NULL,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void clock_settings_alarm_draw_callback(Canvas* canvas, void* ctx) {
|
||||||
|
ClockSettingsAlramModel* model = ctx;
|
||||||
|
char buffer[64] = {};
|
||||||
|
|
||||||
|
canvas_draw_icon_animation(canvas, 5, 6, model->icon);
|
||||||
|
|
||||||
|
canvas_set_font(canvas, FontBigNumbers);
|
||||||
|
snprintf(buffer, sizeof(buffer), "%02u:%02u", model->now.hour, model->now.minute);
|
||||||
|
canvas_draw_str(canvas, 58, 32, buffer);
|
||||||
|
|
||||||
|
canvas_set_font(canvas, FontPrimary);
|
||||||
|
snprintf(
|
||||||
|
buffer,
|
||||||
|
sizeof(buffer),
|
||||||
|
"%02u.%02u.%04u",
|
||||||
|
model->now.day,
|
||||||
|
model->now.month,
|
||||||
|
model->now.year);
|
||||||
|
canvas_draw_str(canvas, 60, 44, buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void clock_settings_alarm_input_callback(InputEvent* input_event, void* ctx) {
|
||||||
|
furi_assert(ctx);
|
||||||
|
FuriMessageQueue* event_queue = ctx;
|
||||||
|
furi_message_queue_put(event_queue, input_event, FuriWaitForever);
|
||||||
|
}
|
||||||
|
|
||||||
|
void clock_settings_alarm_animation_callback(IconAnimation* instance, void* context) {
|
||||||
|
UNUSED(instance);
|
||||||
|
ViewPort* view_port = context;
|
||||||
|
view_port_update(view_port);
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t clock_settings_alarm(void* p) {
|
||||||
|
UNUSED(p);
|
||||||
|
|
||||||
|
// View Model
|
||||||
|
ClockSettingsAlramModel model;
|
||||||
|
|
||||||
|
furi_hal_rtc_get_datetime(&model.now);
|
||||||
|
model.icon = icon_animation_alloc(&A_Alarm_47x39);
|
||||||
|
|
||||||
|
// Alloc message queue
|
||||||
|
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
|
||||||
|
|
||||||
|
// Configure view port
|
||||||
|
ViewPort* view_port = view_port_alloc();
|
||||||
|
view_port_draw_callback_set(view_port, clock_settings_alarm_draw_callback, &model);
|
||||||
|
view_port_input_callback_set(view_port, clock_settings_alarm_input_callback, event_queue);
|
||||||
|
|
||||||
|
// Register view port in GUI
|
||||||
|
Gui* gui = furi_record_open(RECORD_GUI);
|
||||||
|
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
|
||||||
|
|
||||||
|
NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
|
||||||
|
notification_message(notification, &sequence_alarm);
|
||||||
|
|
||||||
|
icon_animation_set_update_callback(
|
||||||
|
model.icon, clock_settings_alarm_animation_callback, view_port);
|
||||||
|
icon_animation_start(model.icon);
|
||||||
|
|
||||||
|
// Process events
|
||||||
|
InputEvent event;
|
||||||
|
bool running = true;
|
||||||
|
while(running) {
|
||||||
|
if(furi_message_queue_get(event_queue, &event, 2000) == FuriStatusOk) {
|
||||||
|
if(event.type == InputTypePress) {
|
||||||
|
running = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
notification_message(notification, &sequence_alarm);
|
||||||
|
furi_hal_rtc_get_datetime(&model.now);
|
||||||
|
view_port_update(view_port);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
icon_animation_stop(model.icon);
|
||||||
|
|
||||||
|
notification_message_block(notification, &sequence_empty);
|
||||||
|
furi_record_close(RECORD_NOTIFICATION);
|
||||||
|
|
||||||
|
view_port_enabled_set(view_port, false);
|
||||||
|
gui_remove_view_port(gui, view_port);
|
||||||
|
view_port_free(view_port);
|
||||||
|
furi_message_queue_free(event_queue);
|
||||||
|
furi_record_close(RECORD_GUI);
|
||||||
|
|
||||||
|
icon_animation_free(model.icon);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
FuriThread* clock_settings_alarm_thread = NULL;
|
||||||
|
|
||||||
|
static void clock_settings_alarm_thread_state_callback(
|
||||||
|
FuriThread* thread,
|
||||||
|
FuriThreadState state,
|
||||||
|
void* context) {
|
||||||
|
furi_assert(clock_settings_alarm_thread == thread);
|
||||||
|
UNUSED(context);
|
||||||
|
|
||||||
|
if(state == FuriThreadStateStopped) {
|
||||||
|
furi_thread_free(thread);
|
||||||
|
clock_settings_alarm_thread = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void clock_settings_alarm_start(void* context, uint32_t arg) {
|
||||||
|
UNUSED(context);
|
||||||
|
UNUSED(arg);
|
||||||
|
|
||||||
|
FURI_LOG_I(TAG, "spawning alarm thread");
|
||||||
|
|
||||||
|
if(clock_settings_alarm_thread) return;
|
||||||
|
|
||||||
|
clock_settings_alarm_thread =
|
||||||
|
furi_thread_alloc_ex("ClockAlarm", 1024, clock_settings_alarm, NULL);
|
||||||
|
furi_thread_set_state_callback(
|
||||||
|
clock_settings_alarm_thread, clock_settings_alarm_thread_state_callback);
|
||||||
|
furi_thread_start(clock_settings_alarm_thread);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void clock_settings_alarm_isr(void* context) {
|
||||||
|
UNUSED(context);
|
||||||
|
furi_timer_pending_callback(clock_settings_alarm_start, NULL, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void clock_settings_start(void) {
|
||||||
|
#ifndef FURI_RAM_EXEC
|
||||||
|
furi_hal_rtc_set_alarm_callback(clock_settings_alarm_isr, NULL);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
#include "../clock_settings.h"
|
||||||
|
|
||||||
|
// Generate scene on_enter handlers array
|
||||||
|
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
|
||||||
|
void (*const clock_settings_scene_on_enter_handlers[])(void*) = {
|
||||||
|
#include "clock_settings_scene_config.h"
|
||||||
|
};
|
||||||
|
#undef ADD_SCENE
|
||||||
|
|
||||||
|
// Generate scene on_event handlers array
|
||||||
|
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event,
|
||||||
|
bool (*const clock_settings_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = {
|
||||||
|
#include "clock_settings_scene_config.h"
|
||||||
|
};
|
||||||
|
#undef ADD_SCENE
|
||||||
|
|
||||||
|
// Generate scene on_exit handlers array
|
||||||
|
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit,
|
||||||
|
void (*const clock_settings_scene_on_exit_handlers[])(void* context) = {
|
||||||
|
#include "clock_settings_scene_config.h"
|
||||||
|
};
|
||||||
|
#undef ADD_SCENE
|
||||||
|
|
||||||
|
// Initialize scene handlers configuration structure
|
||||||
|
const SceneManagerHandlers clock_settings_scene_handlers = {
|
||||||
|
.on_enter_handlers = clock_settings_scene_on_enter_handlers,
|
||||||
|
.on_event_handlers = clock_settings_scene_on_event_handlers,
|
||||||
|
.on_exit_handlers = clock_settings_scene_on_exit_handlers,
|
||||||
|
.scene_num = ClockSettingsSceneNum,
|
||||||
|
};
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <gui/scene_manager.h>
|
||||||
|
|
||||||
|
// Generate scene id and total number
|
||||||
|
#define ADD_SCENE(prefix, name, id) ClockSettingsScene##id,
|
||||||
|
typedef enum {
|
||||||
|
#include "clock_settings_scene_config.h"
|
||||||
|
ClockSettingsSceneNum,
|
||||||
|
} ClockSettingsScene;
|
||||||
|
#undef ADD_SCENE
|
||||||
|
|
||||||
|
extern const SceneManagerHandlers clock_settings_scene_handlers;
|
||||||
|
|
||||||
|
// Generate scene on_enter handlers declaration
|
||||||
|
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*);
|
||||||
|
#include "clock_settings_scene_config.h"
|
||||||
|
#undef ADD_SCENE
|
||||||
|
|
||||||
|
// Generate scene on_event handlers declaration
|
||||||
|
#define ADD_SCENE(prefix, name, id) \
|
||||||
|
bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event);
|
||||||
|
#include "clock_settings_scene_config.h"
|
||||||
|
#undef ADD_SCENE
|
||||||
|
|
||||||
|
// Generate scene on_exit handlers declaration
|
||||||
|
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context);
|
||||||
|
#include "clock_settings_scene_config.h"
|
||||||
|
#undef ADD_SCENE
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
ADD_SCENE(clock_settings, start, Start)
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
#include "../clock_settings.h"
|
||||||
|
#include <furi_hal.h>
|
||||||
|
|
||||||
|
#define TAG "SceneStart"
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
SubmenuIndexPwm,
|
||||||
|
SubmenuIndexClockOutput,
|
||||||
|
} SubmenuIndex;
|
||||||
|
|
||||||
|
void clock_settings_scene_start_submenu_callback(void* context, uint32_t index) {
|
||||||
|
ClockSettings* app = context;
|
||||||
|
|
||||||
|
view_dispatcher_send_custom_event(app->view_dispatcher, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
void clock_settings_scene_start_on_enter(void* context) {
|
||||||
|
ClockSettings* app = context;
|
||||||
|
|
||||||
|
view_dispatcher_switch_to_view(app->view_dispatcher, ClockSettingsViewPwm);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool clock_settings_scene_start_on_event(void* context, SceneManagerEvent event) {
|
||||||
|
UNUSED(context);
|
||||||
|
UNUSED(event);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void clock_settings_scene_start_on_exit(void* context) {
|
||||||
|
UNUSED(context);
|
||||||
|
}
|
||||||
@@ -0,0 +1,438 @@
|
|||||||
|
#include "clock_settings_module.h"
|
||||||
|
|
||||||
|
#include <gui/elements.h>
|
||||||
|
#include <assets_icons.h>
|
||||||
|
#include <locale/locale.h>
|
||||||
|
|
||||||
|
#define TAG "ClockSettingsModule"
|
||||||
|
|
||||||
|
struct ClockSettingsModule {
|
||||||
|
FuriEventLoopTimer* timer;
|
||||||
|
View* view;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
DateTime current;
|
||||||
|
DateTime alarm;
|
||||||
|
bool alarm_enabled;
|
||||||
|
bool editing;
|
||||||
|
|
||||||
|
uint8_t row;
|
||||||
|
uint8_t column;
|
||||||
|
} ClockSettingsModuleViewModel;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
EditStateNone,
|
||||||
|
EditStateActive,
|
||||||
|
EditStateActiveEditing,
|
||||||
|
} EditState;
|
||||||
|
|
||||||
|
#define get_state(m, r, c) \
|
||||||
|
((m)->row == (r) && (m)->column == (c) ? \
|
||||||
|
((m)->editing ? EditStateActiveEditing : EditStateActive) : \
|
||||||
|
EditStateNone)
|
||||||
|
|
||||||
|
#define ROW_0_Y (4)
|
||||||
|
#define ROW_0_H (20)
|
||||||
|
|
||||||
|
#define ROW_1_Y (30)
|
||||||
|
#define ROW_1_H (12)
|
||||||
|
|
||||||
|
#define ROW_2_Y (48)
|
||||||
|
#define ROW_2_H (12)
|
||||||
|
|
||||||
|
#define ROW_COUNT 3
|
||||||
|
#define COLUMN_COUNT 3
|
||||||
|
|
||||||
|
static inline void clock_settings_module_cleanup_date(DateTime* dt) {
|
||||||
|
uint8_t day_per_month =
|
||||||
|
datetime_get_days_per_month(datetime_is_leap_year(dt->year), dt->month);
|
||||||
|
if(dt->day > day_per_month) {
|
||||||
|
dt->day = day_per_month;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void clock_settings_module_draw_block(
|
||||||
|
Canvas* canvas,
|
||||||
|
int32_t x,
|
||||||
|
int32_t y,
|
||||||
|
size_t w,
|
||||||
|
size_t h,
|
||||||
|
Font font,
|
||||||
|
EditState state,
|
||||||
|
const char* text) {
|
||||||
|
canvas_set_color(canvas, ColorBlack);
|
||||||
|
if(state != EditStateNone) {
|
||||||
|
if(state == EditStateActiveEditing) {
|
||||||
|
canvas_draw_icon(canvas, x + w / 2 - 2, y - 1 - 3, &I_SmallArrowUp_3x5);
|
||||||
|
canvas_draw_icon(canvas, x + w / 2 - 2, y + h + 1, &I_SmallArrowDown_3x5);
|
||||||
|
}
|
||||||
|
canvas_draw_rbox(canvas, x, y, w, h, 1);
|
||||||
|
canvas_set_color(canvas, ColorWhite);
|
||||||
|
} else {
|
||||||
|
canvas_draw_rframe(canvas, x, y, w, h, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas_set_font(canvas, font);
|
||||||
|
canvas_draw_str_aligned(canvas, x + w / 2, y + h / 2, AlignCenter, AlignCenter, text);
|
||||||
|
if(state != EditStateNone) {
|
||||||
|
canvas_set_color(canvas, ColorBlack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
clock_settings_module_draw_time_callback(Canvas* canvas, ClockSettingsModuleViewModel* model) {
|
||||||
|
char buffer[64];
|
||||||
|
|
||||||
|
canvas_set_font(canvas, FontPrimary);
|
||||||
|
canvas_draw_str(canvas, 0, ROW_0_Y + 15, "Time");
|
||||||
|
|
||||||
|
snprintf(buffer, sizeof(buffer), "%02u", model->current.hour);
|
||||||
|
clock_settings_module_draw_block(
|
||||||
|
canvas, 32, ROW_0_Y, 28, ROW_0_H, FontBigNumbers, get_state(model, 0, 0), buffer);
|
||||||
|
canvas_draw_box(canvas, 62, ROW_0_Y + ROW_0_H - 7, 2, 2);
|
||||||
|
canvas_draw_box(canvas, 62, ROW_0_Y + ROW_0_H - 7 - 6, 2, 2);
|
||||||
|
|
||||||
|
snprintf(buffer, sizeof(buffer), "%02u", model->current.minute);
|
||||||
|
clock_settings_module_draw_block(
|
||||||
|
canvas, 66, ROW_0_Y, 28, ROW_0_H, FontBigNumbers, get_state(model, 0, 1), buffer);
|
||||||
|
canvas_draw_box(canvas, 96, ROW_0_Y + ROW_0_H - 7, 2, 2);
|
||||||
|
canvas_draw_box(canvas, 96, ROW_0_Y + ROW_0_H - 7 - 6, 2, 2);
|
||||||
|
|
||||||
|
snprintf(buffer, sizeof(buffer), "%02u", model->current.second);
|
||||||
|
clock_settings_module_draw_block(
|
||||||
|
canvas, 100, ROW_0_Y, 28, ROW_0_H, FontBigNumbers, get_state(model, 0, 2), buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
clock_settings_module_draw_date_callback(Canvas* canvas, ClockSettingsModuleViewModel* model) {
|
||||||
|
char buffer[64];
|
||||||
|
|
||||||
|
canvas_set_font(canvas, FontPrimary);
|
||||||
|
canvas_draw_str(canvas, 0, ROW_1_Y + 9, "Date");
|
||||||
|
// Day
|
||||||
|
snprintf(buffer, sizeof(buffer), "%02u", model->current.day);
|
||||||
|
clock_settings_module_draw_block(
|
||||||
|
canvas, 44, ROW_1_Y, 17, ROW_1_H, FontPrimary, get_state(model, 1, 0), buffer);
|
||||||
|
canvas_draw_box(canvas, 71 - 6, ROW_1_Y + ROW_1_H - 4, 2, 2);
|
||||||
|
// Month
|
||||||
|
snprintf(buffer, sizeof(buffer), "%02u", model->current.month);
|
||||||
|
clock_settings_module_draw_block(
|
||||||
|
canvas, 71, ROW_1_Y, 17, ROW_1_H, FontPrimary, get_state(model, 1, 1), buffer);
|
||||||
|
canvas_draw_box(canvas, 98 - 6, ROW_1_Y + ROW_1_H - 4, 2, 2);
|
||||||
|
// Year
|
||||||
|
snprintf(buffer, sizeof(buffer), "%04u", model->current.year);
|
||||||
|
clock_settings_module_draw_block(
|
||||||
|
canvas, 98, ROW_1_Y, 30, ROW_1_H, FontPrimary, get_state(model, 1, 2), buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
clock_settings_module_draw_alarm_callback(Canvas* canvas, ClockSettingsModuleViewModel* model) {
|
||||||
|
char buffer[64];
|
||||||
|
|
||||||
|
canvas_set_font(canvas, FontPrimary);
|
||||||
|
canvas_draw_str(canvas, 0, ROW_2_Y + 9, "Alarm");
|
||||||
|
|
||||||
|
snprintf(buffer, sizeof(buffer), "%02u", model->alarm.hour);
|
||||||
|
clock_settings_module_draw_block(
|
||||||
|
canvas, 58, ROW_2_Y, 17, ROW_2_H, FontPrimary, get_state(model, 2, 0), buffer);
|
||||||
|
canvas_draw_box(canvas, 81 - 4, ROW_2_Y + ROW_2_H - 4, 2, 2);
|
||||||
|
canvas_draw_box(canvas, 81 - 4, ROW_2_Y + ROW_2_H - 4 - 4, 2, 2);
|
||||||
|
|
||||||
|
snprintf(buffer, sizeof(buffer), "%02u", model->alarm.minute);
|
||||||
|
clock_settings_module_draw_block(
|
||||||
|
canvas, 81, ROW_2_Y, 17, ROW_2_H, FontPrimary, get_state(model, 2, 1), buffer);
|
||||||
|
|
||||||
|
clock_settings_module_draw_block(
|
||||||
|
canvas,
|
||||||
|
106,
|
||||||
|
ROW_2_Y,
|
||||||
|
22,
|
||||||
|
ROW_2_H,
|
||||||
|
FontPrimary,
|
||||||
|
get_state(model, 2, 2),
|
||||||
|
model->alarm_enabled ? "On" : "Off");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void clock_settings_module_draw_callback(Canvas* canvas, void* _model) {
|
||||||
|
ClockSettingsModuleViewModel* model = _model;
|
||||||
|
clock_settings_module_draw_time_callback(canvas, model);
|
||||||
|
clock_settings_module_draw_date_callback(canvas, model);
|
||||||
|
clock_settings_module_draw_alarm_callback(canvas, model);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool clock_settings_module_input_navigation_callback(
|
||||||
|
InputEvent* event,
|
||||||
|
ClockSettingsModuleViewModel* model) {
|
||||||
|
if(event->key == InputKeyUp) {
|
||||||
|
if(model->row > 0) model->row--;
|
||||||
|
} else if(event->key == InputKeyDown) {
|
||||||
|
if(model->row < ROW_COUNT - 1) model->row++;
|
||||||
|
} else if(event->key == InputKeyOk) {
|
||||||
|
model->editing = !model->editing;
|
||||||
|
} else if(event->key == InputKeyRight) {
|
||||||
|
if(model->column < COLUMN_COUNT - 1) model->column++;
|
||||||
|
} else if(event->key == InputKeyLeft) {
|
||||||
|
if(model->column > 0) model->column--;
|
||||||
|
} else if(event->key == InputKeyBack && model->editing) {
|
||||||
|
model->editing = false;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool clock_settings_module_input_time_callback(
|
||||||
|
InputEvent* event,
|
||||||
|
ClockSettingsModuleViewModel* model) {
|
||||||
|
if(event->key == InputKeyUp) {
|
||||||
|
if(model->column == 0) {
|
||||||
|
model->current.hour++;
|
||||||
|
model->current.hour = model->current.hour % 24;
|
||||||
|
} else if(model->column == 1) {
|
||||||
|
model->current.minute++;
|
||||||
|
model->current.minute = model->current.minute % 60;
|
||||||
|
} else if(model->column == 2) {
|
||||||
|
model->current.second++;
|
||||||
|
model->current.second = model->current.second % 60;
|
||||||
|
} else {
|
||||||
|
furi_crash();
|
||||||
|
}
|
||||||
|
} else if(event->key == InputKeyDown) {
|
||||||
|
if(model->column == 0) {
|
||||||
|
if(model->current.hour > 0) {
|
||||||
|
model->current.hour--;
|
||||||
|
} else {
|
||||||
|
model->current.hour = 23;
|
||||||
|
}
|
||||||
|
model->current.hour = model->current.hour % 24;
|
||||||
|
} else if(model->column == 1) {
|
||||||
|
if(model->current.minute > 0) {
|
||||||
|
model->current.minute--;
|
||||||
|
} else {
|
||||||
|
model->current.minute = 59;
|
||||||
|
}
|
||||||
|
model->current.minute = model->current.minute % 60;
|
||||||
|
} else if(model->column == 2) {
|
||||||
|
if(model->current.second > 0) {
|
||||||
|
model->current.second--;
|
||||||
|
} else {
|
||||||
|
model->current.second = 59;
|
||||||
|
}
|
||||||
|
model->current.second = model->current.second % 60;
|
||||||
|
} else {
|
||||||
|
furi_crash();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return clock_settings_module_input_navigation_callback(event, model);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool clock_settings_module_input_date_callback(
|
||||||
|
InputEvent* event,
|
||||||
|
ClockSettingsModuleViewModel* model) {
|
||||||
|
if(event->key == InputKeyUp) {
|
||||||
|
if(model->column == 0) {
|
||||||
|
if(model->current.day < 31) model->current.day++;
|
||||||
|
} else if(model->column == 1) {
|
||||||
|
if(model->current.month < 12) {
|
||||||
|
model->current.month++;
|
||||||
|
}
|
||||||
|
} else if(model->column == 2) {
|
||||||
|
if(model->current.year < 2099) {
|
||||||
|
model->current.year++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
furi_crash();
|
||||||
|
}
|
||||||
|
} else if(event->key == InputKeyDown) {
|
||||||
|
if(model->column == 0) {
|
||||||
|
if(model->current.day > 1) {
|
||||||
|
model->current.day--;
|
||||||
|
}
|
||||||
|
} else if(model->column == 1) {
|
||||||
|
if(model->current.month > 1) {
|
||||||
|
model->current.month--;
|
||||||
|
}
|
||||||
|
} else if(model->column == 2) {
|
||||||
|
if(model->current.year > 2000) {
|
||||||
|
model->current.year--;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
furi_crash();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return clock_settings_module_input_navigation_callback(event, model);
|
||||||
|
}
|
||||||
|
|
||||||
|
clock_settings_module_cleanup_date(&model->current);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool clock_settings_module_input_alarm_callback(
|
||||||
|
InputEvent* event,
|
||||||
|
ClockSettingsModuleViewModel* model) {
|
||||||
|
if(event->key == InputKeyUp) {
|
||||||
|
if(model->column == 0) {
|
||||||
|
model->alarm.hour++;
|
||||||
|
model->alarm.hour = model->alarm.hour % 24;
|
||||||
|
} else if(model->column == 1) {
|
||||||
|
model->alarm.minute++;
|
||||||
|
model->alarm.minute = model->alarm.minute % 60;
|
||||||
|
} else if(model->column == 2) {
|
||||||
|
model->alarm_enabled = !model->alarm_enabled;
|
||||||
|
} else {
|
||||||
|
furi_crash();
|
||||||
|
}
|
||||||
|
} else if(event->key == InputKeyDown) {
|
||||||
|
if(model->column == 0) {
|
||||||
|
if(model->alarm.hour > 0) {
|
||||||
|
model->alarm.hour--;
|
||||||
|
} else {
|
||||||
|
model->alarm.hour = 23;
|
||||||
|
}
|
||||||
|
model->alarm.hour = model->alarm.hour % 24;
|
||||||
|
} else if(model->column == 1) {
|
||||||
|
if(model->alarm.minute > 0) {
|
||||||
|
model->alarm.minute--;
|
||||||
|
} else {
|
||||||
|
model->alarm.minute = 59;
|
||||||
|
}
|
||||||
|
model->alarm.minute = model->alarm.minute % 60;
|
||||||
|
} else if(model->column == 2) {
|
||||||
|
model->alarm_enabled = !model->alarm_enabled;
|
||||||
|
} else {
|
||||||
|
furi_crash();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return clock_settings_module_input_navigation_callback(event, model);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool clock_settings_module_input_callback(InputEvent* event, void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
|
||||||
|
ClockSettingsModule* instance = context;
|
||||||
|
bool consumed = false;
|
||||||
|
|
||||||
|
with_view_model(
|
||||||
|
instance->view,
|
||||||
|
ClockSettingsModuleViewModel * model,
|
||||||
|
{
|
||||||
|
if(event->type == InputTypeShort || event->type == InputTypeRepeat) {
|
||||||
|
bool previous_editing = model->editing;
|
||||||
|
if(model->editing) {
|
||||||
|
if(model->row == 0) {
|
||||||
|
consumed = clock_settings_module_input_time_callback(event, model);
|
||||||
|
} else if(model->row == 1) {
|
||||||
|
consumed = clock_settings_module_input_date_callback(event, model);
|
||||||
|
} else if(model->row == 2) {
|
||||||
|
consumed = clock_settings_module_input_alarm_callback(event, model);
|
||||||
|
} else {
|
||||||
|
furi_crash();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
consumed = clock_settings_module_input_navigation_callback(event, model);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Switching between navigate/edit
|
||||||
|
if(model->editing != previous_editing) {
|
||||||
|
if(model->row == 2) {
|
||||||
|
if(!model->editing) {
|
||||||
|
// Disable alarm
|
||||||
|
furi_hal_rtc_set_alarm(NULL, false);
|
||||||
|
// Set new alarm
|
||||||
|
furi_hal_rtc_set_alarm(&model->alarm, model->alarm_enabled);
|
||||||
|
// Confirm
|
||||||
|
model->alarm_enabled = furi_hal_rtc_get_alarm(&model->alarm);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if(model->editing) {
|
||||||
|
// stop timer to prevent mess with current date time
|
||||||
|
furi_event_loop_timer_stop(instance->timer);
|
||||||
|
} else {
|
||||||
|
// save date time and restart timer
|
||||||
|
furi_hal_rtc_set_datetime(&model->current);
|
||||||
|
furi_event_loop_timer_start(instance->timer, 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
true);
|
||||||
|
|
||||||
|
return consumed;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void clock_settings_module_timer_callback(void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
ClockSettingsModule* instance = context;
|
||||||
|
|
||||||
|
DateTime dt;
|
||||||
|
furi_hal_rtc_get_datetime(&dt);
|
||||||
|
with_view_model(
|
||||||
|
instance->view, ClockSettingsModuleViewModel * model, { model->current = dt; }, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void clock_settings_module_view_enter_callback(void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
ClockSettingsModule* instance = context;
|
||||||
|
|
||||||
|
clock_settings_module_timer_callback(context);
|
||||||
|
|
||||||
|
DateTime alarm;
|
||||||
|
bool enabled = furi_hal_rtc_get_alarm(&alarm);
|
||||||
|
|
||||||
|
with_view_model(
|
||||||
|
instance->view,
|
||||||
|
ClockSettingsModuleViewModel * model,
|
||||||
|
{
|
||||||
|
model->alarm = alarm;
|
||||||
|
model->alarm_enabled = enabled;
|
||||||
|
},
|
||||||
|
true);
|
||||||
|
|
||||||
|
furi_event_loop_timer_start(instance->timer, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void clock_settings_module_view_exit_callback(void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
ClockSettingsModule* instance = context;
|
||||||
|
furi_event_loop_timer_stop(instance->timer);
|
||||||
|
}
|
||||||
|
|
||||||
|
ClockSettingsModule* clock_settings_module_alloc(FuriEventLoop* event_loop) {
|
||||||
|
ClockSettingsModule* instance = malloc(sizeof(ClockSettingsModule));
|
||||||
|
|
||||||
|
instance->timer = furi_event_loop_timer_alloc(
|
||||||
|
event_loop, clock_settings_module_timer_callback, FuriEventLoopTimerTypePeriodic, instance);
|
||||||
|
instance->view = view_alloc();
|
||||||
|
view_set_enter_callback(instance->view, clock_settings_module_view_enter_callback);
|
||||||
|
view_set_exit_callback(instance->view, clock_settings_module_view_exit_callback);
|
||||||
|
view_allocate_model(
|
||||||
|
instance->view, ViewModelTypeLocking, sizeof(ClockSettingsModuleViewModel));
|
||||||
|
with_view_model(
|
||||||
|
instance->view, ClockSettingsModuleViewModel * model, { model->row = 0; }, false);
|
||||||
|
view_set_context(instance->view, instance);
|
||||||
|
view_set_draw_callback(instance->view, clock_settings_module_draw_callback);
|
||||||
|
view_set_input_callback(instance->view, clock_settings_module_input_callback);
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
void clock_settings_module_free(ClockSettingsModule* instance) {
|
||||||
|
furi_assert(instance);
|
||||||
|
view_free(instance->view);
|
||||||
|
free(instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
View* clock_settings_module_get_view(ClockSettingsModule* instance) {
|
||||||
|
furi_assert(instance);
|
||||||
|
return instance->view;
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <furi_hal.h>
|
||||||
|
#include <gui/view.h>
|
||||||
|
|
||||||
|
typedef struct ClockSettingsModule ClockSettingsModule;
|
||||||
|
typedef void (*ClockSettingsModuleViewCallback)(
|
||||||
|
uint8_t channel_id,
|
||||||
|
uint32_t freq,
|
||||||
|
uint8_t duty,
|
||||||
|
void* context);
|
||||||
|
|
||||||
|
ClockSettingsModule* clock_settings_module_alloc(FuriEventLoop* event_loop);
|
||||||
|
|
||||||
|
void clock_settings_module_free(ClockSettingsModule* instance);
|
||||||
|
|
||||||
|
View* clock_settings_module_get_view(ClockSettingsModule* instance);
|
||||||
|
|
||||||
|
void clock_settings_module_set(
|
||||||
|
ClockSettingsModule* instance,
|
||||||
|
const DateTime* datetime,
|
||||||
|
bool enabled);
|
||||||
|
|
||||||
|
bool clock_settings_module_get(ClockSettingsModule* instance, DateTime* datetime);
|
||||||
@@ -5,8 +5,8 @@ App(
|
|||||||
provides=[
|
provides=[
|
||||||
"updater_app",
|
"updater_app",
|
||||||
"js_app",
|
"js_app",
|
||||||
"js_app_start",
|
|
||||||
"mfkey",
|
"mfkey",
|
||||||
|
"picopass",
|
||||||
# "archive",
|
# "archive",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -6,6 +6,16 @@ App(
|
|||||||
stack_size=2 * 1024,
|
stack_size=2 * 1024,
|
||||||
resources="examples",
|
resources="examples",
|
||||||
order=0,
|
order=0,
|
||||||
|
provides=["js_app_start"],
|
||||||
|
sources=[
|
||||||
|
"js_app.c",
|
||||||
|
"js_modules.c",
|
||||||
|
"js_thread.c",
|
||||||
|
"plugin_api/app_api_table.cpp",
|
||||||
|
"views/console_view.c",
|
||||||
|
"modules/js_flipper.c",
|
||||||
|
"modules/js_tests.c",
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
App(
|
App(
|
||||||
@@ -13,6 +23,7 @@ App(
|
|||||||
apptype=FlipperAppType.STARTUP,
|
apptype=FlipperAppType.STARTUP,
|
||||||
entry_point="js_app_on_system_start",
|
entry_point="js_app_on_system_start",
|
||||||
order=160,
|
order=160,
|
||||||
|
sources=["js_app.c"],
|
||||||
)
|
)
|
||||||
|
|
||||||
App(
|
App(
|
||||||
@@ -30,7 +41,7 @@ App(
|
|||||||
appid="js_gui",
|
appid="js_gui",
|
||||||
apptype=FlipperAppType.PLUGIN,
|
apptype=FlipperAppType.PLUGIN,
|
||||||
entry_point="js_gui_ep",
|
entry_point="js_gui_ep",
|
||||||
requires=["js_app", "js_event_loop"],
|
requires=["js_app"],
|
||||||
sources=["modules/js_gui/js_gui.c", "modules/js_gui/js_gui_api_table.cpp"],
|
sources=["modules/js_gui/js_gui.c", "modules/js_gui/js_gui_api_table.cpp"],
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -38,7 +49,7 @@ App(
|
|||||||
appid="js_gui__loading",
|
appid="js_gui__loading",
|
||||||
apptype=FlipperAppType.PLUGIN,
|
apptype=FlipperAppType.PLUGIN,
|
||||||
entry_point="js_view_loading_ep",
|
entry_point="js_view_loading_ep",
|
||||||
requires=["js_app", "js_gui", "js_event_loop"],
|
requires=["js_app"],
|
||||||
sources=["modules/js_gui/loading.c"],
|
sources=["modules/js_gui/loading.c"],
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -46,7 +57,7 @@ App(
|
|||||||
appid="js_gui__empty_screen",
|
appid="js_gui__empty_screen",
|
||||||
apptype=FlipperAppType.PLUGIN,
|
apptype=FlipperAppType.PLUGIN,
|
||||||
entry_point="js_view_empty_screen_ep",
|
entry_point="js_view_empty_screen_ep",
|
||||||
requires=["js_app", "js_gui", "js_event_loop"],
|
requires=["js_app"],
|
||||||
sources=["modules/js_gui/empty_screen.c"],
|
sources=["modules/js_gui/empty_screen.c"],
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -54,7 +65,7 @@ App(
|
|||||||
appid="js_gui__submenu",
|
appid="js_gui__submenu",
|
||||||
apptype=FlipperAppType.PLUGIN,
|
apptype=FlipperAppType.PLUGIN,
|
||||||
entry_point="js_view_submenu_ep",
|
entry_point="js_view_submenu_ep",
|
||||||
requires=["js_app", "js_gui"],
|
requires=["js_app"],
|
||||||
sources=["modules/js_gui/submenu.c"],
|
sources=["modules/js_gui/submenu.c"],
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -62,10 +73,18 @@ App(
|
|||||||
appid="js_gui__text_input",
|
appid="js_gui__text_input",
|
||||||
apptype=FlipperAppType.PLUGIN,
|
apptype=FlipperAppType.PLUGIN,
|
||||||
entry_point="js_view_text_input_ep",
|
entry_point="js_view_text_input_ep",
|
||||||
requires=["js_app", "js_gui", "js_event_loop"],
|
requires=["js_app"],
|
||||||
sources=["modules/js_gui/text_input.c"],
|
sources=["modules/js_gui/text_input.c"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
App(
|
||||||
|
appid="js_gui__byte_input",
|
||||||
|
apptype=FlipperAppType.PLUGIN,
|
||||||
|
entry_point="js_view_byte_input_ep",
|
||||||
|
requires=["js_app"],
|
||||||
|
sources=["modules/js_gui/byte_input.c"],
|
||||||
|
)
|
||||||
|
|
||||||
App(
|
App(
|
||||||
appid="js_gui__text_box",
|
appid="js_gui__text_box",
|
||||||
apptype=FlipperAppType.PLUGIN,
|
apptype=FlipperAppType.PLUGIN,
|
||||||
@@ -82,6 +101,15 @@ App(
|
|||||||
sources=["modules/js_gui/dialog.c"],
|
sources=["modules/js_gui/dialog.c"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
App(
|
||||||
|
appid="js_gui__file_picker",
|
||||||
|
apptype=FlipperAppType.PLUGIN,
|
||||||
|
entry_point="js_gui_file_picker_ep",
|
||||||
|
requires=["js_app"],
|
||||||
|
sources=["modules/js_gui/file_picker.c"],
|
||||||
|
fap_libs=["assets"],
|
||||||
|
)
|
||||||
|
|
||||||
App(
|
App(
|
||||||
appid="js_notification",
|
appid="js_notification",
|
||||||
apptype=FlipperAppType.PLUGIN,
|
apptype=FlipperAppType.PLUGIN,
|
||||||
@@ -110,7 +138,7 @@ App(
|
|||||||
appid="js_gpio",
|
appid="js_gpio",
|
||||||
apptype=FlipperAppType.PLUGIN,
|
apptype=FlipperAppType.PLUGIN,
|
||||||
entry_point="js_gpio_ep",
|
entry_point="js_gpio_ep",
|
||||||
requires=["js_app", "js_event_loop"],
|
requires=["js_app"],
|
||||||
sources=["modules/js_gpio.c"],
|
sources=["modules/js_gpio.c"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,13 @@ let views = {
|
|||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
badusb.setup({ vid: 0xAAAA, pid: 0xBBBB, mfrName: "Flipper", prodName: "Zero" });
|
badusb.setup({
|
||||||
|
vid: 0xAAAA,
|
||||||
|
pid: 0xBBBB,
|
||||||
|
mfrName: "Flipper",
|
||||||
|
prodName: "Zero",
|
||||||
|
layoutPath: "/ext/badusb/assets/layouts/en-US.kl"
|
||||||
|
});
|
||||||
|
|
||||||
eventLoop.subscribe(views.dialog.input, function (_sub, button, eventLoop, gui) {
|
eventLoop.subscribe(views.dialog.input, function (_sub, button, eventLoop, gui) {
|
||||||
if (button !== "center")
|
if (button !== "center")
|
||||||
@@ -39,7 +45,13 @@ eventLoop.subscribe(views.dialog.input, function (_sub, button, eventLoop, gui)
|
|||||||
|
|
||||||
badusb.println("Flipper Model: " + flipper.getModel());
|
badusb.println("Flipper Model: " + flipper.getModel());
|
||||||
badusb.println("Flipper Name: " + flipper.getName());
|
badusb.println("Flipper Name: " + flipper.getName());
|
||||||
badusb.println("Battery level: " + toString(flipper.getBatteryCharge()) + "%");
|
badusb.println("Battery level: " + flipper.getBatteryCharge().toString() + "%");
|
||||||
|
|
||||||
|
// Alt+Numpad method works only on Windows!!!
|
||||||
|
badusb.altPrintln("This was printed with Alt+Numpad method!");
|
||||||
|
|
||||||
|
// There's also badusb.print() and badusb.altPrint()
|
||||||
|
// which don't add the return at the end
|
||||||
|
|
||||||
notify.success();
|
notify.success();
|
||||||
} else {
|
} else {
|
||||||
@@ -47,6 +59,9 @@ eventLoop.subscribe(views.dialog.input, function (_sub, button, eventLoop, gui)
|
|||||||
notify.error();
|
notify.error();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Optional, but allows to unlock usb interface to switch profile
|
||||||
|
badusb.quit();
|
||||||
|
|
||||||
eventLoop.stop();
|
eventLoop.stop();
|
||||||
}, eventLoop, gui);
|
}, eventLoop, gui);
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ eventLoop.subscribe(eventLoop.timer("periodic", 1000), function (_, _item, led,
|
|||||||
// read potentiometer when button is pressed
|
// read potentiometer when button is pressed
|
||||||
print("Press the button (PC1)");
|
print("Press the button (PC1)");
|
||||||
eventLoop.subscribe(button.interrupt(), function (_, _item, pot) {
|
eventLoop.subscribe(button.interrupt(), function (_, _item, pot) {
|
||||||
print("PC0 is at", pot.read_analog(), "mV");
|
print("PC0 is at", pot.readAnalog(), "mV");
|
||||||
}, pot);
|
}, pot);
|
||||||
|
|
||||||
// the program will just exit unless this is here
|
// the program will just exit unless this is here
|
||||||
|
|||||||
@@ -5,8 +5,11 @@ let loadingView = require("gui/loading");
|
|||||||
let submenuView = require("gui/submenu");
|
let submenuView = require("gui/submenu");
|
||||||
let emptyView = require("gui/empty_screen");
|
let emptyView = require("gui/empty_screen");
|
||||||
let textInputView = require("gui/text_input");
|
let textInputView = require("gui/text_input");
|
||||||
|
let byteInputView = require("gui/byte_input");
|
||||||
let textBoxView = require("gui/text_box");
|
let textBoxView = require("gui/text_box");
|
||||||
let dialogView = require("gui/dialog");
|
let dialogView = require("gui/dialog");
|
||||||
|
let filePicker = require("gui/file_picker");
|
||||||
|
let flipper = require("flipper");
|
||||||
|
|
||||||
// declare view instances
|
// declare view instances
|
||||||
let views = {
|
let views = {
|
||||||
@@ -16,9 +19,14 @@ let views = {
|
|||||||
header: "Enter your name",
|
header: "Enter your name",
|
||||||
minLength: 0,
|
minLength: 0,
|
||||||
maxLength: 32,
|
maxLength: 32,
|
||||||
|
defaultText: flipper.getName(),
|
||||||
|
defaultTextClear: true,
|
||||||
}),
|
}),
|
||||||
helloDialog: dialogView.makeWith({
|
helloDialog: dialogView.make(),
|
||||||
center: "Hi Flipper! :)",
|
bytekb: byteInputView.makeWith({
|
||||||
|
header: "Look ma, I'm a header text!",
|
||||||
|
length: 8,
|
||||||
|
defaultData: Uint8Array([0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88]),
|
||||||
}),
|
}),
|
||||||
longText: textBoxView.makeWith({
|
longText: textBoxView.makeWith({
|
||||||
text: "This is a very long string that demonstrates the TextBox view. Use the D-Pad to scroll backwards and forwards.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse rhoncus est malesuada quam egestas ultrices. Maecenas non eros a nulla eleifend vulputate et ut risus. Quisque in mauris mattis, venenatis risus eget, aliquam diam. Fusce pretium feugiat mauris, ut faucibus ex volutpat in. Phasellus volutpat ex sed gravida consectetur. Aliquam sed lectus feugiat, tristique lectus et, bibendum lacus. Ut sit amet augue eu sapien elementum aliquam quis vitae tortor. Vestibulum quis commodo odio. In elementum fermentum massa, eu pellentesque nibh cursus at. Integer eleifend lacus nec purus elementum sodales. Nulla elementum neque urna, non vulputate massa semper sed. Fusce ut nisi vitae dui blandit congue pretium vitae turpis.",
|
text: "This is a very long string that demonstrates the TextBox view. Use the D-Pad to scroll backwards and forwards.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse rhoncus est malesuada quam egestas ultrices. Maecenas non eros a nulla eleifend vulputate et ut risus. Quisque in mauris mattis, venenatis risus eget, aliquam diam. Fusce pretium feugiat mauris, ut faucibus ex volutpat in. Phasellus volutpat ex sed gravida consectetur. Aliquam sed lectus feugiat, tristique lectus et, bibendum lacus. Ut sit amet augue eu sapien elementum aliquam quis vitae tortor. Vestibulum quis commodo odio. In elementum fermentum massa, eu pellentesque nibh cursus at. Integer eleifend lacus nec purus elementum sodales. Nulla elementum neque urna, non vulputate massa semper sed. Fusce ut nisi vitae dui blandit congue pretium vitae turpis.",
|
||||||
@@ -29,7 +37,9 @@ let views = {
|
|||||||
"Hourglass screen",
|
"Hourglass screen",
|
||||||
"Empty screen",
|
"Empty screen",
|
||||||
"Text input & Dialog",
|
"Text input & Dialog",
|
||||||
|
"Byte input",
|
||||||
"Text box",
|
"Text box",
|
||||||
|
"File picker",
|
||||||
"Exit app",
|
"Exit app",
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
@@ -49,15 +59,28 @@ eventLoop.subscribe(views.demos.chosen, function (_sub, index, gui, eventLoop, v
|
|||||||
} else if (index === 2) {
|
} else if (index === 2) {
|
||||||
gui.viewDispatcher.switchTo(views.keyboard);
|
gui.viewDispatcher.switchTo(views.keyboard);
|
||||||
} else if (index === 3) {
|
} else if (index === 3) {
|
||||||
gui.viewDispatcher.switchTo(views.longText);
|
gui.viewDispatcher.switchTo(views.bytekb);
|
||||||
} else if (index === 4) {
|
} else if (index === 4) {
|
||||||
|
gui.viewDispatcher.switchTo(views.longText);
|
||||||
|
} else if (index === 5) {
|
||||||
|
let path = filePicker.pickFile("/ext", "*");
|
||||||
|
if (path) {
|
||||||
|
views.helloDialog.set("text", "You selected:\n" + path);
|
||||||
|
} else {
|
||||||
|
views.helloDialog.set("text", "You didn't select a file");
|
||||||
|
}
|
||||||
|
views.helloDialog.set("center", "Nice!");
|
||||||
|
gui.viewDispatcher.switchTo(views.helloDialog);
|
||||||
|
} else if (index === 6) {
|
||||||
eventLoop.stop();
|
eventLoop.stop();
|
||||||
}
|
}
|
||||||
}, gui, eventLoop, views);
|
}, gui, eventLoop, views);
|
||||||
|
|
||||||
// say hi after keyboard input
|
// say hi after keyboard input
|
||||||
eventLoop.subscribe(views.keyboard.input, function (_sub, name, gui, views) {
|
eventLoop.subscribe(views.keyboard.input, function (_sub, name, gui, views) {
|
||||||
|
views.keyboard.set("defaultText", name); // Remember for next usage
|
||||||
views.helloDialog.set("text", "Hi " + name + "! :)");
|
views.helloDialog.set("text", "Hi " + name + "! :)");
|
||||||
|
views.helloDialog.set("center", "Hi Flipper! :)");
|
||||||
gui.viewDispatcher.switchTo(views.helloDialog);
|
gui.viewDispatcher.switchTo(views.helloDialog);
|
||||||
}, gui, views);
|
}, gui, views);
|
||||||
|
|
||||||
@@ -67,11 +90,27 @@ eventLoop.subscribe(views.helloDialog.input, function (_sub, button, gui, views)
|
|||||||
gui.viewDispatcher.switchTo(views.demos);
|
gui.viewDispatcher.switchTo(views.demos);
|
||||||
}, gui, views);
|
}, gui, views);
|
||||||
|
|
||||||
// go to the demo chooser screen when the back key is pressed
|
// show data after byte input
|
||||||
eventLoop.subscribe(gui.viewDispatcher.navigation, function (_sub, _, gui, views) {
|
eventLoop.subscribe(views.bytekb.input, function (_sub, data, gui, views) {
|
||||||
gui.viewDispatcher.switchTo(views.demos);
|
let data_view = Uint8Array(data);
|
||||||
|
let text = "0x";
|
||||||
|
for (let i = 0; i < data_view.length; i++) {
|
||||||
|
text += data_view[i].toString(16);
|
||||||
|
}
|
||||||
|
views.helloDialog.set("text", "You typed:\n" + text);
|
||||||
|
views.helloDialog.set("center", "Cool!");
|
||||||
|
gui.viewDispatcher.switchTo(views.helloDialog);
|
||||||
}, gui, views);
|
}, gui, views);
|
||||||
|
|
||||||
|
// go to the demo chooser screen when the back key is pressed
|
||||||
|
eventLoop.subscribe(gui.viewDispatcher.navigation, function (_sub, _, gui, views, eventLoop) {
|
||||||
|
if (gui.viewDispatcher.currentView === views.demos) {
|
||||||
|
eventLoop.stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
gui.viewDispatcher.switchTo(views.demos);
|
||||||
|
}, gui, views, eventLoop);
|
||||||
|
|
||||||
// run UI
|
// run UI
|
||||||
gui.viewDispatcher.switchTo(views.demos);
|
gui.viewDispatcher.switchTo(views.demos);
|
||||||
eventLoop.run();
|
eventLoop.run();
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
let eventLoop = require("event_loop");
|
||||||
|
let gui = require("gui");
|
||||||
|
let dialog = require("gui/dialog");
|
||||||
|
let textInput = require("gui/text_input");
|
||||||
|
let loading = require("gui/loading");
|
||||||
|
let storage = require("storage");
|
||||||
|
|
||||||
|
// No eval() or exec() so need to run code from file, and filename must be unique
|
||||||
|
storage.makeDirectory("/ext/.tmp");
|
||||||
|
storage.makeDirectory("/ext/.tmp/js");
|
||||||
|
storage.rmrf("/ext/.tmp/js/repl")
|
||||||
|
storage.makeDirectory("/ext/.tmp/js/repl")
|
||||||
|
let ctx = {
|
||||||
|
tmpTemplate: "/ext/.tmp/js/repl/",
|
||||||
|
tmpNumber: 0,
|
||||||
|
persistentScope: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
let views = {
|
||||||
|
dialog: dialog.makeWith({
|
||||||
|
header: "Interactive Console",
|
||||||
|
text: "Press OK to Start",
|
||||||
|
center: "Run Some JS"
|
||||||
|
}),
|
||||||
|
textInput: textInput.makeWith({
|
||||||
|
header: "Type JavaScript Code:",
|
||||||
|
minLength: 0,
|
||||||
|
maxLength: 256,
|
||||||
|
defaultText: "2+2",
|
||||||
|
defaultTextClear: true,
|
||||||
|
}),
|
||||||
|
loading: loading.make(),
|
||||||
|
};
|
||||||
|
|
||||||
|
eventLoop.subscribe(views.dialog.input, function (_sub, button, gui, views) {
|
||||||
|
if (button === "center") {
|
||||||
|
gui.viewDispatcher.switchTo(views.textInput);
|
||||||
|
}
|
||||||
|
}, gui, views);
|
||||||
|
|
||||||
|
eventLoop.subscribe(views.textInput.input, function (_sub, text, gui, views, ctx) {
|
||||||
|
gui.viewDispatcher.switchTo(views.loading);
|
||||||
|
|
||||||
|
let path = ctx.tmpTemplate + (ctx.tmpNumber++).toString();
|
||||||
|
let file = storage.openFile(path, "w", "create_always");
|
||||||
|
file.write(text);
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
// Hide GUI before running, we want to see console and avoid deadlock if code fails
|
||||||
|
gui.viewDispatcher.sendTo("back");
|
||||||
|
let result = load(path, ctx.persistentScope); // Load runs JS and returns last value on stack
|
||||||
|
storage.remove(path);
|
||||||
|
|
||||||
|
// Must convert to string explicitly
|
||||||
|
if (result === null) { // mJS: typeof null === "null", ECMAScript: typeof null === "object", IDE complains when checking "null" type
|
||||||
|
result = "null";
|
||||||
|
} else if (typeof result === "string") {
|
||||||
|
result = "'" + result + "'";
|
||||||
|
} else if (typeof result === "number") {
|
||||||
|
result = result.toString();
|
||||||
|
} else if (typeof result === "bigint") { // mJS doesn't support BigInt() but might aswell check
|
||||||
|
result = "bigint";
|
||||||
|
} else if (typeof result === "boolean") {
|
||||||
|
result = result ? "true" : "false";
|
||||||
|
} else if (typeof result === "symbol") { // mJS doesn't support Symbol() but might aswell check
|
||||||
|
result = "symbol";
|
||||||
|
} else if (typeof result === "undefined") {
|
||||||
|
result = "undefined";
|
||||||
|
} else if (typeof result === "object") {
|
||||||
|
result = "object"; // JSON.stringify() is not implemented
|
||||||
|
} else if (typeof result === "function") {
|
||||||
|
result = "function";
|
||||||
|
} else {
|
||||||
|
result = "unknown type: " + typeof result;
|
||||||
|
}
|
||||||
|
|
||||||
|
gui.viewDispatcher.sendTo("front");
|
||||||
|
views.dialog.set("header", "JS Returned:");
|
||||||
|
views.dialog.set("text", result);
|
||||||
|
gui.viewDispatcher.switchTo(views.dialog);
|
||||||
|
views.textInput.set("defaultText", text);
|
||||||
|
}, gui, views, ctx);
|
||||||
|
|
||||||
|
eventLoop.subscribe(gui.viewDispatcher.navigation, function (_sub, _, eventLoop) {
|
||||||
|
eventLoop.stop();
|
||||||
|
}, eventLoop);
|
||||||
|
|
||||||
|
gui.viewDispatcher.switchTo(views.dialog);
|
||||||
|
|
||||||
|
// Message behind GUI if something breaks
|
||||||
|
print("If you're stuck here, something went wrong, re-run the script")
|
||||||
|
eventLoop.run();
|
||||||
|
print("\n\nFinished correctly :)")
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
let math = load("/ext/apps/Scripts/load_api.js");
|
let math = load(__dirname + "/load_api.js");
|
||||||
let result = math.add(5, 10);
|
let result = math.add(5, 10);
|
||||||
print(result);
|
print(result);
|
||||||
|
|||||||
9
applications/system/js_app/examples/apps/Scripts/path.js
Normal file
9
applications/system/js_app/examples/apps/Scripts/path.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
let storage = require("storage");
|
||||||
|
|
||||||
|
print("script has __dirname of" + __dirname);
|
||||||
|
print("script has __filename of" + __filename);
|
||||||
|
if (storage.fileExists(__dirname + "/math.js")) {
|
||||||
|
print("math.js exist here.");
|
||||||
|
} else {
|
||||||
|
print("math.js does not exist here.");
|
||||||
|
}
|
||||||
29
applications/system/js_app/examples/apps/Scripts/storage.js
Normal file
29
applications/system/js_app/examples/apps/Scripts/storage.js
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
let storage = require("storage");
|
||||||
|
let path = "/ext/storage.test";
|
||||||
|
|
||||||
|
print("File exists:", storage.fileExists(path));
|
||||||
|
|
||||||
|
print("Writing...");
|
||||||
|
let file = storage.openFile(path, "w", "create_always");
|
||||||
|
file.write("Hello ");
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
print("File exists:", storage.fileExists(path));
|
||||||
|
|
||||||
|
file = storage.openFile(path, "w", "open_append");
|
||||||
|
file.write("World!");
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
print("Reading...");
|
||||||
|
file = storage.openFile(path, "r", "open_existing");
|
||||||
|
let text = file.read("ascii", 128);
|
||||||
|
file.close();
|
||||||
|
print(text);
|
||||||
|
|
||||||
|
print("Removing...")
|
||||||
|
storage.remove(path);
|
||||||
|
|
||||||
|
print("Done")
|
||||||
|
|
||||||
|
// You don't need to close the file after each operation, this is just to show some different ways to use the API
|
||||||
|
// There's also many more functions and options, check type definitions in firmware repo
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
let sampleText = "Hello, World!";
|
||||||
|
|
||||||
|
let lengthOfText = "Length of text: " + sampleText.length.toString();
|
||||||
|
print(lengthOfText);
|
||||||
|
|
||||||
|
let start = 7;
|
||||||
|
let end = 12;
|
||||||
|
let substringResult = sampleText.slice(start, end);
|
||||||
|
print(substringResult);
|
||||||
|
|
||||||
|
let searchStr = "World";
|
||||||
|
let result2 = sampleText.indexOf(searchStr).toString();
|
||||||
|
print(result2);
|
||||||
|
|
||||||
|
let upperCaseText = "Text in upper case: " + sampleText.toUpperCase();
|
||||||
|
print(upperCaseText);
|
||||||
|
|
||||||
|
let lowerCaseText = "Text in lower case: " + sampleText.toLowerCase();
|
||||||
|
print(lowerCaseText);
|
||||||
@@ -6,6 +6,9 @@ while (1) {
|
|||||||
if (rx_data !== undefined) {
|
if (rx_data !== undefined) {
|
||||||
serial.write(rx_data);
|
serial.write(rx_data);
|
||||||
let data_view = Uint8Array(rx_data);
|
let data_view = Uint8Array(rx_data);
|
||||||
print("0x" + toString(data_view[0], 16));
|
print("0x" + data_view[0].toString(16));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// There's also serial.end(), so you can serial.setup() again in same script
|
||||||
|
// You can also use serial.readAny(timeout), will avoid starving your loop with single byte reads
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ static void js_app_free(JsApp* app) {
|
|||||||
int32_t js_app(void* arg) {
|
int32_t js_app(void* arg) {
|
||||||
JsApp* app = js_app_alloc();
|
JsApp* app = js_app_alloc();
|
||||||
|
|
||||||
FuriString* script_path = furi_string_alloc_set(APP_ASSETS_PATH());
|
FuriString* script_path = furi_string_alloc_set(EXT_PATH("apps/Scripts"));
|
||||||
do {
|
do {
|
||||||
if(arg != NULL && strlen(arg) > 0) {
|
if(arg != NULL && strlen(arg) > 0) {
|
||||||
furi_string_set(script_path, (const char*)arg);
|
furi_string_set(script_path, (const char*)arg);
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
#include <core/common_defines.h>
|
#include <core/common_defines.h>
|
||||||
#include "js_modules.h"
|
#include "js_modules.h"
|
||||||
#include <m-array.h>
|
#include <m-array.h>
|
||||||
|
#include <dialogs/dialogs.h>
|
||||||
|
#include <assets_icons.h>
|
||||||
|
|
||||||
#include "modules/js_flipper.h"
|
#include "modules/js_flipper.h"
|
||||||
#ifdef FW_CFG_unit_tests
|
#ifdef FW_CFG_unit_tests
|
||||||
@@ -76,6 +78,12 @@ JsModuleData* js_find_loaded_module(JsModules* instance, const char* name) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mjs_val_t js_module_require(JsModules* modules, const char* name, size_t name_len) {
|
mjs_val_t js_module_require(JsModules* modules, const char* name, size_t name_len) {
|
||||||
|
// Ignore the initial part of the module name
|
||||||
|
const char* optional_module_prefix = "@" JS_SDK_VENDOR "/fz-sdk/";
|
||||||
|
if(strncmp(name, optional_module_prefix, strlen(optional_module_prefix)) == 0) {
|
||||||
|
name += strlen(optional_module_prefix);
|
||||||
|
}
|
||||||
|
|
||||||
// Check if module is already installed
|
// Check if module is already installed
|
||||||
JsModuleData* module_inst = js_find_loaded_module(modules, name);
|
JsModuleData* module_inst = js_find_loaded_module(modules, name);
|
||||||
if(module_inst) { //-V547
|
if(module_inst) { //-V547
|
||||||
@@ -175,3 +183,133 @@ void* js_module_get(JsModules* modules, const char* name) {
|
|||||||
furi_string_free(module_name);
|
furi_string_free(module_name);
|
||||||
return module_inst ? module_inst->context : NULL;
|
return module_inst ? module_inst->context : NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
JsSdkCompatStatusCompatible,
|
||||||
|
JsSdkCompatStatusFirmwareTooOld,
|
||||||
|
JsSdkCompatStatusFirmwareTooNew,
|
||||||
|
} JsSdkCompatStatus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Checks compatibility between the firmware and the JS SDK version
|
||||||
|
* expected by the script
|
||||||
|
*/
|
||||||
|
static JsSdkCompatStatus
|
||||||
|
js_internal_sdk_compatibility_status(int32_t exp_major, int32_t exp_minor) {
|
||||||
|
if(exp_major < JS_SDK_MAJOR) return JsSdkCompatStatusFirmwareTooNew;
|
||||||
|
if(exp_major > JS_SDK_MAJOR || exp_minor > JS_SDK_MINOR)
|
||||||
|
return JsSdkCompatStatusFirmwareTooOld;
|
||||||
|
return JsSdkCompatStatusCompatible;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define JS_SDK_COMPAT_ARGS \
|
||||||
|
int32_t major, minor; \
|
||||||
|
JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_INT32(&major), JS_ARG_INT32(&minor));
|
||||||
|
|
||||||
|
void js_sdk_compatibility_status(struct mjs* mjs) {
|
||||||
|
JS_SDK_COMPAT_ARGS;
|
||||||
|
JsSdkCompatStatus status = js_internal_sdk_compatibility_status(major, minor);
|
||||||
|
switch(status) {
|
||||||
|
case JsSdkCompatStatusCompatible:
|
||||||
|
mjs_return(mjs, mjs_mk_string(mjs, "compatible", ~0, 0));
|
||||||
|
return;
|
||||||
|
case JsSdkCompatStatusFirmwareTooOld:
|
||||||
|
mjs_return(mjs, mjs_mk_string(mjs, "firmwareTooOld", ~0, 0));
|
||||||
|
return;
|
||||||
|
case JsSdkCompatStatusFirmwareTooNew:
|
||||||
|
mjs_return(mjs, mjs_mk_string(mjs, "firmwareTooNew", ~0, 0));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void js_is_sdk_compatible(struct mjs* mjs) {
|
||||||
|
JS_SDK_COMPAT_ARGS;
|
||||||
|
JsSdkCompatStatus status = js_internal_sdk_compatibility_status(major, minor);
|
||||||
|
mjs_return(mjs, mjs_mk_boolean(mjs, status == JsSdkCompatStatusCompatible));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Asks the user whether to continue executing an incompatible script
|
||||||
|
*/
|
||||||
|
static bool js_internal_compat_ask_user(const char* message) {
|
||||||
|
DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
|
||||||
|
DialogMessage* dialog = dialog_message_alloc();
|
||||||
|
dialog_message_set_header(dialog, message, 64, 0, AlignCenter, AlignTop);
|
||||||
|
dialog_message_set_text(
|
||||||
|
dialog, "This script may not\nwork as expected", 79, 32, AlignCenter, AlignCenter);
|
||||||
|
dialog_message_set_icon(dialog, &I_Warning_30x23, 0, 18);
|
||||||
|
dialog_message_set_buttons(dialog, "Go back", NULL, "Run anyway");
|
||||||
|
DialogMessageButton choice = dialog_message_show(dialogs, dialog);
|
||||||
|
dialog_message_free(dialog);
|
||||||
|
furi_record_close(RECORD_DIALOGS);
|
||||||
|
return choice == DialogMessageButtonRight;
|
||||||
|
}
|
||||||
|
|
||||||
|
void js_check_sdk_compatibility(struct mjs* mjs) {
|
||||||
|
JS_SDK_COMPAT_ARGS;
|
||||||
|
JsSdkCompatStatus status = js_internal_sdk_compatibility_status(major, minor);
|
||||||
|
if(status != JsSdkCompatStatusCompatible) {
|
||||||
|
FURI_LOG_E(
|
||||||
|
TAG,
|
||||||
|
"Script requests JS SDK %ld.%ld, firmware provides JS SDK %d.%d",
|
||||||
|
major,
|
||||||
|
minor,
|
||||||
|
JS_SDK_MAJOR,
|
||||||
|
JS_SDK_MINOR);
|
||||||
|
|
||||||
|
const char* message = (status == JsSdkCompatStatusFirmwareTooOld) ? "Outdated Firmware" :
|
||||||
|
"Outdated Script";
|
||||||
|
if(!js_internal_compat_ask_user(message)) {
|
||||||
|
JS_ERROR_AND_RETURN(mjs, MJS_NOT_IMPLEMENTED_ERROR, "Incompatible script");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char* extra_features[] = {
|
||||||
|
"baseline", // dummy "feature"
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Determines whether a feature is supported
|
||||||
|
*/
|
||||||
|
static bool js_internal_supports(const char* feature) {
|
||||||
|
for(size_t i = 0; i < COUNT_OF(extra_features); i++) { // -V1008
|
||||||
|
if(strcmp(feature, extra_features[i]) == 0) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Determines whether all of the requested features are supported
|
||||||
|
*/
|
||||||
|
static bool js_internal_supports_all_of(struct mjs* mjs, mjs_val_t feature_arr) {
|
||||||
|
furi_assert(mjs_is_array(feature_arr));
|
||||||
|
|
||||||
|
for(size_t i = 0; i < mjs_array_length(mjs, feature_arr); i++) {
|
||||||
|
mjs_val_t feature = mjs_array_get(mjs, feature_arr, i);
|
||||||
|
const char* feature_str = mjs_get_string(mjs, &feature, NULL);
|
||||||
|
if(!feature_str) return false;
|
||||||
|
|
||||||
|
if(!js_internal_supports(feature_str)) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void js_does_sdk_support(struct mjs* mjs) {
|
||||||
|
mjs_val_t features;
|
||||||
|
JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_ARR(&features));
|
||||||
|
mjs_return(mjs, mjs_mk_boolean(mjs, js_internal_supports_all_of(mjs, features)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void js_check_sdk_features(struct mjs* mjs) {
|
||||||
|
mjs_val_t features;
|
||||||
|
JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_ARR(&features));
|
||||||
|
if(!js_internal_supports_all_of(mjs, features)) {
|
||||||
|
FURI_LOG_E(TAG, "Script requests unsupported features");
|
||||||
|
|
||||||
|
if(!js_internal_compat_ask_user("Unsupported Feature")) {
|
||||||
|
JS_ERROR_AND_RETURN(mjs, MJS_NOT_IMPLEMENTED_ERROR, "Incompatible script");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,6 +9,10 @@
|
|||||||
#define PLUGIN_APP_ID "js"
|
#define PLUGIN_APP_ID "js"
|
||||||
#define PLUGIN_API_VERSION 1
|
#define PLUGIN_API_VERSION 1
|
||||||
|
|
||||||
|
#define JS_SDK_VENDOR "flipperdevices"
|
||||||
|
#define JS_SDK_MAJOR 0
|
||||||
|
#define JS_SDK_MINOR 1
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Returns the foreign pointer in `obj["_"]`
|
* @brief Returns the foreign pointer in `obj["_"]`
|
||||||
*/
|
*/
|
||||||
@@ -275,3 +279,28 @@ mjs_val_t js_module_require(JsModules* modules, const char* name, size_t name_le
|
|||||||
* @returns Pointer to module context, NULL if the module is not instantiated
|
* @returns Pointer to module context, NULL if the module is not instantiated
|
||||||
*/
|
*/
|
||||||
void* js_module_get(JsModules* modules, const char* name);
|
void* js_module_get(JsModules* modules, const char* name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief `sdkCompatibilityStatus` function
|
||||||
|
*/
|
||||||
|
void js_sdk_compatibility_status(struct mjs* mjs);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief `isSdkCompatible` function
|
||||||
|
*/
|
||||||
|
void js_is_sdk_compatible(struct mjs* mjs);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief `checkSdkCompatibility` function
|
||||||
|
*/
|
||||||
|
void js_check_sdk_compatibility(struct mjs* mjs);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief `doesSdkSupport` function
|
||||||
|
*/
|
||||||
|
void js_does_sdk_support(struct mjs* mjs);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief `checkSdkFeatures` function
|
||||||
|
*/
|
||||||
|
void js_check_sdk_features(struct mjs* mjs);
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
#include <common/cs_dbg.h>
|
#include <common/cs_dbg.h>
|
||||||
|
#include <toolbox/path.h>
|
||||||
#include <toolbox/stream/file_stream.h>
|
#include <toolbox/stream/file_stream.h>
|
||||||
|
#include <toolbox/strint.h>
|
||||||
#include <loader/firmware_api/firmware_api.h>
|
#include <loader/firmware_api/firmware_api.h>
|
||||||
#include <flipper_application/api_hashtable/api_hashtable.h>
|
#include <flipper_application/api_hashtable/api_hashtable.h>
|
||||||
#include <flipper_application/plugins/composite_resolver.h>
|
#include <flipper_application/plugins/composite_resolver.h>
|
||||||
@@ -194,14 +196,25 @@ static void js_require(struct mjs* mjs) {
|
|||||||
mjs_return(mjs, req_object);
|
mjs_return(mjs, req_object);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void js_global_to_string(struct mjs* mjs) {
|
static void js_parse_int(struct mjs* mjs) {
|
||||||
int base = 10;
|
const char* str;
|
||||||
if(mjs_nargs(mjs) > 1) base = mjs_get_int(mjs, mjs_arg(mjs, 1));
|
JS_FETCH_ARGS_OR_RETURN(mjs, JS_AT_LEAST, JS_ARG_STR(&str));
|
||||||
double num = mjs_get_int(mjs, mjs_arg(mjs, 0));
|
|
||||||
char tmp_str[] = "-2147483648";
|
int32_t base = 10;
|
||||||
itoa(num, tmp_str, base);
|
if(mjs_nargs(mjs) >= 2) {
|
||||||
mjs_val_t ret = mjs_mk_string(mjs, tmp_str, ~0, true);
|
mjs_val_t base_arg = mjs_arg(mjs, 1);
|
||||||
mjs_return(mjs, ret);
|
if(!mjs_is_number(base_arg)) {
|
||||||
|
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "Base must be a number");
|
||||||
|
mjs_return(mjs, MJS_UNDEFINED);
|
||||||
|
}
|
||||||
|
base = mjs_get_int(mjs, base_arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t num;
|
||||||
|
if(strint_to_int32(str, NULL, &num, base) != StrintParseNoError) {
|
||||||
|
num = 0;
|
||||||
|
}
|
||||||
|
mjs_return(mjs, mjs_mk_number(mjs, num));
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef JS_DEBUG
|
#ifdef JS_DEBUG
|
||||||
@@ -231,18 +244,48 @@ static int32_t js_thread(void* arg) {
|
|||||||
struct mjs* mjs = mjs_create(worker);
|
struct mjs* mjs = mjs_create(worker);
|
||||||
worker->modules = js_modules_create(mjs, worker->resolver);
|
worker->modules = js_modules_create(mjs, worker->resolver);
|
||||||
mjs_val_t global = mjs_get_global(mjs);
|
mjs_val_t global = mjs_get_global(mjs);
|
||||||
mjs_set(mjs, global, "print", ~0, MJS_MK_FN(js_print));
|
|
||||||
mjs_set(mjs, global, "delay", ~0, MJS_MK_FN(js_delay));
|
|
||||||
mjs_set(mjs, global, "toString", ~0, MJS_MK_FN(js_global_to_string));
|
|
||||||
mjs_set(mjs, global, "ffi_address", ~0, MJS_MK_FN(js_ffi_address));
|
|
||||||
mjs_set(mjs, global, "require", ~0, MJS_MK_FN(js_require));
|
|
||||||
|
|
||||||
mjs_val_t console_obj = mjs_mk_object(mjs);
|
mjs_val_t console_obj = mjs_mk_object(mjs);
|
||||||
mjs_set(mjs, console_obj, "log", ~0, MJS_MK_FN(js_console_log));
|
|
||||||
mjs_set(mjs, console_obj, "warn", ~0, MJS_MK_FN(js_console_warn));
|
if(worker->path) {
|
||||||
mjs_set(mjs, console_obj, "error", ~0, MJS_MK_FN(js_console_error));
|
FuriString* dirpath = furi_string_alloc();
|
||||||
mjs_set(mjs, console_obj, "debug", ~0, MJS_MK_FN(js_console_debug));
|
path_extract_dirname(furi_string_get_cstr(worker->path), dirpath);
|
||||||
mjs_set(mjs, global, "console", ~0, console_obj);
|
mjs_set(
|
||||||
|
mjs,
|
||||||
|
global,
|
||||||
|
"__filename",
|
||||||
|
~0,
|
||||||
|
mjs_mk_string(
|
||||||
|
mjs, furi_string_get_cstr(worker->path), furi_string_size(worker->path), true));
|
||||||
|
mjs_set(
|
||||||
|
mjs,
|
||||||
|
global,
|
||||||
|
"__dirname",
|
||||||
|
~0,
|
||||||
|
mjs_mk_string(mjs, furi_string_get_cstr(dirpath), furi_string_size(dirpath), true));
|
||||||
|
furi_string_free(dirpath);
|
||||||
|
}
|
||||||
|
|
||||||
|
JS_ASSIGN_MULTI(mjs, global) {
|
||||||
|
JS_FIELD("print", MJS_MK_FN(js_print));
|
||||||
|
JS_FIELD("delay", MJS_MK_FN(js_delay));
|
||||||
|
JS_FIELD("parseInt", MJS_MK_FN(js_parse_int));
|
||||||
|
JS_FIELD("ffi_address", MJS_MK_FN(js_ffi_address));
|
||||||
|
JS_FIELD("require", MJS_MK_FN(js_require));
|
||||||
|
JS_FIELD("console", console_obj);
|
||||||
|
|
||||||
|
JS_FIELD("sdkCompatibilityStatus", MJS_MK_FN(js_sdk_compatibility_status));
|
||||||
|
JS_FIELD("isSdkCompatible", MJS_MK_FN(js_is_sdk_compatible));
|
||||||
|
JS_FIELD("checkSdkCompatibility", MJS_MK_FN(js_check_sdk_compatibility));
|
||||||
|
JS_FIELD("doesSdkSupport", MJS_MK_FN(js_does_sdk_support));
|
||||||
|
JS_FIELD("checkSdkFeatures", MJS_MK_FN(js_check_sdk_features));
|
||||||
|
}
|
||||||
|
|
||||||
|
JS_ASSIGN_MULTI(mjs, console_obj) {
|
||||||
|
JS_FIELD("log", MJS_MK_FN(js_console_log));
|
||||||
|
JS_FIELD("warn", MJS_MK_FN(js_console_warn));
|
||||||
|
JS_FIELD("error", MJS_MK_FN(js_console_error));
|
||||||
|
JS_FIELD("debug", MJS_MK_FN(js_console_debug));
|
||||||
|
}
|
||||||
|
|
||||||
mjs_set_ffi_resolver(mjs, js_dlsym, worker->resolver);
|
mjs_set_ffi_resolver(mjs, js_dlsym, worker->resolver);
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,11 @@
|
|||||||
#include "../js_modules.h"
|
#include "../js_modules.h"
|
||||||
#include <furi_hal.h>
|
#include <furi_hal.h>
|
||||||
|
|
||||||
|
#define ASCII_TO_KEY(layout, x) (((uint8_t)x < 128) ? (layout[(uint8_t)x]) : HID_KEYBOARD_NONE)
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
FuriHalUsbHidConfig* hid_cfg;
|
FuriHalUsbHidConfig* hid_cfg;
|
||||||
|
uint16_t layout[128];
|
||||||
FuriHalUsbInterface* usb_if_prev;
|
FuriHalUsbInterface* usb_if_prev;
|
||||||
uint8_t key_hold_cnt;
|
uint8_t key_hold_cnt;
|
||||||
} JsBadusbInst;
|
} JsBadusbInst;
|
||||||
@@ -64,9 +67,36 @@ static const struct {
|
|||||||
{"F22", HID_KEYBOARD_F22},
|
{"F22", HID_KEYBOARD_F22},
|
||||||
{"F23", HID_KEYBOARD_F23},
|
{"F23", HID_KEYBOARD_F23},
|
||||||
{"F24", HID_KEYBOARD_F24},
|
{"F24", HID_KEYBOARD_F24},
|
||||||
|
|
||||||
|
{"NUM0", HID_KEYPAD_0},
|
||||||
|
{"NUM1", HID_KEYPAD_1},
|
||||||
|
{"NUM2", HID_KEYPAD_2},
|
||||||
|
{"NUM3", HID_KEYPAD_3},
|
||||||
|
{"NUM4", HID_KEYPAD_4},
|
||||||
|
{"NUM5", HID_KEYPAD_5},
|
||||||
|
{"NUM6", HID_KEYPAD_6},
|
||||||
|
{"NUM7", HID_KEYPAD_7},
|
||||||
|
{"NUM8", HID_KEYPAD_8},
|
||||||
|
{"NUM9", HID_KEYPAD_9},
|
||||||
};
|
};
|
||||||
|
|
||||||
static bool setup_parse_params(struct mjs* mjs, mjs_val_t arg, FuriHalUsbHidConfig* hid_cfg) {
|
static void js_badusb_quit_free(JsBadusbInst* badusb) {
|
||||||
|
if(badusb->usb_if_prev) {
|
||||||
|
furi_hal_hid_kb_release_all();
|
||||||
|
furi_check(furi_hal_usb_set_config(badusb->usb_if_prev, NULL));
|
||||||
|
badusb->usb_if_prev = NULL;
|
||||||
|
}
|
||||||
|
if(badusb->hid_cfg) {
|
||||||
|
free(badusb->hid_cfg);
|
||||||
|
badusb->hid_cfg = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool setup_parse_params(
|
||||||
|
JsBadusbInst* badusb,
|
||||||
|
struct mjs* mjs,
|
||||||
|
mjs_val_t arg,
|
||||||
|
FuriHalUsbHidConfig* hid_cfg) {
|
||||||
if(!mjs_is_object(arg)) {
|
if(!mjs_is_object(arg)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -74,6 +104,7 @@ static bool setup_parse_params(struct mjs* mjs, mjs_val_t arg, FuriHalUsbHidConf
|
|||||||
mjs_val_t pid_obj = mjs_get(mjs, arg, "pid", ~0);
|
mjs_val_t pid_obj = mjs_get(mjs, arg, "pid", ~0);
|
||||||
mjs_val_t mfr_obj = mjs_get(mjs, arg, "mfrName", ~0);
|
mjs_val_t mfr_obj = mjs_get(mjs, arg, "mfrName", ~0);
|
||||||
mjs_val_t prod_obj = mjs_get(mjs, arg, "prodName", ~0);
|
mjs_val_t prod_obj = mjs_get(mjs, arg, "prodName", ~0);
|
||||||
|
mjs_val_t layout_obj = mjs_get(mjs, arg, "layoutPath", ~0);
|
||||||
|
|
||||||
if(mjs_is_number(vid_obj) && mjs_is_number(pid_obj)) {
|
if(mjs_is_number(vid_obj) && mjs_is_number(pid_obj)) {
|
||||||
hid_cfg->vid = mjs_get_int32(mjs, vid_obj);
|
hid_cfg->vid = mjs_get_int32(mjs, vid_obj);
|
||||||
@@ -100,6 +131,25 @@ static bool setup_parse_params(struct mjs* mjs, mjs_val_t arg, FuriHalUsbHidConf
|
|||||||
strlcpy(hid_cfg->product, str_temp, sizeof(hid_cfg->product));
|
strlcpy(hid_cfg->product, str_temp, sizeof(hid_cfg->product));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(mjs_is_string(layout_obj)) {
|
||||||
|
size_t str_len = 0;
|
||||||
|
const char* str_temp = mjs_get_string(mjs, &layout_obj, &str_len);
|
||||||
|
if((str_len == 0) || (str_temp == NULL)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
File* file = storage_file_alloc(furi_record_open(RECORD_STORAGE));
|
||||||
|
bool layout_loaded = storage_file_open(file, str_temp, FSAM_READ, FSOM_OPEN_EXISTING) &&
|
||||||
|
storage_file_read(file, badusb->layout, sizeof(badusb->layout)) ==
|
||||||
|
sizeof(badusb->layout);
|
||||||
|
storage_file_free(file);
|
||||||
|
furi_record_close(RECORD_STORAGE);
|
||||||
|
if(!layout_loaded) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
memcpy(badusb->layout, hid_asciimap, MIN(sizeof(hid_asciimap), sizeof(badusb->layout)));
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,7 +172,7 @@ static void js_badusb_setup(struct mjs* mjs) {
|
|||||||
} else if(num_args == 1) {
|
} else if(num_args == 1) {
|
||||||
badusb->hid_cfg = malloc(sizeof(FuriHalUsbHidConfig));
|
badusb->hid_cfg = malloc(sizeof(FuriHalUsbHidConfig));
|
||||||
// Parse argument object
|
// Parse argument object
|
||||||
args_correct = setup_parse_params(mjs, mjs_arg(mjs, 0), badusb->hid_cfg);
|
args_correct = setup_parse_params(badusb, mjs, mjs_arg(mjs, 0), badusb->hid_cfg);
|
||||||
}
|
}
|
||||||
if(!args_correct) {
|
if(!args_correct) {
|
||||||
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "");
|
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "");
|
||||||
@@ -142,6 +192,22 @@ static void js_badusb_setup(struct mjs* mjs) {
|
|||||||
mjs_return(mjs, MJS_UNDEFINED);
|
mjs_return(mjs, MJS_UNDEFINED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void js_badusb_quit(struct mjs* mjs) {
|
||||||
|
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0);
|
||||||
|
JsBadusbInst* badusb = mjs_get_ptr(mjs, obj_inst);
|
||||||
|
furi_assert(badusb);
|
||||||
|
|
||||||
|
if(badusb->usb_if_prev == NULL) {
|
||||||
|
mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "HID is not started");
|
||||||
|
mjs_return(mjs, MJS_UNDEFINED);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
js_badusb_quit_free(badusb);
|
||||||
|
|
||||||
|
mjs_return(mjs, MJS_UNDEFINED);
|
||||||
|
}
|
||||||
|
|
||||||
static void js_badusb_is_connected(struct mjs* mjs) {
|
static void js_badusb_is_connected(struct mjs* mjs) {
|
||||||
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0);
|
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0);
|
||||||
JsBadusbInst* badusb = mjs_get_ptr(mjs, obj_inst);
|
JsBadusbInst* badusb = mjs_get_ptr(mjs, obj_inst);
|
||||||
@@ -157,9 +223,9 @@ static void js_badusb_is_connected(struct mjs* mjs) {
|
|||||||
mjs_return(mjs, mjs_mk_boolean(mjs, is_connected));
|
mjs_return(mjs, mjs_mk_boolean(mjs, is_connected));
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t get_keycode_by_name(const char* key_name, size_t name_len) {
|
uint16_t get_keycode_by_name(JsBadusbInst* badusb, const char* key_name, size_t name_len) {
|
||||||
if(name_len == 1) { // Single char
|
if(name_len == 1) { // Single char
|
||||||
return HID_ASCII_TO_KEY(key_name[0]);
|
return (ASCII_TO_KEY(badusb->layout, key_name[0]));
|
||||||
}
|
}
|
||||||
|
|
||||||
for(size_t i = 0; i < COUNT_OF(key_codes); i++) {
|
for(size_t i = 0; i < COUNT_OF(key_codes); i++) {
|
||||||
@@ -176,7 +242,7 @@ uint16_t get_keycode_by_name(const char* key_name, size_t name_len) {
|
|||||||
return HID_KEYBOARD_NONE;
|
return HID_KEYBOARD_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool parse_keycode(struct mjs* mjs, size_t nargs, uint16_t* keycode) {
|
static bool parse_keycode(JsBadusbInst* badusb, struct mjs* mjs, size_t nargs, uint16_t* keycode) {
|
||||||
uint16_t key_tmp = 0;
|
uint16_t key_tmp = 0;
|
||||||
for(size_t i = 0; i < nargs; i++) {
|
for(size_t i = 0; i < nargs; i++) {
|
||||||
mjs_val_t arg = mjs_arg(mjs, i);
|
mjs_val_t arg = mjs_arg(mjs, i);
|
||||||
@@ -187,7 +253,7 @@ static bool parse_keycode(struct mjs* mjs, size_t nargs, uint16_t* keycode) {
|
|||||||
// String error
|
// String error
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
uint16_t str_key = get_keycode_by_name(key_name, name_len);
|
uint16_t str_key = get_keycode_by_name(badusb, key_name, name_len);
|
||||||
if(str_key == HID_KEYBOARD_NONE) {
|
if(str_key == HID_KEYBOARD_NONE) {
|
||||||
// Unknown key code
|
// Unknown key code
|
||||||
return false;
|
return false;
|
||||||
@@ -225,7 +291,7 @@ static void js_badusb_press(struct mjs* mjs) {
|
|||||||
uint16_t keycode = HID_KEYBOARD_NONE;
|
uint16_t keycode = HID_KEYBOARD_NONE;
|
||||||
size_t num_args = mjs_nargs(mjs);
|
size_t num_args = mjs_nargs(mjs);
|
||||||
if(num_args > 0) {
|
if(num_args > 0) {
|
||||||
args_correct = parse_keycode(mjs, num_args, &keycode);
|
args_correct = parse_keycode(badusb, mjs, num_args, &keycode);
|
||||||
}
|
}
|
||||||
if(!args_correct) {
|
if(!args_correct) {
|
||||||
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "");
|
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "");
|
||||||
@@ -251,7 +317,7 @@ static void js_badusb_hold(struct mjs* mjs) {
|
|||||||
uint16_t keycode = HID_KEYBOARD_NONE;
|
uint16_t keycode = HID_KEYBOARD_NONE;
|
||||||
size_t num_args = mjs_nargs(mjs);
|
size_t num_args = mjs_nargs(mjs);
|
||||||
if(num_args > 0) {
|
if(num_args > 0) {
|
||||||
args_correct = parse_keycode(mjs, num_args, &keycode);
|
args_correct = parse_keycode(badusb, mjs, num_args, &keycode);
|
||||||
}
|
}
|
||||||
if(!args_correct) {
|
if(!args_correct) {
|
||||||
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "");
|
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "");
|
||||||
@@ -290,7 +356,7 @@ static void js_badusb_release(struct mjs* mjs) {
|
|||||||
mjs_return(mjs, MJS_UNDEFINED);
|
mjs_return(mjs, MJS_UNDEFINED);
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
args_correct = parse_keycode(mjs, num_args, &keycode);
|
args_correct = parse_keycode(badusb, mjs, num_args, &keycode);
|
||||||
}
|
}
|
||||||
if(!args_correct) {
|
if(!args_correct) {
|
||||||
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "");
|
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "");
|
||||||
@@ -304,7 +370,35 @@ static void js_badusb_release(struct mjs* mjs) {
|
|||||||
mjs_return(mjs, MJS_UNDEFINED);
|
mjs_return(mjs, MJS_UNDEFINED);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void badusb_print(struct mjs* mjs, bool ln) {
|
// Make sure NUMLOCK is enabled for altchar
|
||||||
|
static void ducky_numlock_on() {
|
||||||
|
if((furi_hal_hid_get_led_state() & HID_KB_LED_NUM) == 0) {
|
||||||
|
furi_hal_hid_kb_press(HID_KEYBOARD_LOCK_NUM_LOCK);
|
||||||
|
furi_hal_hid_kb_release(HID_KEYBOARD_LOCK_NUM_LOCK);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simulate pressing a character using ALT+Numpad ASCII code
|
||||||
|
static void ducky_altchar(JsBadusbInst* badusb, const char* ascii_code) {
|
||||||
|
// Hold the ALT key
|
||||||
|
furi_hal_hid_kb_press(KEY_MOD_LEFT_ALT);
|
||||||
|
|
||||||
|
// Press the corresponding numpad key for each digit of the ASCII code
|
||||||
|
for(size_t i = 0; ascii_code[i] != '\0'; i++) {
|
||||||
|
char digitChar[5] = {'N', 'U', 'M', ascii_code[i], '\0'}; // Construct the numpad key name
|
||||||
|
uint16_t numpad_keycode = get_keycode_by_name(badusb, digitChar, strlen(digitChar));
|
||||||
|
if(numpad_keycode == HID_KEYBOARD_NONE) {
|
||||||
|
continue; // Skip if keycode not found
|
||||||
|
}
|
||||||
|
furi_hal_hid_kb_press(numpad_keycode);
|
||||||
|
furi_hal_hid_kb_release(numpad_keycode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release the ALT key
|
||||||
|
furi_hal_hid_kb_release(KEY_MOD_LEFT_ALT);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void badusb_print(struct mjs* mjs, bool ln, bool alt) {
|
||||||
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0);
|
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0);
|
||||||
JsBadusbInst* badusb = mjs_get_ptr(mjs, obj_inst);
|
JsBadusbInst* badusb = mjs_get_ptr(mjs, obj_inst);
|
||||||
furi_assert(badusb);
|
furi_assert(badusb);
|
||||||
@@ -350,10 +444,20 @@ static void badusb_print(struct mjs* mjs, bool ln) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(alt) {
|
||||||
|
ducky_numlock_on();
|
||||||
|
}
|
||||||
for(size_t i = 0; i < text_len; i++) {
|
for(size_t i = 0; i < text_len; i++) {
|
||||||
uint16_t keycode = HID_ASCII_TO_KEY(text_str[i]);
|
if(alt) {
|
||||||
furi_hal_hid_kb_press(keycode);
|
// Convert character to ascii numeric value
|
||||||
furi_hal_hid_kb_release(keycode);
|
char ascii_str[4];
|
||||||
|
snprintf(ascii_str, sizeof(ascii_str), "%u", (uint8_t)text_str[i]);
|
||||||
|
ducky_altchar(badusb, ascii_str);
|
||||||
|
} else {
|
||||||
|
uint16_t keycode = ASCII_TO_KEY(badusb->layout, text_str[i]);
|
||||||
|
furi_hal_hid_kb_press(keycode);
|
||||||
|
furi_hal_hid_kb_release(keycode);
|
||||||
|
}
|
||||||
if(delay_val > 0) {
|
if(delay_val > 0) {
|
||||||
bool need_exit = js_delay_with_flags(mjs, delay_val);
|
bool need_exit = js_delay_with_flags(mjs, delay_val);
|
||||||
if(need_exit) {
|
if(need_exit) {
|
||||||
@@ -371,11 +475,19 @@ static void badusb_print(struct mjs* mjs, bool ln) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void js_badusb_print(struct mjs* mjs) {
|
static void js_badusb_print(struct mjs* mjs) {
|
||||||
badusb_print(mjs, false);
|
badusb_print(mjs, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void js_badusb_println(struct mjs* mjs) {
|
static void js_badusb_println(struct mjs* mjs) {
|
||||||
badusb_print(mjs, true);
|
badusb_print(mjs, true, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void js_badusb_alt_print(struct mjs* mjs) {
|
||||||
|
badusb_print(mjs, false, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void js_badusb_alt_println(struct mjs* mjs) {
|
||||||
|
badusb_print(mjs, true, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void* js_badusb_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) {
|
static void* js_badusb_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) {
|
||||||
@@ -384,25 +496,22 @@ static void* js_badusb_create(struct mjs* mjs, mjs_val_t* object, JsModules* mod
|
|||||||
mjs_val_t badusb_obj = mjs_mk_object(mjs);
|
mjs_val_t badusb_obj = mjs_mk_object(mjs);
|
||||||
mjs_set(mjs, badusb_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, badusb));
|
mjs_set(mjs, badusb_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, badusb));
|
||||||
mjs_set(mjs, badusb_obj, "setup", ~0, MJS_MK_FN(js_badusb_setup));
|
mjs_set(mjs, badusb_obj, "setup", ~0, MJS_MK_FN(js_badusb_setup));
|
||||||
|
mjs_set(mjs, badusb_obj, "quit", ~0, MJS_MK_FN(js_badusb_quit));
|
||||||
mjs_set(mjs, badusb_obj, "isConnected", ~0, MJS_MK_FN(js_badusb_is_connected));
|
mjs_set(mjs, badusb_obj, "isConnected", ~0, MJS_MK_FN(js_badusb_is_connected));
|
||||||
mjs_set(mjs, badusb_obj, "press", ~0, MJS_MK_FN(js_badusb_press));
|
mjs_set(mjs, badusb_obj, "press", ~0, MJS_MK_FN(js_badusb_press));
|
||||||
mjs_set(mjs, badusb_obj, "hold", ~0, MJS_MK_FN(js_badusb_hold));
|
mjs_set(mjs, badusb_obj, "hold", ~0, MJS_MK_FN(js_badusb_hold));
|
||||||
mjs_set(mjs, badusb_obj, "release", ~0, MJS_MK_FN(js_badusb_release));
|
mjs_set(mjs, badusb_obj, "release", ~0, MJS_MK_FN(js_badusb_release));
|
||||||
mjs_set(mjs, badusb_obj, "print", ~0, MJS_MK_FN(js_badusb_print));
|
mjs_set(mjs, badusb_obj, "print", ~0, MJS_MK_FN(js_badusb_print));
|
||||||
mjs_set(mjs, badusb_obj, "println", ~0, MJS_MK_FN(js_badusb_println));
|
mjs_set(mjs, badusb_obj, "println", ~0, MJS_MK_FN(js_badusb_println));
|
||||||
|
mjs_set(mjs, badusb_obj, "altPrint", ~0, MJS_MK_FN(js_badusb_alt_print));
|
||||||
|
mjs_set(mjs, badusb_obj, "altPrintln", ~0, MJS_MK_FN(js_badusb_alt_println));
|
||||||
*object = badusb_obj;
|
*object = badusb_obj;
|
||||||
return badusb;
|
return badusb;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void js_badusb_destroy(void* inst) {
|
static void js_badusb_destroy(void* inst) {
|
||||||
JsBadusbInst* badusb = inst;
|
JsBadusbInst* badusb = inst;
|
||||||
if(badusb->usb_if_prev) {
|
js_badusb_quit_free(badusb);
|
||||||
furi_hal_hid_kb_release_all();
|
|
||||||
furi_check(furi_hal_usb_set_config(badusb->usb_if_prev, NULL));
|
|
||||||
}
|
|
||||||
if(badusb->hid_cfg) {
|
|
||||||
free(badusb->hid_cfg);
|
|
||||||
}
|
|
||||||
free(badusb);
|
free(badusb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ static void js_event_loop_callback_generic(void* param) {
|
|||||||
/**
|
/**
|
||||||
* @brief Handles non-timer events
|
* @brief Handles non-timer events
|
||||||
*/
|
*/
|
||||||
static bool js_event_loop_callback(void* object, void* param) {
|
static void js_event_loop_callback(void* object, void* param) {
|
||||||
JsEventLoopCallbackContext* context = param;
|
JsEventLoopCallbackContext* context = param;
|
||||||
|
|
||||||
if(context->transformer) {
|
if(context->transformer) {
|
||||||
@@ -102,8 +102,6 @@ static bool js_event_loop_callback(void* object, void* param) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
js_event_loop_callback_generic(param);
|
js_event_loop_callback_generic(param);
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -27,11 +27,19 @@ static void js_flipper_get_battery(struct mjs* mjs) {
|
|||||||
|
|
||||||
void* js_flipper_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) {
|
void* js_flipper_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) {
|
||||||
UNUSED(modules);
|
UNUSED(modules);
|
||||||
|
mjs_val_t sdk_vsn = mjs_mk_array(mjs);
|
||||||
|
mjs_array_push(mjs, sdk_vsn, mjs_mk_number(mjs, JS_SDK_MAJOR));
|
||||||
|
mjs_array_push(mjs, sdk_vsn, mjs_mk_number(mjs, JS_SDK_MINOR));
|
||||||
|
|
||||||
mjs_val_t flipper_obj = mjs_mk_object(mjs);
|
mjs_val_t flipper_obj = mjs_mk_object(mjs);
|
||||||
mjs_set(mjs, flipper_obj, "getModel", ~0, MJS_MK_FN(js_flipper_get_model));
|
|
||||||
mjs_set(mjs, flipper_obj, "getName", ~0, MJS_MK_FN(js_flipper_get_name));
|
|
||||||
mjs_set(mjs, flipper_obj, "getBatteryCharge", ~0, MJS_MK_FN(js_flipper_get_battery));
|
|
||||||
*object = flipper_obj;
|
*object = flipper_obj;
|
||||||
|
JS_ASSIGN_MULTI(mjs, flipper_obj) {
|
||||||
|
JS_FIELD("getModel", MJS_MK_FN(js_flipper_get_model));
|
||||||
|
JS_FIELD("getName", MJS_MK_FN(js_flipper_get_name));
|
||||||
|
JS_FIELD("getBatteryCharge", MJS_MK_FN(js_flipper_get_battery));
|
||||||
|
JS_FIELD("firmwareVendor", mjs_mk_string(mjs, JS_SDK_VENDOR, ~0, false));
|
||||||
|
JS_FIELD("jsSdkVersion", sdk_vsn);
|
||||||
|
}
|
||||||
|
|
||||||
return (void*)1;
|
return (void*)1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -220,7 +220,7 @@ static void js_gpio_interrupt(struct mjs* mjs) {
|
|||||||
* let gpio = require("gpio");
|
* let gpio = require("gpio");
|
||||||
* let pot = gpio.get("pc0");
|
* let pot = gpio.get("pc0");
|
||||||
* pot.init({ direction: "in", inMode: "analog" });
|
* pot.init({ direction: "in", inMode: "analog" });
|
||||||
* print("voltage:" pot.read_analog(), "mV");
|
* print("voltage:" pot.readAnalog(), "mV");
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
static void js_gpio_read_analog(struct mjs* mjs) {
|
static void js_gpio_read_analog(struct mjs* mjs) {
|
||||||
@@ -269,12 +269,11 @@ static void js_gpio_get(struct mjs* mjs) {
|
|||||||
manager_data->interrupt_semaphore = furi_semaphore_alloc(UINT32_MAX, 0);
|
manager_data->interrupt_semaphore = furi_semaphore_alloc(UINT32_MAX, 0);
|
||||||
manager_data->adc_handle = module->adc_handle;
|
manager_data->adc_handle = module->adc_handle;
|
||||||
manager_data->adc_channel = pin_record->channel;
|
manager_data->adc_channel = pin_record->channel;
|
||||||
mjs_own(mjs, &manager);
|
|
||||||
mjs_set(mjs, manager, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, manager_data));
|
mjs_set(mjs, manager, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, manager_data));
|
||||||
mjs_set(mjs, manager, "init", ~0, MJS_MK_FN(js_gpio_init));
|
mjs_set(mjs, manager, "init", ~0, MJS_MK_FN(js_gpio_init));
|
||||||
mjs_set(mjs, manager, "write", ~0, MJS_MK_FN(js_gpio_write));
|
mjs_set(mjs, manager, "write", ~0, MJS_MK_FN(js_gpio_write));
|
||||||
mjs_set(mjs, manager, "read", ~0, MJS_MK_FN(js_gpio_read));
|
mjs_set(mjs, manager, "read", ~0, MJS_MK_FN(js_gpio_read));
|
||||||
mjs_set(mjs, manager, "read_analog", ~0, MJS_MK_FN(js_gpio_read_analog));
|
mjs_set(mjs, manager, "readAnalog", ~0, MJS_MK_FN(js_gpio_read_analog));
|
||||||
mjs_set(mjs, manager, "interrupt", ~0, MJS_MK_FN(js_gpio_interrupt));
|
mjs_set(mjs, manager, "interrupt", ~0, MJS_MK_FN(js_gpio_interrupt));
|
||||||
mjs_return(mjs, manager);
|
mjs_return(mjs, manager);
|
||||||
|
|
||||||
|
|||||||
158
applications/system/js_app/modules/js_gui/byte_input.c
Normal file
158
applications/system/js_app/modules/js_gui/byte_input.c
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
#include "../../js_modules.h" // IWYU pragma: keep
|
||||||
|
#include "js_gui.h"
|
||||||
|
#include "../js_event_loop/js_event_loop.h"
|
||||||
|
#include <gui/modules/byte_input.h>
|
||||||
|
|
||||||
|
#define DEFAULT_BUF_SZ 4
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint8_t* buffer;
|
||||||
|
size_t buffer_size;
|
||||||
|
size_t default_data_size;
|
||||||
|
FuriString* header;
|
||||||
|
FuriSemaphore* input_semaphore;
|
||||||
|
JsEventLoopContract contract;
|
||||||
|
} JsByteKbContext;
|
||||||
|
|
||||||
|
static mjs_val_t
|
||||||
|
input_transformer(struct mjs* mjs, FuriSemaphore* semaphore, JsByteKbContext* context) {
|
||||||
|
furi_check(furi_semaphore_acquire(semaphore, 0) == FuriStatusOk);
|
||||||
|
return mjs_mk_array_buf(mjs, (char*)context->buffer, context->buffer_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void input_callback(JsByteKbContext* context) {
|
||||||
|
furi_semaphore_release(context->input_semaphore);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool header_assign(
|
||||||
|
struct mjs* mjs,
|
||||||
|
ByteInput* input,
|
||||||
|
JsViewPropValue value,
|
||||||
|
JsByteKbContext* context) {
|
||||||
|
UNUSED(mjs);
|
||||||
|
furi_string_set(context->header, value.string);
|
||||||
|
byte_input_set_header_text(input, furi_string_get_cstr(context->header));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
len_assign(struct mjs* mjs, ByteInput* input, JsViewPropValue value, JsByteKbContext* context) {
|
||||||
|
UNUSED(mjs);
|
||||||
|
UNUSED(input);
|
||||||
|
size_t new_buffer_size = value.number;
|
||||||
|
if(new_buffer_size < context->default_data_size) {
|
||||||
|
// Avoid confusing parameters from user
|
||||||
|
mjs_prepend_errorf(
|
||||||
|
mjs, MJS_BAD_ARGS_ERROR, "length must be larger than defaultData length");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
context->buffer_size = new_buffer_size;
|
||||||
|
context->buffer = realloc(context->buffer, context->buffer_size); //-V701
|
||||||
|
byte_input_set_result_callback(
|
||||||
|
input,
|
||||||
|
(ByteInputCallback)input_callback,
|
||||||
|
NULL,
|
||||||
|
context,
|
||||||
|
context->buffer,
|
||||||
|
context->buffer_size);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool default_data_assign(
|
||||||
|
struct mjs* mjs,
|
||||||
|
ByteInput* input,
|
||||||
|
JsViewPropValue value,
|
||||||
|
JsByteKbContext* context) {
|
||||||
|
UNUSED(mjs);
|
||||||
|
|
||||||
|
mjs_val_t array_buf = value.term;
|
||||||
|
if(mjs_is_data_view(array_buf)) {
|
||||||
|
array_buf = mjs_dataview_get_buf(mjs, array_buf);
|
||||||
|
}
|
||||||
|
char* default_data = mjs_array_buf_get_ptr(mjs, array_buf, &context->default_data_size);
|
||||||
|
if(context->buffer_size < context->default_data_size) {
|
||||||
|
// Ensure buffer is large enough for defaultData
|
||||||
|
context->buffer_size = context->default_data_size;
|
||||||
|
context->buffer = realloc(context->buffer, context->buffer_size); //-V701
|
||||||
|
}
|
||||||
|
memcpy(context->buffer, (uint8_t*)default_data, context->default_data_size);
|
||||||
|
if(context->buffer_size > context->default_data_size) {
|
||||||
|
// Reset previous data after defaultData
|
||||||
|
memset(
|
||||||
|
context->buffer + context->default_data_size,
|
||||||
|
0x00,
|
||||||
|
context->buffer_size - context->default_data_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
byte_input_set_result_callback(
|
||||||
|
input,
|
||||||
|
(ByteInputCallback)input_callback,
|
||||||
|
NULL,
|
||||||
|
context,
|
||||||
|
context->buffer,
|
||||||
|
context->buffer_size);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static JsByteKbContext* ctx_make(struct mjs* mjs, ByteInput* input, mjs_val_t view_obj) {
|
||||||
|
JsByteKbContext* context = malloc(sizeof(JsByteKbContext));
|
||||||
|
*context = (JsByteKbContext){
|
||||||
|
.buffer_size = DEFAULT_BUF_SZ,
|
||||||
|
.buffer = malloc(DEFAULT_BUF_SZ),
|
||||||
|
.header = furi_string_alloc(),
|
||||||
|
.input_semaphore = furi_semaphore_alloc(1, 0),
|
||||||
|
};
|
||||||
|
context->contract = (JsEventLoopContract){
|
||||||
|
.magic = JsForeignMagic_JsEventLoopContract,
|
||||||
|
.object_type = JsEventLoopObjectTypeSemaphore,
|
||||||
|
.object = context->input_semaphore,
|
||||||
|
.non_timer =
|
||||||
|
{
|
||||||
|
.event = FuriEventLoopEventIn,
|
||||||
|
.transformer = (JsEventLoopTransformer)input_transformer,
|
||||||
|
.transformer_context = context,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
byte_input_set_result_callback(
|
||||||
|
input,
|
||||||
|
(ByteInputCallback)input_callback,
|
||||||
|
NULL,
|
||||||
|
context,
|
||||||
|
context->buffer,
|
||||||
|
context->buffer_size);
|
||||||
|
mjs_set(mjs, view_obj, "input", ~0, mjs_mk_foreign(mjs, &context->contract));
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ctx_destroy(ByteInput* input, JsByteKbContext* context, FuriEventLoop* loop) {
|
||||||
|
UNUSED(input);
|
||||||
|
furi_event_loop_maybe_unsubscribe(loop, context->input_semaphore);
|
||||||
|
furi_semaphore_free(context->input_semaphore);
|
||||||
|
furi_string_free(context->header);
|
||||||
|
free(context->buffer);
|
||||||
|
free(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const JsViewDescriptor view_descriptor = {
|
||||||
|
.alloc = (JsViewAlloc)byte_input_alloc,
|
||||||
|
.free = (JsViewFree)byte_input_free,
|
||||||
|
.get_view = (JsViewGetView)byte_input_get_view,
|
||||||
|
.custom_make = (JsViewCustomMake)ctx_make,
|
||||||
|
.custom_destroy = (JsViewCustomDestroy)ctx_destroy,
|
||||||
|
.prop_cnt = 3,
|
||||||
|
.props = {
|
||||||
|
(JsViewPropDescriptor){
|
||||||
|
.name = "header",
|
||||||
|
.type = JsViewPropTypeString,
|
||||||
|
.assign = (JsViewPropAssign)header_assign},
|
||||||
|
(JsViewPropDescriptor){
|
||||||
|
.name = "length",
|
||||||
|
.type = JsViewPropTypeNumber,
|
||||||
|
.assign = (JsViewPropAssign)len_assign},
|
||||||
|
(JsViewPropDescriptor){
|
||||||
|
.name = "defaultData",
|
||||||
|
.type = JsViewPropTypeTypedArr,
|
||||||
|
.assign = (JsViewPropAssign)default_data_assign},
|
||||||
|
}};
|
||||||
|
|
||||||
|
JS_GUI_VIEW_DEF(byte_input, &view_descriptor);
|
||||||
47
applications/system/js_app/modules/js_gui/file_picker.c
Normal file
47
applications/system/js_app/modules/js_gui/file_picker.c
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
#include "../../js_modules.h"
|
||||||
|
#include <dialogs/dialogs.h>
|
||||||
|
#include <assets_icons.h>
|
||||||
|
|
||||||
|
static void js_gui_file_picker_pick_file(struct mjs* mjs) {
|
||||||
|
const char *base_path, *extension;
|
||||||
|
JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&base_path), JS_ARG_STR(&extension));
|
||||||
|
|
||||||
|
DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
|
||||||
|
const DialogsFileBrowserOptions browser_options = {
|
||||||
|
.extension = extension,
|
||||||
|
.icon = &I_file_10px,
|
||||||
|
.base_path = base_path,
|
||||||
|
};
|
||||||
|
FuriString* path = furi_string_alloc_set(base_path);
|
||||||
|
if(dialog_file_browser_show(dialogs, path, path, &browser_options)) {
|
||||||
|
mjs_return(mjs, mjs_mk_string(mjs, furi_string_get_cstr(path), ~0, true));
|
||||||
|
} else {
|
||||||
|
mjs_return(mjs, MJS_UNDEFINED);
|
||||||
|
}
|
||||||
|
furi_string_free(path);
|
||||||
|
furi_record_close(RECORD_DIALOGS);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void* js_gui_file_picker_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) {
|
||||||
|
UNUSED(modules);
|
||||||
|
*object = mjs_mk_object(mjs);
|
||||||
|
mjs_set(mjs, *object, "pickFile", ~0, MJS_MK_FN(js_gui_file_picker_pick_file));
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const JsModuleDescriptor js_gui_file_picker_desc = {
|
||||||
|
"gui__file_picker",
|
||||||
|
js_gui_file_picker_create,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const FlipperAppPluginDescriptor plugin_descriptor = {
|
||||||
|
.appid = PLUGIN_APP_ID,
|
||||||
|
.ep_api_version = PLUGIN_API_VERSION,
|
||||||
|
.entry_point = &js_gui_file_picker_desc,
|
||||||
|
};
|
||||||
|
|
||||||
|
const FlipperAppPluginDescriptor* js_gui_file_picker_ep(void) {
|
||||||
|
return &plugin_descriptor;
|
||||||
|
}
|
||||||
@@ -101,8 +101,10 @@ static void js_gui_vd_switch_to(struct mjs* mjs) {
|
|||||||
mjs_val_t view;
|
mjs_val_t view;
|
||||||
JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_OBJ(&view));
|
JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_OBJ(&view));
|
||||||
JsGuiViewData* view_data = JS_GET_INST(mjs, view);
|
JsGuiViewData* view_data = JS_GET_INST(mjs, view);
|
||||||
JsGui* module = JS_GET_CONTEXT(mjs);
|
mjs_val_t vd_obj = mjs_get_this(mjs);
|
||||||
|
JsGui* module = JS_GET_INST(mjs, vd_obj);
|
||||||
view_dispatcher_switch_to_view(module->dispatcher, (uint32_t)view_data->id);
|
view_dispatcher_switch_to_view(module->dispatcher, (uint32_t)view_data->id);
|
||||||
|
mjs_set(mjs, vd_obj, "currentView", ~0, view);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void* js_gui_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) {
|
static void* js_gui_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) {
|
||||||
@@ -154,6 +156,7 @@ static void* js_gui_create(struct mjs* mjs, mjs_val_t* object, JsModules* module
|
|||||||
JS_FIELD("switchTo", MJS_MK_FN(js_gui_vd_switch_to));
|
JS_FIELD("switchTo", MJS_MK_FN(js_gui_vd_switch_to));
|
||||||
JS_FIELD("custom", mjs_mk_foreign(mjs, &module->custom_contract));
|
JS_FIELD("custom", mjs_mk_foreign(mjs, &module->custom_contract));
|
||||||
JS_FIELD("navigation", mjs_mk_foreign(mjs, &module->navigation_contract));
|
JS_FIELD("navigation", mjs_mk_foreign(mjs, &module->navigation_contract));
|
||||||
|
JS_FIELD("currentView", MJS_NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
// create API object
|
// create API object
|
||||||
@@ -213,7 +216,21 @@ static bool
|
|||||||
expected_type = "array";
|
expected_type = "array";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
c_value = (JsViewPropValue){.array = value};
|
c_value = (JsViewPropValue){.term = value};
|
||||||
|
} break;
|
||||||
|
case JsViewPropTypeTypedArr: {
|
||||||
|
if(!mjs_is_typed_array(value)) {
|
||||||
|
expected_type = "typed_array";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
c_value = (JsViewPropValue){.term = value};
|
||||||
|
} break;
|
||||||
|
case JsViewPropTypeBool: {
|
||||||
|
if(!mjs_is_boolean(value)) {
|
||||||
|
expected_type = "bool";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
c_value = (JsViewPropValue){.boolean = mjs_get_bool(mjs, value)};
|
||||||
} break;
|
} break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,12 +9,15 @@ typedef enum {
|
|||||||
JsViewPropTypeString,
|
JsViewPropTypeString,
|
||||||
JsViewPropTypeNumber,
|
JsViewPropTypeNumber,
|
||||||
JsViewPropTypeArr,
|
JsViewPropTypeArr,
|
||||||
|
JsViewPropTypeTypedArr,
|
||||||
|
JsViewPropTypeBool,
|
||||||
} JsViewPropType;
|
} JsViewPropType;
|
||||||
|
|
||||||
typedef union {
|
typedef union {
|
||||||
const char* string;
|
const char* string;
|
||||||
int32_t number;
|
int32_t number;
|
||||||
mjs_val_t array;
|
bool boolean;
|
||||||
|
mjs_val_t term;
|
||||||
} JsViewPropValue;
|
} JsViewPropValue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -33,9 +33,9 @@ static bool
|
|||||||
static bool items_assign(struct mjs* mjs, Submenu* submenu, JsViewPropValue value, void* context) {
|
static bool items_assign(struct mjs* mjs, Submenu* submenu, JsViewPropValue value, void* context) {
|
||||||
UNUSED(mjs);
|
UNUSED(mjs);
|
||||||
submenu_reset(submenu);
|
submenu_reset(submenu);
|
||||||
size_t len = mjs_array_length(mjs, value.array);
|
size_t len = mjs_array_length(mjs, value.term);
|
||||||
for(size_t i = 0; i < len; i++) {
|
for(size_t i = 0; i < len; i++) {
|
||||||
mjs_val_t item = mjs_array_get(mjs, value.array, i);
|
mjs_val_t item = mjs_array_get(mjs, value.term, i);
|
||||||
if(!mjs_is_string(item)) return false;
|
if(!mjs_is_string(item)) return false;
|
||||||
submenu_add_item(submenu, mjs_get_string(mjs, &item, NULL), i, choose_callback, context);
|
submenu_add_item(submenu, mjs_get_string(mjs, &item, NULL), i, choose_callback, context);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,9 @@
|
|||||||
typedef struct {
|
typedef struct {
|
||||||
char* buffer;
|
char* buffer;
|
||||||
size_t buffer_size;
|
size_t buffer_size;
|
||||||
|
size_t default_text_size;
|
||||||
FuriString* header;
|
FuriString* header;
|
||||||
|
bool default_text_clear;
|
||||||
FuriSemaphore* input_semaphore;
|
FuriSemaphore* input_semaphore;
|
||||||
JsEventLoopContract contract;
|
JsEventLoopContract contract;
|
||||||
} JsKbdContext;
|
} JsKbdContext;
|
||||||
@@ -48,7 +50,14 @@ static bool max_len_assign(
|
|||||||
JsViewPropValue value,
|
JsViewPropValue value,
|
||||||
JsKbdContext* context) {
|
JsKbdContext* context) {
|
||||||
UNUSED(mjs);
|
UNUSED(mjs);
|
||||||
context->buffer_size = (size_t)(value.number + 1);
|
size_t new_buffer_size = value.number + 1;
|
||||||
|
if(new_buffer_size < context->default_text_size) {
|
||||||
|
// Avoid confusing parameters from user
|
||||||
|
mjs_prepend_errorf(
|
||||||
|
mjs, MJS_BAD_ARGS_ERROR, "maxLength must be larger than defaultText length");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
context->buffer_size = new_buffer_size;
|
||||||
context->buffer = realloc(context->buffer, context->buffer_size); //-V701
|
context->buffer = realloc(context->buffer, context->buffer_size); //-V701
|
||||||
text_input_set_result_callback(
|
text_input_set_result_callback(
|
||||||
input,
|
input,
|
||||||
@@ -56,17 +65,63 @@ static bool max_len_assign(
|
|||||||
context,
|
context,
|
||||||
context->buffer,
|
context->buffer,
|
||||||
context->buffer_size,
|
context->buffer_size,
|
||||||
true);
|
context->default_text_clear);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool default_text_assign(
|
||||||
|
struct mjs* mjs,
|
||||||
|
TextInput* input,
|
||||||
|
JsViewPropValue value,
|
||||||
|
JsKbdContext* context) {
|
||||||
|
UNUSED(mjs);
|
||||||
|
UNUSED(input);
|
||||||
|
|
||||||
|
if(value.string) {
|
||||||
|
context->default_text_size = strlen(value.string) + 1;
|
||||||
|
if(context->buffer_size < context->default_text_size) {
|
||||||
|
// Ensure buffer is large enough for defaultData
|
||||||
|
context->buffer_size = context->default_text_size;
|
||||||
|
context->buffer = realloc(context->buffer, context->buffer_size); //-V701
|
||||||
|
}
|
||||||
|
// Also trim excess previous data with strlcpy()
|
||||||
|
strlcpy(context->buffer, value.string, context->buffer_size); //-V575
|
||||||
|
text_input_set_result_callback(
|
||||||
|
input,
|
||||||
|
(TextInputCallback)input_callback,
|
||||||
|
context,
|
||||||
|
context->buffer,
|
||||||
|
context->buffer_size,
|
||||||
|
context->default_text_clear);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool default_text_clear_assign(
|
||||||
|
struct mjs* mjs,
|
||||||
|
TextInput* input,
|
||||||
|
JsViewPropValue value,
|
||||||
|
JsKbdContext* context) {
|
||||||
|
UNUSED(mjs);
|
||||||
|
|
||||||
|
context->default_text_clear = value.boolean;
|
||||||
|
text_input_set_result_callback(
|
||||||
|
input,
|
||||||
|
(TextInputCallback)input_callback,
|
||||||
|
context,
|
||||||
|
context->buffer,
|
||||||
|
context->buffer_size,
|
||||||
|
context->default_text_clear);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static JsKbdContext* ctx_make(struct mjs* mjs, TextInput* input, mjs_val_t view_obj) {
|
static JsKbdContext* ctx_make(struct mjs* mjs, TextInput* input, mjs_val_t view_obj) {
|
||||||
UNUSED(input);
|
|
||||||
JsKbdContext* context = malloc(sizeof(JsKbdContext));
|
JsKbdContext* context = malloc(sizeof(JsKbdContext));
|
||||||
*context = (JsKbdContext){
|
*context = (JsKbdContext){
|
||||||
.buffer_size = DEFAULT_BUF_SZ,
|
.buffer_size = DEFAULT_BUF_SZ,
|
||||||
.buffer = malloc(DEFAULT_BUF_SZ),
|
.buffer = malloc(DEFAULT_BUF_SZ),
|
||||||
.header = furi_string_alloc(),
|
.header = furi_string_alloc(),
|
||||||
|
.default_text_clear = false,
|
||||||
.input_semaphore = furi_semaphore_alloc(1, 0),
|
.input_semaphore = furi_semaphore_alloc(1, 0),
|
||||||
};
|
};
|
||||||
context->contract = (JsEventLoopContract){
|
context->contract = (JsEventLoopContract){
|
||||||
@@ -80,8 +135,13 @@ static JsKbdContext* ctx_make(struct mjs* mjs, TextInput* input, mjs_val_t view_
|
|||||||
.transformer_context = context,
|
.transformer_context = context,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
UNUSED(mjs);
|
text_input_set_result_callback(
|
||||||
UNUSED(view_obj);
|
input,
|
||||||
|
(TextInputCallback)input_callback,
|
||||||
|
context,
|
||||||
|
context->buffer,
|
||||||
|
context->buffer_size,
|
||||||
|
context->default_text_clear);
|
||||||
mjs_set(mjs, view_obj, "input", ~0, mjs_mk_foreign(mjs, &context->contract));
|
mjs_set(mjs, view_obj, "input", ~0, mjs_mk_foreign(mjs, &context->contract));
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
@@ -101,7 +161,7 @@ static const JsViewDescriptor view_descriptor = {
|
|||||||
.get_view = (JsViewGetView)text_input_get_view,
|
.get_view = (JsViewGetView)text_input_get_view,
|
||||||
.custom_make = (JsViewCustomMake)ctx_make,
|
.custom_make = (JsViewCustomMake)ctx_make,
|
||||||
.custom_destroy = (JsViewCustomDestroy)ctx_destroy,
|
.custom_destroy = (JsViewCustomDestroy)ctx_destroy,
|
||||||
.prop_cnt = 3,
|
.prop_cnt = 5,
|
||||||
.props = {
|
.props = {
|
||||||
(JsViewPropDescriptor){
|
(JsViewPropDescriptor){
|
||||||
.name = "header",
|
.name = "header",
|
||||||
@@ -115,6 +175,14 @@ static const JsViewDescriptor view_descriptor = {
|
|||||||
.name = "maxLength",
|
.name = "maxLength",
|
||||||
.type = JsViewPropTypeNumber,
|
.type = JsViewPropTypeNumber,
|
||||||
.assign = (JsViewPropAssign)max_len_assign},
|
.assign = (JsViewPropAssign)max_len_assign},
|
||||||
|
(JsViewPropDescriptor){
|
||||||
|
.name = "defaultText",
|
||||||
|
.type = JsViewPropTypeString,
|
||||||
|
.assign = (JsViewPropAssign)default_text_assign},
|
||||||
|
(JsViewPropDescriptor){
|
||||||
|
.name = "defaultTextClear",
|
||||||
|
.type = JsViewPropTypeBool,
|
||||||
|
.assign = (JsViewPropAssign)default_text_clear_assign},
|
||||||
}};
|
}};
|
||||||
|
|
||||||
JS_GUI_VIEW_DEF(text_input, &view_descriptor);
|
JS_GUI_VIEW_DEF(text_input, &view_descriptor);
|
||||||
|
|||||||
@@ -308,7 +308,7 @@ void js_math_trunc(struct mjs* mjs) {
|
|||||||
static void* js_math_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) {
|
static void* js_math_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) {
|
||||||
UNUSED(modules);
|
UNUSED(modules);
|
||||||
mjs_val_t math_obj = mjs_mk_object(mjs);
|
mjs_val_t math_obj = mjs_mk_object(mjs);
|
||||||
mjs_set(mjs, math_obj, "is_equal", ~0, MJS_MK_FN(js_math_is_equal));
|
mjs_set(mjs, math_obj, "isEqual", ~0, MJS_MK_FN(js_math_is_equal));
|
||||||
mjs_set(mjs, math_obj, "abs", ~0, MJS_MK_FN(js_math_abs));
|
mjs_set(mjs, math_obj, "abs", ~0, MJS_MK_FN(js_math_abs));
|
||||||
mjs_set(mjs, math_obj, "acos", ~0, MJS_MK_FN(js_math_acos));
|
mjs_set(mjs, math_obj, "acos", ~0, MJS_MK_FN(js_math_acos));
|
||||||
mjs_set(mjs, math_obj, "acosh", ~0, MJS_MK_FN(js_math_acosh));
|
mjs_set(mjs, math_obj, "acosh", ~0, MJS_MK_FN(js_math_acosh));
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#include <core/common_defines.h>
|
#include <core/common_defines.h>
|
||||||
|
#include <expansion/expansion.h>
|
||||||
#include <furi_hal.h>
|
#include <furi_hal.h>
|
||||||
#include "../js_modules.h"
|
#include "../js_modules.h"
|
||||||
#include <m-array.h>
|
#include <m-array.h>
|
||||||
@@ -89,16 +90,51 @@ static void js_serial_setup(struct mjs* mjs) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
serial->rx_stream = furi_stream_buffer_alloc(RX_BUF_LEN, 1);
|
expansion_disable(furi_record_open(RECORD_EXPANSION));
|
||||||
|
furi_record_close(RECORD_EXPANSION);
|
||||||
|
|
||||||
serial->serial_handle = furi_hal_serial_control_acquire(serial_id);
|
serial->serial_handle = furi_hal_serial_control_acquire(serial_id);
|
||||||
if(serial->serial_handle) {
|
if(serial->serial_handle) {
|
||||||
|
serial->rx_stream = furi_stream_buffer_alloc(RX_BUF_LEN, 1);
|
||||||
furi_hal_serial_init(serial->serial_handle, baudrate);
|
furi_hal_serial_init(serial->serial_handle, baudrate);
|
||||||
furi_hal_serial_async_rx_start(
|
furi_hal_serial_async_rx_start(
|
||||||
serial->serial_handle, js_serial_on_async_rx, serial, false);
|
serial->serial_handle, js_serial_on_async_rx, serial, false);
|
||||||
serial->setup_done = true;
|
serial->setup_done = true;
|
||||||
|
} else {
|
||||||
|
expansion_enable(furi_record_open(RECORD_EXPANSION));
|
||||||
|
furi_record_close(RECORD_EXPANSION);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void js_serial_deinit(JsSerialInst* js_serial) {
|
||||||
|
if(js_serial->setup_done) {
|
||||||
|
furi_hal_serial_async_rx_stop(js_serial->serial_handle);
|
||||||
|
furi_hal_serial_deinit(js_serial->serial_handle);
|
||||||
|
furi_hal_serial_control_release(js_serial->serial_handle);
|
||||||
|
js_serial->serial_handle = NULL;
|
||||||
|
furi_stream_buffer_free(js_serial->rx_stream);
|
||||||
|
|
||||||
|
expansion_enable(furi_record_open(RECORD_EXPANSION));
|
||||||
|
furi_record_close(RECORD_EXPANSION);
|
||||||
|
|
||||||
|
js_serial->setup_done = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void js_serial_end(struct mjs* mjs) {
|
||||||
|
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0);
|
||||||
|
JsSerialInst* serial = mjs_get_ptr(mjs, obj_inst);
|
||||||
|
furi_assert(serial);
|
||||||
|
|
||||||
|
if(!serial->setup_done) {
|
||||||
|
mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Serial is not configured");
|
||||||
|
mjs_return(mjs, MJS_UNDEFINED);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
js_serial_deinit(serial);
|
||||||
|
}
|
||||||
|
|
||||||
static void js_serial_write(struct mjs* mjs) {
|
static void js_serial_write(struct mjs* mjs) {
|
||||||
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0);
|
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0);
|
||||||
JsSerialInst* serial = mjs_get_ptr(mjs, obj_inst);
|
JsSerialInst* serial = mjs_get_ptr(mjs, obj_inst);
|
||||||
@@ -346,6 +382,55 @@ static void js_serial_read_bytes(struct mjs* mjs) {
|
|||||||
free(read_buf);
|
free(read_buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static char* js_serial_receive_any(JsSerialInst* serial, size_t* len, uint32_t timeout) {
|
||||||
|
uint32_t flags = ThreadEventCustomDataRx;
|
||||||
|
if(furi_stream_buffer_is_empty(serial->rx_stream)) {
|
||||||
|
flags = js_flags_wait(serial->mjs, ThreadEventCustomDataRx, timeout);
|
||||||
|
}
|
||||||
|
if(flags & ThreadEventCustomDataRx) { // New data received
|
||||||
|
*len = furi_stream_buffer_bytes_available(serial->rx_stream);
|
||||||
|
if(!*len) return NULL;
|
||||||
|
char* buf = malloc(*len);
|
||||||
|
furi_stream_buffer_receive(serial->rx_stream, buf, *len, 0);
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void js_serial_read_any(struct mjs* mjs) {
|
||||||
|
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0);
|
||||||
|
JsSerialInst* serial = mjs_get_ptr(mjs, obj_inst);
|
||||||
|
furi_assert(serial);
|
||||||
|
if(!serial->setup_done) {
|
||||||
|
mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Serial is not configured");
|
||||||
|
mjs_return(mjs, MJS_UNDEFINED);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t timeout = FuriWaitForever;
|
||||||
|
|
||||||
|
do {
|
||||||
|
size_t num_args = mjs_nargs(mjs);
|
||||||
|
if(num_args == 1) {
|
||||||
|
mjs_val_t timeout_arg = mjs_arg(mjs, 0);
|
||||||
|
if(!mjs_is_number(timeout_arg)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
timeout = mjs_get_int32(mjs, timeout_arg);
|
||||||
|
}
|
||||||
|
} while(0);
|
||||||
|
|
||||||
|
size_t bytes_read = 0;
|
||||||
|
char* read_buf = js_serial_receive_any(serial, &bytes_read, timeout);
|
||||||
|
|
||||||
|
mjs_val_t return_obj = MJS_UNDEFINED;
|
||||||
|
if(bytes_read > 0 && read_buf) {
|
||||||
|
return_obj = mjs_mk_string(mjs, read_buf, bytes_read, true);
|
||||||
|
}
|
||||||
|
mjs_return(mjs, return_obj);
|
||||||
|
free(read_buf);
|
||||||
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
js_serial_expect_parse_string(struct mjs* mjs, mjs_val_t arg, PatternArray_t patterns) {
|
js_serial_expect_parse_string(struct mjs* mjs, mjs_val_t arg, PatternArray_t patterns) {
|
||||||
size_t str_len = 0;
|
size_t str_len = 0;
|
||||||
@@ -580,10 +665,12 @@ static void* js_serial_create(struct mjs* mjs, mjs_val_t* object, JsModules* mod
|
|||||||
mjs_val_t serial_obj = mjs_mk_object(mjs);
|
mjs_val_t serial_obj = mjs_mk_object(mjs);
|
||||||
mjs_set(mjs, serial_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, js_serial));
|
mjs_set(mjs, serial_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, js_serial));
|
||||||
mjs_set(mjs, serial_obj, "setup", ~0, MJS_MK_FN(js_serial_setup));
|
mjs_set(mjs, serial_obj, "setup", ~0, MJS_MK_FN(js_serial_setup));
|
||||||
|
mjs_set(mjs, serial_obj, "end", ~0, MJS_MK_FN(js_serial_end));
|
||||||
mjs_set(mjs, serial_obj, "write", ~0, MJS_MK_FN(js_serial_write));
|
mjs_set(mjs, serial_obj, "write", ~0, MJS_MK_FN(js_serial_write));
|
||||||
mjs_set(mjs, serial_obj, "read", ~0, MJS_MK_FN(js_serial_read));
|
mjs_set(mjs, serial_obj, "read", ~0, MJS_MK_FN(js_serial_read));
|
||||||
mjs_set(mjs, serial_obj, "readln", ~0, MJS_MK_FN(js_serial_readln));
|
mjs_set(mjs, serial_obj, "readln", ~0, MJS_MK_FN(js_serial_readln));
|
||||||
mjs_set(mjs, serial_obj, "readBytes", ~0, MJS_MK_FN(js_serial_read_bytes));
|
mjs_set(mjs, serial_obj, "readBytes", ~0, MJS_MK_FN(js_serial_read_bytes));
|
||||||
|
mjs_set(mjs, serial_obj, "readAny", ~0, MJS_MK_FN(js_serial_read_any));
|
||||||
mjs_set(mjs, serial_obj, "expect", ~0, MJS_MK_FN(js_serial_expect));
|
mjs_set(mjs, serial_obj, "expect", ~0, MJS_MK_FN(js_serial_expect));
|
||||||
*object = serial_obj;
|
*object = serial_obj;
|
||||||
|
|
||||||
@@ -592,14 +679,7 @@ static void* js_serial_create(struct mjs* mjs, mjs_val_t* object, JsModules* mod
|
|||||||
|
|
||||||
static void js_serial_destroy(void* inst) {
|
static void js_serial_destroy(void* inst) {
|
||||||
JsSerialInst* js_serial = inst;
|
JsSerialInst* js_serial = inst;
|
||||||
if(js_serial->setup_done) {
|
js_serial_deinit(js_serial);
|
||||||
furi_hal_serial_async_rx_stop(js_serial->serial_handle);
|
|
||||||
furi_hal_serial_deinit(js_serial->serial_handle);
|
|
||||||
furi_hal_serial_control_release(js_serial->serial_handle);
|
|
||||||
js_serial->serial_handle = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
furi_stream_buffer_free(js_serial->rx_stream);
|
|
||||||
free(js_serial);
|
free(js_serial);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
20
applications/system/js_app/packages/create-fz-app/README.md
Normal file
20
applications/system/js_app/packages/create-fz-app/README.md
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# Flipper Zero JavaScript SDK Wizard
|
||||||
|
This package contains an interactive wizard that lets you scaffold a JavaScript
|
||||||
|
application for Flipper Zero.
|
||||||
|
|
||||||
|
## Getting started
|
||||||
|
Create your application using the interactive wizard:
|
||||||
|
```shell
|
||||||
|
npx @flipperdevices/create-fz-app@latest
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, enter the directory with your application and launch it:
|
||||||
|
```shell
|
||||||
|
cd my-flip-app
|
||||||
|
npm start
|
||||||
|
```
|
||||||
|
|
||||||
|
You are free to use `pnpm` or `yarn` instead of `npm`.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
Check out the [JavaScript section in the Developer Documentation](https://developer.flipper.net/flipperzero/doxygen/js.html)
|
||||||
68
applications/system/js_app/packages/create-fz-app/index.js
Executable file
68
applications/system/js_app/packages/create-fz-app/index.js
Executable file
@@ -0,0 +1,68 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
import prompts from "prompts";
|
||||||
|
import fs from "node:fs";
|
||||||
|
import path from "node:path";
|
||||||
|
import { fileURLToPath } from "url";
|
||||||
|
import { spawnSync } from "node:child_process";
|
||||||
|
import { replaceInFileSync } from "replace-in-file";
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const { name, pkgManager, confirm } = await prompts([
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
name: "name",
|
||||||
|
message: "What is the name of your project?",
|
||||||
|
initial: "my-flip-app"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "select",
|
||||||
|
name: "pkgManager",
|
||||||
|
message: "What package manager should your project use?",
|
||||||
|
choices: [
|
||||||
|
{ title: "npm", value: "npm" },
|
||||||
|
{ title: "pnpm", value: "pnpm" },
|
||||||
|
{ title: "yarn", value: "yarn" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "confirm",
|
||||||
|
name: "confirm",
|
||||||
|
message: "Create project?",
|
||||||
|
initial: true,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!confirm)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (fs.existsSync(name)) {
|
||||||
|
const { replace } = await prompts([
|
||||||
|
{
|
||||||
|
type: "confirm",
|
||||||
|
name: "replace",
|
||||||
|
message: `File or directory \`${name}\` already exists. Continue anyway?`,
|
||||||
|
initial: false,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
if (!replace)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.rmSync(name, { recursive: true, force: true });
|
||||||
|
|
||||||
|
console.log("Copying files...");
|
||||||
|
fs.cpSync(path.resolve(__dirname, "template"), name, { recursive: true });
|
||||||
|
replaceInFileSync({ files: `${name}/**/*`, from: /<app_name>/g, to: name });
|
||||||
|
|
||||||
|
console.log("Installing packages...");
|
||||||
|
spawnSync("bash", ["-c", `cd ${name} && ${pkgManager} install`], {
|
||||||
|
cwd: process.cwd(),
|
||||||
|
detached: true,
|
||||||
|
stdio: "inherit",
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`Done! Created ${name}. Run \`cd ${name} && ${pkgManager} start\` to run it on your Flipper.`);
|
||||||
|
})();
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"name": "@flipperdevices/create-fz-app",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"description": "Template package for JS apps Flipper Zero",
|
||||||
|
"bin": "index.js",
|
||||||
|
"type": "module",
|
||||||
|
"keywords": [
|
||||||
|
"flipper",
|
||||||
|
"flipper zero"
|
||||||
|
],
|
||||||
|
"author": "Flipper Devices",
|
||||||
|
"license": "GPL-3.0-only",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/flipperdevices/flipperzero-firmware.git",
|
||||||
|
"directory": "applications/system/js_app/packages/create-fz-app"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"prompts": "^2.4.2",
|
||||||
|
"replace-in-file": "^8.2.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
373
applications/system/js_app/packages/create-fz-app/pnpm-lock.yaml
generated
Normal file
373
applications/system/js_app/packages/create-fz-app/pnpm-lock.yaml
generated
Normal file
@@ -0,0 +1,373 @@
|
|||||||
|
lockfileVersion: '9.0'
|
||||||
|
|
||||||
|
settings:
|
||||||
|
autoInstallPeers: true
|
||||||
|
excludeLinksFromLockfile: false
|
||||||
|
|
||||||
|
importers:
|
||||||
|
|
||||||
|
.:
|
||||||
|
dependencies:
|
||||||
|
prompts:
|
||||||
|
specifier: ^2.4.2
|
||||||
|
version: 2.4.2
|
||||||
|
replace-in-file:
|
||||||
|
specifier: ^8.2.0
|
||||||
|
version: 8.2.0
|
||||||
|
|
||||||
|
packages:
|
||||||
|
|
||||||
|
'@isaacs/cliui@8.0.2':
|
||||||
|
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
'@pkgjs/parseargs@0.11.0':
|
||||||
|
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
|
||||||
|
engines: {node: '>=14'}
|
||||||
|
|
||||||
|
ansi-regex@5.0.1:
|
||||||
|
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
ansi-regex@6.1.0:
|
||||||
|
resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
ansi-styles@4.3.0:
|
||||||
|
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
ansi-styles@6.2.1:
|
||||||
|
resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
balanced-match@1.0.2:
|
||||||
|
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
||||||
|
|
||||||
|
brace-expansion@2.0.1:
|
||||||
|
resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==}
|
||||||
|
|
||||||
|
chalk@5.3.0:
|
||||||
|
resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==}
|
||||||
|
engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}
|
||||||
|
|
||||||
|
cliui@8.0.1:
|
||||||
|
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
color-convert@2.0.1:
|
||||||
|
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
|
||||||
|
engines: {node: '>=7.0.0'}
|
||||||
|
|
||||||
|
color-name@1.1.4:
|
||||||
|
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
|
||||||
|
|
||||||
|
cross-spawn@7.0.3:
|
||||||
|
resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==}
|
||||||
|
engines: {node: '>= 8'}
|
||||||
|
|
||||||
|
eastasianwidth@0.2.0:
|
||||||
|
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
|
||||||
|
|
||||||
|
emoji-regex@8.0.0:
|
||||||
|
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
|
||||||
|
|
||||||
|
emoji-regex@9.2.2:
|
||||||
|
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
|
||||||
|
|
||||||
|
escalade@3.2.0:
|
||||||
|
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
|
foreground-child@3.3.0:
|
||||||
|
resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==}
|
||||||
|
engines: {node: '>=14'}
|
||||||
|
|
||||||
|
get-caller-file@2.0.5:
|
||||||
|
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
|
||||||
|
engines: {node: 6.* || 8.* || >= 10.*}
|
||||||
|
|
||||||
|
glob@10.4.5:
|
||||||
|
resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
is-fullwidth-code-point@3.0.0:
|
||||||
|
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
isexe@2.0.0:
|
||||||
|
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
|
||||||
|
|
||||||
|
jackspeak@3.4.3:
|
||||||
|
resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
|
||||||
|
|
||||||
|
kleur@3.0.3:
|
||||||
|
resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
|
lru-cache@10.4.3:
|
||||||
|
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
|
||||||
|
|
||||||
|
minimatch@9.0.5:
|
||||||
|
resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
|
||||||
|
engines: {node: '>=16 || 14 >=14.17'}
|
||||||
|
|
||||||
|
minipass@7.1.2:
|
||||||
|
resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
|
||||||
|
engines: {node: '>=16 || 14 >=14.17'}
|
||||||
|
|
||||||
|
package-json-from-dist@1.0.1:
|
||||||
|
resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
|
||||||
|
|
||||||
|
path-key@3.1.1:
|
||||||
|
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
path-scurry@1.11.1:
|
||||||
|
resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==}
|
||||||
|
engines: {node: '>=16 || 14 >=14.18'}
|
||||||
|
|
||||||
|
prompts@2.4.2:
|
||||||
|
resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==}
|
||||||
|
engines: {node: '>= 6'}
|
||||||
|
|
||||||
|
replace-in-file@8.2.0:
|
||||||
|
resolution: {integrity: sha512-hMsQtdYHwWviQT5ZbNsgfu0WuCiNlcUSnnD+aHAL081kbU9dPkPocDaHlDvAHKydTWWpx1apfcEcmvIyQk3CpQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
require-directory@2.1.1:
|
||||||
|
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
shebang-command@2.0.0:
|
||||||
|
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
shebang-regex@3.0.0:
|
||||||
|
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
signal-exit@4.1.0:
|
||||||
|
resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
|
||||||
|
engines: {node: '>=14'}
|
||||||
|
|
||||||
|
sisteransi@1.0.5:
|
||||||
|
resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==}
|
||||||
|
|
||||||
|
string-width@4.2.3:
|
||||||
|
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
string-width@5.1.2:
|
||||||
|
resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
strip-ansi@6.0.1:
|
||||||
|
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
strip-ansi@7.1.0:
|
||||||
|
resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
which@2.0.2:
|
||||||
|
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
|
||||||
|
engines: {node: '>= 8'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
wrap-ansi@7.0.0:
|
||||||
|
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
wrap-ansi@8.1.0:
|
||||||
|
resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
y18n@5.0.8:
|
||||||
|
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
yargs-parser@21.1.1:
|
||||||
|
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
yargs@17.7.2:
|
||||||
|
resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
snapshots:
|
||||||
|
|
||||||
|
'@isaacs/cliui@8.0.2':
|
||||||
|
dependencies:
|
||||||
|
string-width: 5.1.2
|
||||||
|
string-width-cjs: string-width@4.2.3
|
||||||
|
strip-ansi: 7.1.0
|
||||||
|
strip-ansi-cjs: strip-ansi@6.0.1
|
||||||
|
wrap-ansi: 8.1.0
|
||||||
|
wrap-ansi-cjs: wrap-ansi@7.0.0
|
||||||
|
|
||||||
|
'@pkgjs/parseargs@0.11.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
ansi-regex@5.0.1: {}
|
||||||
|
|
||||||
|
ansi-regex@6.1.0: {}
|
||||||
|
|
||||||
|
ansi-styles@4.3.0:
|
||||||
|
dependencies:
|
||||||
|
color-convert: 2.0.1
|
||||||
|
|
||||||
|
ansi-styles@6.2.1: {}
|
||||||
|
|
||||||
|
balanced-match@1.0.2: {}
|
||||||
|
|
||||||
|
brace-expansion@2.0.1:
|
||||||
|
dependencies:
|
||||||
|
balanced-match: 1.0.2
|
||||||
|
|
||||||
|
chalk@5.3.0: {}
|
||||||
|
|
||||||
|
cliui@8.0.1:
|
||||||
|
dependencies:
|
||||||
|
string-width: 4.2.3
|
||||||
|
strip-ansi: 6.0.1
|
||||||
|
wrap-ansi: 7.0.0
|
||||||
|
|
||||||
|
color-convert@2.0.1:
|
||||||
|
dependencies:
|
||||||
|
color-name: 1.1.4
|
||||||
|
|
||||||
|
color-name@1.1.4: {}
|
||||||
|
|
||||||
|
cross-spawn@7.0.3:
|
||||||
|
dependencies:
|
||||||
|
path-key: 3.1.1
|
||||||
|
shebang-command: 2.0.0
|
||||||
|
which: 2.0.2
|
||||||
|
|
||||||
|
eastasianwidth@0.2.0: {}
|
||||||
|
|
||||||
|
emoji-regex@8.0.0: {}
|
||||||
|
|
||||||
|
emoji-regex@9.2.2: {}
|
||||||
|
|
||||||
|
escalade@3.2.0: {}
|
||||||
|
|
||||||
|
foreground-child@3.3.0:
|
||||||
|
dependencies:
|
||||||
|
cross-spawn: 7.0.3
|
||||||
|
signal-exit: 4.1.0
|
||||||
|
|
||||||
|
get-caller-file@2.0.5: {}
|
||||||
|
|
||||||
|
glob@10.4.5:
|
||||||
|
dependencies:
|
||||||
|
foreground-child: 3.3.0
|
||||||
|
jackspeak: 3.4.3
|
||||||
|
minimatch: 9.0.5
|
||||||
|
minipass: 7.1.2
|
||||||
|
package-json-from-dist: 1.0.1
|
||||||
|
path-scurry: 1.11.1
|
||||||
|
|
||||||
|
is-fullwidth-code-point@3.0.0: {}
|
||||||
|
|
||||||
|
isexe@2.0.0: {}
|
||||||
|
|
||||||
|
jackspeak@3.4.3:
|
||||||
|
dependencies:
|
||||||
|
'@isaacs/cliui': 8.0.2
|
||||||
|
optionalDependencies:
|
||||||
|
'@pkgjs/parseargs': 0.11.0
|
||||||
|
|
||||||
|
kleur@3.0.3: {}
|
||||||
|
|
||||||
|
lru-cache@10.4.3: {}
|
||||||
|
|
||||||
|
minimatch@9.0.5:
|
||||||
|
dependencies:
|
||||||
|
brace-expansion: 2.0.1
|
||||||
|
|
||||||
|
minipass@7.1.2: {}
|
||||||
|
|
||||||
|
package-json-from-dist@1.0.1: {}
|
||||||
|
|
||||||
|
path-key@3.1.1: {}
|
||||||
|
|
||||||
|
path-scurry@1.11.1:
|
||||||
|
dependencies:
|
||||||
|
lru-cache: 10.4.3
|
||||||
|
minipass: 7.1.2
|
||||||
|
|
||||||
|
prompts@2.4.2:
|
||||||
|
dependencies:
|
||||||
|
kleur: 3.0.3
|
||||||
|
sisteransi: 1.0.5
|
||||||
|
|
||||||
|
replace-in-file@8.2.0:
|
||||||
|
dependencies:
|
||||||
|
chalk: 5.3.0
|
||||||
|
glob: 10.4.5
|
||||||
|
yargs: 17.7.2
|
||||||
|
|
||||||
|
require-directory@2.1.1: {}
|
||||||
|
|
||||||
|
shebang-command@2.0.0:
|
||||||
|
dependencies:
|
||||||
|
shebang-regex: 3.0.0
|
||||||
|
|
||||||
|
shebang-regex@3.0.0: {}
|
||||||
|
|
||||||
|
signal-exit@4.1.0: {}
|
||||||
|
|
||||||
|
sisteransi@1.0.5: {}
|
||||||
|
|
||||||
|
string-width@4.2.3:
|
||||||
|
dependencies:
|
||||||
|
emoji-regex: 8.0.0
|
||||||
|
is-fullwidth-code-point: 3.0.0
|
||||||
|
strip-ansi: 6.0.1
|
||||||
|
|
||||||
|
string-width@5.1.2:
|
||||||
|
dependencies:
|
||||||
|
eastasianwidth: 0.2.0
|
||||||
|
emoji-regex: 9.2.2
|
||||||
|
strip-ansi: 7.1.0
|
||||||
|
|
||||||
|
strip-ansi@6.0.1:
|
||||||
|
dependencies:
|
||||||
|
ansi-regex: 5.0.1
|
||||||
|
|
||||||
|
strip-ansi@7.1.0:
|
||||||
|
dependencies:
|
||||||
|
ansi-regex: 6.1.0
|
||||||
|
|
||||||
|
which@2.0.2:
|
||||||
|
dependencies:
|
||||||
|
isexe: 2.0.0
|
||||||
|
|
||||||
|
wrap-ansi@7.0.0:
|
||||||
|
dependencies:
|
||||||
|
ansi-styles: 4.3.0
|
||||||
|
string-width: 4.2.3
|
||||||
|
strip-ansi: 6.0.1
|
||||||
|
|
||||||
|
wrap-ansi@8.1.0:
|
||||||
|
dependencies:
|
||||||
|
ansi-styles: 6.2.1
|
||||||
|
string-width: 5.1.2
|
||||||
|
strip-ansi: 7.1.0
|
||||||
|
|
||||||
|
y18n@5.0.8: {}
|
||||||
|
|
||||||
|
yargs-parser@21.1.1: {}
|
||||||
|
|
||||||
|
yargs@17.7.2:
|
||||||
|
dependencies:
|
||||||
|
cliui: 8.0.1
|
||||||
|
escalade: 3.2.0
|
||||||
|
get-caller-file: 2.0.5
|
||||||
|
require-directory: 2.1.1
|
||||||
|
string-width: 4.2.3
|
||||||
|
y18n: 5.0.8
|
||||||
|
yargs-parser: 21.1.1
|
||||||
2
applications/system/js_app/packages/create-fz-app/template/.gitignore
vendored
Normal file
2
applications/system/js_app/packages/create-fz-app/template/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
/dist
|
||||||
|
node_modules/
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
build: {
|
||||||
|
// Where to put the compiled file
|
||||||
|
output: "dist/<app_name>.js",
|
||||||
|
|
||||||
|
// Whether to reduce the final file size at the cost of readability and
|
||||||
|
// clarity of error messages
|
||||||
|
minify: false,
|
||||||
|
|
||||||
|
// Set this to `false` if you've thoroughly read the documentation and
|
||||||
|
// are sure that you can use manual version checks to your advantage
|
||||||
|
enforceSdkVersion: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
upload: {
|
||||||
|
// Where to grab the file from. If you're not doing any extra processing
|
||||||
|
// after the SDK, this should match `build.output`
|
||||||
|
input: "dist/<app_name>.js",
|
||||||
|
|
||||||
|
// Where to put the file on the device
|
||||||
|
output: "/ext/apps/Scripts/<app_name>.js",
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
// import modules
|
||||||
|
// caution: `eventLoop` HAS to be imported before `gui`, and `gui` HAS to be
|
||||||
|
// imported before any `gui` submodules.
|
||||||
|
import * as eventLoop from "@flipperdevices/fz-sdk/event_loop";
|
||||||
|
import * as gui from "@flipperdevices/fz-sdk/gui";
|
||||||
|
import * as dialog from "@flipperdevices/fz-sdk/gui/dialog";
|
||||||
|
|
||||||
|
// a common pattern is to declare all the views that your app uses on one object
|
||||||
|
const views = {
|
||||||
|
dialog: dialog.makeWith({
|
||||||
|
header: "Hello from <app_name>",
|
||||||
|
text: "Check out index.ts and\nchange something :)",
|
||||||
|
center: "Gonna do that!",
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
// stop app on center button press
|
||||||
|
eventLoop.subscribe(views.dialog.input, (_sub, button, eventLoop) => {
|
||||||
|
if (button === "center")
|
||||||
|
eventLoop.stop();
|
||||||
|
}, eventLoop);
|
||||||
|
|
||||||
|
// stop app on back button press
|
||||||
|
eventLoop.subscribe(gui.viewDispatcher.navigation, (_sub, _item, eventLoop) => {
|
||||||
|
eventLoop.stop();
|
||||||
|
}, eventLoop);
|
||||||
|
|
||||||
|
// run app
|
||||||
|
gui.viewDispatcher.switchTo(views.dialog);
|
||||||
|
eventLoop.run();
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"name": "<app_name>",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc && node node_modules/@flipperdevices/fz-sdk/sdk.js build",
|
||||||
|
"start": "npm run build && node node_modules/@flipperdevices/fz-sdk/sdk.js upload"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@flipperdevices/fz-sdk": "^0.1",
|
||||||
|
"typescript": "^5.6.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "dist",
|
||||||
|
"checkJs": true,
|
||||||
|
"module": "CommonJS",
|
||||||
|
"noLib": true,
|
||||||
|
"target": "ES2015",
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"./node_modules/@flipperdevices/fz-sdk/global.d.ts",
|
||||||
|
],
|
||||||
|
"include": [
|
||||||
|
"./**/*.ts",
|
||||||
|
"./**/*.js"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"./node_modules/**/*",
|
||||||
|
"dist/**/*",
|
||||||
|
],
|
||||||
|
}
|
||||||
1
applications/system/js_app/packages/fz-sdk/.gitignore
vendored
Normal file
1
applications/system/js_app/packages/fz-sdk/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
docs/
|
||||||
31
applications/system/js_app/packages/fz-sdk/README.md
Normal file
31
applications/system/js_app/packages/fz-sdk/README.md
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# Flipper Zero JavaScript SDK
|
||||||
|
This package contains official tooling and typings for developing Flipper Zero
|
||||||
|
applications in JavaScript.
|
||||||
|
|
||||||
|
## Getting started
|
||||||
|
Create your application using the interactive wizard:
|
||||||
|
```shell
|
||||||
|
npx @flipperdevices/create-fz-app@latest
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, enter the directory with your application and launch it:
|
||||||
|
```shell
|
||||||
|
cd my-flip-app
|
||||||
|
npm start
|
||||||
|
```
|
||||||
|
|
||||||
|
You are free to use `pnpm` or `yarn` instead of `npm`.
|
||||||
|
|
||||||
|
## Versioning
|
||||||
|
For each version of this package, the major and minor components match those of
|
||||||
|
the Flipper Zero JS SDK version that that package version targets. This version
|
||||||
|
follows semver. For example, apps compiled with SDK version `0.1.0` will be
|
||||||
|
compatible with SDK versions `0.1`...`1.0` (not including `1.0`).
|
||||||
|
|
||||||
|
Every API has a version history reflected in its JSDoc comment. It is heavily
|
||||||
|
recommended to check SDK compatibility using a combination of
|
||||||
|
`sdkCompatibilityStatus`, `isSdkCompatible`, `assertSdkCompatibility` depending
|
||||||
|
on your use case.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
Check out the [JavaScript section in the Developer Documentation](https://developer.flipper.net/flipperzero/doxygen/js.html)
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
/**
|
/**
|
||||||
* @brief Special key codes that this module recognizes
|
* @brief Special key codes that this module recognizes
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
*/
|
*/
|
||||||
export type ModifierKey = "CTRL" | "SHIFT" | "ALT" | "GUI";
|
export type ModifierKey = "CTRL" | "SHIFT" | "ALT" | "GUI";
|
||||||
|
|
||||||
|
/** @version Added in JS SDK 0.1 */
|
||||||
export type MainKey =
|
export type MainKey =
|
||||||
"DOWN" | "LEFT" | "RIGHT" | "UP" |
|
"DOWN" | "LEFT" | "RIGHT" | "UP" |
|
||||||
|
|
||||||
@@ -14,6 +16,9 @@ export type MainKey =
|
|||||||
"F11" | "F12" | "F13" | "F14" | "F15" | "F16" | "F17" | "F18" | "F19" |
|
"F11" | "F12" | "F13" | "F14" | "F15" | "F16" | "F17" | "F18" | "F19" |
|
||||||
"F20" | "F21" | "F22" | "F23" | "F24" |
|
"F20" | "F21" | "F22" | "F23" | "F24" |
|
||||||
|
|
||||||
|
"NUM0" | "NUM1" | "NUM2" | "NUM3" | "NUM4" | "NUM5" | "NUM6" | "NUM7" |
|
||||||
|
"NUM8" | "NUM9" |
|
||||||
|
|
||||||
"\n" | " " | "!" | "\"" | "#" | "$" | "%" | "&" | "'" | "(" | ")" | "*" |
|
"\n" | " " | "!" | "\"" | "#" | "$" | "%" | "&" | "'" | "(" | ")" | "*" |
|
||||||
"+" | "," | "-" | "." | "/" | ":" | ";" | "<" | ">" | "=" | "?" | "@" | "[" |
|
"+" | "," | "-" | "." | "/" | ":" | ";" | "<" | ">" | "=" | "?" | "@" | "[" |
|
||||||
"]" | "\\" | "^" | "_" | "`" | "{" | "}" | "|" | "~" |
|
"]" | "\\" | "^" | "_" | "`" | "{" | "}" | "|" | "~" |
|
||||||
@@ -28,16 +33,19 @@ export type MainKey =
|
|||||||
"m" | "n" | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" |
|
"m" | "n" | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" |
|
||||||
"y" | "z";
|
"y" | "z";
|
||||||
|
|
||||||
|
/** @version Added in JS SDK 0.1 */
|
||||||
export type KeyCode = MainKey | ModifierKey | number;
|
export type KeyCode = MainKey | ModifierKey | number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Initializes the module
|
* @brief Initializes the module
|
||||||
* @param settings USB device settings. Omit to select default parameters
|
* @param settings USB device settings. Omit to select default parameters
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
*/
|
*/
|
||||||
export declare function setup(settings?: { vid: number, pid: number, mfrName?: string, prodName?: string }): void;
|
export declare function setup(settings?: { vid: number, pid: number, mfrName?: string, prodName?: string, layoutPath?: string }): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Tells whether the virtual USB HID device has successfully connected
|
* @brief Tells whether the virtual USB HID device has successfully connected
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
*/
|
*/
|
||||||
export declare function isConnected(): boolean;
|
export declare function isConnected(): boolean;
|
||||||
|
|
||||||
@@ -46,6 +54,7 @@ export declare function isConnected(): boolean;
|
|||||||
* @param keys The arguments represent a set of keys to. Out of that set, only
|
* @param keys The arguments represent a set of keys to. Out of that set, only
|
||||||
* one of the keys may represent a "main key" (see `MainKey`), with
|
* one of the keys may represent a "main key" (see `MainKey`), with
|
||||||
* the rest being modifier keys (see `ModifierKey`).
|
* the rest being modifier keys (see `ModifierKey`).
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
*/
|
*/
|
||||||
export declare function press(...keys: KeyCode[]): void;
|
export declare function press(...keys: KeyCode[]): void;
|
||||||
|
|
||||||
@@ -54,6 +63,7 @@ export declare function press(...keys: KeyCode[]): void;
|
|||||||
* @param keys The arguments represent a set of keys to. Out of that set, only
|
* @param keys The arguments represent a set of keys to. Out of that set, only
|
||||||
* one of the keys may represent a "main key" (see `MainKey`), with
|
* one of the keys may represent a "main key" (see `MainKey`), with
|
||||||
* the rest being modifier keys (see `ModifierKey`).
|
* the rest being modifier keys (see `ModifierKey`).
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
*/
|
*/
|
||||||
export declare function hold(...keys: KeyCode[]): void;
|
export declare function hold(...keys: KeyCode[]): void;
|
||||||
|
|
||||||
@@ -62,6 +72,7 @@ export declare function hold(...keys: KeyCode[]): void;
|
|||||||
* @param keys The arguments represent a set of keys to. Out of that set, only
|
* @param keys The arguments represent a set of keys to. Out of that set, only
|
||||||
* one of the keys may represent a "main key" (see `MainKey`), with
|
* one of the keys may represent a "main key" (see `MainKey`), with
|
||||||
* the rest being modifier keys (see `ModifierKey`).
|
* the rest being modifier keys (see `ModifierKey`).
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
*/
|
*/
|
||||||
export declare function release(...keys: KeyCode[]): void;
|
export declare function release(...keys: KeyCode[]): void;
|
||||||
|
|
||||||
@@ -69,6 +80,7 @@ export declare function release(...keys: KeyCode[]): void;
|
|||||||
* @brief Prints a string by repeatedly pressing and releasing keys
|
* @brief Prints a string by repeatedly pressing and releasing keys
|
||||||
* @param string The string to print
|
* @param string The string to print
|
||||||
* @param delay How many milliseconds to wait between key presses
|
* @param delay How many milliseconds to wait between key presses
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
*/
|
*/
|
||||||
export declare function print(string: string, delay?: number): void;
|
export declare function print(string: string, delay?: number): void;
|
||||||
|
|
||||||
@@ -77,5 +89,29 @@ export declare function print(string: string, delay?: number): void;
|
|||||||
* "Enter" after printing the string
|
* "Enter" after printing the string
|
||||||
* @param string The string to print
|
* @param string The string to print
|
||||||
* @param delay How many milliseconds to wait between key presses
|
* @param delay How many milliseconds to wait between key presses
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
*/
|
*/
|
||||||
export declare function println(string: string, delay?: number): void;
|
export declare function println(string: string, delay?: number): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Prints a string by Alt+Numpad method - works only on Windows!
|
||||||
|
* @param string The string to print
|
||||||
|
* @param delay How many milliseconds to wait between key presses
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
|
export declare function altPrint(string: string, delay?: number): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Prints a string by Alt+Numpad method - works only on Windows!
|
||||||
|
* Presses "Enter" after printing the string
|
||||||
|
* @param string The string to print
|
||||||
|
* @param delay How many milliseconds to wait between key presses
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
|
export declare function altPrintln(string: string, delay?: number): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Releases usb, optional, but allows to switch usb profile
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
|
export declare function quit(): void;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
# Welcome
|
||||||
182
applications/system/js_app/packages/fz-sdk/event_loop/index.d.ts
vendored
Normal file
182
applications/system/js_app/packages/fz-sdk/event_loop/index.d.ts
vendored
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
/**
|
||||||
|
* Module for dealing with events
|
||||||
|
*
|
||||||
|
* ```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`.
|
||||||
|
*
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
* @module
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
type Lit = undefined | null | {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscription control interface
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
|
export interface Subscription {
|
||||||
|
/**
|
||||||
|
* Cancels the subscription, preventing any future events managed by the
|
||||||
|
* subscription from firing
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
|
cancel(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opaque event source identifier
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
|
export type Contract<Item = undefined> = symbol & { "__tag__": "contract" };
|
||||||
|
// introducing a nominal type in a hacky way; the `__tag__` property doesn't really exist.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A callback can be assigned to an event loop to listen to an event. It may
|
||||||
|
* return an array with values that will be passed to it as arguments the next
|
||||||
|
* time that it is called. The first argument is always the subscription
|
||||||
|
* manager, and the second argument is always the item that trigged the event.
|
||||||
|
* The type of the item is defined by the event source.
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
|
export type Callback<Item, Args extends Lit[]> = (subscription: Subscription, item: Item, ...args: Args) => Args | undefined | void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscribes a callback to an event
|
||||||
|
* @param contract Event identifier
|
||||||
|
* @param callback Function to call when the event is triggered
|
||||||
|
* @param args Initial arguments passed to the callback
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
|
export function subscribe<Item, Args extends Lit[]>(contract: Contract<Item>, callback: Callback<Item, Args>, ...args: Args): Subscription;
|
||||||
|
/**
|
||||||
|
* Runs the event loop until it is stopped (potentially never)
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
|
export function run(): void | never;
|
||||||
|
/**
|
||||||
|
* Stops the event loop
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
|
export function stop(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a timer event that can be subscribed to just like any other event
|
||||||
|
* @param mode Either `"oneshot"` or `"periodic"`
|
||||||
|
* @param interval Timer interval in milliseconds
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
|
export function timer(mode: "oneshot" | "periodic", interval: number): Contract;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Message queue
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
|
export declare class Queue<T> {
|
||||||
|
/**
|
||||||
|
* Message event
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
|
input: Contract<T>;
|
||||||
|
/**
|
||||||
|
* Sends a message to the queue
|
||||||
|
* @param message message to send
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
|
send(message: T): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a message queue
|
||||||
|
* @param length maximum queue capacity
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
|
export function queue<T>(length: number): Queue<T>;
|
||||||
41
applications/system/js_app/packages/fz-sdk/flipper/index.d.ts
vendored
Normal file
41
applications/system/js_app/packages/fz-sdk/flipper/index.d.ts
vendored
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
/**
|
||||||
|
* Module for querying device properties
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
* @module
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the device model
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
|
export declare function getModel(): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the name of the virtual dolphin
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
|
export declare function getName(): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the battery charge percentage
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
|
export declare function getBatteryCharge(): number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @warning Do **NOT** use this to check the presence or absence of features. If
|
||||||
|
* you do, I'm gonna be sad :( Instead, refer to `checkSdkFeatures` and
|
||||||
|
* other similar mechanisms.
|
||||||
|
* @note Original firmware reports `"flipperdevices"`.
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
|
export declare const firmwareVendor: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @warning Do **NOT** use this to check the presence or absence of features. If
|
||||||
|
* you do, I'm gonna be sad :( Instead, refer to
|
||||||
|
* `checkSdkCompatibility` and other similar mechanisms.
|
||||||
|
* @note You're looking at JS SDK 0.1
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
|
export declare const jsSdkVersion: [number, number];
|
||||||
402
applications/system/js_app/packages/fz-sdk/global.d.ts
vendored
Normal file
402
applications/system/js_app/packages/fz-sdk/global.d.ts
vendored
Normal file
@@ -0,0 +1,402 @@
|
|||||||
|
/**
|
||||||
|
* Things from this module are automatically available to you without having to
|
||||||
|
* explicitly import anything.
|
||||||
|
*
|
||||||
|
* # SDK versioning and features
|
||||||
|
*
|
||||||
|
* ## Motivation
|
||||||
|
* It is very important that you check that features are implemented before you
|
||||||
|
* use them. By adding the necessary checks, you ensure that your users get a
|
||||||
|
* clear warning instead of a cryptic error message when running the script.
|
||||||
|
*
|
||||||
|
* This system has been designed in collaboration with our community in order to
|
||||||
|
* make things better for everybody involved. You can find out more in this
|
||||||
|
* discussion: https://github.com/flipperdevices/flipperzero-firmware/pull/3961
|
||||||
|
*
|
||||||
|
* ## Community agreement
|
||||||
|
* Each interpreter implementation (aka "JS SDK", aka "JS API"), including
|
||||||
|
* those found in third-party firmware distributions, defines two markers for
|
||||||
|
* signaling what it supports: the **SDK version** and the
|
||||||
|
* **extra feature set**.
|
||||||
|
*
|
||||||
|
* The **SDK version** consists of two semver-like integer components: the major
|
||||||
|
* version and the minor version. Like semver, the major version is bumped when
|
||||||
|
* a breaking change is introduced (i.e. one that would require correction of
|
||||||
|
* apps by their developers), and the minor version is bumped when a new
|
||||||
|
* non-breaking feature is introduced. Because we have adopted TypeScript,
|
||||||
|
* the https://www.semver-ts.org/ standard is used to determine whether a change
|
||||||
|
* is breaking or not. The basis of `semver-ts` is the "no new red squiggles"
|
||||||
|
* rule.
|
||||||
|
*
|
||||||
|
* Every major version is associated with a set of **extra features** that are
|
||||||
|
* present in some firmware distributions but not others. Distributions may
|
||||||
|
* cross-port features between each other, until at some point they get ported
|
||||||
|
* into the upstream firmware distribution. With the next major release of the
|
||||||
|
* JS SDK, all extra features present in the upstream distribution are now
|
||||||
|
* declared **baseline features**, and thus no longer recognized as "extra
|
||||||
|
* features".
|
||||||
|
*
|
||||||
|
* Before using a feature, you must check that the interpreter that you're
|
||||||
|
* running on actually supports it. If you don't, the portability of your
|
||||||
|
* application will suffer.
|
||||||
|
*
|
||||||
|
* ## Implementation
|
||||||
|
* Use the following functions to check version compatibility:
|
||||||
|
* - `checkSdkCompatibility` when your script absolutely cannot function on an
|
||||||
|
* incompatible interpreter
|
||||||
|
* - `isSdkCompatible` when your script can leverage multiple interpreter
|
||||||
|
* editions to its advantage
|
||||||
|
* - `sdkCompatibilityStatus` when you need a detailed status on compatibility
|
||||||
|
*
|
||||||
|
* Use the following functions to check feature compatibility:
|
||||||
|
* - `checkSdkFeatures` when your script absolutely cannot function on an
|
||||||
|
* incompatible interpreter
|
||||||
|
* - `doesSdkSupport` when your script can leverage multiple interpreter
|
||||||
|
* editions to its advantage
|
||||||
|
*
|
||||||
|
* ## Automatic version enforcement
|
||||||
|
* The SDK will automatically insert a call to `checkSdkCompatibility` in the
|
||||||
|
* beginning of the resulting script. If you would like to disable this check
|
||||||
|
* and instead use other manual compatibility checking facilities, edit your
|
||||||
|
* `fz-sdk.config.json5`.
|
||||||
|
*
|
||||||
|
* # Standard library
|
||||||
|
* Standard library features are mostly unimplemented. This module defines,
|
||||||
|
* among other things, the features that _are_ implemented.
|
||||||
|
*
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
* @module
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Checks compatibility between the script and the JS SDK that the
|
||||||
|
* firmware provides
|
||||||
|
*
|
||||||
|
* @note You're looking at JS SDK v0.1
|
||||||
|
*
|
||||||
|
* @param expectedMajor JS SDK major version expected by the script
|
||||||
|
* @param expectedMinor JS SDK minor version expected by the script
|
||||||
|
* @returns Compatibility status:
|
||||||
|
* - `"compatible"` if the script and the JS SDK are compatible
|
||||||
|
* - `"firmwareTooOld"` if the expected major version is larger than the
|
||||||
|
* version of the firmware, or if the expected minor version is larger than
|
||||||
|
* the version of the firmware
|
||||||
|
* - `"firmwareTooNew"` if the expected major version is lower than the
|
||||||
|
* version of the firmware
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
|
declare function sdkCompatibilityStatus(expectedMajor: number, expectedMinor: number):
|
||||||
|
"compatible" | "firmwareTooOld" | "firmwareTooNew";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Checks compatibility between the script and the JS SDK that the
|
||||||
|
* firmware provides in a boolean fashion
|
||||||
|
*
|
||||||
|
* @note You're looking at JS SDK v0.1
|
||||||
|
*
|
||||||
|
* @param expectedMajor JS SDK major version expected by the script
|
||||||
|
* @param expectedMinor JS SDK minor version expected by the script
|
||||||
|
* @returns `true` if the two are compatible, `false` otherwise
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
|
declare function isSdkCompatible(expectedMajor: number, expectedMinor: number): boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Asks the user whether to continue executing the script if the versions
|
||||||
|
* are not compatible. Does nothing if they are.
|
||||||
|
*
|
||||||
|
* @note You're looking at JS SDK v0.1
|
||||||
|
*
|
||||||
|
* @param expectedMajor JS SDK major version expected by the script
|
||||||
|
* @param expectedMinor JS SDK minor version expected by the script
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
|
declare function checkSdkCompatibility(expectedMajor: number, expectedMinor: number): void | never;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Checks whether all of the specified extra features are supported by
|
||||||
|
* the interpreter.
|
||||||
|
* @warning This function will return `false` if a queried feature is now
|
||||||
|
* recognized as a baseline feature. For more info, consult the module
|
||||||
|
* documentation.
|
||||||
|
* @param features Array of named features to query
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
|
declare function doesSdkSupport(features: string[]): boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Checks whether all of the specified extra features are supported by
|
||||||
|
* the interpreter, asking the user if they want to continue running the
|
||||||
|
* script if they're not.
|
||||||
|
* @warning This function will act as if the feature is not implemented for
|
||||||
|
* features that are now recognized as baseline features. For more
|
||||||
|
* info, consult the module documentation.
|
||||||
|
* @param features Array of named features to query
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
|
declare function checkSdkFeatures(features: string[]): void | never;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Pauses JavaScript execution for a while
|
||||||
|
* @param ms How many milliseconds to pause the execution for
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
|
declare function delay(ms: number): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Prints to the GUI console view
|
||||||
|
* @param args The arguments are converted to strings, concatenated without any
|
||||||
|
* spaces in between and printed to the console view
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
|
declare function print(...args: any[]): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Converts a string to a number
|
||||||
|
* @param text The string to convert to a number
|
||||||
|
* @param base Integer base (`2`...`16`), default: 10
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
|
declare function parseInt(text: string, base?: number): number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Path to the directory containing the current script
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
|
declare const __dirname: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Path to the current script file
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
|
declare const __filename: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Runs a JS file and returns value from it
|
||||||
|
*
|
||||||
|
* Reads a file at the specified path and runs it as JS, returning the last evaluated value.
|
||||||
|
*
|
||||||
|
* The result is cached and this filepath will not re-evaluated on future
|
||||||
|
* load() calls for this session.
|
||||||
|
*
|
||||||
|
* @param path The path to the file
|
||||||
|
* @param scope An object to use as global scope while running this file
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
|
declare function load(path: string, scope?: object): any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Return 1-byte string whose ASCII code is the integer `n`
|
||||||
|
*
|
||||||
|
* If `n` is not numeric or outside of `0-255` range, `null` is returned
|
||||||
|
*
|
||||||
|
* @param n The ASCII code to convert to string
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
|
declare function chr(n: number): string | null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Loads a natively implemented module
|
||||||
|
* @param module The name of the module to load
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
|
declare function require(module: string): any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief mJS Foreign Pointer type
|
||||||
|
*
|
||||||
|
* JavaScript code cannot do anything with values of `RawPointer` type except
|
||||||
|
* acquire them from native code and pass them right back to other parts of
|
||||||
|
* native code. These values cannot be turned into something meaningful, nor can
|
||||||
|
* be they modified.
|
||||||
|
*
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
|
declare type RawPointer = symbol & { "__tag__": "raw_ptr" };
|
||||||
|
// introducing a nominal type in a hacky way; the `__tag__` property doesn't really exist.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Holds raw bytes
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
|
declare class ArrayBuffer {
|
||||||
|
/**
|
||||||
|
* @brief The pointer to the byte buffer
|
||||||
|
* @note Like other `RawPointer` values, this value is essentially useless
|
||||||
|
* to JS code.
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
|
getPtr: RawPointer;
|
||||||
|
/**
|
||||||
|
* @brief The length of the buffer in bytes
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
|
byteLength: number;
|
||||||
|
/**
|
||||||
|
* @brief Creates an `ArrayBuffer` that contains a sub-part of the buffer
|
||||||
|
* @param start The index of the byte in the source buffer to be used as the
|
||||||
|
* start for the new buffer
|
||||||
|
* @param end The index of the byte in the source buffer that follows the
|
||||||
|
* byte to be used as the last byte for the new buffer
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
|
slice(start: number, end?: number): ArrayBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare function ArrayBuffer(): ArrayBuffer;
|
||||||
|
|
||||||
|
declare type ElementType = "u8" | "i8" | "u16" | "i16" | "u32" | "i32";
|
||||||
|
|
||||||
|
declare class TypedArray<E extends ElementType> {
|
||||||
|
/**
|
||||||
|
* @brief The length of the buffer in bytes
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
|
byteLength: number;
|
||||||
|
/**
|
||||||
|
* @brief The length of the buffer in typed elements
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
|
length: number;
|
||||||
|
/**
|
||||||
|
* @brief The underlying `ArrayBuffer`
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
|
buffer: ArrayBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare class Uint8Array extends TypedArray<"u8"> { }
|
||||||
|
declare class Int8Array extends TypedArray<"i8"> { }
|
||||||
|
declare class Uint16Array extends TypedArray<"u16"> { }
|
||||||
|
declare class Int16Array extends TypedArray<"i16"> { }
|
||||||
|
declare class Uint32Array extends TypedArray<"u32"> { }
|
||||||
|
declare class Int32Array extends TypedArray<"i32"> { }
|
||||||
|
|
||||||
|
declare function Uint8Array(data: ArrayBuffer | number | number[]): Uint8Array;
|
||||||
|
declare function Int8Array(data: ArrayBuffer | number | number[]): Int8Array;
|
||||||
|
declare function Uint16Array(data: ArrayBuffer | number | number[]): Uint16Array;
|
||||||
|
declare function Int16Array(data: ArrayBuffer | number | number[]): Int16Array;
|
||||||
|
declare function Uint32Array(data: ArrayBuffer | number | number[]): Uint32Array;
|
||||||
|
declare function Int32Array(data: ArrayBuffer | number | number[]): Int32Array;
|
||||||
|
|
||||||
|
declare const console: {
|
||||||
|
/**
|
||||||
|
* @brief Prints to the UART logs at the `[I]` level
|
||||||
|
* @param args The arguments are converted to strings, concatenated without any
|
||||||
|
* spaces in between and printed to the logs
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
|
log(...args: any[]): void;
|
||||||
|
/**
|
||||||
|
* @brief Prints to the UART logs at the `[D]` level
|
||||||
|
* @param args The arguments are converted to strings, concatenated without any
|
||||||
|
* spaces in between and printed to the logs
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
|
debug(...args: any[]): void;
|
||||||
|
/**
|
||||||
|
* @brief Prints to the UART logs at the `[W]` level
|
||||||
|
* @param args The arguments are converted to strings, concatenated without any
|
||||||
|
* spaces in between and printed to the logs
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
|
warn(...args: any[]): void;
|
||||||
|
/**
|
||||||
|
* @brief Prints to the UART logs at the `[E]` level
|
||||||
|
* @param args The arguments are converted to strings, concatenated without any
|
||||||
|
* spaces in between and printed to the logs
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
|
error(...args: any[]): void;
|
||||||
|
};
|
||||||
|
|
||||||
|
declare class Array<T> {
|
||||||
|
/**
|
||||||
|
* @brief Takes items out of the array
|
||||||
|
*
|
||||||
|
* Removes elements from the array and returns them in a new array
|
||||||
|
*
|
||||||
|
* @param start The index to start taking elements from
|
||||||
|
* @param deleteCount How many elements to take
|
||||||
|
* @returns The elements that were taken out of the original array as a new
|
||||||
|
* array
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
|
splice(start: number, deleteCount: number): T[];
|
||||||
|
/**
|
||||||
|
* @brief Adds a value to the end of the array
|
||||||
|
* @param value The value to add
|
||||||
|
* @returns New length of the array
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
|
push(value: T): number;
|
||||||
|
/**
|
||||||
|
* @brief How many elements there are in the array
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
|
length: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare class String {
|
||||||
|
/**
|
||||||
|
* @brief How many characters there are in the string
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
|
length: number;
|
||||||
|
/**
|
||||||
|
* @brief Returns the character code at an index in the string
|
||||||
|
* @param index The index to consult
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
|
charCodeAt(index: number): number;
|
||||||
|
/**
|
||||||
|
* See `charCodeAt`
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
|
at(index: number): number;
|
||||||
|
/**
|
||||||
|
* @brief Return index of first occurrence of substr within the string or `-1` if not found
|
||||||
|
* @param substr The string to search for
|
||||||
|
* @param fromIndex The index to start searching from
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
|
indexOf(substr: string, fromIndex?: number): number;
|
||||||
|
/**
|
||||||
|
* @brief Return a substring between two indices
|
||||||
|
* @param start The index to start substring at
|
||||||
|
* @param end The index to end substring at
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
|
slice(start: number, end?: number): string;
|
||||||
|
/**
|
||||||
|
* @brief Return this string transformed to upper case
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
|
toUpperCase(): string;
|
||||||
|
/**
|
||||||
|
* @brief Return this string transformed to lower case
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
|
toLowerCase(): string;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare class Boolean { }
|
||||||
|
|
||||||
|
declare class Function { }
|
||||||
|
|
||||||
|
declare class Number {
|
||||||
|
/**
|
||||||
|
* @brief Converts this number to a string
|
||||||
|
* @param base Integer base (`2`...`16`), default: 10
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
|
toString(base?: number): string;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare class Object { }
|
||||||
|
|
||||||
|
declare class RegExp { }
|
||||||
|
|
||||||
|
declare interface IArguments { }
|
||||||
|
|
||||||
|
declare type Partial<O extends object> = { [K in keyof O]?: O[K] };
|
||||||
@@ -1,5 +1,37 @@
|
|||||||
|
/**
|
||||||
|
* Module for accessing the GPIO (General Purpose Input/Output) ports
|
||||||
|
*
|
||||||
|
* ```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);
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
* @module
|
||||||
|
*/
|
||||||
|
|
||||||
import type { Contract } from "../event_loop";
|
import type { Contract } from "../event_loop";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
export interface Mode {
|
export interface Mode {
|
||||||
direction: "in" | "out";
|
direction: "in" | "out";
|
||||||
outMode?: "push_pull" | "open_drain";
|
outMode?: "push_pull" | "open_drain";
|
||||||
@@ -8,31 +40,39 @@ export interface Mode {
|
|||||||
pull?: "up" | "down";
|
pull?: "up" | "down";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
export interface Pin {
|
export interface Pin {
|
||||||
/**
|
/**
|
||||||
* Configures a pin. This may be done several times.
|
* Configures a pin. This may be done several times.
|
||||||
* @param mode Pin configuration object
|
* @param mode Pin configuration object
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
*/
|
*/
|
||||||
init(mode: Mode): void;
|
init(mode: Mode): void;
|
||||||
/**
|
/**
|
||||||
* Sets the output value of a pin if it's been configured with
|
* Sets the output value of a pin if it's been configured with
|
||||||
* `direction: "out"`.
|
* `direction: "out"`.
|
||||||
* @param value Logic value to output
|
* @param value Logic value to output
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
*/
|
*/
|
||||||
write(value: boolean): void;
|
write(value: boolean): void;
|
||||||
/**
|
/**
|
||||||
* Gets the input value of a pin if it's been configured with
|
* Gets the input value of a pin if it's been configured with
|
||||||
* `direction: "in"`, but not `inMode: "analog"`.
|
* `direction: "in"`, but not `inMode: "analog"`.
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
*/
|
*/
|
||||||
read(): boolean;
|
read(): boolean;
|
||||||
/**
|
/**
|
||||||
* Gets the input voltage of a pin in millivolts if it's been configured
|
* Gets the input voltage of a pin in millivolts if it's been configured
|
||||||
* with `direction: "in"` and `inMode: "analog"`
|
* with `direction: "in"` and `inMode: "analog"`
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
*/
|
*/
|
||||||
read_analog(): number;
|
readAnalog(): number;
|
||||||
/**
|
/**
|
||||||
* Returns an `event_loop` event that can be used to listen to interrupts,
|
* Returns an `event_loop` event that can be used to listen to interrupts,
|
||||||
* as configured by `init`
|
* as configured by `init`
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
*/
|
*/
|
||||||
interrupt(): Contract;
|
interrupt(): Contract;
|
||||||
}
|
}
|
||||||
@@ -41,5 +81,6 @@ export interface Pin {
|
|||||||
* Returns an object that can be used to manage a GPIO pin. For the list of
|
* Returns an object that can be used to manage a GPIO pin. For the list of
|
||||||
* available pins, see https://docs.flipper.net/gpio-and-modules#miFsS
|
* available pins, see https://docs.flipper.net/gpio-and-modules#miFsS
|
||||||
* @param pin Pin name (e.g. `"PC3"`) or number (e.g. `7`)
|
* @param pin Pin name (e.g. `"PC3"`) or number (e.g. `7`)
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
*/
|
*/
|
||||||
export function get(pin: string | number): Pin;
|
export function get(pin: string | number): Pin;
|
||||||
41
applications/system/js_app/packages/fz-sdk/gui/byte_input.d.ts
vendored
Normal file
41
applications/system/js_app/packages/fz-sdk/gui/byte_input.d.ts
vendored
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
/**
|
||||||
|
* Displays a byte input keyboard.
|
||||||
|
*
|
||||||
|
* <img src="../images/byte_input.png" width="200" alt="Sample screenshot of the view" />
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* let eventLoop = require("event_loop");
|
||||||
|
* let gui = require("gui");
|
||||||
|
* let byteInputView = require("gui/byte_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
|
||||||
|
* - `header`: Text displayed at the top of the screen
|
||||||
|
* - `length`: Length of data to edit
|
||||||
|
* - `defaultData`: Data to show by default
|
||||||
|
*
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
* @module
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { View, ViewFactory } from ".";
|
||||||
|
import type { Contract } from "../event_loop";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
header: string,
|
||||||
|
length: number,
|
||||||
|
defaultData: Uint8Array | ArrayBuffer,
|
||||||
|
}
|
||||||
|
declare class ByteInput extends View<Props> {
|
||||||
|
input: Contract<string>;
|
||||||
|
}
|
||||||
|
declare class ByteInputFactory extends ViewFactory<Props, ByteInput> { }
|
||||||
|
declare const factory: ByteInputFactory;
|
||||||
|
export = factory;
|
||||||
45
applications/system/js_app/packages/fz-sdk/gui/dialog.d.ts
vendored
Normal file
45
applications/system/js_app/packages/fz-sdk/gui/dialog.d.ts
vendored
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
/**
|
||||||
|
* Displays a dialog with up to three options.
|
||||||
|
*
|
||||||
|
* <img src="../images/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 displayed in bold at the top of the screen
|
||||||
|
* - `text`: Text displayed in the middle of the string
|
||||||
|
* - `left`: Text for the left button
|
||||||
|
* - `center`: Text for the center button
|
||||||
|
* - `right`: Text for the right button
|
||||||
|
*
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
* @module
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { View, ViewFactory } from ".";
|
||||||
|
import type { Contract } from "../event_loop";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
header: string,
|
||||||
|
text: string,
|
||||||
|
left: string,
|
||||||
|
center: string,
|
||||||
|
right: string,
|
||||||
|
}
|
||||||
|
declare class Dialog extends View<Props> {
|
||||||
|
input: Contract<"left" | "center" | "right">;
|
||||||
|
}
|
||||||
|
declare class DialogFactory extends ViewFactory<Props, Dialog> { }
|
||||||
|
declare const factory: DialogFactory;
|
||||||
|
export = factory;
|
||||||
32
applications/system/js_app/packages/fz-sdk/gui/empty_screen.d.ts
vendored
Normal file
32
applications/system/js_app/packages/fz-sdk/gui/empty_screen.d.ts
vendored
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
/**
|
||||||
|
* Displays nothing.
|
||||||
|
*
|
||||||
|
* <img src="../images/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.
|
||||||
|
*
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
* @module
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { View, ViewFactory } from ".";
|
||||||
|
|
||||||
|
type Props = {};
|
||||||
|
declare class EmptyScreen extends View<Props> { }
|
||||||
|
declare class EmptyScreenFactory extends ViewFactory<Props, EmptyScreen> { }
|
||||||
|
declare const factory: EmptyScreenFactory;
|
||||||
|
export = factory;
|
||||||
7
applications/system/js_app/packages/fz-sdk/gui/file_picker.d.ts
vendored
Normal file
7
applications/system/js_app/packages/fz-sdk/gui/file_picker.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* @brief Displays a file picker and returns the selected file, or undefined if cancelled
|
||||||
|
* @param basePath The path to start at
|
||||||
|
* @param extension The file extension(s) to show (like `.sub`, `.iso|.img`, `*`)
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
|
export declare function pickFile(basePath: string, extension: string): string | undefined;
|
||||||
191
applications/system/js_app/packages/fz-sdk/gui/index.d.ts
vendored
Normal file
191
applications/system/js_app/packages/fz-sdk/gui/index.d.ts
vendored
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
/**
|
||||||
|
* ```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();
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
* @module
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { Contract } from "../event_loop";
|
||||||
|
|
||||||
|
type Properties = { [K: string]: any };
|
||||||
|
|
||||||
|
export declare class View<Props extends Properties> {
|
||||||
|
/**
|
||||||
|
* Assign value to property by name
|
||||||
|
* @param property Name of the property
|
||||||
|
* @param value Value to assign
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
|
set<P extends keyof Props>(property: P, value: Props[P]): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export declare class ViewFactory<Props extends Properties, V extends View<Props>> {
|
||||||
|
/**
|
||||||
|
* Create view instance with default values, can be changed later with set()
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
|
make(): V;
|
||||||
|
/**
|
||||||
|
* Create view instance with custom values, can be changed later with set()
|
||||||
|
* @param initial Dictionary of property names to values
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
|
makeWith(initial: Partial<Props>): V;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
|
declare class ViewDispatcher {
|
||||||
|
/**
|
||||||
|
* Event source for `sendCustom` events
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
|
custom: Contract<number>;
|
||||||
|
/**
|
||||||
|
* Event source for navigation events (back key presses)
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
|
navigation: Contract;
|
||||||
|
/**
|
||||||
|
* View object currently shown
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
|
currentView: View<any>;
|
||||||
|
/**
|
||||||
|
* Sends a number to the custom event handler
|
||||||
|
* @param event number to send
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
|
sendCustom(event: number): void;
|
||||||
|
/**
|
||||||
|
* Switches to a view
|
||||||
|
* @param assoc View-ViewDispatcher association as returned by `add`
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
|
switchTo(assoc: View<any>): void;
|
||||||
|
/**
|
||||||
|
* Sends this ViewDispatcher to the front or back, above or below all other
|
||||||
|
* GUI viewports
|
||||||
|
* @param direction Either `"front"` or `"back"`
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
|
sendTo(direction: "front" | "back"): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
|
export const viewDispatcher: ViewDispatcher;
|
||||||
33
applications/system/js_app/packages/fz-sdk/gui/loading.d.ts
vendored
Normal file
33
applications/system/js_app/packages/fz-sdk/gui/loading.d.ts
vendored
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
/**
|
||||||
|
* 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="../images/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.
|
||||||
|
*
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
* @module
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { View, ViewFactory } from ".";
|
||||||
|
|
||||||
|
type Props = {};
|
||||||
|
declare class Loading extends View<Props> { }
|
||||||
|
declare class LoadingFactory extends ViewFactory<Props, Loading> { }
|
||||||
|
declare const factory: LoadingFactory;
|
||||||
|
export = factory;
|
||||||
39
applications/system/js_app/packages/fz-sdk/gui/submenu.d.ts
vendored
Normal file
39
applications/system/js_app/packages/fz-sdk/gui/submenu.d.ts
vendored
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
/**
|
||||||
|
* Displays a scrollable list of clickable textual entries.
|
||||||
|
*
|
||||||
|
* <img src="../images/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`: Text displayed at the top of the screen in bold
|
||||||
|
* - `items`: Array of selectable textual items
|
||||||
|
*
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
* @module
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { View, ViewFactory } from ".";
|
||||||
|
import type { Contract } from "../event_loop";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
header: string,
|
||||||
|
items: string[],
|
||||||
|
};
|
||||||
|
declare class Submenu extends View<Props> {
|
||||||
|
chosen: Contract<number>;
|
||||||
|
}
|
||||||
|
declare class SubmenuFactory extends ViewFactory<Props, Submenu> { }
|
||||||
|
declare const factory: SubmenuFactory;
|
||||||
|
export = factory;
|
||||||
41
applications/system/js_app/packages/fz-sdk/gui/text_box.d.ts
vendored
Normal file
41
applications/system/js_app/packages/fz-sdk/gui/text_box.d.ts
vendored
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
/**
|
||||||
|
* 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 in the text box
|
||||||
|
* - `font`: The font to display the text in (`"text"` or `"hex"`)
|
||||||
|
* - `focus`: The initial focus of the text box (`"start"` or `"end"`)
|
||||||
|
*
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
* @module
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { View, ViewFactory } from ".";
|
||||||
|
import type { Contract } from "../event_loop";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
text: string,
|
||||||
|
font: "text" | "hex",
|
||||||
|
focus: "start" | "end",
|
||||||
|
}
|
||||||
|
declare class TextBox extends View<Props> {
|
||||||
|
chosen: Contract<number>;
|
||||||
|
}
|
||||||
|
declare class TextBoxFactory extends ViewFactory<Props, TextBox> { }
|
||||||
|
declare const factory: TextBoxFactory;
|
||||||
|
export = factory;
|
||||||
45
applications/system/js_app/packages/fz-sdk/gui/text_input.d.ts
vendored
Normal file
45
applications/system/js_app/packages/fz-sdk/gui/text_input.d.ts
vendored
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
/**
|
||||||
|
* Displays a text input keyboard.
|
||||||
|
*
|
||||||
|
* <img src="../images/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
|
||||||
|
* - `header`: Text displayed at the top of the screen
|
||||||
|
* - `minLength`: Minimum allowed text length
|
||||||
|
* - `maxLength`: Maximum allowed text length
|
||||||
|
* - `defaultText`: Text to show by default
|
||||||
|
* - `defaultTextClear`: Whether to clear the default text on next character typed
|
||||||
|
*
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
* @module
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { View, ViewFactory } from ".";
|
||||||
|
import type { Contract } from "../event_loop";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
header: string,
|
||||||
|
minLength: number,
|
||||||
|
maxLength: number,
|
||||||
|
defaultText: string,
|
||||||
|
defaultTextClear: boolean,
|
||||||
|
}
|
||||||
|
declare class TextInput extends View<Props> {
|
||||||
|
input: Contract<string>;
|
||||||
|
}
|
||||||
|
declare class TextInputFactory extends ViewFactory<Props, TextInput> { }
|
||||||
|
declare const factory: TextInputFactory;
|
||||||
|
export = factory;
|
||||||
60
applications/system/js_app/packages/fz-sdk/math/index.d.ts
vendored
Normal file
60
applications/system/js_app/packages/fz-sdk/math/index.d.ts
vendored
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
/**
|
||||||
|
* Math operations
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
* @module
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** @version Added in JS SDK 0.1 */
|
||||||
|
export function isEqual(a: number, b: number, tolerance: number): boolean;
|
||||||
|
/** @version Added in JS SDK 0.1 */
|
||||||
|
export function abs(n: number): number;
|
||||||
|
/** @version Added in JS SDK 0.1 */
|
||||||
|
export function acos(n: number): number;
|
||||||
|
/** @version Added in JS SDK 0.1 */
|
||||||
|
export function acosh(n: number): number;
|
||||||
|
/** @version Added in JS SDK 0.1 */
|
||||||
|
export function asin(n: number): number;
|
||||||
|
/** @version Added in JS SDK 0.1 */
|
||||||
|
export function asinh(n: number): number;
|
||||||
|
/** @version Added in JS SDK 0.1 */
|
||||||
|
export function atan(n: number): number;
|
||||||
|
/** @version Added in JS SDK 0.1 */
|
||||||
|
export function atan2(a: number, b: number): number;
|
||||||
|
/** @version Added in JS SDK 0.1 */
|
||||||
|
export function atanh(n: number): number;
|
||||||
|
/** @version Added in JS SDK 0.1 */
|
||||||
|
export function cbrt(n: number): number;
|
||||||
|
/** @version Added in JS SDK 0.1 */
|
||||||
|
export function ceil(n: number): number;
|
||||||
|
/** @version Added in JS SDK 0.1 */
|
||||||
|
export function clz32(n: number): number;
|
||||||
|
/** @version Added in JS SDK 0.1 */
|
||||||
|
export function cos(n: number): number;
|
||||||
|
/** @version Added in JS SDK 0.1 */
|
||||||
|
export function exp(n: number): number;
|
||||||
|
/** @version Added in JS SDK 0.1 */
|
||||||
|
export function floor(n: number): number;
|
||||||
|
/** @version Added in JS SDK 0.1 */
|
||||||
|
export function log(n: number): number;
|
||||||
|
/** @version Added in JS SDK 0.1 */
|
||||||
|
export function max(n: number, m: number): number;
|
||||||
|
/** @version Added in JS SDK 0.1 */
|
||||||
|
export function min(n: number, m: number): number;
|
||||||
|
/** @version Added in JS SDK 0.1 */
|
||||||
|
export function pow(n: number, m: number): number;
|
||||||
|
/** @version Added in JS SDK 0.1 */
|
||||||
|
export function random(): number;
|
||||||
|
/** @version Added in JS SDK 0.1 */
|
||||||
|
export function sign(n: number): number;
|
||||||
|
/** @version Added in JS SDK 0.1 */
|
||||||
|
export function sin(n: number): number;
|
||||||
|
/** @version Added in JS SDK 0.1 */
|
||||||
|
export function sqrt(n: number): number;
|
||||||
|
/** @version Added in JS SDK 0.1 */
|
||||||
|
export function trunc(n: number): number;
|
||||||
|
/** @version Added in JS SDK 0.1 */
|
||||||
|
declare const PI: number;
|
||||||
|
/** @version Added in JS SDK 0.1 */
|
||||||
|
declare const E: number;
|
||||||
|
/** @version Added in JS SDK 0.1 */
|
||||||
|
declare const EPSILON: number;
|
||||||
@@ -1,20 +1,32 @@
|
|||||||
|
/**
|
||||||
|
* Module for using the color LED and vibration motor
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
* @module
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Signals success to the user via the color LED, speaker and vibration
|
* @brief Signals success to the user via the color LED, speaker and vibration
|
||||||
* motor
|
* motor
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
*/
|
*/
|
||||||
export declare function success(): void;
|
export declare function success(): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Signals failure to the user via the color LED, speaker and vibration
|
* @brief Signals failure to the user via the color LED, speaker and vibration
|
||||||
* motor
|
* motor
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
*/
|
*/
|
||||||
export declare function error(): void;
|
export declare function error(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
export type Color = "red" | "green" | "blue" | "yellow" | "cyan" | "magenta";
|
export type Color = "red" | "green" | "blue" | "yellow" | "cyan" | "magenta";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Displays a basic color on the color LED
|
* @brief Displays a basic color on the color LED
|
||||||
* @param color The color to display, see `Color`
|
* @param color The color to display, see `Color`
|
||||||
* @param duration The duration, either `"short"` (10ms) or `"long"` (100ms)
|
* @param duration The duration, either `"short"` (10ms) or `"long"` (100ms)
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
*/
|
*/
|
||||||
export declare function blink(color: Color, duration: "short" | "long"): void;
|
export declare function blink(color: Color, duration: "short" | "long"): void;
|
||||||
27
applications/system/js_app/packages/fz-sdk/package.json
Normal file
27
applications/system/js_app/packages/fz-sdk/package.json
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"name": "@flipperdevices/fz-sdk",
|
||||||
|
"version": "0.1.1",
|
||||||
|
"description": "Type declarations and documentation for native JS modules available on Flipper Zero",
|
||||||
|
"keywords": [
|
||||||
|
"flipper",
|
||||||
|
"flipper zero",
|
||||||
|
"framework"
|
||||||
|
],
|
||||||
|
"author": "Flipper Devices",
|
||||||
|
"license": "GPL-3.0-only",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/flipperdevices/flipperzero-firmware.git",
|
||||||
|
"directory": "applications/system/js_app/packages/fz-sdk"
|
||||||
|
},
|
||||||
|
"type": "module",
|
||||||
|
"dependencies": {
|
||||||
|
"esbuild": "^0.24.0",
|
||||||
|
"esbuild-plugin-tsc": "^0.4.0",
|
||||||
|
"json5": "^2.2.3",
|
||||||
|
"typedoc": "^0.26.10",
|
||||||
|
"typedoc-material-theme": "^1.1.0",
|
||||||
|
"prompts": "^2.4.2",
|
||||||
|
"serialport": "^12.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
896
applications/system/js_app/packages/fz-sdk/pnpm-lock.yaml
generated
Normal file
896
applications/system/js_app/packages/fz-sdk/pnpm-lock.yaml
generated
Normal file
@@ -0,0 +1,896 @@
|
|||||||
|
lockfileVersion: '9.0'
|
||||||
|
|
||||||
|
settings:
|
||||||
|
autoInstallPeers: true
|
||||||
|
excludeLinksFromLockfile: false
|
||||||
|
|
||||||
|
importers:
|
||||||
|
|
||||||
|
.:
|
||||||
|
dependencies:
|
||||||
|
prompts:
|
||||||
|
specifier: ^2.4.2
|
||||||
|
version: 2.4.2
|
||||||
|
serialport:
|
||||||
|
specifier: ^12.0.0
|
||||||
|
version: 12.0.0
|
||||||
|
devDependencies:
|
||||||
|
esbuild:
|
||||||
|
specifier: ^0.24.0
|
||||||
|
version: 0.24.0
|
||||||
|
esbuild-plugin-tsc:
|
||||||
|
specifier: ^0.4.0
|
||||||
|
version: 0.4.0(typescript@5.6.3)
|
||||||
|
json5:
|
||||||
|
specifier: ^2.2.3
|
||||||
|
version: 2.2.3
|
||||||
|
typedoc:
|
||||||
|
specifier: ^0.26.10
|
||||||
|
version: 0.26.10(typescript@5.6.3)
|
||||||
|
typedoc-material-theme:
|
||||||
|
specifier: ^1.1.0
|
||||||
|
version: 1.1.0(typedoc@0.26.10(typescript@5.6.3))
|
||||||
|
|
||||||
|
packages:
|
||||||
|
|
||||||
|
'@esbuild/aix-ppc64@0.24.0':
|
||||||
|
resolution: {integrity: sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [ppc64]
|
||||||
|
os: [aix]
|
||||||
|
|
||||||
|
'@esbuild/android-arm64@0.24.0':
|
||||||
|
resolution: {integrity: sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [android]
|
||||||
|
|
||||||
|
'@esbuild/android-arm@0.24.0':
|
||||||
|
resolution: {integrity: sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm]
|
||||||
|
os: [android]
|
||||||
|
|
||||||
|
'@esbuild/android-x64@0.24.0':
|
||||||
|
resolution: {integrity: sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [android]
|
||||||
|
|
||||||
|
'@esbuild/darwin-arm64@0.24.0':
|
||||||
|
resolution: {integrity: sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [darwin]
|
||||||
|
|
||||||
|
'@esbuild/darwin-x64@0.24.0':
|
||||||
|
resolution: {integrity: sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [darwin]
|
||||||
|
|
||||||
|
'@esbuild/freebsd-arm64@0.24.0':
|
||||||
|
resolution: {integrity: sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [freebsd]
|
||||||
|
|
||||||
|
'@esbuild/freebsd-x64@0.24.0':
|
||||||
|
resolution: {integrity: sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [freebsd]
|
||||||
|
|
||||||
|
'@esbuild/linux-arm64@0.24.0':
|
||||||
|
resolution: {integrity: sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-arm@0.24.0':
|
||||||
|
resolution: {integrity: sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-ia32@0.24.0':
|
||||||
|
resolution: {integrity: sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [ia32]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-loong64@0.24.0':
|
||||||
|
resolution: {integrity: sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [loong64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-mips64el@0.24.0':
|
||||||
|
resolution: {integrity: sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [mips64el]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-ppc64@0.24.0':
|
||||||
|
resolution: {integrity: sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [ppc64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-riscv64@0.24.0':
|
||||||
|
resolution: {integrity: sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [riscv64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-s390x@0.24.0':
|
||||||
|
resolution: {integrity: sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [s390x]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-x64@0.24.0':
|
||||||
|
resolution: {integrity: sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/netbsd-x64@0.24.0':
|
||||||
|
resolution: {integrity: sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [netbsd]
|
||||||
|
|
||||||
|
'@esbuild/openbsd-arm64@0.24.0':
|
||||||
|
resolution: {integrity: sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [openbsd]
|
||||||
|
|
||||||
|
'@esbuild/openbsd-x64@0.24.0':
|
||||||
|
resolution: {integrity: sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [openbsd]
|
||||||
|
|
||||||
|
'@esbuild/sunos-x64@0.24.0':
|
||||||
|
resolution: {integrity: sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [sunos]
|
||||||
|
|
||||||
|
'@esbuild/win32-arm64@0.24.0':
|
||||||
|
resolution: {integrity: sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@esbuild/win32-ia32@0.24.0':
|
||||||
|
resolution: {integrity: sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [ia32]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@esbuild/win32-x64@0.24.0':
|
||||||
|
resolution: {integrity: sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@material/material-color-utilities@0.2.7':
|
||||||
|
resolution: {integrity: sha512-0FCeqG6WvK4/Cc06F/xXMd/pv4FeisI0c1tUpBbfhA2n9Y8eZEv4Karjbmf2ZqQCPUWMrGp8A571tCjizxoTiQ==}
|
||||||
|
|
||||||
|
'@serialport/binding-mock@10.2.2':
|
||||||
|
resolution: {integrity: sha512-HAFzGhk9OuFMpuor7aT5G1ChPgn5qSsklTFOTUX72Rl6p0xwcSVsRtG/xaGp6bxpN7fI9D/S8THLBWbBgS6ldw==}
|
||||||
|
engines: {node: '>=12.0.0'}
|
||||||
|
|
||||||
|
'@serialport/bindings-cpp@12.0.1':
|
||||||
|
resolution: {integrity: sha512-r2XOwY2dDvbW7dKqSPIk2gzsr6M6Qpe9+/Ngs94fNaNlcTRCV02PfaoDmRgcubpNVVcLATlxSxPTIDw12dbKOg==}
|
||||||
|
engines: {node: '>=16.0.0'}
|
||||||
|
|
||||||
|
'@serialport/bindings-interface@1.2.2':
|
||||||
|
resolution: {integrity: sha512-CJaUd5bLvtM9c5dmO9rPBHPXTa9R2UwpkJ0wdh9JCYcbrPWsKz+ErvR0hBLeo7NPeiFdjFO4sonRljiw4d2XiA==}
|
||||||
|
engines: {node: ^12.22 || ^14.13 || >=16}
|
||||||
|
|
||||||
|
'@serialport/parser-byte-length@12.0.0':
|
||||||
|
resolution: {integrity: sha512-0ei0txFAj+s6FTiCJFBJ1T2hpKkX8Md0Pu6dqMrYoirjPskDLJRgZGLqoy3/lnU1bkvHpnJO+9oJ3PB9v8rNlg==}
|
||||||
|
engines: {node: '>=12.0.0'}
|
||||||
|
|
||||||
|
'@serialport/parser-cctalk@12.0.0':
|
||||||
|
resolution: {integrity: sha512-0PfLzO9t2X5ufKuBO34DQKLXrCCqS9xz2D0pfuaLNeTkyGUBv426zxoMf3rsMRodDOZNbFblu3Ae84MOQXjnZw==}
|
||||||
|
engines: {node: '>=12.0.0'}
|
||||||
|
|
||||||
|
'@serialport/parser-delimiter@11.0.0':
|
||||||
|
resolution: {integrity: sha512-aZLJhlRTjSmEwllLG7S4J8s8ctRAS0cbvCpO87smLvl3e4BgzbVgF6Z6zaJd3Aji2uSiYgfedCdNc4L6W+1E2g==}
|
||||||
|
engines: {node: '>=12.0.0'}
|
||||||
|
|
||||||
|
'@serialport/parser-delimiter@12.0.0':
|
||||||
|
resolution: {integrity: sha512-gu26tVt5lQoybhorLTPsH2j2LnX3AOP2x/34+DUSTNaUTzu2fBXw+isVjQJpUBFWu6aeQRZw5bJol5X9Gxjblw==}
|
||||||
|
engines: {node: '>=12.0.0'}
|
||||||
|
|
||||||
|
'@serialport/parser-inter-byte-timeout@12.0.0':
|
||||||
|
resolution: {integrity: sha512-GnCh8K0NAESfhCuXAt+FfBRz1Cf9CzIgXfp7SdMgXwrtuUnCC/yuRTUFWRvuzhYKoAo1TL0hhUo77SFHUH1T/w==}
|
||||||
|
engines: {node: '>=12.0.0'}
|
||||||
|
|
||||||
|
'@serialport/parser-packet-length@12.0.0':
|
||||||
|
resolution: {integrity: sha512-p1hiCRqvGHHLCN/8ZiPUY/G0zrxd7gtZs251n+cfNTn+87rwcdUeu9Dps3Aadx30/sOGGFL6brIRGK4l/t7MuQ==}
|
||||||
|
engines: {node: '>=8.6.0'}
|
||||||
|
|
||||||
|
'@serialport/parser-readline@11.0.0':
|
||||||
|
resolution: {integrity: sha512-rRAivhRkT3YO28WjmmG4FQX6L+KMb5/ikhyylRfzWPw0nSXy97+u07peS9CbHqaNvJkMhH1locp2H36aGMOEIA==}
|
||||||
|
engines: {node: '>=12.0.0'}
|
||||||
|
|
||||||
|
'@serialport/parser-readline@12.0.0':
|
||||||
|
resolution: {integrity: sha512-O7cywCWC8PiOMvo/gglEBfAkLjp/SENEML46BXDykfKP5mTPM46XMaX1L0waWU6DXJpBgjaL7+yX6VriVPbN4w==}
|
||||||
|
engines: {node: '>=12.0.0'}
|
||||||
|
|
||||||
|
'@serialport/parser-ready@12.0.0':
|
||||||
|
resolution: {integrity: sha512-ygDwj3O4SDpZlbrRUraoXIoIqb8sM7aMKryGjYTIF0JRnKeB1ys8+wIp0RFMdFbO62YriUDextHB5Um5cKFSWg==}
|
||||||
|
engines: {node: '>=12.0.0'}
|
||||||
|
|
||||||
|
'@serialport/parser-regex@12.0.0':
|
||||||
|
resolution: {integrity: sha512-dCAVh4P/pZrLcPv9NJ2mvPRBg64L5jXuiRxIlyxxdZGH4WubwXVXY/kBTihQmiAMPxbT3yshSX8f2+feqWsxqA==}
|
||||||
|
engines: {node: '>=12.0.0'}
|
||||||
|
|
||||||
|
'@serialport/parser-slip-encoder@12.0.0':
|
||||||
|
resolution: {integrity: sha512-0APxDGR9YvJXTRfY+uRGhzOhTpU5akSH183RUcwzN7QXh8/1jwFsFLCu0grmAUfi+fItCkR+Xr1TcNJLR13VNA==}
|
||||||
|
engines: {node: '>=12.0.0'}
|
||||||
|
|
||||||
|
'@serialport/parser-spacepacket@12.0.0':
|
||||||
|
resolution: {integrity: sha512-dozONxhPC/78pntuxpz/NOtVps8qIc/UZzdc/LuPvVsqCoJXiRxOg6ZtCP/W58iibJDKPZPAWPGYeZt9DJxI+Q==}
|
||||||
|
engines: {node: '>=12.0.0'}
|
||||||
|
|
||||||
|
'@serialport/stream@12.0.0':
|
||||||
|
resolution: {integrity: sha512-9On64rhzuqKdOQyiYLYv2lQOh3TZU/D3+IWCR5gk0alPel2nwpp4YwDEGiUBfrQZEdQ6xww0PWkzqth4wqwX3Q==}
|
||||||
|
engines: {node: '>=12.0.0'}
|
||||||
|
|
||||||
|
'@shikijs/core@1.22.0':
|
||||||
|
resolution: {integrity: sha512-S8sMe4q71TJAW+qG93s5VaiihujRK6rqDFqBnxqvga/3LvqHEnxqBIOPkt//IdXVtHkQWKu4nOQNk0uBGicU7Q==}
|
||||||
|
|
||||||
|
'@shikijs/engine-javascript@1.22.0':
|
||||||
|
resolution: {integrity: sha512-AeEtF4Gcck2dwBqCFUKYfsCq0s+eEbCEbkUuFou53NZ0sTGnJnJ/05KHQFZxpii5HMXbocV9URYVowOP2wH5kw==}
|
||||||
|
|
||||||
|
'@shikijs/engine-oniguruma@1.22.0':
|
||||||
|
resolution: {integrity: sha512-5iBVjhu/DYs1HB0BKsRRFipRrD7rqjxlWTj4F2Pf+nQSPqc3kcyqFFeZXnBMzDf0HdqaFVvhDRAGiYNvyLP+Mw==}
|
||||||
|
|
||||||
|
'@shikijs/types@1.22.0':
|
||||||
|
resolution: {integrity: sha512-Fw/Nr7FGFhlQqHfxzZY8Cwtwk5E9nKDUgeLjZgt3UuhcM3yJR9xj3ZGNravZZok8XmEZMiYkSMTPlPkULB8nww==}
|
||||||
|
|
||||||
|
'@shikijs/vscode-textmate@9.3.0':
|
||||||
|
resolution: {integrity: sha512-jn7/7ky30idSkd/O5yDBfAnVt+JJpepofP/POZ1iMOxK59cOfqIgg/Dj0eFsjOTMw+4ycJN0uhZH/Eb0bs/EUA==}
|
||||||
|
|
||||||
|
'@types/hast@3.0.4':
|
||||||
|
resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==}
|
||||||
|
|
||||||
|
'@types/mdast@4.0.4':
|
||||||
|
resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==}
|
||||||
|
|
||||||
|
'@types/unist@3.0.3':
|
||||||
|
resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==}
|
||||||
|
|
||||||
|
'@ungap/structured-clone@1.2.0':
|
||||||
|
resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==}
|
||||||
|
|
||||||
|
argparse@2.0.1:
|
||||||
|
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
|
||||||
|
|
||||||
|
balanced-match@1.0.2:
|
||||||
|
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
||||||
|
|
||||||
|
brace-expansion@2.0.1:
|
||||||
|
resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==}
|
||||||
|
|
||||||
|
ccount@2.0.1:
|
||||||
|
resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==}
|
||||||
|
|
||||||
|
character-entities-html4@2.1.0:
|
||||||
|
resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==}
|
||||||
|
|
||||||
|
character-entities-legacy@3.0.0:
|
||||||
|
resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==}
|
||||||
|
|
||||||
|
comma-separated-tokens@2.0.3:
|
||||||
|
resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==}
|
||||||
|
|
||||||
|
debug@4.3.4:
|
||||||
|
resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
|
||||||
|
engines: {node: '>=6.0'}
|
||||||
|
peerDependencies:
|
||||||
|
supports-color: '*'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
supports-color:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
dequal@2.0.3:
|
||||||
|
resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
|
devlop@1.1.0:
|
||||||
|
resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==}
|
||||||
|
|
||||||
|
entities@4.5.0:
|
||||||
|
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
|
||||||
|
engines: {node: '>=0.12'}
|
||||||
|
|
||||||
|
esbuild-plugin-tsc@0.4.0:
|
||||||
|
resolution: {integrity: sha512-q9gWIovt1nkwchMLc2zhyksaiHOv3kDK4b0AUol8lkMCRhJ1zavgfb2fad6BKp7FT9rh/OHmEBXVjczLoi/0yw==}
|
||||||
|
peerDependencies:
|
||||||
|
typescript: ^4.0.0 || ^5.0.0
|
||||||
|
|
||||||
|
esbuild@0.24.0:
|
||||||
|
resolution: {integrity: sha512-FuLPevChGDshgSicjisSooU0cemp/sGXR841D5LHMB7mTVOmsEHcAxaH3irL53+8YDIeVNQEySh4DaYU/iuPqQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
hast-util-to-html@9.0.3:
|
||||||
|
resolution: {integrity: sha512-M17uBDzMJ9RPCqLMO92gNNUDuBSq10a25SDBI08iCCxmorf4Yy6sYHK57n9WAbRAAaU+DuR4W6GN9K4DFZesYg==}
|
||||||
|
|
||||||
|
hast-util-whitespace@3.0.0:
|
||||||
|
resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==}
|
||||||
|
|
||||||
|
html-void-elements@3.0.0:
|
||||||
|
resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==}
|
||||||
|
|
||||||
|
json5@2.2.3:
|
||||||
|
resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
kleur@3.0.3:
|
||||||
|
resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
|
linkify-it@5.0.0:
|
||||||
|
resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==}
|
||||||
|
|
||||||
|
lunr@2.3.9:
|
||||||
|
resolution: {integrity: sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==}
|
||||||
|
|
||||||
|
markdown-it@14.1.0:
|
||||||
|
resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
mdast-util-to-hast@13.2.0:
|
||||||
|
resolution: {integrity: sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==}
|
||||||
|
|
||||||
|
mdurl@2.0.0:
|
||||||
|
resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==}
|
||||||
|
|
||||||
|
micromark-util-character@2.1.0:
|
||||||
|
resolution: {integrity: sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==}
|
||||||
|
|
||||||
|
micromark-util-encode@2.0.0:
|
||||||
|
resolution: {integrity: sha512-pS+ROfCXAGLWCOc8egcBvT0kf27GoWMqtdarNfDcjb6YLuV5cM3ioG45Ys2qOVqeqSbjaKg72vU+Wby3eddPsA==}
|
||||||
|
|
||||||
|
micromark-util-sanitize-uri@2.0.0:
|
||||||
|
resolution: {integrity: sha512-WhYv5UEcZrbAtlsnPuChHUAsu/iBPOVaEVsntLBIdpibO0ddy8OzavZz3iL2xVvBZOpolujSliP65Kq0/7KIYw==}
|
||||||
|
|
||||||
|
micromark-util-symbol@2.0.0:
|
||||||
|
resolution: {integrity: sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==}
|
||||||
|
|
||||||
|
micromark-util-types@2.0.0:
|
||||||
|
resolution: {integrity: sha512-oNh6S2WMHWRZrmutsRmDDfkzKtxF+bc2VxLC9dvtrDIRFln627VsFP6fLMgTryGDljgLPjkrzQSDcPrjPyDJ5w==}
|
||||||
|
|
||||||
|
minimatch@9.0.5:
|
||||||
|
resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
|
||||||
|
engines: {node: '>=16 || 14 >=14.17'}
|
||||||
|
|
||||||
|
ms@2.1.2:
|
||||||
|
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
|
||||||
|
|
||||||
|
node-addon-api@7.0.0:
|
||||||
|
resolution: {integrity: sha512-vgbBJTS4m5/KkE16t5Ly0WW9hz46swAstv0hYYwMtbG7AznRhNyfLRe8HZAiWIpcHzoO7HxhLuBQj9rJ/Ho0ZA==}
|
||||||
|
|
||||||
|
node-gyp-build@4.6.0:
|
||||||
|
resolution: {integrity: sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
oniguruma-to-js@0.4.3:
|
||||||
|
resolution: {integrity: sha512-X0jWUcAlxORhOqqBREgPMgnshB7ZGYszBNspP+tS9hPD3l13CdaXcHbgImoHUHlrvGx/7AvFEkTRhAGYh+jzjQ==}
|
||||||
|
|
||||||
|
prompts@2.4.2:
|
||||||
|
resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==}
|
||||||
|
engines: {node: '>= 6'}
|
||||||
|
|
||||||
|
property-information@6.5.0:
|
||||||
|
resolution: {integrity: sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==}
|
||||||
|
|
||||||
|
punycode.js@2.3.1:
|
||||||
|
resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
|
regex@4.3.3:
|
||||||
|
resolution: {integrity: sha512-r/AadFO7owAq1QJVeZ/nq9jNS1vyZt+6t1p/E59B56Rn2GCya+gr1KSyOzNL/er+r+B7phv5jG2xU2Nz1YkmJg==}
|
||||||
|
|
||||||
|
serialport@12.0.0:
|
||||||
|
resolution: {integrity: sha512-AmH3D9hHPFmnF/oq/rvigfiAouAKyK/TjnrkwZRYSFZxNggJxwvbAbfYrLeuvq7ktUdhuHdVdSjj852Z55R+uA==}
|
||||||
|
engines: {node: '>=16.0.0'}
|
||||||
|
|
||||||
|
shiki@1.22.0:
|
||||||
|
resolution: {integrity: sha512-/t5LlhNs+UOKQCYBtl5ZsH/Vclz73GIqT2yQsCBygr8L/ppTdmpL4w3kPLoZJbMKVWtoG77Ue1feOjZfDxvMkw==}
|
||||||
|
|
||||||
|
sisteransi@1.0.5:
|
||||||
|
resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==}
|
||||||
|
|
||||||
|
space-separated-tokens@2.0.2:
|
||||||
|
resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==}
|
||||||
|
|
||||||
|
stringify-entities@4.0.4:
|
||||||
|
resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==}
|
||||||
|
|
||||||
|
strip-comments@2.0.1:
|
||||||
|
resolution: {integrity: sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
trim-lines@3.0.1:
|
||||||
|
resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==}
|
||||||
|
|
||||||
|
typedoc-material-theme@1.1.0:
|
||||||
|
resolution: {integrity: sha512-LLWGVb8w+i+QGnsu/a0JKjcuzndFQt/UeGVOQz0HFFGGocROEHv5QYudIACrj+phL2LDwH05tJx0Ob3pYYH2UA==}
|
||||||
|
engines: {node: '>=18.0.0', npm: '>=8.6.0'}
|
||||||
|
peerDependencies:
|
||||||
|
typedoc: ^0.25.13 || ^0.26.3
|
||||||
|
|
||||||
|
typedoc@0.26.10:
|
||||||
|
resolution: {integrity: sha512-xLmVKJ8S21t+JeuQLNueebEuTVphx6IrP06CdV7+0WVflUSW3SPmR+h1fnWVdAR/FQePEgsSWCUHXqKKjzuUAw==}
|
||||||
|
engines: {node: '>= 18'}
|
||||||
|
hasBin: true
|
||||||
|
peerDependencies:
|
||||||
|
typescript: 4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x
|
||||||
|
|
||||||
|
typescript@5.6.3:
|
||||||
|
resolution: {integrity: sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==}
|
||||||
|
engines: {node: '>=14.17'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
uc.micro@2.1.0:
|
||||||
|
resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==}
|
||||||
|
|
||||||
|
unist-util-is@6.0.0:
|
||||||
|
resolution: {integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==}
|
||||||
|
|
||||||
|
unist-util-position@5.0.0:
|
||||||
|
resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==}
|
||||||
|
|
||||||
|
unist-util-stringify-position@4.0.0:
|
||||||
|
resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==}
|
||||||
|
|
||||||
|
unist-util-visit-parents@6.0.1:
|
||||||
|
resolution: {integrity: sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==}
|
||||||
|
|
||||||
|
unist-util-visit@5.0.0:
|
||||||
|
resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==}
|
||||||
|
|
||||||
|
vfile-message@4.0.2:
|
||||||
|
resolution: {integrity: sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==}
|
||||||
|
|
||||||
|
vfile@6.0.3:
|
||||||
|
resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==}
|
||||||
|
|
||||||
|
yaml@2.6.0:
|
||||||
|
resolution: {integrity: sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ==}
|
||||||
|
engines: {node: '>= 14'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
zwitch@2.0.4:
|
||||||
|
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
|
||||||
|
|
||||||
|
snapshots:
|
||||||
|
|
||||||
|
'@esbuild/aix-ppc64@0.24.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/android-arm64@0.24.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/android-arm@0.24.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/android-x64@0.24.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/darwin-arm64@0.24.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/darwin-x64@0.24.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/freebsd-arm64@0.24.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/freebsd-x64@0.24.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-arm64@0.24.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-arm@0.24.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-ia32@0.24.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-loong64@0.24.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-mips64el@0.24.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-ppc64@0.24.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-riscv64@0.24.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-s390x@0.24.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-x64@0.24.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/netbsd-x64@0.24.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/openbsd-arm64@0.24.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/openbsd-x64@0.24.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/sunos-x64@0.24.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/win32-arm64@0.24.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/win32-ia32@0.24.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/win32-x64@0.24.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@material/material-color-utilities@0.2.7': {}
|
||||||
|
|
||||||
|
'@serialport/binding-mock@10.2.2':
|
||||||
|
dependencies:
|
||||||
|
'@serialport/bindings-interface': 1.2.2
|
||||||
|
debug: 4.3.4
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
|
'@serialport/bindings-cpp@12.0.1':
|
||||||
|
dependencies:
|
||||||
|
'@serialport/bindings-interface': 1.2.2
|
||||||
|
'@serialport/parser-readline': 11.0.0
|
||||||
|
debug: 4.3.4
|
||||||
|
node-addon-api: 7.0.0
|
||||||
|
node-gyp-build: 4.6.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
|
'@serialport/bindings-interface@1.2.2': {}
|
||||||
|
|
||||||
|
'@serialport/parser-byte-length@12.0.0': {}
|
||||||
|
|
||||||
|
'@serialport/parser-cctalk@12.0.0': {}
|
||||||
|
|
||||||
|
'@serialport/parser-delimiter@11.0.0': {}
|
||||||
|
|
||||||
|
'@serialport/parser-delimiter@12.0.0': {}
|
||||||
|
|
||||||
|
'@serialport/parser-inter-byte-timeout@12.0.0': {}
|
||||||
|
|
||||||
|
'@serialport/parser-packet-length@12.0.0': {}
|
||||||
|
|
||||||
|
'@serialport/parser-readline@11.0.0':
|
||||||
|
dependencies:
|
||||||
|
'@serialport/parser-delimiter': 11.0.0
|
||||||
|
|
||||||
|
'@serialport/parser-readline@12.0.0':
|
||||||
|
dependencies:
|
||||||
|
'@serialport/parser-delimiter': 12.0.0
|
||||||
|
|
||||||
|
'@serialport/parser-ready@12.0.0': {}
|
||||||
|
|
||||||
|
'@serialport/parser-regex@12.0.0': {}
|
||||||
|
|
||||||
|
'@serialport/parser-slip-encoder@12.0.0': {}
|
||||||
|
|
||||||
|
'@serialport/parser-spacepacket@12.0.0': {}
|
||||||
|
|
||||||
|
'@serialport/stream@12.0.0':
|
||||||
|
dependencies:
|
||||||
|
'@serialport/bindings-interface': 1.2.2
|
||||||
|
debug: 4.3.4
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
|
'@shikijs/core@1.22.0':
|
||||||
|
dependencies:
|
||||||
|
'@shikijs/engine-javascript': 1.22.0
|
||||||
|
'@shikijs/engine-oniguruma': 1.22.0
|
||||||
|
'@shikijs/types': 1.22.0
|
||||||
|
'@shikijs/vscode-textmate': 9.3.0
|
||||||
|
'@types/hast': 3.0.4
|
||||||
|
hast-util-to-html: 9.0.3
|
||||||
|
|
||||||
|
'@shikijs/engine-javascript@1.22.0':
|
||||||
|
dependencies:
|
||||||
|
'@shikijs/types': 1.22.0
|
||||||
|
'@shikijs/vscode-textmate': 9.3.0
|
||||||
|
oniguruma-to-js: 0.4.3
|
||||||
|
|
||||||
|
'@shikijs/engine-oniguruma@1.22.0':
|
||||||
|
dependencies:
|
||||||
|
'@shikijs/types': 1.22.0
|
||||||
|
'@shikijs/vscode-textmate': 9.3.0
|
||||||
|
|
||||||
|
'@shikijs/types@1.22.0':
|
||||||
|
dependencies:
|
||||||
|
'@shikijs/vscode-textmate': 9.3.0
|
||||||
|
'@types/hast': 3.0.4
|
||||||
|
|
||||||
|
'@shikijs/vscode-textmate@9.3.0': {}
|
||||||
|
|
||||||
|
'@types/hast@3.0.4':
|
||||||
|
dependencies:
|
||||||
|
'@types/unist': 3.0.3
|
||||||
|
|
||||||
|
'@types/mdast@4.0.4':
|
||||||
|
dependencies:
|
||||||
|
'@types/unist': 3.0.3
|
||||||
|
|
||||||
|
'@types/unist@3.0.3': {}
|
||||||
|
|
||||||
|
'@ungap/structured-clone@1.2.0': {}
|
||||||
|
|
||||||
|
argparse@2.0.1: {}
|
||||||
|
|
||||||
|
balanced-match@1.0.2: {}
|
||||||
|
|
||||||
|
brace-expansion@2.0.1:
|
||||||
|
dependencies:
|
||||||
|
balanced-match: 1.0.2
|
||||||
|
|
||||||
|
ccount@2.0.1: {}
|
||||||
|
|
||||||
|
character-entities-html4@2.1.0: {}
|
||||||
|
|
||||||
|
character-entities-legacy@3.0.0: {}
|
||||||
|
|
||||||
|
comma-separated-tokens@2.0.3: {}
|
||||||
|
|
||||||
|
debug@4.3.4:
|
||||||
|
dependencies:
|
||||||
|
ms: 2.1.2
|
||||||
|
|
||||||
|
dequal@2.0.3: {}
|
||||||
|
|
||||||
|
devlop@1.1.0:
|
||||||
|
dependencies:
|
||||||
|
dequal: 2.0.3
|
||||||
|
|
||||||
|
entities@4.5.0: {}
|
||||||
|
|
||||||
|
esbuild-plugin-tsc@0.4.0(typescript@5.6.3):
|
||||||
|
dependencies:
|
||||||
|
strip-comments: 2.0.1
|
||||||
|
typescript: 5.6.3
|
||||||
|
|
||||||
|
esbuild@0.24.0:
|
||||||
|
optionalDependencies:
|
||||||
|
'@esbuild/aix-ppc64': 0.24.0
|
||||||
|
'@esbuild/android-arm': 0.24.0
|
||||||
|
'@esbuild/android-arm64': 0.24.0
|
||||||
|
'@esbuild/android-x64': 0.24.0
|
||||||
|
'@esbuild/darwin-arm64': 0.24.0
|
||||||
|
'@esbuild/darwin-x64': 0.24.0
|
||||||
|
'@esbuild/freebsd-arm64': 0.24.0
|
||||||
|
'@esbuild/freebsd-x64': 0.24.0
|
||||||
|
'@esbuild/linux-arm': 0.24.0
|
||||||
|
'@esbuild/linux-arm64': 0.24.0
|
||||||
|
'@esbuild/linux-ia32': 0.24.0
|
||||||
|
'@esbuild/linux-loong64': 0.24.0
|
||||||
|
'@esbuild/linux-mips64el': 0.24.0
|
||||||
|
'@esbuild/linux-ppc64': 0.24.0
|
||||||
|
'@esbuild/linux-riscv64': 0.24.0
|
||||||
|
'@esbuild/linux-s390x': 0.24.0
|
||||||
|
'@esbuild/linux-x64': 0.24.0
|
||||||
|
'@esbuild/netbsd-x64': 0.24.0
|
||||||
|
'@esbuild/openbsd-arm64': 0.24.0
|
||||||
|
'@esbuild/openbsd-x64': 0.24.0
|
||||||
|
'@esbuild/sunos-x64': 0.24.0
|
||||||
|
'@esbuild/win32-arm64': 0.24.0
|
||||||
|
'@esbuild/win32-ia32': 0.24.0
|
||||||
|
'@esbuild/win32-x64': 0.24.0
|
||||||
|
|
||||||
|
hast-util-to-html@9.0.3:
|
||||||
|
dependencies:
|
||||||
|
'@types/hast': 3.0.4
|
||||||
|
'@types/unist': 3.0.3
|
||||||
|
ccount: 2.0.1
|
||||||
|
comma-separated-tokens: 2.0.3
|
||||||
|
hast-util-whitespace: 3.0.0
|
||||||
|
html-void-elements: 3.0.0
|
||||||
|
mdast-util-to-hast: 13.2.0
|
||||||
|
property-information: 6.5.0
|
||||||
|
space-separated-tokens: 2.0.2
|
||||||
|
stringify-entities: 4.0.4
|
||||||
|
zwitch: 2.0.4
|
||||||
|
|
||||||
|
hast-util-whitespace@3.0.0:
|
||||||
|
dependencies:
|
||||||
|
'@types/hast': 3.0.4
|
||||||
|
|
||||||
|
html-void-elements@3.0.0: {}
|
||||||
|
|
||||||
|
json5@2.2.3: {}
|
||||||
|
|
||||||
|
kleur@3.0.3: {}
|
||||||
|
|
||||||
|
linkify-it@5.0.0:
|
||||||
|
dependencies:
|
||||||
|
uc.micro: 2.1.0
|
||||||
|
|
||||||
|
lunr@2.3.9: {}
|
||||||
|
|
||||||
|
markdown-it@14.1.0:
|
||||||
|
dependencies:
|
||||||
|
argparse: 2.0.1
|
||||||
|
entities: 4.5.0
|
||||||
|
linkify-it: 5.0.0
|
||||||
|
mdurl: 2.0.0
|
||||||
|
punycode.js: 2.3.1
|
||||||
|
uc.micro: 2.1.0
|
||||||
|
|
||||||
|
mdast-util-to-hast@13.2.0:
|
||||||
|
dependencies:
|
||||||
|
'@types/hast': 3.0.4
|
||||||
|
'@types/mdast': 4.0.4
|
||||||
|
'@ungap/structured-clone': 1.2.0
|
||||||
|
devlop: 1.1.0
|
||||||
|
micromark-util-sanitize-uri: 2.0.0
|
||||||
|
trim-lines: 3.0.1
|
||||||
|
unist-util-position: 5.0.0
|
||||||
|
unist-util-visit: 5.0.0
|
||||||
|
vfile: 6.0.3
|
||||||
|
|
||||||
|
mdurl@2.0.0: {}
|
||||||
|
|
||||||
|
micromark-util-character@2.1.0:
|
||||||
|
dependencies:
|
||||||
|
micromark-util-symbol: 2.0.0
|
||||||
|
micromark-util-types: 2.0.0
|
||||||
|
|
||||||
|
micromark-util-encode@2.0.0: {}
|
||||||
|
|
||||||
|
micromark-util-sanitize-uri@2.0.0:
|
||||||
|
dependencies:
|
||||||
|
micromark-util-character: 2.1.0
|
||||||
|
micromark-util-encode: 2.0.0
|
||||||
|
micromark-util-symbol: 2.0.0
|
||||||
|
|
||||||
|
micromark-util-symbol@2.0.0: {}
|
||||||
|
|
||||||
|
micromark-util-types@2.0.0: {}
|
||||||
|
|
||||||
|
minimatch@9.0.5:
|
||||||
|
dependencies:
|
||||||
|
brace-expansion: 2.0.1
|
||||||
|
|
||||||
|
ms@2.1.2: {}
|
||||||
|
|
||||||
|
node-addon-api@7.0.0: {}
|
||||||
|
|
||||||
|
node-gyp-build@4.6.0: {}
|
||||||
|
|
||||||
|
oniguruma-to-js@0.4.3:
|
||||||
|
dependencies:
|
||||||
|
regex: 4.3.3
|
||||||
|
|
||||||
|
prompts@2.4.2:
|
||||||
|
dependencies:
|
||||||
|
kleur: 3.0.3
|
||||||
|
sisteransi: 1.0.5
|
||||||
|
|
||||||
|
property-information@6.5.0: {}
|
||||||
|
|
||||||
|
punycode.js@2.3.1: {}
|
||||||
|
|
||||||
|
regex@4.3.3: {}
|
||||||
|
|
||||||
|
serialport@12.0.0:
|
||||||
|
dependencies:
|
||||||
|
'@serialport/binding-mock': 10.2.2
|
||||||
|
'@serialport/bindings-cpp': 12.0.1
|
||||||
|
'@serialport/parser-byte-length': 12.0.0
|
||||||
|
'@serialport/parser-cctalk': 12.0.0
|
||||||
|
'@serialport/parser-delimiter': 12.0.0
|
||||||
|
'@serialport/parser-inter-byte-timeout': 12.0.0
|
||||||
|
'@serialport/parser-packet-length': 12.0.0
|
||||||
|
'@serialport/parser-readline': 12.0.0
|
||||||
|
'@serialport/parser-ready': 12.0.0
|
||||||
|
'@serialport/parser-regex': 12.0.0
|
||||||
|
'@serialport/parser-slip-encoder': 12.0.0
|
||||||
|
'@serialport/parser-spacepacket': 12.0.0
|
||||||
|
'@serialport/stream': 12.0.0
|
||||||
|
debug: 4.3.4
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
|
shiki@1.22.0:
|
||||||
|
dependencies:
|
||||||
|
'@shikijs/core': 1.22.0
|
||||||
|
'@shikijs/engine-javascript': 1.22.0
|
||||||
|
'@shikijs/engine-oniguruma': 1.22.0
|
||||||
|
'@shikijs/types': 1.22.0
|
||||||
|
'@shikijs/vscode-textmate': 9.3.0
|
||||||
|
'@types/hast': 3.0.4
|
||||||
|
|
||||||
|
sisteransi@1.0.5: {}
|
||||||
|
|
||||||
|
space-separated-tokens@2.0.2: {}
|
||||||
|
|
||||||
|
stringify-entities@4.0.4:
|
||||||
|
dependencies:
|
||||||
|
character-entities-html4: 2.1.0
|
||||||
|
character-entities-legacy: 3.0.0
|
||||||
|
|
||||||
|
strip-comments@2.0.1: {}
|
||||||
|
|
||||||
|
trim-lines@3.0.1: {}
|
||||||
|
|
||||||
|
typedoc-material-theme@1.1.0(typedoc@0.26.10(typescript@5.6.3)):
|
||||||
|
dependencies:
|
||||||
|
'@material/material-color-utilities': 0.2.7
|
||||||
|
typedoc: 0.26.10(typescript@5.6.3)
|
||||||
|
|
||||||
|
typedoc@0.26.10(typescript@5.6.3):
|
||||||
|
dependencies:
|
||||||
|
lunr: 2.3.9
|
||||||
|
markdown-it: 14.1.0
|
||||||
|
minimatch: 9.0.5
|
||||||
|
shiki: 1.22.0
|
||||||
|
typescript: 5.6.3
|
||||||
|
yaml: 2.6.0
|
||||||
|
|
||||||
|
typescript@5.6.3: {}
|
||||||
|
|
||||||
|
uc.micro@2.1.0: {}
|
||||||
|
|
||||||
|
unist-util-is@6.0.0:
|
||||||
|
dependencies:
|
||||||
|
'@types/unist': 3.0.3
|
||||||
|
|
||||||
|
unist-util-position@5.0.0:
|
||||||
|
dependencies:
|
||||||
|
'@types/unist': 3.0.3
|
||||||
|
|
||||||
|
unist-util-stringify-position@4.0.0:
|
||||||
|
dependencies:
|
||||||
|
'@types/unist': 3.0.3
|
||||||
|
|
||||||
|
unist-util-visit-parents@6.0.1:
|
||||||
|
dependencies:
|
||||||
|
'@types/unist': 3.0.3
|
||||||
|
unist-util-is: 6.0.0
|
||||||
|
|
||||||
|
unist-util-visit@5.0.0:
|
||||||
|
dependencies:
|
||||||
|
'@types/unist': 3.0.3
|
||||||
|
unist-util-is: 6.0.0
|
||||||
|
unist-util-visit-parents: 6.0.1
|
||||||
|
|
||||||
|
vfile-message@4.0.2:
|
||||||
|
dependencies:
|
||||||
|
'@types/unist': 3.0.3
|
||||||
|
unist-util-stringify-position: 4.0.0
|
||||||
|
|
||||||
|
vfile@6.0.3:
|
||||||
|
dependencies:
|
||||||
|
'@types/unist': 3.0.3
|
||||||
|
vfile-message: 4.0.2
|
||||||
|
|
||||||
|
yaml@2.6.0: {}
|
||||||
|
|
||||||
|
zwitch@2.0.4: {}
|
||||||
176
applications/system/js_app/packages/fz-sdk/sdk.js
Normal file
176
applications/system/js_app/packages/fz-sdk/sdk.js
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
import fs from "node:fs";
|
||||||
|
import path from "node:path";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
import { SerialPort } from "serialport";
|
||||||
|
import prompts from "prompts";
|
||||||
|
import esbuild from "esbuild";
|
||||||
|
import json5 from "json5";
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
|
||||||
|
async function build(config) {
|
||||||
|
await esbuild.build({
|
||||||
|
entryPoints: ["./dist/index.js"],
|
||||||
|
outfile: config.output,
|
||||||
|
tsconfig: "./tsconfig.json",
|
||||||
|
format: "cjs",
|
||||||
|
bundle: true,
|
||||||
|
minify: config.minify,
|
||||||
|
external: [
|
||||||
|
"@flipperdevices/fz-sdk/*"
|
||||||
|
],
|
||||||
|
supported: {
|
||||||
|
"array-spread": false,
|
||||||
|
"arrow": false,
|
||||||
|
"async-await": false,
|
||||||
|
"async-generator": false,
|
||||||
|
"bigint": false,
|
||||||
|
"class": false,
|
||||||
|
"const-and-let": true,
|
||||||
|
"decorators": false,
|
||||||
|
"default-argument": false,
|
||||||
|
"destructuring": false,
|
||||||
|
"dynamic-import": false,
|
||||||
|
"exponent-operator": false,
|
||||||
|
"export-star-as": false,
|
||||||
|
"for-await": false,
|
||||||
|
"for-of": false,
|
||||||
|
"function-name-configurable": false,
|
||||||
|
"function-or-class-property-access": false,
|
||||||
|
"generator": false,
|
||||||
|
"hashbang": false,
|
||||||
|
"import-assertions": false,
|
||||||
|
"import-meta": false,
|
||||||
|
"inline-script": false,
|
||||||
|
"logical-assignment": false,
|
||||||
|
"nested-rest-binding": false,
|
||||||
|
"new-target": false,
|
||||||
|
"node-colon-prefix-import": false,
|
||||||
|
"node-colon-prefix-require": false,
|
||||||
|
"nullish-coalescing": false,
|
||||||
|
"object-accessors": false,
|
||||||
|
"object-extensions": false,
|
||||||
|
"object-rest-spread": false,
|
||||||
|
"optional-catch-binding": false,
|
||||||
|
"optional-chain": false,
|
||||||
|
"regexp-dot-all-flag": false,
|
||||||
|
"regexp-lookbehind-assertions": false,
|
||||||
|
"regexp-match-indices": false,
|
||||||
|
"regexp-named-capture-groups": false,
|
||||||
|
"regexp-set-notation": false,
|
||||||
|
"regexp-sticky-and-unicode-flags": false,
|
||||||
|
"regexp-unicode-property-escapes": false,
|
||||||
|
"rest-argument": false,
|
||||||
|
"template-literal": false,
|
||||||
|
"top-level-await": false,
|
||||||
|
"typeof-exotic-object-is-object": false,
|
||||||
|
"unicode-escapes": false,
|
||||||
|
"using": false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
let outContents = fs.readFileSync(config.output, "utf8");
|
||||||
|
outContents = "let exports = {};\n" + outContents;
|
||||||
|
|
||||||
|
if (config.enforceSdkVersion) {
|
||||||
|
const version = json5.parse(fs.readFileSync(path.join(__dirname, "package.json"), "utf8")).version;
|
||||||
|
let [major, minor, _] = version.split(".");
|
||||||
|
outContents = `checkSdkCompatibility(${major}, ${minor});\n${outContents}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.writeFileSync(config.output, outContents);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function upload(config) {
|
||||||
|
const appFile = fs.readFileSync(config.input, "utf8");
|
||||||
|
const flippers = (await SerialPort.list()).filter(x => x.serialNumber?.startsWith("flip_"));
|
||||||
|
|
||||||
|
if (!flippers) {
|
||||||
|
console.error("No Flippers found");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
let portPath = flippers[0].path;
|
||||||
|
if (flippers.length > 1) {
|
||||||
|
port = (await prompts([{
|
||||||
|
type: "select",
|
||||||
|
name: "port",
|
||||||
|
message: "Select Flipper to run the app on",
|
||||||
|
choices: flippers.map(x => ({ title: x.serialNumber.replace("flip_", ""), value: x.path })),
|
||||||
|
}])).port;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Connecting to Flipper at ${portPath}`);
|
||||||
|
let port = new SerialPort({ path: portPath, baudRate: 230400 });
|
||||||
|
let received = "";
|
||||||
|
let lastMatch = 0;
|
||||||
|
async function waitFor(string, timeoutMs) {
|
||||||
|
return new Promise((resolve, _reject) => {
|
||||||
|
let timeout = undefined;
|
||||||
|
if (timeoutMs) {
|
||||||
|
timeout = setTimeout(() => {
|
||||||
|
console.error("Error: timeout");
|
||||||
|
process.exit(1);
|
||||||
|
}, timeoutMs);
|
||||||
|
}
|
||||||
|
setInterval(() => {
|
||||||
|
let idx = received.indexOf(string, lastMatch);
|
||||||
|
if (idx !== -1) {
|
||||||
|
lastMatch = idx;
|
||||||
|
if (timeoutMs)
|
||||||
|
clearTimeout(timeout);
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
}, 50);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
port.on("data", (data) => {
|
||||||
|
received += data.toString();
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitFor(">: ", 1000);
|
||||||
|
console.log("Uploading application file");
|
||||||
|
port.write(`storage remove ${config.output}\x0d`);
|
||||||
|
port.drain();
|
||||||
|
await waitFor(">: ", 1000);
|
||||||
|
port.write(`storage write_chunk ${config.output} ${appFile.length}\x0d`);
|
||||||
|
await waitFor("Ready", 1000);
|
||||||
|
port.write(appFile);
|
||||||
|
port.drain();
|
||||||
|
await waitFor(">: ", 1000);
|
||||||
|
|
||||||
|
console.log("Launching application");
|
||||||
|
port.write(`js ${config.output}\x0d`);
|
||||||
|
port.drain();
|
||||||
|
|
||||||
|
await waitFor("Running", 1000);
|
||||||
|
process.stdout.write(received.slice(lastMatch));
|
||||||
|
port.on("data", (data) => {
|
||||||
|
process.stdout.write(data.toString());
|
||||||
|
});
|
||||||
|
process.on("exit", () => {
|
||||||
|
port.write("\x03");
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitFor("Script done!", 0);
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const commands = {
|
||||||
|
"build": build,
|
||||||
|
"upload": upload,
|
||||||
|
};
|
||||||
|
|
||||||
|
const config = json5.parse(fs.readFileSync("./fz-sdk.config.json5", "utf8"));
|
||||||
|
const command = process.argv[2];
|
||||||
|
|
||||||
|
if (!Object.keys(commands).includes(command)) {
|
||||||
|
console.error(`Unknown command ${command}. Supported: ${Object.keys(commands).join(", ")}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
await commands[command](config[command]);
|
||||||
|
})();
|
||||||
@@ -1,7 +1,17 @@
|
|||||||
|
/**
|
||||||
|
* Module for accessing the serial port
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
* @module
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Initializes the serial port
|
* @brief Initializes the serial port
|
||||||
|
*
|
||||||
|
* Automatically disables Expansion module service to prevent interference.
|
||||||
|
*
|
||||||
* @param port The port to initialize (`"lpuart"` or `"start"`)
|
* @param port The port to initialize (`"lpuart"` or `"start"`)
|
||||||
* @param baudRate
|
* @param baudRate
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
*/
|
*/
|
||||||
export declare function setup(port: "lpuart" | "usart", baudRate: number): void;
|
export declare function setup(port: "lpuart" | "usart", baudRate: number): void;
|
||||||
|
|
||||||
@@ -13,6 +23,7 @@ export declare function setup(port: "lpuart" | "usart", baudRate: number): void;
|
|||||||
* - Arrays of numbers will get sent as a sequence of bytes.
|
* - Arrays of numbers will get sent as a sequence of bytes.
|
||||||
* - `ArrayBuffer`s and `TypedArray`s will be sent as a sequence
|
* - `ArrayBuffer`s and `TypedArray`s will be sent as a sequence
|
||||||
* of bytes.
|
* of bytes.
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
*/
|
*/
|
||||||
export declare function write<E extends ElementType>(value: string | number | number[] | ArrayBuffer | TypedArray<E>): void;
|
export declare function write<E extends ElementType>(value: string | number | number[] | ArrayBuffer | TypedArray<E>): void;
|
||||||
|
|
||||||
@@ -24,6 +35,7 @@ export declare function write<E extends ElementType>(value: string | number | nu
|
|||||||
* unset, the function will wait forever.
|
* unset, the function will wait forever.
|
||||||
* @returns The received data interpreted as ASCII, or `undefined` if 0 bytes
|
* @returns The received data interpreted as ASCII, or `undefined` if 0 bytes
|
||||||
* were read.
|
* were read.
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
*/
|
*/
|
||||||
export declare function read(length: number, timeout?: number): string | undefined;
|
export declare function read(length: number, timeout?: number): string | undefined;
|
||||||
|
|
||||||
@@ -39,9 +51,24 @@ export declare function read(length: number, timeout?: number): string | undefin
|
|||||||
* applies to characters, not entire strings.
|
* applies to characters, not entire strings.
|
||||||
* @returns The received data interpreted as ASCII, or `undefined` if 0 bytes
|
* @returns The received data interpreted as ASCII, or `undefined` if 0 bytes
|
||||||
* were read.
|
* were read.
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
*/
|
*/
|
||||||
export declare function readln(timeout?: number): string;
|
export declare function readln(timeout?: number): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Read any available amount of data from the serial port
|
||||||
|
*
|
||||||
|
* Can be useful to avoid starving your loop with small reads.
|
||||||
|
*
|
||||||
|
* @param timeout The number of time, in milliseconds, after which this function
|
||||||
|
* will give up and return nothing. If unset, the function will
|
||||||
|
* wait forever.
|
||||||
|
* @returns The received data interpreted as ASCII, or `undefined` if 0 bytes
|
||||||
|
* were read.
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
|
export declare function readAny(timeout?: number): string | undefined;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Reads data from the serial port
|
* @brief Reads data from the serial port
|
||||||
* @param length The number of bytes to read
|
* @param length The number of bytes to read
|
||||||
@@ -50,6 +77,7 @@ export declare function readln(timeout?: number): string;
|
|||||||
* unset, the function will wait forever.
|
* unset, the function will wait forever.
|
||||||
* @returns The received data as an ArrayBuffer, or `undefined` if 0 bytes were
|
* @returns The received data as an ArrayBuffer, or `undefined` if 0 bytes were
|
||||||
* read.
|
* read.
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
*/
|
*/
|
||||||
export declare function readBytes(length: number, timeout?: number): ArrayBuffer;
|
export declare function readBytes(length: number, timeout?: number): ArrayBuffer;
|
||||||
|
|
||||||
@@ -73,5 +101,12 @@ export declare function readBytes(length: number, timeout?: number): ArrayBuffer
|
|||||||
* @returns The index of the matched pattern if multiple were provided, or 0 if
|
* @returns The index of the matched pattern if multiple were provided, or 0 if
|
||||||
* only one was provided and it matched, or `undefined` if none of the
|
* only one was provided and it matched, or `undefined` if none of the
|
||||||
* patterns matched.
|
* patterns matched.
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
*/
|
*/
|
||||||
export declare function expect(patterns: string | number[] | string[] | number[][], timeout?: number): number | undefined;
|
export declare function expect(patterns: string | number[] | string[] | number[][], timeout?: number): number | undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Deinitializes the serial port, allowing multiple initializations per script run
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
|
export declare function end(): void;
|
||||||
@@ -1,8 +1,15 @@
|
|||||||
|
/**
|
||||||
|
* Module for accessing the filesystem
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
* @module
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* File readability mode:
|
* File readability mode:
|
||||||
* - `"r"`: read-only
|
* - `"r"`: read-only
|
||||||
* - `"w"`: write-only
|
* - `"w"`: write-only
|
||||||
* - `"rw"`: read-write
|
* - `"rw"`: read-write
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
*/
|
*/
|
||||||
export type AccessMode = "r" | "w" | "rw";
|
export type AccessMode = "r" | "w" | "rw";
|
||||||
|
|
||||||
@@ -13,53 +20,78 @@ export type AccessMode = "r" | "w" | "rw";
|
|||||||
* - `"open_append"`: open file and set r/w pointer to EOF, or create a new one if it doesn't exist
|
* - `"open_append"`: open file and set r/w pointer to EOF, or create a new one if it doesn't exist
|
||||||
* - `"create_new"`: create new file or fail if it exists
|
* - `"create_new"`: create new file or fail if it exists
|
||||||
* - `"create_always"`: truncate and open file, or create a new empty one if it doesn't exist
|
* - `"create_always"`: truncate and open file, or create a new empty one if it doesn't exist
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
*/
|
*/
|
||||||
export type OpenMode = "open_existing" | "open_always" | "open_append" | "create_new" | "create_always";
|
export type OpenMode = "open_existing" | "open_always" | "open_append" | "create_new" | "create_always";
|
||||||
|
|
||||||
/** Standard UNIX timestamp */
|
/**
|
||||||
|
* Standard UNIX timestamp
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
export type Timestamp = number;
|
export type Timestamp = number;
|
||||||
|
|
||||||
/** File information structure */
|
/**
|
||||||
|
* File information structure
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
export declare class FileInfo {
|
export declare class FileInfo {
|
||||||
/**
|
/**
|
||||||
* Full path (e.g. "/ext/test", returned by `stat`) or file name
|
* Full path (e.g. "/ext/test", returned by `stat`) or file name
|
||||||
* (e.g. "test", returned by `readDirectory`)
|
* (e.g. "test", returned by `readDirectory`)
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
*/
|
*/
|
||||||
path: string;
|
path: string;
|
||||||
/**
|
/**
|
||||||
* Is the file a directory?
|
* Is the file a directory?
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
*/
|
*/
|
||||||
isDirectory: boolean;
|
isDirectory: boolean;
|
||||||
/**
|
/**
|
||||||
* File size in bytes, or 0 in the case of directories
|
* File size in bytes, or 0 in the case of directories
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
*/
|
*/
|
||||||
size: number;
|
size: number;
|
||||||
/**
|
/**
|
||||||
* Time of last access as a UNIX timestamp
|
* Time of last access as a UNIX timestamp
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
*/
|
*/
|
||||||
accessTime: Timestamp;
|
accessTime: Timestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Filesystem information structure */
|
/**
|
||||||
|
* Filesystem information structure
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
export declare class FsInfo {
|
export declare class FsInfo {
|
||||||
/** Total size of the filesystem, in bytes */
|
/**
|
||||||
|
* Total size of the filesystem, in bytes
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
totalSpace: number;
|
totalSpace: number;
|
||||||
/** Free space in the filesystem, in bytes */
|
/**
|
||||||
|
* Free space in the filesystem, in bytes
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
freeSpace: number;
|
freeSpace: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
// file operations
|
// file operations
|
||||||
|
|
||||||
/** File class */
|
/**
|
||||||
|
* File class
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
export declare class File {
|
export declare class File {
|
||||||
/**
|
/**
|
||||||
* Closes the file. After this method is called, all other operations
|
* Closes the file. After this method is called, all other operations
|
||||||
* related to this file become unavailable.
|
* related to this file become unavailable.
|
||||||
* @returns `true` on success, `false` on failure
|
* @returns `true` on success, `false` on failure
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
*/
|
*/
|
||||||
close(): boolean;
|
close(): boolean;
|
||||||
/**
|
/**
|
||||||
* Is the file currently open?
|
* Is the file currently open?
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
*/
|
*/
|
||||||
isOpen(): boolean;
|
isOpen(): boolean;
|
||||||
/**
|
/**
|
||||||
@@ -70,6 +102,7 @@ export declare class File {
|
|||||||
* @returns an `ArrayBuf` if the mode is `"binary"`, a `string` if the mode
|
* @returns an `ArrayBuf` if the mode is `"binary"`, a `string` if the mode
|
||||||
* is `ascii`. The number of bytes that was actually read may be
|
* is `ascii`. The number of bytes that was actually read may be
|
||||||
* fewer than requested.
|
* fewer than requested.
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
*/
|
*/
|
||||||
read<T extends ArrayBuffer | string>(mode: T extends ArrayBuffer ? "binary" : "ascii", bytes: number): T;
|
read<T extends ArrayBuffer | string>(mode: T extends ArrayBuffer ? "binary" : "ascii", bytes: number): T;
|
||||||
/**
|
/**
|
||||||
@@ -77,36 +110,43 @@ export declare class File {
|
|||||||
* @param data The data to write: a string that will be ASCII-encoded, or an
|
* @param data The data to write: a string that will be ASCII-encoded, or an
|
||||||
* ArrayBuf
|
* ArrayBuf
|
||||||
* @returns the amount of bytes that was actually written
|
* @returns the amount of bytes that was actually written
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
*/
|
*/
|
||||||
write(data: ArrayBuffer | string): number;
|
write(data: ArrayBuffer | string): number;
|
||||||
/**
|
/**
|
||||||
* Moves the R/W pointer forward
|
* Moves the R/W pointer forward
|
||||||
* @param bytes How many bytes to move the pointer forward by
|
* @param bytes How many bytes to move the pointer forward by
|
||||||
* @returns `true` on success, `false` on failure
|
* @returns `true` on success, `false` on failure
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
*/
|
*/
|
||||||
seekRelative(bytes: number): boolean;
|
seekRelative(bytes: number): boolean;
|
||||||
/**
|
/**
|
||||||
* Moves the R/W pointer to an absolute position inside the file
|
* Moves the R/W pointer to an absolute position inside the file
|
||||||
* @param bytes The position inside the file
|
* @param bytes The position inside the file
|
||||||
* @returns `true` on success, `false` on failure
|
* @returns `true` on success, `false` on failure
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
*/
|
*/
|
||||||
seekAbsolute(bytes: number): boolean;
|
seekAbsolute(bytes: number): boolean;
|
||||||
/**
|
/**
|
||||||
* Gets the absolute position of the R/W pointer in bytes
|
* Gets the absolute position of the R/W pointer in bytes
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
*/
|
*/
|
||||||
tell(): number;
|
tell(): number;
|
||||||
/**
|
/**
|
||||||
* Discards the data after the current position of the R/W pointer in a file
|
* Discards the data after the current position of the R/W pointer in a file
|
||||||
* opened in either write-only or read-write mode.
|
* opened in either write-only or read-write mode.
|
||||||
* @returns `true` on success, `false` on failure
|
* @returns `true` on success, `false` on failure
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
*/
|
*/
|
||||||
truncate(): boolean;
|
truncate(): boolean;
|
||||||
/**
|
/**
|
||||||
* Reads the total size of the file in bytes
|
* Reads the total size of the file in bytes
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
*/
|
*/
|
||||||
size(): number;
|
size(): number;
|
||||||
/**
|
/**
|
||||||
* Detects whether the R/W pointer has reached the end of the file
|
* Detects whether the R/W pointer has reached the end of the file
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
*/
|
*/
|
||||||
eof(): boolean;
|
eof(): boolean;
|
||||||
/**
|
/**
|
||||||
@@ -115,6 +155,7 @@ export declare class File {
|
|||||||
* @param dest The file to copy the bytes into
|
* @param dest The file to copy the bytes into
|
||||||
* @param bytes The number of bytes to copy
|
* @param bytes The number of bytes to copy
|
||||||
* @returns `true` on success, `false` on failure
|
* @returns `true` on success, `false` on failure
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
*/
|
*/
|
||||||
copyTo(dest: File, bytes: number): boolean;
|
copyTo(dest: File, bytes: number): boolean;
|
||||||
}
|
}
|
||||||
@@ -126,12 +167,14 @@ export declare class File {
|
|||||||
* @param openMode `"open_existing"`, `"open_always"`, `"open_append"`,
|
* @param openMode `"open_existing"`, `"open_always"`, `"open_append"`,
|
||||||
* `"create_new"` or `"create_always"`; see `OpenMode`
|
* `"create_new"` or `"create_always"`; see `OpenMode`
|
||||||
* @returns a `File` on success, or `undefined` on failure
|
* @returns a `File` on success, or `undefined` on failure
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
*/
|
*/
|
||||||
export declare function openFile(path: string, accessMode: AccessMode, openMode: OpenMode): File | undefined;
|
export declare function openFile(path: string, accessMode: AccessMode, openMode: OpenMode): File | undefined;
|
||||||
/**
|
/**
|
||||||
* Detects whether a file exists
|
* Detects whether a file exists
|
||||||
* @param path The path to the file
|
* @param path The path to the file
|
||||||
* @returns `true` on success, `false` on failure
|
* @returns `true` on success, `false` on failure
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
*/
|
*/
|
||||||
export declare function fileExists(path: string): boolean;
|
export declare function fileExists(path: string): boolean;
|
||||||
|
|
||||||
@@ -142,17 +185,20 @@ export declare function fileExists(path: string): boolean;
|
|||||||
* @param path The path to the directory
|
* @param path The path to the directory
|
||||||
* @returns Array of `FileInfo` structures with directory entries,
|
* @returns Array of `FileInfo` structures with directory entries,
|
||||||
* or `undefined` on failure
|
* or `undefined` on failure
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
*/
|
*/
|
||||||
export declare function readDirectory(path: string): FileInfo[] | undefined;
|
export declare function readDirectory(path: string): FileInfo[] | undefined;
|
||||||
/**
|
/**
|
||||||
* Detects whether a directory exists
|
* Detects whether a directory exists
|
||||||
* @param path The path to the directory
|
* @param path The path to the directory
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
*/
|
*/
|
||||||
export declare function directoryExists(path: string): boolean;
|
export declare function directoryExists(path: string): boolean;
|
||||||
/**
|
/**
|
||||||
* Creates an empty directory
|
* Creates an empty directory
|
||||||
* @param path The path to the new directory
|
* @param path The path to the new directory
|
||||||
* @returns `true` on success, `false` on failure
|
* @returns `true` on success, `false` on failure
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
*/
|
*/
|
||||||
export declare function makeDirectory(path: string): boolean;
|
export declare function makeDirectory(path: string): boolean;
|
||||||
|
|
||||||
@@ -161,24 +207,28 @@ export declare function makeDirectory(path: string): boolean;
|
|||||||
/**
|
/**
|
||||||
* Detects whether a file or a directory exists
|
* Detects whether a file or a directory exists
|
||||||
* @param path The path to the file or directory
|
* @param path The path to the file or directory
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
*/
|
*/
|
||||||
export declare function fileOrDirExists(path: string): boolean;
|
export declare function fileOrDirExists(path: string): boolean;
|
||||||
/**
|
/**
|
||||||
* Acquires metadata about a file or directory
|
* Acquires metadata about a file or directory
|
||||||
* @param path The path to the file or directory
|
* @param path The path to the file or directory
|
||||||
* @returns A `FileInfo` structure or `undefined` on failure
|
* @returns A `FileInfo` structure or `undefined` on failure
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
*/
|
*/
|
||||||
export declare function stat(path: string): FileInfo | undefined;
|
export declare function stat(path: string): FileInfo | undefined;
|
||||||
/**
|
/**
|
||||||
* Removes a file or an empty directory
|
* Removes a file or an empty directory
|
||||||
* @param path The path to the file or directory
|
* @param path The path to the file or directory
|
||||||
* @returns `true` on success, `false` on failure
|
* @returns `true` on success, `false` on failure
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
*/
|
*/
|
||||||
export declare function remove(path: string): boolean;
|
export declare function remove(path: string): boolean;
|
||||||
/**
|
/**
|
||||||
* Removes a file or recursively removes a possibly non-empty directory
|
* Removes a file or recursively removes a possibly non-empty directory
|
||||||
* @param path The path to the file or directory
|
* @param path The path to the file or directory
|
||||||
* @returns `true` on success, `false` on failure
|
* @returns `true` on success, `false` on failure
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
*/
|
*/
|
||||||
export declare function rmrf(path: string): boolean;
|
export declare function rmrf(path: string): boolean;
|
||||||
/**
|
/**
|
||||||
@@ -187,6 +237,7 @@ export declare function rmrf(path: string): boolean;
|
|||||||
* @param newPath The new path that the file or directory will become accessible
|
* @param newPath The new path that the file or directory will become accessible
|
||||||
* under
|
* under
|
||||||
* @returns `true` on success, `false` on failure
|
* @returns `true` on success, `false` on failure
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
*/
|
*/
|
||||||
export declare function rename(oldPath: string, newPath: string): boolean;
|
export declare function rename(oldPath: string, newPath: string): boolean;
|
||||||
/**
|
/**
|
||||||
@@ -194,11 +245,13 @@ export declare function rename(oldPath: string, newPath: string): boolean;
|
|||||||
* @param oldPath The original path to the file or directory
|
* @param oldPath The original path to the file or directory
|
||||||
* @param newPath The new path that the copy of the file or directory will be
|
* @param newPath The new path that the copy of the file or directory will be
|
||||||
* accessible under
|
* accessible under
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
*/
|
*/
|
||||||
export declare function copy(oldPath: string, newPath: string): boolean;
|
export declare function copy(oldPath: string, newPath: string): boolean;
|
||||||
/**
|
/**
|
||||||
* Fetches generic information about a filesystem
|
* Fetches generic information about a filesystem
|
||||||
* @param filesystem The path to the filesystem (e.g. `"/ext"` or `"/int"`)
|
* @param filesystem The path to the filesystem (e.g. `"/ext"` or `"/int"`)
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
*/
|
*/
|
||||||
export declare function fsInfo(filesystem: string): FsInfo | undefined;
|
export declare function fsInfo(filesystem: string): FsInfo | undefined;
|
||||||
/**
|
/**
|
||||||
@@ -218,6 +271,7 @@ export declare function fsInfo(filesystem: string): FsInfo | undefined;
|
|||||||
* @param maxLen The maximum length of the filename with the numeric suffix
|
* @param maxLen The maximum length of the filename with the numeric suffix
|
||||||
* @returns The base of the filename with the next available numeric suffix,
|
* @returns The base of the filename with the next available numeric suffix,
|
||||||
* without the extension or the base directory.
|
* without the extension or the base directory.
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
*/
|
*/
|
||||||
export declare function nextAvailableFilename(dirPath: string, fileName: string, fileExt: string, maxLen: number): string;
|
export declare function nextAvailableFilename(dirPath: string, fileName: string, fileExt: string, maxLen: number): string;
|
||||||
|
|
||||||
@@ -226,6 +280,7 @@ export declare function nextAvailableFilename(dirPath: string, fileName: string,
|
|||||||
/**
|
/**
|
||||||
* Determines whether the two paths are equivalent. Respects filesystem-defined
|
* Determines whether the two paths are equivalent. Respects filesystem-defined
|
||||||
* path equivalence rules.
|
* path equivalence rules.
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
*/
|
*/
|
||||||
export declare function arePathsEqual(path1: string, path2: string): boolean;
|
export declare function arePathsEqual(path1: string, path2: string): boolean;
|
||||||
/**
|
/**
|
||||||
@@ -233,5 +288,6 @@ export declare function arePathsEqual(path1: string, path2: string): boolean;
|
|||||||
* filesystem-defined path equivalence rules.
|
* filesystem-defined path equivalence rules.
|
||||||
* @param parentPath The parent path
|
* @param parentPath The parent path
|
||||||
* @param childPath The child path
|
* @param childPath The child path
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
*/
|
*/
|
||||||
export declare function isSubpathOf(parentPath: string, childPath: string): boolean;
|
export declare function isSubpathOf(parentPath: string, childPath: string): boolean;
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
/**
|
/**
|
||||||
* Unit test module. Only available if the firmware has been configured with
|
* Unit test module. Only available if the firmware has been configured with
|
||||||
* `FIRMWARE_APP_SET=unit_tests`.
|
* `FIRMWARE_APP_SET=unit_tests`.
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
* @module
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export function fail(message: string): never;
|
export function fail(message: string): never;
|
||||||
13
applications/system/js_app/packages/fz-sdk/tsconfig.json
Normal file
13
applications/system/js_app/packages/fz-sdk/tsconfig.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"checkJs": true,
|
||||||
|
"module": "CommonJS",
|
||||||
|
"noLib": true,
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"./**/*.d.ts"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules",
|
||||||
|
]
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user