Merge remote-tracking branch 'ofw/dev' into mntm-dev
27
CHANGELOG.md
@@ -2,10 +2,21 @@
|
||||
- UL: Desktop: Option to prevent Auto Lock when connected to USB/RPC (by @Dmitry422)
|
||||
- Desktop settings will be reset, need to reconfigure
|
||||
- Keybinds will remain configured
|
||||
- OFW: JS: New `gui/widget` view, replaces old `widget` module (by @portasynthinca3)
|
||||
- Scripts using `widget` module will need to be updated
|
||||
- Check the `gui.js` example for reference usage
|
||||
|
||||
### Added:
|
||||
- Apps:
|
||||
- Games: Quadrastic (by @ivanbarsukov)
|
||||
- OFW: RFID: EM4305 support (by @Astrrra)
|
||||
- Desktop:
|
||||
- UL: Option to prevent Auto Lock when connected to USB/RPC (by @Dmitry422)
|
||||
- OFW: Add the Showtime animation (by @Astrrra)
|
||||
- OFW: JS: Features & bugfixes, SDK 0.2 (by @portasynthinca3)
|
||||
- New `gui/widget` view, replaces old `widget` module
|
||||
- Support for PWM in `gpio` module
|
||||
- Stop `eventloop` on request and error
|
||||
|
||||
### Updated:
|
||||
- Apps:
|
||||
@@ -17,11 +28,23 @@
|
||||
- Metroflip: Big refactor with plugins and assets to save RAM, RavKav moved to Calypso parser (by @luu176), unified Calypso parser (by @DocSystem)
|
||||
- Picopass: Added Save SR as legacy from saved menu, fix write key 'retry' when presented with new card (by @bettse)
|
||||
- Pinball0: Prevent tilt before ball is in play, fixed Endless table by making bottom portal extend full width (by @rdefeo)
|
||||
- OFW: Infrared: Increase max carrier limit to 1000000 (by @skotopes)
|
||||
- NFC:
|
||||
- OFW: Added naming for DESFire cards + fix MF3ICD40 cards unable to be read (by @Demae)
|
||||
- OFW: Enable MFUL sync poller to be provided with passwords (by @GMMan)
|
||||
- Infrared:
|
||||
- OFW: Add Fujitsu ASTG12LVCC to AC Universal Remote (by @KereruA0i)
|
||||
- OFW: Increase max carrier limit to 1000000 (by @skotopes)
|
||||
- OFW: API: Update mbedtls & expose AES (by @portasynthinca3)
|
||||
|
||||
### Fixed:
|
||||
- Asset Packs: Fix level-up animations not being themed (by @Willy-JL)
|
||||
- About: Fix missing Prev. button when invoked from Device Info keybind (by @Willy-JL)
|
||||
- OFW: uFBT: Bumped action version in example github workflow for project template (by @hedger)
|
||||
- OFW: NFC: ST25TB poller mode check (by @RebornedBrain)
|
||||
- Furi:
|
||||
- OFW: EventLoop unsubscribe fix (by @gsurkov & @portasynthinca3)
|
||||
- OFW: Various bug fixes and improvements (by @skotopes)
|
||||
- OFW: Ensure that `furi_record_create()` is passed a non-NULL data pointer (by @dcoles)
|
||||
|
||||
### Removed:
|
||||
- Nothing
|
||||
- JS: Removed old `widget` module, replaced by new `gui/widget` view
|
||||
|
||||
@@ -12,4 +12,4 @@ tests.assert_eq(false, doesSdkSupport(["abobus", "other-nonexistent-feature"]));
|
||||
|
||||
tests.assert_eq("momentum", flipper.firmwareVendor);
|
||||
tests.assert_eq(0, flipper.jsSdkVersion[0]);
|
||||
tests.assert_eq(1, flipper.jsSdkVersion[1]);
|
||||
tests.assert_eq(2, flipper.jsSdkVersion[1]);
|
||||
|
||||
@@ -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 = {};
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -185,7 +185,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);
|
||||
@@ -293,8 +293,6 @@ static int32_t usb_uart_worker(void* context) {
|
||||
furi_hal_serial_send_break(usb_uart->serial_handle);
|
||||
}
|
||||
}
|
||||
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);
|
||||
|
||||
@@ -307,6 +305,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);
|
||||
|
||||
@@ -2079,3 +2079,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
|
||||
#
|
||||
|
||||
@@ -131,6 +131,23 @@ App(
|
||||
fap_libs=[],
|
||||
)
|
||||
|
||||
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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 },
|
||||
{ element: "frame", x: 100, y: 27, w: 28, h: 20, radius: 3 },
|
||||
{ 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",
|
||||
],
|
||||
}),
|
||||
@@ -78,6 +95,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);
|
||||
@@ -117,6 +136,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();
|
||||
|
||||
@@ -267,6 +267,8 @@ void js_check_sdk_compatibility(struct mjs* mjs) {
|
||||
|
||||
static const char* extra_features[] = {
|
||||
"baseline", // dummy "feature"
|
||||
"gpio-pwm",
|
||||
"gui-widget",
|
||||
|
||||
// extra modules
|
||||
"blebeacon",
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
#define JS_SDK_VENDOR_FIRMWARE "momentum"
|
||||
#define JS_SDK_VENDOR "flipperdevices"
|
||||
#define JS_SDK_MAJOR 0
|
||||
#define JS_SDK_MINOR 1
|
||||
#define JS_SDK_MINOR 2
|
||||
|
||||
/**
|
||||
* @brief Returns the foreign pointer in `obj["_"]`
|
||||
@@ -255,6 +255,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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
61
applications/system/js_app/modules/js_gui/icon.c
Normal file
@@ -0,0 +1,61 @@
|
||||
#include "../../js_modules.h"
|
||||
#include <assets_icons.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),
|
||||
};
|
||||
|
||||
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_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) {
|
||||
UNUSED(modules);
|
||||
*object = mjs_mk_object(mjs);
|
||||
JS_ASSIGN_MULTI(mjs, *object) {
|
||||
JS_FIELD("getBuiltin", MJS_MK_FN(js_gui_icon_get_builtin));
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void js_gui_icon_destroy(void* inst) {
|
||||
UNUSED(inst);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
281
applications/system/js_app/modules/js_gui/widget.c
Normal file
@@ -0,0 +1,281 @@
|
||||
#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, "frame") == 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);
|
||||
widget_add_frame_element(widget, x, y, w, h, radius);
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
11
applications/system/js_app/packages/fz-sdk/gui/icon.d.ts
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
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;
|
||||
@@ -27,13 +27,13 @@
|
||||
* 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` | ❌ |
|
||||
* | `file_browser` | ✅ (as `file_picker`) |
|
||||
* | `loading` | ✅ |
|
||||
* | `menu` | ❌ |
|
||||
* | `number_input` | ❌ |
|
||||
@@ -42,7 +42,7 @@
|
||||
* | `text_box` | ✅ |
|
||||
* | `text_input` | ✅ |
|
||||
* | `variable_item_list` | ❌ |
|
||||
* | `widget` | ❌ |
|
||||
* | `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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -39,9 +39,10 @@ type Props = {
|
||||
defaultTextClear: boolean,
|
||||
illegalSymbols: 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;
|
||||
|
||||
66
applications/system/js_app/packages/fz-sdk/gui/widget.d.ts
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* 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 FrameElement = { element: "frame", radius: number } & Position & Size;
|
||||
|
||||
type Element = StringMultilineElement
|
||||
| StringElement
|
||||
| TextBoxElement
|
||||
| TextScrollElement
|
||||
| ButtonElement
|
||||
| IconElement
|
||||
| FrameElement;
|
||||
|
||||
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;
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@next-flip/fz-sdk-mntm",
|
||||
"version": "0.1.4",
|
||||
"version": "0.2.0",
|
||||
"description": "Type declarations and documentation for native JS modules available on Momentum Custom Firmware for Flipper Zero",
|
||||
"keywords": [
|
||||
"momentum",
|
||||
|
||||
BIN
assets/dolphin/external/L1_Showtime_128x64/frame_0.png
vendored
Executable file
|
After Width: | Height: | Size: 542 B |
BIN
assets/dolphin/external/L1_Showtime_128x64/frame_1.png
vendored
Executable file
|
After Width: | Height: | Size: 696 B |
BIN
assets/dolphin/external/L1_Showtime_128x64/frame_10.png
vendored
Executable file
|
After Width: | Height: | Size: 779 B |
BIN
assets/dolphin/external/L1_Showtime_128x64/frame_11.png
vendored
Executable file
|
After Width: | Height: | Size: 753 B |
BIN
assets/dolphin/external/L1_Showtime_128x64/frame_12.png
vendored
Executable file
|
After Width: | Height: | Size: 749 B |
BIN
assets/dolphin/external/L1_Showtime_128x64/frame_13.png
vendored
Executable file
|
After Width: | Height: | Size: 646 B |
BIN
assets/dolphin/external/L1_Showtime_128x64/frame_14.png
vendored
Executable file
|
After Width: | Height: | Size: 708 B |
BIN
assets/dolphin/external/L1_Showtime_128x64/frame_15.png
vendored
Executable file
|
After Width: | Height: | Size: 707 B |
BIN
assets/dolphin/external/L1_Showtime_128x64/frame_16.png
vendored
Executable file
|
After Width: | Height: | Size: 515 B |
BIN
assets/dolphin/external/L1_Showtime_128x64/frame_17.png
vendored
Executable file
|
After Width: | Height: | Size: 525 B |
BIN
assets/dolphin/external/L1_Showtime_128x64/frame_18.png
vendored
Executable file
|
After Width: | Height: | Size: 581 B |
BIN
assets/dolphin/external/L1_Showtime_128x64/frame_19.png
vendored
Executable file
|
After Width: | Height: | Size: 553 B |
BIN
assets/dolphin/external/L1_Showtime_128x64/frame_2.png
vendored
Executable file
|
After Width: | Height: | Size: 744 B |
BIN
assets/dolphin/external/L1_Showtime_128x64/frame_20.png
vendored
Executable file
|
After Width: | Height: | Size: 626 B |
BIN
assets/dolphin/external/L1_Showtime_128x64/frame_21.png
vendored
Executable file
|
After Width: | Height: | Size: 676 B |
BIN
assets/dolphin/external/L1_Showtime_128x64/frame_22.png
vendored
Executable file
|
After Width: | Height: | Size: 676 B |
BIN
assets/dolphin/external/L1_Showtime_128x64/frame_23.png
vendored
Executable file
|
After Width: | Height: | Size: 597 B |
BIN
assets/dolphin/external/L1_Showtime_128x64/frame_24.png
vendored
Executable file
|
After Width: | Height: | Size: 550 B |
BIN
assets/dolphin/external/L1_Showtime_128x64/frame_25.png
vendored
Executable file
|
After Width: | Height: | Size: 525 B |
BIN
assets/dolphin/external/L1_Showtime_128x64/frame_26.png
vendored
Executable file
|
After Width: | Height: | Size: 639 B |
BIN
assets/dolphin/external/L1_Showtime_128x64/frame_27.png
vendored
Executable file
|
After Width: | Height: | Size: 606 B |
BIN
assets/dolphin/external/L1_Showtime_128x64/frame_28.png
vendored
Executable file
|
After Width: | Height: | Size: 716 B |
BIN
assets/dolphin/external/L1_Showtime_128x64/frame_29.png
vendored
Executable file
|
After Width: | Height: | Size: 614 B |
BIN
assets/dolphin/external/L1_Showtime_128x64/frame_3.png
vendored
Executable file
|
After Width: | Height: | Size: 696 B |
BIN
assets/dolphin/external/L1_Showtime_128x64/frame_30.png
vendored
Executable file
|
After Width: | Height: | Size: 750 B |
BIN
assets/dolphin/external/L1_Showtime_128x64/frame_31.png
vendored
Executable file
|
After Width: | Height: | Size: 782 B |
BIN
assets/dolphin/external/L1_Showtime_128x64/frame_32.png
vendored
Executable file
|
After Width: | Height: | Size: 474 B |
BIN
assets/dolphin/external/L1_Showtime_128x64/frame_33.png
vendored
Executable file
|
After Width: | Height: | Size: 523 B |
BIN
assets/dolphin/external/L1_Showtime_128x64/frame_34.png
vendored
Executable file
|
After Width: | Height: | Size: 577 B |
BIN
assets/dolphin/external/L1_Showtime_128x64/frame_35.png
vendored
Executable file
|
After Width: | Height: | Size: 579 B |
BIN
assets/dolphin/external/L1_Showtime_128x64/frame_36.png
vendored
Executable file
|
After Width: | Height: | Size: 321 B |
BIN
assets/dolphin/external/L1_Showtime_128x64/frame_37.png
vendored
Executable file
|
After Width: | Height: | Size: 570 B |
BIN
assets/dolphin/external/L1_Showtime_128x64/frame_38.png
vendored
Executable file
|
After Width: | Height: | Size: 775 B |
BIN
assets/dolphin/external/L1_Showtime_128x64/frame_39.png
vendored
Executable file
|
After Width: | Height: | Size: 795 B |
BIN
assets/dolphin/external/L1_Showtime_128x64/frame_4.png
vendored
Executable file
|
After Width: | Height: | Size: 713 B |
BIN
assets/dolphin/external/L1_Showtime_128x64/frame_40.png
vendored
Executable file
|
After Width: | Height: | Size: 703 B |
BIN
assets/dolphin/external/L1_Showtime_128x64/frame_41.png
vendored
Executable file
|
After Width: | Height: | Size: 681 B |
BIN
assets/dolphin/external/L1_Showtime_128x64/frame_42.png
vendored
Executable file
|
After Width: | Height: | Size: 665 B |
BIN
assets/dolphin/external/L1_Showtime_128x64/frame_43.png
vendored
Executable file
|
After Width: | Height: | Size: 794 B |
BIN
assets/dolphin/external/L1_Showtime_128x64/frame_44.png
vendored
Executable file
|
After Width: | Height: | Size: 888 B |
BIN
assets/dolphin/external/L1_Showtime_128x64/frame_45.png
vendored
Executable file
|
After Width: | Height: | Size: 914 B |
BIN
assets/dolphin/external/L1_Showtime_128x64/frame_46.png
vendored
Executable file
|
After Width: | Height: | Size: 913 B |
BIN
assets/dolphin/external/L1_Showtime_128x64/frame_47.png
vendored
Executable file
|
After Width: | Height: | Size: 929 B |
BIN
assets/dolphin/external/L1_Showtime_128x64/frame_48.png
vendored
Executable file
|
After Width: | Height: | Size: 925 B |
BIN
assets/dolphin/external/L1_Showtime_128x64/frame_49.png
vendored
Executable file
|
After Width: | Height: | Size: 925 B |
BIN
assets/dolphin/external/L1_Showtime_128x64/frame_5.png
vendored
Executable file
|
After Width: | Height: | Size: 735 B |
BIN
assets/dolphin/external/L1_Showtime_128x64/frame_6.png
vendored
Executable file
|
After Width: | Height: | Size: 762 B |
BIN
assets/dolphin/external/L1_Showtime_128x64/frame_7.png
vendored
Executable file
|
After Width: | Height: | Size: 764 B |
BIN
assets/dolphin/external/L1_Showtime_128x64/frame_8.png
vendored
Executable file
|
After Width: | Height: | Size: 741 B |
BIN
assets/dolphin/external/L1_Showtime_128x64/frame_9.png
vendored
Executable file
|
After Width: | Height: | Size: 721 B |
23
assets/dolphin/external/L1_Showtime_128x64/meta.txt
vendored
Executable file
@@ -0,0 +1,23 @@
|
||||
Filetype: Flipper Animation
|
||||
Version: 1
|
||||
|
||||
Width: 128
|
||||
Height: 64
|
||||
Passive frames: 26
|
||||
Active frames: 26
|
||||
Frames order: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 41 42 43 44 45 46 47 48 49
|
||||
Active cycles: 1
|
||||
Frame rate: 2
|
||||
Duration: 360
|
||||
Active cooldown: 7
|
||||
|
||||
Bubble slots: 1
|
||||
|
||||
Slot: 0
|
||||
X: 69
|
||||
Y: 47
|
||||
Text: SHOWTIME!
|
||||
AlignH: Left
|
||||
AlignV: Center
|
||||
StartFrame: 41
|
||||
EndFrame: 44
|
||||
9
assets/dolphin/external/manifest.txt
vendored
@@ -237,7 +237,7 @@ Min butthurt: 0
|
||||
Max butthurt: 14
|
||||
Min level: 16
|
||||
Max level: 30
|
||||
Weight: 4
|
||||
Weight: 3
|
||||
|
||||
Name: L1_Sleigh_ride_128x64
|
||||
Min butthurt: 0
|
||||
@@ -245,3 +245,10 @@ Max butthurt: 14
|
||||
Min level: 9
|
||||
Max level: 30
|
||||
Weight: 4
|
||||
|
||||
Name: L1_Showtime_128x64
|
||||
Min butthurt: 0
|
||||
Max butthurt: 10
|
||||
Min level: 16
|
||||
Max level: 30
|
||||
Weight: 4
|
||||
|
||||
BIN
documentation/images/widget.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
@@ -28,13 +28,13 @@ In Flipper's terminology, a "View" is a fullscreen design element that assumes
|
||||
control over the entire viewport and all input events. Different types of views
|
||||
are available (not all of which are unfortunately currently implemented in JS):
|
||||
| View | Has JS adapter? |
|
||||
|----------------------|------------------|
|
||||
|----------------------|-----------------------|
|
||||
| `button_menu` | ❌ |
|
||||
| `button_panel` | ❌ |
|
||||
| `byte_input` | ❌ |
|
||||
| `byte_input` | ✅ |
|
||||
| `dialog_ex` | ✅ (as `dialog`) |
|
||||
| `empty_screen` | ✅ |
|
||||
| `file_browser` | ❌ |
|
||||
| `file_browser` | ✅ (as `file_picker`) |
|
||||
| `loading` | ✅ |
|
||||
| `menu` | ❌ |
|
||||
| `number_input` | ❌ |
|
||||
@@ -43,7 +43,7 @@ are available (not all of which are unfortunately currently implemented in JS):
|
||||
| `text_box` | ✅ |
|
||||
| `text_input` | ✅ |
|
||||
| `variable_item_list` | ❌ |
|
||||
| `widget` | ❌ |
|
||||
| `widget` | ✅ |
|
||||
|
||||
In JS, each view has its own set of properties (or just "props"). The programmer
|
||||
can manipulate these properties in two ways:
|
||||
|
||||
25
documentation/js/js_gui__widget.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# js_gui__widget {#js_gui__widget}
|
||||
|
||||
# Widget GUI view
|
||||
Displays a combination of custom elements on one screen
|
||||
|
||||
<img src="widget.png" width="200" alt="Sample screenshot of the view" />
|
||||
|
||||
```js
|
||||
let eventLoop = require("event_loop");
|
||||
let gui = require("gui");
|
||||
let widgetView = 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.js` example script.
|
||||
|
||||
# View props
|
||||
This view does not have any props.
|
||||
|
||||
# Children
|
||||
This view has the elements as its children.
|
||||
@@ -32,14 +32,6 @@ static void furi_event_loop_item_notify(FuriEventLoopItem* instance);
|
||||
|
||||
static bool furi_event_loop_item_is_waiting(FuriEventLoopItem* instance);
|
||||
|
||||
static void furi_event_loop_process_pending_callbacks(FuriEventLoop* instance) {
|
||||
for(; !PendingQueue_empty_p(instance->pending_queue);
|
||||
PendingQueue_pop_back(NULL, instance->pending_queue)) {
|
||||
const FuriEventLoopPendingQueueItem* item = PendingQueue_back(instance->pending_queue);
|
||||
item->callback(item->context);
|
||||
}
|
||||
}
|
||||
|
||||
static bool furi_event_loop_signal_callback(uint32_t signal, void* arg, void* context) {
|
||||
furi_assert(context);
|
||||
FuriEventLoop* instance = context;
|
||||
@@ -130,12 +122,16 @@ static inline FuriEventLoopProcessStatus
|
||||
furi_event_loop_unsubscribe(instance, item->object);
|
||||
}
|
||||
|
||||
instance->current_item = item;
|
||||
|
||||
if(item->event & FuriEventLoopEventFlagEdge) {
|
||||
status = furi_event_loop_process_edge_event(item);
|
||||
} else {
|
||||
status = furi_event_loop_process_level_event(item);
|
||||
}
|
||||
|
||||
instance->current_item = NULL;
|
||||
|
||||
if(item->owner == NULL) {
|
||||
status = FuriEventLoopProcessStatusFreeLater;
|
||||
}
|
||||
@@ -193,6 +189,14 @@ static void furi_event_loop_process_waiting_list(FuriEventLoop* instance) {
|
||||
furi_event_loop_sync_flags(instance);
|
||||
}
|
||||
|
||||
static void furi_event_loop_process_pending_callbacks(FuriEventLoop* instance) {
|
||||
for(; !PendingQueue_empty_p(instance->pending_queue);
|
||||
PendingQueue_pop_back(NULL, instance->pending_queue)) {
|
||||
const FuriEventLoopPendingQueueItem* item = PendingQueue_back(instance->pending_queue);
|
||||
item->callback(item->context);
|
||||
}
|
||||
}
|
||||
|
||||
static void furi_event_loop_restore_flags(FuriEventLoop* instance, uint32_t flags) {
|
||||
if(flags) {
|
||||
xTaskNotifyIndexed(
|
||||
@@ -203,7 +207,6 @@ static void furi_event_loop_restore_flags(FuriEventLoop* instance, uint32_t flag
|
||||
void furi_event_loop_run(FuriEventLoop* instance) {
|
||||
furi_check(instance);
|
||||
furi_check(instance->thread_id == furi_thread_get_current_id());
|
||||
|
||||
FuriThread* thread = furi_thread_get_current();
|
||||
|
||||
// Set the default signal callback if none was previously set
|
||||
@@ -213,9 +216,9 @@ void furi_event_loop_run(FuriEventLoop* instance) {
|
||||
|
||||
furi_event_loop_init_tick(instance);
|
||||
|
||||
while(true) {
|
||||
instance->state = FuriEventLoopStateIdle;
|
||||
instance->state = FuriEventLoopStateRunning;
|
||||
|
||||
while(true) {
|
||||
const TickType_t ticks_to_sleep =
|
||||
MIN(furi_event_loop_get_timer_wait_time(instance),
|
||||
furi_event_loop_get_tick_wait_time(instance));
|
||||
@@ -224,8 +227,6 @@ void furi_event_loop_run(FuriEventLoop* instance) {
|
||||
BaseType_t ret = xTaskNotifyWaitIndexed(
|
||||
FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX, 0, FuriEventLoopFlagAll, &flags, ticks_to_sleep);
|
||||
|
||||
instance->state = FuriEventLoopStateProcessing;
|
||||
|
||||
if(ret == pdTRUE) {
|
||||
if(flags & FuriEventLoopFlagStop) {
|
||||
instance->state = FuriEventLoopStateStopped;
|
||||
@@ -448,7 +449,7 @@ void furi_event_loop_unsubscribe(FuriEventLoop* instance, FuriEventLoopObject* o
|
||||
WaitingList_unlink(item);
|
||||
}
|
||||
|
||||
if(instance->state == FuriEventLoopStateProcessing) {
|
||||
if(instance->current_item == item) {
|
||||
furi_event_loop_item_free_later(item);
|
||||
} else {
|
||||
furi_event_loop_item_free(item);
|
||||
|
||||
@@ -64,8 +64,7 @@ typedef enum {
|
||||
|
||||
typedef enum {
|
||||
FuriEventLoopStateStopped,
|
||||
FuriEventLoopStateIdle,
|
||||
FuriEventLoopStateProcessing,
|
||||
FuriEventLoopStateRunning,
|
||||
} FuriEventLoopState;
|
||||
|
||||
typedef struct {
|
||||
@@ -81,6 +80,7 @@ struct FuriEventLoop {
|
||||
|
||||
// Poller state
|
||||
volatile FuriEventLoopState state;
|
||||
volatile FuriEventLoopItem* current_item;
|
||||
|
||||
// Event handling
|
||||
FuriEventLoopTree_t tree;
|
||||
|
||||
@@ -80,6 +80,7 @@ bool furi_record_exists(const char* name) {
|
||||
void furi_record_create(const char* name, void* data) {
|
||||
furi_check(furi_record);
|
||||
furi_check(name);
|
||||
furi_check(data);
|
||||
|
||||
furi_record_lock();
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ bool furi_record_exists(const char* name);
|
||||
/** Create record
|
||||
*
|
||||
* @param name record name
|
||||
* @param data data pointer
|
||||
* @param data data pointer (not NULL)
|
||||
* @note Thread safe. Create and destroy must be executed from the same
|
||||
* thread.
|
||||
*/
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
|
||||
#define THREAD_MAX_STACK_SIZE (UINT16_MAX * sizeof(StackType_t))
|
||||
|
||||
#define THREAD_STACK_WATERMARK_MIN (256u)
|
||||
|
||||
typedef struct {
|
||||
FuriThreadStdoutWriteCallback write_callback;
|
||||
FuriString* buffer;
|
||||
@@ -117,6 +119,18 @@ static void furi_thread_body(void* context) {
|
||||
|
||||
furi_check(!thread->is_service, "Service threads MUST NOT return");
|
||||
|
||||
size_t stack_watermark = furi_thread_get_stack_space(thread);
|
||||
if(stack_watermark < THREAD_STACK_WATERMARK_MIN) {
|
||||
#ifdef FURI_DEBUG
|
||||
furi_crash("Stack watermark is dangerously low");
|
||||
#endif
|
||||
FURI_LOG_E( //-V779
|
||||
thread->name ? thread->name : "Thread",
|
||||
"Stack watermark is too low %zu < " STRINGIFY(
|
||||
THREAD_STACK_WATERMARK_MIN) ". Increase stack size.",
|
||||
stack_watermark);
|
||||
}
|
||||
|
||||
if(thread->heap_trace_enabled == true) {
|
||||
furi_delay_ms(33);
|
||||
thread->heap_size = memmgr_heap_get_thread_memory((FuriThreadId)thread);
|
||||
|
||||
@@ -510,9 +510,6 @@ static void lfrfid_worker_mode_emulate_process(LFRFIDWorker* worker) {
|
||||
static void lfrfid_worker_mode_write_process(LFRFIDWorker* worker) {
|
||||
LFRFIDProtocol protocol = worker->protocol;
|
||||
LFRFIDWriteRequest* request = malloc(sizeof(LFRFIDWriteRequest));
|
||||
request->write_type = LFRFIDWriteTypeT5577;
|
||||
|
||||
bool can_be_written = protocol_dict_get_write_data(worker->protocols, protocol, request);
|
||||
|
||||
uint32_t write_start_time = furi_get_tick();
|
||||
bool too_long = false;
|
||||
@@ -521,13 +518,43 @@ static void lfrfid_worker_mode_write_process(LFRFIDWorker* worker) {
|
||||
size_t data_size = protocol_dict_get_data_size(worker->protocols, protocol);
|
||||
uint8_t* verify_data = malloc(data_size);
|
||||
uint8_t* read_data = malloc(data_size);
|
||||
|
||||
protocol_dict_get_data(worker->protocols, protocol, verify_data, data_size);
|
||||
|
||||
if(can_be_written) {
|
||||
while(!lfrfid_worker_check_for_stop(worker)) {
|
||||
FURI_LOG_D(TAG, "Data write");
|
||||
t5577_write(&request->t5577);
|
||||
uint16_t skips = 0;
|
||||
for(size_t i = 0; i < LFRFIDWriteTypeMax; i++) {
|
||||
memset(request, 0, sizeof(LFRFIDWriteRequest));
|
||||
LFRFIDWriteType write_type = i;
|
||||
request->write_type = write_type;
|
||||
|
||||
protocol_dict_set_data(worker->protocols, protocol, verify_data, data_size);
|
||||
|
||||
bool can_be_written =
|
||||
protocol_dict_get_write_data(worker->protocols, protocol, request);
|
||||
|
||||
if(!can_be_written) {
|
||||
skips++;
|
||||
if(skips == LFRFIDWriteTypeMax) {
|
||||
if(worker->write_cb) {
|
||||
worker->write_cb(LFRFIDWorkerWriteProtocolCannotBeWritten, worker->cb_ctx);
|
||||
}
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
memset(read_data, 0, data_size);
|
||||
|
||||
if(request->write_type == LFRFIDWriteTypeT5577) {
|
||||
t5577_write(&request->t5577);
|
||||
} else if(request->write_type == LFRFIDWriteTypeEM4305) {
|
||||
em4305_write(&request->em4305);
|
||||
} else {
|
||||
furi_crash("Unknown write type");
|
||||
}
|
||||
}
|
||||
ProtocolId read_result = PROTOCOL_NO;
|
||||
LFRFIDWorkerReadState state = lfrfid_worker_read_internal(
|
||||
worker,
|
||||
@@ -574,11 +601,6 @@ static void lfrfid_worker_mode_write_process(LFRFIDWorker* worker) {
|
||||
|
||||
lfrfid_worker_delay(worker, LFRFID_WORKER_WRITE_DROP_TIME_MS);
|
||||
}
|
||||
} else {
|
||||
if(worker->write_cb) {
|
||||
worker->write_cb(LFRFIDWorkerWriteProtocolCannotBeWritten, worker->cb_ctx);
|
||||
}
|
||||
}
|
||||
|
||||
free(request);
|
||||
free(verify_data);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
#include <toolbox/protocols/protocol.h>
|
||||
#include "../tools/t5577.h"
|
||||
#include "../tools/em4305.h"
|
||||
|
||||
typedef enum {
|
||||
LFRFIDFeatureASK = 1 << 0, /** ASK Demodulation */
|
||||
@@ -32,6 +33,7 @@ typedef enum {
|
||||
LFRFIDProtocolSecurakey,
|
||||
LFRFIDProtocolGProxII,
|
||||
LFRFIDProtocolInstaFob,
|
||||
|
||||
LFRFIDProtocolMax,
|
||||
} LFRFIDProtocol;
|
||||
|
||||
@@ -39,11 +41,15 @@ extern const ProtocolBase* lfrfid_protocols[];
|
||||
|
||||
typedef enum {
|
||||
LFRFIDWriteTypeT5577,
|
||||
LFRFIDWriteTypeEM4305,
|
||||
|
||||
LFRFIDWriteTypeMax,
|
||||
} LFRFIDWriteType;
|
||||
|
||||
typedef struct {
|
||||
LFRFIDWriteType write_type;
|
||||
union {
|
||||
LFRFIDT5577 t5577;
|
||||
LFRFIDEM4305 em4305;
|
||||
};
|
||||
} LFRFIDWriteRequest;
|
||||
|
||||
@@ -407,6 +407,24 @@ bool protocol_electra_write_data(ProtocolElectra* protocol, void* data) {
|
||||
request->t5577.blocks_to_write = 5;
|
||||
result = true;
|
||||
}
|
||||
if(request->write_type == LFRFIDWriteTypeEM4305) {
|
||||
request->em4305.word[4] =
|
||||
(EM4x05_MODULATION_MANCHESTER | EM4x05_SET_BITRATE(64) | (8 << EM4x05_MAXBLOCK_SHIFT));
|
||||
uint64_t encoded_data_reversed = 0;
|
||||
uint64_t encoded_epilogue_reversed = 0;
|
||||
for(uint8_t i = 0; i < 64; i++) {
|
||||
encoded_data_reversed = (encoded_data_reversed << 1) |
|
||||
((protocol->encoded_base_data >> i) & 1);
|
||||
encoded_epilogue_reversed = (encoded_epilogue_reversed << 1) |
|
||||
((protocol->encoded_epilogue >> i) & 1);
|
||||
}
|
||||
request->em4305.word[5] = encoded_data_reversed & 0xFFFFFFFF;
|
||||
request->em4305.word[6] = encoded_data_reversed >> 32;
|
||||
request->em4305.word[7] = encoded_epilogue_reversed & 0xFFFFFFFF;
|
||||
request->em4305.word[8] = encoded_epilogue_reversed >> 32;
|
||||
request->em4305.mask = 0x01F0;
|
||||
result = true;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -69,6 +69,19 @@ uint32_t protocol_em4100_get_t5577_bitrate(ProtocolEM4100* proto) {
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t protocol_em4100_get_em4305_bitrate(ProtocolEM4100* proto) {
|
||||
switch(proto->clock_per_bit) {
|
||||
case 64:
|
||||
return EM4x05_SET_BITRATE(64);
|
||||
case 32:
|
||||
return EM4x05_SET_BITRATE(32);
|
||||
case 16:
|
||||
return EM4x05_SET_BITRATE(16);
|
||||
default:
|
||||
return EM4x05_SET_BITRATE(64);
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t protocol_em4100_get_short_time_low(ProtocolEM4100* proto) {
|
||||
return EM_READ_SHORT_TIME_BASE / protocol_em4100_get_time_divisor(proto) -
|
||||
EM_READ_JITTER_TIME_BASE / protocol_em4100_get_time_divisor(proto);
|
||||
@@ -339,6 +352,19 @@ bool protocol_em4100_write_data(ProtocolEM4100* protocol, void* data) {
|
||||
request->t5577.block[2] = protocol->encoded_data;
|
||||
request->t5577.blocks_to_write = 3;
|
||||
result = true;
|
||||
} else if(request->write_type == LFRFIDWriteTypeEM4305) {
|
||||
request->em4305.word[4] =
|
||||
(EM4x05_MODULATION_MANCHESTER | protocol_em4100_get_em4305_bitrate(protocol) |
|
||||
(6 << EM4x05_MAXBLOCK_SHIFT));
|
||||
uint64_t encoded_data_reversed = 0;
|
||||
for(uint8_t i = 0; i < 64; i++) {
|
||||
encoded_data_reversed = (encoded_data_reversed << 1) |
|
||||
((protocol->encoded_data >> i) & 1);
|
||||
}
|
||||
request->em4305.word[5] = encoded_data_reversed;
|
||||
request->em4305.word[6] = encoded_data_reversed >> 32;
|
||||
request->em4305.mask = 0x70;
|
||||
result = true;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -264,6 +264,20 @@ bool protocol_gallagher_write_data(ProtocolGallagher* protocol, void* data) {
|
||||
request->t5577.block[3] = bit_lib_get_bits_32(protocol->encoded_data, 64, 32);
|
||||
request->t5577.blocks_to_write = 4;
|
||||
result = true;
|
||||
} else if(request->write_type == LFRFIDWriteTypeEM4305) {
|
||||
request->em4305.word[4] =
|
||||
(EM4x05_MODULATION_MANCHESTER | EM4x05_SET_BITRATE(32) | (7 << EM4x05_MAXBLOCK_SHIFT));
|
||||
uint32_t encoded_data_reversed[3] = {0};
|
||||
for(uint8_t i = 0; i < (32 * 3); i++) {
|
||||
encoded_data_reversed[i / 32] =
|
||||
(encoded_data_reversed[i / 32] << 1) |
|
||||
(bit_lib_get_bit(protocol->encoded_data, ((32 * 3) - i)) & 1);
|
||||
}
|
||||
request->em4305.word[5] = encoded_data_reversed[2];
|
||||
request->em4305.word[6] = encoded_data_reversed[1];
|
||||
request->em4305.word[7] = encoded_data_reversed[0];
|
||||
request->em4305.mask = 0xF0;
|
||||
result = true;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -171,6 +171,19 @@ bool protocol_viking_write_data(ProtocolViking* protocol, void* data) {
|
||||
request->t5577.block[2] = bit_lib_get_bits_32(protocol->encoded_data, 32, 32);
|
||||
request->t5577.blocks_to_write = 3;
|
||||
result = true;
|
||||
} else if(request->write_type == LFRFIDWriteTypeEM4305) {
|
||||
request->em4305.word[4] =
|
||||
(EM4x05_MODULATION_MANCHESTER | EM4x05_SET_BITRATE(32) | (6 << EM4x05_MAXBLOCK_SHIFT));
|
||||
uint32_t encoded_data_reversed[2] = {0};
|
||||
for(uint8_t i = 0; i < 64; i++) {
|
||||
encoded_data_reversed[i / 32] =
|
||||
(encoded_data_reversed[i / 32] << 1) |
|
||||
(bit_lib_get_bit(protocol->encoded_data, (63 - i)) & 1);
|
||||
}
|
||||
request->em4305.word[5] = encoded_data_reversed[1];
|
||||
request->em4305.word[6] = encoded_data_reversed[0];
|
||||
request->em4305.mask = 0x70;
|
||||
result = true;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
152
lib/lfrfid/tools/em4305.c
Normal file
@@ -0,0 +1,152 @@
|
||||
#include "em4305.h"
|
||||
#include <furi.h>
|
||||
#include <furi_hal_rfid.h>
|
||||
|
||||
#define TAG "EM4305"
|
||||
|
||||
#define EM4305_TIMING_1 (32)
|
||||
#define EM4305_TIMING_0_OFF (23)
|
||||
#define EM4305_TIMING_0_ON (18)
|
||||
|
||||
#define EM4305_FIELD_STOP_OFF_CYCLES (55)
|
||||
#define EM4305_FIELD_STOP_ON_CYCLES (18)
|
||||
|
||||
#define EM4305_TIMING_POWER_CHECK (1480)
|
||||
#define EM4305_TIMING_EEPROM_WRITE (9340)
|
||||
|
||||
static bool em4305_line_parity(uint8_t data) {
|
||||
uint8_t parity = 0;
|
||||
for(uint8_t i = 0; i < 8; i++) {
|
||||
parity ^= (data >> i) & 1;
|
||||
}
|
||||
return parity;
|
||||
}
|
||||
|
||||
static uint64_t em4305_prepare_data(uint32_t data) {
|
||||
uint8_t i, j;
|
||||
uint64_t data_with_parity = 0;
|
||||
|
||||
// 4 lines of 8 bits of data
|
||||
// line even parity at bits 8 17 26 35
|
||||
// column even parity at bits 36-43
|
||||
// bit 44 is always 0
|
||||
// final table is 5 lines of 9 bits
|
||||
|
||||
// line parity
|
||||
for(i = 0; i < 4; i++) {
|
||||
for(j = 0; j < 8; j++) {
|
||||
data_with_parity = (data_with_parity << 1) | ((data >> (i * 8 + j)) & 1);
|
||||
}
|
||||
data_with_parity = (data_with_parity << 1) | (uint64_t)em4305_line_parity(data >> (i * 8));
|
||||
}
|
||||
|
||||
// column parity
|
||||
for(i = 0; i < 8; i++) {
|
||||
uint8_t column_parity = 0;
|
||||
for(j = 0; j < 4; j++) {
|
||||
column_parity ^= (data >> (j * 8 + i)) & 1;
|
||||
}
|
||||
data_with_parity = (data_with_parity << 1) | column_parity;
|
||||
}
|
||||
|
||||
// bit 44
|
||||
data_with_parity = (data_with_parity << 1) | 0;
|
||||
|
||||
return data_with_parity;
|
||||
}
|
||||
|
||||
static void em4305_start(void) {
|
||||
furi_hal_rfid_tim_read_start(125000, 0.5);
|
||||
|
||||
// do not ground the antenna
|
||||
furi_hal_rfid_pin_pull_release();
|
||||
}
|
||||
|
||||
static void em4305_stop(void) {
|
||||
furi_hal_rfid_tim_read_stop();
|
||||
furi_hal_rfid_pins_reset();
|
||||
}
|
||||
|
||||
static void em4305_write_bit(bool value) {
|
||||
if(value) {
|
||||
furi_delay_us(EM4305_TIMING_1 * 8);
|
||||
} else {
|
||||
furi_hal_rfid_tim_read_pause();
|
||||
furi_delay_us(EM4305_TIMING_0_OFF * 8);
|
||||
furi_hal_rfid_tim_read_continue();
|
||||
furi_delay_us(EM4305_TIMING_0_ON * 8);
|
||||
}
|
||||
}
|
||||
|
||||
static void em4305_write_opcode(uint8_t value) {
|
||||
// 3 bit opcode
|
||||
for(uint8_t i = 0; i < 3; i++) {
|
||||
em4305_write_bit((value >> i) & 1);
|
||||
}
|
||||
|
||||
// parity
|
||||
bool parity = 0;
|
||||
for(uint8_t i = 0; i < 3; i++) {
|
||||
parity ^= (value >> i) & 1;
|
||||
}
|
||||
em4305_write_bit(parity);
|
||||
}
|
||||
|
||||
static void em4305_field_stop() {
|
||||
furi_hal_rfid_tim_read_pause();
|
||||
furi_delay_us(EM4305_FIELD_STOP_OFF_CYCLES * 8);
|
||||
furi_hal_rfid_tim_read_continue();
|
||||
furi_delay_us(EM4305_FIELD_STOP_ON_CYCLES * 8);
|
||||
}
|
||||
|
||||
static void em4305_write_word(uint8_t address, uint32_t data) {
|
||||
// parity
|
||||
uint64_t data_with_parity = em4305_prepare_data(data);
|
||||
|
||||
// power up the tag
|
||||
furi_delay_us(8000);
|
||||
|
||||
// field stop
|
||||
em4305_field_stop();
|
||||
|
||||
// start bit
|
||||
em4305_write_bit(0);
|
||||
|
||||
// opcode
|
||||
em4305_write_opcode(EM4x05_OPCODE_WRITE);
|
||||
|
||||
// address
|
||||
bool address_parity = 0;
|
||||
for(uint8_t i = 0; i < 4; i++) {
|
||||
em4305_write_bit((address >> (i)) & 1);
|
||||
address_parity ^= (address >> (i)) & 1;
|
||||
}
|
||||
em4305_write_bit(0);
|
||||
em4305_write_bit(0);
|
||||
em4305_write_bit(address_parity);
|
||||
|
||||
// data
|
||||
for(uint8_t i = 0; i < 45; i++) {
|
||||
em4305_write_bit((data_with_parity >> (44 - i)) & 1);
|
||||
}
|
||||
|
||||
// wait for power check and eeprom write
|
||||
furi_delay_us(EM4305_TIMING_POWER_CHECK);
|
||||
furi_delay_us(EM4305_TIMING_EEPROM_WRITE);
|
||||
}
|
||||
|
||||
void em4305_write(LFRFIDEM4305* data) {
|
||||
furi_check(data);
|
||||
|
||||
em4305_start();
|
||||
FURI_CRITICAL_ENTER();
|
||||
|
||||
for(uint8_t i = 0; i < EM4x05_WORD_COUNT; i++) {
|
||||
if(data->mask & (1 << i)) {
|
||||
em4305_write_word(i, data->word[i]);
|
||||
}
|
||||
}
|
||||
|
||||
FURI_CRITICAL_EXIT();
|
||||
em4305_stop();
|
||||
}
|
||||
61
lib/lfrfid/tools/em4305.h
Normal file
@@ -0,0 +1,61 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// EM4305/4205 chip config definitions, thanks proxmark3!
|
||||
#define EM4x05_GET_BITRATE(x) ((((x) & 0x3F) * 2) + 2)
|
||||
// Note: only data rates 8, 16, 32, 40(*) and 64 are supported. (*) only with EM4305 330pF
|
||||
#define EM4x05_SET_BITRATE(x) (((x) - 2) / 2)
|
||||
#define EM4x05_MODULATION_NRZ (0x00000000)
|
||||
#define EM4x05_MODULATION_MANCHESTER (0x00000040)
|
||||
#define EM4x05_MODULATION_BIPHASE (0x00000080)
|
||||
#define EM4x05_MODULATION_MILLER (0x000000C0) // not supported by all 4x05/4x69 chips
|
||||
#define EM4x05_MODULATION_PSK1 (0x00000100) // not supported by all 4x05/4x69 chips
|
||||
#define EM4x05_MODULATION_PSK2 (0x00000140) // not supported by all 4x05/4x69 chips
|
||||
#define EM4x05_MODULATION_PSK3 (0x00000180) // not supported by all 4x05/4x69 chips
|
||||
#define EM4x05_MODULATION_FSK1 (0x00000200) // not supported by all 4x05/4x69 chips
|
||||
#define EM4x05_MODULATION_FSK2 (0x00000240) // not supported by all 4x05/4x69 chips
|
||||
#define EM4x05_PSK_RF_2 (0)
|
||||
#define EM4x05_PSK_RF_4 (0x00000400)
|
||||
#define EM4x05_PSK_RF_8 (0x00000800)
|
||||
#define EM4x05_MAXBLOCK_SHIFT (14)
|
||||
#define EM4x05_FIRST_USER_BLOCK (5)
|
||||
#define EM4x05_SET_NUM_BLOCKS(x) \
|
||||
(((x) + 4) << 14) // number of blocks sent during default read mode
|
||||
#define EM4x05_GET_NUM_BLOCKS(x) ((((x) >> 14) & 0xF) - 4)
|
||||
#define EM4x05_READ_LOGIN_REQ (1 << 18)
|
||||
#define EM4x05_READ_HK_LOGIN_REQ (1 << 19)
|
||||
#define EM4x05_WRITE_LOGIN_REQ (1 << 20)
|
||||
#define EM4x05_WRITE_HK_LOGIN_REQ (1 << 21)
|
||||
#define EM4x05_READ_AFTER_WRITE (1 << 22)
|
||||
#define EM4x05_DISABLE_ALLOWED (1 << 23)
|
||||
#define EM4x05_READER_TALK_FIRST (1 << 24)
|
||||
#define EM4x05_INVERT (1 << 25)
|
||||
#define EM4x05_PIGEON (1 << 26)
|
||||
|
||||
#define EM4x05_WORD_COUNT (16)
|
||||
|
||||
#define EM4x05_OPCODE_LOGIN (0b001)
|
||||
#define EM4x05_OPCODE_WRITE (0b010)
|
||||
#define EM4x05_OPCODE_READ (0b100)
|
||||
#define EM4x05_OPCODE_PROTECT (0b110)
|
||||
#define EM4x05_OPCODE_DISABLE (0b101)
|
||||
|
||||
typedef struct {
|
||||
uint32_t word[EM4x05_WORD_COUNT]; /**< Word data to write */
|
||||
uint16_t mask; /**< Word mask */
|
||||
} LFRFIDEM4305;
|
||||
|
||||
/** Write EM4305 tag data to tag
|
||||
*
|
||||
* @param data The data to write (mask is taken from that data)
|
||||
*/
|
||||
void em4305_write(LFRFIDEM4305* data);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||