diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b043ed45..b9cba5555 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,35 +8,48 @@ - Non-exhaustive list of changes to help you fix your scripts: - `badusb`: - `setup()`: `mfr_name`, `prod_name`, `layout_path` parameters renamed to `mfrName`, `prodName`, `layoutPath` - - existing scripts using badusb will need to simply rename these + - effort required to update old scripts using badusb: very minimal - `dialog`: - removed, now replaced by `gui/dialog` and `gui/file_picker` (see below) - `event_loop`: - new module, allows timer functionality, callbacks and event-driven programming, used heavily alongside gpio and gui modules - `gpio`: - fully overhauled, now you `get()` pin instances and perform actions on them like `.init()` - - existing scripts using gpio will need some reworking + - now supports interrupts, callbacks and more cool things + - effort required to update old scripts using gpio: moderate - `gui`: - new module, fully overhauled, replaces dialog, keyboard, submenu, textbox modules - higher barrier to entry than older modules (requires usage of `event_loop` and `gui.viewDispatcher`), but much more flexible, powerful and easier to extend - includes all previously available js gui functionality (except `widget`), and also adds `gui/loading` and `gui/empty_screen` views - - existing scripts using gui in any way will need a huge amount of reworking + - currently `gui/file_picker` works different than other new view objects, it is a simple `.pickFile()` synchronous function, but this [may change later](https://github.com/flipperdevices/flipperzero-firmware/pull/3961#discussion_r1805579153) + - effort required to update old scripts using gui: extensive - `keyboard`: - removed, now replaced by `gui/text_input` and `gui/byte_input` (see above) + - `math`: + - `is_equal()` renamed to `isEqual()` - `storage`: - fully overhauled, now you `openFile()`s and perform actions on them like `.read()` - now supports many more operations including different open modes, directories and much more - `virtualInit()`, `virtualMount()`, `virtualQuit()` still work the same - - existing scripts using storage will need some reworking + - effort required to update old scripts using storage: moderate - `submenu`: - removed, now replaced by `gui/submenu` (see above) - `textbox`: - removed, now replace by `gui/text_box` (see above) - `widget`: - only gui functionality not ported to new gui module, remains unchanged for now but likely to be ported later on - - Added type definitions (typescript files for type checking in IDE, Flipper does not run typescript, and you code in javascript) + - globals: + - `__filepath` and `__dirpath` renamed to `__filename` and `__dirname` like in nodejs + - `to_string()` renamed and moved to number class as `n.toString()`, now supports optional base parameter + - `to_hex_string()` removed, now use `n.toString(16)` + - `parse_int()` renamed to `parseInt()`, now supports optional base parameter + - `to_upper_case()` and `to_lower_case()` renamed and moved to string class as `s.toUpperCase()` and `s.toLowerCase()` + - effort required to update old scripts using these: minimal + - Added type definitions (typescript files for type checking in IDE, Flipper does not run typescript) - Documentation is incomplete and deprecated, from now on you should refer to type definitions (`applications/system/js_app/types`), those will always be correct - Type definitions for extra modules we have that OFW doesn't will come later +- GUI: Refactored TextInput illegal symbols (by @Willy-JL) + - If your app used `text_input_add_illegal_symbols(text_input)` it should change to `text_input_show_illegal_symbols(text_input, true)` ### Added: - Apps: @@ -46,10 +59,12 @@ - Sub-GHz: - Show satellites count with an icon (#215 by @m7i-org) - Add Bresser 3CH weather station protocol (#217 #245 by @m7i-org) + - Add Vauno-EN8822 weather station protocol (#262 by @m7i-org) - UL: Add Marantec24 protocol (static 24 bit) with add manually (by @xMasterX) - UL: Add GangQi protocol (static 34 bit) with button parsing and add manually (by @xMasterX & @Skorpionm) - UL: Add Hollarm protocol (static 42 bit) with button parsing and add manually (by @xMasterX & @Skorpionm) - UL: Add Hay21 protocol (dynamic 21 bit) with button parsing (by @xMasterX) + - UL: Add Keeloq Monarch full support, with add manually (by @ashphx) - UL: Princeton custom buttons support (by @xMasterX) - NFC: - OFW: MIFARE Classic Key Recovery Improvements (PR 3822 by @noproto) @@ -61,9 +76,13 @@ - OFW: H World Hotel Chain Room Key Parser and MFC keys (by @zinongli) - OFW: Parser for Tianjin Railway Transit (by @zinongli) - Infrared: - - Bluray/DVD Universal Remote (#250 by @jaylikesbunda) + - Bluray/DVD Universal Remote (#250 #264 by @jaylikesbunda) - Option to "Load from Library File" for Universal Remotes (#255 by @zxkmm) - Updater: New Yappy themed icon while updating (#253 by @the1anonlypr3 & @Kuronons & @nescap) +- JS: + - New `i2c` module (#259 by @jamisonderek) + - New `spi` module (#272 by @jamisonderek) +- RPC: Added ASCII event support (#284 by @Willy-JL) - BadKB: - OFW: Add linux/gnome badusb demo files (by @thomasnemer) - Add older qFlipper install demos for windows and macos (by @DXVVAY & @grugnoymeme) @@ -71,6 +90,7 @@ - OFW: Dolphin: Happy mode in Desktop settings (by @portasynthinca3) - OFW: CLI: Improvements part I, `neofetch` command (by @portasynthinca3), fix for lab.flipper.net (by @xMasterX) - GUI: + - ByteInput supports ASCII input (by @Willy-JL) - OFW: Add up and down button drawing functions to GUI elements (by @DerSkythe) - OFW: Extended icon draw function in Canvas (by @RebornedBrain) - OFW: RPC: Support 5V on GPIO control for ext. modules (by @gsurkov) @@ -88,21 +108,27 @@ - DTMF Dolphin: Add EAS tone support (by @JendrBendr) - NFC Playlist: Error screens for playlist already exists and item already in playlist, general improvements (by @acegoal07), refactor rename/new scene without thread (by @Willy-JL) - CLI-GUI Bridge: Fixes and improvements (by @ranchordo) - - Seader: Enable T=1 (by @bettse) + - Seader: Enable T=1, show error for timeout, fix wrong LRC logging, fix crash scanning NTAG215 with MFC option (by @bettse) - BLE Spam: Fix menu index callback (by @Willy-JL) - Solitaire: App rewrite, Added quick solve, New effects and sounds, Removed hacky canvas manipulation (by @doofy-dev) - CLI-GUI Bridge: Add more symbols to keyboard (#222 by @Willy-JL) - UL: Sub-GHz Bruteforcer: Add new protocols for existing dump option (by @xMasterX), use FW functions for top buttons (by @DerSkythe) - UL: NRF24 Apps: Use string library compatible with OFW SDK (by @xMasterX) + - UL: W5500 Ethernet: Various fixes and improvements (by @xMasterX) - OFW: SPI Mem Manager: Fixed UI rendering bug related to line breaks (by @portasynthinca3) - OFW: USB/BT Remote: Mouse clicker option to click as fast as possible (by @sumukhj1219) - CLI: Print plugin name on load fail (by @Willy-JL) - NFC: + - NDEF Parser: + - Mifare Classic support (#265 by @luu176), protocol-agnostic rewrite and more improvements (#265 by @Willy-JL) + - SLIX support, parse even with TLV terminator omitted, parse empty NDEF TLVs (#278 by @Willy-JL) + - Decoding of URL-encoded URI characters (#267 by @jaylikesbunda) + - SmartPoster record support (#275 by @Willy-JL) + - Enable parsing NTAG I2C Plus 1k and 2k chips too (#237 by @RocketGod-git) - Added 6 new Mifare Classic keys from Bulgaria Hotel (#216 by @z3r0l1nk) - - NDEF parser supports NTAG I2C Plus 1k and 2k chips too (by @RocketGod-git) - UL: Add iq aparts hotel key (by @xMasterX) - OFW/UL: Rename 'Detect Reader' to 'Extract MFC Keys' (by @bettse & @xMasterX) - - OFW: Plantain parser improvements (by @assasinfil) + - OFW: Plantain parser improvements (by @assasinfil) and fixes (by @mxcdoam) - OFW: Moscow social card parser (by @assasinfil) - OFW: Fixes and improvements to iso14443_4a listener and poller (by @RebornedBrain) - OFW: Update BART station codes in Clipper plugin (by @ted-logan) @@ -152,6 +178,7 @@ - Fallback SD format prompt when storage settings is unavailable (by @Willy-JL) - OFW: Fix folder rename fails (by @portasynthinca3) - About: Fix BLE stack version string (by @Willy-JL) +- RPC: Fixed apps not updating and staying at 100% (by @Willy-JL) - OFW: Loader: Warn about missing SD card for main apps (by @Willy-JL) - NFC: - UL: Read Ultralight block by block (by @mishamyte) @@ -166,6 +193,7 @@ - OFW: Prevent idle priority threads from potentially starving the FreeRTOS idle task (by @CookiePLMonster) - OFW: Wait for RNG ready state and no errors before sampling (by @n1kolasM) - OFW: A Lot of Fixes (by @skotopes) +- OFW: CLI: Add warning about stealth mode in vibro command (by @ivanbarsukov) - OFW: Debug: Use proper hook for handle_exit in flipperapps (by @skotopes) - OFW: API: Fix kerel typo in documentation (by @EntranceJew) diff --git a/ReadMe.md b/ReadMe.md index 6c05f5bef..5552bdb86 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -34,7 +34,7 @@ We offer a powerful and easy-to-use application tailor-made for our firmware, th - Interface: Tweak every part of your Flipper, from the desktop animations, to the main menu, lockscreen behavior, file browser, etc. -- Protocols: Configure SubGhz settings, add/remove custom frequencies, and setup which GPIO pins are used by different external modules. +- Protocols: Configure SubGhz settings, add/remove custom frequencies, extend SubGhz frequencies to 281-361, 378-481, 749-962 MHz and setup which GPIO pins are used by different external modules. - Misc: Everything else that doesn't fit the previous categories. Change your Flipper's name, XP level, screen options, and configure the RGB backlight. diff --git a/applications/debug/unit_tests/resources/unit_tests/subghz/vauno_en8822c.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/vauno_en8822c.sub new file mode 100644 index 000000000..28751fe2f --- /dev/null +++ b/applications/debug/unit_tests/resources/unit_tests/subghz/vauno_en8822c.sub @@ -0,0 +1,16 @@ +Filetype: Flipper SubGhz Key File +Version: 1 +Frequency: 433920000 +Preset: FuriHalSubGhzPresetOok270Async +Lat: 0.000000 +Lon: 0.000000 +Protocol: Vauno-EN8822C +Id: 64 +Bit: 42 +Data: 00 00 01 00 02 16 88 1D +Batt: 0 +Hum: 81 +Ts: 1728836876 +Ch: 0 +Btn: 255 +Temp: 13.300000 diff --git a/applications/debug/unit_tests/resources/unit_tests/subghz/vauno_en8822c_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/vauno_en8822c_raw.sub new file mode 100644 index 000000000..86e51ebe7 --- /dev/null +++ b/applications/debug/unit_tests/resources/unit_tests/subghz/vauno_en8822c_raw.sub @@ -0,0 +1,6 @@ +Filetype: Flipper SubGhz RAW File +Version: 1 +Frequency: 433920000 +Preset: FuriHalSubGhzPresetOok270Async +Protocol: RAW +RAW_Data: 588 -4136 546 -4150 578 -4158 546 -2096 578 -2092 592 -2068 592 -4126 578 -4144 570 -2064 580 -2092 594 -2102 564 -2168 582 -2078 582 -2112 544 -2102 572 -2096 572 -4130 574 -2114 552 -2090 594 -2064 590 -2104 560 -2094 566 -2090 564 -2092 580 -2098 566 -2096 570 -2164 578 -2090 546 -4140 578 -2104 568 -4138 578 -2078 550 -2086 594 -9092 592 -61848 200 -17582 66 -5072 130 -11038 100 -60 164 -8288 166 -2374 302 -94 166 -300 66 -18706 96 -25730 192 -66 158 -29578 64 -99962 128 -99858 96 -99908 130 -9892 164 -96 98 -89580 162 -106 76 -11000 196 -88450 506 -3900 522 -1900 522 -3878 500 -1936 482 -1958 488 -1938 500 -3906 462 -1966 482 -1950 500 -1924 502 -1958 474 -1940 480 -1960 520 -3858 512 -3878 524 -3888 464 -1968 482 -3910 478 -7792 518 -1912 502 -7802 478 -1962 476 -3902 510 -1938 500 -1938 480 -1954 504 -1936 488 -1954 476 -1960 476 -1948 478 -1964 466 -1994 446 -1970 464 -1972 480 -1954 474 -1954 482 -1970 474 -3934 446 -1958 466 -1978 480 -1956 468 -1960 472 -3900 516 -1948 478 -3908 494 -3878 508 -1938 502 -3896 482 -1950 480 -1938 514 -1924 516 -3884 484 -1940 502 -1942 480 -1958 502 -1930 506 -1938 480 -1934 516 -3860 510 -3912 488 -3888 504 -1940 486 -3876 506 -7800 526 -1928 476 -7790 512 -1942 508 -3876 486 -1918 518 -1962 482 -1954 482 -1930 498 -1958 470 -1936 490 -1962 484 -1960 476 -1932 520 -1938 490 -1928 508 -1952 478 -1940 480 -1958 500 -3892 498 -1930 484 -1960 474 -1936 496 -1934 514 -3890 508 -1906 522 -3882 478 -3916 484 -1932 506 -3888 512 -1928 502 -1928 502 -1938 492 -3920 490 -1924 510 -1928 498 -1934 480 -1954 488 -1972 462 -1964 478 -3896 484 -3902 476 -3912 484 -1944 476 -3916 498 -7784 510 -1930 504 -7796 498 -1936 518 -3864 492 -1964 482 -1950 474 -1962 470 -1954 490 -1960 478 -1952 482 -1934 520 -1934 482 -1948 482 -1944 486 -1970 448 -1976 500 -1936 480 -1964 474 -3902 484 -1960 480 -1952 482 -1962 474 -1950 486 -3914 448 -1984 450 -3916 520 -3872 490 -1942 480 -3938 448 -1970 464 -1972 482 -1964 484 -3910 484 -1938 476 -1950 516 -1926 470 -1970 514 -1906 500 -1950 506 -3876 510 -3850 524 -3896 486 -1934 518 -3870 506 -7798 512 -1912 512 -7798 516 -1918 478 -3924 458 -1956 472 -1954 506 -1936 512 -1930 474 -1968 482 -1930 504 -1940 486 -1958 482 -1960 486 -1936 486 -1936 494 -1934 516 -1912 510 -1962 474 -3882 504 -1956 478 -1938 510 -1926 500 -1924 514 -3920 450 -1970 478 -3904 488 -3896 502 -1942 486 -3912 476 -1926 508 -1940 486 -1962 474 -3920 484 -1930 512 -1944 470 -1970 470 -1948 502 -1936 478 -1962 482 -3914 484 -3878 502 -3906 472 -1968 488 -3894 498 -7774 496 -1948 diff --git a/applications/debug/unit_tests/tests/subghz/subghz_test.c b/applications/debug/unit_tests/tests/subghz/subghz_test.c index 8f1471089..f2e48e079 100644 --- a/applications/debug/unit_tests/tests/subghz/subghz_test.c +++ b/applications/debug/unit_tests/tests/subghz/subghz_test.c @@ -691,6 +691,13 @@ MU_TEST(subghz_decoder_bresser_3ch_v0_test) { "Test decoder " WS_PROTOCOL_BRESSER_3CH_NAME " v0 error\r\n"); } +MU_TEST(subghz_decoder_vauno_en8822c_test) { + mu_assert( + subghz_decoder_test( + EXT_PATH("unit_tests/subghz/vauno_en8822c.sub"), WS_PROTOCOL_VAUNO_EN8822C_NAME), + "Test decoder " WS_PROTOCOL_VAUNO_EN8822C_NAME " error\r\n"); +} + //test encoders MU_TEST(subghz_encoder_princeton_test) { mu_assert( @@ -916,6 +923,7 @@ MU_TEST_SUITE(subghz) { MU_RUN_TEST(subghz_decoder_solight_te44_test); MU_RUN_TEST(subghz_decoder_bresser_3ch_v1_test); MU_RUN_TEST(subghz_decoder_bresser_3ch_v0_test); + MU_RUN_TEST(subghz_decoder_vauno_en8822c_test); MU_RUN_TEST(subghz_encoder_princeton_test); MU_RUN_TEST(subghz_encoder_came_test); diff --git a/applications/external b/applications/external index 6e157c28b..d0300c7a5 160000 --- a/applications/external +++ b/applications/external @@ -1 +1 @@ -Subproject commit 6e157c28b3bea31122f0fcb4282a81cd4fcafa04 +Subproject commit d0300c7a5a353ba0c73030c81afc75ec440797e6 diff --git a/applications/main/infrared/resources/infrared/assets/bluray_dvd.ir b/applications/main/infrared/resources/infrared/assets/bluray_dvd.ir index 18aecdaee..aff93ef1a 100644 --- a/applications/main/infrared/resources/infrared/assets/bluray_dvd.ir +++ b/applications/main/infrared/resources/infrared/assets/bluray_dvd.ir @@ -1,7 +1,7 @@ Filetype: IR library file Version: 1 # -# Last Updated: 2nd October 2024 +# Last Updated: 15th October 2024 # name: Power type: parsed @@ -9,12 +9,6 @@ protocol: Samsung32 address: 2D 00 00 00 command: 30 00 00 00 # -name: Ok -type: parsed -protocol: Samsung32 -address: 2D 00 00 00 -command: 58 00 00 00 -# name: Play type: parsed protocol: Samsung32 @@ -41,12 +35,6 @@ protocol: Samsung32 address: 2C 00 00 00 command: 1E 00 00 00 # -name: Ok -type: parsed -protocol: Samsung32 -address: 2C 00 00 00 -command: AA 00 00 00 -# name: Play type: parsed protocol: Samsung32 @@ -59,6 +47,18 @@ protocol: Samsung32 address: 2C 00 00 00 command: 4F 00 00 00 # +name: Eject +type: parsed +protocol: Samsung32 +address: 2C 00 00 00 +command: 9A 00 00 00 +# +name: Ok +type: parsed +protocol: Samsung32 +address: 2C 00 00 00 +command: AA 00 00 00 +# name: Fast_fo type: parsed protocol: Samsung32 @@ -71,12 +71,6 @@ protocol: Samsung32 address: 2C 00 00 00 command: 53 00 00 00 # -name: Eject -type: parsed -protocol: Samsung32 -address: 2C 00 00 00 -command: 9A 00 00 00 -# name: Subtitle type: parsed protocol: Samsung32 @@ -89,7 +83,7 @@ protocol: Samsung32 address: 2D 00 00 00 command: 36 00 00 00 # -# Model: OPPO BDP93 +# Model: OPPO BDP103 # name: Power type: parsed @@ -97,17 +91,29 @@ protocol: NEC address: 49 00 00 00 command: 1A 00 00 00 # +name: Eject +type: parsed +protocol: NEC +address: 49 00 00 00 +command: 1B 00 00 00 +# name: Ok type: parsed protocol: NEC address: 49 00 00 00 command: 53 00 00 00 # -name: Play +name: Fast_fo type: parsed protocol: NEC address: 49 00 00 00 -command: 56 00 00 00 +command: 51 00 00 00 +# +name: Fast_ba +type: parsed +protocol: NEC +address: 49 00 00 00 +command: 55 00 00 00 # name: Pause type: parsed @@ -115,13 +121,19 @@ protocol: NEC address: 49 00 00 00 command: 14 00 00 00 # +name: Play +type: parsed +protocol: NEC +address: 49 00 00 00 +command: 56 00 00 00 +# name: Subtitle type: parsed protocol: NEC address: 49 00 00 00 command: 4B 00 00 00 # -# Model: Panasonic DMP_BD75 +# Model: Panasonic DMPBDT167 # name: Power type: parsed @@ -129,42 +141,54 @@ protocol: Kaseikyo address: B0 02 20 00 command: D0 03 00 00 # +name: Eject +type: parsed +protocol: Kaseikyo +address: B0 02 20 00 +command: 10 00 00 00 +# name: Ok type: parsed protocol: Kaseikyo address: B0 02 20 02 command: 20 00 00 00 # -name: Play -type: parsed -protocol: Kaseikyo -address: B0 02 20 00 -command: A0 00 00 00 -# -name: Pause -type: parsed -protocol: Kaseikyo -address: B0 02 20 00 -command: 60 00 00 00 -# -name: Fast_ba -type: parsed -protocol: Kaseikyo -address: B0 02 20 00 -command: 40 00 00 00 -# name: Fast_fo type: parsed protocol: Kaseikyo address: B0 02 20 00 command: 50 00 00 00 # +name: Fast_ba +type: parsed +protocol: Kaseikyo +address: B0 02 20 00 +command: 40 00 00 00 +# +name: Pause +type: parsed +protocol: Kaseikyo +address: B0 02 20 00 +command: 60 00 00 00 +# +name: Play +type: parsed +protocol: Kaseikyo +address: B0 02 20 00 +command: A0 00 00 00 +# name: Subtitle type: parsed protocol: Kaseikyo address: B1 02 20 01 command: 10 03 00 00 # +name: Subtitle +type: parsed +protocol: Kaseikyo +address: B0 02 20 02 +command: 30 01 00 00 +# # Model: Philips BDP2501/F7 # name: Power @@ -343,12 +367,6 @@ protocol: Samsung32 address: 07 00 00 00 command: 02 00 00 00 # -name: Ok -type: parsed -protocol: Samsung32 -address: 07 00 00 00 -command: 68 00 00 00 -# name: Play type: parsed protocol: Samsung32 @@ -361,12 +379,6 @@ protocol: Samsung32 address: 07 00 00 00 command: 4A 00 00 00 # -name: Fast_fo -type: parsed -protocol: Samsung32 -address: 07 00 00 00 -command: 48 00 00 00 -# # Model: Samsung BD-D5300 # name: Power @@ -387,12 +399,6 @@ frequency: 38000 duty_cycle: 0.330000 data: 4512 4510 505 498 507 499 505 498 507 499 506 498 507 1503 507 498 506 498 507 498 507 499 506 498 506 499 506 499 505 499 506 499 505 499 506 4505 505 1504 505 1505 505 1504 483 520 507 500 482 1527 504 499 507 500 482 1527 482 1527 482 520 507 500 483 1527 482 521 484 1526 484 1526 505 499 484 521 483 1527 483 1526 483 # -name: Fast_ba -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 4512 4509 537 470 535 468 537 470 535 468 537 470 535 1475 535 468 537 470 535 469 536 470 535 468 537 469 536 469 536 469 536 469 536 469 536 4477 534 1476 534 1476 534 1475 535 468 537 470 535 1476 534 468 537 470 535 1475 535 468 537 470 535 468 537 1472 538 471 534 1476 534 1475 535 468 537 1473 536 1473 537 1473 536 -# name: Play type: raw frequency: 38000 @@ -405,6 +411,106 @@ frequency: 38000 duty_cycle: 0.330000 data: 4510 4508 537 470 535 467 538 470 535 468 537 471 534 1475 534 467 538 470 535 467 538 469 536 467 538 470 535 467 538 470 535 468 537 469 536 4472 537 1472 537 1472 537 1472 537 470 535 467 538 470 535 1475 534 1475 534 1474 535 467 538 470 535 468 537 1472 537 1473 536 469 536 468 537 469 536 1474 535 1473 536 1473 536 # +# Model: Samsung BDD7500 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4488 4496 566 432 567 432 567 431 568 432 567 432 514 1480 567 432 567 433 566 432 567 433 566 432 567 432 567 431 568 432 567 432 567 433 566 4427 567 1428 566 1427 567 1428 566 432 567 433 566 432 567 433 566 432 567 432 567 433 566 432 567 433 566 1346 648 1428 566 1427 567 1428 566 1428 566 1428 566 1428 566 1428 566 +# +name: Eject +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4488 4498 460 537 462 536 463 536 463 537 462 536 463 1532 462 536 463 536 463 536 463 536 463 537 462 536 463 536 463 537 462 536 463 537 462 4532 462 1532 462 1532 462 1532 462 537 462 1531 463 536 463 536 463 536 463 536 463 536 463 537 462 537 462 536 463 1531 463 1531 463 1532 462 1532 462 1532 462 1531 544 1450 463 +# +name: Ok +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4487 4497 462 538 461 538 461 537 462 538 461 537 462 1532 462 538 461 538 461 538 461 505 494 537 462 537 462 538 461 537 462 537 462 538 461 4533 461 1532 462 1534 460 1532 462 479 520 538 461 537 462 1532 462 1533 461 1532 462 538 461 538 461 538 461 1533 461 1533 461 537 462 538 461 537 462 1532 462 1533 461 1533 461 +# +name: Fast_fo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4488 4496 463 536 463 536 463 536 463 536 463 537 462 1532 462 536 463 538 461 537 462 537 462 536 463 538 461 537 462 537 462 536 463 537 462 4532 462 1532 462 1532 462 1532 462 536 463 1530 464 537 462 1532 462 536 463 1531 463 537 462 537 462 537 462 536 463 1531 463 504 495 1531 463 537 462 1532 462 1531 463 1532 462 +# +name: Fast_ba +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4489 4496 514 485 514 484 515 485 514 485 514 484 515 1479 515 485 514 484 515 485 514 485 514 485 514 478 521 485 514 484 515 484 515 484 515 4482 512 1480 514 1479 515 1480 514 485 514 486 513 1480 514 485 514 485 514 1479 515 485 514 485 514 485 514 1481 513 485 514 1480 514 1480 514 486 513 1479 515 1481 513 1481 513 +# +name: Pause +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4489 4496 487 513 486 513 486 513 486 513 486 513 486 1507 487 512 487 484 515 513 486 513 486 513 486 513 486 514 485 513 486 513 486 513 486 4510 486 1506 486 1507 487 1509 485 513 486 513 486 1508 486 512 487 513 486 1509 485 1508 486 514 485 512 487 1508 486 513 486 1509 485 1509 485 515 484 513 486 1509 485 1509 485 +# +name: Play +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4486 4498 460 539 460 538 461 538 461 539 460 538 461 1534 460 539 460 538 461 538 461 542 457 538 461 538 461 539 460 539 460 538 461 538 461 4534 483 1510 461 1533 461 1533 461 538 461 537 462 537 462 1533 461 538 461 1533 461 537 462 537 462 538 461 1532 462 1532 462 538 461 1532 462 537 462 1533 461 1533 461 1532 462 +# +name: Subtitle +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4489 4495 514 428 571 485 514 486 513 486 513 486 513 1482 512 487 512 488 511 486 488 510 489 512 487 511 488 511 488 512 487 506 493 512 487 4507 487 1507 487 1507 487 1508 486 510 489 513 486 1506 488 1507 487 513 486 458 541 1507 487 512 487 512 487 1507 487 512 487 512 487 1507 487 1506 488 512 487 1426 568 1506 488 +# +# Model: Samsung BDE5300 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4490 4479 514 482 516 483 515 482 516 482 516 482 516 1456 515 483 515 483 515 484 514 483 515 483 515 513 485 484 514 483 515 484 514 481 517 4451 515 1457 514 1486 485 1457 514 483 515 484 514 482 516 482 516 484 514 482 516 482 516 483 515 482 516 1456 515 1458 513 1455 516 1456 515 1456 515 1456 515 1456 515 1456 515 +# +name: Eject +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4464 4505 461 537 461 536 462 536 462 537 461 537 461 1510 461 537 461 537 461 537 461 536 462 537 461 536 462 537 461 537 461 536 462 537 461 4505 462 1509 462 1510 461 1508 463 537 461 1509 462 537 461 537 461 538 460 537 461 536 462 536 462 537 461 536 462 1510 461 1510 461 1509 462 1509 462 1509 462 1509 462 1510 461 57969 4462 4502 461 537 461 536 462 537 461 537 461 536 462 1510 461 536 462 537 461 536 462 464 534 536 462 537 461 536 462 536 462 535 463 536 462 4505 461 1510 461 1509 462 1510 461 537 461 1509 462 536 462 536 462 536 462 535 463 537 461 536 462 536 462 536 462 1509 462 1509 462 1509 462 1510 461 1509 462 1509 462 1510 461 +# +name: Ok +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4464 4505 461 508 490 536 462 537 461 537 461 537 461 1508 463 537 461 537 461 540 458 536 462 537 461 536 462 536 462 537 461 538 460 536 462 4506 460 1511 460 1509 462 1510 461 537 461 537 461 536 462 1510 461 1508 463 1512 459 537 461 536 462 535 463 1509 462 1510 461 535 463 537 461 537 461 1510 461 1511 460 1510 461 57969 4462 4502 461 537 461 537 461 539 459 536 462 537 461 1510 461 536 462 537 461 537 461 536 462 537 461 537 461 537 461 538 460 536 462 538 460 4505 461 1510 461 1510 461 1511 460 537 461 537 461 521 477 1510 461 1511 460 1510 461 537 461 537 461 537 461 1510 461 1510 461 537 461 538 460 537 461 1509 462 1509 462 1510 461 +# +name: Fast_fo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4460 4506 460 537 461 537 461 537 461 538 460 538 460 1511 460 538 460 539 459 539 459 538 460 538 460 537 461 540 458 538 460 538 460 538 460 4506 460 1512 459 1511 460 1510 461 538 460 1510 461 537 461 1511 460 537 461 1509 462 537 461 536 462 537 461 538 460 1509 462 537 461 1511 460 536 462 1511 460 1423 548 1510 461 57968 4463 4503 460 537 461 537 461 537 461 537 461 535 463 1509 462 536 462 537 461 537 461 536 462 538 460 537 461 464 534 536 462 536 462 537 461 4504 462 1510 461 1510 461 1510 461 536 462 1510 461 535 463 1509 462 537 461 1509 462 535 463 536 462 535 463 537 461 1510 461 536 462 1510 461 537 461 1509 462 1509 462 1509 462 +# +name: Fast_ba +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4461 4504 462 537 461 537 461 537 461 537 461 537 461 1510 461 537 461 537 461 537 461 537 461 538 460 536 462 537 461 536 462 536 462 537 461 4505 461 1510 461 1510 461 1511 460 536 462 537 461 1511 460 536 462 536 462 1510 461 537 461 537 461 537 461 1509 462 538 460 1510 461 1510 461 536 462 1510 461 1510 461 1509 462 57968 4464 4501 462 536 462 536 462 537 461 536 462 536 462 1510 461 537 461 536 462 536 462 536 462 537 461 535 463 536 462 535 463 537 461 537 461 4503 463 1510 461 1511 460 1509 462 537 461 538 460 1510 461 535 463 537 461 1509 462 536 462 537 461 536 462 1511 460 537 461 1509 462 1510 461 537 461 1510 461 1509 462 1510 461 +# +name: Pause +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4463 4505 461 536 462 537 461 537 461 536 462 536 462 1511 460 536 462 536 462 536 462 537 461 536 462 537 461 537 461 537 461 537 461 537 461 4505 461 1512 462 1508 461 1508 463 537 461 536 462 1510 461 537 461 537 461 1510 461 1509 462 537 461 536 462 1509 462 535 560 1353 521 1510 461 538 460 536 462 1509 462 1511 460 57968 4463 4502 461 536 462 537 461 536 462 538 460 538 460 1509 462 516 482 536 462 536 462 536 462 537 461 537 461 536 462 536 462 537 461 537 461 4507 461 1508 461 1513 458 1508 463 537 461 536 462 1510 461 536 462 536 462 1510 461 1509 462 536 462 537 461 1509 462 537 461 1510 461 1510 461 537 461 538 460 1510 461 1509 462 +# +name: Play +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4462 4508 481 514 484 518 480 516 482 515 483 514 484 1488 461 538 460 539 459 537 461 538 460 538 460 537 461 538 460 539 459 539 459 538 460 4507 459 1511 460 1512 459 1511 460 537 461 537 461 536 462 1511 460 538 460 1509 462 537 461 538 460 538 461 1509 461 1508 463 537 461 1510 461 538 460 1510 461 1510 461 1510 461 57968 4464 4501 462 536 462 536 462 537 461 535 463 475 523 1508 463 536 462 536 462 537 461 537 461 537 461 537 461 536 462 537 461 536 462 538 460 4505 461 1509 462 1509 462 1509 462 537 461 536 462 537 461 1509 462 536 462 1509 462 537 461 536 462 537 461 1509 462 1509 462 536 462 1510 461 536 462 1510 461 1509 462 1510 461 +# +name: Subtitle +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4489 4478 516 481 517 483 515 483 515 482 516 484 514 1456 515 481 517 484 514 482 516 484 514 483 515 483 515 481 517 482 516 367 632 482 516 4451 515 1457 514 1457 514 1455 516 483 515 483 515 1456 515 1412 559 483 515 484 514 1456 515 483 515 486 512 1458 513 483 515 482 516 1456 515 1455 516 484 514 1457 514 1457 514 57918 4486 4476 514 484 514 483 515 484 514 484 514 484 514 1458 513 483 515 483 515 484 514 484 514 483 515 484 514 483 515 483 515 484 514 483 515 4452 514 1457 514 1457 514 1456 515 482 516 484 514 1457 514 1457 514 484 514 486 512 1456 515 483 515 484 514 1456 515 485 513 484 514 1457 514 1457 514 483 515 1457 514 1456 515 +# # Model: Sanyo Bluray_NC088 # name: Power @@ -425,12 +531,6 @@ protocol: NECext address: 87 22 00 00 command: 72 8D 00 00 # -name: Fast_ba -type: parsed -protocol: NECext -address: 87 22 00 00 -command: 52 AD 00 00 -# name: Play type: parsed protocol: NECext @@ -507,12 +607,6 @@ protocol: SIRC20 address: 5A 1C 00 00 command: 15 00 00 00 # -name: Ok -type: parsed -protocol: SIRC20 -address: 5A 1C 00 00 -command: 3D 00 00 00 -# name: Play type: parsed protocol: SIRC20 @@ -525,6 +619,24 @@ protocol: SIRC20 address: 5A 1C 00 00 command: 19 00 00 00 # +name: Subtitle +type: parsed +protocol: SIRC20 +address: 5A 1C 00 00 +command: 63 00 00 00 +# +name: Ok +type: parsed +protocol: SIRC20 +address: 5A 1C 00 00 +command: 3D 00 00 00 +# +name: Eject +type: parsed +protocol: SIRC20 +address: 5A 1C 00 00 +command: 16 00 00 00 +# name: Fast_fo type: parsed protocol: SIRC20 @@ -537,18 +649,6 @@ protocol: SIRC20 address: 5A 1C 00 00 command: 1B 00 00 00 # -name: Eject -type: parsed -protocol: SIRC20 -address: 5A 1C 00 00 -command: 16 00 00 00 -# -name: Subtitle -type: parsed -protocol: SIRC20 -address: 5A 1C 00 00 -command: 63 00 00 00 -# # Model: Toshiba SE-R0398 # name: Power @@ -575,18 +675,6 @@ protocol: NECext address: 45 B5 00 00 command: C2 3D 00 00 # -name: Fast_ba -type: parsed -protocol: NECext -address: 45 B5 00 00 -command: 19 E6 00 00 -# -name: Fast_fo -type: parsed -protocol: NECext -address: 45 B5 00 00 -command: 13 EC 00 00 -# name: Subtitle type: parsed protocol: NECext @@ -643,6 +731,12 @@ protocol: NEC address: 00 00 00 00 command: 5C 00 00 00 # +name: Play +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 33 00 00 00 +# # Model: Bose 3-2-1_Series_1 # name: Power @@ -651,12 +745,6 @@ protocol: NECext address: BA 4B 00 00 command: 4C B3 00 00 # -name: Ok -type: parsed -protocol: NECext -address: BA 4B 00 00 -command: E0 1F 00 00 -# name: Pause type: parsed protocol: NECext @@ -709,12 +797,6 @@ protocol: NEC address: 00 00 00 00 command: 03 00 00 00 # -name: Fast_ba -type: parsed -protocol: Samsung32 -address: 2D 00 00 00 -command: 32 00 00 00 -# # Model: LG DKS-6100Q # name: Power @@ -723,12 +805,6 @@ protocol: Samsung32 address: 10 00 00 00 command: 1E 00 00 00 # -name: Fast_ba -type: parsed -protocol: Samsung32 -address: 10 00 00 00 -command: B1 00 00 00 -# name: Play type: parsed protocol: Samsung32 @@ -741,30 +817,6 @@ frequency: 38000 duty_cycle: 0.330000 data: 3487 3438 903 833 877 859 878 860 877 2596 878 861 876 2599 875 860 877 2598 876 860 877 2596 878 860 877 861 876 2597 877 2597 905 2597 878 858 879 2597 878 859 878 2597 877 860 877 2598 877 858 879 2599 876 2597 904 33497 3487 3463 878 860 877 859 878 832 878 2624 878 832 905 2598 876 860 877 2598 876 860 877 2597 877 859 878 834 903 2597 877 2597 877 2598 904 834 876 2623 879 832 878 2627 875 832 878 2626 876 832 905 2599 875 2598 877 # -name: Fast_ba -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 3482 3466 878 860 877 860 877 833 877 2625 877 834 903 2598 876 860 877 833 877 2626 876 2597 877 860 877 834 903 2598 877 2597 877 2598 904 832 878 2626 876 833 877 2625 877 2598 876 859 878 832 905 2598 877 2598 876 33546 3483 3440 904 834 876 859 878 861 876 2596 878 861 876 2598 876 859 878 858 879 2597 877 2597 877 860 877 859 878 2597 877 2597 905 2596 879 858 879 2597 877 860 877 2596 878 2597 878 859 878 859 878 2597 877 2597 904 -# -name: Fast_fo -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 3484 3464 907 829 908 830 907 804 906 2596 906 804 933 2569 878 858 879 832 878 2622 880 831 879 858 879 860 877 2596 878 2623 879 2597 877 858 879 2597 877 857 880 2595 879 2594 880 857 880 2595 907 2595 879 2595 880 33544 3485 3437 907 831 906 831 906 830 907 2568 906 830 907 2567 907 831 906 830 907 2567 907 830 907 805 932 802 908 2594 908 2568 907 2567 907 832 905 2568 906 831 906 2568 879 2622 879 831 906 2596 878 2596 879 2596 906 -# -name: Eject -type: parsed -protocol: NEC -address: 49 00 00 00 -command: 1B 00 00 00 -# -name: Eject -type: parsed -protocol: Kaseikyo -address: B0 02 20 00 -command: 10 00 00 00 -# name: Subtitle type: parsed protocol: Kaseikyo @@ -819,44 +871,20 @@ command: 9E 00 00 00 # # Model: Prinz DVD_Player_T182 # -name: Eject -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 8345 4517 571 597 543 571 568 572 547 593 546 567 573 568 540 599 540 574 576 1678 570 1684 575 1679 600 1681 567 1686 573 1682 597 1683 576 1679 569 1685 594 1686 573 1681 567 600 550 564 576 564 545 1683 596 1685 574 566 574 567 541 598 542 1686 573 1682 597 1683 576 564 576 565 543 40175 8355 2264 575 -# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 8348 4516 572 570 569 544 595 545 574 566 574 540 599 541 567 572 567 547 603 1677 571 1684 575 1679 600 1681 567 1687 572 1682 597 1684 575 1679 569 1685 594 546 573 1681 598 542 577 563 576 538 601 1679 569 1685 574 566 573 1681 567 573 566 1688 571 1683 597 1685 574 540 599 540 568 40177 8374 2266 573 # -name: Ok -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 8368 4519 569 598 541 572 567 573 546 595 544 569 570 569 550 591 548 565 574 1679 569 1686 573 1681 598 1683 576 1678 570 1684 595 1686 573 1682 576 1677 602 1679 569 571 568 572 547 1681 598 568 540 600 550 1678 570 596 543 544 595 1685 573 1681 567 599 551 1677 571 1683 596 571 548 40170 8376 2268 571 -# name: Play type: raw frequency: 38000 duty_cycle: 0.330000 data: 8362 4520 567 599 540 547 603 564 544 595 544 543 596 544 575 592 547 566 573 1680 568 1685 574 1681 598 1709 550 1704 544 1710 569 1712 546 1708 540 1714 576 1705 543 544 595 1711 547 540 599 541 567 573 566 1687 571 569 570 544 595 1711 547 540 599 1681 567 1686 573 1682 597 570 549 40168 8377 2261 567 # -name: Fast_ba -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 8328 4524 563 592 527 569 550 545 553 596 533 565 554 568 530 595 534 566 553 1683 565 1684 564 1684 647 1624 562 1685 563 1685 646 1625 561 1687 561 598 531 567 552 571 527 1687 592 567 531 596 533 567 552 1687 572 1678 570 1680 589 1685 563 572 557 1681 619 1630 566 1684 595 565 533 40169 8337 2267 572 -# # Model: Sony DVD_RMT-D197A # -name: Eject -type: parsed -protocol: SIRC20 -address: 3A 09 00 00 -command: 16 00 00 00 -# name: Power type: parsed protocol: SIRC20 @@ -869,24 +897,6 @@ protocol: SIRC20 address: 3A 09 00 00 command: 63 00 00 00 # -name: Ok -type: parsed -protocol: SIRC20 -address: 3A 09 00 00 -command: 0B 00 00 00 -# -name: Fast_fo -type: parsed -protocol: SIRC20 -address: 3A 09 00 00 -command: 23 00 00 00 -# -name: Fast_ba -type: parsed -protocol: SIRC20 -address: 3A 09 00 00 -command: 22 00 00 00 -# name: Play type: parsed protocol: SIRC20 @@ -913,18 +923,6 @@ protocol: NEC address: 00 00 00 00 command: 1E 00 00 00 # -name: Play -type: parsed -protocol: NEC -address: 00 00 00 00 -command: 1D 00 00 00 -# -name: Ok -type: parsed -protocol: NEC -address: 00 00 00 00 -command: 0E 00 00 00 -# # Model: Toshiba SE_R0108 # name: Power @@ -951,18 +949,6 @@ protocol: NEC address: 45 00 00 00 command: 15 00 00 00 # -name: Fast_ba -type: parsed -protocol: NEC -address: 45 00 00 00 -command: 19 00 00 00 -# -name: Ok -type: parsed -protocol: NEC -address: 45 00 00 00 -command: 21 00 00 00 -# # Model: TOSHIBA SE_R0420 # name: Power @@ -999,18 +985,6 @@ protocol: NEC address: 00 00 00 00 command: 53 00 00 00 # -name: Fast_ba -type: parsed -protocol: NEC -address: 00 00 00 00 -command: 18 00 00 00 -# -name: Fast_fo -type: parsed -protocol: NEC -address: 00 00 00 00 -command: 5F 00 00 00 -# # Model: APEX RM_3800 # name: Power @@ -1025,18 +999,6 @@ protocol: NEC address: 01 00 00 00 command: 19 00 00 00 # -name: Ok -type: parsed -protocol: NEC -address: 01 00 00 00 -command: 13 00 00 00 -# -name: Fast_ba -type: parsed -protocol: NEC -address: 01 00 00 00 -command: 07 00 00 00 -# # Model: RVR-4000 # name: Power @@ -1045,12 +1007,6 @@ protocol: NEC address: 35 00 00 00 command: 1D 00 00 00 # -name: Fast_ba -type: parsed -protocol: NEC -address: 35 00 00 00 -command: 19 00 00 00 -# name: Play type: parsed protocol: NEC @@ -1063,12 +1019,6 @@ protocol: NEC address: 35 00 00 00 command: 1B 00 00 00 # -name: Fast_fo -type: parsed -protocol: NEC -address: 35 00 00 00 -command: 18 00 00 00 -# # Model: JVC HR-A591U # name: Power @@ -1089,24 +1039,6 @@ frequency: 38000 duty_cycle: 0.330000 data: 8414 4228 507 1601 506 1600 507 547 506 548 505 546 507 547 506 1601 506 547 506 547 506 546 507 1600 507 1599 508 547 506 547 506 546 507 546 507 23151 505 1600 507 1601 506 547 506 547 506 547 506 547 506 1600 507 546 507 547 506 547 506 1600 507 1600 507 546 507 548 505 547 506 546 507 23150 506 1600 507 1601 506 545 508 547 506 547 506 545 508 1601 506 548 505 547 506 546 507 1599 508 1600 507 547 506 547 506 546 507 547 506 # -name: Fast_ba -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 8416 4228 507 1601 506 1601 506 549 504 546 507 547 506 547 506 1602 505 548 505 1602 505 1601 506 1599 508 547 506 546 507 547 506 547 506 546 507 22096 507 1600 507 1601 506 548 505 546 507 548 505 548 505 1603 504 547 506 1599 508 1600 507 1601 506 547 506 548 505 547 506 546 507 546 507 22096 506 1600 507 1600 507 546 507 547 506 547 506 546 507 1600 507 546 507 1601 506 1600 507 1601 506 546 507 547 506 546 507 548 505 547 506 -# -name: Fast_fo -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 8414 4229 506 1601 506 1601 506 547 506 547 506 546 507 546 507 1600 507 546 507 546 507 1601 506 1602 505 546 507 546 507 547 506 547 506 546 507 23151 505 1600 507 1601 506 546 507 546 507 546 507 547 506 1601 506 547 506 547 506 1601 506 1600 507 546 507 545 508 547 506 547 506 547 506 23149 507 1601 506 1601 506 546 507 546 507 546 507 547 506 1599 508 547 506 547 506 1602 505 1600 507 547 506 547 506 547 506 547 506 546 507 -# -name: Ok -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 8414 4230 506 1600 507 1601 506 547 506 545 508 547 506 546 507 1600 507 547 506 546 507 546 507 1600 507 1601 506 1599 508 1599 508 547 506 546 507 21043 506 1599 508 1599 508 547 506 546 507 547 506 546 507 1600 507 547 506 547 506 546 507 1600 507 1600 507 1601 506 1600 507 546 507 547 506 21043 506 1599 508 1601 506 546 507 546 507 546 507 546 507 1601 506 547 506 546 507 547 506 1601 506 1601 506 1601 506 1600 507 546 507 547 506 -# # Model: JVC HR-J700E # name: Power @@ -1127,12 +1059,6 @@ frequency: 38000 duty_cycle: 0.330000 data: 8511 4145 626 1488 624 1486 626 430 626 433 623 430 626 429 627 1487 625 430 626 1485 627 429 627 1484 627 1485 627 429 626 429 627 429 627 429 626 21132 626 1486 626 1485 626 430 626 430 626 429 626 429 627 1488 623 429 627 1487 624 429 627 1485 627 1487 624 431 625 430 625 429 627 429 627 22224 626 1487 625 1484 628 428 627 430 626 430 625 429 627 1487 625 431 624 1486 626 430 625 1487 625 1485 627 430 625 429 626 429 627 429 626 22224 625 1485 627 1486 625 431 625 430 626 429 627 429 626 1487 625 431 624 1485 626 429 627 1487 624 1485 627 430 625 431 625 430 625 429 626 # -name: Fast_ba -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 8509 4148 547 1566 545 1565 547 508 547 509 547 506 550 508 548 1564 548 508 548 1565 547 1565 547 1565 547 507 549 507 549 507 549 508 548 508 548 21187 546 1565 547 1566 546 508 548 507 549 508 547 508 548 1564 548 509 546 1563 549 1562 549 1565 547 508 548 507 549 507 549 508 548 507 549 22302 547 1565 547 1565 547 509 546 507 548 507 549 507 549 1564 548 508 547 1565 547 1564 548 1563 549 509 547 507 548 508 548 506 549 508 548 22303 546 1565 547 1565 546 508 548 509 547 507 549 509 547 1564 547 510 546 1564 548 1566 546 1566 546 507 548 509 547 508 548 509 547 507 549 22302 547 1565 546 1564 548 508 548 508 547 507 549 508 548 1564 548 509 547 1563 549 1564 547 1564 548 508 547 508 548 507 549 507 549 508 548 22302 547 1564 548 1563 549 508 548 507 548 508 548 511 545 1567 545 510 546 1564 548 1565 547 1564 548 507 626 429 549 508 548 509 624 431 547 -# name: Ok type: raw frequency: 38000 @@ -1147,18 +1073,6 @@ frequency: 38000 duty_cycle: 0.330000 data: 8371 4179 504 1565 505 1591 504 542 506 568 506 569 505 544 504 1592 504 570 504 1565 505 1591 504 544 503 1591 505 571 503 543 505 569 505 568 506 22944 504 1565 504 1590 505 544 504 569 505 568 506 542 505 1591 505 569 505 1565 504 1592 504 544 503 1591 505 570 504 543 505 569 505 569 505 22917 505 1590 505 1564 505 569 505 572 502 543 505 570 504 1565 504 570 504 1592 504 1565 504 571 503 1591 505 544 504 568 506 542 506 570 504 # -name: Fast_ba -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 8376 4179 506 1564 506 1593 503 541 507 570 504 568 506 542 506 1591 505 569 505 1566 504 1592 504 1566 504 570 504 570 504 542 506 570 504 568 506 22873 505 1591 505 1566 504 570 504 569 505 543 505 570 504 1565 505 569 505 1590 506 1565 505 1590 506 568 506 543 505 568 506 543 505 569 505 22873 505 1591 505 1564 506 570 504 569 505 543 505 569 505 1565 505 568 506 1592 504 1566 504 1591 505 569 505 542 506 568 506 543 505 569 505 -# -name: Fast_fo -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 8373 4181 503 1567 503 1591 506 543 505 569 505 569 505 542 506 1592 505 569 505 542 506 1591 505 1565 505 569 505 569 505 543 505 569 505 570 504 23897 503 1592 504 1566 504 569 505 569 505 543 505 569 505 1565 505 569 505 569 505 1566 504 1592 505 569 505 542 506 568 506 542 506 568 506 -# name: Play type: raw frequency: 38000 @@ -1217,12 +1131,6 @@ frequency: 38000 duty_cycle: 0.330000 data: 3518 3478 902 859 909 860 908 839 902 2601 908 859 909 2581 901 862 906 2601 908 839 901 2602 907 861 907 839 901 2600 909 2601 908 2580 902 859 909 2601 908 839 901 2601 908 860 907 2581 901 860 908 2603 906 2580 902 34025 3518 3473 907 860 908 840 900 860 908 2602 907 840 900 2601 908 859 909 2582 900 861 907 2602 907 839 902 859 909 2602 907 2581 901 2601 908 860 908 2581 901 858 910 2601 908 840 901 2602 907 860 908 2582 900 2602 907 # -name: Fast_ba -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 3511 3500 932 815 925 833 935 833 935 2556 926 835 933 2575 934 813 927 835 933 2577 932 2555 927 834 934 835 933 2554 928 2575 934 2576 933 814 926 2577 932 833 935 2556 926 2575 934 835 933 814 926 2575 934 2575 934 33973 3519 3479 899 860 934 834 908 842 898 2602 907 860 908 2583 899 861 907 860 908 2584 898 2602 907 862 906 841 899 2602 933 2577 932 2556 926 836 932 2575 934 814 927 2576 933 2576 933 814 926 834 934 2575 934 2555 927 -# name: Pause type: raw frequency: 38000 @@ -1237,12 +1145,6 @@ protocol: Kaseikyo address: 90 02 20 00 command: A0 00 00 00 # -name: Fast_ba -type: parsed -protocol: Kaseikyo -address: 90 02 20 00 -command: 20 00 00 00 -# name: Power type: parsed protocol: Kaseikyo @@ -1269,12 +1171,6 @@ frequency: 38000 duty_cycle: 0.330000 data: 3538 1702 463 407 461 1273 463 406 462 405 463 405 463 405 463 405 487 382 487 407 460 408 459 410 457 411 457 411 457 1280 457 411 457 411 457 411 457 411 457 411 457 411 457 1280 457 411 457 412 456 1280 457 412 457 412 456 411 457 411 457 412 456 412 456 412 456 412 456 1280 457 412 456 1281 456 1281 456 1281 456 1281 456 412 456 412 456 1281 456 413 455 1281 456 1282 455 413 455 1282 455 413 455 1282 455 74229 3532 1709 457 411 457 1280 457 411 457 412 456 412 456 412 457 411 457 412 456 412 456 412 456 412 456 412 456 412 456 1281 456 412 457 412 456 412 456 412 456 412 456 412 456 1281 456 412 456 412 456 1281 456 413 455 413 455 413 455 413 455 413 455 413 455 413 455 413 455 1282 455 413 455 1282 455 1282 455 1282 455 1282 455 414 454 414 454 1283 454 414 454 1283 454 1283 454 414 454 1283 454 414 454 1283 454 # -name: Ok -type: parsed -protocol: Kaseikyo -address: 91 02 20 01 -command: 80 01 00 00 -# # Model: Panasonic VCR_PV9662 # name: Power @@ -1295,30 +1191,12 @@ frequency: 38000 duty_cycle: 0.330000 data: 3518 1687 446 421 449 1284 445 422 448 419 451 416 454 413 446 421 449 418 452 415 444 422 448 419 451 416 454 413 446 1287 453 414 445 422 448 419 451 416 454 413 446 420 450 1284 445 421 449 418 452 1282 447 420 450 417 453 414 445 421 449 418 452 415 444 423 447 420 450 417 453 1281 449 418 452 1281 449 419 451 416 454 413 446 421 449 418 452 1282 448 419 451 1283 446 1287 453 414 445 422 448 1286 454 74506 3516 1689 444 423 447 1287 453 414 445 422 448 418 452 415 444 423 447 420 450 417 453 414 445 421 449 418 452 415 444 1290 450 416 454 413 446 421 449 418 452 415 444 422 448 1286 454 413 446 420 450 1284 446 422 448 419 451 416 443 423 447 420 450 417 453 414 445 421 449 418 452 1282 448 419 451 1283 447 420 450 417 453 415 444 422 448 419 451 1283 447 420 450 1284 446 1288 452 415 444 423 447 1286 454 # -name: Fast_ba -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 3514 1691 453 414 445 1287 453 415 444 422 448 419 451 417 453 413 446 421 449 418 452 415 444 422 448 419 451 416 454 1279 451 417 453 414 445 421 449 418 452 415 444 423 447 1286 454 413 446 421 449 1284 445 422 448 419 451 416 454 413 446 421 449 417 453 414 445 422 448 419 451 1282 448 419 451 416 454 414 445 421 449 418 452 415 444 422 448 1285 455 412 447 420 450 1283 447 421 449 418 452 1281 449 74543 3516 1689 445 423 447 1286 454 413 446 421 449 418 452 415 444 422 448 419 451 416 443 423 447 420 450 417 453 414 445 1288 452 415 444 422 448 419 451 416 454 413 446 420 450 1284 446 421 449 418 452 1282 448 419 451 416 454 413 446 421 449 418 452 414 445 422 448 419 451 416 443 1289 451 416 454 413 446 421 449 418 452 415 444 422 448 419 451 1282 448 420 450 417 453 1280 450 418 452 415 444 1288 452 74540 3519 1686 448 419 451 1282 448 420 450 417 453 414 445 422 448 419 451 415 444 423 447 420 450 417 453 414 445 421 449 1284 446 422 448 419 451 416 454 413 446 420 450 417 453 1281 449 418 452 415 444 1289 451 416 454 413 446 421 449 418 452 415 444 422 448 419 451 416 443 423 447 1286 454 413 446 421 449 418 452 415 444 422 448 419 451 416 454 1279 451 417 453 414 445 1287 453 415 444 422 448 1286 454 -# -name: Fast_fo -type: raw -frequency: 38000 -duty_cycle: 0.330000 -data: 3513 1687 447 421 449 1311 419 421 449 419 451 416 454 413 446 421 449 418 452 414 445 422 448 419 451 416 454 413 446 1286 454 413 446 421 449 418 452 415 455 412 447 420 450 1282 448 420 450 417 453 1281 449 418 452 416 454 413 446 420 450 417 453 414 445 422 448 419 451 1281 449 1286 454 413 446 421 449 418 452 415 444 422 448 419 451 1282 448 1286 454 413 446 421 449 1284 446 422 448 419 451 1282 448 74517 3509 1691 453 414 445 1316 424 416 454 413 446 420 450 417 453 414 445 422 448 419 451 416 454 413 446 420 450 417 453 1280 450 417 453 415 444 422 448 419 451 416 454 413 446 1286 454 414 445 421 449 1285 445 422 448 420 450 417 453 414 445 421 449 418 452 415 444 423 447 1285 455 1279 451 417 453 414 445 422 448 419 451 416 454 413 446 1286 454 1280 450 418 452 415 455 1278 452 416 454 413 446 1287 453 74511 3515 1685 449 419 451 1282 448 420 450 417 453 414 445 422 448 418 452 415 455 412 447 420 450 417 453 414 445 421 449 1284 446 421 449 419 451 416 454 413 446 420 450 417 453 1280 450 418 452 415 444 1288 452 416 454 413 446 421 449 418 452 415 444 422 448 419 451 416 454 1279 451 1283 447 420 450 418 452 415 444 422 448 419 451 416 454 1279 451 1284 446 421 449 419 451 1309 421 419 451 417 453 1280 450 74543 3515 1690 454 413 446 1314 426 414 445 422 448 419 451 416 454 413 446 420 450 417 453 414 445 421 449 418 452 415 444 1288 452 416 454 413 446 420 450 417 453 414 445 422 448 1285 445 422 448 420 450 1283 447 420 450 418 452 415 444 422 448 419 451 416 454 413 446 420 450 1283 447 1287 453 415 455 412 447 420 450 417 453 414 445 421 449 1284 446 1288 452 416 454 413 446 1314 426 414 445 422 448 1312 428 74533 3513 1687 447 421 449 1312 418 422 448 419 451 416 454 414 445 421 449 418 452 415 444 422 448 419 451 416 454 413 446 1286 454 413 446 421 449 418 452 415 455 412 447 420 450 1283 447 420 450 418 452 1308 422 418 452 416 454 413 446 420 450 417 453 414 445 422 448 419 451 1281 449 1313 427 413 446 421 449 418 452 415 444 422 448 419 451 1282 448 1286 454 414 445 422 448 1312 418 422 448 420 450 1310 420 -# name: Pause type: raw frequency: 38000 duty_cycle: 0.330000 data: 3510 1694 450 416 454 1280 450 417 453 414 445 422 448 418 452 415 455 412 447 420 450 416 454 413 446 420 450 417 453 1282 448 419 451 416 454 412 447 420 450 417 453 414 445 1288 452 415 444 422 448 1286 454 413 446 420 450 417 453 414 445 422 448 418 452 415 455 412 447 419 451 1284 446 1288 452 414 445 422 448 419 451 416 454 413 446 420 450 1284 446 1288 452 415 444 1290 450 416 454 413 446 1288 452 74520 3511 1688 446 422 448 1286 454 412 447 420 450 416 454 413 446 421 449 418 452 415 444 422 448 419 451 415 455 412 447 1287 453 414 445 421 449 418 452 415 444 422 448 419 451 1283 447 420 450 417 453 1281 449 418 452 415 444 422 448 419 451 416 454 413 446 420 450 417 453 414 445 1289 451 1283 447 420 450 417 453 414 445 421 449 418 452 415 455 1279 451 1284 446 421 449 1285 445 422 448 419 451 1283 447 # -name: Fast_fo -type: parsed -protocol: Kaseikyo -address: 90 02 20 00 -command: 30 00 00 00 -# # Model: Philips-RC2K16 # name: Power @@ -1347,24 +1225,12 @@ protocol: SIRC address: 0B 00 00 00 command: 16 00 00 00 # -name: Ok -type: parsed -protocol: SIRC -address: 01 00 00 00 -command: 0B 00 00 00 -# name: Pause type: parsed protocol: SIRC address: 0B 00 00 00 command: 19 00 00 00 # -name: Fast_ba -type: parsed -protocol: SIRC -address: 0B 00 00 00 -command: 1B 00 00 00 -# name: Play type: parsed protocol: SIRC @@ -1386,3 +1252,646 @@ type: parsed protocol: NEC address: 7F 00 00 00 command: 00 00 00 00 +# +name: Ok +type: parsed +protocol: Samsung32 +address: 2D 00 00 00 +command: 58 00 00 00 +# +name: Ok +type: parsed +protocol: Samsung32 +address: 07 00 00 00 +command: 68 00 00 00 +# +name: Fast_fo +type: parsed +protocol: Samsung32 +address: 07 00 00 00 +command: 48 00 00 00 +# +name: Fast_ba +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4512 4509 537 470 535 468 537 470 535 468 537 470 535 1475 535 468 537 470 535 469 536 470 535 468 537 469 536 469 536 469 536 469 536 469 536 4477 534 1476 534 1476 534 1475 535 468 537 470 535 1476 534 468 537 470 535 1475 535 468 537 470 535 468 537 1472 538 471 534 1476 534 1475 535 468 537 1473 536 1473 537 1473 536 +# +name: Fast_ba +type: parsed +protocol: NECext +address: 87 22 00 00 +command: 52 AD 00 00 +# +name: Fast_fo +type: parsed +protocol: NECext +address: 87 22 00 00 +command: 53 AC 00 00 +# +name: Fast_ba +type: parsed +protocol: NECext +address: 45 B5 00 00 +command: 19 E6 00 00 +# +name: Fast_fo +type: parsed +protocol: NECext +address: 45 B5 00 00 +command: 13 EC 00 00 +# +name: Ok +type: parsed +protocol: NECext +address: BA 4B 00 00 +command: E0 1F 00 00 +# +# Model: Brandt DVDP-7R +# +name: Subtitle +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 50 00 00 00 +# +name: Fast_fo +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 02 00 00 00 +# +name: Fast_ba +type: parsed +protocol: Samsung32 +address: 2D 00 00 00 +command: 32 00 00 00 +# +name: Fast_ba +type: parsed +protocol: Samsung32 +address: 10 00 00 00 +command: B1 00 00 00 +# +name: Fast_ba +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3482 3466 878 860 877 860 877 833 877 2625 877 834 903 2598 876 860 877 833 877 2626 876 2597 877 860 877 834 903 2598 877 2597 877 2598 904 832 878 2626 876 833 877 2625 877 2598 876 859 878 832 905 2598 877 2598 876 33546 3483 3440 904 834 876 859 878 861 876 2596 878 861 876 2598 876 859 878 858 879 2597 877 2597 877 860 877 859 878 2597 877 2597 905 2596 879 858 879 2597 877 860 877 2596 878 2597 878 859 878 859 878 2597 877 2597 904 +# +name: Fast_fo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3484 3464 907 829 908 830 907 804 906 2596 906 804 933 2569 878 858 879 832 878 2622 880 831 879 858 879 860 877 2596 878 2623 879 2597 877 858 879 2597 877 857 880 2595 879 2594 880 857 880 2595 907 2595 879 2595 880 33544 3485 3437 907 831 906 831 906 830 907 2568 906 830 907 2567 907 831 906 830 907 2567 907 830 907 805 932 802 908 2594 908 2568 907 2567 907 832 905 2568 906 831 906 2568 879 2622 879 831 906 2596 878 2596 879 2596 906 +# +# Model: Prinz DVD_Player_T182 +# +name: Eject +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8345 4517 571 597 543 571 568 572 547 593 546 567 573 568 540 599 540 574 576 1678 570 1684 575 1679 600 1681 567 1686 573 1682 597 1683 576 1679 569 1685 594 1686 573 1681 567 600 550 564 576 564 545 1683 596 1685 574 566 574 567 541 598 542 1686 573 1682 597 1683 576 564 576 565 543 40175 8355 2264 575 +# +name: Ok +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8368 4519 569 598 541 572 567 573 546 595 544 569 570 569 550 591 548 565 574 1679 569 1686 573 1681 598 1683 576 1678 570 1684 595 1686 573 1682 576 1677 602 1679 569 571 568 572 547 1681 598 568 540 600 550 1678 570 596 543 544 595 1685 573 1681 567 599 551 1677 571 1683 596 571 548 40170 8376 2268 571 +# +name: Fast_ba +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8328 4524 563 592 527 569 550 545 553 596 533 565 554 568 530 595 534 566 553 1683 565 1684 564 1684 647 1624 562 1685 563 1685 646 1625 561 1687 561 598 531 567 552 571 527 1687 592 567 531 596 533 567 552 1687 572 1678 570 1680 589 1685 563 572 557 1681 619 1630 566 1684 595 565 533 40169 8337 2267 572 +# +# Model: Sony DVD_RMT-D197A +# +name: Eject +type: parsed +protocol: SIRC20 +address: 3A 09 00 00 +command: 16 00 00 00 +# +name: Ok +type: parsed +protocol: SIRC20 +address: 3A 09 00 00 +command: 0B 00 00 00 +# +name: Fast_fo +type: parsed +protocol: SIRC20 +address: 3A 09 00 00 +command: 23 00 00 00 +# +name: Fast_ba +type: parsed +protocol: SIRC20 +address: 3A 09 00 00 +command: 22 00 00 00 +# +name: Play +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 1D 00 00 00 +# +name: Ok +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 0E 00 00 00 +# +name: Fast_ba +type: parsed +protocol: NEC +address: 45 00 00 00 +command: 19 00 00 00 +# +name: Fast_fo +type: parsed +protocol: NEC +address: 45 00 00 00 +command: 13 00 00 00 +# +name: Ok +type: parsed +protocol: NEC +address: 45 00 00 00 +command: 21 00 00 00 +# +name: Fast_ba +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 18 00 00 00 +# +name: Fast_fo +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 5F 00 00 00 +# +name: Ok +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 13 00 00 00 +# +name: Fast_ba +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 07 00 00 00 +# +name: Fast_ba +type: parsed +protocol: NEC +address: 35 00 00 00 +command: 19 00 00 00 +# +name: Fast_fo +type: parsed +protocol: NEC +address: 35 00 00 00 +command: 18 00 00 00 +# +name: Fast_ba +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8416 4228 507 1601 506 1601 506 549 504 546 507 547 506 547 506 1602 505 548 505 1602 505 1601 506 1599 508 547 506 546 507 547 506 547 506 546 507 22096 507 1600 507 1601 506 548 505 546 507 548 505 548 505 1603 504 547 506 1599 508 1600 507 1601 506 547 506 548 505 547 506 546 507 546 507 22096 506 1600 507 1600 507 546 507 547 506 547 506 546 507 1600 507 546 507 1601 506 1600 507 1601 506 546 507 547 506 546 507 548 505 547 506 +# +name: Fast_fo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8414 4229 506 1601 506 1601 506 547 506 547 506 546 507 546 507 1600 507 546 507 546 507 1601 506 1602 505 546 507 546 507 547 506 547 506 546 507 23151 505 1600 507 1601 506 546 507 546 507 546 507 547 506 1601 506 547 506 547 506 1601 506 1600 507 546 507 545 508 547 506 547 506 547 506 23149 507 1601 506 1601 506 546 507 546 507 546 507 547 506 1599 508 547 506 547 506 1602 505 1600 507 547 506 547 506 547 506 547 506 546 507 +# +name: Ok +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8414 4230 506 1600 507 1601 506 547 506 545 508 547 506 546 507 1600 507 547 506 546 507 546 507 1600 507 1601 506 1599 508 1599 508 547 506 546 507 21043 506 1599 508 1599 508 547 506 546 507 547 506 546 507 1600 507 547 506 547 506 546 507 1600 507 1600 507 1601 506 1600 507 546 507 547 506 21043 506 1599 508 1601 506 546 507 546 507 546 507 546 507 1601 506 547 506 546 507 547 506 1601 506 1601 506 1601 506 1600 507 546 507 547 506 +# +name: Fast_ba +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8509 4148 547 1566 545 1565 547 508 547 509 547 506 550 508 548 1564 548 508 548 1565 547 1565 547 1565 547 507 549 507 549 507 549 508 548 508 548 21187 546 1565 547 1566 546 508 548 507 549 508 547 508 548 1564 548 509 546 1563 549 1562 549 1565 547 508 548 507 549 507 549 508 548 507 549 22302 547 1565 547 1565 547 509 546 507 548 507 549 507 549 1564 548 508 547 1565 547 1564 548 1563 549 509 547 507 548 508 548 506 549 508 548 22303 546 1565 547 1565 546 508 548 509 547 507 549 509 547 1564 547 510 546 1564 548 1566 546 1566 546 507 548 509 547 508 548 509 547 507 549 22302 547 1565 546 1564 548 508 548 508 547 507 549 508 548 1564 548 509 547 1563 549 1564 547 1564 548 508 547 508 548 507 549 507 549 508 548 22302 547 1564 548 1563 549 508 548 507 548 508 548 511 545 1567 545 510 546 1564 548 1565 547 1564 548 507 626 429 549 508 548 509 624 431 547 +# +name: Fast_fo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8419 4234 549 1564 548 1564 548 508 548 508 548 507 548 508 548 1564 548 507 549 507 548 1564 548 1564 548 508 548 509 547 509 547 508 547 508 548 22258 547 1563 549 1565 547 508 548 508 547 508 548 508 548 1562 550 509 547 507 549 1564 548 1563 549 507 549 507 549 507 548 507 548 508 548 23360 545 1563 549 1564 548 508 548 510 545 509 547 508 548 1564 548 508 547 508 548 1565 547 1563 549 507 549 506 549 506 550 508 548 509 547 23358 547 1564 548 1566 546 508 547 508 548 508 548 506 550 1564 548 507 548 508 548 1565 547 1564 548 510 546 508 548 509 546 508 548 513 542 23358 548 1564 548 1565 547 510 546 508 548 509 547 507 549 1564 548 509 546 507 549 1563 549 1563 549 507 549 506 549 507 549 509 547 509 547 +# +name: Fast_ba +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8376 4179 506 1564 506 1593 503 541 507 570 504 568 506 542 506 1591 505 569 505 1566 504 1592 504 1566 504 570 504 570 504 542 506 570 504 568 506 22873 505 1591 505 1566 504 570 504 569 505 543 505 570 504 1565 505 569 505 1590 506 1565 505 1590 506 568 506 543 505 568 506 543 505 569 505 22873 505 1591 505 1564 506 570 504 569 505 543 505 569 505 1565 505 568 506 1592 504 1566 504 1591 505 569 505 542 506 568 506 543 505 569 505 +# +name: Fast_fo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8373 4181 503 1567 503 1591 506 543 505 569 505 569 505 542 506 1592 505 569 505 542 506 1591 505 1565 505 569 505 569 505 543 505 569 505 570 504 23897 503 1592 504 1566 504 569 505 569 505 543 505 569 505 1565 505 569 505 569 505 1566 504 1592 505 569 505 542 506 568 506 542 506 568 506 +# +name: Fast_ba +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3511 3500 932 815 925 833 935 833 935 2556 926 835 933 2575 934 813 927 835 933 2577 932 2555 927 834 934 835 933 2554 928 2575 934 2576 933 814 926 2577 932 833 935 2556 926 2575 934 835 933 814 926 2575 934 2575 934 33973 3519 3479 899 860 934 834 908 842 898 2602 907 860 908 2583 899 861 907 860 908 2584 898 2602 907 862 906 841 899 2602 933 2577 932 2556 926 836 932 2575 934 814 927 2576 933 2576 933 814 926 834 934 2575 934 2555 927 +# +name: Fast_ba +type: parsed +protocol: Kaseikyo +address: 90 02 20 00 +command: 20 00 00 00 +# +name: Ok +type: parsed +protocol: Kaseikyo +address: 91 02 20 01 +command: 80 01 00 00 +# +name: Fast_ba +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3514 1691 453 414 445 1287 453 415 444 422 448 419 451 417 453 413 446 421 449 418 452 415 444 422 448 419 451 416 454 1279 451 417 453 414 445 421 449 418 452 415 444 423 447 1286 454 413 446 421 449 1284 445 422 448 419 451 416 454 413 446 421 449 417 453 414 445 422 448 419 451 1282 448 419 451 416 454 414 445 421 449 418 452 415 444 422 448 1285 455 412 447 420 450 1283 447 421 449 418 452 1281 449 74543 3516 1689 445 423 447 1286 454 413 446 421 449 418 452 415 444 422 448 419 451 416 443 423 447 420 450 417 453 414 445 1288 452 415 444 422 448 419 451 416 454 413 446 420 450 1284 446 421 449 418 452 1282 448 419 451 416 454 413 446 421 449 418 452 414 445 422 448 419 451 416 443 1289 451 416 454 413 446 421 449 418 452 415 444 422 448 419 451 1282 448 420 450 417 453 1280 450 418 452 415 444 1288 452 74540 3519 1686 448 419 451 1282 448 420 450 417 453 414 445 422 448 419 451 415 444 423 447 420 450 417 453 414 445 421 449 1284 446 422 448 419 451 416 454 413 446 420 450 417 453 1281 449 418 452 415 444 1289 451 416 454 413 446 421 449 418 452 415 444 422 448 419 451 416 443 423 447 1286 454 413 446 421 449 418 452 415 444 422 448 419 451 416 454 1279 451 417 453 414 445 1287 453 415 444 422 448 1286 454 +# +name: Fast_fo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3513 1687 447 421 449 1311 419 421 449 419 451 416 454 413 446 421 449 418 452 414 445 422 448 419 451 416 454 413 446 1286 454 413 446 421 449 418 452 415 455 412 447 420 450 1282 448 420 450 417 453 1281 449 418 452 416 454 413 446 420 450 417 453 414 445 422 448 419 451 1281 449 1286 454 413 446 421 449 418 452 415 444 422 448 419 451 1282 448 1286 454 413 446 421 449 1284 446 422 448 419 451 1282 448 74517 3509 1691 453 414 445 1316 424 416 454 413 446 420 450 417 453 414 445 422 448 419 451 416 454 413 446 420 450 417 453 1280 450 417 453 415 444 422 448 419 451 416 454 413 446 1286 454 414 445 421 449 1285 445 422 448 420 450 417 453 414 445 421 449 418 452 415 444 423 447 1285 455 1279 451 417 453 414 445 422 448 419 451 416 454 413 446 1286 454 1280 450 418 452 415 455 1278 452 416 454 413 446 1287 453 74511 3515 1685 449 419 451 1282 448 420 450 417 453 414 445 422 448 418 452 415 455 412 447 420 450 417 453 414 445 421 449 1284 446 421 449 419 451 416 454 413 446 420 450 417 453 1280 450 418 452 415 444 1288 452 416 454 413 446 421 449 418 452 415 444 422 448 419 451 416 454 1279 451 1283 447 420 450 418 452 415 444 422 448 419 451 416 454 1279 451 1284 446 421 449 419 451 1309 421 419 451 417 453 1280 450 74543 3515 1690 454 413 446 1314 426 414 445 422 448 419 451 416 454 413 446 420 450 417 453 414 445 421 449 418 452 415 444 1288 452 416 454 413 446 420 450 417 453 414 445 422 448 1285 445 422 448 420 450 1283 447 420 450 418 452 415 444 422 448 419 451 416 454 413 446 420 450 1283 447 1287 453 415 455 412 447 420 450 417 453 414 445 421 449 1284 446 1288 452 416 454 413 446 1314 426 414 445 422 448 1312 428 74533 3513 1687 447 421 449 1312 418 422 448 419 451 416 454 414 445 421 449 418 452 415 444 422 448 419 451 416 454 413 446 1286 454 413 446 421 449 418 452 415 455 412 447 420 450 1283 447 420 450 418 452 1308 422 418 452 416 454 413 446 420 450 417 453 414 445 422 448 419 451 1281 449 1313 427 413 446 421 449 418 452 415 444 422 448 419 451 1282 448 1286 454 414 445 422 448 1312 418 422 448 420 450 1310 420 +# +name: Fast_fo +type: parsed +protocol: Kaseikyo +address: 90 02 20 00 +command: 30 00 00 00 +# +name: Ok +type: parsed +protocol: SIRC +address: 01 00 00 00 +command: 0B 00 00 00 +# +name: Fast_ba +type: parsed +protocol: SIRC +address: 0B 00 00 00 +command: 1B 00 00 00 +# +name: Fast_fo +type: parsed +protocol: Samsung32 +address: 2D 00 00 00 +command: 33 00 00 00 +# +# Model: NAD T557 +# +name: Power +type: parsed +protocol: NECext +address: 86 0F 00 00 +command: CA 35 00 00 +# +name: Eject +type: parsed +protocol: NECext +address: 86 0F 00 00 +command: D0 2F 00 00 +# +name: Ok +type: parsed +protocol: NECext +address: 86 0F 00 00 +command: DE 21 00 00 +# +name: Fast_fo +type: parsed +protocol: NECext +address: 86 0F 00 00 +command: D6 29 00 00 +# +name: Fast_ba +type: parsed +protocol: NECext +address: 86 0F 00 00 +command: D7 28 00 00 +# +name: Pause +type: parsed +protocol: NECext +address: 86 0F 00 00 +command: D2 2D 00 00 +# +name: Play +type: parsed +protocol: NECext +address: 86 0F 00 00 +command: D3 2C 00 00 +# +name: Subtitle +type: parsed +protocol: NECext +address: 86 0F 00 00 +command: CD 32 00 00 +# +# Model: Samsung UBDK8500 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4486 4500 459 538 483 517 482 516 484 516 483 515 484 1510 484 503 496 515 484 515 460 538 485 515 484 515 484 515 484 515 484 515 484 514 485 4510 484 1509 485 1508 486 1510 484 515 484 515 484 514 485 514 485 515 484 514 485 515 484 515 484 514 485 1509 485 1509 485 1510 484 1510 484 1539 455 1510 484 1510 484 1510 484 +# +name: Eject +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4484 4501 458 540 459 540 459 540 459 540 459 541 458 1536 458 541 458 540 459 540 459 540 459 542 457 540 459 540 459 541 458 541 458 541 458 4536 458 1536 458 1536 458 1535 459 540 459 1536 458 541 458 541 458 540 459 541 458 541 458 541 458 541 458 541 458 1535 459 1536 458 1535 459 1536 458 1536 458 1536 458 1536 458 +# +name: Ok +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4487 4499 460 538 461 538 461 538 461 537 462 538 461 1533 461 539 460 540 459 539 460 538 461 484 515 538 461 539 460 540 459 540 459 539 460 4533 461 1535 459 1535 459 1534 460 540 459 489 510 538 461 1534 460 1534 460 1534 460 540 459 539 460 539 460 1534 460 1535 459 538 461 539 460 539 460 1533 461 1533 461 1534 460 +# +name: Fast_fo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4512 4473 486 512 487 512 487 512 487 512 487 512 487 1507 487 512 487 512 487 513 486 513 486 513 486 512 487 513 486 512 487 512 487 512 487 4507 487 1507 487 1508 486 1508 486 512 487 1507 487 512 487 1507 487 513 486 1507 487 512 487 512 487 512 514 485 487 1507 487 512 487 1507 487 512 487 1507 487 1507 487 1507 487 +# +name: Fast_ba +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4485 4498 460 538 461 539 460 539 460 539 460 539 460 1534 460 539 460 539 460 539 460 539 460 539 460 539 460 471 528 538 461 539 460 539 460 4535 459 1534 460 1534 460 1534 460 539 460 539 460 1472 522 539 460 539 460 1534 460 539 460 539 460 538 461 1534 460 539 460 1535 459 1535 459 539 460 1534 460 1537 460 1532 459 +# +name: Pause +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4484 4500 459 540 459 540 459 540 459 540 459 540 459 1535 459 540 459 540 459 540 459 527 472 540 459 540 459 539 460 540 459 540 459 540 459 4535 459 1534 460 1534 460 1534 460 540 459 540 459 539 460 1535 459 539 460 1535 459 540 459 540 459 540 459 1534 460 1534 460 540 459 1534 460 539 460 1535 459 1534 460 1535 459 +# +name: Play +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4486 4499 483 516 483 515 484 515 484 514 485 515 484 1510 484 514 485 516 483 516 483 515 484 515 484 516 483 516 483 515 484 516 483 516 483 4450 544 1511 461 1533 461 1534 482 516 461 539 460 539 460 1534 460 539 460 1534 460 539 460 538 461 539 460 1533 461 1534 460 538 461 1534 460 538 461 1534 460 1534 460 1533 461 +# +# Model: Sylvania SDVD1111 +# +name: Power +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 5F 00 00 00 +# +name: Eject +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 4E 00 00 00 +# +name: Ok +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 55 00 00 00 +# +name: Fast_fo +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 40 00 00 00 +# +name: Fast_ba +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 5D 00 00 00 +# +name: Pause +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8983 4501 564 559 565 559 565 558 566 559 565 558 566 442 682 566 558 560 564 1669 565 1669 565 1669 565 1670 564 1669 565 1668 566 1599 635 1670 564 560 564 560 564 560 564 1669 565 1669 565 559 565 1670 564 559 565 1668 566 1669 565 1669 565 559 565 558 566 1670 564 560 564 1669 565 40232 8985 2239 566 +# +name: Play +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 5B 00 00 00 +# +name: Subtitle +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 02 00 00 00 +# +name: Fast_ba +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4541 4492 516 514 491 514 491 513 492 486 519 485 520 1515 486 519 486 518 487 517 488 517 488 516 489 515 490 515 490 514 491 513 492 499 486 4496 512 1497 514 1520 491 1518 493 511 494 484 511 1525 486 518 487 517 488 1521 490 515 490 514 491 513 492 1490 511 520 485 1524 487 1521 490 488 517 1492 519 1516 485 1523 488 57487 4537 4521 487 518 487 490 515 516 489 489 516 515 490 1519 492 512 493 512 493 511 494 510 485 520 485 519 486 519 486 518 487 517 488 503 492 4489 519 1516 485 1524 487 1522 489 515 490 515 490 1518 493 512 493 511 494 1515 486 492 513 518 487 517 488 1494 517 488 517 1491 520 1515 486 519 486 1522 489 1520 491 1491 520 +# +name: Fast_ba +type: parsed +protocol: Samsung32 +address: 07 00 00 00 +command: 45 00 00 00 +# +# Model: Marantz BD8002 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 873 931 873 931 1763 943 873 1833 873 931 873 897 907 3546 873 931 873 1834 872 931 1763 943 873 931 873 931 873 931 873 931 873 1834 1762 75052 873 932 872 932 1762 944 872 1834 872 932 872 932 872 3547 872 932 872 1834 872 932 1762 944 872 932 872 932 872 932 872 932 872 1835 1761 75054 871 932 872 934 1760 944 872 1834 872 932 872 932 872 3547 872 932 872 1836 870 932 1762 945 871 932 872 932 872 932 872 932 872 1834 1762 +# +name: Eject +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 872 931 873 932 1762 944 872 1833 873 931 873 931 873 4448 1763 1846 873 931 1763 1845 1763 943 873 931 873 930 874 931 873 931 873 75041 872 932 872 931 1763 885 932 1834 872 932 872 932 872 4449 1762 1875 844 932 1762 1847 1762 944 872 932 872 932 872 932 872 932 872 75041 872 932 872 932 1762 888 928 1834 872 932 872 931 873 4449 1763 1846 872 931 1763 1846 1763 944 872 932 872 932 872 932 872 932 872 +# +name: Ok +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1764 943 873 931 897 906 874 1833 898 907 897 906 898 3520 900 1809 1763 1846 897 906 898 906 1763 944 897 907 897 907 897 907 897 907 897 75016 1762 943 898 906 898 906 898 1809 897 907 897 907 897 3522 897 1808 1764 1844 899 907 897 907 1763 943 898 907 897 907 897 907 897 906 898 75015 1763 944 897 907 872 931 898 1809 897 907 897 907 897 3522 897 1809 1763 1846 897 907 897 907 1762 944 897 907 896 908 897 907 897 907 897 +# +name: Fast_fo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 873 931 873 931 1763 943 873 1832 874 931 873 931 873 4448 873 931 1763 1846 1763 943 873 931 873 931 873 931 873 931 873 931 873 931 873 75038 899 907 897 907 1787 919 897 1809 897 907 897 907 897 4424 897 907 1787 1822 1786 919 898 907 897 907 873 931 897 939 865 907 897 907 897 74960 953 907 897 907 1787 919 897 1809 897 907 897 907 897 4423 898 907 1787 1822 1786 919 897 907 897 907 897 907 897 893 911 907 897 907 897 +# +name: Fast_ba +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 873 931 1763 944 872 931 873 1834 872 932 872 931 873 4449 873 931 1763 944 872 1834 1762 944 872 931 873 931 873 933 871 931 873 932 872 75040 873 931 1763 944 872 932 872 1835 871 932 872 932 872 4448 873 932 1762 944 872 1834 1762 944 872 931 873 931 873 931 873 931 873 931 873 75039 898 907 1787 919 897 907 897 1809 897 907 897 907 897 4424 897 906 1788 919 897 1809 1787 919 897 907 897 907 897 907 897 907 897 907 897 +# +name: Pause +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 873 931 873 930 1764 943 873 1833 873 931 873 925 879 4448 873 931 1763 943 873 931 873 931 873 931 873 930 874 931 873 931 873 931 873 931 873 75039 874 931 873 930 1787 920 873 1833 873 931 873 931 873 4448 873 931 1763 943 873 931 873 931 873 931 873 931 873 931 873 930 874 931 873 932 872 75040 896 907 897 835 1860 919 897 1809 897 907 897 908 873 4448 873 931 1786 920 873 931 873 931 873 931 873 931 873 931 873 931 873 931 873 901 903 +# +name: Play +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 874 930 1764 942 874 931 873 1833 873 931 873 931 873 4448 873 930 1764 1846 1762 1846 1763 943 873 931 873 931 873 931 873 931 873 75040 873 931 1763 944 872 931 873 1833 873 931 873 931 873 4449 872 931 1763 1846 1762 1845 1764 943 873 931 873 931 873 931 873 931 873 75040 873 931 1763 944 872 931 873 1834 872 931 873 931 873 4449 872 931 1763 1846 1763 1846 1763 943 873 931 873 931 873 931 873 931 873 +# +# Model: Yamaha BDS667 +# +name: Power +type: parsed +protocol: NEC +address: 7C 00 00 00 +command: 80 00 00 00 +# +name: Eject +type: parsed +protocol: NEC +address: 7C 00 00 00 +command: 81 00 00 00 +# +name: Ok +type: parsed +protocol: NEC +address: 7C 00 00 00 +command: B8 00 00 00 +# +name: Fast_fo +type: parsed +protocol: NEC +address: 7C 00 00 00 +command: 87 00 00 00 +# +name: Fast_ba +type: parsed +protocol: NEC +address: 7C 00 00 00 +command: 86 00 00 00 +# +name: Pause +type: parsed +protocol: NEC +address: 7C 00 00 00 +command: 83 00 00 00 +# +name: Play +type: parsed +protocol: NEC +address: 7C 00 00 00 +command: 82 00 00 00 +# Model: JVC XVBP1 +# +name: Power +type: parsed +protocol: Kaseikyo +address: 2A 03 01 00 +command: 01 01 00 00 +# +name: Eject +type: parsed +protocol: Kaseikyo +address: 2A 03 01 00 +command: 41 01 00 00 +# +name: Ok +type: parsed +protocol: Kaseikyo +address: 2A 03 01 02 +command: 41 02 00 00 +# +name: Fast_fo +type: parsed +protocol: Kaseikyo +address: 2A 03 01 01 +command: 61 00 00 00 +# +name: Fast_ba +type: parsed +protocol: Kaseikyo +address: 2A 03 01 01 +command: 01 00 00 00 +# +name: Pause +type: parsed +protocol: Kaseikyo +address: 2A 03 01 00 +command: D1 01 00 00 +# +name: Play +type: parsed +protocol: Kaseikyo +address: 2A 03 01 00 +command: C1 01 00 00 +# +name: Subtitle +type: parsed +protocol: Kaseikyo +address: 2A 03 01 02 +command: E1 01 00 00 +# +# Model: Soniq B100 +# +name: Power +type: parsed +protocol: NECext +address: 00 DF 00 00 +command: 1C E3 00 00 +# +name: Eject +type: parsed +protocol: NECext +address: 00 DF 00 00 +command: 08 F7 00 00 +# +name: Ok +type: parsed +protocol: NECext +address: 00 DF 00 00 +command: 06 F9 00 00 +# +name: Fast_fo +type: parsed +protocol: NECext +address: 00 DF 00 00 +command: 09 F6 00 00 +# +name: Fast_ba +type: parsed +protocol: NECext +address: 00 DF 00 00 +command: 05 FA 00 00 +# +name: Pause +type: parsed +protocol: NECext +address: 00 DF 00 00 +command: 03 FC 00 00 +# +name: Play +type: parsed +protocol: NECext +address: 00 DF 00 00 +command: 1E E1 00 00 +# +name: Subtitle +type: parsed +protocol: NECext +address: 00 DF 00 00 +command: 5A A5 00 00 diff --git a/applications/main/infrared/resources/infrared/assets/digital_sign.ir b/applications/main/infrared/resources/infrared/assets/digital_sign.ir index bfe058c54..7c7c81fef 100644 --- a/applications/main/infrared/resources/infrared/assets/digital_sign.ir +++ b/applications/main/infrared/resources/infrared/assets/digital_sign.ir @@ -76,30 +76,6 @@ address: 80 02 20 00 command: 70 00 00 00 # name: POWER -type: parsed -protocol: RC6 -address: 00 00 00 00 -command: 0C 00 00 00 -# -name: SOURCE -type: parsed -protocol: RC6 -address: 00 00 00 00 -command: 38 00 00 00 -# -name: PLAY -type: parsed -protocol: RC6 -address: 00 00 00 00 -command: 2C 00 00 00 -# -name: STOP -type: parsed -protocol: RC6 -address: 00 00 00 00 -command: 31 00 00 00 -# -name: POWER type: raw frequency: 38000 duty_cycle: 0.330000 diff --git a/applications/main/nfc/application.fam b/applications/main/nfc/application.fam index 0104c078c..53c04f609 100644 --- a/applications/main/nfc/application.fam +++ b/applications/main/nfc/application.fam @@ -255,8 +255,29 @@ App( ) App( - appid="ndef_parser", + appid="ndef_ul_parser", apptype=FlipperAppType.PLUGIN, + cdefines=[("NDEF_PROTO", "NDEF_PROTO_UL")], + entry_point="ndef_plugin_ep", + targets=["f7"], + requires=["nfc"], + sources=["plugins/supported_cards/ndef.c"], +) + +App( + appid="ndef_mfc_parser", + apptype=FlipperAppType.PLUGIN, + cdefines=[("NDEF_PROTO", "NDEF_PROTO_MFC")], + entry_point="ndef_plugin_ep", + targets=["f7"], + requires=["nfc"], + sources=["plugins/supported_cards/ndef.c"], +) + +App( + appid="ndef_slix_parser", + apptype=FlipperAppType.PLUGIN, + cdefines=[("NDEF_PROTO", "NDEF_PROTO_SLIX")], entry_point="ndef_plugin_ep", targets=["f7"], requires=["nfc"], diff --git a/applications/main/nfc/plugins/supported_cards/ndef.c b/applications/main/nfc/plugins/supported_cards/ndef.c index 05b31f32b..820324421 100644 --- a/applications/main/nfc/plugins/supported_cards/ndef.c +++ b/applications/main/nfc/plugins/supported_cards/ndef.c @@ -1,153 +1,464 @@ // Parser for NDEF format data // Supports multiple NDEF messages and records in same tag -// Parsed types: URI (+ Phone, Mail), Text, BT MAC, Contact, WiFi, Empty +// Parsed types: URI (+ Phone, Mail), Text, BT MAC, Contact, WiFi, Empty, SmartPoster // Documentation and sources indicated where relevant // Made by @Willy-JL +// Mifare Ultralight support by @Willy-JL +// Mifare Classic support by @luu176 & @Willy-JL +// SLIX support by @Willy-JL + +// We use an arbitrary position system here, in order to support more protocols. +// Each protocol parses basic structure of the card, then starts ndef_parse_tlv() +// using an arbitrary position value that it can understand. When accessing data +// to parse NDEF content, ndef_get() will then map this arbitrary value to the +// card using state in Ndef struct, skipping blocks or sectors as needed. This +// way, NDEF parsing code does not need to know details of card layout. #include "nfc_supported_card_plugin.h" #include #include +#include +#include #include #define TAG "NDEF" +#define NDEF_PROTO_INVALID (-1) +#define NDEF_PROTO_RAW (0) // For parsing data fed manually +#define NDEF_PROTO_UL (1) +#define NDEF_PROTO_MFC (2) +#define NDEF_PROTO_SLIX (3) +#define NDEF_PROTO_TOTAL (4) + +#ifndef NDEF_PROTO +#error Must specify what protocol to use with NDEF_PROTO define! +#endif +#if NDEF_PROTO <= NDEF_PROTO_INVALID || NDEF_PROTO >= NDEF_PROTO_TOTAL +#error Invalid NDEF_PROTO specified! +#endif + +#define NDEF_TITLE(device, parsed_data) \ + furi_string_printf( \ + parsed_data, \ + "\e#NDEF Format Data\nCard type: %s\n", \ + nfc_device_get_name(device, NfcDeviceNameTypeFull)) + +// ---=== structures ===--- + +// TLV structure: +// https://docs.nordicsemi.com/bundle/ncs-latest/page/nrfxlib/nfc/doc/type_2_tag.html#data +typedef enum FURI_PACKED { + NdefTlvPadding = 0x00, + NdefTlvLockControl = 0x01, + NdefTlvMemoryControl = 0x02, + NdefTlvNdefMessage = 0x03, + NdefTlvProprietary = 0xFD, + NdefTlvTerminator = 0xFE, +} NdefTlv; + +// Type Name Format values: +// https://docs.nordicsemi.com/bundle/ncs-latest/page/nrf/protocols/nfc/index.html#flags-and-tnf +typedef enum FURI_PACKED { + NdefTnfEmpty = 0x00, + NdefTnfWellKnownType = 0x01, + NdefTnfMediaType = 0x02, + NdefTnfAbsoluteUri = 0x03, + NdefTnfExternalType = 0x04, + NdefTnfUnknown = 0x05, + NdefTnfUnchanged = 0x06, + NdefTnfReserved = 0x07, +} NdefTnf; + +// Flags and TNF structure: +// https://docs.nordicsemi.com/bundle/ncs-latest/page/nrf/protocols/nfc/index.html#flags-and-tnf +typedef struct FURI_PACKED { + // Reversed due to endianness + NdefTnf type_name_format : 3; + bool id_length_present : 1; + bool short_record : 1; + bool chunk_flag : 1; + bool message_end : 1; + bool message_begin : 1; +} NdefFlagsTnf; +_Static_assert(sizeof(NdefFlagsTnf) == 1); + +// URI payload format: +// https://learn.adafruit.com/adafruit-pn532-rfid-nfc/ndef#uri-records-0x55-slash-u-607763 +static const char* ndef_uri_prepends[] = { + [0x00] = NULL, // Allows detecting no prepend and checking schema for type + [0x01] = "http://www.", + [0x02] = "https://www.", + [0x03] = "http://", + [0x04] = "https://", + [0x05] = "tel:", + [0x06] = "mailto:", + [0x07] = "ftp://anonymous:anonymous@", + [0x08] = "ftp://ftp.", + [0x09] = "ftps://", + [0x0A] = "sftp://", + [0x0B] = "smb://", + [0x0C] = "nfs://", + [0x0D] = "ftp://", + [0x0E] = "dav://", + [0x0F] = "news:", + [0x10] = "telnet://", + [0x11] = "imap:", + [0x12] = "rtsp://", + [0x13] = "urn:", + [0x14] = "pop:", + [0x15] = "sip:", + [0x16] = "sips:", + [0x17] = "tftp:", + [0x18] = "btspp://", + [0x19] = "btl2cap://", + [0x1A] = "btgoep://", + [0x1B] = "tcpobex://", + [0x1C] = "irdaobex://", + [0x1D] = "file://", + [0x1E] = "urn:epc:id:", + [0x1F] = "urn:epc:tag:", + [0x20] = "urn:epc:pat:", + [0x21] = "urn:epc:raw:", + [0x22] = "urn:epc:", + [0x23] = "urn:nfc:", +}; + +// ---=== card memory layout abstraction ===--- + +// Shared context and state, read above +typedef struct { + FuriString* output; +#if NDEF_PROTO == NDEF_PROTO_RAW + struct { + const uint8_t* data; + size_t size; + } raw; +#elif NDEF_PROTO == NDEF_PROTO_UL + struct { + const uint8_t* start; + size_t size; + } ul; +#elif NDEF_PROTO == NDEF_PROTO_MFC + struct { + const MfClassicBlock* blocks; + size_t size; + } mfc; +#elif NDEF_PROTO == NDEF_PROTO_SLIX + struct { + const uint8_t* start; + size_t size; + } slix; +#endif +} Ndef; + +static bool ndef_get(Ndef* ndef, size_t pos, size_t len, void* buf) { +#if NDEF_PROTO == NDEF_PROTO_RAW + + // Using user-provided pointer, simply need to remap to it + if(pos + len > ndef->raw.size) return false; + memcpy(buf, ndef->raw.data + pos, len); + return true; + +#elif NDEF_PROTO == NDEF_PROTO_UL + + // Memory space is contiguous, simply need to remap to data pointer + if(pos + len > ndef->ul.size) return false; + memcpy(buf, ndef->ul.start + pos, len); + return true; + +#elif NDEF_PROTO == NDEF_PROTO_MFC + + // We need to skip sector trailers and MAD2, NDEF parsing just uses + // a position offset in data space, as if it were contiguous. + + // Start with a simple data space size check + if(pos + len > ndef->mfc.size) return false; + + // First 128 blocks are 32 sectors: 3 data blocks, 1 sector trailer. + // Sector 16 contains MAD2 and we need to skip this. + // So the first 93 (31*3) data blocks correspond to 128 real blocks. + // Last 128 blocks are 8 sectors: 15 data blocks, 1 sector trailer. + // So the last 120 (8*15) data blocks correspond to 128 real blocks. + div_t small_sector_data_blocks = div(pos, MF_CLASSIC_BLOCK_SIZE); + size_t large_sector_data_blocks = 0; + if(small_sector_data_blocks.quot > 93) { + large_sector_data_blocks = small_sector_data_blocks.quot - 93; + small_sector_data_blocks.quot = 93; + } + + div_t small_sectors = div(small_sector_data_blocks.quot, 3); + size_t real_block = small_sectors.quot * 4 + small_sectors.rem; + if(small_sectors.quot >= 16) { + real_block += 4; // Skip MAD2 + } + if(large_sector_data_blocks) { + div_t large_sectors = div(large_sector_data_blocks, 15); + real_block += large_sectors.quot * 16 + large_sectors.rem; + } + + const uint8_t* cur = &ndef->mfc.blocks[real_block].data[small_sector_data_blocks.rem]; + while(len) { + size_t sector_trailer = mf_classic_get_sector_trailer_num_by_block(real_block); + const uint8_t* end = &ndef->mfc.blocks[sector_trailer].data[0]; + + size_t chunk_len = MIN((size_t)(end - cur), len); + memcpy(buf, cur, chunk_len); + len -= chunk_len; + + if(len) { + real_block = sector_trailer + 1; + if(real_block == 64) { + real_block += 4; // Skip MAD2 + } + cur = &ndef->mfc.blocks[real_block].data[0]; + } + } + + return true; + +#elif NDEF_PROTO == NDEF_PROTO_SLIX + + // Memory space is contiguous, simply need to remap to data pointer + if(pos + len > ndef->slix.size) return false; + memcpy(buf, ndef->slix.start + pos, len); + return true; + +#else + + UNUSED(ndef); + UNUSED(pos); + UNUSED(len); + UNUSED(buf); + return false; + +#endif +} + +// ---=== output helpers ===--- + +static inline bool is_printable(char c) { + return (c >= ' ' && c <= '~') || c == '\r' || c == '\n'; +} + static bool is_text(const uint8_t* buf, size_t len) { for(size_t i = 0; i < len; i++) { - const char c = buf[i]; - if((c < ' ' || c > '~') && c != '\r' && c != '\n') { - return false; - } + if(!is_printable(buf[i])) return false; } return true; } +static bool ndef_dump(Ndef* ndef, const char* prefix, size_t pos, size_t len, bool force_hex) { + if(prefix) furi_string_cat_printf(ndef->output, "%s: ", prefix); + // We don't have direct access to memory chunks due to different card layouts + // Making a temporary buffer is wasteful of RAM and we can't afford this + // So while iterating like this is inefficient, it saves RAM and works between multiple card types + if(!force_hex) { + // If we find a non-printable character along the way, reset string to prev state and re-do as hex + size_t string_prev = furi_string_size(ndef->output); + for(size_t i = 0; i < len; i++) { + char c; + if(!ndef_get(ndef, pos + i, 1, &c)) return false; + if(!is_printable(c)) { + furi_string_left(ndef->output, string_prev); + force_hex = true; + break; + } + furi_string_push_back(ndef->output, c); + } + } + if(force_hex) { + for(size_t i = 0; i < len; i++) { + uint8_t b; + if(!ndef_get(ndef, pos + i, 1, &b)) return false; + furi_string_cat_printf(ndef->output, "%02X ", b); + } + } + furi_string_cat(ndef->output, "\n"); + return true; +} + static void - print_data(FuriString* str, const char* prefix, const uint8_t* buf, size_t len, bool force_hex) { - if(prefix) furi_string_cat_printf(str, "%s: ", prefix); + ndef_print(Ndef* ndef, const char* prefix, const void* buf, size_t len, bool force_hex) { + if(prefix) furi_string_cat_printf(ndef->output, "%s: ", prefix); if(!force_hex && is_text(buf, len)) { - char* tmp = malloc(len + 1); - memcpy(tmp, buf, len); - tmp[len] = '\0'; - furi_string_cat_printf(str, "%s", tmp); - free(tmp); + furi_string_cat_printf(ndef->output, "%.*s", len, (const char*)buf); } else { - for(uint8_t i = 0; i < len; i++) { - furi_string_cat_printf(str, "%02X ", buf[i]); + for(size_t i = 0; i < len; i++) { + furi_string_cat_printf(ndef->output, "%02X ", ((const uint8_t*)buf)[i]); } } - furi_string_cat(str, "\n"); + furi_string_cat(ndef->output, "\n"); } -static void parse_ndef_uri(FuriString* str, const uint8_t* payload, uint32_t payload_len) { - // https://learn.adafruit.com/adafruit-pn532-rfid-nfc/ndef#uri-records-0x55-slash-u-607763 - const char* prepends[] = { - [0x00] = "", - [0x01] = "http://www.", - [0x02] = "https://www.", - [0x03] = "http://", - [0x04] = "https://", - [0x05] = "tel:", - [0x06] = "mailto:", - [0x07] = "ftp://anonymous:anonymous@", - [0x08] = "ftp://ftp.", - [0x09] = "ftps://", - [0x0A] = "sftp://", - [0x0B] = "smb://", - [0x0C] = "nfs://", - [0x0D] = "ftp://", - [0x0E] = "dav://", - [0x0F] = "news:", - [0x10] = "telnet://", - [0x11] = "imap:", - [0x12] = "rtsp://", - [0x13] = "urn:", - [0x14] = "pop:", - [0x15] = "sip:", - [0x16] = "sips:", - [0x17] = "tftp:", - [0x18] = "btspp://", - [0x19] = "btl2cap://", - [0x1A] = "btgoep://", - [0x1B] = "tcpobex://", - [0x1C] = "irdaobex://", - [0x1D] = "file://", - [0x1E] = "urn:epc:id:", - [0x1F] = "urn:epc:tag:", - [0x20] = "urn:epc:pat:", - [0x21] = "urn:epc:raw:", - [0x22] = "urn:epc:", - [0x23] = "urn:nfc:", - }; - const char* prepend = ""; - uint8_t prepend_type = payload[0]; - if(prepend_type < COUNT_OF(prepends)) { - prepend = prepends[prepend_type]; - } - size_t prepend_len = strlen(prepend); +// ---=== payload parsing ===--- - size_t uri_len = prepend_len + (payload_len - 1); - char* const uri_buf = malloc(uri_len); - memcpy(uri_buf, prepend, prepend_len); - memcpy(uri_buf + prepend_len, payload + 1, payload_len - 1); - char* uri = uri_buf; +static inline uint8_t hex_to_int(char c) { + if(c >= '0' && c <= '9') return c - '0'; + if(c >= 'A' && c <= 'F') return c - 'A' + 10; + if(c >= 'a' && c <= 'f') return c - 'a' + 10; + return 0; +} +static char url_decode_char(const char* str) { + return (hex_to_int(str[0]) << 4) | hex_to_int(str[1]); +} + +static bool ndef_parse_uri(Ndef* ndef, size_t pos, size_t len) { const char* type = "URI"; - if(strncmp(uri, "http", strlen("http")) == 0) { - type = "URL"; - } else if(strncmp(uri, "tel:", strlen("tel:")) == 0) { - type = "Phone"; - uri += strlen("tel:"); - uri_len -= strlen("tel:"); - } else if(strncmp(uri, "mailto:", strlen("mailto:")) == 0) { - type = "Mail"; - uri += strlen("mailto:"); - uri_len -= strlen("mailto:"); + + // Parse URI prepend type + const char* prepend = NULL; + uint8_t prepend_type; + if(!ndef_get(ndef, pos++, 1, &prepend_type)) return false; + len--; + if(prepend_type < COUNT_OF(ndef_uri_prepends)) { + prepend = ndef_uri_prepends[prepend_type]; } - - furi_string_cat_printf(str, "%s\n", type); - print_data(str, NULL, (uint8_t*)uri, uri_len, false); - free(uri_buf); -} - -static void parse_ndef_text(FuriString* str, const uint8_t* payload, uint32_t payload_len) { - furi_string_cat(str, "Text\n"); - print_data(str, NULL, payload + 3, payload_len - 3, false); -} - -static void parse_ndef_bt(FuriString* str, const uint8_t* payload, uint32_t payload_len) { - furi_string_cat(str, "BT MAC\n"); - print_data(str, NULL, payload + 2, payload_len - 2, true); -} - -static void parse_ndef_vcard(FuriString* str, const uint8_t* payload, uint32_t payload_len) { - char* tmp = malloc(payload_len + 1); - memcpy(tmp, payload, payload_len); - tmp[payload_len] = '\0'; - FuriString* fmt = furi_string_alloc_set(tmp); - free(tmp); - - furi_string_trim(fmt); - if(furi_string_start_with(fmt, "BEGIN:VCARD")) { - furi_string_right(fmt, furi_string_search_char(fmt, '\n')); - if(furi_string_end_with(fmt, "END:VCARD")) { - furi_string_left(fmt, furi_string_search_rchar(fmt, '\n')); - } - furi_string_trim(fmt); - if(furi_string_start_with(fmt, "VERSION:")) { - furi_string_right(fmt, furi_string_search_char(fmt, '\n')); - furi_string_trim(fmt); + if(prepend) { + if(strncmp(prepend, "http", 4) == 0) { + type = "URL"; + } else if(strncmp(prepend, "tel:", 4) == 0) { + type = "Phone"; + prepend = ""; // Not NULL to avoid schema check below, only want to hide it from output + } else if(strncmp(prepend, "mailto:", 7) == 0) { + type = "Mail"; + prepend = ""; // Not NULL to avoid schema check below, only want to hide it from output } } - furi_string_cat(str, "Contact\n"); - print_data(str, NULL, (uint8_t*)furi_string_get_cstr(fmt), furi_string_size(fmt), false); - furi_string_free(fmt); + // Parse and optionally skip schema, if no prepend was specified + if(!prepend) { + char schema[7] = {0}; // Longest schema we check is 7 char long without terminator + if(!ndef_get(ndef, pos, MIN(sizeof(schema), len), schema)) return false; + if(strncmp(schema, "http", 4) == 0) { + type = "URL"; + } else if(strncmp(schema, "tel:", 4) == 0) { + type = "Phone"; + pos += 4; + len -= 4; + } else if(strncmp(schema, "mailto:", 7) == 0) { + type = "Mail"; + pos += 7; + len -= 7; + } + } + + // Print static data as-is + furi_string_cat_printf(ndef->output, "%s\n", type); + if(prepend) { + furi_string_cat(ndef->output, prepend); + } + + // Print URI one char at a time and perform URL decode + while(len) { + char c; + if(!ndef_get(ndef, pos++, 1, &c)) return false; + len--; + if(c != '%' || len < 2) { // Not encoded, or not enough remaining text for encoded char + furi_string_push_back(ndef->output, c); + continue; + } + char enc[2]; + if(!ndef_get(ndef, pos, 2, enc)) return false; + enc[0] = toupper(enc[0]); + enc[1] = toupper(enc[1]); + // Only consume and print these 2 characters if they're valid URL encoded + // Otherwise they're processed in next iterations and we output the % char + if(((enc[0] >= 'A' && enc[0] <= 'F') || (enc[0] >= '0' && enc[0] <= '9')) && + ((enc[1] >= 'A' && enc[1] <= 'F') || (enc[1] >= '0' && enc[1] <= '9'))) { + pos += 2; + len -= 2; + c = url_decode_char(enc); + } + furi_string_push_back(ndef->output, c); + } + + return true; } -static void parse_ndef_wifi(FuriString* str, const uint8_t* payload, uint32_t payload_len) { -// https://android.googlesource.com/platform/packages/apps/Nfc/+/refs/heads/main/src/com/android/nfc/NfcWifiProtectedSetup.java +static bool ndef_parse_text(Ndef* ndef, size_t pos, size_t len) { + furi_string_cat(ndef->output, "Text\n"); + if(!ndef_dump(ndef, NULL, pos + 3, len - 3, false)) return false; + return true; +} + +static bool ndef_parse_bt(Ndef* ndef, size_t pos, size_t len) { + furi_string_cat(ndef->output, "BT MAC\n"); + if(len != 8) return false; + if(!ndef_dump(ndef, NULL, pos + 2, len - 2, true)) return false; + return true; +} + +static bool ndef_parse_vcard(Ndef* ndef, size_t pos, size_t len) { + // We hide redundant tags the user is probably not interested in. + // Would be easier with FuriString checks for start_with() and end_with() + // but to do that would waste lots of RAM on a cloned string buffer + // so instead we just look for these markers at start/end and shift + // pos and len then use ndef_dump() to output one char at a time. + // Results in minimal stack and no heap usage at all. + static const char* const begin_tag = "BEGIN:VCARD"; + static const uint8_t begin_len = strlen(begin_tag); + static const char* const version_tag = "VERSION:"; + static const uint8_t version_len = strlen(version_tag); + static const char* const end_tag = "END:VCARD"; + static const uint8_t end_len = strlen(end_tag); + char tmp[13] = {0}; // Enough for BEGIN:VCARD\r\n + uint8_t skip = 0; + + // Skip BEGIN tag + if(len >= sizeof(tmp)) { + if(!ndef_get(ndef, pos, sizeof(tmp), tmp)) return false; + if(strncmp(begin_tag, tmp, begin_len) == 0) { + skip = begin_len; + if(tmp[skip] == '\r') skip++; + if(tmp[skip] == '\n') skip++; + pos += skip; + len -= skip; + } + } + + // Skip VERSION tag + if(len >= sizeof(tmp)) { + if(!ndef_get(ndef, pos, sizeof(tmp), tmp)) return false; + if(strncmp(version_tag, tmp, version_len) == 0) { + skip = version_len; + while(skip < len) { + if(!ndef_get(ndef, pos + skip, 1, &tmp[0])) return false; + skip++; + if(tmp[0] == '\n') break; + } + pos += skip; + len -= skip; + } + } + + // Skip END tag + if(len >= sizeof(tmp)) { + if(!ndef_get(ndef, pos + len - sizeof(tmp), sizeof(tmp), tmp)) return false; + // Read more than length of END tag and check multiple offsets, might have some padding after + // Worst case: there is END:VCARD\r\n\r\n which is same length as tmp buffer (13) + // Not sure if this is in spec but might aswell check + static const uint8_t offsets = sizeof(tmp) - end_len + 1; + for(uint8_t offset = 0; offset < offsets; offset++) { + if(strncmp(end_tag, tmp + offset, end_len) == 0) { + skip = sizeof(tmp) - offset; + len -= skip; + break; + } + } + } + + furi_string_cat(ndef->output, "Contact\n"); + ndef_dump(ndef, NULL, pos, len, false); + + return true; +} + +// Loosely based on Android WiFi NDEF implementation: +// https://android.googlesource.com/platform/packages/apps/Nfc/+/025560080737b43876c9d81feff3151f497947e8/src/com/android/nfc/NfcWifiProtectedSetup.java +static bool ndef_parse_wifi(Ndef* ndef, size_t pos, size_t len) { #define CREDENTIAL_FIELD_ID (0x100E) #define SSID_FIELD_ID (0x1045) #define NETWORK_KEY_FIELD_ID (0x1027) @@ -161,44 +472,57 @@ static void parse_ndef_wifi(FuriString* str, const uint8_t* payload, uint32_t pa #define AUTH_TYPE_WPA_AND_WPA2_PSK (0x0022) #define MAX_NETWORK_KEY_SIZE_BYTES (64) - size_t i = 0; - while(i < payload_len) { - uint16_t field_id = bit_lib_bytes_to_num_be(payload + i, 2); - i += 2; - uint16_t field_len = bit_lib_bytes_to_num_be(payload + i, 2); - i += 2; + furi_string_cat(ndef->output, "WiFi\n"); + size_t end = pos + len; + + uint8_t tmp_buf[2]; + while(pos < end) { + if(!ndef_get(ndef, pos, 2, &tmp_buf)) return false; + uint16_t field_id = bit_lib_bytes_to_num_be(tmp_buf, 2); + pos += 2; + if(!ndef_get(ndef, pos, 2, &tmp_buf)) return false; + uint16_t field_len = bit_lib_bytes_to_num_be(tmp_buf, 2); + pos += 2; + FURI_LOG_D(TAG, "wifi field: %04X len: %d", field_id, field_len); + + if(pos + field_len > end) { + return false; + } if(field_id == CREDENTIAL_FIELD_ID) { - furi_string_cat(str, "WiFi\n"); - size_t start_position = i; - while(i < start_position + field_len) { - uint16_t cfg_id = bit_lib_bytes_to_num_be(payload + i, 2); - i += 2; - uint16_t cfg_len = bit_lib_bytes_to_num_be(payload + i, 2); - i += 2; + size_t field_end = pos + field_len; + while(pos < field_end) { + if(!ndef_get(ndef, pos, 2, &tmp_buf)) return false; + uint16_t cfg_id = bit_lib_bytes_to_num_be(tmp_buf, 2); + pos += 2; + if(!ndef_get(ndef, pos, 2, &tmp_buf)) return false; + uint16_t cfg_len = bit_lib_bytes_to_num_be(tmp_buf, 2); + pos += 2; + FURI_LOG_D(TAG, "wifi cfg: %04X len: %d", cfg_id, cfg_len); - if(i + cfg_len > start_position + field_len) { - return; + if(pos + cfg_len > field_end) { + return false; } switch(cfg_id) { case SSID_FIELD_ID: - print_data(str, "SSID", payload + i, cfg_len, false); - i += cfg_len; + if(!ndef_dump(ndef, "SSID", pos, cfg_len, false)) return false; + pos += cfg_len; break; case NETWORK_KEY_FIELD_ID: if(cfg_len > MAX_NETWORK_KEY_SIZE_BYTES) { - return; + return false; } - print_data(str, "PWD", payload + i, cfg_len, false); - i += cfg_len; + if(!ndef_dump(ndef, "PWD", pos, cfg_len, false)) return false; + pos += cfg_len; break; case AUTH_TYPE_FIELD_ID: if(cfg_len != AUTH_TYPE_EXPECTED_SIZE) { - return; + return false; } - short auth_type = bit_lib_bytes_to_num_be(payload + i, 2); - i += 2; + if(!ndef_get(ndef, pos, 2, &tmp_buf)) return false; + uint16_t auth_type = bit_lib_bytes_to_num_be(tmp_buf, 2); + pos += 2; const char* auth; switch(auth_type) { case AUTH_TYPE_OPEN: @@ -223,252 +547,507 @@ static void parse_ndef_wifi(FuriString* str, const uint8_t* payload, uint32_t pa auth = "Unknown"; break; } - print_data(str, "AUTH", (uint8_t*)auth, strlen(auth), false); + ndef_print(ndef, "AUTH", auth, strlen(auth), false); break; default: - i += cfg_len; + pos += cfg_len; break; } } - return; + return true; } - i += field_len; + pos += field_len; } + + furi_string_cat(ndef->output, "No data parsed\n"); + return true; } -static void parse_ndef_payload( - FuriString* str, - uint8_t tnf, +// ---=== ndef layout parsing ===--- + +bool ndef_parse_record( + Ndef* ndef, + size_t pos, + size_t len, + NdefTnf tnf, const char* type, - uint8_t type_len, - const uint8_t* payload, - uint32_t payload_len) { - if(!payload_len) { - furi_string_cat(str, "Empty\n"); - return; + uint8_t type_len); +bool ndef_parse_message(Ndef* ndef, size_t pos, size_t len, size_t message_num, bool smart_poster); +size_t ndef_parse_tlv(Ndef* ndef, size_t pos, size_t len, size_t already_parsed); + +bool ndef_parse_record( + Ndef* ndef, + size_t pos, + size_t len, + NdefTnf tnf, + const char* type, + uint8_t type_len) { + FURI_LOG_D(TAG, "payload type: %.*s len: %d", type_len, type, len); + if(!len) { + furi_string_cat(ndef->output, "Empty\n"); + return true; } + switch(tnf) { - case 0x01: // NFC Forum well-known type [NFC RTD] - if(strncmp("U", type, type_len) == 0) { - parse_ndef_uri(str, payload, payload_len); + case NdefTnfWellKnownType: + if(strncmp("Sp", type, type_len) == 0) { + furi_string_cat(ndef->output, "SmartPoster\nContained records below\n\n"); + return ndef_parse_message(ndef, pos, len, 0, true); + } else if(strncmp("U", type, type_len) == 0) { + return ndef_parse_uri(ndef, pos, len); } else if(strncmp("T", type, type_len) == 0) { - parse_ndef_text(str, payload, payload_len); - } else { - print_data(str, "Well-known type", (uint8_t*)type, type_len, false); - print_data(str, "Payload", payload, payload_len, false); + return ndef_parse_text(ndef, pos, len); } - break; - case 0x02: // Media-type [RFC 2046] - if(strncmp("application/vnd.bluetooth.ep.oob", type, type_len) == 0) { - parse_ndef_bt(str, payload, payload_len); - } else if(strncmp("text/vcard", type, type_len) == 0) { - parse_ndef_vcard(str, payload, payload_len); - } else if(strncmp("application/vnd.wfa.wsc", type, type_len) == 0) { - parse_ndef_wifi(str, payload, payload_len); - } else { - print_data(str, "Media Type", (uint8_t*)type, type_len, false); - print_data(str, "Payload", payload, payload_len, false); - } - break; - case 0x00: // Empty - case 0x03: // Absolute URI [RFC 3986] - case 0x04: // NFC Forum external type [NFC RTD] - case 0x05: // Unknown - case 0x06: // Unchanged - case 0x07: // Reserved - default: // Unknown // Dump data without parsing - print_data(str, "Type name format", &tnf, 1, true); - print_data(str, "Type", (uint8_t*)type, type_len, false); - print_data(str, "Payload", payload, payload_len, false); - break; + furi_string_cat(ndef->output, "Unknown\n"); + ndef_print(ndef, "Well-known Type", type, type_len, false); + if(!ndef_dump(ndef, "Payload", pos, len, false)) return false; + return true; + + case NdefTnfMediaType: + if(strncmp("application/vnd.bluetooth.ep.oob", type, type_len) == 0) { + return ndef_parse_bt(ndef, pos, len); + } else if(strncmp("text/vcard", type, type_len) == 0) { + return ndef_parse_vcard(ndef, pos, len); + } else if(strncmp("application/vnd.wfa.wsc", type, type_len) == 0) { + return ndef_parse_wifi(ndef, pos, len); + } + // Dump data without parsing + furi_string_cat(ndef->output, "Unknown\n"); + ndef_print(ndef, "Media Type", type, type_len, false); + if(!ndef_dump(ndef, "Payload", pos, len, false)) return false; + return true; + + case NdefTnfEmpty: + case NdefTnfAbsoluteUri: + case NdefTnfExternalType: + case NdefTnfUnknown: + case NdefTnfUnchanged: + case NdefTnfReserved: + default: + // Dump data without parsing + furi_string_cat(ndef->output, "Unsupported\n"); + ndef_print(ndef, "Type name format", &tnf, 1, true); + ndef_print(ndef, "Type", type, type_len, false); + if(!ndef_dump(ndef, "Payload", pos, len, false)) return false; + return true; } } -static const uint8_t* parse_ndef_message( - FuriString* str, - size_t message_num, - const uint8_t* cur, - const uint8_t* message_end) { - // NDEF message and record documentation: - // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/nrf/protocols/nfc/index.html#ndef-message-and-record-format +// NDEF message structure: +// https://docs.nordicsemi.com/bundle/ncs-latest/page/nrf/protocols/nfc/index.html#ndef_message_and_record_format +bool ndef_parse_message(Ndef* ndef, size_t pos, size_t len, size_t message_num, bool smart_poster) { + size_t end = pos + len; + size_t record_num = 0; bool last_record = false; - while(cur < message_end) { + while(pos < end) { // Flags and TNF - uint8_t flags_tnf = *cur++; + NdefFlagsTnf flags_tnf; + if(!ndef_get(ndef, pos++, 1, &flags_tnf)) return false; + FURI_LOG_D(TAG, "flags_tnf: %02X", *(uint8_t*)&flags_tnf); + FURI_LOG_D(TAG, "flags_tnf.message_begin: %d", flags_tnf.message_begin); + FURI_LOG_D(TAG, "flags_tnf.message_end: %d", flags_tnf.message_end); + FURI_LOG_D(TAG, "flags_tnf.chunk_flag: %d", flags_tnf.chunk_flag); + FURI_LOG_D(TAG, "flags_tnf.short_record: %d", flags_tnf.short_record); + FURI_LOG_D(TAG, "flags_tnf.id_length_present: %d", flags_tnf.id_length_present); + FURI_LOG_D(TAG, "flags_tnf.type_name_format: %02X", flags_tnf.type_name_format); // Message Begin should only be set on first record - if(record_num++ && flags_tnf & (1 << 7)) break; + if(record_num++ && flags_tnf.message_begin) return false; // Message End should only be set on last record - if(last_record) break; - if(flags_tnf & (1 << 6)) last_record = true; - // Chunked Flag not supported - if(flags_tnf & (1 << 5)) break; - // Payload Length field of 1 vs 4 bytes - bool short_record = flags_tnf & (1 << 4); - // Is payload ID length and value present - bool id_present = flags_tnf & (1 << 3); - // Type Name Format 3 bit value - uint8_t tnf = flags_tnf & 0b00000111; + if(last_record) return false; + if(flags_tnf.message_end) last_record = true; + // Chunk Flag not supported + if(flags_tnf.chunk_flag) return false; // Type Length - uint8_t type_len = *cur++; + uint8_t type_len; + if(!ndef_get(ndef, pos++, 1, &type_len)) return false; - // Payload Length + // Payload Length field of 1 or 4 bytes uint32_t payload_len; - if(short_record) { - payload_len = *cur++; + if(flags_tnf.short_record) { + uint8_t payload_len_short; + if(!ndef_get(ndef, pos++, 1, &payload_len_short)) return false; + payload_len = payload_len_short; } else { - payload_len = bit_lib_bytes_to_num_be(cur, 4); - cur += 4; + if(!ndef_get(ndef, pos, sizeof(payload_len), &payload_len)) return false; + payload_len = bit_lib_bytes_to_num_be((void*)&payload_len, sizeof(payload_len)); + pos += sizeof(payload_len); } // ID Length uint8_t id_len = 0; - if(id_present) { - id_len = *cur++; + if(flags_tnf.id_length_present) { + if(!ndef_get(ndef, pos++, 1, &id_len)) return false; } // Payload Type - char* type = NULL; + char type_buf[32]; // Longest type supported in ndef_parse_record() is 32 chars excl terminator + char* type = type_buf; + bool type_was_allocated = false; if(type_len) { - type = malloc(type_len); - memcpy(type, cur, type_len); - cur += type_len; + if(type_len > sizeof(type_buf)) { + type = malloc(type_len); + type_was_allocated = true; + } + if(!ndef_get(ndef, pos, type_len, type)) { + if(type_was_allocated) free(type); + return false; + } + pos += type_len; } // Payload ID - cur += id_len; + pos += id_len; - furi_string_cat_printf(str, "\e*> M:%d R:%d - ", message_num, record_num); - parse_ndef_payload(str, tnf, type, type_len, cur, payload_len); - cur += payload_len; + if(smart_poster) { + furi_string_cat_printf(ndef->output, "\e*> SP-R%d: ", record_num); + } else { + furi_string_cat_printf(ndef->output, "\e*> M%d-R%d: ", message_num, record_num); + } + if(!ndef_parse_record(ndef, pos, payload_len, flags_tnf.type_name_format, type, type_len)) { + if(type_was_allocated) free(type); + return false; + } + pos += payload_len; - free(type); - furi_string_trim(str, "\n"); - furi_string_cat(str, "\n\n"); + if(type_was_allocated) free(type); + furi_string_trim(ndef->output, "\n"); + furi_string_cat(ndef->output, "\n\n"); } - return cur; + + if(record_num == 0) { + if(smart_poster) { + furi_string_cat(ndef->output, "\e*> SP: Empty\n\n"); + } else { + furi_string_cat_printf(ndef->output, "\e*> M%d: Empty\n\n", message_num); + } + } + + return pos == end && (last_record || record_num == 0); } -static bool ndef_parse(const NfcDevice* device, FuriString* parsed_data) { +// TLV structure: +// https://docs.nordicsemi.com/bundle/ncs-latest/page/nrfxlib/nfc/doc/type_2_tag.html#data +size_t ndef_parse_tlv(Ndef* ndef, size_t pos, size_t len, size_t already_parsed) { + size_t end = pos + len; + size_t message_num = 0; + + while(pos < end) { + NdefTlv tlv; + if(!ndef_get(ndef, pos++, 1, &tlv)) return 0; + FURI_LOG_D(TAG, "tlv: %02X", tlv); + + switch(tlv) { + default: + // Unknown, bail to avoid problems + return 0; + + case NdefTlvPadding: + // Has no length, skip to next byte + break; + + case NdefTlvTerminator: + // NDEF message finished, return whether we parsed something + return message_num; + + case NdefTlvLockControl: + case NdefTlvMemoryControl: + case NdefTlvProprietary: + case NdefTlvNdefMessage: { + // Calculate length + uint16_t len; + uint8_t len_type; + if(!ndef_get(ndef, pos++, 1, &len_type)) return 0; + if(len_type < 0xFF) { // 1 byte length + len = len_type; + } else { // 3 byte length (0xFF marker + 2 byte integer) + if(!ndef_get(ndef, pos, sizeof(len), &len)) return 0; + len = bit_lib_bytes_to_num_be((void*)&len, sizeof(len)); + pos += sizeof(len); + } + + if(tlv != NdefTlvNdefMessage) { + // We don't care, skip this TLV block to next one + pos += len; + break; + } + + if(!ndef_parse_message(ndef, pos, len, ++message_num + already_parsed, false)) + return 0; + pos += len; + + break; + } + } + } + + // Reached data end with no TLV terminator, + // but also no errors, treat this as a success + return message_num; +} + +#if NDEF_PROTO != NDEF_PROTO_RAW + +// ---=== protocol entry-points ===--- + +#if NDEF_PROTO == NDEF_PROTO_UL + +// MF UL memory layout: +// https://docs.nordicsemi.com/bundle/ncs-latest/page/nrfxlib/nfc/doc/type_2_tag.html#memory_layout +static bool ndef_ul_parse(const NfcDevice* device, FuriString* parsed_data) { furi_assert(device); furi_assert(parsed_data); const MfUltralightData* data = nfc_device_get_data(device, NfcProtocolMfUltralight); - bool parsed = false; + // Check card type can contain NDEF + if(data->type != MfUltralightTypeNTAG203 && data->type != MfUltralightTypeNTAG213 && + data->type != MfUltralightTypeNTAG215 && data->type != MfUltralightTypeNTAG216 && + data->type != MfUltralightTypeNTAGI2C1K && data->type != MfUltralightTypeNTAGI2C2K && + data->type != MfUltralightTypeNTAGI2CPlus1K && + data->type != MfUltralightTypeNTAGI2CPlus2K) { + return false; + } - do { - // Memory layout documentation: - // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/nrfxlib/nfc/doc/type_2_tag.html#id2 + // Check Capability Container (CC) values + struct { + uint8_t nfc_magic_number; + uint8_t document_version_number; + uint8_t data_area_size; // Usable byte size / 8, only includes user memory + uint8_t read_write_access; + }* cc = (void*)&data->page[3].data[0]; + if(cc->nfc_magic_number != 0xE1) return false; + if(cc->document_version_number != 0x10) return false; - // Check card type can contain NDEF - if(data->type != MfUltralightTypeNTAG203 && data->type != MfUltralightTypeNTAG213 && - data->type != MfUltralightTypeNTAG215 && data->type != MfUltralightTypeNTAG216 && - data->type != MfUltralightTypeNTAGI2C1K && data->type != MfUltralightTypeNTAGI2C2K && - data->type != MfUltralightTypeNTAGI2CPlus1K && - data->type != MfUltralightTypeNTAGI2CPlus2K) { - break; - } + // Calculate usable data area + const uint8_t* start = &data->page[4].data[0]; + const uint8_t* end = start + (cc->data_area_size * 8); + size_t max_size = mf_ultralight_get_pages_total(data->type) * MF_ULTRALIGHT_PAGE_SIZE; + end = MIN(end, &data->page[0].data[0] + max_size); + size_t data_start = 0; + size_t data_size = end - start; - // Double check Capability Container (CC) and find data area bounds - struct { - uint8_t nfc_magic_number; - uint8_t document_version_number; - uint8_t data_area_size; - uint8_t read_write_access; - }* cc = (void*)&data->page[3].data[0]; - if(cc->nfc_magic_number != 0xE1) break; - if(cc->document_version_number != 0x10) break; - const uint8_t* cur = &data->page[4].data[0]; - const uint8_t* end = cur + (cc->data_area_size * 2 * MF_ULTRALIGHT_PAGE_SIZE); - size_t max_size = mf_ultralight_get_pages_total(data->type) * MF_ULTRALIGHT_PAGE_SIZE; - end = MIN(end, &data->page[0].data[0] + max_size); - size_t message_num = 0; + NDEF_TITLE(device, parsed_data); - // Parse as TLV (see docs above) - while(cur < end) { - switch(*cur++) { - case 0x03: { // NDEF message - if(cur >= end) break; - uint16_t len; - if(*cur < 0xFF) { // 1 byte length - len = *cur++; - } else { // 3 byte length (0xFF marker + 2 byte integer) - if(cur + 2 >= end) { - cur = end; - break; - } - len = bit_lib_bytes_to_num_be(++cur, 2); - cur += 2; - } - if(cur + len >= end) { - cur = end; - break; - } + Ndef ndef = { + .output = parsed_data, + .ul = + { + .start = start, + .size = data_size, + }, + }; + size_t parsed = ndef_parse_tlv(&ndef, data_start, data_size - data_start, 0); - if(message_num++ == 0) { - furi_string_printf( - parsed_data, - "\e#NDEF Format Data\nCard type: %s\n", - mf_ultralight_get_device_name(data, NfcDeviceNameTypeFull)); - } + if(parsed) { + furi_string_trim(parsed_data, "\n"); + furi_string_cat(parsed_data, "\n"); + } else { + furi_string_reset(parsed_data); + } - const uint8_t* message_end = cur + len; - cur = parse_ndef_message(parsed_data, message_num, cur, message_end); - if(cur != message_end) cur = end; + return parsed > 0; +} - break; - } +#elif NDEF_PROTO == NDEF_PROTO_MFC - case 0xFE: // TLV end - cur = end; - if(message_num != 0) parsed = true; - break; +// MFC MAD datasheet: +// https://www.nxp.com/docs/en/application-note/AN10787.pdf +#define AID_SIZE (2) +static const uint64_t mad_key = 0xA0A1A2A3A4A5; - case 0x00: // Padding, has no length, skip - break; +// NDEF on MFC breakdown: +// https://learn.adafruit.com/adafruit-pn532-rfid-nfc/ndef#storing-ndef-messages-in-mifare-sectors-607778 +static const uint8_t ndef_aid[AID_SIZE] = {0x03, 0xE1}; - case 0x01: // Lock control - case 0x02: // Memory control - case 0xFD: // Proprietary - // We don't care, skip this TLV block - if(cur >= end) break; - if(*cur < 0xFF) { // 1 byte length - cur += *cur + 1; // Shift by TLV length - } else { // 3 byte length (0xFF marker + 2 byte integer) - if(cur + 2 >= end) { - cur = end; - break; - } - cur += bit_lib_bytes_to_num_be(cur + 1, 2) + 3; // Shift by TLV length - } - break; +static bool ndef_mfc_parse(const NfcDevice* device, FuriString* parsed_data) { + furi_assert(device); + furi_assert(parsed_data); - default: // Unknown, bail to avoid problems - cur = end; - break; + const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic); + + // Check card type can contain NDEF + if(data->type != MfClassicType1k && data->type != MfClassicType4k && + data->type != MfClassicTypeMini) { + return false; + } + + // Check MADs for what sectors contain NDEF data AIDs + bool sectors_with_ndef[MF_CLASSIC_TOTAL_SECTORS_MAX] = {0}; + const size_t sector_count = mf_classic_get_total_sectors_num(data->type); + const struct { + size_t block; + uint8_t aid_count; + } mads[2] = { + {1, 15}, + {64, 23}, + }; + for(uint8_t mad = 0; mad < COUNT_OF(mads); mad++) { + const size_t block = mads[mad].block; + const size_t sector = mf_classic_get_sector_by_block(block); + if(sector_count <= sector) break; // Skip this MAD if not present + // Check MAD key + const MfClassicSectorTrailer* sector_trailer = + mf_classic_get_sector_trailer_by_sector(data, sector); + const uint64_t sector_key_a = bit_lib_bytes_to_num_be( + sector_trailer->key_a.data, COUNT_OF(sector_trailer->key_a.data)); + if(sector_key_a != mad_key) return false; + // Find NDEF AIDs + for(uint8_t aid_index = 0; aid_index < mads[mad].aid_count; aid_index++) { + const uint8_t* aid = &data->block[block].data[2 + aid_index * AID_SIZE]; + if(memcmp(aid, ndef_aid, AID_SIZE) == 0) { + sectors_with_ndef[aid_index + 1] = true; } } + } + + NDEF_TITLE(device, parsed_data); + + // Calculate how large the data space is, so excluding sector trailers and MAD2. + // Makes sure we stay within this card's actual content when parsing. + // First 32 sectors: 3 data blocks, 1 sector trailer. + // Sector 16 contains MAD2 and we need to skip this. + // So the first 32 sectors correspond to 93 (31*3) data blocks. + // Last 8 sectors: 15 data blocks, 1 sector trailer. + // So the last 8 sectors correspond to 120 (8*15) data blocks. + size_t data_size; + if(sector_count > 32) { + data_size = 93 + (sector_count - 32) * 15; + } else { + data_size = sector_count * 3; + if(sector_count >= 16) { + data_size -= 3; // Skip MAD2 + } + } + data_size *= MF_CLASSIC_BLOCK_SIZE; + + Ndef ndef = { + .output = parsed_data, + .mfc = + { + .blocks = data->block, + .size = data_size, + }, + }; + size_t total_parsed = 0; + + for(size_t sector = 0; sector < sector_count; sector++) { + if(!sectors_with_ndef[sector]) continue; + FURI_LOG_D(TAG, "sector: %d", sector); + size_t string_prev = furi_string_size(parsed_data); + + // Convert real sector number to data block number + // to skip sector trailers and MAD2 + size_t data_block; + if(sector < 32) { + data_block = sector * 3; + if(sector >= 16) { + data_block -= 3; // Skip MAD2 + } + } else { + data_block = 93 + (sector - 32) * 15; + } + FURI_LOG_D(TAG, "data_block: %d", data_block); + size_t data_start = data_block * MF_CLASSIC_BLOCK_SIZE; + size_t parsed = ndef_parse_tlv(&ndef, data_start, data_size - data_start, total_parsed); if(parsed) { + total_parsed += parsed; furi_string_trim(parsed_data, "\n"); furi_string_cat(parsed_data, "\n"); } else { - furi_string_reset(parsed_data); + furi_string_left(parsed_data, string_prev); } - } while(false); + } - return parsed; + if(!total_parsed) { + furi_string_reset(parsed_data); + } + + return total_parsed > 0; } +#elif NDEF_PROTO == NDEF_PROTO_SLIX + +// SLIX NDEF memory layout: +// https://community.nxp.com/pwmxy87654/attachments/pwmxy87654/nfc/7583/1/EEOL_2011FEB16_EMS_RFD_AN_01.pdf +static bool ndef_slix_parse(const NfcDevice* device, FuriString* parsed_data) { + furi_assert(device); + furi_assert(parsed_data); + + const Iso15693_3Data* data = nfc_device_get_data(device, NfcProtocolIso15693_3); + const uint8_t block_size = iso15693_3_get_block_size(data); + const uint16_t block_count = iso15693_3_get_block_count(data); + const uint8_t* blocks = simple_array_cget_data(data->block_data); + + // TODO: Find some way to check for other iso15693 NDEF cards and + // split this to also support non-slix iso15693 NDEF tags + // Rest of the code works on iso15693 too, but uses SLIX layout assumptions + if(block_size != SLIX_BLOCK_SIZE) { + return false; + } + + // Check Capability Container (CC) values + struct { + uint8_t nfc_magic_number; + uint8_t read_write_access : 4; // Reversed due to endianness + uint8_t document_version_number : 4; + uint8_t data_area_size; // Total byte size / 8, includes block 0 + uint8_t mbread_ipread; + }* cc = (void*)&blocks[0 * block_size]; + if(cc->nfc_magic_number != 0xE1) return false; + if(cc->document_version_number != 0x4) return false; + + // Calculate usable data area + const uint8_t* start = &blocks[1 * block_size]; + const uint8_t* end = blocks + (cc->data_area_size * 8); + size_t max_size = block_count * block_size; + end = MIN(end, blocks + max_size); + size_t data_start = 0; + size_t data_size = end - start; + + NDEF_TITLE(device, parsed_data); + + Ndef ndef = { + .output = parsed_data, + .slix = + { + .start = start, + .size = data_size, + }, + }; + size_t parsed = ndef_parse_tlv(&ndef, data_start, data_size - data_start, 0); + + if(parsed) { + furi_string_trim(parsed_data, "\n"); + furi_string_cat(parsed_data, "\n"); + } else { + furi_string_reset(parsed_data); + } + + return parsed > 0; +} + +#endif + +// ---=== boilerplate ===--- + /* Actual implementation of app<>plugin interface */ static const NfcSupportedCardsPlugin ndef_plugin = { - .protocol = NfcProtocolMfUltralight, .verify = NULL, .read = NULL, - .parse = ndef_parse, +#if NDEF_PROTO == NDEF_PROTO_UL + .parse = ndef_ul_parse, + .protocol = NfcProtocolMfUltralight, +#elif NDEF_PROTO == NDEF_PROTO_MFC + .parse = ndef_mfc_parse, + .protocol = NfcProtocolMfClassic, +#elif NDEF_PROTO == NDEF_PROTO_SLIX + .parse = ndef_slix_parse, + .protocol = NfcProtocolSlix, +#endif }; /* Plugin descriptor to comply with basic plugin specification */ @@ -482,3 +1061,5 @@ static const FlipperAppPluginDescriptor ndef_plugin_descriptor = { const FlipperAppPluginDescriptor* ndef_plugin_ep(void) { return &ndef_plugin_descriptor; } + +#endif diff --git a/applications/main/nfc/plugins/supported_cards/plantain.c b/applications/main/nfc/plugins/supported_cards/plantain.c index 59253194e..d57545957 100644 --- a/applications/main/nfc/plugins/supported_cards/plantain.c +++ b/applications/main/nfc/plugins/supported_cards/plantain.c @@ -201,8 +201,9 @@ static bool plantain_read(Nfc* nfc, NfcDevice* device) { static bool plantain_parse(const NfcDevice* device, FuriString* parsed_data) { furi_assert(device); - + size_t uid_len = 0; const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic); + const uint8_t* uid = mf_classic_get_uid(data, &uid_len); bool parsed = false; @@ -220,12 +221,30 @@ static bool plantain_parse(const NfcDevice* device, FuriString* parsed_data) { if(key != cfg.keys[cfg.data_sector].a) break; furi_string_printf(parsed_data, "\e#Plantain card\n"); + + const uint8_t* temp_ptr = &uid[0]; + + // UID is read from last to first byte + uint8_t card_number_tmp[uid_len]; + + if(uid_len == 4) { + for(size_t i = 0; i < 4; i++) { + card_number_tmp[i] = temp_ptr[3 - i]; + } + } else if(uid_len == 7) { + for(size_t i = 0; i < 7; i++) { + card_number_tmp[i] = temp_ptr[6 - i]; + } + } else { + break; + } + //UID is converted to a card number uint64_t card_number = 0; - for(size_t i = 0; i < 7; i++) { - card_number = (card_number << 8) | data->block[0].data[6 - i]; + for(size_t i = 0; i < uid_len; i++) { + card_number = (card_number << 8) | card_number_tmp[i]; } - // Print card number with 4-digit groups + // Print card number with 4-digit groups. "3" in "3078" denotes a ticket type "3 - full ticket", will differ on discounted cards. furi_string_cat_printf(parsed_data, "Number: "); FuriString* card_number_s = furi_string_alloc(); furi_string_cat_printf(card_number_s, "%lld", card_number); @@ -237,6 +256,7 @@ static bool plantain_parse(const NfcDevice* device, FuriString* parsed_data) { furi_string_push_back(tmp_s, ' '); } furi_string_cat_printf(parsed_data, "%s\n", furi_string_get_cstr(tmp_s)); + // this works for 2K Plantain if(data->type == MfClassicType1k) { //balance uint32_t balance = 0; @@ -290,20 +310,70 @@ static bool plantain_parse(const NfcDevice* device, FuriString* parsed_data) { last_payment_date.year, last_payment_date.hour, last_payment_date.minute); - //payment summ + //payment amount. This needs to be investigated more, currently it shows incorrect amount on some cards. uint16_t last_payment = (data->block[18].data[9] << 8) | data->block[18].data[8]; furi_string_cat_printf(parsed_data, "Amount: %d rub", last_payment / 100); furi_string_free(card_number_s); furi_string_free(tmp_s); + //This is for 4K Plantains. } else if(data->type == MfClassicType4k) { + //balance + uint32_t balance = 0; + for(uint8_t i = 0; i < 4; i++) { + balance = (balance << 8) | data->block[16].data[3 - i]; + } + furi_string_cat_printf(parsed_data, "Balance: %ld rub\n", balance / 100); + //trips - uint8_t trips_metro = data->block[36].data[0]; - uint8_t trips_ground = data->block[36].data[1]; + uint8_t trips_metro = data->block[21].data[0]; + uint8_t trips_ground = data->block[21].data[1]; furi_string_cat_printf(parsed_data, "Trips: %d\n", trips_metro + trips_ground); + //trip time + uint32_t last_trip_timestamp = 0; + for(uint8_t i = 0; i < 3; i++) { + last_trip_timestamp = (last_trip_timestamp << 8) | data->block[21].data[4 - i]; + } + DateTime last_trip = {0}; + from_minutes_to_datetime(last_trip_timestamp + 24 * 60, &last_trip, 2010); + furi_string_cat_printf( + parsed_data, + "Trip start: %02d.%02d.%04d %02d:%02d\n", + last_trip.day, + last_trip.month, + last_trip.year, + last_trip.hour, + last_trip.minute); + //validator + uint16_t validator = (data->block[20].data[5] << 8) | data->block[20].data[4]; + furi_string_cat_printf(parsed_data, "Validator: %d\n", validator); + //tariff + uint16_t fare = (data->block[20].data[7] << 8) | data->block[20].data[6]; + furi_string_cat_printf(parsed_data, "Tariff: %d rub\n", fare / 100); //trips in metro furi_string_cat_printf(parsed_data, "Trips (Metro): %d\n", trips_metro); //trips on ground furi_string_cat_printf(parsed_data, "Trips (Ground): %d\n", trips_ground); + //last payment + uint32_t last_payment_timestamp = 0; + for(uint8_t i = 0; i < 3; i++) { + last_payment_timestamp = (last_payment_timestamp << 8) | + data->block[18].data[4 - i]; + } + DateTime last_payment_date = {0}; + from_minutes_to_datetime(last_payment_timestamp + 24 * 60, &last_payment_date, 2010); + furi_string_cat_printf( + parsed_data, + "Last pay: %02d.%02d.%04d %02d:%02d\n", + last_payment_date.day, + last_payment_date.month, + last_payment_date.year, + last_payment_date.hour, + last_payment_date.minute); + //payment amount + uint16_t last_payment = (data->block[18].data[9] << 8) | data->block[18].data[8]; + furi_string_cat_printf(parsed_data, "Amount: %d rub", last_payment / 100); + furi_string_free(card_number_s); + furi_string_free(tmp_s); } parsed = true; } while(false); diff --git a/applications/main/nfc/plugins/supported_cards/trt.c b/applications/main/nfc/plugins/supported_cards/trt.c index 5407ab1f7..793ad7f2d 100644 --- a/applications/main/nfc/plugins/supported_cards/trt.c +++ b/applications/main/nfc/plugins/supported_cards/trt.c @@ -40,7 +40,10 @@ static bool trt_parse(const NfcDevice* device, FuriString* parsed_data) { const uint8_t* full_record_pointer = &data->page[FULL_SALE_TIME_STAMP_PAGE].data[0]; uint32_t latest_sale_record = bit_lib_get_bits_32(partial_record_pointer, 3, 20); uint32_t latest_sale_full_record = bit_lib_get_bits_32(full_record_pointer, 0, 27); - if(latest_sale_record != (latest_sale_full_record & 0xFFFFF)) break; + if(latest_sale_record != (latest_sale_full_record & 0xFFFFF)) + break; // check if the copy matches + if((latest_sale_record == 0) || (latest_sale_full_record == 0)) + break; // prevent false positive // Parse date // yyy yyyymmmm dddddhhh hhnnnnnn diff --git a/applications/main/nfc/views/dict_attack.c b/applications/main/nfc/views/dict_attack.c index 4efbe6d60..726076972 100644 --- a/applications/main/nfc/views/dict_attack.c +++ b/applications/main/nfc/views/dict_attack.c @@ -45,6 +45,7 @@ static void dict_attack_draw_callback(Canvas* canvas, void* model) { furi_string_set(m->header, "PRNG Analysis"); break; case MfClassicNestedPhaseDictAttack: + case MfClassicNestedPhaseDictAttackVerify: case MfClassicNestedPhaseDictAttackResume: furi_string_set(m->header, "Nested Dictionary"); break; @@ -91,6 +92,7 @@ static void dict_attack_draw_callback(Canvas* canvas, void* model) { float dict_progress = 0; if(m->nested_phase == MfClassicNestedPhaseAnalyzePRNG || m->nested_phase == MfClassicNestedPhaseDictAttack || + m->nested_phase == MfClassicNestedPhaseDictAttackVerify || m->nested_phase == MfClassicNestedPhaseDictAttackResume) { // Phase: Nested dictionary attack uint8_t target_sector = diff --git a/applications/main/subghz/helpers/subghz_custom_event.h b/applications/main/subghz/helpers/subghz_custom_event.h index 86723e5d2..a1bc99998 100644 --- a/applications/main/subghz/helpers/subghz_custom_event.h +++ b/applications/main/subghz/helpers/subghz_custom_event.h @@ -89,6 +89,7 @@ typedef enum { SetTypeAllmatic433, SetTypeAllmatic868, SetTypeCenturion433, + SetTypeMonarch433, SetTypeSommer_FM_434, SetTypeSommer_FM_868, SetTypeSommer_FM238_434, diff --git a/applications/main/subghz/resources/subghz/assets/keeloq_mfcodes b/applications/main/subghz/resources/subghz/assets/keeloq_mfcodes index 0db72444e..23b7a031a 100644 --- a/applications/main/subghz/resources/subghz/assets/keeloq_mfcodes +++ b/applications/main/subghz/resources/subghz/assets/keeloq_mfcodes @@ -1,60 +1,61 @@ Filetype: Flipper SubGhz Keystore File Version: 0 Encryption: 1 -IV: 41 72 65 20 79 6F 75 20 61 20 46 75 72 72 79 3F -8B441920A9F30EA40001F37E0F3C0D72161467678511928D219A7DBC42F8D9B7 -1ADE5B8541491C46674EECC05A1C4297CB28259954FD7C6DA0EAADE8D57773FC -0497A50135FA4468B49694974DAADEF2A9CC2B7BAFC8465E0AA58E940C085850 -4BC77520AE09B4F6A5A5365B6B9C16CA53566E096159A98EF0AB6E7FE5C2DD1C -B674AC1674312E74471E44917F6FC049DAB48BBB65B84F14A4CD64223A49E93DC0C507B8E17BFFDCBB67B55F1B4091C6 -FBBE4E4F703A9C1F17268F73EC00A3F76517A4470783363102D2B7647F0F2E7F -21BE8A51F32CE62DA6B5E207E627D224639877860C71933C4F54AC7BD742B265 -74E3EA89CC7AA99855C1BC87734506413B882CF36D68C3329022415EB29D1360 -567937E84052F230610B3A2FC26CA1BE32B9617F7554EF5E6E7CB35A76718ADA -4C34F7FE5D6EAE40B0EB6BAB7DE4F0D059C32E4F312F9C62F2C50C54C5486276 -74C8656F8EA033C60F3183FA0923C9AF1EB6689FAACC39E6548ECE16E3312D39DCF45AC6EC70A70421FBFFA43D91E19E -275903678A81B6E62CD2BB4C50E93CB3E4B092E2894EBAA04AC4E0244E4D5BA9FCDFC6BCF845EA41DF0691BF2AEDF300 -BFE52324D666ADEB3D33D94331376B33A3D31566331DE0F372548FE7255F8085 -8E1B2E82B4C78360B3174741FB8031EE0DFEB479859BE8708F8AF40C5228BFDF -6014A4A9D9703DAC11DA6B93FB7A0E0E0856E9379148F3CC125D77BA4C3D4931 -B8C22EA72B35E60A7A3DD02956D2375EE7C07E243F17F413CA4AC3A1445AA250 -B545037B8798370A8638B7ADCBE9EEF748F73C89EB5E04B460057BAFA52B1D7DF29B009FBB3FA07CE87247D185BE4926 -09AEA3223E4FFB06333425BA977403E7C2F9B5F121FDA8313E73FE98D0041425 -E7A97F65883E4D3CDA02483738219E219EED0681C2BB140EF3E5D02230433D42 -4AE67B9EA288183047A9498F58978E7C41BB8905533F42FF5B94E41AFEFF0EB4907A8C472B9E32F6D659CFA6C391179B -1F9752FBA7A7A00D518D91BA8A8C1ADEF2EDF6282A5AFB2E4332B4351CF67F18317D2BCFF2DA6B5216EA138A4CCBDB81 -EDC508FE0D0E894B529C5E4AB82A7188072592BE9D4D9A3D2485822A9F4C5A67 -780582645A71D3A9841B0E8F5E84A2285F783872102AA1A98A956B98AEA19421 -37E2858B90868BCAA6B3198C3B205DC1413A9D378AF1671BEA3B8F47FCE1C5CE -229719C5AFE1E567B8BD5F07EFF89C30B11CB79C9C841FFE7B53404CA5422EDB -CDA16E59B8C24DBE95ABF978CAD4D04A4F124DED1965124F92AACA4C5AEB8721 -5C61D5C655DD9EB78C4F8E965101F98D7899155D2379EF6553FA9DEA0302A232 -7D3DCFDE942398244624C7ABFE08363404BA688EDA39D45A05BB6FB982BB9042 -ECCB10BCB2A8CF2093C61634E9AFEE9B69EB4A7C8B7139061730B5FA869F65BF -7AE6C19ED3C97F45D1167FC9EF52CCE321EA002D31D8AEA94106A2F711626551 -793A984F6EBD35253C319FAB671348A4C6843AEA7CCCD5A52ACA9BBE96484B5E -BB3ADBB5FD60F0FB01E5A8B1610D30A24975DC67ED1B70472E509218E213393E -BB971C6304723B9928F50FA2E5C30A48ED5527556EE1BA5A7EA75AB7A4ED4137 -489579673025F7A351072EB8D5C7FC1DBF651AA6233FB01D99DAC4BDC0A60F2F -ACA000B78B091A9B2621A9FA1A50FA6A3B63DDDF7D966224837028F93D570C08 -C1EB4EA179EC0437D5B0E72C47AAD06803F7619C53D3D2FEF38DD3C068118766B73202B75C534C6249312245218C9285 -639C6F53600C0F01410361164FEF715DB8E08C68D68B8AE76CF2D09A0C6C4CA1 -F5F2D77DD3AF60489312055EBA3B511F7D9E312F02465639FFF3E71A862BB9EB -3EC1F7B2C43646249C4D14B4B1B85B3416B8D3B0B8DA9E87BD6199DC27BD45C2 -AA24DB7345317FE980C917909F364EAA13591A664E0893271E9DBCC250782EA5 -32C18DBA479AD57BFF9FE7B7BD1022D4969D8E2EC99BC0799148809796E227D5 -494B44697DACDFDBBFEB0FDBEF6E9B5D7291B6CB43B4C93088F3ED8632AF23B5 -B79CC1A90C7AC36DA2F318BF69E6BB1FAC32C178453663069C0CF2C4AE77B211 -A059D006EF98446BDF9E1F5E09641A641CA88348E79499D79BCFDE6646C716A0BE2253421858D327E796EB5237B6E2C4 -9E90AE6138A3676E3E5058FF277331032CD6BBF114768104A33D684B0825C5A5 -7477B55332666FC6B1279F5FDF84DCA01176498984809B74E17AB726F888000F -6A0300B90BC11127A031E62537468A1CA7A0775BE85ED12069074E26181FBA50 -4DAF986F9A850EB18F0FE3B88B9796D80EEC3984C92F4F8B32D513A176CCAA9A -EB84517DCAA7D10A6E7C4D0A7995AE278AAF3FDE52DAE30C7FD3FB853C799369 -0941FB8965604ABC852B94F29396783783632E4C80AEE665E42C233B6E7AA7D0 -71FEF69734589BE3C0893A1584FC1C0A75A5D4BF88CFF323C8C68E0EEC06993C -790A26B77912EE07BABCDF478D8E492422B73465DC209E5DBC8E31F43AA2DA7448EAB6312AC1DC4FE3F20D7A2EC93968 -CE0AA966F33B7AC9E9982D2241524F2FC7613DAAC6C5C768AAAE29A630B414CE -93FA632D945E18DE6358D4F443CB6F023C640407B7FAD6E8AF6EF15139B044E3 -059ABA7A866A865159D4E0B3E30FA1900BAB2BAB82A1D1F9D74CAEC11FA95393 -9C5A4CF1D49E0F741577BAB868575739AEBBDC15D7178C87A46EA3B3E7E856EFD815AD5F998778E8FBAA2832F387B967 +IV: 46 75 72 72 79 20 63 68 65 63 6B 20 3F 3F 21 21 +3FF98B52C38558ECDB26C3E86D118FAC9AF22EDDDE7649CB53726855CEBCCF9E +06B901C6E36E381B4A83554ED972288977F999C232106D337C3BE4A9A44608F8 +8153E6CAF4B272BA0EA168B9F0F29CF02CEB33E4ABBD4D5C858D1ADBFF474A25 +F61216CE3A7E500E0C9B2173F91C2E7B1BB7D7AA65D4150EAC28169116647DDD +A3ABC262415035A190447EC9C15CFD1AB1720560AB7C82D7447215342305299732776A9A6DEA4D359C52A23BFCE6B015 +5784D0E77A55E1361F47A1F6CFFBFAF715CFCAD2BD502ED266AA86DC47F98541 +E082BF64C75023F23FB333C53F2590F408FD932EB71DF4ED4D0E0EAF2AEBD488 +EFF328889D57D9F4B9111918F9C9BC641645104223009842FFF7B6F73E24E5B7 +BDCD1DBCBCA789C5A5C3623C0A287D791F1CACCBBD7D144E2EF1F92DA5513D90 +6310362BE6ED7CB5E3F4CA84D3093DF620BABD2C9D419A2BA5AA05F241EE7592 +4C6F97C553276D5403103A6BA6C4DBBE017A9E4159E6E5AC4C28ACD645DB1E8D4551CDC228A0457BEEF49179A081E1FD +861ECA071AEEF3854005FEC9BF22C7DB76D07B7930968314FFAC70995E28680E473F8A1A7E56E8776EDD1C6E2DD6DD26 +55ADDFBE97BCB0FB43EDEB9CAFF9F32DD788A6306D3702BC2923DA5C69F5F0A5 +AB838DA2B25547AA43DEB7FB40B15E289B9209057BE564E7CB78F0D5DCFC65FD +21BACE924E522AD0E97F0AAF64A9DB6A5F7BA09B3A1759AAFD4016F3ADD4353D +4B37D449E44BAE7F377EE5CE52E94882E42617417F77ECE9803D9435892167F2 +E63BA93D98BB3EEEAD34B38C5F271FB777AEABD9E6A05597AE09815E578AA811900AE9F144FBEC7DBFDDEC87D1ED368B +7DF24DCB7D73F1638FFD22325463B23C101653EE770F086D323BE90868A7E267 +18E533990C408061309323675116429652B3F4D228F00D704310E7AB26F6BAF4 +26042EC21D73490D9E7F968EDCC4CECF3989E6E982427DEB7012478E214CED8797A9BEBC481C81E9646214809A409B4A +AC4169C26E402720F1E2A0AD690AE2CF708CE203898BC7102178A738C70F361718E794F0D0CB1A2F1938EF35CCA11887 +6962CADB9BD1B86B779DF7F4D7E06E92EB42474C7FD9EA23879F9982D7127357 +23AEC0F6F9FF529DDDCD1CDAA77F7B136CE2CCD3AC8B949954D5D3B2ECFA8CBC +C6A3A849BC4A8DB438065255BD387DCD77AD7E7FEB3B0E11B6D3A43279AB9087 +CCB71F8BDC8B31F36D6141B3B57BF31B7EDF72B87593B7497871F5738A0B7E00 +345FE0FAD3F60C017D9793406981878EA2226072D624208AC33682793B415C41 +78AF1FCFDB780744708DBB547F7C3F095BA5FAFB34FA83BF4323B32829836ED1 +C2F19F077E71E710D39F5F11C1FE55C2A3CD6F33384CD8603288765F266F1BDF +084B1A9565AFC88900D0FF25413E659E17AAB649DB2B7A9F0381CB0DF6D2B8E5 +568CECD994676CF3D6B225DEF21D40787DCB41C101F52B0C5AA43ED66709D158 +0742E0AAB6504AC63A9C59FBDD980BD4C4760EFF3E556C8A6241442CDDD23A74 +37BB60EE11BEC7D25A607DE9B0B6D3EAD23321AFEB94995FAB42184A95D1CADA +42FD71B98A3BEE9CF372B03E158D51181BFAA9CF54F7300A93FE7665402B3D1F +9346B7D12346E264E84F91145EBF86C53DD0061E0FA6556FAE5F6FBB8CEB799E +430B0D7FC09C09051AEB667A370E6D9D9BDF0C0C553AD2791682D43CE5DC48B7 +5BBB5129797BC07CDC1D25A8A919A637B9FFF2F920BD42D1721018ABF8D34959AF877AFF450AD91548184E1A0D991CC8 +0BC8D3E1C2D9A8FB445FC55564471007A28C09C9CE602203176F1BFF02AC6328 +1C4CA912791BCD9CB231C64AB479AF240D02EC9F431D7C479B9A172E5B97F5CD +15A9CDF17E72255DE0942C09E67CD251C3D465246845F9C0B97A7EFEE4AF9110 +F95543395BFF39B0A093AC80D0EF1BEFA218A3769D074250414D1357105A4D34 +138D433824E691C6B67A08CF1DDE55FBCA2D65AC0B7D9320EA1FDCB1742B11BF +3BE3A385F1C0B8922C4E23EFF6912748DF715A4546CECB5E8972C1D1E47D0D3A +9ECA2554ED36700326F2E5140C434ADA8DFF55A53382F19541E9AAE45DA5CBF2 +5C75D528678AF199E191C49F310913F401014F97EEA5FA507C7310B48A98FDE3297EA398B08959951FF99EDDB64C5E0D +22A9C66645B3944925A496D9F2312429CC787B6314948B482EAD9360124F59DA +DA3A8571664DBBFC1DE97B53E7C141554A2FBCEBA980696D32409CC5ADB7FC41 +20A52AABC518FCC2FC75AE3F5CC7C4838AA4973111DA696B890D884A18098D91 +EBB7163F580A1A5D26F12FBE650A227791193BA9AFCE277584B171F2FE1C77CF +86369EE5277CB81B9417B6232F8D994FAEAB34D0D5363B143257C62B10CACAFD +2E2EAB32891E172A3C31D434703480E69793435BB198E6AA06AA066EE8234D85 +745FC576D77C41BAEFF15A822E6B4058A485A2CAA0A3B283928D17AC02299AD9 +1FF8D49F2F7D785D64B6FE365CD9C2BD958E9527F66BB8A85C9AEBC73ECFE064 +3CFB77F3E274C1EB2772CDFA7B5B17255C2554198BE60C058A3405AEC644FABC5AAECC8F9C7F4A4E5B2D5252E8C62628 +AE514C44B55A1A4744E1106FD226C587D1B71CA7B5DCF010265D769E22012866 +2B2D787A4B0F30CAB9CD3DBC7686165637F091B31745CAB53B369A804F76F9EE +EA4279C80F0B4AC0B32AB9E8B8CFB9C25FF81840BEE65B2160F85E56FA576C48 +E41D853750D68643E929F94F46BCDC050040935883E9A0C45BB238CDE06340DD745BDA7D6C16AE2B028E073EDDC0FC49 diff --git a/applications/main/subghz/scenes/subghz_scene_set_type.c b/applications/main/subghz/scenes/subghz_scene_set_type.c index 43bf05044..a03c194f8 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_type.c +++ b/applications/main/subghz/scenes/subghz_scene_set_type.c @@ -28,6 +28,7 @@ static const char* submenu_names[SetTypeMAX] = { [SetTypeAllmatic433] = "KL: Allmatic 433MHz", [SetTypeAllmatic868] = "KL: Allmatic 868MHz", [SetTypeCenturion433] = "KL: Centurion 433MHz", + [SetTypeMonarch433] = "KL: Monarch 433MHz", [SetTypeSommer_FM_434] = "KL: Sommer 434MHz", [SetTypeSommer_FM_868] = "KL: Sommer 868MHz", [SetTypeSommer_FM238_434] = "KL: Sommer fm2 434Mhz", @@ -417,6 +418,16 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { .keeloq.cnt = 0x03, .keeloq.manuf = "Centurion"}; break; + case SetTypeMonarch433: + gen_info = (GenInfo){ + .type = GenKeeloq, + .mod = "AM650", + .freq = 433920000, + .keeloq.serial = (key & 0x0000FFFF), + .keeloq.btn = 0x0A, + .keeloq.cnt = 0x03, + .keeloq.manuf = "Monarch"}; + break; case SetTypeElmesElectronic: gen_info = (GenInfo){ .type = GenKeeloq, diff --git a/applications/services/cli/cli_commands.c b/applications/services/cli/cli_commands.c index 1d21c07f6..a305ae734 100644 --- a/applications/services/cli/cli_commands.c +++ b/applications/services/cli/cli_commands.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -487,13 +488,24 @@ void cli_command_sysctl(Cli* cli, FuriString* args, void* context) { void cli_command_vibro(Cli* cli, FuriString* args, void* context) { UNUSED(cli); UNUSED(context); + if(!furi_string_cmp(args, "0")) { NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); notification_message_block(notification, &sequence_reset_vibro); furi_record_close(RECORD_NOTIFICATION); } else if(!furi_string_cmp(args, "1")) { + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagStealthMode)) { + printf("Flipper is in stealth mode. Unmute the device to control vibration."); + return; + } + NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); - notification_message_block(notification, &sequence_set_vibro_on); + if(notification->settings.vibro_on) { + notification_message_block(notification, &sequence_set_vibro_on); + } else { + printf("Vibro is disabled in settings. Enable it to control vibration."); + } + furi_record_close(RECORD_NOTIFICATION); } else { cli_print_usage("vibro", "<1|0>", furi_string_get_cstr(args)); diff --git a/applications/services/gui/modules/byte_input.c b/applications/services/gui/modules/byte_input.c index 3e23ee6c1..c9f689662 100644 --- a/applications/services/gui/modules/byte_input.c +++ b/applications/services/gui/modules/byte_input.c @@ -726,6 +726,51 @@ static bool byte_input_view_input_callback(InputEvent* event, void* context) { return consumed; } +static bool byte_input_view_ascii_callback(AsciiEvent* event, void* context) { + ByteInput* byte_input = context; + furi_assert(byte_input); + + switch(event->value) { + case AsciiValueDC3: // Right + case AsciiValueDC4: // Left + with_view_model( + byte_input->view, + ByteInputModel * model, + { + if(event->value == AsciiValueDC3) { + byte_input_inc_selected_byte_mini(model); + } else { + byte_input_dec_selected_byte_mini(model); + } + }, + true); + return true; + default: // Look in keyboard + for(size_t r = 0; r < keyboard_row_count; r++) { + const ByteInputKey* row = byte_input_get_row(r); + uint8_t size = byte_input_get_row_size(r); + for(size_t key = 0; key < size; key++) { + char value = row[key].value; + if(event->value == value) { + with_view_model( + byte_input->view, + ByteInputModel * model, + { + model->selected_row = r; + model->selected_column = key; + byte_input_handle_ok(model); + }, + true); + return true; + } + } + } + break; + } + + return false; +} + /** Reset all input-related data in model * * @param model The model @@ -747,6 +792,7 @@ ByteInput* byte_input_alloc(void) { view_allocate_model(byte_input->view, ViewModelTypeLocking, sizeof(ByteInputModel)); view_set_draw_callback(byte_input->view, byte_input_view_draw_callback); view_set_input_callback(byte_input->view, byte_input_view_input_callback); + view_set_ascii_callback(byte_input->view, byte_input_view_ascii_callback); with_view_model( byte_input->view, diff --git a/applications/services/gui/modules/text_input.c b/applications/services/gui/modules/text_input.c index 58c53653a..b4a9ce46e 100644 --- a/applications/services/gui/modules/text_input.c +++ b/applications/services/gui/modules/text_input.c @@ -2,7 +2,6 @@ #include #include #include -#include struct TextInput { View* view; @@ -38,10 +37,10 @@ typedef struct { FuriString* validator_text; bool validator_message_visible; - char extra_symbols[9]; + bool illegal_symbols; bool cursor_select; - size_t cursor_pos; uint8_t selected_keyboard; + size_t cursor_pos; } TextInputModel; static const uint8_t keyboard_origin_x = 1; @@ -102,7 +101,7 @@ static const TextInputKey keyboard_keys_row_3[] = { {'9', 120, 32}, }; -static TextInputKey symbol_keyboard_keys_row_1[] = { +static const TextInputKey symbol_keyboard_keys_row_1[] = { {'!', 2, 8}, {'@', 12, 8}, {'#', 22, 8}, @@ -118,7 +117,7 @@ static TextInputKey symbol_keyboard_keys_row_1[] = { {'3', 120, 8}, }; -static TextInputKey symbol_keyboard_keys_row_2[] = { +static const TextInputKey symbol_keyboard_keys_row_2[] = { {'~', 2, 20}, {'+', 12, 20}, {'-', 22, 20}, @@ -133,7 +132,7 @@ static TextInputKey symbol_keyboard_keys_row_2[] = { {'6', 120, 20}, }; -static TextInputKey symbol_keyboard_keys_row_3[] = { +static const TextInputKey symbol_keyboard_keys_row_3[] = { {SWITCH_KEYBOARD_KEY, 0, 23}, {'.', 15, 32}, {',', 29, 32}, @@ -241,6 +240,33 @@ static char char_to_uppercase(const char letter) { } } +static char char_to_illegal_symbol(char original) { + switch(original) { + default: + return original; + case '0': + return '_'; + case '1': + return '<'; + case '2': + return '>'; + case '3': + return ':'; + case '4': + return '"'; + case '5': + return '/'; + case '6': + return '\\'; + case '7': + return '|'; + case '8': + return '?'; + case '9': + return '*'; + } +} + static void text_input_backspace_cb(TextInputModel* model) { if(model->clear_default_text) { model->text_buffer[0] = 0; @@ -356,7 +382,8 @@ static void text_input_view_draw_callback(Canvas* canvas, void* _model) { keyboard_origin_x + keys[column].x, keyboard_origin_y + keys[column].y - (glyph == '_' || char_is_lowercase(glyph)), - glyph); + (symbols && model->illegal_symbols) ? char_to_illegal_symbol(glyph) : + glyph); } } } @@ -479,6 +506,10 @@ static void text_input_handle_ok(TextInput* text_input, TextInputModel* model, I model->selected_keyboard != symbol_keyboard.keyboard_index) { selected = char_to_uppercase(selected); } + if(model->selected_keyboard == symbol_keyboard.keyboard_index && + model->illegal_symbols) { + selected = char_to_illegal_symbol(selected); + } if(model->clear_default_text) { model->text_buffer[0] = selected; model->text_buffer[1] = '\0'; @@ -622,35 +653,34 @@ static bool text_input_view_ascii_callback(AsciiEvent* event, void* context) { true); return true; default: // Look in keyboards + TextInputModel* model = view_get_model(text_input->view); + uint8_t text_length = model->text_buffer ? strlen(model->text_buffer) : 0; + bool uppercase = model->clear_default_text || text_length == 0; for(size_t k = 0; k < keyboard_count; k++) { + bool symbols = k == symbol_keyboard.keyboard_index; const Keyboard* keyboard = keyboards[k]; for(size_t r = 0; r < keyboard_row_count; r++) { const TextInputKey* row = get_row(keyboard, r); uint8_t size = get_row_size(keyboard, r); for(size_t key = 0; key < size; key++) { char lower = row[key].text; - char upper = char_to_uppercase(lower); + if(symbols && model->illegal_symbols) lower = char_to_illegal_symbol(lower); + char upper = symbols ? lower : char_to_uppercase(lower); if(event->value == lower || event->value == upper) { - with_view_model( - text_input->view, - TextInputModel * model, - { - model->cursor_select = false; - model->selected_keyboard = k; - model->selected_row = r; - model->selected_column = key; - bool shift = - (event->value == upper) != - (model->clear_default_text || strlen(model->text_buffer) == 0); - text_input_handle_ok( - text_input, model, shift ? InputTypeLong : InputTypeShort); - }, - true); + model->cursor_select = false; + model->selected_keyboard = k; + model->selected_row = r; + model->selected_column = key; + bool shift = (event->value == upper) != uppercase && !symbols; + text_input_handle_ok( + text_input, model, shift ? InputTypeLong : InputTypeShort); + view_commit_model(text_input->view, true); return true; } } } } + view_commit_model(text_input->view, false); break; } @@ -668,33 +698,6 @@ void text_input_timer_callback(void* context) { true); } -static void reset_extra_symbols(TextInputModel* model) { - memset(model->extra_symbols, 0, sizeof(model->extra_symbols)); - size_t symbol = 0; - for(size_t row = 0; row < 3; row++) { - size_t size = get_row_size(&symbol_keyboard, row) - 3; - for(size_t i = 0; i < 3; i++) { - FURI_CONST_ASSIGN_( - char, get_row(&symbol_keyboard, row)[size++].text, '1' + (symbol++)); - } - } - FURI_CONST_ASSIGN_( - char, get_row(&symbol_keyboard, 0)[get_row_size(&symbol_keyboard, 0) - 4].text, '0'); -} - -static void apply_extra_symbols(TextInputModel* model) { - size_t symbol = 0; - for(size_t row = 0; row < 3; row++) { - size_t size = get_row_size(&symbol_keyboard, row) - 3; - for(size_t i = 0; i < 3; i++) { - FURI_CONST_ASSIGN_( - char, get_row(&symbol_keyboard, row)[size++].text, model->extra_symbols[symbol++]); - } - } - FURI_CONST_ASSIGN_( - char, get_row(&symbol_keyboard, 0)[get_row_size(&symbol_keyboard, 0) - 4].text, '_'); -} - TextInput* text_input_alloc(void) { TextInput* text_input = malloc(sizeof(TextInput)); text_input->view = view_alloc(); @@ -712,7 +715,7 @@ TextInput* text_input_alloc(void) { { model->validator_text = furi_string_alloc(); model->minimum_length = 1; - reset_extra_symbols(model); + model->illegal_symbols = false; model->cursor_pos = 0; model->cursor_select = false; }, @@ -752,7 +755,7 @@ void text_input_reset(TextInput* text_input) { model->selected_column = 0; model->selected_keyboard = 0; model->minimum_length = 1; - reset_extra_symbols(model); + model->illegal_symbols = false; model->clear_default_text = false; model->cursor_pos = 0; model->cursor_select = false; @@ -813,43 +816,10 @@ void text_input_set_minimum_length(TextInput* text_input, size_t minimum_length) true); } -void text_input_add_extra_symbol(TextInput* text_input, char symbol) { - furi_check(text_input); - if(!symbol) return; - with_view_model( - text_input->view, - TextInputModel * model, - { - for(size_t i = 0; i < sizeof(model->extra_symbols); i++) { - if(!model->extra_symbols[i]) { - model->extra_symbols[i] = symbol; - apply_extra_symbols(model); - break; - } - } - }, - true); -} - -void text_input_add_illegal_symbols(TextInput* text_input) { +void text_input_show_illegal_symbols(TextInput* text_input, bool show) { furi_check(text_input); with_view_model( - text_input->view, - TextInputModel * model, - { - size_t i = 0; - model->extra_symbols[i++] = '<'; - model->extra_symbols[i++] = '>'; - model->extra_symbols[i++] = ':'; - model->extra_symbols[i++] = '"'; - model->extra_symbols[i++] = '/'; - model->extra_symbols[i++] = '\\'; - model->extra_symbols[i++] = '|'; - model->extra_symbols[i++] = '?'; - model->extra_symbols[i++] = '*'; - apply_extra_symbols(model); - }, - true); + text_input->view, TextInputModel * model, { model->illegal_symbols = show; }, true); } void text_input_set_validator( diff --git a/applications/services/gui/modules/text_input.h b/applications/services/gui/modules/text_input.h index 7656a21bf..6353d9732 100644 --- a/applications/services/gui/modules/text_input.h +++ b/applications/services/gui/modules/text_input.h @@ -77,11 +77,12 @@ void text_input_set_validator( TextInputValidatorCallback callback, void* callback_context); -// Add up to 9 extra characters for symbol keyboard -void text_input_add_extra_symbol(TextInput* text_input, char symbol); - -// Add the 9 predefined illegal (windows) symbols <>:"/\|?* -void text_input_add_illegal_symbols(TextInput* text_input); +/** + * @brief Show the 9 illegal (windows) symbols <>:"/\|?* in the symbols keyboard instead of the numbers + * @param [in] text_input TextInput + * @param [in] show Whether to show the illegal symbols or not + */ +void text_input_show_illegal_symbols(TextInput* text_input, bool show); TextInputValidatorCallback text_input_get_validator_callback(TextInput* text_input); diff --git a/applications/services/rpc/rpc_gui.c b/applications/services/rpc/rpc_gui.c index 447156bc9..70c5f1463 100644 --- a/applications/services/rpc/rpc_gui.c +++ b/applications/services/rpc/rpc_gui.c @@ -269,6 +269,37 @@ static void rpc_send_and_release_empty(session, request->command_id, PB_CommandStatus_OK); } +static void + rpc_system_gui_send_ascii_event_request_process(const PB_Main* request, void* context) { + furi_assert(request); + furi_assert(request->which_content == PB_Main_gui_send_ascii_event_request_tag); + furi_assert(context); + + FURI_LOG_D(TAG, "SendAsciiEvent"); + + RpcGuiSystem* rpc_gui = context; + RpcSession* session = rpc_gui->session; + furi_assert(session); + + bool is_valid = (request->content.gui_send_ascii_event_request.value <= 0xFF); + + if(!is_valid) { + rpc_send_and_release_empty( + session, request->command_id, PB_CommandStatus_ERROR_INVALID_PARAMETERS); + return; + } + + AsciiEvent event = { + .value = request->content.gui_send_ascii_event_request.value, + }; + + // Submit event + FuriPubSub* ascii_events = furi_record_open(RECORD_ASCII_EVENTS); + furi_pubsub_publish(ascii_events, &event); + furi_record_close(RECORD_ASCII_EVENTS); + rpc_send_and_release_empty(session, request->command_id, PB_CommandStatus_OK); +} + static void rpc_system_gui_virtual_display_render_callback(Canvas* canvas, void* context) { furi_assert(canvas); furi_assert(context); @@ -459,6 +490,9 @@ void* rpc_system_gui_alloc(RpcSession* session) { rpc_handler.message_handler = rpc_system_gui_send_input_event_request_process; rpc_add_handler(session, PB_Main_gui_send_input_event_request_tag, &rpc_handler); + rpc_handler.message_handler = rpc_system_gui_send_ascii_event_request_process; + rpc_add_handler(session, PB_Main_gui_send_ascii_event_request_tag, &rpc_handler); + rpc_handler.message_handler = rpc_system_gui_start_virtual_display_process; rpc_add_handler(session, PB_Main_gui_start_virtual_display_request_tag, &rpc_handler); diff --git a/applications/services/rpc/rpc_storage.c b/applications/services/rpc/rpc_storage.c index 79b91d947..10261461c 100644 --- a/applications/services/rpc/rpc_storage.c +++ b/applications/services/rpc/rpc_storage.c @@ -629,7 +629,7 @@ static void rpc_system_storage_rename_process(const PB_Main* request, void* cont rpc_system_storage_reset_state(rpc_storage, session, true); if(path_contains_only_ascii(request->content.storage_rename_request.new_path)) { - FS_Error error = storage_common_rename_safe( + FS_Error error = storage_common_rename( rpc_storage->api, request->content.storage_rename_request.old_path, request->content.storage_rename_request.new_path); diff --git a/applications/system/js_app/application.fam b/applications/system/js_app/application.fam index ef8105d6b..38278212e 100644 --- a/applications/system/js_app/application.fam +++ b/applications/system/js_app/application.fam @@ -53,7 +53,7 @@ App( appid="js_gui", apptype=FlipperAppType.PLUGIN, entry_point="js_gui_ep", - requires=["js_app", "js_event_loop"], + requires=["js_app"], sources=["modules/js_gui/js_gui.c", "modules/js_gui/js_gui_api_table.cpp"], ) @@ -61,7 +61,7 @@ App( appid="js_gui__loading", apptype=FlipperAppType.PLUGIN, entry_point="js_view_loading_ep", - requires=["js_app", "js_gui", "js_event_loop"], + requires=["js_app"], sources=["modules/js_gui/loading.c"], ) @@ -69,7 +69,7 @@ App( appid="js_gui__empty_screen", apptype=FlipperAppType.PLUGIN, entry_point="js_view_empty_screen_ep", - requires=["js_app", "js_gui", "js_event_loop"], + requires=["js_app"], sources=["modules/js_gui/empty_screen.c"], ) @@ -77,7 +77,7 @@ App( appid="js_gui__submenu", apptype=FlipperAppType.PLUGIN, entry_point="js_view_submenu_ep", - requires=["js_app", "js_gui"], + requires=["js_app"], sources=["modules/js_gui/submenu.c"], ) @@ -85,7 +85,7 @@ App( appid="js_gui__text_input", apptype=FlipperAppType.PLUGIN, entry_point="js_view_text_input_ep", - requires=["js_app", "js_gui", "js_event_loop"], + requires=["js_app"], sources=["modules/js_gui/text_input.c"], ) @@ -93,7 +93,7 @@ App( appid="js_gui__byte_input", apptype=FlipperAppType.PLUGIN, entry_point="js_view_byte_input_ep", - requires=["js_app", "js_gui", "js_event_loop"], + requires=["js_app"], sources=["modules/js_gui/byte_input.c"], ) @@ -149,7 +149,7 @@ App( appid="js_gpio", apptype=FlipperAppType.PLUGIN, entry_point="js_gpio_ep", - requires=["js_app", "js_event_loop"], + requires=["js_app"], sources=["modules/js_gpio.c"], ) @@ -208,3 +208,19 @@ App( requires=["js_app"], sources=["modules/js_usbdisk/*.c"], ) + +App( + appid="js_i2c", + apptype=FlipperAppType.PLUGIN, + entry_point="js_i2c_ep", + requires=["js_app"], + sources=["modules/js_i2c.c"], +) + +App( + appid="js_spi", + apptype=FlipperAppType.PLUGIN, + entry_point="js_spi_ep", + requires=["js_app"], + sources=["modules/js_spi.c"], +) diff --git a/applications/system/js_app/examples/apps/Scripts/Examples/badusb_demo.js b/applications/system/js_app/examples/apps/Scripts/Examples/badusb_demo.js index 758cffd13..d1ace3845 100644 --- a/applications/system/js_app/examples/apps/Scripts/Examples/badusb_demo.js +++ b/applications/system/js_app/examples/apps/Scripts/Examples/badusb_demo.js @@ -45,7 +45,7 @@ eventLoop.subscribe(views.dialog.input, function (_sub, button, eventLoop, gui) badusb.println("Flipper Model: " + flipper.getModel()); badusb.println("Flipper Name: " + flipper.getName()); - badusb.println("Battery level: " + toString(flipper.getBatteryCharge()) + "%"); + badusb.println("Battery level: " + flipper.getBatteryCharge().toString() + "%"); // Alt+Numpad method works only on Windows!!! badusb.altPrintln("This was printed with Alt+Numpad method!"); diff --git a/applications/system/js_app/examples/apps/Scripts/Examples/blebeacon.js b/applications/system/js_app/examples/apps/Scripts/Examples/blebeacon.js index 9a30ad151..1187f1c20 100644 --- a/applications/system/js_app/examples/apps/Scripts/Examples/blebeacon.js +++ b/applications/system/js_app/examples/apps/Scripts/Examples/blebeacon.js @@ -45,7 +45,7 @@ function sendRandomModelAdvertisement() { blebeacon.start(); - print("Sent data for model ID " + toString(model)); + print("Sent data for model ID " + model.toString()); currentIndex = (currentIndex + 1) % watchValues.length; diff --git a/applications/system/js_app/examples/apps/Scripts/Examples/gui.js b/applications/system/js_app/examples/apps/Scripts/Examples/gui.js index 10e88b649..a1e023853 100644 --- a/applications/system/js_app/examples/apps/Scripts/Examples/gui.js +++ b/applications/system/js_app/examples/apps/Scripts/Examples/gui.js @@ -17,18 +17,16 @@ let views = { empty: emptyView.make(), keyboard: textInputView.makeWith({ header: "Enter your name", - defaultText: flipper.getName(), - defaultTextClear: true, - // Props for makeWith() are passed in reverse order, so maxLength must be after defaultText minLength: 0, maxLength: 32, + defaultText: flipper.getName(), + defaultTextClear: true, }), helloDialog: dialogView.make(), bytekb: byteInputView.makeWith({ header: "Look ma, I'm a header text!", - defaultData: Uint8Array([0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88]), - // Props for makeWith() are passed in reverse order, so length must be after defaultData length: 8, + defaultData: Uint8Array([0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88]), }), longText: textBoxView.makeWith({ text: "This is a very long string that demonstrates the TextBox view. Use the D-Pad to scroll backwards and forwards.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse rhoncus est malesuada quam egestas ultrices. Maecenas non eros a nulla eleifend vulputate et ut risus. Quisque in mauris mattis, venenatis risus eget, aliquam diam. Fusce pretium feugiat mauris, ut faucibus ex volutpat in. Phasellus volutpat ex sed gravida consectetur. Aliquam sed lectus feugiat, tristique lectus et, bibendum lacus. Ut sit amet augue eu sapien elementum aliquam quis vitae tortor. Vestibulum quis commodo odio. In elementum fermentum massa, eu pellentesque nibh cursus at. Integer eleifend lacus nec purus elementum sodales. Nulla elementum neque urna, non vulputate massa semper sed. Fusce ut nisi vitae dui blandit congue pretium vitae turpis.", @@ -97,7 +95,7 @@ eventLoop.subscribe(views.bytekb.input, function (_sub, data, gui, views) { let data_view = Uint8Array(data); let text = "0x"; for (let i = 0; i < data_view.length; i++) { - text += toString(data_view[i], 16); + text += data_view[i].toString(16); } views.helloDialog.set("text", "You typed:\n" + text); views.helloDialog.set("center", "Cool!"); diff --git a/applications/system/js_app/examples/apps/Scripts/Examples/i2c.js b/applications/system/js_app/examples/apps/Scripts/Examples/i2c.js new file mode 100644 index 000000000..456b44ccd --- /dev/null +++ b/applications/system/js_app/examples/apps/Scripts/Examples/i2c.js @@ -0,0 +1,48 @@ +// Connect an 24C32N EEPROM to the I2C bus of the board. SDA=pin 15, SCL=pin 16, VCC=pin 9, GND=pin 8. +let i2c = require("i2c"); + +function i2c_find_first_device() { + let addr = -1; + for (let try_addr = 0; try_addr !== 0xff; try_addr++) { + if (i2c.isDeviceReady(try_addr, 5)) { + addr = try_addr; + break; + } + } + return addr; +} + +let addr = i2c_find_first_device(); +if (addr === -1) { + print("I2C device not found"); + print("Please connect a 24C32N EEPROM I2C device to the Flipper Zero."); + print("SDA=pin 15, SCL=pin 16, VCC=pin 9, GND=pin 8."); +} else { + print("I2C device found at address: " + addr.toString(16)); + delay(1000); + + // first two bytes are the start address (0x0000) + // the remaining bytes are the data to store. + // can also use Uint8Array([0x00, 0x00, ...]) as write parameter + i2c.write(addr, [0x00, 0x00, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47]); + while (i2c.isDeviceReady(addr, 9) === false) { + print("Waiting for device to be ready..."); + } + + // write the address to read from (we start at address 0x0001) + // read 3 bytes - 0x42, 0x43, 0x44 + let data_buf = i2c.writeRead(addr, [0x00, 0x01], 3, 100); + let data = Uint8Array(data_buf); + print("Read bytes: " + data.length.toString()); + for (let i = 0; i < data.length; i++) { + print("data[" + i.toString() + "] = " + data[i].toString(16)); + } + + // read two more bytes (0x45, 0x46) from current address + data_buf = i2c.read(addr, 2); + data = Uint8Array(data_buf); + print("Read bytes: " + data.length.toString()); + for (let i = 0; i < data.length; i++) { + print("data[" + i.toString() + "] = " + data[i].toString(16)); + } +} diff --git a/applications/system/js_app/examples/apps/Scripts/Examples/load.js b/applications/system/js_app/examples/apps/Scripts/Examples/load.js index 3b8f2d8fd..82b2d2046 100644 --- a/applications/system/js_app/examples/apps/Scripts/Examples/load.js +++ b/applications/system/js_app/examples/apps/Scripts/Examples/load.js @@ -1,3 +1,3 @@ -let math = load(__dirpath + "/load_api.js"); +let math = load(__dirname + "/load_api.js"); let result = math.add(5, 10); print(result); diff --git a/applications/system/js_app/examples/apps/Scripts/Examples/path.js b/applications/system/js_app/examples/apps/Scripts/Examples/path.js index 1f50717a7..0be31b81d 100644 --- a/applications/system/js_app/examples/apps/Scripts/Examples/path.js +++ b/applications/system/js_app/examples/apps/Scripts/Examples/path.js @@ -1,8 +1,8 @@ let storage = require("storage"); -print("script has __dirpath of" + __dirpath); -print("script has __filepath of" + __filepath); -if (storage.fileExists(__dirpath + "/math.js")) { +print("script has __dirname of" + __dirname); +print("script has __filename of" + __filename); +if (storage.fileExists(__dirname + "/math.js")) { print("math.js exist here."); } else { print("math.js does not exist here."); diff --git a/applications/system/js_app/examples/apps/Scripts/Examples/spi.js b/applications/system/js_app/examples/apps/Scripts/Examples/spi.js new file mode 100644 index 000000000..810637b3b --- /dev/null +++ b/applications/system/js_app/examples/apps/Scripts/Examples/spi.js @@ -0,0 +1,88 @@ +// Connect a w25q32 SPI device to the Flipper Zero. +// D1=pin 2 (MOSI), SLK=pin 5 (SCK), GND=pin 8 (GND), D0=pin 3 (MISO), CS=pin 4 (CS), VCC=pin 9 (3V3) +let spi = require("spi"); + +// Display textbox so user can scroll to see all output. +let eventLoop = require("event_loop"); +let gui = require("gui"); +let text = "SPI demo\n"; +let textBox = require("gui/text_box").makeWith({ + focus: "end", + font: "text", + text: text, +}); + +function addText(add) { + text += add; + textBox.set("text", text); +} + +gui.viewDispatcher.switchTo(textBox); + +// writeRead returns a buffer the same length as the input buffer. +// We send 6 bytes of data, starting with 0x90, which is the command to read the manufacturer ID. +// Can also use Uint8Array([0x90, 0x00, ...]) as write parameter +// Optional timeout parameter in ms. We set to 100ms. +let data_buf = spi.writeRead([0x90, 0x0, 0x0, 0x0, 0x0, 0x0], 100); +let data = Uint8Array(data_buf); +if (data.length === 6) { + if (data[4] === 0xEF) { + addText("Found Winbond device\n"); + if (data[5] === 0x15) { + addText("Device ID: W25Q32\n"); + } else { + addText("Unknown device ID: " + data[5].toString(16) + "\n"); + } + } else if (data[4] === 0x0) { + addText("Be sure Winbond W25Q32 is connected to Flipper Zero SPI pins.\n"); + } else { + addText("Unknown device. Manufacturer ID: " + data[4].toString(16) + "\n"); + } +} + +addText("\nReading JEDEC ID\n"); + +// Acquire the SPI bus. Multiple calls will happen with Chip Select (CS) held low. +spi.acquire(); + +// Send command (0x9F) to read JEDEC ID. +// Can also use Uint8Array([0x9F]) as write parameter +// Note: you can pass an optional timeout parameter in milliseconds. +spi.write([0x9F]); + +// Request 3 bytes of data. +// Note: you can pass an optional timeout parameter in milliseconds. +data_buf = spi.read(3); + +// Release the SPI bus as soon as we are done with the set of SPI commands. +spi.release(); + +data = Uint8Array(data_buf); +addText("JEDEC MF ID: " + data[0].toString(16) + "\n"); +addText("JEDEC Memory Type: " + data[1].toString(16) + "\n"); +addText("JEDEC Capacity ID: " + data[2].toString(16) + "\n"); + +if (data[0] === 0xEF) { + addText("Found Winbond device\n"); +} +let capacity = data[1] << 8 | data[2]; +if (capacity === 0x4016) { + addText("Device: W25Q32\n"); +} else if (capacity === 0x4015) { + addText("Device: W25Q16\n"); +} else if (capacity === 0x4014) { + addText("Device: W25Q80\n"); +} else { + addText("Unknown device\n"); +} + +// Wait for user to close the app +eventLoop.subscribe(gui.viewDispatcher.navigation, function (_sub, _, eventLoop) { + eventLoop.stop(); +}, eventLoop); + +// This script has no interaction, only textbox, so event loop doesn't need to be running all the time +// We run it at the end to accept input for the back button press to quit +// But before that, user sees a textbox and pressing back has no effect +// This is fine because it allows simpler logic and the code above takes no time at all to run +eventLoop.run(); diff --git a/applications/system/js_app/examples/apps/Scripts/Examples/stringutils.js b/applications/system/js_app/examples/apps/Scripts/Examples/stringutils.js index c1f4d447a..b2facb237 100644 --- a/applications/system/js_app/examples/apps/Scripts/Examples/stringutils.js +++ b/applications/system/js_app/examples/apps/Scripts/Examples/stringutils.js @@ -1,6 +1,6 @@ let sampleText = "Hello, World!"; -let lengthOfText = "Length of text: " + toString(sampleText.length); +let lengthOfText = "Length of text: " + sampleText.length.toString(); print(lengthOfText); let start = 7; @@ -9,11 +9,11 @@ let substringResult = sampleText.slice(start, end); print(substringResult); let searchStr = "World"; -let result2 = toString(sampleText.indexOf(searchStr)); +let result2 = sampleText.indexOf(searchStr).toString(); print(result2); -let upperCaseText = "Text in upper case: " + toUpperCase(sampleText); +let upperCaseText = "Text in upper case: " + sampleText.toUpperCase(); print(upperCaseText); -let lowerCaseText = "Text in lower case: " + toLowerCase(sampleText); +let lowerCaseText = "Text in lower case: " + sampleText.toLowerCase(); print(lowerCaseText); diff --git a/applications/system/js_app/examples/apps/Scripts/Examples/uart_echo.js b/applications/system/js_app/examples/apps/Scripts/Examples/uart_echo.js index 72b4617a6..d43c0baa3 100644 --- a/applications/system/js_app/examples/apps/Scripts/Examples/uart_echo.js +++ b/applications/system/js_app/examples/apps/Scripts/Examples/uart_echo.js @@ -6,7 +6,7 @@ while (1) { if (rx_data !== undefined) { serial.write(rx_data); let data_view = Uint8Array(rx_data); - print("0x" + toString(data_view[0], 16)); + print("0x" + data_view[0].toString(16)); } } diff --git a/applications/system/js_app/examples/apps/Scripts/interactive.js b/applications/system/js_app/examples/apps/Scripts/interactive.js index 29f3bb253..40ca98c30 100644 --- a/applications/system/js_app/examples/apps/Scripts/interactive.js +++ b/applications/system/js_app/examples/apps/Scripts/interactive.js @@ -24,11 +24,10 @@ let views = { }), textInput: textInput.makeWith({ header: "Type JavaScript Code:", - defaultText: "2+2", - defaultTextClear: true, - // Props for makeWith() are passed in reverse order, so maxLength must be after defaultText minLength: 0, maxLength: 256, + defaultText: "2+2", + defaultTextClear: true, }), loading: loading.make(), }; @@ -42,7 +41,7 @@ eventLoop.subscribe(views.dialog.input, function (_sub, button, gui, views) { eventLoop.subscribe(views.textInput.input, function (_sub, text, gui, views, ctx) { gui.viewDispatcher.switchTo(views.loading); - let path = ctx.tmpTemplate + toString(ctx.tmpNumber++); + let path = ctx.tmpTemplate + (ctx.tmpNumber++).toString(); let file = storage.openFile(path, "w", "create_always"); file.write(text); file.close(); @@ -58,7 +57,7 @@ eventLoop.subscribe(views.textInput.input, function (_sub, text, gui, views, ctx } else if (typeof result === "string") { result = "'" + result + "'"; } else if (typeof result === "number") { - result = toString(result); + result = result.toString(); } else if (typeof result === "bigint") { // mJS doesn't support BigInt() but might aswell check result = "bigint"; } else if (typeof result === "boolean") { diff --git a/applications/system/js_app/js_thread.c b/applications/system/js_app/js_thread.c index d26fe1fb0..83f9e604c 100644 --- a/applications/system/js_app/js_thread.c +++ b/applications/system/js_app/js_thread.c @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -195,98 +196,27 @@ static void js_require(struct mjs* mjs) { mjs_return(mjs, req_object); } -static void js_global_to_string(struct mjs* mjs) { - int base = 10; - if(mjs_nargs(mjs) > 1) base = mjs_get_int(mjs, mjs_arg(mjs, 1)); - double num = mjs_get_double(mjs, mjs_arg(mjs, 0)); - char tmp_str[] = "-2147483648"; - itoa(num, tmp_str, base); - mjs_val_t ret = mjs_mk_string(mjs, tmp_str, ~0, true); - mjs_return(mjs, ret); -} - static void js_parse_int(struct mjs* mjs) { - mjs_val_t arg = mjs_arg(mjs, 0); - if(!mjs_is_string(arg)) { - mjs_return(mjs, mjs_mk_number(mjs, 0)); - return; - } - size_t str_len = 0; - const char* str = mjs_get_string(mjs, &arg, &str_len); - if((str_len == 0) || (str == NULL)) { - mjs_return(mjs, mjs_mk_number(mjs, 0)); - return; - } + const char* str; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_AT_LEAST, JS_ARG_STR(&str)); - int32_t num = 0; - int32_t sign = 1; - size_t i = 0; - - if(str[0] == '-') { - sign = -1; - i = 1; - } else if(str[0] == '+') { - i = 1; - } - - for(; i < str_len; i++) { - if(str[i] >= '0' && str[i] <= '9') { - num = num * 10 + (str[i] - '0'); - } else { - break; + int32_t base = 10; + if(mjs_nargs(mjs) >= 2) { + mjs_val_t base_arg = mjs_arg(mjs, 1); + if(!mjs_is_number(base_arg)) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "Base must be a number"); + mjs_return(mjs, MJS_UNDEFINED); } + base = mjs_get_int(mjs, base_arg); } - num *= sign; + int32_t num; + if(strint_to_int32(str, NULL, &num, base) != StrintParseNoError) { + num = 0; + } mjs_return(mjs, mjs_mk_number(mjs, num)); } -static void js_to_upper_case(struct mjs* mjs) { - mjs_val_t arg0 = mjs_arg(mjs, 0); - - size_t str_len; - const char* str = NULL; - if(mjs_is_string(arg0)) { - str = mjs_get_string(mjs, &arg0, &str_len); - } - if(!str) { - mjs_return(mjs, MJS_UNDEFINED); - return; - } - - char* upperStr = strdup(str); - for(size_t i = 0; i < str_len; i++) { - upperStr[i] = toupper(upperStr[i]); - } - - mjs_val_t resultStr = mjs_mk_string(mjs, upperStr, ~0, true); - free(upperStr); - mjs_return(mjs, resultStr); -} - -static void js_to_lower_case(struct mjs* mjs) { - mjs_val_t arg0 = mjs_arg(mjs, 0); - - size_t str_len; - const char* str = NULL; - if(mjs_is_string(arg0)) { - str = mjs_get_string(mjs, &arg0, &str_len); - } - if(!str) { - mjs_return(mjs, MJS_UNDEFINED); - return; - } - - char* lowerStr = strdup(str); - for(size_t i = 0; i < str_len; i++) { - lowerStr[i] = tolower(lowerStr[i]); - } - - mjs_val_t resultStr = mjs_mk_string(mjs, lowerStr, ~0, true); - free(lowerStr); - mjs_return(mjs, resultStr); -} - #ifdef JS_DEBUG static void js_dump_write_callback(void* ctx, const char* format, ...) { File* file = ctx; @@ -320,26 +250,23 @@ static int32_t js_thread(void* arg) { mjs_set( mjs, global, - "__filepath", + "__filename", ~0, mjs_mk_string( mjs, furi_string_get_cstr(worker->path), furi_string_size(worker->path), true)); mjs_set( mjs, global, - "__dirpath", + "__dirname", ~0, mjs_mk_string(mjs, furi_string_get_cstr(dirpath), furi_string_size(dirpath), true)); furi_string_free(dirpath); } mjs_set(mjs, global, "print", ~0, MJS_MK_FN(js_print)); mjs_set(mjs, global, "delay", ~0, MJS_MK_FN(js_delay)); - mjs_set(mjs, global, "toString", ~0, MJS_MK_FN(js_global_to_string)); mjs_set(mjs, global, "ffi_address", ~0, MJS_MK_FN(js_ffi_address)); mjs_set(mjs, global, "require", ~0, MJS_MK_FN(js_require)); mjs_set(mjs, global, "parseInt", ~0, MJS_MK_FN(js_parse_int)); - mjs_set(mjs, global, "toUpperCase", ~0, MJS_MK_FN(js_to_upper_case)); - mjs_set(mjs, global, "toLowerCase", ~0, MJS_MK_FN(js_to_lower_case)); mjs_val_t console_obj = mjs_mk_object(mjs); mjs_set(mjs, console_obj, "log", ~0, MJS_MK_FN(js_console_log)); diff --git a/applications/system/js_app/modules/js_gpio.c b/applications/system/js_app/modules/js_gpio.c index 70021968f..d2d65da4d 100644 --- a/applications/system/js_app/modules/js_gpio.c +++ b/applications/system/js_app/modules/js_gpio.c @@ -269,7 +269,6 @@ static void js_gpio_get(struct mjs* mjs) { manager_data->interrupt_semaphore = furi_semaphore_alloc(UINT32_MAX, 0); manager_data->adc_handle = module->adc_handle; manager_data->adc_channel = pin_record->channel; - mjs_own(mjs, &manager); mjs_set(mjs, manager, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, manager_data)); mjs_set(mjs, manager, "init", ~0, MJS_MK_FN(js_gpio_init)); mjs_set(mjs, manager, "write", ~0, MJS_MK_FN(js_gpio_write)); diff --git a/applications/system/js_app/modules/js_gui/byte_input.c b/applications/system/js_app/modules/js_gui/byte_input.c index 5c8844d22..2d6dae475 100644 --- a/applications/system/js_app/modules/js_gui/byte_input.c +++ b/applications/system/js_app/modules/js_gui/byte_input.c @@ -8,6 +8,7 @@ typedef struct { uint8_t* buffer; size_t buffer_size; + size_t default_data_size; FuriString* header; FuriSemaphore* input_semaphore; JsEventLoopContract contract; @@ -38,7 +39,14 @@ static bool len_assign(struct mjs* mjs, ByteInput* input, JsViewPropValue value, JsByteKbContext* context) { UNUSED(mjs); UNUSED(input); - context->buffer_size = (size_t)(value.number); + size_t new_buffer_size = value.number; + if(new_buffer_size < context->default_data_size) { + // Avoid confusing parameters from user + mjs_prepend_errorf( + mjs, MJS_BAD_ARGS_ERROR, "length must be larger than defaultData length"); + return false; + } + context->buffer_size = new_buffer_size; context->buffer = realloc(context->buffer, context->buffer_size); //-V701 byte_input_set_result_callback( input, @@ -57,16 +65,24 @@ static bool default_data_assign( JsByteKbContext* context) { UNUSED(mjs); - mjs_val_t array_buf = value.array; + mjs_val_t array_buf = value.term; if(mjs_is_data_view(array_buf)) { array_buf = mjs_dataview_get_buf(mjs, array_buf); } - size_t default_data_len = 0; - char* default_data = mjs_array_buf_get_ptr(mjs, array_buf, &default_data_len); - memcpy( - context->buffer, - (uint8_t*)default_data, - MIN((size_t)context->buffer_size, default_data_len)); + char* default_data = mjs_array_buf_get_ptr(mjs, array_buf, &context->default_data_size); + if(context->buffer_size < context->default_data_size) { + // Ensure buffer is large enough for defaultData + context->buffer_size = context->default_data_size; + context->buffer = realloc(context->buffer, context->buffer_size); //-V701 + } + memcpy(context->buffer, (uint8_t*)default_data, context->default_data_size); + if(context->buffer_size > context->default_data_size) { + // Reset previous data after defaultData + memset( + context->buffer + context->default_data_size, + 0x00, + context->buffer_size - context->default_data_size); + } byte_input_set_result_callback( input, diff --git a/applications/system/js_app/modules/js_gui/js_gui.c b/applications/system/js_app/modules/js_gui/js_gui.c index cd87e5c4b..22d04855d 100644 --- a/applications/system/js_app/modules/js_gui/js_gui.c +++ b/applications/system/js_app/modules/js_gui/js_gui.c @@ -216,14 +216,14 @@ static bool expected_type = "array"; break; } - c_value = (JsViewPropValue){.array = value}; + c_value = (JsViewPropValue){.term = value}; } break; case JsViewPropTypeTypedArr: { if(!mjs_is_typed_array(value)) { expected_type = "typed_array"; break; } - c_value = (JsViewPropValue){.array = value}; + c_value = (JsViewPropValue){.term = value}; } break; case JsViewPropTypeBool: { if(!mjs_is_boolean(value)) { @@ -260,26 +260,6 @@ static void js_gui_view_set(struct mjs* mjs) { mjs_return(mjs, MJS_UNDEFINED); } -/** - * @brief `View.hasProperty` - */ -static void js_gui_view_has_property(struct mjs* mjs) { - const char* name; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&name)); - JsGuiViewData* data = JS_GET_CONTEXT(mjs); - const JsViewDescriptor* descriptor = data->descriptor; - - for(size_t i = 0; i < descriptor->prop_cnt; i++) { - JsViewPropDescriptor prop = descriptor->props[i]; - if(strcmp(prop.name, name) != 0) continue; - - mjs_return(mjs, mjs_mk_boolean(mjs, true)); - return; - } - - mjs_return(mjs, mjs_mk_boolean(mjs, false)); -} - /** * @brief `View` destructor */ @@ -304,7 +284,6 @@ static mjs_val_t js_gui_make_view(struct mjs* mjs, const JsViewDescriptor* descr // generic view API mjs_val_t view_obj = mjs_mk_object(mjs); mjs_set(mjs, view_obj, "set", ~0, MJS_MK_FN(js_gui_view_set)); - mjs_set(mjs, view_obj, "hasProperty", ~0, MJS_MK_FN(js_gui_view_has_property)); // object data JsGuiViewData* data = malloc(sizeof(JsGuiViewData)); diff --git a/applications/system/js_app/modules/js_gui/js_gui.h b/applications/system/js_app/modules/js_gui/js_gui.h index 67266b1fc..d400d0a33 100644 --- a/applications/system/js_app/modules/js_gui/js_gui.h +++ b/applications/system/js_app/modules/js_gui/js_gui.h @@ -16,8 +16,8 @@ typedef enum { typedef union { const char* string; int32_t number; - mjs_val_t array; bool boolean; + mjs_val_t term; } JsViewPropValue; /** diff --git a/applications/system/js_app/modules/js_gui/submenu.c b/applications/system/js_app/modules/js_gui/submenu.c index aecd413be..c142bcddb 100644 --- a/applications/system/js_app/modules/js_gui/submenu.c +++ b/applications/system/js_app/modules/js_gui/submenu.c @@ -33,9 +33,9 @@ static bool static bool items_assign(struct mjs* mjs, Submenu* submenu, JsViewPropValue value, void* context) { UNUSED(mjs); submenu_reset(submenu); - size_t len = mjs_array_length(mjs, value.array); + size_t len = mjs_array_length(mjs, value.term); for(size_t i = 0; i < len; i++) { - mjs_val_t item = mjs_array_get(mjs, value.array, i); + mjs_val_t item = mjs_array_get(mjs, value.term, i); if(!mjs_is_string(item)) return false; submenu_add_item(submenu, mjs_get_string(mjs, &item, NULL), i, choose_callback, context); } diff --git a/applications/system/js_app/modules/js_gui/text_input.c b/applications/system/js_app/modules/js_gui/text_input.c index d2bf4a8f9..38dabb488 100644 --- a/applications/system/js_app/modules/js_gui/text_input.c +++ b/applications/system/js_app/modules/js_gui/text_input.c @@ -8,6 +8,7 @@ typedef struct { char* buffer; size_t buffer_size; + size_t default_text_size; FuriString* header; bool default_text_clear; FuriSemaphore* input_semaphore; @@ -49,7 +50,14 @@ static bool max_len_assign( JsViewPropValue value, JsKbdContext* context) { UNUSED(mjs); - context->buffer_size = (size_t)(value.number + 1); + size_t new_buffer_size = value.number + 1; + if(new_buffer_size < context->default_text_size) { + // Avoid confusing parameters from user + mjs_prepend_errorf( + mjs, MJS_BAD_ARGS_ERROR, "maxLength must be larger than defaultText length"); + return false; + } + context->buffer_size = new_buffer_size; context->buffer = realloc(context->buffer, context->buffer_size); //-V701 text_input_set_result_callback( input, @@ -70,6 +78,13 @@ static bool default_text_assign( UNUSED(input); if(value.string) { + context->default_text_size = strlen(value.string) + 1; + if(context->buffer_size < context->default_text_size) { + // Ensure buffer is large enough for defaultData + context->buffer_size = context->default_text_size; + context->buffer = realloc(context->buffer, context->buffer_size); //-V701 + } + // Also trim excess previous data with strlcpy() strlcpy(context->buffer, value.string, context->buffer_size); text_input_set_result_callback( input, @@ -120,6 +135,7 @@ static JsKbdContext* ctx_make(struct mjs* mjs, TextInput* input, mjs_val_t view_ .transformer_context = context, }, }; + text_input_show_illegal_symbols(input, true); // Temporary until prop is available text_input_set_result_callback( input, (TextInputCallback)input_callback, diff --git a/applications/system/js_app/modules/js_i2c.c b/applications/system/js_app/modules/js_i2c.c new file mode 100644 index 000000000..6f105142c --- /dev/null +++ b/applications/system/js_app/modules/js_i2c.c @@ -0,0 +1,280 @@ +#include "../js_modules.h" +#include + +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_range(struct mjs* mjs, size_t min_count, size_t max_count) { + size_t num_args = mjs_nargs(mjs); + if(num_args < min_count || num_args > max_count) { + ret_bad_args(mjs, "Wrong argument count"); + return false; + } + return true; +} + +static void js_i2c_is_device_ready(struct mjs* mjs) { + if(!check_arg_count_range(mjs, 1, 2)) return; + + mjs_val_t addr_arg = mjs_arg(mjs, 0); + if(!mjs_is_number(addr_arg)) { + ret_bad_args(mjs, "Addr must be a number"); + return; + } + uint32_t addr = mjs_get_int32(mjs, addr_arg); + + uint32_t timeout = 1; + if(mjs_nargs(mjs) > 1) { // Timeout is optional argument + mjs_val_t timeout_arg = mjs_arg(mjs, 1); + if(!mjs_is_number(timeout_arg)) { + ret_bad_args(mjs, "Timeout must be a number"); + return; + } + timeout = mjs_get_int32(mjs, timeout_arg); + } + + furi_hal_i2c_acquire(&furi_hal_i2c_handle_external); + bool ready = furi_hal_i2c_is_device_ready(&furi_hal_i2c_handle_external, addr, timeout); + furi_hal_i2c_release(&furi_hal_i2c_handle_external); + + mjs_return(mjs, mjs_mk_boolean(mjs, ready)); +} + +static void js_i2c_write(struct mjs* mjs) { + if(!check_arg_count_range(mjs, 2, 3)) return; + + mjs_val_t addr_arg = mjs_arg(mjs, 0); + if(!mjs_is_number(addr_arg)) { + ret_bad_args(mjs, "Addr must be a number"); + return; + } + uint32_t addr = mjs_get_int32(mjs, addr_arg); + + mjs_val_t tx_buf_arg = mjs_arg(mjs, 1); + bool tx_buf_was_allocated = false; + uint8_t* tx_buf = NULL; + size_t tx_len = 0; + if(mjs_is_array(tx_buf_arg)) { + tx_len = mjs_array_length(mjs, tx_buf_arg); + if(tx_len == 0) { + ret_bad_args(mjs, "Data array must not be empty"); + return; + } + tx_buf = malloc(tx_len); + tx_buf_was_allocated = true; + for(size_t i = 0; i < tx_len; i++) { + mjs_val_t val = mjs_array_get(mjs, tx_buf_arg, i); + if(!mjs_is_number(val)) { + ret_bad_args(mjs, "Data array must contain only numbers"); + free(tx_buf); + return; + } + uint32_t byte_val = mjs_get_int32(mjs, val); + if(byte_val > 0xFF) { + ret_bad_args(mjs, "Data array values must be 0-255"); + free(tx_buf); + return; + } + tx_buf[i] = byte_val; + } + } else if(mjs_is_typed_array(tx_buf_arg)) { + mjs_val_t array_buf = tx_buf_arg; + if(mjs_is_data_view(tx_buf_arg)) { + array_buf = mjs_dataview_get_buf(mjs, tx_buf_arg); + } + tx_buf = (uint8_t*)mjs_array_buf_get_ptr(mjs, array_buf, &tx_len); + if(tx_len == 0) { + ret_bad_args(mjs, "Data array must not be empty"); + return; + } + } else { + ret_bad_args(mjs, "Data must be an array, arraybuf or dataview"); + return; + } + + uint32_t timeout = 1; + if(mjs_nargs(mjs) > 2) { // Timeout is optional argument + mjs_val_t timeout_arg = mjs_arg(mjs, 2); + if(!mjs_is_number(timeout_arg)) { + ret_bad_args(mjs, "Timeout must be a number"); + if(tx_buf_was_allocated) free(tx_buf); + return; + } + timeout = mjs_get_int32(mjs, timeout_arg); + } + + furi_hal_i2c_acquire(&furi_hal_i2c_handle_external); + bool result = furi_hal_i2c_tx(&furi_hal_i2c_handle_external, addr, tx_buf, tx_len, timeout); + furi_hal_i2c_release(&furi_hal_i2c_handle_external); + + if(tx_buf_was_allocated) free(tx_buf); + mjs_return(mjs, mjs_mk_boolean(mjs, result)); +} + +static void js_i2c_read(struct mjs* mjs) { + if(!check_arg_count_range(mjs, 2, 3)) return; + + mjs_val_t addr_arg = mjs_arg(mjs, 0); + if(!mjs_is_number(addr_arg)) { + ret_bad_args(mjs, "Addr must be a number"); + return; + } + uint32_t addr = mjs_get_int32(mjs, addr_arg); + + mjs_val_t rx_len_arg = mjs_arg(mjs, 1); + if(!mjs_is_number(rx_len_arg)) { + ret_bad_args(mjs, "Length must be a number"); + return; + } + size_t rx_len = mjs_get_int32(mjs, rx_len_arg); + if(rx_len == 0) { + ret_bad_args(mjs, "Length must not zero"); + return; + } + uint8_t* rx_buf = malloc(rx_len); + + uint32_t timeout = 1; + if(mjs_nargs(mjs) > 2) { // Timeout is optional argument + mjs_val_t timeout_arg = mjs_arg(mjs, 2); + if(!mjs_is_number(timeout_arg)) { + ret_bad_args(mjs, "Timeout must be a number"); + free(rx_buf); + return; + } + timeout = mjs_get_int32(mjs, timeout_arg); + } + + furi_hal_i2c_acquire(&furi_hal_i2c_handle_external); + bool result = furi_hal_i2c_rx(&furi_hal_i2c_handle_external, addr, rx_buf, rx_len, timeout); + furi_hal_i2c_release(&furi_hal_i2c_handle_external); + + mjs_val_t ret = MJS_UNDEFINED; + if(result) { + ret = mjs_mk_array_buf(mjs, (char*)rx_buf, rx_len); + } + free(rx_buf); + mjs_return(mjs, ret); +} + +static void js_i2c_write_read(struct mjs* mjs) { + if(!check_arg_count_range(mjs, 3, 4)) return; + + mjs_val_t addr_arg = mjs_arg(mjs, 0); + if(!mjs_is_number(addr_arg)) { + ret_bad_args(mjs, "Addr must be a number"); + return; + } + uint32_t addr = mjs_get_int32(mjs, addr_arg); + + mjs_val_t tx_buf_arg = mjs_arg(mjs, 1); + bool tx_buf_was_allocated = false; + uint8_t* tx_buf = NULL; + size_t tx_len = 0; + if(mjs_is_array(tx_buf_arg)) { + tx_len = mjs_array_length(mjs, tx_buf_arg); + if(tx_len == 0) { + ret_bad_args(mjs, "Data array must not be empty"); + return; + } + tx_buf = malloc(tx_len); + tx_buf_was_allocated = true; + for(size_t i = 0; i < tx_len; i++) { + mjs_val_t val = mjs_array_get(mjs, tx_buf_arg, i); + if(!mjs_is_number(val)) { + ret_bad_args(mjs, "Data array must contain only numbers"); + free(tx_buf); + return; + } + uint32_t byte_val = mjs_get_int32(mjs, val); + if(byte_val > 0xFF) { + ret_bad_args(mjs, "Data array values must be 0-255"); + free(tx_buf); + return; + } + tx_buf[i] = byte_val; + } + } else if(mjs_is_typed_array(tx_buf_arg)) { + mjs_val_t array_buf = tx_buf_arg; + if(mjs_is_data_view(tx_buf_arg)) { + array_buf = mjs_dataview_get_buf(mjs, tx_buf_arg); + } + tx_buf = (uint8_t*)mjs_array_buf_get_ptr(mjs, array_buf, &tx_len); + if(tx_len == 0) { + ret_bad_args(mjs, "Data array must not be empty"); + return; + } + } else { + ret_bad_args(mjs, "Data must be an array, arraybuf or dataview"); + return; + } + + mjs_val_t rx_len_arg = mjs_arg(mjs, 2); + if(!mjs_is_number(rx_len_arg)) { + ret_bad_args(mjs, "Length must be a number"); + if(tx_buf_was_allocated) free(tx_buf); + return; + } + size_t rx_len = mjs_get_int32(mjs, rx_len_arg); + if(rx_len == 0) { + ret_bad_args(mjs, "Length must not zero"); + if(tx_buf_was_allocated) free(tx_buf); + return; + } + uint8_t* rx_buf = malloc(rx_len); + + uint32_t timeout = 1; + if(mjs_nargs(mjs) > 3) { // Timeout is optional argument + mjs_val_t timeout_arg = mjs_arg(mjs, 3); + if(!mjs_is_number(timeout_arg)) { + ret_bad_args(mjs, "Timeout must be a number"); + if(tx_buf_was_allocated) free(tx_buf); + free(rx_buf); + return; + } + timeout = mjs_get_int32(mjs, timeout_arg); + } + + furi_hal_i2c_acquire(&furi_hal_i2c_handle_external); + bool result = furi_hal_i2c_trx( + &furi_hal_i2c_handle_external, addr, tx_buf, tx_len, rx_buf, rx_len, timeout); + furi_hal_i2c_release(&furi_hal_i2c_handle_external); + + mjs_val_t ret = MJS_UNDEFINED; + if(result) { + ret = mjs_mk_array_buf(mjs, (char*)rx_buf, rx_len); + } + if(tx_buf_was_allocated) free(tx_buf); + free(rx_buf); + mjs_return(mjs, ret); +} + +static void* js_i2c_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) { + UNUSED(modules); + mjs_val_t i2c_obj = mjs_mk_object(mjs); + mjs_set(mjs, i2c_obj, "isDeviceReady", ~0, MJS_MK_FN(js_i2c_is_device_ready)); + mjs_set(mjs, i2c_obj, "write", ~0, MJS_MK_FN(js_i2c_write)); + mjs_set(mjs, i2c_obj, "read", ~0, MJS_MK_FN(js_i2c_read)); + mjs_set(mjs, i2c_obj, "writeRead", ~0, MJS_MK_FN(js_i2c_write_read)); + *object = i2c_obj; + + return (void*)1; +} + +static const JsModuleDescriptor js_i2c_desc = { + "i2c", + js_i2c_create, + NULL, + NULL, +}; + +static const FlipperAppPluginDescriptor i2c_plugin_descriptor = { + .appid = PLUGIN_APP_ID, + .ep_api_version = PLUGIN_API_VERSION, + .entry_point = &js_i2c_desc, +}; + +const FlipperAppPluginDescriptor* js_i2c_ep(void) { + return &i2c_plugin_descriptor; +} diff --git a/applications/system/js_app/modules/js_math.c b/applications/system/js_app/modules/js_math.c index 7d54cf9b9..cf66b6a44 100644 --- a/applications/system/js_app/modules/js_math.c +++ b/applications/system/js_app/modules/js_math.c @@ -308,7 +308,7 @@ void js_math_trunc(struct mjs* mjs) { static void* js_math_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) { UNUSED(modules); mjs_val_t math_obj = mjs_mk_object(mjs); - mjs_set(mjs, math_obj, "is_equal", ~0, MJS_MK_FN(js_math_is_equal)); + mjs_set(mjs, math_obj, "isEqual", ~0, MJS_MK_FN(js_math_is_equal)); 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)); diff --git a/applications/system/js_app/modules/js_spi.c b/applications/system/js_app/modules/js_spi.c new file mode 100644 index 000000000..c0f4d684d --- /dev/null +++ b/applications/system/js_app/modules/js_spi.c @@ -0,0 +1,283 @@ +#include "../js_modules.h" +#include + +typedef struct { + bool acquired_bus; +} JsSpiInst; + +static JsSpiInst* get_this_ctx(struct mjs* mjs) { + mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); + JsSpiInst* spi = mjs_get_ptr(mjs, obj_inst); + furi_assert(spi); + return spi; +} + +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_range(struct mjs* mjs, size_t min_count, size_t max_count) { + size_t num_args = mjs_nargs(mjs); + if(num_args < min_count || num_args > max_count) { + ret_bad_args(mjs, "Wrong argument count"); + return false; + } + return true; +} + +static void js_spi_acquire(struct mjs* mjs) { + if(!check_arg_count_range(mjs, 0, 0)) return; + JsSpiInst* spi = get_this_ctx(mjs); + if(!spi->acquired_bus) { + furi_hal_spi_acquire(&furi_hal_spi_bus_handle_external); + spi->acquired_bus = true; + } + mjs_return(mjs, MJS_UNDEFINED); +} + +static void js_spi_release(struct mjs* mjs) { + if(!check_arg_count_range(mjs, 0, 0)) return; + JsSpiInst* spi = get_this_ctx(mjs); + if(spi->acquired_bus) { + furi_hal_spi_release(&furi_hal_spi_bus_handle_external); + spi->acquired_bus = false; + } + mjs_return(mjs, MJS_UNDEFINED); +} + +static bool js_spi_is_acquired(struct mjs* mjs) { + JsSpiInst* spi = get_this_ctx(mjs); + return spi->acquired_bus; +} + +static void js_spi_write(struct mjs* mjs) { + if(!check_arg_count_range(mjs, 1, 2)) return; + + mjs_val_t tx_buf_arg = mjs_arg(mjs, 0); + bool tx_buf_was_allocated = false; + uint8_t* tx_buf = NULL; + size_t tx_len = 0; + if(mjs_is_array(tx_buf_arg)) { + tx_len = mjs_array_length(mjs, tx_buf_arg); + if(tx_len == 0) { + ret_bad_args(mjs, "Data array must not be empty"); + return; + } + tx_buf = malloc(tx_len); + tx_buf_was_allocated = true; + for(size_t i = 0; i < tx_len; i++) { + mjs_val_t val = mjs_array_get(mjs, tx_buf_arg, i); + if(!mjs_is_number(val)) { + ret_bad_args(mjs, "Data array must contain only numbers"); + free(tx_buf); + return; + } + uint32_t byte_val = mjs_get_int32(mjs, val); + if(byte_val > 0xFF) { + ret_bad_args(mjs, "Data array values must be 0-255"); + free(tx_buf); + return; + } + tx_buf[i] = byte_val; + } + } else if(mjs_is_typed_array(tx_buf_arg)) { + mjs_val_t array_buf = tx_buf_arg; + if(mjs_is_data_view(tx_buf_arg)) { + array_buf = mjs_dataview_get_buf(mjs, tx_buf_arg); + } + tx_buf = (uint8_t*)mjs_array_buf_get_ptr(mjs, array_buf, &tx_len); + if(tx_len == 0) { + ret_bad_args(mjs, "Data array must not be empty"); + return; + } + } else { + ret_bad_args(mjs, "Data must be an array, arraybuf or dataview"); + return; + } + + uint32_t timeout = 1; + if(mjs_nargs(mjs) > 1) { // Timeout is optional argument + mjs_val_t timeout_arg = mjs_arg(mjs, 1); + if(!mjs_is_number(timeout_arg)) { + ret_bad_args(mjs, "Timeout must be a number"); + if(tx_buf_was_allocated) free(tx_buf); + return; + } + timeout = mjs_get_int32(mjs, timeout_arg); + } + + if(!js_spi_is_acquired(mjs)) { + furi_hal_spi_acquire(&furi_hal_spi_bus_handle_external); + } + bool result = furi_hal_spi_bus_tx(&furi_hal_spi_bus_handle_external, tx_buf, tx_len, timeout); + if(!js_spi_is_acquired(mjs)) { + furi_hal_spi_release(&furi_hal_spi_bus_handle_external); + } + + if(tx_buf_was_allocated) free(tx_buf); + mjs_return(mjs, mjs_mk_boolean(mjs, result)); +} + +static void js_spi_read(struct mjs* mjs) { + if(!check_arg_count_range(mjs, 1, 2)) return; + + mjs_val_t rx_len_arg = mjs_arg(mjs, 0); + if(!mjs_is_number(rx_len_arg)) { + ret_bad_args(mjs, "Length must be a number"); + return; + } + size_t rx_len = mjs_get_int32(mjs, rx_len_arg); + if(rx_len == 0) { + ret_bad_args(mjs, "Length must not zero"); + return; + } + + uint8_t* rx_buf = malloc(rx_len); + + uint32_t timeout = 1; + if(mjs_nargs(mjs) > 1) { // Timeout is optional argument + mjs_val_t timeout_arg = mjs_arg(mjs, 1); + if(!mjs_is_number(timeout_arg)) { + ret_bad_args(mjs, "Timeout must be a number"); + free(rx_buf); + return; + } + timeout = mjs_get_int32(mjs, timeout_arg); + } + + if(!js_spi_is_acquired(mjs)) { + furi_hal_spi_acquire(&furi_hal_spi_bus_handle_external); + } + bool result = furi_hal_spi_bus_rx(&furi_hal_spi_bus_handle_external, rx_buf, rx_len, timeout); + if(!js_spi_is_acquired(mjs)) { + furi_hal_spi_release(&furi_hal_spi_bus_handle_external); + } + + mjs_val_t ret = MJS_UNDEFINED; + if(result) { + ret = mjs_mk_array_buf(mjs, (char*)rx_buf, rx_len); + } + free(rx_buf); + mjs_return(mjs, ret); +} + +static void js_spi_write_read(struct mjs* mjs) { + if(!check_arg_count_range(mjs, 1, 2)) return; + + mjs_val_t tx_buf_arg = mjs_arg(mjs, 0); + bool tx_buf_was_allocated = false; + uint8_t* tx_buf = NULL; + size_t data_len = 0; + if(mjs_is_array(tx_buf_arg)) { + data_len = mjs_array_length(mjs, tx_buf_arg); + if(data_len == 0) { + ret_bad_args(mjs, "Data array must not be empty"); + return; + } + tx_buf = malloc(data_len); + tx_buf_was_allocated = true; + for(size_t i = 0; i < data_len; i++) { + mjs_val_t val = mjs_array_get(mjs, tx_buf_arg, i); + if(!mjs_is_number(val)) { + ret_bad_args(mjs, "Data array must contain only numbers"); + free(tx_buf); + return; + } + uint32_t byte_val = mjs_get_int32(mjs, val); + if(byte_val > 0xFF) { + ret_bad_args(mjs, "Data array values must be 0-255"); + free(tx_buf); + return; + } + tx_buf[i] = byte_val; + } + } else if(mjs_is_typed_array(tx_buf_arg)) { + mjs_val_t array_buf = tx_buf_arg; + if(mjs_is_data_view(tx_buf_arg)) { + array_buf = mjs_dataview_get_buf(mjs, tx_buf_arg); + } + tx_buf = (uint8_t*)mjs_array_buf_get_ptr(mjs, array_buf, &data_len); + if(data_len == 0) { + ret_bad_args(mjs, "Data array must not be empty"); + return; + } + } else { + ret_bad_args(mjs, "Data must be an array, arraybuf or dataview"); + return; + } + + uint8_t* rx_buf = malloc(data_len); // RX and TX are same length for SPI writeRead. + + uint32_t timeout = 1; + if(mjs_nargs(mjs) > 1) { // Timeout is optional argument + mjs_val_t timeout_arg = mjs_arg(mjs, 1); + if(!mjs_is_number(timeout_arg)) { + ret_bad_args(mjs, "Timeout must be a number"); + if(tx_buf_was_allocated) free(tx_buf); + free(rx_buf); + return; + } + timeout = mjs_get_int32(mjs, timeout_arg); + } + + if(!js_spi_is_acquired(mjs)) { + furi_hal_spi_acquire(&furi_hal_spi_bus_handle_external); + } + bool result = + furi_hal_spi_bus_trx(&furi_hal_spi_bus_handle_external, tx_buf, rx_buf, data_len, timeout); + if(!js_spi_is_acquired(mjs)) { + furi_hal_spi_release(&furi_hal_spi_bus_handle_external); + } + + mjs_val_t ret = MJS_UNDEFINED; + if(result) { + ret = mjs_mk_array_buf(mjs, (char*)rx_buf, data_len); + } + if(tx_buf_was_allocated) free(tx_buf); + free(rx_buf); + mjs_return(mjs, ret); +} + +static void* js_spi_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) { + UNUSED(modules); + JsSpiInst* spi = (JsSpiInst*)malloc(sizeof(JsSpiInst)); + spi->acquired_bus = false; + mjs_val_t spi_obj = mjs_mk_object(mjs); + mjs_set(mjs, spi_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, spi)); + mjs_set(mjs, spi_obj, "acquire", ~0, MJS_MK_FN(js_spi_acquire)); + mjs_set(mjs, spi_obj, "release", ~0, MJS_MK_FN(js_spi_release)); + mjs_set(mjs, spi_obj, "write", ~0, MJS_MK_FN(js_spi_write)); + mjs_set(mjs, spi_obj, "read", ~0, MJS_MK_FN(js_spi_read)); + mjs_set(mjs, spi_obj, "writeRead", ~0, MJS_MK_FN(js_spi_write_read)); + *object = spi_obj; + + furi_hal_spi_bus_handle_init(&furi_hal_spi_bus_handle_external); + return (void*)spi; +} + +static void js_spi_destroy(void* inst) { + JsSpiInst* spi = (JsSpiInst*)inst; + if(spi->acquired_bus) { + furi_hal_spi_release(&furi_hal_spi_bus_handle_external); + } + free(spi); + furi_hal_spi_bus_handle_deinit(&furi_hal_spi_bus_handle_external); +} + +static const JsModuleDescriptor js_spi_desc = { + "spi", + js_spi_create, + js_spi_destroy, + NULL, +}; + +static const FlipperAppPluginDescriptor spi_plugin_descriptor = { + .appid = PLUGIN_APP_ID, + .ep_api_version = PLUGIN_API_VERSION, + .entry_point = &js_spi_desc, +}; + +const FlipperAppPluginDescriptor* js_spi_ep(void) { + return &spi_plugin_descriptor; +} diff --git a/applications/system/js_app/types/badusb/index.d.ts b/applications/system/js_app/types/badusb/index.d.ts index 4fbda5ef8..57c2662cd 100644 --- a/applications/system/js_app/types/badusb/index.d.ts +++ b/applications/system/js_app/types/badusb/index.d.ts @@ -40,7 +40,7 @@ export type KeyCode = MainKey | ModifierKey | number; * * @param settings USB device settings. Omit to select default parameters */ -export declare function setup(settings?: { vid: number, pid: number, mfrName?: string, prodName?: string, layoutPath: string }): void; +export declare function setup(settings?: { vid: number, pid: number, mfrName?: string, prodName?: string, layoutPath?: string }): void; /** * @brief Tells whether the virtual USB HID device has successfully connected diff --git a/applications/system/js_app/types/global.d.ts b/applications/system/js_app/types/global.d.ts index 14e045a1e..a55dae7d9 100644 --- a/applications/system/js_app/types/global.d.ts +++ b/applications/system/js_app/types/global.d.ts @@ -11,46 +11,30 @@ declare function delay(ms: number): void; */ declare function print(...args: any[]): void; -/** - * @brief Converts a number to a string - * @param value The number to convert to a string - * @param base Integer base (`2`...`16`), default: 16 - */ -declare function toString(value: number, base?: number): string; - /** * @brief Converts a string to a number * @param text The string to convert to a number + * @param base Integer base (`2`...`16`), default: 10 */ -declare function parseInt(text: string): number; - -/** - * @brief Transforms a string to upper case - * @param text The string to transforms to upper case - */ -declare function toUpperCase(text: string): string; - -/** - * @brief Transforms a string to lower case - * @param text The string to transforms to lower case - */ -declare function toLowerCase(text: string): string; +declare function parseInt(text: string, base?: number): number; /** * @brief Path to the directory containing the current script */ -declare const __dirpath: string; +declare const __dirname: string; /** * @brief Path to the current script file */ -declare const __filepath: string; +declare const __filename: string; /** * @brief Reads a JS value from a file * - * Reads a file at the specified path, interprets it as a JS value and returns - * the last value pushed on the stack. + * Reads a file at the specified path and runs it as JS, returning the last evaluated value. + * + * The result is cached and this filepath will not re-evaluated on future + * load() calls for this session. * * @param path The path to the file * @param scope An object to use as global scope while running this file @@ -211,13 +195,27 @@ declare class String { * @param end The index to end substring at */ slice(start: number, end?: number): string; + /** + * @brief Return this string transformed to upper case + */ + toUpperCase(): string; + /** + * @brief Return this string transformed to lower case + */ + toLowerCase(): string; } declare class Boolean { } declare class Function { } -declare class Number { } +declare class Number { + /** + * @brief Converts this number to a string + * @param base Integer base (`2`...`16`), default: 10 + */ + toString(base?: number): string; +} declare class Object { } diff --git a/applications/system/js_app/types/gui/index.d.ts b/applications/system/js_app/types/gui/index.d.ts index 5efcfdc03..d4cdc6bc9 100644 --- a/applications/system/js_app/types/gui/index.d.ts +++ b/applications/system/js_app/types/gui/index.d.ts @@ -9,11 +9,6 @@ export declare class View { * @param value Value to assign */ set(property: P, value: Props[P]): void; - /** - * Check if property is available - * @param name Name of the property - */ - hasProperty(name: string): boolean; } export declare class ViewFactory> { diff --git a/applications/system/js_app/types/i2c/index.d.ts b/applications/system/js_app/types/i2c/index.d.ts new file mode 100644 index 000000000..ea0a640cb --- /dev/null +++ b/applications/system/js_app/types/i2c/index.d.ts @@ -0,0 +1,31 @@ +/** + * @brief Check if there is an I2C device ready on the bus + * @param address The device address to check + * @param timeout Timeout in milliseconds + */ +export declare function isDeviceReady(address: number, timeout?: number): boolean; + +/** + * @brief Write data to I2C device and return success status + * @param address The device address to write to + * @param data The data to write to the device + * @param timeout Timeout in milliseconds + */ +export declare function write(address: number, data: number[] | ArrayBuffer, timeout?: number): boolean; + +/** + * @brief Read data from I2C device or return undefined on failure + * @param address The device address to read from + * @param length How many bytes to read + * @param timeout Timeout in milliseconds + */ +export declare function read(address: number, length: number, timeout?: number): ArrayBuffer | undefined; + +/** + * @brief Write data then read from I2C device or return undefined on failure + * @param address The device address to talk to + * @param writeData The data to write to the device + * @param readLength How many bytes to read + * @param timeout Timeout in milliseconds + */ +export declare function writeRead(address: number, writeData: number[] | ArrayBuffer, readLength: number, timeout?: number): ArrayBuffer | undefined; diff --git a/applications/system/js_app/types/math/index.d.ts b/applications/system/js_app/types/math/index.d.ts index 25abca4af..4924eea7e 100644 --- a/applications/system/js_app/types/math/index.d.ts +++ b/applications/system/js_app/types/math/index.d.ts @@ -1,3 +1,4 @@ +export function isEqual(a: number, b: number, tolerance: number): boolean; export function abs(n: number): number; export function acos(n: number): number; export function acosh(n: number): number; @@ -12,6 +13,7 @@ export function clz32(n: number): number; export function cos(n: number): number; export function exp(n: number): number; export function floor(n: number): number; +export function log(n: number): number; export function max(n: number, m: number): number; export function min(n: number, m: number): number; export function pow(n: number, m: number): number; @@ -21,4 +23,5 @@ export function sin(n: number): number; export function sqrt(n: number): number; export function trunc(n: number): number; declare const PI: number; +declare const E: number; declare const EPSILON: number; diff --git a/applications/system/js_app/types/spi/index.d.ts b/applications/system/js_app/types/spi/index.d.ts new file mode 100644 index 000000000..8d72bc29c --- /dev/null +++ b/applications/system/js_app/types/spi/index.d.ts @@ -0,0 +1,30 @@ +/** + * @brief Acquire SPI bus + */ +export declare function acquire(): void; + +/** + * @brief Release SPI bus + */ +export declare function release(): void; + +/** + * @brief Write data to SPI bus and return success status + * @param data The data to write + * @param timeout Timeout in milliseconds + */ +export declare function write(data: number[] | ArrayBuffer, timeout?: number): boolean; + +/** + * @brief Read data from SPI bus or return undefined on failure + * @param length How many bytes to read + * @param timeout Timeout in milliseconds + */ +export declare function read(length: number, timeout?: number): ArrayBuffer | undefined; + +/** + * @brief Write and read data on SPI bus or return undefined on failure + * @param data The data to write, its length also indicates how many bytes will be read + * @param timeout Timeout in milliseconds + */ +export declare function writeRead(data: number[] | ArrayBuffer, timeout?: number): ArrayBuffer | undefined; diff --git a/assets/protobuf b/assets/protobuf index b7d588169..c255d71a9 160000 --- a/assets/protobuf +++ b/assets/protobuf @@ -1 +1 @@ -Subproject commit b7d5881690298de836df6305d4c71f96d5d34c61 +Subproject commit c255d71a90af202515deb7aaa51685d45a196152 diff --git a/documentation/doxygen/index.dox b/documentation/doxygen/index.dox index 7bd9024a1..9587afd8b 100644 --- a/documentation/doxygen/index.dox +++ b/documentation/doxygen/index.dox @@ -1,7 +1,8 @@ /** -@mainpage Overview +@mainpage notitle +## Welcome to Flipper Developer Documentation! -Welcome to the Flipper Developer Documentation! +--- This documentation is intended for developers interested in modifying the Flipper Zero firmware, creating Apps and JavaScript programs, or working on external hardware modules for the device. diff --git a/documentation/js/js_math.md b/documentation/js/js_math.md index 12dae8fb3..ca16a9111 100644 --- a/documentation/js/js_math.md +++ b/documentation/js/js_math.md @@ -223,7 +223,7 @@ math.floor(45.05); // 45 math.floor(45.95); // 45 ``` -## is_equal +## isEqual Return true if the difference between numbers `a` and `b` is less than the specified parameter `e`. ### Parameters @@ -236,8 +236,8 @@ True if the difference between numbers `a` and `b` is less than the specified pa ### Example ```js -math.is_equal(1.4, 1.6, 0.2); // false -math.is_equal(3.556, 3.555, 0.01); // true +math.isEqual(1.4, 1.6, 0.2); // false +math.isEqual(3.556, 3.555, 0.01); // true ``` ## max diff --git a/lib/digital_signal/digital_sequence.c b/lib/digital_signal/digital_sequence.c index f93ce6d95..14cb3aab2 100644 --- a/lib/digital_signal/digital_sequence.c +++ b/lib/digital_signal/digital_sequence.c @@ -15,6 +15,9 @@ * Example: * ./fbt --extra-define=DIGITAL_SIGNAL_DEBUG_OUTPUT_PIN=gpio_ext_pb3 */ +#ifdef DIGITAL_SIGNAL_DEBUG_OUTPUT_PIN +#include +#endif #define TAG "DigitalSequence" diff --git a/lib/mjs/mjs_exec.c b/lib/mjs/mjs_exec.c index 265e7d5c3..8fdb2d7e5 100644 --- a/lib/mjs/mjs_exec.c +++ b/lib/mjs/mjs_exec.c @@ -452,6 +452,12 @@ static int getprop_builtin_string( } else if(strcmp(name, "slice") == 0) { *res = mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_string_slice); return 1; + } else if(strcmp(name, "toUpperCase") == 0) { + *res = mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_string_to_upper_case); + return 1; + } else if(strcmp(name, "toLowerCase") == 0) { + *res = mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_string_to_lower_case); + return 1; } else if(isnum) { /* * string subscript: return a new one-byte string if the index @@ -469,6 +475,22 @@ static int getprop_builtin_string( return 0; } +static int getprop_builtin_number( + struct mjs* mjs, + mjs_val_t val, + const char* name, + size_t name_len, + mjs_val_t* res) { + if(strcmp(name, "toString") == 0) { + *res = mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_number_to_string); + return 1; + } + + (void)val; + (void)name_len; + return 0; +} + static int getprop_builtin_array( struct mjs* mjs, mjs_val_t val, @@ -583,6 +605,8 @@ static int getprop_builtin(struct mjs* mjs, mjs_val_t val, mjs_val_t name, mjs_v } else if(s != NULL && n == 5 && strncmp(s, "apply", n) == 0) { *res = mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_apply_); handled = 1; + } else if(mjs_is_number(val)) { + handled = getprop_builtin_number(mjs, val, s, n, res); } else if(mjs_is_array(val)) { handled = getprop_builtin_array(mjs, val, s, n, res); } else if(mjs_is_foreign(val)) { diff --git a/lib/mjs/mjs_primitive.c b/lib/mjs/mjs_primitive.c index b63a268e5..e73ae892d 100644 --- a/lib/mjs/mjs_primitive.c +++ b/lib/mjs/mjs_primitive.c @@ -6,6 +6,8 @@ #include "mjs_core.h" #include "mjs_internal.h" #include "mjs_primitive.h" +#include "mjs_string_public.h" +#include "mjs_util.h" mjs_val_t mjs_mk_null(void) { return MJS_NULL; @@ -158,3 +160,31 @@ MJS_PRIVATE void mjs_op_isnan(struct mjs* mjs) { mjs_return(mjs, ret); } + +MJS_PRIVATE void mjs_number_to_string(struct mjs* mjs) { + mjs_val_t ret = MJS_UNDEFINED; + mjs_val_t base_v = MJS_UNDEFINED; + int32_t base = 10; + int32_t num; + + /* get number from `this` */ + if(!mjs_check_arg(mjs, -1 /*this*/, "this", MJS_TYPE_NUMBER, NULL)) { + goto clean; + } + num = mjs_get_int32(mjs, mjs->vals.this_obj); + + if(mjs_nargs(mjs) >= 1) { + /* get base from arg 0 */ + if(!mjs_check_arg(mjs, 0, "base", MJS_TYPE_NUMBER, &base_v)) { + goto clean; + } + base = mjs_get_int(mjs, base_v); + } + + char tmp_str[] = "-2147483648"; + itoa(num, tmp_str, base); + ret = mjs_mk_string(mjs, tmp_str, ~0, true); + +clean: + mjs_return(mjs, ret); +} diff --git a/lib/mjs/mjs_primitive.h b/lib/mjs/mjs_primitive.h index 6d6f91785..f1c3c8220 100644 --- a/lib/mjs/mjs_primitive.h +++ b/lib/mjs/mjs_primitive.h @@ -34,6 +34,11 @@ MJS_PRIVATE void* get_ptr(mjs_val_t v); */ MJS_PRIVATE void mjs_op_isnan(struct mjs* mjs); +/* + * Implementation for JS Number.toString() + */ +MJS_PRIVATE void mjs_number_to_string(struct mjs* mjs); + #if defined(__cplusplus) } #endif /* __cplusplus */ diff --git a/lib/mjs/mjs_string.c b/lib/mjs/mjs_string.c index f74bf1074..f771ff4ea 100644 --- a/lib/mjs/mjs_string.c +++ b/lib/mjs/mjs_string.c @@ -286,6 +286,41 @@ MJS_PRIVATE mjs_val_t s_concat(struct mjs* mjs, mjs_val_t a, mjs_val_t b) { return res; } +MJS_PRIVATE void mjs_string_to_case(struct mjs* mjs, bool upper) { + mjs_val_t ret = MJS_UNDEFINED; + size_t size; + const char* s = NULL; + + /* get string from `this` */ + if(!mjs_check_arg(mjs, -1 /*this*/, "this", MJS_TYPE_STRING, NULL)) { + goto clean; + } + s = mjs_get_string(mjs, &mjs->vals.this_obj, &size); + + if(size == 0) { + ret = mjs_mk_string(mjs, "", 0, 1); + goto clean; + } + + char* tmp = malloc(size); + for(size_t i = 0; i < size; i++) { + tmp[i] = upper ? toupper(s[i]) : tolower(s[i]); + } + ret = mjs_mk_string(mjs, tmp, size, 1); + free(tmp); + +clean: + mjs_return(mjs, ret); +} + +MJS_PRIVATE void mjs_string_to_lower_case(struct mjs* mjs) { + mjs_string_to_case(mjs, false); +} + +MJS_PRIVATE void mjs_string_to_upper_case(struct mjs* mjs) { + mjs_string_to_case(mjs, true); +} + MJS_PRIVATE void mjs_string_slice(struct mjs* mjs) { int nargs = mjs_nargs(mjs); mjs_val_t ret = mjs_mk_number(mjs, 0); diff --git a/lib/mjs/mjs_string.h b/lib/mjs/mjs_string.h index ba6869b62..f0801f1a4 100644 --- a/lib/mjs/mjs_string.h +++ b/lib/mjs/mjs_string.h @@ -33,6 +33,8 @@ MJS_PRIVATE void embed_string( MJS_PRIVATE void mjs_mkstr(struct mjs* mjs); +MJS_PRIVATE void mjs_string_to_lower_case(struct mjs* mjs); +MJS_PRIVATE void mjs_string_to_upper_case(struct mjs* mjs); MJS_PRIVATE void mjs_string_slice(struct mjs* mjs); MJS_PRIVATE void mjs_string_index_of(struct mjs* mjs); MJS_PRIVATE void mjs_string_char_code_at(struct mjs* mjs); diff --git a/lib/nfc/protocols/mf_classic/mf_classic.c b/lib/nfc/protocols/mf_classic/mf_classic.c index 4f92201e3..b1c5c20c9 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic.c +++ b/lib/nfc/protocols/mf_classic/mf_classic.c @@ -543,6 +543,22 @@ void mf_classic_set_key_not_found( } } +MfClassicKey + mf_classic_get_key(const MfClassicData* data, uint8_t sector_num, MfClassicKeyType key_type) { + furi_check(data); + furi_check(sector_num < mf_classic_get_total_sectors_num(data->type)); + furi_check(key_type == MfClassicKeyTypeA || key_type == MfClassicKeyTypeB); + + const MfClassicSectorTrailer* sector_trailer = + mf_classic_get_sector_trailer_by_sector(data, sector_num); + + if(key_type == MfClassicKeyTypeA) { + return sector_trailer->key_a; + } else { + return sector_trailer->key_b; + } +} + bool mf_classic_is_block_read(const MfClassicData* data, uint8_t block_num) { furi_check(data); diff --git a/lib/nfc/protocols/mf_classic/mf_classic.h b/lib/nfc/protocols/mf_classic/mf_classic.h index 38c3e9ba7..6ae7a623e 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic.h +++ b/lib/nfc/protocols/mf_classic/mf_classic.h @@ -213,6 +213,9 @@ void mf_classic_set_key_not_found( uint8_t sector_num, MfClassicKeyType key_type); +MfClassicKey + mf_classic_get_key(const MfClassicData* data, uint8_t sector_num, MfClassicKeyType key_type); + bool mf_classic_is_block_read(const MfClassicData* data, uint8_t block_num); void mf_classic_set_block_read(MfClassicData* data, uint8_t block_num, MfClassicBlock* block_data); diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index 7417322e9..edfdb2a95 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -8,9 +8,9 @@ // TODO: Buffer writes for Hardnested, set state to Log when finished and sum property matches // TODO: Store target key in CUID dictionary -// TODO: Fix rare nested_target_key 64 bug // TODO: Dead code for malloc returning NULL? // TODO: Auth1 static encrypted exists (rare) +// TODO: Use keys found by NFC plugins, cached keys #define MF_CLASSIC_MAX_BUFF_SIZE (64) @@ -1663,7 +1663,7 @@ NfcCommand mf_classic_poller_handler_nested_dict_attack(MfClassicPoller* instanc "Found key candidate %06llx", bit_lib_bytes_to_num_be(key_candidate->data, sizeof(MfClassicKey))); dict_attack_ctx->current_key = *key_candidate; - dict_attack_ctx->reuse_key_sector = (target_block / 4); + dict_attack_ctx->reuse_key_sector = target_sector; dict_attack_ctx->current_key_type = target_key_type; free(key_candidate); break; @@ -1780,7 +1780,7 @@ NfcCommand mf_classic_poller_handler_nested_log(MfClassicPoller* instance) { bool mf_classic_nested_is_target_key_found(MfClassicPoller* instance, bool is_dict_attack) { MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; bool is_weak = dict_attack_ctx->prng_type == MfClassicPrngTypeWeak; - uint8_t nested_target_key = dict_attack_ctx->nested_target_key; + uint16_t nested_target_key = dict_attack_ctx->nested_target_key; MfClassicKeyType target_key_type; uint8_t target_sector; @@ -1818,12 +1818,13 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance bool initial_dict_attack_iter = false; if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseNone) { dict_attack_ctx->auth_passed = true; - dict_attack_ctx->nested_known_key = dict_attack_ctx->current_key; bool backdoor_present = (dict_attack_ctx->backdoor != MfClassicBackdoorNone); if(!(backdoor_present)) { for(uint8_t sector = 0; sector < instance->sectors_total; sector++) { for(uint8_t key_type = 0; key_type < 2; key_type++) { if(mf_classic_is_key_found(instance->data, sector, key_type)) { + dict_attack_ctx->nested_known_key = + mf_classic_get_key(instance->data, sector, key_type); dict_attack_ctx->nested_known_key_sector = sector; dict_attack_ctx->nested_known_key_type = key_type; break; @@ -1832,6 +1833,7 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance } dict_attack_ctx->nested_phase = MfClassicNestedPhaseAnalyzePRNG; } else { + dict_attack_ctx->nested_known_key = dict_attack_ctx->current_key; dict_attack_ctx->nested_known_key_sector = 0; dict_attack_ctx->nested_known_key_type = MfClassicKeyTypeA; dict_attack_ctx->prng_type = MfClassicPrngTypeWeak; @@ -1879,9 +1881,10 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance uint16_t dict_target_key_max = (dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) ? (instance->sectors_total * 2) : (instance->sectors_total * 16); - if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseDictAttackResume) { + if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseDictAttackVerify) { if(!(mf_classic_nested_is_target_key_found(instance, true)) && (dict_attack_ctx->nested_nonce.count > 0)) { + dict_attack_ctx->nested_phase = MfClassicNestedPhaseDictAttackResume; instance->state = MfClassicPollerStateNestedDictAttack; return command; } else { @@ -1896,7 +1899,8 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance dict_attack_ctx->nested_phase = MfClassicNestedPhaseDictAttack; } } - if((dict_attack_ctx->nested_phase == MfClassicNestedPhaseDictAttack) && + if((dict_attack_ctx->nested_phase == MfClassicNestedPhaseDictAttack || + dict_attack_ctx->nested_phase == MfClassicNestedPhaseDictAttackResume) && (dict_attack_ctx->nested_target_key < dict_target_key_max)) { bool is_last_iter_for_hard_key = ((!is_weak) && ((dict_attack_ctx->nested_target_key % 8) == 7)); @@ -1920,11 +1924,14 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance NULL; } if((is_weak || is_last_iter_for_hard_key) && dict_attack_ctx->nested_nonce.count > 0) { - // Key reuse - dict_attack_ctx->nested_phase = MfClassicNestedPhaseDictAttackResume; + // Key verify and reuse + dict_attack_ctx->nested_phase = MfClassicNestedPhaseDictAttackVerify; dict_attack_ctx->auth_passed = false; instance->state = MfClassicPollerStateKeyReuseStartNoOffset; return command; + } else if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseDictAttackResume) { + dict_attack_ctx->nested_phase = MfClassicNestedPhaseDictAttack; + dict_attack_ctx->auth_passed = true; } if(!(dict_attack_ctx->auth_passed)) { dict_attack_ctx->attempt_count++; @@ -2004,9 +2011,7 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance } uint16_t nonce_collect_key_max; if(dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) { - nonce_collect_key_max = dict_attack_ctx->static_encrypted ? - ((instance->sectors_total * 4) - 2) : - (instance->sectors_total * 4); + nonce_collect_key_max = instance->sectors_total * 4; } else { nonce_collect_key_max = instance->sectors_total * 2; } @@ -2047,30 +2052,6 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance dict_attack_ctx->attempt_count = 0; } dict_attack_ctx->auth_passed = true; - if(!(dict_attack_ctx->current_key_checked)) { - dict_attack_ctx->current_key_checked = true; - - // Check if the nested target key is a known key - if(mf_classic_nested_is_target_key_found(instance, false)) { - // Continue to next key - if(!(dict_attack_ctx->static_encrypted)) { - dict_attack_ctx->nested_target_key++; - dict_attack_ctx->current_key_checked = false; - } - instance->state = MfClassicPollerStateNestedController; - return command; - } - - // If it is not a known key, we'll need to calibrate for static encrypted backdoored tags - if((dict_attack_ctx->backdoor == MfClassicBackdoorAuth3) && - (dict_attack_ctx->nested_target_key < nonce_collect_key_max) && - !(recalibrated)) { - dict_attack_ctx->calibrated = false; - dict_attack_ctx->nested_phase = MfClassicNestedPhaseRecalibrate; - instance->state = MfClassicPollerStateNestedController; - return command; - } - } // If we have tried to collect this nonce too many times, skip if((is_weak && (dict_attack_ctx->attempt_count >= MF_CLASSIC_NESTED_RETRY_MAXIMUM)) || @@ -2096,12 +2077,52 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance dict_attack_ctx->attempt_count = 0; } + FURI_LOG_D( + TAG, + "Nested target key: %u (max: %u)", + dict_attack_ctx->nested_target_key, + nonce_collect_key_max); + + if(!(dict_attack_ctx->current_key_checked)) { + if(dict_attack_ctx->nested_target_key == nonce_collect_key_max) { + // All nonces have been collected + FURI_LOG_D(TAG, "All nonces collected"); + instance->state = MfClassicPollerStateNestedController; + return command; + } + + dict_attack_ctx->current_key_checked = true; + + // Check if the nested target key is a known key + if(mf_classic_nested_is_target_key_found(instance, false)) { + // Continue to next key + if(!(dict_attack_ctx->static_encrypted)) { + dict_attack_ctx->nested_target_key++; + dict_attack_ctx->current_key_checked = false; + } + instance->state = MfClassicPollerStateNestedController; + return command; + } + + // If it is not a known key, we'll need to calibrate for static encrypted backdoored tags + if((dict_attack_ctx->backdoor == MfClassicBackdoorAuth3) && + (dict_attack_ctx->nested_target_key < nonce_collect_key_max) && + !(recalibrated)) { + dict_attack_ctx->calibrated = false; + dict_attack_ctx->nested_phase = MfClassicNestedPhaseRecalibrate; + instance->state = MfClassicPollerStateNestedController; + return command; + } + } + FURI_LOG_T(TAG, "Collecting a nonce"); + // Collect a nonce dict_attack_ctx->auth_passed = false; instance->state = MfClassicPollerStateNestedCollectNtEnc; return command; } } + dict_attack_ctx->nested_target_key = 0; dict_attack_ctx->nested_phase = MfClassicNestedPhaseFinished; instance->state = MfClassicPollerStateSuccess; return command; diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.h b/lib/nfc/protocols/mf_classic/mf_classic_poller.h index 7dfd3b6ab..8efb931aa 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.h @@ -52,14 +52,15 @@ typedef enum { * @brief MfClassic poller nested attack phase. */ typedef enum { - MfClassicNestedPhaseNone, - MfClassicNestedPhaseAnalyzePRNG, - MfClassicNestedPhaseDictAttack, - MfClassicNestedPhaseDictAttackResume, - MfClassicNestedPhaseCalibrate, - MfClassicNestedPhaseRecalibrate, - MfClassicNestedPhaseCollectNtEnc, - MfClassicNestedPhaseFinished, + MfClassicNestedPhaseNone, /**< No nested attack has taken place yet. */ + MfClassicNestedPhaseAnalyzePRNG, /**< Analyze nonces produced by the PRNG to determine if they fit a weak PRNG */ + MfClassicNestedPhaseDictAttack, /**< Search keys which match the expected PRNG properties and parity for collected nonces */ + MfClassicNestedPhaseDictAttackVerify, /**< Verify candidate keys by authenticating to the sector with the key */ + MfClassicNestedPhaseDictAttackResume, /**< Resume nested dictionary attack from the last tested (invalid) key */ + MfClassicNestedPhaseCalibrate, /**< Perform necessary calculations to recover the plaintext nonce during later collection phase (weak PRNG tags only) */ + MfClassicNestedPhaseRecalibrate, /**< Collect the next plaintext static encrypted nonce for backdoor static encrypted nonce nested attack */ + MfClassicNestedPhaseCollectNtEnc, /**< Log nonces collected during nested authentication for key recovery */ + MfClassicNestedPhaseFinished, /**< Nested attack has finished */ } MfClassicNestedPhase; /** diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c index aac05748f..deccdbcda 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c @@ -5,7 +5,7 @@ #include -#define TAG "MfCLassicPoller" +#define TAG "MfClassicPoller" MfClassicError mf_classic_process_error(Iso14443_3aError error) { MfClassicError ret = MfClassicErrorNone; diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c index dca31f247..7d51f6c6e 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c @@ -508,7 +508,7 @@ static NfcCommand mf_ultralight_poller_handler_read_pages(MfUltralightPoller* in if(instance->error == MfUltralightErrorNone) { if(start_page < instance->pages_total) { FURI_LOG_D(TAG, "Read page %d success", start_page); - instance->data->page[start_page] = data.page[start_page]; + instance->data->page[start_page] = data.page[0]; instance->pages_read++; instance->data->pages_read = instance->pages_read; } diff --git a/lib/subghz/protocols/keeloq.c b/lib/subghz/protocols/keeloq.c index 8db4d1120..b1dbb6f86 100644 --- a/lib/subghz/protocols/keeloq.c +++ b/lib/subghz/protocols/keeloq.c @@ -255,6 +255,9 @@ static bool subghz_protocol_keeloq_gen_data( } else if(strcmp(instance->manufacture_name, "Centurion") == 0) { decrypt = btn << 28 | (0x1CE) << 16 | instance->generic.cnt; // Centurion -> no serial in hop, uses fixed value 0x1CE - normal learning + } else if(strcmp(instance->manufacture_name, "Monarch") == 0) { + decrypt = btn << 28 | (0x100) << 16 | instance->generic.cnt; + // Monarch -> no serial in hop, uses fixed value 0x100 - normal learning } else if(strcmp(instance->manufacture_name, "Dea_Mio") == 0) { uint8_t first_disc_num = (instance->generic.serial >> 8) & 0xF; uint8_t result_disc = (0xC + (first_disc_num % 4)); @@ -430,12 +433,13 @@ static bool (strcmp(instance->manufacture_name, "Dea_Mio") == 0) || (strcmp(instance->manufacture_name, "NICE_MHOUSE") == 0)) { klq_last_custom_btn = 0xF; - } else if((strcmp(instance->manufacture_name, "FAAC_RC,XT") == 0)) { + } else if( + (strcmp(instance->manufacture_name, "FAAC_RC,XT") == 0) || + (strcmp(instance->manufacture_name, "Monarch") == 0) || + (strcmp(instance->manufacture_name, "NICE_Smilo") == 0)) { klq_last_custom_btn = 0xB; } else if((strcmp(instance->manufacture_name, "Novoferm") == 0)) { klq_last_custom_btn = 0x9; - } else if((strcmp(instance->manufacture_name, "NICE_Smilo") == 0)) { - klq_last_custom_btn = 0xB; } else if((strcmp(instance->manufacture_name, "EcoStar") == 0)) { klq_last_custom_btn = 0x6; } diff --git a/lib/subghz/protocols/protocol_items.c b/lib/subghz/protocols/protocol_items.c index c6a1c08f5..3ebb8b4f7 100644 --- a/lib/subghz/protocols/protocol_items.c +++ b/lib/subghz/protocols/protocol_items.c @@ -67,6 +67,7 @@ const SubGhzProtocol* subghz_protocol_registry_items[] = { &ws_protocol_kedsum_th, &ws_protocol_emose601x, &ws_protocol_acurite_5n1, + &ws_protocol_vauno_en8822c, &subghz_protocol_pocsag, &tpms_protocol_schrader_gg4, &subghz_protocol_bin_raw, diff --git a/lib/subghz/protocols/protocol_items.h b/lib/subghz/protocols/protocol_items.h index 48e72cc44..8fbbc9a82 100644 --- a/lib/subghz/protocols/protocol_items.h +++ b/lib/subghz/protocols/protocol_items.h @@ -68,6 +68,7 @@ #include "acurite_5n1.h" #include "solight_te44.h" #include "bresser_3ch.h" +#include "vauno_en8822c.h" #include "pocsag.h" #include "schrader_gg4.h" #include "bin_raw.h" diff --git a/lib/subghz/protocols/vauno_en8822c.c b/lib/subghz/protocols/vauno_en8822c.c new file mode 100644 index 000000000..e80ec2f84 --- /dev/null +++ b/lib/subghz/protocols/vauno_en8822c.c @@ -0,0 +1,258 @@ +#include "vauno_en8822c.h" +#include "furi/core/log.h" +#define TAG "WSProtocolVaunoEN8822C" + +/* + * Help + * https://github.com/merbanan/rtl_433/blob/master/src/devices/vauno_en8822c.c + * + * Vauno EN8822C sensor on 433.92MHz. + * + * Largely the same as Esperanza EWS, s3318p. + * @sa esperanza_ews.c s3318p.c + + * List of known supported devices: + * - Vauno EN8822C-1 + * - FUZHOU ESUN ELECTRONIC outdoor T21 sensor + * + * Frame structure (42 bits): + * + * Byte: 0 1 2 3 4 + * Nibble: 1 2 3 4 5 6 7 8 9 10 11 + * Type: IIIIIIII B?CCTTTT TTTTTTTT HHHHHHHF FFFBXXXX XX + * + * - I: Random device ID + * - C: Channel (1-3) + * - T: Temperature (Little-endian) + * - H: Humidity (Little-endian) + * - F: Flags (unknown) + * - B: Battery (1=low voltage ~<2.5V) + * - X: Checksum (6 bit nibble sum) + * + * Sample Data: + * + * [00] {42} af 0f a2 7c 01 c0 : 10101111 00001111 10100010 01111100 00000001 11 + * + * - Sensor ID = 175 = 0xaf + * - Channel = 0 + * - temp = -93 = 0x111110100010 + * - TemperatureC = -9.3 + * - hum = 62% = 0x0111110 + * + * Copyright (C) 2022 Jamie Barron + * + * @m7i-org - because the ether is wavy + * + */ + +static const SubGhzBlockConst ws_protocol_vauno_en8822c_const = { + .te_short = 500, + .te_long = 1940, + .te_delta = 150, + .min_count_bit_for_found = 42, +}; + +struct WSProtocolDecoderVaunoEN8822C { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + WSBlockGeneric generic; +}; + +struct WSProtocolEncoderVaunoEN8822C { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + WSBlockGeneric generic; +}; + +const SubGhzProtocolDecoder ws_protocol_vauno_en8822c_decoder = { + .alloc = ws_protocol_decoder_vauno_en8822c_alloc, + .free = ws_protocol_decoder_vauno_en8822c_free, + + .feed = ws_protocol_decoder_vauno_en8822c_feed, + .reset = ws_protocol_decoder_vauno_en8822c_reset, + + .get_hash_data = NULL, + .get_hash_data_long = ws_protocol_decoder_vauno_en8822c_get_hash_data, + .serialize = ws_protocol_decoder_vauno_en8822c_serialize, + .deserialize = ws_protocol_decoder_vauno_en8822c_deserialize, + .get_string = ws_protocol_decoder_vauno_en8822c_get_string, + .get_string_brief = NULL, +}; + +const SubGhzProtocolEncoder ws_protocol_vauno_en8822c_encoder = { + .alloc = NULL, + .free = NULL, + + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol ws_protocol_vauno_en8822c = { + .name = WS_PROTOCOL_VAUNO_EN8822C_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | + SubGhzProtocolFlag_Save, + .filter = SubGhzProtocolFilter_Weather, + .decoder = &ws_protocol_vauno_en8822c_decoder, + .encoder = &ws_protocol_vauno_en8822c_encoder, +}; + +typedef enum { + VaunoEN8822CDecoderStepReset = 0, + VaunoEN8822CDecoderStepSaveDuration, + VaunoEN8822CDecoderStepCheckDuration, +} VaunoEN8822CDecoderStep; + +void* ws_protocol_decoder_vauno_en8822c_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + WSProtocolDecoderVaunoEN8822C* instance = malloc(sizeof(WSProtocolDecoderVaunoEN8822C)); + instance->base.protocol = &ws_protocol_vauno_en8822c; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void ws_protocol_decoder_vauno_en8822c_free(void* context) { + furi_assert(context); + WSProtocolDecoderVaunoEN8822C* instance = context; + free(instance); +} + +void ws_protocol_decoder_vauno_en8822c_reset(void* context) { + furi_assert(context); + WSProtocolDecoderVaunoEN8822C* instance = context; + instance->decoder.parser_step = VaunoEN8822CDecoderStepReset; +} + +static bool ws_protocol_vauno_en8822c_check(WSProtocolDecoderVaunoEN8822C* instance) { + if(!instance->decoder.decode_data) return false; + + // The sum of all nibbles should match the last 6 bits + uint8_t sum = 0; + for(uint8_t i = 6; i <= 38; i += 4) { + sum += ((instance->decoder.decode_data >> i) & 0x0f); + } + + return sum != 0 && (sum & 0x3f) == (instance->decoder.decode_data & 0x3f); +} + +/** + * Analysis of received data + * @param instance Pointer to a WSBlockGeneric* instance + */ +static void ws_protocol_vauno_en8822c_extract_data(WSBlockGeneric* instance) { + instance->id = (instance->data >> 34) & 0xff; + instance->battery_low = (instance->data >> 33) & 0x01; + instance->channel = ((instance->data >> 30) & 0x03); + + int16_t temp = (instance->data >> 18) & 0x0fff; + /* Handle signed data */ + if(temp & 0x0800) { + temp |= 0xf000; + } + instance->temp = (float)temp / 10.0; + + instance->humidity = (instance->data >> 11) & 0x7f; + instance->btn = WS_NO_BTN; +} + +void ws_protocol_decoder_vauno_en8822c_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + WSProtocolDecoderVaunoEN8822C* instance = context; + + switch(instance->decoder.parser_step) { + case VaunoEN8822CDecoderStepReset: + if((!level) && DURATION_DIFF(duration, ws_protocol_vauno_en8822c_const.te_long * 4) < + ws_protocol_vauno_en8822c_const.te_delta) { + instance->decoder.parser_step = VaunoEN8822CDecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } + break; + + case VaunoEN8822CDecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = VaunoEN8822CDecoderStepCheckDuration; + } else { + instance->decoder.parser_step = VaunoEN8822CDecoderStepReset; + } + break; + + case VaunoEN8822CDecoderStepCheckDuration: + if(!level) { + if(DURATION_DIFF(instance->decoder.te_last, ws_protocol_vauno_en8822c_const.te_short) < + ws_protocol_vauno_en8822c_const.te_delta) { + if(DURATION_DIFF(duration, ws_protocol_vauno_en8822c_const.te_long * 2) < + ws_protocol_vauno_en8822c_const.te_delta) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = VaunoEN8822CDecoderStepSaveDuration; + } else if( + DURATION_DIFF(duration, ws_protocol_vauno_en8822c_const.te_long) < + ws_protocol_vauno_en8822c_const.te_delta) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = VaunoEN8822CDecoderStepSaveDuration; + } else if( + DURATION_DIFF(duration, ws_protocol_vauno_en8822c_const.te_long * 4) < + ws_protocol_vauno_en8822c_const.te_delta) { + instance->decoder.parser_step = VaunoEN8822CDecoderStepReset; + if(instance->decoder.decode_count_bit == + ws_protocol_vauno_en8822c_const.min_count_bit_for_found && + ws_protocol_vauno_en8822c_check(instance)) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + ws_protocol_vauno_en8822c_extract_data(&instance->generic); + + if(instance->base.callback) { + instance->base.callback(&instance->base, instance->base.context); + } + } else if(instance->decoder.decode_count_bit == 1) { + instance->decoder.parser_step = VaunoEN8822CDecoderStepSaveDuration; + } + + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } else + instance->decoder.parser_step = VaunoEN8822CDecoderStepReset; + } else + instance->decoder.parser_step = VaunoEN8822CDecoderStepReset; + } else + instance->decoder.parser_step = VaunoEN8822CDecoderStepReset; + break; + } +} + +uint32_t ws_protocol_decoder_vauno_en8822c_get_hash_data(void* context) { + furi_assert(context); + WSProtocolDecoderVaunoEN8822C* instance = context; + return subghz_protocol_blocks_get_hash_data_long( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +SubGhzProtocolStatus ws_protocol_decoder_vauno_en8822c_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + WSProtocolDecoderVaunoEN8822C* instance = context; + return ws_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +SubGhzProtocolStatus + ws_protocol_decoder_vauno_en8822c_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + WSProtocolDecoderVaunoEN8822C* instance = context; + return ws_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + ws_protocol_vauno_en8822c_const.min_count_bit_for_found); +} + +void ws_protocol_decoder_vauno_en8822c_get_string(void* context, FuriString* output) { + furi_assert(context); + WSProtocolDecoderVaunoEN8822C* instance = context; + ws_block_generic_get_string(&instance->generic, output); +} diff --git a/lib/subghz/protocols/vauno_en8822c.h b/lib/subghz/protocols/vauno_en8822c.h new file mode 100644 index 000000000..5c61d32d6 --- /dev/null +++ b/lib/subghz/protocols/vauno_en8822c.h @@ -0,0 +1,80 @@ +#pragma once + +#include + +#include +#include +#include +#include "ws_generic.h" +#include + +#define WS_PROTOCOL_VAUNO_EN8822C_NAME "Vauno-EN8822C" + +typedef struct WSProtocolDecoderVaunoEN8822C WSProtocolDecoderVaunoEN8822C; +typedef struct WSProtocolEncoderVaunoEN8822C WSProtocolEncoderVaunoEN8822C; + +extern const SubGhzProtocolDecoder ws_protocol_vauno_en8822c_decoder; +extern const SubGhzProtocolEncoder ws_protocol_vauno_en8822c_encoder; +extern const SubGhzProtocol ws_protocol_vauno_en8822c; + +/** + * Allocate WSProtocolDecoderVaunoEN8822C. + * @param environment Pointer to a SubGhzEnvironment instance + * @return WSProtocolDecoderVaunoEN8822C* pointer to a WSProtocolDecoderVaunoEN8822C instance + */ +void* ws_protocol_decoder_vauno_en8822c_alloc(SubGhzEnvironment* environment); + +/** + * Free WSProtocolDecoderVaunoEN8822C. + * @param context Pointer to a WSProtocolDecoderVaunoEN8822C instance + */ +void ws_protocol_decoder_vauno_en8822c_free(void* context); + +/** + * Reset decoder WSProtocolDecoderVaunoEN8822C. + * @param context Pointer to a WSProtocolDecoderVaunoEN8822C instance + */ +void ws_protocol_decoder_vauno_en8822c_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a WSProtocolDecoderVaunoEN8822C instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void ws_protocol_decoder_vauno_en8822c_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a WSProtocolDecoderVaunoEN8822C instance + * @return hash Hash sum + */ +uint32_t ws_protocol_decoder_vauno_en8822c_get_hash_data(void* context); + +/** + * Serialize data WSProtocolDecoderVaunoEN8822C. + * @param context Pointer to a WSProtocolDecoderVaunoEN8822C instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return status + */ +SubGhzProtocolStatus ws_protocol_decoder_vauno_en8822c_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data WSProtocolDecoderVaunoEN8822C. + * @param context Pointer to a WSProtocolDecoderVaunoEN8822C instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + ws_protocol_decoder_vauno_en8822c_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a WSProtocolDecoderVaunoEN8822C instance + * @param output Resulting text + */ +void ws_protocol_decoder_vauno_en8822c_get_string(void* context, FuriString* output); diff --git a/scripts/asset_packer.py b/scripts/asset_packer.py index dcfa68f8e..dda625fc3 100755 --- a/scripts/asset_packer.py +++ b/scripts/asset_packer.py @@ -45,6 +45,10 @@ def convert_bmx(img: "Image.Image | pathlib.Path") -> bytes: return data +def copy_file_as_lf(src: "pathlib.Path", dst: "pathlib.Path"): + dst.write_bytes(src.read_bytes().replace(b"\r\n", b"\n")) + + def pack_anim(src: pathlib.Path, dst: pathlib.Path): if not (src / "meta.txt").is_file(): return @@ -53,7 +57,7 @@ def pack_anim(src: pathlib.Path, dst: pathlib.Path): if not frame.is_file(): continue if frame.name == "meta.txt": - shutil.copyfile(frame, dst / frame.name) + copy_file_as_lf(frame, dst / frame.name) elif frame.name.startswith("frame_"): if frame.suffix == ".png": (dst / frame.with_suffix(".bm").name).write_bytes(convert_bm(frame)) @@ -145,7 +149,7 @@ def pack( if (source / "Anims/manifest.txt").exists(): (packed / "Anims").mkdir(parents=True, exist_ok=True) - shutil.copyfile( + copy_file_as_lf( source / "Anims/manifest.txt", packed / "Anims/manifest.txt" ) manifest = (source / "Anims/manifest.txt").read_bytes() diff --git a/scripts/fbt_tools/fbt_extapps.py b/scripts/fbt_tools/fbt_extapps.py index 9c3c28124..da5e272a8 100644 --- a/scripts/fbt_tools/fbt_extapps.py +++ b/scripts/fbt_tools/fbt_extapps.py @@ -314,8 +314,9 @@ def _validate_app_imports(target, source, env): "totp_", "token_info_", "memset_s", - # troika + # social_moscow, troika "mosgortrans_parse_transport_block", + "render_section_header", ) ) and any( @@ -324,6 +325,7 @@ def _validate_app_imports(target, source, env): "advanced_plugin", "gallagher", "js_", # js_app and all js_ modules + "social_moscow", "test_js", "totp_", "troika", diff --git a/scripts/fix_ir_universals.py b/scripts/fix_ir_universals.py new file mode 100755 index 000000000..e51687593 --- /dev/null +++ b/scripts/fix_ir_universals.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +import pathlib + +universals = ( + pathlib.Path(__file__).parent + / "../applications/main/infrared/resources/infrared/assets" +) + +for universal in universals.glob("*.ir"): + text = universal.read_text() + lines = text.splitlines() + signal = [] + comment = [] + signals = [] + for line in lines: + if line.startswith("#"): + comment.append(line) + continue + signal.append(line) + if line.startswith(("data: ", "command: ")): # Last line of this signal + signals.append(("\n".join(signal), "\n".join(comment))) + signal.clear() + comment.clear() + found = dict() + for signal, comment in signals: + if signal in found: + if ( + universal.stem == "projectors" + and found[signal] == 1 + and signal.startswith("name: Power") + ): + # Projectors need double press of power to confirm shutdown, so 1 dupe is fine + found[signal] += 1 + continue + replace = f"\n{comment}\n{signal}" + pos = text.rfind(replace) + text = text[:pos] + text[pos + len(replace) :] + continue + found[signal] = 1 + universal.write_text(text) diff --git a/scripts/fix_mfc_dict.py b/scripts/fix_mfc_dict.py old mode 100644 new mode 100755 index 3bdaf5c69..f89973d3c --- a/scripts/fix_mfc_dict.py +++ b/scripts/fix_mfc_dict.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python3 import pathlib import string diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 9ceb0f14b..55c66b02d 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -975,7 +975,7 @@ Function,+,dir_walk_read,DirWalkResult,"DirWalk*, FuriString*, FileInfo*" Function,+,dir_walk_set_filter_cb,void,"DirWalk*, DirWalkFilterCb, void*" Function,+,dir_walk_set_recurse_filter,void,"DirWalk*, const char**, size_t" Function,+,dir_walk_set_recursive,void,"DirWalk*, _Bool" -Function,-,div,div_t,"int, int" +Function,+,div,div_t,"int, int" Function,+,dolphin_deed,void,DolphinDeed Function,+,dolphin_deed_get_app,DolphinApp,DolphinDeed Function,+,dolphin_deed_get_app_limit,uint8_t,DolphinApp @@ -2587,6 +2587,7 @@ Function,+,mf_classic_get_base_data,Iso14443_3aData*,const MfClassicData* Function,+,mf_classic_get_blocks_num_in_sector,uint8_t,uint8_t Function,+,mf_classic_get_device_name,const char*,"const MfClassicData*, NfcDeviceNameType" Function,+,mf_classic_get_first_block_num_of_sector,uint8_t,uint8_t +Function,+,mf_classic_get_key,MfClassicKey,"const MfClassicData*, uint8_t, MfClassicKeyType" Function,+,mf_classic_get_read_sectors_and_keys,void,"const MfClassicData*, uint8_t*, uint8_t*" Function,+,mf_classic_get_sector_by_block,uint8_t,uint8_t Function,+,mf_classic_get_sector_trailer_by_sector,MfClassicSectorTrailer*,"const MfClassicData*, uint8_t" @@ -3668,8 +3669,6 @@ Function,+,text_box_reset,void,TextBox* Function,+,text_box_set_focus,void,"TextBox*, TextBoxFocus" Function,+,text_box_set_font,void,"TextBox*, TextBoxFont" Function,+,text_box_set_text,void,"TextBox*, const char*" -Function,+,text_input_add_extra_symbol,void,"TextInput*, char" -Function,+,text_input_add_illegal_symbols,void,TextInput* Function,+,text_input_alloc,TextInput*, Function,+,text_input_free,void,TextInput* Function,+,text_input_get_validator_callback,TextInputValidatorCallback,TextInput* @@ -3680,6 +3679,7 @@ Function,+,text_input_set_header_text,void,"TextInput*, const char*" Function,+,text_input_set_minimum_length,void,"TextInput*, size_t" Function,+,text_input_set_result_callback,void,"TextInput*, TextInputCallback, void*, char*, size_t, _Bool" Function,+,text_input_set_validator,void,"TextInput*, TextInputValidatorCallback, void*" +Function,+,text_input_show_illegal_symbols,void,"TextInput*, _Bool" Function,-,tgamma,double,double Function,-,tgammaf,float,float Function,-,tgammal,long double,long double
(property: P, value: Props[P]): void; - /** - * Check if property is available - * @param name Name of the property - */ - hasProperty(name: string): boolean; } export declare class ViewFactory> { diff --git a/applications/system/js_app/types/i2c/index.d.ts b/applications/system/js_app/types/i2c/index.d.ts new file mode 100644 index 000000000..ea0a640cb --- /dev/null +++ b/applications/system/js_app/types/i2c/index.d.ts @@ -0,0 +1,31 @@ +/** + * @brief Check if there is an I2C device ready on the bus + * @param address The device address to check + * @param timeout Timeout in milliseconds + */ +export declare function isDeviceReady(address: number, timeout?: number): boolean; + +/** + * @brief Write data to I2C device and return success status + * @param address The device address to write to + * @param data The data to write to the device + * @param timeout Timeout in milliseconds + */ +export declare function write(address: number, data: number[] | ArrayBuffer, timeout?: number): boolean; + +/** + * @brief Read data from I2C device or return undefined on failure + * @param address The device address to read from + * @param length How many bytes to read + * @param timeout Timeout in milliseconds + */ +export declare function read(address: number, length: number, timeout?: number): ArrayBuffer | undefined; + +/** + * @brief Write data then read from I2C device or return undefined on failure + * @param address The device address to talk to + * @param writeData The data to write to the device + * @param readLength How many bytes to read + * @param timeout Timeout in milliseconds + */ +export declare function writeRead(address: number, writeData: number[] | ArrayBuffer, readLength: number, timeout?: number): ArrayBuffer | undefined; diff --git a/applications/system/js_app/types/math/index.d.ts b/applications/system/js_app/types/math/index.d.ts index 25abca4af..4924eea7e 100644 --- a/applications/system/js_app/types/math/index.d.ts +++ b/applications/system/js_app/types/math/index.d.ts @@ -1,3 +1,4 @@ +export function isEqual(a: number, b: number, tolerance: number): boolean; export function abs(n: number): number; export function acos(n: number): number; export function acosh(n: number): number; @@ -12,6 +13,7 @@ export function clz32(n: number): number; export function cos(n: number): number; export function exp(n: number): number; export function floor(n: number): number; +export function log(n: number): number; export function max(n: number, m: number): number; export function min(n: number, m: number): number; export function pow(n: number, m: number): number; @@ -21,4 +23,5 @@ export function sin(n: number): number; export function sqrt(n: number): number; export function trunc(n: number): number; declare const PI: number; +declare const E: number; declare const EPSILON: number; diff --git a/applications/system/js_app/types/spi/index.d.ts b/applications/system/js_app/types/spi/index.d.ts new file mode 100644 index 000000000..8d72bc29c --- /dev/null +++ b/applications/system/js_app/types/spi/index.d.ts @@ -0,0 +1,30 @@ +/** + * @brief Acquire SPI bus + */ +export declare function acquire(): void; + +/** + * @brief Release SPI bus + */ +export declare function release(): void; + +/** + * @brief Write data to SPI bus and return success status + * @param data The data to write + * @param timeout Timeout in milliseconds + */ +export declare function write(data: number[] | ArrayBuffer, timeout?: number): boolean; + +/** + * @brief Read data from SPI bus or return undefined on failure + * @param length How many bytes to read + * @param timeout Timeout in milliseconds + */ +export declare function read(length: number, timeout?: number): ArrayBuffer | undefined; + +/** + * @brief Write and read data on SPI bus or return undefined on failure + * @param data The data to write, its length also indicates how many bytes will be read + * @param timeout Timeout in milliseconds + */ +export declare function writeRead(data: number[] | ArrayBuffer, timeout?: number): ArrayBuffer | undefined; diff --git a/assets/protobuf b/assets/protobuf index b7d588169..c255d71a9 160000 --- a/assets/protobuf +++ b/assets/protobuf @@ -1 +1 @@ -Subproject commit b7d5881690298de836df6305d4c71f96d5d34c61 +Subproject commit c255d71a90af202515deb7aaa51685d45a196152 diff --git a/documentation/doxygen/index.dox b/documentation/doxygen/index.dox index 7bd9024a1..9587afd8b 100644 --- a/documentation/doxygen/index.dox +++ b/documentation/doxygen/index.dox @@ -1,7 +1,8 @@ /** -@mainpage Overview +@mainpage notitle +## Welcome to Flipper Developer Documentation! -Welcome to the Flipper Developer Documentation! +--- This documentation is intended for developers interested in modifying the Flipper Zero firmware, creating Apps and JavaScript programs, or working on external hardware modules for the device. diff --git a/documentation/js/js_math.md b/documentation/js/js_math.md index 12dae8fb3..ca16a9111 100644 --- a/documentation/js/js_math.md +++ b/documentation/js/js_math.md @@ -223,7 +223,7 @@ math.floor(45.05); // 45 math.floor(45.95); // 45 ``` -## is_equal +## isEqual Return true if the difference between numbers `a` and `b` is less than the specified parameter `e`. ### Parameters @@ -236,8 +236,8 @@ True if the difference between numbers `a` and `b` is less than the specified pa ### Example ```js -math.is_equal(1.4, 1.6, 0.2); // false -math.is_equal(3.556, 3.555, 0.01); // true +math.isEqual(1.4, 1.6, 0.2); // false +math.isEqual(3.556, 3.555, 0.01); // true ``` ## max diff --git a/lib/digital_signal/digital_sequence.c b/lib/digital_signal/digital_sequence.c index f93ce6d95..14cb3aab2 100644 --- a/lib/digital_signal/digital_sequence.c +++ b/lib/digital_signal/digital_sequence.c @@ -15,6 +15,9 @@ * Example: * ./fbt --extra-define=DIGITAL_SIGNAL_DEBUG_OUTPUT_PIN=gpio_ext_pb3 */ +#ifdef DIGITAL_SIGNAL_DEBUG_OUTPUT_PIN +#include +#endif #define TAG "DigitalSequence" diff --git a/lib/mjs/mjs_exec.c b/lib/mjs/mjs_exec.c index 265e7d5c3..8fdb2d7e5 100644 --- a/lib/mjs/mjs_exec.c +++ b/lib/mjs/mjs_exec.c @@ -452,6 +452,12 @@ static int getprop_builtin_string( } else if(strcmp(name, "slice") == 0) { *res = mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_string_slice); return 1; + } else if(strcmp(name, "toUpperCase") == 0) { + *res = mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_string_to_upper_case); + return 1; + } else if(strcmp(name, "toLowerCase") == 0) { + *res = mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_string_to_lower_case); + return 1; } else if(isnum) { /* * string subscript: return a new one-byte string if the index @@ -469,6 +475,22 @@ static int getprop_builtin_string( return 0; } +static int getprop_builtin_number( + struct mjs* mjs, + mjs_val_t val, + const char* name, + size_t name_len, + mjs_val_t* res) { + if(strcmp(name, "toString") == 0) { + *res = mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_number_to_string); + return 1; + } + + (void)val; + (void)name_len; + return 0; +} + static int getprop_builtin_array( struct mjs* mjs, mjs_val_t val, @@ -583,6 +605,8 @@ static int getprop_builtin(struct mjs* mjs, mjs_val_t val, mjs_val_t name, mjs_v } else if(s != NULL && n == 5 && strncmp(s, "apply", n) == 0) { *res = mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_apply_); handled = 1; + } else if(mjs_is_number(val)) { + handled = getprop_builtin_number(mjs, val, s, n, res); } else if(mjs_is_array(val)) { handled = getprop_builtin_array(mjs, val, s, n, res); } else if(mjs_is_foreign(val)) { diff --git a/lib/mjs/mjs_primitive.c b/lib/mjs/mjs_primitive.c index b63a268e5..e73ae892d 100644 --- a/lib/mjs/mjs_primitive.c +++ b/lib/mjs/mjs_primitive.c @@ -6,6 +6,8 @@ #include "mjs_core.h" #include "mjs_internal.h" #include "mjs_primitive.h" +#include "mjs_string_public.h" +#include "mjs_util.h" mjs_val_t mjs_mk_null(void) { return MJS_NULL; @@ -158,3 +160,31 @@ MJS_PRIVATE void mjs_op_isnan(struct mjs* mjs) { mjs_return(mjs, ret); } + +MJS_PRIVATE void mjs_number_to_string(struct mjs* mjs) { + mjs_val_t ret = MJS_UNDEFINED; + mjs_val_t base_v = MJS_UNDEFINED; + int32_t base = 10; + int32_t num; + + /* get number from `this` */ + if(!mjs_check_arg(mjs, -1 /*this*/, "this", MJS_TYPE_NUMBER, NULL)) { + goto clean; + } + num = mjs_get_int32(mjs, mjs->vals.this_obj); + + if(mjs_nargs(mjs) >= 1) { + /* get base from arg 0 */ + if(!mjs_check_arg(mjs, 0, "base", MJS_TYPE_NUMBER, &base_v)) { + goto clean; + } + base = mjs_get_int(mjs, base_v); + } + + char tmp_str[] = "-2147483648"; + itoa(num, tmp_str, base); + ret = mjs_mk_string(mjs, tmp_str, ~0, true); + +clean: + mjs_return(mjs, ret); +} diff --git a/lib/mjs/mjs_primitive.h b/lib/mjs/mjs_primitive.h index 6d6f91785..f1c3c8220 100644 --- a/lib/mjs/mjs_primitive.h +++ b/lib/mjs/mjs_primitive.h @@ -34,6 +34,11 @@ MJS_PRIVATE void* get_ptr(mjs_val_t v); */ MJS_PRIVATE void mjs_op_isnan(struct mjs* mjs); +/* + * Implementation for JS Number.toString() + */ +MJS_PRIVATE void mjs_number_to_string(struct mjs* mjs); + #if defined(__cplusplus) } #endif /* __cplusplus */ diff --git a/lib/mjs/mjs_string.c b/lib/mjs/mjs_string.c index f74bf1074..f771ff4ea 100644 --- a/lib/mjs/mjs_string.c +++ b/lib/mjs/mjs_string.c @@ -286,6 +286,41 @@ MJS_PRIVATE mjs_val_t s_concat(struct mjs* mjs, mjs_val_t a, mjs_val_t b) { return res; } +MJS_PRIVATE void mjs_string_to_case(struct mjs* mjs, bool upper) { + mjs_val_t ret = MJS_UNDEFINED; + size_t size; + const char* s = NULL; + + /* get string from `this` */ + if(!mjs_check_arg(mjs, -1 /*this*/, "this", MJS_TYPE_STRING, NULL)) { + goto clean; + } + s = mjs_get_string(mjs, &mjs->vals.this_obj, &size); + + if(size == 0) { + ret = mjs_mk_string(mjs, "", 0, 1); + goto clean; + } + + char* tmp = malloc(size); + for(size_t i = 0; i < size; i++) { + tmp[i] = upper ? toupper(s[i]) : tolower(s[i]); + } + ret = mjs_mk_string(mjs, tmp, size, 1); + free(tmp); + +clean: + mjs_return(mjs, ret); +} + +MJS_PRIVATE void mjs_string_to_lower_case(struct mjs* mjs) { + mjs_string_to_case(mjs, false); +} + +MJS_PRIVATE void mjs_string_to_upper_case(struct mjs* mjs) { + mjs_string_to_case(mjs, true); +} + MJS_PRIVATE void mjs_string_slice(struct mjs* mjs) { int nargs = mjs_nargs(mjs); mjs_val_t ret = mjs_mk_number(mjs, 0); diff --git a/lib/mjs/mjs_string.h b/lib/mjs/mjs_string.h index ba6869b62..f0801f1a4 100644 --- a/lib/mjs/mjs_string.h +++ b/lib/mjs/mjs_string.h @@ -33,6 +33,8 @@ MJS_PRIVATE void embed_string( MJS_PRIVATE void mjs_mkstr(struct mjs* mjs); +MJS_PRIVATE void mjs_string_to_lower_case(struct mjs* mjs); +MJS_PRIVATE void mjs_string_to_upper_case(struct mjs* mjs); MJS_PRIVATE void mjs_string_slice(struct mjs* mjs); MJS_PRIVATE void mjs_string_index_of(struct mjs* mjs); MJS_PRIVATE void mjs_string_char_code_at(struct mjs* mjs); diff --git a/lib/nfc/protocols/mf_classic/mf_classic.c b/lib/nfc/protocols/mf_classic/mf_classic.c index 4f92201e3..b1c5c20c9 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic.c +++ b/lib/nfc/protocols/mf_classic/mf_classic.c @@ -543,6 +543,22 @@ void mf_classic_set_key_not_found( } } +MfClassicKey + mf_classic_get_key(const MfClassicData* data, uint8_t sector_num, MfClassicKeyType key_type) { + furi_check(data); + furi_check(sector_num < mf_classic_get_total_sectors_num(data->type)); + furi_check(key_type == MfClassicKeyTypeA || key_type == MfClassicKeyTypeB); + + const MfClassicSectorTrailer* sector_trailer = + mf_classic_get_sector_trailer_by_sector(data, sector_num); + + if(key_type == MfClassicKeyTypeA) { + return sector_trailer->key_a; + } else { + return sector_trailer->key_b; + } +} + bool mf_classic_is_block_read(const MfClassicData* data, uint8_t block_num) { furi_check(data); diff --git a/lib/nfc/protocols/mf_classic/mf_classic.h b/lib/nfc/protocols/mf_classic/mf_classic.h index 38c3e9ba7..6ae7a623e 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic.h +++ b/lib/nfc/protocols/mf_classic/mf_classic.h @@ -213,6 +213,9 @@ void mf_classic_set_key_not_found( uint8_t sector_num, MfClassicKeyType key_type); +MfClassicKey + mf_classic_get_key(const MfClassicData* data, uint8_t sector_num, MfClassicKeyType key_type); + bool mf_classic_is_block_read(const MfClassicData* data, uint8_t block_num); void mf_classic_set_block_read(MfClassicData* data, uint8_t block_num, MfClassicBlock* block_data); diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index 7417322e9..edfdb2a95 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -8,9 +8,9 @@ // TODO: Buffer writes for Hardnested, set state to Log when finished and sum property matches // TODO: Store target key in CUID dictionary -// TODO: Fix rare nested_target_key 64 bug // TODO: Dead code for malloc returning NULL? // TODO: Auth1 static encrypted exists (rare) +// TODO: Use keys found by NFC plugins, cached keys #define MF_CLASSIC_MAX_BUFF_SIZE (64) @@ -1663,7 +1663,7 @@ NfcCommand mf_classic_poller_handler_nested_dict_attack(MfClassicPoller* instanc "Found key candidate %06llx", bit_lib_bytes_to_num_be(key_candidate->data, sizeof(MfClassicKey))); dict_attack_ctx->current_key = *key_candidate; - dict_attack_ctx->reuse_key_sector = (target_block / 4); + dict_attack_ctx->reuse_key_sector = target_sector; dict_attack_ctx->current_key_type = target_key_type; free(key_candidate); break; @@ -1780,7 +1780,7 @@ NfcCommand mf_classic_poller_handler_nested_log(MfClassicPoller* instance) { bool mf_classic_nested_is_target_key_found(MfClassicPoller* instance, bool is_dict_attack) { MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; bool is_weak = dict_attack_ctx->prng_type == MfClassicPrngTypeWeak; - uint8_t nested_target_key = dict_attack_ctx->nested_target_key; + uint16_t nested_target_key = dict_attack_ctx->nested_target_key; MfClassicKeyType target_key_type; uint8_t target_sector; @@ -1818,12 +1818,13 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance bool initial_dict_attack_iter = false; if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseNone) { dict_attack_ctx->auth_passed = true; - dict_attack_ctx->nested_known_key = dict_attack_ctx->current_key; bool backdoor_present = (dict_attack_ctx->backdoor != MfClassicBackdoorNone); if(!(backdoor_present)) { for(uint8_t sector = 0; sector < instance->sectors_total; sector++) { for(uint8_t key_type = 0; key_type < 2; key_type++) { if(mf_classic_is_key_found(instance->data, sector, key_type)) { + dict_attack_ctx->nested_known_key = + mf_classic_get_key(instance->data, sector, key_type); dict_attack_ctx->nested_known_key_sector = sector; dict_attack_ctx->nested_known_key_type = key_type; break; @@ -1832,6 +1833,7 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance } dict_attack_ctx->nested_phase = MfClassicNestedPhaseAnalyzePRNG; } else { + dict_attack_ctx->nested_known_key = dict_attack_ctx->current_key; dict_attack_ctx->nested_known_key_sector = 0; dict_attack_ctx->nested_known_key_type = MfClassicKeyTypeA; dict_attack_ctx->prng_type = MfClassicPrngTypeWeak; @@ -1879,9 +1881,10 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance uint16_t dict_target_key_max = (dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) ? (instance->sectors_total * 2) : (instance->sectors_total * 16); - if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseDictAttackResume) { + if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseDictAttackVerify) { if(!(mf_classic_nested_is_target_key_found(instance, true)) && (dict_attack_ctx->nested_nonce.count > 0)) { + dict_attack_ctx->nested_phase = MfClassicNestedPhaseDictAttackResume; instance->state = MfClassicPollerStateNestedDictAttack; return command; } else { @@ -1896,7 +1899,8 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance dict_attack_ctx->nested_phase = MfClassicNestedPhaseDictAttack; } } - if((dict_attack_ctx->nested_phase == MfClassicNestedPhaseDictAttack) && + if((dict_attack_ctx->nested_phase == MfClassicNestedPhaseDictAttack || + dict_attack_ctx->nested_phase == MfClassicNestedPhaseDictAttackResume) && (dict_attack_ctx->nested_target_key < dict_target_key_max)) { bool is_last_iter_for_hard_key = ((!is_weak) && ((dict_attack_ctx->nested_target_key % 8) == 7)); @@ -1920,11 +1924,14 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance NULL; } if((is_weak || is_last_iter_for_hard_key) && dict_attack_ctx->nested_nonce.count > 0) { - // Key reuse - dict_attack_ctx->nested_phase = MfClassicNestedPhaseDictAttackResume; + // Key verify and reuse + dict_attack_ctx->nested_phase = MfClassicNestedPhaseDictAttackVerify; dict_attack_ctx->auth_passed = false; instance->state = MfClassicPollerStateKeyReuseStartNoOffset; return command; + } else if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseDictAttackResume) { + dict_attack_ctx->nested_phase = MfClassicNestedPhaseDictAttack; + dict_attack_ctx->auth_passed = true; } if(!(dict_attack_ctx->auth_passed)) { dict_attack_ctx->attempt_count++; @@ -2004,9 +2011,7 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance } uint16_t nonce_collect_key_max; if(dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) { - nonce_collect_key_max = dict_attack_ctx->static_encrypted ? - ((instance->sectors_total * 4) - 2) : - (instance->sectors_total * 4); + nonce_collect_key_max = instance->sectors_total * 4; } else { nonce_collect_key_max = instance->sectors_total * 2; } @@ -2047,30 +2052,6 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance dict_attack_ctx->attempt_count = 0; } dict_attack_ctx->auth_passed = true; - if(!(dict_attack_ctx->current_key_checked)) { - dict_attack_ctx->current_key_checked = true; - - // Check if the nested target key is a known key - if(mf_classic_nested_is_target_key_found(instance, false)) { - // Continue to next key - if(!(dict_attack_ctx->static_encrypted)) { - dict_attack_ctx->nested_target_key++; - dict_attack_ctx->current_key_checked = false; - } - instance->state = MfClassicPollerStateNestedController; - return command; - } - - // If it is not a known key, we'll need to calibrate for static encrypted backdoored tags - if((dict_attack_ctx->backdoor == MfClassicBackdoorAuth3) && - (dict_attack_ctx->nested_target_key < nonce_collect_key_max) && - !(recalibrated)) { - dict_attack_ctx->calibrated = false; - dict_attack_ctx->nested_phase = MfClassicNestedPhaseRecalibrate; - instance->state = MfClassicPollerStateNestedController; - return command; - } - } // If we have tried to collect this nonce too many times, skip if((is_weak && (dict_attack_ctx->attempt_count >= MF_CLASSIC_NESTED_RETRY_MAXIMUM)) || @@ -2096,12 +2077,52 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance dict_attack_ctx->attempt_count = 0; } + FURI_LOG_D( + TAG, + "Nested target key: %u (max: %u)", + dict_attack_ctx->nested_target_key, + nonce_collect_key_max); + + if(!(dict_attack_ctx->current_key_checked)) { + if(dict_attack_ctx->nested_target_key == nonce_collect_key_max) { + // All nonces have been collected + FURI_LOG_D(TAG, "All nonces collected"); + instance->state = MfClassicPollerStateNestedController; + return command; + } + + dict_attack_ctx->current_key_checked = true; + + // Check if the nested target key is a known key + if(mf_classic_nested_is_target_key_found(instance, false)) { + // Continue to next key + if(!(dict_attack_ctx->static_encrypted)) { + dict_attack_ctx->nested_target_key++; + dict_attack_ctx->current_key_checked = false; + } + instance->state = MfClassicPollerStateNestedController; + return command; + } + + // If it is not a known key, we'll need to calibrate for static encrypted backdoored tags + if((dict_attack_ctx->backdoor == MfClassicBackdoorAuth3) && + (dict_attack_ctx->nested_target_key < nonce_collect_key_max) && + !(recalibrated)) { + dict_attack_ctx->calibrated = false; + dict_attack_ctx->nested_phase = MfClassicNestedPhaseRecalibrate; + instance->state = MfClassicPollerStateNestedController; + return command; + } + } + FURI_LOG_T(TAG, "Collecting a nonce"); + // Collect a nonce dict_attack_ctx->auth_passed = false; instance->state = MfClassicPollerStateNestedCollectNtEnc; return command; } } + dict_attack_ctx->nested_target_key = 0; dict_attack_ctx->nested_phase = MfClassicNestedPhaseFinished; instance->state = MfClassicPollerStateSuccess; return command; diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.h b/lib/nfc/protocols/mf_classic/mf_classic_poller.h index 7dfd3b6ab..8efb931aa 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.h @@ -52,14 +52,15 @@ typedef enum { * @brief MfClassic poller nested attack phase. */ typedef enum { - MfClassicNestedPhaseNone, - MfClassicNestedPhaseAnalyzePRNG, - MfClassicNestedPhaseDictAttack, - MfClassicNestedPhaseDictAttackResume, - MfClassicNestedPhaseCalibrate, - MfClassicNestedPhaseRecalibrate, - MfClassicNestedPhaseCollectNtEnc, - MfClassicNestedPhaseFinished, + MfClassicNestedPhaseNone, /**< No nested attack has taken place yet. */ + MfClassicNestedPhaseAnalyzePRNG, /**< Analyze nonces produced by the PRNG to determine if they fit a weak PRNG */ + MfClassicNestedPhaseDictAttack, /**< Search keys which match the expected PRNG properties and parity for collected nonces */ + MfClassicNestedPhaseDictAttackVerify, /**< Verify candidate keys by authenticating to the sector with the key */ + MfClassicNestedPhaseDictAttackResume, /**< Resume nested dictionary attack from the last tested (invalid) key */ + MfClassicNestedPhaseCalibrate, /**< Perform necessary calculations to recover the plaintext nonce during later collection phase (weak PRNG tags only) */ + MfClassicNestedPhaseRecalibrate, /**< Collect the next plaintext static encrypted nonce for backdoor static encrypted nonce nested attack */ + MfClassicNestedPhaseCollectNtEnc, /**< Log nonces collected during nested authentication for key recovery */ + MfClassicNestedPhaseFinished, /**< Nested attack has finished */ } MfClassicNestedPhase; /** diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c index aac05748f..deccdbcda 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c @@ -5,7 +5,7 @@ #include -#define TAG "MfCLassicPoller" +#define TAG "MfClassicPoller" MfClassicError mf_classic_process_error(Iso14443_3aError error) { MfClassicError ret = MfClassicErrorNone; diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c index dca31f247..7d51f6c6e 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c @@ -508,7 +508,7 @@ static NfcCommand mf_ultralight_poller_handler_read_pages(MfUltralightPoller* in if(instance->error == MfUltralightErrorNone) { if(start_page < instance->pages_total) { FURI_LOG_D(TAG, "Read page %d success", start_page); - instance->data->page[start_page] = data.page[start_page]; + instance->data->page[start_page] = data.page[0]; instance->pages_read++; instance->data->pages_read = instance->pages_read; } diff --git a/lib/subghz/protocols/keeloq.c b/lib/subghz/protocols/keeloq.c index 8db4d1120..b1dbb6f86 100644 --- a/lib/subghz/protocols/keeloq.c +++ b/lib/subghz/protocols/keeloq.c @@ -255,6 +255,9 @@ static bool subghz_protocol_keeloq_gen_data( } else if(strcmp(instance->manufacture_name, "Centurion") == 0) { decrypt = btn << 28 | (0x1CE) << 16 | instance->generic.cnt; // Centurion -> no serial in hop, uses fixed value 0x1CE - normal learning + } else if(strcmp(instance->manufacture_name, "Monarch") == 0) { + decrypt = btn << 28 | (0x100) << 16 | instance->generic.cnt; + // Monarch -> no serial in hop, uses fixed value 0x100 - normal learning } else if(strcmp(instance->manufacture_name, "Dea_Mio") == 0) { uint8_t first_disc_num = (instance->generic.serial >> 8) & 0xF; uint8_t result_disc = (0xC + (first_disc_num % 4)); @@ -430,12 +433,13 @@ static bool (strcmp(instance->manufacture_name, "Dea_Mio") == 0) || (strcmp(instance->manufacture_name, "NICE_MHOUSE") == 0)) { klq_last_custom_btn = 0xF; - } else if((strcmp(instance->manufacture_name, "FAAC_RC,XT") == 0)) { + } else if( + (strcmp(instance->manufacture_name, "FAAC_RC,XT") == 0) || + (strcmp(instance->manufacture_name, "Monarch") == 0) || + (strcmp(instance->manufacture_name, "NICE_Smilo") == 0)) { klq_last_custom_btn = 0xB; } else if((strcmp(instance->manufacture_name, "Novoferm") == 0)) { klq_last_custom_btn = 0x9; - } else if((strcmp(instance->manufacture_name, "NICE_Smilo") == 0)) { - klq_last_custom_btn = 0xB; } else if((strcmp(instance->manufacture_name, "EcoStar") == 0)) { klq_last_custom_btn = 0x6; } diff --git a/lib/subghz/protocols/protocol_items.c b/lib/subghz/protocols/protocol_items.c index c6a1c08f5..3ebb8b4f7 100644 --- a/lib/subghz/protocols/protocol_items.c +++ b/lib/subghz/protocols/protocol_items.c @@ -67,6 +67,7 @@ const SubGhzProtocol* subghz_protocol_registry_items[] = { &ws_protocol_kedsum_th, &ws_protocol_emose601x, &ws_protocol_acurite_5n1, + &ws_protocol_vauno_en8822c, &subghz_protocol_pocsag, &tpms_protocol_schrader_gg4, &subghz_protocol_bin_raw, diff --git a/lib/subghz/protocols/protocol_items.h b/lib/subghz/protocols/protocol_items.h index 48e72cc44..8fbbc9a82 100644 --- a/lib/subghz/protocols/protocol_items.h +++ b/lib/subghz/protocols/protocol_items.h @@ -68,6 +68,7 @@ #include "acurite_5n1.h" #include "solight_te44.h" #include "bresser_3ch.h" +#include "vauno_en8822c.h" #include "pocsag.h" #include "schrader_gg4.h" #include "bin_raw.h" diff --git a/lib/subghz/protocols/vauno_en8822c.c b/lib/subghz/protocols/vauno_en8822c.c new file mode 100644 index 000000000..e80ec2f84 --- /dev/null +++ b/lib/subghz/protocols/vauno_en8822c.c @@ -0,0 +1,258 @@ +#include "vauno_en8822c.h" +#include "furi/core/log.h" +#define TAG "WSProtocolVaunoEN8822C" + +/* + * Help + * https://github.com/merbanan/rtl_433/blob/master/src/devices/vauno_en8822c.c + * + * Vauno EN8822C sensor on 433.92MHz. + * + * Largely the same as Esperanza EWS, s3318p. + * @sa esperanza_ews.c s3318p.c + + * List of known supported devices: + * - Vauno EN8822C-1 + * - FUZHOU ESUN ELECTRONIC outdoor T21 sensor + * + * Frame structure (42 bits): + * + * Byte: 0 1 2 3 4 + * Nibble: 1 2 3 4 5 6 7 8 9 10 11 + * Type: IIIIIIII B?CCTTTT TTTTTTTT HHHHHHHF FFFBXXXX XX + * + * - I: Random device ID + * - C: Channel (1-3) + * - T: Temperature (Little-endian) + * - H: Humidity (Little-endian) + * - F: Flags (unknown) + * - B: Battery (1=low voltage ~<2.5V) + * - X: Checksum (6 bit nibble sum) + * + * Sample Data: + * + * [00] {42} af 0f a2 7c 01 c0 : 10101111 00001111 10100010 01111100 00000001 11 + * + * - Sensor ID = 175 = 0xaf + * - Channel = 0 + * - temp = -93 = 0x111110100010 + * - TemperatureC = -9.3 + * - hum = 62% = 0x0111110 + * + * Copyright (C) 2022 Jamie Barron + * + * @m7i-org - because the ether is wavy + * + */ + +static const SubGhzBlockConst ws_protocol_vauno_en8822c_const = { + .te_short = 500, + .te_long = 1940, + .te_delta = 150, + .min_count_bit_for_found = 42, +}; + +struct WSProtocolDecoderVaunoEN8822C { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + WSBlockGeneric generic; +}; + +struct WSProtocolEncoderVaunoEN8822C { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + WSBlockGeneric generic; +}; + +const SubGhzProtocolDecoder ws_protocol_vauno_en8822c_decoder = { + .alloc = ws_protocol_decoder_vauno_en8822c_alloc, + .free = ws_protocol_decoder_vauno_en8822c_free, + + .feed = ws_protocol_decoder_vauno_en8822c_feed, + .reset = ws_protocol_decoder_vauno_en8822c_reset, + + .get_hash_data = NULL, + .get_hash_data_long = ws_protocol_decoder_vauno_en8822c_get_hash_data, + .serialize = ws_protocol_decoder_vauno_en8822c_serialize, + .deserialize = ws_protocol_decoder_vauno_en8822c_deserialize, + .get_string = ws_protocol_decoder_vauno_en8822c_get_string, + .get_string_brief = NULL, +}; + +const SubGhzProtocolEncoder ws_protocol_vauno_en8822c_encoder = { + .alloc = NULL, + .free = NULL, + + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol ws_protocol_vauno_en8822c = { + .name = WS_PROTOCOL_VAUNO_EN8822C_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | + SubGhzProtocolFlag_Save, + .filter = SubGhzProtocolFilter_Weather, + .decoder = &ws_protocol_vauno_en8822c_decoder, + .encoder = &ws_protocol_vauno_en8822c_encoder, +}; + +typedef enum { + VaunoEN8822CDecoderStepReset = 0, + VaunoEN8822CDecoderStepSaveDuration, + VaunoEN8822CDecoderStepCheckDuration, +} VaunoEN8822CDecoderStep; + +void* ws_protocol_decoder_vauno_en8822c_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + WSProtocolDecoderVaunoEN8822C* instance = malloc(sizeof(WSProtocolDecoderVaunoEN8822C)); + instance->base.protocol = &ws_protocol_vauno_en8822c; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void ws_protocol_decoder_vauno_en8822c_free(void* context) { + furi_assert(context); + WSProtocolDecoderVaunoEN8822C* instance = context; + free(instance); +} + +void ws_protocol_decoder_vauno_en8822c_reset(void* context) { + furi_assert(context); + WSProtocolDecoderVaunoEN8822C* instance = context; + instance->decoder.parser_step = VaunoEN8822CDecoderStepReset; +} + +static bool ws_protocol_vauno_en8822c_check(WSProtocolDecoderVaunoEN8822C* instance) { + if(!instance->decoder.decode_data) return false; + + // The sum of all nibbles should match the last 6 bits + uint8_t sum = 0; + for(uint8_t i = 6; i <= 38; i += 4) { + sum += ((instance->decoder.decode_data >> i) & 0x0f); + } + + return sum != 0 && (sum & 0x3f) == (instance->decoder.decode_data & 0x3f); +} + +/** + * Analysis of received data + * @param instance Pointer to a WSBlockGeneric* instance + */ +static void ws_protocol_vauno_en8822c_extract_data(WSBlockGeneric* instance) { + instance->id = (instance->data >> 34) & 0xff; + instance->battery_low = (instance->data >> 33) & 0x01; + instance->channel = ((instance->data >> 30) & 0x03); + + int16_t temp = (instance->data >> 18) & 0x0fff; + /* Handle signed data */ + if(temp & 0x0800) { + temp |= 0xf000; + } + instance->temp = (float)temp / 10.0; + + instance->humidity = (instance->data >> 11) & 0x7f; + instance->btn = WS_NO_BTN; +} + +void ws_protocol_decoder_vauno_en8822c_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + WSProtocolDecoderVaunoEN8822C* instance = context; + + switch(instance->decoder.parser_step) { + case VaunoEN8822CDecoderStepReset: + if((!level) && DURATION_DIFF(duration, ws_protocol_vauno_en8822c_const.te_long * 4) < + ws_protocol_vauno_en8822c_const.te_delta) { + instance->decoder.parser_step = VaunoEN8822CDecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } + break; + + case VaunoEN8822CDecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = VaunoEN8822CDecoderStepCheckDuration; + } else { + instance->decoder.parser_step = VaunoEN8822CDecoderStepReset; + } + break; + + case VaunoEN8822CDecoderStepCheckDuration: + if(!level) { + if(DURATION_DIFF(instance->decoder.te_last, ws_protocol_vauno_en8822c_const.te_short) < + ws_protocol_vauno_en8822c_const.te_delta) { + if(DURATION_DIFF(duration, ws_protocol_vauno_en8822c_const.te_long * 2) < + ws_protocol_vauno_en8822c_const.te_delta) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = VaunoEN8822CDecoderStepSaveDuration; + } else if( + DURATION_DIFF(duration, ws_protocol_vauno_en8822c_const.te_long) < + ws_protocol_vauno_en8822c_const.te_delta) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = VaunoEN8822CDecoderStepSaveDuration; + } else if( + DURATION_DIFF(duration, ws_protocol_vauno_en8822c_const.te_long * 4) < + ws_protocol_vauno_en8822c_const.te_delta) { + instance->decoder.parser_step = VaunoEN8822CDecoderStepReset; + if(instance->decoder.decode_count_bit == + ws_protocol_vauno_en8822c_const.min_count_bit_for_found && + ws_protocol_vauno_en8822c_check(instance)) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + ws_protocol_vauno_en8822c_extract_data(&instance->generic); + + if(instance->base.callback) { + instance->base.callback(&instance->base, instance->base.context); + } + } else if(instance->decoder.decode_count_bit == 1) { + instance->decoder.parser_step = VaunoEN8822CDecoderStepSaveDuration; + } + + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } else + instance->decoder.parser_step = VaunoEN8822CDecoderStepReset; + } else + instance->decoder.parser_step = VaunoEN8822CDecoderStepReset; + } else + instance->decoder.parser_step = VaunoEN8822CDecoderStepReset; + break; + } +} + +uint32_t ws_protocol_decoder_vauno_en8822c_get_hash_data(void* context) { + furi_assert(context); + WSProtocolDecoderVaunoEN8822C* instance = context; + return subghz_protocol_blocks_get_hash_data_long( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +SubGhzProtocolStatus ws_protocol_decoder_vauno_en8822c_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + WSProtocolDecoderVaunoEN8822C* instance = context; + return ws_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +SubGhzProtocolStatus + ws_protocol_decoder_vauno_en8822c_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + WSProtocolDecoderVaunoEN8822C* instance = context; + return ws_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + ws_protocol_vauno_en8822c_const.min_count_bit_for_found); +} + +void ws_protocol_decoder_vauno_en8822c_get_string(void* context, FuriString* output) { + furi_assert(context); + WSProtocolDecoderVaunoEN8822C* instance = context; + ws_block_generic_get_string(&instance->generic, output); +} diff --git a/lib/subghz/protocols/vauno_en8822c.h b/lib/subghz/protocols/vauno_en8822c.h new file mode 100644 index 000000000..5c61d32d6 --- /dev/null +++ b/lib/subghz/protocols/vauno_en8822c.h @@ -0,0 +1,80 @@ +#pragma once + +#include + +#include +#include +#include +#include "ws_generic.h" +#include + +#define WS_PROTOCOL_VAUNO_EN8822C_NAME "Vauno-EN8822C" + +typedef struct WSProtocolDecoderVaunoEN8822C WSProtocolDecoderVaunoEN8822C; +typedef struct WSProtocolEncoderVaunoEN8822C WSProtocolEncoderVaunoEN8822C; + +extern const SubGhzProtocolDecoder ws_protocol_vauno_en8822c_decoder; +extern const SubGhzProtocolEncoder ws_protocol_vauno_en8822c_encoder; +extern const SubGhzProtocol ws_protocol_vauno_en8822c; + +/** + * Allocate WSProtocolDecoderVaunoEN8822C. + * @param environment Pointer to a SubGhzEnvironment instance + * @return WSProtocolDecoderVaunoEN8822C* pointer to a WSProtocolDecoderVaunoEN8822C instance + */ +void* ws_protocol_decoder_vauno_en8822c_alloc(SubGhzEnvironment* environment); + +/** + * Free WSProtocolDecoderVaunoEN8822C. + * @param context Pointer to a WSProtocolDecoderVaunoEN8822C instance + */ +void ws_protocol_decoder_vauno_en8822c_free(void* context); + +/** + * Reset decoder WSProtocolDecoderVaunoEN8822C. + * @param context Pointer to a WSProtocolDecoderVaunoEN8822C instance + */ +void ws_protocol_decoder_vauno_en8822c_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a WSProtocolDecoderVaunoEN8822C instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void ws_protocol_decoder_vauno_en8822c_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a WSProtocolDecoderVaunoEN8822C instance + * @return hash Hash sum + */ +uint32_t ws_protocol_decoder_vauno_en8822c_get_hash_data(void* context); + +/** + * Serialize data WSProtocolDecoderVaunoEN8822C. + * @param context Pointer to a WSProtocolDecoderVaunoEN8822C instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return status + */ +SubGhzProtocolStatus ws_protocol_decoder_vauno_en8822c_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data WSProtocolDecoderVaunoEN8822C. + * @param context Pointer to a WSProtocolDecoderVaunoEN8822C instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + ws_protocol_decoder_vauno_en8822c_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a WSProtocolDecoderVaunoEN8822C instance + * @param output Resulting text + */ +void ws_protocol_decoder_vauno_en8822c_get_string(void* context, FuriString* output); diff --git a/scripts/asset_packer.py b/scripts/asset_packer.py index dcfa68f8e..dda625fc3 100755 --- a/scripts/asset_packer.py +++ b/scripts/asset_packer.py @@ -45,6 +45,10 @@ def convert_bmx(img: "Image.Image | pathlib.Path") -> bytes: return data +def copy_file_as_lf(src: "pathlib.Path", dst: "pathlib.Path"): + dst.write_bytes(src.read_bytes().replace(b"\r\n", b"\n")) + + def pack_anim(src: pathlib.Path, dst: pathlib.Path): if not (src / "meta.txt").is_file(): return @@ -53,7 +57,7 @@ def pack_anim(src: pathlib.Path, dst: pathlib.Path): if not frame.is_file(): continue if frame.name == "meta.txt": - shutil.copyfile(frame, dst / frame.name) + copy_file_as_lf(frame, dst / frame.name) elif frame.name.startswith("frame_"): if frame.suffix == ".png": (dst / frame.with_suffix(".bm").name).write_bytes(convert_bm(frame)) @@ -145,7 +149,7 @@ def pack( if (source / "Anims/manifest.txt").exists(): (packed / "Anims").mkdir(parents=True, exist_ok=True) - shutil.copyfile( + copy_file_as_lf( source / "Anims/manifest.txt", packed / "Anims/manifest.txt" ) manifest = (source / "Anims/manifest.txt").read_bytes() diff --git a/scripts/fbt_tools/fbt_extapps.py b/scripts/fbt_tools/fbt_extapps.py index 9c3c28124..da5e272a8 100644 --- a/scripts/fbt_tools/fbt_extapps.py +++ b/scripts/fbt_tools/fbt_extapps.py @@ -314,8 +314,9 @@ def _validate_app_imports(target, source, env): "totp_", "token_info_", "memset_s", - # troika + # social_moscow, troika "mosgortrans_parse_transport_block", + "render_section_header", ) ) and any( @@ -324,6 +325,7 @@ def _validate_app_imports(target, source, env): "advanced_plugin", "gallagher", "js_", # js_app and all js_ modules + "social_moscow", "test_js", "totp_", "troika", diff --git a/scripts/fix_ir_universals.py b/scripts/fix_ir_universals.py new file mode 100755 index 000000000..e51687593 --- /dev/null +++ b/scripts/fix_ir_universals.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +import pathlib + +universals = ( + pathlib.Path(__file__).parent + / "../applications/main/infrared/resources/infrared/assets" +) + +for universal in universals.glob("*.ir"): + text = universal.read_text() + lines = text.splitlines() + signal = [] + comment = [] + signals = [] + for line in lines: + if line.startswith("#"): + comment.append(line) + continue + signal.append(line) + if line.startswith(("data: ", "command: ")): # Last line of this signal + signals.append(("\n".join(signal), "\n".join(comment))) + signal.clear() + comment.clear() + found = dict() + for signal, comment in signals: + if signal in found: + if ( + universal.stem == "projectors" + and found[signal] == 1 + and signal.startswith("name: Power") + ): + # Projectors need double press of power to confirm shutdown, so 1 dupe is fine + found[signal] += 1 + continue + replace = f"\n{comment}\n{signal}" + pos = text.rfind(replace) + text = text[:pos] + text[pos + len(replace) :] + continue + found[signal] = 1 + universal.write_text(text) diff --git a/scripts/fix_mfc_dict.py b/scripts/fix_mfc_dict.py old mode 100644 new mode 100755 index 3bdaf5c69..f89973d3c --- a/scripts/fix_mfc_dict.py +++ b/scripts/fix_mfc_dict.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python3 import pathlib import string diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 9ceb0f14b..55c66b02d 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -975,7 +975,7 @@ Function,+,dir_walk_read,DirWalkResult,"DirWalk*, FuriString*, FileInfo*" Function,+,dir_walk_set_filter_cb,void,"DirWalk*, DirWalkFilterCb, void*" Function,+,dir_walk_set_recurse_filter,void,"DirWalk*, const char**, size_t" Function,+,dir_walk_set_recursive,void,"DirWalk*, _Bool" -Function,-,div,div_t,"int, int" +Function,+,div,div_t,"int, int" Function,+,dolphin_deed,void,DolphinDeed Function,+,dolphin_deed_get_app,DolphinApp,DolphinDeed Function,+,dolphin_deed_get_app_limit,uint8_t,DolphinApp @@ -2587,6 +2587,7 @@ Function,+,mf_classic_get_base_data,Iso14443_3aData*,const MfClassicData* Function,+,mf_classic_get_blocks_num_in_sector,uint8_t,uint8_t Function,+,mf_classic_get_device_name,const char*,"const MfClassicData*, NfcDeviceNameType" Function,+,mf_classic_get_first_block_num_of_sector,uint8_t,uint8_t +Function,+,mf_classic_get_key,MfClassicKey,"const MfClassicData*, uint8_t, MfClassicKeyType" Function,+,mf_classic_get_read_sectors_and_keys,void,"const MfClassicData*, uint8_t*, uint8_t*" Function,+,mf_classic_get_sector_by_block,uint8_t,uint8_t Function,+,mf_classic_get_sector_trailer_by_sector,MfClassicSectorTrailer*,"const MfClassicData*, uint8_t" @@ -3668,8 +3669,6 @@ Function,+,text_box_reset,void,TextBox* Function,+,text_box_set_focus,void,"TextBox*, TextBoxFocus" Function,+,text_box_set_font,void,"TextBox*, TextBoxFont" Function,+,text_box_set_text,void,"TextBox*, const char*" -Function,+,text_input_add_extra_symbol,void,"TextInput*, char" -Function,+,text_input_add_illegal_symbols,void,TextInput* Function,+,text_input_alloc,TextInput*, Function,+,text_input_free,void,TextInput* Function,+,text_input_get_validator_callback,TextInputValidatorCallback,TextInput* @@ -3680,6 +3679,7 @@ Function,+,text_input_set_header_text,void,"TextInput*, const char*" Function,+,text_input_set_minimum_length,void,"TextInput*, size_t" Function,+,text_input_set_result_callback,void,"TextInput*, TextInputCallback, void*, char*, size_t, _Bool" Function,+,text_input_set_validator,void,"TextInput*, TextInputValidatorCallback, void*" +Function,+,text_input_show_illegal_symbols,void,"TextInput*, _Bool" Function,-,tgamma,double,double Function,-,tgammaf,float,float Function,-,tgammal,long double,long double