diff --git a/CHANGELOG.md b/CHANGELOG.md index 1acb41c57..c145fffd8 100644 --- a/CHANGELOG.md +++ b/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 diff --git a/applications/debug/unit_tests/resources/unit_tests/js/basic.js b/applications/debug/unit_tests/resources/unit_tests/js/basic.js index b1e7dcc84..669322eec 100644 --- a/applications/debug/unit_tests/resources/unit_tests/js/basic.js +++ b/applications/debug/unit_tests/resources/unit_tests/js/basic.js @@ -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]); diff --git a/applications/debug/unit_tests/tests/furi/furi_event_loop_test.c b/applications/debug/unit_tests/tests/furi/furi_event_loop_test.c index 73f38ab77..08e0e8ad2 100644 --- a/applications/debug/unit_tests/tests/furi/furi_event_loop_test.c +++ b/applications/debug/unit_tests/tests/furi/furi_event_loop_test.c @@ -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 = {}; diff --git a/applications/debug/unit_tests/tests/furi/furi_test.c b/applications/debug/unit_tests/tests/furi/furi_test.c index f23be37a9..03d49aa7e 100644 --- a/applications/debug/unit_tests/tests/furi/furi_test.c +++ b/applications/debug/unit_tests/tests/furi/furi_test.c @@ -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); diff --git a/applications/debug/unit_tests/tests/nfc/nfc_test.c b/applications/debug/unit_tests/tests/nfc/nfc_test.c index 4ba934b6d..e028b8041 100644 --- a/applications/debug/unit_tests/tests/nfc/nfc_test.c +++ b/applications/debug/unit_tests/tests/nfc/nfc_test.c @@ -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); diff --git a/applications/main/gpio/usb_uart_bridge.c b/applications/main/gpio/usb_uart_bridge.c index 0d2c63b3c..b755a89c2 100644 --- a/applications/main/gpio/usb_uart_bridge.c +++ b/applications/main/gpio/usb_uart_bridge.c @@ -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); diff --git a/applications/main/infrared/resources/infrared/assets/ac.ir b/applications/main/infrared/resources/infrared/assets/ac.ir index cf594586a..7213b8e56 100644 --- a/applications/main/infrared/resources/infrared/assets/ac.ir +++ b/applications/main/infrared/resources/infrared/assets/ac.ir @@ -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 +# diff --git a/applications/system/js_app/application.fam b/applications/system/js_app/application.fam index 6d2b4fe80..644d2d660 100644 --- a/applications/system/js_app/application.fam +++ b/applications/system/js_app/application.fam @@ -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, diff --git a/applications/system/js_app/examples/apps/Scripts/Examples/gpio.js b/applications/system/js_app/examples/apps/Scripts/Examples/gpio.js index 24d0f0286..6ea0a948f 100644 --- a/applications/system/js_app/examples/apps/Scripts/Examples/gpio.js +++ b/applications/system/js_app/examples/apps/Scripts/Examples/gpio.js @@ -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) { diff --git a/applications/system/js_app/examples/apps/Scripts/Examples/gui.js b/applications/system/js_app/examples/apps/Scripts/Examples/gui.js index 110b388b8..06043ab3a 100644 --- a/applications/system/js_app/examples/apps/Scripts/Examples/gui.js +++ b/applications/system/js_app/examples/apps/Scripts/Examples/gui.js @@ -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(); diff --git a/applications/system/js_app/js_modules.c b/applications/system/js_app/js_modules.c index 6918bfaba..4e6bdf8a6 100644 --- a/applications/system/js_app/js_modules.c +++ b/applications/system/js_app/js_modules.c @@ -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", diff --git a/applications/system/js_app/js_modules.h b/applications/system/js_app/js_modules.h index 95cfb913b..1a564ce01 100644 --- a/applications/system/js_app/js_modules.h +++ b/applications/system/js_app/js_modules.h @@ -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); diff --git a/applications/system/js_app/js_thread.c b/applications/system/js_app/js_thread.c index 600c2676e..4a6d23011 100644 --- a/applications/system/js_app/js_thread.c +++ b/applications/system/js_app/js_thread.c @@ -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); diff --git a/applications/system/js_app/modules/js_event_loop/js_event_loop.c b/applications/system/js_app/modules/js_event_loop/js_event_loop.c index 7f45c1a0f..625301ad1 100644 --- a/applications/system/js_app/modules/js_event_loop/js_event_loop.c +++ b/applications/system/js_app/modules/js_event_loop/js_event_loop.c @@ -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; //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); } } diff --git a/applications/system/js_app/modules/js_gpio.c b/applications/system/js_app/modules/js_gpio.c index ae3fefd71..23884a6d4 100644 --- a/applications/system/js_app/modules/js_gpio.c +++ b/applications/system/js_app/modules/js_gpio.c @@ -1,6 +1,7 @@ #include "../js_modules.h" // IWYU pragma: keep #include "./js_event_loop/js_event_loop.h" #include +#include #include #include #include @@ -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 diff --git a/applications/system/js_app/modules/js_gui/icon.c b/applications/system/js_app/modules/js_gui/icon.c new file mode 100644 index 000000000..4b7926ba1 --- /dev/null +++ b/applications/system/js_app/modules/js_gui/icon.c @@ -0,0 +1,61 @@ +#include "../../js_modules.h" +#include + +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; +} diff --git a/applications/system/js_app/modules/js_gui/js_gui.c b/applications/system/js_app/modules/js_gui/js_gui.c index 22d04855d..e505681df 100644 --- a/applications/system/js_app/modules/js_gui/js_gui.c +++ b/applications/system/js_app/modules/js_gui/js_gui.c @@ -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); } diff --git a/applications/system/js_app/modules/js_gui/js_gui.h b/applications/system/js_app/modules/js_gui/js_gui.h index d400d0a33..d9d98df39 100644 --- a/applications/system/js_app/modules/js_gui/js_gui.h +++ b/applications/system/js_app/modules/js_gui/js_gui.h @@ -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; // 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 diff --git a/applications/system/js_app/modules/js_gui/widget.c b/applications/system/js_app/modules/js_gui/widget.c new file mode 100644 index 000000000..5f9a222cc --- /dev/null +++ b/applications/system/js_app/modules/js_gui/widget.c @@ -0,0 +1,281 @@ +#include "../../js_modules.h" // IWYU pragma: keep +#include "js_gui.h" +#include "../js_event_loop/js_event_loop.h" +#include + +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); diff --git a/applications/system/js_app/packages/fz-sdk/gpio/index.d.ts b/applications/system/js_app/packages/fz-sdk/gpio/index.d.ts index b484ebbf6..cd5ce2b60 100644 --- a/applications/system/js_app/packages/fz-sdk/gpio/index.d.ts +++ b/applications/system/js_app/packages/fz-sdk/gpio/index.d.ts @@ -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; } /** diff --git a/applications/system/js_app/packages/fz-sdk/gui/byte_input.d.ts b/applications/system/js_app/packages/fz-sdk/gui/byte_input.d.ts index 5556e7fbb..7080ad3ae 100644 --- a/applications/system/js_app/packages/fz-sdk/gui/byte_input.d.ts +++ b/applications/system/js_app/packages/fz-sdk/gui/byte_input.d.ts @@ -33,9 +33,10 @@ type Props = { length: number, defaultData: Uint8Array | ArrayBuffer, } -declare class ByteInput extends View { +type Child = never; +declare class ByteInput extends View { input: Contract; } -declare class ByteInputFactory extends ViewFactory { } +declare class ByteInputFactory extends ViewFactory { } declare const factory: ByteInputFactory; export = factory; diff --git a/applications/system/js_app/packages/fz-sdk/gui/dialog.d.ts b/applications/system/js_app/packages/fz-sdk/gui/dialog.d.ts index 9bd0c3966..2fffcb873 100644 --- a/applications/system/js_app/packages/fz-sdk/gui/dialog.d.ts +++ b/applications/system/js_app/packages/fz-sdk/gui/dialog.d.ts @@ -37,9 +37,10 @@ type Props = { center: string, right: string, } -declare class Dialog extends View { +type Child = never; +declare class Dialog extends View { input: Contract<"left" | "center" | "right">; } -declare class DialogFactory extends ViewFactory { } +declare class DialogFactory extends ViewFactory { } declare const factory: DialogFactory; export = factory; diff --git a/applications/system/js_app/packages/fz-sdk/gui/empty_screen.d.ts b/applications/system/js_app/packages/fz-sdk/gui/empty_screen.d.ts index 49e591426..6a848bd03 100644 --- a/applications/system/js_app/packages/fz-sdk/gui/empty_screen.d.ts +++ b/applications/system/js_app/packages/fz-sdk/gui/empty_screen.d.ts @@ -26,7 +26,8 @@ import type { View, ViewFactory } from "."; type Props = {}; -declare class EmptyScreen extends View { } -declare class EmptyScreenFactory extends ViewFactory { } +type Child = never; +declare class EmptyScreen extends View { } +declare class EmptyScreenFactory extends ViewFactory { } declare const factory: EmptyScreenFactory; export = factory; diff --git a/applications/system/js_app/packages/fz-sdk/gui/icon.d.ts b/applications/system/js_app/packages/fz-sdk/gui/icon.d.ts new file mode 100644 index 000000000..577e1e6a9 --- /dev/null +++ b/applications/system/js_app/packages/fz-sdk/gui/icon.d.ts @@ -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; diff --git a/applications/system/js_app/packages/fz-sdk/gui/index.d.ts b/applications/system/js_app/packages/fz-sdk/gui/index.d.ts index 93a6846c2..969b6934e 100644 --- a/applications/system/js_app/packages/fz-sdk/gui/index.d.ts +++ b/applications/system/js_app/packages/fz-sdk/gui/index.d.ts @@ -26,23 +26,23 @@ * assumes control over the entire viewport and all input events. Different * types of views are available (not all of which are unfortunately currently * implemented in JS): - * | View | Has JS adapter? | - * |----------------------|------------------| - * | `button_menu` | ❌ | - * | `button_panel` | ❌ | - * | `byte_input` | ✅ | - * | `dialog_ex` | ✅ (as `dialog`) | - * | `empty_screen` | ✅ | - * | `file_browser` | ❌ | - * | `loading` | ✅ | - * | `menu` | ❌ | - * | `number_input` | ❌ | - * | `popup` | ❌ | - * | `submenu` | ✅ | - * | `text_box` | ✅ | - * | `text_input` | ✅ | - * | `variable_item_list` | ❌ | - * | `widget` | ❌ | + * | View | Has JS adapter? | + * |----------------------|-----------------------| + * | `button_menu` | ❌ | + * | `button_panel` | ❌ | + * | `byte_input` | ✅ | + * | `dialog_ex` | ✅ (as `dialog`) | + * | `empty_screen` | ✅ | + * | `file_browser` | ✅ (as `file_picker`) | + * | `loading` | ✅ | + * | `menu` | ❌ | + * | `number_input` | ❌ | + * | `popup` | ❌ | + * | `submenu` | ✅ | + * | `text_box` | ✅ | + * | `text_input` | ✅ | + * | `variable_item_list` | ❌ | + * | `widget` | ✅ | * * In JS, each view has its own set of properties (or just "props"). The * programmer can manipulate these properties in two ways: @@ -121,7 +121,7 @@ import type { Contract } from "../event_loop"; type Properties = { [K: string]: any }; -export declare class View { +export declare class View { /** * Assign value to property by name * @param property Name of the property @@ -129,9 +129,26 @@ export declare class View { * @version Added in JS SDK 0.1 */ set

