diff --git a/.vscode/ReadMe.md b/.vscode/ReadMe.md index 5aed0435cb..c7fc69f78b 100644 --- a/.vscode/ReadMe.md +++ b/.vscode/ReadMe.md @@ -1,4 +1,4 @@ -# Visual Studio Code workspace for Flipper Zero +# Visual Studio Code workspace for Flipper Zero {#vscode} ## Setup diff --git a/applications/drivers/subghz/cc1101_ext/cc1101_ext.h b/applications/drivers/subghz/cc1101_ext/cc1101_ext.h index 9cd3ba3466..d2279d0826 100644 --- a/applications/drivers/subghz/cc1101_ext/cc1101_ext.h +++ b/applications/drivers/subghz/cc1101_ext/cc1101_ext.h @@ -1,6 +1,6 @@ /** - * @file furi_hal_subghz.h - * SubGhz HAL API + * @file cc1101_ext.h + * @brief External CC1101 transceiver access API. */ #pragma once diff --git a/applications/examples/example_apps_assets/README.md b/applications/examples/example_apps_assets/README.md index 024c0877be..bf7e63e428 100644 --- a/applications/examples/example_apps_assets/README.md +++ b/applications/examples/example_apps_assets/README.md @@ -1,7 +1,11 @@ -# Apps Assets folder Example +# Apps Assets folder Example {#example_app_assets} This example shows how to use the Apps Assets folder to store data that is not part of the application itself, but is required for its operation, and that data is provided with the application. +## Source code + +Source code for this example can be found [here](https://github.com/flipperdevices/flipperzero-firmware/tree/dev/applications/examples/example_apps_assets). + ## What is the Apps Assets Folder? The **Apps Assets** folder is a folder where external applications unpack their assets. diff --git a/applications/examples/example_apps_assets/example_apps_assets.c b/applications/examples/example_apps_assets/example_apps_assets.c index 2c2cc8a874..dae81a8dac 100644 --- a/applications/examples/example_apps_assets/example_apps_assets.c +++ b/applications/examples/example_apps_assets/example_apps_assets.c @@ -1,3 +1,7 @@ +/** + * @file example_apps_assets.c + * @brief Application assets example. + */ #include #include #include diff --git a/applications/examples/example_apps_data/README.md b/applications/examples/example_apps_data/README.md index 0e51daf18c..fb76175470 100644 --- a/applications/examples/example_apps_data/README.md +++ b/applications/examples/example_apps_data/README.md @@ -1,7 +1,11 @@ -# Apps Data folder Example +# Apps Data folder Example {#example_app_data} This example demonstrates how to utilize the Apps Data folder to store data that is not part of the app itself, such as user data, configuration files, and so forth. +## Source code + +Source code for this example can be found [here](https://github.com/flipperdevices/flipperzero-firmware/tree/dev/applications/examples/example_apps_data). + ## What is the Apps Data Folder? The **Apps Data** folder is a folder used to store data for external apps that are not part of the main firmware. diff --git a/applications/examples/example_apps_data/example_apps_data.c b/applications/examples/example_apps_data/example_apps_data.c index 7a297b01cf..f40d526c9a 100644 --- a/applications/examples/example_apps_data/example_apps_data.c +++ b/applications/examples/example_apps_data/example_apps_data.c @@ -1,3 +1,7 @@ +/** + * @file example_apps_data.c + * @brief Application data example. + */ #include #include diff --git a/applications/examples/example_ble_beacon/ble_beacon_app.h b/applications/examples/example_ble_beacon/ble_beacon_app.h index 563bd5beda..61c8c56d1e 100644 --- a/applications/examples/example_ble_beacon/ble_beacon_app.h +++ b/applications/examples/example_ble_beacon/ble_beacon_app.h @@ -1,3 +1,7 @@ +/** + * @file ble_beacon_app.h + * @brief BLE beacon example. + */ #pragma once #include "extra_beacon.h" diff --git a/applications/examples/example_custom_font/example_custom_font.c b/applications/examples/example_custom_font/example_custom_font.c index 15eeb5f02a..2fec419041 100644 --- a/applications/examples/example_custom_font/example_custom_font.c +++ b/applications/examples/example_custom_font/example_custom_font.c @@ -1,3 +1,7 @@ +/** + * @file example_custom_font.c + * @brief Custom font example. + */ #include #include diff --git a/applications/examples/example_images/ReadMe.md b/applications/examples/example_images/ReadMe.md index d884a0a975..bf57950086 100644 --- a/applications/examples/example_images/ReadMe.md +++ b/applications/examples/example_images/ReadMe.md @@ -1,11 +1,21 @@ -# Application icons +# Application icons {#example_app_images} + +## Source code + +Source code for this example can be found [here](https://github.com/flipperdevices/flipperzero-firmware/tree/dev/applications/examples/example_images). + +## General principle + To use icons, do the following: -* add a line to the application manifest: `fap_icon_assets="folder"`, where `folder` points to the folder where your icons are located -* add `#include "application_id_icons.h"` to the application code, where `application_id` is the appid from the manifest -* every icon in the folder will be available as a `I_icon_name` variable, where `icon_name` is the name of the icon file without the extension + +* Add a line to the application manifest: `fap_icon_assets="folder"`, where `folder` points to the folder where your icons are located +* Add `#include "application_id_icons.h"` to the application code, where `application_id` is the appid from the manifest +* Every icon in the folder will be available as a `I_icon_name` variable, where `icon_name` is the name of the icon file without the extension ## Example + We have an application with the following manifest: + ``` App( appid="example_images", @@ -17,6 +27,7 @@ App( So the icons are in the `images` folder and will be available in the generated `example_images_icons.h` file. The example code is located in `example_images_main.c` and contains the following line: + ``` #include "example_images_icons.h" ``` diff --git a/applications/examples/example_images/example_images.c b/applications/examples/example_images/example_images.c index b00818cd66..c43a30b698 100644 --- a/applications/examples/example_images/example_images.c +++ b/applications/examples/example_images/example_images.c @@ -1,3 +1,7 @@ +/** + * @file example_images.c + * @brief Custom images example. + */ #include #include diff --git a/applications/examples/example_plugins/example_plugins.c b/applications/examples/example_plugins/example_plugins.c index 7e71e0d2eb..4f2150884c 100644 --- a/applications/examples/example_plugins/example_plugins.c +++ b/applications/examples/example_plugins/example_plugins.c @@ -1,5 +1,7 @@ -/* - * An example of a plugin host application. +/** + * @file example_plugins.c + * @brief Plugin host application example. + * * Loads a single plugin and calls its methods. */ diff --git a/applications/examples/example_plugins/example_plugins_multi.c b/applications/examples/example_plugins/example_plugins_multi.c index 3525b39ea4..40abff5612 100644 --- a/applications/examples/example_plugins/example_plugins_multi.c +++ b/applications/examples/example_plugins/example_plugins_multi.c @@ -1,5 +1,7 @@ -/* - * An example of an advanced plugin host application. +/** + * @file example_plugins_multi.c + * @brief Advanced plugin host application example. + * * It uses PluginManager to load all plugins from a directory */ diff --git a/applications/examples/example_plugins/plugin1.c b/applications/examples/example_plugins/plugin1.c index 1562193533..de8041f343 100644 --- a/applications/examples/example_plugins/plugin1.c +++ b/applications/examples/example_plugins/plugin1.c @@ -1,4 +1,9 @@ -/* A simple plugin implementing example_plugins application's plugin interface */ +/** + * @file plugin1.c + * @brief Plugin example 1. + * + * A simple plugin implementing example_plugins application's plugin interface + */ #include "plugin_interface.h" diff --git a/applications/examples/example_plugins/plugin2.c b/applications/examples/example_plugins/plugin2.c index 0b774dad21..a196437f4b 100644 --- a/applications/examples/example_plugins/plugin2.c +++ b/applications/examples/example_plugins/plugin2.c @@ -1,4 +1,9 @@ -/* Second plugin implementing example_plugins application's plugin interface */ +/** + * @file plugin2.c + * @brief Plugin example 2. + * + * Second plugin implementing example_plugins application's plugin interface + */ #include "plugin_interface.h" diff --git a/applications/examples/example_plugins/plugin_interface.h b/applications/examples/example_plugins/plugin_interface.h index 25d95d2943..85428429ee 100644 --- a/applications/examples/example_plugins/plugin_interface.h +++ b/applications/examples/example_plugins/plugin_interface.h @@ -1,7 +1,11 @@ +/** + * @file plugin_interface.h + * @brief Example plugin interface. + * + * Common interface between a plugin and host application + */ #pragma once -/* Common interface between a plugin and host application */ - #define PLUGIN_APP_ID "example_plugins" #define PLUGIN_API_VERSION 1 diff --git a/applications/examples/example_plugins_advanced/app_api.h b/applications/examples/example_plugins_advanced/app_api.h index 7035b79f52..60a52e6f76 100644 --- a/applications/examples/example_plugins_advanced/app_api.h +++ b/applications/examples/example_plugins_advanced/app_api.h @@ -1,9 +1,12 @@ -#pragma once - -/* +/** + * @file app_api.h + * @brief Application API example. + * * This file contains an API that is internally implemented by the application * It is also exposed to plugins to allow them to use the application's API. */ +#pragma once + #include #ifdef __cplusplus diff --git a/applications/examples/example_plugins_advanced/plugin1.c b/applications/examples/example_plugins_advanced/plugin1.c index bf0ab50b42..9130810079 100644 --- a/applications/examples/example_plugins_advanced/plugin1.c +++ b/applications/examples/example_plugins_advanced/plugin1.c @@ -1,4 +1,7 @@ -/* +/** + * @file plugin1.c + * @brief Plugin example 1. + * * This plugin uses both firmware's API interface and private application headers. * It can be loaded by a plugin manager that uses CompoundApiInterface, * which combines both interfaces. diff --git a/applications/examples/example_plugins_advanced/plugin2.c b/applications/examples/example_plugins_advanced/plugin2.c index f0b2f726db..1ea5590b22 100644 --- a/applications/examples/example_plugins_advanced/plugin2.c +++ b/applications/examples/example_plugins_advanced/plugin2.c @@ -1,4 +1,7 @@ -/* +/** + * @file plugin2.c + * @brief Plugin example 2. + * * This plugin uses both firmware's API interface and private application headers. * It can be loaded by a plugin manager that uses CompoundApiInterface, * which combines both interfaces. diff --git a/applications/examples/example_plugins_advanced/plugin_interface.h b/applications/examples/example_plugins_advanced/plugin_interface.h index d99b335ff0..d78dc9ecc1 100644 --- a/applications/examples/example_plugins_advanced/plugin_interface.h +++ b/applications/examples/example_plugins_advanced/plugin_interface.h @@ -1,7 +1,11 @@ +/** + * @file plugin_interface.h + * @brief Example plugin interface. + * + * Common interface between a plugin and host application + */ #pragma once -/* Common interface between a plugin and host application */ - #define PLUGIN_APP_ID "example_plugins_advanced" #define PLUGIN_API_VERSION 1 diff --git a/applications/examples/example_thermo/README.md b/applications/examples/example_thermo/README.md index d298de6430..4af2e60432 100644 --- a/applications/examples/example_thermo/README.md +++ b/applications/examples/example_thermo/README.md @@ -1,8 +1,14 @@ -# 1-Wire Thermometer +# 1-Wire Thermometer {#example_thermo} + This example application demonstrates the use of the 1-Wire library with a DS18B20 thermometer. It also covers basic GUI, input handling, threads and localisation. +## Source code + +Source code for this example can be found [here](https://github.com/flipperdevices/flipperzero-firmware/tree/dev/applications/examples/example_thermo). + ## Electrical connections + Before launching the application, connect the sensor to Flipper's external GPIO according to the table below: | DS18B20 | Flipper | | :-----: | :-----: | @@ -15,12 +21,14 @@ Before launching the application, connect the sensor to Flipper's external GPIO *NOTE 2*: For any other pin than 17, connect an external 4.7k pull-up resistor to pin 9. ## Launching the application + In order to launch this demo, follow the steps below: 1. Make sure your Flipper has an SD card installed. 2. Connect your Flipper to the computer via a USB cable. 3. Run `./fbt launch APPSRC=example_thermo` in your terminal emulator of choice. ## Changing the data pin + It is possible to use other GPIO pin as a 1-Wire data pin. In order to change it, set the `THERMO_GPIO_PIN` macro to any of the options listed below: ```c diff --git a/applications/examples/example_thermo/example_thermo.c b/applications/examples/example_thermo/example_thermo.c index 5abd963a19..576ece3826 100644 --- a/applications/examples/example_thermo/example_thermo.c +++ b/applications/examples/example_thermo/example_thermo.c @@ -1,4 +1,7 @@ -/* +/** + * @file example_thermo.c + * @brief 1-Wire thermometer example. + * * This file contains an example application that reads and displays * the temperature from a DS18B20 1-wire thermometer. * diff --git a/applications/main/infrared/resources/infrared/assets/ac.ir b/applications/main/infrared/resources/infrared/assets/ac.ir index c26d282c4c..2f076fbabd 100644 --- a/applications/main/infrared/resources/infrared/assets/ac.ir +++ b/applications/main/infrared/resources/infrared/assets/ac.ir @@ -1,43 +1,43 @@ Filetype: IR library file Version: 1 -# Last Updated 16th Aug, 2023 -# Last Checked 16th Aug, 2023 +# Last Updated 21st Feb, 2024 +# Last Checked 21st Feb, 2024 # # Model: Electrolux EACM-16 HP/N3 name: Off type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 502 3436 510 475 509 476 508 477 507 477 507 479 505 480 504 480 504 490 504 481 502 482 501 483 563 420 511 474 510 475 509 476 508 485 561 423 508 476 508 477 507 478 506 479 505 480 504 481 503 517 508 476 508 478 506 479 505 479 505 481 503 483 521 1456 501 498 507 479 505 480 504 481 503 482 501 483 563 421 562 422 509 499 506 479 505 480 504 481 503 482 502 484 510 1451 506 479 505 1542 562 1396 509 471 502 476 508 469 504 3425 511 # name: Dh type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 507 3430 506 479 505 480 504 481 503 481 503 483 501 485 509 1453 504 1465 503 482 502 483 511 473 500 485 509 476 508 477 507 478 506 487 507 477 507 478 506 479 505 480 504 482 502 483 501 484 500 523 503 482 502 484 500 485 509 476 508 476 508 478 506 1456 501 501 504 482 502 483 501 484 500 485 509 476 508 477 507 1455 502 509 506 479 505 1457 500 485 509 476 508 1454 503 482 502 483 501 568 499 1459 509 1450 507 471 502 474 510 3421 505 # name: Cool_hi type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 504 3433 503 482 502 484 510 474 510 475 509 476 508 478 506 1456 564 1405 510 475 509 476 508 502 482 477 507 478 506 479 505 480 504 489 505 480 504 481 503 482 502 483 511 473 511 474 510 475 509 509 506 479 505 480 504 481 503 482 512 473 511 474 510 476 508 1469 509 475 509 476 508 477 507 478 506 479 505 480 504 481 503 505 510 475 509 502 482 503 481 504 480 505 478 507 477 1459 509 560 507 1451 506 473 511 493 480 1450 507 3422 503 # name: Cool_lo type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 525 3615 530 506 561 474 562 474 562 473 563 473 531 505 562 1502 528 1542 562 474 562 474 530 505 531 504 532 504 532 504 616 419 533 510 589 447 526 509 527 509 527 509 527 508 528 508 528 507 529 542 525 510 526 509 527 509 527 509 527 508 528 508 528 1535 527 524 533 503 533 503 533 502 534 502 534 502 534 501 535 501 525 534 533 502 534 502 534 501 535 502 534 1529 533 503 533 503 533 587 533 497 528 501 524 1536 526 501 524 3609 526 # name: Heat_hi type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 531 3406 530 455 529 456 528 457 537 447 537 448 535 450 534 1429 528 1442 536 448 536 449 534 451 532 452 532 453 530 454 530 455 529 464 530 454 529 456 528 457 537 448 536 449 535 450 533 451 533 490 535 449 534 450 534 451 533 452 532 453 531 455 529 1433 534 1443 535 449 535 450 534 452 531 453 530 454 530 455 529 456 538 472 532 452 532 454 530 1433 535 1427 530 1432 536 1427 530 1431 537 1511 530 448 536 1422 535 1423 534 1422 535 3395 530 # name: Heat_lo type: raw frequency: 38000 -duty_cycle: 0.33 +duty_cycle: 0.330000 data: 506 3430 506 478 506 479 505 480 504 481 503 482 502 484 500 1463 505 1465 503 482 502 483 501 484 500 485 509 476 508 477 507 478 506 486 508 477 507 478 506 479 505 480 504 481 503 482 502 483 500 523 502 482 502 483 501 484 500 485 509 476 508 478 506 1455 502 498 507 478 506 479 505 481 503 482 501 483 500 484 500 485 509 500 505 481 502 482 502 1461 507 1455 502 1459 509 476 508 477 507 563 504 1453 504 1454 503 1454 503 1453 504 3426 499 # # Model: Hisense Generic @@ -959,3 +959,27 @@ type: raw frequency: 38000 duty_cycle: 0.330000 data: 664 17757 3057 8901 528 469 550 1434 556 465 553 440 529 465 554 439 555 439 555 438 555 440 553 1435 552 443 550 470 524 1466 522 472 522 472 521 1467 523 1466 549 1440 549 1440 548 1440 548 445 549 445 549 445 549 446 548 470 524 471 523 471 523 471 523 472 522 472 522 473 522 472 523 472 548 446 549 445 550 445 548 446 548 446 548 446 548 446 548 447 547 470 524 471 523 471 523 471 523 471 523 474 519 474 521 474 521 473 522 473 546 447 547 1441 548 1442 546 1442 547 1442 546 2947 3023 8935 522 1466 522 472 522 498 496 498 495 499 496 498 496 498 521 473 522 471 523 1466 522 471 523 471 522 1466 523 471 523 1467 522 1467 522 1467 521 1493 495 1494 496 1493 521 473 522 472 522 471 523 472 522 471 523 472 522 472 522 472 522 472 522 472 522 472 522 472 522 473 521 499 495 499 495 499 495 499 496 498 522 473 522 472 523 471 522 472 522 472 522 472 522 472 522 472 522 472 522 473 521 473 521 473 521 473 521 499 495 500 494 500 495 499 496 498 522 2947 3023 8937 521 1468 520 473 521 473 521 473 521 473 521 473 521 473 521 474 520 474 520 1470 518 500 494 500 494 500 494 500 494 1494 521 1468 521 473 521 1468 520 1468 520 1469 520 1469 520 1470 518 1495 493 1496 493 1495 494 500 519 475 520 474 520 1469 519 1469 520 1469 519 474 520 474 520 475 519 477 517 500 494 1496 493 1496 493 1496 493 500 495 1495 519 475 518 475 519 475 518 476 518 475 519 1470 519 500 494 500 494 501 493 501 493 501 493 1499 490 1497 493 1497 492 1496 518 +# +name: Heat_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 148 110377 9082 4422 707 499 706 500 704 1603 703 1606 701 505 700 507 699 506 700 507 699 506 700 1607 700 1608 700 1609 699 506 699 507 699 506 699 507 699 507 699 506 700 507 699 507 699 507 699 1608 699 1607 699 507 699 507 699 507 699 507 699 507 699 1609 699 507 699 1608 699 507 699 507 699 1609 699 507 699 19940 700 506 700 506 699 507 699 507 699 506 700 506 700 507 699 507 699 507 700 507 699 506 699 507 699 507 699 507 699 507 699 507 699 507 699 507 699 507 698 507 700 507 699 507 699 507 699 507 699 507 699 508 698 508 698 507 699 507 699 508 698 1610 699 508 698 +# +name: Cool_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 315 101050 3094 3056 3093 4437 580 1648 572 534 576 1649 582 525 574 530 580 1646 574 1653 578 529 570 534 576 529 571 534 576 529 570 1655 576 1651 580 527 572 532 578 1647 573 1654 577 1651 580 526 573 531 579 526 573 531 579 526 573 531 579 526 573 531 579 526 573 531 579 525 574 531 579 525 574 531 579 1646 574 532 578 526 573 531 579 526 573 531 579 526 573 1652 579 527 572 1653 578 528 571 534 576 528 571 533 577 528 571 533 577 528 572 533 577 528 572 532 578 527 572 532 578 527 572 532 578 526 573 1652 579 527 572 532 578 527 572 532 578 527 572 532 578 526 573 531 579 526 573 531 579 526 573 531 579 525 574 530 580 525 574 530 580 525 574 530 580 524 575 529 581 524 575 529 571 534 576 528 571 533 577 528 571 533 577 528 571 533 577 527 572 532 578 527 572 532 578 526 573 531 579 526 573 531 579 525 574 531 579 525 574 530 580 525 574 1650 581 525 574 1651 580 1647 573 533 577 527 572 1653 578 528 572 1654 577 1650 581 1646 574 71637 254 +# +name: Cool_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 284 19161 3098 3053 3096 4435 572 1656 575 532 578 1648 572 534 576 530 570 1682 549 1652 579 527 572 534 576 1649 571 1656 575 1652 579 1649 571 1656 575 531 579 527 572 1653 578 1649 571 1656 575 531 579 527 572 532 578 527 572 533 577 527 572 533 577 527 573 532 578 527 572 532 578 527 573 532 578 527 572 1652 579 527 572 533 577 528 571 533 577 528 571 533 577 1648 572 533 577 1649 571 535 575 530 569 536 574 531 569 536 574 530 569 536 574 530 570 535 575 530 570 535 575 530 569 535 575 530 569 535 575 1649 571 535 575 531 568 536 574 531 568 536 574 531 568 536 574 531 569 536 574 530 569 536 574 530 569 535 575 530 569 535 575 530 569 535 575 530 570 535 575 529 570 534 576 529 570 534 576 529 570 534 576 528 571 534 576 528 571 534 576 528 571 534 576 528 571 534 576 528 571 533 577 528 571 533 577 528 572 533 577 528 571 533 577 528 572 1652 579 527 572 1653 578 529 570 534 576 529 570 535 575 529 570 1654 577 1677 554 1673 547 +# +name: Cool_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 301 132136 5036 2167 337 1766 361 689 358 692 366 684 363 1770 357 692 366 684 363 718 329 690 357 1776 361 687 360 1773 364 1767 360 689 358 1775 362 1769 357 1774 363 1768 359 1773 364 684 363 718 329 1773 364 684 363 718 329 691 356 694 364 716 331 719 328 1775 362 1769 358 1774 363 1768 359 1772 365 714 333 1770 357 1774 363 716 331 719 328 722 336 715 332 718 329 721 326 724 334 716 331 719 328 722 336 715 332 718 329 1773 364 1767 360 1772 354 1777 360 719 328 721 326 725 333 717 330 29455 5036 2139 354 1777 360 688 359 691 367 714 333 1770 356 692 366 684 363 687 360 690 357 1776 361 688 359 1773 364 1768 359 689 358 1775 362 1769 357 1774 363 1768 359 1773 364 684 363 687 360 1773 364 685 362 688 359 691 356 694 364 686 361 689 358 692 366 685 362 688 359 691 356 1777 360 1771 355 693 365 685 362 1771 355 693 365 1768 359 1773 364 1767 360 689 358 692 366 685 362 1771 355 1775 362 687 360 690 357 1775 362 687 360 690 357 693 365 716 331 689 358 1774 363 686 361 689 358 692 366 685 362 688 359 691 356 694 364 686 361 689 358 692 366 685 362 688 359 691 356 694 364 686 361 689 358 692 366 685 362 1771 355 693 365 1768 358 1773 364 684 363 687 360 690 357 693 365 1768 359 690 357 1776 361 688 359 691 356 694 364 686 361 689 358 692 366 685 362 1770 356 693 365 685 362 688 359 691 356 1777 360 1771 355 693 365 686 361 689 358 692 366 685 362 1770 356 diff --git a/applications/main/nfc/plugins/supported_cards/bip.c b/applications/main/nfc/plugins/supported_cards/bip.c index f6fed6774b..c3587fbd33 100644 --- a/applications/main/nfc/plugins/supported_cards/bip.c +++ b/applications/main/nfc/plugins/supported_cards/bip.c @@ -1,70 +1,139 @@ #include "nfc_supported_card_plugin.h" +#include -#include -#include -#include +#include "protocols/mf_classic/mf_classic.h" #include +#include +#include + #define TAG "Bip" -#define SECTOR_BLOCK_OFFSET(sector, block) (((sector) * 4) + (block)) - -static const uint64_t bip_keys_a[] = { - 0x3a42f33af429, - 0x6338a371c0ed, - 0xf124c2578ad0, - 0x32ac3b90ac13, - 0x4ad1e273eaf1, - 0xe2c42591368a, - 0x2a3c347a1200, - 0x16f3d5ab1139, - 0x937a4fff3011, - 0x35c3d2caee88, - 0x693143f10368, - 0xa3f97428dd01, - 0x63f17a449af0, - 0xc4652c54261c, - 0xd49e2826664f, - 0x3df14c8000a1, -}; +#define BIP_CARD_ID_SECTOR_NUMBER (0) +#define BIP_BALANCE_SECTOR_NUMBER (8) +#define BIP_TRIP_TIME_WINDOW_SECTOR_NUMBER (5) +#define BIP_LAST_TOP_UPS_SECTOR_NUMBER (10) +#define BIP_TRIPS_INFO_SECTOR_NUMBER (11) -static const uint64_t bip_keys_b[] = { - 0x1fc235ac1309, - 0x243f160918d1, - 0x9afc42372af1, - 0x682d401abb09, - 0x067db45454a9, - 0x15fc4c7613fe, - 0x68d30288910a, - 0xf59a36a2546d, - 0x64e3c10394c2, - 0xb736412614af, - 0x324f5df65310, - 0x643fb6de2217, - 0x82f435dedf01, - 0x0263de1278f3, - 0x51284c3686a6, - 0x6a470d54127c, +typedef struct { + DateTime datetime; + uint16_t amount; +} BipTransaction; + +typedef struct { + uint64_t a; + uint64_t b; +} MfClassicKeyPair; + +static const MfClassicKeyPair bip_1k_keys[] = { + {.a = 0x3a42f33af429, .b = 0x1fc235ac1309}, + {.a = 0x6338a371c0ed, .b = 0x243f160918d1}, + {.a = 0xf124c2578ad0, .b = 0x9afc42372af1}, + {.a = 0x32ac3b90ac13, .b = 0x682d401abb09}, + {.a = 0x4ad1e273eaf1, .b = 0x067db45454a9}, + {.a = 0xe2c42591368a, .b = 0x15fc4c7613fe}, + {.a = 0x2a3c347a1200, .b = 0x68d30288910a}, + {.a = 0x16f3d5ab1139, .b = 0xf59a36a2546d}, + {.a = 0x937a4fff3011, .b = 0x64e3c10394c2}, + {.a = 0x35c3d2caee88, .b = 0xb736412614af}, + {.a = 0x693143f10368, .b = 0x324f5df65310}, + {.a = 0xa3f97428dd01, .b = 0x643fb6de2217}, + {.a = 0x63f17a449af0, .b = 0x82f435dedf01}, + {.a = 0xc4652c54261c, .b = 0x0263de1278f3}, + {.a = 0xd49e2826664f, .b = 0x51284c3686a6}, + {.a = 0x3df14c8000a1, .b = 0x6a470d54127c}, }; -bool bip_verify(Nfc* nfc) { - bool verified = true; +static void bip_parse_datetime(const MfClassicBlock* block, DateTime* parsed_data) { + furi_assert(block); + furi_assert(parsed_data); + + parsed_data->day = (((block->data[1] << 8) + block->data[0]) >> 6) & 0x1f; + parsed_data->month = (((block->data[1] << 8) + block->data[0]) >> 11) & 0xf; + parsed_data->year = 2000 + ((((block->data[2] << 8) + block->data[1]) >> 7) & 0x1f); + parsed_data->hour = (((block->data[3] << 8) + block->data[2]) >> 4) & 0x1f; + parsed_data->minute = (((block->data[3] << 8) + block->data[2]) >> 9) & 0x3f; + parsed_data->second = (((block->data[4] << 8) + block->data[3]) >> 7) & 0x3f; +} + +static void bip_print_datetime(const DateTime* datetime, FuriString* str) { + furi_assert(datetime); + furi_assert(str); - const uint8_t verify_sector = 0; - uint8_t block_num = mf_classic_get_first_block_num_of_sector(verify_sector); - FURI_LOG_D(TAG, "Verifying sector %u", verify_sector); + LocaleDateFormat date_format = locale_get_date_format(); + const char* separator = (date_format == LocaleDateFormatDMY) ? "." : "/"; - MfClassicKey key_a_0 = {}; - bit_lib_num_to_bytes_be(bip_keys_a[0], COUNT_OF(key_a_0.data), key_a_0.data); + FuriString* date_str = furi_string_alloc(); + locale_format_date(date_str, datetime, date_format, separator); - MfClassicAuthContext auth_ctx = {}; - MfClassicError error = - mf_classic_poller_sync_auth(nfc, block_num, &key_a_0, MfClassicKeyTypeA, &auth_ctx); + FuriString* time_str = furi_string_alloc(); + locale_format_time(time_str, datetime, locale_get_time_format(), true); - if(error == MfClassicErrorNotPresent) { - FURI_LOG_D(TAG, "Failed to read block %u: %d", block_num, error); - verified = false; + furi_string_cat_printf( + str, "%s %s", furi_string_get_cstr(date_str), furi_string_get_cstr(time_str)); + + furi_string_free(date_str); + furi_string_free(time_str); +} + +static int datetime_cmp(const DateTime* dt_1, const DateTime* dt_2) { + furi_assert(dt_1); + furi_assert(dt_2); + + if(dt_1->year != dt_2->year) { + return dt_1->year - dt_2->year; + } + if(dt_1->month != dt_2->month) { + return dt_1->month - dt_2->month; + } + if(dt_1->day != dt_2->day) { + return dt_1->day - dt_2->day; } + if(dt_1->hour != dt_2->hour) { + return dt_1->hour - dt_2->hour; + } + if(dt_1->minute != dt_2->minute) { + return dt_1->minute - dt_2->minute; + } + if(dt_1->second != dt_2->second) { + return dt_1->second - dt_2->second; + } + return 0; +} + +static bool is_bip_block_empty(const MfClassicBlock* block) { + furi_assert(block); + // check if all but last byte are zero (last is checksum) + for(size_t i = 0; i < sizeof(block->data) - 1; i++) { + if(block->data[i] != 0) { + return false; + } + } + return true; +} + +bool bip_verify(Nfc* nfc) { + bool verified = false; + + do { + const uint8_t verify_sector = 0; + uint8_t block_num = mf_classic_get_first_block_num_of_sector(verify_sector); + FURI_LOG_D(TAG, "Verifying sector %u", verify_sector); + + MfClassicKey key = {}; + bit_lib_num_to_bytes_be(bip_1k_keys[0].a, COUNT_OF(key.data), key.data); + + MfClassicAuthContext auth_ctx = {}; + MfClassicError error = + mf_classic_poller_sync_auth(nfc, block_num, &key, MfClassicKeyTypeA, &auth_ctx); + + if(error == MfClassicErrorNone) { + FURI_LOG_D(TAG, "Failed to read block %u: %d", block_num, error); + break; + } + + verified = true; + } while(false); return verified; } @@ -79,31 +148,33 @@ static bool bip_read(Nfc* nfc, NfcDevice* device) { nfc_device_copy_data(device, NfcProtocolMfClassic, data); do { - MfClassicType type = MfClassicType1k; + MfClassicType type = MfClassicTypeMini; MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type); - if(error == MfClassicErrorNotPresent) { - FURI_LOG_W(TAG, "Card not MIFARE Classic 1k"); - break; - } + if(error != MfClassicErrorNone) break; data->type = type; - MfClassicDeviceKeys keys = {}; + if(type != MfClassicType1k) break; + + MfClassicDeviceKeys keys = { + .key_a_mask = 0, + .key_b_mask = 0, + }; for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { - bit_lib_num_to_bytes_be(bip_keys_a[i], sizeof(MfClassicKey), keys.key_a[i].data); + bit_lib_num_to_bytes_be(bip_1k_keys[i].a, sizeof(MfClassicKey), keys.key_a[i].data); FURI_BIT_SET(keys.key_a_mask, i); - bit_lib_num_to_bytes_be(bip_keys_b[i], sizeof(MfClassicKey), keys.key_b[i].data); + bit_lib_num_to_bytes_be(bip_1k_keys[i].b, sizeof(MfClassicKey), keys.key_b[i].data); FURI_BIT_SET(keys.key_b_mask, i); } error = mf_classic_poller_sync_read(nfc, &keys, data); if(error == MfClassicErrorNotPresent) { - FURI_LOG_W(TAG, "Failed to read data. Bad keys?"); + FURI_LOG_W(TAG, "Failed to read data"); break; } nfc_device_set_data(device, NfcProtocolMfClassic, data); - is_read = true; + is_read = (error == MfClassicErrorNone); } while(false); mf_classic_free(data); @@ -111,183 +182,91 @@ static bool bip_read(Nfc* nfc, NfcDevice* device) { return is_read; } -typedef struct { - uint16_t year; - uint8_t month; - uint8_t day; - uint8_t hour; - uint8_t minute; - uint8_t second; -} BipTimestamp; - -static void parse_bip_timestamp(const MfClassicBlock* block, BipTimestamp* timestamp) { - furi_assert(block); - furi_assert(timestamp); - - timestamp->day = (((block->data[1] << 8) + block->data[0]) >> 6) & 0x1f; - timestamp->month = (((block->data[1] << 8) + block->data[0]) >> 11) & 0xf; - timestamp->year = 2000 + ((((block->data[2] << 8) + block->data[1]) >> 7) & 0x1f); - timestamp->hour = (((block->data[3] << 8) + block->data[2]) >> 4) & 0x1f; - timestamp->minute = (((block->data[3] << 8) + block->data[2]) >> 9) & 0x3f; - timestamp->second = (((block->data[4] << 8) + block->data[3]) >> 7) & 0x3f; -} - -static int compare_bip_timestamp(const BipTimestamp* t1, const BipTimestamp* t2) { - furi_assert(t1); - furi_assert(t2); - if(t1->year != t2->year) { - return t1->year - t2->year; - } - if(t1->month != t2->month) { - return t1->month - t2->month; - } - if(t1->day != t2->day) { - return t1->day - t2->day; - } - if(t1->hour != t2->hour) { - return t1->hour - t2->hour; - } - if(t1->minute != t2->minute) { - return t1->minute - t2->minute; - } - if(t1->second != t2->second) { - return t1->second - t2->second; - } - return 0; -} - -static void print_bip_timestamp(const BipTimestamp* timestamp, FuriString* str) { - furi_assert(timestamp); - furi_assert(str); - furi_string_cat_printf( - str, - "%04u-%02u-%02u %02u:%02u:%02u", - timestamp->year, - timestamp->month, - timestamp->day, - timestamp->hour, - timestamp->minute, - timestamp->second); -} - -static bool is_bip_block_empty(const MfClassicBlock* block) { - furi_assert(block); - // check if all but last byte are zero (last is checksum) - for(size_t i = 0; i < sizeof(block->data) - 1; i++) { - if(block->data[i] != 0) { - return false; - } - } - return true; -} - -static void parse_uint16_le(const uint8_t* data, uint16_t* value) { - furi_assert(data); - furi_assert(value); - - *value = (data[0]) | (data[1] << 8); -} - -static void parse_uint32_le(const uint8_t* data, uint32_t* value) { - furi_assert(data); - furi_assert(value); - - *value = (data[0]) | (data[1] << 8) | (data[2] << 16) | (data[3] << 24); -} - -static void parse_uint16_txn_amount(const uint8_t* data, uint16_t* value) { - furi_assert(data); - furi_assert(value); - - parse_uint16_le(data, value); - *value = *value >> 2; -} - -typedef struct { - BipTimestamp timestamp; - uint16_t amount; -} BipTransaction; - static bool bip_parse(const NfcDevice* device, FuriString* parsed_data) { furi_assert(device); furi_assert(parsed_data); - bool parsed = true; - struct { uint32_t card_id; uint16_t balance; uint16_t flags; - BipTimestamp trip_time_window; + DateTime trip_time_window; BipTransaction top_ups[3]; BipTransaction charges[3]; - } bip_data = { - .card_id = 0, - .balance = 0, - .flags = 0, - .trip_time_window = {0, 0, 0, 0, 0, 0}, - .top_ups = - { - {{0, 0, 0, 0, 0, 0}, 0}, - {{0, 0, 0, 0, 0, 0}, 0}, - {{0, 0, 0, 0, 0, 0}, 0}, - }, - .charges = - { - {{0, 0, 0, 0, 0, 0}, 0}, - {{0, 0, 0, 0, 0, 0}, 0}, - {{0, 0, 0, 0, 0, 0}, 0}, - }, - }; + } bip_data = {0}; const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic); + bool parsed = false; + do { - // verify first sector keys + // verify sector 0 key A MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, 0); + + if(data->type != MfClassicType1k) break; + uint64_t key = bit_lib_bytes_to_num_be(sec_tr->key_a.data, 6); - if(key != bip_keys_a[0]) { - parsed = false; + if(key != bip_1k_keys[0].a) { break; } + + // verify sector 0 key B key = bit_lib_bytes_to_num_be(sec_tr->key_b.data, 6); - if(key != bip_keys_b[0]) { - parsed = false; + if(key != bip_1k_keys[0].b) { break; } // Get Card ID, little-endian 4 bytes at sector 0 block 1, bytes 4-7 - parse_uint32_le(&data->block[SECTOR_BLOCK_OFFSET(0, 1)].data[4], &bip_data.card_id); + const uint8_t card_id_start_block_num = + mf_classic_get_first_block_num_of_sector(BIP_CARD_ID_SECTOR_NUMBER); + const uint8_t* block_start_ptr = &data->block[card_id_start_block_num + 1].data[0]; + + bip_data.card_id = bit_lib_bytes_to_num_le(block_start_ptr + 4, 4); // Get balance, little-endian 2 bytes at sector 8 block 1, bytes 0-1 - parse_uint16_le(&data->block[SECTOR_BLOCK_OFFSET(8, 1)].data[0], &bip_data.balance); + const uint8_t balance_start_block_num = + mf_classic_get_first_block_num_of_sector(BIP_BALANCE_SECTOR_NUMBER); + block_start_ptr = &data->block[balance_start_block_num + 1].data[0]; + + bip_data.balance = bit_lib_bytes_to_num_le(block_start_ptr, 2); // Get balance flags (negative balance, etc.), little-endian 2 bytes at sector 8 block 1, bytes 2-3 - parse_uint16_le(&data->block[SECTOR_BLOCK_OFFSET(8, 1)].data[2], &bip_data.flags); + bip_data.flags = bit_lib_bytes_to_num_le(block_start_ptr + 2, 2); // Get trip time window, proprietary format, at sector 5 block 1, bytes 0-7 - parse_bip_timestamp(&data->block[SECTOR_BLOCK_OFFSET(5, 1)], &bip_data.trip_time_window); + const uint8_t trip_time_window_start_block_num = + mf_classic_get_first_block_num_of_sector(BIP_TRIP_TIME_WINDOW_SECTOR_NUMBER); + const MfClassicBlock* trip_window_block_ptr = + &data->block[trip_time_window_start_block_num + 1]; + + bip_parse_datetime(trip_window_block_ptr, &bip_data.trip_time_window); // Last 3 top-ups: sector 10, ring-buffer of 3 blocks, timestamp in bytes 0-7, amount in bytes 9-10 + const uint8_t top_ups_start_block_num = + mf_classic_get_first_block_num_of_sector(BIP_LAST_TOP_UPS_SECTOR_NUMBER); for(size_t i = 0; i < 3; i++) { - if(is_bip_block_empty(&data->block[SECTOR_BLOCK_OFFSET(10, i)])) { - continue; - } + const MfClassicBlock* block = &data->block[top_ups_start_block_num + i]; + + if(is_bip_block_empty(block)) continue; + BipTransaction* top_up = &bip_data.top_ups[i]; - parse_bip_timestamp(&data->block[SECTOR_BLOCK_OFFSET(10, i)], &top_up->timestamp); - parse_uint16_txn_amount( - &data->block[SECTOR_BLOCK_OFFSET(10, i)].data[9], &top_up->amount); + bip_parse_datetime(block, &top_up->datetime); + + top_up->amount = bit_lib_bytes_to_num_le(&block->data[9], 2) >> 2; } // Last 3 charges (i.e. trips), sector 11, ring-buffer of 3 blocks, timestamp in bytes 0-7, amount in bytes 10-11 + const uint8_t trips_start_block_num = + mf_classic_get_first_block_num_of_sector(BIP_TRIPS_INFO_SECTOR_NUMBER); for(size_t i = 0; i < 3; i++) { - if(is_bip_block_empty(&data->block[SECTOR_BLOCK_OFFSET(11, i)])) { - continue; - } + const MfClassicBlock* block = &data->block[trips_start_block_num + i]; + + if(is_bip_block_empty(block)) continue; + BipTransaction* charge = &bip_data.charges[i]; - parse_bip_timestamp(&data->block[SECTOR_BLOCK_OFFSET(11, i)], &charge->timestamp); - parse_uint16_txn_amount( - &data->block[SECTOR_BLOCK_OFFSET(11, i)].data[10], &charge->amount); + bip_parse_datetime(block, &charge->datetime); + + charge->amount = bit_lib_bytes_to_num_le(&block->data[10], 2) >> 2; } // All data is now parsed and stored in bip_data, now print it @@ -303,14 +282,14 @@ static bool bip_parse(const NfcDevice* device, FuriString* parsed_data) { bip_data.balance, bip_data.flags); - print_bip_timestamp(&bip_data.trip_time_window, parsed_data); + bip_print_datetime(&bip_data.trip_time_window, parsed_data); // Find newest top-up size_t newest_top_up = 0; for(size_t i = 1; i < 3; i++) { - const BipTimestamp* newest = &bip_data.top_ups[newest_top_up].timestamp; - const BipTimestamp* current = &bip_data.top_ups[i].timestamp; - if(compare_bip_timestamp(current, newest) > 0) { + const DateTime* newest = &bip_data.top_ups[newest_top_up].datetime; + const DateTime* current = &bip_data.top_ups[i].datetime; + if(datetime_cmp(current, newest) > 0) { newest_top_up = i; } } @@ -320,15 +299,15 @@ static bool bip_parse(const NfcDevice* device, FuriString* parsed_data) { for(size_t i = 0; i < 3; i++) { const BipTransaction* top_up = &bip_data.top_ups[(3u + newest_top_up - i) % 3]; furi_string_cat_printf(parsed_data, "\n+$%d\n @", top_up->amount); - print_bip_timestamp(&top_up->timestamp, parsed_data); + bip_print_datetime(&top_up->datetime, parsed_data); } // Find newest charge size_t newest_charge = 0; for(size_t i = 1; i < 3; i++) { - const BipTimestamp* newest = &bip_data.charges[newest_charge].timestamp; - const BipTimestamp* current = &bip_data.charges[i].timestamp; - if(compare_bip_timestamp(current, newest) > 0) { + const DateTime* newest = &bip_data.charges[newest_charge].datetime; + const DateTime* current = &bip_data.charges[i].datetime; + if(datetime_cmp(current, newest) > 0) { newest_charge = i; } } @@ -338,7 +317,7 @@ static bool bip_parse(const NfcDevice* device, FuriString* parsed_data) { for(size_t i = 0; i < 3; i++) { const BipTransaction* charge = &bip_data.charges[(3u + newest_charge - i) % 3]; furi_string_cat_printf(parsed_data, "\n-$%d\n @", charge->amount); - print_bip_timestamp(&charge->timestamp, parsed_data); + bip_print_datetime(&charge->datetime, parsed_data); } parsed = true; diff --git a/applications/main/nfc/plugins/supported_cards/troika.c b/applications/main/nfc/plugins/supported_cards/troika.c index 50806e137d..758f824b70 100644 --- a/applications/main/nfc/plugins/supported_cards/troika.c +++ b/applications/main/nfc/plugins/supported_cards/troika.c @@ -73,14 +73,14 @@ static const MfClassicKeyPair troika_4k_keys[] = { {.a = 0x7A38E3511A38, .b = 0xAB16584C972A}, //30 {.a = 0x7545DF809202, .b = 0xECF751084A80}, //31 {.a = 0x5125974CD391, .b = 0xD3EAFB5DF46D}, //32 - {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, //33 - {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, //34 - {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, //35 - {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, //36 - {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, //37 - {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, //38 - {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, //39 - {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, //40 + {.a = 0x7A86AA203788, .b = 0xE41242278CA2}, //33 + {.a = 0xAFCEF64C9913, .b = 0x9DB96DCA4324}, //34 + {.a = 0x04EAA462F70B, .b = 0xAC17B93E2FAE}, //35 + {.a = 0xE734C210F27E, .b = 0x29BA8C3E9FDA}, //36 + {.a = 0xD5524F591EED, .b = 0x5DAF42861B4D}, //37 + {.a = 0xE4821A377B75, .b = 0xE8709E486465}, //38 + {.a = 0x518DC6EEA089, .b = 0x97C64AC98CA4}, //39 + {.a = 0xBB52F8CCE07F, .b = 0x6B6119752C70}, //40 }; static bool troika_get_card_config(TroikaCardConfig* config, MfClassicType type) { diff --git a/applications/services/expansion/expansion.c b/applications/services/expansion/expansion.c index 5b834b48d3..9c64d0b5a1 100644 --- a/applications/services/expansion/expansion.c +++ b/applications/services/expansion/expansion.c @@ -18,6 +18,7 @@ typedef enum { ExpansionStateDisabled, ExpansionStateEnabled, ExpansionStateRunning, + ExpansionStateConnectionEstablished, } ExpansionState; typedef enum { @@ -26,10 +27,15 @@ typedef enum { ExpansionMessageTypeSetListenSerial, ExpansionMessageTypeModuleConnected, ExpansionMessageTypeModuleDisconnected, + ExpansionMessageTypeConnectionEstablished, + ExpansionMessageTypeIsConnected, } ExpansionMessageType; typedef union { - FuriHalSerialId serial_id; + union { + FuriHalSerialId serial_id; + bool* is_connected; + }; } ExpansionMessageData; typedef struct { @@ -68,13 +74,21 @@ static void expansion_detect_callback(void* context) { UNUSED(status); } -static void expansion_worker_callback(void* context) { +static void expansion_worker_callback(void* context, ExpansionWorkerCallbackReason reason) { furi_assert(context); Expansion* instance = context; - ExpansionMessage message = { - .type = ExpansionMessageTypeModuleDisconnected, - .api_lock = NULL, // Not locking the API here to avoid a deadlock + ExpansionMessage message; + switch(reason) { + case ExpansionWorkerCallbackReasonExit: + message.type = ExpansionMessageTypeModuleDisconnected; + message.api_lock = NULL; // Not locking the API here to avoid a deadlock + break; + + case ExpansionWorkerCallbackReasonConnected: + message.type = ExpansionMessageTypeConnectionEstablished; + message.api_lock = api_lock_alloc_locked(); + break; }; const FuriStatus status = furi_message_queue_put(instance->queue, &message, FuriWaitForever); @@ -105,7 +119,9 @@ static void if(instance->state == ExpansionStateDisabled) { return; - } else if(instance->state == ExpansionStateRunning) { + } else if( + instance->state == ExpansionStateRunning || + instance->state == ExpansionStateConnectionEstablished) { expansion_worker_stop(instance->worker); expansion_worker_free(instance->worker); } else { @@ -122,7 +138,8 @@ static void expansion_control_handler_set_listen_serial( const ExpansionMessageData* data) { furi_check(data->serial_id < FuriHalSerialIdMax); - if(instance->state == ExpansionStateRunning) { + if(instance->state == ExpansionStateRunning || + instance->state == ExpansionStateConnectionEstablished) { expansion_worker_stop(instance->worker); expansion_worker_free(instance->worker); @@ -160,7 +177,8 @@ static void expansion_control_handler_module_disconnected( Expansion* instance, const ExpansionMessageData* data) { UNUSED(data); - if(instance->state != ExpansionStateRunning) { + if(instance->state != ExpansionStateRunning && + instance->state != ExpansionStateConnectionEstablished) { return; } @@ -170,6 +188,23 @@ static void expansion_control_handler_module_disconnected( instance->serial_id, expansion_detect_callback, instance); } +static void expansion_control_handler_connection_established( + Expansion* instance, + const ExpansionMessageData* data) { + UNUSED(data); + if(instance->state != ExpansionStateRunning && + instance->state != ExpansionStateConnectionEstablished) { + return; + } + + instance->state = ExpansionStateConnectionEstablished; +} + +static void + expansion_control_handler_is_connected(Expansion* instance, const ExpansionMessageData* data) { + *data->is_connected = instance->state == ExpansionStateConnectionEstablished; +} + typedef void (*ExpansionControlHandler)(Expansion*, const ExpansionMessageData*); static const ExpansionControlHandler expansion_control_handlers[] = { @@ -178,6 +213,8 @@ static const ExpansionControlHandler expansion_control_handlers[] = { [ExpansionMessageTypeSetListenSerial] = expansion_control_handler_set_listen_serial, [ExpansionMessageTypeModuleConnected] = expansion_control_handler_module_connected, [ExpansionMessageTypeModuleDisconnected] = expansion_control_handler_module_disconnected, + [ExpansionMessageTypeConnectionEstablished] = expansion_control_handler_connection_established, + [ExpansionMessageTypeIsConnected] = expansion_control_handler_is_connected, }; static int32_t expansion_control(void* context) { @@ -249,6 +286,22 @@ void expansion_disable(Expansion* instance) { api_lock_wait_unlock_and_free(message.api_lock); } +bool expansion_is_connected(Expansion* instance) { + furi_check(instance); + bool is_connected; + + ExpansionMessage message = { + .type = ExpansionMessageTypeIsConnected, + .data.is_connected = &is_connected, + .api_lock = api_lock_alloc_locked(), + }; + + furi_message_queue_put(instance->queue, &message, FuriWaitForever); + api_lock_wait_unlock_and_free(message.api_lock); + + return is_connected; +} + void expansion_set_listen_serial(Expansion* instance, FuriHalSerialId serial_id) { furi_check(instance); furi_check(serial_id < FuriHalSerialIdMax); diff --git a/applications/services/expansion/expansion.h b/applications/services/expansion/expansion.h index e169b3c15d..1b0879b1ec 100644 --- a/applications/services/expansion/expansion.h +++ b/applications/services/expansion/expansion.h @@ -50,6 +50,15 @@ void expansion_enable(Expansion* instance); */ void expansion_disable(Expansion* instance); +/** + * @brief Check if an expansion module is connected. + * + * @param[in,out] instance pointer to the Expansion instance. + * + * @returns true if the module is connected and initialized, false otherwise. + */ +bool expansion_is_connected(Expansion* instance); + /** * @brief Enable support for expansion modules on designated serial port. * diff --git a/applications/services/expansion/expansion_worker.c b/applications/services/expansion/expansion_worker.c index fd92063d25..4047212eb8 100644 --- a/applications/services/expansion/expansion_worker.c +++ b/applications/services/expansion/expansion_worker.c @@ -223,6 +223,7 @@ static bool expansion_worker_handle_state_handshake( if(furi_hal_serial_is_baud_rate_supported(instance->serial_handle, baud_rate)) { instance->state = ExpansionWorkerStateConnected; + instance->callback(instance->cb_context, ExpansionWorkerCallbackReasonConnected); // Send response at previous baud rate if(!expansion_worker_send_status_response(instance, ExpansionFrameErrorNone)) break; furi_hal_serial_set_br(instance->serial_handle, baud_rate); @@ -351,7 +352,7 @@ static int32_t expansion_worker(void* context) { // Do not invoke worker callback on user-requested exit if((instance->exit_reason != ExpansionWorkerExitReasonUser) && (instance->callback != NULL)) { - instance->callback(instance->cb_context); + instance->callback(instance->cb_context, ExpansionWorkerCallbackReasonExit); } return 0; diff --git a/applications/services/expansion/expansion_worker.h b/applications/services/expansion/expansion_worker.h index 761f79c1d9..faab2887f3 100644 --- a/applications/services/expansion/expansion_worker.h +++ b/applications/services/expansion/expansion_worker.h @@ -17,14 +17,20 @@ */ typedef struct ExpansionWorker ExpansionWorker; +typedef enum { + ExpansionWorkerCallbackReasonExit, + ExpansionWorkerCallbackReasonConnected, +} ExpansionWorkerCallbackReason; + /** * @brief Worker callback type. * * @see expansion_worker_set_callback() * * @param[in,out] context pointer to a user-defined object. + * @param[in] reason reason for the callback. */ -typedef void (*ExpansionWorkerCallback)(void* context); +typedef void (*ExpansionWorkerCallback)(void* context, ExpansionWorkerCallbackReason reason); /** * @brief Create an expansion worker instance. diff --git a/applications/services/gui/canvas.h b/applications/services/gui/canvas.h index e4c8d4ea8f..5c31107454 100644 --- a/applications/services/gui/canvas.h +++ b/applications/services/gui/canvas.h @@ -248,7 +248,6 @@ void canvas_draw_bitmap( * @param x x coordinate * @param y y coordinate * @param icon Icon instance - * @param flip IconFlip * @param rotation IconRotation */ void canvas_draw_icon_ex( diff --git a/applications/services/gui/canvas_i.h b/applications/services/gui/canvas_i.h index 0982830c98..5d2a38ddf7 100644 --- a/applications/services/gui/canvas_i.h +++ b/applications/services/gui/canvas_i.h @@ -107,7 +107,7 @@ CanvasOrientation canvas_get_orientation(const Canvas* canvas); /** Draw a u8g2 bitmap * - * @param canvas Canvas instance + * @param u8g2 u8g2 instance * @param x x coordinate * @param y y coordinate * @param width width diff --git a/applications/services/gui/elements.h b/applications/services/gui/elements.h index f565b0ad86..56c6807d0c 100644 --- a/applications/services/gui/elements.h +++ b/applications/services/gui/elements.h @@ -205,8 +205,7 @@ void elements_bubble(Canvas* canvas, uint8_t x, uint8_t y, uint8_t width, uint8_ * @param canvas Canvas instance * @param x left x coordinates * @param y top y coordinate - * @param width bubble width - * @param height bubble height + * @param text text to display * @param horizontal horizontal aligning * @param vertical aligning */ diff --git a/applications/services/gui/modules/dialog_ex.h b/applications/services/gui/modules/dialog_ex.h index 26a4653545..91424f78c0 100644 --- a/applications/services/gui/modules/dialog_ex.h +++ b/applications/services/gui/modules/dialog_ex.h @@ -114,7 +114,6 @@ void dialog_ex_set_text( * @param x x position * @param y y position * @param icon The icon - * @param name icon to be shown */ void dialog_ex_set_icon(DialogEx* dialog_ex, uint8_t x, uint8_t y, const Icon* icon); diff --git a/applications/services/gui/scene_manager.h b/applications/services/gui/scene_manager.h index c349a12cea..54dfa9cd47 100644 --- a/applications/services/gui/scene_manager.h +++ b/applications/services/gui/scene_manager.h @@ -108,7 +108,6 @@ bool scene_manager_handle_back_event(SceneManager* scene_manager); * Calls Scene event handler with Tick event parameter * * @param scene_manager SceneManager instance - * @return true if event was consumed, false otherwise */ void scene_manager_handle_tick_event(SceneManager* scene_manager); diff --git a/applications/services/gui/view.h b/applications/services/gui/view.h index e50f33e172..3c6f77a8fe 100644 --- a/applications/services/gui/view.h +++ b/applications/services/gui/view.h @@ -34,15 +34,15 @@ typedef enum { typedef struct View View; /** View Draw callback - * @param canvas, pointer to canvas - * @param view_model, pointer to context + * @param canvas pointer to canvas + * @param model pointer to model * @warning called from GUI thread */ typedef void (*ViewDrawCallback)(Canvas* canvas, void* model); /** View Input callback - * @param event, pointer to input event data - * @param context, pointer to context + * @param event pointer to input event data + * @param context pointer to context * @return true if event handled, false if event ignored * @warning called from GUI thread */ @@ -57,29 +57,29 @@ typedef bool (*ViewInputCallback)(InputEvent* event, void* context); typedef bool (*ViewAsciiCallback)(AsciiEvent* event, void* context); /** View Custom callback - * @param event, number of custom event - * @param context, pointer to context + * @param event number of custom event + * @param context pointer to context * @return true if event handled, false if event ignored */ typedef bool (*ViewCustomCallback)(uint32_t event, void* context); /** View navigation callback - * @param context, pointer to context + * @param context pointer to context * @return next view id * @warning called from GUI thread */ typedef uint32_t (*ViewNavigationCallback)(void* context); /** View callback - * @param context, pointer to context + * @param context pointer to context * @warning called from GUI thread */ typedef void (*ViewCallback)(void* context); /** View Update Callback Called upon model change, need to be propagated to GUI * throw ViewPort update - * @param view, pointer to view - * @param context, pointer to context + * @param view pointer to view + * @param context pointer to context * @warning called from GUI thread */ typedef void (*ViewUpdateCallback)(View* view, void* context); diff --git a/applications/services/gui/view_stack.h b/applications/services/gui/view_stack.h index f16f5febc3..ebe65ab88f 100644 --- a/applications/services/gui/view_stack.h +++ b/applications/services/gui/view_stack.h @@ -44,7 +44,7 @@ View* view_stack_get_view(ViewStack* view_stack); * Adds View on top of ViewStack. * * @param view_stack instance - * @view view view to add + * @param view view to add */ void view_stack_add_view(ViewStack* view_stack, View* view); @@ -52,7 +52,7 @@ void view_stack_add_view(ViewStack* view_stack, View* view); * If no View to remove found - ignore. * * @param view_stack instance - * @view view view to remove + * @param view view to remove */ void view_stack_remove_view(ViewStack* view_stack, View* view); diff --git a/applications/services/storage/storage.h b/applications/services/storage/storage.h index e278902a36..765904b123 100644 --- a/applications/services/storage/storage.h +++ b/applications/services/storage/storage.h @@ -368,7 +368,7 @@ FS_Error storage_common_merge(Storage* storage, const char* old_path, const char * @brief Create a directory. * * @param storage pointer to a storage API instance. - * @param fs_path pointer to a zero-terminated string containing the directory path. + * @param path pointer to a zero-terminated string containing the directory path. * @return FSE_OK if the directory has been successfully created, any other error code on failure. */ FS_Error storage_common_mkdir(Storage* storage, const char* path); @@ -395,7 +395,6 @@ FS_Error storage_common_fs_info( * * @param storage pointer to a storage API instance. * @param path pointer to a zero-terminated string containing the path in question. - * @return true if the path was successfully resolved, false otherwise. */ void storage_common_resolve_path_and_ensure_app_directory(Storage* storage, FuriString* path); @@ -555,7 +554,8 @@ FS_Error storage_int_backup(Storage* storage, const char* dstname); * @param converter pointer to a filename conversion function (may be NULL). * @return FSE_OK if the storage was successfully restored, any other error code on failure. */ -FS_Error storage_int_restore(Storage* api, const char* dstname, Storage_name_converter converter); +FS_Error + storage_int_restore(Storage* storage, const char* dstname, Storage_name_converter converter); /******************* FatFs Virtual Mount Functions *******************/ diff --git a/applications/services/storage/storage_internal_api.c b/applications/services/storage/storage_internal_api.c index 6d620b9c07..d91c71c0ec 100644 --- a/applications/services/storage/storage_internal_api.c +++ b/applications/services/storage/storage_internal_api.c @@ -2,8 +2,8 @@ #include "storage.h" #include -FS_Error storage_int_backup(Storage* api, const char* dstname) { - TarArchive* archive = tar_archive_alloc(api); +FS_Error storage_int_backup(Storage* storage, const char* dstname) { + TarArchive* archive = tar_archive_alloc(storage); bool success = tar_archive_open(archive, dstname, TAR_OPEN_MODE_WRITE) && tar_archive_add_dir(archive, STORAGE_INT_PATH_PREFIX, "") && tar_archive_finalize(archive); @@ -11,8 +11,9 @@ FS_Error storage_int_backup(Storage* api, const char* dstname) { return success ? FSE_OK : FSE_INTERNAL; } -FS_Error storage_int_restore(Storage* api, const char* srcname, Storage_name_converter converter) { - TarArchive* archive = tar_archive_alloc(api); +FS_Error + storage_int_restore(Storage* storage, const char* srcname, Storage_name_converter converter) { + TarArchive* archive = tar_archive_alloc(storage); bool success = tar_archive_open(archive, srcname, TAR_OPEN_MODE_READ) && tar_archive_unpack_to(archive, STORAGE_INT_PATH_PREFIX, converter); tar_archive_free(archive); diff --git a/applications/system/js_app/application.fam b/applications/system/js_app/application.fam index 16963317ef..1da65ee1d0 100644 --- a/applications/system/js_app/application.fam +++ b/applications/system/js_app/application.fam @@ -62,3 +62,42 @@ App( requires=["js_app"], sources=["modules/js_usbdisk/*.c"], ) + +App( + appid="js_submenu", + apptype=FlipperAppType.PLUGIN, + entry_point="js_submenu_ep", + requires=["js_app"], + sources=["modules/js_submenu.c"], +) + +App( + appid="js_blebeacon", + apptype=FlipperAppType.PLUGIN, + entry_point="js_blebeacon_ep", + requires=["js_app"], + sources=["modules/js_blebeacon.c"], +) + +App( + appid="js_math", + apptype=FlipperAppType.PLUGIN, + entry_point="js_math_ep", + requires=["js_app"], + sources=["modules/js_math.c"], +) + +App( + appid="js_keyboard", + apptype=FlipperAppType.PLUGIN, + entry_point="js_keyboard_ep", + requires=["js_app"], + sources=["modules/js_keyboard.c"], +) +App( + appid="js_subghz", + apptype=FlipperAppType.PLUGIN, + entry_point="js_subghz_ep", + requires=["js_app"], + sources=["modules/js_subghz/*.c"], +) diff --git a/applications/system/js_app/examples/apps/Scripts/blebeacon.js b/applications/system/js_app/examples/apps/Scripts/blebeacon.js new file mode 100644 index 0000000000..53983a7455 --- /dev/null +++ b/applications/system/js_app/examples/apps/Scripts/blebeacon.js @@ -0,0 +1,59 @@ +let blebeacon = require("blebeacon"); + +// Stop if previous background beacon is active +if (blebeacon.isActive()) { + blebeacon.stop(); +} + +// Make sure it resets at script exit, true will keep advertising in background +// This is false by default, can be omitted +blebeacon.keepAlive(false); + + +let math = require("math"); + +let currentIndex = 0; +let watchValues = [ + 0x1A, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0A, 0x0B, 0x0C, 0x11, 0x12, 0x13, 0x14, 0x15, + 0x16, 0x17, 0x18, 0xE4, 0xE5, 0x1B, 0x1C, 0x1D, 0x1E, + 0x20, 0xEC, 0xEF +]; + +function generateRandomMac() { + let mac = []; + for (let i = 0; i < 6; i++) { + mac.push(math.floor(math.random() * 256)); + } + return Uint8Array(mac); +} + +function sendRandomModelAdvertisement() { + let model = watchValues[currentIndex]; + + let packet = [ + 14, 0xFF, 0x75, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x01, 0xFF, 0x00, 0x00, 0x43, + model + ]; + + let intervalMs = 50; + + // Power level, min interval and max interval are optional + blebeacon.setConfig(generateRandomMac(), 0x1F, intervalMs, intervalMs * 3); + + blebeacon.setData(Uint8Array(packet)); + + blebeacon.start(); + + print("Sent data for model ID " + to_string(model)); + + currentIndex = (currentIndex + 1) % watchValues.length; + + delay(intervalMs); + + blebeacon.stop(); +} + +while (true) { + sendRandomModelAdvertisement(); +} \ No newline at end of file diff --git a/applications/system/js_app/examples/apps/Scripts/keyboard.js b/applications/system/js_app/examples/apps/Scripts/keyboard.js new file mode 100644 index 0000000000..a34607c299 --- /dev/null +++ b/applications/system/js_app/examples/apps/Scripts/keyboard.js @@ -0,0 +1,19 @@ +let keyboard = require("keyboard"); + +keyboard.setHeader("Example Text Input"); + +// Default text is optional +let text = keyboard.text(100, "Default text", true); +print("Got text:", text); + +keyboard.setHeader("Example Byte Input"); + +// Default data is optional +let data = keyboard.byte(6, Uint8Array([1, 2, 3, 4, 5, 6])); +data = Uint8Array(data); +let result = "0x"; +for (let i = 0; i < data.byteLength; i++) { + if (data[i] < 0x10) result += "0"; + result += to_hex_string(data[i]); +} +print("Got data:", result); \ No newline at end of file diff --git a/applications/system/js_app/examples/apps/Scripts/math.js b/applications/system/js_app/examples/apps/Scripts/math.js new file mode 100644 index 0000000000..49212f904a --- /dev/null +++ b/applications/system/js_app/examples/apps/Scripts/math.js @@ -0,0 +1,47 @@ +let math = require("math"); + +let absResult = math.abs(-5); +let acosResult = math.acos(0.5); +let acoshResult = math.acosh(2); +let asinResult = math.asin(0.5); +let asinhResult = math.asinh(2); +let atanResult = math.atan(1); +let atan2Result = math.atan2(1, 1); +let atanhResult = math.atanh(0.5); +let cbrtResult = math.cbrt(27); +let ceilResult = math.ceil(5.3); +let clz32Result = math.clz32(1); +let cosResult = math.cos(math.PI); +let expResult = math.exp(1); +let floorResult = math.floor(5.7); +let maxResult = math.max(3, 5); +let minResult = math.min(3, 5); +let powResult = math.pow(2, 3); +let randomResult = math.random(); +let signResult = math.sign(-5); +let sinResult = math.sin(math.PI / 2); +let sqrtResult = math.sqrt(25); +let truncResult = math.trunc(5.7); + +print("math.abs(-5):", absResult); +print("math.acos(0.5):", acosResult); +print("math.acosh(2):", acoshResult); +print("math.asin(0.5):", asinResult); +print("math.asinh(2):", asinhResult); +print("math.atan(1):", atanResult); +print("math.atan2(1, 1):", atan2Result); +print("math.atanh(0.5):", atanhResult); +print("math.cbrt(27):", cbrtResult); +print("math.ceil(5.3):", ceilResult); +print("math.clz32(1):", clz32Result); +print("math.cos(math.PI):", cosResult); +print("math.exp(1):", expResult); +print("math.floor(5.7):", floorResult); +print("math.max(3, 5):", maxResult); +print("math.min(3, 5):", minResult); +print("math.pow(2, 3):", powResult); +print("math.random():", randomResult); +print("math.sign(-5):", signResult); +print("math.sin(math.PI/2):", sinResult); +print("math.sqrt(25):", sqrtResult); +print("math.trunc(5.7):", truncResult); \ No newline at end of file diff --git a/applications/system/js_app/examples/apps/Scripts/subghz.js b/applications/system/js_app/examples/apps/Scripts/subghz.js new file mode 100644 index 0000000000..39dadf0709 --- /dev/null +++ b/applications/system/js_app/examples/apps/Scripts/subghz.js @@ -0,0 +1,37 @@ +let subghz = require("subghz"); +subghz.setup(); + +function printRXline() { + if (subghz.getState() !== "RX") { + subghz.setRx(); // to RX + } + + let rssi = subghz.getRssi(); + let freq = subghz.getFrequency(); + let ext = subghz.isExternal(); + + print("rssi: ", rssi, "dBm", "@", freq, "MHz", "ext: ", ext); +} + +function changeFrequency(freq) { + if (subghz.getState() !== "IDLE") { + subghz.setIdle(); // need to be idle to change frequency + } + subghz.setFrequency(freq); +} + +subghz.setIdle(); +print(subghz.getState()); // "IDLE" +subghz.setRx(); +print(subghz.getState()); // "RX" + +changeFrequency(433920000); +printRXline(); +delay(1000); + +let result = subghz.transmitFile("/ext/subghz/0.sub"); +print(result ? "Send success" : "Send failed"); +delay(1000); + +changeFrequency(315000000); +printRXline(); \ No newline at end of file diff --git a/applications/system/js_app/examples/apps/Scripts/submenu.js b/applications/system/js_app/examples/apps/Scripts/submenu.js new file mode 100644 index 0000000000..6744ca4526 --- /dev/null +++ b/applications/system/js_app/examples/apps/Scripts/submenu.js @@ -0,0 +1,11 @@ +let submenu = require("submenu"); + +submenu.addItem("Item 1", 0); +submenu.addItem("Item 2", 1); +submenu.addItem("Item 3", 2); + +submenu.setHeader("Select an option:"); + +let result = submenu.show(); + +print("Result:", result); diff --git a/applications/system/js_app/modules/js_blebeacon.c b/applications/system/js_app/modules/js_blebeacon.c new file mode 100644 index 0000000000..4d19accb1c --- /dev/null +++ b/applications/system/js_app/modules/js_blebeacon.c @@ -0,0 +1,245 @@ +#include "../js_modules.h" +#include +#include + +typedef struct { + bool saved_prev_cfg; + bool prev_cfg_set; + GapExtraBeaconConfig prev_cfg; + + bool saved_prev_data; + uint8_t prev_data[EXTRA_BEACON_MAX_DATA_SIZE]; + uint8_t prev_data_len; + + bool saved_prev_active; + bool prev_active; + + bool keep_alive; +} JsBlebeaconInst; + +static JsBlebeaconInst* get_this_ctx(struct mjs* mjs) { + mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); + JsBlebeaconInst* storage = mjs_get_ptr(mjs, obj_inst); + furi_assert(storage); + return storage; +} + +static void ret_bad_args(struct mjs* mjs, const char* error) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "%s", error); + mjs_return(mjs, MJS_UNDEFINED); +} + +static void ret_int_err(struct mjs* mjs, const char* error) { + mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "%s", error); + mjs_return(mjs, MJS_UNDEFINED); +} + +static bool check_arg_count(struct mjs* mjs, size_t count) { + size_t num_args = mjs_nargs(mjs); + if(num_args != count) { + ret_bad_args(mjs, "Wrong argument count"); + return false; + } + return true; +} + +static bool get_int_arg(struct mjs* mjs, size_t index, uint8_t* value, bool error) { + mjs_val_t int_obj = mjs_arg(mjs, index); + if(!mjs_is_number(int_obj)) { + if(error) ret_bad_args(mjs, "Argument must be a number"); + return false; + } + *value = mjs_get_int(mjs, int_obj); + return true; +} + +static void js_blebeacon_is_active(struct mjs* mjs) { + JsBlebeaconInst* blebeacon = get_this_ctx(mjs); + if(!check_arg_count(mjs, 0)) return; + UNUSED(blebeacon); + + mjs_return(mjs, mjs_mk_boolean(mjs, furi_hal_bt_extra_beacon_is_active())); +} + +static void js_blebeacon_set_config(struct mjs* mjs) { + JsBlebeaconInst* blebeacon = get_this_ctx(mjs); + if(mjs_nargs(mjs) < 1 || mjs_nargs(mjs) > 4) { + ret_bad_args(mjs, "Wrong argument count"); + return; + } + + char* mac = NULL; + size_t mac_len = 0; + mjs_val_t mac_arg = mjs_arg(mjs, 0); + if(mjs_is_typed_array(mac_arg)) { + if(mjs_is_data_view(mac_arg)) { + mac_arg = mjs_dataview_get_buf(mjs, mac_arg); + } + mac = mjs_array_buf_get_ptr(mjs, mac_arg, &mac_len); + } + if(!mac || mac_len != EXTRA_BEACON_MAC_ADDR_SIZE) { + ret_bad_args(mjs, "Wrong MAC address"); + return; + } + + uint8_t power = GapAdvPowerLevel_0dBm; + get_int_arg(mjs, 1, &power, false); + power = CLAMP(power, GapAdvPowerLevel_6dBm, GapAdvPowerLevel_Neg40dBm); + + uint8_t intv_min = 50; + get_int_arg(mjs, 2, &intv_min, false); + intv_min = MAX(intv_min, 20); + + uint8_t intv_max = 150; + get_int_arg(mjs, 3, &intv_max, false); + intv_max = MAX(intv_max, intv_min); + + GapExtraBeaconConfig config = { + .min_adv_interval_ms = intv_min, + .max_adv_interval_ms = intv_max, + .adv_channel_map = GapAdvChannelMapAll, + .adv_power_level = power, + .address_type = GapAddressTypePublic, + }; + memcpy(config.address, (uint8_t*)mac, sizeof(config.address)); + + if(!blebeacon->saved_prev_cfg) { + blebeacon->saved_prev_cfg = true; + const GapExtraBeaconConfig* prev_cfg_ptr = furi_hal_bt_extra_beacon_get_config(); + if(prev_cfg_ptr) { + blebeacon->prev_cfg_set = true; + memcpy(&blebeacon->prev_cfg, prev_cfg_ptr, sizeof(blebeacon->prev_cfg)); + } else { + blebeacon->prev_cfg_set = false; + } + } + if(!furi_hal_bt_extra_beacon_set_config(&config)) { + ret_int_err(mjs, "Failed setting beacon config"); + return; + } + + mjs_return(mjs, MJS_UNDEFINED); +} + +static void js_blebeacon_set_data(struct mjs* mjs) { + JsBlebeaconInst* blebeacon = get_this_ctx(mjs); + if(!check_arg_count(mjs, 1)) return; + + char* data = NULL; + size_t data_len = 0; + mjs_val_t data_arg = mjs_arg(mjs, 0); + if(mjs_is_typed_array(data_arg)) { + if(mjs_is_data_view(data_arg)) { + data_arg = mjs_dataview_get_buf(mjs, data_arg); + } + data = mjs_array_buf_get_ptr(mjs, data_arg, &data_len); + } + if(!data) { + ret_bad_args(mjs, "Data must be a Uint8Array"); + return; + } + + if(!blebeacon->saved_prev_data) { + blebeacon->saved_prev_data = true; + blebeacon->prev_data_len = furi_hal_bt_extra_beacon_get_data(blebeacon->prev_data); + } + if(!furi_hal_bt_extra_beacon_set_data((uint8_t*)data, data_len)) { + ret_int_err(mjs, "Failed setting beacon data"); + return; + } + + mjs_return(mjs, MJS_UNDEFINED); +} + +static void js_blebeacon_start(struct mjs* mjs) { + JsBlebeaconInst* blebeacon = get_this_ctx(mjs); + if(!check_arg_count(mjs, 0)) return; + + if(!blebeacon->saved_prev_active) { + blebeacon->saved_prev_active = true; + blebeacon->prev_active = furi_hal_bt_extra_beacon_is_active(); + } + if(!furi_hal_bt_extra_beacon_start()) { + ret_int_err(mjs, "Failed starting beacon"); + return; + } + + mjs_return(mjs, MJS_UNDEFINED); +} + +static void js_blebeacon_stop(struct mjs* mjs) { + JsBlebeaconInst* blebeacon = get_this_ctx(mjs); + if(!check_arg_count(mjs, 0)) return; + UNUSED(blebeacon); + + if(!blebeacon->saved_prev_active) { + blebeacon->saved_prev_active = true; + blebeacon->prev_active = furi_hal_bt_extra_beacon_is_active(); + } + if(!furi_hal_bt_extra_beacon_stop()) { + ret_int_err(mjs, "Failed stopping beacon"); + return; + } + + mjs_return(mjs, MJS_UNDEFINED); +} + +static void js_blebeacon_keep_alive(struct mjs* mjs) { + JsBlebeaconInst* blebeacon = get_this_ctx(mjs); + if(!check_arg_count(mjs, 1)) return; + + mjs_val_t bool_obj = mjs_arg(mjs, 0); + blebeacon->keep_alive = mjs_get_bool(mjs, bool_obj); + + mjs_return(mjs, MJS_UNDEFINED); +} + +static void* js_blebeacon_create(struct mjs* mjs, mjs_val_t* object) { + JsBlebeaconInst* blebeacon = malloc(sizeof(JsBlebeaconInst)); + mjs_val_t blebeacon_obj = mjs_mk_object(mjs); + mjs_set(mjs, blebeacon_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, blebeacon)); + mjs_set(mjs, blebeacon_obj, "isActive", ~0, MJS_MK_FN(js_blebeacon_is_active)); + mjs_set(mjs, blebeacon_obj, "setConfig", ~0, MJS_MK_FN(js_blebeacon_set_config)); + mjs_set(mjs, blebeacon_obj, "setData", ~0, MJS_MK_FN(js_blebeacon_set_data)); + mjs_set(mjs, blebeacon_obj, "start", ~0, MJS_MK_FN(js_blebeacon_start)); + mjs_set(mjs, blebeacon_obj, "stop", ~0, MJS_MK_FN(js_blebeacon_stop)); + mjs_set(mjs, blebeacon_obj, "keepAlive", ~0, MJS_MK_FN(js_blebeacon_keep_alive)); + *object = blebeacon_obj; + return blebeacon; +} + +static void js_blebeacon_destroy(void* inst) { + JsBlebeaconInst* blebeacon = inst; + if(!blebeacon->keep_alive) { + if(furi_hal_bt_extra_beacon_is_active()) { + furi_check(furi_hal_bt_extra_beacon_stop()); + } + if(blebeacon->saved_prev_cfg && blebeacon->prev_cfg_set) { + furi_check(furi_hal_bt_extra_beacon_set_config(&blebeacon->prev_cfg)); + } + if(blebeacon->saved_prev_data) { + furi_check( + furi_hal_bt_extra_beacon_set_data(blebeacon->prev_data, blebeacon->prev_data_len)); + } + if(blebeacon->prev_active) { + furi_check(furi_hal_bt_extra_beacon_start()); + } + } + free(blebeacon); +} + +static const JsModuleDescriptor js_blebeacon_desc = { + "blebeacon", + js_blebeacon_create, + js_blebeacon_destroy, +}; + +static const FlipperAppPluginDescriptor plugin_descriptor = { + .appid = PLUGIN_APP_ID, + .ep_api_version = PLUGIN_API_VERSION, + .entry_point = &js_blebeacon_desc, +}; + +const FlipperAppPluginDescriptor* js_blebeacon_ep(void) { + return &plugin_descriptor; +} \ No newline at end of file diff --git a/applications/system/js_app/modules/js_keyboard.c b/applications/system/js_app/modules/js_keyboard.c new file mode 100644 index 0000000000..8958dcaf8c --- /dev/null +++ b/applications/system/js_app/modules/js_keyboard.c @@ -0,0 +1,195 @@ +#include "../js_modules.h" +#include +#include +#include + +#define membersof(x) (sizeof(x) / sizeof(x[0])) + +typedef struct { + char* data; + TextInput* text_input; + ByteInput* byte_input; + ViewDispatcher* view_dispatcher; + uint8_t* byteinput; +} JsKeyboardInst; + +typedef enum { + JsKeyboardViewTextInput, + JsKeyboardViewByteInput, +} JsKeyboardView; + +static void ret_bad_args(struct mjs* mjs, const char* error) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "%s", error); + mjs_return(mjs, MJS_UNDEFINED); +} + +static bool get_str_arg(struct mjs* mjs, size_t index, const char** value, bool error) { + mjs_val_t str_obj = mjs_arg(mjs, index); + if(!mjs_is_string(str_obj)) { + if(error) ret_bad_args(mjs, "Argument must be a string"); + return false; + } + size_t str_len = 0; + *value = mjs_get_string(mjs, &str_obj, &str_len); + if((str_len == 0) || (*value == NULL)) { + if(error) ret_bad_args(mjs, "Bad string argument"); + return false; + } + return true; +} + +static bool get_int_arg(struct mjs* mjs, size_t index, size_t* value, bool error) { + mjs_val_t int_obj = mjs_arg(mjs, index); + if(!mjs_is_number(int_obj)) { + if(error) ret_bad_args(mjs, "Argument must be a number"); + return false; + } + *value = mjs_get_int(mjs, int_obj); + return true; +} + +static JsKeyboardInst* get_this_ctx(struct mjs* mjs) { + mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); + JsKeyboardInst* storage = mjs_get_ptr(mjs, obj_inst); + furi_assert(storage); + return storage; +} + +void text_input_callback(void* context) { + JsKeyboardInst* keyboard = (JsKeyboardInst*)context; + view_dispatcher_stop(keyboard->view_dispatcher); +} + +void byte_input_callback(void* context) { + JsKeyboardInst* keyboard = (JsKeyboardInst*)context; + view_dispatcher_stop(keyboard->view_dispatcher); +} + +static void js_keyboard_set_header(struct mjs* mjs) { + JsKeyboardInst* keyboard = get_this_ctx(mjs); + + const char* header; + if(!get_str_arg(mjs, 0, &header, true)) return; + + text_input_set_header_text(keyboard->text_input, header); + byte_input_set_header_text(keyboard->byte_input, header); + + mjs_return(mjs, MJS_UNDEFINED); +} + +static void js_keyboard_text(struct mjs* mjs) { + JsKeyboardInst* keyboard = get_this_ctx(mjs); + + size_t input_length; + if(!get_int_arg(mjs, 0, &input_length, true)) return; + char* buffer = malloc(input_length); + + const char* default_text = ""; + bool clear_default = false; + if(get_str_arg(mjs, 1, &default_text, false)) { + strlcpy(buffer, default_text, input_length); + mjs_val_t bool_obj = mjs_arg(mjs, 2); + clear_default = mjs_get_bool(mjs, bool_obj); + } + + view_dispatcher_attach_to_gui( + keyboard->view_dispatcher, furi_record_open(RECORD_GUI), ViewDispatcherTypeFullscreen); + furi_record_close(RECORD_GUI); + + text_input_set_result_callback( + keyboard->text_input, text_input_callback, keyboard, buffer, input_length, clear_default); + + view_dispatcher_switch_to_view(keyboard->view_dispatcher, JsKeyboardViewTextInput); + + view_dispatcher_run(keyboard->view_dispatcher); + + text_input_reset(keyboard->text_input); + + mjs_return(mjs, mjs_mk_string(mjs, buffer, ~0, true)); + free(buffer); +} + +static void js_keyboard_byte(struct mjs* mjs) { + JsKeyboardInst* keyboard = get_this_ctx(mjs); + + size_t input_length; + if(!get_int_arg(mjs, 0, &input_length, true)) return; + uint8_t* buffer = malloc(input_length); + + mjs_val_t default_data_arg = mjs_arg(mjs, 1); + if(mjs_is_typed_array(default_data_arg)) { + if(mjs_is_data_view(default_data_arg)) { + default_data_arg = mjs_dataview_get_buf(mjs, default_data_arg); + } + size_t default_data_len = 0; + char* default_data = mjs_array_buf_get_ptr(mjs, default_data_arg, &default_data_len); + memcpy(buffer, (uint8_t*)default_data, MIN((size_t)input_length, default_data_len)); + } + + view_dispatcher_attach_to_gui( + keyboard->view_dispatcher, furi_record_open(RECORD_GUI), ViewDispatcherTypeFullscreen); + furi_record_close(RECORD_GUI); + + byte_input_set_result_callback( + keyboard->byte_input, byte_input_callback, NULL, keyboard, buffer, input_length); + + view_dispatcher_switch_to_view(keyboard->view_dispatcher, JsKeyboardViewByteInput); + + view_dispatcher_run(keyboard->view_dispatcher); + + byte_input_set_result_callback(keyboard->byte_input, NULL, NULL, NULL, NULL, 0); + byte_input_set_header_text(keyboard->byte_input, ""); + + mjs_return(mjs, mjs_mk_array_buf(mjs, (char*)buffer, input_length)); + free(buffer); +} + +static void* js_keyboard_create(struct mjs* mjs, mjs_val_t* object) { + JsKeyboardInst* keyboard = malloc(sizeof(JsKeyboardInst)); + mjs_val_t keyboard_obj = mjs_mk_object(mjs); + mjs_set(mjs, keyboard_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, keyboard)); + mjs_set(mjs, keyboard_obj, "setHeader", ~0, MJS_MK_FN(js_keyboard_set_header)); + mjs_set(mjs, keyboard_obj, "text", ~0, MJS_MK_FN(js_keyboard_text)); + mjs_set(mjs, keyboard_obj, "byte", ~0, MJS_MK_FN(js_keyboard_byte)); + keyboard->byte_input = byte_input_alloc(); + keyboard->text_input = text_input_alloc(); + keyboard->view_dispatcher = view_dispatcher_alloc(); + view_dispatcher_enable_queue(keyboard->view_dispatcher); + view_dispatcher_add_view( + keyboard->view_dispatcher, + JsKeyboardViewTextInput, + text_input_get_view(keyboard->text_input)); + view_dispatcher_add_view( + keyboard->view_dispatcher, + JsKeyboardViewByteInput, + byte_input_get_view(keyboard->byte_input)); + *object = keyboard_obj; + return keyboard; +} + +static void js_keyboard_destroy(void* inst) { + JsKeyboardInst* keyboard = inst; + view_dispatcher_remove_view(keyboard->view_dispatcher, JsKeyboardViewByteInput); + byte_input_free(keyboard->byte_input); + view_dispatcher_remove_view(keyboard->view_dispatcher, JsKeyboardViewTextInput); + text_input_free(keyboard->text_input); + view_dispatcher_free(keyboard->view_dispatcher); + free(keyboard->data); + free(keyboard); +} + +static const JsModuleDescriptor js_keyboard_desc = { + "keyboard", + js_keyboard_create, + js_keyboard_destroy, +}; + +static const FlipperAppPluginDescriptor plugin_descriptor = { + .appid = PLUGIN_APP_ID, + .ep_api_version = PLUGIN_API_VERSION, + .entry_point = &js_keyboard_desc, +}; + +const FlipperAppPluginDescriptor* js_keyboard_ep(void) { + return &plugin_descriptor; +} \ No newline at end of file diff --git a/applications/system/js_app/modules/js_math.c b/applications/system/js_app/modules/js_math.c new file mode 100644 index 0000000000..80d97fb9cf --- /dev/null +++ b/applications/system/js_app/modules/js_math.c @@ -0,0 +1,309 @@ +#include "../js_modules.h" +#include "furi_hal_random.h" + +#define JS_MATH_PI (double)3.14159265358979323846 +#define JS_MATH_E (double)2.7182818284590452354 + +static void ret_bad_args(struct mjs* mjs, const char* error) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "%s", error); + mjs_return(mjs, mjs_mk_undefined()); +} + +static bool check_arg_count(struct mjs* mjs, size_t count) { + size_t num_args = mjs_nargs(mjs); + if(num_args != count) { + ret_bad_args(mjs, "Wrong argument count"); + return false; + } + return true; +} + +void js_math_abs(struct mjs* mjs) { + if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { + mjs_return(mjs, MJS_UNDEFINED); + } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + mjs_return(mjs, x < 0 ? mjs_mk_number(mjs, -x) : mjs_arg(mjs, 0)); +} + +void js_math_acos(struct mjs* mjs) { + if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { + mjs_return(mjs, MJS_UNDEFINED); + } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + if(x < -1 || x > 1) { + ret_bad_args(mjs, "Invalid input value for Math.acos"); + mjs_return(mjs, MJS_UNDEFINED); + } + mjs_return(mjs, mjs_mk_number(mjs, JS_MATH_PI / (double)2 - atan(x / sqrt(1 - x * x)))); +} + +void js_math_acosh(struct mjs* mjs) { + if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { + mjs_return(mjs, MJS_UNDEFINED); + } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + if(x < 1) { + ret_bad_args(mjs, "Invalid input value for Math.acosh"); + mjs_return(mjs, MJS_UNDEFINED); + } + mjs_return(mjs, mjs_mk_number(mjs, log(x + sqrt(x * x - 1)))); +} + +void js_math_asin(struct mjs* mjs) { + if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { + mjs_return(mjs, MJS_UNDEFINED); + } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + mjs_return(mjs, mjs_mk_number(mjs, atan(x / sqrt(1 - x * x)))); +} + +void js_math_asinh(struct mjs* mjs) { + if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { + mjs_return(mjs, MJS_UNDEFINED); + } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + mjs_return(mjs, mjs_mk_number(mjs, log(x + sqrt(x * x + 1)))); +} + +void js_math_atan(struct mjs* mjs) { + if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { + mjs_return(mjs, MJS_UNDEFINED); + } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + mjs_return(mjs, mjs_mk_number(mjs, atan(x))); +} + +void js_math_atan2(struct mjs* mjs) { + if(!check_arg_count(mjs, 2) || !mjs_is_number(mjs_arg(mjs, 0)) || + !mjs_is_number(mjs_arg(mjs, 1))) { + mjs_return(mjs, MJS_UNDEFINED); + } + double y = mjs_get_double(mjs, mjs_arg(mjs, 0)); + double x = mjs_get_double(mjs, mjs_arg(mjs, 1)); + mjs_return(mjs, mjs_mk_number(mjs, atan2(y, x))); +} + +void js_math_atanh(struct mjs* mjs) { + if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { + mjs_return(mjs, MJS_UNDEFINED); + } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + if(x <= -1 || x >= 1) { + ret_bad_args(mjs, "Invalid input value for Math.atanh"); + mjs_return(mjs, MJS_UNDEFINED); + } + mjs_return(mjs, mjs_mk_number(mjs, (double)0.5 * log((1 + x) / (1 - x)))); +} + +void js_math_cbrt(struct mjs* mjs) { + if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { + mjs_return(mjs, MJS_UNDEFINED); + } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + mjs_return(mjs, mjs_mk_number(mjs, pow(x, 1.0 / 3.0))); +} + +void js_math_ceil(struct mjs* mjs) { + if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { + mjs_return(mjs, MJS_UNDEFINED); + } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + mjs_return(mjs, mjs_mk_number(mjs, (int)(x + (double)0.5))); +} + +void js_math_clz32(struct mjs* mjs) { + if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { + mjs_return(mjs, MJS_UNDEFINED); + } + unsigned int x = (unsigned int)mjs_get_int(mjs, mjs_arg(mjs, 0)); + int count = 0; + while(x) { + x >>= 1; + count++; + } + mjs_return(mjs, mjs_mk_number(mjs, 32 - count)); +} + +void js_math_cos(struct mjs* mjs) { + if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { + mjs_return(mjs, MJS_UNDEFINED); + } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + mjs_return(mjs, mjs_mk_number(mjs, cos(x))); +} + +void js_math_exp(struct mjs* mjs) { + if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { + mjs_return(mjs, MJS_UNDEFINED); + } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + double result = 1; + double term = 1; + for(int i = 1; i < 100; i++) { + term *= x / i; + result += term; + } + mjs_return(mjs, mjs_mk_number(mjs, result)); +} + +void js_math_floor(struct mjs* mjs) { + if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { + mjs_return(mjs, MJS_UNDEFINED); + } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + mjs_return(mjs, mjs_mk_number(mjs, (int)x)); +} + +void js_math_log(struct mjs* mjs) { + if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { + mjs_return(mjs, MJS_UNDEFINED); + } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + if(x <= 0) { + ret_bad_args(mjs, "Invalid input value for Math.log"); + mjs_return(mjs, MJS_UNDEFINED); + } + double result = 0; + while(x >= JS_MATH_E) { + x /= JS_MATH_E; + result++; + } + mjs_return(mjs, mjs_mk_number(mjs, result + log(x))); +} + +void js_math_max(struct mjs* mjs) { + if(!check_arg_count(mjs, 2) || !mjs_is_number(mjs_arg(mjs, 0)) || + !mjs_is_number(mjs_arg(mjs, 1))) { + mjs_return(mjs, MJS_UNDEFINED); + } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + double y = mjs_get_double(mjs, mjs_arg(mjs, 1)); + mjs_return(mjs, mjs_mk_number(mjs, x > y ? x : y)); +} + +void js_math_min(struct mjs* mjs) { + if(!check_arg_count(mjs, 2) || !mjs_is_number(mjs_arg(mjs, 0)) || + !mjs_is_number(mjs_arg(mjs, 1))) { + mjs_return(mjs, MJS_UNDEFINED); + } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + double y = mjs_get_double(mjs, mjs_arg(mjs, 1)); + mjs_return(mjs, mjs_mk_number(mjs, x < y ? x : y)); +} + +void js_math_pow(struct mjs* mjs) { + if(!check_arg_count(mjs, 2) || !mjs_is_number(mjs_arg(mjs, 0)) || + !mjs_is_number(mjs_arg(mjs, 1))) { + mjs_return(mjs, MJS_UNDEFINED); + } + double base = mjs_get_double(mjs, mjs_arg(mjs, 0)); + double exponent = mjs_get_double(mjs, mjs_arg(mjs, 1)); + double result = 1; + for(int i = 0; i < exponent; i++) { + result *= base; + } + mjs_return(mjs, mjs_mk_number(mjs, result)); +} + +void js_math_random(struct mjs* mjs) { + if(!check_arg_count(mjs, 0)) { + mjs_return(mjs, MJS_UNDEFINED); + } + const uint32_t random_val = furi_hal_random_get(); + double rnd = (double)random_val / RAND_MAX; + mjs_return(mjs, mjs_mk_number(mjs, rnd)); +} + +void js_math_sign(struct mjs* mjs) { + if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { + mjs_return(mjs, MJS_UNDEFINED); + } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + mjs_return(mjs, mjs_mk_number(mjs, x == 0 ? 0 : (x < 0 ? -1 : 1))); +} + +void js_math_sin(struct mjs* mjs) { + if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { + mjs_return(mjs, MJS_UNDEFINED); + } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + double result = x; + double term = x; + for(int i = 1; i < 10; i++) { + term *= -x * x / ((2 * i) * (2 * i + 1)); + result += term; + } + mjs_return(mjs, mjs_mk_number(mjs, result)); +} + +void js_math_sqrt(struct mjs* mjs) { + if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { + mjs_return(mjs, MJS_UNDEFINED); + } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + if(x < 0) { + ret_bad_args(mjs, "Invalid input value for Math.sqrt"); + mjs_return(mjs, MJS_UNDEFINED); + } + double result = 1; + while(result * result < x) { + result += (double)0.001; + } + mjs_return(mjs, mjs_mk_number(mjs, result)); +} + +void js_math_trunc(struct mjs* mjs) { + if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { + mjs_return(mjs, MJS_UNDEFINED); + } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + mjs_return(mjs, mjs_mk_number(mjs, x < 0 ? ceil(x) : floor(x))); +} + +static void* js_math_create(struct mjs* mjs, mjs_val_t* object) { + mjs_val_t math_obj = mjs_mk_object(mjs); + mjs_set(mjs, math_obj, "abs", ~0, MJS_MK_FN(js_math_abs)); + mjs_set(mjs, math_obj, "acos", ~0, MJS_MK_FN(js_math_acos)); + mjs_set(mjs, math_obj, "acosh", ~0, MJS_MK_FN(js_math_acosh)); + mjs_set(mjs, math_obj, "asin", ~0, MJS_MK_FN(js_math_asin)); + mjs_set(mjs, math_obj, "asinh", ~0, MJS_MK_FN(js_math_asinh)); + mjs_set(mjs, math_obj, "atan", ~0, MJS_MK_FN(js_math_atan)); + mjs_set(mjs, math_obj, "atan2", ~0, MJS_MK_FN(js_math_atan2)); + mjs_set(mjs, math_obj, "atanh", ~0, MJS_MK_FN(js_math_atanh)); + mjs_set(mjs, math_obj, "cbrt", ~0, MJS_MK_FN(js_math_cbrt)); + mjs_set(mjs, math_obj, "ceil", ~0, MJS_MK_FN(js_math_ceil)); + mjs_set(mjs, math_obj, "clz32", ~0, MJS_MK_FN(js_math_clz32)); + mjs_set(mjs, math_obj, "cos", ~0, MJS_MK_FN(js_math_cos)); + mjs_set(mjs, math_obj, "exp", ~0, MJS_MK_FN(js_math_exp)); + mjs_set(mjs, math_obj, "floor", ~0, MJS_MK_FN(js_math_floor)); + mjs_set(mjs, math_obj, "log", ~0, MJS_MK_FN(js_math_log)); + mjs_set(mjs, math_obj, "max", ~0, MJS_MK_FN(js_math_max)); + mjs_set(mjs, math_obj, "min", ~0, MJS_MK_FN(js_math_min)); + mjs_set(mjs, math_obj, "pow", ~0, MJS_MK_FN(js_math_pow)); + mjs_set(mjs, math_obj, "random", ~0, MJS_MK_FN(js_math_random)); + mjs_set(mjs, math_obj, "sign", ~0, MJS_MK_FN(js_math_sign)); + mjs_set(mjs, math_obj, "sin", ~0, MJS_MK_FN(js_math_sin)); + mjs_set(mjs, math_obj, "sqrt", ~0, MJS_MK_FN(js_math_sqrt)); + mjs_set(mjs, math_obj, "trunc", ~0, MJS_MK_FN(js_math_trunc)); + mjs_set(mjs, math_obj, "PI", ~0, mjs_mk_number(mjs, JS_MATH_PI)); + mjs_set(mjs, math_obj, "E", ~0, mjs_mk_number(mjs, JS_MATH_E)); + *object = math_obj; + return (void*)1; +} + +static const JsModuleDescriptor js_math_desc = { + "math", + js_math_create, + NULL, +}; + +static const FlipperAppPluginDescriptor plugin_descriptor = { + .appid = PLUGIN_APP_ID, + .ep_api_version = PLUGIN_API_VERSION, + .entry_point = &js_math_desc, +}; + +const FlipperAppPluginDescriptor* js_math_ep(void) { + return &plugin_descriptor; +} \ No newline at end of file diff --git a/applications/system/js_app/modules/js_subghz/js_subghz.c b/applications/system/js_app/modules/js_subghz/js_subghz.c new file mode 100644 index 0000000000..913f8c670d --- /dev/null +++ b/applications/system/js_app/modules/js_subghz/js_subghz.c @@ -0,0 +1,391 @@ +#include "../../js_modules.h" +#include "radio_device_loader.h" + +#include +#include +#include + +#include + +#define tag "js_subghz" + +typedef enum { + JsSubghzRadioStateRX, + JsSubghzRadioStateTX, + JsSubghzRadioStateIDLE, +} JsSubghzRadioState; + +typedef struct { + const SubGhzDevice* radio_device; + int frequency; + bool is_external; + JsSubghzRadioState state; +} JsSubghzInst; + +// from subghz cli +static FuriHalSubGhzPreset js_subghz_get_preset_name(const char* preset_name) { + FuriHalSubGhzPreset preset = FuriHalSubGhzPresetIDLE; + if(!strcmp(preset_name, "FuriHalSubGhzPresetOok270Async")) { + preset = FuriHalSubGhzPresetOok270Async; + } else if(!strcmp(preset_name, "FuriHalSubGhzPresetOok650Async")) { + preset = FuriHalSubGhzPresetOok650Async; + } else if(!strcmp(preset_name, "FuriHalSubGhzPreset2FSKDev238Async")) { + preset = FuriHalSubGhzPreset2FSKDev238Async; + } else if(!strcmp(preset_name, "FuriHalSubGhzPreset2FSKDev476Async")) { + preset = FuriHalSubGhzPreset2FSKDev476Async; + } else if(!strcmp(preset_name, "FuriHalSubGhzPresetCustom")) { + preset = FuriHalSubGhzPresetCustom; + } else { + FURI_LOG_I(tag, "unknown preset"); + } + return preset; +} + +static int32_t get_int_arg(struct mjs* mjs, size_t index, int32_t* value) { + mjs_val_t int_obj = mjs_arg(mjs, index); + if(!mjs_is_number(int_obj)) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "Argument must be a number"); + mjs_return(mjs, MJS_UNDEFINED); + return false; + } + *value = mjs_get_int(mjs, int_obj); + return true; +} + +static void js_subghz_set_rx(struct mjs* mjs) { + mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); + JsSubghzInst* js_subghz = mjs_get_ptr(mjs, obj_inst); + furi_assert(js_subghz); + + if(js_subghz->state == JsSubghzRadioStateRX) { + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + subghz_devices_set_rx(js_subghz->radio_device); + js_subghz->state = JsSubghzRadioStateRX; +} + +static void js_subgjz_set_idle(struct mjs* mjs) { + mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); + JsSubghzInst* js_subghz = mjs_get_ptr(mjs, obj_inst); + furi_assert(js_subghz); + + if(js_subghz->state == JsSubghzRadioStateIDLE) { + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + subghz_devices_idle(js_subghz->radio_device); + js_subghz->state = JsSubghzRadioStateIDLE; +} + +static void js_subghz_get_rssi(struct mjs* mjs) { + mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); + JsSubghzInst* js_subghz = mjs_get_ptr(mjs, obj_inst); + furi_assert(js_subghz); + + if(js_subghz->state != JsSubghzRadioStateRX) { + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + float rssi = subghz_devices_get_rssi(js_subghz->radio_device); + mjs_return(mjs, mjs_mk_number(mjs, (double)rssi)); +} + +static void js_subghz_get_state(struct mjs* mjs) { + mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); + JsSubghzInst* js_subghz = mjs_get_ptr(mjs, obj_inst); + furi_assert(js_subghz); + + const char* state; + switch(js_subghz->state) { + case JsSubghzRadioStateRX: + state = "RX"; + break; + case JsSubghzRadioStateTX: + state = "TX"; + break; + case JsSubghzRadioStateIDLE: + state = "IDLE"; + break; + default: + state = ""; + break; + } + + mjs_return(mjs, mjs_mk_string(mjs, state, ~0, true)); +} + +static void js_subghz_is_external(struct mjs* mjs) { + mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); + JsSubghzInst* js_subghz = mjs_get_ptr(mjs, obj_inst); + furi_assert(js_subghz); + + mjs_return(mjs, mjs_mk_boolean(mjs, js_subghz->is_external)); +} + +static void js_subghz_set_frequency(struct mjs* mjs) { + mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); + JsSubghzInst* js_subghz = mjs_get_ptr(mjs, obj_inst); + furi_assert(js_subghz); + + if(js_subghz->state != JsSubghzRadioStateIDLE) { + mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Radio is not in IDLE state"); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + int32_t frequency; + if(!get_int_arg(mjs, 0, &frequency)) return; + + if(!subghz_devices_is_frequency_valid(js_subghz->radio_device, frequency)) { + mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Invalid frequency"); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + js_subghz->frequency = subghz_devices_set_frequency(js_subghz->radio_device, frequency); + + mjs_return(mjs, mjs_mk_number(mjs, (double)js_subghz->frequency)); +} + +static void js_subghz_get_frequency(struct mjs* mjs) { + mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); + JsSubghzInst* js_subghz = mjs_get_ptr(mjs, obj_inst); + furi_assert(js_subghz); + + mjs_return(mjs, mjs_mk_number(mjs, (double)js_subghz->frequency)); +} + +static void js_subghz_transmit_file(struct mjs* mjs) { + mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); + JsSubghzInst* js_subghz = mjs_get_ptr(mjs, obj_inst); + furi_assert(js_subghz); + + mjs_val_t file = mjs_arg(mjs, 0); + + if(!mjs_is_string(file)) { + mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "File must be a string"); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + const char* file_path = mjs_get_string(mjs, &file, NULL); + if(!file_path) { + mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Failed to get file path"); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* fff_file = flipper_format_file_alloc(storage); + FlipperFormat* fff_data_raw = flipper_format_string_alloc(); + + if(!flipper_format_file_open_existing(fff_file, file_path)) { + flipper_format_free(fff_file); + furi_record_close(RECORD_STORAGE); + mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Failed to open file"); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + SubGhzEnvironment* environment = subghz_environment_alloc(); + if(!subghz_environment_load_keystore(environment, SUBGHZ_KEYSTORE_DIR_NAME)) { + FURI_LOG_I(tag, "Load_keystore keeloq_mfcodes \033[0;31mERROR\033[0m\r\n"); + } + if(!subghz_environment_load_keystore(environment, SUBGHZ_KEYSTORE_DIR_USER_NAME)) { + FURI_LOG_I(tag, "Load_keystore keeloq_mfcodes_user \033[0;33mAbsent\033[0m\r\n"); + } + subghz_environment_set_alutech_at_4n_rainbow_table_file_name( + environment, SUBGHZ_ALUTECH_AT_4N_DIR_NAME); + subghz_environment_set_nice_flor_s_rainbow_table_file_name( + environment, SUBGHZ_NICE_FLOR_S_DIR_NAME); + subghz_environment_set_protocol_registry(environment, (void*)&subghz_protocol_registry); + + FuriString* temp_str = furi_string_alloc(); + SubGhzTransmitter* transmitter = NULL; + bool is_init_protocol = true; + bool is_sent = false; + uint32_t frequency = 0; + uint32_t repeat = 10; + + do { + //Load frequency + if(!flipper_format_read_uint32(fff_file, "Frequency", &frequency, 1)) { + mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Failed to read frequency from file"); + mjs_return(mjs, MJS_UNDEFINED); + break; + } + + if(!subghz_devices_is_frequency_valid(js_subghz->radio_device, frequency)) { + mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Invalid frequency"); + mjs_return(mjs, MJS_UNDEFINED); + break; + } + + if(!flipper_format_read_string(fff_file, "Preset", temp_str)) { + mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Failed to read preset from file"); + mjs_return(mjs, MJS_UNDEFINED); + break; + } + + subghz_devices_reset(js_subghz->radio_device); + + if(!strcmp(furi_string_get_cstr(temp_str), "FuriHalSubGhzPresetCustom")) { + mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Custom presets are not supported (yet)"); + mjs_return(mjs, MJS_UNDEFINED); + break; + } else { + subghz_devices_load_preset( + js_subghz->radio_device, + js_subghz_get_preset_name(furi_string_get_cstr(temp_str)), + NULL); + } + + js_subghz->frequency = subghz_devices_set_frequency(js_subghz->radio_device, frequency); + + if(!flipper_format_read_string(fff_file, "Protocol", temp_str)) { + mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Failed to read protocol from file"); + mjs_return(mjs, MJS_UNDEFINED); + break; + } + + SubGhzProtocolStatus status; + bool is_raw = false; + + if(!strcmp(furi_string_get_cstr(temp_str), "RAW")) { + subghz_protocol_raw_gen_fff_data( + fff_data_raw, file_path, subghz_devices_get_name(js_subghz->radio_device)); + is_raw = true; + } + + transmitter = subghz_transmitter_alloc_init(environment, furi_string_get_cstr(temp_str)); + if(transmitter == NULL) { + is_init_protocol = false; + } + + if(is_init_protocol) { + status = subghz_transmitter_deserialize(transmitter, is_raw ? fff_data_raw : fff_file); + if(status != SubGhzProtocolStatusOk) { + FURI_LOG_I(tag, "failed to deserialize transmitter"); + is_init_protocol = false; + } + } else { + FURI_LOG_I(tag, "failed to allocate transmitter"); + subghz_devices_idle(js_subghz->radio_device); + js_subghz->state = JsSubghzRadioStateIDLE; + } + } while(false); + + if(is_init_protocol) { + if(!js_subghz->is_external) { + furi_hal_power_suppress_charge_enter(); + } + + FURI_LOG_I(tag, "transmitting file %s", file_path); + + do { + furi_delay_ms(200); + if(subghz_devices_start_async_tx( + js_subghz->radio_device, subghz_transmitter_yield, transmitter)) { + while(!subghz_devices_is_async_complete_tx(js_subghz->radio_device)) { + furi_delay_ms(333); + } + subghz_devices_stop_async_tx(js_subghz->radio_device); + is_sent = true; + } else { + FURI_LOG_E(tag, "failed to start async tx"); + } + + } while(repeat && !strcmp(furi_string_get_cstr(temp_str), "RAW")); + + subghz_devices_idle(js_subghz->radio_device); + js_subghz->state = JsSubghzRadioStateIDLE; + + if(!js_subghz->is_external) { + furi_hal_power_suppress_charge_exit(); + } + } + + furi_string_free(temp_str); + flipper_format_free(fff_file); + flipper_format_free(fff_data_raw); + furi_record_close(RECORD_STORAGE); + + subghz_environment_reset_keeloq(environment); + subghz_environment_free(environment); + subghz_transmitter_free(transmitter); + + mjs_return(mjs, mjs_mk_boolean(mjs, is_sent)); +} + +static void js_subghz_setup(struct mjs* mjs) { + mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); + JsSubghzInst* js_subghz = mjs_get_ptr(mjs, obj_inst); + furi_assert(js_subghz); + + js_subghz->radio_device = + radio_device_loader_set(js_subghz->radio_device, SubGhzRadioDeviceTypeExternalCC1101); + + if(!subghz_devices_is_connect(js_subghz->radio_device)) { + js_subghz->is_external = true; + } else { + js_subghz->is_external = false; + } + + js_subghz->state = JsSubghzRadioStateIDLE; + js_subghz->frequency = 433920000; + + subghz_devices_reset(js_subghz->radio_device); + subghz_devices_idle(js_subghz->radio_device); + + mjs_return(mjs, MJS_UNDEFINED); +} + +static void* js_subghz_create(struct mjs* mjs, mjs_val_t* object) { + JsSubghzInst* js_subghz = malloc(sizeof(JsSubghzInst)); + mjs_val_t subghz_obj = mjs_mk_object(mjs); + + subghz_devices_init(); + + mjs_set(mjs, subghz_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, js_subghz)); + mjs_set(mjs, subghz_obj, "setup", ~0, MJS_MK_FN(js_subghz_setup)); + mjs_set(mjs, subghz_obj, "setRx", ~0, MJS_MK_FN(js_subghz_set_rx)); + mjs_set(mjs, subghz_obj, "setIdle", ~0, MJS_MK_FN(js_subgjz_set_idle)); + mjs_set(mjs, subghz_obj, "getRssi", ~0, MJS_MK_FN(js_subghz_get_rssi)); + mjs_set(mjs, subghz_obj, "getState", ~0, MJS_MK_FN(js_subghz_get_state)); + mjs_set(mjs, subghz_obj, "getFrequency", ~0, MJS_MK_FN(js_subghz_get_frequency)); + mjs_set(mjs, subghz_obj, "setFrequency", ~0, MJS_MK_FN(js_subghz_set_frequency)); + mjs_set(mjs, subghz_obj, "isExternal", ~0, MJS_MK_FN(js_subghz_is_external)); + mjs_set(mjs, subghz_obj, "transmitFile", ~0, MJS_MK_FN(js_subghz_transmit_file)); + + *object = subghz_obj; + + return js_subghz; +} + +static void js_subghz_destroy(void* inst) { + JsSubghzInst* js_subghz = inst; + + subghz_devices_deinit(); + + free(js_subghz); +} + +static const JsModuleDescriptor js_subghz_desc = { + "subghz", + js_subghz_create, + js_subghz_destroy, +}; + +static const FlipperAppPluginDescriptor plugin_descriptor = { + .appid = PLUGIN_APP_ID, + .ep_api_version = PLUGIN_API_VERSION, + .entry_point = &js_subghz_desc, +}; + +const FlipperAppPluginDescriptor* js_subghz_ep(void) { + return &plugin_descriptor; +} diff --git a/applications/system/js_app/modules/js_subghz/radio_device_loader.c b/applications/system/js_app/modules/js_subghz/radio_device_loader.c new file mode 100644 index 0000000000..d2cffde583 --- /dev/null +++ b/applications/system/js_app/modules/js_subghz/radio_device_loader.c @@ -0,0 +1,64 @@ +#include "radio_device_loader.h" + +#include +#include + +static void radio_device_loader_power_on() { + uint8_t attempts = 0; + while(!furi_hal_power_is_otg_enabled() && attempts++ < 5) { + furi_hal_power_enable_otg(); + //CC1101 power-up time + furi_delay_ms(10); + } +} + +static void radio_device_loader_power_off() { + if(furi_hal_power_is_otg_enabled()) furi_hal_power_disable_otg(); +} + +bool radio_device_loader_is_connect_external(const char* name) { + bool is_connect = false; + bool is_otg_enabled = furi_hal_power_is_otg_enabled(); + + if(!is_otg_enabled) { + radio_device_loader_power_on(); + } + + const SubGhzDevice* device = subghz_devices_get_by_name(name); + if(device) { + is_connect = subghz_devices_is_connect(device); + } + + if(!is_otg_enabled) { + radio_device_loader_power_off(); + } + return is_connect; +} + +const SubGhzDevice* radio_device_loader_set( + const SubGhzDevice* current_radio_device, + SubGhzRadioDeviceType radio_device_type) { + const SubGhzDevice* radio_device; + + if(radio_device_type == SubGhzRadioDeviceTypeExternalCC1101 && + radio_device_loader_is_connect_external(SUBGHZ_DEVICE_CC1101_EXT_NAME)) { + radio_device_loader_power_on(); + radio_device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_EXT_NAME); + subghz_devices_begin(radio_device); + } else if(current_radio_device == NULL) { + radio_device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME); + } else { + radio_device_loader_end(current_radio_device); + radio_device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME); + } + + return radio_device; +} + +void radio_device_loader_end(const SubGhzDevice* radio_device) { + furi_assert(radio_device); + radio_device_loader_power_off(); + if(radio_device != subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME)) { + subghz_devices_end(radio_device); + } +} \ No newline at end of file diff --git a/applications/system/js_app/modules/js_subghz/radio_device_loader.h b/applications/system/js_app/modules/js_subghz/radio_device_loader.h new file mode 100644 index 0000000000..bee4e2c362 --- /dev/null +++ b/applications/system/js_app/modules/js_subghz/radio_device_loader.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +/** SubGhzRadioDeviceType */ +typedef enum { + SubGhzRadioDeviceTypeInternal, + SubGhzRadioDeviceTypeExternalCC1101, +} SubGhzRadioDeviceType; + +const SubGhzDevice* radio_device_loader_set( + const SubGhzDevice* current_radio_device, + SubGhzRadioDeviceType radio_device_type); + +void radio_device_loader_end(const SubGhzDevice* radio_device); \ No newline at end of file diff --git a/applications/system/js_app/modules/js_submenu.c b/applications/system/js_app/modules/js_submenu.c new file mode 100644 index 0000000000..b87f34fa8b --- /dev/null +++ b/applications/system/js_app/modules/js_submenu.c @@ -0,0 +1,152 @@ +#include +#include +#include +#include "../js_modules.h" + +typedef struct { + Submenu* submenu; + ViewDispatcher* view_dispatcher; + uint32_t result; +} JsSubmenuInst; + +typedef enum { + JsSubmenuViewSubmenu, +} JsSubmenuView; + +static JsSubmenuInst* get_this_ctx(struct mjs* mjs) { + mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); + JsSubmenuInst* storage = mjs_get_ptr(mjs, obj_inst); + furi_assert(storage); + return storage; +} + +static void ret_bad_args(struct mjs* mjs, const char* error) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "%s", error); + mjs_return(mjs, MJS_UNDEFINED); +} + +static bool check_arg_count(struct mjs* mjs, size_t count) { + size_t num_args = mjs_nargs(mjs); + if(num_args != count) { + ret_bad_args(mjs, "Wrong argument count"); + return false; + } + return true; +} + +static bool get_str_arg(struct mjs* mjs, size_t index, const char** value) { + mjs_val_t str_obj = mjs_arg(mjs, index); + if(!mjs_is_string(str_obj)) { + ret_bad_args(mjs, "Argument must be a string"); + return false; + } + size_t str_len = 0; + *value = mjs_get_string(mjs, &str_obj, &str_len); + if((str_len == 0) || (*value == NULL)) { + ret_bad_args(mjs, "Bad string argument"); + return false; + } + return true; +} + +static int32_t get_int_arg(struct mjs* mjs, size_t index, int32_t* value) { + mjs_val_t int_obj = mjs_arg(mjs, index); + if(!mjs_is_number(int_obj)) { + ret_bad_args(mjs, "Argument must be a number"); + return false; + } + *value = mjs_get_int32(mjs, int_obj); + return true; +} + +static void submenu_callback(void* context, uint32_t id) { + UNUSED(id); + JsSubmenuInst* submenu = context; + submenu->result = id; + view_dispatcher_stop(submenu->view_dispatcher); +} + +static void js_submenu_add_item(struct mjs* mjs) { + JsSubmenuInst* submenu = get_this_ctx(mjs); + if(!check_arg_count(mjs, 2)) return; + + const char* label; + if(!get_str_arg(mjs, 0, &label)) return; + + int32_t id; + if(!get_int_arg(mjs, 1, &id)) return; + + submenu_add_item(submenu->submenu, label, id, submenu_callback, submenu); + + mjs_return(mjs, MJS_UNDEFINED); +} + +static void js_submenu_set_header(struct mjs* mjs) { + JsSubmenuInst* submenu = get_this_ctx(mjs); + if(!check_arg_count(mjs, 1)) return; + + const char* header; + if(!get_str_arg(mjs, 0, &header)) return; + + submenu_set_header(submenu->submenu, header); + + mjs_return(mjs, MJS_UNDEFINED); +} + +static void js_submenu_show(struct mjs* mjs) { + JsSubmenuInst* submenu = get_this_ctx(mjs); + if(!check_arg_count(mjs, 0)) return; + submenu->result = 0; + + view_dispatcher_attach_to_gui( + submenu->view_dispatcher, furi_record_open(RECORD_GUI), ViewDispatcherTypeFullscreen); + furi_record_close(RECORD_GUI); + + view_dispatcher_switch_to_view(submenu->view_dispatcher, JsSubmenuViewSubmenu); + + view_dispatcher_run(submenu->view_dispatcher); + + submenu_reset(submenu->submenu); + + mjs_return(mjs, mjs_mk_number(mjs, submenu->result)); +} + +static void* js_submenu_create(struct mjs* mjs, mjs_val_t* object) { + JsSubmenuInst* submenu = malloc(sizeof(JsSubmenuInst)); + mjs_val_t submenu_obj = mjs_mk_object(mjs); + mjs_set(mjs, submenu_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, submenu)); + mjs_set(mjs, submenu_obj, "addItem", ~0, MJS_MK_FN(js_submenu_add_item)); + mjs_set(mjs, submenu_obj, "setHeader", ~0, MJS_MK_FN(js_submenu_set_header)); + mjs_set(mjs, submenu_obj, "show", ~0, MJS_MK_FN(js_submenu_show)); + submenu->submenu = submenu_alloc(); + submenu->view_dispatcher = view_dispatcher_alloc(); + view_dispatcher_enable_queue(submenu->view_dispatcher); + view_dispatcher_add_view( + submenu->view_dispatcher, JsSubmenuViewSubmenu, submenu_get_view(submenu->submenu)); + *object = submenu_obj; + return submenu; +} + +static void js_submenu_destroy(void* inst) { + JsSubmenuInst* submenu = inst; + view_dispatcher_remove_view(submenu->view_dispatcher, JsSubmenuViewSubmenu); + submenu_free(submenu->submenu); + view_dispatcher_free(submenu->view_dispatcher); + free(submenu); +} + +static const JsModuleDescriptor js_submenu_desc = { + "submenu", + js_submenu_create, + js_submenu_destroy, +}; + +static const FlipperAppPluginDescriptor submenu_plugin_descriptor = { + .appid = PLUGIN_APP_ID, + .ep_api_version = PLUGIN_API_VERSION, + .entry_point = &js_submenu_desc, +}; + +const FlipperAppPluginDescriptor* js_submenu_ep(void) { + return &submenu_plugin_descriptor; +} \ No newline at end of file diff --git a/assets/ReadMe.md b/assets/ReadMe.md index 67777fa86a..d72308cae5 100644 --- a/assets/ReadMe.md +++ b/assets/ReadMe.md @@ -1,17 +1,19 @@ -# Requirements +# Firmware Assets {#firmware_assets} + +## Requirements - Python3 - Python3 packages: Pillow & heatshrink2 -# Compiling +## Compiling ```bash ./fbt icons proto dolphin_internal dolphin_blocking dolphin_ext resources ``` -# Asset naming rules +## Asset naming rules -## Images and Animations +### Images and Animations `NAME_VARIANT_SIZE` @@ -22,16 +24,16 @@ Image names will be automatically prefixed with `I_`, animation names with `A_`. Icons and Animations will be gathered into `icon.h` and `icon.c`. -## Dolphin and Games assets +### Dolphin and Games assets Rules are same as for Images and Animations plus assets are grouped by level and level prepends `NAME`. Good starting point: https://docs.unrealengine.com/4.27/en-US/ProductionPipelines/AssetNaming/ -# Important notes +## Important notes Don't include assets that you are not using, compiler is not going to strip unused assets. -# Structure +## Structure - `dolphin` - Dolphin game assets sources. Goes to `compiled` and `resources` folders in `build` directory. - `packs` - Hot-swappable asset packs used system wide, both animations and icons. Compiled to `.bm` and `.bmx`, then put at `SD/asset_packs` - `icons` - Icons sources. Goes to `compiled` folder in `build` directory. diff --git a/assets/dolphin/ReadMe.md b/assets/dolphin/ReadMe.md index 05da49c962..53ab62c649 100644 --- a/assets/dolphin/ReadMe.md +++ b/assets/dolphin/ReadMe.md @@ -1,4 +1,4 @@ -# Dolphin assets +# Dolphin assets {#dolphin_assets} Dolphin assets are split into 3 parts: diff --git a/furi/core/check.h b/furi/core/check.h index 9b44049604..d57dd9413d 100644 --- a/furi/core/check.h +++ b/furi/core/check.h @@ -52,7 +52,7 @@ FURI_NORETURN void __furi_halt_implementation(); /** Crash system * - * @param optional message (const char*) + * @param ... optional message (const char*) */ #define furi_crash(...) M_APPLY(__furi_crash, M_IF_EMPTY(__VA_ARGS__)((NULL), (__VA_ARGS__))) @@ -66,7 +66,7 @@ FURI_NORETURN void __furi_halt_implementation(); /** Halt system * - * @param optional message (const char*) + * @param ... optional message (const char*) */ #define furi_halt(...) M_APPLY(__furi_halt, M_IF_EMPTY(__VA_ARGS__)((NULL), (__VA_ARGS__))) @@ -80,8 +80,7 @@ FURI_NORETURN void __furi_halt_implementation(); /** Check condition and crash if failed * - * @param condition to check - * @param optional message (const char*) + * @param ... condition to check and optional message (const char*) */ #define furi_check(...) \ M_APPLY(__furi_check, M_DEFAULT_ARGS(2, (__FURI_CHECK_MESSAGE_FLAG), __VA_ARGS__)) @@ -106,8 +105,7 @@ FURI_NORETURN void __furi_halt_implementation(); * * @warning only will do check if firmware compiled in debug mode * - * @param condition to check - * @param optional message (const char*) + * @param ... condition to check and optional message (const char*) */ #define furi_assert(...) \ M_APPLY(__furi_assert, M_DEFAULT_ARGS(2, (__FURI_ASSERT_MESSAGE_FLAG), __VA_ARGS__)) diff --git a/furi/core/kernel.h b/furi/core/kernel.h index c962402efd..592f01d57d 100644 --- a/furi/core/kernel.h +++ b/furi/core/kernel.h @@ -79,7 +79,7 @@ void furi_delay_tick(uint32_t ticks); * * @warning This should never be called in interrupt request context. * - * @param[in] ticks The tick until which kerel should delay task execution + * @param[in] tick The tick until which kerel should delay task execution * * @return The furi status. */ diff --git a/furi/core/log.h b/furi/core/log.h index a587d8ab27..3ce88db5b2 100644 --- a/furi/core/log.h +++ b/furi/core/log.h @@ -51,7 +51,7 @@ void furi_log_init(void); /** Add log TX callback * - * @param[in] callback The callback + * @param[in] handler The callback and its context * * @return true on success, false otherwise */ @@ -59,7 +59,7 @@ bool furi_log_add_handler(FuriLogHandler handler); /** Remove log TX callback * - * @param[in] callback The callback + * @param[in] handler The callback and its context * * @return true on success, false otherwise */ @@ -112,15 +112,16 @@ FuriLogLevel furi_log_get_level(void); /** Log level to string * * @param[in] level The level + * @param[out] str String representation of the level * - * @return The string + * @return True if success, False otherwise */ bool furi_log_level_to_string(FuriLogLevel level, const char** str); /** Log level from string * * @param[in] str The string - * @param level The level + * @param[out] level The level * * @return True if success, False otherwise */ diff --git a/furi/core/memmgr_heap.h b/furi/core/memmgr_heap.h index 9aacba1ca7..660c5c6bf0 100644 --- a/furi/core/memmgr_heap.h +++ b/furi/core/memmgr_heap.h @@ -18,13 +18,13 @@ extern "C" { * * @param thread_id - thread id to track */ -void memmgr_heap_enable_thread_trace(FuriThreadId taks_handle); +void memmgr_heap_enable_thread_trace(FuriThreadId thread_id); /** Memmgr heap disable thread allocation tracking * * @param thread_id - thread id to track */ -void memmgr_heap_disable_thread_trace(FuriThreadId taks_handle); +void memmgr_heap_disable_thread_trace(FuriThreadId thread_id); /** Memmgr heap get allocatred thread memory * @@ -32,7 +32,7 @@ void memmgr_heap_disable_thread_trace(FuriThreadId taks_handle); * * @return bytes allocated right now */ -size_t memmgr_heap_get_thread_memory(FuriThreadId taks_handle); +size_t memmgr_heap_get_thread_memory(FuriThreadId thread_id); /** Memmgr heap get the max contiguous block size on the heap * diff --git a/furi/core/message_queue.h b/furi/core/message_queue.h index 392a145f10..8d7f389e12 100644 --- a/furi/core/message_queue.h +++ b/furi/core/message_queue.h @@ -32,7 +32,6 @@ void furi_message_queue_free(FuriMessageQueue* instance); * @param instance pointer to FuriMessageQueue instance * @param[in] msg_ptr The message pointer * @param[in] timeout The timeout - * @param[in] msg_prio The message prio * * @return The furi status. */ @@ -43,7 +42,6 @@ FuriStatus * * @param instance pointer to FuriMessageQueue instance * @param msg_ptr The message pointer - * @param msg_prio The message prioority * @param[in] timeout The timeout * * @return The furi status. diff --git a/furi/core/string.h b/furi/core/string.h index 0e3e6a88e6..77ae9da6dc 100644 --- a/furi/core/string.h +++ b/furi/core/string.h @@ -102,7 +102,7 @@ void furi_string_reserve(FuriString* string, size_t size); /** * @brief Reset string. * Make the string empty. - * @param s + * @param string */ void furi_string_reset(FuriString* string); diff --git a/furi/core/thread.h b/furi/core/thread.h index 83c051cc22..489a468448 100644 --- a/furi/core/thread.h +++ b/furi/core/thread.h @@ -235,8 +235,6 @@ int32_t furi_thread_get_return_code(FuriThread* thread); /** Thread related methods that doesn't involve FuriThread directly */ /** Get FreeRTOS FuriThreadId for current thread - * - * @param thread FuriThread instance * * @return FuriThreadId or NULL */ @@ -263,7 +261,7 @@ uint32_t furi_thread_flags_wait(uint32_t flags, uint32_t options, uint32_t timeo * @brief Enumerate threads * * @param thread_array array of FuriThreadId, where thread ids will be stored - * @param array_items array size + * @param array_item_count array size * @return uint32_t threads count */ uint32_t furi_thread_enumerate(FuriThreadId* thread_array, uint32_t array_item_count); diff --git a/lib/flipper_application/elf/elf_file.h b/lib/flipper_application/elf/elf_file.h index 631fe122f9..921b506bdb 100644 --- a/lib/flipper_application/elf/elf_file.h +++ b/lib/flipper_application/elf/elf_file.h @@ -98,8 +98,7 @@ bool elf_file_is_init_complete(ELFFile* elf); /** * @brief Get actual entry point for ELF file * @param elf_file - * @param args - * @return int32_t + * @return void* */ void* elf_file_get_entry_point(ELFFile* elf_file); @@ -148,4 +147,4 @@ ElfProcessSectionResult elf_process_section( #ifdef __cplusplus } -#endif \ No newline at end of file +#endif diff --git a/lib/nfc/nfc.h b/lib/nfc/nfc.h index 4f7980b026..6f3e25a5d0 100644 --- a/lib/nfc/nfc.h +++ b/lib/nfc/nfc.h @@ -170,7 +170,7 @@ void nfc_set_fdt_listen_fc(Nfc* instance, uint32_t fdt_listen_fc); * @brief Set mask receive time. * * @param[in,out] instance pointer to the instance to be modified. - * @param[in] mask_rx_time mask receive time, in carrier cycles. + * @param[in] mask_rx_time_fc mask receive time, in carrier cycles. */ void nfc_set_mask_receive_time_fc(Nfc* instance, uint32_t mask_rx_time_fc); diff --git a/lib/nfc/nfc_scanner.h b/lib/nfc/nfc_scanner.h index a1b4aabcda..c13a58b741 100644 --- a/lib/nfc/nfc_scanner.h +++ b/lib/nfc/nfc_scanner.h @@ -72,14 +72,14 @@ NfcScanner* nfc_scanner_alloc(Nfc* nfc); /** * @brief Delete an NfcScanner instance. * - * @param[in,out] pointer to the instance to be deleted. + * @param[in,out] instance pointer to the instance to be deleted. */ void nfc_scanner_free(NfcScanner* instance); /** * @brief Start an NfcScanner. * - * @param[in,out] pointer to the instance to be started. + * @param[in,out] instance pointer to the instance to be started. * @param[in] callback pointer to the callback function (will be called upon a detection event). * @param[in] context pointer to the caller-specific context (will be passed to the callback). */ @@ -88,7 +88,7 @@ void nfc_scanner_start(NfcScanner* instance, NfcScannerCallback callback, void* /** * @brief Stop an NfcScanner. * - * @param[in,out] pointer to the instance to be stopped. + * @param[in,out] instance pointer to the instance to be stopped. */ void nfc_scanner_stop(NfcScanner* instance); diff --git a/lib/subghz/subghz_setting.c b/lib/subghz/subghz_setting.c index 43f270978e..480cda5322 100644 --- a/lib/subghz/subghz_setting.c +++ b/lib/subghz/subghz_setting.c @@ -43,6 +43,7 @@ static const uint32_t subghz_frequency_list[] = { 390000000, 418000000, 430000000, + 430500000, 431000000, 431500000, 433075000, /* LPD433 first */ diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 21acb0fdc1..507a3cdbeb 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1031,6 +1031,7 @@ Function,-,exp2l,long double,long double Function,+,expansion_disable,void,Expansion* Function,+,expansion_enable,void,Expansion* Function,+,expansion_get_settings,ExpansionSettings*,Expansion* +Function,+,expansion_is_connected,_Bool,Expansion* Function,+,expansion_set_listen_serial,void,"Expansion*, FuriHalSerialId" Function,-,expansion_settings_load,_Bool,ExpansionSettings* Function,+,expansion_settings_save,_Bool,ExpansionSettings* diff --git a/targets/f7/furi_hal/furi_hal_rtc.h b/targets/f7/furi_hal/furi_hal_rtc.h index 36b297d29c..25685f9cdb 100644 --- a/targets/f7/furi_hal/furi_hal_rtc.h +++ b/targets/f7/furi_hal/furi_hal_rtc.h @@ -199,7 +199,7 @@ FuriHalRtcHeapTrackMode furi_hal_rtc_get_heap_track_mode(void); /** Set locale units * - * @param[in] mode The RTC Locale Units + * @param[in] value The RTC Locale Units */ void furi_hal_rtc_set_locale_units(FuriHalRtcLocaleUnits value); diff --git a/targets/furi_hal_include/furi_hal_bt.h b/targets/furi_hal_include/furi_hal_bt.h index e498b15862..cc81e09547 100644 --- a/targets/furi_hal_include/furi_hal_bt.h +++ b/targets/furi_hal_include/furi_hal_bt.h @@ -97,11 +97,12 @@ void furi_hal_bt_reinit(); /** Change BLE app * Restarts 2nd core * - * @param profile FuriHalBleProfileTemplate instance - * @param event_cb GapEventCallback instance - * @param context pointer to context + * @param profile_template FuriHalBleProfileTemplate instance + * @param profile_params Parameters to pass to the profile. Can be NULL + * @param event_cb GapEventCallback instance + * @param context pointer to context * - * @return instance of profile, NULL on failure + * @return instance of profile, NULL on failure */ FURI_WARN_UNUSED FuriHalBleProfileBase* furi_hal_bt_change_app( const FuriHalBleProfileTemplate* profile_template, diff --git a/targets/furi_hal_include/furi_hal_i2c.h b/targets/furi_hal_include/furi_hal_i2c.h index f493655b4d..44f647cefc 100644 --- a/targets/furi_hal_include/furi_hal_i2c.h +++ b/targets/furi_hal_include/furi_hal_i2c.h @@ -91,7 +91,7 @@ bool furi_hal_i2c_tx( * @param size Size of data buffer * @param begin How to begin the transaction * @param end How to end the transaction - * @param timer Timeout timer + * @param timeout Timeout in milliseconds * * @return true on successful transfer, false otherwise */ @@ -131,7 +131,7 @@ bool furi_hal_i2c_rx( * @param size Size of data buffer * @param begin How to begin the transaction * @param end How to end the transaction - * @param timer Timeout timer + * @param timeout Timeout in milliseconds * * @return true on successful transfer, false otherwise */ diff --git a/targets/furi_hal_include/furi_hal_infrared.h b/targets/furi_hal_include/furi_hal_infrared.h index 93cba676aa..e0aaabd8e8 100644 --- a/targets/furi_hal_include/furi_hal_infrared.h +++ b/targets/furi_hal_include/furi_hal_infrared.h @@ -36,15 +36,15 @@ typedef void (*FuriHalInfraredTxSignalSentISRCallback)(void* context); /** Signature of callback function for receiving continuous INFRARED rx signal. * - * @param ctx[in] context to pass to callback - * @param level[in] level of input INFRARED rx signal - * @param duration[in] duration of continuous rx signal level in us + * @param[in] ctx context to pass to callback + * @param[in] level level of input INFRARED rx signal + * @param[in] duration duration of continuous rx signal level in us */ typedef void (*FuriHalInfraredRxCaptureCallback)(void* ctx, bool level, uint32_t duration); /** Signature of callback function for reaching silence timeout on INFRARED port. * - * @param ctx[in] context to pass to callback + * @param[in] ctx context to pass to callback */ typedef void (*FuriHalInfraredRxTimeoutCallback)(void* ctx); diff --git a/targets/furi_hal_include/furi_hal_nfc.h b/targets/furi_hal_include/furi_hal_nfc.h index 3d145d1002..3d8ff394ef 100644 --- a/targets/furi_hal_include/furi_hal_nfc.h +++ b/targets/furi_hal_include/furi_hal_nfc.h @@ -228,9 +228,9 @@ FuriHalNfcError furi_hal_nfc_poller_tx(const uint8_t* tx_data, size_t tx_bits); * * The receive buffer must be big enough to accomodate all of the expected data. * - * @param rx_data[out] pointer to a byte array to be filled with received data. - * @param rx_data_size[in] maximum received data size, in bytes. - * @param rx_bits[out] pointer to the variable to hold received data size, in bits. + * @param[out] rx_data pointer to a byte array to be filled with received data. + * @param[in] rx_data_size maximum received data size, in bytes. + * @param[out] rx_bits pointer to the variable to hold received data size, in bits. * @returns FuriHalNfcErrorNone on success, any other error code on failure. */ FuriHalNfcError furi_hal_nfc_poller_rx(uint8_t* rx_data, size_t rx_data_size, size_t* rx_bits); @@ -249,9 +249,9 @@ FuriHalNfcError furi_hal_nfc_listener_tx(const uint8_t* tx_data, size_t tx_bits) * * The receive buffer must be big enough to accomodate all of the expected data. * - * @param rx_data[out] pointer to a byte array to be filled with received data. - * @param rx_data_size[in] maximum received data size, in bytes. - * @param rx_bits[out] pointer to the variable to hold received data size, in bits. + * @param[out] rx_data pointer to a byte array to be filled with received data. + * @param[in] rx_data_size maximum received data size, in bytes. + * @param[out] rx_bits pointer to the variable to hold received data size, in bits. * @returns FuriHalNfcErrorNone on success, any other error code on failure. */ FuriHalNfcError furi_hal_nfc_listener_rx(uint8_t* rx_data, size_t rx_data_size, size_t* rx_bits); @@ -395,9 +395,9 @@ FuriHalNfcError furi_hal_nfc_iso14443a_tx_sdd_frame(const uint8_t* tx_data, size * * The receive buffer must be big enough to accomodate all of the expected data. * - * @param rx_data[out] pointer to a byte array to be filled with received data. - * @param rx_data_size[in] maximum received data size, in bytes. - * @param rx_bits[out] pointer to the variable to hold received data size, in bits. + * @param[in] rx_data pointer to a byte array to be filled with received data. + * @param[in] rx_data_size maximum received data size, in bytes. + * @param[in] rx_bits pointer to the variable to hold received data size, in bits. * @returns FuriHalNfcErrorNone on success, any other error code on failure. */ FuriHalNfcError diff --git a/targets/furi_hal_include/furi_hal_power.h b/targets/furi_hal_include/furi_hal_power.h index ebe0fe6149..fa5e179c00 100644 --- a/targets/furi_hal_include/furi_hal_power.h +++ b/targets/furi_hal_include/furi_hal_power.h @@ -135,9 +135,7 @@ float furi_hal_power_get_battery_charge_voltage_limit(); * * Invalid values will be clamped downward to the nearest valid value. * - * @param voltage[in] voltage in V - * - * @return voltage in V + * @param[in] voltage voltage in V */ void furi_hal_power_set_battery_charge_voltage_limit(float voltage); @@ -161,7 +159,7 @@ uint32_t furi_hal_power_get_battery_design_capacity(); /** Get battery voltage in V * - * @param ic FuriHalPowerIc to get measurment + * @param[in] ic FuriHalPowerIc to get measurment * * @return voltage in V */ @@ -169,7 +167,7 @@ float furi_hal_power_get_battery_voltage(FuriHalPowerIC ic); /** Get battery current in A * - * @param ic FuriHalPowerIc to get measurment + * @param[in] ic FuriHalPowerIc to get measurment * * @return current in A */ @@ -177,7 +175,7 @@ float furi_hal_power_get_battery_current(FuriHalPowerIC ic); /** Get temperature in C * - * @param ic FuriHalPowerIc to get measurment + * @param[in] ic FuriHalPowerIc to get measurment * * @return temperature in C */