mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-07-13 23:58:11 -07:00
Merge remote-tracking branch 'mntm/dev' into js-app-internal
This commit is contained in:
+37
-9
@@ -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)
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ We offer a powerful and easy-to-use application tailor-made for our firmware, th
|
||||
|
||||
- <ins><b>Interface:</b></ins> Tweak every part of your Flipper, from the desktop animations, to the main menu, lockscreen behavior, file browser, etc.
|
||||
|
||||
- <ins><b>Protocols:</b></ins> Configure SubGhz settings, add/remove custom frequencies, and setup which GPIO pins are used by different external modules.
|
||||
- <ins><b>Protocols:</b></ins> 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.
|
||||
|
||||
- <ins><b>Misc:</b></ins> Everything else that doesn't fit the previous categories. Change your Flipper's name, XP level, screen options, and configure the <a href="https://github.com/Z3BRO/Flipper-Zero-RGB-Backlight">RGB backlight</a>.
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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);
|
||||
|
||||
+1
-1
Submodule applications/external updated: 6e157c28b3...d0300c7a5a
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
@@ -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"],
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -89,6 +89,7 @@ typedef enum {
|
||||
SetTypeAllmatic433,
|
||||
SetTypeAllmatic868,
|
||||
SetTypeCenturion433,
|
||||
SetTypeMonarch433,
|
||||
SetTypeSommer_FM_434,
|
||||
SetTypeSommer_FM_868,
|
||||
SetTypeSommer_FM238_434,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <task_control_block.h>
|
||||
#include <time.h>
|
||||
#include <notification/notification_messages.h>
|
||||
#include <notification/notification_app.h>
|
||||
#include <loader/loader.h>
|
||||
#include <lib/toolbox/args.h>
|
||||
#include <lib/toolbox/strint.h>
|
||||
@@ -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));
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#include <gui/elements.h>
|
||||
#include <assets_icons.h>
|
||||
#include <furi.h>
|
||||
#include <core/dangerous_defines.h>
|
||||
|
||||
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(
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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"],
|
||||
)
|
||||
|
||||
@@ -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!");
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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!");
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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.");
|
||||
|
||||
@@ -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();
|
||||
@@ -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);
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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") {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <common/cs_dbg.h>
|
||||
#include <toolbox/path.h>
|
||||
#include <toolbox/stream/file_stream.h>
|
||||
#include <toolbox/strint.h>
|
||||
#include <loader/firmware_api/firmware_api.h>
|
||||
#include <flipper_application/api_hashtable/api_hashtable.h>
|
||||
#include <flipper_application/plugins/composite_resolver.h>
|
||||
@@ -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));
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -0,0 +1,280 @@
|
||||
#include "../js_modules.h"
|
||||
#include <furi_hal_i2c.h>
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -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));
|
||||
|
||||
@@ -0,0 +1,283 @@
|
||||
#include "../js_modules.h"
|
||||
#include <furi_hal_spi.h>
|
||||
|
||||
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;
|
||||
}
|
||||
+1
-1
@@ -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
|
||||
|
||||
+23
-25
@@ -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 { }
|
||||
|
||||
|
||||
@@ -9,11 +9,6 @@ export declare class View<Props extends Properties> {
|
||||
* @param value Value to assign
|
||||
*/
|
||||
set<P extends keyof Props>(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<Props extends Properties, V extends View<Props>> {
|
||||
|
||||
+31
@@ -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;
|
||||
@@ -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;
|
||||
|
||||
+30
@@ -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;
|
||||
+1
-1
Submodule assets/protobuf updated: b7d5881690...c255d71a90
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
* Example:
|
||||
* ./fbt --extra-define=DIGITAL_SIGNAL_DEBUG_OUTPUT_PIN=gpio_ext_pb3
|
||||
*/
|
||||
#ifdef DIGITAL_SIGNAL_DEBUG_OUTPUT_PIN
|
||||
#include <furi_hal.h>
|
||||
#endif
|
||||
|
||||
#define TAG "DigitalSequence"
|
||||
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
#include <nfc/helpers/iso14443_crc.h>
|
||||
|
||||
#define TAG "MfCLassicPoller"
|
||||
#define TAG "MfClassicPoller"
|
||||
|
||||
MfClassicError mf_classic_process_error(Iso14443_3aError error) {
|
||||
MfClassicError ret = MfClassicErrorNone;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 <gumbald@gmail.com>
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
#pragma once
|
||||
|
||||
#include <lib/subghz/protocols/base.h>
|
||||
|
||||
#include <lib/subghz/blocks/const.h>
|
||||
#include <lib/subghz/blocks/decoder.h>
|
||||
#include <lib/subghz/blocks/encoder.h>
|
||||
#include "ws_generic.h"
|
||||
#include <lib/subghz/blocks/math.h>
|
||||
|
||||
#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);
|
||||
@@ -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()
|
||||
|
||||
@@ -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",
|
||||
|
||||
Executable
+40
@@ -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)
|
||||
Regular → Executable
+1
@@ -1,3 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
import pathlib
|
||||
import string
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
Reference in New Issue
Block a user