Compare commits

..

338 Commits

Author SHA1 Message Date
jLynx 8e90c65a62 v2.0.1 (#2090)
* Update version.txt

* Update past_version.txt
2024-04-07 16:24:01 +12:00
gullradriel bfe67a13b7 removed dead link (#2089)
Co-authored-by: gullradriel <gullradriel@no-mail.com>
2024-04-07 00:59:40 +08:00
gullradriel f091db3270 Mic audio ak4951 fix + SSTV description (#2088)
* fixed too long description, thanks to ImDroided

* fixed ak4951 RxAudio not working on startup when enabled

---------

Co-authored-by: gullradriel <gullradriel@no-mail.com>
2024-04-05 16:29:18 -05:00
Mark Thompson a602abf1d8 Fix Cancel button in Settings->Autostart (#2087) 2024-04-05 16:04:39 +02:00
ImDroided 689224fd8d Added CubeSats to Presets 2024-04-05 08:52:14 +02:00
gullradriel 23cf1c2737 fix build warning in settings (#2081)
* fix warning ui::SetAutostartView::opts' should be initialized in the member initialization list

* clang

---------

Co-authored-by: gullradriel <gullradriel@no-mail.com>
2024-04-03 14:17:48 -05:00
Totoo c48cbb7e55 Autostart option (#2079)
* Autostart app on boot
* Settings page
* ext app support
2024-04-03 16:27:13 +02:00
Totoo 3665b3c607 Added sil value ASDB (#2078)
* Added sil value. resolves #2005

* readibility
2024-04-03 15:57:39 +08:00
Totoo 804b7c87b7 allow to load more files but unordered (#2077) 2024-04-02 20:19:28 +02:00
Totoo a30875b582 Fileman memory opt (#2076)
* paginator
* fixes
* string instead of path, so saves ram.
* fix FileLoadView
* pagination list number
* pagination "size" test
* added detection of special folder name to display page numbers instead of (0 or a number of byte)
* order of things: only one directory '..', only on first page
Co-authored-by: gullradriel <gullradriel@no-mail.com>
2024-04-02 17:02:02 +02:00
Mark Thompson 56303eb385 Use freq from AppSettings unless passed from another app (#2073) 2024-03-30 12:57:43 -05:00
Totoo d29172f41e Add GPS icon, fix overwrite bug (#2072) 2024-03-30 16:48:45 +01:00
sommermorgentraum 6d9d5ad1af fix read file (#2070) 2024-03-30 13:03:27 +01:00
gullradriel 2acefee4d2 adjust GUI for more preset name room (#2068) 2024-03-30 10:34:37 +01:00
Mark Thompson cb9fee8f08 Modify warning text (#2066) 2024-03-29 18:50:28 -05:00
Mark Thompson b473930f94 Beep-on-packet support in ADSB RX app (#2065) 2024-03-29 16:34:18 -05:00
Mark Thompson 6e5eadd25c Beep-on-packet support in AIS app (#2064)
* Beep-on-packet support in AIS app
2024-03-29 22:08:32 +01:00
Dumpster-Diver 76017c91a9 Create RADIOS_FM_FR_PARIS.TXT with FM Radios in Paris (#2049)
* Create RADIOS_FM_FR.TXT with FM Radios in Paris

This new file will add FM frequencies for France. As a start it covers Paris FM radio frequencies. It is based on https://www.csa.fr/maradiofm/liste_ville?recherche=3&commune=Paris

* Update and rename RADIOS_FM_FR.TXT to RADIOS_FM_FR_PARIS.TXT

Reduce frequency description length to fit under the 30 chars limit
2024-03-29 15:52:00 -05:00
Mark Thompson f1ebb1a6e3 Beep-on-packet support in Weather app (#2063) 2024-03-29 21:29:50 +01:00
Mark Thompson 746bf1c15f Beep-on-packet support in TPMS app (#2062)
* Beep-on-packet support in TPMS app
* audio::output::stop when exiting
2024-03-29 21:28:27 +01:00
Mark Thompson 0db65ccb6b Beep-on-packet support in POCSAG app (#2061) 2024-03-29 21:27:53 +01:00
Mark Thompson fe2fbb847f Beep-on-packets option in Settings and updated ERT & Sonde apps (#2058)
* Beep-on-packets option in Settings
* Add beep to ERT app
2024-03-29 19:26:45 +01:00
Mark Thompson ba36680a7b FileMan support for folder Cut & Paste and Timestamp Rename (#2055) 2024-03-29 10:44:37 +01:00
set sail into the sea of life 90ff1e62ce move the no ext app tile to forward (#2047)
* strong notice of missing ext app

* textual

* move the notice tile to front and remove the painter string

* remove notice icon

* format

* for back button

* remove <>
2024-03-28 21:05:09 -05:00
Mark Thompson 2e1e02baac Removed special font characters (#2056) 2024-03-28 21:04:19 -05:00
Totoo 2dca408e45 Wardrive Map app (#2050)
* WardriveMap app

* better txt

* Count only displayed markers
2024-03-28 19:25:23 -05:00
Daniel fa56cdd015 Adding Australian UHF channels and Brisbane Airband to FREQMAN, adding Australian UHF to Looking Glass (#2048) 2024-03-27 19:57:40 -05:00
Mark Thompson 264fa9a28e Continued folder names consolidation & GPS default folder (#2042)
* Continued folder names consolidation

* Clang
2024-03-26 11:32:24 -05:00
Mark Thompson d5c8525afc Declare all folder names in a single source file (#2039) 2024-03-25 08:44:49 +01:00
ImDroided d9bbd1b9ff SSTV Freqs (#2038)
* Added and fixed freqs

Figured I would clean these up and add a bit.

* Create SSTV.TXT

SSTV Frequencies for SSTV app
2024-03-25 01:21:05 -05:00
gullradriel b47cee435e fix typo (#2037) 2024-03-24 18:50:42 -05:00
gullradriel c078bac0e7 Looking glass beep (#2036)
* first draft of looking beep
* fixed beep squelch range in percent
* took out steps 
* gui adjustements
* uniformize calculation and beep squelch in db
* uniformisation, fix 24/48 error
2024-03-24 22:32:13 +01:00
gullradriel 6177b08632 fixing level beep, bad copy paste on special SPEC mode (#2035)
Co-authored-by: gullradriel <gullradriel@no-mail.com>
2024-03-24 18:47:15 +08:00
Mark Thompson 53a1bacd46 Revert "Add cmake dependency on baseband for external APPS" (#2034)
This reverts commit abb9b6c8ad.
2024-03-24 10:39:20 +08:00
ImDroided 4c012a9493 Added and fixed freqs (#2033)
Figured I would clean these up and add a bit.
2024-03-23 21:16:27 -05:00
Mark Thompson abb9b6c8ad Add make dependency on baseband for external APPS (#2031) 2024-03-23 21:34:15 +01:00
gullradriel 6e2507d609 fix gui glitch on beep squelch (#2030) 2024-03-23 21:01:48 +01:00
gullradriel 536981998b Level fix and beep, RSSI avg fix (#2027)
* fix average value being overflow'd
* fix audio and mod changes, preps for beep mode
* fixed beep_freq range, added a stop and set a variable sooner
* added support for audio beep messages
* better scaler for beep
* added bip squelch and saving of bip squelch and audio mode
* saving modulation, fixing audio
* added save and restore of bandwidth
* simpler ctcss clean on change mode
2024-03-23 19:27:05 +01:00
Mark Thompson 1a87f2d701 Revert accidental newbutton bg color change (#2029) 2024-03-23 13:26:46 -05:00
Mark Thompson 81e24d582d More debug Audio Beep Test tweaks (#2028) 2024-03-23 13:25:36 -05:00
Mark Thompson 28a5fc5915 Improved Audio Beep Test (#2026) 2024-03-23 08:21:35 +01:00
Mark Thompson ba4290cf0d Audio Beep Test (Debug) external app (#2023) 2024-03-22 11:10:16 +01:00
Mark Thompson 692644d214 Radiosonde QR code fixes (#2021)
* Increase size of small QR Code

* Fixed QR code lat/lon for iPhone

* Show current geopos location on map

* Deprecate large QR code
2024-03-21 10:56:06 -05:00
Mark Thompson 4aeaf94d2d Radiosonde beep tone tweaks (#2020)
* Tweak RSSI audio beep frequency range
* Clang & changed min freq
* Save beep/log/crc settings in .ini file
* Update copyright string
* Added generic audio_beep message
2024-03-21 10:14:54 +01:00
Totoo 8391ca8052 Fox hunt (#2015)
* foxhunt_rx
2024-03-19 21:33:53 +01:00
Mark Thompson 807c76346b Improved audio beep tone (#2014)
* Improved sine wave beep tone

* Prevent divide-by-zero
2024-03-19 15:30:05 -05:00
Mark Thompson 308573918c Added a beep duration parameter (#2013) 2024-03-19 18:23:06 +01:00
Mark Thompson 74442f197d Fixed Sonde RSSI "Beep" (#2012) 2024-03-19 10:53:55 +01:00
Mark Thompson b53c8e1f80 Stop audio DMA in baseband::shutdown() (#2011) 2024-03-19 05:58:23 +01:00
Mark Thompson c5f73cf8f4 Darken FileMan button colors for visibility (#2010) 2024-03-19 05:56:23 +01:00
Totoo c1bf2620c7 Bt namespam (#2009)
* Add random names
2024-03-18 21:45:45 +01:00
Mark Thompson 8383363e74 Preserve radio mode when updating iq_tx_phase_cal (#2008) 2024-03-18 21:45:02 +01:00
Alexandros Andreou 6e34343bde added all and all-safe attack types to blespam (#2003) 2024-03-18 09:13:10 +01:00
gullradriel 01e4ff65a2 fix butngrid/capture and fix contributor name (#1995)
* fixing contributor name

* used clear instead of swap

---------

Co-authored-by: GullCode <gullradriel@hotmail.com>
2024-03-16 00:39:34 +08:00
Totoo 9e40e38f07 Record gps lat, lon, satinuse from recorder app if there was gps signal. (#1992)
* Record gps lat, lon, satinuse from recorder app if there was gps signal.

* Add _GEO to filename that has geotag
2024-03-15 21:56:20 +08:00
not tre mann 181624daf1 lambda the flash util menu add item code (#1993)
* lambda the flash util menu add code

* clean up
2024-03-14 22:56:55 -05:00
Mark Thompson f0614c882b File manager "Clean" button tweaks (#1990)
* File Manager "clean" button tweaks

* Add files via upload
2024-03-13 22:42:40 +01:00
not tre mann 14f42d1fe8 support "clean" files (#1983)
* init for clean files
* textual
* icons position
2024-03-13 18:06:14 +01:00
Mark Thompson 999f9e2ded Fix #1982 (enable DMA interrupt for all apps) (#1987) 2024-03-13 16:57:35 +01:00
Mark Thompson 61dc8a0225 Disabled unused baseband "no-op" process (#1986) 2024-03-13 16:08:52 +01:00
Mark Thompson 0b2d5f75cc Start audio DMA only in apps that use audio (#1982)
* Start audio DMA only in apps that use audio
* Rename main.cpp to main.cpp.unuse
* shrink_tx_buffer fix for transfers_per_buffer==1 scenario
2024-03-13 16:07:44 +01:00
Erwin Ried 3c489e1a81 Steamlining the warning (#1984)
I think it is enough to note nobody should pay for the download
2024-03-13 19:30:05 +08:00
gullradriel 910fd82051 freqman: limiting description size to 30, and minor fix (#1977)
* Limiting description size to 30 as it was documented before

* cosmetic adjustement

---------

Co-authored-by: GullCode <gullradriel@hotmail.com>
2024-03-11 16:40:47 +01:00
gullradriel 866e12fbc5 added RXIQCALC (#1974)
Co-authored-by: GullCode <gullradriel@hotmail.com>
2024-03-10 23:25:18 +01:00
Mark Thompson ccd71d9593 Set iq_phase_cal in radio when starting SPEC mode (#1972) 2024-03-10 20:50:48 +01:00
Mark Thompson e9b9ba4602 Deallocate menu buttons to save memory (#1971)
* Deallocate hidden menu buttons
* Added Copyright
* Added comments
2024-03-10 20:49:36 +01:00
gullradriel 986e37a3c9 fix unused warning (#1970) 2024-03-10 20:05:05 +01:00
gullradriel 2ac36d2d0b Level app: coloration of RxSat value (#1969) 2024-03-10 18:29:21 +01:00
Mark Thompson 3b79549129 Added enums for recon_config bits (#1965) 2024-03-10 08:30:05 +01:00
gullradriel 160a778104 Recon repeat auto recorded file mode (#1960)
* added everything needed to have a repeated file mode option to keep or delete files

* automatic filename for keep file

* fixing restart after replay

* fixed auto record replay/repeat restart

---------

Co-authored-by: GullCode <gullradriel@hotmail.com>
2024-03-10 08:45:54 +08:00
Mark Thompson e496f8ecc2 Eliminate unnecessary NumberField to save code space (#1964)
* Eliminate extra NumberField to save code space

* Clang
2024-03-09 17:36:48 -06:00
Brumi-2021 9d8132978f Adding Rx IQ error phase CAL to SPEC Audio_App (#1963) 2024-03-09 23:46:38 +01:00
Mark Thompson 73c29f666f Commented out Debug -> Memory Usage app (#1962) 2024-03-09 22:03:07 +01:00
Mark Thompson d895c9fa91 Show true M0 free heap space in DFU overlay (#1961) 2024-03-09 21:39:06 +01:00
gullradriel b5e66387c3 Rxsat in Level app (#1959)
* added Rx Saturation

* testing reducing values to uint8_t

* clang format

* refactorisation

* cleanings

* cleanings

* set back request_m4_performance_counter to zero on app exit

---------

Co-authored-by: GullCode <gullradriel@hotmail.com>
2024-03-09 22:26:56 +08:00
Mark Thompson 1fbfdbccf8 Remove unused text_info widget (#1955) 2024-03-08 01:29:13 +08:00
Totoo 2648f18b12 External sensor tester app (#1949)
* added tilt
* added extsensors app
* added icon
* Added EnvironmentData to ExtSensor app and message.hpp
* Added gotenv command
2024-03-07 17:22:24 +01:00
Mark Thompson 908c0e2cd5 Fix compiler warning (#1952) 2024-03-07 17:18:08 +01:00
Mark Thompson 4f3530db5e Update FPV_ANALOG.TXT (#1951)
* Include FPV in freq description

* Added comment referencing source of this data
2024-03-08 00:00:53 +08:00
not tre mann 73137891c4 change sun bitmap to make it more like a sun (#1946) 2024-03-07 07:50:49 +01:00
not tre mann c28087ac44 cheat code protect in pacman app (#1945) 2024-03-07 07:49:17 +01:00
Totoo 9e9dd3a521 Dynamic usb buffer fill for screenframeshort (#1935)
* fully fill usb serial buffer
* Update usb_serial_shell.cpp
* Added a comment, to trigger wf
2024-03-07 07:42:58 +01:00
Dima Danylevskyi 3bf689f339 Add FPV analog frequenies to FREQMAN (#1940) 2024-03-07 07:37:41 +01:00
Erwin Ried 86c326ef12 Update README.md (#1937)
Adding a warning
2024-02-26 17:50:11 +01:00
jLynx 5ae97d47f1 Fixed existing dir check (#1933) 2024-02-25 08:28:44 +01:00
Erwin Ried 762512b7dc Rename problem-upgrading-the-firmware.md to 03_problem-upgrading-the-firmware.md 2024-02-23 14:13:54 +01:00
Erwin Ried 9c848a6215 Update config.yml 2024-02-23 14:13:23 +01:00
Erwin Ried 9c86e15ddb Rename feature_request.yml to 02_feature_request.yml 2024-02-23 14:12:20 +01:00
Erwin Ried 57ccdb0044 Update and rename feature_request.md to feature_request.yml 2024-02-23 14:11:13 +01:00
Erwin Ried 1172175cc8 Rename bug_report.md to 01_bug_report.yml 2024-02-23 14:06:04 +01:00
Erwin Ried 9ed8ad5903 Create config.yml 2024-02-23 14:04:52 +01:00
Erwin Ried e4da8b979e Update bug_report.md (#1929) 2024-02-23 14:02:11 +01:00
Erwin Ried 4b83fd8a40 Updating the videos from people about hackrf (#1918)
The latest series from sasquash looks promising to engage beginners :) also will do some tracking with grabify to check engagement
2024-02-19 19:30:14 +08:00
Mark Thompson cd80da58d8 Improved flash image validation (#1916) 2024-02-18 19:02:42 +01:00
Mark Thompson a1f7010e08 Optimized fake brightness code (#1915) 2024-02-17 18:44:06 -06:00
Noah Axon 3499d2fa75 Fix Recon Modulation/Bandwidth presets (#1913) 2024-02-17 19:24:11 +01:00
notComposer 948e039574 fix shift back in screenshot and mayhem hub (#1910)
* fix_shift_back

* clean up

* gitignore

* remove the workaround in notpad cuz it's been fixed in this PR

* format

* add credit for mark

* 2024
2024-02-18 00:44:31 +08:00
Bernd Herzog 27dc37713b added crc32 command to usb shell (#1911) 2024-02-17 10:38:09 +01:00
jLynx ed834e3553 V2.0.0 setup (#1908)
* Update past_version.txt

* Update version.txt
2024-02-16 19:14:46 +00:00
Mark Thompson ce08d93ae2 Fix Pacman pause button glitches (again) (#1906) 2024-02-16 07:50:50 +01:00
Mark Thompson 13fd1b1f3b Support for configurable Menu Color & scrolling fix (#1905)
* Support for Configurable Menu Color Scheme
* Limit min value to 8 so doesn't get reset to default
* Increased max encoder rate multiplier value to 15
* Fixed menu scrolling issue
2024-02-16 07:19:43 +01:00
Mark Thompson d04c781ada Added Reboot app to Debug menu (#1904) 2024-02-16 07:18:33 +01:00
Mark Thompson 24605777a6 Show on_encoder delta in Buttons Test app (#1903) 2024-02-15 21:33:46 +01:00
Mark Thompson b5ac792853 Add color to position info lines in Notepad (#1902) 2024-02-15 18:12:40 +01:00
Mark Thompson 44c319dcc6 Refresh status bar after Pmem Reset (#1899) 2024-02-15 15:00:25 +01:00
Mark Thompson a442971b81 Eliminate unneeded screen flashing when scrolling menu (#1900) 2024-02-15 14:59:30 +01:00
Mark Thompson 8b5adb6bc1 Treat encoder rate multiplier value of 0 in pmem the same as 1 (#1898)
* Double-check encoder rate multiplier is not 0

* Eliminated redundant check in pmem init()
2024-02-15 07:05:05 +01:00
Bernd Herzog c3add0ce84 Cpld autodetect & boot splash screen (#1888)
* added delayed error message when hackrf cpld initialization fails

* refactoring

* implemented portapack cpld autodetection

* refactoring

* fixed valid config range

* added lcd fast setup

* added boot splash screen

* added one frame delay to remove flickering

* fixed config persistence
2024-02-14 23:17:33 +01:00
Mark Thompson 8725b01995 Fixed Pacman pause button (#1894) 2024-02-14 14:43:31 -06:00
E.T 936e8279f9 Add lto flag only to "internal" source files (#1895) 2024-02-14 13:54:20 -06:00
Mark Thompson 6193023c8f Fixed Pacman global object initialization (#1891) 2024-02-14 06:02:27 +01:00
Mark Thompson 1800939833 Fixed Tetris by eliminating some classes (#1890) 2024-02-13 20:54:40 +01:00
Mark Thompson 2f48fc2ef9 Disable LTO for external apps (#1886)
* Disable lto for external apps only

* Modified "shared external code" checks
2024-02-13 10:11:54 -06:00
Bernd Herzog 918ec0574f added delayed error message when hackrf cpld initialization fails (#1887)
* added delayed error message when hackrf cpld initialization fails

* refactoring
2024-02-13 13:06:47 +01:00
Mark Thompson 1139b22141 Fix compile error (#1884) 2024-02-12 19:04:48 +01:00
Brumi-2021 7598c25fc8 Solves random USB/LSB mode in SSB Mic App (H1R1). (#1882)
* Solves random USB/LSB mode in SSB Mic App (H1R1).

* format
2024-02-11 23:16:29 +01:00
Mark Thompson 0f6a9277a8 Warning for shared external code during build (#1881) 2024-02-11 21:25:00 +01:00
Mark Thompson 6a8650d484 Update Copyright in comment blocks (#1878) 2024-02-10 16:56:50 +01:00
Mark Thompson 46d9e02684 Encoder rotation rate multiplier support (#1876) 2024-02-10 09:32:03 +01:00
Mark Thompson 367479d163 Just shift menu icons up 1 row when hit bottom (#1874) 2024-02-10 09:21:35 +01:00
Mark Thompson bbbf5a37c8 Add progress bar to Soundboard app (#1875)
* Add progress bar to Soundboard app

* Clang
2024-02-10 09:04:00 +08:00
Mark Thompson b2ad1fa979 Cycle through brightness levels when icon selected (#1873) 2024-02-09 09:16:55 -06:00
Mark Thompson 998be5ba09 Set fake brightness to 50% default if never configured in Settings (#1871) 2024-02-09 18:36:02 +08:00
theHallwayThatBringMePassion 9e8d279298 fix double darken (#1866) 2024-02-08 18:39:57 +01:00
E.T fd03bb800c Brightness settings fixes 2.0 (#1870)
* Format

* Fix startup crash 🙈

* Revert "Revert "Brightness settings fixes" (#1865)"

Had to rebase then revert the revert to make git recognise and merge the change :)

This reverts commit b7e5bedf4b.

# Conflicts:
#	firmware/application/ui_navigation.cpp

* Add zxkmm to header too
2024-02-08 18:28:49 +01:00
Mark Thompson f80c28cde7 Rename playlist.ini to replay.ini to match app (#1864) 2024-02-08 11:34:40 +01:00
theHallwayThatBringMePassion b7e5bedf4b Revert "Brightness settings fixes" (#1865)
This reverts commit 994e426d1e.
2024-02-08 10:09:00 +08:00
E.T 994e426d1e Brightness settings fixes (#1863)
* Brightness settings fixes

- Statusbar toggle now updated when changing in settings page
- Annoying message boxes removed
- SystemView (aka whole screen) is now refreshed after toggling from status

* Format
2024-02-07 17:52:12 -06:00
Mark Thompson c5bf0d94f9 Skip over INFO chunk in WAV files (#1862) 2024-02-07 21:37:49 +01:00
Mark Thompson 8ad9ada015 Functionally revert PR #1837 (#1861) 2024-02-07 14:07:21 -06:00
Mark Thompson e854261124 WAV viewer tweaks (#1854) 2024-02-07 09:15:46 +01:00
theHallwayThatBringMePassion 0370b4eb55 fake brightness adjust (#1845)
* temp

* temp - 1

* next step note

* fix circuit dependency (to fix:can't read p.mem)

* fix circuit dependency (to fix:can't read p.mem)

* notes

* bitmap

* bitmap

* remove my stupid forward declearation

* clean up

* clean up

* level control

* clean up

* clean up

* clean up

* clean up

* format

* format

* format

* format

* format

* fix for comment advices

* fix for comment advices

* fix display issue in notepad app

* slightly increase performance...

* temp for grey_scale and high_constrast

* format

* note for cache implement

* fix for comment request

* fix for comment request
2024-02-07 16:07:30 +08:00
Matias Fernandez 5eef5b4e31 Rename Marine VHF freqman file to make more explicit what is the file about when you are browsing the files in the scanner app (#1858) 2024-02-07 00:08:37 -06:00
Mark Thompson 61d9c051de When setting Time & Date indicate if in DST range (#1851) 2024-02-06 14:25:44 -06:00
Mark Thompson 5ea1bff1e6 WAV Viewer & Soundboard enhancements (8 or 16-bit WAV files) (#1849)
* WAV Viewer & Soundboard enhancements
* Reduced width of sample rate field
2024-02-06 11:33:00 +01:00
Mark Thompson bc035cff6a ADSB retrieve_record just-in-case check (#1848) 2024-02-05 20:13:06 +01:00
E.T f2c008602d Adsb rx airline display fix (#1847)
* This doesn't do anything, just gives me some sanity :D

* Don’t abuse std

* Only look for airline info if the callsign is received

* Format…

* Let the airline update when callsign is received
2024-02-05 12:06:29 -06:00
Brumi-2021 b8073bca0f Adding_TX_IQ_phase_Calibration_to_Mic_App (#1843)
* Adding_TX_IQ_phase_Calibration_to_Mic_App

* Adding_persistent_CAL_data_and_correct_init_data
2024-02-03 17:57:45 -06:00
Mark Thompson c30a61441b Tetris external app (#1842)
* Tetris external app
* 4 levels
2024-02-03 19:33:36 +01:00
Matias Fernandez f0f279eec5 Add frequency ranges to looking glass presets (#1841)
* Add DECT frequency range. The most common allocated range fits within 1880MHz-1930MHz (different countries use different ranges). To provide extra detail at the edges I'm extending 1MHz the range on each direction. So the final range covers from 1879MHz to 1931MHz

* Add VHF Marine band. Even when Wikipedia says that the Marine Band covers from 156MHz to 174MHz, I couldn't find any channels defined above 163MHz. So, the final range I'm using goes from 155MHz to 163MHz to ensure some detail at the edges.

* Add a narrow range around 433MHz frequency. This is a widely used frequency for all sorts of remote controls, keyfobs, etc. I think it's useful to have a narrower range just focusing on this frequency

* Add FRS/GMRS frequency range

* Alphabetize the list of ranges, keeping the really big ranges of whole ITU bands and the full supported range at the end of the list
2024-02-03 09:57:07 +01:00
Mark Thompson 464cc8449e Use std:rand() in Touchscreen Test (#1840) 2024-02-03 09:54:10 +01:00
Mark Thompson ee2e57d702 2nd-Level Encoder Debouncing for scrappy encoder dials (#1838)
* Experimental encoder debouncing

* Experimental encoder debouncing

* Clang

* Comment changed
2024-02-01 14:13:28 -06:00
Mark Thompson a2a5fb166e Improved Debounce for Encoders (#1837)
* Fix variable type declaration

* Fix typo

* Two-bit encoder debouncing

* Slight optimization

* Comment change
2024-01-31 14:13:21 -06:00
Mark Thompson f59f5dfaa3 Fixed ms_duration function to show actual ms (#1836) 2024-01-31 21:08:17 +01:00
E.T 13f12227bd Remove ccache hack from dockerfiles (#1835)
Now we have a proper solution in cmake
2024-01-30 21:32:01 +01:00
Herman Semenov c9db21063b Speedup compilation using ccache package (#1832) 2024-01-30 20:55:59 +01:00
Mark Thompson 125184f300 Add audio playback ability to WAV Viewer app (#1829) 2024-01-30 20:55:32 +01:00
ʎʞsʌoʞᴉɐɥɔꓕ ɹʇoʎꓒ 8068517808 add warn when enable stealth mode & add nav valid check (#1823)
* add warn when enable stealth

* change FLASH ERROR to FLASH ERR cuz previous one were overlapped with date

* check if nav is valid
2024-01-30 13:58:40 +08:00
Mark Thompson a4f6bbda5e Fix compiler warning (#1827)
* Fix compiler warning

* Fix compiler warning

* Friggin C-lang
2024-01-29 18:07:32 -06:00
Totoo 8b2598fdac Call the flasher app when flashing from serial (#1825) 2024-01-29 20:52:35 +01:00
sommermoregentraum c36fe78bd5 pacman: cheat lifes and cheat skip level (#1821)
cheat lives and cheat skip level and fixed review issue. Thanks @NotherNgineer
2024-01-29 00:54:13 +08:00
Mark Thompson bd8385464e Tuner code readability (no functional change) (#1820) 2024-01-28 10:02:11 -06:00
Totoo 8c996b5bc6 Embedded friendly usb (#1817) 2024-01-27 19:33:08 +01:00
Mark Thompson d1901939e1 Fix tuning of 2170-2300 and 2700-2740 MHz (#1819) 2024-01-27 13:30:32 +01:00
Mark Thompson 41ec11fb66 Check app checksums during untar (#1815) 2024-01-27 13:29:24 +01:00
Mark Thompson df47490d85 Allow 3 boot failures before Config Mode starts (#1816)
* Allow 3 boot attempts before Config Mode starts

* Add files via upload
2024-01-26 21:02:52 +13:00
Totoo a2f6d14b15 ISS for aprs, for the request #1810 (#1814) 2024-01-25 21:10:43 +01:00
Mark Thompson 6a6c6d6502 Add Checksums to Firmware & External App images (#1809)
* Pad image to 1MB and add simple checksum

* Test code to verify firmware checksum

* Comment out unneeded zlib

* Add files via upload

* Print space remaining in ROM

* Append checksum to external apps too

* Check external app checksums when loading

* Is it 2024 already?!

* Validate firmware checksum before flashing

* Add files via upload

* Added flash error warning to nav screen

* Clang

* Replaced some hard-coded values with #defines

* Check FW checksum before USB serial flash too

* Add files via upload
2024-01-24 16:37:21 -06:00
Mark Thompson 2d98c5d311 Resolve Soundboard tone key issue (#1806)
* Resolve Soundboard tone key issue
2024-01-23 20:53:04 +01:00
sommermoregentraum 03b13f8ab0 prevent crash when nothing to broadcast in soundbrd app (#1807)
* prevent crash when nothing to broadcast in soundbrd app

* format
2024-01-22 22:15:37 -06:00
E.T 6e74ad942f Gh workflow changelog formatting fix (#1803)
* Remove replacement of newlines with hex codes

Assuming that its unnecessary as we are no longer outputting these via stdout but pipe it into some binary that could handle newlines in its stdin therefore don't need and support this transformation

* Attempt 2 after reading gh docs :D

* Fix changelog for stable release too
2024-01-22 10:50:14 +01:00
Mark Thompson 0f85f247b6 Fix rounding bug when displaying frequencies (#1801) 2024-01-22 07:54:29 +01:00
sommermoregentraum ac1d350aaf add a item that warn user to put ext app if nothing to pop in home page (#1798)
add an item that warn user to put ext app if nothing to pop
2024-01-22 03:21:06 +08:00
Mark Thompson 5f8e1ef307 Daylight Savings Time support (#1793)
* Daylight Savings Time support

* Cleanup

* Clean-up

* Revert ADSB change

* Clean-up

* Corrected date in comment, ironically
2024-01-21 12:47:28 -06:00
E.T aa5d4ad078 Update github workflow to remove deprecated set-output usage (#1799) 2024-01-22 07:24:38 +13:00
sommermoregentraum 44d9572f5c add_bitmap_reverse_decode (#1795) 2024-01-21 18:51:08 +08:00
sommermoregentraum 3314001205 bring keyfob app back (#1794)
* bring keyfob app back

* format

* cleanup

* format

* remove committed line from original list
2024-01-21 11:49:17 +01:00
E.T ce1084abc7 Introduce common app list for menu and serial (#1791) 2024-01-20 23:35:39 +01:00
E.T ea8563cb19 Fix workflow to pick up the OCI named ppfw (#1787) 2024-01-18 21:08:02 +01:00
Mark Thompson b3c1c83677 Remove time from PPFW filename and modify Flash app to support .tar (#1783)
* Update timestamp in PPFW filename

* Only include today's DATE in filename

* Remove date from PPFW filename

* Allow any .tar file name in FIRMWARE folder

* Add files via upload

* Removed unnecessary lines per ufoka
2024-01-18 13:59:44 -06:00
Totoo fcbc3b4d75 pmemreset and settingsreset commands (#1786) 2024-01-18 16:12:13 +01:00
Mark Thompson 39e1a0ffe8 Cmake - generate new ppfw when firmware changes (#1782) 2024-01-17 07:01:48 +01:00
Mark Thompson 99bbd8805c Added Settings app to disable Config Mode (#1781) 2024-01-17 06:59:35 +01:00
Mark Thompson e7af85ffba Add new pmem fields to Debug Dump (#1780) 2024-01-16 22:49:56 +01:00
Mark Thompson 75f3277102 Geomap zoom-out fixes (#1777) 2024-01-16 18:47:31 +01:00
Totoo 216f8b64bf Fix build after merge (#1776) 2024-01-16 10:05:59 +01:00
Mark Thompson bc301c5fdb Geomap enhancements (#1767)
* Increase Geomap zoom-in ability
* Use floating point for current position
* Show grid squares when zoomed in too much for map
* Zoom in fast after exceeding map resolution & clean up redundant code
* Revert order of functions to make it easier to review
* Changed grid color for better contrast with markers
* Optimizations
* Set x_pos/x_pos to center pixel versus upper left corner
* Show more distant planes when zoomed out
* Correct pixel offset when zooming in
* Fix oops in x_pos/y_pos centering change
* Wrapping support for lat/lon fields
* Wrapping support (for Geomap lat/lon fields)
* Handle wrapping for negative lat/lon
2024-01-16 09:30:31 +01:00
gullradriel ae9d6de093 added sysinfo and radioinfo (#1769)
* added sysinfo and radioinfo

* using fillObuffer instead of chprintf

---------

Co-authored-by: GullCode <gullradriel@hotmail.com>
2024-01-15 15:54:27 +13:00
Bernd Herzog 7a4c3184a1 reworked usb serial communication (#1766) 2024-01-13 18:05:29 +01:00
Mark Thompson 58307aee9c Make use of "ensure_directory" function (#1765) 2024-01-13 07:57:32 +01:00
Bernd Herzog 409242507c Usb serial fix queue full crash (#1763)
* fixed usb serial queue crashing

* fixed usb input buffer handling

* fixed black screen issue
2024-01-12 21:49:10 +01:00
E.T 0a8194fa10 Really go home before starting app from serial (#1762) 2024-01-12 17:33:41 +01:00
Bernd Herzog eaac40df3a fixed usb serial queue crashing (#1761) 2024-01-12 16:04:32 +01:00
Mark Thompson 58b488b840 Added new "App Setting" app (#1759)
* New "App Settings" app for quicker settings access
2024-01-12 13:41:13 +01:00
Totoo 1676702a09 Shows a message when flashing from serial (#1758) 2024-01-12 10:30:24 +13:00
Totoo 3943848add GPS + orientation for Sonde (#1757)
* RadioSonde has gpsdata
* Orientation update in Sonde
2024-01-11 22:17:06 +01:00
Totoo 831dbeaab5 Add extra info from serial (#1754)
* Inject GPS postition from serial
2024-01-11 17:46:35 +01:00
jLynx 49e719ded8 Update repo links (#1750)
* Update repo links to new link

* Removed travis

* Removed travis

* Updated more links
2024-01-10 21:13:43 +13:00
Mark Thompson c2d2409bff Modify encoder handler to not call "on_encoder" functions if no change (#1749) 2024-01-09 23:32:58 -06:00
Mark Thompson 485f57d86b Added Audio Out to Soundboard app (#1748) 2024-01-09 13:50:45 -06:00
Totoo 0e6f303eec fix buffer size bug (#1747)
* fix buffer size bug

* faster file over usbcdc - wip

* minimal faster display tx
2024-01-10 06:30:43 +13:00
Totoo ad94ae05c1 Fwrite fix (#1744) 2024-01-09 09:09:25 +13:00
Bernd Herzog 89dc8c0463 found missing revert (#1743) 2024-01-09 08:13:51 +13:00
Bernd Herzog 77640c5cc0 Usb serial filesystem improvements (#1742)
* refactoring

* improved file system commands

* added binary read/write
2024-01-08 19:53:40 +01:00
Erwin Ried cdc8cfeb7d Update README.md (#1740) 2024-01-08 22:45:46 +13:00
Bernd Herzog 4740df2e2c Usb serial shell workerthread #2 (#1738) 2024-01-07 22:25:43 +01:00
Bernd Herzog ec0f45a488 added sys locks to time critical lcd updates (#1737) 2024-01-07 19:50:26 +01:00
Totoo 9d7e06c255 Appstart and applist from serial (#1736)
* Start app, and list fixed ones.
* Add ext app support
2024-01-07 18:56:09 +01:00
sommermoregentraum 4b93e78dd9 make pacman's face (mouse) face to the dir which he hits the wall (#1735) 2024-01-07 16:55:45 +01:00
Bernd Herzog 5ced8c90d3 added radio saturation to performance counter (#1734) 2024-01-07 15:31:08 +01:00
Bernd Herzog 9d22711368 added usb event to not wait for the next frame (#1733) 2024-01-07 14:09:22 +01:00
Bernd Herzog 23e6295dd2 removed the use of the hackrf cpld eeprom (#1732)
* removed the use of the hackrf cpld eeprom

* readded comment
2024-01-07 12:29:53 +01:00
Bernd Herzog bbd75ab7ef fixed usb descriptor product string length (#1731) 2024-01-07 11:57:16 +01:00
jLynx 7508fee62c Fixed sdcard zip folder structure (#1730)
* Fixed sd card zip folder structure

* Update create_nightly_release.yml

Updated zip for work map

* Update create_stable_release.yml

Replicated on stable release
2024-01-07 19:51:27 +13:00
John Stockdale fe88760766 First version of rtcget and rtcset (#1727) 2024-01-06 09:15:59 +01:00
E.T 75ece38725 Dockerfile overhaul (#1726)
Create entrypoint to orchestrate the build steps
Supported commands: make, ninja
Passes additional arguments to the make / ninja command at the end (like -jNN)
There is a shortcut to make -jNN by just specifying -jNN
Anything else will be directly executed (like getting a shell into the container with bash -li is still possible)
2024-01-06 00:42:36 +01:00
E.T b53032a8a5 Ppfw docker build (#1725)
* Docker build ppfw by default

* Allow GH workflows to be triggered from any branch

* Fix asset path for github upload
2024-01-05 21:59:15 +01:00
Mark Thompson e7f8952ece Fix compile error in deprecated app (#1723) 2024-01-05 10:13:43 -06:00
Totoo 82a6ae0791 Geomap speed display (#1722)
* speed to geomap
* Add speed to map
* Fix hidden state
* UI fix on ADSB tx
* UI fixes
2024-01-05 13:44:30 +01:00
Totoo d303098e35 Moved SPainter and GPSSim to ext app (#1721)
* Moved GPSSim
* Moved spainter
2024-01-05 10:46:09 +01:00
Totoo 1a69ce2d97 Accessibility over serial (#1717)
* Initial accessibility support
* added it to some widgets to test
* More widget accessibility response
* More widgets, better output
* Mark selected widget on list
* typo
2024-01-05 07:43:30 +01:00
Erwin Ried eedebe1c52 Update your links (#1719) 2024-01-05 07:43:13 +01:00
E.T 9e61f80809 Create cmake target ppfw (#1716)
* Create cmake target ppfw and oci

It will build an OCI ppfw package to be flashed or shared on test-drive

Additionally some minor cmake cleanup
And updated gitignore to allow multiple build folders

* Make ppfw / oci build default
2024-01-04 22:59:25 +01:00
Mark Thompson ff591c68a0 Updated Flash message to say device will restart (#1715) 2024-01-04 11:47:45 -06:00
Totoo 8761b9d7e0 Send ASCII chars from USB serial to selected widget (#1708)
* Initial commit for keyboard emulation
* Added on_keyboard to some widgets
* TextEdit partly
* Multi key send at once
* Frequency control support
* Fix encoder emulation
* Add keyboard to geomap
* More widgets
2024-01-04 17:36:31 +01:00
Erwin Ried 1b5125b0a8 Adding the new TechMinds video (#1712)
I think it explains very well everything, even if he calls it "Portapak"
2024-01-04 11:28:33 +01:00
E.T 9ba885361d add _OCI to ppfw package name (#1711) 2024-01-04 20:52:42 +13:00
Mark Thompson afa34d83b2 Added Zoom-Out capability to Geomap (#1710) 2024-01-04 07:32:44 +01:00
E.T d93c914dd3 Create firmware ppfw.tar to flash firmware together with apps (#1709)
* Github actions test

* Action to create and upload ppfw.tar

+remove unused travis config

* Changelog token fix

* Also generate ppfw for stable releases

* Make tar verbose
2024-01-03 22:32:34 +01:00
Mark Thompson 715a2dd448 Add Tamper Flags to ERT RX display and meter ID to log file (#1707) 2024-01-03 20:17:33 +01:00
Totoo 58bf60695d Touch emulation from usb cdc (#1706) 2024-01-03 14:06:29 +01:00
Totoo fbe7954f2e Added support for extracting firmware from TAR file (with apps) (#1704)
* Added support for extracting firmware from TAR file (with apps)
* Added tar to usb cdc firmware command.
* Serial flash tar
* Show error on bad tar
* Check tar for valid filenames
2024-01-03 14:05:24 +01:00
tomvandermussele d122a8fc3b Created Belgian Airband file (#1705)
* Created file in FREQMAN for the Belgian Airband (Brussels Airport)

* Added Military to BE Airband
2024-01-03 12:02:04 +01:00
Bernd Herzog 3998dc124a added cpld info shell commands (#1703)
* added cpld info shell commands

* fixed reset
2024-01-02 00:18:53 +01:00
jLynx 3d2da9c0db Serial info command (#1700)
* WIP adding more info

* Added source output & R9 detection

* WIP

* WIP

* Moved hackrf back

* Fixed output error
2024-01-02 07:25:36 +13:00
Erwin Ried 3189d3af4c Update FUNDING.yml (#1702)
Removing bountysource, since they scammed everyone: https://github.com/bountysource/core/issues/1586
2024-01-01 15:14:37 +01:00
E.T 1b3a99cc5e Gcc warning onscreen (#1701)
* Fix GCC version detection for old CMAKE

* Yellow tint version number / hash on gcc mismatch
2023-12-31 15:46:26 -06:00
E.T 935c43e88f Gcc version warning (#1698)
* Compiler warning
* Fix warning message
2023-12-31 16:36:19 +01:00
gullradriel 2893c031ab Recon repeat delay (#1699)
- Recon repeat delay, range: from 0s to 254s, timer is played before each TX
- fixed missing '<' '>' around button names for DELETE and REMOVE name to indicate that they can also be used to skip in the list
- added missing pmem recon dump entries
2023-12-30 22:32:41 +01:00
E.T 94cdb16ca9 Mention external apps in stable release notes (#1694) 2023-12-29 15:11:40 -06:00
jLynx 84d4066986 V1.9.1 (#1691)
* Update past_version.txt

* Update version.txt
2023-12-29 22:07:43 +13:00
Mark Thompson 2153c2dd10 ERT app - Fix freq field overlapping amp setting (#1689) 2023-12-29 07:28:42 +01:00
gullradriel a7393c3492 fix for freq_b equal zero in repeater entries (#1686) 2023-12-28 20:10:38 +01:00
gullradriel 794fece8cc Recon raw repeat (#1658)
* UI for Recon Repeater config
* persistent recon repeat settings
* record_view: added possibility to force keep the provided record filename
* working auto record to same file
* Recon TX 
* added Repeater type
* adding yellow coloring on Repeater config+a modal, comments in the code
* default repeater values
2023-12-28 11:25:53 +01:00
Totoo 1bf95e85a0 Fixed #1670 (#1676) 2023-12-26 07:16:54 +01:00
Brumi-2021 a85357a8af Minor Improvement (Extending_min_CLK_out_from_10khz_to_4khz) (#1675) 2023-12-26 07:11:14 +01:00
Totoo 7bf3e02f6c Faster usb serial file download (#1674)
* Made file read much faster via USB serial
* Remove old code
2023-12-25 17:25:16 +01:00
Mark Thompson 459e8d0b24 Disallow CLKOUT freq changes on r9 (#1672) 2023-12-24 17:28:22 +01:00
Mark Thompson 93585d846a CLKOUT workaround for r9 boards (#1671) 2023-12-24 11:06:11 +01:00
Mark Thompson 4eb5c4603e Simplify CLKOUT frequency entry (#1668) 2023-12-24 08:55:15 +01:00
Mark Thompson f42ad5ffc6 Configure CLKOUT at power-up per PMEM setting (#1669) 2023-12-24 08:54:56 +01:00
gullradriel d0bd8e50be Added USB serial checkbox (#1664) 2023-12-22 11:00:27 +01:00
Mark Thompson 337e3f4449 Enhance Debug->Peripherals to view all registers of SI5351 chip (#1663) 2023-12-22 08:45:05 +01:00
jLynx 53bbf74f4d V1.9.0 (#1660)
* Update version.txt

* Update past_version.txt
2023-12-22 07:35:54 +13:00
Mark Thompson 19a66fac20 Enable Sample Rate to be spec'd in .ini file for Weather & SubGhzDecoder apps (#1659) 2023-12-21 19:33:06 +01:00
Totoo 36e1b9a36e Portapack USB screen tx (#1653)
* implemented 2 screen read functions, and haxfixed windows usb bug
* Support up to 64 byte per packet for faster tx
* Added filesize command
2023-12-19 22:25:22 +01:00
Netro 1ca9288bbe initial USB serial thread integrated to BLE RX (#1651) 2023-12-19 22:20:13 +01:00
Mark Thompson 4ca2d1e352 Fix compile error (#1650) 2023-12-18 07:35:00 +01:00
gullradriel 0b11730835 fix wrong direction in tx freq correction (#1649)
Co-authored-by: GullCode <gullradriel@hotmail.com>
2023-12-17 18:47:20 -06:00
Bernd Herzog 6069145b68 Usb serial (#1648)
* enabled usb clock
* added usb stack to application
* fixed pll0usb clock setup
* implemented serial usb handshake
* implemented serial communication
* integrated chibios shell
* implemented device reset
* implemented enter dfu mode
* implemented hackrf mode command
* implemented flash command
* implemented memory manipulation
* implemented button control
* fixed mode change
* improved reset behavior
* implemented directory commands
* implemented file commands
* improved data communication
* refactorings
2023-12-17 17:20:35 +01:00
Totoo 2ccda5aebd Subghz decoder (#1646)
* Initial commit - wip

* Half part of the transition of baseband processor.

* More SGD

* WIP, Weather refactor, UI improv

* Rename

* Added 4msps, and fixes

* Fixes

* princeton working

* Renamed proc_weather, bc now multifunctional

* Proto: bett

* FPS_CAME = 4,
    FPS_PRASTEL = 5,
    FPS_AIRFORCE = 6,

* Came Atomo, fixes

* Separate weather and sgd, bc of baseband size limit

* Fix display

* Save space

* More protos

* Dooya proto added

* More protos

* add protos

* More protos

* Move weather to ext app

* nw

* Revert "Move weather to ext app"

This reverts commit 8a84aac2f5.

* revert

* Fix merge

* Better naming

* More protos

* More protos

* Add protos

* Fix warning

* Add NeroRadio

* more protos

* more protos

* More protos

* Shrink a bit

* fixes

* More protos

* Nicer code

* Fix naming

* Fix format

* Remove unused

* Fix some protos, that needs a LOOOONG part with the same lo/high

* Modify key calculation
2023-12-16 16:37:51 -06:00
Totoo 02810bf527 Move Jammer ext app (with baseband image too) (#1647) 2023-12-15 20:18:21 +01:00
Totoo b58ee761a1 Move more apps + language module (#1643)
* CoasterP to ext (Burger pager)
* LGE to ext app
* Solve compiler literal removal with centralizing common string literals.
2023-12-12 16:55:50 +01:00
Totoo 5b9d898202 Fix oregon2 weather protocol (#1642) 2023-12-12 06:45:56 +01:00
Totoo 5f8e44e7b7 NRF RX to ext app (#1641)
* NFR Rx to ext app
2023-12-11 11:57:04 +01:00
Totoo ec80859d93 AnalogTV to ext (#1638) 2023-12-11 08:55:07 +01:00
Totoo 5e9d92d65c Set sr to 4m (#1636)
* Set sr to 4m

* Rename variable
2023-12-10 23:40:11 +01:00
Mark Thompson c3bb9c0a16 Weather display beautification (#1635) 2023-12-10 17:57:21 +01:00
Netro bd475e6f9c BLE RX - Find From File Initial Commit. (#1627)
* initial work
* Handling capping count only at total count
* Adding logging back in
2023-12-09 18:02:00 +01:00
Mark Thompson 0f1708d8fd Increase SSP timing margin for MAX2839 registers (#1632) 2023-12-09 14:09:20 +01:00
Mark Thompson efa0e270ad Updated Weather & TPMS app icons (#1630) 2023-12-09 09:25:41 +01:00
Totoo ff882024d4 Ble spam improvements (#1626)
* Much faster ble  spam tx
* Better Android handling
2023-12-09 09:24:02 +01:00
Brumi-2021 60d95447fb Improved sensitivity/selectivity Weather App (#1628) 2023-12-08 21:33:48 +01:00
Mark Thompson a474bf8fe3 Add files via upload (#1629) 2023-12-08 19:20:59 +01:00
Totoo 1bf0d2b3e6 BLE spam ext app (#1624)
* BLESpam ext app first release

* Ios partly added

* Fix missing!

* Maybe fix Ios

* Add comment

* Fixes

* Added samsung and windows

* cleaning a bit

* Partial size fix

* Move continuity to blespam namespace.

* Optimize code layout, so not using fw space

* Ui changes
2023-12-06 08:35:23 -05:00
Mark Thompson ea02d98a59 Preserve BLE RX app settings across runs, and changed BLE app settings file names (#1622)
* Add files via upload

* Add files via upload

* Add files via upload
2023-12-04 12:06:07 -05:00
Mark Thompson 9624d7b429 Fix compiler warning (#1620) 2023-12-03 08:19:04 +01:00
Totoo 4f440055cb Fix to_string_hex_array string reserve (#1619) 2023-12-02 16:42:09 -06:00
Netro 7354ba8fd5 Random data selection feature done. (#1617) 2023-12-02 08:05:29 +01:00
Netro b7b4a10485 Refined Tx Random Data (Still WIP) (#1616)
* managing initial cursor
* work on adding marked cursors
2023-11-30 21:57:22 +01:00
Totoo cca0e18f5a Weather improvements (#1615)
* Added Acurite986 protocol
* Added signal age
* Added myself to about screen
2023-11-30 12:36:59 +01:00
Netro 8846926b68 BLE TX: use text viewer to inject rand data WIP (#1613) 2023-11-29 18:54:17 +01:00
Mark Thompson 7ceefb1b81 Weather app tweaks (#1610)
* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload
2023-11-29 10:37:01 -06:00
Erwin Ried 861f684d00 Discord icon/invite more clear on top (#1612) 2023-11-29 13:22:51 +01:00
Erwin Ried 17b146b960 Update README.md (#1611) 2023-11-29 11:23:27 +01:00
Totoo 02251eeeb4 Added weather station app with 18 protocol parsers (#1607)
* Added weather station app with 18 protocol parsers
* Fix button and formatting
* Set BW to 1.75m, changed to us in dsp part
2023-11-28 21:11:30 +01:00
Netro c486572d3d BLE TX; Added feature to randomize found substring in data (#1609) 2023-11-28 21:11:07 +01:00
Netro a6ed6e3099 BLE RX: Making auto channel timer independent of packet events. (#1608) 2023-11-28 18:18:15 +01:00
Netro 175e5e2e8c BLE: Minor improvements to fix occasional not start on tx app first time. (#1605) 2023-11-27 21:59:29 +01:00
Mark Thompson 7bc27038be Save Morse app settings (#1602) 2023-11-26 12:17:23 +01:00
Brumi-2021 609bf3219f solve Morse_TX_App latch up in r9 (#1601) 2023-11-25 01:01:52 +01:00
Mark Thompson bdbd616a84 added a frequency field to the ERT app 2023-11-24 09:25:50 +01:00
Mark Thompson 1cbe5cb293 Save TPMS units in App Settings and tune frequency (#1598) 2023-11-21 18:44:10 +01:00
gullradriel df6003c08f fix button label (#1597)
Co-authored-by: GullCode <gullradriel@hotmail.com>
2023-11-20 12:09:47 -05:00
Netro 50fcbf0f5a BLE: added deleting and renaming temp file (#1596) 2023-11-20 08:37:56 +01:00
Netro 5a1676a0b9 BLE List improvements (#1595)
* WIP on add to save list

* Added rewriting existing lines and appending new to same file.

* format

* not looking on new file.

* Added temp list due to some issue of recent being updated during save

I was receiving a system halt and this seems to fix it. Needs more testing though.

* clang format

* Stored entries backward. Fix.

* Update file working, need to replace temp file though.
2023-11-19 20:53:53 -05:00
gullradriel 309f2fbd2c support for a sdcard speed option (#1594)
* support for a sdcard speed option

* trying to link sdio_cclk_set from file

* changed io to IO in checkbox text

* changed order so high speed option is read after pmem is restored from sd

* test button

* took out unneeded comment

* force behavior of test button

---------

Co-authored-by: GullCode <gullradriel@hotmail.com>
2023-11-19 08:08:29 -06:00
gullradriel 650aacfaa7 BLE RX: added clear list button (#1593)
* added clear list button

* Bigger buttons and text

* Change case on labels

---------

Co-authored-by: GullCode <gullradriel@hotmail.com>
2023-11-18 16:32:52 -05:00
Mark Thompson 8655027a5b Fixed SPI Read timing issue affecting MAX283x chips (#1590)
* Slowed SPI timing to MAX283x by 20%

* Fix temperature scaling
2023-11-18 09:50:57 -06:00
Mark Thompson de937f02d4 Added Memory Dump app for Debug (#1588)
* Add files via upload

* Add files via upload

* Added single word read/write support

* Add files via upload
2023-11-18 07:16:56 -06:00
Mark Thompson 2c0446e83d Improved Focus Manager navigation (#1589)
* Focus manager navigation fix

* Add files via upload
2023-11-18 06:58:38 -06:00
Netro 9e6699d8cc Fixed name. (#1586) 2023-11-17 13:28:09 -05:00
Mark Thompson ef92c5bc0a Enhanced Debug -> Peripherals app to allow direct register modification (#1584)
* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload

* Clang

* Clang

* Tweak position of field on screen

* Reverted text on Write button per reviewer feedback
2023-11-17 12:10:11 -06:00
Netro 10f6600f52 No need limit to 50 since we moved to min 16ms send times. (#1583) 2023-11-16 21:28:22 +01:00
Netro aeb607e73b BLE Rx Save List / Save packet from BLE Rx (#1582)
* Added Tx Save from Rx Details View
* Added saving of current list
2023-11-16 20:00:52 +01:00
Netro 8479d2edf0 BLE Comm WIP (#1578)
* Initial BLE Comm commit.
* SCAN_RSP MAC was reversed.
* Added Auto Channel Hop.
* Improvements to Tx to better handle timers.
* Auto channel and more work on timers.
* more advertisement numbers.
2023-11-16 14:46:45 +01:00
Bernd Herzog d2e9a8dc06 Hackrf mode when no pp attached (#1580)
* added portapack presence detection

* adjusted timeout

* fixed ak4951 initialisation
2023-11-14 12:22:26 +01:00
Netro 4571cb0b61 BLE Rx Refactoring / Cleanup (#1577) , also added showing MAC for Scan_Rsp Adv_Direct 2023-11-12 19:10:53 +01:00
gullradriel 351f7b13b3 BTLE RX: fix for name disappearing (#1575)
* Fix for name disappearing

* cleanings

---------

Co-authored-by: GullCode <gullradriel@hotmail.com>
2023-11-12 09:59:36 -05:00
Netro 5953cb57b0 Rx parsing fix (#1573)
* restored mistakenly quoted code
* removed unneeded line.
* added parsing for other PDU Types and added PDU Type label.
* added missing SCAN_IND
2023-11-12 08:54:04 +01:00
Brumi-2021 b60b873428 Changing BLTE FIR filter to improve RX sensitivity (#1574) 2023-11-11 20:43:10 -05:00
Netro a3249cae26 BLE Rx/Tx App Cleanup (#1570)
* Showing highest dB first
* Including namestring in search
* Using replace seems to work better.
* bletx cleanup
* removing pop
* pop is needed on this view.
* cleanup switching
* reduce to 1 entry list
* Setting to use the name of BLE with a toggle checkbox.
* Removed &nav reference.
* Removing const
* Fixed issue with memory being run out when switching from a previous app
* Fixed not setting updated progressbar for each new packet.
2023-11-11 20:46:51 +01:00
Kyle Reed f7f784c0f4 Cleanup dead code in the ADSB RX proc (#1572)
* Cleanup dead code in the ADSB RX proc

* Fix comments

* Formatting
2023-11-10 11:38:06 -08:00
Kyle Reed f4f538f69b ADSB RX App cleanup (#1569)
* WIP refactoring/gardening

* WIP cleanup, adding status dots

* Rename ageStep

* WIP Cleanup

* Wrapping up ADSB refactor/cleanup.

* Don't initialize strings to "     "

* Better map refresh

* Fix colorization of recent entries

* Fit and finish
2023-11-10 08:22:29 -08:00
Netro bbd1a5a2ef Changing filter on ble rx. Seems to work better? (#1568) 2023-11-08 19:22:57 +01:00
Nemanja Nedeljković bc71238046 Patch build for gcc 13.2.0 (#1567) 2023-11-08 19:22:26 +01:00
Netro a9df9dde69 BLE Rx parsing improvements (#1566)
* Running simple state machine to better catch in between buffers. 
* Fixed buffer size 2048 was too large for a decimated before only containing 512 (2048/4 Decimation) Bytes.
* attempt at trying to minimize uncleared str buffers
2023-11-07 18:10:18 +01:00
Mark Thompson 7a87e3f3af Added File Manager button to show hidden files (#1564)
* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload
2023-11-06 16:33:19 -06:00
Mark Thompson 68f960e4e7 Temperature scaling changes for HackRF r9 (MAX2839) (#1561)
* Add files via upload

* Add files via upload

* Add files via upload
2023-11-06 13:56:09 -06:00
gullradriel 6784fe72dd fixed display when delete/remove entry (#1563) 2023-11-06 20:38:37 +01:00
Netro 312df0fcec Save tx file (#1560)
* Added saving current packets to file
2023-11-06 19:09:26 +01:00
Erwin Ried 2913a41aec Update README.md (#1559) 2023-11-06 11:16:13 +01:00
Netro adabbfbef1 Send on select (#1557)
* fixed bug not fully displaying rx packet
* added sending selected packet
* setting packet count to 100 as 1 is too aggressive for speed > 2
* setting limit to 50 as 50 does not bog down UI at speed 5.
* fix tx channel getting out of sync with freq
2023-11-06 11:16:01 +01:00
gullradriel 2b7962fa7c Level RSSIGraph tuning (#1556)
* Added whole graph RSSI min/avg/max/delta to RSSIGraph
* fix for min=0 values when modulation is changed
* literal number as a #define flag
2023-11-06 11:12:36 +01:00
gullradriel 645f02e2f3 updated submodule (#1551)
Co-authored-by: GullCode <gullradriel@hotmail.com>
2023-11-04 22:41:38 -05:00
Netro b178462e47 BLE Rx improvements. (#1550)
* Added parsing for ble name added focus for ble tx play button.
* Added sort by name
* Fixed issue where we were pushing too early before pop was finished.
* Fix ordering
* Fix field location.
* truncating entries to stop memory overflow
* Added Number of Hits.
* Limiting number of hits to uint16_t, as any higher would overflow screen
* Limiting number of hits to uint16_t due to possible screen overflow.
* Stop creating a log every time app starts. Cluttering log folder.
2023-11-04 08:29:48 +01:00
Netro 1639348b94 More btle improvements (#1549)
* Cleanup of BLE Rx naming and added switching between the two.
Fixed bug in packet count not resetting when opening new file in BLE Tx
* Bug fix and color change.
2023-11-03 08:08:49 +01:00
Netro f67fe262bb Add open file focus and changing the way send and loop works (#1548) 2023-11-02 19:47:34 +01:00
Netro 0681117af6 Bletx multi packet (#1547)
* Added multiple packet support
* Fixed Issue where default was RAW and discovery needed toggle to work on init
* Added random MAC toggle
2023-11-02 17:32:29 +01:00
Netro b5c244a09b Ble tx improvements (#1546) 2023-11-01 23:24:55 +01:00
Netro dceb7255b0 BLE Tx App / BLE Rx Filtering. (#1543)
BLE TX app creation
BLE RX and TX app improvements
2023-11-01 11:46:41 +01:00
Brumi-2021 62307b93d7 Clean compile warning (#1545) 2023-11-01 00:25:11 +01:00
Mark Thompson 61ce8b11a6 Mic app enhancements - "Transmitting" icon, PTT TX button always enabled, and persistent settings (#1531)
* Add files via upload

* Clang

* Clang

* Persist mic settings

* Preserve mode setting

* Clang

* Add files via upload

* Add files via upload

* Clang

* Clang

* Adjusted Transmitting icon position

* Clang

* Changed common freq checkbox description

* Fix swizzled USB/LSB/DSB modulation values in widget
2023-10-31 23:31:10 +01:00
gullradriel 211f40ddf3 Recon: fix output when typing name (#1538)
* Changed some buttons name and fixed blank output if typed

* code format

---------

Co-authored-by: Gull Ra Driel <gull@NiloreaDevLaptop.home>
Co-authored-by: gullradriel <gull@nomail.net>
2023-10-30 16:54:37 -05:00
pathetic nihilist 01dd0c79ed tiny fix: fix for wrong wav (#1540)
* fix_for_wrong_wav

* correct_wrong_file
2023-10-30 11:15:09 -05:00
Brumi-2021 be7b402c95 Adding raw data stream BPSK - QPSK (#1534)
* Adding raw data stream BPSK - QPSK
* minor code compact
2023-10-30 11:32:34 +01:00
Mark Thompson a11929d2ca Preallocate enough vectors for common app settings #1535 2023-10-30 11:32:01 +01:00
Mark Thompson 0bacb31d17 Improve blacklist to require precise app name matches (#1532)
* Add files via upload

* Add files via upload

* Add files via upload

* Clang

* Update ui_btngrid.cpp

* Update comment
2023-10-28 21:32:59 +02:00
Mark Thompson cc7e4aec8a Add files via upload (#1533) 2023-10-28 08:28:17 +02:00
Netro 15c338e5b3 Added Sorting to BLE app (#1527)
* First BLE work

* Adding new fsk proc WIP

* Reverting ble stuff

* Initial compile working

* more work.

* Adding waterfall for debug

* more edits to debug

* Work to get widgets to show.

* cleanup before attempting diff fsk modulation method

* Temporary debug to learn how decimation scales.

* Tab view for console and spectrum. Spectrum still not working right.

* Fixed spectrum offset.

* Added audio sampling rate increments to freqman

* Added overriding range for frequency field and working off  deviation

* BLE cleanup. Got PDU parsing.

* Parsing CRC

* forgot :

* Removing AA again because cluttering UI

* fix compile

* attempt at throttling.

* WIP changes.

* Decimating by 4 to handle issue with overloading.

* Attempt to parse MAC still needs work.

* Small fixes. MAC still wrong.

* Fixed invalid indexing on Symbols.

* List view of BLE Mac Addresses

* Added Channel Option and improved GUI header.

* renaming to dB and fixing some warnings.

* Advertisements only.

* Initial cut of BLE Advertisement scan app.

* Copyrights

* formatting correctly in association to clang13

* Fixing warning and hiding fsk rx.

* spacing

* Removing some cmake install files that weren't suppose to be there.

* missed some.

* Added name to about.

* Edits for PR review pt.1

* Refactor ORing with 0 doesn't make sense.

* remove  parenthesis

* More PR Review changes.

* Fix compiler error.

* PR Review edits.

* PR review changes.

* Fixes.

* Unneeded ;

* Update ui_about_simple.cpp

* Disabling frequency stepping.

* removing unused label

* Fixing something that got messed up in merge?

* removing file that got added.

* editing labels.

* Added sort functionality.

* removed unused code.

---------

Co-authored-by: jLynx <admin@jlynx.net>
2023-10-24 17:08:45 -05:00
Netro 80bfcd5eb1 Restricting Frequency change to only Channel Options Toggle. (#1526)
* First BLE work
2023-10-23 20:54:04 +02:00
Netro b96f14762f New and Improved BLE App. (#1524)
* First BLE work

* Adding new fsk proc WIP

* Reverting ble stuff

* Initial compile working

* more work.

* Adding waterfall for debug

* more edits to debug

* Work to get widgets to show.

* cleanup before attempting diff fsk modulation method

* Temporary debug to learn how decimation scales.

* Tab view for console and spectrum. Spectrum still not working right.

* Fixed spectrum offset.

* Added audio sampling rate increments to freqman

* Added overriding range for frequency field and working off  deviation

* BLE cleanup. Got PDU parsing.

* Parsing CRC

* forgot :

* Removing AA again because cluttering UI

* fix compile

* attempt at throttling.

* WIP changes.

* Decimating by 4 to handle issue with overloading.

* Attempt to parse MAC still needs work.

* Small fixes. MAC still wrong.

* Fixed invalid indexing on Symbols.

* List view of BLE Mac Addresses

* Added Channel Option and improved GUI header.

* renaming to dB and fixing some warnings.

* Advertisements only.

* Initial cut of BLE Advertisement scan app.

* Copyrights

* formatting correctly in association to clang13

* Fixing warning and hiding fsk rx.

* spacing

* Removing some cmake install files that weren't suppose to be there.

* missed some.

* Added name to about.

* Edits for PR review pt.1

* Refactor ORing with 0 doesn't make sense.

* remove  parenthesis

* More PR Review changes.

* Fix compiler error.

* PR Review edits.

* PR review changes.

* Fixes.

* Unneeded ;

* Update ui_about_simple.cpp

---------

Co-authored-by: jLynx <admin@jlynx.net>
2023-10-22 22:58:14 -07:00
Brumi-2021 0feacfa102 Solve mic to hp small side effect (#1523)
* Allowing to hear Mic at TX time with Receiver ON

* Solve small side effect to Audio RX App
2023-10-22 19:10:26 +02:00
Brumi-2021 a1496c1b79 Allowing to hear Mic at TX time with Receiver ON (#1522) 2023-10-22 01:13:32 +02:00
Brumi-2021 1b73a138b6 Mic to hp refactoring (#1521)
* Refactoring previous "Hear Mic" PR

* Missed file
2023-10-21 17:31:17 +02:00
Brumi-2021 efcedd9005 Add "Hear Mic" feature to the Mic App (#1518)
* Add "Hear Mic" feature to the Mic App

* Following consensus change about ternary operators
2023-10-20 18:43:22 -05:00
Mark Thompson 86d4b17257 Simple amplification option in IQ Trim app (#1506)
* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload
2023-10-19 20:14:25 +02:00
Mark Thompson f6a437f7fb Create SETTINGS folder if one does not exist (#1512)
* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload
2023-10-19 20:09:49 +02:00
486 changed files with 33862 additions and 4270 deletions
+1 -1
View File
@@ -9,4 +9,4 @@ community_bridge: # Replace with a single Community Bridge project-name e.g., cl
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt 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
+48
View File
@@ -0,0 +1,48 @@
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,24 @@
name: Feature Request
description: For feature requests regarding the firmware.
labels: ['feature request']
body:
- type: markdown
attributes:
value: |
Thank you for taking the time to fill out an issue, this template is meant for any feature suggestions.
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.
@@ -9,8 +9,8 @@ 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
* 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
@@ -27,12 +27,12 @@ Steps to reproduce the behavior:
**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
You can find the list of versions here: https://github.com/portapack-mayhem/mayhem-firmware/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
You can find the latest nightly version here https://github.com/portapack-mayhem/mayhem-firmware/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.
-35
View File
@@ -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.
+5
View File
@@ -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.
-20
View File
@@ -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.
+2 -2
View File
@@ -6,8 +6,8 @@ from datetime import datetime, timedelta, timezone
# Set up your personal access token and the repository details
token = os.environ.get('GH_TOKEN')
repo_owner = "eried"
repo_name = "portapack-mayhem"
repo_owner = "portapack-mayhem"
repo_name = "mayhem-firmware"
def print_stable_changelog(previous_sha):
+28 -13
View File
@@ -20,7 +20,7 @@ jobs:
- name: check latest commit is less than a day
id: should_run
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:
needs: check_date
if: ${{ needs.check_date.outputs.should_run != 'false' }}
@@ -28,15 +28,20 @@ jobs:
steps:
- name: Get current 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
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
uses: actions/checkout@master
with:
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
- name: Git Sumbodule Update
run: |
@@ -49,10 +54,10 @@ jobs:
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
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 && cd sdcard && zip -r ../sdcard-no-map.zip . && cd ..
- name: Download world map
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
run: |
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 *
- name: Create SD Card ZIP
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 && cd sdcard && zip -r ../sdcard.zip . && cd ..
- name: Create changelog
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
CHANGELOG=$(python3 .github/workflows/changelog.py)
CHANGELOG="${CHANGELOG//'%'/'%25'}"
CHANGELOG="${CHANGELOG//$'\n'/'%0A'}"
CHANGELOG="${CHANGELOG//$'\r'/'%0D'}"
echo "::set-output name=content::$CHANGELOG"
{
echo 'content<<EOF'
python3 .github/workflows/changelog.py
echo EOF
} >> "$GITHUB_OUTPUT"
id: changelog
- name: Create Release
id: create_release
@@ -88,6 +93,16 @@ jobs:
${{ steps.changelog.outputs.content }}
draft: false
prerelease: true
- name: Upload Firmware TAR Asset
id: upload-firmware-tar-asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: build/firmware/portapack-mayhem_OCI.ppfw.tar
asset_name: mayhem_nightly_${{ steps.version_date.outputs.date }}_OCI.ppfw.tar
asset_content_type: application/x-tar
- name: Upload Firmware Asset
id: upload-firmware-asset
uses: actions/upload-release-asset@v1
+33 -17
View File
@@ -9,22 +9,27 @@ jobs:
steps:
- name: Get current date
id: date
run: echo "::set-output name=date::$(date +'%Y-%m-%d')"
run: echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT
- name: Checkout
uses: actions/checkout@master
with:
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
- name: Git Sumbodule Update
run: |
git submodule update --init --recursive
- name: Get 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
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
run: docker build -t portapack-dev -f dockerfile-nogit . --tag my-image-name:$(date +%s)
- 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
- name: Create Small SD Card ZIP - No World Map
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 && cd sdcard && zip -r ../sdcard-no-map.zip . && cd ..
- name: Download world map
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
run: |
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 *
- name: Create SD Card ZIP
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 && cd sdcard && zip -r ../sdcard.zip . && cd ..
- name: Create changelog
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
CHANGELOG=$(python3 .github/workflows/changelog.py ${{ steps.past_version.outputs.past_version }})
CHANGELOG="${CHANGELOG//'%'/'%25'}"
CHANGELOG="${CHANGELOG//$'\n'/'%0A'}"
CHANGELOG="${CHANGELOG//$'\r'/'%0D'}"
echo "::set-output name=content::$CHANGELOG"
{
echo 'content<<EOF'
python3 .github/workflows/changelog.py ${{ steps.past_version.outputs.past_version }}
echo EOF
} >> "$GITHUB_OUTPUT"
id: changelog
- name: Create Release
id: create_release
@@ -66,21 +71,32 @@ jobs:
release_name: Mayhem firmware ${{ steps.version.outputs.version }}
body: |
**Stable release - ${{ steps.version.outputs.version }}**
This is a fork of the [Havoc](https://github.com/furrtek/portapack-havoc/) firmware, which itself was a fork of the [PortaPack](https://github.com/sharebrained/portapack-hackrf) firmware, an add-on for the [HackRF](http://greatscottgadgets.com/hackrf/). Please check the [readme](https://github.com/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
### Revision (${{ steps.version.outputs.version }}):
${{ 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
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
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
prerelease: false
- name: Upload Firmware TAR Asset
id: upload-firmware-tar-asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: build/firmware/portapack-mayhem_OCI.ppfw.tar
asset_name: mayhem_${{ steps.version.outputs.version }}_OCI.ppfw.tar
asset_content_type: application/x-tar
- name: Upload Firmware Asset
id: upload-firmware-asset
uses: actions/upload-release-asset@v1
+1 -1
View File
@@ -1 +1 @@
v1.7.4
v2.0.0
+1 -1
View File
@@ -1 +1 @@
v1.8.0
v2.0.1
+11 -3
View File
@@ -49,16 +49,17 @@
*.map
*.lst
.dep/
build/
/build*
CMakeFiles/
# Debugging
.gdbinit*
# Editor files
# Editor/ IDE files
*.sublime-project
*.sublime-workspace
.vscode
.idea
# VSCodium extensions
.history
@@ -67,8 +68,15 @@ CMakeFiles/
.DS_Store
/firmware/CMakeCache.txt
# Python env
# Python env/ venv and cache
env/
venv/
**/__pycache__/
*.pyc
# Other
*.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
View File
@@ -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
+17 -2
View File
@@ -18,15 +18,17 @@
# Boston, MA 02110-1301, USA.
#
cmake_minimum_required(VERSION 3.0)
cmake_minimum_required(VERSION 3.5)
cmake_policy(SET CMP0005 NEW)
set(CMAKE_TOOLCHAIN_FILE ${CMAKE_CURRENT_LIST_DIR}/firmware/toolchain-arm-cortex-m.cmake)
project(portapack-h1)
set(EXPECTED_GCC_VERSION "9.2.1")
set(VERSION "$ENV{VERSION_STRING}")
if ("$ENV{VERSION_STRING}" STREQUAL "")
if ("${VERSION}" STREQUAL "")
execute_process(
COMMAND git log -n 1 --format=%h
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
@@ -40,6 +42,10 @@ if ("$ENV{VERSION_STRING}" STREQUAL "")
else (GIT_VERSION_FOUND)
set(VERSION "${GIT_VERSION}")
endif (GIT_VERSION_FOUND)
set(VERSION_NOHASH "dev")
else()
set(VERSION_NOHASH "${VERSION}")
endif()
execute_process(
@@ -48,6 +54,8 @@ execute_process(
)
set(VERSION_MD5 "0x${VERSION_MD5}")
message("Building version: ${VERSION} MD5: ${VERSION_MD5}")
set(LICENSE_PATH ${CMAKE_CURRENT_LIST_DIR}/LICENSE)
set(HARDWARE_PATH ${CMAKE_CURRENT_LIST_DIR}/hardware)
@@ -64,5 +72,12 @@ 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_BIN_IMAGE ${hackrf_usb_BINARY_DIR}/${HACKRF_FIRMWARE_BIN_FILENAME})
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()
add_subdirectory(firmware)
+25 -17
View File
@@ -1,58 +1,66 @@
> [!WARNING]
> __IF YOU'VE PAID FOR MAYHEM OR ANY PREPACKAGED PACKAGES, YOU'RE BEING SCAMMED.__
> The only legitimate link leading to our repositories is the organization [portapack-mayhem](https://github.com/portapack-mayhem/mayhem-firmware).
# PortaPack Mayhem
[![Build Status](https://travis-ci.com/eried/portapack-mayhem.svg?branch=master)](https://travis-ci.com/eried/portapack-mayhem) [![Nightly Release](https://github.com/eried/portapack-mayhem/actions/workflows/create_nightly_release.yml/badge.svg?branch=next)](https://github.com/eried/portapack-mayhem/actions/workflows/create_nightly_release.yml) [![CodeScene Code Health](https://codescene.io/projects/8381/status-badges/code-health)](https://codescene.io/projects/8381) [![GitHub All Releases](https://img.shields.io/github/downloads/eried/portapack-mayhem/total)](https://github.com/eried/portapack-mayhem/releases) [![GitHub Releases](https://img.shields.io/github/downloads/eried/portapack-mayhem/latest/total)](https://github.com/eried/portapack-mayhem/releases/latest) [![Docker Hub Pulls](https://img.shields.io/docker/pulls/eried/portapack.svg)](https://hub.docker.com/r/eried/portapack) [![Discord Chat](https://img.shields.io/discord/719669764804444213.svg)](https://discord.gg/tuwVMv3) [![Check bounties!](https://img.shields.io/bountysource/team/portapack-mayhem/activity?color=%2333ccff&label=bountysource%20%28USD%29&style=plastic)](https://www.bountysource.com/teams/portapack-mayhem/issues)
[![Nightly Release](https://github.com/portapack-mayhem/mayhem-firmware/actions/workflows/create_nightly_release.yml/badge.svg?branch=next)](https://github.com/portapack-mayhem/mayhem-firmware/actions/workflows/create_nightly_release.yml) [![CodeScene Code Health](https://codescene.io/projects/8381/status-badges/code-health)](https://codescene.io/projects/8381) [![GitHub All Releases](https://img.shields.io/github/downloads/portapack-mayhem/mayhem-firmware/total)](https://github.com/portapack-mayhem/mayhem-firmware/releases) [![GitHub Releases](https://img.shields.io/github/downloads/portapack-mayhem/mayhem-firmware/latest/total)](https://github.com/portapack-mayhem/mayhem-firmware/releases/latest) [![Docker Hub Pulls](https://img.shields.io/docker/pulls/eried/portapack.svg)](https://hub.docker.com/r/eried/portapack) [![Discord Chat](https://dcbadge.vercel.app/api/server/tuwVMv3?style=flat)](https://discord.gg/tuwVMv3)
This is a fork of the [Havoc](https://github.com/furrtek/portapack-havoc/) firmware, which itself was a fork of the [PortaPack](https://github.com/sharebrained/portapack-hackrf) firmware, an add-on for the [HackRF](http://greatscottgadgets.com/hackrf/). A fork is a derivate, in this case one that has extra features and fixes when compared to the older versions.
[<img src="https://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://s.click.aliexpress.com/e/_DmU7GQX) (clone) with a custom [3d printed case](https://github.com/portapack-mayhem/mayhem-firmware/wiki/H2-Enclosure)*
# What is this?
If you are new to *HackRF+PortaPack+Mayhem*, check this video:
If you are new to *HackRF+PortaPack+Mayhem*, check these:
[![What is?](https://img.youtube.com/vi/alrFbY5vxt4/0.jpg)](https://www.youtube.com/watch?v=alrFbY5vxt4)
[<img alt="HackRF 101 : Everything You Need to Know to Get Started!" src="https://img.youtube.com/vi/xGR_PMD9LeU/maxresdefault.jpg" width="512">](https://grabify.link/C0J6ZR)
[<img alt="Beginner's Guide To The HackRF & Portapak With Mayhem" src="https://img.youtube.com/vi/H-bqdWfbhpg/maxresdefault.jpg" width="254">](https://grabify.link/5MU2VH) [<img alt="What is the HackRF One Portapack H2+?" src="https://img.youtube.com/vi/alrFbY5vxt4/maxresdefault.jpg" width="254">](https://grabify.link/9UZGEW)
# 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?
:heavy_check_mark: A recommended one is this [PortaPack H2](https://s.click.aliexpress.com/e/_DmU7GQX) pack, that includes everything you need. Sadly, the people making the H2 never made the updated schematics available (against the terms of the license).
: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/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: 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.
: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:
![image](https://user-images.githubusercontent.com/1091420/214579017-9ad970b9-0917-48f6-a550-588226d3f89b.png)
: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:
![image](https://user-images.githubusercontent.com/1091420/214581333-424900ee-26f8-4e96-be2f-69d8dc995ba9.png)
## Where is the latest version?
The current **stable release** is on the [![GitHub release (latest by date)](https://img.shields.io/github/v/release/eried/portapack-mayhem?label=Releases&style=social)](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 [![GitHub release (latest by date)](https://img.shields.io/github/v/release/portapack-mayhem/mayhem-firmware?label=Releases&style=social)](https://github.com/portapack-mayhem/mayhem-firmware/releases/latest) page. Follow the instructions you can find in the release description. The **latest (nightly) release** can be found [here](https://github.com/portapack-mayhem/mayhem-firmware/releases/).
## How can I collaborate
You can write [documentation](https://github.com/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).
[![Contributors](https://contrib.rocks/image?repo=eried/portapack-mayhem)](https://github.com/eried/portapack-mayhem/graphs/contributors)
[![Contributors](https://contrib.rocks/image?repo=portapack-mayhem/mayhem-firmware)](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).
## 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).
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>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/portapack-mayhem/mayhem-firmware/issues/new/choose) or [choosing](https://github.com/portapack-mayhem/mayhem-firmware/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>
Promote your bounty over our Discord by clicking the chat badge on [top](#portapack-mayhem).
<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?
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).
+16 -15
View File
@@ -10,32 +10,33 @@ VOLUME /havoc
WORKDIR /havoc/firmware
# Fetch dependencies from APT
RUN apt-get update && \
apt-get install -y git tar wget dfu-util cmake python3 ccache bzip2 liblz4-tool curl && \
apt-get -qy autoremove
RUN apt-get update \
&& apt-get install -y git tar wget dfu-util cmake python3 ccache bzip2 liblz4-tool curl ninja-build \
&& apt-get -qy autoremove \
&& rm -rf /var/lib/apt/lists/*
#Install current pip from PyPa
RUN curl https://bootstrap.pypa.io/pip/3.4/get-pip.py -o get-pip.py && \
python3 get-pip.py
#Fetch additional dependencies from Python 3.x pip
RUN pip install pyyaml
RUN ln -s /usr/bin/python3 /usr/bin/python && \
ln -s /usr/bin/pip3 /usr/bin/pip
RUN pip install pyyaml \
&& ln -s /usr/bin/python3 /usr/bin/python \
&& ln -s /usr/bin/pip3 /usr/bin/pip
ENV LANG C.UTF-8
ENV LC_ALL C.UTF-8
# Grab the GNU ARM toolchain from arm.com
# Then extract contents to /opt/build/armbin/
RUN mkdir /opt/build && cd /opt/build && \
wget -O gcc-arm-none-eabi $ARMBINURL && \
mkdir armbin && \
tar --strip=1 -xjvf gcc-arm-none-eabi -C armbin
RUN mkdir /opt/build \
&& cd /opt/build \
&& wget -O gcc-arm-none-eabi $ARMBINURL \
&& mkdir armbin \
&& tar --strip=1 -xjvf gcc-arm-none-eabi -C armbin
# Configure CCACHE
RUN mkdir ~/bin && cd ~/bin && \
for tool in gcc g++ cpp c++;do ln -s $(which ccache) arm-none-eabi-$tool;done
ADD firmware/tools/docker-entrypoint.sh /usr/local/bin/entrypoint.sh
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
CMD cd .. && cd build && \
cmake .. && make firmware
# replace make with ninja temporarily while building your image if you prefer to use that by default
CMD ["make"]
+1 -5
View File
@@ -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 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 && \
cmake .. && make firmware
cmake .. && make ppfw
+16 -15
View File
@@ -10,32 +10,33 @@ VOLUME /havoc
WORKDIR /havoc/firmware
# Fetch dependencies from APT
RUN apt-get update && \
apt-get install -y git tar wget dfu-util cmake python3 ccache bzip2 liblz4-tool curl && \
apt-get -qy autoremove
RUN apt-get update \
&& apt-get install -y git tar wget dfu-util cmake python3 ccache bzip2 liblz4-tool curl ninja-build \
&& apt-get -qy autoremove \
&& rm -rf /var/lib/apt/lists/*
#Install current pip from PyPa
RUN curl https://bootstrap.pypa.io/pip/3.4/get-pip.py -o get-pip.py && \
python3 get-pip.py
#Fetch additional dependencies from Python 3.x pip
RUN pip install pyyaml
RUN ln -s /usr/bin/python3 /usr/bin/python && \
ln -s /usr/bin/pip3 /usr/bin/pip
RUN pip install pyyaml \
&& ln -s /usr/bin/python3 /usr/bin/python \
&& ln -s /usr/bin/pip3 /usr/bin/pip
ENV LANG C.UTF-8
ENV LC_ALL C.UTF-8
# Grab the GNU ARM toolchain from arm.com
# Then extract contents to /opt/build/armbin/
RUN mkdir /opt/build && cd /opt/build && \
wget -O gcc-arm-none-eabi $ARMBINURL && \
mkdir armbin && \
tar --strip=1 -xjvf gcc-arm-none-eabi -C armbin
RUN mkdir /opt/build \
&& cd /opt/build \
&& wget -O gcc-arm-none-eabi $ARMBINURL \
&& mkdir armbin \
&& tar --strip=1 -xjvf gcc-arm-none-eabi -C armbin
# Configure CCACHE
RUN mkdir ~/bin && cd ~/bin && \
for tool in gcc g++ cpp c++;do ln -s $(which ccache) arm-none-eabi-$tool;done
ADD firmware/tools/docker-entrypoint.sh /usr/local/bin/entrypoint.sh
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
CMD cd .. && cd build && \
cmake .. && make firmware
# replace make with ninja temporarily while building your image if you prefer to use that by default
CMD ["make"]
+2 -2
View File
@@ -1,6 +1,6 @@
<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>
<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>
+51 -1
View File
@@ -18,6 +18,8 @@
# Boston, MA 02110-1301, USA.
#
cmake_minimum_required(VERSION 3.5)
project(firmware)
set(BASEBAND ${PROJECT_SOURCE_DIR}/baseband)
@@ -33,6 +35,18 @@ set(LZ4 lz4)
set(FIRMWARE_NAME portapack-h1_h2-mayhem)
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(baseband)
@@ -48,12 +62,22 @@ add_custom_command(
)
add_custom_target(
firmware ALL
firmware
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(
program
COMMAND ${PROJECT_SOURCE_DIR}/tools/enter_mode.sh hackrf
COMMAND dfu-util --device 1fc9:000c --download ${HACKRF_FIRMWARE_DFU_IMAGE} || (exit 0) # We need to add it for dfu-utils v.011 , (in v.09 it is not necessary)
COMMAND sleep 3s
COMMAND hackrf_spiflash -i -R -w ${FIRMWARE_FILENAME}
@@ -67,6 +91,21 @@ add_custom_target(
DEPENDS program
)
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 cd firmware_tar && tar -cvaf ../${PPFW_FILENAME} *
DEPENDS firmware ${FIRMWARE_FILENAME}
# 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.
add_custom_command(
OUTPUT ${FIRMWARE_NAME}-${VERSION}.tar.bz2 ${FIRMWARE_NAME}-${VERSION}.zip
@@ -86,7 +125,18 @@ add_custom_command(
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(
release
DEPENDS MD5SUMS SHA256SUMS
)
+49 -19
View File
@@ -44,7 +44,7 @@ if(cpp20_supported)
else()
set(USE_CPPOPT "-std=c++17")
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
set(USE_LINK_GC yes)
@@ -52,7 +52,7 @@ set(USE_LINK_GC yes)
# Linker extra options here.
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)
# If enabled, this option allows to compile the application in THUMB mode.
@@ -113,12 +113,23 @@ set(CSRC
${BOARDSRC}
${FATFSSRC}
firmware_info.c
usb_serial_cdc.c
usb_serial_descriptor.c
usb_serial_endpoints.c
usb_serial_device_to_host.c
${HACKRF_PATH}/firmware/common/usb.c
${HACKRF_PATH}/firmware/common/usb_queue.c
${HACKRF_PATH}/firmware/hackrf_usb/usb_device.c
${HACKRF_PATH}/firmware/common/usb_request.c
${HACKRF_PATH}/firmware/common/usb_standard_request.c
${CHIBIOS}/os/various/chprintf.c
)
# C++ sources that can be compiled in ARM or THUMB mode depending on the global
# setting.
set(CPPSRC
main.cpp
shell.cpp
${COMMON}/acars_packet.cpp
${COMMON}/adsb.cpp
${COMMON}/adsb_frame.cpp
@@ -162,6 +173,7 @@ set(CPPSRC
${COMMON}/ui_painter.cpp
${COMMON}/ui_text.cpp
${COMMON}/ui_widget.cpp
${COMMON}/ui_language.cpp
${COMMON}/utility.cpp
${COMMON}/wm8731.cpp
${COMMON}/performance_counter.cpp
@@ -178,6 +190,7 @@ set(CPPSRC
event_m0.cpp
file_reader.cpp
file.cpp
file_path.cpp
freqman_db.cpp
freqman.cpp
io_convert.cpp
@@ -190,6 +203,12 @@ set(CPPSRC
log_file.cpp
metadata_file.cpp
portapack.cpp
usb_serial_shell.cpp
usb_serial_shell_filesystem.cpp
usb_serial_event.cpp
usb_serial_thread.cpp
usb_serial.cpp
usb_serial_host_to_device.cpp
qrcodegen.cpp
radio.cpp
receiver_model.cpp
@@ -243,12 +262,14 @@ set(CPPSRC
apps/acars_app.cpp
apps/ais_app.cpp
apps/analog_audio_app.cpp
apps/analog_tv_app.cpp
# apps/analog_tv_app.cpp
apps/ble_comm_app.cpp
apps/ble_rx_app.cpp
apps/ble_tx_app.cpp
apps/capture_app.cpp
apps/ert_app.cpp
apps/gps_sim_app.cpp
apps/lge_app.cpp
apps/lge_app.cpp
# apps/gps_sim_app.cpp
# apps/lge_app.cpp
apps/pocsag_app.cpp
# apps/replay_app.cpp
apps/soundboard_app.cpp
@@ -261,29 +282,30 @@ set(CPPSRC
apps/ui_aprs_tx.cpp
apps/ui_bht_tx.cpp
apps/ui_btle_rx.cpp
apps/ui_coasterp.cpp
# apps/ui_coasterp.cpp
apps/ui_debug.cpp
apps/ui_dfu_menu.cpp
apps/ui_encoders.cpp
apps/ui_fileman.cpp
apps/ui_flash_utility.cpp
apps/ui_freqman.cpp
apps/ui_fsk_rx.cpp
apps/ui_iq_trim.cpp
apps/ui_jammer.cpp
# apps/ui_jammer.cpp
# apps/ui_keyfob.cpp
apps/ui_lcr.cpp
apps/ui_level.cpp
# apps/ui_lcr.cpp
apps/ui_level.cpp
apps/ui_looking_glass_app.cpp
apps/ui_mictx.cpp
apps/ui_modemsetup.cpp
apps/ui_morse.cpp
apps/ui_nrf_rx.cpp
# apps/ui_nrf_rx.cpp
# apps/ui_nuoptix.cpp
apps/ui_playlist.cpp
apps/ui_pocsag_tx.cpp
apps/ui_rds.cpp
apps/ui_recon_settings.cpp
apps/ui_recon.cpp
apps/ui_recon.cpp
apps/ui_remote.cpp
apps/ui_scanner.cpp
apps/ui_sd_over_usb.cpp
@@ -292,24 +314,26 @@ set(CPPSRC
apps/ui_settings.cpp
apps/ui_siggen.cpp
apps/ui_sonde.cpp
apps/ui_spectrum_painter_image.cpp
apps/ui_spectrum_painter_text.cpp
apps/ui_spectrum_painter.cpp
# apps/ui_spectrum_painter_image.cpp
# apps/ui_spectrum_painter_text.cpp
# apps/ui_spectrum_painter.cpp
apps/ui_ss_viewer.cpp
apps/ui_sstvtx.cpp
apps/ui_subghzd.cpp
# apps/ui_test.cpp
apps/ui_text_editor.cpp
apps/ui_tone_search.cpp
apps/ui_touch_calibration.cpp
apps/ui_touchtunes.cpp
apps/ui_view_wav.cpp
apps/ui_weatherstation.cpp
apps/ui_whipcalc.cpp
protocols/aprs.cpp
protocols/ax25.cpp
protocols/bht.cpp
protocols/dcs.cpp
protocols/encoders.cpp
protocols/lcr.cpp
# protocols/lcr.cpp
protocols/modems.cpp
protocols/rds.cpp
# ui_handwrite.cpp
@@ -323,10 +347,13 @@ set(CPPSRC
${CPLD_20170522_DATA_CPP}
${HACKRF_CPLD_DATA_CPP}
ui_external_items_menu_loader.cpp
${EXTCPPSRC}
view_factory_base.cpp
)
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.
# NOTE: Mixing ARM and THUMB mode enables the -mthumb-interwork compiler
# option that results in lower performance and larger code size.
@@ -357,6 +384,9 @@ set(INCDIR ${CMAKE_CURRENT_BINARY_DIR} ${COMMON} ${PORTINC} ${KERNINC} ${TESTINC
${HALINC} ${PLATFORMINC} ${BOARDINC}
${FATFSINC}
${CHIBIOS}/os/various
${HACKRF_PATH}/firmware/libopencm3/include
${HACKRF_PATH}/firmware/common
${HACKRF_PATH}/firmware
ui
hw
apps
@@ -400,7 +430,7 @@ set(CPPWARN "-Wall -Wextra -Wno-psabi")
# List all default C defines here, like -D_DEBUG=1
# TODO: Switch -DCRT0_INIT_DATA depending on load from RAM or SPIFI?
# NOTE: _RANDOM_TCC to kill a GCC 4.9.3 error with std::max argument types
set(DDEFS "-DLPC43XX -DLPC43XX_M0 -D__NEWLIB__ -DHACKRF_ONE -DTOOLCHAIN_GCC -DTOOLCHAIN_GCC_ARM -D_RANDOM_TCC=0 -D'VERSION_STRING=\"${VERSION}\"' -DVERSION_MD5=${VERSION_MD5}")
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
set(DADEFS)
+6 -8
View File
@@ -30,6 +30,7 @@
#include "portapack.hpp"
#include "portapack_persistent_memory.hpp"
#include "utility.hpp"
#include "file_path.hpp"
#include <algorithm>
#include <cstring>
@@ -40,7 +41,7 @@ using namespace portapack;
namespace {
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
@@ -156,6 +157,7 @@ bool save_settings(std::string_view store_name, const SettingBindings& bindings)
File f;
auto path = get_settings_path(std::string{store_name});
ensure_directory(settings_dir);
auto error = f.create(path);
if (error)
return false;
@@ -173,16 +175,12 @@ void copy_to_radio_model(const AppSettings& settings) {
// Specifically 'modulation' which requires a running baseband.
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);
}
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.set_configuration_without_update(
static_cast<ReceiverModel::Mode>(settings.modulation),
@@ -249,7 +247,7 @@ SettingsManager::SettingsManager(
settings_.options = options;
// Pre-alloc enough for app settings and additional settings.
additional_settings.reserve(17 + additional_settings.size());
additional_settings.reserve(COMMON_APP_SETTINGS_COUNT + additional_settings.size());
bindings_ = std::move(additional_settings);
// Settings should always be loaded because apps now rely
+4 -4
View File
@@ -39,6 +39,8 @@
// Bring in the string_view literal.
using std::literals::operator""sv;
#define COMMON_APP_SETTINGS_COUNT 19
/* Represents a named setting bound to a variable instance. */
/* Using void* instead of std::variant, because variant is a pain to dispatch over. */
class BoundSetting {
@@ -108,6 +110,7 @@ bool save_settings(std::string_view store_name, const SettingBindings& bindings)
namespace app_settings {
enum class Mode : uint8_t {
NO_RF = 0x00,
RX = 0x01,
TX = 0x02,
RX_TX = 0x03, // Both TX/RX
@@ -115,9 +118,6 @@ enum class Mode : uint8_t {
enum class Options {
None = 0x0000,
/* Don't use target frequency from app settings. */
UseGlobalTargetFrequency = 0x0001,
};
/* NB: See RX/TX model headers for default values. */
@@ -140,8 +140,8 @@ struct AppSettings {
uint8_t nbfm_config_index = 0;
uint8_t wfm_config_index = 0;
uint8_t squelch = 80;
uint8_t volume;
// NOTE: update COMMON_APP_SETTINGS_COUNT when adding to this
};
/* Copies common values to the receiver/transmitter models. */
+2 -1
View File
@@ -24,6 +24,7 @@
#include "baseband_api.hpp"
#include "portapack_persistent_memory.hpp"
#include "file_path.hpp"
using namespace portapack;
using namespace acars;
@@ -77,7 +78,7 @@ ACARSAppView::ACARSAppView(NavigationView& nav)
logger = std::make_unique<ACARSLogger>();
if (logger)
logger->append(LOG_ROOT_DIR "/ACARS.TXT");
logger->append(logs_dir / u"ACARS.TXT");
}
ACARSAppView::~ACARSAppView() {
+1 -1
View File
@@ -35,7 +35,7 @@
class ACARSLogger {
public:
Optional<File::Error> append(const std::string& filename) {
Optional<File::Error> append(const std::filesystem::path& filename) {
return log_file.append(filename);
}
+20 -4
View File
@@ -20,9 +20,11 @@
*/
#include "ais_app.hpp"
#include "audio.hpp"
#include "string_format.hpp"
#include "database.hpp"
#include "file_path.hpp"
#include "baseband_api.hpp"
@@ -31,6 +33,8 @@ using namespace portapack;
#include <algorithm>
namespace pmem = portapack::persistent_memory;
namespace ais {
namespace format {
@@ -64,9 +68,9 @@ static std::string mmsi(
static std::string mid(
const ais::MMSI& mmsi) {
std::database db;
database db;
std::string mid_code = "";
std::database::MidDBRecord mid_record = {};
database::MidDBRecord mid_record = {};
int return_code = 0;
// 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);
if (pmem::beep_on_packets()) {
baseband::request_audio_beep(1000, 24000, 60);
}
}
void AISRecentEntry::update(const ais::Packet& packet) {
@@ -293,6 +301,7 @@ AISRecentEntryDetailView::AISRecentEntryDetailView(NavigationView& nav) {
ais::format::text(entry_.name),
0,
GeoPos::alt_unit::METERS,
GeoPos::spd_unit::NONE,
ais::format::latlon_float(entry_.last_position.latitude.normalized()),
ais::format::latlon_float(entry_.last_position.longitude.normalized()),
entry_.last_position.true_heading,
@@ -315,7 +324,7 @@ AISRecentEntryDetailView& AISRecentEntryDetailView::operator=(const AISRecentEnt
void AISRecentEntryDetailView::update_position() {
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() {
@@ -375,6 +384,7 @@ AISAppView::AISAppView(NavigationView& nav)
&field_lna,
&field_vga,
&rssi,
&field_volume,
&channel,
&recent_entries_view,
&recent_entry_detail_view,
@@ -398,11 +408,17 @@ AISAppView::AISAppView(NavigationView& nav)
logger = std::make_unique<AISLogger>();
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() {
audio::output::stop();
receiver_model.disable();
baseband::shutdown();
}
+3
View File
@@ -209,6 +209,9 @@ class AISAppView : public View {
{21 * 8, 0, 6 * 8, 4},
};
AudioVolumeField field_volume{
{28 * 8, 0 * 16}};
Channel channel{
{21 * 8, 5, 6 * 8, 4},
};
+28 -4
View File
@@ -30,6 +30,7 @@
#include "string_format.hpp"
#include "ui_freqman.hpp"
#include "utility.hpp"
#include "radio.hpp"
using namespace portapack;
using namespace tonekey;
@@ -112,10 +113,14 @@ SPECOptionsView::SPECOptionsView(
: View{parent_rect} {
set_style(style);
add_children({&label_config,
&options_config,
&text_speed,
&field_speed});
add_children({
&label_config,
&options_config,
&text_speed,
&field_speed,
&text_rx_cal,
&field_rx_iq_phase_cal,
});
options_config.set_selected_index(view->get_spec_bw_index());
options_config.on_change = [this, view](size_t n, OptionsField::value_t bw) {
@@ -126,6 +131,14 @@ SPECOptionsView::SPECOptionsView(
field_speed.on_change = [this, view](int32_t 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 *******************************************************/
@@ -195,7 +208,9 @@ AnalogAudioView::AnalogAudioView(
NavigationView& nav,
ReceiverModel::settings_t override)
: AnalogAudioView(nav) {
// Settings to override when launched from another app (versus from AppSettings .ini file)
// TODO: Which other settings make sense to override?
field_frequency.set_value(override.frequency_app_override);
on_frequency_step_changed(override.frequency_step);
options_modulation.set_by_value(toUType(override.mode));
}
@@ -213,6 +228,15 @@ void AnalogAudioView::set_spec_bw(size_t index, uint32_t bw) {
receiver_model.set_baseband_bandwidth(bw / 2);
}
uint8_t AnalogAudioView::get_spec_iq_phase_calibration_value() { // define accessor functions inside AnalogAudioView to read & write real iq_phase_calibration_value
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() {
return spec_trigger;
}
+19 -2
View File
@@ -133,6 +133,16 @@ class SPECOptionsView : public View {
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 {
@@ -152,14 +162,21 @@ class AnalogAudioView : public View {
uint16_t get_spec_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:
static constexpr ui::Dim header_height = 3 * 16;
NavigationView& nav_;
RxRadioState radio_state_{};
uint8_t iq_phase_calibration_value{15}; // initial default RX IQ phase calibration value , used for both max2837 & max2839
app_settings::SettingsManager settings_{
"rx_audio", app_settings::Mode::RX,
app_settings::Options::UseGlobalTargetFrequency};
"rx_audio",
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 nbfm_view_rect{0 * 8, 1 * 16, 18 * 8, 1 * 16};
+332
View File
@@ -0,0 +1,332 @@
/*
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2017 Furrtek
* Copyright (C) 2023 TJ Baginski
*
* 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 "ble_comm_app.hpp"
#include "ui_modemsetup.hpp"
#include "modems.hpp"
#include "audio.hpp"
#include "rtc_time.hpp"
#include "baseband_api.hpp"
#include "string_format.hpp"
#include "portapack_persistent_memory.hpp"
#include "ui_text.hpp"
#include "file_path.hpp"
using namespace portapack;
using namespace modems;
void BLECommLogger::log_raw_data(const std::string& data) {
log_file.write_entry(data);
}
namespace ui {
static std::uint64_t get_freq_by_channel_number(uint8_t channel_number) {
uint64_t freq_hz;
switch (channel_number) {
case 37:
freq_hz = 2'402'000'000ull;
break;
case 38:
freq_hz = 2'426'000'000ull;
break;
case 39:
freq_hz = 2'480'000'000ull;
break;
case 0 ... 10:
freq_hz = 2'404'000'000ull + channel_number * 2'000'000ull;
break;
case 11 ... 36:
freq_hz = 2'428'000'000ull + (channel_number - 11) * 2'000'000ull;
break;
default:
freq_hz = UINT64_MAX;
}
return freq_hz;
}
void BLECommView::focus() {
options_channel.focus();
}
BLECommView::BLECommView(NavigationView& nav)
: nav_{nav} {
add_children({&rssi,
&channel,
&field_rf_amp,
&field_lna,
&field_vga,
&options_channel,
&field_frequency,
&label_send_adv,
&button_send_adv,
&check_log,
&label_packets_sent,
&text_packets_sent,
&console});
field_frequency.set_step(0);
button_send_adv.on_select = [this](ImageButton&) {
this->toggle();
};
check_log.set_value(logging);
check_log.on_select = [this](Checkbox&, bool v) {
str_log = "";
logging = v;
if (logger && logging)
logger->append(logs_dir.string() + "/BLELOG_" + to_string_timestamp(rtc_time::now()) + ".TXT");
};
options_channel.on_change = [this](size_t, int32_t i) {
// If we selected Auto don't do anything and Auto will handle changing.
if (i == 40) {
auto_channel = true;
return;
} else {
auto_channel = false;
}
field_frequency.set_value(get_freq_by_channel_number(i));
channel_number_rx = i;
channel_number_tx = i;
};
options_channel.set_selected_index(3, true);
logger = std::make_unique<BLECommLogger>();
// Generate new random Mac Address upon each new startup.
generateRandomMacAddress(randomMac);
// Setup Initial Advertise Packet.
advertisePacket = build_adv_packet();
}
void BLECommView::set_parent_rect(const 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};
console.set_parent_rect(content_rect);
}
BLECommView::~BLECommView() {
receiver_model.disable();
transmitter_model.disable();
baseband::shutdown();
}
bool BLECommView::in_tx_mode() const {
return (bool)is_running_tx;
}
void BLECommView::toggle() {
if (in_tx_mode()) {
sendAdvertisement(false);
} else {
sendAdvertisement(true);
}
}
BLETxPacket BLECommView::build_adv_packet() {
BLETxPacket bleTxPacket;
memset(&bleTxPacket, 0, sizeof(BLETxPacket));
std::string dataString = "11094861636b524620506f7274617061636b";
strncpy(bleTxPacket.macAddress, randomMac, 12);
strncpy(bleTxPacket.advertisementData, dataString.c_str(), dataString.length());
// Duty cycle of 40% per 100ms advertisment periods.
strncpy(bleTxPacket.packetCount, "80", 3);
bleTxPacket.packet_count = 80;
bleTxPacket.packetType = PKT_TYPE_DISCOVERY;
return bleTxPacket;
}
void BLECommView::sendAdvertisement(bool enable) {
if (enable) {
startTx(advertisePacket);
is_adv = true;
} else {
is_adv = false;
stopTx();
}
}
void BLECommView::startTx(BLETxPacket packetToSend) {
int randomChannel = channel_number_tx;
if (auto_channel) {
int min = 37;
int max = 39;
randomChannel = min + std::rand() % (max - min + 1);
field_frequency.set_value(get_freq_by_channel_number(randomChannel));
}
if (!in_tx_mode()) {
switch_rx_tx(false);
currentPacket = packetToSend;
packet_counter = currentPacket.packet_count;
button_send_adv.set_bitmap(&bitmap_stop);
baseband::set_btletx(randomChannel, currentPacket.macAddress, currentPacket.advertisementData, currentPacket.packetType);
transmitter_model.set_tx_gain(47);
transmitter_model.enable();
is_running_tx = true;
} else {
baseband::set_btletx(randomChannel, currentPacket.macAddress, currentPacket.advertisementData, currentPacket.packetType);
}
if ((packet_counter % 10) == 0) {
text_packets_sent.set(to_string_dec_uint(packet_counter));
}
is_sending = true;
packet_counter--;
}
void BLECommView::stopTx() {
button_send_adv.set_bitmap(&bitmap_play);
text_packets_sent.set(to_string_dec_uint(packet_counter));
switch_rx_tx(true);
baseband::set_btlerx(channel_number_rx);
receiver_model.enable();
is_running_tx = false;
}
void BLECommView::switch_rx_tx(bool inRxMode) {
if (inRxMode) {
// Start Rx
transmitter_model.disable();
baseband::shutdown();
baseband::run_image(portapack::spi_flash::image_tag_btle_rx);
} else {
// Start Tx
receiver_model.disable();
baseband::shutdown();
baseband::run_image(portapack::spi_flash::image_tag_btle_tx);
}
}
void BLECommView::on_data(BlePacketData* packet) {
parse_received_packet(packet, (ADV_PDU_TYPE)packet->type);
}
// called each 1/60th of second, so 6 = 100ms
void BLECommView::on_timer() {
// Send advertise burst only once every 100ms
if (++timer_counter == timer_period) {
timer_counter = 0;
if (!is_adv) {
sendAdvertisement(true);
}
}
}
void BLECommView::on_tx_progress(const bool done) {
if (done) {
if (in_tx_mode()) {
is_sending = false;
if (packet_counter == 0) {
if (is_adv) {
sendAdvertisement(false);
} else {
stopTx();
}
} else {
startTx(currentPacket);
}
}
}
}
void BLECommView::parse_received_packet(const BlePacketData* packet, ADV_PDU_TYPE pdu_type) {
std::string data_string;
int i;
for (i = 0; i < packet->dataLen; i++) {
data_string += to_string_hex(packet->data[i], 2);
}
receivedPacket.dbValue = packet->max_dB;
receivedPacket.timestamp = to_string_timestamp(rtc_time::now());
receivedPacket.dataString = data_string;
receivedPacket.packetData.type = packet->type;
receivedPacket.packetData.size = packet->size;
receivedPacket.packetData.dataLen = packet->dataLen;
receivedPacket.packetData.macAddress[0] = packet->macAddress[0];
receivedPacket.packetData.macAddress[1] = packet->macAddress[1];
receivedPacket.packetData.macAddress[2] = packet->macAddress[2];
receivedPacket.packetData.macAddress[3] = packet->macAddress[3];
receivedPacket.packetData.macAddress[4] = packet->macAddress[4];
receivedPacket.packetData.macAddress[5] = packet->macAddress[5];
receivedPacket.numHits++;
for (int i = 0; i < packet->dataLen; i++) {
receivedPacket.packetData.data[i] = packet->data[i];
}
if (pdu_type == SCAN_REQ || pdu_type == CONNECT_REQ) {
ADV_PDU_PAYLOAD_TYPE_1_3* directed_mac_data = (ADV_PDU_PAYLOAD_TYPE_1_3*)packet->data;
std::reverse(directed_mac_data->A1, directed_mac_data->A1 + 6);
console.clear(true);
std::string str_console = "";
std::string pduTypeStr = "";
if (pdu_type == SCAN_REQ) {
pduTypeStr += "SCAN_REQ";
} else if (pdu_type == CONNECT_REQ) {
pduTypeStr += "CONNECT_REQ";
}
str_console += "PACKET TYPE:" + pduTypeStr + "\n";
str_console += "MY MAC:" + to_string_formatted_mac_address(randomMac) + "\n";
str_console += "SCAN MAC:" + to_string_mac_address(directed_mac_data->A1, 6, false) + "\n";
console.write(str_console);
}
}
} /* namespace ui */
+203
View File
@@ -0,0 +1,203 @@
/*
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2017 Furrtek
* Copyright (C) 2023 TJ Baginski
*
* 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 __BLE_COMM_APP_H__
#define __BLE_COMM_APP_H__
#include "ble_rx_app.hpp"
#include "ble_tx_app.hpp"
#include "ui.hpp"
#include "ui_navigation.hpp"
#include "ui_receiver.hpp"
#include "ui_transmitter.hpp"
#include "ui_freq_field.hpp"
#include "ui_record_view.hpp"
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "log_file.hpp"
#include "utility.hpp"
#include "recent_entries.hpp"
class BLECommLogger {
public:
Optional<File::Error> append(const std::filesystem::path& filename) {
return log_file.append(filename);
}
void log_raw_data(const std::string& data);
private:
LogFile log_file{};
};
namespace ui {
class BLECommView : public View {
public:
BLECommView(NavigationView& nav);
~BLECommView();
void set_parent_rect(const Rect new_parent_rect) override;
void paint(Painter&) override{};
void focus() override;
std::string title() const override { return "BLE Comm"; };
private:
BLETxPacket build_adv_packet();
void switch_rx_tx(bool inRxMode);
void startTx(BLETxPacket packetToSend);
void stopTx();
void toggle();
bool in_tx_mode() const;
void on_timer();
void on_data(BlePacketData* packetData);
void on_tx_progress(const bool done);
void parse_received_packet(const BlePacketData* packet, ADV_PDU_TYPE pdu_type);
void sendAdvertisement(bool enable);
NavigationView& nav_;
RxRadioState radio_state_rx_{
2'402'000'000 /* frequency */,
4'000'000 /* bandwidth */,
4'000'000 /* sampling rate */,
ReceiverModel::Mode::WidebandFMAudio};
TxRadioState radio_state_tx_{
2'402'000'000 /* frequency */,
4'000'000 /* bandwidth */,
4'000'000 /* sampling rate */
};
app_settings::SettingsManager settings_{
"BLE Comm Tx", app_settings::Mode::RX_TX};
uint8_t console_color{0};
uint32_t prev_value{0};
uint8_t channel_number_tx = 37;
uint8_t channel_number_rx = 37;
bool auto_channel = false;
char randomMac[13] = "010203040506";
bool is_running_tx = false;
bool is_sending = false;
bool is_adv = false;
int16_t timer_period{6}; // Delay each packet by 16ms.
int16_t timer_counter = 0;
int16_t timer_rx_counter = 0;
int16_t timer_rx_period{12}; // Poll Rx for at least 200ms. (TBD)
uint32_t packet_counter{0};
BLETxPacket advertisePacket{};
BLETxPacket currentPacket{};
BleRecentEntry receivedPacket{};
static constexpr auto header_height = 5 * 16;
OptionsField options_channel{
{0 * 8, 0 * 8},
5,
{{"Ch.37 ", 37},
{"Ch.38", 38},
{"Ch.39", 39},
{"Auto", 40}}};
RxFrequencyField field_frequency{
{6 * 8, 0 * 16},
nav_};
RFAmpField field_rf_amp{
{16 * 8, 0 * 16}};
LNAGainField field_lna{
{18 * 8, 0 * 16}};
VGAGainField field_vga{
{21 * 8, 0 * 16}};
RSSI rssi{
{24 * 8, 0, 6 * 8, 4}};
Channel channel{
{24 * 8, 5, 6 * 8, 4}};
Labels label_send_adv{
{{0 * 8, 2 * 8}, "Send Advertisement:", Color::light_grey()}};
ImageButton button_send_adv{
{21 * 8, 1 * 16, 10 * 8, 2 * 16},
&bitmap_play,
Color::green(),
Color::black()};
Checkbox check_log{
{24 * 8, 2 * 8},
3,
"Log",
true};
Labels label_packets_sent{
{{0 * 8, 4 * 8}, "Packets Left:", Color::light_grey()}};
Text text_packets_sent{
{13 * 8, 2 * 16, 12 * 8, 16},
"-"};
Console console{
{0, 4 * 16, 240, 240}};
std::string str_log{""};
bool logging{false};
std::unique_ptr<BLECommLogger> logger{};
MessageHandlerRegistration message_handler_packet{
Message::ID::BlePacket,
[this](Message* const p) {
const auto message = static_cast<const BLEPacketMessage*>(p);
this->on_data(message->packet);
}};
MessageHandlerRegistration message_handler_tx_progress{
Message::ID::TXProgress,
[this](const Message* const p) {
const auto message = *reinterpret_cast<const TXProgressMessage*>(p);
this->on_tx_progress(message.done);
}};
MessageHandlerRegistration message_handler_frame_sync{
Message::ID::DisplayFrameSync,
[this](const Message* const) {
this->on_timer();
}};
};
} /* namespace ui */
#endif /*__UI_AFSK_RX_H__*/
+933
View File
@@ -0,0 +1,933 @@
/*
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2017 Furrtek
* Copyright (C) 2023 TJ Baginski
*
* 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 "ble_rx_app.hpp"
#include "ble_rx_app.hpp"
#include "ui_modemsetup.hpp"
#include "modems.hpp"
#include "audio.hpp"
#include "io_file.hpp"
#include "rtc_time.hpp"
#include "baseband_api.hpp"
#include "string_format.hpp"
#include "portapack_persistent_memory.hpp"
#include "ui_fileman.hpp"
#include "ui_textentry.hpp"
using namespace portapack;
using namespace modems;
namespace fs = std::filesystem;
void BLELogger::log_raw_data(const std::string& data) {
log_file.write_entry(data);
}
std::string pad_string_with_spaces(int snakes) {
std::string paddedStr(snakes, ' ');
return paddedStr;
}
uint64_t copy_mac_address_to_uint64(const uint8_t* macAddress) {
uint64_t result = 0;
// Copy each byte of the MAC address to the corresponding byte in the uint64_t.
for (int i = 0; i < 6; ++i) {
result |= static_cast<uint64_t>(macAddress[i]) << ((5 - i) * 8);
}
return result;
}
void reverse_byte_array(uint8_t* arr, int length) {
int start = 0;
int end = length - 1;
while (start < end) {
// Swap elements at start and end
uint8_t temp = arr[start];
arr[start] = arr[end];
arr[end] = temp;
// Move the indices towards the center
start++;
end--;
}
}
namespace ui {
std::string pdu_type_to_string(ADV_PDU_TYPE type) {
std::string pduTypeStr = "";
switch (type) {
case ADV_IND:
pduTypeStr += "ADV_IND";
break;
case ADV_DIRECT_IND:
pduTypeStr += "ADV_DIRECT_IND";
break;
case ADV_NONCONN_IND:
pduTypeStr += "ADV_NONCONN_IND";
break;
case SCAN_REQ:
pduTypeStr += "SCAN_REQ";
break;
case SCAN_RSP:
pduTypeStr += "SCAN_RSP";
break;
case CONNECT_REQ:
pduTypeStr += "CONNECT_REQ";
break;
case ADV_SCAN_IND:
pduTypeStr += "ADV_SCAN_IND";
break;
case RESERVED0:
case RESERVED1:
case RESERVED2:
case RESERVED3:
case RESERVED4:
case RESERVED5:
case RESERVED6:
case RESERVED7:
case RESERVED8:
pduTypeStr += "RESERVED";
break;
default:
pduTypeStr += "UNKNOWN";
break;
}
return pduTypeStr;
}
template <>
void RecentEntriesTable<BleRecentEntries>::draw(
const Entry& entry,
const Rect& target_rect,
Painter& painter,
const Style& style) {
std::string line{};
line.reserve(30);
if (!entry.nameString.empty() && entry.include_name) {
line = entry.nameString;
if (line.length() < 17) {
line += pad_string_with_spaces(17 - line.length());
} else {
line = truncate(line, 17);
}
} else {
line = to_string_mac_address(entry.packetData.macAddress, 6, false);
}
// Pushing single digit values down right justified.
std::string hitsStr = to_string_dec_int(entry.numHits);
int hitsDigits = hitsStr.length();
uint8_t hits_spacing = 8 - hitsDigits;
// Pushing single digit values down right justified.
std::string dbStr = to_string_dec_int(entry.dbValue);
int dbDigits = dbStr.length();
uint8_t db_spacing = 5 - dbDigits;
line += pad_string_with_spaces(hits_spacing) + hitsStr;
line += pad_string_with_spaces(db_spacing) + dbStr;
line.resize(target_rect.width() / 8, ' ');
painter.draw_string(target_rect.location(), style, line);
}
BleRecentEntryDetailView::BleRecentEntryDetailView(NavigationView& nav, const BleRecentEntry& entry)
: nav_{nav},
entry_{entry} {
add_children({&button_done,
&button_send,
&button_save,
&label_mac_address,
&text_mac_address,
&label_pdu_type,
&text_pdu_type,
&labels});
text_mac_address.set(to_string_mac_address(entry.packetData.macAddress, 6, false));
text_pdu_type.set(pdu_type_to_string(entry.pduType));
button_done.on_select = [&nav](const ui::Button&) {
nav.pop();
};
button_send.on_select = [this, &nav](const ui::Button&) {
auto packetToSend = build_packet();
nav.set_on_pop([packetToSend, &nav]() {
nav.replace<BLETxView>(packetToSend);
});
nav.pop();
};
button_save.on_select = [this, &nav](const ui::Button&) {
auto packetToSave = build_packet();
packetFileBuffer = "";
text_prompt(
nav,
packetFileBuffer,
64,
[this, packetToSave](std::string& buffer) {
on_save_file(buffer, packetToSave);
});
};
}
void BleRecentEntryDetailView::on_save_file(const std::string value, BLETxPacket packetToSave) {
ensure_directory(packet_save_path);
auto folder = packet_save_path.parent_path();
auto ext = packet_save_path.extension();
auto new_path = folder / value + ext;
saveFile(new_path, packetToSave);
}
bool BleRecentEntryDetailView::saveFile(const std::filesystem::path& path, BLETxPacket packetToSave) {
File f;
auto error = f.create(path);
if (error)
return false;
std::string macAddressStr = packetToSave.macAddress;
std::string advertisementDataStr = packetToSave.advertisementData;
std::string packetCountStr = packetToSave.packetCount;
std::string packetString = macAddressStr + ' ' + advertisementDataStr + ' ' + packetCountStr;
f.write(packetString.c_str(), packetString.length());
return true;
}
void BleRecentEntryDetailView::update_data() {
}
void BleRecentEntryDetailView::focus() {
button_done.focus();
}
Rect BleRecentEntryDetailView::draw_field(
Painter& painter,
const Rect& draw_rect,
const Style& style,
const std::string& label,
const std::string& value) {
const int label_length_max = 4;
painter.draw_string(Point{draw_rect.left(), draw_rect.top()}, style, label);
painter.draw_string(Point{draw_rect.left() + (label_length_max + 1) * 8, draw_rect.top()}, style, value);
return {draw_rect.left(), draw_rect.top() + draw_rect.height(), draw_rect.width(), draw_rect.height()};
}
void BleRecentEntryDetailView::paint(Painter& painter) {
View::paint(painter);
const auto s = style();
const auto rect = screen_rect();
auto field_rect = Rect{rect.left(), rect.top() + 64, rect.width(), 16};
uint8_t type[total_data_lines];
uint8_t length[total_data_lines];
uint8_t data[total_data_lines][40];
int currentByte = 0;
int currentPacket = 0;
int i = 0;
int j = 0;
int k = 0;
switch (entry_.pduType) {
case ADV_IND:
case ADV_NONCONN_IND:
case SCAN_RSP:
case ADV_SCAN_IND: {
ADV_PDU_PAYLOAD_TYPE_0_2_4_6* advertiseData = (ADV_PDU_PAYLOAD_TYPE_0_2_4_6*)entry_.packetData.data;
for (currentByte = 0; (currentByte < entry_.packetData.dataLen) && (currentPacket < total_data_lines);) {
length[currentPacket] = advertiseData->Data[currentByte++];
type[currentPacket] = advertiseData->Data[currentByte++];
// Subtract 1 because type is part of the length.
for (i = 0; i < length[currentPacket] - 1; i++) {
data[currentPacket][i] = advertiseData->Data[currentByte++];
}
currentPacket++;
}
for (i = 0; i < currentPacket; i++) {
uint8_t number_data_lines = ceil((float)(length[i] - 1) / 10.0);
uint8_t current_line = 0;
std::array<std::string, total_data_lines> data_strings{};
for (j = 0; (j < (number_data_lines * 10)) && (j < length[i] - 1); j++) {
if ((j / 10) != current_line) {
current_line++;
}
data_strings[current_line] += to_string_hex(data[i][j], 2);
}
// Read the type back to the total length.
field_rect = draw_field(painter, field_rect, s, to_string_hex(length[i]), to_string_hex(type[i]) + pad_string_with_spaces(3) + data_strings[0]);
if (number_data_lines > 1) {
for (k = 1; k < number_data_lines; k++) {
if (!data_strings[k].empty()) {
field_rect = draw_field(painter, field_rect, s, "", pad_string_with_spaces(5) + data_strings[k]);
}
}
}
}
} break;
case ADV_DIRECT_IND:
case SCAN_REQ: {
ADV_PDU_PAYLOAD_TYPE_1_3* directed_mac_data = (ADV_PDU_PAYLOAD_TYPE_1_3*)entry_.packetData.data;
uint8_t type = 0xFF;
field_rect = draw_field(painter, field_rect, s, to_string_hex(entry_.packetData.dataLen), to_string_hex(type) + pad_string_with_spaces(3) + to_string_mac_address(directed_mac_data->A1, 6, false));
} break;
case CONNECT_REQ:
default: {
uint8_t type = 0xFF;
// TODO: Display Connect Request Information. For right now just printing full hex data.
// This struct will eventually be used to break apart containing data of Connect Request.
// ADV_PDU_PAYLOAD_TYPE_5 * connect_req = (ADV_PDU_PAYLOAD_TYPE_5 *)entry_.packetData.data;
for (currentByte = 0; (currentByte < entry_.packetData.dataLen); currentByte++) {
data[0][currentByte] = entry_.packetData.data[currentByte];
}
uint8_t number_data_lines = ceil((float)entry_.packetData.dataLen / 10.0);
uint8_t current_line = 0;
std::array<std::string, total_data_lines> data_strings{};
for (j = 0; (j < (number_data_lines * 10)) && (j < entry_.packetData.dataLen); j++) {
if ((j / 10) != current_line) {
current_line++;
}
data_strings[current_line] += to_string_hex(data[0][j], 2);
}
field_rect = draw_field(painter, field_rect, s, to_string_hex(entry_.packetData.dataLen), to_string_hex(type) + pad_string_with_spaces(3) + data_strings[0]);
if (number_data_lines > 1) {
for (k = 1; k < number_data_lines; k++) {
if (!data_strings[k].empty()) {
field_rect = draw_field(painter, field_rect, s, "", pad_string_with_spaces(5) + data_strings[k]);
}
}
}
} break;
}
}
void BleRecentEntryDetailView::set_entry(const BleRecentEntry& entry) {
entry_ = entry;
set_dirty();
}
BLETxPacket BleRecentEntryDetailView::build_packet() {
BLETxPacket bleTxPacket;
memset(&bleTxPacket, 0, sizeof(BLETxPacket));
std::string macAddressStr = to_string_mac_address(entry_.packetData.macAddress, 6, true);
strncpy(bleTxPacket.macAddress, macAddressStr.c_str(), 12);
strncpy(bleTxPacket.advertisementData, entry_.dataString.c_str(), entry_.packetData.dataLen * 2);
strncpy(bleTxPacket.packetCount, "50", 3);
bleTxPacket.packet_count = 50;
return bleTxPacket;
}
static std::uint64_t get_freq_by_channel_number(uint8_t channel_number) {
uint64_t freq_hz;
switch (channel_number) {
case 37:
freq_hz = 2'402'000'000ull;
break;
case 38:
freq_hz = 2'426'000'000ull;
break;
case 39:
freq_hz = 2'480'000'000ull;
break;
case 0 ... 10:
freq_hz = 2'404'000'000ull + channel_number * 2'000'000ull;
break;
case 11 ... 36:
freq_hz = 2'428'000'000ull + (channel_number - 11) * 2'000'000ull;
break;
default:
freq_hz = UINT64_MAX;
}
return freq_hz;
}
void BLERxView::focus() {
options_channel.focus();
}
void BLERxView::file_error() {
nav_.display_modal("Error", "File read error.");
}
BLERxView::BLERxView(NavigationView& nav)
: nav_{nav} {
baseband::run_image(portapack::spi_flash::image_tag_btle_rx);
add_children({&rssi,
&channel,
&field_rf_amp,
&field_lna,
&field_vga,
&options_channel,
&field_frequency,
&check_log,
&button_find,
&check_name,
&label_sort,
&options_sort,
&label_found,
&text_found_count,
&check_serial_log,
&button_filter,
&button_save_list,
&button_clear_list,
&button_switch,
&recent_entries_view});
recent_entries_view.on_select = [this](const BleRecentEntry& entry) {
nav_.push<BleRecentEntryDetailView>(entry);
};
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(log_packets_path);
ensure_directory(packet_save_path);
filterBuffer = filter;
button_filter.on_select = [this](Button&) {
text_prompt(
nav_,
filterBuffer,
64,
[this](std::string& buffer) {
on_filter_change(buffer);
});
};
logger = std::make_unique<BLELogger>();
check_log.on_select = [this](Checkbox&, bool v) {
str_log = "";
logging = v;
if (logger && logging)
logger->append(blerx_dir.string() + "/Logs/BLELOG_" + to_string_timestamp(rtc_time::now()) + ".TXT");
};
check_log.set_value(logging);
button_save_list.on_select = [this, &nav](const ui::Button&) {
listFileBuffer = "";
text_prompt(
nav,
listFileBuffer,
64,
[this](std::string& buffer) {
on_save_file(buffer);
});
};
button_clear_list.on_select = [this](Button&) {
recent.clear();
};
button_switch.on_select = [&nav](Button&) {
nav.replace<BLETxView>();
};
field_frequency.set_step(0);
check_name.set_value(name_enable);
check_name.on_select = [this](Checkbox&, bool v) {
name_enable = v;
setAllMembersToValue(recent, &BleRecentEntry::include_name, v);
recent_entries_view.set_dirty();
};
options_channel.on_change = [this](size_t index, int32_t v) {
channel_index = (uint8_t)index;
// If we selected Auto don't do anything and Auto will handle changing.
if (v == 40) {
auto_channel = true;
return;
} else {
auto_channel = false;
}
field_frequency.set_value(get_freq_by_channel_number(v));
channel_number = v;
baseband::set_btlerx(channel_number);
};
options_sort.on_change = [this](size_t index, int32_t v) {
sort_index = (uint8_t)index;
handle_entries_sort(v);
};
options_channel.set_selected_index(channel_index, true);
options_sort.set_selected_index(sort_index, true);
button_find.on_select = [this](Button&) {
auto open_view = nav_.push<FileLoadView>(".TXT");
open_view->on_changed = [this](std::filesystem::path new_file_path) {
on_file_changed(new_file_path);
// nav_.set_on_pop([this]() { button_play.focus(); });
};
};
// Auto-configure modem for LCR RX (will be removed later)
baseband::set_btlerx(channel_number);
receiver_model.enable();
}
std::string BLERxView::build_line_str(BleRecentEntry entry) {
std::string macAddressStr = to_string_mac_address(entry.packetData.macAddress, 6, false) + ",";
std::string timestameStr = entry.timestamp + ",";
std::string nameStr = entry.nameString + ",";
std::string pduStr = pdu_type_to_string(entry.pduType) + ",";
std::string dataStr = "0x" + entry.dataString + ",";
std::string hitsStr = to_string_dec_int(entry.numHits) + ",";
std::string dbStr = to_string_dec_int(entry.dbValue) + ",";
std::string channelStr = to_string_dec_int(entry.channelNumber) + ",";
std::string lineStr = timestameStr + macAddressStr + nameStr + pduStr + dataStr + hitsStr + dbStr + channelStr;
lineStr += pad_string_with_spaces(maxLineLength - lineStr.length());
return lineStr;
}
void BLERxView::on_save_file(const std::string value) {
auto folder = packet_save_path.parent_path();
auto ext = packet_save_path.extension();
auto new_path = folder / value + ext;
saveFile(new_path);
}
bool BLERxView::saveFile(const std::filesystem::path& path) {
// Check to see if file was previously saved.
bool file_existed = file_exists(path);
// Attempt to open, if it can't be opened. Create new.
auto src = std::make_unique<File>();
auto error = src->open(path, false, true);
if (error) {
return false;
}
for (const auto& entry : recent) {
tempList.emplace_back(entry);
}
if (!file_existed) {
src->write_line(headerStr.c_str());
auto it = tempList.begin();
while (it != tempList.end()) {
BleRecentEntry entry = (BleRecentEntry)*it;
src->write_line(build_line_str(entry).c_str());
it++;
}
} else {
// Check file for macAddressStr before adding.
char currentLine[maxLineLength];
uint64_t startPos = headerStr.length();
uint64_t bytesRead = 0;
uint64_t bytePos = 0;
File::Size currentSize = src->size();
auto dst = std::make_unique<File>();
const std::filesystem::path tempFilePath = path + "~";
auto error = dst->open(tempFilePath, false, true);
if (error) {
return false;
}
dst->write_line(headerStr.c_str());
src->seek(startPos);
// Look for ones found and rewrite.
do {
memset(currentLine, 0, maxLineLength);
bytesRead = readUntil(*src, currentLine, currentSize, '\n');
if (!bytesRead) {
break;
}
bytePos += bytesRead;
std::string lineStr = "";
std::string macAddressStr = "";
BleRecentEntry foundEntry;
char* token;
token = strtok(currentLine, ",");
while (token != NULL) {
auto it = tempList.begin();
while (it != tempList.end()) {
BleRecentEntry& entry = reinterpret_cast<BleRecentEntry&>(*it);
macAddressStr = to_string_mac_address(entry.packetData.macAddress, 6, false);
if (strstr(token, macAddressStr.c_str()) != NULL) {
entry.entryFound = true;
foundEntry = entry;
break;
}
it++;
}
if (foundEntry.entryFound) {
break;
}
token = strtok(NULL, ",");
}
if (foundEntry.entryFound) {
dst->write_line(build_line_str(foundEntry).c_str());
}
} while (bytePos <= currentSize);
// Write the ones not found.
auto it = tempList.begin();
while (it != tempList.end()) {
BleRecentEntry entry = (BleRecentEntry)*it;
if (!entry.entryFound) {
dst->write_line(build_line_str(entry).c_str());
}
it++;
}
// Close files before renaming/deleting.
src.reset();
dst.reset();
// Delete original and overwrite with temp file.
delete_file(path);
rename_file(tempFilePath, path);
}
tempList.clear();
return true;
}
void BLERxView::on_data(BlePacketData* packet) {
if (!logging) {
str_log = "";
}
str_console += pdu_type_to_string((ADV_PDU_TYPE)packet->type);
str_console += " Len:";
str_console += to_string_dec_uint(packet->size);
str_console += " Mac:";
str_console += to_string_mac_address(packet->macAddress, 6, false);
str_console += " Data:";
int i;
for (i = 0; i < packet->dataLen; i++) {
str_console += to_string_hex(packet->data[i], 2);
}
uint64_t macAddressEncoded = copy_mac_address_to_uint64(packet->macAddress);
// Start of Packet stuffing.
// Masking off the top 2 bytes to avoid invalid keys.
auto& entry = ::on_packet(recent, macAddressEncoded & 0xFFFFFFFFFFFF);
updateEntry(packet, entry, (ADV_PDU_TYPE)packet->type);
// Add entries if they meet the criteria.
auto value = filter;
resetFilteredEntries(recent, [&value](const BleRecentEntry& entry) {
return (entry.dataString.find(value) == std::string::npos) && (entry.nameString.find(value) == std::string::npos);
});
handle_entries_sort(options_sort.selected_index());
// Log at End of Packet.
if (logger && logging) {
logger->log_raw_data(str_console + "\r\n");
}
if (serial_logging) {
usb_serial_thread->serial_str = str_console + "\r\n";
usb_serial_thread->str_ready = true;
}
str_console = "";
if (!searchList.empty()) {
auto it = searchList.begin();
while (it != searchList.end()) {
std::string searchStr = (std::string)*it;
if (entry.dataString.find(searchStr) != std::string::npos) {
searchList.erase(it);
found_count++;
break;
}
it++;
}
text_found_count.set(to_string_dec_uint(found_count) + "/" + to_string_dec_uint(total_count));
}
}
void BLERxView::on_filter_change(std::string value) {
// New filter? Reset list from recent entries.
if (filter != value) {
resetFilteredEntries(recent, [&value](const BleRecentEntry& entry) {
return (entry.dataString.find(value) == std::string::npos) && (entry.nameString.find(value) == std::string::npos);
});
}
filter = value;
}
void BLERxView::on_file_changed(const std::filesystem::path& new_file_path) {
file_path = new_file_path;
found_count = 0;
total_count = 0;
searchList.clear();
{ // Get the size of the data file.
File data_file;
auto error = data_file.open(file_path, true, false);
if (error) {
file_error();
file_path = "";
return;
}
uint64_t bytesRead = 0;
uint64_t bytePos = 0;
char currentLine[maxLineLength];
do {
memset(currentLine, 0, maxLineLength);
bytesRead = readUntil(data_file, currentLine, maxLineLength, '\n');
// Remove return if found.
if (currentLine[strlen(currentLine)] == '\r') {
currentLine[strlen(currentLine)] = '\0';
}
if (!bytesRead) {
break;
}
searchList.push_back(currentLine);
total_count++;
bytePos += bytesRead;
} while (bytePos <= data_file.size());
}
}
// called each 1/60th of second, so 6 = 100ms
void BLERxView::on_timer() {
if (++timer_count == timer_period) {
timer_count = 0;
if (auto_channel) {
int min = 37;
int max = 39;
int randomChannel = min + std::rand() % (max - min + 1);
field_frequency.set_value(get_freq_by_channel_number(randomChannel));
baseband::set_btlerx(randomChannel);
}
}
}
void BLERxView::handle_entries_sort(uint8_t index) {
switch (index) {
case 0:
sortEntriesBy(
recent, [](const BleRecentEntry& entry) { return entry.macAddress; }, true);
break;
case 1:
sortEntriesBy(
recent, [](const BleRecentEntry& entry) { return entry.numHits; }, false);
break;
case 2:
sortEntriesBy(
recent, [](const BleRecentEntry& entry) { return entry.dbValue; }, false);
break;
case 3:
sortEntriesBy(
recent, [](const BleRecentEntry& entry) { return entry.timestamp; }, false);
break;
case 4:
sortEntriesBy(
recent, [](const BleRecentEntry& entry) { return entry.nameString; }, true);
break;
default:
break;
}
recent_entries_view.set_dirty();
}
void BLERxView::set_parent_rect(const 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};
recent_entries_view.set_parent_rect(content_rect);
}
BLERxView::~BLERxView() {
receiver_model.disable();
baseband::shutdown();
}
void BLERxView::updateEntry(const BlePacketData* packet, BleRecentEntry& entry, ADV_PDU_TYPE pdu_type) {
std::string data_string;
int i;
for (i = 0; i < packet->dataLen; i++) {
data_string += to_string_hex(packet->data[i], 2);
}
entry.dbValue = packet->max_dB;
entry.timestamp = to_string_timestamp(rtc_time::now());
entry.dataString = data_string;
entry.packetData.type = packet->type;
entry.packetData.size = packet->size;
entry.packetData.dataLen = packet->dataLen;
// Mac Address of sender.
entry.packetData.macAddress[0] = packet->macAddress[0];
entry.packetData.macAddress[1] = packet->macAddress[1];
entry.packetData.macAddress[2] = packet->macAddress[2];
entry.packetData.macAddress[3] = packet->macAddress[3];
entry.packetData.macAddress[4] = packet->macAddress[4];
entry.packetData.macAddress[5] = packet->macAddress[5];
entry.numHits++;
entry.pduType = pdu_type;
entry.channelNumber = channel_number;
// Parse Data Section into buffer to be interpretted later.
for (int i = 0; i < packet->dataLen; i++) {
entry.packetData.data[i] = packet->data[i];
}
entry.include_name = check_name.value();
// Only parse name for advertisment packets and empty name entries
if ((pdu_type == ADV_IND || pdu_type == ADV_NONCONN_IND || pdu_type == SCAN_RSP || pdu_type == ADV_SCAN_IND) && entry.nameString.empty()) {
ADV_PDU_PAYLOAD_TYPE_0_2_4_6* advertiseData = (ADV_PDU_PAYLOAD_TYPE_0_2_4_6*)entry.packetData.data;
uint8_t currentByte = 0;
uint8_t length = 0;
uint8_t type = 0;
std::string decoded_data;
for (currentByte = 0; (currentByte < entry.packetData.dataLen);) {
length = advertiseData->Data[currentByte++];
type = advertiseData->Data[currentByte++];
// Subtract 1 because type is part of the length.
for (int i = 0; i < length - 1; i++) {
if (type == 0x08 || type == 0x09) {
decoded_data += (char)advertiseData->Data[currentByte];
}
currentByte++;
}
if (!decoded_data.empty()) {
entry.nameString = std::move(decoded_data);
break;
}
}
} else if (pdu_type == ADV_DIRECT_IND || pdu_type == SCAN_REQ) {
ADV_PDU_PAYLOAD_TYPE_1_3* directed_mac_data = (ADV_PDU_PAYLOAD_TYPE_1_3*)entry.packetData.data;
reverse_byte_array(directed_mac_data->A1, 6);
}
}
} /* namespace ui */
+378
View File
@@ -0,0 +1,378 @@
/*
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2017 Furrtek
* Copyright (C) 2023 TJ Baginski
*
* 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 __BLE_RX_APP_H__
#define __BLE_RX_APP_H__
#include "ble_tx_app.hpp"
#include "ui.hpp"
#include "ui_navigation.hpp"
#include "ui_receiver.hpp"
#include "ui_freq_field.hpp"
#include "ui_record_view.hpp"
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "log_file.hpp"
#include "utility.hpp"
#include "usb_serial_thread.hpp"
#include "file_path.hpp"
#include "recent_entries.hpp"
class BLELogger {
public:
Optional<File::Error> append(const std::filesystem::path& filename) {
return log_file.append(filename);
}
void log_raw_data(const std::string& data);
private:
LogFile log_file{};
};
namespace ui {
typedef enum {
ADV_IND = 0,
ADV_DIRECT_IND = 1,
ADV_NONCONN_IND = 2,
SCAN_REQ = 3,
SCAN_RSP = 4,
CONNECT_REQ = 5,
ADV_SCAN_IND = 6,
RESERVED0 = 7,
RESERVED1 = 8,
RESERVED2 = 9,
RESERVED3 = 10,
RESERVED4 = 11,
RESERVED5 = 12,
RESERVED6 = 13,
RESERVED7 = 14,
RESERVED8 = 15
} ADV_PDU_TYPE;
struct BleRecentEntry {
using Key = uint64_t;
static constexpr Key invalid_key = 0xffffffff;
uint64_t macAddress;
int dbValue;
BlePacketData packetData;
std::string timestamp;
std::string dataString;
std::string nameString;
bool include_name;
uint16_t numHits;
ADV_PDU_TYPE pduType;
uint8_t channelNumber;
bool entryFound;
BleRecentEntry()
: BleRecentEntry{0} {
}
BleRecentEntry(
const uint64_t macAddress)
: macAddress{macAddress},
dbValue{},
packetData{},
timestamp{},
dataString{},
nameString{},
include_name{},
numHits{},
pduType{},
channelNumber{},
entryFound{} {
}
Key key() const {
return macAddress;
}
};
using BleRecentEntries = RecentEntries<BleRecentEntry>;
using BleRecentEntriesView = RecentEntriesView<BleRecentEntries>;
class BleRecentEntryDetailView : public View {
public:
BleRecentEntryDetailView(NavigationView& nav, const BleRecentEntry& entry);
void set_entry(const BleRecentEntry& new_entry);
const BleRecentEntry& entry() const { return entry_; };
void update_data();
void focus() override;
void paint(Painter&) override;
private:
NavigationView& nav_;
BleRecentEntry entry_{};
BLETxPacket build_packet();
void on_save_file(const std::string value, BLETxPacket packetToSave);
bool saveFile(const std::filesystem::path& path, BLETxPacket packetToSave);
std::string packetFileBuffer{};
std::filesystem::path packet_save_path{blerx_dir / u"Lists/????.csv"};
static constexpr uint8_t total_data_lines{5};
Labels label_mac_address{
{{0 * 8, 0 * 16}, "Mac Address:", Color::light_grey()}};
Text text_mac_address{
{12 * 8, 0 * 16, 17 * 8, 16},
"-"};
Labels label_pdu_type{
{{0 * 8, 1 * 16}, "PDU Type:", Color::light_grey()}};
Text text_pdu_type{
{9 * 8, 1 * 16, 17 * 8, 16},
"-"};
Labels labels{
{{0 * 8, 3 * 16}, "Len", Color::light_grey()},
{{5 * 8, 3 * 16}, "Type", Color::light_grey()},
{{10 * 8, 3 * 16}, "Value", Color::light_grey()},
};
Button button_send{
{19, 224, 96, 24},
"Send"};
Button button_done{
{125, 224, 96, 24},
"Done"};
Button button_save{
{72, 264, 96, 24},
"Save"};
bool send_updates{false};
Rect draw_field(
Painter& painter,
const Rect& draw_rect,
const Style& style,
const std::string& label,
const std::string& value);
};
class BLERxView : public View {
public:
BLERxView(NavigationView& nav);
~BLERxView();
void set_parent_rect(const Rect new_parent_rect) override;
void paint(Painter&) override{};
void focus() override;
std::string title() const override { return "BLE RX"; };
private:
std::string build_line_str(BleRecentEntry entry);
void on_save_file(const std::string value);
bool saveFile(const std::filesystem::path& path);
std::unique_ptr<UsbSerialThread> usb_serial_thread{};
void on_data(BlePacketData* packetData);
void on_filter_change(std::string value);
void on_file_changed(const std::filesystem::path& new_file_path);
void file_error();
void on_timer();
void handle_entries_sort(uint8_t index);
void updateEntry(const BlePacketData* packet, BleRecentEntry& entry, ADV_PDU_TYPE pdu_type);
NavigationView& nav_;
RxRadioState radio_state_{
2402000000 /* frequency */,
4000000 /* bandwidth */,
4000000 /* sampling rate */,
ReceiverModel::Mode::WidebandFMAudio};
uint8_t channel_index{0};
uint8_t sort_index{0};
std::string filter{};
bool logging{false};
bool serial_logging{false};
bool name_enable{true};
app_settings::SettingsManager settings_{
"rx_ble",
app_settings::Mode::RX,
{
{"channel_index"sv, &channel_index},
{"sort_index"sv, &sort_index},
{"filter"sv, &filter},
{"log"sv, &logging},
// 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},
}};
std::string str_console = "";
uint8_t console_color{0};
uint32_t prev_value{0};
uint8_t channel_number = 37;
bool auto_channel = false;
int16_t timer_count{0};
int16_t timer_period{6}; // 100ms
std::string filterBuffer{};
std::string listFileBuffer{};
std::string headerStr = "Timestamp, MAC Address, Name, Packet Type, Data, Hits, dB, Channel";
uint16_t maxLineLength = 140;
std::filesystem::path file_path{};
uint64_t found_count = 0;
uint64_t total_count = 0;
std::vector<std::string> searchList{};
std::filesystem::path find_packet_path{blerx_dir / u"Find/????.TXT"};
std::filesystem::path log_packets_path{blerx_dir / u"Logs/????.TXT"};
std::filesystem::path packet_save_path{blerx_dir / u"Lists/????.csv"};
static constexpr auto header_height = 4 * 16;
static constexpr auto switch_button_height = 3 * 16;
OptionsField options_channel{
{0 * 8, 0 * 8},
5,
{{"Ch.37 ", 37},
{"Ch.38", 38},
{"Ch.39", 39},
{"Auto", 40}}};
RxFrequencyField field_frequency{
{6 * 8, 0 * 16},
nav_};
RFAmpField field_rf_amp{
{16 * 8, 0 * 16}};
LNAGainField field_lna{
{18 * 8, 0 * 16}};
VGAGainField field_vga{
{21 * 8, 0 * 16}};
RSSI rssi{
{24 * 8, 0, 6 * 8, 4}};
Channel channel{
{24 * 8, 5, 6 * 8, 4}};
Labels label_sort{
{{0 * 8, 3 * 8}, "Sort:", Color::light_grey()}};
OptionsField options_sort{
{5 * 8, 3 * 8},
4,
{{"MAC", 0},
{"Hits", 1},
{"dB", 2},
{"Time", 3},
{"Name", 4}}};
Button button_filter{
{11 * 8, 3 * 8, 4 * 8, 16},
"Filter"};
Checkbox check_log{
{17 * 8, 3 * 8},
3,
"Log",
true};
Checkbox check_name{
{23 * 8, 3 * 8},
3,
"Name",
true};
Button button_find{
{0 * 8, 6 * 8, 4 * 8, 16},
"Find"};
Labels label_found{
{{5 * 8, 6 * 8}, "Found:", Color::light_grey()}};
Text text_found_count{
{11 * 8, 3 * 16, 20 * 8, 16},
"0/0"};
Checkbox check_serial_log{
{17 * 8, 3 * 16 - 2},
7,
"USB Log",
true};
Console console{
{0, 4 * 16, 240, 240}};
Button button_clear_list{
{2 * 8, 320 - (16 + 32), 7 * 8, 32},
"Clear"};
Button button_save_list{
{11 * 8, 320 - (16 + 32), 11 * 8, 32},
"Export CSV"};
Button button_switch{
{240 - 6 * 8, 320 - (16 + 32), 4 * 8, 32},
"Tx"};
std::string str_log{""};
std::unique_ptr<BLELogger> logger{};
BleRecentEntries recent{};
BleRecentEntries tempList{};
const RecentEntriesColumns columns{{
{"Mac Address", 17},
{"Hits", 7},
{"dB", 4},
}};
BleRecentEntriesView recent_entries_view{columns, recent};
MessageHandlerRegistration message_handler_packet{
Message::ID::BlePacket,
[this](Message* const p) {
const auto message = static_cast<const BLEPacketMessage*>(p);
this->on_data(message->packet);
}};
MessageHandlerRegistration message_handler_frame_sync{
Message::ID::DisplayFrameSync,
[this](const Message* const) {
this->on_timer();
}};
};
} /* namespace ui */
#endif /*__UI_AFSK_RX_H__*/
+562
View File
@@ -0,0 +1,562 @@
/*
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2017 Furrtek
* Copyright (C) 2023 TJ Baginski
*
* 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 "ble_tx_app.hpp"
#include "ble_rx_app.hpp"
#include "ui_fileman.hpp"
#include "ui_modemsetup.hpp"
#include "audio.hpp"
#include "baseband_api.hpp"
#include "io_file.hpp"
#include "modems.hpp"
#include "portapack_persistent_memory.hpp"
#include "rtc_time.hpp"
#include "string_format.hpp"
#include "file_path.hpp"
using namespace portapack;
using namespace modems;
namespace fs = std::filesystem;
void BLELoggerTx::log_raw_data(const std::string& data) {
log_file.write_entry(data);
}
bool hasValidHexPairs(const std::string& str, int totalPairs) {
int validPairs = 0;
for (int i = 0; i < totalPairs * 2; i += 2) {
char c1 = str[i];
char c2 = str[i + 1];
if (!(std::isxdigit(c1) && std::isxdigit(c2))) {
return false; // Return false if any pair is invalid.
}
validPairs++;
}
return (validPairs == totalPairs);
}
std::vector<std::string> splitIntoStrings(const char* input) {
std::vector<std::string> result;
int length = std::strlen(input);
int start = 0;
while (start < length) {
int remaining = length - start;
int chunkSize = (remaining > 29) ? 29 : remaining;
result.push_back(std::string(input + start, chunkSize));
start += chunkSize;
}
return result;
}
uint32_t stringToUint32(const std::string& str) {
size_t pos = 0;
uint32_t result = 0;
while (pos < str.size() && std::isdigit(str[pos])) {
int digit = str[pos] - '0';
// Check for overflow before adding the next digit
if (result > (UINT32_MAX - digit) / 10) {
return 0;
}
result = result * 10 + digit;
pos++;
}
return result;
}
static std::uint64_t get_freq_by_channel_number(uint8_t channel_number) {
uint64_t freq_hz;
switch (channel_number) {
case 37:
freq_hz = 2'402'000'000ull;
break;
case 38:
freq_hz = 2'426'000'000ull;
break;
case 39:
freq_hz = 2'480'000'000ull;
break;
case 0 ... 10:
freq_hz = 2'404'000'000ull + channel_number * 2'000'000ull;
break;
case 11 ... 36:
freq_hz = 2'428'000'000ull + (channel_number - 11) * 2'000'000ull;
break;
default:
freq_hz = UINT64_MAX;
}
return freq_hz;
}
namespace ui {
void BLETxView::focus() {
button_open.focus();
}
bool BLETxView::is_active() const {
return (bool)is_running;
}
void BLETxView::file_error() {
nav_.display_modal("Error", "File read error.");
}
bool BLETxView::saveFile(const std::filesystem::path& path) {
File f;
auto error = f.create(path);
if (error)
return false;
for (uint32_t i = 0; i < num_packets; i++) {
std::string macAddressStr = packets[i].macAddress;
std::string advertisementDataStr = packets[i].advertisementData;
std::string packetCountStr = packets[i].packetCount;
std::string packetString = macAddressStr + ' ' + advertisementDataStr + ' ' + packetCountStr;
// Are we on the last line?
if (i != num_packets - 1) {
packetString += '\n';
}
f.write(packetString.c_str(), packetString.length());
}
return true;
}
void BLETxView::toggle() {
if (is_active()) {
stop();
} else {
start();
}
}
void BLETxView::start() {
baseband::run_image(portapack::spi_flash::image_tag_btle_tx);
transmitter_model.enable();
// Generate new random Mac Address.
generateRandomMacAddress(randomMac);
// If this is our first run, check file.
if (!is_active()) {
File data_file;
auto error = data_file.open(file_path);
if (error && !file_override) {
file_error();
check_loop.set_value(false);
return;
}
button_play.set_bitmap(&bitmap_stop);
is_running = true;
}
char advertisementData[63] = {0};
strcpy(advertisementData, packets[current_packet].advertisementData);
// TODO: Make this a checkbox.
if (!markedBytes.empty()) {
for (size_t i = 0; i < strlen(advertisementData); i++) {
bool found = false;
auto it = std::find(markedBytes.begin(), markedBytes.end(), i);
if (it != markedBytes.end()) {
found = true;
}
if (found) {
uint8_t hexDigit;
switch (marked_data_sequence.selected_index_value()) {
case 0:
hexDigit = marked_counter++;
break;
case 1:
hexDigit = marked_counter--;
break;
case 2: {
uint8_t min = 0x00;
uint8_t max = 0x0F;
hexDigit = min + std::rand() % (max - min + 1);
} break;
default:
hexDigit = 0;
break;
}
advertisementData[i] = uint_to_char(hexDigit, 16);
// Bounding to Hex.
if (marked_counter == 16) {
marked_counter = 0;
} else if (marked_counter == 255) {
marked_counter = 15;
}
}
}
}
// Setup next packet configuration.
progressbar.set_max(packets[current_packet].packet_count);
baseband::set_btletx(channel_number, random_mac ? randomMac : packets[current_packet].macAddress, advertisementData, pduType);
}
void BLETxView::stop() {
transmitter_model.disable();
baseband::shutdown();
progressbar.set_value(0);
button_play.set_bitmap(&bitmap_play);
check_loop.set_value(false);
update_current_packet(packets[0], 0);
is_running = false;
}
void BLETxView::reset() {
transmitter_model.disable();
baseband::shutdown();
start();
}
// called each 1/60th of second, so 6 = 100ms
void BLETxView::on_timer() {
if (++timer_count == timer_period) {
timer_count = 0;
if (is_active()) {
// Reached end of current packet repeats.
if (packet_counter == 0) {
// Done sending all packets.
if (current_packet == (num_packets - 1)) {
current_packet = 0;
// If looping, restart from beginning.
if (check_loop.value()) {
update_current_packet(packets[current_packet], current_packet);
reset();
} else {
stop();
}
} else {
current_packet++;
update_current_packet(packets[current_packet], current_packet);
reset();
}
} else {
reset();
}
}
}
if (++auto_channel_counter == auto_channel_period) {
auto_channel_counter = 0;
if (auto_channel) {
int min = 37;
int max = 39;
channel_number = min + std::rand() % (max - min + 1);
field_frequency.set_value(get_freq_by_channel_number(channel_number));
}
}
}
void BLETxView::on_tx_progress(const bool done) {
if (done) {
if (is_active()) {
if ((packet_counter % 10) == 0) {
text_packets_sent.set(to_string_dec_uint(packet_counter));
}
packet_counter--;
progressbar.set_value(packets[current_packet].packet_count - packet_counter);
}
}
}
BLETxView::BLETxView(NavigationView& nav)
: nav_{nav} {
add_children({&button_open,
&text_filename,
&progressbar,
&check_rand_mac,
&field_frequency,
&tx_view, // now it handles previous rfgain, rfamp.
&check_loop,
&button_play,
&label_speed,
&options_speed,
&options_channel,
&options_adv_type,
&label_marked_data,
&marked_data_sequence,
&label_packet_index,
&text_packet_index,
&label_packets_sent,
&text_packets_sent,
&label_mac_address,
&text_mac_address,
&label_data_packet,
&dataEditView,
&button_clear_marked,
&button_save_packet,
&button_switch});
field_frequency.set_step(0);
ensure_directory(packet_save_path);
button_play.on_select = [this](ImageButton&) {
this->toggle();
};
options_channel.on_change = [this](size_t, int32_t i) {
// If we selected Auto don't do anything and Auto will handle changing.
if (i == 40) {
auto_channel = true;
return;
} else {
auto_channel = false;
}
field_frequency.set_value(get_freq_by_channel_number(i));
channel_number = i;
};
options_speed.on_change = [this](size_t, int32_t i) {
timer_period = i;
timer_count = 0;
};
options_adv_type.on_change = [this](size_t, int32_t i) {
pduType = (PKT_TYPE)i;
};
options_speed.set_selected_index(0);
options_channel.set_selected_index(0);
options_adv_type.set_selected_index(0);
check_rand_mac.set_value(false);
check_rand_mac.on_select = [this](Checkbox&, bool v) {
random_mac = v;
};
button_open.on_select = [this](Button&) {
auto open_view = nav_.push<FileLoadView>(".TXT");
open_view->on_changed = [this](std::filesystem::path new_file_path) {
on_file_changed(new_file_path);
nav_.set_on_pop([this]() { button_play.focus(); });
};
};
button_save_packet.on_select = [this, &nav](Button&) {
packetFileBuffer = "";
text_prompt(
nav,
packetFileBuffer,
64,
[this](std::string& buffer) {
on_save_file(buffer);
});
};
button_switch.on_select = [&nav](Button&) {
nav.replace<BLERxView>();
};
dataEditView.on_select = [this] {
// Save last selected cursor.
cursor_pos.line = dataEditView.line();
cursor_pos.col = dataEditView.col();
// Reject setting newline at index 29.
if (cursor_pos.col != 29) {
uint16_t dataBytePos = (cursor_pos.line * 29) + cursor_pos.col;
auto it = std::find(markedBytes.begin(), markedBytes.end(), dataBytePos);
if (it != markedBytes.end()) {
markedBytes.erase(it);
} else {
markedBytes.push_back(dataBytePos);
}
dataEditView.cursor_mark_selected();
}
};
button_clear_marked.on_select = [this](Button&) {
marked_counter = 0;
markedBytes.clear();
dataEditView.cursor_clear_marked();
};
}
BLETxView::BLETxView(
NavigationView& nav,
BLETxPacket packet)
: BLETxView(nav) {
packets[0] = packet;
update_current_packet(packets[0], 0);
num_packets = 1;
file_override = true;
}
void BLETxView::on_file_changed(const fs::path& new_file_path) {
file_path = new_file_path;
num_packets = 0;
{ // Get the size of the data file.
File data_file;
auto error = data_file.open(file_path);
if (error) {
file_error();
file_path = "";
return;
}
do {
readUntil(data_file, packets[num_packets].macAddress, mac_address_size_str, ' ');
readUntil(data_file, packets[num_packets].advertisementData, max_packet_size_str, ' ');
readUntil(data_file, packets[num_packets].packetCount, max_packet_repeat_str, '\n');
uint64_t macAddressSize = strlen(packets[num_packets].macAddress);
uint64_t advertisementDataSize = strlen(packets[num_packets].advertisementData);
uint64_t packetCountSize = strlen(packets[num_packets].packetCount);
packets[num_packets].packet_count = stringToUint32(packets[num_packets].packetCount);
// Verify Data.
if ((macAddressSize == mac_address_size_str) && (advertisementDataSize < max_packet_size_str) && (packetCountSize < max_packet_repeat_str) &&
hasValidHexPairs(packets[num_packets].macAddress, macAddressSize / 2) && hasValidHexPairs(packets[num_packets].advertisementData, advertisementDataSize / 2) && (packets[num_packets].packet_count >= 1) && (packets[num_packets].packet_count < max_packet_repeat_count)) {
text_filename.set(truncate(file_path.filename().string(), 12));
} else {
// Did not find any packets.
if (num_packets == 0) {
file_path = "";
return;
}
break;
}
num_packets++;
} while (num_packets < max_num_packets);
update_current_packet(packets[0], 0);
}
}
void BLETxView::on_save_file(const std::string value) {
auto folder = packet_save_path.parent_path();
auto ext = packet_save_path.extension();
auto new_path = folder / value + ext;
saveFile(new_path);
}
void BLETxView::on_data(uint32_t value, bool is_data) {
std::string str_console = "";
if (is_data) {
str_console += (char)(value);
}
}
void BLETxView::update_current_packet(BLETxPacket packet, uint32_t currentIndex) {
std::string formattedMacAddress = to_string_formatted_mac_address(packet.macAddress);
std::vector<std::string> strings = splitIntoStrings(packet.advertisementData);
text_packet_index.set(to_string_dec_uint(current_packet));
text_packets_sent.set(to_string_dec_uint(packet.packet_count));
text_mac_address.set(formattedMacAddress);
packet_counter = packet.packet_count;
current_packet = currentIndex;
dataFile.create(dataTempFilePath);
for (const std::string& str : strings) {
dataFile.write(str.c_str(), str.size());
dataFile.write("\n", 1);
;
}
dataFile.~File();
auto result = FileWrapper::open(dataTempFilePath);
if (!result)
return;
dataFileWrapper = *std::move(result);
dataEditView.set_font_zoom(true);
dataEditView.set_file(*dataFileWrapper);
dataEditView.redraw(true, true);
}
void BLETxView::set_parent_rect(const 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};
dataEditView.set_parent_rect(content_rect);
}
BLETxView::~BLETxView() {
delete_file(dataTempFilePath);
transmitter_model.disable();
baseband::shutdown();
}
} /* namespace ui */
+338
View File
@@ -0,0 +1,338 @@
/*
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2017 Furrtek
* Copyright (C) 2023 TJ Baginski
*
* 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 __BLE_TX_APP_H__
#define __BLE_TX_APP_H__
#include "ui.hpp"
#include "ui_navigation.hpp"
#include "ui_receiver.hpp"
#include "ui_transmitter.hpp"
#include "ui_text_editor.hpp"
#include "ui_freq_field.hpp"
#include "ui_record_view.hpp"
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "replay_thread.hpp"
#include "log_file.hpp"
#include "utility.hpp"
#include "file_path.hpp"
#include "recent_entries.hpp"
#include <string>
#include <memory>
class BLELoggerTx {
public:
Optional<File::Error> append(const std::filesystem::path& filename) {
return log_file.append(filename);
}
void log_raw_data(const std::string& data);
private:
LogFile log_file{};
};
namespace ui {
enum PKT_TYPE {
PKT_TYPE_INVALID_TYPE,
PKT_TYPE_RAW,
PKT_TYPE_DISCOVERY,
PKT_TYPE_IBEACON,
PKT_TYPE_ADV_IND,
PKT_TYPE_ADV_DIRECT_IND,
PKT_TYPE_ADV_NONCONN_IND,
PKT_TYPE_ADV_SCAN_IND,
PKT_TYPE_SCAN_REQ,
PKT_TYPE_SCAN_RSP,
PKT_TYPE_CONNECT_REQ,
PKT_TYPE_LL_DATA,
PKT_TYPE_LL_CONNECTION_UPDATE_REQ,
PKT_TYPE_LL_CHANNEL_MAP_REQ,
PKT_TYPE_LL_TERMINATE_IND,
PKT_TYPE_LL_ENC_REQ,
PKT_TYPE_LL_ENC_RSP,
PKT_TYPE_LL_START_ENC_REQ,
PKT_TYPE_LL_START_ENC_RSP,
PKT_TYPE_LL_UNKNOWN_RSP,
PKT_TYPE_LL_FEATURE_REQ,
PKT_TYPE_LL_FEATURE_RSP,
PKT_TYPE_LL_PAUSE_ENC_REQ,
PKT_TYPE_LL_PAUSE_ENC_RSP,
PKT_TYPE_LL_VERSION_IND,
PKT_TYPE_LL_REJECT_IND,
PKT_TYPE_NUM_PKT_TYPE
};
struct BLETxPacket {
char macAddress[13];
char advertisementData[63];
char packetCount[11];
uint32_t packet_count;
PKT_TYPE packetType;
};
class BLETxView : public View {
public:
BLETxView(NavigationView& nav);
BLETxView(NavigationView& nav, BLETxPacket packet);
~BLETxView();
void set_parent_rect(const Rect new_parent_rect) override;
void paint(Painter&) override{};
void focus() override;
bool is_active() const;
void toggle();
void start();
void stop();
void reset();
void handle_replay_thread_done(const uint32_t return_code);
void file_error();
bool saveFile(const std::filesystem::path& path);
std::string title() const override { return "BLE TX"; };
private:
void on_timer();
void on_data(uint32_t value, bool is_data);
void on_file_changed(const std::filesystem::path& new_file_path);
void on_save_file(const std::string value);
void on_tx_progress(const bool done);
void on_random_data_change(std::string value);
void update_current_packet(BLETxPacket packet, uint32_t currentIndex);
NavigationView& nav_;
TxRadioState radio_state_{
2'402'000'000 /* frequency */,
4'000'000 /* bandwidth */,
4'000'000 /* sampling rate */
};
app_settings::SettingsManager settings_{
"tx_ble", app_settings::Mode::TX};
uint8_t console_color{0};
uint32_t prev_value{0};
std::filesystem::path file_path{};
std::filesystem::path packet_save_path{bletx_dir / u"BLETX_????.TXT"};
uint8_t channel_number = 37;
bool auto_channel = false;
char randomMac[13] = "010203040506";
bool is_running = false;
int16_t timer_count{0};
int16_t timer_period{1};
int16_t auto_channel_counter = 0;
int16_t auto_channel_period{6};
bool repeatLoop = false;
uint32_t packet_counter{0};
uint32_t num_packets{0};
uint32_t current_packet{0};
bool random_mac = false;
bool file_override = false;
typedef struct {
uint16_t line;
uint16_t col;
} CursorPos;
std::unique_ptr<FileWrapper> dataFileWrapper{};
File dataFile{};
std::filesystem::path dataTempFilePath{bletx_dir / u"dataFileTemp.TXT"};
std::vector<uint16_t> markedBytes{};
CursorPos cursor_pos{};
uint8_t marked_counter = 0;
static constexpr uint8_t mac_address_size_str{12};
static constexpr uint8_t max_packet_size_str{62};
static constexpr uint8_t max_packet_repeat_str{10};
static constexpr uint32_t max_packet_repeat_count{UINT32_MAX};
static constexpr uint32_t max_num_packets{32};
BLETxPacket packets[max_num_packets];
PKT_TYPE pduType = {PKT_TYPE_DISCOVERY};
static constexpr auto header_height = 10 * 16;
static constexpr auto switch_button_height = 6 * 16;
Button button_open{
{0 * 8, 0 * 16, 10 * 8, 2 * 16},
"Open file"};
Text text_filename{
{11 * 8, 0 * 16, 12 * 8, 16},
"-"};
ProgressBar progressbar{
{11 * 8, 1 * 16, 9 * 8, 16}};
Checkbox check_rand_mac{
{21 * 8, 1 * 16},
6,
"?? Mac",
true};
TxFrequencyField field_frequency{
{0 * 8, 2 * 16},
nav_};
TransmitterView2 tx_view{
{11 * 8, 2 * 16},
/*short_ui*/ true};
Checkbox check_loop{
{21 * 8, 2 * 16},
4,
"Loop",
true};
ImageButton button_play{
{28 * 8, 2 * 16, 2 * 8, 1 * 16},
&bitmap_play,
Color::green(),
Color::black()};
Labels label_speed{
{{0 * 8, 6 * 8}, "Speed:", Color::light_grey()}};
OptionsField options_speed{
{7 * 8, 6 * 8},
3,
{{"1 ", 1}, // 16ms
{"2 ", 2}, // 32ms
{"3 ", 3}, // 48ms
{"4 ", 6}, // 100ms
{"5 ", 12}}}; // 200ms
OptionsField options_channel{
{11 * 8, 6 * 8},
5,
{{"Ch.37 ", 37},
{"Ch.38", 38},
{"Ch.39", 39},
{"Auto", 40}}};
OptionsField options_adv_type{
{17 * 8, 6 * 8},
14,
{{"DISCOVERY ", PKT_TYPE_DISCOVERY},
{"ADV_IND", PKT_TYPE_ADV_IND},
{"ADV_DIRECT", PKT_TYPE_ADV_DIRECT_IND},
{"ADV_NONCONN", PKT_TYPE_ADV_NONCONN_IND},
{"ADV_SCAN_IND", PKT_TYPE_ADV_SCAN_IND},
{"SCAN_REQ", PKT_TYPE_SCAN_REQ},
{"SCAN_RSP", PKT_TYPE_SCAN_RSP},
{"CONNECT_REQ", PKT_TYPE_CONNECT_REQ}}};
Labels label_marked_data{
{{0 * 8, 4 * 16}, "Marked Data:", Color::light_grey()}};
OptionsField marked_data_sequence{
{12 * 8, 8 * 8},
8,
{{"Ascend", 0},
{"Descend", 1},
{"Random", 2}}};
Labels label_packet_index{
{{0 * 8, 12 * 8}, "Packet Index:", Color::light_grey()}};
Text text_packet_index{
{13 * 8, 6 * 16, 12 * 8, 16},
"-"};
Labels label_packets_sent{
{{0 * 8, 14 * 8}, "Repeat Count:", Color::light_grey()}};
Text text_packets_sent{
{13 * 8, 7 * 16, 12 * 8, 16},
"-"};
Labels label_mac_address{
{{0 * 8, 16 * 8}, "Mac Address:", Color::light_grey()}};
Text text_mac_address{
{12 * 8, 8 * 16, 20 * 8, 16},
"-"};
Labels label_data_packet{
{{0 * 8, 9 * 16}, "Packet Data:", Color::light_grey()}};
Console console{
{0, 9 * 18, 240, 240}};
TextViewer dataEditView{
{0, 9 * 18, 240, 240}};
Button button_clear_marked{
{1 * 8, 14 * 16, 13 * 8, 3 * 8},
"Clear Marked"};
Button button_save_packet{
{1 * 8, 16 * 16, 13 * 8, 2 * 16},
"Save Packet"};
Button button_switch{
{16 * 8, 16 * 16, 13 * 8, 2 * 16},
"Switch to Rx"};
std::string str_log{""};
bool logging{true};
bool logging_done{false};
std::string packetFileBuffer{};
std::unique_ptr<BLELoggerTx> logger{};
MessageHandlerRegistration message_handler_packet{
Message::ID::AFSKData,
[this](Message* const p) {
const auto message = static_cast<const AFSKDataMessage*>(p);
this->on_data(message->value, message->is_data);
}};
MessageHandlerRegistration message_handler_tx_progress{
Message::ID::TXProgress,
[this](const Message* const p) {
const auto message = *reinterpret_cast<const TXProgressMessage*>(p);
this->on_tx_progress(message.done);
}};
MessageHandlerRegistration message_handler_frame_sync{
Message::ID::DisplayFrameSync,
[this](const Message* const) {
this->on_timer();
}};
};
} /* namespace ui */
#endif /*__UI_AFSK_RX_H__*/
@@ -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() {
receiver_model.disable();
baseband::shutdown();
+4 -3
View File
@@ -31,12 +31,14 @@
#include "ui_spectrum.hpp"
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "file_path.hpp"
namespace ui {
class CaptureAppView : public View {
public:
CaptureAppView(NavigationView& nav);
CaptureAppView(NavigationView& nav, ReceiverModel::settings_t override);
~CaptureAppView();
void focus() override;
@@ -51,8 +53,7 @@ class CaptureAppView : public View {
NavigationView& nav_;
RxRadioState radio_state_{ReceiverModel::Mode::Capture};
app_settings::SettingsManager settings_{
"rx_capture", app_settings::Mode::RX,
app_settings::Options::UseGlobalTargetFrequency};
"rx_capture", app_settings::Mode::RX};
Labels labels{
{{0 * 8, 1 * 16}, "Rate:", Color::light_grey()},
@@ -101,7 +102,7 @@ class CaptureAppView : public View {
RecordView record_view{
{0 * 8, 2 * 16, 30 * 8, 1 * 16},
u"BBD_????.*",
u"CAPTURES",
captures_dir,
RecordView::FileType::RawS16,
16384,
3};
+42 -13
View File
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2023 Mark Thompson
*
* This file is part of PortaPack.
*
@@ -22,7 +23,7 @@
#include "ert_app.hpp"
#include "baseband_api.hpp"
#include "audio.hpp"
#include "portapack.hpp"
using namespace portapack;
@@ -30,6 +31,9 @@ using namespace portapack;
#include "crc.hpp"
#include "string_format.hpp"
#include "file_path.hpp"
namespace pmem = portapack::persistent_memory;
namespace ert {
@@ -54,20 +58,31 @@ std::string id(ID 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) {
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 ert */
void ERTLogger::on_packet(const ert::Packet& packet) {
void ERTLogger::on_packet(const ert::Packet& packet, const uint32_t target_frequency) {
const auto formatted = packet.symbols_formatted();
std::string entry = ert::format::type(packet.type()) + " " + formatted.data + "/" + formatted.errors;
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 + " ID:" + to_string_dec_uint(packet.id(), 1);
log_file.write_entry(packet.received_at(), entry);
}
@@ -77,6 +92,8 @@ void ERTRecentEntry::update(const ert::Packet& packet) {
received_count++;
last_consumption = packet.consumption();
last_tamper_flags = packet.tamper_flags();
packet_type = packet.type();
}
namespace ui {
@@ -87,38 +104,46 @@ void RecentEntriesTable<ERTRecentEntries>::draw(
const Rect& target_rect,
Painter& painter,
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 += " +++";
} else {
line += " " + to_string_dec_uint(entry.received_count, 3);
}
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 += (entry.received_count > 99) ? " ++" : to_string_dec_uint(entry.received_count, 3);
line.resize(target_rect.width() / 8, ' ');
painter.draw_string(target_rect.location(), style, line);
}
ERTAppView::ERTAppView(NavigationView&) {
ERTAppView::ERTAppView(NavigationView& nav)
: nav_{nav} {
baseband::run_image(portapack::spi_flash::image_tag_ert);
add_children({
&field_frequency,
&field_rf_amp,
&field_lna,
&field_vga,
&rssi,
&field_volume,
&recent_entries_view,
});
field_frequency.set_step(1000000);
receiver_model.enable();
logger = std::make_unique<ERTLogger>();
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() {
audio::output::stop();
receiver_model.disable();
baseband::shutdown();
}
@@ -134,7 +159,7 @@ void ERTAppView::set_parent_rect(const Rect new_parent_rect) {
void ERTAppView::on_packet(const ert::Packet& packet) {
if (logger) {
logger->on_packet(packet);
logger->on_packet(packet, receiver_model.target_frequency());
}
if (packet.crc_ok()) {
@@ -142,6 +167,10 @@ void ERTAppView::on_packet(const ert::Packet& packet) {
entry.update(packet);
recent_entries_view.set_dirty();
}
if (pmem::beep_on_packets()) {
baseband::request_audio_beep(1000, 24000, 60);
}
}
void ERTAppView::on_show_list() {
+18 -6
View File
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2023 Mark Thompson
*
* This file is part of PortaPack.
*
@@ -24,6 +25,7 @@
#include "ui_navigation.hpp"
#include "ui_receiver.hpp"
#include "ui_freq_field.hpp"
#include "ui_rssi.hpp"
#include "ui_channel.hpp"
@@ -71,11 +73,12 @@ struct ERTRecentEntry {
ert::ID id{ert::invalid_id};
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};
ert::Consumption last_consumption{};
ERTRecentEntry(
const Key& key)
: id{key.id},
@@ -95,7 +98,7 @@ class ERTLogger {
return log_file.append(filename);
}
void on_packet(const ert::Packet& packet);
void on_packet(const ert::Packet& packet, const uint32_t target_frequency);
private:
LogFile log_file{};
@@ -126,6 +129,7 @@ class ERTAppView : public View {
ERTRecentEntries recent{};
std::unique_ptr<ERTLogger> logger{};
NavigationView& nav_;
RxRadioState radio_state_{
911600000 /* frequency */,
2500000 /* bandwidth */,
@@ -135,14 +139,19 @@ class ERTAppView : public View {
const RecentEntriesColumns columns{{
{"ID", 10},
{"Tp", 2},
{"Consumpt", 10},
{"Cnt", 3},
{"Ty", 2},
{"Consumpt", 8},
{"Tamp", 4},
{"Ct", 2},
}};
ERTRecentEntriesView recent_entries_view{columns, recent};
static constexpr auto header_height = 1 * 16;
RxFrequencyField field_frequency{
{0 * 8, 0 * 16},
nav_};
RFAmpField field_rf_amp{
{13 * 8, 0 * 16}};
@@ -156,6 +165,9 @@ class ERTAppView : public View {
{21 * 8, 0, 6 * 8, 4},
};
AudioVolumeField field_volume{
{28 * 8, 0 * 16}};
MessageHandlerRegistration message_handler_packet{
Message::ID::ERTPacket,
[this](Message* const p) {
+10 -1
View File
@@ -27,6 +27,7 @@
#include "portapack_persistent_memory.hpp"
#include "string_format.hpp"
#include "utility.hpp"
#include "file_path.hpp"
using namespace portapack;
using namespace pocsag;
@@ -115,7 +116,7 @@ POCSAGAppView::POCSAGAppView(NavigationView& nav)
: 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.on_change = [this](int32_t v) {
@@ -135,6 +136,10 @@ POCSAGAppView::POCSAGAppView(NavigationView& nav)
};
refresh_ui();
if (pmem::beep_on_packets())
audio::set_rate(audio::Rate::Hz_24000);
audio::output::start();
receiver_model.enable();
baseband::set_pocsag();
@@ -303,6 +308,10 @@ void POCSAGAppView::on_packet(const POCSAGPacketMessage* message) {
// Set status icon color to indicate state machine 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) {
+1 -1
View File
@@ -39,7 +39,7 @@
class POCSAGLogger {
public:
Optional<File::Error> append(const std::string& filename) {
Optional<File::Error> append(const std::filesystem::path& filename) {
return log_file.append(filename);
}
+1 -1
View File
@@ -44,7 +44,7 @@ void ReplayAppView::set_ready() {
}
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{};
{ // Get the size of the data file.
+39 -16
View File
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
* Copyright (C) 2024 Mark Thompson
*
* This file is part of PortaPack.
*
@@ -26,6 +27,8 @@
#include "string_format.hpp"
#include "tonesets.hpp"
#include "ui_tone_key.hpp"
#include "audio.hpp"
#include "file_path.hpp"
using namespace tonekey;
using namespace portapack;
@@ -40,6 +43,7 @@ void SoundBoardView::stop() {
if (is_active())
replay_thread.reset();
audio::output::stop();
transmitter_model.disable();
tx_view.set_transmitting(false);
@@ -49,7 +53,7 @@ void SoundBoardView::stop() {
void SoundBoardView::handle_replay_thread_done(const uint32_t return_code) {
stop();
// progressbar.set_value(0);
progressbar.set_value(0);
if (return_code == ReplayThread::END_OF_FILE) {
if (check_random.value()) {
@@ -78,9 +82,16 @@ void SoundBoardView::file_error() {
}
void SoundBoardView::start_tx(const uint32_t id) {
if (file_list.empty()) {
file_error();
return;
}
auto reader = std::make_unique<WAVFileReader>();
uint32_t tone_key_index = options_tone_key.selected_index();
uint32_t sample_rate;
uint8_t bits_per_sample;
stop();
@@ -91,11 +102,12 @@ void SoundBoardView::start_tx(const uint32_t id) {
playing_id = id;
// progressbar.set_max(reader->sample_count());
progressbar.set_max(reader->sample_count());
// button_play.set_bitmap(&bitmap_stop);
sample_rate = reader->sample_rate();
bits_per_sample = reader->bits_per_sample();
replay_thread = std::make_unique<ReplayThread>(
std::move(reader),
@@ -111,18 +123,22 @@ void SoundBoardView::start_tx(const uint32_t id) {
1536000 / 20, // Update vu-meter at 20Hz
transmitter_model.channel_bandwidth(),
0, // Gain is unused
8, // shift_bits_s16, default 8 bits, but also unused
TONES_F2D(tone_key_frequency(tone_key_index), 1536000),
0, // AM
0, // DSB
0, // USB
0 // LSB
8, // shift_bits_s16, default 8 bits, but also unused
bits_per_sample,
TONES_F2D(tone_key_frequency(tone_key_index), TONES_SAMPLERATE),
false, // AM
false, // DSB
false, // USB
false // LSB
);
baseband::set_sample_rate(sample_rate);
transmitter_model.enable();
tx_view.set_transmitting(true);
if (tone_key_index == 0)
audio::output::start();
}
/*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)progress; // avoid warning
// progressbar.set_value(progress);
progressbar.set_value(progress);
}
void SoundBoardView::on_select_entry() {
@@ -150,7 +165,7 @@ void SoundBoardView::refresh_list() {
// List directories and files, put directories up top
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 (entry.path().string().length()) {
auto entry_extension = entry.path().extension().string();
@@ -159,8 +174,8 @@ void SoundBoardView::refresh_list() {
c = toupper(c);
if (entry_extension == ".WAV") {
if (reader->open(u"/WAV/" + entry.path().native())) {
if ((reader->channels() == 1) && (reader->bits_per_sample() == 8)) {
if (reader->open(wav_dir / entry.path())) {
if ((reader->channels() == 1) && ((reader->bits_per_sample() == 8) || (reader->bits_per_sample() == 16))) {
// sounds[c].ms_duration = reader->ms_duration();
// sounds[c].path = u"WAV/" + entry.path().native();
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++) {
menu_view.add_item({file_list[n].string().substr(0, 30),
ui::Color::white(),
ui::Color::dark_magenta(),
nullptr,
[this](KeyEvent) {
on_select_entry();
@@ -226,7 +241,9 @@ SoundBoardView::SoundBoardView(
&options_tone_key,
//&text_title,
//&text_duration,
//&progressbar,
&progressbar,
&field_volume,
&text_volume_disabled,
&page_info,
&check_loop,
&check_random,
@@ -252,6 +269,13 @@ SoundBoardView::SoundBoardView(
tone_keys_populate(options_tone_key);
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_random.set_value(false);
@@ -274,7 +298,6 @@ SoundBoardView::SoundBoardView(
SoundBoardView::~SoundBoardView() {
stop();
transmitter_model.disable();
baseband::shutdown();
}
+12 -6
View File
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
* Copyright (C) 2024 Mark Thompson
*
* This file is part of PortaPack.
*
@@ -91,7 +92,7 @@ class SoundBoardView : public View {
void on_select_entry();
Labels labels{
//{ { 0, 20 * 8 + 4 }, "Title:", Color::light_grey() },
{{24 * 8, 180}, "Vol:", Color::light_grey()},
{{0, 180}, "Key:", Color::light_grey()}};
Button button_next_page{
@@ -103,7 +104,7 @@ class SoundBoardView : public View {
"<="};
Text page_info{
{0, 30 * 8 - 4, 30 * 8, 16}};
{0, 29 * 8, 30 * 8, 16}};
MenuView menu_view{
{0, 0, 240, 175},
@@ -122,10 +123,16 @@ class SoundBoardView : public View {
};*/
OptionsField options_tone_key{
{32, 180},
{4 * 8, 180},
18,
{}};
AudioVolumeField field_volume{
{28 * 8, 180}};
Text text_volume_disabled{
{28 * 8, 180, 3 * 8, 16},
"--"};
Checkbox check_loop{
{0, 25 * 8 + 4},
4,
@@ -136,9 +143,8 @@ class SoundBoardView : public View {
6,
"Random"};
// ProgressBar progressbar {
// { 0 * 8, 30 * 8 - 4, 30 * 8, 16 }
// };
ProgressBar progressbar{
{0 * 8, 31 * 8 + 2, 30 * 8, 4}};
TransmitterView tx_view{
16 * 16,
+23 -13
View File
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2023 Mark Thompson
*
* This file is part of PortaPack.
*
@@ -22,21 +23,21 @@
#include "tpms_app.hpp"
#include "baseband_api.hpp"
#include "audio.hpp"
#include "portapack.hpp"
using namespace portapack;
#include "string_format.hpp"
#include "utility.hpp"
#include "file_path.hpp"
namespace pmem = portapack::persistent_memory;
namespace tpms {
namespace format {
static bool use_kpa = true;
static bool use_celsius = true;
std::string type(Reading::Type type) {
return to_string_dec_uint(toUType(type), 2);
}
@@ -46,11 +47,11 @@ std::string id(TransponderID id) {
}
std::string pressure(Pressure pressure) {
return to_string_dec_int(use_kpa ? pressure.kilopascal() : pressure.psi(), 3);
return to_string_dec_int(units_psi ? pressure.psi() : pressure.kilopascal(), 3);
}
std::string temperature(Temperature temperature) {
return to_string_dec_int(use_celsius ? temperature.celsius() : temperature.fahrenheit(), 3);
return to_string_dec_int(units_fahr ? temperature.fahrenheit() : temperature.celsius(), 3);
}
std::string flags(Flags flags) {
@@ -148,6 +149,7 @@ TPMSAppView::TPMSAppView(NavigationView&) {
baseband::run_image(portapack::spi_flash::image_tag_tpms);
add_children({&rssi,
&field_volume,
&channel,
&options_band,
&options_pressure,
@@ -165,26 +167,30 @@ TPMSAppView::TPMSAppView(NavigationView&) {
options_band.set_by_value(receiver_model.target_frequency());
options_pressure.on_change = [this](size_t, int32_t i) {
tpms::format::use_kpa = !i;
tpms::format::units_psi = (bool)i;
update_view();
};
options_pressure.set_selected_index(0, true);
options_pressure.set_selected_index(tpms::format::units_psi, true);
options_temperature.on_change = [this](size_t, int32_t i) {
tpms::format::use_celsius = !i;
tpms::format::units_fahr = (bool)i;
update_view();
};
options_temperature.set_selected_index(0, true);
options_temperature.set_selected_index(tpms::format::units_fahr, true);
logger = std::make_unique<TPMSLogger>();
if (logger) {
logger->append(LOG_ROOT_DIR "/TPMS.TXT");
logger->append(logs_dir / u"TPMS.TXT");
}
if (pmem::beep_on_packets()) {
audio::set_rate(audio::Rate::Hz_24000);
audio::output::start();
}
}
TPMSAppView::~TPMSAppView() {
audio::output::stop();
receiver_model.disable();
baseband::shutdown();
}
@@ -217,6 +223,10 @@ void TPMSAppView::on_packet(const tpms::Packet& packet) {
entry.update(reading);
recent_entries_view.set_dirty();
}
if (pmem::beep_on_packets()) {
baseband::request_audio_beep(1000, 24000, 60);
}
}
void TPMSAppView::on_show_list() {
+25 -3
View File
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2023 Mark Thompson
*
* This file is part of PortaPack.
*
@@ -37,6 +38,17 @@
#include "tpms_packet.hpp"
namespace tpms {
namespace format {
static bool units_psi{false};
static bool units_fahr{false};
} /* namespace format */
} /* namespace tpms */
namespace std {
} /* namespace std */
@@ -103,11 +115,17 @@ class TPMSAppView : public View {
private:
RxRadioState radio_state_{
315000000 /* frequency*/,
314950000 /* frequency*/,
1750000 /* bandwidth */,
2457600 /* sampling rate */};
app_settings::SettingsManager settings_{
"rx_tpms", app_settings::Mode::RX};
"rx_tpms",
app_settings::Mode::RX,
{
{"units_psi"sv, &tpms::format::units_psi},
{"units_fahr"sv, &tpms::format::units_fahr},
}};
MessageHandlerRegistration message_handler_packet{
Message::ID::TPMSPacket,
@@ -125,15 +143,19 @@ class TPMSAppView : public View {
{21 * 8, 0, 6 * 8, 4},
};
AudioVolumeField field_volume{
{28 * 8, 0 * 16}};
Channel channel{
{21 * 8, 5, 6 * 8, 4},
};
// "315 MHz" TPMS sensors transmit at either 314.9 or 315 MHz but we should pick up either
OptionsField options_band{
{0 * 8, 0 * 16},
3,
{
{"315", 315000000},
{"315", 314950000},
{"434", 433920000},
}};
@@ -13,17 +13,17 @@ AboutView::AboutView(NavigationView& nav) {
}
void AboutView::update() {
if (++timer > 200) {
if (++timer > 400) {
timer = 0;
switch (++frame) {
case 1:
// TODO: Generate this automatically from github
// https://github.com/eried/portapack-mayhem/graphs/contributors?to=2022-01-01&from=2020-04-12&type=c
// https://github.com/portapack-mayhem/mayhem-firmware/graphs/contributors?to=2022-01-01&from=2020-04-12&type=c
console.writeln(STR_COLOR_DARK_YELLOW "Mayhem:");
console.writeln("eried,euquiq,gregoryfenton");
console.writeln("johnelder,jwetzell,nnemanjan00");
console.writeln("N0vaPixel,klockee,GullCode");
console.writeln("N0vaPixel,klockee,gullradriel");
console.writeln("jamesshao8,ITAxReal,rascafr");
console.writeln("mcules,dqs105,strijar");
console.writeln("zhang00963,RedFox-Fr,aldude999");
@@ -37,11 +37,12 @@ void AboutView::update() {
break;
case 2:
console.writeln("NotherNgineer,zxkmm,u-foka");
console.writeln("Netro,HTotoo");
console.writeln("");
break;
case 3:
// https://github.com/eried/portapack-mayhem/graphs/contributors?to=2020-04-12&from=2015-07-31&type=c
// https://github.com/portapack-mayhem/mayhem-firmware/graphs/contributors?to=2020-04-12&from=2015-07-31&type=c
console.writeln(STR_COLOR_DARK_YELLOW "Havoc:");
console.writeln("furrtek,mrmookie,NotPike");
console.writeln("mjwaxios,ImDroided,Giorgiofox");
@@ -54,7 +55,7 @@ void AboutView::update() {
break;
case 4:
// https://github.com/eried/portapack-mayhem/graphs/contributors?from=2014-07-05&to=2015-07-31&type=c
// https://github.com/portapack-mayhem/mayhem-firmware/graphs/contributors?from=2014-07-05&to=2015-07-31&type=c
console.writeln(STR_COLOR_DARK_YELLOW "PortaPack:");
console.writeln("jboone,argilo");
console.writeln("");
+433 -356
View File
@@ -1,6 +1,8 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2017 Furrtek
* Copyright (C) 2023 Kyle Reed
* Copyright (C) 2024 Mark Thompson
*
* This file is part of PortaPack.
*
@@ -20,21 +22,27 @@
* Boston, MA 02110-1301, USA.
*/
#include <strings.h>
#include <algorithm>
#include "ui_adsb_rx.hpp"
#include "ui_alphanum.hpp"
#include "rtc_time.hpp"
#include "string_format.hpp"
#include "baseband_api.hpp"
#include "portapack_persistent_memory.hpp"
#include "rtc_time.hpp"
#include "string_format.hpp"
#include "file_path.hpp"
#include "audio.hpp"
using namespace portapack;
namespace pmem = portapack::persistent_memory;
namespace ui {
bool ac_details_view_active{false};
static std::string get_map_tag(const AircraftRecentEntry& entry) {
return trimr(entry.callsign.empty() ? entry.icao_str : entry.callsign);
}
template <>
void RecentEntriesTable<AircraftRecentEntries>::draw(
@@ -43,23 +51,25 @@ void RecentEntriesTable<AircraftRecentEntries>::draw(
Painter& painter,
const Style& style) {
Color target_color;
auto entry_age = entry.age;
std::string entry_string;
// Color decay for flights not being updated anymore
if (entry_age < ADSB_CURRENT) {
entry_string = "";
target_color = Color::green();
} else if (entry_age < ADSB_RECENT) {
entry_string = STR_COLOR_LIGHT_GREY;
target_color = Color::light_grey();
} else {
entry_string = STR_COLOR_DARK_GREY;
target_color = Color::grey();
}
switch (entry.state) {
case ADSBAgeState::Invalid:
case ADSBAgeState::Current:
entry_string = "";
target_color = Color::green();
break;
case ADSBAgeState::Recent:
entry_string = STR_COLOR_LIGHT_GREY;
target_color = Color::light_grey();
break;
default:
entry_string = STR_COLOR_DARK_GREY;
target_color = Color::grey();
};
entry_string +=
(entry.callsign[0] != ' ' ? entry.callsign + " " : entry.icaoStr + " ") +
(entry.callsign.empty() ? entry.icao_str + " " : entry.callsign + " ") +
to_string_dec_uint((unsigned int)(entry.pos.altitude / 100), 4) +
to_string_dec_uint((unsigned int)entry.velo.speed, 4) +
to_string_dec_uint((unsigned int)(entry.amp >> 9), 4) + " " +
@@ -72,47 +82,60 @@ void RecentEntriesTable<AircraftRecentEntries>::draw(
entry_string);
if (entry.pos.valid)
painter.draw_bitmap(target_rect.location() + Point(8 * 8, 0), bitmap_target, target_color, style.background);
painter.draw_bitmap(target_rect.location() + Point(8 * 8, 0),
bitmap_target, target_color, style.background);
}
void ADSBLogger::log_str(std::string& logline) {
log_file.write_entry(logline);
/* ADSBLogger ********************************************/
void ADSBLogger::log(const ADSBLogEntry& log_entry) {
std::string log_line;
log_line.reserve(100);
log_line = log_entry.raw_data;
log_line += "ICAO:" + log_entry.icao;
if (!log_entry.callsign.empty())
log_line += " " + log_entry.callsign;
if (log_entry.pos.valid)
log_line += " Alt:" + to_string_dec_int(log_entry.pos.altitude) +
" Lat:" + to_string_decimal(log_entry.pos.latitude, 7) +
" Lon:" + to_string_decimal(log_entry.pos.longitude, 7);
if (log_entry.vel.valid)
log_line += " Type:" + to_string_dec_uint(log_entry.vel_type) +
" Hdg:" + to_string_dec_uint(log_entry.vel.heading) +
" 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);
}
// Aircraft Details
void ADSBRxAircraftDetailsView::focus() {
button_close.focus();
}
ADSBRxAircraftDetailsView::~ADSBRxAircraftDetailsView() {
on_close_();
}
/* ADSBRxAircraftDetailsView *****************************/
ADSBRxAircraftDetailsView::ADSBRxAircraftDetailsView(
NavigationView& nav,
const AircraftRecentEntry& entry,
const std::function<void(void)> on_close)
: entry_copy(entry),
on_close_(on_close) {
add_children({&labels,
&text_icao_address,
&text_registration,
&text_manufacturer,
&text_model,
&text_type,
&text_number_of_engines,
&text_engine_type,
&text_owner,
&text_operator,
&button_close});
const AircraftRecentEntry& entry) {
add_children(
{&labels,
&text_icao_address,
&text_registration,
&text_manufacturer,
&text_model,
&text_type,
&text_number_of_engines,
&text_engine_type,
&text_owner,
&text_operator,
&button_close});
std::unique_ptr<ADSBLogger> logger{};
icao_code = to_string_hex(entry_copy.ICAO_address, 6);
text_icao_address.set(to_string_hex(entry_copy.ICAO_address, 6));
text_icao_address.set(entry.icao_str);
// Try getting the aircraft information from icao24.db
return_code = db.retrieve_aircraft_record(&aircraft_record, icao_code);
database db{};
database::AircraftDBRecord aircraft_record;
auto return_code = db.retrieve_aircraft_record(&aircraft_record, entry.icao_str);
switch (return_code) {
case DATABASE_RECORD_FOUND:
text_registration.set(aircraft_record.aircraft_registration);
@@ -120,6 +143,7 @@ ADSBRxAircraftDetailsView::ADSBRxAircraftDetailsView(
text_model.set(aircraft_record.aircraft_model);
text_owner.set(aircraft_record.aircraft_owner);
text_operator.set(aircraft_record.aircraft_operator);
// Check for ICAO type, e.g. L2J
if (strlen(aircraft_record.icao_type) == 3) {
switch (aircraft_record.icao_type[0]) {
@@ -142,7 +166,8 @@ ADSBRxAircraftDetailsView::ADSBRxAircraftDetailsView(
text_type.set("Tilt-wing aircraft");
break;
}
text_number_of_engines.set(std::string(1, aircraft_record.icao_type[1]));
text_number_of_engines.set(std::string{1, aircraft_record.icao_type[1]});
switch (aircraft_record.icao_type[2]) {
case 'P':
text_engine_type.set("Piston engine");
@@ -157,8 +182,8 @@ ADSBRxAircraftDetailsView::ADSBRxAircraftDetailsView(
text_engine_type.set("Electric engine");
break;
}
}
// Check for ICAO type designator
else if (strlen(aircraft_record.icao_type) == 4) {
if (strcmp(aircraft_record.icao_type, "SHIP") == 0)
@@ -179,349 +204,401 @@ ADSBRxAircraftDetailsView::ADSBRxAircraftDetailsView(
text_type.set("Powered parachute/paraplane");
}
break;
case DATABASE_RECORD_NOT_FOUND:
// Defaults should be filled by the constructor
break;
case DATABASE_NOT_FOUND:
text_manufacturer.set("No icao24.db file");
break;
}
button_close.on_select = [&nav](Button&) {
ac_details_view_active = false;
nav.pop();
};
};
}
// End of Aicraft details
void ADSBRxAircraftDetailsView::focus() {
button_close.focus();
}
/* ADSBRxDetailsView *************************************/
ADSBRxDetailsView::ADSBRxDetailsView(
NavigationView& nav,
const AircraftRecentEntry& entry)
: entry_(entry) {
add_children(
{&labels,
&text_icao_address,
&text_callsign,
&text_last_seen,
&text_airline,
&text_country,
&text_infos,
&text_info2,
&text_frame_pos_even,
&text_frame_pos_odd,
&button_aircraft_details,
&button_see_map});
text_icao_address.set(entry_.icao_str);
button_aircraft_details.on_select = [this, &nav](Button&) {
aircraft_details_view_ = nav.push<ADSBRxAircraftDetailsView>(entry_);
nav.set_on_pop([this]() {
aircraft_details_view_ = nullptr;
refresh_ui();
});
};
button_see_map.on_select = [this, &nav](Button&) {
geomap_view_ = nav.push<GeoMapView>(
get_map_tag(entry_),
entry_.pos.altitude,
GeoPos::alt_unit::FEET,
GeoPos::spd_unit::MPH,
entry_.pos.latitude,
entry_.pos.longitude,
entry_.velo.heading);
nav.set_on_pop([this]() {
geomap_view_ = nullptr;
refresh_ui();
});
};
refresh_ui();
};
void ADSBRxDetailsView::focus() {
button_see_map.focus();
}
void ADSBRxDetailsView::update(const AircraftRecentEntry& entry) {
entry_copy = entry;
uint32_t age = entry_copy.age;
entry_ = entry;
if (aircraft_details_view_) {
// AC Details view is showing, nothing to update.
} else if (geomap_view_) {
// Map is showing, update the current item.
geomap_view_->update_tag(get_map_tag(entry_));
geomap_view_->update_position(entry.pos.latitude, entry.pos.longitude, entry.velo.heading, entry.pos.altitude, entry.velo.speed);
} else {
// Details is showing, update details.
refresh_ui();
}
}
void ADSBRxDetailsView::clear_map_markers() {
if (geomap_view_)
geomap_view_->clear_markers();
}
bool ADSBRxDetailsView::add_map_marker(const AircraftRecentEntry& entry) {
// Map not shown, can't add markers.
if (!geomap_view_)
return false;
GeoMarker marker{};
marker.lon = entry.pos.longitude;
marker.lat = entry.pos.latitude;
marker.angle = entry.velo.heading;
marker.tag = get_map_tag(entry);
auto markerStored = geomap_view_->store_marker(marker);
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() {
// 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;
if (age < 60)
text_last_seen.set(to_string_dec_uint(age) + " seconds ago");
else
text_last_seen.set(to_string_dec_uint(age / 60) + " minutes ago");
text_infos.set(entry_copy.info_string);
if (entry_copy.velo.heading < 360 && entry_copy.velo.speed >= 0) { // I don't like this but...
text_info2.set("Hdg:" + to_string_dec_uint(entry_copy.velo.heading) + " Spd:" + to_string_dec_int(entry_copy.velo.speed));
} else {
text_info2.set("");
}
text_frame_pos_even.set(to_string_hex_array(entry_copy.frame_pos_even.get_raw_data(), 14));
text_frame_pos_odd.set(to_string_hex_array(entry_copy.frame_pos_odd.get_raw_data(), 14));
text_callsign.set(entry_.callsign);
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)
text_info2.set("Hdg:" + to_string_dec_uint(entry_.velo.heading) +
" Spd:" + to_string_dec_int(entry_.velo.speed) + str_sil);
else
text_info2.set(str_sil);
if (send_updates) {
geomap_view->update_tag(trimr(entry.callsign[0] != ' ' ? entry.callsign : to_string_hex(entry.ICAO_address, 6)));
geomap_view->update_position(entry_copy.pos.latitude, entry_copy.pos.longitude, entry_copy.velo.heading, entry_copy.pos.altitude);
}
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));
}
ADSBRxDetailsView::~ADSBRxDetailsView() {
ac_details_view_active = false;
on_close_();
}
ADSBRxDetailsView::ADSBRxDetailsView(
NavigationView& nav,
const AircraftRecentEntry& entry,
const std::function<void(void)> on_close)
: entry_copy(entry),
on_close_(on_close) {
add_children({&labels,
&text_icao_address,
&text_callsign,
&text_last_seen,
&text_airline,
&text_country,
&text_infos,
&text_info2,
&text_frame_pos_even,
&text_frame_pos_odd,
&button_aircraft_details,
&button_see_map});
std::unique_ptr<ADSBLogger> logger{};
update(entry_copy);
// The following won't (shouldn't !) change for a given airborne aircraft
// Try getting the airline's name from airlines.db
airline_code = entry_copy.callsign.substr(0, 3);
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_callsign.set(entry_copy.callsign);
text_icao_address.set(to_string_hex(entry_copy.ICAO_address, 6));
button_aircraft_details.on_select = [this, &nav](Button&) {
ac_details_view_active = true;
aircraft_details_view = nav.push<ADSBRxAircraftDetailsView>(entry_copy, [this]() { send_updates = false; });
send_updates = false;
};
button_see_map.on_select = [this, &nav](Button&) {
if (!send_updates) { // Prevent recursively launching the map
geomap_view = nav.push<GeoMapView>(
trimr(entry_copy.callsign[0] != ' ' ? entry_copy.callsign : entry_copy.icaoStr),
entry_copy.pos.altitude,
GeoPos::alt_unit::FEET,
entry_copy.pos.latitude,
entry_copy.pos.longitude,
entry_copy.velo.heading,
[this]() {
send_updates = false;
});
send_updates = true;
}
};
};
void ADSBRxView::focus() {
field_vga.focus();
}
ADSBRxView::~ADSBRxView() {
rtc_time::signal_tick_second -= signal_token_tick_second;
receiver_model.disable();
baseband::shutdown();
}
AircraftRecentEntry ADSBRxView::find_or_create_entry(uint32_t ICAO_address) {
auto it = find(recent, ICAO_address);
// If not found
if (it == std::end(recent)) {
recent.emplace_front(ICAO_address); // Add it
it = find(recent, ICAO_address); // Find it again
}
return *it;
}
void ADSBRxView::replace_entry(AircraftRecentEntry& entry) {
uint32_t ICAO_address = entry.ICAO_address;
std::replace_if(
recent.begin(), recent.end(),
[ICAO_address](const AircraftRecentEntry& compEntry) { return ICAO_address == compEntry.ICAO_address; },
entry);
}
void ADSBRxView::remove_old_entries() {
auto it = recent.rbegin();
auto end = recent.rend();
while (it != end) {
if (it->age_state >= 4) {
std::advance(it, 1);
recent.erase(it.base());
} else {
break; // stop looking because the list is sorted
}
}
}
void ADSBRxView::sort_entries_by_state() {
// Sorting List pn age_state using lambda function as comparator
recent.sort([](const AircraftRecentEntry& left, const AircraftRecentEntry& right) { return (left.age_state < right.age_state); });
}
void ADSBRxView::on_frame(const ADSBFrameMessage* message) {
logger = std::make_unique<ADSBLogger>();
rtc::RTC datetime;
std::string callsign;
std::string str_info;
std::string logentry;
auto frame = message->frame;
uint32_t ICAO_address = frame.get_ICAO_address();
if (frame.check_CRC() && ICAO_address) {
rtcGetTime(&RTCD1, &datetime);
auto entry = find_or_create_entry(ICAO_address);
frame.set_rx_timestamp(datetime.minute() * 60 + datetime.second());
entry.reset_age();
if (entry.hits == 0) {
entry.amp = message->amp; // Store amplitude on first hit
} else {
entry.amp = ((entry.amp * 15) + message->amp) >> 4; // Update smoothed amplitude on updates
}
entry.inc_hit();
if (logger) {
logentry += to_string_hex_array(frame.get_raw_data(), 14) + " ";
logentry += "ICAO:" + entry.icaoStr + " ";
}
if (frame.get_DF() == DF_ADSB) {
uint8_t msg_type = frame.get_msg_type();
uint8_t msg_sub = frame.get_msg_sub();
uint8_t* raw_data = frame.get_raw_data();
// 4: // surveillance, altitude reply
if ((msg_type >= AIRCRAFT_ID_L) && (msg_type <= AIRCRAFT_ID_H)) {
callsign = decode_frame_id(frame);
entry.set_callsign(callsign);
if (logger) {
logentry += callsign + " ";
}
}
// 9:
// 18: { // Extended squitter/non-transponder
// 21: // Comm-B, identity reply
// 20: // Comm-B, altitude reply
else if (((msg_type >= AIRBORNE_POS_BARO_L) && (msg_type <= AIRBORNE_POS_BARO_H)) ||
((msg_type >= AIRBORNE_POS_GPS_L) && (msg_type <= AIRBORNE_POS_GPS_H))) {
entry.set_frame_pos(frame, raw_data[6] & 4);
if (entry.pos.valid) {
str_info = "Alt:" + to_string_dec_int(entry.pos.altitude) +
" Lat:" + to_string_decimal(entry.pos.latitude, 2) +
" Lon:" + to_string_decimal(entry.pos.longitude, 2);
entry.set_info_string(str_info);
if (logger) {
// printing the coordinates in the log file with more
// resolution, as we are not constrained by screen
// real estate there:
std::string log_info = "Alt:" + to_string_dec_int(entry.pos.altitude) +
" Lat:" + to_string_decimal(entry.pos.latitude, 7) +
" Lon:" + to_string_decimal(entry.pos.longitude, 7);
logentry += log_info + " ";
}
}
} else if (msg_type == AIRBORNE_VEL && msg_sub >= VEL_GND_SUBSONIC && msg_sub <= VEL_AIR_SUPERSONIC) {
entry.set_frame_velo(frame);
if (logger) {
logger->append(LOG_ROOT_DIR "/ADSB.TXT");
logentry += "Type:" + to_string_dec_uint(msg_sub) +
" Hdg:" + to_string_dec_uint(entry.velo.heading) +
" Spd: " + to_string_dec_int(entry.velo.speed);
}
}
replace_entry(entry);
} // frame.get_DF() == DF_ADSB
if (logger) {
logger->append(LOG_ROOT_DIR "/ADSB.TXT");
// will log each frame in format:
// 20171103100227 8DADBEEFDEADBEEFDEADBEEFDEADBEEF ICAO:nnnnnn callsign Alt:nnnnnn Latnnn.nn Lonnnn.nn
logger->log_str(logentry);
}
}
}
void ADSBRxView::on_tick_second() {
if (recent.size() <= 16) { // Not many entries update everything (16 is one screen full)
updateDetailsAndMap(1);
updateRecentEntries();
} else if (updateState == 0) { // Even second
updateState = 1;
updateDetailsAndMap(2);
} else { // Odd second only performed when there are many entries
updateState = 0;
updateRecentEntries();
}
}
void ADSBRxView::updateDetailsAndMap(int ageStep) {
ui::GeoMarker marker;
bool storeNewMarkers = false;
// NB: Temporarily pausing updates in rtc_timer_tick context when viewing AC Details screen (kludge for some Guru faults)
// TODO: More targeted blocking of updates in rtc_timer_tick when ADSB processes are running
if (ac_details_view_active)
return;
// Sort and truncate the entries, grouped, newest group first
sort_entries_by_state();
truncate_entries(recent);
remove_old_entries();
// Calculate if it is time to update markers
if (send_updates && details_view && details_view->geomap_view) {
ticksSinceMarkerRefresh += ageStep;
if (ticksSinceMarkerRefresh >= MARKER_UPDATE_SECONDS) { // Update other aircraft every few seconds
storeNewMarkers = true;
ticksSinceMarkerRefresh = 0;
}
} else {
ticksSinceMarkerRefresh = MARKER_UPDATE_SECONDS; // Send the markers as soon as the geoview exists
}
// Increment age, and pass updates to the details and map
const bool otherMarkersCanBeSent = send_updates && storeNewMarkers && details_view && details_view->geomap_view; // Save retesting all of this
MapMarkerStored markerStored = MARKER_NOT_STORED;
if (otherMarkersCanBeSent) {
details_view->geomap_view->clear_markers();
}
// Loop through all entries
for (auto& entry : recent) {
entry.inc_age(ageStep);
// Only if there is a details view
if (send_updates && details_view) {
if (entry.key() == detailed_entry_key) // Check if the ICAO address match
{
details_view->update(entry);
}
// Store if the view is present and the list isn't full
// Note -- Storing the selected entry too, in case map panning occurs
if (otherMarkersCanBeSent && (markerStored != MARKER_LIST_FULL) && entry.pos.valid && (entry.age_state <= 2)) {
marker.lon = entry.pos.longitude;
marker.lat = entry.pos.latitude;
marker.angle = entry.velo.heading;
marker.tag = trimr(entry.callsign[0] != ' ' ? entry.callsign : entry.icaoStr);
markerStored = details_view->geomap_view->store_marker(marker);
}
}
} // Loop through all entries, if only to update the age
}
void ADSBRxView::updateRecentEntries() {
// Redraw the list of aircraft
if (!send_updates) {
recent_entries_view.set_dirty();
}
}
/* ADSBRxView ********************************************/
ADSBRxView::ADSBRxView(NavigationView& nav) {
baseband::run_image(portapack::spi_flash::image_tag_adsb_rx);
add_children({&labels,
&field_lna,
&field_vga,
&field_rf_amp,
&rssi,
&recent_entries_view});
add_children(
{&labels,
&field_lna,
&field_vga,
&field_rf_amp,
&rssi,
&recent_entries_view,
&status_frame,
&status_good_frame,
&field_volume});
recent_entries_view.set_parent_rect({0, 16, 240, 272});
recent_entries_view.on_select = [this, &nav](const AircraftRecentEntry& entry) {
detailed_entry_key = entry.key();
details_view = nav.push<ADSBRxDetailsView>(
entry,
[this]() {
send_updates = false;
});
send_updates = true;
detail_key = entry.key();
details_view = nav.push<ADSBRxDetailsView>(entry);
nav.set_on_pop([this]() {
detail_key = AircraftRecentEntry::invalid_key;
details_view = nullptr;
});
};
signal_token_tick_second = rtc_time::signal_tick_second += [this]() {
on_tick_second();
};
baseband::set_adsb();
logger = std::make_unique<ADSBLogger>();
logger->append(logs_dir / u"ADSB.TXT");
receiver_model.enable();
baseband::set_adsb();
if (pmem::beep_on_packets()) {
audio::set_rate(audio::Rate::Hz_24000);
audio::output::start();
}
}
ADSBRxView::~ADSBRxView() {
rtc_time::signal_tick_second -= signal_token_tick_second;
audio::output::stop();
receiver_model.disable();
baseband::shutdown();
}
void ADSBRxView::focus() {
field_vga.focus();
}
void ADSBRxView::on_frame(const ADSBFrameMessage* message) {
auto frame = message->frame;
uint32_t ICAO_address = frame.get_ICAO_address();
status_frame.toggle();
// Bad frame, skip it.
if (!frame.check_CRC() || ICAO_address == 0)
return;
ADSBLogEntry log_entry;
status_good_frame.toggle();
rtc::RTC datetime;
rtcGetTime(&RTCD1, &datetime); // Reading RTC directly to avoid DST transitions when calculating delta
frame.set_rx_timestamp(datetime.minute() * 60 + datetime.second());
// NB: Reference to update entry in-place.
auto& entry = find_or_create_entry(ICAO_address);
entry.inc_hit();
entry.reset_age();
// Store smoothed amplitude on updates.
entry.amp = entry.hits == 0
? message->amp
: ((entry.amp * 15) + message->amp) >> 4;
log_entry.raw_data = to_string_hex_array(frame.get_raw_data(), 14);
log_entry.icao = entry.icao_str;
if (frame.get_DF() == DF_ADSB) {
uint8_t msg_type = frame.get_msg_type();
uint8_t msg_sub = frame.get_msg_sub();
uint8_t* raw_data = frame.get_raw_data();
// 4: // surveillance, altitude reply
if ((msg_type >= AIRCRAFT_ID_L) && (msg_type <= AIRCRAFT_ID_H)) {
entry.set_callsign(decode_frame_id(frame));
log_entry.callsign = entry.callsign;
}
// 9:
// 18: // Extended squitter/non-transponder
// 21: // Comm-B, identity reply
// 20: // Comm-B, altitude reply
else if (((msg_type >= AIRBORNE_POS_BARO_L) && (msg_type <= AIRBORNE_POS_BARO_H)) ||
((msg_type >= AIRBORNE_POS_GPS_L) && (msg_type <= AIRBORNE_POS_GPS_H))) {
entry.set_frame_pos(frame, raw_data[6] & 4);
log_entry.pos = entry.pos;
if (entry.pos.valid) {
std::string str_info =
"Alt:" + to_string_dec_int(entry.pos.altitude) +
" Lat:" + to_string_decimal(entry.pos.latitude, 2) +
" Lon:" + to_string_decimal(entry.pos.longitude, 2);
entry.set_info_string(std::move(str_info));
}
} else if (msg_type == AIRBORNE_VEL && msg_sub >= VEL_GND_SUBSONIC && msg_sub <= VEL_AIR_SUPERSONIC) {
entry.set_frame_velo(frame);
log_entry.vel = entry.velo;
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);
if (pmem::beep_on_packets()) {
baseband::request_audio_beep(1000, 24000, 60);
}
}
void ADSBRxView::on_tick_second() {
status_frame.reset();
status_good_frame.reset();
++tick_count;
++ticks_since_marker_refresh;
// Small list, update all at once.
if (recent.size() <= max_update_entries) {
update_recent_entries(/*age_delta*/ 1);
refresh_ui();
return;
}
// Too many items, split update work into two phases:
// Entry maintenance and UI update.
if ((tick_count & 1) == 0)
update_recent_entries(/*age_delta*/ 2);
else
refresh_ui();
}
void ADSBRxView::refresh_ui() {
// There's only one ticks handler, but 3 UIs that need to be updated.
// This code will dispatch updates to the currently active view.
if (details_view) {
// The details view is showing, forward updates to that UI.
bool current_updated = false;
bool map_needs_update = false;
if (details_view->map_active()) {
// Is it time to clear and refresh the map's markers?
if (ticks_since_marker_refresh >= MARKER_UPDATE_SECONDS) {
map_needs_update = true;
ticks_since_marker_refresh = 0;
details_view->clear_map_markers();
}
} else {
// Refresh map immediately once active.
ticks_since_marker_refresh = MARKER_UPDATE_SECONDS;
}
// Process the entries list.
for (const auto& entry : recent) {
// Found the entry being shown in details view. Update it.
if (entry.key() == detail_key) {
details_view->update(entry);
current_updated = true;
}
// NB: current entry also gets a marker so it shows up if map is panned.
if (map_needs_update && entry.pos.valid && entry.state <= ADSBAgeState::Recent) {
map_needs_update = details_view->add_map_marker(entry);
}
// Any work left to do?
if (current_updated && !map_needs_update)
break;
}
} else {
// Main page is the top view. Redraw the entries view.
recent_entries_view.set_dirty();
}
}
void ADSBRxView::update_recent_entries(int age_delta) {
for (auto& entry : recent)
entry.inc_age(age_delta);
// Sort and truncate the entries, grouped by state, newest first.
sort_entries_by_state();
truncate_entries(recent);
remove_expired_entries();
}
AircraftRecentEntry& ADSBRxView::find_or_create_entry(uint32_t ICAO_address) {
// Find ...
auto it = find(recent, ICAO_address);
if (it != recent.end())
return *it;
// ... or Create.
return recent.emplace_front(ICAO_address);
}
void ADSBRxView::sort_entries_by_state() {
recent.sort([](const auto& left, const auto& right) {
return (left.state < right.state);
});
}
void ADSBRxView::remove_expired_entries() {
// NB: Assumes entried are sorted with oldest last.
auto it = recent.rbegin();
auto end = recent.rend();
// Find the first !expired entry from the back.
while (it != end) {
if (it->state != ADSBAgeState::Expired)
break;
std::advance(it, 1);
}
// Remove the range of expired items.
recent.erase(it.base(), recent.end());
}
} /* namespace ui */
+156 -103
View File
@@ -1,6 +1,8 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2017 Furrtek
* Copyright (C) 2023 Kyle Reed
* Copyright (C) 2024 Mark Thompson
*
* This file is part of PortaPack.
*
@@ -21,29 +23,23 @@
*/
#include "ui.hpp"
#include "ui_receiver.hpp"
#include "ui_geomap.hpp"
#include "string_format.hpp"
#include "file.hpp"
#include "database.hpp"
#include "recent_entries.hpp"
#include "log_file.hpp"
#include "adsb.hpp"
#include "message.hpp"
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "crc.hpp"
#include "database.hpp"
#include "file.hpp"
#include "log_file.hpp"
#include "message.hpp"
#include "radio_state.hpp"
#include "recent_entries.hpp"
#include "string_format.hpp"
using namespace adsb;
namespace ui {
#define ADSB_CURRENT 10 // Seconds
#define ADSB_RECENT 30 // Seconds
#define ADSB_REMOVE 300 // Used for removing old entries
#define AIRCRAFT_ID_L 1 // aircraft ID message type (lowest type id)
#define AIRCRAFT_ID_H 4 // aircraft ID message type (highest type id)
@@ -58,6 +54,8 @@ namespace ui {
#define AIRBORNE_POS_GPS_L 20 // airborne position (lowest 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_H 31 // reserved for other uses
@@ -66,8 +64,26 @@ namespace ui {
#define VEL_AIR_SUBSONIC 3
#define VEL_AIR_SUPERSONIC 4
#define O_E_FRAME_TIMEOUT 20 // timeout between odd and even frames
#define O_E_FRAME_TIMEOUT 20 // timeout between odd and even frames
#define MARKER_UPDATE_SECONDS 5 // "other" map marker redraw interval
/* Thresholds (in seconds) that define the transition between ages. */
struct ADSBAgeLimit {
static constexpr int Current = 10;
static constexpr int Recent = 30;
static constexpr int Expired = 300;
};
/* Age states used for sorting and drawing recent entries. */
enum class ADSBAgeState : uint8_t {
Invalid,
Current,
Recent,
Old,
Expired,
};
/* Data extracted from ADSB frames. */
struct AircraftRecentEntry {
using Key = uint32_t;
@@ -76,30 +92,32 @@ struct AircraftRecentEntry {
uint32_t ICAO_address{};
uint16_t hits{0};
uint16_t age_state{1};
uint32_t age{0};
ADSBAgeState state{ADSBAgeState::Invalid};
uint32_t age{0}; // In seconds
uint32_t amp{0};
adsb_pos pos{false, 0, 0, 0};
adsb_vel velo{false, 0, 999, 0};
ADSBFrame frame_pos_even{};
ADSBFrame frame_pos_odd{};
std::string icaoStr{" "};
std::string callsign{" "};
std::string info_string{""};
std::string icao_str{};
std::string callsign{};
std::string info_string{};
AircraftRecentEntry(
const uint32_t ICAO_address)
uint8_t sil{0}; // Surveillance integrity level
AircraftRecentEntry(const uint32_t ICAO_address)
: ICAO_address{ICAO_address} {
this->icaoStr = to_string_hex(ICAO_address, 6);
this->icao_str = to_string_hex(ICAO_address, 6);
}
/* RecentEntries helpers expect a "key" on every item. */
Key key() const {
return ICAO_address;
}
void set_callsign(std::string& new_callsign) {
callsign = new_callsign;
void set_callsign(std::string new_callsign) {
callsign = std::move(new_callsign);
}
void inc_hit() {
@@ -122,8 +140,8 @@ struct AircraftRecentEntry {
velo = decode_frame_velo(frame);
}
void set_info_string(std::string& new_info_string) {
info_string = new_info_string;
void set_info_string(std::string new_info_string) {
info_string = std::move(new_info_string);
}
void reset_age() {
@@ -132,59 +150,60 @@ struct AircraftRecentEntry {
void inc_age(int delta) {
age += delta;
if (age < ADSB_CURRENT) {
age_state = pos.valid ? 0 : 1;
} else if (age < ADSB_RECENT) {
age_state = 2;
} else if (age < ADSB_REMOVE) {
age_state = 3;
} else {
age_state = 4;
}
if (age < ADSBAgeLimit::Current)
state = pos.valid ? ADSBAgeState::Invalid
: ADSBAgeState::Current;
else if (age < ADSBAgeLimit::Recent)
state = ADSBAgeState::Recent;
else if (age < ADSBAgeLimit::Expired)
state = ADSBAgeState::Old;
else
state = ADSBAgeState::Expired;
}
};
// NB: uses std::list underneath so assuming refs are NOT invalidated.
using AircraftRecentEntries = RecentEntries<AircraftRecentEntry>;
/* Holds data for logging. */
struct ADSBLogEntry {
std::string raw_data{};
std::string icao{};
std::string callsign{};
adsb_pos pos{};
adsb_vel vel{};
uint8_t vel_type{};
uint8_t sil{};
};
// TODO: Make logging optional.
/* Logs entries to a log file. */
class ADSBLogger {
public:
Optional<File::Error> append(const std::filesystem::path& filename) {
return log_file.append(filename);
}
void log_str(std::string& logline);
void log(const ADSBLogEntry& log_entry);
private:
LogFile log_file{};
};
/* Shows detailed information about an aircraft. */
class ADSBRxAircraftDetailsView : public View {
public:
ADSBRxAircraftDetailsView(NavigationView&, const AircraftRecentEntry& entry, const std::function<void(void)> on_close);
~ADSBRxAircraftDetailsView();
ADSBRxAircraftDetailsView(const ADSBRxAircraftDetailsView&) = delete;
ADSBRxAircraftDetailsView(ADSBRxAircraftDetailsView&&) = delete;
ADSBRxAircraftDetailsView& operator=(const ADSBRxAircraftDetailsView&) = delete;
ADSBRxAircraftDetailsView& operator=(ADSBRxAircraftDetailsView&&) = delete;
ADSBRxAircraftDetailsView(
NavigationView&,
const AircraftRecentEntry& entry);
void focus() override;
void update(const AircraftRecentEntry& entry);
std::string title() const override { return "AC Details"; };
AircraftRecentEntry get_current_entry() { return entry_copy; }
std::database::AircraftDBRecord aircraft_record = {};
std::string title() const override { return "AC Details"; }
private:
AircraftRecentEntry entry_copy{0};
std::function<void(void)> on_close_{};
bool send_updates{false};
std::database db = {};
std::string icao_code = "";
int return_code = 0;
Labels labels{
{{0 * 8, 1 * 16}, "ICAO:", Color::light_grey()},
{{0 * 8, 2 * 16}, "Registration:", Color::light_grey()},
@@ -237,36 +256,37 @@ class ADSBRxAircraftDetailsView : public View {
"Back"};
};
/* Shows detailed information about an aircraft's flight. */
class ADSBRxDetailsView : public View {
public:
ADSBRxDetailsView(NavigationView&, const AircraftRecentEntry& entry, const std::function<void(void)> on_close);
~ADSBRxDetailsView();
ADSBRxDetailsView(NavigationView&, const AircraftRecentEntry& entry);
ADSBRxDetailsView(const ADSBRxDetailsView&) = delete;
ADSBRxDetailsView(ADSBRxDetailsView&&) = delete;
ADSBRxDetailsView& operator=(const ADSBRxDetailsView&) = delete;
ADSBRxDetailsView& operator=(ADSBRxDetailsView&&) = delete;
void focus() override;
void update(const AircraftRecentEntry& entry);
std::string title() const override { return "Details"; };
/* Calls forwarded to map view if shown. */
bool map_active() const { return geomap_view_; }
void clear_map_markers();
/* Adds a marker for the entry to the map. Returns true on success. */
bool add_map_marker(const AircraftRecentEntry& entry);
AircraftRecentEntry get_current_entry() { return entry_copy; }
std::database::AirlinesDBRecord airline_record = {};
GeoMapView* geomap_view{nullptr};
std::string title() const override { return "Details"; }
private:
AircraftRecentEntry entry_copy{0};
std::function<void(void)> on_close_{};
ADSBRxAircraftDetailsView* aircraft_details_view{nullptr};
bool send_updates{false};
std::database db = {};
std::string airline_code = "";
int return_code = 0;
void refresh_ui();
void on_gps(const GPSPosDataMessage* msg);
void on_orientation(const OrientationDataMessage* msg);
GeoMapView* geomap_view_{nullptr};
ADSBRxAircraftDetailsView* aircraft_details_view_{nullptr};
// NB: Keeping a copy so that it doesn't end up dangling
// if removed from the recent entries list.
AircraftRecentEntry entry_{AircraftRecentEntry::invalid_key};
bool airline_checked{false};
Labels labels{
{{0 * 8, 1 * 16}, "ICAO:", Color::light_grey()},
@@ -319,8 +339,22 @@ class ADSBRxDetailsView : public View {
Button button_see_map{
{16 * 8, 9 * 16, 12 * 8, 3 * 16},
"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. */
class ADSBRxView : public View {
public:
ADSBRxView(NavigationView& nav);
@@ -332,46 +366,52 @@ class ADSBRxView : public View {
ADSBRxView& operator=(ADSBRxView&&) = delete;
void focus() override;
std::string title() const override { return "ADS-B RX"; };
void replace_entry(AircraftRecentEntry& entry);
void remove_old_entries();
AircraftRecentEntry find_or_create_entry(uint32_t ICAO_address);
void sort_entries_by_state();
private:
RxRadioState radio_state_{
1090000000 /* frequency */,
2500000 /* bandwidth */,
2000000 /* sampling rate */,
1'090'000'000 /* frequency */,
2'500'000 /* bandwidth */,
2'000'000 /* sampling rate */,
ReceiverModel::Mode::SpectrumAnalysis};
app_settings::SettingsManager settings_{
"rx_adsb", app_settings::Mode::RX};
std::unique_ptr<ADSBLogger> logger{};
/* Event Handlers */
void on_frame(const ADSBFrameMessage* message);
void on_tick_second();
int updateState = {0};
void updateRecentEntries();
void updateDetailsAndMap(int ageStep);
#define MARKER_UPDATE_SECONDS (5)
int ticksSinceMarkerRefresh{MARKER_UPDATE_SECONDS - 1};
const RecentEntriesColumns columns{{{"ICAO/Call", 9},
{"Lvl", 3},
{"Spd", 3},
{"Amp", 3},
{"Hit", 3},
{"Age", 4}}};
AircraftRecentEntries recent{};
RecentEntriesView<RecentEntries<AircraftRecentEntry>> recent_entries_view{columns, recent};
void refresh_ui();
SignalToken signal_token_tick_second{};
uint8_t tick_count = 0;
uint16_t ticks_since_marker_refresh{MARKER_UPDATE_SECONDS};
/* Max number of entries that can be updated in a single pass.
* 16 is one screen of recent entries. */
static constexpr uint8_t max_update_entries = 16;
/* Recent Entries */
const RecentEntriesColumns columns{
{{"ICAO/Call", 9},
{"Lvl", 3},
{"Spd", 3},
{"Amp", 3},
{"Hit", 3},
{"Age", 4}}};
AircraftRecentEntries recent{};
RecentEntriesView<AircraftRecentEntries> recent_entries_view{columns, recent};
/* Entry Management */
void update_recent_entries(int age_delta);
AircraftRecentEntry& find_or_create_entry(uint32_t ICAO_address);
void sort_entries_by_state();
void remove_expired_entries();
/* The key of the entry in the details view if shown. */
AircraftRecentEntry::Key detail_key{AircraftRecentEntry::invalid_key};
ADSBRxDetailsView* details_view{nullptr};
uint32_t detailed_entry_key{0};
bool send_updates{false};
Labels labels{
{{0 * 8, 0 * 8}, "LNA: VGA: AMP:", Color::light_grey()}};
@@ -386,9 +426,22 @@ class ADSBRxView : public View {
{18 * 8, 0 * 16}};
RSSI rssi{
{20 * 8, 4, 10 * 8, 8},
{20 * 8, 4, 7 * 8, 8},
};
ActivityDot status_frame{
{27 * 8 + 2, 5, 2, 2},
Color::white(),
};
ActivityDot status_good_frame{
{27 * 8 + 2, 9, 2, 2},
Color::green(),
};
AudioVolumeField field_volume{
{28 * 8, 0 * 16}};
MessageHandlerRegistration message_handler_frame{
Message::ID::ADSBFrame,
[this](Message* const p) {
+3 -1
View File
@@ -85,10 +85,12 @@ ADSBPositionView::ADSBPositionView(
nav.push<GeoMapView>(
geopos.altitude(),
GeoPos::alt_unit::FEET,
GeoPos::spd_unit::HIDDEN,
geopos.lat(),
geopos.lon(),
[this](int32_t altitude, float lat, float lon) {
[this](int32_t altitude, float lat, float lon, int32_t speed) {
geopos.set_altitude(altitude);
geopos.set_speed(speed);
geopos.set_lat(lat);
geopos.set_lon(lon);
});
+2 -1
View File
@@ -58,7 +58,8 @@ class ADSBPositionView : public OptionTabView {
private:
GeoPos geopos{
{0, 2 * 16},
GeoPos::FEET};
GeoPos::FEET,
GeoPos::HIDDEN};
Button button_set_map{
{8 * 8, 6 * 16, 14 * 8, 2 * 16},
+7 -3
View File
@@ -27,6 +27,7 @@
#include "baseband_api.hpp"
#include "string_format.hpp"
#include "portapack_persistent_memory.hpp"
#include "file_path.hpp"
using namespace portapack;
@@ -101,6 +102,8 @@ APRSRxView::APRSRxView(NavigationView& nav, Rect parent_rect)
field_frequency.set_value(145175000);
} else if (i == 3) {
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>();
if (logger)
logger->append(LOG_ROOT_DIR "/APRS.TXT");
logger->append(logs_dir / u"APRS.TXT");
baseband::set_aprs(1200);
@@ -258,7 +261,7 @@ void APRSTableView::on_pkt(const APRSPacketMessage* message) {
std::string source_formatted = packet.get_source_formatted();
std::string info_string = packet.get_stream_text();
rtcGetTime(&RTCD1, &datetime);
rtc_time::now(datetime);
auto& entry = ::on_packet(recent, packet.get_source());
entry.reset_age();
entry.inc_hit();
@@ -317,7 +320,7 @@ void APRSDetailsView::update() {
}
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() {
@@ -339,6 +342,7 @@ APRSDetailsView::APRSDetailsView(
entry_copy.source_formatted,
0, // entry_copy.pos.altitude,
GeoPos::alt_unit::FEET,
GeoPos::spd_unit::HIDDEN,
entry_copy.pos.latitude,
entry_copy.pos.longitude,
0, /*entry_copy.velo.heading,*/
+5 -3
View File
@@ -36,10 +36,11 @@
#include "log_file.hpp"
#include "utility.hpp"
#include "file_path.hpp"
class APRSLogger {
public:
Optional<File::Error> append(const std::string& filename) {
Optional<File::Error> append(const std::filesystem::path& filename) {
return log_file.append(filename);
}
@@ -221,7 +222,8 @@ class APRSRxView : public View {
{{"NA ", 0},
{"EUR", 1},
{"AUS", 2},
{"NZ ", 3}}};
{"NZ ", 3},
{"ISS", 4}}};
RxFrequencyField field_frequency{
{3 * 8, 0 * 16},
@@ -231,7 +233,7 @@ class APRSRxView : public View {
RecordView record_view{
{0 * 8, 1 * 16, 30 * 8, 1 * 16},
u"AFS_????.WAV",
u"APRS",
aprs_dir,
RecordView::FileType::WAV,
4096,
4};
+1 -1
View File
@@ -70,7 +70,7 @@ BTLERxView::BTLERxView(NavigationView& nav)
};
// Auto-configure modem for LCR RX (will be removed later)
baseband::set_btle(persistent_memory::modem_baudrate(), 8, 0, false);
baseband::set_btlerx(channel_number);
audio::set_rate(audio::Rate::Hz_24000);
audio::output::start();
+2
View File
@@ -60,6 +60,8 @@ class BTLERxView : public View {
uint8_t console_color{0};
uint32_t prev_value{0};
static constexpr uint8_t channel_number = 37;
RFAmpField field_rf_amp{
{13 * 8, 0 * 16}};
LNAGainField field_lna{
+179 -80
View File
@@ -1,5 +1,7 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2024 Mark Thompson
* Copyright (C) 2024 u-foka
*
* This file is part of PortaPack.
*
@@ -124,8 +126,8 @@ void TemperatureWidget::paint(Painter& painter) {
}
TemperatureWidget::temperature_t TemperatureWidget::temperature(const sample_t sensor_value) const {
/*It seems to be a temperature difference of 25C*/
return -40 + (sensor_value * 4.31) + 25; // max2837 datasheet temp 25ºC has sensor value: 15
// Scaling is different for MAX2837 vs MAX2839 so it's now done in the respective chip-specific module
return sensor_value;
}
std::string TemperatureWidget::temperature_str(const temperature_t temperature) const {
@@ -159,11 +161,8 @@ void TemperatureView::focus() {
/* RegistersWidget *******************************************************/
RegistersWidget::RegistersWidget(
RegistersWidgetConfig&& config,
std::function<uint32_t(const size_t register_number)>&& reader)
: Widget{},
config(std::move(config)),
reader(std::move(reader)) {
RegistersWidgetConfig&& config)
: Widget{}, config(std::move(config)), page_number(0) {
}
void RegistersWidget::update() {
@@ -179,12 +178,15 @@ void RegistersWidget::paint(Painter& painter) {
void RegistersWidget::draw_legend(const Coord left, Painter& painter) {
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 (size_t i = 0; i < config.registers_count; i += config.registers_per_row()) {
const Point offset{
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(
pos + offset,
style().invert(),
@@ -196,15 +198,16 @@ void RegistersWidget::draw_values(
const Coord left,
Painter& painter) {
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 (size_t i = 0; i < config.registers_count; i++) {
const Point offset = {
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)};
const auto value = reader(i);
const auto text = to_string_hex(value, config.value_length());
const auto text = (r >= config.registers_count) ? spaces : to_string_hex(reg_read(r), config.value_length());
painter.draw_string(
pos + offset,
style(),
@@ -212,19 +215,61 @@ void RegistersWidget::draw_values(
}
}
uint32_t RegistersWidget::reg_read(const uint32_t register_number) {
if (register_number < config.registers_count) {
switch (config.chip_type) {
case CT_PMEM:
return portapack::persistent_memory::pmem_data_word(register_number / 4) >> (register_number % 4 * 8);
case CT_RFFC5072:
return radio::debug::first_if::register_read(register_number);
case CT_MAX283X:
return radio::debug::second_if::register_read(register_number);
case CT_SI5351:
return portapack::clock_generator.read_register(register_number);
case CT_AUDIO:
return audio::debug::reg_read(register_number);
}
}
return 0xFFFF;
}
void RegistersWidget::reg_write(const uint32_t register_number, const uint32_t value) {
if (register_number < config.registers_count) {
switch (config.chip_type) {
case CT_PMEM:
break;
case CT_RFFC5072:
radio::debug::first_if::register_write(register_number, value);
break;
case CT_MAX283X:
radio::debug::second_if::register_write(register_number, value);
break;
case CT_SI5351:
portapack::clock_generator.write_register(register_number, value);
break;
case CT_AUDIO:
audio::debug::reg_write(register_number, value);
break;
}
}
}
/* RegistersView *********************************************************/
RegistersView::RegistersView(
NavigationView& nav,
const std::string& title,
RegistersWidgetConfig&& config,
std::function<uint32_t(const size_t register_number)>&& reader)
: registers_widget{std::move(config), std::move(reader)} {
RegistersWidgetConfig&& config)
: registers_widget{std::move(config)} {
add_children({
&text_title,
&registers_widget,
&button_update,
&button_done,
&labels,
&field_write_reg_num,
&field_write_data_val,
&button_write,
});
button_update.on_select = [this](Button&) {
@@ -233,16 +278,38 @@ RegistersView::RegistersView(
button_done.on_select = [&nav](Button&) { nav.pop(); };
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,
static_cast<int>(title.size()) * 8, 16});
text_title.set(title);
field_write_reg_num.on_change = [this](SymField&) {
field_write_data_val.set_value(this->registers_widget.reg_read(field_write_reg_num.to_integer()));
field_write_data_val.set_dirty();
};
const auto value = registers_widget.reg_read(0);
field_write_data_val.set_value(value);
button_write.set_style(&Styles::red);
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.update();
};
}
void RegistersView::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 ************************************************/
void ControlsSwitchesWidget::on_show() {
@@ -257,6 +324,11 @@ bool ControlsSwitchesWidget::on_key(const KeyEvent key) {
return true;
}
bool ControlsSwitchesWidget::on_encoder(const EncoderEvent delta) {
last_delta = delta;
return true;
}
void ControlsSwitchesWidget::paint(Painter& painter) {
const auto pos = screen_pos();
@@ -339,6 +411,8 @@ void ControlsSwitchesWidget::paint(Painter& painter) {
switches_event >>= 1;
}
painter.draw_string({5 * 8, 12 * 16}, Styles::light_grey, to_string_dec_int(last_delta, 3));
}
void ControlsSwitchesWidget::on_frame_sync() {
@@ -372,76 +446,115 @@ void DebugControlsView::focus() {
/* 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* si5351x = hackrf_r9 ? "Si5351A" : "Si5351C";
add_items({
{"RFFC5072", ui::Color::dark_cyan(), &bitmap_icon_peripherals_details, [&nav]() { nav.push<RegistersView>(
"RFFC5072", RegistersWidgetConfig{31, 16},
[](const size_t register_number) { return radio::debug::first_if::register_read(register_number); }); }},
{max283x, ui::Color::dark_cyan(), &bitmap_icon_peripherals_details, [&nav, max283x]() { nav.push<RegistersView>(
max283x, RegistersWidgetConfig{32, 10},
[](const size_t register_number) { return radio::debug::second_if::register_read(register_number); }); }},
{si5351x, ui::Color::dark_cyan(), &bitmap_icon_peripherals_details, [&nav, si5351x]() { nav.push<RegistersView>(
si5351x, RegistersWidgetConfig{96, 8},
[](const size_t register_number) { return portapack::clock_generator.read_register(register_number); }); }},
{audio::debug::codec_name(), ui::Color::dark_cyan(), &bitmap_icon_peripherals_details, [&nav]() { nav.push<RegistersView>(
audio::debug::codec_name(), RegistersWidgetConfig{audio::debug::reg_count(), audio::debug::reg_bits()},
[](const size_t register_number) { return audio::debug::reg_read(register_number); }); }},
{"RFFC5072", ui::Color::dark_cyan(), &bitmap_icon_peripherals_details, [this]() { nav_.push<RegistersView>("RFFC5072", RegistersWidgetConfig{CT_RFFC5072, 31, 31, 16}); }},
{max283x, ui::Color::dark_cyan(), &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, [this, si5351x]() { nav_.push<RegistersView>(si5351x, RegistersWidgetConfig{CT_SI5351, 188, 96, 8}); }},
{audio::debug::codec_name(), ui::Color::dark_cyan(), &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()}); }},
});
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(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()) {
add_items({{"..", ui::Color::light_grey(), &bitmap_icon_previous, [&nav]() { nav.pop(); }}});
add_items({{"..", ui::Color::light_grey(), &bitmap_icon_previous, [this]() { nav_.pop(); }}});
}
add_items({
{"Buttons Test", ui::Color::dark_cyan(), &bitmap_icon_controls, [&nav]() { nav.push<DebugControlsView>(); }},
{"Debug Dump", ui::Color::dark_cyan(), &bitmap_icon_memory, [&nav]() { portapack::persistent_memory::debug_dump(); }},
{"M0 Stack Dump", ui::Color::dark_cyan(), &bitmap_icon_memory, [&nav]() { stack_dump(); }},
{"Memory", ui::Color::dark_cyan(), &bitmap_icon_memory, [&nav]() { nav.push<DebugMemoryView>(); }},
{"P.Memory", ui::Color::dark_cyan(), &bitmap_icon_memory, [&nav]() { nav.push<DebugPmemView>(); }},
{"Peripherals", ui::Color::dark_cyan(), &bitmap_icon_peripherals, [&nav]() { nav.push<DebugPeripheralsMenuView>(); }},
//{ "Radio State", ui::Color::white(), nullptr, [&nav](){ nav.push<NotImplementedView>(); } },
{"SD Card", ui::Color::dark_cyan(), &bitmap_icon_sdcard, [&nav]() { nav.push<SDCardDebugView>(); }},
{"Temperature", ui::Color::dark_cyan(), &bitmap_icon_temperature, [&nav]() { nav.push<TemperatureView>(); }},
{"Touch Test", ui::Color::dark_cyan(), &bitmap_icon_notepad, [&nav]() { nav.push<DebugScreenTest>(); }},
{"Buttons Test", ui::Color::dark_cyan(), &bitmap_icon_controls, [this]() { nav_.push<DebugControlsView>(); }},
{"Debug Dump", ui::Color::dark_cyan(), &bitmap_icon_memory, [this]() { portapack::persistent_memory::debug_dump(); }},
{"M0 Stack Dump", ui::Color::dark_cyan(), &bitmap_icon_memory, [this]() { stack_dump(); }},
{"Memory Dump", ui::Color::dark_cyan(), &bitmap_icon_memory, [this]() { nav_.push<DebugMemoryDumpView>(); }},
//{"Memory Usage", ui::Color::dark_cyan(), &bitmap_icon_memory, [this]() { nav_.push<DebugMemoryView>(); }},
{"Peripherals", ui::Color::dark_cyan(), &bitmap_icon_peripherals, [this]() { nav_.push<DebugPeripheralsMenuView>(); }},
{"Pers. Memory", ui::Color::dark_cyan(), &bitmap_icon_memory, [this]() { nav_.push<DebugPmemView>(); }},
//{ "Radio State", ui::Color::white(), nullptr, [this](){ nav_.push<NotImplementedView>(); } },
{"Reboot", ui::Color::dark_cyan(), &bitmap_icon_setup, [this]() { nav_.push<DebugReboot>(); }},
{"SD Card", ui::Color::dark_cyan(), &bitmap_icon_sdcard, [this]() { nav_.push<SDCardDebugView>(); }},
{"Temperature", ui::Color::dark_cyan(), &bitmap_icon_temperature, [this]() { nav_.push<TemperatureView>(); }},
{"Touch Test", ui::Color::dark_cyan(), &bitmap_icon_notepad, [this]() { nav_.push<DebugScreenTest>(); }},
});
for (auto const& gridItem : ExternalItemsMenuLoader::load_external_items(app_location_t::DEBUG, nav)) {
for (auto const& gridItem : ExternalItemsMenuLoader::load_external_items(app_location_t::DEBUG, nav_)) {
add_item(gridItem);
};
}
set_max_rows(2); // allow wider buttons
/* DebugMemoryDumpView *********************************************************/
DebugMemoryDumpView::DebugMemoryDumpView(NavigationView& nav) {
add_children({
&button_dump,
&button_read,
&button_write,
&button_done,
&labels,
&field_starting_address,
&field_byte_count,
&field_rw_address,
&field_data_value,
});
button_done.on_select = [&nav](Button&) { nav.pop(); };
button_dump.on_select = [this](Button&) {
if (field_byte_count.to_integer() != 0)
memory_dump((uint32_t*)field_starting_address.to_integer(), ((uint32_t)field_byte_count.to_integer() + 3) / 4, false);
};
button_read.on_select = [this](Button&) {
field_data_value.set_value(*(uint32_t*)field_rw_address.to_integer());
field_data_value.set_dirty();
};
button_write.set_style(&Styles::red);
button_write.on_select = [this](Button&) {
*(uint32_t*)field_rw_address.to_integer() = (uint32_t)field_data_value.to_integer();
};
}
void DebugMemoryDumpView::focus() {
button_done.focus();
}
/* DebugPmemView *********************************************************/
uint32_t pmem_checksum(volatile const uint32_t data[63]) {
CRC<32> crc{0x04c11db7, 0xffffffff, 0xffffffff};
for (size_t i = 0; i < 63; i++) {
const auto word = data[i];
crc.process_byte((word >> 0) & 0xff);
crc.process_byte((word >> 8) & 0xff);
crc.process_byte((word >> 16) & 0xff);
crc.process_byte((word >> 24) & 0xff);
}
return crc.checksum();
}
DebugPmemView::DebugPmemView(NavigationView& nav)
: data{*reinterpret_cast<pmem_data*>(memory::map::backup_ram.base())}, registers_widget(RegistersWidgetConfig{page_size, 8}, std::bind(&DebugPmemView::registers_widget_feed, this, std::placeholders::_1)) {
static_assert(sizeof(pmem_data) == memory::map::backup_ram.size());
add_children({&text_page, &registers_widget, &text_checksum, &text_checksum2, &button_ok});
: registers_widget(RegistersWidgetConfig{CT_PMEM, PMEM_SIZE_BYTES, page_size, 8}) {
add_children({&registers_widget, &text_checksum, &text_checksum2, &button_ok});
registers_widget.set_parent_rect({0, 32, 240, 192});
text_checksum.set("Size: " + to_string_dec_uint(portapack::persistent_memory::data_size(), 3) + " CRC: " + to_string_hex(data.check_value, 8));
text_checksum2.set("Calculated CRC: " + to_string_hex(pmem_checksum(data.regfile), 8));
text_checksum.set("Size: " + to_string_dec_uint(portapack::persistent_memory::data_size(), 3) + " CRC: " + to_string_hex(portapack::persistent_memory::pmem_stored_checksum(), 8));
text_checksum2.set("Calculated CRC: " + to_string_hex(portapack::persistent_memory::pmem_calculated_checksum(), 8));
button_ok.on_select = [&nav](Button&) {
nav.pop();
@@ -451,7 +564,7 @@ DebugPmemView::DebugPmemView(NavigationView& nav)
}
bool DebugPmemView::on_encoder(const EncoderEvent delta) {
page = std::max(0l, std::min((int32_t)page_max, page + delta));
registers_widget.set_page(std::max(0ul, std::min((uint32_t)page_count - 1, registers_widget.page() + delta)));
update();
@@ -463,22 +576,15 @@ void DebugPmemView::focus() {
}
void DebugPmemView::update() {
text_page.set(to_string_hex(page_size * page, 2) + "+");
registers_widget.update();
}
uint32_t DebugPmemView::registers_widget_feed(const size_t register_number) {
if (page_size * page + register_number >= memory::map::backup_ram.size()) {
return 0xff;
}
return data.regfile[(page_size * page + register_number) / 4] >> (register_number % 4 * 8);
}
/* DebugScreenTest ****************************************************/
DebugScreenTest::DebugScreenTest(NavigationView& nav)
: nav_{nav} {
set_focusable(true);
std::srand(LPC_RTC->CTIME0);
}
bool DebugScreenTest::on_key(const KeyEvent key) {
@@ -488,10 +594,10 @@ bool DebugScreenTest::on_key(const KeyEvent key) {
nav_.pop();
break;
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;
case KeyEvent::Left:
pen_color = semirand();
pen_color = std::rand();
break;
default:
break;
@@ -511,17 +617,10 @@ bool DebugScreenTest::on_touch(const TouchEvent event) {
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) {
painter.fill_rectangle({0, 16, screen_width, screen_height - 16}, Color::white());
painter.draw_string({10 * 8, screen_height / 2}, Styles::white, "Use Stylus");
pen_color = semirand();
pen_color = std::rand();
}
/* DebugLCRView *******************************************************/
+122 -32
View File
@@ -1,5 +1,7 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2024 Mark Thompson
* Copyright (C) 2024 u-foka
*
* This file is part of PortaPack.
*
@@ -48,8 +50,8 @@ class DebugMemoryView : public View {
private:
Text text_title{
{96, 96, 48, 16},
"Memory",
{72, 96, 96, 16},
"Memory Usage",
};
Text text_label_m0_core_free{
@@ -131,9 +133,19 @@ class TemperatureView : public View {
"Done"};
};
typedef enum {
CT_PMEM,
CT_RFFC5072,
CT_MAX283X,
CT_SI5351,
CT_AUDIO,
} chip_type_t;
struct RegistersWidgetConfig {
size_t registers_count;
size_t register_bits;
chip_type_t chip_type;
uint32_t registers_count;
uint32_t registers_per_page;
uint32_t register_bits;
constexpr size_t legend_length() const {
return (registers_count >= 0x10) ? 2 : 1;
@@ -174,17 +186,22 @@ struct RegistersWidgetConfig {
class RegistersWidget : public Widget {
public:
RegistersWidget(
RegistersWidgetConfig&& config,
std::function<uint32_t(const size_t register_number)>&& reader);
RegistersWidget(RegistersWidgetConfig&& config);
void update();
void paint(Painter& painter) override;
uint32_t reg_read(const uint32_t register_number);
void reg_write(const uint32_t register_number, const uint32_t value);
void set_page(int32_t value) { page_number = value; }
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:
const RegistersWidgetConfig config;
const std::function<uint32_t(const size_t register_number)> reader;
uint32_t page_number;
static constexpr size_t row_height = 16;
@@ -194,13 +211,9 @@ class RegistersWidget : public Widget {
class RegistersView : public View {
public:
RegistersView(
NavigationView& nav,
const std::string& title,
RegistersWidgetConfig&& config,
std::function<uint32_t(const size_t register_number)>&& reader);
void focus();
RegistersView(NavigationView& nav, const std::string& title, RegistersWidgetConfig&& config);
void focus() override;
bool on_encoder(const EncoderEvent delta) override;
private:
Text text_title{};
@@ -208,12 +221,30 @@ class RegistersView : public View {
RegistersWidget registers_widget;
Button button_update{
{16, 256, 96, 24},
{16, 280, 96, 24},
"Update"};
Button button_done{
{128, 256, 96, 24},
{128, 280, 96, 24},
"Done"};
Button button_write{
{144, 248, 80, 20},
"Write"};
Labels labels{
{{1 * 8, 248}, "Reg:", Color::light_grey()},
{{8 * 8, 248}, "Data:", Color::light_grey()}};
SymField field_write_reg_num{
{5 * 8, 248},
2,
SymField::Type::Hex};
SymField field_write_data_val{
{13 * 8, 248},
4,
SymField::Type::Hex};
};
class ControlsSwitchesWidget : public Widget {
@@ -222,18 +253,21 @@ class ControlsSwitchesWidget : public Widget {
Rect parent_rect)
: Widget{parent_rect},
key_event_mask(0),
long_press_key_event_mask{0} {
long_press_key_event_mask{0},
last_delta{0} {
set_focusable(true);
}
void on_show() override;
bool on_key(const KeyEvent key) override;
bool on_encoder(const EncoderEvent delta) override;
void paint(Painter& painter) override;
private:
uint8_t key_event_mask;
uint8_t long_press_key_event_mask;
EncoderEvent last_delta;
MessageHandlerRegistration message_handler_frame_sync{
Message::ID::DisplayFrameSync,
@@ -255,6 +289,7 @@ class DebugControlsView : public View {
private:
Labels labels{
{{8 * 8, 1 * 16}, "Controls State", Color::white()},
{{0 * 8, 11 * 16}, "Dial:", Color::grey()},
{{0 * 8, 14 * 16}, "Long-Press Mode:", Color::grey()}};
ControlsSwitchesWidget switches_widget{
@@ -274,6 +309,58 @@ class DebugControlsView : public View {
"Done"};
};
class DebugMemoryDumpView : public View {
public:
DebugMemoryDumpView(NavigationView& nav);
void focus() override;
std::string title() const override { return "Memory Dump"; };
private:
Button button_dump{
{72, 4 * 16, 96, 24},
"Dump"};
Button button_read{
{16, 11 * 16, 96, 24},
"Read"};
Button button_write{
{128, 11 * 16, 96, 24},
"Write"};
Button button_done{
{128, 240, 96, 24},
"Done"};
Labels labels{
{{5 * 8, 1 * 16}, "Dump Range to File", Color::yellow()},
{{0 * 8, 2 * 16}, "Starting Address: 0x", Color::light_grey()},
{{0 * 8, 3 * 16}, "Byte Count: 0x", Color::light_grey()},
{{3 * 8, 8 * 16}, "Read/Write Single Word", Color::yellow()},
{{0 * 8, 9 * 16}, "Memory Address: 0x", Color::light_grey()},
{{0 * 8, 10 * 16}, "Data Value: 0x", Color::light_grey()}};
SymField field_starting_address{
{20 * 8, 2 * 16},
8,
SymField::Type::Hex};
SymField field_byte_count{
{20 * 8, 3 * 16},
8,
SymField::Type::Hex};
SymField field_rw_address{
{20 * 8, 9 * 16},
8,
SymField::Type::Hex};
SymField field_data_value{
{20 * 8, 10 * 16},
8,
SymField::Type::Hex};
};
class DebugPmemView : public View {
public:
DebugPmemView(NavigationView& nav);
@@ -282,19 +369,8 @@ class DebugPmemView : public View {
std::string title() const override { return "P.Mem Debug"; }
private:
struct pmem_data {
uint32_t regfile[63];
uint32_t check_value;
};
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_max{(portapack::memory::map::backup_ram.size() + page_size - 1) / page_size - 1};
int32_t page{0};
volatile const pmem_data& data;
Text text_page{{16, 16, 208, 16}};
static constexpr uint8_t page_count{(portapack::memory::map::backup_ram.size() + page_size - 1) / page_size};
RegistersWidget registers_widget;
@@ -307,7 +383,6 @@ class DebugPmemView : public View {
};
void update();
uint32_t registers_widget_feed(const size_t register_number);
};
class DebugScreenTest : public View {
@@ -316,7 +391,6 @@ class DebugScreenTest : public View {
bool on_key(KeyEvent key) override;
bool on_encoder(EncoderEvent delta) override;
bool on_touch(TouchEvent event) override;
uint16_t semirand();
void paint(Painter& painter) override;
private:
@@ -349,12 +423,28 @@ class DebugPeripheralsMenuView : public BtnGridView {
public:
DebugPeripheralsMenuView(NavigationView& nav);
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 {
public:
DebugMenuView(NavigationView& nav);
std::string title() const override { return "Debug"; };
private:
NavigationView& nav_;
void on_populate() override;
};
} /* namespace ui */
+27 -18
View File
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2023 Bernd Herzog
* Copyright (C) 2023 Mark Thompson
*
* This file is part of PortaPack.
*
@@ -37,23 +38,29 @@ DfuMenu::DfuMenu(NavigationView& nav)
&text_info_line_5,
&text_info_line_6,
&text_info_line_7,
&text_info_line_8});
&text_info_line_8,
&text_info_line_9,
&text_info_line_10});
}
void DfuMenu::paint(Painter& painter) {
auto utilisation = get_cpu_utilisation_in_percent();
size_t m0_fragmented_free_space = 0;
const auto m0_fragments = chHeapStatus(NULL, &m0_fragmented_free_space);
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_3.set(to_string_dec_uint(utilisation, 6));
text_info_line_4.set(to_string_dec_uint(shared_memory.m4_heap_usage, 6));
text_info_line_5.set(to_string_dec_uint(shared_memory.m4_stack_usage, 6));
text_info_line_6.set(to_string_dec_uint(shared_memory.m4_cpu_usage, 6));
text_info_line_7.set(to_string_dec_uint(shared_memory.m4_buffer_missed, 6));
text_info_line_8.set(to_string_dec_uint(chTimeNow() / 1000, 6));
text_info_line_2.set(to_string_dec_uint(m0_fragmented_free_space, 6));
text_info_line_3.set(to_string_dec_uint(m0_fragments, 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(utilisation, 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_stack_usage, 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));
constexpr auto margin = 5;
constexpr auto lines = 8 + 2;
constexpr auto lines = 10 + 2;
painter.fill_rectangle(
{{6 * CHARACTER_WIDTH - margin, 3 * LINE_HEIGHT - margin},
@@ -94,23 +101,25 @@ DfuMenu2::DfuMenu2(NavigationView& nav)
&text_info_line_7,
&text_info_line_8,
&text_info_line_9,
&text_info_line_10});
&text_info_line_10,
&text_info_line_11});
}
void DfuMenu2::paint(Painter& painter) {
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_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_5.set(to_string_dec_uint(portapack::receiver_model.am_configuration(), 10));
text_info_line_6.set(to_string_dec_uint(portapack::receiver_model.nbfm_configuration(), 10));
text_info_line_7.set(to_string_dec_uint(portapack::receiver_model.wfm_configuration(), 10));
text_info_line_8.set(to_string_dec_uint(portapack::transmitter_model.target_frequency(), 10));
text_info_line_9.set(to_string_dec_uint(portapack::transmitter_model.baseband_bandwidth(), 10));
text_info_line_10.set(to_string_dec_uint(portapack::transmitter_model.sampling_rate(), 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((uint32_t)portapack::receiver_model.modulation(), 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.nbfm_configuration(), 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.target_frequency(), 10));
text_info_line_10.set(to_string_dec_uint(portapack::transmitter_model.baseband_bandwidth(), 10));
text_info_line_11.set(to_string_dec_uint(portapack::transmitter_model.sampling_rate(), 10));
constexpr auto margin = 5;
constexpr auto lines = 10 + 2;
constexpr auto lines = 11 + 2;
painter.fill_rectangle(
{{5 * CHARACTER_WIDTH - margin, 3 * LINE_HEIGHT - margin},
+23 -15
View File
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2023 Bernd Herzog
* Copyright (C) 2023 Mark Thompson
*
* This file is part of PortaPack.
*
@@ -48,14 +49,16 @@ class DfuMenu : public View {
Text text_head{{6 * CHARACTER_WIDTH, 3 * LINE_HEIGHT, 11 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, "Performance"};
Labels labels{
{{6 * CHARACTER_WIDTH, 5 * LINE_HEIGHT}, "M0 heap:", Color::dark_cyan()},
{{6 * CHARACTER_WIDTH, 6 * LINE_HEIGHT}, "M0 stack:", Color::dark_cyan()},
{{6 * CHARACTER_WIDTH, 7 * LINE_HEIGHT}, "M0 cpu %:", Color::dark_cyan()},
{{6 * CHARACTER_WIDTH, 8 * LINE_HEIGHT}, "M4 heap:", Color::dark_cyan()},
{{6 * CHARACTER_WIDTH, 9 * LINE_HEIGHT}, "M4 stack:", Color::dark_cyan()},
{{6 * CHARACTER_WIDTH, 10 * LINE_HEIGHT}, "M4 cpu %:", Color::dark_cyan()},
{{6 * CHARACTER_WIDTH, 11 * LINE_HEIGHT}, "M4 miss:", Color::dark_cyan()},
{{6 * CHARACTER_WIDTH, 12 * LINE_HEIGHT}, "uptime:", Color::dark_cyan()}};
{{6 * CHARACTER_WIDTH, 5 * LINE_HEIGHT}, "M0 core:", Color::dark_cyan()},
{{6 * CHARACTER_WIDTH, 6 * LINE_HEIGHT}, "M0 heap:", Color::dark_cyan()},
{{6 * CHARACTER_WIDTH, 7 * LINE_HEIGHT}, "M0 frags:", Color::dark_cyan()},
{{6 * CHARACTER_WIDTH, 8 * LINE_HEIGHT}, "M0 stack:", Color::dark_cyan()},
{{6 * CHARACTER_WIDTH, 9 * LINE_HEIGHT}, "M0 cpu %:", Color::dark_cyan()},
{{6 * CHARACTER_WIDTH, 10 * LINE_HEIGHT}, "M4 heap:", Color::dark_cyan()},
{{6 * CHARACTER_WIDTH, 11 * LINE_HEIGHT}, "M4 stack:", Color::dark_cyan()},
{{6 * CHARACTER_WIDTH, 12 * LINE_HEIGHT}, "M4 cpu %:", Color::dark_cyan()},
{{6 * CHARACTER_WIDTH, 13 * LINE_HEIGHT}, "M4 miss:", Color::dark_cyan()},
{{6 * CHARACTER_WIDTH, 14 * LINE_HEIGHT}, "uptime:", Color::dark_cyan()}};
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}, ""};
@@ -65,6 +68,8 @@ 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_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_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}, ""};
};
class DfuMenu2 : public View {
@@ -83,13 +88,15 @@ class DfuMenu2 : public View {
{{5 * CHARACTER_WIDTH, 5 * LINE_HEIGHT}, "RX Freq:", Color::dark_cyan()},
{{5 * CHARACTER_WIDTH, 6 * LINE_HEIGHT}, "RX BW:", Color::dark_cyan()},
{{5 * CHARACTER_WIDTH, 7 * LINE_HEIGHT}, "RX SampR:", Color::dark_cyan()},
{{5 * CHARACTER_WIDTH, 8 * LINE_HEIGHT}, "Modulatn:", Color::dark_cyan()},
{{5 * CHARACTER_WIDTH, 9 * LINE_HEIGHT}, "AM cfg:", Color::dark_cyan()},
{{5 * CHARACTER_WIDTH, 10 * LINE_HEIGHT}, "NBFM cfg:", Color::dark_cyan()},
{{5 * CHARACTER_WIDTH, 11 * LINE_HEIGHT}, "WFM cfg:", Color::dark_cyan()},
{{5 * CHARACTER_WIDTH, 12 * LINE_HEIGHT}, "TX Freq:", Color::dark_cyan()},
{{5 * CHARACTER_WIDTH, 13 * LINE_HEIGHT}, "TX BW:", Color::dark_cyan()},
{{5 * CHARACTER_WIDTH, 14 * LINE_HEIGHT}, "TX SampR:", Color::dark_cyan()}};
{{5 * CHARACTER_WIDTH, 8 * LINE_HEIGHT}, "RX Satu%:", Color::dark_cyan()},
{{5 * CHARACTER_WIDTH, 9 * LINE_HEIGHT}, "Modulatn:", Color::dark_cyan()},
{{5 * CHARACTER_WIDTH, 10 * LINE_HEIGHT}, "AM cfg:", Color::dark_cyan()},
{{5 * CHARACTER_WIDTH, 11 * LINE_HEIGHT}, "NBFM cfg:", Color::dark_cyan()},
{{5 * CHARACTER_WIDTH, 12 * LINE_HEIGHT}, "WFM cfg:", Color::dark_cyan()},
{{5 * CHARACTER_WIDTH, 13 * LINE_HEIGHT}, "TX Freq:", Color::dark_cyan()},
{{5 * CHARACTER_WIDTH, 14 * LINE_HEIGHT}, "TX BW:", Color::dark_cyan()},
{{5 * CHARACTER_WIDTH, 15 * LINE_HEIGHT}, "TX SampR:", Color::dark_cyan()},
};
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}, ""};
@@ -101,6 +108,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_9{{14 * CHARACTER_WIDTH, 13 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
Text text_info_line_10{{14 * CHARACTER_WIDTH, 14 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
Text text_info_line_11{{14 * CHARACTER_WIDTH, 15 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
};
} /* namespace ui */
+245 -49
View File
@@ -62,7 +62,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.
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(
std::begin(entries), std::end(entries), entry,
[](const fileman_entry& lhs, const fileman_entry& rhs) {
@@ -146,9 +146,82 @@ namespace ui {
/* 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;
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();
bool cxx_file = path_iequal(cxx_ext, extension_filter);
@@ -156,24 +229,46 @@ void FileManBaseView::load_directory_contents(const fs::path& dir_path) {
for (const auto& entry : fs::directory_iterator(dir_path, u"*")) {
// Hide files starting with '.' (hidden / tmp).
if (is_hidden_file(entry.path()))
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())))
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())) {
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.
if (!dir_path.empty())
entry_list.insert(entry_list.begin(), {parent_dir_path, 0, true});
if (!dir_path.empty() && pagination == 0)
entry_list.insert(entry_list.begin(), {parent_dir_path.string(), 0, true});
// add next page
if (list_size > start + items_per_page) {
entry_list.push_back({str_next, (uint32_t)pagination + 1, true});
}
// add prev page
if (pagination > 0) {
entry_list.insert(entry_list.begin(), {str_back, (uint32_t)pagination - 1, true});
}
}
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 / get_selected_entry().path;
@@ -181,7 +276,10 @@ fs::path FileManBaseView::get_selected_full_path() const {
const fileman_entry& FileManBaseView::get_selected_entry() const {
// 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(
@@ -230,7 +328,7 @@ void FileManBaseView::push_dir(const fs::path& path) {
current_path /= path;
saved_index_stack.push_back(menu_view.highlighted_index());
menu_view.set_highlighted(0);
reload_current();
reload_current(true);
}
}
@@ -239,29 +337,56 @@ void FileManBaseView::pop_dir() {
return;
current_path = current_path.parent_path();
reload_current();
reload_current(true);
menu_view.set_highlighted(saved_index_stack.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() {
if (on_refresh_widgets)
on_refresh_widgets(false);
auto prev_highlight = menu_view.highlighted_index();
prev_highlight = menu_view.highlighted_index();
menu_view.clear();
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) {
auto size_str =
(entry.path == parent_dir_path)
? ""
: to_string_dec_uint(file_count(current_path / entry.path));
std::string size_str{};
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
} else {
size_str = (entry.path == parent_dir_path.string()) ? "" : to_string_dec_uint(file_count(current_path / entry.path));
}
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(),
&bitmap_icon_dir,
[this](KeyEvent key) {
@@ -270,11 +395,11 @@ void FileManBaseView::refresh_list() {
}});
} 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);
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.icon,
[this](KeyEvent key) {
@@ -282,16 +407,13 @@ void FileManBaseView::refresh_list() {
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);
}
void FileManBaseView::reload_current() {
void FileManBaseView::reload_current(bool reset_pagination) {
if (reset_pagination) pagination = 0;
load_directory_contents(current_path);
refresh_list();
}
@@ -327,6 +449,21 @@ FileLoadView::FileLoadView(
on_select_entry = [this](KeyEvent) {
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);
} else {
if (on_changed)
@@ -402,6 +539,7 @@ void FileSaveView::refresh_widgets() {
void FileManagerView::refresh_widgets(const bool v) {
button_rename.hidden(v);
button_delete.hidden(v);
button_clean.hidden(v);
button_cut.hidden(v);
button_copy.hidden(v);
button_paste.hidden(v);
@@ -415,10 +553,10 @@ void FileManagerView::on_rename(std::string_view hint) {
auto& entry = get_selected_entry();
// 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())
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.
uint32_t cursor_pos = (uint32_t)name_buffer.length();
@@ -439,21 +577,21 @@ void FileManagerView::on_rename(std::string_view hint) {
auto new_name = renamed_path.replace_extension(partner.extension());
rename_file(partner, current_path / new_name);
}
reload_current();
reload_current(false);
});
if (!has_partner)
reload_current();
reload_current(false);
});
}
void FileManagerView::on_delete() {
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;
}
auto name = get_selected_entry().path.filename().string();
auto name = get_filename(get_selected_entry().path);
nav_.push<ModalMessageView>(
"Delete", "Delete " + name + "\nAre you sure?", YESNO,
[this](bool choice) {
@@ -465,11 +603,36 @@ void FileManagerView::on_delete() {
[this](const fs::path& partner, bool should_delete) {
if (should_delete)
delete_file(partner);
reload_current();
reload_current(true);
});
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 +641,7 @@ void FileManagerView::on_new_dir() {
name_buffer = "";
text_prompt(nav_, name_buffer, max_filename_length, [this](std::string& dir_name) {
make_new_directory(current_path / dir_name);
reload_current();
reload_current(true);
});
}
@@ -488,7 +651,10 @@ void FileManagerView::on_paste() {
fs::filesystem_error result;
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)
result = copy_file(clipboard_path, current_path / new_name);
@@ -499,14 +665,14 @@ void FileManagerView::on_paste() {
clipboard_path = fs::path{};
clipboard_mode = ClipboardMode::None;
menu_view.focus();
reload_current();
reload_current(true);
}
void FileManagerView::on_new_file() {
name_buffer = "";
text_prompt(nav_, name_buffer, max_filename_length, [this](std::string& file_name) {
make_new_file(current_path / file_name);
reload_current();
reload_current(true);
});
}
@@ -529,7 +695,7 @@ bool FileManagerView::handle_file_open() {
return true;
} else if (path_iequal(bmp_ext, ext)) {
nav_.push<SplashViewer>(path);
reload_current();
reload_current(false);
return true;
} else if (path_iequal(rem_ext, ext)) {
nav_.push<RemoteView>(path);
@@ -541,7 +707,7 @@ bool FileManagerView::handle_file_open() {
bool FileManagerView::selected_is_valid() const {
return !entry_list.empty() &&
get_selected_entry().path != parent_dir_path;
get_selected_entry().path != parent_dir_path.string();
}
FileManagerView::FileManagerView(
@@ -560,6 +726,7 @@ FileManagerView::FileManagerView(
&text_date,
&button_rename,
&button_delete,
&button_clean,
&button_cut,
&button_copy,
&button_paste,
@@ -568,10 +735,11 @@ FileManagerView::FileManagerView(
&button_open_notepad,
&button_rename_timestamp,
&button_open_iq_trim,
&button_show_hidden_files,
});
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("Too many files!");
} else {
@@ -586,13 +754,30 @@ FileManagerView::FileManagerView(
refresh_list();
on_select_entry = [this](KeyEvent key) {
if (key == KeyEvent::Select && get_selected_entry().is_directory) {
push_dir(get_selected_entry().path);
} else if (key == KeyEvent::Select && handle_file_open()) {
return;
} else {
button_rename.focus();
if (key == KeyEvent::Select) {
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);
return;
} else if (handle_file_open()) {
return;
}
}
button_rename.focus();
};
button_rename.on_select = [this]() {
@@ -605,8 +790,13 @@ FileManagerView::FileManagerView(
on_delete();
};
button_clean.on_select = [this]() {
if (selected_is_valid())
on_clean();
};
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_mode = ClipboardMode::Cut;
} else
@@ -625,7 +815,7 @@ FileManagerView::FileManagerView(
if (clipboard_mode != ClipboardMode::None)
on_paste();
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]() {
@@ -645,7 +835,7 @@ FileManagerView::FileManagerView(
};
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));
} else
nav_.display_modal("Timestamp Rename", "Can't rename that.");
@@ -658,6 +848,12 @@ FileManagerView::FileManagerView(
} else
nav_.display_modal("IQ Trim", "Not a capture file.");
};
button_show_hidden_files.on_select = [this]() {
show_hidden_files = !show_hidden_files;
button_show_hidden_files.set_color(show_hidden_files ? Color::green() : Color::dark_grey());
reload_current();
};
}
} // namespace ui
+35 -9
View File
@@ -31,7 +31,7 @@
namespace ui {
struct fileman_entry {
std::filesystem::path path{};
std::string path{};
uint32_t size{};
bool is_directory{};
};
@@ -61,8 +61,12 @@ class FileManBaseView : public View {
void push_dir(const std::filesystem::path& path);
protected:
static constexpr size_t max_filename_length = 64;
static constexpr size_t max_items_shown = 100;
uint32_t prev_highlight = 0;
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 {
std::filesystem::path extension;
@@ -85,10 +89,12 @@ class FileManBaseView : public View {
std::filesystem::path get_selected_full_path() const;
const fileman_entry& get_selected_entry() const;
int file_count_filtered(const std::filesystem::path& directory);
void pop_dir();
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_unordered(const std::filesystem::path& dir_path, size_t file_cnt);
const file_assoc_t& get_assoc(const std::filesystem::path& ext) const;
NavigationView& nav_;
@@ -97,13 +103,18 @@ class FileManBaseView : public View {
std::function<void(KeyEvent)> on_select_entry{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".."};
std::filesystem::path current_path{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{};
bool show_hidden_files{false};
Labels labels{
{{0, 0}, "Path:", Color::light_grey()}};
@@ -205,6 +216,7 @@ class FileManagerView : public FileManBaseView {
void refresh_widgets(const bool v);
void on_rename(std::string_view hint);
void on_delete();
void on_clean();
void on_paste();
void on_new_dir();
void on_new_file();
@@ -225,11 +237,17 @@ class FileManagerView : public FileManBaseView {
Color::dark_blue()};
NewButton button_delete{
{4 * 8, 29 * 8, 4 * 8, 32},
{9 * 8, 34 * 8, 4 * 8, 32},
{},
&bitmap_icon_trash,
Color::red()};
NewButton button_clean{
{13 * 8, 34 * 8, 4 * 8, 32},
{},
&bitmap_icon_clean,
Color::red()};
NewButton button_cut{
{9 * 8, 29 * 8, 4 * 8, 32},
{},
@@ -267,17 +285,25 @@ class FileManagerView : public FileManBaseView {
Color::orange()};
NewButton button_rename_timestamp{
{4 * 8, 34 * 8, 4 * 8, 32},
{4 * 8, 29 * 8, 4 * 8, 32},
{},
&bitmap_icon_options_datetime,
Color::orange(),
Color::dark_blue(),
/*vcenter*/ true};
NewButton button_open_iq_trim{
{9 * 8, 34 * 8, 4 * 8, 32},
{4 * 8, 34 * 8, 4 * 8, 32},
{},
&bitmap_icon_trim,
Color::orange()};
NewButton button_show_hidden_files{
{17 * 8, 34 * 8, 4 * 8, 32},
{},
&bitmap_icon_hide,
Color::dark_grey()};
};
} /* namespace ui */
+108 -16
View File
@@ -21,15 +21,61 @@
*/
#include "ui_flash_utility.hpp"
#include "ui_styles.hpp"
#include "portapack_shared_memory.hpp"
#include "file_path.hpp"
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};
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)
: nav_(nav) {
add_children({&labels,
@@ -37,19 +83,29 @@ FlashUtilityView::FlashUtilityView(NavigationView& nav)
menu_view.set_parent_rect({0, 3 * 8, 240, 33 * 8});
ensure_directory(firmware_folder);
ensure_directory(firmware_dir);
for (const auto& entry : std::filesystem::directory_iterator(firmware_folder, u"*.bin")) {
auto filename = entry.path().filename();
auto path = entry.path().native();
auto add_firmware_items = [&](
const std::filesystem::path& folder_path,
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),
ui::Color::red(),
&bitmap_icon_temperature,
[this, path](KeyEvent) {
this->firmware_selected(path);
}});
}
menu_view.add_item({filename.string().substr(0, max_filename_length),
color,
&bitmap_icon_temperature,
[this, path](KeyEvent) {
this->firmware_selected(path);
}});
}
};
add_firmware_items(firmware_dir, u"*.bin", ui::Color::red());
add_firmware_items(firmware_dir, u"*.tar", ui::Color::purple());
// add_firmware_items(user_firmware_folder,u"*.bin", ui::Color::purple());
}
void FlashUtilityView::firmware_selected(std::filesystem::path::string_type path) {
@@ -59,14 +115,49 @@ void FlashUtilityView::firmware_selected(std::filesystem::path::string_type path
YESNO,
[this, path](bool 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);
}
});
}
void FlashUtilityView::flash_firmware(std::filesystem::path::string_type path) {
bool FlashUtilityView::endsWith(const std::u16string& str, const std::u16string& suffix) {
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(
{0, 0, portapack::display.width(), portapack::display.height()},
ui::Color::black());
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}, ui::Color::black());
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}, ui::Color::black());
painter.draw_string({0, 60}, Styles::red, "BAD FIRMWARE FILE");
chThdSleepMilliseconds(5000);
return false;
}
painter.fill_rectangle(
{0, 0, portapack::display.width(), portapack::display.height()},
ui::Color::black());
@@ -74,15 +165,16 @@ void FlashUtilityView::flash_firmware(std::filesystem::path::string_type path) {
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, 84}, this->nav_.style(), "and TX are 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);
m4_init(portapack::spi_flash::image_tag_flash_utility, portapack::memory::map::m4_code, false);
m0_halt();
return true; // fixes compiler warning (line should not be reached due to halt)
}
void FlashUtilityView::focus() {
menu_view.focus();
}
} /* namespace ui */
} /* namespace ui */
+12 -2
View File
@@ -29,11 +29,18 @@
#include "ff.h"
#include "baseband_api.hpp"
#include "core_control.hpp"
#include "untar.hpp"
#include <cstdint>
#define FLASH_ROM_SIZE 1048576
#define FLASH_STARTING_ADDRESS 0x00000000
#define FLASH_EXPECTED_CHECKSUM 0x00000000
#define FLASH_CHECKSUM_ERROR 0xFFFFFFFF
namespace ui {
bool valid_firmware_file(std::filesystem::path::string_type path);
class FlashUtilityView : public View {
public:
FlashUtilityView(NavigationView& nav);
@@ -41,6 +48,7 @@ class FlashUtilityView : public View {
void focus() override;
std::string title() const override { return "Flash Utility"; };
bool flash_firmware(std::filesystem::path::string_type path);
private:
NavigationView& nav_;
@@ -55,8 +63,10 @@ class FlashUtilityView : public View {
{0, 2 * 8, 240, 26 * 8},
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 flash_firmware(std::filesystem::path::string_type path);
bool endsWith(const std::u16string& str, const std::u16string& suffix);
};
} /* namespace ui */
+4 -2
View File
@@ -31,6 +31,7 @@
#include "ui_receiver.hpp"
#include "ui_styles.hpp"
#include "utility.hpp"
#include "file_path.hpp"
#include <memory>
@@ -228,7 +229,7 @@ void FrequencyManagerView::on_edit_freq() {
void FrequencyManagerView::on_edit_desc() {
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();
entry.description = std::move(new_desc);
db_.replace_entry(current_index(), entry);
@@ -412,7 +413,8 @@ void FrequencyEditView::refresh_ui() {
auto is_range = entry_.type == freqman_type::Range;
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_step.set_style(is_range ? &Styles::white : &Styles::grey);
+5 -6
View File
@@ -40,8 +40,6 @@ class FreqManBaseView : public View {
void focus() override;
static constexpr size_t desc_edit_max = 0x80;
protected:
using options_t = OptionsField::options_t;
@@ -63,11 +61,11 @@ class FreqManBaseView : public View {
/* The top section (category) is 20px tall. */
Labels label_category{
{{0, 2}, "Category:", Color::light_grey()}};
{{0, 2}, "F:", Color::light_grey()}};
OptionsField options_category{
{9 * 8, 2},
14 /* length */,
{3 * 8, 2},
20 /* length */,
{}};
FreqManUIList freqlist_view{
@@ -220,7 +218,8 @@ class FrequencyEditView : public View {
{"Single", 0},
{"Range", 1},
{"HamRadio", 2},
{"Raw", 3},
{"Repeater", 3},
{"Raw", 4},
}};
FrequencyField field_freq_a{{13 * 8, 4 * 16}};
+198
View File
@@ -0,0 +1,198 @@
/*
* 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_fsk_rx.hpp"
#include "audio.hpp"
#include "baseband_api.hpp"
#include "portapack_persistent_memory.hpp"
#include "string_format.hpp"
#include "utility.hpp"
#include "file_path.hpp"
#include "ui_freqman.hpp"
using namespace portapack;
namespace pmem = portapack::persistent_memory;
void FskRxLogger::log_raw_data(const std::string& data, const uint32_t frequency) {
std::string entry = "Raw: F:" + to_string_dec_uint(frequency) + "Hz";
// // Raw hex dump of all the codewords
// for (size_t c = 0; c < 16; c++)
// entry += to_string_hex(packet[c], 8) + " ";
log_file.write_entry(data + entry);
}
void FskRxLogger::log_decoded(Timestamp timestamp, const std::string& text) {
log_file.write_entry(timestamp, text);
}
namespace ui {
//---------------------------------------------------------------------------------------------------------------
// Console View
//---------------------------------------------------------------------------------------------------------------
FskRxAppConsoleView::FskRxAppConsoleView(NavigationView& nav, Rect parent_rect)
: View(parent_rect), nav_{nav} {
add_child(&console);
};
void FskRxAppConsoleView::on_packet(uint32_t value, bool is_data) {
if (is_data) {
console.write(to_string_dec_uint(value) + " ");
}
}
void FskRxAppConsoleView::on_show() {
hidden(false);
}
void FskRxAppConsoleView::on_hide() {
hidden(true);
}
FskRxAppConsoleView::~FskRxAppConsoleView() {
}
//---------------------------------------------------------------------------------------------------------------
// Spectrum View
//---------------------------------------------------------------------------------------------------------------
FskRxAppView::FskRxAppView(NavigationView& nav, Rect parent_rect)
: View(parent_rect), nav_{nav} {
add_child(&waterfall);
hidden(true);
}
FskRxAppView::~FskRxAppView() {
}
void FskRxAppView::focus() {
}
void FskRxAppView::on_show() {
hidden(false);
waterfall.start();
}
void FskRxAppView::on_hide() {
hidden(true);
waterfall.stop();
}
//---------------------------------------------------------------------------------------------------------------
// Base View
//---------------------------------------------------------------------------------------------------------------
FskxRxMainView::FskxRxMainView(NavigationView& nav)
: nav_{nav} {
add_children({&tab_view,
&view_data,
&view_stream,
&labels,
&rssi,
&channel,
&field_rf_amp,
&field_lna,
&field_vga,
&field_frequency,
&deviation_frequency,
&record_view});
baseband::run_image(portapack::spi_flash::image_tag_fskrx);
// DEBUG
record_view.on_error = [&nav](std::string message) {
nav.display_modal("Error", message);
};
deviation_frequency.on_change = [this](rf::Frequency f) {
refresh_ui(f);
};
// Set initial sampling rate
/* Bandwidth of 2FSK is 2 * Deviation */
record_view.set_sampling_rate(initial_deviation * 2);
field_frequency.set_value(initial_target_frequency);
deviation_frequency.set_value(initial_deviation);
logger.append(logs_dir / u"FSKRX.TXT");
baseband::set_fsk(initial_deviation);
audio::output::start();
receiver_model.enable();
}
void FskxRxMainView::handle_decoded(Timestamp timestamp, const std::string& prefix) {
if (logging()) {
logger.log_decoded(timestamp, prefix);
}
}
void FskxRxMainView::refresh_ui(rf::Frequency deviationHz) {
/* Nyquist would imply a sample rate of 2x bandwidth, but because the ADC
* provides 2 values (I,Q), the sample_rate is equal to bandwidth here. */
/* Bandwidth of 2FSK is 2 * Deviation */
auto sample_rate = deviationHz * 2;
/* base_rate (bandwidth) is used for FFT calculation and display LCD, and also in recording writing SD Card rate. */
/* ex. sampling_rate values, 4Mhz, when recording 500 kHz (BW) and fs 8 Mhz, when selected 1 Mhz BW ... */
/* ex. recording 500kHz BW to .C16 file, base_rate clock 500kHz x2(I,Q) x 2 bytes (int signed) =2MB/sec rate SD Card. */
if (!view_stream.hidden()) {
view_stream.waterfall.stop();
}
// record_view determines the correct oversampling to apply and returns the actual sample rate.
// NB: record_view is what actually updates proc_capture baseband settings.
auto actual_sample_rate = record_view.set_sampling_rate(sample_rate);
// Update the radio model with the actual sampling rate.
receiver_model.set_sampling_rate(actual_sample_rate);
// Get suitable anti-aliasing BPF bandwidth for MAX2837 given the actual sample rate.
auto anti_alias_filter_bandwidth = filter_bandwidth_for_sampling_rate(actual_sample_rate);
receiver_model.set_baseband_bandwidth(anti_alias_filter_bandwidth);
if (!view_stream.hidden()) {
view_stream.waterfall.start();
}
}
void FskxRxMainView::focus() {
field_frequency.focus();
}
void FskxRxMainView::set_parent_rect(const Rect new_parent_rect) {
View::set_parent_rect(new_parent_rect);
ui::Rect waterfall_rect{0, 0, new_parent_rect.width(), new_parent_rect.height() - header_height};
view_stream.waterfall.set_parent_rect(waterfall_rect);
}
FskxRxMainView::~FskxRxMainView() {
audio::output::stop();
receiver_model.disable();
baseband::shutdown();
}
} /* namespace ui */
+178
View File
@@ -0,0 +1,178 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2018 Furrtek
* Copyright (C) 2023 gullradriel, Nilorea Studio Inc.
*
* 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_FSK_RX_H__
#define __UI_FSK_RX_H__
#include "ui_widget.hpp"
#include "ui_freq_field.hpp"
#include "ui_receiver.hpp"
#include "ui_record_view.hpp"
#include "ui_rssi.hpp"
#include "ui_spectrum.hpp"
#include "ui_styles.hpp"
#include "ui_tabview.hpp"
#include "app_settings.hpp"
#include "log_file.hpp"
#include "radio_state.hpp"
#include "pocsag_app.hpp"
#include <functional>
class FskRxLogger {
public:
Optional<File::Error> append(const std::filesystem::path& filename) {
return log_file.append(filename);
}
void log_raw_data(const std::string& data, const uint32_t frequency);
void log_decoded(Timestamp timestamp, const std::string& text);
private:
LogFile log_file{};
};
namespace ui {
class FskRxAppConsoleView : public View {
public:
FskRxAppConsoleView(NavigationView& nav, Rect parent_rec);
~FskRxAppConsoleView();
std::string title() const override { return "FSK RX Data"; };
void on_packet(uint32_t value, bool is_data);
void on_show() override;
void on_hide() override;
private:
NavigationView& nav_;
Console console{
{0, 0, 240, 224}};
};
class FskRxAppView : public View {
public:
FskRxAppView(NavigationView& nav, Rect parent_rect);
~FskRxAppView();
void focus() override;
void on_show() override;
void on_hide() override;
spectrum::WaterfallView waterfall{};
std::string title() const override { return "FSK RX Stream"; };
private:
NavigationView& nav_;
RxRadioState radio_state_{};
};
class FskxRxMainView : public View {
public:
FskxRxMainView(NavigationView& nav);
~FskxRxMainView();
void focus() override;
void set_parent_rect(const Rect new_parent_rect) override;
std::string title() const override { return "FSK RX"; };
private:
static constexpr uint32_t initial_target_frequency = 902'075'000;
static constexpr ui::Dim header_height = (5 * 16);
uint32_t initial_deviation{3750};
bool logging() const { return false; };
bool logging_raw() const { return false; };
NavigationView& nav_;
Rect view_rect = {0, header_height, 240, 224};
FskRxAppView view_stream{nav_, view_rect};
FskRxAppConsoleView view_data{nav_, view_rect};
TabView tab_view{
{"Data", Color::yellow(), &view_data},
{"Stream", Color::cyan(), &view_stream}};
void refresh_ui(rf::Frequency f);
void on_packet(uint32_t value, bool is_data);
void handle_decoded(Timestamp timestamp, const std::string& prefix);
uint32_t last_address = 0;
FskRxLogger logger{};
uint16_t packet_count = 0;
RxFrequencyField field_frequency{
{0 * 8, 4 * 8},
nav_};
RFAmpField field_rf_amp{
{11 * 8, 2 * 16}};
LNAGainField field_lna{
{13 * 8, 2 * 16}};
VGAGainField field_vga{
{16 * 8, 2 * 16}};
RSSI rssi{
{19 * 8 - 4, 35, 6 * 8, 4}};
Channel channel{
{19 * 8 - 4, 40, 6 * 8, 4}};
Labels labels{
{{0 * 8, 3 * 16}, "Deviation:", Color::light_grey()},
};
FrequencyField deviation_frequency{
{10 * 8, 3 * 16},
{3750, 500000},
};
// DEBUG
RecordView record_view{
{0 * 8, 4 * 16, 30 * 8, 1 * 16},
u"FSKRX_????.C16",
u"FSKRX",
RecordView::FileType::RawS16,
16384,
3};
MessageHandlerRegistration message_handler_packet{
Message::ID::AFSKData,
[this](Message* const p) {
const auto message = static_cast<const AFSKDataMessage*>(p);
this->view_data.on_packet(message->value, message->is_data);
}};
};
} /* namespace ui */
#endif /*__POCSAG_APP_H__*/
+22 -3
View File
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2023 Kyle Reed
* Copyright (C) 2024 Mark Thompson
*
* This file is part of PortaPack.
*
@@ -24,6 +25,7 @@
#include "complex.hpp"
#include "portapack.hpp"
#include "ui_fileman.hpp"
#include "file_path.hpp"
using namespace portapack;
namespace fs = std::filesystem;
@@ -40,12 +42,13 @@ IQTrimView::IQTrimView(NavigationView& nav)
&text_samples,
&text_max,
&field_cutoff,
&field_amplify,
&button_trim,
});
field_path.on_select = [this](TextField&) {
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_file(path);
};
@@ -72,6 +75,11 @@ IQTrimView::IQTrimView(NavigationView& nav)
refresh_ui();
};
field_amplify.set_value(1); // 1X is default (no amplification)
field_amplify.on_change = [this](int32_t) {
refresh_ui();
};
button_trim.on_select = [this](Button&) {
if (trim_capture()) {
profile_capture();
@@ -133,7 +141,18 @@ void IQTrimView::focus() {
void IQTrimView::refresh_ui() {
field_path.set_text(path_.filename().string());
text_samples.set(to_string_dec_uint(info_->sample_count));
text_max.set(to_string_dec_uint(info_->max_power));
// show max power after amplification applied
uint64_t power_amp = field_amplify.value() * field_amplify.value() * field_amplify.value() * field_amplify.value();
text_max.set(to_string_dec_uint(info_->max_power * power_amp));
// 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;
if ((field_amplify.value() * info_->max_iq) > clipping_limit)
text_max.set_style(&Styles::red);
else
text_max.set_style(&Styles::light_grey);
set_dirty();
}
@@ -191,7 +210,7 @@ bool IQTrimView::trim_capture() {
}
progress_ui.show_trimming();
trimmed = iq::trim_capture_with_range(path_, trim_range, progress_ui.get_callback());
trimmed = iq::trim_capture_with_range(path_, trim_range, progress_ui.get_callback(), field_amplify.value());
progress_ui.clear();
if (!trimmed)
+11 -1
View File
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2023 Kyle Reed
* Copyright (C) 2024 Mark Thompson
*
* This file is part of PortaPack.
*
@@ -107,6 +108,8 @@ class IQTrimView : public View {
{{0 * 8, 9 * 16}, "Max Pwr:", Color::light_grey()},
{{0 * 8, 10 * 16}, "Cutoff :", Color::light_grey()},
{{12 * 8, 10 * 16}, "%", Color::light_grey()},
{{0 * 8, 12 * 16}, "Amplify:", Color::light_grey()},
{{10 * 8, 12 * 16}, "x", Color::light_grey()},
};
TextField field_path{
@@ -135,7 +138,7 @@ class IQTrimView : public View {
"0"};
Text text_max{
{9 * 8, 9 * 16, 10 * 8, 1 * 16},
{9 * 8, 9 * 16, 20 * 8, 1 * 16},
"0"};
NumberField field_cutoff{
@@ -145,6 +148,13 @@ class IQTrimView : public View {
1,
' '};
NumberField field_amplify{
{9 * 8, 12 * 16},
1,
{1, 9},
1,
' '};
Button button_trim{
{20 * 8, 16 * 16, 8 * 8, 2 * 16},
"Trim"};
+127 -40
View File
@@ -27,6 +27,7 @@
#include "baseband_api.hpp"
#include "file.hpp"
#include "oversample.hpp"
#include "ui_font_fixed_8x16.hpp"
using namespace portapack;
using namespace tonekey;
@@ -34,11 +35,37 @@ using portapack::memory::map::backup_ram;
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() {
button_frequency.focus();
}
LevelView::~LevelView() {
// reset performance counters request to default
shared_memory.request_m4_performance_counter = 0;
receiver_model.disable();
baseband::shutdown();
}
@@ -58,16 +85,17 @@ LevelView::LevelView(NavigationView& nav)
&text_ctcss,
&freq_stats_rssi,
&freq_stats_db,
&audio_mode,
&freq_stats_rx,
&text_beep_squelch,
&field_beep_squelch,
&field_audio_mode,
&peak_mode,
&rssi,
&rssi_graph});
// activate vertical bar mode
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();
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]() {
int64_t def_step = freqman_entry_get_step_value(step_mode.selected_index());
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>");
};
freqman_set_modulation_option(field_mode);
field_mode.on_change = [this](size_t, OptionsField::value_t v) {
if (v != -1) {
receiver_model.disable();
baseband::shutdown();
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) {
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) {
audio::output::stop();
} else if (v == 1) {
audio::set_rate(audio_sampling_rate);
audio::output::start();
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) {
if (v == 0) {
@@ -135,82 +168,124 @@ LevelView::LevelView(NavigationView& nav)
peak_mode.set_selected_index(2);
rssi_resolution.set_selected_index(1);
// FILL STEP OPTIONS
freqman_set_modulation_option(field_mode);
freqman_set_step_option_short(step_mode);
freq_stats_rssi.set_style(&Styles::white);
freq_stats_db.set_style(&Styles::white);
freq_stats_rx.set_style(&Styles::white);
}
void LevelView::on_statistics_update(const ChannelStatistics& statistics) {
static int last_max_db = -1000;
static int last_min_rssi = -1000;
static int last_avg_rssi = -1000;
static int last_max_rssi = -1000;
static int16_t last_max_db = 0;
static uint8_t last_min_rssi = 0;
static uint8_t last_avg_rssi = 0;
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);
bool refresh_db = false;
bool refresh_rssi = false;
// refresh db
if (last_max_db != statistics.max_db) {
refresh_db = true;
}
if (last_min_rssi != rssi.get_min() || last_avg_rssi != rssi.get_avg() || last_max_rssi != rssi.get_max()) {
refresh_rssi = true;
}
if (refresh_db) {
last_max_db = statistics.max_db;
freq_stats_db.set("Power: " + to_string_dec_int(statistics.max_db) + " db");
}
if (refresh_rssi) {
last_min_rssi = rssi.get_min();
last_avg_rssi = rssi.get_avg();
last_max_rssi = rssi.get_max();
freq_stats_rssi.set("RSSI: " + to_string_dec_int(rssi.get_min()) + "/" + to_string_dec_int(rssi.get_avg()) + "/" + to_string_dec_int(rssi.get_max()) + ",dt: " + to_string_dec_int(rssi.get_delta()));
// refresh rssi
if (last_min_rssi != rssi_graph.get_graph_min() || last_avg_rssi != rssi_graph.get_graph_avg() || last_max_rssi != rssi_graph.get_graph_max()) {
last_min_rssi = rssi_graph.get_graph_min();
last_avg_rssi = rssi_graph.get_graph_avg();
last_max_rssi = rssi_graph.get_graph_max();
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 */
size_t LevelView::change_mode(freqman_index_t new_mod) {
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) {
case AM_MODULATION:
audio_sampling_rate = audio::Rate::Hz_12000;
freqman_set_bandwidth_option(new_mod, field_bw);
baseband::run_image(portapack::spi_flash::image_tag_am_audio);
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
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;
case NFM_MODULATION:
audio_sampling_rate = audio::Rate::Hz_24000;
freqman_set_bandwidth_option(new_mod, field_bw);
baseband::run_image(portapack::spi_flash::image_tag_nfm_audio);
receiver_model.set_modulation(ReceiverModel::Mode::NarrowbandFMAudio);
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
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;
case WFM_MODULATION:
audio_sampling_rate = audio::Rate::Hz_48000;
freqman_set_bandwidth_option(new_mod, field_bw);
baseband::run_image(portapack::spi_flash::image_tag_wfm_audio);
receiver_model.set_modulation(ReceiverModel::Mode::WidebandFMAudio);
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) only/default
// bw 200k (0) default
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;
case SPEC_MODULATION:
audio_sampling_rate = audio::Rate::Hz_24000;
freqman_set_bandwidth_option(new_mod, field_bw);
baseband::run_image(portapack::spi_flash::image_tag_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::set_sample_rate(sampling_rate, get_oversample_rate(sampling_rate));
// The radio needs to know the effective sampling rate.
auto actual_sampling_rate = get_actual_sample_rate(sampling_rate);
receiver_model.set_sampling_rate(actual_sampling_rate);
@@ -225,6 +300,18 @@ size_t LevelView::change_mode(freqman_index_t new_mod) {
receiver_model.set_sampling_rate(3072000);
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();
}
+47 -19
View File
@@ -55,16 +55,30 @@ class LevelView : public View {
NavigationView& nav_;
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);
void on_statistics_update(const ChannelStatistics& statistics);
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};
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{
{{0 * 8, 0 * 16}, "LNA: VGA: AMP: VOL: ", Color::light_grey()},
@@ -102,28 +116,32 @@ class LevelView : public View {
{0 * 8, 2 * 16 + 8, 15 * 8, 1 * 8},
""};
OptionsField audio_mode{
OptionsField field_audio_mode{
{21 * 8, 1 * 16},
9,
{
{"audio off", 0},
{"audio on", 1}
//{"tone on", 2},
//{"tone off", 3},
}};
{{"audio off", 0},
{"audio on", 1}}};
Text text_ctcss{
{22 * 8, 3 * 16 + 4, 8 * 8, 1 * 8},
""};
Text text_beep_squelch{
{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{
{0 * 8, 3 * 16 + 4, 22 * 8, 14},
{0 * 8, 3 * 16 + 4, 15 * 8, 1 * 16},
};
// Power: -XXX 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{
@@ -138,6 +156,7 @@ class LevelView : public View {
{"peak:5s", 5000},
{"peak:10s", 10000},
}};
OptionsField rssi_resolution{
{44 + 20 * 8, 4 * 16 + 4},
4,
@@ -149,14 +168,23 @@ class LevelView : public View {
{"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{
// 240x320 =>
{0, 5 * 16 + 4, 240 - 5 * 8, 320 - (5 * 16 + 4)},
{0, 6 * 16 + 8, 240 - 5 * 8, 320 - (6 * 16)},
};
RSSI rssi{
// 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);
@@ -25,6 +25,8 @@
#include "convert.hpp"
#include "file_reader.hpp"
#include "string_format.hpp"
#include "audio.hpp"
#include "file_path.hpp"
using namespace portapack;
@@ -34,11 +36,39 @@ void GlassView::focus() {
}
GlassView::~GlassView() {
audio::output::stop();
receiver_model.set_sampling_rate(3072000); // Just a hack to avoid hanging other apps
receiver_model.disable();
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(&Styles::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(&Styles::white);
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) {
if (mode == LOOKING_GLASS_SINGLEPASS) {
// <20MHz spectrum mode
@@ -173,6 +203,8 @@ void GlassView::on_channel_spectrum(const ChannelSpectrum& spectrum) {
// we actually need SCREEN_W (240) of those bins
for (uint8_t bin = 0; bin < bin_length; bin++) {
get_max_power(spectrum, bin, max_power);
if (max_power > range_max_power)
range_max_power = max_power;
// process dc spike if enable
if (bin == 119) {
uint8_t next_max_power = 0;
@@ -184,14 +216,21 @@ void GlassView::on_channel_spectrum(const ChannelSpectrum& spectrum) {
}
}
// 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
}
}
if (mode != LOOKING_GLASS_SINGLEPASS) {
f_center += looking_glass_step;
retune();
} else
} else {
baseband::spectrum_streaming_start();
}
}
void GlassView::on_hide() {
@@ -327,17 +366,20 @@ GlassView::GlassView(
&field_lna,
&field_vga,
&field_range,
&steps_config,
//&steps_config,
&scan_type,
&view_config,
&level_integration,
&field_volume,
&filter_config,
&field_rf_amp,
&range_presets,
&button_beep_squelch,
&field_marker,
&field_trigger,
&button_jump,
&button_rst,
&field_rx_iq_phase_cal,
&freq_stats});
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_max.set_step(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) {
mode = v;
@@ -474,6 +516,13 @@ GlassView::GlassView(
reset_live_view();
};
field_rx_iq_phase_cal.set_range(0, hackrf_r9 ? 63 : 31); // max2839 has 6 bits [0..63], max2837 has 5 bits [0..31]
field_rx_iq_phase_cal.set_value(get_spec_iq_phase_calibration_value()); // using accessor function of AnalogAudioView to read iq_phase_calibration_value from rx_audio.ini
field_rx_iq_phase_cal.on_change = [this](int32_t v) {
set_spec_iq_phase_calibration_value(v); // using accessor function of AnalogAudioView to write inside SPEC submenu, register value to max283x and save it to rx_audio.ini
};
set_spec_iq_phase_calibration_value(get_spec_iq_phase_calibration_value()); // initialize iq_phase_calibration in radio
display.scroll_set_area(109, 319);
// trigger:
@@ -489,11 +538,41 @@ 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_squelch_level(0);
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();
}
uint8_t GlassView::get_spec_iq_phase_calibration_value() { // define accessor functions inside AnalogAudioView to read & write real iq_phase_calibration_value
return iq_phase_calibration_value;
}
void GlassView::set_spec_iq_phase_calibration_value(uint8_t cal_value) { // define accessor functions
iq_phase_calibration_value = cal_value;
radio::set_rx_max283x_iq_phase_calibration(iq_phase_calibration_value);
}
void GlassView::load_presets() {
File presets_file;
auto error = presets_file.open("LOOKINGGLASS/PRESETS.TXT");
auto error = presets_file.open(looking_glass_dir / u"PRESETS.TXT");
presets_db.clear();
// Add the "Manual" entry.
@@ -69,6 +69,9 @@ class GlassView : public View {
void on_hide() override;
void focus() override;
uint8_t get_spec_iq_phase_calibration_value();
void set_spec_iq_phase_calibration_value(uint8_t cal_value);
private:
NavigationView& nav_;
RxRadioState radio_state_{ReceiverModel::Mode::SpectrumAnalysis};
@@ -79,8 +82,11 @@ class GlassView : public View {
uint8_t filter_index = 0; // OFF
uint8_t trigger = 32;
uint8_t mode = LOOKING_GLASS_FASTSCAN;
uint8_t live_frequency_view = 0; // Spectrum
uint8_t live_frequency_integrate = 3; // Default (3 * old value + new_value) / 4
uint8_t live_frequency_view = 0; // Spectrum
uint8_t live_frequency_integrate = 3; // Default (3 * old value + new_value) / 4
uint8_t iq_phase_calibration_value{15}; // initial default RX IQ phase calibration value , used for both max2837 & max2839
int32_t beep_squelch = 20; // range from -100 to +20, >=20 disabled
bool beep_enabled = false; // activate on bip button click
app_settings::SettingsManager settings_{
"rx_glass"sv,
app_settings::Mode::RX,
@@ -93,6 +99,9 @@ class GlassView : public View {
{"scan_mode"sv, &mode},
{"freq_view"sv, &live_frequency_view},
{"freq_integrate"sv, &live_frequency_integrate},
{"iq_phase_calibration"sv, &iq_phase_calibration_value}, // we are saving and restoring that CAL from Settings.
{"beep_squelch"sv, &beep_squelch},
{"beep_enabled"sv, &beep_enabled},
}};
struct preset_entry {
@@ -101,7 +110,10 @@ class GlassView : public View {
std::string label{};
};
int32_t map(int32_t value, int32_t fromLow, int32_t fromHigh, int32_t toLow, int32_t toHigh);
std::vector<preset_entry> presets_db{};
void manage_beep_audio();
void update_display_beep();
void update_min(int32_t v);
void update_max(int32_t v);
void update_range_field();
@@ -148,6 +160,8 @@ class GlassView : public View {
int32_t steps = 1;
bool locked_range = false;
uint8_t range_max_power = 0;
uint8_t range_max_power_counter = 0;
uint8_t max_power = 0;
rf::Frequency max_freq_hold = 0;
rf::Frequency last_max_freq = 0;
@@ -159,9 +173,10 @@ class GlassView : public View {
Labels labels{
{{0, 0 * 16}, "MIN: MAX: LNA VGA ", Color::light_grey()},
{{0, 1 * 16}, "RANGE: FILTER: AMP:", Color::light_grey()},
{{0, 2 * 16}, "PRESET:", Color::light_grey()},
{{0, 3 * 16}, "MARKER: MHz", Color::light_grey()},
{{0, 4 * 16}, "RES: STEP:", Color::light_grey()}};
{{0, 2 * 16}, "P:", Color::light_grey()},
{{0, 3 * 16}, "MARKER: MHz RXIQCAL", Color::light_grey()},
//{{0, 4 * 16}, "RES: STEPS:", Color::light_grey()}};
{{0, 4 * 16}, "RES: VOL:", Color::light_grey()}};
NumberField field_frequency_min{
{4 * 8, 0 * 16},
@@ -200,14 +215,26 @@ class GlassView : public View {
{28 * 8, 1 * 16}};
OptionsField range_presets{
{7 * 8, 2 * 16},
{2 * 8, 2 * 16},
20,
{}};
ButtonWithEncoder button_beep_squelch{
{240 - 8 * 8, 2 * 16 + 4, 8 * 8, 1 * 8},
""};
TextField field_marker{
{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{
{4 * 8, 4 * 16},
3,
@@ -215,7 +242,10 @@ class GlassView : public View {
2,
' '};
OptionsField steps_config{
AudioVolumeField field_volume{
{13 * 8, 4 * 16}};
/*OptionsField steps_config{
{13 * 8, 4 * 16},
3,
{
@@ -225,7 +255,7 @@ class GlassView : public View {
{"100", 100},
{"250", 250},
{"500", 500},
}};
}};*/
OptionsField scan_type{
{17 * 8, 4 * 16},
+370 -378
View File
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
* Copyright (C) 2024 Mark Thompson
*
* This file is part of PortaPack.
*
@@ -31,6 +32,7 @@
#include "tonesets.hpp"
#include "ui_tone_key.hpp"
#include "wm8731.hpp"
#include "radio.hpp"
#include <cstring>
@@ -51,7 +53,7 @@ void MicTXView::focus() {
field_rxfrequency.focus();
break;
default:
field_va.focus();
check_va.focus();
break;
}
}
@@ -60,24 +62,57 @@ void MicTXView::update_vumeter() {
vumeter.set_value(audio_level);
}
void MicTXView::update_tx_icon() {
tx_icon.set_foreground(transmitting ? Color::red() : Color::black());
tx_icon.set_background(transmitting ? Color::yellow() : Color::black());
}
void MicTXView::on_tx_progress(const bool done) {
// Roger beep played, stop transmitting
if (done)
set_tx(false);
}
uint8_t MicTXView::shift_bits(void) {
uint8_t shift_bits_s16{4}; // shift bits factor to the captured ADC S16 audio sample.
if (audio::debug::codec_name() == "WM8731") {
switch (ak4951_alc_and_wm8731_boost_GUI) {
case 0: // +12 dBs respect reference level orig fw 1.5.x fw FM : when +20dB's boost ON) and shift bits (>>8),
shift_bits_s16 = 6; // now mic-boost on (+20dBs) and shift bits (>>6), +20+12=32 dBs (orig fw +20 dBs+ 0dBs)=> +12dB's respect ref.
break;
case 1: // +06 dBs reference level, (when +20dB's boost ON)
shift_bits_s16 = 7; // now mic-boost on (+20dBs) and shift bits (>>7), +20+06=26 dBs (orig fw +20 dBs+ 0dBs) => +06dB's respect ref.
break;
case 2:
shift_bits_s16 = 4; // +04 dBs respect ref level, (when +20dB's boost OFF)
break; // now mic-boost off (+00dBs) shift bits (4) (+0+24dB's)=24 dBs => +04dB's respect ref.
case 3:
shift_bits_s16 = 5; // -02 dBs respect ref level, (when +20dB's boost OFF)
break; // now mic-boost off (+00dBs) shift bits (5) (+0+18dB's)=18 dBs => -02dB's respect ref.
case 4:
shift_bits_s16 = 6; // -08 dBs respect ref level, (when +20dB's boost OFF)
break; // now mic-boost off (+00dBs) shift bits (6) (+0+12dB's)=12 dBs => -08dB's respect ref.
}
} else {
shift_bits_s16 = 8; // Initialized default fixed >>8_FM for FM tx mod, shift audio data for AK4951, using top 8 bits s16 data (>>8)
}
return shift_bits_s16;
}
void MicTXView::configure_baseband() {
// TODO: Can this use the transmitter model instead?
baseband::set_audiotx_config(
sampling_rate / 20, // Update vu-meter at 20Hz
transmitting ? transmitter_model.channel_bandwidth() : 0,
mic_gain,
shift_bits_s16, // to be used in dsp_modulate
mic_gain_x10 / 10.0,
shift_bits(), // to be used in dsp_modulate
8, // bits per sample
TONES_F2D(tone_key_frequency(tone_key_index), sampling_rate),
enable_am,
enable_dsb,
enable_usb,
enable_lsb);
(mic_mod_index == MIC_MOD_AM),
(mic_mod_index == MIC_MOD_DSB),
(mic_mod_index == MIC_MOD_USB),
(mic_mod_index == MIC_MOD_LSB));
}
void MicTXView::set_tx(bool enable) {
@@ -95,18 +130,30 @@ 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
} else {
if (transmitting && rogerbeep_enabled) {
baseband::request_beep(); // Transmit the roger beep
transmitting = false; // And flag the end of the transmission so ...
} else { // (if roger beep was enabled, this will be executed after the beep ends transmitting.
baseband::request_roger_beep(); // Transmit the roger beep
transmitting = false; // Flag the end of the transmission (transmitter will be disabled after the beep)
} else {
transmitting = false;
configure_baseband();
transmitter_model.disable();
if (rx_enabled) // If audio RX is enabled and we've been transmitting
if (rx_enabled) {
rxaudio(true); // Turn back on audio RX
// TODO FIXME: this isn't working: vu meter isn't going to 0:
vumeter.set_value(0); // Reset vumeter
vumeter.dirty(); // Force to refresh vumeter.
}
}
}
update_tx_icon();
}
bool MicTXView::tx_button_held() {
const auto switches_state = get_switches_state();
return switches_state[(size_t)ui::KeyEvent::Select];
};
void MicTXView::do_timing() {
if (va_enabled) {
if (!transmitting) {
@@ -128,7 +175,8 @@ void MicTXView::do_timing() {
if ((decay_timer >> 8) >= decay_ms) {
decay_timer = 0;
attack_timer = 0;
set_tx(false);
if (!button_touch && !tx_button_held())
set_tx(false);
} else {
decay_timer += lcd_frame_duration;
}
@@ -138,42 +186,39 @@ void MicTXView::do_timing() {
}
} else {
// Check for PTT release
const auto switches_state = get_switches_state();
if (!switches_state[4] && transmitting && !button_touch) // Select button
if (transmitting && !button_touch && !tx_button_held())
set_tx(false);
}
}
void MicTXView::rxaudio(bool is_on) {
if (is_on) {
void MicTXView::rxaudio(bool enable) {
if (enable) {
audio::input::stop();
baseband::shutdown();
if (enable_am || enable_usb || enable_lsb || enable_dsb) { // "NFM/FM", 0, " WFM ", 1, " AM ", 2, " USB ", 3, " LSB ", 4, " DSB-SC", 5
baseband::run_image(portapack::spi_flash::image_tag_am_audio);
receiver_model.set_modulation(ReceiverModel::Mode::AMAudio); // that AM demodulation engine is common to all Amplitude mod : AM/USB/LSB/DSB (2,3,4,5)
if (options_mode.selected_index() < 5) // We will be called here with 2,3,4,5. We treat here demod. filter 2,3,4; (excluding DSB-C case (5) it is treated more down).
receiver_model.set_am_configuration(options_mode.selected_index() - 1); // selecting proper filter(2,3,4). 2-1=1=>6k-AM(1), 3-1=2=>+3k-USB(2), 4-1=3=>-3K-LSB(3),
} else { // We are in NFM/FM or WFM (NFM BW:8k5 or 11k / FM BW 16k / WFM BW:200k)
if (enable_wfm) { // WFM, BW 200Khz aprox, or the two new addional BW filters (180k, 40k)
switch (mic_mod_index) {
case MIC_MOD_NFM: // NFM BW:8k5 or 11k / FM BW 16k
baseband::run_image(portapack::spi_flash::image_tag_nfm_audio);
receiver_model.set_modulation(ReceiverModel::Mode::NarrowbandFMAudio);
// receiver_model.set_nbfm_configuration(n); is called above, depending user's selection (8k5, 11k, 16k).
break;
case MIC_MOD_WFM: // WFM, BW 200Khz aprox, or the two new addional BW filters (180k, 40k)
baseband::run_image(portapack::spi_flash::image_tag_wfm_audio);
receiver_model.set_modulation(ReceiverModel::Mode::WidebandFMAudio);
// receiver_model.set_wfm_configuration(n); // it is called above, depending user's selection (200k, 180k, 0k).
} else { // NFM BW:8k5 or 11k / FM BW 16k
baseband::run_image(portapack::spi_flash::image_tag_nfm_audio);
receiver_model.set_modulation(ReceiverModel::Mode::NarrowbandFMAudio); //
// receiver_model.set_nbfm_configuration(n); is called above, depending user's selection (8k5, 11k, 16k).
}
// receiver_model.set_wfm_configuration(n); is called above, depending user's selection (200k, 180k, 0k).
break;
case MIC_MOD_AM:
case MIC_MOD_DSB:
case MIC_MOD_USB:
case MIC_MOD_LSB:
baseband::run_image(portapack::spi_flash::image_tag_am_audio);
receiver_model.set_modulation(ReceiverModel::Mode::AMAudio); // that AM demodulation engine is common to all Amplitude mod : AM/USB/LSB/DSB (2,3,4,5)
if (options_mode.selected_index() < 5) // We will be called here with 2,3,4,5. We treat here demod. filter 2,3,4; (excluding DSB-C case (5) it is treated more down).
receiver_model.set_am_configuration(options_mode.selected_index() - 1); // selecting proper filter(2,3,4). 2-1=1=>6k-AM(1), 3-1=2=>+3k-USB(2), 4-1=3=>-3K-LSB(3),
break;
}
if (bool_same_F_tx_rx_enabled) // when stop TX, define to which freq RX we return
receiver_model.set_target_frequency(tx_frequency); // Update freq also for RX = TX
else
receiver_model.set_target_frequency(rx_frequency); // Now with separate freq controls!
receiver_model.set_lna(rx_lna);
receiver_model.set_vga(rx_vga);
receiver_model.set_rf_amp(rx_amp);
receiver_model.set_target_frequency(bool_same_F_tx_rx_enabled ? tx_frequency : rx_frequency);
receiver_model.enable();
audio::output::start();
} else { // These incredibly convoluted steps are required for the vumeter to reappear when stopping RX.
@@ -183,14 +228,82 @@ void MicTXView::rxaudio(bool is_on) {
baseband::run_image(portapack::spi_flash::image_tag_mic_tx);
audio::output::stop();
audio::input::start(ak4951_alc_and_wm8731_boost_GUI); // When detected AK4951 => set up ALC mode; when detected WM8731 => set up mic_boost ON/OFF.
audio::input::start(ak4951_alc_and_wm8731_boost_GUI, mic_to_HP_enabled); // set up ALC mode (AK4951) or set up mic_boost ON/OFF (WM8731). and the check box "Hear Mic"
portapack::pin_i2s0_rx_sda.mode(3);
configure_baseband();
}
}
void MicTXView::set_ptt_visibility(bool v) {
tx_button.hidden(!v);
void MicTXView::set_rxbw_options(void) {
using option_t = std::pair<std::string, int32_t>;
using options_t = std::vector<option_t>;
options_t rxbw; // Aux structure to change dynamically field_rxbw contents
switch (mic_mod_index) {
case MIC_MOD_NFM:
freqman_set_bandwidth_option(NFM_MODULATION, field_rxbw); // restore dynamic field_rxbw value with NFM options from freqman_db.cpp
field_rxbw.set_by_value(receiver_model.nbfm_configuration());
break;
case MIC_MOD_WFM:
freqman_set_bandwidth_option(WFM_MODULATION, field_rxbw); // restore dynamic field_rxbw value with WFM options from freqman_db.cpp
field_rxbw.set_by_value(receiver_model.wfm_configuration());
break;
case MIC_MOD_AM:
rxbw.emplace_back("DSB 9k", 0); // we offer in AM DSB two audio BW 9k / 6k.
rxbw.emplace_back("DSB 6k", 1);
field_rxbw.set_options(rxbw); // store that aux GUI option to the field_rxbw.
break;
case MIC_MOD_DSB:
rxbw.emplace_back("USB+3k", 0); // added dynamically two options (index 0,1) to that DSB-SC case to the field_rxbw value.
rxbw.emplace_back("LSB-3k", 1);
field_rxbw.set_options(rxbw); // store that aux GUI option to the field_rxbw.
break;
case MIC_MOD_USB:
rxbw.emplace_back("USB+3k", 0); // locked a fixed option, to display it.
field_rxbw.set_options(rxbw); // store that aux GUI option to the field_rxbw.
break;
case MIC_MOD_LSB:
rxbw.emplace_back("LSB-3k", 0); // locked a fixed option, to display it.
field_rxbw.set_options(rxbw); // store that aux GUI option to the field_rxbw.
break;
}
}
void MicTXView::set_rxbw_defaults(bool use_app_settings) {
if (use_app_settings) {
field_bw.set_value(transmitter_model.channel_bandwidth() / 1000);
field_rxbw.set_by_value(rxbw_index);
} else if (mic_mod_index == MIC_MOD_NFM) {
field_bw.set_value(10); // NFM TX bw 10k, RX bw 16k (2) default
field_rxbw.set_by_value(2);
} else if (mic_mod_index == MIC_MOD_WFM) {
field_bw.set_value(75); // WFM TX bw 75K, RX bw 200k (0) default
field_rxbw.set_by_value(0);
}
// field_bw is hidden in other modulation cases
}
void MicTXView::update_receiver_rxbw(void) {
// In Previous fw versions, that nbfm_configuration(n) was done in any mode (FM/AM/SSB/DSB)...strictly speaking only need it in (NFM/FM)
// In AM TX mode, we will allow both independent RX audio BW : AM 9K (9K00AE3 / AM 6K (6K00AE3). (In AM option v can be 0 (9k), 1 (6k)
// In DSB-SC TX mode, we will allow both independent RX SSB demodulation (USB / LSB side band). in that submenu, v is 0 (SSB1 USB) or 1 (SSB2 LSB)
switch (mic_mod_index) {
case MIC_MOD_NFM:
receiver_model.set_nbfm_configuration(rxbw_index); // we are in NFM/FM case, we need to select proper NFM/FM RX channel filter, NFM BW 8K5(0), NFM BW 11K(1), FM BW 16K (2)
break;
case MIC_MOD_WFM:
receiver_model.set_wfm_configuration(rxbw_index); // we are in WFM case, we need to select proper WFB RX BW filter, WFM BW 200K(0), WFM BW 180K(1), WFM BW 40K(2)
break;
case MIC_MOD_AM:
receiver_model.set_am_configuration(rxbw_index); // we are in AM TX mode, we need to select proper AM full path config AM-9K filter. 0+0 =>AM-9K(0), 0+1=1 =>AM-6K(1),
break;
case MIC_MOD_USB:
case MIC_MOD_LSB:
break; // change can't happen in these modes because there's only one BW choice
case MIC_MOD_DSB:
receiver_model.set_am_configuration(rxbw_index + 2); // we are in DSB-SC TX mode, we need to select proper SSB filter. 0+2 =>usb(2), 1+2=3 =>lsb(3),
break;
}
}
MicTXView::MicTXView(
@@ -199,400 +312,254 @@ MicTXView::MicTXView(
baseband::run_image(portapack::spi_flash::image_tag_mic_tx);
if (audio::debug::codec_name() == "WM8731") {
add_children({&labels_WM8731, // we have audio codec WM8731, same MIC menu as original.
&vumeter,
&options_gain, // MIC GAIN float factor on the GUI.
&options_wm8731_boost_mode,
// &check_va,
&field_va,
&field_va_level,
&field_va_attack,
&field_va_decay,
&field_bw,
&tx_view, // it already integrates previous rfgain, rfamp.
&options_mode,
&field_frequency,
&options_tone_key,
&check_rogerbeep,
&check_common_freq_tx_rx, // added to handle common or separate freq- TX/RX
&check_rxactive,
&field_volume,
&field_rxbw,
&field_squelch,
&field_rxfrequency,
&field_rxlna,
&field_rxvga,
&field_rxamp,
&tx_button});
bool wm = (audio::debug::codec_name() == "WM8731");
add_children({&labels_both,
wm ? &labels_WM8731 : &labels_AK4951, // enable ALC if AK4951
&vumeter,
&options_gain, // MIC GAIN float factor on the GUI.
wm ? &options_wm8731_boost_mode : &options_ak4951_alc_mode,
&check_va,
&field_va_level,
&field_va_attack,
&field_va_decay,
&field_bw,
&tx_view, // it already integrates previous rfgain, rfamp.
&options_mode,
&field_frequency,
&options_tone_key,
&check_rogerbeep,
&check_mic_to_HP, // check box to activate "hear mic"
&check_common_freq_tx_rx, // added to handle common or separate freq- TX/RX
&check_rxactive,
&field_volume,
&field_rxbw,
&field_squelch,
&field_rxfrequency,
&field_rxlna,
&field_rxvga,
&field_rxamp,
&field_tx_iq_phase_cal,
&tx_button,
&tx_icon});
} else {
add_children({&labels_AK4951, // we have audio codec AK4951, enable Automatic Level Control
&vumeter,
&options_gain,
&options_ak4951_alc_mode,
// &check_va,
&field_va,
&field_va_level,
&field_va_attack,
&field_va_decay,
&field_bw,
&tx_view, // it already integrates previous rfgain, rfamp.
&options_mode,
&field_frequency,
&options_tone_key,
&check_rogerbeep,
&check_common_freq_tx_rx, // added to handle common or separate freq- TX/RX
&check_rxactive,
&field_volume,
&field_rxbw,
&field_squelch,
&field_rxfrequency,
&field_rxlna,
&field_rxvga,
&field_rxamp,
&tx_button});
}
set_rxbw_options();
set_rxbw_defaults(settings_.loaded());
tone_keys_populate(options_tone_key);
options_tone_key.set_selected_index(tone_key_index);
options_tone_key.on_change = [this](size_t i, int32_t) {
tone_key_index = i;
};
options_tone_key.set_selected_index(0);
check_rogerbeep.set_value(rogerbeep_enabled);
check_rogerbeep.on_select = [this](Checkbox&, bool v) {
rogerbeep_enabled = v;
};
field_rxlna.set_value(receiver_model.lna());
field_rxlna.on_change = [this](int32_t v) {
receiver_model.set_lna(v);
};
field_rxvga.set_value(receiver_model.vga());
field_rxvga.on_change = [this](int32_t v) {
receiver_model.set_vga(v);
};
field_rxamp.set_value(receiver_model.rf_amp());
field_rxamp.on_change = [this](int32_t 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) {
mic_gain = v / 10.0;
mic_gain_x10 = v;
configure_baseband();
};
options_gain.set_selected_index(1); // x1.0 preselected default.
options_gain.set_by_value(mic_gain_x10); // x1.0 preselected default.
field_rxbw.on_change = [this](size_t, int32_t v) {
rxbw_index = v;
update_receiver_rxbw();
};
field_rxbw.set_by_value(rxbw_index);
field_squelch.set_value(receiver_model.squelch_level());
field_squelch.on_change = [this](int32_t v) {
receiver_model.set_squelch_level(v);
};
if (audio::debug::codec_name() == "WM8731") {
options_wm8731_boost_mode.on_change = [this](size_t, int8_t v) {
switch (v) {
case 0: // +12 dBs respect reference level orig fw 1.5.x fw FM : when +20dB's boost ON) and shift bits (>>8),
shift_bits_s16 = 6; // now mic-boost on (+20dBs) and shift bits (>>6), +20+12=32 dBs (orig fw +20 dBs+ 0dBs)=> +12dB's respect ref.
break;
case 1: // +06 dBs reference level, (when +20dB's boost ON)
shift_bits_s16 = 7; // now mic-boost on (+20dBs) and shift bits (>>7), +20+06=26 dBs (orig fw +20 dBs+ 0dBs) => +06dB's respect ref.
break;
case 2:
shift_bits_s16 = 4; // +04 dBs respect ref level, (when +20dB's boost OFF)
break; // now mic-boost off (+00dBs) shift bits (4) (+0+24dB's)=24 dBs => +04dB's respect ref.
case 3:
shift_bits_s16 = 5; // -02 dBs respect ref level, (when +20dB's boost OFF)
break; // now mic-boost off (+00dBs) shift bits (5) (+0+18dB's)=18 dBs => -02dB's respect ref.
case 4:
shift_bits_s16 = 6; // -08 dBs respect ref level, (when +20dB's boost OFF)
break; // now mic-boost off (+00dBs) shift bits (6) (+0+12dB's)=12 dBs => -08dB's respect ref.
}
ak4951_alc_and_wm8731_boost_GUI = v; // 0..4, WM8731_boost dB's options, (combination boost on/off, and effective gain in captured data >>x)
audio::input::start(ak4951_alc_and_wm8731_boost_GUI); // Detected (WM8731), set up the proper wm_boost on/off, 0..4 (0,1) boost_on, (2,3,4) boost_off
configure_baseband(); // to update in real time, sending msg, var-parameters >>shift_bits FM msg, to audio_tx from M0 to M4 Proc -
ak4951_alc_and_wm8731_boost_GUI = v; // 0..4, WM8731_boost dB's options, (combination boost on/off, and effective gain in captured data >>x)
audio::input::start(ak4951_alc_and_wm8731_boost_GUI, mic_to_HP_enabled); // Detected (WM8731), set up the proper wm_boost on/off, 0..4 (0,1) boost_on, (2,3,4) boost_off,and the check box "Hear Mic"
configure_baseband(); // to update in real time, sending msg, var-parameters >>shift_bits FM msg, to audio_tx from M0 to M4 Proc -
};
options_wm8731_boost_mode.set_selected_index(3); // preset GUI index 3 as default WM -> -02 dB's.
if (!settings_.loaded()) {
ak4951_alc_and_wm8731_boost_GUI = 3; // preset GUI index 3 as default WM -> -02 dB's
}
options_wm8731_boost_mode.set_selected_index(ak4951_alc_and_wm8731_boost_GUI);
} else {
shift_bits_s16 = 8; // Initialized default fixed >>8_FM for FM tx mod, shift audio data for AK4951, using top 8 bits s16 data (>>8)
options_ak4951_alc_mode.on_change = [this](size_t, int8_t v) {
ak4951_alc_and_wm8731_boost_GUI = v; // 0..11, AK4951 Mic -Automatic volume Level Control options,
audio::input::start(ak4951_alc_and_wm8731_boost_GUI); // Detected (AK4951) ==> Set up proper ALC mode from 0..11 options
configure_baseband(); // sending fixed >>8_FM, var-parameters msg, to audiotx from this M0 to M4 process.
ak4951_alc_and_wm8731_boost_GUI = v; // 0..11, AK4951 Mic -Automatic volume Level Control options,
audio::input::start(ak4951_alc_and_wm8731_boost_GUI, mic_to_HP_enabled); // Detected (AK4951) ==> Set up proper ALC mode from 0..11 options, and the check box "Hear Mic"
configure_baseband(); // sending fixed >>8_FM, var-parameters msg, to audiotx from this M0 to M4 process.
};
if (!settings_.loaded())
ak4951_alc_and_wm8731_boost_GUI = 0;
options_ak4951_alc_mode.set_selected_index(ak4951_alc_and_wm8731_boost_GUI);
}
// options_ak4951_alc_mode.set_selected_index(0);
// WARNING: transmitter_model.set_target_frequency() and receiver_model.set_target_frequency() both update the same tuning freq, but one has an offset!
tx_frequency = transmitter_model.target_frequency();
field_frequency.set_value(transmitter_model.target_frequency());
field_frequency.set_step(receiver_model.frequency_step());
field_frequency.on_change = [this](rf::Frequency f) {
tx_frequency = f;
if (!rx_enabled) { // not activated receiver. just update freq TX
if (!rx_enabled)
transmitter_model.set_target_frequency(f);
} else { // activated receiver.
if (bool_same_F_tx_rx_enabled) // user selected common freq- TX = RX
receiver_model.set_target_frequency(f); // Update common freq also for RX
}
else if (bool_same_F_tx_rx_enabled)
receiver_model.set_target_frequency(f);
// else tuning freq will be updated when rx is enabled, transmitting starts, or when rx_frequency is changed (if rx_enabled)
};
field_frequency.on_edit = [this, &nav]() {
focused_ui = 0;
// TODO: Provide separate modal method/scheme?
auto new_view = nav.push<FrequencyKeypadView>(tx_frequency);
new_view->on_changed = [this](rf::Frequency f) {
tx_frequency = f;
if (!rx_enabled) {
transmitter_model.set_target_frequency(f);
} else {
if (bool_same_F_tx_rx_enabled)
receiver_model.set_target_frequency(f); // Update freq also for RX
}
this->field_frequency.set_value(f);
set_dirty();
};
};
field_frequency.set_value(tx_frequency);
field_bw.on_change = [this](uint32_t v) {
transmitter_model.set_channel_bandwidth(v * 1000);
};
field_bw.set_value(10); // pre-default first time, TX deviation FM for NFM / FM
// TODO: would be nice if frequency step was configurable in this app
field_frequency.set_step(receiver_model.frequency_step());
// now, no need direct update, field_rfgain, field_rfamp (it is done in ui_transmitter.cpp)
options_mode.on_change = [this](size_t, int32_t v) { // { "NFM/FM", 0 }, { " WFM ", 1 }, { "AM", 2 }, { "USB", 3 }, { "LSB", 4 }, { "DSB", 5 }
enable_am = false;
enable_usb = false;
enable_lsb = false;
enable_dsb = false;
enable_wfm = false;
using option_t = std::pair<std::string, int32_t>;
using options_t = std::vector<option_t>;
options_t rxbw; // Aux structure to change dynamically field_rxbw contents,
switch (v) {
case 0: //{ "FM", 0 }
enable_am = false;
enable_usb = false;
enable_lsb = false;
enable_dsb = false;
field_bw.set_value(10); // pre-default deviation FM for WFM
// field_bw.set_value(transmitter_model.channel_bandwidth() / 1000);
// if (rx_enabled)
rxaudio(rx_enabled); // Update now if we have RX audio on
options_tone_key.hidden(0); // we are in FM mode, we should have active the Key-tones & CTCSS option.
freqman_set_bandwidth_option(NFM_MODULATION, field_rxbw); // restore dynamic field_rxbw value with NFM options from freqman_db.cpp
field_rxbw.set_by_value(2); // // bw 16k (2) default
field_rxbw.hidden(0); // we are in FM mode, we need to allow the user set up of the RX NFM BW selection (8K5, 11K, 16K)
field_bw.hidden(0); // we are in FM mode, we need to allow FM deviation parameter, in non FM mode.
break;
case 1: //{ "WFM", 1 }
enable_am = false;
enable_usb = false;
enable_lsb = false;
enable_dsb = false;
enable_wfm = true;
field_bw.set_value(75); // pre-default deviation FM for WFM
// field_bw.set_value(transmitter_model.channel_bandwidth() / 1000);
// if (rx_enabled)
rxaudio(rx_enabled); // Update now if we have RX audio on
options_tone_key.hidden(0); // we are in WFM mode, we should have active the Key-tones & CTCSS option.
freqman_set_bandwidth_option(WFM_MODULATION, field_rxbw); // restore dynamic field_rxbw value with WFM options from freqman_db.cpp
field_rxbw.set_by_value(0); // bw 200k (0) default
field_rxbw.hidden(0); // we are in WFM mode, we need to show to the user the selected BW WFM filter.
field_bw.hidden(0); // we are in WFM mode, we need to allow WFM deviation parameter, in non FM mode.
break;
case 2: //{ "AM", 2 }
enable_am = true;
rxaudio(rx_enabled); // Update now if we have RX audio on
options_tone_key.set_selected_index(0); // we are NOT in FM mode, we reset the possible previous key-tones &CTCSS selection.
set_dirty(); // Refresh display
options_tone_key.hidden(1); // we hide that Key-tones & CTCSS input selecction, (no meaning in AM/DSB/SSB).
rxbw.emplace_back("DSB 9k", 0); // we offer in AM DSB two audio BW 9k / 6k.
rxbw.emplace_back("DSB 6k", 1);
field_rxbw.set_options(rxbw); // store that aux GUI option to the field_rxbw.
field_rxbw.hidden(0); // we show fixed RX AM BW 6Khz
field_bw.hidden(1); // we hide the FM TX deviation parameter, in non FM mode.
check_rogerbeep.hidden(0); // make visible again the "rogerbeep" selection.
break;
case 3: //{ "USB", 3 }
enable_usb = true;
rxaudio(rx_enabled); // Update now if we have RX audio on
check_rogerbeep.set_value(false); // reset the possible activation of roger beep, because it is not compatible with SSB, by now.
check_rogerbeep.hidden(1); // hide that roger beep selection.
rxbw.emplace_back("USB+3k", 0); // locked a fixed option, to display it.
field_rxbw.set_options(rxbw); // store that aux GUI option to the field_rxbw.
set_dirty(); // Refresh display
break;
case 4: //{ "LSB", 4 }
enable_lsb = true;
rxaudio(rx_enabled); // Update now if we have RX audio on
check_rogerbeep.set_value(false); // reset the possible activation of roger beep, because it is not compatible with SSB, by now.
check_rogerbeep.hidden(1); // hide that roger beep selection.
rxbw.emplace_back("LSB-3k", 0); // locked a fixed option, to display it.
field_rxbw.set_options(rxbw); // store that aux GUI option to the field_rxbw.
set_dirty(); // Refresh display
break;
case 5: //{ "DSB-SC", 5 }
enable_dsb = true;
rxaudio(rx_enabled); // Update now if we have RX audio on
check_rogerbeep.hidden(0); // make visible again the "rogerbeep" selection.
rxbw.emplace_back("USB+3k", 0); // added dynamically two options (index 0,1) to that DSB-SC case to the field_rxbw value.
rxbw.emplace_back("LSB-3k", 1);
field_rxbw.set_options(rxbw); // store that aux GUI option to the field_rxbw.
break;
}
// configure_baseband();
};
/*
check_va.on_select = [this](Checkbox&, bool v) {
va_enabled = v;
text_ptt.hidden(v); //hide / show PTT text
check_rxactive.hidden(v); //hide / show the RX AUDIO
set_dirty(); //Refresh display
};
*/
field_va.set_selected_index(1);
field_va.on_change = [this](size_t, int32_t v) {
switch (v) {
case 0:
va_enabled = 0;
this->set_ptt_visibility(0);
check_rxactive.hidden(0);
ptt_enabled = 0;
break;
case 1:
va_enabled = 0;
this->set_ptt_visibility(1);
check_rxactive.hidden(0);
ptt_enabled = 1;
break;
case 2:
if (!rx_enabled) {
va_enabled = 1;
this->set_ptt_visibility(0);
check_rxactive.hidden(1);
ptt_enabled = 0;
} else {
field_va.set_selected_index(1);
}
break;
}
set_dirty();
};
check_rogerbeep.on_select = [this](Checkbox&, bool v) {
rogerbeep_enabled = v;
};
check_common_freq_tx_rx.on_select = [this](Checkbox&, bool v) {
bool_same_F_tx_rx_enabled = v;
field_rxfrequency.hidden(v); // Hide or show separated freq RX field. (When no hide user can enter down indep. freq for RX)
set_dirty(); // Refresh GUI interface
receiver_model.set_target_frequency(v ? tx_frequency : rx_frequency); // To go to the proper tuned freq. when toggling it
};
field_va_level.on_change = [this](int32_t v) {
va_level = v;
vumeter.set_mark(v);
};
field_va_level.set_value(40);
field_va_attack.on_change = [this](int32_t v) {
attack_ms = v;
};
field_va_attack.set_value(500);
field_va_decay.on_change = [this](int32_t v) {
decay_ms = v;
};
field_va_decay.set_value(1000);
check_rxactive.on_select = [this](Checkbox&, bool v) {
// vumeter.set_value(0); //Start with a clean vumeter
rx_enabled = v;
// check_va.hidden(v); //Hide or show voice activation
rxaudio(v); // Activate-Deactivate audio rx accordingly
set_dirty(); // Refresh interface
};
field_rxbw.on_change = [this](size_t, int32_t v) {
if (!(enable_am || enable_usb || enable_lsb || enable_dsb || enable_wfm)) {
// In Previous fw versions, that nbfm_configuration(n) was done in any mode (FM/AM/SSB/DSB)...strictly speaking only need it in (NFM/FM)
receiver_model.set_nbfm_configuration(v); // we are in NFM/FM case, we need to select proper NFM/FM RX channel filter, NFM BW 8K5(0), NFM BW 11K(1), FM BW 16K (2)
} else { // we are not in NFM/FM mode. (we could be in any of the rest : AM /USB/LSB/DSB-SC)
if (enable_am) { // we are in AM TX mode, we will allow both independent RX audio BW : AM 9K (9K00AE3 / AM 6K (6K00AE3). (In AM option v can be 0 (9k), 1 (6k)
receiver_model.set_am_configuration(v); // we are in AM TX mode, we need to select proper AM full path config AM-9K filter. 0+0 =>AM-9K(0), 0+1=1 =>AM-6K(1),
}
if (enable_dsb) { // we are in DSB-SC in TX mode, we will allow both independent RX SSB demodulation (USB / LSB side band). in that submenu, v is 0 (SSB1 USB) or 1 (SSB2 LSB)
receiver_model.set_am_configuration(v + 2); // we are in DSB-SC TX mode, we need to select proper SSB filter. 0+2 =>usb(2), 1+2=3 =>lsb(3),
}
if (enable_wfm) {
receiver_model.set_wfm_configuration(v); // we are in WFM case, we need to select proper WFB RX BW filter, WFM BW 200K(0), WFM BW 180K(1), WFM BW 40K(2)
}
}
};
field_squelch.on_change = [this](int32_t v) {
receiver_model.set_squelch_level(100 - v);
};
field_squelch.set_value(0);
receiver_model.set_squelch_level(0);
rx_frequency = receiver_model.target_frequency();
// WARNING: transmitter_model.set_target_frequency() and receiver_model.set_target_frequency() both update the same tuning freq, but one has an offset!
field_rxfrequency.set_value(rx_frequency);
field_rxfrequency.set_step(receiver_model.frequency_step());
field_rxfrequency.on_change = [this](rf::Frequency f) { // available when field rxfrequency not hidden => user selected separated freq RX/TX-
rx_frequency = f;
if (rx_enabled)
receiver_model.set_target_frequency(f);
receiver_model.set_target_frequency(rx_frequency);
};
field_rxfrequency.on_edit = [this, &nav]() { // available when field rxfrequency not hidden => user selected separated freq RX/TX-
focused_ui = 1;
// TODO: Provide separate modal method/scheme?
auto new_view = nav.push<FrequencyKeypadView>(rx_frequency);
new_view->on_changed = [this](rf::Frequency f) {
rx_frequency = f;
if (rx_enabled)
receiver_model.set_target_frequency(f);
this->field_rxfrequency.set_value(f);
set_dirty();
};
};
rx_lna = receiver_model.lna();
field_rxlna.on_change = [this](int32_t v) {
rx_lna = v;
if (rx_enabled)
receiver_model.set_lna(v);
};
field_rxlna.set_value(rx_lna);
// TODO: would be nice if frequency step was configurable in this app
field_rxfrequency.set_step(receiver_model.frequency_step());
rx_vga = receiver_model.vga();
field_rxvga.on_change = [this](int32_t v) {
rx_vga = v;
if (rx_enabled)
receiver_model.set_vga(v);
check_common_freq_tx_rx.on_select = [this](Checkbox&, bool v) {
bool_same_F_tx_rx_enabled = v;
field_rxfrequency.hidden(v);
set_dirty();
receiver_model.set_target_frequency(bool_same_F_tx_rx_enabled ? tx_frequency : rx_frequency); // To go to the proper tuned freq. when toggling it
};
field_rxvga.set_value(rx_vga);
check_common_freq_tx_rx.set_value(bool_same_F_tx_rx_enabled);
rx_amp = receiver_model.rf_amp();
field_rxamp.on_change = [this](int32_t v) {
rx_amp = v;
if (rx_enabled)
receiver_model.set_rf_amp(rx_amp);
field_bw.on_change = [this](uint32_t v) {
transmitter_model.set_channel_bandwidth(v * 1000);
};
field_rxamp.set_value(rx_amp);
field_bw.set_value(transmitter_model.channel_bandwidth() / 1000); // pre-default first time, TX deviation FM for NFM / FM
// now, no need direct update, field_rfgain, field_rfamp (it is done in ui_transmitter.cpp)
options_mode.on_change = [this](size_t, int32_t v) {
mic_mod_index = v;
// update bw & tone key visibility - only visible in NFM/WFM modes
if ((mic_mod_index == MIC_MOD_NFM) || (mic_mod_index == MIC_MOD_WFM)) {
field_bw.hidden(false);
options_tone_key.hidden(false);
} else {
field_bw.hidden(true);
options_tone_key.set_selected_index(0);
options_tone_key.hidden(true);
}
// update rogerbeep visibility - disable in USB/LSB modes
if ((mic_mod_index == MIC_MOD_USB) || (mic_mod_index == MIC_MOD_LSB)) {
check_rogerbeep.set_value(false);
check_rogerbeep.hidden(true);
} else {
check_rogerbeep.hidden(false);
}
// update squelch visibility - only visible in NFM mode
field_squelch.hidden(mic_mod_index != MIC_MOD_NFM);
// update bandwidth
set_rxbw_options();
set_rxbw_defaults(false);
field_rxbw.hidden(false);
set_dirty(); // Refresh display
rxaudio(rx_enabled); // Update now if we have RX audio on
// configure_baseband();
};
options_mode.set_selected_index(mic_mod_index);
check_mic_to_HP.on_select = [this](Checkbox&, bool v) {
mic_to_HP_enabled = v;
if (mic_to_HP_enabled) { // When user click to "hear mic to HP", we will select the higher acoustic sound Option MODE ALC or BOOST-
audio::input::loopback_mic_to_hp_enable();
if (audio::debug::codec_name() == "WM8731") {
options_wm8731_boost_mode.set_selected_index(0); // In WM we always go to Boost +12 dBs respect reference level
} else if (ak4951_alc_and_wm8731_boost_GUI == 0) // In AK we are changing only that ALC index =0, because in that option, there is no sound.
options_ak4951_alc_mode.set_selected_index(1); // alc_mode =0 , means no ALC,no DIGITAL filter block (by passed), and that mode has no loopback mode.
} else {
audio::input::loopback_mic_to_hp_disable();
}
};
check_mic_to_HP.set_value(mic_to_HP_enabled);
tx_button.on_select = [this](Button&) {
if (ptt_enabled && !transmitting) {
if (!transmitting) {
set_tx(true);
}
};
tx_button.on_touch_release = [this](Button&) {
if (button_touch) {
button_touch = false;
set_tx(false);
}
button_touch = false;
};
tx_button.on_touch_press = [this](Button&) {
if (!transmitting) {
button_touch = true;
}
button_touch = true;
};
field_va_level.on_change = [this](int32_t v) {
va_level = v;
vumeter.set_mark(v);
};
field_va_level.set_value(va_level);
field_va_attack.set_value(attack_ms);
field_va_attack.on_change = [this](int32_t v) {
attack_ms = v;
};
field_va_decay.set_value(decay_ms);
field_va_decay.on_change = [this](int32_t v) {
decay_ms = v;
};
check_va.on_select = [this](Checkbox&, bool v) {
va_enabled = v;
if (va_enabled)
check_rxactive.set_value(false); // Disallow RX-audio in VOX mode (for now) - Future TODO: Should allow VOX during RX audio
};
check_va.set_value(va_enabled);
// These shouldn't be necessary, but because
// this app uses both transmitter_model and directly
// configures the baseband, these end up being required.
@@ -602,25 +569,45 @@ MicTXView::MicTXView(
set_tx(false);
audio::set_rate(audio::Rate::Hz_24000);
audio::input::start(ak4951_alc_and_wm8731_boost_GUI); // When detected AK4951 => set up ALC mode; when detected WM8731 => set up mic_boost ON/OFF.
audio::input::start(ak4951_alc_and_wm8731_boost_GUI, mic_to_HP_enabled); // set up ALC mode (AK4951) or set up mic_boost ON/OFF (WM8731). and the check box "Hear Mic"
// Workaround for bad RX reception when app first started -- shouldn't be necessary:
// Trigger receiver to update modulation.
if (rx_enabled)
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(
NavigationView& nav,
ReceiverModel::settings_t override)
: MicTXView(nav) {
// Settings to override when launched from another app (versus from AppSettings .ini file)
// Try to use the modulation/bandwidth from RX settings.
// TODO: These concepts should be merged so there's only one.
// TODO: enums/constants for these indexes.
switch (override.mode) {
case ReceiverModel::Mode::AMAudio:
options_mode.set_selected_index(2);
options_mode.set_selected_index(MIC_MOD_AM);
break;
case ReceiverModel::Mode::NarrowbandFMAudio:
options_mode.set_selected_index(0);
options_mode.set_selected_index(MIC_MOD_NFM);
break;
case ReceiverModel::Mode::WidebandFMAudio:
options_mode.set_selected_index(1);
options_mode.set_selected_index(MIC_MOD_WFM);
break;
// Unsupported modulations.
@@ -630,6 +617,9 @@ MicTXView::MicTXView(
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
// TODO: bandwidth selection is tied too tightly to the UI
// controls. It's not possible to set the bandwidth here without
// refactoring. Also options_mode seems to have a category error.
@@ -637,10 +627,12 @@ MicTXView::MicTXView(
MicTXView::~MicTXView() {
audio::input::stop();
transmitter_model.set_target_frequency(tx_frequency);
transmitter_model.disable();
if (rx_enabled) // Also turn off audio rx if enabled
if (rx_enabled) { // Also turn off both (audio rx if enabled, and disable mic_loop to HP)
rxaudio(false);
audio::input::loopback_mic_to_hp_disable(); // Leave Mic audio off in the main menu (as original audio path, otherwise we had No audio in next "Audio App")
}
transmitter_model.set_target_frequency(tx_frequency); // Set tx_frequency here to be saved in app_settings (might not match rx_frequency)
transmitter_model.disable();
baseband::shutdown();
}
+89 -72
View File
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
* Copyright (C) 2024 Mark Thompson
*
* This file is part of PortaPack.
*
@@ -70,67 +71,86 @@ class MicTXView : public View {
static constexpr uint32_t lcd_frame_duration = (256 * 1000UL) / 60; // 1 frame @ 60fps in ms .8 fixed point /60
void update_vumeter();
bool tx_button_held();
void do_timing();
void set_tx(bool enable);
// void on_target_frequency_changed(rf::Frequency f);
void on_tx_progress(const bool done);
void update_tx_icon();
uint8_t shift_bits(void);
void configure_baseband();
void set_rxbw_options(void);
void set_rxbw_defaults(bool use_app_settings);
void update_receiver_rxbw(void);
void rxaudio(bool is_on);
void set_ptt_visibility(bool v);
RxRadioState rx_radio_state_{};
TxRadioState tx_radio_state_{
0 /* frequency */,
1750000 /* bandwidth */,
sampling_rate /* sampling rate */
};
app_settings::SettingsManager settings_{
"tx_mic", app_settings::Mode::RX_TX,
app_settings::Options::UseGlobalTargetFrequency};
bool transmitting{false};
enum Mic_Modulation : uint32_t {
MIC_MOD_NFM = 0,
MIC_MOD_WFM = 1,
MIC_MOD_AM = 2,
MIC_MOD_USB = 3,
MIC_MOD_LSB = 4,
MIC_MOD_DSB = 5,
};
// Settings
uint32_t mic_mod_index{0};
uint32_t rxbw_index{0};
bool va_enabled{false};
bool ptt_enabled{true};
bool rogerbeep_enabled{false};
bool mic_to_HP_enabled{false};
bool bool_same_F_tx_rx_enabled{false};
rf::Frequency rx_frequency{0};
bool rx_enabled{false};
uint32_t tone_key_index{};
float mic_gain{1.0};
uint32_t tone_key_index{0};
uint32_t mic_gain_x10{1};
uint8_t ak4951_alc_and_wm8731_boost_GUI{0};
uint32_t va_level{40};
uint32_t attack_ms{500};
uint32_t decay_ms{1000};
uint8_t iq_phase_calibration_value{15};
app_settings::SettingsManager settings_{
"tx_mic",
app_settings::Mode::RX_TX,
{
{"mic_mod_index"sv, &mic_mod_index},
{"rxbw_index"sv, &rxbw_index},
{"same_F_tx_rx"sv, &bool_same_F_tx_rx_enabled},
{"mic_rx_frequency"sv, &rx_frequency},
{"rx_enabled"sv, &rx_enabled},
{"mic_gain_x10"sv, &mic_gain_x10},
{"mic_to_HP"sv, &mic_to_HP_enabled},
{"alc_and_boost"sv, &ak4951_alc_and_wm8731_boost_GUI},
{"va_level"sv, &va_level},
{"attack_ms"sv, &attack_ms},
{"decay_ms"sv, &decay_ms},
{"vox"sv, &va_enabled},
{"rogerbeep"sv, &rogerbeep_enabled},
{"tone_key_index"sv, &tone_key_index},
{"iq_phase_calibration"sv, &iq_phase_calibration_value},
}};
rf::Frequency tx_frequency{0};
bool transmitting{false};
uint32_t audio_level{0};
uint32_t va_level{};
uint32_t attack_ms{};
uint32_t decay_ms{};
uint32_t attack_timer{0};
uint32_t decay_timer{0};
int32_t tx_gain{47};
bool rf_amp{false};
int32_t rx_lna{32};
int32_t rx_vga{32};
bool rx_amp{false};
rf::Frequency tx_frequency{0};
rf::Frequency rx_frequency{0};
int32_t focused_ui{2};
bool button_touch{false};
uint8_t shift_bits_s16{4}; // shift bits factor to the captured ADC S16 audio sample.
// AM TX Stuff
// TODO: Some of this stuff is mutually exclusive. Need a better representation.
bool enable_am{false};
bool enable_dsb{false};
bool enable_usb{false};
bool enable_lsb{false};
bool enable_wfm{false}; // added to distinguish in the FM mode, RX BW : NFM (8K5, 11K), FM (16K), WFM(200K)
Labels labels_WM8731{
Labels labels_both{
{{3 * 8, 1 * 8}, "MIC-GAIN:", Color::light_grey()},
{{17 * 8, 1 * 8}, "Boost", Color::light_grey()},
{{3 * 8, 3 * 8}, "F:", Color::light_grey()},
{{15 * 8, 3 * 8}, "FM TXBW: kHz", Color::light_grey()}, // 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
{{3 * 8, 8 * 8}, "TX Activation:", Color::light_grey()}, // we delete { { 3 * 8, 5 * 8 }, "GAIN:", Color::light_grey() },
{{4 * 8, 10 * 8}, "LVL:", Color::light_grey()}, // we delete { {11 * 8, 5 * 8 }, "Amp:", Color::light_grey() },
{{12 * 8, 10 * 8}, "ATT:", Color::light_grey()},
{{20 * 8, 10 * 8}, "DEC:", Color::light_grey()},
@@ -142,26 +162,12 @@ class MicTXView : public View {
{{5 * 8, (25 * 8) + 2}, "F_RX:", Color::light_grey()},
{{5 * 8, (27 * 8) + 2}, "LNA:", Color::light_grey()},
{{12 * 8, (27 * 8) + 2}, "VGA:", Color::light_grey()},
{{19 * 8, (27 * 8) + 2}, "AMP:", Color::light_grey()}};
{{19 * 8, (27 * 8) + 2}, "AMP:", Color::light_grey()},
{{21 * 8, (31 * 8)}, "TX-IQ-CAL:", Color::light_grey()}};
Labels labels_WM8731{
{{17 * 8, 1 * 8}, "Boost", Color::light_grey()}};
Labels labels_AK4951{
{{3 * 8, 1 * 8}, "MIC-GAIN:", Color::light_grey()},
{{17 * 8, 1 * 8}, "ALC", Color::light_grey()},
{{3 * 8, 3 * 8}, "F:", Color::light_grey()},
{{15 * 8, 3 * 8}, "FM TXBW: kHz", Color::light_grey()},
{{18 * 8, (5 * 8)}, "Mode:", Color::light_grey()}, // now, no need to handle GAIN, Amp here It is handled by ui_transmitter.cpp
{{3 * 8, 8 * 8}, "TX Activation:", Color::light_grey()}, // we delete { { 3 * 8, 5 * 8 }, "GAIN:", Color::light_grey() },
{{4 * 8, 10 * 8}, "LVL:", Color::light_grey()}, // we delete { {11 * 8, 5 * 8 }, "Amp:", Color::light_grey() },
{{12 * 8, 10 * 8}, "ATT:", Color::light_grey()},
{{20 * 8, 10 * 8}, "DEC:", Color::light_grey()},
{{3 * 8, (13 * 8) - 5}, "TONE KEY:", Color::light_grey()},
{{3 * 8, (18 * 8) - 1}, "======== Receiver ========", Color::green()},
{{(5 * 8), (23 * 8) + 2}, "VOL:", Color::light_grey()},
{{14 * 8, (23 * 8) + 2}, "RXBW:", Color::light_grey()}, // we remove the label "FM" because we will display all MOD types RX_BW.
{{20 * 8, (25 * 8) + 2}, "SQ:", Color::light_grey()},
{{5 * 8, (25 * 8) + 2}, "F_RX:", Color::light_grey()},
{{5 * 8, (27 * 8) + 2}, "LNA:", Color::light_grey()},
{{12 * 8, (27 * 8) + 2}, "VGA:", Color::light_grey()},
{{19 * 8, (27 * 8) + 2}, "AMP:", Color::light_grey()}};
{{17 * 8, 1 * 8}, "ALC", Color::light_grey()}};
VuMeter vumeter{
{0 * 8, 1 * 8, 2 * 8, 33 * 8},
@@ -224,28 +230,19 @@ class MicTXView : public View {
{24 * 8, 5 * 8},
6,
{
{"NFM/FM", 0},
{" WFM ", 1},
{" AM ", 2}, // in fact that TX mode = AM -DSB with carrier.
{" USB ", 3},
{" LSB ", 4},
{"DSB-SC", 5} // We are TX Double Side AM Band with suppressed carrier, and allowing in RX both indep SSB lateral band (USB/LSB).
{"NFM/FM", MIC_MOD_NFM},
{" WFM ", MIC_MOD_WFM},
{" AM ", MIC_MOD_AM}, // in fact that TX mode = AM -DSB with carrier.
{" USB ", MIC_MOD_USB},
{" LSB ", MIC_MOD_LSB},
{"DSB-SC", MIC_MOD_DSB} // We are TX Double Side AM Band with suppressed carrier, and allowing in RX both indep SSB lateral band (USB/LSB).
}};
/*
Checkbox check_va {
{ 3 * 8, (10 * 8) - 4 },
7,
"Voice activation",
false
};
*/
OptionsField field_va{
{17 * 8, 8 * 8},
4,
{{" OFF", 0},
{" PTT", 1},
{"AUTO", 2}}};
Checkbox check_va{
{3 * 8, 8 * 7},
10,
"VOX enable",
false};
NumberField field_va_level{
{8 * 8, 10 * 8},
@@ -277,6 +274,12 @@ class MicTXView : public View {
"Roger beep",
false};
Checkbox check_mic_to_HP{
{18 * 8, (14 * 8) + 4},
10,
"Hear Mic",
false};
Checkbox check_rxactive{
{3 * 8, (21 * 8) - 7},
8, // it was 18, but if it is string size should be 8
@@ -286,7 +289,7 @@ class MicTXView : public View {
Checkbox check_common_freq_tx_rx{
{18 * 8, (21 * 8) - 7},
8,
"F TX=RX",
"F RX=TX",
false};
AudioVolumeField field_volume{
@@ -338,11 +341,25 @@ 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{
{10 * 8, 30 * 8, 10 * 8, 5 * 8},
"TX",
"PTT TX",
true};
Image tx_icon{
{6 * 8, 31 * 8 + 4, 16, 16},
&bitmap_icon_microphone,
Color::black(),
Color::black()};
MessageHandlerRegistration message_handler_lcd_sync{
Message::ID::DisplayFrameSync,
[this](const Message* const) {
+43 -23
View File
@@ -56,13 +56,23 @@ static msg_t ookthread_fn(void* arg) {
v = (symbol < 2) ? 1 : 0; // TX on for dot or dash, off for pause
delay = morse_symbols[symbol];
gpio_og_tx.write(v);
if (hackrf_r9) { // r9 has a common PIN 54 for MODE CONTROL TX / RX, <=r6 has two independent MODE CONTROL pins (TX and RX)
gpio_r9_rx.write(!v); // in hackrf r9 opposite logic "0" means tx , "1" rx = no tx)
} else {
gpio_og_tx.write(v); // in hackrf <=r6, "1" means tx , "0" no tx.
}
arg_c->on_tx_progress(i, false);
chThdSleepMilliseconds(delay * arg_c->time_unit_ms);
}
gpio_og_tx.write(0); // Ensure TX is off
if (hackrf_r9) { // r9 has a common PIN 54 for MODE CONTROL TX / RX, <=r6 has two independent MODE CONTROL pins (TX and RX)
gpio_r9_rx.write(1); // Hackrf_r9 , common pin, rx="1" (we ensure TX is off) / tx="0"
} else {
gpio_og_tx.write(0); // Hackrf <=r6 independent TX / RX pins ,now touching the tx pin ==> Ensure TX is off
}
arg_c->on_tx_progress(0, true);
chThdExit(0);
@@ -121,10 +131,10 @@ bool MorseView::start_tx() {
transmitter_model.set_baseband_bandwidth(1'750'000); // Min TX LPF .already tested in FM morse max tone 9,999k , max dev 150khz
transmitter_model.enable();
if (modulation == CW) {
baseband::set_tones_config(transmitter_model.channel_bandwidth(), 0, symbol_count, false, false);
if (mode_cw) {
ookthread = chThdCreateStatic(ookthread_wa, sizeof(ookthread_wa), NORMALPRIO + 10, ookthread_fn, this);
} else if (modulation == FM) {
baseband::set_tones_config(transmitter_model.channel_bandwidth(), 0, symbol_count, false, false);
}
return true;
@@ -133,8 +143,8 @@ bool MorseView::start_tx() {
void MorseView::update_tx_duration() {
uint32_t duration_ms;
time_unit_ms = 1200 / field_speed.value();
symbol_count = morse_encode(message, time_unit_ms, field_tone.value(), &time_units);
time_unit_ms = 1200 / speed;
symbol_count = morse_encode(message, time_unit_ms, tone, &time_units);
if (symbol_count) {
duration_ms = time_units * time_unit_ms;
@@ -175,7 +185,7 @@ void MorseView::on_loop_progress(const uint32_t progress, const bool done) {
}
void MorseView::set_foxhunt(size_t i) {
message = foxhunt_codes[i];
message = foxhunt_codes[i - 1];
buffer = message.c_str();
text_message.set(message);
update_tx_duration();
@@ -188,7 +198,7 @@ MorseView::MorseView(
add_children({&labels,
&checkbox_foxhunt,
&options_foxhunt,
&field_foxhunt,
&field_speed,
&field_tone,
&options_modulation,
@@ -200,36 +210,46 @@ MorseView::MorseView(
&tx_view});
// Default settings
field_speed.set_value(15); // 15wps
field_tone.set_value(700); // 700Hz FM tone
options_modulation.set_selected_index(0); // CW mode
options_loop.set_selected_index(0); // Off
field_speed.set_value(speed);
field_tone.set_value(tone);
options_modulation.set_by_value(mode_cw);
options_loop.set_by_value(loop);
checkbox_foxhunt.set_value(foxhunt_mode);
field_foxhunt.set_value(foxhunt_code);
checkbox_foxhunt.on_select = [this](Checkbox&, bool value) {
foxhunt_mode = value;
if (foxhunt_mode)
set_foxhunt(options_foxhunt.selected_index_value());
set_foxhunt(foxhunt_code);
};
options_foxhunt.on_change = [this](size_t i, int32_t) {
field_foxhunt.on_change = [this](int32_t value) {
foxhunt_code = value;
if (foxhunt_mode)
set_foxhunt(i);
set_foxhunt(foxhunt_code);
};
options_modulation.on_change = [this](size_t i, int32_t) {
modulation = (modulation_t)i;
};
options_loop.on_change = [this](size_t i, uint32_t n) {
options_modulation.on_change = [this](size_t i, int32_t value) {
(void)i; // avoid unused warning
loop = n;
mode_cw = (bool)value;
};
field_speed.on_change = [this](int32_t) {
options_loop.on_change = [this](size_t i, uint32_t value) {
(void)i; // avoid unused warning
loop = value;
};
field_speed.on_change = [this](int32_t value) {
speed = value;
update_tx_duration();
};
field_tone.on_change = [this](int32_t value) {
tone = value;
};
button_message.on_select = [this, &nav](Button&) {
this->on_set_text(nav);
};
+25 -24
View File
@@ -65,7 +65,6 @@ class MorseView : public View {
private:
NavigationView& nav_;
std::string buffer{"PORTAPACK"};
std::string message{};
uint32_t time_units{0};
@@ -74,14 +73,25 @@ class MorseView : public View {
1750000 /* bandwidth */,
1536000 /* sampling rate */
};
app_settings::SettingsManager settings_{
"tx_morse", app_settings::Mode::TX};
enum modulation_t {
CW = 0,
FM = 1
};
modulation_t modulation{CW};
std::string buffer{"PORTAPACK"};
bool mode_cw{true};
bool foxhunt_mode{false};
uint32_t foxhunt_code{1};
uint32_t speed{15};
uint32_t tone{700};
app_settings::SettingsManager settings_{
"tx_morse",
app_settings::Mode::TX,
{
{"message"sv, &buffer},
{"foxhunt"sv, &foxhunt_mode},
{"foxhunt_code"sv, &foxhunt_code},
{"speed"sv, &speed},
{"tone"sv, &tone},
{"mode_cw"sv, &mode_cw},
{"loop"sv, &loop},
}};
bool start_tx();
void update_tx_duration();
@@ -90,7 +100,6 @@ class MorseView : public View {
Thread* ookthread{nullptr};
Thread* loopthread{nullptr};
bool foxhunt_mode{false};
bool run{false};
Labels labels{
@@ -104,20 +113,12 @@ class MorseView : public View {
{4 * 8, 16},
8,
"Foxhunt:"};
OptionsField options_foxhunt{
NumberField field_foxhunt{
{17 * 8, 16 + 4},
7,
{{"1 (MOE)", 0},
{"2 (MOI)", 1},
{"3 (MOS)", 2},
{"4 (MOH)", 3},
{"5 (MO5)", 4},
{"6 (MON)", 5},
{"7 (MOD)", 6},
{"8 (MOB)", 7},
{"9 (MO6)", 8},
{"X (MO) ", 9},
{"T (S) ", 10}}};
2,
{1, 11},
1,
' '};
NumberField field_speed{
{10 * 8, 6 * 8},
@@ -136,8 +137,8 @@ class MorseView : public View {
OptionsField options_modulation{
{15 * 8, 10 * 8},
2,
{{"CW", 0},
{"FM", 1}}};
{{"CW", true},
{"FM", false}}};
OptionsField options_loop{
{9 * 8, 12 * 8},
+2 -11
View File
@@ -277,18 +277,9 @@ NumbersStationView::NumbersStationView(
symfield_code.set_offset(10, 12); // End
/*
rtc::RTC datetime;
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;
dayofweek = rtc_time::current_day_of_week();
text_title.set(day_of_week[dayofweek]);
*/
*/
button_exit.on_select = [&nav](Button&) {
nav.pop();
+2 -3
View File
@@ -78,8 +78,6 @@ void NuoptixView::transmit(bool setup) {
}
transmitter_model.set_rf_amp(true);
transmitter_model.set_lna(40);
transmitter_model.set_vga(40);
transmitter_model.enable();
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
audio::set_rate(audio::Rate::Hz_24000);
baseband::set_tones_config(transmitter_model.channel_bandwidth(), 0, 6 * 2, true, true);
timecode++;
@@ -156,7 +155,7 @@ NuoptixView::NuoptixView(
tx_view.on_edit_frequency = [this, &nav]() {
auto new_view = nav.push<FrequencyKeypadView>(transmitter_model.target_frequency());
new_view->on_changed = [this](rf::Frequency f) {
transmitter_model.target_frequency(f);
transmitter_model.set_target_frequency(f);
};
};
+7 -5
View File
@@ -3,6 +3,7 @@
* Copyright (C) 2016 Furrtek
* Copyleft () 2022 NotPike
* Copyright (C) 2023 Kyle Reed, zxkmm
* Copyright (C) 2024 Mark Thompson
*
* This file is part of PortaPack.
*
@@ -35,6 +36,7 @@
#include "string_format.hpp"
#include "ui_fileman.hpp"
#include "utility.hpp"
#include "file_path.hpp"
#include <unistd.h>
#include <fstream>
@@ -129,7 +131,7 @@ void PlaylistView::open_file(bool prompt_save) {
}
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) {
on_file_changed(new_file_path);
};
@@ -168,7 +170,7 @@ void PlaylistView::save_file(bool show_dialogs) {
void PlaylistView::add_entry(fs::path&& path) {
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.
if (parent())
@@ -386,10 +388,10 @@ PlaylistView::PlaylistView(
&waterfall,
});
ensure_directory(u"PLAYLIST");
ensure_directory(playlist_dir);
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) {
if (current())
current()->metadata.center_frequency = f;
@@ -409,7 +411,7 @@ PlaylistView::PlaylistView(
if (is_active())
return;
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) {
add_entry(std::move(path));
};
+2 -1
View File
@@ -2,6 +2,7 @@
* Copyright (C) 2016 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
* Copyright (C) 2023 Kyle Reed, zxkmm
* Copyright (C) 2024 Mark Thompson
*
* This file is part of PortaPack.
*
@@ -56,7 +57,7 @@ class PlaylistView : public View {
NavigationView& nav_;
TxRadioState radio_state_{};
app_settings::SettingsManager settings_{
"tx_playlist", app_settings::Mode::TX};
"tx_replay", app_settings::Mode::TX};
// More header == less spectrum view.
static constexpr ui::Dim header_height = 6 * 16;
+309 -67
View File
@@ -22,13 +22,24 @@
*/
#include "ui_recon.hpp"
#include "ui_fileman.hpp"
#include "ui_freqman.hpp"
#include "capture_app.hpp"
#include "convert.hpp"
#include "file.hpp"
#include "file_reader.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 tonekey;
@@ -37,6 +48,36 @@ namespace fs = std::filesystem;
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(&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);
}
}
void ReconView::check_update_ranges_from_current() {
if (frequency_list.size() && current_is_valid() && current_entry().type == freqman_type::Range) {
if (update_ranges && !manual_mode) {
@@ -67,21 +108,26 @@ void ReconView::set_loop_config(bool v) {
persistent_memory::set_recon_continuous(continuous);
}
void ReconView::recon_stop_recording() {
void ReconView::recon_stop_recording(bool exiting) {
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();
button_config.set_style(&Styles::white); // disable config while recording as it's causing an IO error pop up at exit
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(&Styles::white);
button_config.set_style(&Styles::white);
}
}
void ReconView::clear_freqlist_for_ui_action() {
recon_stop_recording();
recon_stop_recording(false);
if (field_mode.selected_index_value() != SPEC_MODULATION)
audio::output::stop();
// flag to detect and reload frequency_list
@@ -111,6 +157,9 @@ void ReconView::update_description() {
case freqman_type::HamRadio:
description = "H: ";
break;
case freqman_type::Repeater:
description = "L: ";
break;
default:
description = "S: ";
}
@@ -182,6 +231,7 @@ void ReconView::load_persisted_settings() {
load_freqs = persistent_memory::recon_load_freqs();
load_ranges = persistent_memory::recon_load_ranges();
load_hamradios = persistent_memory::recon_load_hamradios();
load_repeaters = persistent_memory::recon_load_repeaters();
update_ranges = persistent_memory::recon_update_ranges_when_recon();
auto_record_locked = persistent_memory::recon_auto_record_locked();
}
@@ -265,9 +315,16 @@ void ReconView::focus() {
}
ReconView::~ReconView() {
recon_stop_recording();
if (recon_tx) {
replay_thread.reset();
}
recon_stop_recording(true);
if (field_mode.selected_index_value() != SPEC_MODULATION)
audio::output::stop();
transmitter_model.disable();
receiver_model.disable();
baseband::shutdown();
}
@@ -275,9 +332,20 @@ ReconView::~ReconView() {
ReconView::ReconView(NavigationView& nav)
: nav_{nav} {
chrono_start = chTimeNow();
tx_view.hidden(true);
// set record View
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);
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,
&field_lna,
&field_vga,
@@ -314,13 +382,9 @@ ReconView::ReconView(NavigationView& nav)
&button_restart,
&button_mic_app,
&button_remove,
record_view.get()});
record_view->hidden(true);
record_view->set_filename_date_frequency(true);
record_view->on_error = [&nav](std::string message) {
nav.display_modal("Error", message);
};
record_view.get(),
&progressbar,
&tx_view});
def_step = 0;
load_persisted_settings();
@@ -444,7 +508,7 @@ ReconView::ReconView(NavigationView& nav)
auto settings = receiver_model.settings();
settings.frequency_step = step_mode.selected_index_value();
if (field_mode.selected_index_value() == SPEC_MODULATION)
nav_.replace<CaptureAppView>();
nav_.replace<CaptureAppView>(settings);
else
nav_.replace<AnalogAudioView>(settings);
};
@@ -475,16 +539,15 @@ 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());
};
button_remove.on_select = [this](ButtonWithEncoder&) {
handle_remove_current_item();
// TODO: refresh UI.
timer = 0;
freq_lock = 0;
recon_redraw();
};
button_remove.on_change = [this]() {
@@ -493,7 +556,7 @@ ReconView::ReconView(NavigationView& nav)
};
button_manual_recon.on_select = [this](Button&) {
button_remove.set_text("DELETE");
button_remove.set_text("<DELETE>");
button_add.hidden(false);
scanner_mode = false;
manual_mode = true;
@@ -567,27 +630,7 @@ ReconView::ReconView(NavigationView& nav)
};
button_restart.on_select = [this](Button&) {
frequency_file_load(true);
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);
}
reload_restart_recon();
};
button_add.on_select = [this](ButtonWithEncoder&) {
@@ -607,14 +650,14 @@ ReconView::ReconView(NavigationView& nav)
scanner_mode = false;
button_scanner_mode.set_style(&Styles::blue);
button_scanner_mode.set_text("RECON");
button_remove.set_text("REMOVE");
button_remove.set_text("<REMOVE>");
} else {
scanner_mode = true;
button_scanner_mode.set_style(&Styles::red);
button_scanner_mode.set_text("SCAN");
button_remove.set_text("DELETE");
button_remove.set_text("<DELETE>");
}
frequency_file_load(true);
frequency_file_load();
if (autostart) {
recon_resume();
} else {
@@ -642,7 +685,7 @@ ReconView::ReconView(NavigationView& nav)
load_persisted_settings();
ui_settings.save();
frequency_file_load(false);
frequency_file_load();
freqlist_cleared_for_ui_action = false;
if (autostart) {
@@ -705,11 +748,14 @@ ReconView::ReconView(NavigationView& nav)
change_mode(AM_MODULATION); // start on AM.
field_mode.set_by_value(AM_MODULATION); // reflect the mode into the manual selector
// tx progress bar
progressbar.hidden(true);
if (filedelete) {
delete_file(freq_file_path);
}
frequency_file_load(false); /* do not stop all at start */
frequency_file_load(); /* do not stop all at start */
if (autostart) {
recon_resume();
} else {
@@ -718,7 +764,7 @@ ReconView::ReconView(NavigationView& nav)
recon_redraw();
}
void ReconView::frequency_file_load(bool) {
void ReconView::frequency_file_load() {
if (field_mode.selected_index_value() != SPEC_MODULATION)
audio::output::stop();
@@ -742,7 +788,8 @@ void ReconView::frequency_file_load(bool) {
freqman_load_options options{
.load_freqs = load_freqs,
.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()) {
file_name.set_style(&Styles::red);
desc_cycle.set("...empty file...");
@@ -777,6 +824,12 @@ void ReconView::frequency_file_load(bool) {
}
void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
if (recon_tx)
return;
if (is_repeat_active())
return;
chrono_end = chTimeNow();
systime_t time_interval = chrono_end - chrono_start;
chrono_start = chrono_end;
@@ -784,7 +837,7 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
// hack to reload the list if it was cleared by going into CONFIG
if (freqlist_cleared_for_ui_action) {
if (!manual_mode) {
frequency_file_load(false);
frequency_file_load();
}
if (autostart && !user_pause) {
recon_resume();
@@ -805,7 +858,7 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
if (status != 1) {
status = 1;
if (wait != 0) {
recon_stop_recording();
recon_stop_recording(false);
if (field_mode.selected_index_value() != SPEC_MODULATION)
audio::output::stop();
}
@@ -836,9 +889,9 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
// contents of a possible recon_start_recording(), but not yet since it's only called once
if (auto_record_locked && !is_recording) {
button_audio_app.set_style(&Styles::red);
if (field_mode.selected_index_value() == SPEC_MODULATION)
if (field_mode.selected_index_value() == SPEC_MODULATION) {
button_audio_app.set_text("RAW REC");
else
} else
button_audio_app.set_text("WAV REC");
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
@@ -957,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
if (has_looped && !continuous) {
@@ -985,6 +1058,7 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
if (entry_has_changed) {
timer = 0;
switch (current_entry().type) {
case freqman_type::Repeater:
case freqman_type::Single:
freq = current_entry().frequency_a;
break;
@@ -1018,7 +1092,7 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
if (stepper > 0) stepper--;
} // if( recon || stepper != 0 || index_stepper != 0 )
} // if (frequency_list.size() > 0 )
} /* on_statistic_updates */
} /* on_statistics_updates */
}
handle_retune();
recon_redraw();
@@ -1081,28 +1155,44 @@ void ReconView::on_stepper_delta(int32_t v) {
}
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_bw.on_change = [this](size_t, OptionsField::value_t) {};
recon_stop_recording();
remove_child(record_view.get());
record_view.reset();
if (new_mod == SPEC_MODULATION) {
recon_stop_recording(false);
if (record_view != nullptr) {
remove_child(record_view.get());
record_view.reset();
}
if (field_mode.selected_index_value() != SPEC_MODULATION) {
audio::output::stop();
record_view = std::make_unique<RecordView>(Rect{0, 0, 30 * 8, 1 * 16},
u"AUTO_RAW_", u"CAPTURES",
RecordView::FileType::RawS16, 16384, 3);
}
if (new_mod == SPEC_MODULATION) {
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 {
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);
record_view->set_filename_date_frequency(true);
}
record_view->set_auto_trim(false);
add_child(record_view.get());
record_view->hidden(true);
record_view->set_filename_date_frequency(true);
record_view->on_error = [this](std::string message) {
nav_.display_modal("Error", message);
};
receiver_model.disable();
transmitter_model.disable();
baseband::shutdown();
size_t recording_sampling_rate = 0;
switch (new_mod) {
@@ -1145,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) {
// 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);
// The radio needs to know the effective sampling rate.
receiver_model.set_sampling_rate(actual_sampling_rate);
receiver_model.set_baseband_bandwidth(filter_bandwidth_for_sampling_rate(actual_sampling_rate));
@@ -1214,12 +1303,165 @@ void ReconView::handle_remove_current_item() {
if (frequency_list.size() > 0) {
current_index = clip<int32_t>(current_index, 0u, frequency_list.size() - 1);
text_cycle.set_text(to_string_dec_uint(current_index + 1, 3));
entry = current_entry();
freq = entry.frequency_a;
} else {
current_index = 0;
text_cycle.set_text(" ");
}
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}, Color::light_grey());
p.draw_string({(SCREEN_W / 2) - 7 * 8, SCREEN_H / 2}, Styles::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 */
+78 -10
View File
@@ -38,14 +38,23 @@
#include "baseband_api.hpp"
#include "string_format.hpp"
#include "file.hpp"
#include "file_path.hpp"
#include "app_settings.hpp"
#include "radio_state.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 {
#define RECON_CFG_FILE "SETTINGS/recon.cfg"
enum class recon_mode : uint8_t {
Recon,
Scanner,
@@ -66,10 +75,17 @@ class ReconView : public View {
private:
NavigationView& nav_;
RxRadioState radio_state_{};
RxRadioState rx_radio_state_{};
TxRadioState tx_radio_state_{
0 /* frequency */,
1750000 /* bandwidth */,
500000 /* sampling rate */
};
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 set_loop_config(bool v);
void clear_freqlist_for_ui_action();
@@ -81,7 +97,7 @@ class ReconView : public View {
void show_max(bool refresh_display = false);
void recon_pause();
void recon_resume();
void frequency_file_load(bool stop_all_before = false);
void frequency_file_load();
void on_statistics_update(const ChannelStatistics& statistics);
void on_index_delta(int32_t v);
void on_stepper_delta(int32_t v);
@@ -93,7 +109,7 @@ class ReconView : public View {
void load_persisted_settings();
bool recon_save_freq(const std::filesystem::path& path, size_t index, bool warn_if_exists);
// 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.
bool current_is_valid();
@@ -125,6 +141,7 @@ class ReconView : public View {
bool load_freqs{true};
bool load_ranges{true};
bool load_hamradios{true};
bool load_repeaters{true};
bool update_ranges{true};
bool fwd{true};
bool recon{true};
@@ -154,8 +171,8 @@ class ReconView : public View {
uint16_t last_nb_match{999};
uint16_t last_freq_lock{999};
size_t last_list_size{0};
int8_t last_rssi_min{-127};
int8_t last_rssi_med{-127};
int8_t last_rssi_min{127};
int8_t last_rssi_med{0};
int8_t last_rssi_max{-127};
int32_t last_index{-1};
int64_t last_freq{0};
@@ -163,6 +180,27 @@ class ReconView : public View {
systime_t chrono_start{};
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.
SettingsStore ui_settings{
"recon"sv,
@@ -354,17 +392,47 @@ class ReconView : public View {
{168, (35 * 8) - 4, 72, 28},
"<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{
Message::ID::CodedSquelch,
[this](const Message* const p) {
const auto message = *reinterpret_cast<const CodedSquelchMessage*>(p);
this->handle_coded_squelch(message.value);
handle_coded_squelch(message.value);
}};
MessageHandlerRegistration message_handler_stats{
Message::ID::ChannelStatistics,
[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);
}};
};
+67 -18
View File
@@ -30,6 +30,7 @@
#include "freqman_db.hpp"
#include "portapack.hpp"
#include "portapack_persistent_memory.hpp"
#include "file_path.hpp"
using namespace std;
using namespace portapack;
@@ -39,10 +40,10 @@ namespace ui {
ReconSetupViewMain::ReconSetupViewMain(NavigationView& nav, Rect parent_rect, std::string input_file, std::string output_file)
: View(parent_rect), _input_file{input_file}, _output_file{output_file} {
hidden(true);
add_children({&button_load_freqs,
add_children({&button_input_file,
&text_input_file,
&button_save_freqs,
&button_output_file,
&button_choose_output_file,
&button_choose_output_name,
&checkbox_autosave_freqs,
&checkbox_autostart_recon,
&checkbox_clear_output});
@@ -52,13 +53,13 @@ ReconSetupViewMain::ReconSetupViewMain(NavigationView& nav, Rect parent_rect, st
checkbox_clear_output.set_value(persistent_memory::recon_clear_output());
text_input_file.set(_input_file);
button_output_file.set_text(_output_file);
button_choose_output_name.set_text(_output_file);
button_load_freqs.on_select = [this, &nav](Button&) {
button_input_file.on_select = [this, &nav](Button&) {
auto open_view = nav.push<FileLoadView>(".TXT");
open_view->push_dir(freqman_dir);
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();
text_input_file.set(_input_file);
} else {
@@ -67,25 +68,24 @@ ReconSetupViewMain::ReconSetupViewMain(NavigationView& nav, Rect parent_rect, st
};
};
button_save_freqs.on_select = [this, &nav](Button&) {
button_choose_output_file.on_select = [this, &nav](Button&) {
auto open_view = nav.push<FileLoadView>(".TXT");
open_view->push_dir(freqman_dir);
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();
button_output_file.set_text(_output_file);
button_choose_output_name.set_text(_output_file);
} else {
nav.display_modal("LOAD ERROR", "A valid file from\nFREQMAN directory is\nrequired.");
nav.display_modal("SAVE ERROR", "A valid file from\nFREQMAN directory is\nrequired.");
}
};
};
button_output_file.on_select = [this, &nav](Button&) {
text_prompt(nav, _output_file, 28,
[this](std::string& buffer) {
_output_file = std::move(buffer);
button_output_file.set_text(_output_file);
});
button_choose_output_name.on_select = [this, &nav](Button&) {
text_prompt(nav, _output_file, 28, [this](std::string& buffer) {
_output_file = buffer;
button_choose_output_name.set_text(_output_file);
});
};
};
@@ -100,12 +100,19 @@ void ReconSetupViewMore::save() {
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_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_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() {
button_load_freqs.focus();
button_input_file.focus();
}
ReconSetupViewMore::ReconSetupViewMore(NavigationView& nav, Rect parent_rect)
@@ -114,16 +121,58 @@ ReconSetupViewMore::ReconSetupViewMore(NavigationView& nav, Rect parent_rect)
hidden(true);
add_children({&checkbox_load_freqs,
&checkbox_load_repeaters,
&checkbox_load_ranges,
&checkbox_load_hamradios,
&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(&Styles::yellow);
field_repeat_file_mode.set_style(&Styles::yellow);
text_repeat_nb.set_style(&Styles::yellow);
field_repeat_nb.set_style(&Styles::yellow);
checkbox_repeat_amp.set_style(&Styles::yellow);
text_repeat_gain.set_style(&Styles::yellow);
field_repeat_gain.set_style(&Styles::yellow);
text_repeat_delay.set_style(&Styles::yellow);
field_repeat_delay.set_style(&Styles::yellow);
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_hamradios.set_value(persistent_memory::recon_load_hamradios());
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_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() {
@@ -30,6 +30,7 @@
#include "ui_tabview.hpp"
#include "ui_navigation.hpp"
#include "string_format.hpp"
#include "ui_styles.hpp"
// 1Mhz helper
#ifdef OneMHz
@@ -37,16 +38,19 @@
#endif
#define OneMHz 1000000
// modes
// main app mode
#define RECON_MATCH_CONTINUOUS 0
#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)
#define STATS_UPDATE_INTERVAL 100
// maximum lock duration
#define RECON_MAX_LOCK_DURATION 9900
#define RECON_DEF_SQUELCH -14
// default number of match to have a lock
@@ -73,17 +77,18 @@ class ReconSetupViewMain : public View {
std::string _input_file{"RECON"};
std::string _output_file{"RECON_RESULTS"};
Button button_load_freqs{
Button button_input_file{
{1 * 8, 12, 18 * 8, 22},
"select input file"};
Text text_input_file{
{1 * 8, 4 + 2 * 16, 18 * 8, 22},
"RECON"};
Button button_save_freqs{
Button button_choose_output_file{
{1 * 8, 4 * 16 - 8, 18 * 8, 22},
"select output file"};
Button button_output_file{
Button button_choose_output_name{
{1 * 8, 5 * 16 - 2, 18 * 8, 22},
"RECON_RESULTS"};
@@ -115,26 +120,88 @@ class ReconSetupViewMore : public View {
Checkbox checkbox_load_freqs{
{1 * 8, 12},
3,
"input: load freqs"};
"load freq",
true};
Checkbox checkbox_load_repeaters{
{14 * 8, 12},
3,
"load repeater",
true};
Checkbox checkbox_load_ranges{
{1 * 8, 42},
3,
"input: load ranges"};
"load range",
true};
Checkbox checkbox_load_hamradios{
{1 * 8, 72},
3,
"input: load hamradios"};
"load hamradio",
true};
Checkbox checkbox_update_ranges_when_recon{
{1 * 8, 102},
3,
"auto update m-ranges"};
Checkbox checkbox_auto_record_locked{
{1 * 8, 132},
3,
"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 {
+5 -4
View File
@@ -32,6 +32,7 @@
#include "ui_receiver.hpp"
#include "ui_textentry.hpp"
#include "utility.hpp"
#include "file_path.hpp"
using namespace portapack;
namespace fs = std::filesystem;
@@ -246,7 +247,7 @@ RemoteEntryEditView::RemoteEntryEditView(
field_path.on_select = [this, &nav](TextField&) {
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) {
load_path(std::move(path));
refresh_ui();
@@ -355,7 +356,7 @@ RemoteView::RemoteView(
Dim waterfall_height = waterfall_bottom - waterfall_top;
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.
if (!load_remote(settings_.remote_path))
@@ -527,7 +528,7 @@ void RemoteView::new_remote() {
void RemoteView::open_remote() {
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) {
save_remote();
load_remote(std::move(path));
@@ -538,7 +539,7 @@ void RemoteView::open_remote() {
void RemoteView::init_remote() {
model_ = {"<Unnamed Remote>", {}};
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);
if (remote_path_.empty())
+6 -3
View File
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2018 Furrtek
* Copyright (C) 2023 Mark Thompson
*
* This file is part of PortaPack.
*
@@ -25,6 +26,7 @@
#include "optional.hpp"
#include "ui_fileman.hpp"
#include "ui_freqman.hpp"
#include "file_path.hpp"
using namespace portapack;
namespace fs = std::filesystem;
@@ -275,7 +277,7 @@ ScannerView::~ScannerView() {
}
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) {
text_max_index.set_style(&Styles::red);
@@ -336,7 +338,7 @@ ScannerView::ScannerView(
auto open_view = nav.push<FileLoadView>(".TXT");
open_view->push_dir(freqman_dir);
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();
frequency_file_load(new_file_path);
} else {
@@ -411,7 +413,7 @@ ScannerView::ScannerView(
button_mic_app.on_select = [this](Button&) {
if (scan_thread)
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());
};
@@ -564,6 +566,7 @@ void ScannerView::frequency_file_load(const fs::path& path) {
def_step_index = entry.step;
switch (entry.type) {
case freqman_type::Repeater:
case freqman_type::Single:
entries.push_back({entry.frequency_a, entry.description});
break;
+1
View File
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2018 Furrtek
* Copyright (C) 2023 Mark Thompson
*
* This file is part of PortaPack.
*
+353 -68
View File
@@ -3,6 +3,9 @@
* Copyright (C) 2016 Furrtek
* Copyright (C) 2023 gullradriel, Nilorea Studio Inc.
* Copyright (C) 2023 Kyle Reed
* Copyright (C) 2024 Mark Thompson
* Copyright (C) 2024 u-foka
* Copyleft (ɔ) 2024 zxkmm under GPL license
*
* This file is part of PortaPack.
*
@@ -27,6 +30,8 @@
#include "ui_navigation.hpp"
#include "ui_receiver.hpp"
#include "ui_touch_calibration.hpp"
#include "ui_text_editor.hpp"
#include "ui_external_items_menu_loader.hpp"
#include "portapack_persistent_memory.hpp"
#include "lpc43xx_cpp.hpp"
@@ -37,11 +42,14 @@ using namespace lpc43xx;
using namespace portapack;
#include "file.hpp"
#include "file_path.hpp"
namespace fs = std::filesystem;
#include "string_format.hpp"
#include "ui_styles.hpp"
#include "ui_font_fixed_8x16.hpp"
#include "cpld_update.hpp"
#include "config_mode.hpp"
namespace pmem = portapack::persistent_memory;
@@ -59,10 +67,9 @@ SetDateTimeView::SetDateTimeView(
NavigationView& nav) {
button_save.on_select = [&nav, this](Button&) {
const auto model = this->form_collect();
const rtc::RTC new_datetime{
model.year, model.month, model.day,
model.hour, model.minute, model.second};
rtcSetTime(&RTCD1, &new_datetime);
rtc::RTC new_datetime{model.year, model.month, model.day, model.hour, model.minute, model.second};
pmem::set_config_dst(model.dst);
rtc_time::set(new_datetime); // NB: 1 hour will be subtracted if value is stored in RTC during DST
nav.pop();
},
@@ -78,20 +85,57 @@ SetDateTimeView::SetDateTimeView(
&field_hour,
&field_minute,
&field_second,
&text_weekday,
&text_day_of_year,
&text_in_dst_range,
&checkbox_dst_enable,
&options_dst_start_which,
&options_dst_start_weekday,
&options_dst_start_month,
&options_dst_end_which,
&options_dst_end_weekday,
&options_dst_end_month,
&button_save,
&button_cancel,
});
// Populate DST options (same string text for start & end)
options_dst_start_which.set_options(which_options);
options_dst_end_which.set_options(which_options);
options_dst_start_weekday.set_options(weekday_options);
options_dst_end_weekday.set_options(weekday_options);
options_dst_start_month.set_options(month_options);
options_dst_end_month.set_options(month_options);
const auto dst_changed_fn = [this](size_t, uint32_t) {
handle_date_field_update();
};
const auto date_changed_fn = [this](int32_t) {
handle_date_field_update();
};
field_year.on_change = date_changed_fn;
field_month.on_change = date_changed_fn;
field_day.on_change = date_changed_fn;
options_dst_start_which.on_change = dst_changed_fn;
options_dst_start_weekday.on_change = dst_changed_fn;
options_dst_start_month.on_change = dst_changed_fn;
options_dst_end_which.on_change = dst_changed_fn;
options_dst_end_weekday.on_change = dst_changed_fn;
options_dst_end_month.on_change = dst_changed_fn;
rtc::RTC datetime;
rtcGetTime(&RTCD1, &datetime);
rtc_time::now(datetime);
SetDateTimeModel model{
datetime.year(),
datetime.month(),
datetime.day(),
datetime.hour(),
datetime.minute(),
datetime.second()};
datetime.second(),
pmem::config_dst()};
form_init(model);
}
@@ -106,6 +150,13 @@ void SetDateTimeView::form_init(const SetDateTimeModel& model) {
field_hour.set_value(model.hour);
field_minute.set_value(model.minute);
field_second.set_value(model.second);
checkbox_dst_enable.set_value(model.dst.b.dst_enabled);
options_dst_start_which.set_by_value(model.dst.b.start_which);
options_dst_start_weekday.set_by_value(model.dst.b.start_weekday);
options_dst_start_month.set_by_value(model.dst.b.start_month);
options_dst_end_which.set_by_value(model.dst.b.end_which);
options_dst_end_weekday.set_by_value(model.dst.b.end_weekday);
options_dst_end_month.set_by_value(model.dst.b.end_month);
}
SetDateTimeModel SetDateTimeView::form_collect() {
@@ -115,7 +166,29 @@ SetDateTimeModel SetDateTimeView::form_collect() {
.day = static_cast<uint8_t>(field_day.value()),
.hour = static_cast<uint8_t>(field_hour.value()),
.minute = static_cast<uint8_t>(field_minute.value()),
.second = static_cast<uint8_t>(field_second.value())};
.second = static_cast<uint8_t>(field_second.value()),
.dst = dst_collect()};
}
pmem::dst_config_t SetDateTimeView::dst_collect() {
pmem::dst_config_t dst;
dst.b.dst_enabled = static_cast<uint8_t>(checkbox_dst_enable.value());
dst.b.start_which = static_cast<uint8_t>(options_dst_start_which.selected_index_value());
dst.b.start_weekday = static_cast<uint8_t>(options_dst_start_weekday.selected_index_value());
dst.b.start_month = static_cast<uint8_t>(options_dst_start_month.selected_index_value());
dst.b.end_which = static_cast<uint8_t>(options_dst_end_which.selected_index_value());
dst.b.end_weekday = static_cast<uint8_t>(options_dst_end_weekday.selected_index_value());
dst.b.end_month = static_cast<uint8_t>(options_dst_end_month.selected_index_value());
return dst;
}
void SetDateTimeView::handle_date_field_update() {
auto weekday = rtc_time::day_of_week(field_year.value(), field_month.value(), field_day.value());
auto doy = rtc_time::day_of_year(field_year.value(), field_month.value(), field_day.value());
bool valid_date = (field_day.value() <= rtc_time::days_per_month(field_year.value(), field_month.value()));
text_weekday.set(valid_date ? weekday_options[weekday].first : "-");
text_day_of_year.set(valid_date ? to_string_dec_uint(doy, 3) : "-");
text_in_dst_range.set(checkbox_dst_enable.value() && rtc_time::dst_test_date_range(field_year.value(), doy, dst_collect()) ? "DST" : "");
}
/* SetRadioView ******************************************/
@@ -133,7 +206,6 @@ SetRadioView::SetRadioView(
&check_clkout,
&field_clkout_freq,
&labels_clkout_khz,
&value_freq_step,
&labels_bias,
&check_bias,
&disable_external_tcxo, // TODO: always show?
@@ -150,23 +222,10 @@ SetRadioView::SetRadioView(
});
}
std::string source_name("---");
switch (reference.source) {
case ClockManager::ReferenceSource::Xtal:
source_name = "HackRF";
break;
case ClockManager::ReferenceSource::PortaPack:
source_name = "PortaPack";
break;
case ClockManager::ReferenceSource::External:
source_name = "External";
break;
}
std::string source_name = clock_manager.get_source();
value_source.set(source_name);
value_source_frequency.set(
to_string_dec_uint(reference.frequency / 1000000, 2) + "." +
to_string_dec_uint((reference.frequency % 1000000) / 100, 4, '0') + " MHz");
value_source_frequency.set(clock_manager.get_freq());
// Make these Text controls look like Labels.
label_source.set_style(&Styles::light_grey);
@@ -185,29 +244,20 @@ SetRadioView::SetRadioView(
send_system_refresh();
};
field_clkout_freq.set_value(pmem::clkout_freq());
value_freq_step.set_style(&Styles::light_grey);
// Disallow CLKOUT freq change on hackrf_r9 due to dependencies on GP_CLKIN (same Si5351A clock);
// see comments in ClockManager::enable_clock_output()
if (hackrf_r9) {
if (pmem::clkout_freq() != 10000)
pmem::set_clkout_freq(10000);
field_clkout_freq.set_focusable(false);
}
field_clkout_freq.on_select = [this](NumberField&) {
freq_step_khz++;
if (freq_step_khz > 3) {
freq_step_khz = 0;
}
switch (freq_step_khz) {
case 0:
value_freq_step.set(" |");
break;
case 1:
value_freq_step.set(" | ");
break;
case 2:
value_freq_step.set(" | ");
break;
case 3:
value_freq_step.set("| ");
break;
}
field_clkout_freq.set_step(pow(10, freq_step_khz));
field_clkout_freq.set_value(pmem::clkout_freq());
field_clkout_freq.on_change = [this](SymField&) {
if (field_clkout_freq.to_integer() < 4) // Min. CLK out of Si5351A/B/C-B is 2.5khz , but in our application -intermediate freq 800Mhz-,Min working CLK=4khz.
field_clkout_freq.set_value(4);
if (field_clkout_freq.to_integer() > 60000)
field_clkout_freq.set_value(60000);
};
check_bias.set_value(get_antenna_bias());
@@ -248,7 +298,7 @@ void SetRadioView::form_init(const SetFrequencyCorrectionModel& model) {
SetFrequencyCorrectionModel SetRadioView::form_collect() {
return {
.ppm = static_cast<int8_t>(field_ppm.value()),
.freq = static_cast<uint32_t>(field_clkout_freq.value()),
.freq = static_cast<uint32_t>(field_clkout_freq.to_integer()),
};
}
@@ -270,6 +320,7 @@ SetUIView::SetUIView(NavigationView& nav) {
&toggle_bias_tee,
&toggle_clock,
&toggle_mute,
&toggle_fake_brightness,
&toggle_sd_card,
&button_save,
&button_cancel});
@@ -303,6 +354,7 @@ SetUIView::SetUIView(NavigationView& nav) {
toggle_clock.set_value(!pmem::ui_hide_clock());
toggle_speaker.set_value(!pmem::ui_hide_speaker());
toggle_mute.set_value(!pmem::ui_hide_mute());
toggle_fake_brightness.set_value(!pmem::ui_hide_fake_brightness());
toggle_sd_card.set_value(!pmem::ui_hide_sd_card());
button_save.on_select = [&nav, this](Button&) {
@@ -329,6 +381,7 @@ SetUIView::SetUIView(NavigationView& nav) {
pmem::set_ui_hide_clock(!toggle_clock.value());
pmem::set_ui_hide_speaker(!toggle_speaker.value());
pmem::set_ui_hide_mute(!toggle_mute.value());
pmem::set_ui_hide_fake_brightness(!toggle_fake_brightness.value());
pmem::set_ui_hide_sd_card(!toggle_sd_card.value());
send_system_refresh();
@@ -343,6 +396,38 @@ void SetUIView::focus() {
button_save.focus();
}
/* SetSDCardView *********************************************/
SetSDCardView::SetSDCardView(NavigationView& nav) {
add_children({&labels,
&checkbox_sdcard_speed,
&button_test_sdcard_high_speed,
&text_sdcard_test_status,
&button_save,
&button_cancel});
checkbox_sdcard_speed.set_value(pmem::config_sdcard_high_speed_io());
button_test_sdcard_high_speed.on_select = [&nav, this](Button&) {
pmem::set_config_sdcard_high_speed_io(true, false);
text_sdcard_test_status.set("!! HIGH SPEED MODE ON !!");
};
button_save.on_select = [&nav, this](Button&) {
pmem::set_config_sdcard_high_speed_io(checkbox_sdcard_speed.value(), true);
send_system_refresh();
nav.pop();
};
button_cancel.on_select = [&nav, this](Button&) {
nav.pop();
};
}
void SetSDCardView::focus() {
button_save.focus();
}
/* SetConverterSettingsView ******************************/
SetConverterSettingsView::SetConverterSettingsView(NavigationView& nav) {
@@ -427,7 +512,7 @@ SetFrequencyCorrectionView::SetFrequencyCorrectionView(NavigationView& nav) {
pmem::set_freq_rx_correction_updown(v);
};
opt_tx_correction_mode.set_by_value(pmem::config_freq_rx_correction_updown());
opt_tx_correction_mode.set_by_value(pmem::config_freq_tx_correction_updown());
opt_tx_correction_mode.on_change = [this](size_t, OptionsField::value_t v) {
pmem::set_freq_tx_correction_updown(v);
};
@@ -488,17 +573,17 @@ SetPersistentMemoryView::SetPersistentMemoryView(NavigationView& nav) {
check_use_sdcard_for_pmem.on_select = [this](Checkbox&, bool v) {
File pmem_flag_file_handle;
if (v) {
if (fs::file_exists(PMEM_FILEFLAG)) {
if (fs::file_exists(settings_dir / PMEM_FILEFLAG)) {
text_pmem_status.set("P.Mem flag file present.");
} else {
auto error = pmem_flag_file_handle.create(PMEM_FILEFLAG);
auto error = pmem_flag_file_handle.create(settings_dir / PMEM_FILEFLAG);
if (error)
text_pmem_status.set("Error creating P.Mem File!");
else
text_pmem_status.set("P.Mem flag file created.");
}
} else {
auto result = delete_file(PMEM_FILEFLAG);
auto result = delete_file(settings_dir / PMEM_FILEFLAG);
if (result.code() != FR_OK)
text_pmem_status.set("Error deleting P.Mem flag!");
else
@@ -531,6 +616,8 @@ SetPersistentMemoryView::SetPersistentMemoryView(NavigationView& nav) {
[this](bool choice) {
if (choice) {
pmem::cache::defaults();
// Refresh status bar
send_system_refresh();
}
});
};
@@ -549,13 +636,17 @@ void SetPersistentMemoryView::focus() {
SetAudioView::SetAudioView(NavigationView& nav) {
add_children({&labels,
&field_tone_mix,
&checkbox_beep_on_packets,
&button_save,
&button_cancel});
field_tone_mix.set_value(pmem::tone_mix());
checkbox_beep_on_packets.set_value(pmem::beep_on_packets());
button_save.on_select = [&nav, this](Button&) {
pmem::set_tone_mix(field_tone_mix.value());
pmem::set_beep_on_packets(checkbox_beep_on_packets.value());
audio::output::update_audio_mute();
nav.pop();
};
@@ -600,13 +691,16 @@ void SetQRCodeView::focus() {
SetEncoderDialView::SetEncoderDialView(NavigationView& nav) {
add_children({&labels,
&field_encoder_dial_sensitivity,
&field_encoder_rate_multiplier,
&button_save,
&button_cancel});
field_encoder_dial_sensitivity.set_by_value(pmem::config_encoder_dial_sensitivity());
field_encoder_dial_sensitivity.set_by_value(pmem::encoder_dial_sensitivity());
field_encoder_rate_multiplier.set_value(pmem::encoder_rate_multiplier());
button_save.on_select = [&nav, this](Button&) {
pmem::set_encoder_dial_sensitivity(field_encoder_dial_sensitivity.selected_index_value());
pmem::set_encoder_rate_multiplier(field_encoder_rate_multiplier.value());
nav.pop();
};
@@ -619,25 +713,216 @@ void SetEncoderDialView::focus() {
button_save.focus();
}
/* AppSettingsView ************************************/
AppSettingsView::AppSettingsView(
NavigationView& nav)
: nav_{nav} {
add_children({&labels,
&menu_view});
menu_view.set_parent_rect({0, 3 * 8, 240, 33 * 8});
ensure_directory(settings_dir);
for (const auto& entry : std::filesystem::directory_iterator(settings_dir, u"*.ini")) {
auto path = settings_dir / entry.path();
menu_view.add_item({path.filename().string().substr(0, 26),
ui::Color::dark_cyan(),
&bitmap_icon_file_text,
[this, path](KeyEvent) {
nav_.push<TextEditorView>(path);
}});
}
}
void AppSettingsView::focus() {
menu_view.focus();
}
/* SetConfigModeView ************************************/
SetConfigModeView::SetConfigModeView(NavigationView& nav) {
add_children({&labels,
&checkbox_config_mode_enabled,
&button_save,
&button_cancel});
checkbox_config_mode_enabled.set_value(!pmem::config_disable_config_mode());
button_save.on_select = [&nav, this](Button&) {
pmem::set_config_disable_config_mode(!checkbox_config_mode_enabled.value());
nav.pop();
};
button_cancel.on_select = [&nav, this](Button&) {
nav.pop();
};
}
void SetConfigModeView::focus() {
button_save.focus();
}
/* SetFakeBrightnessView ************************************/
SetFakeBrightnessView::SetFakeBrightnessView(NavigationView& nav) {
add_children({&labels,
&field_fake_brightness,
&button_save,
&button_cancel,
&checkbox_brightness_switch});
field_fake_brightness.set_by_value(pmem::fake_brightness_level());
checkbox_brightness_switch.set_value(pmem::apply_fake_brightness());
button_save.on_select = [&nav, this](Button&) {
pmem::set_apply_fake_brightness(checkbox_brightness_switch.value());
pmem::set_fake_brightness_level(field_fake_brightness.selected_index_value());
send_system_refresh();
nav.pop();
};
button_cancel.on_select = [&nav, this](Button&) {
nav.pop();
};
}
void SetFakeBrightnessView::focus() {
button_save.focus();
}
/* SetMenuColorView ************************************/
void SetMenuColorView::paint_sample() {
Color c = Color(field_red_level.value(), field_green_level.value(), field_blue_level.value());
button_sample.set_bg_color(c);
}
SetMenuColorView::SetMenuColorView(NavigationView& nav) {
add_children({&labels,
&button_sample,
&field_red_level,
&field_green_level,
&field_blue_level,
&button_save,
&button_cancel});
button_sample.set_focusable(false);
Color c = pmem::menu_color();
field_red_level.set_value(c.r());
field_green_level.set_value(c.g());
field_blue_level.set_value(c.b());
paint_sample();
const auto color_changed_fn = [this](int32_t) {
paint_sample();
};
field_red_level.on_change = color_changed_fn;
field_green_level.on_change = color_changed_fn;
field_blue_level.on_change = color_changed_fn;
button_save.on_select = [&nav, this](Button&) {
Color c = Color(field_red_level.value(), field_green_level.value(), field_blue_level.value());
pmem::set_menu_color(c);
send_system_refresh();
nav.pop();
};
button_cancel.on_select = [&nav, this](Button&) {
nav.pop();
};
}
void SetMenuColorView::focus() {
button_save.focus();
}
/* SetAutoStartView ************************************/
SetAutostartView::SetAutostartView(NavigationView& nav) {
add_children({&labels,
&button_save,
&button_cancel,
&options});
button_save.on_select = [&nav, this](Button&) {
autostart_app = "";
if (selected != 0) {
auto it = full_app_list.find(selected);
if (it != full_app_list.end())
autostart_app = it->second;
}
nav.pop();
};
button_cancel.on_select = [&nav, this](Button&) {
nav.pop();
};
// options
i = 0;
OptionsField::option_t o{"-none-", i};
opts.emplace_back(o);
for (auto& app : NavigationView::appList) {
if (app.id == nullptr) continue;
i++;
o = {app.displayName, i};
opts.emplace_back(o);
full_app_list.emplace(i, app.id);
if (autostart_app == app.id) selected = i;
}
ExternalItemsMenuLoader::load_all_external_items_callback([this](ui::AppInfoConsole& app) {
if (app.appCallName == nullptr) return;
i++;
OptionsField::option_t o = {app.appFriendlyName, i};
opts.emplace_back(o);
full_app_list.emplace(i, app.appCallName);
if (autostart_app == app.appCallName) selected = i;
});
options.set_options(opts);
options.on_change = [this](size_t, OptionsField::value_t v) {
selected = v;
};
options.set_selected_index(selected);
}
void SetAutostartView::focus() {
options.focus();
}
/* SettingsMenuView **************************************/
SettingsMenuView::SettingsMenuView(NavigationView& nav) {
if (pmem::show_gui_return_icon()) {
add_items({{"..", ui::Color::light_grey(), &bitmap_icon_previous, [&nav]() { nav.pop(); }}});
}
add_items({
{"Audio", ui::Color::dark_cyan(), &bitmap_icon_speaker, [&nav]() { nav.push<SetAudioView>(); }},
{"Calibration", ui::Color::dark_cyan(), &bitmap_icon_options_touch, [&nav]() { nav.push<TouchCalibrationView>(); }},
{"Converter", ui::Color::dark_cyan(), &bitmap_icon_options_radio, [&nav]() { nav.push<SetConverterSettingsView>(); }},
{"Date/Time", ui::Color::dark_cyan(), &bitmap_icon_options_datetime, [&nav]() { nav.push<SetDateTimeView>(); }},
{"Encoder Dial", ui::Color::dark_cyan(), &bitmap_icon_setup, [&nav]() { nav.push<SetEncoderDialView>(); }},
{"Freq. Correct", ui::Color::dark_cyan(), &bitmap_icon_options_radio, [&nav]() { nav.push<SetFrequencyCorrectionView>(); }},
{"P.Memory Mgmt", ui::Color::dark_cyan(), &bitmap_icon_memory, [&nav]() { nav.push<SetPersistentMemoryView>(); }},
{"QR Code", ui::Color::dark_cyan(), &bitmap_icon_qr_code, [&nav]() { nav.push<SetQRCodeView>(); }},
{"Radio", ui::Color::dark_cyan(), &bitmap_icon_options_radio, [&nav]() { nav.push<SetRadioView>(); }},
{"User Interface", ui::Color::dark_cyan(), &bitmap_icon_options_ui, [&nav]() { nav.push<SetUIView>(); }},
});
SettingsMenuView::SettingsMenuView(NavigationView& nav)
: nav_(nav) {
set_max_rows(2); // allow wider buttons
}
void SettingsMenuView::on_populate() {
if (pmem::show_gui_return_icon()) {
add_items({{"..", ui::Color::light_grey(), &bitmap_icon_previous, [this]() { nav_.pop(); }}});
}
add_items({
{"App Settings", ui::Color::dark_cyan(), &bitmap_icon_notepad, [this]() { nav_.push<AppSettingsView>(); }},
{"Audio", ui::Color::dark_cyan(), &bitmap_icon_speaker, [this]() { nav_.push<SetAudioView>(); }},
{"Calibration", ui::Color::dark_cyan(), &bitmap_icon_options_touch, [this]() { nav_.push<TouchCalibrationView>(); }},
{"Config Mode", ui::Color::dark_cyan(), &bitmap_icon_clk_ext, [this]() { nav_.push<SetConfigModeView>(); }},
{"Converter", ui::Color::dark_cyan(), &bitmap_icon_options_radio, [this]() { nav_.push<SetConverterSettingsView>(); }},
{"Date/Time", ui::Color::dark_cyan(), &bitmap_icon_options_datetime, [this]() { nav_.push<SetDateTimeView>(); }},
{"Encoder Dial", ui::Color::dark_cyan(), &bitmap_icon_setup, [this]() { nav_.push<SetEncoderDialView>(); }},
{"Freq. Correct", ui::Color::dark_cyan(), &bitmap_icon_options_radio, [this]() { nav_.push<SetFrequencyCorrectionView>(); }},
{"P.Memory Mgmt", ui::Color::dark_cyan(), &bitmap_icon_memory, [this]() { nav_.push<SetPersistentMemoryView>(); }},
{"Radio", ui::Color::dark_cyan(), &bitmap_icon_options_radio, [this]() { nav_.push<SetRadioView>(); }},
{"SD Card", ui::Color::dark_cyan(), &bitmap_icon_sdcard, [this]() { nav_.push<SetSDCardView>(); }},
{"User Interface", ui::Color::dark_cyan(), &bitmap_icon_options_ui, [this]() { nav_.push<SetUIView>(); }},
//{"QR Code", ui::Color::dark_cyan(), &bitmap_icon_qr_code, [this]() { nav_.push<SetQRCodeView>(); }},
{"Brightness", ui::Color::dark_cyan(), &bitmap_icon_brightness, [this]() { nav_.push<SetFakeBrightnessView>(); }},
{"Menu Color", ui::Color::dark_cyan(), &bitmap_icon_brightness, [this]() { nav_.push<SetMenuColorView>(); }},
{"Autostart", ui::Color::dark_cyan(), &bitmap_icon_setup, [this]() { nav_.push<SetAutostartView>(); }},
});
}
} /* namespace ui */
+336 -33
View File
@@ -3,6 +3,9 @@
* Copyright (C) 2016 Furrtek
* Copyright (C) 2023 gullradriel, Nilorea Studio Inc.
* Copyright (C) 2023 Kyle Reed
* Copyright (C) 2024 Mark Thompson
* Copyright (C) 2024 u-foka
* Copyleft (ɔ) 2024 zxkmm under GPL license
*
* This file is part of PortaPack.
*
@@ -44,6 +47,7 @@ struct SetDateTimeModel {
uint8_t hour;
uint8_t minute;
uint8_t second;
portapack::persistent_memory::dst_config_t dst;
};
class SetDateTimeView : public View {
@@ -55,56 +59,114 @@ class SetDateTimeView : public View {
std::string title() const override { return "Date/Time"; };
private:
using option_t = std::pair<std::string, int32_t>;
std::vector<option_t> which_options = {{"1st", 0}, {"2nd", 1}, {"3rd", 2}, {"4th", 3}, {"Last", 4}};
std::vector<option_t> weekday_options = {{"Sun", 0}, {"Mon", 1}, {"Tue", 2}, {"Wed", 3}, {"Thu", 4}, {"Fri", 5}, {"Sat", 6}};
std::vector<option_t> month_options = {{"Jan", 1}, {"Feb", 2}, {"Mar", 3}, {"Apr", 4}, {"May", 5}, {"Jun", 6}, {"Jul", 7}, {"Aug", 8}, {"Sep", 9}, {"Oct", 10}, {"Nov", 11}, {"Dec", 12}};
Labels labels{
{{1 * 8, 1 * 16}, "Adjust the RTC clock date &", Color::light_grey()},
{{1 * 8, 2 * 16}, "time. If clock resets after", Color::light_grey()},
{{1 * 8, 3 * 16}, "reboot, coin batt. is dead. ", Color::light_grey()},
{{5 * 8, 8 * 16 - 2}, "YYYY-MM-DD HH:MM:SS", Color::grey()},
{{9 * 8, 9 * 16}, "- - : :", Color::light_grey()}};
{{1 * 8, 5 * 16 - 2}, "YYYY-MM-DD HH:MM:SS DoW DoY", Color::grey()},
{{5 * 8, 6 * 16}, "- - : :", Color::light_grey()},
{{1 * 8, 11 * 16}, "DST adds 1 hour to RTC time.", Color::light_grey()},
{{0 * 8, 12 * 16}, "Start: 0:00 on Nth DDD in", Color::light_grey()},
{{0 * 8, 13 * 16}, "End: 1:00 on Nth DDD in", Color::light_grey()}};
NumberField field_year{
{5 * 8, 9 * 16},
{1 * 8, 6 * 16},
4,
{2015, 2099},
1,
'0',
true,
};
NumberField field_month{
{10 * 8, 9 * 16},
{6 * 8, 6 * 16},
2,
{1, 12},
1,
'0',
true,
};
NumberField field_day{
{13 * 8, 9 * 16},
{9 * 8, 6 * 16},
2,
{1, 31},
1,
'0',
true,
};
NumberField field_hour{
{16 * 8, 9 * 16},
{12 * 8, 6 * 16},
2,
{0, 23},
1,
'0',
true,
};
NumberField field_minute{
{19 * 8, 9 * 16},
{15 * 8, 6 * 16},
2,
{0, 59},
1,
'0',
true,
};
NumberField field_second{
{22 * 8, 9 * 16},
{18 * 8, 6 * 16},
2,
{0, 59},
1,
'0',
true,
};
Text text_weekday{
{22 * 8, 6 * 16, 3 * 8, 16},
""};
Text text_day_of_year{
{26 * 8, 6 * 16, 3 * 8, 16},
""};
Text text_in_dst_range{
{17 * 8, 7 * 16, 3 * 8, 16},
""};
Checkbox checkbox_dst_enable{
{2 * 8, 9 * 16},
23,
"Enable Daylight Savings"};
OptionsField options_dst_start_which{
{15 * 8, 12 * 16},
4,
{}};
OptionsField options_dst_start_weekday{
{20 * 8, 12 * 16},
3,
{}};
OptionsField options_dst_start_month{
{27 * 8, 12 * 16},
3,
{}};
OptionsField options_dst_end_which{
{15 * 8, 13 * 16},
4,
{}};
OptionsField options_dst_end_weekday{
{20 * 8, 13 * 16},
3,
{}};
OptionsField options_dst_end_month{
{27 * 8, 13 * 16},
3,
{}};
Button button_save{
{2 * 8, 16 * 16, 12 * 8, 32},
@@ -115,6 +177,8 @@ class SetDateTimeView : public View {
void form_init(const SetDateTimeModel& model);
SetDateTimeModel form_collect();
portapack::persistent_memory::dst_config_t dst_collect();
void handle_date_field_update();
};
struct SetFrequencyCorrectionModel {
@@ -163,20 +227,14 @@ class SetRadioView : public View {
13,
"Enable CLKOUT"};
NumberField field_clkout_freq{
SymField field_clkout_freq{
{20 * 8, 6 * 16},
5,
{10, 60000},
1000,
' '};
SymField::Type::Dec};
Labels labels_clkout_khz{
{{26 * 8, 6 * 16}, "kHz", Color::light_grey()}};
Text value_freq_step{
{21 * 8, (7 * 16), 4 * 8, 16},
"| "};
Labels labels_bias{
{{4 * 8 + 4, 8 * 16}, "CAUTION: Ensure that all", Color::red()},
{{5 * 8 + 0, 9 * 16}, "devices attached to the", Color::red()},
@@ -265,39 +323,43 @@ class SetUIView : public View {
};
ImageToggle toggle_camera{
{7 * 8, 14 * 16 + 2, 16, 16},
{6 * 8, 14 * 16 + 2, 16, 16},
&bitmap_icon_camera};
ImageToggle toggle_sleep{
{9 * 8, 14 * 16 + 2, 16, 16},
{8 * 8, 14 * 16 + 2, 16, 16},
&bitmap_icon_sleep};
ImageToggle toggle_stealth{
{11 * 8, 14 * 16 + 2, 16, 16},
{10 * 8, 14 * 16 + 2, 16, 16},
&bitmap_icon_stealth};
ImageToggle toggle_converter{
{13 * 8, 14 * 16 + 2, 16, 16},
{12 * 8, 14 * 16 + 2, 16, 16},
&bitmap_icon_upconvert};
ImageToggle toggle_bias_tee{
{15 * 8, 14 * 16 + 2, 16, 16},
{14 * 8, 14 * 16 + 2, 16, 16},
&bitmap_icon_biast_off};
ImageToggle toggle_clock{
{17 * 8, 14 * 16 + 2, 8, 16},
{16 * 8, 14 * 16 + 2, 8, 16},
&bitmap_icon_clk_ext};
ImageToggle toggle_mute{
{18 * 8, 14 * 16 + 2, 16, 16},
{17 * 8, 14 * 16 + 2, 16, 16},
&bitmap_icon_speaker_and_headphones_mute};
ImageToggle toggle_speaker{
{20 * 8, 14 * 16 + 2, 16, 16},
{19 * 8, 14 * 16 + 2, 16, 16},
&bitmap_icon_speaker_mute};
ImageToggle toggle_fake_brightness{
{21 * 8, 14 * 16 + 2, 16, 16},
&bitmap_icon_brightness};
ImageToggle toggle_sd_card{
{22 * 8, 14 * 16 + 2, 16, 16},
{23 * 8, 14 * 16 + 2, 16, 16},
&bitmap_sd_card_ok};
Button button_save{
@@ -306,8 +368,43 @@ class SetUIView : public View {
Button button_cancel{
{16 * 8, 16 * 16, 12 * 8, 32},
"Cancel",
};
"Cancel"};
};
class SetSDCardView : public View {
public:
SetSDCardView(NavigationView& nav);
void focus() override;
std::string title() const override { return "SD Card"; };
private:
Labels labels{
// 01234567890123456789012345678
{{1 * 8, 120 - 48}, " HIGH SPEED SDCARD IO ", Color::light_grey()},
{{1 * 8, 120 - 32}, " May or may not work !! ", Color::light_grey()}};
Checkbox checkbox_sdcard_speed{
{2 * 8, 120},
20,
"enable high speed IO"};
Button button_test_sdcard_high_speed{
{2 * 8, 152, 27 * 8, 32},
"TEST BUTTON (NO PMEM SAVE)"};
Text text_sdcard_test_status{
{2 * 8, 198, 28 * 8, 16},
""};
Button button_save{
{2 * 8, 16 * 16, 12 * 8, 32},
"Save"};
Button button_cancel{
{16 * 8, 16 * 16, 12 * 8, 32},
"Cancel"};
};
class SetConverterSettingsView : public View {
@@ -409,8 +506,12 @@ class SetAudioView : public View {
Labels labels{
{{1 * 8, 1 * 16}, "Controls the volume of the", Color::light_grey()},
{{1 * 8, 2 * 16}, "tone when transmitting in", Color::light_grey()},
{{1 * 8, 3 * 16}, "Soundboard or Mic apps.", Color::light_grey()},
{{1 * 8, 3 * 16}, "Soundboard or Mic apps:", Color::light_grey()},
{{2 * 8, 5 * 16}, "Tone key mix: %", Color::light_grey()},
{{1 * 8, 8 * 16}, "Controls whether apps should", Color::light_grey()},
{{1 * 8, 9 * 16}, "beep on speaker & headphone", Color::light_grey()},
{{1 * 8, 10 * 16}, "when a packet is received", Color::light_grey()},
{{1 * 8, 11 * 16}, "(not all apps support this):", Color::light_grey()},
};
NumberField field_tone_mix{
@@ -420,6 +521,11 @@ class SetAudioView : public View {
1,
'0'};
Checkbox checkbox_beep_on_packets{
{3 * 8, 13 * 16},
16,
"Beep on RX packets"};
Button button_save{
{2 * 8, 16 * 16, 12 * 8, 32},
"Save"};
@@ -460,6 +566,7 @@ class SetQRCodeView : public View {
};
using portapack::persistent_memory::encoder_dial_sensitivity;
using portapack::persistent_memory::encoder_rate_multiplier;
class SetEncoderDialView : public View {
public:
@@ -471,18 +578,30 @@ class SetEncoderDialView : public View {
private:
Labels labels{
{{1 * 8, 1 * 16}, "Adjusts how many steps to", Color::light_grey()},
{{1 * 8, 2 * 16}, "change the encoder value.", Color::light_grey()},
{{2 * 8, 4 * 16}, "Dial sensitivity:", Color::light_grey()},
{{1 * 8, 1 * 16}, "Adjusts sensitivity to dial", Color::light_grey()},
{{1 * 8, 2 * 16}, "rotation position (number of", Color::light_grey()},
{{1 * 8, 3 * 16}, "steps per full rotation):", Color::light_grey()},
{{2 * 8, 5 * 16}, "Dial sensitivity:", Color::light_grey()},
{{1 * 8, 8 * 16}, "Adjusts sensitivity to dial", Color::light_grey()},
{{1 * 8, 9 * 16}, "rotation rate (default 1", Color::light_grey()},
{{1 * 8, 10 * 16}, "means no rate dependency):", Color::light_grey()},
{{3 * 8, 12 * 16}, "Rate multiplier:", Color::light_grey()},
};
OptionsField field_encoder_dial_sensitivity{
{20 * 8, 4 * 16},
{20 * 8, 5 * 16},
6,
{{"LOW", encoder_dial_sensitivity::DIAL_SENSITIVITY_LOW},
{"NORMAL", encoder_dial_sensitivity::DIAL_SENSITIVITY_NORMAL},
{"HIGH", encoder_dial_sensitivity::DIAL_SENSITIVITY_HIGH}}};
NumberField field_encoder_rate_multiplier{
{20 * 8, 12 * 16},
2,
{1, 15},
1,
' '};
Button button_save{
{2 * 8, 16 * 16, 12 * 8, 32},
"Save"};
@@ -535,11 +654,195 @@ class SetPersistentMemoryView : public View {
};
};
class AppSettingsView : public View {
public:
AppSettingsView(NavigationView& nav);
std::string title() const override { return "App Settings"; };
void focus() override;
private:
NavigationView& nav_;
Labels labels{
{{0, 4}, "Select file to edit:", Color::white()}};
MenuView menu_view{
{0, 2 * 8, 240, 26 * 8},
true};
};
class SetConfigModeView : public View {
public:
SetConfigModeView(NavigationView& nav);
void focus() override;
std::string title() const override { return "Config Mode"; };
private:
Labels labels{
{{1 * 8, 1 * 16}, "Controls whether firmware", Color::light_grey()},
{{1 * 8, 2 * 16}, "will enter Config Mode", Color::light_grey()},
{{1 * 8, 3 * 16}, "after a boot failure.", Color::light_grey()},
};
Checkbox checkbox_config_mode_enabled{
{2 * 8, 6 * 16},
16,
"Config Mode enable"};
Button button_save{
{2 * 8, 16 * 16, 12 * 8, 32},
"Save"};
Button button_cancel{
{16 * 8, 16 * 16, 12 * 8, 32},
"Cancel",
};
};
using portapack::persistent_memory::fake_brightness_level_options;
class SetFakeBrightnessView : public View {
public:
SetFakeBrightnessView(NavigationView& nav);
void focus() override;
std::string title() const override { return "Brightness"; };
private:
Labels labels{
{{1 * 8, 1 * 16}, "Limits screen brightness", Color::light_grey()},
{{1 * 8, 2 * 16}, "(has a small performance", Color::light_grey()},
{{1 * 8, 3 * 16}, "impact when enabled).", Color::light_grey()},
{{2 * 8, 8 * 16}, "Brightness:", Color::light_grey()},
};
OptionsField field_fake_brightness{
{20 * 8, 8 * 16},
6,
{{"12.5%", fake_brightness_level_options::BRIGHTNESS_12p5},
{"25%", fake_brightness_level_options::BRIGHTNESS_25},
{"50%", fake_brightness_level_options::BRIGHTNESS_50}}};
Checkbox checkbox_brightness_switch{
{1 * 8, 5 * 16},
16,
"Enable brightness adjust"};
Button button_save{
{2 * 8, 16 * 16, 12 * 8, 32},
"Save"};
Button button_cancel{
{16 * 8, 16 * 16, 12 * 8, 32},
"Cancel",
};
};
class SetMenuColorView : public View {
public:
SetMenuColorView(NavigationView& nav);
void focus() override;
std::string title() const override { return "Menu Color"; };
private:
void paint_sample();
Labels labels{
{{3 * 8, 1 * 16}, "Menu Button Color Scheme", Color::light_grey()},
{{2 * 8, 8 * 16}, "Red Level:", Color::light_grey()},
{{2 * 8, 9 * 16}, "Green Level:", Color::light_grey()},
{{2 * 8, 10 * 16}, "Blue Level:", Color::light_grey()},
};
NewButton button_sample{
{8 * 8, 4 * 16, 14 * 8, 3 * 16},
"New Color",
&bitmap_icon_brightness,
};
NumberField field_red_level{
{15 * 8, 8 * 16},
3,
{8, 248},
8,
' ',
};
NumberField field_green_level{
{15 * 8, 9 * 16},
3,
{8, 248},
8,
' ',
};
NumberField field_blue_level{
{15 * 8, 10 * 16},
3,
{8, 248},
8,
' ',
};
Button button_save{
{2 * 8, 16 * 16, 12 * 8, 32},
"Save"};
Button button_cancel{
{16 * 8, 16 * 16, 12 * 8, 32},
"Cancel",
};
};
class SetAutostartView : public View {
public:
SetAutostartView(NavigationView& nav);
void focus() override;
std::string title() const override { return "Autostart"; };
private:
int32_t i = 0;
std::string autostart_app{""};
OptionsField::options_t opts{};
std::map<int32_t, std::string> full_app_list{}; // looking table
int32_t selected = 0;
SettingsStore nav_setting{
"nav"sv,
{{"autostart_app"sv, &autostart_app}}};
Labels labels{
{{1 * 8, 1 * 16}, "Select app to start on boot", Color::light_grey()},
{{2 * 8, 2 * 16}, "(an SD Card is required)", Color::light_grey()}};
Button button_save{
{2 * 8, 16 * 16, 12 * 8, 32},
"Save"};
OptionsField options{
{8 * 8, 4 * 16},
30,
{}};
Button button_cancel{
{16 * 8, 16 * 16, 12 * 8, 32},
"Cancel",
};
};
class SettingsMenuView : public BtnGridView {
public:
SettingsMenuView(NavigationView& nav);
std::string title() const override { return "Settings"; };
private:
NavigationView& nav_;
void on_populate() override;
};
} /* namespace ui */
+8
View File
@@ -87,10 +87,18 @@ SigGenView::SigGenView(
&field_stop,
&tx_view});
symfield_tone.hidden(1); // At first launch , by default we are in CW Shape has NO MOD , we are not using Tone modulation.
symfield_tone.set_value(1000); // Default: 1000 Hz
options_shape.on_change = [this](size_t, OptionsField::value_t v) {
text_shape.set(shape_strings[v]);
if (auto_update)
update_config();
if ((v == 0) || (v == 6)) { // In Shapes Options (CW & Pseudo Random Noise) we are not using Tone modulation freq.
symfield_tone.hidden(1);
} else {
symfield_tone.hidden(0);
}
set_dirty();
};
options_shape.set_selected_index(0);
text_shape.set(shape_strings[0]);
+14 -11
View File
@@ -58,15 +58,16 @@ class SigGenView : public View {
app_settings::SettingsManager settings_{
"tx_siggen", app_settings::Mode::TX};
const std::string shape_strings[7] = {
"CW-just carrier",
"Sine signal ",
"Triangle signal",
"Saw up signal ",
"Saw down signal",
"Square signal ",
"Noise signal " // using 16 bits LFSR register, 16 order polynomial feedback.
};
const std::string shape_strings[9] = {
"CW (No mod.) ",
"Sine mod. FM",
"Triangle mod.FM", // max 15 character text space.
"Saw up mod. FM",
"Saw down mod.FM",
"Square mod. FM",
"Pseudo Noise FM", // using 16 bits LFSR register, 16 order polynomial feedback.
"BPSK 0,1,0,1...",
"QPSK 00-01-10.."};
bool auto_update{false};
@@ -86,10 +87,12 @@ class SigGenView : public View {
{&bitmap_sig_saw_up, 3},
{&bitmap_sig_saw_down, 4},
{&bitmap_sig_square, 5},
{&bitmap_sig_noise, 6}}};
{&bitmap_sig_noise, 6},
{&bitmap_sig_noise, 7}, // Pending to add a correct BPSK icon.
{&bitmap_sig_noise, 8}}}; // Pending to add a correct QPSK icon.
Text text_shape{
{15 * 8, 4 + 10, 8 * 8, 16},
{15 * 8, 4 + 10, 15 * 8, 16},
""};
SymField symfield_tone{
+33 -56
View File
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2017 Furrtek
* Copyright (C) 2024 Mark Thompson
*
* This file is part of PortaPack.
*
@@ -24,12 +25,14 @@
#include "baseband_api.hpp"
#include "audio.hpp"
#include "app_settings.hpp"
#include "file_path.hpp"
#include "portapack.hpp"
#include <cstring>
#include <stdio.h>
using namespace portapack;
namespace pmem = portapack::persistent_memory;
#include "string_format.hpp"
#include "complex.hpp"
@@ -52,7 +55,6 @@ SondeView::SondeView(NavigationView& nav)
&field_vga,
&rssi,
&field_volume,
&check_beep,
&check_log,
&check_crc,
&text_signature,
@@ -70,14 +72,12 @@ SondeView::SondeView(NavigationView& nav)
geopos.set_read_only(true);
check_beep.on_select = [this](Checkbox&, bool v) {
beep = v;
};
check_log.set_value(logging);
check_log.on_select = [this](Checkbox&, bool v) {
logging = v;
};
check_crc.set_value(use_crc);
check_crc.on_select = [this](Checkbox&, bool v) {
use_crc = v;
};
@@ -86,24 +86,32 @@ SondeView::SondeView(NavigationView& nav)
// QR code with geo URI
button_see_qr.on_select = [this, &nav](Button&) {
nav.push<QRCodeView>(geo_uri);
std::string geo_uri = "geo:" + to_string_decimal(geopos.lat(), 5) + "," + to_string_decimal(geopos.lon(), 5); // 5 decimal digits for ~1 meter accuracy
nav.push<QRCodeView>(geo_uri.data());
};
button_see_map.on_select = [this, &nav](Button&) {
nav.push<GeoMapView>(
geomap_view_ = nav.push<GeoMapView>(
sonde_id,
gps_info.alt,
geopos.altitude(),
GeoPos::alt_unit::METERS,
gps_info.lat,
gps_info.lon,
GeoPos::spd_unit::HIDDEN,
geopos.lat(),
geopos.lon(),
999); // set a dummy heading out of range to draw a cross...probably not ideal?
nav.set_on_pop([this]() {
geomap_view_ = nullptr;
});
};
logger = std::make_unique<SondeLogger>();
if (logger)
logger->append(LOG_ROOT_DIR "/SONDE.TXT");
logger->append(logs_dir / u"SONDE.TXT");
audio::output::start();
if (pmem::beep_on_packets()) {
audio::set_rate(audio::Rate::Hz_24000);
audio::output::start();
}
// inject a PitchRSSIConfigureMessage in order to arm
// the pitch rssi events that will be used by the
@@ -123,55 +131,24 @@ SondeView::~SondeView() {
audio::output::stop();
}
void SondeView::focus() {
field_frequency.focus();
void SondeView::on_gps(const GPSPosDataMessage* msg) {
if (!geomap_view_)
return;
geomap_view_->update_my_position(msg->lat, msg->lon, msg->altitude);
}
void SondeView::on_orientation(const OrientationDataMessage* msg) {
if (!geomap_view_)
return;
geomap_view_->update_my_orientation(msg->angle, true);
}
// used to convert float to character pointer, since unfortunately function like
// sprintf and c_str aren't supported.
char* SondeView::float_to_char(float x, char* p) {
char* s = p + 9; // go to end of buffer
uint16_t decimals; // variable to store the decimals
int units; // variable to store the units (part to left of decimal place)
if (x < 0) { // take care of negative numbers
decimals = (int)(x * -100000) % 100000; // make 1000 for 3 decimals etc.
units = (int)(-1 * x);
} else { // positive numbers
decimals = (int)(x * 100000) % 100000;
units = (int)x;
}
// TODO: more elegant solution (loop?)
*--s = (decimals % 10) + '0';
decimals /= 10;
*--s = (decimals % 10) + '0';
decimals /= 10;
*--s = (decimals % 10) + '0';
decimals /= 10;
*--s = (decimals % 10) + '0';
decimals /= 10;
*--s = (decimals % 10) + '0';
*--s = '.';
while (units > 0) {
*--s = (units % 10) + '0';
units /= 10;
}
if (x < 0) *--s = '-'; // unary minus sign for negative numbers
return s;
void SondeView::focus() {
field_frequency.focus();
}
void SondeView::on_packet(const sonde::Packet& packet) {
if (!use_crc || packet.crc_ok()) // euquiq: Reject bad packet if crc is on
{
char buffer_lat[10] = {};
char buffer_lon[10] = {};
strcpy(geo_uri, "geo:");
strcat(geo_uri, float_to_char(gps_info.lat, buffer_lat));
strcat(geo_uri, ",");
strcat(geo_uri, float_to_char(gps_info.lon, buffer_lon));
text_signature.set(packet.type_string());
sonde_id = packet.serial_number(); // used also as tag on the geomap
@@ -204,8 +181,8 @@ void SondeView::on_packet(const sonde::Packet& packet) {
logger->on_packet(packet);
}
if (beep) {
baseband::request_beep();
if (pmem::beep_on_packets()) {
baseband::request_rssi_beep();
}
}
}
+30 -13
View File
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2017 Furrtek
* Copyright (C) 2024 Mark Thompson
*
* This file is part of PortaPack.
*
@@ -60,6 +61,8 @@ class SondeView : public View {
SondeView(NavigationView& nav);
~SondeView();
SondeView(const SondeView& other) = delete;
SondeView& operator=(const SondeView& other) = delete;
void focus() override;
@@ -72,15 +75,17 @@ class SondeView : public View {
1750000 /* bandwidth */,
2457600 /* sampling rate */
};
app_settings::SettingsManager settings_{
"rx_sonde", app_settings::Mode::RX};
std::unique_ptr<SondeLogger> logger{};
bool logging{false};
bool use_crc{false};
bool beep{false};
app_settings::SettingsManager settings_{
"rx_sonde",
app_settings::Mode::RX,
{
{"logging"sv, &logging},
{"use_crc"sv, &use_crc},
}};
char geo_uri[32] = {};
std::unique_ptr<SondeLogger> logger{};
sonde::GPS_data gps_info{};
sonde::temp_humid temp_humid_info{};
@@ -117,11 +122,6 @@ class SondeView : public View {
AudioVolumeField field_volume{
{28 * 8, 0 * 16}};
Checkbox check_beep{
{22 * 8, 6 * 16},
3,
"Beep"};
Checkbox check_log{
{22 * 8, 8 * 16},
3,
@@ -162,7 +162,8 @@ class SondeView : public View {
GeoPos geopos{
{0, 12 * 16},
GeoPos::alt_unit::METERS};
GeoPos::alt_unit::METERS,
GeoPos::spd_unit::HIDDEN};
Button button_see_qr{
{2 * 8, 15 * 16, 12 * 8, 3 * 16},
@@ -172,6 +173,8 @@ class SondeView : public View {
{16 * 8, 15 * 16, 12 * 8, 3 * 16},
"See on map"};
GeoMapView* geomap_view_{nullptr};
MessageHandlerRegistration message_handler_packet{
Message::ID::SondePacket,
[this](Message* const p) {
@@ -180,8 +183,22 @@ class SondeView : public View {
this->on_packet(packet);
}};
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);
}};
void on_gps(const GPSPosDataMessage* msg);
void on_orientation(const OrientationDataMessage* msg);
void on_packet(const sonde::Packet& packet);
char* float_to_char(float x, char* p);
};
} /* namespace ui */
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2023 Kyle Reed
* Copyright (C) 2023 Mark Thompson
*
* This file is part of PortaPack.
*
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2023 Kyle Reed
* Copyright (C) 2023 Mark Thompson
*
* This file is part of PortaPack.
*
+242
View File
@@ -0,0 +1,242 @@
/*
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2017 Furrtek
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include "ui_subghzd.hpp"
#include "audio.hpp"
#include "baseband_api.hpp"
#include "string_format.hpp"
#include "portapack_persistent_memory.hpp"
using namespace portapack;
using namespace ui;
namespace ui {
void SubGhzDRecentEntryDetailView::update_data() {
// set text elements
text_type.set(SubGhzDView::getSensorTypeName((FPROTO_SUBGHZD_SENSOR)entry_.sensorType));
text_id.set("0x" + to_string_hex(entry_.serial));
if (entry_.bits > 0) console.writeln("Bits: " + to_string_dec_uint(entry_.bits));
if (entry_.btn != SD_NO_BTN) console.writeln("Btn: " + to_string_dec_uint(entry_.btn));
if (entry_.cnt != SD_NO_CNT) console.writeln("Cnt: " + to_string_dec_uint(entry_.cnt));
if (entry_.data != 0) console.writeln("Data: " + to_string_hex(entry_.data));
}
SubGhzDRecentEntryDetailView::SubGhzDRecentEntryDetailView(NavigationView& nav, const SubGhzDRecentEntry& entry)
: nav_{nav},
entry_{entry} {
add_children({&button_done,
&text_type,
&text_id,
&console,
&labels});
button_done.on_select = [&nav](const ui::Button&) {
nav.pop();
};
update_data();
}
void SubGhzDRecentEntryDetailView::focus() {
button_done.focus();
}
void SubGhzDView::focus() {
field_frequency.focus();
}
SubGhzDView::SubGhzDView(NavigationView& nav)
: nav_{nav} {
add_children({&rssi,
&field_rf_amp,
&field_lna,
&field_vga,
&field_frequency,
&button_clear_list,
&recent_entries_view});
baseband::run_image(portapack::spi_flash::image_tag_subghzd);
button_clear_list.on_select = [this](Button&) {
recent.clear();
recent_entries_view.set_dirty();
};
field_frequency.set_step(100000);
const Rect content_rect{0, header_height, screen_width, screen_height - header_height};
recent_entries_view.set_parent_rect(content_rect);
recent_entries_view.on_select = [this](const SubGhzDRecentEntry& entry) {
nav_.push<SubGhzDRecentEntryDetailView>(entry);
};
baseband::set_subghzd_config(0, receiver_model.sampling_rate()); // 0=am
receiver_model.enable();
signal_token_tick_second = rtc_time::signal_tick_second += [this]() {
on_tick_second();
};
}
void SubGhzDView::on_tick_second() {
for (auto& entry : recent) {
entry.inc_age(1);
}
recent_entries_view.set_dirty();
}
void SubGhzDView::on_data(const SubGhzDDataMessage* data) {
SubGhzDRecentEntry key{data->sensorType, data->serial, data->bits, data->data, data->btn, data->cnt};
auto matching_recent = find(recent, key.key());
if (matching_recent != std::end(recent)) {
// Found within. Move to front of list, increment counter.
(*matching_recent).reset_age();
recent.push_front(*matching_recent);
recent.erase(matching_recent);
} else {
recent.emplace_front(key);
truncate_entries(recent, 64);
}
recent_entries_view.set_dirty();
}
SubGhzDView::~SubGhzDView() {
rtc_time::signal_tick_second -= signal_token_tick_second;
receiver_model.disable();
baseband::shutdown();
}
const char* SubGhzDView::getSensorTypeName(FPROTO_SUBGHZD_SENSOR type) {
switch (type) {
case FPS_PRINCETON:
return "Princeton";
case FPS_BETT:
return "Bett";
case FPS_CAME:
return "Came";
case FPS_PRASTEL:
return "Prastel";
case FPS_AIRFORCE:
return "Airforce";
case FPS_CAMEATOMO:
return "Came Atomo";
case FPS_CAMETWEE:
return "Came Twee";
case FPS_CHAMBCODE:
return "Chamb Code";
case FPS_CLEMSA:
return "Clemsa";
case FPS_DOITRAND:
return "Doitrand";
case FPS_DOOYA:
return "Dooya";
case FPS_FAAC:
return "Faac";
case FPS_GATETX:
return "Gate TX";
case FPS_HOLTEK:
return "Holtek";
case FPS_HOLTEKHT12X:
return "Holtek HT12X";
case FPS_HONEYWELL:
return "Honeywell";
case FPS_HONEYWELLWDB:
return "Honeywell Wdb";
case FPS_HORMANN:
return "Hormann";
case FPS_IDO:
return "Ido 11x";
case FPS_INTERTECHNOV3:
return "InterTehcno v3";
case FPS_KEELOQ:
return "KeeLoq";
case FPS_KINGGATESSTYLO4K:
return "Kinggate Stylo4K";
case FPS_LINEAR:
return "Linear";
case FPS_LINEARDELTA3:
return "Linear Delta3";
case FPS_MAGELLAN:
return "Magellan";
case FPS_MARANTEC:
return "Marantec";
case FPS_MASTERCODE:
return "Mastercode";
case FPS_MEGACODE:
return "Megacode";
case FPS_NERORADIO:
return "Nero Radio";
case FPS_NERO_SKETCH:
return "Nero Sketch";
case FPS_NICEFLO:
return "Nice Flo";
case FPS_NICEFLORS:
return "Nice Flor S";
case FPS_PHOENIXV2:
return "Phoenix V2";
case FPS_POWERSMART:
return "PowerSmart";
case FPS_SECPLUSV1:
return "SecPlus V1";
case FPS_SECPLUSV2:
return "SecPlus V2";
case FPS_SMC5326:
return "SMC5326";
case FPS_STARLINE:
return "Star Line";
case FPS_X10:
return "X10";
case FPS_Invalid:
default:
return "Unknown";
}
}
std::string SubGhzDView::pad_string_with_spaces(int snakes) {
std::string paddedStr(snakes, ' ');
return paddedStr;
}
template <>
void RecentEntriesTable<ui::SubGhzDRecentEntries>::draw(
const Entry& entry,
const Rect& target_rect,
Painter& painter,
const Style& style) {
std::string line{};
line.reserve(30);
line = SubGhzDView::getSensorTypeName((FPROTO_SUBGHZD_SENSOR)entry.sensorType);
line = line + " " + to_string_hex(entry.serial);
if (line.length() < 19) {
line += SubGhzDView::pad_string_with_spaces(19 - line.length());
} else {
line = truncate(line, 19);
}
std::string ageStr = to_string_dec_uint(entry.age);
std::string bitsStr = to_string_dec_uint(entry.bits);
line += SubGhzDView::pad_string_with_spaces(5 - bitsStr.length()) + bitsStr;
line += SubGhzDView::pad_string_with_spaces(4 - ageStr.length()) + ageStr;
line.resize(target_rect.width() / 8, ' ');
painter.draw_string(target_rect.location(), style, line);
}
} // namespace ui
+171
View File
@@ -0,0 +1,171 @@
/*
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2017 Furrtek
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#ifndef __UI_SUBGHZD_H__
#define __UI_SUBGHZD_H__
#include "ui.hpp"
#include "ui_navigation.hpp"
#include "ui_receiver.hpp"
#include "ui_freq_field.hpp"
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "utility.hpp"
#include "recent_entries.hpp"
#include "../baseband/fprotos/subghztypes.hpp"
using namespace ui;
namespace ui {
struct SubGhzDRecentEntry {
using Key = uint64_t;
static constexpr Key invalid_key = 0x0fffffff;
uint8_t sensorType = FPS_Invalid;
uint8_t btn = SD_NO_BTN;
uint32_t serial = SD_NO_SERIAL;
uint16_t bits = 0;
uint16_t age = 0; // updated on each seconds, show how long the signal was last seen
uint32_t cnt = SD_NO_CNT;
uint64_t data = 0;
SubGhzDRecentEntry() {}
SubGhzDRecentEntry(
uint8_t sensorType,
uint32_t serial,
uint16_t bits = 0,
uint64_t data = 0,
uint8_t btn = SD_NO_BTN,
uint32_t cnt = SD_NO_CNT)
: sensorType{sensorType},
btn{btn},
serial{serial},
bits{bits},
cnt{cnt},
data{data} {
}
Key key() const {
return (data ^ ((static_cast<uint64_t>(serial) << 32) | (static_cast<uint64_t>(sensorType) & 0xFF) << 0));
}
void inc_age(int delta) {
if (UINT16_MAX - delta > age) age += delta;
}
void reset_age() {
age = 0;
}
};
using SubGhzDRecentEntries = RecentEntries<SubGhzDRecentEntry>;
using SubGhzDRecentEntriesView = RecentEntriesView<SubGhzDRecentEntries>;
class SubGhzDView : public View {
public:
SubGhzDView(NavigationView& nav);
~SubGhzDView();
void focus() override;
std::string title() const override { return "SubGhzD"; };
static const char* getSensorTypeName(FPROTO_SUBGHZD_SENSOR type);
static std::string pad_string_with_spaces(int snakes);
private:
void on_tick_second();
void on_data(const SubGhzDDataMessage* data);
NavigationView& nav_;
RxRadioState radio_state_{
433'920'000 /* frequency */,
1'750'000 /* bandwidth */,
4'000'000 /* sampling rate */,
ReceiverModel::Mode::AMAudio};
app_settings::SettingsManager settings_{
"rx_subghzd",
app_settings::Mode::RX,
{}};
SubGhzDRecentEntries recent{};
RFAmpField field_rf_amp{
{13 * 8, 0 * 16}};
LNAGainField field_lna{
{15 * 8, 0 * 16}};
VGAGainField field_vga{
{18 * 8, 0 * 16}};
RSSI rssi{
{21 * 8, 0, 6 * 8, 4}};
RxFrequencyField field_frequency{
{0 * 8, 0 * 16},
nav_};
SignalToken signal_token_tick_second{};
Button button_clear_list{
{0, 16, 7 * 8, 32},
"Clear"};
static constexpr auto header_height = 3 * 16;
const RecentEntriesColumns columns{{
{"Type", 19},
{"Bits", 4},
{"Age", 3},
}};
SubGhzDRecentEntriesView recent_entries_view{columns, recent};
MessageHandlerRegistration message_handler_packet{
Message::ID::SubGhzDData,
[this](Message* const p) {
const auto message = static_cast<const SubGhzDDataMessage*>(p);
this->on_data(message);
}};
};
class SubGhzDRecentEntryDetailView : public View {
public:
SubGhzDRecentEntryDetailView(NavigationView& nav, const SubGhzDRecentEntry& entry);
void update_data();
void focus() override;
private:
NavigationView& nav_;
SubGhzDRecentEntry entry_{};
Text text_type{{0 * 8, 1 * 16, 15 * 8, 16}, "?"};
Text text_id{{6 * 8, 2 * 16, 10 * 8, 16}, "?"};
Console console{
{0, 4 * 16, 240, screen_height - (4 * 16) - 36}};
Labels labels{
{{0 * 8, 0 * 16}, "Type:", Color::light_grey()},
{{0 * 8, 2 * 16}, "Serial: ", Color::light_grey()},
{{0 * 8, 3 * 16}, "Data:", Color::light_grey()},
};
Button button_done{
{screen_width - 96 - 4, screen_height - 32 - 12, 96, 32},
"Done"};
};
} // namespace ui
#endif /*__UI_SUBGHZD_H__*/
+1 -1
View File
@@ -38,7 +38,7 @@
class TestLogger {
public:
Optional<File::Error> append(const std::string& filename) {
Optional<File::Error> append(const std::filesystem::path& filename) {
return log_file.append(filename);
}

Some files were not shown because too many files have changed in this diff Show More