Merge branch 'dev' into release
@@ -1,5 +1,5 @@
|
||||
diff --git a/applications/services/notification/notification_app.c b/applications/services/notification/notification_app.c
|
||||
index d4c5b91..8b43599 100644
|
||||
index 35d2fe6..1af97e2 100644
|
||||
--- a/applications/services/notification/notification_app.c
|
||||
+++ b/applications/services/notification/notification_app.c
|
||||
@@ -9,6 +9,7 @@
|
||||
@@ -10,16 +10,16 @@ index d4c5b91..8b43599 100644
|
||||
|
||||
#define TAG "NotificationSrv"
|
||||
|
||||
@@ -588,6 +589,7 @@ int32_t notification_srv(void* p) {
|
||||
@@ -616,6 +617,7 @@ int32_t notification_srv(void* p) {
|
||||
break;
|
||||
case SaveSettingsMessage:
|
||||
notification_save_settings(app);
|
||||
+ rgb_backlight_save_settings();
|
||||
break;
|
||||
}
|
||||
|
||||
case LoadSettingsMessage:
|
||||
notification_load_settings(app);
|
||||
diff --git a/applications/settings/notification_settings/notification_settings_app.c b/applications/settings/notification_settings/notification_settings_app.c
|
||||
index 7576dcf..ae022e2 100644
|
||||
index 2462b32..8e045ce 100644
|
||||
--- a/applications/settings/notification_settings/notification_settings_app.c
|
||||
+++ b/applications/settings/notification_settings/notification_settings_app.c
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
@@ -8,6 +8,3 @@ charset = utf-8
|
||||
[*.{cpp,h,c,py,sh}]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
[{Makefile,*.mk}]
|
||||
indent_size = tab
|
||||
|
||||
3
.gitmodules
vendored
@@ -1,9 +1,6 @@
|
||||
[submodule "lib/mlib"]
|
||||
path = lib/mlib
|
||||
url = https://github.com/P-p-H-d/mlib.git
|
||||
[submodule "lib/littlefs"]
|
||||
path = lib/littlefs
|
||||
url = https://github.com/littlefs-project/littlefs.git
|
||||
[submodule "lib/nanopb"]
|
||||
path = lib/nanopb
|
||||
url = https://github.com/nanopb/nanopb.git
|
||||
|
||||
@@ -1 +1 @@
|
||||
--ignore-ccache -C gccarm --rules-config .pvsconfig -e lib/cmsis_core -e lib/fatfs -e lib/fnv1a-hash -e lib/FreeRTOS-Kernel -e lib/heatshrink -e lib/libusb_stm32 -e lib/littlefs -e lib/mbedtls -e lib/microtar -e lib/mlib -e lib/stm32wb_cmsis -e lib/stm32wb_copro -e lib/stm32wb_hal -e lib/u8g2 -e lib/nanopb -e lib/mjs -e */arm-none-eabi/*
|
||||
--ignore-ccache -C gccarm --rules-config .pvsconfig -e lib/cmsis_core -e lib/fatfs -e lib/fnv1a-hash -e lib/FreeRTOS-Kernel -e lib/heatshrink -e lib/libusb_stm32 -e lib/mbedtls -e lib/microtar -e lib/mlib -e lib/stm32wb_cmsis -e lib/stm32wb_copro -e lib/stm32wb_hal -e lib/u8g2 -e lib/nanopb -e lib/mjs -e */arm-none-eabi/*
|
||||
|
||||
4
.sublime-project
vendored
@@ -10,10 +10,8 @@
|
||||
"clangd": {
|
||||
"enabled": true,
|
||||
"initializationOptions": {
|
||||
// Use with toolchain version 39+
|
||||
// Set `"binary": "custom",` option in LSP-clangd config to use toolchain clangd
|
||||
// "custom_command": ["toolchain/current/bin/clangd"],
|
||||
|
||||
"custom_command": ["toolchain/current/bin/clangd"],
|
||||
"clangd.compile-commands-dir": "build/latest",
|
||||
"clangd.header-insertion": "never",
|
||||
"clangd.query-driver": "**/arm-none-eabi-*",
|
||||
|
||||
2
.vscode/example/settings.json.tmpl
vendored
@@ -12,7 +12,7 @@
|
||||
"SConstruct": "python",
|
||||
"*.fam": "python"
|
||||
},
|
||||
// "clangd.path": "${workspaceFolder}/toolchain/current/bin/clangd@FBT_PLATFORM_EXECUTABLE_EXT@",
|
||||
"clangd.path": "${workspaceFolder}/toolchain/current/bin/clangd@FBT_PLATFORM_EXECUTABLE_EXT@",
|
||||
"clangd.arguments": [
|
||||
"--query-driver=**/arm-none-eabi-*",
|
||||
"--compile-commands-dir=${workspaceFolder}/build/latest",
|
||||
|
||||
56
.vscode/example/tasks.json
vendored
@@ -75,48 +75,42 @@
|
||||
"type": "shell",
|
||||
"command": "./fbt updater_all"
|
||||
},
|
||||
{
|
||||
"label": "[Debug] Flash (USB, w/o resources)",
|
||||
"group": "build",
|
||||
"type": "shell",
|
||||
"command": "./fbt FORCE=1 flash_usb"
|
||||
},
|
||||
{
|
||||
"label": "[Release] Flash (USB, w/o resources)",
|
||||
"group": "build",
|
||||
"type": "shell",
|
||||
"command": "./fbt COMPACT=1 DEBUG=0 FORCE=1 flash_usb"
|
||||
},
|
||||
{
|
||||
"label": "[Debug] Flash (USB, w/o resources)",
|
||||
"group": "build",
|
||||
"type": "shell",
|
||||
"command": "./fbt FORCE=1 flash_usb"
|
||||
},
|
||||
{
|
||||
"label": "[Debug:unit_tests] Flash (USB)",
|
||||
"group": "build",
|
||||
"type": "shell",
|
||||
"command": "./fbt FIRMWARE_APP_SET=unit_tests FORCE=1 flash_usb_full"
|
||||
},
|
||||
{
|
||||
"label": "[Debug] Flash (USB, with resources)",
|
||||
"group": "build",
|
||||
"type": "shell",
|
||||
"command": "./fbt FORCE=1 flash_usb_full"
|
||||
},
|
||||
{
|
||||
"label": "[Release] Flash (USB, with resources)",
|
||||
"group": "build",
|
||||
"type": "shell",
|
||||
"command": "./fbt COMPACT=1 DEBUG=0 FORCE=1 flash_usb_full"
|
||||
},
|
||||
{
|
||||
"label": "[Debug] Flash (USB, with resources)",
|
||||
"group": "build",
|
||||
"type": "shell",
|
||||
"command": "./fbt FORCE=1 flash_usb_full"
|
||||
},
|
||||
{
|
||||
"label": "[Debug] Create PVS-Studio report",
|
||||
"group": "build",
|
||||
"type": "shell",
|
||||
"command": "./fbt firmware_pvs"
|
||||
},
|
||||
{
|
||||
"label": "[Debug] Build FAPs",
|
||||
"group": "build",
|
||||
"type": "shell",
|
||||
"command": "./fbt fap_dist"
|
||||
},
|
||||
{
|
||||
"label": "[Release] Build FAPs",
|
||||
"group": "build",
|
||||
@@ -124,10 +118,10 @@
|
||||
"command": "./fbt COMPACT=1 DEBUG=0 fap_dist"
|
||||
},
|
||||
{
|
||||
"label": "[Debug] Build App",
|
||||
"label": "[Debug] Build FAPs",
|
||||
"group": "build",
|
||||
"type": "shell",
|
||||
"command": "./fbt build APPSRC=${relativeFileDirname}"
|
||||
"command": "./fbt fap_dist"
|
||||
},
|
||||
{
|
||||
"label": "[Release] Build App",
|
||||
@@ -136,10 +130,10 @@
|
||||
"command": "./fbt COMPACT=1 DEBUG=0 build APPSRC=${relativeFileDirname}"
|
||||
},
|
||||
{
|
||||
"label": "[Debug] Launch App on Flipper",
|
||||
"label": "[Debug] Build App",
|
||||
"group": "build",
|
||||
"type": "shell",
|
||||
"command": "./fbt launch APPSRC=${relativeFileDirname}"
|
||||
"command": "./fbt build APPSRC=${relativeFileDirname}"
|
||||
},
|
||||
{
|
||||
"label": "[Release] Launch App on Flipper",
|
||||
@@ -147,6 +141,12 @@
|
||||
"type": "shell",
|
||||
"command": "./fbt COMPACT=1 DEBUG=0 launch APPSRC=${relativeFileDirname}"
|
||||
},
|
||||
{
|
||||
"label": "[Debug] Launch App on Flipper",
|
||||
"group": "build",
|
||||
"type": "shell",
|
||||
"command": "./fbt launch APPSRC=${relativeFileDirname}"
|
||||
},
|
||||
{
|
||||
"label": "[Debug] Launch App on Flipper with Serial Console",
|
||||
"dependsOrder": "sequence",
|
||||
@@ -156,18 +156,18 @@
|
||||
"Serial Console"
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "[Debug] Build and upload all FAPs to Flipper over USB",
|
||||
"group": "build",
|
||||
"type": "shell",
|
||||
"command": "./fbt fap_deploy"
|
||||
},
|
||||
{
|
||||
"label": "[Release] Build and upload all FAPs to Flipper over USB",
|
||||
"group": "build",
|
||||
"type": "shell",
|
||||
"command": "./fbt COMPACT=1 DEBUG=0 fap_deploy"
|
||||
},
|
||||
{
|
||||
"label": "[Debug] Build and upload all FAPs to Flipper over USB",
|
||||
"group": "build",
|
||||
"type": "shell",
|
||||
"command": "./fbt fap_deploy"
|
||||
},
|
||||
{
|
||||
// Press Ctrl+] to quit
|
||||
"label": "Serial Console",
|
||||
|
||||
117
CHANGELOG.md
@@ -1,64 +1,39 @@
|
||||
## Main changes
|
||||
- SubGHz:
|
||||
- **Novoferm** remotes full support
|
||||
- Fix Decode scene in RAW files
|
||||
- Add manually -> Add Sommer FM238 option for cases when default option doesn't work (named as Sommer fm2)
|
||||
- Remove broken preset modulation
|
||||
- Normstahl, Sommer, MHouse, Aprimatic -> Fixes for button codes and more in Add manually
|
||||
- Custom button improvements for MHouse, Novoferm, Nice Smilo
|
||||
- Hormann EcoStar -> Add manually support, and custom button support
|
||||
- Hormann HSM 44bit static -> Button code decoding fix
|
||||
- Choose RSSI threshold for Hopping mode (by @Willy-JL)
|
||||
- OFW: Added protocol for Dickert MAHS garage door remote control
|
||||
- Fix rare crash when opening Read mode via Frequency analyzer
|
||||
- Refactor frequency analyzer code for better readability (by @derskythe | PR #782)
|
||||
- 125kHz RFID:
|
||||
- OFW: Add lfrfid GProxII support
|
||||
- NFC:
|
||||
- OFW: Ultralight C authentication with des key
|
||||
- EMV Transactions less nested, hide if unavailable (by @Willy-JL | PR #771)
|
||||
- Update Mifare Classic default keys dict with new keys from proxmark3 repo and UberGuidoZ repo
|
||||
- LF RFID:
|
||||
- Update T5577 password list (by @korden32 | PR #774)
|
||||
- Add DEZ 8 display form for EM4100 (by @korden32 | PR #776 & (#777 by @mishamyte))
|
||||
- JS:
|
||||
- Refactor widget and keyboard modules, fix crash (by @Willy-JL | PR #770)
|
||||
- SubGHz module fixes and improvements (by @Willy-JL)
|
||||
* OFW: Infrared: check for negative timings
|
||||
* OFW: Fix iButton/LFRFID Add Manually results being discarded
|
||||
* OFW: Event Loop Timers
|
||||
* OFW: Updater: resource compression
|
||||
- OFW: Fix plantain balance string
|
||||
- OFW: Now fifo size in ST25 chip is calculated properly
|
||||
* Docs: Remove not printable symbols and update docs (by @derskythe | PR #783)
|
||||
* OFW: Fix cumulative error in infrared signals
|
||||
* OFW: iButton ID writing (Enable ID writing for ds1971 and ds1996)
|
||||
* Apps: **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev)
|
||||
## Other changes
|
||||
* OFW: HID/BLE Keyboard UI refactoring
|
||||
* OFW: CCID: Add CCIDWorker
|
||||
* OFW: Disabled ISR runtime stats collection for updater builds
|
||||
* OFW: VSCode fixes: .gitignore & clangd
|
||||
* OFW: ufbt: synced .clang-format rules with main
|
||||
* OFW: Code formatting update
|
||||
* OFW: scripts: runfap: fixed starting apps with spaces in path
|
||||
* OFW: toolchain: v38. clangd as default language server
|
||||
* OFW: NFC: ISO15693 Render Typo Fix
|
||||
* OFW: tar archive: fix double free
|
||||
* OFW: ufbt: added ARGS to commandline parser
|
||||
* OFW: lib: sconscript todo cleanup
|
||||
* OFW: Intruder animation
|
||||
* OFW: Desktop: allow to close blocking bad sd animation
|
||||
* OFW: Updater: reset various debug flags on production build flash (was done in same way in UL before)
|
||||
* OFW: Fix PVS Warnings
|
||||
* OFW: CCID: Improve request and response data handling
|
||||
* OFW: Furi: count ISR time. Cli: show ISR time in top.
|
||||
* OFW: toolchain: v37
|
||||
* OFW: NFC: Cache plugin name not full path, saves some RAM (by @Willy-JL)
|
||||
* OFW: copro: bumped to 1.20.0
|
||||
* OFW: input_srv: Put input state data on the stack of the service
|
||||
* OFW: Coalesce some allocations
|
||||
* OFW: updater: slightly smaller image
|
||||
* OFW: Updater: Fix double dir cleanup
|
||||
* OFW: cli: storage: minor subcommand lookup refactor
|
||||
* OFW: LFRFID Securakey: Add Support for RKKTH Plain Text Format
|
||||
* OFW: NFC: Add mf_classic_set_sector_trailer_read function
|
||||
* OFW: Separate editing and renaming in iButton and LFRFID
|
||||
* OFW: New js modules documentation added
|
||||
* OFW: Update link to mfkey32
|
||||
* OFW: NFC: Desfire Renderer Minor Debug
|
||||
* OFW: RPC: Fix input lockup on disconnect
|
||||
* OFW: Thread Signals
|
||||
* Misc: Fix typo in comment in QueueTools.py (by @eltociear | PR #785)
|
||||
* OFW PR 3840: GUI: NumberInput small improvements (by @Willy-JL)
|
||||
* OFW PR 3838: SubGhz: Fix RPC status for ButtonRelease event (by @Skorpionm)
|
||||
* OFW: scripts: improved size validator for updater image
|
||||
* OFW: Desktop: seaprate callbacks for dolphin and storage subscriptions
|
||||
* OFW: Make file extensions case-insensitive
|
||||
* OFW: Remove internal storage folder if corresponding flag set
|
||||
* OFW: **Added a text input that only accepts full numbers (int)**
|
||||
* OFW: FuriEventLoop Pt.2
|
||||
* OFW: Images linting: ensure that all images conform specification
|
||||
* OFW: **Storage: remove LFS**
|
||||
* OFW: NFC: Change the plantain last number display from "?" to "X"
|
||||
* OFW: CCID App: Refactor
|
||||
* OFW: Refactor detected protocols list
|
||||
* OFW: fix: Ensure proper closure of variadic function in `mjs_array`
|
||||
* OFW: **Added** `-Wundef` **to compiler options**
|
||||
* OFW: toolchain: v39
|
||||
* OFW: Furi: update string documentation
|
||||
* OFW: Fix typo in "charge me" screen.
|
||||
* OFW: Reordered VS-Code Tasks to follow the `Release` > `Debug` schema
|
||||
* OFW: Remove unused entries from .editorconfig
|
||||
<br><br>
|
||||
#### Known NFC post-refactor regressions list:
|
||||
- Mifare Mini clones reading is broken (original mini working fine) (OFW)
|
||||
@@ -71,21 +46,21 @@
|
||||
[-> Download qFlipper (official link)](https://flipperzero.one/update)
|
||||
|
||||
## Please support development of the project
|
||||
|Service|Remark|Link/Wallet|
|
||||
|-|-|-|
|
||||
|**Patreon**||https://patreon.com/mmxdev|
|
||||
|**Boosty**|patreon alternative|https://boosty.to/mmxdev|
|
||||
|cloudtips|only RU payments accepted|https://pay.cloudtips.ru/p/7b3e9d65|
|
||||
|YooMoney|only RU payments accepted|https://yoomoney.ru/fundraise/XA49mgQLPA0.221209|
|
||||
|USDT|(TRC20)|`TSXcitMSnWXUFqiUfEXrTVpVewXy2cYhrs`|
|
||||
|ETH|(BSC/ERC20-Tokens)|`darkflippers.eth` (or `0xFebF1bBc8229418FF2408C07AF6Afa49152fEc6a`)|
|
||||
|BTC||`bc1q0np836jk9jwr4dd7p6qv66d04vamtqkxrecck9`|
|
||||
|SOL|(Solana/Tokens)|`DSgwouAEgu8iP5yr7EHHDqMNYWZxAqXWsTEeqCAXGLj8`|
|
||||
|DOGE||`D6R6gYgBn5LwTNmPyvAQR6bZ9EtGgFCpvv`|
|
||||
|LTC||`ltc1q3ex4ejkl0xpx3znwrmth4lyuadr5qgv8tmq8z9`|
|
||||
|BCH||`qquxfyzntuqufy2dx0hrfr4sndp0tucvky4sw8qyu3`|
|
||||
|XMR|(Monero)| `41xUz92suUu1u5Mu4qkrcs52gtfpu9rnZRdBpCJ244KRHf6xXSvVFevdf2cnjS7RAeYr5hn9MsEfxKoFDRSctFjG5fv1Mhn`|
|
||||
|TON||`UQCOqcnYkvzOZUV_9bPE_8oTbOrOF03MnF-VcJyjisTZmsxa`|
|
||||
|Service|Remark|QR Code|Link/Wallet|
|
||||
|-|-|-|-|
|
||||
|**Patreon**||<div align="center"><a href="https://github.com/user-attachments/assets/a88a90a5-28c3-40b4-864a-0c0b79494a42"><img src="https://github.com/user-attachments/assets/da3a864d-d1c7-42cc-8a86-6fcaf26663ec" alt="QR image"/></a></div>|https://patreon.com/mmxdev|
|
||||
|**Boosty**|patreon alternative|<div align="center"><a href="https://github.com/user-attachments/assets/893c0760-f738-42c1-acaa-916019a7bdf8"><img src="https://github.com/user-attachments/assets/da3a864d-d1c7-42cc-8a86-6fcaf26663ec" alt="QR image"/></a></div>|https://boosty.to/mmxdev|
|
||||
|cloudtips|only RU payments accepted|<div align="center"><a href="https://github.com/user-attachments/assets/5de31d6a-ef24-4d30-bd8e-c06af815332a"><img src="https://github.com/user-attachments/assets/da3a864d-d1c7-42cc-8a86-6fcaf26663ec" alt="QR image"/></a></div>|https://pay.cloudtips.ru/p/7b3e9d65|
|
||||
|YooMoney|only RU payments accepted|<div align="center"><a href="https://github.com/user-attachments/assets/33454f79-074b-4349-b453-f94fdadc3c68"><img src="https://github.com/user-attachments/assets/da3a864d-d1c7-42cc-8a86-6fcaf26663ec" alt="QR image"/></a></div>|https://yoomoney.ru/fundraise/XA49mgQLPA0.221209|
|
||||
|USDT|(TRC20)|<div align="center"><a href="https://github.com/user-attachments/assets/0500498d-18ed-412d-a1a4-8a66d0b6f057"><img src="https://github.com/user-attachments/assets/da3a864d-d1c7-42cc-8a86-6fcaf26663ec" alt="QR image"/></a></div>|`TSXcitMSnWXUFqiUfEXrTVpVewXy2cYhrs`|
|
||||
|ETH|(BSC/ERC20-Tokens)|<div align="center"><a href="https://github.com/user-attachments/assets/0f323e98-c524-4f41-abb2-f4f1cec83ab6"><img src="https://github.com/user-attachments/assets/da3a864d-d1c7-42cc-8a86-6fcaf26663ec" alt="QR image"/></a></div>|`darkflippers.eth` (or `0xFebF1bBc8229418FF2408C07AF6Afa49152fEc6a`)|
|
||||
|BTC||<div align="center"><a href="https://github.com/user-attachments/assets/5a904d45-947e-4b92-9f0f-7fbaaa7b37f8"><img src="https://github.com/user-attachments/assets/da3a864d-d1c7-42cc-8a86-6fcaf26663ec" alt="QR image"/></a></div>|`bc1q0np836jk9jwr4dd7p6qv66d04vamtqkxrecck9`|
|
||||
|SOL|(Solana/Tokens)|<div align="center"><a href="https://github.com/user-attachments/assets/ab33c5e0-dd59-497b-9c91-ceb89c36b34d"><img src="https://github.com/user-attachments/assets/da3a864d-d1c7-42cc-8a86-6fcaf26663ec" alt="QR image"/></a></div>|`DSgwouAEgu8iP5yr7EHHDqMNYWZxAqXWsTEeqCAXGLj8`|
|
||||
|DOGE||<div align="center"><a href="https://github.com/user-attachments/assets/2937edd0-5c85-4465-a444-14d4edb481c0"><img src="https://github.com/user-attachments/assets/da3a864d-d1c7-42cc-8a86-6fcaf26663ec" alt="QR image"/></a></div>|`D6R6gYgBn5LwTNmPyvAQR6bZ9EtGgFCpvv`|
|
||||
|LTC||<div align="center"><a href="https://github.com/user-attachments/assets/441985fe-f028-4400-83c1-c215760c1e74"><img src="https://github.com/user-attachments/assets/da3a864d-d1c7-42cc-8a86-6fcaf26663ec" alt="QR image"/></a></div>|`ltc1q3ex4ejkl0xpx3znwrmth4lyuadr5qgv8tmq8z9`|
|
||||
|BCH||<div align="center"><a href="https://github.com/user-attachments/assets/7f365976-19a3-4777-b17e-4bfba5f69eff"><img src="https://github.com/user-attachments/assets/da3a864d-d1c7-42cc-8a86-6fcaf26663ec" alt="QR image"/></a></div>|`qquxfyzntuqufy2dx0hrfr4sndp0tucvky4sw8qyu3`|
|
||||
|XMR|(Monero)|<div align="center"><a href="https://github.com/user-attachments/assets/96186c06-61e7-4b4d-b716-6eaf1779bfd8"><img src="https://github.com/user-attachments/assets/da3a864d-d1c7-42cc-8a86-6fcaf26663ec" alt="QR image"/></a></div>|`41xUz92suUu1u5Mu4qkrcs52gtfpu9rnZRdBpCJ244KRHf6xXSvVFevdf2cnjS7RAeYr5hn9MsEfxKoFDRSctFjG5fv1Mhn`|
|
||||
|TON||<div align="center"><a href="https://github.com/user-attachments/assets/92a57e57-7462-42b7-a342-6f22c6e600c1"><img src="https://github.com/user-attachments/assets/da3a864d-d1c7-42cc-8a86-6fcaf26663ec" alt="QR image"/></a></div>|`UQCOqcnYkvzOZUV_9bPE_8oTbOrOF03MnF-VcJyjisTZmsxa`|
|
||||
|
||||
#### Thanks to our sponsors who supported project in the past and special thanks to sponsors who supports us on regular basis:
|
||||
@mishamyte, ClaraCrazy, Pathfinder [Count Zero cDc], callmezimbra, Quen0n, MERRON, grvpvl (lvpvrg), art_col, ThurstonWaffles, Moneron, UterGrooll, LUCFER, Northpirate, zloepuzo, T.Rat, Alexey B., ionelife, ...
|
||||
|
||||
30
ReadMe.md
@@ -139,21 +139,21 @@ Our team is small and the guys are working on this project as much as they can s
|
||||
The amount of work done on this project is huge and we need your support, no matter how large or small. Even if you just say, "Thank you Unleashed firmware developers!" somewhere. Doing so will help us continue our work and will help drive us to make the firmware better every time.
|
||||
Also, regarding our releases, every build has and always will be free and open-source. There will be no paywall releases or closed-source apps within the firmware. As long as I am working on this project it will never happen.
|
||||
You can support us by using links or addresses below:
|
||||
|Service|Remark|Link/Wallet|
|
||||
|-|-|-|
|
||||
|**Patreon**||https://patreon.com/mmxdev|
|
||||
|**Boosty**|patreon alternative|https://boosty.to/mmxdev|
|
||||
|cloudtips|only RU payments accepted|https://pay.cloudtips.ru/p/7b3e9d65|
|
||||
|YooMoney|only RU payments accepted|https://yoomoney.ru/fundraise/XA49mgQLPA0.221209|
|
||||
|USDT|(TRC20)|`TSXcitMSnWXUFqiUfEXrTVpVewXy2cYhrs`|
|
||||
|ETH|(BSC/ERC20-Tokens)|`darkflippers.eth` (or `0xFebF1bBc8229418FF2408C07AF6Afa49152fEc6a`)|
|
||||
|BTC||`bc1q0np836jk9jwr4dd7p6qv66d04vamtqkxrecck9`|
|
||||
|SOL|(Solana/Tokens)|`DSgwouAEgu8iP5yr7EHHDqMNYWZxAqXWsTEeqCAXGLj8`|
|
||||
|DOGE||`D6R6gYgBn5LwTNmPyvAQR6bZ9EtGgFCpvv`|
|
||||
|LTC||`ltc1q3ex4ejkl0xpx3znwrmth4lyuadr5qgv8tmq8z9`|
|
||||
|BCH||`qquxfyzntuqufy2dx0hrfr4sndp0tucvky4sw8qyu3`|
|
||||
|XMR|(Monero)| `41xUz92suUu1u5Mu4qkrcs52gtfpu9rnZRdBpCJ244KRHf6xXSvVFevdf2cnjS7RAeYr5hn9MsEfxKoFDRSctFjG5fv1Mhn`|
|
||||
|TON||`UQCOqcnYkvzOZUV_9bPE_8oTbOrOF03MnF-VcJyjisTZmsxa`|
|
||||
|Service|Remark|QR Code|Link/Wallet|
|
||||
|-|-|-|-|
|
||||
|**Patreon**||<div align="center"><a href="https://github.com/user-attachments/assets/a88a90a5-28c3-40b4-864a-0c0b79494a42"><img src="https://github.com/user-attachments/assets/da3a864d-d1c7-42cc-8a86-6fcaf26663ec" alt="QR image"/></a></div>|https://patreon.com/mmxdev|
|
||||
|**Boosty**|patreon alternative|<div align="center"><a href="https://github.com/user-attachments/assets/893c0760-f738-42c1-acaa-916019a7bdf8"><img src="https://github.com/user-attachments/assets/da3a864d-d1c7-42cc-8a86-6fcaf26663ec" alt="QR image"/></a></div>|https://boosty.to/mmxdev|
|
||||
|cloudtips|only RU payments accepted|<div align="center"><a href="https://github.com/user-attachments/assets/5de31d6a-ef24-4d30-bd8e-c06af815332a"><img src="https://github.com/user-attachments/assets/da3a864d-d1c7-42cc-8a86-6fcaf26663ec" alt="QR image"/></a></div>|https://pay.cloudtips.ru/p/7b3e9d65|
|
||||
|YooMoney|only RU payments accepted|<div align="center"><a href="https://github.com/user-attachments/assets/33454f79-074b-4349-b453-f94fdadc3c68"><img src="https://github.com/user-attachments/assets/da3a864d-d1c7-42cc-8a86-6fcaf26663ec" alt="QR image"/></a></div>|https://yoomoney.ru/fundraise/XA49mgQLPA0.221209|
|
||||
|USDT|(TRC20)|<div align="center"><a href="https://github.com/user-attachments/assets/0500498d-18ed-412d-a1a4-8a66d0b6f057"><img src="https://github.com/user-attachments/assets/da3a864d-d1c7-42cc-8a86-6fcaf26663ec" alt="QR image"/></a></div>|`TSXcitMSnWXUFqiUfEXrTVpVewXy2cYhrs`|
|
||||
|ETH|(BSC/ERC20-Tokens)|<div align="center"><a href="https://github.com/user-attachments/assets/0f323e98-c524-4f41-abb2-f4f1cec83ab6"><img src="https://github.com/user-attachments/assets/da3a864d-d1c7-42cc-8a86-6fcaf26663ec" alt="QR image"/></a></div>|`darkflippers.eth` (or `0xFebF1bBc8229418FF2408C07AF6Afa49152fEc6a`)|
|
||||
|BTC||<div align="center"><a href="https://github.com/user-attachments/assets/5a904d45-947e-4b92-9f0f-7fbaaa7b37f8"><img src="https://github.com/user-attachments/assets/da3a864d-d1c7-42cc-8a86-6fcaf26663ec" alt="QR image"/></a></div>|`bc1q0np836jk9jwr4dd7p6qv66d04vamtqkxrecck9`|
|
||||
|SOL|(Solana/Tokens)|<div align="center"><a href="https://github.com/user-attachments/assets/ab33c5e0-dd59-497b-9c91-ceb89c36b34d"><img src="https://github.com/user-attachments/assets/da3a864d-d1c7-42cc-8a86-6fcaf26663ec" alt="QR image"/></a></div>|`DSgwouAEgu8iP5yr7EHHDqMNYWZxAqXWsTEeqCAXGLj8`|
|
||||
|DOGE||<div align="center"><a href="https://github.com/user-attachments/assets/2937edd0-5c85-4465-a444-14d4edb481c0"><img src="https://github.com/user-attachments/assets/da3a864d-d1c7-42cc-8a86-6fcaf26663ec" alt="QR image"/></a></div>|`D6R6gYgBn5LwTNmPyvAQR6bZ9EtGgFCpvv`|
|
||||
|LTC||<div align="center"><a href="https://github.com/user-attachments/assets/441985fe-f028-4400-83c1-c215760c1e74"><img src="https://github.com/user-attachments/assets/da3a864d-d1c7-42cc-8a86-6fcaf26663ec" alt="QR image"/></a></div>|`ltc1q3ex4ejkl0xpx3znwrmth4lyuadr5qgv8tmq8z9`|
|
||||
|BCH||<div align="center"><a href="https://github.com/user-attachments/assets/7f365976-19a3-4777-b17e-4bfba5f69eff"><img src="https://github.com/user-attachments/assets/da3a864d-d1c7-42cc-8a86-6fcaf26663ec" alt="QR image"/></a></div>|`qquxfyzntuqufy2dx0hrfr4sndp0tucvky4sw8qyu3`|
|
||||
|XMR|(Monero)|<div align="center"><a href="https://github.com/user-attachments/assets/96186c06-61e7-4b4d-b716-6eaf1779bfd8"><img src="https://github.com/user-attachments/assets/da3a864d-d1c7-42cc-8a86-6fcaf26663ec" alt="QR image"/></a></div>|`41xUz92suUu1u5Mu4qkrcs52gtfpu9rnZRdBpCJ244KRHf6xXSvVFevdf2cnjS7RAeYr5hn9MsEfxKoFDRSctFjG5fv1Mhn`|
|
||||
|TON||<div align="center"><a href="https://github.com/user-attachments/assets/92a57e57-7462-42b7-a342-6f22c6e600c1"><img src="https://github.com/user-attachments/assets/da3a864d-d1c7-42cc-8a86-6fcaf26663ec" alt="QR image"/></a></div>|`UQCOqcnYkvzOZUV_9bPE_8oTbOrOF03MnF-VcJyjisTZmsxa`|
|
||||
|
||||
## Community apps included
|
||||
|
||||
|
||||
40
SConstruct
@@ -322,7 +322,12 @@ firmware_env.Append(
|
||||
"SConstruct",
|
||||
"firmware.scons",
|
||||
"fbt_options.py",
|
||||
]
|
||||
],
|
||||
IMG_LINT_SOURCES=[
|
||||
# Image assets
|
||||
"applications",
|
||||
"assets",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -359,6 +364,39 @@ distenv.PhonyTarget(
|
||||
PY_LINT_SOURCES=firmware_env["PY_LINT_SOURCES"],
|
||||
)
|
||||
|
||||
# Image assets linting
|
||||
distenv.PhonyTarget(
|
||||
"lint_img",
|
||||
[
|
||||
[
|
||||
"${PYTHON3}",
|
||||
"${FBT_SCRIPT_DIR}/imglint.py",
|
||||
"check",
|
||||
"${IMG_LINT_SOURCES}",
|
||||
"${ARGS}",
|
||||
]
|
||||
],
|
||||
IMG_LINT_SOURCES=firmware_env["IMG_LINT_SOURCES"],
|
||||
)
|
||||
|
||||
distenv.PhonyTarget(
|
||||
"format_img",
|
||||
[
|
||||
[
|
||||
"${PYTHON3}",
|
||||
"${FBT_SCRIPT_DIR}/imglint.py",
|
||||
"format",
|
||||
"${IMG_LINT_SOURCES}",
|
||||
"${ARGS}",
|
||||
]
|
||||
],
|
||||
IMG_LINT_SOURCES=firmware_env["IMG_LINT_SOURCES"],
|
||||
)
|
||||
|
||||
distenv.Alias("lint_all", ["lint", "lint_py", "lint_img"])
|
||||
distenv.Alias("format_all", ["format", "format_py", "format_img"])
|
||||
|
||||
|
||||
# Start Flipper CLI via PySerial's miniterm
|
||||
distenv.PhonyTarget(
|
||||
"cli",
|
||||
|
||||
@@ -5,45 +5,49 @@
|
||||
AccessorAppViewManager::AccessorAppViewManager() {
|
||||
event_queue = furi_message_queue_alloc(10, sizeof(AccessorEvent));
|
||||
|
||||
view_dispatcher = view_dispatcher_alloc();
|
||||
auto callback = cbc::obtain_connector(this, &AccessorAppViewManager::previous_view_callback);
|
||||
view_holder = view_holder_alloc();
|
||||
auto callback =
|
||||
cbc::obtain_connector(this, &AccessorAppViewManager::view_holder_back_callback);
|
||||
|
||||
// allocate views
|
||||
submenu = submenu_alloc();
|
||||
add_view(ViewType::Submenu, submenu_get_view(submenu));
|
||||
|
||||
popup = popup_alloc();
|
||||
add_view(ViewType::Popup, popup_get_view(popup));
|
||||
|
||||
// set back callback
|
||||
view_holder_set_back_callback(view_holder, callback, NULL);
|
||||
|
||||
gui = static_cast<Gui*>(furi_record_open(RECORD_GUI));
|
||||
view_dispatcher_attach_to_gui(view_dispatcher, gui, ViewDispatcherTypeFullscreen);
|
||||
|
||||
// set previous view callback for all views
|
||||
view_set_previous_callback(submenu_get_view(submenu), callback);
|
||||
view_set_previous_callback(popup_get_view(popup), callback);
|
||||
view_holder_attach_to_gui(view_holder, gui);
|
||||
}
|
||||
|
||||
AccessorAppViewManager::~AccessorAppViewManager() {
|
||||
// remove views
|
||||
view_dispatcher_remove_view(
|
||||
view_dispatcher, static_cast<uint32_t>(AccessorAppViewManager::ViewType::Submenu));
|
||||
view_dispatcher_remove_view(
|
||||
view_dispatcher, static_cast<uint32_t>(AccessorAppViewManager::ViewType::Popup));
|
||||
|
||||
// remove current view
|
||||
view_holder_set_view(view_holder, NULL);
|
||||
// free view modules
|
||||
furi_record_close(RECORD_GUI);
|
||||
submenu_free(submenu);
|
||||
popup_free(popup);
|
||||
|
||||
// free dispatcher
|
||||
view_dispatcher_free(view_dispatcher);
|
||||
|
||||
// free view holder
|
||||
view_holder_free(view_holder);
|
||||
// free event queue
|
||||
furi_message_queue_free(event_queue);
|
||||
}
|
||||
|
||||
void AccessorAppViewManager::switch_to(ViewType type) {
|
||||
view_dispatcher_switch_to_view(view_dispatcher, static_cast<uint32_t>(type));
|
||||
View* view;
|
||||
|
||||
switch(type) {
|
||||
case ViewType::Submenu:
|
||||
view = submenu_get_view(submenu);
|
||||
break;
|
||||
case ViewType::Popup:
|
||||
view = popup_get_view(popup);
|
||||
break;
|
||||
default:
|
||||
furi_crash();
|
||||
}
|
||||
|
||||
view_holder_set_view(view_holder, view);
|
||||
}
|
||||
|
||||
Submenu* AccessorAppViewManager::get_submenu() {
|
||||
@@ -65,16 +69,10 @@ void AccessorAppViewManager::send_event(AccessorEvent* event) {
|
||||
furi_check(result == FuriStatusOk);
|
||||
}
|
||||
|
||||
uint32_t AccessorAppViewManager::previous_view_callback(void*) {
|
||||
void AccessorAppViewManager::view_holder_back_callback(void*) {
|
||||
if(event_queue != NULL) {
|
||||
AccessorEvent event;
|
||||
event.type = AccessorEvent::Type::Back;
|
||||
send_event(&event);
|
||||
}
|
||||
|
||||
return VIEW_IGNORE;
|
||||
}
|
||||
|
||||
void AccessorAppViewManager::add_view(ViewType view_type, View* view) {
|
||||
view_dispatcher_add_view(view_dispatcher, static_cast<uint32_t>(view_type), view);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
#include <furi.h>
|
||||
#include <gui/view_dispatcher.h>
|
||||
#include <gui/view_holder.h>
|
||||
#include <gui/modules/submenu.h>
|
||||
#include <gui/modules/popup.h>
|
||||
#include "accessor_event.h"
|
||||
@@ -10,7 +10,6 @@ public:
|
||||
enum class ViewType : uint8_t {
|
||||
Submenu,
|
||||
Popup,
|
||||
Tune,
|
||||
};
|
||||
|
||||
FuriMessageQueue* event_queue;
|
||||
@@ -27,11 +26,10 @@ public:
|
||||
Popup* get_popup(void);
|
||||
|
||||
private:
|
||||
ViewDispatcher* view_dispatcher;
|
||||
Gui* gui;
|
||||
ViewHolder* view_holder;
|
||||
|
||||
uint32_t previous_view_callback(void* context);
|
||||
void add_view(ViewType view_type, View* view);
|
||||
void view_holder_back_callback(void* context);
|
||||
|
||||
// view elements
|
||||
Submenu* submenu;
|
||||
|
||||
@@ -42,7 +42,6 @@ BatteryTestApp* battery_test_alloc(void) {
|
||||
|
||||
// View dispatcher
|
||||
app->view_dispatcher = view_dispatcher_alloc();
|
||||
view_dispatcher_enable_queue(app->view_dispatcher);
|
||||
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
|
||||
view_dispatcher_set_tick_event_callback(
|
||||
app->view_dispatcher, battery_test_battery_info_update_model, 500);
|
||||
|
||||
@@ -36,7 +36,6 @@ BtDebugApp* bt_debug_app_alloc(void) {
|
||||
|
||||
// View dispatcher
|
||||
app->view_dispatcher = view_dispatcher_alloc();
|
||||
view_dispatcher_enable_queue(app->view_dispatcher);
|
||||
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
|
||||
|
||||
// Views
|
||||
|
||||
@@ -6,10 +6,13 @@
|
||||
#include <gui/view_dispatcher.h>
|
||||
#include <gui/modules/submenu.h>
|
||||
#include <gui/gui.h>
|
||||
#include "iso7816_callbacks.h"
|
||||
#include "iso7816_t0_apdu.h"
|
||||
#include "iso7816_atr.h"
|
||||
#include "iso7816_response.h"
|
||||
|
||||
#include "iso7816/iso7816_handler.h"
|
||||
#include "iso7816/iso7816_t0_apdu.h"
|
||||
#include "iso7816/iso7816_atr.h"
|
||||
#include "iso7816/iso7816_response.h"
|
||||
|
||||
#include "ccid_test_app_commands.h"
|
||||
|
||||
typedef enum {
|
||||
EventTypeInput,
|
||||
@@ -20,6 +23,7 @@ typedef struct {
|
||||
ViewPort* view_port;
|
||||
FuriMessageQueue* event_queue;
|
||||
FuriHalUsbCcidConfig ccid_cfg;
|
||||
Iso7816Handler* iso7816_handler;
|
||||
} CcidTestApp;
|
||||
|
||||
typedef struct {
|
||||
@@ -63,6 +67,15 @@ uint32_t ccid_test_exit(void* context) {
|
||||
CcidTestApp* ccid_test_app_alloc(void) {
|
||||
CcidTestApp* app = malloc(sizeof(CcidTestApp));
|
||||
|
||||
//setup CCID USB
|
||||
// On linux: set VID PID using: /usr/lib/pcsc/drivers/ifd-ccid.bundle/Contents/Info.plist
|
||||
app->ccid_cfg.vid = 0x076B;
|
||||
app->ccid_cfg.pid = 0x3A21;
|
||||
|
||||
app->iso7816_handler = iso7816_handler_alloc();
|
||||
app->iso7816_handler->iso7816_answer_to_reset = iso7816_answer_to_reset;
|
||||
app->iso7816_handler->iso7816_process_command = iso7816_process_command;
|
||||
|
||||
// Gui
|
||||
app->gui = furi_record_open(RECORD_GUI);
|
||||
|
||||
@@ -92,174 +105,26 @@ void ccid_test_app_free(CcidTestApp* app) {
|
||||
furi_record_close(RECORD_GUI);
|
||||
app->gui = NULL;
|
||||
|
||||
free(app->iso7816_handler);
|
||||
|
||||
// Free rest
|
||||
free(app);
|
||||
}
|
||||
|
||||
void ccid_icc_power_on_callback(uint8_t* atrBuffer, uint32_t* atrlen, void* context) {
|
||||
UNUSED(context);
|
||||
|
||||
iso7816_icc_power_on_callback(atrBuffer, atrlen);
|
||||
}
|
||||
|
||||
void ccid_xfr_datablock_callback(
|
||||
const uint8_t* pcToReaderDataBlock,
|
||||
uint32_t pcToReaderDataBlockLen,
|
||||
uint8_t* readerToPcDataBlock,
|
||||
uint32_t* readerToPcDataBlockLen,
|
||||
void* context) {
|
||||
UNUSED(context);
|
||||
|
||||
iso7816_xfr_datablock_callback(
|
||||
pcToReaderDataBlock, pcToReaderDataBlockLen, readerToPcDataBlock, readerToPcDataBlockLen);
|
||||
}
|
||||
|
||||
static const CcidCallbacks ccid_cb = {
|
||||
ccid_icc_power_on_callback,
|
||||
ccid_xfr_datablock_callback,
|
||||
};
|
||||
|
||||
//Instruction 1: returns an OK response unconditionally
|
||||
//APDU example: 0x01:0x01:0x00:0x00
|
||||
//response: SW1=0x90, SW2=0x00
|
||||
void handle_instruction_01(ISO7816_Response_APDU* responseAPDU) {
|
||||
responseAPDU->DataLen = 0;
|
||||
iso7816_set_response(responseAPDU, ISO7816_RESPONSE_OK);
|
||||
}
|
||||
|
||||
//Instruction 2: expect command with no body, replies wit with a body with two bytes
|
||||
//APDU example: 0x01:0x02:0x00:0x00:0x02
|
||||
//response: 'bc' (0x62, 0x63) SW1=0x90, SW2=0x00
|
||||
void handle_instruction_02(
|
||||
uint8_t p1,
|
||||
uint8_t p2,
|
||||
uint16_t lc,
|
||||
uint16_t le,
|
||||
ISO7816_Response_APDU* responseAPDU) {
|
||||
if(p1 == 0 && p2 == 0 && lc == 0 && le >= 2) {
|
||||
responseAPDU->Data[0] = 0x62;
|
||||
responseAPDU->Data[1] = 0x63;
|
||||
|
||||
responseAPDU->DataLen = 2;
|
||||
|
||||
iso7816_set_response(responseAPDU, ISO7816_RESPONSE_OK);
|
||||
} else if(p1 != 0 || p2 != 0) {
|
||||
iso7816_set_response(responseAPDU, ISO7816_RESPONSE_WRONG_PARAMETERS_P1_P2);
|
||||
} else {
|
||||
iso7816_set_response(responseAPDU, ISO7816_RESPONSE_WRONG_LENGTH);
|
||||
}
|
||||
}
|
||||
|
||||
//Instruction 3: sends a command with a body with two bytes, receives a response with no bytes
|
||||
//APDU example: 0x01:0x03:0x00:0x00:0x02:CA:FE
|
||||
//response SW1=0x90, SW2=0x00
|
||||
void handle_instruction_03(
|
||||
uint8_t p1,
|
||||
uint8_t p2,
|
||||
uint16_t lc,
|
||||
ISO7816_Response_APDU* responseAPDU) {
|
||||
if(p1 == 0 && p2 == 0 && lc == 2) {
|
||||
responseAPDU->DataLen = 0;
|
||||
iso7816_set_response(responseAPDU, ISO7816_RESPONSE_OK);
|
||||
} else if(p1 != 0 || p2 != 0) {
|
||||
iso7816_set_response(responseAPDU, ISO7816_RESPONSE_WRONG_PARAMETERS_P1_P2);
|
||||
} else {
|
||||
iso7816_set_response(responseAPDU, ISO7816_RESPONSE_WRONG_LENGTH);
|
||||
}
|
||||
}
|
||||
|
||||
//instruction 4: sends a command with a body with 'n' bytes, receives a response with 'n' bytes
|
||||
//APDU example: 0x01:0x04:0x00:0x00:0x04:0x01:0x02:0x03:0x04:0x04
|
||||
//receives (0x01, 0x02, 0x03, 0x04) SW1=0x90, SW2=0x00
|
||||
void handle_instruction_04(
|
||||
uint8_t p1,
|
||||
uint8_t p2,
|
||||
uint16_t lc,
|
||||
uint16_t le,
|
||||
const uint8_t* commandApduDataBuffer,
|
||||
ISO7816_Response_APDU* responseAPDU) {
|
||||
if(p1 == 0 && p2 == 0 && lc > 0 && le > 0 && le >= lc) {
|
||||
for(uint16_t i = 0; i < lc; i++) {
|
||||
responseAPDU->Data[i] = commandApduDataBuffer[i];
|
||||
}
|
||||
|
||||
responseAPDU->DataLen = lc;
|
||||
|
||||
iso7816_set_response(responseAPDU, ISO7816_RESPONSE_OK);
|
||||
} else if(p1 != 0 || p2 != 0) {
|
||||
iso7816_set_response(responseAPDU, ISO7816_RESPONSE_WRONG_PARAMETERS_P1_P2);
|
||||
} else {
|
||||
iso7816_set_response(responseAPDU, ISO7816_RESPONSE_WRONG_LENGTH);
|
||||
}
|
||||
}
|
||||
|
||||
void iso7816_answer_to_reset(Iso7816Atr* atr) {
|
||||
//minimum valid ATR: https://smartcard-atr.apdu.fr/parse?ATR=3B+00
|
||||
atr->TS = 0x3B;
|
||||
atr->T0 = 0x00;
|
||||
}
|
||||
|
||||
void iso7816_process_command(
|
||||
const ISO7816_Command_APDU* commandAPDU,
|
||||
ISO7816_Response_APDU* responseAPDU) {
|
||||
//example 1: sends a command with no body, receives a response with no body
|
||||
//sends APDU 0x01:0x01:0x00:0x00
|
||||
//receives SW1=0x90, SW2=0x00
|
||||
|
||||
if(commandAPDU->CLA == 0x01) {
|
||||
switch(commandAPDU->INS) {
|
||||
case 0x01:
|
||||
handle_instruction_01(responseAPDU);
|
||||
break;
|
||||
case 0x02:
|
||||
handle_instruction_02(
|
||||
commandAPDU->P1, commandAPDU->P2, commandAPDU->Lc, commandAPDU->Le, responseAPDU);
|
||||
break;
|
||||
case 0x03:
|
||||
handle_instruction_03(commandAPDU->P1, commandAPDU->P2, commandAPDU->Lc, responseAPDU);
|
||||
break;
|
||||
case 0x04:
|
||||
handle_instruction_04(
|
||||
commandAPDU->P1,
|
||||
commandAPDU->P2,
|
||||
commandAPDU->Lc,
|
||||
commandAPDU->Le,
|
||||
commandAPDU->Data,
|
||||
responseAPDU);
|
||||
break;
|
||||
default:
|
||||
iso7816_set_response(responseAPDU, ISO7816_RESPONSE_INSTRUCTION_NOT_SUPPORTED);
|
||||
}
|
||||
} else {
|
||||
iso7816_set_response(responseAPDU, ISO7816_RESPONSE_CLASS_NOT_SUPPORTED);
|
||||
}
|
||||
}
|
||||
|
||||
static const Iso7816Callbacks iso87816_cb = {
|
||||
iso7816_answer_to_reset,
|
||||
iso7816_process_command,
|
||||
};
|
||||
|
||||
int32_t ccid_test_app(void* p) {
|
||||
UNUSED(p);
|
||||
|
||||
//setup view
|
||||
CcidTestApp* app = ccid_test_app_alloc();
|
||||
|
||||
//setup CCID USB
|
||||
// On linux: set VID PID using: /usr/lib/pcsc/drivers/ifd-ccid.bundle/Contents/Info.plist
|
||||
app->ccid_cfg.vid = 0x076B;
|
||||
app->ccid_cfg.pid = 0x3A21;
|
||||
|
||||
FuriHalUsbInterface* usb_mode_prev = furi_hal_usb_get_config();
|
||||
furi_hal_usb_unlock();
|
||||
|
||||
furi_check(furi_hal_usb_set_config(&usb_ccid, &app->ccid_cfg) == true);
|
||||
furi_hal_usb_ccid_set_callbacks((CcidCallbacks*)&ccid_cb, NULL);
|
||||
furi_hal_usb_ccid_set_callbacks(
|
||||
(CcidCallbacks*)&app->iso7816_handler->ccid_callbacks, app->iso7816_handler);
|
||||
furi_hal_usb_ccid_insert_smartcard();
|
||||
|
||||
iso7816_set_callbacks((Iso7816Callbacks*)&iso87816_cb);
|
||||
|
||||
//handle button events
|
||||
CcidTestAppEvent event;
|
||||
while(1) {
|
||||
@@ -280,8 +145,6 @@ int32_t ccid_test_app(void* p) {
|
||||
furi_hal_usb_ccid_set_callbacks(NULL, NULL);
|
||||
furi_hal_usb_set_config(usb_mode_prev, NULL);
|
||||
|
||||
iso7816_set_callbacks(NULL);
|
||||
|
||||
//teardown view
|
||||
ccid_test_app_free(app);
|
||||
return 0;
|
||||
|
||||
123
applications/debug/ccid_test/ccid_test_app_commands.c
Normal file
@@ -0,0 +1,123 @@
|
||||
#include "iso7816/iso7816_t0_apdu.h"
|
||||
#include "iso7816/iso7816_response.h"
|
||||
|
||||
//Instruction 1: returns an OK response unconditionally
|
||||
//APDU example: 0x01:0x01:0x00:0x00
|
||||
//response: SW1=0x90, SW2=0x00
|
||||
void handle_instruction_01(ISO7816_Response_APDU* response_apdu) {
|
||||
response_apdu->DataLen = 0;
|
||||
iso7816_set_response(response_apdu, ISO7816_RESPONSE_OK);
|
||||
}
|
||||
|
||||
//Instruction 2: expect command with no body, replies wit with a body with two bytes
|
||||
//APDU example: 0x01:0x02:0x00:0x00:0x02
|
||||
//response: 'bc' (0x62, 0x63) SW1=0x90, SW2=0x00
|
||||
void handle_instruction_02(
|
||||
uint8_t p1,
|
||||
uint8_t p2,
|
||||
uint16_t lc,
|
||||
uint16_t le,
|
||||
ISO7816_Response_APDU* response_apdu) {
|
||||
if(p1 == 0 && p2 == 0 && lc == 0 && le >= 2) {
|
||||
response_apdu->Data[0] = 0x62;
|
||||
response_apdu->Data[1] = 0x63;
|
||||
|
||||
response_apdu->DataLen = 2;
|
||||
|
||||
iso7816_set_response(response_apdu, ISO7816_RESPONSE_OK);
|
||||
} else if(p1 != 0 || p2 != 0) {
|
||||
iso7816_set_response(response_apdu, ISO7816_RESPONSE_WRONG_PARAMETERS_P1_P2);
|
||||
} else {
|
||||
iso7816_set_response(response_apdu, ISO7816_RESPONSE_WRONG_LENGTH);
|
||||
}
|
||||
}
|
||||
|
||||
//Instruction 3: sends a command with a body with two bytes, receives a response with no bytes
|
||||
//APDU example: 0x01:0x03:0x00:0x00:0x02:CA:FE
|
||||
//response SW1=0x90, SW2=0x00
|
||||
void handle_instruction_03(
|
||||
uint8_t p1,
|
||||
uint8_t p2,
|
||||
uint16_t lc,
|
||||
ISO7816_Response_APDU* response_apdu) {
|
||||
if(p1 == 0 && p2 == 0 && lc == 2) {
|
||||
response_apdu->DataLen = 0;
|
||||
iso7816_set_response(response_apdu, ISO7816_RESPONSE_OK);
|
||||
} else if(p1 != 0 || p2 != 0) {
|
||||
iso7816_set_response(response_apdu, ISO7816_RESPONSE_WRONG_PARAMETERS_P1_P2);
|
||||
} else {
|
||||
iso7816_set_response(response_apdu, ISO7816_RESPONSE_WRONG_LENGTH);
|
||||
}
|
||||
}
|
||||
|
||||
//instruction 4: sends a command with a body with 'n' bytes, receives a response with 'n' bytes
|
||||
//APDU example: 0x01:0x04:0x00:0x00:0x04:0x01:0x02:0x03:0x04:0x04
|
||||
//receives (0x01, 0x02, 0x03, 0x04) SW1=0x90, SW2=0x00
|
||||
void handle_instruction_04(
|
||||
uint8_t p1,
|
||||
uint8_t p2,
|
||||
uint16_t lc,
|
||||
uint16_t le,
|
||||
const uint8_t* command_apdu_data_buffer,
|
||||
ISO7816_Response_APDU* response_apdu) {
|
||||
if(p1 == 0 && p2 == 0 && lc > 0 && le > 0 && le >= lc) {
|
||||
for(uint16_t i = 0; i < lc; i++) {
|
||||
response_apdu->Data[i] = command_apdu_data_buffer[i];
|
||||
}
|
||||
|
||||
response_apdu->DataLen = lc;
|
||||
|
||||
iso7816_set_response(response_apdu, ISO7816_RESPONSE_OK);
|
||||
} else if(p1 != 0 || p2 != 0) {
|
||||
iso7816_set_response(response_apdu, ISO7816_RESPONSE_WRONG_PARAMETERS_P1_P2);
|
||||
} else {
|
||||
iso7816_set_response(response_apdu, ISO7816_RESPONSE_WRONG_LENGTH);
|
||||
}
|
||||
}
|
||||
|
||||
void iso7816_answer_to_reset(Iso7816Atr* atr) {
|
||||
//minimum valid ATR: https://smartcard-atr.apdu.fr/parse?ATR=3B+00
|
||||
atr->TS = 0x3B;
|
||||
atr->T0 = 0x00;
|
||||
}
|
||||
|
||||
void iso7816_process_command(
|
||||
const ISO7816_Command_APDU* command_apdu,
|
||||
ISO7816_Response_APDU* response_apdu) {
|
||||
//example 1: sends a command with no body, receives a response with no body
|
||||
//sends APDU 0x01:0x01:0x00:0x00
|
||||
//receives SW1=0x90, SW2=0x00
|
||||
|
||||
if(command_apdu->CLA == 0x01) {
|
||||
switch(command_apdu->INS) {
|
||||
case 0x01:
|
||||
handle_instruction_01(response_apdu);
|
||||
break;
|
||||
case 0x02:
|
||||
handle_instruction_02(
|
||||
command_apdu->P1,
|
||||
command_apdu->P2,
|
||||
command_apdu->Lc,
|
||||
command_apdu->Le,
|
||||
response_apdu);
|
||||
break;
|
||||
case 0x03:
|
||||
handle_instruction_03(
|
||||
command_apdu->P1, command_apdu->P2, command_apdu->Lc, response_apdu);
|
||||
break;
|
||||
case 0x04:
|
||||
handle_instruction_04(
|
||||
command_apdu->P1,
|
||||
command_apdu->P2,
|
||||
command_apdu->Lc,
|
||||
command_apdu->Le,
|
||||
command_apdu->Data,
|
||||
response_apdu);
|
||||
break;
|
||||
default:
|
||||
iso7816_set_response(response_apdu, ISO7816_RESPONSE_INSTRUCTION_NOT_SUPPORTED);
|
||||
}
|
||||
} else {
|
||||
iso7816_set_response(response_apdu, ISO7816_RESPONSE_CLASS_NOT_SUPPORTED);
|
||||
}
|
||||
}
|
||||
7
applications/debug/ccid_test/ccid_test_app_commands.h
Normal file
@@ -0,0 +1,7 @@
|
||||
#include "iso7816/iso7816_t0_apdu.h"
|
||||
|
||||
void iso7816_answer_to_reset(Iso7816Atr* atr);
|
||||
|
||||
void iso7816_process_command(
|
||||
const ISO7816_Command_APDU* command_apdu,
|
||||
ISO7816_Response_APDU* response_apdu);
|
||||
68
applications/debug/ccid_test/iso7816/iso7816_handler.c
Normal file
@@ -0,0 +1,68 @@
|
||||
// transforms low level calls such as XFRCallback or ICC Power on to a structured one
|
||||
// an application can register these calls and listen for the callbacks defined in Iso7816Callbacks
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
|
||||
#include "iso7816_t0_apdu.h"
|
||||
#include "iso7816_atr.h"
|
||||
#include "iso7816_handler.h"
|
||||
#include "iso7816_response.h"
|
||||
|
||||
void iso7816_icc_power_on_callback(uint8_t* atr_data, uint32_t* atr_data_len, void* context) {
|
||||
furi_check(context);
|
||||
|
||||
Iso7816Handler* handler = (Iso7816Handler*)context;
|
||||
|
||||
Iso7816Atr iso7816_atr;
|
||||
handler->iso7816_answer_to_reset(&iso7816_atr);
|
||||
|
||||
furi_assert(iso7816_atr.T0 == 0x00);
|
||||
|
||||
uint8_t atr_buffer[2] = {iso7816_atr.TS, iso7816_atr.T0};
|
||||
|
||||
*atr_data_len = 2;
|
||||
|
||||
memcpy(atr_data, atr_buffer, sizeof(uint8_t) * (*atr_data_len));
|
||||
}
|
||||
|
||||
//dataBlock points to the buffer
|
||||
//dataBlockLen tells reader how nany bytes should be read
|
||||
void iso7816_xfr_datablock_callback(
|
||||
const uint8_t* pc_to_reader_datablock,
|
||||
uint32_t pc_to_reader_datablock_len,
|
||||
uint8_t* reader_to_pc_datablock,
|
||||
uint32_t* reader_to_pc_datablock_len,
|
||||
void* context) {
|
||||
furi_check(context);
|
||||
|
||||
Iso7816Handler* handler = (Iso7816Handler*)context;
|
||||
|
||||
ISO7816_Response_APDU* response_apdu = (ISO7816_Response_APDU*)&handler->response_apdu_buffer;
|
||||
|
||||
ISO7816_Command_APDU* command_apdu = (ISO7816_Command_APDU*)&handler->command_apdu_buffer;
|
||||
|
||||
uint8_t result = iso7816_read_command_apdu(
|
||||
command_apdu, pc_to_reader_datablock, pc_to_reader_datablock_len);
|
||||
|
||||
if(result == ISO7816_READ_COMMAND_APDU_OK) {
|
||||
handler->iso7816_process_command(command_apdu, response_apdu);
|
||||
|
||||
furi_assert(response_apdu->DataLen < CCID_SHORT_APDU_SIZE);
|
||||
} else if(result == ISO7816_READ_COMMAND_APDU_ERROR_WRONG_LE) {
|
||||
iso7816_set_response(response_apdu, ISO7816_RESPONSE_WRONG_LE);
|
||||
} else if(result == ISO7816_READ_COMMAND_APDU_ERROR_WRONG_LENGTH) {
|
||||
iso7816_set_response(response_apdu, ISO7816_RESPONSE_WRONG_LENGTH);
|
||||
}
|
||||
|
||||
iso7816_write_response_apdu(response_apdu, reader_to_pc_datablock, reader_to_pc_datablock_len);
|
||||
}
|
||||
|
||||
Iso7816Handler* iso7816_handler_alloc() {
|
||||
Iso7816Handler* handler = malloc(sizeof(Iso7816Handler));
|
||||
handler->ccid_callbacks.icc_power_on_callback = iso7816_icc_power_on_callback;
|
||||
handler->ccid_callbacks.xfr_datablock_callback = iso7816_xfr_datablock_callback;
|
||||
return handler;
|
||||
}
|
||||
18
applications/debug/ccid_test/iso7816/iso7816_handler.h
Normal file
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include "iso7816_atr.h"
|
||||
#include "iso7816_t0_apdu.h"
|
||||
|
||||
typedef struct {
|
||||
CcidCallbacks ccid_callbacks;
|
||||
void (*iso7816_answer_to_reset)(Iso7816Atr* atr);
|
||||
void (*iso7816_process_command)(
|
||||
const ISO7816_Command_APDU* command,
|
||||
ISO7816_Response_APDU* response);
|
||||
|
||||
uint8_t command_apdu_buffer[sizeof(ISO7816_Command_APDU) + CCID_SHORT_APDU_SIZE];
|
||||
uint8_t response_apdu_buffer[sizeof(ISO7816_Response_APDU) + CCID_SHORT_APDU_SIZE];
|
||||
} Iso7816Handler;
|
||||
|
||||
Iso7816Handler* iso7816_handler_alloc();
|
||||
@@ -61,24 +61,25 @@ uint8_t iso7816_read_command_apdu(
|
||||
//data buffer contains the whole APU response (response + trailer (SW1+SW2))
|
||||
void iso7816_write_response_apdu(
|
||||
const ISO7816_Response_APDU* response,
|
||||
uint8_t* readerToPcDataBlock,
|
||||
uint32_t* readerToPcDataBlockLen) {
|
||||
uint8_t* reader_to_pc_datablock,
|
||||
uint32_t* reader_to_pc_datablock_len) {
|
||||
uint32_t responseDataBufferIndex = 0;
|
||||
|
||||
//response body
|
||||
if(response->DataLen > 0) {
|
||||
while(responseDataBufferIndex < response->DataLen) {
|
||||
readerToPcDataBlock[responseDataBufferIndex] = response->Data[responseDataBufferIndex];
|
||||
reader_to_pc_datablock[responseDataBufferIndex] =
|
||||
response->Data[responseDataBufferIndex];
|
||||
responseDataBufferIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
//trailer
|
||||
readerToPcDataBlock[responseDataBufferIndex] = response->SW1;
|
||||
reader_to_pc_datablock[responseDataBufferIndex] = response->SW1;
|
||||
responseDataBufferIndex++;
|
||||
|
||||
readerToPcDataBlock[responseDataBufferIndex] = response->SW2;
|
||||
reader_to_pc_datablock[responseDataBufferIndex] = response->SW2;
|
||||
responseDataBufferIndex++;
|
||||
|
||||
*readerToPcDataBlockLen = responseDataBufferIndex;
|
||||
*reader_to_pc_datablock_len = responseDataBufferIndex;
|
||||
}
|
||||
@@ -31,12 +31,11 @@ typedef struct {
|
||||
uint8_t Data[0];
|
||||
} FURI_PACKED ISO7816_Response_APDU;
|
||||
|
||||
void iso7816_answer_to_reset(Iso7816Atr* atr);
|
||||
uint8_t iso7816_read_command_apdu(
|
||||
ISO7816_Command_APDU* command,
|
||||
const uint8_t* pcToReaderDataBlock,
|
||||
uint32_t pcToReaderDataBlockLen);
|
||||
const uint8_t* pc_to_reader_datablock,
|
||||
uint32_t pc_to_reader_datablock_len);
|
||||
void iso7816_write_response_apdu(
|
||||
const ISO7816_Response_APDU* response,
|
||||
uint8_t* readerToPcDataBlock,
|
||||
uint32_t* readerToPcDataBlockLen);
|
||||
uint8_t* reader_to_pc_datablock,
|
||||
uint32_t* reader_to_pc_datablock_len);
|
||||
@@ -1,65 +0,0 @@
|
||||
// transforms low level calls such as XFRCallback or ICC Power on to a structured one
|
||||
// an application can register these calls and listen for the callbacks defined in Iso7816Callbacks
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
|
||||
#include "iso7816_t0_apdu.h"
|
||||
#include "iso7816_atr.h"
|
||||
#include "iso7816_callbacks.h"
|
||||
#include "iso7816_response.h"
|
||||
|
||||
static Iso7816Callbacks* callbacks = NULL;
|
||||
|
||||
static uint8_t commandApduBuffer[sizeof(ISO7816_Command_APDU) + CCID_SHORT_APDU_SIZE];
|
||||
static uint8_t responseApduBuffer[sizeof(ISO7816_Response_APDU) + CCID_SHORT_APDU_SIZE];
|
||||
|
||||
void iso7816_set_callbacks(Iso7816Callbacks* cb) {
|
||||
callbacks = cb;
|
||||
}
|
||||
|
||||
void iso7816_icc_power_on_callback(uint8_t* atrBuffer, uint32_t* atrlen) {
|
||||
Iso7816Atr atr;
|
||||
callbacks->iso7816_answer_to_reset(&atr);
|
||||
|
||||
furi_assert(atr.T0 == 0x00);
|
||||
|
||||
uint8_t AtrBuffer[2] = {atr.TS, atr.T0};
|
||||
|
||||
*atrlen = 2;
|
||||
|
||||
memcpy(atrBuffer, AtrBuffer, sizeof(uint8_t) * (*atrlen));
|
||||
}
|
||||
|
||||
//dataBlock points to the buffer
|
||||
//dataBlockLen tells reader how nany bytes should be read
|
||||
void iso7816_xfr_datablock_callback(
|
||||
const uint8_t* pcToReaderDataBlock,
|
||||
uint32_t pcToReaderDataBlockLen,
|
||||
uint8_t* readerToPcDataBlock,
|
||||
uint32_t* readerToPcDataBlockLen) {
|
||||
ISO7816_Response_APDU* responseAPDU = (ISO7816_Response_APDU*)&responseApduBuffer;
|
||||
|
||||
if(callbacks != NULL) {
|
||||
ISO7816_Command_APDU* commandAPDU = (ISO7816_Command_APDU*)&commandApduBuffer;
|
||||
|
||||
uint8_t result =
|
||||
iso7816_read_command_apdu(commandAPDU, pcToReaderDataBlock, pcToReaderDataBlockLen);
|
||||
|
||||
if(result == ISO7816_READ_COMMAND_APDU_OK) {
|
||||
callbacks->iso7816_process_command(commandAPDU, responseAPDU);
|
||||
|
||||
furi_assert(responseAPDU->DataLen < CCID_SHORT_APDU_SIZE);
|
||||
} else if(result == ISO7816_READ_COMMAND_APDU_ERROR_WRONG_LE) {
|
||||
iso7816_set_response(responseAPDU, ISO7816_RESPONSE_WRONG_LE);
|
||||
} else if(result == ISO7816_READ_COMMAND_APDU_ERROR_WRONG_LENGTH) {
|
||||
iso7816_set_response(responseAPDU, ISO7816_RESPONSE_WRONG_LENGTH);
|
||||
}
|
||||
} else {
|
||||
iso7816_set_response(responseAPDU, ISO7816_RESPONSE_INTERNAL_EXCEPTION);
|
||||
}
|
||||
|
||||
iso7816_write_response_apdu(responseAPDU, readerToPcDataBlock, readerToPcDataBlockLen);
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include "iso7816_atr.h"
|
||||
#include "iso7816_t0_apdu.h"
|
||||
|
||||
typedef struct {
|
||||
void (*iso7816_answer_to_reset)(Iso7816Atr* atr);
|
||||
void (*iso7816_process_command)(
|
||||
const ISO7816_Command_APDU* command,
|
||||
ISO7816_Response_APDU* response);
|
||||
} Iso7816Callbacks;
|
||||
|
||||
void iso7816_set_callbacks(Iso7816Callbacks* cb);
|
||||
|
||||
void iso7816_icc_power_on_callback(uint8_t* atrBuffer, uint32_t* atrlen);
|
||||
void iso7816_xfr_datablock_callback(
|
||||
const uint8_t* dataBlock,
|
||||
uint32_t dataBlockLen,
|
||||
uint8_t* responseDataBlock,
|
||||
uint32_t* responseDataBlockLen);
|
||||
@@ -66,7 +66,6 @@ CrashTest* crash_test_alloc(void) {
|
||||
|
||||
instance->gui = furi_record_open(RECORD_GUI);
|
||||
instance->view_dispatcher = view_dispatcher_alloc();
|
||||
view_dispatcher_enable_queue(instance->view_dispatcher);
|
||||
view_dispatcher_attach_to_gui(
|
||||
instance->view_dispatcher, instance->gui, ViewDispatcherTypeFullscreen);
|
||||
|
||||
|
||||
@@ -126,7 +126,6 @@ DisplayTest* display_test_alloc(void) {
|
||||
|
||||
instance->gui = furi_record_open(RECORD_GUI);
|
||||
instance->view_dispatcher = view_dispatcher_alloc();
|
||||
view_dispatcher_enable_queue(instance->view_dispatcher);
|
||||
view_dispatcher_attach_to_gui(
|
||||
instance->view_dispatcher, instance->gui, ViewDispatcherTypeFullscreen);
|
||||
|
||||
|
||||
@@ -82,7 +82,8 @@ static void view_port_input_callback(InputEvent* input_event, void* context) {
|
||||
furi_message_queue_put(app->input_queue, input_event, 0);
|
||||
}
|
||||
|
||||
static bool input_queue_callback(FuriMessageQueue* queue, void* context) {
|
||||
static bool input_queue_callback(FuriEventLoopObject* object, void* context) {
|
||||
FuriMessageQueue* queue = object;
|
||||
EventLoopBlinkTestApp* app = context;
|
||||
|
||||
InputEvent event;
|
||||
@@ -144,7 +145,7 @@ int32_t event_loop_blink_test_app(void* arg) {
|
||||
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
|
||||
|
||||
furi_event_loop_tick_set(app.event_loop, 500, event_loop_tick_callback, &app);
|
||||
furi_event_loop_message_queue_subscribe(
|
||||
furi_event_loop_subscribe_message_queue(
|
||||
app.event_loop, app.input_queue, FuriEventLoopEventIn, input_queue_callback, &app);
|
||||
|
||||
furi_event_loop_run(app.event_loop);
|
||||
@@ -154,7 +155,7 @@ int32_t event_loop_blink_test_app(void* arg) {
|
||||
|
||||
furi_record_close(RECORD_GUI);
|
||||
|
||||
furi_event_loop_message_queue_unsubscribe(app.event_loop, app.input_queue);
|
||||
furi_event_loop_unsubscribe(app.event_loop, app.input_queue);
|
||||
furi_message_queue_free(app.input_queue);
|
||||
|
||||
for(size_t i = 0; i < TIMER_COUNT; ++i) {
|
||||
|
||||
@@ -33,8 +33,6 @@ FileBrowserApp* file_browser_app_alloc(char* arg) {
|
||||
app->dialogs = furi_record_open(RECORD_DIALOGS);
|
||||
|
||||
app->view_dispatcher = view_dispatcher_alloc();
|
||||
view_dispatcher_enable_queue(app->view_dispatcher);
|
||||
|
||||
app->scene_manager = scene_manager_alloc(&file_browser_scene_handlers, app);
|
||||
|
||||
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
|
||||
|
||||
|
Before Width: | Height: | Size: 576 B After Width: | Height: | Size: 96 B |
@@ -19,7 +19,7 @@ bool file_browser_scene_start_on_event(void* context, SceneManagerEvent event) {
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
furi_string_set(app->file_path, ANY_PATH("badusb/demo_windows.txt"));
|
||||
furi_string_set(app->file_path, EXT_PATH("badusb/demo_windows.txt"));
|
||||
scene_manager_next_scene(app->scene_manager, FileBrowserSceneBrowser);
|
||||
consumed = true;
|
||||
} else if(event.type == SceneManagerEventTypeTick) {
|
||||
|
||||
8
applications/debug/infrared_test/application.fam
Normal file
@@ -0,0 +1,8 @@
|
||||
App(
|
||||
appid="infrared_test",
|
||||
name="Infrared Test",
|
||||
apptype=FlipperAppType.DEBUG,
|
||||
entry_point="infrared_test_app",
|
||||
fap_category="Debug",
|
||||
targets=["f7"],
|
||||
)
|
||||
61
applications/debug/infrared_test/infrared_test.c
Normal file
@@ -0,0 +1,61 @@
|
||||
#include <furi.h>
|
||||
#include <furi_hal_infrared.h>
|
||||
|
||||
#define TAG "InfraredTest"
|
||||
|
||||
#define CARRIER_FREQ_HZ (38000UL)
|
||||
#define CARRIER_DUTY (0.33f)
|
||||
|
||||
#define BURST_DURATION_US (600UL)
|
||||
#define BURST_COUNT (50UL)
|
||||
|
||||
typedef struct {
|
||||
bool level;
|
||||
uint32_t count;
|
||||
} InfraredTestApp;
|
||||
|
||||
static FuriHalInfraredTxGetDataState
|
||||
infrared_test_app_tx_data_callback(void* context, uint32_t* duration, bool* level) {
|
||||
furi_assert(context);
|
||||
furi_assert(duration);
|
||||
furi_assert(level);
|
||||
|
||||
InfraredTestApp* app = context;
|
||||
|
||||
*duration = BURST_DURATION_US;
|
||||
*level = app->level;
|
||||
|
||||
app->level = !app->level;
|
||||
app->count += 1;
|
||||
|
||||
if(app->count < BURST_COUNT * 2) {
|
||||
return FuriHalInfraredTxGetDataStateOk;
|
||||
} else {
|
||||
return FuriHalInfraredTxGetDataStateLastDone;
|
||||
}
|
||||
}
|
||||
|
||||
int32_t infrared_test_app(void* arg) {
|
||||
UNUSED(arg);
|
||||
|
||||
InfraredTestApp app = {
|
||||
.level = true,
|
||||
};
|
||||
|
||||
FURI_LOG_I(TAG, "Starting test signal on PA7");
|
||||
|
||||
furi_hal_infrared_set_tx_output(FuriHalInfraredTxPinExtPA7);
|
||||
furi_hal_infrared_async_tx_set_data_isr_callback(infrared_test_app_tx_data_callback, &app);
|
||||
furi_hal_infrared_async_tx_start(CARRIER_FREQ_HZ, CARRIER_DUTY);
|
||||
furi_hal_infrared_async_tx_wait_termination();
|
||||
furi_hal_infrared_set_tx_output(FuriHalInfraredTxPinInternal);
|
||||
|
||||
FURI_LOG_I(TAG, "Test signal end");
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"The measured signal should be %luus +-%.1fus",
|
||||
(app.count - 1) * BURST_DURATION_US,
|
||||
(double)1000000.0 / CARRIER_FREQ_HZ);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -17,7 +17,6 @@ static LfRfidDebug* lfrfid_debug_alloc(void) {
|
||||
|
||||
app->view_dispatcher = view_dispatcher_alloc();
|
||||
app->scene_manager = scene_manager_alloc(&lfrfid_debug_scene_handlers, app);
|
||||
view_dispatcher_enable_queue(app->view_dispatcher);
|
||||
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
|
||||
view_dispatcher_set_custom_event_callback(
|
||||
app->view_dispatcher, lfrfid_debug_custom_event_callback);
|
||||
|
||||
@@ -61,7 +61,6 @@ static LocaleTestApp* locale_test_alloc(void) {
|
||||
|
||||
// View dispatcher
|
||||
app->view_dispatcher = view_dispatcher_alloc();
|
||||
view_dispatcher_enable_queue(app->view_dispatcher);
|
||||
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
|
||||
|
||||
// Views
|
||||
|
||||
@@ -99,7 +99,6 @@ static RpcDebugApp* rpc_debug_app_alloc(void) {
|
||||
view_dispatcher_set_tick_event_callback(
|
||||
app->view_dispatcher, rpc_debug_app_tick_event_callback, 100);
|
||||
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
|
||||
view_dispatcher_enable_queue(app->view_dispatcher);
|
||||
|
||||
app->widget = widget_alloc();
|
||||
view_dispatcher_add_view(
|
||||
|
||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 301 B |
|
Before Width: | Height: | Size: 181 B After Width: | Height: | Size: 96 B |
@@ -30,7 +30,6 @@ SubGhzTestApp* subghz_test_app_alloc(void) {
|
||||
// View Dispatcher
|
||||
app->view_dispatcher = view_dispatcher_alloc();
|
||||
app->scene_manager = scene_manager_alloc(&subghz_test_scene_handlers, app);
|
||||
view_dispatcher_enable_queue(app->view_dispatcher);
|
||||
|
||||
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
|
||||
view_dispatcher_set_custom_event_callback(
|
||||
|
||||
@@ -126,7 +126,6 @@ int32_t text_box_view_test_app(void* p) {
|
||||
Gui* gui = furi_record_open(RECORD_GUI);
|
||||
ViewDispatcher* view_dispatcher = view_dispatcher_alloc();
|
||||
view_dispatcher_attach_to_gui(view_dispatcher, gui, ViewDispatcherTypeFullscreen);
|
||||
view_dispatcher_enable_queue(view_dispatcher);
|
||||
|
||||
TextBoxViewTest instance = {
|
||||
.text_box = text_box_alloc(),
|
||||
|
||||
@@ -242,7 +242,6 @@ static UartEchoApp* uart_echo_app_alloc(uint32_t baudrate) {
|
||||
|
||||
// View dispatcher
|
||||
app->view_dispatcher = view_dispatcher_alloc();
|
||||
view_dispatcher_enable_queue(app->view_dispatcher);
|
||||
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
|
||||
|
||||
// Views
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
Filetype: Flipper SubGhz Key File
|
||||
Version: 1
|
||||
Frequency: 433920000
|
||||
Preset: FuriHalSubGhzPresetOok650Async
|
||||
Protocol: Dickert_MAHS
|
||||
Bit: 36
|
||||
Key: 00 00 00 01 55 57 55 15
|
||||
@@ -0,0 +1,7 @@
|
||||
Filetype: Flipper SubGhz RAW File
|
||||
Version: 1
|
||||
Frequency: 433920000
|
||||
Preset: FuriHalSubGhzPresetOok650Async
|
||||
Protocol: RAW
|
||||
RAW_Data: 112254 -62882 64 -8912 798 -844 416 -418 806 -850 396 -45206 440 -428 794 -442 804 -422 822 -810 414 -414 824 -832 412 -416 808 -848 376 -446 792 -846 382 -448 816 -828 410 -416 810 -844 382 -416 834 -818 410 -414 810 -856 408 -810 412 -836 384 -442 808 -814 402 -844 414 -834 378 -436 808 -844 396 -422 798 -844 416 -416 814 -404 812 -440 810 -842 396 -422 798 -840 414 -414 806 -850 398 -45210 450 -420 796 -436 780 -446 802 -848 380 -434 806 -846 400 -422 800 -840 410 -408 836 -812 414 -410 826 -840 378 -440 804 -848 396 -426 812 -810 426 -394 826 -844 414 -810 420 -834 378 -442 808 -832 412 -812 416 -830 410 -406 810 -844 400 -420 832 -810 414 -416 800 -446 798 -440 812 -808 426 -410 800 -836 412 -414 806 -836 412 -45216 450 -420 798 -434 806 -414 802 -846 382 -438 814 -832 410 -410 838 -834 396 -430 810 -842 394 -392 826 -840 414 -414 802 -850 396 -428 812 -842 394 -394 828 -842 414 -810 424 -812 392 -434 812 -844 398 -848 380 -844 408 -416 820 -810 414 -406 816 -836 412 -416 836 -414 816 -398 816 -840 420 -410 802 -844 416 -416 804 -824 410 -45232 446 -400 802 -442 810 -432 804 -842 396 -392 826 -842 410 -410 834 -818 378 -442 804 -854 406 -408 806 -838 408 -428 804 -844 396 -392 826 -840 410 -410 834 -810 414 -832 408 -834 380 -440 802 -826 410 -836 412 -838 396 -424 796 -842 414 -414 804 -848 396 -426 812 -412 814 -414 824 -832 410 -416 806 -848 382 -420 834 -814 422 -45228 416 -422 802 -446 810 -420 790 -846 382 -448 818 -828 408 -416 808 -848 382 -418 830 -816 410 -412 812 -856 410 -382 834 -846 382 -418 832 -818 408 -412 812 -856 408 -814 414 -838 396 -428 810 -808 424 -836 380 -844 404 -416 802 -840 424 -394 826 -840 414 -382 836 -412 822 -436 812 -806 424 -394 826 -844 416 -382 838 -816 402 -45228 438 -430 796 -444 806 -424 822 -810 412 -416 822 -832 412 -416 804 -844 408 -414 824 -812 412 -408 812 -834 410 -414 804 -848 408 -412 802 -840 424 -412 802 -834 412 -842 384 -848 396 -426 814 -808 424 -816 392 -866 382 -414 838 -816 414 -428 792 -846 380 -440 810 -438 812 -412 802 -846 380 -438 826 -840 380 -416 838 -814 404 -45226 450 -404 820 -408 806 -452 792 -848 382 -440 814 -832 410 -416 810 -846 378 -450 792 -846 380 -446 816 -830 410 -386 836 -846 376 -410 828 -846 380 -446 814 -828 410 -814 414 -836 396 -428 810 -842 394 -816 410 -836 406 -430 812 -810 426 -394 826 -838
|
||||
RAW_Data: 414 -414 808 -416 826 -438 814 -816 420 -414 834 -814 418 -418 808 -848 398 -45218 412 -438 824 -412 812 -418 832 -852 378 -446 782 -862 410 -386 838 -848 384 -420 836 -820 418 -414 814 -854 408 -388 838 -814 418 -422 836 -816 394 -434 812 -846 398 -850 380 -848 410 -418 822 -812 416 -850 368 -854 412 -418 810 -850 384 -422 834 -820 416 -414 812 -428 836 -412 804 -848 382 -450 818 -828 412 -418 808 -850 380 -45228 452 -420 798 -434 806 -416 834 -818 384 -440 810 -820 404 -420 834 -814 416 -418 834 -824 386 -442 810 -818 404 -420 834 -814 416 -418 834 -820 410 -414 810 -850 406 -812 414 -816 404 -420 818 -838 386 -848 394 -828 414 -414 838 -814 406 -420 820 -842 384 -446 794 -438 810 -412 802 -848 394 -432 812 -842 394 -392 830 -842 414 -105578 64 -1760 130 -196 130 -832 160 -128 62 -1278 194 -1316 230 -96 362 -64 64 -398
|
||||
@@ -19,25 +19,24 @@ typedef struct {
|
||||
uint32_t consumer_counter;
|
||||
} TestFuriData;
|
||||
|
||||
bool test_furi_event_loop_producer_mq_callback(FuriMessageQueue* queue, void* context) {
|
||||
bool test_furi_event_loop_producer_mq_callback(FuriEventLoopObject* object, void* context) {
|
||||
furi_check(context);
|
||||
|
||||
TestFuriData* data = context;
|
||||
furi_check(data->mq == queue, "Invalid queue");
|
||||
furi_check(data->mq == object, "Invalid queue");
|
||||
|
||||
FURI_LOG_I(
|
||||
TAG, "producer_mq_callback: %lu %lu", data->producer_counter, data->consumer_counter);
|
||||
|
||||
// Remove and add should not cause crash
|
||||
// if(data->producer_counter == EVENT_LOOP_EVENT_COUNT/2) {
|
||||
// furi_event_loop_message_queue_remove(data->producer_event_loop, data->mq);
|
||||
// furi_event_loop_message_queue_add(
|
||||
// data->producer_event_loop,
|
||||
// data->mq,
|
||||
// FuriEventLoopEventOut,
|
||||
// test_furi_event_loop_producer_mq_callback,
|
||||
// data);
|
||||
// }
|
||||
if(data->producer_counter == EVENT_LOOP_EVENT_COUNT / 2) {
|
||||
furi_event_loop_unsubscribe(data->producer_event_loop, data->mq);
|
||||
furi_event_loop_subscribe_message_queue(
|
||||
data->producer_event_loop,
|
||||
data->mq,
|
||||
FuriEventLoopEventOut,
|
||||
test_furi_event_loop_producer_mq_callback,
|
||||
data);
|
||||
}
|
||||
|
||||
if(data->producer_counter == EVENT_LOOP_EVENT_COUNT) {
|
||||
furi_event_loop_stop(data->producer_event_loop);
|
||||
@@ -61,7 +60,7 @@ int32_t test_furi_event_loop_producer(void* p) {
|
||||
FURI_LOG_I(TAG, "producer start 1st run");
|
||||
|
||||
data->producer_event_loop = furi_event_loop_alloc();
|
||||
furi_event_loop_message_queue_subscribe(
|
||||
furi_event_loop_subscribe_message_queue(
|
||||
data->producer_event_loop,
|
||||
data->mq,
|
||||
FuriEventLoopEventOut,
|
||||
@@ -73,7 +72,7 @@ int32_t test_furi_event_loop_producer(void* p) {
|
||||
// 2 EventLoop index, 0xFFFFFFFF - all possible flags, emulate uncleared flags
|
||||
xTaskNotifyIndexed(xTaskGetCurrentTaskHandle(), 2, 0xFFFFFFFF, eSetBits);
|
||||
|
||||
furi_event_loop_message_queue_unsubscribe(data->producer_event_loop, data->mq);
|
||||
furi_event_loop_unsubscribe(data->producer_event_loop, data->mq);
|
||||
furi_event_loop_free(data->producer_event_loop);
|
||||
|
||||
FURI_LOG_I(TAG, "producer start 2nd run");
|
||||
@@ -81,7 +80,7 @@ int32_t test_furi_event_loop_producer(void* p) {
|
||||
data->producer_counter = 0;
|
||||
data->producer_event_loop = furi_event_loop_alloc();
|
||||
|
||||
furi_event_loop_message_queue_subscribe(
|
||||
furi_event_loop_subscribe_message_queue(
|
||||
data->producer_event_loop,
|
||||
data->mq,
|
||||
FuriEventLoopEventOut,
|
||||
@@ -90,7 +89,7 @@ int32_t test_furi_event_loop_producer(void* p) {
|
||||
|
||||
furi_event_loop_run(data->producer_event_loop);
|
||||
|
||||
furi_event_loop_message_queue_unsubscribe(data->producer_event_loop, data->mq);
|
||||
furi_event_loop_unsubscribe(data->producer_event_loop, data->mq);
|
||||
furi_event_loop_free(data->producer_event_loop);
|
||||
|
||||
FURI_LOG_I(TAG, "producer end");
|
||||
@@ -98,11 +97,11 @@ int32_t test_furi_event_loop_producer(void* p) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool test_furi_event_loop_consumer_mq_callback(FuriMessageQueue* queue, void* context) {
|
||||
bool test_furi_event_loop_consumer_mq_callback(FuriEventLoopObject* object, void* context) {
|
||||
furi_check(context);
|
||||
|
||||
TestFuriData* data = context;
|
||||
furi_check(data->mq == queue);
|
||||
furi_check(data->mq == object);
|
||||
|
||||
furi_delay_us(furi_hal_random_get() % 1000);
|
||||
furi_check(furi_message_queue_get(data->mq, &data->consumer_counter, 0) == FuriStatusOk);
|
||||
@@ -110,16 +109,15 @@ bool test_furi_event_loop_consumer_mq_callback(FuriMessageQueue* queue, void* co
|
||||
FURI_LOG_I(
|
||||
TAG, "consumer_mq_callback: %lu %lu", data->producer_counter, data->consumer_counter);
|
||||
|
||||
// Remove and add should not cause crash
|
||||
// if(data->producer_counter == EVENT_LOOP_EVENT_COUNT/2) {
|
||||
// furi_event_loop_message_queue_remove(data->consumer_event_loop, data->mq);
|
||||
// furi_event_loop_message_queue_add(
|
||||
// data->consumer_event_loop,
|
||||
// data->mq,
|
||||
// FuriEventLoopEventIn,
|
||||
// test_furi_event_loop_producer_mq_callback,
|
||||
// data);
|
||||
// }
|
||||
if(data->consumer_counter == EVENT_LOOP_EVENT_COUNT / 2) {
|
||||
furi_event_loop_unsubscribe(data->consumer_event_loop, data->mq);
|
||||
furi_event_loop_subscribe_message_queue(
|
||||
data->consumer_event_loop,
|
||||
data->mq,
|
||||
FuriEventLoopEventIn,
|
||||
test_furi_event_loop_consumer_mq_callback,
|
||||
data);
|
||||
}
|
||||
|
||||
if(data->consumer_counter == EVENT_LOOP_EVENT_COUNT) {
|
||||
furi_event_loop_stop(data->consumer_event_loop);
|
||||
@@ -137,7 +135,7 @@ int32_t test_furi_event_loop_consumer(void* p) {
|
||||
FURI_LOG_I(TAG, "consumer start 1st run");
|
||||
|
||||
data->consumer_event_loop = furi_event_loop_alloc();
|
||||
furi_event_loop_message_queue_subscribe(
|
||||
furi_event_loop_subscribe_message_queue(
|
||||
data->consumer_event_loop,
|
||||
data->mq,
|
||||
FuriEventLoopEventIn,
|
||||
@@ -149,14 +147,14 @@ int32_t test_furi_event_loop_consumer(void* p) {
|
||||
// 2 EventLoop index, 0xFFFFFFFF - all possible flags, emulate uncleared flags
|
||||
xTaskNotifyIndexed(xTaskGetCurrentTaskHandle(), 2, 0xFFFFFFFF, eSetBits);
|
||||
|
||||
furi_event_loop_message_queue_unsubscribe(data->consumer_event_loop, data->mq);
|
||||
furi_event_loop_unsubscribe(data->consumer_event_loop, data->mq);
|
||||
furi_event_loop_free(data->consumer_event_loop);
|
||||
|
||||
FURI_LOG_I(TAG, "consumer start 2nd run");
|
||||
|
||||
data->consumer_counter = 0;
|
||||
data->consumer_event_loop = furi_event_loop_alloc();
|
||||
furi_event_loop_message_queue_subscribe(
|
||||
furi_event_loop_subscribe_message_queue(
|
||||
data->consumer_event_loop,
|
||||
data->mq,
|
||||
FuriEventLoopEventIn,
|
||||
@@ -165,7 +163,7 @@ int32_t test_furi_event_loop_consumer(void* p) {
|
||||
|
||||
furi_event_loop_run(data->consumer_event_loop);
|
||||
|
||||
furi_event_loop_message_queue_unsubscribe(data->consumer_event_loop, data->mq);
|
||||
furi_event_loop_unsubscribe(data->consumer_event_loop, data->mq);
|
||||
furi_event_loop_free(data->consumer_event_loop);
|
||||
|
||||
FURI_LOG_I(TAG, "consumer end");
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <loader/loader.h>
|
||||
#include <storage/filesystem_api_defines.h>
|
||||
|
||||
#include <lib/toolbox/api_lock.h>
|
||||
#include <lib/toolbox/md5_calc.h>
|
||||
#include <lib/toolbox/path.h>
|
||||
|
||||
@@ -35,8 +36,8 @@ static uint32_t command_id = 0;
|
||||
typedef struct {
|
||||
RpcSession* session;
|
||||
FuriStreamBuffer* output_stream;
|
||||
FuriSemaphore* close_session_semaphore;
|
||||
FuriSemaphore* terminate_semaphore;
|
||||
FuriApiLock session_close_lock;
|
||||
FuriApiLock session_terminate_lock;
|
||||
uint32_t timeout;
|
||||
} RpcSessionContext;
|
||||
|
||||
@@ -92,8 +93,8 @@ static void test_rpc_setup(void) {
|
||||
|
||||
rpc_session[0].output_stream = furi_stream_buffer_alloc(4096, 1);
|
||||
rpc_session_set_send_bytes_callback(rpc_session[0].session, output_bytes_callback);
|
||||
rpc_session[0].close_session_semaphore = furi_semaphore_alloc(1, 0);
|
||||
rpc_session[0].terminate_semaphore = furi_semaphore_alloc(1, 0);
|
||||
rpc_session[0].session_close_lock = api_lock_alloc_locked();
|
||||
rpc_session[0].session_terminate_lock = api_lock_alloc_locked();
|
||||
rpc_session_set_close_callback(rpc_session[0].session, test_rpc_session_close_callback);
|
||||
rpc_session_set_terminated_callback(
|
||||
rpc_session[0].session, test_rpc_session_terminated_callback);
|
||||
@@ -112,8 +113,8 @@ static void test_rpc_setup_second_session(void) {
|
||||
|
||||
rpc_session[1].output_stream = furi_stream_buffer_alloc(1000, 1);
|
||||
rpc_session_set_send_bytes_callback(rpc_session[1].session, output_bytes_callback);
|
||||
rpc_session[1].close_session_semaphore = furi_semaphore_alloc(1, 0);
|
||||
rpc_session[1].terminate_semaphore = furi_semaphore_alloc(1, 0);
|
||||
rpc_session[1].session_close_lock = api_lock_alloc_locked();
|
||||
rpc_session[1].session_terminate_lock = api_lock_alloc_locked();
|
||||
rpc_session_set_close_callback(rpc_session[1].session, test_rpc_session_close_callback);
|
||||
rpc_session_set_terminated_callback(
|
||||
rpc_session[1].session, test_rpc_session_terminated_callback);
|
||||
@@ -121,36 +122,32 @@ static void test_rpc_setup_second_session(void) {
|
||||
}
|
||||
|
||||
static void test_rpc_teardown(void) {
|
||||
furi_check(rpc_session[0].close_session_semaphore);
|
||||
furi_semaphore_acquire(rpc_session[0].terminate_semaphore, 0);
|
||||
furi_check(rpc_session[0].session_close_lock);
|
||||
api_lock_relock(rpc_session[0].session_terminate_lock);
|
||||
rpc_session_close(rpc_session[0].session);
|
||||
furi_check(
|
||||
furi_semaphore_acquire(rpc_session[0].terminate_semaphore, FuriWaitForever) ==
|
||||
FuriStatusOk);
|
||||
api_lock_wait_unlock(rpc_session[0].session_terminate_lock);
|
||||
furi_record_close(RECORD_RPC);
|
||||
furi_stream_buffer_free(rpc_session[0].output_stream);
|
||||
furi_semaphore_free(rpc_session[0].close_session_semaphore);
|
||||
furi_semaphore_free(rpc_session[0].terminate_semaphore);
|
||||
api_lock_free(rpc_session[0].session_close_lock);
|
||||
api_lock_free(rpc_session[0].session_terminate_lock);
|
||||
++command_id;
|
||||
rpc_session[0].output_stream = NULL;
|
||||
rpc_session[0].close_session_semaphore = NULL;
|
||||
rpc_session[0].session_close_lock = NULL;
|
||||
rpc = NULL;
|
||||
rpc_session[0].session = NULL;
|
||||
}
|
||||
|
||||
static void test_rpc_teardown_second_session(void) {
|
||||
furi_check(rpc_session[1].close_session_semaphore);
|
||||
furi_semaphore_acquire(rpc_session[1].terminate_semaphore, 0);
|
||||
furi_check(rpc_session[1].session_close_lock);
|
||||
api_lock_relock(rpc_session[1].session_terminate_lock);
|
||||
rpc_session_close(rpc_session[1].session);
|
||||
furi_check(
|
||||
furi_semaphore_acquire(rpc_session[1].terminate_semaphore, FuriWaitForever) ==
|
||||
FuriStatusOk);
|
||||
api_lock_wait_unlock(rpc_session[1].session_terminate_lock);
|
||||
furi_stream_buffer_free(rpc_session[1].output_stream);
|
||||
furi_semaphore_free(rpc_session[1].close_session_semaphore);
|
||||
furi_semaphore_free(rpc_session[1].terminate_semaphore);
|
||||
api_lock_free(rpc_session[1].session_close_lock);
|
||||
api_lock_free(rpc_session[1].session_terminate_lock);
|
||||
++command_id;
|
||||
rpc_session[1].output_stream = NULL;
|
||||
rpc_session[1].close_session_semaphore = NULL;
|
||||
rpc_session[1].session_close_lock = NULL;
|
||||
rpc_session[1].session = NULL;
|
||||
}
|
||||
|
||||
@@ -204,14 +201,14 @@ static void test_rpc_session_close_callback(void* context) {
|
||||
furi_check(context);
|
||||
RpcSessionContext* callbacks_context = context;
|
||||
|
||||
furi_check(furi_semaphore_release(callbacks_context->close_session_semaphore) == FuriStatusOk);
|
||||
api_lock_unlock(callbacks_context->session_close_lock);
|
||||
}
|
||||
|
||||
static void test_rpc_session_terminated_callback(void* context) {
|
||||
furi_check(context);
|
||||
RpcSessionContext* callbacks_context = context;
|
||||
|
||||
furi_check(furi_semaphore_release(callbacks_context->terminate_semaphore) == FuriStatusOk);
|
||||
api_lock_unlock(callbacks_context->session_terminate_lock);
|
||||
}
|
||||
|
||||
static void test_rpc_print_message_list(MsgList_t msg_list) {
|
||||
@@ -1645,7 +1642,7 @@ static void test_rpc_feed_rubbish_run(
|
||||
|
||||
test_rpc_add_empty_to_list(expected, PB_CommandStatus_ERROR_DECODE, 0);
|
||||
|
||||
furi_check(furi_semaphore_acquire(rpc_session[0].close_session_semaphore, 0) != FuriStatusOk);
|
||||
furi_check(api_lock_is_locked(rpc_session[0].session_close_lock));
|
||||
test_rpc_encode_and_feed(input_before, 0);
|
||||
test_send_rubbish(rpc_session[0].session, pattern, pattern_size, size);
|
||||
test_rpc_encode_and_feed(input_after, 0);
|
||||
|
||||
@@ -663,6 +663,13 @@ MU_TEST(subghz_decoder_mastercode_test) {
|
||||
"Test decoder " SUBGHZ_PROTOCOL_MASTERCODE_NAME " error\r\n");
|
||||
}
|
||||
|
||||
MU_TEST(subghz_decoder_dickert_test) {
|
||||
mu_assert(
|
||||
subghz_decoder_test(
|
||||
EXT_PATH("unit_tests/subghz/dickert_raw.sub"), SUBGHZ_PROTOCOL_DICKERT_MAHS_NAME),
|
||||
"Test decoder " SUBGHZ_PROTOCOL_DICKERT_MAHS_NAME " error\r\n");
|
||||
}
|
||||
|
||||
//test encoders
|
||||
MU_TEST(subghz_encoder_princeton_test) {
|
||||
mu_assert(
|
||||
@@ -820,6 +827,12 @@ MU_TEST(subghz_encoder_mastercode_test) {
|
||||
"Test encoder " SUBGHZ_PROTOCOL_MASTERCODE_NAME " error\r\n");
|
||||
}
|
||||
|
||||
MU_TEST(subghz_encoder_dickert_test) {
|
||||
mu_assert(
|
||||
subghz_encoder_test(EXT_PATH("unit_tests/subghz/dickert_mahs.sub")),
|
||||
"Test encoder " SUBGHZ_PROTOCOL_DICKERT_MAHS_NAME " error\r\n");
|
||||
}
|
||||
|
||||
MU_TEST(subghz_random_test) {
|
||||
mu_assert(subghz_decode_random_test(TEST_RANDOM_DIR_NAME), "Random test error\r\n");
|
||||
}
|
||||
@@ -871,6 +884,7 @@ MU_TEST_SUITE(subghz) {
|
||||
MU_RUN_TEST(subghz_decoder_nice_one_test);
|
||||
MU_RUN_TEST(subghz_decoder_kinggates_stylo4k_test);
|
||||
MU_RUN_TEST(subghz_decoder_mastercode_test);
|
||||
MU_RUN_TEST(subghz_decoder_dickert_test);
|
||||
|
||||
MU_RUN_TEST(subghz_encoder_princeton_test);
|
||||
MU_RUN_TEST(subghz_encoder_came_test);
|
||||
@@ -898,6 +912,7 @@ MU_TEST_SUITE(subghz) {
|
||||
MU_RUN_TEST(subghz_encoder_holtek_ht12x_test);
|
||||
MU_RUN_TEST(subghz_encoder_dooya_test);
|
||||
MU_RUN_TEST(subghz_encoder_mastercode_test);
|
||||
MU_RUN_TEST(subghz_encoder_dickert_test);
|
||||
|
||||
MU_RUN_TEST(subghz_random_test);
|
||||
subghz_test_deinit();
|
||||
|
||||
@@ -36,14 +36,10 @@ static constexpr auto unit_tests_api_table = sort(create_array_t<sym_entry>(
|
||||
API_METHOD(furi_event_loop_alloc, FuriEventLoop*, (void)),
|
||||
API_METHOD(furi_event_loop_free, void, (FuriEventLoop*)),
|
||||
API_METHOD(
|
||||
furi_event_loop_message_queue_subscribe,
|
||||
furi_event_loop_subscribe_message_queue,
|
||||
void,
|
||||
(FuriEventLoop*,
|
||||
FuriMessageQueue*,
|
||||
FuriEventLoopEvent,
|
||||
FuriEventLoopMessageQueueCallback,
|
||||
void*)),
|
||||
API_METHOD(furi_event_loop_message_queue_unsubscribe, void, (FuriEventLoop*, FuriMessageQueue*)),
|
||||
(FuriEventLoop*, FuriMessageQueue*, FuriEventLoopEvent, FuriEventLoopEventCallback, void*)),
|
||||
API_METHOD(furi_event_loop_unsubscribe, void, (FuriEventLoop*, FuriEventLoopObject*)),
|
||||
API_METHOD(furi_event_loop_run, void, (FuriEventLoop*)),
|
||||
API_METHOD(furi_event_loop_stop, void, (FuriEventLoop*)),
|
||||
API_VARIABLE(PB_Main_msg, PB_Main_msg_t)));
|
||||
|
||||
@@ -63,7 +63,6 @@ UsbTestApp* usb_test_app_alloc(void) {
|
||||
|
||||
// View dispatcher
|
||||
app->view_dispatcher = view_dispatcher_alloc();
|
||||
view_dispatcher_enable_queue(app->view_dispatcher);
|
||||
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
|
||||
|
||||
// Views
|
||||
|
||||
@@ -75,7 +75,6 @@ static BleBeaconApp* ble_beacon_app_alloc(void) {
|
||||
view_dispatcher_set_tick_event_callback(
|
||||
app->view_dispatcher, ble_beacon_app_tick_event_callback, 100);
|
||||
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
|
||||
view_dispatcher_enable_queue(app->view_dispatcher);
|
||||
|
||||
app->submenu = submenu_alloc();
|
||||
view_dispatcher_add_view(
|
||||
|
||||
|
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 96 B |
|
Before Width: | Height: | Size: 8.7 KiB After Width: | Height: | Size: 258 B |
36
applications/examples/example_event_loop/application.fam
Normal file
@@ -0,0 +1,36 @@
|
||||
App(
|
||||
appid="example_event_loop_timer",
|
||||
name="Example: Event Loop Timer",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
sources=["example_event_loop_timer.c"],
|
||||
entry_point="example_event_loop_timer_app",
|
||||
fap_category="Examples",
|
||||
)
|
||||
|
||||
App(
|
||||
appid="example_event_loop_mutex",
|
||||
name="Example: Event Loop Mutex",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
sources=["example_event_loop_mutex.c"],
|
||||
entry_point="example_event_loop_mutex_app",
|
||||
fap_category="Examples",
|
||||
)
|
||||
|
||||
App(
|
||||
appid="example_event_loop_stream_buffer",
|
||||
name="Example: Event Loop Stream Buffer",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
sources=["example_event_loop_stream_buffer.c"],
|
||||
entry_point="example_event_loop_stream_buffer_app",
|
||||
fap_category="Examples",
|
||||
)
|
||||
|
||||
App(
|
||||
appid="example_event_loop_multi",
|
||||
name="Example: Event Loop Multi",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
sources=["example_event_loop_multi.c"],
|
||||
entry_point="example_event_loop_multi_app",
|
||||
requires=["gui"],
|
||||
fap_category="Examples",
|
||||
)
|
||||
@@ -0,0 +1,342 @@
|
||||
/**
|
||||
* @file example_event_loop_multi.c
|
||||
* @brief Example application that demonstrates multiple primitives used with two FuriEventLoop instances.
|
||||
*
|
||||
* This application simulates a complex use case of having two concurrent event loops (each one executing in
|
||||
* its own thread) using a stream buffer for communication and additional timers and message passing to handle
|
||||
* the keypad input. Additionally, it shows how to use thread signals to stop an event loop in another thread.
|
||||
* The GUI functionality is there only for the purpose of exclusive access to the input events.
|
||||
*
|
||||
* The application's functionality consists of the following:
|
||||
* - Print keypad key names and types when pressed,
|
||||
* - If the Back key is long-pressed, a countdown starts upon completion of which the app exits,
|
||||
* - The countdown can be cancelled by long-pressing the Ok button, it also resets the counter,
|
||||
* - Blocks of random data are periodically generated in a separate thread,
|
||||
* - When ready, the main application thread gets notified and prints the data.
|
||||
*/
|
||||
|
||||
#include <furi.h>
|
||||
#include <gui/gui.h>
|
||||
#include <gui/view_port.h>
|
||||
|
||||
#include <furi_hal_random.h>
|
||||
|
||||
#define TAG "ExampleEventLoopMulti"
|
||||
|
||||
#define COUNTDOWN_START_VALUE (5UL)
|
||||
#define COUNTDOWN_INTERVAL_MS (1000UL)
|
||||
#define WORKER_DATA_INTERVAL_MS (1500UL)
|
||||
|
||||
#define INPUT_QUEUE_SIZE (8)
|
||||
#define STREAM_BUFFER_SIZE (16)
|
||||
|
||||
typedef struct {
|
||||
FuriEventLoop* event_loop;
|
||||
FuriEventLoopTimer* timer;
|
||||
FuriStreamBuffer* stream_buffer;
|
||||
} EventLoopMultiAppWorker;
|
||||
|
||||
typedef struct {
|
||||
Gui* gui;
|
||||
ViewPort* view_port;
|
||||
FuriThread* worker_thread;
|
||||
FuriEventLoop* event_loop;
|
||||
FuriMessageQueue* input_queue;
|
||||
FuriEventLoopTimer* exit_timer;
|
||||
FuriStreamBuffer* stream_buffer;
|
||||
uint32_t exit_countdown_value;
|
||||
} EventLoopMultiApp;
|
||||
|
||||
/*
|
||||
* Worker functions
|
||||
*/
|
||||
|
||||
// This function is executed each time the data is taken out of the stream buffer. It is used to restart the worker timer.
|
||||
static bool
|
||||
event_loop_multi_app_stream_buffer_worker_callback(FuriEventLoopObject* object, void* context) {
|
||||
furi_assert(context);
|
||||
EventLoopMultiAppWorker* worker = context;
|
||||
|
||||
furi_assert(object == worker->stream_buffer);
|
||||
|
||||
FURI_LOG_I(TAG, "Data was removed from buffer");
|
||||
// Restart the timer to generate another block of random data.
|
||||
furi_event_loop_timer_start(worker->timer, WORKER_DATA_INTERVAL_MS);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// This function is executed when the worker timer expires. The timer will NOT restart automatically
|
||||
// since it is of one-shot type.
|
||||
static void event_loop_multi_app_worker_timer_callback(void* context) {
|
||||
furi_assert(context);
|
||||
EventLoopMultiAppWorker* worker = context;
|
||||
|
||||
// Generate a block of random data.
|
||||
uint8_t data[STREAM_BUFFER_SIZE];
|
||||
furi_hal_random_fill_buf(data, sizeof(data));
|
||||
// Put the generated data in the stream buffer.
|
||||
// IMPORTANT: No waiting in the event handlers!
|
||||
furi_check(
|
||||
furi_stream_buffer_send(worker->stream_buffer, &data, sizeof(data), 0) == sizeof(data));
|
||||
}
|
||||
|
||||
static EventLoopMultiAppWorker*
|
||||
event_loop_multi_app_worker_alloc(FuriStreamBuffer* stream_buffer) {
|
||||
EventLoopMultiAppWorker* worker = malloc(sizeof(EventLoopMultiAppWorker));
|
||||
// Create the worker event loop.
|
||||
worker->event_loop = furi_event_loop_alloc();
|
||||
// Create the timer governing the data generation.
|
||||
// It is of one-shot type, i.e. it will not restart automatically upon expiration.
|
||||
worker->timer = furi_event_loop_timer_alloc(
|
||||
worker->event_loop,
|
||||
event_loop_multi_app_worker_timer_callback,
|
||||
FuriEventLoopTimerTypeOnce,
|
||||
worker);
|
||||
|
||||
// Using the same stream buffer as the main thread (it was already created beforehand).
|
||||
worker->stream_buffer = stream_buffer;
|
||||
// Notify the worker event loop about data being taken out of the stream buffer.
|
||||
furi_event_loop_subscribe_stream_buffer(
|
||||
worker->event_loop,
|
||||
worker->stream_buffer,
|
||||
FuriEventLoopEventOut | FuriEventLoopEventFlagEdge,
|
||||
event_loop_multi_app_stream_buffer_worker_callback,
|
||||
worker);
|
||||
|
||||
return worker;
|
||||
}
|
||||
|
||||
static void event_loop_multi_app_worker_free(EventLoopMultiAppWorker* worker) {
|
||||
// IMPORTANT: The user code MUST unsubscribe from all events before deleting the event loop.
|
||||
// Failure to do so will result in a crash.
|
||||
furi_event_loop_unsubscribe(worker->event_loop, worker->stream_buffer);
|
||||
// IMPORTANT: All timers MUST be deleted before deleting the associated event loop.
|
||||
// Failure to do so will result in a crash.
|
||||
furi_event_loop_timer_free(worker->timer);
|
||||
// Now it is okay to delete the event loop.
|
||||
furi_event_loop_free(worker->event_loop);
|
||||
|
||||
free(worker);
|
||||
}
|
||||
|
||||
static void event_loop_multi_app_worker_run(EventLoopMultiAppWorker* worker) {
|
||||
furi_event_loop_timer_start(worker->timer, WORKER_DATA_INTERVAL_MS);
|
||||
furi_event_loop_run(worker->event_loop);
|
||||
}
|
||||
|
||||
// This function is the worker thread body and (obviously) is executed in the worker thread.
|
||||
static int32_t event_loop_multi_app_worker_thread(void* context) {
|
||||
furi_assert(context);
|
||||
EventLoopMultiApp* app = context;
|
||||
|
||||
// Because an event loop is used, it MUST be created in the thread it will be run in.
|
||||
// Therefore, the worker creation and deletion is handled in the worker thread.
|
||||
EventLoopMultiAppWorker* worker = event_loop_multi_app_worker_alloc(app->stream_buffer);
|
||||
event_loop_multi_app_worker_run(worker);
|
||||
event_loop_multi_app_worker_free(worker);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Main application functions
|
||||
*/
|
||||
|
||||
// This function is executed in the GUI context each time an input event occurs (e.g. the user pressed a key)
|
||||
static void event_loop_multi_app_input_callback(InputEvent* event, void* context) {
|
||||
furi_assert(context);
|
||||
EventLoopMultiApp* app = context;
|
||||
// Pass the event to the the application's input queue
|
||||
furi_check(furi_message_queue_put(app->input_queue, event, FuriWaitForever) == FuriStatusOk);
|
||||
}
|
||||
|
||||
// This function is executed each time new data is available in the stream buffer.
|
||||
static bool
|
||||
event_loop_multi_app_stream_buffer_callback(FuriEventLoopObject* object, void* context) {
|
||||
furi_assert(context);
|
||||
EventLoopMultiApp* app = context;
|
||||
|
||||
furi_assert(object == app->stream_buffer);
|
||||
// Get the data from the stream buffer
|
||||
uint8_t data[STREAM_BUFFER_SIZE];
|
||||
// IMPORTANT: No waiting in the event handlers!
|
||||
furi_check(
|
||||
furi_stream_buffer_receive(app->stream_buffer, &data, sizeof(data), 0) == sizeof(data));
|
||||
|
||||
// Format the data for printing and print it to the debug output.
|
||||
FuriString* tmp_str = furi_string_alloc();
|
||||
for(uint32_t i = 0; i < sizeof(data); ++i) {
|
||||
furi_string_cat_printf(tmp_str, "%02X ", data[i]);
|
||||
}
|
||||
|
||||
FURI_LOG_I(TAG, "Received data: %s", furi_string_get_cstr(tmp_str));
|
||||
furi_string_free(tmp_str);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// This function is executed each time a new message is inserted in the input queue.
|
||||
static bool event_loop_multi_app_input_queue_callback(FuriEventLoopObject* object, void* context) {
|
||||
furi_assert(context);
|
||||
EventLoopMultiApp* app = context;
|
||||
|
||||
furi_assert(object == app->input_queue);
|
||||
|
||||
InputEvent event;
|
||||
// IMPORTANT: No waiting in the event handlers!
|
||||
furi_check(furi_message_queue_get(app->input_queue, &event, 0) == FuriStatusOk);
|
||||
|
||||
if(event.type == InputTypeLong) {
|
||||
// The user has long-pressed the Back key, try starting the countdown.
|
||||
if(event.key == InputKeyBack) {
|
||||
if(!furi_event_loop_timer_is_running(app->exit_timer)) {
|
||||
// Actually start the countdown
|
||||
FURI_LOG_I(TAG, "Starting exit countdown!");
|
||||
furi_event_loop_timer_start(app->exit_timer, COUNTDOWN_INTERVAL_MS);
|
||||
|
||||
} else {
|
||||
// The countdown is already in progress, print a warning message
|
||||
FURI_LOG_W(TAG, "Countdown has already been started");
|
||||
}
|
||||
|
||||
// The user has long-pressed the Ok key, try stopping the countdown.
|
||||
} else if(event.key == InputKeyOk) {
|
||||
if(furi_event_loop_timer_is_running(app->exit_timer)) {
|
||||
// Actually cancel the countdown
|
||||
FURI_LOG_I(TAG, "Exit countdown cancelled!");
|
||||
app->exit_countdown_value = COUNTDOWN_START_VALUE;
|
||||
furi_event_loop_timer_stop(app->exit_timer);
|
||||
|
||||
} else {
|
||||
// The countdown is not running, print a warning message
|
||||
FURI_LOG_W(TAG, "Countdown has not been started yet");
|
||||
}
|
||||
|
||||
} else {
|
||||
// Not a Back or Ok key, just print its name.
|
||||
FURI_LOG_I(TAG, "Long press: %s", input_get_key_name(event.key));
|
||||
}
|
||||
|
||||
} else if(event.type == InputTypeShort) {
|
||||
// Not a long press, just print the key's name.
|
||||
FURI_LOG_I(TAG, "Short press: %s", input_get_key_name(event.key));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// This function is executed each time the countdown timer expires.
|
||||
static void event_loop_multi_app_exit_timer_callback(void* context) {
|
||||
furi_assert(context);
|
||||
EventLoopMultiApp* app = context;
|
||||
|
||||
FURI_LOG_I(TAG, "Exiting in %lu ...", app->exit_countdown_value);
|
||||
|
||||
// If the coundown value has reached 0, exit the application
|
||||
if(app->exit_countdown_value == 0) {
|
||||
FURI_LOG_I(TAG, "Exiting NOW!");
|
||||
|
||||
// Send a signal to the worker thread to exit.
|
||||
// A signal handler that handles FuriSignalExit is already set by default.
|
||||
furi_thread_signal(app->worker_thread, FuriSignalExit, NULL);
|
||||
// Request the application event loop to stop.
|
||||
furi_event_loop_stop(app->event_loop);
|
||||
|
||||
// Otherwise just decrement it and wait for the next time the timer expires.
|
||||
} else {
|
||||
app->exit_countdown_value -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
static EventLoopMultiApp* event_loop_multi_app_alloc(void) {
|
||||
EventLoopMultiApp* app = malloc(sizeof(EventLoopMultiApp));
|
||||
// Create event loop instances.
|
||||
app->event_loop = furi_event_loop_alloc();
|
||||
|
||||
// Create a worker thread instance. The worker event loop will execute inside it.
|
||||
app->worker_thread = furi_thread_alloc_ex(
|
||||
"EventLoopMultiWorker", 1024, event_loop_multi_app_worker_thread, app);
|
||||
// Create a message queue to receive the input events.
|
||||
app->input_queue = furi_message_queue_alloc(INPUT_QUEUE_SIZE, sizeof(InputEvent));
|
||||
// Create a stream buffer to receive the generated data.
|
||||
app->stream_buffer = furi_stream_buffer_alloc(STREAM_BUFFER_SIZE, STREAM_BUFFER_SIZE);
|
||||
// Create a timer to run the countdown.
|
||||
app->exit_timer = furi_event_loop_timer_alloc(
|
||||
app->event_loop,
|
||||
event_loop_multi_app_exit_timer_callback,
|
||||
FuriEventLoopTimerTypePeriodic,
|
||||
app);
|
||||
|
||||
app->gui = furi_record_open(RECORD_GUI);
|
||||
app->view_port = view_port_alloc();
|
||||
// Start the countdown from this value
|
||||
app->exit_countdown_value = COUNTDOWN_START_VALUE;
|
||||
// Gain exclusive access to the input events
|
||||
view_port_input_callback_set(app->view_port, event_loop_multi_app_input_callback, app);
|
||||
gui_add_view_port(app->gui, app->view_port, GuiLayerFullscreen);
|
||||
// Notify the event loop about incoming messages in the queue
|
||||
furi_event_loop_subscribe_message_queue(
|
||||
app->event_loop,
|
||||
app->input_queue,
|
||||
FuriEventLoopEventIn,
|
||||
event_loop_multi_app_input_queue_callback,
|
||||
app);
|
||||
// Notify the event loop about new data in the stream buffer
|
||||
furi_event_loop_subscribe_stream_buffer(
|
||||
app->event_loop,
|
||||
app->stream_buffer,
|
||||
FuriEventLoopEventIn | FuriEventLoopEventFlagEdge,
|
||||
event_loop_multi_app_stream_buffer_callback,
|
||||
app);
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
static void event_loop_multi_app_free(EventLoopMultiApp* app) {
|
||||
gui_remove_view_port(app->gui, app->view_port);
|
||||
furi_record_close(RECORD_GUI);
|
||||
// IMPORTANT: The user code MUST unsubscribe from all events before deleting the event loop.
|
||||
// Failure to do so will result in a crash.
|
||||
furi_event_loop_unsubscribe(app->event_loop, app->input_queue);
|
||||
furi_event_loop_unsubscribe(app->event_loop, app->stream_buffer);
|
||||
// Delete all instances
|
||||
view_port_free(app->view_port);
|
||||
furi_message_queue_free(app->input_queue);
|
||||
furi_stream_buffer_free(app->stream_buffer);
|
||||
// IMPORTANT: All timers MUST be deleted before deleting the associated event loop.
|
||||
// Failure to do so will result in a crash.
|
||||
furi_event_loop_timer_free(app->exit_timer);
|
||||
furi_thread_free(app->worker_thread);
|
||||
furi_event_loop_free(app->event_loop);
|
||||
|
||||
free(app);
|
||||
}
|
||||
|
||||
static void event_loop_multi_app_run(EventLoopMultiApp* app) {
|
||||
FURI_LOG_I(TAG, "Press keys to see them printed here.");
|
||||
FURI_LOG_I(TAG, "Long press \"Back\" to exit after %lu seconds.", COUNTDOWN_START_VALUE);
|
||||
FURI_LOG_I(TAG, "Long press \"Ok\" to cancel the countdown.");
|
||||
|
||||
// Start the worker thread
|
||||
furi_thread_start(app->worker_thread);
|
||||
// Run the application event loop. This call will block until the application is about to exit.
|
||||
furi_event_loop_run(app->event_loop);
|
||||
// Wait for the worker thread to finish.
|
||||
furi_thread_join(app->worker_thread);
|
||||
}
|
||||
|
||||
/*******************************************************************
|
||||
* vvv START HERE vvv
|
||||
*
|
||||
* The application's entry point - referenced in application.fam
|
||||
*******************************************************************/
|
||||
int32_t example_event_loop_multi_app(void* arg) {
|
||||
UNUSED(arg);
|
||||
|
||||
EventLoopMultiApp* app = event_loop_multi_app_alloc();
|
||||
event_loop_multi_app_run(app);
|
||||
event_loop_multi_app_free(app);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
/**
|
||||
* @file example_event_loop_mutex.c
|
||||
* @brief Example application that demonstrates the FuriEventLoop and FuriMutex integration.
|
||||
*
|
||||
* This application simulates a use case where a time-consuming blocking operation is executed
|
||||
* in a separate thread and a mutex is being used for synchronization. The application runs 10 iterations
|
||||
* of the above mentioned simulated work and prints the results to the debug output each time, then exits.
|
||||
*/
|
||||
|
||||
#include <furi.h>
|
||||
#include <furi_hal_random.h>
|
||||
|
||||
#define TAG "ExampleEventLoopMutex"
|
||||
|
||||
#define WORKER_ITERATION_COUNT (10)
|
||||
// We are interested in IN events (for the mutex, that means that the mutex has been released),
|
||||
// using edge trigger mode (reacting only to changes in mutex state) and
|
||||
// employing one-shot mode to automatically unsubscribe before the event is processed.
|
||||
#define MUTEX_EVENT_AND_FLAGS \
|
||||
(FuriEventLoopEventIn | FuriEventLoopEventFlagEdge | FuriEventLoopEventFlagOnce)
|
||||
|
||||
typedef struct {
|
||||
FuriEventLoop* event_loop;
|
||||
FuriThread* worker_thread;
|
||||
FuriMutex* worker_mutex;
|
||||
uint8_t worker_result;
|
||||
} EventLoopMutexApp;
|
||||
|
||||
// This funciton is being run in a separate thread to simulate lenghty blocking operations
|
||||
static int32_t event_loop_mutex_app_worker_thread(void* context) {
|
||||
furi_assert(context);
|
||||
EventLoopMutexApp* app = context;
|
||||
|
||||
FURI_LOG_I(TAG, "Worker thread started");
|
||||
|
||||
// Run 10 iterations of simulated work
|
||||
for(uint32_t i = 0; i < WORKER_ITERATION_COUNT; ++i) {
|
||||
FURI_LOG_I(TAG, "Doing work ...");
|
||||
// Take the mutex so that no-one can access the worker_result variable
|
||||
furi_check(furi_mutex_acquire(app->worker_mutex, FuriWaitForever) == FuriStatusOk);
|
||||
// Simulate a blocking operation with a random delay between 900 and 1100 ms
|
||||
const uint32_t work_time_ms = 900 + furi_hal_random_get() % 200;
|
||||
furi_delay_ms(work_time_ms);
|
||||
// Simulate a result with a random number between 0 and 255
|
||||
app->worker_result = furi_hal_random_get() % 0xFF;
|
||||
|
||||
FURI_LOG_I(TAG, "Work done in %lu ms", work_time_ms);
|
||||
// Release the mutex, which will notify the event loop that the result is ready
|
||||
furi_check(furi_mutex_release(app->worker_mutex) == FuriStatusOk);
|
||||
// Return control to the scheduler so that the event loop can take the mutex in its turn
|
||||
furi_thread_yield();
|
||||
}
|
||||
|
||||
FURI_LOG_I(TAG, "All work done, worker thread out!");
|
||||
// Request the event loop to stop
|
||||
furi_event_loop_stop(app->event_loop);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// This function is being run each time when the mutex gets released
|
||||
static bool event_loop_mutex_app_event_callback(FuriEventLoopObject* object, void* context) {
|
||||
furi_assert(context);
|
||||
|
||||
EventLoopMutexApp* app = context;
|
||||
furi_assert(object == app->worker_mutex);
|
||||
|
||||
// Take the mutex so that no-one can access the worker_result variable
|
||||
// IMPORTANT: the wait time MUST be 0, i.e. the event loop event callbacks
|
||||
// must NOT ever block. If it is possible that the mutex will be taken by
|
||||
// others, then the event callback code must take it into account.
|
||||
furi_check(furi_mutex_acquire(app->worker_mutex, 0) == FuriStatusOk);
|
||||
// Access the worker_result variable and print it.
|
||||
FURI_LOG_I(TAG, "Result available! Value: %u", app->worker_result);
|
||||
// Release the mutex, enabling the worker thread to continue when it's ready
|
||||
furi_check(furi_mutex_release(app->worker_mutex) == FuriStatusOk);
|
||||
// Subscribe for the mutex release events again, since we were unsubscribed automatically
|
||||
// before processing the event.
|
||||
furi_event_loop_subscribe_mutex(
|
||||
app->event_loop,
|
||||
app->worker_mutex,
|
||||
MUTEX_EVENT_AND_FLAGS,
|
||||
event_loop_mutex_app_event_callback,
|
||||
app);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static EventLoopMutexApp* event_loop_mutex_app_alloc(void) {
|
||||
EventLoopMutexApp* app = malloc(sizeof(EventLoopMutexApp));
|
||||
|
||||
// Create an event loop instance.
|
||||
app->event_loop = furi_event_loop_alloc();
|
||||
// Create a worker thread instance.
|
||||
app->worker_thread = furi_thread_alloc_ex(
|
||||
"EventLoopMutexWorker", 1024, event_loop_mutex_app_worker_thread, app);
|
||||
// Create a mutex instance.
|
||||
app->worker_mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
||||
// Subscribe for the mutex release events.
|
||||
// Note that since FuriEventLoopEventFlagOneShot is used, we will be automatically unsubscribed
|
||||
// from events before entering the event processing callback. This is necessary in order to not
|
||||
// trigger on events caused by releasing the mutex in the callback.
|
||||
furi_event_loop_subscribe_mutex(
|
||||
app->event_loop,
|
||||
app->worker_mutex,
|
||||
MUTEX_EVENT_AND_FLAGS,
|
||||
event_loop_mutex_app_event_callback,
|
||||
app);
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
static void event_loop_mutex_app_free(EventLoopMutexApp* app) {
|
||||
// IMPORTANT: The user code MUST unsubscribe from all events before deleting the event loop.
|
||||
// Failure to do so will result in a crash.
|
||||
furi_event_loop_unsubscribe(app->event_loop, app->worker_mutex);
|
||||
// Delete all instances
|
||||
furi_thread_free(app->worker_thread);
|
||||
furi_mutex_free(app->worker_mutex);
|
||||
furi_event_loop_free(app->event_loop);
|
||||
|
||||
free(app);
|
||||
}
|
||||
|
||||
static void event_loop_mutex_app_run(EventLoopMutexApp* app) {
|
||||
furi_thread_start(app->worker_thread);
|
||||
furi_event_loop_run(app->event_loop);
|
||||
furi_thread_join(app->worker_thread);
|
||||
}
|
||||
|
||||
// The application's entry point - referenced in application.fam
|
||||
int32_t example_event_loop_mutex_app(void* arg) {
|
||||
UNUSED(arg);
|
||||
|
||||
EventLoopMutexApp* app = event_loop_mutex_app_alloc();
|
||||
event_loop_mutex_app_run(app);
|
||||
event_loop_mutex_app_free(app);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
/**
|
||||
* @file example_event_loop_stream_buffer.c
|
||||
* @brief Example application that demonstrates the FuriEventLoop and FuriStreamBuffer integration.
|
||||
*
|
||||
* This application simulates a use case where some data data stream comes from a separate thread (or hardware)
|
||||
* and a stream buffer is used to act as an intermediate buffer. The worker thread produces 10 iterations of 32
|
||||
* bytes of simulated data, and each time when the buffer is half-filled, the data is taken out of it and printed
|
||||
* to the debug output. After completing all iterations, the application exits.
|
||||
*/
|
||||
|
||||
#include <furi.h>
|
||||
#include <furi_hal_random.h>
|
||||
|
||||
#define TAG "ExampleEventLoopStreamBuffer"
|
||||
|
||||
#define WORKER_ITERATION_COUNT (10)
|
||||
|
||||
#define STREAM_BUFFER_SIZE (32)
|
||||
#define STREAM_BUFFER_TRIG_LEVEL (STREAM_BUFFER_SIZE / 2)
|
||||
#define STREAM_BUFFER_EVENT_AND_FLAGS (FuriEventLoopEventIn | FuriEventLoopEventFlagEdge)
|
||||
|
||||
typedef struct {
|
||||
FuriEventLoop* event_loop;
|
||||
FuriThread* worker_thread;
|
||||
FuriStreamBuffer* stream_buffer;
|
||||
} EventLoopStreamBufferApp;
|
||||
|
||||
// This funciton is being run in a separate thread to simulate data coming from a producer thread or some device.
|
||||
static int32_t event_loop_stream_buffer_app_worker_thread(void* context) {
|
||||
furi_assert(context);
|
||||
EventLoopStreamBufferApp* app = context;
|
||||
|
||||
FURI_LOG_I(TAG, "Worker thread started");
|
||||
|
||||
for(uint32_t i = 0; i < WORKER_ITERATION_COUNT; ++i) {
|
||||
// Produce 32 bytes of simulated data.
|
||||
for(uint32_t j = 0; j < STREAM_BUFFER_SIZE; ++j) {
|
||||
// Simulate incoming data by generating a random byte.
|
||||
uint8_t data = furi_hal_random_get() % 0xFF;
|
||||
// Put the byte in the buffer. Depending on the use case, it may or may be not acceptable
|
||||
// to wait for free space to become available.
|
||||
furi_check(
|
||||
furi_stream_buffer_send(app->stream_buffer, &data, 1, FuriWaitForever) == 1);
|
||||
// Delay between 30 and 50 ms to slow down the output for clarity.
|
||||
furi_delay_ms(30 + furi_hal_random_get() % 20);
|
||||
}
|
||||
}
|
||||
|
||||
FURI_LOG_I(TAG, "All work done, worker thread out!");
|
||||
// Request the event loop to stop
|
||||
furi_event_loop_stop(app->event_loop);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// This function is being run each time when the number of bytes in the buffer is above its trigger level.
|
||||
static bool
|
||||
event_loop_stream_buffer_app_event_callback(FuriEventLoopObject* object, void* context) {
|
||||
furi_assert(context);
|
||||
EventLoopStreamBufferApp* app = context;
|
||||
|
||||
furi_assert(object == app->stream_buffer);
|
||||
|
||||
// Temporary buffer that can hold at most half of the stream buffer's capacity.
|
||||
uint8_t data[STREAM_BUFFER_TRIG_LEVEL];
|
||||
// Receive the data. It is guaranteed that the amount of data in the buffer will be equal to
|
||||
// or greater than the trigger level, therefore, no waiting delay is necessary.
|
||||
furi_check(
|
||||
furi_stream_buffer_receive(app->stream_buffer, data, sizeof(data), 0) == sizeof(data));
|
||||
|
||||
// Format the data for printing and print it to the debug output.
|
||||
FuriString* tmp_str = furi_string_alloc();
|
||||
for(uint32_t i = 0; i < sizeof(data); ++i) {
|
||||
furi_string_cat_printf(tmp_str, "%02X ", data[i]);
|
||||
}
|
||||
|
||||
FURI_LOG_I(TAG, "Received data: %s", furi_string_get_cstr(tmp_str));
|
||||
furi_string_free(tmp_str);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static EventLoopStreamBufferApp* event_loop_stream_buffer_app_alloc(void) {
|
||||
EventLoopStreamBufferApp* app = malloc(sizeof(EventLoopStreamBufferApp));
|
||||
|
||||
// Create an event loop instance.
|
||||
app->event_loop = furi_event_loop_alloc();
|
||||
// Create a worker thread instance.
|
||||
app->worker_thread = furi_thread_alloc_ex(
|
||||
"EventLoopStreamBufferWorker", 1024, event_loop_stream_buffer_app_worker_thread, app);
|
||||
// Create a stream_buffer instance.
|
||||
app->stream_buffer = furi_stream_buffer_alloc(STREAM_BUFFER_SIZE, STREAM_BUFFER_TRIG_LEVEL);
|
||||
// Subscribe for the stream buffer IN events in edge triggered mode.
|
||||
furi_event_loop_subscribe_stream_buffer(
|
||||
app->event_loop,
|
||||
app->stream_buffer,
|
||||
STREAM_BUFFER_EVENT_AND_FLAGS,
|
||||
event_loop_stream_buffer_app_event_callback,
|
||||
app);
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
static void event_loop_stream_buffer_app_free(EventLoopStreamBufferApp* app) {
|
||||
// IMPORTANT: The user code MUST unsubscribe from all events before deleting the event loop.
|
||||
// Failure to do so will result in a crash.
|
||||
furi_event_loop_unsubscribe(app->event_loop, app->stream_buffer);
|
||||
// Delete all instances
|
||||
furi_thread_free(app->worker_thread);
|
||||
furi_stream_buffer_free(app->stream_buffer);
|
||||
furi_event_loop_free(app->event_loop);
|
||||
|
||||
free(app);
|
||||
}
|
||||
|
||||
static void event_loop_stream_buffer_app_run(EventLoopStreamBufferApp* app) {
|
||||
furi_thread_start(app->worker_thread);
|
||||
furi_event_loop_run(app->event_loop);
|
||||
furi_thread_join(app->worker_thread);
|
||||
}
|
||||
|
||||
// The application's entry point - referenced in application.fam
|
||||
int32_t example_event_loop_stream_buffer_app(void* arg) {
|
||||
UNUSED(arg);
|
||||
|
||||
EventLoopStreamBufferApp* app = event_loop_stream_buffer_app_alloc();
|
||||
event_loop_stream_buffer_app_run(app);
|
||||
event_loop_stream_buffer_app_free(app);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* @file example_event_loop_timer.c
|
||||
* @brief Example application that demonstrates FuriEventLoop's software timer capability.
|
||||
*
|
||||
* This application prints a countdown from 10 to 0 to the debug output and then exits.
|
||||
* Despite only one timer being used in this example for clarity, an event loop instance can have
|
||||
* an arbitrary number of independent timers of any type (periodic or one-shot).
|
||||
*
|
||||
*/
|
||||
#include <furi.h>
|
||||
|
||||
#define TAG "ExampleEventLoopTimer"
|
||||
|
||||
#define COUNTDOWN_START_VALUE (10)
|
||||
#define COUNTDOWN_INTERVAL_MS (1000)
|
||||
|
||||
typedef struct {
|
||||
FuriEventLoop* event_loop;
|
||||
FuriEventLoopTimer* timer;
|
||||
uint32_t countdown_value;
|
||||
} EventLoopTimerApp;
|
||||
|
||||
// This function is called each time the timer expires (i.e. once per 1000 ms (1s) in this example)
|
||||
static void event_loop_timer_callback(void* context) {
|
||||
furi_assert(context);
|
||||
EventLoopTimerApp* app = context;
|
||||
|
||||
// Print the countdown value
|
||||
FURI_LOG_I(TAG, "T-00:00:%02lu", app->countdown_value);
|
||||
|
||||
if(app->countdown_value == 0) {
|
||||
// If the countdown reached 0, print the final line and stop the event loop
|
||||
FURI_LOG_I(TAG, "Blast off to adventure!");
|
||||
// After this call, the control will be returned back to event_loop_timers_app_run()
|
||||
furi_event_loop_stop(app->event_loop);
|
||||
|
||||
} else {
|
||||
// Decrement the countdown value
|
||||
app->countdown_value -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
static EventLoopTimerApp* event_loop_timer_app_alloc(void) {
|
||||
EventLoopTimerApp* app = malloc(sizeof(EventLoopTimerApp));
|
||||
|
||||
// Create an event loop instance.
|
||||
app->event_loop = furi_event_loop_alloc();
|
||||
// Create a software timer instance.
|
||||
// The timer is bound to the event loop instance and will execute in its context.
|
||||
// Here, the timer type is periodic, i.e. it will restart automatically after expiring.
|
||||
app->timer = furi_event_loop_timer_alloc(
|
||||
app->event_loop, event_loop_timer_callback, FuriEventLoopTimerTypePeriodic, app);
|
||||
// The countdown value will be tracked in this variable.
|
||||
app->countdown_value = COUNTDOWN_START_VALUE;
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
static void event_loop_timer_app_free(EventLoopTimerApp* app) {
|
||||
// IMPORTANT: All event loop timers MUST be deleted BEFORE deleting the event loop itself.
|
||||
// Failure to do so will result in a crash.
|
||||
furi_event_loop_timer_free(app->timer);
|
||||
// With all timers deleted, it's safe to delete the event loop.
|
||||
furi_event_loop_free(app->event_loop);
|
||||
free(app);
|
||||
}
|
||||
|
||||
static void event_loop_timer_app_run(EventLoopTimerApp* app) {
|
||||
FURI_LOG_I(TAG, "All systems go! Prepare for countdown!");
|
||||
|
||||
// Timers can be started either before the event loop is run, or in any
|
||||
// callback function called by a running event loop.
|
||||
furi_event_loop_timer_start(app->timer, COUNTDOWN_INTERVAL_MS);
|
||||
// This call will block until furi_event_loop_stop() is called.
|
||||
furi_event_loop_run(app->event_loop);
|
||||
}
|
||||
|
||||
// The application's entry point - referenced in application.fam
|
||||
int32_t example_event_loop_timer_app(void* arg) {
|
||||
UNUSED(arg);
|
||||
|
||||
EventLoopTimerApp* app = event_loop_timer_app_alloc();
|
||||
event_loop_timer_app_run(app);
|
||||
event_loop_timer_app_free(app);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 226 B |
7
applications/examples/example_number_input/ReadMe.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# Number Input
|
||||
|
||||
Simple keyboard that limits user inputs to a full number (integer). Useful to enforce correct entries without the need of intense validations after a user input.
|
||||
|
||||
Definition of min/max values is required. Numbers are of type int32_t. If negative numbers are allowed withing min - max, an additional button is displayed to switch the sign between + and -.
|
||||
|
||||
It is also possible to define a header text, shown in this example app with the 3 different input options.
|
||||
10
applications/examples/example_number_input/application.fam
Normal file
@@ -0,0 +1,10 @@
|
||||
App(
|
||||
appid="example_number_input",
|
||||
name="Example: Number Input",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="example_number_input",
|
||||
requires=["gui"],
|
||||
stack_size=1 * 1024,
|
||||
fap_icon="example_number_input_10px.png",
|
||||
fap_category="Examples",
|
||||
)
|
||||
@@ -0,0 +1,79 @@
|
||||
#include "example_number_input.h"
|
||||
|
||||
bool example_number_input_custom_event_callback(void* context, uint32_t event) {
|
||||
furi_assert(context);
|
||||
ExampleNumberInput* app = context;
|
||||
return scene_manager_handle_custom_event(app->scene_manager, event);
|
||||
}
|
||||
|
||||
static bool example_number_input_back_event_callback(void* context) {
|
||||
furi_assert(context);
|
||||
ExampleNumberInput* app = context;
|
||||
return scene_manager_handle_back_event(app->scene_manager);
|
||||
}
|
||||
|
||||
static ExampleNumberInput* example_number_input_alloc() {
|
||||
ExampleNumberInput* app = malloc(sizeof(ExampleNumberInput));
|
||||
app->gui = furi_record_open(RECORD_GUI);
|
||||
|
||||
app->view_dispatcher = view_dispatcher_alloc();
|
||||
|
||||
app->scene_manager = scene_manager_alloc(&example_number_input_scene_handlers, app);
|
||||
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
|
||||
view_dispatcher_set_custom_event_callback(
|
||||
app->view_dispatcher, example_number_input_custom_event_callback);
|
||||
view_dispatcher_set_navigation_event_callback(
|
||||
app->view_dispatcher, example_number_input_back_event_callback);
|
||||
|
||||
app->number_input = number_input_alloc();
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher,
|
||||
ExampleNumberInputViewIdNumberInput,
|
||||
number_input_get_view(app->number_input));
|
||||
|
||||
app->dialog_ex = dialog_ex_alloc();
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher,
|
||||
ExampleNumberInputViewIdShowNumber,
|
||||
dialog_ex_get_view(app->dialog_ex));
|
||||
|
||||
app->current_number = 5;
|
||||
app->min_value = INT32_MIN;
|
||||
app->max_value = INT32_MAX;
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
static void example_number_input_free(ExampleNumberInput* app) {
|
||||
furi_assert(app);
|
||||
|
||||
view_dispatcher_remove_view(app->view_dispatcher, ExampleNumberInputViewIdShowNumber);
|
||||
dialog_ex_free(app->dialog_ex);
|
||||
|
||||
view_dispatcher_remove_view(app->view_dispatcher, ExampleNumberInputViewIdNumberInput);
|
||||
number_input_free(app->number_input);
|
||||
|
||||
scene_manager_free(app->scene_manager);
|
||||
view_dispatcher_free(app->view_dispatcher);
|
||||
|
||||
furi_record_close(RECORD_GUI);
|
||||
app->gui = NULL;
|
||||
|
||||
//Remove whatever is left
|
||||
free(app);
|
||||
}
|
||||
|
||||
int32_t example_number_input(void* p) {
|
||||
UNUSED(p);
|
||||
ExampleNumberInput* app = example_number_input_alloc();
|
||||
|
||||
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
|
||||
|
||||
scene_manager_next_scene(app->scene_manager, ExampleNumberInputSceneShowNumber);
|
||||
|
||||
view_dispatcher_run(app->view_dispatcher);
|
||||
|
||||
example_number_input_free(app);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
|
||||
#include <gui/gui.h>
|
||||
#include <gui/elements.h>
|
||||
#include <gui/scene_manager.h>
|
||||
#include <gui/modules/dialog_ex.h>
|
||||
#include <gui/modules/number_input.h>
|
||||
#include <gui/view.h>
|
||||
#include <gui/view_dispatcher.h>
|
||||
#include <input/input.h>
|
||||
|
||||
#include "scenes/example_number_input_scene.h"
|
||||
|
||||
typedef struct ExampleNumberInputShowNumber ExampleNumberInputShowNumber;
|
||||
|
||||
typedef enum {
|
||||
ExampleNumberInputViewIdShowNumber,
|
||||
ExampleNumberInputViewIdNumberInput,
|
||||
} ExampleNumberInputViewId;
|
||||
|
||||
typedef struct {
|
||||
Gui* gui;
|
||||
SceneManager* scene_manager;
|
||||
ViewDispatcher* view_dispatcher;
|
||||
|
||||
NumberInput* number_input;
|
||||
DialogEx* dialog_ex;
|
||||
|
||||
int32_t current_number;
|
||||
int32_t min_value;
|
||||
int32_t max_value;
|
||||
} ExampleNumberInput;
|
||||
|
After Width: | Height: | Size: 87 B |
@@ -0,0 +1,30 @@
|
||||
#include "example_number_input_scene.h"
|
||||
|
||||
// Generate scene on_enter handlers array
|
||||
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
|
||||
void (*const example_number_input_on_enter_handlers[])(void*) = {
|
||||
#include "example_number_input_scene_config.h"
|
||||
};
|
||||
#undef ADD_SCENE
|
||||
|
||||
// Generate scene on_event handlers array
|
||||
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event,
|
||||
bool (*const example_number_input_on_event_handlers[])(void* context, SceneManagerEvent event) = {
|
||||
#include "example_number_input_scene_config.h"
|
||||
};
|
||||
#undef ADD_SCENE
|
||||
|
||||
// Generate scene on_exit handlers array
|
||||
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit,
|
||||
void (*const example_number_input_on_exit_handlers[])(void* context) = {
|
||||
#include "example_number_input_scene_config.h"
|
||||
};
|
||||
#undef ADD_SCENE
|
||||
|
||||
// Initialize scene handlers configuration structure
|
||||
const SceneManagerHandlers example_number_input_scene_handlers = {
|
||||
.on_enter_handlers = example_number_input_on_enter_handlers,
|
||||
.on_event_handlers = example_number_input_on_event_handlers,
|
||||
.on_exit_handlers = example_number_input_on_exit_handlers,
|
||||
.scene_num = ExampleNumberInputSceneNum,
|
||||
};
|
||||
@@ -3,27 +3,27 @@
|
||||
#include <gui/scene_manager.h>
|
||||
|
||||
// Generate scene id and total number
|
||||
#define ADD_SCENE(prefix, name, id) StorageMoveToSd##id,
|
||||
#define ADD_SCENE(prefix, name, id) ExampleNumberInputScene##id,
|
||||
typedef enum {
|
||||
#include "storage_move_to_sd_scene_config.h"
|
||||
StorageMoveToSdSceneNum,
|
||||
} StorageMoveToSdScene;
|
||||
#include "example_number_input_scene_config.h"
|
||||
ExampleNumberInputSceneNum,
|
||||
} ExampleNumberInputScene;
|
||||
#undef ADD_SCENE
|
||||
|
||||
extern const SceneManagerHandlers storage_move_to_sd_scene_handlers;
|
||||
extern const SceneManagerHandlers example_number_input_scene_handlers;
|
||||
|
||||
// Generate scene on_enter handlers declaration
|
||||
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*);
|
||||
#include "storage_move_to_sd_scene_config.h"
|
||||
#include "example_number_input_scene_config.h"
|
||||
#undef ADD_SCENE
|
||||
|
||||
// Generate scene on_event handlers declaration
|
||||
#define ADD_SCENE(prefix, name, id) \
|
||||
bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event);
|
||||
#include "storage_move_to_sd_scene_config.h"
|
||||
#include "example_number_input_scene_config.h"
|
||||
#undef ADD_SCENE
|
||||
|
||||
// Generate scene on_exit handlers declaration
|
||||
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context);
|
||||
#include "storage_move_to_sd_scene_config.h"
|
||||
#include "example_number_input_scene_config.h"
|
||||
#undef ADD_SCENE
|
||||
@@ -0,0 +1,4 @@
|
||||
ADD_SCENE(example_number_input, input_number, InputNumber)
|
||||
ADD_SCENE(example_number_input, show_number, ShowNumber)
|
||||
ADD_SCENE(example_number_input, input_max, InputMax)
|
||||
ADD_SCENE(example_number_input, input_min, InputMin)
|
||||
@@ -0,0 +1,39 @@
|
||||
#include "../example_number_input.h"
|
||||
|
||||
void example_number_input_scene_input_max_callback(void* context, int32_t number) {
|
||||
ExampleNumberInput* app = context;
|
||||
app->max_value = number;
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, 0);
|
||||
}
|
||||
|
||||
void example_number_input_scene_input_max_on_enter(void* context) {
|
||||
furi_assert(context);
|
||||
ExampleNumberInput* app = context;
|
||||
NumberInput* number_input = app->number_input;
|
||||
|
||||
number_input_set_header_text(number_input, "Enter the maximum value");
|
||||
number_input_set_result_callback(
|
||||
number_input,
|
||||
example_number_input_scene_input_max_callback,
|
||||
context,
|
||||
app->max_value,
|
||||
app->min_value,
|
||||
INT32_MAX);
|
||||
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, ExampleNumberInputViewIdNumberInput);
|
||||
}
|
||||
|
||||
bool example_number_input_scene_input_max_on_event(void* context, SceneManagerEvent event) {
|
||||
ExampleNumberInput* app = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
scene_manager_previous_scene(app->scene_manager);
|
||||
return true;
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void example_number_input_scene_input_max_on_exit(void* context) {
|
||||
UNUSED(context);
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
#include "../example_number_input.h"
|
||||
|
||||
void example_number_input_scene_input_min_callback(void* context, int32_t number) {
|
||||
ExampleNumberInput* app = context;
|
||||
app->min_value = number;
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, 0);
|
||||
}
|
||||
|
||||
void example_number_input_scene_input_min_on_enter(void* context) {
|
||||
furi_assert(context);
|
||||
ExampleNumberInput* app = context;
|
||||
NumberInput* number_input = app->number_input;
|
||||
|
||||
number_input_set_header_text(number_input, "Enter the minimum value");
|
||||
number_input_set_result_callback(
|
||||
number_input,
|
||||
example_number_input_scene_input_min_callback,
|
||||
context,
|
||||
app->min_value,
|
||||
INT32_MIN,
|
||||
app->max_value);
|
||||
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, ExampleNumberInputViewIdNumberInput);
|
||||
}
|
||||
|
||||
bool example_number_input_scene_input_min_on_event(void* context, SceneManagerEvent event) {
|
||||
ExampleNumberInput* app = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
scene_manager_previous_scene(app->scene_manager);
|
||||
return true;
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void example_number_input_scene_input_min_on_exit(void* context) {
|
||||
UNUSED(context);
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
#include "../example_number_input.h"
|
||||
|
||||
void example_number_input_scene_input_number_callback(void* context, int32_t number) {
|
||||
ExampleNumberInput* app = context;
|
||||
app->current_number = number;
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, 0);
|
||||
}
|
||||
|
||||
void example_number_input_scene_input_number_on_enter(void* context) {
|
||||
furi_assert(context);
|
||||
ExampleNumberInput* app = context;
|
||||
NumberInput* number_input = app->number_input;
|
||||
|
||||
char str[50];
|
||||
snprintf(str, sizeof(str), "Set Number (%ld - %ld)", app->min_value, app->max_value);
|
||||
|
||||
number_input_set_header_text(number_input, str);
|
||||
number_input_set_result_callback(
|
||||
number_input,
|
||||
example_number_input_scene_input_number_callback,
|
||||
context,
|
||||
app->current_number,
|
||||
app->min_value,
|
||||
app->max_value);
|
||||
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, ExampleNumberInputViewIdNumberInput);
|
||||
}
|
||||
|
||||
bool example_number_input_scene_input_number_on_event(void* context, SceneManagerEvent event) {
|
||||
ExampleNumberInput* app = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) { //Back button pressed
|
||||
scene_manager_previous_scene(app->scene_manager);
|
||||
return true;
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void example_number_input_scene_input_number_on_exit(void* context) {
|
||||
UNUSED(context);
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
#include "../example_number_input.h"
|
||||
|
||||
static void
|
||||
example_number_input_scene_confirm_dialog_callback(DialogExResult result, void* context) {
|
||||
ExampleNumberInput* app = context;
|
||||
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, result);
|
||||
}
|
||||
|
||||
static void example_number_input_scene_update_view(void* context) {
|
||||
ExampleNumberInput* app = context;
|
||||
DialogEx* dialog_ex = app->dialog_ex;
|
||||
|
||||
dialog_ex_set_header(dialog_ex, "The number is", 64, 0, AlignCenter, AlignTop);
|
||||
|
||||
static char buffer[12]; //needs static for extended lifetime
|
||||
|
||||
snprintf(buffer, sizeof(buffer), "%ld", app->current_number);
|
||||
dialog_ex_set_text(dialog_ex, buffer, 64, 29, AlignCenter, AlignCenter);
|
||||
|
||||
dialog_ex_set_left_button_text(dialog_ex, "Min");
|
||||
dialog_ex_set_right_button_text(dialog_ex, "Max");
|
||||
dialog_ex_set_center_button_text(dialog_ex, "Change");
|
||||
|
||||
dialog_ex_set_result_callback(dialog_ex, example_number_input_scene_confirm_dialog_callback);
|
||||
dialog_ex_set_context(dialog_ex, app);
|
||||
}
|
||||
|
||||
void example_number_input_scene_show_number_on_enter(void* context) {
|
||||
furi_assert(context);
|
||||
ExampleNumberInput* app = context;
|
||||
|
||||
example_number_input_scene_update_view(app);
|
||||
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, ExampleNumberInputViewIdShowNumber);
|
||||
}
|
||||
|
||||
bool example_number_input_scene_show_number_on_event(void* context, SceneManagerEvent event) {
|
||||
ExampleNumberInput* app = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
switch(event.event) {
|
||||
case DialogExResultCenter:
|
||||
scene_manager_next_scene(app->scene_manager, ExampleNumberInputSceneInputNumber);
|
||||
consumed = true;
|
||||
break;
|
||||
case DialogExResultLeft:
|
||||
scene_manager_next_scene(app->scene_manager, ExampleNumberInputSceneInputMin);
|
||||
consumed = true;
|
||||
break;
|
||||
case DialogExResultRight:
|
||||
scene_manager_next_scene(app->scene_manager, ExampleNumberInputSceneInputMax);
|
||||
consumed = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void example_number_input_scene_show_number_on_exit(void* context) {
|
||||
UNUSED(context);
|
||||
}
|
||||
|
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 91 B |
@@ -0,0 +1,8 @@
|
||||
App(
|
||||
appid="example_view_dispatcher",
|
||||
name="Example: ViewDispatcher",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="example_view_dispatcher_app",
|
||||
requires=["gui"],
|
||||
fap_category="Examples",
|
||||
)
|
||||
@@ -0,0 +1,173 @@
|
||||
/**
|
||||
* @file example_view_dispatcher.c
|
||||
* @brief Example application demonstrating the usage of the ViewDispatcher library.
|
||||
*
|
||||
* This application can display one of two views: either a Widget or a Submenu.
|
||||
* Each view has its own way of switching to another one:
|
||||
*
|
||||
* - A center button in the Widget view.
|
||||
* - A submenu item in the Submenu view
|
||||
*
|
||||
* Press either to switch to a different view. Press Back to exit the application.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <gui/gui.h>
|
||||
#include <gui/view_dispatcher.h>
|
||||
|
||||
#include <gui/modules/widget.h>
|
||||
#include <gui/modules/submenu.h>
|
||||
|
||||
// Enumeration of the view indexes.
|
||||
typedef enum {
|
||||
ViewIndexWidget,
|
||||
ViewIndexSubmenu,
|
||||
ViewIndexCount,
|
||||
} ViewIndex;
|
||||
|
||||
// Enumeration of submenu items.
|
||||
typedef enum {
|
||||
SubmenuIndexNothing,
|
||||
SubmenuIndexSwitchView,
|
||||
} SubmenuIndex;
|
||||
|
||||
// Main application structure.
|
||||
typedef struct {
|
||||
ViewDispatcher* view_dispatcher;
|
||||
Widget* widget;
|
||||
Submenu* submenu;
|
||||
} ExampleViewDispatcherApp;
|
||||
|
||||
// This function is called when the user has pressed the Back key.
|
||||
static bool example_view_dispatcher_app_navigation_callback(void* context) {
|
||||
furi_assert(context);
|
||||
ExampleViewDispatcherApp* app = context;
|
||||
// Back means exit the application, which can be done by stopping the ViewDispatcher.
|
||||
view_dispatcher_stop(app->view_dispatcher);
|
||||
return true;
|
||||
}
|
||||
|
||||
// This function is called when there are custom events to process.
|
||||
static bool example_view_dispatcher_app_custom_event_callback(void* context, uint32_t event) {
|
||||
furi_assert(context);
|
||||
ExampleViewDispatcherApp* app = context;
|
||||
// The event numerical value can mean different things (the application is responsible to uphold its chosen convention)
|
||||
// In this example, the only possible meaning is the view index to switch to.
|
||||
furi_assert(event < ViewIndexCount);
|
||||
// Switch to the requested view.
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, event);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// This function is called when the user presses the "Switch View" button on the Widget view.
|
||||
static void example_view_dispatcher_app_button_callback(
|
||||
GuiButtonType button_type,
|
||||
InputType input_type,
|
||||
void* context) {
|
||||
furi_assert(context);
|
||||
ExampleViewDispatcherApp* app = context;
|
||||
// Only request the view switch if the user short-presses the Center button.
|
||||
if(button_type == GuiButtonTypeCenter && input_type == InputTypeShort) {
|
||||
// Request switch to the Submenu view via the custom event queue.
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, ViewIndexSubmenu);
|
||||
}
|
||||
}
|
||||
|
||||
// This function is called when the user activates the "Switch View" submenu item.
|
||||
static void example_view_dispatcher_app_submenu_callback(void* context, uint32_t index) {
|
||||
furi_assert(context);
|
||||
ExampleViewDispatcherApp* app = context;
|
||||
// Only request the view switch if the user activates the "Switch View" item.
|
||||
if(index == SubmenuIndexSwitchView) {
|
||||
// Request switch to the Widget view via the custom event queue.
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, ViewIndexWidget);
|
||||
}
|
||||
}
|
||||
|
||||
// Application constructor function.
|
||||
static ExampleViewDispatcherApp* example_view_dispatcher_app_alloc() {
|
||||
ExampleViewDispatcherApp* app = malloc(sizeof(ExampleViewDispatcherApp));
|
||||
// Access the GUI API instance.
|
||||
Gui* gui = furi_record_open(RECORD_GUI);
|
||||
// Create and initialize the Widget view.
|
||||
app->widget = widget_alloc();
|
||||
widget_add_string_multiline_element(
|
||||
app->widget, 64, 32, AlignCenter, AlignCenter, FontSecondary, "Press the Button below");
|
||||
widget_add_button_element(
|
||||
app->widget,
|
||||
GuiButtonTypeCenter,
|
||||
"Switch View",
|
||||
example_view_dispatcher_app_button_callback,
|
||||
app);
|
||||
// Create and initialize the Submenu view.
|
||||
app->submenu = submenu_alloc();
|
||||
submenu_add_item(app->submenu, "Do Nothing", SubmenuIndexNothing, NULL, NULL);
|
||||
submenu_add_item(
|
||||
app->submenu,
|
||||
"Switch View",
|
||||
SubmenuIndexSwitchView,
|
||||
example_view_dispatcher_app_submenu_callback,
|
||||
app);
|
||||
// Create the ViewDispatcher instance.
|
||||
app->view_dispatcher = view_dispatcher_alloc();
|
||||
// Let the GUI know about this ViewDispatcher instance.
|
||||
view_dispatcher_attach_to_gui(app->view_dispatcher, gui, ViewDispatcherTypeFullscreen);
|
||||
// Register the views within the ViewDispatcher instance. This alone will not show any of them on the screen.
|
||||
// Each view must have its own index to refer to it later (it is best done via an enumeration as shown here).
|
||||
view_dispatcher_add_view(app->view_dispatcher, ViewIndexWidget, widget_get_view(app->widget));
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, ViewIndexSubmenu, submenu_get_view(app->submenu));
|
||||
// Set the custom event callback. It will be called each time a custom event is scheduled
|
||||
// using the view_dispatcher_send_custom_callback() function.
|
||||
view_dispatcher_set_custom_event_callback(
|
||||
app->view_dispatcher, example_view_dispatcher_app_custom_event_callback);
|
||||
// Set the navigation, or back button callback. It will be called if the user pressed the Back button
|
||||
// and the event was not handled in the currently displayed view.
|
||||
view_dispatcher_set_navigation_event_callback(
|
||||
app->view_dispatcher, example_view_dispatcher_app_navigation_callback);
|
||||
// The context will be passed to the callbacks as a parameter, so we have access to our application object.
|
||||
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
// Application destructor function.
|
||||
static void example_view_dispatcher_app_free(ExampleViewDispatcherApp* app) {
|
||||
// All views must be un-registered (removed) from a ViewDispatcher instance
|
||||
// before deleting it. Failure to do so will result in a crash.
|
||||
view_dispatcher_remove_view(app->view_dispatcher, ViewIndexWidget);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, ViewIndexSubmenu);
|
||||
// Now it is safe to delete the ViewDispatcher instance.
|
||||
view_dispatcher_free(app->view_dispatcher);
|
||||
// Delete the views
|
||||
widget_free(app->widget);
|
||||
submenu_free(app->submenu);
|
||||
// End access to hte the GUI API.
|
||||
furi_record_close(RECORD_GUI);
|
||||
// Free the remaining memory.
|
||||
free(app);
|
||||
}
|
||||
|
||||
static void example_view_dispatcher_app_run(ExampleViewDispatcherApp* app) {
|
||||
// Display the Widget view on the screen.
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, ViewIndexWidget);
|
||||
// This function will block until view_dispatcher_stop() is called.
|
||||
// Internally, it uses a FuriEventLoop (see FuriEventLoop examples for more info on this).
|
||||
view_dispatcher_run(app->view_dispatcher);
|
||||
}
|
||||
|
||||
/*******************************************************************
|
||||
* vvv START HERE vvv
|
||||
*
|
||||
* The application's entry point - referenced in application.fam
|
||||
*******************************************************************/
|
||||
int32_t example_view_dispatcher_app(void* arg) {
|
||||
UNUSED(arg);
|
||||
|
||||
ExampleViewDispatcherApp* app = example_view_dispatcher_app_alloc();
|
||||
example_view_dispatcher_app_run(app);
|
||||
example_view_dispatcher_app_free(app);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
App(
|
||||
appid="example_view_holder",
|
||||
name="Example: ViewHolder",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="example_view_holder_app",
|
||||
requires=["gui"],
|
||||
fap_category="Examples",
|
||||
)
|
||||
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* @file example_view_holder.c
|
||||
* @brief Example application demonstrating the usage of the ViewHolder library.
|
||||
*
|
||||
* This application will display a text box with some scrollable text in it.
|
||||
* Press the Back key to exit the application.
|
||||
*/
|
||||
|
||||
#include <gui/gui.h>
|
||||
#include <gui/view_holder.h>
|
||||
#include <gui/modules/text_box.h>
|
||||
|
||||
#include <api_lock.h>
|
||||
|
||||
// This function will be called when the user presses the Back button.
|
||||
static void example_view_holder_back_callback(void* context) {
|
||||
FuriApiLock exit_lock = context;
|
||||
// Unlock the exit lock, thus enabling the app to exit.
|
||||
api_lock_unlock(exit_lock);
|
||||
}
|
||||
|
||||
int32_t example_view_holder_app(void* arg) {
|
||||
UNUSED(arg);
|
||||
|
||||
// Access the GUI API instance.
|
||||
Gui* gui = furi_record_open(RECORD_GUI);
|
||||
// Create a TextBox view. The Gui object only accepts
|
||||
// ViewPort instances, so we will need to address that later.
|
||||
TextBox* text_box = text_box_alloc();
|
||||
// Set some text so that the text box is not empty.
|
||||
text_box_set_text(
|
||||
text_box,
|
||||
"ViewHolder is being used\n"
|
||||
"to show this TextBox view.\n\n"
|
||||
"Scroll down to see more.\n\n\n"
|
||||
"Press \"Back\" to exit.");
|
||||
|
||||
// Create a ViewHolder instance. It will serve as an adapter to convert
|
||||
// between the View type provided by the TextBox view and the ViewPort type
|
||||
// that the GUI can actually display.
|
||||
ViewHolder* view_holder = view_holder_alloc();
|
||||
// Let the GUI know about this ViewHolder instance.
|
||||
view_holder_attach_to_gui(view_holder, gui);
|
||||
// Set the view that we want to display.
|
||||
view_holder_set_view(view_holder, text_box_get_view(text_box));
|
||||
|
||||
// The part below is not really related to this example, but is necessary for it to function.
|
||||
// We need to somehow stall the application thread so that the view stays on the screen (otherwise
|
||||
// the app will just exit and won't display anything) and at the same time we need a way to quit out
|
||||
// of the application.
|
||||
|
||||
// In this example, a simple FuriApiLock instance is used. A real-world application is likely to have some
|
||||
// kind of event handling loop here instead. (see the ViewDispatcher example or one of FuriEventLoop
|
||||
// examples for that).
|
||||
|
||||
// Create a pre-locked FuriApiLock instance.
|
||||
FuriApiLock exit_lock = api_lock_alloc_locked();
|
||||
// Set a Back event callback for the ViewHolder instance. It will be called when the user
|
||||
// presses the Back button. We pass the exit lock instance as the context to be able to access
|
||||
// it inside the callback function.
|
||||
view_holder_set_back_callback(view_holder, example_view_holder_back_callback, exit_lock);
|
||||
|
||||
// This call will block the application thread from running until the exit lock gets unlocked somehow
|
||||
// (the only way it can happen in this example is via the back callback).
|
||||
api_lock_wait_unlock_and_free(exit_lock);
|
||||
|
||||
// The back key has been pressed, which unlocked the exit lock. The application is about to exit.
|
||||
|
||||
// The view must be removed from a ViewHolder instance before deleting it.
|
||||
view_holder_set_view(view_holder, NULL);
|
||||
// Delete everything to prevent memory leaks.
|
||||
view_holder_free(view_holder);
|
||||
text_box_free(text_box);
|
||||
// End access to the GUI API.
|
||||
furi_record_close(RECORD_GUI);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -30,7 +30,6 @@ ArchiveApp* archive_alloc(void) {
|
||||
archive->view_dispatcher = view_dispatcher_alloc();
|
||||
|
||||
ViewDispatcher* view_dispatcher = archive->view_dispatcher;
|
||||
view_dispatcher_enable_queue(view_dispatcher);
|
||||
view_dispatcher_set_event_callback_context(view_dispatcher, archive);
|
||||
view_dispatcher_set_custom_event_callback(view_dispatcher, archive_custom_event_callback);
|
||||
view_dispatcher_set_navigation_event_callback(view_dispatcher, archive_back_event_callback);
|
||||
|
||||
@@ -30,8 +30,8 @@ bool archive_app_is_available(void* context, const char* path) {
|
||||
bool file_exists = false;
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
|
||||
if(storage_file_exists(storage, ANY_PATH("u2f/key.u2f"))) {
|
||||
file_exists = storage_file_exists(storage, ANY_PATH("u2f/cnt.u2f"));
|
||||
if(storage_file_exists(storage, EXT_PATH("u2f/key.u2f"))) {
|
||||
file_exists = storage_file_exists(storage, EXT_PATH("u2f/cnt.u2f"));
|
||||
}
|
||||
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
@@ -68,8 +68,8 @@ void archive_app_delete_file(void* context, const char* path) {
|
||||
|
||||
if(app == ArchiveAppTypeU2f) {
|
||||
Storage* fs_api = furi_record_open(RECORD_STORAGE);
|
||||
res = (storage_common_remove(fs_api, ANY_PATH("u2f/key.u2f")) == FSE_OK);
|
||||
res |= (storage_common_remove(fs_api, ANY_PATH("u2f/cnt.u2f")) == FSE_OK);
|
||||
res = (storage_common_remove(fs_api, EXT_PATH("u2f/key.u2f")) == FSE_OK);
|
||||
res |= (storage_common_remove(fs_api, EXT_PATH("u2f/cnt.u2f")) == FSE_OK);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
if(archive_is_favorite("/app:u2f/U2F Token")) {
|
||||
|
||||
@@ -450,16 +450,14 @@ void archive_favorites_move_mode(ArchiveBrowserView* browser, bool active) {
|
||||
}
|
||||
|
||||
static bool archive_is_dir_exists(FuriString* path) {
|
||||
if(furi_string_equal(path, STORAGE_ANY_PATH_PREFIX)) {
|
||||
return true;
|
||||
}
|
||||
bool state = false;
|
||||
FileInfo file_info;
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
if(storage_common_stat(storage, furi_string_get_cstr(path), &file_info) == FSE_OK) {
|
||||
if(file_info_is_dir(&file_info)) {
|
||||
state = true;
|
||||
}
|
||||
|
||||
if(furi_string_equal(path, STORAGE_EXT_PATH_PREFIX)) {
|
||||
state = storage_sd_status(storage) == FSE_OK;
|
||||
} else if(storage_common_stat(storage, furi_string_get_cstr(path), &file_info) == FSE_OK) {
|
||||
state = file_info_is_dir(&file_info);
|
||||
}
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
return state;
|
||||
|
||||
@@ -9,17 +9,17 @@
|
||||
|
||||
static const char* tab_default_paths[] = {
|
||||
[ArchiveTabFavorites] = "/app:favorites",
|
||||
[ArchiveTabIButton] = ANY_PATH("ibutton"),
|
||||
[ArchiveTabNFC] = ANY_PATH("nfc"),
|
||||
[ArchiveTabSubGhz] = ANY_PATH("subghz"),
|
||||
[ArchiveTabIButton] = EXT_PATH("ibutton"),
|
||||
[ArchiveTabNFC] = EXT_PATH("nfc"),
|
||||
[ArchiveTabSubGhz] = EXT_PATH("subghz"),
|
||||
[ArchiveTabSubGhzRemote] = EXT_PATH("subghz_remote"),
|
||||
[ArchiveTabLFRFID] = ANY_PATH("lfrfid"),
|
||||
[ArchiveTabInfrared] = ANY_PATH("infrared"),
|
||||
[ArchiveTabBadUsb] = ANY_PATH("badusb"),
|
||||
[ArchiveTabLFRFID] = EXT_PATH("lfrfid"),
|
||||
[ArchiveTabInfrared] = EXT_PATH("infrared"),
|
||||
[ArchiveTabBadUsb] = EXT_PATH("badusb"),
|
||||
[ArchiveTabU2f] = "/app:u2f",
|
||||
[ArchiveTabApplications] = ANY_PATH("apps"),
|
||||
[ArchiveTabApplications] = EXT_PATH("apps"),
|
||||
[ArchiveTabInternal] = STORAGE_INT_PATH_PREFIX,
|
||||
[ArchiveTabBrowser] = STORAGE_ANY_PATH_PREFIX,
|
||||
[ArchiveTabBrowser] = STORAGE_EXT_PATH_PREFIX,
|
||||
};
|
||||
|
||||
static const char* known_ext[] = {
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
#include <storage/storage.h>
|
||||
|
||||
#define ARCHIVE_FAV_PATH ANY_PATH("favorites.txt")
|
||||
#define ARCHIVE_FAV_TEMP_PATH ANY_PATH("favorites.tmp")
|
||||
#define ARCHIVE_FAV_PATH EXT_PATH("favorites.txt")
|
||||
#define ARCHIVE_FAV_TEMP_PATH EXT_PATH("favorites.tmp")
|
||||
|
||||
uint16_t archive_favorites_count(void* context);
|
||||
bool archive_favorites_read(void* context);
|
||||
|
||||
@@ -15,7 +15,7 @@ void archive_set_file_type(ArchiveFile_t* file, const char* path, bool is_folder
|
||||
} else {
|
||||
for(size_t i = 0; i < COUNT_OF(known_ext); i++) {
|
||||
if((known_ext[i][0] == '?') || (known_ext[i][0] == '*')) continue;
|
||||
if(furi_string_end_with(file->path, known_ext[i])) {
|
||||
if(furi_string_end_withi(file->path, known_ext[i])) {
|
||||
if((i == ArchiveFileTypeBadUsb) || (i == ArchiveFileTypeSubGhzRemote)) {
|
||||
if(furi_string_search(
|
||||
file->path, archive_get_default_path(ArchiveTabBadUsb)) == 0) {
|
||||
|
||||
@@ -112,8 +112,6 @@ BadUsbApp* bad_usb_app_alloc(char* arg) {
|
||||
app->dialogs = furi_record_open(RECORD_DIALOGS);
|
||||
|
||||
app->view_dispatcher = view_dispatcher_alloc();
|
||||
view_dispatcher_enable_queue(app->view_dispatcher);
|
||||
|
||||
app->scene_manager = scene_manager_alloc(&bad_usb_scene_handlers, app);
|
||||
|
||||
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
|
||||
|
||||
|
Before Width: | Height: | Size: 576 B After Width: | Height: | Size: 96 B |
@@ -32,7 +32,6 @@ GpioApp* gpio_app_alloc(void) {
|
||||
|
||||
app->view_dispatcher = view_dispatcher_alloc();
|
||||
app->scene_manager = scene_manager_alloc(&gpio_scene_handlers, app);
|
||||
view_dispatcher_enable_queue(app->view_dispatcher);
|
||||
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
|
||||
|
||||
view_dispatcher_set_custom_event_callback(
|
||||
|
||||
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 91 B |
@@ -85,7 +85,6 @@ iButton* ibutton_alloc(void) {
|
||||
ibutton->scene_manager = scene_manager_alloc(&ibutton_scene_handlers, ibutton);
|
||||
|
||||
ibutton->view_dispatcher = view_dispatcher_alloc();
|
||||
view_dispatcher_enable_queue(ibutton->view_dispatcher);
|
||||
view_dispatcher_set_event_callback_context(ibutton->view_dispatcher, ibutton);
|
||||
view_dispatcher_set_custom_event_callback(
|
||||
ibutton->view_dispatcher, ibutton_custom_event_callback);
|
||||
|
||||
@@ -143,7 +143,7 @@ void ibutton_cli_write(Cli* cli, FuriString* args) {
|
||||
}
|
||||
|
||||
if(!(ibutton_protocols_get_features(protocols, ibutton_key_get_protocol_id(key)) &
|
||||
iButtonProtocolFeatureWriteBlank)) {
|
||||
iButtonProtocolFeatureWriteId)) {
|
||||
ibutton_cli_print_usage();
|
||||
break;
|
||||
}
|
||||
@@ -152,7 +152,7 @@ void ibutton_cli_write(Cli* cli, FuriString* args) {
|
||||
ibutton_cli_print_key(protocols, key);
|
||||
printf("Press Ctrl+C to abort\r\n");
|
||||
|
||||
ibutton_worker_write_blank_start(worker, key);
|
||||
ibutton_worker_write_id_start(worker, key);
|
||||
while(true) {
|
||||
uint32_t flags = furi_event_flag_wait(
|
||||
write_context.event, EVENT_FLAG_IBUTTON_COMPLETE, FuriFlagWaitAny, 100);
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
#include "ibutton_custom_event.h"
|
||||
#include "scenes/ibutton_scene.h"
|
||||
|
||||
#define IBUTTON_APP_FOLDER ANY_PATH("ibutton")
|
||||
#define IBUTTON_APP_FOLDER EXT_PATH("ibutton")
|
||||
#define IBUTTON_APP_FILENAME_PREFIX "iBtn"
|
||||
#define IBUTTON_APP_FILENAME_EXTENSION ".ibtn"
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
|
||||
typedef enum {
|
||||
iButtonWriteModeInvalid,
|
||||
iButtonWriteModeBlank,
|
||||
iButtonWriteModeId,
|
||||
iButtonWriteModeCopy,
|
||||
} iButtonWriteMode;
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 304 B After Width: | Height: | Size: 96 B |
@@ -5,7 +5,7 @@ typedef enum {
|
||||
SubmenuIndexSave,
|
||||
SubmenuIndexEmulate,
|
||||
SubmenuIndexViewData,
|
||||
SubmenuIndexWriteBlank,
|
||||
SubmenuIndexWriteId,
|
||||
SubmenuIndexWriteCopy,
|
||||
} SubmenuIndex;
|
||||
|
||||
@@ -30,11 +30,11 @@ void ibutton_scene_read_key_menu_on_enter(void* context) {
|
||||
ibutton_scene_read_key_menu_submenu_callback,
|
||||
ibutton);
|
||||
|
||||
if(features & iButtonProtocolFeatureWriteBlank) {
|
||||
if(features & iButtonProtocolFeatureWriteId) {
|
||||
submenu_add_item(
|
||||
submenu,
|
||||
"Write ID",
|
||||
SubmenuIndexWriteBlank,
|
||||
SubmenuIndexWriteId,
|
||||
ibutton_scene_read_key_menu_submenu_callback,
|
||||
ibutton);
|
||||
}
|
||||
@@ -78,8 +78,8 @@ bool ibutton_scene_read_key_menu_on_event(void* context, SceneManagerEvent event
|
||||
dolphin_deed(DolphinDeedIbuttonEmulate);
|
||||
} else if(event.event == SubmenuIndexViewData) {
|
||||
scene_manager_next_scene(scene_manager, iButtonSceneViewData);
|
||||
} else if(event.event == SubmenuIndexWriteBlank) {
|
||||
ibutton->write_mode = iButtonWriteModeBlank;
|
||||
} else if(event.event == SubmenuIndexWriteId) {
|
||||
ibutton->write_mode = iButtonWriteModeId;
|
||||
scene_manager_next_scene(scene_manager, iButtonSceneWrite);
|
||||
} else if(event.event == SubmenuIndexWriteCopy) {
|
||||
ibutton->write_mode = iButtonWriteModeCopy;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
enum SubmenuIndex {
|
||||
SubmenuIndexEmulate,
|
||||
SubmenuIndexWriteBlank,
|
||||
SubmenuIndexWriteId,
|
||||
SubmenuIndexWriteCopy,
|
||||
SubmenuIndexEdit,
|
||||
SubmenuIndexRename,
|
||||
@@ -20,9 +20,9 @@ void ibutton_scene_saved_key_menu_on_enter(void* context) {
|
||||
|
||||
submenu_add_item(submenu, "Emulate", SubmenuIndexEmulate, ibutton_submenu_callback, ibutton);
|
||||
|
||||
if(features & iButtonProtocolFeatureWriteBlank) {
|
||||
if(features & iButtonProtocolFeatureWriteId) {
|
||||
submenu_add_item(
|
||||
submenu, "Write ID", SubmenuIndexWriteBlank, ibutton_submenu_callback, ibutton);
|
||||
submenu, "Write ID", SubmenuIndexWriteId, ibutton_submenu_callback, ibutton);
|
||||
}
|
||||
|
||||
if(features & iButtonProtocolFeatureWriteCopy) {
|
||||
@@ -55,8 +55,8 @@ bool ibutton_scene_saved_key_menu_on_event(void* context, SceneManagerEvent even
|
||||
if(event.event == SubmenuIndexEmulate) {
|
||||
scene_manager_next_scene(scene_manager, iButtonSceneEmulate);
|
||||
dolphin_deed(DolphinDeedIbuttonEmulate);
|
||||
} else if(event.event == SubmenuIndexWriteBlank) {
|
||||
ibutton->write_mode = iButtonWriteModeBlank;
|
||||
} else if(event.event == SubmenuIndexWriteId) {
|
||||
ibutton->write_mode = iButtonWriteModeId;
|
||||
scene_manager_next_scene(scene_manager, iButtonSceneWrite);
|
||||
} else if(event.event == SubmenuIndexWriteCopy) {
|
||||
ibutton->write_mode = iButtonWriteModeCopy;
|
||||
|
||||
@@ -51,9 +51,9 @@ void ibutton_scene_write_on_enter(void* context) {
|
||||
|
||||
ibutton_worker_write_set_callback(worker, ibutton_scene_write_callback, ibutton);
|
||||
|
||||
if(ibutton->write_mode == iButtonWriteModeBlank) {
|
||||
if(ibutton->write_mode == iButtonWriteModeId) {
|
||||
furi_string_set(tmp, "Writing ID");
|
||||
ibutton_worker_write_blank_start(worker, key);
|
||||
ibutton_worker_write_id_start(worker, key);
|
||||
|
||||
} else if(ibutton->write_mode == iButtonWriteModeCopy) {
|
||||
furi_string_set(tmp, "Full Writing");
|
||||
|
||||
|
Before Width: | Height: | Size: 305 B After Width: | Height: | Size: 96 B |
@@ -150,7 +150,6 @@ static InfraredApp* infrared_alloc(void) {
|
||||
infrared->gui = furi_record_open(RECORD_GUI);
|
||||
|
||||
ViewDispatcher* view_dispatcher = infrared->view_dispatcher;
|
||||
view_dispatcher_enable_queue(view_dispatcher);
|
||||
view_dispatcher_set_event_callback_context(view_dispatcher, infrared);
|
||||
view_dispatcher_set_custom_event_callback(view_dispatcher, infrared_custom_event_callback);
|
||||
view_dispatcher_set_navigation_event_callback(view_dispatcher, infrared_back_event_callback);
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
#define INFRARED_MAX_BUTTON_NAME_LENGTH 22
|
||||
#define INFRARED_MAX_REMOTE_NAME_LENGTH 22
|
||||
|
||||
#define INFRARED_APP_FOLDER ANY_PATH("infrared")
|
||||
#define INFRARED_APP_FOLDER EXT_PATH("infrared")
|
||||
#define INFRARED_APP_EXTENSION ".ir"
|
||||
|
||||
#define INFRARED_DEFAULT_REMOTE_NAME "Remote"
|
||||
|
||||
|
Before Width: | Height: | Size: 308 B After Width: | Height: | Size: 95 B |
@@ -77,7 +77,6 @@ static LfRfid* lfrfid_alloc(void) {
|
||||
|
||||
lfrfid->view_dispatcher = view_dispatcher_alloc();
|
||||
lfrfid->scene_manager = scene_manager_alloc(&lfrfid_scene_handlers, lfrfid);
|
||||
view_dispatcher_enable_queue(lfrfid->view_dispatcher);
|
||||
view_dispatcher_set_event_callback_context(lfrfid->view_dispatcher, lfrfid);
|
||||
view_dispatcher_set_custom_event_callback(
|
||||
lfrfid->view_dispatcher, lfrfid_debug_custom_event_callback);
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
#define LFRFID_KEY_NAME_SIZE 22
|
||||
#define LFRFID_TEXT_STORE_SIZE 40
|
||||
|
||||
#define LFRFID_APP_FOLDER ANY_PATH("lfrfid")
|
||||
#define LFRFID_APP_FOLDER EXT_PATH("lfrfid")
|
||||
#define LFRFID_SD_FOLDER EXT_PATH("lfrfid")
|
||||
#define LFRFID_APP_FILENAME_PREFIX "RFID"
|
||||
#define LFRFID_APP_FILENAME_EXTENSION ".rfid"
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include <nfc/protocols/mf_classic/mf_classic.h>
|
||||
#include <furi/furi.h>
|
||||
|
||||
#define NFC_APP_FOLDER ANY_PATH("nfc")
|
||||
#define NFC_APP_FOLDER EXT_PATH("nfc")
|
||||
#define NFC_APP_MF_CLASSIC_DICT_USER_PATH (NFC_APP_FOLDER "/assets/mf_classic_dict_user.nfc")
|
||||
|
||||
struct MfUserDict {
|
||||
|
||||
85
applications/main/nfc/helpers/nfc_detected_protocols.c
Normal file
@@ -0,0 +1,85 @@
|
||||
#include "nfc_detected_protocols.h"
|
||||
|
||||
#include <furi.h>
|
||||
|
||||
struct NfcDetectedProtocols {
|
||||
uint32_t protocols_detected_num;
|
||||
NfcProtocol protocols_detected[NfcProtocolNum];
|
||||
uint32_t selected_idx;
|
||||
};
|
||||
|
||||
NfcDetectedProtocols* nfc_detected_protocols_alloc(void) {
|
||||
NfcDetectedProtocols* instance = malloc(sizeof(NfcDetectedProtocols));
|
||||
|
||||
instance->protocols_detected_num = 0;
|
||||
instance->selected_idx = 0;
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void nfc_detected_protocols_free(NfcDetectedProtocols* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void nfc_detected_protocols_reset(NfcDetectedProtocols* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
instance->protocols_detected_num = 0;
|
||||
memset(instance->protocols_detected, 0, sizeof(instance->protocols_detected));
|
||||
instance->selected_idx = 0;
|
||||
}
|
||||
|
||||
void nfc_detected_protocols_select(NfcDetectedProtocols* instance, uint32_t idx) {
|
||||
furi_assert(instance);
|
||||
|
||||
instance->selected_idx = idx;
|
||||
}
|
||||
|
||||
void nfc_detected_protocols_set(
|
||||
NfcDetectedProtocols* instance,
|
||||
const NfcProtocol* types,
|
||||
uint32_t count) {
|
||||
furi_assert(instance);
|
||||
furi_assert(types);
|
||||
furi_assert(count < NfcProtocolNum);
|
||||
|
||||
memcpy(instance->protocols_detected, types, count);
|
||||
instance->protocols_detected_num = count;
|
||||
instance->selected_idx = 0;
|
||||
}
|
||||
|
||||
uint32_t nfc_detected_protocols_get_num(NfcDetectedProtocols* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
return instance->protocols_detected_num;
|
||||
}
|
||||
|
||||
NfcProtocol nfc_detected_protocols_get_protocol(NfcDetectedProtocols* instance, uint32_t idx) {
|
||||
furi_assert(instance);
|
||||
furi_assert(idx < instance->protocols_detected_num);
|
||||
|
||||
return instance->protocols_detected[idx];
|
||||
}
|
||||
|
||||
void nfc_detected_protocols_fill_all_protocols(NfcDetectedProtocols* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
instance->protocols_detected_num = NfcProtocolNum;
|
||||
for(uint32_t i = 0; i < NfcProtocolNum; i++) {
|
||||
instance->protocols_detected[i] = i;
|
||||
}
|
||||
}
|
||||
|
||||
NfcProtocol nfc_detected_protocols_get_selected(NfcDetectedProtocols* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
return instance->protocols_detected[instance->selected_idx];
|
||||
}
|
||||
|
||||
uint32_t nfc_detected_protocols_get_selected_idx(NfcDetectedProtocols* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
return instance->selected_idx;
|
||||
}
|
||||