mirror of
https://github.com/DarkFlippers/unleashed-firmware.git
synced 2024-12-22 21:01:40 +03:00
Merge remote-tracking branch 'OFW/dev' into dev
This commit is contained in:
commit
04c8093672
3
.gitignore
vendored
3
.gitignore
vendored
@ -70,3 +70,6 @@ PVS-Studio.log
|
|||||||
.gdbinit
|
.gdbinit
|
||||||
|
|
||||||
/fbt_options_local.py
|
/fbt_options_local.py
|
||||||
|
|
||||||
|
# JS packages
|
||||||
|
node_modules/
|
||||||
|
@ -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) {
|
||||||
|
@ -140,6 +140,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.
|
||||||
//
|
//
|
||||||
@ -150,6 +163,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);
|
||||||
|
|
||||||
|
@ -582,7 +582,7 @@ bool ndef_parse_record(
|
|||||||
NdefTnf tnf,
|
NdefTnf tnf,
|
||||||
const char* type,
|
const char* type,
|
||||||
uint8_t type_len) {
|
uint8_t type_len) {
|
||||||
FURI_LOG_D(TAG, "payload type: %.*s len: %d", type_len, type, len);
|
FURI_LOG_D(TAG, "payload type: %.*s len: %hu", type_len, type, len);
|
||||||
if(!len) {
|
if(!len) {
|
||||||
furi_string_cat(ndef->output, "Empty\n");
|
furi_string_cat(ndef->output, "Empty\n");
|
||||||
return true;
|
return true;
|
||||||
@ -702,9 +702,9 @@ bool ndef_parse_message(Ndef* ndef, size_t pos, size_t len, size_t message_num,
|
|||||||
pos += id_len;
|
pos += id_len;
|
||||||
|
|
||||||
if(smart_poster) {
|
if(smart_poster) {
|
||||||
furi_string_cat_printf(ndef->output, "\e*> SP-R%d: ", record_num);
|
furi_string_cat_printf(ndef->output, "\e*> SP-R%zu: ", record_num);
|
||||||
} else {
|
} else {
|
||||||
furi_string_cat_printf(ndef->output, "\e*> M%d-R%d: ", message_num, record_num);
|
furi_string_cat_printf(ndef->output, "\e*> M%zu-R%zu: ", message_num, record_num);
|
||||||
}
|
}
|
||||||
if(!ndef_parse_record(ndef, pos, payload_len, flags_tnf.type_name_format, type, type_len)) {
|
if(!ndef_parse_record(ndef, pos, payload_len, flags_tnf.type_name_format, type, type_len)) {
|
||||||
if(type_was_allocated) free(type);
|
if(type_was_allocated) free(type);
|
||||||
@ -721,7 +721,7 @@ bool ndef_parse_message(Ndef* ndef, size_t pos, size_t len, size_t message_num,
|
|||||||
if(smart_poster) {
|
if(smart_poster) {
|
||||||
furi_string_cat(ndef->output, "\e*> SP: Empty\n\n");
|
furi_string_cat(ndef->output, "\e*> SP: Empty\n\n");
|
||||||
} else {
|
} else {
|
||||||
furi_string_cat_printf(ndef->output, "\e*> M%d: Empty\n\n", message_num);
|
furi_string_cat_printf(ndef->output, "\e*> M%zu: Empty\n\n", message_num);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -949,7 +949,7 @@ static bool ndef_mfc_parse(const NfcDevice* device, FuriString* parsed_data) {
|
|||||||
} else {
|
} else {
|
||||||
data_block = 93 + (sector - 32) * 15;
|
data_block = 93 + (sector - 32) * 15;
|
||||||
}
|
}
|
||||||
FURI_LOG_D(TAG, "data_block: %d", data_block);
|
FURI_LOG_D(TAG, "data_block: %zu", data_block);
|
||||||
size_t data_start = data_block * MF_CLASSIC_BLOCK_SIZE;
|
size_t data_start = data_block * MF_CLASSIC_BLOCK_SIZE;
|
||||||
size_t parsed = ndef_parse_tlv(&ndef, data_start, data_size - data_start, total_parsed);
|
size_t parsed = ndef_parse_tlv(&ndef, data_start, data_size - data_start, total_parsed);
|
||||||
|
|
||||||
@ -982,7 +982,7 @@ static bool ndef_slix_parse(const NfcDevice* device, FuriString* parsed_data) {
|
|||||||
const uint16_t block_count = iso15693_3_get_block_count(data);
|
const uint16_t block_count = iso15693_3_get_block_count(data);
|
||||||
const uint8_t* blocks = simple_array_cget_data(data->block_data);
|
const uint8_t* blocks = simple_array_cget_data(data->block_data);
|
||||||
|
|
||||||
// TODO: Find some way to check for other iso15693 NDEF cards and
|
// TODO(-nofl): Find some way to check for other iso15693 NDEF cards and
|
||||||
// split this to also support non-slix iso15693 NDEF tags
|
// split this to also support non-slix iso15693 NDEF tags
|
||||||
// Rest of the code works on iso15693 too, but uses SLIX layout assumptions
|
// Rest of the code works on iso15693 too, but uses SLIX layout assumptions
|
||||||
if(block_size != SLIX_BLOCK_SIZE) {
|
if(block_size != SLIX_BLOCK_SIZE) {
|
||||||
|
@ -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
|
||||||
|
@ -380,7 +380,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;
|
||||||
|
|
||||||
@ -412,8 +412,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,7 +5,6 @@ App(
|
|||||||
provides=[
|
provides=[
|
||||||
"updater_app",
|
"updater_app",
|
||||||
"js_app",
|
"js_app",
|
||||||
"js_app_start",
|
|
||||||
# "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(
|
||||||
@ -96,6 +107,7 @@ App(
|
|||||||
entry_point="js_gui_file_picker_ep",
|
entry_point="js_gui_file_picker_ep",
|
||||||
requires=["js_app"],
|
requires=["js_app"],
|
||||||
sources=["modules/js_gui/file_picker.c"],
|
sources=["modules/js_gui/file_picker.c"],
|
||||||
|
fap_libs=["assets"],
|
||||||
)
|
)
|
||||||
|
|
||||||
App(
|
App(
|
||||||
|
@ -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 :)")
|
@ -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
|
||||||
|
3
applications/system/js_app/examples/apps/Scripts/load.js
Normal file
3
applications/system/js_app/examples/apps/Scripts/load.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
let math = load(__dirname + "/load_api.js");
|
||||||
|
let result = math.add(5, 10);
|
||||||
|
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);
|
@ -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);
|
||||||
|
@ -269,11 +269,48 @@ static int32_t js_thread(void* arg) {
|
|||||||
mjs_set(mjs, global, "parseInt", ~0, MJS_MK_FN(js_parse_int));
|
mjs_set(mjs, global, "parseInt", ~0, MJS_MK_FN(js_parse_int));
|
||||||
|
|
||||||
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("toString", MJS_MK_FN(js_global_to_string));
|
||||||
|
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);
|
||||||
|
|
||||||
|
@ -204,6 +204,22 @@ static void js_badusb_quit(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);
|
||||||
|
@ -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) {
|
||||||
@ -273,7 +273,7 @@ static void js_gpio_get(struct mjs* mjs) {
|
|||||||
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);
|
||||||
|
|
||||||
|
@ -1,17 +1,6 @@
|
|||||||
#include "../../js_modules.h"
|
#include "../../js_modules.h"
|
||||||
#include <dialogs/dialogs.h>
|
#include <dialogs/dialogs.h>
|
||||||
|
#include <assets_icons.h>
|
||||||
// File icon
|
|
||||||
#include <gui/icon_i.h>
|
|
||||||
static const uint8_t _I_file_10px_0[] = {
|
|
||||||
0x00, 0x7f, 0x00, 0xa1, 0x00, 0x2d, 0x01, 0xe1, 0x01, 0x0d, 0x01,
|
|
||||||
0x01, 0x01, 0x7d, 0x01, 0x01, 0x01, 0x01, 0x01, 0xff, 0x01,
|
|
||||||
};
|
|
||||||
static const uint8_t* const _I_file_10px[] = {_I_file_10px_0};
|
|
||||||
|
|
||||||
static const Icon I_file_10px =
|
|
||||||
{.width = 10, .height = 10, .frame_count = 1, .frame_rate = 0, .frames = _I_file_10px};
|
|
||||||
// File icon end
|
|
||||||
|
|
||||||
static void js_gui_file_picker_pick_file(struct mjs* mjs) {
|
static void js_gui_file_picker_pick_file(struct mjs* mjs) {
|
||||||
const char *base_path, *extension;
|
const char *base_path, *extension;
|
||||||
|
@ -85,7 +85,7 @@ static bool default_text_assign(
|
|||||||
context->buffer = realloc(context->buffer, context->buffer_size); //-V701
|
context->buffer = realloc(context->buffer, context->buffer_size); //-V701
|
||||||
}
|
}
|
||||||
// Also trim excess previous data with strlcpy()
|
// Also trim excess previous data with strlcpy()
|
||||||
strlcpy(context->buffer, value.string, context->buffer_size);
|
strlcpy(context->buffer, value.string, context->buffer_size); //-V575
|
||||||
text_input_set_result_callback(
|
text_input_set_result_callback(
|
||||||
input,
|
input,
|
||||||
(TextInputCallback)input_callback,
|
(TextInputCallback)input_callback,
|
||||||
|
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
Normal file
373
applications/system/js_app/packages/create-fz-app/pnpm-lock.yaml
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" |
|
||||||
|
|
||||||
@ -31,6 +33,7 @@ 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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -39,11 +42,13 @@ export type KeyCode = MainKey | ModifierKey | number;
|
|||||||
* Automatically unlocks USB profile, so qFlipper connection will be interrupted.
|
* Automatically unlocks USB profile, so qFlipper connection will be interrupted.
|
||||||
*
|
*
|
||||||
* @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, layoutPath?: 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;
|
||||||
|
|
||||||
@ -52,6 +57,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;
|
||||||
|
|
||||||
@ -60,6 +66,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;
|
||||||
|
|
||||||
@ -68,6 +75,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;
|
||||||
|
|
||||||
@ -75,6 +83,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;
|
||||||
|
|
||||||
@ -83,6 +92,7 @@ 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;
|
||||||
|
|
||||||
@ -90,6 +100,7 @@ export declare function println(string: string, delay?: number): void;
|
|||||||
* @brief Prints a string by Alt+Numpad method - works only on Windows!
|
* @brief Prints a string by Alt+Numpad method - works only on Windows!
|
||||||
* @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 altPrint(string: string, delay?: number): void;
|
export declare function altPrint(string: string, delay?: number): void;
|
||||||
|
|
||||||
@ -98,10 +109,12 @@ export declare function altPrint(string: string, delay?: number): void;
|
|||||||
* Presses "Enter" after printing the string
|
* Presses "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 altPrintln(string: string, delay?: number): void;
|
export declare function altPrintln(string: string, delay?: number): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Releases usb, optional, but allows to switch usb profile
|
* @brief Releases usb, optional, but allows to switch usb profile
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
*/
|
*/
|
||||||
export declare function quit(): void;
|
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];
|
367
applications/system/js_app/packages/fz-sdk/global.d.ts
vendored
Normal file
367
applications/system/js_app/packages/fz-sdk/global.d.ts
vendored
Normal file
@ -0,0 +1,367 @@
|
|||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
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 Reads a JS value from a file
|
||||||
|
*
|
||||||
|
* Reads a file at the specified path, interprets it as a JS value and returns
|
||||||
|
* said value.
|
||||||
|
*
|
||||||
|
* @param path The path to the file
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
*/
|
||||||
|
declare function load(path: string): any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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;
|
171
applications/system/js_app/packages/fz-sdk/gui/index.d.ts
vendored
Normal file
171
applications/system/js_app/packages/fz-sdk/gui/index.d.ts
vendored
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
/**
|
||||||
|
* ```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> {
|
||||||
|
set<P extends keyof Props>(property: P, value: Props[P]): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export declare class ViewFactory<Props extends Properties, V extends View<Props>> {
|
||||||
|
make(): V;
|
||||||
|
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;
|
||||||
|
/**
|
||||||
|
* 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;
|
@ -1,27 +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;
|
export function isEqual(a: number, b: number, tolerance: number): boolean;
|
||||||
|
/** @version Added in JS SDK 0.1 */
|
||||||
export function abs(n: number): number;
|
export function abs(n: number): number;
|
||||||
|
/** @version Added in JS SDK 0.1 */
|
||||||
export function acos(n: number): number;
|
export function acos(n: number): number;
|
||||||
|
/** @version Added in JS SDK 0.1 */
|
||||||
export function acosh(n: number): number;
|
export function acosh(n: number): number;
|
||||||
|
/** @version Added in JS SDK 0.1 */
|
||||||
export function asin(n: number): number;
|
export function asin(n: number): number;
|
||||||
|
/** @version Added in JS SDK 0.1 */
|
||||||
export function asinh(n: number): number;
|
export function asinh(n: number): number;
|
||||||
|
/** @version Added in JS SDK 0.1 */
|
||||||
export function atan(n: number): number;
|
export function atan(n: number): number;
|
||||||
|
/** @version Added in JS SDK 0.1 */
|
||||||
export function atan2(a: number, b: number): number;
|
export function atan2(a: number, b: number): number;
|
||||||
|
/** @version Added in JS SDK 0.1 */
|
||||||
export function atanh(n: number): number;
|
export function atanh(n: number): number;
|
||||||
|
/** @version Added in JS SDK 0.1 */
|
||||||
export function cbrt(n: number): number;
|
export function cbrt(n: number): number;
|
||||||
|
/** @version Added in JS SDK 0.1 */
|
||||||
export function ceil(n: number): number;
|
export function ceil(n: number): number;
|
||||||
|
/** @version Added in JS SDK 0.1 */
|
||||||
export function clz32(n: number): number;
|
export function clz32(n: number): number;
|
||||||
|
/** @version Added in JS SDK 0.1 */
|
||||||
export function cos(n: number): number;
|
export function cos(n: number): number;
|
||||||
|
/** @version Added in JS SDK 0.1 */
|
||||||
export function exp(n: number): number;
|
export function exp(n: number): number;
|
||||||
|
/** @version Added in JS SDK 0.1 */
|
||||||
export function floor(n: number): number;
|
export function floor(n: number): number;
|
||||||
|
/** @version Added in JS SDK 0.1 */
|
||||||
export function log(n: number): number;
|
export function log(n: number): number;
|
||||||
|
/** @version Added in JS SDK 0.1 */
|
||||||
export function max(n: number, m: number): number;
|
export function max(n: number, m: number): number;
|
||||||
|
/** @version Added in JS SDK 0.1 */
|
||||||
export function min(n: number, m: number): number;
|
export function min(n: number, m: number): number;
|
||||||
|
/** @version Added in JS SDK 0.1 */
|
||||||
export function pow(n: number, m: number): number;
|
export function pow(n: number, m: number): number;
|
||||||
|
/** @version Added in JS SDK 0.1 */
|
||||||
export function random(): number;
|
export function random(): number;
|
||||||
|
/** @version Added in JS SDK 0.1 */
|
||||||
export function sign(n: number): number;
|
export function sign(n: number): number;
|
||||||
|
/** @version Added in JS SDK 0.1 */
|
||||||
export function sin(n: number): number;
|
export function sin(n: number): number;
|
||||||
|
/** @version Added in JS SDK 0.1 */
|
||||||
export function sqrt(n: number): number;
|
export function sqrt(n: number): number;
|
||||||
|
/** @version Added in JS SDK 0.1 */
|
||||||
export function trunc(n: number): number;
|
export function trunc(n: number): number;
|
||||||
|
/** @version Added in JS SDK 0.1 */
|
||||||
declare const PI: number;
|
declare const PI: number;
|
||||||
|
/** @version Added in JS SDK 0.1 */
|
||||||
declare const E: number;
|
declare const E: number;
|
||||||
|
/** @version Added in JS SDK 0.1 */
|
||||||
declare const EPSILON: number;
|
declare const EPSILON: number;
|
@ -1,6 +1,13 @@
|
|||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
|
||||||
@ -10,11 +17,15 @@ export declare function success(): void;
|
|||||||
*/
|
*/
|
||||||
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
Normal file
896
applications/system/js_app/packages/fz-sdk/pnpm-lock.yaml
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,3 +1,9 @@
|
|||||||
|
/**
|
||||||
|
* Module for accessing the serial port
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
|
* @module
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Initializes the serial port
|
* @brief Initializes the serial port
|
||||||
*
|
*
|
||||||
@ -5,6 +11,7 @@
|
|||||||
*
|
*
|
||||||
* @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;
|
||||||
|
|
||||||
@ -16,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;
|
||||||
|
|
||||||
@ -27,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;
|
||||||
|
|
||||||
@ -42,6 +51,7 @@ 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;
|
||||||
|
|
||||||
@ -55,6 +65,7 @@ export declare function readln(timeout?: number): string;
|
|||||||
* wait forever.
|
* 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 readAny(timeout?: number): string | undefined;
|
export declare function readAny(timeout?: number): string | undefined;
|
||||||
|
|
||||||
@ -66,6 +77,7 @@ export declare function readAny(timeout?: number): string | undefined;
|
|||||||
* 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;
|
||||||
|
|
||||||
@ -89,10 +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
|
* @brief Deinitializes the serial port, allowing multiple initializations per script run
|
||||||
|
* @version Added in JS SDK 0.1
|
||||||
*/
|
*/
|
||||||
export declare function end(): void;
|
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",
|
||||||
|
]
|
||||||
|
}
|
19
applications/system/js_app/packages/fz-sdk/typedoc.json
Normal file
19
applications/system/js_app/packages/fz-sdk/typedoc.json
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://typedoc.org/schema.json",
|
||||||
|
"name": "Flipper Zero JS API",
|
||||||
|
"excludePrivate": true,
|
||||||
|
"entryPointStrategy": "expand",
|
||||||
|
"entryPoints": [
|
||||||
|
".",
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules"
|
||||||
|
],
|
||||||
|
"cleanOutputDir": true,
|
||||||
|
"out": "./docs",
|
||||||
|
"plugin": [
|
||||||
|
"typedoc-material-theme",
|
||||||
|
],
|
||||||
|
"readme": "./docs_readme.md",
|
||||||
|
"themeColor": "#ff8200",
|
||||||
|
}
|
@ -1,70 +0,0 @@
|
|||||||
type Lit = undefined | null | {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Subscription control interface
|
|
||||||
*/
|
|
||||||
export interface Subscription {
|
|
||||||
/**
|
|
||||||
* Cancels the subscription, preventing any future events managed by the
|
|
||||||
* subscription from firing
|
|
||||||
*/
|
|
||||||
cancel(): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Opaque event source identifier
|
|
||||||
*/
|
|
||||||
export type Contract<Item = undefined> = symbol;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
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
|
|
||||||
*/
|
|
||||||
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)
|
|
||||||
*/
|
|
||||||
export function run(): void | never;
|
|
||||||
/**
|
|
||||||
* Stops the event loop
|
|
||||||
*/
|
|
||||||
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
|
|
||||||
*/
|
|
||||||
export function timer(mode: "oneshot" | "periodic", interval: number): Contract;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Message queue
|
|
||||||
*/
|
|
||||||
export interface Queue<T> {
|
|
||||||
/**
|
|
||||||
* Message event
|
|
||||||
*/
|
|
||||||
input: Contract<T>;
|
|
||||||
/**
|
|
||||||
* Sends a message to the queue
|
|
||||||
* @param message message to send
|
|
||||||
*/
|
|
||||||
send(message: T): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a message queue
|
|
||||||
* @param length maximum queue capacity
|
|
||||||
*/
|
|
||||||
export function queue<T>(length: number): Queue<T>;
|
|
@ -1,14 +0,0 @@
|
|||||||
/**
|
|
||||||
* @brief Returns the device model
|
|
||||||
*/
|
|
||||||
export declare function getModel(): string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns the name of the virtual dolphin
|
|
||||||
*/
|
|
||||||
export declare function getName(): string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns the battery charge percentage
|
|
||||||
*/
|
|
||||||
export declare function getBatteryCharge(): number;
|
|
16
applications/system/js_app/types/gui/dialog.d.ts
vendored
16
applications/system/js_app/types/gui/dialog.d.ts
vendored
@ -1,16 +0,0 @@
|
|||||||
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;
|
|
@ -1,7 +0,0 @@
|
|||||||
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;
|
|
@ -1,7 +0,0 @@
|
|||||||
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;
|
|
@ -1,13 +0,0 @@
|
|||||||
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;
|
|
@ -1,14 +0,0 @@
|
|||||||
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;
|
|
@ -1,16 +0,0 @@
|
|||||||
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;
|
|
BIN
assets/icons/Archive/file_10px.png
Normal file
BIN
assets/icons/Archive/file_10px.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 95 B |
BIN
assets/icons/Settings/Alarm_47x39/frame_0.png
Normal file
BIN
assets/icons/Settings/Alarm_47x39/frame_0.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 262 B |
BIN
assets/icons/Settings/Alarm_47x39/frame_1.png
Normal file
BIN
assets/icons/Settings/Alarm_47x39/frame_1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 281 B |
BIN
assets/icons/Settings/Alarm_47x39/frame_2.png
Normal file
BIN
assets/icons/Settings/Alarm_47x39/frame_2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 291 B |
BIN
assets/icons/Settings/Alarm_47x39/frame_3.png
Normal file
BIN
assets/icons/Settings/Alarm_47x39/frame_3.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 279 B |
BIN
assets/icons/Settings/Alarm_47x39/frame_4.png
Normal file
BIN
assets/icons/Settings/Alarm_47x39/frame_4.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 262 B |
1
assets/icons/Settings/Alarm_47x39/frame_rate
Normal file
1
assets/icons/Settings/Alarm_47x39/frame_rate
Normal file
@ -0,0 +1 @@
|
|||||||
|
2
|
@ -20,7 +20,7 @@ To run the unit tests, follow these steps:
|
|||||||
3. Launch the CLI session and run the `unit_tests` command.
|
3. Launch the CLI session and run the `unit_tests` command.
|
||||||
|
|
||||||
**NOTE:** To run a particular test (and skip all others), specify its name as the command argument.
|
**NOTE:** To run a particular test (and skip all others), specify its name as the command argument.
|
||||||
See [test_index.c](https://github.com/flipperdevices/flipperzero-firmware/blob/dev/applications/debug/unit_tests/test_index.c) for the complete list of test names.
|
Test names match application names defined [here](https://github.com/flipperdevices/flipperzero-firmware/blob/dev/applications/debug/unit_tests/application.fam).
|
||||||
|
|
||||||
## Adding unit tests
|
## Adding unit tests
|
||||||
|
|
||||||
@ -28,7 +28,7 @@ See [test_index.c](https://github.com/flipperdevices/flipperzero-firmware/blob/d
|
|||||||
|
|
||||||
#### Entry point
|
#### Entry point
|
||||||
|
|
||||||
The common entry point for all tests is the [unit_tests](https://github.com/flipperdevices/flipperzero-firmware/tree/dev/applications/debug/unit_tests) app. Test-specific code is placed into an arbitrarily named subdirectory and is then called from the [test_index.c](https://github.com/flipperdevices/flipperzero-firmware/tree/dev/applications/debug/unit_tests/test_index.c) source file.
|
The common entry point for all tests is the [unit_tests](https://github.com/flipperdevices/flipperzero-firmware/tree/dev/applications/debug/unit_tests) app. Test-specific code is packaged as a `PLUGIN` app placed in a subdirectory of `tests` in the `unit_tests` mother-app and referenced in the common `application.fam`. Look at other tests for an example.
|
||||||
|
|
||||||
#### Test assets
|
#### Test assets
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user