mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-08-11 02:13:41 +00:00
Compare commits
476 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b048b8f4f1 | |||
| 9067e007b6 | |||
| 367eaf54c0 | |||
| de30cbfe1b | |||
| a4d91f12ef | |||
| c4f32ac436 | |||
| efe214167d | |||
| 483a100100 | |||
| 8561ff32b3 | |||
| 87eb8e5863 | |||
| d11669f711 | |||
| 7513cd46fc | |||
| d671455f79 | |||
| 42122a739c | |||
| 03fe508aae | |||
| e3eafbeb66 | |||
| e3b3fa2fc8 | |||
| 5f0ae22f66 | |||
| f9dd28f31d | |||
| b99cec5111 | |||
| fe2ba80a10 | |||
| b5d84fcc4d | |||
| 7481aefe9f | |||
| 6b641c8a5d | |||
| 2ad34bbc09 | |||
| 9c74a1f392 | |||
| abadf35e06 | |||
| 288e87537a | |||
| d25899de10 | |||
| 8a16ec829c | |||
| f6d6e3be4c | |||
| c16bac24c7 | |||
| 4e6a2cc934 | |||
| cafe62564e | |||
| 960e5b8329 | |||
| f5a5e8e5e8 | |||
| d8a1e2530a | |||
| b84a424875 | |||
| 62d0e598d4 | |||
| a93f2ff583 | |||
| e2586ceb22 | |||
| 5a2b0cdc2a | |||
| f852900cd0 | |||
| 9ab367f2e0 | |||
| c7a0846645 | |||
| 569bcaad22 | |||
| 6b5955e0b5 | |||
| 9042d1c433 | |||
| 378b2808ba | |||
| 5b363b79aa | |||
| 0d51787099 | |||
| 8f5b25b10d | |||
| 4e56d0f420 | |||
| 2bb3b415d1 | |||
| 6e0394fb6b | |||
| da21ea7866 | |||
| 2c434946cd | |||
| f3aee44e3b | |||
| 165c190d92 | |||
| 8f9246c1ea | |||
| f1d52ae78f | |||
| 865bdee9b3 | |||
| 314e910cbc | |||
| ec6c7eff86 | |||
| 95b2e9fbc3 | |||
| 52ff442fcc | |||
| 95d246a020 | |||
| c34a6940ca | |||
| 33330e4c34 | |||
| 66f1835232 | |||
| 4dfbf666d0 | |||
| 245a36459f | |||
| 590f19062b | |||
| f38ef4ebc8 | |||
| 900d2f2482 | |||
| 5aff5ac277 | |||
| cf4d245964 | |||
| c7f7173958 | |||
| 02c0f67a8d | |||
| 2bacbc80a8 | |||
| ab986a0378 | |||
| c9f310f548 | |||
| 1dafdd5a26 | |||
| fc5beb0c09 | |||
| 3f10663bc9 | |||
| 396f8aef41 | |||
| c095a88cb3 | |||
| 900c80549f | |||
| 4dce613172 | |||
| cf44076a34 | |||
| dd006b4830 | |||
| fc18992442 | |||
| ef2ae9874b | |||
| 5deeaed5f0 | |||
| d5f94398ce | |||
| fba15c48e5 | |||
| 888634ce35 | |||
| 03131c8a0c | |||
| 605c9efa0c | |||
| aee54e5b2d | |||
| e33e697cca | |||
| d7d27d99e7 | |||
| 78d3480b68 | |||
| 70b42f4a56 | |||
| 5c719645d6 | |||
| b0eb776bdd | |||
| ce87161f9b | |||
| 8883f2decc | |||
| e06d23ea3e | |||
| 0da90c7c5e | |||
| e7572e5d41 | |||
| 50f1e51d7e | |||
| 55a15958ec | |||
| 395e20d17b | |||
| 9bf1f2175e | |||
| ec57f99a2c | |||
| 5c2d1d3dfb | |||
| 52dd2ea2bb | |||
| b273165a0c | |||
| 5aecd4097e | |||
| 2c2959df30 | |||
| 9a3c2cc6ac | |||
| c91a6db165 | |||
| bf5ea0a004 | |||
| bf166bb2fc | |||
| 52239d320a | |||
| 561143cfb0 | |||
| 678ee2963e | |||
| bae3854d27 | |||
| 0645f1897a | |||
| da0a5c3e73 | |||
| 22932e636d | |||
| 2b7302e659 | |||
| 60ce89021a | |||
| 25556428a7 | |||
| 584587d814 | |||
| a0179117b1 | |||
| ad577a1c66 | |||
| b9958a689b | |||
| 3d8bc308ee | |||
| 9ca36394d0 | |||
| f489d43259 | |||
| 6b663894b0 | |||
| b8ca356ce3 | |||
| 3a54b112ec | |||
| b013e68b17 | |||
| b2bf0f2459 | |||
| 56adca3bf6 | |||
| cd7c412aa4 | |||
| 16c4c28584 | |||
| 10d17fa7ce | |||
| e63f54c0a2 | |||
| b09efb4d3c | |||
| bfbbacd43c | |||
| 065d01b591 | |||
| b197a4b1de | |||
| 85eaa9d800 | |||
| 1ad092f341 | |||
| 0fe51fa7e3 | |||
| 4cc2ba690b | |||
| abee022000 | |||
| 430c8e5e79 | |||
| 7ea3eec016 | |||
| eb524f7110 | |||
| 3413d71bb3 | |||
| c64dddf5cc | |||
| 3d56bccd49 | |||
| 705e4f125f | |||
| 363b27efe8 | |||
| f8ed7489c9 | |||
| 0685fb7a9f | |||
| 6498f5a1b2 | |||
| 77f747aab2 | |||
| 819b4d0fed | |||
| 15df51f976 | |||
| e9e78609b7 | |||
| 785bd06938 | |||
| 300a4b03c6 | |||
| eaa07f8369 | |||
| c31a978e6f | |||
| 266db4b158 | |||
| 290bf543a1 | |||
| e17f4c4ab4 | |||
| 83a4580353 | |||
| 00f40bf171 | |||
| 794daf5257 | |||
| 95eafeb812 | |||
| 94d46036f8 | |||
| 68000f0b94 | |||
| eff68693ad | |||
| 2f468fd37c | |||
| 39424632bb | |||
| cd30022b61 | |||
| be872003ec | |||
| 9e7758718c | |||
| 89b878d3bb | |||
| 56d965f3ae | |||
| fe9f0c228f | |||
| 6562e596e8 | |||
| c27192bdbb | |||
| 927e3b11e7 | |||
| a44a00acf0 | |||
| 6739b43914 | |||
| b306a0d490 | |||
| c6130c6e8d | |||
| 97f2ca42f1 | |||
| e2b642ae25 | |||
| 106d9b1207 | |||
| c8ae419e0b | |||
| 2319dcd0d6 | |||
| 44d9ce00cd | |||
| 5cc5f8faa6 | |||
| 3a0ca480b0 | |||
| 7a2b432bfa | |||
| 86d7b345d0 | |||
| 877ede5f86 | |||
| 19eb1c40ab | |||
| 9c03a18893 | |||
| cd504eb4a7 | |||
| dc976c7f38 | |||
| 266d90a600 | |||
| 2884bb9ab7 | |||
| 133b2d2065 | |||
| 2f2671f76c | |||
| f5c2c78733 | |||
| e4ab7227fc | |||
| 93799bde6d | |||
| 3d979a613a | |||
| ee97b51eb3 | |||
| 70ac1a03a1 | |||
| 2261e53981 | |||
| f86d3e51f1 | |||
| f71f19e719 | |||
| c53adfc765 | |||
| 7e8ad83537 | |||
| 472571b1ed | |||
| 106e56abc3 | |||
| 4129d57c09 | |||
| caac5e1041 | |||
| 6b02ba6e5d | |||
| c01597baf2 | |||
| 43a5163b77 | |||
| c46cc431c9 | |||
| a4d23768c1 | |||
| 344aa0c741 | |||
| fd7637187a | |||
| 674b865ec0 | |||
| 00c9c6b40e | |||
| 113cbc42c9 | |||
| 454b8776b6 | |||
| efdf043d62 | |||
| 0f9d9e589d | |||
| 3b57672dda | |||
| b739dd883c | |||
| bf18851b6b | |||
| 81afec9ea4 | |||
| 60a5222058 | |||
| 7041d507ca | |||
| d3373734e9 | |||
| 200831ad67 | |||
| dafe3b7641 | |||
| 63634407b6 | |||
| 9f06ccf9ae | |||
| 6782e12a15 | |||
| cbbd8103d3 | |||
| e88ab44f49 | |||
| 545dead1ca | |||
| e3b2a65b39 | |||
| 2f4d222403 | |||
| 54d99945f2 | |||
| 65a3c797c6 | |||
| 6a81a981aa | |||
| 193ac73b34 | |||
| 477d035dce | |||
| 1d8f53b49e | |||
| db324a5225 | |||
| b2331bf818 | |||
| 4b18ac42e9 | |||
| 41dd0c4674 | |||
| 371b6b5079 | |||
| 23cabb8b8a | |||
| b3edaf3cd0 | |||
| 9afd3a9dca | |||
| d80f41e9d7 | |||
| 46d87d95ae | |||
| 7626dff1ef | |||
| f101680521 | |||
| 325f295ee8 | |||
| b15bb59678 | |||
| 776c9bc7c9 | |||
| b9ebefc7c7 | |||
| 70ace4be8f | |||
| fe826d5d98 | |||
| 4dda7cfaa5 | |||
| e6d4081c06 | |||
| 4f738adc70 | |||
| d516191ccb | |||
| 29bdc539a3 | |||
| 29bba4d0ea | |||
| a6f886ad0a | |||
| 2d2d9223f2 | |||
| 6e33007cb6 | |||
| 3f7b59f27e | |||
| 375d1ad54e | |||
| 6b05878532 | |||
| 3983749f11 | |||
| 28e2f770e5 | |||
| 4c5275247e | |||
| a0185d28eb | |||
| 2500df310f | |||
| 54f9ff116b | |||
| 47f231ad63 | |||
| f90d3fabce | |||
| 4e276cdc71 | |||
| 0eb03373b1 | |||
| d5ea0f0369 | |||
| 22cc311447 | |||
| 4fbba205ad | |||
| 7b91103118 | |||
| c0aa4a1738 | |||
| e26f77ee77 | |||
| f53262783a | |||
| b456c18008 | |||
| fa4b74fd6f | |||
| 18bc2cf11c | |||
| 20f28c8331 | |||
| ea38a0fe48 | |||
| fb2e576b34 | |||
| 1d79e30d4b | |||
| be372e12bc | |||
| 00853f526a | |||
| 37ca7a601c | |||
| b50d18eafc | |||
| 1070d951e6 | |||
| a980fe21ac | |||
| 9e96715176 | |||
| fecfe8b8fc | |||
| dfdd52c667 | |||
| 695e6d19f4 | |||
| 6e9ecf8e7b | |||
| fd158e873a | |||
| f90bd44617 | |||
| 2602c417be | |||
| aa1264cf91 | |||
| c79abccffd | |||
| d3c9dc1f10 | |||
| 3c8335c942 | |||
| ac31f4781c | |||
| c80cae3638 | |||
| a1d7cf2b86 | |||
| 6f6d863a14 | |||
| 43a1bc0445 | |||
| 0d02f431c7 | |||
| 322b06d670 | |||
| db187301ab | |||
| 84cb32ee14 | |||
| 61bd696b69 | |||
| db65ae192a | |||
| 7553dafffc | |||
| cfdd1a4c22 | |||
| d4342e01a4 | |||
| 4342f5c0ee | |||
| bd781ce37b | |||
| c2e05dea48 | |||
| de3212cba5 | |||
| 9aff3f4121 | |||
| 6b6a00d511 | |||
| 806219f46e | |||
| a153cbf30b | |||
| 584af02dba | |||
| f344a1ca40 | |||
| 511675c9a1 | |||
| 7bbd608c90 | |||
| f941ae871a | |||
| eff214cc79 | |||
| 319ed3e7b4 | |||
| 288f6bd517 | |||
| 809abb6842 | |||
| 31082c63af | |||
| ff14008b43 | |||
| 1377516dce | |||
| 4162820409 | |||
| 4bbe1175c1 | |||
| ecd1a217d7 | |||
| 335cace137 | |||
| dbd708a1c7 | |||
| 4d842b5fae | |||
| e24f1b131d | |||
| 3f2c7351f1 | |||
| afc75f6cda | |||
| 69b17faddf | |||
| 2c118ae584 | |||
| 75c34747cb | |||
| 7754c0f37f | |||
| 37cc35d3c6 | |||
| 8a7aa9c0f8 | |||
| 22d6e34cd6 | |||
| 2229381ce3 | |||
| 4a63bdd7a0 | |||
| 21773cc3c3 | |||
| 4b000c8da6 | |||
| 20c64c98bd | |||
| 139ade0670 | |||
| c5b7326d4b | |||
| a60169c7f1 | |||
| 0ce6ea8318 | |||
| 4ecc9d04fe | |||
| d73db884f0 | |||
| ff2f27e297 | |||
| 443ffd8ad3 | |||
| 403c362702 | |||
| 6aa29e265b | |||
| 40cf2b3f8c | |||
| 4aa5fc1fbe | |||
| 717d615f4f | |||
| dce4e91e41 | |||
| dc2f3260f3 | |||
| 2d85e73f0d | |||
| 6ee7270db7 | |||
| b4112f0c04 | |||
| e920932886 | |||
| 081b11ad41 | |||
| adc23315a2 | |||
| 084b88564b | |||
| 24e9504688 | |||
| c670ca59a8 | |||
| 2ca8ea6342 | |||
| eb50b790c2 | |||
| 1df318355b | |||
| a17995fd2b | |||
| ee472e1ed2 | |||
| e933c8b498 | |||
| 18bebbfb6d | |||
| 52c3760e90 | |||
| b6e498a6d3 | |||
| ddf7f7ccb5 | |||
| fcdccdea85 | |||
| 5e55444f19 | |||
| 73f7f84718 | |||
| 7ad4ad99dd | |||
| 200f10397b | |||
| 9b352f45a2 | |||
| 51c445054e | |||
| 02586101e6 | |||
| b66d8b1aa1 | |||
| 8dc3851b55 | |||
| aef7c2be93 | |||
| 4e38dc4d3e | |||
| 520ad97f78 | |||
| 5bd208bca1 | |||
| a65ef3ce2e | |||
| b2bb37af74 | |||
| 54ad82a423 | |||
| e463405c77 | |||
| c413f925a9 | |||
| 9e1eb31150 | |||
| 7eb08f34cb | |||
| 2de1f8e6bf | |||
| 55db0e8c87 | |||
| dfa35b4290 | |||
| 0124051c4f | |||
| 65f301399f | |||
| 97889494cf | |||
| 32b35b1507 | |||
| fccff0484b | |||
| d94afe364b | |||
| 2e4db3dd27 | |||
| 97d3edbde7 | |||
| 4c18b80e42 | |||
| 01034af077 | |||
| 57ce978bab | |||
| 854ac514cb | |||
| b47a5fe0c8 | |||
| af362600ef | |||
| bad57d1391 | |||
| ef28e63a8c |
+6
-7
@@ -1,14 +1,13 @@
|
||||
---
|
||||
BasedOnStyle: Chromium
|
||||
IndentWidth: '4'
|
||||
SortIncludes: 'false'
|
||||
AllowAllArgumentsOnNextLine: 'true'
|
||||
IndentWidth: 4
|
||||
SortIncludes: false
|
||||
AllowAllArgumentsOnNextLine: true
|
||||
ColumnLimit: 0
|
||||
Cpp11BracedListStyle: 'true'
|
||||
AllowShortLoopsOnASingleLine: 'true'
|
||||
AllowShortIfStatementsOnASingleLine: 'true'
|
||||
Cpp11BracedListStyle: true
|
||||
AllowShortLoopsOnASingleLine: true
|
||||
AllowShortIfStatementsOnASingleLine: true
|
||||
SpacesInLineCommentPrefix:
|
||||
Minimum: 1
|
||||
Maximum: 1
|
||||
|
||||
...
|
||||
|
||||
+2
-1
@@ -1 +1,2 @@
|
||||
build
|
||||
*
|
||||
!firmware/tools/docker-entrypoint.sh
|
||||
@@ -8,6 +8,12 @@ body:
|
||||
value: |
|
||||
Thank you for taking the time to fill out an issue, this template is meant for any issues related to the Mayhem firmware.
|
||||
Please try the latest nightly release before submitting this. You can find the latest nightly version here: https://github.com/portapack-mayhem/mayhem-firmware/releases
|
||||
> [!NOTE]
|
||||
For hardware related issue, please open as a hardware issue instead.
|
||||
For feature requests, please open as a feature request instead.
|
||||
If you have questions, please join our Discord server to ask.
|
||||
For your information, they are options you can choose when you open an issue.
|
||||
Unrelated issues will be closed without notice.
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
name: Hardware / QC / QA / selling
|
||||
description: Issues that related with hardware, quality control, quality assurance, selling, etc.
|
||||
labels:
|
||||
- question
|
||||
- hardware problem
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thank you for taking the time to fill out an issue.
|
||||
|
||||
We would like to inform you that we can't provide much help regarding hardware/QC/QA/selling issues because we are not controlling various vendors/manufacturers, and also we are not profiting from you buying your device.
|
||||
|
||||
You are still welcome to ask, but please be aware that for this kind of issue, ask the seller would be the best option.
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Describe the problem
|
||||
description: "A clear and concise description of what the problem is."
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: hardware-info
|
||||
attributes:
|
||||
label: Hardware information
|
||||
description: "Please provide the hardware information here."
|
||||
placeholder: |
|
||||
1. HackrfRF revision
|
||||
2. Portapack revision
|
||||
3. (if it's audio/mic/display/touchscreen/buttons related) audio chip/ CPLD/ screen info...
|
||||
4. (if hardware is visually damaged) please provide a photo
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: anything-else
|
||||
attributes:
|
||||
label: Anything else?
|
||||
description: Let us know if you have anything else to share.
|
||||
@@ -0,0 +1,91 @@
|
||||
<!--
|
||||
👋 Thank you for your contribution!
|
||||
⚠️ Before you start, please review our Contributing Guidelines:
|
||||
https://github.com/portapack-mayhem/mayhem-firmware/wiki/Contributing-Guidelines
|
||||
-->
|
||||
|
||||
## Brief description of what you did
|
||||
<!--
|
||||
Replace this line with a description of your changes. What problem does this PR solve? What does it add or fix?
|
||||
Don't forget to describe how maintainers can test your modifications!
|
||||
-->
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Proof that your changes work
|
||||
<!--
|
||||
1. PRs without proof will not be merged. Please provide evidence for every section below that applies to your change.
|
||||
2. Trusted contributors: If you have a track record of merged PRs that were properly tested and documented, maintainers may waive one or more of the proof sections below at their discretion. You are still encouraged to include whatever evidence you have, but sections may be marked `N/A — trusted contributor` with a short note.
|
||||
3. First-time contributors must provide full proof. See the Contributing Guidelines(https://github.com/portapack-mayhem/mayhem-firmware/wiki/Contributing-Guidelines) and Trusted Contributors (https://github.com/portapack-mayhem/mayhem-firmware/wiki/Trusted-Contributors) articles for details.
|
||||
-->
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### 🖥️ Proof it compiles
|
||||
<!--
|
||||
Attach a log snippet showing a successful build from the final firmware size/space summary in the build output. Example: `Space remaining in flash ROM: 19552 bytes ( 1.9 % )`.
|
||||
Paste log below.
|
||||
-->
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### 📱 Proof of testing on a real device
|
||||
<!--
|
||||
Attach photos, screenshots, or a short video of your changes running on actual PortaPack hardware. Emulator or simulator output is not a substitute for real-device testing.
|
||||
Paste photo / video / screenshot below
|
||||
-->
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### 📡 Proof against a real emitter/receiver (if applicable)
|
||||
<!--
|
||||
1. If your PR involves RX/TX, protocol decoding, signal generation, or any RF-related functionality, you **must** demonstrate it working against a real emitter or receiver. Include photos, videos, logs, or waterfall/spectrum screenshots.
|
||||
2. If this section does not apply to your PR, write `N/A` below and briefly explain why.
|
||||
3. Paste RF evidence, or write N/A with a short justification
|
||||
-->
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 📚 Wiki documentation commitment
|
||||
<!--
|
||||
By submitting this PR, you acknowledge that once it is merged you are responsible for creating or updating the corresponding page on the project wiki(https://github.com/portapack-mayhem/mayhem-firmware/wiki).
|
||||
Your wiki article should include:
|
||||
- A screenshot of the main app screen (strongly recommended)
|
||||
- A clear description of what the app does
|
||||
- An explanation of the controls and UI elements
|
||||
- Any known limitations, quirks, or caveats
|
||||
- Anything else a user should know to use the app effectively (dependencies, required hardware, file formats, etc.)
|
||||
- If you are modifying an existing app, make sure the wiki page reflects your changes.
|
||||
-->
|
||||
|
||||
|
||||
- [ ] I will (or already) create(d) wiki document for my newly added feature
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] Kept changes minimal and limited to necessary files
|
||||
- [ ] Verified functionality remains intact and code compiles
|
||||
- [ ] Attached proof that the code compiles successfully *(or marked N/A as a trusted contributor)*
|
||||
- [ ] Attached proof of testing on real PortaPack hardware *(or marked N/A as a trusted contributor)*
|
||||
- [ ] Attached proof of testing against a real emitter/receiver (if RF-related), or marked N/A with justification
|
||||
- [ ] I understand that by getting this PR merged, I am implicitly agreeing to create or update the corresponding wiki page (including a main-screen screenshot, description, controls, and limitations)
|
||||
- [ ] I own all rights to this code (i.e., all code contained in this PR), including compliant usage rights for third-party libraries, and I agree that this code is licensed under the license of this project (GPL-3.0).
|
||||
- [ ] If any third-party libraries are used, I confirm that their licenses comply with the requirements for contributing to this repository.
|
||||
- [ ] Reviewed the [Contributing Guidelines](https://github.com/portapack-mayhem/mayhem-firmware/wiki/Contributing-Guidelines)
|
||||
@@ -3,7 +3,7 @@ name: Check formatting
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
check_date:
|
||||
check_format:
|
||||
runs-on: ubuntu-latest
|
||||
name: Check formatting
|
||||
strategy:
|
||||
@@ -13,11 +13,20 @@ jobs:
|
||||
- "firmware/application"
|
||||
- "firmware/baseband"
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: print latest_commit
|
||||
run: echo ${{ github.sha }}
|
||||
- name: clang-format Check
|
||||
uses: jidicula/clang-format-action@v4.11.0
|
||||
with:
|
||||
check-path: ${{ matrix.path }}
|
||||
fallback-style: Chromium
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: clang-format check (repo pinned)
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
CF="./tools/clang-format.sh"
|
||||
chmod +x "$CF" || true
|
||||
|
||||
"$CF" --version
|
||||
|
||||
ROOT="${{ matrix.path }}"
|
||||
find "$ROOT" -type f \( \
|
||||
-name '*.c' -o -name '*.cpp' -o \
|
||||
-name '*.h' -o -name '*.hpp' \
|
||||
\) -print0 \
|
||||
| xargs -0 -r "$CF" --dry-run --Werror --style=file
|
||||
|
||||
@@ -3,7 +3,7 @@ name: Nightly Release
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 0 * * *"
|
||||
|
||||
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
@@ -21,27 +21,36 @@ jobs:
|
||||
id: should_run
|
||||
continue-on-error: true
|
||||
run: test -z $(git rev-list --after="24 hours" ${{ github.sha }}) && echo "should_run=false" >> $GITHUB_OUTPUT
|
||||
|
||||
build:
|
||||
needs: check_date
|
||||
if: ${{ needs.check_date.outputs.should_run != 'false' }}
|
||||
runs-on: ubuntu-latest
|
||||
name: build (${{ matrix.device_name }})
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- device: default
|
||||
device_name: HackRF
|
||||
cmake_args: ""
|
||||
artifact_suffix: "_hackrf"
|
||||
- device: portarf
|
||||
device_name: PortaRF
|
||||
cmake_args: "-DFLASH_MB_SIZE=2 -DFLASH_MB_LIMIT_SIZE=2"
|
||||
artifact_suffix: "_portarf"
|
||||
- device: hpro
|
||||
device_name: HackRF Pro
|
||||
cmake_args: "-DBOARD=PRALINE"
|
||||
artifact_suffix: "_hpro"
|
||||
steps:
|
||||
- name: Get current date
|
||||
id: date
|
||||
run: echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT
|
||||
- name: Get version date
|
||||
id: version_date
|
||||
run: echo "date=n_$(date +'%y%m%d')" >> $GITHUB_OUTPUT
|
||||
- name: Checkout
|
||||
- name: Checkout
|
||||
uses: actions/checkout@master
|
||||
with:
|
||||
fetch-depth: 0
|
||||
#ref: next
|
||||
# The branch, tag or SHA to checkout. When checking out the repository that
|
||||
# triggered a workflow, this defaults to the reference or SHA for that event.
|
||||
# Otherwise, uses the default branch.
|
||||
# https://github.com/actions/checkout
|
||||
# So scheduled runs will use the default branch (next) but its now possible to trigger a workflow from another branch
|
||||
submodules: true
|
||||
- name: Git Sumbodule Update
|
||||
run: |
|
||||
@@ -51,10 +60,10 @@ jobs:
|
||||
- name: Make build folder
|
||||
run: mkdir ${{ github.workspace }}/build
|
||||
- name: Run the Docker image
|
||||
run: docker run -e VERSION_STRING=${{ steps.version_date.outputs.date }} -i -v ${{ github.workspace }}:/havoc portapack-dev
|
||||
run: docker run -e VERSION_STRING=${{ steps.version_date.outputs.date }} -i -v ${{ github.workspace }}:/havoc portapack-dev ${{ matrix.cmake_args }}
|
||||
- name: Create Small SD Card ZIP - No World Map
|
||||
run: |
|
||||
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-mayhem-firmware.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version_date.outputs.date }}.bin && cp build/firmware/portapack-mayhem_OCI.ppfw.tar sdcard/FIRMWARE/mayhem_nightly_${{ steps.version_date.outputs.date }}_OCI.ppfw.tar && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && cp build/firmware/standalone/*/*.ppmp sdcard/APPS && cd sdcard && zip -r ../sdcard-no-map.zip . && cd ..
|
||||
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-mayhem-firmware.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version_date.outputs.date }}.bin && cp build/firmware/portapack-mayhem_OCI.ppfw.tar sdcard/FIRMWARE/mayhem_nightly_${{ steps.version_date.outputs.date }}${{ matrix.artifact_suffix }}_OCI.ppfw.tar && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && cp build/firmware/standalone/*/*.ppmp sdcard/APPS && cd sdcard && zip -r ../sdcard-no-map.zip . && cd ..
|
||||
- name: Download world map
|
||||
run: |
|
||||
wget https://github.com/portapack-mayhem/mayhem-firmware/releases/download/world_map/world_map.zip
|
||||
@@ -63,13 +72,55 @@ jobs:
|
||||
unzip world_map.zip -d sdcard/ADSB
|
||||
- name: Prepare Firmware ZIP
|
||||
run: |
|
||||
cp build/hackrf/firmware/hackrf_usb/hackrf_usb.dfu flashing/utils/hackrf_one_usb.dfu && cp build/hackrf/firmware/hackrf_usb/hackrf_usb.bin flashing/utils/hackrf_one_usb.bin
|
||||
mkdir -p flashing/firmware && cp build/hackrf/firmware/hackrf_usb/hackrf_usb.dfu flashing/firmware/hackrf_usb.dfu && cp build/hackrf/firmware/hackrf_usb/hackrf_usb.bin flashing/firmware/hackrf_usb.bin
|
||||
- name: Create Firmware ZIP
|
||||
run: |
|
||||
zip -j firmware.zip build/firmware/portapack-mayhem-firmware.bin && cd flashing && zip -r ../firmware.zip *
|
||||
- name: Create SD Card ZIP
|
||||
run: |
|
||||
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-mayhem-firmware.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version_date.outputs.date }}.bin && cp build/firmware/portapack-mayhem_OCI.ppfw.tar sdcard/FIRMWARE/mayhem_nightly_${{ steps.version_date.outputs.date }}_OCI.ppfw.tar && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && cp build/firmware/standalone/*/*.ppmp sdcard/APPS && cd sdcard && zip -r ../sdcard.zip . && cd ..
|
||||
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-mayhem-firmware.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version_date.outputs.date }}.bin && cp build/firmware/portapack-mayhem_OCI.ppfw.tar sdcard/FIRMWARE/mayhem_nightly_${{ steps.version_date.outputs.date }}${{ matrix.artifact_suffix }}_OCI.ppfw.tar && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && cp build/firmware/standalone/*/*.ppmp sdcard/APPS && cd sdcard && zip -r ../sdcard.zip . && cd ..
|
||||
- name: Upload firmware zip artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: firmware-${{ matrix.device }}
|
||||
path: ./firmware.zip
|
||||
- name: Upload SD card artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sdcard-${{ matrix.device }}
|
||||
path: ./sdcard.zip
|
||||
- name: Upload SD card no-map artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sdcard-no-map-${{ matrix.device }}
|
||||
path: ./sdcard-no-map.zip
|
||||
- name: Upload OCI tar artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: oci-tar-${{ matrix.device }}
|
||||
path: build/firmware/portapack-mayhem_OCI.ppfw.tar
|
||||
|
||||
release:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Get current date
|
||||
id: date
|
||||
run: echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT
|
||||
- name: Get version date
|
||||
id: version_date
|
||||
run: echo "date=n_$(date +'%y%m%d')" >> $GITHUB_OUTPUT
|
||||
- name: Checkout
|
||||
uses: actions/checkout@master
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: true
|
||||
- name: Download all artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: artifacts
|
||||
- name: Create changelog
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -80,14 +131,42 @@ jobs:
|
||||
echo EOF
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
id: changelog
|
||||
# Combine all 3 device firmware bins into a single zip with device selector bat
|
||||
- name: Create Combined Firmware ZIP
|
||||
run: |
|
||||
mkdir -p firmware_combined
|
||||
unzip artifacts/firmware-default/firmware.zip -d firmware_combined
|
||||
mv firmware_combined/portapack-mayhem-firmware.bin firmware_combined/firmware/firmware_hackrf.bin
|
||||
unzip -j artifacts/firmware-portarf/firmware.zip portapack-mayhem-firmware.bin -d firmware_combined/firmware
|
||||
mv firmware_combined/firmware/portapack-mayhem-firmware.bin firmware_combined/firmware/firmware_portarf.bin
|
||||
unzip -j artifacts/firmware-hpro/firmware.zip portapack-mayhem-firmware.bin -d firmware_combined/firmware
|
||||
mv firmware_combined/firmware/portapack-mayhem-firmware.bin firmware_combined/firmware/firmware_hpro.bin
|
||||
unzip -j artifacts/firmware-hpro/firmware.zip firmware/hackrf_usb.dfu firmware/hackrf_usb.bin -d firmware_combined_hpro
|
||||
mv firmware_combined_hpro/hackrf_usb.dfu firmware_combined/firmware/hackrf_hpro_usb.dfu
|
||||
mv firmware_combined_hpro/hackrf_usb.bin firmware_combined/firmware/hackrf_hpro_usb.bin
|
||||
cd firmware_combined && zip -r ../firmware_all_devices.zip . && cd ..
|
||||
- name: Prepare release assets
|
||||
run: |
|
||||
VER="${{ steps.version_date.outputs.date }}"
|
||||
mkdir -p release_assets
|
||||
# OCI tar assets
|
||||
cp artifacts/oci-tar-default/portapack-mayhem_OCI.ppfw.tar "release_assets/OCI_hackrf_mayhem_nightly_${VER}.ppfw.tar"
|
||||
cp artifacts/oci-tar-portarf/portapack-mayhem_OCI.ppfw.tar "release_assets/OCI_portarf_mayhem_nightly_${VER}.ppfw.tar"
|
||||
cp artifacts/oci-tar-hpro/portapack-mayhem_OCI.ppfw.tar "release_assets/OCI_hpro_mayhem_nightly_${VER}.ppfw.tar"
|
||||
# Combined firmware
|
||||
cp firmware_all_devices.zip "release_assets/FIRMWARE_mayhem_nightly_${VER}.zip"
|
||||
# SD card assets
|
||||
cp artifacts/sdcard-default/sdcard.zip "release_assets/COPY_TO_SDCARD_hackrf_mayhem_nightly_${VER}.zip"
|
||||
cp artifacts/sdcard-no-map-default/sdcard-no-map.zip "release_assets/COPY_TO_SDCARD_hackrf_mayhem_nightly_${VER}-no-world-map.zip"
|
||||
cp artifacts/sdcard-portarf/sdcard.zip "release_assets/COPY_TO_SDCARD_portarf_mayhem_nightly_${VER}.zip"
|
||||
cp artifacts/sdcard-no-map-portarf/sdcard-no-map.zip "release_assets/COPY_TO_SDCARD_portarf_mayhem_nightly_${VER}-no-world-map.zip"
|
||||
cp artifacts/sdcard-hpro/sdcard.zip "release_assets/COPY_TO_SDCARD_hpro_mayhem_nightly_${VER}.zip"
|
||||
cp artifacts/sdcard-no-map-hpro/sdcard-no-map.zip "release_assets/COPY_TO_SDCARD_hpro_mayhem_nightly_${VER}-no-world-map.zip"
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: nightly-tag-${{ steps.date.outputs.date }}
|
||||
release_name: Nightly Release - ${{ steps.date.outputs.date }}
|
||||
name: Nightly Release - ${{ steps.date.outputs.date }}
|
||||
body: |
|
||||
**Nightly release - ${{ steps.date.outputs.date }}**
|
||||
This build is the latest and greatest, although may not be the most stable as this is a nightly release.
|
||||
@@ -96,43 +175,4 @@ jobs:
|
||||
${{ steps.changelog.outputs.content }}
|
||||
draft: false
|
||||
prerelease: true
|
||||
- name: Upload Firmware TAR Asset
|
||||
id: upload-firmware-tar-asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: build/firmware/portapack-mayhem_OCI.ppfw.tar
|
||||
asset_name: mayhem_nightly_${{ steps.version_date.outputs.date }}_OCI.ppfw.tar
|
||||
asset_content_type: application/x-tar
|
||||
- name: Upload Firmware Asset
|
||||
id: upload-firmware-asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ./firmware.zip
|
||||
asset_name: mayhem_nightly_${{ steps.version_date.outputs.date }}_FIRMWARE.zip
|
||||
asset_content_type: application/zip
|
||||
- name: Upload SD Card Assets
|
||||
id: upload-sd-card-asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ./sdcard.zip
|
||||
asset_name: mayhem_nightly_${{ steps.version_date.outputs.date }}_COPY_TO_SDCARD.zip
|
||||
asset_content_type: application/zip
|
||||
- name: Upload SD Card Assets - No Map
|
||||
id: upload-sd-card-asset-no-map
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ./sdcard-no-map.zip
|
||||
asset_name: mayhem_nightly_${{ steps.version_date.outputs.date }}_COPY_TO_SDCARD-no-world-map.zip
|
||||
asset_content_type: application/zip
|
||||
files: release_assets/*
|
||||
|
||||
@@ -1,25 +1,36 @@
|
||||
name: Stable Release
|
||||
|
||||
on:
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
name: build (${{ matrix.device_name }})
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- device: default
|
||||
device_name: HackRF
|
||||
cmake_args: ""
|
||||
artifact_suffix: "_hackrf"
|
||||
- device: portarf
|
||||
device_name: PortaRF
|
||||
cmake_args: "-DFLASH_MB_SIZE=2 -DFLASH_MB_LIMIT_SIZE=2"
|
||||
artifact_suffix: "_portarf"
|
||||
- device: hpro
|
||||
device_name: HackRF Pro
|
||||
cmake_args: "-DBOARD=PRALINE"
|
||||
artifact_suffix: "_hpro"
|
||||
steps:
|
||||
- name: Get current date
|
||||
id: date
|
||||
run: echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT
|
||||
- name: Checkout
|
||||
- name: Checkout
|
||||
uses: actions/checkout@master
|
||||
with:
|
||||
fetch-depth: 0
|
||||
#ref: next
|
||||
# The branch, tag or SHA to checkout. When checking out the repository that
|
||||
# triggered a workflow, this defaults to the reference or SHA for that event.
|
||||
# Otherwise, uses the default branch.
|
||||
# https://github.com/actions/checkout
|
||||
# So scheduled runs will use the default branch (next) but its now possible to trigger a workflow from another branch
|
||||
submodules: true
|
||||
- name: Git Sumbodule Update
|
||||
run: |
|
||||
@@ -27,18 +38,15 @@ jobs:
|
||||
- name: Get version
|
||||
id: version
|
||||
run: echo "version=$(cat .github/workflows/version.txt)" >> $GITHUB_OUTPUT
|
||||
- name: Get past version
|
||||
id: past_version
|
||||
run: echo "past_version=$(cat .github/workflows/past_version.txt)" >> $GITHUB_OUTPUT
|
||||
- name: Build the Docker image
|
||||
run: docker build -t portapack-dev -f dockerfile-nogit . --tag my-image-name:$(date +%s)
|
||||
- name: Make build folder
|
||||
run: mkdir ${{ github.workspace }}/build
|
||||
- name: Run the Docker image
|
||||
run: docker run -e VERSION_STRING=${{ steps.version.outputs.version }} -i -v ${{ github.workspace }}:/havoc portapack-dev
|
||||
run: docker run -e VERSION_STRING=${{ steps.version.outputs.version }} -i -v ${{ github.workspace }}:/havoc portapack-dev ${{ matrix.cmake_args }}
|
||||
- name: Create Small SD Card ZIP - No World Map
|
||||
run: |
|
||||
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-mayhem-firmware.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version.outputs.version }}.bin && cp build/firmware/portapack-mayhem_OCI.ppfw.tar sdcard/FIRMWARE/mayhem_${{ steps.version.outputs.version }}_OCI.ppfw.tar && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && cp build/firmware/standalone/*/*.ppmp sdcard/APPS && cd sdcard && zip -r ../sdcard-no-map.zip . && cd ..
|
||||
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-mayhem-firmware.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version.outputs.version }}.bin && cp build/firmware/portapack-mayhem_OCI.ppfw.tar sdcard/FIRMWARE/mayhem_${{ steps.version.outputs.version }}${{ matrix.artifact_suffix }}_OCI.ppfw.tar && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && cp build/firmware/standalone/*/*.ppmp sdcard/APPS && cd sdcard && zip -r ../sdcard-no-map.zip . && cd ..
|
||||
- name: Download world map
|
||||
run: |
|
||||
wget https://github.com/portapack-mayhem/mayhem-firmware/releases/download/world_map/world_map.zip
|
||||
@@ -47,13 +55,58 @@ jobs:
|
||||
unzip world_map.zip -d sdcard/ADSB
|
||||
- name: Prepare Firmware ZIP
|
||||
run: |
|
||||
cp build/hackrf/firmware/hackrf_usb/hackrf_usb.dfu flashing/utils/hackrf_one_usb.dfu && cp build/hackrf/firmware/hackrf_usb/hackrf_usb.bin flashing/utils/hackrf_one_usb.bin
|
||||
mkdir -p flashing/firmware && cp build/hackrf/firmware/hackrf_usb/hackrf_usb.dfu flashing/firmware/hackrf_usb.dfu && cp build/hackrf/firmware/hackrf_usb/hackrf_usb.bin flashing/firmware/hackrf_usb.bin
|
||||
- name: Create Firmware ZIP
|
||||
run: |
|
||||
zip -j firmware.zip build/firmware/portapack-mayhem-firmware.bin && cd flashing && zip -r ../firmware.zip *
|
||||
- name: Create SD Card ZIP
|
||||
run: |
|
||||
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-mayhem-firmware.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version.outputs.version }}.bin && cp build/firmware/portapack-mayhem_OCI.ppfw.tar sdcard/FIRMWARE/mayhem_${{ steps.version.outputs.version }}_OCI.ppfw.tar && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && cp build/firmware/standalone/*/*.ppmp sdcard/APPS && cd sdcard && zip -r ../sdcard.zip . && cd ..
|
||||
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-mayhem-firmware.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version.outputs.version }}.bin && cp build/firmware/portapack-mayhem_OCI.ppfw.tar sdcard/FIRMWARE/mayhem_${{ steps.version.outputs.version }}${{ matrix.artifact_suffix }}_OCI.ppfw.tar && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && cp build/firmware/standalone/*/*.ppmp sdcard/APPS && cd sdcard && zip -r ../sdcard.zip . && cd ..
|
||||
- name: Upload firmware zip artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: firmware-${{ matrix.device }}
|
||||
path: ./firmware.zip
|
||||
- name: Upload SD card artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sdcard-${{ matrix.device }}
|
||||
path: ./sdcard.zip
|
||||
- name: Upload SD card no-map artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sdcard-no-map-${{ matrix.device }}
|
||||
path: ./sdcard-no-map.zip
|
||||
- name: Upload OCI tar artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: oci-tar-${{ matrix.device }}
|
||||
path: build/firmware/portapack-mayhem_OCI.ppfw.tar
|
||||
|
||||
release:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Get current date
|
||||
id: date
|
||||
run: echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT
|
||||
- name: Checkout
|
||||
uses: actions/checkout@master
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: true
|
||||
- name: Get version
|
||||
id: version
|
||||
run: echo "version=$(cat .github/workflows/version.txt)" >> $GITHUB_OUTPUT
|
||||
- name: Get past version
|
||||
id: past_version
|
||||
run: echo "past_version=$(cat .github/workflows/past_version.txt)" >> $GITHUB_OUTPUT
|
||||
- name: Download all artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: artifacts
|
||||
- name: Create changelog
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -64,21 +117,49 @@ jobs:
|
||||
echo EOF
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
id: changelog
|
||||
# Combine all 3 device firmware bins into a single zip with device selector bat
|
||||
- name: Create Combined Firmware ZIP
|
||||
run: |
|
||||
mkdir -p firmware_combined
|
||||
unzip artifacts/firmware-default/firmware.zip -d firmware_combined
|
||||
mv firmware_combined/portapack-mayhem-firmware.bin firmware_combined/firmware/firmware_hackrf.bin
|
||||
unzip -j artifacts/firmware-portarf/firmware.zip portapack-mayhem-firmware.bin -d firmware_combined/firmware
|
||||
mv firmware_combined/firmware/portapack-mayhem-firmware.bin firmware_combined/firmware/firmware_portarf.bin
|
||||
unzip -j artifacts/firmware-hpro/firmware.zip portapack-mayhem-firmware.bin -d firmware_combined/firmware
|
||||
mv firmware_combined/firmware/portapack-mayhem-firmware.bin firmware_combined/firmware/firmware_hpro.bin
|
||||
unzip -j artifacts/firmware-hpro/firmware.zip firmware/hackrf_usb.dfu firmware/hackrf_usb.bin -d firmware_combined_hpro
|
||||
mv firmware_combined_hpro/hackrf_usb.dfu firmware_combined/firmware/hackrf_hpro_usb.dfu
|
||||
mv firmware_combined_hpro/hackrf_usb.bin firmware_combined/firmware/hackrf_hpro_usb.bin
|
||||
cd firmware_combined && zip -r ../firmware_all_devices.zip . && cd ..
|
||||
- name: Prepare release assets
|
||||
run: |
|
||||
VER="${{ steps.version.outputs.version }}"
|
||||
mkdir -p release_assets
|
||||
# OCI tar assets
|
||||
cp artifacts/oci-tar-default/portapack-mayhem_OCI.ppfw.tar "release_assets/OCI_hackrf_mayhem_${VER}.ppfw.tar"
|
||||
cp artifacts/oci-tar-portarf/portapack-mayhem_OCI.ppfw.tar "release_assets/OCI_portarf_mayhem_${VER}.ppfw.tar"
|
||||
cp artifacts/oci-tar-hpro/portapack-mayhem_OCI.ppfw.tar "release_assets/OCI_hpro_mayhem_${VER}.ppfw.tar"
|
||||
# Combined firmware
|
||||
cp firmware_all_devices.zip "release_assets/FIRMWARE_mayhem_${VER}.zip"
|
||||
# SD card assets
|
||||
cp artifacts/sdcard-default/sdcard.zip "release_assets/COPY_TO_SDCARD_hackrf_mayhem_${VER}.zip"
|
||||
cp artifacts/sdcard-no-map-default/sdcard-no-map.zip "release_assets/COPY_TO_SDCARD_hackrf_mayhem_${VER}-no-world-map.zip"
|
||||
cp artifacts/sdcard-portarf/sdcard.zip "release_assets/COPY_TO_SDCARD_portarf_mayhem_${VER}.zip"
|
||||
cp artifacts/sdcard-no-map-portarf/sdcard-no-map.zip "release_assets/COPY_TO_SDCARD_portarf_mayhem_${VER}-no-world-map.zip"
|
||||
cp artifacts/sdcard-hpro/sdcard.zip "release_assets/COPY_TO_SDCARD_hpro_mayhem_${VER}.zip"
|
||||
cp artifacts/sdcard-no-map-hpro/sdcard-no-map.zip "release_assets/COPY_TO_SDCARD_hpro_mayhem_${VER}-no-world-map.zip"
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: ${{ steps.version.outputs.version }}
|
||||
release_name: Mayhem firmware ${{ steps.version.outputs.version }}
|
||||
name: Mayhem firmware ${{ steps.version.outputs.version }}
|
||||
body: |
|
||||
**Stable release - ${{ steps.version.outputs.version }}**
|
||||
This is a fork of the [Havoc](https://github.com/furrtek/portapack-havoc/) firmware, which itself was a fork of the [PortaPack](https://github.com/sharebrained/portapack-hackrf) firmware, an add-on for the [HackRF](http://greatscottgadgets.com/hackrf/). Please check the [readme](https://github.com/portapack-mayhem/mayhem-firmware/blob/master/README.md) for details.
|
||||
## Release notes
|
||||
### Revision (${{ steps.version.outputs.version }}):
|
||||
${{ steps.changelog.outputs.content }}
|
||||
|
||||
|
||||
**Full Changelog**: https://github.com/portapack-mayhem/mayhem-firmware/compare/${{ steps.past_version.outputs.past_version }}...${{ steps.version.outputs.version }}
|
||||
|
||||
## Installation
|
||||
@@ -90,44 +171,4 @@ jobs:
|
||||
For certain functionality, like external apps, the world map, GPS simulator, and others you need to uncompress (using [7-zip](https://www.7-zip.org/download.html)) the files from `mayhem_vX.Y.Z_COPY_TO_SDCARD.zip` to a FAT32 formatted MicroSD card.
|
||||
draft: true
|
||||
prerelease: false
|
||||
- name: Upload Firmware TAR Asset
|
||||
id: upload-firmware-tar-asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: build/firmware/portapack-mayhem_OCI.ppfw.tar
|
||||
asset_name: mayhem_${{ steps.version.outputs.version }}_OCI.ppfw.tar
|
||||
asset_content_type: application/x-tar
|
||||
- name: Upload Firmware Asset
|
||||
id: upload-firmware-asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ./firmware.zip
|
||||
asset_name: mayhem_${{ steps.version.outputs.version }}_FIRMWARE.zip
|
||||
asset_content_type: application/zip
|
||||
- name: Upload SD Card Assets
|
||||
id: upload-sd-card-asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ./sdcard.zip
|
||||
asset_name: mayhem_${{ steps.version.outputs.version }}_COPY_TO_SDCARD.zip
|
||||
asset_content_type: application/zip
|
||||
- name: Upload SD Card Assets - No Map
|
||||
id: upload-sd-card-asset-no-map
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ./sdcard-no-map.zip
|
||||
asset_name: mayhem_${{ steps.version.outputs.version }}_COPY_TO_SDCARD-no-world-map.zip
|
||||
asset_content_type: application/zip
|
||||
|
||||
files: release_assets/*
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
name: Hardware Issue Warning
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [labeled]
|
||||
|
||||
jobs:
|
||||
add-comment:
|
||||
if: github.event.label.name == 'hardware problem'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
steps:
|
||||
- name: Add warning comment
|
||||
uses: peter-evans/create-or-update-comment@v3
|
||||
with:
|
||||
issue-number: ${{ github.event.issue.number }}
|
||||
body: |
|
||||
> [!IMPORTANT]
|
||||
If you are a seller/vendor and your customer has directed you to this page as a reference/evidence of a "broken device" or similar issue, please be aware that we are not providing any "official guarantee of a faulty device." We are only offering suggestions based on what the submitter has stated. We do not guarantee that the device is broken or anything else.
|
||||
@@ -1 +1 @@
|
||||
v2.0.2
|
||||
v2.3.2
|
||||
|
||||
@@ -1 +1 @@
|
||||
v2.1.0
|
||||
v2.4.0
|
||||
|
||||
@@ -61,6 +61,8 @@ cmake-build-debug/
|
||||
*.sublime-workspace
|
||||
.vscode
|
||||
.idea
|
||||
*.swp
|
||||
.claude
|
||||
|
||||
# VSCodium extensions
|
||||
.history
|
||||
@@ -81,3 +83,4 @@ venv/
|
||||
# generated bitmap arr file
|
||||
# TODO: generate bitmap during build, since we use python during build anyway, lemme know if this is a bad idea @zxkmm
|
||||
/firmware/tools/bitmap.hpp
|
||||
tmp/*
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
# PortaPack Mayhem – Agent Guidelines
|
||||
|
||||
Welcome to the PortaPack Mayhem repository. This document sets mandatory rules and best practices for human developers and AI coding assistants. It exists to prevent unverified, untested code from overwhelming the maintainers, while still enabling AI to accelerate development.
|
||||
|
||||
## 🚫 STRICTLY PROHIBITED
|
||||
|
||||
1. **No Fully Automated Pull Requests**
|
||||
Autonomous agents (bots, crawlers, automated workflows) must **never** open a Pull Request without direct human supervision, review, and explicit approval.
|
||||
|
||||
2. **No “Blind” AI-Generated Submissions**
|
||||
If you cannot explain, debug, or maintain the C++/DSP code an AI produced for you, do **not** submit it. PRs that appear to be untested, AI-only “compile-and-pray” attempts will be closed immediately.
|
||||
|
||||
3. **No Unverified Hardware Interactions**
|
||||
AI cannot test on real hardware. Do **not** submit code that has not been compiled and physically tested on a PortaPack device (or a highly accurate emulator). This includes UI rendering, RF transmission quality, and baseband stability.
|
||||
|
||||
## ✅ ENCOURAGED AI USE CASES
|
||||
|
||||
We strongly encourage using AI assistants (Copilot, ChatGPT, Claude, local LLMs) as pair-programmers. Excellent use cases include:
|
||||
|
||||
- **Bug Triage & Root-Causing:** Analyze crash logs, stack traces, and complex memory issues to identify the root cause of an open issue.
|
||||
- **Code Comprehension:** Map Mayhem’s architecture, understand M0‑M4 IPC, or break down complex DSP algorithms.
|
||||
- **Development Assistance:** Generate boilerplate UI code, write unit tests, optimize math functions, or draft documentation – provided a human reviews and refines the output.
|
||||
- **Targeted Refactoring:** Suggest cleaner, modern C++ for small legacy code segments. **Do not attempt to rewrite the entire codebase.**
|
||||
|
||||
## 📋 MANDATORY RULES FOR AI‑ASSISTED CONTRIBUTIONS
|
||||
|
||||
When submitting AI‑assisted work, you must:
|
||||
|
||||
1. **Take Full Ownership**
|
||||
You are strictly responsible for every line of code you submit, regardless of origin. Be prepared to answer detailed technical questions during review.
|
||||
|
||||
2. **Hardware Verification Required**
|
||||
In your PR, explicitly state: *“I have compiled this code and tested it on physical PortaPack hardware.”* PRs without this statement will be rejected.
|
||||
|
||||
3. **Transparency (Recommended)**
|
||||
If AI significantly helped architect a feature or fix a complex bug, mention it briefly in the PR description (e.g., *“Used an LLM to help optimize the DSP filter loop”*).
|
||||
|
||||
4. **Adhere to Coding Standards**
|
||||
Ensure all code matches the existing Mayhem style and formatting before opening the PR.
|
||||
|
||||
## ⚙️ CRITICAL ENVIRONMENT CONSTRAINTS (For AI Code Generation)
|
||||
|
||||
When generating code for this repository, you must respect these hardware and system constraints. **Violating any of them will result in immediate rejection.**
|
||||
|
||||
- **Severe Memory Limits** – RAM is extremely scarce. Every byte counts. Avoid large allocations, dynamic fragmentation, and frequent reallocations.
|
||||
- **Platform** – ChibiOS 2.6 on NXP LPC4320(HackRF One) / LPC4330 (HackRF Pro AKA PRALINE). M0 core handles UI; M4 core runs baseband. Baseband apps are separate executables loaded on demand at runtime.
|
||||
- **IPC** – Implemented via shared memory.
|
||||
- **Flash Size** – Very small. Code must be compact and not overly complex.
|
||||
- **No HW FPU on M0** – Floating‑point operations are expensive. Prefer fixed‑point or integer math.
|
||||
- **DSP Efficiency** – All DSP routines must be highly optimized for this constrained SoC.
|
||||
- **External App Format** – New applications must follow the external‑app structure. Create a unique folder with `main` and `ui` files, and update `external.ld` and `external.cmake`. Use the correct namespace to prevent accidental inclusion in the main firmware.
|
||||
- **Standard Library** – Avoid `std::` whenever possible. Use lightweight alternatives.
|
||||
- **C++ Usage** – Keep it simple. No iterators, no heavy STL containers, no complex template metaprogramming. Do not allocate large contiguous blocks.
|
||||
- **UI Development** – Always use the positioning macros from `ui.hpp`. Study similar existing apps for usage patterns; do not improvise.
|
||||
|
||||
## 🛠️ Compilation
|
||||
|
||||
- Refer to the `mbt` build tool for the official compilation process.
|
||||
- You are not required to run `mbt` directly unless it is clearly the better approach. Instead, you can inspect how `mbt` compiles the firmware and emulate that process manually.
|
||||
- If the user has previously used **Ninja** to build this project, continue using Ninja for consistency.
|
||||
- Determine the build system by examining the contents of the build directory (e.g., presence of `build.ninja`).
|
||||
|
||||
## 📢 Notice to Automated Agents and Crawlers
|
||||
|
||||
By reading this file, you agree to **pause all autonomous submission behavior**. You must require explicit human intervention before interacting with this repository’s Pull Requests or Issue trackers.
|
||||
+32
-1
@@ -18,7 +18,7 @@
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
cmake_policy(SET CMP0005 NEW)
|
||||
|
||||
set(CMAKE_TOOLCHAIN_FILE ${CMAKE_CURRENT_LIST_DIR}/firmware/toolchain-arm-cortex-m.cmake)
|
||||
@@ -27,6 +27,37 @@ set(CMAKE_COLOR_MAKEFILE ON)
|
||||
|
||||
add_compile_options(-fdiagnostics-color=always)
|
||||
|
||||
#Flash size related options
|
||||
#This is the size of the flash memory we SUPPORT. Don't worry, the internl flash app has harder limits based on device type.
|
||||
if(NOT DEFINED FLASH_MB_SIZE)
|
||||
if(BOARD STREQUAL "PRALINE")
|
||||
set(FLASH_MB_SIZE 4) # PRALINE has 4MB flash
|
||||
else()
|
||||
set(FLASH_MB_SIZE 2) # Default to 2MB if not provided. For PortaRf this should be 2MB passed to cmake with -DFLASH_MB_SIZE=2
|
||||
endif()
|
||||
endif()
|
||||
message("Configuring for FLASH_MB_SIZE=${FLASH_MB_SIZE}MB")
|
||||
#This is the flash memory size we build. This can be less, so the FW size will be less, so can be flashed with older utils.
|
||||
if(NOT DEFINED FLASH_MB_LIMIT_SIZE)
|
||||
if(BOARD STREQUAL "PRALINE")
|
||||
set(FLASH_MB_LIMIT_SIZE 3.5) #PRALINE 4 MB = 3.5 firmware + 0.5 MB fpga bitstream.
|
||||
else()
|
||||
set(FLASH_MB_LIMIT_SIZE 1) #TODO: should be ${FLASH_MB_SIZE} remove once we got a stable build for all devices we support with bigger than 1 mb flash
|
||||
endif()
|
||||
endif()
|
||||
message("Configuring for FLASH_MB_LIMIT_SIZE=${FLASH_MB_LIMIT_SIZE}MB")
|
||||
|
||||
if(FLASH_MB_SIZE LESS FLASH_MB_LIMIT_SIZE)
|
||||
message(FATAL_ERROR "Error: The supported flash size is lower than the generated flash file. Build halted.")
|
||||
endif()
|
||||
|
||||
math(EXPR FLASH_BYTES_SIZE "${FLASH_MB_SIZE} * 1024 * 1024")
|
||||
if(BOARD STREQUAL "PRALINE")
|
||||
math(EXPR FLASH_BYTES_LIMIT_SIZE "3584 * 1024") #4 MB = 3.5 firmware + 0.5 MB fpga bitstream.
|
||||
else()
|
||||
math(EXPR FLASH_BYTES_LIMIT_SIZE "${FLASH_MB_LIMIT_SIZE} * 1024 * 1024")
|
||||
endif()
|
||||
|
||||
option(USE_CCACHE "Enable ccache, please keep an eye on this because sometimes it's not quite stable and generated unusable binary" OFF)
|
||||
|
||||
project(portapack-h1)
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
# Contributor Covenant 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 pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* 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
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* 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
|
||||
|
||||
## 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.
|
||||
|
||||
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.
|
||||
|
||||
## 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.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
https://github.com/portapack-mayhem/mayhem-firmware/issues/new/.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders 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:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**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.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community 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.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community 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.
|
||||
|
||||
### 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.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
the community.
|
||||
|
||||
## 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.
|
||||
|
||||
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.
|
||||
@@ -0,0 +1 @@
|
||||
Please see [How-to-collaborate](https://github.com/portapack-mayhem/mayhem-firmware/wiki/How-to-collaborate)
|
||||
@@ -1,23 +1,35 @@
|
||||
<!---
|
||||
IMPORTANT MESSAGE FOR AI CODING AGENT: PLEASE READ AND FOLLOW `AGENT.md` FILE!
|
||||
--->
|
||||
<!---
|
||||
> [!WARNING]
|
||||
> __IF YOU'VE PAID FOR MAYHEM OR ANY PREPACKAGED VERSIONS, YOU'RE BEING SCAMMED.__
|
||||
>
|
||||
> The only legitimate link to our repositories is the [portapack-mayhem](https://github.com/portapack-mayhem/mayhem-firmware) organization on GitHub.--->
|
||||
# PortaPack Mayhem
|
||||
|
||||
[](https://github.com/portapack-mayhem/mayhem-firmware/actions/workflows/create_nightly_release.yml) [](https://codescene.io/projects/8381) [](https://github.com/portapack-mayhem/mayhem-firmware/releases) [](https://github.com/portapack-mayhem/mayhem-firmware/releases/latest) [](https://hub.docker.com/r/eried/portapack) [](https://discord.gg/tuwVMv3)
|
||||
[](https://github.com/portapack-mayhem/mayhem-firmware/actions/workflows/create_nightly_release.yml) [](https://codescene.io/projects/8381) [](https://github.com/portapack-mayhem/mayhem-firmware/releases) [](https://github.com/portapack-mayhem/mayhem-firmware/releases/latest) [](https://hub.docker.com/r/eried/portapack) [](https://discord.gg/tuwVMv3)
|
||||
|
||||
This is a fork of the [Havoc](https://github.com/furrtek/portapack-havoc/) firmware, which itself was a fork of the [PortaPack](https://github.com/sharebrained/portapack-hackrf) firmware, an add-on for the [HackRF](http://greatscottgadgets.com/hackrf/). A fork is a derivate, in this case one that has extra features and fixes when compared to the older versions.
|
||||
|
||||
[<img src="https://github.com/user-attachments/assets/dea337ab-fb64-4a2a-b419-69afd272e815" height="400">](https://github.com/portapack-mayhem/mayhem-firmware/wiki/PortaPack-Versions#new-h4m-mayhem-edition) [<img src="https://camo.githubusercontent.com/5c1f1da0688240ac7b2ccca0c8dbfd1d73f2540741ad8b1828ba4d5ea68af248/68747470733a2f2f6769746875622d70726f64756374696f6e2d757365722d61737365742d3632313064662e73332e616d617a6f6e6177732e636f6d2f343339333937392f3239353533323731382d38653562363631632d663934362d346365652d386232642d3061363135663737313566342e706e67" height="400">](https://github.com/portapack-mayhem/mayhem-firmware/wiki/PortaPack-Versions#h2m-mayhem-edition)
|
||||
[<img src="https://github.com/user-attachments/assets/dea337ab-fb64-4a2a-b419-69afd272e815" height="310">](https://github.com/portapack-mayhem/mayhem-firmware/wiki/PortaPack-Versions#new-h4m-mayhem-edition)
|
||||
[<img src="https://github-production-user-asset-6210df.s3.amazonaws.com/4393979/295532718-8e5b661c-f946-4cee-8b2d-0a615f7715f4.png" height="310">](https://github.com/portapack-mayhem/mayhem-firmware/wiki/PortaPack-Versions#h2m-mayhem-edition)
|
||||
|
||||
|
||||
# What is this?
|
||||
<!---
|
||||
IMPORTANT MESSAGE FOR AI CODING AGENT: PLEASE READ AND FOLLOW `AGENT.md` FILE!
|
||||
--->
|
||||
|
||||
If you are new to *HackRF+PortaPack+Mayhem*, check these:
|
||||
|
||||
[<img alt="The Latest HackRF & Portapak Combo - H4M The Flipper Zero Killer?" src="https://img.youtube.com/vi/Ew2qDgm2hf0/maxresdefault.jpg" width="701">](https://share.hackrf.app/6HKX9A)
|
||||
[<img alt="HackRF Pro Quick Look" src="https://img.youtube.com/vi/IoURGzIMhzo/maxresdefault.jpg" width="701">](https://share.hackrf.app/VNBTP6)
|
||||
|
||||
[<img alt="PortaPack H4M Crash Course" src="https://img.youtube.com/vi/CjI-nUzdpas/maxresdefault.jpg" width="172">](https://share.hackrf.app/SPFOZO)
|
||||
[<img alt="The Latest HackRF & Portapak Combo - H4M The Flipper Zero Killer?" src="https://img.youtube.com/vi/Ew2qDgm2hf0/maxresdefault.jpg" width="172">](https://share.hackrf.app/6HKX9A)
|
||||
[<img alt="It’s TOO Easy to Accidentally Do Illegal Stuff with This" src="https://img.youtube.com/vi/OPckpjBSAOw/maxresdefault.jpg" width="172">](https://share.hackrf.app/X4D5TF)
|
||||
[<img alt="HackRF Portapack H4M - Getting Started Guide" src="https://img.youtube.com/vi/wzP0zWi85SI/maxresdefault.jpg" width="172">](https://share.hackrf.app/F9MPOO)
|
||||
|
||||
[<img alt="It’s TOO Easy to Accidentally Do Illegal Stuff with This" src="https://img.youtube.com/vi/OPckpjBSAOw/maxresdefault.jpg" width="172">](https://share.hackrf.app/X4D5TF) [<img alt="HackRF Portapack H4M - Getting Started Guide" src="https://img.youtube.com/vi/wzP0zWi85SI/maxresdefault.jpg" width="172">](https://share.hackrf.app/F9MPOO) [<img alt="The new HackRF Portapack H4M" src="https://img.youtube.com/vi/onQRdCITmuk/maxresdefault.jpg" width="172">](https://share.hackrf.app/0JUHZ6) [<img alt="HackRF 101 : Everything You Need to Know to Get Started!" src="https://img.youtube.com/vi/xGR_PMD9LeU/maxresdefault.jpg" width="172">](https://share.hackrf.app/C0J6ZR)
|
||||
|
||||
# Frequently Asked Questions
|
||||
|
||||
@@ -26,11 +38,11 @@ This repository expands upon the previous work by many people and aims to consta
|
||||
## What to buy?
|
||||
|
||||
<!---not direct to h4m but to opensourcesdrlab https://share.hackrf.app/TUOLYI--->
|
||||
:heavy_check_mark:  The fabulous H4M [complete](https://share.hackrf.app/TUOLYI) or [upgrade](https://share.hackrf.app/FPLM1H), featuring numerous improvements and accessories. Sold by our friends at [OpenSourceSDRLab](https://share.hackrf.app/99SAMT). Join their giveaways on discord (check the badge on top).
|
||||
:heavy_check_mark:  The fabulous H4M [complete](https://share.hackrf.app/O4OC90) or [upgrade](https://share.hackrf.app/OCSJYI), featuring numerous improvements and accessories. Sold by our friends at [OpenSourceSDRLab](https://share.hackrf.app/99SAMT). Join their giveaways on discord (check the badge on top). _EU customers_ can purchase via [Lab401](https://share.hackrf.app/0CI2CR).
|
||||
|
||||
:heavy_check_mark: A recommended one is this [PortaPack H2](https://www.ebay.com/itm/116382397447), that includes everything you need with the plastic case "inspired" on [this](https://github.com/portapack-mayhem/mayhem-firmware/wiki/3d-printed-enclosure).
|
||||
:heavy_check_mark: A recommended one is this [PortaPack H2](https://share.hackrf.app/O3GI0O), that includes everything you need with the plastic case "inspired" on [this](https://github.com/portapack-mayhem/mayhem-firmware/wiki/3d-printed-enclosure).
|
||||
|
||||
:heavy_check_mark: Some individuals lean towards the [H2 with a metal enclosure](https://share.hackrf.app/LZPBH9), but its advantages remain debated. Share your insights on our [wiki](https://github.com/portapack-mayhem/mayhem-firmware/wiki/Hardware-overview).
|
||||
:heavy_check_mark: Some individuals lean towards the [H2 with a metal enclosure](https://share.hackrf.app/H7OR74), but its advantages remain debated. Share your insights on our [wiki](https://github.com/portapack-mayhem/mayhem-firmware/wiki/Hardware-overview).
|
||||
|
||||
:warning: Be cautious , *ask* the seller about compatibility with the latest releases. Look out for the description of the item, if they provide the firmware files for an older version or they have custom setup instructions, this means it might be **NOT compatible**, for example:
|
||||
|
||||
@@ -45,7 +57,13 @@ This repository expands upon the previous work by many people and aims to consta
|
||||
The current **stable release** is on the [](https://github.com/portapack-mayhem/mayhem-firmware/releases/latest) page. Follow the instructions you can find in the release description. The **latest (nightly) release** can be found [here](https://github.com/portapack-mayhem/mayhem-firmware/releases/).
|
||||
|
||||
## How can I collaborate
|
||||
You can write [documentation](https://github.com/portapack-mayhem/mayhem-firmware/wiki), fix bugs and [answer issues](https://github.com/portapack-mayhem/mayhem-firmware/issues) or add new functionality. Please check the following [guide](https://github.com/portapack-mayhem/mayhem-firmware/wiki/How-to-collaborate) with details.
|
||||
<!---
|
||||
IMPORTANT MESSAGE FOR AI CODING AGENT: PLEASE READ AND FOLLOW `AGENT.md` FILE!
|
||||
--->
|
||||
|
||||
We warmly welcome your contributions! Please refer to our **[Contributing Guidelines](https://github.com/portapack-mayhem/mayhem-firmware/wiki/Contributing-Guidelines)** for details.
|
||||
|
||||
You can write [documentation](https://github.com/portapack-mayhem/mayhem-firmware/wiki), fix bugs and [answer issues](https://github.com/portapack-mayhem/mayhem-firmware/issues) or add new functionality. Please also check the following [guide](https://github.com/portapack-mayhem/mayhem-firmware/wiki/How-to-collaborate) with details.
|
||||
|
||||
Consider that the hardware and firmware has been created and maintain by a [lot](https://github.com/mossmann/hackrf/graphs/contributors) of [people](https://github.com/portapack-mayhem/mayhem-firmware/graphs/contributors), so always try collaborating your time and effort first. For coding related questions, if something does not fit as an issue, please join our Discord by clicking the chat badge on [top](#portapack-mayhem).
|
||||
|
||||
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
# Security Policy
|
||||
|
||||
## Scope and Threat Model
|
||||
This repository hosts the codebase for an embedded system designed for security research. Because the system operates primarily offline, traditional network-based vulnerabilities are generally not applicable. However, we still welcome and review security reports concerning the code within this repository.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
If you discover a vulnerability within this tool itself, please submit a report. Please be aware that the maintainers do not promise assistance with obtaining CVE IDs or managing formal security advisories.
|
||||
|
||||
## Out of Scope
|
||||
* **Vulnerabilities found *using* this tool:** Please do not submit reports here for vulnerabilities you have discovered in other systems or targets using this tool. This channel is strictly for reporting vulnerabilities found *in* the tool itself.
|
||||
* **Other Repositories:** For security issues concerning related tools, such as MayhemHub, please submit your reports directly to their respective repositories.
|
||||
|
||||
## Research Attribution
|
||||
If you utilize this tool to successfully discover vulnerabilities in external systems, we would greatly appreciate it if you credit or mention our project in your published research or write-ups.
|
||||
|
||||
## Legal and Security Disclaimer
|
||||
For comprehensive guidelines regarding the legal and secure usage of this tool, as well as our full liability disclaimer, please refer to: [Intended-Use-and-Legality](https://github.com/portapack-mayhem/mayhem-firmware/wiki/). Users are expected to comply with all applicable laws and regulations when using this software and/or hardware.
|
||||
+3
-3
@@ -3,7 +3,7 @@ FROM ubuntu:noble
|
||||
# Set location to download ARM toolkit from.
|
||||
# This will need to be changed over time or replaced with a static link to the latest release.
|
||||
ENV ARMBINURL=https://developer.arm.com/-/media/Files/downloads/gnu-rm/9-2019q4/gcc-arm-none-eabi-9-2019-q4-major-x86_64-linux.tar.bz2?revision=108bd959-44bd-4619-9c19-26187abf5225&la=en&hash=E788CE92E5DFD64B2A8C246BBA91A249CB8E2D2D \
|
||||
PATH=$HOME/bin:$PATH:/opt/build/armbin/bin
|
||||
PATH=$PATH:/opt/build/armbin/bin
|
||||
|
||||
#Create volume /havoc/bin for compiled firmware binaries
|
||||
VOLUME /havoc
|
||||
@@ -15,8 +15,8 @@ RUN apt-get update \
|
||||
&& apt-get -qy autoremove \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ENV LANG C.UTF-8
|
||||
ENV LC_ALL C.UTF-8
|
||||
ENV LANG=C.UTF-8
|
||||
ENV LC_ALL=C.UTF-8
|
||||
|
||||
# Grab the GNU ARM toolchain from arm.com
|
||||
# Then extract contents to /opt/build/armbin/
|
||||
|
||||
Executable
+86
@@ -0,0 +1,86 @@
|
||||
#!/bin/bash
|
||||
|
||||
#################################################
|
||||
# This script aids building mayhem inside docker
|
||||
#
|
||||
# Basic usage:
|
||||
# - Build dev container: ./dockerize.sh build
|
||||
# - Build mayhem: ./dockerize.sh
|
||||
#
|
||||
# The image will be automatically build if it
|
||||
# does not exist, but if the dockerfile changes
|
||||
# it need to be rebuilt manually.
|
||||
#
|
||||
# Advanced parameters:
|
||||
# - Get a shell inside the build image to
|
||||
# inspect problems: ./dockerize.sh shell
|
||||
# - Give additional parameters to the container:
|
||||
# Additional parameters to make: ./dockerize.sh -j10
|
||||
# Use ninja instead of make: ./dockerize.sh ninja -j10
|
||||
# Addotopnal CMake parameters: ./dockerize.sh -DBOARD=PRALINE
|
||||
# Change the default build directory: ./dockerize.sh -B build-alt
|
||||
# (Alternatively --workdir works as well)
|
||||
# - Use a different dockerfile:
|
||||
# ./dockerize.sh build dockerfile-other
|
||||
# - Use a different cpu architecture:
|
||||
# ./dockerize.sh build dockerfile-nogit-arm arm64
|
||||
#
|
||||
# Environment variables:
|
||||
# - It is possible to override the default image
|
||||
# name that is being used to build and run the
|
||||
# build container using an environment variable.
|
||||
# The default is 'portapack-dev'
|
||||
# Override by setting the following environment
|
||||
# variable: MAYHEM_DEV_DOCKER_IMAGE
|
||||
#
|
||||
|
||||
#
|
||||
# Copyright (C) 2024 u-foka
|
||||
#
|
||||
# This file is part of PortaPack.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; see the file COPYING. If not, write to
|
||||
# the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
|
||||
set -e # exit immediatelly on any failure
|
||||
|
||||
DIR="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )"
|
||||
|
||||
IMAGE="${MAYHEM_DEV_DOCKER_IMAGE:-portapack-dev}"
|
||||
|
||||
build_image() {
|
||||
DOCKERFILE=${1:-dockerfile-nogit}
|
||||
PLATFORM=${2:-amd64}
|
||||
docker build --platform "linux/${PLATFORM}" -t "${IMAGE}" -f "${DOCKERFILE}" .
|
||||
}
|
||||
|
||||
start_docker() {
|
||||
if [ -z "$(docker images -q ${IMAGE} 2> /dev/null)" ]; then
|
||||
build_image
|
||||
fi
|
||||
|
||||
exec docker run -v "${DIR}:/havoc" -u "$(id -u):$(id -g)" -ti --rm "${IMAGE}" "$@"
|
||||
}
|
||||
|
||||
if [ "$1" = 'shell' ]; then # open a shell into the container
|
||||
start_docker "bash -li"
|
||||
elif [ "$1" = 'build' ]; then # build the default (or specified) target with ninja
|
||||
shift # remove the first item from $@ as we consumed it, we can then pass the rest on to make
|
||||
build_image "$@"
|
||||
exit $?
|
||||
fi
|
||||
|
||||
start_docker "$@"
|
||||
@@ -1 +0,0 @@
|
||||
portapack.ried.cl
|
||||
+36
-9
@@ -18,7 +18,7 @@
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
project(firmware)
|
||||
|
||||
@@ -48,19 +48,46 @@ if(NOT ${CMAKE_CXX_COMPILER_VERSION} VERSION_EQUAL ${EXPECTED_GCC_VERSION})
|
||||
set(GCC_VERSION_MISMATCH 1)
|
||||
endif()
|
||||
|
||||
#generate h files
|
||||
configure_file(flashsize.h.in flashsize.h)
|
||||
add_compile_options(-include ${CMAKE_CURRENT_BINARY_DIR}/flashsize.h)
|
||||
|
||||
add_subdirectory(application)
|
||||
add_subdirectory(baseband)
|
||||
add_subdirectory(standalone)
|
||||
add_subdirectory(test)
|
||||
|
||||
# NOTE: Dependencies break if the .bin files aren't included in DEPENDS. WTF, CMake?
|
||||
add_custom_command(
|
||||
OUTPUT ${FIRMWARE_FILENAME}
|
||||
COMMAND ${MAKE_SPI_IMAGE} ${application_BINARY_DIR}/application.bin ${baseband_BINARY_DIR}/baseband.img ${FIRMWARE_FILENAME}
|
||||
DEPENDS baseband application ${MAKE_SPI_IMAGE}
|
||||
${baseband_BINARY_DIR}/baseband.img ${application_BINARY_DIR}/application.bin
|
||||
VERBATIM
|
||||
)
|
||||
add_dependencies(application.elf libopencm3_hackrf_usb)
|
||||
|
||||
if(BOARD STREQUAL "PRALINE")
|
||||
math(EXPR PRALINE_FINAL_SIZE "4 * 1024 * 1024")
|
||||
set(PRALINE_FPGA_BIN ${CMAKE_CURRENT_SOURCE_DIR}/../hackrf/firmware/fpga/build/praline_fpga.bin)
|
||||
set(APPEND_FPGA_SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/tools/append_fpga_bitstream.py)
|
||||
|
||||
# NOTE: Dependencies break if the .bin files aren't included in DEPENDS. WTF, CMake?
|
||||
add_custom_command(
|
||||
OUTPUT ${FIRMWARE_FILENAME}
|
||||
COMMAND ${MAKE_SPI_IMAGE} ${application_BINARY_DIR}/application.bin ${baseband_BINARY_DIR}/baseband.img ${FIRMWARE_FILENAME} ${FLASH_BYTES_LIMIT_SIZE}
|
||||
# PRALINE: Append FPGA bitstream to firmware at offset 0x380000
|
||||
# The base firmware is 3.5MB, FPGA bitstream gets appended at the end of it, final size is 4MB
|
||||
COMMAND ${CMAKE_COMMAND} -E echo "Appending PRALINE FPGA bitstream to firmware..."
|
||||
COMMAND python3 ${APPEND_FPGA_SCRIPT} ${FIRMWARE_FILENAME} ${PRALINE_FPGA_BIN} ${FIRMWARE_FILENAME}.tmp ${PRALINE_FINAL_SIZE}
|
||||
COMMAND ${CMAKE_COMMAND} -E rename ${FIRMWARE_FILENAME}.tmp ${FIRMWARE_FILENAME}
|
||||
DEPENDS baseband application ${MAKE_SPI_IMAGE}
|
||||
${baseband_BINARY_DIR}/baseband.img ${application_BINARY_DIR}/application.bin
|
||||
${PRALINE_FPGA_BIN} ${APPEND_FPGA_SCRIPT}
|
||||
VERBATIM
|
||||
)
|
||||
else() # Sadly this part had to be duplicated (or at least I couldn't find a better way to do it)
|
||||
# NOTE: Dependencies break if the .bin files aren't included in DEPENDS. WTF, CMake?
|
||||
add_custom_command(
|
||||
OUTPUT ${FIRMWARE_FILENAME}
|
||||
COMMAND ${MAKE_SPI_IMAGE} ${application_BINARY_DIR}/application.bin ${baseband_BINARY_DIR}/baseband.img ${FIRMWARE_FILENAME} ${FLASH_BYTES_LIMIT_SIZE}
|
||||
DEPENDS baseband application ${MAKE_SPI_IMAGE}
|
||||
${baseband_BINARY_DIR}/baseband.img ${application_BINARY_DIR}/application.bin
|
||||
VERBATIM
|
||||
)
|
||||
endif()
|
||||
|
||||
add_custom_target(
|
||||
firmware
|
||||
|
||||
@@ -86,6 +86,9 @@ set(CPLD_20150901_DATA_CPP ${CMAKE_CURRENT_BINARY_DIR}/portapack_cpld_20150901_d
|
||||
set(CPLD_20170522_SVF_PATH ${HARDWARE_PATH}/portapack_h1/cpld/20170522/output_files/portapack_h1_cpld.svf)
|
||||
set(CPLD_20170522_DATA_CPP ${CMAKE_CURRENT_BINARY_DIR}/portapack_cpld_20170522_data.cpp)
|
||||
|
||||
set(CPLD_H4M_SVF_PATH ${HARDWARE_PATH}/portapack_h4m/CPLD/AG256SL100/output_files/portapack_h4m_cpld.svf)
|
||||
set(CPLD_H4M_DATA_CPP ${CMAKE_CURRENT_BINARY_DIR}/portapack_cpld_h4m_data.cpp)
|
||||
|
||||
set(HACKRF_CPLD_DATA_HPP ${CMAKE_CURRENT_BINARY_DIR}/hackrf_cpld_data.hpp)
|
||||
set(HACKRF_CPLD_DATA_CPP ${CMAKE_CURRENT_BINARY_DIR}/hackrf_cpld_data.cpp)
|
||||
|
||||
@@ -118,11 +121,16 @@ set(CSRC
|
||||
usb_serial_endpoints.c
|
||||
usb_serial_device_to_host.c
|
||||
i2c_device_to_host.c
|
||||
hackrf_core_mini.c
|
||||
${HACKRF_PATH}/firmware/common/adc.c
|
||||
${HACKRF_PATH}/firmware/common/usb.c
|
||||
${HACKRF_PATH}/firmware/common/usb_queue.c
|
||||
${HACKRF_PATH}/firmware/hackrf_usb/usb_device.c
|
||||
${HACKRF_PATH}/firmware/common/usb_request.c
|
||||
${HACKRF_PATH}/firmware/common/usb_standard_request.c
|
||||
${HACKRF_PATH}/firmware/libopencm3/lib/lpc43xx/scu.c
|
||||
${HACKRF_PATH}/firmware/common/gpio_lpc.c
|
||||
${HACKRF_PATH}/firmware/common/platform_detect.c
|
||||
${CHIBIOS}/os/various/chprintf.c
|
||||
)
|
||||
|
||||
@@ -184,6 +192,7 @@ set(CPPSRC
|
||||
${COMMON}/battery.cpp
|
||||
${COMMON}/performance_counter.cpp
|
||||
${COMMON}/bmpfile.cpp
|
||||
${COMMON}/gpio.cpp
|
||||
app_settings.cpp
|
||||
audio.cpp
|
||||
baseband_api.cpp
|
||||
@@ -191,8 +200,7 @@ set(CPPSRC
|
||||
clock_manager.cpp
|
||||
core_control.cpp
|
||||
database.cpp
|
||||
de_bruijn.cpp
|
||||
rfm69.cpp
|
||||
gradient.cpp
|
||||
event_m0.cpp
|
||||
file_reader.cpp
|
||||
file.cpp
|
||||
@@ -206,6 +214,9 @@ set(CPPSRC
|
||||
irq_controls.cpp
|
||||
irq_lcd_frame.cpp
|
||||
irq_rtc.cpp
|
||||
keeloq_common.cpp
|
||||
keeloq_file.cpp
|
||||
keeloq_keystore.cpp
|
||||
log_file.cpp
|
||||
metadata_file.cpp
|
||||
flipper_subfile.cpp
|
||||
@@ -228,7 +239,6 @@ set(CPPSRC
|
||||
serializer.cpp
|
||||
spectrum_color_lut.cpp
|
||||
string_format.cpp
|
||||
temperature_logger.cpp
|
||||
theme.cpp
|
||||
touch.cpp
|
||||
tone_key.cpp
|
||||
@@ -238,6 +248,7 @@ set(CPPSRC
|
||||
hw/encoder.cpp
|
||||
hw/max2837.cpp
|
||||
hw/max2839.cpp
|
||||
hw/max2831.cpp
|
||||
hw/max5864.cpp
|
||||
hw/rffc507x.cpp
|
||||
hw/rffc507x_spi.cpp
|
||||
@@ -247,6 +258,7 @@ set(CPPSRC
|
||||
ook_file.cpp
|
||||
ui_baseband_stats_view.cpp
|
||||
ui_navigation.cpp
|
||||
ui_notifications.cpp
|
||||
ui_record_view.cpp
|
||||
ui_sd_card_status_view.cpp
|
||||
ui/ui_alphanum.cpp
|
||||
@@ -261,28 +273,19 @@ set(CPPSRC
|
||||
ui/ui_receiver.cpp
|
||||
ui/ui_rssi.cpp
|
||||
ui/ui_freqlist.cpp
|
||||
ui/ui_tv.cpp
|
||||
ui/ui_spectrum.cpp
|
||||
ui/ui_tabview.cpp
|
||||
ui/ui_textentry.cpp
|
||||
ui/ui_tone_key.cpp
|
||||
ui/ui_transmitter.cpp
|
||||
ui/ui_bmpview.cpp
|
||||
apps/ais_app.cpp
|
||||
apps/analog_audio_app.cpp
|
||||
apps/ble_comm_app.cpp
|
||||
apps/ble_rx_app.cpp
|
||||
apps/ble_tx_app.cpp
|
||||
apps/capture_app.cpp
|
||||
apps/ert_app.cpp
|
||||
apps/pocsag_app.cpp
|
||||
apps/soundboard_app.cpp
|
||||
apps/ui_about_simple.cpp
|
||||
apps/ui_adsb_rx.cpp
|
||||
apps/ui_aprs_rx.cpp
|
||||
apps/ui_aprs_tx.cpp
|
||||
apps/ui_battinfo.cpp
|
||||
apps/ui_bht_tx.cpp
|
||||
apps/ui_bmp_file_viewer.cpp
|
||||
apps/ui_btle_rx.cpp
|
||||
apps/ui_debug.cpp
|
||||
@@ -294,21 +297,15 @@ set(CPPSRC
|
||||
apps/ui_flash_utility.cpp
|
||||
apps/ui_freqman.cpp
|
||||
apps/ui_iq_trim.cpp
|
||||
apps/ui_level.cpp
|
||||
apps/ui_looking_glass_app.cpp
|
||||
apps/ui_mictx.cpp
|
||||
apps/ui_modemsetup.cpp
|
||||
apps/ui_playlist.cpp
|
||||
apps/ui_pocsag_tx.cpp
|
||||
apps/ui_rds.cpp
|
||||
apps/ui_recon_settings.cpp
|
||||
apps/ui_recon.cpp
|
||||
apps/ui_scanner.cpp
|
||||
apps/ui_sd_over_usb.cpp
|
||||
apps/ui_sd_wipe.cpp
|
||||
apps/ui_search.cpp
|
||||
apps/ui_settings.cpp
|
||||
apps/ui_siggen.cpp
|
||||
apps/ui_sonde.cpp
|
||||
apps/ui_ss_viewer.cpp
|
||||
apps/ui_standalone_view.cpp
|
||||
@@ -317,12 +314,9 @@ set(CPPSRC
|
||||
apps/ui_text_editor.cpp
|
||||
apps/ui_touch_calibration.cpp
|
||||
apps/ui_touchtunes.cpp
|
||||
apps/ui_view_wav.cpp
|
||||
apps/ui_weatherstation.cpp
|
||||
apps/ui_whipcalc.cpp
|
||||
protocols/aprs.cpp
|
||||
protocols/ax25.cpp
|
||||
protocols/bht.cpp
|
||||
protocols/dcs.cpp
|
||||
protocols/encoders.cpp
|
||||
protocols/modems.cpp
|
||||
@@ -331,6 +325,7 @@ set(CPPSRC
|
||||
config_mode.cpp
|
||||
${CPLD_20150901_DATA_CPP}
|
||||
${CPLD_20170522_DATA_CPP}
|
||||
${CPLD_H4M_DATA_CPP}
|
||||
${HACKRF_CPLD_DATA_CPP}
|
||||
ui_external_items_menu_loader.cpp
|
||||
view_factory_base.cpp
|
||||
@@ -377,7 +372,7 @@ set(INCDIR ${CMAKE_CURRENT_BINARY_DIR} ${COMMON} ${PORTINC} ${KERNINC} ${TESTINC
|
||||
hw
|
||||
apps
|
||||
protocols
|
||||
bitmaps
|
||||
bmps
|
||||
)
|
||||
|
||||
#
|
||||
@@ -416,7 +411,11 @@ set(CPPWARN "-Wall -Wextra -Wno-psabi")
|
||||
# List all default C defines here, like -D_DEBUG=1
|
||||
# TODO: Switch -DCRT0_INIT_DATA depending on load from RAM or SPIFI?
|
||||
# NOTE: _RANDOM_TCC to kill a GCC 4.9.3 error with std::max argument types
|
||||
set(DDEFS "-DLPC43XX -DLPC43XX_M0 -D__NEWLIB__ -DHACKRF_ONE -DTOOLCHAIN_GCC -DTOOLCHAIN_GCC_ARM -D_RANDOM_TCC=0 -D'VERSION_STRING=\"${VERSION}\"' -DVERSION_MD5=${VERSION_MD5} -D'GCC_VERSION_MISMATCH=${GCC_VERSION_MISMATCH}'")
|
||||
# Use BOARD variable if set, otherwise default to HACKRF_ONE
|
||||
if(NOT DEFINED BOARD)
|
||||
set(BOARD "HACKRF_ONE")
|
||||
endif()
|
||||
set(DDEFS "-DLPC43XX -DLPC43XX_M0 -D__NEWLIB__ -D${BOARD} -DTOOLCHAIN_GCC -DTOOLCHAIN_GCC_ARM -D_RANDOM_TCC=0 -D'VERSION_STRING=\"${VERSION}\"' -DVERSION_MD5=${VERSION_MD5} -D'GCC_VERSION_MISMATCH=${GCC_VERSION_MISMATCH}'")
|
||||
|
||||
# List all default ASM defines here, like -D_DEBUG=1
|
||||
set(DADEFS)
|
||||
@@ -474,6 +473,12 @@ add_custom_command(
|
||||
DEPENDS ${EXTRACT_CPLD_DATA} ${CPLD_20170522_SVF_PATH}
|
||||
)
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${CPLD_H4M_DATA_CPP}
|
||||
COMMAND ${EXTRACT_CPLD_DATA} ${CPLD_H4M_SVF_PATH} rev_h4m >${CPLD_H4M_DATA_CPP}
|
||||
DEPENDS ${EXTRACT_CPLD_DATA} ${CPLD_H4M_SVF_PATH}
|
||||
)
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${HACKRF_CPLD_DATA_CPP}
|
||||
COMMAND ${HACKRF_CPLD_TOOL} --xsvf ${HACKRF_CPLD_XSVF_PATH} --portapack-data ${HACKRF_CPLD_DATA_CPP}
|
||||
@@ -481,7 +486,13 @@ add_custom_command(
|
||||
)
|
||||
|
||||
add_executable(${PROJECT_NAME}.elf ${CSRC} ${CPPSRC} ${ASMSRC})
|
||||
set_target_properties(${PROJECT_NAME}.elf PROPERTIES LINK_DEPENDS ${LDSCRIPT})
|
||||
target_link_options(${PROJECT_NAME}.elf PRIVATE
|
||||
"LINKER:--defsym,LD_FLASH_SIZE=${FLASH_BYTES_LIMIT_SIZE}"
|
||||
)
|
||||
# NB: LPC43xx_M0.ld does INCLUDE external.ld, so that one has to be listed too -
|
||||
# otherwise editing the external app section rules silently does not relink.
|
||||
set_target_properties(${PROJECT_NAME}.elf PROPERTIES
|
||||
LINK_DEPENDS "${LDSCRIPT};${CMAKE_CURRENT_SOURCE_DIR}/external/external.ld")
|
||||
add_definitions(${DEFS})
|
||||
include_directories(. ${INCDIR})
|
||||
link_directories(${LLIBDIR})
|
||||
@@ -500,3 +511,5 @@ add_custom_target(
|
||||
${PROJECT_NAME}
|
||||
DEPENDS ${PROJECT_NAME}.bin
|
||||
)
|
||||
|
||||
add_dependencies(${PROJECT_NAME} baseband)
|
||||
|
||||
@@ -50,6 +50,9 @@ void BoundSetting::parse(std::string_view value) {
|
||||
case SettingType::I64:
|
||||
parse_int(value, as<int64_t>());
|
||||
break;
|
||||
case SettingType::U64:
|
||||
parse_int(value, as<uint64_t>());
|
||||
break;
|
||||
case SettingType::I32:
|
||||
parse_int(value, as<int32_t>());
|
||||
break;
|
||||
@@ -68,6 +71,9 @@ void BoundSetting::parse(std::string_view value) {
|
||||
as<bool>() = (parsed != 0);
|
||||
break;
|
||||
}
|
||||
case SettingType::Float:
|
||||
as<float>() = atof(value.data());
|
||||
break;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -84,6 +90,9 @@ void BoundSetting::write(File& file) const {
|
||||
case SettingType::I64:
|
||||
file.write(to_string_dec_int(as<int64_t>(), buffer, length), length);
|
||||
break;
|
||||
case SettingType::U64:
|
||||
file.write(to_string_dec_uint(as<uint64_t>(), buffer, length), length);
|
||||
break;
|
||||
case SettingType::I32:
|
||||
file.write(to_string_dec_int(as<int32_t>(), buffer, length), length);
|
||||
break;
|
||||
@@ -94,13 +103,17 @@ void BoundSetting::write(File& file) const {
|
||||
file.write(to_string_dec_uint(as<uint8_t>(), buffer, length), length);
|
||||
break;
|
||||
case SettingType::String: {
|
||||
const auto& str = as<std::string>();
|
||||
file.write(str.data(), str.length());
|
||||
break;
|
||||
file.write_line(as<std::string>());
|
||||
return;
|
||||
}
|
||||
case SettingType::Bool:
|
||||
file.write(as<bool>() ? "1" : "0", 1);
|
||||
break;
|
||||
|
||||
case SettingType::Float: {
|
||||
auto float_str = to_string_decimal(as<float>(), 6);
|
||||
file.write(float_str.c_str(), float_str.length());
|
||||
}
|
||||
}
|
||||
|
||||
file.write("\r\n", 2);
|
||||
@@ -188,6 +201,7 @@ void copy_to_radio_model(const AppSettings& settings) {
|
||||
settings.am_config_index,
|
||||
settings.nbfm_config_index,
|
||||
settings.wfm_config_index,
|
||||
settings.wfmam_config_index,
|
||||
settings.squelch);
|
||||
}
|
||||
|
||||
@@ -218,6 +232,7 @@ void copy_from_radio_model(AppSettings& settings) {
|
||||
settings.am_config_index = receiver_model.am_configuration();
|
||||
settings.nbfm_config_index = receiver_model.nbfm_configuration();
|
||||
settings.wfm_config_index = receiver_model.wfm_configuration();
|
||||
settings.wfmam_config_index = receiver_model.wfmam_configuration();
|
||||
}
|
||||
|
||||
settings.step = receiver_model.frequency_step();
|
||||
@@ -271,6 +286,7 @@ SettingsManager::SettingsManager(
|
||||
bindings_.emplace_back("am_config_index"sv, &settings_.am_config_index);
|
||||
bindings_.emplace_back("nbfm_config_index"sv, &settings_.nbfm_config_index);
|
||||
bindings_.emplace_back("wfm_config_index"sv, &settings_.wfm_config_index);
|
||||
bindings_.emplace_back("wfmam_config_index"sv, &settings_.wfmam_config_index);
|
||||
bindings_.emplace_back("squelch"sv, &settings_.squelch);
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include <variant>
|
||||
|
||||
#include "file.hpp"
|
||||
#include "hackrf_hal.hpp"
|
||||
#include "max283x.hpp"
|
||||
#include "string_format.hpp"
|
||||
|
||||
@@ -47,17 +48,22 @@ class BoundSetting {
|
||||
/* The type of bound setting. */
|
||||
enum class SettingType : uint8_t {
|
||||
I64,
|
||||
U64,
|
||||
I32,
|
||||
U32,
|
||||
U8,
|
||||
String,
|
||||
Bool,
|
||||
Float
|
||||
};
|
||||
|
||||
public:
|
||||
BoundSetting(std::string_view name, int64_t* target)
|
||||
: name_{name}, target_{target}, type_{SettingType::I64} {}
|
||||
|
||||
BoundSetting(std::string_view name, uint64_t* target)
|
||||
: name_{name}, target_{target}, type_{SettingType::U64} {}
|
||||
|
||||
BoundSetting(std::string_view name, int32_t* target)
|
||||
: name_{name}, target_{target}, type_{SettingType::I32} {}
|
||||
|
||||
@@ -73,6 +79,9 @@ class BoundSetting {
|
||||
BoundSetting(std::string_view name, bool* target)
|
||||
: name_{name}, target_{target}, type_{SettingType::Bool} {}
|
||||
|
||||
BoundSetting(std::string_view name, float* target)
|
||||
: name_{name}, target_{target}, type_{SettingType::Float} {}
|
||||
|
||||
std::string_view name() const { return name_; }
|
||||
void parse(std::string_view value);
|
||||
void write(File& file) const;
|
||||
@@ -130,7 +139,7 @@ struct AppSettings {
|
||||
uint8_t vga = 32;
|
||||
uint8_t rx_amp = 0;
|
||||
uint8_t tx_amp = 0;
|
||||
uint8_t tx_gain = 35;
|
||||
uint8_t tx_gain = hackrf::one::default_tx_gain_db;
|
||||
uint32_t channel_bandwidth = 1;
|
||||
uint32_t rx_frequency;
|
||||
uint32_t tx_frequency;
|
||||
@@ -139,6 +148,7 @@ struct AppSettings {
|
||||
uint8_t am_config_index = 0;
|
||||
uint8_t nbfm_config_index = 0;
|
||||
uint8_t wfm_config_index = 0;
|
||||
uint8_t wfmam_config_index = 0;
|
||||
uint8_t squelch = 80;
|
||||
uint8_t volume;
|
||||
// NOTE: update COMMON_APP_SETTINGS_COUNT when adding to this
|
||||
|
||||
@@ -40,6 +40,7 @@ namespace ui {
|
||||
/* AMOptionsView *********************************************************/
|
||||
|
||||
AMOptionsView::AMOptionsView(
|
||||
AnalogAudioView* view,
|
||||
Rect parent_rect,
|
||||
const Style* style)
|
||||
: View{parent_rect} {
|
||||
@@ -48,12 +49,23 @@ AMOptionsView::AMOptionsView(
|
||||
add_children({
|
||||
&label_config,
|
||||
&options_config,
|
||||
&zoom_config,
|
||||
});
|
||||
|
||||
freqman_set_bandwidth_option(AM_MODULATION, options_config); // adding the common message from freqman.cpp to the options_config
|
||||
options_config.set_by_value(receiver_model.am_configuration());
|
||||
options_config.on_change = [this](size_t, OptionsField::value_t n) {
|
||||
receiver_model.set_am_configuration(n);
|
||||
zoom_config.on_change = [this, view](size_t, OptionsField::value_t n) { // n , has two option values. when GUI =zoom+1 => (0), when GUI=zoom+2 (6)
|
||||
receiver_model.set_am_configuration(view->get_previous_AM_mode_option() + n); // n (0 or 6)
|
||||
view->set_zoom_factor(AM_MODULATION, n);
|
||||
view->set_previous_zoom_option(n);
|
||||
};
|
||||
|
||||
// restore zoom selection
|
||||
zoom_config.set_by_value(view->get_zoom_factor(AM_MODULATION));
|
||||
|
||||
freqman_set_bandwidth_option(AM_MODULATION, options_config); // freqman.cpp to the options_config, only allowing 5 modes freqman_bandwidths[AM] {"DSB 9k", 0}, {"DSB 6k", 1}, {"USB+3k", 2}, {"LSB-3k", 3}, {"CW", 4},
|
||||
options_config.set_by_value(receiver_model.am_configuration() - view->get_previous_zoom_option()); // restore AM GUI option mode , AM FIR index filters (0..11) values , <baseband::AMConfig, 12> am_configs has 12 fir index elements.
|
||||
options_config.on_change = [this, view](size_t, OptionsField::value_t n) {
|
||||
receiver_model.set_am_configuration(n + view->get_previous_zoom_option()); // we select proper FIR AM filter (0..11), = 0..4 GUI AM modes + offset +6 (if zoom+2)
|
||||
view->set_previous_AM_mode_option(n); // (0..4) allowing 5 AM modes (DSB9K, DSB6K, USB,LSB, CW)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -102,6 +114,54 @@ WFMOptionsView::WFMOptionsView(
|
||||
};
|
||||
}
|
||||
|
||||
/* WFMAMAptOptionsView *******************************************************/
|
||||
|
||||
WFMAMAptOptionsView::WFMAMAptOptionsView(
|
||||
Rect parent_rect,
|
||||
const Style* style)
|
||||
: View{parent_rect} {
|
||||
set_style(style);
|
||||
|
||||
add_children({
|
||||
&label_config,
|
||||
&options_config,
|
||||
});
|
||||
|
||||
freqman_set_bandwidth_option(WFMAM_MODULATION, options_config); // adding the common message from freqman.cpp to the options_config
|
||||
options_config.set_by_value(receiver_model.wfmam_configuration());
|
||||
options_config.on_change = [this](size_t, OptionsField::value_t n) {
|
||||
receiver_model.set_wfmam_configuration(n);
|
||||
};
|
||||
}
|
||||
|
||||
/* AMFMAptOptionsView *********************************************************/
|
||||
|
||||
AMFMAptOptionsView::AMFMAptOptionsView(
|
||||
AnalogAudioView* view,
|
||||
Rect parent_rect,
|
||||
const Style* style)
|
||||
: View{parent_rect} {
|
||||
set_style(style);
|
||||
|
||||
add_children({
|
||||
&label_config,
|
||||
&options_config,
|
||||
&zoom_config,
|
||||
});
|
||||
|
||||
freqman_set_bandwidth_option(AMFM_MODULATION, options_config); // adding the common message from freqman.cpp to the options_config
|
||||
receiver_model.set_amfm_configuration(5); // Fix index 5 manually, not from freqman: set to RX AM (USB+FM) mode to demod audio tone, and get Wefax_APT signal.
|
||||
options_config.set_by_value(receiver_model.amfm_configuration());
|
||||
|
||||
zoom_config.on_change = [this, view](size_t, OptionsField::value_t n) {
|
||||
receiver_model.set_amfm_configuration(5 + n);
|
||||
view->set_zoom_factor(AMFM_MODULATION, n);
|
||||
};
|
||||
|
||||
// restore zoom selection
|
||||
zoom_config.set_by_value(view->get_zoom_factor(AMFM_MODULATION));
|
||||
}
|
||||
|
||||
/* SPECOptionsView *******************************************************/
|
||||
|
||||
SPECOptionsView::SPECOptionsView(
|
||||
@@ -139,6 +199,55 @@ SPECOptionsView::SPECOptionsView(
|
||||
view->set_spec_iq_phase_calibration_value(view->get_spec_iq_phase_calibration_value()); // initialize iq_phase_calibration in radio
|
||||
}
|
||||
|
||||
/* PralineOptionsView *******************************************************/
|
||||
|
||||
#ifdef PRALINE
|
||||
PralineOptionsView::PralineOptionsView(Rect parent_rect, const Style* style) {
|
||||
set_style(style);
|
||||
set_parent_rect(parent_rect);
|
||||
add_children({&label_sr, &options_sr, &label_dc, &options_dc,
|
||||
&label_qi, &options_qi, &label_qs, &options_qs});
|
||||
|
||||
// READ the current state from hardware
|
||||
fpga_reg_1 = radio::debug::fpga::register_read(1);
|
||||
|
||||
// INITIALIZE UI WIDGETS based on read bits
|
||||
options_dc.set_by_value((fpga_reg_1 & 0x01) ? 1 : 0); // Bit 0
|
||||
options_qi.set_by_value((fpga_reg_1 & 0x02) ? 1 : 0); // Bit 1
|
||||
options_qs.set_by_value((fpga_reg_1 & 0x04) ? 1 : 0); // Bit 2
|
||||
options_sr.set_value(receiver_model.sampling_rate());
|
||||
|
||||
options_sr.on_change = [this](int32_t v) { receiver_model.set_sampling_rate(static_cast<uint32_t>(v)); };
|
||||
options_dc.on_change = [this](size_t, OptionsField::value_t v) {
|
||||
if (v)
|
||||
fpga_reg_1 |= 0x01;
|
||||
else
|
||||
fpga_reg_1 &= ~0x01;
|
||||
update_fpga_ctrl();
|
||||
};
|
||||
|
||||
options_qi.on_change = [this](size_t, OptionsField::value_t v) {
|
||||
if (v)
|
||||
fpga_reg_1 |= 0x02;
|
||||
else
|
||||
fpga_reg_1 &= ~0x02;
|
||||
update_fpga_ctrl();
|
||||
};
|
||||
|
||||
options_qs.on_change = [this](size_t, OptionsField::value_t v) {
|
||||
if (v)
|
||||
fpga_reg_1 |= 0x04;
|
||||
else
|
||||
fpga_reg_1 &= ~0x04;
|
||||
update_fpga_ctrl();
|
||||
};
|
||||
}
|
||||
|
||||
void PralineOptionsView::update_fpga_ctrl() {
|
||||
radio::debug::fpga::register_write(1, fpga_reg_1);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* AnalogAudioView *******************************************************/
|
||||
|
||||
AnalogAudioView::AnalogAudioView(
|
||||
@@ -157,7 +266,13 @@ AnalogAudioView::AnalogAudioView(
|
||||
&field_volume,
|
||||
&text_ctcss,
|
||||
&record_view,
|
||||
&waterfall});
|
||||
#ifdef PRALINE
|
||||
&waterfall,
|
||||
&button_pro
|
||||
#else
|
||||
&waterfall
|
||||
#endif
|
||||
});
|
||||
|
||||
// Filename Datetime and Frequency
|
||||
record_view.set_filename_date_frequency(true);
|
||||
@@ -165,6 +280,12 @@ AnalogAudioView::AnalogAudioView(
|
||||
field_frequency.on_show_options = [this]() {
|
||||
this->on_show_options_frequency();
|
||||
};
|
||||
field_frequency.changing = [this](rf::Frequency frequency) {
|
||||
return this->on_frequency_changed(frequency);
|
||||
};
|
||||
field_frequency.entered = [this](rf::Frequency frequency) {
|
||||
this->set_frequency_absolute(frequency);
|
||||
};
|
||||
|
||||
field_lna.on_show_options = [this]() {
|
||||
this->on_show_options_rf_gain();
|
||||
@@ -175,8 +296,9 @@ AnalogAudioView::AnalogAudioView(
|
||||
};
|
||||
|
||||
auto modulation = receiver_model.modulation();
|
||||
|
||||
// This app doesn't handle "Capture" mode.
|
||||
if (modulation > ReceiverModel::Mode::SpectrumAnalysis)
|
||||
if (modulation == ReceiverModel::Mode::Capture)
|
||||
modulation = ReceiverModel::Mode::SpectrumAnalysis;
|
||||
|
||||
options_modulation.set_by_value(toUType(modulation));
|
||||
@@ -192,13 +314,20 @@ AnalogAudioView::AnalogAudioView(
|
||||
};
|
||||
|
||||
waterfall.on_select = [this](int32_t offset) {
|
||||
field_frequency.set_value(receiver_model.target_frequency() + offset);
|
||||
field_frequency.set_value(
|
||||
field_frequency.value() + offset * receiver_model.frequency_step());
|
||||
};
|
||||
waterfall.set_live_tuning(true);
|
||||
|
||||
#ifdef PRALINE
|
||||
button_pro.on_select = [this](Button&) { this->on_show_options_praline(); };
|
||||
#endif
|
||||
|
||||
audio::output::start();
|
||||
|
||||
// This call starts the correct baseband image to run
|
||||
// and sets the radio up as necessary for the given modulation.
|
||||
sliding_center_frequency = receiver_model.target_frequency();
|
||||
on_modulation_changed(modulation);
|
||||
}
|
||||
|
||||
@@ -208,7 +337,7 @@ AnalogAudioView::AnalogAudioView(
|
||||
: AnalogAudioView(nav) {
|
||||
// Settings to override when launched from another app (versus from AppSettings .ini file)
|
||||
// TODO: Which other settings make sense to override?
|
||||
field_frequency.set_value(override.frequency_app_override);
|
||||
set_frequency_absolute(override.frequency_app_override);
|
||||
on_frequency_step_changed(override.frequency_step);
|
||||
options_modulation.set_by_value(toUType(override.mode));
|
||||
}
|
||||
@@ -226,11 +355,42 @@ void AnalogAudioView::set_spec_bw(size_t index, uint32_t bw) {
|
||||
receiver_model.set_baseband_bandwidth(bw / 2);
|
||||
}
|
||||
|
||||
uint8_t AnalogAudioView::get_spec_iq_phase_calibration_value() { // define accessor functions inside AnalogAudioView to read & write real iq_phase_calibration_value
|
||||
uint8_t AnalogAudioView::get_zoom_factor(uint8_t mode) { // define accessor functions inside AnalogAudioView to read zoom value
|
||||
if (mode == AM_MODULATION)
|
||||
return zoom_factor_am;
|
||||
else if (mode == AMFM_MODULATION)
|
||||
return zoom_factor_amfm;
|
||||
return 0; // default if unsupported mode
|
||||
}
|
||||
|
||||
void AnalogAudioView::set_zoom_factor(uint8_t mode, uint8_t zoom) { // define accessor functions inside AnalogAudioView to write zoom value
|
||||
if (mode == AM_MODULATION)
|
||||
zoom_factor_am = zoom;
|
||||
else if (mode == AMFM_MODULATION)
|
||||
zoom_factor_amfm = zoom;
|
||||
}
|
||||
|
||||
uint8_t AnalogAudioView::get_previous_AM_mode_option() {
|
||||
return previous_AM_mode_option;
|
||||
}
|
||||
|
||||
void AnalogAudioView::set_previous_AM_mode_option(uint8_t mode) {
|
||||
previous_AM_mode_option = mode;
|
||||
}
|
||||
|
||||
uint8_t AnalogAudioView::get_previous_zoom_option() {
|
||||
return previous_zoom;
|
||||
}
|
||||
|
||||
void AnalogAudioView::set_previous_zoom_option(uint8_t zoom) {
|
||||
previous_zoom = zoom;
|
||||
}
|
||||
|
||||
uint8_t AnalogAudioView::get_spec_iq_phase_calibration_value() { // define accessor functions inside AnalogAudioView to read iq_phase_calibration_value
|
||||
return iq_phase_calibration_value;
|
||||
}
|
||||
|
||||
void AnalogAudioView::set_spec_iq_phase_calibration_value(uint8_t cal_value) { // define accessor functions
|
||||
void AnalogAudioView::set_spec_iq_phase_calibration_value(uint8_t cal_value) { // define accessor functions inside AnalogAudioView to write iq_phase_calibration_value
|
||||
iq_phase_calibration_value = cal_value;
|
||||
radio::set_rx_max283x_iq_phase_calibration(iq_phase_calibration_value);
|
||||
}
|
||||
@@ -246,6 +406,7 @@ void AnalogAudioView::set_spec_trigger(uint16_t trigger) {
|
||||
}
|
||||
|
||||
AnalogAudioView::~AnalogAudioView() {
|
||||
receiver_model.set_hidden_offset(0);
|
||||
audio::output::stop();
|
||||
receiver_model.disable();
|
||||
baseband::shutdown();
|
||||
@@ -267,14 +428,10 @@ void AnalogAudioView::on_baseband_bandwidth_changed(uint32_t bandwidth_hz) {
|
||||
}
|
||||
|
||||
void AnalogAudioView::on_modulation_changed(ReceiverModel::Mode modulation) {
|
||||
// This app doesn't know what to do with "Capture" mode.
|
||||
if (modulation > ReceiverModel::Mode::SpectrumAnalysis)
|
||||
modulation = ReceiverModel::Mode::SpectrumAnalysis;
|
||||
|
||||
baseband::spectrum_streaming_stop();
|
||||
waterfall.stop();
|
||||
update_modulation(modulation);
|
||||
on_show_options_modulation();
|
||||
baseband::spectrum_streaming_start();
|
||||
waterfall.start();
|
||||
}
|
||||
|
||||
void AnalogAudioView::remove_options_widget() {
|
||||
@@ -329,7 +486,7 @@ void AnalogAudioView::on_show_options_modulation() {
|
||||
const auto modulation = receiver_model.modulation();
|
||||
switch (modulation) {
|
||||
case ReceiverModel::Mode::AMAudio:
|
||||
widget = std::make_unique<AMOptionsView>(options_view_rect, Theme::getInstance()->option_active);
|
||||
widget = std::make_unique<AMOptionsView>(this, options_view_rect, Theme::getInstance()->option_active);
|
||||
waterfall.show_audio_spectrum_view(false);
|
||||
text_ctcss.hidden(true);
|
||||
break;
|
||||
@@ -346,6 +503,18 @@ void AnalogAudioView::on_show_options_modulation() {
|
||||
text_ctcss.hidden(true);
|
||||
break;
|
||||
|
||||
case ReceiverModel::Mode::WFMAudioAMApt:
|
||||
widget = std::make_unique<WFMAMAptOptionsView>(options_view_rect, Theme::getInstance()->option_active);
|
||||
waterfall.show_audio_spectrum_view(true);
|
||||
text_ctcss.hidden(true);
|
||||
break;
|
||||
|
||||
case ReceiverModel::Mode::AMAudioFMApt:
|
||||
widget = std::make_unique<AMFMAptOptionsView>(this, options_view_rect, Theme::getInstance()->option_active);
|
||||
waterfall.show_audio_spectrum_view(false);
|
||||
text_ctcss.hidden(true);
|
||||
break;
|
||||
|
||||
case ReceiverModel::Mode::SpectrumAnalysis:
|
||||
widget = std::make_unique<SPECOptionsView>(this, nbfm_view_rect, Theme::getInstance()->option_active);
|
||||
waterfall.show_audio_spectrum_view(false);
|
||||
@@ -387,6 +556,12 @@ void AnalogAudioView::update_modulation(ReceiverModel::Mode modulation) {
|
||||
case ReceiverModel::Mode::WidebandFMAudio:
|
||||
image_tag = portapack::spi_flash::image_tag_wfm_audio;
|
||||
break;
|
||||
case ReceiverModel::Mode::WFMAudioAMApt:
|
||||
image_tag = portapack::spi_flash::image_tag_wfm_audio;
|
||||
break;
|
||||
case ReceiverModel::Mode::AMAudioFMApt:
|
||||
image_tag = portapack::spi_flash::image_tag_am_audio;
|
||||
break;
|
||||
case ReceiverModel::Mode::SpectrumAnalysis:
|
||||
image_tag = portapack::spi_flash::image_tag_wideband_spectrum;
|
||||
break;
|
||||
@@ -407,8 +582,14 @@ void AnalogAudioView::update_modulation(ReceiverModel::Mode modulation) {
|
||||
receiver_model.set_sampling_rate(is_wideband_spectrum_mode ? spec_bw : 3072000);
|
||||
receiver_model.set_baseband_bandwidth(is_wideband_spectrum_mode ? spec_bw / 2 : 1750000);
|
||||
|
||||
reset_sliding_frequency(modulation);
|
||||
|
||||
receiver_model.enable();
|
||||
|
||||
if (sliding_enabled) {
|
||||
baseband::set_audio_ddc_frequency(0);
|
||||
}
|
||||
|
||||
// TODO: This doesn't belong here! There's a better way.
|
||||
size_t sampling_rate = 0;
|
||||
switch (modulation) {
|
||||
@@ -421,6 +602,12 @@ void AnalogAudioView::update_modulation(ReceiverModel::Mode modulation) {
|
||||
case ReceiverModel::Mode::WidebandFMAudio:
|
||||
sampling_rate = 48000;
|
||||
break;
|
||||
case ReceiverModel::Mode::WFMAudioAMApt:
|
||||
sampling_rate = 12000;
|
||||
break;
|
||||
case ReceiverModel::Mode::AMAudioFMApt:
|
||||
sampling_rate = 12000;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -436,6 +623,70 @@ void AnalogAudioView::handle_coded_squelch(uint32_t value) {
|
||||
}
|
||||
|
||||
void AnalogAudioView::on_freqchg(int64_t freq) {
|
||||
field_frequency.set_value(freq);
|
||||
set_frequency_absolute(freq);
|
||||
}
|
||||
|
||||
void AnalogAudioView::set_frequency_absolute(rf::Frequency frequency) {
|
||||
if (!sliding_enabled) {
|
||||
field_frequency.set_value(frequency);
|
||||
return;
|
||||
}
|
||||
|
||||
sliding_center_frequency = frequency;
|
||||
|
||||
/* set_value() does not call on_change when the displayed frequency is
|
||||
* already equal, so reset the hardware and DDC explicitly in that case. */
|
||||
if (field_frequency.value() == frequency) {
|
||||
receiver_model.set_target_frequency_with_hidden_offset(frequency, 0);
|
||||
baseband::set_audio_ddc_frequency(0);
|
||||
} else {
|
||||
field_frequency.set_value(frequency);
|
||||
}
|
||||
}
|
||||
|
||||
int32_t AnalogAudioView::sliding_limit() const {
|
||||
const bool zoom_x2 =
|
||||
receiver_model.modulation() == ReceiverModel::Mode::AMAudio &&
|
||||
previous_zoom != 0;
|
||||
return zoom_x2 ? sliding_limit_zoom_x2 : sliding_limit_zoom_x1;
|
||||
}
|
||||
|
||||
void AnalogAudioView::reset_sliding_frequency(ReceiverModel::Mode modulation) {
|
||||
sliding_enabled =
|
||||
modulation == ReceiverModel::Mode::AMAudio ||
|
||||
modulation == ReceiverModel::Mode::NarrowbandFMAudio;
|
||||
sliding_center_frequency = receiver_model.target_frequency();
|
||||
|
||||
/* AMFM keeps its existing Wefax offset; sliding applies to AM and NFM. */
|
||||
receiver_model.set_hidden_offset(
|
||||
modulation == ReceiverModel::Mode::AMAudioFMApt ? -2200 : 0);
|
||||
}
|
||||
|
||||
bool AnalogAudioView::on_frequency_changed(rf::Frequency frequency) {
|
||||
if (!sliding_enabled)
|
||||
return false;
|
||||
|
||||
const auto limit = sliding_limit();
|
||||
int64_t offset = frequency - sliding_center_frequency;
|
||||
if (offset > limit) {
|
||||
sliding_center_frequency = frequency - limit;
|
||||
offset = limit;
|
||||
} else if (offset < -limit) {
|
||||
sliding_center_frequency = frequency + limit;
|
||||
offset = -limit;
|
||||
}
|
||||
|
||||
/* Store the displayed frequency and retune the hardware centre atomically. */
|
||||
receiver_model.set_target_frequency_with_hidden_offset(
|
||||
frequency, sliding_center_frequency - frequency);
|
||||
baseband::set_audio_ddc_frequency(static_cast<int32_t>(offset));
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef PRALINE
|
||||
void AnalogAudioView::on_show_options_praline() {
|
||||
set_options_widget(std::make_unique<PralineOptionsView>(options_view_rect, Theme::getInstance()->option_active));
|
||||
}
|
||||
#endif
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -35,22 +35,88 @@
|
||||
|
||||
namespace ui {
|
||||
|
||||
class AnalogAudioView;
|
||||
|
||||
#ifdef PRALINE
|
||||
class PralineOptionsView : public View {
|
||||
public:
|
||||
PralineOptionsView(Rect parent_rect, const Style* style);
|
||||
|
||||
private:
|
||||
// Layout: SR (Sample Rate), DC (DC Block), QI (Q-Invert), QS (Quarter Shift), D (Decim)
|
||||
Text label_sr{{UI_POS_X(0), UI_POS_Y(0), UI_POS_WIDTH(2), UI_POS_HEIGHT(1)}, "SR"};
|
||||
|
||||
// Parameters: Position, Length (in chars), Range, Step, and default value
|
||||
NumberField options_sr{
|
||||
{UI_POS_X(3), UI_POS_Y(0)},
|
||||
8, // Number of digits to show
|
||||
{0, 40000000}, // Range: 0 to 40,000 kHz
|
||||
100, // Step: 100 Hz
|
||||
' ' // Fix: Single quotes for char
|
||||
};
|
||||
|
||||
Text label_dc{{UI_POS_X(14), UI_POS_Y(0), UI_POS_WIDTH(2), UI_POS_HEIGHT(1)}, "DC"};
|
||||
OptionsField options_dc{{UI_POS_X(16), UI_POS_Y(0)}, 2, {{"Of", 0}, {"On", 1}}};
|
||||
|
||||
Text label_qi{{UI_POS_X(20), UI_POS_Y(0), UI_POS_WIDTH(2), UI_POS_HEIGHT(1)}, "QI"};
|
||||
OptionsField options_qi{{UI_POS_X(22), UI_POS_Y(0)}, 2, {{"Of", 0}, {"On", 1}}};
|
||||
|
||||
Text label_qs{{UI_POS_X(26), UI_POS_Y(0), UI_POS_WIDTH(2), UI_POS_HEIGHT(1)}, "QS"};
|
||||
OptionsField options_qs{{UI_POS_X(28), UI_POS_Y(0)}, 2, {{"Of", 0}, {"On", 1}}};
|
||||
|
||||
uint8_t fpga_reg_1{0x00}; // Tracks DC, QI, QS bits
|
||||
void update_fpga_ctrl();
|
||||
};
|
||||
#endif
|
||||
|
||||
class AMOptionsView : public View {
|
||||
public:
|
||||
AMOptionsView(Rect parent_rect, const Style* style);
|
||||
AMOptionsView(AnalogAudioView* view, Rect parent_rect, const Style* style);
|
||||
|
||||
private:
|
||||
Text label_config{
|
||||
{0 * 8, 0 * 16, 2 * 8, 1 * 16},
|
||||
{UI_POS_X(0), UI_POS_Y(0), UI_POS_WIDTH(2), UI_POS_HEIGHT(1)},
|
||||
"BW",
|
||||
};
|
||||
|
||||
OptionsField options_config{
|
||||
{3 * 8, 0 * 16},
|
||||
{UI_POS_X(3), UI_POS_Y(0)},
|
||||
6, // Max option length
|
||||
{
|
||||
// Using common messages from freqman_ui.cpp
|
||||
}};
|
||||
|
||||
OptionsField zoom_config{
|
||||
{UI_POS_X_RIGHT(7), UI_POS_Y(0)},
|
||||
7,
|
||||
{{"ZOOM x1", 0},
|
||||
{"ZOOM x2", 6}} // offset index AM modes array FIR filters.
|
||||
};
|
||||
};
|
||||
|
||||
class AMFMAptOptionsView : public View {
|
||||
public:
|
||||
AMFMAptOptionsView(AnalogAudioView* view, Rect parent_rect, const Style* style);
|
||||
|
||||
private:
|
||||
Text label_config{
|
||||
{UI_POS_X(0), UI_POS_Y(0), UI_POS_WIDTH(2), UI_POS_HEIGHT(1)},
|
||||
"BW",
|
||||
};
|
||||
|
||||
OptionsField options_config{
|
||||
{UI_POS_X(3), UI_POS_Y(0)},
|
||||
17, // Max option length chars "USB+FM(Wefax Apt)"
|
||||
{
|
||||
// Using common messages from freqman_ui.cpp In HF USB , Here we only need USB Audio demod, + post-FM demod fsubcarrier FM tone to get APT signal.
|
||||
}};
|
||||
|
||||
OptionsField zoom_config{
|
||||
{UI_POS_X_RIGHT(7), UI_POS_Y(0)},
|
||||
7,
|
||||
{{"ZOOM x1", 0},
|
||||
{"ZOOM x2", 6}} // offset index array filters.
|
||||
};
|
||||
};
|
||||
|
||||
class NBFMOptionsView : public View {
|
||||
@@ -59,21 +125,21 @@ class NBFMOptionsView : public View {
|
||||
|
||||
private:
|
||||
Text label_config{
|
||||
{0 * 8, 0 * 16, 2 * 8, 1 * 16},
|
||||
{UI_POS_X(0), UI_POS_Y(0), UI_POS_WIDTH(2), UI_POS_HEIGHT(1)},
|
||||
"BW",
|
||||
};
|
||||
OptionsField options_config{
|
||||
{3 * 8, 0 * 16},
|
||||
{UI_POS_X(3), UI_POS_Y(0)},
|
||||
3, // Max option length
|
||||
{
|
||||
// Using common messages from freqman_ui.cpp
|
||||
}};
|
||||
|
||||
Text text_squelch{
|
||||
{7 * 8, 0 * 16, 8 * 8, 1 * 16},
|
||||
{UI_POS_X(7), UI_POS_Y(0), UI_POS_WIDTH(8), UI_POS_HEIGHT(1)},
|
||||
"SQ /99"};
|
||||
NumberField field_squelch{
|
||||
{10 * 8, 0 * 16},
|
||||
{UI_POS_X(10), UI_POS_Y(0)},
|
||||
2,
|
||||
{0, 99},
|
||||
1,
|
||||
@@ -87,18 +153,33 @@ class WFMOptionsView : public View {
|
||||
|
||||
private:
|
||||
Text label_config{
|
||||
{0 * 8, 0 * 16, 2 * 8, 1 * 16},
|
||||
{UI_POS_X(0), UI_POS_Y(0), UI_POS_WIDTH(2), UI_POS_HEIGHT(1)},
|
||||
"BW",
|
||||
};
|
||||
OptionsField options_config{
|
||||
{3 * 8, 0 * 16},
|
||||
{UI_POS_X(3), UI_POS_Y(0)},
|
||||
4, // Max option length
|
||||
{
|
||||
// Using common messages from freqman_ui.cpp
|
||||
}};
|
||||
};
|
||||
|
||||
class AnalogAudioView;
|
||||
class WFMAMAptOptionsView : public View {
|
||||
public:
|
||||
WFMAMAptOptionsView(Rect parent_rect, const Style* style);
|
||||
|
||||
private:
|
||||
Text label_config{
|
||||
{UI_POS_X(0), UI_POS_Y(0), UI_POS_WIDTH(2), UI_POS_HEIGHT(1)},
|
||||
"BW",
|
||||
};
|
||||
OptionsField options_config{
|
||||
{UI_POS_X(3), UI_POS_Y(0)},
|
||||
16, // Max option char length "80k-NOAA Apt LPF" , example.
|
||||
{
|
||||
// Using common messages from freqman_ui.cpp
|
||||
}};
|
||||
};
|
||||
|
||||
class SPECOptionsView : public View {
|
||||
public:
|
||||
@@ -106,11 +187,11 @@ class SPECOptionsView : public View {
|
||||
|
||||
private:
|
||||
Text label_config{
|
||||
{0 * 8, 0 * 16, 2 * 8, 1 * 16},
|
||||
{UI_POS_X(0), UI_POS_Y(0), UI_POS_WIDTH(2), UI_POS_HEIGHT(1)},
|
||||
"BW",
|
||||
};
|
||||
OptionsField options_config{
|
||||
{3 * 8, 0 * 16},
|
||||
{UI_POS_X(3), UI_POS_Y(0)},
|
||||
4,
|
||||
{
|
||||
{"20m ", 20000000},
|
||||
@@ -123,20 +204,20 @@ class SPECOptionsView : public View {
|
||||
}};
|
||||
|
||||
Text text_speed{
|
||||
{9 * 8, 0 * 16, 8 * 8, 1 * 16},
|
||||
{UI_POS_X(9), UI_POS_Y(0), UI_POS_WIDTH(8), UI_POS_HEIGHT(1)},
|
||||
"SP /63"};
|
||||
NumberField field_speed{
|
||||
{12 * 8, 0 * 16},
|
||||
{UI_POS_X(12), UI_POS_Y(0)},
|
||||
2,
|
||||
{0, 63},
|
||||
1,
|
||||
' ',
|
||||
};
|
||||
Text text_rx_cal{
|
||||
{19 * 8, 0 * 16, 11 * 8, 1 * 16}, // 18 (x col.) x char_size, 12 (length) x 8 blanking space to delete previous chars.
|
||||
{UI_POS_X(19), UI_POS_Y(0), UI_POS_WIDTH(11), UI_POS_HEIGHT(1)}, // 18 (x col.) x char_size, 12 (length) x 8 blanking space to delete previous chars.
|
||||
"Rx_IQ_CAL "};
|
||||
NumberField field_rx_iq_phase_cal{
|
||||
{28 * 8, 0 * 16},
|
||||
{screen_width - 2 * 8, UI_POS_Y(0)},
|
||||
2,
|
||||
{0, 63}, // 5 or 6 bits IQ CAL phase adjustment (range updated later)
|
||||
1,
|
||||
@@ -164,66 +245,95 @@ class AnalogAudioView : public View {
|
||||
uint8_t get_spec_iq_phase_calibration_value();
|
||||
void set_spec_iq_phase_calibration_value(uint8_t cal_value);
|
||||
|
||||
uint8_t get_zoom_factor(uint8_t mode);
|
||||
void set_zoom_factor(uint8_t mode, uint8_t zoom);
|
||||
|
||||
uint8_t get_previous_AM_mode_option();
|
||||
void set_previous_AM_mode_option(uint8_t mode);
|
||||
|
||||
uint8_t get_previous_zoom_option();
|
||||
void set_previous_zoom_option(uint8_t zoom);
|
||||
|
||||
#ifdef PRALINE
|
||||
Button button_pro{{UI_POS_X(12), UI_POS_Y(2), UI_POS_WIDTH(3), UI_POS_HEIGHT(1)}, "PRO"};
|
||||
void on_show_options_praline();
|
||||
#endif
|
||||
|
||||
private:
|
||||
static constexpr ui::Dim header_height = 3 * 16;
|
||||
|
||||
NavigationView& nav_;
|
||||
RxRadioState radio_state_{};
|
||||
uint8_t iq_phase_calibration_value{15}; // initial default RX IQ phase calibration value , used for both max2837 & max2839
|
||||
uint8_t zoom_factor_am{0}; // initial zoom factor in AM mode
|
||||
uint8_t zoom_factor_amfm{0}; // initial zoom factor in AMFM mode
|
||||
uint8_t previous_AM_mode_option{0}; // GUI 5 AM modes : (0..4 ) (DSB9K, DSB6K, USB,LSB, CW). Used to select proper FIR filter (0..11) AM mode + offset 0 (zoom+1) or +6 (if zoom+2)
|
||||
uint8_t previous_zoom{0}; // GUI ZOOM+1, ZOOM+2 , equivalent to two values offset 0 (zoom+1) or +6 (if zoom+2)
|
||||
static constexpr int32_t sliding_limit_zoom_x1 = 50000;
|
||||
static constexpr int32_t sliding_limit_zoom_x2 = 30000;
|
||||
rf::Frequency sliding_center_frequency{0};
|
||||
bool sliding_enabled{false};
|
||||
|
||||
app_settings::SettingsManager settings_{
|
||||
"rx_audio",
|
||||
app_settings::Mode::RX,
|
||||
{
|
||||
{"iq_phase_calibration"sv, &iq_phase_calibration_value}, // we are saving and restoring that CAL from Settings.
|
||||
{"zoom_factor_am"sv, &zoom_factor_am}, // we are saving and restoring AM ZOOM factor from Settings.
|
||||
{"zoom_factor_amfm"sv, &zoom_factor_amfm}, // we are saving and restoring AMFM ZOOM factor from Settings.
|
||||
{"previous_AM_mode_option"sv, &previous_AM_mode_option}, // we are saving and restoring AMFM ZOOM factor from Settings.
|
||||
{"previous_zoom"sv, &previous_zoom}, // we are saving and restoring AMFM ZOOM factor from Settings.
|
||||
}};
|
||||
|
||||
const Rect options_view_rect{0 * 8, 1 * 16, 30 * 8, 1 * 16};
|
||||
const Rect nbfm_view_rect{0 * 8, 1 * 16, 18 * 8, 1 * 16};
|
||||
const Rect options_view_rect{UI_POS_X(0), UI_POS_Y(1), UI_POS_MAXWIDTH, UI_POS_HEIGHT(1)};
|
||||
const Rect nbfm_view_rect{UI_POS_X(0), UI_POS_Y(1), UI_POS_WIDTH(18), UI_POS_HEIGHT(1)};
|
||||
|
||||
size_t spec_bw_index = 0;
|
||||
uint32_t spec_bw = 20000000;
|
||||
uint16_t spec_trigger = 63;
|
||||
|
||||
RSSI rssi{
|
||||
{21 * 8, 0, 6 * 8, 4}};
|
||||
{UI_POS_X(21), 0, UI_POS_WIDTH_REMAINING(21) - UI_POS_WIDTH(2), 4}};
|
||||
|
||||
Channel channel{
|
||||
{21 * 8, 5, 6 * 8, 4}};
|
||||
{UI_POS_X(21), 5, UI_POS_WIDTH_REMAINING(21) - UI_POS_WIDTH(2), 4}};
|
||||
|
||||
Audio audio{
|
||||
{21 * 8, 10, 6 * 8, 4}};
|
||||
{UI_POS_X(21), 10, UI_POS_WIDTH_REMAINING(21) - UI_POS_WIDTH(2), 4}};
|
||||
|
||||
RxFrequencyField field_frequency{
|
||||
{5 * 8, 0 * 16},
|
||||
{UI_POS_X(5), UI_POS_Y(0)},
|
||||
nav_};
|
||||
|
||||
LNAGainField field_lna{
|
||||
{15 * 8, 0 * 16}};
|
||||
{UI_POS_X(15), UI_POS_Y(0)}};
|
||||
|
||||
VGAGainField field_vga{
|
||||
{18 * 8, 0 * 16}};
|
||||
{UI_POS_X(18), UI_POS_Y(0)}};
|
||||
|
||||
OptionsField options_modulation{
|
||||
{0 * 8, 0 * 16},
|
||||
{UI_POS_X(0), UI_POS_Y(0)},
|
||||
4,
|
||||
{
|
||||
{" AM ", toUType(ReceiverModel::Mode::AMAudio)},
|
||||
{"NFM ", toUType(ReceiverModel::Mode::NarrowbandFMAudio)},
|
||||
{"WFM ", toUType(ReceiverModel::Mode::WidebandFMAudio)},
|
||||
{"SPEC", toUType(ReceiverModel::Mode::SpectrumAnalysis)},
|
||||
{"AMFM", toUType(ReceiverModel::Mode::AMAudioFMApt)}, // Added to handle HF WeatherFax , SSB (USB demod) + Tone_Subcarrier FM demod
|
||||
{"FMAM", toUType(ReceiverModel::Mode::WFMAudioAMApt)} // Added to handle SAT NOAA APT
|
||||
}};
|
||||
|
||||
AudioVolumeField field_volume{
|
||||
{28 * 8, 0 * 16}};
|
||||
{screen_width - 2 * 8, UI_POS_Y(0)}};
|
||||
|
||||
Text text_ctcss{
|
||||
{16 * 8, 1 * 16, 14 * 8, 1 * 16},
|
||||
{UI_POS_X(16), UI_POS_Y(1), UI_POS_WIDTH(14), UI_POS_HEIGHT(1)},
|
||||
""};
|
||||
|
||||
std::unique_ptr<Widget> options_widget{};
|
||||
|
||||
RecordView record_view{
|
||||
{0 * 8, 2 * 16, 30 * 8, 1 * 16},
|
||||
{UI_POS_X(0), UI_POS_Y(2), UI_POS_MAXWIDTH, UI_POS_HEIGHT(1)},
|
||||
u"AUD",
|
||||
u"AUDIO",
|
||||
RecordView::FileType::WAV,
|
||||
@@ -248,6 +358,10 @@ class AnalogAudioView : public View {
|
||||
void handle_coded_squelch(uint32_t value);
|
||||
|
||||
void on_freqchg(int64_t freq);
|
||||
bool on_frequency_changed(rf::Frequency frequency);
|
||||
void set_frequency_absolute(rf::Frequency frequency);
|
||||
int32_t sliding_limit() const;
|
||||
void reset_sliding_frequency(ReceiverModel::Mode modulation);
|
||||
|
||||
MessageHandlerRegistration message_handler_coded_squelch{
|
||||
Message::ID::CodedSquelch,
|
||||
|
||||
@@ -69,7 +69,7 @@ static std::uint64_t get_freq_by_channel_number(uint8_t channel_number) {
|
||||
}
|
||||
|
||||
void BLECommView::focus() {
|
||||
options_channel.focus();
|
||||
field_frequency.focus();
|
||||
}
|
||||
|
||||
BLECommView::BLECommView(NavigationView& nav)
|
||||
@@ -79,10 +79,7 @@ BLECommView::BLECommView(NavigationView& nav)
|
||||
&field_rf_amp,
|
||||
&field_lna,
|
||||
&field_vga,
|
||||
&options_channel,
|
||||
&field_frequency,
|
||||
&label_send_adv,
|
||||
&button_send_adv,
|
||||
&check_log,
|
||||
&label_packets_sent,
|
||||
&text_packets_sent,
|
||||
@@ -90,10 +87,6 @@ BLECommView::BLECommView(NavigationView& nav)
|
||||
|
||||
field_frequency.set_step(0);
|
||||
|
||||
button_send_adv.on_select = [this](ImageButton&) {
|
||||
this->toggle();
|
||||
};
|
||||
|
||||
check_log.set_value(logging);
|
||||
|
||||
check_log.on_select = [this](Checkbox&, bool v) {
|
||||
@@ -104,27 +97,8 @@ BLECommView::BLECommView(NavigationView& nav)
|
||||
logger->append(logs_dir.string() + "/BLELOG_" + to_string_timestamp(rtc_time::now()) + ".TXT");
|
||||
};
|
||||
|
||||
options_channel.on_change = [this](size_t, int32_t i) {
|
||||
// If we selected Auto don't do anything and Auto will handle changing.
|
||||
if (i == 40) {
|
||||
auto_channel = true;
|
||||
return;
|
||||
} else {
|
||||
auto_channel = false;
|
||||
}
|
||||
|
||||
field_frequency.set_value(get_freq_by_channel_number(i));
|
||||
channel_number_rx = i;
|
||||
channel_number_tx = i;
|
||||
};
|
||||
|
||||
options_channel.set_selected_index(3, true);
|
||||
|
||||
logger = std::make_unique<BLECommLogger>();
|
||||
|
||||
// Generate new random Mac Address upon each new startup.
|
||||
generateRandomMacAddress(randomMac);
|
||||
|
||||
// Setup Initial Advertise Packet.
|
||||
advertisePacket = build_adv_packet();
|
||||
}
|
||||
@@ -145,86 +119,66 @@ bool BLECommView::in_tx_mode() const {
|
||||
return (bool)is_running_tx;
|
||||
}
|
||||
|
||||
void BLECommView::toggle() {
|
||||
if (in_tx_mode()) {
|
||||
sendAdvertisement(false);
|
||||
} else {
|
||||
sendAdvertisement(true);
|
||||
}
|
||||
}
|
||||
|
||||
BLETxPacket BLECommView::build_adv_packet() {
|
||||
BLETxPacket bleTxPacket;
|
||||
memset(&bleTxPacket, 0, sizeof(BLETxPacket));
|
||||
|
||||
std::string dataString = "11094861636b524620506f7274617061636b";
|
||||
|
||||
strncpy(bleTxPacket.macAddress, randomMac, 12);
|
||||
std::string dataString = "02010603030F1807084861636B5246";
|
||||
strncpy(bleTxPacket.advertisementData, dataString.c_str(), dataString.length());
|
||||
|
||||
// Duty cycle of 40% per 100ms advertisment periods.
|
||||
strncpy(bleTxPacket.packetCount, "80", 3);
|
||||
bleTxPacket.packet_count = 80;
|
||||
strncpy(bleTxPacket.packetCount, "1", 2);
|
||||
bleTxPacket.packet_count = 1;
|
||||
|
||||
bleTxPacket.packetType = PKT_TYPE_DISCOVERY;
|
||||
bleTxPacket.pduType = PKT_TYPE_ADV_IND;
|
||||
|
||||
return bleTxPacket;
|
||||
}
|
||||
|
||||
void BLECommView::sendAdvertisement(bool enable) {
|
||||
if (enable) {
|
||||
startTx(advertisePacket);
|
||||
is_adv = true;
|
||||
} else {
|
||||
is_adv = false;
|
||||
stopTx();
|
||||
}
|
||||
void BLECommView::sendAdvertisement(void) {
|
||||
startTx(advertisePacket);
|
||||
ble_state = Ble_State_Advertising;
|
||||
}
|
||||
|
||||
void BLECommView::startTx(BLETxPacket packetToSend) {
|
||||
int randomChannel = channel_number_tx;
|
||||
switch_rx_tx(false);
|
||||
|
||||
if (auto_channel) {
|
||||
int min = 37;
|
||||
int max = 39;
|
||||
currentPacket = packetToSend;
|
||||
packet_counter = currentPacket.packet_count;
|
||||
|
||||
randomChannel = min + std::rand() % (max - min + 1);
|
||||
|
||||
field_frequency.set_value(get_freq_by_channel_number(randomChannel));
|
||||
switch (advCount) {
|
||||
case 0:
|
||||
channel_number_tx = 37;
|
||||
break;
|
||||
case 1:
|
||||
channel_number_tx = 38;
|
||||
break;
|
||||
case 2:
|
||||
channel_number_tx = 39;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!in_tx_mode()) {
|
||||
switch_rx_tx(false);
|
||||
field_frequency.set_value(get_freq_by_channel_number(37));
|
||||
|
||||
currentPacket = packetToSend;
|
||||
packet_counter = currentPacket.packet_count;
|
||||
advCount++;
|
||||
|
||||
button_send_adv.set_bitmap(&bitmap_stop);
|
||||
baseband::set_btletx(randomChannel, currentPacket.macAddress, currentPacket.advertisementData, currentPacket.packetType);
|
||||
transmitter_model.set_tx_gain(47);
|
||||
transmitter_model.enable();
|
||||
|
||||
is_running_tx = true;
|
||||
} else {
|
||||
baseband::set_btletx(randomChannel, currentPacket.macAddress, currentPacket.advertisementData, currentPacket.packetType);
|
||||
if (advCount == 3) {
|
||||
advCount = 0;
|
||||
}
|
||||
|
||||
if ((packet_counter % 10) == 0) {
|
||||
text_packets_sent.set(to_string_dec_uint(packet_counter));
|
||||
}
|
||||
baseband::set_btletx(37, deviceMAC, currentPacket.advertisementData, currentPacket.pduType);
|
||||
transmitter_model.set_tx_gain(47);
|
||||
transmitter_model.enable();
|
||||
|
||||
is_sending = true;
|
||||
|
||||
packet_counter--;
|
||||
is_running_tx = true;
|
||||
}
|
||||
|
||||
void BLECommView::stopTx() {
|
||||
button_send_adv.set_bitmap(&bitmap_play);
|
||||
text_packets_sent.set(to_string_dec_uint(packet_counter));
|
||||
|
||||
switch_rx_tx(true);
|
||||
|
||||
baseband::set_btlerx(channel_number_rx);
|
||||
field_frequency.set_value(get_freq_by_channel_number(channel_number_rx));
|
||||
receiver_model.enable();
|
||||
|
||||
is_running_tx = false;
|
||||
@@ -248,14 +202,12 @@ void BLECommView::on_data(BlePacketData* packet) {
|
||||
parse_received_packet(packet, (ADV_PDU_TYPE)packet->type);
|
||||
}
|
||||
|
||||
// called each 1/60th of second, so 6 = 100ms
|
||||
void BLECommView::on_timer() {
|
||||
// Send advertise burst only once every 100ms
|
||||
if (++timer_counter == timer_period) {
|
||||
timer_counter = 0;
|
||||
|
||||
if (!is_adv) {
|
||||
sendAdvertisement(true);
|
||||
if (ble_state == Ble_State_Receiving || ble_state == Ble_State_Idle) {
|
||||
sendAdvertisement();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -263,17 +215,18 @@ void BLECommView::on_timer() {
|
||||
void BLECommView::on_tx_progress(const bool done) {
|
||||
if (done) {
|
||||
if (in_tx_mode()) {
|
||||
is_sending = false;
|
||||
ble_state = Ble_State_Receiving;
|
||||
|
||||
if (packet_counter == 0) {
|
||||
if (is_adv) {
|
||||
sendAdvertisement(false);
|
||||
} else {
|
||||
stopTx();
|
||||
}
|
||||
} else {
|
||||
startTx(currentPacket);
|
||||
}
|
||||
timer_counter = 0;
|
||||
timer_period = 12;
|
||||
|
||||
stopTx();
|
||||
|
||||
// else
|
||||
// {
|
||||
// startTx(advertisePacket);
|
||||
// advCount++;
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -309,23 +262,28 @@ void BLECommView::parse_received_packet(const BlePacketData* packet, ADV_PDU_TYP
|
||||
}
|
||||
|
||||
if (pdu_type == SCAN_REQ || pdu_type == CONNECT_REQ) {
|
||||
ADV_PDU_PAYLOAD_TYPE_1_3* directed_mac_data = (ADV_PDU_PAYLOAD_TYPE_1_3*)packet->data;
|
||||
|
||||
std::reverse(directed_mac_data->A1, directed_mac_data->A1 + 6);
|
||||
console.clear(true);
|
||||
std::string str_console = "";
|
||||
std::string pduTypeStr = "";
|
||||
|
||||
if (pdu_type == SCAN_REQ) {
|
||||
pduTypeStr += "SCAN_REQ";
|
||||
} else if (pdu_type == CONNECT_REQ) {
|
||||
pduTypeStr += "CONNECT_REQ";
|
||||
}
|
||||
ADV_PDU_PAYLOAD_TYPE_1_3* directed_mac_data = (ADV_PDU_PAYLOAD_TYPE_1_3*)packet->data;
|
||||
|
||||
str_console += "PACKET TYPE:" + pduTypeStr + "\n";
|
||||
str_console += "MY MAC:" + to_string_formatted_mac_address(randomMac) + "\n";
|
||||
str_console += "SCAN MAC:" + to_string_mac_address(directed_mac_data->A1, 6, false) + "\n";
|
||||
console.write(str_console);
|
||||
std::reverse(directed_mac_data->A1, directed_mac_data->A1 + 6);
|
||||
std::string directedMAC = to_string_mac_address(directed_mac_data->A1, 6, false);
|
||||
|
||||
// Compare directed MAC Hex with the device MAC.
|
||||
if (1) {
|
||||
// std::string str_console = "Received SCAN_REQ from directed MAC: " + directedMAC + "\n";
|
||||
// console.clear(true);
|
||||
// console.writeln(str_console);
|
||||
}
|
||||
} else if (pdu_type == CONNECT_REQ) {
|
||||
ADV_PDU_PAYLOAD_TYPE_5* connectReq = (ADV_PDU_PAYLOAD_TYPE_5*)packet->data;
|
||||
|
||||
std::reverse(connectReq->AdvA, connectReq->AdvA + 6);
|
||||
std::string directedMAC = to_string_mac_address(connectReq->AdvA, 6, false);
|
||||
|
||||
std::string str_console = "Received CONNECT_REQ from directed MAC: " + directedMAC + "\n";
|
||||
console.clear(true);
|
||||
console.writeln(str_console);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ class BLECommView : public View {
|
||||
~BLECommView();
|
||||
|
||||
void set_parent_rect(const Rect new_parent_rect) override;
|
||||
void paint(Painter&) override{};
|
||||
void paint(Painter&) override {};
|
||||
|
||||
void focus() override;
|
||||
|
||||
@@ -76,7 +76,16 @@ class BLECommView : public View {
|
||||
void on_data(BlePacketData* packetData);
|
||||
void on_tx_progress(const bool done);
|
||||
void parse_received_packet(const BlePacketData* packet, ADV_PDU_TYPE pdu_type);
|
||||
void sendAdvertisement(bool enable);
|
||||
void sendAdvertisement(void);
|
||||
|
||||
typedef enum {
|
||||
Ble_State_Idle = 0,
|
||||
Ble_State_Advertising = 1,
|
||||
Ble_State_Receiving = 2,
|
||||
Ble_State_Sending = 3
|
||||
} BleState;
|
||||
|
||||
BleState ble_state{Ble_State_Idle};
|
||||
|
||||
NavigationView& nav_;
|
||||
|
||||
@@ -101,8 +110,9 @@ class BLECommView : public View {
|
||||
uint8_t channel_number_tx = 37;
|
||||
uint8_t channel_number_rx = 37;
|
||||
bool auto_channel = false;
|
||||
uint8_t advCount = 0;
|
||||
|
||||
char randomMac[13] = "010203040506";
|
||||
char deviceMAC[13] = "C23456789ABC";
|
||||
|
||||
bool is_running_tx = false;
|
||||
bool is_sending = false;
|
||||
@@ -111,7 +121,7 @@ class BLECommView : public View {
|
||||
int16_t timer_period{6}; // Delay each packet by 16ms.
|
||||
int16_t timer_counter = 0;
|
||||
int16_t timer_rx_counter = 0;
|
||||
int16_t timer_rx_period{12}; // Poll Rx for at least 200ms. (TBD)
|
||||
int16_t timer_rx_period{12}; // Poll Rx for at least 150ms. (TBD)
|
||||
|
||||
uint32_t packet_counter{0};
|
||||
BLETxPacket advertisePacket{};
|
||||
@@ -121,7 +131,7 @@ class BLECommView : public View {
|
||||
static constexpr auto header_height = 5 * 16;
|
||||
|
||||
OptionsField options_channel{
|
||||
{0 * 8, 0 * 8},
|
||||
{UI_POS_X(0), 0 * 8},
|
||||
5,
|
||||
{{"Ch.37 ", 37},
|
||||
{"Ch.38", 38},
|
||||
@@ -129,17 +139,17 @@ class BLECommView : public View {
|
||||
{"Auto", 40}}};
|
||||
|
||||
RxFrequencyField field_frequency{
|
||||
{6 * 8, 0 * 16},
|
||||
{6 * 8, UI_POS_Y(0)},
|
||||
nav_};
|
||||
|
||||
RFAmpField field_rf_amp{
|
||||
{16 * 8, 0 * 16}};
|
||||
{16 * 8, UI_POS_Y(0)}};
|
||||
|
||||
LNAGainField field_lna{
|
||||
{18 * 8, 0 * 16}};
|
||||
{18 * 8, UI_POS_Y(0)}};
|
||||
|
||||
VGAGainField field_vga{
|
||||
{21 * 8, 0 * 16}};
|
||||
{21 * 8, UI_POS_Y(0)}};
|
||||
|
||||
RSSI rssi{
|
||||
{24 * 8, 0, 6 * 8, 4}};
|
||||
@@ -147,15 +157,6 @@ class BLECommView : public View {
|
||||
Channel channel{
|
||||
{24 * 8, 5, 6 * 8, 4}};
|
||||
|
||||
Labels label_send_adv{
|
||||
{{0 * 8, 2 * 8}, "Send Advertisement:", Theme::getInstance()->fg_light->foreground}};
|
||||
|
||||
ImageButton button_send_adv{
|
||||
{21 * 8, 1 * 16, 10 * 8, 2 * 16},
|
||||
&bitmap_play,
|
||||
Theme::getInstance()->fg_green->foreground,
|
||||
Theme::getInstance()->fg_green->background};
|
||||
|
||||
Checkbox check_log{
|
||||
{24 * 8, 2 * 8},
|
||||
3,
|
||||
@@ -163,14 +164,14 @@ class BLECommView : public View {
|
||||
true};
|
||||
|
||||
Labels label_packets_sent{
|
||||
{{0 * 8, 4 * 8}, "Packets Left:", Theme::getInstance()->fg_light->foreground}};
|
||||
{{UI_POS_X(0), 4 * 8}, "Packets Left:", Theme::getInstance()->fg_light->foreground}};
|
||||
|
||||
Text text_packets_sent{
|
||||
{13 * 8, 2 * 16, 12 * 8, 16},
|
||||
"-"};
|
||||
|
||||
Console console{
|
||||
{0, 4 * 16, 240, 240}};
|
||||
{0, 4 * 16, ui::screen_width, ui::screen_height - 80}};
|
||||
|
||||
std::string str_log{""};
|
||||
bool logging{false};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,6 +2,7 @@
|
||||
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2017 Furrtek
|
||||
* Copyright (C) 2023 TJ Baginski
|
||||
* Copyright (C) 2025 Tommaso Ventafridda
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -33,6 +34,7 @@
|
||||
#include "ui_record_view.hpp"
|
||||
#include "app_settings.hpp"
|
||||
#include "radio_state.hpp"
|
||||
#include "database.hpp"
|
||||
#include "log_file.hpp"
|
||||
#include "utility.hpp"
|
||||
#include "usb_serial_thread.hpp"
|
||||
@@ -40,6 +42,12 @@
|
||||
|
||||
#include "recent_entries.hpp"
|
||||
|
||||
// Add for heap debugging
|
||||
// For ChibiOS core functions
|
||||
#include "ch.h"
|
||||
#include "chcore.h"
|
||||
#include "chheap.h"
|
||||
|
||||
class BLELogger {
|
||||
public:
|
||||
Optional<File::Error> append(const std::filesystem::path& filename) {
|
||||
@@ -69,24 +77,36 @@ typedef enum {
|
||||
RESERVED5 = 12,
|
||||
RESERVED6 = 13,
|
||||
RESERVED7 = 14,
|
||||
RESERVED8 = 15
|
||||
RESERVED8 = 15,
|
||||
UNKNOWN = 16
|
||||
} ADV_PDU_TYPE;
|
||||
|
||||
typedef enum {
|
||||
MAC_VENDOR_UNKNOWN = 0,
|
||||
MAC_VENDOR_FOUND = 1,
|
||||
MAC_VENDOR_NOT_FOUND = 2,
|
||||
MAC_DB_NOT_FOUND = 3
|
||||
} MAC_VENDOR_STATUS;
|
||||
|
||||
struct BleRecentEntry {
|
||||
using Key = uint64_t;
|
||||
|
||||
static constexpr Key invalid_key = 0xffffffff;
|
||||
static constexpr Key invalid_key = 0xFFFFFFFFFFFFF;
|
||||
|
||||
uint64_t macAddress;
|
||||
uint64_t uniqueKey;
|
||||
int dbValue;
|
||||
BlePacketData packetData;
|
||||
|
||||
std::string timestamp;
|
||||
std::string dataString;
|
||||
std::string nameString;
|
||||
std::string informationString;
|
||||
bool include_name;
|
||||
uint16_t numHits;
|
||||
ADV_PDU_TYPE pduType;
|
||||
uint8_t channelNumber;
|
||||
MAC_VENDOR_STATUS vendor_status;
|
||||
std::string vendor_name;
|
||||
bool entryFound;
|
||||
|
||||
BleRecentEntry()
|
||||
@@ -94,22 +114,25 @@ struct BleRecentEntry {
|
||||
}
|
||||
|
||||
BleRecentEntry(
|
||||
const uint64_t macAddress)
|
||||
: macAddress{macAddress},
|
||||
const uint64_t uniqueKey)
|
||||
: uniqueKey{uniqueKey},
|
||||
dbValue{},
|
||||
packetData{},
|
||||
timestamp{},
|
||||
dataString{},
|
||||
nameString{},
|
||||
informationString{},
|
||||
include_name{},
|
||||
numHits{},
|
||||
pduType{},
|
||||
channelNumber{},
|
||||
vendor_status{MAC_VENDOR_UNKNOWN},
|
||||
vendor_name{},
|
||||
entryFound{} {
|
||||
}
|
||||
|
||||
Key key() const {
|
||||
return macAddress;
|
||||
return uniqueKey;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -126,11 +149,11 @@ class BleRecentEntryDetailView : public View {
|
||||
void update_data();
|
||||
void focus() override;
|
||||
void paint(Painter&) override;
|
||||
static BLETxPacket build_packet(BleRecentEntry entry_);
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
BleRecentEntry entry_{};
|
||||
BLETxPacket build_packet();
|
||||
void on_save_file(const std::string value, BLETxPacket packetToSave);
|
||||
bool saveFile(const std::filesystem::path& path, BLETxPacket packetToSave);
|
||||
std::string packetFileBuffer{};
|
||||
@@ -139,35 +162,42 @@ class BleRecentEntryDetailView : public View {
|
||||
static constexpr uint8_t total_data_lines{5};
|
||||
|
||||
Labels label_mac_address{
|
||||
{{0 * 8, 0 * 16}, "Mac Address:", Theme::getInstance()->fg_light->foreground}};
|
||||
{{UI_POS_X(0), UI_POS_Y(0)}, "Mac Address:", Theme::getInstance()->fg_light->foreground}};
|
||||
|
||||
Text text_mac_address{
|
||||
{12 * 8, 0 * 16, 17 * 8, 16},
|
||||
{UI_POS_X(12), UI_POS_Y(0), UI_POS_WIDTH(17), UI_POS_HEIGHT(1)},
|
||||
"-"};
|
||||
|
||||
Labels label_pdu_type{
|
||||
{{0 * 8, 1 * 16}, "PDU Type:", Theme::getInstance()->fg_light->foreground}};
|
||||
{{UI_POS_X(0), UI_POS_Y(1)}, "PDU Type:", Theme::getInstance()->fg_light->foreground}};
|
||||
|
||||
Text text_pdu_type{
|
||||
{9 * 8, 1 * 16, 17 * 8, 16},
|
||||
{9 * 8, UI_POS_Y(1), 17 * 8, UI_POS_HEIGHT(1)},
|
||||
"-"};
|
||||
|
||||
Labels label_vendor{
|
||||
{{UI_POS_X(0), UI_POS_Y(2)}, "Vendor:", Theme::getInstance()->fg_light->foreground}};
|
||||
|
||||
Text text_vendor{
|
||||
{7 * 8, UI_POS_Y(2), 23 * 8, UI_POS_HEIGHT(1)},
|
||||
"-"};
|
||||
|
||||
Labels labels{
|
||||
{{0 * 8, 3 * 16}, "Len", Theme::getInstance()->fg_light->foreground},
|
||||
{{5 * 8, 3 * 16}, "Type", Theme::getInstance()->fg_light->foreground},
|
||||
{{10 * 8, 3 * 16}, "Value", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(0), UI_POS_Y(3)}, "Len", Theme::getInstance()->fg_light->foreground},
|
||||
{{5 * 8, UI_POS_Y(3)}, "Type", Theme::getInstance()->fg_light->foreground},
|
||||
{{10 * 8, UI_POS_Y(3)}, "Value", Theme::getInstance()->fg_light->foreground},
|
||||
};
|
||||
|
||||
Button button_send{
|
||||
{19, 224, 96, 24},
|
||||
{UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), 224, 96, 24},
|
||||
"Send"};
|
||||
|
||||
Button button_done{
|
||||
{125, 224, 96, 24},
|
||||
{UI_POS_X_CENTER(12) + UI_POS_WIDTH(8), 224, 96, 24},
|
||||
"Done"};
|
||||
|
||||
Button button_save{
|
||||
{72, 264, 96, 24},
|
||||
{UI_POS_X_CENTER(12), 264, 96, 24},
|
||||
"Save"};
|
||||
|
||||
bool send_updates{false};
|
||||
@@ -186,7 +216,7 @@ class BLERxView : public View {
|
||||
~BLERxView();
|
||||
|
||||
void set_parent_rect(const Rect new_parent_rect) override;
|
||||
void paint(Painter&) override{};
|
||||
void paint(Painter&) override {};
|
||||
|
||||
void focus() override;
|
||||
|
||||
@@ -198,15 +228,19 @@ class BLERxView : public View {
|
||||
bool saveFile(const std::filesystem::path& path);
|
||||
std::unique_ptr<UsbSerialThread> usb_serial_thread{};
|
||||
void on_data(BlePacketData* packetData);
|
||||
void log_ble_packet(BlePacketData* packet);
|
||||
void on_filter_change(std::string value);
|
||||
void on_file_changed(const std::filesystem::path& new_file_path);
|
||||
void file_error();
|
||||
void on_timer();
|
||||
void handle_entries_sort(uint8_t index);
|
||||
void handle_filter_options(uint8_t index);
|
||||
void updateEntry(const BlePacketData* packet, BleRecentEntry& entry, ADV_PDU_TYPE pdu_type);
|
||||
bool handle_filter_options(uint8_t index, const BleRecentEntry& entry);
|
||||
bool updateEntry(const BlePacketData* packet, BleRecentEntry& entry, ADV_PDU_TYPE pdu_type);
|
||||
bool parse_beacon_data(const uint8_t* data, uint8_t length, std::string& nameString, std::string& informationString);
|
||||
bool parse_tracking_beacon_data(const uint8_t* data, uint8_t length, std::string& nameString, std::string& informationString);
|
||||
|
||||
NavigationView& nav_;
|
||||
|
||||
RxRadioState radio_state_{
|
||||
2402000000 /* frequency */,
|
||||
4000000 /* bandwidth */,
|
||||
@@ -216,9 +250,12 @@ class BLERxView : public View {
|
||||
uint8_t channel_index{0};
|
||||
uint8_t sort_index{0};
|
||||
uint8_t filter_index{0};
|
||||
bool uniqueParsing = false;
|
||||
bool duplicatePackets = false;
|
||||
std::string filter{};
|
||||
bool logging{false};
|
||||
bool serial_logging{false};
|
||||
bool async_tx_states_when_entered{false};
|
||||
|
||||
bool name_enable{true};
|
||||
app_settings::SettingsManager settings_{
|
||||
@@ -233,6 +270,8 @@ class BLERxView : public View {
|
||||
// disabled to always start without USB serial activated until we can make it non blocking if not connected
|
||||
// {"serial_log"sv, &serial_logging},
|
||||
{"name"sv, &name_enable},
|
||||
{"unique_parsing"sv, &uniqueParsing},
|
||||
{"duplicate_packets"sv, &duplicatePackets},
|
||||
}};
|
||||
|
||||
std::string str_console = "";
|
||||
@@ -242,7 +281,7 @@ class BLERxView : public View {
|
||||
bool auto_channel = false;
|
||||
|
||||
int16_t timer_count{0};
|
||||
int16_t timer_period{6}; // 100ms
|
||||
int16_t timer_period{1}; // 25ms
|
||||
|
||||
std::string filterBuffer{};
|
||||
std::string listFileBuffer{};
|
||||
@@ -258,11 +297,11 @@ class BLERxView : public View {
|
||||
std::filesystem::path log_packets_path{blerx_dir / u"Logs/????.TXT"};
|
||||
std::filesystem::path packet_save_path{blerx_dir / u"Lists/????.csv"};
|
||||
|
||||
static constexpr auto header_height = 9 * 8;
|
||||
static constexpr auto header_height = 12 * 8;
|
||||
static constexpr auto switch_button_height = 3 * 16;
|
||||
|
||||
OptionsField options_channel{
|
||||
{0 * 8, 0 * 8},
|
||||
{UI_POS_X(0), UI_POS_Y(0)},
|
||||
5,
|
||||
{{"Ch.37", 37},
|
||||
{"Ch.38", 38},
|
||||
@@ -270,45 +309,50 @@ class BLERxView : public View {
|
||||
{"Auto", 40}}};
|
||||
|
||||
RxFrequencyField field_frequency{
|
||||
{6 * 8, 0 * 16},
|
||||
{UI_POS_X(6), UI_POS_Y(0)},
|
||||
nav_};
|
||||
|
||||
RFAmpField field_rf_amp{
|
||||
{16 * 8, 0 * 16}};
|
||||
{UI_POS_X(16), UI_POS_Y(0)}};
|
||||
|
||||
LNAGainField field_lna{
|
||||
{18 * 8, 0 * 16}};
|
||||
{UI_POS_X(18), UI_POS_Y(0)}};
|
||||
|
||||
VGAGainField field_vga{
|
||||
{21 * 8, 0 * 16}};
|
||||
{UI_POS_X(21), UI_POS_Y(0)}};
|
||||
|
||||
RSSI rssi{
|
||||
{24 * 8, 0, 6 * 8, 4}};
|
||||
{UI_POS_X(24), 0, UI_POS_WIDTH_REMAINING(24), 4}};
|
||||
|
||||
Channel channel{
|
||||
{24 * 8, 5, 6 * 8, 4}};
|
||||
{UI_POS_X(24), 5, UI_POS_WIDTH_REMAINING(24), 4}};
|
||||
|
||||
Labels label_sort{
|
||||
{{0 * 8, 2 * 8}, "Sort:", Theme::getInstance()->fg_light->foreground}};
|
||||
{{UI_POS_X(0), UI_POS_Y(1)}, "Sort:", Theme::getInstance()->fg_light->foreground}};
|
||||
|
||||
OptionsField options_sort{
|
||||
{5 * 8, 2 * 8},
|
||||
{5 * 8, UI_POS_Y(1)},
|
||||
4,
|
||||
{{"MAC", 0},
|
||||
{"Hits", 1},
|
||||
{"dB", 2},
|
||||
{"Time", 3},
|
||||
{"Name", 4}}};
|
||||
{"Name", 4},
|
||||
{"Info", 5}}};
|
||||
|
||||
Button button_filter{
|
||||
{11 * 8, 2 * 8, 7 * 8, 16},
|
||||
{11 * 8, UI_POS_Y(1), 7 * 8, 16},
|
||||
"Filter:"};
|
||||
|
||||
OptionsField options_filter{
|
||||
{18 * 8 + 2, 2 * 8},
|
||||
4,
|
||||
{18 * 8 + 2, UI_POS_Y(1)},
|
||||
7,
|
||||
{{"Data", 0},
|
||||
{"MAC", 1}}};
|
||||
{"MAC", 1},
|
||||
{"Name", 2},
|
||||
{"Info", 3},
|
||||
{"Vendor", 4},
|
||||
{"Channel", 5}}};
|
||||
|
||||
Checkbox check_log{
|
||||
{10 * 8, 4 * 8 + 2},
|
||||
@@ -317,41 +361,50 @@ class BLERxView : public View {
|
||||
true};
|
||||
|
||||
Checkbox check_name{
|
||||
{0 * 8, 4 * 8 + 2},
|
||||
{UI_POS_X(0), 4 * 8 + 2},
|
||||
3,
|
||||
"Name",
|
||||
true};
|
||||
|
||||
Button button_find{
|
||||
{0 * 8, 7 * 8 - 2, 4 * 8, 16},
|
||||
"Find"};
|
||||
|
||||
Labels label_found{
|
||||
{{5 * 8, 7 * 8 - 2}, "Found:", Theme::getInstance()->fg_light->foreground}};
|
||||
|
||||
Text text_found_count{
|
||||
{11 * 8, 7 * 8 - 2, 20 * 8, 16},
|
||||
"0/0"};
|
||||
|
||||
Checkbox check_serial_log{
|
||||
{18 * 8 + 2, 4 * 8 + 2},
|
||||
7,
|
||||
"USB Log",
|
||||
true};
|
||||
|
||||
// Console console{
|
||||
// {0, 10 * 8, 240, 240}};
|
||||
Checkbox check_unique{
|
||||
{0 * 8 + 2, 7 * 8 + 2},
|
||||
7,
|
||||
"Unique",
|
||||
true};
|
||||
|
||||
Checkbox check_duplicate_packets{
|
||||
{10 * 8 + 2, 7 * 8 + 2},
|
||||
7,
|
||||
"Duplicate",
|
||||
true};
|
||||
|
||||
Button button_find{
|
||||
{UI_POS_X(0), 10 * 8 - 2, 4 * 8, 16},
|
||||
"Find"};
|
||||
|
||||
Labels label_found{
|
||||
{{5 * 8, 10 * 8 - 2}, "Found:", Theme::getInstance()->fg_light->foreground}};
|
||||
|
||||
Text text_found_count{
|
||||
{11 * 8, 10 * 8 - 2, 20 * 8, 16},
|
||||
"0/0"};
|
||||
|
||||
Button button_clear_list{
|
||||
{2 * 8, 320 - (16 + 32), 7 * 8, 32},
|
||||
{2 * 8, screen_height - (16 + 32), 7 * 8, 32},
|
||||
"Clear"};
|
||||
|
||||
Button button_save_list{
|
||||
{11 * 8, 320 - (16 + 32), 11 * 8, 32},
|
||||
{UI_POS_X_CENTER(11), screen_height - (16 + 32), 11 * 8, 32},
|
||||
"Export CSV"};
|
||||
|
||||
Button button_switch{
|
||||
{240 - 6 * 8, 320 - (16 + 32), 4 * 8, 32},
|
||||
{UI_POS_X_RIGHT(6), screen_height - (16 + 32), 4 * 8, 32},
|
||||
"Tx"};
|
||||
|
||||
std::string str_log{""};
|
||||
@@ -360,10 +413,10 @@ class BLERxView : public View {
|
||||
BleRecentEntries recent{};
|
||||
BleRecentEntries tempList{};
|
||||
|
||||
const RecentEntriesColumns columns{{
|
||||
{"Mac Address", 17},
|
||||
RecentEntriesColumns columns{{
|
||||
{"Name", 0},
|
||||
{"Hits", 7},
|
||||
{"dB", 4},
|
||||
{"dBm", 4},
|
||||
}};
|
||||
|
||||
BleRecentEntriesView recent_entries_view{columns, recent};
|
||||
@@ -380,6 +433,20 @@ class BLERxView : public View {
|
||||
[this](const Message* const) {
|
||||
this->on_timer();
|
||||
}};
|
||||
|
||||
// Widget to control the list limit
|
||||
Labels label_max_entries{
|
||||
{{UI_POS_X(22), 10 * 8 - 2}, "List:", Theme::getInstance()->fg_light->foreground}};
|
||||
NumberField field_max_entries{
|
||||
{UI_POS_X(28), 10 * 8 - 2}, // Correct: Position (x, y)
|
||||
2, // Number of digits
|
||||
{5, 64}, // Range (min, max) <- Coincides with max_entries
|
||||
1, // Step size
|
||||
' ' // Filler character
|
||||
};
|
||||
|
||||
// Variable to store the current limit
|
||||
size_t max_recent_entries = 25;
|
||||
}; /* BLERxView */
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
#include "rtc_time.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "file_path.hpp"
|
||||
#include "usb_serial_asyncmsg.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
using namespace modems;
|
||||
@@ -115,7 +116,7 @@ static std::uint64_t get_freq_by_channel_number(uint8_t channel_number) {
|
||||
freq_hz = 2'428'000'000ull + (channel_number - 11) * 2'000'000ull;
|
||||
break;
|
||||
default:
|
||||
freq_hz = UINT64_MAX;
|
||||
freq_hz = 2'402'000'000ull; // Default to channel 37 frequency if invalid channel number
|
||||
}
|
||||
|
||||
return freq_hz;
|
||||
@@ -123,6 +124,31 @@ static std::uint64_t get_freq_by_channel_number(uint8_t channel_number) {
|
||||
|
||||
namespace ui {
|
||||
|
||||
PKT_TYPE get_pkt_type_from_string(const std::string& type_str) {
|
||||
if (type_str == "RAW") {
|
||||
return PKT_TYPE_RAW;
|
||||
} else if (type_str == "DISCOVERY") {
|
||||
return PKT_TYPE_DISCOVERY;
|
||||
} else if (type_str == "IBEACON") {
|
||||
return PKT_TYPE_IBEACON;
|
||||
} else if (type_str == "ADV_IND") {
|
||||
return PKT_TYPE_ADV_IND;
|
||||
} else if (type_str == "ADV_DIRECT_IND") {
|
||||
return PKT_TYPE_ADV_DIRECT_IND;
|
||||
} else if (type_str == "ADV_NONCONN_IND") {
|
||||
return PKT_TYPE_ADV_NONCONN_IND;
|
||||
} else if (type_str == "ADV_SCAN_IND") {
|
||||
return PKT_TYPE_ADV_SCAN_IND;
|
||||
} else if (type_str == "SCAN_REQ") {
|
||||
return PKT_TYPE_SCAN_REQ;
|
||||
} else if (type_str == "SCAN_RSP") {
|
||||
return PKT_TYPE_SCAN_RSP;
|
||||
} else if (type_str == "CONNECT_REQ") {
|
||||
return PKT_TYPE_CONNECT_REQ;
|
||||
}
|
||||
return PKT_TYPE_INVALID_TYPE;
|
||||
}
|
||||
|
||||
void BLETxView::focus() {
|
||||
button_open.focus();
|
||||
}
|
||||
@@ -167,29 +193,13 @@ void BLETxView::toggle() {
|
||||
}
|
||||
}
|
||||
|
||||
void BLETxView::start() {
|
||||
baseband::run_image(portapack::spi_flash::image_tag_btle_tx);
|
||||
transmitter_model.enable();
|
||||
|
||||
void BLETxView::send_packet() {
|
||||
// Generate new random Mac Address.
|
||||
transmitter_model.enable();
|
||||
generateRandomMacAddress(randomMac);
|
||||
|
||||
// If this is our first run, check file.
|
||||
if (!is_active()) {
|
||||
File data_file;
|
||||
|
||||
auto error = data_file.open(file_path);
|
||||
if (error && !file_override) {
|
||||
file_error();
|
||||
check_loop.set_value(false);
|
||||
return;
|
||||
}
|
||||
|
||||
button_play.set_bitmap(&bitmap_stop);
|
||||
is_running = true;
|
||||
}
|
||||
|
||||
char advertisementData[63] = {0};
|
||||
packets[current_packet].advertisementData[62] = '\0';
|
||||
strcpy(advertisementData, packets[current_packet].advertisementData);
|
||||
|
||||
// TODO: Make this a checkbox.
|
||||
@@ -216,7 +226,7 @@ void BLETxView::start() {
|
||||
uint8_t min = 0x00;
|
||||
uint8_t max = 0x0F;
|
||||
|
||||
hexDigit = min + std::rand() % (max - min + 1);
|
||||
hexDigit = min + rand() % (max - min + 1);
|
||||
} break;
|
||||
default:
|
||||
hexDigit = 0;
|
||||
@@ -235,9 +245,30 @@ void BLETxView::start() {
|
||||
}
|
||||
}
|
||||
|
||||
// Setup next packet configuration.
|
||||
progressbar.set_max(packets[current_packet].packet_count);
|
||||
baseband::set_btletx(channel_number, random_mac ? randomMac : packets[current_packet].macAddress, advertisementData, pduType);
|
||||
progressbar.set_value(packets[current_packet].packet_count - packet_counter);
|
||||
text_packets_sent.set(to_string_dec_uint(packet_counter));
|
||||
|
||||
baseband::set_btletx(channel_number, random_mac ? randomMac : packets[current_packet].macAddress, advertisementData, packets[current_packet].pduType);
|
||||
|
||||
packetDone = false;
|
||||
}
|
||||
|
||||
void BLETxView::start() {
|
||||
if (file_path.empty() || num_packets == 0) {
|
||||
file_error();
|
||||
check_loop.set_value(false);
|
||||
return;
|
||||
}
|
||||
|
||||
baseband::run_image(portapack::spi_flash::image_tag_btle_tx);
|
||||
transmitter_model.enable();
|
||||
|
||||
button_play.set_bitmap(&bitmap_stop);
|
||||
is_running = true;
|
||||
|
||||
advCount = 0;
|
||||
|
||||
send_packet();
|
||||
}
|
||||
|
||||
void BLETxView::stop() {
|
||||
@@ -253,73 +284,83 @@ void BLETxView::stop() {
|
||||
is_running = false;
|
||||
}
|
||||
|
||||
void BLETxView::reset() {
|
||||
transmitter_model.disable();
|
||||
baseband::shutdown();
|
||||
|
||||
start();
|
||||
}
|
||||
|
||||
// called each 1/60th of second, so 6 = 100ms
|
||||
void BLETxView::on_timer() {
|
||||
if (++timer_count == timer_period) {
|
||||
timer_count = 0;
|
||||
|
||||
if (is_active()) {
|
||||
// Reached end of current packet repeats.
|
||||
if (packet_counter == 0) {
|
||||
// Done sending all packets.
|
||||
if (current_packet == (num_packets - 1)) {
|
||||
current_packet = 0;
|
||||
|
||||
// If looping, restart from beginning.
|
||||
if (check_loop.value()) {
|
||||
update_current_packet(packets[current_packet], current_packet);
|
||||
reset();
|
||||
} else {
|
||||
stop();
|
||||
}
|
||||
} else {
|
||||
current_packet++;
|
||||
update_current_packet(packets[current_packet], current_packet);
|
||||
reset();
|
||||
}
|
||||
} else {
|
||||
reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (++auto_channel_counter == auto_channel_period) {
|
||||
auto_channel_counter = 0;
|
||||
|
||||
if (auto_channel) {
|
||||
int min = 37;
|
||||
int max = 39;
|
||||
|
||||
channel_number = min + std::rand() % (max - min + 1);
|
||||
|
||||
field_frequency.set_value(get_freq_by_channel_number(channel_number));
|
||||
if (is_active() && packetDone) {
|
||||
send_packet();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BLETxView::on_tx_progress(const bool done) {
|
||||
void BLETxView::on_tx_progress(const bool done, uint32_t progress) {
|
||||
// TODO: make use of progress variable
|
||||
(void)progress;
|
||||
if (!is_active() || num_packets == 0) {
|
||||
return;
|
||||
}
|
||||
if (done) {
|
||||
if (is_active()) {
|
||||
if ((packet_counter % 10) == 0) {
|
||||
text_packets_sent.set(to_string_dec_uint(packet_counter));
|
||||
}
|
||||
transmitter_model.disable();
|
||||
|
||||
packet_counter--;
|
||||
progressbar.set_value(packets[current_packet].packet_count - packet_counter);
|
||||
if (auto_channel) {
|
||||
advCount++;
|
||||
if (advCount == 3) {
|
||||
channel_number = 37;
|
||||
field_frequency.set_value(get_freq_by_channel_number(channel_number));
|
||||
if (packet_counter > 0) packet_counter--;
|
||||
packetDone = true;
|
||||
advCount = 0;
|
||||
} else {
|
||||
channel_number = 37 + advCount;
|
||||
field_frequency.set_value(get_freq_by_channel_number(channel_number));
|
||||
send_packet();
|
||||
}
|
||||
} else if (all_channels) {
|
||||
advCount++;
|
||||
if (advCount == 40) {
|
||||
channel_number = 0;
|
||||
field_frequency.set_value(get_freq_by_channel_number(channel_number));
|
||||
if (packet_counter > 0) packet_counter--;
|
||||
packetDone = true;
|
||||
advCount = 0;
|
||||
} else {
|
||||
channel_number = advCount;
|
||||
field_frequency.set_value(get_freq_by_channel_number(channel_number));
|
||||
send_packet();
|
||||
}
|
||||
} else {
|
||||
if (packet_counter > 0) packet_counter--;
|
||||
packetDone = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reached end of current packet repeats.
|
||||
if (packet_counter == 0) {
|
||||
// Done sending all packets.
|
||||
current_packet++;
|
||||
if (current_packet >= num_packets) {
|
||||
current_packet = 0;
|
||||
|
||||
// If looping, restart from beginning.
|
||||
if (check_loop.value()) {
|
||||
update_current_packet(packets[current_packet], current_packet);
|
||||
} else {
|
||||
stop();
|
||||
}
|
||||
} else {
|
||||
update_current_packet(packets[current_packet], current_packet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BLETxView::BLETxView(NavigationView& nav)
|
||||
: nav_{nav} {
|
||||
add_children({&button_open,
|
||||
add_children({&dataEditView,
|
||||
&button_open,
|
||||
&text_filename,
|
||||
&progressbar,
|
||||
&check_rand_mac,
|
||||
@@ -330,7 +371,6 @@ BLETxView::BLETxView(NavigationView& nav)
|
||||
&label_speed,
|
||||
&options_speed,
|
||||
&options_channel,
|
||||
&options_adv_type,
|
||||
&label_marked_data,
|
||||
&marked_data_sequence,
|
||||
&label_packet_index,
|
||||
@@ -340,7 +380,6 @@ BLETxView::BLETxView(NavigationView& nav)
|
||||
&label_mac_address,
|
||||
&text_mac_address,
|
||||
&label_data_packet,
|
||||
&dataEditView,
|
||||
&button_clear_marked,
|
||||
&button_save_packet,
|
||||
&button_switch});
|
||||
@@ -354,16 +393,20 @@ BLETxView::BLETxView(NavigationView& nav)
|
||||
};
|
||||
|
||||
options_channel.on_change = [this](size_t, int32_t i) {
|
||||
// If we selected Auto don't do anything and Auto will handle changing.
|
||||
if (i == 40) {
|
||||
auto_channel = true;
|
||||
return;
|
||||
auto_channel = (i == BLT_CHAN_AUTO);
|
||||
all_channels = (i == BLT_CHAN_ALL);
|
||||
|
||||
advCount = 0;
|
||||
|
||||
if (auto_channel) {
|
||||
channel_number = 37;
|
||||
} else if (all_channels) {
|
||||
channel_number = 0;
|
||||
} else {
|
||||
auto_channel = false;
|
||||
channel_number = i;
|
||||
}
|
||||
|
||||
field_frequency.set_value(get_freq_by_channel_number(i));
|
||||
channel_number = i;
|
||||
field_frequency.set_value(get_freq_by_channel_number(channel_number));
|
||||
};
|
||||
|
||||
options_speed.on_change = [this](size_t, int32_t i) {
|
||||
@@ -371,13 +414,8 @@ BLETxView::BLETxView(NavigationView& nav)
|
||||
timer_count = 0;
|
||||
};
|
||||
|
||||
options_adv_type.on_change = [this](size_t, int32_t i) {
|
||||
pduType = (PKT_TYPE)i;
|
||||
};
|
||||
|
||||
options_speed.set_selected_index(0);
|
||||
options_channel.set_selected_index(0);
|
||||
options_adv_type.set_selected_index(0);
|
||||
options_channel.set_by_value(BLT_CHAN_AUTO);
|
||||
|
||||
check_rand_mac.set_value(false);
|
||||
check_rand_mac.on_select = [this](Checkbox&, bool v) {
|
||||
@@ -387,8 +425,7 @@ BLETxView::BLETxView(NavigationView& nav)
|
||||
button_open.on_select = [this](Button&) {
|
||||
auto open_view = nav_.push<FileLoadView>(".TXT");
|
||||
open_view->on_changed = [this](std::filesystem::path new_file_path) {
|
||||
on_file_changed(new_file_path);
|
||||
|
||||
this->on_file_changed(new_file_path);
|
||||
nav_.set_on_pop([this]() { button_play.focus(); });
|
||||
};
|
||||
};
|
||||
@@ -399,8 +436,9 @@ BLETxView::BLETxView(NavigationView& nav)
|
||||
nav,
|
||||
packetFileBuffer,
|
||||
64,
|
||||
ENTER_KEYBOARD_MODE_ALPHA,
|
||||
[this](std::string& buffer) {
|
||||
on_save_file(buffer);
|
||||
this->on_save_file(buffer);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -429,6 +467,23 @@ BLETxView::BLETxView(NavigationView& nav)
|
||||
}
|
||||
};
|
||||
|
||||
dataEditView.on_change = [this](uint8_t value) {
|
||||
// Reject setting newline at index 29.
|
||||
if (cursor_pos.col != 29) {
|
||||
uint16_t dataBytePos = (cursor_pos.line * 29) + cursor_pos.col;
|
||||
|
||||
packets[current_packet].advertisementData[dataBytePos] = uint_to_char(value, 16);
|
||||
|
||||
update_current_packet(packets[current_packet], current_packet);
|
||||
}
|
||||
};
|
||||
|
||||
dataEditView.on_cursor_moved = [this]() {
|
||||
// Save last selected cursor.
|
||||
cursor_pos.line = dataEditView.line();
|
||||
cursor_pos.col = dataEditView.col();
|
||||
};
|
||||
|
||||
button_clear_marked.on_select = [this](Button&) {
|
||||
marked_counter = 0;
|
||||
markedBytes.clear();
|
||||
@@ -462,19 +517,27 @@ void BLETxView::on_file_changed(const fs::path& new_file_path) {
|
||||
}
|
||||
|
||||
do {
|
||||
readUntil(data_file, packets[num_packets].macAddress, mac_address_size_str, ' ');
|
||||
readUntil(data_file, packets[num_packets].advertisementData, max_packet_size_str, ' ');
|
||||
readUntil(data_file, packets[num_packets].packetCount, max_packet_repeat_str, '\n');
|
||||
readUntil(data_file, packets[num_packets].macAddress, sizeof(packets[num_packets].macAddress), ' ');
|
||||
readUntil(data_file, packets[num_packets].advertisementData, sizeof(packets[num_packets].advertisementData), ' ');
|
||||
readUntil(data_file, packets[num_packets].packetType, sizeof(packets[num_packets].packetType), ' ');
|
||||
readUntil(data_file, packets[num_packets].packetCount, sizeof(packets[num_packets].packetCount), '\n');
|
||||
|
||||
uint64_t macAddressSize = strlen(packets[num_packets].macAddress);
|
||||
uint64_t advertisementDataSize = strlen(packets[num_packets].advertisementData);
|
||||
uint64_t packetCountSize = strlen(packets[num_packets].packetCount);
|
||||
uint64_t packetTypeSize = strlen(packets[num_packets].packetType);
|
||||
|
||||
packets[num_packets].packet_count = stringToUint32(packets[num_packets].packetCount);
|
||||
packets[num_packets].pduType = (PKT_TYPE)get_pkt_type_from_string(packets[num_packets].packetType);
|
||||
|
||||
// Verify Data.
|
||||
if ((macAddressSize == mac_address_size_str) && (advertisementDataSize < max_packet_size_str) && (packetCountSize < max_packet_repeat_str) &&
|
||||
hasValidHexPairs(packets[num_packets].macAddress, macAddressSize / 2) && hasValidHexPairs(packets[num_packets].advertisementData, advertisementDataSize / 2) && (packets[num_packets].packet_count >= 1) && (packets[num_packets].packet_count < max_packet_repeat_count)) {
|
||||
if ((macAddressSize == mac_address_size_str) &&
|
||||
(advertisementDataSize < max_packet_size_str) &&
|
||||
(packetCountSize < max_packet_repeat_str) &&
|
||||
hasValidHexPairs(packets[num_packets].macAddress, macAddressSize / 2) &&
|
||||
hasValidHexPairs(packets[num_packets].advertisementData, advertisementDataSize / 2) &&
|
||||
(packets[num_packets].packet_count >= 1) && (packets[num_packets].packet_count < max_packet_repeat_count) &&
|
||||
(packetTypeSize <= max_packet_type_str)) {
|
||||
text_filename.set(truncate(file_path.filename().string(), 12));
|
||||
|
||||
} else {
|
||||
@@ -490,7 +553,7 @@ void BLETxView::on_file_changed(const fs::path& new_file_path) {
|
||||
num_packets++;
|
||||
|
||||
} while (num_packets < max_num_packets);
|
||||
|
||||
data_file.close();
|
||||
update_current_packet(packets[0], 0);
|
||||
}
|
||||
}
|
||||
@@ -516,24 +579,25 @@ void BLETxView::update_current_packet(BLETxPacket packet, uint32_t currentIndex)
|
||||
|
||||
std::vector<std::string> strings = splitIntoStrings(packet.advertisementData);
|
||||
|
||||
packet_counter = packet.packet_count;
|
||||
current_packet = currentIndex;
|
||||
|
||||
text_packet_index.set(to_string_dec_uint(current_packet));
|
||||
|
||||
text_packets_sent.set(to_string_dec_uint(packet.packet_count));
|
||||
|
||||
text_mac_address.set(formattedMacAddress);
|
||||
|
||||
packet_counter = packet.packet_count;
|
||||
current_packet = currentIndex;
|
||||
progressbar.set_max(packet.packet_count);
|
||||
|
||||
dataFile.create(dataTempFilePath);
|
||||
|
||||
for (const std::string& str : strings) {
|
||||
dataFile.write(str.c_str(), str.size());
|
||||
dataFile.write("\n", 1);
|
||||
;
|
||||
}
|
||||
|
||||
dataFile.~File();
|
||||
dataFile.close();
|
||||
|
||||
auto result = FileWrapper::open(dataTempFilePath);
|
||||
|
||||
@@ -545,6 +609,7 @@ void BLETxView::update_current_packet(BLETxPacket packet, uint32_t currentIndex)
|
||||
dataEditView.set_font_zoom(true);
|
||||
dataEditView.set_file(*dataFileWrapper);
|
||||
dataEditView.redraw(true, true);
|
||||
dataEditView.cursor_set(cursor_pos.line, cursor_pos.col);
|
||||
}
|
||||
|
||||
void BLETxView::set_parent_rect(const Rect new_parent_rect) {
|
||||
|
||||
@@ -38,11 +38,16 @@
|
||||
#include "utility.hpp"
|
||||
#include "file_path.hpp"
|
||||
|
||||
#include "ble_tx_app.hpp"
|
||||
|
||||
#include "recent_entries.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
#define BLT_CHAN_AUTO 40
|
||||
#define BLT_CHAN_ALL 41
|
||||
|
||||
class BLELoggerTx {
|
||||
public:
|
||||
Optional<File::Error> append(const std::filesystem::path& filename) {
|
||||
@@ -91,8 +96,9 @@ struct BLETxPacket {
|
||||
char macAddress[13];
|
||||
char advertisementData[63];
|
||||
char packetCount[11];
|
||||
uint32_t packet_count;
|
||||
PKT_TYPE packetType;
|
||||
char packetType[17];
|
||||
uint32_t packet_count; // repeat the same packet count times
|
||||
PKT_TYPE pduType;
|
||||
};
|
||||
|
||||
class BLETxView : public View {
|
||||
@@ -102,15 +108,15 @@ class BLETxView : public View {
|
||||
~BLETxView();
|
||||
|
||||
void set_parent_rect(const Rect new_parent_rect) override;
|
||||
void paint(Painter&) override{};
|
||||
void paint(Painter&) override {};
|
||||
|
||||
void focus() override;
|
||||
|
||||
bool is_active() const;
|
||||
void toggle();
|
||||
void start();
|
||||
void send_packet();
|
||||
void stop();
|
||||
void reset();
|
||||
void handle_replay_thread_done(const uint32_t return_code);
|
||||
void file_error();
|
||||
bool saveFile(const std::filesystem::path& path);
|
||||
@@ -122,7 +128,7 @@ class BLETxView : public View {
|
||||
void on_data(uint32_t value, bool is_data);
|
||||
void on_file_changed(const std::filesystem::path& new_file_path);
|
||||
void on_save_file(const std::string value);
|
||||
void on_tx_progress(const bool done);
|
||||
void on_tx_progress(const bool done, uint32_t progress);
|
||||
void on_random_data_change(std::string value);
|
||||
void update_current_packet(BLETxPacket packet, uint32_t currentIndex);
|
||||
|
||||
@@ -142,6 +148,7 @@ class BLETxView : public View {
|
||||
std::filesystem::path packet_save_path{bletx_dir / u"BLETX_????.TXT"};
|
||||
uint8_t channel_number = 37;
|
||||
bool auto_channel = false;
|
||||
bool all_channels = false;
|
||||
|
||||
char randomMac[13] = "010203040506";
|
||||
|
||||
@@ -153,9 +160,11 @@ class BLETxView : public View {
|
||||
int16_t auto_channel_period{6};
|
||||
|
||||
bool repeatLoop = false;
|
||||
uint32_t packet_counter{0};
|
||||
uint32_t num_packets{0};
|
||||
uint32_t current_packet{0};
|
||||
uint32_t packet_counter{0}; // the counter (decr) of the actual packet as it can be sent multiple times
|
||||
uint32_t num_packets{0}; // how many packets in the file
|
||||
uint32_t current_packet{0}; // the actual packet's num, i'm sending if i have multiple packets
|
||||
uint8_t packetTxCount{0}; // not used
|
||||
bool packetDone = false;
|
||||
bool random_mac = false;
|
||||
bool file_override = false;
|
||||
|
||||
@@ -170,39 +179,39 @@ class BLETxView : public View {
|
||||
std::vector<uint16_t> markedBytes{};
|
||||
CursorPos cursor_pos{};
|
||||
uint8_t marked_counter = 0;
|
||||
uint8_t advCount = 0;
|
||||
|
||||
static constexpr uint8_t mac_address_size_str{12};
|
||||
static constexpr uint8_t max_packet_size_str{62};
|
||||
static constexpr uint8_t max_packet_repeat_str{10};
|
||||
static constexpr uint8_t max_packet_type_str{16};
|
||||
static constexpr uint32_t max_packet_repeat_count{UINT32_MAX};
|
||||
static constexpr uint32_t max_num_packets{32};
|
||||
|
||||
BLETxPacket packets[max_num_packets];
|
||||
|
||||
PKT_TYPE pduType = {PKT_TYPE_DISCOVERY};
|
||||
|
||||
static constexpr auto header_height = 10 * 16;
|
||||
static constexpr auto switch_button_height = 6 * 16;
|
||||
|
||||
Button button_open{
|
||||
{0 * 8, 0 * 16, 10 * 8, 2 * 16},
|
||||
{UI_POS_X(0), UI_POS_Y(0), 10 * 8, 2 * 16},
|
||||
"Open file"};
|
||||
|
||||
Text text_filename{
|
||||
{11 * 8, 0 * 16, 12 * 8, 16},
|
||||
{11 * 8, UI_POS_Y(0), UI_POS_WIDTH_REMAINING(11), 16},
|
||||
"-"};
|
||||
|
||||
ProgressBar progressbar{
|
||||
{11 * 8, 1 * 16, 9 * 8, 16}};
|
||||
{11 * 8, 1 * 16, UI_POS_WIDTH_REMAINING(11 + 10), 16}};
|
||||
|
||||
Checkbox check_rand_mac{
|
||||
{21 * 8, 1 * 16},
|
||||
{UI_POS_X_RIGHT(9), 1 * 16},
|
||||
6,
|
||||
"?? Mac",
|
||||
true};
|
||||
|
||||
TxFrequencyField field_frequency{
|
||||
{0 * 8, 2 * 16},
|
||||
{UI_POS_X(0), 2 * 16},
|
||||
nav_};
|
||||
|
||||
TransmitterView2 tx_view{
|
||||
@@ -210,36 +219,74 @@ class BLETxView : public View {
|
||||
/*short_ui*/ true};
|
||||
|
||||
Checkbox check_loop{
|
||||
{21 * 8, 2 * 16},
|
||||
{UI_POS_X_RIGHT(9), 2 * 16},
|
||||
4,
|
||||
"Loop",
|
||||
true};
|
||||
|
||||
ImageButton button_play{
|
||||
{28 * 8, 2 * 16, 2 * 8, 1 * 16},
|
||||
{UI_POS_X_RIGHT(2), 2 * 16, 2 * 8, 1 * 16},
|
||||
&bitmap_play,
|
||||
Theme::getInstance()->fg_green->foreground,
|
||||
Theme::getInstance()->fg_green->background};
|
||||
|
||||
Labels label_speed{
|
||||
{{0 * 8, 6 * 8}, "Speed:", Theme::getInstance()->fg_light->foreground}};
|
||||
{{UI_POS_X(0), 6 * 8}, "Speed:", Theme::getInstance()->fg_light->foreground}};
|
||||
|
||||
OptionsField options_speed{
|
||||
{7 * 8, 6 * 8},
|
||||
3,
|
||||
{{"1 ", 1}, // 16ms
|
||||
{"2 ", 2}, // 32ms
|
||||
{"3 ", 3}, // 48ms
|
||||
{"4 ", 6}, // 100ms
|
||||
{"5 ", 12}}}; // 200ms
|
||||
{{"1 ", 2}, // 25ms
|
||||
{"2 ", 4}, // 50ms
|
||||
{"3 ", 6}, // 75ms
|
||||
{"4 ", 8}, // 100ms
|
||||
{"5 ", 12}}}; // 150ms
|
||||
|
||||
OptionsField options_channel{
|
||||
{11 * 8, 6 * 8},
|
||||
5,
|
||||
{{"Ch.37 ", 37},
|
||||
{{"Ch.0", 0},
|
||||
{"Ch.1", 1},
|
||||
{"Ch.2", 2},
|
||||
{"Ch.3", 3},
|
||||
{"Ch.4", 4},
|
||||
{"Ch.5", 5},
|
||||
{"Ch.6", 6},
|
||||
{"Ch.7", 7},
|
||||
{"Ch.8", 8},
|
||||
{"Ch.9", 9},
|
||||
{"Ch.10", 10},
|
||||
{"Ch.11", 11},
|
||||
{"Ch.12", 12},
|
||||
{"Ch.13", 13},
|
||||
{"Ch.14", 14},
|
||||
{"Ch.15", 15},
|
||||
{"Ch.16", 16},
|
||||
{"Ch.17", 17},
|
||||
{"Ch.18", 18},
|
||||
{"Ch.19", 19},
|
||||
{"Ch.20", 20},
|
||||
{"Ch.21", 21},
|
||||
{"Ch.22", 22},
|
||||
{"Ch.23", 23},
|
||||
{"Ch.24", 24},
|
||||
{"Ch.25", 25},
|
||||
{"Ch.26", 26},
|
||||
{"Ch.27", 27},
|
||||
{"Ch.28", 28},
|
||||
{"Ch.29", 29},
|
||||
{"Ch.30", 30},
|
||||
{"Ch.31", 31},
|
||||
{"Ch.32", 32},
|
||||
{"Ch.33", 33},
|
||||
{"Ch.34", 34},
|
||||
{"Ch.35", 35},
|
||||
{"Ch.36", 36},
|
||||
{"Ch.37", 37},
|
||||
{"Ch.38", 38},
|
||||
{"Ch.39", 39},
|
||||
{"Auto", 40}}};
|
||||
{"Auto", BLT_CHAN_AUTO},
|
||||
{"All", BLT_CHAN_ALL}}};
|
||||
|
||||
OptionsField options_adv_type{
|
||||
{17 * 8, 6 * 8},
|
||||
@@ -254,7 +301,7 @@ class BLETxView : public View {
|
||||
{"CONNECT_REQ", PKT_TYPE_CONNECT_REQ}}};
|
||||
|
||||
Labels label_marked_data{
|
||||
{{0 * 8, 4 * 16}, "Marked Data:", Theme::getInstance()->fg_light->foreground}};
|
||||
{{UI_POS_X(0), 4 * 16}, "Marked Data:", Theme::getInstance()->fg_light->foreground}};
|
||||
|
||||
OptionsField marked_data_sequence{
|
||||
{12 * 8, 8 * 8},
|
||||
@@ -264,45 +311,42 @@ class BLETxView : public View {
|
||||
{"Random", 2}}};
|
||||
|
||||
Labels label_packet_index{
|
||||
{{0 * 8, 12 * 8}, "Packet Index:", Theme::getInstance()->fg_light->foreground}};
|
||||
{{UI_POS_X(0), 12 * 8}, "Packet Index:", Theme::getInstance()->fg_light->foreground}};
|
||||
|
||||
Text text_packet_index{
|
||||
{13 * 8, 6 * 16, 12 * 8, 16},
|
||||
"-"};
|
||||
|
||||
Labels label_packets_sent{
|
||||
{{0 * 8, 14 * 8}, "Repeat Count:", Theme::getInstance()->fg_light->foreground}};
|
||||
{{UI_POS_X(0), 14 * 8}, "Repeat Count:", Theme::getInstance()->fg_light->foreground}};
|
||||
|
||||
Text text_packets_sent{
|
||||
{13 * 8, 7 * 16, 12 * 8, 16},
|
||||
"-"};
|
||||
|
||||
Labels label_mac_address{
|
||||
{{0 * 8, 16 * 8}, "Mac Address:", Theme::getInstance()->fg_light->foreground}};
|
||||
{{UI_POS_X(0), 16 * 8}, "Mac Address:", Theme::getInstance()->fg_light->foreground}};
|
||||
|
||||
Text text_mac_address{
|
||||
{12 * 8, 8 * 16, 20 * 8, 16},
|
||||
"-"};
|
||||
|
||||
Labels label_data_packet{
|
||||
{{0 * 8, 9 * 16}, "Packet Data:", Theme::getInstance()->fg_light->foreground}};
|
||||
|
||||
Console console{
|
||||
{0, 9 * 18, 240, 240}};
|
||||
{{UI_POS_X(0), 9 * 16}, "Packet Data:", Theme::getInstance()->fg_light->foreground}};
|
||||
|
||||
TextViewer dataEditView{
|
||||
{0, 9 * 18, 240, 240}};
|
||||
|
||||
Button button_clear_marked{
|
||||
{1 * 8, 14 * 16, 13 * 8, 3 * 8},
|
||||
{UI_POS_X(1), UI_POS_Y_BOTTOM(5), UI_POS_WIDTH(13), UI_POS_HEIGHT(1.5)},
|
||||
"Clear Marked"};
|
||||
|
||||
Button button_save_packet{
|
||||
{1 * 8, 16 * 16, 13 * 8, 2 * 16},
|
||||
{UI_POS_X(1), UI_POS_Y_BOTTOM(3), UI_POS_WIDTH(13), UI_POS_HEIGHT(2)},
|
||||
"Save Packet"};
|
||||
|
||||
Button button_switch{
|
||||
{16 * 8, 16 * 16, 13 * 8, 2 * 16},
|
||||
{UI_POS_X_RIGHT(14), UI_POS_Y_BOTTOM(3), UI_POS_WIDTH(13), UI_POS_HEIGHT(2)},
|
||||
"Switch to Rx"};
|
||||
|
||||
std::string str_log{""};
|
||||
@@ -323,7 +367,7 @@ class BLETxView : public View {
|
||||
Message::ID::TXProgress,
|
||||
[this](const Message* const p) {
|
||||
const auto message = *reinterpret_cast<const TXProgressMessage*>(p);
|
||||
this->on_tx_progress(message.done);
|
||||
this->on_tx_progress(message.done, message.progress);
|
||||
}};
|
||||
|
||||
MessageHandlerRegistration message_handler_frame_sync{
|
||||
|
||||
@@ -55,22 +55,24 @@ CaptureAppView::CaptureAppView(NavigationView& nav)
|
||||
this->field_frequency.set_step(v);
|
||||
};
|
||||
|
||||
option_format.set_selected_index(0); // Default to C16
|
||||
option_format.set_selected_index(file_format);
|
||||
option_format.on_change = [this](size_t, uint32_t file_type) {
|
||||
file_format = file_type;
|
||||
record_view.set_file_type((RecordView::FileType)file_type);
|
||||
};
|
||||
|
||||
check_trim.set_value(trim);
|
||||
check_trim.on_select = [this](Checkbox&, bool v) {
|
||||
trim = v;
|
||||
record_view.set_auto_trim(v);
|
||||
};
|
||||
|
||||
freqman_set_bandwidth_option(SPEC_MODULATION, option_bandwidth);
|
||||
option_bandwidth.on_change = [this](size_t, uint32_t bandwidth) {
|
||||
option_bandwidth.on_change = [this](size_t, uint32_t new_capture_rate) {
|
||||
/* Nyquist would imply a sample rate of 2x bandwidth, but because the ADC
|
||||
* provides 2 values (I,Q), the sample_rate is equal to bandwidth here. */
|
||||
auto sample_rate = bandwidth;
|
||||
|
||||
/* base_rate (bandwidth) is used for FFT calculation and display LCD, and also in recording writing SD Card rate. */
|
||||
/* capture_rate (bandwidth) is used for FFT calculation and display LCD, and also in recording writing SD Card rate. */
|
||||
/* ex. sampling_rate values, 4Mhz, when recording 500 kHz (BW) and fs 8 Mhz, when selected 1 Mhz BW ... */
|
||||
/* ex. recording 500kHz BW to .C16 file, base_rate clock 500kHz x2(I,Q) x 2 bytes (int signed) =2MB/sec rate SD Card. */
|
||||
|
||||
@@ -78,7 +80,7 @@ CaptureAppView::CaptureAppView(NavigationView& nav)
|
||||
|
||||
// record_view determines the correct oversampling to apply and returns the actual sample rate.
|
||||
// NB: record_view is what actually updates proc_capture baseband settings.
|
||||
auto actual_sample_rate = record_view.set_sampling_rate(sample_rate);
|
||||
auto actual_sample_rate = record_view.set_sampling_rate(new_capture_rate);
|
||||
|
||||
// Update the radio model with the actual sampling rate.
|
||||
receiver_model.set_sampling_rate(actual_sample_rate);
|
||||
@@ -88,19 +90,19 @@ CaptureAppView::CaptureAppView(NavigationView& nav)
|
||||
receiver_model.set_baseband_bandwidth(anti_alias_filter_bandwidth);
|
||||
|
||||
// Automatically switch default capture format to C8 when bandwidth setting is increased to >=1.5MHz anb back to C16 for <=1,25Mhz
|
||||
if ((bandwidth >= 1500000) && (previous_bandwidth < 1500000)) {
|
||||
if ((new_capture_rate >= 1500000) && (capture_rate < 1500000)) {
|
||||
option_format.set_selected_index(1); // Default C8 format for REC, 1500K ... 5500k
|
||||
}
|
||||
if ((bandwidth <= 1250000) && (previous_bandwidth > 1250000)) {
|
||||
if ((new_capture_rate <= 1250000) && (capture_rate > 1250000)) {
|
||||
option_format.set_selected_index(0); // Default C16 format for REC , 12k5 ... 1250K
|
||||
}
|
||||
previous_bandwidth = bandwidth;
|
||||
capture_rate = new_capture_rate;
|
||||
|
||||
waterfall.start();
|
||||
};
|
||||
|
||||
receiver_model.enable();
|
||||
option_bandwidth.set_by_value(500000);
|
||||
option_bandwidth.set_by_value(capture_rate);
|
||||
|
||||
record_view.on_error = [&nav](std::string message) {
|
||||
nav.display_modal("Error", message);
|
||||
|
||||
@@ -48,39 +48,48 @@ class CaptureAppView : public View {
|
||||
|
||||
private:
|
||||
static constexpr ui::Dim header_height = 3 * 16;
|
||||
uint32_t previous_bandwidth{500000};
|
||||
|
||||
uint32_t capture_rate{500000};
|
||||
uint32_t file_format{0};
|
||||
bool trim{false};
|
||||
|
||||
NavigationView& nav_;
|
||||
RxRadioState radio_state_{ReceiverModel::Mode::Capture};
|
||||
app_settings::SettingsManager settings_{
|
||||
"rx_capture", app_settings::Mode::RX};
|
||||
"rx_capture",
|
||||
app_settings::Mode::RX,
|
||||
{
|
||||
{"capture_rate"sv, &capture_rate},
|
||||
{"file_format"sv, &file_format},
|
||||
{"trim"sv, &trim},
|
||||
}};
|
||||
|
||||
Labels labels{
|
||||
{{0 * 8, 1 * 16}, "Rate:", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(0), 1 * 16}, "Rate:", Theme::getInstance()->fg_light->foreground},
|
||||
{{11 * 8, 1 * 16}, "Format:", Theme::getInstance()->fg_light->foreground},
|
||||
};
|
||||
|
||||
RSSI rssi{
|
||||
{24 * 8, 0, 6 * 8, 4}};
|
||||
{UI_POS_X(24), 0, UI_POS_WIDTH_REMAINING(24), 4}};
|
||||
|
||||
Channel channel{
|
||||
{24 * 8, 5, 6 * 8, 4}};
|
||||
{UI_POS_X(24), 5, UI_POS_WIDTH_REMAINING(24), 4}};
|
||||
|
||||
RxFrequencyField field_frequency{
|
||||
{0 * 8, 0 * 16},
|
||||
{UI_POS_X(0), UI_POS_Y(0)},
|
||||
nav_};
|
||||
|
||||
FrequencyStepView field_frequency_step{
|
||||
{10 * 8, 0 * 16}};
|
||||
{10 * 8, UI_POS_Y(0)}};
|
||||
|
||||
RFAmpField field_rf_amp{
|
||||
{16 * 8, 0 * 16}};
|
||||
{16 * 8, UI_POS_Y(0)}};
|
||||
|
||||
LNAGainField field_lna{
|
||||
{18 * 8, 0 * 16}};
|
||||
{18 * 8, UI_POS_Y(0)}};
|
||||
|
||||
VGAGainField field_vga{
|
||||
{21 * 8, 0 * 16}};
|
||||
{21 * 8, UI_POS_Y(0)}};
|
||||
|
||||
OptionsField option_bandwidth{
|
||||
{5 * 8, 1 * 16},
|
||||
@@ -100,7 +109,7 @@ class CaptureAppView : public View {
|
||||
/*small*/ true};
|
||||
|
||||
RecordView record_view{
|
||||
{0 * 8, 2 * 16, 30 * 8, 1 * 16},
|
||||
{UI_POS_X(0), 2 * 16, screen_width, 1 * 16},
|
||||
u"BBD_????.*",
|
||||
captures_dir,
|
||||
RecordView::FileType::RawS16,
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include "string_format.hpp"
|
||||
#include "utility.hpp"
|
||||
#include "file_path.hpp"
|
||||
#include "usb_serial_asyncmsg.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
using namespace pocsag;
|
||||
@@ -56,20 +57,24 @@ POCSAGSettingsView::POCSAGSettingsView(
|
||||
: settings_{settings} {
|
||||
add_children(
|
||||
{&labels,
|
||||
&opt_baud_rate,
|
||||
&check_log,
|
||||
&check_log_raw,
|
||||
&check_small_font,
|
||||
&check_hide_bad,
|
||||
&check_hide_addr_only,
|
||||
&check_numeric_detect,
|
||||
&opt_filter_mode,
|
||||
&field_filter_address,
|
||||
&button_save});
|
||||
|
||||
opt_baud_rate.set_by_value(settings_.baud_rate);
|
||||
check_log.set_value(settings_.enable_logging);
|
||||
check_log_raw.set_value(settings_.enable_raw_log);
|
||||
check_small_font.set_value(settings_.enable_small_font);
|
||||
check_hide_bad.set_value(settings_.hide_bad_data);
|
||||
check_hide_addr_only.set_value(settings_.hide_addr_only);
|
||||
check_numeric_detect.set_value(settings_.enable_numeric_detect);
|
||||
opt_filter_mode.set_by_value(settings_.filter_mode);
|
||||
field_filter_address.set_value(settings_.filter_address);
|
||||
|
||||
@@ -79,9 +84,10 @@ POCSAGSettingsView::POCSAGSettingsView(
|
||||
settings_.enable_small_font = check_small_font.value();
|
||||
settings_.hide_bad_data = check_hide_bad.value();
|
||||
settings_.hide_addr_only = check_hide_addr_only.value();
|
||||
settings_.enable_numeric_detect = check_numeric_detect.value();
|
||||
settings_.filter_mode = opt_filter_mode.selected_index_value();
|
||||
settings_.filter_address = field_filter_address.to_integer();
|
||||
|
||||
settings_.baud_rate = opt_baud_rate.selected_index_value();
|
||||
nav.pop();
|
||||
};
|
||||
}
|
||||
@@ -142,7 +148,7 @@ POCSAGAppView::POCSAGAppView(NavigationView& nav)
|
||||
|
||||
audio::output::start();
|
||||
receiver_model.enable();
|
||||
baseband::set_pocsag();
|
||||
baseband::set_pocsag((int8_t)settings_.baud_rate);
|
||||
}
|
||||
|
||||
void POCSAGAppView::focus() {
|
||||
@@ -182,6 +188,7 @@ void POCSAGAppView::refresh_ui() {
|
||||
btn_text = "Filter Last";
|
||||
break;
|
||||
}
|
||||
baseband::set_pocsag((int8_t)settings_.baud_rate);
|
||||
button_filter_last.set_text(btn_text);
|
||||
}
|
||||
|
||||
@@ -216,12 +223,24 @@ void POCSAGAppView::handle_decoded(Timestamp timestamp, const std::string& prefi
|
||||
return;
|
||||
}
|
||||
|
||||
// Color indicates the message has a lot of decoding errors.
|
||||
std::string color = bad_data ? STR_COLOR_MAGENTA : STR_COLOR_WHITE;
|
||||
// Type indicator with its own color.
|
||||
std::string type_str;
|
||||
bool numeric_detect = settings_.enable_numeric_detect;
|
||||
if (numeric_detect && pocsag_state.detected == pocsag::DET_NUMERIC)
|
||||
type_str = STR_COLOR_GREEN "n";
|
||||
else if (numeric_detect && pocsag_state.detected == pocsag::DET_ALPHA)
|
||||
type_str = STR_COLOR_LIGHT_GREY "a";
|
||||
else if (pocsag_state.out_type == ADDRESS)
|
||||
type_str = STR_COLOR_DARK_YELLOW "t";
|
||||
else
|
||||
type_str = "";
|
||||
|
||||
std::string console_info = "\n" + color + prefix;
|
||||
console_info += " #" + to_string_dec_uint(pocsag_state.address);
|
||||
// Header: timestamp+baud in light grey, capcode+function in white.
|
||||
std::string console_info = "\n" STR_COLOR_LIGHT_GREY + prefix;
|
||||
console_info += STR_COLOR_WHITE " #" + to_string_dec_uint(pocsag_state.address);
|
||||
console_info += " F" + to_string_dec_uint(pocsag_state.function);
|
||||
if (!type_str.empty())
|
||||
console_info += " " + type_str;
|
||||
|
||||
if (pocsag_state.out_type == ADDRESS) {
|
||||
last_address = pocsag_state.address;
|
||||
@@ -237,23 +256,111 @@ void POCSAGAppView::handle_decoded(Timestamp timestamp, const std::string& prefi
|
||||
}
|
||||
}
|
||||
|
||||
/* Serial: tone-only page */
|
||||
if (portapack::usb_serial.serial_connected()) {
|
||||
std::string s = "\r\nPOCSAG " + to_string_dec_uint(current_bitrate) + " " + to_string_dec_uint(pocsag_state.address) + " " + std::string(1, 'A' + pocsag_state.function) + (current_inverted ? " I" : " S") + " tone";
|
||||
UsbSerialAsyncmsg::asyncmsg(s);
|
||||
}
|
||||
|
||||
} else if (pocsag_state.out_type == MESSAGE) {
|
||||
if (pocsag_state.address != last_address) {
|
||||
// New message
|
||||
if (pocsag_state.new_message) {
|
||||
last_address = pocsag_state.address;
|
||||
serial_numeric_sent = 0;
|
||||
console.writeln(console_info);
|
||||
console.write(color + pocsag_state.output);
|
||||
|
||||
// If heuristic chose numeric, show numeric line first (green).
|
||||
if (numeric_detect &&
|
||||
pocsag_state.detected == pocsag::DET_NUMERIC &&
|
||||
pocsag_state.numeric_len > 0) {
|
||||
std::string num_str(pocsag_state.numeric_buf, pocsag_state.numeric_len);
|
||||
console.write(STR_COLOR_GREEN + num_str);
|
||||
console.writeln("");
|
||||
}
|
||||
|
||||
// Alpha decode (already has per-char color escapes from decoder).
|
||||
console.write(pocsag_state.output);
|
||||
|
||||
/* Serial: header + first chunk.
|
||||
* hex field contains rendered alpha as hex bytes (color escapes
|
||||
* stripped). Non-printable chars show as '.' (0x2E),
|
||||
* uncorrectable chars show as '?' (0x3F) — original 7-bit
|
||||
* values are not preserved.
|
||||
* Numeric decode goes in the quoted message field only. */
|
||||
if (portapack::usb_serial.serial_connected()) {
|
||||
/* Build hex representation of decoded alpha characters. */
|
||||
std::string raw;
|
||||
for (size_t i = 0; i < pocsag_state.output.size(); ++i)
|
||||
raw += to_string_hex((uint8_t)pocsag_state.output[i], 2);
|
||||
|
||||
std::string s = "\r\nPOCSAG " + to_string_dec_uint(current_bitrate) + " " + to_string_dec_uint(pocsag_state.address) + " " + std::string(1, 'A' + pocsag_state.function) + (current_inverted ? " I" : " S");
|
||||
|
||||
if (numeric_detect && pocsag_state.detected == pocsag::DET_NUMERIC) {
|
||||
s += " numeric \"";
|
||||
if (pocsag_state.numeric_len > 0)
|
||||
s += std::string(pocsag_state.numeric_buf, pocsag_state.numeric_len);
|
||||
s += "\"";
|
||||
serial_numeric_sent = pocsag_state.numeric_len;
|
||||
} else {
|
||||
/* For alpha, the quoted text is the same chars as raw but as ASCII.
|
||||
* Escape " and \ to avoid breaking the quoted field. */
|
||||
s += " alpha \"";
|
||||
for (size_t i = 0; i < pocsag_state.output.size(); ++i) {
|
||||
if (pocsag_state.output[i] == '"' || pocsag_state.output[i] == '\\') {
|
||||
s += '\\';
|
||||
}
|
||||
s += pocsag_state.output[i];
|
||||
}
|
||||
s += "\"";
|
||||
}
|
||||
s += " hex:" + raw;
|
||||
UsbSerialAsyncmsg::asyncmsg(s);
|
||||
}
|
||||
} else {
|
||||
// Message continues...
|
||||
console.write(color + pocsag_state.output);
|
||||
// Message continues from previous batch.
|
||||
bool is_numeric = numeric_detect && pocsag_state.detected == pocsag::DET_NUMERIC;
|
||||
|
||||
// GUI: show numeric continuation if applicable.
|
||||
if (is_numeric && pocsag_state.numeric_len > serial_numeric_sent) {
|
||||
std::string num_str(pocsag_state.numeric_buf + serial_numeric_sent,
|
||||
pocsag_state.numeric_len - serial_numeric_sent);
|
||||
console.write(STR_COLOR_GREEN + num_str);
|
||||
}
|
||||
console.write(pocsag_state.output);
|
||||
|
||||
/* Serial: continuation chunk with full header.
|
||||
* Type label carries over from first batch (numeric+ or alpha+). */
|
||||
if (portapack::usb_serial.serial_connected()) {
|
||||
std::string raw;
|
||||
std::string decoded;
|
||||
for (size_t i = 0; i < pocsag_state.output.size(); ++i) {
|
||||
if (pocsag_state.output[i] == '"' || pocsag_state.output[i] == '\\')
|
||||
decoded += '\\';
|
||||
decoded += pocsag_state.output[i];
|
||||
raw += to_string_hex((uint8_t)pocsag_state.output[i], 2);
|
||||
}
|
||||
if (!decoded.empty() || pocsag_state.numeric_len > serial_numeric_sent) {
|
||||
std::string s = "\r\nPOCSAG " + to_string_dec_uint(current_bitrate) + " " + to_string_dec_uint(pocsag_state.address) + " " + std::string(1, 'A' + pocsag_state.function) + (current_inverted ? " I" : " S") + (is_numeric ? " numeric+" : " alpha+") + " \"";
|
||||
if (is_numeric && pocsag_state.numeric_len > serial_numeric_sent)
|
||||
s += std::string(pocsag_state.numeric_buf + serial_numeric_sent,
|
||||
pocsag_state.numeric_len - serial_numeric_sent);
|
||||
else
|
||||
s += decoded;
|
||||
s += "\" hex:" + raw;
|
||||
UsbSerialAsyncmsg::asyncmsg(s);
|
||||
}
|
||||
}
|
||||
serial_numeric_sent = pocsag_state.numeric_len;
|
||||
}
|
||||
|
||||
if (logging()) {
|
||||
logger.log_decoded(
|
||||
timestamp,
|
||||
to_string_dec_uint(pocsag_state.address) +
|
||||
" F" + to_string_dec_uint(pocsag_state.function) +
|
||||
" " + pocsag_state.output);
|
||||
std::string log_entry = to_string_dec_uint(pocsag_state.address) +
|
||||
" F" + to_string_dec_uint(pocsag_state.function);
|
||||
if (numeric_detect &&
|
||||
pocsag_state.detected == pocsag::DET_NUMERIC &&
|
||||
pocsag_state.numeric_len > 0)
|
||||
log_entry += " N:" + std::string(pocsag_state.numeric_buf, pocsag_state.numeric_len);
|
||||
log_entry += " " + pocsag_state.output;
|
||||
logger.log_decoded(timestamp, log_entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -297,13 +404,19 @@ void POCSAGAppView::on_packet(const POCSAGPacketMessage* message) {
|
||||
image_status.set_foreground(Theme::getInstance()->fg_magenta->foreground);
|
||||
pocsag_state.codeword_index = 0;
|
||||
pocsag_state.errors = 0;
|
||||
current_bitrate = message->packet.bitrate();
|
||||
current_inverted = message->packet.inverted();
|
||||
|
||||
// Handle multiple messages (if any).
|
||||
while (pocsag_decode_batch(message->packet, pocsag_state))
|
||||
handle_decoded(message->packet.timestamp(), prefix);
|
||||
|
||||
// Handle the remainder.
|
||||
handle_decoded(message->packet.timestamp(), prefix);
|
||||
// Handle the remainder. Skip if decoder is still in
|
||||
// STATE_HAVE_ADDRESS — the address was at the end of this
|
||||
// batch and we can't yet tell if it's tone-only or has
|
||||
// message data in the next batch.
|
||||
if (pocsag_state.mode != STATE_HAVE_ADDRESS)
|
||||
handle_decoded(message->packet.timestamp(), prefix);
|
||||
}
|
||||
|
||||
// Set status icon color to indicate state machine state.
|
||||
|
||||
@@ -125,7 +125,9 @@ struct POCSAGSettings {
|
||||
bool enable_raw_log = false;
|
||||
bool hide_bad_data = false;
|
||||
bool hide_addr_only = false;
|
||||
bool enable_numeric_detect = true; /* heuristic alpha/numeric type detection */
|
||||
uint8_t filter_mode = false;
|
||||
int32_t baud_rate = -1;
|
||||
uint32_t filter_address = 0;
|
||||
};
|
||||
|
||||
@@ -139,9 +141,18 @@ class POCSAGSettingsView : public View {
|
||||
private:
|
||||
POCSAGSettings& settings_;
|
||||
|
||||
OptionsField opt_baud_rate{
|
||||
{8 * 8, 0 * 16},
|
||||
4,
|
||||
{{"Auto", -1},
|
||||
{" 512", 0},
|
||||
{"1200", 1},
|
||||
{"2400", 2}}};
|
||||
|
||||
Labels labels{
|
||||
{{2 * 8, 12 * 16}, "Filter Mode:", Theme::getInstance()->fg_light->foreground},
|
||||
{{2 * 8, 13 * 16}, "Filter Addr:", Theme::getInstance()->fg_light->foreground},
|
||||
{{2 * 8, 0 * 16}, "Baud:", Theme::getInstance()->fg_light->foreground},
|
||||
{{2 * 8, 14 * 16}, "Filter Mode:", Theme::getInstance()->fg_light->foreground},
|
||||
{{2 * 8, 15 * 16}, "Filter Addr:", Theme::getInstance()->fg_light->foreground},
|
||||
};
|
||||
|
||||
Checkbox check_log{
|
||||
@@ -169,21 +180,26 @@ class POCSAGSettingsView : public View {
|
||||
22,
|
||||
"Hide Addr Only"};
|
||||
|
||||
Checkbox check_numeric_detect{
|
||||
{2 * 8, 12 * 16},
|
||||
22,
|
||||
"Detect Numeric"};
|
||||
|
||||
OptionsField opt_filter_mode{
|
||||
{15 * 8, 12 * 16},
|
||||
{15 * 8, 14 * 16},
|
||||
4,
|
||||
{{"None", FILTER_NONE},
|
||||
{"Drop", FILTER_DROP},
|
||||
{"Keep", FILTER_KEEP}}};
|
||||
|
||||
SymField field_filter_address{
|
||||
{15 * 8, 13 * 16},
|
||||
{15 * 8, 15 * 16},
|
||||
7,
|
||||
SymField::Type::Dec,
|
||||
true /*explicit_edit*/};
|
||||
|
||||
Button button_save{
|
||||
{11 * 8, 16 * 16, 10 * 8, 2 * 16},
|
||||
{UI_POS_X_CENTER(10), UI_POS_Y(17), 10 * 8, 2 * 16},
|
||||
"Save"};
|
||||
};
|
||||
|
||||
@@ -221,6 +237,8 @@ class POCSAGAppView : public View {
|
||||
{"filter_address"sv, &settings_.filter_address},
|
||||
{"hide_bad_data"sv, &settings_.hide_bad_data},
|
||||
{"hide_addr_only"sv, &settings_.hide_addr_only},
|
||||
{"numeric_detect"sv, &settings_.enable_numeric_detect},
|
||||
{"baud_rate"sv, &settings_.baud_rate},
|
||||
}};
|
||||
|
||||
void refresh_ui();
|
||||
@@ -230,36 +248,40 @@ class POCSAGAppView : public View {
|
||||
void on_stats(const POCSAGStatsMessage* stats);
|
||||
|
||||
uint32_t last_address = 0;
|
||||
uint16_t current_bitrate = 0; /* bitrate of current packet being decoded */
|
||||
bool current_inverted = false; /* polarity of current packet being decoded */
|
||||
uint8_t serial_numeric_sent = 0; /* numeric chars already sent to serial/GUI */
|
||||
|
||||
pocsag::EccContainer ecc{};
|
||||
pocsag::POCSAGState pocsag_state{&ecc};
|
||||
POCSAGLogger logger{};
|
||||
uint16_t packet_count = 0;
|
||||
|
||||
RxFrequencyField field_frequency{
|
||||
{0 * 8, 0 * 8},
|
||||
{UI_POS_X(0), 0 * 8},
|
||||
nav_};
|
||||
|
||||
RFAmpField field_rf_amp{
|
||||
{11 * 8, 0 * 16}};
|
||||
{11 * 8, UI_POS_Y(0)}};
|
||||
LNAGainField field_lna{
|
||||
{13 * 8, 0 * 16}};
|
||||
{13 * 8, UI_POS_Y(0)}};
|
||||
VGAGainField field_vga{
|
||||
{16 * 8, 0 * 16}};
|
||||
{16 * 8, UI_POS_Y(0)}};
|
||||
|
||||
RSSI rssi{
|
||||
{19 * 8 - 4, 3, 6 * 8, 4}};
|
||||
{19 * 8 - 4, 3, UI_POS_WIDTH_REMAINING(26), 4}};
|
||||
Audio audio{
|
||||
{19 * 8 - 4, 8, 6 * 8, 4}};
|
||||
{19 * 8 - 4, 8, UI_POS_WIDTH_REMAINING(26), 4}};
|
||||
|
||||
NumberField field_squelch{
|
||||
{25 * 8, 0 * 16},
|
||||
{UI_POS_X_RIGHT(6), UI_POS_Y(0)},
|
||||
2,
|
||||
{0, 99},
|
||||
1,
|
||||
' ',
|
||||
true /*wrap*/};
|
||||
AudioVolumeField field_volume{
|
||||
{28 * 8, 0 * 16}};
|
||||
{UI_POS_X_RIGHT(2), UI_POS_Y(0)}};
|
||||
|
||||
Image image_status{
|
||||
{0 * 8 + 4, 1 * 16 + 2, 16, 16},
|
||||
|
||||
@@ -7,30 +7,34 @@
|
||||
namespace ui {
|
||||
|
||||
// Information: a line starting with a '#' will be yellow coloured
|
||||
constexpr std::string_view authors_list[] = {
|
||||
"# * List of contributors * ",
|
||||
constexpr std::string_view mayhem_information_list[] = {
|
||||
"#****** Mayhem Community ******",
|
||||
" ",
|
||||
" https://discord.hackrf.app",
|
||||
" ",
|
||||
"#**** List of contributors ****",
|
||||
" ",
|
||||
"#Mayhem-Firmware:",
|
||||
"jboone,eried,furrtek,",
|
||||
"NotherNgineer,gullradriel,",
|
||||
"jLynx,kallanreed,Brumi-2021,",
|
||||
"htotoo,bernd-herzog,zxkmm,",
|
||||
"htotoo,zxkmm,bernd-herzog,",
|
||||
"ArjanOnwezen,euquiq,u-foka,",
|
||||
"iNetro,heurist1,dqs105,",
|
||||
"teixeluis,jwetzell,",
|
||||
"jimilinuxguy,gregoryfenton,",
|
||||
"notpike,strijar,BehleZebub,",
|
||||
"arneluehrs,rascafr,joyel24,",
|
||||
"ImDroided,zigad,johnelder,",
|
||||
"klockee,nnesetto,LupusE,",
|
||||
"argilo,dc2dc,formtapez,",
|
||||
"arneluehrs,mcules,rascafr,",
|
||||
"joyel24,ImDroided,zigad,",
|
||||
"johnelder,klockee,nnesetto,",
|
||||
"LupusE,argilo,dc2dc,formtapez,",
|
||||
"RocketGod-git,mrmookie,",
|
||||
"ITAxReal,F33RNI,F4GEV,",
|
||||
"rusty-labs,mjwaxios,andrej-mk,",
|
||||
"RedFox-Fr,nemanjan00,",
|
||||
"MichalLeonBorsuk,",
|
||||
"MatiasFernandez,Giorgiofox,",
|
||||
"ckuethe",
|
||||
"MatiasFernandez,Giorgiofox",
|
||||
"TommasoVentafridda",
|
||||
" ",
|
||||
"#Havoc:",
|
||||
"jboone,furrtek,eried,argilo,",
|
||||
@@ -72,7 +76,7 @@ AboutView::AboutView(NavigationView& nav) {
|
||||
button_ok.focus();
|
||||
};
|
||||
|
||||
for (auto& authors_line : authors_list) {
|
||||
for (auto& authors_line : mayhem_information_list) {
|
||||
// if it's starting with #, it's a title and we have to substract the '#' and paint yellow
|
||||
if (authors_line.size() > 0) {
|
||||
if (authors_line[0] == '#') {
|
||||
@@ -103,15 +107,13 @@ void AboutView::on_frame_sync() {
|
||||
menu_view.set_highlighted(current + 1);
|
||||
} else {
|
||||
menu_view.set_highlighted(0);
|
||||
// ^ to go back to the REAL top instead of make the 2 at the top to make the title disappeares
|
||||
menu_view.set_highlighted(2); // the first line that has human name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AboutView::focus() {
|
||||
button_ok.focus();
|
||||
menu_view.set_highlighted(2); // the first line that has human name
|
||||
menu_view.set_highlighted(3); // contributors block starting line
|
||||
}
|
||||
|
||||
bool AboutView::on_touch(const TouchEvent) {
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
/*
|
||||
TODO: Now it is dyn width. There should be an algorithm to fill the menu based on it's size.
|
||||
*/
|
||||
|
||||
namespace ui {
|
||||
class AboutView : public View {
|
||||
public:
|
||||
@@ -22,11 +26,11 @@ class AboutView : public View {
|
||||
uint16_t frame_sync_count{0};
|
||||
void on_frame_sync();
|
||||
MenuView menu_view{
|
||||
{0, 0, 240, 264},
|
||||
{0, 0, screen_width, screen_height - 56},
|
||||
true};
|
||||
|
||||
Button button_ok{
|
||||
{240 / 3, 270, 240 / 3, 24},
|
||||
{screen_width / 3, screen_height - 50, screen_width / 3, 24},
|
||||
"OK",
|
||||
};
|
||||
|
||||
|
||||
@@ -1,120 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2017 Furrtek
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ui_aprs_tx.hpp"
|
||||
#include "ui_alphanum.hpp"
|
||||
|
||||
#include "aprs.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "portapack.hpp"
|
||||
#include "baseband_api.hpp"
|
||||
#include "portapack_shared_memory.hpp"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
|
||||
#include <cstring>
|
||||
#include <stdio.h>
|
||||
|
||||
using namespace aprs;
|
||||
using namespace portapack;
|
||||
|
||||
namespace ui {
|
||||
|
||||
void APRSTXView::focus() {
|
||||
tx_view.focus();
|
||||
}
|
||||
|
||||
APRSTXView::~APRSTXView() {
|
||||
transmitter_model.disable();
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
void APRSTXView::start_tx() {
|
||||
// TODO: Clean up this API to take string_views to avoid allocations.
|
||||
make_aprs_frame(
|
||||
sym_source.to_string().c_str(), num_ssid_source.value(),
|
||||
sym_dest.to_string().c_str(), num_ssid_dest.value(),
|
||||
payload);
|
||||
|
||||
// uint8_t * bb_data_ptr = shared_memory.bb_data.data;
|
||||
// text_payload.set(to_string_hex_array(bb_data_ptr + 56, 15));
|
||||
|
||||
transmitter_model.enable();
|
||||
|
||||
baseband::set_afsk_data(
|
||||
AFSK_TX_SAMPLERATE / 1200,
|
||||
1200,
|
||||
2200,
|
||||
1,
|
||||
10000, // APRS uses fixed 10k bandwidth
|
||||
8);
|
||||
}
|
||||
|
||||
void APRSTXView::on_tx_progress(const uint32_t progress, const bool done) {
|
||||
(void)progress;
|
||||
|
||||
if (done) {
|
||||
transmitter_model.disable();
|
||||
tx_view.set_transmitting(false);
|
||||
}
|
||||
}
|
||||
|
||||
APRSTXView::APRSTXView(NavigationView& nav) {
|
||||
baseband::run_image(portapack::spi_flash::image_tag_afsk);
|
||||
|
||||
add_children({&labels,
|
||||
&sym_source,
|
||||
&num_ssid_source,
|
||||
&sym_dest,
|
||||
&num_ssid_dest,
|
||||
&text_payload,
|
||||
&button_set,
|
||||
&tx_view});
|
||||
|
||||
button_set.on_select = [this, &nav](Button&) {
|
||||
text_prompt(
|
||||
nav,
|
||||
payload,
|
||||
30,
|
||||
[this](std::string& s) {
|
||||
text_payload.set(s);
|
||||
});
|
||||
};
|
||||
|
||||
tx_view.on_edit_frequency = [this, &nav]() {
|
||||
auto new_view = nav.push<FrequencyKeypadView>(transmitter_model.target_frequency());
|
||||
new_view->on_changed = [this](rf::Frequency f) {
|
||||
transmitter_model.set_target_frequency(f);
|
||||
};
|
||||
};
|
||||
|
||||
tx_view.on_start = [this]() {
|
||||
start_tx();
|
||||
tx_view.set_transmitting(true);
|
||||
};
|
||||
|
||||
tx_view.on_stop = [this]() {
|
||||
tx_view.set_transmitting(false);
|
||||
transmitter_model.disable();
|
||||
};
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
@@ -1,114 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2017 Furrtek
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_textentry.hpp"
|
||||
#include "ui_widget.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "ui_transmitter.hpp"
|
||||
|
||||
#include "message.hpp"
|
||||
#include "modems.hpp"
|
||||
#include "transmitter_model.hpp"
|
||||
#include "app_settings.hpp"
|
||||
#include "radio_state.hpp"
|
||||
#include "portapack.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
class APRSTXView : public View {
|
||||
public:
|
||||
APRSTXView(NavigationView& nav);
|
||||
~APRSTXView();
|
||||
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override { return "APRS TX"; };
|
||||
|
||||
private:
|
||||
TxRadioState radio_state_{
|
||||
144390000 /* frequency */,
|
||||
1750000 /* bandwidth */,
|
||||
AFSK_TX_SAMPLERATE /* sampling rate */
|
||||
};
|
||||
app_settings::SettingsManager settings_{
|
||||
"tx_aprs", app_settings::Mode::TX};
|
||||
|
||||
std::string payload{""};
|
||||
|
||||
void start_tx();
|
||||
void generate_frame();
|
||||
void generate_frame_pos();
|
||||
void on_tx_progress(const uint32_t progress, const bool done);
|
||||
|
||||
Labels labels{
|
||||
{{0 * 8, 1 * 16}, "Source: SSID:", Theme::getInstance()->fg_light->foreground}, // 6 alphanum + SSID
|
||||
{{0 * 8, 2 * 16}, " Dest.: SSID:", Theme::getInstance()->fg_light->foreground},
|
||||
{{0 * 8, 4 * 16}, "Info field:", Theme::getInstance()->fg_light->foreground},
|
||||
};
|
||||
|
||||
SymField sym_source{
|
||||
{7 * 8, 1 * 16},
|
||||
6,
|
||||
SymField::Type::Alpha};
|
||||
|
||||
NumberField num_ssid_source{
|
||||
{19 * 8, 1 * 16},
|
||||
2,
|
||||
{0, 15},
|
||||
1,
|
||||
' '};
|
||||
|
||||
SymField sym_dest{
|
||||
{7 * 8, 2 * 16},
|
||||
6,
|
||||
SymField::Type::Alpha};
|
||||
|
||||
NumberField num_ssid_dest{
|
||||
{19 * 8, 2 * 16},
|
||||
2,
|
||||
{0, 15},
|
||||
1,
|
||||
' '};
|
||||
|
||||
Text text_payload{
|
||||
{0 * 8, 5 * 16, 30 * 8, 16},
|
||||
"-"};
|
||||
Button button_set{
|
||||
{0 * 8, 6 * 16, 80, 32},
|
||||
"Set"};
|
||||
|
||||
TransmitterView tx_view{
|
||||
16 * 16,
|
||||
5000,
|
||||
0 // disable setting bandwith, since APRS used fixed 10k bandwidth
|
||||
};
|
||||
|
||||
MessageHandlerRegistration message_handler_tx_progress{
|
||||
Message::ID::TXProgress,
|
||||
[this](const Message* const p) {
|
||||
const auto message = *reinterpret_cast<const TXProgressMessage*>(p);
|
||||
this->on_tx_progress(message.progress, message.done);
|
||||
}};
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
@@ -27,6 +27,8 @@
|
||||
#include "portapack.hpp"
|
||||
#include "battery.hpp"
|
||||
#include <cstring>
|
||||
#include "ui_settings.hpp"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
|
||||
@@ -50,7 +52,6 @@ void BattinfoView::update_result() {
|
||||
text_voltage.set("UNKNOWN");
|
||||
text_current.set("-");
|
||||
text_charge.set("-");
|
||||
// text_cycles.set("-");
|
||||
text_ttef.set("-");
|
||||
text_method.set("-");
|
||||
// text_warn.set("");
|
||||
@@ -58,7 +59,9 @@ void BattinfoView::update_result() {
|
||||
}
|
||||
bool uichg = false;
|
||||
uint8_t valid_mask = 0;
|
||||
battery::BatteryManagement::getBatteryInfo(valid_mask, percent, voltage, current);
|
||||
bool battMayChanged = false;
|
||||
battery::BatteryManagement::getBatteryInfo(valid_mask, percent, voltage, current, battMayChanged);
|
||||
text_state.set((battMayChanged || !persistent_memory::battery_cap_valid()) ? "Needs config." : "ok");
|
||||
// update text fields
|
||||
if (percent <= 100 && (valid_mask & battery::BatteryManagement::BATT_VALID_VOLTAGE) == battery::BatteryManagement::BATT_VALID_VOLTAGE)
|
||||
text_percent.set(to_string_dec_uint(percent) + " %");
|
||||
@@ -74,6 +77,7 @@ void BattinfoView::update_result() {
|
||||
labels_opt.hidden(false);
|
||||
text_current.hidden(false);
|
||||
text_charge.hidden(false);
|
||||
text_state.hidden(false);
|
||||
text_current.set(to_string_dec_int(current) + " mA");
|
||||
if (current >= 25) // when >25mA it is charging in any scenario
|
||||
text_charge.set("Charging");
|
||||
@@ -96,7 +100,7 @@ void BattinfoView::update_result() {
|
||||
labels_opt.hidden(true);
|
||||
text_current.hidden(true);
|
||||
text_charge.hidden(true);
|
||||
// text_cycles.hidden(true);
|
||||
text_state.hidden(true);
|
||||
text_ttef.hidden(true);
|
||||
// text_warn.set("");
|
||||
}
|
||||
@@ -138,14 +142,13 @@ void BattinfoView::update_result() {
|
||||
}
|
||||
if ((valid_mask & battery::BatteryManagement::BATT_VALID_PERCENT) == battery::BatteryManagement::BATT_VALID_PERCENT) {
|
||||
text_method.set("IC");
|
||||
button_mode.set_text("Volt");
|
||||
} else {
|
||||
text_method.set("Voltage");
|
||||
button_mode.set_text("IC");
|
||||
}
|
||||
if (battMayChanged) text_capacity.set_style(Theme::getInstance()->fg_red);
|
||||
if (uichg) set_dirty();
|
||||
// to update status bar too, send message in behalf of batt manager
|
||||
BatteryStateMessage msg{valid_mask, percent, current >= 25, voltage};
|
||||
BatteryStateMessage msg{valid_mask, percent, current >= 25, voltage, battMayChanged};
|
||||
EventDispatcher::send_message(msg);
|
||||
}
|
||||
|
||||
@@ -158,26 +161,21 @@ BattinfoView::BattinfoView(NavigationView& nav)
|
||||
&text_current,
|
||||
&text_charge,
|
||||
&text_method,
|
||||
&button_mode,
|
||||
&button_settings,
|
||||
&button_exit,
|
||||
// &text_cycles,
|
||||
&text_capacity,
|
||||
//&text_state, //disabled for now
|
||||
// &text_warn,
|
||||
&text_ttef});
|
||||
|
||||
button_exit.on_select = [this, &nav](Button&) {
|
||||
nav.pop();
|
||||
};
|
||||
button_mode.on_select = [this, &nav](Button&) {
|
||||
if (button_mode.text() == "IC") {
|
||||
battery::BatteryManagement::set_calc_override(false);
|
||||
persistent_memory::set_ui_override_batt_calc(false);
|
||||
button_mode.set_text("Volt");
|
||||
} else {
|
||||
battery::BatteryManagement::set_calc_override(true);
|
||||
persistent_memory::set_ui_override_batt_calc(true);
|
||||
button_mode.set_text("IC");
|
||||
}
|
||||
button_settings.on_select = [this, &nav](Button&) {
|
||||
nav.replace<SetBatteryView>();
|
||||
};
|
||||
text_capacity.set(to_string_dec_uint(persistent_memory::battery_cap_mah()) + " mAh");
|
||||
if (!persistent_memory::battery_cap_valid()) text_capacity.set_style(Theme::getInstance()->fg_red);
|
||||
update_result();
|
||||
if (thread == nullptr) thread = chThdCreateFromHeap(NULL, 1024, NORMALPRIO + 10, BattinfoView::static_fn, this);
|
||||
}
|
||||
|
||||
@@ -54,51 +54,57 @@ class BattinfoView : public View {
|
||||
int32_t current = 0;
|
||||
|
||||
Labels labels{
|
||||
{{2 * 8, 1 * 16}, "Percent:", Theme::getInstance()->fg_light->foreground},
|
||||
{{2 * 8, 2 * 16}, "Voltage:", Theme::getInstance()->fg_light->foreground},
|
||||
{{2 * 8, 3 * 16}, "Method:", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(2), UI_POS_Y(1)}, "Percent:", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(2), UI_POS_Y(2)}, "Voltage:", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(2), UI_POS_Y(3)}, "Method:", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(2), UI_POS_Y(4)}, "Capacity:", Theme::getInstance()->fg_light->foreground},
|
||||
};
|
||||
|
||||
Labels labels_opt{
|
||||
{{2 * 8, 4 * 16}, "Current:", Theme::getInstance()->fg_light->foreground},
|
||||
{{2 * 8, 5 * 16}, "Charge:", Theme::getInstance()->fg_light->foreground},
|
||||
{{2 * 8, 6 * 16}, "TTF/E:", Theme::getInstance()->fg_light->foreground},
|
||||
// {{2 * 8, 7 * 16}, "Cycles:", Theme::getInstance()->fg_light->foreground},
|
||||
{{2 * 8, 10 * 16}, "Change method:", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(2), UI_POS_Y(5)}, "Current:", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(2), UI_POS_Y(6)}, "Charge:", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(2), UI_POS_Y(7)}, "TTF/E:", Theme::getInstance()->fg_light->foreground},
|
||||
//{{UI_POS_X(2), UI_POS_Y(8)}, "State:", Theme::getInstance()->fg_light->foreground}, //for now, this is disabled. see max ic implementation
|
||||
{{UI_POS_X(2), UI_POS_Y(10)}, "Change settings:", Theme::getInstance()->fg_light->foreground},
|
||||
};
|
||||
|
||||
Text text_percent{
|
||||
{13 * 8, 1 * 16, 10 * 16, 16},
|
||||
{UI_POS_X(13), UI_POS_Y(1), UI_POS_WIDTH(11), UI_POS_HEIGHT(1)},
|
||||
"-"};
|
||||
Text text_voltage{
|
||||
{13 * 8, 2 * 16, 10 * 16, 16},
|
||||
{UI_POS_X(13), UI_POS_Y(2), UI_POS_WIDTH(11), UI_POS_HEIGHT(1)},
|
||||
"-"};
|
||||
Text text_method{
|
||||
{13 * 8, 3 * 16, 10 * 16, 16},
|
||||
{UI_POS_X(13), UI_POS_Y(3), UI_POS_WIDTH(11), UI_POS_HEIGHT(1)},
|
||||
"-"};
|
||||
Text text_capacity{
|
||||
{UI_POS_X(13), UI_POS_Y(4), UI_POS_WIDTH(11), UI_POS_HEIGHT(1)},
|
||||
"-"};
|
||||
Text text_current{
|
||||
{13 * 8, 4 * 16, 10 * 16, 16},
|
||||
{UI_POS_X(13), UI_POS_Y(5), UI_POS_WIDTH(11), UI_POS_HEIGHT(1)},
|
||||
"-"};
|
||||
Text text_charge{
|
||||
{13 * 8, 5 * 16, 10 * 16, 16},
|
||||
{UI_POS_X(13), UI_POS_Y(6), UI_POS_WIDTH(11), UI_POS_HEIGHT(1)},
|
||||
"-"};
|
||||
Text text_ttef{
|
||||
{13 * 8, 6 * 16, 10 * 16, 16},
|
||||
"-"};
|
||||
/* Text text_cycles{
|
||||
{13 * 8, 7 * 16, 10 * 16, 16},
|
||||
{UI_POS_X(13), UI_POS_Y(7), UI_POS_WIDTH(11), UI_POS_HEIGHT(1)},
|
||||
"-"};
|
||||
|
||||
Text text_state{
|
||||
{UI_POS_X(13), UI_POS_Y(8), UI_POS_WIDTH_REMAINING(13), UI_POS_HEIGHT(1)},
|
||||
"ok"};
|
||||
/*
|
||||
Text text_warn{
|
||||
{1 * 8, 8 * 16, 30 * 8, 2 * 16},
|
||||
""}; */
|
||||
{UI_POS_X(1), UI_POS_Y(9), screen_width, UI_POS_HEIGHT(2)},
|
||||
""};
|
||||
*/
|
||||
|
||||
Button button_mode{
|
||||
{2 * 8, 11 * 16 + 5, 5 * 16, 32},
|
||||
"Volt"};
|
||||
Button button_settings{
|
||||
{UI_POS_X(2), UI_POS_Y(11) + 5, UI_POS_WIDTH(10), UI_POS_HEIGHT(2)},
|
||||
"Settings"};
|
||||
|
||||
Button button_exit{
|
||||
{72, 17 * 16, 96, 32},
|
||||
{UI_POS_X_CENTER(12), UI_POS_Y_BOTTOM(4), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)},
|
||||
"Back"};
|
||||
static msg_t static_fn(void* arg);
|
||||
Thread* thread{nullptr};
|
||||
@@ -106,4 +112,4 @@ class BattinfoView : public View {
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
#endif /*__UI_BATTINFO__*/
|
||||
#endif /*__UI_BATTINFO__*/
|
||||
|
||||
@@ -42,7 +42,7 @@ class BMPFileViewer : public View {
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
std::filesystem::path path_{};
|
||||
BMPViewer bmp{{0, 0, 240, 320}};
|
||||
BMPViewer bmp{{0, 0, screen_width, screen_height}};
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
|
||||
@@ -63,26 +63,26 @@ class BTLERxView : public View {
|
||||
static constexpr uint8_t channel_number = 37;
|
||||
|
||||
RFAmpField field_rf_amp{
|
||||
{13 * 8, 0 * 16}};
|
||||
{UI_POS_X(13), UI_POS_Y(0)}};
|
||||
LNAGainField field_lna{
|
||||
{15 * 8, 0 * 16}};
|
||||
{UI_POS_X(15), UI_POS_Y(0)}};
|
||||
VGAGainField field_vga{
|
||||
{18 * 8, 0 * 16}};
|
||||
{UI_POS_X(18), UI_POS_Y(0)}};
|
||||
RSSI rssi{
|
||||
{21 * 8, 0, 6 * 8, 4}};
|
||||
{UI_POS_X(21), UI_POS_Y(0), UI_POS_WIDTH_REMAINING(21), 4}};
|
||||
Channel channel{
|
||||
{21 * 8, 5, 6 * 8, 4}};
|
||||
{UI_POS_X(21), 5, UI_POS_WIDTH_REMAINING(21), 4}};
|
||||
|
||||
RxFrequencyField field_frequency{
|
||||
{0 * 8, 0 * 16},
|
||||
{UI_POS_X(0), UI_POS_Y(0)},
|
||||
nav_};
|
||||
|
||||
Button button_modem_setup{
|
||||
{240 - 12 * 8, 1 * 16, 96, 24},
|
||||
{screen_width - 12 * 8, UI_POS_Y(1), 96, 24},
|
||||
"Modem setup"};
|
||||
|
||||
Console console{
|
||||
{0, 4 * 16, 240, 240}};
|
||||
{0, 4 * 16, screen_width, screen_height - 80}};
|
||||
|
||||
MessageHandlerRegistration message_handler_packet{
|
||||
Message::ID::AFSKData,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -12,7 +12,7 @@ BatteryCapacityView::RegisterEntry BatteryCapacityView::get_entry(size_t index)
|
||||
|
||||
BatteryCapacityView::BatteryCapacityView(NavigationView& nav) {
|
||||
for (size_t i = 0; i < ENTRIES_PER_PAGE; ++i) {
|
||||
name_texts[i].set_parent_rect({0 * 8, static_cast<int>((i + 1) * 16), 8 * 8, 16});
|
||||
name_texts[i].set_parent_rect({UI_POS_X(0), static_cast<int>((i + 1) * 16), 8 * 8, 16});
|
||||
addr_texts[i].set_parent_rect({9 * 8, static_cast<int>((i + 1) * 16), 4 * 8, 16});
|
||||
hex_texts[i].set_parent_rect({14 * 8, static_cast<int>((i + 1) * 16), 6 * 8, 16});
|
||||
value_texts[i].set_parent_rect({21 * 8, static_cast<int>((i + 1) * 16), 10 * 8, 16});
|
||||
|
||||
@@ -24,18 +24,18 @@ class BatteryCapacityView : public View {
|
||||
static RegisterEntry get_entry(size_t index);
|
||||
|
||||
Labels labels{
|
||||
{{0 * 8, 0 * 16}, "Reg", Theme::getInstance()->fg_yellow->foreground},
|
||||
{{9 * 8, 0 * 16}, "Addr", Theme::getInstance()->fg_yellow->foreground},
|
||||
{{14 * 8, 0 * 16}, "Hex", Theme::getInstance()->fg_yellow->foreground},
|
||||
{{21 * 8, 0 * 16}, "Value", Theme::getInstance()->fg_yellow->foreground},
|
||||
{{UI_POS_X(0), UI_POS_Y(0)}, "Reg", Theme::getInstance()->fg_yellow->foreground},
|
||||
{{9 * 8, UI_POS_Y(0)}, "Addr", Theme::getInstance()->fg_yellow->foreground},
|
||||
{{14 * 8, UI_POS_Y(0)}, "Hex", Theme::getInstance()->fg_yellow->foreground},
|
||||
{{21 * 8, UI_POS_Y(0)}, "Value", Theme::getInstance()->fg_yellow->foreground},
|
||||
};
|
||||
std::array<Text, 16> name_texts = {};
|
||||
std::array<Text, 16> addr_texts = {};
|
||||
std::array<Text, 16> hex_texts = {};
|
||||
std::array<Text, 16> value_texts = {};
|
||||
|
||||
Text page_text{{144, 284, 80, 16}, "Page 1/1"};
|
||||
Button button_done{{16, 280, 96, 24}, "Done"};
|
||||
Text page_text{{144, UI_POS_Y_BOTTOM(3), 80, 16}, "Page 1/1"};
|
||||
Button button_done{{16, UI_POS_Y_BOTTOM(3), 96, 24}, "Done"};
|
||||
|
||||
void update_values();
|
||||
void populate_page(int start_index);
|
||||
|
||||
@@ -103,7 +103,8 @@ DfuMenu2::DfuMenu2(NavigationView& nav)
|
||||
&text_info_line_8,
|
||||
&text_info_line_9,
|
||||
&text_info_line_10,
|
||||
&text_info_line_11});
|
||||
&text_info_line_11,
|
||||
&text_info_line_12});
|
||||
}
|
||||
|
||||
void DfuMenu2::paint(Painter& painter) {
|
||||
@@ -118,9 +119,12 @@ void DfuMenu2::paint(Painter& painter) {
|
||||
text_info_line_9.set(to_string_dec_uint(portapack::transmitter_model.target_frequency(), 10));
|
||||
text_info_line_10.set(to_string_dec_uint(portapack::transmitter_model.baseband_bandwidth(), 10));
|
||||
text_info_line_11.set(to_string_dec_uint(portapack::transmitter_model.sampling_rate(), 10));
|
||||
text_info_line_12.set(std::string("M") + (radio::get_mixer_invert() ? "Y" : "N") +
|
||||
" B" + (radio::get_baseband_invert() ? "Y" : "N") +
|
||||
" IQ" + ((radio::get_mixer_invert() ^ radio::get_baseband_invert()) ? "Y" : "N"));
|
||||
|
||||
constexpr auto margin = 5;
|
||||
constexpr auto lines = 11 + 2;
|
||||
constexpr auto lines = 12 + 2;
|
||||
|
||||
painter.fill_rectangle(
|
||||
{{5 * CHARACTER_WIDTH - margin, 3 * LINE_HEIGHT - margin},
|
||||
|
||||
@@ -96,6 +96,7 @@ class DfuMenu2 : public View {
|
||||
{{5 * CHARACTER_WIDTH, 13 * LINE_HEIGHT}, "TX Freq:", Theme::getInstance()->fg_darkcyan->foreground},
|
||||
{{5 * CHARACTER_WIDTH, 14 * LINE_HEIGHT}, "TX BW:", Theme::getInstance()->fg_darkcyan->foreground},
|
||||
{{5 * CHARACTER_WIDTH, 15 * LINE_HEIGHT}, "TX SampR:", Theme::getInstance()->fg_darkcyan->foreground},
|
||||
{{5 * CHARACTER_WIDTH, 16 * LINE_HEIGHT}, "IQ Inv:", Theme::getInstance()->fg_darkcyan->foreground},
|
||||
};
|
||||
|
||||
Text text_info_line_1{{14 * CHARACTER_WIDTH, 5 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
@@ -109,6 +110,7 @@ class DfuMenu2 : public View {
|
||||
Text text_info_line_9{{14 * CHARACTER_WIDTH, 13 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_10{{14 * CHARACTER_WIDTH, 14 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_11{{14 * CHARACTER_WIDTH, 15 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_12{{(14 + 1) * CHARACTER_WIDTH, 16 * LINE_HEIGHT, (10 - 1) * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
#include "ui_transmitter.hpp"
|
||||
#include "transmitter_model.hpp"
|
||||
#include "encoders.hpp"
|
||||
#include "de_bruijn.hpp"
|
||||
#include "app_settings.hpp"
|
||||
#include "radio_state.hpp"
|
||||
|
||||
@@ -125,7 +124,7 @@ class EncodersConfigView : public View {
|
||||
""};
|
||||
|
||||
Waveform waveform{
|
||||
{0, 17 * 8, 240, 32},
|
||||
{0, 17 * 8, screen_width, 32},
|
||||
waveform_buffer,
|
||||
0,
|
||||
0,
|
||||
@@ -202,7 +201,7 @@ class EncodersView : public View {
|
||||
void start_tx(const bool scan);
|
||||
void on_tx_progress(const uint32_t progress, const bool done);
|
||||
|
||||
Rect view_rect = {0, 4 * 8, 240, 168};
|
||||
Rect view_rect = {0, 4 * 8, screen_width, 168};
|
||||
|
||||
EncodersConfigView view_config{nav_, view_rect};
|
||||
EncodersScanView view_scan{nav_, view_rect};
|
||||
@@ -217,10 +216,10 @@ class EncodersView : public View {
|
||||
"Ready"};
|
||||
|
||||
ProgressBar progressbar{
|
||||
{2 * 8, 13 * 16 + 20, 208, 16}};
|
||||
{2 * 8, 13 * 16 + 20, UI_POS_WIDTH_REMAINING(4), 16}};
|
||||
|
||||
TransmitterView tx_view{
|
||||
16 * 16,
|
||||
(int16_t)UI_POS_Y_BOTTOM(4),
|
||||
50000,
|
||||
9};
|
||||
|
||||
|
||||
@@ -92,12 +92,21 @@ void ExternalModuleView::on_tick_second() {
|
||||
case app_location_t::TX:
|
||||
btnText += " (TX)";
|
||||
break;
|
||||
case app_location_t::TRX:
|
||||
btnText += " (TRX)";
|
||||
break;
|
||||
case app_location_t::SETTINGS:
|
||||
btnText += " (Settings)";
|
||||
break;
|
||||
case app_location_t::DEBUG:
|
||||
btnText += " (Debug)";
|
||||
break;
|
||||
case app_location_t::HOME:
|
||||
btnText += " (Home)";
|
||||
break;
|
||||
case app_location_t::GAMES:
|
||||
btnText += " (Games)";
|
||||
break;
|
||||
}
|
||||
|
||||
switch (i) {
|
||||
@@ -119,4 +128,5 @@ void ExternalModuleView::on_tick_second() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
|
||||
@@ -84,7 +84,7 @@ class ExternalModuleView : public View {
|
||||
Text text_app5_name{{24, 160, 200, 16}};
|
||||
|
||||
Button dummy{
|
||||
{240, 0, 0, 0},
|
||||
{screen_width, 0, 0, 0},
|
||||
""};
|
||||
|
||||
SignalToken signal_token_tick_second{};
|
||||
|
||||
@@ -43,6 +43,7 @@ namespace ui {
|
||||
static const fs::path txt_ext{u".TXT"};
|
||||
static const fs::path ppl_ext{u".PPL"};
|
||||
static const fs::path c8_ext{u".C8"};
|
||||
static const fs::path cu8_ext{u".CU8"};
|
||||
static const fs::path c16_ext{u".C16"};
|
||||
static const fs::path cxx_ext{u".C*"};
|
||||
static const fs::path png_ext{u".PNG"};
|
||||
@@ -88,8 +89,11 @@ fs::path get_partner_file(fs::path path) {
|
||||
path.replace_extension(txt_ext);
|
||||
else if (path_iequal(ext, txt_ext)) {
|
||||
path.replace_extension(c8_ext);
|
||||
if (!fs::file_exists(path))
|
||||
path.replace_extension(cu8_ext);
|
||||
if (!fs::file_exists(path))
|
||||
path.replace_extension(c16_ext);
|
||||
|
||||
} else
|
||||
return {};
|
||||
|
||||
@@ -228,6 +232,8 @@ void FileManBaseView::load_directory_contents(const fs::path& dir_path) {
|
||||
|
||||
text_current.set(dir_path.empty() ? "(sd root)" : truncate(dir_path, 24));
|
||||
|
||||
// Collect all entries first
|
||||
std::list<fileman_entry> all_entries;
|
||||
for (const auto& entry : fs::directory_iterator(dir_path, u"*")) {
|
||||
// Hide files starting with '.' (hidden / tmp).
|
||||
if (!show_hidden_files && is_hidden_file(entry.path()))
|
||||
@@ -235,36 +241,40 @@ void FileManBaseView::load_directory_contents(const fs::path& dir_path) {
|
||||
|
||||
if (fs::is_regular_file(entry.status())) {
|
||||
if (!filtering || path_iequal(entry.path().extension(), extension_filter) || (cxx_file && is_cxx_capture_file(entry.path())))
|
||||
insert_sorted(entry_list, {entry.path().string(), (uint32_t)entry.size(), false});
|
||||
insert_sorted(all_entries, {entry.path().string(), (uint32_t)entry.size(), false});
|
||||
} else if (fs::is_directory(entry.status())) {
|
||||
insert_sorted(entry_list, {entry.path().string(), 0, true});
|
||||
insert_sorted(all_entries, {entry.path().string(), 0, true});
|
||||
}
|
||||
}
|
||||
|
||||
// paginating
|
||||
auto list_size = entry_list.size();
|
||||
nb_pages = 1 + (list_size / items_per_page);
|
||||
size_t start = pagination * items_per_page;
|
||||
size_t stop = start + items_per_page;
|
||||
if (list_size > start) {
|
||||
if (list_size < stop)
|
||||
stop = list_size;
|
||||
entry_list.erase(std::next(entry_list.begin(), stop), entry_list.end());
|
||||
entry_list.erase(entry_list.begin(), std::next(entry_list.begin(), start));
|
||||
// Calculate pagination
|
||||
nb_pages = (all_entries.size() + items_per_page - 1) / items_per_page;
|
||||
if (nb_pages == 0) nb_pages = 1;
|
||||
|
||||
size_t start_idx = pagination * items_per_page;
|
||||
size_t end_idx = std::min(start_idx + items_per_page, all_entries.size());
|
||||
|
||||
// Add "parent" directory if not at the root and on first page
|
||||
if (!dir_path.empty() && pagination == 0) {
|
||||
entry_list.push_back({parent_dir_path.string(), 0, true});
|
||||
}
|
||||
|
||||
// Add "parent" directory if not at the root.
|
||||
if (!dir_path.empty() && pagination == 0)
|
||||
entry_list.insert(entry_list.begin(), {parent_dir_path.string(), 0, true});
|
||||
|
||||
// add next page
|
||||
if (list_size > start + items_per_page) {
|
||||
entry_list.push_back({str_next, (uint32_t)pagination + 1, true});
|
||||
}
|
||||
|
||||
// add prev page
|
||||
// Add prev page navigation if not on first page
|
||||
if (pagination > 0) {
|
||||
entry_list.insert(entry_list.begin(), {str_back, (uint32_t)pagination - 1, true});
|
||||
entry_list.push_back({str_back, (uint32_t)pagination - 1, true});
|
||||
}
|
||||
|
||||
// Add entries for current page
|
||||
auto it = all_entries.begin();
|
||||
std::advance(it, start_idx);
|
||||
|
||||
for (size_t i = start_idx; i < end_idx && it != all_entries.end(); i++, ++it) {
|
||||
entry_list.push_back(*it);
|
||||
}
|
||||
|
||||
// Add next page navigation if not on last page
|
||||
if (end_idx < all_entries.size()) {
|
||||
entry_list.push_back({str_next, (uint32_t)pagination + 1, true});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,6 +298,7 @@ FileManBaseView::FileManBaseView(
|
||||
std::string filter)
|
||||
: nav_{nav},
|
||||
extension_filter{filter} {
|
||||
max_filename_length = screen_width / 8 - 10;
|
||||
add_children({&labels,
|
||||
&text_current,
|
||||
&button_exit});
|
||||
@@ -320,30 +331,49 @@ void FileManBaseView::focus() {
|
||||
} else {
|
||||
menu_view.focus();
|
||||
}
|
||||
|
||||
// Set menu to the correct page and select the correct item
|
||||
menu_view.set_highlighted(prev_highlight % items_per_page);
|
||||
}
|
||||
|
||||
// Push directory - store the global index (page * items_per_page + local_index)
|
||||
void FileManBaseView::push_dir(const fs::path& path) {
|
||||
if (path == parent_dir_path) {
|
||||
pop_dir();
|
||||
} else {
|
||||
// Save global index (combines page number and item position)
|
||||
saved_index_stack.push_back(menu_view.highlighted_index() + (pagination * items_per_page));
|
||||
|
||||
current_path /= path;
|
||||
saved_index_stack.push_back(menu_view.highlighted_index());
|
||||
menu_view.set_highlighted(0);
|
||||
reload_current(true);
|
||||
reload_current(true); // Reset pagination when entering new directory
|
||||
}
|
||||
}
|
||||
|
||||
void FileManBaseView::pop_dir() {
|
||||
if (saved_index_stack.empty())
|
||||
if (current_path.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Move to parent directory
|
||||
current_path = current_path.parent_path();
|
||||
reload_current(true);
|
||||
menu_view.set_highlighted(saved_index_stack.back());
|
||||
saved_index_stack.pop_back();
|
||||
|
||||
// Restore the previous global index if available
|
||||
if (!saved_index_stack.empty()) {
|
||||
uint32_t global_index = saved_index_stack.back();
|
||||
saved_index_stack.pop_back();
|
||||
|
||||
// Calculate pagination from global index
|
||||
pagination = global_index / items_per_page;
|
||||
|
||||
// Calculate local index within the page
|
||||
prev_highlight = global_index % items_per_page;
|
||||
restoring_navigation = true;
|
||||
}
|
||||
|
||||
reload_current(false); // Important: don't reset pagination
|
||||
}
|
||||
|
||||
std::string get_extension(std::string t) {
|
||||
std::string FileManBaseView::get_extension(const std::string& t) const {
|
||||
const auto index = t.find_last_of(u'.');
|
||||
if (index == t.npos) {
|
||||
return {};
|
||||
@@ -360,6 +390,7 @@ std::string get_stem(std::string t) {
|
||||
return t.substr(0, index);
|
||||
}
|
||||
}
|
||||
|
||||
std::string get_filename(std::string _s) {
|
||||
const auto index = _s.find_last_of("/");
|
||||
if (index == _s.npos) {
|
||||
@@ -368,16 +399,17 @@ std::string get_filename(std::string _s) {
|
||||
return _s.substr(index + 1);
|
||||
}
|
||||
}
|
||||
|
||||
void FileManBaseView::refresh_list() {
|
||||
if (on_refresh_widgets)
|
||||
on_refresh_widgets(false);
|
||||
|
||||
prev_highlight = menu_view.highlighted_index();
|
||||
if (!restoring_navigation) {
|
||||
prev_highlight = menu_view.highlighted_index();
|
||||
}
|
||||
menu_view.clear();
|
||||
|
||||
for (const auto& entry : entry_list) {
|
||||
auto entry_name = std::string{entry.path.length() <= 20 ? entry.path : entry.path.substr(0, 20)};
|
||||
|
||||
if (entry.is_directory) {
|
||||
std::string size_str{};
|
||||
if (entry.path == str_next || entry.path == str_back) {
|
||||
@@ -386,8 +418,10 @@ void FileManBaseView::refresh_list() {
|
||||
size_str = (entry.path == parent_dir_path.string()) ? "" : to_string_dec_uint(file_count(current_path / entry.path));
|
||||
}
|
||||
|
||||
// CRITICAL: Use "\t" (Tab) to separate the path and the size!
|
||||
// Do not use spaces or padding here. The menu will handle the alignment.
|
||||
menu_view.add_item(
|
||||
{entry_name.substr(0, max_filename_length) + std::string(21 - entry_name.length(), ' ') + size_str,
|
||||
{entry.path + "\t" + size_str,
|
||||
Theme::getInstance()->fg_yellow->foreground,
|
||||
&bitmap_icon_dir,
|
||||
[this](KeyEvent key) {
|
||||
@@ -399,8 +433,9 @@ void FileManBaseView::refresh_list() {
|
||||
const auto& assoc = get_assoc(get_extension(entry.path));
|
||||
auto size_str = to_string_file_size(entry.size);
|
||||
|
||||
// CRITICAL: Use "\t" (Tab) to separate the path and the size!
|
||||
menu_view.add_item(
|
||||
{entry_name.substr(0, max_filename_length) + std::string(21 - entry_name.length(), ' ') + size_str,
|
||||
{entry.path + "\t" + size_str,
|
||||
assoc.color,
|
||||
assoc.icon,
|
||||
[this](KeyEvent key) {
|
||||
@@ -411,14 +446,29 @@ void FileManBaseView::refresh_list() {
|
||||
}
|
||||
|
||||
menu_view.set_highlighted(prev_highlight);
|
||||
restoring_navigation = false;
|
||||
}
|
||||
|
||||
void FileManBaseView::reload_current(bool reset_pagination) {
|
||||
if (reset_pagination) pagination = 0;
|
||||
// Only reset pagination if explicitly requested
|
||||
if (reset_pagination) {
|
||||
pagination = 0;
|
||||
}
|
||||
load_directory_contents(current_path);
|
||||
refresh_list();
|
||||
}
|
||||
|
||||
void FileManBaseView::copy_waterfall(std::filesystem::path path) {
|
||||
nav_.push<ModalMessageView>(
|
||||
"Install", " Use this gradient file\n for all waterfalls?", YESNO,
|
||||
[this, path](bool choice) {
|
||||
if (choice) {
|
||||
delete_file(default_gradient_file);
|
||||
copy_file(path, default_gradient_file);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const FileManBaseView::file_assoc_t& FileManBaseView::get_assoc(
|
||||
const fs::path& ext) const {
|
||||
size_t index = 0;
|
||||
@@ -444,7 +494,7 @@ FileLoadView::FileLoadView(
|
||||
add_children({&menu_view});
|
||||
|
||||
// Resize menu view to fill screen
|
||||
menu_view.set_parent_rect({0, 3 * 8, 240, 29 * 8});
|
||||
menu_view.set_parent_rect({0, 3 * 8, screen_width, UI_POS_HEIGHT_REMAINING(4.5)});
|
||||
|
||||
refresh_list();
|
||||
|
||||
@@ -478,63 +528,6 @@ void FileLoadView::refresh_widgets(const bool) {
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
/* FileSaveView **************************************************************/
|
||||
/*
|
||||
FileSaveView::FileSaveView(
|
||||
NavigationView& nav,
|
||||
const fs::path& path,
|
||||
const fs::path& file
|
||||
) : nav_{ nav },
|
||||
path_{ path },
|
||||
file_{ file }
|
||||
{
|
||||
add_children({
|
||||
&text_path,
|
||||
&button_edit_path,
|
||||
&text_name,
|
||||
&button_edit_name,
|
||||
&button_save,
|
||||
&button_cancel,
|
||||
});
|
||||
|
||||
button_edit_path.on_select = [this](Button&) {
|
||||
buffer_ = path_.string();
|
||||
text_prompt(nav_, buffer_, max_filename_length,
|
||||
[this](std::string&) {
|
||||
path_ = buffer_;
|
||||
refresh_widgets();
|
||||
});
|
||||
};
|
||||
|
||||
button_edit_name.on_select = [this](Button&) {
|
||||
buffer_ = file_.string();
|
||||
text_prompt(nav_, buffer_, max_filename_length,
|
||||
[this](std::string&) {
|
||||
file_ = buffer_;
|
||||
refresh_widgets();
|
||||
});
|
||||
};
|
||||
|
||||
button_save.on_select = [this](Button&) {
|
||||
if (on_save)
|
||||
on_save(path_ / file_);
|
||||
else
|
||||
nav_.pop();
|
||||
};
|
||||
|
||||
button_cancel.on_select = [this](Button&) {
|
||||
nav_.pop();
|
||||
};
|
||||
|
||||
refresh_widgets();
|
||||
}
|
||||
|
||||
void FileSaveView::refresh_widgets() {
|
||||
text_path.set(truncate(path_, 30));
|
||||
text_name.set(truncate(file_, 30));
|
||||
set_dirty();
|
||||
}
|
||||
*/
|
||||
/* FileManagerView ***********************************************************/
|
||||
|
||||
void FileManagerView::refresh_widgets(const bool v) {
|
||||
@@ -566,7 +559,7 @@ void FileManagerView::on_rename(std::string_view hint) {
|
||||
cursor_pos = pos;
|
||||
|
||||
text_prompt(
|
||||
nav_, name_buffer, cursor_pos, max_filename_length,
|
||||
nav_, name_buffer, cursor_pos, max_filename_length, ENTER_KEYBOARD_MODE_ALPHA,
|
||||
[this](std::string& renamed) {
|
||||
auto renamed_path = fs::path{renamed};
|
||||
rename_file(get_selected_full_path(), current_path / renamed_path);
|
||||
@@ -640,7 +633,7 @@ void FileManagerView::on_clean() {
|
||||
|
||||
void FileManagerView::on_new_dir() {
|
||||
name_buffer = "";
|
||||
text_prompt(nav_, name_buffer, max_filename_length, [this](std::string& dir_name) {
|
||||
text_prompt(nav_, name_buffer, max_filename_length, ENTER_KEYBOARD_MODE_ALPHA, [this](std::string& dir_name) {
|
||||
make_new_directory(current_path / dir_name);
|
||||
reload_current(true);
|
||||
});
|
||||
@@ -671,7 +664,7 @@ void FileManagerView::on_paste() {
|
||||
|
||||
void FileManagerView::on_new_file() {
|
||||
name_buffer = "";
|
||||
text_prompt(nav_, name_buffer, max_filename_length, [this](std::string& file_name) {
|
||||
text_prompt(nav_, name_buffer, max_filename_length, ENTER_KEYBOARD_MODE_ALPHA, [this](std::string& file_name) {
|
||||
make_new_file(current_path / file_name);
|
||||
reload_current(true);
|
||||
});
|
||||
@@ -685,7 +678,11 @@ bool FileManagerView::handle_file_open() {
|
||||
auto ext = path.extension();
|
||||
|
||||
if (path_iequal(txt_ext, ext)) {
|
||||
nav_.push<TextEditorView>(path);
|
||||
if (path_iequal(current_path, u"/" + waterfalls_dir)) {
|
||||
copy_waterfall(path);
|
||||
} else {
|
||||
nav_.push<TextEditorView>(path);
|
||||
}
|
||||
return true;
|
||||
} else if (is_cxx_capture_file(path) || path_iequal(ppl_ext, ext)) {
|
||||
// TODO: Enough memory to push?
|
||||
|
||||
@@ -64,7 +64,8 @@ class FileManBaseView : public View {
|
||||
uint32_t prev_highlight = 0;
|
||||
uint8_t pagination = 0;
|
||||
uint8_t nb_pages = 1;
|
||||
static constexpr size_t max_filename_length = 20;
|
||||
bool restoring_navigation = false;
|
||||
size_t max_filename_length = 20;
|
||||
static constexpr size_t max_items_loaded = 75; // too memory hungry, so won't sort it
|
||||
static constexpr size_t items_per_page = 20;
|
||||
|
||||
@@ -79,6 +80,7 @@ class FileManBaseView : public View {
|
||||
{u".PNG", &bitmap_icon_file_image, ui::Color::green()},
|
||||
{u".BMP", &bitmap_icon_file_image, ui::Color::green()},
|
||||
{u".C8", &bitmap_icon_file_iq, ui::Color::dark_cyan()},
|
||||
{u".CU8", &bitmap_icon_file_iq, ui::Color::dark_cyan()},
|
||||
{u".C16", &bitmap_icon_file_iq, ui::Color::dark_cyan()},
|
||||
{u".WAV", &bitmap_icon_file_wav, ui::Color::dark_magenta()},
|
||||
{u".PPL", &bitmap_icon_file_iq, ui::Color::white()}, // Playlist/Replay
|
||||
@@ -96,6 +98,8 @@ class FileManBaseView : public View {
|
||||
void load_directory_contents(const std::filesystem::path& dir_path);
|
||||
void load_directory_contents_unordered(const std::filesystem::path& dir_path, size_t file_cnt);
|
||||
const file_assoc_t& get_assoc(const std::filesystem::path& ext) const;
|
||||
std::string get_extension(const std::string& t) const;
|
||||
void copy_waterfall(std::filesystem::path path);
|
||||
|
||||
NavigationView& nav_;
|
||||
|
||||
@@ -119,16 +123,16 @@ class FileManBaseView : public View {
|
||||
{{0, 0}, "Path:", Theme::getInstance()->fg_light->foreground}};
|
||||
|
||||
Text text_current{
|
||||
{6 * 8, 0 * 8, 24 * 8, 16},
|
||||
{UI_POS_X(6), UI_POS_Y(0), UI_POS_WIDTH_REMAINING(6), UI_POS_HEIGHT(1)},
|
||||
"",
|
||||
};
|
||||
|
||||
MenuView menu_view{
|
||||
{0, 2 * 8, 240, 26 * 8},
|
||||
{0, UI_POS_Y(1), UI_POS_MAXWIDTH, UI_POS_HEIGHT_REMAINING(7)},
|
||||
true};
|
||||
|
||||
Button button_exit{
|
||||
{22 * 8, 34 * 8, 8 * 8, 32},
|
||||
{UI_POS_X_RIGHT(8), UI_POS_Y_BOTTOM(3), 8 * 8, UI_POS_HEIGHT(2)},
|
||||
"Exit"};
|
||||
};
|
||||
|
||||
@@ -171,7 +175,7 @@ private:
|
||||
};
|
||||
|
||||
Text text_path {
|
||||
{ 0 * 8, 2 * 16, 30 * 8, 16 },
|
||||
{ 0 * 8, 2 * 16, screen_width, 16 },
|
||||
"",
|
||||
};
|
||||
|
||||
@@ -181,7 +185,7 @@ private:
|
||||
};
|
||||
|
||||
Text text_name {
|
||||
{ 0 * 8, 7 * 16, 30 * 8, 16 },
|
||||
{ 0 * 8, 7 * 16, screen_width, 16 },
|
||||
"",
|
||||
};
|
||||
|
||||
@@ -227,66 +231,66 @@ class FileManagerView : public FileManBaseView {
|
||||
bool selected_is_valid() const;
|
||||
|
||||
Text text_date{
|
||||
{0 * 8, 26 * 8, 28 * 8, 16},
|
||||
{UI_POS_X(0), UI_POS_Y_BOTTOM(6), 28 * 8, UI_POS_HEIGHT(1)},
|
||||
""};
|
||||
|
||||
NewButton button_rename{
|
||||
{0 * 8, 29 * 8, 4 * 8, 32},
|
||||
{UI_POS_X(0), UI_POS_Y_BOTTOM(5), UI_POS_WIDTH(4), UI_POS_HEIGHT(2)},
|
||||
{},
|
||||
&bitmap_icon_rename,
|
||||
Theme::getInstance()->fg_blue->foreground};
|
||||
|
||||
NewButton button_delete{
|
||||
{9 * 8, 34 * 8, 4 * 8, 32},
|
||||
{UI_POS_X(9), UI_POS_Y_BOTTOM(3), UI_POS_WIDTH(4), UI_POS_HEIGHT(2)},
|
||||
{},
|
||||
&bitmap_icon_trash,
|
||||
Theme::getInstance()->fg_red->foreground};
|
||||
|
||||
NewButton button_clean{
|
||||
{13 * 8, 34 * 8, 4 * 8, 32},
|
||||
{UI_POS_X(13), UI_POS_Y_BOTTOM(3), UI_POS_WIDTH(4), UI_POS_HEIGHT(2)},
|
||||
{},
|
||||
&bitmap_icon_clean,
|
||||
Theme::getInstance()->fg_red->foreground};
|
||||
|
||||
NewButton button_cut{
|
||||
{9 * 8, 29 * 8, 4 * 8, 32},
|
||||
{UI_POS_X(9), UI_POS_Y_BOTTOM(5), UI_POS_WIDTH(4), UI_POS_HEIGHT(2)},
|
||||
{},
|
||||
&bitmap_icon_cut,
|
||||
Theme::getInstance()->fg_dark->foreground};
|
||||
|
||||
NewButton button_copy{
|
||||
{13 * 8, 29 * 8, 4 * 8, 32},
|
||||
{UI_POS_X(13), UI_POS_Y_BOTTOM(5), UI_POS_WIDTH(4), UI_POS_HEIGHT(2)},
|
||||
{},
|
||||
&bitmap_icon_copy,
|
||||
Theme::getInstance()->fg_dark->foreground};
|
||||
|
||||
NewButton button_paste{
|
||||
{17 * 8, 29 * 8, 4 * 8, 32},
|
||||
{UI_POS_X(17), UI_POS_Y_BOTTOM(5), UI_POS_WIDTH(4), UI_POS_HEIGHT(2)},
|
||||
{},
|
||||
&bitmap_icon_paste,
|
||||
Theme::getInstance()->fg_dark->foreground};
|
||||
|
||||
NewButton button_new_dir{
|
||||
{22 * 8, 29 * 8, 4 * 8, 32},
|
||||
{UI_POS_X(22), UI_POS_Y_BOTTOM(5), UI_POS_WIDTH(4), UI_POS_HEIGHT(2)},
|
||||
{},
|
||||
&bitmap_icon_new_dir,
|
||||
Theme::getInstance()->fg_green->foreground};
|
||||
|
||||
NewButton button_new_file{
|
||||
{26 * 8, 29 * 8, 4 * 8, 32},
|
||||
{UI_POS_X(26), UI_POS_Y_BOTTOM(5), UI_POS_WIDTH(4), UI_POS_HEIGHT(2)},
|
||||
{},
|
||||
&bitmap_icon_new_file,
|
||||
Theme::getInstance()->fg_green->foreground};
|
||||
|
||||
NewButton button_open_notepad{
|
||||
{0 * 8, 34 * 8, 4 * 8, 32},
|
||||
{UI_POS_X(0), UI_POS_Y_BOTTOM(3), UI_POS_WIDTH(4), UI_POS_HEIGHT(2)},
|
||||
{},
|
||||
&bitmap_icon_notepad,
|
||||
Theme::getInstance()->fg_orange->foreground};
|
||||
|
||||
NewButton button_rename_timestamp{
|
||||
|
||||
{4 * 8, 29 * 8, 4 * 8, 32},
|
||||
{UI_POS_X(4), UI_POS_Y_BOTTOM(5), UI_POS_WIDTH(4), UI_POS_HEIGHT(2)},
|
||||
{},
|
||||
&bitmap_icon_options_datetime,
|
||||
Theme::getInstance()->fg_blue->foreground,
|
||||
@@ -294,13 +298,13 @@ class FileManagerView : public FileManBaseView {
|
||||
|
||||
NewButton button_open_iq_trim{
|
||||
|
||||
{4 * 8, 34 * 8, 4 * 8, 32},
|
||||
{UI_POS_X(4), UI_POS_Y_BOTTOM(3), UI_POS_WIDTH(4), UI_POS_HEIGHT(2)},
|
||||
{},
|
||||
&bitmap_icon_trim,
|
||||
Theme::getInstance()->fg_orange->foreground};
|
||||
|
||||
NewButton button_show_hidden_files{
|
||||
{17 * 8, 34 * 8, 4 * 8, 32},
|
||||
{UI_POS_X(17), UI_POS_Y_BOTTOM(3), UI_POS_WIDTH(4), UI_POS_HEIGHT(2)},
|
||||
{},
|
||||
&bitmap_icon_hide,
|
||||
Theme::getInstance()->fg_dark->foreground};
|
||||
|
||||
@@ -32,7 +32,7 @@ static const char* hackrf_magic = "HACKRFFW";
|
||||
#define FIRST_CHECKSUM_NIGHTLY 240125
|
||||
|
||||
Thread* FlashUtilityView::thread{nullptr};
|
||||
static constexpr size_t max_filename_length = 26;
|
||||
static constexpr size_t max_filename_length = 60; // max length of filename
|
||||
|
||||
bool valid_firmware_file(std::filesystem::path::string_type path) {
|
||||
File firmware_file;
|
||||
@@ -45,10 +45,14 @@ bool valid_firmware_file(std::filesystem::path::string_type path) {
|
||||
// test read of the whole file just to validate checksum (baseband flash code will re-read when flashing)
|
||||
auto result = firmware_file.open(path.c_str());
|
||||
if (!result.is_valid()) {
|
||||
uint64_t file_size = firmware_file.size();
|
||||
if (file_size > FLASH_ROM_SIZE) {
|
||||
// Firmware file is larger than the flash size for this device
|
||||
return false;
|
||||
}
|
||||
checksum = 0;
|
||||
for (uint32_t offset = 0; offset < FLASH_ROM_SIZE; offset += sizeof(read_buffer)) {
|
||||
for (uint64_t offset = 0; offset < FLASH_ROM_SIZE && offset < file_size; offset += sizeof(read_buffer)) {
|
||||
auto readResult = firmware_file.read(&read_buffer, sizeof(read_buffer));
|
||||
|
||||
if ((!readResult) || (readResult.value() != sizeof(read_buffer))) {
|
||||
// File was smaller than 1MB:
|
||||
// If version is such that the file SHOULD have been 1MB, call it a checksum error (otherwise say it's OK).
|
||||
@@ -80,7 +84,7 @@ FlashUtilityView::FlashUtilityView(NavigationView& nav)
|
||||
add_children({&labels,
|
||||
&menu_view});
|
||||
|
||||
menu_view.set_parent_rect({0, 3 * 8, 240, 33 * 8});
|
||||
menu_view.set_parent_rect({0, 3 * 8, screen_width, 33 * 8});
|
||||
|
||||
ensure_directory(apps_dir);
|
||||
ensure_directory(firmware_dir);
|
||||
@@ -147,6 +151,7 @@ std::filesystem::path FlashUtilityView::extract_tar(std::filesystem::path::strin
|
||||
|
||||
bool FlashUtilityView::flash_firmware(std::filesystem::path::string_type path) {
|
||||
ui::Painter painter;
|
||||
menu_view.hidden(true);
|
||||
if (endsWith(path, u".tar")) {
|
||||
// extract, then update
|
||||
path = extract_tar(u'/' + path, painter).native();
|
||||
@@ -156,13 +161,14 @@ bool FlashUtilityView::flash_firmware(std::filesystem::path::string_type path) {
|
||||
painter.fill_rectangle({0, 50, portapack::display.width(), 90}, Theme::getInstance()->bg_darkest->background);
|
||||
painter.draw_string({0, 60}, *Theme::getInstance()->fg_red, "BAD FIRMWARE FILE OR W/R ERR");
|
||||
chThdSleepMilliseconds(5000);
|
||||
menu_view.hidden(false);
|
||||
return false;
|
||||
}
|
||||
painter.fill_rectangle(
|
||||
{0, 0, portapack::display.width(), portapack::display.height()},
|
||||
Theme::getInstance()->bg_darkest->background);
|
||||
|
||||
painter.draw_string({12, 24}, this->nav_.style(), "This will take 15 seconds.");
|
||||
painter.draw_string({12, 24}, this->nav_.style(), "This will take 15-45 sec.");
|
||||
painter.draw_string({12, 64}, this->nav_.style(), "Please wait while LED RX");
|
||||
painter.draw_string({12, 84}, this->nav_.style(), "is on and TX is flashing.");
|
||||
painter.draw_string({12, 124}, this->nav_.style(), "Device will then restart.");
|
||||
@@ -177,4 +183,4 @@ void FlashUtilityView::focus() {
|
||||
menu_view.focus();
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
#include "untar.hpp"
|
||||
#include <cstdint>
|
||||
|
||||
#define FLASH_ROM_SIZE 1048576
|
||||
#define FLASH_ROM_SIZE (FLASH_SIZE_MB * 1024 * 1024)
|
||||
#define FLASH_STARTING_ADDRESS 0x00000000
|
||||
#define FLASH_EXPECTED_CHECKSUM 0x00000000
|
||||
#define FLASH_CHECKSUM_ERROR 0xFFFFFFFF
|
||||
@@ -60,7 +60,7 @@ class FlashUtilityView : public View {
|
||||
{{4, 4}, "Select firmware to flash:", Theme::getInstance()->bg_darkest->foreground}};
|
||||
|
||||
MenuView menu_view{
|
||||
{0, 2 * 8, 240, 26 * 8},
|
||||
{0, UI_POS_Y(1), screen_width, UI_POS_HEIGHT_REMAINING(2)},
|
||||
true};
|
||||
|
||||
std::filesystem::path extract_tar(std::filesystem::path::string_type path, ui::Painter& painter); // extracts the tar file, and returns the firmware.bin path from it. empty string if no fw
|
||||
|
||||
@@ -45,8 +45,8 @@ using option_db_t = std::pair<std::string_view, int32_t>;
|
||||
using options_db_t = std::vector<option_db_t>;
|
||||
|
||||
extern options_db_t freqman_modulations;
|
||||
extern options_db_t freqman_bandwidths[4];
|
||||
extern options_db_t freqman_steps;
|
||||
extern options_db_t freqman_bandwidths[6];
|
||||
// extern options_db_t freqman_steps; // now included via ui_receiver.hpp
|
||||
extern options_db_t freqman_steps_short;
|
||||
|
||||
options_t dboptions_to_options(const options_db_t& dboptions) {
|
||||
@@ -240,7 +240,7 @@ void FrequencyManagerView::on_edit_freq() {
|
||||
|
||||
void FrequencyManagerView::on_edit_desc() {
|
||||
temp_buffer_ = current_entry().description;
|
||||
text_prompt(nav_, temp_buffer_, freqman_max_desc_size, [this](std::string& new_desc) {
|
||||
text_prompt(nav_, temp_buffer_, freqman_max_desc_size, ENTER_KEYBOARD_MODE_ALPHA, [this](std::string& new_desc) {
|
||||
auto entry = current_entry();
|
||||
entry.description = std::move(new_desc);
|
||||
db_.replace_entry(current_index(), entry);
|
||||
@@ -250,7 +250,7 @@ void FrequencyManagerView::on_edit_desc() {
|
||||
|
||||
void FrequencyManagerView::on_add_category() {
|
||||
temp_buffer_.clear();
|
||||
text_prompt(nav_, temp_buffer_, 20, [this](std::string& new_name) {
|
||||
text_prompt(nav_, temp_buffer_, 20, ENTER_KEYBOARD_MODE_ALPHA, [this](std::string& new_name) {
|
||||
if (!new_name.empty()) {
|
||||
create_freqman_file(new_name);
|
||||
refresh_categories();
|
||||
|
||||
@@ -65,14 +65,14 @@ class FreqManBaseView : public View {
|
||||
|
||||
OptionsField options_category{
|
||||
{3 * 8, 2},
|
||||
20 /* length */,
|
||||
19 /* length */,
|
||||
{}};
|
||||
|
||||
FreqManUIList freqlist_view{
|
||||
{0, 3 * 8, screen_width, 12 * 16 + 2 /* 2 Keeps text out of border. */}};
|
||||
|
||||
Button button_exit{
|
||||
{15 * 8, 17 * 16, 15 * 8, 2 * 16},
|
||||
{UI_POS_X_RIGHT(14), UI_POS_Y_BOTTOM(3), UI_POS_WIDTH(14), UI_POS_HEIGHT(2)},
|
||||
"Exit"};
|
||||
|
||||
protected:
|
||||
@@ -97,14 +97,14 @@ class FrequencySaveView : public FreqManBaseView {
|
||||
0};
|
||||
|
||||
Labels labels{
|
||||
{{0 * 8, 6 * 16}, "Description:", Theme::getInstance()->bg_darkest->foreground}};
|
||||
{{UI_POS_X(0), 6 * 16}, "Description:", Theme::getInstance()->bg_darkest->foreground}};
|
||||
|
||||
TextField field_description{
|
||||
{0 * 8, 7 * 16, 30 * 8, 1 * 16},
|
||||
{UI_POS_X(0), 7 * 16, screen_width, 1 * 16},
|
||||
""};
|
||||
|
||||
Button button_save{
|
||||
{0 * 8, 17 * 16, 15 * 8, 2 * 16},
|
||||
{UI_POS_X(0), UI_POS_Y_BOTTOM(3), UI_POS_WIDTH(14), UI_POS_HEIGHT(2)},
|
||||
"Save"};
|
||||
};
|
||||
|
||||
@@ -134,44 +134,44 @@ class FrequencyManagerView : public FreqManBaseView {
|
||||
void on_del_entry();
|
||||
|
||||
NewButton button_add_category{
|
||||
{23 * 8, 0 * 16, 7 * 4, 20},
|
||||
{UI_POS_X_RIGHT(8), UI_POS_Y(0), 7 * 4, 20},
|
||||
{},
|
||||
&bitmap_icon_new_file,
|
||||
Theme::getInstance()->bg_darkest->foreground,
|
||||
true};
|
||||
|
||||
NewButton button_del_category{
|
||||
{26 * 8 + 4, 0 * 16, 7 * 4, 20},
|
||||
{UI_POS_X_RIGHT(4), UI_POS_Y(0), 7 * 4, 20},
|
||||
{},
|
||||
&bitmap_icon_trash,
|
||||
Theme::getInstance()->fg_red->foreground,
|
||||
true};
|
||||
|
||||
Button button_edit_entry{
|
||||
{0 * 8, 14 * 16 - 4, 15 * 8, 1 * 16 + 4},
|
||||
{UI_POS_X(0), UI_POS_Y_BOTTOM(6.5) + 4, 15 * 8, 1 * 16 + 4},
|
||||
"Edit"};
|
||||
|
||||
Rectangle rect_padding{
|
||||
{15 * 8, 14 * 16 - 4, 15 * 8, 1 * 16 + 4},
|
||||
{15 * 8, UI_POS_Y_BOTTOM(6.5), 15 * 8, 1 * 16 + 4},
|
||||
Theme::getInstance()->fg_medium->background};
|
||||
|
||||
Button button_edit_freq{
|
||||
{0 * 8, 15 * 16, 15 * 8, 2 * 16},
|
||||
{UI_POS_X(0), UI_POS_Y_BOTTOM(5), 15 * 8, 2 * 16},
|
||||
"Frequency"};
|
||||
|
||||
Button button_edit_desc{
|
||||
{0 * 8, 17 * 16, 15 * 8, 2 * 16},
|
||||
{UI_POS_X(0), UI_POS_Y_BOTTOM(3), 15 * 8, 2 * 16},
|
||||
"Description"};
|
||||
|
||||
NewButton button_add_entry{
|
||||
{15 * 8, 15 * 16, 7 * 8 + 4, 2 * 16},
|
||||
{15 * 8, UI_POS_Y_BOTTOM(5), 7 * 8 + 4, 2 * 16},
|
||||
{},
|
||||
&bitmap_icon_add,
|
||||
Theme::getInstance()->bg_darkest->foreground,
|
||||
true};
|
||||
|
||||
NewButton button_del_entry{
|
||||
{22 * 8 + 4, 15 * 16, 7 * 8 + 4, 2 * 16},
|
||||
{22 * 8 + 4, UI_POS_Y_BOTTOM(5), 7 * 8 + 4, 2 * 16},
|
||||
{},
|
||||
&bitmap_icon_delete,
|
||||
Theme::getInstance()->fg_red->foreground,
|
||||
@@ -201,14 +201,14 @@ class FrequencyEditView : public View {
|
||||
|
||||
Labels labels{
|
||||
{{5 * 8, 1 * 16}, "Edit Frequency Entry", Theme::getInstance()->bg_darkest->foreground},
|
||||
{{0 * 8, 3 * 16}, "Entry Type :", Theme::getInstance()->fg_light->foreground},
|
||||
{{0 * 8, 4 * 16}, "Frequency A:", Theme::getInstance()->fg_light->foreground},
|
||||
{{0 * 8, 5 * 16}, "Frequency B:", Theme::getInstance()->fg_light->foreground},
|
||||
{{0 * 8, 6 * 16}, "Modulation :", Theme::getInstance()->fg_light->foreground},
|
||||
{{0 * 8, 7 * 16}, "Bandwidth :", Theme::getInstance()->fg_light->foreground},
|
||||
{{0 * 8, 8 * 16}, "Step :", Theme::getInstance()->fg_light->foreground},
|
||||
{{0 * 8, 9 * 16}, "Tone Freq :", Theme::getInstance()->fg_light->foreground},
|
||||
{{0 * 8, 10 * 16}, "Description:", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(0), 3 * 16}, "Entry Type :", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(0), 4 * 16}, "Frequency A:", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(0), 5 * 16}, "Frequency B:", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(0), 6 * 16}, "Modulation :", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(0), 7 * 16}, "Bandwidth :", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(0), 8 * 16}, "Step :", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(0), 9 * 16}, "Tone Freq :", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(0), 10 * 16}, "Description:", Theme::getInstance()->fg_light->foreground},
|
||||
};
|
||||
|
||||
OptionsField field_type{
|
||||
@@ -243,11 +243,11 @@ class FrequencyEditView : public View {
|
||||
{}};
|
||||
|
||||
Button button_save{
|
||||
{0 * 8, 17 * 16, 15 * 8, 2 * 16},
|
||||
{UI_POS_X(0), UI_POS_Y_BOTTOM(3), 15 * 8, 2 * 16},
|
||||
"Save"};
|
||||
|
||||
Button button_cancel{
|
||||
{15 * 8, 17 * 16, 15 * 8, 2 * 16},
|
||||
{UI_POS_X_RIGHT(15), UI_POS_Y_BOTTOM(3), 15 * 8, 2 * 16},
|
||||
"Cancel"};
|
||||
};
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ namespace ui {
|
||||
|
||||
IQTrimView::IQTrimView(NavigationView& nav)
|
||||
: nav_{nav} {
|
||||
power_buckets_.resize(screen_width);
|
||||
add_children({
|
||||
&labels,
|
||||
&field_path,
|
||||
@@ -170,7 +171,8 @@ void IQTrimView::update_range_controls(iq::TrimRange trim_range) {
|
||||
}
|
||||
|
||||
void IQTrimView::profile_capture() {
|
||||
power_buckets_ = {};
|
||||
power_buckets_.clear();
|
||||
power_buckets_.resize(screen_width);
|
||||
iq::PowerBuckets buckets{
|
||||
.p = power_buckets_.data(),
|
||||
.size = power_buckets_.size()};
|
||||
|
||||
@@ -54,7 +54,7 @@ class TrimProgressUI {
|
||||
}
|
||||
|
||||
void clear() {
|
||||
p.fill_rectangle({0 * 8, 4 * 16, screen_width, 3 * 16}, Theme::getInstance()->bg_darkest->background);
|
||||
p.fill_rectangle({UI_POS_X(0), 4 * 16, screen_width, 3 * 16}, Theme::getInstance()->bg_darkest->background);
|
||||
}
|
||||
|
||||
auto get_callback() {
|
||||
@@ -96,26 +96,26 @@ class IQTrimView : public View {
|
||||
|
||||
std::filesystem::path path_{};
|
||||
Optional<iq::CaptureInfo> info_{};
|
||||
std::array<iq::PowerBuckets::Bucket, screen_width> power_buckets_{};
|
||||
std::vector<iq::PowerBuckets::Bucket> power_buckets_{};
|
||||
TrimProgressUI progress_ui{};
|
||||
|
||||
Labels labels{
|
||||
{{0 * 8, 0 * 16}, "Capture File:", Theme::getInstance()->fg_light->foreground},
|
||||
{{0 * 8, 6 * 16}, "Start :", Theme::getInstance()->fg_light->foreground},
|
||||
{{0 * 8, 7 * 16}, "End :", Theme::getInstance()->fg_light->foreground},
|
||||
{{0 * 8, 8 * 16}, "Samples:", Theme::getInstance()->fg_light->foreground},
|
||||
{{0 * 8, 9 * 16}, "Max Pwr:", Theme::getInstance()->fg_light->foreground},
|
||||
{{0 * 8, 10 * 16}, "Cutoff :", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(0), UI_POS_Y(0)}, "Capture File:", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(0), 6 * 16}, "Start :", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(0), 7 * 16}, "End :", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(0), 8 * 16}, "Samples:", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(0), 9 * 16}, "Max Pwr:", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(0), 10 * 16}, "Cutoff :", Theme::getInstance()->fg_light->foreground},
|
||||
{{12 * 8, 10 * 16}, "%", Theme::getInstance()->fg_light->foreground},
|
||||
{{0 * 8, 12 * 16}, "Amplify:", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(0), 12 * 16}, "Amplify:", Theme::getInstance()->fg_light->foreground},
|
||||
{{10 * 8, 12 * 16}, "x", Theme::getInstance()->fg_light->foreground},
|
||||
};
|
||||
|
||||
TextField field_path{
|
||||
{0 * 8, 1 * 16, 30 * 8, 1 * 16},
|
||||
{UI_POS_X(0), 1 * 16, screen_width, 1 * 16},
|
||||
"Open File..."};
|
||||
|
||||
Point pos_lines{0 * 8, 4 * 16};
|
||||
Point pos_lines{UI_POS_X(0), 4 * 16};
|
||||
Dim height_lines{2 * 16};
|
||||
|
||||
NumberField field_start{
|
||||
@@ -155,7 +155,7 @@ class IQTrimView : public View {
|
||||
' '};
|
||||
|
||||
Button button_trim{
|
||||
{20 * 8, 16 * 16, 8 * 8, 2 * 16},
|
||||
{UI_POS_X_CENTER(8), UI_POS_Y_BOTTOM(3), 8 * 8, 2 * 16},
|
||||
"Trim"};
|
||||
};
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include "ui_looking_glass_app.hpp"
|
||||
#include "convert.hpp"
|
||||
#include "file_reader.hpp"
|
||||
#include "file.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "audio.hpp"
|
||||
#include "file_path.hpp"
|
||||
@@ -31,6 +32,7 @@
|
||||
using namespace portapack;
|
||||
|
||||
namespace ui {
|
||||
|
||||
void GlassView::focus() {
|
||||
range_presets.focus();
|
||||
}
|
||||
@@ -44,7 +46,7 @@ GlassView::~GlassView() {
|
||||
|
||||
// Function to map the value from one range to another
|
||||
int32_t GlassView::map(int32_t value, int32_t fromLow, int32_t fromHigh, int32_t toLow, int32_t toHigh) {
|
||||
return toLow + (value - fromLow) * (toHigh - toLow) / (fromHigh - fromLow);
|
||||
return toLow + ((value - fromLow) * (toHigh - toLow) + (fromHigh - fromLow) / 2) / (fromHigh - fromLow);
|
||||
}
|
||||
|
||||
void GlassView::update_display_beep() {
|
||||
@@ -69,7 +71,7 @@ void GlassView::manage_beep_audio() {
|
||||
}
|
||||
}
|
||||
|
||||
void GlassView::get_max_power(const ChannelSpectrum& spectrum, uint8_t bin, uint8_t& max_power) {
|
||||
void GlassView::get_max_power(const ChannelSpectrum& spectrum, uint16_t bin, uint8_t& max_power) {
|
||||
if (mode == LOOKING_GLASS_SINGLEPASS) {
|
||||
// <20MHz spectrum mode
|
||||
if (bin < 120) {
|
||||
@@ -91,17 +93,24 @@ void GlassView::get_max_power(const ChannelSpectrum& spectrum, uint8_t bin, uint
|
||||
}
|
||||
}
|
||||
|
||||
rf::Frequency GlassView::get_freq_from_bin_pos(uint8_t pos) {
|
||||
rf::Frequency GlassView::get_freq_from_bin_pos(uint16_t pos) {
|
||||
rf::Frequency freq_at_pos = 0;
|
||||
if (mode == LOOKING_GLASS_SINGLEPASS) {
|
||||
// starting from the middle, minus 8 ignored bin on each side. Since pos is [-120,120] after the (pos - 120), it's divided by SCREEN_W(240)/2 => 120
|
||||
freq_at_pos = f_center_ini + ((pos - 120) * ((looking_glass_range - ((16 * looking_glass_range) / SPEC_NB_BINS)) / 2)) / (SCREEN_W / 2);
|
||||
// starting from the middle, minus 8 ignored bin on each side. Since pos is [-120,120] after the (pos - 120), it's divided by screen_width(240)/2 => 120
|
||||
freq_at_pos = f_center_ini + ((pos - 120) * ((looking_glass_range - ((16 * looking_glass_range) / SPEC_NB_BINS)) / 2)) / (screen_width / 2);
|
||||
} else
|
||||
freq_at_pos = f_min + (2 * offset * each_bin_size) + (pos * looking_glass_range) / SCREEN_W;
|
||||
freq_at_pos = f_min + (2 * offset * each_bin_size) + (pos * looking_glass_range) / screen_width;
|
||||
return freq_at_pos;
|
||||
}
|
||||
|
||||
void GlassView::on_marker_change() {
|
||||
void GlassView::on_marker_change(int8_t delta) {
|
||||
if ((marker_pixel_index + delta) < 0)
|
||||
marker_pixel_index = marker_pixel_index + delta + screen_width;
|
||||
else if ((marker_pixel_index + delta) > screen_width)
|
||||
marker_pixel_index = marker_pixel_index + delta - screen_width;
|
||||
else
|
||||
marker_pixel_index = marker_pixel_index + delta;
|
||||
|
||||
marker = get_freq_from_bin_pos(marker_pixel_index);
|
||||
field_marker.set_text(to_string_short_freq(marker));
|
||||
plot_marker(marker_pixel_index); // Refresh marker on screen
|
||||
@@ -121,49 +130,52 @@ void GlassView::reset_live_view() {
|
||||
max_freq_power = -1000;
|
||||
|
||||
// Clear screen in peak mode.
|
||||
const int spectrum_height = screen_height - spectrum_y;
|
||||
if (live_frequency_view == 2)
|
||||
display.fill_rectangle({{0, 108 + 16}, {SCREEN_W, SCREEN_H - (108 + 16)}}, {0, 0, 0});
|
||||
display.fill_rectangle({{0, spectrum_y}, {screen_width, spectrum_height}}, {0, 0, 0});
|
||||
}
|
||||
|
||||
void GlassView::add_spectrum_pixel(uint8_t power) {
|
||||
spectrum_row[pixel_index] = spectrum_rgb3_lut[power]; // row of colors
|
||||
spectrum_row[pixel_index] = gradient.lut[power]; // row of colors
|
||||
spectrum_data[pixel_index] = (live_frequency_integrate * spectrum_data[pixel_index] + power) / (live_frequency_integrate + 1); // smoothing
|
||||
pixel_index++;
|
||||
|
||||
if (pixel_index == SCREEN_W) // got an entire waterfall line
|
||||
if (pixel_index == screen_width) // got an entire waterfall line
|
||||
{
|
||||
if (live_frequency_view > 0) {
|
||||
constexpr int rssi_sample_range = SPEC_NB_BINS;
|
||||
constexpr float rssi_voltage_min = 0.4;
|
||||
constexpr float rssi_voltage_max = 2.2;
|
||||
constexpr float adc_voltage_max = 3.3;
|
||||
constexpr int raw_min = rssi_sample_range * rssi_voltage_min / adc_voltage_max;
|
||||
constexpr int raw_max = rssi_sample_range * rssi_voltage_max / adc_voltage_max;
|
||||
constexpr int raw_delta = raw_max - raw_min;
|
||||
const range_t<int> y_max_range{0, 320 - (108 + 16)};
|
||||
|
||||
constexpr int raw_min = 0.5f + rssi_sample_range * rssi_voltage_min / adc_voltage_max;
|
||||
constexpr int raw_max = 0.5f + rssi_sample_range * rssi_voltage_max / adc_voltage_max;
|
||||
const int spectrum_height = screen_height - spectrum_y;
|
||||
// drawing and keeping track of max freq
|
||||
for (uint16_t xpos = 0; xpos < SCREEN_W; xpos++) {
|
||||
for (uint16_t xpos = 0; xpos < screen_width; xpos++) {
|
||||
// save max powerwull freq
|
||||
if (spectrum_data[xpos] > max_freq_power) {
|
||||
max_freq_power = spectrum_data[xpos];
|
||||
max_freq_hold = get_freq_from_bin_pos(xpos);
|
||||
}
|
||||
int16_t point = y_max_range.clip(((spectrum_data[xpos] - raw_min) * (320 - (108 + 16))) / raw_delta);
|
||||
uint8_t color_gradient = (point * 255) / 212;
|
||||
int16_t point = map(spectrum_data[xpos], 0, 255, 0, spectrum_height);
|
||||
// clear if not in peak view
|
||||
if (live_frequency_view != 2) {
|
||||
display.fill_rectangle({{xpos, 108 + 16}, {1, SCREEN_H - point}}, {0, 0, 0});
|
||||
display.fill_rectangle({{xpos, spectrum_y}, {1, screen_height - point}}, {0, 0, 0});
|
||||
}
|
||||
display.fill_rectangle({{xpos, SCREEN_H - point}, {1, point}}, {color_gradient, 0, uint8_t(255 - color_gradient)});
|
||||
display.fill_rectangle({{xpos, screen_height - point}, {1, point}}, gradient.lut[spectrum_data[xpos]]);
|
||||
}
|
||||
// indicate RSSI min and max power
|
||||
if (show_rssi_guides) {
|
||||
display.fill_rectangle({{0, screen_height - map(raw_min, 0, 255, 0, spectrum_height)}, {screen_width, 1}}, -gradient.lut[raw_min]);
|
||||
display.fill_rectangle({{0, screen_height - map(raw_max, 0, 255, 0, spectrum_height)}, {screen_width, 1}}, -gradient.lut[raw_max]);
|
||||
}
|
||||
if (last_max_freq != max_freq_hold) {
|
||||
last_max_freq = max_freq_hold;
|
||||
freq_stats.set("MAX HOLD: " + to_string_short_freq(max_freq_hold));
|
||||
freq_stats.set("MAX: " + to_string_short_freq(max_freq_hold));
|
||||
}
|
||||
plot_marker(marker_pixel_index);
|
||||
} else {
|
||||
display.draw_pixels({{0, display.scroll(1)}, {SCREEN_W, 1}}, spectrum_row); // new line at top, one less var, speedier
|
||||
display.draw_pixels({{0, display.scroll(1)}, {screen_width, 1}}, spectrum_row); // new line at top, one less var, speedier
|
||||
}
|
||||
pixel_index = 0; // Start New cascade line
|
||||
}
|
||||
@@ -199,8 +211,8 @@ bool GlassView::process_bins(uint8_t* powerlevel) {
|
||||
void GlassView::on_channel_spectrum(const ChannelSpectrum& spectrum) {
|
||||
baseband::spectrum_streaming_stop();
|
||||
// Convert bins of this spectrum slice into a representative max_power and when enough, into pixels
|
||||
// we actually need SCREEN_W (240) of those bins
|
||||
for (uint8_t bin = 0; bin < bin_length; bin++) {
|
||||
// we actually need screen_width (240) of those bins
|
||||
for (uint16_t bin = 0; bin < bin_length; bin++) {
|
||||
get_max_power(spectrum, bin, max_power);
|
||||
if (max_power > range_max_power)
|
||||
range_max_power = max_power;
|
||||
@@ -208,7 +220,7 @@ void GlassView::on_channel_spectrum(const ChannelSpectrum& spectrum) {
|
||||
if (bin == 119) {
|
||||
uint8_t next_max_power = 0;
|
||||
get_max_power(spectrum, bin + 1, next_max_power);
|
||||
for (uint8_t it = 0; it < ignore_dc; it++) {
|
||||
for (uint16_t it = 0; it < ignore_dc; it++) {
|
||||
uint8_t med_max_power = (max_power + next_max_power) / 2; // due to the way process_bins works we have to keep resetting the color
|
||||
if (process_bins(&med_max_power) == true)
|
||||
return; // new line signaled, return
|
||||
@@ -238,7 +250,7 @@ void GlassView::on_hide() {
|
||||
}
|
||||
|
||||
void GlassView::on_show() {
|
||||
display.scroll_set_area(109, 319); // Restart scroll on the correct coordinates
|
||||
display.scroll_set_area(109, screen_height - 1); // Restart scroll on the correct coordinates
|
||||
baseband::spectrum_streaming_start();
|
||||
}
|
||||
|
||||
@@ -253,11 +265,11 @@ void GlassView::on_range_changed() {
|
||||
// if the view is done in one pass, show it like in analog_audio_app
|
||||
mode = LOOKING_GLASS_SINGLEPASS;
|
||||
offset = 2;
|
||||
bin_length = SCREEN_W;
|
||||
bin_length = screen_width;
|
||||
ignore_dc = 0;
|
||||
looking_glass_bandwidth = looking_glass_range;
|
||||
looking_glass_sampling_rate = looking_glass_range;
|
||||
each_bin_size = looking_glass_bandwidth / SCREEN_W;
|
||||
each_bin_size = looking_glass_bandwidth / screen_width;
|
||||
looking_glass_step = looking_glass_bandwidth;
|
||||
f_center_ini = f_min + (looking_glass_bandwidth / 2); // Initial center frequency for sweep
|
||||
} else {
|
||||
@@ -269,7 +281,7 @@ void GlassView::on_range_changed() {
|
||||
if (mode == LOOKING_GLASS_FASTSCAN) {
|
||||
offset = 2;
|
||||
ignore_dc = 4;
|
||||
bin_length = SCREEN_W;
|
||||
bin_length = screen_width;
|
||||
} else { // if( mode == LOOKING_GLASS_SLOWSCAN )
|
||||
offset = 2;
|
||||
bin_length = 80;
|
||||
@@ -279,13 +291,13 @@ void GlassView::on_range_changed() {
|
||||
f_center_ini = f_min - (offset * each_bin_size) + (looking_glass_bandwidth / 2); // Initial center frequency for sweep
|
||||
}
|
||||
search_span = looking_glass_range / MHZ_DIV;
|
||||
marker_pixel_step = looking_glass_range / SCREEN_W; // Each pixel value in Hz
|
||||
marker_pixel_step = looking_glass_range / screen_width; // Each pixel value in Hz
|
||||
|
||||
pixel_index = 0;
|
||||
max_power = 0;
|
||||
bins_hz_size = 0;
|
||||
|
||||
on_marker_change();
|
||||
on_marker_change(0);
|
||||
update_range_field();
|
||||
|
||||
// set the sample rate and bandwidth
|
||||
@@ -298,16 +310,16 @@ void GlassView::on_range_changed() {
|
||||
receiver_model.set_target_frequency(f_center); // tune rx for this slice
|
||||
}
|
||||
|
||||
void GlassView::plot_marker(uint8_t pos) {
|
||||
uint8_t shift_y = 0;
|
||||
void GlassView::plot_marker(uint16_t pos) {
|
||||
uint16_t shift_y = 0;
|
||||
if (live_frequency_view > 0) // plot one line down when in live view
|
||||
{
|
||||
shift_y = 16;
|
||||
}
|
||||
portapack::display.fill_rectangle({0, 100 + shift_y, SCREEN_W, 8}, Theme::getInstance()->bg_darkest->background); // Clear old marker and whole marker rectangle btw
|
||||
portapack::display.fill_rectangle({pos - 2, 100 + shift_y, 5, 3}, Theme::getInstance()->fg_red->foreground); // Red marker top
|
||||
portapack::display.fill_rectangle({pos - 1, 103 + shift_y, 3, 3}, Theme::getInstance()->fg_red->foreground); // Red marker middle
|
||||
portapack::display.fill_rectangle({pos, 106 + shift_y, 1, 2}, Theme::getInstance()->fg_red->foreground); // Red marker bottom
|
||||
portapack::display.fill_rectangle({0, 100 + shift_y, screen_width, 8}, Theme::getInstance()->bg_darkest->background); // Clear old marker and whole marker rectangle btw
|
||||
portapack::display.fill_rectangle({pos - 2, 100 + shift_y, 5, 3}, Theme::getInstance()->fg_red->foreground); // Red marker top
|
||||
portapack::display.fill_rectangle({pos - 1, 103 + shift_y, 3, 3}, Theme::getInstance()->fg_red->foreground); // Red marker middle
|
||||
portapack::display.fill_rectangle({pos, 106 + shift_y, 1, 2}, Theme::getInstance()->fg_red->foreground); // Red marker bottom
|
||||
}
|
||||
|
||||
void GlassView::update_min(int32_t v) {
|
||||
@@ -316,8 +328,8 @@ void GlassView::update_min(int32_t v) {
|
||||
min_size = search_span;
|
||||
if (min_size < 1)
|
||||
min_size = 1;
|
||||
if (v > 7200 - min_size) {
|
||||
v = 7200 - min_size;
|
||||
if (v > LOOKING_GLASS_MAX_FREQ_MHZ - min_size) {
|
||||
v = LOOKING_GLASS_MAX_FREQ_MHZ - min_size;
|
||||
}
|
||||
if (v > (field_frequency_max.value() - min_size))
|
||||
field_frequency_max.set_value(v + min_size, false);
|
||||
@@ -336,6 +348,9 @@ void GlassView::update_max(int32_t v) {
|
||||
if (v < min_size) {
|
||||
v = min_size;
|
||||
}
|
||||
if (v > LOOKING_GLASS_MAX_FREQ_MHZ) {
|
||||
v = LOOKING_GLASS_MAX_FREQ_MHZ;
|
||||
}
|
||||
if (v < (field_frequency_min.value() + min_size))
|
||||
field_frequency_min.set_value(v - min_size, false);
|
||||
if (locked_range)
|
||||
@@ -358,6 +373,11 @@ GlassView::GlassView(
|
||||
NavigationView& nav)
|
||||
: nav_(nav) {
|
||||
baseband::run_image(portapack::spi_flash::image_tag_wideband_spectrum);
|
||||
spectrum_row.resize(screen_width);
|
||||
spectrum_data.resize(screen_width);
|
||||
if (!gradient.load_file(default_gradient_file)) {
|
||||
gradient.set_default();
|
||||
}
|
||||
|
||||
add_children({&labels,
|
||||
&field_frequency_min,
|
||||
@@ -376,10 +396,12 @@ GlassView::GlassView(
|
||||
&button_beep_squelch,
|
||||
&field_marker,
|
||||
&field_trigger,
|
||||
&button_marker_minus,
|
||||
&button_marker_plus,
|
||||
&button_jump,
|
||||
&button_rst,
|
||||
&field_rx_iq_phase_cal,
|
||||
&freq_stats});
|
||||
&button_rssi,
|
||||
&freq_stats, &button_play});
|
||||
|
||||
load_presets(); // Load available presets from TXT files (or default).
|
||||
preset_index = clip<uint8_t>(preset_index, 0, presets_db.size());
|
||||
@@ -433,26 +455,29 @@ GlassView::GlassView(
|
||||
freq_stats.hidden(true);
|
||||
button_jump.hidden(true);
|
||||
button_rst.hidden(true);
|
||||
display.scroll_set_area(109, 319); // Restart scroll on the correct coordinates.
|
||||
button_rssi.hidden(true);
|
||||
display.scroll_set_area(109, screen_height - 1); // Restart scroll on the correct coordinates.
|
||||
break;
|
||||
|
||||
case 1: // LEVEL
|
||||
display.fill_rectangle({{0, 108}, {SCREEN_W, 24}}, {0, 0, 0});
|
||||
display.fill_rectangle({{0, 108}, {screen_width, 24}}, {0, 0, 0});
|
||||
display.scroll_disable();
|
||||
level_integration.hidden(false);
|
||||
freq_stats.hidden(false);
|
||||
button_jump.hidden(false);
|
||||
button_rst.hidden(false);
|
||||
button_rssi.hidden(false);
|
||||
break;
|
||||
|
||||
case 2: // PEAK
|
||||
default:
|
||||
display.fill_rectangle({{0, 108}, {SCREEN_W, 24}}, {0, 0, 0});
|
||||
display.fill_rectangle({{0, 108}, {screen_width, 24}}, {0, 0, 0});
|
||||
display.scroll_disable();
|
||||
level_integration.hidden(false);
|
||||
freq_stats.hidden(false);
|
||||
button_jump.hidden(false);
|
||||
button_rst.hidden(false);
|
||||
button_rssi.hidden(false);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -486,13 +511,7 @@ GlassView::GlassView(
|
||||
range_presets.set_selected_index(preset_index);
|
||||
|
||||
field_marker.on_encoder_change = [this](TextField&, EncoderEvent delta) {
|
||||
if ((marker_pixel_index + delta) < 0)
|
||||
marker_pixel_index = marker_pixel_index + delta + SCREEN_W;
|
||||
else if ((marker_pixel_index + delta) > SCREEN_W)
|
||||
marker_pixel_index = marker_pixel_index + delta - SCREEN_W;
|
||||
else
|
||||
marker_pixel_index = marker_pixel_index + delta;
|
||||
on_marker_change();
|
||||
on_marker_change(delta);
|
||||
};
|
||||
|
||||
field_marker.on_select = [this](TextField&) {
|
||||
@@ -511,6 +530,14 @@ GlassView::GlassView(
|
||||
update_range_field();
|
||||
};
|
||||
|
||||
button_marker_minus.on_select = [this](Button&) {
|
||||
on_marker_change(-1);
|
||||
};
|
||||
|
||||
button_marker_plus.on_select = [this](Button&) {
|
||||
on_marker_change(1);
|
||||
};
|
||||
|
||||
button_jump.on_select = [this](Button&) {
|
||||
// Launch Audio with peak frequency.
|
||||
launch_audio(max_freq_hold);
|
||||
@@ -520,14 +547,32 @@ GlassView::GlassView(
|
||||
reset_live_view();
|
||||
};
|
||||
|
||||
field_rx_iq_phase_cal.set_range(0, hackrf_r9 ? 63 : 31); // max2839 has 6 bits [0..63], max2837 has 5 bits [0..31]
|
||||
field_rx_iq_phase_cal.set_value(get_spec_iq_phase_calibration_value()); // using accessor function of AnalogAudioView to read iq_phase_calibration_value from rx_audio.ini
|
||||
field_rx_iq_phase_cal.on_change = [this](int32_t v) {
|
||||
set_spec_iq_phase_calibration_value(v); // using accessor function of AnalogAudioView to write inside SPEC submenu, register value to max283x and save it to rx_audio.ini
|
||||
button_rssi.on_select = [this](Button& button) {
|
||||
show_rssi_guides = !show_rssi_guides;
|
||||
if (show_rssi_guides) {
|
||||
button.set_style(Theme::getInstance()->fg_green);
|
||||
} else {
|
||||
button.set_style(Theme::getInstance()->bg_darkest);
|
||||
}
|
||||
reset_live_view();
|
||||
};
|
||||
set_spec_iq_phase_calibration_value(get_spec_iq_phase_calibration_value()); // initialize iq_phase_calibration in radio
|
||||
|
||||
display.scroll_set_area(109, 319);
|
||||
button_play.on_select = [this](ImageButton&) {
|
||||
paused = !paused;
|
||||
if (paused) {
|
||||
button_play.set_foreground(Theme::getInstance()->fg_red->foreground);
|
||||
button_play.set_background(Theme::getInstance()->fg_red->background);
|
||||
baseband::spectrum_streaming_stop();
|
||||
button_play.set_bitmap(&bitmap_play);
|
||||
} else {
|
||||
button_play.set_foreground(Theme::getInstance()->fg_green->foreground);
|
||||
button_play.set_background(Theme::getInstance()->fg_green->background);
|
||||
baseband::spectrum_streaming_start();
|
||||
button_play.set_bitmap(&bitmap_stop);
|
||||
}
|
||||
};
|
||||
|
||||
display.scroll_set_area(109, screen_height - 1); // Restart scroll on the correct coordinates
|
||||
|
||||
// trigger:
|
||||
// Discord User jteich: WidebandSpectrum::on_message to set the trigger value. In WidebandSpectrum::execute,
|
||||
@@ -535,7 +580,7 @@ GlassView::GlassView(
|
||||
// at which time it pushes the buffer up with channel_spectrum.feed
|
||||
baseband::set_spectrum(looking_glass_bandwidth, trigger);
|
||||
|
||||
marker_pixel_index = SCREEN_W / 2;
|
||||
marker_pixel_index = screen_width / 2;
|
||||
on_range_changed(); // Force a UI update.
|
||||
|
||||
receiver_model.set_sampling_rate(looking_glass_sampling_rate); // 20mhz
|
||||
@@ -576,44 +621,40 @@ void GlassView::on_freqchg(int64_t freq) {
|
||||
on_range_changed();
|
||||
}
|
||||
|
||||
uint8_t GlassView::get_spec_iq_phase_calibration_value() { // define accessor functions inside AnalogAudioView to read & write real iq_phase_calibration_value
|
||||
return iq_phase_calibration_value;
|
||||
}
|
||||
|
||||
void GlassView::set_spec_iq_phase_calibration_value(uint8_t cal_value) { // define accessor functions
|
||||
iq_phase_calibration_value = cal_value;
|
||||
radio::set_rx_max283x_iq_phase_calibration(iq_phase_calibration_value);
|
||||
}
|
||||
|
||||
void GlassView::load_presets() {
|
||||
File presets_file;
|
||||
auto error = presets_file.open(looking_glass_dir / u"PRESETS.TXT");
|
||||
presets_db.clear();
|
||||
|
||||
// Add the "Manual" entry.
|
||||
presets_db.push_back({0, 0, "Manual"});
|
||||
|
||||
if (!error) {
|
||||
auto reader = FileLineReader(presets_file);
|
||||
for (const auto& line : reader) {
|
||||
if (line.length() == 0 || line[0] == '#')
|
||||
continue;
|
||||
scan_root_files(looking_glass_dir, u"*.TXT", [this](const std::filesystem::path& path) {
|
||||
if (path.empty() || path.native()[0] == u'.')
|
||||
return;
|
||||
|
||||
auto cols = split_string(line, ',');
|
||||
if (cols.size() != 3)
|
||||
continue;
|
||||
File presets_file;
|
||||
auto error = presets_file.open(looking_glass_dir / path);
|
||||
if (!error) {
|
||||
auto reader = FileLineReader(presets_file);
|
||||
for (const auto& line : reader) {
|
||||
if (line.length() == 0 || line[0] == '#')
|
||||
continue;
|
||||
|
||||
preset_entry entry{};
|
||||
parse_int(cols[0], entry.min);
|
||||
parse_int(cols[1], entry.max);
|
||||
entry.label = trimr(cols[2]);
|
||||
auto cols = split_string(line, ',');
|
||||
if (cols.size() != 3)
|
||||
continue;
|
||||
|
||||
if (entry.min == 0 || entry.max == 0 || entry.min >= entry.max)
|
||||
continue; // Invalid line.
|
||||
preset_entry entry{};
|
||||
parse_int(cols[0], entry.min);
|
||||
parse_int(cols[1], entry.max);
|
||||
entry.label = trimr(cols[2]);
|
||||
|
||||
presets_db.emplace_back(std::move(entry));
|
||||
if (entry.max == 0 || entry.min >= entry.max)
|
||||
continue; // Invalid line.
|
||||
|
||||
presets_db.emplace_back(std::move(entry));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
populate_presets();
|
||||
}
|
||||
|
||||
@@ -35,13 +35,14 @@
|
||||
#include "ui_receiver.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "analog_audio_app.hpp"
|
||||
#include "spectrum_color_lut.hpp"
|
||||
#include "gradient.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
#define LOOKING_GLASS_SLICE_WIDTH_MAX 20000000
|
||||
#define LOOKING_GLASS_MAX_SAMPLERATE 20000000
|
||||
#define MHZ_DIV 1000000
|
||||
#define LOOKING_GLASS_MAX_FREQ_MHZ 7250 // HackRF upper tuning limit (band_high top), in MHz
|
||||
|
||||
// blanked DC (16 centered bins ignored ) and top left and right (2 bins ignored on each side )
|
||||
#define LOOKING_GLASS_FASTSCAN 0
|
||||
@@ -51,9 +52,6 @@ namespace ui {
|
||||
#define LOOKING_GLASS_SINGLEPASS 2
|
||||
// one spectrum line number of bins
|
||||
#define SPEC_NB_BINS 256
|
||||
// screen dimensions
|
||||
#define SCREEN_W 240
|
||||
#define SCREEN_H 320
|
||||
|
||||
class GlassView : public View {
|
||||
public:
|
||||
@@ -69,11 +67,9 @@ class GlassView : public View {
|
||||
void on_hide() override;
|
||||
void focus() override;
|
||||
|
||||
uint8_t get_spec_iq_phase_calibration_value();
|
||||
void set_spec_iq_phase_calibration_value(uint8_t cal_value);
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
Gradient gradient{};
|
||||
RxRadioState radio_state_{ReceiverModel::Mode::SpectrumAnalysis};
|
||||
// Settings
|
||||
rf::Frequency f_min = 260 * MHZ_DIV; // Default to 315/433 remote range.
|
||||
@@ -82,11 +78,10 @@ class GlassView : public View {
|
||||
uint8_t filter_index = 0; // OFF
|
||||
uint8_t trigger = 32;
|
||||
uint8_t mode = LOOKING_GLASS_FASTSCAN;
|
||||
uint8_t live_frequency_view = 0; // Spectrum
|
||||
uint8_t live_frequency_integrate = 3; // Default (3 * old value + new_value) / 4
|
||||
uint8_t iq_phase_calibration_value{15}; // initial default RX IQ phase calibration value , used for both max2837 & max2839
|
||||
int32_t beep_squelch = 20; // range from -100 to +20, >=20 disabled
|
||||
bool beep_enabled = false; // activate on bip button click
|
||||
uint8_t live_frequency_view = 0; // Spectrum
|
||||
uint8_t live_frequency_integrate = 3; // Default (3 * old value + new_value) / 4
|
||||
int32_t beep_squelch = 20; // range from -100 to +20, >=20 disabled
|
||||
bool beep_enabled = false; // activate on bip button click
|
||||
app_settings::SettingsManager settings_{
|
||||
"rx_glass"sv,
|
||||
app_settings::Mode::RX,
|
||||
@@ -99,7 +94,6 @@ class GlassView : public View {
|
||||
{"scan_mode"sv, &mode},
|
||||
{"freq_view"sv, &live_frequency_view},
|
||||
{"freq_integrate"sv, &live_frequency_integrate},
|
||||
{"iq_phase_calibration"sv, &iq_phase_calibration_value}, // we are saving and restoring that CAL from Settings.
|
||||
{"beep_squelch"sv, &beep_squelch},
|
||||
{"beep_enabled"sv, &beep_enabled},
|
||||
}};
|
||||
@@ -111,6 +105,7 @@ class GlassView : public View {
|
||||
};
|
||||
|
||||
void on_freqchg(int64_t freq);
|
||||
// Function to map the value from one range to another
|
||||
int32_t map(int32_t value, int32_t fromLow, int32_t fromHigh, int32_t toLow, int32_t toHigh);
|
||||
std::vector<preset_entry> presets_db{};
|
||||
void manage_beep_audio();
|
||||
@@ -118,9 +113,9 @@ class GlassView : public View {
|
||||
void update_min(int32_t v);
|
||||
void update_max(int32_t v);
|
||||
void update_range_field();
|
||||
void get_max_power(const ChannelSpectrum& spectrum, uint8_t bin, uint8_t& max_power);
|
||||
rf::Frequency get_freq_from_bin_pos(uint8_t pos);
|
||||
void on_marker_change();
|
||||
void get_max_power(const ChannelSpectrum& spectrum, uint16_t bin, uint8_t& max_power);
|
||||
rf::Frequency get_freq_from_bin_pos(uint16_t pos);
|
||||
void on_marker_change(int8_t delta);
|
||||
void retune();
|
||||
bool process_bins(uint8_t* powerlevel);
|
||||
void on_channel_spectrum(const ChannelSpectrum& spectrum);
|
||||
@@ -130,17 +125,19 @@ class GlassView : public View {
|
||||
void on_range_changed();
|
||||
void reset_live_view();
|
||||
void add_spectrum_pixel(uint8_t power);
|
||||
void plot_marker(uint8_t pos);
|
||||
void plot_marker(uint16_t pos);
|
||||
void load_presets();
|
||||
void populate_presets();
|
||||
void launch_audio(rf::Frequency center_freq);
|
||||
|
||||
bool paused{false};
|
||||
|
||||
rf::Frequency search_span{0};
|
||||
rf::Frequency f_center{0};
|
||||
rf::Frequency f_center_ini{0};
|
||||
|
||||
rf::Frequency marker{0};
|
||||
uint8_t marker_pixel_index{0};
|
||||
uint16_t marker_pixel_index{0};
|
||||
rf::Frequency marker_pixel_step{0};
|
||||
|
||||
// Size of one spectrum bin in Hz.
|
||||
@@ -154,8 +151,8 @@ class GlassView : public View {
|
||||
uint8_t min_color_power{0}; // Filter cutoff level.
|
||||
uint32_t pixel_index{0};
|
||||
|
||||
std::array<Color, SCREEN_W> spectrum_row{};
|
||||
std::array<uint8_t, SCREEN_W> spectrum_data{};
|
||||
std::vector<Color> spectrum_row{};
|
||||
std::vector<uint8_t> spectrum_data{};
|
||||
ChannelSpectrumFIFO* fifo{};
|
||||
|
||||
int32_t steps = 1;
|
||||
@@ -167,44 +164,47 @@ class GlassView : public View {
|
||||
rf::Frequency max_freq_hold = 0;
|
||||
rf::Frequency last_max_freq = 0;
|
||||
int16_t max_freq_power = -1000;
|
||||
uint8_t bin_length = SCREEN_W;
|
||||
uint8_t bin_length = screen_width;
|
||||
uint8_t offset = 0;
|
||||
uint8_t ignore_dc = 0;
|
||||
bool show_rssi_guides = false;
|
||||
|
||||
// start of spectrum area
|
||||
static constexpr int spectrum_y = (108 + 16);
|
||||
|
||||
Labels labels{
|
||||
{{0, 0 * 16}, "MIN: MAX: LNA VGA ", Theme::getInstance()->fg_light->foreground},
|
||||
{{0, 1 * 16}, "RANGE: FILTER: AMP:", Theme::getInstance()->fg_light->foreground},
|
||||
{{0, 2 * 16}, "P:", Theme::getInstance()->fg_light->foreground},
|
||||
{{0, 3 * 16}, "MARKER: MHz RXIQCAL", Theme::getInstance()->fg_light->foreground},
|
||||
//{{0, 4 * 16}, "RES: STEPS:", Theme::getInstance()->fg_light->foreground}};
|
||||
{{0, 4 * 16}, "RES: VOL:", Theme::getInstance()->fg_light->foreground}};
|
||||
{{0, UI_POS_Y(0)}, "MIN: MAX: LNA VGA ", Theme::getInstance()->fg_light->foreground},
|
||||
{{0, UI_POS_Y(1)}, "RANGE: FILTER: AMP:", Theme::getInstance()->fg_light->foreground},
|
||||
{{0, UI_POS_Y(2)}, "P:", Theme::getInstance()->fg_light->foreground},
|
||||
{{0, UI_POS_Y(3)}, "MARKER( / ): MHz", Theme::getInstance()->fg_light->foreground},
|
||||
{{0, UI_POS_Y(4)}, "RES: VOL:", Theme::getInstance()->fg_light->foreground}};
|
||||
|
||||
NumberField field_frequency_min{
|
||||
{4 * 8, 0 * 16},
|
||||
{UI_POS_X(4), UI_POS_Y(0)},
|
||||
4,
|
||||
{0, 7199},
|
||||
{0, LOOKING_GLASS_MAX_FREQ_MHZ - 1},
|
||||
1, // number of steps by encoder delta
|
||||
' '};
|
||||
|
||||
NumberField field_frequency_max{
|
||||
{13 * 8, 0 * 16},
|
||||
{UI_POS_X(13), UI_POS_Y(0)},
|
||||
4,
|
||||
{1, 7200},
|
||||
{1, LOOKING_GLASS_MAX_FREQ_MHZ},
|
||||
1, // number of steps by encoder delta
|
||||
' '};
|
||||
|
||||
LNAGainField field_lna{
|
||||
{21 * 8, 0 * 16}};
|
||||
{UI_POS_X(21), UI_POS_Y(0)}};
|
||||
|
||||
VGAGainField field_vga{
|
||||
{27 * 8, 0 * 16}};
|
||||
{UI_POS_X(27), UI_POS_Y(0)}};
|
||||
|
||||
TextField field_range{
|
||||
{6 * 8, 1 * 16, 6 * 8, 16},
|
||||
{UI_POS_X(6), UI_POS_Y(1), UI_POS_WIDTH(6), UI_POS_HEIGHT(1)},
|
||||
""};
|
||||
|
||||
OptionsField filter_config{
|
||||
{20 * 8, 1 * 16},
|
||||
{UI_POS_X(20), UI_POS_Y(1)},
|
||||
4,
|
||||
{
|
||||
{"OFF ", 0},
|
||||
@@ -213,41 +213,47 @@ class GlassView : public View {
|
||||
}};
|
||||
|
||||
RFAmpField field_rf_amp{
|
||||
{28 * 8, 1 * 16}};
|
||||
{UI_POS_X(28), UI_POS_Y(1)}};
|
||||
|
||||
OptionsField range_presets{
|
||||
{2 * 8, 2 * 16},
|
||||
{UI_POS_X(2), UI_POS_Y(2)},
|
||||
20,
|
||||
{}};
|
||||
|
||||
ButtonWithEncoder button_beep_squelch{
|
||||
{240 - 8 * 8, 2 * 16 + 4, 8 * 8, 1 * 8},
|
||||
{UI_POS_X_RIGHT(8), UI_POS_Y(2.25), UI_POS_WIDTH(8), UI_POS_HEIGHT(0.5)},
|
||||
""};
|
||||
|
||||
ImageButton button_play{
|
||||
{UI_POS_X_RIGHT(2), UI_POS_Y(3.25) - 2, UI_POS_WIDTH(2), UI_POS_HEIGHT(0.5)},
|
||||
&bitmap_stop,
|
||||
Theme::getInstance()->fg_green->foreground,
|
||||
Theme::getInstance()->fg_green->background};
|
||||
|
||||
TextField field_marker{
|
||||
{7 * 8, 3 * 16, 9 * 8, 16},
|
||||
{UI_POS_X(12), UI_POS_Y(3), UI_POS_WIDTH(9), UI_POS_HEIGHT(1)},
|
||||
""};
|
||||
|
||||
NumberField field_rx_iq_phase_cal{
|
||||
{28 * 8, 3 * 16},
|
||||
2,
|
||||
{0, 63}, // 5 or 6 bits IQ CAL phase adjustment (range updated later)
|
||||
1,
|
||||
' ',
|
||||
};
|
||||
Button button_marker_minus{
|
||||
{UI_POS_X(7), UI_POS_Y(3.25), UI_POS_WIDTH(1), UI_POS_HEIGHT(0.5)},
|
||||
"-"};
|
||||
|
||||
Button button_marker_plus{
|
||||
{UI_POS_X(9), UI_POS_Y(3.25), UI_POS_WIDTH(1), UI_POS_HEIGHT(0.5)},
|
||||
"+"};
|
||||
|
||||
NumberField field_trigger{
|
||||
{4 * 8, 4 * 16},
|
||||
{UI_POS_X(4), UI_POS_Y(4)},
|
||||
3,
|
||||
{2, 128},
|
||||
2,
|
||||
' '};
|
||||
|
||||
AudioVolumeField field_volume{
|
||||
{13 * 8, 4 * 16}};
|
||||
{UI_POS_X(13), UI_POS_Y(4)}};
|
||||
|
||||
/*OptionsField steps_config{
|
||||
{13 * 8, 4 * 16},
|
||||
{UI_POS_X(13), UI_POS_Y(4)},
|
||||
3,
|
||||
{
|
||||
{"1", 1},
|
||||
@@ -259,7 +265,7 @@ class GlassView : public View {
|
||||
}};*/
|
||||
|
||||
OptionsField scan_type{
|
||||
{17 * 8, 4 * 16},
|
||||
{UI_POS_X(17), UI_POS_Y(4)},
|
||||
2,
|
||||
{
|
||||
{"F-", LOOKING_GLASS_FASTSCAN},
|
||||
@@ -267,7 +273,7 @@ class GlassView : public View {
|
||||
}};
|
||||
|
||||
OptionsField view_config{
|
||||
{19 * 8, 4 * 16},
|
||||
{UI_POS_X(19), UI_POS_Y(4)},
|
||||
7,
|
||||
{
|
||||
{"SPCTR-V", 0},
|
||||
@@ -276,7 +282,7 @@ class GlassView : public View {
|
||||
}};
|
||||
|
||||
OptionsField level_integration{
|
||||
{27 * 8, 4 * 16},
|
||||
{UI_POS_X(27), UI_POS_Y(4)},
|
||||
2,
|
||||
{
|
||||
{"x0", 0},
|
||||
@@ -292,15 +298,19 @@ class GlassView : public View {
|
||||
}};
|
||||
|
||||
Button button_jump{
|
||||
{SCREEN_W - 4 * 8, 5 * 16, 4 * 8, 16},
|
||||
{UI_POS_X_RIGHT(4), UI_POS_Y(5), UI_POS_WIDTH(4), UI_POS_HEIGHT(1)},
|
||||
"JMP"};
|
||||
|
||||
Button button_rst{
|
||||
{SCREEN_W - 9 * 8, 5 * 16, 4 * 8, 16},
|
||||
{UI_POS_X_RIGHT(9), UI_POS_Y(5), UI_POS_WIDTH(4), UI_POS_HEIGHT(1)},
|
||||
"RST"};
|
||||
|
||||
Button button_rssi{
|
||||
{UI_POS_X_RIGHT(15), UI_POS_Y(5), UI_POS_WIDTH(5), UI_POS_HEIGHT(1)},
|
||||
"RSSI"};
|
||||
|
||||
Text freq_stats{
|
||||
{0 * 8, 5 * 16, SCREEN_W - 10 * 8, 8},
|
||||
{UI_POS_X(0), UI_POS_Y(5), UI_POS_WIDTH(14), UI_POS_HEIGHT(1)},
|
||||
""};
|
||||
|
||||
MessageHandlerRegistration message_handler_spectrum_config{
|
||||
|
||||
@@ -202,7 +202,7 @@ void MicTXView::rxaudio(bool enable) {
|
||||
receiver_model.set_modulation(ReceiverModel::Mode::NarrowbandFMAudio);
|
||||
// receiver_model.set_nbfm_configuration(n); is called above, depending user's selection (8k5, 11k, 16k).
|
||||
break;
|
||||
case MIC_MOD_WFM: // WFM, BW 200Khz aprox, or the two new addional BW filters (180k, 40k)
|
||||
case MIC_MOD_WFM: // WFM, BW 200Khz aprox, or the two new addional BW filters (180k, 80k)
|
||||
baseband::run_image(portapack::spi_flash::image_tag_wfm_audio);
|
||||
receiver_model.set_modulation(ReceiverModel::Mode::WidebandFMAudio);
|
||||
// receiver_model.set_wfm_configuration(n); is called above, depending user's selection (200k, 180k, 0k).
|
||||
@@ -300,7 +300,7 @@ void MicTXView::update_receiver_rxbw(void) {
|
||||
receiver_model.set_nbfm_configuration(rxbw_index); // we are in NFM/FM case, we need to select proper NFM/FM RX channel filter, NFM BW 8K5(0), NFM BW 11K(1), FM BW 16K (2)
|
||||
break;
|
||||
case MIC_MOD_WFM:
|
||||
receiver_model.set_wfm_configuration(rxbw_index); // we are in WFM case, we need to select proper WFB RX BW filter, WFM BW 200K(0), WFM BW 180K(1), WFM BW 40K(2)
|
||||
receiver_model.set_wfm_configuration(rxbw_index); // we are in WFM case, we need to select proper WFB RX BW filter, WFM BW 200K(0), WFM BW 180K(1), WFM BW 80K(2)
|
||||
break;
|
||||
case MIC_MOD_AM:
|
||||
receiver_model.set_am_configuration(rxbw_index); // we are in AM TX mode, we need to select proper AM full path config AM-9K filter. 0+0 =>AM-9K(0), 0+1=1 =>AM-6K(1),
|
||||
@@ -350,6 +350,12 @@ MicTXView::MicTXView(
|
||||
&tx_button,
|
||||
&tx_icon});
|
||||
|
||||
// disable key repeat on select
|
||||
initial_switch_config_ = get_switches_repeat_config();
|
||||
SwitchesState config = initial_switch_config_;
|
||||
config[toUType(Switch::Sel)] = false;
|
||||
set_switches_repeat_config(config);
|
||||
|
||||
set_rxbw_options();
|
||||
set_rxbw_defaults(settings_.loaded());
|
||||
|
||||
@@ -446,6 +452,8 @@ MicTXView::MicTXView(
|
||||
field_frequency.set_value(tx_frequency);
|
||||
|
||||
// TODO: would be nice if frequency step was configurable in this app
|
||||
// but now, make it a bit lower
|
||||
receiver_model.set_frequency_step(12'500); // set freq step to 12.5kHz
|
||||
field_frequency.set_step(receiver_model.frequency_step());
|
||||
|
||||
// WARNING: transmitter_model.set_target_frequency() and receiver_model.set_target_frequency() both update the same tuning freq, but one has an offset!
|
||||
@@ -638,6 +646,9 @@ MicTXView::MicTXView(
|
||||
}
|
||||
|
||||
MicTXView::~MicTXView() {
|
||||
// restore select key repeat mode
|
||||
set_switches_repeat_config(initial_switch_config_);
|
||||
|
||||
audio::input::stop();
|
||||
if (rx_enabled) { // Also turn off both (audio rx if enabled, and disable mic_loop to HP)
|
||||
rxaudio(false);
|
||||
|
||||
@@ -90,6 +90,9 @@ class MicTXView : public View {
|
||||
sampling_rate /* sampling rate */
|
||||
};
|
||||
|
||||
// structure used to control key repeat
|
||||
SwitchesState initial_switch_config_{};
|
||||
|
||||
enum Mic_Modulation : uint32_t {
|
||||
MIC_MOD_NFM = 0,
|
||||
MIC_MOD_WFM = 1,
|
||||
@@ -170,7 +173,7 @@ class MicTXView : public View {
|
||||
{{17 * 8, 1 * 8}, "ALC", Theme::getInstance()->fg_light->foreground}};
|
||||
|
||||
VuMeter vumeter{
|
||||
{0 * 8, 1 * 8, 2 * 8, 33 * 8},
|
||||
{UI_POS_X(0), 1 * 8, 2 * 8, 33 * 8},
|
||||
12,
|
||||
true};
|
||||
|
||||
@@ -301,7 +304,8 @@ class MicTXView : public View {
|
||||
{
|
||||
{" 8k5 ", 0}, // Initial dynamic values when we start Mic App.
|
||||
{" 11k ", 1},
|
||||
{" 16k ", 2},
|
||||
{" 12k5 ", 2},
|
||||
{" 16k ", 3},
|
||||
}};
|
||||
|
||||
NumberField field_squelch{
|
||||
@@ -350,7 +354,7 @@ class MicTXView : public View {
|
||||
};
|
||||
|
||||
Button tx_button{
|
||||
{10 * 8, 30 * 8, 10 * 8, 5 * 8},
|
||||
{10 * 8, screen_width, 10 * 8, 5 * 8},
|
||||
"PTT TX",
|
||||
true};
|
||||
|
||||
|
||||
@@ -104,7 +104,7 @@ class ModemSetupView : public View {
|
||||
"SET"};
|
||||
|
||||
Button button_save{
|
||||
{9 * 8, 250, 96, 40},
|
||||
{UI_POS_X_CENTER(12), 250, UI_POS_WIDTH(12), 40},
|
||||
"Save"};
|
||||
};
|
||||
|
||||
|
||||
@@ -387,7 +387,6 @@ PlaylistView::PlaylistView(
|
||||
&button_next,
|
||||
&waterfall,
|
||||
});
|
||||
|
||||
ensure_directory(playlist_dir);
|
||||
waterfall.show_audio_spectrum_view(false);
|
||||
|
||||
|
||||
@@ -107,24 +107,24 @@ class PlaylistView : public View {
|
||||
void handle_replay_thread_done(uint32_t return_code);
|
||||
|
||||
Text text_filename{
|
||||
{0 * 8, 0 * 16, 30 * 8, 16}};
|
||||
{UI_POS_X(0), UI_POS_Y(0), screen_width, 16}};
|
||||
|
||||
FrequencyField field_frequency{
|
||||
{0 * 8, 1 * 16}};
|
||||
{UI_POS_X(0), 1 * 16}};
|
||||
|
||||
Text text_sample_rate{
|
||||
{10 * 8, 1 * 16, 7 * 8, 16}};
|
||||
|
||||
ProgressBar progressbar_track{
|
||||
{18 * 8, 1 * 16, 12 * 8, 8 + 1}};
|
||||
{18 * 8, 1 * 16, UI_POS_WIDTH_REMAINING(19), 8 + 1}};
|
||||
|
||||
// (-1) to overlap with progressbar_track so there's
|
||||
// only 1 pixel between them instead of 2.
|
||||
ProgressBar progressbar_transmit{
|
||||
{18 * 8, 3 * 8 - 1, 12 * 8, 8}};
|
||||
{18 * 8, 3 * 8 - 1, UI_POS_WIDTH_REMAINING(19), 8}};
|
||||
|
||||
Text text_duration{
|
||||
{0 * 8, 2 * 16, 5 * 8, 16}};
|
||||
{UI_POS_X(0), 2 * 16, 5 * 8, 16}};
|
||||
|
||||
// TODO: delay duration field.
|
||||
|
||||
@@ -133,19 +133,19 @@ class PlaylistView : public View {
|
||||
/*short_ui*/ true};
|
||||
|
||||
Checkbox check_loop{
|
||||
{21 * 8, 2 * 16},
|
||||
{UI_POS_X_RIGHT(9), 2 * 16},
|
||||
4,
|
||||
"Loop",
|
||||
true};
|
||||
|
||||
ImageButton button_play{
|
||||
{28 * 8, 2 * 16, 2 * 8, 1 * 16},
|
||||
{screen_width - 2 * 8, 2 * 16, 2 * 8, 1 * 16},
|
||||
&bitmap_play,
|
||||
Theme::getInstance()->fg_green->foreground,
|
||||
Theme::getInstance()->fg_green->background};
|
||||
|
||||
Text text_track{
|
||||
{0 * 8, 3 * 16, 30 * 8, 16}};
|
||||
{UI_POS_X(0), 3 * 16, screen_width, 16}};
|
||||
|
||||
NewButton button_prev{
|
||||
{2 * 8, 4 * 16, 4 * 8, 2 * 16},
|
||||
|
||||
@@ -65,6 +65,7 @@ RDSPSNView::RDSPSNView(
|
||||
nav,
|
||||
PSN,
|
||||
8,
|
||||
ENTER_KEYBOARD_MODE_ALPHA,
|
||||
[this](std::string& s) {
|
||||
text_psn.set(s);
|
||||
});
|
||||
@@ -86,6 +87,7 @@ RDSRadioTextView::RDSRadioTextView(
|
||||
nav,
|
||||
radiotext,
|
||||
28,
|
||||
ENTER_KEYBOARD_MODE_ALPHA,
|
||||
[this](std::string& s) {
|
||||
text_radiotext.set(s);
|
||||
});
|
||||
|
||||
@@ -82,7 +82,7 @@ class RDSRadioTextView : public OptionTabView {
|
||||
{1 * 8, 4 * 16, 28 * 8, 16},
|
||||
"-"};
|
||||
Button button_set{
|
||||
{88, 6 * 16, 64, 32},
|
||||
{UI_POS_X_CENTER(8), 6 * 16, UI_POS_WIDTH(8), 32},
|
||||
"Set"};
|
||||
};
|
||||
|
||||
@@ -92,7 +92,7 @@ class RDSDateTimeView : public OptionTabView {
|
||||
|
||||
private:
|
||||
Labels labels{
|
||||
{{44, 5 * 16}, "Not yet implemented", Theme::getInstance()->error_dark->foreground}};
|
||||
{{UI_POS_X_CENTER(19), 5 * 16}, "Not yet implemented", Theme::getInstance()->error_dark->foreground}};
|
||||
};
|
||||
|
||||
class RDSAudioView : public OptionTabView {
|
||||
@@ -101,7 +101,7 @@ class RDSAudioView : public OptionTabView {
|
||||
|
||||
private:
|
||||
Labels labels{
|
||||
{{44, 5 * 16}, "Not yet implemented", Theme::getInstance()->error_dark->foreground}};
|
||||
{{UI_POS_X_CENTER(19), 5 * 16}, "Not yet implemented", Theme::getInstance()->error_dark->foreground}};
|
||||
};
|
||||
|
||||
class RDSThread {
|
||||
@@ -160,7 +160,7 @@ class RDSView : public View {
|
||||
|
||||
void start_tx();
|
||||
|
||||
Rect view_rect = {0, 8 * 8, 240, 192};
|
||||
Rect view_rect = {0, 8 * 8, screen_width, 192};
|
||||
|
||||
RDSPSNView view_PSN{nav_, view_rect};
|
||||
RDSRadioTextView view_radiotext{nav_, view_rect};
|
||||
@@ -174,7 +174,7 @@ class RDSView : public View {
|
||||
{"Audio", Theme::getInstance()->fg_orange->foreground, &view_audio}};
|
||||
|
||||
Labels labels{
|
||||
{{0 * 8, 28}, "Program type:", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(0), 28}, "Program type:", Theme::getInstance()->fg_light->foreground},
|
||||
//{ { 14 * 8, 16 + 8 }, "CC:", Theme::getInstance()->fg_light->foreground },
|
||||
{{2 * 8, 28 + 16}, "Program ID:", Theme::getInstance()->fg_light->foreground},
|
||||
//{ { 13 * 8, 32 + 8 }, "Cov:",Theme::getInstance()->fg_light->foreground },
|
||||
@@ -319,7 +319,7 @@ class RDSView : public View {
|
||||
"TP"};
|
||||
|
||||
TransmitterView tx_view{
|
||||
16 * 16,
|
||||
(int16_t)UI_POS_Y_BOTTOM(4),
|
||||
50000,
|
||||
9};
|
||||
|
||||
|
||||
@@ -335,7 +335,7 @@ ReconView::ReconView(NavigationView& nav)
|
||||
tx_view.hidden(true);
|
||||
|
||||
// set record View
|
||||
record_view = std::make_unique<RecordView>(Rect{0, 0, 30 * 8, 1 * 16},
|
||||
record_view = std::make_unique<RecordView>(Rect{0, 0, screen_width, 1 * 16},
|
||||
u"AUTO_AUDIO", audio_dir,
|
||||
RecordView::FileType::WAV, 4096, 4);
|
||||
record_view->set_filename_date_frequency(true);
|
||||
@@ -517,11 +517,7 @@ ReconView::ReconView(NavigationView& nav)
|
||||
};
|
||||
set_loop_config(continuous);
|
||||
|
||||
rssi.set_focusable(true);
|
||||
rssi.set_peak(true, 500);
|
||||
rssi.on_select = [this](RSSI&) {
|
||||
nav_.replace<LevelView>();
|
||||
};
|
||||
|
||||
// TODO: *BUG* Both transmitter_model and receiver_model share the same pmem setting for target_frequency.
|
||||
button_mic_app.on_select = [this](Button&) {
|
||||
@@ -1090,8 +1086,8 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
|
||||
if (stepper < 0) stepper++;
|
||||
if (stepper > 0) stepper--;
|
||||
} // if( recon || stepper != 0 || index_stepper != 0 )
|
||||
} // if (frequency_list.size() > 0 )
|
||||
} /* on_statistics_updates */
|
||||
} // if (frequency_list.size() > 0 )
|
||||
} /* on_statistics_updates */
|
||||
}
|
||||
handle_retune();
|
||||
recon_redraw();
|
||||
@@ -1168,18 +1164,18 @@ size_t ReconView::change_mode(freqman_index_t new_mod) {
|
||||
}
|
||||
if (new_mod == SPEC_MODULATION) {
|
||||
if (persistent_memory::recon_repeat_recorded()) {
|
||||
record_view = std::make_unique<RecordView>(Rect{0, 0, 30 * 8, 1 * 16},
|
||||
record_view = std::make_unique<RecordView>(Rect{0, 0, screen_width, 1 * 16},
|
||||
u"RECON_REPEAT.C16", captures_dir,
|
||||
RecordView::FileType::RawS16, 16384, 3);
|
||||
record_view->set_filename_as_is(true);
|
||||
} else {
|
||||
record_view = std::make_unique<RecordView>(Rect{0, 0, 30 * 8, 1 * 16},
|
||||
record_view = std::make_unique<RecordView>(Rect{0, 0, screen_width, 1 * 16},
|
||||
u"AUTO_RAW", captures_dir,
|
||||
RecordView::FileType::RawS16, 16384, 3);
|
||||
record_view->set_filename_date_frequency(true);
|
||||
}
|
||||
} else {
|
||||
record_view = std::make_unique<RecordView>(Rect{0, 0, 30 * 8, 1 * 16},
|
||||
record_view = std::make_unique<RecordView>(Rect{0, 0, screen_width, 1 * 16},
|
||||
u"AUTO_AUDIO", audio_dir,
|
||||
RecordView::FileType::WAV, 4096, 4);
|
||||
record_view->set_filename_date_frequency(true);
|
||||
@@ -1257,9 +1253,18 @@ size_t ReconView::change_mode(freqman_index_t new_mod) {
|
||||
|
||||
field_mode.set_selected_index(new_mod);
|
||||
field_mode.on_change = [this](size_t, OptionsField::value_t v) {
|
||||
if (v != -1) {
|
||||
change_mode(v);
|
||||
// initialize to a value under SPEC
|
||||
static freqman_index_t last_mode = WFM_MODULATION;
|
||||
if (v > SPEC_MODULATION) {
|
||||
if (last_mode == SPEC_MODULATION)
|
||||
v = AM_MODULATION;
|
||||
else
|
||||
v = SPEC_MODULATION;
|
||||
field_mode.set_selected_index(v);
|
||||
return; // Important ! Guru will occur if you don't return when modifying field from within on_change !
|
||||
}
|
||||
last_mode = v;
|
||||
change_mode(v);
|
||||
};
|
||||
// for some motive, audio output gets stopped.
|
||||
if (!recon && field_mode.selected_index_value() != SPEC_MODULATION)
|
||||
@@ -1400,8 +1405,8 @@ void ReconView::start_repeat() {
|
||||
std::string delay_message = "TX DELAY: " + to_string_dec_uint(delay) + "s";
|
||||
|
||||
// update display information
|
||||
p.fill_rectangle({0, (SCREEN_H / 2) - 16, SCREEN_W, 64}, Theme::getInstance()->fg_light->foreground);
|
||||
p.draw_string({(SCREEN_W / 2) - 7 * 8, SCREEN_H / 2}, *Theme::getInstance()->fg_red, delay_message);
|
||||
p.fill_rectangle({0, (screen_height / 2) - 16, screen_width, 64}, Theme::getInstance()->fg_light->foreground);
|
||||
p.draw_string({(screen_width / 2) - 7 * 8, screen_height / 2}, *Theme::getInstance()->fg_red, delay_message);
|
||||
|
||||
// sleep 1 second
|
||||
chThdSleepMilliseconds(1000);
|
||||
|
||||
@@ -31,7 +31,6 @@
|
||||
#include "analog_audio_app.hpp"
|
||||
#include "audio.hpp"
|
||||
#include "ui_mictx.hpp"
|
||||
#include "ui_level.hpp"
|
||||
#include "ui_looking_glass_app.hpp"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
#include "baseband_api.hpp"
|
||||
@@ -218,35 +217,36 @@ class ReconView : public View {
|
||||
std::unique_ptr<RecordView> record_view{};
|
||||
|
||||
Labels labels{
|
||||
{{0 * 8, 0 * 16}, "LNA: VGA: AMP: VOL: ", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(0), UI_POS_Y(0)}, "LNA: VGA: AMP: ", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X_RIGHT(6), UI_POS_Y(0)}, "VOL: ", Theme::getInstance()->fg_light->foreground},
|
||||
{{3 * 8, 8 * 16}, "START END", Theme::getInstance()->fg_light->foreground},
|
||||
{{0 * 8, (22 * 8)}, " S: ", Theme::getInstance()->fg_light->foreground},
|
||||
{{0 * 8, (24 * 8) + 4}, "NBLCKS:x W,L: , ", Theme::getInstance()->fg_light->foreground},
|
||||
{{0 * 8, (26 * 8) + 4}, "MODE: , SQUELCH: ", Theme::getInstance()->fg_light->foreground}};
|
||||
{{UI_POS_X(0), (22 * 8)}, " S: ", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(0), (24 * 8) + 4}, "NBLCKS:x W,L: , ", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(0), (26 * 8) + 4}, "MODE: , SQUELCH: ", Theme::getInstance()->fg_light->foreground}};
|
||||
|
||||
LNAGainField field_lna{
|
||||
{4 * 8, 0 * 16}};
|
||||
{4 * 8, UI_POS_Y(0)}};
|
||||
|
||||
VGAGainField field_vga{
|
||||
{11 * 8, 0 * 16}};
|
||||
{11 * 8, UI_POS_Y(0)}};
|
||||
|
||||
RFAmpField field_rf_amp{
|
||||
{18 * 8, 0 * 16}};
|
||||
{18 * 8, UI_POS_Y(0)}};
|
||||
|
||||
AudioVolumeField field_volume{
|
||||
{24 * 8, 0 * 16}};
|
||||
{UI_POS_X_RIGHT(2), UI_POS_Y(0)}};
|
||||
|
||||
Text file_name{
|
||||
// show file used
|
||||
{0, 1 * 16, SCREEN_W, 16},
|
||||
{0, 1 * 16, screen_width, 16},
|
||||
};
|
||||
|
||||
Text desc_cycle{
|
||||
{0, 2 * 16, SCREEN_W, 16},
|
||||
{0, 2 * 16, screen_width, 16},
|
||||
};
|
||||
|
||||
RSSI rssi{
|
||||
{0 * 16, 3 * 16 + 2, SCREEN_W - 8 * 8 + 4, 12},
|
||||
{0 * 16, 3 * 16 + 2, screen_width - 8 * 8 + 4, 12},
|
||||
};
|
||||
|
||||
ButtonWithEncoder text_cycle{
|
||||
@@ -286,19 +286,19 @@ class ReconView : public View {
|
||||
|
||||
// Button can be RECON or SCANNER
|
||||
Button button_scanner_mode{
|
||||
{SCREEN_W - 7 * 8, 3 * 16, 7 * 8, 28},
|
||||
{screen_width - 7 * 8, 3 * 16, 7 * 8, 28},
|
||||
"RECON"};
|
||||
|
||||
Button button_loop_config{
|
||||
{SCREEN_W - 7 * 8, 5 * 16, 7 * 8, 28},
|
||||
{screen_width - 7 * 8, 5 * 16, 7 * 8, 28},
|
||||
"[LOOP]"};
|
||||
|
||||
Button button_config{
|
||||
{SCREEN_W - 7 * 8, 7 * 16, 7 * 8, 28},
|
||||
{screen_width - 7 * 8, 7 * 16, 7 * 8, 28},
|
||||
"CONFIG"};
|
||||
|
||||
ButtonWithEncoder button_manual_start{
|
||||
{0 * 8, 9 * 16, 11 * 8, 28},
|
||||
{UI_POS_X(0), 9 * 16, 11 * 8, 28},
|
||||
""};
|
||||
|
||||
ButtonWithEncoder button_manual_end{
|
||||
@@ -306,7 +306,7 @@ class ReconView : public View {
|
||||
""};
|
||||
|
||||
OptionsField field_recon_match_mode{
|
||||
{0 * 8, 11 * 16},
|
||||
{UI_POS_X(0), 11 * 16},
|
||||
16, // CONTINUOUS MATCH MODE / SPARSE TIMED MATCH MODE
|
||||
{
|
||||
{"MATCH:CONTINOUS", 0},
|
||||
@@ -364,15 +364,15 @@ class ReconView : public View {
|
||||
};
|
||||
|
||||
ButtonWithEncoder button_pause{
|
||||
{0, (15 * 16) - 4, 72, 28},
|
||||
{0, (15 * 16) - 4, UI_POS_WIDTH(9), 28},
|
||||
"PAUSE"};
|
||||
|
||||
Button button_audio_app{
|
||||
{84, (15 * 16) - 4, 72, 28},
|
||||
{UI_POS_X_CENTER(9), (15 * 16) - 4, UI_POS_WIDTH(9), 28},
|
||||
"AUDIO"};
|
||||
|
||||
ButtonWithEncoder button_add{
|
||||
{168, (15 * 16) - 4, 72, 28},
|
||||
{UI_POS_X_RIGHT(9), (15 * 16) - 4, UI_POS_WIDTH(9), 28},
|
||||
"<STORE>"};
|
||||
|
||||
Button button_dir{
|
||||
@@ -384,15 +384,15 @@ class ReconView : public View {
|
||||
"RST"};
|
||||
|
||||
Button button_mic_app{
|
||||
{84, (35 * 8) - 4, 72, 28},
|
||||
{UI_POS_X_CENTER(9), (35 * 8) - 4, UI_POS_WIDTH(9), 28},
|
||||
"MIC TX"};
|
||||
|
||||
ButtonWithEncoder button_remove{
|
||||
{168, (35 * 8) - 4, 72, 28},
|
||||
{UI_POS_X_RIGHT(9), (35 * 8) - 4, UI_POS_WIDTH(9), 28},
|
||||
"<REMOVE>"};
|
||||
|
||||
ProgressBar progressbar{
|
||||
{0 * 8, SCREEN_H / 2 - 16, SCREEN_W, 32}};
|
||||
{UI_POS_X(0), screen_height / 2 - 16, screen_width, 32}};
|
||||
|
||||
TransmitterView2 tx_view{
|
||||
{11 * 8, 2 * 16},
|
||||
|
||||
@@ -82,7 +82,7 @@ ReconSetupViewMain::ReconSetupViewMain(NavigationView& nav, Rect parent_rect, st
|
||||
};
|
||||
|
||||
button_choose_output_name.on_select = [this, &nav](Button&) {
|
||||
text_prompt(nav, _output_file, 28, [this](std::string& buffer) {
|
||||
text_prompt(nav, _output_file, 28, ENTER_KEYBOARD_MODE_ALPHA, [this](std::string& buffer) {
|
||||
_output_file = buffer;
|
||||
button_choose_output_name.set_text(_output_file);
|
||||
});
|
||||
|
||||
@@ -57,10 +57,6 @@
|
||||
#define RECON_MIN_LOCK_DURATION 100 // have to be >= and a multiple of STATS_UPDATE_INTERVAL
|
||||
#define RECON_DEF_WAIT_DURATION 1000 // will be incremented/decremented by STATS_UPDATE_INTERVAL steps
|
||||
|
||||
// screen size helper
|
||||
#define SCREEN_W 240
|
||||
// #define SCREEN_H 320
|
||||
|
||||
// recon settings nb params
|
||||
#define RECON_SETTINGS_NB_PARAMS 7
|
||||
|
||||
@@ -219,7 +215,7 @@ class ReconSetupView : public View {
|
||||
std::string input_file{"RECON"};
|
||||
std::string output_file{"RECON_RESULTS"};
|
||||
|
||||
Rect view_rect{0, 3 * 8, SCREEN_W, 230};
|
||||
Rect view_rect{0, 3 * 8, screen_width, 230};
|
||||
|
||||
ReconSetupViewMain viewMain{nav_, view_rect, input_file, output_file};
|
||||
ReconSetupViewMore viewMore{nav_, view_rect};
|
||||
@@ -229,7 +225,7 @@ class ReconSetupView : public View {
|
||||
{"More", Theme::getInstance()->fg_green->foreground, &viewMore}};
|
||||
|
||||
Button button_save{
|
||||
{9 * 8, 255, 14 * 8, 40},
|
||||
{UI_POS_X_CENTER(18), 255, UI_POS_WIDTH(18), 40},
|
||||
"SAVE"};
|
||||
};
|
||||
|
||||
|
||||
@@ -26,17 +26,24 @@
|
||||
#include "binder.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "ui_freqman.hpp"
|
||||
#include "audio.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
namespace pmem = portapack::persistent_memory;
|
||||
|
||||
namespace ui {
|
||||
|
||||
void SearchLogger::log_data(SearchRecentEntry& data) {
|
||||
log_file.write_entry(";" + to_string_short_freq(data.frequency) + ";" + std::to_string(data.duration));
|
||||
}
|
||||
|
||||
template <>
|
||||
void RecentEntriesTable<SearchRecentEntries>::draw(
|
||||
const Entry& entry,
|
||||
const Rect& target_rect,
|
||||
Painter& painter,
|
||||
const Style& style) {
|
||||
const Style& style,
|
||||
RecentEntriesColumns& columns) {
|
||||
std::string str_duration = "";
|
||||
|
||||
if (entry.duration < 600)
|
||||
@@ -44,9 +51,10 @@ void RecentEntriesTable<SearchRecentEntries>::draw(
|
||||
else
|
||||
str_duration = to_string_dec_uint(entry.duration / 600) + "m" + to_string_dec_uint((entry.duration / 10) % 60) + "s";
|
||||
|
||||
str_duration.resize(target_rect.width() / 8, ' ');
|
||||
|
||||
painter.draw_string(target_rect.location(), style, to_string_short_freq(entry.frequency) + " " + entry.time + " " + str_duration);
|
||||
str_duration.resize(11, ' ');
|
||||
std::string freq = to_string_short_freq(entry.frequency);
|
||||
freq.resize(columns.at(0).second, ' ');
|
||||
painter.draw_string(target_rect.location(), style, freq + " " + entry.time + " " + str_duration);
|
||||
}
|
||||
|
||||
/* SearchView ********************************************/
|
||||
@@ -54,8 +62,13 @@ void RecentEntriesTable<SearchRecentEntries>::draw(
|
||||
SearchView::SearchView(
|
||||
NavigationView& nav)
|
||||
: nav_(nav) {
|
||||
spectrum_row.resize(240);
|
||||
baseband::run_image(portapack::spi_flash::image_tag_wideband_spectrum);
|
||||
|
||||
if (!gradient.load_file(default_gradient_file)) {
|
||||
gradient.set_default();
|
||||
}
|
||||
|
||||
add_children({&labels,
|
||||
&field_frequency_min,
|
||||
&field_frequency_max,
|
||||
@@ -71,11 +84,12 @@ SearchView::SearchView(
|
||||
&check_snap,
|
||||
&options_snap,
|
||||
&big_display,
|
||||
&check_log,
|
||||
&recent_entries_view});
|
||||
|
||||
baseband::set_spectrum(SEARCH_SLICE_WIDTH, 31);
|
||||
|
||||
recent_entries_view.set_parent_rect({0, 28 * 8, screen_width, 12 * 8});
|
||||
recent_entries_view.set_parent_rect({0, 28 * 8, screen_width, screen_height - 28 * 8});
|
||||
recent_entries_view.on_select = [this, &nav](const SearchRecentEntry& entry) {
|
||||
nav.push<FrequencySaveView>(entry.frequency);
|
||||
};
|
||||
@@ -96,6 +110,14 @@ SearchView::SearchView(
|
||||
on_range_changed();
|
||||
});
|
||||
|
||||
check_log.on_select = [this](Checkbox&, bool v) {
|
||||
logging = v;
|
||||
if (logging) {
|
||||
logger.append(logs_dir.string() + "/SEARCH_" + to_string_timestamp(rtc_time::now()) + ".CSV");
|
||||
logger.write_header();
|
||||
}
|
||||
};
|
||||
|
||||
bind(field_threshold, settings_.power_threshold);
|
||||
bind(check_snap, settings_.snap_search);
|
||||
bind(options_snap, settings_.snap_step);
|
||||
@@ -104,9 +126,15 @@ SearchView::SearchView(
|
||||
|
||||
on_range_changed();
|
||||
receiver_model.enable();
|
||||
|
||||
if (pmem::beep_on_packets()) {
|
||||
audio::set_rate(audio::Rate::Hz_24000);
|
||||
audio::output::start();
|
||||
}
|
||||
}
|
||||
|
||||
SearchView::~SearchView() {
|
||||
audio::output::stop();
|
||||
receiver_model.disable();
|
||||
baseband::shutdown();
|
||||
}
|
||||
@@ -135,9 +163,8 @@ void SearchView::do_detection() {
|
||||
// Display spectrum
|
||||
bin_skip_acc = 0;
|
||||
pixel_index = 0;
|
||||
display.draw_pixels(
|
||||
{{0, 88}, {(Dim)spectrum_row.size(), 1}},
|
||||
spectrum_row);
|
||||
uint16_t center_align_start = (screen_width - spectrum_row.size()) / 2;
|
||||
display.draw_pixels({{center_align_start, 88}, {(Dim)spectrum_row.size(), 1}}, spectrum_row);
|
||||
|
||||
mean_power = mean_acc / (SEARCH_BIN_NB_NO_DC * slices_nb);
|
||||
mean_acc = 0;
|
||||
@@ -188,7 +215,9 @@ void SearchView::do_detection() {
|
||||
|
||||
locked = true;
|
||||
locked_bin = bin_max;
|
||||
|
||||
if (pmem::beep_on_packets()) {
|
||||
baseband::request_audio_beep(1000, 24000, 60);
|
||||
}
|
||||
// TODO: open Audio.
|
||||
} else
|
||||
text_infos.set("Out of range");
|
||||
@@ -206,6 +235,7 @@ void SearchView::do_detection() {
|
||||
|
||||
auto& entry = ::on_packet(recent, resolved_frequency);
|
||||
entry.set_duration(duration);
|
||||
if (logging) logger.log_data(entry);
|
||||
recent_entries_view.set_dirty();
|
||||
|
||||
text_infos.set("Listening");
|
||||
@@ -221,7 +251,7 @@ void SearchView::do_detection() {
|
||||
// Refresh red tick
|
||||
portapack::display.fill_rectangle({last_tick_pos, 90, 1, 6}, Theme::getInstance()->fg_red->background);
|
||||
if (bin_max > -1) {
|
||||
last_tick_pos = (Coord)(bin_max / slices_nb);
|
||||
last_tick_pos = (Coord)(bin_max / slices_nb) + center_align_start;
|
||||
portapack::display.fill_rectangle({last_tick_pos, 90, 1, 6}, Theme::getInstance()->fg_red->foreground);
|
||||
}
|
||||
}
|
||||
@@ -290,7 +320,7 @@ void SearchView::on_channel_spectrum(const ChannelSpectrum& spectrum) {
|
||||
power = spectrum.db[bin - 128];
|
||||
}
|
||||
|
||||
add_spectrum_pixel(spectrum_rgb3_lut[power]);
|
||||
add_spectrum_pixel(gradient.lut[power]);
|
||||
|
||||
mean_acc += power;
|
||||
if (power > max_power) {
|
||||
@@ -370,7 +400,7 @@ void SearchView::add_spectrum_pixel(Color color) {
|
||||
|
||||
bin_skip_acc -= 0x10000;
|
||||
|
||||
if (pixel_index < screen_width)
|
||||
if (pixel_index < spectrum_row.size())
|
||||
spectrum_row[pixel_index++] = color;
|
||||
}
|
||||
|
||||
|
||||
@@ -24,8 +24,10 @@
|
||||
#include "receiver_model.hpp"
|
||||
#include "recent_entries.hpp"
|
||||
#include "radio_state.hpp"
|
||||
#include "spectrum_color_lut.hpp"
|
||||
#include "gradient.hpp"
|
||||
#include "ui_receiver.hpp"
|
||||
#include "log_file.hpp"
|
||||
#include "file_path.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
@@ -70,6 +72,21 @@ struct SearchRecentEntry {
|
||||
|
||||
using SearchRecentEntries = RecentEntries<SearchRecentEntry>;
|
||||
|
||||
class SearchLogger {
|
||||
public:
|
||||
Optional<File::Error> append(const std::filesystem::path& filename) {
|
||||
return log_file.append(filename);
|
||||
}
|
||||
|
||||
void log_data(SearchRecentEntry& data);
|
||||
void write_header() {
|
||||
log_file.write_raw("Time;Freq;Duration;");
|
||||
}
|
||||
|
||||
private:
|
||||
LogFile log_file{};
|
||||
};
|
||||
|
||||
class SearchView : public View {
|
||||
public:
|
||||
SearchView(NavigationView& nav);
|
||||
@@ -88,6 +105,7 @@ class SearchView : public View {
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
Gradient gradient{};
|
||||
RxRadioState radio_state_{
|
||||
100'000'000 /* frequency */,
|
||||
2500000 /* bandwidth */,
|
||||
@@ -125,7 +143,7 @@ class SearchView : public View {
|
||||
uint32_t bin_skip_acc = 0;
|
||||
uint32_t bin_skip_frac = 0;
|
||||
uint32_t pixel_index = 0;
|
||||
std::array<Color, 240> spectrum_row{};
|
||||
std::vector<Color> spectrum_row{};
|
||||
ChannelSpectrumFIFO* fifo = nullptr;
|
||||
|
||||
uint8_t detect_timer = 0;
|
||||
@@ -147,6 +165,8 @@ class SearchView : public View {
|
||||
uint16_t locked_bin = 0;
|
||||
uint8_t search_counter = 0;
|
||||
bool locked = false;
|
||||
bool logging = false;
|
||||
SearchLogger logger{};
|
||||
|
||||
void do_detection();
|
||||
void do_timers();
|
||||
@@ -154,75 +174,82 @@ class SearchView : public View {
|
||||
void on_range_changed();
|
||||
void add_spectrum_pixel(Color color);
|
||||
|
||||
const RecentEntriesColumns columns{{{"Frequency", 9},
|
||||
{"Time", 8},
|
||||
{"Duration", 11}}};
|
||||
RecentEntriesColumns columns{{{"Frequency", 0},
|
||||
{"Time", 8},
|
||||
{"Duration", 11}}};
|
||||
SearchRecentEntries recent{};
|
||||
RecentEntriesView<RecentEntries<SearchRecentEntry>> recent_entries_view{columns, recent};
|
||||
|
||||
Labels labels{
|
||||
{{1 * 8, 0}, "Min: Max: LNA VGA", Theme::getInstance()->fg_light->foreground},
|
||||
{{1 * 8, 4 * 8}, "Trig: /255 Mean: /255", Theme::getInstance()->fg_light->foreground},
|
||||
{{1 * 8, 6 * 8}, "Slices: /32 Rate: Hz", Theme::getInstance()->fg_light->foreground},
|
||||
{{6 * 8, 10 * 8}, "Timer Status", Theme::getInstance()->fg_light->foreground},
|
||||
{{1 * 8, 25 * 8}, "Accuracy +/-4.9kHz", Theme::getInstance()->fg_light->foreground},
|
||||
{{26 * 8, 25 * 8}, "MHz", Theme::getInstance()->fg_light->foreground}};
|
||||
{{UI_POS_X(1), UI_POS_Y(0)}, "Min: Max: ", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X_RIGHT(7), UI_POS_Y(0)}, "LNA VGA", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(1), UI_POS_Y(2)}, "Trig: /255", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X_RIGHT(12), UI_POS_Y(2)}, "Mean: /255", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(1), UI_POS_Y(3)}, "Slices: /32", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X_RIGHT(10), UI_POS_Y(3)}, "Rate: Hz", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(6), UI_POS_Y(5)}, "Timer Status", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(1), 25 * 8}, "Accuracy +/-4.9kHz", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X_RIGHT(4), 25 * 8}, "MHz", Theme::getInstance()->fg_light->foreground}};
|
||||
|
||||
Checkbox check_log{
|
||||
{UI_POS_X_RIGHT(6), UI_POS_Y(5)},
|
||||
3,
|
||||
"LOG",
|
||||
true};
|
||||
|
||||
FrequencyField field_frequency_min{
|
||||
{1 * 8, 1 * 16}};
|
||||
{UI_POS_X(1), UI_POS_Y(1)}};
|
||||
FrequencyField field_frequency_max{
|
||||
{11 * 8, 1 * 16}};
|
||||
{UI_POS_X(11), UI_POS_Y(1)}};
|
||||
|
||||
LNAGainField field_lna{
|
||||
{22 * 8, 1 * 16}};
|
||||
{UI_POS_X_RIGHT(7), UI_POS_Y(1)}};
|
||||
VGAGainField field_vga{
|
||||
{26 * 8, 1 * 16}};
|
||||
{UI_POS_X_RIGHT(3), UI_POS_Y(1)}};
|
||||
|
||||
NumberField field_threshold{
|
||||
{6 * 8, 2 * 16},
|
||||
{UI_POS_X(6), UI_POS_Y(2)},
|
||||
3,
|
||||
{5, 255},
|
||||
5,
|
||||
' '};
|
||||
Text text_mean{
|
||||
{22 * 8, 2 * 16, 3 * 8, 16},
|
||||
{UI_POS_X_RIGHT(7), UI_POS_Y(2), UI_POS_WIDTH(3), UI_POS_HEIGHT(1)},
|
||||
"---"};
|
||||
Text text_slices{
|
||||
{8 * 8, 3 * 16, 2 * 8, 16},
|
||||
{UI_POS_X(8), UI_POS_Y(3), UI_POS_WIDTH(2), UI_POS_HEIGHT(1)},
|
||||
"--"};
|
||||
Text text_rate{
|
||||
{24 * 8, 3 * 16, 3 * 8, 16},
|
||||
{UI_POS_X_RIGHT(5), UI_POS_Y(3), UI_POS_WIDTH(3), UI_POS_HEIGHT(1)},
|
||||
"---"};
|
||||
|
||||
VuMeter vu_max{
|
||||
{1 * 8, 11 * 8 - 4, 3 * 8, 48},
|
||||
{UI_POS_X(1), 11 * 8 - 4, 3 * 8, 48},
|
||||
18,
|
||||
false};
|
||||
|
||||
ProgressBar progress_timers{
|
||||
{6 * 8, 12 * 8, 6 * 8, 16}};
|
||||
{UI_POS_X(6), UI_POS_Y(6), UI_POS_WIDTH(6), UI_POS_HEIGHT(1)}};
|
||||
Text text_infos{
|
||||
{13 * 8, 12 * 8, 15 * 8, 16},
|
||||
{UI_POS_X(13), UI_POS_Y(6), UI_POS_WIDTH(15), UI_POS_HEIGHT(1)},
|
||||
"Listening"};
|
||||
|
||||
Checkbox check_snap{
|
||||
{6 * 8, 15 * 8},
|
||||
{UI_POS_X(6), 15 * 8},
|
||||
7,
|
||||
"Snap to:",
|
||||
true};
|
||||
OptionsField options_snap{
|
||||
{17 * 8, 15 * 8}, // Position
|
||||
7, // Length
|
||||
{ // Options
|
||||
{UI_POS_X(17), 15 * 8}, // Position
|
||||
7, // Length
|
||||
{ // Options
|
||||
{"25kHz ", 25'000},
|
||||
{"12.5kHz", 12'500},
|
||||
{"8.33kHz", 8'333},
|
||||
{"2.5kHz", 2'500},
|
||||
{"500Hz", 500}}};
|
||||
|
||||
BigFrequency big_display{
|
||||
{4, 9 * 16, 28 * 8, 52},
|
||||
0};
|
||||
BigFrequency big_display{{UI_POS_X_CENTER(28), UI_POS_Y(9), UI_POS_WIDTH(28), 52}, 0};
|
||||
|
||||
MessageHandlerRegistration message_handler_spectrum_config{
|
||||
Message::ID::ChannelSpectrumConfig,
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* Copyright (C) 2024 Mark Thompson
|
||||
* Copyright (C) 2024 u-foka
|
||||
* Copyright (C) 2024 HTotoo
|
||||
* Copyleft (ɔ) 2024 zxkmm under GPL license
|
||||
* copyleft 2024 zxkmm
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -33,6 +33,9 @@
|
||||
#include "ui_touch_calibration.hpp"
|
||||
#include "ui_text_editor.hpp"
|
||||
#include "ui_external_items_menu_loader.hpp"
|
||||
#include "ui_ss_viewer.hpp"
|
||||
#include "ui_fileman.hpp"
|
||||
#include "ui_sd_card_debug.hpp"
|
||||
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
#include "lpc43xx_cpp.hpp"
|
||||
@@ -40,6 +43,7 @@ using namespace lpc43xx;
|
||||
|
||||
#include "audio.hpp"
|
||||
#include "portapack.hpp"
|
||||
#include "portapack_io.hpp"
|
||||
using namespace portapack;
|
||||
|
||||
#include "file.hpp"
|
||||
@@ -53,6 +57,8 @@ namespace fs = std::filesystem;
|
||||
#include "i2cdevmanager.hpp"
|
||||
#include "i2cdev_max17055.hpp"
|
||||
|
||||
#include "file_reader.hpp"
|
||||
|
||||
extern ui::SystemView* system_view_ptr;
|
||||
|
||||
namespace pmem = portapack::persistent_memory;
|
||||
@@ -306,13 +312,45 @@ SetFrequencyCorrectionModel SetRadioView::form_collect() {
|
||||
};
|
||||
}
|
||||
|
||||
/* SetTXLimitView ************************************/
|
||||
|
||||
SetTXLimitView::SetTXLimitView(NavigationView& nav) {
|
||||
add_children({
|
||||
&labels,
|
||||
&tx_gain_max_db,
|
||||
&tx_disable_switch,
|
||||
&tx_amp_disable_switch,
|
||||
&button_save,
|
||||
&button_cancel,
|
||||
});
|
||||
|
||||
tx_disable_switch.set_value(pmem::config_tx_disabled());
|
||||
tx_amp_disable_switch.set_value(pmem::config_tx_amp_disabled());
|
||||
tx_gain_max_db.set_value(pmem::config_tx_gain_max_db());
|
||||
|
||||
button_save.on_select = [&nav, this](Button&) {
|
||||
pmem::set_config_tx_disabled(tx_disable_switch.value());
|
||||
pmem::set_config_tx_amp_disabled(tx_amp_disable_switch.value());
|
||||
pmem::set_config_tx_gain_max_db(tx_gain_max_db.value());
|
||||
send_system_refresh();
|
||||
nav.pop();
|
||||
};
|
||||
|
||||
button_cancel.on_select = [&nav, this](Button&) {
|
||||
nav.pop();
|
||||
};
|
||||
}
|
||||
|
||||
void SetTXLimitView::focus() {
|
||||
button_save.focus();
|
||||
}
|
||||
|
||||
/* SetUIView *********************************************/
|
||||
|
||||
SetUIView::SetUIView(NavigationView& nav) {
|
||||
add_children({&checkbox_disable_touchscreen,
|
||||
&checkbox_bloff,
|
||||
&options_bloff,
|
||||
&checkbox_showsplash,
|
||||
&checkbox_showclock,
|
||||
&options_clockformat,
|
||||
&checkbox_guireturnflag,
|
||||
@@ -324,6 +362,7 @@ SetUIView::SetUIView(NavigationView& nav) {
|
||||
&toggle_bias_tee,
|
||||
&toggle_clock,
|
||||
&toggle_mute,
|
||||
&toggle_fake_brightness,
|
||||
&toggle_sd_card,
|
||||
&button_save,
|
||||
&button_cancel});
|
||||
@@ -338,7 +377,6 @@ SetUIView::SetUIView(NavigationView& nav) {
|
||||
}
|
||||
|
||||
checkbox_disable_touchscreen.set_value(pmem::disable_touchscreen());
|
||||
checkbox_showsplash.set_value(pmem::config_splash());
|
||||
checkbox_showclock.set_value(!pmem::hide_clock());
|
||||
checkbox_guireturnflag.set_value(pmem::show_gui_return_icon());
|
||||
|
||||
@@ -361,6 +399,7 @@ SetUIView::SetUIView(NavigationView& nav) {
|
||||
toggle_clock.set_value(!pmem::ui_hide_clock());
|
||||
toggle_speaker.set_value(!pmem::ui_hide_speaker());
|
||||
toggle_mute.set_value(!pmem::ui_hide_mute());
|
||||
toggle_fake_brightness.set_value(!pmem::ui_hide_fake_brightness());
|
||||
toggle_battery_icon.set_value(!pmem::ui_hide_battery_icon());
|
||||
toggle_battery_text.set_value(!pmem::ui_hide_numeric_battery());
|
||||
toggle_sd_card.set_value(!pmem::ui_hide_sd_card());
|
||||
@@ -376,7 +415,6 @@ SetUIView::SetUIView(NavigationView& nav) {
|
||||
pmem::set_clock_with_date(false);
|
||||
}
|
||||
|
||||
pmem::set_config_splash(checkbox_showsplash.value());
|
||||
pmem::set_clock_hidden(!checkbox_showclock.value());
|
||||
pmem::set_gui_return_icon(checkbox_guireturnflag.value());
|
||||
pmem::set_disable_touchscreen(checkbox_disable_touchscreen.value());
|
||||
@@ -389,6 +427,7 @@ SetUIView::SetUIView(NavigationView& nav) {
|
||||
pmem::set_ui_hide_clock(!toggle_clock.value());
|
||||
pmem::set_ui_hide_speaker(!toggle_speaker.value());
|
||||
pmem::set_ui_hide_mute(!toggle_mute.value());
|
||||
pmem::set_ui_hide_fake_brightness(!toggle_fake_brightness.value());
|
||||
pmem::set_ui_hide_battery_icon(!toggle_battery_icon.value());
|
||||
pmem::set_ui_hide_numeric_battery(!toggle_battery_text.value());
|
||||
pmem::set_ui_hide_sd_card(!toggle_sd_card.value());
|
||||
@@ -408,7 +447,11 @@ void SetUIView::focus() {
|
||||
/* SetSDCardView *********************************************/
|
||||
|
||||
SetSDCardView::SetSDCardView(NavigationView& nav) {
|
||||
add_children({&labels,
|
||||
add_children({&status_labels,
|
||||
&text_card_status,
|
||||
&text_filesystem_type,
|
||||
&button_more_info,
|
||||
&labels,
|
||||
&checkbox_sdcard_speed,
|
||||
&button_test_sdcard_high_speed,
|
||||
&text_sdcard_test_status,
|
||||
@@ -417,6 +460,10 @@ SetSDCardView::SetSDCardView(NavigationView& nav) {
|
||||
|
||||
checkbox_sdcard_speed.set_value(pmem::config_sdcard_high_speed_io());
|
||||
|
||||
button_more_info.on_select = [&nav, this](Button&) {
|
||||
nav.push<SDCardDebugView>();
|
||||
};
|
||||
|
||||
button_test_sdcard_high_speed.on_select = [&nav, this](Button&) {
|
||||
pmem::set_config_sdcard_high_speed_io(true, false);
|
||||
text_sdcard_test_status.set("!! HIGH SPEED MODE ON !!");
|
||||
@@ -434,7 +481,79 @@ SetSDCardView::SetSDCardView(NavigationView& nav) {
|
||||
}
|
||||
|
||||
void SetSDCardView::focus() {
|
||||
button_save.focus();
|
||||
button_cancel.focus();
|
||||
}
|
||||
|
||||
void SetSDCardView::on_show() {
|
||||
sd_card_status_signal_token = sd_card::status_signal += [this](const sd_card::Status) {
|
||||
update_sd_card_status();
|
||||
};
|
||||
|
||||
update_sd_card_status();
|
||||
}
|
||||
|
||||
void SetSDCardView::on_hide() {
|
||||
sd_card::status_signal -= sd_card_status_signal_token;
|
||||
}
|
||||
|
||||
void SetSDCardView::update_sd_card_status() {
|
||||
using sd_card::Status;
|
||||
|
||||
const auto status = sd_card::status();
|
||||
|
||||
// Update card status text
|
||||
switch (status) {
|
||||
case Status::NotPresent:
|
||||
text_card_status.set("Not Inserted");
|
||||
text_filesystem_type.set("---");
|
||||
break;
|
||||
case Status::Present:
|
||||
text_card_status.set("Inserted");
|
||||
text_filesystem_type.set("---");
|
||||
break;
|
||||
case Status::Mounted:
|
||||
text_card_status.set("Mounted");
|
||||
// Determine filesystem type
|
||||
{
|
||||
const auto fs_type = sd_card::fs.fs_type;
|
||||
std::string fs_name;
|
||||
switch (fs_type) {
|
||||
case FS_FAT12:
|
||||
fs_name = "FAT12";
|
||||
break;
|
||||
case FS_FAT16:
|
||||
fs_name = "FAT16";
|
||||
break;
|
||||
case FS_FAT32:
|
||||
fs_name = "FAT32";
|
||||
break;
|
||||
case FS_EXFAT:
|
||||
fs_name = "exFAT";
|
||||
break;
|
||||
default:
|
||||
fs_name = "Unknown";
|
||||
break;
|
||||
}
|
||||
text_filesystem_type.set(fs_name);
|
||||
}
|
||||
break;
|
||||
case Status::ConnectError:
|
||||
text_card_status.set("Connect Error");
|
||||
text_filesystem_type.set("---");
|
||||
break;
|
||||
case Status::MountError:
|
||||
text_card_status.set("Mount Error");
|
||||
text_filesystem_type.set("---");
|
||||
break;
|
||||
case Status::IOError:
|
||||
text_card_status.set("I/O Error");
|
||||
text_filesystem_type.set("---");
|
||||
break;
|
||||
default:
|
||||
text_card_status.set("Unknown");
|
||||
text_filesystem_type.set("---");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* SetConverterSettingsView ******************************/
|
||||
@@ -669,32 +788,6 @@ void SetAudioView::focus() {
|
||||
button_save.focus();
|
||||
}
|
||||
|
||||
/* SetQRCodeView *****************************************/
|
||||
|
||||
SetQRCodeView::SetQRCodeView(NavigationView& nav) {
|
||||
add_children({
|
||||
&labels,
|
||||
&checkbox_bigger_qr,
|
||||
&button_save,
|
||||
&button_cancel,
|
||||
});
|
||||
|
||||
checkbox_bigger_qr.set_value(pmem::show_bigger_qr_code());
|
||||
|
||||
button_save.on_select = [&nav, this](Button&) {
|
||||
pmem::set_show_bigger_qr_code(checkbox_bigger_qr.value());
|
||||
nav.pop();
|
||||
};
|
||||
|
||||
button_cancel.on_select = [&nav, this](Button&) {
|
||||
nav.pop();
|
||||
};
|
||||
}
|
||||
|
||||
void SetQRCodeView::focus() {
|
||||
button_save.focus();
|
||||
}
|
||||
|
||||
/* SetEncoderDialView ************************************/
|
||||
|
||||
SetEncoderDialView::SetEncoderDialView(NavigationView& nav) {
|
||||
@@ -702,14 +795,34 @@ SetEncoderDialView::SetEncoderDialView(NavigationView& nav) {
|
||||
&field_encoder_dial_sensitivity,
|
||||
&field_encoder_rate_multiplier,
|
||||
&button_save,
|
||||
&button_cancel});
|
||||
&button_cancel,
|
||||
&button_dial_sensitivity_plus,
|
||||
&button_dial_sensitivity_minus,
|
||||
&button_rate_multiplier_plus,
|
||||
&button_rate_multiplier_minus,
|
||||
&field_encoder_dial_direction});
|
||||
|
||||
field_encoder_dial_sensitivity.set_by_value(pmem::encoder_dial_sensitivity());
|
||||
field_encoder_rate_multiplier.set_value(pmem::encoder_rate_multiplier());
|
||||
field_encoder_dial_direction.set_by_value(pmem::encoder_dial_direction());
|
||||
|
||||
button_dial_sensitivity_plus.on_select = [this](Button&) {
|
||||
field_encoder_dial_sensitivity.on_encoder(1);
|
||||
};
|
||||
button_dial_sensitivity_minus.on_select = [this](Button&) {
|
||||
field_encoder_dial_sensitivity.on_encoder(-1);
|
||||
};
|
||||
button_rate_multiplier_plus.on_select = [this](Button&) {
|
||||
field_encoder_rate_multiplier.on_encoder(1);
|
||||
};
|
||||
button_rate_multiplier_minus.on_select = [this](Button&) {
|
||||
field_encoder_rate_multiplier.on_encoder(-1);
|
||||
};
|
||||
|
||||
button_save.on_select = [&nav, this](Button&) {
|
||||
pmem::set_encoder_dial_sensitivity(field_encoder_dial_sensitivity.selected_index_value());
|
||||
pmem::set_encoder_rate_multiplier(field_encoder_rate_multiplier.value());
|
||||
pmem::set_encoder_dial_direction(field_encoder_dial_direction.selected_index_value());
|
||||
nav.pop();
|
||||
};
|
||||
|
||||
@@ -722,6 +835,36 @@ void SetEncoderDialView::focus() {
|
||||
button_save.focus();
|
||||
}
|
||||
|
||||
/* SetButtonsView ************************************/
|
||||
|
||||
SetButtonsView::SetButtonsView(NavigationView& nav) {
|
||||
add_children({&labels,
|
||||
&button_save,
|
||||
&button_cancel,
|
||||
&field_repeat_delay,
|
||||
&field_repeat_speed,
|
||||
&field_long_press_delay});
|
||||
|
||||
field_repeat_delay.set_by_value(pmem::ui_button_repeat_delay());
|
||||
field_repeat_speed.set_by_value(pmem::ui_button_repeat_speed());
|
||||
field_long_press_delay.set_by_value(pmem::ui_button_long_press_delay());
|
||||
|
||||
button_save.on_select = [&nav, this](Button&) {
|
||||
pmem::set_ui_button_repeat_delay(field_repeat_delay.selected_index_value());
|
||||
pmem::set_ui_button_repeat_speed(field_repeat_speed.selected_index_value());
|
||||
pmem::set_ui_button_long_press_delay(field_long_press_delay.selected_index_value());
|
||||
nav.pop();
|
||||
};
|
||||
|
||||
button_cancel.on_select = [&nav, this](Button&) {
|
||||
nav.pop();
|
||||
};
|
||||
}
|
||||
|
||||
void SetButtonsView::focus() {
|
||||
button_save.focus();
|
||||
}
|
||||
|
||||
/* AppSettingsView ************************************/
|
||||
|
||||
AppSettingsView::AppSettingsView(
|
||||
@@ -730,7 +873,7 @@ AppSettingsView::AppSettingsView(
|
||||
add_children({&labels,
|
||||
&menu_view});
|
||||
|
||||
menu_view.set_parent_rect({0, 3 * 8, 240, 33 * 8});
|
||||
menu_view.set_parent_rect({0, 3 * 8, screen_width, UI_POS_HEIGHT_REMAINING(3)});
|
||||
|
||||
ensure_directory(settings_dir);
|
||||
|
||||
@@ -777,21 +920,38 @@ void SetConfigModeView::focus() {
|
||||
/* SetDisplayView ************************************/
|
||||
|
||||
SetDisplayView::SetDisplayView(NavigationView& nav) {
|
||||
add_children({&button_save,
|
||||
add_children({&labels,
|
||||
&field_fake_brightness,
|
||||
&button_save,
|
||||
&button_cancel,
|
||||
&checkbox_invert_switch});
|
||||
&checkbox_brightness_switch});
|
||||
|
||||
checkbox_invert_switch.set_value(pmem::config_lcd_inverted_mode());
|
||||
if (portapack::device_type == portapack::DeviceType::DEV_PORTAPACK) {
|
||||
add_child(&checkbox_ips_screen_switch);
|
||||
}
|
||||
|
||||
field_fake_brightness.set_by_value(pmem::fake_brightness_level());
|
||||
checkbox_brightness_switch.set_value(pmem::apply_fake_brightness());
|
||||
checkbox_ips_screen_switch.set_value(pmem::config_lcd_normally_black());
|
||||
|
||||
button_save.on_select = [&nav, this](Button&) {
|
||||
if (checkbox_invert_switch.value() != pmem::config_lcd_inverted_mode()) {
|
||||
display.set_inverted(checkbox_invert_switch.value());
|
||||
pmem::set_lcd_inverted_mode(checkbox_invert_switch.value());
|
||||
pmem::set_apply_fake_brightness(checkbox_brightness_switch.value());
|
||||
pmem::set_fake_brightness_level(field_fake_brightness.selected_index_value());
|
||||
if (checkbox_ips_screen_switch.value() != pmem::config_lcd_normally_black()) {
|
||||
pmem::set_lcd_normally_black(checkbox_ips_screen_switch.value());
|
||||
}
|
||||
send_system_refresh();
|
||||
nav.pop();
|
||||
};
|
||||
|
||||
// only enable invert OR fake brightness
|
||||
checkbox_ips_screen_switch.on_select = [this](Checkbox&, bool v) {
|
||||
if (v) checkbox_brightness_switch.set_value(false);
|
||||
};
|
||||
checkbox_brightness_switch.on_select = [this](Checkbox&, bool v) {
|
||||
if (v) checkbox_ips_screen_switch.set_value(false);
|
||||
};
|
||||
|
||||
button_cancel.on_select = [&nav, this](Button&) {
|
||||
nav.pop();
|
||||
};
|
||||
@@ -957,67 +1117,6 @@ void SetMenuColorView::focus() {
|
||||
button_save.focus();
|
||||
}
|
||||
|
||||
/* SetAutoStartView ************************************/
|
||||
|
||||
SetAutostartView::SetAutostartView(NavigationView& nav) {
|
||||
add_children({&labels,
|
||||
&button_save,
|
||||
&button_cancel,
|
||||
&button_reset,
|
||||
&options});
|
||||
|
||||
button_save.on_select = [&nav, this](Button&) {
|
||||
autostart_app = "";
|
||||
if (selected != 0) {
|
||||
auto it = full_app_list.find(selected);
|
||||
if (it != full_app_list.end())
|
||||
autostart_app = it->second;
|
||||
}
|
||||
nav.pop();
|
||||
};
|
||||
|
||||
button_cancel.on_select = [&nav, this](Button&) {
|
||||
nav.pop();
|
||||
};
|
||||
|
||||
button_reset.on_select = [this](Button&) {
|
||||
selected = 0;
|
||||
options.set_selected_index(0);
|
||||
autostart_app = "";
|
||||
};
|
||||
|
||||
// options
|
||||
i = 0;
|
||||
OptionsField::option_t o{"-none-", i};
|
||||
opts.emplace_back(o);
|
||||
for (auto& app : NavigationView::appList) {
|
||||
if (app.id == nullptr) continue;
|
||||
i++;
|
||||
o = {app.displayName, i};
|
||||
opts.emplace_back(o);
|
||||
full_app_list.emplace(i, app.id);
|
||||
if (autostart_app == app.id) selected = i;
|
||||
}
|
||||
ExternalItemsMenuLoader::load_all_external_items_callback([this](ui::AppInfoConsole& app) {
|
||||
if (app.appCallName == nullptr) return;
|
||||
i++;
|
||||
OptionsField::option_t o = {app.appFriendlyName, i};
|
||||
opts.emplace_back(o);
|
||||
full_app_list.emplace(i, app.appCallName);
|
||||
if (autostart_app == app.appCallName) selected = i;
|
||||
});
|
||||
|
||||
options.set_options(opts);
|
||||
options.on_change = [this](size_t, OptionsField::value_t v) {
|
||||
selected = v;
|
||||
};
|
||||
options.set_selected_index(selected);
|
||||
}
|
||||
|
||||
void SetAutostartView::focus() {
|
||||
options.focus();
|
||||
}
|
||||
|
||||
/* SetThemeView ************************************/
|
||||
|
||||
SetThemeView::SetThemeView(NavigationView& nav) {
|
||||
@@ -1061,26 +1160,62 @@ SetBatteryView::SetBatteryView(NavigationView& nav) {
|
||||
add_children({&labels,
|
||||
&button_save,
|
||||
&button_cancel,
|
||||
&checkbox_overridebatt});
|
||||
&field_battcap,
|
||||
&button_help_cap,
|
||||
&checkbox_overridebatt,
|
||||
&checkbox_battery_replaceable,
|
||||
&checkbox_battery_charge_hint});
|
||||
|
||||
if (i2cdev::I2CDevManager::get_dev_by_model(I2C_DEVMDL::I2CDEVMDL_MAX17055)) add_children({&button_reset, &labels2});
|
||||
|
||||
button_save.on_select = [&nav, this](Button&) {
|
||||
pmem::set_ui_override_batt_calc(checkbox_overridebatt.value());
|
||||
pmem::set_ui_battery_charge_hint(checkbox_battery_charge_hint.value());
|
||||
pmem::set_battery_replaceable(checkbox_battery_replaceable.value());
|
||||
battery::BatteryManagement::set_calc_override(checkbox_overridebatt.value());
|
||||
i2cdev::I2cDev_MAX17055* dev = (i2cdev::I2cDev_MAX17055*)i2cdev::I2CDevManager::get_dev_by_model(I2C_DEVMDL::I2CDEVMDL_MAX17055);
|
||||
if (dev) dev->resetChangedFlag();
|
||||
if ((((uint32_t)field_battcap.value() != pmem::battery_cap_mah()) || (!pmem::battery_cap_valid())) || (dev && dev->getIsBattChanged())) {
|
||||
pmem::set_battery_cap_mah(field_battcap.value());
|
||||
if (dev && !dev->reInit()) {
|
||||
nav.display_modal("Error", "Battery gauge re-init failed");
|
||||
return;
|
||||
}
|
||||
if (dev) {
|
||||
dev->resetChangedFlag();
|
||||
dev->update(); // resend the new data
|
||||
}
|
||||
}
|
||||
send_system_refresh();
|
||||
nav.pop();
|
||||
};
|
||||
|
||||
button_reset.on_select = [&nav, this](Button&) {
|
||||
auto dev = (i2cdev::I2cDev_MAX17055*)i2cdev::I2CDevManager::get_dev_by_model(I2C_DEVMDL::I2CDEVMDL_MAX17055);
|
||||
if (dev->reset_learned())
|
||||
if (dev && dev->reset_learned())
|
||||
nav.display_modal("Reset", "Battery parameters reset");
|
||||
else
|
||||
nav.display_modal("Error", "Error parameter reset");
|
||||
};
|
||||
|
||||
button_help_cap.on_select = [&nav, this](Button&) {
|
||||
nav.display_modal("Battery Capacity",
|
||||
"Only change default, if you\n"
|
||||
" changed the battery!\n"
|
||||
"Defaults:\n"
|
||||
"H4 + Hackrf One: 2500\n"
|
||||
"H4 + Hackrf Pro: 2000\n"
|
||||
"H4Pro + Hackrf Pro: custom\n"
|
||||
"PortaRf: 3000\n"
|
||||
|
||||
);
|
||||
};
|
||||
|
||||
checkbox_overridebatt.set_value(pmem::ui_override_batt_calc());
|
||||
checkbox_battery_charge_hint.set_value(pmem::ui_battery_charge_hint());
|
||||
checkbox_battery_replaceable.set_value(pmem::battery_replaceable());
|
||||
|
||||
field_battcap.set_value(pmem::battery_cap_mah());
|
||||
|
||||
button_cancel.on_select = [&nav, this](Button&) {
|
||||
nav.pop();
|
||||
@@ -1091,6 +1226,46 @@ void SetBatteryView::focus() {
|
||||
button_cancel.focus();
|
||||
}
|
||||
|
||||
/* SetSlpash *********************************************/
|
||||
|
||||
SetSplash::SetSplash(NavigationView& nav) {
|
||||
add_children({&checkbox_showsplash,
|
||||
&checkbox_randomsplash,
|
||||
&message,
|
||||
&button_picture_select,
|
||||
&button_save,
|
||||
&button_cancel});
|
||||
|
||||
checkbox_showsplash.set_value(pmem::config_splash());
|
||||
splash_bmp_exists = file_exists(splash_dot_bmp);
|
||||
checkbox_randomsplash.set_value(!splash_bmp_exists);
|
||||
message.hidden(splash_bmp_exists);
|
||||
|
||||
checkbox_randomsplash.on_select = [this](Checkbox&, bool v) {
|
||||
random_enabled = v;
|
||||
};
|
||||
|
||||
button_picture_select.on_select = [this, &nav](Button&) {
|
||||
auto ret = nav.push<FileManagerView>();
|
||||
ret->push_dir(splash_dir);
|
||||
};
|
||||
|
||||
button_save.on_select = [&nav, this](Button&) {
|
||||
if (random_enabled == true) delete_file(splash_dot_bmp);
|
||||
pmem::set_config_splash(checkbox_showsplash.value());
|
||||
send_system_refresh();
|
||||
nav.pop();
|
||||
};
|
||||
|
||||
button_cancel.on_select = [&nav, this](Button&) {
|
||||
nav.pop();
|
||||
};
|
||||
}
|
||||
|
||||
void SetSplash::focus() {
|
||||
button_save.focus();
|
||||
}
|
||||
|
||||
/* SettingsMenuView **************************************/
|
||||
|
||||
SettingsMenuView::SettingsMenuView(NavigationView& nav)
|
||||
@@ -1099,9 +1274,12 @@ SettingsMenuView::SettingsMenuView(NavigationView& nav)
|
||||
}
|
||||
|
||||
void SettingsMenuView::on_populate() {
|
||||
if (pmem::show_gui_return_icon()) {
|
||||
const bool return_icon = pmem::show_gui_return_icon();
|
||||
|
||||
if (return_icon) {
|
||||
add_items({{"..", ui::Color::light_grey(), &bitmap_icon_previous, [this]() { nav_.pop(); }}});
|
||||
}
|
||||
|
||||
add_items({
|
||||
{"App Settings", ui::Color::dark_cyan(), &bitmap_icon_notepad, [this]() { nav_.push<AppSettingsView>(); }},
|
||||
{"Audio", ui::Color::dark_cyan(), &bitmap_icon_speaker, [this]() { nav_.push<SetAudioView>(); }},
|
||||
@@ -1111,18 +1289,22 @@ void SettingsMenuView::on_populate() {
|
||||
{"Converter", ui::Color::dark_cyan(), &bitmap_icon_options_radio, [this]() { nav_.push<SetConverterSettingsView>(); }},
|
||||
{"Date/Time", ui::Color::dark_cyan(), &bitmap_icon_options_datetime, [this]() { nav_.push<SetDateTimeView>(); }},
|
||||
{"Encoder Dial", ui::Color::dark_cyan(), &bitmap_icon_setup, [this]() { nav_.push<SetEncoderDialView>(); }},
|
||||
{"Button Speed", ui::Color::dark_cyan(), &bitmap_icon_controls, [this]() { nav_.push<SetButtonsView>(); }},
|
||||
{"Freq. Correct", ui::Color::dark_cyan(), &bitmap_icon_options_radio, [this]() { nav_.push<SetFrequencyCorrectionView>(); }},
|
||||
{"P.Memory Mgmt", ui::Color::dark_cyan(), &bitmap_icon_memory, [this]() { nav_.push<SetPersistentMemoryView>(); }},
|
||||
{"Radio", ui::Color::dark_cyan(), &bitmap_icon_options_radio, [this]() { nav_.push<SetRadioView>(); }},
|
||||
{"TX Limit", ui::Color::dark_cyan(), &bitmap_icon_options_radio, [this]() { nav_.push<SetTXLimitView>(); }},
|
||||
{"SD Card", ui::Color::dark_cyan(), &bitmap_icon_sdcard, [this]() { nav_.push<SetSDCardView>(); }},
|
||||
{"User Interface", ui::Color::dark_cyan(), &bitmap_icon_options_ui, [this]() { nav_.push<SetUIView>(); }},
|
||||
//{"QR Code", ui::Color::dark_cyan(), &bitmap_icon_qr_code, [this]() { nav_.push<SetQRCodeView>(); }},
|
||||
{"Display", ui::Color::dark_cyan(), &bitmap_icon_brightness, [this]() { nav_.push<SetDisplayView>(); }},
|
||||
{"Menu Color", ui::Color::dark_cyan(), &bitmap_icon_brightness, [this]() { nav_.push<SetMenuColorView>(); }},
|
||||
{"Theme", ui::Color::dark_cyan(), &bitmap_icon_setup, [this]() { nav_.push<SetThemeView>(); }},
|
||||
{"Autostart", ui::Color::dark_cyan(), &bitmap_icon_setup, [this]() { nav_.push<SetAutostartView>(); }},
|
||||
{"Splash settings", ui::Color::dark_cyan(), &bitmap_icon_file_image, [this]() { nav_.push<SetSplash>(); }},
|
||||
});
|
||||
|
||||
if (battery::BatteryManagement::isDetected()) add_item({"Battery", ui::Color::dark_cyan(), &bitmap_icon_batt_icon, [this]() { nav_.push<SetBatteryView>(); }});
|
||||
|
||||
add_external_items(nav_, app_location_t::SETTINGS, *this, return_icon ? 1 : 0);
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* Copyright (C) 2023 Kyle Reed
|
||||
* Copyright (C) 2024 Mark Thompson
|
||||
* Copyright (C) 2024 u-foka
|
||||
* Copyleft (ɔ) 2024 zxkmm under GPL license
|
||||
* copyleft 2024 zxkmm
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -34,6 +34,7 @@
|
||||
#include "ui_navigation.hpp"
|
||||
#include "bitmap.hpp"
|
||||
#include "ff.h"
|
||||
#include "sd_card.hpp"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
#include "irq_controls.hpp"
|
||||
|
||||
@@ -72,8 +73,8 @@ class SetDateTimeView : public View {
|
||||
{{1 * 8, 5 * 16 - 2}, "YYYY-MM-DD HH:MM:SS DoW DoY", Theme::getInstance()->fg_medium->foreground},
|
||||
{{5 * 8, 6 * 16}, "- - : :", Theme::getInstance()->fg_light->foreground},
|
||||
{{1 * 8, 11 * 16}, "DST adds 1 hour to RTC time.", Theme::getInstance()->fg_light->foreground},
|
||||
{{0 * 8, 12 * 16}, "Start: 0:00 on Nth DDD in", Theme::getInstance()->fg_light->foreground},
|
||||
{{0 * 8, 13 * 16}, "End: 1:00 on Nth DDD in", Theme::getInstance()->fg_light->foreground}};
|
||||
{{UI_POS_X(0), 12 * 16}, "Start: 0:00 on Nth DDD in", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(0), 13 * 16}, "End: 1:00 on Nth DDD in", Theme::getInstance()->fg_light->foreground}};
|
||||
|
||||
NumberField field_year{
|
||||
{1 * 8, 6 * 16},
|
||||
@@ -170,10 +171,10 @@ class SetDateTimeView : public View {
|
||||
{}};
|
||||
|
||||
Button button_save{
|
||||
{2 * 8, 16 * 16, 12 * 8, 32},
|
||||
{UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), 12 * 8, 32},
|
||||
"Save"};
|
||||
Button button_cancel{
|
||||
{16 * 8, 16 * 16, 12 * 8, 32},
|
||||
{UI_POS_X_CENTER(12) + UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), 12 * 8, 32},
|
||||
"Cancel"};
|
||||
|
||||
void form_init(const SetDateTimeModel& model);
|
||||
@@ -253,10 +254,10 @@ class SetRadioView : public View {
|
||||
"Disable external TCXO"};
|
||||
|
||||
Button button_save{
|
||||
{2 * 8, 16 * 16, 12 * 8, 32},
|
||||
{UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), 12 * 8, 32},
|
||||
"Save"};
|
||||
Button button_cancel{
|
||||
{16 * 8, 16 * 16, 12 * 8, 32},
|
||||
{UI_POS_X_CENTER(12) + UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), 12 * 8, 32},
|
||||
"Cancel",
|
||||
};
|
||||
|
||||
@@ -264,6 +265,51 @@ class SetRadioView : public View {
|
||||
SetFrequencyCorrectionModel form_collect();
|
||||
};
|
||||
|
||||
class SetTXLimitView : public View {
|
||||
public:
|
||||
SetTXLimitView(NavigationView& nav);
|
||||
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override { return "TX Limit"; };
|
||||
|
||||
private:
|
||||
Labels labels{
|
||||
{{1 * 8, 1 * 16}, "Limits RF TX Gain", Theme::getInstance()->fg_light->foreground},
|
||||
{{1 * 8, 2 * 16}, "(This may affect", Theme::getInstance()->fg_light->foreground},
|
||||
{{1 * 8, 3 * 16}, "all applications.)", Theme::getInstance()->fg_light->foreground},
|
||||
{{2 * 8, 12 * 16}, "TX Max Gain:", Theme::getInstance()->fg_light->foreground},
|
||||
|
||||
};
|
||||
|
||||
Checkbox tx_disable_switch{
|
||||
{1 * 8, 6 * 16},
|
||||
23,
|
||||
"Disable TX"};
|
||||
|
||||
Checkbox tx_amp_disable_switch{
|
||||
{1 * 8, 8 * 16},
|
||||
23,
|
||||
"Disable TX Amp"};
|
||||
|
||||
NumberField tx_gain_max_db{
|
||||
{20 * 8, 12 * 16},
|
||||
6,
|
||||
{0, 47},
|
||||
1,
|
||||
' ',
|
||||
};
|
||||
|
||||
Button button_save{
|
||||
{UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)},
|
||||
"Save"};
|
||||
|
||||
Button button_cancel{
|
||||
{UI_POS_X_CENTER(16) + UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)},
|
||||
"Cancel",
|
||||
};
|
||||
};
|
||||
|
||||
using portapack::persistent_memory::backlight_timeout_t;
|
||||
|
||||
class SetUIView : public View {
|
||||
@@ -298,81 +344,80 @@ class SetUIView : public View {
|
||||
{"1 hour", backlight_timeout_t::Timeout3600Sec},
|
||||
}};
|
||||
|
||||
Checkbox checkbox_showsplash{
|
||||
{3 * 8, 6 * 16},
|
||||
20,
|
||||
"Show splash"};
|
||||
|
||||
Checkbox checkbox_showclock{
|
||||
{3 * 8, 8 * 16},
|
||||
{3 * 8, 6 * 16},
|
||||
20,
|
||||
"Show clock with:"};
|
||||
|
||||
OptionsField options_clockformat{
|
||||
{60, 9 * 16 + 8},
|
||||
{60, 7 * 16 + 8},
|
||||
20,
|
||||
{{"time only", 0},
|
||||
{"time and date", 1}}};
|
||||
|
||||
Checkbox checkbox_guireturnflag{
|
||||
{3 * 8, 11 * 16},
|
||||
{3 * 8, 9 * 16},
|
||||
20,
|
||||
"Back button in menu"};
|
||||
|
||||
Labels labels{
|
||||
{{3 * 8, 13 * 16}, "Show/Hide Status Icons", Theme::getInstance()->fg_light->foreground},
|
||||
{{3 * 8, 11 * 16}, "Show/Hide Status Icons", Theme::getInstance()->fg_light->foreground},
|
||||
};
|
||||
|
||||
ImageToggle toggle_camera{
|
||||
{2 * 8, 14 * 16 + 2, 16, 16},
|
||||
{2 * 8, 12 * 16 + 2, 16, 16},
|
||||
&bitmap_icon_camera};
|
||||
|
||||
ImageToggle toggle_sleep{
|
||||
{4 * 8, 14 * 16 + 2, 16, 16},
|
||||
{4 * 8, 12 * 16 + 2, 16, 16},
|
||||
&bitmap_icon_sleep};
|
||||
|
||||
ImageToggle toggle_stealth{
|
||||
{6 * 8, 14 * 16 + 2, 16, 16},
|
||||
{6 * 8, 12 * 16 + 2, 16, 16},
|
||||
&bitmap_icon_stealth};
|
||||
|
||||
ImageToggle toggle_converter{
|
||||
{8 * 8, 14 * 16 + 2, 16, 16},
|
||||
{8 * 8, 12 * 16 + 2, 16, 16},
|
||||
&bitmap_icon_upconvert};
|
||||
|
||||
ImageToggle toggle_bias_tee{
|
||||
{10 * 8, 14 * 16 + 2, 16, 16},
|
||||
{10 * 8, 12 * 16 + 2, 16, 16},
|
||||
&bitmap_icon_biast_off};
|
||||
|
||||
ImageToggle toggle_clock{
|
||||
{12 * 8, 14 * 16 + 2, 8, 16},
|
||||
{12 * 8, 12 * 16 + 2, 8, 16},
|
||||
&bitmap_icon_clk_ext};
|
||||
|
||||
ImageToggle toggle_mute{
|
||||
{13 * 8, 14 * 16 + 2, 16, 16},
|
||||
{13 * 8, 12 * 16 + 2, 16, 16},
|
||||
&bitmap_icon_speaker_and_headphones_mute};
|
||||
|
||||
ImageToggle toggle_speaker{
|
||||
{15 * 8, 14 * 16 + 2, 16, 16},
|
||||
{15 * 8, 12 * 16 + 2, 16, 16},
|
||||
&bitmap_icon_speaker_mute};
|
||||
|
||||
ImageToggle toggle_battery_icon{
|
||||
{17 * 8, 14 * 16 + 2, 16, 16},
|
||||
{17 * 8, 12 * 16 + 2, 16, 16},
|
||||
&bitmap_icon_batt_icon};
|
||||
|
||||
ImageToggle toggle_battery_text{
|
||||
{19 * 8, 14 * 16 + 2, 16, 16},
|
||||
{19 * 8, 12 * 16 + 2, 16, 16},
|
||||
&bitmap_icon_batt_text};
|
||||
|
||||
ImageToggle toggle_fake_brightness{
|
||||
{21 * 8, 12 * 16 + 2, 16, 16},
|
||||
&bitmap_icon_brightness};
|
||||
|
||||
ImageToggle toggle_sd_card{
|
||||
{21 * 8, 14 * 16 + 2, 16, 16},
|
||||
{23 * 8, 12 * 16 + 2, 16, 16},
|
||||
&bitmap_sd_card_ok};
|
||||
|
||||
Button button_save{
|
||||
{2 * 8, 16 * 16, 12 * 8, 32},
|
||||
{UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)},
|
||||
"Save"};
|
||||
|
||||
Button button_cancel{
|
||||
{16 * 8, 16 * 16, 12 * 8, 32},
|
||||
{UI_POS_X_CENTER(16) + UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)},
|
||||
"Cancel"};
|
||||
};
|
||||
|
||||
@@ -380,36 +425,60 @@ class SetSDCardView : public View {
|
||||
public:
|
||||
SetSDCardView(NavigationView& nav);
|
||||
|
||||
void on_show() override;
|
||||
void on_hide() override;
|
||||
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override { return "SD Card"; };
|
||||
|
||||
private:
|
||||
SignalToken sd_card_status_signal_token{};
|
||||
// Status section (top half)
|
||||
Labels status_labels{
|
||||
{{10, 24}, "Card Status:", Theme::getInstance()->fg_light->foreground},
|
||||
{{10, 48}, "Filesystem:", Theme::getInstance()->fg_light->foreground}};
|
||||
|
||||
Text text_card_status{
|
||||
{120, 24, 110, 16},
|
||||
""};
|
||||
|
||||
Text text_filesystem_type{
|
||||
{120, 48, 110, 16},
|
||||
""};
|
||||
|
||||
Button button_more_info{
|
||||
{UI_POS_X_CENTER(20), 90, UI_POS_WIDTH(20), UI_POS_HEIGHT(2)},
|
||||
"More Info"};
|
||||
|
||||
// Settings section (bottom half)
|
||||
Labels labels{
|
||||
// 01234567890123456789012345678
|
||||
{{1 * 8, 120 - 48}, " HIGH SPEED SDCARD IO ", Theme::getInstance()->fg_light->foreground},
|
||||
{{1 * 8, 120 - 32}, " May or may not work !! ", Theme::getInstance()->fg_light->foreground}};
|
||||
{{UI_POS_X_CENTER(26), 140}, " HIGH SPEED SDCARD IO ", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X_CENTER(26), 156}, " May or may not work !! ", Theme::getInstance()->fg_light->foreground}};
|
||||
|
||||
Checkbox checkbox_sdcard_speed{
|
||||
{2 * 8, 120},
|
||||
{UI_POS_X_CENTER(26), 180},
|
||||
20,
|
||||
"enable high speed IO"};
|
||||
|
||||
Button button_test_sdcard_high_speed{
|
||||
{2 * 8, 152, 27 * 8, 32},
|
||||
{UI_POS_X_CENTER(27), 210, UI_POS_WIDTH(27), UI_POS_HEIGHT(2)},
|
||||
"TEST BUTTON (NO PMEM SAVE)"};
|
||||
|
||||
Text text_sdcard_test_status{
|
||||
{2 * 8, 198, 28 * 8, 16},
|
||||
{UI_POS_X_CENTER(28), 256, UI_POS_WIDTH(28), UI_POS_HEIGHT(1)},
|
||||
""};
|
||||
|
||||
Button button_save{
|
||||
{2 * 8, 16 * 16, 12 * 8, 32},
|
||||
{UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), 12 * 8, 32},
|
||||
"Save"};
|
||||
|
||||
Button button_cancel{
|
||||
{16 * 8, 16 * 16, 12 * 8, 32},
|
||||
{UI_POS_X_CENTER(12) + UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), 12 * 8, 32},
|
||||
"Cancel"};
|
||||
|
||||
void update_sd_card_status();
|
||||
};
|
||||
|
||||
class SetConverterSettingsView : public View {
|
||||
@@ -451,7 +520,7 @@ class SetConverterSettingsView : public View {
|
||||
{8 * 8, 10 * 16}};
|
||||
|
||||
Button button_return{
|
||||
{16 * 8, 16 * 16, 12 * 8, 32},
|
||||
{UI_POS_X_CENTER(12), UI_POS_Y_BOTTOM(4), 12 * 8, 32},
|
||||
"Return",
|
||||
};
|
||||
};
|
||||
@@ -494,7 +563,7 @@ class SetFrequencyCorrectionView : public View {
|
||||
{8 * 8, 10 * 16}};
|
||||
|
||||
Button button_return{
|
||||
{16 * 8, 16 * 16, 12 * 8, 32},
|
||||
{UI_POS_X_CENTER(12), UI_POS_Y_BOTTOM(4), 12 * 8, UI_POS_HEIGHT(2)},
|
||||
"Return",
|
||||
};
|
||||
};
|
||||
@@ -532,44 +601,16 @@ class SetAudioView : public View {
|
||||
"Beep on RX packets"};
|
||||
|
||||
Button button_save{
|
||||
{2 * 8, 16 * 16, 12 * 8, 32},
|
||||
{UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), 12 * 8, 32},
|
||||
"Save"};
|
||||
|
||||
Button button_cancel{
|
||||
{16 * 8, 16 * 16, 12 * 8, 32},
|
||||
"Cancel",
|
||||
};
|
||||
};
|
||||
|
||||
class SetQRCodeView : public View {
|
||||
public:
|
||||
SetQRCodeView(NavigationView& nav);
|
||||
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override { return "QR Code"; };
|
||||
|
||||
private:
|
||||
Labels labels{
|
||||
{{1 * 8, 1 * 16}, "Change the size of the QR", Theme::getInstance()->fg_light->foreground},
|
||||
{{1 * 8, 2 * 16}, "code shown in Radiosonde.", Theme::getInstance()->fg_light->foreground},
|
||||
};
|
||||
|
||||
Checkbox checkbox_bigger_qr{
|
||||
{3 * 8, 4 * 16},
|
||||
20,
|
||||
"Show large QR code"};
|
||||
|
||||
Button button_save{
|
||||
{2 * 8, 16 * 16, 12 * 8, 32},
|
||||
"Save"};
|
||||
|
||||
Button button_cancel{
|
||||
{16 * 8, 16 * 16, 12 * 8, 32},
|
||||
{UI_POS_X_CENTER(12) + UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), 12 * 8, 32},
|
||||
"Cancel",
|
||||
};
|
||||
};
|
||||
|
||||
using portapack::persistent_memory::encoder_dial_direction;
|
||||
using portapack::persistent_memory::encoder_dial_sensitivity;
|
||||
using portapack::persistent_memory::encoder_rate_multiplier;
|
||||
|
||||
@@ -583,36 +624,101 @@ class SetEncoderDialView : public View {
|
||||
|
||||
private:
|
||||
Labels labels{
|
||||
{{1 * 8, 1 * 16}, "Adjusts sensitivity to dial", Theme::getInstance()->fg_light->foreground},
|
||||
{{1 * 8, 2 * 16}, "rotation position (number of", Theme::getInstance()->fg_light->foreground},
|
||||
{{1 * 8, 3 * 16}, "steps per full rotation):", Theme::getInstance()->fg_light->foreground},
|
||||
{{2 * 8, 5 * 16}, "Dial sensitivity:", Theme::getInstance()->fg_light->foreground},
|
||||
{{1 * 8, 8 * 16}, "Adjusts sensitivity to dial", Theme::getInstance()->fg_light->foreground},
|
||||
{{1 * 8, 9 * 16}, "rotation rate (default 1", Theme::getInstance()->fg_light->foreground},
|
||||
{{1 * 8, 10 * 16}, "means no rate dependency):", Theme::getInstance()->fg_light->foreground},
|
||||
{{3 * 8, 12 * 16}, "Rate multiplier:", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(0), UI_POS_Y(0)}, "Sensitivity to dial rotation", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(0), 1 * 16}, "position (x steps per 360):", Theme::getInstance()->fg_light->foreground},
|
||||
{{1 * 8, 3 * 16}, "Sensitivity:", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(0), 7 * 16}, "Rotation rate (default 1", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(0), 8 * 16}, "means no rate dependency):", Theme::getInstance()->fg_light->foreground},
|
||||
{{2 * 8, 10 * 16}, "Rate multiplier:", Theme::getInstance()->fg_light->foreground},
|
||||
{{4 * 8, 14 * 16}, "Direction:", Theme::getInstance()->fg_light->foreground},
|
||||
|
||||
};
|
||||
|
||||
OptionsField field_encoder_dial_sensitivity{
|
||||
{20 * 8, 5 * 16},
|
||||
{20 * 8, 3 * 16},
|
||||
6,
|
||||
{{"LOW", encoder_dial_sensitivity::DIAL_SENSITIVITY_LOW},
|
||||
{"NORMAL", encoder_dial_sensitivity::DIAL_SENSITIVITY_NORMAL},
|
||||
{"HIGH", encoder_dial_sensitivity::DIAL_SENSITIVITY_HIGH}}};
|
||||
|
||||
NumberField field_encoder_rate_multiplier{
|
||||
{20 * 8, 12 * 16},
|
||||
{20 * 8, 10 * 16},
|
||||
2,
|
||||
{1, 15},
|
||||
1,
|
||||
' '};
|
||||
|
||||
OptionsField field_encoder_dial_direction{
|
||||
{18 * 8, 14 * 16},
|
||||
7,
|
||||
{{"NORMAL", false},
|
||||
{"REVERSE", true}}};
|
||||
|
||||
Button button_dial_sensitivity_plus{
|
||||
{20 * 8, 2 * 16, 16, 16},
|
||||
"+"};
|
||||
|
||||
Button button_dial_sensitivity_minus{
|
||||
{20 * 8, 4 * 16, 16, 16},
|
||||
"-"};
|
||||
|
||||
Button button_rate_multiplier_plus{
|
||||
{20 * 8, 9 * 16, 16, 16},
|
||||
"+"};
|
||||
|
||||
Button button_rate_multiplier_minus{
|
||||
{20 * 8, 11 * 16, 16, 16},
|
||||
"-"};
|
||||
|
||||
Button button_save{
|
||||
{2 * 8, 16 * 16, 12 * 8, 32},
|
||||
{UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), 12 * 8, 32},
|
||||
"Save"};
|
||||
|
||||
Button button_cancel{
|
||||
{16 * 8, 16 * 16, 12 * 8, 32},
|
||||
{UI_POS_X_CENTER(12) + UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), 12 * 8, 32},
|
||||
"Cancel",
|
||||
};
|
||||
};
|
||||
|
||||
class SetButtonsView : public View {
|
||||
public:
|
||||
SetButtonsView(NavigationView& nav);
|
||||
void focus() override;
|
||||
std::string title() const override { return "Button Speed"; };
|
||||
|
||||
private:
|
||||
Labels labels{
|
||||
{{1 * 8, 1 * 16}, "Adjusts response time when a", Theme::getInstance()->fg_light->foreground},
|
||||
{{1 * 8, 2 * 16}, "button is held down.", Theme::getInstance()->fg_light->foreground},
|
||||
{{2 * 8, 5 * 16}, "Repeat delay:", Theme::getInstance()->fg_light->foreground},
|
||||
{{2 * 8, 7 * 16}, "Repeat speed:", Theme::getInstance()->fg_light->foreground},
|
||||
{{2 * 8, 9 * 16}, "Long press delay:", Theme::getInstance()->fg_light->foreground},
|
||||
};
|
||||
|
||||
OptionsField field_repeat_delay{
|
||||
{20 * 8, 5 * 16},
|
||||
6,
|
||||
{{"NORMAL", false},
|
||||
{"FAST", true}}};
|
||||
|
||||
OptionsField field_repeat_speed{
|
||||
{20 * 8, 7 * 16},
|
||||
6,
|
||||
{{"NORMAL", false},
|
||||
{"FAST", true}}};
|
||||
|
||||
OptionsField field_long_press_delay{
|
||||
{20 * 8, 9 * 16},
|
||||
6,
|
||||
{{"NORMAL", false},
|
||||
{"FAST", true}}};
|
||||
|
||||
Button button_save{
|
||||
{UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), 12 * 8, 32},
|
||||
"Save"};
|
||||
|
||||
Button button_cancel{
|
||||
{UI_POS_X_CENTER(12) + UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), 12 * 8, 32},
|
||||
"Cancel",
|
||||
};
|
||||
};
|
||||
@@ -633,7 +739,7 @@ class SetPersistentMemoryView : public View {
|
||||
};
|
||||
|
||||
Text text_pmem_status{
|
||||
{1 * 8, 4 * 16 + 8, 28 * 8, 16},
|
||||
{UI_POS_X_CENTER(28), 4 * 16 + 8, 28 * 8, 16},
|
||||
""};
|
||||
|
||||
Checkbox check_use_sdcard_for_pmem{
|
||||
@@ -642,19 +748,19 @@ class SetPersistentMemoryView : public View {
|
||||
"Use SD card for P.Mem"};
|
||||
|
||||
Button button_save_mem_to_file{
|
||||
{1 * 8, 8 * 16, 28 * 8, 2 * 16},
|
||||
{UI_POS_X_CENTER(28), 8 * 16, 28 * 8, 2 * 16},
|
||||
"Save P.Mem to SD card"};
|
||||
|
||||
Button button_load_mem_from_file{
|
||||
{1 * 8, 10 * 16 + 2, 28 * 8, 2 * 16},
|
||||
{UI_POS_X_CENTER(28), 10 * 16 + 2, 28 * 8, 2 * 16},
|
||||
"Load P.Mem from SD Card"};
|
||||
|
||||
Button button_load_mem_defaults{
|
||||
{1 * 8, 12 * 16 + 4, 28 * 8, 2 * 16},
|
||||
{UI_POS_X_CENTER(28), 12 * 16 + 4, 28 * 8, 2 * 16},
|
||||
"Reset P.Mem to defaults"};
|
||||
|
||||
Button button_return{
|
||||
{16 * 8, 16 * 16, 12 * 8, 32},
|
||||
{UI_POS_X_CENTER(12), UI_POS_Y_BOTTOM(4), 12 * 8, 32},
|
||||
"Return",
|
||||
};
|
||||
};
|
||||
@@ -672,7 +778,7 @@ class AppSettingsView : public View {
|
||||
{{0, 4}, "Select file to edit:", Theme::getInstance()->bg_darkest->foreground}};
|
||||
|
||||
MenuView menu_view{
|
||||
{0, 2 * 8, 240, 26 * 8},
|
||||
{0, 2 * 8, screen_width, UI_POS_HEIGHT_REMAINING(3)},
|
||||
true};
|
||||
};
|
||||
|
||||
@@ -697,14 +803,15 @@ class SetConfigModeView : public View {
|
||||
"Config Mode enable"};
|
||||
|
||||
Button button_save{
|
||||
{2 * 8, 16 * 16, 12 * 8, 32},
|
||||
{UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), 12 * 8, 32},
|
||||
"Save"};
|
||||
|
||||
Button button_cancel{
|
||||
{16 * 8, 16 * 16, 12 * 8, 32},
|
||||
{UI_POS_X_CENTER(12) + UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), 12 * 8, 32},
|
||||
"Cancel",
|
||||
};
|
||||
};
|
||||
using portapack::persistent_memory::fake_brightness_level_options;
|
||||
|
||||
class SetDisplayView : public View {
|
||||
public:
|
||||
@@ -715,17 +822,38 @@ class SetDisplayView : public View {
|
||||
std::string title() const override { return "Display"; };
|
||||
|
||||
private:
|
||||
Checkbox checkbox_invert_switch{
|
||||
{1 * 8, 2 * 16},
|
||||
Labels labels{
|
||||
{{1 * 8, 1 * 16}, "Limits screen brightness", Theme::getInstance()->fg_light->foreground},
|
||||
{{1 * 8, 2 * 16}, "(has a small performance", Theme::getInstance()->fg_light->foreground},
|
||||
{{1 * 8, 3 * 16}, "impact when enabled).", Theme::getInstance()->fg_light->foreground},
|
||||
{{2 * 8, 8 * 16}, "Brightness:", Theme::getInstance()->fg_light->foreground},
|
||||
{{2 * 8, 10 * 16}, "REBOOT TO APPLY SCREEN TYPE", Theme::getInstance()->fg_light->foreground},
|
||||
|
||||
};
|
||||
|
||||
OptionsField field_fake_brightness{
|
||||
{20 * 8, 8 * 16},
|
||||
6,
|
||||
{{"12.5%", fake_brightness_level_options::BRIGHTNESS_12p5},
|
||||
{"25%", fake_brightness_level_options::BRIGHTNESS_25},
|
||||
{"50%", fake_brightness_level_options::BRIGHTNESS_50}}};
|
||||
|
||||
Checkbox checkbox_brightness_switch{
|
||||
{1 * 8, 5 * 16},
|
||||
16,
|
||||
"Enable brightness adjust"};
|
||||
|
||||
Checkbox checkbox_ips_screen_switch{
|
||||
{1 * 8, 12 * 16},
|
||||
23,
|
||||
"Invert colors (For IPS)"};
|
||||
"IPS Screen"};
|
||||
|
||||
Button button_save{
|
||||
{2 * 8, 16 * 16, 12 * 8, 32},
|
||||
{UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)},
|
||||
"Save"};
|
||||
|
||||
Button button_cancel{
|
||||
{16 * 8, 16 * 16, 12 * 8, 32},
|
||||
{UI_POS_X_CENTER(16) + UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)},
|
||||
"Cancel",
|
||||
};
|
||||
};
|
||||
@@ -778,19 +906,19 @@ class SetTouchscreenThresholdView : public View {
|
||||
};
|
||||
|
||||
Button button_autodetect{
|
||||
{2 * 8, 13 * 16, 12 * 8, 32},
|
||||
{UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), 13 * 16, 12 * 8, 32},
|
||||
"Auto Detect"};
|
||||
Button button_reset{
|
||||
{16 * 8, 13 * 16, 12 * 8, 32},
|
||||
{UI_POS_X_CENTER(12) + UI_POS_WIDTH(8), 13 * 16, 12 * 8, 32},
|
||||
"Reset",
|
||||
};
|
||||
|
||||
Button button_save{
|
||||
{2 * 8, 16 * 16, 12 * 8, 32},
|
||||
{UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), 16 * 16, 12 * 8, 32},
|
||||
"Save"};
|
||||
|
||||
Button button_cancel{
|
||||
{16 * 8, 16 * 16, 12 * 8, 32},
|
||||
{UI_POS_X_CENTER(12) + UI_POS_WIDTH(8), 16 * 16, 12 * 8, 32},
|
||||
"Cancel",
|
||||
};
|
||||
|
||||
@@ -813,14 +941,14 @@ class SetMenuColorView : public View {
|
||||
void paint_sample();
|
||||
|
||||
Labels labels{
|
||||
{{3 * 8, 1 * 16}, "Menu Button Color Scheme", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X_CENTER(25), 1 * 16}, "Menu Button Color Scheme", Theme::getInstance()->fg_light->foreground},
|
||||
{{2 * 8, 8 * 16}, "Red Level:", Theme::getInstance()->fg_light->foreground},
|
||||
{{2 * 8, 9 * 16}, "Green Level:", Theme::getInstance()->fg_light->foreground},
|
||||
{{2 * 8, 10 * 16}, "Blue Level:", Theme::getInstance()->fg_light->foreground},
|
||||
};
|
||||
|
||||
NewButton button_sample{
|
||||
{8 * 8, 4 * 16, 14 * 8, 3 * 16},
|
||||
{UI_POS_X_CENTER(14), 4 * 16, 14 * 8, 3 * 16},
|
||||
"New Color",
|
||||
&bitmap_icon_brightness,
|
||||
};
|
||||
@@ -850,61 +978,20 @@ class SetMenuColorView : public View {
|
||||
};
|
||||
|
||||
Button button_reset{
|
||||
{2 * 8, 13 * 16, 12 * 8, 32},
|
||||
{UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(7), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)},
|
||||
"Reset",
|
||||
};
|
||||
|
||||
Button button_save{
|
||||
{2 * 8, 16 * 16, 12 * 8, 32},
|
||||
{UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)},
|
||||
"Save"};
|
||||
|
||||
Button button_cancel{
|
||||
{16 * 8, 16 * 16, 12 * 8, 32},
|
||||
{UI_POS_X_CENTER(12) + UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)},
|
||||
"Cancel",
|
||||
};
|
||||
};
|
||||
|
||||
class SetAutostartView : public View {
|
||||
public:
|
||||
SetAutostartView(NavigationView& nav);
|
||||
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override { return "Autostart"; };
|
||||
|
||||
private:
|
||||
int32_t i = 0;
|
||||
std::string autostart_app{""};
|
||||
OptionsField::options_t opts{};
|
||||
std::map<int32_t, std::string> full_app_list{}; // looking table
|
||||
int32_t selected = 0;
|
||||
SettingsStore nav_setting{
|
||||
"nav"sv,
|
||||
{{"autostart_app"sv, &autostart_app}}};
|
||||
Labels labels{
|
||||
{{1 * 8, 1 * 16}, "Select app to start on boot", Theme::getInstance()->fg_light->foreground},
|
||||
{{2 * 8, 2 * 16}, "(an SD Card is required)", Theme::getInstance()->fg_light->foreground}};
|
||||
|
||||
Button button_save{
|
||||
{2 * 8, 16 * 16, 12 * 8, 32},
|
||||
"Save"};
|
||||
|
||||
OptionsField options{
|
||||
{0 * 8, 4 * 16},
|
||||
screen_width / 8,
|
||||
{},
|
||||
true};
|
||||
|
||||
Button button_cancel{
|
||||
{16 * 8, 16 * 16, 12 * 8, 32},
|
||||
"Cancel",
|
||||
};
|
||||
|
||||
Button button_reset{
|
||||
{2 * 8, 6 * 16, screen_width - 4 * 8, 32},
|
||||
"Reset"};
|
||||
};
|
||||
|
||||
class SetThemeView : public View {
|
||||
public:
|
||||
SetThemeView(NavigationView& nav);
|
||||
@@ -919,19 +1006,16 @@ class SetThemeView : public View {
|
||||
{{1 * 8, 1 * 16}, "Select a theme.", Theme::getInstance()->fg_light->foreground},
|
||||
{{1 * 8, 2 * 16}, "Restart PP to fully apply!", Theme::getInstance()->fg_light->foreground}};
|
||||
|
||||
Button button_save{
|
||||
{2 * 8, 16 * 16, 12 * 8, 32},
|
||||
"Save"};
|
||||
|
||||
OptionsField options{
|
||||
{0 * 8, 4 * 16},
|
||||
screen_width / 8,
|
||||
{UI_POS_X(0), 4 * 16},
|
||||
(size_t)(screen_width / 8),
|
||||
{
|
||||
{"Default - Grey", 0},
|
||||
{"Yellow", 1},
|
||||
{"Aqua", 2},
|
||||
{"Green", 3},
|
||||
{"Red", 4},
|
||||
{"Dark", 5},
|
||||
},
|
||||
true};
|
||||
|
||||
@@ -940,8 +1024,12 @@ class SetThemeView : public View {
|
||||
23,
|
||||
"Set Menu color too"};
|
||||
|
||||
Button button_save{
|
||||
{UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)},
|
||||
"Save"};
|
||||
|
||||
Button button_cancel{
|
||||
{16 * 8, 16 * 16, 12 * 8, 32},
|
||||
{UI_POS_X_CENTER(12) + UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)},
|
||||
"Cancel",
|
||||
};
|
||||
};
|
||||
@@ -957,26 +1045,55 @@ class SetBatteryView : public View {
|
||||
private:
|
||||
int32_t selected = 0;
|
||||
Labels labels{
|
||||
{{1 * 8, 1 * 16}, "Override batt calculation", Theme::getInstance()->fg_light->foreground},
|
||||
{{1 * 8, 2 * 16}, "method to voltage based", Theme::getInstance()->fg_light->foreground}};
|
||||
Labels labels2{
|
||||
{{1 * 8, 6 * 16}, "Reset IC's learned params.", Theme::getInstance()->fg_light->foreground}};
|
||||
Button button_save{
|
||||
{2 * 8, 16 * 16, 12 * 8, 32},
|
||||
"Save"};
|
||||
{{UI_POS_X_CENTER(26), UI_POS_Y(0)}, "Override batt calculation", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X_CENTER(24), UI_POS_Y(1)}, "method to voltage based", Theme::getInstance()->fg_light->foreground},
|
||||
/**/
|
||||
{{UI_POS_X_CENTER(29), UI_POS_Y(4)}, "Display a hint to remind you", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X_CENTER(16), UI_POS_Y(5)}, "when you charge", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X_CENTER(17), UI_POS_Y(8)}, "Battery capacity", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(7), UI_POS_Y(9)}, "mAh", Theme::getInstance()->fg_light->foreground}};
|
||||
|
||||
Labels labels2{{{UI_POS_X(1), UI_POS_Y_BOTTOM(7)}, "Reset IC's learned params.", Theme::getInstance()->fg_light->foreground}};
|
||||
|
||||
NumberField field_battcap{
|
||||
{UI_POS_X(1), UI_POS_Y(9)},
|
||||
5,
|
||||
{BATT_18650_MIN_MAH, BATT_18650_MAX_MAH},
|
||||
100,
|
||||
' ',
|
||||
};
|
||||
|
||||
Button button_help_cap{
|
||||
{UI_POS_X(12), UI_POS_Y(9), UI_POS_WIDTH(5), UI_POS_HEIGHT(1)},
|
||||
"Help",
|
||||
};
|
||||
|
||||
Checkbox checkbox_overridebatt{
|
||||
{2 * 8, 4 * 16},
|
||||
{UI_POS_X(2), UI_POS_Y(2)},
|
||||
23,
|
||||
"Override"};
|
||||
|
||||
Checkbox checkbox_battery_charge_hint{
|
||||
{UI_POS_X(2), UI_POS_Y(6)},
|
||||
23,
|
||||
"Charge hint"};
|
||||
|
||||
Checkbox checkbox_battery_replaceable{
|
||||
{UI_POS_X(2), UI_POS_Y(11)},
|
||||
23,
|
||||
"Replaceable battery"};
|
||||
|
||||
Button button_cancel{
|
||||
{16 * 8, 16 * 16, 12 * 8, 32},
|
||||
{UI_POS_X_CENTER(12) + UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(3), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)},
|
||||
"Cancel",
|
||||
};
|
||||
|
||||
Button button_save{
|
||||
{UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(3), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)},
|
||||
"Save"};
|
||||
|
||||
Button button_reset{
|
||||
{2 * 8, 8 * 16, 12 * 8, 32},
|
||||
{UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(6), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)},
|
||||
"Reset",
|
||||
};
|
||||
};
|
||||
@@ -988,9 +1105,47 @@ class SettingsMenuView : public BtnGridView {
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
|
||||
void on_populate() override;
|
||||
};
|
||||
|
||||
class SetSplash : public View {
|
||||
public:
|
||||
SetSplash(NavigationView& nav);
|
||||
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override { return "Splash"; };
|
||||
|
||||
private:
|
||||
bool splash_bmp_exists = false;
|
||||
bool random_enabled = false;
|
||||
|
||||
Checkbox checkbox_showsplash{
|
||||
{3 * 8, 1 * 16},
|
||||
20,
|
||||
"Show splash"};
|
||||
|
||||
Checkbox checkbox_randomsplash{
|
||||
{3 * 8, 3 * 16},
|
||||
20,
|
||||
"Random splash on boot"};
|
||||
|
||||
Text message{{UI_POS_X(1), UI_POS_Y(6), UI_POS_WIDTH(26), UI_POS_HEIGHT(1)}, "Pick image to stop random."};
|
||||
|
||||
Button button_picture_select{
|
||||
{UI_POS_X_CENTER(8) - UI_POS_WIDTH(4), UI_POS_Y_BOTTOM(7), UI_POS_WIDTH(13), UI_POS_HEIGHT(2)},
|
||||
"Select pict."};
|
||||
|
||||
Button button_save{
|
||||
{UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)},
|
||||
"Save"};
|
||||
|
||||
Button button_cancel{
|
||||
{UI_POS_X_CENTER(16) + UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)},
|
||||
"Cancel"};
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
#endif /*__UI_SETTINGS_H__*/
|
||||
|
||||
@@ -57,7 +57,7 @@ void SIGFRXView::paint(Painter& painter) {
|
||||
uint8_t i, xp;
|
||||
|
||||
// portapack::display.draw_bmp_from_bmp_hex_arr({0, 302-160}, fox_bmp);
|
||||
portapack::display.fill_rectangle({0, 16, 240, 160 - 16}, Theme::getInstance()->bg_darkest->foreground);
|
||||
portapack::display.fill_rectangle({0, 16, screen_width, 160 - 16}, Theme::getInstance()->bg_darkest->foreground);
|
||||
for (i = 0; i < 6; i++) {
|
||||
xp = sigfrx_marks[i * 3];
|
||||
painter.draw_string({(ui::Coord)sigfrx_marks[(i * 3) + 1], 144 - 20}, style_white, to_string_dec_uint(sigfrx_marks[(i * 3) + 2]));
|
||||
@@ -66,7 +66,7 @@ void SIGFRXView::paint(Painter& painter) {
|
||||
}
|
||||
|
||||
void SIGFRXView::on_channel_spectrum(const ChannelSpectrum& spectrum) {
|
||||
portapack::display.fill_rectangle({0, 144, 240, 4}, Theme::getInstance()->bg_darkest->foreground);
|
||||
portapack::display.fill_rectangle({0, 144, screen_width, 4}, Theme::getInstance()->bg_darkest->foreground);
|
||||
|
||||
uint8_t xmax = 0, imax = 0;
|
||||
size_t i;
|
||||
|
||||
@@ -30,11 +30,10 @@
|
||||
#include "portapack.hpp"
|
||||
#include <cstring>
|
||||
#include <stdio.h>
|
||||
#include "rtc_time.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
namespace pmem = portapack::persistent_memory;
|
||||
|
||||
#include "string_format.hpp"
|
||||
#include "complex.hpp"
|
||||
|
||||
void SondeLogger::on_packet(const sonde::Packet& packet) {
|
||||
@@ -54,6 +53,7 @@ SondeView::SondeView(NavigationView& nav)
|
||||
&field_lna,
|
||||
&field_vga,
|
||||
&rssi,
|
||||
&channel,
|
||||
&field_volume,
|
||||
&check_log,
|
||||
&check_crc,
|
||||
@@ -64,6 +64,8 @@ SondeView::SondeView(NavigationView& nav)
|
||||
&text_frame,
|
||||
&text_temp,
|
||||
&text_humid,
|
||||
&text_press,
|
||||
&text_vspeed,
|
||||
&geopos,
|
||||
&button_see_qr,
|
||||
&button_see_map});
|
||||
@@ -106,7 +108,7 @@ SondeView::SondeView(NavigationView& nav)
|
||||
|
||||
logger = std::make_unique<SondeLogger>();
|
||||
if (logger)
|
||||
logger->append(logs_dir / u"SONDE.TXT");
|
||||
logger->append(logs_dir / u"SONDE_" + to_string_timestamp(rtc_time::now()) + u".TXT");
|
||||
|
||||
if (pmem::beep_on_packets()) {
|
||||
audio::set_rate(audio::Rate::Hz_24000);
|
||||
@@ -154,7 +156,7 @@ void SondeView::on_packet(const sonde::Packet& packet) {
|
||||
sonde_id = packet.serial_number(); // used also as tag on the geomap
|
||||
text_serial.set(sonde_id);
|
||||
|
||||
text_timestamp.set(to_string_timestamp(packet.received_at()));
|
||||
text_timestamp.set(to_string_datetime(packet.received_at(), TimeFormat::YMDHMS));
|
||||
|
||||
text_voltage.set(unit_auto_scale(packet.battery_voltage(), 2, 2) + "V");
|
||||
|
||||
@@ -162,21 +164,42 @@ void SondeView::on_packet(const sonde::Packet& packet) {
|
||||
|
||||
temp_humid_info = packet.get_temp_humid();
|
||||
if (temp_humid_info.humid != 0) {
|
||||
double decimals = abs(get_decimals(temp_humid_info.humid, 10, true));
|
||||
text_humid.set(to_string_dec_int((int)temp_humid_info.humid) + "." + to_string_dec_uint(decimals, 1) + "%");
|
||||
text_humid.set(to_string_decimal(temp_humid_info.humid, 1) + "%");
|
||||
}
|
||||
|
||||
if (temp_humid_info.temp != 0) {
|
||||
double decimals = abs(get_decimals(temp_humid_info.temp, 10, true));
|
||||
text_temp.set(to_string_dec_int((int)temp_humid_info.temp) + "." + to_string_dec_uint(decimals, 1) + STR_DEGREES_C);
|
||||
text_temp.set(to_string_decimal(temp_humid_info.temp, 1) + STR_DEGREES_C);
|
||||
}
|
||||
|
||||
if (packet.get_pressure() != 0) {
|
||||
text_press.set(to_string_decimal(packet.get_pressure(), 1) + " hPa");
|
||||
}
|
||||
|
||||
gps_info = packet.get_GPS_data();
|
||||
|
||||
geopos.set_altitude(gps_info.alt);
|
||||
geopos.set_lat(gps_info.lat);
|
||||
geopos.set_lon(gps_info.lon);
|
||||
|
||||
if (last_timestamp_update_ != 0 && last_altitude_ != 0) {
|
||||
// calculate speeds
|
||||
float vspeed = 0;
|
||||
time_t currpackettime = rtc_time::rtcToUnixUTC(packet.received_at());
|
||||
int32_t time_diff = (currpackettime - last_timestamp_update_);
|
||||
if (time_diff >= 10) { // update only every 10 seconds
|
||||
vspeed = (static_cast<int>(gps_info.alt) - static_cast<int>(last_altitude_)) / (float)time_diff;
|
||||
last_timestamp_update_ = currpackettime;
|
||||
last_altitude_ = gps_info.alt;
|
||||
text_vspeed.set(to_string_decimal(vspeed, 1) + " m/s");
|
||||
}
|
||||
} else { // save first valid packet time + altitude
|
||||
last_timestamp_update_ = rtc_time::rtcToUnixUTC(packet.received_at());
|
||||
last_altitude_ = geopos.altitude();
|
||||
}
|
||||
if (gps_info.is_valid()) { // only update when valid, to prevent flashing
|
||||
geopos.set_altitude(gps_info.alt);
|
||||
geopos.set_lat(gps_info.lat);
|
||||
geopos.set_lon(gps_info.lon);
|
||||
if (geomap_view_) {
|
||||
geomap_view_->update_position(gps_info.lat, gps_info.lon, 400, gps_info.alt, 0);
|
||||
}
|
||||
}
|
||||
if (logger && logging) {
|
||||
logger->on_packet(packet);
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include "ui_rssi.hpp"
|
||||
#include "ui_qrcode.hpp"
|
||||
#include "ui_geomap.hpp"
|
||||
#include "string_format.hpp"
|
||||
|
||||
#include "event_m0.hpp"
|
||||
|
||||
@@ -94,86 +95,100 @@ class SondeView : public View {
|
||||
// AudioOutput audio_output { };
|
||||
|
||||
Labels labels{
|
||||
{{4 * 8, 2 * 16}, "Type:", Theme::getInstance()->fg_light->foreground},
|
||||
{{6 * 8, 3 * 16}, "ID:", Theme::getInstance()->fg_light->foreground},
|
||||
{{0 * 8, 4 * 16}, "DateTime:", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(4), UI_POS_Y(2)}, "Type:", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(6), UI_POS_Y(3)}, "ID:", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(0), UI_POS_Y(4)}, "DateTime:", Theme::getInstance()->fg_light->foreground},
|
||||
|
||||
{{3 * 8, 5 * 16}, "Vbatt:", Theme::getInstance()->fg_light->foreground},
|
||||
{{3 * 8, 6 * 16}, "Frame:", Theme::getInstance()->fg_light->foreground},
|
||||
{{4 * 8, 7 * 16}, "Temp:", Theme::getInstance()->fg_light->foreground},
|
||||
{{0 * 8, 8 * 16}, "Humidity:", Theme::getInstance()->fg_light->foreground}};
|
||||
{{UI_POS_X(3), UI_POS_Y(5)}, "Vbatt:", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(3), UI_POS_Y(6)}, "Frame:", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(4), UI_POS_Y(7)}, "Temp:", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(0), UI_POS_Y(8)}, "Humidity:", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(0), UI_POS_Y(9)}, "Pressure:", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(2), UI_POS_Y(10)}, "VSpeed:", Theme::getInstance()->fg_light->foreground}};
|
||||
|
||||
RxFrequencyField field_frequency{
|
||||
{0 * 8, 0 * 8},
|
||||
{UI_POS_X(0), UI_POS_Y(0)},
|
||||
nav_};
|
||||
|
||||
RFAmpField field_rf_amp{
|
||||
{13 * 8, 0 * 16}};
|
||||
|
||||
{UI_POS_X(13), UI_POS_Y(0)}};
|
||||
LNAGainField field_lna{
|
||||
{15 * 8, 0 * 16}};
|
||||
{UI_POS_X(15), UI_POS_Y(0)}};
|
||||
|
||||
VGAGainField field_vga{
|
||||
{18 * 8, 0 * 16}};
|
||||
{UI_POS_X(18), UI_POS_Y(0)}};
|
||||
|
||||
RSSI rssi{
|
||||
{21 * 8, 0, 6 * 8, 4}};
|
||||
{UI_POS_X(21), UI_POS_Y(0), UI_POS_WIDTH_REMAINING(24), 4}};
|
||||
Channel channel{
|
||||
{UI_POS_X(21), UI_POS_Y(0) + 5, UI_POS_WIDTH_REMAINING(24), 4},
|
||||
};
|
||||
|
||||
AudioVolumeField field_volume{
|
||||
{28 * 8, 0 * 16}};
|
||||
{UI_POS_X_RIGHT(2), UI_POS_Y(0)}};
|
||||
|
||||
Checkbox check_log{
|
||||
{22 * 8, 8 * 16},
|
||||
{UI_POS_X_RIGHT(8), UI_POS_Y(8)},
|
||||
3,
|
||||
"Log"};
|
||||
|
||||
Checkbox check_crc{
|
||||
{22 * 8, 10 * 16},
|
||||
{UI_POS_X_RIGHT(8), UI_POS_Y(10)},
|
||||
3,
|
||||
"CRC"};
|
||||
|
||||
Text text_signature{
|
||||
{9 * 8, 2 * 16, 10 * 8, 16},
|
||||
{UI_POS_X(9), UI_POS_Y(2), UI_POS_WIDTH_REMAINING(10), UI_POS_HEIGHT(1)},
|
||||
"..."};
|
||||
|
||||
Text text_serial{
|
||||
{9 * 8, 3 * 16, 11 * 8, 16},
|
||||
{UI_POS_X(9), UI_POS_Y(3), UI_POS_WIDTH_REMAINING(10), UI_POS_HEIGHT(1)},
|
||||
"..."};
|
||||
|
||||
Text text_timestamp{
|
||||
{9 * 8, 4 * 16, 11 * 8, 16},
|
||||
{UI_POS_X(9), UI_POS_Y(4), UI_POS_WIDTH_REMAINING(9), UI_POS_HEIGHT(1)},
|
||||
"..."};
|
||||
|
||||
Text text_voltage{
|
||||
{9 * 8, 5 * 16, 10 * 8, 16},
|
||||
{UI_POS_X(9), UI_POS_Y(5), UI_POS_WIDTH(10), UI_POS_HEIGHT(1)},
|
||||
"..."};
|
||||
|
||||
Text text_frame{
|
||||
{9 * 8, 6 * 16, 10 * 8, 16},
|
||||
{UI_POS_X(9), UI_POS_Y(6), UI_POS_WIDTH(10), UI_POS_HEIGHT(1)},
|
||||
"..."};
|
||||
|
||||
Text text_temp{
|
||||
{9 * 8, 7 * 16, 10 * 8, 16},
|
||||
{UI_POS_X(9), UI_POS_Y(7), UI_POS_WIDTH(10), UI_POS_HEIGHT(1)},
|
||||
"..."};
|
||||
|
||||
Text text_humid{
|
||||
{9 * 8, 8 * 16, 10 * 8, 16},
|
||||
{UI_POS_X(9), UI_POS_Y(8), UI_POS_WIDTH(10), UI_POS_HEIGHT(1)},
|
||||
"..."};
|
||||
|
||||
Text text_press{
|
||||
{UI_POS_X(9), UI_POS_Y(9), UI_POS_WIDTH(10), UI_POS_HEIGHT(1)},
|
||||
"..."};
|
||||
|
||||
Text text_vspeed{
|
||||
{UI_POS_X(9), UI_POS_Y(10), UI_POS_WIDTH(10), UI_POS_HEIGHT(1)},
|
||||
"..."};
|
||||
|
||||
GeoPos geopos{
|
||||
{0, 12 * 16},
|
||||
{UI_POS_X(0), UI_POS_Y(12)},
|
||||
GeoPos::alt_unit::METERS,
|
||||
GeoPos::spd_unit::HIDDEN};
|
||||
|
||||
Button button_see_qr{
|
||||
{2 * 8, 15 * 16, 12 * 8, 3 * 16},
|
||||
{UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), UI_POS_WIDTH(12), UI_POS_HEIGHT(3)},
|
||||
"See QR"};
|
||||
|
||||
Button button_see_map{
|
||||
{16 * 8, 15 * 16, 12 * 8, 3 * 16},
|
||||
{UI_POS_X_CENTER(12) + UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), UI_POS_WIDTH(12), UI_POS_HEIGHT(3)},
|
||||
"See on map"};
|
||||
|
||||
GeoMapView* geomap_view_{nullptr};
|
||||
time_t last_timestamp_update_{0};
|
||||
uint32_t last_altitude_{0};
|
||||
|
||||
MessageHandlerRegistration message_handler_packet{
|
||||
Message::ID::SondePacket,
|
||||
|
||||
@@ -27,8 +27,6 @@ namespace fs = std::filesystem;
|
||||
|
||||
namespace ui {
|
||||
|
||||
const std::filesystem::path splash_dot_bmp{u"/splash.bmp"};
|
||||
|
||||
ScreenshotViewer::ScreenshotViewer(
|
||||
NavigationView& nav,
|
||||
const std::filesystem::path& path)
|
||||
@@ -44,7 +42,6 @@ bool ScreenshotViewer::on_key(KeyEvent) {
|
||||
|
||||
void ScreenshotViewer::paint(Painter& painter) {
|
||||
File file{};
|
||||
|
||||
painter.fill_rectangle({0, 0, screen_width, screen_height}, Color::black());
|
||||
|
||||
auto show_invalid = [&]() {
|
||||
@@ -58,7 +55,7 @@ void ScreenshotViewer::paint(Painter& painter) {
|
||||
}
|
||||
|
||||
// Screenshots from PNGWriter are all this size.
|
||||
if (file.size() != 232383) {
|
||||
if ((file.size() != 232383 && screen_width == 240) || (screen_width == 320 && file.size() != 463743)) {
|
||||
show_invalid();
|
||||
return;
|
||||
}
|
||||
@@ -66,7 +63,7 @@ void ScreenshotViewer::paint(Painter& painter) {
|
||||
constexpr size_t read_chunk = 80; // NB: must be a factor of pixel_width.
|
||||
constexpr size_t buffer_size = sizeof(ColorRGB888) * read_chunk;
|
||||
uint8_t buffer[buffer_size];
|
||||
std::array<Color, screen_width> pixel_data;
|
||||
std::vector<Color> pixel_data(screen_width);
|
||||
|
||||
// Seek past all the headers.
|
||||
file.seek(43);
|
||||
|
||||
@@ -28,11 +28,10 @@
|
||||
#include "ui_painter.hpp"
|
||||
#include "ui_widget.hpp"
|
||||
#include "file.hpp"
|
||||
#include "file_path.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
extern const std::filesystem::path splash_dot_bmp;
|
||||
|
||||
class ScreenshotViewer : public View {
|
||||
public:
|
||||
ScreenshotViewer(NavigationView& nav, const std::filesystem::path& path);
|
||||
@@ -49,7 +48,6 @@ class SplashViewer : public View {
|
||||
SplashViewer(NavigationView& nav, const std::filesystem::path& path);
|
||||
bool on_key(KeyEvent key) override;
|
||||
void paint(Painter& painter) override;
|
||||
void update_ss(void);
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
|
||||
@@ -27,11 +27,14 @@
|
||||
|
||||
#include "ui_font_fixed_5x8.hpp"
|
||||
#include "ui_font_fixed_8x16.hpp"
|
||||
#include "portapack.hpp"
|
||||
|
||||
#include "file.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
void create_thread(int32_t (*fn)(void*), void* arg, size_t stack_size, int priority) {
|
||||
// TODO: collect memory on terminate, once this is used
|
||||
// TODO: collect memory on terminate, once this is used. This is a HUGE TODO! need to call chThdWait on all of them!
|
||||
chThdCreateFromHeap(NULL, stack_size, priority, fn, arg);
|
||||
}
|
||||
|
||||
@@ -93,34 +96,146 @@ bool i2c_read(uint8_t* cmd, size_t cmd_len, uint8_t* data, size_t data_len) {
|
||||
return dev->i2c_read(cmd, cmd_len, data, data_len);
|
||||
}
|
||||
|
||||
// v3
|
||||
// Version 3
|
||||
FRESULT ext_f_open(FIL* fp, const TCHAR* path, BYTE mode) {
|
||||
return f_open(fp, path, mode);
|
||||
}
|
||||
FRESULT ext_f_close(FIL* fp) {
|
||||
return f_close(fp);
|
||||
}
|
||||
FRESULT ext_f_read(FIL* fp, void* buff, UINT btr, UINT* br) {
|
||||
return f_read(fp, buff, btr, br);
|
||||
}
|
||||
FRESULT ext_f_write(FIL* fp, const void* buff, UINT btw, UINT* bw) {
|
||||
return f_write(fp, buff, btw, bw);
|
||||
}
|
||||
FRESULT ext_f_lseek(FIL* fp, FSIZE_t ofs) {
|
||||
return f_lseek(fp, ofs);
|
||||
}
|
||||
FRESULT ext_f_truncate(FIL* fp) {
|
||||
return f_truncate(fp);
|
||||
}
|
||||
FRESULT ext_f_sync(FIL* fp) {
|
||||
return f_sync(fp);
|
||||
}
|
||||
FRESULT ext_f_opendir(DIR* dp, const TCHAR* path) {
|
||||
return f_opendir(dp, path);
|
||||
}
|
||||
FRESULT ext_f_closedir(DIR* dp) {
|
||||
return f_closedir(dp);
|
||||
}
|
||||
FRESULT ext_f_readdir(DIR* dp, FILINFO* fno) {
|
||||
return f_readdir(dp, fno);
|
||||
}
|
||||
FRESULT ext_f_findfirst(DIR* dp, FILINFO* fno, const TCHAR* path, const TCHAR* pattern) {
|
||||
return f_findfirst(dp, fno, path, pattern);
|
||||
}
|
||||
FRESULT ext_f_findnext(DIR* dp, FILINFO* fno) {
|
||||
return f_findnext(dp, fno);
|
||||
}
|
||||
FRESULT ext_f_mkdir(const TCHAR* path) {
|
||||
return f_mkdir(path);
|
||||
}
|
||||
FRESULT ext_f_unlink(const TCHAR* path) {
|
||||
return f_unlink(path);
|
||||
}
|
||||
FRESULT ext_f_rename(const TCHAR* path_old, const TCHAR* path_new) {
|
||||
return f_rename(path_old, path_new);
|
||||
}
|
||||
FRESULT ext_f_stat(const TCHAR* path, FILINFO* fno) {
|
||||
return f_stat(path, fno);
|
||||
}
|
||||
FRESULT ext_f_utime(const TCHAR* path, const FILINFO* fno) {
|
||||
return f_utime(path, fno);
|
||||
}
|
||||
FRESULT ext_f_getfree(const TCHAR* path, DWORD* nclst, FATFS** fatfs) {
|
||||
return f_getfree(path, nclst, fatfs);
|
||||
}
|
||||
FRESULT ext_f_mount(FATFS* fs, const TCHAR* path, BYTE opt) {
|
||||
return f_mount(fs, path, opt);
|
||||
}
|
||||
int ext_f_putc(TCHAR c, FIL* fp) {
|
||||
return f_putc(c, fp);
|
||||
}
|
||||
int ext_f_puts(const TCHAR* str, FIL* cp) {
|
||||
return f_puts(str, cp);
|
||||
}
|
||||
int ext_f_printf(FIL* fp, const TCHAR* str, ...) {
|
||||
return f_printf(fp, str);
|
||||
}
|
||||
TCHAR* ext_f_gets(TCHAR* buff, int len, FIL* fp) {
|
||||
return f_gets(buff, len, fp);
|
||||
}
|
||||
void ext_draw_pixels(const ui::Rect r, const ui::Color* const colors, const size_t count) {
|
||||
portapack::display.draw_pixels(r, colors, count);
|
||||
}
|
||||
void ext_draw_pixel(const ui::Point p, const ui::Color color) {
|
||||
portapack::display.draw_pixel(p, color);
|
||||
}
|
||||
|
||||
StandaloneView* standaloneView = nullptr;
|
||||
|
||||
void exit_app() {
|
||||
if (standaloneView) standaloneView->exit();
|
||||
}
|
||||
|
||||
void set_dirty() {
|
||||
if (standaloneView != nullptr)
|
||||
standaloneView->set_dirty();
|
||||
}
|
||||
|
||||
standalone_application_api_t api = {
|
||||
/* .malloc = */ &alloc,
|
||||
/* .calloc = */ &calloc,
|
||||
/* .realloc = */ &realloc,
|
||||
/* .free = */ &chHeapFree,
|
||||
/* .create_thread = */ &create_thread,
|
||||
/* .fill_rectangle = */ &fill_rectangle,
|
||||
/* .swizzled_switches = */ &swizzled_switches,
|
||||
/* .get_switches_state = */ &get_switches_state_ulong,
|
||||
/* .fixed_5x8_glyph_data = */ ui::font::fixed_5x8.get_data(),
|
||||
/* .fixed_8x16_glyph_data = */ ui::font::fixed_8x16.get_data(),
|
||||
/* .fill_rectangle_unrolled8 = */ &fill_rectangle_unrolled8,
|
||||
/* .draw_bitmap = */ &draw_bitmap,
|
||||
/* .scroll_area_y = */ &scroll_area_y,
|
||||
/* .scroll_set_area = */ &scroll_set_area,
|
||||
/* .scroll_disable = */ &scroll_disable,
|
||||
/* .scroll_set_position = */ &scroll_set_position,
|
||||
/* .scroll = */ &scroll,
|
||||
/* .i2c_read = */ &i2c_read,
|
||||
/* .panic = */ &chDbgPanic,
|
||||
/* .set_dirty = */ &set_dirty,
|
||||
.malloc = &alloc,
|
||||
.calloc = &calloc,
|
||||
.realloc = &realloc,
|
||||
.free = &chHeapFree,
|
||||
.create_thread = &create_thread,
|
||||
.fill_rectangle = &fill_rectangle,
|
||||
.swizzled_switches = &swizzled_switches,
|
||||
.get_switches_state = &get_switches_state_ulong,
|
||||
.fixed_5x8_glyph_data = ui::font::fixed_5x8.get_data(),
|
||||
.fixed_8x16_glyph_data = ui::font::fixed_8x16.get_data(),
|
||||
.fill_rectangle_unrolled8 = &fill_rectangle_unrolled8,
|
||||
.draw_bitmap = &draw_bitmap,
|
||||
.scroll_area_y = &scroll_area_y,
|
||||
.scroll_set_area = &scroll_set_area,
|
||||
.scroll_disable = &scroll_disable,
|
||||
.scroll_set_position = &scroll_set_position,
|
||||
.scroll = &scroll,
|
||||
.i2c_read = &i2c_read,
|
||||
.panic = &chDbgPanic,
|
||||
.set_dirty = &set_dirty,
|
||||
// Version 3
|
||||
.f_open = &ext_f_open,
|
||||
.f_close = &ext_f_close,
|
||||
.f_read = &ext_f_read,
|
||||
.f_write = &ext_f_write,
|
||||
.f_lseek = &ext_f_lseek,
|
||||
.f_truncate = &ext_f_truncate,
|
||||
.f_sync = &ext_f_sync,
|
||||
.f_opendir = &ext_f_opendir,
|
||||
.f_closedir = &ext_f_closedir,
|
||||
.f_readdir = &ext_f_readdir,
|
||||
.f_findfirst = &ext_f_findfirst,
|
||||
.f_findnext = &ext_f_findnext,
|
||||
.f_mkdir = &ext_f_mkdir,
|
||||
.f_unlink = &ext_f_unlink,
|
||||
.f_rename = &ext_f_rename,
|
||||
.f_stat = &ext_f_stat,
|
||||
.f_utime = &ext_f_utime,
|
||||
.f_getfree = &ext_f_getfree,
|
||||
.f_mount = &ext_f_mount,
|
||||
.f_putc = &ext_f_putc,
|
||||
.f_puts = &ext_f_puts,
|
||||
.f_printf = &ext_f_printf,
|
||||
.f_gets = &ext_f_gets,
|
||||
.draw_pixels = &ext_draw_pixels,
|
||||
.draw_pixel = &ext_draw_pixel,
|
||||
.exit_app = &exit_app,
|
||||
// version 4
|
||||
.screen_height = &screen_height,
|
||||
.screen_width = &screen_width,
|
||||
};
|
||||
|
||||
StandaloneView::StandaloneView(NavigationView& nav, uint8_t* app_image)
|
||||
@@ -158,22 +273,21 @@ bool StandaloneView::on_key(const KeyEvent key) {
|
||||
if (get_application_information()->header_version > 1) {
|
||||
return get_application_information()->OnKeyEvent((uint8_t)key);
|
||||
}
|
||||
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool StandaloneView::on_encoder(const EncoderEvent event) {
|
||||
if (get_application_information()->header_version > 1) {
|
||||
return get_application_information()->OnEncoder((int32_t)event);
|
||||
}
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool StandaloneView::on_touch(const TouchEvent event) {
|
||||
if (get_application_information()->header_version > 1) {
|
||||
get_application_information()->OnTouchEvent(event.point.x(), event.point.y(), (uint32_t)event.type);
|
||||
return get_application_information()->OnTouchEvent(event.point.x(), event.point.y(), (uint32_t)event.type);
|
||||
}
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool StandaloneView::on_keyboard(const KeyboardEvent event) {
|
||||
@@ -202,4 +316,8 @@ void StandaloneView::on_before_detach() {
|
||||
context().focus_manager().clearMirror();
|
||||
}
|
||||
|
||||
void StandaloneView::exit() {
|
||||
nav_.pop();
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
|
||||
@@ -51,6 +51,8 @@ class StandaloneView : public View {
|
||||
|
||||
void frame_sync();
|
||||
|
||||
void exit();
|
||||
|
||||
private:
|
||||
bool initialized = false;
|
||||
NavigationView& nav_;
|
||||
|
||||
@@ -25,6 +25,10 @@
|
||||
#include "baseband_api.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "file_path.hpp"
|
||||
#include "ui_textentry.hpp"
|
||||
#include "../keeloq_keystore.hpp"
|
||||
#include "../keeloq_file.hpp"
|
||||
#include "../keeloq_common.hpp"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
@@ -56,6 +60,51 @@ void SubGhzDRecentEntryDetailView::update_data() {
|
||||
if (cnt != SD_NO_CNT) console.writeln("Cnt: " + to_string_dec_uint(cnt));
|
||||
|
||||
if (entry_.data != 0) console.writeln("Data: " + to_string_hex(entry_.data));
|
||||
|
||||
if (entry_.sensorType == FPS_RESTAURANT_PAGER) {
|
||||
uint8_t pager_addr = (entry_.data >> 5) & 0x0F;
|
||||
uint8_t func_code = (entry_.data >> 1) & 0x0F;
|
||||
console.writeln("Station: 0x" + to_string_hex(serial) + " (" + to_string_dec_uint(serial) + ")");
|
||||
console.writeln("Pager: " + to_string_dec_uint(pager_addr));
|
||||
if (func_code == 0x0D)
|
||||
console.writeln("Action: Buzz");
|
||||
else if (func_code == 0x0F)
|
||||
console.writeln("Action: Sync");
|
||||
else
|
||||
console.writeln("Action: 0x" + to_string_hex(func_code));
|
||||
}
|
||||
|
||||
if (entry_.sensorType == FPS_KEELOQ) {
|
||||
console.writeln("Fix: " + to_string_hex(fix));
|
||||
console.writeln("Encrypted: " + to_string_hex(encrypted));
|
||||
console.writeln("Manufacturer: " + mf_name);
|
||||
|
||||
if (hop != SD_NO_HOP) {
|
||||
console.writeln("Hop: " + to_string_hex(hop));
|
||||
|
||||
add_children({&button_save});
|
||||
|
||||
button_save.on_select = [this](const Button&) {
|
||||
keeloq_file_buffer.clear();
|
||||
|
||||
text_prompt(
|
||||
nav_,
|
||||
keeloq_file_buffer,
|
||||
64,
|
||||
ENTER_KEYBOARD_MODE_ALPHA,
|
||||
[this](std::string& buffer) {
|
||||
KeeloqData params{
|
||||
mf_name,
|
||||
serial,
|
||||
(uint16_t)cnt,
|
||||
btn};
|
||||
|
||||
ensure_directory(keeloq_remotes_dir);
|
||||
write_keeloq_file(keeloq_remotes_dir / buffer + ".KEELOQ", params);
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SubGhzDRecentEntryDetailView::SubGhzDRecentEntryDetailView(NavigationView& nav, const SubGhzDRecentEntry& entry)
|
||||
@@ -84,6 +133,7 @@ void SubGhzDView::focus() {
|
||||
SubGhzDView::SubGhzDView(NavigationView& nav)
|
||||
: nav_{nav} {
|
||||
add_children({&rssi,
|
||||
&channel,
|
||||
&field_rf_amp,
|
||||
&field_lna,
|
||||
&field_vga,
|
||||
@@ -241,6 +291,10 @@ const char* SubGhzDView::getSensorTypeName(FPROTO_SUBGHZD_SENSOR type) {
|
||||
return "GangQi";
|
||||
case FPS_MARANTEC24:
|
||||
return "Marantec24";
|
||||
case FPS_HOLTEKHT6P20B:
|
||||
return "Holtek HT6P20B";
|
||||
case FPS_RESTAURANT_PAGER:
|
||||
return "Rest. Pager";
|
||||
case FPS_Invalid:
|
||||
default:
|
||||
return "Unknown";
|
||||
@@ -261,17 +315,21 @@ void RecentEntriesTable<ui::SubGhzDRecentEntries>::draw(
|
||||
const Entry& entry,
|
||||
const Rect& target_rect,
|
||||
Painter& painter,
|
||||
const Style& style) {
|
||||
const Style& style,
|
||||
RecentEntriesColumns& columns) {
|
||||
std::string line{};
|
||||
line.reserve(30);
|
||||
|
||||
line = SubGhzDView::getSensorTypeName((FPROTO_SUBGHZD_SENSOR)entry.sensorType);
|
||||
line = line + " " + to_string_hex(entry.data << 32);
|
||||
if (line.length() < 19) {
|
||||
line += SubGhzDView::pad_string_with_spaces(19 - line.length());
|
||||
if (entry.sensorType == FPS_RESTAURANT_PAGER) {
|
||||
uint8_t pgr = (entry.data >> 5) & 0x0F;
|
||||
uint8_t func = (entry.data >> 1) & 0x0F;
|
||||
line += " P:" + to_string_dec_uint(pgr) + (func == 0x0D ? " Buzz" : func == 0x0F ? " Sync"
|
||||
: "");
|
||||
} else {
|
||||
line = truncate(line, 19);
|
||||
line = line + " " + to_string_hex(entry.data << 32);
|
||||
}
|
||||
line.resize(columns.at(0).second, ' ');
|
||||
std::string ageStr = to_string_dec_uint(entry.age);
|
||||
std::string bitsStr = to_string_dec_uint(entry.bits);
|
||||
line += SubGhzDView::pad_string_with_spaces(5 - bitsStr.length()) + bitsStr;
|
||||
@@ -303,6 +361,29 @@ void atomo_decrypt(uint8_t* buff) {
|
||||
}
|
||||
}
|
||||
|
||||
bool SubGhzDRecentEntryDetailView::keeloq_check_decrypt(uint32_t decrypt) {
|
||||
uint16_t end_serial = serial & 0xFF;
|
||||
|
||||
if ((decrypt >> 28 == btn) && (((((uint16_t)(decrypt >> 16)) & 0xFF) == end_serial) ||
|
||||
((((uint16_t)(decrypt >> 16)) & 0xFF) == 0))) {
|
||||
cnt = decrypt & 0xFFFF;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SubGhzDRecentEntryDetailView::keeloq_check_decrypt_centurion(uint32_t decrypt) {
|
||||
if ((decrypt >> 28 == btn) && ((((uint16_t)(decrypt >> 16)) & 0x3FF) == 0x1CE)) {
|
||||
cnt = decrypt & 0xFFFF;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint32_t came_twee_magic_numbers_xor[15] = {
|
||||
0x0E0E0E00,
|
||||
0x1D1D1D11,
|
||||
@@ -341,7 +422,7 @@ void SubGhzDRecentEntryDetailView::parseProtocol() {
|
||||
if (entry_.sensorType == FPS_CAMEATOMO) {
|
||||
entry_.data ^= 0xFFFFFFFFFFFFFFFF;
|
||||
entry_.data <<= 4;
|
||||
uint8_t pack[8] = {};
|
||||
uint8_t pack[8];
|
||||
pack[0] = (entry_.data >> 56);
|
||||
pack[1] = ((entry_.data >> 48) & 0xFF);
|
||||
pack[2] = ((entry_.data >> 40) & 0xFF);
|
||||
@@ -591,7 +672,57 @@ void SubGhzDRecentEntryDetailView::parseProtocol() {
|
||||
}
|
||||
|
||||
if (entry_.sensorType == FPS_KEELOQ) {
|
||||
// too many sub protocol versions, skipping. maybe in future when we'll have much more fw space
|
||||
uint64_t data_rev = FProtoGeneral::subghz_protocol_blocks_reverse_key(entry_.data, 64);
|
||||
|
||||
btn = data_rev >> 60;
|
||||
serial = (data_rev >> 32) & 0xFFFFFFF;
|
||||
fix = data_rev >> 32;
|
||||
encrypted = data_rev & 0xFFFFFFFF;
|
||||
|
||||
KeeloqKeystore keystore{};
|
||||
|
||||
const auto& keys = keystore.get_keys();
|
||||
|
||||
if (keys.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto& key : keys) {
|
||||
switch (key.type) {
|
||||
case KEELOQ_SIMPLE_LEARNING: {
|
||||
uint32_t decrypted = keeloq_decrypt(encrypted, key.key);
|
||||
|
||||
if (keeloq_check_decrypt(decrypted)) {
|
||||
mf_name = key.mf_name;
|
||||
hop = decrypted;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case KEELOQ_NORMAL_LEARNING: {
|
||||
uint64_t man = keeloq_normal_learning(fix, key.key);
|
||||
uint32_t decrypted = keeloq_decrypt(encrypted, man);
|
||||
|
||||
if (key.mf_name == "Centurion" && keeloq_check_decrypt_centurion(decrypted)) {
|
||||
mf_name = "Centurion";
|
||||
hop = decrypted;
|
||||
|
||||
return;
|
||||
} else if (keeloq_check_decrypt(decrypted)) {
|
||||
mf_name = key.mf_name;
|
||||
hop = decrypted;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -758,5 +889,18 @@ void SubGhzDRecentEntryDetailView::parseProtocol() {
|
||||
btn = entry_.data & 0xf;
|
||||
return;
|
||||
}
|
||||
|
||||
if (entry_.sensorType == FPS_HOLTEKHT6P20B) {
|
||||
serial = entry_.data >> 8;
|
||||
btn = (entry_.data >> 4) & 0xF;
|
||||
return;
|
||||
}
|
||||
|
||||
if (entry_.sensorType == FPS_RESTAURANT_PAGER) {
|
||||
// 25-bit EV1527-variant: [sysid:16][pager:4][func:4][stop:1]
|
||||
serial = (entry_.data >> 9) & 0xFFFF; // System ID
|
||||
// btn/cnt left as SD_NO values; friendly output in update_data()
|
||||
return;
|
||||
}
|
||||
}
|
||||
} // namespace ui
|
||||
} // namespace ui
|
||||
|
||||
@@ -25,7 +25,11 @@
|
||||
|
||||
#define SD_NO_SERIAL 0xFFFFFFFF
|
||||
#define SD_NO_BTN 0xFF
|
||||
#define SD_NO_CNT 0xFF
|
||||
#define SD_NO_CNT 0xFFFFFFFF
|
||||
|
||||
#define SD_NO_HOP 0xFFFFFFFF
|
||||
#define SD_NO_FIX 0xFFFFFFFF
|
||||
#define SD_NO_ENCRYPTED 0xFFFFFFFF
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
@@ -122,15 +126,18 @@ class SubGhzDView : public View {
|
||||
SubGhzDRecentEntries recent{};
|
||||
|
||||
RFAmpField field_rf_amp{
|
||||
{13 * 8, 0 * 16}};
|
||||
{13 * 8, UI_POS_Y(0)}};
|
||||
LNAGainField field_lna{
|
||||
{15 * 8, 0 * 16}};
|
||||
{15 * 8, UI_POS_Y(0)}};
|
||||
VGAGainField field_vga{
|
||||
{18 * 8, 0 * 16}};
|
||||
{18 * 8, UI_POS_Y(0)}};
|
||||
RSSI rssi{
|
||||
{21 * 8, 0, 6 * 8, 4}};
|
||||
{21 * 8, 0, UI_POS_WIDTH_REMAINING(24), 4}};
|
||||
Channel channel{
|
||||
{21 * 8, 5, UI_POS_WIDTH_REMAINING(24), 4},
|
||||
};
|
||||
RxFrequencyField field_frequency{
|
||||
{0 * 8, 0 * 16},
|
||||
{UI_POS_X(0), UI_POS_Y(0)},
|
||||
nav_};
|
||||
|
||||
SignalToken signal_token_tick_second{};
|
||||
@@ -149,8 +156,8 @@ class SubGhzDView : public View {
|
||||
|
||||
std::unique_ptr<SubGhzDLogger> logger{};
|
||||
|
||||
const RecentEntriesColumns columns{{
|
||||
{"Type", 19},
|
||||
RecentEntriesColumns columns{{
|
||||
{"Type", 0},
|
||||
{"Bits", 4},
|
||||
{"Age", 3},
|
||||
}};
|
||||
@@ -188,22 +195,39 @@ class SubGhzDRecentEntryDetailView : public View {
|
||||
uint32_t cnt = SD_NO_CNT;
|
||||
uint32_t seed = 0;
|
||||
|
||||
Text text_type{{0 * 8, 1 * 16, 15 * 8, 16}, "?"};
|
||||
// keeloq specific
|
||||
std::string keeloq_file_buffer{};
|
||||
|
||||
uint32_t hop = SD_NO_HOP;
|
||||
uint32_t fix = SD_NO_FIX;
|
||||
uint32_t encrypted = SD_NO_ENCRYPTED;
|
||||
|
||||
std::string mf_name = "Unknown";
|
||||
|
||||
bool keeloq_check_decrypt(uint32_t decrypt);
|
||||
bool keeloq_check_decrypt_centurion(uint32_t decrypt);
|
||||
// end keeloq specific
|
||||
|
||||
Text text_type{{UI_POS_X(0), 1 * 16, 15 * 8, 16}, "?"};
|
||||
Text text_id{{6 * 8, 2 * 16, 10 * 8, 16}, "?"};
|
||||
|
||||
Console console{
|
||||
{0, 4 * 16, 240, screen_height - (4 * 16) - 36}};
|
||||
{0, 4 * 16, screen_width, screen_height - (4 * 16) - 36}};
|
||||
|
||||
Labels labels{
|
||||
{{0 * 8, 0 * 16}, "Type:", Theme::getInstance()->fg_light->foreground},
|
||||
{{0 * 8, 2 * 16}, "Serial: ", Theme::getInstance()->fg_light->foreground},
|
||||
{{0 * 8, 3 * 16}, "Data:", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(0), UI_POS_Y(0)}, "Type:", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(0), 2 * 16}, "Serial: ", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(0), 3 * 16}, "Data:", Theme::getInstance()->fg_light->foreground},
|
||||
};
|
||||
|
||||
Button button_done{
|
||||
{screen_width - 96 - 4, screen_height - 32 - 12, 96, 32},
|
||||
"Done"};
|
||||
|
||||
Button button_save{
|
||||
{4, screen_height - 32 - 12, 96, 32},
|
||||
"Save"};
|
||||
|
||||
void parseProtocol();
|
||||
};
|
||||
|
||||
|
||||
@@ -99,8 +99,8 @@ void TestView::on_packet(const testapp::Packet& packet) {
|
||||
display.draw_pixel(Point(cur_x, 4 * 16 + (256 - ((raw_alt - cal_value) / 4))), Color::white());
|
||||
|
||||
cur_x++;
|
||||
if (cur_x >= 240) {
|
||||
display.fill_rectangle(Rect(0, 5 * 16, 240, 256), Color::black());
|
||||
if (cur_x >= screen_width) {
|
||||
display.fill_rectangle(Rect(0, 5 * 16, screen_width, 256), Color::black());
|
||||
cur_x = 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -76,29 +76,29 @@ class TestView : public View {
|
||||
bool logging{false};
|
||||
|
||||
Labels labels{
|
||||
{{0 * 8, 1 * 16}, "Data:", Theme::getInstance()->fg_light->foreground}};
|
||||
{{UI_POS_X(0), 1 * 16}, "Data:", Theme::getInstance()->fg_light->foreground}};
|
||||
|
||||
RxFrequencyField field_frequency{
|
||||
{0 * 8, 0 * 8},
|
||||
{UI_POS_X(0), 0 * 8},
|
||||
nav_};
|
||||
RFAmpField field_rf_amp{
|
||||
{13 * 8, 0 * 16}};
|
||||
{13 * 8, UI_POS_Y(0)}};
|
||||
|
||||
LNAGainField field_lna{
|
||||
{15 * 8, 0 * 16}};
|
||||
{15 * 8, UI_POS_Y(0)}};
|
||||
|
||||
VGAGainField field_vga{
|
||||
{18 * 8, 0 * 16}};
|
||||
{18 * 8, UI_POS_Y(0)}};
|
||||
|
||||
RSSI rssi{
|
||||
{21 * 8, 0, 6 * 8, 4},
|
||||
};
|
||||
|
||||
Text text_debug_a{
|
||||
{0 * 8, 4 * 16, 30 * 8, 16},
|
||||
{UI_POS_X(0), 4 * 16, screen_width, 16},
|
||||
"..."};
|
||||
Text text_debug_b{
|
||||
{0 * 8, 5 * 16, 30 * 8, 16},
|
||||
{UI_POS_X(0), 5 * 16, screen_width, 16},
|
||||
"..."};
|
||||
|
||||
Button button_cal{
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
|
||||
#include "log_file.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "irq_controls.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
namespace fs = std::filesystem;
|
||||
@@ -81,21 +82,75 @@ void TextViewer::paint(Painter& painter) {
|
||||
paint_cursor(painter);
|
||||
}
|
||||
|
||||
void TextViewer::enable_long_press() {
|
||||
// Enable long press on "Select".
|
||||
SwitchesState config;
|
||||
config[toUType(Switch::Sel)] = true;
|
||||
set_switches_long_press_config(config);
|
||||
}
|
||||
|
||||
void TextViewer::on_focus() {
|
||||
enable_long_press();
|
||||
}
|
||||
|
||||
bool TextViewer::on_key(const KeyEvent key) {
|
||||
int16_t delta_col = 0;
|
||||
int16_t delta_line = 0;
|
||||
|
||||
if (key == KeyEvent::Left)
|
||||
delta_col = -1;
|
||||
else if (key == KeyEvent::Right)
|
||||
delta_col = 1;
|
||||
else if (key == KeyEvent::Up)
|
||||
delta_line = -1;
|
||||
else if (key == KeyEvent::Down)
|
||||
delta_line = 1;
|
||||
else if (key == KeyEvent::Select && on_select) {
|
||||
on_select();
|
||||
return true;
|
||||
if (key == KeyEvent::Select) {
|
||||
// Toggle 'digit' mode with long-press.
|
||||
if (digit_mode_ || key_is_long_pressed(key)) {
|
||||
digit_mode_ = !digit_mode_;
|
||||
set_dirty();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (digit_mode_) {
|
||||
switch (key) {
|
||||
case KeyEvent::Left:
|
||||
delta_col = -1;
|
||||
break;
|
||||
case KeyEvent::Right:
|
||||
delta_col = 1;
|
||||
break;
|
||||
case KeyEvent::Up:
|
||||
set_value(value_ == 0x0F ? 0x00 : value_ + 1);
|
||||
break;
|
||||
case KeyEvent::Down:
|
||||
set_value(value_ == 0x00 ? 0x0F : value_ - 1);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
if (delta_col == 0 && delta_line == 0) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
switch (key) {
|
||||
case KeyEvent::Left:
|
||||
delta_col = -1;
|
||||
break;
|
||||
case KeyEvent::Right:
|
||||
delta_col = 1;
|
||||
break;
|
||||
case KeyEvent::Up:
|
||||
delta_line = -1;
|
||||
break;
|
||||
case KeyEvent::Down:
|
||||
delta_line = 1;
|
||||
break;
|
||||
case KeyEvent::Select: {
|
||||
if (on_select) {
|
||||
on_select();
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Always allow cursor direction to be updated.
|
||||
@@ -111,19 +166,42 @@ bool TextViewer::on_key(const KeyEvent key) {
|
||||
bool TextViewer::on_encoder(EncoderEvent delta) {
|
||||
bool updated = false;
|
||||
|
||||
if (cursor_.dir == ScrollDirection::Horizontal)
|
||||
updated = apply_scrolling_constraints(0, delta);
|
||||
else {
|
||||
delta *= 16;
|
||||
updated = apply_scrolling_constraints(delta, 0);
|
||||
if (digit_mode_) {
|
||||
if (delta > 0) {
|
||||
set_value(value_ == 0x0F ? 0x00 : value_ + 1);
|
||||
} else if (delta < 0) {
|
||||
set_value(value_ == 0x00 ? 0x0F : value_ - 1);
|
||||
}
|
||||
|
||||
updated = true;
|
||||
} else {
|
||||
if (cursor_.dir == ScrollDirection::Horizontal) {
|
||||
updated = apply_scrolling_constraints(0, delta);
|
||||
} else {
|
||||
delta *= 16;
|
||||
updated = apply_scrolling_constraints(delta, 0);
|
||||
}
|
||||
|
||||
if (updated) {
|
||||
redraw();
|
||||
}
|
||||
}
|
||||
|
||||
if (updated)
|
||||
redraw();
|
||||
|
||||
return updated;
|
||||
}
|
||||
|
||||
void TextViewer::set_value(uint8_t new_value) {
|
||||
if (new_value != value_) {
|
||||
value_ = new_value;
|
||||
|
||||
if (on_change) {
|
||||
on_change(value_);
|
||||
}
|
||||
|
||||
set_dirty();
|
||||
}
|
||||
}
|
||||
|
||||
void TextViewer::redraw(bool redraw_text, bool redraw_marked) {
|
||||
paint_state_.redraw_text = redraw_text;
|
||||
paint_state_.redraw_marked = redraw_marked;
|
||||
@@ -349,7 +427,7 @@ void TextViewer::set_font_zoom(bool zoom) {
|
||||
/* TextEditorMenu ***************************************************/
|
||||
|
||||
TextEditorMenu::TextEditorMenu()
|
||||
: View{{7 * 4, 9 * 4, 25 * 8, 25 * 8}} {
|
||||
: View{{UI_POS_X_CENTER(25), 9 * 4, 25 * 8, 25 * 8}} {
|
||||
add_children(
|
||||
{
|
||||
&rect_frame,
|
||||
@@ -534,7 +612,7 @@ void TextEditorView::open_file(const fs::path& path) {
|
||||
viewer.clear_file();
|
||||
delete_temp_file(path_);
|
||||
|
||||
path_ = {};
|
||||
path_ = "";
|
||||
file_dirty_ = false;
|
||||
has_temp_file_ = false;
|
||||
auto result = FileWrapper::open(
|
||||
@@ -617,6 +695,7 @@ void TextEditorView::show_edit_line() {
|
||||
edit_line_buffer_,
|
||||
viewer.col(),
|
||||
max_edit_length,
|
||||
ENTER_KEYBOARD_MODE_ALPHA,
|
||||
[this](std::string& buffer) {
|
||||
auto range = file_->line_range(viewer.line());
|
||||
if (!range)
|
||||
|
||||
@@ -54,10 +54,13 @@ class TextViewer : public Widget {
|
||||
|
||||
std::function<void()> on_select{};
|
||||
std::function<void()> on_cursor_moved{};
|
||||
std::function<void(uint8_t)> on_change{};
|
||||
|
||||
void paint(Painter& painter) override;
|
||||
bool on_key(KeyEvent key) override;
|
||||
bool on_encoder(EncoderEvent delta) override;
|
||||
void set_value(uint8_t new_value);
|
||||
void on_focus() override;
|
||||
|
||||
void redraw(bool redraw_text = false, bool redraw_marked = false);
|
||||
|
||||
@@ -74,6 +77,7 @@ class TextViewer : public Widget {
|
||||
void cursor_set(uint16_t line, uint16_t col);
|
||||
void cursor_mark_selected();
|
||||
void cursor_clear_marked();
|
||||
void enable_long_press();
|
||||
|
||||
typedef std::pair<uint16_t, uint16_t> LineColPair;
|
||||
std::vector<LineColPair> lineColPair{};
|
||||
@@ -95,6 +99,9 @@ class TextViewer : public Widget {
|
||||
int8_t char_height{};
|
||||
uint8_t max_line{};
|
||||
uint8_t max_col{};
|
||||
bool digit_mode_{false};
|
||||
uint8_t value_{0};
|
||||
bool allow_digit_mode_{true};
|
||||
|
||||
/* Returns true if the cursor was updated. */
|
||||
bool apply_scrolling_constraints(
|
||||
@@ -157,7 +164,7 @@ class TextEditorMenu : public View {
|
||||
void hide_children(bool hidden);
|
||||
|
||||
Rectangle rect_frame{
|
||||
{0 * 8, 0 * 8, 23 * 8, 23 * 8},
|
||||
{UI_POS_X(0), 0 * 8, 23 * 8, 23 * 8},
|
||||
Theme::getInstance()->fg_dark->foreground};
|
||||
|
||||
NewButton button_home{
|
||||
@@ -260,23 +267,23 @@ class TextEditorView : public View {
|
||||
|
||||
TextViewer viewer{
|
||||
/* 272 = screen_height - 16 (top bar) - 32 (bottom controls) */
|
||||
{0, 0, screen_width, 272}};
|
||||
{0, 0, screen_width, UI_POS_HEIGHT_REMAINING(4)}};
|
||||
|
||||
TextEditorMenu menu{};
|
||||
|
||||
NewButton button_menu{
|
||||
{26 * 8, 34 * 8, 4 * 8, 4 * 8},
|
||||
{UI_POS_X_RIGHT(4), UI_POS_Y_BOTTOM(3), UI_POS_WIDTH(4), UI_POS_HEIGHT(2)},
|
||||
{},
|
||||
&bitmap_icon_controls,
|
||||
Theme::getInstance()->bg_dark->background,
|
||||
/*vcenter*/ true};
|
||||
|
||||
Text text_position{
|
||||
{0 * 8, 34 * 8, 26 * 8, 2 * 8},
|
||||
{UI_POS_X(0), UI_POS_Y_BOTTOM(3), UI_POS_WIDTH_REMAINING(5), UI_POS_HEIGHT(1)},
|
||||
""};
|
||||
|
||||
Text text_size{
|
||||
{0 * 8, 36 * 8, 26 * 8, 2 * 8},
|
||||
{UI_POS_X(0), UI_POS_Y_BOTTOM(2), UI_POS_WIDTH_REMAINING(5), UI_POS_HEIGHT(1)},
|
||||
""};
|
||||
};
|
||||
|
||||
|
||||
@@ -81,13 +81,13 @@ class TouchCalibrationView : public View {
|
||||
Color::black()};
|
||||
|
||||
Image image_calibrate_1{
|
||||
{240 - 32 - 16, (320 - 16) / 2 - 16, 32, 32},
|
||||
{screen_width - 32 - 16, (screen_height - 16) / 2 - 16, 32, 32},
|
||||
&bitmap_target_calibrate,
|
||||
Color::white(),
|
||||
Color::black()};
|
||||
|
||||
Image image_calibrate_2{
|
||||
{240 / 2 - 16, (320 - 16) - 32 - 16, 32, 32},
|
||||
{screen_width / 2 - 16, (screen_height - 16) - 32 - 16, 32, 32},
|
||||
&bitmap_target_calibrate,
|
||||
Color::white(),
|
||||
Color::black()};
|
||||
@@ -99,13 +99,13 @@ class TouchCalibrationView : public View {
|
||||
Color::black()};
|
||||
|
||||
Image image_verify_1{
|
||||
{240 - 32 - 16, (320 - 16) / 2 - 16, 32, 32},
|
||||
{screen_width - 32 - 16, (screen_height - 16) / 2 - 16, 32, 32},
|
||||
&bitmap_target_verify,
|
||||
Color::white(),
|
||||
Color::black()};
|
||||
|
||||
Image image_verify_2{
|
||||
{240 / 2 - 16, (320 - 16) - 32 - 16, 32, 32},
|
||||
{screen_width / 2 - 16, (screen_height - 16) - 32 - 16, 32, 32},
|
||||
&bitmap_target_verify,
|
||||
Color::white(),
|
||||
Color::black()};
|
||||
@@ -131,11 +131,11 @@ class TouchCalibrationView : public View {
|
||||
"Calibration failed. Retry?"};
|
||||
|
||||
Button button_cancel{
|
||||
{40, 200, 64, 24},
|
||||
{40, 200, UI_POS_WIDTH(8), 24},
|
||||
"Cancel"};
|
||||
|
||||
Button button_ok{
|
||||
{136, 200, 64, 24},
|
||||
{136, 200, UI_POS_WIDTH(8), 24},
|
||||
"OK"};
|
||||
|
||||
void on_frame_sync();
|
||||
|
||||
@@ -162,16 +162,16 @@ class TouchTunesView : public View {
|
||||
{{18 * 8 + 4, 18 * 8}, "V"},
|
||||
{{22 * 8, 18 * 8}, "F4"},
|
||||
|
||||
{{0 * 8, 5 * 8}, "1"},
|
||||
{{UI_POS_X(0), 5 * 8}, "1"},
|
||||
{{4 * 8, 5 * 8}, "2"},
|
||||
{{8 * 8, 5 * 8}, "3"},
|
||||
{{0 * 8, 10 * 8}, "4"},
|
||||
{{UI_POS_X(0), 10 * 8}, "4"},
|
||||
{{4 * 8, 10 * 8}, "5"},
|
||||
{{8 * 8, 10 * 8}, "6"},
|
||||
{{0 * 8, 15 * 8}, "7"},
|
||||
{{UI_POS_X(0), 15 * 8}, "7"},
|
||||
{{4 * 8, 15 * 8}, "8"},
|
||||
{{8 * 8, 15 * 8}, "9"},
|
||||
{{0 * 8, 20 * 8}, "*"},
|
||||
{{UI_POS_X(0), 20 * 8}, "*"},
|
||||
{{4 * 8, 20 * 8}, "0"},
|
||||
{{8 * 8, 20 * 8}, "#"},
|
||||
|
||||
|
||||
@@ -107,6 +107,7 @@ void WeatherView::focus() {
|
||||
WeatherView::WeatherView(NavigationView& nav)
|
||||
: nav_{nav} {
|
||||
add_children({&rssi,
|
||||
&channel,
|
||||
&field_rf_amp,
|
||||
&field_lna,
|
||||
&field_vga,
|
||||
@@ -196,6 +197,10 @@ WeatherView::~WeatherView() {
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
uint16_t WeatherView::bcd_decode_short(uint32_t data) {
|
||||
return (data & 0xF) * 10 + ((data >> 4) & 0xF);
|
||||
}
|
||||
|
||||
const char* WeatherView::getWeatherSensorTypeName(FPROTO_WEATHER_SENSOR type) {
|
||||
switch (type) {
|
||||
case FPW_NexusTH:
|
||||
@@ -224,6 +229,8 @@ const char* WeatherView::getWeatherSensorTypeName(FPROTO_WEATHER_SENSOR type) {
|
||||
return "LaCrosse TX141THBv2";
|
||||
case FPW_OREGON2:
|
||||
return "Oregon2";
|
||||
case FPW_OREGON2B:
|
||||
return "Oregon2B";
|
||||
case FPW_OREGON3:
|
||||
return "Oregon3";
|
||||
case FPW_OREGONv1:
|
||||
@@ -269,16 +276,13 @@ void RecentEntriesTable<ui::WeatherRecentEntries>::draw(
|
||||
const Entry& entry,
|
||||
const Rect& target_rect,
|
||||
Painter& painter,
|
||||
const Style& style) {
|
||||
const Style& style,
|
||||
RecentEntriesColumns& columns) {
|
||||
std::string line{};
|
||||
line.reserve(30);
|
||||
|
||||
line = WeatherView::getWeatherSensorTypeName((FPROTO_WEATHER_SENSOR)entry.sensorType);
|
||||
if (line.length() < 10) {
|
||||
line += WeatherView::pad_string_with_spaces(10 - line.length());
|
||||
} else {
|
||||
line = truncate(line, 10);
|
||||
}
|
||||
line.resize(columns.at(0).second, ' ');
|
||||
|
||||
std::string temp = (weather_units_fahr ? to_string_decimal((entry.temp * 9 / 5) + 32, 1) : to_string_decimal(entry.temp, 1));
|
||||
std::string humStr = (entry.humidity != WS_NO_HUMIDITY) ? to_string_dec_uint(entry.humidity) + "%" : "-";
|
||||
@@ -301,7 +305,9 @@ WeatherRecentEntry WeatherView::process_data(const WeatherDataMessage* data) {
|
||||
WeatherRecentEntry ret = {};
|
||||
ret.sensorType = data->sensorType;
|
||||
int16_t i16 = 0;
|
||||
int32_t i32 = 0;
|
||||
uint16_t u16 = 0;
|
||||
|
||||
uint64_t u64 = 0;
|
||||
uint8_t u8 = 0;
|
||||
uint8_t channel_3021[] = {3, 0, 2, 1};
|
||||
@@ -444,12 +450,34 @@ WeatherRecentEntry WeatherView::process_data(const WeatherDataMessage* data) {
|
||||
ret.humidity = (data->decode_data >> 8) & 0xFF;
|
||||
break;
|
||||
case FPW_OREGON2:
|
||||
i16 = ((data->decode_data >> 4) & 0xF) * 10 + (((data->decode_data >> 4) >> 4) & 0xF);
|
||||
i16 *= 10;
|
||||
i16 += (data->decode_data >> 12) & 0xF;
|
||||
if (data->decode_data & 0xF) i16 = -i16;
|
||||
ret.temp = (float)i16 / 10.0;
|
||||
// todo fix missing parts.
|
||||
i32 = bcd_decode_short(data->decode_data >> 4);
|
||||
i32 *= 10;
|
||||
i32 += (data->decode_data >> 12) & 0xf;
|
||||
if (data->decode_data & 0xF) i32 = -i32;
|
||||
ret.temp = (float)i32 / 10.0;
|
||||
ret.battery_low = ((data->decode_data >> 32) & 0x4) ? 1 : 0;
|
||||
u8 = (data->decode_data >> 44) & 0xF;
|
||||
ret.channel = 1;
|
||||
while (u8 > 1) {
|
||||
ret.channel++;
|
||||
u8 >>= 1;
|
||||
}
|
||||
break;
|
||||
case FPW_OREGON2B:
|
||||
ret.humidity = bcd_decode_short(data->decode_data);
|
||||
u64 = data->decode_data >> 8;
|
||||
i32 = bcd_decode_short(u64 >> 4);
|
||||
i32 *= 10;
|
||||
i32 += (u64 >> 12) & 0xf;
|
||||
if (u64 & 0xF) i32 = -i32;
|
||||
ret.temp = (float)i32 / 10.0;
|
||||
ret.battery_low = ((data->decode_data >> 32) & 0x4) ? 1 : 0;
|
||||
u8 = (data->decode_data >> 44) & 0xF;
|
||||
ret.channel = 1;
|
||||
while (u8 > 1) {
|
||||
ret.channel++;
|
||||
u8 >>= 1;
|
||||
}
|
||||
break;
|
||||
case FPW_OREGON3:
|
||||
// todo check
|
||||
|
||||
@@ -114,6 +114,7 @@ class WeatherView : public View {
|
||||
|
||||
private:
|
||||
void on_tick_second();
|
||||
uint16_t bcd_decode_short(uint32_t data);
|
||||
void on_data(const WeatherDataMessage* data);
|
||||
WeatherRecentEntry process_data(const WeatherDataMessage* data);
|
||||
NavigationView& nav_;
|
||||
@@ -134,25 +135,28 @@ class WeatherView : public View {
|
||||
WeatherRecentEntries recent{};
|
||||
|
||||
OptionsField options_temperature{
|
||||
{10 * 8, 0 * 16},
|
||||
{10 * 8, UI_POS_Y(0)},
|
||||
2,
|
||||
{{STR_DEGREES_C, 0},
|
||||
{STR_DEGREES_F, 1}}};
|
||||
|
||||
RFAmpField field_rf_amp{
|
||||
{13 * 8, 0 * 16}};
|
||||
{13 * 8, UI_POS_Y(0)}};
|
||||
LNAGainField field_lna{
|
||||
{15 * 8, 0 * 16}};
|
||||
{15 * 8, UI_POS_Y(0)}};
|
||||
VGAGainField field_vga{
|
||||
{18 * 8, 0 * 16}};
|
||||
{18 * 8, UI_POS_Y(0)}};
|
||||
RSSI rssi{
|
||||
{21 * 8, 0, 6 * 8, 4}};
|
||||
{21 * 8, 0, UI_POS_WIDTH_REMAINING(24), 4}};
|
||||
Channel channel{
|
||||
{21 * 8, 5, UI_POS_WIDTH_REMAINING(24), 4},
|
||||
};
|
||||
|
||||
AudioVolumeField field_volume{
|
||||
{28 * 8, 0 * 16}};
|
||||
{UI_POS_X_RIGHT(2), UI_POS_Y(0)}};
|
||||
|
||||
RxFrequencyField field_frequency{
|
||||
{0 * 8, 0 * 16},
|
||||
{UI_POS_X(0), UI_POS_Y(0)},
|
||||
nav_};
|
||||
|
||||
SignalToken signal_token_tick_second{};
|
||||
@@ -171,8 +175,8 @@ class WeatherView : public View {
|
||||
|
||||
std::unique_ptr<WeatherLogger> logger{};
|
||||
|
||||
const RecentEntriesColumns columns{{
|
||||
{"Type", 10},
|
||||
RecentEntriesColumns columns{{
|
||||
{"Type", 0},
|
||||
{"Temp", 5},
|
||||
{"Hum", 4},
|
||||
{"Ch", 3},
|
||||
@@ -215,14 +219,14 @@ class WeatherRecentEntryDetailView : public View {
|
||||
Text text_age{{10 * 8, 7 * 16, 10 * 8, 16}, "?"};
|
||||
|
||||
Labels labels{
|
||||
{{0 * 8, 0 * 16}, "Weather Station", Theme::getInstance()->fg_light->foreground},
|
||||
{{0 * 8, 1 * 16}, "Type:", Theme::getInstance()->fg_light->foreground},
|
||||
{{0 * 8, 2 * 16}, "Id: ", Theme::getInstance()->fg_light->foreground},
|
||||
{{0 * 8, 3 * 16}, "Temp:", Theme::getInstance()->fg_light->foreground},
|
||||
{{0 * 8, 4 * 16}, "Humidity:", Theme::getInstance()->fg_light->foreground},
|
||||
{{0 * 8, 5 * 16}, "Channel:", Theme::getInstance()->fg_light->foreground},
|
||||
{{0 * 8, 6 * 16}, "Battery:", Theme::getInstance()->fg_light->foreground},
|
||||
{{0 * 8, 7 * 16}, "Age:", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(0), UI_POS_Y(0)}, "Weather Station", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(0), 1 * 16}, "Type:", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(0), 2 * 16}, "Id: ", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(0), 3 * 16}, "Temp:", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(0), 4 * 16}, "Humidity:", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(0), 5 * 16}, "Channel:", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(0), 6 * 16}, "Battery:", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(0), 7 * 16}, "Age:", Theme::getInstance()->fg_light->foreground},
|
||||
};
|
||||
|
||||
Button button_done{
|
||||
|
||||
@@ -225,8 +225,15 @@ volume_range_t volume_range() {
|
||||
return audio_codec->headphone_gain_range();
|
||||
}
|
||||
|
||||
void set_volume(const volume_t volume) {
|
||||
audio_codec->set_headphone_volume(volume);
|
||||
bool set_volume(const volume_t volume) {
|
||||
// add retry method
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
if (audio_codec->set_headphone_volume(volume)) {
|
||||
return true;
|
||||
}
|
||||
chThdSleepMilliseconds(2);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} /* namespace headphone */
|
||||
|
||||
@@ -49,7 +49,7 @@ class Codec {
|
||||
virtual void headphone_enable() = 0;
|
||||
virtual void headphone_disable() = 0;
|
||||
virtual volume_range_t headphone_gain_range() const = 0;
|
||||
virtual void set_headphone_volume(const volume_t volume) = 0;
|
||||
virtual bool set_headphone_volume(const volume_t volume) = 0;
|
||||
|
||||
virtual void microphone_enable(int8_t alc_mode, bool mic_to_HP_enabled) = 0; // added user-GUI AK4951 ,selected ALC mode.
|
||||
virtual void microphone_disable() = 0;
|
||||
@@ -93,7 +93,7 @@ namespace headphone {
|
||||
|
||||
volume_range_t volume_range();
|
||||
|
||||
void set_volume(const volume_t volume);
|
||||
bool set_volume(const volume_t volume);
|
||||
|
||||
} /* namespace headphone */
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
*/
|
||||
|
||||
#include "baseband_api.hpp"
|
||||
#include <cstring>
|
||||
|
||||
#include "audio.hpp"
|
||||
#include "tonesets.hpp"
|
||||
@@ -52,12 +53,21 @@ static void send_message(const Message* const message) {
|
||||
creg::m0apptxevent::assert_event();
|
||||
|
||||
if constexpr (check_for_message_hang) {
|
||||
#ifdef PRALINE
|
||||
/* Timeout: ~3 seconds at typical clock speeds */
|
||||
auto count = 200'000'000u;
|
||||
#else
|
||||
auto count = UINT32_MAX;
|
||||
#endif
|
||||
while (shared_memory.baseband_message && --count)
|
||||
/* spin */;
|
||||
|
||||
if (count == 0)
|
||||
#ifdef PRALINE
|
||||
chDbgPanic("BB Msg Timeout");
|
||||
#else
|
||||
chDbgPanic("Baseband Send Fail");
|
||||
#endif
|
||||
} else {
|
||||
while (shared_memory.baseband_message)
|
||||
/* spin */;
|
||||
@@ -66,12 +76,13 @@ static void send_message(const Message* const message) {
|
||||
|
||||
void AMConfig::apply() const {
|
||||
const AMConfigureMessage message{
|
||||
taps_6k0_decim_0, // common FIR filter taps pre-decim_0 to all 5 x AM mod types.(AM-9K, AM-6K, USB, LSB, CW)
|
||||
taps_6k0_decim_1, // common FIR filter taps pre-decim_1 to all 5 x AM mod. types.
|
||||
decim_2, // var decim_2 FIR taps filter , variable values, depending selected AM mod(AM 9k / 6k all rest AM modes)
|
||||
channel, // var channel FIR taps filter , variable values, depending selected AM mode, each one different (DSB-9K, DSB-6K, USB-3K, LSB-3K,CW)
|
||||
modulation, // var parameter .
|
||||
audio_12k_hpf_300hz_config};
|
||||
taps_6k0_decim_0, // common FIR filter taps pre-decim_0 to all 6 x AM mod types.(AM-9K, AM-6K, USB, LSB, CW, AMFM-WFAX)
|
||||
decim_1, // var decim_1 FIR taps filter , variable values , to handle two spectrum decim factor 1 and 2 (zoom) and more APT LPF filtered .
|
||||
decim_2, // var decim_2 FIR taps filter , variable values, depending selected AM mod(AM 9k / 6k and all rest AM modes)
|
||||
channel, // var channel FIR taps filter , variable values, depending selected AM mode, each one different (DSB-9K, DSB-6K, USB-3K, LSB-3K,CW,AMFM-WFAX)
|
||||
modulation, // var parameter . enum class Modulation : int32_t {DSB = 0, SSB = 1, SSB_FM = 2}
|
||||
audio_12k_iir_filter_config, // var parameter , 300 Hz hpf all except Wefax (1.500Hz lpf)
|
||||
spectrum_decimation_factor}; // var parameter , waterfall no zoom : 1 ,for zoom x 2 : 2
|
||||
send_message(&message);
|
||||
audio::set_rate(audio::Rate::Hz_12000);
|
||||
}
|
||||
@@ -92,9 +103,9 @@ void NBFMConfig::apply(const uint8_t squelch_level) const {
|
||||
|
||||
void WFMConfig::apply() const {
|
||||
const WFMConfigureMessage message{
|
||||
decim_0, // taps_200k_decim_0 , taps_180k_wfm_decim_0, taps_40k_wfm_decim_0
|
||||
decim_1, // taps_200k_decim_1 or taps_180k_wfm_decim_1, taps_40k_wfm_decim_1
|
||||
taps_64_lp_156_198,
|
||||
decim_0, // Dynamic array 24 taps : taps_200k_decim_0 , taps_180k_wfm_decim_0, taps_80k_wfm_decim_0
|
||||
decim_1, // Dynamic array 16 taps : taps_200k_decim_1 or taps_180k_wfm_decim_1, taps_80k_wfm_decim_1
|
||||
taps_64_lp_156_198, // Fixed channel audio filter 15khz
|
||||
75000,
|
||||
audio_48k_hpf_30hz_config,
|
||||
audio_48k_deemph_2122_6_config};
|
||||
@@ -102,6 +113,18 @@ void WFMConfig::apply() const {
|
||||
audio::set_rate(audio::Rate::Hz_48000);
|
||||
}
|
||||
|
||||
void WFMAMConfig::apply() const {
|
||||
const WFMAMConfigureMessage message{
|
||||
decim_0, // Fixed 24 taps array : taps_16k0_decim_0
|
||||
decim_1, // Dynamic 32 taps array : taps_80k_wfmam_decim_1, 38k_wfmam
|
||||
taps_64_lp_bpf, // Dynamic 64 taps array , to filter modulated DSB AM 2k4 carrier before demod. AM .(LPF / BPF)
|
||||
17000, // NOAA satellite tx , FM deviation = +-17Khz.
|
||||
apt_audio_12k_notch_2k4_config,
|
||||
apt_audio_12k_lpf_2000hz_config};
|
||||
send_message(&message);
|
||||
audio::set_rate(audio::Rate::Hz_12000);
|
||||
}
|
||||
|
||||
void set_tone(const uint32_t index, const uint32_t delta, const uint32_t duration) {
|
||||
shared_memory.bb_data.tones_data.tone_defs[index].delta = delta;
|
||||
shared_memory.bb_data.tones_data.tone_defs[index].duration = duration;
|
||||
@@ -134,6 +157,19 @@ void set_sstv_data(const uint8_t vis_code, const uint32_t pixel_duration) {
|
||||
send_message(&message);
|
||||
}
|
||||
|
||||
void set_sstvrx_data(const uint8_t code) {
|
||||
const SSTVRXConfigureMessage message{
|
||||
code};
|
||||
send_message(&message);
|
||||
}
|
||||
|
||||
void set_sstvrx_phase_slant(const int16_t phase, const int16_t slant) {
|
||||
const SSTVRXPhaseSlantMessage message{
|
||||
phase,
|
||||
slant};
|
||||
send_message(&message);
|
||||
}
|
||||
|
||||
void set_afsk(const uint32_t baudrate, const uint32_t word_length, const uint32_t trigger_value, const bool trigger_word) {
|
||||
const AFSKRxConfigureMessage message{
|
||||
baudrate,
|
||||
@@ -173,13 +209,14 @@ void set_nrf(const uint32_t baudrate, const uint32_t word_length, const uint32_t
|
||||
send_message(&message);
|
||||
}
|
||||
|
||||
void set_fsk(const size_t deviation) {
|
||||
void set_fsk(const uint8_t samplesPerSymbol, const uint32_t syncWord, const uint8_t syncWordLength, const uint32_t preamble, const uint8_t preambleLength, uint16_t numDataBytes) {
|
||||
const FSKRxConfigureMessage message{
|
||||
taps_200k_decim_0,
|
||||
taps_16k0_decim_1,
|
||||
taps_11k0_channel,
|
||||
2,
|
||||
deviation};
|
||||
samplesPerSymbol,
|
||||
syncWord,
|
||||
syncWordLength,
|
||||
preamble,
|
||||
preambleLength,
|
||||
numDataBytes};
|
||||
|
||||
send_message(&message);
|
||||
}
|
||||
@@ -274,8 +311,8 @@ void set_fsk_data(const uint32_t stream_length, const uint32_t samples_per_bit,
|
||||
send_message(&message);
|
||||
}
|
||||
|
||||
void set_pocsag() {
|
||||
const POCSAGConfigureMessage message{};
|
||||
void set_pocsag(int8_t baud_config) {
|
||||
const POCSAGConfigureMessage message{baud_config};
|
||||
send_message(&message);
|
||||
}
|
||||
|
||||
@@ -298,12 +335,52 @@ void set_rds_data(const uint16_t message_length) {
|
||||
send_message(&message);
|
||||
}
|
||||
|
||||
void set_spectrum(const size_t sampling_rate, const size_t trigger) {
|
||||
void set_spectrum(
|
||||
const size_t sampling_rate,
|
||||
const size_t trigger) {
|
||||
const WidebandSpectrumConfigMessage message{
|
||||
sampling_rate, trigger};
|
||||
send_message(&message);
|
||||
}
|
||||
|
||||
void set_audio_ddc_frequency(int32_t frequency) {
|
||||
const AudioDDCConfigMessage message{frequency};
|
||||
send_message(&message);
|
||||
}
|
||||
|
||||
void set_time_sink(
|
||||
const size_t sampling_rate,
|
||||
const size_t trigger) {
|
||||
const TimeSinkConfigMessage message{
|
||||
sampling_rate, trigger};
|
||||
send_message(&message);
|
||||
}
|
||||
|
||||
void set_wefax_config(uint8_t lpm = 120, uint8_t ioc = 0) {
|
||||
const WeFaxRxConfigureMessage message{lpm, ioc};
|
||||
send_message(&message);
|
||||
}
|
||||
|
||||
void set_noaaapt_config() {
|
||||
const NoaaAptRxConfigureMessage message{};
|
||||
send_message(&message);
|
||||
}
|
||||
|
||||
void set_vor_config(bool enabled) {
|
||||
const VorRxConfigureMessage message{enabled};
|
||||
send_message(&message);
|
||||
}
|
||||
|
||||
void set_vor_tx_config(uint16_t radial_deg, bool ident_enabled, const std::string& ident, bool enabled) {
|
||||
const VorTxConfigureMessage message{radial_deg, ident_enabled, ident.c_str(), enabled};
|
||||
send_message(&message);
|
||||
}
|
||||
|
||||
void set_flex_config() {
|
||||
const FlexConfigureMessage message{};
|
||||
send_message(&message);
|
||||
}
|
||||
|
||||
void set_siggen_tone(const uint32_t tone) {
|
||||
const SigGenToneMessage message{
|
||||
TONES_F2D(tone, TONES_SAMPLERATE)};
|
||||
@@ -326,8 +403,77 @@ void set_subghzd_config(uint8_t modulation = 0, uint32_t sampling_rate = 0) {
|
||||
send_message(&message);
|
||||
}
|
||||
|
||||
void set_moreserx_config(uint8_t mode) {
|
||||
const MorseRXConfigureMessage message{mode};
|
||||
send_message(&message);
|
||||
}
|
||||
|
||||
void set_tonedetect_config(uint8_t squelch, uint32_t ctcss_freq_x10) {
|
||||
const ToneDetectConfigureMessage message{squelch, ctcss_freq_x10};
|
||||
send_message(&message);
|
||||
}
|
||||
|
||||
void set_morsetx_config(uint8_t mode, uint32_t tone, float fm_delta) {
|
||||
const MorseTXConfigureMessage message{mode, tone, fm_delta};
|
||||
send_message(&message);
|
||||
}
|
||||
|
||||
void set_morsetx_key(bool key_down) {
|
||||
const MorseTXkeyMessage message{key_down};
|
||||
send_message(&message);
|
||||
}
|
||||
|
||||
void set_bitstream_config(uint32_t deviation, uint8_t mode) {
|
||||
const StreamTXConfigurationMessage message{deviation, mode};
|
||||
send_message(&message);
|
||||
}
|
||||
|
||||
void set_rtty_config(uint16_t baud, uint16_t shift, uint8_t* payload, uint16_t payload_length) {
|
||||
RTTYDataMessage message{baud, shift};
|
||||
if (payload && payload_length > 0) {
|
||||
message.data_len = payload_length > message.max_len ? message.max_len : payload_length;
|
||||
for (size_t i = 0; i < message.data_len; ++i) {
|
||||
message.data[i] = payload[i];
|
||||
}
|
||||
}
|
||||
send_message(&message);
|
||||
}
|
||||
|
||||
void set_rtty_config(RTTYDataMessage& message) {
|
||||
send_message(&message);
|
||||
}
|
||||
|
||||
void set_epirb_tx_config(EPIRBTXDataMessage& message) {
|
||||
send_message(&message);
|
||||
}
|
||||
|
||||
void set_epirb_rx_config(EPIRBRXConfig& message) {
|
||||
send_message(&message);
|
||||
}
|
||||
|
||||
void set_p25tx_data(const uint8_t* dibits, uint16_t frame_length) {
|
||||
const size_t max_len = sizeof(shared_memory.bb_data.data);
|
||||
if (frame_length > max_len) frame_length = max_len;
|
||||
memcpy(shared_memory.bb_data.data, dibits, frame_length);
|
||||
P25TxConfigureMessage msg{};
|
||||
msg.frame_length = frame_length;
|
||||
send_message(&msg);
|
||||
}
|
||||
|
||||
void set_hunter_config(uint32_t threshold, uint32_t hangtime_ms, bool start) {
|
||||
HunterConfigMessage message{};
|
||||
message.energy_threshold = threshold;
|
||||
message.hangtime_ms = hangtime_ms;
|
||||
message.start = start;
|
||||
send_message(&message);
|
||||
}
|
||||
|
||||
static bool baseband_image_running = false;
|
||||
|
||||
bool is_image_running() {
|
||||
return baseband_image_running;
|
||||
}
|
||||
|
||||
void run_image(const spi_flash::image_tag_t image_tag) {
|
||||
if (baseband_image_running) {
|
||||
chDbgPanic("BBRunning");
|
||||
@@ -387,7 +533,12 @@ void shutdown() {
|
||||
send_message(&message);
|
||||
|
||||
shared_memory.application_queue.reset();
|
||||
|
||||
// Allow time for the shutdown message to be processed and for the baseband
|
||||
// core to stop before starting another image. Otherwise, the M4 may still be
|
||||
// running and cause a crash when the next image is started.
|
||||
#ifdef PRALINE
|
||||
chThdSleepMilliseconds(20);
|
||||
#endif
|
||||
baseband_image_running = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -36,9 +36,12 @@
|
||||
namespace baseband {
|
||||
|
||||
struct AMConfig {
|
||||
const fir_taps_real<32> decim_2; // added to handle two types decim_2 9k, 6k
|
||||
const fir_taps_real<32> decim_1; // added to handle two var LPF in AMFM to avoid aliasing when spectrum zoom factor 2.
|
||||
const fir_taps_real<32> decim_2; // added to handle two var types decim_2 9k, 6k
|
||||
const fir_taps_complex<64> channel;
|
||||
const AMConfigureMessage::Modulation modulation;
|
||||
const iir_biquad_config_t audio_12k_iir_filter_config; // added to handle two var IIR filter types : 300 hpf(as before) , 1500Hz lpf for Wefax.
|
||||
const size_t spectrum_decimation_factor; // used to handle LCD AM waterfall zoom x1 / zoom x2.
|
||||
|
||||
void apply() const;
|
||||
};
|
||||
@@ -53,23 +56,33 @@ struct NBFMConfig {
|
||||
};
|
||||
|
||||
struct WFMConfig {
|
||||
const fir_taps_real<24> decim_0; // To handle both WFM filters , 200k and 40K for NOAA APT
|
||||
const fir_taps_real<24> decim_0; // To handle all 3 WFM filters , 200k, 180k and 80K-
|
||||
const fir_taps_real<16> decim_1;
|
||||
|
||||
void apply() const;
|
||||
};
|
||||
|
||||
struct WFMAMConfig {
|
||||
const fir_taps_real<24> decim_0; // To handle WFM filter BW=40K for NOAA APT
|
||||
const fir_taps_real<32> decim_1;
|
||||
const fir_taps_real<64> taps_64_lp_bpf; // to handle dynamically LPF / BPF .
|
||||
|
||||
void apply() const;
|
||||
};
|
||||
|
||||
void set_tone(const uint32_t index, const uint32_t delta, const uint32_t duration);
|
||||
void set_tones_config(const uint32_t bw, const uint32_t pre_silence, const uint16_t tone_count, const bool dual_tone, const bool audio_out);
|
||||
void kill_tone();
|
||||
void set_sstv_data(const uint8_t vis_code, const uint32_t pixel_duration);
|
||||
void set_sstvrx_data(const uint8_t code);
|
||||
void set_sstvrx_phase_slant(const int16_t phase, const int16_t slant);
|
||||
void set_audiotx_config(const uint32_t divider, const float deviation_hz, const float audio_gain, uint8_t audio_shift_bits_s16, uint8_t bits_per_sample, const uint32_t tone_key_delta, const bool am_enabled, const bool dsb_enabled, const bool usb_enabled, const bool lsb_enabled);
|
||||
void set_fifo_data(const int8_t* data);
|
||||
void set_pitch_rssi(int32_t avg, bool enabled);
|
||||
void set_afsk_data(const uint32_t afsk_samples_per_bit, const uint32_t afsk_phase_inc_mark, const uint32_t afsk_phase_inc_space, const uint8_t afsk_repeat, const uint32_t afsk_bw, const uint8_t symbol_count);
|
||||
void kill_afsk();
|
||||
void set_afsk(const uint32_t baudrate, const uint32_t word_length, const uint32_t trigger_value, const bool trigger_word);
|
||||
void set_fsk(const size_t deviation);
|
||||
void set_fsk(const uint8_t samplesPerSymbol, const uint32_t syncWord, const uint8_t syncWordLength, const uint32_t preamble, const uint8_t preambleLength, uint16_t numDataBytes);
|
||||
void set_aprs(const uint32_t baudrate);
|
||||
|
||||
void set_btlerx(uint8_t channel_number);
|
||||
@@ -80,21 +93,44 @@ void set_nrf(const uint32_t baudrate, const uint32_t word_length, const uint32_t
|
||||
void set_ook_data(const uint32_t stream_length, const uint32_t samples_per_bit, const uint8_t repeat, const uint32_t pause_symbols, const uint8_t de_bruijn_length = 0);
|
||||
void kill_ook();
|
||||
void set_fsk_data(const uint32_t stream_length, const uint32_t samples_per_bit, const uint32_t shift, const uint32_t progress_notice);
|
||||
void set_pocsag();
|
||||
void set_pocsag(int8_t baud_config = -1);
|
||||
void set_adsb();
|
||||
void set_jammer(const bool run, const jammer::JammerType type, const uint32_t speed);
|
||||
void set_rds_data(const uint16_t message_length);
|
||||
void set_spectrum(const size_t sampling_rate, const size_t trigger);
|
||||
void set_spectrum(
|
||||
const size_t sampling_rate,
|
||||
const size_t trigger);
|
||||
void set_audio_ddc_frequency(int32_t frequency);
|
||||
void set_time_sink(
|
||||
const size_t sampling_rate,
|
||||
const size_t trigger);
|
||||
void set_siggen_tone(const uint32_t tone);
|
||||
void set_siggen_config(const uint32_t bw, const uint32_t shape, const uint32_t duration);
|
||||
void set_spectrum_painter_config(const uint16_t width, const uint16_t height, bool update, int32_t bw);
|
||||
void set_subghzd_config(uint8_t modulation, uint32_t sampling_rate);
|
||||
void set_moreserx_config(uint8_t mode);
|
||||
void set_tonedetect_config(uint8_t squelch, uint32_t ctcss_freq_x10 = 0);
|
||||
void set_morsetx_config(uint8_t mode, uint32_t tone, float fm_delta);
|
||||
void set_morsetx_key(bool key_down);
|
||||
void set_wefax_config(uint8_t lpm, uint8_t ioc);
|
||||
void set_noaaapt_config();
|
||||
void set_vor_config(bool enabled = true);
|
||||
void set_vor_tx_config(uint16_t radial_deg, bool ident_enabled = true, const std::string& ident = "", bool enabled = true);
|
||||
void set_flex_config();
|
||||
void set_bitstream_config(uint32_t deviation, uint8_t mode); // mode 0 for am, 1 for 2fsk
|
||||
void set_rtty_config(uint16_t baud, uint16_t shift, uint8_t* payload = nullptr, uint16_t payload_length = 0); // baud*100
|
||||
void set_rtty_config(RTTYDataMessage& message);
|
||||
void set_epirb_tx_config(EPIRBTXDataMessage& message);
|
||||
void set_epirb_rx_config(EPIRBRXConfig& message);
|
||||
void set_p25tx_data(const uint8_t* dibits, uint16_t frame_length);
|
||||
void set_hunter_config(uint32_t threshold, uint32_t hangtime_ms, bool start);
|
||||
|
||||
void request_roger_beep();
|
||||
void request_rssi_beep();
|
||||
void request_beep_stop();
|
||||
void request_audio_beep(uint32_t freq, uint32_t sample_rate, uint32_t duration_ms);
|
||||
|
||||
bool is_image_running();
|
||||
void run_image(const portapack::spi_flash::image_tag_t image_tag);
|
||||
void run_prepared_image(const uint32_t m4_code);
|
||||
void shutdown();
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user