mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-08-23 16:09:05 +00:00
Compare commits
328 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 572776bb9e | |||
| 073c4ed9f2 | |||
| 42233e62df | |||
| 9390317a75 | |||
| 89b51095bf | |||
| 9438c9a574 | |||
| 7feef9d38b | |||
| 582bb02a75 | |||
| bfa7f3ca6b | |||
| 21facd9cc3 | |||
| d30234aaa6 | |||
| d45a17236e | |||
| 281e52d143 | |||
| 1f78646054 | |||
| 31c844b48e | |||
| 87069f11e5 | |||
| e6afd7744d | |||
| 76763b9bab | |||
| 02b75f567a | |||
| a2c4fefe34 | |||
| 121352b149 | |||
| 0ae7768f20 | |||
| 1a0555f9eb | |||
| 6752bf0279 | |||
| 6dc7e3dfc5 | |||
| c992780974 | |||
| 750d592288 | |||
| c77252cca2 | |||
| df3981876d | |||
| 05146638fc | |||
| 765e3be55b | |||
| 9211975868 | |||
| 96a60e82bc | |||
| 5d9428e568 | |||
| 2fdd531fe7 | |||
| f13756d4c7 | |||
| ae6724b359 | |||
| e11c28cf30 | |||
| b93fbad207 | |||
| 2bedb5f09f | |||
| 19eb6b44d5 | |||
| 048359fb0e | |||
| ae75540d35 | |||
| 9ef58b7763 | |||
| 032168234a | |||
| b53b5c70d3 | |||
| ee53016c6e | |||
| 2bf1116c6a | |||
| ced8012783 | |||
| a8203a6372 | |||
| e4d8bcb88d | |||
| db3ae29bd0 | |||
| 2a78c543a1 | |||
| 53b59a8a22 | |||
| 100bea644c | |||
| fe71592b68 | |||
| 4a96feb829 | |||
| e666eb1797 | |||
| 963c6e5238 | |||
| fac71677d4 | |||
| 075b23b291 | |||
| 3628576d60 | |||
| a3b71d0e3d | |||
| 0ea8453e8a | |||
| 1ffedace7b | |||
| e43f7a7980 | |||
| f6a2d21624 | |||
| f572b00391 | |||
| d74fd527e2 | |||
| 819f35ac93 | |||
| 60da661999 | |||
| 43077c79cf | |||
| 06651dc97c | |||
| 67975d76a0 | |||
| 282e4da1cb | |||
| e7359563c6 | |||
| 03ca87b13e | |||
| b2da21b88a | |||
| bb1b703dc1 | |||
| 990f63e5de | |||
| 6fe8ab4f99 | |||
| d6ed12191d | |||
| 8b159f447a | |||
| e9c32504d1 | |||
| 004799e1a3 | |||
| 8e90c65a62 | |||
| bfe67a13b7 | |||
| f091db3270 | |||
| a602abf1d8 | |||
| 689224fd8d | |||
| 23cf1c2737 | |||
| c48cbb7e55 | |||
| 3665b3c607 | |||
| 804b7c87b7 | |||
| a30875b582 | |||
| 56303eb385 | |||
| d29172f41e | |||
| 6d9d5ad1af | |||
| 2acefee4d2 | |||
| cb9fee8f08 | |||
| b473930f94 | |||
| 6e5eadd25c | |||
| 76017c91a9 | |||
| f1ebb1a6e3 | |||
| 746bf1c15f | |||
| 0db65ccb6b | |||
| fe2fbb847f | |||
| ba36680a7b | |||
| 90ff1e62ce | |||
| 2e1e02baac | |||
| 2dca408e45 | |||
| fa56cdd015 | |||
| 264fa9a28e | |||
| d5c8525afc | |||
| d9bbd1b9ff | |||
| b47cee435e | |||
| c078bac0e7 | |||
| 6177b08632 | |||
| 53a1bacd46 | |||
| 4c012a9493 | |||
| abb9b6c8ad | |||
| 6e2507d609 | |||
| 536981998b | |||
| 1a87f2d701 | |||
| 81e24d582d | |||
| 28a5fc5915 | |||
| ba4290cf0d | |||
| 692644d214 | |||
| 4aeaf94d2d | |||
| 8391ca8052 | |||
| 807c76346b | |||
| 308573918c | |||
| 74442f197d | |||
| b53c8e1f80 | |||
| c5f73cf8f4 | |||
| c1bf2620c7 | |||
| 8383363e74 | |||
| 6e34343bde | |||
| 01e4ff65a2 | |||
| 9e40e38f07 | |||
| 181624daf1 | |||
| f0614c882b | |||
| 14f42d1fe8 | |||
| 999f9e2ded | |||
| 61dc8a0225 | |||
| 0b2d5f75cc | |||
| 3c489e1a81 | |||
| 910fd82051 | |||
| 866e12fbc5 | |||
| ccd71d9593 | |||
| e9b9ba4602 | |||
| 986e37a3c9 | |||
| 2ac36d2d0b | |||
| 3b79549129 | |||
| 160a778104 | |||
| e496f8ecc2 | |||
| 9d8132978f | |||
| 73c29f666f | |||
| d895c9fa91 | |||
| b5e66387c3 | |||
| 1fbfdbccf8 | |||
| 2648f18b12 | |||
| 908c0e2cd5 | |||
| 4f3530db5e | |||
| 73137891c4 | |||
| c28087ac44 | |||
| 9e9dd3a521 | |||
| 3bf689f339 | |||
| 86c326ef12 | |||
| 5ae97d47f1 | |||
| 762512b7dc | |||
| 9c848a6215 | |||
| 9c86e15ddb | |||
| 57ccdb0044 | |||
| 1172175cc8 | |||
| 9ed8ad5903 | |||
| e4da8b979e | |||
| 4b83fd8a40 | |||
| cd80da58d8 | |||
| a1f7010e08 | |||
| 3499d2fa75 | |||
| 948e039574 | |||
| 27dc37713b | |||
| ed834e3553 | |||
| ce08d93ae2 | |||
| 13fd1b1f3b | |||
| d04c781ada | |||
| 24605777a6 | |||
| b5ac792853 | |||
| 44c319dcc6 | |||
| a442971b81 | |||
| 8b5adb6bc1 | |||
| c3add0ce84 | |||
| 8725b01995 | |||
| 936e8279f9 | |||
| 6193023c8f | |||
| 1800939833 | |||
| 2f48fc2ef9 | |||
| 918ec0574f | |||
| 1139b22141 | |||
| 7598c25fc8 | |||
| 0f6a9277a8 | |||
| 6a8650d484 | |||
| 46d9e02684 | |||
| 367479d163 | |||
| bbbf5a37c8 | |||
| b2ad1fa979 | |||
| 998be5ba09 | |||
| 9e8d279298 | |||
| fd03bb800c | |||
| f80c28cde7 | |||
| b7e5bedf4b | |||
| 994e426d1e | |||
| c5bf0d94f9 | |||
| 8ad9ada015 | |||
| e854261124 | |||
| 0370b4eb55 | |||
| 5eef5b4e31 | |||
| 61d9c051de | |||
| 5ea1bff1e6 | |||
| bc035cff6a | |||
| f2c008602d | |||
| b8073bca0f | |||
| c30a61441b | |||
| f0f279eec5 | |||
| 464cc8449e | |||
| ee2e57d702 | |||
| a2a5fb166e | |||
| f59f5dfaa3 | |||
| 13f12227bd | |||
| c9db21063b | |||
| 125184f300 | |||
| 8068517808 | |||
| a4f6bbda5e | |||
| 8b2598fdac | |||
| c36fe78bd5 | |||
| bd8385464e | |||
| 8c996b5bc6 | |||
| d1901939e1 | |||
| 41ec11fb66 | |||
| df47490d85 | |||
| a2f6d14b15 | |||
| 6a6c6d6502 | |||
| 2d98c5d311 | |||
| 03b13f8ab0 | |||
| 6e74ad942f | |||
| 0f85f247b6 | |||
| ac1d350aaf | |||
| 5f8e1ef307 | |||
| aa5d4ad078 | |||
| 44d9572f5c | |||
| 3314001205 | |||
| ce1084abc7 | |||
| ea8563cb19 | |||
| b3c1c83677 | |||
| fcbc3b4d75 | |||
| 39e1a0ffe8 | |||
| 99bbd8805c | |||
| e7af85ffba | |||
| 75f3277102 | |||
| 216f8b64bf | |||
| bc301c5fdb | |||
| ae9d6de093 | |||
| 7a4c3184a1 | |||
| 58307aee9c | |||
| 409242507c | |||
| 0a8194fa10 | |||
| eaac40df3a | |||
| 58b488b840 | |||
| 1676702a09 | |||
| 3943848add | |||
| 831dbeaab5 | |||
| 49e719ded8 | |||
| c2d2409bff | |||
| 485f57d86b | |||
| 0e6f303eec | |||
| ad94ae05c1 | |||
| 89dc8c0463 | |||
| 77640c5cc0 | |||
| cdc8cfeb7d | |||
| 4740df2e2c | |||
| ec0f45a488 | |||
| 9d7e06c255 | |||
| 4b93e78dd9 | |||
| 5ced8c90d3 | |||
| 9d22711368 | |||
| 23e6295dd2 | |||
| bbd75ab7ef | |||
| 7508fee62c | |||
| fe88760766 | |||
| 75ece38725 | |||
| b53032a8a5 | |||
| e7f8952ece | |||
| 82a6ae0791 | |||
| d303098e35 | |||
| 1a69ce2d97 | |||
| eedebe1c52 | |||
| 9e61f80809 | |||
| ff591c68a0 | |||
| 8761b9d7e0 | |||
| 1b5125b0a8 | |||
| 9ba885361d | |||
| afa34d83b2 | |||
| d93c914dd3 | |||
| 715a2dd448 | |||
| 58bf60695d | |||
| fbe7954f2e | |||
| d122a8fc3b | |||
| 3998dc124a | |||
| 3d2da9c0db | |||
| 3189d3af4c | |||
| 1b3a99cc5e | |||
| 935c43e88f | |||
| 2893c031ab | |||
| 94cdb16ca9 | |||
| 84d4066986 | |||
| 2153c2dd10 | |||
| a7393c3492 | |||
| 794fece8cc | |||
| 1bf95e85a0 | |||
| a85357a8af | |||
| 7bf3e02f6c | |||
| 459e8d0b24 | |||
| 93585d846a | |||
| 4eb5c4603e | |||
| f42ad5ffc6 | |||
| d0bd8e50be | |||
| 337e3f4449 |
+1
-1
@@ -9,4 +9,4 @@ community_bridge: # Replace with a single Community Bridge project-name e.g., cl
|
|||||||
liberapay: # Replace with a single Liberapay username
|
liberapay: # Replace with a single Liberapay username
|
||||||
issuehunt: # Replace with a single IssueHunt username
|
issuehunt: # Replace with a single IssueHunt username
|
||||||
otechie: # Replace with a single Otechie username
|
otechie: # Replace with a single Otechie username
|
||||||
custom: https://salt.bountysource.com/checkout/amount?team=portapack-mayhem
|
custom: # https://salt.bountysource.com/checkout/amount?team=portapack-mayhem
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
name: Bug report
|
||||||
|
description: File a bug reports regarding the firmware.
|
||||||
|
labels:
|
||||||
|
- bug
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
Thank you for taking the time to fill out an issue, this template is meant for any issues related to the 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
|
||||||
|
- type: textarea
|
||||||
|
id: description
|
||||||
|
attributes:
|
||||||
|
label: Describe the bug.
|
||||||
|
description: "A clear and concise description of what the bug is."
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: repro
|
||||||
|
attributes:
|
||||||
|
label: Reproduction
|
||||||
|
description: "How can this bug be reproduced?"
|
||||||
|
placeholder: |
|
||||||
|
1. Switch on...
|
||||||
|
2. Press button '....'
|
||||||
|
3. Wait for the end of the universe
|
||||||
|
4. It burns
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: expected
|
||||||
|
attributes:
|
||||||
|
label: Expected behavior
|
||||||
|
description: "A clear and concise description of what you expected to happen"
|
||||||
|
placeholder: |
|
||||||
|
1. Generates file on...
|
||||||
|
2. I get a cheeseburger...
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
id: target
|
||||||
|
attributes:
|
||||||
|
label: Environment/versions
|
||||||
|
description: Specify extra details about versions and environments affected
|
||||||
|
- type: textarea
|
||||||
|
id: anything-else
|
||||||
|
attributes:
|
||||||
|
label: Anything else?
|
||||||
|
description: Let us know if you have anything else to share.
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
name: Feature Request
|
||||||
|
description: For feature requests regarding the firmware.
|
||||||
|
labels:
|
||||||
|
- enhancement
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
Thank you for taking the time to fill out an issue, this template is meant for any feature suggestions.
|
||||||
|
Please try the latest nightly release before submitting this, make sure it has not been implemented already. You can find the latest nightly version here: https://github.com/portapack-mayhem/mayhem-firmware/releases
|
||||||
|
- type: textarea
|
||||||
|
id: proposal
|
||||||
|
attributes:
|
||||||
|
label: "Description of the feature you're suggesting."
|
||||||
|
description: |
|
||||||
|
Please describe your feature request in as many details as possible.
|
||||||
|
- Describe what it should do.
|
||||||
|
- Note whether it is to extend existing functionality or introduce new functionality.
|
||||||
|
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,45 @@
|
|||||||
|
name: Firmware/boot issue
|
||||||
|
description: If you are having firmware upgrade or booting problems.
|
||||||
|
labels:
|
||||||
|
- firmware
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: >2-
|
||||||
|
Thank you for taking the time to fill out an issue, this template is meant for any issues related to the Mayhem firmware.
|
||||||
|
Before creating this issue, **do the following**:
|
||||||
|
* Read the Wiki on booting:
|
||||||
|
https://github.com/portapack-mayhem/mayhem-firmware/wiki/Won't-boot
|
||||||
|
|
||||||
|
* Read:
|
||||||
|
https://github.com/portapack-mayhem/mayhem-firmware/wiki/Update-firmware
|
||||||
|
|
||||||
|
* Watch carefully: https://www.youtube.com/watch?v=_zx4ZvurgOs
|
||||||
|
|
||||||
|
* (if you are not in Windows) also check:
|
||||||
|
https://www.youtube.com/watch?v=kjFB58Y1TAo
|
||||||
|
|
||||||
|
* Check hardware versions:
|
||||||
|
https://github.com/portapack-mayhem/mayhem-firmware/wiki/PortaPack-Versions
|
||||||
|
- type: textarea
|
||||||
|
id: problem
|
||||||
|
attributes:
|
||||||
|
label: What happened?
|
||||||
|
description: If after following the above troubleshooting the problem still remains
|
||||||
|
placeholder: Describe what was is the problem
|
||||||
|
- type: input
|
||||||
|
id: hardware
|
||||||
|
attributes:
|
||||||
|
label: Your hardware
|
||||||
|
description: Check the link with the versions above
|
||||||
|
placeholder: Which device you have?
|
||||||
|
- type: checkboxes
|
||||||
|
id: firmware_update_before
|
||||||
|
attributes:
|
||||||
|
label: Tests
|
||||||
|
description: >-
|
||||||
|
Things might be confusing the first time, please check the documentation
|
||||||
|
above
|
||||||
|
options:
|
||||||
|
- label: I managed to update/boot previously
|
||||||
|
required: false
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
---
|
|
||||||
name: Bug report
|
|
||||||
about: Create a report to help us improve the software
|
|
||||||
title: ''
|
|
||||||
labels: bug
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
(Please try the latest nightly release before submitting this. You can find the latest nightly version here: https://github.com/eried/portapack-mayhem/releases)
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
**Describe the bug**
|
|
||||||
A clear and concise description of what the bug is.
|
|
||||||
|
|
||||||
**To Reproduce**
|
|
||||||
Steps to reproduce the behavior:
|
|
||||||
1. Go to '...'
|
|
||||||
2. Tap on '....'
|
|
||||||
3.
|
|
||||||
|
|
||||||
**Expected behavior**
|
|
||||||
A clear and concise description of what you expected to happen.
|
|
||||||
|
|
||||||
**Affected versions**
|
|
||||||
Please write any difference related with the Expected behavior, on the following versions:
|
|
||||||
* Latest Stable release:
|
|
||||||
* Latest Nightly release:
|
|
||||||
* Previous working release:
|
|
||||||
|
|
||||||
**Additional**
|
|
||||||
If the bug is difficult to explain, additionally to the text please include images and videos.
|
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
blank_issues_enabled: false
|
||||||
|
contact_links:
|
||||||
|
- name: Need more help?
|
||||||
|
url: https://discord.com/invite/tuwVMv3
|
||||||
|
about: For any question that does not fit as an issue, check our discord.
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
---
|
|
||||||
name: Feature request
|
|
||||||
about: Suggest an idea for this project
|
|
||||||
title: ''
|
|
||||||
labels: enhancement
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Is your feature request related to a problem? Please describe.**
|
|
||||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
|
||||||
|
|
||||||
**Describe the solution you'd like**
|
|
||||||
A clear and concise description of what you want to happen. Remember that adding stuff is always possible, but time is a limited resource for everyone. Check the wiki for more information how to compile the firmware and try to explore modifying the code yourself.
|
|
||||||
|
|
||||||
**Describe alternatives you've considered**
|
|
||||||
A clear and concise description of any alternative solutions or features you've considered.
|
|
||||||
|
|
||||||
**Additional**
|
|
||||||
If the suggestion is difficult to explain, additionally to the text please include images and videos.
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
---
|
|
||||||
name: Problem upgrading the firmware or booting
|
|
||||||
about: If you are having firmware upgrade or booting problems
|
|
||||||
title: ''
|
|
||||||
labels: 'firmware'
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
----
|
|
||||||
Before creating this issue, **do the following**:
|
|
||||||
* Read the Wiki on booting: https://github.com/eried/portapack-mayhem/wiki/Won't-boot
|
|
||||||
* Read: https://github.com/eried/portapack-havoc/wiki/Update-firmware
|
|
||||||
* Watch carefully: https://www.youtube.com/watch?v=_zx4ZvurgOs
|
|
||||||
* (if you are not in Windows) also check: https://www.youtube.com/watch?v=kjFB58Y1TAo
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
**Describe the issue**
|
|
||||||
A clear and concise description of what the issue you are facing is.
|
|
||||||
|
|
||||||
**To Reproduce**
|
|
||||||
Steps to reproduce the behavior:
|
|
||||||
1. Go to '...'
|
|
||||||
2. Tap on '....'
|
|
||||||
|
|
||||||
|
|
||||||
**My Hardware**
|
|
||||||
Please specify what PortaPack hardware version you are using.
|
|
||||||
You can find the list of versions here: https://github.com/eried/portapack-mayhem/wiki/PortaPack-Versions
|
|
||||||
|
|
||||||
**Affected versions**
|
|
||||||
Please tell us what version you are running.
|
|
||||||
If your device is still functional, try the latest nightly release before submitting this.
|
|
||||||
You can find the latest nightly version here https://github.com/eried/portapack-mayhem/releases
|
|
||||||
|
|
||||||
**Were you able to update the firmware before?**
|
|
||||||
Things might be confusing the first time, please check the video available on the link above.
|
|
||||||
|
|
||||||
**Can you try the upgrade with a different PC/Portapack/HackRF?**
|
|
||||||
If is possible, swap hardware and try again. Also, try different USB cables, even if the one you are using works fine for other purposes.
|
|
||||||
|
|
||||||
**Additional**
|
|
||||||
If the issue is difficult to explain, additionally to the text please include images and videos.
|
|
||||||
@@ -6,8 +6,8 @@ from datetime import datetime, timedelta, timezone
|
|||||||
|
|
||||||
# Set up your personal access token and the repository details
|
# Set up your personal access token and the repository details
|
||||||
token = os.environ.get('GH_TOKEN')
|
token = os.environ.get('GH_TOKEN')
|
||||||
repo_owner = "eried"
|
repo_owner = "portapack-mayhem"
|
||||||
repo_name = "portapack-mayhem"
|
repo_name = "mayhem-firmware"
|
||||||
|
|
||||||
|
|
||||||
def print_stable_changelog(previous_sha):
|
def print_stable_changelog(previous_sha):
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ jobs:
|
|||||||
- name: check latest commit is less than a day
|
- name: check latest commit is less than a day
|
||||||
id: should_run
|
id: should_run
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
run: test -z $(git rev-list --after="24 hours" ${{ github.sha }}) && echo "::set-output name=should_run::false"
|
run: test -z $(git rev-list --after="24 hours" ${{ github.sha }}) && echo "should_run=false" >> $GITHUB_OUTPUT
|
||||||
build:
|
build:
|
||||||
needs: check_date
|
needs: check_date
|
||||||
if: ${{ needs.check_date.outputs.should_run != 'false' }}
|
if: ${{ needs.check_date.outputs.should_run != 'false' }}
|
||||||
@@ -28,15 +28,20 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Get current date
|
- name: Get current date
|
||||||
id: date
|
id: date
|
||||||
run: echo "::set-output name=date::$(date +'%Y-%m-%d')"
|
run: echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT
|
||||||
- name: Get version date
|
- name: Get version date
|
||||||
id: version_date
|
id: version_date
|
||||||
run: echo "::set-output name=date::n_$(date +'%y%m%d')"
|
run: echo "date=n_$(date +'%y%m%d')" >> $GITHUB_OUTPUT
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@master
|
uses: actions/checkout@master
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
ref: next
|
#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
|
submodules: true
|
||||||
- name: Git Sumbodule Update
|
- name: Git Sumbodule Update
|
||||||
run: |
|
run: |
|
||||||
@@ -49,10 +54,10 @@ jobs:
|
|||||||
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
|
||||||
- name: Create Small SD Card ZIP - No World Map
|
- name: Create Small SD Card ZIP - No World Map
|
||||||
run: |
|
run: |
|
||||||
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version_date.outputs.date }}.bin && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && zip -r sdcard-no-map.zip sdcard
|
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version_date.outputs.date }}.bin && 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
|
- name: Download world map
|
||||||
run: |
|
run: |
|
||||||
wget https://github.com/eried/portapack-mayhem/releases/download/world_map/world_map.zip
|
wget https://github.com/portapack-mayhem/mayhem-firmware/releases/download/world_map/world_map.zip
|
||||||
- name: Unzip world map
|
- name: Unzip world map
|
||||||
run: |
|
run: |
|
||||||
unzip world_map.zip -d sdcard/ADSB
|
unzip world_map.zip -d sdcard/ADSB
|
||||||
@@ -61,16 +66,16 @@ jobs:
|
|||||||
zip -j firmware.zip build/firmware/portapack-h1_h2-mayhem.bin && cd flashing && zip -r ../firmware.zip *
|
zip -j firmware.zip build/firmware/portapack-h1_h2-mayhem.bin && cd flashing && zip -r ../firmware.zip *
|
||||||
- name: Create SD Card ZIP
|
- name: Create SD Card ZIP
|
||||||
run: |
|
run: |
|
||||||
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version_date.outputs.date }}.bin && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && zip -r sdcard.zip sdcard
|
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version_date.outputs.date }}.bin && 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: Create changelog
|
- name: Create changelog
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
CHANGELOG=$(python3 .github/workflows/changelog.py)
|
{
|
||||||
CHANGELOG="${CHANGELOG//'%'/'%25'}"
|
echo 'content<<EOF'
|
||||||
CHANGELOG="${CHANGELOG//$'\n'/'%0A'}"
|
python3 .github/workflows/changelog.py
|
||||||
CHANGELOG="${CHANGELOG//$'\r'/'%0D'}"
|
echo EOF
|
||||||
echo "::set-output name=content::$CHANGELOG"
|
} >> "$GITHUB_OUTPUT"
|
||||||
id: changelog
|
id: changelog
|
||||||
- name: Create Release
|
- name: Create Release
|
||||||
id: create_release
|
id: create_release
|
||||||
@@ -88,6 +93,16 @@ jobs:
|
|||||||
${{ steps.changelog.outputs.content }}
|
${{ steps.changelog.outputs.content }}
|
||||||
draft: false
|
draft: false
|
||||||
prerelease: true
|
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
|
- name: Upload Firmware Asset
|
||||||
id: upload-firmware-asset
|
id: upload-firmware-asset
|
||||||
uses: actions/upload-release-asset@v1
|
uses: actions/upload-release-asset@v1
|
||||||
|
|||||||
@@ -9,22 +9,27 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Get current date
|
- name: Get current date
|
||||||
id: date
|
id: date
|
||||||
run: echo "::set-output name=date::$(date +'%Y-%m-%d')"
|
run: echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@master
|
uses: actions/checkout@master
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
ref: next
|
#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
|
submodules: true
|
||||||
- name: Git Sumbodule Update
|
- name: Git Sumbodule Update
|
||||||
run: |
|
run: |
|
||||||
git submodule update --init --recursive
|
git submodule update --init --recursive
|
||||||
- name: Get version
|
- name: Get version
|
||||||
id: version
|
id: version
|
||||||
run: echo "::set-output name=version::$(cat .github/workflows/version.txt)"
|
run: echo "version=$(cat .github/workflows/version.txt)" >> $GITHUB_OUTPUT
|
||||||
- name: Get past version
|
- name: Get past version
|
||||||
id: past_version
|
id: past_version
|
||||||
run: echo "::set-output name=past_version::$(cat .github/workflows/past_version.txt)"
|
run: echo "past_version=$(cat .github/workflows/past_version.txt)" >> $GITHUB_OUTPUT
|
||||||
- name: Build the Docker image
|
- name: Build the Docker image
|
||||||
run: docker build -t portapack-dev -f dockerfile-nogit . --tag my-image-name:$(date +%s)
|
run: docker build -t portapack-dev -f dockerfile-nogit . --tag my-image-name:$(date +%s)
|
||||||
- name: Make build folder
|
- name: Make build folder
|
||||||
@@ -33,10 +38,10 @@ jobs:
|
|||||||
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
|
||||||
- name: Create Small SD Card ZIP - No World Map
|
- name: Create Small SD Card ZIP - No World Map
|
||||||
run: |
|
run: |
|
||||||
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version.outputs.version }}.bin && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && zip -r sdcard-no-map.zip sdcard
|
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version.outputs.version }}.bin && 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
|
- name: Download world map
|
||||||
run: |
|
run: |
|
||||||
wget https://github.com/eried/portapack-mayhem/releases/download/world_map/world_map.zip
|
wget https://github.com/portapack-mayhem/mayhem-firmware/releases/download/world_map/world_map.zip
|
||||||
- name: Unzip world map
|
- name: Unzip world map
|
||||||
run: |
|
run: |
|
||||||
unzip world_map.zip -d sdcard/ADSB
|
unzip world_map.zip -d sdcard/ADSB
|
||||||
@@ -45,16 +50,16 @@ jobs:
|
|||||||
zip -j firmware.zip build/firmware/portapack-h1_h2-mayhem.bin && cd flashing && zip -r ../firmware.zip *
|
zip -j firmware.zip build/firmware/portapack-h1_h2-mayhem.bin && cd flashing && zip -r ../firmware.zip *
|
||||||
- name: Create SD Card ZIP
|
- name: Create SD Card ZIP
|
||||||
run: |
|
run: |
|
||||||
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version.outputs.version }}.bin && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && zip -r sdcard.zip sdcard
|
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version.outputs.version }}.bin && 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: Create changelog
|
- name: Create changelog
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
CHANGELOG=$(python3 .github/workflows/changelog.py ${{ steps.past_version.outputs.past_version }})
|
{
|
||||||
CHANGELOG="${CHANGELOG//'%'/'%25'}"
|
echo 'content<<EOF'
|
||||||
CHANGELOG="${CHANGELOG//$'\n'/'%0A'}"
|
python3 .github/workflows/changelog.py ${{ steps.past_version.outputs.past_version }}
|
||||||
CHANGELOG="${CHANGELOG//$'\r'/'%0D'}"
|
echo EOF
|
||||||
echo "::set-output name=content::$CHANGELOG"
|
} >> "$GITHUB_OUTPUT"
|
||||||
id: changelog
|
id: changelog
|
||||||
- name: Create Release
|
- name: Create Release
|
||||||
id: create_release
|
id: create_release
|
||||||
@@ -66,21 +71,32 @@ jobs:
|
|||||||
release_name: Mayhem firmware ${{ steps.version.outputs.version }}
|
release_name: Mayhem firmware ${{ steps.version.outputs.version }}
|
||||||
body: |
|
body: |
|
||||||
**Stable release - ${{ steps.version.outputs.version }}**
|
**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/eried/portapack-mayhem/blob/master/README.md) for details.
|
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
|
## Release notes
|
||||||
### Revision (${{ steps.version.outputs.version }}):
|
### Revision (${{ steps.version.outputs.version }}):
|
||||||
${{ steps.changelog.outputs.content }}
|
${{ steps.changelog.outputs.content }}
|
||||||
|
|
||||||
**Full Changelog**: https://github.com/eried/portapack-mayhem/compare/${{ steps.past_version.outputs.past_version }}...${{ steps.version.outputs.version }}
|
**Full Changelog**: https://github.com/portapack-mayhem/mayhem-firmware/compare/${{ steps.past_version.outputs.past_version }}...${{ steps.version.outputs.version }}
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
Check the [wiki](https://github.com/eried/portapack-havoc/wiki/Update-firmware) for details how to upgrade.
|
Check the [wiki](https://github.com/portapack-mayhem/mayhem-firmware/wiki/Update-firmware) for details how to upgrade.
|
||||||
|
__Warning:__ Since release 1.8.0, some applications has been moved to the SD card as we ran out of flash space.
|
||||||
|
|
||||||
### MicroSD card files
|
### MicroSD card files
|
||||||
|
|
||||||
For certain functionality, like 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.
|
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
|
draft: true
|
||||||
prerelease: false
|
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
|
- name: Upload Firmware Asset
|
||||||
id: upload-firmware-asset
|
id: upload-firmware-asset
|
||||||
uses: actions/upload-release-asset@v1
|
uses: actions/upload-release-asset@v1
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
v1.8.0
|
v2.0.1
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
v1.9.0
|
v2.0.2
|
||||||
|
|||||||
+11
-3
@@ -49,13 +49,14 @@
|
|||||||
*.map
|
*.map
|
||||||
*.lst
|
*.lst
|
||||||
.dep/
|
.dep/
|
||||||
build/
|
/build*
|
||||||
CMakeFiles/
|
CMakeFiles/
|
||||||
|
cmake-build-debug/
|
||||||
|
|
||||||
# Debugging
|
# Debugging
|
||||||
.gdbinit*
|
.gdbinit*
|
||||||
|
|
||||||
# Editor/IDE files
|
# Editor/ IDE files
|
||||||
*.sublime-project
|
*.sublime-project
|
||||||
*.sublime-workspace
|
*.sublime-workspace
|
||||||
.vscode
|
.vscode
|
||||||
@@ -68,8 +69,15 @@ CMakeFiles/
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
/firmware/CMakeCache.txt
|
/firmware/CMakeCache.txt
|
||||||
|
|
||||||
# Python env
|
# Python env/ venv and cache
|
||||||
env/
|
env/
|
||||||
|
venv/
|
||||||
|
**/__pycache__/
|
||||||
|
*.pyc
|
||||||
|
|
||||||
# Other
|
# Other
|
||||||
*.bak
|
*.bak
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|||||||
-55
@@ -1,55 +0,0 @@
|
|||||||
language: cpp
|
|
||||||
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- os: linux
|
|
||||||
compiler: gcc
|
|
||||||
cache: apt
|
|
||||||
dist: xenial
|
|
||||||
|
|
||||||
env:
|
|
||||||
global:
|
|
||||||
- PROJECT_NAME=PortaPack-HAVOC
|
|
||||||
- SHORT_COMMIT_HASH=`git rev-parse --short HEAD`
|
|
||||||
- VERSION_STRING=nightly-$SHORT_COMMIT_HASH
|
|
||||||
- BUILD_DATE="`date +%Y-%m-%d`"
|
|
||||||
- BUILD_NAME="$PROJECT_NAME-$BUILD_DATE-$SHORT_COMMIT_HASH"
|
|
||||||
- ARTEFACT_BASE=$TRAVIS_BUILD_DIR/artefacts/
|
|
||||||
- ARTEFACT_PATH=$ARTEFACT_BASE/$BUILD_NAME
|
|
||||||
|
|
||||||
before_install:
|
|
||||||
- sudo add-apt-repository ppa:team-gcc-arm-embedded/ppa -y
|
|
||||||
- sudo apt-get update -q
|
|
||||||
- sudo apt-get install gcc-arm-embedded -y
|
|
||||||
|
|
||||||
script:
|
|
||||||
# TODO: Introduce top-level Makefile, this is lame.
|
|
||||||
- sed -e "s/\#set(VERSION.*/set(VERSION \"$VERSION_STRING\")/" -i".bak" CMakeLists.txt
|
|
||||||
- mkdir build/
|
|
||||||
- pushd build/
|
|
||||||
- cmake ..
|
|
||||||
- make firmware
|
|
||||||
- popd
|
|
||||||
|
|
||||||
after_success:
|
|
||||||
- mkdir -p $ARTEFACT_PATH
|
|
||||||
# Copy firmware to firmware-bin directory
|
|
||||||
- cd $TRAVIS_BUILD_DIR/build
|
|
||||||
- cp firmware/portapack-h1-havoc.bin $ARTEFACT_PATH/
|
|
||||||
- cp hackrf/firmware/hackrf_usb/hackrf_usb.dfu $ARTEFACT_PATH/
|
|
||||||
- cd $TRAVIS_BUILD_DIR
|
|
||||||
- cp LICENSE $ARTEFACT_PATH/
|
|
||||||
# Build the archive
|
|
||||||
- cd $ARTEFACT_BASE
|
|
||||||
- tar -cJvf $ARTEFACT_BASE/$BUILD_NAME.tar.xz $BUILD_NAME
|
|
||||||
- md5sum --binary $BUILD_NAME.tar.xz >MD5SUMS
|
|
||||||
- sha256sum --binary $BUILD_NAME.tar.xz >SHA256SUMS
|
|
||||||
|
|
||||||
addons:
|
|
||||||
apt:
|
|
||||||
packages:
|
|
||||||
- coreutils
|
|
||||||
- tar
|
|
||||||
- sed
|
|
||||||
- cmake
|
|
||||||
- dfu-util
|
|
||||||
Vendored
+148
@@ -0,0 +1,148 @@
|
|||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "(gdb) JTAG probe",
|
||||||
|
"type": "cppdbg",
|
||||||
|
"request": "launch",
|
||||||
|
"miDebuggerPath": "arm-none-eabi-gdb",
|
||||||
|
"targetArchitecture": "arm",
|
||||||
|
"program": "${workspaceRoot}/build/firmware/baseband/baseband_adsbrx.elf",
|
||||||
|
"cwd": "${workspaceRoot}",
|
||||||
|
"setupCommands": [
|
||||||
|
// use logging for troubleshooting
|
||||||
|
//{"text": "set logging file ${workspaceRoot}/build/arm-none-eabi-gdb.log"},
|
||||||
|
//{"text": "set logging on"},
|
||||||
|
{
|
||||||
|
"text": "file '${workspaceRoot}/build/firmware/baseband/baseband_adsbrx.elf'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "target extended-remote /dev/ttyACM0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "monitor swdp_scan"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "attach 1"
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"launchCompleteCommand": "None",
|
||||||
|
"externalConsole": false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "(gdb) OpenOCD m4 baseband",
|
||||||
|
"type": "cppdbg",
|
||||||
|
"request": "launch",
|
||||||
|
"program": "${workspaceRoot}/build/firmware/baseband/baseband_sd_over_usb.elf",
|
||||||
|
"args": [],
|
||||||
|
"cwd": "${workspaceRoot}",
|
||||||
|
// "environment": [
|
||||||
|
// {
|
||||||
|
// "name": "PATH",
|
||||||
|
// "value": "${env:PATH}"
|
||||||
|
// }
|
||||||
|
// ],
|
||||||
|
"externalConsole": false,
|
||||||
|
"MIMode": "gdb",
|
||||||
|
"miDebuggerPath": "arm-none-eabi-gdb",
|
||||||
|
"targetArchitecture": "arm",
|
||||||
|
"debugServerPath": "openocd",
|
||||||
|
"debugServerArgs": "-f interface/ftdi/um232h.cfg -f target/lpc4350.cfg -c \"gdb_breakpoint_override hard\"",
|
||||||
|
"serverStarted": "Listening on port [0-9]+ for gdb connections",
|
||||||
|
"filterStderr": true,
|
||||||
|
"filterStdout": false,
|
||||||
|
"launchCompleteCommand": "None",
|
||||||
|
"postRemoteConnectCommands": [
|
||||||
|
{
|
||||||
|
"description": "Target Remote Device on Port 3333",
|
||||||
|
"text": "target extended-remote :3333",
|
||||||
|
"ignoreFailures": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Respect Hardware Limitations",
|
||||||
|
"text": "set remote hardware-watchpoint-limit 2",
|
||||||
|
"ignoreFailures": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Respect Hardware Limitations",
|
||||||
|
"text": "set remote hardware-breakpoint-limit 4",
|
||||||
|
"ignoreFailures": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Shutdown GDB Server on GDB Detach",
|
||||||
|
"text": "monitor [target current] configure -event gdb-detach { shutdown }",
|
||||||
|
"ignoreFailures": false
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"stopAtConnect": false,
|
||||||
|
"logging": {
|
||||||
|
"exceptions": true,
|
||||||
|
"engineLogging": false,
|
||||||
|
"moduleLoad": true,
|
||||||
|
"programOutput": true,
|
||||||
|
"trace": false,
|
||||||
|
"traceResponse": false
|
||||||
|
},
|
||||||
|
"useExtendedRemote": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "(gdb) OpenOCD m0 application",
|
||||||
|
"type": "cppdbg",
|
||||||
|
"request": "launch",
|
||||||
|
"program": "${workspaceRoot}/build/firmware/application/application.elf",
|
||||||
|
"args": [],
|
||||||
|
"cwd": "${workspaceRoot}",
|
||||||
|
// "environment": [
|
||||||
|
// {
|
||||||
|
// "name": "PATH",
|
||||||
|
// "value": "${env:PATH}"
|
||||||
|
// }
|
||||||
|
// ],
|
||||||
|
"externalConsole": false,
|
||||||
|
"MIMode": "gdb",
|
||||||
|
"miDebuggerPath": "arm-none-eabi-gdb",
|
||||||
|
"targetArchitecture": "arm",
|
||||||
|
"debugServerPath": "openocd",
|
||||||
|
"debugServerArgs": "-f interface/ftdi/um232h.cfg -f target/lpc4350.cfg -c \"gdb_breakpoint_override hard\"",
|
||||||
|
"serverStarted": "Listening on port [0-9]+ for gdb connections",
|
||||||
|
"filterStderr": true,
|
||||||
|
"filterStdout": false,
|
||||||
|
"launchCompleteCommand": "None",
|
||||||
|
"postRemoteConnectCommands": [
|
||||||
|
{
|
||||||
|
"description": "Target Remote Device on Port 3334",
|
||||||
|
"text": "target extended-remote :3334",
|
||||||
|
"ignoreFailures": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Respect Hardware Limitations",
|
||||||
|
"text": "set remote hardware-watchpoint-limit 1",
|
||||||
|
"ignoreFailures": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Respect Hardware Limitations",
|
||||||
|
"text": "set remote hardware-breakpoint-limit 2",
|
||||||
|
"ignoreFailures": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Shutdown GDB Server on GDB Detach",
|
||||||
|
"text": "monitor [target current] configure -event gdb-detach { shutdown }",
|
||||||
|
"ignoreFailures": false
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"stopAtConnect": false,
|
||||||
|
"logging": {
|
||||||
|
"exceptions": true,
|
||||||
|
"engineLogging": false,
|
||||||
|
"moduleLoad": true,
|
||||||
|
"programOutput": true,
|
||||||
|
"trace": false,
|
||||||
|
"traceResponse": false
|
||||||
|
},
|
||||||
|
"useExtendedRemote": true
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
+18
-2
@@ -18,15 +18,17 @@
|
|||||||
# Boston, MA 02110-1301, USA.
|
# Boston, MA 02110-1301, USA.
|
||||||
#
|
#
|
||||||
|
|
||||||
cmake_minimum_required(VERSION 3.0)
|
cmake_minimum_required(VERSION 3.5)
|
||||||
cmake_policy(SET CMP0005 NEW)
|
cmake_policy(SET CMP0005 NEW)
|
||||||
|
|
||||||
set(CMAKE_TOOLCHAIN_FILE ${CMAKE_CURRENT_LIST_DIR}/firmware/toolchain-arm-cortex-m.cmake)
|
set(CMAKE_TOOLCHAIN_FILE ${CMAKE_CURRENT_LIST_DIR}/firmware/toolchain-arm-cortex-m.cmake)
|
||||||
|
|
||||||
project(portapack-h1)
|
project(portapack-h1)
|
||||||
|
|
||||||
|
set(EXPECTED_GCC_VERSION "9.2.1")
|
||||||
|
|
||||||
set(VERSION "$ENV{VERSION_STRING}")
|
set(VERSION "$ENV{VERSION_STRING}")
|
||||||
if ("$ENV{VERSION_STRING}" STREQUAL "")
|
if ("${VERSION}" STREQUAL "")
|
||||||
execute_process(
|
execute_process(
|
||||||
COMMAND git log -n 1 --format=%h
|
COMMAND git log -n 1 --format=%h
|
||||||
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
|
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
|
||||||
@@ -40,6 +42,10 @@ if ("$ENV{VERSION_STRING}" STREQUAL "")
|
|||||||
else (GIT_VERSION_FOUND)
|
else (GIT_VERSION_FOUND)
|
||||||
set(VERSION "${GIT_VERSION}")
|
set(VERSION "${GIT_VERSION}")
|
||||||
endif (GIT_VERSION_FOUND)
|
endif (GIT_VERSION_FOUND)
|
||||||
|
|
||||||
|
set(VERSION_NOHASH "dev")
|
||||||
|
else()
|
||||||
|
set(VERSION_NOHASH "${VERSION}")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
execute_process(
|
execute_process(
|
||||||
@@ -48,6 +54,8 @@ execute_process(
|
|||||||
)
|
)
|
||||||
set(VERSION_MD5 "0x${VERSION_MD5}")
|
set(VERSION_MD5 "0x${VERSION_MD5}")
|
||||||
|
|
||||||
|
message("Building version: ${VERSION} MD5: ${VERSION_MD5}")
|
||||||
|
|
||||||
set(LICENSE_PATH ${CMAKE_CURRENT_LIST_DIR}/LICENSE)
|
set(LICENSE_PATH ${CMAKE_CURRENT_LIST_DIR}/LICENSE)
|
||||||
set(HARDWARE_PATH ${CMAKE_CURRENT_LIST_DIR}/hardware)
|
set(HARDWARE_PATH ${CMAKE_CURRENT_LIST_DIR}/hardware)
|
||||||
|
|
||||||
@@ -64,5 +72,13 @@ set(HACKRF_CPLD_XSVF_PATH ${HACKRF_PATH}/firmware/cpld/sgpio_if/${HACKRF_CPLD_XS
|
|||||||
set(HACKRF_FIRMWARE_DFU_IMAGE ${hackrf_usb_BINARY_DIR}/${HACKRF_FIRMWARE_DFU_FILENAME})
|
set(HACKRF_FIRMWARE_DFU_IMAGE ${hackrf_usb_BINARY_DIR}/${HACKRF_FIRMWARE_DFU_FILENAME})
|
||||||
set(HACKRF_FIRMWARE_BIN_IMAGE ${hackrf_usb_BINARY_DIR}/${HACKRF_FIRMWARE_BIN_FILENAME})
|
set(HACKRF_FIRMWARE_BIN_IMAGE ${hackrf_usb_BINARY_DIR}/${HACKRF_FIRMWARE_BIN_FILENAME})
|
||||||
|
|
||||||
|
# this seems causing some issues (ref. in discord discussions), so temporarily disabled, until figure out the pros and cons and bugs of this tool.
|
||||||
|
#find_program(CCACHE "ccache")
|
||||||
|
#if(CCACHE)
|
||||||
|
# set(CMAKE_C_COMPILER_LAUNCHER ${CCACHE})
|
||||||
|
# set(CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE})
|
||||||
|
# set(ENV{CCACHE_SLOPPINESS} pch_defines,time_macros)
|
||||||
|
#endif(CCACHE)
|
||||||
|
|
||||||
enable_testing()
|
enable_testing()
|
||||||
add_subdirectory(firmware)
|
add_subdirectory(firmware)
|
||||||
|
|||||||
@@ -1,62 +1,63 @@
|
|||||||
|
> [!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
|
# PortaPack Mayhem
|
||||||
|
|
||||||
[](https://travis-ci.com/eried/portapack-mayhem) [](https://github.com/eried/portapack-mayhem/actions/workflows/create_nightly_release.yml) [](https://codescene.io/projects/8381) [](https://github.com/eried/portapack-mayhem/releases) [](https://github.com/eried/portapack-mayhem/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.
|
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://raw.githubusercontent.com/wiki/eried/portapack-mayhem/img/hw_overview_h2_front.png" height="400">](https://github.com/eried/portapack-mayhem/wiki/Hardware-overview) [<img src="https://raw.githubusercontent.com/wiki/eried/portapack-mayhem/img/hw_overview_h2_inside.png" height="400">](https://github.com/eried/portapack-mayhem/wiki/Hardware-overview#portapack-internals)
|
[<img src="https://raw.githubusercontent.com/wiki/portapack-mayhem/mayhem-firmware/img/hw_overview_h2_front.png" height="400">](https://github.com/portapack-mayhem/mayhem-firmware/wiki/Hardware-overview) [<img src="https://raw.githubusercontent.com/wiki/portapack-mayhem/mayhem-firmware/img/hw_overview_h2_inside.png" height="400">](https://github.com/portapack-mayhem/mayhem-firmware/wiki/Hardware-overview#portapack-internals)
|
||||||
|
|
||||||
*[PortaPack H2+HackRF+battery](https://s.click.aliexpress.com/e/_DmU7GQX) (clone) with a custom [3d printed case](https://github.com/eried/portapack-mayhem/wiki/H2-Enclosure)*
|
*[PortaPack H2+HackRF+battery](https://grabify.link/7T28JP) (clone) with a custom [3d printed case](https://github.com/portapack-mayhem/mayhem-firmware/wiki/H2-Enclosure)*
|
||||||
|
|
||||||
# What is this?
|
# What is this?
|
||||||
|
|
||||||
If you are new to *HackRF+PortaPack+Mayhem*, check this video:
|
If you are new to *HackRF+PortaPack+Mayhem*, check these:
|
||||||
|
|
||||||
[](https://www.youtube.com/watch?v=alrFbY5vxt4)
|
[<img alt="It’s TOO Easy to Accidentally Do Illegal Stuff with This" src="https://img.youtube.com/vi/OPckpjBSAOw/maxresdefault.jpg" width="700">](https://grabify.link/X4D5TF)
|
||||||
|
|
||||||
|
[<img alt="What is the HackRF One Portapack H2+?" src="https://img.youtube.com/vi/alrFbY5vxt4/maxresdefault.jpg" width="230">](https://grabify.link/9UZGEW) [<img alt="Beginner's Guide To The HackRF & Portapak With Mayhem" src="https://img.youtube.com/vi/H-bqdWfbhpg/maxresdefault.jpg" width="230">](https://grabify.link/5MU2VH) [<img alt="HackRF 101 : Everything You Need to Know to Get Started!" src="https://img.youtube.com/vi/xGR_PMD9LeU/maxresdefault.jpg" width="230">](https://grabify.link/C0J6ZR)
|
||||||
|
|
||||||
# Frequently Asked Questions
|
# Frequently Asked Questions
|
||||||
|
|
||||||
This repository expands upon the previous work by many people and aims to constantly add new features, bugfixes and generate documentation to make further development easier. [Collaboration](https://github.com/eried/portapack-mayhem/wiki/How-to-collaborate) is always welcomed and appreciated.
|
This repository expands upon the previous work by many people and aims to constantly add new features, bugfixes and generate documentation to make further development easier. [Collaboration](https://github.com/portapack-mayhem/mayhem-firmware/wiki/How-to-collaborate) is always welcomed and appreciated.
|
||||||
|
|
||||||
## What to buy?
|
## What to buy?
|
||||||
|
|
||||||
:heavy_check_mark: A recommended one is this [PortaPack H2](https://s.click.aliexpress.com/e/_DmU7GQX), that includes everything you need with the plastic case "inspired" on [this](https://github.com/eried/portapack-mayhem/wiki/H2-Enclosure).
|
:heavy_check_mark:  The fabulous [PortaPack H4M Mayhem](https://grabify.link/VPMPSL), featuring numerous improvements and accessories. Sold by our friends at OpenSourceSDRLab. Join their giveaways on discord (check the badge on top).
|
||||||
|
|
||||||
:heavy_check_mark: Our friends at OpenSourceSDRLab give away five units every three months in our discord (check the badge on top) of their [PortaPack H2](https://www.aliexpress.com/item/4000247041639.html?gatewayAdapt=4itemAdapt), you can support them too by ordering.
|
:heavy_check_mark: A recommended one is this [PortaPack H2](https://grabify.link/7T28JP), that includes everything you need with the plastic case "inspired" on [this](https://github.com/portapack-mayhem/mayhem-firmware/wiki/H2-Enclosure).
|
||||||
|
|
||||||
:heavy_check_mark: Another popular option is the clone of the [PortaPack H1](https://s.click.aliexpress.com/e/_Dkbqs2X).
|
:heavy_check_mark: Some individuals lean towards the [H2 with a metal enclosure](https://grabify.link/HTDXG5), 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:
|
: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:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
:warning: If it looks **too different**, this might mean that they are using their own recipe, check the [different models](https://github.com/eried/portapack-mayhem/wiki/PortaPack-Versions) in our wiki. For example all the H3 and clones of that version use their own version of the firmware. They do not contribute the changes back and eventually you will be left with a device that nobody maintains:
|
:warning: If it looks **too different**, this might mean that they are using their own recipe, check the [different models](https://github.com/portapack-mayhem/mayhem-firmware/wiki/PortaPack-Versions) in our wiki. For example all the H3 and clones of that version use their own version of the firmware. They do not contribute the changes back and eventually you will be left with a device that nobody maintains:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Where is the latest version?
|
## Where is the latest version?
|
||||||
|
|
||||||
The current **stable release** is on the [](https://github.com/eried/portapack-mayhem/releases/latest) page. Follow the instructions you can find in the release description. The **latest (nightly) release** can be found [here](https://github.com/eried/portapack-mayhem/releases/).
|
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
|
## How can I collaborate
|
||||||
You can write [documentation](https://github.com/eried/portapack-mayhem/wiki), fix bugs and [answer issues](https://github.com/eried/portapack-mayhem/issues) or add new functionality. Please check the following [guide](https://github.com/eried/portapack-mayhem/wiki/How-to-collaborate) with 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 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/eried/portapack-mayhem/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).
|
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).
|
||||||
|
|
||||||
[](https://github.com/eried/portapack-mayhem/graphs/contributors)
|
[](https://github.com/portapack-mayhem/mayhem-firmware/graphs/contributors)
|
||||||
|
|
||||||
To support the people behind the hardware, please buy a genuine [HackRF](https://greatscottgadgets.com/hackrf/) and [PortaPack](https://store.sharebrained.com/products/portapack-for-hackrf-one-kit).
|
To support the people behind the hardware, please buy a genuine [HackRF](https://greatscottgadgets.com/hackrf/) and [PortaPack](https://store.sharebrained.com/products/portapack-for-hackrf-one-kit).
|
||||||
|
|
||||||
## What if I really want something specific?
|
## What if I really want something specific?
|
||||||
If what you need can be relevant in general, you can [request a feature](https://github.com/eried/portapack-mayhem/issues/new?labels=enhancement&template=feature_request.md).
|
If what you need can be relevant in general, you can [request a feature](https://github.com/portapack-mayhem/mayhem-firmware/issues/new?labels=enhancement&template=feature_request.md). Alternatively, go to our Discord by clicking the chat badge on [top](#portapack-mayhem) and discuss there.
|
||||||
|
|
||||||
<del>You can create a bounty and invite people to your own bounty. This will incentivize coders to work on a new feature, solving a bug or even writting documentation. Start a bounty by [creating](https://github.com/eried/portapack-mayhem/issues/new/choose) or [choosing](https://github.com/eried/portapack-mayhem/issues/) an existing issue. Then, go to [Bountysource](https://www.bountysource.com/) and post a bounty using the link to that specific [issue](https://www.bountysource.com/teams/portapack-mayhem/issues).</del>
|
|
||||||
|
|
||||||
<del>Promote your bounty over our Discord by clicking the chat badge on [top](#portapack-mayhem).</del>
|
|
||||||
|
|
||||||
Bountysource has not been reliable lately, so until this changes, please **DO NOT** post a bounty there. Go to our Discord by clicking the chat badge on [top](#portapack-mayhem) and discuss there.
|
|
||||||
|
|
||||||
## What if I need help?
|
## What if I need help?
|
||||||
First, check the [documentation](https://github.com/eried/portapack-mayhem/wiki). If you find a bug or you think the problem is related to the current repository, please open an [issue](https://github.com/eried/portapack-mayhem/issues/new/choose).
|
First, check the [documentation](https://github.com/portapack-mayhem/mayhem-firmware/wiki). If you find a bug or you think the problem is related to the current repository, please open an [issue](https://github.com/portapack-mayhem/mayhem-firmware/issues/new/choose).
|
||||||
|
|
||||||
You can reach the [official community](https://www.facebook.com/groups/177623356165819) in Facebook, and our Discord by clicking the chat badge on [top](#portapack-mayhem).
|
You can reach the [official community](https://www.facebook.com/groups/177623356165819) in Facebook, and our Discord by clicking the chat badge on [top](#portapack-mayhem).
|
||||||
|
|||||||
+16
-15
@@ -10,32 +10,33 @@ VOLUME /havoc
|
|||||||
WORKDIR /havoc/firmware
|
WORKDIR /havoc/firmware
|
||||||
|
|
||||||
# Fetch dependencies from APT
|
# Fetch dependencies from APT
|
||||||
RUN apt-get update && \
|
RUN apt-get update \
|
||||||
apt-get install -y git tar wget dfu-util cmake python3 ccache bzip2 liblz4-tool curl && \
|
&& apt-get install -y git tar wget dfu-util cmake python3 ccache bzip2 liblz4-tool curl ninja-build \
|
||||||
apt-get -qy autoremove
|
&& apt-get -qy autoremove \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
#Install current pip from PyPa
|
#Install current pip from PyPa
|
||||||
RUN curl https://bootstrap.pypa.io/pip/3.4/get-pip.py -o get-pip.py && \
|
RUN curl https://bootstrap.pypa.io/pip/3.4/get-pip.py -o get-pip.py && \
|
||||||
python3 get-pip.py
|
python3 get-pip.py
|
||||||
|
|
||||||
#Fetch additional dependencies from Python 3.x pip
|
#Fetch additional dependencies from Python 3.x pip
|
||||||
RUN pip install pyyaml
|
RUN pip install pyyaml \
|
||||||
RUN ln -s /usr/bin/python3 /usr/bin/python && \
|
&& ln -s /usr/bin/python3 /usr/bin/python \
|
||||||
ln -s /usr/bin/pip3 /usr/bin/pip
|
&& ln -s /usr/bin/pip3 /usr/bin/pip
|
||||||
|
|
||||||
ENV LANG C.UTF-8
|
ENV LANG C.UTF-8
|
||||||
ENV LC_ALL C.UTF-8
|
ENV LC_ALL C.UTF-8
|
||||||
|
|
||||||
# Grab the GNU ARM toolchain from arm.com
|
# Grab the GNU ARM toolchain from arm.com
|
||||||
# Then extract contents to /opt/build/armbin/
|
# Then extract contents to /opt/build/armbin/
|
||||||
RUN mkdir /opt/build && cd /opt/build && \
|
RUN mkdir /opt/build \
|
||||||
wget -O gcc-arm-none-eabi $ARMBINURL && \
|
&& cd /opt/build \
|
||||||
mkdir armbin && \
|
&& wget -O gcc-arm-none-eabi $ARMBINURL \
|
||||||
tar --strip=1 -xjvf gcc-arm-none-eabi -C armbin
|
&& mkdir armbin \
|
||||||
|
&& tar --strip=1 -xjvf gcc-arm-none-eabi -C armbin
|
||||||
|
|
||||||
# Configure CCACHE
|
ADD firmware/tools/docker-entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||||
RUN mkdir ~/bin && cd ~/bin && \
|
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
|
||||||
for tool in gcc g++ cpp c++;do ln -s $(which ccache) arm-none-eabi-$tool;done
|
|
||||||
|
|
||||||
CMD cd .. && cd build && \
|
# replace make with ninja temporarily while building your image if you prefer to use that by default
|
||||||
cmake .. && make firmware
|
CMD ["make"]
|
||||||
|
|||||||
@@ -19,9 +19,5 @@ RUN apk add --no-cache g++ gcc clang clang-static clang-dev llvm-dev llvm-static
|
|||||||
ENV LANG C.UTF-8
|
ENV LANG C.UTF-8
|
||||||
ENV LC_ALL C.UTF-8
|
ENV LC_ALL C.UTF-8
|
||||||
|
|
||||||
# Configure CCACHE
|
|
||||||
RUN mkdir ~/bin && cd ~/bin && \
|
|
||||||
for tool in gcc g++ cpp c++;do ln -s $(which ccache) arm-none-eabi-$tool;done
|
|
||||||
|
|
||||||
CMD cd .. && cd build && \
|
CMD cd .. && cd build && \
|
||||||
cmake .. && make firmware
|
cmake .. && make ppfw
|
||||||
|
|||||||
+16
-15
@@ -10,32 +10,33 @@ VOLUME /havoc
|
|||||||
WORKDIR /havoc/firmware
|
WORKDIR /havoc/firmware
|
||||||
|
|
||||||
# Fetch dependencies from APT
|
# Fetch dependencies from APT
|
||||||
RUN apt-get update && \
|
RUN apt-get update \
|
||||||
apt-get install -y git tar wget dfu-util cmake python3 ccache bzip2 liblz4-tool curl && \
|
&& apt-get install -y git tar wget dfu-util cmake python3 ccache bzip2 liblz4-tool curl ninja-build \
|
||||||
apt-get -qy autoremove
|
&& apt-get -qy autoremove \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
#Install current pip from PyPa
|
#Install current pip from PyPa
|
||||||
RUN curl https://bootstrap.pypa.io/pip/3.4/get-pip.py -o get-pip.py && \
|
RUN curl https://bootstrap.pypa.io/pip/3.4/get-pip.py -o get-pip.py && \
|
||||||
python3 get-pip.py
|
python3 get-pip.py
|
||||||
|
|
||||||
#Fetch additional dependencies from Python 3.x pip
|
#Fetch additional dependencies from Python 3.x pip
|
||||||
RUN pip install pyyaml
|
RUN pip install pyyaml \
|
||||||
RUN ln -s /usr/bin/python3 /usr/bin/python && \
|
&& ln -s /usr/bin/python3 /usr/bin/python \
|
||||||
ln -s /usr/bin/pip3 /usr/bin/pip
|
&& ln -s /usr/bin/pip3 /usr/bin/pip
|
||||||
|
|
||||||
ENV LANG C.UTF-8
|
ENV LANG C.UTF-8
|
||||||
ENV LC_ALL C.UTF-8
|
ENV LC_ALL C.UTF-8
|
||||||
|
|
||||||
# Grab the GNU ARM toolchain from arm.com
|
# Grab the GNU ARM toolchain from arm.com
|
||||||
# Then extract contents to /opt/build/armbin/
|
# Then extract contents to /opt/build/armbin/
|
||||||
RUN mkdir /opt/build && cd /opt/build && \
|
RUN mkdir /opt/build \
|
||||||
wget -O gcc-arm-none-eabi $ARMBINURL && \
|
&& cd /opt/build \
|
||||||
mkdir armbin && \
|
&& wget -O gcc-arm-none-eabi $ARMBINURL \
|
||||||
tar --strip=1 -xjvf gcc-arm-none-eabi -C armbin
|
&& mkdir armbin \
|
||||||
|
&& tar --strip=1 -xjvf gcc-arm-none-eabi -C armbin
|
||||||
|
|
||||||
# Configure CCACHE
|
ADD firmware/tools/docker-entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||||
RUN mkdir ~/bin && cd ~/bin && \
|
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
|
||||||
for tool in gcc g++ cpp c++;do ln -s $(which ccache) arm-none-eabi-$tool;done
|
|
||||||
|
|
||||||
CMD cd .. && cd build && \
|
# replace make with ninja temporarily while building your image if you prefer to use that by default
|
||||||
cmake .. && make firmware
|
CMD ["make"]
|
||||||
|
|||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta http-equiv="refresh" content="0; URL=https://github.com/eried/portapack-mayhem/" />
|
<meta http-equiv="refresh" content="0; URL=https://github.com/portapack-mayhem/mayhem-firmware/" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<p>If you are not redirected, <a href="https://github.com/eried/portapack-mayhem/">click here</a>.</p>
|
<p>If you are not redirected, <a href="https://github.com/portapack-mayhem/mayhem-firmware/">click here</a>.</p>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
+53
-2
@@ -18,6 +18,8 @@
|
|||||||
# Boston, MA 02110-1301, USA.
|
# Boston, MA 02110-1301, USA.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
cmake_minimum_required(VERSION 3.5)
|
||||||
|
|
||||||
project(firmware)
|
project(firmware)
|
||||||
|
|
||||||
set(BASEBAND ${PROJECT_SOURCE_DIR}/baseband)
|
set(BASEBAND ${PROJECT_SOURCE_DIR}/baseband)
|
||||||
@@ -33,9 +35,22 @@ set(LZ4 lz4)
|
|||||||
|
|
||||||
set(FIRMWARE_NAME portapack-h1_h2-mayhem)
|
set(FIRMWARE_NAME portapack-h1_h2-mayhem)
|
||||||
set(FIRMWARE_FILENAME ${FIRMWARE_NAME}.bin)
|
set(FIRMWARE_FILENAME ${FIRMWARE_NAME}.bin)
|
||||||
|
set(PPFW_FILENAME "portapack-mayhem_OCI.ppfw.tar")
|
||||||
|
|
||||||
|
# In our current build container cmake need a little help to get the version :)
|
||||||
|
if(NOT DEFINED ${CMAKE_CXX_COMPILER_VERSION})
|
||||||
|
execute_process(COMMAND bash "-c" "arm-none-eabi-g++ -v 2>&1 | grep 'gcc version' | awk '{print $3}'" OUTPUT_VARIABLE CMAKE_CXX_COMPILER_VERSION)
|
||||||
|
string(STRIP ${CMAKE_CXX_COMPILER_VERSION} CMAKE_CXX_COMPILER_VERSION)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(GCC_VERSION_MISMATCH 0)
|
||||||
|
if(NOT ${CMAKE_CXX_COMPILER_VERSION} VERSION_EQUAL ${EXPECTED_GCC_VERSION})
|
||||||
|
set(GCC_VERSION_MISMATCH 1)
|
||||||
|
endif()
|
||||||
|
|
||||||
add_subdirectory(application)
|
add_subdirectory(application)
|
||||||
add_subdirectory(baseband)
|
add_subdirectory(baseband)
|
||||||
|
add_subdirectory(standalone)
|
||||||
add_subdirectory(test)
|
add_subdirectory(test)
|
||||||
|
|
||||||
# NOTE: Dependencies break if the .bin files aren't included in DEPENDS. WTF, CMake?
|
# NOTE: Dependencies break if the .bin files aren't included in DEPENDS. WTF, CMake?
|
||||||
@@ -48,10 +63,19 @@ add_custom_command(
|
|||||||
)
|
)
|
||||||
|
|
||||||
add_custom_target(
|
add_custom_target(
|
||||||
firmware ALL
|
firmware
|
||||||
DEPENDS ${FIRMWARE_FILENAME} ${HACKRF_FIRMWARE_DFU_FILENAME}
|
DEPENDS ${FIRMWARE_FILENAME} ${HACKRF_FIRMWARE_DFU_FILENAME}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if(${GCC_VERSION_MISMATCH})
|
||||||
|
set(COMPILER_MISMATCH_MESSAGE "WARNING: Compiler version mismatch, please use the official compiler version ${EXPECTED_GCC_VERSION} when sharing builds! Current compiler version: ${CMAKE_CXX_COMPILER_VERSION}")
|
||||||
|
message(${COMPILER_MISMATCH_MESSAGE})
|
||||||
|
add_custom_command(
|
||||||
|
TARGET firmware POST_BUILD
|
||||||
|
COMMAND echo ${COMPILER_MISMATCH_MESSAGE}
|
||||||
|
VERBATIM)
|
||||||
|
endif()
|
||||||
|
|
||||||
add_custom_target(
|
add_custom_target(
|
||||||
program
|
program
|
||||||
COMMAND ${PROJECT_SOURCE_DIR}/tools/enter_mode.sh hackrf
|
COMMAND ${PROJECT_SOURCE_DIR}/tools/enter_mode.sh hackrf
|
||||||
@@ -65,7 +89,23 @@ add_custom_target(
|
|||||||
program-external-apps
|
program-external-apps
|
||||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||||
COMMAND ${PROJECT_SOURCE_DIR}/tools/copy_external_apps.sh
|
COMMAND ${PROJECT_SOURCE_DIR}/tools/copy_external_apps.sh
|
||||||
DEPENDS program
|
DEPENDS program standalone_apps
|
||||||
|
)
|
||||||
|
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT ${PPFW_FILENAME}
|
||||||
|
|
||||||
|
COMMAND rm -rf firmware_tar
|
||||||
|
COMMAND mkdir -p firmware_tar/FIRMWARE
|
||||||
|
# Using VERSION_NOHASH to avoid dev builds piling up in the FIRMWARE folder of the sd card of testers in #test-drive
|
||||||
|
COMMAND cp ${FIRMWARE_FILENAME} firmware_tar/FIRMWARE/portapack-mayhem_${VERSION_NOHASH}.bin
|
||||||
|
COMMAND mkdir -p firmware_tar/APPS
|
||||||
|
COMMAND cp application/*.ppma firmware_tar/APPS
|
||||||
|
COMMAND cp standalone/*/*.ppmp firmware_tar/APPS
|
||||||
|
COMMAND cd firmware_tar && tar -cvaf ../${PPFW_FILENAME} *
|
||||||
|
DEPENDS firmware ${FIRMWARE_FILENAME} standalone_apps
|
||||||
|
# Dont use VERBATIM here as it prevents usage of globbing (*)
|
||||||
|
# There shouldnt be any funny business in the filenames above :)
|
||||||
)
|
)
|
||||||
|
|
||||||
# TODO: Bad hack to fix location of LICENSE file for tar.
|
# TODO: Bad hack to fix location of LICENSE file for tar.
|
||||||
@@ -87,7 +127,18 @@ add_custom_command(
|
|||||||
DEPENDS ${FIRMWARE_NAME}-${VERSION}.tar.bz2 ${FIRMWARE_NAME}-${VERSION}.zip
|
DEPENDS ${FIRMWARE_NAME}-${VERSION}.tar.bz2 ${FIRMWARE_NAME}-${VERSION}.zip
|
||||||
)
|
)
|
||||||
|
|
||||||
|
add_custom_target(
|
||||||
|
ppfw ALL
|
||||||
|
DEPENDS ${PPFW_FILENAME}
|
||||||
|
)
|
||||||
|
|
||||||
|
add_custom_target(
|
||||||
|
oci
|
||||||
|
DEPENDS ${PPFW_FILENAME}
|
||||||
|
)
|
||||||
|
|
||||||
add_custom_target(
|
add_custom_target(
|
||||||
release
|
release
|
||||||
DEPENDS MD5SUMS SHA256SUMS
|
DEPENDS MD5SUMS SHA256SUMS
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ if(cpp20_supported)
|
|||||||
else()
|
else()
|
||||||
set(USE_CPPOPT "-std=c++17")
|
set(USE_CPPOPT "-std=c++17")
|
||||||
endif()
|
endif()
|
||||||
set(USE_CPPOPT "${USE_CPPOPT} -flto -fno-rtti -fno-exceptions -Weffc++ -Wuninitialized -Wno-volatile")
|
set(USE_CPPOPT "${USE_CPPOPT} -fno-rtti -fno-exceptions -Weffc++ -Wuninitialized -Wno-volatile")
|
||||||
|
|
||||||
# Enable this if you want the linker to remove unused code and data
|
# Enable this if you want the linker to remove unused code and data
|
||||||
set(USE_LINK_GC yes)
|
set(USE_LINK_GC yes)
|
||||||
@@ -52,7 +52,7 @@ set(USE_LINK_GC yes)
|
|||||||
# Linker extra options here.
|
# Linker extra options here.
|
||||||
set(USE_LDOPT)
|
set(USE_LDOPT)
|
||||||
|
|
||||||
# Enable this if you want link time optimizations (LTO)
|
# Enable this if you want link time optimizations (LTO) - this flag affects chibios only
|
||||||
set(USE_LTO no)
|
set(USE_LTO no)
|
||||||
|
|
||||||
# If enabled, this option allows to compile the application in THUMB mode.
|
# If enabled, this option allows to compile the application in THUMB mode.
|
||||||
@@ -116,13 +116,12 @@ set(CSRC
|
|||||||
usb_serial_cdc.c
|
usb_serial_cdc.c
|
||||||
usb_serial_descriptor.c
|
usb_serial_descriptor.c
|
||||||
usb_serial_endpoints.c
|
usb_serial_endpoints.c
|
||||||
usb_serial_io.c
|
usb_serial_device_to_host.c
|
||||||
${HACKRF_PATH}/firmware/common/usb.c
|
${HACKRF_PATH}/firmware/common/usb.c
|
||||||
${HACKRF_PATH}/firmware/common/usb_queue.c
|
${HACKRF_PATH}/firmware/common/usb_queue.c
|
||||||
${HACKRF_PATH}/firmware/hackrf_usb/usb_device.c
|
${HACKRF_PATH}/firmware/hackrf_usb/usb_device.c
|
||||||
${HACKRF_PATH}/firmware/common/usb_request.c
|
${HACKRF_PATH}/firmware/common/usb_request.c
|
||||||
${HACKRF_PATH}/firmware/common/usb_standard_request.c
|
${HACKRF_PATH}/firmware/common/usb_standard_request.c
|
||||||
${CHIBIOS}/os/various/shell.c
|
|
||||||
${CHIBIOS}/os/various/chprintf.c
|
${CHIBIOS}/os/various/chprintf.c
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -130,6 +129,7 @@ set(CSRC
|
|||||||
# setting.
|
# setting.
|
||||||
set(CPPSRC
|
set(CPPSRC
|
||||||
main.cpp
|
main.cpp
|
||||||
|
shell.cpp
|
||||||
${COMMON}/acars_packet.cpp
|
${COMMON}/acars_packet.cpp
|
||||||
${COMMON}/adsb.cpp
|
${COMMON}/adsb.cpp
|
||||||
${COMMON}/adsb_frame.cpp
|
${COMMON}/adsb_frame.cpp
|
||||||
@@ -176,7 +176,11 @@ set(CPPSRC
|
|||||||
${COMMON}/ui_language.cpp
|
${COMMON}/ui_language.cpp
|
||||||
${COMMON}/utility.cpp
|
${COMMON}/utility.cpp
|
||||||
${COMMON}/wm8731.cpp
|
${COMMON}/wm8731.cpp
|
||||||
|
${COMMON}/ads1110.cpp
|
||||||
|
${COMMON}/max17055.cpp
|
||||||
|
${COMMON}/battery.cpp
|
||||||
${COMMON}/performance_counter.cpp
|
${COMMON}/performance_counter.cpp
|
||||||
|
${COMMON}/bmpfile.cpp
|
||||||
app_settings.cpp
|
app_settings.cpp
|
||||||
audio.cpp
|
audio.cpp
|
||||||
baseband_api.cpp
|
baseband_api.cpp
|
||||||
@@ -190,6 +194,7 @@ set(CPPSRC
|
|||||||
event_m0.cpp
|
event_m0.cpp
|
||||||
file_reader.cpp
|
file_reader.cpp
|
||||||
file.cpp
|
file.cpp
|
||||||
|
file_path.cpp
|
||||||
freqman_db.cpp
|
freqman_db.cpp
|
||||||
freqman.cpp
|
freqman.cpp
|
||||||
io_convert.cpp
|
io_convert.cpp
|
||||||
@@ -201,11 +206,15 @@ set(CPPSRC
|
|||||||
irq_rtc.cpp
|
irq_rtc.cpp
|
||||||
log_file.cpp
|
log_file.cpp
|
||||||
metadata_file.cpp
|
metadata_file.cpp
|
||||||
|
flipper_subfile.cpp
|
||||||
portapack.cpp
|
portapack.cpp
|
||||||
usb_serial_shell.cpp
|
usb_serial_shell.cpp
|
||||||
|
usb_serial_shell_filesystem.cpp
|
||||||
usb_serial_event.cpp
|
usb_serial_event.cpp
|
||||||
usb_serial_thread.cpp
|
usb_serial_thread.cpp
|
||||||
usb_serial.cpp
|
usb_serial.cpp
|
||||||
|
usb_serial_host_to_device.cpp
|
||||||
|
usb_serial_asyncmsg.cpp
|
||||||
qrcodegen.cpp
|
qrcodegen.cpp
|
||||||
radio.cpp
|
radio.cpp
|
||||||
receiver_model.cpp
|
receiver_model.cpp
|
||||||
@@ -218,6 +227,7 @@ set(CPPSRC
|
|||||||
spectrum_color_lut.cpp
|
spectrum_color_lut.cpp
|
||||||
string_format.cpp
|
string_format.cpp
|
||||||
temperature_logger.cpp
|
temperature_logger.cpp
|
||||||
|
theme.cpp
|
||||||
touch.cpp
|
touch.cpp
|
||||||
tone_key.cpp
|
tone_key.cpp
|
||||||
transmitter_model.cpp
|
transmitter_model.cpp
|
||||||
@@ -251,11 +261,11 @@ set(CPPSRC
|
|||||||
ui/ui_freqlist.cpp
|
ui/ui_freqlist.cpp
|
||||||
ui/ui_tv.cpp
|
ui/ui_tv.cpp
|
||||||
ui/ui_spectrum.cpp
|
ui/ui_spectrum.cpp
|
||||||
ui/ui_styles.cpp
|
|
||||||
ui/ui_tabview.cpp
|
ui/ui_tabview.cpp
|
||||||
ui/ui_textentry.cpp
|
ui/ui_textentry.cpp
|
||||||
ui/ui_tone_key.cpp
|
ui/ui_tone_key.cpp
|
||||||
ui/ui_transmitter.cpp
|
ui/ui_transmitter.cpp
|
||||||
|
ui/ui_bmpview.cpp
|
||||||
apps/acars_app.cpp
|
apps/acars_app.cpp
|
||||||
apps/ais_app.cpp
|
apps/ais_app.cpp
|
||||||
apps/analog_audio_app.cpp
|
apps/analog_audio_app.cpp
|
||||||
@@ -265,22 +275,24 @@ set(CPPSRC
|
|||||||
apps/ble_tx_app.cpp
|
apps/ble_tx_app.cpp
|
||||||
apps/capture_app.cpp
|
apps/capture_app.cpp
|
||||||
apps/ert_app.cpp
|
apps/ert_app.cpp
|
||||||
apps/gps_sim_app.cpp
|
# apps/gps_sim_app.cpp
|
||||||
# apps/lge_app.cpp
|
# apps/lge_app.cpp
|
||||||
apps/pocsag_app.cpp
|
apps/pocsag_app.cpp
|
||||||
# apps/replay_app.cpp
|
# apps/replay_app.cpp
|
||||||
apps/soundboard_app.cpp
|
apps/soundboard_app.cpp
|
||||||
apps/tpms_app.cpp
|
# apps/tpms_app.cpp
|
||||||
apps/tpms_app.cpp
|
|
||||||
apps/ui_about_simple.cpp
|
apps/ui_about_simple.cpp
|
||||||
apps/ui_adsb_rx.cpp
|
apps/ui_adsb_rx.cpp
|
||||||
apps/ui_adsb_tx.cpp
|
# apps/ui_adsb_tx.cpp #moved to ext
|
||||||
apps/ui_aprs_rx.cpp
|
apps/ui_aprs_rx.cpp
|
||||||
apps/ui_aprs_tx.cpp
|
apps/ui_aprs_tx.cpp
|
||||||
|
apps/ui_battinfo.cpp
|
||||||
apps/ui_bht_tx.cpp
|
apps/ui_bht_tx.cpp
|
||||||
|
apps/ui_bmp_file_viewer.cpp
|
||||||
apps/ui_btle_rx.cpp
|
apps/ui_btle_rx.cpp
|
||||||
# apps/ui_coasterp.cpp
|
# apps/ui_coasterp.cpp
|
||||||
apps/ui_debug.cpp
|
apps/ui_debug.cpp
|
||||||
|
apps/ui_debug_battery.cpp
|
||||||
apps/ui_dfu_menu.cpp
|
apps/ui_dfu_menu.cpp
|
||||||
apps/ui_encoders.cpp
|
apps/ui_encoders.cpp
|
||||||
apps/ui_fileman.cpp
|
apps/ui_fileman.cpp
|
||||||
@@ -291,18 +303,18 @@ set(CPPSRC
|
|||||||
# apps/ui_jammer.cpp
|
# apps/ui_jammer.cpp
|
||||||
# apps/ui_keyfob.cpp
|
# apps/ui_keyfob.cpp
|
||||||
# apps/ui_lcr.cpp
|
# apps/ui_lcr.cpp
|
||||||
apps/ui_level.cpp
|
apps/ui_level.cpp
|
||||||
apps/ui_looking_glass_app.cpp
|
apps/ui_looking_glass_app.cpp
|
||||||
apps/ui_mictx.cpp
|
apps/ui_mictx.cpp
|
||||||
apps/ui_modemsetup.cpp
|
apps/ui_modemsetup.cpp
|
||||||
apps/ui_morse.cpp
|
# apps/ui_morse.cpp
|
||||||
# apps/ui_nrf_rx.cpp
|
# apps/ui_nrf_rx.cpp
|
||||||
# apps/ui_nuoptix.cpp
|
# apps/ui_nuoptix.cpp
|
||||||
apps/ui_playlist.cpp
|
apps/ui_playlist.cpp
|
||||||
apps/ui_pocsag_tx.cpp
|
apps/ui_pocsag_tx.cpp
|
||||||
apps/ui_rds.cpp
|
apps/ui_rds.cpp
|
||||||
apps/ui_recon_settings.cpp
|
apps/ui_recon_settings.cpp
|
||||||
apps/ui_recon.cpp
|
apps/ui_recon.cpp
|
||||||
apps/ui_remote.cpp
|
apps/ui_remote.cpp
|
||||||
apps/ui_scanner.cpp
|
apps/ui_scanner.cpp
|
||||||
apps/ui_sd_over_usb.cpp
|
apps/ui_sd_over_usb.cpp
|
||||||
@@ -311,11 +323,12 @@ set(CPPSRC
|
|||||||
apps/ui_settings.cpp
|
apps/ui_settings.cpp
|
||||||
apps/ui_siggen.cpp
|
apps/ui_siggen.cpp
|
||||||
apps/ui_sonde.cpp
|
apps/ui_sonde.cpp
|
||||||
apps/ui_spectrum_painter_image.cpp
|
# apps/ui_spectrum_painter_image.cpp
|
||||||
apps/ui_spectrum_painter_text.cpp
|
# apps/ui_spectrum_painter_text.cpp
|
||||||
apps/ui_spectrum_painter.cpp
|
# apps/ui_spectrum_painter.cpp
|
||||||
apps/ui_ss_viewer.cpp
|
apps/ui_ss_viewer.cpp
|
||||||
apps/ui_sstvtx.cpp
|
# apps/ui_sstvtx.cpp #moved to ext
|
||||||
|
apps/ui_standalone_view.cpp
|
||||||
apps/ui_subghzd.cpp
|
apps/ui_subghzd.cpp
|
||||||
# apps/ui_test.cpp
|
# apps/ui_test.cpp
|
||||||
apps/ui_text_editor.cpp
|
apps/ui_text_editor.cpp
|
||||||
@@ -344,10 +357,13 @@ set(CPPSRC
|
|||||||
${CPLD_20170522_DATA_CPP}
|
${CPLD_20170522_DATA_CPP}
|
||||||
${HACKRF_CPLD_DATA_CPP}
|
${HACKRF_CPLD_DATA_CPP}
|
||||||
ui_external_items_menu_loader.cpp
|
ui_external_items_menu_loader.cpp
|
||||||
|
view_factory_base.cpp
|
||||||
${EXTCPPSRC}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
set_source_files_properties(${CPPSRC} PROPERTIES COMPILE_FLAGS -flto) # Add lto flag to the non-external sources only
|
||||||
|
|
||||||
|
list (APPEND CPPSRC ${EXTCPPSRC}) # Append external sources after setting lto flag to internal ones
|
||||||
|
|
||||||
# C sources to be compiled in ARM mode regardless of the global setting.
|
# C sources to be compiled in ARM mode regardless of the global setting.
|
||||||
# NOTE: Mixing ARM and THUMB mode enables the -mthumb-interwork compiler
|
# NOTE: Mixing ARM and THUMB mode enables the -mthumb-interwork compiler
|
||||||
# option that results in lower performance and larger code size.
|
# option that results in lower performance and larger code size.
|
||||||
@@ -424,7 +440,7 @@ set(CPPWARN "-Wall -Wextra -Wno-psabi")
|
|||||||
# List all default C defines here, like -D_DEBUG=1
|
# List all default C defines here, like -D_DEBUG=1
|
||||||
# TODO: Switch -DCRT0_INIT_DATA depending on load from RAM or SPIFI?
|
# 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
|
# 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}")
|
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}'")
|
||||||
|
|
||||||
# List all default ASM defines here, like -D_DEBUG=1
|
# List all default ASM defines here, like -D_DEBUG=1
|
||||||
set(DADEFS)
|
set(DADEFS)
|
||||||
@@ -495,6 +511,7 @@ include_directories(. ${INCDIR})
|
|||||||
link_directories(${LLIBDIR})
|
link_directories(${LLIBDIR})
|
||||||
target_link_libraries(${PROJECT_NAME}.elf ${LIBS} "-L${CMAKE_CURRENT_LIST_DIR}/external")
|
target_link_libraries(${PROJECT_NAME}.elf ${LIBS} "-L${CMAKE_CURRENT_LIST_DIR}/external")
|
||||||
target_link_libraries(${PROJECT_NAME}.elf -Wl,-Map=${PROJECT_NAME}.map)
|
target_link_libraries(${PROJECT_NAME}.elf -Wl,-Map=${PROJECT_NAME}.map)
|
||||||
|
target_link_libraries(${PROJECT_NAME}.elf -Wl,--print-memory-usage)
|
||||||
|
|
||||||
add_custom_command(
|
add_custom_command(
|
||||||
OUTPUT ${PROJECT_NAME}.bin
|
OUTPUT ${PROJECT_NAME}.bin
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
#include "portapack.hpp"
|
#include "portapack.hpp"
|
||||||
#include "portapack_persistent_memory.hpp"
|
#include "portapack_persistent_memory.hpp"
|
||||||
#include "utility.hpp"
|
#include "utility.hpp"
|
||||||
|
#include "file_path.hpp"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
@@ -40,7 +41,7 @@ using namespace portapack;
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
fs::path get_settings_path(const std::string& app_name) {
|
fs::path get_settings_path(const std::string& app_name) {
|
||||||
return fs::path{u"/SETTINGS"} / app_name + u".ini";
|
return settings_dir / app_name + u".ini";
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
@@ -156,7 +157,7 @@ bool save_settings(std::string_view store_name, const SettingBindings& bindings)
|
|||||||
File f;
|
File f;
|
||||||
auto path = get_settings_path(std::string{store_name});
|
auto path = get_settings_path(std::string{store_name});
|
||||||
|
|
||||||
make_new_directory(SETTINGS_DIR);
|
ensure_directory(settings_dir);
|
||||||
auto error = f.create(path);
|
auto error = f.create(path);
|
||||||
if (error)
|
if (error)
|
||||||
return false;
|
return false;
|
||||||
@@ -174,16 +175,12 @@ void copy_to_radio_model(const AppSettings& settings) {
|
|||||||
// Specifically 'modulation' which requires a running baseband.
|
// Specifically 'modulation' which requires a running baseband.
|
||||||
|
|
||||||
if (flags_enabled(settings.mode, Mode::TX)) {
|
if (flags_enabled(settings.mode, Mode::TX)) {
|
||||||
if (!flags_enabled(settings.options, Options::UseGlobalTargetFrequency))
|
persistent_memory::set_target_frequency(settings.tx_frequency);
|
||||||
persistent_memory::set_target_frequency(settings.tx_frequency);
|
|
||||||
|
|
||||||
transmitter_model.configure_from_app_settings(settings);
|
transmitter_model.configure_from_app_settings(settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (flags_enabled(settings.mode, Mode::RX)) {
|
if (flags_enabled(settings.mode, Mode::RX)) {
|
||||||
if (!flags_enabled(settings.options, Options::UseGlobalTargetFrequency))
|
persistent_memory::set_target_frequency(settings.rx_frequency);
|
||||||
persistent_memory::set_target_frequency(settings.rx_frequency);
|
|
||||||
|
|
||||||
receiver_model.configure_from_app_settings(settings);
|
receiver_model.configure_from_app_settings(settings);
|
||||||
receiver_model.set_configuration_without_update(
|
receiver_model.set_configuration_without_update(
|
||||||
static_cast<ReceiverModel::Mode>(settings.modulation),
|
static_cast<ReceiverModel::Mode>(settings.modulation),
|
||||||
|
|||||||
@@ -36,9 +36,6 @@
|
|||||||
#include "max283x.hpp"
|
#include "max283x.hpp"
|
||||||
#include "string_format.hpp"
|
#include "string_format.hpp"
|
||||||
|
|
||||||
// Folder to store app settings, pmem_fileflag, and date_fileflag
|
|
||||||
#define SETTINGS_DIR u"/SETTINGS"
|
|
||||||
|
|
||||||
// Bring in the string_view literal.
|
// Bring in the string_view literal.
|
||||||
using std::literals::operator""sv;
|
using std::literals::operator""sv;
|
||||||
|
|
||||||
@@ -113,6 +110,7 @@ bool save_settings(std::string_view store_name, const SettingBindings& bindings)
|
|||||||
namespace app_settings {
|
namespace app_settings {
|
||||||
|
|
||||||
enum class Mode : uint8_t {
|
enum class Mode : uint8_t {
|
||||||
|
NO_RF = 0x00,
|
||||||
RX = 0x01,
|
RX = 0x01,
|
||||||
TX = 0x02,
|
TX = 0x02,
|
||||||
RX_TX = 0x03, // Both TX/RX
|
RX_TX = 0x03, // Both TX/RX
|
||||||
@@ -120,9 +118,6 @@ enum class Mode : uint8_t {
|
|||||||
|
|
||||||
enum class Options {
|
enum class Options {
|
||||||
None = 0x0000,
|
None = 0x0000,
|
||||||
|
|
||||||
/* Don't use target frequency from app settings. */
|
|
||||||
UseGlobalTargetFrequency = 0x0001,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/* NB: See RX/TX model headers for default values. */
|
/* NB: See RX/TX model headers for default values. */
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
|
|
||||||
#include "baseband_api.hpp"
|
#include "baseband_api.hpp"
|
||||||
#include "portapack_persistent_memory.hpp"
|
#include "portapack_persistent_memory.hpp"
|
||||||
|
#include "file_path.hpp"
|
||||||
|
|
||||||
using namespace portapack;
|
using namespace portapack;
|
||||||
using namespace acars;
|
using namespace acars;
|
||||||
@@ -77,7 +78,7 @@ ACARSAppView::ACARSAppView(NavigationView& nav)
|
|||||||
|
|
||||||
logger = std::make_unique<ACARSLogger>();
|
logger = std::make_unique<ACARSLogger>();
|
||||||
if (logger)
|
if (logger)
|
||||||
logger->append(LOG_ROOT_DIR "/ACARS.TXT");
|
logger->append(logs_dir / u"ACARS.TXT");
|
||||||
}
|
}
|
||||||
|
|
||||||
ACARSAppView::~ACARSAppView() {
|
ACARSAppView::~ACARSAppView() {
|
||||||
|
|||||||
@@ -35,7 +35,7 @@
|
|||||||
|
|
||||||
class ACARSLogger {
|
class ACARSLogger {
|
||||||
public:
|
public:
|
||||||
Optional<File::Error> append(const std::string& filename) {
|
Optional<File::Error> append(const std::filesystem::path& filename) {
|
||||||
return log_file.append(filename);
|
return log_file.append(filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,9 +20,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "ais_app.hpp"
|
#include "ais_app.hpp"
|
||||||
|
#include "audio.hpp"
|
||||||
|
|
||||||
#include "string_format.hpp"
|
#include "string_format.hpp"
|
||||||
#include "database.hpp"
|
#include "database.hpp"
|
||||||
|
#include "file_path.hpp"
|
||||||
|
|
||||||
#include "baseband_api.hpp"
|
#include "baseband_api.hpp"
|
||||||
|
|
||||||
@@ -31,6 +33,8 @@ using namespace portapack;
|
|||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
|
namespace pmem = portapack::persistent_memory;
|
||||||
|
|
||||||
namespace ais {
|
namespace ais {
|
||||||
namespace format {
|
namespace format {
|
||||||
|
|
||||||
@@ -64,9 +68,9 @@ static std::string mmsi(
|
|||||||
|
|
||||||
static std::string mid(
|
static std::string mid(
|
||||||
const ais::MMSI& mmsi) {
|
const ais::MMSI& mmsi) {
|
||||||
std::database db;
|
database db;
|
||||||
std::string mid_code = "";
|
std::string mid_code = "";
|
||||||
std::database::MidDBRecord mid_record = {};
|
database::MidDBRecord mid_record = {};
|
||||||
int return_code = 0;
|
int return_code = 0;
|
||||||
|
|
||||||
// Try getting the country name from mids.db using MID code for given MMSI
|
// Try getting the country name from mids.db using MID code for given MMSI
|
||||||
@@ -189,6 +193,10 @@ void AISLogger::on_packet(const ais::Packet& packet) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
log_file.write_entry(packet.received_at(), entry);
|
log_file.write_entry(packet.received_at(), entry);
|
||||||
|
|
||||||
|
if (pmem::beep_on_packets()) {
|
||||||
|
baseband::request_audio_beep(1000, 24000, 60);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AISRecentEntry::update(const ais::Packet& packet) {
|
void AISRecentEntry::update(const ais::Packet& packet) {
|
||||||
@@ -293,6 +301,7 @@ AISRecentEntryDetailView::AISRecentEntryDetailView(NavigationView& nav) {
|
|||||||
ais::format::text(entry_.name),
|
ais::format::text(entry_.name),
|
||||||
0,
|
0,
|
||||||
GeoPos::alt_unit::METERS,
|
GeoPos::alt_unit::METERS,
|
||||||
|
GeoPos::spd_unit::NONE,
|
||||||
ais::format::latlon_float(entry_.last_position.latitude.normalized()),
|
ais::format::latlon_float(entry_.last_position.latitude.normalized()),
|
||||||
ais::format::latlon_float(entry_.last_position.longitude.normalized()),
|
ais::format::latlon_float(entry_.last_position.longitude.normalized()),
|
||||||
entry_.last_position.true_heading,
|
entry_.last_position.true_heading,
|
||||||
@@ -315,7 +324,7 @@ AISRecentEntryDetailView& AISRecentEntryDetailView::operator=(const AISRecentEnt
|
|||||||
|
|
||||||
void AISRecentEntryDetailView::update_position() {
|
void AISRecentEntryDetailView::update_position() {
|
||||||
if (send_updates)
|
if (send_updates)
|
||||||
geomap_view->update_position(ais::format::latlon_float(entry_.last_position.latitude.normalized()), ais::format::latlon_float(entry_.last_position.longitude.normalized()), (float)entry_.last_position.true_heading, 0);
|
geomap_view->update_position(ais::format::latlon_float(entry_.last_position.latitude.normalized()), ais::format::latlon_float(entry_.last_position.longitude.normalized()), (float)entry_.last_position.true_heading, 0, entry_.last_position.speed_over_ground);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AISRecentEntryDetailView::focus() {
|
void AISRecentEntryDetailView::focus() {
|
||||||
@@ -375,6 +384,7 @@ AISAppView::AISAppView(NavigationView& nav)
|
|||||||
&field_lna,
|
&field_lna,
|
||||||
&field_vga,
|
&field_vga,
|
||||||
&rssi,
|
&rssi,
|
||||||
|
&field_volume,
|
||||||
&channel,
|
&channel,
|
||||||
&recent_entries_view,
|
&recent_entries_view,
|
||||||
&recent_entry_detail_view,
|
&recent_entry_detail_view,
|
||||||
@@ -398,11 +408,17 @@ AISAppView::AISAppView(NavigationView& nav)
|
|||||||
|
|
||||||
logger = std::make_unique<AISLogger>();
|
logger = std::make_unique<AISLogger>();
|
||||||
if (logger) {
|
if (logger) {
|
||||||
logger->append(LOG_ROOT_DIR "/AIS.TXT");
|
logger->append(logs_dir / u"AIS.TXT");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pmem::beep_on_packets()) {
|
||||||
|
audio::set_rate(audio::Rate::Hz_24000);
|
||||||
|
audio::output::start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AISAppView::~AISAppView() {
|
AISAppView::~AISAppView() {
|
||||||
|
audio::output::stop();
|
||||||
receiver_model.disable();
|
receiver_model.disable();
|
||||||
baseband::shutdown();
|
baseband::shutdown();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -209,6 +209,9 @@ class AISAppView : public View {
|
|||||||
{21 * 8, 0, 6 * 8, 4},
|
{21 * 8, 0, 6 * 8, 4},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
AudioVolumeField field_volume{
|
||||||
|
{28 * 8, 0 * 16}};
|
||||||
|
|
||||||
Channel channel{
|
Channel channel{
|
||||||
{21 * 8, 5, 6 * 8, 4},
|
{21 * 8, 5, 6 * 8, 4},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
#include "string_format.hpp"
|
#include "string_format.hpp"
|
||||||
#include "ui_freqman.hpp"
|
#include "ui_freqman.hpp"
|
||||||
#include "utility.hpp"
|
#include "utility.hpp"
|
||||||
|
#include "radio.hpp"
|
||||||
|
|
||||||
using namespace portapack;
|
using namespace portapack;
|
||||||
using namespace tonekey;
|
using namespace tonekey;
|
||||||
@@ -38,8 +39,6 @@ namespace ui {
|
|||||||
|
|
||||||
/* AMOptionsView *********************************************************/
|
/* AMOptionsView *********************************************************/
|
||||||
|
|
||||||
static const Style& style_options_group = Styles::bg_blue;
|
|
||||||
|
|
||||||
AMOptionsView::AMOptionsView(
|
AMOptionsView::AMOptionsView(
|
||||||
Rect parent_rect,
|
Rect parent_rect,
|
||||||
const Style* style)
|
const Style* style)
|
||||||
@@ -112,10 +111,14 @@ SPECOptionsView::SPECOptionsView(
|
|||||||
: View{parent_rect} {
|
: View{parent_rect} {
|
||||||
set_style(style);
|
set_style(style);
|
||||||
|
|
||||||
add_children({&label_config,
|
add_children({
|
||||||
&options_config,
|
&label_config,
|
||||||
&text_speed,
|
&options_config,
|
||||||
&field_speed});
|
&text_speed,
|
||||||
|
&field_speed,
|
||||||
|
&text_rx_cal,
|
||||||
|
&field_rx_iq_phase_cal,
|
||||||
|
});
|
||||||
|
|
||||||
options_config.set_selected_index(view->get_spec_bw_index());
|
options_config.set_selected_index(view->get_spec_bw_index());
|
||||||
options_config.on_change = [this, view](size_t n, OptionsField::value_t bw) {
|
options_config.on_change = [this, view](size_t n, OptionsField::value_t bw) {
|
||||||
@@ -126,6 +129,14 @@ SPECOptionsView::SPECOptionsView(
|
|||||||
field_speed.on_change = [this, view](int32_t v) {
|
field_speed.on_change = [this, view](int32_t v) {
|
||||||
view->set_spec_trigger(v);
|
view->set_spec_trigger(v);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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(view->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, view](int32_t v) {
|
||||||
|
view->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
|
||||||
|
};
|
||||||
|
|
||||||
|
view->set_spec_iq_phase_calibration_value(view->get_spec_iq_phase_calibration_value()); // initialize iq_phase_calibration in radio
|
||||||
}
|
}
|
||||||
|
|
||||||
/* AnalogAudioView *******************************************************/
|
/* AnalogAudioView *******************************************************/
|
||||||
@@ -195,7 +206,9 @@ AnalogAudioView::AnalogAudioView(
|
|||||||
NavigationView& nav,
|
NavigationView& nav,
|
||||||
ReceiverModel::settings_t override)
|
ReceiverModel::settings_t override)
|
||||||
: AnalogAudioView(nav) {
|
: AnalogAudioView(nav) {
|
||||||
|
// Settings to override when launched from another app (versus from AppSettings .ini file)
|
||||||
// TODO: Which other settings make sense to override?
|
// TODO: Which other settings make sense to override?
|
||||||
|
field_frequency.set_value(override.frequency_app_override);
|
||||||
on_frequency_step_changed(override.frequency_step);
|
on_frequency_step_changed(override.frequency_step);
|
||||||
options_modulation.set_by_value(toUType(override.mode));
|
options_modulation.set_by_value(toUType(override.mode));
|
||||||
}
|
}
|
||||||
@@ -213,6 +226,15 @@ void AnalogAudioView::set_spec_bw(size_t index, uint32_t bw) {
|
|||||||
receiver_model.set_baseband_bandwidth(bw / 2);
|
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
|
||||||
|
return iq_phase_calibration_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AnalogAudioView::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);
|
||||||
|
}
|
||||||
|
|
||||||
uint16_t AnalogAudioView::get_spec_trigger() {
|
uint16_t AnalogAudioView::get_spec_trigger() {
|
||||||
return spec_trigger;
|
return spec_trigger;
|
||||||
}
|
}
|
||||||
@@ -273,13 +295,13 @@ void AnalogAudioView::set_options_widget(std::unique_ptr<Widget> new_widget) {
|
|||||||
options_widget = std::move(new_widget);
|
options_widget = std::move(new_widget);
|
||||||
} else {
|
} else {
|
||||||
// TODO: Lame hack to hide options view due to my bad paint/damage algorithm.
|
// TODO: Lame hack to hide options view due to my bad paint/damage algorithm.
|
||||||
options_widget = std::make_unique<Rectangle>(options_view_rect, style_options_group.background);
|
options_widget = std::make_unique<Rectangle>(options_view_rect, Theme::getInstance()->option_active->background);
|
||||||
}
|
}
|
||||||
add_child(options_widget.get());
|
add_child(options_widget.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
void AnalogAudioView::on_show_options_frequency() {
|
void AnalogAudioView::on_show_options_frequency() {
|
||||||
auto widget = std::make_unique<FrequencyOptionsView>(options_view_rect, &style_options_group);
|
auto widget = std::make_unique<FrequencyOptionsView>(options_view_rect, Theme::getInstance()->option_active);
|
||||||
|
|
||||||
widget->set_step(receiver_model.frequency_step());
|
widget->set_step(receiver_model.frequency_step());
|
||||||
widget->on_change_step = [this](rf::Frequency f) {
|
widget->on_change_step = [this](rf::Frequency f) {
|
||||||
@@ -291,14 +313,14 @@ void AnalogAudioView::on_show_options_frequency() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
set_options_widget(std::move(widget));
|
set_options_widget(std::move(widget));
|
||||||
field_frequency.set_style(&style_options_group);
|
field_frequency.set_style(Theme::getInstance()->option_active);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AnalogAudioView::on_show_options_rf_gain() {
|
void AnalogAudioView::on_show_options_rf_gain() {
|
||||||
auto widget = std::make_unique<RadioGainOptionsView>(options_view_rect, &style_options_group);
|
auto widget = std::make_unique<RadioGainOptionsView>(options_view_rect, Theme::getInstance()->option_active);
|
||||||
|
|
||||||
set_options_widget(std::move(widget));
|
set_options_widget(std::move(widget));
|
||||||
field_lna.set_style(&style_options_group);
|
field_lna.set_style(Theme::getInstance()->option_active);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AnalogAudioView::on_show_options_modulation() {
|
void AnalogAudioView::on_show_options_modulation() {
|
||||||
@@ -307,25 +329,25 @@ void AnalogAudioView::on_show_options_modulation() {
|
|||||||
const auto modulation = receiver_model.modulation();
|
const auto modulation = receiver_model.modulation();
|
||||||
switch (modulation) {
|
switch (modulation) {
|
||||||
case ReceiverModel::Mode::AMAudio:
|
case ReceiverModel::Mode::AMAudio:
|
||||||
widget = std::make_unique<AMOptionsView>(options_view_rect, &style_options_group);
|
widget = std::make_unique<AMOptionsView>(options_view_rect, Theme::getInstance()->option_active);
|
||||||
waterfall.show_audio_spectrum_view(false);
|
waterfall.show_audio_spectrum_view(false);
|
||||||
text_ctcss.hidden(true);
|
text_ctcss.hidden(true);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ReceiverModel::Mode::NarrowbandFMAudio:
|
case ReceiverModel::Mode::NarrowbandFMAudio:
|
||||||
widget = std::make_unique<NBFMOptionsView>(nbfm_view_rect, &style_options_group);
|
widget = std::make_unique<NBFMOptionsView>(nbfm_view_rect, Theme::getInstance()->option_active);
|
||||||
waterfall.show_audio_spectrum_view(false);
|
waterfall.show_audio_spectrum_view(false);
|
||||||
text_ctcss.hidden(false);
|
text_ctcss.hidden(false);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ReceiverModel::Mode::WidebandFMAudio:
|
case ReceiverModel::Mode::WidebandFMAudio:
|
||||||
widget = std::make_unique<WFMOptionsView>(options_view_rect, &style_options_group);
|
widget = std::make_unique<WFMOptionsView>(options_view_rect, Theme::getInstance()->option_active);
|
||||||
waterfall.show_audio_spectrum_view(true);
|
waterfall.show_audio_spectrum_view(true);
|
||||||
text_ctcss.hidden(true);
|
text_ctcss.hidden(true);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ReceiverModel::Mode::SpectrumAnalysis:
|
case ReceiverModel::Mode::SpectrumAnalysis:
|
||||||
widget = std::make_unique<SPECOptionsView>(this, nbfm_view_rect, &style_options_group);
|
widget = std::make_unique<SPECOptionsView>(this, nbfm_view_rect, Theme::getInstance()->option_active);
|
||||||
waterfall.show_audio_spectrum_view(false);
|
waterfall.show_audio_spectrum_view(false);
|
||||||
text_ctcss.hidden(true);
|
text_ctcss.hidden(true);
|
||||||
break;
|
break;
|
||||||
@@ -336,7 +358,7 @@ void AnalogAudioView::on_show_options_modulation() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
set_options_widget(std::move(widget));
|
set_options_widget(std::move(widget));
|
||||||
options_modulation.set_style(&style_options_group);
|
options_modulation.set_style(Theme::getInstance()->option_active);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AnalogAudioView::on_frequency_step_changed(rf::Frequency f) {
|
void AnalogAudioView::on_frequency_step_changed(rf::Frequency f) {
|
||||||
@@ -413,4 +435,7 @@ void AnalogAudioView::handle_coded_squelch(uint32_t value) {
|
|||||||
text_ctcss.set(tone_key_string_by_value(value, text_ctcss.parent_rect().width() / 8));
|
text_ctcss.set(tone_key_string_by_value(value, text_ctcss.parent_rect().width() / 8));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AnalogAudioView::on_freqchg(int64_t freq) {
|
||||||
|
field_frequency.set_value(freq);
|
||||||
|
}
|
||||||
} /* namespace ui */
|
} /* namespace ui */
|
||||||
|
|||||||
@@ -29,7 +29,6 @@
|
|||||||
#include "ui_freq_field.hpp"
|
#include "ui_freq_field.hpp"
|
||||||
#include "ui_spectrum.hpp"
|
#include "ui_spectrum.hpp"
|
||||||
#include "ui_record_view.hpp"
|
#include "ui_record_view.hpp"
|
||||||
#include "ui_styles.hpp"
|
|
||||||
#include "app_settings.hpp"
|
#include "app_settings.hpp"
|
||||||
#include "radio_state.hpp"
|
#include "radio_state.hpp"
|
||||||
#include "tone_key.hpp"
|
#include "tone_key.hpp"
|
||||||
@@ -133,6 +132,16 @@ class SPECOptionsView : public View {
|
|||||||
1,
|
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.
|
||||||
|
"Rx_IQ_CAL "};
|
||||||
|
NumberField field_rx_iq_phase_cal{
|
||||||
|
{28 * 8, 0 * 16},
|
||||||
|
2,
|
||||||
|
{0, 63}, // 5 or 6 bits IQ CAL phase adjustment (range updated later)
|
||||||
|
1,
|
||||||
|
' ',
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
class AnalogAudioView : public View {
|
class AnalogAudioView : public View {
|
||||||
@@ -152,14 +161,21 @@ class AnalogAudioView : public View {
|
|||||||
uint16_t get_spec_trigger();
|
uint16_t get_spec_trigger();
|
||||||
void set_spec_trigger(uint16_t trigger);
|
void set_spec_trigger(uint16_t trigger);
|
||||||
|
|
||||||
|
uint8_t get_spec_iq_phase_calibration_value();
|
||||||
|
void set_spec_iq_phase_calibration_value(uint8_t cal_value);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static constexpr ui::Dim header_height = 3 * 16;
|
static constexpr ui::Dim header_height = 3 * 16;
|
||||||
|
|
||||||
NavigationView& nav_;
|
NavigationView& nav_;
|
||||||
RxRadioState radio_state_{};
|
RxRadioState radio_state_{};
|
||||||
|
uint8_t iq_phase_calibration_value{15}; // initial default RX IQ phase calibration value , used for both max2837 & max2839
|
||||||
app_settings::SettingsManager settings_{
|
app_settings::SettingsManager settings_{
|
||||||
"rx_audio", app_settings::Mode::RX,
|
"rx_audio",
|
||||||
app_settings::Options::UseGlobalTargetFrequency};
|
app_settings::Mode::RX,
|
||||||
|
{
|
||||||
|
{"iq_phase_calibration"sv, &iq_phase_calibration_value}, // we are saving and restoring that CAL from Settings.
|
||||||
|
}};
|
||||||
|
|
||||||
const Rect options_view_rect{0 * 8, 1 * 16, 30 * 8, 1 * 16};
|
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 nbfm_view_rect{0 * 8, 1 * 16, 18 * 8, 1 * 16};
|
||||||
@@ -231,12 +247,21 @@ class AnalogAudioView : public View {
|
|||||||
|
|
||||||
void handle_coded_squelch(uint32_t value);
|
void handle_coded_squelch(uint32_t value);
|
||||||
|
|
||||||
|
void on_freqchg(int64_t freq);
|
||||||
|
|
||||||
MessageHandlerRegistration message_handler_coded_squelch{
|
MessageHandlerRegistration message_handler_coded_squelch{
|
||||||
Message::ID::CodedSquelch,
|
Message::ID::CodedSquelch,
|
||||||
[this](const Message* p) {
|
[this](const Message* p) {
|
||||||
const auto message = *reinterpret_cast<const CodedSquelchMessage*>(p);
|
const auto message = *reinterpret_cast<const CodedSquelchMessage*>(p);
|
||||||
this->handle_coded_squelch(message.value);
|
this->handle_coded_squelch(message.value);
|
||||||
}};
|
}};
|
||||||
|
|
||||||
|
MessageHandlerRegistration message_handler_freqchg{
|
||||||
|
Message::ID::FreqChangeCommand,
|
||||||
|
[this](Message* const p) {
|
||||||
|
const auto message = static_cast<const FreqChangeCommandMessage*>(p);
|
||||||
|
this->on_freqchg(message->freq);
|
||||||
|
}};
|
||||||
};
|
};
|
||||||
|
|
||||||
} /* namespace ui */
|
} /* namespace ui */
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
#include "string_format.hpp"
|
#include "string_format.hpp"
|
||||||
#include "portapack_persistent_memory.hpp"
|
#include "portapack_persistent_memory.hpp"
|
||||||
#include "ui_text.hpp"
|
#include "ui_text.hpp"
|
||||||
|
#include "file_path.hpp"
|
||||||
|
|
||||||
using namespace portapack;
|
using namespace portapack;
|
||||||
using namespace modems;
|
using namespace modems;
|
||||||
@@ -100,7 +101,7 @@ BLECommView::BLECommView(NavigationView& nav)
|
|||||||
logging = v;
|
logging = v;
|
||||||
|
|
||||||
if (logger && logging)
|
if (logger && logging)
|
||||||
logger->append(LOG_ROOT_DIR "/BLELOG_" + to_string_timestamp(rtc_time::now()) + ".TXT");
|
logger->append(logs_dir.string() + "/BLELOG_" + to_string_timestamp(rtc_time::now()) + ".TXT");
|
||||||
};
|
};
|
||||||
|
|
||||||
options_channel.on_change = [this](size_t, int32_t i) {
|
options_channel.on_change = [this](size_t, int32_t i) {
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
|
|
||||||
class BLECommLogger {
|
class BLECommLogger {
|
||||||
public:
|
public:
|
||||||
Optional<File::Error> append(const std::string& filename) {
|
Optional<File::Error> append(const std::filesystem::path& filename) {
|
||||||
return log_file.append(filename);
|
return log_file.append(filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,13 +148,13 @@ class BLECommView : public View {
|
|||||||
{24 * 8, 5, 6 * 8, 4}};
|
{24 * 8, 5, 6 * 8, 4}};
|
||||||
|
|
||||||
Labels label_send_adv{
|
Labels label_send_adv{
|
||||||
{{0 * 8, 2 * 8}, "Send Advertisement:", Color::light_grey()}};
|
{{0 * 8, 2 * 8}, "Send Advertisement:", Theme::getInstance()->fg_light->foreground}};
|
||||||
|
|
||||||
ImageButton button_send_adv{
|
ImageButton button_send_adv{
|
||||||
{21 * 8, 1 * 16, 10 * 8, 2 * 16},
|
{21 * 8, 1 * 16, 10 * 8, 2 * 16},
|
||||||
&bitmap_play,
|
&bitmap_play,
|
||||||
Color::green(),
|
Theme::getInstance()->fg_green->foreground,
|
||||||
Color::black()};
|
Theme::getInstance()->fg_green->background};
|
||||||
|
|
||||||
Checkbox check_log{
|
Checkbox check_log{
|
||||||
{24 * 8, 2 * 8},
|
{24 * 8, 2 * 8},
|
||||||
@@ -163,7 +163,7 @@ class BLECommView : public View {
|
|||||||
true};
|
true};
|
||||||
|
|
||||||
Labels label_packets_sent{
|
Labels label_packets_sent{
|
||||||
{{0 * 8, 4 * 8}, "Packets Left:", Color::light_grey()}};
|
{{0 * 8, 4 * 8}, "Packets Left:", Theme::getInstance()->fg_light->foreground}};
|
||||||
|
|
||||||
Text text_packets_sent{
|
Text text_packets_sent{
|
||||||
{13 * 8, 2 * 16, 12 * 8, 16},
|
{13 * 8, 2 * 16, 12 * 8, 16},
|
||||||
|
|||||||
@@ -426,7 +426,9 @@ BLERxView::BLERxView(NavigationView& nav)
|
|||||||
&options_sort,
|
&options_sort,
|
||||||
&label_found,
|
&label_found,
|
||||||
&text_found_count,
|
&text_found_count,
|
||||||
|
&check_serial_log,
|
||||||
&button_filter,
|
&button_filter,
|
||||||
|
&options_filter,
|
||||||
&button_save_list,
|
&button_save_list,
|
||||||
&button_clear_list,
|
&button_clear_list,
|
||||||
&button_switch,
|
&button_switch,
|
||||||
@@ -436,7 +438,15 @@ BLERxView::BLERxView(NavigationView& nav)
|
|||||||
nav_.push<BleRecentEntryDetailView>(entry);
|
nav_.push<BleRecentEntryDetailView>(entry);
|
||||||
};
|
};
|
||||||
|
|
||||||
usb_serial_thread = std::make_unique<UsbSerialThread>();
|
check_serial_log.on_select = [this](Checkbox&, bool v) {
|
||||||
|
serial_logging = v;
|
||||||
|
if (v) {
|
||||||
|
usb_serial_thread = std::make_unique<UsbSerialThread>();
|
||||||
|
} else {
|
||||||
|
usb_serial_thread.reset();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
check_serial_log.set_value(serial_logging);
|
||||||
|
|
||||||
ensure_directory(find_packet_path);
|
ensure_directory(find_packet_path);
|
||||||
ensure_directory(log_packets_path);
|
ensure_directory(log_packets_path);
|
||||||
@@ -456,18 +466,14 @@ BLERxView::BLERxView(NavigationView& nav)
|
|||||||
|
|
||||||
logger = std::make_unique<BLELogger>();
|
logger = std::make_unique<BLELogger>();
|
||||||
|
|
||||||
check_log.set_value(logging);
|
|
||||||
|
|
||||||
check_log.on_select = [this](Checkbox&, bool v) {
|
check_log.on_select = [this](Checkbox&, bool v) {
|
||||||
str_log = "";
|
str_log = "";
|
||||||
logging = v;
|
logging = v;
|
||||||
|
|
||||||
if (logger && logging)
|
if (logger && logging)
|
||||||
logger->append(
|
logger->append(blerx_dir.string() + "/Logs/BLELOG_" + to_string_timestamp(rtc_time::now()) + ".TXT");
|
||||||
"BLERX/Logs"
|
|
||||||
"/BLELOG_" +
|
|
||||||
to_string_timestamp(rtc_time::now()) + ".TXT");
|
|
||||||
};
|
};
|
||||||
|
check_log.set_value(logging);
|
||||||
|
|
||||||
button_save_list.on_select = [this, &nav](const ui::Button&) {
|
button_save_list.on_select = [this, &nav](const ui::Button&) {
|
||||||
listFileBuffer = "";
|
listFileBuffer = "";
|
||||||
@@ -494,6 +500,7 @@ BLERxView::BLERxView(NavigationView& nav)
|
|||||||
|
|
||||||
check_name.on_select = [this](Checkbox&, bool v) {
|
check_name.on_select = [this](Checkbox&, bool v) {
|
||||||
name_enable = v;
|
name_enable = v;
|
||||||
|
// update the include_name instance variable value of each entry in recent entries
|
||||||
setAllMembersToValue(recent, &BleRecentEntry::include_name, v);
|
setAllMembersToValue(recent, &BleRecentEntry::include_name, v);
|
||||||
recent_entries_view.set_dirty();
|
recent_entries_view.set_dirty();
|
||||||
};
|
};
|
||||||
@@ -520,8 +527,14 @@ BLERxView::BLERxView(NavigationView& nav)
|
|||||||
handle_entries_sort(v);
|
handle_entries_sort(v);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
options_filter.on_change = [this](size_t index, int32_t v) {
|
||||||
|
filter_index = (uint8_t)index;
|
||||||
|
handle_filter_options(v);
|
||||||
|
};
|
||||||
|
|
||||||
options_channel.set_selected_index(channel_index, true);
|
options_channel.set_selected_index(channel_index, true);
|
||||||
options_sort.set_selected_index(sort_index, true);
|
options_sort.set_selected_index(sort_index, true);
|
||||||
|
options_filter.set_selected_index(filter_index, true);
|
||||||
|
|
||||||
button_find.on_select = [this](Button&) {
|
button_find.on_select = [this](Button&) {
|
||||||
auto open_view = nav_.push<FileLoadView>(".TXT");
|
auto open_view = nav_.push<FileLoadView>(".TXT");
|
||||||
@@ -711,10 +724,11 @@ void BLERxView::on_data(BlePacketData* packet) {
|
|||||||
updateEntry(packet, entry, (ADV_PDU_TYPE)packet->type);
|
updateEntry(packet, entry, (ADV_PDU_TYPE)packet->type);
|
||||||
|
|
||||||
// Add entries if they meet the criteria.
|
// Add entries if they meet the criteria.
|
||||||
auto value = filter;
|
// auto value = filter;
|
||||||
resetFilteredEntries(recent, [&value](const BleRecentEntry& entry) {
|
// resetFilteredEntries(recent, [&value](const BleRecentEntry& entry) {
|
||||||
return (entry.dataString.find(value) == std::string::npos) && (entry.nameString.find(value) == std::string::npos);
|
// return (entry.dataString.find(value) == std::string::npos) && (entry.nameString.find(value) == std::string::npos);
|
||||||
});
|
// });
|
||||||
|
handle_filter_options(options_filter.selected_index());
|
||||||
|
|
||||||
handle_entries_sort(options_sort.selected_index());
|
handle_entries_sort(options_sort.selected_index());
|
||||||
|
|
||||||
@@ -723,8 +737,10 @@ void BLERxView::on_data(BlePacketData* packet) {
|
|||||||
logger->log_raw_data(str_console + "\r\n");
|
logger->log_raw_data(str_console + "\r\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
usb_serial_thread->serial_str = str_console + "\r\n";
|
if (serial_logging) {
|
||||||
usb_serial_thread->str_ready = true;
|
usb_serial_thread->serial_str = str_console + "\r\n";
|
||||||
|
usb_serial_thread->str_ready = true;
|
||||||
|
}
|
||||||
str_console = "";
|
str_console = "";
|
||||||
|
|
||||||
if (!searchList.empty()) {
|
if (!searchList.empty()) {
|
||||||
@@ -749,16 +765,17 @@ void BLERxView::on_data(BlePacketData* packet) {
|
|||||||
void BLERxView::on_filter_change(std::string value) {
|
void BLERxView::on_filter_change(std::string value) {
|
||||||
// New filter? Reset list from recent entries.
|
// New filter? Reset list from recent entries.
|
||||||
if (filter != value) {
|
if (filter != value) {
|
||||||
resetFilteredEntries(recent, [&value](const BleRecentEntry& entry) {
|
// resetFilteredEntries(recent, [&value](const BleRecentEntry& entry) {
|
||||||
return (entry.dataString.find(value) == std::string::npos) && (entry.nameString.find(value) == std::string::npos);
|
// // return (entry.dataString.find(value) == std::string::npos) && (entry.nameString.find(value) == std::string::npos);
|
||||||
});
|
// return (entry.dataString.find(value) == std::string::npos) && (entry.nameString.find(value) == std::string::npos) && (to_string_mac_address(entry.packetData.macAddress, 6, false).find(value) == std::string::npos);
|
||||||
|
// });
|
||||||
|
filter = value;
|
||||||
|
handle_filter_options(options_filter.selected_index());
|
||||||
}
|
}
|
||||||
|
|
||||||
filter = value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void BLERxView::on_file_changed(const std::filesystem::path& new_file_path) {
|
void BLERxView::on_file_changed(const std::filesystem::path& new_file_path) {
|
||||||
file_path = fs::path(u"/") + new_file_path;
|
file_path = new_file_path;
|
||||||
found_count = 0;
|
found_count = 0;
|
||||||
total_count = 0;
|
total_count = 0;
|
||||||
searchList.clear();
|
searchList.clear();
|
||||||
@@ -845,6 +862,24 @@ void BLERxView::handle_entries_sort(uint8_t index) {
|
|||||||
recent_entries_view.set_dirty();
|
recent_entries_view.set_dirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BLERxView::handle_filter_options(uint8_t index) {
|
||||||
|
auto value = filter;
|
||||||
|
switch (index) {
|
||||||
|
case 0: // filter by Data
|
||||||
|
resetFilteredEntries(recent, [&value](const BleRecentEntry& entry) {
|
||||||
|
return (entry.dataString.find(value) == std::string::npos) && (entry.nameString.find(value) == std::string::npos);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 1: // filter by MAC address (All caps: e.g. AA:BB:CC:DD:EE:FF)
|
||||||
|
resetFilteredEntries(recent, [&value](const BleRecentEntry& entry) {
|
||||||
|
return (to_string_mac_address(entry.packetData.macAddress, 6, false).find(value) == std::string::npos);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void BLERxView::set_parent_rect(const Rect new_parent_rect) {
|
void BLERxView::set_parent_rect(const Rect new_parent_rect) {
|
||||||
View::set_parent_rect(new_parent_rect);
|
View::set_parent_rect(new_parent_rect);
|
||||||
const Rect content_rect{0, header_height, new_parent_rect.width(), new_parent_rect.height() - header_height - switch_button_height};
|
const Rect content_rect{0, header_height, new_parent_rect.width(), new_parent_rect.height() - header_height - switch_button_height};
|
||||||
@@ -907,6 +942,7 @@ void BLERxView::updateEntry(const BlePacketData* packet, BleRecentEntry& entry,
|
|||||||
|
|
||||||
// Subtract 1 because type is part of the length.
|
// Subtract 1 because type is part of the length.
|
||||||
for (int i = 0; i < length - 1; i++) {
|
for (int i = 0; i < length - 1; i++) {
|
||||||
|
// parse the name of bluetooth device: 0x08->Shortened Local Name; 0x09->Complete Local Name
|
||||||
if (type == 0x08 || type == 0x09) {
|
if (type == 0x08 || type == 0x09) {
|
||||||
decoded_data += (char)advertiseData->Data[currentByte];
|
decoded_data += (char)advertiseData->Data[currentByte];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,12 +36,13 @@
|
|||||||
#include "log_file.hpp"
|
#include "log_file.hpp"
|
||||||
#include "utility.hpp"
|
#include "utility.hpp"
|
||||||
#include "usb_serial_thread.hpp"
|
#include "usb_serial_thread.hpp"
|
||||||
|
#include "file_path.hpp"
|
||||||
|
|
||||||
#include "recent_entries.hpp"
|
#include "recent_entries.hpp"
|
||||||
|
|
||||||
class BLELogger {
|
class BLELogger {
|
||||||
public:
|
public:
|
||||||
Optional<File::Error> append(const std::string& filename) {
|
Optional<File::Error> append(const std::filesystem::path& filename) {
|
||||||
return log_file.append(filename);
|
return log_file.append(filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,28 +134,28 @@ class BleRecentEntryDetailView : public View {
|
|||||||
void on_save_file(const std::string value, BLETxPacket packetToSave);
|
void on_save_file(const std::string value, BLETxPacket packetToSave);
|
||||||
bool saveFile(const std::filesystem::path& path, BLETxPacket packetToSave);
|
bool saveFile(const std::filesystem::path& path, BLETxPacket packetToSave);
|
||||||
std::string packetFileBuffer{};
|
std::string packetFileBuffer{};
|
||||||
std::filesystem::path packet_save_path{u"BLERX/Lists/????.csv"};
|
std::filesystem::path packet_save_path{blerx_dir / u"Lists/????.csv"};
|
||||||
|
|
||||||
static constexpr uint8_t total_data_lines{5};
|
static constexpr uint8_t total_data_lines{5};
|
||||||
|
|
||||||
Labels label_mac_address{
|
Labels label_mac_address{
|
||||||
{{0 * 8, 0 * 16}, "Mac Address:", Color::light_grey()}};
|
{{0 * 8, 0 * 16}, "Mac Address:", Theme::getInstance()->fg_light->foreground}};
|
||||||
|
|
||||||
Text text_mac_address{
|
Text text_mac_address{
|
||||||
{12 * 8, 0 * 16, 17 * 8, 16},
|
{12 * 8, 0 * 16, 17 * 8, 16},
|
||||||
"-"};
|
"-"};
|
||||||
|
|
||||||
Labels label_pdu_type{
|
Labels label_pdu_type{
|
||||||
{{0 * 8, 1 * 16}, "PDU Type:", Color::light_grey()}};
|
{{0 * 8, 1 * 16}, "PDU Type:", Theme::getInstance()->fg_light->foreground}};
|
||||||
|
|
||||||
Text text_pdu_type{
|
Text text_pdu_type{
|
||||||
{9 * 8, 1 * 16, 17 * 8, 16},
|
{9 * 8, 1 * 16, 17 * 8, 16},
|
||||||
"-"};
|
"-"};
|
||||||
|
|
||||||
Labels labels{
|
Labels labels{
|
||||||
{{0 * 8, 3 * 16}, "Len", Color::light_grey()},
|
{{0 * 8, 3 * 16}, "Len", Theme::getInstance()->fg_light->foreground},
|
||||||
{{5 * 8, 3 * 16}, "Type", Color::light_grey()},
|
{{5 * 8, 3 * 16}, "Type", Theme::getInstance()->fg_light->foreground},
|
||||||
{{10 * 8, 3 * 16}, "Value", Color::light_grey()},
|
{{10 * 8, 3 * 16}, "Value", Theme::getInstance()->fg_light->foreground},
|
||||||
};
|
};
|
||||||
|
|
||||||
Button button_send{
|
Button button_send{
|
||||||
@@ -202,6 +203,7 @@ class BLERxView : public View {
|
|||||||
void file_error();
|
void file_error();
|
||||||
void on_timer();
|
void on_timer();
|
||||||
void handle_entries_sort(uint8_t index);
|
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);
|
void updateEntry(const BlePacketData* packet, BleRecentEntry& entry, ADV_PDU_TYPE pdu_type);
|
||||||
|
|
||||||
NavigationView& nav_;
|
NavigationView& nav_;
|
||||||
@@ -213,8 +215,10 @@ class BLERxView : public View {
|
|||||||
|
|
||||||
uint8_t channel_index{0};
|
uint8_t channel_index{0};
|
||||||
uint8_t sort_index{0};
|
uint8_t sort_index{0};
|
||||||
|
uint8_t filter_index{0};
|
||||||
std::string filter{};
|
std::string filter{};
|
||||||
bool logging{false};
|
bool logging{false};
|
||||||
|
bool serial_logging{false};
|
||||||
|
|
||||||
bool name_enable{true};
|
bool name_enable{true};
|
||||||
app_settings::SettingsManager settings_{
|
app_settings::SettingsManager settings_{
|
||||||
@@ -225,6 +229,9 @@ class BLERxView : public View {
|
|||||||
{"sort_index"sv, &sort_index},
|
{"sort_index"sv, &sort_index},
|
||||||
{"filter"sv, &filter},
|
{"filter"sv, &filter},
|
||||||
{"log"sv, &logging},
|
{"log"sv, &logging},
|
||||||
|
{"filter_index"sv, &filter_index},
|
||||||
|
// 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},
|
{"name"sv, &name_enable},
|
||||||
}};
|
}};
|
||||||
|
|
||||||
@@ -247,17 +254,17 @@ class BLERxView : public View {
|
|||||||
uint64_t total_count = 0;
|
uint64_t total_count = 0;
|
||||||
std::vector<std::string> searchList{};
|
std::vector<std::string> searchList{};
|
||||||
|
|
||||||
std::filesystem::path find_packet_path{u"BLERX/Find/????.TXT"};
|
std::filesystem::path find_packet_path{blerx_dir / u"Find/????.TXT"};
|
||||||
std::filesystem::path log_packets_path{u"BLERX/Logs/????.TXT"};
|
std::filesystem::path log_packets_path{blerx_dir / u"Logs/????.TXT"};
|
||||||
std::filesystem::path packet_save_path{u"BLERX/Lists/????.csv"};
|
std::filesystem::path packet_save_path{blerx_dir / u"Lists/????.csv"};
|
||||||
|
|
||||||
static constexpr auto header_height = 4 * 16;
|
static constexpr auto header_height = 9 * 8;
|
||||||
static constexpr auto switch_button_height = 3 * 16;
|
static constexpr auto switch_button_height = 3 * 16;
|
||||||
|
|
||||||
OptionsField options_channel{
|
OptionsField options_channel{
|
||||||
{0 * 8, 0 * 8},
|
{0 * 8, 0 * 8},
|
||||||
5,
|
5,
|
||||||
{{"Ch.37 ", 37},
|
{{"Ch.37", 37},
|
||||||
{"Ch.38", 38},
|
{"Ch.38", 38},
|
||||||
{"Ch.39", 39},
|
{"Ch.39", 39},
|
||||||
{"Auto", 40}}};
|
{"Auto", 40}}};
|
||||||
@@ -282,10 +289,10 @@ class BLERxView : public View {
|
|||||||
{24 * 8, 5, 6 * 8, 4}};
|
{24 * 8, 5, 6 * 8, 4}};
|
||||||
|
|
||||||
Labels label_sort{
|
Labels label_sort{
|
||||||
{{0 * 8, 3 * 8}, "Sort:", Color::light_grey()}};
|
{{0 * 8, 2 * 8}, "Sort:", Theme::getInstance()->fg_light->foreground}};
|
||||||
|
|
||||||
OptionsField options_sort{
|
OptionsField options_sort{
|
||||||
{5 * 8, 3 * 8},
|
{5 * 8, 2 * 8},
|
||||||
4,
|
4,
|
||||||
{{"MAC", 0},
|
{{"MAC", 0},
|
||||||
{"Hits", 1},
|
{"Hits", 1},
|
||||||
@@ -294,34 +301,46 @@ class BLERxView : public View {
|
|||||||
{"Name", 4}}};
|
{"Name", 4}}};
|
||||||
|
|
||||||
Button button_filter{
|
Button button_filter{
|
||||||
{11 * 8, 3 * 8, 4 * 8, 16},
|
{11 * 8, 2 * 8, 7 * 8, 16},
|
||||||
"Filter"};
|
"Filter:"};
|
||||||
|
|
||||||
|
OptionsField options_filter{
|
||||||
|
{18 * 8 + 2, 2 * 8},
|
||||||
|
4,
|
||||||
|
{{"Data", 0},
|
||||||
|
{"MAC", 1}}};
|
||||||
|
|
||||||
Checkbox check_log{
|
Checkbox check_log{
|
||||||
{17 * 8, 3 * 8},
|
{10 * 8, 4 * 8 + 2},
|
||||||
3,
|
3,
|
||||||
"Log",
|
"Log",
|
||||||
true};
|
true};
|
||||||
|
|
||||||
Checkbox check_name{
|
Checkbox check_name{
|
||||||
{23 * 8, 3 * 8},
|
{0 * 8, 4 * 8 + 2},
|
||||||
3,
|
3,
|
||||||
"Name",
|
"Name",
|
||||||
true};
|
true};
|
||||||
|
|
||||||
Button button_find{
|
Button button_find{
|
||||||
{0 * 8, 6 * 8, 4 * 8, 16},
|
{0 * 8, 7 * 8 - 2, 4 * 8, 16},
|
||||||
"Find"};
|
"Find"};
|
||||||
|
|
||||||
Labels label_found{
|
Labels label_found{
|
||||||
{{5 * 8, 6 * 8}, "Found:", Color::light_grey()}};
|
{{5 * 8, 7 * 8 - 2}, "Found:", Theme::getInstance()->fg_light->foreground}};
|
||||||
|
|
||||||
Text text_found_count{
|
Text text_found_count{
|
||||||
{11 * 8, 3 * 16, 20 * 8, 16},
|
{11 * 8, 7 * 8 - 2, 20 * 8, 16},
|
||||||
"0/0"};
|
"0/0"};
|
||||||
|
|
||||||
Console console{
|
Checkbox check_serial_log{
|
||||||
{0, 4 * 16, 240, 240}};
|
{18 * 8 + 2, 4 * 8 + 2},
|
||||||
|
7,
|
||||||
|
"USB Log",
|
||||||
|
true};
|
||||||
|
|
||||||
|
// Console console{
|
||||||
|
// {0, 10 * 8, 240, 240}};
|
||||||
|
|
||||||
Button button_clear_list{
|
Button button_clear_list{
|
||||||
{2 * 8, 320 - (16 + 32), 7 * 8, 32},
|
{2 * 8, 320 - (16 + 32), 7 * 8, 32},
|
||||||
@@ -361,7 +380,7 @@ class BLERxView : public View {
|
|||||||
[this](const Message* const) {
|
[this](const Message* const) {
|
||||||
this->on_timer();
|
this->on_timer();
|
||||||
}};
|
}};
|
||||||
};
|
}; /* BLERxView */
|
||||||
|
|
||||||
} /* namespace ui */
|
} /* namespace ui */
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,7 @@
|
|||||||
#include "portapack_persistent_memory.hpp"
|
#include "portapack_persistent_memory.hpp"
|
||||||
#include "rtc_time.hpp"
|
#include "rtc_time.hpp"
|
||||||
#include "string_format.hpp"
|
#include "string_format.hpp"
|
||||||
|
#include "file_path.hpp"
|
||||||
|
|
||||||
using namespace portapack;
|
using namespace portapack;
|
||||||
using namespace modems;
|
using namespace modems;
|
||||||
@@ -448,7 +449,7 @@ BLETxView::BLETxView(
|
|||||||
}
|
}
|
||||||
|
|
||||||
void BLETxView::on_file_changed(const fs::path& new_file_path) {
|
void BLETxView::on_file_changed(const fs::path& new_file_path) {
|
||||||
file_path = fs::path(u"/") + new_file_path;
|
file_path = new_file_path;
|
||||||
num_packets = 0;
|
num_packets = 0;
|
||||||
|
|
||||||
{ // Get the size of the data file.
|
{ // Get the size of the data file.
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
#include "replay_thread.hpp"
|
#include "replay_thread.hpp"
|
||||||
#include "log_file.hpp"
|
#include "log_file.hpp"
|
||||||
#include "utility.hpp"
|
#include "utility.hpp"
|
||||||
|
#include "file_path.hpp"
|
||||||
|
|
||||||
#include "recent_entries.hpp"
|
#include "recent_entries.hpp"
|
||||||
|
|
||||||
@@ -44,7 +45,7 @@
|
|||||||
|
|
||||||
class BLELoggerTx {
|
class BLELoggerTx {
|
||||||
public:
|
public:
|
||||||
Optional<File::Error> append(const std::string& filename) {
|
Optional<File::Error> append(const std::filesystem::path& filename) {
|
||||||
return log_file.append(filename);
|
return log_file.append(filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,7 +139,7 @@ class BLETxView : public View {
|
|||||||
uint32_t prev_value{0};
|
uint32_t prev_value{0};
|
||||||
|
|
||||||
std::filesystem::path file_path{};
|
std::filesystem::path file_path{};
|
||||||
std::filesystem::path packet_save_path{u"BLETX/BLETX_????.TXT"};
|
std::filesystem::path packet_save_path{bletx_dir / u"BLETX_????.TXT"};
|
||||||
uint8_t channel_number = 37;
|
uint8_t channel_number = 37;
|
||||||
bool auto_channel = false;
|
bool auto_channel = false;
|
||||||
|
|
||||||
@@ -165,7 +166,7 @@ class BLETxView : public View {
|
|||||||
|
|
||||||
std::unique_ptr<FileWrapper> dataFileWrapper{};
|
std::unique_ptr<FileWrapper> dataFileWrapper{};
|
||||||
File dataFile{};
|
File dataFile{};
|
||||||
std::filesystem::path dataTempFilePath{u"BLETX/dataFileTemp.TXT"};
|
std::filesystem::path dataTempFilePath{bletx_dir / u"dataFileTemp.TXT"};
|
||||||
std::vector<uint16_t> markedBytes{};
|
std::vector<uint16_t> markedBytes{};
|
||||||
CursorPos cursor_pos{};
|
CursorPos cursor_pos{};
|
||||||
uint8_t marked_counter = 0;
|
uint8_t marked_counter = 0;
|
||||||
@@ -217,11 +218,11 @@ class BLETxView : public View {
|
|||||||
ImageButton button_play{
|
ImageButton button_play{
|
||||||
{28 * 8, 2 * 16, 2 * 8, 1 * 16},
|
{28 * 8, 2 * 16, 2 * 8, 1 * 16},
|
||||||
&bitmap_play,
|
&bitmap_play,
|
||||||
Color::green(),
|
Theme::getInstance()->fg_green->foreground,
|
||||||
Color::black()};
|
Theme::getInstance()->fg_green->background};
|
||||||
|
|
||||||
Labels label_speed{
|
Labels label_speed{
|
||||||
{{0 * 8, 6 * 8}, "Speed:", Color::light_grey()}};
|
{{0 * 8, 6 * 8}, "Speed:", Theme::getInstance()->fg_light->foreground}};
|
||||||
|
|
||||||
OptionsField options_speed{
|
OptionsField options_speed{
|
||||||
{7 * 8, 6 * 8},
|
{7 * 8, 6 * 8},
|
||||||
@@ -253,7 +254,7 @@ class BLETxView : public View {
|
|||||||
{"CONNECT_REQ", PKT_TYPE_CONNECT_REQ}}};
|
{"CONNECT_REQ", PKT_TYPE_CONNECT_REQ}}};
|
||||||
|
|
||||||
Labels label_marked_data{
|
Labels label_marked_data{
|
||||||
{{0 * 8, 4 * 16}, "Marked Data:", Color::light_grey()}};
|
{{0 * 8, 4 * 16}, "Marked Data:", Theme::getInstance()->fg_light->foreground}};
|
||||||
|
|
||||||
OptionsField marked_data_sequence{
|
OptionsField marked_data_sequence{
|
||||||
{12 * 8, 8 * 8},
|
{12 * 8, 8 * 8},
|
||||||
@@ -263,28 +264,28 @@ class BLETxView : public View {
|
|||||||
{"Random", 2}}};
|
{"Random", 2}}};
|
||||||
|
|
||||||
Labels label_packet_index{
|
Labels label_packet_index{
|
||||||
{{0 * 8, 12 * 8}, "Packet Index:", Color::light_grey()}};
|
{{0 * 8, 12 * 8}, "Packet Index:", Theme::getInstance()->fg_light->foreground}};
|
||||||
|
|
||||||
Text text_packet_index{
|
Text text_packet_index{
|
||||||
{13 * 8, 6 * 16, 12 * 8, 16},
|
{13 * 8, 6 * 16, 12 * 8, 16},
|
||||||
"-"};
|
"-"};
|
||||||
|
|
||||||
Labels label_packets_sent{
|
Labels label_packets_sent{
|
||||||
{{0 * 8, 14 * 8}, "Repeat Count:", Color::light_grey()}};
|
{{0 * 8, 14 * 8}, "Repeat Count:", Theme::getInstance()->fg_light->foreground}};
|
||||||
|
|
||||||
Text text_packets_sent{
|
Text text_packets_sent{
|
||||||
{13 * 8, 7 * 16, 12 * 8, 16},
|
{13 * 8, 7 * 16, 12 * 8, 16},
|
||||||
"-"};
|
"-"};
|
||||||
|
|
||||||
Labels label_mac_address{
|
Labels label_mac_address{
|
||||||
{{0 * 8, 16 * 8}, "Mac Address:", Color::light_grey()}};
|
{{0 * 8, 16 * 8}, "Mac Address:", Theme::getInstance()->fg_light->foreground}};
|
||||||
|
|
||||||
Text text_mac_address{
|
Text text_mac_address{
|
||||||
{12 * 8, 8 * 16, 20 * 8, 16},
|
{12 * 8, 8 * 16, 20 * 8, 16},
|
||||||
"-"};
|
"-"};
|
||||||
|
|
||||||
Labels label_data_packet{
|
Labels label_data_packet{
|
||||||
{{0 * 8, 9 * 16}, "Packet Data:", Color::light_grey()}};
|
{{0 * 8, 9 * 16}, "Packet Data:", Theme::getInstance()->fg_light->foreground}};
|
||||||
|
|
||||||
Console console{
|
Console console{
|
||||||
{0, 9 * 18, 240, 240}};
|
{0, 9 * 18, 240, 240}};
|
||||||
|
|||||||
@@ -104,6 +104,14 @@ CaptureAppView::CaptureAppView(NavigationView& nav)
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CaptureAppView::CaptureAppView(
|
||||||
|
NavigationView& nav,
|
||||||
|
ReceiverModel::settings_t override)
|
||||||
|
: CaptureAppView(nav) {
|
||||||
|
// Settings to override when launched from another app (versus from AppSettings .ini file)
|
||||||
|
field_frequency.set_value(override.frequency_app_override);
|
||||||
|
}
|
||||||
|
|
||||||
CaptureAppView::~CaptureAppView() {
|
CaptureAppView::~CaptureAppView() {
|
||||||
receiver_model.disable();
|
receiver_model.disable();
|
||||||
baseband::shutdown();
|
baseband::shutdown();
|
||||||
@@ -120,4 +128,8 @@ void CaptureAppView::focus() {
|
|||||||
record_view.focus();
|
record_view.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CaptureAppView::on_freqchg(int64_t freq) {
|
||||||
|
field_frequency.set_value(freq);
|
||||||
|
}
|
||||||
|
|
||||||
} /* namespace ui */
|
} /* namespace ui */
|
||||||
|
|||||||
@@ -31,12 +31,14 @@
|
|||||||
#include "ui_spectrum.hpp"
|
#include "ui_spectrum.hpp"
|
||||||
#include "app_settings.hpp"
|
#include "app_settings.hpp"
|
||||||
#include "radio_state.hpp"
|
#include "radio_state.hpp"
|
||||||
|
#include "file_path.hpp"
|
||||||
|
|
||||||
namespace ui {
|
namespace ui {
|
||||||
|
|
||||||
class CaptureAppView : public View {
|
class CaptureAppView : public View {
|
||||||
public:
|
public:
|
||||||
CaptureAppView(NavigationView& nav);
|
CaptureAppView(NavigationView& nav);
|
||||||
|
CaptureAppView(NavigationView& nav, ReceiverModel::settings_t override);
|
||||||
~CaptureAppView();
|
~CaptureAppView();
|
||||||
|
|
||||||
void focus() override;
|
void focus() override;
|
||||||
@@ -51,12 +53,11 @@ class CaptureAppView : public View {
|
|||||||
NavigationView& nav_;
|
NavigationView& nav_;
|
||||||
RxRadioState radio_state_{ReceiverModel::Mode::Capture};
|
RxRadioState radio_state_{ReceiverModel::Mode::Capture};
|
||||||
app_settings::SettingsManager settings_{
|
app_settings::SettingsManager settings_{
|
||||||
"rx_capture", app_settings::Mode::RX,
|
"rx_capture", app_settings::Mode::RX};
|
||||||
app_settings::Options::UseGlobalTargetFrequency};
|
|
||||||
|
|
||||||
Labels labels{
|
Labels labels{
|
||||||
{{0 * 8, 1 * 16}, "Rate:", Color::light_grey()},
|
{{0 * 8, 1 * 16}, "Rate:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{11 * 8, 1 * 16}, "Format:", Color::light_grey()},
|
{{11 * 8, 1 * 16}, "Format:", Theme::getInstance()->fg_light->foreground},
|
||||||
};
|
};
|
||||||
|
|
||||||
RSSI rssi{
|
RSSI rssi{
|
||||||
@@ -101,12 +102,21 @@ class CaptureAppView : public View {
|
|||||||
RecordView record_view{
|
RecordView record_view{
|
||||||
{0 * 8, 2 * 16, 30 * 8, 1 * 16},
|
{0 * 8, 2 * 16, 30 * 8, 1 * 16},
|
||||||
u"BBD_????.*",
|
u"BBD_????.*",
|
||||||
u"CAPTURES",
|
captures_dir,
|
||||||
RecordView::FileType::RawS16,
|
RecordView::FileType::RawS16,
|
||||||
16384,
|
16384,
|
||||||
3};
|
3};
|
||||||
|
|
||||||
spectrum::WaterfallView waterfall{};
|
spectrum::WaterfallView waterfall{};
|
||||||
|
|
||||||
|
MessageHandlerRegistration message_handler_freqchg{
|
||||||
|
Message::ID::FreqChangeCommand,
|
||||||
|
[this](Message* const p) {
|
||||||
|
const auto message = static_cast<const FreqChangeCommandMessage*>(p);
|
||||||
|
this->on_freqchg(message->freq);
|
||||||
|
}};
|
||||||
|
|
||||||
|
void on_freqchg(int64_t freq);
|
||||||
};
|
};
|
||||||
|
|
||||||
} /* namespace ui */
|
} /* namespace ui */
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||||
|
* Copyright (C) 2023 Mark Thompson
|
||||||
*
|
*
|
||||||
* This file is part of PortaPack.
|
* This file is part of PortaPack.
|
||||||
*
|
*
|
||||||
@@ -22,7 +23,7 @@
|
|||||||
#include "ert_app.hpp"
|
#include "ert_app.hpp"
|
||||||
|
|
||||||
#include "baseband_api.hpp"
|
#include "baseband_api.hpp"
|
||||||
|
#include "audio.hpp"
|
||||||
#include "portapack.hpp"
|
#include "portapack.hpp"
|
||||||
using namespace portapack;
|
using namespace portapack;
|
||||||
|
|
||||||
@@ -30,6 +31,9 @@ using namespace portapack;
|
|||||||
|
|
||||||
#include "crc.hpp"
|
#include "crc.hpp"
|
||||||
#include "string_format.hpp"
|
#include "string_format.hpp"
|
||||||
|
#include "file_path.hpp"
|
||||||
|
|
||||||
|
namespace pmem = portapack::persistent_memory;
|
||||||
|
|
||||||
namespace ert {
|
namespace ert {
|
||||||
|
|
||||||
@@ -54,24 +58,31 @@ std::string id(ID value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::string consumption(Consumption value) {
|
std::string consumption(Consumption value) {
|
||||||
return to_string_dec_uint(value, 10);
|
return to_string_dec_uint(value, 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string commodity_type(CommodityType value) {
|
std::string commodity_type(CommodityType value) {
|
||||||
return to_string_dec_uint(value, 2);
|
return to_string_dec_uint(value, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string tamper_flags(TamperFlags value) {
|
||||||
|
return to_string_hex(value & 0xFFFF, 4); // Note: ignoring bits 32-47 of tamper flags in IDM type due to screen width
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string tamper_flags_scm(TamperFlags value) {
|
||||||
|
return " " + to_string_hex(value & 0x0F, 1) + "/" + to_string_hex(value >> 4, 1); // Physical/Encoder flags
|
||||||
|
}
|
||||||
|
|
||||||
} /* namespace format */
|
} /* namespace format */
|
||||||
|
|
||||||
} /* namespace ert */
|
} /* namespace ert */
|
||||||
|
|
||||||
void ERTLogger::on_packet(const ert::Packet& packet, const uint32_t target_frequency) {
|
void ERTLogger::on_packet(const ert::Packet& packet, const uint32_t target_frequency) {
|
||||||
const auto formatted = packet.symbols_formatted();
|
const auto formatted = packet.symbols_formatted();
|
||||||
|
|
||||||
// TODO: function doesn't take uint64_t, so when >= 1<<32, weirdness will ensue!
|
|
||||||
const auto target_frequency_str = to_string_dec_uint(target_frequency, 10);
|
const auto target_frequency_str = to_string_dec_uint(target_frequency, 10);
|
||||||
|
|
||||||
std::string entry = target_frequency_str + " " + ert::format::type(packet.type()) + " " + formatted.data + "/" + formatted.errors;
|
std::string entry = target_frequency_str + " " + ert::format::type(packet.type()) + " " + formatted.data + "/" + formatted.errors + " ID:" + to_string_dec_uint(packet.id(), 1);
|
||||||
|
|
||||||
log_file.write_entry(packet.received_at(), entry);
|
log_file.write_entry(packet.received_at(), entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,6 +92,8 @@ void ERTRecentEntry::update(const ert::Packet& packet) {
|
|||||||
received_count++;
|
received_count++;
|
||||||
|
|
||||||
last_consumption = packet.consumption();
|
last_consumption = packet.consumption();
|
||||||
|
last_tamper_flags = packet.tamper_flags();
|
||||||
|
packet_type = packet.type();
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace ui {
|
namespace ui {
|
||||||
@@ -91,13 +104,10 @@ void RecentEntriesTable<ERTRecentEntries>::draw(
|
|||||||
const Rect& target_rect,
|
const Rect& target_rect,
|
||||||
Painter& painter,
|
Painter& painter,
|
||||||
const Style& style) {
|
const Style& style) {
|
||||||
std::string line = ert::format::id(entry.id) + " " + ert::format::commodity_type(entry.commodity_type) + " " + ert::format::consumption(entry.last_consumption);
|
std::string line = ert::format::id(entry.id) + " " + ert::format::commodity_type(entry.commodity_type) + " " + ert::format::consumption(entry.last_consumption) + " ";
|
||||||
|
|
||||||
if (entry.received_count > 999) {
|
line += (entry.packet_type == ert::Packet::Type::SCM) ? ert::format::tamper_flags_scm(entry.last_tamper_flags) : ert::format::tamper_flags(entry.last_tamper_flags);
|
||||||
line += " +++";
|
line += (entry.received_count > 99) ? " ++" : to_string_dec_uint(entry.received_count, 3);
|
||||||
} else {
|
|
||||||
line += " " + to_string_dec_uint(entry.received_count, 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
line.resize(target_rect.width() / 8, ' ');
|
line.resize(target_rect.width() / 8, ' ');
|
||||||
painter.draw_string(target_rect.location(), style, line);
|
painter.draw_string(target_rect.location(), style, line);
|
||||||
@@ -113,6 +123,7 @@ ERTAppView::ERTAppView(NavigationView& nav)
|
|||||||
&field_lna,
|
&field_lna,
|
||||||
&field_vga,
|
&field_vga,
|
||||||
&rssi,
|
&rssi,
|
||||||
|
&field_volume,
|
||||||
&recent_entries_view,
|
&recent_entries_view,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -122,11 +133,17 @@ ERTAppView::ERTAppView(NavigationView& nav)
|
|||||||
|
|
||||||
logger = std::make_unique<ERTLogger>();
|
logger = std::make_unique<ERTLogger>();
|
||||||
if (logger) {
|
if (logger) {
|
||||||
logger->append(LOG_ROOT_DIR "/ERT.TXT");
|
logger->append(logs_dir / u"ERT.TXT");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pmem::beep_on_packets()) {
|
||||||
|
audio::set_rate(audio::Rate::Hz_24000);
|
||||||
|
audio::output::start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ERTAppView::~ERTAppView() {
|
ERTAppView::~ERTAppView() {
|
||||||
|
audio::output::stop();
|
||||||
receiver_model.disable();
|
receiver_model.disable();
|
||||||
baseband::shutdown();
|
baseband::shutdown();
|
||||||
}
|
}
|
||||||
@@ -150,6 +167,10 @@ void ERTAppView::on_packet(const ert::Packet& packet) {
|
|||||||
entry.update(packet);
|
entry.update(packet);
|
||||||
recent_entries_view.set_dirty();
|
recent_entries_view.set_dirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (pmem::beep_on_packets()) {
|
||||||
|
baseband::request_audio_beep(1000, 24000, 60);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ERTAppView::on_show_list() {
|
void ERTAppView::on_show_list() {
|
||||||
@@ -157,4 +178,8 @@ void ERTAppView::on_show_list() {
|
|||||||
recent_entries_view.focus();
|
recent_entries_view.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ERTAppView::on_freqchg(int64_t freq) {
|
||||||
|
field_frequency.set_value(freq);
|
||||||
|
}
|
||||||
|
|
||||||
} /* namespace ui */
|
} /* namespace ui */
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||||
|
* Copyright (C) 2023 Mark Thompson
|
||||||
*
|
*
|
||||||
* This file is part of PortaPack.
|
* This file is part of PortaPack.
|
||||||
*
|
*
|
||||||
@@ -72,11 +73,12 @@ struct ERTRecentEntry {
|
|||||||
|
|
||||||
ert::ID id{ert::invalid_id};
|
ert::ID id{ert::invalid_id};
|
||||||
ert::CommodityType commodity_type{ert::invalid_commodity_type};
|
ert::CommodityType commodity_type{ert::invalid_commodity_type};
|
||||||
|
ert::Consumption last_consumption{};
|
||||||
|
ert::TamperFlags last_tamper_flags{};
|
||||||
|
ert::Packet::Type packet_type{};
|
||||||
|
|
||||||
size_t received_count{0};
|
size_t received_count{0};
|
||||||
|
|
||||||
ert::Consumption last_consumption{};
|
|
||||||
|
|
||||||
ERTRecentEntry(
|
ERTRecentEntry(
|
||||||
const Key& key)
|
const Key& key)
|
||||||
: id{key.id},
|
: id{key.id},
|
||||||
@@ -137,16 +139,17 @@ class ERTAppView : public View {
|
|||||||
|
|
||||||
const RecentEntriesColumns columns{{
|
const RecentEntriesColumns columns{{
|
||||||
{"ID", 10},
|
{"ID", 10},
|
||||||
{"Tp", 2},
|
{"Ty", 2},
|
||||||
{"Consumpt", 10},
|
{"Consumpt", 8},
|
||||||
{"Cnt", 3},
|
{"Tamp", 4},
|
||||||
|
{"Ct", 2},
|
||||||
}};
|
}};
|
||||||
ERTRecentEntriesView recent_entries_view{columns, recent};
|
ERTRecentEntriesView recent_entries_view{columns, recent};
|
||||||
|
|
||||||
static constexpr auto header_height = 1 * 16;
|
static constexpr auto header_height = 1 * 16;
|
||||||
|
|
||||||
RxFrequencyField field_frequency{
|
RxFrequencyField field_frequency{
|
||||||
{5 * 8, 0 * 16},
|
{0 * 8, 0 * 16},
|
||||||
nav_};
|
nav_};
|
||||||
|
|
||||||
RFAmpField field_rf_amp{
|
RFAmpField field_rf_amp{
|
||||||
@@ -162,6 +165,9 @@ class ERTAppView : public View {
|
|||||||
{21 * 8, 0, 6 * 8, 4},
|
{21 * 8, 0, 6 * 8, 4},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
AudioVolumeField field_volume{
|
||||||
|
{28 * 8, 0 * 16}};
|
||||||
|
|
||||||
MessageHandlerRegistration message_handler_packet{
|
MessageHandlerRegistration message_handler_packet{
|
||||||
Message::ID::ERTPacket,
|
Message::ID::ERTPacket,
|
||||||
[this](Message* const p) {
|
[this](Message* const p) {
|
||||||
@@ -170,6 +176,14 @@ class ERTAppView : public View {
|
|||||||
this->on_packet(packet);
|
this->on_packet(packet);
|
||||||
}};
|
}};
|
||||||
|
|
||||||
|
MessageHandlerRegistration message_handler_freqchg{
|
||||||
|
Message::ID::FreqChangeCommand,
|
||||||
|
[this](Message* const p) {
|
||||||
|
const auto message = static_cast<const FreqChangeCommandMessage*>(p);
|
||||||
|
this->on_freqchg(message->freq);
|
||||||
|
}};
|
||||||
|
|
||||||
|
void on_freqchg(int64_t freq);
|
||||||
void on_packet(const ert::Packet& packet);
|
void on_packet(const ert::Packet& packet);
|
||||||
void on_show_list();
|
void on_show_list();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
#include "portapack_persistent_memory.hpp"
|
#include "portapack_persistent_memory.hpp"
|
||||||
#include "string_format.hpp"
|
#include "string_format.hpp"
|
||||||
#include "utility.hpp"
|
#include "utility.hpp"
|
||||||
|
#include "file_path.hpp"
|
||||||
|
|
||||||
using namespace portapack;
|
using namespace portapack;
|
||||||
using namespace pocsag;
|
using namespace pocsag;
|
||||||
@@ -115,7 +116,7 @@ POCSAGAppView::POCSAGAppView(NavigationView& nav)
|
|||||||
: FILTER_DROP;
|
: FILTER_DROP;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.append(LOG_ROOT_DIR "/POCSAG.TXT");
|
logger.append(logs_dir / u"POCSAG.TXT");
|
||||||
|
|
||||||
field_squelch.set_value(receiver_model.squelch_level());
|
field_squelch.set_value(receiver_model.squelch_level());
|
||||||
field_squelch.on_change = [this](int32_t v) {
|
field_squelch.on_change = [this](int32_t v) {
|
||||||
@@ -135,6 +136,10 @@ POCSAGAppView::POCSAGAppView(NavigationView& nav)
|
|||||||
};
|
};
|
||||||
|
|
||||||
refresh_ui();
|
refresh_ui();
|
||||||
|
|
||||||
|
if (pmem::beep_on_packets())
|
||||||
|
audio::set_rate(audio::Rate::Hz_24000);
|
||||||
|
|
||||||
audio::output::start();
|
audio::output::start();
|
||||||
receiver_model.enable();
|
receiver_model.enable();
|
||||||
baseband::set_pocsag();
|
baseband::set_pocsag();
|
||||||
@@ -158,8 +163,8 @@ void POCSAGAppView::refresh_ui() {
|
|||||||
// Set console font style.
|
// Set console font style.
|
||||||
console.set_style(
|
console.set_style(
|
||||||
settings_.enable_small_font
|
settings_.enable_small_font
|
||||||
? &Styles::white_small
|
? Theme::getInstance()->bg_darkest_small
|
||||||
: &Styles::white);
|
: Theme::getInstance()->bg_darkest);
|
||||||
|
|
||||||
// Update filter button text.
|
// Update filter button text.
|
||||||
std::string btn_text = "Filter Last";
|
std::string btn_text = "Filter Last";
|
||||||
@@ -255,19 +260,19 @@ void POCSAGAppView::handle_decoded(Timestamp timestamp, const std::string& prefi
|
|||||||
|
|
||||||
static Color get_status_color(const POCSAGState& state) {
|
static Color get_status_color(const POCSAGState& state) {
|
||||||
if (state.out_type == IDLE)
|
if (state.out_type == IDLE)
|
||||||
return Color::white();
|
return Theme::getInstance()->bg_darkest->foreground;
|
||||||
|
|
||||||
switch (state.mode) {
|
switch (state.mode) {
|
||||||
case STATE_CLEAR:
|
case STATE_CLEAR:
|
||||||
return Color::cyan();
|
return Theme::getInstance()->fg_cyan->foreground;
|
||||||
case STATE_HAVE_ADDRESS:
|
case STATE_HAVE_ADDRESS:
|
||||||
return Color::yellow();
|
return Theme::getInstance()->fg_yellow->foreground;
|
||||||
case STATE_GETTING_MSG:
|
case STATE_GETTING_MSG:
|
||||||
return Color::green();
|
return Theme::getInstance()->fg_green->foreground;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shouldn't get here...
|
// Shouldn't get here...
|
||||||
return Color::red();
|
return Theme::getInstance()->fg_red->foreground;
|
||||||
}
|
}
|
||||||
|
|
||||||
void POCSAGAppView::on_packet(const POCSAGPacketMessage* message) {
|
void POCSAGAppView::on_packet(const POCSAGPacketMessage* message) {
|
||||||
@@ -289,7 +294,7 @@ void POCSAGAppView::on_packet(const POCSAGPacketMessage* message) {
|
|||||||
last_address = 0;
|
last_address = 0;
|
||||||
} else {
|
} else {
|
||||||
// Set color before to be able to see if decode gets stuck.
|
// Set color before to be able to see if decode gets stuck.
|
||||||
image_status.set_foreground(Color::magenta());
|
image_status.set_foreground(Theme::getInstance()->fg_magenta->foreground);
|
||||||
pocsag_state.codeword_index = 0;
|
pocsag_state.codeword_index = 0;
|
||||||
pocsag_state.errors = 0;
|
pocsag_state.errors = 0;
|
||||||
|
|
||||||
@@ -303,6 +308,10 @@ void POCSAGAppView::on_packet(const POCSAGPacketMessage* message) {
|
|||||||
|
|
||||||
// Set status icon color to indicate state machine state.
|
// Set status icon color to indicate state machine state.
|
||||||
image_status.set_foreground(get_status_color(pocsag_state));
|
image_status.set_foreground(get_status_color(pocsag_state));
|
||||||
|
|
||||||
|
if (pmem::beep_on_packets()) {
|
||||||
|
baseband::request_audio_beep(1000, 24000, 60);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void POCSAGAppView::on_stats(const POCSAGStatsMessage* stats) {
|
void POCSAGAppView::on_stats(const POCSAGStatsMessage* stats) {
|
||||||
@@ -312,6 +321,10 @@ void POCSAGAppView::on_stats(const POCSAGStatsMessage* stats) {
|
|||||||
widget_frames.set_sync(stats->has_sync);
|
widget_frames.set_sync(stats->has_sync);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void POCSAGAppView::on_freqchg(int64_t freq) {
|
||||||
|
field_frequency.set_value(freq);
|
||||||
|
}
|
||||||
|
|
||||||
void BaudIndicator::paint(Painter& painter) {
|
void BaudIndicator::paint(Painter& painter) {
|
||||||
auto p = screen_pos();
|
auto p = screen_pos();
|
||||||
char top = '-';
|
char top = '-';
|
||||||
@@ -323,8 +336,8 @@ void BaudIndicator::paint(Painter& painter) {
|
|||||||
bot = (r % 10) + '0';
|
bot = (r % 10) + '0';
|
||||||
}
|
}
|
||||||
|
|
||||||
painter.draw_char(p, Styles::white_small, top);
|
painter.draw_char(p, *Theme::getInstance()->bg_darkest_small, top);
|
||||||
painter.draw_char({p.x(), p.y() + 8}, Styles::white_small, bot);
|
painter.draw_char({p.x(), p.y() + 8}, *Theme::getInstance()->bg_darkest_small, bot);
|
||||||
}
|
}
|
||||||
|
|
||||||
void BitsIndicator::paint(Painter&) {
|
void BitsIndicator::paint(Painter&) {
|
||||||
@@ -334,17 +347,17 @@ void BitsIndicator::paint(Painter&) {
|
|||||||
|
|
||||||
int x = p.x() + (i / height);
|
int x = p.x() + (i / height);
|
||||||
int y = p.y() + (i % height);
|
int y = p.y() + (i % height);
|
||||||
display.draw_pixel({x, y}, is_set ? Color::white() : Color::black());
|
display.draw_pixel({x, y}, is_set ? Theme::getInstance()->bg_darkest->foreground : Theme::getInstance()->bg_darkest->background);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void FrameIndicator::paint(Painter& painter) {
|
void FrameIndicator::paint(Painter& painter) {
|
||||||
auto p = screen_pos();
|
auto p = screen_pos();
|
||||||
painter.draw_rectangle({p, {2, height}}, has_sync_ ? Color::green() : Color::grey());
|
painter.draw_rectangle({p, {2, height}}, has_sync_ ? Theme::getInstance()->fg_green->foreground : Theme::getInstance()->bg_medium->background);
|
||||||
|
|
||||||
for (size_t i = 0; i < height; ++i) {
|
for (size_t i = 0; i < height; ++i) {
|
||||||
auto p2 = p + Point{2, 15 - (int)i};
|
auto p2 = p + Point{2, 15 - (int)i};
|
||||||
painter.draw_hline(p2, 2, i < frame_count_ ? Color::white() : Color::black());
|
painter.draw_hline(p2, 2, i < frame_count_ ? Theme::getInstance()->bg_darkest->foreground : Theme::getInstance()->bg_darkest->background);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,6 @@
|
|||||||
#include "ui_freq_field.hpp"
|
#include "ui_freq_field.hpp"
|
||||||
#include "ui_receiver.hpp"
|
#include "ui_receiver.hpp"
|
||||||
#include "ui_rssi.hpp"
|
#include "ui_rssi.hpp"
|
||||||
#include "ui_styles.hpp"
|
|
||||||
|
|
||||||
#include "app_settings.hpp"
|
#include "app_settings.hpp"
|
||||||
#include "log_file.hpp"
|
#include "log_file.hpp"
|
||||||
@@ -39,7 +38,7 @@
|
|||||||
|
|
||||||
class POCSAGLogger {
|
class POCSAGLogger {
|
||||||
public:
|
public:
|
||||||
Optional<File::Error> append(const std::string& filename) {
|
Optional<File::Error> append(const std::filesystem::path& filename) {
|
||||||
return log_file.append(filename);
|
return log_file.append(filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,8 +140,8 @@ class POCSAGSettingsView : public View {
|
|||||||
POCSAGSettings& settings_;
|
POCSAGSettings& settings_;
|
||||||
|
|
||||||
Labels labels{
|
Labels labels{
|
||||||
{{2 * 8, 12 * 16}, "Filter Mode:", Color::light_grey()},
|
{{2 * 8, 12 * 16}, "Filter Mode:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{2 * 8, 13 * 16}, "Filter Addr:", Color::light_grey()},
|
{{2 * 8, 13 * 16}, "Filter Addr:", Theme::getInstance()->fg_light->foreground},
|
||||||
};
|
};
|
||||||
|
|
||||||
Checkbox check_log{
|
Checkbox check_log{
|
||||||
@@ -265,8 +264,8 @@ class POCSAGAppView : public View {
|
|||||||
Image image_status{
|
Image image_status{
|
||||||
{0 * 8 + 4, 1 * 16 + 2, 16, 16},
|
{0 * 8 + 4, 1 * 16 + 2, 16, 16},
|
||||||
&bitmap_icon_pocsag,
|
&bitmap_icon_pocsag,
|
||||||
Color::white(),
|
Theme::getInstance()->bg_darkest->foreground,
|
||||||
Color::black()};
|
Theme::getInstance()->bg_darkest->background};
|
||||||
|
|
||||||
Text text_packet_count{
|
Text text_packet_count{
|
||||||
{3 * 8, 1 * 16 + 2, 5 * 8, 16},
|
{3 * 8, 1 * 16 + 2, 5 * 8, 16},
|
||||||
@@ -293,6 +292,15 @@ class POCSAGAppView : public View {
|
|||||||
Console console{
|
Console console{
|
||||||
{0, 2 * 16 + 6, screen_width, screen_height - 54}};
|
{0, 2 * 16 + 6, screen_width, screen_height - 54}};
|
||||||
|
|
||||||
|
void on_freqchg(int64_t freq);
|
||||||
|
|
||||||
|
MessageHandlerRegistration message_handler_freqchg{
|
||||||
|
Message::ID::FreqChangeCommand,
|
||||||
|
[this](Message* const p) {
|
||||||
|
const auto message = static_cast<const FreqChangeCommandMessage*>(p);
|
||||||
|
this->on_freqchg(message->freq);
|
||||||
|
}};
|
||||||
|
|
||||||
MessageHandlerRegistration message_handler_packet{
|
MessageHandlerRegistration message_handler_packet{
|
||||||
Message::ID::POCSAGPacket,
|
Message::ID::POCSAGPacket,
|
||||||
[this](Message* const p) {
|
[this](Message* const p) {
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ void ReplayAppView::set_ready() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ReplayAppView::on_file_changed(const fs::path& new_file_path) {
|
void ReplayAppView::on_file_changed(const fs::path& new_file_path) {
|
||||||
file_path = fs::path(u"/") + new_file_path;
|
file_path = new_file_path;
|
||||||
File::Size file_size{};
|
File::Size file_size{};
|
||||||
|
|
||||||
{ // Get the size of the data file.
|
{ // Get the size of the data file.
|
||||||
|
|||||||
@@ -112,8 +112,8 @@ class ReplayAppView : public View {
|
|||||||
ImageButton button_play{
|
ImageButton button_play{
|
||||||
{28 * 8, 2 * 16, 2 * 8, 1 * 16},
|
{28 * 8, 2 * 16, 2 * 8, 1 * 16},
|
||||||
&bitmap_play,
|
&bitmap_play,
|
||||||
Color::green(),
|
Theme::getInstance()->fg_green->foreground,
|
||||||
Color::black()};
|
Theme::getInstance()->fg_green->background};
|
||||||
|
|
||||||
spectrum::WaterfallView waterfall{};
|
spectrum::WaterfallView waterfall{};
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||||
* Copyright (C) 2016 Furrtek
|
* Copyright (C) 2016 Furrtek
|
||||||
|
* Copyright (C) 2024 Mark Thompson
|
||||||
*
|
*
|
||||||
* This file is part of PortaPack.
|
* This file is part of PortaPack.
|
||||||
*
|
*
|
||||||
@@ -26,6 +27,8 @@
|
|||||||
#include "string_format.hpp"
|
#include "string_format.hpp"
|
||||||
#include "tonesets.hpp"
|
#include "tonesets.hpp"
|
||||||
#include "ui_tone_key.hpp"
|
#include "ui_tone_key.hpp"
|
||||||
|
#include "audio.hpp"
|
||||||
|
#include "file_path.hpp"
|
||||||
|
|
||||||
using namespace tonekey;
|
using namespace tonekey;
|
||||||
using namespace portapack;
|
using namespace portapack;
|
||||||
@@ -40,6 +43,7 @@ void SoundBoardView::stop() {
|
|||||||
if (is_active())
|
if (is_active())
|
||||||
replay_thread.reset();
|
replay_thread.reset();
|
||||||
|
|
||||||
|
audio::output::stop();
|
||||||
transmitter_model.disable();
|
transmitter_model.disable();
|
||||||
tx_view.set_transmitting(false);
|
tx_view.set_transmitting(false);
|
||||||
|
|
||||||
@@ -49,7 +53,7 @@ void SoundBoardView::stop() {
|
|||||||
|
|
||||||
void SoundBoardView::handle_replay_thread_done(const uint32_t return_code) {
|
void SoundBoardView::handle_replay_thread_done(const uint32_t return_code) {
|
||||||
stop();
|
stop();
|
||||||
// progressbar.set_value(0);
|
progressbar.set_value(0);
|
||||||
|
|
||||||
if (return_code == ReplayThread::END_OF_FILE) {
|
if (return_code == ReplayThread::END_OF_FILE) {
|
||||||
if (check_random.value()) {
|
if (check_random.value()) {
|
||||||
@@ -78,9 +82,16 @@ void SoundBoardView::file_error() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void SoundBoardView::start_tx(const uint32_t id) {
|
void SoundBoardView::start_tx(const uint32_t id) {
|
||||||
|
if (file_list.empty()) {
|
||||||
|
file_error();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
auto reader = std::make_unique<WAVFileReader>();
|
auto reader = std::make_unique<WAVFileReader>();
|
||||||
|
|
||||||
uint32_t tone_key_index = options_tone_key.selected_index();
|
uint32_t tone_key_index = options_tone_key.selected_index();
|
||||||
uint32_t sample_rate;
|
uint32_t sample_rate;
|
||||||
|
uint8_t bits_per_sample;
|
||||||
|
|
||||||
stop();
|
stop();
|
||||||
|
|
||||||
@@ -91,11 +102,12 @@ void SoundBoardView::start_tx(const uint32_t id) {
|
|||||||
|
|
||||||
playing_id = id;
|
playing_id = id;
|
||||||
|
|
||||||
// progressbar.set_max(reader->sample_count());
|
progressbar.set_max(reader->sample_count());
|
||||||
|
|
||||||
// button_play.set_bitmap(&bitmap_stop);
|
// button_play.set_bitmap(&bitmap_stop);
|
||||||
|
|
||||||
sample_rate = reader->sample_rate();
|
sample_rate = reader->sample_rate();
|
||||||
|
bits_per_sample = reader->bits_per_sample();
|
||||||
|
|
||||||
replay_thread = std::make_unique<ReplayThread>(
|
replay_thread = std::make_unique<ReplayThread>(
|
||||||
std::move(reader),
|
std::move(reader),
|
||||||
@@ -111,18 +123,22 @@ void SoundBoardView::start_tx(const uint32_t id) {
|
|||||||
1536000 / 20, // Update vu-meter at 20Hz
|
1536000 / 20, // Update vu-meter at 20Hz
|
||||||
transmitter_model.channel_bandwidth(),
|
transmitter_model.channel_bandwidth(),
|
||||||
0, // Gain is unused
|
0, // Gain is unused
|
||||||
8, // shift_bits_s16, default 8 bits, but also unused
|
8, // shift_bits_s16, default 8 bits, but also unused
|
||||||
TONES_F2D(tone_key_frequency(tone_key_index), 1536000),
|
bits_per_sample,
|
||||||
0, // AM
|
TONES_F2D(tone_key_frequency(tone_key_index), TONES_SAMPLERATE),
|
||||||
0, // DSB
|
false, // AM
|
||||||
0, // USB
|
false, // DSB
|
||||||
0 // LSB
|
false, // USB
|
||||||
|
false // LSB
|
||||||
);
|
);
|
||||||
baseband::set_sample_rate(sample_rate);
|
baseband::set_sample_rate(sample_rate);
|
||||||
|
|
||||||
transmitter_model.enable();
|
transmitter_model.enable();
|
||||||
|
|
||||||
tx_view.set_transmitting(true);
|
tx_view.set_transmitting(true);
|
||||||
|
|
||||||
|
if (tone_key_index == 0)
|
||||||
|
audio::output::start();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*void SoundBoardView::show_infos() {
|
/*void SoundBoardView::show_infos() {
|
||||||
@@ -134,8 +150,7 @@ void SoundBoardView::start_tx(const uint32_t id) {
|
|||||||
}*/
|
}*/
|
||||||
|
|
||||||
void SoundBoardView::on_tx_progress(const uint32_t progress) {
|
void SoundBoardView::on_tx_progress(const uint32_t progress) {
|
||||||
(void)progress; // avoid warning
|
progressbar.set_value(progress);
|
||||||
// progressbar.set_value(progress);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SoundBoardView::on_select_entry() {
|
void SoundBoardView::on_select_entry() {
|
||||||
@@ -150,7 +165,7 @@ void SoundBoardView::refresh_list() {
|
|||||||
|
|
||||||
// List directories and files, put directories up top
|
// List directories and files, put directories up top
|
||||||
uint32_t count = 0;
|
uint32_t count = 0;
|
||||||
for (const auto& entry : std::filesystem::directory_iterator(u"WAV", u"*")) {
|
for (const auto& entry : std::filesystem::directory_iterator(wav_dir, u"*")) {
|
||||||
if (std::filesystem::is_regular_file(entry.status())) {
|
if (std::filesystem::is_regular_file(entry.status())) {
|
||||||
if (entry.path().string().length()) {
|
if (entry.path().string().length()) {
|
||||||
auto entry_extension = entry.path().extension().string();
|
auto entry_extension = entry.path().extension().string();
|
||||||
@@ -159,8 +174,8 @@ void SoundBoardView::refresh_list() {
|
|||||||
c = toupper(c);
|
c = toupper(c);
|
||||||
|
|
||||||
if (entry_extension == ".WAV") {
|
if (entry_extension == ".WAV") {
|
||||||
if (reader->open(u"/WAV/" + entry.path().native())) {
|
if (reader->open(wav_dir / entry.path())) {
|
||||||
if ((reader->channels() == 1) && (reader->bits_per_sample() == 8)) {
|
if ((reader->channels() == 1) && ((reader->bits_per_sample() == 8) || (reader->bits_per_sample() == 16))) {
|
||||||
// sounds[c].ms_duration = reader->ms_duration();
|
// sounds[c].ms_duration = reader->ms_duration();
|
||||||
// sounds[c].path = u"WAV/" + entry.path().native();
|
// sounds[c].path = u"WAV/" + entry.path().native();
|
||||||
if (count >= (page - 1) * 100 && count < page * 100) {
|
if (count >= (page - 1) * 100 && count < page * 100) {
|
||||||
@@ -199,7 +214,7 @@ void SoundBoardView::refresh_list() {
|
|||||||
|
|
||||||
for (size_t n = 0; n < file_list.size(); n++) {
|
for (size_t n = 0; n < file_list.size(); n++) {
|
||||||
menu_view.add_item({file_list[n].string().substr(0, 30),
|
menu_view.add_item({file_list[n].string().substr(0, 30),
|
||||||
ui::Color::white(),
|
ui::Theme::getInstance()->fg_magenta->foreground,
|
||||||
nullptr,
|
nullptr,
|
||||||
[this](KeyEvent) {
|
[this](KeyEvent) {
|
||||||
on_select_entry();
|
on_select_entry();
|
||||||
@@ -226,7 +241,9 @@ SoundBoardView::SoundBoardView(
|
|||||||
&options_tone_key,
|
&options_tone_key,
|
||||||
//&text_title,
|
//&text_title,
|
||||||
//&text_duration,
|
//&text_duration,
|
||||||
//&progressbar,
|
&progressbar,
|
||||||
|
&field_volume,
|
||||||
|
&text_volume_disabled,
|
||||||
&page_info,
|
&page_info,
|
||||||
&check_loop,
|
&check_loop,
|
||||||
&check_random,
|
&check_random,
|
||||||
@@ -252,6 +269,13 @@ SoundBoardView::SoundBoardView(
|
|||||||
tone_keys_populate(options_tone_key);
|
tone_keys_populate(options_tone_key);
|
||||||
options_tone_key.set_selected_index(0);
|
options_tone_key.set_selected_index(0);
|
||||||
|
|
||||||
|
text_volume_disabled.hidden(true);
|
||||||
|
options_tone_key.on_change = [this](size_t index, OptionsField::value_t) {
|
||||||
|
bool tone_key_enabled = (index != 0);
|
||||||
|
text_volume_disabled.hidden(!tone_key_enabled);
|
||||||
|
field_volume.hidden(tone_key_enabled);
|
||||||
|
};
|
||||||
|
|
||||||
check_loop.set_value(false);
|
check_loop.set_value(false);
|
||||||
check_random.set_value(false);
|
check_random.set_value(false);
|
||||||
|
|
||||||
@@ -274,7 +298,6 @@ SoundBoardView::SoundBoardView(
|
|||||||
|
|
||||||
SoundBoardView::~SoundBoardView() {
|
SoundBoardView::~SoundBoardView() {
|
||||||
stop();
|
stop();
|
||||||
transmitter_model.disable();
|
|
||||||
baseband::shutdown();
|
baseband::shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||||
* Copyright (C) 2016 Furrtek
|
* Copyright (C) 2016 Furrtek
|
||||||
|
* Copyright (C) 2024 Mark Thompson
|
||||||
*
|
*
|
||||||
* This file is part of PortaPack.
|
* This file is part of PortaPack.
|
||||||
*
|
*
|
||||||
@@ -91,8 +92,8 @@ class SoundBoardView : public View {
|
|||||||
void on_select_entry();
|
void on_select_entry();
|
||||||
|
|
||||||
Labels labels{
|
Labels labels{
|
||||||
//{ { 0, 20 * 8 + 4 }, "Title:", Color::light_grey() },
|
{{24 * 8, 180}, "Vol:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{0, 180}, "Key:", Color::light_grey()}};
|
{{0, 180}, "Key:", Theme::getInstance()->fg_light->foreground}};
|
||||||
|
|
||||||
Button button_next_page{
|
Button button_next_page{
|
||||||
{30 * 7, 25 * 8, 10 * 3, 2 * 14},
|
{30 * 7, 25 * 8, 10 * 3, 2 * 14},
|
||||||
@@ -103,7 +104,7 @@ class SoundBoardView : public View {
|
|||||||
"<="};
|
"<="};
|
||||||
|
|
||||||
Text page_info{
|
Text page_info{
|
||||||
{0, 30 * 8 - 4, 30 * 8, 16}};
|
{0, 29 * 8, 30 * 8, 16}};
|
||||||
|
|
||||||
MenuView menu_view{
|
MenuView menu_view{
|
||||||
{0, 0, 240, 175},
|
{0, 0, 240, 175},
|
||||||
@@ -122,10 +123,16 @@ class SoundBoardView : public View {
|
|||||||
};*/
|
};*/
|
||||||
|
|
||||||
OptionsField options_tone_key{
|
OptionsField options_tone_key{
|
||||||
{32, 180},
|
{4 * 8, 180},
|
||||||
18,
|
18,
|
||||||
{}};
|
{}};
|
||||||
|
|
||||||
|
AudioVolumeField field_volume{
|
||||||
|
{28 * 8, 180}};
|
||||||
|
Text text_volume_disabled{
|
||||||
|
{28 * 8, 180, 3 * 8, 16},
|
||||||
|
"--"};
|
||||||
|
|
||||||
Checkbox check_loop{
|
Checkbox check_loop{
|
||||||
{0, 25 * 8 + 4},
|
{0, 25 * 8 + 4},
|
||||||
4,
|
4,
|
||||||
@@ -136,9 +143,8 @@ class SoundBoardView : public View {
|
|||||||
6,
|
6,
|
||||||
"Random"};
|
"Random"};
|
||||||
|
|
||||||
// ProgressBar progressbar {
|
ProgressBar progressbar{
|
||||||
// { 0 * 8, 30 * 8 - 4, 30 * 8, 16 }
|
{0 * 8, 31 * 8 + 2, 30 * 8, 4}};
|
||||||
// };
|
|
||||||
|
|
||||||
TransmitterView tx_view{
|
TransmitterView tx_view{
|
||||||
16 * 16,
|
16 * 16,
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ void CreditsWidget::new_row(
|
|||||||
void CreditsWidget::clear() {
|
void CreditsWidget::clear() {
|
||||||
display.fill_rectangle(
|
display.fill_rectangle(
|
||||||
screen_rect(),
|
screen_rect(),
|
||||||
Color::black());
|
Theme::getInstance()->bg_darkest->background);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AboutView::update() {
|
void AboutView::update() {
|
||||||
@@ -109,7 +109,7 @@ void AboutView::update() {
|
|||||||
const size_t start = (glyph.size().width() / 8) * render_line;
|
const size_t start = (glyph.size().width() / 8) * render_line;
|
||||||
for (Dim c = 0; c < glyph.size().width(); c++) {
|
for (Dim c = 0; c < glyph.size().width(); c++) {
|
||||||
const auto pixel = glyph.pixels()[start + (c >> 3)] & (1U << (c & 0x7));
|
const auto pixel = glyph.pixels()[start + (c >> 3)] & (1U << (c & 0x7));
|
||||||
pixel_row[start_pos + i + c] = pixel ? Color::white() : Color::black();
|
pixel_row[start_pos + i + c] = pixel ? Theme::getInstance()->bg_darkest->foreground : Theme::getInstance()->bg_darkest->background;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto advance = glyph.advance();
|
const auto advance = glyph.advance();
|
||||||
|
|||||||
@@ -1,83 +1,88 @@
|
|||||||
#include "ui_about_simple.hpp"
|
#include "ui_about_simple.hpp"
|
||||||
|
|
||||||
namespace ui {
|
namespace ui {
|
||||||
AboutView::AboutView(NavigationView& nav) {
|
|
||||||
add_children({&console, &button_ok});
|
// TODO: Generate this automatically from github
|
||||||
|
// Information: a line starting with a '#' will be yellow coloured
|
||||||
button_ok.on_select = [&nav](Button&) {
|
const std::string authors_list[] = {
|
||||||
nav.pop();
|
"# * List of contributors * ",
|
||||||
};
|
" ",
|
||||||
|
"#Mayhem:",
|
||||||
console.writeln(STR_COLOR_LIGHT_GREY "List of contributors:");
|
"eried,euquiq,gregoryfenton",
|
||||||
console.writeln("");
|
"johnelder,jwetzell,nnemanjan00",
|
||||||
}
|
"N0vaPixel,klockee,gullradriel",
|
||||||
|
"jamesshao8,ITAxReal,rascafr",
|
||||||
void AboutView::update() {
|
"mcules,dqs105,strijar",
|
||||||
if (++timer > 200) {
|
"zhang00963,RedFox-Fr,aldude999",
|
||||||
timer = 0;
|
"East2West,fossum,ArjanOnwezen",
|
||||||
|
"vXxOinvizioNxX,teixeluis",
|
||||||
switch (++frame) {
|
"Brumi-2021,texasyojimbo",
|
||||||
case 1:
|
"heurist1,intoxsick,ckuethe",
|
||||||
// TODO: Generate this automatically from github
|
"notpike,jLynx,zigad",
|
||||||
// https://github.com/eried/portapack-mayhem/graphs/contributors?to=2022-01-01&from=2020-04-12&type=c
|
"MichalLeonBorsuk,jimilinuxguy",
|
||||||
console.writeln(STR_COLOR_DARK_YELLOW "Mayhem:");
|
"kallanreed,bernd-herzog",
|
||||||
console.writeln("eried,euquiq,gregoryfenton");
|
"NotherNgineer,zxkmm,u-foka",
|
||||||
console.writeln("johnelder,jwetzell,nnemanjan00");
|
"Netro,HTotoo",
|
||||||
console.writeln("N0vaPixel,klockee,GullCode");
|
" ",
|
||||||
console.writeln("jamesshao8,ITAxReal,rascafr");
|
"#Havoc:",
|
||||||
console.writeln("mcules,dqs105,strijar");
|
"furrtek,mrmookie,NotPike",
|
||||||
console.writeln("zhang00963,RedFox-Fr,aldude999");
|
"mjwaxios,ImDroided,Giorgiofox",
|
||||||
console.writeln("East2West,fossum,ArjanOnwezen");
|
"F4GEV,z4ziggy,xmycroftx",
|
||||||
console.writeln("vXxOinvizioNxX,teixeluis");
|
"troussos,silascutler",
|
||||||
console.writeln("Brumi-2021,texasyojimbo");
|
"nickbouwhuis,msoose,leres",
|
||||||
console.writeln("heurist1,intoxsick,ckuethe");
|
"joakar,dhoetger,clem-42",
|
||||||
console.writeln("notpike,jLynx,zigad");
|
"brianlechthaler,ZeroChaos-...",
|
||||||
console.writeln("MichalLeonBorsuk,jimilinuxguy");
|
" ",
|
||||||
console.writeln("kallanreed,bernd-herzog");
|
"#PortaPack:",
|
||||||
break;
|
"jboone,argilo",
|
||||||
case 2:
|
" ",
|
||||||
console.writeln("NotherNgineer,zxkmm,u-foka");
|
"#HackRF:",
|
||||||
console.writeln("Netro,HTotoo");
|
"mossmann,dominicgs,bvernoux",
|
||||||
console.writeln("");
|
"bgamari,schneider42,miek",
|
||||||
break;
|
"willcode,hessu,Sec42",
|
||||||
|
"yhetti,ckuethe,smunaut",
|
||||||
case 3:
|
"wishi,mrbubble62,scateu..."};
|
||||||
// https://github.com/eried/portapack-mayhem/graphs/contributors?to=2020-04-12&from=2015-07-31&type=c
|
|
||||||
console.writeln(STR_COLOR_DARK_YELLOW "Havoc:");
|
AboutView::AboutView(NavigationView& nav) {
|
||||||
console.writeln("furrtek,mrmookie,NotPike");
|
add_children({&menu_view,
|
||||||
console.writeln("mjwaxios,ImDroided,Giorgiofox");
|
&button_ok});
|
||||||
console.writeln("F4GEV,z4ziggy,xmycroftx");
|
|
||||||
console.writeln("troussos,silascutler");
|
button_ok.on_select = [&nav](Button&) {
|
||||||
console.writeln("nickbouwhuis,msoose,leres");
|
nav.pop();
|
||||||
console.writeln("joakar,dhoetger,clem-42");
|
};
|
||||||
console.writeln("brianlechthaler,ZeroChaos-...");
|
|
||||||
console.writeln("");
|
menu_view.on_left = [this]() {
|
||||||
break;
|
button_ok.focus();
|
||||||
|
};
|
||||||
case 4:
|
|
||||||
// https://github.com/eried/portapack-mayhem/graphs/contributors?from=2014-07-05&to=2015-07-31&type=c
|
menu_view.on_right = [this]() {
|
||||||
console.writeln(STR_COLOR_DARK_YELLOW "PortaPack:");
|
button_ok.focus();
|
||||||
console.writeln("jboone,argilo");
|
};
|
||||||
console.writeln("");
|
|
||||||
break;
|
for (const std::string& authors_line : authors_list) {
|
||||||
|
// if it's starting with #, it's a title and we have to substract the '#' and paint yellow
|
||||||
case 5:
|
if (authors_line.size() > 0) {
|
||||||
// https://github.com/mossmann/hackrf/graphs/contributors
|
if (authors_line[0] == '#') {
|
||||||
console.writeln(STR_COLOR_DARK_YELLOW "HackRF:");
|
menu_view.add_item(
|
||||||
console.writeln("mossmann,dominicgs,bvernoux");
|
{authors_line.substr(1, authors_line.size() - 1),
|
||||||
console.writeln("bgamari,schneider42,miek");
|
ui::Theme::getInstance()->fg_yellow->foreground,
|
||||||
console.writeln("willcode,hessu,Sec42");
|
nullptr,
|
||||||
console.writeln("yhetti,ckuethe,smunaut");
|
nullptr});
|
||||||
console.writeln("wishi,mrbubble62,scateu...");
|
} else {
|
||||||
console.writeln("");
|
menu_view.add_item(
|
||||||
frame = 0; // Loop
|
{authors_line,
|
||||||
break;
|
Theme::getInstance()->bg_darkest->foreground,
|
||||||
}
|
nullptr,
|
||||||
}
|
nullptr});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
void AboutView::focus() {
|
}
|
||||||
button_ok.focus();
|
}
|
||||||
}
|
|
||||||
|
void AboutView::focus() {
|
||||||
} /* namespace ui */
|
menu_view.focus();
|
||||||
|
// put focus on last text line to make it more obvious that list is scrollable
|
||||||
|
menu_view.set_highlighted(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
} /* namespace ui */
|
||||||
|
|||||||
@@ -12,25 +12,16 @@ class AboutView : public View {
|
|||||||
AboutView(NavigationView& nav);
|
AboutView(NavigationView& nav);
|
||||||
void focus() override;
|
void focus() override;
|
||||||
std::string title() const override { return "About"; };
|
std::string title() const override { return "About"; };
|
||||||
int32_t timer{180};
|
|
||||||
short frame{0};
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void update();
|
MenuView menu_view{
|
||||||
|
{0, 0, 240, 264},
|
||||||
Console console{
|
true};
|
||||||
{0, 10, 240, 240}};
|
|
||||||
|
|
||||||
Button button_ok{
|
Button button_ok{
|
||||||
{240 / 3, 270, 240 / 3, 24},
|
{240 / 3, 270, 240 / 3, 24},
|
||||||
"OK",
|
"OK",
|
||||||
};
|
};
|
||||||
|
|
||||||
MessageHandlerRegistration message_handler_update{
|
|
||||||
Message::ID::DisplayFrameSync,
|
|
||||||
[this](const Message* const) {
|
|
||||||
this->update();
|
|
||||||
}};
|
|
||||||
};
|
};
|
||||||
} // namespace ui
|
} // namespace ui
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||||
* Copyright (C) 2017 Furrtek
|
* Copyright (C) 2017 Furrtek
|
||||||
* Copyright (C) 2023 Kyle Reed
|
* Copyright (C) 2023 Kyle Reed
|
||||||
|
* Copyright (C) 2024 Mark Thompson
|
||||||
*
|
*
|
||||||
* This file is part of PortaPack.
|
* This file is part of PortaPack.
|
||||||
*
|
*
|
||||||
@@ -30,9 +31,13 @@
|
|||||||
#include "portapack_persistent_memory.hpp"
|
#include "portapack_persistent_memory.hpp"
|
||||||
#include "rtc_time.hpp"
|
#include "rtc_time.hpp"
|
||||||
#include "string_format.hpp"
|
#include "string_format.hpp"
|
||||||
|
#include "file_path.hpp"
|
||||||
|
#include "audio.hpp"
|
||||||
|
|
||||||
using namespace portapack;
|
using namespace portapack;
|
||||||
|
|
||||||
|
namespace pmem = portapack::persistent_memory;
|
||||||
|
|
||||||
namespace ui {
|
namespace ui {
|
||||||
|
|
||||||
static std::string get_map_tag(const AircraftRecentEntry& entry) {
|
static std::string get_map_tag(const AircraftRecentEntry& entry) {
|
||||||
@@ -52,15 +57,15 @@ void RecentEntriesTable<AircraftRecentEntries>::draw(
|
|||||||
case ADSBAgeState::Invalid:
|
case ADSBAgeState::Invalid:
|
||||||
case ADSBAgeState::Current:
|
case ADSBAgeState::Current:
|
||||||
entry_string = "";
|
entry_string = "";
|
||||||
target_color = Color::green();
|
target_color = Theme::getInstance()->fg_green->foreground;
|
||||||
break;
|
break;
|
||||||
case ADSBAgeState::Recent:
|
case ADSBAgeState::Recent:
|
||||||
entry_string = STR_COLOR_LIGHT_GREY;
|
entry_string = STR_COLOR_LIGHT_GREY;
|
||||||
target_color = Color::light_grey();
|
target_color = Theme::getInstance()->fg_light->foreground;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
entry_string = STR_COLOR_DARK_GREY;
|
entry_string = STR_COLOR_DARK_GREY;
|
||||||
target_color = Color::grey();
|
target_color = Theme::getInstance()->fg_medium->foreground;
|
||||||
};
|
};
|
||||||
|
|
||||||
entry_string +=
|
entry_string +=
|
||||||
@@ -102,7 +107,8 @@ void ADSBLogger::log(const ADSBLogEntry& log_entry) {
|
|||||||
log_line += " Type:" + to_string_dec_uint(log_entry.vel_type) +
|
log_line += " Type:" + to_string_dec_uint(log_entry.vel_type) +
|
||||||
" Hdg:" + to_string_dec_uint(log_entry.vel.heading) +
|
" Hdg:" + to_string_dec_uint(log_entry.vel.heading) +
|
||||||
" Spd: " + to_string_dec_int(log_entry.vel.speed);
|
" Spd: " + to_string_dec_int(log_entry.vel.speed);
|
||||||
|
if (log_entry.sil != 0)
|
||||||
|
log_line += " Sil:" + to_string_dec_uint(log_entry.sil);
|
||||||
log_file.write_entry(log_line);
|
log_file.write_entry(log_line);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,8 +133,8 @@ ADSBRxAircraftDetailsView::ADSBRxAircraftDetailsView(
|
|||||||
text_icao_address.set(entry.icao_str);
|
text_icao_address.set(entry.icao_str);
|
||||||
|
|
||||||
// Try getting the aircraft information from icao24.db
|
// Try getting the aircraft information from icao24.db
|
||||||
std::database db{};
|
database db{};
|
||||||
std::database::AircraftDBRecord aircraft_record;
|
database::AircraftDBRecord aircraft_record;
|
||||||
auto return_code = db.retrieve_aircraft_record(&aircraft_record, entry.icao_str);
|
auto return_code = db.retrieve_aircraft_record(&aircraft_record, entry.icao_str);
|
||||||
switch (return_code) {
|
switch (return_code) {
|
||||||
case DATABASE_RECORD_FOUND:
|
case DATABASE_RECORD_FOUND:
|
||||||
@@ -199,6 +205,10 @@ ADSBRxAircraftDetailsView::ADSBRxAircraftDetailsView(
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case DATABASE_RECORD_NOT_FOUND:
|
||||||
|
// Defaults should be filled by the constructor
|
||||||
|
break;
|
||||||
|
|
||||||
case DATABASE_NOT_FOUND:
|
case DATABASE_NOT_FOUND:
|
||||||
text_manufacturer.set("No icao24.db file");
|
text_manufacturer.set("No icao24.db file");
|
||||||
break;
|
break;
|
||||||
@@ -233,24 +243,6 @@ ADSBRxDetailsView::ADSBRxDetailsView(
|
|||||||
&button_aircraft_details,
|
&button_aircraft_details,
|
||||||
&button_see_map});
|
&button_see_map});
|
||||||
|
|
||||||
// The following won't change for a given airborne aircraft.
|
|
||||||
// Try getting the airline's name from airlines.db.
|
|
||||||
// NB: Only works once callsign has been read and won't be updated.
|
|
||||||
std::database db;
|
|
||||||
std::database::AirlinesDBRecord airline_record;
|
|
||||||
std::string airline_code = entry_.callsign.substr(0, 3);
|
|
||||||
auto return_code = db.retrieve_airline_record(&airline_record, airline_code);
|
|
||||||
|
|
||||||
switch (return_code) {
|
|
||||||
case DATABASE_RECORD_FOUND:
|
|
||||||
text_airline.set(airline_record.airline);
|
|
||||||
text_country.set(airline_record.country);
|
|
||||||
break;
|
|
||||||
case DATABASE_NOT_FOUND:
|
|
||||||
text_airline.set("No airlines.db file");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
text_icao_address.set(entry_.icao_str);
|
text_icao_address.set(entry_.icao_str);
|
||||||
|
|
||||||
button_aircraft_details.on_select = [this, &nav](Button&) {
|
button_aircraft_details.on_select = [this, &nav](Button&) {
|
||||||
@@ -266,6 +258,7 @@ ADSBRxDetailsView::ADSBRxDetailsView(
|
|||||||
get_map_tag(entry_),
|
get_map_tag(entry_),
|
||||||
entry_.pos.altitude,
|
entry_.pos.altitude,
|
||||||
GeoPos::alt_unit::FEET,
|
GeoPos::alt_unit::FEET,
|
||||||
|
GeoPos::spd_unit::MPH,
|
||||||
entry_.pos.latitude,
|
entry_.pos.latitude,
|
||||||
entry_.pos.longitude,
|
entry_.pos.longitude,
|
||||||
entry_.velo.heading);
|
entry_.velo.heading);
|
||||||
@@ -290,7 +283,7 @@ void ADSBRxDetailsView::update(const AircraftRecentEntry& entry) {
|
|||||||
} else if (geomap_view_) {
|
} else if (geomap_view_) {
|
||||||
// Map is showing, update the current item.
|
// Map is showing, update the current item.
|
||||||
geomap_view_->update_tag(get_map_tag(entry_));
|
geomap_view_->update_tag(get_map_tag(entry_));
|
||||||
geomap_view_->update_position(entry.pos.latitude, entry.pos.longitude, entry.velo.heading, entry.pos.altitude);
|
geomap_view_->update_position(entry.pos.latitude, entry.pos.longitude, entry.velo.heading, entry.pos.altitude, entry.velo.speed);
|
||||||
} else {
|
} else {
|
||||||
// Details is showing, update details.
|
// Details is showing, update details.
|
||||||
refresh_ui();
|
refresh_ui();
|
||||||
@@ -317,7 +310,43 @@ bool ADSBRxDetailsView::add_map_marker(const AircraftRecentEntry& entry) {
|
|||||||
return markerStored == MARKER_STORED;
|
return markerStored == MARKER_STORED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ADSBRxDetailsView::on_gps(const GPSPosDataMessage* msg) {
|
||||||
|
if (!geomap_view_)
|
||||||
|
return;
|
||||||
|
geomap_view_->update_my_position(msg->lat, msg->lon, msg->altitude);
|
||||||
|
}
|
||||||
|
void ADSBRxDetailsView::on_orientation(const OrientationDataMessage* msg) {
|
||||||
|
if (!geomap_view_)
|
||||||
|
return;
|
||||||
|
geomap_view_->update_my_orientation(msg->angle);
|
||||||
|
}
|
||||||
|
|
||||||
void ADSBRxDetailsView::refresh_ui() {
|
void ADSBRxDetailsView::refresh_ui() {
|
||||||
|
// The following won't change for a given airborne aircraft.
|
||||||
|
// Try getting the airline's name from airlines.db.
|
||||||
|
if (!airline_checked && !entry_.callsign.empty()) {
|
||||||
|
airline_checked = true;
|
||||||
|
|
||||||
|
database db;
|
||||||
|
database::AirlinesDBRecord airline_record;
|
||||||
|
std::string airline_code = entry_.callsign.substr(0, 3);
|
||||||
|
auto return_code = db.retrieve_airline_record(&airline_record, airline_code);
|
||||||
|
|
||||||
|
switch (return_code) {
|
||||||
|
case DATABASE_RECORD_FOUND:
|
||||||
|
text_airline.set(airline_record.airline);
|
||||||
|
text_country.set(airline_record.country);
|
||||||
|
break;
|
||||||
|
case DATABASE_RECORD_NOT_FOUND:
|
||||||
|
// text_airline.set("-"); // It's what it is constructed with
|
||||||
|
// text_country.set("-"); // It's what it is constructed with
|
||||||
|
break;
|
||||||
|
case DATABASE_NOT_FOUND:
|
||||||
|
text_airline.set("No airlines.db file");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
auto age = entry_.age;
|
auto age = entry_.age;
|
||||||
if (age < 60)
|
if (age < 60)
|
||||||
text_last_seen.set(to_string_dec_uint(age) + " seconds ago");
|
text_last_seen.set(to_string_dec_uint(age) + " seconds ago");
|
||||||
@@ -326,11 +355,12 @@ void ADSBRxDetailsView::refresh_ui() {
|
|||||||
|
|
||||||
text_callsign.set(entry_.callsign);
|
text_callsign.set(entry_.callsign);
|
||||||
text_infos.set(entry_.info_string);
|
text_infos.set(entry_.info_string);
|
||||||
|
std::string str_sil = (entry_.sil > 0) ? " Sil:" + to_string_dec_uint(entry_.sil) : "";
|
||||||
if (entry_.velo.heading < 360 && entry_.velo.speed >= 0)
|
if (entry_.velo.heading < 360 && entry_.velo.speed >= 0)
|
||||||
text_info2.set("Hdg:" + to_string_dec_uint(entry_.velo.heading) +
|
text_info2.set("Hdg:" + to_string_dec_uint(entry_.velo.heading) +
|
||||||
" Spd:" + to_string_dec_int(entry_.velo.speed));
|
" Spd:" + to_string_dec_int(entry_.velo.speed) + str_sil);
|
||||||
else
|
else
|
||||||
text_info2.set("");
|
text_info2.set(str_sil);
|
||||||
|
|
||||||
text_frame_pos_even.set(to_string_hex_array(entry_.frame_pos_even.get_raw_data(), 14));
|
text_frame_pos_even.set(to_string_hex_array(entry_.frame_pos_even.get_raw_data(), 14));
|
||||||
text_frame_pos_odd.set(to_string_hex_array(entry_.frame_pos_odd.get_raw_data(), 14));
|
text_frame_pos_odd.set(to_string_hex_array(entry_.frame_pos_odd.get_raw_data(), 14));
|
||||||
@@ -348,7 +378,8 @@ ADSBRxView::ADSBRxView(NavigationView& nav) {
|
|||||||
&rssi,
|
&rssi,
|
||||||
&recent_entries_view,
|
&recent_entries_view,
|
||||||
&status_frame,
|
&status_frame,
|
||||||
&status_good_frame});
|
&status_good_frame,
|
||||||
|
&field_volume});
|
||||||
|
|
||||||
recent_entries_view.set_parent_rect({0, 16, 240, 272});
|
recent_entries_view.set_parent_rect({0, 16, 240, 272});
|
||||||
recent_entries_view.on_select = [this, &nav](const AircraftRecentEntry& entry) {
|
recent_entries_view.on_select = [this, &nav](const AircraftRecentEntry& entry) {
|
||||||
@@ -366,14 +397,20 @@ ADSBRxView::ADSBRxView(NavigationView& nav) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
logger = std::make_unique<ADSBLogger>();
|
logger = std::make_unique<ADSBLogger>();
|
||||||
logger->append(LOG_ROOT_DIR "/ADSB.TXT");
|
logger->append(logs_dir / u"ADSB.TXT");
|
||||||
|
|
||||||
receiver_model.enable();
|
receiver_model.enable();
|
||||||
baseband::set_adsb();
|
baseband::set_adsb();
|
||||||
|
|
||||||
|
if (pmem::beep_on_packets()) {
|
||||||
|
audio::set_rate(audio::Rate::Hz_24000);
|
||||||
|
audio::output::start();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ADSBRxView::~ADSBRxView() {
|
ADSBRxView::~ADSBRxView() {
|
||||||
rtc_time::signal_tick_second -= signal_token_tick_second;
|
rtc_time::signal_tick_second -= signal_token_tick_second;
|
||||||
|
audio::output::stop();
|
||||||
receiver_model.disable();
|
receiver_model.disable();
|
||||||
baseband::shutdown();
|
baseband::shutdown();
|
||||||
}
|
}
|
||||||
@@ -395,7 +432,7 @@ void ADSBRxView::on_frame(const ADSBFrameMessage* message) {
|
|||||||
status_good_frame.toggle();
|
status_good_frame.toggle();
|
||||||
|
|
||||||
rtc::RTC datetime;
|
rtc::RTC datetime;
|
||||||
rtcGetTime(&RTCD1, &datetime);
|
rtcGetTime(&RTCD1, &datetime); // Reading RTC directly to avoid DST transitions when calculating delta
|
||||||
frame.set_rx_timestamp(datetime.minute() * 60 + datetime.second());
|
frame.set_rx_timestamp(datetime.minute() * 60 + datetime.second());
|
||||||
|
|
||||||
// NB: Reference to update entry in-place.
|
// NB: Reference to update entry in-place.
|
||||||
@@ -436,7 +473,6 @@ void ADSBRxView::on_frame(const ADSBFrameMessage* message) {
|
|||||||
"Alt:" + to_string_dec_int(entry.pos.altitude) +
|
"Alt:" + to_string_dec_int(entry.pos.altitude) +
|
||||||
" Lat:" + to_string_decimal(entry.pos.latitude, 2) +
|
" Lat:" + to_string_decimal(entry.pos.latitude, 2) +
|
||||||
" Lon:" + to_string_decimal(entry.pos.longitude, 2);
|
" Lon:" + to_string_decimal(entry.pos.longitude, 2);
|
||||||
|
|
||||||
entry.set_info_string(std::move(str_info));
|
entry.set_info_string(std::move(str_info));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -444,10 +480,16 @@ void ADSBRxView::on_frame(const ADSBFrameMessage* message) {
|
|||||||
entry.set_frame_velo(frame);
|
entry.set_frame_velo(frame);
|
||||||
log_entry.vel = entry.velo;
|
log_entry.vel = entry.velo;
|
||||||
log_entry.vel_type = msg_sub;
|
log_entry.vel_type = msg_sub;
|
||||||
|
} else if (msg_type == AIRBORNE_OP_STATUS) { // for ver 1
|
||||||
|
entry.sil = frame.get_sil_value();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logger->log(log_entry);
|
logger->log(log_entry);
|
||||||
|
|
||||||
|
if (pmem::beep_on_packets()) {
|
||||||
|
baseband::request_audio_beep(1000, 24000, 60);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ADSBRxView::on_tick_second() {
|
void ADSBRxView::on_tick_second() {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||||
* Copyright (C) 2017 Furrtek
|
* Copyright (C) 2017 Furrtek
|
||||||
* Copyright (C) 2023 Kyle Reed
|
* Copyright (C) 2023 Kyle Reed
|
||||||
|
* Copyright (C) 2024 Mark Thompson
|
||||||
*
|
*
|
||||||
* This file is part of PortaPack.
|
* This file is part of PortaPack.
|
||||||
*
|
*
|
||||||
@@ -53,6 +54,8 @@ namespace ui {
|
|||||||
#define AIRBORNE_POS_GPS_L 20 // airborne position (lowest type id)
|
#define AIRBORNE_POS_GPS_L 20 // airborne position (lowest type id)
|
||||||
#define AIRBORNE_POS_GPS_H 22 // airborne position (highest type id)
|
#define AIRBORNE_POS_GPS_H 22 // airborne position (highest type id)
|
||||||
|
|
||||||
|
#define AIRBORNE_OP_STATUS 31 // Aircraft operation status
|
||||||
|
|
||||||
#define RESERVED_L 23 // reserved for other uses
|
#define RESERVED_L 23 // reserved for other uses
|
||||||
#define RESERVED_H 31 // reserved for other uses
|
#define RESERVED_H 31 // reserved for other uses
|
||||||
|
|
||||||
@@ -101,6 +104,8 @@ struct AircraftRecentEntry {
|
|||||||
std::string callsign{};
|
std::string callsign{};
|
||||||
std::string info_string{};
|
std::string info_string{};
|
||||||
|
|
||||||
|
uint8_t sil{0}; // Surveillance integrity level
|
||||||
|
|
||||||
AircraftRecentEntry(const uint32_t ICAO_address)
|
AircraftRecentEntry(const uint32_t ICAO_address)
|
||||||
: ICAO_address{ICAO_address} {
|
: ICAO_address{ICAO_address} {
|
||||||
this->icao_str = to_string_hex(ICAO_address, 6);
|
this->icao_str = to_string_hex(ICAO_address, 6);
|
||||||
@@ -172,6 +177,7 @@ struct ADSBLogEntry {
|
|||||||
adsb_pos pos{};
|
adsb_pos pos{};
|
||||||
adsb_vel vel{};
|
adsb_vel vel{};
|
||||||
uint8_t vel_type{};
|
uint8_t vel_type{};
|
||||||
|
uint8_t sil{};
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: Make logging optional.
|
// TODO: Make logging optional.
|
||||||
@@ -199,15 +205,15 @@ class ADSBRxAircraftDetailsView : public View {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
Labels labels{
|
Labels labels{
|
||||||
{{0 * 8, 1 * 16}, "ICAO:", Color::light_grey()},
|
{{0 * 8, 1 * 16}, "ICAO:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{0 * 8, 2 * 16}, "Registration:", Color::light_grey()},
|
{{0 * 8, 2 * 16}, "Registration:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{0 * 8, 3 * 16}, "Manufacturer:", Color::light_grey()},
|
{{0 * 8, 3 * 16}, "Manufacturer:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{0 * 8, 5 * 16}, "Model:", Color::light_grey()},
|
{{0 * 8, 5 * 16}, "Model:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{0 * 8, 7 * 16}, "Type:", Color::light_grey()},
|
{{0 * 8, 7 * 16}, "Type:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{0 * 8, 8 * 16}, "Number of engines:", Color::light_grey()},
|
{{0 * 8, 8 * 16}, "Number of engines:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{0 * 8, 9 * 16}, "Engine type:", Color::light_grey()},
|
{{0 * 8, 9 * 16}, "Engine type:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{0 * 8, 11 * 16}, "Owner:", Color::light_grey()},
|
{{0 * 8, 11 * 16}, "Owner:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{0 * 8, 13 * 16}, "Operator:", Color::light_grey()}};
|
{{0 * 8, 13 * 16}, "Operator:", Theme::getInstance()->fg_light->foreground}};
|
||||||
|
|
||||||
Text text_icao_address{
|
Text text_icao_address{
|
||||||
{5 * 8, 1 * 16, 6 * 8, 16},
|
{5 * 8, 1 * 16, 6 * 8, 16},
|
||||||
@@ -271,6 +277,8 @@ class ADSBRxDetailsView : public View {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
void refresh_ui();
|
void refresh_ui();
|
||||||
|
void on_gps(const GPSPosDataMessage* msg);
|
||||||
|
void on_orientation(const OrientationDataMessage* msg);
|
||||||
|
|
||||||
GeoMapView* geomap_view_{nullptr};
|
GeoMapView* geomap_view_{nullptr};
|
||||||
ADSBRxAircraftDetailsView* aircraft_details_view_{nullptr};
|
ADSBRxAircraftDetailsView* aircraft_details_view_{nullptr};
|
||||||
@@ -278,15 +286,16 @@ class ADSBRxDetailsView : public View {
|
|||||||
// NB: Keeping a copy so that it doesn't end up dangling
|
// NB: Keeping a copy so that it doesn't end up dangling
|
||||||
// if removed from the recent entries list.
|
// if removed from the recent entries list.
|
||||||
AircraftRecentEntry entry_{AircraftRecentEntry::invalid_key};
|
AircraftRecentEntry entry_{AircraftRecentEntry::invalid_key};
|
||||||
|
bool airline_checked{false};
|
||||||
|
|
||||||
Labels labels{
|
Labels labels{
|
||||||
{{0 * 8, 1 * 16}, "ICAO:", Color::light_grey()},
|
{{0 * 8, 1 * 16}, "ICAO:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{13 * 8, 1 * 16}, "Callsign:", Color::light_grey()},
|
{{13 * 8, 1 * 16}, "Callsign:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{0 * 8, 2 * 16}, "Last seen:", Color::light_grey()},
|
{{0 * 8, 2 * 16}, "Last seen:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{0 * 8, 3 * 16}, "Airline:", Color::light_grey()},
|
{{0 * 8, 3 * 16}, "Airline:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{0 * 8, 5 * 16}, "Country:", Color::light_grey()},
|
{{0 * 8, 5 * 16}, "Country:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{0 * 8, 13 * 16}, "Even position frame:", Color::light_grey()},
|
{{0 * 8, 13 * 16}, "Even position frame:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{0 * 8, 15 * 16}, "Odd position frame:", Color::light_grey()}};
|
{{0 * 8, 15 * 16}, "Odd position frame:", Theme::getInstance()->fg_light->foreground}};
|
||||||
|
|
||||||
Text text_icao_address{
|
Text text_icao_address{
|
||||||
{5 * 8, 1 * 16, 6 * 8, 16},
|
{5 * 8, 1 * 16, 6 * 8, 16},
|
||||||
@@ -330,6 +339,19 @@ class ADSBRxDetailsView : public View {
|
|||||||
Button button_see_map{
|
Button button_see_map{
|
||||||
{16 * 8, 9 * 16, 12 * 8, 3 * 16},
|
{16 * 8, 9 * 16, 12 * 8, 3 * 16},
|
||||||
"See on map"};
|
"See on map"};
|
||||||
|
|
||||||
|
MessageHandlerRegistration message_handler_gps{
|
||||||
|
Message::ID::GPSPosData,
|
||||||
|
[this](Message* const p) {
|
||||||
|
const auto message = static_cast<const GPSPosDataMessage*>(p);
|
||||||
|
this->on_gps(message);
|
||||||
|
}};
|
||||||
|
MessageHandlerRegistration message_handler_orientation{
|
||||||
|
Message::ID::OrientationData,
|
||||||
|
[this](Message* const p) {
|
||||||
|
const auto message = static_cast<const OrientationDataMessage*>(p);
|
||||||
|
this->on_orientation(message);
|
||||||
|
}};
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Main ADSB application view and message dispatch. */
|
/* Main ADSB application view and message dispatch. */
|
||||||
@@ -392,7 +414,7 @@ class ADSBRxView : public View {
|
|||||||
ADSBRxDetailsView* details_view{nullptr};
|
ADSBRxDetailsView* details_view{nullptr};
|
||||||
|
|
||||||
Labels labels{
|
Labels labels{
|
||||||
{{0 * 8, 0 * 8}, "LNA: VGA: AMP:", Color::light_grey()}};
|
{{0 * 8, 0 * 8}, "LNA: VGA: AMP:", Theme::getInstance()->fg_light->foreground}};
|
||||||
|
|
||||||
LNAGainField field_lna{
|
LNAGainField field_lna{
|
||||||
{4 * 8, 0 * 16}};
|
{4 * 8, 0 * 16}};
|
||||||
@@ -404,19 +426,22 @@ class ADSBRxView : public View {
|
|||||||
{18 * 8, 0 * 16}};
|
{18 * 8, 0 * 16}};
|
||||||
|
|
||||||
RSSI rssi{
|
RSSI rssi{
|
||||||
{20 * 8, 4, 10 * 7, 8},
|
{20 * 8, 4, 7 * 8, 8},
|
||||||
};
|
};
|
||||||
|
|
||||||
ActivityDot status_frame{
|
ActivityDot status_frame{
|
||||||
{screen_width - 3, 5, 2, 2},
|
{27 * 8 + 2, 5, 2, 2},
|
||||||
Color::white(),
|
Theme::getInstance()->bg_darkest->foreground,
|
||||||
};
|
};
|
||||||
|
|
||||||
ActivityDot status_good_frame{
|
ActivityDot status_good_frame{
|
||||||
{screen_width - 3, 9, 2, 2},
|
{27 * 8 + 2, 9, 2, 2},
|
||||||
Color::green(),
|
Theme::getInstance()->fg_green->foreground,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
AudioVolumeField field_volume{
|
||||||
|
{28 * 8, 0 * 16}};
|
||||||
|
|
||||||
MessageHandlerRegistration message_handler_frame{
|
MessageHandlerRegistration message_handler_frame{
|
||||||
Message::ID::ADSBFrame,
|
Message::ID::ADSBFrame,
|
||||||
[this](Message* const p) {
|
[this](Message* const p) {
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
#include "baseband_api.hpp"
|
#include "baseband_api.hpp"
|
||||||
#include "string_format.hpp"
|
#include "string_format.hpp"
|
||||||
#include "portapack_persistent_memory.hpp"
|
#include "portapack_persistent_memory.hpp"
|
||||||
|
#include "file_path.hpp"
|
||||||
|
|
||||||
using namespace portapack;
|
using namespace portapack;
|
||||||
|
|
||||||
@@ -45,7 +46,7 @@ void RecentEntriesTable<APRSRecentEntries>::draw(
|
|||||||
Color target_color;
|
Color target_color;
|
||||||
// auto entry_age = entry.age;
|
// auto entry_age = entry.age;
|
||||||
|
|
||||||
target_color = Color::green();
|
target_color = Theme::getInstance()->fg_green->foreground;
|
||||||
|
|
||||||
std::string entry_string = "";
|
std::string entry_string = "";
|
||||||
|
|
||||||
@@ -101,6 +102,8 @@ APRSRxView::APRSRxView(NavigationView& nav, Rect parent_rect)
|
|||||||
field_frequency.set_value(145175000);
|
field_frequency.set_value(145175000);
|
||||||
} else if (i == 3) {
|
} else if (i == 3) {
|
||||||
field_frequency.set_value(144575000);
|
field_frequency.set_value(144575000);
|
||||||
|
} else if (i == 4) {
|
||||||
|
field_frequency.set_value(145825000);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -109,7 +112,7 @@ APRSRxView::APRSRxView(NavigationView& nav, Rect parent_rect)
|
|||||||
|
|
||||||
logger = std::make_unique<APRSLogger>();
|
logger = std::make_unique<APRSLogger>();
|
||||||
if (logger)
|
if (logger)
|
||||||
logger->append(LOG_ROOT_DIR "/APRS.TXT");
|
logger->append(logs_dir / u"APRS.TXT");
|
||||||
|
|
||||||
baseband::set_aprs(1200);
|
baseband::set_aprs(1200);
|
||||||
|
|
||||||
@@ -119,6 +122,10 @@ APRSRxView::APRSRxView(NavigationView& nav, Rect parent_rect)
|
|||||||
receiver_model.enable();
|
receiver_model.enable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void APRSRxView::on_freqchg(int64_t freq) {
|
||||||
|
field_frequency.set_value(freq);
|
||||||
|
}
|
||||||
|
|
||||||
void APRSRxView::on_packet(const APRSPacketMessage* message) {
|
void APRSRxView::on_packet(const APRSPacketMessage* message) {
|
||||||
std::string str_console = "\x1B";
|
std::string str_console = "\x1B";
|
||||||
|
|
||||||
@@ -258,7 +265,7 @@ void APRSTableView::on_pkt(const APRSPacketMessage* message) {
|
|||||||
std::string source_formatted = packet.get_source_formatted();
|
std::string source_formatted = packet.get_source_formatted();
|
||||||
std::string info_string = packet.get_stream_text();
|
std::string info_string = packet.get_stream_text();
|
||||||
|
|
||||||
rtcGetTime(&RTCD1, &datetime);
|
rtc_time::now(datetime);
|
||||||
auto& entry = ::on_packet(recent, packet.get_source());
|
auto& entry = ::on_packet(recent, packet.get_source());
|
||||||
entry.reset_age();
|
entry.reset_age();
|
||||||
entry.inc_hit();
|
entry.inc_hit();
|
||||||
@@ -317,7 +324,7 @@ void APRSDetailsView::update() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (send_updates)
|
if (send_updates)
|
||||||
geomap_view->update_position(entry_copy.pos.latitude, entry_copy.pos.longitude, 0, 0);
|
geomap_view->update_position(entry_copy.pos.latitude, entry_copy.pos.longitude, 0, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
APRSDetailsView::~APRSDetailsView() {
|
APRSDetailsView::~APRSDetailsView() {
|
||||||
@@ -339,6 +346,7 @@ APRSDetailsView::APRSDetailsView(
|
|||||||
entry_copy.source_formatted,
|
entry_copy.source_formatted,
|
||||||
0, // entry_copy.pos.altitude,
|
0, // entry_copy.pos.altitude,
|
||||||
GeoPos::alt_unit::FEET,
|
GeoPos::alt_unit::FEET,
|
||||||
|
GeoPos::spd_unit::HIDDEN,
|
||||||
entry_copy.pos.latitude,
|
entry_copy.pos.latitude,
|
||||||
entry_copy.pos.longitude,
|
entry_copy.pos.longitude,
|
||||||
0, /*entry_copy.velo.heading,*/
|
0, /*entry_copy.velo.heading,*/
|
||||||
|
|||||||
@@ -36,10 +36,11 @@
|
|||||||
|
|
||||||
#include "log_file.hpp"
|
#include "log_file.hpp"
|
||||||
#include "utility.hpp"
|
#include "utility.hpp"
|
||||||
|
#include "file_path.hpp"
|
||||||
|
|
||||||
class APRSLogger {
|
class APRSLogger {
|
||||||
public:
|
public:
|
||||||
Optional<File::Error> append(const std::string& filename) {
|
Optional<File::Error> append(const std::filesystem::path& filename) {
|
||||||
return log_file.append(filename);
|
return log_file.append(filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,6 +185,7 @@ class APRSRxView : public View {
|
|||||||
|
|
||||||
std::string title() const override { return "APRS RX"; };
|
std::string title() const override { return "APRS RX"; };
|
||||||
void on_packet(const APRSPacketMessage* message);
|
void on_packet(const APRSPacketMessage* message);
|
||||||
|
void on_freqchg(int64_t freq);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void on_data(uint32_t value, bool is_data);
|
void on_data(uint32_t value, bool is_data);
|
||||||
@@ -221,7 +223,8 @@ class APRSRxView : public View {
|
|||||||
{{"NA ", 0},
|
{{"NA ", 0},
|
||||||
{"EUR", 1},
|
{"EUR", 1},
|
||||||
{"AUS", 2},
|
{"AUS", 2},
|
||||||
{"NZ ", 3}}};
|
{"NZ ", 3},
|
||||||
|
{"ISS", 4}}};
|
||||||
|
|
||||||
RxFrequencyField field_frequency{
|
RxFrequencyField field_frequency{
|
||||||
{3 * 8, 0 * 16},
|
{3 * 8, 0 * 16},
|
||||||
@@ -231,7 +234,7 @@ class APRSRxView : public View {
|
|||||||
RecordView record_view{
|
RecordView record_view{
|
||||||
{0 * 8, 1 * 16, 30 * 8, 1 * 16},
|
{0 * 8, 1 * 16, 30 * 8, 1 * 16},
|
||||||
u"AFS_????.WAV",
|
u"AFS_????.WAV",
|
||||||
u"APRS",
|
aprs_dir,
|
||||||
RecordView::FileType::WAV,
|
RecordView::FileType::WAV,
|
||||||
4096,
|
4096,
|
||||||
4};
|
4};
|
||||||
@@ -259,8 +262,8 @@ class APRSRXView : public View {
|
|||||||
APRSTableView view_table{nav_, view_rect};
|
APRSTableView view_table{nav_, view_rect};
|
||||||
|
|
||||||
TabView tab_view{
|
TabView tab_view{
|
||||||
{"Stream", Color::cyan(), &view_stream},
|
{"Stream", Theme::getInstance()->fg_cyan->foreground, &view_stream},
|
||||||
{"List", Color::yellow(), &view_table}};
|
{"List", Theme::getInstance()->fg_yellow->foreground, &view_table}};
|
||||||
|
|
||||||
MessageHandlerRegistration message_handler_packet{
|
MessageHandlerRegistration message_handler_packet{
|
||||||
Message::ID::APRSPacket,
|
Message::ID::APRSPacket,
|
||||||
@@ -269,6 +272,13 @@ class APRSRXView : public View {
|
|||||||
this->view_stream.on_packet(message);
|
this->view_stream.on_packet(message);
|
||||||
this->view_table.on_pkt(message);
|
this->view_table.on_pkt(message);
|
||||||
}};
|
}};
|
||||||
|
|
||||||
|
MessageHandlerRegistration message_handler_freqchg{
|
||||||
|
Message::ID::FreqChangeCommand,
|
||||||
|
[this](Message* const p) {
|
||||||
|
const auto message = static_cast<const FreqChangeCommandMessage*>(p);
|
||||||
|
this->view_stream.on_freqchg(message->freq);
|
||||||
|
}};
|
||||||
};
|
};
|
||||||
|
|
||||||
} /* namespace ui */
|
} /* namespace ui */
|
||||||
|
|||||||
@@ -61,9 +61,9 @@ class APRSTXView : public View {
|
|||||||
void on_tx_progress(const uint32_t progress, const bool done);
|
void on_tx_progress(const uint32_t progress, const bool done);
|
||||||
|
|
||||||
Labels labels{
|
Labels labels{
|
||||||
{{0 * 8, 1 * 16}, "Source: SSID:", Color::light_grey()}, // 6 alphanum + SSID
|
{{0 * 8, 1 * 16}, "Source: SSID:", Theme::getInstance()->fg_light->foreground}, // 6 alphanum + SSID
|
||||||
{{0 * 8, 2 * 16}, " Dest.: SSID:", Color::light_grey()},
|
{{0 * 8, 2 * 16}, " Dest.: SSID:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{0 * 8, 4 * 16}, "Info field:", Color::light_grey()},
|
{{0 * 8, 4 * 16}, "Info field:", Theme::getInstance()->fg_light->foreground},
|
||||||
};
|
};
|
||||||
|
|
||||||
SymField sym_source{
|
SymField sym_source{
|
||||||
|
|||||||
@@ -0,0 +1,188 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||||
|
* Copyright (C) 2016 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_battinfo.hpp"
|
||||||
|
|
||||||
|
#include "event_m0.hpp"
|
||||||
|
#include "portapack.hpp"
|
||||||
|
#include "battery.hpp"
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
using namespace portapack;
|
||||||
|
|
||||||
|
namespace ui {
|
||||||
|
|
||||||
|
void BattinfoView::focus() {
|
||||||
|
button_exit.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
// called each 1/60th of second, so 6 = 100ms
|
||||||
|
void BattinfoView::on_timer() {
|
||||||
|
if (++timer_counter == timer_period) {
|
||||||
|
timer_counter = 0;
|
||||||
|
update_result();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BattinfoView::update_result() {
|
||||||
|
if (!battery::BatteryManagement::isDetected()) {
|
||||||
|
text_percent.set("UNKNOWN");
|
||||||
|
text_voltage.set("UNKNOWN");
|
||||||
|
text_current.set("-");
|
||||||
|
text_charge.set("-");
|
||||||
|
text_cycles.set("-");
|
||||||
|
text_ttef.set("-");
|
||||||
|
text_method.set("-");
|
||||||
|
text_warn.set("");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
bool uichg = false;
|
||||||
|
uint8_t valid_mask = 0;
|
||||||
|
battery::BatteryManagement::getBatteryInfo(valid_mask, percent, voltage, current);
|
||||||
|
// 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) + " %");
|
||||||
|
else
|
||||||
|
text_percent.set("UNKNOWN");
|
||||||
|
if (voltage > 1 && (valid_mask & battery::BatteryManagement::BATT_VALID_VOLTAGE) == battery::BatteryManagement::BATT_VALID_VOLTAGE) {
|
||||||
|
text_voltage.set(to_string_decimal(voltage / 1000.0, 3) + " V");
|
||||||
|
} else {
|
||||||
|
text_voltage.set("UNKNOWN");
|
||||||
|
}
|
||||||
|
if ((valid_mask & battery::BatteryManagement::BATT_VALID_CURRENT) == battery::BatteryManagement::BATT_VALID_CURRENT) {
|
||||||
|
if (labels_opt.hidden()) uichg = true;
|
||||||
|
labels_opt.hidden(false);
|
||||||
|
text_current.hidden(false);
|
||||||
|
text_charge.hidden(false);
|
||||||
|
text_current.set(to_string_dec_int(current) + " mA");
|
||||||
|
text_charge.set(current >= 0 ? "Charging" : "Discharging");
|
||||||
|
labels_opt.hidden(false);
|
||||||
|
|
||||||
|
text_ttef.hidden(false);
|
||||||
|
} else {
|
||||||
|
if (!labels_opt.hidden()) uichg = true;
|
||||||
|
labels_opt.hidden(true);
|
||||||
|
text_current.hidden(true);
|
||||||
|
text_charge.hidden(true);
|
||||||
|
text_cycles.hidden(true);
|
||||||
|
text_ttef.hidden(true);
|
||||||
|
text_warn.set("");
|
||||||
|
}
|
||||||
|
if ((valid_mask & battery::BatteryManagement::BATT_VALID_CYCLES) == battery::BatteryManagement::BATT_VALID_CYCLES) {
|
||||||
|
text_cycles.hidden(false);
|
||||||
|
uint16_t cycles = battery::BatteryManagement::get_cycles();
|
||||||
|
if (cycles < 2)
|
||||||
|
text_warn.set("SoC improves after 2 cycles");
|
||||||
|
else
|
||||||
|
text_warn.set("");
|
||||||
|
text_cycles.set(to_string_dec_uint(cycles));
|
||||||
|
} else {
|
||||||
|
text_cycles.hidden(true);
|
||||||
|
text_warn.set("");
|
||||||
|
}
|
||||||
|
if ((valid_mask & battery::BatteryManagement::BATT_VALID_TTEF) == battery::BatteryManagement::BATT_VALID_TTEF) {
|
||||||
|
text_ttef.hidden(false);
|
||||||
|
float ttef = 0;
|
||||||
|
if (current <= 0) {
|
||||||
|
ttef = battery::BatteryManagement::get_tte();
|
||||||
|
} else {
|
||||||
|
ttef = battery::BatteryManagement::get_ttf();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert ttef to hours and minutes
|
||||||
|
uint8_t hours = static_cast<uint8_t>(ttef);
|
||||||
|
uint8_t minutes = static_cast<uint8_t>((ttef - hours) * 60 + 0.5); // +0.5 for rounding
|
||||||
|
|
||||||
|
// Create the formatted string
|
||||||
|
std::string formatted_time;
|
||||||
|
if (hours > 0) {
|
||||||
|
formatted_time += to_string_dec_uint(hours) + "h ";
|
||||||
|
}
|
||||||
|
formatted_time += to_string_dec_uint(minutes) + "m";
|
||||||
|
|
||||||
|
text_ttef.set(formatted_time);
|
||||||
|
} else {
|
||||||
|
text_ttef.hidden(true);
|
||||||
|
}
|
||||||
|
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 (uichg) set_dirty();
|
||||||
|
// to update status bar too, send message in behalf of batt manager
|
||||||
|
BatteryStateMessage msg{valid_mask, percent, current >= 0, voltage};
|
||||||
|
EventDispatcher::send_message(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
BattinfoView::BattinfoView(NavigationView& nav)
|
||||||
|
: nav_{nav} {
|
||||||
|
add_children({&labels,
|
||||||
|
&labels_opt,
|
||||||
|
&text_percent,
|
||||||
|
&text_voltage,
|
||||||
|
&text_current,
|
||||||
|
&text_charge,
|
||||||
|
&text_method,
|
||||||
|
&button_mode,
|
||||||
|
&button_exit,
|
||||||
|
&text_cycles,
|
||||||
|
&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");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
update_result();
|
||||||
|
if (thread == nullptr) thread = chThdCreateFromHeap(NULL, 1024, NORMALPRIO + 10, BattinfoView::static_fn, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
msg_t BattinfoView::static_fn(void* arg) {
|
||||||
|
auto obj = static_cast<BattinfoView*>(arg);
|
||||||
|
while (!chThdShouldTerminate()) {
|
||||||
|
chThdSleepMilliseconds(16);
|
||||||
|
obj->on_timer();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
BattinfoView::~BattinfoView() {
|
||||||
|
if (thread) {
|
||||||
|
chThdTerminate(thread);
|
||||||
|
chThdWait(thread);
|
||||||
|
thread = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace ui
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||||
|
* Copyright (C) 2016 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __UI_BATTINFO_H__
|
||||||
|
#define __UI_BATTINFO_H__
|
||||||
|
|
||||||
|
#include "ui.hpp"
|
||||||
|
#include "ui_widget.hpp"
|
||||||
|
#include "ui_navigation.hpp"
|
||||||
|
#include "string_format.hpp"
|
||||||
|
|
||||||
|
namespace ui {
|
||||||
|
class BattinfoView : public View {
|
||||||
|
public:
|
||||||
|
~BattinfoView();
|
||||||
|
BattinfoView(NavigationView& nav);
|
||||||
|
BattinfoView(const BattinfoView&) = delete;
|
||||||
|
BattinfoView(BattinfoView&&) = delete;
|
||||||
|
BattinfoView& operator=(const BattinfoView&) = delete;
|
||||||
|
BattinfoView& operator=(BattinfoView&&) = delete;
|
||||||
|
|
||||||
|
void focus() override;
|
||||||
|
std::string title() const override { return "Battery"; };
|
||||||
|
|
||||||
|
private:
|
||||||
|
void update_result();
|
||||||
|
void on_timer();
|
||||||
|
NavigationView& nav_;
|
||||||
|
uint16_t timer_period = 60;
|
||||||
|
uint16_t timer_counter = 0;
|
||||||
|
uint8_t percent = 0;
|
||||||
|
uint16_t voltage = 0;
|
||||||
|
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},
|
||||||
|
};
|
||||||
|
|
||||||
|
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},
|
||||||
|
};
|
||||||
|
|
||||||
|
Text text_percent{
|
||||||
|
{13 * 8, 1 * 16, 10 * 16, 16},
|
||||||
|
"-"};
|
||||||
|
Text text_voltage{
|
||||||
|
{13 * 8, 2 * 16, 10 * 16, 16},
|
||||||
|
"-"};
|
||||||
|
Text text_method{
|
||||||
|
{13 * 8, 3 * 16, 10 * 16, 16},
|
||||||
|
"-"};
|
||||||
|
Text text_current{
|
||||||
|
{13 * 8, 4 * 16, 10 * 16, 16},
|
||||||
|
"-"};
|
||||||
|
Text text_charge{
|
||||||
|
{13 * 8, 5 * 16, 10 * 16, 16},
|
||||||
|
"-"};
|
||||||
|
Text text_ttef{
|
||||||
|
{13 * 8, 6 * 16, 10 * 16, 16},
|
||||||
|
"-"};
|
||||||
|
Text text_cycles{
|
||||||
|
{13 * 8, 7 * 16, 10 * 16, 16},
|
||||||
|
"-"};
|
||||||
|
|
||||||
|
Text text_warn{
|
||||||
|
{2 * 8, 8 * 16, 30 * 8, 2 * 16},
|
||||||
|
""};
|
||||||
|
|
||||||
|
Button button_mode{
|
||||||
|
{2 * 8, 11 * 16 + 5, 5 * 16, 32},
|
||||||
|
"Volt"};
|
||||||
|
|
||||||
|
Button button_exit{
|
||||||
|
{72, 17 * 16, 96, 32},
|
||||||
|
"Back"};
|
||||||
|
static msg_t static_fn(void* arg);
|
||||||
|
Thread* thread{nullptr};
|
||||||
|
};
|
||||||
|
|
||||||
|
} /* namespace ui */
|
||||||
|
|
||||||
|
#endif /*__UI_BATTINFO__*/
|
||||||
@@ -50,12 +50,12 @@ class XylosView : public View {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
Labels labels{
|
Labels labels{
|
||||||
{{8 * 8, 1 * 8}, "Header:", Color::light_grey()},
|
{{8 * 8, 1 * 8}, "Header:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{4 * 8, 3 * 8}, "City code:", Color::light_grey()},
|
{{4 * 8, 3 * 8}, "City code:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{7 * 8, 5 * 8}, "Family:", Color::light_grey()},
|
{{7 * 8, 5 * 8}, "Family:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{2 * 8, 7 * 8 + 2}, "Subfamily:", Color::light_grey()},
|
{{2 * 8, 7 * 8 + 2}, "Subfamily:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{2 * 8, 11 * 8}, "Receiver ID:", Color::light_grey()},
|
{{2 * 8, 11 * 8}, "Receiver ID:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{2 * 8, 14 * 8}, "Relay:", Color::light_grey()}};
|
{{2 * 8, 14 * 8}, "Relay:", Theme::getInstance()->fg_light->foreground}};
|
||||||
|
|
||||||
NumberField field_header_a{
|
NumberField field_header_a{
|
||||||
{16 * 8, 1 * 8},
|
{16 * 8, 1 * 8},
|
||||||
@@ -130,9 +130,9 @@ class EPARView : public View {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
Labels labels{
|
Labels labels{
|
||||||
{{4 * 8, 1 * 8}, "City code:", Color::light_grey()},
|
{{4 * 8, 1 * 8}, "City code:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{8 * 8, 3 * 8}, "Group:", Color::light_grey()},
|
{{8 * 8, 3 * 8}, "Group:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{8 * 8, 7 * 8}, "Relay:", Color::light_grey()}};
|
{{8 * 8, 7 * 8}, "Relay:", Theme::getInstance()->fg_light->foreground}};
|
||||||
|
|
||||||
NumberField field_city{
|
NumberField field_city{
|
||||||
{16 * 8, 1 * 8},
|
{16 * 8, 1 * 8},
|
||||||
@@ -195,11 +195,11 @@ class BHTView : public View {
|
|||||||
EPARView view_EPAR{view_rect};
|
EPARView view_EPAR{view_rect};
|
||||||
|
|
||||||
TabView tab_view{
|
TabView tab_view{
|
||||||
{"Xylos", Color::cyan(), &view_xylos},
|
{"Xylos", Theme::getInstance()->fg_cyan->foreground, &view_xylos},
|
||||||
{"EPAR", Color::green(), &view_EPAR}};
|
{"EPAR", Theme::getInstance()->fg_green->foreground, &view_EPAR}};
|
||||||
|
|
||||||
Labels labels{
|
Labels labels{
|
||||||
{{29 * 8, 14 * 16 + 4}, "s", Color::light_grey()}};
|
{{29 * 8, 14 * 16 + 4}, "s", Theme::getInstance()->fg_light->foreground}};
|
||||||
|
|
||||||
Checkbox checkbox_scan{
|
Checkbox checkbox_scan{
|
||||||
{1 * 8, 25 * 8},
|
{1 * 8, 25 * 8},
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2024 HTotoo
|
||||||
|
*
|
||||||
|
* 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_bmp_file_viewer.hpp"
|
||||||
|
|
||||||
|
extern ui::SystemView* system_view_ptr;
|
||||||
|
|
||||||
|
using namespace portapack;
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
|
namespace ui {
|
||||||
|
|
||||||
|
BMPFileViewer::BMPFileViewer(
|
||||||
|
NavigationView& nav,
|
||||||
|
const std::filesystem::path& path)
|
||||||
|
: nav_{nav},
|
||||||
|
path_{path} {
|
||||||
|
add_children(
|
||||||
|
{&bmp});
|
||||||
|
bmp.set_enter_pass(true); // pass the enter key to me, so i can exit. this will disable zoom + pos reset
|
||||||
|
set_focusable(true);
|
||||||
|
system_view_ptr->set_app_fullscreen(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
BMPFileViewer::~BMPFileViewer() {
|
||||||
|
system_view_ptr->set_app_fullscreen(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BMPFileViewer::focus() {
|
||||||
|
bmp.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BMPFileViewer::on_key(KeyEvent k) {
|
||||||
|
if (k == KeyEvent::Select) {
|
||||||
|
nav_.pop();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BMPFileViewer::paint(Painter&) {
|
||||||
|
bmp.load_bmp(path_);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ui
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2024 HTotoo
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __UI_BMP_FILE_VIEWER_H__
|
||||||
|
#define __UI_BMP_FILE_VIEWER_H__
|
||||||
|
|
||||||
|
#include "ui.hpp"
|
||||||
|
#include "ui_navigation.hpp"
|
||||||
|
#include "ui_painter.hpp"
|
||||||
|
#include "ui_widget.hpp"
|
||||||
|
#include "file.hpp"
|
||||||
|
#include "ui_bmpview.hpp"
|
||||||
|
|
||||||
|
namespace ui {
|
||||||
|
|
||||||
|
class BMPFileViewer : public View {
|
||||||
|
public:
|
||||||
|
BMPFileViewer(NavigationView& nav, const std::filesystem::path& path);
|
||||||
|
~BMPFileViewer();
|
||||||
|
bool on_key(KeyEvent key) override;
|
||||||
|
void paint(Painter& painter) override;
|
||||||
|
void focus() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
NavigationView& nav_;
|
||||||
|
std::filesystem::path path_{};
|
||||||
|
BMPViewer bmp{{0, 0, 240, 320}};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ui
|
||||||
|
|
||||||
|
#endif // __UI_BMP_FILE_VIEWER_H__
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||||
|
* Copyright (C) 2024 Mark Thompson
|
||||||
|
* Copyright (C) 2024 u-foka
|
||||||
*
|
*
|
||||||
* This file is part of PortaPack.
|
* This file is part of PortaPack.
|
||||||
*
|
*
|
||||||
@@ -32,9 +34,9 @@
|
|||||||
|
|
||||||
#include "ui_sd_card_debug.hpp"
|
#include "ui_sd_card_debug.hpp"
|
||||||
#include "ui_font_fixed_8x16.hpp"
|
#include "ui_font_fixed_8x16.hpp"
|
||||||
#include "ui_styles.hpp"
|
|
||||||
#include "ui_painter.hpp"
|
#include "ui_painter.hpp"
|
||||||
#include "ui_external_items_menu_loader.hpp"
|
#include "ui_external_items_menu_loader.hpp"
|
||||||
|
#include "ui_debug_battery.hpp"
|
||||||
|
|
||||||
#include "portapack.hpp"
|
#include "portapack.hpp"
|
||||||
#include "portapack_persistent_memory.hpp"
|
#include "portapack_persistent_memory.hpp"
|
||||||
@@ -78,7 +80,7 @@ void TemperatureWidget::paint(Painter& painter) {
|
|||||||
|
|
||||||
const auto rect = screen_rect();
|
const auto rect = screen_rect();
|
||||||
const Color color_background{0, 0, 64};
|
const Color color_background{0, 0, 64};
|
||||||
const Color color_foreground = Color::green();
|
const Color color_foreground = Theme::getInstance()->fg_green->foreground;
|
||||||
const Color color_reticle{128, 128, 128};
|
const Color color_reticle{128, 128, 128};
|
||||||
|
|
||||||
const auto graph_width = static_cast<int>(logger.capacity()) * bar_width;
|
const auto graph_width = static_cast<int>(logger.capacity()) * bar_width;
|
||||||
@@ -176,12 +178,15 @@ void RegistersWidget::paint(Painter& painter) {
|
|||||||
|
|
||||||
void RegistersWidget::draw_legend(const Coord left, Painter& painter) {
|
void RegistersWidget::draw_legend(const Coord left, Painter& painter) {
|
||||||
const auto pos = screen_pos();
|
const auto pos = screen_pos();
|
||||||
|
const std::string spaces(config.legend_length(), ' ');
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < config.registers_per_page; i += config.registers_per_row()) {
|
||||||
|
uint32_t r = page_number * config.registers_per_page + i;
|
||||||
|
|
||||||
for (uint32_t i = 0; i < config.registers_count; i += config.registers_per_row()) {
|
|
||||||
const Point offset{
|
const Point offset{
|
||||||
left, static_cast<int>((i / config.registers_per_row()) * row_height)};
|
left, static_cast<int>((i / config.registers_per_row()) * row_height)};
|
||||||
|
|
||||||
const auto text = to_string_hex(i, config.legend_length());
|
const auto text = (r >= config.registers_count) ? spaces : to_string_hex(r, config.legend_length());
|
||||||
painter.draw_string(
|
painter.draw_string(
|
||||||
pos + offset,
|
pos + offset,
|
||||||
style().invert(),
|
style().invert(),
|
||||||
@@ -193,15 +198,16 @@ void RegistersWidget::draw_values(
|
|||||||
const Coord left,
|
const Coord left,
|
||||||
Painter& painter) {
|
Painter& painter) {
|
||||||
const auto pos = screen_pos();
|
const auto pos = screen_pos();
|
||||||
|
const std::string spaces(config.value_length(), ' ');
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < config.registers_per_page; i++) {
|
||||||
|
uint32_t r = page_number * config.registers_per_page + i;
|
||||||
|
|
||||||
for (uint32_t i = 0; i < config.registers_count; i++) {
|
|
||||||
const Point offset = {
|
const Point offset = {
|
||||||
static_cast<int>(left + config.legend_width() + 8 + (i % config.registers_per_row()) * (config.value_width() + 8)),
|
static_cast<int>(left + config.legend_width() + 8 + (i % config.registers_per_row()) * (config.value_width() + 8)),
|
||||||
static_cast<int>((i / config.registers_per_row()) * row_height)};
|
static_cast<int>((i / config.registers_per_row()) * row_height)};
|
||||||
|
|
||||||
const auto value = reg_read(i);
|
const auto text = (r >= config.registers_count) ? spaces : to_string_hex(reg_read(r), config.value_length());
|
||||||
|
|
||||||
const auto text = to_string_hex(value, config.value_length());
|
|
||||||
painter.draw_string(
|
painter.draw_string(
|
||||||
pos + offset,
|
pos + offset,
|
||||||
style(),
|
style(),
|
||||||
@@ -213,13 +219,15 @@ uint32_t RegistersWidget::reg_read(const uint32_t register_number) {
|
|||||||
if (register_number < config.registers_count) {
|
if (register_number < config.registers_count) {
|
||||||
switch (config.chip_type) {
|
switch (config.chip_type) {
|
||||||
case CT_PMEM:
|
case CT_PMEM:
|
||||||
return portapack::persistent_memory::pmem_data_word((page_number * config.registers_count + register_number) / 4) >> (register_number % 4 * 8);
|
return portapack::persistent_memory::pmem_data_word(register_number / 4) >> (register_number % 4 * 8);
|
||||||
case CT_RFFC5072:
|
case CT_RFFC5072:
|
||||||
return radio::debug::first_if::register_read(register_number);
|
return radio::debug::first_if::register_read(register_number);
|
||||||
case CT_MAX283X:
|
case CT_MAX283X:
|
||||||
return radio::debug::second_if::register_read(register_number);
|
return radio::debug::second_if::register_read(register_number);
|
||||||
case CT_SI5351:
|
case CT_SI5351:
|
||||||
return portapack::clock_generator.read_register(register_number);
|
return portapack::clock_generator.read_register(register_number);
|
||||||
|
case CT_BATTERY:
|
||||||
|
return battery::BatteryManagement::read_register(register_number);
|
||||||
case CT_AUDIO:
|
case CT_AUDIO:
|
||||||
return audio::debug::reg_read(register_number);
|
return audio::debug::reg_read(register_number);
|
||||||
}
|
}
|
||||||
@@ -241,6 +249,9 @@ void RegistersWidget::reg_write(const uint32_t register_number, const uint32_t v
|
|||||||
case CT_SI5351:
|
case CT_SI5351:
|
||||||
portapack::clock_generator.write_register(register_number, value);
|
portapack::clock_generator.write_register(register_number, value);
|
||||||
break;
|
break;
|
||||||
|
case CT_BATTERY:
|
||||||
|
battery::BatteryManagement::write_register(register_number, value);
|
||||||
|
break;
|
||||||
case CT_AUDIO:
|
case CT_AUDIO:
|
||||||
audio::debug::reg_write(register_number, value);
|
audio::debug::reg_write(register_number, value);
|
||||||
break;
|
break;
|
||||||
@@ -272,6 +283,7 @@ RegistersView::RegistersView(
|
|||||||
button_done.on_select = [&nav](Button&) { nav.pop(); };
|
button_done.on_select = [&nav](Button&) { nav.pop(); };
|
||||||
|
|
||||||
registers_widget.set_parent_rect({0, 48, 240, 192});
|
registers_widget.set_parent_rect({0, 48, 240, 192});
|
||||||
|
registers_widget.set_page(0);
|
||||||
|
|
||||||
text_title.set_parent_rect({(240 - static_cast<int>(title.size()) * 8) / 2, 16,
|
text_title.set_parent_rect({(240 - static_cast<int>(title.size()) * 8) / 2, 16,
|
||||||
static_cast<int>(title.size()) * 8, 16});
|
static_cast<int>(title.size()) * 8, 16});
|
||||||
@@ -285,7 +297,7 @@ RegistersView::RegistersView(
|
|||||||
const auto value = registers_widget.reg_read(0);
|
const auto value = registers_widget.reg_read(0);
|
||||||
field_write_data_val.set_value(value);
|
field_write_data_val.set_value(value);
|
||||||
|
|
||||||
button_write.set_style(&Styles::red);
|
button_write.set_style(Theme::getInstance()->fg_red);
|
||||||
button_write.on_select = [this](Button&) {
|
button_write.on_select = [this](Button&) {
|
||||||
this->registers_widget.reg_write(field_write_reg_num.to_integer(), field_write_data_val.to_integer());
|
this->registers_widget.reg_write(field_write_reg_num.to_integer(), field_write_data_val.to_integer());
|
||||||
this->registers_widget.update();
|
this->registers_widget.update();
|
||||||
@@ -296,12 +308,19 @@ void RegistersView::focus() {
|
|||||||
button_done.focus();
|
button_done.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool RegistersView::on_encoder(const EncoderEvent delta) {
|
||||||
|
registers_widget.set_page(std::max(0ul, std::min(registers_widget.page_count() - 1, registers_widget.page() + delta)));
|
||||||
|
registers_widget.update();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/* ControlsSwitchesWidget ************************************************/
|
/* ControlsSwitchesWidget ************************************************/
|
||||||
|
|
||||||
void ControlsSwitchesWidget::on_show() {
|
void ControlsSwitchesWidget::on_show() {
|
||||||
display.fill_rectangle(
|
display.fill_rectangle(
|
||||||
screen_rect(),
|
screen_rect(),
|
||||||
Color::black());
|
Theme::getInstance()->bg_darkest->background);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ControlsSwitchesWidget::on_key(const KeyEvent key) {
|
bool ControlsSwitchesWidget::on_key(const KeyEvent key) {
|
||||||
@@ -310,6 +329,11 @@ bool ControlsSwitchesWidget::on_key(const KeyEvent key) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ControlsSwitchesWidget::on_encoder(const EncoderEvent delta) {
|
||||||
|
last_delta = delta;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void ControlsSwitchesWidget::paint(Painter& painter) {
|
void ControlsSwitchesWidget::paint(Painter& painter) {
|
||||||
const auto pos = screen_pos();
|
const auto pos = screen_pos();
|
||||||
|
|
||||||
@@ -326,11 +350,11 @@ void ControlsSwitchesWidget::paint(Painter& painter) {
|
|||||||
}};
|
}};
|
||||||
|
|
||||||
for (const auto r : button_rects) {
|
for (const auto r : button_rects) {
|
||||||
painter.fill_rectangle(r + pos, Color::blue());
|
painter.fill_rectangle(r + pos, Theme::getInstance()->fg_blue->foreground);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (get_touch_frame().touch)
|
if (get_touch_frame().touch)
|
||||||
painter.fill_rectangle(button_rects[8] + pos, Color::yellow());
|
painter.fill_rectangle(button_rects[8] + pos, Theme::getInstance()->fg_yellow->foreground);
|
||||||
|
|
||||||
const std::array<Rect, 8> raw_rects{{
|
const std::array<Rect, 8> raw_rects{{
|
||||||
{64 + 1, 32 + 1, 16 - 2, 16 - 2}, // Right
|
{64 + 1, 32 + 1, 16 - 2, 16 - 2}, // Right
|
||||||
@@ -346,7 +370,7 @@ void ControlsSwitchesWidget::paint(Painter& painter) {
|
|||||||
auto switches_raw = control::debug::switches();
|
auto switches_raw = control::debug::switches();
|
||||||
for (const auto r : raw_rects) {
|
for (const auto r : raw_rects) {
|
||||||
if (switches_raw & 1)
|
if (switches_raw & 1)
|
||||||
painter.fill_rectangle(r + pos, Color::yellow());
|
painter.fill_rectangle(r + pos, Theme::getInstance()->fg_yellow->foreground);
|
||||||
|
|
||||||
switches_raw >>= 1;
|
switches_raw >>= 1;
|
||||||
}
|
}
|
||||||
@@ -363,7 +387,7 @@ void ControlsSwitchesWidget::paint(Painter& painter) {
|
|||||||
auto switches_debounced = get_switches_state().to_ulong();
|
auto switches_debounced = get_switches_state().to_ulong();
|
||||||
for (const auto r : debounced_rects) {
|
for (const auto r : debounced_rects) {
|
||||||
if (switches_debounced & 1)
|
if (switches_debounced & 1)
|
||||||
painter.fill_rectangle(r + pos, Color::green());
|
painter.fill_rectangle(r + pos, Theme::getInstance()->fg_green->foreground);
|
||||||
|
|
||||||
switches_debounced >>= 1;
|
switches_debounced >>= 1;
|
||||||
}
|
}
|
||||||
@@ -380,7 +404,7 @@ void ControlsSwitchesWidget::paint(Painter& painter) {
|
|||||||
auto switches_event = key_event_mask;
|
auto switches_event = key_event_mask;
|
||||||
for (const auto r : events_rects) {
|
for (const auto r : events_rects) {
|
||||||
if (switches_event & 1)
|
if (switches_event & 1)
|
||||||
painter.fill_rectangle(r + pos, Color::red());
|
painter.fill_rectangle(r + pos, Theme::getInstance()->fg_red->foreground);
|
||||||
|
|
||||||
switches_event >>= 1;
|
switches_event >>= 1;
|
||||||
}
|
}
|
||||||
@@ -388,10 +412,12 @@ void ControlsSwitchesWidget::paint(Painter& painter) {
|
|||||||
switches_event = long_press_key_event_mask;
|
switches_event = long_press_key_event_mask;
|
||||||
for (const auto r : events_rects) {
|
for (const auto r : events_rects) {
|
||||||
if (switches_event & 1)
|
if (switches_event & 1)
|
||||||
painter.fill_rectangle(r + pos, Color::cyan());
|
painter.fill_rectangle(r + pos, Theme::getInstance()->fg_cyan->foreground);
|
||||||
|
|
||||||
switches_event >>= 1;
|
switches_event >>= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
painter.draw_string({5 * 8, 12 * 16}, *Theme::getInstance()->fg_light, to_string_dec_int(last_delta, 3));
|
||||||
}
|
}
|
||||||
|
|
||||||
void ControlsSwitchesWidget::on_frame_sync() {
|
void ControlsSwitchesWidget::on_frame_sync() {
|
||||||
@@ -425,43 +451,75 @@ void DebugControlsView::focus() {
|
|||||||
|
|
||||||
/* DebugPeripheralsMenuView **********************************************/
|
/* DebugPeripheralsMenuView **********************************************/
|
||||||
|
|
||||||
DebugPeripheralsMenuView::DebugPeripheralsMenuView(NavigationView& nav) {
|
DebugPeripheralsMenuView::DebugPeripheralsMenuView(NavigationView& nav)
|
||||||
|
: nav_(nav) {
|
||||||
|
set_max_rows(2); // allow wider buttons
|
||||||
|
}
|
||||||
|
|
||||||
|
void DebugPeripheralsMenuView::on_populate() {
|
||||||
const char* max283x = hackrf_r9 ? "MAX2839" : "MAX2837";
|
const char* max283x = hackrf_r9 ? "MAX2839" : "MAX2837";
|
||||||
const char* si5351x = hackrf_r9 ? "Si5351A" : "Si5351C";
|
const char* si5351x = hackrf_r9 ? "Si5351A" : "Si5351C";
|
||||||
add_items({
|
add_items({
|
||||||
{"RFFC5072", ui::Color::dark_cyan(), &bitmap_icon_peripherals_details, [&nav]() { nav.push<RegistersView>("RFFC5072", RegistersWidgetConfig{CT_RFFC5072, 31, 16}); }},
|
{"RFFC5072", Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_peripherals_details, [this]() { nav_.push<RegistersView>("RFFC5072", RegistersWidgetConfig{CT_RFFC5072, 31, 31, 16}); }},
|
||||||
{max283x, ui::Color::dark_cyan(), &bitmap_icon_peripherals_details, [&nav, max283x]() { nav.push<RegistersView>(max283x, RegistersWidgetConfig{CT_MAX283X, 32, 10}); }},
|
{max283x, Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_peripherals_details, [this, max283x]() { nav_.push<RegistersView>(max283x, RegistersWidgetConfig{CT_MAX283X, 32, 32, 10}); }},
|
||||||
{si5351x, ui::Color::dark_cyan(), &bitmap_icon_peripherals_details, [&nav, si5351x]() { nav.push<RegistersView>(si5351x, RegistersWidgetConfig{CT_SI5351, 96, 8}); }},
|
{si5351x, Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_peripherals_details, [this, si5351x]() { nav_.push<RegistersView>(si5351x, RegistersWidgetConfig{CT_SI5351, 188, 96, 8}); }},
|
||||||
{audio::debug::codec_name(), ui::Color::dark_cyan(), &bitmap_icon_peripherals_details, [&nav]() { nav.push<RegistersView>(audio::debug::codec_name(), RegistersWidgetConfig{CT_AUDIO, audio::debug::reg_count(), audio::debug::reg_bits()}); }},
|
{audio::debug::codec_name(), Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_peripherals_details, [this]() { nav_.push<RegistersView>(audio::debug::codec_name(), RegistersWidgetConfig{CT_AUDIO, audio::debug::reg_count(), audio::debug::reg_count(), audio::debug::reg_bits()}); }},
|
||||||
});
|
});
|
||||||
|
if (battery::BatteryManagement::detectedModule() == battery::BatteryManagement::BatteryModules::BATT_MAX17055) {
|
||||||
|
add_item(
|
||||||
|
{"MAX17055", Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_peripherals_details, [this]() { nav_.push<RegistersView>("MAX17055", RegistersWidgetConfig{CT_BATTERY, 256, 16, 16}); }});
|
||||||
|
}
|
||||||
set_max_rows(2); // allow wider buttons
|
set_max_rows(2); // allow wider buttons
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* DebugReboot **********************************************/
|
||||||
|
|
||||||
|
DebugReboot::DebugReboot(NavigationView& nav) {
|
||||||
|
(void)nav;
|
||||||
|
|
||||||
|
LPC_RGU->RESET_CTRL[0] = (1 << 0);
|
||||||
|
|
||||||
|
while (1)
|
||||||
|
__WFE();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DebugReboot::on_populate() {
|
||||||
|
}
|
||||||
|
|
||||||
/* DebugMenuView *********************************************************/
|
/* DebugMenuView *********************************************************/
|
||||||
|
|
||||||
DebugMenuView::DebugMenuView(NavigationView& nav) {
|
DebugMenuView::DebugMenuView(NavigationView& nav)
|
||||||
|
: nav_(nav) {
|
||||||
|
set_max_rows(2); // allow wider buttons
|
||||||
|
}
|
||||||
|
|
||||||
|
void DebugMenuView::on_populate() {
|
||||||
if (portapack::persistent_memory::show_gui_return_icon()) {
|
if (portapack::persistent_memory::show_gui_return_icon()) {
|
||||||
add_items({{"..", ui::Color::light_grey(), &bitmap_icon_previous, [&nav]() { nav.pop(); }}});
|
add_items({{"..", ui::Theme::getInstance()->fg_light->foreground, &bitmap_icon_previous, [this]() { nav_.pop(); }}});
|
||||||
}
|
}
|
||||||
add_items({
|
add_items({
|
||||||
{"Buttons Test", ui::Color::dark_cyan(), &bitmap_icon_controls, [&nav]() { nav.push<DebugControlsView>(); }},
|
{"Buttons Test", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_controls, [this]() { nav_.push<DebugControlsView>(); }},
|
||||||
{"Debug Dump", ui::Color::dark_cyan(), &bitmap_icon_memory, [&nav]() { portapack::persistent_memory::debug_dump(); }},
|
{"Debug Dump", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_memory, [this]() { portapack::persistent_memory::debug_dump(); }},
|
||||||
{"M0 Stack Dump", ui::Color::dark_cyan(), &bitmap_icon_memory, [&nav]() { stack_dump(); }},
|
{"M0 Stack Dump", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_memory, [this]() { stack_dump(); }},
|
||||||
{"Memory Dump", ui::Color::dark_cyan(), &bitmap_icon_memory, [&nav]() { nav.push<DebugMemoryDumpView>(); }},
|
{"Memory Dump", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_memory, [this]() { nav_.push<DebugMemoryDumpView>(); }},
|
||||||
{"Memory Usage", ui::Color::dark_cyan(), &bitmap_icon_memory, [&nav]() { nav.push<DebugMemoryView>(); }},
|
//{"Memory Usage", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_memory, [this]() { nav_.push<DebugMemoryView>(); }},
|
||||||
{"Peripherals", ui::Color::dark_cyan(), &bitmap_icon_peripherals, [&nav]() { nav.push<DebugPeripheralsMenuView>(); }},
|
{"Peripherals", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_peripherals, [this]() { nav_.push<DebugPeripheralsMenuView>(); }},
|
||||||
{"Pers. Memory", ui::Color::dark_cyan(), &bitmap_icon_memory, [&nav]() { nav.push<DebugPmemView>(); }},
|
{"Pers. Memory", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_memory, [this]() { nav_.push<DebugPmemView>(); }},
|
||||||
//{ "Radio State", ui::Color::white(), nullptr, [&nav](){ nav.push<NotImplementedView>(); } },
|
//{ "Radio State", ui::Theme::getInstance()->bg_darkest->foreground, nullptr, [this](){ nav_.push<NotImplementedView>(); } },
|
||||||
{"SD Card", ui::Color::dark_cyan(), &bitmap_icon_sdcard, [&nav]() { nav.push<SDCardDebugView>(); }},
|
{"SD Card", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_sdcard, [this]() { nav_.push<SDCardDebugView>(); }},
|
||||||
{"Temperature", ui::Color::dark_cyan(), &bitmap_icon_temperature, [&nav]() { nav.push<TemperatureView>(); }},
|
{"Temperature", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_temperature, [this]() { nav_.push<TemperatureView>(); }},
|
||||||
{"Touch Test", ui::Color::dark_cyan(), &bitmap_icon_notepad, [&nav]() { nav.push<DebugScreenTest>(); }},
|
{"Touch Test", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_notepad, [this]() { nav_.push<DebugScreenTest>(); }},
|
||||||
|
{"Reboot", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_setup, [this]() { nav_.push<DebugReboot>(); }},
|
||||||
});
|
});
|
||||||
|
|
||||||
for (auto const& gridItem : ExternalItemsMenuLoader::load_external_items(app_location_t::DEBUG, nav)) {
|
if (battery::BatteryManagement::detectedModule() == battery::BatteryManagement::BatteryModules::BATT_MAX17055) {
|
||||||
|
add_item(
|
||||||
|
{"Battery", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_batt_icon, [this]() { nav_.push<BatteryCapacityView>(); }});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto const& gridItem : ExternalItemsMenuLoader::load_external_items(app_location_t::DEBUG, nav_)) {
|
||||||
add_item(gridItem);
|
add_item(gridItem);
|
||||||
};
|
};
|
||||||
|
|
||||||
set_max_rows(2); // allow wider buttons
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* DebugMemoryDumpView *********************************************************/
|
/* DebugMemoryDumpView *********************************************************/
|
||||||
@@ -491,7 +549,7 @@ DebugMemoryDumpView::DebugMemoryDumpView(NavigationView& nav) {
|
|||||||
field_data_value.set_dirty();
|
field_data_value.set_dirty();
|
||||||
};
|
};
|
||||||
|
|
||||||
button_write.set_style(&Styles::red);
|
button_write.set_style(Theme::getInstance()->fg_red);
|
||||||
button_write.on_select = [this](Button&) {
|
button_write.on_select = [this](Button&) {
|
||||||
*(uint32_t*)field_rw_address.to_integer() = (uint32_t)field_data_value.to_integer();
|
*(uint32_t*)field_rw_address.to_integer() = (uint32_t)field_data_value.to_integer();
|
||||||
};
|
};
|
||||||
@@ -504,8 +562,8 @@ void DebugMemoryDumpView::focus() {
|
|||||||
/* DebugPmemView *********************************************************/
|
/* DebugPmemView *********************************************************/
|
||||||
|
|
||||||
DebugPmemView::DebugPmemView(NavigationView& nav)
|
DebugPmemView::DebugPmemView(NavigationView& nav)
|
||||||
: registers_widget(RegistersWidgetConfig{CT_PMEM, page_size, 8}) {
|
: registers_widget(RegistersWidgetConfig{CT_PMEM, PMEM_SIZE_BYTES, page_size, 8}) {
|
||||||
add_children({&text_page, ®isters_widget, &text_checksum, &text_checksum2, &button_ok});
|
add_children({®isters_widget, &text_checksum, &text_checksum2, &button_ok});
|
||||||
|
|
||||||
registers_widget.set_parent_rect({0, 32, 240, 192});
|
registers_widget.set_parent_rect({0, 32, 240, 192});
|
||||||
|
|
||||||
@@ -532,7 +590,6 @@ void DebugPmemView::focus() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void DebugPmemView::update() {
|
void DebugPmemView::update() {
|
||||||
text_page.set(to_string_hex(registers_widget.page() * page_size, 2) + "+");
|
|
||||||
registers_widget.update();
|
registers_widget.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -541,6 +598,7 @@ void DebugPmemView::update() {
|
|||||||
DebugScreenTest::DebugScreenTest(NavigationView& nav)
|
DebugScreenTest::DebugScreenTest(NavigationView& nav)
|
||||||
: nav_{nav} {
|
: nav_{nav} {
|
||||||
set_focusable(true);
|
set_focusable(true);
|
||||||
|
std::srand(LPC_RTC->CTIME0);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DebugScreenTest::on_key(const KeyEvent key) {
|
bool DebugScreenTest::on_key(const KeyEvent key) {
|
||||||
@@ -550,10 +608,10 @@ bool DebugScreenTest::on_key(const KeyEvent key) {
|
|||||||
nav_.pop();
|
nav_.pop();
|
||||||
break;
|
break;
|
||||||
case KeyEvent::Down:
|
case KeyEvent::Down:
|
||||||
painter.fill_rectangle({0, 0, screen_width, screen_height}, semirand());
|
painter.fill_rectangle({0, 0, screen_width, screen_height}, std::rand());
|
||||||
break;
|
break;
|
||||||
case KeyEvent::Left:
|
case KeyEvent::Left:
|
||||||
pen_color = semirand();
|
pen_color = std::rand();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
@@ -573,17 +631,10 @@ bool DebugScreenTest::on_touch(const TouchEvent event) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t DebugScreenTest::semirand() {
|
|
||||||
static uint64_t seed{0x0102030405060708};
|
|
||||||
seed = seed * 137;
|
|
||||||
seed = (seed >> 1) | ((seed & 0x01) << 63);
|
|
||||||
return (uint16_t)seed;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DebugScreenTest::paint(Painter& painter) {
|
void DebugScreenTest::paint(Painter& painter) {
|
||||||
painter.fill_rectangle({0, 16, screen_width, screen_height - 16}, Color::white());
|
painter.fill_rectangle({0, 16, screen_width, screen_height - 16}, Theme::getInstance()->bg_darkest->foreground);
|
||||||
painter.draw_string({10 * 8, screen_height / 2}, Styles::white, "Use Stylus");
|
painter.draw_string({10 * 8, screen_height / 2}, *Theme::getInstance()->bg_darkest, "Use Stylus");
|
||||||
pen_color = semirand();
|
pen_color = std::rand();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* DebugLCRView *******************************************************/
|
/* DebugLCRView *******************************************************/
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||||
|
* Copyright (C) 2024 Mark Thompson
|
||||||
|
* Copyright (C) 2024 u-foka
|
||||||
*
|
*
|
||||||
* This file is part of PortaPack.
|
* This file is part of PortaPack.
|
||||||
*
|
*
|
||||||
@@ -137,11 +139,13 @@ typedef enum {
|
|||||||
CT_MAX283X,
|
CT_MAX283X,
|
||||||
CT_SI5351,
|
CT_SI5351,
|
||||||
CT_AUDIO,
|
CT_AUDIO,
|
||||||
|
CT_BATTERY,
|
||||||
} chip_type_t;
|
} chip_type_t;
|
||||||
|
|
||||||
struct RegistersWidgetConfig {
|
struct RegistersWidgetConfig {
|
||||||
chip_type_t chip_type;
|
chip_type_t chip_type;
|
||||||
uint32_t registers_count;
|
uint32_t registers_count;
|
||||||
|
uint32_t registers_per_page;
|
||||||
uint32_t register_bits;
|
uint32_t register_bits;
|
||||||
|
|
||||||
constexpr size_t legend_length() const {
|
constexpr size_t legend_length() const {
|
||||||
@@ -194,6 +198,7 @@ class RegistersWidget : public Widget {
|
|||||||
|
|
||||||
void set_page(int32_t value) { page_number = value; }
|
void set_page(int32_t value) { page_number = value; }
|
||||||
uint32_t page(void) { return page_number; }
|
uint32_t page(void) { return page_number; }
|
||||||
|
uint32_t page_count(void) { return (config.registers_count + config.registers_per_page - 1) / config.registers_per_page; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const RegistersWidgetConfig config;
|
const RegistersWidgetConfig config;
|
||||||
@@ -208,8 +213,8 @@ class RegistersWidget : public Widget {
|
|||||||
class RegistersView : public View {
|
class RegistersView : public View {
|
||||||
public:
|
public:
|
||||||
RegistersView(NavigationView& nav, const std::string& title, RegistersWidgetConfig&& config);
|
RegistersView(NavigationView& nav, const std::string& title, RegistersWidgetConfig&& config);
|
||||||
|
void focus() override;
|
||||||
void focus();
|
bool on_encoder(const EncoderEvent delta) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Text text_title{};
|
Text text_title{};
|
||||||
@@ -229,8 +234,8 @@ class RegistersView : public View {
|
|||||||
"Write"};
|
"Write"};
|
||||||
|
|
||||||
Labels labels{
|
Labels labels{
|
||||||
{{1 * 8, 248}, "Reg:", Color::light_grey()},
|
{{1 * 8, 248}, "Reg:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{8 * 8, 248}, "Data:", Color::light_grey()}};
|
{{8 * 8, 248}, "Data:", Theme::getInstance()->fg_light->foreground}};
|
||||||
|
|
||||||
SymField field_write_reg_num{
|
SymField field_write_reg_num{
|
||||||
{5 * 8, 248},
|
{5 * 8, 248},
|
||||||
@@ -249,18 +254,21 @@ class ControlsSwitchesWidget : public Widget {
|
|||||||
Rect parent_rect)
|
Rect parent_rect)
|
||||||
: Widget{parent_rect},
|
: Widget{parent_rect},
|
||||||
key_event_mask(0),
|
key_event_mask(0),
|
||||||
long_press_key_event_mask{0} {
|
long_press_key_event_mask{0},
|
||||||
|
last_delta{0} {
|
||||||
set_focusable(true);
|
set_focusable(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void on_show() override;
|
void on_show() override;
|
||||||
bool on_key(const KeyEvent key) override;
|
bool on_key(const KeyEvent key) override;
|
||||||
|
bool on_encoder(const EncoderEvent delta) override;
|
||||||
|
|
||||||
void paint(Painter& painter) override;
|
void paint(Painter& painter) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint8_t key_event_mask;
|
uint8_t key_event_mask;
|
||||||
uint8_t long_press_key_event_mask;
|
uint8_t long_press_key_event_mask;
|
||||||
|
EncoderEvent last_delta;
|
||||||
|
|
||||||
MessageHandlerRegistration message_handler_frame_sync{
|
MessageHandlerRegistration message_handler_frame_sync{
|
||||||
Message::ID::DisplayFrameSync,
|
Message::ID::DisplayFrameSync,
|
||||||
@@ -281,8 +289,9 @@ class DebugControlsView : public View {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
Labels labels{
|
Labels labels{
|
||||||
{{8 * 8, 1 * 16}, "Controls State", Color::white()},
|
{{8 * 8, 1 * 16}, "Controls State", Theme::getInstance()->bg_darkest->foreground},
|
||||||
{{0 * 8, 14 * 16}, "Long-Press Mode:", Color::grey()}};
|
{{0 * 8, 11 * 16}, "Dial:", Theme::getInstance()->fg_medium->foreground},
|
||||||
|
{{0 * 8, 14 * 16}, "Long-Press Mode:", Theme::getInstance()->fg_medium->foreground}};
|
||||||
|
|
||||||
ControlsSwitchesWidget switches_widget{
|
ControlsSwitchesWidget switches_widget{
|
||||||
{80, 80, 80, 112},
|
{80, 80, 80, 112},
|
||||||
@@ -325,12 +334,12 @@ class DebugMemoryDumpView : public View {
|
|||||||
"Done"};
|
"Done"};
|
||||||
|
|
||||||
Labels labels{
|
Labels labels{
|
||||||
{{5 * 8, 1 * 16}, "Dump Range to File", Color::yellow()},
|
{{5 * 8, 1 * 16}, "Dump Range to File", Theme::getInstance()->fg_yellow->foreground},
|
||||||
{{0 * 8, 2 * 16}, "Starting Address: 0x", Color::light_grey()},
|
{{0 * 8, 2 * 16}, "Starting Address: 0x", Theme::getInstance()->fg_light->foreground},
|
||||||
{{0 * 8, 3 * 16}, "Byte Count: 0x", Color::light_grey()},
|
{{0 * 8, 3 * 16}, "Byte Count: 0x", Theme::getInstance()->fg_light->foreground},
|
||||||
{{3 * 8, 8 * 16}, "Read/Write Single Word", Color::yellow()},
|
{{3 * 8, 8 * 16}, "Read/Write Single Word", Theme::getInstance()->fg_yellow->foreground},
|
||||||
{{0 * 8, 9 * 16}, "Memory Address: 0x", Color::light_grey()},
|
{{0 * 8, 9 * 16}, "Memory Address: 0x", Theme::getInstance()->fg_light->foreground},
|
||||||
{{0 * 8, 10 * 16}, "Data Value: 0x", Color::light_grey()}};
|
{{0 * 8, 10 * 16}, "Data Value: 0x", Theme::getInstance()->fg_light->foreground}};
|
||||||
|
|
||||||
SymField field_starting_address{
|
SymField field_starting_address{
|
||||||
{20 * 8, 2 * 16},
|
{20 * 8, 2 * 16},
|
||||||
@@ -364,8 +373,6 @@ class DebugPmemView : public View {
|
|||||||
static constexpr uint8_t page_size{96}; // Must be multiply of 4 otherwise bit shifting for register view wont work properly
|
static constexpr uint8_t page_size{96}; // Must be multiply of 4 otherwise bit shifting for register view wont work properly
|
||||||
static constexpr uint8_t page_count{(portapack::memory::map::backup_ram.size() + page_size - 1) / page_size};
|
static constexpr uint8_t page_count{(portapack::memory::map::backup_ram.size() + page_size - 1) / page_size};
|
||||||
|
|
||||||
Text text_page{{16, 16, 208, 16}};
|
|
||||||
|
|
||||||
RegistersWidget registers_widget;
|
RegistersWidget registers_widget;
|
||||||
|
|
||||||
Text text_checksum{{16, 232, 208, 16}};
|
Text text_checksum{{16, 232, 208, 16}};
|
||||||
@@ -385,7 +392,6 @@ class DebugScreenTest : public View {
|
|||||||
bool on_key(KeyEvent key) override;
|
bool on_key(KeyEvent key) override;
|
||||||
bool on_encoder(EncoderEvent delta) override;
|
bool on_encoder(EncoderEvent delta) override;
|
||||||
bool on_touch(TouchEvent event) override;
|
bool on_touch(TouchEvent event) override;
|
||||||
uint16_t semirand();
|
|
||||||
void paint(Painter& painter) override;
|
void paint(Painter& painter) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -418,12 +424,28 @@ class DebugPeripheralsMenuView : public BtnGridView {
|
|||||||
public:
|
public:
|
||||||
DebugPeripheralsMenuView(NavigationView& nav);
|
DebugPeripheralsMenuView(NavigationView& nav);
|
||||||
std::string title() const override { return "Peripherals"; };
|
std::string title() const override { return "Peripherals"; };
|
||||||
|
|
||||||
|
private:
|
||||||
|
NavigationView& nav_;
|
||||||
|
void on_populate() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class DebugReboot : public BtnGridView {
|
||||||
|
public:
|
||||||
|
DebugReboot(NavigationView& nav);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void on_populate() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
class DebugMenuView : public BtnGridView {
|
class DebugMenuView : public BtnGridView {
|
||||||
public:
|
public:
|
||||||
DebugMenuView(NavigationView& nav);
|
DebugMenuView(NavigationView& nav);
|
||||||
std::string title() const override { return "Debug"; };
|
std::string title() const override { return "Debug"; };
|
||||||
|
|
||||||
|
private:
|
||||||
|
NavigationView& nav_;
|
||||||
|
void on_populate() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
} /* namespace ui */
|
} /* namespace ui */
|
||||||
|
|||||||
@@ -0,0 +1,104 @@
|
|||||||
|
#include "ui_debug_battery.hpp"
|
||||||
|
#include "string_format.hpp"
|
||||||
|
|
||||||
|
namespace ui {
|
||||||
|
|
||||||
|
BatteryCapacityView::RegisterEntry BatteryCapacityView::get_entry(size_t index) {
|
||||||
|
if (index < battery::max17055::MAX17055::entries_count) {
|
||||||
|
return battery::max17055::MAX17055::entries[index];
|
||||||
|
}
|
||||||
|
return {"", 0, "", 0, false, "", false, 0, false, false, false, 0, false};
|
||||||
|
}
|
||||||
|
|
||||||
|
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});
|
||||||
|
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});
|
||||||
|
|
||||||
|
add_child(&name_texts[i]);
|
||||||
|
add_child(&addr_texts[i]);
|
||||||
|
add_child(&hex_texts[i]);
|
||||||
|
add_child(&value_texts[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
add_children({&labels, &page_text, &button_done});
|
||||||
|
|
||||||
|
button_done.on_select = [&nav](Button&) { nav.pop(); };
|
||||||
|
|
||||||
|
populate_page(0);
|
||||||
|
update_page_text();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BatteryCapacityView::focus() {
|
||||||
|
button_done.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BatteryCapacityView::on_encoder(const EncoderEvent delta) {
|
||||||
|
int32_t new_page = current_page + delta;
|
||||||
|
if (new_page >= 0 && new_page < ((int32_t)battery::max17055::MAX17055::entries_count + ENTRIES_PER_PAGE - 1) / ENTRIES_PER_PAGE) {
|
||||||
|
current_page = new_page;
|
||||||
|
populate_page(current_page * ENTRIES_PER_PAGE);
|
||||||
|
update_page_text();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BatteryCapacityView::update_values() {
|
||||||
|
for (size_t i = 0; i < ENTRIES_PER_PAGE; ++i) {
|
||||||
|
size_t entry_index = current_page * ENTRIES_PER_PAGE + i;
|
||||||
|
if (entry_index < battery::max17055::MAX17055::entries_count) {
|
||||||
|
const auto entry = get_entry(entry_index);
|
||||||
|
uint16_t raw_value = battery::BatteryManagement::read_register(entry.address);
|
||||||
|
|
||||||
|
hex_texts[i].set("0x" + to_string_hex(raw_value, 4));
|
||||||
|
|
||||||
|
float scaled_value;
|
||||||
|
if (entry.is_signed) {
|
||||||
|
int16_t signed_value = static_cast<int16_t>(raw_value);
|
||||||
|
scaled_value = signed_value * entry.scalar;
|
||||||
|
} else {
|
||||||
|
scaled_value = raw_value * entry.scalar;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format the value with appropriate decimal places
|
||||||
|
std::string formatted_value;
|
||||||
|
if (entry.resolution > 0) {
|
||||||
|
formatted_value = to_string_decimal(scaled_value, std::min(entry.resolution, 3));
|
||||||
|
} else {
|
||||||
|
formatted_value = to_string_dec_int(scaled_value); // Show up to 3 decimal places
|
||||||
|
}
|
||||||
|
|
||||||
|
value_texts[i].set(formatted_value + " " + entry.unit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BatteryCapacityView::populate_page(int start_index) {
|
||||||
|
for (size_t i = 0; i < ENTRIES_PER_PAGE; ++i) {
|
||||||
|
size_t entry_index = start_index + i;
|
||||||
|
if (entry_index < battery::max17055::MAX17055::entries_count) {
|
||||||
|
const auto entry = get_entry(entry_index);
|
||||||
|
name_texts[i].set(entry.name);
|
||||||
|
addr_texts[i].set("0x" + to_string_hex(entry.address, 2));
|
||||||
|
name_texts[i].hidden(false);
|
||||||
|
addr_texts[i].hidden(false);
|
||||||
|
hex_texts[i].hidden(false);
|
||||||
|
value_texts[i].hidden(false);
|
||||||
|
} else {
|
||||||
|
name_texts[i].hidden(true);
|
||||||
|
addr_texts[i].hidden(true);
|
||||||
|
hex_texts[i].hidden(true);
|
||||||
|
value_texts[i].hidden(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
update_values();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BatteryCapacityView::update_page_text() {
|
||||||
|
int total_pages = (battery::max17055::MAX17055::entries_count + ENTRIES_PER_PAGE - 1) / ENTRIES_PER_PAGE;
|
||||||
|
page_text.set("Page " + to_string_dec_uint(current_page + 1) + "/" + to_string_dec_uint(total_pages));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ui
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
#ifndef __UI_DEBUG_BATTERY_HPP__
|
||||||
|
#define __UI_DEBUG_BATTERY_HPP__
|
||||||
|
|
||||||
|
#include "ui.hpp"
|
||||||
|
#include "ui_widget.hpp"
|
||||||
|
#include "ui_navigation.hpp"
|
||||||
|
#include "battery.hpp"
|
||||||
|
#include "max17055.hpp"
|
||||||
|
|
||||||
|
namespace ui {
|
||||||
|
|
||||||
|
class BatteryCapacityView : public View {
|
||||||
|
public:
|
||||||
|
BatteryCapacityView(NavigationView& nav);
|
||||||
|
void focus() override;
|
||||||
|
std::string title() const override { return "Battery Registers"; }
|
||||||
|
|
||||||
|
bool on_encoder(const EncoderEvent delta) override;
|
||||||
|
|
||||||
|
using RegisterEntry = battery::max17055::RegisterEntry;
|
||||||
|
|
||||||
|
private:
|
||||||
|
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},
|
||||||
|
};
|
||||||
|
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"};
|
||||||
|
|
||||||
|
void update_values();
|
||||||
|
void populate_page(int start_index);
|
||||||
|
void update_page_text();
|
||||||
|
int current_page = 0;
|
||||||
|
static constexpr int ENTRIES_PER_PAGE = 16;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ui
|
||||||
|
|
||||||
|
#endif // __UI_DEBUG_BATTERY_HPP__
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2023 Bernd Herzog
|
* Copyright (C) 2023 Bernd Herzog
|
||||||
|
* Copyright (C) 2023 Mark Thompson
|
||||||
*
|
*
|
||||||
* This file is part of PortaPack.
|
* This file is part of PortaPack.
|
||||||
*
|
*
|
||||||
@@ -37,48 +38,63 @@ DfuMenu::DfuMenu(NavigationView& nav)
|
|||||||
&text_info_line_5,
|
&text_info_line_5,
|
||||||
&text_info_line_6,
|
&text_info_line_6,
|
||||||
&text_info_line_7,
|
&text_info_line_7,
|
||||||
&text_info_line_8});
|
&text_info_line_8,
|
||||||
|
&text_info_line_9,
|
||||||
|
&text_info_line_10});
|
||||||
|
|
||||||
|
if (battery::BatteryManagement::isDetected()) {
|
||||||
|
add_child(&voltage_label);
|
||||||
|
add_child(&text_info_line_11);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DfuMenu::paint(Painter& painter) {
|
void DfuMenu::paint(Painter& painter) {
|
||||||
auto utilisation = get_cpu_utilisation_in_percent();
|
auto utilisation = get_cpu_utilisation_in_percent();
|
||||||
|
size_t m0_fragmented_free_space = 0;
|
||||||
|
const auto m0_fragments = chHeapStatus(NULL, &m0_fragmented_free_space);
|
||||||
|
|
||||||
|
auto lines = (battery::BatteryManagement::isDetected() ? 11 : 10) + 2;
|
||||||
|
|
||||||
text_info_line_1.set(to_string_dec_uint(chCoreStatus(), 6));
|
text_info_line_1.set(to_string_dec_uint(chCoreStatus(), 6));
|
||||||
text_info_line_2.set(to_string_dec_uint((uint32_t)get_free_stack_space(), 6));
|
text_info_line_2.set(to_string_dec_uint(m0_fragmented_free_space, 6));
|
||||||
text_info_line_3.set(to_string_dec_uint(utilisation, 6));
|
text_info_line_3.set(to_string_dec_uint(m0_fragments, 6));
|
||||||
text_info_line_4.set(to_string_dec_uint(shared_memory.m4_heap_usage, 6));
|
text_info_line_4.set(to_string_dec_uint((uint32_t)get_free_stack_space(), 6));
|
||||||
text_info_line_5.set(to_string_dec_uint(shared_memory.m4_stack_usage, 6));
|
text_info_line_5.set(to_string_dec_uint(utilisation, 6));
|
||||||
text_info_line_6.set(to_string_dec_uint(shared_memory.m4_cpu_usage, 6));
|
text_info_line_6.set(to_string_dec_uint(shared_memory.m4_heap_usage, 6));
|
||||||
text_info_line_7.set(to_string_dec_uint(shared_memory.m4_buffer_missed, 6));
|
text_info_line_7.set(to_string_dec_uint(shared_memory.m4_stack_usage, 6));
|
||||||
text_info_line_8.set(to_string_dec_uint(chTimeNow() / 1000, 6));
|
text_info_line_8.set(to_string_dec_uint(shared_memory.m4_performance_counter, 6));
|
||||||
|
text_info_line_9.set(to_string_dec_uint(shared_memory.m4_buffer_missed, 6));
|
||||||
|
text_info_line_10.set(to_string_dec_uint(chTimeNow() / 1000, 6));
|
||||||
|
if (battery::BatteryManagement::isDetected()) {
|
||||||
|
text_info_line_11.set(to_string_decimal_padding((float)battery::BatteryManagement::getVoltage() / 1000.0, 3, 6));
|
||||||
|
}
|
||||||
|
|
||||||
constexpr auto margin = 5;
|
constexpr auto margin = 5;
|
||||||
constexpr auto lines = 8 + 2;
|
|
||||||
|
|
||||||
painter.fill_rectangle(
|
painter.fill_rectangle(
|
||||||
{{6 * CHARACTER_WIDTH - margin, 3 * LINE_HEIGHT - margin},
|
{{6 * CHARACTER_WIDTH - margin, 3 * LINE_HEIGHT - margin},
|
||||||
{15 * CHARACTER_WIDTH + margin * 2, lines * LINE_HEIGHT + margin * 2}},
|
{15 * CHARACTER_WIDTH + margin * 2, lines * LINE_HEIGHT + margin * 2}},
|
||||||
ui::Color::black());
|
Theme::getInstance()->bg_darkest->background);
|
||||||
|
|
||||||
painter.fill_rectangle(
|
painter.fill_rectangle(
|
||||||
{{5 * CHARACTER_WIDTH - margin, 3 * LINE_HEIGHT - margin},
|
{{5 * CHARACTER_WIDTH - margin, 3 * LINE_HEIGHT - margin},
|
||||||
{CHARACTER_WIDTH, lines * LINE_HEIGHT + margin * 2}},
|
{CHARACTER_WIDTH, lines * LINE_HEIGHT + margin * 2}},
|
||||||
ui::Color::dark_cyan());
|
ui::Theme::getInstance()->fg_darkcyan->foreground);
|
||||||
|
|
||||||
painter.fill_rectangle(
|
painter.fill_rectangle(
|
||||||
{{21 * CHARACTER_WIDTH + margin, 3 * LINE_HEIGHT - margin},
|
{{21 * CHARACTER_WIDTH + margin, 3 * LINE_HEIGHT - margin},
|
||||||
{CHARACTER_WIDTH, lines * LINE_HEIGHT + margin * 2}},
|
{CHARACTER_WIDTH, lines * LINE_HEIGHT + margin * 2}},
|
||||||
ui::Color::dark_cyan());
|
ui::Theme::getInstance()->fg_darkcyan->foreground);
|
||||||
|
|
||||||
painter.fill_rectangle(
|
painter.fill_rectangle(
|
||||||
{{5 * CHARACTER_WIDTH - margin, 3 * LINE_HEIGHT - margin - 8},
|
{{5 * CHARACTER_WIDTH - margin, 3 * LINE_HEIGHT - margin - 8},
|
||||||
{17 * CHARACTER_WIDTH + margin * 2, 8}},
|
{17 * CHARACTER_WIDTH + margin * 2, 8}},
|
||||||
ui::Color::dark_cyan());
|
ui::Theme::getInstance()->fg_darkcyan->foreground);
|
||||||
|
|
||||||
painter.fill_rectangle(
|
painter.fill_rectangle(
|
||||||
{{5 * CHARACTER_WIDTH - margin, (lines + 3) * LINE_HEIGHT + margin},
|
{{5 * CHARACTER_WIDTH - margin, (lines + 3) * LINE_HEIGHT + margin},
|
||||||
{17 * CHARACTER_WIDTH + margin * 2, 8}},
|
{17 * CHARACTER_WIDTH + margin * 2, 8}},
|
||||||
ui::Color::dark_cyan());
|
ui::Theme::getInstance()->fg_darkcyan->foreground);
|
||||||
}
|
}
|
||||||
|
|
||||||
DfuMenu2::DfuMenu2(NavigationView& nav)
|
DfuMenu2::DfuMenu2(NavigationView& nav)
|
||||||
@@ -94,48 +110,50 @@ DfuMenu2::DfuMenu2(NavigationView& nav)
|
|||||||
&text_info_line_7,
|
&text_info_line_7,
|
||||||
&text_info_line_8,
|
&text_info_line_8,
|
||||||
&text_info_line_9,
|
&text_info_line_9,
|
||||||
&text_info_line_10});
|
&text_info_line_10,
|
||||||
|
&text_info_line_11});
|
||||||
}
|
}
|
||||||
|
|
||||||
void DfuMenu2::paint(Painter& painter) {
|
void DfuMenu2::paint(Painter& painter) {
|
||||||
text_info_line_1.set(to_string_dec_uint(portapack::receiver_model.target_frequency(), 10));
|
text_info_line_1.set(to_string_dec_uint(portapack::receiver_model.target_frequency(), 10));
|
||||||
text_info_line_2.set(to_string_dec_uint(portapack::receiver_model.baseband_bandwidth(), 10));
|
text_info_line_2.set(to_string_dec_uint(portapack::receiver_model.baseband_bandwidth(), 10));
|
||||||
text_info_line_3.set(to_string_dec_uint(portapack::receiver_model.sampling_rate(), 10));
|
text_info_line_3.set(to_string_dec_uint(portapack::receiver_model.sampling_rate(), 10));
|
||||||
text_info_line_4.set(to_string_dec_uint((uint32_t)portapack::receiver_model.modulation(), 10));
|
text_info_line_4.set(to_string_dec_uint(((uint32_t)shared_memory.m4_performance_counter) * 100 / 127, 10));
|
||||||
text_info_line_5.set(to_string_dec_uint(portapack::receiver_model.am_configuration(), 10));
|
text_info_line_5.set(to_string_dec_uint((uint32_t)portapack::receiver_model.modulation(), 10));
|
||||||
text_info_line_6.set(to_string_dec_uint(portapack::receiver_model.nbfm_configuration(), 10));
|
text_info_line_6.set(to_string_dec_uint(portapack::receiver_model.am_configuration(), 10));
|
||||||
text_info_line_7.set(to_string_dec_uint(portapack::receiver_model.wfm_configuration(), 10));
|
text_info_line_7.set(to_string_dec_uint(portapack::receiver_model.nbfm_configuration(), 10));
|
||||||
text_info_line_8.set(to_string_dec_uint(portapack::transmitter_model.target_frequency(), 10));
|
text_info_line_8.set(to_string_dec_uint(portapack::receiver_model.wfm_configuration(), 10));
|
||||||
text_info_line_9.set(to_string_dec_uint(portapack::transmitter_model.baseband_bandwidth(), 10));
|
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.sampling_rate(), 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));
|
||||||
|
|
||||||
constexpr auto margin = 5;
|
constexpr auto margin = 5;
|
||||||
constexpr auto lines = 10 + 2;
|
constexpr auto lines = 11 + 2;
|
||||||
|
|
||||||
painter.fill_rectangle(
|
painter.fill_rectangle(
|
||||||
{{5 * CHARACTER_WIDTH - margin, 3 * LINE_HEIGHT - margin},
|
{{5 * CHARACTER_WIDTH - margin, 3 * LINE_HEIGHT - margin},
|
||||||
{19 * CHARACTER_WIDTH + margin * 2, lines * LINE_HEIGHT + margin * 2}},
|
{19 * CHARACTER_WIDTH + margin * 2, lines * LINE_HEIGHT + margin * 2}},
|
||||||
ui::Color::black());
|
Theme::getInstance()->bg_darkest->background);
|
||||||
|
|
||||||
painter.fill_rectangle(
|
painter.fill_rectangle(
|
||||||
{{4 * CHARACTER_WIDTH - margin, 3 * LINE_HEIGHT - margin},
|
{{4 * CHARACTER_WIDTH - margin, 3 * LINE_HEIGHT - margin},
|
||||||
{CHARACTER_WIDTH, lines * LINE_HEIGHT + margin * 2}},
|
{CHARACTER_WIDTH, lines * LINE_HEIGHT + margin * 2}},
|
||||||
ui::Color::dark_cyan());
|
ui::Theme::getInstance()->fg_darkcyan->foreground);
|
||||||
|
|
||||||
painter.fill_rectangle(
|
painter.fill_rectangle(
|
||||||
{{24 * CHARACTER_WIDTH + margin, 3 * LINE_HEIGHT - margin},
|
{{24 * CHARACTER_WIDTH + margin, 3 * LINE_HEIGHT - margin},
|
||||||
{CHARACTER_WIDTH, lines * LINE_HEIGHT + margin * 2}},
|
{CHARACTER_WIDTH, lines * LINE_HEIGHT + margin * 2}},
|
||||||
ui::Color::dark_cyan());
|
ui::Theme::getInstance()->fg_darkcyan->foreground);
|
||||||
|
|
||||||
painter.fill_rectangle(
|
painter.fill_rectangle(
|
||||||
{{4 * CHARACTER_WIDTH - margin, 3 * LINE_HEIGHT - margin - 8},
|
{{4 * CHARACTER_WIDTH - margin, 3 * LINE_HEIGHT - margin - 8},
|
||||||
{21 * CHARACTER_WIDTH + margin * 2, 8}},
|
{21 * CHARACTER_WIDTH + margin * 2, 8}},
|
||||||
ui::Color::dark_cyan());
|
ui::Theme::getInstance()->fg_darkcyan->foreground);
|
||||||
|
|
||||||
painter.fill_rectangle(
|
painter.fill_rectangle(
|
||||||
{{4 * CHARACTER_WIDTH - margin, (lines + 3) * LINE_HEIGHT + margin},
|
{{4 * CHARACTER_WIDTH - margin, (lines + 3) * LINE_HEIGHT + margin},
|
||||||
{21 * CHARACTER_WIDTH + margin * 2, 8}},
|
{21 * CHARACTER_WIDTH + margin * 2, 8}},
|
||||||
ui::Color::dark_cyan());
|
ui::Theme::getInstance()->fg_darkcyan->foreground);
|
||||||
}
|
}
|
||||||
|
|
||||||
} /* namespace ui */
|
} /* namespace ui */
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2023 Bernd Herzog
|
* Copyright (C) 2023 Bernd Herzog
|
||||||
|
* Copyright (C) 2023 Mark Thompson
|
||||||
*
|
*
|
||||||
* This file is part of PortaPack.
|
* This file is part of PortaPack.
|
||||||
*
|
*
|
||||||
@@ -48,14 +49,18 @@ class DfuMenu : public View {
|
|||||||
Text text_head{{6 * CHARACTER_WIDTH, 3 * LINE_HEIGHT, 11 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, "Performance"};
|
Text text_head{{6 * CHARACTER_WIDTH, 3 * LINE_HEIGHT, 11 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, "Performance"};
|
||||||
|
|
||||||
Labels labels{
|
Labels labels{
|
||||||
{{6 * CHARACTER_WIDTH, 5 * LINE_HEIGHT}, "M0 heap:", Color::dark_cyan()},
|
{{6 * CHARACTER_WIDTH, 5 * LINE_HEIGHT}, "M0 core:", Theme::getInstance()->fg_darkcyan->foreground},
|
||||||
{{6 * CHARACTER_WIDTH, 6 * LINE_HEIGHT}, "M0 stack:", Color::dark_cyan()},
|
{{6 * CHARACTER_WIDTH, 6 * LINE_HEIGHT}, "M0 heap:", Theme::getInstance()->fg_darkcyan->foreground},
|
||||||
{{6 * CHARACTER_WIDTH, 7 * LINE_HEIGHT}, "M0 cpu %:", Color::dark_cyan()},
|
{{6 * CHARACTER_WIDTH, 7 * LINE_HEIGHT}, "M0 frags:", Theme::getInstance()->fg_darkcyan->foreground},
|
||||||
{{6 * CHARACTER_WIDTH, 8 * LINE_HEIGHT}, "M4 heap:", Color::dark_cyan()},
|
{{6 * CHARACTER_WIDTH, 8 * LINE_HEIGHT}, "M0 stack:", Theme::getInstance()->fg_darkcyan->foreground},
|
||||||
{{6 * CHARACTER_WIDTH, 9 * LINE_HEIGHT}, "M4 stack:", Color::dark_cyan()},
|
{{6 * CHARACTER_WIDTH, 9 * LINE_HEIGHT}, "M0 cpu %:", Theme::getInstance()->fg_darkcyan->foreground},
|
||||||
{{6 * CHARACTER_WIDTH, 10 * LINE_HEIGHT}, "M4 cpu %:", Color::dark_cyan()},
|
{{6 * CHARACTER_WIDTH, 10 * LINE_HEIGHT}, "M4 heap:", Theme::getInstance()->fg_darkcyan->foreground},
|
||||||
{{6 * CHARACTER_WIDTH, 11 * LINE_HEIGHT}, "M4 miss:", Color::dark_cyan()},
|
{{6 * CHARACTER_WIDTH, 11 * LINE_HEIGHT}, "M4 stack:", Theme::getInstance()->fg_darkcyan->foreground},
|
||||||
{{6 * CHARACTER_WIDTH, 12 * LINE_HEIGHT}, "uptime:", Color::dark_cyan()}};
|
{{6 * CHARACTER_WIDTH, 12 * LINE_HEIGHT}, "M4 cpu %:", Theme::getInstance()->fg_darkcyan->foreground},
|
||||||
|
{{6 * CHARACTER_WIDTH, 13 * LINE_HEIGHT}, "M4 miss:", Theme::getInstance()->fg_darkcyan->foreground},
|
||||||
|
{{6 * CHARACTER_WIDTH, 14 * LINE_HEIGHT}, "Uptime:", Theme::getInstance()->fg_darkcyan->foreground}};
|
||||||
|
|
||||||
|
Labels voltage_label{{{6 * CHARACTER_WIDTH, 15 * LINE_HEIGHT}, "Voltage:", Theme::getInstance()->fg_darkcyan->foreground}};
|
||||||
|
|
||||||
Text text_info_line_1{{15 * CHARACTER_WIDTH, 5 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
Text text_info_line_1{{15 * CHARACTER_WIDTH, 5 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||||
Text text_info_line_2{{15 * CHARACTER_WIDTH, 6 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
Text text_info_line_2{{15 * CHARACTER_WIDTH, 6 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||||
@@ -65,6 +70,9 @@ class DfuMenu : public View {
|
|||||||
Text text_info_line_6{{15 * CHARACTER_WIDTH, 10 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
Text text_info_line_6{{15 * CHARACTER_WIDTH, 10 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||||
Text text_info_line_7{{15 * CHARACTER_WIDTH, 11 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
Text text_info_line_7{{15 * CHARACTER_WIDTH, 11 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||||
Text text_info_line_8{{15 * CHARACTER_WIDTH, 12 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
Text text_info_line_8{{15 * CHARACTER_WIDTH, 12 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||||
|
Text text_info_line_9{{15 * CHARACTER_WIDTH, 13 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||||
|
Text text_info_line_10{{15 * CHARACTER_WIDTH, 14 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||||
|
Text text_info_line_11{{15 * CHARACTER_WIDTH, 15 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||||
};
|
};
|
||||||
|
|
||||||
class DfuMenu2 : public View {
|
class DfuMenu2 : public View {
|
||||||
@@ -80,16 +88,18 @@ class DfuMenu2 : public View {
|
|||||||
Text text_head{{6 * CHARACTER_WIDTH, 3 * LINE_HEIGHT, 14 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, "Radio Settings"};
|
Text text_head{{6 * CHARACTER_WIDTH, 3 * LINE_HEIGHT, 14 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, "Radio Settings"};
|
||||||
|
|
||||||
Labels labels{
|
Labels labels{
|
||||||
{{5 * CHARACTER_WIDTH, 5 * LINE_HEIGHT}, "RX Freq:", Color::dark_cyan()},
|
{{5 * CHARACTER_WIDTH, 5 * LINE_HEIGHT}, "RX Freq:", Theme::getInstance()->fg_darkcyan->foreground},
|
||||||
{{5 * CHARACTER_WIDTH, 6 * LINE_HEIGHT}, "RX BW:", Color::dark_cyan()},
|
{{5 * CHARACTER_WIDTH, 6 * LINE_HEIGHT}, "RX BW:", Theme::getInstance()->fg_darkcyan->foreground},
|
||||||
{{5 * CHARACTER_WIDTH, 7 * LINE_HEIGHT}, "RX SampR:", Color::dark_cyan()},
|
{{5 * CHARACTER_WIDTH, 7 * LINE_HEIGHT}, "RX SampR:", Theme::getInstance()->fg_darkcyan->foreground},
|
||||||
{{5 * CHARACTER_WIDTH, 8 * LINE_HEIGHT}, "Modulatn:", Color::dark_cyan()},
|
{{5 * CHARACTER_WIDTH, 8 * LINE_HEIGHT}, "RX Satu%:", Theme::getInstance()->fg_darkcyan->foreground},
|
||||||
{{5 * CHARACTER_WIDTH, 9 * LINE_HEIGHT}, "AM cfg:", Color::dark_cyan()},
|
{{5 * CHARACTER_WIDTH, 9 * LINE_HEIGHT}, "Modulatn:", Theme::getInstance()->fg_darkcyan->foreground},
|
||||||
{{5 * CHARACTER_WIDTH, 10 * LINE_HEIGHT}, "NBFM cfg:", Color::dark_cyan()},
|
{{5 * CHARACTER_WIDTH, 10 * LINE_HEIGHT}, "AM cfg:", Theme::getInstance()->fg_darkcyan->foreground},
|
||||||
{{5 * CHARACTER_WIDTH, 11 * LINE_HEIGHT}, "WFM cfg:", Color::dark_cyan()},
|
{{5 * CHARACTER_WIDTH, 11 * LINE_HEIGHT}, "NBFM cfg:", Theme::getInstance()->fg_darkcyan->foreground},
|
||||||
{{5 * CHARACTER_WIDTH, 12 * LINE_HEIGHT}, "TX Freq:", Color::dark_cyan()},
|
{{5 * CHARACTER_WIDTH, 12 * LINE_HEIGHT}, "WFM cfg:", Theme::getInstance()->fg_darkcyan->foreground},
|
||||||
{{5 * CHARACTER_WIDTH, 13 * LINE_HEIGHT}, "TX BW:", Color::dark_cyan()},
|
{{5 * CHARACTER_WIDTH, 13 * LINE_HEIGHT}, "TX Freq:", Theme::getInstance()->fg_darkcyan->foreground},
|
||||||
{{5 * CHARACTER_WIDTH, 14 * LINE_HEIGHT}, "TX SampR:", Color::dark_cyan()}};
|
{{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},
|
||||||
|
};
|
||||||
|
|
||||||
Text text_info_line_1{{14 * CHARACTER_WIDTH, 5 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
Text text_info_line_1{{14 * CHARACTER_WIDTH, 5 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||||
Text text_info_line_2{{14 * CHARACTER_WIDTH, 6 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
Text text_info_line_2{{14 * CHARACTER_WIDTH, 6 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||||
@@ -101,6 +111,7 @@ class DfuMenu2 : public View {
|
|||||||
Text text_info_line_8{{14 * CHARACTER_WIDTH, 12 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
Text text_info_line_8{{14 * CHARACTER_WIDTH, 12 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||||
Text text_info_line_9{{14 * CHARACTER_WIDTH, 13 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
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_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}, ""};
|
||||||
};
|
};
|
||||||
|
|
||||||
} /* namespace ui */
|
} /* namespace ui */
|
||||||
|
|||||||
@@ -64,16 +64,16 @@ class EncodersConfigView : public View {
|
|||||||
void on_type_change(size_t index);
|
void on_type_change(size_t index);
|
||||||
|
|
||||||
Labels labels{
|
Labels labels{
|
||||||
{{1 * 8, 0}, "Type:", Color::light_grey()},
|
{{1 * 8, 0}, "Type:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{17 * 8, 0}, "Repeat:", Color::light_grey()},
|
{{17 * 8, 0}, "Repeat:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{1 * 8, 2 * 8}, "Clk:", Color::light_grey()},
|
{{1 * 8, 2 * 8}, "Clk:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{10 * 8, 2 * 8}, "kHz", Color::light_grey()},
|
{{10 * 8, 2 * 8}, "kHz", Theme::getInstance()->fg_light->foreground},
|
||||||
{{17 * 8, 2 * 8}, "Step:", Color::light_grey()},
|
{{17 * 8, 2 * 8}, "Step:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{1 * 8, 4 * 8}, "Frame:", Color::light_grey()},
|
{{1 * 8, 4 * 8}, "Frame:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{13 * 8, 4 * 8}, "us", Color::light_grey()},
|
{{13 * 8, 4 * 8}, "us", Theme::getInstance()->fg_light->foreground},
|
||||||
{{17 * 8, 4 * 8}, "Step:", Color::light_grey()},
|
{{17 * 8, 4 * 8}, "Step:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{2 * 8, 7 * 8}, "Symbols:", Color::light_grey()},
|
{{2 * 8, 7 * 8}, "Symbols:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{1 * 8, 14 * 8}, "Waveform:", Color::light_grey()}};
|
{{1 * 8, 14 * 8}, "Waveform:", Theme::getInstance()->fg_light->foreground}};
|
||||||
|
|
||||||
OptionsField options_enctype{// Options are loaded at runtime
|
OptionsField options_enctype{// Options are loaded at runtime
|
||||||
{6 * 8, 0},
|
{6 * 8, 0},
|
||||||
@@ -128,7 +128,7 @@ class EncodersConfigView : public View {
|
|||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
true,
|
true,
|
||||||
Color::yellow()};
|
Theme::getInstance()->fg_yellow->foreground};
|
||||||
};
|
};
|
||||||
|
|
||||||
class EncodersScanView : public View {
|
class EncodersScanView : public View {
|
||||||
@@ -160,9 +160,9 @@ class EncodersScanView : public View {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
Labels labels{
|
Labels labels{
|
||||||
{{1 * 8, 0 * 8}, "Length:", Color::light_grey()},
|
{{1 * 8, 0 * 8}, "Length:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{1 * 8, 2 * 8}, "Bit length:", Color::light_grey()},
|
{{1 * 8, 2 * 8}, "Bit length:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{16 * 8, 2 * 8}, "us", Color::light_grey()},
|
{{16 * 8, 2 * 8}, "us", Theme::getInstance()->fg_light->foreground},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -206,8 +206,8 @@ class EncodersView : public View {
|
|||||||
EncodersScanView view_scan{nav_, view_rect};
|
EncodersScanView view_scan{nav_, view_rect};
|
||||||
|
|
||||||
TabView tab_view{
|
TabView tab_view{
|
||||||
{"Config", Color::cyan(), &view_config},
|
{"Config", Theme::getInstance()->fg_cyan->foreground, &view_config},
|
||||||
{"de Bruijn", Color::green(), &view_scan},
|
{"de Bruijn", Theme::getInstance()->fg_green->foreground, &view_scan},
|
||||||
};
|
};
|
||||||
|
|
||||||
Text text_status{
|
Text text_status{
|
||||||
|
|||||||
@@ -29,11 +29,13 @@
|
|||||||
#include "ui_playlist.hpp"
|
#include "ui_playlist.hpp"
|
||||||
#include "ui_remote.hpp"
|
#include "ui_remote.hpp"
|
||||||
#include "ui_ss_viewer.hpp"
|
#include "ui_ss_viewer.hpp"
|
||||||
|
#include "ui_bmp_file_viewer.hpp"
|
||||||
#include "ui_text_editor.hpp"
|
#include "ui_text_editor.hpp"
|
||||||
#include "ui_iq_trim.hpp"
|
#include "ui_iq_trim.hpp"
|
||||||
#include "string_format.hpp"
|
#include "string_format.hpp"
|
||||||
#include "portapack.hpp"
|
#include "portapack.hpp"
|
||||||
#include "event_m0.hpp"
|
#include "event_m0.hpp"
|
||||||
|
#include "file_path.hpp"
|
||||||
|
|
||||||
using namespace portapack;
|
using namespace portapack;
|
||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
@@ -62,7 +64,7 @@ std::string truncate(const fs::path& path, size_t max_length) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Inserts the entry into the entry list sorted directories first then by file name.
|
// Inserts the entry into the entry list sorted directories first then by file name.
|
||||||
void insert_sorted(std::vector<fileman_entry>& entries, fileman_entry&& entry) {
|
void insert_sorted(std::list<fileman_entry>& entries, fileman_entry&& entry) {
|
||||||
auto it = std::lower_bound(
|
auto it = std::lower_bound(
|
||||||
std::begin(entries), std::end(entries), entry,
|
std::begin(entries), std::end(entries), entry,
|
||||||
[](const fileman_entry& lhs, const fileman_entry& rhs) {
|
[](const fileman_entry& lhs, const fileman_entry& rhs) {
|
||||||
@@ -146,9 +148,82 @@ namespace ui {
|
|||||||
|
|
||||||
/* FileManBaseView ***********************************************************/
|
/* FileManBaseView ***********************************************************/
|
||||||
|
|
||||||
void FileManBaseView::load_directory_contents(const fs::path& dir_path) {
|
void FileManBaseView::load_directory_contents_unordered(const fs::path& dir_path, size_t file_cnt) {
|
||||||
current_path = dir_path;
|
current_path = dir_path;
|
||||||
entry_list.clear();
|
entry_list.clear();
|
||||||
|
menu_view.clear();
|
||||||
|
auto filtering = !extension_filter.empty();
|
||||||
|
bool cxx_file = path_iequal(cxx_ext, extension_filter);
|
||||||
|
|
||||||
|
text_current.set(dir_path.empty() ? "(sd root)" : truncate(dir_path, 24));
|
||||||
|
|
||||||
|
nb_pages = 1 + (file_cnt / items_per_page);
|
||||||
|
size_t start = pagination * items_per_page;
|
||||||
|
size_t stop = start + items_per_page;
|
||||||
|
if (file_cnt < stop) stop = file_cnt;
|
||||||
|
if (start > file_cnt) start = 0; // shouldn't hapen but check against it won't hurt
|
||||||
|
|
||||||
|
size_t curr = 0;
|
||||||
|
|
||||||
|
for (const auto& entry : fs::directory_iterator(dir_path, u"*")) {
|
||||||
|
if (entry_list.size() >= items_per_page) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Hide files starting with '.' (hidden / tmp).
|
||||||
|
if (!show_hidden_files && is_hidden_file(entry.path()))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (fs::is_regular_file(entry.status())) {
|
||||||
|
if (!filtering || path_iequal(entry.path().extension(), extension_filter) || (cxx_file && is_cxx_capture_file(entry.path()))) {
|
||||||
|
curr++;
|
||||||
|
if (curr >= start) insert_sorted(entry_list, {entry.path().string(), (uint32_t)entry.size(), false});
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (fs::is_directory(entry.status())) {
|
||||||
|
curr++;
|
||||||
|
if (curr >= start) insert_sorted(entry_list, {entry.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 (file_cnt > start + items_per_page) {
|
||||||
|
entry_list.push_back({str_next, (uint32_t)pagination + 1, true});
|
||||||
|
}
|
||||||
|
|
||||||
|
// add prev page
|
||||||
|
if (pagination > 0) {
|
||||||
|
entry_list.insert(entry_list.begin(), {str_back, (uint32_t)pagination - 1, true});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int FileManBaseView::file_count_filtered(const fs::path& directory) {
|
||||||
|
int count{0};
|
||||||
|
auto filtering = !extension_filter.empty();
|
||||||
|
bool cxx_file = path_iequal(cxx_ext, extension_filter);
|
||||||
|
|
||||||
|
for (auto& entry : std::filesystem::directory_iterator(directory, (const TCHAR*)u"*")) {
|
||||||
|
if (fs::is_regular_file(entry.status())) {
|
||||||
|
if (!filtering || path_iequal(entry.path().extension(), extension_filter) || (cxx_file && is_cxx_capture_file(entry.path())))
|
||||||
|
++count;
|
||||||
|
} else
|
||||||
|
++count;
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileManBaseView::load_directory_contents(const fs::path& dir_path) {
|
||||||
|
size_t file_cnt = file_count_filtered(dir_path);
|
||||||
|
if (file_cnt >= max_items_loaded) {
|
||||||
|
load_directory_contents_unordered(dir_path, file_cnt);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
current_path = dir_path;
|
||||||
|
entry_list.clear();
|
||||||
|
menu_view.clear();
|
||||||
auto filtering = !extension_filter.empty();
|
auto filtering = !extension_filter.empty();
|
||||||
bool cxx_file = path_iequal(cxx_ext, extension_filter);
|
bool cxx_file = path_iequal(cxx_ext, extension_filter);
|
||||||
|
|
||||||
@@ -161,19 +236,41 @@ void FileManBaseView::load_directory_contents(const fs::path& dir_path) {
|
|||||||
|
|
||||||
if (fs::is_regular_file(entry.status())) {
|
if (fs::is_regular_file(entry.status())) {
|
||||||
if (!filtering || path_iequal(entry.path().extension(), extension_filter) || (cxx_file && is_cxx_capture_file(entry.path())))
|
if (!filtering || path_iequal(entry.path().extension(), extension_filter) || (cxx_file && is_cxx_capture_file(entry.path())))
|
||||||
insert_sorted(entry_list, {entry.path(), (uint32_t)entry.size(), false});
|
insert_sorted(entry_list, {entry.path().string(), (uint32_t)entry.size(), false});
|
||||||
} else if (fs::is_directory(entry.status())) {
|
} else if (fs::is_directory(entry.status())) {
|
||||||
insert_sorted(entry_list, {entry.path(), 0, true});
|
insert_sorted(entry_list, {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));
|
||||||
|
}
|
||||||
|
|
||||||
// Add "parent" directory if not at the root.
|
// Add "parent" directory if not at the root.
|
||||||
if (!dir_path.empty())
|
if (!dir_path.empty() && pagination == 0)
|
||||||
entry_list.insert(entry_list.begin(), {parent_dir_path, 0, true});
|
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
|
||||||
|
if (pagination > 0) {
|
||||||
|
entry_list.insert(entry_list.begin(), {str_back, (uint32_t)pagination - 1, true});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fs::path FileManBaseView::get_selected_full_path() const {
|
fs::path FileManBaseView::get_selected_full_path() const {
|
||||||
if (get_selected_entry().path == parent_dir_path)
|
if (get_selected_entry().path == parent_dir_path.string())
|
||||||
return current_path.parent_path();
|
return current_path.parent_path();
|
||||||
|
|
||||||
return current_path / get_selected_entry().path;
|
return current_path / get_selected_entry().path;
|
||||||
@@ -181,7 +278,10 @@ fs::path FileManBaseView::get_selected_full_path() const {
|
|||||||
|
|
||||||
const fileman_entry& FileManBaseView::get_selected_entry() const {
|
const fileman_entry& FileManBaseView::get_selected_entry() const {
|
||||||
// TODO: return reference to an "empty" entry on OOB?
|
// TODO: return reference to an "empty" entry on OOB?
|
||||||
return entry_list[menu_view.highlighted_index()];
|
auto it = entry_list.begin();
|
||||||
|
if (menu_view.highlighted_index() >= 1) std::advance(it, menu_view.highlighted_index());
|
||||||
|
return *it;
|
||||||
|
// return entry_list[menu_view.highlighted_index()];
|
||||||
}
|
}
|
||||||
|
|
||||||
FileManBaseView::FileManBaseView(
|
FileManBaseView::FileManBaseView(
|
||||||
@@ -230,7 +330,7 @@ void FileManBaseView::push_dir(const fs::path& path) {
|
|||||||
current_path /= path;
|
current_path /= path;
|
||||||
saved_index_stack.push_back(menu_view.highlighted_index());
|
saved_index_stack.push_back(menu_view.highlighted_index());
|
||||||
menu_view.set_highlighted(0);
|
menu_view.set_highlighted(0);
|
||||||
reload_current();
|
reload_current(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,30 +339,57 @@ void FileManBaseView::pop_dir() {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
current_path = current_path.parent_path();
|
current_path = current_path.parent_path();
|
||||||
reload_current();
|
reload_current(true);
|
||||||
menu_view.set_highlighted(saved_index_stack.back());
|
menu_view.set_highlighted(saved_index_stack.back());
|
||||||
saved_index_stack.pop_back();
|
saved_index_stack.pop_back();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string get_extension(std::string t) {
|
||||||
|
const auto index = t.find_last_of(u'.');
|
||||||
|
if (index == t.npos) {
|
||||||
|
return {};
|
||||||
|
} else {
|
||||||
|
return t.substr(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string get_stem(std::string t) {
|
||||||
|
const auto index = t.find_last_of(u'.');
|
||||||
|
if (index == t.npos) {
|
||||||
|
return t;
|
||||||
|
} else {
|
||||||
|
return t.substr(0, index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::string get_filename(std::string _s) {
|
||||||
|
const auto index = _s.find_last_of("/");
|
||||||
|
if (index == _s.npos) {
|
||||||
|
return _s;
|
||||||
|
} else {
|
||||||
|
return _s.substr(index + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
void FileManBaseView::refresh_list() {
|
void FileManBaseView::refresh_list() {
|
||||||
if (on_refresh_widgets)
|
if (on_refresh_widgets)
|
||||||
on_refresh_widgets(false);
|
on_refresh_widgets(false);
|
||||||
|
|
||||||
auto prev_highlight = menu_view.highlighted_index();
|
prev_highlight = menu_view.highlighted_index();
|
||||||
menu_view.clear();
|
menu_view.clear();
|
||||||
|
|
||||||
for (const auto& entry : entry_list) {
|
for (const auto& entry : entry_list) {
|
||||||
auto entry_name = truncate(entry.path, 20);
|
auto entry_name = std::string{entry.path.length() <= 20 ? entry.path : entry.path.substr(0, 20)};
|
||||||
|
|
||||||
if (entry.is_directory) {
|
if (entry.is_directory) {
|
||||||
auto size_str =
|
std::string size_str{};
|
||||||
(entry.path == parent_dir_path)
|
if (entry.path == str_next || entry.path == str_back) {
|
||||||
? ""
|
size_str = to_string_dec_uint(1 + entry.size) + "/" + to_string_dec_uint(nb_pages); // show computed number of pages
|
||||||
: to_string_dec_uint(file_count(current_path / entry.path));
|
} else {
|
||||||
|
size_str = (entry.path == parent_dir_path.string()) ? "" : to_string_dec_uint(file_count(current_path / entry.path));
|
||||||
|
}
|
||||||
|
|
||||||
menu_view.add_item(
|
menu_view.add_item(
|
||||||
{entry_name + std::string(21 - entry_name.length(), ' ') + size_str,
|
{entry_name.substr(0, max_filename_length) + std::string(21 - entry_name.length(), ' ') + size_str,
|
||||||
ui::Color::yellow(),
|
Theme::getInstance()->fg_yellow->foreground,
|
||||||
&bitmap_icon_dir,
|
&bitmap_icon_dir,
|
||||||
[this](KeyEvent key) {
|
[this](KeyEvent key) {
|
||||||
if (on_select_entry)
|
if (on_select_entry)
|
||||||
@@ -270,11 +397,11 @@ void FileManBaseView::refresh_list() {
|
|||||||
}});
|
}});
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
const auto& assoc = get_assoc(entry.path.extension());
|
const auto& assoc = get_assoc(get_extension(entry.path));
|
||||||
auto size_str = to_string_file_size(entry.size);
|
auto size_str = to_string_file_size(entry.size);
|
||||||
|
|
||||||
menu_view.add_item(
|
menu_view.add_item(
|
||||||
{entry_name + std::string(21 - entry_name.length(), ' ') + size_str,
|
{entry_name.substr(0, max_filename_length) + std::string(21 - entry_name.length(), ' ') + size_str,
|
||||||
assoc.color,
|
assoc.color,
|
||||||
assoc.icon,
|
assoc.icon,
|
||||||
[this](KeyEvent key) {
|
[this](KeyEvent key) {
|
||||||
@@ -282,16 +409,13 @@ void FileManBaseView::refresh_list() {
|
|||||||
on_select_entry(key);
|
on_select_entry(key);
|
||||||
}});
|
}});
|
||||||
}
|
}
|
||||||
|
|
||||||
// HACK: Should page menu items instead of limiting the number.
|
|
||||||
if (menu_view.item_count() >= max_items_shown)
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
menu_view.set_highlighted(prev_highlight);
|
menu_view.set_highlighted(prev_highlight);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FileManBaseView::reload_current() {
|
void FileManBaseView::reload_current(bool reset_pagination) {
|
||||||
|
if (reset_pagination) pagination = 0;
|
||||||
load_directory_contents(current_path);
|
load_directory_contents(current_path);
|
||||||
refresh_list();
|
refresh_list();
|
||||||
}
|
}
|
||||||
@@ -327,6 +451,21 @@ FileLoadView::FileLoadView(
|
|||||||
|
|
||||||
on_select_entry = [this](KeyEvent) {
|
on_select_entry = [this](KeyEvent) {
|
||||||
if (get_selected_entry().is_directory) {
|
if (get_selected_entry().is_directory) {
|
||||||
|
if (get_selected_entry().path == str_full) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (get_selected_entry().path == str_back) {
|
||||||
|
pagination--;
|
||||||
|
menu_view.set_highlighted(0);
|
||||||
|
reload_current(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (get_selected_entry().path == str_next) {
|
||||||
|
pagination++;
|
||||||
|
menu_view.set_highlighted(0);
|
||||||
|
reload_current(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
push_dir(get_selected_entry().path);
|
push_dir(get_selected_entry().path);
|
||||||
} else {
|
} else {
|
||||||
if (on_changed)
|
if (on_changed)
|
||||||
@@ -402,6 +541,7 @@ void FileSaveView::refresh_widgets() {
|
|||||||
void FileManagerView::refresh_widgets(const bool v) {
|
void FileManagerView::refresh_widgets(const bool v) {
|
||||||
button_rename.hidden(v);
|
button_rename.hidden(v);
|
||||||
button_delete.hidden(v);
|
button_delete.hidden(v);
|
||||||
|
button_clean.hidden(v);
|
||||||
button_cut.hidden(v);
|
button_cut.hidden(v);
|
||||||
button_copy.hidden(v);
|
button_copy.hidden(v);
|
||||||
button_paste.hidden(v);
|
button_paste.hidden(v);
|
||||||
@@ -415,10 +555,10 @@ void FileManagerView::on_rename(std::string_view hint) {
|
|||||||
auto& entry = get_selected_entry();
|
auto& entry = get_selected_entry();
|
||||||
|
|
||||||
// Append the hint to the filename stem as a rename suggestion.
|
// Append the hint to the filename stem as a rename suggestion.
|
||||||
name_buffer = entry.path.stem().string();
|
name_buffer = get_stem(entry.path);
|
||||||
if (!hint.empty())
|
if (!hint.empty())
|
||||||
name_buffer += "_" + std::string{hint};
|
name_buffer += "_" + std::string{hint};
|
||||||
name_buffer += entry.path.extension().string();
|
name_buffer += get_extension(entry.path);
|
||||||
|
|
||||||
// Set the rename cursor to before the extension to make renaming simpler.
|
// Set the rename cursor to before the extension to make renaming simpler.
|
||||||
uint32_t cursor_pos = (uint32_t)name_buffer.length();
|
uint32_t cursor_pos = (uint32_t)name_buffer.length();
|
||||||
@@ -439,21 +579,21 @@ void FileManagerView::on_rename(std::string_view hint) {
|
|||||||
auto new_name = renamed_path.replace_extension(partner.extension());
|
auto new_name = renamed_path.replace_extension(partner.extension());
|
||||||
rename_file(partner, current_path / new_name);
|
rename_file(partner, current_path / new_name);
|
||||||
}
|
}
|
||||||
reload_current();
|
reload_current(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!has_partner)
|
if (!has_partner)
|
||||||
reload_current();
|
reload_current(false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void FileManagerView::on_delete() {
|
void FileManagerView::on_delete() {
|
||||||
if (is_directory(get_selected_full_path()) && !is_empty_directory(get_selected_full_path())) {
|
if (is_directory(get_selected_full_path()) && !is_empty_directory(get_selected_full_path())) {
|
||||||
nav_.display_modal("Delete", "Directory not empty!");
|
nav_.display_modal("Delete", " Folder is not empty;\n Use \"Clean\" button to\n empty it first.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto name = get_selected_entry().path.filename().string();
|
auto name = get_filename(get_selected_entry().path);
|
||||||
nav_.push<ModalMessageView>(
|
nav_.push<ModalMessageView>(
|
||||||
"Delete", "Delete " + name + "\nAre you sure?", YESNO,
|
"Delete", "Delete " + name + "\nAre you sure?", YESNO,
|
||||||
[this](bool choice) {
|
[this](bool choice) {
|
||||||
@@ -465,11 +605,36 @@ void FileManagerView::on_delete() {
|
|||||||
[this](const fs::path& partner, bool should_delete) {
|
[this](const fs::path& partner, bool should_delete) {
|
||||||
if (should_delete)
|
if (should_delete)
|
||||||
delete_file(partner);
|
delete_file(partner);
|
||||||
reload_current();
|
reload_current(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!has_partner)
|
if (!has_partner)
|
||||||
reload_current();
|
reload_current(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileManagerView::on_clean() {
|
||||||
|
if (is_empty_directory(get_selected_full_path())) {
|
||||||
|
nav_.display_modal("Clean", "Folder is Empty;\nUse \"Delete\" button instead\nof \"Clean\" button to delete\nit.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto path_name = is_directory(get_selected_full_path()) ? get_selected_full_path() : get_selected_full_path().parent_path();
|
||||||
|
|
||||||
|
// selected either a single file (delete files in this directory) or a directory that is not empty (del sub files)
|
||||||
|
nav_.push<ModalMessageView>(
|
||||||
|
"Clean", " ALL FILES in this folder\n (excluding sub-folders)\n will be DELETED!\n\n Delete all files?", YESNO,
|
||||||
|
[this, path_name](bool choice) {
|
||||||
|
if (choice) {
|
||||||
|
std::vector<std::filesystem::path> file_list;
|
||||||
|
file_list = scan_root_files(path_name, u"*");
|
||||||
|
|
||||||
|
for (const auto& file_name : file_list) {
|
||||||
|
std::filesystem::path current_full_path = path_name / file_name;
|
||||||
|
delete_file(current_full_path);
|
||||||
|
}
|
||||||
|
reload_current(true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -478,7 +643,7 @@ void FileManagerView::on_new_dir() {
|
|||||||
name_buffer = "";
|
name_buffer = "";
|
||||||
text_prompt(nav_, name_buffer, max_filename_length, [this](std::string& dir_name) {
|
text_prompt(nav_, name_buffer, max_filename_length, [this](std::string& dir_name) {
|
||||||
make_new_directory(current_path / dir_name);
|
make_new_directory(current_path / dir_name);
|
||||||
reload_current();
|
reload_current(true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -488,7 +653,10 @@ void FileManagerView::on_paste() {
|
|||||||
fs::filesystem_error result;
|
fs::filesystem_error result;
|
||||||
|
|
||||||
if (clipboard_mode == ClipboardMode::Cut)
|
if (clipboard_mode == ClipboardMode::Cut)
|
||||||
result = rename_file(clipboard_path, current_path / new_name);
|
if ((current_path / clipboard_path.filename()) == clipboard_path)
|
||||||
|
result = FR_OK; // Skip paste to avoid renaming if path is unchanged
|
||||||
|
else
|
||||||
|
result = rename_file(clipboard_path, current_path / new_name);
|
||||||
|
|
||||||
else if (clipboard_mode == ClipboardMode::Copy)
|
else if (clipboard_mode == ClipboardMode::Copy)
|
||||||
result = copy_file(clipboard_path, current_path / new_name);
|
result = copy_file(clipboard_path, current_path / new_name);
|
||||||
@@ -499,14 +667,14 @@ void FileManagerView::on_paste() {
|
|||||||
clipboard_path = fs::path{};
|
clipboard_path = fs::path{};
|
||||||
clipboard_mode = ClipboardMode::None;
|
clipboard_mode = ClipboardMode::None;
|
||||||
menu_view.focus();
|
menu_view.focus();
|
||||||
reload_current();
|
reload_current(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FileManagerView::on_new_file() {
|
void FileManagerView::on_new_file() {
|
||||||
name_buffer = "";
|
name_buffer = "";
|
||||||
text_prompt(nav_, name_buffer, max_filename_length, [this](std::string& file_name) {
|
text_prompt(nav_, name_buffer, max_filename_length, [this](std::string& file_name) {
|
||||||
make_new_file(current_path / file_name);
|
make_new_file(current_path / file_name);
|
||||||
reload_current();
|
reload_current(true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -528,8 +696,13 @@ bool FileManagerView::handle_file_open() {
|
|||||||
nav_.push<ScreenshotViewer>(path);
|
nav_.push<ScreenshotViewer>(path);
|
||||||
return true;
|
return true;
|
||||||
} else if (path_iequal(bmp_ext, ext)) {
|
} else if (path_iequal(bmp_ext, ext)) {
|
||||||
nav_.push<SplashViewer>(path);
|
if (path_iequal(current_path, u"/" + splash_dir)) {
|
||||||
reload_current();
|
nav_.push<SplashViewer>(path); // splash, so load that viewer
|
||||||
|
} else {
|
||||||
|
nav_.push<BMPFileViewer>(path); // any other bmp
|
||||||
|
}
|
||||||
|
|
||||||
|
reload_current(false);
|
||||||
return true;
|
return true;
|
||||||
} else if (path_iequal(rem_ext, ext)) {
|
} else if (path_iequal(rem_ext, ext)) {
|
||||||
nav_.push<RemoteView>(path);
|
nav_.push<RemoteView>(path);
|
||||||
@@ -541,7 +714,7 @@ bool FileManagerView::handle_file_open() {
|
|||||||
|
|
||||||
bool FileManagerView::selected_is_valid() const {
|
bool FileManagerView::selected_is_valid() const {
|
||||||
return !entry_list.empty() &&
|
return !entry_list.empty() &&
|
||||||
get_selected_entry().path != parent_dir_path;
|
get_selected_entry().path != parent_dir_path.string();
|
||||||
}
|
}
|
||||||
|
|
||||||
FileManagerView::FileManagerView(
|
FileManagerView::FileManagerView(
|
||||||
@@ -560,6 +733,7 @@ FileManagerView::FileManagerView(
|
|||||||
&text_date,
|
&text_date,
|
||||||
&button_rename,
|
&button_rename,
|
||||||
&button_delete,
|
&button_delete,
|
||||||
|
&button_clean,
|
||||||
&button_cut,
|
&button_cut,
|
||||||
&button_copy,
|
&button_copy,
|
||||||
&button_paste,
|
&button_paste,
|
||||||
@@ -572,11 +746,11 @@ FileManagerView::FileManagerView(
|
|||||||
});
|
});
|
||||||
|
|
||||||
menu_view.on_highlight = [this]() {
|
menu_view.on_highlight = [this]() {
|
||||||
if (menu_view.highlighted_index() >= max_items_shown - 1) {
|
if (menu_view.highlighted_index() >= max_items_loaded - 1) { // todo check this if correct
|
||||||
text_date.set_style(&Styles::red);
|
text_date.set_style(Theme::getInstance()->fg_red);
|
||||||
text_date.set("Too many files!");
|
text_date.set("Too many files!");
|
||||||
} else {
|
} else {
|
||||||
text_date.set_style(&Styles::grey);
|
text_date.set_style(Theme::getInstance()->fg_medium);
|
||||||
if (selected_is_valid())
|
if (selected_is_valid())
|
||||||
text_date.set((is_directory(get_selected_full_path()) ? "Created " : "Modified ") + to_string_FAT_timestamp(file_created_date(get_selected_full_path())));
|
text_date.set((is_directory(get_selected_full_path()) ? "Created " : "Modified ") + to_string_FAT_timestamp(file_created_date(get_selected_full_path())));
|
||||||
else
|
else
|
||||||
@@ -587,13 +761,30 @@ FileManagerView::FileManagerView(
|
|||||||
refresh_list();
|
refresh_list();
|
||||||
|
|
||||||
on_select_entry = [this](KeyEvent key) {
|
on_select_entry = [this](KeyEvent key) {
|
||||||
if (key == KeyEvent::Select && get_selected_entry().is_directory) {
|
if (key == KeyEvent::Select) {
|
||||||
push_dir(get_selected_entry().path);
|
if (get_selected_entry().is_directory) {
|
||||||
} else if (key == KeyEvent::Select && handle_file_open()) {
|
if (get_selected_entry().path == str_full) {
|
||||||
return;
|
return;
|
||||||
} else {
|
}
|
||||||
button_rename.focus();
|
if (get_selected_entry().path == str_back) {
|
||||||
|
pagination--;
|
||||||
|
menu_view.set_highlighted(0);
|
||||||
|
reload_current(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (get_selected_entry().path == str_next) {
|
||||||
|
pagination++;
|
||||||
|
menu_view.set_highlighted(0);
|
||||||
|
reload_current(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
push_dir(get_selected_entry().path);
|
||||||
|
return;
|
||||||
|
} else if (handle_file_open()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
button_rename.focus();
|
||||||
};
|
};
|
||||||
|
|
||||||
button_rename.on_select = [this]() {
|
button_rename.on_select = [this]() {
|
||||||
@@ -606,8 +797,13 @@ FileManagerView::FileManagerView(
|
|||||||
on_delete();
|
on_delete();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
button_clean.on_select = [this]() {
|
||||||
|
if (selected_is_valid())
|
||||||
|
on_clean();
|
||||||
|
};
|
||||||
|
|
||||||
button_cut.on_select = [this]() {
|
button_cut.on_select = [this]() {
|
||||||
if (selected_is_valid() && !get_selected_entry().is_directory) {
|
if (selected_is_valid()) {
|
||||||
clipboard_path = get_selected_full_path();
|
clipboard_path = get_selected_full_path();
|
||||||
clipboard_mode = ClipboardMode::Cut;
|
clipboard_mode = ClipboardMode::Cut;
|
||||||
} else
|
} else
|
||||||
@@ -626,7 +822,7 @@ FileManagerView::FileManagerView(
|
|||||||
if (clipboard_mode != ClipboardMode::None)
|
if (clipboard_mode != ClipboardMode::None)
|
||||||
on_paste();
|
on_paste();
|
||||||
else
|
else
|
||||||
nav_.display_modal("Paste", "Cut or copy a file first.");
|
nav_.display_modal("Paste", " Cut or copy a file,\n or cut a folder, first.");
|
||||||
};
|
};
|
||||||
|
|
||||||
button_new_dir.on_select = [this]() {
|
button_new_dir.on_select = [this]() {
|
||||||
@@ -646,7 +842,7 @@ FileManagerView::FileManagerView(
|
|||||||
};
|
};
|
||||||
|
|
||||||
button_rename_timestamp.on_select = [this]() {
|
button_rename_timestamp.on_select = [this]() {
|
||||||
if (selected_is_valid() && !get_selected_entry().is_directory) {
|
if (selected_is_valid()) {
|
||||||
on_rename(::truncate(to_string_timestamp(rtc_time::now()), 8));
|
on_rename(::truncate(to_string_timestamp(rtc_time::now()), 8));
|
||||||
} else
|
} else
|
||||||
nav_.display_modal("Timestamp Rename", "Can't rename that.");
|
nav_.display_modal("Timestamp Rename", "Can't rename that.");
|
||||||
@@ -662,7 +858,7 @@ FileManagerView::FileManagerView(
|
|||||||
|
|
||||||
button_show_hidden_files.on_select = [this]() {
|
button_show_hidden_files.on_select = [this]() {
|
||||||
show_hidden_files = !show_hidden_files;
|
show_hidden_files = !show_hidden_files;
|
||||||
button_show_hidden_files.set_color(show_hidden_files ? Color::green() : Color::dark_grey());
|
button_show_hidden_files.set_color(show_hidden_files ? *Theme::getInstance()->status_active : Theme::getInstance()->bg_dark->background);
|
||||||
reload_current();
|
reload_current();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
namespace ui {
|
namespace ui {
|
||||||
|
|
||||||
struct fileman_entry {
|
struct fileman_entry {
|
||||||
std::filesystem::path path{};
|
std::string path{};
|
||||||
uint32_t size{};
|
uint32_t size{};
|
||||||
bool is_directory{};
|
bool is_directory{};
|
||||||
};
|
};
|
||||||
@@ -61,8 +61,12 @@ class FileManBaseView : public View {
|
|||||||
void push_dir(const std::filesystem::path& path);
|
void push_dir(const std::filesystem::path& path);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
static constexpr size_t max_filename_length = 64;
|
uint32_t prev_highlight = 0;
|
||||||
static constexpr size_t max_items_shown = 100;
|
uint8_t pagination = 0;
|
||||||
|
uint8_t nb_pages = 1;
|
||||||
|
static constexpr 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;
|
||||||
|
|
||||||
struct file_assoc_t {
|
struct file_assoc_t {
|
||||||
std::filesystem::path extension;
|
std::filesystem::path extension;
|
||||||
@@ -77,18 +81,20 @@ class FileManBaseView : public View {
|
|||||||
{u".C8", &bitmap_icon_file_iq, ui::Color::dark_cyan()},
|
{u".C8", &bitmap_icon_file_iq, ui::Color::dark_cyan()},
|
||||||
{u".C16", &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".WAV", &bitmap_icon_file_wav, ui::Color::dark_magenta()},
|
||||||
{u".PPL", &bitmap_icon_file_iq, ui::Color::white()}, // Playlist/Replay
|
{u".PPL", &bitmap_icon_file_iq, ui::Color::white()}, // Playlist/Replay
|
||||||
{u".REM", &bitmap_icon_remote, ui::Color::orange()}, // Remote
|
{u".REM", &bitmap_icon_remote, ui::Color::orange()}, // Remote
|
||||||
{u"", &bitmap_icon_file, ui::Color::light_grey()} // NB: Must be last.
|
{u"", &bitmap_icon_file, Theme::getInstance()->fg_light->foreground} // NB: Must be last.
|
||||||
};
|
};
|
||||||
|
|
||||||
std::filesystem::path get_selected_full_path() const;
|
std::filesystem::path get_selected_full_path() const;
|
||||||
const fileman_entry& get_selected_entry() const;
|
const fileman_entry& get_selected_entry() const;
|
||||||
|
|
||||||
|
int file_count_filtered(const std::filesystem::path& directory);
|
||||||
void pop_dir();
|
void pop_dir();
|
||||||
void refresh_list();
|
void refresh_list();
|
||||||
void reload_current();
|
void reload_current(bool reset_pagination = false);
|
||||||
void load_directory_contents(const std::filesystem::path& dir_path);
|
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;
|
const file_assoc_t& get_assoc(const std::filesystem::path& ext) const;
|
||||||
|
|
||||||
NavigationView& nav_;
|
NavigationView& nav_;
|
||||||
@@ -97,17 +103,20 @@ class FileManBaseView : public View {
|
|||||||
std::function<void(KeyEvent)> on_select_entry{nullptr};
|
std::function<void(KeyEvent)> on_select_entry{nullptr};
|
||||||
std::function<void(bool)> on_refresh_widgets{nullptr};
|
std::function<void(bool)> on_refresh_widgets{nullptr};
|
||||||
|
|
||||||
|
const std::string str_back{"<--"};
|
||||||
|
const std::string str_next{"-->"};
|
||||||
|
const std::string str_full{"Can't load more.."};
|
||||||
const std::filesystem::path parent_dir_path{u".."};
|
const std::filesystem::path parent_dir_path{u".."};
|
||||||
std::filesystem::path current_path{u""};
|
std::filesystem::path current_path{u""};
|
||||||
std::filesystem::path extension_filter{u""};
|
std::filesystem::path extension_filter{u""};
|
||||||
|
|
||||||
std::vector<fileman_entry> entry_list{};
|
std::list<fileman_entry> entry_list{};
|
||||||
std::vector<uint32_t> saved_index_stack{};
|
std::vector<uint32_t> saved_index_stack{};
|
||||||
|
|
||||||
bool show_hidden_files{false};
|
bool show_hidden_files{false};
|
||||||
|
|
||||||
Labels labels{
|
Labels labels{
|
||||||
{{0, 0}, "Path:", Color::light_grey()}};
|
{{0, 0}, "Path:", Theme::getInstance()->fg_light->foreground}};
|
||||||
|
|
||||||
Text text_current{
|
Text text_current{
|
||||||
{6 * 8, 0 * 8, 24 * 8, 16},
|
{6 * 8, 0 * 8, 24 * 8, 16},
|
||||||
@@ -157,8 +166,8 @@ private:
|
|||||||
std::string buffer_ { };
|
std::string buffer_ { };
|
||||||
|
|
||||||
Labels labels {
|
Labels labels {
|
||||||
{ { 0 * 8, 1 * 16 }, "Path:", Color::light_grey() },
|
{ { 0 * 8, 1 * 16 }, "Path:", Theme::getInstance()->fg_light->foreground },
|
||||||
{ { 0 * 8, 6 * 16 }, "Filename:", Color::light_grey() },
|
{ { 0 * 8, 6 * 16 }, "Filename:",Theme::getInstance()->fg_light->foreground },
|
||||||
};
|
};
|
||||||
|
|
||||||
Text text_path {
|
Text text_path {
|
||||||
@@ -207,6 +216,7 @@ class FileManagerView : public FileManBaseView {
|
|||||||
void refresh_widgets(const bool v);
|
void refresh_widgets(const bool v);
|
||||||
void on_rename(std::string_view hint);
|
void on_rename(std::string_view hint);
|
||||||
void on_delete();
|
void on_delete();
|
||||||
|
void on_clean();
|
||||||
void on_paste();
|
void on_paste();
|
||||||
void on_new_dir();
|
void on_new_dir();
|
||||||
void on_new_file();
|
void on_new_file();
|
||||||
@@ -224,68 +234,76 @@ class FileManagerView : public FileManBaseView {
|
|||||||
{0 * 8, 29 * 8, 4 * 8, 32},
|
{0 * 8, 29 * 8, 4 * 8, 32},
|
||||||
{},
|
{},
|
||||||
&bitmap_icon_rename,
|
&bitmap_icon_rename,
|
||||||
Color::dark_blue()};
|
Theme::getInstance()->fg_blue->foreground};
|
||||||
|
|
||||||
NewButton button_delete{
|
NewButton button_delete{
|
||||||
{4 * 8, 29 * 8, 4 * 8, 32},
|
{9 * 8, 34 * 8, 4 * 8, 32},
|
||||||
{},
|
{},
|
||||||
&bitmap_icon_trash,
|
&bitmap_icon_trash,
|
||||||
Color::red()};
|
Theme::getInstance()->fg_red->foreground};
|
||||||
|
|
||||||
|
NewButton button_clean{
|
||||||
|
{13 * 8, 34 * 8, 4 * 8, 32},
|
||||||
|
{},
|
||||||
|
&bitmap_icon_clean,
|
||||||
|
Theme::getInstance()->fg_red->foreground};
|
||||||
|
|
||||||
NewButton button_cut{
|
NewButton button_cut{
|
||||||
{9 * 8, 29 * 8, 4 * 8, 32},
|
{9 * 8, 29 * 8, 4 * 8, 32},
|
||||||
{},
|
{},
|
||||||
&bitmap_icon_cut,
|
&bitmap_icon_cut,
|
||||||
Color::dark_grey()};
|
Theme::getInstance()->fg_dark->foreground};
|
||||||
|
|
||||||
NewButton button_copy{
|
NewButton button_copy{
|
||||||
{13 * 8, 29 * 8, 4 * 8, 32},
|
{13 * 8, 29 * 8, 4 * 8, 32},
|
||||||
{},
|
{},
|
||||||
&bitmap_icon_copy,
|
&bitmap_icon_copy,
|
||||||
Color::dark_grey()};
|
Theme::getInstance()->fg_dark->foreground};
|
||||||
|
|
||||||
NewButton button_paste{
|
NewButton button_paste{
|
||||||
{17 * 8, 29 * 8, 4 * 8, 32},
|
{17 * 8, 29 * 8, 4 * 8, 32},
|
||||||
{},
|
{},
|
||||||
&bitmap_icon_paste,
|
&bitmap_icon_paste,
|
||||||
Color::dark_grey()};
|
Theme::getInstance()->fg_dark->foreground};
|
||||||
|
|
||||||
NewButton button_new_dir{
|
NewButton button_new_dir{
|
||||||
{22 * 8, 29 * 8, 4 * 8, 32},
|
{22 * 8, 29 * 8, 4 * 8, 32},
|
||||||
{},
|
{},
|
||||||
&bitmap_icon_new_dir,
|
&bitmap_icon_new_dir,
|
||||||
Color::green()};
|
Theme::getInstance()->fg_green->foreground};
|
||||||
|
|
||||||
NewButton button_new_file{
|
NewButton button_new_file{
|
||||||
{26 * 8, 29 * 8, 4 * 8, 32},
|
{26 * 8, 29 * 8, 4 * 8, 32},
|
||||||
{},
|
{},
|
||||||
&bitmap_icon_new_file,
|
&bitmap_icon_new_file,
|
||||||
Color::green()};
|
Theme::getInstance()->fg_green->foreground};
|
||||||
|
|
||||||
NewButton button_open_notepad{
|
NewButton button_open_notepad{
|
||||||
{0 * 8, 34 * 8, 4 * 8, 32},
|
{0 * 8, 34 * 8, 4 * 8, 32},
|
||||||
{},
|
{},
|
||||||
&bitmap_icon_notepad,
|
&bitmap_icon_notepad,
|
||||||
Color::orange()};
|
Theme::getInstance()->fg_orange->foreground};
|
||||||
|
|
||||||
NewButton button_rename_timestamp{
|
NewButton button_rename_timestamp{
|
||||||
{4 * 8, 34 * 8, 4 * 8, 32},
|
|
||||||
|
{4 * 8, 29 * 8, 4 * 8, 32},
|
||||||
{},
|
{},
|
||||||
&bitmap_icon_options_datetime,
|
&bitmap_icon_options_datetime,
|
||||||
Color::orange(),
|
Theme::getInstance()->fg_blue->foreground,
|
||||||
/*vcenter*/ true};
|
/*vcenter*/ true};
|
||||||
|
|
||||||
NewButton button_open_iq_trim{
|
NewButton button_open_iq_trim{
|
||||||
{9 * 8, 34 * 8, 4 * 8, 32},
|
|
||||||
|
{4 * 8, 34 * 8, 4 * 8, 32},
|
||||||
{},
|
{},
|
||||||
&bitmap_icon_trim,
|
&bitmap_icon_trim,
|
||||||
Color::orange()};
|
Theme::getInstance()->fg_orange->foreground};
|
||||||
|
|
||||||
NewButton button_show_hidden_files{
|
NewButton button_show_hidden_files{
|
||||||
{13 * 8, 34 * 8, 4 * 8, 32},
|
{17 * 8, 34 * 8, 4 * 8, 32},
|
||||||
{},
|
{},
|
||||||
&bitmap_icon_hide,
|
&bitmap_icon_hide,
|
||||||
Color::dark_grey()};
|
Theme::getInstance()->fg_dark->foreground};
|
||||||
};
|
};
|
||||||
|
|
||||||
} /* namespace ui */
|
} /* namespace ui */
|
||||||
|
|||||||
@@ -22,14 +22,59 @@
|
|||||||
|
|
||||||
#include "ui_flash_utility.hpp"
|
#include "ui_flash_utility.hpp"
|
||||||
#include "portapack_shared_memory.hpp"
|
#include "portapack_shared_memory.hpp"
|
||||||
|
#include "file_path.hpp"
|
||||||
|
|
||||||
namespace ui {
|
namespace ui {
|
||||||
|
|
||||||
static const char16_t* firmware_folder = u"/FIRMWARE";
|
// Firmware image validation
|
||||||
|
static const char* hackrf_magic = "HACKRFFW";
|
||||||
|
#define FIRMWARE_INFO_AREA_OFFSET 0x400
|
||||||
|
#define FIRST_CHECKSUM_NIGHTLY 240125
|
||||||
|
|
||||||
Thread* FlashUtilityView::thread{nullptr};
|
Thread* FlashUtilityView::thread{nullptr};
|
||||||
static constexpr size_t max_filename_length = 26;
|
static constexpr size_t max_filename_length = 26;
|
||||||
|
|
||||||
|
bool valid_firmware_file(std::filesystem::path::string_type path) {
|
||||||
|
File firmware_file;
|
||||||
|
bool require_checksum{false};
|
||||||
|
uint32_t read_buffer[128];
|
||||||
|
uint32_t checksum{FLASH_CHECKSUM_ERROR}; // initializing to invalid checksum in case file can't be read
|
||||||
|
|
||||||
|
static_assert((FIRMWARE_INFO_AREA_OFFSET % sizeof(read_buffer)) == 0, "Read buffer size must divide evenly into FIRMWARE_INFO_AREA_OFFSET");
|
||||||
|
|
||||||
|
// 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()) {
|
||||||
|
checksum = 0;
|
||||||
|
for (uint32_t offset = 0; offset < FLASH_ROM_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).
|
||||||
|
checksum = (require_checksum) ? FLASH_CHECKSUM_ERROR : FLASH_EXPECTED_CHECKSUM;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Did we just read the firmware info area?
|
||||||
|
if (offset == FIRMWARE_INFO_AREA_OFFSET) {
|
||||||
|
// If there's no info area (missing HACKRFFW signature), it could be an ancient FW version (so skipping check)
|
||||||
|
if (memcmp(read_buffer, hackrf_magic, 8) == 0) {
|
||||||
|
char* version_string = (char*)&read_buffer[4];
|
||||||
|
|
||||||
|
// Require a 1MB firmware image with a valid checksum if release version >=v2.x or nightly >n_240125
|
||||||
|
if (((version_string[0] == 'v') && (std::atoi(&version_string[1]) >= 2)) ||
|
||||||
|
((version_string[0] == 'n') && (version_string[1] == '_') && (std::atoi(&version_string[2]) >= FIRST_CHECKSUM_NIGHTLY)))
|
||||||
|
require_checksum = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checksum += simple_checksum((uint32_t)read_buffer, sizeof(read_buffer));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (checksum == FLASH_EXPECTED_CHECKSUM);
|
||||||
|
}
|
||||||
|
|
||||||
FlashUtilityView::FlashUtilityView(NavigationView& nav)
|
FlashUtilityView::FlashUtilityView(NavigationView& nav)
|
||||||
: nav_(nav) {
|
: nav_(nav) {
|
||||||
add_children({&labels,
|
add_children({&labels,
|
||||||
@@ -37,19 +82,30 @@ FlashUtilityView::FlashUtilityView(NavigationView& nav)
|
|||||||
|
|
||||||
menu_view.set_parent_rect({0, 3 * 8, 240, 33 * 8});
|
menu_view.set_parent_rect({0, 3 * 8, 240, 33 * 8});
|
||||||
|
|
||||||
ensure_directory(firmware_folder);
|
ensure_directory(apps_dir);
|
||||||
|
ensure_directory(firmware_dir);
|
||||||
|
|
||||||
for (const auto& entry : std::filesystem::directory_iterator(firmware_folder, u"*.bin")) {
|
auto add_firmware_items = [&](
|
||||||
auto filename = entry.path().filename();
|
const std::filesystem::path& folder_path,
|
||||||
auto path = entry.path().native();
|
const std::filesystem::path& wild,
|
||||||
|
ui::Color color) {
|
||||||
|
for (const auto& entry : std::filesystem::directory_iterator(folder_path, wild)) {
|
||||||
|
auto filename = entry.path().filename();
|
||||||
|
auto path = entry.path().native();
|
||||||
|
|
||||||
menu_view.add_item({filename.string().substr(0, max_filename_length),
|
menu_view.add_item({filename.string().substr(0, max_filename_length),
|
||||||
ui::Color::red(),
|
color,
|
||||||
&bitmap_icon_temperature,
|
&bitmap_icon_temperature,
|
||||||
[this, path](KeyEvent) {
|
[this, path](KeyEvent) {
|
||||||
this->firmware_selected(path);
|
this->firmware_selected(path);
|
||||||
}});
|
}});
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
add_firmware_items(firmware_dir, u"*.bin", ui::Theme::getInstance()->fg_red->foreground);
|
||||||
|
add_firmware_items(firmware_dir, u"*.tar", ui::Theme::getInstance()->fg_cyan->foreground);
|
||||||
|
|
||||||
|
// add_firmware_items(user_firmware_folder,u"*.bin", ui::Theme::getInstance()->fg_cyan->foreground);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FlashUtilityView::firmware_selected(std::filesystem::path::string_type path) {
|
void FlashUtilityView::firmware_selected(std::filesystem::path::string_type path) {
|
||||||
@@ -59,30 +115,66 @@ void FlashUtilityView::firmware_selected(std::filesystem::path::string_type path
|
|||||||
YESNO,
|
YESNO,
|
||||||
[this, path](bool choice) {
|
[this, path](bool choice) {
|
||||||
if (choice) {
|
if (choice) {
|
||||||
std::u16string full_path = std::u16string(u"FIRMWARE/") + path;
|
std::filesystem::path::string_type full_path = firmware_dir.native() + u"/" + path;
|
||||||
this->flash_firmware(full_path);
|
this->flash_firmware(full_path);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void FlashUtilityView::flash_firmware(std::filesystem::path::string_type path) {
|
bool FlashUtilityView::endsWith(const std::u16string& str, const std::u16string& suffix) {
|
||||||
ui::Painter painter;
|
if (str.length() >= suffix.length()) {
|
||||||
|
std::u16string endOfString = str.substr(str.length() - suffix.length());
|
||||||
|
return endOfString == suffix;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::filesystem::path FlashUtilityView::extract_tar(std::filesystem::path::string_type path, ui::Painter& painter) {
|
||||||
|
//
|
||||||
painter.fill_rectangle(
|
painter.fill_rectangle(
|
||||||
{0, 0, portapack::display.width(), portapack::display.height()},
|
{0, 0, portapack::display.width(), portapack::display.height()},
|
||||||
ui::Color::black());
|
Theme::getInstance()->bg_darkest->background);
|
||||||
|
painter.draw_string({12, 24}, this->nav_.style(), "Unpacking TAR file...");
|
||||||
|
|
||||||
|
auto res = UnTar::untar(path, [this](const std::string fileName) {
|
||||||
|
ui::Painter painter;
|
||||||
|
painter.fill_rectangle({0, 50, portapack::display.width(), 90}, Theme::getInstance()->bg_darkest->background);
|
||||||
|
painter.draw_string({0, 60}, this->nav_.style(), fileName);
|
||||||
|
});
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FlashUtilityView::flash_firmware(std::filesystem::path::string_type path) {
|
||||||
|
ui::Painter painter;
|
||||||
|
if (endsWith(path, u".tar")) {
|
||||||
|
// extract, then update
|
||||||
|
path = extract_tar(u'/' + path, painter).native();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (path.empty() || !valid_firmware_file(path.c_str())) {
|
||||||
|
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);
|
||||||
|
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 seconds.");
|
||||||
painter.draw_string({12, 64}, this->nav_.style(), "Please wait while LEDs RX");
|
painter.draw_string({12, 64}, this->nav_.style(), "Please wait while LED RX");
|
||||||
painter.draw_string({12, 84}, this->nav_.style(), "and TX are flashing.");
|
painter.draw_string({12, 84}, this->nav_.style(), "is on and TX is flashing.");
|
||||||
painter.draw_string({12, 124}, this->nav_.style(), "Then restart the device.");
|
painter.draw_string({12, 124}, this->nav_.style(), "Device will then restart.");
|
||||||
|
|
||||||
std::memcpy(&shared_memory.bb_data.data[0], path.c_str(), (path.length() + 1) * 2);
|
std::memcpy(&shared_memory.bb_data.data[0], path.c_str(), (path.length() + 1) * 2);
|
||||||
m4_init(portapack::spi_flash::image_tag_flash_utility, portapack::memory::map::m4_code, false);
|
m4_init(portapack::spi_flash::image_tag_flash_utility, portapack::memory::map::m4_code, false);
|
||||||
m0_halt();
|
m0_halt();
|
||||||
|
return true; // fixes compiler warning (line should not be reached due to halt)
|
||||||
}
|
}
|
||||||
|
|
||||||
void FlashUtilityView::focus() {
|
void FlashUtilityView::focus() {
|
||||||
menu_view.focus();
|
menu_view.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
} /* namespace ui */
|
} /* namespace ui */
|
||||||
@@ -29,11 +29,18 @@
|
|||||||
#include "ff.h"
|
#include "ff.h"
|
||||||
#include "baseband_api.hpp"
|
#include "baseband_api.hpp"
|
||||||
#include "core_control.hpp"
|
#include "core_control.hpp"
|
||||||
|
#include "untar.hpp"
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
|
#define FLASH_ROM_SIZE 1048576
|
||||||
|
#define FLASH_STARTING_ADDRESS 0x00000000
|
||||||
|
#define FLASH_EXPECTED_CHECKSUM 0x00000000
|
||||||
|
#define FLASH_CHECKSUM_ERROR 0xFFFFFFFF
|
||||||
|
|
||||||
namespace ui {
|
namespace ui {
|
||||||
|
|
||||||
|
bool valid_firmware_file(std::filesystem::path::string_type path);
|
||||||
|
|
||||||
class FlashUtilityView : public View {
|
class FlashUtilityView : public View {
|
||||||
public:
|
public:
|
||||||
FlashUtilityView(NavigationView& nav);
|
FlashUtilityView(NavigationView& nav);
|
||||||
@@ -41,6 +48,7 @@ class FlashUtilityView : public View {
|
|||||||
void focus() override;
|
void focus() override;
|
||||||
|
|
||||||
std::string title() const override { return "Flash Utility"; };
|
std::string title() const override { return "Flash Utility"; };
|
||||||
|
bool flash_firmware(std::filesystem::path::string_type path);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
NavigationView& nav_;
|
NavigationView& nav_;
|
||||||
@@ -49,14 +57,16 @@ class FlashUtilityView : public View {
|
|||||||
static Thread* thread;
|
static Thread* thread;
|
||||||
|
|
||||||
Labels labels{
|
Labels labels{
|
||||||
{{4, 4}, "Select firmware to flash:", Color::white()}};
|
{{4, 4}, "Select firmware to flash:", Theme::getInstance()->bg_darkest->foreground}};
|
||||||
|
|
||||||
MenuView menu_view{
|
MenuView menu_view{
|
||||||
{0, 2 * 8, 240, 26 * 8},
|
{0, 2 * 8, 240, 26 * 8},
|
||||||
true};
|
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
|
||||||
void firmware_selected(std::filesystem::path::string_type path);
|
void firmware_selected(std::filesystem::path::string_type path);
|
||||||
void flash_firmware(std::filesystem::path::string_type path);
|
|
||||||
|
bool endsWith(const std::u16string& str, const std::u16string& suffix);
|
||||||
};
|
};
|
||||||
|
|
||||||
} /* namespace ui */
|
} /* namespace ui */
|
||||||
|
|||||||
@@ -29,8 +29,8 @@
|
|||||||
#include "rtc_time.hpp"
|
#include "rtc_time.hpp"
|
||||||
#include "tone_key.hpp"
|
#include "tone_key.hpp"
|
||||||
#include "ui_receiver.hpp"
|
#include "ui_receiver.hpp"
|
||||||
#include "ui_styles.hpp"
|
|
||||||
#include "utility.hpp"
|
#include "utility.hpp"
|
||||||
|
#include "file_path.hpp"
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
@@ -228,7 +228,7 @@ void FrequencyManagerView::on_edit_freq() {
|
|||||||
|
|
||||||
void FrequencyManagerView::on_edit_desc() {
|
void FrequencyManagerView::on_edit_desc() {
|
||||||
temp_buffer_ = current_entry().description;
|
temp_buffer_ = current_entry().description;
|
||||||
text_prompt(nav_, temp_buffer_, desc_edit_max, [this](std::string& new_desc) {
|
text_prompt(nav_, temp_buffer_, freqman_max_desc_size, [this](std::string& new_desc) {
|
||||||
auto entry = current_entry();
|
auto entry = current_entry();
|
||||||
entry.description = std::move(new_desc);
|
entry.description = std::move(new_desc);
|
||||||
db_.replace_entry(current_index(), entry);
|
db_.replace_entry(current_index(), entry);
|
||||||
@@ -412,18 +412,19 @@ void FrequencyEditView::refresh_ui() {
|
|||||||
|
|
||||||
auto is_range = entry_.type == freqman_type::Range;
|
auto is_range = entry_.type == freqman_type::Range;
|
||||||
auto is_ham = entry_.type == freqman_type::HamRadio;
|
auto is_ham = entry_.type == freqman_type::HamRadio;
|
||||||
auto has_freq_b = is_range || is_ham;
|
auto is_repeater = entry_.type == freqman_type::Repeater;
|
||||||
|
auto has_freq_b = is_range || is_ham || is_repeater;
|
||||||
|
|
||||||
field_freq_b.set_style(has_freq_b ? &Styles::white : &Styles::grey);
|
field_freq_b.set_style(has_freq_b ? Theme::getInstance()->bg_darkest : Theme::getInstance()->fg_medium);
|
||||||
field_step.set_style(is_range ? &Styles::white : &Styles::grey);
|
field_step.set_style(is_range ? Theme::getInstance()->bg_darkest : Theme::getInstance()->fg_medium);
|
||||||
field_tone.set_style(is_ham ? &Styles::white : &Styles::grey);
|
field_tone.set_style(is_ham ? Theme::getInstance()->bg_darkest : Theme::getInstance()->fg_medium);
|
||||||
|
|
||||||
if (is_valid(entry_)) {
|
if (is_valid(entry_)) {
|
||||||
text_validation.set("Valid");
|
text_validation.set("Valid");
|
||||||
text_validation.set_style(&Styles::green);
|
text_validation.set_style(Theme::getInstance()->fg_green);
|
||||||
} else {
|
} else {
|
||||||
text_validation.set("Error");
|
text_validation.set("Error");
|
||||||
text_validation.set_style(&Styles::red);
|
text_validation.set_style(Theme::getInstance()->fg_red);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,8 +40,6 @@ class FreqManBaseView : public View {
|
|||||||
|
|
||||||
void focus() override;
|
void focus() override;
|
||||||
|
|
||||||
static constexpr size_t desc_edit_max = 0x80;
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
using options_t = OptionsField::options_t;
|
using options_t = OptionsField::options_t;
|
||||||
|
|
||||||
@@ -63,11 +61,11 @@ class FreqManBaseView : public View {
|
|||||||
|
|
||||||
/* The top section (category) is 20px tall. */
|
/* The top section (category) is 20px tall. */
|
||||||
Labels label_category{
|
Labels label_category{
|
||||||
{{0, 2}, "Category:", Color::light_grey()}};
|
{{0, 2}, "F:", Theme::getInstance()->fg_light->foreground}};
|
||||||
|
|
||||||
OptionsField options_category{
|
OptionsField options_category{
|
||||||
{9 * 8, 2},
|
{3 * 8, 2},
|
||||||
14 /* length */,
|
20 /* length */,
|
||||||
{}};
|
{}};
|
||||||
|
|
||||||
FreqManUIList freqlist_view{
|
FreqManUIList freqlist_view{
|
||||||
@@ -99,7 +97,7 @@ class FrequencySaveView : public FreqManBaseView {
|
|||||||
0};
|
0};
|
||||||
|
|
||||||
Labels labels{
|
Labels labels{
|
||||||
{{0 * 8, 6 * 16}, "Description:", Color::white()}};
|
{{0 * 8, 6 * 16}, "Description:", Theme::getInstance()->bg_darkest->foreground}};
|
||||||
|
|
||||||
TextField field_description{
|
TextField field_description{
|
||||||
{0 * 8, 7 * 16, 30 * 8, 1 * 16},
|
{0 * 8, 7 * 16, 30 * 8, 1 * 16},
|
||||||
@@ -139,14 +137,14 @@ class FrequencyManagerView : public FreqManBaseView {
|
|||||||
{23 * 8, 0 * 16, 7 * 4, 20},
|
{23 * 8, 0 * 16, 7 * 4, 20},
|
||||||
{},
|
{},
|
||||||
&bitmap_icon_new_file,
|
&bitmap_icon_new_file,
|
||||||
Color::white(),
|
Theme::getInstance()->bg_darkest->foreground,
|
||||||
true};
|
true};
|
||||||
|
|
||||||
NewButton button_del_category{
|
NewButton button_del_category{
|
||||||
{26 * 8 + 4, 0 * 16, 7 * 4, 20},
|
{26 * 8 + 4, 0 * 16, 7 * 4, 20},
|
||||||
{},
|
{},
|
||||||
&bitmap_icon_trash,
|
&bitmap_icon_trash,
|
||||||
Color::red(),
|
Theme::getInstance()->fg_red->foreground,
|
||||||
true};
|
true};
|
||||||
|
|
||||||
Button button_edit_entry{
|
Button button_edit_entry{
|
||||||
@@ -155,7 +153,7 @@ class FrequencyManagerView : public FreqManBaseView {
|
|||||||
|
|
||||||
Rectangle rect_padding{
|
Rectangle rect_padding{
|
||||||
{15 * 8, 14 * 16 - 4, 15 * 8, 1 * 16 + 4},
|
{15 * 8, 14 * 16 - 4, 15 * 8, 1 * 16 + 4},
|
||||||
Color::grey()};
|
Theme::getInstance()->fg_medium->background};
|
||||||
|
|
||||||
Button button_edit_freq{
|
Button button_edit_freq{
|
||||||
{0 * 8, 15 * 16, 15 * 8, 2 * 16},
|
{0 * 8, 15 * 16, 15 * 8, 2 * 16},
|
||||||
@@ -169,14 +167,14 @@ class FrequencyManagerView : public FreqManBaseView {
|
|||||||
{15 * 8, 15 * 16, 7 * 8 + 4, 2 * 16},
|
{15 * 8, 15 * 16, 7 * 8 + 4, 2 * 16},
|
||||||
{},
|
{},
|
||||||
&bitmap_icon_add,
|
&bitmap_icon_add,
|
||||||
Color::white(),
|
Theme::getInstance()->bg_darkest->foreground,
|
||||||
true};
|
true};
|
||||||
|
|
||||||
NewButton button_del_entry{
|
NewButton button_del_entry{
|
||||||
{22 * 8 + 4, 15 * 16, 7 * 8 + 4, 2 * 16},
|
{22 * 8 + 4, 15 * 16, 7 * 8 + 4, 2 * 16},
|
||||||
{},
|
{},
|
||||||
&bitmap_icon_delete,
|
&bitmap_icon_delete,
|
||||||
Color::red(),
|
Theme::getInstance()->fg_red->foreground,
|
||||||
true};
|
true};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -202,15 +200,15 @@ class FrequencyEditView : public View {
|
|||||||
void populate_tone_options();
|
void populate_tone_options();
|
||||||
|
|
||||||
Labels labels{
|
Labels labels{
|
||||||
{{5 * 8, 1 * 16}, "Edit Frequency Entry", Color::white()},
|
{{5 * 8, 1 * 16}, "Edit Frequency Entry", Theme::getInstance()->bg_darkest->foreground},
|
||||||
{{0 * 8, 3 * 16}, "Entry Type :", Color::light_grey()},
|
{{0 * 8, 3 * 16}, "Entry Type :", Theme::getInstance()->fg_light->foreground},
|
||||||
{{0 * 8, 4 * 16}, "Frequency A:", Color::light_grey()},
|
{{0 * 8, 4 * 16}, "Frequency A:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{0 * 8, 5 * 16}, "Frequency B:", Color::light_grey()},
|
{{0 * 8, 5 * 16}, "Frequency B:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{0 * 8, 6 * 16}, "Modulation :", Color::light_grey()},
|
{{0 * 8, 6 * 16}, "Modulation :", Theme::getInstance()->fg_light->foreground},
|
||||||
{{0 * 8, 7 * 16}, "Bandwidth :", Color::light_grey()},
|
{{0 * 8, 7 * 16}, "Bandwidth :", Theme::getInstance()->fg_light->foreground},
|
||||||
{{0 * 8, 8 * 16}, "Step :", Color::light_grey()},
|
{{0 * 8, 8 * 16}, "Step :", Theme::getInstance()->fg_light->foreground},
|
||||||
{{0 * 8, 9 * 16}, "Tone Freq :", Color::light_grey()},
|
{{0 * 8, 9 * 16}, "Tone Freq :", Theme::getInstance()->fg_light->foreground},
|
||||||
{{0 * 8, 10 * 16}, "Description:", Color::light_grey()},
|
{{0 * 8, 10 * 16}, "Description:", Theme::getInstance()->fg_light->foreground},
|
||||||
};
|
};
|
||||||
|
|
||||||
OptionsField field_type{
|
OptionsField field_type{
|
||||||
@@ -220,7 +218,8 @@ class FrequencyEditView : public View {
|
|||||||
{"Single", 0},
|
{"Single", 0},
|
||||||
{"Range", 1},
|
{"Range", 1},
|
||||||
{"HamRadio", 2},
|
{"HamRadio", 2},
|
||||||
{"Raw", 3},
|
{"Repeater", 3},
|
||||||
|
{"Raw", 4},
|
||||||
}};
|
}};
|
||||||
|
|
||||||
FrequencyField field_freq_a{{13 * 8, 4 * 16}};
|
FrequencyField field_freq_a{{13 * 8, 4 * 16}};
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
#include "portapack_persistent_memory.hpp"
|
#include "portapack_persistent_memory.hpp"
|
||||||
#include "string_format.hpp"
|
#include "string_format.hpp"
|
||||||
#include "utility.hpp"
|
#include "utility.hpp"
|
||||||
|
#include "file_path.hpp"
|
||||||
|
|
||||||
#include "ui_freqman.hpp"
|
#include "ui_freqman.hpp"
|
||||||
|
|
||||||
@@ -134,7 +135,7 @@ FskxRxMainView::FskxRxMainView(NavigationView& nav)
|
|||||||
field_frequency.set_value(initial_target_frequency);
|
field_frequency.set_value(initial_target_frequency);
|
||||||
deviation_frequency.set_value(initial_deviation);
|
deviation_frequency.set_value(initial_deviation);
|
||||||
|
|
||||||
logger.append(LOG_ROOT_DIR "/FSKRX.TXT");
|
logger.append(logs_dir / u"FSKRX.TXT");
|
||||||
|
|
||||||
baseband::set_fsk(initial_deviation);
|
baseband::set_fsk(initial_deviation);
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,6 @@
|
|||||||
#include "ui_record_view.hpp"
|
#include "ui_record_view.hpp"
|
||||||
#include "ui_rssi.hpp"
|
#include "ui_rssi.hpp"
|
||||||
#include "ui_spectrum.hpp"
|
#include "ui_spectrum.hpp"
|
||||||
#include "ui_styles.hpp"
|
|
||||||
#include "ui_tabview.hpp"
|
#include "ui_tabview.hpp"
|
||||||
|
|
||||||
#include "app_settings.hpp"
|
#include "app_settings.hpp"
|
||||||
@@ -42,7 +41,7 @@
|
|||||||
|
|
||||||
class FskRxLogger {
|
class FskRxLogger {
|
||||||
public:
|
public:
|
||||||
Optional<File::Error> append(const std::string& filename) {
|
Optional<File::Error> append(const std::filesystem::path& filename) {
|
||||||
return log_file.append(filename);
|
return log_file.append(filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,8 +116,8 @@ class FskxRxMainView : public View {
|
|||||||
FskRxAppConsoleView view_data{nav_, view_rect};
|
FskRxAppConsoleView view_data{nav_, view_rect};
|
||||||
|
|
||||||
TabView tab_view{
|
TabView tab_view{
|
||||||
{"Data", Color::yellow(), &view_data},
|
{"Data", Theme::getInstance()->fg_yellow->foreground, &view_data},
|
||||||
{"Stream", Color::cyan(), &view_stream}};
|
{"Stream", Theme::getInstance()->fg_cyan->foreground, &view_stream}};
|
||||||
|
|
||||||
void refresh_ui(rf::Frequency f);
|
void refresh_ui(rf::Frequency f);
|
||||||
void on_packet(uint32_t value, bool is_data);
|
void on_packet(uint32_t value, bool is_data);
|
||||||
@@ -148,7 +147,7 @@ class FskxRxMainView : public View {
|
|||||||
{19 * 8 - 4, 40, 6 * 8, 4}};
|
{19 * 8 - 4, 40, 6 * 8, 4}};
|
||||||
|
|
||||||
Labels labels{
|
Labels labels{
|
||||||
{{0 * 8, 3 * 16}, "Deviation:", Color::light_grey()},
|
{{0 * 8, 3 * 16}, "Deviation:", Theme::getInstance()->fg_light->foreground},
|
||||||
};
|
};
|
||||||
|
|
||||||
FrequencyField deviation_frequency{
|
FrequencyField deviation_frequency{
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2023 Kyle Reed
|
* Copyright (C) 2023 Kyle Reed
|
||||||
|
* Copyright (C) 2024 Mark Thompson
|
||||||
*
|
*
|
||||||
* This file is part of PortaPack.
|
* This file is part of PortaPack.
|
||||||
*
|
*
|
||||||
@@ -24,6 +25,7 @@
|
|||||||
#include "complex.hpp"
|
#include "complex.hpp"
|
||||||
#include "portapack.hpp"
|
#include "portapack.hpp"
|
||||||
#include "ui_fileman.hpp"
|
#include "ui_fileman.hpp"
|
||||||
|
#include "file_path.hpp"
|
||||||
|
|
||||||
using namespace portapack;
|
using namespace portapack;
|
||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
@@ -46,14 +48,14 @@ IQTrimView::IQTrimView(NavigationView& nav)
|
|||||||
|
|
||||||
field_path.on_select = [this](TextField&) {
|
field_path.on_select = [this](TextField&) {
|
||||||
auto open_view = nav_.push<FileLoadView>(".C*");
|
auto open_view = nav_.push<FileLoadView>(".C*");
|
||||||
open_view->push_dir(u"CAPTURES");
|
open_view->push_dir(captures_dir);
|
||||||
open_view->on_changed = [this](fs::path path) {
|
open_view->on_changed = [this](fs::path path) {
|
||||||
open_file(path);
|
open_file(path);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
text_samples.set_style(&Styles::light_grey);
|
text_samples.set_style(Theme::getInstance()->fg_light);
|
||||||
text_max.set_style(&Styles::light_grey);
|
text_max.set_style(Theme::getInstance()->fg_light);
|
||||||
|
|
||||||
field_start.on_change = [this](int32_t v) {
|
field_start.on_change = [this](int32_t v) {
|
||||||
if (field_end.value() < v)
|
if (field_end.value() < v)
|
||||||
@@ -147,9 +149,9 @@ void IQTrimView::refresh_ui() {
|
|||||||
// show max power in red if amplification is too high, causing clipping
|
// show max power in red if amplification is too high, causing clipping
|
||||||
uint32_t clipping_limit = (fs::capture_file_sample_size(path_) == sizeof(complex8_t)) ? 0x80 : 0x8000;
|
uint32_t clipping_limit = (fs::capture_file_sample_size(path_) == sizeof(complex8_t)) ? 0x80 : 0x8000;
|
||||||
if ((field_amplify.value() * info_->max_iq) > clipping_limit)
|
if ((field_amplify.value() * info_->max_iq) > clipping_limit)
|
||||||
text_max.set_style(&Styles::red);
|
text_max.set_style(Theme::getInstance()->fg_red);
|
||||||
else
|
else
|
||||||
text_max.set_style(&Styles::light_grey);
|
text_max.set_style(Theme::getInstance()->fg_light);
|
||||||
|
|
||||||
set_dirty();
|
set_dirty();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2023 Kyle Reed
|
* Copyright (C) 2023 Kyle Reed
|
||||||
|
* Copyright (C) 2024 Mark Thompson
|
||||||
*
|
*
|
||||||
* This file is part of PortaPack.
|
* This file is part of PortaPack.
|
||||||
*
|
*
|
||||||
@@ -27,7 +28,6 @@
|
|||||||
#include "optional.hpp"
|
#include "optional.hpp"
|
||||||
#include "ui.hpp"
|
#include "ui.hpp"
|
||||||
#include "ui_navigation.hpp"
|
#include "ui_navigation.hpp"
|
||||||
#include "ui_styles.hpp"
|
|
||||||
#include "ui_widget.hpp"
|
#include "ui_widget.hpp"
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
@@ -40,21 +40,21 @@ class TrimProgressUI {
|
|||||||
public:
|
public:
|
||||||
void show_reading() {
|
void show_reading() {
|
||||||
clear();
|
clear();
|
||||||
p.draw_string({6 * 8, 5 * 16}, Styles::yellow, "Reading Capture...");
|
p.draw_string({6 * 8, 5 * 16}, *Theme::getInstance()->fg_yellow, "Reading Capture...");
|
||||||
}
|
}
|
||||||
|
|
||||||
void show_trimming() {
|
void show_trimming() {
|
||||||
clear();
|
clear();
|
||||||
p.draw_string({5 * 8, 5 * 16}, Styles::yellow, "Trimming Capture...");
|
p.draw_string({5 * 8, 5 * 16}, *Theme::getInstance()->fg_yellow, "Trimming Capture...");
|
||||||
}
|
}
|
||||||
|
|
||||||
void show_progress(uint8_t percent) {
|
void show_progress(uint8_t percent) {
|
||||||
auto width = percent * screen_width / 100;
|
auto width = percent * screen_width / 100;
|
||||||
p.draw_hline({0, 6 * 16 + 2}, width, Color::yellow());
|
p.draw_hline({0, 6 * 16 + 2}, width, Theme::getInstance()->fg_yellow->foreground);
|
||||||
}
|
}
|
||||||
|
|
||||||
void clear() {
|
void clear() {
|
||||||
p.fill_rectangle({0 * 8, 4 * 16, screen_width, 3 * 16}, Color::black());
|
p.fill_rectangle({0 * 8, 4 * 16, screen_width, 3 * 16}, Theme::getInstance()->bg_darkest->background);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto get_callback() {
|
auto get_callback() {
|
||||||
@@ -100,15 +100,15 @@ class IQTrimView : public View {
|
|||||||
TrimProgressUI progress_ui{};
|
TrimProgressUI progress_ui{};
|
||||||
|
|
||||||
Labels labels{
|
Labels labels{
|
||||||
{{0 * 8, 0 * 16}, "Capture File:", Color::light_grey()},
|
{{0 * 8, 0 * 16}, "Capture File:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{0 * 8, 6 * 16}, "Start :", Color::light_grey()},
|
{{0 * 8, 6 * 16}, "Start :", Theme::getInstance()->fg_light->foreground},
|
||||||
{{0 * 8, 7 * 16}, "End :", Color::light_grey()},
|
{{0 * 8, 7 * 16}, "End :", Theme::getInstance()->fg_light->foreground},
|
||||||
{{0 * 8, 8 * 16}, "Samples:", Color::light_grey()},
|
{{0 * 8, 8 * 16}, "Samples:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{0 * 8, 9 * 16}, "Max Pwr:", Color::light_grey()},
|
{{0 * 8, 9 * 16}, "Max Pwr:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{0 * 8, 10 * 16}, "Cutoff :", Color::light_grey()},
|
{{0 * 8, 10 * 16}, "Cutoff :", Theme::getInstance()->fg_light->foreground},
|
||||||
{{12 * 8, 10 * 16}, "%", Color::light_grey()},
|
{{12 * 8, 10 * 16}, "%", Theme::getInstance()->fg_light->foreground},
|
||||||
{{0 * 8, 12 * 16}, "Amplify:", Color::light_grey()},
|
{{0 * 8, 12 * 16}, "Amplify:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{10 * 8, 12 * 16}, "x", Color::light_grey()},
|
{{10 * 8, 12 * 16}, "x", Theme::getInstance()->fg_light->foreground},
|
||||||
};
|
};
|
||||||
|
|
||||||
TextField field_path{
|
TextField field_path{
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
#include "baseband_api.hpp"
|
#include "baseband_api.hpp"
|
||||||
#include "file.hpp"
|
#include "file.hpp"
|
||||||
#include "oversample.hpp"
|
#include "oversample.hpp"
|
||||||
|
#include "ui_font_fixed_8x16.hpp"
|
||||||
|
|
||||||
using namespace portapack;
|
using namespace portapack;
|
||||||
using namespace tonekey;
|
using namespace tonekey;
|
||||||
@@ -34,11 +35,37 @@ using portapack::memory::map::backup_ram;
|
|||||||
|
|
||||||
namespace ui {
|
namespace ui {
|
||||||
|
|
||||||
|
// Function to map the value from one range to another
|
||||||
|
int32_t LevelView::map(int32_t value, int32_t fromLow, int32_t fromHigh, int32_t toLow, int32_t toHigh) {
|
||||||
|
return toLow + (value - fromLow) * (toHigh - toLow) / (fromHigh - fromLow);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LevelView::m4_manage_stat_update() {
|
||||||
|
if (audio_mode) {
|
||||||
|
if (radio_mode == WFM_MODULATION || radio_mode == SPEC_MODULATION) {
|
||||||
|
shared_memory.request_m4_performance_counter = 1;
|
||||||
|
} else {
|
||||||
|
shared_memory.request_m4_performance_counter = 2;
|
||||||
|
}
|
||||||
|
if (radio_mode == SPEC_MODULATION) {
|
||||||
|
beep = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
shared_memory.request_m4_performance_counter = 2;
|
||||||
|
if (radio_mode == SPEC_MODULATION) {
|
||||||
|
beep = false;
|
||||||
|
baseband::request_beep_stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void LevelView::focus() {
|
void LevelView::focus() {
|
||||||
button_frequency.focus();
|
button_frequency.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
LevelView::~LevelView() {
|
LevelView::~LevelView() {
|
||||||
|
// reset performance counters request to default
|
||||||
|
shared_memory.request_m4_performance_counter = 0;
|
||||||
receiver_model.disable();
|
receiver_model.disable();
|
||||||
baseband::shutdown();
|
baseband::shutdown();
|
||||||
}
|
}
|
||||||
@@ -58,16 +85,17 @@ LevelView::LevelView(NavigationView& nav)
|
|||||||
&text_ctcss,
|
&text_ctcss,
|
||||||
&freq_stats_rssi,
|
&freq_stats_rssi,
|
||||||
&freq_stats_db,
|
&freq_stats_db,
|
||||||
&audio_mode,
|
&freq_stats_rx,
|
||||||
|
&text_beep_squelch,
|
||||||
|
&field_beep_squelch,
|
||||||
|
&field_audio_mode,
|
||||||
&peak_mode,
|
&peak_mode,
|
||||||
&rssi,
|
&rssi,
|
||||||
&rssi_graph});
|
&rssi_graph});
|
||||||
|
|
||||||
|
// activate vertical bar mode
|
||||||
rssi.set_vertical_rssi(true);
|
rssi.set_vertical_rssi(true);
|
||||||
|
|
||||||
change_mode(NFM_MODULATION); // Start on AM
|
|
||||||
field_mode.set_by_value(NFM_MODULATION); // Reflect the mode into the manual selector
|
|
||||||
|
|
||||||
freq_ = receiver_model.target_frequency();
|
freq_ = receiver_model.target_frequency();
|
||||||
button_frequency.set_text("<" + to_string_short_freq(freq_) + " MHz>");
|
button_frequency.set_text("<" + to_string_short_freq(freq_) + " MHz>");
|
||||||
|
|
||||||
@@ -80,6 +108,11 @@ LevelView::LevelView(NavigationView& nav)
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
field_beep_squelch.set_value(beep_squelch);
|
||||||
|
field_beep_squelch.on_change = [this](int32_t v) {
|
||||||
|
beep_squelch = v;
|
||||||
|
};
|
||||||
|
|
||||||
button_frequency.on_change = [this]() {
|
button_frequency.on_change = [this]() {
|
||||||
int64_t def_step = freqman_entry_get_step_value(step_mode.selected_index());
|
int64_t def_step = freqman_entry_get_step_value(step_mode.selected_index());
|
||||||
freq_ = freq_ + (button_frequency.get_encoder_delta() * def_step);
|
freq_ = freq_ + (button_frequency.get_encoder_delta() * def_step);
|
||||||
@@ -95,17 +128,14 @@ LevelView::LevelView(NavigationView& nav)
|
|||||||
button_frequency.set_text("<" + to_string_short_freq(freq_) + " MHz>");
|
button_frequency.set_text("<" + to_string_short_freq(freq_) + " MHz>");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
freqman_set_modulation_option(field_mode);
|
||||||
field_mode.on_change = [this](size_t, OptionsField::value_t v) {
|
field_mode.on_change = [this](size_t, OptionsField::value_t v) {
|
||||||
if (v != -1) {
|
if (v != -1) {
|
||||||
receiver_model.disable();
|
|
||||||
baseband::shutdown();
|
|
||||||
change_mode(v);
|
change_mode(v);
|
||||||
if (audio_mode.selected_index() != 0) {
|
|
||||||
audio::output::start();
|
|
||||||
}
|
|
||||||
receiver_model.enable();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
field_mode.set_by_value(radio_mode); // Reflect the mode into the manual selector
|
||||||
|
field_bw.set_selected_index(radio_bw);
|
||||||
|
|
||||||
rssi_resolution.on_change = [this](size_t, OptionsField::value_t v) {
|
rssi_resolution.on_change = [this](size_t, OptionsField::value_t v) {
|
||||||
if (v != -1) {
|
if (v != -1) {
|
||||||
@@ -113,15 +143,18 @@ LevelView::LevelView(NavigationView& nav)
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
audio_mode.on_change = [this](size_t, OptionsField::value_t v) {
|
field_audio_mode.on_change = [this](size_t, OptionsField::value_t v) {
|
||||||
|
audio_mode = v;
|
||||||
if (v == 0) {
|
if (v == 0) {
|
||||||
audio::output::stop();
|
audio::output::stop();
|
||||||
} else if (v == 1) {
|
} else if (v == 1) {
|
||||||
|
audio::set_rate(audio_sampling_rate);
|
||||||
audio::output::start();
|
audio::output::start();
|
||||||
receiver_model.set_headphone_volume(receiver_model.headphone_volume()); // WM8731 hack.
|
receiver_model.set_headphone_volume(receiver_model.headphone_volume()); // WM8731 hack.
|
||||||
} else {
|
|
||||||
}
|
}
|
||||||
|
m4_manage_stat_update(); // rx_sat hack
|
||||||
};
|
};
|
||||||
|
field_audio_mode.set_selected_index(audio_mode);
|
||||||
|
|
||||||
peak_mode.on_change = [this](size_t, OptionsField::value_t v) {
|
peak_mode.on_change = [this](size_t, OptionsField::value_t v) {
|
||||||
if (v == 0) {
|
if (v == 0) {
|
||||||
@@ -135,17 +168,18 @@ LevelView::LevelView(NavigationView& nav)
|
|||||||
peak_mode.set_selected_index(2);
|
peak_mode.set_selected_index(2);
|
||||||
rssi_resolution.set_selected_index(1);
|
rssi_resolution.set_selected_index(1);
|
||||||
// FILL STEP OPTIONS
|
// FILL STEP OPTIONS
|
||||||
freqman_set_modulation_option(field_mode);
|
|
||||||
freqman_set_step_option_short(step_mode);
|
freqman_set_step_option_short(step_mode);
|
||||||
freq_stats_rssi.set_style(&Styles::white);
|
freq_stats_rssi.set_style(Theme::getInstance()->bg_darkest);
|
||||||
freq_stats_db.set_style(&Styles::white);
|
freq_stats_db.set_style(Theme::getInstance()->bg_darkest);
|
||||||
|
freq_stats_rx.set_style(Theme::getInstance()->bg_darkest);
|
||||||
}
|
}
|
||||||
|
|
||||||
void LevelView::on_statistics_update(const ChannelStatistics& statistics) {
|
void LevelView::on_statistics_update(const ChannelStatistics& statistics) {
|
||||||
static int16_t last_max_db = -1000;
|
static int16_t last_max_db = 0;
|
||||||
static int16_t last_min_rssi = -1000;
|
static uint8_t last_min_rssi = 0;
|
||||||
static int16_t last_avg_rssi = -1000;
|
static uint8_t last_avg_rssi = 0;
|
||||||
static int16_t last_max_rssi = -1000;
|
static uint8_t last_max_rssi = 0;
|
||||||
|
static uint8_t last_rx_sat = 0;
|
||||||
|
|
||||||
rssi_graph.add_values(rssi.get_min(), rssi.get_avg(), rssi.get_max(), statistics.max_db);
|
rssi_graph.add_values(rssi.get_min(), rssi.get_avg(), rssi.get_max(), statistics.max_db);
|
||||||
|
|
||||||
@@ -159,51 +193,99 @@ void LevelView::on_statistics_update(const ChannelStatistics& statistics) {
|
|||||||
last_min_rssi = rssi_graph.get_graph_min();
|
last_min_rssi = rssi_graph.get_graph_min();
|
||||||
last_avg_rssi = rssi_graph.get_graph_avg();
|
last_avg_rssi = rssi_graph.get_graph_avg();
|
||||||
last_max_rssi = rssi_graph.get_graph_max();
|
last_max_rssi = rssi_graph.get_graph_max();
|
||||||
freq_stats_rssi.set("RSSI: " + to_string_dec_int(last_min_rssi) + "/" + to_string_dec_int(last_avg_rssi) + "/" + to_string_dec_int(last_max_rssi) + ",dt: " + to_string_dec_int(rssi_graph.get_graph_delta()));
|
freq_stats_rssi.set("RSSI: " + to_string_dec_uint(last_min_rssi) + "/" + to_string_dec_uint(last_avg_rssi) + "/" + to_string_dec_uint(last_max_rssi));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (beep && statistics.max_db > beep_squelch) {
|
||||||
|
baseband::request_audio_beep(map(statistics.max_db, -100, 20, 400, 2600), 24000, 150);
|
||||||
|
}
|
||||||
|
|
||||||
|
// refresh sat
|
||||||
|
if (radio_mode == SPEC_MODULATION || (radio_mode == WFM_MODULATION && audio_mode == 1)) {
|
||||||
|
Style style_freq_stats_rx{
|
||||||
|
.font = font::fixed_8x16,
|
||||||
|
.background = {55, 55, 55},
|
||||||
|
.foreground = {155, 155, 155},
|
||||||
|
};
|
||||||
|
freq_stats_rx.set_style(&style_freq_stats_rx);
|
||||||
|
freq_stats_rx.set("RxSat off");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uint8_t rx_sat = ((uint32_t)shared_memory.m4_performance_counter) * 100 / 127;
|
||||||
|
if (last_rx_sat != rx_sat) {
|
||||||
|
last_rx_sat = rx_sat;
|
||||||
|
|
||||||
|
uint8_t br = 0;
|
||||||
|
uint8_t bg = 0;
|
||||||
|
uint8_t bb = 0;
|
||||||
|
if (rx_sat <= 80) {
|
||||||
|
bg = (255 * rx_sat) / 80;
|
||||||
|
bb = 255 - bg;
|
||||||
|
} else if (rx_sat > 80) {
|
||||||
|
br = (255 * (rx_sat - 80)) / 20;
|
||||||
|
bg = 255 - br;
|
||||||
|
}
|
||||||
|
Style style_freq_stats_rx{
|
||||||
|
.font = font::fixed_8x16,
|
||||||
|
.background = {br, bg, bb},
|
||||||
|
.foreground = {255, 255, 255},
|
||||||
|
};
|
||||||
|
freq_stats_rx.set_style(&style_freq_stats_rx);
|
||||||
|
freq_stats_rx.set("RxSat: " + to_string_dec_uint(rx_sat) + "%");
|
||||||
|
}
|
||||||
|
|
||||||
} /* on_statistic_updates */
|
} /* on_statistic_updates */
|
||||||
|
|
||||||
size_t LevelView::change_mode(freqman_index_t new_mod) {
|
size_t LevelView::change_mode(freqman_index_t new_mod) {
|
||||||
field_bw.on_change = [this](size_t n, OptionsField::value_t) { (void)n; };
|
field_bw.on_change = [this](size_t n, OptionsField::value_t) { (void)n; };
|
||||||
|
|
||||||
|
radio_mode = new_mod;
|
||||||
|
|
||||||
|
audio::output::stop();
|
||||||
|
receiver_model.disable();
|
||||||
|
baseband::shutdown();
|
||||||
|
|
||||||
switch (new_mod) {
|
switch (new_mod) {
|
||||||
case AM_MODULATION:
|
case AM_MODULATION:
|
||||||
|
audio_sampling_rate = audio::Rate::Hz_12000;
|
||||||
freqman_set_bandwidth_option(new_mod, field_bw);
|
freqman_set_bandwidth_option(new_mod, field_bw);
|
||||||
baseband::run_image(portapack::spi_flash::image_tag_am_audio);
|
baseband::run_image(portapack::spi_flash::image_tag_am_audio);
|
||||||
receiver_model.set_modulation(ReceiverModel::Mode::AMAudio);
|
receiver_model.set_modulation(ReceiverModel::Mode::AMAudio);
|
||||||
receiver_model.set_am_configuration(field_bw.selected_index_value());
|
|
||||||
field_bw.on_change = [this](size_t, OptionsField::value_t n) { receiver_model.set_am_configuration(n); };
|
|
||||||
// bw DSB (0) default
|
// bw DSB (0) default
|
||||||
field_bw.set_by_value(0);
|
field_bw.set_by_value(0);
|
||||||
text_ctcss.set(" ");
|
receiver_model.set_am_configuration(0);
|
||||||
|
field_bw.on_change = [this](size_t index, OptionsField::value_t n) { radio_bw = index ; receiver_model.set_am_configuration(n); };
|
||||||
break;
|
break;
|
||||||
case NFM_MODULATION:
|
case NFM_MODULATION:
|
||||||
|
audio_sampling_rate = audio::Rate::Hz_24000;
|
||||||
freqman_set_bandwidth_option(new_mod, field_bw);
|
freqman_set_bandwidth_option(new_mod, field_bw);
|
||||||
baseband::run_image(portapack::spi_flash::image_tag_nfm_audio);
|
baseband::run_image(portapack::spi_flash::image_tag_nfm_audio);
|
||||||
receiver_model.set_modulation(ReceiverModel::Mode::NarrowbandFMAudio);
|
receiver_model.set_modulation(ReceiverModel::Mode::NarrowbandFMAudio);
|
||||||
receiver_model.set_nbfm_configuration(field_bw.selected_index_value());
|
receiver_model.set_nbfm_configuration(field_bw.selected_index_value());
|
||||||
field_bw.on_change = [this](size_t, OptionsField::value_t n) { receiver_model.set_nbfm_configuration(n); };
|
|
||||||
// bw 16k (2) default
|
// bw 16k (2) default
|
||||||
field_bw.set_by_value(2);
|
field_bw.set_by_value(2);
|
||||||
|
field_bw.on_change = [this](size_t index, OptionsField::value_t n) { radio_bw = index ; receiver_model.set_nbfm_configuration(n); };
|
||||||
break;
|
break;
|
||||||
case WFM_MODULATION:
|
case WFM_MODULATION:
|
||||||
|
audio_sampling_rate = audio::Rate::Hz_48000;
|
||||||
freqman_set_bandwidth_option(new_mod, field_bw);
|
freqman_set_bandwidth_option(new_mod, field_bw);
|
||||||
baseband::run_image(portapack::spi_flash::image_tag_wfm_audio);
|
baseband::run_image(portapack::spi_flash::image_tag_wfm_audio);
|
||||||
receiver_model.set_modulation(ReceiverModel::Mode::WidebandFMAudio);
|
receiver_model.set_modulation(ReceiverModel::Mode::WidebandFMAudio);
|
||||||
receiver_model.set_wfm_configuration(field_bw.selected_index_value());
|
receiver_model.set_wfm_configuration(field_bw.selected_index_value());
|
||||||
field_bw.on_change = [this](size_t, OptionsField::value_t n) { receiver_model.set_wfm_configuration(n); };
|
// bw 200k (0) default
|
||||||
// bw 200k (0) only/default
|
|
||||||
field_bw.set_by_value(0);
|
field_bw.set_by_value(0);
|
||||||
text_ctcss.set(" ");
|
field_bw.on_change = [this](size_t index, OptionsField::value_t n) { radio_bw = index ; receiver_model.set_wfm_configuration(n); };
|
||||||
break;
|
break;
|
||||||
case SPEC_MODULATION:
|
case SPEC_MODULATION:
|
||||||
|
audio_sampling_rate = audio::Rate::Hz_24000;
|
||||||
freqman_set_bandwidth_option(new_mod, field_bw);
|
freqman_set_bandwidth_option(new_mod, field_bw);
|
||||||
baseband::run_image(portapack::spi_flash::image_tag_capture);
|
baseband::run_image(portapack::spi_flash::image_tag_capture);
|
||||||
receiver_model.set_modulation(ReceiverModel::Mode::Capture);
|
receiver_model.set_modulation(ReceiverModel::Mode::Capture);
|
||||||
field_bw.on_change = [this](size_t, OptionsField::value_t sampling_rate) {
|
// 12k5 (0) default
|
||||||
|
field_bw.on_change = [this](size_t index, OptionsField::value_t sampling_rate) {
|
||||||
|
radio_bw = index;
|
||||||
// Baseband needs to know the desired sampling and oversampling rates.
|
// Baseband needs to know the desired sampling and oversampling rates.
|
||||||
baseband::set_sample_rate(sampling_rate, get_oversample_rate(sampling_rate));
|
baseband::set_sample_rate(sampling_rate, get_oversample_rate(sampling_rate));
|
||||||
|
|
||||||
// The radio needs to know the effective sampling rate.
|
// The radio needs to know the effective sampling rate.
|
||||||
auto actual_sampling_rate = get_actual_sample_rate(sampling_rate);
|
auto actual_sampling_rate = get_actual_sample_rate(sampling_rate);
|
||||||
receiver_model.set_sampling_rate(actual_sampling_rate);
|
receiver_model.set_sampling_rate(actual_sampling_rate);
|
||||||
@@ -218,6 +300,18 @@ size_t LevelView::change_mode(freqman_index_t new_mod) {
|
|||||||
receiver_model.set_sampling_rate(3072000);
|
receiver_model.set_sampling_rate(3072000);
|
||||||
receiver_model.set_baseband_bandwidth(1750000);
|
receiver_model.set_baseband_bandwidth(1750000);
|
||||||
}
|
}
|
||||||
|
if (new_mod != NFM_MODULATION) {
|
||||||
|
text_ctcss.set(" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
m4_manage_stat_update(); // rx_sat hack
|
||||||
|
|
||||||
|
if (audio_mode) {
|
||||||
|
audio::set_rate(audio_sampling_rate);
|
||||||
|
audio::output::start();
|
||||||
|
receiver_model.set_headphone_volume(receiver_model.headphone_volume()); // WM8731 hack.
|
||||||
|
}
|
||||||
|
receiver_model.enable();
|
||||||
|
|
||||||
return step_mode.selected_index();
|
return step_mode.selected_index();
|
||||||
}
|
}
|
||||||
@@ -229,4 +323,9 @@ void LevelView::handle_coded_squelch(const uint32_t value) {
|
|||||||
text_ctcss.set(" ");
|
text_ctcss.set(" ");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void LevelView::on_freqchg(int64_t freq) {
|
||||||
|
receiver_model.set_target_frequency(freq);
|
||||||
|
button_frequency.set_text("<" + to_string_short_freq(freq) + " MHz>");
|
||||||
|
}
|
||||||
|
|
||||||
} /* namespace ui */
|
} /* namespace ui */
|
||||||
|
|||||||
@@ -38,7 +38,6 @@
|
|||||||
#include "ui_mictx.hpp"
|
#include "ui_mictx.hpp"
|
||||||
#include "ui_receiver.hpp"
|
#include "ui_receiver.hpp"
|
||||||
#include "ui_spectrum.hpp"
|
#include "ui_spectrum.hpp"
|
||||||
#include "ui_styles.hpp"
|
|
||||||
|
|
||||||
namespace ui {
|
namespace ui {
|
||||||
|
|
||||||
@@ -55,20 +54,34 @@ class LevelView : public View {
|
|||||||
NavigationView& nav_;
|
NavigationView& nav_;
|
||||||
|
|
||||||
RxRadioState radio_state_{};
|
RxRadioState radio_state_{};
|
||||||
app_settings::SettingsManager settings_{
|
|
||||||
"rx_level", app_settings::Mode::RX};
|
|
||||||
|
|
||||||
|
int32_t map(int32_t value, int32_t fromLow, int32_t fromHigh, int32_t toLow, int32_t toHigh);
|
||||||
size_t change_mode(freqman_index_t mod_type);
|
size_t change_mode(freqman_index_t mod_type);
|
||||||
void on_statistics_update(const ChannelStatistics& statistics);
|
void on_statistics_update(const ChannelStatistics& statistics);
|
||||||
void set_display_freq(int64_t freq);
|
void set_display_freq(int64_t freq);
|
||||||
|
void m4_manage_stat_update(); // to finely adjust the RxSaturation usage
|
||||||
|
|
||||||
// TODO: needed?
|
|
||||||
int32_t db{0};
|
|
||||||
rf::Frequency freq_ = {0};
|
rf::Frequency freq_ = {0};
|
||||||
|
bool beep = false;
|
||||||
|
uint8_t radio_mode = 0;
|
||||||
|
uint8_t radio_bw = 0;
|
||||||
|
uint8_t audio_mode = 0;
|
||||||
|
int32_t beep_squelch = 0;
|
||||||
|
audio::Rate audio_sampling_rate = audio::Rate::Hz_48000;
|
||||||
|
|
||||||
|
app_settings::SettingsManager settings_{
|
||||||
|
"rx_level",
|
||||||
|
app_settings::Mode::RX,
|
||||||
|
{
|
||||||
|
{"beep_squelch"sv, &beep_squelch},
|
||||||
|
{"audio_mode"sv, &audio_mode},
|
||||||
|
{"radio_mode"sv, &radio_mode},
|
||||||
|
{"radio_bw"sv, &radio_bw},
|
||||||
|
}};
|
||||||
|
|
||||||
Labels labels{
|
Labels labels{
|
||||||
{{0 * 8, 0 * 16}, "LNA: VGA: AMP: VOL: ", Color::light_grey()},
|
{{0 * 8, 0 * 16}, "LNA: VGA: AMP: VOL: ", Theme::getInstance()->fg_light->foreground},
|
||||||
{{0 * 8, 1 * 16}, "BW: MODE: S: ", Color::light_grey()},
|
{{0 * 8, 1 * 16}, "BW: MODE: S: ", Theme::getInstance()->fg_light->foreground},
|
||||||
};
|
};
|
||||||
|
|
||||||
LNAGainField field_lna{
|
LNAGainField field_lna{
|
||||||
@@ -102,28 +115,32 @@ class LevelView : public View {
|
|||||||
{0 * 8, 2 * 16 + 8, 15 * 8, 1 * 8},
|
{0 * 8, 2 * 16 + 8, 15 * 8, 1 * 8},
|
||||||
""};
|
""};
|
||||||
|
|
||||||
OptionsField audio_mode{
|
OptionsField field_audio_mode{
|
||||||
{21 * 8, 1 * 16},
|
{21 * 8, 1 * 16},
|
||||||
9,
|
9,
|
||||||
{
|
{{"audio off", 0},
|
||||||
{"audio off", 0},
|
{"audio on", 1}}};
|
||||||
{"audio on", 1}
|
|
||||||
//{"tone on", 2},
|
|
||||||
//{"tone off", 3},
|
|
||||||
}};
|
|
||||||
|
|
||||||
Text text_ctcss{
|
Text text_beep_squelch{
|
||||||
{22 * 8, 3 * 16 + 4, 8 * 8, 1 * 8},
|
{21 * 8, 3 * 16 + 4, 4 * 8, 1 * 8},
|
||||||
""};
|
"Bip>"};
|
||||||
|
|
||||||
// RSSI: XX/XX/XXX,dt: XX
|
NumberField field_beep_squelch{
|
||||||
|
{25 * 8, 3 * 16 + 4},
|
||||||
|
4,
|
||||||
|
{-100, 20},
|
||||||
|
1,
|
||||||
|
' ',
|
||||||
|
};
|
||||||
|
|
||||||
|
// RSSI: XX/XX/XXX
|
||||||
Text freq_stats_rssi{
|
Text freq_stats_rssi{
|
||||||
{0 * 8, 3 * 16 + 4, 22 * 8, 14},
|
{0 * 8, 3 * 16 + 4, 15 * 8, 1 * 16},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Power: -XXX db
|
// Power: -XXX db
|
||||||
Text freq_stats_db{
|
Text freq_stats_db{
|
||||||
{0 * 8, 4 * 16 + 4, 14 * 8, 14},
|
{0 * 8, 4 * 16 + 4, 15 * 8, 1 * 16},
|
||||||
};
|
};
|
||||||
|
|
||||||
OptionsField peak_mode{
|
OptionsField peak_mode{
|
||||||
@@ -138,6 +155,7 @@ class LevelView : public View {
|
|||||||
{"peak:5s", 5000},
|
{"peak:5s", 5000},
|
||||||
{"peak:10s", 10000},
|
{"peak:10s", 10000},
|
||||||
}};
|
}};
|
||||||
|
|
||||||
OptionsField rssi_resolution{
|
OptionsField rssi_resolution{
|
||||||
{44 + 20 * 8, 4 * 16 + 4},
|
{44 + 20 * 8, 4 * 16 + 4},
|
||||||
4,
|
4,
|
||||||
@@ -149,18 +167,36 @@ class LevelView : public View {
|
|||||||
{"240x", 240},
|
{"240x", 240},
|
||||||
}};
|
}};
|
||||||
|
|
||||||
|
// RxSat: XX%
|
||||||
|
Text freq_stats_rx{
|
||||||
|
{0 * 8, 5 * 16 + 4, 10 * 8, 1 * 16},
|
||||||
|
};
|
||||||
|
|
||||||
|
Text text_ctcss{
|
||||||
|
{12 * 8, 5 * 16 + 4, 8 * 8, 1 * 8},
|
||||||
|
""};
|
||||||
|
|
||||||
RSSIGraph rssi_graph{
|
RSSIGraph rssi_graph{
|
||||||
// 240x320 =>
|
// 240x320 =>
|
||||||
{0, 5 * 16 + 4, 240 - 5 * 8, 320 - (5 * 16 + 4)},
|
{0, 6 * 16 + 8, 240 - 5 * 8, 320 - (6 * 16)},
|
||||||
};
|
};
|
||||||
|
|
||||||
RSSI rssi{
|
RSSI rssi{
|
||||||
// 240x320 =>
|
// 240x320 =>
|
||||||
{240 - 5 * 8, 5 * 16 + 4, 5 * 8, 320 - (5 * 16 + 4)},
|
{240 - 5 * 8, 6 * 16 + 8, 5 * 8, 320 - (6 * 16)},
|
||||||
};
|
};
|
||||||
|
|
||||||
void handle_coded_squelch(const uint32_t value);
|
void handle_coded_squelch(const uint32_t value);
|
||||||
|
|
||||||
|
void on_freqchg(int64_t freq);
|
||||||
|
|
||||||
|
MessageHandlerRegistration message_handler_freqchg{
|
||||||
|
Message::ID::FreqChangeCommand,
|
||||||
|
[this](Message* const p) {
|
||||||
|
const auto message = static_cast<const FreqChangeCommandMessage*>(p);
|
||||||
|
this->on_freqchg(message->freq);
|
||||||
|
}};
|
||||||
|
|
||||||
MessageHandlerRegistration message_handler_coded_squelch{
|
MessageHandlerRegistration message_handler_coded_squelch{
|
||||||
Message::ID::CodedSquelch,
|
Message::ID::CodedSquelch,
|
||||||
[this](const Message* const p) {
|
[this](const Message* const p) {
|
||||||
|
|||||||
@@ -25,6 +25,8 @@
|
|||||||
#include "convert.hpp"
|
#include "convert.hpp"
|
||||||
#include "file_reader.hpp"
|
#include "file_reader.hpp"
|
||||||
#include "string_format.hpp"
|
#include "string_format.hpp"
|
||||||
|
#include "audio.hpp"
|
||||||
|
#include "file_path.hpp"
|
||||||
|
|
||||||
using namespace portapack;
|
using namespace portapack;
|
||||||
|
|
||||||
@@ -34,11 +36,39 @@ void GlassView::focus() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
GlassView::~GlassView() {
|
GlassView::~GlassView() {
|
||||||
|
audio::output::stop();
|
||||||
receiver_model.set_sampling_rate(3072000); // Just a hack to avoid hanging other apps
|
receiver_model.set_sampling_rate(3072000); // Just a hack to avoid hanging other apps
|
||||||
receiver_model.disable();
|
receiver_model.disable();
|
||||||
baseband::shutdown();
|
baseband::shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GlassView::update_display_beep() {
|
||||||
|
if (beep_enabled) {
|
||||||
|
button_beep_squelch.set_style(Theme::getInstance()->fg_green);
|
||||||
|
// bip-XXdb
|
||||||
|
button_beep_squelch.set_text("bip" + to_string_dec_int(beep_squelch, 3) + "db");
|
||||||
|
receiver_model.set_headphone_volume(receiver_model.headphone_volume()); // WM8731 hack.
|
||||||
|
} else {
|
||||||
|
button_beep_squelch.set_style(Theme::getInstance()->bg_darkest);
|
||||||
|
button_beep_squelch.set_text("bip OFF ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GlassView::manage_beep_audio() {
|
||||||
|
if (beep_enabled) {
|
||||||
|
audio::set_rate(audio::Rate::Hz_24000);
|
||||||
|
audio::output::start();
|
||||||
|
} else {
|
||||||
|
baseband::request_beep_stop();
|
||||||
|
audio::output::stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void GlassView::get_max_power(const ChannelSpectrum& spectrum, uint8_t bin, uint8_t& max_power) {
|
void GlassView::get_max_power(const ChannelSpectrum& spectrum, uint8_t bin, uint8_t& max_power) {
|
||||||
if (mode == LOOKING_GLASS_SINGLEPASS) {
|
if (mode == LOOKING_GLASS_SINGLEPASS) {
|
||||||
// <20MHz spectrum mode
|
// <20MHz spectrum mode
|
||||||
@@ -173,6 +203,8 @@ void GlassView::on_channel_spectrum(const ChannelSpectrum& spectrum) {
|
|||||||
// we actually need SCREEN_W (240) of those bins
|
// we actually need SCREEN_W (240) of those bins
|
||||||
for (uint8_t bin = 0; bin < bin_length; bin++) {
|
for (uint8_t bin = 0; bin < bin_length; bin++) {
|
||||||
get_max_power(spectrum, bin, max_power);
|
get_max_power(spectrum, bin, max_power);
|
||||||
|
if (max_power > range_max_power)
|
||||||
|
range_max_power = max_power;
|
||||||
// process dc spike if enable
|
// process dc spike if enable
|
||||||
if (bin == 119) {
|
if (bin == 119) {
|
||||||
uint8_t next_max_power = 0;
|
uint8_t next_max_power = 0;
|
||||||
@@ -184,14 +216,21 @@ void GlassView::on_channel_spectrum(const ChannelSpectrum& spectrum) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// process actual bin
|
// process actual bin
|
||||||
if (process_bins(&max_power) == true)
|
if (process_bins(&max_power)) {
|
||||||
|
int8_t power = map(range_max_power, 0, 255, -100, 20);
|
||||||
|
if (power >= beep_squelch) {
|
||||||
|
baseband::request_audio_beep(map(range_max_power, 0, 256, 400, 2600), 24000, 250);
|
||||||
|
}
|
||||||
|
range_max_power = 0;
|
||||||
return; // new line signaled, return
|
return; // new line signaled, return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (mode != LOOKING_GLASS_SINGLEPASS) {
|
if (mode != LOOKING_GLASS_SINGLEPASS) {
|
||||||
f_center += looking_glass_step;
|
f_center += looking_glass_step;
|
||||||
retune();
|
retune();
|
||||||
} else
|
} else {
|
||||||
baseband::spectrum_streaming_start();
|
baseband::spectrum_streaming_start();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GlassView::on_hide() {
|
void GlassView::on_hide() {
|
||||||
@@ -266,10 +305,10 @@ void GlassView::plot_marker(uint8_t pos) {
|
|||||||
{
|
{
|
||||||
shift_y = 16;
|
shift_y = 16;
|
||||||
}
|
}
|
||||||
portapack::display.fill_rectangle({0, 100 + shift_y, SCREEN_W, 8}, Color::black()); // Clear old marker and whole marker rectangle btw
|
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}, Color::red()); // Red marker top
|
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}, Color::red()); // Red marker middle
|
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}, Color::red()); // Red marker bottom
|
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) {
|
void GlassView::update_min(int32_t v) {
|
||||||
@@ -308,10 +347,10 @@ void GlassView::update_max(int32_t v) {
|
|||||||
|
|
||||||
void GlassView::update_range_field() {
|
void GlassView::update_range_field() {
|
||||||
if (!locked_range) {
|
if (!locked_range) {
|
||||||
field_range.set_style(&Styles::white);
|
field_range.set_style(Theme::getInstance()->bg_darkest);
|
||||||
field_range.set_text(" " + to_string_dec_uint(search_span) + " ");
|
field_range.set_text(" " + to_string_dec_uint(search_span) + " ");
|
||||||
} else {
|
} else {
|
||||||
field_range.set_style(&Styles::red);
|
field_range.set_style(Theme::getInstance()->fg_red);
|
||||||
field_range.set_text(">" + to_string_dec_uint(search_span) + "<");
|
field_range.set_text(">" + to_string_dec_uint(search_span) + "<");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -327,17 +366,20 @@ GlassView::GlassView(
|
|||||||
&field_lna,
|
&field_lna,
|
||||||
&field_vga,
|
&field_vga,
|
||||||
&field_range,
|
&field_range,
|
||||||
&steps_config,
|
//&steps_config,
|
||||||
&scan_type,
|
&scan_type,
|
||||||
&view_config,
|
&view_config,
|
||||||
&level_integration,
|
&level_integration,
|
||||||
|
&field_volume,
|
||||||
&filter_config,
|
&filter_config,
|
||||||
&field_rf_amp,
|
&field_rf_amp,
|
||||||
&range_presets,
|
&range_presets,
|
||||||
|
&button_beep_squelch,
|
||||||
&field_marker,
|
&field_marker,
|
||||||
&field_trigger,
|
&field_trigger,
|
||||||
&button_jump,
|
&button_jump,
|
||||||
&button_rst,
|
&button_rst,
|
||||||
|
&field_rx_iq_phase_cal,
|
||||||
&freq_stats});
|
&freq_stats});
|
||||||
|
|
||||||
load_presets(); // Load available presets from TXT files (or default).
|
load_presets(); // Load available presets from TXT files (or default).
|
||||||
@@ -369,12 +411,12 @@ GlassView::GlassView(
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
steps_config.on_change = [this](size_t, OptionsField::value_t v) {
|
/*steps_config.on_change = [this](size_t, OptionsField::value_t v) {
|
||||||
field_frequency_min.set_step(v);
|
field_frequency_min.set_step(v);
|
||||||
field_frequency_max.set_step(v);
|
field_frequency_max.set_step(v);
|
||||||
steps = v;
|
steps = v;
|
||||||
};
|
};
|
||||||
steps_config.set_selected_index(0); // 1 Mhz step.
|
steps_config.set_selected_index(0); // 1 Mhz step.*/
|
||||||
|
|
||||||
scan_type.on_change = [this](size_t, OptionsField::value_t v) {
|
scan_type.on_change = [this](size_t, OptionsField::value_t v) {
|
||||||
mode = v;
|
mode = v;
|
||||||
@@ -474,6 +516,13 @@ GlassView::GlassView(
|
|||||||
reset_live_view();
|
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
|
||||||
|
};
|
||||||
|
set_spec_iq_phase_calibration_value(get_spec_iq_phase_calibration_value()); // initialize iq_phase_calibration in radio
|
||||||
|
|
||||||
display.scroll_set_area(109, 319);
|
display.scroll_set_area(109, 319);
|
||||||
|
|
||||||
// trigger:
|
// trigger:
|
||||||
@@ -489,11 +538,52 @@ GlassView::GlassView(
|
|||||||
receiver_model.set_baseband_bandwidth(looking_glass_bandwidth); // possible values: 1.75/2.5/3.5/5/5.5/6/7/8/9/10/12/14/15/20/24/28MHz
|
receiver_model.set_baseband_bandwidth(looking_glass_bandwidth); // possible values: 1.75/2.5/3.5/5/5.5/6/7/8/9/10/12/14/15/20/24/28MHz
|
||||||
receiver_model.set_squelch_level(0);
|
receiver_model.set_squelch_level(0);
|
||||||
receiver_model.enable();
|
receiver_model.enable();
|
||||||
|
|
||||||
|
button_beep_squelch.on_select = [this](ButtonWithEncoder& button) {
|
||||||
|
(void)button;
|
||||||
|
beep_enabled = 1 - beep_enabled;
|
||||||
|
manage_beep_audio();
|
||||||
|
update_display_beep();
|
||||||
|
};
|
||||||
|
|
||||||
|
button_beep_squelch.on_change = [this]() {
|
||||||
|
int new_beep_squelch = beep_squelch + button_beep_squelch.get_encoder_delta();
|
||||||
|
if (new_beep_squelch < -99)
|
||||||
|
new_beep_squelch = -99;
|
||||||
|
if (new_beep_squelch > 20)
|
||||||
|
new_beep_squelch = 20;
|
||||||
|
beep_squelch = new_beep_squelch;
|
||||||
|
button_beep_squelch.set_encoder_delta(0);
|
||||||
|
update_display_beep();
|
||||||
|
};
|
||||||
|
|
||||||
|
manage_beep_audio();
|
||||||
|
update_display_beep();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GlassView::on_freqchg(int64_t freq) {
|
||||||
|
int64_t half_range = abs(field_frequency_max.value() - field_frequency_max.value()) / 2;
|
||||||
|
if (half_range < 1) {
|
||||||
|
half_range = 1;
|
||||||
|
}
|
||||||
|
range_presets.set_selected_index(0); // Manual
|
||||||
|
update_min(freq - half_range);
|
||||||
|
update_max(freq + half_range);
|
||||||
|
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() {
|
void GlassView::load_presets() {
|
||||||
File presets_file;
|
File presets_file;
|
||||||
auto error = presets_file.open("LOOKINGGLASS/PRESETS.TXT");
|
auto error = presets_file.open(looking_glass_dir / u"PRESETS.TXT");
|
||||||
presets_db.clear();
|
presets_db.clear();
|
||||||
|
|
||||||
// Add the "Manual" entry.
|
// Add the "Manual" entry.
|
||||||
|
|||||||
@@ -33,7 +33,6 @@
|
|||||||
#include "ui_widget.hpp"
|
#include "ui_widget.hpp"
|
||||||
#include "ui_navigation.hpp"
|
#include "ui_navigation.hpp"
|
||||||
#include "ui_receiver.hpp"
|
#include "ui_receiver.hpp"
|
||||||
#include "ui_styles.hpp"
|
|
||||||
#include "string_format.hpp"
|
#include "string_format.hpp"
|
||||||
#include "analog_audio_app.hpp"
|
#include "analog_audio_app.hpp"
|
||||||
#include "spectrum_color_lut.hpp"
|
#include "spectrum_color_lut.hpp"
|
||||||
@@ -69,6 +68,9 @@ class GlassView : public View {
|
|||||||
void on_hide() override;
|
void on_hide() override;
|
||||||
void focus() override;
|
void focus() override;
|
||||||
|
|
||||||
|
uint8_t get_spec_iq_phase_calibration_value();
|
||||||
|
void set_spec_iq_phase_calibration_value(uint8_t cal_value);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
NavigationView& nav_;
|
NavigationView& nav_;
|
||||||
RxRadioState radio_state_{ReceiverModel::Mode::SpectrumAnalysis};
|
RxRadioState radio_state_{ReceiverModel::Mode::SpectrumAnalysis};
|
||||||
@@ -79,8 +81,11 @@ class GlassView : public View {
|
|||||||
uint8_t filter_index = 0; // OFF
|
uint8_t filter_index = 0; // OFF
|
||||||
uint8_t trigger = 32;
|
uint8_t trigger = 32;
|
||||||
uint8_t mode = LOOKING_GLASS_FASTSCAN;
|
uint8_t mode = LOOKING_GLASS_FASTSCAN;
|
||||||
uint8_t live_frequency_view = 0; // Spectrum
|
uint8_t live_frequency_view = 0; // Spectrum
|
||||||
uint8_t live_frequency_integrate = 3; // Default (3 * old value + new_value) / 4
|
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
|
||||||
app_settings::SettingsManager settings_{
|
app_settings::SettingsManager settings_{
|
||||||
"rx_glass"sv,
|
"rx_glass"sv,
|
||||||
app_settings::Mode::RX,
|
app_settings::Mode::RX,
|
||||||
@@ -93,6 +98,9 @@ class GlassView : public View {
|
|||||||
{"scan_mode"sv, &mode},
|
{"scan_mode"sv, &mode},
|
||||||
{"freq_view"sv, &live_frequency_view},
|
{"freq_view"sv, &live_frequency_view},
|
||||||
{"freq_integrate"sv, &live_frequency_integrate},
|
{"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},
|
||||||
}};
|
}};
|
||||||
|
|
||||||
struct preset_entry {
|
struct preset_entry {
|
||||||
@@ -101,7 +109,11 @@ class GlassView : public View {
|
|||||||
std::string label{};
|
std::string label{};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
void on_freqchg(int64_t freq);
|
||||||
|
int32_t map(int32_t value, int32_t fromLow, int32_t fromHigh, int32_t toLow, int32_t toHigh);
|
||||||
std::vector<preset_entry> presets_db{};
|
std::vector<preset_entry> presets_db{};
|
||||||
|
void manage_beep_audio();
|
||||||
|
void update_display_beep();
|
||||||
void update_min(int32_t v);
|
void update_min(int32_t v);
|
||||||
void update_max(int32_t v);
|
void update_max(int32_t v);
|
||||||
void update_range_field();
|
void update_range_field();
|
||||||
@@ -148,6 +160,8 @@ class GlassView : public View {
|
|||||||
int32_t steps = 1;
|
int32_t steps = 1;
|
||||||
bool locked_range = false;
|
bool locked_range = false;
|
||||||
|
|
||||||
|
uint8_t range_max_power = 0;
|
||||||
|
uint8_t range_max_power_counter = 0;
|
||||||
uint8_t max_power = 0;
|
uint8_t max_power = 0;
|
||||||
rf::Frequency max_freq_hold = 0;
|
rf::Frequency max_freq_hold = 0;
|
||||||
rf::Frequency last_max_freq = 0;
|
rf::Frequency last_max_freq = 0;
|
||||||
@@ -157,11 +171,12 @@ class GlassView : public View {
|
|||||||
uint8_t ignore_dc = 0;
|
uint8_t ignore_dc = 0;
|
||||||
|
|
||||||
Labels labels{
|
Labels labels{
|
||||||
{{0, 0 * 16}, "MIN: MAX: LNA VGA ", Color::light_grey()},
|
{{0, 0 * 16}, "MIN: MAX: LNA VGA ", Theme::getInstance()->fg_light->foreground},
|
||||||
{{0, 1 * 16}, "RANGE: FILTER: AMP:", Color::light_grey()},
|
{{0, 1 * 16}, "RANGE: FILTER: AMP:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{0, 2 * 16}, "PRESET:", Color::light_grey()},
|
{{0, 2 * 16}, "P:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{0, 3 * 16}, "MARKER: MHz", Color::light_grey()},
|
{{0, 3 * 16}, "MARKER: MHz RXIQCAL", Theme::getInstance()->fg_light->foreground},
|
||||||
{{0, 4 * 16}, "RES: STEP:", Color::light_grey()}};
|
//{{0, 4 * 16}, "RES: STEPS:", Theme::getInstance()->fg_light->foreground}};
|
||||||
|
{{0, 4 * 16}, "RES: VOL:", Theme::getInstance()->fg_light->foreground}};
|
||||||
|
|
||||||
NumberField field_frequency_min{
|
NumberField field_frequency_min{
|
||||||
{4 * 8, 0 * 16},
|
{4 * 8, 0 * 16},
|
||||||
@@ -200,14 +215,26 @@ class GlassView : public View {
|
|||||||
{28 * 8, 1 * 16}};
|
{28 * 8, 1 * 16}};
|
||||||
|
|
||||||
OptionsField range_presets{
|
OptionsField range_presets{
|
||||||
{7 * 8, 2 * 16},
|
{2 * 8, 2 * 16},
|
||||||
20,
|
20,
|
||||||
{}};
|
{}};
|
||||||
|
|
||||||
|
ButtonWithEncoder button_beep_squelch{
|
||||||
|
{240 - 8 * 8, 2 * 16 + 4, 8 * 8, 1 * 8},
|
||||||
|
""};
|
||||||
|
|
||||||
TextField field_marker{
|
TextField field_marker{
|
||||||
{7 * 8, 3 * 16, 9 * 8, 16},
|
{7 * 8, 3 * 16, 9 * 8, 16},
|
||||||
""};
|
""};
|
||||||
|
|
||||||
|
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,
|
||||||
|
' ',
|
||||||
|
};
|
||||||
|
|
||||||
NumberField field_trigger{
|
NumberField field_trigger{
|
||||||
{4 * 8, 4 * 16},
|
{4 * 8, 4 * 16},
|
||||||
3,
|
3,
|
||||||
@@ -215,7 +242,10 @@ class GlassView : public View {
|
|||||||
2,
|
2,
|
||||||
' '};
|
' '};
|
||||||
|
|
||||||
OptionsField steps_config{
|
AudioVolumeField field_volume{
|
||||||
|
{13 * 8, 4 * 16}};
|
||||||
|
|
||||||
|
/*OptionsField steps_config{
|
||||||
{13 * 8, 4 * 16},
|
{13 * 8, 4 * 16},
|
||||||
3,
|
3,
|
||||||
{
|
{
|
||||||
@@ -225,7 +255,7 @@ class GlassView : public View {
|
|||||||
{"100", 100},
|
{"100", 100},
|
||||||
{"250", 250},
|
{"250", 250},
|
||||||
{"500", 500},
|
{"500", 500},
|
||||||
}};
|
}};*/
|
||||||
|
|
||||||
OptionsField scan_type{
|
OptionsField scan_type{
|
||||||
{17 * 8, 4 * 16},
|
{17 * 8, 4 * 16},
|
||||||
@@ -278,6 +308,7 @@ class GlassView : public View {
|
|||||||
const auto message = *reinterpret_cast<const ChannelSpectrumConfigMessage*>(p);
|
const auto message = *reinterpret_cast<const ChannelSpectrumConfigMessage*>(p);
|
||||||
this->fifo = message.fifo;
|
this->fifo = message.fifo;
|
||||||
}};
|
}};
|
||||||
|
|
||||||
MessageHandlerRegistration message_handler_frame_sync{
|
MessageHandlerRegistration message_handler_frame_sync{
|
||||||
Message::ID::DisplayFrameSync,
|
Message::ID::DisplayFrameSync,
|
||||||
[this](const Message* const) {
|
[this](const Message* const) {
|
||||||
@@ -288,6 +319,13 @@ class GlassView : public View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}};
|
}};
|
||||||
|
|
||||||
|
MessageHandlerRegistration message_handler_freqchg{
|
||||||
|
Message::ID::FreqChangeCommand,
|
||||||
|
[this](Message* const p) {
|
||||||
|
const auto message = static_cast<const FreqChangeCommandMessage*>(p);
|
||||||
|
this->on_freqchg(message->freq);
|
||||||
|
}};
|
||||||
};
|
};
|
||||||
} // namespace ui
|
} // namespace ui
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||||
* Copyright (C) 2016 Furrtek
|
* Copyright (C) 2016 Furrtek
|
||||||
|
* Copyright (C) 2024 Mark Thompson
|
||||||
*
|
*
|
||||||
* This file is part of PortaPack.
|
* This file is part of PortaPack.
|
||||||
*
|
*
|
||||||
@@ -31,6 +32,7 @@
|
|||||||
#include "tonesets.hpp"
|
#include "tonesets.hpp"
|
||||||
#include "ui_tone_key.hpp"
|
#include "ui_tone_key.hpp"
|
||||||
#include "wm8731.hpp"
|
#include "wm8731.hpp"
|
||||||
|
#include "radio.hpp"
|
||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
@@ -61,8 +63,8 @@ void MicTXView::update_vumeter() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void MicTXView::update_tx_icon() {
|
void MicTXView::update_tx_icon() {
|
||||||
tx_icon.set_foreground(transmitting ? Color::red() : Color::black());
|
tx_icon.set_foreground(transmitting ? Theme::getInstance()->fg_red->foreground : Theme::getInstance()->bg_darkest->background);
|
||||||
tx_icon.set_background(transmitting ? Color::yellow() : Color::black());
|
tx_icon.set_background(transmitting ? Theme::getInstance()->fg_yellow->foreground : Theme::getInstance()->bg_darkest->background);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MicTXView::on_tx_progress(const bool done) {
|
void MicTXView::on_tx_progress(const bool done) {
|
||||||
@@ -105,6 +107,7 @@ void MicTXView::configure_baseband() {
|
|||||||
transmitting ? transmitter_model.channel_bandwidth() : 0,
|
transmitting ? transmitter_model.channel_bandwidth() : 0,
|
||||||
mic_gain_x10 / 10.0,
|
mic_gain_x10 / 10.0,
|
||||||
shift_bits(), // to be used in dsp_modulate
|
shift_bits(), // to be used in dsp_modulate
|
||||||
|
8, // bits per sample
|
||||||
TONES_F2D(tone_key_frequency(tone_key_index), sampling_rate),
|
TONES_F2D(tone_key_frequency(tone_key_index), sampling_rate),
|
||||||
(mic_mod_index == MIC_MOD_AM),
|
(mic_mod_index == MIC_MOD_AM),
|
||||||
(mic_mod_index == MIC_MOD_DSB),
|
(mic_mod_index == MIC_MOD_DSB),
|
||||||
@@ -127,9 +130,9 @@ void MicTXView::set_tx(bool enable) {
|
|||||||
portapack::pin_i2s0_rx_sda.mode(3); // This is already done in audio::init but gets changed by the CPLD overlay reprogramming
|
portapack::pin_i2s0_rx_sda.mode(3); // This is already done in audio::init but gets changed by the CPLD overlay reprogramming
|
||||||
} else {
|
} else {
|
||||||
if (transmitting && rogerbeep_enabled) {
|
if (transmitting && rogerbeep_enabled) {
|
||||||
baseband::request_beep(); // Transmit the roger beep
|
baseband::request_roger_beep(); // Transmit the roger beep
|
||||||
transmitting = false; // And flag the end of the transmission so ...
|
transmitting = false; // Flag the end of the transmission (transmitter will be disabled after the beep)
|
||||||
} else { // (if roger beep was enabled, this will be executed after the beep ends transmitting.
|
} else {
|
||||||
transmitting = false;
|
transmitting = false;
|
||||||
configure_baseband();
|
configure_baseband();
|
||||||
transmitter_model.disable();
|
transmitter_model.disable();
|
||||||
@@ -266,16 +269,24 @@ void MicTXView::set_rxbw_options(void) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MicTXView::set_rxbw_defaults(bool use_app_settings) {
|
void MicTXView::set_rxbw_defaults(bool use_app_settings) { // Initially in that function we set up rxbw, but now also txbw.
|
||||||
if (use_app_settings) {
|
if (use_app_settings) {
|
||||||
field_bw.set_value(transmitter_model.channel_bandwidth() / 1000);
|
field_bw.set_value(transmitter_model.channel_bandwidth() / 1000);
|
||||||
field_rxbw.set_by_value(rxbw_index);
|
field_rxbw.set_by_value(rxbw_index);
|
||||||
} else if (mic_mod_index == MIC_MOD_NFM) {
|
} else if (mic_mod_index == MIC_MOD_NFM) {
|
||||||
field_bw.set_value(10); // NFM TX bw 10k, RX bw 16k (2) default
|
field_bw.set_value(10); // NFM TX bw 10k, RX bw 16k (index 2) default
|
||||||
field_rxbw.set_by_value(2);
|
field_bw.set_range(1, 60); // In NFM , FM , we are limitting index modulation range (0.08 ..5) ; (Ex max dev 60khz/12k = 5)
|
||||||
|
field_bw.set_step(1);
|
||||||
|
field_rxbw.set_by_value(2); // 16k from the three options (8k5,11k,16k)
|
||||||
} else if (mic_mod_index == MIC_MOD_WFM) {
|
} else if (mic_mod_index == MIC_MOD_WFM) {
|
||||||
field_bw.set_value(75); // WFM TX bw 75K, RX bw 200k (0) default
|
field_bw.set_value(75); // WFM TX bw 75K, RX bw 200k (index 0) default
|
||||||
|
field_bw.set_range(1, 150); // In our case Mod. Index range (1,67 ...12,5) ; 150k/12k=12,5
|
||||||
|
field_bw.set_step(1);
|
||||||
field_rxbw.set_by_value(0);
|
field_rxbw.set_by_value(0);
|
||||||
|
} else if ((mic_mod_index == MIC_MOD_USB) | (mic_mod_index == MIC_MOD_LSB)) {
|
||||||
|
field_bw.set_value(3); // In SSB by default let's limit TX_BW to 3kHz.
|
||||||
|
field_bw.set_range(2, 3); // User TXBW GUI range to modify that SSB TX_BW to limit SSB radiated spectrum.
|
||||||
|
field_bw.set_step(1);
|
||||||
}
|
}
|
||||||
// field_bw is hidden in other modulation cases
|
// field_bw is hidden in other modulation cases
|
||||||
}
|
}
|
||||||
@@ -335,6 +346,7 @@ MicTXView::MicTXView(
|
|||||||
&field_rxlna,
|
&field_rxlna,
|
||||||
&field_rxvga,
|
&field_rxvga,
|
||||||
&field_rxamp,
|
&field_rxamp,
|
||||||
|
&field_tx_iq_phase_cal,
|
||||||
&tx_button,
|
&tx_button,
|
||||||
&tx_icon});
|
&tx_icon});
|
||||||
|
|
||||||
@@ -367,6 +379,14 @@ MicTXView::MicTXView(
|
|||||||
receiver_model.set_rf_amp(v);
|
receiver_model.set_rf_amp(v);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
radio::set_tx_max283x_iq_phase_calibration(iq_phase_calibration_value);
|
||||||
|
field_tx_iq_phase_cal.set_range(0, hackrf_r9 ? 63 : 31); // max2839 has 6 bits [0..63], max2837 has 5 bits [0..31]
|
||||||
|
field_tx_iq_phase_cal.set_value(iq_phase_calibration_value);
|
||||||
|
field_tx_iq_phase_cal.on_change = [this](int32_t v) {
|
||||||
|
iq_phase_calibration_value = v;
|
||||||
|
radio::set_tx_max283x_iq_phase_calibration(iq_phase_calibration_value);
|
||||||
|
};
|
||||||
|
|
||||||
options_gain.on_change = [this](size_t, int32_t v) {
|
options_gain.on_change = [this](size_t, int32_t v) {
|
||||||
mic_gain_x10 = v;
|
mic_gain_x10 = v;
|
||||||
configure_baseband();
|
configure_baseband();
|
||||||
@@ -470,7 +490,11 @@ MicTXView::MicTXView(
|
|||||||
field_bw.hidden(false);
|
field_bw.hidden(false);
|
||||||
options_tone_key.hidden(false);
|
options_tone_key.hidden(false);
|
||||||
} else {
|
} else {
|
||||||
field_bw.hidden(true);
|
if ((mic_mod_index == MIC_MOD_USB) || (mic_mod_index == MIC_MOD_LSB)) {
|
||||||
|
field_bw.hidden(false);
|
||||||
|
} else {
|
||||||
|
field_bw.hidden(true);
|
||||||
|
}
|
||||||
options_tone_key.set_selected_index(0);
|
options_tone_key.set_selected_index(0);
|
||||||
options_tone_key.hidden(true);
|
options_tone_key.hidden(true);
|
||||||
}
|
}
|
||||||
@@ -511,21 +535,6 @@ MicTXView::MicTXView(
|
|||||||
};
|
};
|
||||||
check_mic_to_HP.set_value(mic_to_HP_enabled);
|
check_mic_to_HP.set_value(mic_to_HP_enabled);
|
||||||
|
|
||||||
check_rxactive.on_select = [this](Checkbox&, bool v) {
|
|
||||||
// vumeter.set_value(0); //Start with a clean vumeter
|
|
||||||
rx_enabled = v;
|
|
||||||
check_mic_to_HP.hidden(rx_enabled); // Toggle Hide / show "Hear Mic" checkbox depending if we activate or not the receiver. (if RX on => no visible "Mic Hear" option)
|
|
||||||
if ((rx_enabled) && (transmitting))
|
|
||||||
check_mic_to_HP.set_value(transmitting); // Once we activate the "Rx audio" in reception time we disable "Hear Mic", but we allow it again in TX time.
|
|
||||||
|
|
||||||
if (rx_enabled)
|
|
||||||
check_va.set_value(false); // Disallow voice activation during RX audio (for now) - Future TODO: Should allow VOX during RX audio
|
|
||||||
|
|
||||||
rxaudio(v); // Activate-Deactivate audio RX (receiver) accordingly
|
|
||||||
set_dirty(); // Refresh interface
|
|
||||||
};
|
|
||||||
check_rxactive.set_value(rx_enabled);
|
|
||||||
|
|
||||||
tx_button.on_select = [this](Button&) {
|
tx_button.on_select = [this](Button&) {
|
||||||
if (!transmitting) {
|
if (!transmitting) {
|
||||||
set_tx(true);
|
set_tx(true);
|
||||||
@@ -578,12 +587,28 @@ MicTXView::MicTXView(
|
|||||||
// Trigger receiver to update modulation.
|
// Trigger receiver to update modulation.
|
||||||
if (rx_enabled)
|
if (rx_enabled)
|
||||||
receiver_model.set_squelch_level(receiver_model.squelch_level());
|
receiver_model.set_squelch_level(receiver_model.squelch_level());
|
||||||
|
|
||||||
|
check_rxactive.on_select = [this](Checkbox&, bool v) {
|
||||||
|
// vumeter.set_value(0); //Start with a clean vumeter
|
||||||
|
rx_enabled = v;
|
||||||
|
check_mic_to_HP.hidden(rx_enabled); // Toggle Hide / show "Hear Mic" checkbox depending if we activate or not the receiver. (if RX on => no visible "Mic Hear" option)
|
||||||
|
if ((rx_enabled) && (transmitting))
|
||||||
|
check_mic_to_HP.set_value(transmitting); // Once we activate the "Rx audio" in reception time we disable "Hear Mic", but we allow it again in TX time.
|
||||||
|
|
||||||
|
if (rx_enabled)
|
||||||
|
check_va.set_value(false); // Disallow voice activation during RX audio (for now) - Future TODO: Should allow VOX during RX audio
|
||||||
|
|
||||||
|
rxaudio(v); // Activate-Deactivate audio RX (receiver) accordingly
|
||||||
|
set_dirty(); // Refresh interface
|
||||||
|
};
|
||||||
|
check_rxactive.set_value(rx_enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
MicTXView::MicTXView(
|
MicTXView::MicTXView(
|
||||||
NavigationView& nav,
|
NavigationView& nav,
|
||||||
ReceiverModel::settings_t override)
|
ReceiverModel::settings_t override)
|
||||||
: MicTXView(nav) {
|
: MicTXView(nav) {
|
||||||
|
// Settings to override when launched from another app (versus from AppSettings .ini file)
|
||||||
// Try to use the modulation/bandwidth from RX settings.
|
// Try to use the modulation/bandwidth from RX settings.
|
||||||
// TODO: These concepts should be merged so there's only one.
|
// TODO: These concepts should be merged so there's only one.
|
||||||
switch (override.mode) {
|
switch (override.mode) {
|
||||||
@@ -604,6 +629,7 @@ MicTXView::MicTXView(
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
field_frequency.set_value(override.frequency_app_override);
|
||||||
check_common_freq_tx_rx.set_value(true); // freq passed from other app is in tx_frequency, so set rx_frequency=tx_frequency
|
check_common_freq_tx_rx.set_value(true); // freq passed from other app is in tx_frequency, so set rx_frequency=tx_frequency
|
||||||
|
|
||||||
// TODO: bandwidth selection is tied too tightly to the UI
|
// TODO: bandwidth selection is tied too tightly to the UI
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||||
* Copyright (C) 2016 Furrtek
|
* Copyright (C) 2016 Furrtek
|
||||||
|
* Copyright (C) 2024 Mark Thompson
|
||||||
*
|
*
|
||||||
* This file is part of PortaPack.
|
* This file is part of PortaPack.
|
||||||
*
|
*
|
||||||
@@ -113,10 +114,10 @@ class MicTXView : public View {
|
|||||||
uint32_t va_level{40};
|
uint32_t va_level{40};
|
||||||
uint32_t attack_ms{500};
|
uint32_t attack_ms{500};
|
||||||
uint32_t decay_ms{1000};
|
uint32_t decay_ms{1000};
|
||||||
|
uint8_t iq_phase_calibration_value{15};
|
||||||
app_settings::SettingsManager settings_{
|
app_settings::SettingsManager settings_{
|
||||||
"tx_mic",
|
"tx_mic",
|
||||||
app_settings::Mode::RX_TX,
|
app_settings::Mode::RX_TX,
|
||||||
app_settings::Options::UseGlobalTargetFrequency,
|
|
||||||
{
|
{
|
||||||
{"mic_mod_index"sv, &mic_mod_index},
|
{"mic_mod_index"sv, &mic_mod_index},
|
||||||
{"rxbw_index"sv, &rxbw_index},
|
{"rxbw_index"sv, &rxbw_index},
|
||||||
@@ -132,6 +133,7 @@ class MicTXView : public View {
|
|||||||
{"vox"sv, &va_enabled},
|
{"vox"sv, &va_enabled},
|
||||||
{"rogerbeep"sv, &rogerbeep_enabled},
|
{"rogerbeep"sv, &rogerbeep_enabled},
|
||||||
{"tone_key_index"sv, &tone_key_index},
|
{"tone_key_index"sv, &tone_key_index},
|
||||||
|
{"iq_phase_calibration"sv, &iq_phase_calibration_value},
|
||||||
}};
|
}};
|
||||||
|
|
||||||
rf::Frequency tx_frequency{0};
|
rf::Frequency tx_frequency{0};
|
||||||
@@ -145,26 +147,27 @@ class MicTXView : public View {
|
|||||||
bool button_touch{false};
|
bool button_touch{false};
|
||||||
|
|
||||||
Labels labels_both{
|
Labels labels_both{
|
||||||
{{3 * 8, 1 * 8}, "MIC-GAIN:", Color::light_grey()},
|
{{3 * 8, 1 * 8}, "MIC-GAIN:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{3 * 8, 3 * 8}, "F:", Color::light_grey()},
|
{{3 * 8, 3 * 8}, "F: MHz", Theme::getInstance()->fg_light->foreground},
|
||||||
{{15 * 8, 3 * 8}, "FM TXBW: kHz", Color::light_grey()}, // to be more symetric and consistent to the below FM RXBW
|
{{18 * 8, 3 * 8}, "TXBW: kHz", Theme::getInstance()->fg_light->foreground}, // to be more symetric and consistent to the below FM RXBW
|
||||||
{{18 * 8, (5 * 8)}, "Mode:", Color::light_grey()}, // now, no need to handle GAIN, Amp here It is handled by ui_transmitter.cpp
|
{{18 * 8, (5 * 8)}, "Mode:", Theme::getInstance()->fg_light->foreground}, // now, no need to handle GAIN, Amp here It is handled by ui_transmitter.cpp
|
||||||
{{4 * 8, 10 * 8}, "LVL:", Color::light_grey()}, // we delete { {11 * 8, 5 * 8 }, "Amp:", Color::light_grey() },
|
{{4 * 8, 10 * 8}, "LVL:", Theme::getInstance()->fg_light->foreground}, // we delete { {11 * 8, 5 * 8 }, "Amp:", Theme::getInstance()->fg_light->foreground },
|
||||||
{{12 * 8, 10 * 8}, "ATT:", Color::light_grey()},
|
{{12 * 8, 10 * 8}, "ATT:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{20 * 8, 10 * 8}, "DEC:", Color::light_grey()},
|
{{20 * 8, 10 * 8}, "DEC:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{3 * 8, (13 * 8) - 5}, "TONE KEY:", Color::light_grey()},
|
{{3 * 8, (13 * 8) - 5}, "TONE KEY:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{3 * 8, (18 * 8) - 1}, "======== Receiver ========", Color::green()},
|
{{3 * 8, (18 * 8) - 1}, "======== Receiver ========", Theme::getInstance()->fg_green->foreground},
|
||||||
{{5 * 8, (23 * 8) + 2}, "VOL:", Color::light_grey()},
|
{{5 * 8, (23 * 8) + 2}, "VOL:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{14 * 8, (23 * 8) + 2}, "RXBW:", Color::light_grey()}, // we remove the label "FM" because we will display all MOD types RX_BW.
|
{{14 * 8, (23 * 8) + 2}, "RXBW:", Theme::getInstance()->fg_light->foreground}, // we remove the label "FM" because we will display all MOD types RX_BW.
|
||||||
{{20 * 8, (25 * 8) + 2}, "SQ:", Color::light_grey()},
|
{{20 * 8, (25 * 8) + 2}, "SQ:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{5 * 8, (25 * 8) + 2}, "F_RX:", Color::light_grey()},
|
{{5 * 8, (25 * 8) + 2}, "F_RX:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{5 * 8, (27 * 8) + 2}, "LNA:", Color::light_grey()},
|
{{5 * 8, (27 * 8) + 2}, "LNA:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{12 * 8, (27 * 8) + 2}, "VGA:", Color::light_grey()},
|
{{12 * 8, (27 * 8) + 2}, "VGA:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{19 * 8, (27 * 8) + 2}, "AMP:", Color::light_grey()}};
|
{{19 * 8, (27 * 8) + 2}, "AMP:", Theme::getInstance()->fg_light->foreground},
|
||||||
|
{{21 * 8, (31 * 8)}, "TX-IQ-CAL:", Theme::getInstance()->fg_light->foreground}};
|
||||||
Labels labels_WM8731{
|
Labels labels_WM8731{
|
||||||
{{17 * 8, 1 * 8}, "Boost", Color::light_grey()}};
|
{{17 * 8, 1 * 8}, "Boost", Theme::getInstance()->fg_light->foreground}};
|
||||||
Labels labels_AK4951{
|
Labels labels_AK4951{
|
||||||
{{17 * 8, 1 * 8}, "ALC", Color::light_grey()}};
|
{{17 * 8, 1 * 8}, "ALC", Theme::getInstance()->fg_light->foreground}};
|
||||||
|
|
||||||
VuMeter vumeter{
|
VuMeter vumeter{
|
||||||
{0 * 8, 1 * 8, 2 * 8, 33 * 8},
|
{0 * 8, 1 * 8, 2 * 8, 33 * 8},
|
||||||
@@ -338,6 +341,14 @@ class MicTXView : public View {
|
|||||||
' ',
|
' ',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
NumberField field_tx_iq_phase_cal{
|
||||||
|
{24 * 8, (33 * 8)},
|
||||||
|
2,
|
||||||
|
{0, 63}, // 5 or 6 bits IQ CAL phase adjustment (range updated later)
|
||||||
|
1,
|
||||||
|
' ',
|
||||||
|
};
|
||||||
|
|
||||||
Button tx_button{
|
Button tx_button{
|
||||||
{10 * 8, 30 * 8, 10 * 8, 5 * 8},
|
{10 * 8, 30 * 8, 10 * 8, 5 * 8},
|
||||||
"PTT TX",
|
"PTT TX",
|
||||||
@@ -346,8 +357,8 @@ class MicTXView : public View {
|
|||||||
Image tx_icon{
|
Image tx_icon{
|
||||||
{6 * 8, 31 * 8 + 4, 16, 16},
|
{6 * 8, 31 * 8 + 4, 16, 16},
|
||||||
&bitmap_icon_microphone,
|
&bitmap_icon_microphone,
|
||||||
Color::black(),
|
Theme::getInstance()->bg_darkest->background,
|
||||||
Color::black()};
|
Theme::getInstance()->bg_darkest->background};
|
||||||
|
|
||||||
MessageHandlerRegistration message_handler_lcd_sync{
|
MessageHandlerRegistration message_handler_lcd_sync{
|
||||||
Message::ID::DisplayFrameSync,
|
Message::ID::DisplayFrameSync,
|
||||||
|
|||||||
@@ -39,12 +39,12 @@ class ModemSetupView : public View {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
Labels labels{
|
Labels labels{
|
||||||
{{2 * 8, 11 * 8}, "Baudrate:", Color::light_grey()},
|
{{2 * 8, 11 * 8}, "Baudrate:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{2 * 8, 13 * 8}, "Mark: Hz", Color::light_grey()},
|
{{2 * 8, 13 * 8}, "Mark: Hz", Theme::getInstance()->fg_light->foreground},
|
||||||
{{2 * 8, 15 * 8}, "Space: Hz", Color::light_grey()},
|
{{2 * 8, 15 * 8}, "Space: Hz", Theme::getInstance()->fg_light->foreground},
|
||||||
{{140, 15 * 8}, "Repeat:", Color::light_grey()},
|
{{140, 15 * 8}, "Repeat:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{1 * 8, 6 * 8}, "Modem preset:", Color::light_grey()},
|
{{1 * 8, 6 * 8}, "Modem preset:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{2 * 8, 22 * 8}, "Serial format:", Color::light_grey()}};
|
{{2 * 8, 22 * 8}, "Serial format:", Theme::getInstance()->fg_light->foreground}};
|
||||||
|
|
||||||
NumberField field_baudrate{
|
NumberField field_baudrate{
|
||||||
{11 * 8, 11 * 8},
|
{11 * 8, 11 * 8},
|
||||||
|
|||||||
@@ -21,7 +21,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "ui_numbers.hpp"
|
#include "ui_numbers.hpp"
|
||||||
#include "ui_styles.hpp"
|
|
||||||
#include "string_format.hpp"
|
#include "string_format.hpp"
|
||||||
|
|
||||||
#include "portapack.hpp"
|
#include "portapack.hpp"
|
||||||
@@ -145,7 +144,7 @@ void NumbersStationView::on_tick_second() {
|
|||||||
armed_blink = not armed_blink;
|
armed_blink = not armed_blink;
|
||||||
|
|
||||||
if (armed_blink)
|
if (armed_blink)
|
||||||
check_armed.set_style(&Styles::red);
|
check_armed.set_style(Theme::getInstance()->fg_red);
|
||||||
else
|
else
|
||||||
check_armed.set_style(&style());
|
check_armed.set_style(&style());
|
||||||
|
|
||||||
@@ -277,18 +276,9 @@ NumbersStationView::NumbersStationView(
|
|||||||
symfield_code.set_offset(10, 12); // End
|
symfield_code.set_offset(10, 12); // End
|
||||||
|
|
||||||
/*
|
/*
|
||||||
rtc::RTC datetime;
|
dayofweek = rtc_time::current_day_of_week();
|
||||||
rtcGetTime(&RTCD1, &datetime);
|
|
||||||
|
|
||||||
// Thanks, Sakamoto-sama !
|
|
||||||
y = datetime.year();
|
|
||||||
m = datetime.month();
|
|
||||||
d = datetime.day();
|
|
||||||
y -= m < 3;
|
|
||||||
dayofweek = (y + y/4 - y/100 + y/400 + month_table[m-1] + d) % 7;
|
|
||||||
|
|
||||||
text_title.set(day_of_week[dayofweek]);
|
text_title.set(day_of_week[dayofweek]);
|
||||||
*/
|
*/
|
||||||
|
|
||||||
button_exit.on_select = [&nav](Button&) {
|
button_exit.on_select = [&nav](Button&) {
|
||||||
nav.pop();
|
nav.pop();
|
||||||
|
|||||||
@@ -129,8 +129,8 @@ class NumbersStationView : public View {
|
|||||||
void start_tx();
|
void start_tx();
|
||||||
|
|
||||||
Labels labels{
|
Labels labels{
|
||||||
{{2 * 8, 5 * 8}, "Voice: Flags:", Color::light_grey()},
|
{{2 * 8, 5 * 8}, "Voice: Flags:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{1 * 8, 8 * 8}, "Code:", Color::light_grey()}};
|
{{1 * 8, 8 * 8}, "Code:", Theme::getInstance()->fg_light->foreground}};
|
||||||
|
|
||||||
OptionsField options_voices{
|
OptionsField options_voices{
|
||||||
{8 * 8, 1 * 8},
|
{8 * 8, 1 * 8},
|
||||||
|
|||||||
@@ -78,8 +78,6 @@ void NuoptixView::transmit(bool setup) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
transmitter_model.set_rf_amp(true);
|
transmitter_model.set_rf_amp(true);
|
||||||
transmitter_model.set_lna(40);
|
|
||||||
transmitter_model.set_vga(40);
|
|
||||||
transmitter_model.enable();
|
transmitter_model.enable();
|
||||||
|
|
||||||
dtmf_message[0] = '*'; // "Pre-tone for restart" method #1
|
dtmf_message[0] = '*'; // "Pre-tone for restart" method #1
|
||||||
@@ -136,6 +134,7 @@ void NuoptixView::transmit(bool setup) {
|
|||||||
shared_memory.bb_data.tones_data.silence = NUOPTIX_TONE_LENGTH; // 49ms tone, 49ms space
|
shared_memory.bb_data.tones_data.silence = NUOPTIX_TONE_LENGTH; // 49ms tone, 49ms space
|
||||||
|
|
||||||
audio::set_rate(audio::Rate::Hz_24000);
|
audio::set_rate(audio::Rate::Hz_24000);
|
||||||
|
|
||||||
baseband::set_tones_config(transmitter_model.channel_bandwidth(), 0, 6 * 2, true, true);
|
baseband::set_tones_config(transmitter_model.channel_bandwidth(), 0, 6 * 2, true, true);
|
||||||
|
|
||||||
timecode++;
|
timecode++;
|
||||||
@@ -156,7 +155,7 @@ NuoptixView::NuoptixView(
|
|||||||
tx_view.on_edit_frequency = [this, &nav]() {
|
tx_view.on_edit_frequency = [this, &nav]() {
|
||||||
auto new_view = nav.push<FrequencyKeypadView>(transmitter_model.target_frequency());
|
auto new_view = nav.push<FrequencyKeypadView>(transmitter_model.target_frequency());
|
||||||
new_view->on_changed = [this](rf::Frequency f) {
|
new_view->on_changed = [this](rf::Frequency f) {
|
||||||
transmitter_model.target_frequency(f);
|
transmitter_model.set_target_frequency(f);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
* Copyright (C) 2016 Furrtek
|
* Copyright (C) 2016 Furrtek
|
||||||
* Copyleft (ↄ) 2022 NotPike
|
* Copyleft (ↄ) 2022 NotPike
|
||||||
* Copyright (C) 2023 Kyle Reed, zxkmm
|
* Copyright (C) 2023 Kyle Reed, zxkmm
|
||||||
|
* Copyright (C) 2024 Mark Thompson
|
||||||
*
|
*
|
||||||
* This file is part of PortaPack.
|
* This file is part of PortaPack.
|
||||||
*
|
*
|
||||||
@@ -35,6 +36,7 @@
|
|||||||
#include "string_format.hpp"
|
#include "string_format.hpp"
|
||||||
#include "ui_fileman.hpp"
|
#include "ui_fileman.hpp"
|
||||||
#include "utility.hpp"
|
#include "utility.hpp"
|
||||||
|
#include "file_path.hpp"
|
||||||
|
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
@@ -129,7 +131,7 @@ void PlaylistView::open_file(bool prompt_save) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto open_view = nav_.push<FileLoadView>(".PPL");
|
auto open_view = nav_.push<FileLoadView>(".PPL");
|
||||||
open_view->push_dir(u"PLAYLIST");
|
open_view->push_dir(playlist_dir);
|
||||||
open_view->on_changed = [this](fs::path new_file_path) {
|
open_view->on_changed = [this](fs::path new_file_path) {
|
||||||
on_file_changed(new_file_path);
|
on_file_changed(new_file_path);
|
||||||
};
|
};
|
||||||
@@ -168,7 +170,7 @@ void PlaylistView::save_file(bool show_dialogs) {
|
|||||||
|
|
||||||
void PlaylistView::add_entry(fs::path&& path) {
|
void PlaylistView::add_entry(fs::path&& path) {
|
||||||
if (playlist_path_.empty()) {
|
if (playlist_path_.empty()) {
|
||||||
playlist_path_ = next_filename_matching_pattern(u"/PLAYLIST/PLAY_????.PPL");
|
playlist_path_ = next_filename_matching_pattern(playlist_dir / u"PLAY_????.PPL");
|
||||||
|
|
||||||
// Hack around focus getting called by ctor before parent is set.
|
// Hack around focus getting called by ctor before parent is set.
|
||||||
if (parent())
|
if (parent())
|
||||||
@@ -386,10 +388,10 @@ PlaylistView::PlaylistView(
|
|||||||
&waterfall,
|
&waterfall,
|
||||||
});
|
});
|
||||||
|
|
||||||
ensure_directory(u"PLAYLIST");
|
ensure_directory(playlist_dir);
|
||||||
waterfall.show_audio_spectrum_view(false);
|
waterfall.show_audio_spectrum_view(false);
|
||||||
|
|
||||||
field_frequency.set_value(100'000'000);
|
field_frequency.set_value(transmitter_model.target_frequency());
|
||||||
field_frequency.on_change = [this](rf::Frequency f) {
|
field_frequency.on_change = [this](rf::Frequency f) {
|
||||||
if (current())
|
if (current())
|
||||||
current()->metadata.center_frequency = f;
|
current()->metadata.center_frequency = f;
|
||||||
@@ -409,7 +411,7 @@ PlaylistView::PlaylistView(
|
|||||||
if (is_active())
|
if (is_active())
|
||||||
return;
|
return;
|
||||||
auto open_view = nav_.push<FileLoadView>(".C*");
|
auto open_view = nav_.push<FileLoadView>(".C*");
|
||||||
open_view->push_dir(u"CAPTURES");
|
open_view->push_dir(captures_dir);
|
||||||
open_view->on_changed = [this](fs::path path) {
|
open_view->on_changed = [this](fs::path path) {
|
||||||
add_entry(std::move(path));
|
add_entry(std::move(path));
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
* Copyright (C) 2016 Jared Boone, ShareBrained Technology, Inc.
|
* Copyright (C) 2016 Jared Boone, ShareBrained Technology, Inc.
|
||||||
* Copyright (C) 2016 Furrtek
|
* Copyright (C) 2016 Furrtek
|
||||||
* Copyright (C) 2023 Kyle Reed, zxkmm
|
* Copyright (C) 2023 Kyle Reed, zxkmm
|
||||||
|
* Copyright (C) 2024 Mark Thompson
|
||||||
*
|
*
|
||||||
* This file is part of PortaPack.
|
* This file is part of PortaPack.
|
||||||
*
|
*
|
||||||
@@ -56,7 +57,7 @@ class PlaylistView : public View {
|
|||||||
NavigationView& nav_;
|
NavigationView& nav_;
|
||||||
TxRadioState radio_state_{};
|
TxRadioState radio_state_{};
|
||||||
app_settings::SettingsManager settings_{
|
app_settings::SettingsManager settings_{
|
||||||
"tx_playlist", app_settings::Mode::TX};
|
"tx_replay", app_settings::Mode::TX};
|
||||||
|
|
||||||
// More header == less spectrum view.
|
// More header == less spectrum view.
|
||||||
static constexpr ui::Dim header_height = 6 * 16;
|
static constexpr ui::Dim header_height = 6 * 16;
|
||||||
@@ -140,8 +141,8 @@ class PlaylistView : public View {
|
|||||||
ImageButton button_play{
|
ImageButton button_play{
|
||||||
{28 * 8, 2 * 16, 2 * 8, 1 * 16},
|
{28 * 8, 2 * 16, 2 * 8, 1 * 16},
|
||||||
&bitmap_play,
|
&bitmap_play,
|
||||||
Color::green(),
|
Theme::getInstance()->fg_green->foreground,
|
||||||
Color::black()};
|
Theme::getInstance()->fg_green->background};
|
||||||
|
|
||||||
Text text_track{
|
Text text_track{
|
||||||
{0 * 8, 3 * 16, 30 * 8, 16}};
|
{0 * 8, 3 * 16, 30 * 8, 16}};
|
||||||
@@ -150,37 +151,37 @@ class PlaylistView : public View {
|
|||||||
{2 * 8, 4 * 16, 4 * 8, 2 * 16},
|
{2 * 8, 4 * 16, 4 * 8, 2 * 16},
|
||||||
"",
|
"",
|
||||||
&bitmap_arrow_left,
|
&bitmap_arrow_left,
|
||||||
Color::dark_grey()};
|
Theme::getInstance()->bg_dark->background};
|
||||||
|
|
||||||
NewButton button_next{
|
NewButton button_next{
|
||||||
{6 * 8, 4 * 16, 4 * 8, 2 * 16},
|
{6 * 8, 4 * 16, 4 * 8, 2 * 16},
|
||||||
"",
|
"",
|
||||||
&bitmap_arrow_right,
|
&bitmap_arrow_right,
|
||||||
Color::dark_grey()};
|
Theme::getInstance()->bg_dark->background};
|
||||||
|
|
||||||
NewButton button_add{
|
NewButton button_add{
|
||||||
{11 * 8, 4 * 16, 4 * 8, 2 * 16},
|
{11 * 8, 4 * 16, 4 * 8, 2 * 16},
|
||||||
"",
|
"",
|
||||||
&bitmap_icon_new_file,
|
&bitmap_icon_new_file,
|
||||||
Color::orange()};
|
Theme::getInstance()->fg_orange->foreground};
|
||||||
|
|
||||||
NewButton button_delete{
|
NewButton button_delete{
|
||||||
{15 * 8, 4 * 16, 4 * 8, 2 * 16},
|
{15 * 8, 4 * 16, 4 * 8, 2 * 16},
|
||||||
"",
|
"",
|
||||||
&bitmap_icon_delete,
|
&bitmap_icon_delete,
|
||||||
Color::orange()};
|
Theme::getInstance()->fg_orange->foreground};
|
||||||
|
|
||||||
NewButton button_open{
|
NewButton button_open{
|
||||||
{20 * 8, 4 * 16, 4 * 8, 2 * 16},
|
{20 * 8, 4 * 16, 4 * 8, 2 * 16},
|
||||||
"",
|
"",
|
||||||
&bitmap_icon_load,
|
&bitmap_icon_load,
|
||||||
Color::dark_blue()};
|
Theme::getInstance()->fg_blue->foreground};
|
||||||
|
|
||||||
NewButton button_save{
|
NewButton button_save{
|
||||||
{24 * 8, 4 * 16, 4 * 8, 2 * 16},
|
{24 * 8, 4 * 16, 4 * 8, 2 * 16},
|
||||||
"",
|
"",
|
||||||
&bitmap_icon_save,
|
&bitmap_icon_save,
|
||||||
Color::dark_blue()};
|
Theme::getInstance()->fg_blue->foreground};
|
||||||
|
|
||||||
spectrum::WaterfallView waterfall{};
|
spectrum::WaterfallView waterfall{};
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +42,32 @@ POCSAGTXView::~POCSAGTXView() {
|
|||||||
baseband::shutdown();
|
baseband::shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void POCSAGTXView::on_remote(const PocsagTosendMessage data) {
|
||||||
|
// check if still sending or not
|
||||||
|
size_t tmp = 0;
|
||||||
|
if (data.baud == 1200) tmp = 1;
|
||||||
|
if (data.baud == 2400) tmp = 2;
|
||||||
|
options_bitrate.set_selected_index(tmp);
|
||||||
|
tmp = 0;
|
||||||
|
options_type.set_selected_index(data.type);
|
||||||
|
if (data.function == 'B') tmp = 1;
|
||||||
|
if (data.function == 'C') tmp = 2;
|
||||||
|
if (data.function == 'D') tmp = 3;
|
||||||
|
options_function.set_selected_index(tmp);
|
||||||
|
options_phase.set_selected_index(data.phase == 'P' ? 0 : 1);
|
||||||
|
field_address.set_value(data.addr);
|
||||||
|
message = (char*)data.msg;
|
||||||
|
text_message.set(message);
|
||||||
|
options_bitrate.dirty();
|
||||||
|
options_type.dirty();
|
||||||
|
options_function.dirty();
|
||||||
|
options_phase.dirty();
|
||||||
|
field_address.dirty();
|
||||||
|
text_message.dirty();
|
||||||
|
tx_view.focus();
|
||||||
|
start_tx();
|
||||||
|
}
|
||||||
|
|
||||||
void POCSAGTXView::on_tx_progress(const uint32_t progress, const bool done) {
|
void POCSAGTXView::on_tx_progress(const uint32_t progress, const bool done) {
|
||||||
if (done) {
|
if (done) {
|
||||||
transmitter_model.disable();
|
transmitter_model.disable();
|
||||||
|
|||||||
@@ -76,15 +76,16 @@ class POCSAGTXView : public View {
|
|||||||
|
|
||||||
void on_set_text(NavigationView& nav);
|
void on_set_text(NavigationView& nav);
|
||||||
void on_tx_progress(const uint32_t progress, const bool done);
|
void on_tx_progress(const uint32_t progress, const bool done);
|
||||||
|
void on_remote(const PocsagTosendMessage data);
|
||||||
bool start_tx();
|
bool start_tx();
|
||||||
|
|
||||||
Labels labels{
|
Labels labels{
|
||||||
{{3 * 8, 4 * 8}, "Bitrate:", Color::light_grey()},
|
{{3 * 8, 4 * 8}, "Bitrate:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{3 * 8, 6 * 8}, "Address:", Color::light_grey()},
|
{{3 * 8, 6 * 8}, "Address:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{6 * 8, 8 * 8}, "Type:", Color::light_grey()},
|
{{6 * 8, 8 * 8}, "Type:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{2 * 8, 10 * 8}, "Function:", Color::light_grey()},
|
{{2 * 8, 10 * 8}, "Function:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{5 * 8, 12 * 8}, "Phase:", Color::light_grey()},
|
{{5 * 8, 12 * 8}, "Phase:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{0 * 8, 14 * 8}, "Message:", Color::light_grey()}};
|
{{0 * 8, 14 * 8}, "Message:", Theme::getInstance()->fg_light->foreground}};
|
||||||
|
|
||||||
OptionsField options_bitrate{
|
OptionsField options_bitrate{
|
||||||
{11 * 8, 4 * 8},
|
{11 * 8, 4 * 8},
|
||||||
@@ -142,6 +143,13 @@ class POCSAGTXView : public View {
|
|||||||
const auto message = *reinterpret_cast<const TXProgressMessage*>(p);
|
const auto message = *reinterpret_cast<const TXProgressMessage*>(p);
|
||||||
this->on_tx_progress(message.progress, message.done);
|
this->on_tx_progress(message.progress, message.done);
|
||||||
}};
|
}};
|
||||||
|
|
||||||
|
MessageHandlerRegistration message_handler_tx_remote{
|
||||||
|
Message::ID::PocsagTosend,
|
||||||
|
[this](const Message* const p) {
|
||||||
|
const auto message = *reinterpret_cast<const PocsagTosendMessage*>(p);
|
||||||
|
this->on_remote(message);
|
||||||
|
}};
|
||||||
};
|
};
|
||||||
|
|
||||||
} /* namespace ui */
|
} /* namespace ui */
|
||||||
|
|||||||
@@ -43,8 +43,8 @@ class RDSPSNView : public OptionTabView {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
Labels labels{
|
Labels labels{
|
||||||
{{1 * 8, 3 * 8}, "Program Service Name", Color::light_grey()},
|
{{1 * 8, 3 * 8}, "Program Service Name", Theme::getInstance()->fg_light->foreground},
|
||||||
{{2 * 8, 7 * 8}, "PSN:", Color::light_grey()}};
|
{{2 * 8, 7 * 8}, "PSN:", Theme::getInstance()->fg_light->foreground}};
|
||||||
|
|
||||||
Button button_set{
|
Button button_set{
|
||||||
{18 * 8, 3 * 16, 80, 32},
|
{18 * 8, 3 * 16, 80, 32},
|
||||||
@@ -75,8 +75,8 @@ class RDSRadioTextView : public OptionTabView {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
Labels labels{
|
Labels labels{
|
||||||
{{2 * 8, 3 * 8}, "Radiotext", Color::light_grey()},
|
{{2 * 8, 3 * 8}, "Radiotext", Theme::getInstance()->fg_light->foreground},
|
||||||
{{1 * 8, 6 * 8}, "Text:", Color::light_grey()}};
|
{{1 * 8, 6 * 8}, "Text:", Theme::getInstance()->fg_light->foreground}};
|
||||||
|
|
||||||
Text text_radiotext{
|
Text text_radiotext{
|
||||||
{1 * 8, 4 * 16, 28 * 8, 16},
|
{1 * 8, 4 * 16, 28 * 8, 16},
|
||||||
@@ -92,7 +92,7 @@ class RDSDateTimeView : public OptionTabView {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
Labels labels{
|
Labels labels{
|
||||||
{{44, 5 * 16}, "Not yet implemented", Color::red()}};
|
{{44, 5 * 16}, "Not yet implemented", Theme::getInstance()->error_dark->foreground}};
|
||||||
};
|
};
|
||||||
|
|
||||||
class RDSAudioView : public OptionTabView {
|
class RDSAudioView : public OptionTabView {
|
||||||
@@ -101,7 +101,7 @@ class RDSAudioView : public OptionTabView {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
Labels labels{
|
Labels labels{
|
||||||
{{44, 5 * 16}, "Not yet implemented", Color::red()}};
|
{{44, 5 * 16}, "Not yet implemented", Theme::getInstance()->error_dark->foreground}};
|
||||||
};
|
};
|
||||||
|
|
||||||
class RDSThread {
|
class RDSThread {
|
||||||
@@ -168,16 +168,16 @@ class RDSView : public View {
|
|||||||
RDSAudioView view_audio{view_rect};
|
RDSAudioView view_audio{view_rect};
|
||||||
|
|
||||||
TabView tab_view{
|
TabView tab_view{
|
||||||
{"Name", Color::cyan(), &view_PSN},
|
{"Name", Theme::getInstance()->fg_cyan->foreground, &view_PSN},
|
||||||
{"Text", Color::green(), &view_radiotext},
|
{"Text", Theme::getInstance()->fg_green->foreground, &view_radiotext},
|
||||||
{"Time", Color::yellow(), &view_datetime},
|
{"Time", Theme::getInstance()->fg_yellow->foreground, &view_datetime},
|
||||||
{"Audio", Color::orange(), &view_audio}};
|
{"Audio", Theme::getInstance()->fg_orange->foreground, &view_audio}};
|
||||||
|
|
||||||
Labels labels{
|
Labels labels{
|
||||||
{{0 * 8, 28}, "Program type:", Color::light_grey()},
|
{{0 * 8, 28}, "Program type:", Theme::getInstance()->fg_light->foreground},
|
||||||
//{ { 14 * 8, 16 + 8 }, "CC:", Color::light_grey() },
|
//{ { 14 * 8, 16 + 8 }, "CC:", Theme::getInstance()->fg_light->foreground },
|
||||||
{{2 * 8, 28 + 16}, "Program ID:", Color::light_grey()},
|
{{2 * 8, 28 + 16}, "Program ID:", Theme::getInstance()->fg_light->foreground},
|
||||||
//{ { 13 * 8, 32 + 8 }, "Cov:", Color::light_grey() },
|
//{ { 13 * 8, 32 + 8 }, "Cov:",Theme::getInstance()->fg_light->foreground },
|
||||||
};
|
};
|
||||||
|
|
||||||
OptionsField options_pty{
|
OptionsField options_pty{
|
||||||
|
|||||||
@@ -22,13 +22,24 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "ui_recon.hpp"
|
#include "ui_recon.hpp"
|
||||||
#include "ui_fileman.hpp"
|
|
||||||
#include "ui_freqman.hpp"
|
#include "ui_freqman.hpp"
|
||||||
#include "capture_app.hpp"
|
#include "capture_app.hpp"
|
||||||
#include "convert.hpp"
|
#include "convert.hpp"
|
||||||
#include "file.hpp"
|
#include "file.hpp"
|
||||||
#include "file_reader.hpp"
|
#include "file_reader.hpp"
|
||||||
#include "tone_key.hpp"
|
#include "tone_key.hpp"
|
||||||
|
#include "replay_app.hpp"
|
||||||
|
#include "string_format.hpp"
|
||||||
|
#include "ui_fileman.hpp"
|
||||||
|
#include "io_file.hpp"
|
||||||
|
#include "io_convert.hpp"
|
||||||
|
#include "oversample.hpp"
|
||||||
|
#include "baseband_api.hpp"
|
||||||
|
#include "metadata_file.hpp"
|
||||||
|
#include "portapack.hpp"
|
||||||
|
#include "portapack_persistent_memory.hpp"
|
||||||
|
#include "utility.hpp"
|
||||||
|
#include "replay_thread.hpp"
|
||||||
|
|
||||||
using namespace portapack;
|
using namespace portapack;
|
||||||
using namespace tonekey;
|
using namespace tonekey;
|
||||||
@@ -37,6 +48,36 @@ namespace fs = std::filesystem;
|
|||||||
|
|
||||||
namespace ui {
|
namespace ui {
|
||||||
|
|
||||||
|
void ReconView::reload_restart_recon() {
|
||||||
|
// force reload of current
|
||||||
|
change_mode(field_mode.selected_index_value());
|
||||||
|
uint8_t previous_index = current_index;
|
||||||
|
reset_indexes();
|
||||||
|
frequency_file_load();
|
||||||
|
current_index = previous_index;
|
||||||
|
handle_retune();
|
||||||
|
if (frequency_list.size() > 0) {
|
||||||
|
if (fwd) {
|
||||||
|
button_dir.set_text("FW>");
|
||||||
|
} else {
|
||||||
|
button_dir.set_text("<RW");
|
||||||
|
}
|
||||||
|
recon_resume();
|
||||||
|
}
|
||||||
|
if (scanner_mode) {
|
||||||
|
file_name.set_style(Theme::getInstance()->fg_red);
|
||||||
|
button_scanner_mode.set_style(Theme::getInstance()->fg_red);
|
||||||
|
button_scanner_mode.set_text("SCAN");
|
||||||
|
} else {
|
||||||
|
file_name.set_style(Theme::getInstance()->fg_blue);
|
||||||
|
button_scanner_mode.set_style(Theme::getInstance()->fg_blue);
|
||||||
|
button_scanner_mode.set_text("RECON");
|
||||||
|
}
|
||||||
|
if (frequency_list.size() > FREQMAN_MAX_PER_FILE) {
|
||||||
|
file_name.set_style(Theme::getInstance()->fg_yellow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void ReconView::check_update_ranges_from_current() {
|
void ReconView::check_update_ranges_from_current() {
|
||||||
if (frequency_list.size() && current_is_valid() && current_entry().type == freqman_type::Range) {
|
if (frequency_list.size() && current_is_valid() && current_entry().type == freqman_type::Range) {
|
||||||
if (update_ranges && !manual_mode) {
|
if (update_ranges && !manual_mode) {
|
||||||
@@ -63,25 +104,30 @@ freqman_entry& ReconView::current_entry() {
|
|||||||
|
|
||||||
void ReconView::set_loop_config(bool v) {
|
void ReconView::set_loop_config(bool v) {
|
||||||
continuous = v;
|
continuous = v;
|
||||||
button_loop_config.set_style(v ? &Styles::green : &Styles::white);
|
button_loop_config.set_style(v ? Theme::getInstance()->fg_green : Theme::getInstance()->bg_darkest);
|
||||||
persistent_memory::set_recon_continuous(continuous);
|
persistent_memory::set_recon_continuous(continuous);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ReconView::recon_stop_recording() {
|
void ReconView::recon_stop_recording(bool exiting) {
|
||||||
if (is_recording) {
|
if (is_recording) {
|
||||||
if (field_mode.selected_index_value() == SPEC_MODULATION)
|
|
||||||
button_audio_app.set_text("RAW");
|
|
||||||
else
|
|
||||||
button_audio_app.set_text("AUDIO");
|
|
||||||
button_audio_app.set_style(&Styles::white);
|
|
||||||
record_view->stop();
|
record_view->stop();
|
||||||
button_config.set_style(&Styles::white); // disable config while recording as it's causing an IO error pop up at exit
|
|
||||||
is_recording = false;
|
is_recording = false;
|
||||||
|
if (field_mode.selected_index_value() == SPEC_MODULATION) {
|
||||||
|
button_audio_app.set_text("RAW");
|
||||||
|
// repeater mode
|
||||||
|
if (!exiting && persistent_memory::recon_repeat_recorded()) {
|
||||||
|
start_repeat();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
button_audio_app.set_text("AUDIO");
|
||||||
|
}
|
||||||
|
button_audio_app.set_style(Theme::getInstance()->bg_darkest);
|
||||||
|
button_config.set_style(Theme::getInstance()->bg_darkest);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ReconView::clear_freqlist_for_ui_action() {
|
void ReconView::clear_freqlist_for_ui_action() {
|
||||||
recon_stop_recording();
|
recon_stop_recording(false);
|
||||||
if (field_mode.selected_index_value() != SPEC_MODULATION)
|
if (field_mode.selected_index_value() != SPEC_MODULATION)
|
||||||
audio::output::stop();
|
audio::output::stop();
|
||||||
// flag to detect and reload frequency_list
|
// flag to detect and reload frequency_list
|
||||||
@@ -111,6 +157,9 @@ void ReconView::update_description() {
|
|||||||
case freqman_type::HamRadio:
|
case freqman_type::HamRadio:
|
||||||
description = "H: ";
|
description = "H: ";
|
||||||
break;
|
break;
|
||||||
|
case freqman_type::Repeater:
|
||||||
|
description = "L: ";
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
description = "S: ";
|
description = "S: ";
|
||||||
}
|
}
|
||||||
@@ -122,23 +171,23 @@ void ReconView::update_description() {
|
|||||||
void ReconView::colorize_waits() {
|
void ReconView::colorize_waits() {
|
||||||
// colorize wait on match
|
// colorize wait on match
|
||||||
if (wait == 0) {
|
if (wait == 0) {
|
||||||
field_wait.set_style(&Styles::blue);
|
field_wait.set_style(Theme::getInstance()->fg_blue);
|
||||||
} else if (wait >= 500) {
|
} else if (wait >= 500) {
|
||||||
field_wait.set_style(&Styles::white);
|
field_wait.set_style(Theme::getInstance()->bg_darkest);
|
||||||
} else if (wait > -500 && wait < 500) {
|
} else if (wait > -500 && wait < 500) {
|
||||||
field_wait.set_style(&Styles::red);
|
field_wait.set_style(Theme::getInstance()->fg_red);
|
||||||
} else if (wait <= -500) {
|
} else if (wait <= -500) {
|
||||||
field_wait.set_style(&Styles::green);
|
field_wait.set_style(Theme::getInstance()->fg_green);
|
||||||
}
|
}
|
||||||
// colorize lock time if in SPARSE mode as in continuous the lock_wait time is disarmed at first lock count
|
// colorize lock time if in SPARSE mode as in continuous the lock_wait time is disarmed at first lock count
|
||||||
if (recon_match_mode == RECON_MATCH_SPARSE) {
|
if (recon_match_mode == RECON_MATCH_SPARSE) {
|
||||||
if ((recon_lock_duration / STATS_UPDATE_INTERVAL) <= recon_lock_nb_match) {
|
if ((recon_lock_duration / STATS_UPDATE_INTERVAL) <= recon_lock_nb_match) {
|
||||||
field_lock_wait.set_style(&Styles::yellow);
|
field_lock_wait.set_style(Theme::getInstance()->fg_yellow);
|
||||||
} else {
|
} else {
|
||||||
field_lock_wait.set_style(&Styles::white);
|
field_lock_wait.set_style(Theme::getInstance()->bg_darkest);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
field_lock_wait.set_style(&Styles::white);
|
field_lock_wait.set_style(Theme::getInstance()->bg_darkest);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,6 +231,7 @@ void ReconView::load_persisted_settings() {
|
|||||||
load_freqs = persistent_memory::recon_load_freqs();
|
load_freqs = persistent_memory::recon_load_freqs();
|
||||||
load_ranges = persistent_memory::recon_load_ranges();
|
load_ranges = persistent_memory::recon_load_ranges();
|
||||||
load_hamradios = persistent_memory::recon_load_hamradios();
|
load_hamradios = persistent_memory::recon_load_hamradios();
|
||||||
|
load_repeaters = persistent_memory::recon_load_repeaters();
|
||||||
update_ranges = persistent_memory::recon_update_ranges_when_recon();
|
update_ranges = persistent_memory::recon_update_ranges_when_recon();
|
||||||
auto_record_locked = persistent_memory::recon_auto_record_locked();
|
auto_record_locked = persistent_memory::recon_auto_record_locked();
|
||||||
}
|
}
|
||||||
@@ -209,17 +259,17 @@ void ReconView::recon_redraw() {
|
|||||||
text_nb_locks.set(to_string_dec_uint(freq_lock) + "/" + to_string_dec_uint(recon_lock_nb_match));
|
text_nb_locks.set(to_string_dec_uint(freq_lock) + "/" + to_string_dec_uint(recon_lock_nb_match));
|
||||||
if (freq_lock == 0) {
|
if (freq_lock == 0) {
|
||||||
// NO FREQ LOCK, ONGOING STANDARD SCANNING
|
// NO FREQ LOCK, ONGOING STANDARD SCANNING
|
||||||
big_display.set_style(&Styles::white);
|
big_display.set_style(Theme::getInstance()->bg_darkest);
|
||||||
if (recon)
|
if (recon)
|
||||||
button_pause.set_text("<PAUSE>");
|
button_pause.set_text("<PAUSE>");
|
||||||
else
|
else
|
||||||
button_pause.set_text("<RESUME>");
|
button_pause.set_text("<RESUME>");
|
||||||
} else if (freq_lock == 1 && recon_lock_nb_match != 1) {
|
} else if (freq_lock == 1 && recon_lock_nb_match != 1) {
|
||||||
// STARTING LOCK FREQ
|
// STARTING LOCK FREQ
|
||||||
big_display.set_style(&Styles::yellow);
|
big_display.set_style(Theme::getInstance()->fg_yellow);
|
||||||
button_pause.set_text("<SKPLCK>");
|
button_pause.set_text("<SKPLCK>");
|
||||||
} else if (freq_lock >= recon_lock_nb_match) {
|
} else if (freq_lock >= recon_lock_nb_match) {
|
||||||
big_display.set_style(&Styles::green);
|
big_display.set_style(Theme::getInstance()->fg_green);
|
||||||
button_pause.set_text("<UNLOCK>");
|
button_pause.set_text("<UNLOCK>");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -265,9 +315,16 @@ void ReconView::focus() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ReconView::~ReconView() {
|
ReconView::~ReconView() {
|
||||||
recon_stop_recording();
|
if (recon_tx) {
|
||||||
|
replay_thread.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
recon_stop_recording(true);
|
||||||
|
|
||||||
if (field_mode.selected_index_value() != SPEC_MODULATION)
|
if (field_mode.selected_index_value() != SPEC_MODULATION)
|
||||||
audio::output::stop();
|
audio::output::stop();
|
||||||
|
|
||||||
|
transmitter_model.disable();
|
||||||
receiver_model.disable();
|
receiver_model.disable();
|
||||||
baseband::shutdown();
|
baseband::shutdown();
|
||||||
}
|
}
|
||||||
@@ -275,9 +332,20 @@ ReconView::~ReconView() {
|
|||||||
ReconView::ReconView(NavigationView& nav)
|
ReconView::ReconView(NavigationView& nav)
|
||||||
: nav_{nav} {
|
: nav_{nav} {
|
||||||
chrono_start = chTimeNow();
|
chrono_start = chTimeNow();
|
||||||
|
|
||||||
|
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, 30 * 8, 1 * 16},
|
||||||
u"AUTO_AUDIO_", u"AUDIO",
|
u"AUTO_AUDIO", audio_dir,
|
||||||
RecordView::FileType::WAV, 4096, 4);
|
RecordView::FileType::WAV, 4096, 4);
|
||||||
|
record_view->set_filename_date_frequency(true);
|
||||||
|
record_view->set_auto_trim(false);
|
||||||
|
record_view->hidden(true);
|
||||||
|
record_view->on_error = [&nav](std::string message) {
|
||||||
|
nav.display_modal("Error", message);
|
||||||
|
};
|
||||||
|
|
||||||
add_children({&labels,
|
add_children({&labels,
|
||||||
&field_lna,
|
&field_lna,
|
||||||
&field_vga,
|
&field_vga,
|
||||||
@@ -314,13 +382,9 @@ ReconView::ReconView(NavigationView& nav)
|
|||||||
&button_restart,
|
&button_restart,
|
||||||
&button_mic_app,
|
&button_mic_app,
|
||||||
&button_remove,
|
&button_remove,
|
||||||
record_view.get()});
|
record_view.get(),
|
||||||
|
&progressbar,
|
||||||
record_view->hidden(true);
|
&tx_view});
|
||||||
record_view->set_filename_date_frequency(true);
|
|
||||||
record_view->on_error = [&nav](std::string message) {
|
|
||||||
nav.display_modal("Error", message);
|
|
||||||
};
|
|
||||||
|
|
||||||
def_step = 0;
|
def_step = 0;
|
||||||
load_persisted_settings();
|
load_persisted_settings();
|
||||||
@@ -444,7 +508,7 @@ ReconView::ReconView(NavigationView& nav)
|
|||||||
auto settings = receiver_model.settings();
|
auto settings = receiver_model.settings();
|
||||||
settings.frequency_step = step_mode.selected_index_value();
|
settings.frequency_step = step_mode.selected_index_value();
|
||||||
if (field_mode.selected_index_value() == SPEC_MODULATION)
|
if (field_mode.selected_index_value() == SPEC_MODULATION)
|
||||||
nav_.replace<CaptureAppView>();
|
nav_.replace<CaptureAppView>(settings);
|
||||||
else
|
else
|
||||||
nav_.replace<AnalogAudioView>(settings);
|
nav_.replace<AnalogAudioView>(settings);
|
||||||
};
|
};
|
||||||
@@ -475,7 +539,7 @@ ReconView::ReconView(NavigationView& nav)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MicTX wants Modulation and Bandwidth overrides, but that's only stored on the RX model.
|
// MicTX wants Frequency, Modulation and Bandwidth overrides, but that's only stored on the RX model.
|
||||||
nav_.replace<MicTXView>(receiver_model.settings());
|
nav_.replace<MicTXView>(receiver_model.settings());
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -492,7 +556,7 @@ ReconView::ReconView(NavigationView& nav)
|
|||||||
};
|
};
|
||||||
|
|
||||||
button_manual_recon.on_select = [this](Button&) {
|
button_manual_recon.on_select = [this](Button&) {
|
||||||
button_remove.set_text("DELETE");
|
button_remove.set_text("<DELETE>");
|
||||||
button_add.hidden(false);
|
button_add.hidden(false);
|
||||||
scanner_mode = false;
|
scanner_mode = false;
|
||||||
manual_mode = true;
|
manual_mode = true;
|
||||||
@@ -527,18 +591,18 @@ ReconView::ReconView(NavigationView& nav)
|
|||||||
current_entry().bandwidth = freqman_invalid_index;
|
current_entry().bandwidth = freqman_invalid_index;
|
||||||
current_entry().step = def_step;
|
current_entry().step = def_step;
|
||||||
|
|
||||||
big_display.set_style(&Styles::white); // Back to white color
|
big_display.set_style(Theme::getInstance()->bg_darkest); // Back to white color
|
||||||
|
|
||||||
freq_stats.set_style(&Styles::white);
|
freq_stats.set_style(Theme::getInstance()->bg_darkest);
|
||||||
freq_stats.set("0/0/0");
|
freq_stats.set("0/0/0");
|
||||||
|
|
||||||
text_cycle.set_text("1");
|
text_cycle.set_text("1");
|
||||||
text_max.set("/1");
|
text_max.set("/1");
|
||||||
button_scanner_mode.set_style(&Styles::white);
|
button_scanner_mode.set_style(Theme::getInstance()->bg_darkest);
|
||||||
button_scanner_mode.set_text("MANUAL");
|
button_scanner_mode.set_text("MANUAL");
|
||||||
file_name.set_style(&Styles::white);
|
file_name.set_style(Theme::getInstance()->bg_darkest);
|
||||||
file_name.set("MANUAL => " + output_file);
|
file_name.set("MANUAL => " + output_file);
|
||||||
desc_cycle.set_style(&Styles::white);
|
desc_cycle.set_style(Theme::getInstance()->bg_darkest);
|
||||||
|
|
||||||
last_entry.modulation = freqman_invalid_index;
|
last_entry.modulation = freqman_invalid_index;
|
||||||
last_entry.bandwidth = freqman_invalid_index;
|
last_entry.bandwidth = freqman_invalid_index;
|
||||||
@@ -566,27 +630,7 @@ ReconView::ReconView(NavigationView& nav)
|
|||||||
};
|
};
|
||||||
|
|
||||||
button_restart.on_select = [this](Button&) {
|
button_restart.on_select = [this](Button&) {
|
||||||
frequency_file_load();
|
reload_restart_recon();
|
||||||
if (frequency_list.size() > 0) {
|
|
||||||
if (fwd) {
|
|
||||||
button_dir.set_text("FW>");
|
|
||||||
} else {
|
|
||||||
button_dir.set_text("<RW");
|
|
||||||
}
|
|
||||||
recon_resume();
|
|
||||||
}
|
|
||||||
if (scanner_mode) {
|
|
||||||
file_name.set_style(&Styles::red);
|
|
||||||
button_scanner_mode.set_style(&Styles::red);
|
|
||||||
button_scanner_mode.set_text("SCAN");
|
|
||||||
} else {
|
|
||||||
file_name.set_style(&Styles::blue);
|
|
||||||
button_scanner_mode.set_style(&Styles::blue);
|
|
||||||
button_scanner_mode.set_text("RECON");
|
|
||||||
}
|
|
||||||
if (frequency_list.size() > FREQMAN_MAX_PER_FILE) {
|
|
||||||
file_name.set_style(&Styles::yellow);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
button_add.on_select = [this](ButtonWithEncoder&) {
|
button_add.on_select = [this](ButtonWithEncoder&) {
|
||||||
@@ -604,14 +648,14 @@ ReconView::ReconView(NavigationView& nav)
|
|||||||
manual_mode = false;
|
manual_mode = false;
|
||||||
if (scanner_mode) {
|
if (scanner_mode) {
|
||||||
scanner_mode = false;
|
scanner_mode = false;
|
||||||
button_scanner_mode.set_style(&Styles::blue);
|
button_scanner_mode.set_style(Theme::getInstance()->fg_blue);
|
||||||
button_scanner_mode.set_text("RECON");
|
button_scanner_mode.set_text("RECON");
|
||||||
button_remove.set_text("REMOVE");
|
button_remove.set_text("<REMOVE>");
|
||||||
} else {
|
} else {
|
||||||
scanner_mode = true;
|
scanner_mode = true;
|
||||||
button_scanner_mode.set_style(&Styles::red);
|
button_scanner_mode.set_style(Theme::getInstance()->fg_red);
|
||||||
button_scanner_mode.set_text("SCAN");
|
button_scanner_mode.set_text("SCAN");
|
||||||
button_remove.set_text("DELETE");
|
button_remove.set_text("<DELETE>");
|
||||||
}
|
}
|
||||||
frequency_file_load();
|
frequency_file_load();
|
||||||
if (autostart) {
|
if (autostart) {
|
||||||
@@ -682,7 +726,7 @@ ReconView::ReconView(NavigationView& nav)
|
|||||||
};
|
};
|
||||||
|
|
||||||
// PRE-CONFIGURATION:
|
// PRE-CONFIGURATION:
|
||||||
button_scanner_mode.set_style(&Styles::blue);
|
button_scanner_mode.set_style(Theme::getInstance()->fg_blue);
|
||||||
button_scanner_mode.set_text("RECON");
|
button_scanner_mode.set_text("RECON");
|
||||||
file_name.set("=>");
|
file_name.set("=>");
|
||||||
|
|
||||||
@@ -704,6 +748,9 @@ ReconView::ReconView(NavigationView& nav)
|
|||||||
change_mode(AM_MODULATION); // start on AM.
|
change_mode(AM_MODULATION); // start on AM.
|
||||||
field_mode.set_by_value(AM_MODULATION); // reflect the mode into the manual selector
|
field_mode.set_by_value(AM_MODULATION); // reflect the mode into the manual selector
|
||||||
|
|
||||||
|
// tx progress bar
|
||||||
|
progressbar.hidden(true);
|
||||||
|
|
||||||
if (filedelete) {
|
if (filedelete) {
|
||||||
delete_file(freq_file_path);
|
delete_file(freq_file_path);
|
||||||
}
|
}
|
||||||
@@ -725,14 +772,14 @@ void ReconView::frequency_file_load() {
|
|||||||
std::string file_input = input_file; // default recon mode
|
std::string file_input = input_file; // default recon mode
|
||||||
if (scanner_mode) {
|
if (scanner_mode) {
|
||||||
file_input = output_file;
|
file_input = output_file;
|
||||||
file_name.set_style(&Styles::red);
|
file_name.set_style(Theme::getInstance()->fg_red);
|
||||||
button_scanner_mode.set_style(&Styles::red);
|
button_scanner_mode.set_style(Theme::getInstance()->fg_red);
|
||||||
desc_cycle.set_style(&Styles::red);
|
desc_cycle.set_style(Theme::getInstance()->fg_red);
|
||||||
button_scanner_mode.set_text("SCAN");
|
button_scanner_mode.set_text("SCAN");
|
||||||
} else {
|
} else {
|
||||||
file_name.set_style(&Styles::blue);
|
file_name.set_style(Theme::getInstance()->fg_blue);
|
||||||
button_scanner_mode.set_style(&Styles::blue);
|
button_scanner_mode.set_style(Theme::getInstance()->fg_blue);
|
||||||
desc_cycle.set_style(&Styles::blue);
|
desc_cycle.set_style(Theme::getInstance()->fg_blue);
|
||||||
button_scanner_mode.set_text("RECON");
|
button_scanner_mode.set_text("RECON");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -741,9 +788,10 @@ void ReconView::frequency_file_load() {
|
|||||||
freqman_load_options options{
|
freqman_load_options options{
|
||||||
.load_freqs = load_freqs,
|
.load_freqs = load_freqs,
|
||||||
.load_ranges = load_ranges,
|
.load_ranges = load_ranges,
|
||||||
.load_hamradios = load_hamradios};
|
.load_hamradios = load_hamradios,
|
||||||
|
.load_repeaters = load_repeaters};
|
||||||
if (!load_freqman_file(file_input, frequency_list, options) || frequency_list.empty()) {
|
if (!load_freqman_file(file_input, frequency_list, options) || frequency_list.empty()) {
|
||||||
file_name.set_style(&Styles::red);
|
file_name.set_style(Theme::getInstance()->fg_red);
|
||||||
desc_cycle.set("...empty file...");
|
desc_cycle.set("...empty file...");
|
||||||
frequency_list.clear();
|
frequency_list.clear();
|
||||||
text_cycle.set_text(" ");
|
text_cycle.set_text(" ");
|
||||||
@@ -751,7 +799,7 @@ void ReconView::frequency_file_load() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (frequency_list.size() > FREQMAN_MAX_PER_FILE) {
|
if (frequency_list.size() > FREQMAN_MAX_PER_FILE) {
|
||||||
file_name.set_style(&Styles::yellow);
|
file_name.set_style(Theme::getInstance()->fg_yellow);
|
||||||
}
|
}
|
||||||
|
|
||||||
reset_indexes();
|
reset_indexes();
|
||||||
@@ -776,6 +824,12 @@ void ReconView::frequency_file_load() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
|
void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
|
||||||
|
if (recon_tx)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (is_repeat_active())
|
||||||
|
return;
|
||||||
|
|
||||||
chrono_end = chTimeNow();
|
chrono_end = chTimeNow();
|
||||||
systime_t time_interval = chrono_end - chrono_start;
|
systime_t time_interval = chrono_end - chrono_start;
|
||||||
chrono_start = chrono_end;
|
chrono_start = chrono_end;
|
||||||
@@ -804,7 +858,7 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
|
|||||||
if (status != 1) {
|
if (status != 1) {
|
||||||
status = 1;
|
status = 1;
|
||||||
if (wait != 0) {
|
if (wait != 0) {
|
||||||
recon_stop_recording();
|
recon_stop_recording(false);
|
||||||
if (field_mode.selected_index_value() != SPEC_MODULATION)
|
if (field_mode.selected_index_value() != SPEC_MODULATION)
|
||||||
audio::output::stop();
|
audio::output::stop();
|
||||||
}
|
}
|
||||||
@@ -834,13 +888,13 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
|
|||||||
audio_output_start();
|
audio_output_start();
|
||||||
// contents of a possible recon_start_recording(), but not yet since it's only called once
|
// contents of a possible recon_start_recording(), but not yet since it's only called once
|
||||||
if (auto_record_locked && !is_recording) {
|
if (auto_record_locked && !is_recording) {
|
||||||
button_audio_app.set_style(&Styles::red);
|
button_audio_app.set_style(Theme::getInstance()->fg_red);
|
||||||
if (field_mode.selected_index_value() == SPEC_MODULATION)
|
if (field_mode.selected_index_value() == SPEC_MODULATION) {
|
||||||
button_audio_app.set_text("RAW REC");
|
button_audio_app.set_text("RAW REC");
|
||||||
else
|
} else
|
||||||
button_audio_app.set_text("WAV REC");
|
button_audio_app.set_text("WAV REC");
|
||||||
record_view->start();
|
record_view->start();
|
||||||
button_config.set_style(&Styles::light_grey); // disable config while recording as it's causing an IO error pop up at exit
|
button_config.set_style(Theme::getInstance()->fg_light); // disable config while recording as it's causing an IO error pop up at exit
|
||||||
is_recording = true;
|
is_recording = true;
|
||||||
}
|
}
|
||||||
// FREQ IS STRONG: GREEN and recon will pause when on_statistics_update()
|
// FREQ IS STRONG: GREEN and recon will pause when on_statistics_update()
|
||||||
@@ -956,6 +1010,26 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (current_entry().type == freqman_type::Repeater) {
|
||||||
|
// repeater is like single, we only listen on frequency_a and then jump to next entry
|
||||||
|
if ((fwd && stepper == 0) || stepper > 0) { // forward
|
||||||
|
current_index++;
|
||||||
|
entry_has_changed = true;
|
||||||
|
// looping
|
||||||
|
if ((uint32_t)current_index >= frequency_list.size()) {
|
||||||
|
has_looped = true;
|
||||||
|
current_index = 0;
|
||||||
|
}
|
||||||
|
} else if ((!fwd && stepper == 0) || stepper < 0) {
|
||||||
|
// reverse
|
||||||
|
current_index--;
|
||||||
|
entry_has_changed = true;
|
||||||
|
// if previous if under the list => go back from end
|
||||||
|
if (current_index < 0) {
|
||||||
|
has_looped = true;
|
||||||
|
current_index = frequency_list.size() - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// set index to boundary if !continuous
|
// set index to boundary if !continuous
|
||||||
if (has_looped && !continuous) {
|
if (has_looped && !continuous) {
|
||||||
@@ -984,6 +1058,7 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
|
|||||||
if (entry_has_changed) {
|
if (entry_has_changed) {
|
||||||
timer = 0;
|
timer = 0;
|
||||||
switch (current_entry().type) {
|
switch (current_entry().type) {
|
||||||
|
case freqman_type::Repeater:
|
||||||
case freqman_type::Single:
|
case freqman_type::Single:
|
||||||
freq = current_entry().frequency_a;
|
freq = current_entry().frequency_a;
|
||||||
break;
|
break;
|
||||||
@@ -1017,7 +1092,7 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
|
|||||||
if (stepper > 0) stepper--;
|
if (stepper > 0) stepper--;
|
||||||
} // if( recon || stepper != 0 || index_stepper != 0 )
|
} // if( recon || stepper != 0 || index_stepper != 0 )
|
||||||
} // if (frequency_list.size() > 0 )
|
} // if (frequency_list.size() > 0 )
|
||||||
} /* on_statistic_updates */
|
} /* on_statistics_updates */
|
||||||
}
|
}
|
||||||
handle_retune();
|
handle_retune();
|
||||||
recon_redraw();
|
recon_redraw();
|
||||||
@@ -1031,7 +1106,7 @@ void ReconView::recon_pause() {
|
|||||||
if (field_mode.selected_index_value() != SPEC_MODULATION)
|
if (field_mode.selected_index_value() != SPEC_MODULATION)
|
||||||
audio_output_start();
|
audio_output_start();
|
||||||
|
|
||||||
big_display.set_style(&Styles::white);
|
big_display.set_style(Theme::getInstance()->bg_darkest);
|
||||||
button_pause.set_text("<RESUME>"); // PAUSED, show resume
|
button_pause.set_text("<RESUME>"); // PAUSED, show resume
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1043,7 +1118,7 @@ void ReconView::recon_resume() {
|
|||||||
if (field_mode.selected_index_value() != SPEC_MODULATION)
|
if (field_mode.selected_index_value() != SPEC_MODULATION)
|
||||||
audio::output::stop();
|
audio::output::stop();
|
||||||
|
|
||||||
big_display.set_style(&Styles::white);
|
big_display.set_style(Theme::getInstance()->bg_darkest);
|
||||||
button_pause.set_text("<PAUSE>");
|
button_pause.set_text("<PAUSE>");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1080,28 +1155,44 @@ void ReconView::on_stepper_delta(int32_t v) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
size_t ReconView::change_mode(freqman_index_t new_mod) {
|
size_t ReconView::change_mode(freqman_index_t new_mod) {
|
||||||
|
if (recon_tx || is_repeat_active() || is_recording)
|
||||||
|
return 0;
|
||||||
field_mode.on_change = [this](size_t, OptionsField::value_t) {};
|
field_mode.on_change = [this](size_t, OptionsField::value_t) {};
|
||||||
field_bw.on_change = [this](size_t, OptionsField::value_t) {};
|
field_bw.on_change = [this](size_t, OptionsField::value_t) {};
|
||||||
recon_stop_recording();
|
recon_stop_recording(false);
|
||||||
remove_child(record_view.get());
|
if (record_view != nullptr) {
|
||||||
record_view.reset();
|
remove_child(record_view.get());
|
||||||
if (new_mod == SPEC_MODULATION) {
|
record_view.reset();
|
||||||
|
}
|
||||||
|
if (field_mode.selected_index_value() != SPEC_MODULATION) {
|
||||||
audio::output::stop();
|
audio::output::stop();
|
||||||
record_view = std::make_unique<RecordView>(Rect{0, 0, 30 * 8, 1 * 16},
|
}
|
||||||
u"AUTO_RAW_", u"CAPTURES",
|
if (new_mod == SPEC_MODULATION) {
|
||||||
RecordView::FileType::RawS16, 16384, 3);
|
if (persistent_memory::recon_repeat_recorded()) {
|
||||||
|
record_view = std::make_unique<RecordView>(Rect{0, 0, 30 * 8, 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},
|
||||||
|
u"AUTO_RAW", captures_dir,
|
||||||
|
RecordView::FileType::RawS16, 16384, 3);
|
||||||
|
record_view->set_filename_date_frequency(true);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
record_view = std::make_unique<RecordView>(Rect{0, 0, 30 * 8, 1 * 16},
|
record_view = std::make_unique<RecordView>(Rect{0, 0, 30 * 8, 1 * 16},
|
||||||
u"AUTO_AUDIO_", u"AUDIO",
|
u"AUTO_AUDIO", audio_dir,
|
||||||
RecordView::FileType::WAV, 4096, 4);
|
RecordView::FileType::WAV, 4096, 4);
|
||||||
|
record_view->set_filename_date_frequency(true);
|
||||||
}
|
}
|
||||||
|
record_view->set_auto_trim(false);
|
||||||
add_child(record_view.get());
|
add_child(record_view.get());
|
||||||
record_view->hidden(true);
|
record_view->hidden(true);
|
||||||
record_view->set_filename_date_frequency(true);
|
|
||||||
record_view->on_error = [this](std::string message) {
|
record_view->on_error = [this](std::string message) {
|
||||||
nav_.display_modal("Error", message);
|
nav_.display_modal("Error", message);
|
||||||
};
|
};
|
||||||
receiver_model.disable();
|
receiver_model.disable();
|
||||||
|
transmitter_model.disable();
|
||||||
baseband::shutdown();
|
baseband::shutdown();
|
||||||
size_t recording_sampling_rate = 0;
|
size_t recording_sampling_rate = 0;
|
||||||
switch (new_mod) {
|
switch (new_mod) {
|
||||||
@@ -1144,7 +1235,6 @@ size_t ReconView::change_mode(freqman_index_t new_mod) {
|
|||||||
field_bw.on_change = [this](size_t, OptionsField::value_t sampling_rate) {
|
field_bw.on_change = [this](size_t, OptionsField::value_t sampling_rate) {
|
||||||
// record_view determines the correct oversampling to apply and returns the actual sample rate.
|
// record_view determines the correct oversampling to apply and returns the actual sample rate.
|
||||||
auto actual_sampling_rate = record_view->set_sampling_rate(sampling_rate);
|
auto actual_sampling_rate = record_view->set_sampling_rate(sampling_rate);
|
||||||
|
|
||||||
// The radio needs to know the effective sampling rate.
|
// The radio needs to know the effective sampling rate.
|
||||||
receiver_model.set_sampling_rate(actual_sampling_rate);
|
receiver_model.set_sampling_rate(actual_sampling_rate);
|
||||||
receiver_model.set_baseband_bandwidth(filter_bandwidth_for_sampling_rate(actual_sampling_rate));
|
receiver_model.set_baseband_bandwidth(filter_bandwidth_for_sampling_rate(actual_sampling_rate));
|
||||||
@@ -1222,4 +1312,156 @@ void ReconView::handle_remove_current_item() {
|
|||||||
update_description();
|
update_description();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ReconView::on_repeat_tx_progress(const uint32_t progress) {
|
||||||
|
progressbar.set_value(progress);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReconView::repeat_file_error(const std::filesystem::path& path, const std::string& message) {
|
||||||
|
nav_.display_modal("Error", "Error opening file \n" + path.string() + "\n" + message);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ReconView::is_repeat_active() const {
|
||||||
|
return replay_thread != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReconView::start_repeat() {
|
||||||
|
// Prepare to send a file.
|
||||||
|
if (recon_tx == false) {
|
||||||
|
recon_tx = true;
|
||||||
|
|
||||||
|
if (record_view != nullptr) {
|
||||||
|
record_view->stop();
|
||||||
|
remove_child(record_view.get());
|
||||||
|
record_view.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
receiver_model.disable();
|
||||||
|
transmitter_model.disable();
|
||||||
|
baseband::shutdown();
|
||||||
|
|
||||||
|
baseband::run_image(portapack::spi_flash::image_tag_replay);
|
||||||
|
|
||||||
|
size_t rawsize = 0;
|
||||||
|
{
|
||||||
|
File capture_file;
|
||||||
|
auto error = capture_file.open(rawfile);
|
||||||
|
if (error) {
|
||||||
|
repeat_file_error(rawfile, "Can't open file to send for size");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
rawsize = capture_file.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset the transmit progress bar.
|
||||||
|
uint8_t sample_size = std::filesystem::capture_file_sample_size(rawfile);
|
||||||
|
progressbar.set_value(0);
|
||||||
|
progressbar.set_max(rawsize * sizeof(complex16_t) / sample_size);
|
||||||
|
progressbar.hidden(false);
|
||||||
|
|
||||||
|
auto metadata = read_metadata_file(rawmeta);
|
||||||
|
// If no metadata found, fallback to the TX frequency.
|
||||||
|
if (!metadata) {
|
||||||
|
metadata = {freq, 500'000};
|
||||||
|
repeat_file_error(rawmeta, "Can't open file to read meta, using default rx_freq,500'000");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the sample rate in proc_replay baseband.
|
||||||
|
baseband::set_sample_rate(metadata->sample_rate,
|
||||||
|
get_oversample_rate(metadata->sample_rate));
|
||||||
|
|
||||||
|
transmitter_model.set_sampling_rate(get_actual_sample_rate(metadata->sample_rate));
|
||||||
|
transmitter_model.set_baseband_bandwidth(metadata->sample_rate <= 500'000 ? 1'750'000 : 2'500'000); // TX LPF min 1M75 for SR <=500K, and 2M5 (by experimental test) for SR >500K
|
||||||
|
|
||||||
|
// set TX to repeater TX freq if entry is Repeater and have a valid freq, else use recorded one
|
||||||
|
if (current_entry().type == freqman_type::Repeater && current_entry().frequency_b != 0) {
|
||||||
|
transmitter_model.set_target_frequency(current_entry().frequency_b);
|
||||||
|
} else {
|
||||||
|
transmitter_model.set_target_frequency(metadata->center_frequency);
|
||||||
|
}
|
||||||
|
|
||||||
|
// set TX powers and enable transmitter
|
||||||
|
transmitter_model.set_tx_gain(persistent_memory::recon_repeat_gain());
|
||||||
|
transmitter_model.set_rf_amp(persistent_memory::recon_repeat_amp());
|
||||||
|
transmitter_model.enable();
|
||||||
|
}
|
||||||
|
|
||||||
|
// clear replay thread and set reader
|
||||||
|
replay_thread.reset();
|
||||||
|
auto reader = std::make_unique<FileConvertReader>();
|
||||||
|
auto error = reader->open(rawfile);
|
||||||
|
if (error) {
|
||||||
|
repeat_file_error(rawfile, "Can't open file to send to thread");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// wait for TX if needed (hackish, direct screen update since the UI will be blocked)
|
||||||
|
if (persistent_memory::recon_repeat_delay() > 0) {
|
||||||
|
uint8_t delay = persistent_memory::recon_repeat_delay();
|
||||||
|
Painter p;
|
||||||
|
while (delay > 0) {
|
||||||
|
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);
|
||||||
|
|
||||||
|
// sleep 1 second
|
||||||
|
chThdSleepMilliseconds(1000);
|
||||||
|
|
||||||
|
// decre delay
|
||||||
|
if (delay > 0)
|
||||||
|
delay = delay - 1;
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReplayThread starts immediately on construction; must be set before creating.
|
||||||
|
repeat_ready_signal = true;
|
||||||
|
repeat_cur_rep++;
|
||||||
|
replay_thread = std::make_unique<ReplayThread>(
|
||||||
|
std::move(reader),
|
||||||
|
/* read_size */ repeat_read_size,
|
||||||
|
/* buffer_count */ repeat_buffer_count,
|
||||||
|
&repeat_ready_signal,
|
||||||
|
[](uint32_t return_code) {
|
||||||
|
ReplayThreadDoneMessage message{return_code};
|
||||||
|
EventDispatcher::send_message(message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReconView::stop_repeat(const bool do_loop) {
|
||||||
|
repeat_ready_signal = false;
|
||||||
|
|
||||||
|
if (is_repeat_active()) {
|
||||||
|
replay_thread.reset();
|
||||||
|
transmitter_model.disable();
|
||||||
|
}
|
||||||
|
|
||||||
|
// repeat transmit if current number of repetitions (repeat_cur_rep) is < recon configured number of repetitions (recon_repeat_nb)
|
||||||
|
if (do_loop && repeat_cur_rep < persistent_memory::recon_repeat_nb()) {
|
||||||
|
start_repeat();
|
||||||
|
} else {
|
||||||
|
repeat_cur_rep = 0;
|
||||||
|
recon_tx = false;
|
||||||
|
if (persistent_memory::recon_repeat_recorded_file_mode() == RECON_REPEAT_AND_KEEP) {
|
||||||
|
// rename file here to keep
|
||||||
|
std::filesystem::path base_path = next_filename_matching_pattern(repeat_rec_path / u"REC_????.*");
|
||||||
|
rename_file(rawfile, base_path.replace_extension(u".C16"));
|
||||||
|
rename_file(rawmeta, base_path.replace_extension(u".TXT"));
|
||||||
|
}
|
||||||
|
reload_restart_recon();
|
||||||
|
progressbar.hidden(true);
|
||||||
|
set_dirty(); // fix progressbar no hiding
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReconView::handle_repeat_thread_done(const uint32_t return_code) {
|
||||||
|
if (return_code == ReplayThread::END_OF_FILE) {
|
||||||
|
stop_repeat(true);
|
||||||
|
} else if (return_code == ReplayThread::READ_ERROR) {
|
||||||
|
stop_repeat(false);
|
||||||
|
repeat_file_error(rawfile, "Can't open file to send.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} /* namespace ui */
|
} /* namespace ui */
|
||||||
|
|||||||
@@ -27,7 +27,6 @@
|
|||||||
#include "ui.hpp"
|
#include "ui.hpp"
|
||||||
#include "receiver_model.hpp"
|
#include "receiver_model.hpp"
|
||||||
#include "ui_receiver.hpp"
|
#include "ui_receiver.hpp"
|
||||||
#include "ui_styles.hpp"
|
|
||||||
#include "freqman.hpp"
|
#include "freqman.hpp"
|
||||||
#include "analog_audio_app.hpp"
|
#include "analog_audio_app.hpp"
|
||||||
#include "audio.hpp"
|
#include "audio.hpp"
|
||||||
@@ -38,14 +37,23 @@
|
|||||||
#include "baseband_api.hpp"
|
#include "baseband_api.hpp"
|
||||||
#include "string_format.hpp"
|
#include "string_format.hpp"
|
||||||
#include "file.hpp"
|
#include "file.hpp"
|
||||||
|
#include "file_path.hpp"
|
||||||
#include "app_settings.hpp"
|
#include "app_settings.hpp"
|
||||||
#include "radio_state.hpp"
|
#include "radio_state.hpp"
|
||||||
#include "ui_recon_settings.hpp"
|
#include "ui_recon_settings.hpp"
|
||||||
|
#include "ui_transmitter.hpp"
|
||||||
|
#include "replay_thread.hpp"
|
||||||
|
#include "metadata_file.hpp"
|
||||||
|
#include "ui_widget.hpp"
|
||||||
|
#include "ui_navigation.hpp"
|
||||||
|
#include "ui_freq_field.hpp"
|
||||||
|
#include "ui_spectrum.hpp"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
namespace ui {
|
namespace ui {
|
||||||
|
|
||||||
#define RECON_CFG_FILE "SETTINGS/recon.cfg"
|
|
||||||
|
|
||||||
enum class recon_mode : uint8_t {
|
enum class recon_mode : uint8_t {
|
||||||
Recon,
|
Recon,
|
||||||
Scanner,
|
Scanner,
|
||||||
@@ -66,10 +74,17 @@ class ReconView : public View {
|
|||||||
private:
|
private:
|
||||||
NavigationView& nav_;
|
NavigationView& nav_;
|
||||||
|
|
||||||
RxRadioState radio_state_{};
|
RxRadioState rx_radio_state_{};
|
||||||
|
TxRadioState tx_radio_state_{
|
||||||
|
0 /* frequency */,
|
||||||
|
1750000 /* bandwidth */,
|
||||||
|
500000 /* sampling rate */
|
||||||
|
};
|
||||||
app_settings::SettingsManager settings_{
|
app_settings::SettingsManager settings_{
|
||||||
"rx_recon"sv, app_settings::Mode::RX};
|
"rx_tx_recon"sv, app_settings::Mode::RX_TX};
|
||||||
|
;
|
||||||
|
|
||||||
|
void reload_restart_recon();
|
||||||
void check_update_ranges_from_current();
|
void check_update_ranges_from_current();
|
||||||
void set_loop_config(bool v);
|
void set_loop_config(bool v);
|
||||||
void clear_freqlist_for_ui_action();
|
void clear_freqlist_for_ui_action();
|
||||||
@@ -93,7 +108,7 @@ class ReconView : public View {
|
|||||||
void load_persisted_settings();
|
void load_persisted_settings();
|
||||||
bool recon_save_freq(const std::filesystem::path& path, size_t index, bool warn_if_exists);
|
bool recon_save_freq(const std::filesystem::path& path, size_t index, bool warn_if_exists);
|
||||||
// placeholder for possible void recon_start_recording();
|
// placeholder for possible void recon_start_recording();
|
||||||
void recon_stop_recording();
|
void recon_stop_recording(bool exiting);
|
||||||
|
|
||||||
// Returns true if 'current_index' is in bounds of frequency_list.
|
// Returns true if 'current_index' is in bounds of frequency_list.
|
||||||
bool current_is_valid();
|
bool current_is_valid();
|
||||||
@@ -125,6 +140,7 @@ class ReconView : public View {
|
|||||||
bool load_freqs{true};
|
bool load_freqs{true};
|
||||||
bool load_ranges{true};
|
bool load_ranges{true};
|
||||||
bool load_hamradios{true};
|
bool load_hamradios{true};
|
||||||
|
bool load_repeaters{true};
|
||||||
bool update_ranges{true};
|
bool update_ranges{true};
|
||||||
bool fwd{true};
|
bool fwd{true};
|
||||||
bool recon{true};
|
bool recon{true};
|
||||||
@@ -163,6 +179,27 @@ class ReconView : public View {
|
|||||||
systime_t chrono_start{};
|
systime_t chrono_start{};
|
||||||
systime_t chrono_end{};
|
systime_t chrono_end{};
|
||||||
|
|
||||||
|
const std::filesystem::path repeat_rec_file = u"RECON_REPEAT.C16";
|
||||||
|
const std::filesystem::path repeat_rec_meta = u"RECON_REPEAT.TXT";
|
||||||
|
const size_t repeat_read_size{16384};
|
||||||
|
const size_t repeat_buffer_count{3};
|
||||||
|
int8_t repeat_cur_rep = 0;
|
||||||
|
int64_t repeat_sample_rate = 0;
|
||||||
|
static constexpr uint32_t repeat_bandwidth = 2500000;
|
||||||
|
void on_repeat_tx_progress(const uint32_t progress);
|
||||||
|
void start_repeat();
|
||||||
|
void stop_repeat(const bool do_loop);
|
||||||
|
bool is_repeat_active() const;
|
||||||
|
void handle_repeat_thread_done(const uint32_t return_code);
|
||||||
|
void repeat_file_error(const std::filesystem::path& path, const std::string& message);
|
||||||
|
std::filesystem::path repeat_file_path{};
|
||||||
|
std::unique_ptr<ReplayThread> replay_thread{};
|
||||||
|
bool repeat_ready_signal{false};
|
||||||
|
bool recon_tx{false};
|
||||||
|
|
||||||
|
std::filesystem::path rawfile = u"/" + repeat_rec_path + u"/" + repeat_rec_file;
|
||||||
|
std::filesystem::path rawmeta = u"/" + repeat_rec_path + u"/" + repeat_rec_meta;
|
||||||
|
|
||||||
// Persisted settings.
|
// Persisted settings.
|
||||||
SettingsStore ui_settings{
|
SettingsStore ui_settings{
|
||||||
"recon"sv,
|
"recon"sv,
|
||||||
@@ -181,11 +218,11 @@ class ReconView : public View {
|
|||||||
std::unique_ptr<RecordView> record_view{};
|
std::unique_ptr<RecordView> record_view{};
|
||||||
|
|
||||||
Labels labels{
|
Labels labels{
|
||||||
{{0 * 8, 0 * 16}, "LNA: VGA: AMP: VOL: ", Color::light_grey()},
|
{{0 * 8, 0 * 16}, "LNA: VGA: AMP: VOL: ", Theme::getInstance()->fg_light->foreground},
|
||||||
{{3 * 8, 8 * 16}, "START END", Color::light_grey()},
|
{{3 * 8, 8 * 16}, "START END", Theme::getInstance()->fg_light->foreground},
|
||||||
{{0 * 8, (22 * 8)}, " S: ", Color::light_grey()},
|
{{0 * 8, (22 * 8)}, " S: ", Theme::getInstance()->fg_light->foreground},
|
||||||
{{0 * 8, (24 * 8) + 4}, "NBLCKS:x W,L: , ", Color::light_grey()},
|
{{0 * 8, (24 * 8) + 4}, "NBLCKS:x W,L: , ", Theme::getInstance()->fg_light->foreground},
|
||||||
{{0 * 8, (26 * 8) + 4}, "MODE: , SQUELCH: ", Color::light_grey()}};
|
{{0 * 8, (26 * 8) + 4}, "MODE: , SQUELCH: ", Theme::getInstance()->fg_light->foreground}};
|
||||||
|
|
||||||
LNAGainField field_lna{
|
LNAGainField field_lna{
|
||||||
{4 * 8, 0 * 16}};
|
{4 * 8, 0 * 16}};
|
||||||
@@ -354,17 +391,47 @@ class ReconView : public View {
|
|||||||
{168, (35 * 8) - 4, 72, 28},
|
{168, (35 * 8) - 4, 72, 28},
|
||||||
"<REMOVE>"};
|
"<REMOVE>"};
|
||||||
|
|
||||||
|
ProgressBar progressbar{
|
||||||
|
{0 * 8, SCREEN_H / 2 - 16, SCREEN_W, 32}};
|
||||||
|
|
||||||
|
TransmitterView2 tx_view{
|
||||||
|
{11 * 8, 2 * 16},
|
||||||
|
/*short_ui*/ true};
|
||||||
|
|
||||||
MessageHandlerRegistration message_handler_coded_squelch{
|
MessageHandlerRegistration message_handler_coded_squelch{
|
||||||
Message::ID::CodedSquelch,
|
Message::ID::CodedSquelch,
|
||||||
[this](const Message* const p) {
|
[this](const Message* const p) {
|
||||||
const auto message = *reinterpret_cast<const CodedSquelchMessage*>(p);
|
const auto message = *reinterpret_cast<const CodedSquelchMessage*>(p);
|
||||||
this->handle_coded_squelch(message.value);
|
handle_coded_squelch(message.value);
|
||||||
}};
|
}};
|
||||||
|
|
||||||
MessageHandlerRegistration message_handler_stats{
|
MessageHandlerRegistration message_handler_stats{
|
||||||
Message::ID::ChannelStatistics,
|
Message::ID::ChannelStatistics,
|
||||||
[this](const Message* const p) {
|
[this](const Message* const p) {
|
||||||
this->on_statistics_update(static_cast<const ChannelStatisticsMessage*>(p)->statistics);
|
on_statistics_update(static_cast<const ChannelStatisticsMessage*>(p)->statistics);
|
||||||
|
}};
|
||||||
|
|
||||||
|
MessageHandlerRegistration message_handler_replay_thread_error{
|
||||||
|
Message::ID::ReplayThreadDone,
|
||||||
|
[this](const Message* p) {
|
||||||
|
auto message = *reinterpret_cast<const ReplayThreadDoneMessage*>(p);
|
||||||
|
handle_repeat_thread_done(message.return_code);
|
||||||
|
}};
|
||||||
|
|
||||||
|
MessageHandlerRegistration message_handler_fifo_signal{
|
||||||
|
Message::ID::RequestSignal,
|
||||||
|
[this](const Message* p) {
|
||||||
|
auto message = static_cast<const RequestSignalMessage*>(p);
|
||||||
|
if (message->signal == RequestSignalMessage::Signal::FillRequest) {
|
||||||
|
repeat_ready_signal = true;
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
|
||||||
|
MessageHandlerRegistration message_handler_tx_progress{
|
||||||
|
Message::ID::TXProgress,
|
||||||
|
[this](const Message* p) {
|
||||||
|
auto message = *reinterpret_cast<const TXProgressMessage*>(p);
|
||||||
|
on_repeat_tx_progress(message.progress);
|
||||||
}};
|
}};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
#include "freqman_db.hpp"
|
#include "freqman_db.hpp"
|
||||||
#include "portapack.hpp"
|
#include "portapack.hpp"
|
||||||
#include "portapack_persistent_memory.hpp"
|
#include "portapack_persistent_memory.hpp"
|
||||||
|
#include "file_path.hpp"
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace portapack;
|
using namespace portapack;
|
||||||
@@ -58,7 +59,7 @@ ReconSetupViewMain::ReconSetupViewMain(NavigationView& nav, Rect parent_rect, st
|
|||||||
auto open_view = nav.push<FileLoadView>(".TXT");
|
auto open_view = nav.push<FileLoadView>(".TXT");
|
||||||
open_view->push_dir(freqman_dir);
|
open_view->push_dir(freqman_dir);
|
||||||
open_view->on_changed = [this, &nav](std::filesystem::path new_file_path) {
|
open_view->on_changed = [this, &nav](std::filesystem::path new_file_path) {
|
||||||
if (new_file_path.native().find(freqman_dir.native()) == 0) {
|
if (new_file_path.native().find((u"/" / freqman_dir).native()) == 0) {
|
||||||
_input_file = new_file_path.stem().string();
|
_input_file = new_file_path.stem().string();
|
||||||
text_input_file.set(_input_file);
|
text_input_file.set(_input_file);
|
||||||
} else {
|
} else {
|
||||||
@@ -71,7 +72,7 @@ ReconSetupViewMain::ReconSetupViewMain(NavigationView& nav, Rect parent_rect, st
|
|||||||
auto open_view = nav.push<FileLoadView>(".TXT");
|
auto open_view = nav.push<FileLoadView>(".TXT");
|
||||||
open_view->push_dir(freqman_dir);
|
open_view->push_dir(freqman_dir);
|
||||||
open_view->on_changed = [this, &nav](std::filesystem::path new_file_path) {
|
open_view->on_changed = [this, &nav](std::filesystem::path new_file_path) {
|
||||||
if (new_file_path.native().find(freqman_dir.native()) == 0) {
|
if (new_file_path.native().find((u"/" / freqman_dir).native()) == 0) {
|
||||||
_output_file = new_file_path.stem().string();
|
_output_file = new_file_path.stem().string();
|
||||||
button_choose_output_name.set_text(_output_file);
|
button_choose_output_name.set_text(_output_file);
|
||||||
} else {
|
} else {
|
||||||
@@ -99,8 +100,15 @@ void ReconSetupViewMore::save() {
|
|||||||
persistent_memory::set_recon_load_freqs(checkbox_load_freqs.value());
|
persistent_memory::set_recon_load_freqs(checkbox_load_freqs.value());
|
||||||
persistent_memory::set_recon_load_ranges(checkbox_load_ranges.value());
|
persistent_memory::set_recon_load_ranges(checkbox_load_ranges.value());
|
||||||
persistent_memory::set_recon_load_hamradios(checkbox_load_hamradios.value());
|
persistent_memory::set_recon_load_hamradios(checkbox_load_hamradios.value());
|
||||||
|
persistent_memory::set_recon_load_repeaters(checkbox_load_repeaters.value());
|
||||||
persistent_memory::set_recon_update_ranges_when_recon(checkbox_update_ranges_when_recon.value());
|
persistent_memory::set_recon_update_ranges_when_recon(checkbox_update_ranges_when_recon.value());
|
||||||
persistent_memory::set_recon_auto_record_locked(checkbox_auto_record_locked.value());
|
persistent_memory::set_recon_auto_record_locked(checkbox_auto_record_locked.value());
|
||||||
|
persistent_memory::set_recon_repeat_recorded(checkbox_repeat_recorded.value());
|
||||||
|
persistent_memory::set_recon_repeat_recorded_file_mode(field_repeat_file_mode.selected_index_value());
|
||||||
|
persistent_memory::set_recon_repeat_nb(field_repeat_nb.value());
|
||||||
|
persistent_memory::set_recon_repeat_amp(checkbox_repeat_amp.value());
|
||||||
|
persistent_memory::set_recon_repeat_gain(field_repeat_gain.value());
|
||||||
|
persistent_memory::set_recon_repeat_delay(field_repeat_delay.value());
|
||||||
};
|
};
|
||||||
|
|
||||||
void ReconSetupViewMain::focus() {
|
void ReconSetupViewMain::focus() {
|
||||||
@@ -113,16 +121,58 @@ ReconSetupViewMore::ReconSetupViewMore(NavigationView& nav, Rect parent_rect)
|
|||||||
hidden(true);
|
hidden(true);
|
||||||
|
|
||||||
add_children({&checkbox_load_freqs,
|
add_children({&checkbox_load_freqs,
|
||||||
|
&checkbox_load_repeaters,
|
||||||
&checkbox_load_ranges,
|
&checkbox_load_ranges,
|
||||||
&checkbox_load_hamradios,
|
&checkbox_load_hamradios,
|
||||||
&checkbox_update_ranges_when_recon,
|
&checkbox_update_ranges_when_recon,
|
||||||
&checkbox_auto_record_locked});
|
&checkbox_auto_record_locked,
|
||||||
|
&checkbox_repeat_recorded,
|
||||||
|
&field_repeat_file_mode,
|
||||||
|
&text_repeat_nb,
|
||||||
|
&field_repeat_nb,
|
||||||
|
&checkbox_repeat_amp,
|
||||||
|
&text_repeat_gain,
|
||||||
|
&field_repeat_gain,
|
||||||
|
&text_repeat_delay,
|
||||||
|
&field_repeat_delay});
|
||||||
|
|
||||||
|
// tx options have to be in yellow to inform the users that activating them will make the device transmit
|
||||||
|
checkbox_repeat_recorded.set_style(Theme::getInstance()->fg_yellow);
|
||||||
|
field_repeat_file_mode.set_style(Theme::getInstance()->fg_yellow);
|
||||||
|
text_repeat_nb.set_style(Theme::getInstance()->fg_yellow);
|
||||||
|
field_repeat_nb.set_style(Theme::getInstance()->fg_yellow);
|
||||||
|
checkbox_repeat_amp.set_style(Theme::getInstance()->fg_yellow);
|
||||||
|
text_repeat_gain.set_style(Theme::getInstance()->fg_yellow);
|
||||||
|
field_repeat_gain.set_style(Theme::getInstance()->fg_yellow);
|
||||||
|
text_repeat_delay.set_style(Theme::getInstance()->fg_yellow);
|
||||||
|
field_repeat_delay.set_style(Theme::getInstance()->fg_yellow);
|
||||||
|
|
||||||
checkbox_load_freqs.set_value(persistent_memory::recon_load_freqs());
|
checkbox_load_freqs.set_value(persistent_memory::recon_load_freqs());
|
||||||
|
checkbox_load_repeaters.set_value(persistent_memory::recon_load_repeaters());
|
||||||
checkbox_load_ranges.set_value(persistent_memory::recon_load_ranges());
|
checkbox_load_ranges.set_value(persistent_memory::recon_load_ranges());
|
||||||
checkbox_load_hamradios.set_value(persistent_memory::recon_load_hamradios());
|
checkbox_load_hamradios.set_value(persistent_memory::recon_load_hamradios());
|
||||||
checkbox_update_ranges_when_recon.set_value(persistent_memory::recon_update_ranges_when_recon());
|
checkbox_update_ranges_when_recon.set_value(persistent_memory::recon_update_ranges_when_recon());
|
||||||
checkbox_auto_record_locked.set_value(persistent_memory::recon_auto_record_locked());
|
checkbox_auto_record_locked.set_value(persistent_memory::recon_auto_record_locked());
|
||||||
|
checkbox_repeat_recorded.set_value(persistent_memory::recon_repeat_recorded());
|
||||||
|
field_repeat_file_mode.set_selected_index(persistent_memory::recon_repeat_recorded_file_mode());
|
||||||
|
checkbox_repeat_amp.set_value(persistent_memory::recon_repeat_amp());
|
||||||
|
field_repeat_nb.set_value(persistent_memory::recon_repeat_nb());
|
||||||
|
field_repeat_gain.set_value(persistent_memory::recon_repeat_gain());
|
||||||
|
field_repeat_delay.set_value(persistent_memory::recon_repeat_delay());
|
||||||
|
|
||||||
|
// tx warning modal
|
||||||
|
checkbox_repeat_recorded.on_select = [this, &nav](Checkbox&, bool v) {
|
||||||
|
if (v) {
|
||||||
|
nav.display_modal(
|
||||||
|
"TX WARNING",
|
||||||
|
"This activate TX ability\nin Recon !",
|
||||||
|
YESNO,
|
||||||
|
[this, &nav](bool choice) {
|
||||||
|
if (!choice)
|
||||||
|
checkbox_repeat_recorded.set_value(choice);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
void ReconSetupViewMore::focus() {
|
void ReconSetupViewMore::focus() {
|
||||||
|
|||||||
@@ -37,16 +37,19 @@
|
|||||||
#endif
|
#endif
|
||||||
#define OneMHz 1000000
|
#define OneMHz 1000000
|
||||||
|
|
||||||
// modes
|
// main app mode
|
||||||
#define RECON_MATCH_CONTINUOUS 0
|
#define RECON_MATCH_CONTINUOUS 0
|
||||||
#define RECON_MATCH_SPARSE 1
|
#define RECON_MATCH_SPARSE 1
|
||||||
|
|
||||||
|
// repeater mode
|
||||||
|
#define RECON_REPEAT_AND_DELETE 0
|
||||||
|
#define RECON_REPEAT_AND_KEEP 1
|
||||||
|
|
||||||
// statistics update interval in ms (change here if the statistics API is changing it's pace)
|
// statistics update interval in ms (change here if the statistics API is changing it's pace)
|
||||||
#define STATS_UPDATE_INTERVAL 100
|
#define STATS_UPDATE_INTERVAL 100
|
||||||
|
|
||||||
// maximum lock duration
|
// maximum lock duration
|
||||||
#define RECON_MAX_LOCK_DURATION 9900
|
#define RECON_MAX_LOCK_DURATION 9900
|
||||||
|
|
||||||
#define RECON_DEF_SQUELCH -14
|
#define RECON_DEF_SQUELCH -14
|
||||||
|
|
||||||
// default number of match to have a lock
|
// default number of match to have a lock
|
||||||
@@ -56,7 +59,7 @@
|
|||||||
|
|
||||||
// screen size helper
|
// screen size helper
|
||||||
#define SCREEN_W 240
|
#define SCREEN_W 240
|
||||||
//#define SCREEN_H 320
|
// #define SCREEN_H 320
|
||||||
|
|
||||||
// recon settings nb params
|
// recon settings nb params
|
||||||
#define RECON_SETTINGS_NB_PARAMS 7
|
#define RECON_SETTINGS_NB_PARAMS 7
|
||||||
@@ -116,26 +119,88 @@ class ReconSetupViewMore : public View {
|
|||||||
Checkbox checkbox_load_freqs{
|
Checkbox checkbox_load_freqs{
|
||||||
{1 * 8, 12},
|
{1 * 8, 12},
|
||||||
3,
|
3,
|
||||||
"input: load freqs"};
|
"load freq",
|
||||||
|
true};
|
||||||
|
|
||||||
|
Checkbox checkbox_load_repeaters{
|
||||||
|
{14 * 8, 12},
|
||||||
|
3,
|
||||||
|
"load repeater",
|
||||||
|
true};
|
||||||
|
|
||||||
Checkbox checkbox_load_ranges{
|
Checkbox checkbox_load_ranges{
|
||||||
{1 * 8, 42},
|
{1 * 8, 42},
|
||||||
3,
|
3,
|
||||||
"input: load ranges"};
|
"load range",
|
||||||
|
true};
|
||||||
|
|
||||||
Checkbox checkbox_load_hamradios{
|
Checkbox checkbox_load_hamradios{
|
||||||
{1 * 8, 72},
|
{1 * 8, 72},
|
||||||
3,
|
3,
|
||||||
"input: load hamradios"};
|
"load hamradio",
|
||||||
|
true};
|
||||||
|
|
||||||
Checkbox checkbox_update_ranges_when_recon{
|
Checkbox checkbox_update_ranges_when_recon{
|
||||||
{1 * 8, 102},
|
{1 * 8, 102},
|
||||||
3,
|
3,
|
||||||
"auto update m-ranges"};
|
"auto update m-ranges"};
|
||||||
|
|
||||||
Checkbox checkbox_auto_record_locked{
|
Checkbox checkbox_auto_record_locked{
|
||||||
{1 * 8, 132},
|
{1 * 8, 132},
|
||||||
3,
|
3,
|
||||||
"record locked periods"};
|
"record locked periods"};
|
||||||
|
|
||||||
|
Checkbox checkbox_repeat_recorded{
|
||||||
|
{1 * 8, 162},
|
||||||
|
0,
|
||||||
|
""};
|
||||||
|
|
||||||
|
OptionsField field_repeat_file_mode{
|
||||||
|
{4 * 8 + 3, 165},
|
||||||
|
13,
|
||||||
|
{{"repeat,delete", RECON_REPEAT_AND_DELETE},
|
||||||
|
{"repeat,keep ", RECON_REPEAT_AND_KEEP}}};
|
||||||
|
|
||||||
|
Text text_repeat_nb{
|
||||||
|
{20 * 8, 165, 3 * 8, 22},
|
||||||
|
"nb:"};
|
||||||
|
|
||||||
|
NumberField field_repeat_nb{
|
||||||
|
{23 * 8, 165},
|
||||||
|
2,
|
||||||
|
{1, 99},
|
||||||
|
1,
|
||||||
|
' ',
|
||||||
|
};
|
||||||
|
|
||||||
|
Checkbox checkbox_repeat_amp{
|
||||||
|
{1 * 8, 192},
|
||||||
|
3,
|
||||||
|
"AMP,"};
|
||||||
|
|
||||||
|
Text text_repeat_gain{
|
||||||
|
{9 * 8, 196, 5 * 8, 22},
|
||||||
|
"GAIN:"};
|
||||||
|
|
||||||
|
NumberField field_repeat_gain{
|
||||||
|
{14 * 8, 196},
|
||||||
|
2,
|
||||||
|
{0, 47},
|
||||||
|
1,
|
||||||
|
' ',
|
||||||
|
};
|
||||||
|
|
||||||
|
Text text_repeat_delay{
|
||||||
|
{16 * 8, 196, 8 * 8, 22},
|
||||||
|
", delay:"};
|
||||||
|
|
||||||
|
NumberField field_repeat_delay{
|
||||||
|
{24 * 8, 196},
|
||||||
|
3,
|
||||||
|
{0, 254},
|
||||||
|
1,
|
||||||
|
' ',
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
class ReconSetupView : public View {
|
class ReconSetupView : public View {
|
||||||
@@ -160,8 +225,8 @@ class ReconSetupView : public View {
|
|||||||
ReconSetupViewMore viewMore{nav_, view_rect};
|
ReconSetupViewMore viewMore{nav_, view_rect};
|
||||||
|
|
||||||
TabView tab_view{
|
TabView tab_view{
|
||||||
{"Main", Color::cyan(), &viewMain},
|
{"Main", Theme::getInstance()->fg_cyan->foreground, &viewMain},
|
||||||
{"More", Color::green(), &viewMore}};
|
{"More", Theme::getInstance()->fg_green->foreground, &viewMore}};
|
||||||
|
|
||||||
Button button_save{
|
Button button_save{
|
||||||
{9 * 8, 255, 14 * 8, 40},
|
{9 * 8, 255, 14 * 8, 40},
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
#include "ui_receiver.hpp"
|
#include "ui_receiver.hpp"
|
||||||
#include "ui_textentry.hpp"
|
#include "ui_textentry.hpp"
|
||||||
#include "utility.hpp"
|
#include "utility.hpp"
|
||||||
|
#include "file_path.hpp"
|
||||||
|
|
||||||
using namespace portapack;
|
using namespace portapack;
|
||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
@@ -176,11 +177,11 @@ void RemoteButton::paint(Painter& painter) {
|
|||||||
// Add a border on the highlighted button.
|
// Add a border on the highlighted button.
|
||||||
if (has_focus() || highlighted()) {
|
if (has_focus() || highlighted()) {
|
||||||
auto r = screen_rect();
|
auto r = screen_rect();
|
||||||
painter.draw_rectangle(r, Color::white());
|
painter.draw_rectangle(r, Theme::getInstance()->bg_darkest->foreground);
|
||||||
|
|
||||||
auto p = r.location() + Point{1, 1};
|
auto p = r.location() + Point{1, 1};
|
||||||
auto s = Size{r.size().width() - 2, r.size().height() - 2};
|
auto s = Size{r.size().width() - 2, r.size().height() - 2};
|
||||||
painter.draw_rectangle({p, s}, Color::light_grey());
|
painter.draw_rectangle({p, s}, Theme::getInstance()->fg_light->foreground);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -246,7 +247,7 @@ RemoteEntryEditView::RemoteEntryEditView(
|
|||||||
|
|
||||||
field_path.on_select = [this, &nav](TextField&) {
|
field_path.on_select = [this, &nav](TextField&) {
|
||||||
auto open_view = nav.push<FileLoadView>(".C*");
|
auto open_view = nav.push<FileLoadView>(".C*");
|
||||||
open_view->push_dir(u"CAPTURES");
|
open_view->push_dir(captures_dir);
|
||||||
open_view->on_changed = [this](fs::path path) {
|
open_view->on_changed = [this](fs::path path) {
|
||||||
load_path(std::move(path));
|
load_path(std::move(path));
|
||||||
refresh_ui();
|
refresh_ui();
|
||||||
@@ -355,7 +356,7 @@ RemoteView::RemoteView(
|
|||||||
Dim waterfall_height = waterfall_bottom - waterfall_top;
|
Dim waterfall_height = waterfall_bottom - waterfall_top;
|
||||||
waterfall.set_parent_rect({0, waterfall_top, screen_width, waterfall_height});
|
waterfall.set_parent_rect({0, waterfall_top, screen_width, waterfall_height});
|
||||||
|
|
||||||
ensure_directory(u"REMOTES");
|
ensure_directory(remotes_dir);
|
||||||
|
|
||||||
// Load the previously loaded remote if exists.
|
// Load the previously loaded remote if exists.
|
||||||
if (!load_remote(settings_.remote_path))
|
if (!load_remote(settings_.remote_path))
|
||||||
@@ -527,7 +528,7 @@ void RemoteView::new_remote() {
|
|||||||
|
|
||||||
void RemoteView::open_remote() {
|
void RemoteView::open_remote() {
|
||||||
auto open_view = nav_.push<FileLoadView>(".REM");
|
auto open_view = nav_.push<FileLoadView>(".REM");
|
||||||
open_view->push_dir(u"REMOTES");
|
open_view->push_dir(remotes_dir);
|
||||||
open_view->on_changed = [this](fs::path path) {
|
open_view->on_changed = [this](fs::path path) {
|
||||||
save_remote();
|
save_remote();
|
||||||
load_remote(std::move(path));
|
load_remote(std::move(path));
|
||||||
@@ -538,7 +539,7 @@ void RemoteView::open_remote() {
|
|||||||
void RemoteView::init_remote() {
|
void RemoteView::init_remote() {
|
||||||
model_ = {"<Unnamed Remote>", {}};
|
model_ = {"<Unnamed Remote>", {}};
|
||||||
reset_buttons();
|
reset_buttons();
|
||||||
set_remote_path(next_filename_matching_pattern(u"/REMOTES/REMOTE_????.REM"));
|
set_remote_path(next_filename_matching_pattern(remotes_dir / u"REMOTE_????.REM"));
|
||||||
set_needs_save(false);
|
set_needs_save(false);
|
||||||
|
|
||||||
if (remote_path_.empty())
|
if (remote_path_.empty())
|
||||||
|
|||||||
@@ -200,15 +200,15 @@ class RemoteEntryEditView : public View {
|
|||||||
void load_path(std::filesystem::path&& path);
|
void load_path(std::filesystem::path&& path);
|
||||||
|
|
||||||
Labels labels{
|
Labels labels{
|
||||||
{{2 * 8, 1 * 16}, "Name:", Color::light_grey()},
|
{{2 * 8, 1 * 16}, "Name:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{2 * 8, 2 * 16}, "Path:", Color::light_grey()},
|
{{2 * 8, 2 * 16}, "Path:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{2 * 8, 3 * 16}, "Freq:", Color::light_grey()},
|
{{2 * 8, 3 * 16}, "Freq:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{17 * 8, 3 * 16}, "MHz", Color::light_grey()},
|
{{17 * 8, 3 * 16}, "MHz", Theme::getInstance()->fg_light->foreground},
|
||||||
{{2 * 8, 4 * 16}, "Rate:", Color::light_grey()},
|
{{2 * 8, 4 * 16}, "Rate:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{2 * 8, 5 * 16}, "Icon:", Color::light_grey()},
|
{{2 * 8, 5 * 16}, "Icon:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{2 * 8, 6 * 16}, "FG Color:", Color::light_grey()},
|
{{2 * 8, 6 * 16}, "FG Color:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{2 * 8, 7 * 16}, "BG Color:", Color::light_grey()},
|
{{2 * 8, 7 * 16}, "BG Color:", Theme::getInstance()->fg_light->foreground},
|
||||||
{{8 * 8, 9 * 16}, "Button preview", Color::light_grey()},
|
{{8 * 8, 9 * 16}, "Button preview", Theme::getInstance()->fg_light->foreground},
|
||||||
};
|
};
|
||||||
|
|
||||||
TextField field_name{{8 * 8, 1 * 16, 20 * 8, 1 * 16}, {}};
|
TextField field_name{{8 * 8, 1 * 16, 20 * 8, 1 * 16}, {}};
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||||
* Copyright (C) 2018 Furrtek
|
* Copyright (C) 2018 Furrtek
|
||||||
|
* Copyright (C) 2023 Mark Thompson
|
||||||
*
|
*
|
||||||
* This file is part of PortaPack.
|
* This file is part of PortaPack.
|
||||||
*
|
*
|
||||||
@@ -25,6 +26,7 @@
|
|||||||
#include "optional.hpp"
|
#include "optional.hpp"
|
||||||
#include "ui_fileman.hpp"
|
#include "ui_fileman.hpp"
|
||||||
#include "ui_freqman.hpp"
|
#include "ui_freqman.hpp"
|
||||||
|
#include "file_path.hpp"
|
||||||
|
|
||||||
using namespace portapack;
|
using namespace portapack;
|
||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
@@ -183,16 +185,16 @@ void ScannerView::bigdisplay_update(int32_t v) {
|
|||||||
|
|
||||||
switch (bigdisplay_current_color) {
|
switch (bigdisplay_current_color) {
|
||||||
case BDC_GREY:
|
case BDC_GREY:
|
||||||
big_display.set_style(&Styles::grey);
|
big_display.set_style(Theme::getInstance()->fg_medium);
|
||||||
break;
|
break;
|
||||||
case BDC_YELLOW:
|
case BDC_YELLOW:
|
||||||
big_display.set_style(&Styles::yellow);
|
big_display.set_style(Theme::getInstance()->fg_yellow);
|
||||||
break;
|
break;
|
||||||
case BDC_GREEN:
|
case BDC_GREEN:
|
||||||
big_display.set_style(&Styles::green);
|
big_display.set_style(Theme::getInstance()->fg_green);
|
||||||
break;
|
break;
|
||||||
case BDC_RED:
|
case BDC_RED:
|
||||||
big_display.set_style(&Styles::red);
|
big_display.set_style(Theme::getInstance()->fg_red);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
@@ -275,13 +277,13 @@ ScannerView::~ScannerView() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ScannerView::show_max_index() { // show total number of freqs to scan
|
void ScannerView::show_max_index() { // show total number of freqs to scan
|
||||||
field_current_index.set_text("---");
|
field_current_index.set_text("<->");
|
||||||
|
|
||||||
if (entries.size() == FREQMAN_MAX_PER_FILE) {
|
if (entries.size() == FREQMAN_MAX_PER_FILE) {
|
||||||
text_max_index.set_style(&Styles::red);
|
text_max_index.set_style(Theme::getInstance()->fg_red);
|
||||||
text_max_index.set("/ " + to_string_dec_uint(FREQMAN_MAX_PER_FILE) + " (DB MAX!)");
|
text_max_index.set("/ " + to_string_dec_uint(FREQMAN_MAX_PER_FILE) + " (DB MAX!)");
|
||||||
} else {
|
} else {
|
||||||
text_max_index.set_style(&Styles::grey);
|
text_max_index.set_style(Theme::getInstance()->fg_medium);
|
||||||
text_max_index.set("/ " + to_string_dec_uint(entries.size()));
|
text_max_index.set("/ " + to_string_dec_uint(entries.size()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -336,7 +338,7 @@ ScannerView::ScannerView(
|
|||||||
auto open_view = nav.push<FileLoadView>(".TXT");
|
auto open_view = nav.push<FileLoadView>(".TXT");
|
||||||
open_view->push_dir(freqman_dir);
|
open_view->push_dir(freqman_dir);
|
||||||
open_view->on_changed = [this, &nav](std::filesystem::path new_file_path) {
|
open_view->on_changed = [this, &nav](std::filesystem::path new_file_path) {
|
||||||
if (new_file_path.native().find(freqman_dir.native()) == 0) {
|
if (new_file_path.native().find((u"/" / freqman_dir).native()) == 0) {
|
||||||
scan_pause();
|
scan_pause();
|
||||||
frequency_file_load(new_file_path);
|
frequency_file_load(new_file_path);
|
||||||
} else {
|
} else {
|
||||||
@@ -411,7 +413,7 @@ ScannerView::ScannerView(
|
|||||||
button_mic_app.on_select = [this](Button&) {
|
button_mic_app.on_select = [this](Button&) {
|
||||||
if (scan_thread)
|
if (scan_thread)
|
||||||
scan_thread->stop();
|
scan_thread->stop();
|
||||||
// MicTX wants Modulation and Bandwidth overrides, but that's only stored on the RX model.
|
// MicTX wants Frequency, Modulation and Bandwidth overrides, but that's only stored on the RX model.
|
||||||
nav_.replace<MicTXView>(receiver_model.settings());
|
nav_.replace<MicTXView>(receiver_model.settings());
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -564,6 +566,7 @@ void ScannerView::frequency_file_load(const fs::path& path) {
|
|||||||
def_step_index = entry.step;
|
def_step_index = entry.step;
|
||||||
|
|
||||||
switch (entry.type) {
|
switch (entry.type) {
|
||||||
|
case freqman_type::Repeater:
|
||||||
case freqman_type::Single:
|
case freqman_type::Single:
|
||||||
entries.push_back({entry.frequency_a, entry.description});
|
entries.push_back({entry.frequency_a, entry.description});
|
||||||
break;
|
break;
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user