Merge remote-tracking branch 'origin/dev' into astra/3934-alarm-improvements

This commit is contained in:
Aleksandr Kutuzov
2025-02-25 06:18:42 +09:00
211 changed files with 5305 additions and 917 deletions

View File

@@ -12,6 +12,7 @@
"SConstruct": "python",
"*.fam": "python"
},
"clangd.checkUpdates": false,
"clangd.path": "${workspaceFolder}/toolchain/current/bin/clangd@FBT_PLATFORM_EXECUTABLE_EXT@",
"clangd.arguments": [
"--query-driver=**/arm-none-eabi-*",

View File

@@ -2,6 +2,7 @@
#include <furi.h>
#include <furi_hal.h>
#include <stdarg.h>
#include <power/power_service/power.h>
void AccessorApp::run(void) {
AccessorEvent event;
@@ -35,16 +36,18 @@ AccessorApp::AccessorApp()
: text_store{0} {
notification = static_cast<NotificationApp*>(furi_record_open(RECORD_NOTIFICATION));
expansion = static_cast<Expansion*>(furi_record_open(RECORD_EXPANSION));
power = static_cast<Power*>(furi_record_open(RECORD_POWER));
onewire_host = onewire_host_alloc(&gpio_ibutton);
expansion_disable(expansion);
furi_hal_power_enable_otg();
power_enable_otg(power, true);
}
AccessorApp::~AccessorApp() {
furi_hal_power_disable_otg();
power_enable_otg(power, false);
expansion_enable(expansion);
furi_record_close(RECORD_EXPANSION);
furi_record_close(RECORD_NOTIFICATION);
furi_record_close(RECORD_POWER);
onewire_host_free(onewire_host);
}

View File

@@ -7,6 +7,7 @@
#include <one_wire/one_wire_host.h>
#include <notification/notification_messages.h>
#include <expansion/expansion.h>
#include <power/power_service/power.h>
class AccessorApp {
public:
@@ -53,4 +54,5 @@ private:
NotificationApp* notification;
Expansion* expansion;
Power* power;
};

View File

@@ -24,8 +24,49 @@ typedef enum {
CrashTestSubmenuAssertMessage,
CrashTestSubmenuCrash,
CrashTestSubmenuHalt,
CrashTestSubmenuHeapUnderflow,
CrashTestSubmenuHeapOverflow,
} CrashTestSubmenu;
static void crash_test_corrupt_heap_underflow(void) {
const size_t block_size = 1000;
const size_t underflow_size = 123;
uint8_t* block = malloc(block_size);
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstringop-overflow" // that's what we want!
memset(block - underflow_size, 0xDD, underflow_size); // -V769
#pragma GCC diagnostic pop
free(block); // should crash here (if compiled with DEBUG=1)
// If we got here, the heap wasn't able to detect our corruption and crash
furi_crash("Test failed, should've crashed with \"FreeRTOS Assert\" error");
}
static void crash_test_corrupt_heap_overflow(void) {
const size_t block_size = 1000;
const size_t overflow_size = 123;
uint8_t* block1 = malloc(block_size);
uint8_t* block2 = malloc(block_size);
memset(block2, 12, 34); // simulate use to avoid optimization // -V597 // -V1086
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstringop-overflow" // that's what we want!
memset(block1 + block_size, 0xDD, overflow_size); // -V769 // -V512
#pragma GCC diagnostic pop
uint8_t* block3 = malloc(block_size);
memset(block3, 12, 34); // simulate use to avoid optimization // -V597 // -V1086
free(block3); // should crash here (if compiled with DEBUG=1)
free(block2);
free(block1);
// If we got here, the heap wasn't able to detect our corruption and crash
furi_crash("Test failed, should've crashed with \"FreeRTOS Assert\" error");
}
static void crash_test_submenu_callback(void* context, uint32_t index) {
CrashTest* instance = (CrashTest*)context;
UNUSED(instance);
@@ -49,6 +90,12 @@ static void crash_test_submenu_callback(void* context, uint32_t index) {
case CrashTestSubmenuHalt:
furi_halt("Crash test: furi_halt");
break;
case CrashTestSubmenuHeapUnderflow:
crash_test_corrupt_heap_underflow();
break;
case CrashTestSubmenuHeapOverflow:
crash_test_corrupt_heap_overflow();
break;
default:
furi_crash();
}
@@ -94,6 +141,18 @@ CrashTest* crash_test_alloc(void) {
instance->submenu, "Crash", CrashTestSubmenuCrash, crash_test_submenu_callback, instance);
submenu_add_item(
instance->submenu, "Halt", CrashTestSubmenuHalt, crash_test_submenu_callback, instance);
submenu_add_item(
instance->submenu,
"Heap underflow",
CrashTestSubmenuHeapUnderflow,
crash_test_submenu_callback,
instance);
submenu_add_item(
instance->submenu,
"Heap overflow",
CrashTestSubmenuHeapOverflow,
crash_test_submenu_callback,
instance);
return instance;
}

View File

@@ -16,6 +16,9 @@
#define LINES_ON_SCREEN 6
#define COLUMNS_ON_SCREEN 21
#define DEFAULT_BAUD_RATE 230400
#define DEFAULT_DATA_BITS FuriHalSerialDataBits8
#define DEFAULT_PARITY FuriHalSerialParityNone
#define DEFAULT_STOP_BITS FuriHalSerialStopBits1
typedef struct UartDumpModel UartDumpModel;
@@ -49,11 +52,12 @@ typedef enum {
WorkerEventRxOverrunError = (1 << 4),
WorkerEventRxFramingError = (1 << 5),
WorkerEventRxNoiseError = (1 << 6),
WorkerEventRxParityError = (1 << 7),
} WorkerEventFlags;
#define WORKER_EVENTS_MASK \
(WorkerEventStop | WorkerEventRxData | WorkerEventRxIdle | WorkerEventRxOverrunError | \
WorkerEventRxFramingError | WorkerEventRxNoiseError)
WorkerEventRxFramingError | WorkerEventRxNoiseError | WorkerEventRxParityError)
const NotificationSequence sequence_notification = {
&message_display_backlight_on,
@@ -62,6 +66,13 @@ const NotificationSequence sequence_notification = {
NULL,
};
const NotificationSequence sequence_error = {
&message_display_backlight_on,
&message_red_255,
&message_delay_10,
NULL,
};
static void uart_echo_view_draw_callback(Canvas* canvas, void* _model) {
UartDumpModel* model = _model;
@@ -133,6 +144,9 @@ static void
if(event & FuriHalSerialRxEventOverrunError) {
flag |= WorkerEventRxOverrunError;
}
if(event & FuriHalSerialRxEventParityError) {
flag |= WorkerEventRxParityError;
}
furi_thread_flags_set(furi_thread_get_id(app->worker_thread), flag);
}
@@ -227,13 +241,21 @@ static int32_t uart_echo_worker(void* context) {
if(events & WorkerEventRxNoiseError) {
furi_hal_serial_tx(app->serial_handle, (uint8_t*)"\r\nDetect NE\r\n", 13);
}
if(events & WorkerEventRxParityError) {
furi_hal_serial_tx(app->serial_handle, (uint8_t*)"\r\nDetect PE\r\n", 13);
}
notification_message(app->notification, &sequence_error);
}
}
return 0;
}
static UartEchoApp* uart_echo_app_alloc(uint32_t baudrate) {
static UartEchoApp* uart_echo_app_alloc(
uint32_t baudrate,
FuriHalSerialDataBits data_bits,
FuriHalSerialParity parity,
FuriHalSerialStopBits stop_bits) {
UartEchoApp* app = malloc(sizeof(UartEchoApp));
app->rx_stream = furi_stream_buffer_alloc(2048, 1);
@@ -275,6 +297,7 @@ static UartEchoApp* uart_echo_app_alloc(uint32_t baudrate) {
app->serial_handle = furi_hal_serial_control_acquire(FuriHalSerialIdUsart);
furi_check(app->serial_handle);
furi_hal_serial_init(app->serial_handle, baudrate);
furi_hal_serial_configure_framing(app->serial_handle, data_bits, parity, stop_bits);
furi_hal_serial_async_rx_start(app->serial_handle, uart_echo_on_irq_cb, app, true);
@@ -318,19 +341,76 @@ static void uart_echo_app_free(UartEchoApp* app) {
free(app);
}
// silences "same-assignment" false positives in the arg parser below
// -V::1048
int32_t uart_echo_app(void* p) {
uint32_t baudrate = DEFAULT_BAUD_RATE;
FuriHalSerialDataBits data_bits = DEFAULT_DATA_BITS;
FuriHalSerialParity parity = DEFAULT_PARITY;
FuriHalSerialStopBits stop_bits = DEFAULT_STOP_BITS;
if(p) {
const char* baudrate_str = p;
if(strint_to_uint32(baudrate_str, NULL, &baudrate, 10) != StrintParseNoError) {
FURI_LOG_E(TAG, "Invalid baudrate: %s", baudrate_str);
baudrate = DEFAULT_BAUD_RATE;
// parse argument
char* parse_ptr = p;
bool parse_success = false;
do {
if(strint_to_uint32(parse_ptr, &parse_ptr, &baudrate, 10) != StrintParseNoError) break;
if(*(parse_ptr++) != '_') break;
uint16_t data_bits_int;
if(strint_to_uint16(parse_ptr, &parse_ptr, &data_bits_int, 10) != StrintParseNoError)
break;
if(data_bits_int == 6)
data_bits = FuriHalSerialDataBits6;
else if(data_bits_int == 7)
data_bits = FuriHalSerialDataBits7;
else if(data_bits_int == 8)
data_bits = FuriHalSerialDataBits8;
else if(data_bits_int == 9)
data_bits = FuriHalSerialDataBits9;
else
break;
char parity_char = *(parse_ptr++);
if(parity_char == 'N')
parity = FuriHalSerialParityNone;
else if(parity_char == 'E')
parity = FuriHalSerialParityEven;
else if(parity_char == 'O')
parity = FuriHalSerialParityOdd;
else
break;
uint16_t stop_bits_int;
if(strint_to_uint16(parse_ptr, &parse_ptr, &stop_bits_int, 10) != StrintParseNoError)
break;
if(stop_bits_int == 5)
stop_bits = FuriHalSerialStopBits0_5;
else if(stop_bits_int == 1)
stop_bits = FuriHalSerialStopBits1;
else if(stop_bits_int == 15)
stop_bits = FuriHalSerialStopBits1_5;
else if(stop_bits_int == 2)
stop_bits = FuriHalSerialStopBits2;
else
break;
parse_success = true;
} while(0);
if(!parse_success) {
FURI_LOG_I(
TAG,
"Couldn't parse baud rate and framing (%s). Applying defaults (%d_8N1)",
(const char*)p,
DEFAULT_BAUD_RATE);
}
}
FURI_LOG_I(TAG, "Using baudrate: %lu", baudrate);
UartEchoApp* app = uart_echo_app_alloc(baudrate);
UartEchoApp* app = uart_echo_app_alloc(baudrate, data_bits, parity, stop_bits);
view_dispatcher_run(app->view_dispatcher);
uart_echo_app_free(app);
return 0;

View File

@@ -12,4 +12,4 @@ 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]);
tests.assert_eq(3, flipper.jsSdkVersion[1]);

View File

@@ -446,6 +446,55 @@ static int32_t test_furi_event_loop_consumer(void* p) {
return 0;
}
typedef struct {
FuriEventLoop* event_loop;
FuriSemaphore* semaphore;
size_t counter;
} SelfUnsubTestTimerContext;
static void test_self_unsub_semaphore_callback(FuriEventLoopObject* object, void* context) {
furi_event_loop_unsubscribe(context, object); // shouldn't crash here
}
static void test_self_unsub_timer_callback(void* arg) {
SelfUnsubTestTimerContext* context = arg;
if(context->counter == 0) {
furi_semaphore_release(context->semaphore);
} else if(context->counter == 1) {
furi_event_loop_stop(context->event_loop);
}
context->counter++;
}
void test_furi_event_loop_self_unsubscribe(void) {
FuriEventLoop* event_loop = furi_event_loop_alloc();
FuriSemaphore* semaphore = furi_semaphore_alloc(1, 0);
furi_event_loop_subscribe_semaphore(
event_loop,
semaphore,
FuriEventLoopEventIn,
test_self_unsub_semaphore_callback,
event_loop);
SelfUnsubTestTimerContext timer_context = {
.event_loop = event_loop,
.semaphore = semaphore,
.counter = 0,
};
FuriEventLoopTimer* timer = furi_event_loop_timer_alloc(
event_loop, test_self_unsub_timer_callback, FuriEventLoopTimerTypePeriodic, &timer_context);
furi_event_loop_timer_start(timer, furi_ms_to_ticks(20));
furi_event_loop_run(event_loop);
furi_event_loop_timer_free(timer);
furi_semaphore_free(semaphore);
furi_event_loop_free(event_loop);
}
void test_furi_event_loop(void) {
TestFuriEventLoopData data = {};

View File

@@ -30,7 +30,9 @@ static size_t mock_in_cb(char* buffer, size_t size, FuriWait wait, void* context
}
void test_stdin(void) {
FuriThreadStdinReadCallback in_cb = furi_thread_get_stdin_callback();
FuriThreadStdinReadCallback in_cb;
void* in_ctx;
furi_thread_get_stdin_callback(&in_cb, &in_ctx);
furi_thread_set_stdin_callback(mock_in_cb, CONTEXT_MAGIC);
char buf[256];
@@ -63,13 +65,14 @@ void test_stdin(void) {
fgets(buf, sizeof(buf), stdin);
mu_assert_string_eq(" World!\n", buf);
furi_thread_set_stdin_callback(in_cb, CONTEXT_MAGIC);
furi_thread_set_stdin_callback(in_cb, in_ctx);
}
// stdout
static FuriString* mock_out;
FuriThreadStdoutWriteCallback original_out_cb;
static FuriThreadStdoutWriteCallback original_out_cb;
static void* original_out_ctx;
static void mock_out_cb(const char* data, size_t size, void* context) {
furi_check(context == CONTEXT_MAGIC);
@@ -83,7 +86,7 @@ static void assert_and_clear_mock_out(const char* expected) {
// return the original stdout callback for the duration of the check
// if the check fails, we don't want the error to end up in our buffer,
// we want to be able to see it!
furi_thread_set_stdout_callback(original_out_cb, CONTEXT_MAGIC);
furi_thread_set_stdout_callback(original_out_cb, original_out_ctx);
mu_assert_string_eq(expected, furi_string_get_cstr(mock_out));
furi_thread_set_stdout_callback(mock_out_cb, CONTEXT_MAGIC);
@@ -91,7 +94,7 @@ static void assert_and_clear_mock_out(const char* expected) {
}
void test_stdout(void) {
original_out_cb = furi_thread_get_stdout_callback();
furi_thread_get_stdout_callback(&original_out_cb, &original_out_ctx);
furi_thread_set_stdout_callback(mock_out_cb, CONTEXT_MAGIC);
mock_out = furi_string_alloc();
@@ -104,5 +107,5 @@ void test_stdout(void) {
assert_and_clear_mock_out("Hello!");
furi_string_free(mock_out);
furi_thread_set_stdout_callback(original_out_cb, CONTEXT_MAGIC);
furi_thread_set_stdout_callback(original_out_cb, original_out_ctx);
}

View File

@@ -8,6 +8,7 @@ void test_furi_concurrent_access(void);
void test_furi_pubsub(void);
void test_furi_memmgr(void);
void test_furi_event_loop(void);
void test_furi_event_loop_self_unsubscribe(void);
void test_errno_saving(void);
void test_furi_primitives(void);
void test_stdin(void);
@@ -46,6 +47,10 @@ MU_TEST(mu_test_furi_event_loop) {
test_furi_event_loop();
}
MU_TEST(mu_test_furi_event_loop_self_unsubscribe) {
test_furi_event_loop_self_unsubscribe();
}
MU_TEST(mu_test_errno_saving) {
test_errno_saving();
}
@@ -68,6 +73,7 @@ MU_TEST_SUITE(test_suite) {
MU_RUN_TEST(mu_test_furi_pubsub);
MU_RUN_TEST(mu_test_furi_memmgr);
MU_RUN_TEST(mu_test_furi_event_loop);
MU_RUN_TEST(mu_test_furi_event_loop_self_unsubscribe);
MU_RUN_TEST(mu_test_stdio);
MU_RUN_TEST(mu_test_errno_saving);
MU_RUN_TEST(mu_test_furi_primitives);

View File

@@ -262,7 +262,7 @@ static void mf_ultralight_reader_test(const char* path) {
nfc_listener_start(mfu_listener, NULL, NULL);
MfUltralightData* mfu_data = mf_ultralight_alloc();
MfUltralightError error = mf_ultralight_poller_sync_read_card(poller, mfu_data);
MfUltralightError error = mf_ultralight_poller_sync_read_card(poller, mfu_data, NULL);
mu_assert(error == MfUltralightErrorNone, "mf_ultralight_poller_sync_read_card() failed");
nfc_listener_stop(mfu_listener);
@@ -315,7 +315,7 @@ MU_TEST(ntag_213_locked_reader) {
nfc_listener_start(mfu_listener, NULL, NULL);
MfUltralightData* mfu_data = mf_ultralight_alloc();
MfUltralightError error = mf_ultralight_poller_sync_read_card(poller, mfu_data);
MfUltralightError error = mf_ultralight_poller_sync_read_card(poller, mfu_data, NULL);
mu_assert(error == MfUltralightErrorNone, "mf_ultralight_poller_sync_read_card() failed");
nfc_listener_stop(mfu_listener);
@@ -353,7 +353,7 @@ static void mf_ultralight_write(void) {
MfUltralightData* mfu_data = mf_ultralight_alloc();
// Initial read
MfUltralightError error = mf_ultralight_poller_sync_read_card(poller, mfu_data);
MfUltralightError error = mf_ultralight_poller_sync_read_card(poller, mfu_data, NULL);
mu_assert(error == MfUltralightErrorNone, "mf_ultralight_poller_sync_read_card() failed");
mu_assert(
@@ -371,7 +371,7 @@ static void mf_ultralight_write(void) {
}
// Verification read
error = mf_ultralight_poller_sync_read_card(poller, mfu_data);
error = mf_ultralight_poller_sync_read_card(poller, mfu_data, NULL);
mu_assert(error == MfUltralightErrorNone, "mf_ultralight_poller_sync_read_card() failed");
nfc_listener_stop(mfu_listener);

View File

@@ -22,7 +22,7 @@
#include <one_wire/maxim_crc.h>
#include <one_wire/one_wire_host.h>
#include <furi_hal_power.h>
#include <power/power_service/power.h>
#define UPDATE_PERIOD_MS 1000UL
#define TEXT_STORE_SIZE 64U
@@ -76,6 +76,7 @@ typedef struct {
FuriThread* reader_thread;
FuriMessageQueue* event_queue;
OneWireHost* onewire;
Power* power;
float temp_celsius;
bool has_device;
} ExampleThermoContext;
@@ -273,7 +274,7 @@ static void example_thermo_input_callback(InputEvent* event, void* ctx) {
/* Starts the reader thread and handles the input */
static void example_thermo_run(ExampleThermoContext* context) {
/* Enable power on external pins */
furi_hal_power_enable_otg();
power_enable_otg(context->power, true);
/* Configure the hardware in host mode */
onewire_host_start(context->onewire);
@@ -309,7 +310,7 @@ static void example_thermo_run(ExampleThermoContext* context) {
onewire_host_stop(context->onewire);
/* Disable power on external pins */
furi_hal_power_disable_otg();
power_enable_otg(context->power, false);
}
/******************** Initialisation & startup *****************************/
@@ -334,6 +335,8 @@ static ExampleThermoContext* example_thermo_context_alloc(void) {
context->onewire = onewire_host_alloc(&THERMO_GPIO_PIN);
context->power = furi_record_open(RECORD_POWER);
return context;
}
@@ -348,6 +351,7 @@ static void example_thermo_context_free(ExampleThermoContext* context) {
view_port_free(context->view_port);
furi_record_close(RECORD_GUI);
furi_record_close(RECORD_POWER);
}
/* The application's entry point. Execution starts from here. */

View File

@@ -37,6 +37,31 @@ bool hid_usb_kb_release(void* inst, uint16_t button) {
return furi_hal_hid_kb_release(button);
}
bool hid_usb_mouse_press(void* inst, uint8_t button) {
UNUSED(inst);
return furi_hal_hid_mouse_press(button);
}
bool hid_usb_mouse_release(void* inst, uint8_t button) {
UNUSED(inst);
return furi_hal_hid_mouse_release(button);
}
bool hid_usb_mouse_scroll(void* inst, int8_t delta) {
UNUSED(inst);
return furi_hal_hid_mouse_scroll(delta);
}
bool hid_usb_mouse_move(void* inst, int8_t dx, int8_t dy) {
UNUSED(inst);
return furi_hal_hid_mouse_move(dx, dy);
}
bool hid_usb_mouse_release_all(void* inst) {
UNUSED(inst);
return furi_hal_hid_mouse_release(0);
}
bool hid_usb_consumer_press(void* inst, uint16_t button) {
UNUSED(inst);
return furi_hal_hid_consumer_key_press(button);
@@ -51,6 +76,7 @@ bool hid_usb_release_all(void* inst) {
UNUSED(inst);
bool state = furi_hal_hid_kb_release_all();
state &= furi_hal_hid_consumer_key_release_all();
state &= hid_usb_mouse_release_all(inst);
return state;
}
@@ -67,6 +93,10 @@ static const BadUsbHidApi hid_api_usb = {
.kb_press = hid_usb_kb_press,
.kb_release = hid_usb_kb_release,
.mouse_press = hid_usb_mouse_press,
.mouse_release = hid_usb_mouse_release,
.mouse_scroll = hid_usb_mouse_scroll,
.mouse_move = hid_usb_mouse_move,
.consumer_press = hid_usb_consumer_press,
.consumer_release = hid_usb_consumer_release,
.release_all = hid_usb_release_all,
@@ -157,6 +187,27 @@ bool hid_ble_kb_release(void* inst, uint16_t button) {
return ble_profile_hid_kb_release(ble_hid->profile, button);
}
bool hid_ble_mouse_press(void* inst, uint8_t button) {
BleHidInstance* ble_hid = inst;
furi_assert(ble_hid);
return ble_profile_hid_mouse_press(ble_hid->profile, button);
}
bool hid_ble_mouse_release(void* inst, uint8_t button) {
BleHidInstance* ble_hid = inst;
furi_assert(ble_hid);
return ble_profile_hid_mouse_release(ble_hid->profile, button);
}
bool hid_ble_mouse_scroll(void* inst, int8_t delta) {
BleHidInstance* ble_hid = inst;
furi_assert(ble_hid);
return ble_profile_hid_mouse_scroll(ble_hid->profile, delta);
}
bool hid_ble_mouse_move(void* inst, int8_t dx, int8_t dy) {
BleHidInstance* ble_hid = inst;
furi_assert(ble_hid);
return ble_profile_hid_mouse_move(ble_hid->profile, dx, dy);
}
bool hid_ble_consumer_press(void* inst, uint16_t button) {
BleHidInstance* ble_hid = inst;
furi_assert(ble_hid);
@@ -174,6 +225,7 @@ bool hid_ble_release_all(void* inst) {
furi_assert(ble_hid);
bool state = ble_profile_hid_kb_release_all(ble_hid->profile);
state &= ble_profile_hid_consumer_key_release_all(ble_hid->profile);
state &= ble_profile_hid_mouse_release_all(ble_hid->profile);
return state;
}
@@ -191,6 +243,10 @@ static const BadUsbHidApi hid_api_ble = {
.kb_press = hid_ble_kb_press,
.kb_release = hid_ble_kb_release,
.mouse_press = hid_ble_mouse_press,
.mouse_release = hid_ble_mouse_release,
.mouse_scroll = hid_ble_mouse_scroll,
.mouse_move = hid_ble_mouse_move,
.consumer_press = hid_ble_consumer_press,
.consumer_release = hid_ble_consumer_release,
.release_all = hid_ble_release_all,

View File

@@ -20,6 +20,10 @@ typedef struct {
bool (*kb_press)(void* inst, uint16_t button);
bool (*kb_release)(void* inst, uint16_t button);
bool (*mouse_press)(void* inst, uint8_t button);
bool (*mouse_release)(void* inst, uint8_t button);
bool (*mouse_scroll)(void* inst, int8_t delta);
bool (*mouse_move)(void* inst, int8_t dx, int8_t dy);
bool (*consumer_press)(void* inst, uint16_t button);
bool (*consumer_release)(void* inst, uint16_t button);
bool (*release_all)(void* inst);

View File

@@ -193,8 +193,16 @@ static int32_t ducky_parse_line(BadUsbScript* bad_usb, FuriString* line) {
return cmd_result;
}
// Mouse Keys
uint16_t key = ducky_get_mouse_keycode_by_name(line_tmp);
if(key != HID_MOUSE_INVALID) {
bad_usb->hid->mouse_press(bad_usb->hid_inst, key);
bad_usb->hid->mouse_release(bad_usb->hid_inst, key);
return 0;
}
// Special keys + modifiers
uint16_t key = ducky_get_keycode(bad_usb, line_tmp, false);
key = ducky_get_keycode(bad_usb, line_tmp, false);
if(key == HID_KEYBOARD_NONE) {
return ducky_error(bad_usb, "No keycode defined for %s", line_tmp);
}

View File

@@ -1,4 +1,5 @@
#include <furi_hal.h>
#include <lib/toolbox/strint.h>
#include "ducky_script.h"
#include "ducky_script_i.h"
@@ -124,34 +125,58 @@ static int32_t ducky_fnc_altstring(BadUsbScript* bad_usb, const char* line, int3
static int32_t ducky_fnc_hold(BadUsbScript* bad_usb, const char* line, int32_t param) {
UNUSED(param);
line = &line[ducky_get_command_len(line) + 1];
uint16_t key = ducky_get_keycode(bad_usb, line, true);
if(key == HID_KEYBOARD_NONE) {
return ducky_error(bad_usb, "No keycode defined for %s", line);
}
bad_usb->key_hold_nb++;
if(bad_usb->key_hold_nb > (HID_KB_MAX_KEYS - 1)) {
return ducky_error(bad_usb, "Too many keys are hold");
return ducky_error(bad_usb, "Too many keys are held");
}
bad_usb->hid->kb_press(bad_usb->hid_inst, key);
return 0;
// Handle Mouse Keys here
uint16_t key = ducky_get_mouse_keycode_by_name(line);
if(key != HID_MOUSE_NONE) {
bad_usb->key_hold_nb++;
bad_usb->hid->mouse_press(bad_usb->hid_inst, key);
return 0;
}
// Handle Keyboard keys here
key = ducky_get_keycode(bad_usb, line, true);
if(key != HID_KEYBOARD_NONE) {
bad_usb->key_hold_nb++;
bad_usb->hid->kb_press(bad_usb->hid_inst, key);
return 0;
}
// keyboard and mouse were none
return ducky_error(bad_usb, "Unknown keycode for %s", line);
}
static int32_t ducky_fnc_release(BadUsbScript* bad_usb, const char* line, int32_t param) {
UNUSED(param);
line = &line[ducky_get_command_len(line) + 1];
uint16_t key = ducky_get_keycode(bad_usb, line, true);
if(key == HID_KEYBOARD_NONE) {
return ducky_error(bad_usb, "No keycode defined for %s", line);
}
if(bad_usb->key_hold_nb == 0) {
return ducky_error(bad_usb, "No keys are hold");
return ducky_error(bad_usb, "No keys are held");
}
bad_usb->key_hold_nb--;
bad_usb->hid->kb_release(bad_usb->hid_inst, key);
return 0;
// Handle Mouse Keys here
uint16_t key = ducky_get_mouse_keycode_by_name(line);
if(key != HID_MOUSE_NONE) {
bad_usb->key_hold_nb--;
bad_usb->hid->mouse_release(bad_usb->hid_inst, key);
return 0;
}
//Handle Keyboard Keys here
key = ducky_get_keycode(bad_usb, line, true);
if(key != HID_KEYBOARD_NONE) {
bad_usb->key_hold_nb--;
bad_usb->hid->kb_release(bad_usb->hid_inst, key);
return 0;
}
// keyboard and mouse were none
return ducky_error(bad_usb, "No keycode defined for %s", line);
}
static int32_t ducky_fnc_media(BadUsbScript* bad_usb, const char* line, int32_t param) {
@@ -191,6 +216,43 @@ static int32_t ducky_fnc_waitforbutton(BadUsbScript* bad_usb, const char* line,
return SCRIPT_STATE_WAIT_FOR_BTN;
}
static int32_t ducky_fnc_mouse_scroll(BadUsbScript* bad_usb, const char* line, int32_t param) {
UNUSED(param);
line = &line[strcspn(line, " ") + 1];
int32_t mouse_scroll_dist = 0;
if(strint_to_int32(line, NULL, &mouse_scroll_dist, 10) != StrintParseNoError) {
return ducky_error(bad_usb, "Invalid Number %s", line);
}
bad_usb->hid->mouse_scroll(bad_usb->hid_inst, mouse_scroll_dist);
return 0;
}
static int32_t ducky_fnc_mouse_move(BadUsbScript* bad_usb, const char* line, int32_t param) {
UNUSED(param);
line = &line[strcspn(line, " ") + 1];
int32_t mouse_move_x = 0;
int32_t mouse_move_y = 0;
if(strint_to_int32(line, NULL, &mouse_move_x, 10) != StrintParseNoError) {
return ducky_error(bad_usb, "Invalid Number %s", line);
}
line = &line[strcspn(line, " ") + 1];
if(strint_to_int32(line, NULL, &mouse_move_y, 10) != StrintParseNoError) {
return ducky_error(bad_usb, "Invalid Number %s", line);
}
bad_usb->hid->mouse_move(bad_usb->hid_inst, mouse_move_x, mouse_move_y);
return 0;
}
static const DuckyCmd ducky_commands[] = {
{"REM", NULL, -1},
{"ID", NULL, -1},
@@ -213,6 +275,10 @@ static const DuckyCmd ducky_commands[] = {
{"WAIT_FOR_BUTTON_PRESS", ducky_fnc_waitforbutton, -1},
{"MEDIA", ducky_fnc_media, -1},
{"GLOBE", ducky_fnc_globe, -1},
{"MOUSEMOVE", ducky_fnc_mouse_move, -1},
{"MOUSE_MOVE", ducky_fnc_mouse_move, -1},
{"MOUSESCROLL", ducky_fnc_mouse_scroll, -1},
{"MOUSE_SCROLL", ducky_fnc_mouse_scroll, -1},
};
#define TAG "BadUsb"

View File

@@ -18,6 +18,9 @@ extern "C" {
#define FILE_BUFFER_LEN 16
#define HID_MOUSE_INVALID 0
#define HID_MOUSE_NONE 0
struct BadUsbScript {
FuriHalUsbHidConfig hid_cfg;
const BadUsbHidApi* hid;
@@ -55,6 +58,8 @@ uint16_t ducky_get_keycode_by_name(const char* param);
uint16_t ducky_get_media_keycode_by_name(const char* param);
uint8_t ducky_get_mouse_keycode_by_name(const char* param);
bool ducky_get_number(const char* param, uint32_t* val);
void ducky_numlock_on(BadUsbScript* bad_usb);

View File

@@ -108,6 +108,17 @@ static const DuckyKey ducky_media_keys[] = {
{"BRIGHT_DOWN", HID_CONSUMER_BRIGHTNESS_DECREMENT},
};
static const DuckyKey ducky_mouse_keys[] = {
{"LEFTCLICK", HID_MOUSE_BTN_LEFT},
{"LEFT_CLICK", HID_MOUSE_BTN_LEFT},
{"RIGHTCLICK", HID_MOUSE_BTN_RIGHT},
{"RIGHT_CLICK", HID_MOUSE_BTN_RIGHT},
{"MIDDLECLICK", HID_MOUSE_BTN_WHEEL},
{"MIDDLE_CLICK", HID_MOUSE_BTN_WHEEL},
{"WHEELCLICK", HID_MOUSE_BTN_WHEEL},
{"WHEEL_CLICK", HID_MOUSE_BTN_WHEEL},
};
uint16_t ducky_get_keycode_by_name(const char* param) {
for(size_t i = 0; i < COUNT_OF(ducky_keys); i++) {
size_t key_cmd_len = strlen(ducky_keys[i].name);
@@ -131,3 +142,15 @@ uint16_t ducky_get_media_keycode_by_name(const char* param) {
return HID_CONSUMER_UNASSIGNED;
}
uint8_t ducky_get_mouse_keycode_by_name(const char* param) {
for(size_t i = 0; i < COUNT_OF(ducky_mouse_keys); i++) {
size_t key_cmd_len = strlen(ducky_mouse_keys[i].name);
if((strncmp(param, ducky_mouse_keys[i].name, key_cmd_len) == 0) &&
(ducky_is_line_end(param[key_cmd_len]))) {
return ducky_mouse_keys[i].keycode;
}
}
return HID_MOUSE_INVALID;
}

View File

@@ -0,0 +1,46 @@
ID 1234:abcd Generic:USB Keyboard
REM Declare ourselves as a generic usb keyboard
REM You can override this to use something else
REM Check the `lsusb` command to know your own devices IDs
DEFAULT_DELAY 200
DEFAULT_STRING_DELAY 100
DELAY 1000
REM Test all mouse functions
LEFTCLICK
RIGHTCLICK
MIDDLECLICK
DELAY 1000
MOUSEMOVE -10 0
REPEAT 20
MOUSEMOVE 0 10
REPEAT 20
MOUSEMOVE 10 0
REPEAT 20
MOUSEMOVE 0 -10
REPEAT 20
DELAY 1000
MOUSESCROLL -50
MOUSESCROLL 50
DELAY 1000
REM Verify Mouse hold working
HOLD LEFTCLICK
DELAY 2000
RELEASE LEFTCLICK
DELAY 1000
REM Verify KB hold working
HOLD M
DELAY 2000
RELEASE M
ENTER

View File

@@ -30,6 +30,8 @@ GpioApp* gpio_app_alloc(void) {
app->gui = furi_record_open(RECORD_GUI);
app->gpio_items = gpio_items_alloc();
app->power = furi_record_open(RECORD_POWER);
app->view_dispatcher = view_dispatcher_alloc();
app->scene_manager = scene_manager_alloc(&gpio_scene_handlers, app);
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
@@ -100,6 +102,7 @@ void gpio_app_free(GpioApp* app) {
// Close records
furi_record_close(RECORD_GUI);
furi_record_close(RECORD_NOTIFICATION);
furi_record_close(RECORD_POWER);
expansion_enable(app->expansion);
furi_record_close(RECORD_EXPANSION);

View File

@@ -5,6 +5,7 @@
#include "scenes/gpio_scene.h"
#include "gpio_custom_event.h"
#include "usb_uart_bridge.h"
#include <power/power_service/power.h>
#include <gui/gui.h>
#include <gui/view_dispatcher.h>
@@ -27,6 +28,7 @@ struct GpioApp {
SceneManager* scene_manager;
Widget* widget;
DialogEx* dialog;
Power* power;
VariableItemList* var_item_list;
VariableItem* var_item_flow;

View File

@@ -60,7 +60,7 @@ void gpio_scene_start_on_enter(void* context) {
GpioOtgSettingsNum,
gpio_scene_start_var_list_change_callback,
app);
if(furi_hal_power_is_otg_enabled()) {
if(power_is_otg_enabled(app->power)) {
variable_item_set_current_value_index(item, GpioOtgOn);
variable_item_set_current_value_text(item, gpio_otg_text[GpioOtgOn]);
} else {
@@ -80,9 +80,9 @@ bool gpio_scene_start_on_event(void* context, SceneManagerEvent event) {
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == GpioStartEventOtgOn) {
furi_hal_power_enable_otg();
power_enable_otg(app->power, true);
} else if(event.event == GpioStartEventOtgOff) {
furi_hal_power_disable_otg();
power_enable_otg(app->power, false);
} else if(event.event == GpioStartEventManualControl) {
scene_manager_set_scene_state(app->scene_manager, GpioSceneStart, GpioItemTest);
scene_manager_next_scene(app->scene_manager, GpioSceneTest);

View File

@@ -183,7 +183,7 @@ static int32_t usb_uart_worker(void* context) {
usb_uart->usb_mutex = furi_mutex_alloc(FuriMutexTypeNormal);
usb_uart->tx_thread =
furi_thread_alloc_ex("UsbUartTxWorker", 512, usb_uart_tx_thread, usb_uart);
furi_thread_alloc_ex("UsbUartTxWorker", 768, usb_uart_tx_thread, usb_uart);
usb_uart_vcp_init(usb_uart, usb_uart->cfg.vcp_ch);
usb_uart_serial_init(usb_uart, usb_uart->cfg.uart_ch);
@@ -288,8 +288,6 @@ static int32_t usb_uart_worker(void* context) {
usb_uart_update_ctrl_lines(usb_uart);
}
}
usb_uart_vcp_deinit(usb_uart, usb_uart->cfg.vcp_ch);
usb_uart_serial_deinit(usb_uart);
furi_hal_gpio_init(USB_USART_DE_RE_PIN, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
@@ -302,6 +300,9 @@ static int32_t usb_uart_worker(void* context) {
furi_thread_join(usb_uart->tx_thread);
furi_thread_free(usb_uart->tx_thread);
usb_uart_vcp_deinit(usb_uart, usb_uart->cfg.vcp_ch);
usb_uart_serial_deinit(usb_uart);
furi_stream_buffer_free(usb_uart->rx_stream);
furi_mutex_free(usb_uart->usb_mutex);
furi_semaphore_free(usb_uart->tx_sem);

View File

@@ -1,6 +1,6 @@
#include "infrared_app_i.h"
#include <furi_hal_power.h>
#include <power/power_service/power.h>
#include <string.h>
#include <toolbox/path.h>
@@ -501,12 +501,12 @@ void infrared_set_tx_pin(InfraredApp* infrared, FuriHalInfraredTxPin tx_pin) {
}
void infrared_enable_otg(InfraredApp* infrared, bool enable) {
if(enable) {
furi_hal_power_enable_otg();
} else {
furi_hal_power_disable_otg();
}
Power* power = furi_record_open(RECORD_POWER);
power_enable_otg(power, enable);
infrared->app_state.is_otg_enabled = enable;
furi_record_close(RECORD_POWER);
}
static void infrared_load_settings(InfraredApp* infrared) {

View File

@@ -2,26 +2,61 @@
#include <stdlib.h>
#include <m-dict.h>
#include <m-array.h>
#include <flipper_format/flipper_format.h>
#include "infrared_signal.h"
ARRAY_DEF(SignalPositionArray, size_t, M_DEFAULT_OPLIST);
typedef struct {
uint32_t index;
uint32_t count;
size_t index;
SignalPositionArray_t signals;
} InfraredBruteForceRecord;
static inline void ir_bf_record_init(InfraredBruteForceRecord* record) {
record->index = 0;
SignalPositionArray_init(record->signals);
}
#define IR_BF_RECORD_INIT(r) (ir_bf_record_init(&(r)))
static inline void
ir_bf_record_init_set(InfraredBruteForceRecord* dest, const InfraredBruteForceRecord* src) {
dest->index = src->index;
SignalPositionArray_init_set(dest->signals, src->signals);
}
#define IR_BF_RECORD_INIT_SET(d, s) (ir_bf_record_init_set(&(d), &(s)))
static inline void
ir_bf_record_set(InfraredBruteForceRecord* dest, const InfraredBruteForceRecord* src) {
dest->index = src->index;
SignalPositionArray_set(dest->signals, src->signals);
}
#define IR_BF_RECORD_SET(d, s) (ir_bf_record_set(&(d), &(s)))
static inline void ir_bf_record_clear(InfraredBruteForceRecord* record) {
SignalPositionArray_clear(record->signals);
}
#define IR_BF_RECORD_CLEAR(r) (ir_bf_record_clear(&(r)))
#define IR_BF_RECORD_OPLIST \
(INIT(IR_BF_RECORD_INIT), \
INIT_SET(IR_BF_RECORD_INIT_SET), \
SET(IR_BF_RECORD_SET), \
CLEAR(IR_BF_RECORD_CLEAR))
DICT_DEF2(
InfraredBruteForceRecordDict,
FuriString*,
FURI_STRING_OPLIST,
InfraredBruteForceRecord,
M_POD_OPLIST);
IR_BF_RECORD_OPLIST);
struct InfraredBruteForce {
FlipperFormat* ff;
const char* db_filename;
FuriString* current_record_name;
InfraredBruteForceRecord current_record;
InfraredSignal* current_signal;
InfraredBruteForceRecordDict_t records;
bool is_started;
@@ -39,6 +74,7 @@ InfraredBruteForce* infrared_brute_force_alloc(void) {
}
void infrared_brute_force_free(InfraredBruteForce* brute_force) {
furi_check(brute_force);
furi_assert(!brute_force->is_started);
InfraredBruteForceRecordDict_clear(brute_force->records);
furi_string_free(brute_force->current_record_name);
@@ -46,11 +82,13 @@ void infrared_brute_force_free(InfraredBruteForce* brute_force) {
}
void infrared_brute_force_set_db_filename(InfraredBruteForce* brute_force, const char* db_filename) {
furi_check(brute_force);
furi_assert(!brute_force->is_started);
brute_force->db_filename = db_filename;
}
InfraredErrorCode infrared_brute_force_calculate_messages(InfraredBruteForce* brute_force) {
furi_check(brute_force);
furi_assert(!brute_force->is_started);
furi_assert(brute_force->db_filename);
InfraredErrorCode error = InfraredErrorCodeNone;
@@ -66,19 +104,19 @@ InfraredErrorCode infrared_brute_force_calculate_messages(InfraredBruteForce* br
break;
}
bool signals_valid = false;
size_t signal_start = flipper_format_tell(ff);
bool signal_valid = false;
while(infrared_signal_read_name(ff, signal_name) == InfraredErrorCodeNone) {
error = infrared_signal_read_body(signal, ff);
signals_valid = (!INFRARED_ERROR_PRESENT(error)) && infrared_signal_is_valid(signal);
if(!signals_valid) break;
signal_valid = (!INFRARED_ERROR_PRESENT(error)) && infrared_signal_is_valid(signal);
if(!signal_valid) break;
InfraredBruteForceRecord* record =
InfraredBruteForceRecordDict_get(brute_force->records, signal_name);
if(record) { //-V547
++(record->count);
}
furi_assert(record);
SignalPositionArray_push_back(record->signals, signal_start);
}
if(!signals_valid) break;
if(!signal_valid) break;
} while(false);
infrared_signal_free(signal);
@@ -93,6 +131,7 @@ bool infrared_brute_force_start(
InfraredBruteForce* brute_force,
uint32_t index,
uint32_t* record_count) {
furi_check(brute_force);
furi_assert(!brute_force->is_started);
bool success = false;
*record_count = 0;
@@ -103,9 +142,10 @@ bool infrared_brute_force_start(
InfraredBruteForceRecordDict_next(it)) {
const InfraredBruteForceRecordDict_itref_t* record = InfraredBruteForceRecordDict_cref(it);
if(record->value.index == index) {
*record_count = record->value.count;
*record_count = SignalPositionArray_size(record->value.signals);
if(*record_count) {
furi_string_set(brute_force->current_record_name, record->key);
brute_force->current_record = record->value;
}
break;
}
@@ -124,10 +164,12 @@ bool infrared_brute_force_start(
}
bool infrared_brute_force_is_started(const InfraredBruteForce* brute_force) {
furi_check(brute_force);
return brute_force->is_started;
}
void infrared_brute_force_stop(InfraredBruteForce* brute_force) {
furi_check(brute_force);
furi_assert(brute_force->is_started);
furi_string_reset(brute_force->current_record_name);
infrared_signal_free(brute_force->current_signal);
@@ -138,25 +180,32 @@ void infrared_brute_force_stop(InfraredBruteForce* brute_force) {
furi_record_close(RECORD_STORAGE);
}
bool infrared_brute_force_send_next(InfraredBruteForce* brute_force) {
bool infrared_brute_force_send(InfraredBruteForce* brute_force, uint32_t signal_index) {
furi_check(brute_force);
furi_assert(brute_force->is_started);
const bool success = infrared_signal_search_by_name_and_read(
brute_force->current_signal,
brute_force->ff,
furi_string_get_cstr(brute_force->current_record_name)) ==
InfraredErrorCodeNone;
if(success) {
infrared_signal_transmit(brute_force->current_signal);
}
return success;
if(signal_index >= SignalPositionArray_size(brute_force->current_record.signals)) return false;
size_t signal_start =
*SignalPositionArray_cget(brute_force->current_record.signals, signal_index);
if(!flipper_format_seek(brute_force->ff, signal_start, FlipperFormatOffsetFromStart))
return false;
if(INFRARED_ERROR_PRESENT(
infrared_signal_read_body(brute_force->current_signal, brute_force->ff)))
return false;
infrared_signal_transmit(brute_force->current_signal);
return true;
}
void infrared_brute_force_add_record(
InfraredBruteForce* brute_force,
uint32_t index,
const char* name) {
InfraredBruteForceRecord value = {.index = index, .count = 0};
InfraredBruteForceRecord value;
ir_bf_record_init(&value);
value.index = index;
FuriString* key;
key = furi_string_alloc_set(name);
InfraredBruteForceRecordDict_set_at(brute_force->records, key, value);

View File

@@ -78,18 +78,16 @@ bool infrared_brute_force_is_started(const InfraredBruteForce* brute_force);
void infrared_brute_force_stop(InfraredBruteForce* brute_force);
/**
* @brief Send the next signal from the chosen category.
*
* This function is called repeatedly until no more signals are left
* in the chosen signal category.
*
* @warning Transmission must be started first by calling infrared_brute_force_start()
* before calling this function.
*
* @param[in,out] brute_force pointer to the instance to be used.
* @returns true if the next signal existed and could be transmitted, false otherwise.
* @brief Send an arbitrary signal from the chosen category.
*
* @param[in] brute_force pointer to the instance
* @param signal_index the index of the signal within the category, must be
* between 0 and `record_count` as told by
* `infrared_brute_force_start`
*
* @returns true on success, false otherwise
*/
bool infrared_brute_force_send_next(InfraredBruteForce* brute_force);
bool infrared_brute_force_send(InfraredBruteForce* brute_force, uint32_t signal_index);
/**
* @brief Add a signal category to an InfraredBruteForce instance's dictionary.

View File

@@ -475,25 +475,24 @@ static void
break;
}
uint32_t record_count;
uint32_t signal_count, current_signal = 0;
bool running = infrared_brute_force_start(
brute_force, INFRARED_BRUTE_FORCE_DUMMY_INDEX, &record_count);
brute_force, INFRARED_BRUTE_FORCE_DUMMY_INDEX, &signal_count);
if(record_count <= 0) {
if(signal_count <= 0) {
printf("Invalid signal name.\r\n");
break;
}
printf("Sending %lu signal(s)...\r\n", record_count);
printf("Sending %lu signal(s)...\r\n", signal_count);
printf("Press Ctrl-C to stop.\r\n");
int records_sent = 0;
while(running) {
running = infrared_brute_force_send_next(brute_force);
running = infrared_brute_force_send(brute_force, current_signal);
if(cli_cmd_interrupt_received(cli)) break;
printf("\r%d%% complete.", (int)((float)records_sent++ / (float)record_count * 100));
printf("\r%d%% complete.", (int)((float)current_signal++ / (float)signal_count * 100));
fflush(stdout);
}

View File

@@ -3,7 +3,7 @@
#include <stdint.h>
#include <stddef.h>
enum InfraredCustomEventType {
typedef enum {
// Reserve first 100 events for button types and indexes, starting from 0
InfraredCustomEventTypeReserved = 100,
InfraredCustomEventTypeMenuSelected,
@@ -13,7 +13,7 @@ enum InfraredCustomEventType {
InfraredCustomEventTypeTextEditDone,
InfraredCustomEventTypePopupClosed,
InfraredCustomEventTypeButtonSelected,
InfraredCustomEventTypeBackPressed,
InfraredCustomEventTypePopupInput,
InfraredCustomEventTypeTaskFinished,
InfraredCustomEventTypeRpcLoadFile,
@@ -27,7 +27,7 @@ enum InfraredCustomEventType {
InfraredCustomEventTypeGpioTxPinChanged,
InfraredCustomEventTypeGpioOtgChanged,
};
} InfraredCustomEventType;
#pragma pack(push, 1)
typedef union {

View File

@@ -1896,3 +1896,42 @@ type: raw
frequency: 38000
duty_cycle: 0.330000
data: 9024 4481 655 551 655 1653 654 550 656 1652 655 1652 656 550 656 550 656 550 656 550 656 551 655 1652 655 551 655 1652 655 550 656 551 655 1654 654 550 656 550 656 551 655 1652 655 550 656 551 654 550 656 1652 655 1651 657 550 655 551 655 550 656 1654 654 550 656 1653 654 551 654 551 655 1651 656 551 655 19984 655 550 656 551 655 550 656 551 655 550 655 551 655 551 655 550 656 1654 653 1653 655 1653 655 550 655 551 655 550 656 551 655 551 655 550 656 551 655 551 655 551 655 551 655 551 655 550 656 550 656 550 656 551 655 551 655 551 655 1652 656 550 656 551 655 550 656 39996 8999 4479 656 551 655 1652 656 550 656 1653 655 1653 655 550 656 551 655 550 656 551 655 551 655 1652 655 551 655 1652 655 550 656 551 655 1653 655 551 655 550 656 550 656 1652 655 551 654 551 655 551 655 1652 655 1652 656 551 655 551 655 552 654 551 655 1653 655 1653 655 551 655 549 656 1653 655 552 654 19984 655 1652 655 551 655 550 656 1652 656 551 655 551 655 551 655 1652 655 1652 655 551 656 1652 656 1653 655 1653 655 551 655 1652 655 551 655 551 655 551 654 551 654 551 655 551 655 1653 655 550 656 551 655 1652 656 1653 654 551 655 551 655 551 655 550 655 550 656 551 655
#
# Model: Fujitsu ASTG12LVCC
#
name: Off
type: raw
frequency: 38000
duty_cycle: 0.330000
data: 3258 1573 427 404 426 404 425 1180 428 403 427 1183 425 402 427 402 428 402 427 1180 428 1181 427 404 426 403 427 402 428 1181 427 1181 427 402 427 405 425 402 427 402 427 403 427 402 428 402 428 403 426 401 429 403 427 402 428 403 427 403 427 1180 428 401 428 404 425 401 428 402 427 402 427 402 427 402 428 1180 427 401 428 403 427 402 427 401 428 1180 427 401 428 402 427 402 428 402 427 401 428 403 427 1180 427 402 427 1180 427 1180 427 1177 429 1179 427 1179 427 1178 428
#
name: Dh
type: raw
frequency: 38000
duty_cycle: 0.330000
data: 39677 99167 3233 1570 425 405 425 404 425 1184 424 405 425 1182 426 405 424 404 426 404 425 1181 427 1182 426 403 427 403 426 404 426 1183 425 1183 425 403 426 404 426 406 424 404 425 405 425 402 427 405 425 404 425 403 426 404 426 404 425 402 427 405 424 1182 425 402 427 404 426 403 426 404 425 404 426 404 425 404 425 1183 424 406 423 404 426 403 426 404 425 1181 427 1182 426 1181 426 1181 426 1181 425 1182 425 1181 426 1182 426 403 426 404 425 1182 426 404 425 404 425 405 425 404 426 403 426 403 427 404 426 404 426 1182 426 1182 426 403 426 404 426 1182 426 405 424 404 426 403 426 1182 426 405 425 403 426 1182 426 404 426 1183 425 403 426 403 426 404 425 403 426 405 425 403 426 1182 425 1182 425 403 427 404 425 1181 426 403 427 403 426 404 425 406 424 404 426 404 425 404 426 404 425 404 426 404 426 404 426 404 426 404 425 404 426 404 426 403 426 403 427 404 425 402 427 405 425 403 426 404 425 404 425 404 425 405 425 404 425 404 425 404 426 403 426 402 427 403 427 403 426 1182 425 404 426 404 425 403 426 1182 425 403 426 1181 426 403 427 403 426 404 425 405 425
#
name: Cool_hi
type: raw
frequency: 38000
duty_cycle: 0.330000
data: 39674 99137 3228 1573 422 407 421 408 422 1185 423 408 422 1187 421 410 419 409 421 408 421 1186 422 1187 421 408 421 408 422 409 421 1187 420 1186 422 408 421 410 419 407 424 408 421 407 421 410 420 409 421 408 422 408 421 408 422 407 422 410 419 408 422 1187 420 408 421 408 422 408 421 408 421 408 421 408 420 409 421 1187 444 382 422 408 422 408 421 408 445 1162 419 1187 420 1184 423 1185 421 1184 423 1186 421 1186 421 1187 422 409 419 409 420 1186 422 407 420 409 422 409 420 407 422 407 422 411 419 406 421 409 422 1185 446 1162 420 409 421 409 421 1189 418 407 421 408 422 407 422 409 420 409 421 408 420 412 417 1187 421 407 422 408 420 410 421 408 421 409 421 409 445 384 420 410 421 407 421 407 422 409 421 1187 420 409 419 409 421 408 422 408 421 410 419 409 420 410 419 410 420 407 422 409 420 408 421 407 422 408 421 408 421 410 419 409 420 407 423 407 422 409 421 410 419 411 418 408 421 408 422 410 420 407 421 409 419 409 421 409 419 408 422 407 422 407 422 409 420 1188 419 409 421 409 420 409 419 1189 419 1186 421 1188 419 1187 420 408 421 407 422 1188 419
#
name: Cool_lo
type: raw
frequency: 38000
duty_cycle: 0.330000
data: 39689 99188 3229 1576 421 407 422 409 420 1188 419 409 421 1187 422 408 422 409 419 410 419 1187 422 1186 423 410 419 409 421 409 420 1187 420 1188 420 410 447 382 420 409 422 410 446 383 422 409 420 407 422 410 395 435 420 408 422 407 422 410 420 409 445 1162 420 410 420 409 420 410 420 409 421 410 419 409 421 409 419 1189 420 407 422 409 395 437 419 410 418 1186 422 1186 423 1187 420 1185 422 1188 420 1184 421 1188 419 1188 419 408 420 410 419 1186 421 408 420 410 419 410 419 409 419 411 418 409 421 410 419 409 420 1187 445 1163 419 412 417 409 420 1188 419 409 419 410 420 409 444 1164 418 1187 419 1189 419 409 419 1187 420 408 422 409 419 410 420 409 419 410 419 434 393 411 420 409 421 408 421 409 419 409 421 1188 418 410 419 410 420 410 418 412 417 409 445 385 419 409 420 410 420 408 419 409 421 410 419 411 419 408 446 382 421 409 420 409 420 410 418 409 420 409 419 410 419 412 442 384 419 411 416 412 419 409 420 410 419 410 419 410 418 410 420 409 420 409 420 434 394 1187 419 412 417 410 418 410 420 1188 419 1187 420 1188 419 410 419 1188 419 411 418 434 396
#
name: Heat_hi
type: raw
frequency: 38000
duty_cycle: 0.330000
data: 39692 99118 3226 1571 423 406 423 405 423 1185 421 405 424 1183 423 406 422 408 422 405 423 1184 446 1160 422 409 420 406 424 405 424 1185 422 1183 424 404 425 406 422 408 421 406 423 407 422 406 423 407 421 406 424 407 422 407 422 404 425 407 421 409 420 1185 422 406 423 408 421 404 424 405 424 407 447 383 421 406 424 1184 447 380 424 406 422 409 421 407 423 1183 447 1159 424 1185 422 1185 421 1184 422 1185 422 1185 421 1185 423 407 423 406 423 1185 423 406 424 406 423 408 446 381 424 406 423 408 421 406 424 406 423 1185 423 1184 422 407 423 407 422 1187 421 408 421 407 423 407 423 405 424 1185 422 1186 421 1184 423 407 422 407 422 1186 422 407 422 406 423 408 422 405 423 408 447 383 420 409 421 406 423 407 423 1184 423 407 423 407 422 408 421 408 423 406 424 406 422 409 422 406 423 408 421 408 421 406 422 406 424 407 422 406 423 409 421 407 422 408 423 406 423 406 423 409 446 382 447 384 420 407 423 405 424 406 423 406 423 407 423 407 422 406 423 405 422 407 424 406 422 1185 422 406 423 407 422 1183 423 1184 422 407 422 1185 423 1186 421 1184 424 407 422 1185 422
#
name: Heat_lo
type: raw
frequency: 38000
duty_cycle: 0.330000
data: 39670 99106 3227 1570 424 406 422 407 422 1183 424 405 424 1184 448 380 423 407 421 406 424 1183 424 1185 421 406 423 404 424 405 424 1184 423 1182 425 407 448 380 423 407 422 405 423 406 422 406 424 405 423 407 421 406 423 407 422 405 424 405 423 406 423 1184 422 408 421 408 422 405 424 406 421 407 422 406 423 405 423 1183 424 406 423 405 423 405 423 405 423 1186 421 1184 422 1184 422 1185 422 1184 447 1159 423 1184 422 1184 422 408 421 407 423 1184 421 407 448 381 422 405 423 409 421 406 422 406 422 407 422 406 423 1183 423 1185 422 406 423 405 424 1184 423 408 421 405 424 405 424 1184 422 1185 422 1184 422 407 423 408 420 409 420 1185 447 382 423 405 423 408 421 406 423 407 422 406 423 406 423 408 421 406 423 1183 424 407 422 406 424 405 424 406 423 407 423 406 423 408 422 407 422 405 424 408 421 407 422 407 422 406 423 406 423 407 422 406 423 406 422 408 421 407 422 408 421 407 422 406 423 408 422 406 423 405 423 409 422 406 422 406 423 406 423 407 422 407 423 405 424 1184 423 407 421 406 424 1184 423 1184 422 407 423 1183 423 405 424 1184 423 409 420 407 422
#

File diff suppressed because it is too large Load Diff

View File

@@ -2,15 +2,30 @@
#include <dolphin/dolphin.h>
void infrared_scene_universal_common_item_callback(void* context, uint32_t index) {
#pragma pack(push, 1)
typedef union {
uint32_t packed_value;
struct {
bool is_paused;
uint8_t padding;
uint16_t signal_index;
};
} InfraredSceneState;
#pragma pack(pop)
void infrared_scene_universal_common_item_callback(void* context, uint32_t index, InputType type) {
InfraredApp* infrared = context;
uint32_t event = infrared_custom_event_pack(InfraredCustomEventTypeButtonSelected, index);
view_dispatcher_send_custom_event(infrared->view_dispatcher, event);
if(type == InputTypeShort) {
uint32_t event = infrared_custom_event_pack(InfraredCustomEventTypeButtonSelected, index);
view_dispatcher_send_custom_event(infrared->view_dispatcher, event);
}
}
static void infrared_scene_universal_common_progress_back_callback(void* context) {
static void infrared_scene_universal_common_progress_input_callback(
void* context,
InfraredProgressViewInput input) {
InfraredApp* infrared = context;
uint32_t event = infrared_custom_event_pack(InfraredCustomEventTypeBackPressed, -1);
uint32_t event = infrared_custom_event_pack(InfraredCustomEventTypePopupInput, input);
view_dispatcher_send_custom_event(infrared->view_dispatcher, event);
}
@@ -19,8 +34,8 @@ static void
ViewStack* view_stack = infrared->view_stack;
InfraredProgressView* progress = infrared->progress;
infrared_progress_view_set_progress_total(progress, record_count);
infrared_progress_view_set_back_callback(
progress, infrared_scene_universal_common_progress_back_callback, infrared);
infrared_progress_view_set_input_callback(
progress, infrared_scene_universal_common_progress_input_callback, infrared);
view_stack_add_view(view_stack, infrared_progress_view_get_view(progress));
infrared_play_notification_message(infrared, InfraredNotificationMessageBlinkStartSend);
}
@@ -51,29 +66,111 @@ void infrared_scene_universal_common_on_enter(void* context) {
infrared_blocking_task_start(infrared, infrared_scene_universal_common_task_callback);
}
static void infrared_scene_universal_common_handle_popup_input(
InfraredApp* infrared,
InfraredProgressViewInput input) {
InfraredBruteForce* brute_force = infrared->brute_force;
SceneManager* scene_manager = infrared->scene_manager;
uint32_t scene_id = scene_manager_get_current_scene(infrared->scene_manager);
switch(input) {
case InfraredProgressViewInputStop: {
infrared_brute_force_stop(brute_force);
infrared_scene_universal_common_hide_popup(infrared);
break;
}
case InfraredProgressViewInputPause: {
infrared_play_notification_message(infrared, InfraredNotificationMessageBlinkStop);
infrared_progress_view_set_paused(infrared->progress, true);
InfraredSceneState scene_state = {
.packed_value = scene_manager_get_scene_state(scene_manager, scene_id)};
scene_state.is_paused = true;
if(scene_state.signal_index)
scene_state.signal_index--; // when running, the state stores the next index
scene_manager_set_scene_state(scene_manager, scene_id, scene_state.packed_value);
break;
}
case InfraredProgressViewInputResume: {
infrared_play_notification_message(infrared, InfraredNotificationMessageBlinkStartSend);
infrared_progress_view_set_paused(infrared->progress, false);
InfraredSceneState scene_state = {
.packed_value = scene_manager_get_scene_state(scene_manager, scene_id)};
scene_state.is_paused = false;
scene_manager_set_scene_state(scene_manager, scene_id, scene_state.packed_value);
break;
}
case InfraredProgressViewInputNextSignal: {
InfraredSceneState scene_state = {
.packed_value = scene_manager_get_scene_state(scene_manager, scene_id)};
scene_state.signal_index++;
if(infrared_progress_view_set_progress(infrared->progress, scene_state.signal_index + 1))
scene_manager_set_scene_state(scene_manager, scene_id, scene_state.packed_value);
break;
}
case InfraredProgressViewInputPreviousSignal: {
InfraredSceneState scene_state = {
.packed_value = scene_manager_get_scene_state(scene_manager, scene_id)};
if(scene_state.signal_index) {
scene_state.signal_index--;
if(infrared_progress_view_set_progress(
infrared->progress, scene_state.signal_index + 1))
scene_manager_set_scene_state(scene_manager, scene_id, scene_state.packed_value);
}
break;
}
case InfraredProgressViewInputSendSingle: {
InfraredSceneState scene_state = {
.packed_value = scene_manager_get_scene_state(scene_manager, scene_id)};
infrared_play_notification_message(infrared, InfraredNotificationMessageBlinkStartSend);
infrared_brute_force_send(infrared->brute_force, scene_state.signal_index);
infrared_play_notification_message(infrared, InfraredNotificationMessageBlinkStop);
break;
}
default:
furi_crash();
}
}
bool infrared_scene_universal_common_on_event(void* context, SceneManagerEvent event) {
InfraredApp* infrared = context;
SceneManager* scene_manager = infrared->scene_manager;
InfraredBruteForce* brute_force = infrared->brute_force;
uint32_t scene_id = scene_manager_get_current_scene(infrared->scene_manager);
bool consumed = false;
if(infrared_brute_force_is_started(brute_force)) {
if(event.type == SceneManagerEventTypeTick) {
bool success = infrared_brute_force_send_next(brute_force);
if(success) {
success = infrared_progress_view_increase_progress(infrared->progress);
InfraredSceneState scene_state = {
.packed_value = scene_manager_get_scene_state(scene_manager, scene_id)};
if(!scene_state.is_paused) {
bool success = infrared_brute_force_send(brute_force, scene_state.signal_index);
if(success) {
success = infrared_progress_view_set_progress(
infrared->progress, scene_state.signal_index + 1);
scene_state.signal_index++;
scene_manager_set_scene_state(
scene_manager, scene_id, scene_state.packed_value);
}
if(!success) {
infrared_brute_force_stop(brute_force);
infrared_scene_universal_common_hide_popup(infrared);
}
consumed = true;
}
if(!success) {
infrared_brute_force_stop(brute_force);
infrared_scene_universal_common_hide_popup(infrared);
}
consumed = true;
} else if(event.type == SceneManagerEventTypeCustom) {
if(infrared_custom_event_get_type(event.event) == InfraredCustomEventTypeBackPressed) {
infrared_brute_force_stop(brute_force);
infrared_scene_universal_common_hide_popup(infrared);
uint16_t event_type;
int16_t event_value;
infrared_custom_event_unpack(event.event, &event_type, &event_value);
if(event_type == InfraredCustomEventTypePopupInput) {
infrared_scene_universal_common_handle_popup_input(infrared, event_value);
consumed = true;
}
consumed = true;
}
} else {
if(event.type == SceneManagerEventTypeBack) {
@@ -87,6 +184,7 @@ bool infrared_scene_universal_common_on_event(void* context, SceneManagerEvent e
if(event_type == InfraredCustomEventTypeButtonSelected) {
uint32_t record_count;
if(infrared_brute_force_start(brute_force, event_value, &record_count)) {
scene_manager_set_scene_state(infrared->scene_manager, scene_id, 0);
dolphin_deed(DolphinDeedIrSend);
infrared_scene_universal_common_show_popup(infrared, record_count);
} else {

View File

@@ -5,4 +5,4 @@
void infrared_scene_universal_common_on_enter(void* context);
bool infrared_scene_universal_common_on_event(void* context, SceneManagerEvent event);
void infrared_scene_universal_common_on_exit(void* context);
void infrared_scene_universal_common_item_callback(void* context, uint32_t index);
void infrared_scene_universal_common_item_callback(void* context, uint32_t index, InputType type);

View File

@@ -118,7 +118,7 @@ void infrared_scene_universal_ac_on_enter(void* context) {
button_panel_add_icon(button_panel, 0, 60, &I_cool_30x51);
button_panel_add_icon(button_panel, 34, 60, &I_heat_30x51);
button_panel_add_label(button_panel, 4, 10, FontPrimary, "AC remote");
button_panel_add_label(button_panel, 24, 10, FontPrimary, "AC");
infrared_scene_universal_common_on_enter(context);
}

View File

@@ -114,7 +114,7 @@ void infrared_scene_universal_audio_on_enter(void* context) {
context);
infrared_brute_force_add_record(brute_force, i++, "Vol_up");
button_panel_add_label(button_panel, 1, 10, FontPrimary, "Mus. remote");
button_panel_add_label(button_panel, 1, 10, FontPrimary, "Audio player");
button_panel_add_icon(button_panel, 34, 56, &I_vol_ac_text_30x30);
infrared_scene_universal_common_on_enter(context);

View File

@@ -63,7 +63,7 @@ void infrared_scene_universal_projector_on_enter(void* context) {
context);
infrared_brute_force_add_record(brute_force, i++, "Vol_dn");
button_panel_add_label(button_panel, 3, 11, FontPrimary, "Proj. remote");
button_panel_add_label(button_panel, 10, 11, FontPrimary, "Projector");
button_panel_add_icon(button_panel, 17, 72, &I_vol_ac_text_30x30);
infrared_scene_universal_common_on_enter(context);

View File

@@ -91,7 +91,7 @@ void infrared_scene_universal_tv_on_enter(void* context) {
context);
infrared_brute_force_add_record(brute_force, i++, "Ch_prev");
button_panel_add_label(button_panel, 5, 10, FontPrimary, "TV remote");
button_panel_add_label(button_panel, 25, 10, FontPrimary, "TV");
infrared_scene_universal_common_on_enter(context);
}

View File

@@ -14,54 +14,80 @@
struct InfraredProgressView {
View* view;
InfraredProgressViewBackCallback back_callback;
InfraredProgressViewInputCallback input_callback;
void* context;
};
typedef struct {
size_t progress;
size_t progress_total;
bool is_paused;
} InfraredProgressViewModel;
bool infrared_progress_view_increase_progress(InfraredProgressView* progress) {
furi_assert(progress);
bool result = false;
InfraredProgressViewModel* model = view_get_model(progress->view);
if(model->progress < model->progress_total) {
++model->progress;
result = model->progress < model->progress_total;
}
view_commit_model(progress->view, true);
return result;
}
static void infrared_progress_view_draw_callback(Canvas* canvas, void* _model) {
InfraredProgressViewModel* model = (InfraredProgressViewModel*)_model;
uint8_t x = 0;
uint8_t y = 36;
uint8_t y = 25;
uint8_t width = 63;
uint8_t height = 59;
uint8_t height = 81;
elements_bold_rounded_frame(canvas, x, y, width, height);
canvas_set_font(canvas, FontSecondary);
elements_multiline_text_aligned(
canvas, x + 34, y + 9, AlignCenter, AlignCenter, "Sending ...");
canvas,
x + 32,
y + 9,
AlignCenter,
AlignCenter,
model->is_paused ? "Paused" : "Sending...");
float progress_value = (float)model->progress / model->progress_total;
elements_progress_bar(canvas, x + 4, y + 19, width - 7, progress_value);
uint8_t percent_value = 100 * model->progress / model->progress_total;
char percents_string[10] = {0};
snprintf(percents_string, sizeof(percents_string), "%d%%", percent_value);
char progress_string[16] = {0};
if(model->is_paused) {
snprintf(
progress_string,
sizeof(progress_string),
"%zu/%zu",
model->progress,
model->progress_total);
} else {
uint8_t percent_value = 100 * model->progress / model->progress_total;
snprintf(progress_string, sizeof(progress_string), "%d%%", percent_value);
}
elements_multiline_text_aligned(
canvas, x + 33, y + 37, AlignCenter, AlignCenter, percents_string);
canvas, x + 33, y + 37, AlignCenter, AlignCenter, progress_string);
canvas_draw_icon(canvas, x + 14, y + height - 14, &I_Pin_back_arrow_10x8);
canvas_draw_str(canvas, x + 30, y + height - 6, "= stop");
uint8_t buttons_x = x + (model->is_paused ? 10 : 14);
uint8_t buttons_y = y + (model->is_paused ? 46 : 50);
canvas_draw_icon(canvas, buttons_x + 0, buttons_y + 0, &I_Pin_back_arrow_10x8);
canvas_draw_str(canvas, buttons_x + 14, buttons_y + 8, model->is_paused ? "resume" : "stop");
canvas_draw_icon(canvas, buttons_x + 1, buttons_y + 10, &I_Ok_btn_9x9);
canvas_draw_str(canvas, buttons_x + 14, buttons_y + 17, model->is_paused ? "send 1" : "pause");
if(model->is_paused) {
canvas_draw_icon(canvas, buttons_x + 2, buttons_y + 21, &I_ButtonLeftSmall_3x5);
canvas_draw_icon(canvas, buttons_x + 7, buttons_y + 21, &I_ButtonRightSmall_3x5);
canvas_draw_str(canvas, buttons_x + 14, buttons_y + 26, "select");
}
}
bool infrared_progress_view_set_progress(InfraredProgressView* instance, uint16_t progress) {
bool result;
with_view_model(
instance->view,
InfraredProgressViewModel * model,
{
result = progress <= model->progress_total;
if(result) model->progress = progress;
},
true);
return result;
}
void infrared_progress_view_set_progress_total(
@@ -74,15 +100,45 @@ void infrared_progress_view_set_progress_total(
view_commit_model(progress->view, false);
}
void infrared_progress_view_set_paused(InfraredProgressView* instance, bool is_paused) {
with_view_model(
instance->view, InfraredProgressViewModel * model, { model->is_paused = is_paused; }, true);
}
bool infrared_progress_view_input_callback(InputEvent* event, void* context) {
InfraredProgressView* instance = context;
if((event->type == InputTypeShort) && (event->key == InputKeyBack)) {
if(instance->back_callback) {
instance->back_callback(instance->context);
}
if(event->type == InputTypePress || event->type == InputTypeRelease) {
return false;
}
if(!instance->input_callback) return false;
with_view_model(
instance->view,
InfraredProgressViewModel * model,
{
if(model->is_paused) {
if(event->key == InputKeyLeft)
instance->input_callback(
instance->context, InfraredProgressViewInputPreviousSignal);
else if(event->key == InputKeyRight)
instance->input_callback(
instance->context, InfraredProgressViewInputNextSignal);
else if(event->key == InputKeyOk)
instance->input_callback(
instance->context, InfraredProgressViewInputSendSingle);
else if(event->key == InputKeyBack)
instance->input_callback(instance->context, InfraredProgressViewInputResume);
} else {
if(event->key == InputKeyOk)
instance->input_callback(instance->context, InfraredProgressViewInputPause);
else if(event->key == InputKeyBack)
instance->input_callback(instance->context, InfraredProgressViewInputStop);
}
},
false);
return true;
}
@@ -106,12 +162,12 @@ void infrared_progress_view_free(InfraredProgressView* progress) {
free(progress);
}
void infrared_progress_view_set_back_callback(
void infrared_progress_view_set_input_callback(
InfraredProgressView* instance,
InfraredProgressViewBackCallback callback,
InfraredProgressViewInputCallback callback,
void* context) {
furi_assert(instance);
instance->back_callback = callback;
instance->input_callback = callback;
instance->context = context;
}

View File

@@ -10,11 +10,20 @@
extern "C" {
#endif
/** Anonumous instance */
/** Anonymous instance */
typedef struct InfraredProgressView InfraredProgressView;
/** Callback for back button handling */
typedef void (*InfraredProgressViewBackCallback)(void*);
typedef enum {
InfraredProgressViewInputStop,
InfraredProgressViewInputPause,
InfraredProgressViewInputResume,
InfraredProgressViewInputPreviousSignal,
InfraredProgressViewInputNextSignal,
InfraredProgressViewInputSendSingle,
} InfraredProgressViewInput;
/** Callback for input handling */
typedef void (*InfraredProgressViewInputCallback)(void* context, InfraredProgressViewInput event);
/** Allocate and initialize Infrared view
*
@@ -35,13 +44,12 @@ void infrared_progress_view_free(InfraredProgressView* instance);
*/
View* infrared_progress_view_get_view(InfraredProgressView* instance);
/** Increase progress on progress view module
/** Set progress of progress view module
*
* @param instance view module
* @retval true - value is incremented and maximum is reached,
* false - value is incremented and maximum is not reached
* @param progress progress value
*/
bool infrared_progress_view_increase_progress(InfraredProgressView* instance);
bool infrared_progress_view_set_progress(InfraredProgressView* instance, uint16_t progress);
/** Set maximum progress value
*
@@ -52,15 +60,22 @@ void infrared_progress_view_set_progress_total(
InfraredProgressView* instance,
uint16_t progress_max);
/** Set back button callback
/** Selects the variant of the View
*
* @param instance view instance
* @param is_paused the "paused" variant is displayed if true; the "sending" one if false
*/
void infrared_progress_view_set_paused(InfraredProgressView* instance, bool is_paused);
/** Set input callback
*
* @param instance - view module
* @param callback - callback to call for back button
* @param callback - callback to call for input
* @param context - context to pass to callback
*/
void infrared_progress_view_set_back_callback(
void infrared_progress_view_set_input_callback(
InfraredProgressView* instance,
InfraredProgressViewBackCallback callback,
InfraredProgressViewInputCallback callback,
void* context);
#ifdef __cplusplus

View File

@@ -247,6 +247,16 @@ App(
sources=["plugins/supported_cards/ndef.c"],
)
App(
appid="disney_infinity_parser",
apptype=FlipperAppType.PLUGIN,
entry_point="disney_infinity_plugin_ep",
targets=["f7"],
requires=["nfc"],
fap_libs=["mbedtls"],
sources=["plugins/supported_cards/disney_infinity.c"],
)
App(
appid="nfc_start",
targets=["f7"],

View File

@@ -0,0 +1,121 @@
#include <mbedtls/sha1.h>
#include "nfc_supported_card_plugin.h"
#include <flipper_application/flipper_application.h>
#include <nfc/nfc_device.h>
#include <bit_lib/bit_lib.h>
#include <nfc/protocols/mf_classic/mf_classic_poller_sync.h>
#include <flipper_format/flipper_format.h>
#define TAG "DisneyInfinity"
#define UID_LEN 7
// Derived from https://nfc.toys/#new-interoperability-for-infinity
static uint8_t seed[38] = {0x0A, 0x14, 0xFD, 0x05, 0x07, 0xFF, 0x4B, 0xCD, 0x02, 0x6B,
0xA8, 0x3F, 0x0A, 0x3B, 0x89, 0xA9, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x28, 0x63, 0x29, 0x20, 0x44, 0x69, 0x73,
0x6E, 0x65, 0x79, 0x20, 0x32, 0x30, 0x31, 0x33};
void di_key(const uint8_t* uid, MfClassicKey* key) {
uint8_t hash[20];
memcpy(seed + 16, uid, UID_LEN);
mbedtls_sha1(seed, sizeof(seed), hash);
key->data[0] = hash[3];
key->data[1] = hash[2];
key->data[2] = hash[1];
key->data[3] = hash[0];
key->data[4] = hash[7];
key->data[5] = hash[6];
}
static bool disney_infinity_read(Nfc* nfc, NfcDevice* device) {
furi_assert(nfc);
furi_assert(device);
size_t* uid_len = 0;
bool is_read = false;
MfClassicData* data = mf_classic_alloc();
nfc_device_copy_data(device, NfcProtocolMfClassic, data);
const uint8_t* uid_bytes = mf_classic_get_uid(data, uid_len);
MfClassicDeviceKeys keys = {};
do {
MfClassicType type = MfClassicTypeMini;
MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type);
if(error != MfClassicErrorNone) break;
data->type = type;
for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) {
di_key(uid_bytes, &keys.key_a[i]);
di_key(uid_bytes, &keys.key_b[i]);
FURI_BIT_SET(keys.key_a_mask, i);
FURI_BIT_SET(keys.key_b_mask, i);
}
error = mf_classic_poller_sync_read(nfc, &keys, data);
if(error != MfClassicErrorNone) {
FURI_LOG_W(TAG, "Failed to read data: %d", error);
break;
}
nfc_device_set_data(device, NfcProtocolMfClassic, data);
is_read = mf_classic_is_card_read(data);
} while(false);
mf_classic_free(data);
return is_read;
}
static bool disney_infinity_parse(const NfcDevice* device, FuriString* parsed_data) {
furi_assert(device);
size_t* uid_len = 0;
bool parsed = false;
FuriString* name = furi_string_alloc();
const uint8_t verify_sector = 0;
MfClassicKey key = {};
const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic);
const uint8_t* uid_bytes = mf_classic_get_uid(data, uid_len);
do {
// verify key
MfClassicSectorTrailer* sec_tr =
mf_classic_get_sector_trailer_by_sector(data, verify_sector);
di_key(uid_bytes, &key);
if(memcmp(key.data, sec_tr->key_a.data, 6) != 0) break;
// At some point I'd like to add name lookup like Skylanders
furi_string_printf(parsed_data, "\e#Disney Infinity\n");
parsed = true;
} while(false);
furi_string_free(name);
return parsed;
}
/* Actual implementation of app<>plugin interface */
static const NfcSupportedCardsPlugin disney_infinity_plugin = {
.protocol = NfcProtocolMfClassic,
.verify = NULL, // Need UID to verify key(s)
.read = disney_infinity_read,
.parse = disney_infinity_parse,
};
/* Plugin descriptor to comply with basic plugin specification */
static const FlipperAppPluginDescriptor disney_infinity_plugin_descriptor = {
.appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID,
.ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION,
.entry_point = &disney_infinity_plugin,
};
/* Plugin entry point - must return a pointer to const descriptor */
const FlipperAppPluginDescriptor* disney_infinity_plugin_ep(void) {
return &disney_infinity_plugin_descriptor;
}

View File

@@ -1,6 +1,8 @@
#include <furi.h>
#include <furi_hal.h>
#include <power/power_service/power.h>
#include <cli/cli.h>
#include <toolbox/args.h>
@@ -26,13 +28,14 @@ static void onewire_cli_print_usage(void) {
static void onewire_cli_search(Cli* cli) {
UNUSED(cli);
OneWireHost* onewire = onewire_host_alloc(&gpio_ibutton);
Power* power = furi_record_open(RECORD_POWER);
uint8_t address[8];
bool done = false;
printf("Search started\r\n");
onewire_host_start(onewire);
furi_hal_power_enable_otg();
power_enable_otg(power, true);
while(!done) {
if(onewire_host_search(onewire, address, OneWireHostSearchModeNormal) != 1) {
@@ -49,8 +52,10 @@ static void onewire_cli_search(Cli* cli) {
furi_delay_ms(100);
}
furi_hal_power_disable_otg();
power_enable_otg(power, false);
onewire_host_free(onewire);
furi_record_close(RECORD_POWER);
}
void onewire_cli(Cli* cli, FuriString* args, void* context) {

View File

@@ -4,27 +4,22 @@
#include <applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h>
#include <lib/subghz/devices/cc1101_int/cc1101_int_interconnect.h>
#include <power/power_service/power.h>
#define TAG "SubGhz"
static void subghz_txrx_radio_device_power_on(SubGhzTxRx* instance) {
UNUSED(instance);
uint8_t attempts = 5;
while(--attempts > 0) {
if(furi_hal_power_enable_otg()) break;
}
if(attempts == 0) {
if(furi_hal_power_get_usb_voltage() < 4.5f) {
FURI_LOG_E(
TAG,
"Error power otg enable. BQ2589 check otg fault = %d",
furi_hal_power_check_otg_fault() ? 1 : 0);
}
}
Power* power = furi_record_open(RECORD_POWER);
power_enable_otg(power, true);
furi_record_close(RECORD_POWER);
}
static void subghz_txrx_radio_device_power_off(SubGhzTxRx* instance) {
UNUSED(instance);
if(furi_hal_power_is_otg_enabled()) furi_hal_power_disable_otg();
Power* power = furi_record_open(RECORD_POWER);
power_enable_otg(power, false);
furi_record_close(RECORD_POWER);
}
SubGhzTxRx* subghz_txrx_alloc(void) {

View File

@@ -28,22 +28,15 @@
#define TAG "SubGhzCli"
static void subghz_cli_radio_device_power_on(void) {
uint8_t attempts = 5;
while(--attempts > 0) {
if(furi_hal_power_enable_otg()) break;
}
if(attempts == 0) {
if(furi_hal_power_get_usb_voltage() < 4.5f) {
FURI_LOG_E(
"TAG",
"Error power otg enable. BQ2589 check otg fault = %d",
furi_hal_power_check_otg_fault() ? 1 : 0);
}
}
Power* power = furi_record_open(RECORD_POWER);
power_enable_otg(power, true);
furi_record_close(RECORD_POWER);
}
static void subghz_cli_radio_device_power_off(void) {
if(furi_hal_power_is_otg_enabled()) furi_hal_power_disable_otg();
Power* power = furi_record_open(RECORD_POWER);
power_enable_otg(power, false);
furi_record_close(RECORD_POWER);
}
static SubGhzEnvironment* subghz_cli_environment_init(void) {
@@ -615,7 +608,7 @@ void subghz_cli_command_tx_from_file(Cli* cli, FuriString* args, void* context)
if(furi_string_size(args)) {
char* args_cstr = (char*)furi_string_get_cstr(args);
StrintParseError parse_err = StrintParseNoError;
parse_err |= strint_to_uint32(args_cstr, &args_cstr, &frequency, 10);
parse_err |= strint_to_uint32(args_cstr, &args_cstr, &repeat, 10);
parse_err |= strint_to_uint32(args_cstr, &args_cstr, &device_ind, 10);
if(parse_err) {
cli_print_usage(

View File

@@ -28,6 +28,8 @@
#include "rpc/rpc_app.h"
#include <power/power_service/power.h>
#include "helpers/subghz_threshold_rssi.h"
#include "helpers/subghz_txrx.h"

View File

@@ -1,6 +1,8 @@
#include "expansion_worker.h"
#include <power/power_service/power.h>
#include <furi_hal_power.h>
#include <furi_hal_serial.h>
#include <furi_hal_serial_control.h>
@@ -250,9 +252,13 @@ static bool expansion_worker_handle_state_connected(
if(!expansion_worker_rpc_session_open(instance)) break;
instance->state = ExpansionWorkerStateRpcActive;
} else if(command == ExpansionFrameControlCommandEnableOtg) {
furi_hal_power_enable_otg();
Power* power = furi_record_open(RECORD_POWER);
power_enable_otg(power, true);
furi_record_close(RECORD_POWER);
} else if(command == ExpansionFrameControlCommandDisableOtg) {
furi_hal_power_disable_otg();
Power* power = furi_record_open(RECORD_POWER);
power_enable_otg(power, false);
furi_record_close(RECORD_POWER);
} else {
break;
}

View File

@@ -2,6 +2,7 @@
#include <gui/canvas.h>
#include <gui/elements.h>
#include <input/input.h>
#include <furi.h>
#include <furi_hal_resources.h>
@@ -46,6 +47,7 @@ ARRAY_DEF(ButtonMatrix, ButtonArray_t);
struct ButtonPanel {
View* view;
bool freeze_input;
};
typedef struct {
@@ -63,7 +65,7 @@ static void button_panel_process_up(ButtonPanel* button_panel);
static void button_panel_process_down(ButtonPanel* button_panel);
static void button_panel_process_left(ButtonPanel* button_panel);
static void button_panel_process_right(ButtonPanel* button_panel);
static void button_panel_process_ok(ButtonPanel* button_panel);
static void button_panel_process_ok(ButtonPanel* button_panel, InputType input);
static void button_panel_view_draw_callback(Canvas* canvas, void* _model);
static bool button_panel_view_input_callback(InputEvent* event, void* context);
@@ -347,7 +349,7 @@ static void button_panel_process_right(ButtonPanel* button_panel) {
true);
}
void button_panel_process_ok(ButtonPanel* button_panel) {
void button_panel_process_ok(ButtonPanel* button_panel, InputType type) {
ButtonItem* button_item = NULL;
with_view_model(
@@ -360,7 +362,7 @@ void button_panel_process_ok(ButtonPanel* button_panel) {
true);
if(button_item && button_item->callback) {
button_item->callback(button_item->callback_context, button_item->index);
button_item->callback(button_item->callback_context, button_item->index, type);
}
}
@@ -368,8 +370,15 @@ static bool button_panel_view_input_callback(InputEvent* event, void* context) {
ButtonPanel* button_panel = context;
furi_assert(button_panel);
bool consumed = false;
if(event->type == InputTypeShort) {
if(event->key == InputKeyOk) {
if((event->type == InputTypePress) || (event->type == InputTypeRelease)) {
button_panel->freeze_input = (event->type == InputTypePress);
}
consumed = true;
button_panel_process_ok(button_panel, event->type);
}
if(!button_panel->freeze_input &&
(!(event->type == InputTypePress) && !(event->type == InputTypeRelease))) {
switch(event->key) {
case InputKeyUp:
consumed = true;
@@ -387,10 +396,6 @@ static bool button_panel_view_input_callback(InputEvent* event, void* context) {
consumed = true;
button_panel_process_right(button_panel);
break;
case InputKeyOk:
consumed = true;
button_panel_process_ok(button_panel);
break;
default:
break;
}

View File

@@ -15,7 +15,7 @@ extern "C" {
typedef struct ButtonPanel ButtonPanel;
/** Callback type to call for handling selecting button_panel items */
typedef void (*ButtonItemCallback)(void* context, uint32_t index);
typedef void (*ButtonItemCallback)(void* context, uint32_t index, InputType type);
/** Allocate new button_panel module.
*

View File

@@ -195,14 +195,27 @@ void widget_add_icon_element(Widget* widget, uint8_t x, uint8_t y, const Icon* i
widget_add_element(widget, icon_element);
}
void widget_add_frame_element(
void widget_add_rect_element(
Widget* widget,
uint8_t x,
uint8_t y,
uint8_t width,
uint8_t height,
uint8_t radius) {
uint8_t radius,
bool fill) {
furi_check(widget);
WidgetElement* frame_element = widget_element_frame_create(x, y, width, height, radius);
widget_add_element(widget, frame_element);
WidgetElement* rect_element = widget_element_rect_create(x, y, width, height, radius, fill);
widget_add_element(widget, rect_element);
}
void widget_add_circle_element(Widget* widget, uint8_t x, uint8_t y, uint8_t radius, bool fill) {
furi_check(widget);
WidgetElement* circle_element = widget_element_circle_create(x, y, radius, fill);
widget_add_element(widget, circle_element);
}
void widget_add_line_element(Widget* widget, uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2) {
furi_check(widget);
WidgetElement* line_element = widget_element_line_create(x1, y1, x2, y2);
widget_add_element(widget, line_element);
}

View File

@@ -152,21 +152,57 @@ void widget_add_button_element(
void widget_add_icon_element(Widget* widget, uint8_t x, uint8_t y, const Icon* icon);
/** Add Frame Element
*
* @param widget Widget instance
* @param x top left x coordinate
* @param y top left y coordinate
* @param width frame width
* @param height frame height
* @param radius frame radius
*
* @warning deprecated, use widget_add_rect_element instead
*/
#define widget_add_frame_element(widget, x, y, width, height, radius) \
widget_add_rect_element((widget), (x), (y), (width), (height), (radius), false)
/** Add Rect Element
*
* @param widget Widget instance
* @param x top left x coordinate
* @param y top left y coordinate
* @param width frame width
* @param height frame height
* @param radius frame radius
* @param width rect width
* @param height rect height
* @param radius corner radius
* @param fill whether to fill the box or not
*/
void widget_add_frame_element(
void widget_add_rect_element(
Widget* widget,
uint8_t x,
uint8_t y,
uint8_t width,
uint8_t height,
uint8_t radius);
uint8_t radius,
bool fill);
/** Add Circle Element
*
* @param widget Widget instance
* @param x center x coordinate
* @param y center y coordinate
* @param radius circle radius
* @param fill whether to fill the circle or not
*/
void widget_add_circle_element(Widget* widget, uint8_t x, uint8_t y, uint8_t radius, bool fill);
/** Add Line Element
*
* @param widget Widget instance
* @param x1 first x coordinate
* @param y1 first y coordinate
* @param x2 second x coordinate
* @param y2 second y coordinate
*/
void widget_add_line_element(Widget* widget, uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2);
#ifdef __cplusplus
}

View File

@@ -5,6 +5,8 @@
#pragma once
#include <input/input.h>
#ifdef __cplusplus
extern "C" {
#endif

View File

@@ -0,0 +1,45 @@
#include "widget_element_i.h"
typedef struct {
uint8_t x;
uint8_t y;
uint8_t radius;
bool fill;
} GuiCircleModel;
static void gui_circle_draw(Canvas* canvas, WidgetElement* element) {
furi_assert(canvas);
furi_assert(element);
GuiCircleModel* model = element->model;
if(model->fill) {
canvas_draw_disc(canvas, model->x, model->y, model->radius);
} else {
canvas_draw_circle(canvas, model->x, model->y, model->radius);
}
}
static void gui_circle_free(WidgetElement* gui_circle) {
furi_assert(gui_circle);
free(gui_circle->model);
free(gui_circle);
}
WidgetElement* widget_element_circle_create(uint8_t x, uint8_t y, uint8_t radius, bool fill) {
// Allocate and init model
GuiCircleModel* model = malloc(sizeof(GuiCircleModel));
model->x = x;
model->y = y;
model->radius = radius;
model->fill = fill;
// Allocate and init Element
WidgetElement* gui_circle = malloc(sizeof(WidgetElement));
gui_circle->parent = NULL;
gui_circle->input = NULL;
gui_circle->draw = gui_circle_draw;
gui_circle->free = gui_circle_free;
gui_circle->model = model;
return gui_circle;
}

View File

@@ -1,48 +0,0 @@
#include "widget_element_i.h"
typedef struct {
uint8_t x;
uint8_t y;
uint8_t width;
uint8_t height;
uint8_t radius;
} GuiFrameModel;
static void gui_frame_draw(Canvas* canvas, WidgetElement* element) {
furi_assert(canvas);
furi_assert(element);
GuiFrameModel* model = element->model;
canvas_draw_rframe(canvas, model->x, model->y, model->width, model->height, model->radius);
}
static void gui_frame_free(WidgetElement* gui_frame) {
furi_assert(gui_frame);
free(gui_frame->model);
free(gui_frame);
}
WidgetElement* widget_element_frame_create(
uint8_t x,
uint8_t y,
uint8_t width,
uint8_t height,
uint8_t radius) {
// Allocate and init model
GuiFrameModel* model = malloc(sizeof(GuiFrameModel));
model->x = x;
model->y = y;
model->width = width;
model->height = height;
model->radius = radius;
// Allocate and init Element
WidgetElement* gui_frame = malloc(sizeof(WidgetElement));
gui_frame->parent = NULL;
gui_frame->input = NULL;
gui_frame->draw = gui_frame_draw;
gui_frame->free = gui_frame_free;
gui_frame->model = model;
return gui_frame;
}

View File

@@ -73,14 +73,16 @@ WidgetElement* widget_element_button_create(
/** Create icon element */
WidgetElement* widget_element_icon_create(uint8_t x, uint8_t y, const Icon* icon);
/** Create frame element */
WidgetElement* widget_element_frame_create(
/** Create rect element */
WidgetElement* widget_element_rect_create(
uint8_t x,
uint8_t y,
uint8_t width,
uint8_t height,
uint8_t radius);
uint8_t radius,
bool fill);
/** Create text scroll element */
WidgetElement* widget_element_text_scroll_create(
uint8_t x,
uint8_t y,
@@ -88,6 +90,12 @@ WidgetElement* widget_element_text_scroll_create(
uint8_t height,
const char* text);
/** Create circle element */
WidgetElement* widget_element_circle_create(uint8_t x, uint8_t y, uint8_t radius, bool fill);
/** Create line element */
WidgetElement* widget_element_line_create(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,41 @@
#include "widget_element_i.h"
typedef struct {
uint8_t x1;
uint8_t y1;
uint8_t x2;
uint8_t y2;
} GuiLineModel;
static void gui_line_draw(Canvas* canvas, WidgetElement* element) {
furi_assert(canvas);
furi_assert(element);
GuiLineModel* model = element->model;
canvas_draw_line(canvas, model->x1, model->y1, model->x2, model->y2);
}
static void gui_line_free(WidgetElement* gui_line) {
furi_assert(gui_line);
free(gui_line->model);
free(gui_line);
}
WidgetElement* widget_element_line_create(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2) {
// Allocate and init model
GuiLineModel* model = malloc(sizeof(GuiLineModel));
model->x1 = x1;
model->y1 = y1;
model->x2 = x2;
model->y2 = y2;
// Allocate and init Element
WidgetElement* gui_line = malloc(sizeof(WidgetElement));
gui_line->parent = NULL;
gui_line->input = NULL;
gui_line->draw = gui_line_draw;
gui_line->free = gui_line_free;
gui_line->model = model;
return gui_line;
}

View File

@@ -0,0 +1,55 @@
#include "widget_element_i.h"
typedef struct {
uint8_t x;
uint8_t y;
uint8_t width;
uint8_t height;
uint8_t radius;
bool fill;
} GuiRectModel;
static void gui_rect_draw(Canvas* canvas, WidgetElement* element) {
furi_assert(canvas);
furi_assert(element);
GuiRectModel* model = element->model;
if(model->fill) {
canvas_draw_rbox(canvas, model->x, model->y, model->width, model->height, model->radius);
} else {
canvas_draw_rframe(canvas, model->x, model->y, model->width, model->height, model->radius);
}
}
static void gui_rect_free(WidgetElement* gui_rect) {
furi_assert(gui_rect);
free(gui_rect->model);
free(gui_rect);
}
WidgetElement* widget_element_rect_create(
uint8_t x,
uint8_t y,
uint8_t width,
uint8_t height,
uint8_t radius,
bool fill) {
// Allocate and init model
GuiRectModel* model = malloc(sizeof(GuiRectModel));
model->x = x;
model->y = y;
model->width = width;
model->height = height;
model->radius = radius;
model->fill = fill;
// Allocate and init Element
WidgetElement* gui_rect = malloc(sizeof(WidgetElement));
gui_rect->parent = NULL;
gui_rect->input = NULL;
gui_rect->draw = gui_rect_draw;
gui_rect->free = gui_rect_free;
gui_rect->model = model;
return gui_rect;
}

View File

@@ -230,6 +230,11 @@ bool scene_manager_search_and_switch_to_another_scene(
}
}
uint32_t scene_manager_get_current_scene(SceneManager* scene_manager) {
furi_check(scene_manager);
return *SceneManagerIdStack_back(scene_manager->scene_id_stack);
}
void scene_manager_stop(SceneManager* scene_manager) {
furi_check(scene_manager);

View File

@@ -170,6 +170,14 @@ bool scene_manager_search_and_switch_to_another_scene(
SceneManager* scene_manager,
uint32_t scene_id);
/** Get id of current scene
*
* @param scene_manager SceneManager instance
*
* @return Scene ID
*/
uint32_t scene_manager_get_current_scene(SceneManager* scene_manager);
/** Exit from current scene
*
* @param scene_manager SceneManager instance

View File

@@ -30,13 +30,16 @@ void power_cli_reboot2dfu(Cli* cli, FuriString* args) {
void power_cli_5v(Cli* cli, FuriString* args) {
UNUSED(cli);
Power* power = furi_record_open(RECORD_POWER);
if(!furi_string_cmp(args, "0")) {
furi_hal_power_disable_otg();
power_enable_otg(power, false);
} else if(!furi_string_cmp(args, "1")) {
furi_hal_power_enable_otg();
power_enable_otg(power, true);
} else {
cli_print_usage("power_otg", "<1|0>", furi_string_get_cstr(args));
}
furi_record_close(RECORD_POWER);
}
void power_cli_3v3(Cli* cli, FuriString* args) {

View File

@@ -64,6 +64,7 @@ static bool power_update_info(Power* power) {
.is_charging = furi_hal_power_is_charging(),
.gauge_is_ok = furi_hal_power_gauge_is_ok(),
.is_shutdown_requested = furi_hal_power_is_shutdown_requested(),
.is_otg_enabled = furi_hal_power_is_otg_enabled(),
.charge = furi_hal_power_get_pct(),
.health = furi_hal_power_get_bat_health_pct(),
.capacity_remaining = furi_hal_power_get_battery_remaining_capacity(),
@@ -216,6 +217,30 @@ static void power_message_callback(FuriEventLoopObject* object, void* context) {
case PowerMessageTypeShowBatteryLowWarning:
power->show_battery_low_warning = *msg.bool_param;
break;
case PowerMessageTypeSwitchOTG:
power->is_otg_requested = *msg.bool_param;
if(power->is_otg_requested) {
// Only try to enable if VBUS voltage is low, otherwise charger will refuse
if(power->info.voltage_vbus < 4.5f) {
size_t retries = 5;
while(retries-- > 0) {
if(furi_hal_power_enable_otg()) {
break;
}
}
if(!retries) {
FURI_LOG_W(TAG, "Failed to enable OTG, will try later");
}
} else {
FURI_LOG_W(
TAG,
"Postponing OTG enable: VBUS(%0.1f) >= 4.5v",
(double)power->info.voltage_vbus);
}
} else {
furi_hal_power_disable_otg();
}
break;
default:
furi_crash();
}
@@ -241,9 +266,18 @@ static void power_tick_callback(void* context) {
if(need_refresh) {
view_port_update(power->battery_view_port);
}
// Check OTG status and disable it in case of fault
if(furi_hal_power_is_otg_enabled()) {
furi_hal_power_check_otg_status();
// Check OTG status, disable in case of a fault
if(furi_hal_power_check_otg_fault()) {
FURI_LOG_E(TAG, "OTG fault detected, disabling OTG");
furi_hal_power_disable_otg();
power->is_otg_requested = false;
}
// Change OTG state if needed (i.e. after disconnecting USB power)
if(power->is_otg_requested &&
(!power->info.is_otg_enabled && power->info.voltage_vbus < 4.5f)) {
FURI_LOG_D(TAG, "OTG requested but not enabled, enabling OTG");
furi_hal_power_enable_otg();
}
}

View File

@@ -39,6 +39,7 @@ typedef struct {
bool gauge_is_ok;
bool is_charging;
bool is_shutdown_requested;
bool is_otg_enabled;
float current_charger;
float current_gauge;
@@ -96,6 +97,19 @@ bool power_is_battery_healthy(Power* power);
*/
void power_enable_low_battery_level_notification(Power* power, bool enable);
/** Enable or disable OTG
*
* @param power Power instance
* @param enable true - enable, false - disable
*/
void power_enable_otg(Power* power, bool enable);
/** Check OTG status
*
* @return true if OTG is requested
*/
bool power_is_otg_enabled(Power* power);
#ifdef __cplusplus
}
#endif

View File

@@ -70,3 +70,22 @@ void power_enable_low_battery_level_notification(Power* power, bool enable) {
furi_check(
furi_message_queue_put(power->message_queue, &msg, FuriWaitForever) == FuriStatusOk);
}
void power_enable_otg(Power* power, bool enable) {
furi_check(power);
PowerMessage msg = {
.type = PowerMessageTypeSwitchOTG,
.bool_param = &enable,
.lock = api_lock_alloc_locked(),
};
furi_check(
furi_message_queue_put(power->message_queue, &msg, FuriWaitForever) == FuriStatusOk);
api_lock_wait_unlock_and_free(msg.lock);
}
bool power_is_otg_enabled(Power* power) {
furi_check(power);
return power->is_otg_requested;
}

View File

@@ -33,6 +33,7 @@ struct Power {
bool battery_low;
bool show_battery_low_warning;
bool is_otg_requested;
uint8_t battery_level;
uint8_t power_off_timeout;
};
@@ -48,6 +49,7 @@ typedef enum {
PowerMessageTypeGetInfo,
PowerMessageTypeIsBatteryHealthy,
PowerMessageTypeShowBatteryLowWarning,
PowerMessageTypeSwitchOTG,
} PowerMessageType;
typedef struct {

View File

@@ -4,6 +4,7 @@
#include <furi_hal_gpio.h>
#include <furi_hal_power.h>
#include <furi_hal_resources.h>
#include <power/power_service/power.h>
static const GpioPin* rpc_pin_to_hal_pin(PB_Gpio_GpioPin rpc_pin) {
switch(rpc_pin) {
@@ -218,12 +219,16 @@ void rpc_system_gpio_set_otg_mode(const PB_Main* request, void* context) {
const PB_Gpio_GpioOtgMode mode = request->content.gpio_set_otg_mode.mode;
Power* power = furi_record_open(RECORD_POWER);
if(mode == PB_Gpio_GpioOtgMode_OFF) {
furi_hal_power_disable_otg();
power_enable_otg(power, false);
} else {
furi_hal_power_enable_otg();
power_enable_otg(power, true);
}
furi_record_close(RECORD_POWER);
rpc_send_and_release_empty(session, request->command_id, PB_CommandStatus_OK);
}

View File

@@ -124,7 +124,7 @@ static bool hid_mouse_clicker_input_callback(InputEvent* event, void* context) {
bool consumed = false;
bool rate_changed = false;
if(event->type != InputTypeShort && event->type != InputTypeRepeat) {
if(event->type == InputTypePress || event->type == InputTypeRelease) {
return false;
}

View File

@@ -110,6 +110,23 @@ App(
fap_libs=["assets"],
)
App(
appid="js_gui__widget",
apptype=FlipperAppType.PLUGIN,
entry_point="js_view_widget_ep",
requires=["js_app"],
sources=["modules/js_gui/widget.c"],
)
App(
appid="js_gui__icon",
apptype=FlipperAppType.PLUGIN,
entry_point="js_gui_icon_ep",
requires=["js_app"],
sources=["modules/js_gui/icon.c"],
fap_libs=["assets"],
)
App(
appid="js_notification",
apptype=FlipperAppType.PLUGIN,

View File

@@ -3,6 +3,7 @@ let gpio = require("gpio");
// initialize pins
let led = gpio.get("pc3"); // same as `gpio.get(7)`
let led2 = gpio.get("pa7"); // same as `gpio.get(2)`
let pot = gpio.get("pc0"); // same as `gpio.get(16)`
let button = gpio.get("pc1"); // same as `gpio.get(15)`
led.init({ direction: "out", outMode: "push_pull" });
@@ -16,6 +17,13 @@ eventLoop.subscribe(eventLoop.timer("periodic", 1000), function (_, _item, led,
return [led, !state];
}, led, true);
// cycle led pwm
print("Commencing PWM (PA7)");
eventLoop.subscribe(eventLoop.timer("periodic", 10), function (_, _item, led2, state) {
led2.pwmWrite(10000, state);
return [led2, (state + 1) % 101];
}, led2, 0);
// read potentiometer when button is pressed
print("Press the button (PC1)");
eventLoop.subscribe(button.interrupt(), function (_, _item, pot) {

View File

@@ -9,8 +9,23 @@ let byteInputView = require("gui/byte_input");
let textBoxView = require("gui/text_box");
let dialogView = require("gui/dialog");
let filePicker = require("gui/file_picker");
let widget = require("gui/widget");
let icon = require("gui/icon");
let flipper = require("flipper");
// declare clock widget children
let cuteDolphinWithWatch = icon.getBuiltin("DolphinWait_59x54");
let jsLogo = icon.getBuiltin("js_script_10px");
let stopwatchWidgetElements = [
{ element: "string", x: 67, y: 44, align: "bl", font: "big_numbers", text: "00 00" },
{ element: "string", x: 77, y: 22, align: "bl", font: "primary", text: "Stopwatch" },
{ element: "frame", x: 64, y: 27, w: 28, h: 20, radius: 3, fill: false },
{ element: "frame", x: 100, y: 27, w: 28, h: 20, radius: 3, fill: false },
{ element: "icon", x: 0, y: 5, iconData: cuteDolphinWithWatch },
{ element: "icon", x: 64, y: 13, iconData: jsLogo },
{ element: "button", button: "right", text: "Back" },
];
// declare view instances
let views = {
loading: loadingView.make(),
@@ -31,6 +46,7 @@ let views = {
longText: textBoxView.makeWith({
text: "This is a very long string that demonstrates the TextBox view. Use the D-Pad to scroll backwards and forwards.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse rhoncus est malesuada quam egestas ultrices. Maecenas non eros a nulla eleifend vulputate et ut risus. Quisque in mauris mattis, venenatis risus eget, aliquam diam. Fusce pretium feugiat mauris, ut faucibus ex volutpat in. Phasellus volutpat ex sed gravida consectetur. Aliquam sed lectus feugiat, tristique lectus et, bibendum lacus. Ut sit amet augue eu sapien elementum aliquam quis vitae tortor. Vestibulum quis commodo odio. In elementum fermentum massa, eu pellentesque nibh cursus at. Integer eleifend lacus nec purus elementum sodales. Nulla elementum neque urna, non vulputate massa semper sed. Fusce ut nisi vitae dui blandit congue pretium vitae turpis.",
}),
stopwatchWidget: widget.makeWith({}, stopwatchWidgetElements),
demos: submenuView.makeWith({
header: "Choose a demo",
items: [
@@ -40,6 +56,7 @@ let views = {
"Byte input",
"Text box",
"File picker",
"Widget",
"Exit app",
],
}),
@@ -72,6 +89,8 @@ eventLoop.subscribe(views.demos.chosen, function (_sub, index, gui, eventLoop, v
views.helloDialog.set("center", "Nice!");
gui.viewDispatcher.switchTo(views.helloDialog);
} else if (index === 6) {
gui.viewDispatcher.switchTo(views.stopwatchWidget);
} else if (index === 7) {
eventLoop.stop();
}
}, gui, eventLoop, views);
@@ -111,6 +130,31 @@ eventLoop.subscribe(gui.viewDispatcher.navigation, function (_sub, _, gui, views
gui.viewDispatcher.switchTo(views.demos);
}, gui, views, eventLoop);
// go to the demo chooser screen when the right key is pressed on the widget screen
eventLoop.subscribe(views.stopwatchWidget.button, function (_sub, buttonId, gui, views) {
if (buttonId === "right")
gui.viewDispatcher.switchTo(views.demos);
}, gui, views);
// count time
eventLoop.subscribe(eventLoop.timer("periodic", 500), function (_sub, _item, views, stopwatchWidgetElements, halfSeconds) {
let text = (halfSeconds / 2 / 60).toString();
if (halfSeconds < 10 * 60 * 2)
text = "0" + text;
text += (halfSeconds % 2 === 0) ? ":" : " ";
if (((halfSeconds / 2) % 60) < 10)
text += "0";
text += ((halfSeconds / 2) % 60).toString();
stopwatchWidgetElements[0].text = text;
views.stopwatchWidget.setChildren(stopwatchWidgetElements);
halfSeconds++;
return [views, stopwatchWidgetElements, halfSeconds];
}, views, stopwatchWidgetElements, 0);
// run UI
gui.viewDispatcher.switchTo(views.demos);
eventLoop.run();

View File

@@ -0,0 +1,15 @@
// This script is like uart_echo, except it uses 8E1 framing (8 data bits, even
// parity, 1 stop bit) as opposed to the default 8N1 (8 data bits, no parity,
// 1 stop bit)
let serial = require("serial");
serial.setup("usart", 230400, { dataBits: "8", parity: "even", stopBits: "1" });
while (1) {
let rx_data = serial.readBytes(1, 1000);
if (rx_data !== undefined) {
serial.write(rx_data);
let data_view = Uint8Array(rx_data);
print("0x" + data_view[0].toString(16));
}
}

View File

@@ -267,6 +267,10 @@ void js_check_sdk_compatibility(struct mjs* mjs) {
static const char* extra_features[] = {
"baseline", // dummy "feature"
"gpio-pwm",
"gui-widget",
"serial-framing",
"gui-widget-extras",
};
/**

View File

@@ -11,7 +11,7 @@
#define JS_SDK_VENDOR "flipperdevices"
#define JS_SDK_MAJOR 0
#define JS_SDK_MINOR 1
#define JS_SDK_MINOR 3
/**
* @brief Returns the foreign pointer in `obj["_"]`
@@ -81,6 +81,11 @@ typedef enum {
*/
#define JS_AT_LEAST >=
typedef struct {
const char* name;
size_t value;
} JsEnumMapping;
#define JS_ENUM_MAP(var_name, ...) \
static const JsEnumMapping var_name##_mapping[] = { \
{NULL, sizeof(var_name)}, \
@@ -90,8 +95,14 @@ typedef enum {
typedef struct {
const char* name;
size_t value;
} JsEnumMapping;
size_t offset;
} JsObjectMapping;
#define JS_OBJ_MAP(var_name, ...) \
static const JsObjectMapping var_name##_mapping[] = { \
__VA_ARGS__, \
{NULL, 0}, \
};
typedef struct {
void* out;
@@ -199,6 +210,54 @@ static inline void
_js_validate_enum, \
var_name##_mapping})
static inline bool _js_validate_object(struct mjs* mjs, mjs_val_t val, const void* extra) {
for(const JsObjectMapping* mapping = (JsObjectMapping*)extra; mapping->name; mapping++)
if(mjs_get(mjs, val, mapping->name, ~0) == MJS_UNDEFINED) return false;
return true;
}
static inline void
_js_convert_object(struct mjs* mjs, mjs_val_t* val, void* out, const void* extra) {
const JsObjectMapping* mapping = (JsObjectMapping*)extra;
for(; mapping->name; mapping++) {
mjs_val_t field_val = mjs_get(mjs, *val, mapping->name, ~0);
*(mjs_val_t*)((uint8_t*)out + mapping->offset) = field_val;
}
}
#define JS_ARG_OBJECT(var_name, name) \
((_js_arg_decl){ \
&var_name, \
mjs_is_object, \
_js_convert_object, \
name " object", \
_js_validate_object, \
var_name##_mapping})
/**
* @brief Validates and converts a JS value with a declarative interface
*
* Example: `int32_t my_value; JS_CONVERT_OR_RETURN(mjs, &mjs_val, JS_ARG_INT32(&my_value), "value source");`
*
* @warning This macro executes `return;` by design in case of a validation failure
*/
#define JS_CONVERT_OR_RETURN(mjs, value, decl, source, ...) \
if(decl.validator) \
if(!decl.validator(*value)) \
JS_ERROR_AND_RETURN( \
mjs, \
MJS_BAD_ARGS_ERROR, \
source ": expected %s", \
##__VA_ARGS__, \
decl.expected_type); \
if(decl.extended_validator) \
if(!decl.extended_validator(mjs, *value, decl.extra_data)) \
JS_ERROR_AND_RETURN( \
mjs, \
MJS_BAD_ARGS_ERROR, \
source ": expected %s", \
##__VA_ARGS__, \
decl.expected_type); \
decl.converter(mjs, value, decl.out, decl.extra_data);
//-V:JS_FETCH_ARGS_OR_RETURN:1008
/**
* @brief Fetches and validates the arguments passed to a JS function
@@ -208,38 +267,21 @@ static inline void
* @warning This macro executes `return;` by design in case of an argument count
* mismatch or a validation failure
*/
#define JS_FETCH_ARGS_OR_RETURN(mjs, arg_operator, ...) \
_js_arg_decl _js_args[] = {__VA_ARGS__}; \
int _js_arg_cnt = COUNT_OF(_js_args); \
mjs_val_t _js_arg_vals[_js_arg_cnt]; \
if(!(mjs_nargs(mjs) arg_operator _js_arg_cnt)) \
JS_ERROR_AND_RETURN( \
mjs, \
MJS_BAD_ARGS_ERROR, \
"expected %s%d arguments, got %d", \
#arg_operator, \
_js_arg_cnt, \
mjs_nargs(mjs)); \
for(int _i = 0; _i < _js_arg_cnt; _i++) { \
_js_arg_vals[_i] = mjs_arg(mjs, _i); \
if(_js_args[_i].validator) \
if(!_js_args[_i].validator(_js_arg_vals[_i])) \
JS_ERROR_AND_RETURN( \
mjs, \
MJS_BAD_ARGS_ERROR, \
"argument %d: expected %s", \
_i, \
_js_args[_i].expected_type); \
if(_js_args[_i].extended_validator) \
if(!_js_args[_i].extended_validator(mjs, _js_arg_vals[_i], _js_args[_i].extra_data)) \
JS_ERROR_AND_RETURN( \
mjs, \
MJS_BAD_ARGS_ERROR, \
"argument %d: expected %s", \
_i, \
_js_args[_i].expected_type); \
_js_args[_i].converter( \
mjs, &_js_arg_vals[_i], _js_args[_i].out, _js_args[_i].extra_data); \
#define JS_FETCH_ARGS_OR_RETURN(mjs, arg_operator, ...) \
_js_arg_decl _js_args[] = {__VA_ARGS__}; \
int _js_arg_cnt = COUNT_OF(_js_args); \
mjs_val_t _js_arg_vals[_js_arg_cnt]; \
if(!(mjs_nargs(mjs) arg_operator _js_arg_cnt)) \
JS_ERROR_AND_RETURN( \
mjs, \
MJS_BAD_ARGS_ERROR, \
"expected %s%d arguments, got %d", \
#arg_operator, \
_js_arg_cnt, \
mjs_nargs(mjs)); \
for(int _i = 0; _i < _js_arg_cnt; _i++) { \
_js_arg_vals[_i] = mjs_arg(mjs, _i); \
JS_CONVERT_OR_RETURN(mjs, &_js_arg_vals[_i], _js_args[_i], "argument %d", _i); \
}
/**
@@ -254,6 +296,18 @@ static inline void
return; \
} while(0)
/**
* @brief Prepends an error, sets the JS return value to `undefined` and returns
* a value C function
* @warning This macro executes `return;` by design
*/
#define JS_ERROR_AND_RETURN_VAL(mjs, error_code, ret_val, ...) \
do { \
mjs_prepend_errorf(mjs, error_code, __VA_ARGS__); \
mjs_return(mjs, MJS_UNDEFINED); \
return ret_val; \
} while(0)
typedef struct JsModules JsModules;
typedef void* (*JsModuleConstructor)(struct mjs* mjs, mjs_val_t* object, JsModules* modules);

View File

@@ -92,7 +92,7 @@ static void js_console_debug(struct mjs* mjs) {
}
static void js_exit_flag_poll(struct mjs* mjs) {
uint32_t flags = furi_thread_flags_wait(ThreadEventStop, FuriFlagWaitAny, 0);
uint32_t flags = furi_thread_flags_wait(ThreadEventStop, FuriFlagWaitAny | FuriFlagNoClear, 0);
if(flags & FuriFlagError) {
return;
}
@@ -102,7 +102,8 @@ static void js_exit_flag_poll(struct mjs* mjs) {
}
bool js_delay_with_flags(struct mjs* mjs, uint32_t time) {
uint32_t flags = furi_thread_flags_wait(ThreadEventStop, FuriFlagWaitAny, time);
uint32_t flags =
furi_thread_flags_wait(ThreadEventStop, FuriFlagWaitAny | FuriFlagNoClear, time);
if(flags & FuriFlagError) {
return false;
}
@@ -124,7 +125,7 @@ uint32_t js_flags_wait(struct mjs* mjs, uint32_t flags_mask, uint32_t timeout) {
uint32_t flags = furi_thread_flags_get();
furi_check((flags & FuriFlagError) == 0);
if(flags == 0) {
flags = furi_thread_flags_wait(flags_mask, FuriFlagWaitAny, timeout);
flags = furi_thread_flags_wait(flags_mask, FuriFlagWaitAny | FuriFlagNoClear, timeout);
} else {
uint32_t state = furi_thread_flags_clear(flags & flags_mask);
furi_check((state & FuriFlagError) == 0);

View File

@@ -12,6 +12,7 @@
* @brief Context passed to the generic event callback
*/
typedef struct {
FuriEventLoop* event_loop;
JsEventLoopObjectType object_type;
struct mjs* mjs;
@@ -36,11 +37,6 @@ typedef struct {
void* subscriptions; // SubscriptionArray_t, which we can't reference in this definition
} JsEventLoopSubscription;
typedef struct {
FuriEventLoop* loop;
struct mjs* mjs;
} JsEventLoopTickContext;
ARRAY_DEF(SubscriptionArray, JsEventLoopSubscription*, M_PTR_OPLIST); //-V575
ARRAY_DEF(ContractArray, JsEventLoopContract*, M_PTR_OPLIST); //-V575
@@ -51,7 +47,6 @@ struct JsEventLoop {
FuriEventLoop* loop;
SubscriptionArray_t subscriptions;
ContractArray_t owned_contracts; //<! Contracts that were produced by this module
JsEventLoopTickContext* tick_context;
};
/**
@@ -60,7 +55,7 @@ struct JsEventLoop {
static void js_event_loop_callback_generic(void* param) {
JsEventLoopCallbackContext* context = param;
mjs_val_t result;
mjs_apply(
mjs_err_t error = mjs_apply(
context->mjs,
&result,
context->callback,
@@ -68,6 +63,12 @@ static void js_event_loop_callback_generic(void* param) {
context->arity,
context->arguments);
bool is_error = strcmp(mjs_strerror(context->mjs, error), "NO_ERROR") != 0;
bool asked_to_stop = js_flags_wait(context->mjs, ThreadEventStop, 0) & ThreadEventStop;
if(is_error || asked_to_stop) {
furi_event_loop_stop(context->event_loop);
}
// save returned args for next call
if(mjs_array_length(context->mjs, result) != context->arity - SYSTEM_ARGS) return;
for(size_t i = 0; i < context->arity - SYSTEM_ARGS; i++) {
@@ -111,11 +112,14 @@ static void js_event_loop_subscription_cancel(struct mjs* mjs) {
JsEventLoopSubscription* subscription = JS_GET_CONTEXT(mjs);
if(subscription->object_type == JsEventLoopObjectTypeTimer) {
// timer operations are deferred, which creates lifetime issues
// just stop the timer and let the cleanup routine free everything when the script is done
furi_event_loop_timer_stop(subscription->object);
} else {
furi_event_loop_unsubscribe(subscription->loop, subscription->object);
return;
}
furi_event_loop_unsubscribe(subscription->loop, subscription->object);
free(subscription->context->arguments);
free(subscription->context);
@@ -158,6 +162,7 @@ static void js_event_loop_subscribe(struct mjs* mjs) {
mjs_set(mjs, subscription_obj, "cancel", ~0, MJS_MK_FN(js_event_loop_subscription_cancel));
// create callback context
context->event_loop = module->loop;
context->object_type = contract->object_type;
context->arity = mjs_nargs(mjs) - SYSTEM_ARGS + 2;
context->arguments = calloc(context->arity, sizeof(mjs_val_t));
@@ -333,37 +338,22 @@ static void js_event_loop_queue(struct mjs* mjs) {
mjs_return(mjs, queue);
}
static void js_event_loop_tick(void* param) {
JsEventLoopTickContext* context = param;
uint32_t flags = furi_thread_flags_wait(ThreadEventStop, FuriFlagWaitAny | FuriFlagNoClear, 0);
if(flags & FuriFlagError) {
return;
}
if(flags & ThreadEventStop) {
furi_event_loop_stop(context->loop);
mjs_exit(context->mjs);
}
}
static void* js_event_loop_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) {
UNUSED(modules);
mjs_val_t event_loop_obj = mjs_mk_object(mjs);
JsEventLoop* module = malloc(sizeof(JsEventLoop));
JsEventLoopTickContext* tick_ctx = malloc(sizeof(JsEventLoopTickContext));
module->loop = furi_event_loop_alloc();
tick_ctx->loop = module->loop;
tick_ctx->mjs = mjs;
module->tick_context = tick_ctx;
furi_event_loop_tick_set(module->loop, 10, js_event_loop_tick, tick_ctx);
SubscriptionArray_init(module->subscriptions);
ContractArray_init(module->owned_contracts);
mjs_set(mjs, event_loop_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, module));
mjs_set(mjs, event_loop_obj, "subscribe", ~0, MJS_MK_FN(js_event_loop_subscribe));
mjs_set(mjs, event_loop_obj, "run", ~0, MJS_MK_FN(js_event_loop_run));
mjs_set(mjs, event_loop_obj, "stop", ~0, MJS_MK_FN(js_event_loop_stop));
mjs_set(mjs, event_loop_obj, "timer", ~0, MJS_MK_FN(js_event_loop_timer));
mjs_set(mjs, event_loop_obj, "queue", ~0, MJS_MK_FN(js_event_loop_queue));
JS_ASSIGN_MULTI(mjs, event_loop_obj) {
JS_FIELD(INST_PROP_NAME, mjs_mk_foreign(mjs, module));
JS_FIELD("subscribe", MJS_MK_FN(js_event_loop_subscribe));
JS_FIELD("run", MJS_MK_FN(js_event_loop_run));
JS_FIELD("stop", MJS_MK_FN(js_event_loop_stop));
JS_FIELD("timer", MJS_MK_FN(js_event_loop_timer));
JS_FIELD("queue", MJS_MK_FN(js_event_loop_queue));
}
*object = event_loop_obj;
return module;
@@ -418,7 +408,6 @@ static void js_event_loop_destroy(void* inst) {
ContractArray_clear(module->owned_contracts);
furi_event_loop_free(module->loop);
free(module->tick_context);
free(module);
}
}

View File

@@ -1,6 +1,7 @@
#include "../js_modules.h" // IWYU pragma: keep
#include "./js_event_loop/js_event_loop.h"
#include <furi_hal_gpio.h>
#include <furi_hal_pwm.h>
#include <furi_hal_resources.h>
#include <expansion/expansion.h>
#include <limits.h>
@@ -17,6 +18,7 @@ typedef struct {
FuriSemaphore* interrupt_semaphore;
JsEventLoopContract* interrupt_contract;
FuriHalAdcChannel adc_channel;
FuriHalPwmOutputId pwm_output;
FuriHalAdcHandle* adc_handle;
} JsGpioPinInst;
@@ -231,6 +233,88 @@ static void js_gpio_read_analog(struct mjs* mjs) {
mjs_return(mjs, mjs_mk_number(mjs, (double)millivolts));
}
/**
* @brief Determines whether this pin supports PWM
*
* Example usage:
*
* ```js
* let gpio = require("gpio");
* assert_eq(true, gpio.get("pa4").isPwmSupported());
* assert_eq(false, gpio.get("pa5").isPwmSupported());
* ```
*/
static void js_gpio_is_pwm_supported(struct mjs* mjs) {
JsGpioPinInst* manager_data = JS_GET_CONTEXT(mjs);
mjs_return(mjs, mjs_mk_boolean(mjs, manager_data->pwm_output != FuriHalPwmOutputIdNone));
}
/**
* @brief Sets PWM parameters and starts the PWM
*
* Example usage:
*
* ```js
* let gpio = require("gpio");
* let pa4 = gpio.get("pa4");
* pa4.pwmWrite(10000, 50);
* ```
*/
static void js_gpio_pwm_write(struct mjs* mjs) {
JsGpioPinInst* manager_data = JS_GET_CONTEXT(mjs);
int32_t frequency, duty;
JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_INT32(&frequency), JS_ARG_INT32(&duty));
if(manager_data->pwm_output == FuriHalPwmOutputIdNone) {
JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "PWM is not supported on this pin");
}
if(furi_hal_pwm_is_running(manager_data->pwm_output)) {
furi_hal_pwm_set_params(manager_data->pwm_output, frequency, duty);
} else {
furi_hal_pwm_start(manager_data->pwm_output, frequency, duty);
}
}
/**
* @brief Determines whether PWM is running
*
* Example usage:
*
* ```js
* let gpio = require("gpio");
* assert_eq(false, gpio.get("pa4").isPwmRunning());
* ```
*/
static void js_gpio_is_pwm_running(struct mjs* mjs) {
JsGpioPinInst* manager_data = JS_GET_CONTEXT(mjs);
if(manager_data->pwm_output == FuriHalPwmOutputIdNone) {
JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "PWM is not supported on this pin");
}
mjs_return(mjs, mjs_mk_boolean(mjs, furi_hal_pwm_is_running(manager_data->pwm_output)));
}
/**
* @brief Stops PWM
*
* Example usage:
*
* ```js
* let gpio = require("gpio");
* let pa4 = gpio.get("pa4");
* pa4.pwmWrite(10000, 50);
* pa4.pwmStop();
* ```
*/
static void js_gpio_pwm_stop(struct mjs* mjs) {
JsGpioPinInst* manager_data = JS_GET_CONTEXT(mjs);
if(manager_data->pwm_output != FuriHalPwmOutputIdNone) {
JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "PWM is not supported on this pin");
}
furi_hal_pwm_stop(manager_data->pwm_output);
}
/**
* @brief Returns an object that manages a specified pin.
*
@@ -269,12 +353,19 @@ static void js_gpio_get(struct mjs* mjs) {
manager_data->interrupt_semaphore = furi_semaphore_alloc(UINT32_MAX, 0);
manager_data->adc_handle = module->adc_handle;
manager_data->adc_channel = pin_record->channel;
mjs_set(mjs, manager, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, manager_data));
mjs_set(mjs, manager, "init", ~0, MJS_MK_FN(js_gpio_init));
mjs_set(mjs, manager, "write", ~0, MJS_MK_FN(js_gpio_write));
mjs_set(mjs, manager, "read", ~0, MJS_MK_FN(js_gpio_read));
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));
manager_data->pwm_output = pin_record->pwm_output;
JS_ASSIGN_MULTI(mjs, manager) {
JS_FIELD(INST_PROP_NAME, mjs_mk_foreign(mjs, manager_data));
JS_FIELD("init", MJS_MK_FN(js_gpio_init));
JS_FIELD("write", MJS_MK_FN(js_gpio_write));
JS_FIELD("read", MJS_MK_FN(js_gpio_read));
JS_FIELD("readAnalog", MJS_MK_FN(js_gpio_read_analog));
JS_FIELD("interrupt", MJS_MK_FN(js_gpio_interrupt));
JS_FIELD("isPwmSupported", MJS_MK_FN(js_gpio_is_pwm_supported));
JS_FIELD("pwmWrite", MJS_MK_FN(js_gpio_pwm_write));
JS_FIELD("isPwmRunning", MJS_MK_FN(js_gpio_is_pwm_running));
JS_FIELD("pwmStop", MJS_MK_FN(js_gpio_pwm_stop));
}
mjs_return(mjs, manager);
// remember pin

View File

@@ -0,0 +1,145 @@
#include "../../js_modules.h"
#include <assets_icons.h>
#include <core/dangerous_defines.h>
#include <gui/icon_i.h>
#include <m-list.h>
typedef struct {
const char* name;
const Icon* data;
} IconDefinition;
#define ICON_DEF(icon) \
(IconDefinition) { \
.name = #icon, .data = &I_##icon \
}
static const IconDefinition builtin_icons[] = {
ICON_DEF(DolphinWait_59x54),
ICON_DEF(js_script_10px),
};
// Firmware's Icon struct needs a frames array, and uses a small CompressHeader
// Here we use a variable size allocation to add the uncompressed data in same allocation
// Also use a one-long array pointing to later in the same struct as the frames array
// CompressHeader includes a first is_compressed byte so we don't need to compress (.fxbm is uncompressed)
typedef struct FURI_PACKED {
Icon icon;
uint8_t* frames[1];
struct {
uint8_t is_compressed;
uint8_t uncompressed_data[];
} frame;
} FxbmIconWrapper;
LIST_DEF(FxbmIconWrapperList, FxbmIconWrapper*, M_PTR_OPLIST); // NOLINT
#define M_OPL_FxbmIconWrapperList_t() LIST_OPLIST(FxbmIconWrapperList)
typedef struct {
FxbmIconWrapperList_t fxbm_list;
} JsGuiIconInst;
static void js_gui_icon_get_builtin(struct mjs* mjs) {
const char* icon_name;
JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&icon_name));
for(size_t i = 0; i < COUNT_OF(builtin_icons); i++) {
if(strcmp(icon_name, builtin_icons[i].name) == 0) {
mjs_return(mjs, mjs_mk_foreign(mjs, (void*)builtin_icons[i].data));
return;
}
}
JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "no such built-in icon");
}
static void js_gui_icon_load_fxbm(struct mjs* mjs) {
const char* fxbm_path;
JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&fxbm_path));
Storage* storage = furi_record_open(RECORD_STORAGE);
File* file = storage_file_alloc(storage);
FxbmIconWrapper* fxbm = NULL;
do {
if(!storage_file_open(file, fxbm_path, FSAM_READ, FSOM_OPEN_EXISTING)) {
break;
}
struct {
uint32_t size; // Total following size including width and height values
uint32_t width;
uint32_t height;
} fxbm_header;
if(storage_file_read(file, &fxbm_header, sizeof(fxbm_header)) != sizeof(fxbm_header)) {
break;
}
size_t frame_size = fxbm_header.size - sizeof(uint32_t) * 2;
fxbm = malloc(sizeof(FxbmIconWrapper) + frame_size);
if(storage_file_read(file, fxbm->frame.uncompressed_data, frame_size) != frame_size) {
free(fxbm);
fxbm = NULL;
break;
}
FURI_CONST_ASSIGN(fxbm->icon.width, fxbm_header.width);
FURI_CONST_ASSIGN(fxbm->icon.height, fxbm_header.height);
FURI_CONST_ASSIGN(fxbm->icon.frame_count, 1);
FURI_CONST_ASSIGN(fxbm->icon.frame_rate, 1);
FURI_CONST_ASSIGN_PTR(fxbm->icon.frames, fxbm->frames);
fxbm->frames[0] = (void*)&fxbm->frame;
fxbm->frame.is_compressed = false;
} while(false);
storage_file_free(file);
furi_record_close(RECORD_STORAGE);
if(!fxbm) {
JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "could not load .fxbm icon");
}
JsGuiIconInst* js_icon = JS_GET_CONTEXT(mjs);
FxbmIconWrapperList_push_back(js_icon->fxbm_list, fxbm);
mjs_return(mjs, mjs_mk_foreign(mjs, (void*)&fxbm->icon));
}
static void* js_gui_icon_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) {
UNUSED(modules);
JsGuiIconInst* js_icon = malloc(sizeof(JsGuiIconInst));
FxbmIconWrapperList_init(js_icon->fxbm_list);
*object = mjs_mk_object(mjs);
JS_ASSIGN_MULTI(mjs, *object) {
JS_FIELD(INST_PROP_NAME, mjs_mk_foreign(mjs, js_icon));
JS_FIELD("getBuiltin", MJS_MK_FN(js_gui_icon_get_builtin));
JS_FIELD("loadFxbm", MJS_MK_FN(js_gui_icon_load_fxbm));
}
return js_icon;
}
static void js_gui_icon_destroy(void* inst) {
JsGuiIconInst* js_icon = inst;
for
M_EACH(fxbm, js_icon->fxbm_list, FxbmIconWrapperList_t) {
free(*fxbm);
}
FxbmIconWrapperList_clear(js_icon->fxbm_list);
free(js_icon);
}
static const JsModuleDescriptor js_gui_icon_desc = {
"gui__icon",
js_gui_icon_create,
js_gui_icon_destroy,
NULL,
};
static const FlipperAppPluginDescriptor plugin_descriptor = {
.appid = PLUGIN_APP_ID,
.ep_api_version = PLUGIN_API_VERSION,
.entry_point = &js_gui_icon_desc,
};
const FlipperAppPluginDescriptor* js_gui_icon_ep(void) {
return &plugin_descriptor;
}

View File

@@ -247,6 +247,22 @@ static bool
return false;
}
/**
* @brief Sets the list of children. Not available from JS.
*/
static bool
js_gui_view_internal_set_children(struct mjs* mjs, mjs_val_t children, JsGuiViewData* data) {
data->descriptor->reset_children(data->specific_view, data->custom_data);
for(size_t i = 0; i < mjs_array_length(mjs, children); i++) {
mjs_val_t child = mjs_array_get(mjs, children, i);
if(!data->descriptor->add_child(mjs, data->specific_view, data->custom_data, child))
return false;
}
return true;
}
/**
* @brief `View.set`
*/
@@ -260,6 +276,46 @@ static void js_gui_view_set(struct mjs* mjs) {
mjs_return(mjs, MJS_UNDEFINED);
}
/**
* @brief `View.addChild`
*/
static void js_gui_view_add_child(struct mjs* mjs) {
JsGuiViewData* data = JS_GET_CONTEXT(mjs);
if(!data->descriptor->add_child || !data->descriptor->reset_children)
JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "this View can't have children");
mjs_val_t child;
JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_ANY(&child));
bool success = data->descriptor->add_child(mjs, data->specific_view, data->custom_data, child);
UNUSED(success);
mjs_return(mjs, MJS_UNDEFINED);
}
/**
* @brief `View.resetChildren`
*/
static void js_gui_view_reset_children(struct mjs* mjs) {
JsGuiViewData* data = JS_GET_CONTEXT(mjs);
if(!data->descriptor->add_child || !data->descriptor->reset_children)
JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "this View can't have children");
data->descriptor->reset_children(data->specific_view, data->custom_data);
mjs_return(mjs, MJS_UNDEFINED);
}
/**
* @brief `View.setChildren`
*/
static void js_gui_view_set_children(struct mjs* mjs) {
JsGuiViewData* data = JS_GET_CONTEXT(mjs);
if(!data->descriptor->add_child || !data->descriptor->reset_children)
JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "this View can't have children");
mjs_val_t children;
JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_ARR(&children));
js_gui_view_internal_set_children(mjs, children, data);
}
/**
* @brief `View` destructor
*/
@@ -283,7 +339,12 @@ static mjs_val_t js_gui_make_view(struct mjs* mjs, const JsViewDescriptor* descr
// generic view API
mjs_val_t view_obj = mjs_mk_object(mjs);
mjs_set(mjs, view_obj, "set", ~0, MJS_MK_FN(js_gui_view_set));
JS_ASSIGN_MULTI(mjs, view_obj) {
JS_FIELD("set", MJS_MK_FN(js_gui_view_set));
JS_FIELD("addChild", MJS_MK_FN(js_gui_view_add_child));
JS_FIELD("resetChildren", MJS_MK_FN(js_gui_view_reset_children));
JS_FIELD("setChildren", MJS_MK_FN(js_gui_view_set_children));
}
// object data
JsGuiViewData* data = malloc(sizeof(JsGuiViewData));
@@ -314,7 +375,7 @@ static void js_gui_vf_make(struct mjs* mjs) {
*/
static void js_gui_vf_make_with(struct mjs* mjs) {
mjs_val_t props;
JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_OBJ(&props));
JS_FETCH_ARGS_OR_RETURN(mjs, JS_AT_LEAST, JS_ARG_OBJ(&props));
const JsViewDescriptor* descriptor = JS_GET_CONTEXT(mjs);
// make the object like normal
@@ -334,6 +395,18 @@ static void js_gui_vf_make_with(struct mjs* mjs) {
}
}
// assign children
if(mjs_nargs(mjs) >= 2) {
if(!data->descriptor->add_child || !data->descriptor->reset_children)
JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "this View can't have children");
mjs_val_t children = mjs_arg(mjs, 1);
if(!mjs_is_array(children))
JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "argument 1: expected array");
if(!js_gui_view_internal_set_children(mjs, children, data)) return;
}
mjs_return(mjs, view_obj);
}

View File

@@ -50,6 +50,11 @@ typedef void (*JsViewFree)(void* specific_view);
typedef void* (*JsViewCustomMake)(struct mjs* mjs, void* specific_view, mjs_val_t view_obj);
/** @brief Context destruction for glue code */
typedef void (*JsViewCustomDestroy)(void* specific_view, void* custom_state, FuriEventLoop* loop);
/** @brief `addChild` callback for glue code */
typedef bool (
*JsViewAddChild)(struct mjs* mjs, void* specific_view, void* custom_state, mjs_val_t child_obj);
/** @brief `resetChildren` callback for glue code */
typedef void (*JsViewResetChildren)(void* specific_view, void* custom_state);
/**
* @brief Descriptor for a JS view
@@ -66,15 +71,22 @@ typedef struct {
JsViewAlloc alloc;
JsViewGetView get_view;
JsViewFree free;
JsViewCustomMake custom_make; // <! May be NULL
JsViewCustomDestroy custom_destroy; // <! May be NULL
JsViewAddChild add_child; // <! May be NULL
JsViewResetChildren reset_children; // <! May be NULL
size_t prop_cnt; //<! Number of properties visible from JS
JsViewPropDescriptor props[]; // <! Descriptors of properties visible from JS
} JsViewDescriptor;
// Callback ordering:
// alloc -> get_view -> [custom_make (if set)] -> props[i].assign -> [custom_destroy (if_set)] -> free
// \_______________ creation ________________/ \___ usage ___/ \_________ destruction _________/
// +-> add_child -+
// +-> reset_children -+
// alloc -> get_view -> custom_make -+-> props[i].assign -+> custom_destroy -> free
// \__________ creation __________/ \____ use ____/ \___ destruction ____/
/**
* @brief Creates a JS `ViewFactory` object

View File

@@ -0,0 +1,317 @@
#include "../../js_modules.h" // IWYU pragma: keep
#include "js_gui.h"
#include "../js_event_loop/js_event_loop.h"
#include <gui/modules/widget.h>
typedef struct {
FuriMessageQueue* queue;
JsEventLoopContract contract;
} JsWidgetCtx;
#define QUEUE_LEN 2
/**
* @brief Parses position (X and Y) from an element declaration object
*/
static bool element_get_position(struct mjs* mjs, mjs_val_t element, int32_t* x, int32_t* y) {
mjs_val_t x_in = mjs_get(mjs, element, "x", ~0);
mjs_val_t y_in = mjs_get(mjs, element, "y", ~0);
if(!mjs_is_number(x_in) || !mjs_is_number(y_in)) return false;
*x = mjs_get_int32(mjs, x_in);
*y = mjs_get_int32(mjs, y_in);
return true;
}
/**
* @brief Parses size (W and h) from an element declaration object
*/
static bool element_get_size(struct mjs* mjs, mjs_val_t element, int32_t* w, int32_t* h) {
mjs_val_t w_in = mjs_get(mjs, element, "w", ~0);
mjs_val_t h_in = mjs_get(mjs, element, "h", ~0);
if(!mjs_is_number(w_in) || !mjs_is_number(h_in)) return false;
*w = mjs_get_int32(mjs, w_in);
*h = mjs_get_int32(mjs, h_in);
return true;
}
/**
* @brief Parses alignment (V and H) from an element declaration object
*/
static bool
element_get_alignment(struct mjs* mjs, mjs_val_t element, Align* align_v, Align* align_h) {
mjs_val_t align_in = mjs_get(mjs, element, "align", ~0);
const char* align = mjs_get_string(mjs, &align_in, NULL);
if(!align) return false;
if(strlen(align) != 2) return false;
if(align[0] == 't') {
*align_v = AlignTop;
} else if(align[0] == 'c') {
*align_v = AlignCenter;
} else if(align[0] == 'b') {
*align_v = AlignBottom;
} else {
return false;
}
if(align[1] == 'l') {
*align_h = AlignLeft;
} else if(align[1] == 'm') { // m = middle
*align_h = AlignCenter;
} else if(align[1] == 'r') {
*align_h = AlignRight;
} else {
return false;
}
return true;
}
/**
* @brief Parses font from an element declaration object
*/
static bool element_get_font(struct mjs* mjs, mjs_val_t element, Font* font) {
mjs_val_t font_in = mjs_get(mjs, element, "font", ~0);
const char* font_str = mjs_get_string(mjs, &font_in, NULL);
if(!font_str) return false;
if(strcmp(font_str, "primary") == 0) {
*font = FontPrimary;
} else if(strcmp(font_str, "secondary") == 0) {
*font = FontSecondary;
} else if(strcmp(font_str, "keyboard") == 0) {
*font = FontKeyboard;
} else if(strcmp(font_str, "big_numbers") == 0) {
*font = FontBigNumbers;
} else {
return false;
}
return true;
}
/**
* @brief Parses text from an element declaration object
*/
static bool element_get_text(struct mjs* mjs, mjs_val_t element, mjs_val_t* text) {
*text = mjs_get(mjs, element, "text", ~0);
return mjs_is_string(*text);
}
/**
* @brief Widget button element callback
*/
static void js_widget_button_callback(GuiButtonType result, InputType type, JsWidgetCtx* context) {
UNUSED(type);
furi_check(furi_message_queue_put(context->queue, &result, 0) == FuriStatusOk);
}
#define DESTRUCTURE_OR_RETURN(mjs, child_obj, part, ...) \
if(!element_get_##part(mjs, child_obj, __VA_ARGS__)) \
JS_ERROR_AND_RETURN_VAL(mjs, MJS_BAD_ARGS_ERROR, false, "failed to fetch element " #part);
static bool js_widget_add_child(
struct mjs* mjs,
Widget* widget,
JsWidgetCtx* context,
mjs_val_t child_obj) {
UNUSED(context);
if(!mjs_is_object(child_obj))
JS_ERROR_AND_RETURN_VAL(mjs, MJS_BAD_ARGS_ERROR, false, "child must be an object");
mjs_val_t element_type_term = mjs_get(mjs, child_obj, "element", ~0);
const char* element_type = mjs_get_string(mjs, &element_type_term, NULL);
if(!element_type)
JS_ERROR_AND_RETURN_VAL(
mjs, MJS_BAD_ARGS_ERROR, false, "child object must have `element` property");
if((strcmp(element_type, "string") == 0) || (strcmp(element_type, "string_multiline") == 0)) {
int32_t x, y;
Align align_v, align_h;
Font font;
mjs_val_t text;
DESTRUCTURE_OR_RETURN(mjs, child_obj, position, &x, &y);
DESTRUCTURE_OR_RETURN(mjs, child_obj, alignment, &align_v, &align_h);
DESTRUCTURE_OR_RETURN(mjs, child_obj, font, &font);
DESTRUCTURE_OR_RETURN(mjs, child_obj, text, &text);
if(strcmp(element_type, "string") == 0) {
widget_add_string_element(
widget, x, y, align_h, align_v, font, mjs_get_string(mjs, &text, NULL));
} else {
widget_add_string_multiline_element(
widget, x, y, align_h, align_v, font, mjs_get_string(mjs, &text, NULL));
}
} else if(strcmp(element_type, "text_box") == 0) {
int32_t x, y, w, h;
Align align_v, align_h;
Font font;
mjs_val_t text;
DESTRUCTURE_OR_RETURN(mjs, child_obj, position, &x, &y);
DESTRUCTURE_OR_RETURN(mjs, child_obj, size, &w, &h);
DESTRUCTURE_OR_RETURN(mjs, child_obj, alignment, &align_v, &align_h);
DESTRUCTURE_OR_RETURN(mjs, child_obj, font, &font);
DESTRUCTURE_OR_RETURN(mjs, child_obj, text, &text);
mjs_val_t strip_to_dots_in = mjs_get(mjs, child_obj, "stripToDots", ~0);
if(!mjs_is_boolean(strip_to_dots_in))
JS_ERROR_AND_RETURN_VAL(
mjs, MJS_BAD_ARGS_ERROR, false, "failed to fetch element stripToDots");
bool strip_to_dots = mjs_get_bool(mjs, strip_to_dots_in);
widget_add_text_box_element(
widget, x, y, w, h, align_h, align_v, mjs_get_string(mjs, &text, NULL), strip_to_dots);
} else if(strcmp(element_type, "text_scroll") == 0) {
int32_t x, y, w, h;
mjs_val_t text;
DESTRUCTURE_OR_RETURN(mjs, child_obj, position, &x, &y);
DESTRUCTURE_OR_RETURN(mjs, child_obj, size, &w, &h);
DESTRUCTURE_OR_RETURN(mjs, child_obj, text, &text);
widget_add_text_scroll_element(widget, x, y, w, h, mjs_get_string(mjs, &text, NULL));
} else if(strcmp(element_type, "button") == 0) {
mjs_val_t btn_in = mjs_get(mjs, child_obj, "button", ~0);
const char* btn_name = mjs_get_string(mjs, &btn_in, NULL);
if(!btn_name)
JS_ERROR_AND_RETURN_VAL(
mjs, MJS_BAD_ARGS_ERROR, false, "failed to fetch element button");
GuiButtonType btn_type;
if(strcmp(btn_name, "left") == 0) {
btn_type = GuiButtonTypeLeft;
} else if(strcmp(btn_name, "center") == 0) {
btn_type = GuiButtonTypeCenter;
} else if(strcmp(btn_name, "right") == 0) {
btn_type = GuiButtonTypeRight;
} else {
JS_ERROR_AND_RETURN_VAL(mjs, MJS_BAD_ARGS_ERROR, false, "incorrect button type");
}
mjs_val_t text;
DESTRUCTURE_OR_RETURN(mjs, child_obj, text, &text);
widget_add_button_element(
widget,
btn_type,
mjs_get_string(mjs, &text, NULL),
(ButtonCallback)js_widget_button_callback,
context);
} else if(strcmp(element_type, "icon") == 0) {
int32_t x, y;
DESTRUCTURE_OR_RETURN(mjs, child_obj, position, &x, &y);
mjs_val_t icon_data_in = mjs_get(mjs, child_obj, "iconData", ~0);
if(!mjs_is_foreign(icon_data_in))
JS_ERROR_AND_RETURN_VAL(
mjs, MJS_BAD_ARGS_ERROR, false, "failed to fetch element iconData");
const Icon* icon = mjs_get_ptr(mjs, icon_data_in);
widget_add_icon_element(widget, x, y, icon);
} else if(strcmp(element_type, "rect") == 0) {
int32_t x, y, w, h;
DESTRUCTURE_OR_RETURN(mjs, child_obj, position, &x, &y);
DESTRUCTURE_OR_RETURN(mjs, child_obj, size, &w, &h);
mjs_val_t radius_in = mjs_get(mjs, child_obj, "radius", ~0);
if(!mjs_is_number(radius_in))
JS_ERROR_AND_RETURN_VAL(
mjs, MJS_BAD_ARGS_ERROR, false, "failed to fetch element radius");
int32_t radius = mjs_get_int32(mjs, radius_in);
mjs_val_t fill_in = mjs_get(mjs, child_obj, "fill", ~0);
if(!mjs_is_boolean(fill_in))
JS_ERROR_AND_RETURN_VAL(
mjs, MJS_BAD_ARGS_ERROR, false, "failed to fetch element fill");
int32_t fill = mjs_get_bool(mjs, fill_in);
widget_add_rect_element(widget, x, y, w, h, radius, fill);
} else if(strcmp(element_type, "circle") == 0) {
int32_t x, y;
DESTRUCTURE_OR_RETURN(mjs, child_obj, position, &x, &y);
mjs_val_t radius_in = mjs_get(mjs, child_obj, "radius", ~0);
if(!mjs_is_number(radius_in))
JS_ERROR_AND_RETURN_VAL(
mjs, MJS_BAD_ARGS_ERROR, false, "failed to fetch element radius");
int32_t radius = mjs_get_int32(mjs, radius_in);
mjs_val_t fill_in = mjs_get(mjs, child_obj, "fill", ~0);
if(!mjs_is_boolean(fill_in))
JS_ERROR_AND_RETURN_VAL(
mjs, MJS_BAD_ARGS_ERROR, false, "failed to fetch element fill");
int32_t fill = mjs_get_bool(mjs, fill_in);
widget_add_circle_element(widget, x, y, radius, fill);
} else if(strcmp(element_type, "line") == 0) {
int32_t x1, y1, x2, y2;
mjs_val_t x1_in = mjs_get(mjs, child_obj, "x1", ~0);
mjs_val_t y1_in = mjs_get(mjs, child_obj, "y1", ~0);
mjs_val_t x2_in = mjs_get(mjs, child_obj, "x2", ~0);
mjs_val_t y2_in = mjs_get(mjs, child_obj, "y2", ~0);
if(!mjs_is_number(x1_in) || !mjs_is_number(y1_in) || !mjs_is_number(x2_in) ||
!mjs_is_number(y2_in))
JS_ERROR_AND_RETURN_VAL(
mjs, MJS_BAD_ARGS_ERROR, false, "failed to fetch element positions");
x1 = mjs_get_int32(mjs, x1_in);
y1 = mjs_get_int32(mjs, y1_in);
x2 = mjs_get_int32(mjs, x2_in);
y2 = mjs_get_int32(mjs, y2_in);
widget_add_line_element(widget, x1, y1, x2, y2);
}
return true;
}
static void js_widget_reset_children(Widget* widget, void* state) {
UNUSED(state);
widget_reset(widget);
}
static mjs_val_t js_widget_button_event_transformer(
struct mjs* mjs,
FuriMessageQueue* queue,
JsWidgetCtx* context) {
UNUSED(context);
GuiButtonType btn_type;
furi_check(furi_message_queue_get(queue, &btn_type, 0) == FuriStatusOk);
const char* btn_name;
if(btn_type == GuiButtonTypeLeft) {
btn_name = "left";
} else if(btn_type == GuiButtonTypeCenter) {
btn_name = "center";
} else if(btn_type == GuiButtonTypeRight) {
btn_name = "right";
} else {
furi_crash();
}
return mjs_mk_string(mjs, btn_name, ~0, false);
}
static void* js_widget_custom_make(struct mjs* mjs, Widget* widget, mjs_val_t view_obj) {
UNUSED(widget);
JsWidgetCtx* context = malloc(sizeof(JsWidgetCtx));
context->queue = furi_message_queue_alloc(QUEUE_LEN, sizeof(GuiButtonType));
context->contract = (JsEventLoopContract){
.magic = JsForeignMagic_JsEventLoopContract,
.object_type = JsEventLoopObjectTypeQueue,
.object = context->queue,
.non_timer =
{
.event = FuriEventLoopEventIn,
.transformer = (JsEventLoopTransformer)js_widget_button_event_transformer,
},
};
mjs_set(mjs, view_obj, "button", ~0, mjs_mk_foreign(mjs, &context->contract));
return context;
}
static void js_widget_custom_destroy(Widget* widget, JsWidgetCtx* context, FuriEventLoop* loop) {
UNUSED(widget);
furi_event_loop_maybe_unsubscribe(loop, context->queue);
furi_message_queue_free(context->queue);
free(context);
}
static const JsViewDescriptor view_descriptor = {
.alloc = (JsViewAlloc)widget_alloc,
.free = (JsViewFree)widget_free,
.get_view = (JsViewGetView)widget_get_view,
.custom_make = (JsViewCustomMake)js_widget_custom_make,
.custom_destroy = (JsViewCustomDestroy)js_widget_custom_destroy,
.add_child = (JsViewAddChild)js_widget_add_child,
.reset_children = (JsViewResetChildren)js_widget_reset_children,
.prop_cnt = 0,
.props = {},
};
JS_GUI_VIEW_DEF(widget, &view_descriptor);

View File

@@ -20,14 +20,6 @@ typedef struct {
char* data;
} PatternArrayItem;
static const struct {
const char* name;
const FuriHalSerialId value;
} serial_channels[] = {
{"usart", FuriHalSerialIdUsart},
{"lpuart", FuriHalSerialIdLpuart},
};
ARRAY_DEF(PatternArray, PatternArrayItem, M_POD_OPLIST);
static void
@@ -43,9 +35,54 @@ static void
}
static void js_serial_setup(struct mjs* mjs) {
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0);
JsSerialInst* serial = mjs_get_ptr(mjs, obj_inst);
furi_assert(serial);
FuriHalSerialId serial_id;
int32_t baudrate;
JS_ENUM_MAP(serial_id, {"lpuart", FuriHalSerialIdLpuart}, {"usart", FuriHalSerialIdUsart});
JS_FETCH_ARGS_OR_RETURN(
mjs, JS_AT_LEAST, JS_ARG_ENUM(serial_id, "SerialId"), JS_ARG_INT32(&baudrate));
FuriHalSerialDataBits data_bits = FuriHalSerialDataBits8;
FuriHalSerialParity parity = FuriHalSerialParityNone;
FuriHalSerialStopBits stop_bits = FuriHalSerialStopBits1;
if(mjs_nargs(mjs) > 2) {
struct framing {
mjs_val_t data_bits;
mjs_val_t parity;
mjs_val_t stop_bits;
} framing;
JS_OBJ_MAP(
framing,
{"dataBits", offsetof(struct framing, data_bits)},
{"parity", offsetof(struct framing, parity)},
{"stopBits", offsetof(struct framing, stop_bits)});
JS_ENUM_MAP(
data_bits,
{"6", FuriHalSerialDataBits6},
{"7", FuriHalSerialDataBits7},
{"8", FuriHalSerialDataBits8},
{"9", FuriHalSerialDataBits9});
JS_ENUM_MAP(
parity,
{"none", FuriHalSerialParityNone},
{"even", FuriHalSerialParityEven},
{"odd", FuriHalSerialParityOdd});
JS_ENUM_MAP(
stop_bits,
{"0.5", FuriHalSerialStopBits0_5},
{"1", FuriHalSerialStopBits1},
{"1.5", FuriHalSerialStopBits1_5},
{"2", FuriHalSerialStopBits2});
mjs_val_t framing_obj = mjs_arg(mjs, 2);
JS_CONVERT_OR_RETURN(mjs, &framing_obj, JS_ARG_OBJECT(framing, "Framing"), "argument 2");
JS_CONVERT_OR_RETURN(
mjs, &framing.data_bits, JS_ARG_ENUM(data_bits, "DataBits"), "argument 2: dataBits");
JS_CONVERT_OR_RETURN(
mjs, &framing.parity, JS_ARG_ENUM(parity, "Parity"), "argument 2: parity");
JS_CONVERT_OR_RETURN(
mjs, &framing.stop_bits, JS_ARG_ENUM(stop_bits, "StopBits"), "argument 2: stopBits");
}
JsSerialInst* serial = JS_GET_CONTEXT(mjs);
if(serial->setup_done) {
mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Serial is already configured");
@@ -53,43 +90,6 @@ static void js_serial_setup(struct mjs* mjs) {
return;
}
bool args_correct = false;
FuriHalSerialId serial_id = FuriHalSerialIdMax;
uint32_t baudrate = 0;
do {
if(mjs_nargs(mjs) != 2) break;
mjs_val_t arg = mjs_arg(mjs, 0);
if(!mjs_is_string(arg)) break;
size_t str_len = 0;
const char* arg_str = mjs_get_string(mjs, &arg, &str_len);
for(size_t i = 0; i < COUNT_OF(serial_channels); i++) {
size_t name_len = strlen(serial_channels[i].name);
if(str_len != name_len) continue;
if(strncmp(arg_str, serial_channels[i].name, str_len) == 0) {
serial_id = serial_channels[i].value;
break;
}
}
if(serial_id == FuriHalSerialIdMax) {
break;
}
arg = mjs_arg(mjs, 1);
if(!mjs_is_number(arg)) break;
baudrate = mjs_get_int32(mjs, arg);
args_correct = true;
} while(0);
if(!args_correct) {
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "");
mjs_return(mjs, MJS_UNDEFINED);
return;
}
expansion_disable(furi_record_open(RECORD_EXPANSION));
furi_record_close(RECORD_EXPANSION);
@@ -97,6 +97,7 @@ static void js_serial_setup(struct mjs* mjs) {
if(serial->serial_handle) {
serial->rx_stream = furi_stream_buffer_alloc(RX_BUF_LEN, 1);
furi_hal_serial_init(serial->serial_handle, baudrate);
furi_hal_serial_configure_framing(serial->serial_handle, data_bits, parity, stop_bits);
furi_hal_serial_async_rx_start(
serial->serial_handle, js_serial_on_async_rx, serial, false);
serial->setup_done = true;

View File

@@ -6,7 +6,7 @@
"start": "npm run build && node node_modules/@flipperdevices/fz-sdk/sdk.js upload"
},
"devDependencies": {
"@flipperdevices/fz-sdk": "^0.1",
"@flipperdevices/fz-sdk": "^0.3",
"typescript": "^5.6.3"
}
}

View File

@@ -72,7 +72,7 @@
* @brief Checks compatibility between the script and the JS SDK that the
* firmware provides
*
* @note You're looking at JS SDK v0.1
* @note You're looking at JS SDK v0.3
*
* @param expectedMajor JS SDK major version expected by the script
* @param expectedMinor JS SDK minor version expected by the script
@@ -92,7 +92,7 @@ declare function sdkCompatibilityStatus(expectedMajor: number, expectedMinor: nu
* @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
* @note You're looking at JS SDK v0.3
*
* @param expectedMajor JS SDK major version expected by the script
* @param expectedMinor JS SDK minor version expected by the script
@@ -105,7 +105,7 @@ declare function isSdkCompatible(expectedMajor: number, expectedMinor: number):
* @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
* @note You're looking at JS SDK v0.3
*
* @param expectedMajor JS SDK major version expected by the script
* @param expectedMinor JS SDK minor version expected by the script

View File

@@ -75,6 +75,34 @@ export interface Pin {
* @version Added in JS SDK 0.1
*/
interrupt(): Contract;
/**
* Determines whether this pin supports PWM. If `false`, all other
* PWM-related methods on this pin will throw an error when called.
* @note On Flipper Zero only pins PA4 and PA7 support PWM
* @version Added in JS SDK 0.2, extra feature `"gpio-pwm"`
*/
isPwmSupported(): boolean;
/**
* Sets PWM parameters and starts the PWM. Configures the pin with
* `{ direction: "out", outMode: "push_pull" }`. Throws an error if PWM is
* not supported on this pin.
* @param freq Frequency in Hz
* @param duty Duty cycle in %
* @version Added in JS SDK 0.2, extra feature `"gpio-pwm"`
*/
pwmWrite(freq: number, duty: number): void;
/**
* Determines whether PWM is running. Throws an error if PWM is not
* supported on this pin.
* @version Added in JS SDK 0.2, extra feature `"gpio-pwm"`
*/
isPwmRunning(): boolean;
/**
* Stops PWM. Does not restore previous pin configuration. Throws an error
* if PWM is not supported on this pin.
* @version Added in JS SDK 0.2, extra feature `"gpio-pwm"`
*/
pwmStop(): void;
}
/**

View File

@@ -33,9 +33,10 @@ type Props = {
length: number,
defaultData: Uint8Array | ArrayBuffer,
}
declare class ByteInput extends View<Props> {
type Child = never;
declare class ByteInput extends View<Props, Child> {
input: Contract<string>;
}
declare class ByteInputFactory extends ViewFactory<Props, ByteInput> { }
declare class ByteInputFactory extends ViewFactory<Props, Child, ByteInput> { }
declare const factory: ByteInputFactory;
export = factory;

View File

@@ -37,9 +37,10 @@ type Props = {
center: string,
right: string,
}
declare class Dialog extends View<Props> {
type Child = never;
declare class Dialog extends View<Props, Child> {
input: Contract<"left" | "center" | "right">;
}
declare class DialogFactory extends ViewFactory<Props, Dialog> { }
declare class DialogFactory extends ViewFactory<Props, Child, Dialog> { }
declare const factory: DialogFactory;
export = factory;

View File

@@ -26,7 +26,8 @@
import type { View, ViewFactory } from ".";
type Props = {};
declare class EmptyScreen extends View<Props> { }
declare class EmptyScreenFactory extends ViewFactory<Props, EmptyScreen> { }
type Child = never;
declare class EmptyScreen extends View<Props, Child> { }
declare class EmptyScreenFactory extends ViewFactory<Props, Child, EmptyScreen> { }
declare const factory: EmptyScreenFactory;
export = factory;

View File

@@ -0,0 +1,18 @@
export type BuiltinIcon = "DolphinWait_59x54" | "js_script_10px";
export type IconData = symbol & { "__tag__": "icon" };
// introducing a nominal type in a hacky way; the `__tag__` property doesn't really exist.
/**
* Gets a built-in firmware icon for use in GUI
* @param icon Name of the icon
* @version Added in JS SDK 0.2, extra feature `"gui-widget"`
*/
export declare function getBuiltin(icon: BuiltinIcon): IconData;
/**
* Loads a .fxbm icon (XBM Flipper sprite, from flipperzero-game-engine) for use in GUI
* @param path Path to the .fxbm file
* @version Added in JS SDK 0.3, extra feature `"gui-widget-extras"`
*/
export declare function loadFxbm(path: string): IconData;

View File

@@ -26,23 +26,23 @@
* assumes control over the entire viewport and all input events. Different
* types of views are available (not all of which are unfortunately currently
* implemented in JS):
* | View | Has JS adapter? |
* |----------------------|------------------|
* | `button_menu` | ❌ |
* | `button_panel` | ❌ |
* | `byte_input` | ✅ |
* | `dialog_ex` | ✅ (as `dialog`) |
* | `empty_screen` | ✅ |
* | `file_browser` | |
* | `loading` | ✅ |
* | `menu` | ❌ |
* | `number_input` | ❌ |
* | `popup` | ❌ |
* | `submenu` | ✅ |
* | `text_box` | ✅ |
* | `text_input` | ✅ |
* | `variable_item_list` | ❌ |
* | `widget` | |
* | View | Has JS adapter? |
* |----------------------|-----------------------|
* | `button_menu` | ❌ |
* | `button_panel` | ❌ |
* | `byte_input` | ✅ |
* | `dialog_ex` | ✅ (as `dialog`) |
* | `empty_screen` | ✅ |
* | `file_browser` | ✅ (as `file_picker`) |
* | `loading` | ✅ |
* | `menu` | ❌ |
* | `number_input` | ❌ |
* | `popup` | ❌ |
* | `submenu` | ✅ |
* | `text_box` | ✅ |
* | `text_input` | ✅ |
* | `variable_item_list` | ❌ |
* | `widget` | |
*
* In JS, each view has its own set of properties (or just "props"). The
* programmer can manipulate these properties in two ways:
@@ -121,7 +121,7 @@ import type { Contract } from "../event_loop";
type Properties = { [K: string]: any };
export declare class View<Props extends Properties> {
export declare class View<Props extends Properties, Child> {
/**
* Assign value to property by name
* @param property Name of the property
@@ -129,9 +129,26 @@ export declare class View<Props extends Properties> {
* @version Added in JS SDK 0.1
*/
set<P extends keyof Props>(property: P, value: Props[P]): void;
/**
* Adds a child to the View
* @param child Child to add
* @version Added in JS SDK 0.2, extra feature `"gui-widget"`
*/
addChild<C extends Child>(child: C): void;
/**
* Removes all children from the View
* @version Added in JS SDK 0.2, extra feature `"gui-widget"`
*/
resetChildren(): void;
/**
* Removes all previous children from the View and assigns new children
* @param children The list of children to assign
* @version Added in JS SDK 0.2, extra feature `"gui-widget"`
*/
setChildren(children: Child[]): void;
}
export declare class ViewFactory<Props extends Properties, V extends View<Props>> {
export declare class ViewFactory<Props extends Properties, Child, V extends View<Props, Child>> {
/**
* Create view instance with default values, can be changed later with set()
* @version Added in JS SDK 0.1
@@ -140,9 +157,10 @@ export declare class ViewFactory<Props extends Properties, V extends View<Props>
/**
* Create view instance with custom values, can be changed later with set()
* @param initial Dictionary of property names to values
* @version Added in JS SDK 0.1
* @param children Optional list of children to add to the view
* @version Added in JS SDK 0.1; amended in JS SDK 0.2, extra feature `"gui-widget"`
*/
makeWith(initial: Partial<Props>): V;
makeWith(initial: Partial<Props>, children?: Child[]): V;
}
/**
@@ -163,7 +181,7 @@ declare class ViewDispatcher {
* View object currently shown
* @version Added in JS SDK 0.1
*/
currentView: View<any>;
currentView: View<any, any>;
/**
* Sends a number to the custom event handler
* @param event number to send
@@ -175,7 +193,7 @@ declare class ViewDispatcher {
* @param assoc View-ViewDispatcher association as returned by `add`
* @version Added in JS SDK 0.1
*/
switchTo(assoc: View<any>): void;
switchTo(assoc: View<any, any>): void;
/**
* Sends this ViewDispatcher to the front or back, above or below all other
* GUI viewports

View File

@@ -27,7 +27,8 @@
import type { View, ViewFactory } from ".";
type Props = {};
declare class Loading extends View<Props> { }
declare class LoadingFactory extends ViewFactory<Props, Loading> { }
type Child = never;
declare class Loading extends View<Props, Child> { }
declare class LoadingFactory extends ViewFactory<Props, Child, Loading> { }
declare const factory: LoadingFactory;
export = factory;

View File

@@ -31,9 +31,10 @@ type Props = {
header: string,
items: string[],
};
declare class Submenu extends View<Props> {
type Child = never;
declare class Submenu extends View<Props, Child> {
chosen: Contract<number>;
}
declare class SubmenuFactory extends ViewFactory<Props, Submenu> { }
declare class SubmenuFactory extends ViewFactory<Props, Child, Submenu> { }
declare const factory: SubmenuFactory;
export = factory;

View File

@@ -33,9 +33,10 @@ type Props = {
font: "text" | "hex",
focus: "start" | "end",
}
declare class TextBox extends View<Props> {
type Child = never;
declare class TextBox extends View<Props, Child> {
chosen: Contract<number>;
}
declare class TextBoxFactory extends ViewFactory<Props, TextBox> { }
declare class TextBoxFactory extends ViewFactory<Props, Child, TextBox> { }
declare const factory: TextBoxFactory;
export = factory;

View File

@@ -37,9 +37,10 @@ type Props = {
defaultText: string,
defaultTextClear: boolean,
}
declare class TextInput extends View<Props> {
type Child = never;
declare class TextInput extends View<Props, Child> {
input: Contract<string>;
}
declare class TextInputFactory extends ViewFactory<Props, TextInput> { }
declare class TextInputFactory extends ViewFactory<Props, Child, TextInput> { }
declare const factory: TextInputFactory;
export = factory;

View File

@@ -0,0 +1,70 @@
/**
* Displays a combination of custom elements on one screen.
*
* <img src="../images/widget.png" width="200" alt="Sample screenshot of the view" />
*
* ```js
* let eventLoop = require("event_loop");
* let gui = require("gui");
* let emptyView = require("gui/widget");
* ```
*
* 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.
*
* # Children
* This view has the elements as its children.
*
* @version Added in JS SDK 0.2, extra feature `"gui-widget"`
* @module
*/
import type { View, ViewFactory } from ".";
import type { IconData } from "./icon";
import type { Contract } from "../event_loop";
type Position = { x: number, y: number };
type Size = { w: number, h: number };
type Alignment = { align: `${"t" | "c" | "b"}${"l" | "m" | "r"}` };
type Font = { font: "primary" | "secondary" | "keyboard" | "big_numbers" };
type Text = { text: string };
type StringMultilineElement = { element: "string_multiline" } & Position & Alignment & Font & Text;
type StringElement = { element: "string" } & Position & Alignment & Font & Text;
type TextBoxElement = { element: "text_box", stripToDots: boolean } & Position & Size & Alignment & Text;
type TextScrollElement = { element: "text_scroll" } & Position & Size & Text;
type ButtonElement = { element: "button", button: "left" | "center" | "right" } & Text;
type IconElement = { element: "icon", iconData: IconData } & Position;
type RectElement = { element: "rect", radius: number, fill: boolean } & Position & Size; /** @version Amended in JS SDK 0.3, extra feature `"gui-widget-extras"` */
type CircleElement = { element: "circle", radius: number, fill: boolean } & Position; /** @version Added in JS SDK 0.3, extra feature `"gui-widget-extras"` */
type LineElement = { element: "line", x1: number, y1: number, x2: number, y2: number }; /** @version Added in JS SDK 0.3, extra feature `"gui-widget-extras"` */
type Element = StringMultilineElement
| StringElement
| TextBoxElement
| TextScrollElement
| ButtonElement
| IconElement
| RectElement
| CircleElement
| LineElement;
type Props = {};
type Child = Element;
declare class Widget extends View<Props, Child> {
/**
* Event source for buttons. Only gets fired if there's a corresponding
* button element.
*/
button: Contract<"left" | "center" | "right">;
}
declare class WidgetFactory extends ViewFactory<Props, Child, Widget> { }
declare const factory: WidgetFactory;
export = factory;

View File

@@ -1,6 +1,6 @@
{
"name": "@flipperdevices/fz-sdk",
"version": "0.1.3",
"version": "0.3.0",
"description": "Type declarations and documentation for native JS modules available on Flipper Zero",
"keywords": [
"flipper",

View File

@@ -4,16 +4,33 @@
* @module
*/
export interface Framing {
/**
* @note 6 data bits can only be selected when parity is enabled (even or
* odd)
* @note 9 data bits can only be selected when parity is disabled (none)
*/
dataBits: "6" | "7" | "8" | "9";
parity: "none" | "even" | "odd";
/**
* @note LPUART only supports whole stop bit lengths (i.e. 1 and 2 but not
* 0.5 and 1.5)
*/
stopBits: "0.5" | "1" | "1.5" | "2";
}
/**
* @brief Initializes the serial port
*
* Automatically disables Expansion module service to prevent interference.
*
* @param port The port to initialize (`"lpuart"` or `"start"`)
* @param baudRate
* @param port The port to initialize (`"lpuart"` or `"usart"`)
* @param baudRate Baud rate
* @param framing See `Framing` type
* @version Added in JS SDK 0.1
* @version Added `framing` parameter in JS SDK 0.3, extra feature `"serial-framing"`
*/
export declare function setup(port: "lpuart" | "usart", baudRate: number): void;
export declare function setup(port: "lpuart" | "usart", baudRate: number, framing?: Framing): void;
/**
* @brief Writes data to the serial port

Binary file not shown.

After

Width:  |  Height:  |  Size: 542 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 696 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 779 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 753 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 749 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 646 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 708 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 707 B

Some files were not shown because too many files have changed in this diff Show More