(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(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> { +export declare class ViewFactory> { /** * 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 /** * 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): V; + makeWith(initial: Partial, children?: Child[]): V; } /** @@ -163,7 +181,7 @@ declare class ViewDispatcher { * View object currently shown * @version Added in JS SDK 0.1 */ - currentView: View; + currentView: View; /** * 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): void; + switchTo(assoc: View): void; /** * Sends this ViewDispatcher to the front or back, above or below all other * GUI viewports diff --git a/applications/system/js_app/packages/fz-sdk/gui/loading.d.ts b/applications/system/js_app/packages/fz-sdk/gui/loading.d.ts index b8b10c43a..d636f21ca 100644 --- a/applications/system/js_app/packages/fz-sdk/gui/loading.d.ts +++ b/applications/system/js_app/packages/fz-sdk/gui/loading.d.ts @@ -27,7 +27,8 @@ import type { View, ViewFactory } from "."; type Props = {}; -declare class Loading extends View { } -declare class LoadingFactory extends ViewFactory { } +type Child = never; +declare class Loading extends View { } +declare class LoadingFactory extends ViewFactory { } declare const factory: LoadingFactory; export = factory; diff --git a/applications/system/js_app/packages/fz-sdk/gui/submenu.d.ts b/applications/system/js_app/packages/fz-sdk/gui/submenu.d.ts index 31e08aab8..e73856bee 100644 --- a/applications/system/js_app/packages/fz-sdk/gui/submenu.d.ts +++ b/applications/system/js_app/packages/fz-sdk/gui/submenu.d.ts @@ -31,9 +31,10 @@ type Props = { header: string, items: string[], }; -declare class Submenu extends View { +type Child = never; +declare class Submenu extends View { chosen: Contract; } -declare class SubmenuFactory extends ViewFactory { } +declare class SubmenuFactory extends ViewFactory { } declare const factory: SubmenuFactory; export = factory; diff --git a/applications/system/js_app/packages/fz-sdk/gui/text_box.d.ts b/applications/system/js_app/packages/fz-sdk/gui/text_box.d.ts index a46ec73fa..32003bd95 100644 --- a/applications/system/js_app/packages/fz-sdk/gui/text_box.d.ts +++ b/applications/system/js_app/packages/fz-sdk/gui/text_box.d.ts @@ -33,9 +33,10 @@ type Props = { font: "text" | "hex", focus: "start" | "end", } -declare class TextBox extends View { +type Child = never; +declare class TextBox extends View { chosen: Contract; } -declare class TextBoxFactory extends ViewFactory { } +declare class TextBoxFactory extends ViewFactory { } declare const factory: TextBoxFactory; export = factory; diff --git a/applications/system/js_app/packages/fz-sdk/gui/text_input.d.ts b/applications/system/js_app/packages/fz-sdk/gui/text_input.d.ts index 6542ff01d..e6107c527 100644 --- a/applications/system/js_app/packages/fz-sdk/gui/text_input.d.ts +++ b/applications/system/js_app/packages/fz-sdk/gui/text_input.d.ts @@ -39,9 +39,10 @@ type Props = { defaultTextClear: boolean, illegalSymbols: boolean, } -declare class TextInput extends View { +type Child = never; +declare class TextInput extends View { input: Contract; } -declare class TextInputFactory extends ViewFactory { } +declare class TextInputFactory extends ViewFactory { } declare const factory: TextInputFactory; export = factory; diff --git a/applications/system/js_app/packages/fz-sdk/gui/widget.d.ts b/applications/system/js_app/packages/fz-sdk/gui/widget.d.ts new file mode 100644 index 000000000..b37af8e3c --- /dev/null +++ b/applications/system/js_app/packages/fz-sdk/gui/widget.d.ts @@ -0,0 +1,66 @@ +/** + * Displays a combination of custom elements on one screen. + * + * 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 { + /** + * Event source for buttons. Only gets fired if there's a corresponding + * button element. + */ + button: Contract<"left" | "center" | "right">; +} +declare class WidgetFactory extends ViewFactory { } +declare const factory: WidgetFactory; +export = factory; diff --git a/applications/system/js_app/packages/fz-sdk/package.json b/applications/system/js_app/packages/fz-sdk/package.json index e0a945346..f3e1d1169 100644 --- a/applications/system/js_app/packages/fz-sdk/package.json +++ b/applications/system/js_app/packages/fz-sdk/package.json @@ -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", diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_0.png b/assets/dolphin/external/L1_Showtime_128x64/frame_0.png new file mode 100755 index 000000000..7eed9a024 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_0.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_1.png b/assets/dolphin/external/L1_Showtime_128x64/frame_1.png new file mode 100755 index 000000000..827730087 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_1.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_10.png b/assets/dolphin/external/L1_Showtime_128x64/frame_10.png new file mode 100755 index 000000000..c627bd6f9 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_10.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_11.png b/assets/dolphin/external/L1_Showtime_128x64/frame_11.png new file mode 100755 index 000000000..0535101be Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_11.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_12.png b/assets/dolphin/external/L1_Showtime_128x64/frame_12.png new file mode 100755 index 000000000..1284019a8 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_12.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_13.png b/assets/dolphin/external/L1_Showtime_128x64/frame_13.png new file mode 100755 index 000000000..bc71a08a0 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_13.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_14.png b/assets/dolphin/external/L1_Showtime_128x64/frame_14.png new file mode 100755 index 000000000..1444ab836 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_14.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_15.png b/assets/dolphin/external/L1_Showtime_128x64/frame_15.png new file mode 100755 index 000000000..0945008a5 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_15.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_16.png b/assets/dolphin/external/L1_Showtime_128x64/frame_16.png new file mode 100755 index 000000000..0d1246fc7 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_16.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_17.png b/assets/dolphin/external/L1_Showtime_128x64/frame_17.png new file mode 100755 index 000000000..4d0b7227f Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_17.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_18.png b/assets/dolphin/external/L1_Showtime_128x64/frame_18.png new file mode 100755 index 000000000..d53d074e6 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_18.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_19.png b/assets/dolphin/external/L1_Showtime_128x64/frame_19.png new file mode 100755 index 000000000..0d421d372 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_19.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_2.png b/assets/dolphin/external/L1_Showtime_128x64/frame_2.png new file mode 100755 index 000000000..a52e051b9 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_2.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_20.png b/assets/dolphin/external/L1_Showtime_128x64/frame_20.png new file mode 100755 index 000000000..a5962bd2c Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_20.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_21.png b/assets/dolphin/external/L1_Showtime_128x64/frame_21.png new file mode 100755 index 000000000..5113c1095 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_21.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_22.png b/assets/dolphin/external/L1_Showtime_128x64/frame_22.png new file mode 100755 index 000000000..88ba06d37 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_22.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_23.png b/assets/dolphin/external/L1_Showtime_128x64/frame_23.png new file mode 100755 index 000000000..6507bdc6b Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_23.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_24.png b/assets/dolphin/external/L1_Showtime_128x64/frame_24.png new file mode 100755 index 000000000..5d4360a82 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_24.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_25.png b/assets/dolphin/external/L1_Showtime_128x64/frame_25.png new file mode 100755 index 000000000..817c78ab5 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_25.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_26.png b/assets/dolphin/external/L1_Showtime_128x64/frame_26.png new file mode 100755 index 000000000..93f02f48e Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_26.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_27.png b/assets/dolphin/external/L1_Showtime_128x64/frame_27.png new file mode 100755 index 000000000..2b856cc79 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_27.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_28.png b/assets/dolphin/external/L1_Showtime_128x64/frame_28.png new file mode 100755 index 000000000..bcf538072 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_28.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_29.png b/assets/dolphin/external/L1_Showtime_128x64/frame_29.png new file mode 100755 index 000000000..68b32d80c Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_29.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_3.png b/assets/dolphin/external/L1_Showtime_128x64/frame_3.png new file mode 100755 index 000000000..e7bf65287 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_3.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_30.png b/assets/dolphin/external/L1_Showtime_128x64/frame_30.png new file mode 100755 index 000000000..4d34f9afb Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_30.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_31.png b/assets/dolphin/external/L1_Showtime_128x64/frame_31.png new file mode 100755 index 000000000..274a16074 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_31.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_32.png b/assets/dolphin/external/L1_Showtime_128x64/frame_32.png new file mode 100755 index 000000000..7dbb729ef Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_32.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_33.png b/assets/dolphin/external/L1_Showtime_128x64/frame_33.png new file mode 100755 index 000000000..3cfbe5a98 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_33.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_34.png b/assets/dolphin/external/L1_Showtime_128x64/frame_34.png new file mode 100755 index 000000000..4e64d9db3 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_34.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_35.png b/assets/dolphin/external/L1_Showtime_128x64/frame_35.png new file mode 100755 index 000000000..09fe5c1cd Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_35.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_36.png b/assets/dolphin/external/L1_Showtime_128x64/frame_36.png new file mode 100755 index 000000000..4139bd8b5 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_36.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_37.png b/assets/dolphin/external/L1_Showtime_128x64/frame_37.png new file mode 100755 index 000000000..0384fbdae Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_37.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_38.png b/assets/dolphin/external/L1_Showtime_128x64/frame_38.png new file mode 100755 index 000000000..2632807f9 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_38.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_39.png b/assets/dolphin/external/L1_Showtime_128x64/frame_39.png new file mode 100755 index 000000000..f257489a1 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_39.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_4.png b/assets/dolphin/external/L1_Showtime_128x64/frame_4.png new file mode 100755 index 000000000..8a0c1734e Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_4.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_40.png b/assets/dolphin/external/L1_Showtime_128x64/frame_40.png new file mode 100755 index 000000000..cbcff2c89 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_40.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_41.png b/assets/dolphin/external/L1_Showtime_128x64/frame_41.png new file mode 100755 index 000000000..4377a91c6 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_41.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_42.png b/assets/dolphin/external/L1_Showtime_128x64/frame_42.png new file mode 100755 index 000000000..30b446707 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_42.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_43.png b/assets/dolphin/external/L1_Showtime_128x64/frame_43.png new file mode 100755 index 000000000..10d9579a3 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_43.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_44.png b/assets/dolphin/external/L1_Showtime_128x64/frame_44.png new file mode 100755 index 000000000..6d9362c51 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_44.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_45.png b/assets/dolphin/external/L1_Showtime_128x64/frame_45.png new file mode 100755 index 000000000..f834ad6c1 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_45.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_46.png b/assets/dolphin/external/L1_Showtime_128x64/frame_46.png new file mode 100755 index 000000000..4c799effb Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_46.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_47.png b/assets/dolphin/external/L1_Showtime_128x64/frame_47.png new file mode 100755 index 000000000..017549d29 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_47.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_48.png b/assets/dolphin/external/L1_Showtime_128x64/frame_48.png new file mode 100755 index 000000000..28497ac23 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_48.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_49.png b/assets/dolphin/external/L1_Showtime_128x64/frame_49.png new file mode 100755 index 000000000..5a25c32da Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_49.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_5.png b/assets/dolphin/external/L1_Showtime_128x64/frame_5.png new file mode 100755 index 000000000..04ad4360c Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_5.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_6.png b/assets/dolphin/external/L1_Showtime_128x64/frame_6.png new file mode 100755 index 000000000..86bcbf48c Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_6.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_7.png b/assets/dolphin/external/L1_Showtime_128x64/frame_7.png new file mode 100755 index 000000000..3e2a2c739 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_7.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_8.png b/assets/dolphin/external/L1_Showtime_128x64/frame_8.png new file mode 100755 index 000000000..d9babe8dc Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_8.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_9.png b/assets/dolphin/external/L1_Showtime_128x64/frame_9.png new file mode 100755 index 000000000..24dcababb Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_9.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/meta.txt b/assets/dolphin/external/L1_Showtime_128x64/meta.txt new file mode 100755 index 000000000..8722477a2 --- /dev/null +++ b/assets/dolphin/external/L1_Showtime_128x64/meta.txt @@ -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 diff --git a/assets/dolphin/external/manifest.txt b/assets/dolphin/external/manifest.txt index 2b15e7a4e..b2d85524a 100644 --- a/assets/dolphin/external/manifest.txt +++ b/assets/dolphin/external/manifest.txt @@ -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 diff --git a/documentation/images/widget.png b/documentation/images/widget.png new file mode 100644 index 000000000..f4dd1ed5b Binary files /dev/null and b/documentation/images/widget.png differ diff --git a/documentation/js/js_gui.md b/documentation/js/js_gui.md index 4d2d2497a..2efe10c05 100644 --- a/documentation/js/js_gui.md +++ b/documentation/js/js_gui.md @@ -27,23 +27,23 @@ always access the canvas through a viewport. In Flipper's terminology, a "View" is a fullscreen design element that assumes control over the entire viewport and all input events. Different types of views are available (not all of which are unfortunately currently implemented in JS): -| View | Has JS adapter? | -|----------------------|------------------| -| `button_menu` | ❌ | -| `button_panel` | ❌ | -| `byte_input` | ❌ | -| `dialog_ex` | ✅ (as `dialog`) | -| `empty_screen` | ✅ | -| `file_browser` | ❌ | -| `loading` | ✅ | -| `menu` | ❌ | -| `number_input` | ❌ | -| `popup` | ❌ | -| `submenu` | ✅ | -| `text_box` | ✅ | -| `text_input` | ✅ | -| `variable_item_list` | ❌ | -| `widget` | ❌ | +| View | Has JS adapter? | +|----------------------|-----------------------| +| `button_menu` | ❌ | +| `button_panel` | ❌ | +| `byte_input` | ✅ | +| `dialog_ex` | ✅ (as `dialog`) | +| `empty_screen` | ✅ | +| `file_browser` | ✅ (as `file_picker`) | +| `loading` | ✅ | +| `menu` | ❌ | +| `number_input` | ❌ | +| `popup` | ❌ | +| `submenu` | ✅ | +| `text_box` | ✅ | +| `text_input` | ✅ | +| `variable_item_list` | ❌ | +| `widget` | ✅ | In JS, each view has its own set of properties (or just "props"). The programmer can manipulate these properties in two ways: diff --git a/documentation/js/js_gui__widget.md b/documentation/js/js_gui__widget.md new file mode 100644 index 000000000..b871595b6 --- /dev/null +++ b/documentation/js/js_gui__widget.md @@ -0,0 +1,25 @@ +# js_gui__widget {#js_gui__widget} + +# Widget GUI view +Displays a combination of custom elements on one screen + +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. diff --git a/furi/core/event_loop.c b/furi/core/event_loop.c index c0998ea90..e09be0ca4 100644 --- a/furi/core/event_loop.c +++ b/furi/core/event_loop.c @@ -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); diff --git a/furi/core/event_loop_i.h b/furi/core/event_loop_i.h index 7016e1e1b..ef2774b97 100644 --- a/furi/core/event_loop_i.h +++ b/furi/core/event_loop_i.h @@ -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; diff --git a/furi/core/record.c b/furi/core/record.c index fa384369a..17c95aa9b 100644 --- a/furi/core/record.c +++ b/furi/core/record.c @@ -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(); diff --git a/furi/core/record.h b/furi/core/record.h index a269484f0..1fb20ed6f 100644 --- a/furi/core/record.h +++ b/furi/core/record.h @@ -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. */ diff --git a/furi/core/thread.c b/furi/core/thread.c index 5b8b59f12..6f74d636c 100644 --- a/furi/core/thread.c +++ b/furi/core/thread.c @@ -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); diff --git a/lib/lfrfid/lfrfid_worker_modes.c b/lib/lfrfid/lfrfid_worker_modes.c index d0569b300..645d2bd82 100644 --- a/lib/lfrfid/lfrfid_worker_modes.c +++ b/lib/lfrfid/lfrfid_worker_modes.c @@ -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,63 +518,88 @@ 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); + while(!lfrfid_worker_check_for_stop(worker)) { + FURI_LOG_D(TAG, "Data write"); + 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; - ProtocolId read_result = PROTOCOL_NO; - LFRFIDWorkerReadState state = lfrfid_worker_read_internal( - worker, - protocol_dict_get_features(worker->protocols, protocol), - LFRFID_WORKER_WRITE_VERIFY_TIME_MS, - &read_result); + protocol_dict_set_data(worker->protocols, protocol, verify_data, data_size); - if(state == LFRFIDWorkerReadOK) { - bool read_success = false; + bool can_be_written = + protocol_dict_get_write_data(worker->protocols, protocol, request); - if(read_result == protocol) { - protocol_dict_get_data(worker->protocols, protocol, read_data, data_size); - - if(memcmp(read_data, verify_data, data_size) == 0) { - read_success = true; - } - } - - if(read_success) { + if(!can_be_written) { + skips++; + if(skips == LFRFIDWriteTypeMax) { if(worker->write_cb) { - worker->write_cb(LFRFIDWorkerWriteOK, worker->cb_ctx); + worker->write_cb(LFRFIDWorkerWriteProtocolCannotBeWritten, worker->cb_ctx); } break; - } else { - unsuccessful_reads++; + } + continue; + } - if(unsuccessful_reads == LFRFID_WORKER_WRITE_MAX_UNSUCCESSFUL_READS) { - if(worker->write_cb) { - worker->write_cb(LFRFIDWorkerWriteFobCannotBeWritten, worker->cb_ctx); - } + 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, + protocol_dict_get_features(worker->protocols, protocol), + LFRFID_WORKER_WRITE_VERIFY_TIME_MS, + &read_result); + + if(state == LFRFIDWorkerReadOK) { + bool read_success = false; + + if(read_result == protocol) { + protocol_dict_get_data(worker->protocols, protocol, read_data, data_size); + + if(memcmp(read_data, verify_data, data_size) == 0) { + read_success = true; + } + } + + if(read_success) { + if(worker->write_cb) { + worker->write_cb(LFRFIDWorkerWriteOK, worker->cb_ctx); + } + break; + } else { + unsuccessful_reads++; + + if(unsuccessful_reads == LFRFID_WORKER_WRITE_MAX_UNSUCCESSFUL_READS) { + if(worker->write_cb) { + worker->write_cb(LFRFIDWorkerWriteFobCannotBeWritten, worker->cb_ctx); } } - } else if(state == LFRFIDWorkerReadExit) { - break; } + } else if(state == LFRFIDWorkerReadExit) { + break; + } - if(!too_long && - (furi_get_tick() - write_start_time) > LFRFID_WORKER_WRITE_TOO_LONG_TIME_MS) { - too_long = true; - if(worker->write_cb) { - worker->write_cb(LFRFIDWorkerWriteTooLongToWrite, worker->cb_ctx); - } + if(!too_long && + (furi_get_tick() - write_start_time) > LFRFID_WORKER_WRITE_TOO_LONG_TIME_MS) { + too_long = true; + if(worker->write_cb) { + worker->write_cb(LFRFIDWorkerWriteTooLongToWrite, worker->cb_ctx); } + } - lfrfid_worker_delay(worker, LFRFID_WORKER_WRITE_DROP_TIME_MS); - } - } else { - if(worker->write_cb) { - worker->write_cb(LFRFIDWorkerWriteProtocolCannotBeWritten, worker->cb_ctx); - } + lfrfid_worker_delay(worker, LFRFID_WORKER_WRITE_DROP_TIME_MS); } free(request); diff --git a/lib/lfrfid/protocols/lfrfid_protocols.h b/lib/lfrfid/protocols/lfrfid_protocols.h index 812ba9f97..8f52347d0 100644 --- a/lib/lfrfid/protocols/lfrfid_protocols.h +++ b/lib/lfrfid/protocols/lfrfid_protocols.h @@ -1,6 +1,7 @@ #pragma once #include #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; diff --git a/lib/lfrfid/protocols/protocol_electra.c b/lib/lfrfid/protocols/protocol_electra.c index 25fcdc514..50d82c8f1 100644 --- a/lib/lfrfid/protocols/protocol_electra.c +++ b/lib/lfrfid/protocols/protocol_electra.c @@ -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; } diff --git a/lib/lfrfid/protocols/protocol_em4100.c b/lib/lfrfid/protocols/protocol_em4100.c index e68bc0e2e..ed18133dc 100644 --- a/lib/lfrfid/protocols/protocol_em4100.c +++ b/lib/lfrfid/protocols/protocol_em4100.c @@ -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; } diff --git a/lib/lfrfid/protocols/protocol_gallagher.c b/lib/lfrfid/protocols/protocol_gallagher.c index 9ae0cf80a..bbed99706 100644 --- a/lib/lfrfid/protocols/protocol_gallagher.c +++ b/lib/lfrfid/protocols/protocol_gallagher.c @@ -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; } diff --git a/lib/lfrfid/protocols/protocol_viking.c b/lib/lfrfid/protocols/protocol_viking.c index 78499a415..7e9009fde 100644 --- a/lib/lfrfid/protocols/protocol_viking.c +++ b/lib/lfrfid/protocols/protocol_viking.c @@ -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; } diff --git a/lib/lfrfid/tools/em4305.c b/lib/lfrfid/tools/em4305.c new file mode 100644 index 000000000..3b072d38d --- /dev/null +++ b/lib/lfrfid/tools/em4305.c @@ -0,0 +1,152 @@ +#include "em4305.h" +#include +#include + +#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(); +} diff --git a/lib/lfrfid/tools/em4305.h b/lib/lfrfid/tools/em4305.h new file mode 100644 index 000000000..0cec00254 --- /dev/null +++ b/lib/lfrfid/tools/em4305.h @@ -0,0 +1,61 @@ +#pragma once +#include +#include + +#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 diff --git a/lib/mbedtls b/lib/mbedtls index edb8fec98..107ea89da 160000 --- a/lib/mbedtls +++ b/lib/mbedtls @@ -1 +1 @@ -Subproject commit edb8fec9882084344a314368ac7fd957a187519c +Subproject commit 107ea89daaefb9867ea9121002fbbdf926780e98 diff --git a/lib/mbedtls.scons b/lib/mbedtls.scons index 77add7696..759f263af 100644 --- a/lib/mbedtls.scons +++ b/lib/mbedtls.scons @@ -6,6 +6,7 @@ env.Append( "#/lib/mbedtls/include", ], SDK_HEADERS=[ + File("mbedtls/include/mbedtls/aes.h"), File("mbedtls/include/mbedtls/des.h"), File("mbedtls/include/mbedtls/sha1.h"), File("mbedtls/include/mbedtls/sha256.h"), @@ -37,6 +38,7 @@ libenv.AppendUnique( # sources = libenv.GlobRecursive("*.c*", "mbedtls/library") # Otherwise, we can just use the files we need: sources = [ + File("mbedtls/library/aes.c"), File("mbedtls/library/bignum.c"), File("mbedtls/library/bignum_core.c"), File("mbedtls/library/ecdsa.c"), diff --git a/lib/mjs/mjs_exec.c b/lib/mjs/mjs_exec.c index 8fdb2d7e5..273c38a34 100644 --- a/lib/mjs/mjs_exec.c +++ b/lib/mjs/mjs_exec.c @@ -584,7 +584,8 @@ static void mjs_apply_(struct mjs* mjs) { if(mjs_is_array(v)) { nargs = mjs_array_length(mjs, v); args = calloc(nargs, sizeof(args[0])); - for(i = 0; i < nargs; i++) args[i] = mjs_array_get(mjs, v, i); + for(i = 0; i < nargs; i++) + args[i] = mjs_array_get(mjs, v, i); } mjs_apply(mjs, &res, func, mjs_arg(mjs, 0), nargs, args); free(args); diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire.c b/lib/nfc/protocols/mf_desfire/mf_desfire.c index 4d54a2c0e..42ad4634b 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire.c +++ b/lib/nfc/protocols/mf_desfire/mf_desfire.c @@ -4,6 +4,46 @@ #define MF_DESFIRE_PROTOCOL_NAME "Mifare DESFire" +#define MF_DESFIRE_HW_MINOR_TYPE (0x00) +#define MF_DESFIRE_HW_MINOR_TYPE_MF3ICD40 (0x02) + +#define MF_DESFIRE_HW_MAJOR_TYPE_EV1 (0x01) +#define MF_DESFIRE_HW_MAJOR_TYPE_EV2 (0x12) +#define MF_DESFIRE_HW_MAJOR_TYPE_EV2_XL (0x22) +#define MF_DESFIRE_HW_MAJOR_TYPE_EV3 (0x33) +#define MF_DESFIRE_HW_MAJOR_TYPE_MF3ICD40 (0x00) + +#define MF_DESFIRE_STORAGE_SIZE_2K (0x16) +#define MF_DESFIRE_STORAGE_SIZE_4K (0x18) +#define MF_DESFIRE_STORAGE_SIZE_8K (0x1A) +#define MF_DESFIRE_STORAGE_SIZE_16K (0x1C) +#define MF_DESFIRE_STORAGE_SIZE_32K (0x1E) +#define MF_DESFIRE_STORAGE_SIZE_MF3ICD40 (0xFF) +#define MF_DESFIRE_STORAGE_SIZE_UNKNOWN (0xFF) + +#define MF_DESFIRE_TEST_TYPE_MF3ICD40(major, minor, storage) \ + (((major) == MF_DESFIRE_HW_MAJOR_TYPE_MF3ICD40) && \ + ((minor) == MF_DESFIRE_HW_MINOR_TYPE_MF3ICD40) && \ + ((storage) == MF_DESFIRE_STORAGE_SIZE_MF3ICD40)) + +static const char* mf_desfire_type_strings[] = { + [MfDesfireTypeMF3ICD40] = "(MF3ICD40)", + [MfDesfireTypeEV1] = "EV1", + [MfDesfireTypeEV2] = "EV2", + [MfDesfireTypeEV2XL] = "EV2 XL", + [MfDesfireTypeEV3] = "EV3", + [MfDesfireTypeUnknown] = "UNK", +}; + +static const char* mf_desfire_size_strings[] = { + [MfDesfireSize2k] = "2K", + [MfDesfireSize4k] = "4K", + [MfDesfireSize8k] = "8K", + [MfDesfireSize16k] = "16K", + [MfDesfireSize32k] = "32K", + [MfDesfireSizeUnknown] = "", +}; + const NfcDeviceBase nfc_device_mf_desfire = { .protocol_name = MF_DESFIRE_PROTOCOL_NAME, .alloc = (NfcDeviceAlloc)mf_desfire_alloc, @@ -26,7 +66,7 @@ MfDesfireData* mf_desfire_alloc(void) { data->master_key_versions = simple_array_alloc(&mf_desfire_key_version_array_config); data->application_ids = simple_array_alloc(&mf_desfire_app_id_array_config); data->applications = simple_array_alloc(&mf_desfire_application_array_config); - + data->device_name = furi_string_alloc(); return data; } @@ -38,6 +78,7 @@ void mf_desfire_free(MfDesfireData* data) { simple_array_free(data->application_ids); simple_array_free(data->master_key_versions); iso14443_4a_free(data->iso14443_4a_data); + furi_string_free(data->device_name); free(data); } @@ -228,10 +269,83 @@ bool mf_desfire_is_equal(const MfDesfireData* data, const MfDesfireData* other) simple_array_is_equal(data->applications, other->applications); } +static MfDesfireType mf_desfire_get_type_from_version(const MfDesfireVersion* const version) { + MfDesfireType type = MfDesfireTypeUnknown; + + switch(version->hw_major) { + case MF_DESFIRE_HW_MAJOR_TYPE_EV1: + type = MfDesfireTypeEV1; + break; + case MF_DESFIRE_HW_MAJOR_TYPE_EV2: + type = MfDesfireTypeEV2; + break; + case MF_DESFIRE_HW_MAJOR_TYPE_EV2_XL: + type = MfDesfireTypeEV2XL; + break; + case MF_DESFIRE_HW_MAJOR_TYPE_EV3: + type = MfDesfireTypeEV3; + break; + default: + if(MF_DESFIRE_TEST_TYPE_MF3ICD40(version->hw_major, version->hw_minor, version->hw_storage)) + type = MfDesfireTypeMF3ICD40; + break; + } + + return type; +} + +static MfDesfireSize mf_desfire_get_size_from_version(const MfDesfireVersion* const version) { + MfDesfireSize size = MfDesfireSizeUnknown; + + switch(version->hw_storage) { + case MF_DESFIRE_STORAGE_SIZE_2K: + size = MfDesfireSize2k; + break; + case MF_DESFIRE_STORAGE_SIZE_4K: + size = MfDesfireSize4k; + break; + case MF_DESFIRE_STORAGE_SIZE_8K: + size = MfDesfireSize8k; + break; + case MF_DESFIRE_STORAGE_SIZE_16K: + size = MfDesfireSize16k; + break; + case MF_DESFIRE_STORAGE_SIZE_32K: + size = MfDesfireSize32k; + break; + default: + if(MF_DESFIRE_TEST_TYPE_MF3ICD40(version->hw_major, version->hw_minor, version->hw_storage)) + size = MfDesfireSize4k; + break; + } + + return size; +} + const char* mf_desfire_get_device_name(const MfDesfireData* data, NfcDeviceNameType name_type) { - UNUSED(data); - UNUSED(name_type); - return MF_DESFIRE_PROTOCOL_NAME; + furi_check(data); + + const MfDesfireType type = mf_desfire_get_type_from_version(&data->version); + const MfDesfireSize size = mf_desfire_get_size_from_version(&data->version); + + if(type == MfDesfireTypeUnknown) { + furi_string_printf(data->device_name, "Unknown %s", MF_DESFIRE_PROTOCOL_NAME); + } else if(name_type == NfcDeviceNameTypeFull) { + furi_string_printf( + data->device_name, + "%s %s %s", + MF_DESFIRE_PROTOCOL_NAME, + mf_desfire_type_strings[type], + mf_desfire_size_strings[size]); + } else { + furi_string_printf( + data->device_name, + "%s %s", + mf_desfire_type_strings[type], + mf_desfire_size_strings[size]); + } + + return furi_string_get_cstr(data->device_name); } const uint8_t* mf_desfire_get_uid(const MfDesfireData* data, size_t* uid_len) { diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire.h b/lib/nfc/protocols/mf_desfire/mf_desfire.h index dd2009276..fb50008db 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire.h +++ b/lib/nfc/protocols/mf_desfire/mf_desfire.h @@ -29,6 +29,28 @@ extern "C" { #define MF_DESFIRE_APP_ID_SIZE (3) #define MF_DESFIRE_VALUE_SIZE (4) +typedef enum { + MfDesfireTypeMF3ICD40, + MfDesfireTypeEV1, + MfDesfireTypeEV2, + MfDesfireTypeEV2XL, + MfDesfireTypeEV3, + + MfDesfireTypeUnknown, + MfDesfireTypeNum, +} MfDesfireType; + +typedef enum { + MfDesfireSize2k, + MfDesfireSize4k, + MfDesfireSize8k, + MfDesfireSize16k, + MfDesfireSize32k, + + MfDesfireSizeUnknown, + MfDesfireSizeNum, +} MfDesfireSize; + typedef struct { uint8_t hw_vendor; uint8_t hw_type; @@ -131,6 +153,7 @@ typedef enum { MfDesfireErrorProtocol, MfDesfireErrorTimeout, MfDesfireErrorAuthentication, + MfDesfireErrorCommandNotSupported, } MfDesfireError; typedef struct { @@ -141,6 +164,7 @@ typedef struct { SimpleArray* master_key_versions; SimpleArray* application_ids; SimpleArray* applications; + FuriString* device_name; } MfDesfireData; extern const NfcDeviceBase nfc_device_mf_desfire; diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_poller.c b/lib/nfc/protocols/mf_desfire/mf_desfire_poller.c index bd8ecfaee..45e5a27f9 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_poller.c +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_poller.c @@ -82,9 +82,12 @@ static NfcCommand mf_desfire_poller_handler_read_free_memory(MfDesfirePoller* in FURI_LOG_D(TAG, "Read free memory success"); instance->state = MfDesfirePollerStateReadMasterKeySettings; } else if(instance->error == MfDesfireErrorNotPresent) { - FURI_LOG_D(TAG, "Read free memoty is unsupported"); + FURI_LOG_D(TAG, "Read free memory is not present"); instance->state = MfDesfirePollerStateReadMasterKeySettings; command = NfcCommandReset; + } else if(instance->error == MfDesfireErrorCommandNotSupported) { + FURI_LOG_D(TAG, "Read free memory is unsupported"); + instance->state = MfDesfirePollerStateReadMasterKeySettings; } else { FURI_LOG_E(TAG, "Failed to read free memory"); iso14443_4a_poller_halt(instance->iso14443_4a_poller); diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c b/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c index 1dd6c50e1..6d8dfda16 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c @@ -25,6 +25,8 @@ MfDesfireError mf_desfire_process_status_code(uint8_t status_code) { return MfDesfireErrorNone; case MF_DESFIRE_STATUS_AUTHENTICATION_ERROR: return MfDesfireErrorAuthentication; + case MF_DESFIRE_STATUS_ILLEGAL_COMMAND_CODE: + return MfDesfireErrorCommandNotSupported; default: return MfDesfireErrorProtocol; } diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync.c index 9958dc50d..252c46399 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync.c +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync.c @@ -23,6 +23,7 @@ typedef struct { FuriThreadId thread_id; MfUltralightError error; MfUltralightPollerContextData data; + const MfUltralightPollerAuthContext* auth_context; } MfUltralightPollerContext; typedef MfUltralightError (*MfUltralightPollerCmdHandler)( @@ -250,12 +251,17 @@ static NfcCommand mf_ultralight_poller_read_callback(NfcGenericEvent event, void poller_context->error = mfu_event->data->error; command = NfcCommandStop; } else if(mfu_event->type == MfUltralightPollerEventTypeAuthRequest) { - mfu_event->data->auth_context.skip_auth = true; - if(mf_ultralight_support_feature( - mfu_poller->feature_set, MfUltralightFeatureSupportAuthenticate)) { - mfu_event->data->auth_context.skip_auth = false; - memset( - mfu_poller->auth_context.tdes_key.data, 0x00, MF_ULTRALIGHT_C_AUTH_DES_KEY_SIZE); + if(poller_context->auth_context != NULL) { + mfu_event->data->auth_context = *poller_context->auth_context; + } else { + mfu_event->data->auth_context.skip_auth = true; + if(mfu_poller->data->type == MfUltralightTypeMfulC) { + mfu_event->data->auth_context.skip_auth = false; + memset( + mfu_poller->auth_context.tdes_key.data, + 0x00, + MF_ULTRALIGHT_C_AUTH_DES_KEY_SIZE); + } } } @@ -266,13 +272,17 @@ static NfcCommand mf_ultralight_poller_read_callback(NfcGenericEvent event, void return command; } -MfUltralightError mf_ultralight_poller_sync_read_card(Nfc* nfc, MfUltralightData* data) { +MfUltralightError mf_ultralight_poller_sync_read_card( + Nfc* nfc, + MfUltralightData* data, + const MfUltralightPollerAuthContext* auth_context) { furi_check(nfc); furi_check(data); MfUltralightPollerContext poller_context = {}; poller_context.thread_id = furi_thread_get_current_id(); poller_context.data.data = mf_ultralight_alloc(); + poller_context.auth_context = auth_context; NfcPoller* poller = nfc_poller_alloc(nfc, NfcProtocolMfUltralight); nfc_poller_start(poller, mf_ultralight_poller_read_callback, &poller_context); diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync.h b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync.h index ac585aad7..3f63203a7 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync.h +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync.h @@ -1,6 +1,7 @@ #pragma once #include "mf_ultralight.h" +#include "mf_ultralight_poller.h" #include #ifdef __cplusplus @@ -27,7 +28,10 @@ MfUltralightError mf_ultralight_poller_sync_read_tearing_flag( uint8_t flag_num, MfUltralightTearingFlag* data); -MfUltralightError mf_ultralight_poller_sync_read_card(Nfc* nfc, MfUltralightData* data); +MfUltralightError mf_ultralight_poller_sync_read_card( + Nfc* nfc, + MfUltralightData* data, + const MfUltralightPollerAuthContext* auth_context); #ifdef __cplusplus } diff --git a/lib/nfc/protocols/st25tb/st25tb_poller.c b/lib/nfc/protocols/st25tb/st25tb_poller.c index fd6dc4f09..674a439cd 100644 --- a/lib/nfc/protocols/st25tb/st25tb_poller.c +++ b/lib/nfc/protocols/st25tb/st25tb_poller.c @@ -88,7 +88,7 @@ static NfcCommand st25tb_poller_request_mode_handler(St25tbPoller* instance) { St25tbPollerEventDataModeRequest* mode_request_data = &instance->st25tb_event_data.mode_request; - furi_assert(mode_request_data->mode < St25tbPollerModeNum); + furi_check(mode_request_data->mode < St25tbPollerModeNum); if(mode_request_data->mode == St25tbPollerModeRead) { instance->state = St25tbPollerStateRead; diff --git a/scripts/debug/flipperapps.py b/scripts/debug/flipperapps.py index 81aa43c34..6d2d4c0a9 100644 --- a/scripts/debug/flipperapps.py +++ b/scripts/debug/flipperapps.py @@ -7,6 +7,33 @@ import zlib import gdb +class bcolors: + HEADER = "\033[95m" + OKBLUE = "\033[94m" + OKCYAN = "\033[96m" + OKGREEN = "\033[92m" + WARNING = "\033[93m" + FAIL = "\033[91m" + ENDC = "\033[0m" + BOLD = "\033[1m" + UNDERLINE = "\033[4m" + + +LOG_PREFIX = "[FURI]" + + +def error(line): + print(f"{bcolors.FAIL}{LOG_PREFIX} {line}{bcolors.ENDC}") + + +def warning(line): + print(f"{bcolors.WARNING}{LOG_PREFIX} {line}{bcolors.ENDC}") + + +def info(line): + print(f"{bcolors.OKGREEN}{LOG_PREFIX} {line}{bcolors.ENDC}") + + def get_file_crc32(filename): with open(filename, "rb") as f: return zlib.crc32(f.read()) @@ -39,12 +66,12 @@ class AppState: def is_debug_available(self) -> bool: have_debug_info = bool(self.debug_link_elf and self.debug_link_crc) if not have_debug_info: - print("No debug info available for this app") + warning("No debug info available for this app") return False debug_elf_path = self.get_original_elf_path() debug_elf_crc32 = get_file_crc32(debug_elf_path) if self.debug_link_crc != debug_elf_crc32: - print( + warning( f"Debug info ({debug_elf_path}) CRC mismatch: {self.debug_link_crc:08x} != {debug_elf_crc32:08x}, rebuild app" ) return False @@ -52,7 +79,7 @@ class AppState: def get_gdb_load_command(self) -> str: load_path = self.get_original_elf_path() - print(f"Loading debug information from {load_path}") + info(f"Loading debug information from {load_path}") load_command = ( f"add-symbol-file -readnow {load_path} 0x{self.text_address:08x} " ) @@ -121,12 +148,12 @@ class SetFapDebugElfRoot(gdb.Command): AppState.DEBUG_ELF_ROOT = arg try: global helper - print(f"Set '{arg}' as debug info lookup path for Flipper external apps") + info(f"Set '{arg}' as debug info lookup path for Flipper external apps") helper.attach_to_fw() gdb.events.stop.connect(helper.handle_stop) gdb.events.gdb_exiting.connect(helper.handle_exit) except gdb.error as e: - print(f"Support for Flipper external apps debug is not available: {e}") + error(f"Support for Flipper external apps debug is not available: {e}") class FlipperAppStateHelper: @@ -148,13 +175,29 @@ class FlipperAppStateHelper: gdb.execute(command) return True except gdb.error as e: - print(f"Failed to execute GDB command '{command}': {e}") + error(f"Failed to execute GDB command '{command}': {e}") return False + def _get_crash_message(self): + message = self.app_check_message.value() + if message == 1: + return "furi_assert failed" + elif message == 2: + return "furi_check failed" + else: + return message + def _sync_apps(self) -> None: + crash_message = self._get_crash_message() + if crash_message: + crash_message = f"! System crashed: {crash_message} !" + error("!" * len(crash_message)) + error(crash_message) + error("!" * len(crash_message)) + self.set_debug_mode(True) if not (app_list := self.app_list_ptr.value()): - print("Reset app loader state") + info("Reset app loader state") for app in self._current_apps: self._exec_gdb_command(app.get_gdb_unload_command()) self._current_apps = [] @@ -167,22 +210,23 @@ class FlipperAppStateHelper: for app in self._current_apps.copy(): if app.entry_address not in loaded_apps: - print(f"Application {app.name} is no longer loaded") + warning(f"Application {app.name} is no longer loaded") if not self._exec_gdb_command(app.get_gdb_unload_command()): - print(f"Failed to unload debug info for {app.name}") + error(f"Failed to unload debug info for {app.name}") self._current_apps.remove(app) for entry_point, app in loaded_apps.items(): if entry_point not in set(app.entry_address for app in self._current_apps): new_app_state = AppState.from_gdb(app) - print(f"New application loaded. Adding debug info") + warning(f"New application loaded. Adding debug info") if self._exec_gdb_command(new_app_state.get_gdb_load_command()): self._current_apps.append(new_app_state) else: - print(f"Failed to load debug info for {new_app_state}") + error(f"Failed to load debug info for {new_app_state}") def attach_to_fw(self) -> None: - print("Attaching to Flipper firmware") + info("Attaching to Flipper firmware") + self.app_check_message = gdb.lookup_global_symbol("__furi_check_message") self.app_list_ptr = gdb.lookup_global_symbol( "flipper_application_loaded_app_list" ) @@ -200,10 +244,10 @@ class FlipperAppStateHelper: try: gdb.execute(f"set variable furi_hal_debug_gdb_session_active = {int(mode)}") except gdb.error as e: - print(f"Failed to set debug mode: {e}") + error(f"Failed to set debug mode: {e}") # Init additional 'fap-set-debug-elf-root' command and set up hooks SetFapDebugElfRoot() helper = FlipperAppStateHelper() -print("Support for Flipper external apps debug is loaded") +info("Support for Flipper external apps debug is loaded") diff --git a/scripts/ufbt/project_template/app_template/.github/workflows/build.yml b/scripts/ufbt/project_template/app_template/.github/workflows/build.yml index 143847c4a..613590d47 100644 --- a/scripts/ufbt/project_template/app_template/.github/workflows/build.yml +++ b/scripts/ufbt/project_template/app_template/.github/workflows/build.yml @@ -34,7 +34,7 @@ jobs: with: sdk-channel: ${{ matrix.sdk-channel }} - name: Upload app artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: # See ufbt action docs for other output variables name: ${{ github.event.repository.name }}-${{ steps.build-app.outputs.suffix }} diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 6ce1736d0..928826d0d 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -84,6 +84,7 @@ Header,+,lib/libusb_stm32/inc/usb_hid.h,, Header,+,lib/libusb_stm32/inc/usb_std.h,, Header,+,lib/libusb_stm32/inc/usb_tmc.h,, Header,+,lib/libusb_stm32/inc/usbd_core.h,, +Header,+,lib/mbedtls/include/mbedtls/aes.h,, Header,+,lib/mbedtls/include/mbedtls/des.h,, Header,+,lib/mbedtls/include/mbedtls/ecdh.h,, Header,+,lib/mbedtls/include/mbedtls/ecdsa.h,, @@ -1892,6 +1893,21 @@ Function,+,manchester_encoder_advance,_Bool,"ManchesterEncoderState*, const _Boo Function,+,manchester_encoder_finish,ManchesterEncoderResult,ManchesterEncoderState* Function,+,manchester_encoder_reset,void,ManchesterEncoderState* Function,+,maxim_crc8,uint8_t,"const uint8_t*, const uint8_t, const uint8_t" +Function,-,mbedtls_aes_crypt_cbc,int,"mbedtls_aes_context*, int, size_t, unsigned char[16], const unsigned char*, unsigned char*" +Function,-,mbedtls_aes_crypt_cfb128,int,"mbedtls_aes_context*, int, size_t, size_t*, unsigned char[16], const unsigned char*, unsigned char*" +Function,-,mbedtls_aes_crypt_cfb8,int,"mbedtls_aes_context*, int, size_t, unsigned char[16], const unsigned char*, unsigned char*" +Function,-,mbedtls_aes_crypt_ctr,int,"mbedtls_aes_context*, size_t, size_t*, unsigned char[16], unsigned char[16], const unsigned char*, unsigned char*" +Function,-,mbedtls_aes_crypt_ecb,int,"mbedtls_aes_context*, int, const unsigned char[16], unsigned char[16]" +Function,-,mbedtls_aes_crypt_ofb,int,"mbedtls_aes_context*, size_t, size_t*, unsigned char[16], const unsigned char*, unsigned char*" +Function,-,mbedtls_aes_crypt_xts,int,"mbedtls_aes_xts_context*, int, size_t, const unsigned char[16], const unsigned char*, unsigned char*" +Function,-,mbedtls_aes_free,void,mbedtls_aes_context* +Function,-,mbedtls_aes_init,void,mbedtls_aes_context* +Function,-,mbedtls_aes_setkey_dec,int,"mbedtls_aes_context*, const unsigned char*, unsigned int" +Function,-,mbedtls_aes_setkey_enc,int,"mbedtls_aes_context*, const unsigned char*, unsigned int" +Function,-,mbedtls_aes_xts_free,void,mbedtls_aes_xts_context* +Function,-,mbedtls_aes_xts_init,void,mbedtls_aes_xts_context* +Function,-,mbedtls_aes_xts_setkey_dec,int,"mbedtls_aes_xts_context*, const unsigned char*, unsigned int" +Function,-,mbedtls_aes_xts_setkey_enc,int,"mbedtls_aes_xts_context*, const unsigned char*, unsigned int" Function,-,mbedtls_des3_crypt_cbc,int,"mbedtls_des3_context*, int, size_t, unsigned char[8], const unsigned char*, unsigned char*" Function,-,mbedtls_des3_crypt_ecb,int,"mbedtls_des3_context*, const unsigned char[8], unsigned char[8]" Function,-,mbedtls_des3_free,void,mbedtls_des3_context* @@ -1915,6 +1931,7 @@ Function,-,mbedtls_ecdh_can_do,int,mbedtls_ecp_group_id Function,-,mbedtls_ecdh_compute_shared,int,"mbedtls_ecp_group*, mbedtls_mpi*, const mbedtls_ecp_point*, const mbedtls_mpi*, int (*)(void*, unsigned char*, size_t), void*" Function,-,mbedtls_ecdh_free,void,mbedtls_ecdh_context* Function,-,mbedtls_ecdh_gen_public,int,"mbedtls_ecp_group*, mbedtls_mpi*, mbedtls_ecp_point*, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_ecdh_get_grp_id,mbedtls_ecp_group_id,mbedtls_ecdh_context* Function,-,mbedtls_ecdh_get_params,int,"mbedtls_ecdh_context*, const mbedtls_ecp_keypair*, mbedtls_ecdh_side" Function,-,mbedtls_ecdh_init,void,mbedtls_ecdh_context* Function,-,mbedtls_ecdh_make_params,int,"mbedtls_ecdh_context*, size_t*, unsigned char*, size_t, int (*)(void*, unsigned char*, size_t), void*" @@ -1955,7 +1972,9 @@ Function,-,mbedtls_ecp_group_init,void,mbedtls_ecp_group* Function,-,mbedtls_ecp_group_load,int,"mbedtls_ecp_group*, mbedtls_ecp_group_id" Function,-,mbedtls_ecp_grp_id_list,const mbedtls_ecp_group_id*, Function,-,mbedtls_ecp_is_zero,int,mbedtls_ecp_point* +Function,-,mbedtls_ecp_keypair_calc_public,int,"mbedtls_ecp_keypair*, int (*)(void*, unsigned char*, size_t), void*" Function,-,mbedtls_ecp_keypair_free,void,mbedtls_ecp_keypair* +Function,-,mbedtls_ecp_keypair_get_group_id,mbedtls_ecp_group_id,const mbedtls_ecp_keypair* Function,-,mbedtls_ecp_keypair_init,void,mbedtls_ecp_keypair* Function,-,mbedtls_ecp_mul,int,"mbedtls_ecp_group*, mbedtls_ecp_point*, const mbedtls_mpi*, const mbedtls_ecp_point*, int (*)(void*, unsigned char*, size_t), void*" Function,-,mbedtls_ecp_mul_restartable,int,"mbedtls_ecp_group*, mbedtls_ecp_point*, const mbedtls_mpi*, const mbedtls_ecp_point*, int (*)(void*, unsigned char*, size_t), void*, mbedtls_ecp_restart_ctx*" @@ -1968,6 +1987,7 @@ Function,-,mbedtls_ecp_point_read_binary,int,"const mbedtls_ecp_group*, mbedtls_ Function,-,mbedtls_ecp_point_read_string,int,"mbedtls_ecp_point*, int, const char*, const char*" Function,-,mbedtls_ecp_point_write_binary,int,"const mbedtls_ecp_group*, const mbedtls_ecp_point*, int, size_t*, unsigned char*, size_t" Function,-,mbedtls_ecp_read_key,int,"mbedtls_ecp_group_id, mbedtls_ecp_keypair*, const unsigned char*, size_t" +Function,-,mbedtls_ecp_set_public_key,int,"mbedtls_ecp_group_id, mbedtls_ecp_keypair*, const mbedtls_ecp_point*" Function,-,mbedtls_ecp_set_zero,int,mbedtls_ecp_point* Function,-,mbedtls_ecp_tls_read_group,int,"mbedtls_ecp_group*, const unsigned char**, size_t" Function,-,mbedtls_ecp_tls_read_group_id,int,"mbedtls_ecp_group_id*, const unsigned char**, size_t" @@ -1975,6 +1995,10 @@ Function,-,mbedtls_ecp_tls_read_point,int,"const mbedtls_ecp_group*, mbedtls_ecp Function,-,mbedtls_ecp_tls_write_group,int,"const mbedtls_ecp_group*, size_t*, unsigned char*, size_t" Function,-,mbedtls_ecp_tls_write_point,int,"const mbedtls_ecp_group*, const mbedtls_ecp_point*, int, size_t*, unsigned char*, size_t" Function,-,mbedtls_ecp_write_key,int,"mbedtls_ecp_keypair*, unsigned char*, size_t" +Function,-,mbedtls_ecp_write_key_ext,int,"const mbedtls_ecp_keypair*, size_t*, unsigned char*, size_t" +Function,-,mbedtls_ecp_write_public_key,int,"const mbedtls_ecp_keypair*, int, size_t*, unsigned char*, size_t" +Function,-,mbedtls_internal_aes_decrypt,int,"mbedtls_aes_context*, const unsigned char[16], unsigned char[16]" +Function,-,mbedtls_internal_aes_encrypt,int,"mbedtls_aes_context*, const unsigned char[16], unsigned char[16]" Function,-,mbedtls_internal_md5_process,int,"mbedtls_md5_context*, const unsigned char[64]" Function,-,mbedtls_internal_sha1_process,int,"mbedtls_sha1_context*, const unsigned char[64]" Function,-,mbedtls_internal_sha256_process,int,"mbedtls_sha256_context*, const unsigned char[64]" @@ -2303,9 +2327,9 @@ Function,+,pipe_install_as_stdio,void,PipeSide* Function,+,pipe_receive,size_t,"PipeSide*, void*, size_t, FuriWait" Function,+,pipe_role,PipeRole,PipeSide* Function,+,pipe_send,size_t,"PipeSide*, const void*, size_t, FuriWait" +Function,+,pipe_set_broken_callback,void,"PipeSide*, PipeSideBrokenCallback, FuriEventLoopEvent" Function,+,pipe_set_callback_context,void,"PipeSide*, void*" Function,+,pipe_set_data_arrived_callback,void,"PipeSide*, PipeSideDataArrivedCallback, FuriEventLoopEvent" -Function,+,pipe_set_broken_callback,void,"PipeSide*, PipeSideBrokenCallback, FuriEventLoopEvent" Function,+,pipe_set_space_freed_callback,void,"PipeSide*, PipeSideSpaceFreedCallback, FuriEventLoopEvent" Function,+,pipe_spaces_available,size_t,PipeSide* Function,+,pipe_state,PipeState,PipeSide* diff --git a/targets/f18/furi_hal/furi_hal_resources.h b/targets/f18/furi_hal/furi_hal_resources.h index 9a0d04cb6..23a88c215 100644 --- a/targets/f18/furi_hal/furi_hal_resources.h +++ b/targets/f18/furi_hal/furi_hal_resources.h @@ -2,6 +2,7 @@ #include #include +#include #ifdef __cplusplus extern "C" { @@ -40,6 +41,7 @@ typedef struct { const GpioPin* pin; const char* name; const FuriHalAdcChannel channel; + const FuriHalPwmOutputId pwm_output; const uint8_t number; const bool debug; } GpioPinRecord; diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 30507f687..a500bc152 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -101,6 +101,7 @@ Header,+,lib/libusb_stm32/inc/usb_hid.h,, Header,+,lib/libusb_stm32/inc/usb_std.h,, Header,+,lib/libusb_stm32/inc/usb_tmc.h,, Header,+,lib/libusb_stm32/inc/usbd_core.h,, +Header,+,lib/mbedtls/include/mbedtls/aes.h,, Header,+,lib/mbedtls/include/mbedtls/des.h,, Header,+,lib/mbedtls/include/mbedtls/ecdh.h,, Header,+,lib/mbedtls/include/mbedtls/ecdsa.h,, @@ -1023,6 +1024,7 @@ Function,+,elements_string_fit_width,void,"Canvas*, FuriString*, size_t" Function,+,elements_text_box,void,"Canvas*, int32_t, int32_t, size_t, size_t, Align, Align, const char*, _Bool" Function,+,elf_resolve_from_hashtable,_Bool,"const ElfApiInterface*, uint32_t, Elf32_Addr*" Function,+,elf_symbolname_hash,uint32_t,const char* +Function,+,em4305_write,void,LFRFIDEM4305* Function,+,empty_screen_alloc,EmptyScreen*, Function,+,empty_screen_free,void,EmptyScreen* Function,+,empty_screen_get_view,View*,EmptyScreen* @@ -2389,6 +2391,21 @@ Function,+,manchester_encoder_advance,_Bool,"ManchesterEncoderState*, const _Boo Function,+,manchester_encoder_finish,ManchesterEncoderResult,ManchesterEncoderState* Function,+,manchester_encoder_reset,void,ManchesterEncoderState* Function,+,maxim_crc8,uint8_t,"const uint8_t*, const uint8_t, const uint8_t" +Function,-,mbedtls_aes_crypt_cbc,int,"mbedtls_aes_context*, int, size_t, unsigned char[16], const unsigned char*, unsigned char*" +Function,-,mbedtls_aes_crypt_cfb128,int,"mbedtls_aes_context*, int, size_t, size_t*, unsigned char[16], const unsigned char*, unsigned char*" +Function,-,mbedtls_aes_crypt_cfb8,int,"mbedtls_aes_context*, int, size_t, unsigned char[16], const unsigned char*, unsigned char*" +Function,-,mbedtls_aes_crypt_ctr,int,"mbedtls_aes_context*, size_t, size_t*, unsigned char[16], unsigned char[16], const unsigned char*, unsigned char*" +Function,-,mbedtls_aes_crypt_ecb,int,"mbedtls_aes_context*, int, const unsigned char[16], unsigned char[16]" +Function,-,mbedtls_aes_crypt_ofb,int,"mbedtls_aes_context*, size_t, size_t*, unsigned char[16], const unsigned char*, unsigned char*" +Function,-,mbedtls_aes_crypt_xts,int,"mbedtls_aes_xts_context*, int, size_t, const unsigned char[16], const unsigned char*, unsigned char*" +Function,-,mbedtls_aes_free,void,mbedtls_aes_context* +Function,-,mbedtls_aes_init,void,mbedtls_aes_context* +Function,-,mbedtls_aes_setkey_dec,int,"mbedtls_aes_context*, const unsigned char*, unsigned int" +Function,-,mbedtls_aes_setkey_enc,int,"mbedtls_aes_context*, const unsigned char*, unsigned int" +Function,-,mbedtls_aes_xts_free,void,mbedtls_aes_xts_context* +Function,-,mbedtls_aes_xts_init,void,mbedtls_aes_xts_context* +Function,-,mbedtls_aes_xts_setkey_dec,int,"mbedtls_aes_xts_context*, const unsigned char*, unsigned int" +Function,-,mbedtls_aes_xts_setkey_enc,int,"mbedtls_aes_xts_context*, const unsigned char*, unsigned int" Function,-,mbedtls_des3_crypt_cbc,int,"mbedtls_des3_context*, int, size_t, unsigned char[8], const unsigned char*, unsigned char*" Function,-,mbedtls_des3_crypt_ecb,int,"mbedtls_des3_context*, const unsigned char[8], unsigned char[8]" Function,-,mbedtls_des3_free,void,mbedtls_des3_context* @@ -2412,6 +2429,7 @@ Function,-,mbedtls_ecdh_can_do,int,mbedtls_ecp_group_id Function,-,mbedtls_ecdh_compute_shared,int,"mbedtls_ecp_group*, mbedtls_mpi*, const mbedtls_ecp_point*, const mbedtls_mpi*, int (*)(void*, unsigned char*, size_t), void*" Function,-,mbedtls_ecdh_free,void,mbedtls_ecdh_context* Function,-,mbedtls_ecdh_gen_public,int,"mbedtls_ecp_group*, mbedtls_mpi*, mbedtls_ecp_point*, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_ecdh_get_grp_id,mbedtls_ecp_group_id,mbedtls_ecdh_context* Function,-,mbedtls_ecdh_get_params,int,"mbedtls_ecdh_context*, const mbedtls_ecp_keypair*, mbedtls_ecdh_side" Function,-,mbedtls_ecdh_init,void,mbedtls_ecdh_context* Function,-,mbedtls_ecdh_make_params,int,"mbedtls_ecdh_context*, size_t*, unsigned char*, size_t, int (*)(void*, unsigned char*, size_t), void*" @@ -2452,7 +2470,9 @@ Function,-,mbedtls_ecp_group_init,void,mbedtls_ecp_group* Function,-,mbedtls_ecp_group_load,int,"mbedtls_ecp_group*, mbedtls_ecp_group_id" Function,-,mbedtls_ecp_grp_id_list,const mbedtls_ecp_group_id*, Function,-,mbedtls_ecp_is_zero,int,mbedtls_ecp_point* +Function,-,mbedtls_ecp_keypair_calc_public,int,"mbedtls_ecp_keypair*, int (*)(void*, unsigned char*, size_t), void*" Function,-,mbedtls_ecp_keypair_free,void,mbedtls_ecp_keypair* +Function,-,mbedtls_ecp_keypair_get_group_id,mbedtls_ecp_group_id,const mbedtls_ecp_keypair* Function,-,mbedtls_ecp_keypair_init,void,mbedtls_ecp_keypair* Function,-,mbedtls_ecp_mul,int,"mbedtls_ecp_group*, mbedtls_ecp_point*, const mbedtls_mpi*, const mbedtls_ecp_point*, int (*)(void*, unsigned char*, size_t), void*" Function,-,mbedtls_ecp_mul_restartable,int,"mbedtls_ecp_group*, mbedtls_ecp_point*, const mbedtls_mpi*, const mbedtls_ecp_point*, int (*)(void*, unsigned char*, size_t), void*, mbedtls_ecp_restart_ctx*" @@ -2465,6 +2485,7 @@ Function,-,mbedtls_ecp_point_read_binary,int,"const mbedtls_ecp_group*, mbedtls_ Function,-,mbedtls_ecp_point_read_string,int,"mbedtls_ecp_point*, int, const char*, const char*" Function,-,mbedtls_ecp_point_write_binary,int,"const mbedtls_ecp_group*, const mbedtls_ecp_point*, int, size_t*, unsigned char*, size_t" Function,-,mbedtls_ecp_read_key,int,"mbedtls_ecp_group_id, mbedtls_ecp_keypair*, const unsigned char*, size_t" +Function,-,mbedtls_ecp_set_public_key,int,"mbedtls_ecp_group_id, mbedtls_ecp_keypair*, const mbedtls_ecp_point*" Function,-,mbedtls_ecp_set_zero,int,mbedtls_ecp_point* Function,-,mbedtls_ecp_tls_read_group,int,"mbedtls_ecp_group*, const unsigned char**, size_t" Function,-,mbedtls_ecp_tls_read_group_id,int,"mbedtls_ecp_group_id*, const unsigned char**, size_t" @@ -2472,6 +2493,10 @@ Function,-,mbedtls_ecp_tls_read_point,int,"const mbedtls_ecp_group*, mbedtls_ecp Function,-,mbedtls_ecp_tls_write_group,int,"const mbedtls_ecp_group*, size_t*, unsigned char*, size_t" Function,-,mbedtls_ecp_tls_write_point,int,"const mbedtls_ecp_group*, const mbedtls_ecp_point*, int, size_t*, unsigned char*, size_t" Function,-,mbedtls_ecp_write_key,int,"mbedtls_ecp_keypair*, unsigned char*, size_t" +Function,-,mbedtls_ecp_write_key_ext,int,"const mbedtls_ecp_keypair*, size_t*, unsigned char*, size_t" +Function,-,mbedtls_ecp_write_public_key,int,"const mbedtls_ecp_keypair*, int, size_t*, unsigned char*, size_t" +Function,-,mbedtls_internal_aes_decrypt,int,"mbedtls_aes_context*, const unsigned char[16], unsigned char[16]" +Function,-,mbedtls_internal_aes_encrypt,int,"mbedtls_aes_context*, const unsigned char[16], unsigned char[16]" Function,-,mbedtls_internal_md5_process,int,"mbedtls_md5_context*, const unsigned char[64]" Function,-,mbedtls_internal_sha1_process,int,"mbedtls_sha1_context*, const unsigned char[64]" Function,-,mbedtls_internal_sha256_process,int,"mbedtls_sha256_context*, const unsigned char[64]" @@ -2729,7 +2754,7 @@ Function,+,mf_ultralight_poller_read_page_from_sector,MfUltralightError,"MfUltra Function,+,mf_ultralight_poller_read_signature,MfUltralightError,"MfUltralightPoller*, MfUltralightSignature*" Function,+,mf_ultralight_poller_read_tearing_flag,MfUltralightError,"MfUltralightPoller*, uint8_t, MfUltralightTearingFlag*" Function,+,mf_ultralight_poller_read_version,MfUltralightError,"MfUltralightPoller*, MfUltralightVersion*" -Function,+,mf_ultralight_poller_sync_read_card,MfUltralightError,"Nfc*, MfUltralightData*" +Function,+,mf_ultralight_poller_sync_read_card,MfUltralightError,"Nfc*, MfUltralightData*, const MfUltralightPollerAuthContext*" Function,+,mf_ultralight_poller_sync_read_counter,MfUltralightError,"Nfc*, uint8_t, MfUltralightCounter*" Function,+,mf_ultralight_poller_sync_read_page,MfUltralightError,"Nfc*, uint16_t, MfUltralightPage*" Function,+,mf_ultralight_poller_sync_read_signature,MfUltralightError,"Nfc*, MfUltralightSignature*" diff --git a/targets/f7/ble_glue/ble_event_thread.c b/targets/f7/ble_glue/ble_event_thread.c index c6bc56846..3d1fdd196 100644 --- a/targets/f7/ble_glue/ble_event_thread.c +++ b/targets/f7/ble_glue/ble_event_thread.c @@ -90,7 +90,7 @@ void ble_event_thread_stop(void) { void ble_event_thread_start(void) { furi_check(event_thread == NULL); - event_thread = furi_thread_alloc_ex("BleEventWorker", 1024, ble_event_thread, NULL); + event_thread = furi_thread_alloc_ex("BleEventWorker", 1280, ble_event_thread, NULL); furi_thread_set_priority(event_thread, FuriThreadPriorityHigh); furi_thread_start(event_thread); } diff --git a/targets/f7/furi_hal/furi_hal_pwm.h b/targets/f7/furi_hal/furi_hal_pwm.h index 16acca05e..30a5467d2 100644 --- a/targets/f7/furi_hal/furi_hal_pwm.h +++ b/targets/f7/furi_hal/furi_hal_pwm.h @@ -12,6 +12,7 @@ extern "C" { #include typedef enum { + FuriHalPwmOutputIdNone, FuriHalPwmOutputIdTim1PA7, FuriHalPwmOutputIdLptim2PA4, } FuriHalPwmOutputId; diff --git a/targets/f7/furi_hal/furi_hal_resources.c b/targets/f7/furi_hal/furi_hal_resources.c index 123ebc420..a6ba0b083 100644 --- a/targets/f7/furi_hal/furi_hal_resources.c +++ b/targets/f7/furi_hal/furi_hal_resources.c @@ -73,6 +73,7 @@ const GpioPinRecord gpio_pins[] = { {.pin = &gpio_ext_pa7, .name = "PA7", .channel = FuriHalAdcChannel12, + .pwm_output = FuriHalPwmOutputIdTim1PA7, .number = 2, .debug = false}, {.pin = &gpio_ext_pa6, @@ -83,6 +84,7 @@ const GpioPinRecord gpio_pins[] = { {.pin = &gpio_ext_pa4, .name = "PA4", .channel = FuriHalAdcChannel9, + .pwm_output = FuriHalPwmOutputIdLptim2PA4, .number = 4, .debug = false}, {.pin = &gpio_ext_pb3, diff --git a/targets/f7/furi_hal/furi_hal_resources.h b/targets/f7/furi_hal/furi_hal_resources.h index ec8794cc1..3e8501b86 100644 --- a/targets/f7/furi_hal/furi_hal_resources.h +++ b/targets/f7/furi_hal/furi_hal_resources.h @@ -2,6 +2,7 @@ #include #include +#include #ifdef __cplusplus extern "C" { @@ -40,6 +41,7 @@ typedef struct { const GpioPin* pin; const char* name; const FuriHalAdcChannel channel; + const FuriHalPwmOutputId pwm_output; const uint8_t number; const bool debug; } GpioPinRecord; diff --git a/targets/f7/furi_hal/furi_hal_usb_cdc.c b/targets/f7/furi_hal/furi_hal_usb_cdc.c index 3408789dd..055618fe6 100644 --- a/targets/f7/furi_hal/furi_hal_usb_cdc.c +++ b/targets/f7/furi_hal/furi_hal_usb_cdc.c @@ -392,10 +392,11 @@ static void cdc_on_suspend(usbd_device* dev); static usbd_respond cdc_ep_config(usbd_device* dev, uint8_t cfg); static usbd_respond cdc_control(usbd_device* dev, usbd_ctlreq* req, usbd_rqc_callback* callback); + static usbd_device* usb_dev; -static FuriHalUsbInterface* cdc_if_cur = NULL; -static bool connected = false; -static CdcCallbacks* callbacks[IF_NUM_MAX] = {NULL}; +static volatile FuriHalUsbInterface* cdc_if_cur = NULL; +static volatile bool connected = false; +static volatile CdcCallbacks* callbacks[IF_NUM_MAX] = {NULL}; static void* cb_ctx[IF_NUM_MAX]; FuriHalUsbInterface usb_cdc_single = { @@ -506,8 +507,10 @@ uint8_t furi_hal_cdc_get_ctrl_line_state(uint8_t if_num) { void furi_hal_cdc_send(uint8_t if_num, uint8_t* buf, uint16_t len) { if(if_num == 0) { usbd_ep_write(usb_dev, CDC0_TXD_EP, buf, len); - } else { + } else if(if_num == 1) { usbd_ep_write(usb_dev, CDC1_TXD_EP, buf, len); + } else { + furi_crash(); } } @@ -515,8 +518,10 @@ int32_t furi_hal_cdc_receive(uint8_t if_num, uint8_t* buf, uint16_t max_len) { int32_t len = 0; if(if_num == 0) { len = usbd_ep_read(usb_dev, CDC0_RXD_EP, buf, max_len); - } else { + } else if(if_num == 1) { len = usbd_ep_read(usb_dev, CDC1_RXD_EP, buf, max_len); + } else { + furi_crash(); } return (len < 0) ? 0 : len; }