Compare commits

...

139 Commits

Author SHA1 Message Date
jLynx e2ad0a1b1a Stable 1.7.4 release (#1340)
* Update version.txt

* Update past_version.txt
2023-08-02 10:24:23 +12:00
Mark Thompson 96cdb2e9e0 Keyboard tweaks (#1338)
* Disallow 1-time shift in digits/symbols mode

* Slightly wider "123" button and smaller OK button

* Slightly wider "123" button and smaller OK button
2023-08-01 14:25:26 -05:00
Mark Thompson 002ef720ee Fixed waterfall frequency scale in Replay app (#1337)
* Fix waterfall frequency scale in Replay

* Fix waterfall frequency scale in Replay
2023-08-01 20:20:19 +02:00
Kyle Reed 2214533894 Keyboard Shift Mode (#1333)
* Add "shift" concept to keyboard

* Better shift colors

* Better keybaord layout for symbols

* Start ShiftLocked for consistency with previous UX.

* Fix number layout

* PR and test-drive feedback
2023-07-31 21:44:05 -07:00
Mark Thompson 06b7a0419e Fixed Select button responsiveness when updating frequency field during heavy CPU activity (e.g. WFM Audio) (#1335)
* Resolve button responsiveness

* Resolve button responsiveness

* Clang

* Clang

* Clang try again

* Removed unnecessary lines

* Address review comments

* Add comments per reviewer suggestion

* Clang test

* Clang retry
2023-07-31 23:27:15 -05:00
Kyle Reed d24ff7b3bc Oversample capturing for low bandwidths (#1332)
* draft_low_bit_rate_solution_Capture_App

* second_draft_dynamic_decim

* Add support for Oversample Rate to capture.

---------

Co-authored-by: Brumi-2021 <ea3hqj@gmail.com>
2023-07-31 17:46:07 +02:00
Mark Thompson a24b3ad3de Correct estimated capture time in C8 format (#1330)
* Correct capture time remaining for C8 format

* Removed unsupported RawS32 type

* Clang
2023-07-31 08:02:11 -07:00
Mark Thompson 91c6e3fc30 Display error message when trying to delete non-empty directory (#1321)
* Non-empty directory check

* Non-empty directory check

* Display error when attempting to delete non-empty directory

* Clang

Delete white-space that the friggin editor added

* is_empty_directory

* is_empty_directory

* is_empty_directory

* Now need to check if it's a directory first
2023-07-30 15:46:59 -05:00
Kyle Reed 411f6c0a34 Progress bar for Notepad IO (#1322) 2023-07-30 09:36:57 +02:00
Kyle Reed 0a3aa706ef Scanner cleanup (#1320)
* FreqmanDB in Scanner

* Format

* Add comment on squelch(0)
2023-07-29 15:36:00 -07:00
Kyle Reed 5ca74db2f9 Fix output file config in recon (#1319) 2023-07-29 07:16:49 +02:00
Kyle Reed f24523c2f1 Add button to prompt rename with a timestamp. (#1315) 2023-07-28 18:53:16 -07:00
Mark Thompson e7c5a862da Revert encoder sensitivity change (#1314) 2023-07-27 16:45:39 +02:00
Mark Thompson b27c738b69 XOR cursor support in Notepad (#1311)
* XOR cursor support in Notepad

* XOR cursor support

* XOR cursor support

* Revert change

* Use static buffer

* Use static buffer

* Clang
2023-07-27 09:14:02 -05:00
jLynx 8c565bbf15 Allow changelog to run without a token (#1312)
* Allow code to run without a token

* Reverted submodule change by mistake
2023-07-27 14:53:56 +12:00
Mark Thompson 37aa9c046f Gps default frequency (#1309)
* Default frequency

* Default frequency

* Use sample rate from .ini file if present

* Eliminate unneeded sampling_rate variable

* Eliminate unneeded sampling_rate variable
2023-07-26 13:52:27 -05:00
Kyle Reed 195a6224a0 Freq field tweaks (#1306)
* UX improvements

* Prevent wrapping
2023-07-25 08:06:13 -05:00
Mark Thompson ea238f4988 Improve Select button response when Long Press is enabled during CPU-intensive apps (WFM) (#1304)
* Long press

* Long-press improvement during CPU-intensive apps

* Long-press improvement during CPU-intensive apps
2023-07-24 20:45:24 -07:00
Kyle Reed 3514a9a608 Digit Mode for frequency field (#1298)
* Remove 'auto' step mode

* Support per-digit edits on the freq field.

* Swizzle instead of raw accessor

* Fix debug ui after swizzle
2023-07-24 18:09:22 +02:00
Mark Thompson e2bca9aebb Restore missing line (#1302) 2023-07-24 10:12:35 -05:00
Mark Thompson 6ae164e59b Show scale on map (#1296)
* Add scale to geomap

* Add scale to geomap
2023-07-24 14:52:30 +02:00
Mark Thompson e6ad5efbb7 Touch Test tweak & Scanner default squelch (#1295)
* Leave nav bar visible at start & change seed
* Change default Scanner squelch
2023-07-23 21:06:19 +02:00
Kyle Reed 7bd370b5bc Fix baseband thread init order bug for all procs. (#1293) 2023-07-23 08:54:17 +02:00
Mark Thompson 828eb67a52 Remove experimental support for Replaying C32 files (#1294) 2023-07-23 08:52:23 +02:00
Mark Thompson c4df2e66be A Simple Touchscreen Test App (Debug) (#1292)
* Touchscreen test

* Touchscreen test

* Touchscreen test

* Clang

* Moved some Debug menu icons
2023-07-22 16:49:28 -05:00
Kyle Reed 47e95c0c47 Workaround for Capture startup hang (#1285)
* Attempt to fix Capture startup hang

* Pump baseband_queue on M4 startup

* Synchronization experiment

* Moved SpectrumCapture member, better hang detection for M0

* Prevent execute from working on members until class has been initialized.

* Formatting

* Remove workaround.

* Rebase on next
2023-07-22 10:06:55 -07:00
gullradriel 3b5890d0aa fix timer and sd settings issue (#1291) 2023-07-22 16:30:02 +02:00
Mark Thompson bee2dc17af Fixes to C8 capture support (#1290)
* Use divide vs shift in C16->C8 and C32->C16 conversion
* Color cyan for .C32 files
2023-07-22 16:12:35 +02:00
Mark Thompson d6b0173e7a C8 capture support (#1286)
* C8 conversion

* C8 conversion

* C8 support

* C8 support

* C8 support

* C8 support

* Don't auto-convert GPS C8 files

* C8 support

* C8 support

* C8 support

* Remove hang workaround (different PR)

* Comment change

* Clang

* Clang

* Clang

* Merged change from PR #1287

* C8 support

* C8 support

* Improve bandwidth display

* Merged minor optimization from PR 1289

* Merge change from PR 1289

* Use complex types for C8/C16 conversion

* C8 support

* C8 support

* C8 support

* C8 support

* Roll back changes

* Roll back C8 changes

* C8 support

* C8 support

* C8 support

* C8 support

* C8 support

* Don't transmit samples past EOF

* Don't transmit samples past EOF

* Clang

* Clang attempt

* Clang attempt

* C8 support

* Clang
2023-07-22 02:20:56 -05:00
Mark Thompson 8eafe27955 Minor Replay & GPS baseband optimizations (#1289)
* No need to scale progress now that widget is fixed
* Use memcpy vs byte copy, elim progress scaling, correct comments
* Minor optimization in C16->C8 conversion
* Only copy bytes actually read from file
2023-07-21 18:50:00 +02:00
Mark Thompson 2af95456a2 Fix ProgressBar widget overflow (#1288) 2023-07-21 00:06:37 -05:00
Mark Thompson 8ce48dbcf6 Interim Capture hang workaround (#1287)
* Temporary hang workaround

* Clang found a trailing space in a comment :-)
2023-07-20 23:04:54 +02:00
gullradriel 92072b4225 removed redundant call (#1282)
Co-authored-by: GullCode <gullradriel@hotmail.com>
2023-07-17 15:31:36 -05:00
gullradriel 4e823dd3a3 Recon adjustements (#1280)
* Fix offset by 1 error in button_remove, added back load_freqfile(true) in restart
* removed badly duplicated load freqman file
2023-07-17 22:19:39 +02:00
gullradriel 53d9cecdaf Encoder default setting (#1281)
* fix default encoder value to normal
* use enum name instead of hardcoded value
2023-07-17 22:16:40 +02:00
Mark Thompson 680f1f49ff Resolve compile error (#1279) 2023-07-17 21:31:25 +02:00
Kyle Reed 6574272ca8 Freqman improvements (#1276)
* Show .1 MHz in pretty freqman string

* Refactor load to user FreqmanDB

* Recon file parsing cleanup

* use strtol for parse_int

* recon file cleanup

* Fix bugs in Recon changes

* PR feedback

---------

Co-authored-by: kallanreed <kallanreed@noreply.github.com>
2023-07-17 11:43:37 -07:00
Brumi-2021 f5c4aa2be2 Correct_RXBW_char_7_spaces_in_Mic_App (#1278)
* Correct_RXBW_char_7_spaces_in_Mic_App

* format_issues_Mic_app

* using_freqman_functions_and_solving_other_defaults
2023-07-17 09:29:24 -05:00
Mark Thompson 2498861003 Increased number of encoder dial sensitivity setting levels (#1275)
* Encoder dial sensitivity 0-4

* Workaround for worn encoders & more sensitivity levels

* Workaround for worn encoders & more sensitivity levels

* Encoder sensitivity levels 0-4

* Encoder sensitivity levels 0-4

* Fix comment

* Clang

* Clang

* Change default encoder sensitivity in pmem

* Set default encoder sensitivity

* Set default encoder sensitivity

* Revert widget type for encoder sensitivity

* Revert widget type for encoder dial sensitivity

* Revert worn encoder workaround (didn't work)

* 5 levels of encoder dial sensitivity

* 5 levels of encoder dial sensitivity
2023-07-16 17:05:27 -05:00
Kyle Reed 25923e82a4 New Freqman edit UI (#1272)
* WIP new edit UI

* Fix textfield highlight

* WIP new edit

* Wrap up first pass of freqman edit

* Fix indexing of options

---------

Co-authored-by: kallanreed <kallanreed@noreply.github.com>
2023-07-14 18:46:39 -07:00
Mark Thompson 61cb57e48d Added to_string_rounded_freq() function (#1271)
* Added to_string_rounded_freq function

* Added to_string_rounded_freq function

* Clang

* Clang try #2

* Use constexpr for pow10 array

* Additional string_format test cases

* Specify std=c++17 for g++ versions that default to an older standard

* Corrected string_format test cases
2023-07-14 13:38:02 -05:00
Kyle Reed 4985d836ea Support save prompt on back arrow (#1270)
* Support save prompt on back arrow
* Add lifetime warning comment
2023-07-13 18:38:40 +02:00
eried e1a79a0ff1 Fonts for pcb 2023-07-13 15:21:05 +02:00
Kyle Reed d72d935607 Support showing invalid entries in Freqman and allow minor edits (#1269)
* Support showing invalid entries in Freqman and allow minor edits
* Use light_grey instead of grey
* Fix comment
* Fix null in description bug
* Fix spacing in delete entry dialog
* trim in delete modal
2023-07-13 08:28:27 +02:00
Mark Thompson 4ed06b9eff Correct HamRadio frequency validation (#1266) 2023-07-12 20:27:02 +02:00
Mark Thompson fa4623db7c Print leading zeroes after decimal point (#1265) 2023-07-12 13:00:45 -05:00
Mark Thompson a44e8b9921 Use unicode standard 0xA0-0xFF for Latin 1 characters (#1263)
* Update chars 0xA0-0xFF to match unicode

* Update ui_text.hpp

Update chars 0xA0-0xFF to match unicode

* Use unicode 0xB0 for degree character

* Clang
2023-07-12 07:14:00 -05:00
jLynx 6b4f1a552e Changelog refactoring (#1264)
* Delete stable_changelog.py

* Update changelog.py

* Update create_stable_release.yml
2023-07-12 18:50:39 +12:00
pathetic nihilist 3d32682a67 time counting fix (#1261) 2023-07-12 13:10:52 +12:00
Kyle Reed 29e495a17f Freqman UI (#1255)
* FreqmanDB direct file
* Clear UI for short lists
* Final touches on freqlist UI.
* Support vertical alignment in NewButton
* New buttons in FreqMan
* Wiring up UI to filewrapper actions
* Work around empty file
2023-07-11 22:48:36 +02:00
Tragic nihilist 0c599f7d3a Changelog fix 3rd way (#1259)
* init_commit_for_changelog_fix_2nd_way

* 2nd_commit_for_changelog_fix_2nd_way

* methods-3

* add_more_exception
2023-07-11 09:51:22 +02:00
jLynx 006e4c9fce Smaller SD card ZIP (#1257)
* Update create_nightly_release.yml

* Update create_stable_release.yml
2023-07-10 19:43:39 +12:00
Kyle Reed 63be4de418 Add some basic validation to freqman parsing (#1256)
Co-authored-by: kallanreed <kallanreed@noreply.github.com>
2023-07-09 11:15:14 -07:00
Kyle Reed 497ca3f934 Refactor freqman_db parsing (#1244)
* WIP freqman changes/memory perf/stash
* Split ui tone_key function out for testing.
* Add more tests and fix bugs.
* Use default max_entries in recond
* Set limit back to 90 for now
2023-07-08 22:04:12 +02:00
Mark Thompson 60de625c37 Added fonts viewer (debug) app (#1251)
* Added fonts viewer (debug) app

* Added fonts viewer (debug) app

* Clang

* Use degree symbol vs asterisk for latitude/longitude degrees

* Save a few bytes by not overriding on_key

* Save a few bytes by not overriding on_key

* Clang
2023-07-08 10:26:05 -05:00
gullradriel a09c0e4c2d Recon and Level fix (#1250)
* Level: Fix for SPEC to other modes
* Recon: Better difference between RAW and AUDIO. Launch Capture instead of audio in SPEC mode
2023-07-07 22:21:48 +02:00
Mark Thompson a60f4ce9a8 Add SPLASH folder to SD Card (#1247)
* Add SPLASH folder and README file

* Sample splash screen files
2023-07-06 23:02:58 +02:00
Mark Thompson fcb681f4ae Support for viewing BMP files in File Manager and setting as Splash screen (#1242)
* Use filesystem::path type vs string for BMP file name

* Use filesystem::path type vs string for BMP files

* Use a global const file path string for "/splash.bmp"

* Support for viewing BMP files and setting as Splash screen

* Support for viewing BMP files and setting as Splash screen

* Support for viewing BMP files and setting as Splash screen

* Update ui_ss_viewer.cpp

* Update ui_ss_viewer.hpp
2023-07-05 15:45:43 -05:00
gullradriel 8530fa8194 Recon unleashed SPEC mode (#1240)
* added quick recon optimized for spec mode
* fix bug going from spec to audio mode
2023-07-05 21:54:28 +02:00
Erwin Ried 89e8956df1 Merge pull request #1239 from gullradriel/recon-gui-fix
fix gui glitches
2023-07-05 12:09:04 +02:00
GullCode 82ba35c162 fix gui glitches 2023-07-05 11:08:29 +02:00
Mark Thompson d750afe38f Use ImageToggle widget for 3 icons (no functional change) (#1237)
* Use ImageToggle button type for 3 icons
2023-07-05 09:12:40 +02:00
Kyle Reed 9b665a43c5 Radio state initialization (#1236)
* WIP RadioState init

* TX/RX cleanup

* Update all apps using RadioState and setting modulation mode

* Set apps to use AM mode

* Don't push modulation update in RadioState.

* Support passing overrides to Audio and MicTX

* Support set_nearest on OptionsField, fix recon step

* Fix audio, typo

---------

Co-authored-by: kallanreed <kylereed@manzana.lan>
Co-authored-by: kallanreed <kallanreed@noreply.github.com>
2023-07-04 18:26:26 -05:00
Mark Thompson 80c769b97d Display CTCSS tone freq in Audio, Recon, and Level apps (#1231)
* Generate CTCSS messages at fixed rate regardless of tone freq

* Generate CTCSS messages at fixed rate regardless of tone freq

* Function for generating CTCSS description strings

* Function for generating CTCSS description strings

* Increase width of CTCSS text to include tone freq

* Increase width of CTCSS text field to include frequency

* Use CTCSS tone freq when saving HAM freqs to freq file

* Use function in tone_key.cpp for displaying CTCSS string

* Use function in tone_key.cpp for CTCSS descr strings

* Use function in tone_key.cpp for CTCSS descr strings

* Clang test

* Clang

* Clang

* Support for reading CTCSS tones from FreqMan file

* Clang

* Clean up and eliminate floating point

* Clean up and eliminate floating point

* Corrected CTCSS field length

* Corrected CTCSS field length

* Clang
2023-07-02 16:53:51 -07:00
Kyle Reed 44dd8fd083 Pmem cleanup (#1230)
* WIP pmem cleanup

* Finish pmem refactor

* Fix dump order

* Set defaults for splash and backlight

* Fix break

---------

Co-authored-by: kallanreed <kallanreed@noreply.github.com>
2023-07-02 09:35:34 -07:00
Mark Thompson 4e128e8930 Fixed CTCSS tone detection (#1226)
* Fixed erroneous rounding code

* Fixed CTCSS tone detection

* Correct tone index for XZ/WZ and sort table by tone freq
2023-07-01 15:36:07 +02:00
jLynx c2279e297c Stable v1.7.3 (#1224)
* Update past_version.txt

* Update version.txt
2023-06-30 20:45:20 +12:00
Mark Thompson cdd524b9f3 Use a common function for finding Tone Key index from received Tone Frequency (#1218)
* Common function for finding CTCSS tone index from freq
2023-06-30 07:37:43 +02:00
Kyle Reed 99809c7919 Consolidate anti_alias BW selection into function (#1217) 2023-06-30 07:35:35 +02:00
Kyle Reed 2390d79111 Fix up Waterfall control names (#1219) 2023-06-30 07:34:19 +02:00
Kyle Reed 3b41d73ddd Remove LTO, minor UI tweaks (#1216) 2023-06-30 07:33:33 +02:00
gullradriel 3d46c28911 fix build (#1215)
* fix build

* fixing build integrationg zoom

---------

Co-authored-by: GullCode <gullradriel@hotmail.com>
2023-06-29 13:58:10 -07:00
Kyle Reed ca7840557b Change how scrolling works, add home/end (#1212)
Conflicts fixed by gull
2023-06-29 22:17:31 +02:00
Mark Thompson ceaa115025 Revert color of "SD over USB" icon back to yellow (#1210) 2023-06-29 22:13:33 +02:00
Mark Thompson 8dbcefbc75 Move initializer from .hpp -> .cpp file (#1213) 2023-06-29 22:07:39 +02:00
Mark Thompson b28b96fb4a Notepad text zoom support 5x8 to 8x16 font (#1211)
* Zoom button 5/8 to 8/16 font

* Zoom button 5/8 to 8/16 font

* Clang & revert unintended change

* Suggested improvements

* Suggested code changes

* Added char_width function

* Added char_width function

* Oops copy-paste error

* Delete added blank line
2023-06-29 10:55:25 -07:00
Stupid retard noob attention king e15a8ed2d8 improve keyboard (#1209)
* init commit for raw input in keyboard

* clean up

* clean up - 1

* clean up - 2

* can input underline and < now

* format fix

* textual change

* textual change - 2

* textual change - 3

* textual change - 4

* edit for PR comment

* edit for PR comment - 2

* edit for PR comment - 3

* edit for PR comment - 4

* edit for PR comment - 5
2023-06-29 09:24:18 -07:00
gullradriel 19b77bf03b fix recon start and mode change (#1208) 2023-06-28 22:21:13 +02:00
Kyle Reed 7b1541d2e8 work around calling focus from ctor path. (#1207) 2023-06-28 21:50:11 +02:00
gullradriel 830fea63ed Optimize compilation flags (#1206)
* restoring -O3 on baseband, activating lto on cpp parts
2023-06-28 21:46:49 +02:00
Kyle Reed 3d59de55f8 Remove raw new/delete from Recon (#1203) 2023-06-28 19:14:30 +02:00
Mark Thompson 5c1f5a7d2e Second DFU Overlay Screen with Receiver Info (#1204)
* 2nd DFU overlay with receiver info for debug
2023-06-28 19:04:16 +02:00
Kyle Reed 4e985420d4 Replace Replay with Playlist (#1202)
* Replace Replay with Playlist
* Suggestions from test-drive. Launch from Fileman
2023-06-28 19:02:06 +02:00
Mark Thompson 3e584a9652 Proposed debug dump formatting tweaks (#1200)
* Debug dump formatting 
* Added FW version to debug dump
2023-06-28 19:01:09 +02:00
Mark Thompson 39acaf846a Revised title bar truncation code & removed title widget shading (#1201)
* Revised title truncate & undo widget shading

* Revised title truncation & undo widget shading
2023-06-27 19:34:25 -07:00
Mark Thompson 76948cffa8 Improve visibility of green icons on white background (#1196) 2023-06-27 10:19:34 +02:00
Mark Thompson 4ea8abb53b Limit title length when too many status icons & shade background of every other icon (#1195)
* Shading alternate icon backgrounds on title bar

* Shading alternate icons on title bar

* Shading alternate icons on title bar

* Clang
2023-06-27 10:15:58 +02:00
gullradriel 32085f317b adding one more character to modulation widget for spec (#1193)
Co-authored-by: GullCode <gullradriel@hotmail.com>
2023-06-27 10:02:04 +02:00
Kyle Reed faa4367295 Allow frequency override in Playlist, Folder fixes (#1194)
* Allow frequency to be changed per-entry

* Folder fixes, use TX freq

---------

Co-authored-by: kallanreed <kallanreed@noreply.github.com>
2023-06-26 19:44:01 +02:00
Mark Thompson 3f8a4957af Moved AK Speaker Disable back to a title bar, and modified Mute Icon to differentiate (#1192)
* Added fn to indicate support for disabling speaker
* Support both mute & speaker disable icons
* Byte grouping for ui_config2_t bitmap
* Fix disabling AK speaker to take effect immediately
2023-06-26 08:08:09 +02:00
gullradriel b5ea81cf96 fix SPEC mode in app (#1191)
Co-authored-by: GullCode <gullradriel@hotmail.com>
2023-06-25 16:52:40 -07:00
Kyle Reed e14fa5eab0 New playlist (#1185)
* updated playlist file format
* fix trim and delay part of item
* playlist add/remove tracks
* UI Tweak
2023-06-25 20:40:19 +02:00
Mark Thompson 960ecf616c Update menu icons colors per app state & add contrast to bright icons for improved visibility when Selected (#1189)
* Orange icons vs yellow

* Update app icon colors per current development states

* Darken bright icons on white backgrounds

* Darken bright icons on white backgrounds
2023-06-25 19:48:51 +02:00
gullradriel 996ccb0e40 Recon capture mod (#1190)
* Recon: disabling config while recording
* Capture and Spectre Painter: using freqman set bandwidth option instead of static define
* Recon: light_grey for disable so we can still see the text
2023-06-25 11:33:44 +02:00
Mark Thompson 407fee23b9 Long button press support (#1188) 2023-06-25 11:32:37 +02:00
gullradriel 199570d4a5 flipper inspired freqlists for recon (#1187) 2023-06-25 11:30:09 +02:00
gullradriel 5cd9c377d2 Recon record (#1182)
* added auto record checbox and functions (audio and raw)
* Changed baseband compile option from -O3 to -O2. Trying lower gave unexpeted and crashing results.
* added 650k SPEC bw
* fix hang if wait is -100
* fixing no SPEC support in scanner
2023-06-25 08:16:49 +02:00
Kyle Reed e08273b8b8 Auto layout of top status icons (#1178)
* WIP - updated pmem

* WIP status tray auto layout

* Add settings page for icons

* Add ui_config2 to pmem dump

---------

Co-authored-by: kallanreed <kallanreed@noreply.github.com>
2023-06-23 16:13:39 -07:00
Kyle Reed 6b0f90c321 Update string_format.cpp (#1184) 2023-06-24 00:08:11 +02:00
Mark Thompson 0dea488e98 Added TouchTunes jukebox icon (#1183)
* Added TouchTunes jukebox icon

* Added TouchTunes jukebox icon

* Added TouchTunes jukebox icon
2023-06-23 15:01:19 -07:00
Mark Thompson e60c4dc34b Paint Roller icon for S.Painter & fix make_bitmap.py to generate Clang-compliant code (#1181)
* Generate Clang-compliant output file

* Paint roller icon for Screen Painter app

* Paint roller icon for Screen Painter app

* Paint roller icon for Spectrum Painter
2023-06-23 07:57:35 -07:00
gullradriel 0c75713820 using emplace_back instead of push_back
* using emplace_back instead of push_back is giving better results in regards of the memory management and crashes
* we tried with all our best ideas, we don't know why it's better but it outperformed every other allocations method tested
2023-06-22 22:26:15 +02:00
gullradriel 6a28987a36 Debug utilities (#1171)
-pmem, receiver and transmitter model dumps
2023-06-22 17:35:21 +02:00
gullradriel 7331979107 Freqman memory fix (#1177)
* Cleanings and setting the limit to a working for all apps one
* fixing a guru memory errors, renaming a leftover variable
* removed unneeded set_dirty
2023-06-22 17:34:20 +02:00
Mark Thompson f22808f8ca Pan & Zoom-in support for GeoMap (#1172)
* Zoom-in support

* Zoom-in support

* Clang formatting

* Clang formatting

* Zoom-in support

* Zoom-in support

* Zoom-in support

* Roll-back wrong file edited

* Zoom-in support

* Clang formatting

* Clang formatting

* Zoom-in support

* Zoom-in support

* Zoom-in support

* Oops copy/paste error
2023-06-21 21:56:07 +02:00
E.T e1c519d71e Frequman widget gets a non owning reference to the database instead of copying (#1175)
-Get rid of -Weffc++ pointer member warnings by restricting copy of FreqManUIList
-Add null checks
-Remove unused macro
2023-06-21 12:05:07 +02:00
Kyle Reed 909f37f89b whipcalc parsing, bug fixes (#1174)
* whipcalc parsing, bug fixes
2023-06-20 20:22:58 +02:00
Kyle Reed 63b0093321 Consolidate metadata file parsing (#1173)
* Add metadata file helper and replace old code
* Finish updating metadata read/write
2023-06-20 09:32:37 +02:00
Mark Thompson fa06df1400 Persistent audio mute support revisions (#1169)
* Don't disable DAC when other audio output is using it
* Persistent audio mute revisions
* Moved persistent audio mute code to audio.cpp
* Make "Disable AK speaker amp" take effect immediately
2023-06-19 21:06:07 +02:00
Kyle Reed f83027d451 Re-add MicTX workaround (#1170) 2023-06-19 21:05:08 +02:00
Kyle Reed 7ebf3d3cdd Fix hang in Audio when switching from Capture app, & bug fixes. (#1167)
* Fix hang in Audio when switching from Capture app
* Bug fixes
2023-06-19 07:48:29 +02:00
Kyle Reed 5daa0dfbb1 Consolidate frequency field on_edit (#1166) 2023-06-18 21:11:04 +02:00
Mark Thompson 7b669d7001 Proposed default focus changes (#1165)
* Changed default focus of pocsag, glass, scanner, sonde
2023-06-18 08:14:29 +02:00
Mark Thompson ba2407d691 Persistent audio mute support (#1161)
* Persistent speaker mute support #1100
* Moving persistent mute support from WM8731/AK4951 codec files to audio.cpp module for simplification
2023-06-17 23:15:21 +02:00
Kyle Reed 5743d3a3b9 Playlist cleanup (#1163)
* Functional rewrite of playlist
2023-06-17 22:01:46 +02:00
Kyle Reed 7f1c0f6f7e Explicitly set tx_model to make MicTX happy (#1162)
Co-authored-by: kallanreed <kallanreed@noreply.github.com>
2023-06-17 19:15:46 +02:00
Kyle Reed 650d299c99 Don't update radio directly when saving/restoring tx/rx model settings. (#1160)
* Set rf direction in RadioState

* centralize cpld load for TX->RX

* Remove calls to load_sram from all apps

* re-add CPLD hack, don't set radio values directly.

---------

Co-authored-by: kallanreed <kallanreed@noreply.github.com>
2023-06-17 07:54:52 -07:00
gullradriel 3a5c2da66c fixing bandwidth application, changed UI a bit (#1158)
* fixxing bandwidth application, moved 'continuous' from config page to main page in form of a 'loop' button. Removed looking glass button as it's not configurable to position on a range/signal
2023-06-16 07:50:04 +02:00
Kyle Reed d29826e6f2 Update playlist file parsing, reduce allocs (#1157)
* Update playlist file parsing, reduce allocs
* Cleanup, move impl to cpp
2023-06-15 22:15:26 +02:00
Kyle Reed 34fefd1cad Add file reader (#1155)
* Add file reader
* Add a simple test example of parsing settings.
* Use new FileLineReader to parse Glass presets
* Trim CRLF from Glass preset name
2023-06-15 09:45:13 +02:00
Mark Thompson a5c7eb2fbc Starting freq fixes when no App Settings, and support for App Settings in Scanner App (Mode/BW/Step only) (#1153)
* Fix initial freq when no App Settings tpms
* Default POCSAG freq when no App Settings
* Enable Scanner App Settings for Modulation/BW/Step
* Default SONDE freq when no App Settings
2023-06-14 16:54:19 +02:00
Kyle Reed 6298388fe1 Stash radio settings per app (#1151)
* add a little Stash utility.
* add radio state RAII helper.
* first part of radio_state changes
* add radio_state_ to rest of apps
* fix freq_step and format
* fix unused ui_sigfrx: corrected sample rate, added back freq setting
2023-06-14 09:57:20 +02:00
Kyle Reed 19491ce3f7 Add debug_log, fix tx_bht (#1149) 2023-06-13 08:14:26 +02:00
Kyle Reed 3db2053c21 Allows apps using app settings to use global frequency (#1148)
* Allow apps to not use persisted freq.
* use rx_freq for RX mode
* remove direct call to persistant_memory in tx_model
* app_setting defaults, and tx_view to use ctor value
2023-06-12 19:40:32 +02:00
gullradriel ccd7bd6fc2 Fixing txt_line_processing logic, adding back track number and plalist name (#1146) 2023-06-12 07:25:25 +02:00
Kyle Reed fff78ce00f Faster app_settings save (#1145)
* No-allocation settings save

* remove fixed TODO

---------

Co-authored-by: kallanreed <kallanreed@outlook.com>
2023-06-11 13:35:45 -07:00
Kyle Reed 8bd3d6249d App settings revamp (#1139)
* WIP AppSetting overhaul

* WIP migrating apps to new settings.

* remove settings, rename tuned => target

* formatting

* Minor fixes

* Fix hang on app load

* run formatter

* PR comment fixes

* Load modulation into receiver model in app_settings

* Run formatter

---------

Co-authored-by: kallanreed <kallanreed@outlook.com>
2023-06-11 11:47:13 -07:00
gullradriel f65e743c4c Recon revamp (#1144)
* fixing recon/scanner restarting while on pause and setting a manual start or end
* gui revamping, duplicated options suppressed from config menu
* recon_match_mode set at start
2023-06-11 18:49:28 +02:00
zxkmm 843880d22e playlist: fix track_number out of range and a little fix and alittle ui change (#1142)
* fix playlist track_number out of range
* implement pause
* fix for review: compatibility for old PPL grammar
2023-06-11 18:22:15 +02:00
gullradriel ec3cc7a859 Looking spectrum tune (#1143)
* finely tuned spectrum and marker precision, all modes

* hidding buttons when mode is not supported

* removing on show hide since widget implementation is buggy. Fine tuned values using signal generator to get around the corners

---------

Co-authored-by: GullCode <gullradriel@hotmail.com>
2023-06-10 17:17:02 +02:00
Erwin Ried b1733cbd2a Merge pull request #1141 from u-foka/pmem-debug-rename
improve P.Memory naming consistency
2023-06-10 02:44:31 +02:00
Eisenberger Tamas 4ec2d64cb7 improve P.Memory naming consistency
I swear I wont touch these strings again :D
2023-06-10 00:34:27 +02:00
E.T d370dae170 increase touch sensitivity a little more (#1140)
and make the code a little more descriptive :)

Co-authored-by: Eisenberger Tamas <e.tamas@iwstudio.hu>
2023-06-09 23:45:26 +02:00
E.T 72f0a1be54 add dockerfile-nogit-arm (#1136)
works on aarc64 docker hosts

Co-authored-by: Eisenberger Tamas <e.tamas@iwstudio.hu>
2023-06-10 07:52:14 +12:00
E.T a2e5e03f07 Gcc12 related fixes (#1138)
* GCC12 related fixes but still compiles on GCC9

* Suppress warnings about volatile +=

* Enable c++20 if supported by the compiler

On gcc12 we need to use -std=c++20 since constexpr .c_str() on std::string is only officially available since c++20 and the new gcc wouldnt let us use it with older standard

* code format

---------

Co-authored-by: Eisenberger Tamas <e.tamas@iwstudio.hu>
2023-06-10 07:50:42 +12:00
E.T 47eda54d6a Format script (#1123)
* Add code formatting script

* dont forma the test framework

* use clang-format-13

---------

Co-authored-by: Eisenberger Tamas <e.tamas@iwstudio.hu>
2023-06-10 07:50:21 +12:00
gullradriel db81b9b1e1 code cleaning, fast and slow mode adjustement, dc pixels displayed (#1137)
* code cleaning, fast and slow mode adjustement, dc pixels displayed

* factorisation, comments

* adjusting offsets for alignement

* indent

---------

Co-authored-by: GullCode <gullradriel@hotmail.com>
2023-06-09 17:13:44 +02:00
E.T b9de1918b4 fix pmem -> make backup_ram_t data members volatile (#1135)
* fix pmem -> make backup_ram_t data members volatile

plus add calculated crc to the pmem debug screen

* rename pmem debug menu to p.mem

As this is how its refered to in the wiki and other screens

* p.mem looked strange with capital P

---------

Co-authored-by: Eisenberger Tamas <e.tamas@iwstudio.hu>
2023-06-09 15:52:09 +02:00
dc2dc b87d26f271 Update HAM_70CM_BAND.TXT (#1134)
420-450 max Amateur Radio Band worldwide
2023-06-08 10:05:10 +02:00
322 changed files with 11903 additions and 6676 deletions
+44 -17
View File
@@ -1,28 +1,55 @@
import os
import re
import sys
raw_git = os.popen('git log next --since="24 hours" --pretty=format:"- %h - {USERNAME}*+%al-%an*: %s"').read()
import requests
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"
def compute_username(line):
stripped = re.search(r'(?<=\*)(.*?)(?=\*)', line).group(0)
def print_stable_changelog(previous_sha):
url = f"compare/{previous_sha}...next"
commits = handle_get_request(url)
for commit in commits["commits"]:
# Print the commit details
print(format_output(commit))
pattern = re.compile("[$@+&?].*[$@+&?]")
if pattern.match(stripped):
stripped = re.sub("[$@+&?].*[$@+&?]", "", stripped)
stripped = re.match(r'.+?(?=-)', stripped).group(0)
def print_nightly_changelog():
# Calculate the date and time 24 hours ago
since_time = (datetime.now(timezone.utc) - timedelta(hours=24)).isoformat() # Check that this is UTC
url = "commits"
commits = handle_get_request(url, since_time)
for commit in commits:
# Print the commit details
print(format_output(commit))
def handle_get_request(path, offset=None):
headers = {} if token == None else {"Authorization": f"Bearer {token}"}
params = {"since": offset}
url_base = f"https://api.github.com/repos/{repo_owner}/{repo_name}/"
response = requests.get(url_base + path, headers=headers, params=params)
# Check if the request was successful (status code 200)
if response.status_code == 200:
return response.json()
else:
stripped = re.sub(r'^.*?-', "", stripped)
return "@" + stripped
print(f"Request failed with status code: {response.status_code}")
return None
def compile_line(line):
username = compute_username(line)
line = re.sub(r'[*].*[*]', "", line)
line = line.replace("{USERNAME}", username)
return line
def format_output(commit):
message_lines = commit["commit"]["message"].split("\n")
author = commit["author"]["login"] if commit["author"] and "login" in commit["author"] else commit["commit"]["author"]["name"]
return '- ' + commit["sha"][:8] + ' - @' + author + ': ' + message_lines[0]
for row in raw_git.splitlines():
print(compile_line(row))
if len(sys.argv) < 2:
print_nightly_changelog()
else:
past_version = sys.argv[1]
print_stable_changelog(past_version)
@@ -47,6 +47,9 @@ jobs:
run: mkdir ${{ github.workspace }}/build
- name: Run the Docker image
run: docker run -e VERSION_STRING=${{ steps.version_date.outputs.date }} -i -v ${{ github.workspace }}:/havoc portapack-dev
- 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 && zip -r sdcard-no-map.zip sdcard
- name: Download world map
run: |
wget https://github.com/eried/portapack-mayhem/releases/download/world_map/world_map.zip
@@ -60,6 +63,8 @@ jobs:
run: |
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version_date.outputs.date }}.bin && zip -r sdcard.zip sdcard
- name: Create changelog
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
run: |
CHANGELOG=$(python3 .github/workflows/changelog.py)
CHANGELOG="${CHANGELOG//'%'/'%25'}"
@@ -103,3 +108,13 @@ jobs:
asset_path: ./sdcard.zip
asset_name: mayhem_nightly_${{ steps.version_date.outputs.date }}_COPY_TO_SDCARD.zip
asset_content_type: application/zip
- name: Upload SD Card Assets - No Map
id: upload-sd-card-asset-no-map
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./sdcard-no-map.zip
asset_name: mayhem_nightly_${{ steps.version_date.outputs.date }}_COPY_TO_SDCARD-no-world-map.zip
asset_content_type: application/zip
+16 -1
View File
@@ -31,6 +31,9 @@ jobs:
run: mkdir ${{ github.workspace }}/build
- name: Run the Docker image
run: docker run -e VERSION_STRING=${{ steps.version.outputs.version }} -i -v ${{ github.workspace }}:/havoc portapack-dev
- 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 && zip -r sdcard-no-map.zip sdcard
- name: Download world map
run: |
wget https://github.com/eried/portapack-mayhem/releases/download/world_map/world_map.zip
@@ -44,8 +47,10 @@ jobs:
run: |
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version.outputs.version }}.bin && zip -r sdcard.zip sdcard
- name: Create changelog
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
run: |
CHANGELOG=$(python3 .github/workflows/stable_changelog.py ${{ steps.past_version.outputs.past_version }})
CHANGELOG=$(python3 .github/workflows/changelog.py ${{ steps.past_version.outputs.past_version }})
CHANGELOG="${CHANGELOG//'%'/'%25'}"
CHANGELOG="${CHANGELOG//$'\n'/'%0A'}"
CHANGELOG="${CHANGELOG//$'\r'/'%0D'}"
@@ -96,4 +101,14 @@ jobs:
asset_path: ./sdcard.zip
asset_name: mayhem_${{ steps.version.outputs.version }}_COPY_TO_SDCARD.zip
asset_content_type: application/zip
- name: Upload SD Card Assets - No Map
id: upload-sd-card-asset-no-map
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./sdcard-no-map.zip
asset_name: mayhem_${{ steps.version.outputs.version }}_COPY_TO_SDCARD-no-world-map.zip
asset_content_type: application/zip
+1 -1
View File
@@ -1 +1 @@
v1.7.1
v1.7.3
-30
View File
@@ -1,30 +0,0 @@
import os
import re
import sys
past_version = sys.argv[1]
raw_git = os.popen('git log ' + past_version + '..next --pretty=format:"- %h - {USERNAME}*+%al-%an*: %s"').read()
def compute_username(line):
stripped = re.search(r'(?<=\*)(.*?)(?=\*)', line).group(0)
pattern = re.compile("[$@+&?].*[$@+&?]")
if pattern.match(stripped):
stripped = re.sub("[$@+&?].*[$@+&?]", "", stripped)
stripped = re.match(r'.+?(?=-)', stripped).group(0)
else:
stripped = re.sub(r'^.*?-', "", stripped)
return "@" + stripped
def compile_line(line):
username = compute_username(line)
line = re.sub(r'[*].*[*]', "", line)
line = line.replace("{USERNAME}", username)
return line
for row in raw_git.splitlines():
print(compile_line(row))
+1 -1
View File
@@ -1 +1 @@
v1.7.2
v1.7.4
+41
View File
@@ -0,0 +1,41 @@
FROM ubuntu:xenial
# Set location to download ARM toolkit from.
# This will need to be changed over time or replaced with a static link to the latest release.
ENV ARMBINURL=https://developer.arm.com/-/media/Files/downloads/gnu-rm/9-2019q4/gcc-arm-none-eabi-9-2019-q4-major-aarch64-linux.tar.bz2?revision=4583ce78-e7e7-459a-ad9f-bff8e94839f1&rev=4583ce78e7e7459aad9fbff8e94839f1&hash=C9B942D74CEA05FF9DE28380F267BB7D3F3B17A6 \
PATH=$HOME/bin:$PATH:/opt/build/armbin/bin
#Create volume /havoc/bin for compiled firmware binaries
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
#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
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
# 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
+15 -2
View File
@@ -27,6 +27,8 @@
enable_language(C CXX ASM)
include(CheckCXXCompilerFlag)
project(application)
# Compiler options here.
@@ -36,7 +38,13 @@ set(USE_OPT "-Os -g --specs=nano.specs")
set(USE_COPT "-std=gnu99")
# C++ specific options here (added to USE_OPT).
set(USE_CPPOPT "-std=c++17 -fno-rtti -fno-exceptions -Weffc++ -Wuninitialized")
check_cxx_compiler_flag("-std=c++20" cpp20_supported)
if(cpp20_supported)
set(USE_CPPOPT "-std=c++20")
else()
set(USE_CPPOPT "-std=c++17")
endif()
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)
@@ -167,14 +175,18 @@ set(CPPSRC
#emu_cc1101.cpp
rfm69.cpp
event_m0.cpp
file_reader.cpp
file.cpp
freqman_db.cpp
freqman.cpp
io_convert.cpp
io_file.cpp
io_wave.cpp
irq_controls.cpp
irq_lcd_frame.cpp
irq_rtc.cpp
log_file.cpp
metadata_file.cpp
portapack.cpp
qrcodegen.cpp
radio.cpp
@@ -224,6 +236,7 @@ set(CPPSRC
ui/ui_styles.cpp
ui/ui_tabview.cpp
ui/ui_textentry.cpp
ui/ui_tone_key.cpp
ui/ui_transmitter.cpp
apps/ui_about_simple.cpp
apps/ui_adsb_rx.cpp
@@ -277,7 +290,7 @@ set(CPPSRC
apps/ert_app.cpp
apps/lge_app.cpp
apps/pocsag_app.cpp
apps/replay_app.cpp
# apps/replay_app.cpp
apps/ui_playlist.cpp
apps/gps_sim_app.cpp
apps/soundboard_app.cpp
+221 -75
View File
@@ -26,86 +26,232 @@
#include "file.hpp"
#include "portapack.hpp"
#include "portapack_persistent_memory.hpp"
#include <cstring>
#include "utility.hpp"
#include <algorithm>
#include <cstring>
#include <string_view>
namespace std {
namespace fs = std::filesystem;
using namespace portapack;
using namespace std::literals;
int app_settings::load(std::string application, AppSettings* settings) {
if (portapack::persistent_memory::load_app_settings()) {
file_path = folder + "/" + application + ".ini";
namespace app_settings {
auto error = settings_file.open(file_path);
if (!error.is_valid()) {
auto error = settings_file.read(file_content, std::min((int)settings_file.size(), MAX_FILE_CONTENT_SIZE));
template <typename T>
static void read_setting(
std::string_view file_content,
std::string_view setting_name,
T& value) {
auto pos = file_content.find(setting_name);
settings->baseband_bandwidth = std::app_settings::read_long_long(file_content, "baseband_bandwidth=");
settings->channel_bandwidth = std::app_settings::read_long_long(file_content, "channel_bandwidth=");
settings->lna = std::app_settings::read_long_long(file_content, "lna=");
settings->modulation = std::app_settings::read_long_long(file_content, "modulation=");
settings->rx_amp = std::app_settings::read_long_long(file_content, "rx_amp=");
settings->rx_frequency = std::app_settings::read_long_long(file_content, "rx_frequency=");
settings->sampling_rate = std::app_settings::read_long_long(file_content, "sampling_rate=");
settings->vga = std::app_settings::read_long_long(file_content, "vga=");
settings->tx_amp = std::app_settings::read_long_long(file_content, "tx_amp=");
settings->tx_frequency = std::app_settings::read_long_long(file_content, "tx_frequency=");
settings->tx_gain = std::app_settings::read_long_long(file_content, "tx_gain=");
settings->step = std::app_settings::read_long_long(file_content, "step=");
settings->modulation = std::app_settings::read_long_long(file_content, "modulation=");
settings->am_config_index = std::app_settings::read_long_long(file_content, "am_config_index=");
settings->nbfm_config_index = std::app_settings::read_long_long(file_content, "nbfm_config_index=");
settings->wfm_config_index = std::app_settings::read_long_long(file_content, "wfm_config_index=");
settings->squelch = std::app_settings::read_long_long(file_content, "squelch=");
rc = SETTINGS_OK;
} else
rc = SETTINGS_UNABLE_TO_LOAD;
} else
rc = SETTINGS_DISABLED;
return (rc);
}
int app_settings::save(std::string application, AppSettings* settings) {
if (portapack::persistent_memory::save_app_settings()) {
file_path = folder + "/" + application + ".ini";
make_new_directory(folder);
auto error = settings_file.create(file_path);
if (!error.is_valid()) {
// Save common setting
settings_file.write_line("baseband_bandwidth=" + to_string_dec_uint(portapack::receiver_model.baseband_bandwidth()));
settings_file.write_line("channel_bandwidth=" + to_string_dec_uint(portapack::transmitter_model.channel_bandwidth()));
settings_file.write_line("lna=" + to_string_dec_uint(portapack::receiver_model.lna()));
settings_file.write_line("rx_amp=" + to_string_dec_uint(portapack::receiver_model.rf_amp()));
settings_file.write_line("sampling_rate=" + to_string_dec_uint(portapack::receiver_model.sampling_rate()));
settings_file.write_line("tx_amp=" + to_string_dec_uint(portapack::transmitter_model.rf_amp()));
settings_file.write_line("tx_gain=" + to_string_dec_uint(portapack::transmitter_model.tx_gain()));
settings_file.write_line("vga=" + to_string_dec_uint(portapack::receiver_model.vga()));
// Save other settings from struct
settings_file.write_line("rx_frequency=" + to_string_dec_uint(settings->rx_frequency));
settings_file.write_line("tx_frequency=" + to_string_dec_uint(settings->tx_frequency));
settings_file.write_line("step=" + to_string_dec_uint(settings->step));
settings_file.write_line("modulation=" + to_string_dec_uint(settings->modulation));
settings_file.write_line("am_config_index=" + to_string_dec_uint(settings->am_config_index));
settings_file.write_line("nbfm_config_index=" + to_string_dec_uint(settings->nbfm_config_index));
settings_file.write_line("wfm_config_index=" + to_string_dec_uint(settings->wfm_config_index));
settings_file.write_line("squelch=" + to_string_dec_uint(settings->squelch));
rc = SETTINGS_OK;
} else
rc = SETTINGS_UNABLE_TO_SAVE;
} else
rc = SETTINGS_DISABLED;
return (rc);
}
long long int app_settings::read_long_long(char* file_content, const char* setting_text) {
auto position = strstr(file_content, (char*)setting_text);
if (position) {
position += strlen((char*)setting_text);
setting_value = strtoll(position, nullptr, 10);
if (pos != file_content.npos) {
pos += setting_name.length();
value = strtoll(&file_content[pos], nullptr, 10);
}
return (setting_value);
}
} /* namespace std */
template <typename T>
static void write_setting(File& file, std::string_view setting_name, const T& value) {
// NB: Not using file.write_line speed this up. This happens on every
// app exit when enabled so should be fast to keep the UX responsive.
StringFormatBuffer buffer;
size_t length = 0;
auto value_str = to_string_dec_uint(value, buffer, length);
file.write(setting_name.data(), setting_name.length());
file.write(value_str, length);
file.write("\r\n", 2);
}
static fs::path get_settings_path(const std::string& app_name) {
return fs::path{u"/SETTINGS"} / app_name + u".ini";
}
namespace setting {
constexpr std::string_view baseband_bandwidth = "baseband_bandwidth="sv;
constexpr std::string_view sampling_rate = "sampling_rate="sv;
constexpr std::string_view lna = "lna="sv;
constexpr std::string_view vga = "vga="sv;
constexpr std::string_view rx_amp = "rx_amp="sv;
constexpr std::string_view tx_amp = "tx_amp="sv;
constexpr std::string_view tx_gain = "tx_gain="sv;
constexpr std::string_view channel_bandwidth = "channel_bandwidth="sv;
constexpr std::string_view rx_frequency = "rx_frequency="sv;
constexpr std::string_view tx_frequency = "tx_frequency="sv;
constexpr std::string_view step = "step="sv;
constexpr std::string_view modulation = "modulation="sv;
constexpr std::string_view am_config_index = "am_config_index="sv;
constexpr std::string_view nbfm_config_index = "nbfm_config_index="sv;
constexpr std::string_view wfm_config_index = "wfm_config_index="sv;
constexpr std::string_view squelch = "squelch="sv;
constexpr std::string_view volume = "volume="sv;
} // namespace setting
// TODO: Only load/save values that are declared used.
// This will prevent switching apps from changing setting unnecessarily.
// TODO: Track which values are actually read.
// TODO: Maybe just use a dictionary which would allow for custom settings.
// TODO: Create a control value binding which will allow controls to
// be declaratively bound to a setting and persistence will be magic.
ResultCode load_settings(const std::string& app_name, AppSettings& settings) {
if (!portapack::persistent_memory::load_app_settings())
return ResultCode::SettingsDisabled;
auto file_path = get_settings_path(app_name);
auto data = File::read_file(file_path);
if (!data)
return ResultCode::LoadFailed;
if (flags_enabled(settings.mode, Mode::TX)) {
read_setting(*data, setting::tx_frequency, settings.tx_frequency);
read_setting(*data, setting::tx_amp, settings.tx_amp);
read_setting(*data, setting::tx_gain, settings.tx_gain);
read_setting(*data, setting::channel_bandwidth, settings.channel_bandwidth);
}
if (flags_enabled(settings.mode, Mode::RX)) {
read_setting(*data, setting::rx_frequency, settings.rx_frequency);
read_setting(*data, setting::lna, settings.lna);
read_setting(*data, setting::vga, settings.vga);
read_setting(*data, setting::rx_amp, settings.rx_amp);
read_setting(*data, setting::modulation, settings.modulation);
read_setting(*data, setting::am_config_index, settings.am_config_index);
read_setting(*data, setting::nbfm_config_index, settings.nbfm_config_index);
read_setting(*data, setting::wfm_config_index, settings.wfm_config_index);
read_setting(*data, setting::squelch, settings.squelch);
}
read_setting(*data, setting::baseband_bandwidth, settings.baseband_bandwidth);
read_setting(*data, setting::sampling_rate, settings.sampling_rate);
read_setting(*data, setting::step, settings.step);
read_setting(*data, setting::volume, settings.volume);
return ResultCode::Ok;
}
ResultCode save_settings(const std::string& app_name, AppSettings& settings) {
if (!portapack::persistent_memory::save_app_settings())
return ResultCode::SettingsDisabled;
File settings_file;
auto file_path = get_settings_path(app_name);
ensure_directory(file_path.parent_path());
auto error = settings_file.create(file_path);
if (error)
return ResultCode::SaveFailed;
if (flags_enabled(settings.mode, Mode::TX)) {
write_setting(settings_file, setting::tx_frequency, settings.tx_frequency);
write_setting(settings_file, setting::tx_amp, settings.tx_amp);
write_setting(settings_file, setting::tx_gain, settings.tx_gain);
write_setting(settings_file, setting::channel_bandwidth, settings.channel_bandwidth);
}
if (flags_enabled(settings.mode, Mode::RX)) {
write_setting(settings_file, setting::rx_frequency, settings.rx_frequency);
write_setting(settings_file, setting::lna, settings.lna);
write_setting(settings_file, setting::vga, settings.vga);
write_setting(settings_file, setting::rx_amp, settings.rx_amp);
write_setting(settings_file, setting::modulation, settings.modulation);
write_setting(settings_file, setting::am_config_index, settings.am_config_index);
write_setting(settings_file, setting::nbfm_config_index, settings.nbfm_config_index);
write_setting(settings_file, setting::wfm_config_index, settings.wfm_config_index);
write_setting(settings_file, setting::squelch, settings.squelch);
}
write_setting(settings_file, setting::baseband_bandwidth, settings.baseband_bandwidth);
write_setting(settings_file, setting::sampling_rate, settings.sampling_rate);
write_setting(settings_file, setting::step, settings.step);
write_setting(settings_file, setting::volume, settings.volume);
return ResultCode::Ok;
}
void copy_to_radio_model(const AppSettings& settings) {
// NB: Don't actually adjust the radio here or it will hang.
// 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);
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);
receiver_model.configure_from_app_settings(settings);
receiver_model.set_configuration_without_update(
static_cast<ReceiverModel::Mode>(settings.modulation),
settings.step,
settings.am_config_index,
settings.nbfm_config_index,
settings.wfm_config_index,
settings.squelch);
}
receiver_model.set_frequency_step(settings.step);
receiver_model.set_normalized_headphone_volume(settings.volume);
}
void copy_from_radio_model(AppSettings& settings) {
if (flags_enabled(settings.mode, Mode::TX)) {
settings.tx_frequency = transmitter_model.target_frequency();
settings.baseband_bandwidth = transmitter_model.baseband_bandwidth();
settings.sampling_rate = transmitter_model.sampling_rate();
settings.tx_amp = transmitter_model.rf_amp();
settings.tx_gain = transmitter_model.tx_gain();
settings.channel_bandwidth = transmitter_model.channel_bandwidth();
}
if (flags_enabled(settings.mode, Mode::RX)) {
settings.rx_frequency = receiver_model.target_frequency();
settings.baseband_bandwidth = receiver_model.baseband_bandwidth();
settings.sampling_rate = receiver_model.sampling_rate();
settings.lna = receiver_model.lna();
settings.vga = receiver_model.vga();
settings.rx_amp = receiver_model.rf_amp();
settings.squelch = receiver_model.squelch_level();
settings.modulation = static_cast<uint8_t>(receiver_model.modulation());
settings.am_config_index = receiver_model.am_configuration();
settings.nbfm_config_index = receiver_model.nbfm_configuration();
settings.wfm_config_index = receiver_model.wfm_configuration();
}
settings.step = receiver_model.frequency_step();
settings.volume = receiver_model.normalized_headphone_volume();
}
/* SettingsManager *************************************************/
SettingsManager::SettingsManager(
std::string app_name,
Mode mode,
Options options)
: app_name_{std::move(app_name)},
settings_{},
loaded_{false} {
settings_.mode = mode;
settings_.options = options;
auto result = load_settings(app_name_, settings_);
if (result == ResultCode::Ok) {
loaded_ = true;
copy_to_radio_model(settings_);
}
}
SettingsManager::~SettingsManager() {
if (portapack::persistent_memory::save_app_settings()) {
copy_from_radio_model(settings_);
save_settings(app_name_, settings_);
}
}
} // namespace app_settings
+80 -37
View File
@@ -27,53 +27,96 @@
#include <cstddef>
#include <cstdint>
#include <string>
#include <utility>
#include "file.hpp"
#include "max283x.hpp"
#include "string_format.hpp"
namespace std {
class app_settings {
namespace app_settings {
enum class ResultCode : uint8_t {
Ok, // settings found
LoadFailed, // settings (file) not found
SaveFailed, // unable to save settings
SettingsDisabled, // load/save disabled in settings
};
enum class Mode : uint8_t {
RX = 0x01,
TX = 0x02,
RX_TX = 0x03, // Both TX/RX
};
enum class Options {
None = 0x0000,
/* Don't use target frequency from app settings. */
UseGlobalTargetFrequency = 0x0001,
};
// TODO: separate types for TX/RX or union?
/* NB: See RX/TX model headers for default values. */
struct AppSettings {
Mode mode = Mode::RX;
Options options = Options::None;
uint32_t baseband_bandwidth = max283x::filter::bandwidth_minimum;
uint32_t sampling_rate = 3072000; // Good for 48k audio.
uint8_t lna = 32;
uint8_t vga = 32;
uint8_t rx_amp = 0;
uint8_t tx_amp = 0;
uint8_t tx_gain = 35;
uint32_t channel_bandwidth = 1;
uint32_t rx_frequency;
uint32_t tx_frequency;
uint32_t step = 25000;
uint8_t modulation = 1; // NFM
uint8_t am_config_index = 0;
uint8_t nbfm_config_index = 0;
uint8_t wfm_config_index = 0;
uint8_t squelch = 80;
uint8_t volume;
};
ResultCode load_settings(const std::string& app_name, AppSettings& settings);
ResultCode save_settings(const std::string& app_name, AppSettings& settings);
/* Copies common values to the receiver/transmitter models. */
void copy_to_radio_model(const AppSettings& settings);
/* Copies common values from the receiver/transmitter models. */
void copy_from_radio_model(AppSettings& settings);
/* RAII wrapper for automatically loading and saving settings for an app.
* NB: This should be added to a class before any LNA/VGA controls so that
* the receiver/transmitter models are set before the control ctors run. */
class SettingsManager {
public:
#define SETTINGS_OK 0 // settings found
#define SETTINGS_UNABLE_TO_LOAD -1 // settings (file) not found
#define SETTINGS_UNABLE_TO_SAVE -2 // unable to save settings
#define SETTINGS_DISABLED -3 // load/save settings disabled in settings
SettingsManager(std::string app_name, Mode mode, Options options = Options::None);
~SettingsManager();
struct AppSettings {
uint32_t baseband_bandwidth;
uint32_t channel_bandwidth;
uint8_t lna;
uint8_t modulation;
uint8_t rx_amp;
uint32_t rx_frequency;
uint32_t sampling_rate;
uint8_t tx_amp;
uint32_t tx_frequency;
uint8_t tx_gain;
uint8_t vga;
uint32_t step;
uint8_t am_config_index;
uint8_t nbfm_config_index;
uint8_t wfm_config_index;
uint8_t squelch;
};
SettingsManager(const SettingsManager&) = delete;
SettingsManager& operator=(const SettingsManager&) = delete;
SettingsManager(SettingsManager&&) = delete;
SettingsManager& operator=(SettingsManager&&) = delete;
int load(std::string application, AppSettings* settings);
int save(std::string application, AppSettings* settings);
/* True if settings were successfully loaded from file. */
bool loaded() const { return loaded_; }
Mode mode() const { return settings_.mode; }
AppSettings& raw() { return settings_; }
private:
#define MAX_FILE_CONTENT_SIZE 1000
std::string app_name_;
AppSettings settings_;
bool loaded_;
};
char file_content[MAX_FILE_CONTENT_SIZE] = {};
std::string file_path = "";
std::string folder = "SETTINGS";
int rc = SETTINGS_OK;
File settings_file{};
long long int setting_value{};
} // namespace app_settings
long long int read_long_long(char* file_content, const char* setting_text);
}; // class app_settings
} // namespace std
ENABLE_FLAGS_OPERATORS(app_settings::Mode);
ENABLE_FLAGS_OPERATORS(app_settings::Options);
#endif /*__APP_SETTINGS_H__*/
+3 -33
View File
@@ -55,12 +55,8 @@ void ACARSLogger::log_raw_data(const acars::Packet& packet, const uint32_t frequ
namespace ui {
void ACARSAppView::update_freq(rf::Frequency f) {
set_target_frequency(f);
portapack::persistent_memory::set_tuned_frequency(f); // Maybe not ?
}
ACARSAppView::ACARSAppView(NavigationView& nav) {
ACARSAppView::ACARSAppView(NavigationView& nav)
: nav_{nav} {
baseband::run_image(portapack::spi_flash::image_tag_acars);
add_children({&rssi,
@@ -72,25 +68,8 @@ ACARSAppView::ACARSAppView(NavigationView& nav) {
&check_log,
&console});
receiver_model.set_sampling_rate(2457600);
receiver_model.set_baseband_bandwidth(1750000);
receiver_model.enable();
field_frequency.set_value(receiver_model.tuning_frequency());
update_freq(receiver_model.tuning_frequency());
field_frequency.set_step(receiver_model.frequency_step());
field_frequency.on_change = [this](rf::Frequency f) {
update_freq(f);
};
field_frequency.on_edit = [this, &nav]() {
// TODO: Provide separate modal method/scheme?
auto new_view = nav.push<FrequencyKeypadView>(receiver_model.tuning_frequency());
new_view->on_changed = [this](rf::Frequency f) {
update_freq(f);
field_frequency.set_value(f);
};
};
check_log.set_value(logging);
check_log.on_select = [this](Checkbox&, bool v) {
logging = v;
@@ -132,16 +111,7 @@ void ACARSAppView::on_packet(const acars::Packet& packet) {
// Log raw data whatever it contains
if (logger && logging)
logger->log_raw_data(packet, target_frequency());
}
void ACARSAppView::set_target_frequency(const uint32_t new_value) {
target_frequency_ = new_value;
receiver_model.set_tuning_frequency(new_value);
}
uint32_t ACARSAppView::target_frequency() const {
return target_frequency_;
logger->log_raw_data(packet, receiver_model.target_frequency());
}
} /* namespace ui */
+15 -14
View File
@@ -23,10 +23,12 @@
#ifndef __ACARS_APP_H__
#define __ACARS_APP_H__
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "ui_widget.hpp"
#include "ui_receiver.hpp"
#include "ui_freq_field.hpp"
#include "ui_rssi.hpp"
#include "log_file.hpp"
#include "acars_packet.hpp"
@@ -56,6 +58,14 @@ class ACARSAppView : public View {
std::string title() const override { return "ACARS (WIP)"; };
private:
NavigationView& nav_;
RxRadioState radio_state_{
1750000 /* bandwidth */,
2457600 /* sampling rate */
};
app_settings::SettingsManager settings_{
"rx_acars.hpp", app_settings::Mode::RX};
bool logging{false};
uint32_t packet_counter{0};
@@ -66,15 +76,13 @@ class ACARSAppView : public View {
VGAGainField field_vga{
{18 * 8, 0 * 16}};
RSSI rssi{
{21 * 8, 0, 6 * 8, 4},
};
{21 * 8, 0, 6 * 8, 4}};
Channel channel{
{21 * 8, 5, 6 * 8, 4},
};
{21 * 8, 5, 6 * 8, 4}};
FrequencyField field_frequency{
RxFrequencyField field_frequency{
{0 * 8, 0 * 8},
};
nav_};
Checkbox check_log{
{22 * 8, 21},
3,
@@ -86,15 +94,8 @@ class ACARSAppView : public View {
std::unique_ptr<ACARSLogger> logger{};
uint32_t target_frequency_{};
void update_freq(rf::Frequency f);
void on_packet(const acars::Packet& packet);
uint32_t target_frequency() const;
void set_target_frequency(const uint32_t new_value);
MessageHandlerRegistration message_handler_packet{
Message::ID::ACARSPacket,
[this](Message* const p) {
+9 -41
View File
@@ -283,8 +283,8 @@ AISRecentEntryDetailView::AISRecentEntryDetailView(NavigationView& nav) {
});
button_done.on_select = [this](const ui::Button&) {
if (this->on_close) {
this->on_close();
if (on_close) {
on_close();
}
};
@@ -380,33 +380,23 @@ AISAppView::AISAppView(NavigationView& nav)
&recent_entry_detail_view,
});
// load app settings
auto rc = settings.load("rx_ais", &app_settings);
if (rc == SETTINGS_OK) {
field_lna.set_value(app_settings.lna);
field_vga.set_value(app_settings.vga);
field_rf_amp.set_value(app_settings.rx_amp);
target_frequency_ = app_settings.rx_frequency;
} else
target_frequency_ = initial_target_frequency;
recent_entry_detail_view.hidden(true);
receiver_model.set_tuning_frequency(tuning_frequency());
receiver_model.set_sampling_rate(sampling_rate);
receiver_model.set_baseband_bandwidth(baseband_bandwidth);
if (!settings_.loaded())
receiver_model.set_target_frequency(initial_target_frequency);
receiver_model.enable();
options_channel.on_change = [this](size_t, OptionsField::value_t v) {
this->on_frequency_changed(v);
receiver_model.set_target_frequency(v);
};
options_channel.set_by_value(target_frequency());
options_channel.set_by_value(receiver_model.target_frequency());
recent_entries_view.on_select = [this](const AISRecentEntry& entry) {
this->on_show_detail(entry);
on_show_detail(entry);
};
recent_entry_detail_view.on_close = [this]() {
this->on_show_list();
on_show_list();
};
logger = std::make_unique<AISLogger>();
@@ -416,12 +406,7 @@ AISAppView::AISAppView(NavigationView& nav)
}
AISAppView::~AISAppView() {
// save app settings
app_settings.rx_frequency = target_frequency_;
settings.save("rx_ais", &app_settings);
receiver_model.disable();
baseband::shutdown();
}
@@ -465,21 +450,4 @@ void AISAppView::on_show_detail(const AISRecentEntry& entry) {
recent_entry_detail_view.focus();
}
void AISAppView::on_frequency_changed(const uint32_t new_target_frequency) {
set_target_frequency(new_target_frequency);
}
void AISAppView::set_target_frequency(const uint32_t new_value) {
target_frequency_ = new_value;
receiver_model.set_tuning_frequency(tuning_frequency());
}
uint32_t AISAppView::target_frequency() const {
return target_frequency_;
}
uint32_t AISAppView::tuning_frequency() const {
return target_frequency() - (sampling_rate / 4);
}
} /* namespace ui */
+7 -17
View File
@@ -34,6 +34,7 @@
#include "log_file.hpp"
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "ais_packet.hpp"
#include "lpc43xx_cpp.hpp"
@@ -154,9 +155,6 @@ class AISAppView : public View {
~AISAppView();
void set_parent_rect(const Rect new_parent_rect) override;
// Prevent painting of region covered entirely by a child.
// TODO: Add flag to View that specifies view does not need to be cleared before painting.
void paint(Painter&) override{};
void focus() override;
@@ -165,12 +163,13 @@ class AISAppView : public View {
private:
static constexpr uint32_t initial_target_frequency = 162025000;
static constexpr uint32_t sampling_rate = 2457600;
static constexpr uint32_t baseband_bandwidth = 1750000;
// app save settings
std::app_settings settings{};
std::app_settings::AppSettings app_settings{};
RxRadioState radio_state_{
1750000 /* bandwidth */,
2457600 /* sampling rate */
};
app_settings::SettingsManager settings_{
"rx_ais", app_settings::Mode::RX};
NavigationView& nav_;
@@ -225,18 +224,9 @@ class AISAppView : public View {
}
}};
uint32_t target_frequency_ = initial_target_frequency;
void on_packet(const ais::Packet& packet);
void on_show_list();
void on_show_detail(const AISRecentEntry& entry);
void on_frequency_changed(const uint32_t new_target_frequency);
uint32_t target_frequency() const;
void set_target_frequency(const uint32_t new_value);
uint32_t tuning_frequency() const;
};
} /* namespace ui */
+51 -107
View File
@@ -22,21 +22,17 @@
#include "analog_audio_app.hpp"
#include "audio.hpp"
#include "baseband_api.hpp"
#include "file.hpp"
#include "portapack.hpp"
#include "portapack_persistent_memory.hpp"
using namespace portapack;
using namespace tonekey;
#include "audio.hpp"
#include "file.hpp"
#include "string_format.hpp"
#include "ui_freqman.hpp"
#include "utility.hpp"
#include "string_format.hpp"
#include "freqman.hpp"
using namespace portapack;
using namespace tonekey;
namespace ui {
@@ -45,8 +41,8 @@ namespace ui {
static const Style& style_options_group = Styles::bg_blue;
AMOptionsView::AMOptionsView(
const Rect parent_rect,
const Style* const style)
Rect parent_rect,
const Style* style)
: View{parent_rect} {
set_style(style);
@@ -65,8 +61,8 @@ AMOptionsView::AMOptionsView(
/* NBFMOptionsView *******************************************************/
NBFMOptionsView::NBFMOptionsView(
const Rect parent_rect,
const Style* const style)
Rect parent_rect,
const Style* style)
: View{parent_rect} {
set_style(style);
@@ -90,8 +86,8 @@ NBFMOptionsView::NBFMOptionsView(
/* WFMOptionsView *******************************************************/
WFMOptionsView::WFMOptionsView(
const Rect parent_rect,
const Style* const style)
Rect parent_rect,
const Style* style)
: View{parent_rect} {
set_style(style);
@@ -111,8 +107,8 @@ WFMOptionsView::WFMOptionsView(
SPECOptionsView::SPECOptionsView(
AnalogAudioView* view,
const Rect parent_rect,
const Style* const style)
Rect parent_rect,
const Style* style)
: View{parent_rect} {
set_style(style);
@@ -137,6 +133,9 @@ SPECOptionsView::SPECOptionsView(
AnalogAudioView::AnalogAudioView(
NavigationView& nav)
: nav_(nav) {
// A baseband image _must_ be running before add waterfall view.
baseband::run_image(portapack::spi_flash::image_tag_wideband_spectrum);
add_children({&rssi,
&channel,
&audio,
@@ -149,35 +148,9 @@ AnalogAudioView::AnalogAudioView(
&record_view,
&waterfall});
// Set on_change before initialising the field
field_frequency.on_change = [this](rf::Frequency f) {
this->on_tuning_frequency_changed(f);
};
// load app settings
auto rc = settings.load("rx_audio", &app_settings);
if (rc == SETTINGS_OK) {
field_lna.set_value(app_settings.lna);
field_vga.set_value(app_settings.vga);
receiver_model.set_rf_amp(app_settings.rx_amp);
// field_frequency.set_value(app_settings.rx_frequency);
receiver_model.set_configuration_without_init(static_cast<ReceiverModel::Mode>(app_settings.modulation), app_settings.step, app_settings.am_config_index, app_settings.nbfm_config_index, app_settings.wfm_config_index, app_settings.squelch);
}
field_frequency.set_value(receiver_model.tuning_frequency());
// Filename Datetime and Frequency
record_view.set_filename_date_frequency(true);
field_frequency.set_step(receiver_model.frequency_step());
field_frequency.on_edit = [this, &nav]() {
// TODO: Provide separate modal method/scheme?
auto new_view = nav.push<FrequencyKeypadView>(receiver_model.tuning_frequency());
new_view->on_changed = [this](rf::Frequency f) {
this->on_tuning_frequency_changed(f);
this->field_frequency.set_value(f);
};
};
field_frequency.on_show_options = [this]() {
this->on_show_options_frequency();
};
@@ -190,9 +163,12 @@ AnalogAudioView::AnalogAudioView(
this->on_show_options_rf_gain();
};
const auto modulation = receiver_model.modulation();
options_modulation.set_by_value(toUType(modulation));
auto modulation = receiver_model.modulation();
// This app doesn't handle "Capture" mode.
if (modulation > ReceiverModel::Mode::SpectrumAnalysis)
modulation = ReceiverModel::Mode::SpectrumAnalysis;
options_modulation.set_by_value(toUType(modulation));
options_modulation.on_change = [this](size_t, OptionsField::value_t v) {
this->on_modulation_changed(static_cast<ReceiverModel::Mode>(v));
};
@@ -205,13 +181,23 @@ AnalogAudioView::AnalogAudioView(
};
waterfall.on_select = [this](int32_t offset) {
field_frequency.set_value(receiver_model.tuning_frequency() + offset);
field_frequency.set_value(receiver_model.target_frequency() + offset);
};
audio::output::start();
update_modulation(static_cast<ReceiverModel::Mode>(modulation));
on_modulation_changed(static_cast<ReceiverModel::Mode>(modulation));
// This call starts the correct baseband image to run
// and sets the radio up as necessary for the given modulation.
on_modulation_changed(modulation);
}
AnalogAudioView::AnalogAudioView(
NavigationView& nav,
ReceiverModel::settings_t override)
: AnalogAudioView(nav) {
// TODO: Which other settings make sense to override?
on_frequency_step_changed(override.frequency_step);
options_modulation.set_by_value(toUType(override.mode));
}
size_t AnalogAudioView::get_spec_bw_index() {
@@ -238,40 +224,15 @@ void AnalogAudioView::set_spec_trigger(uint16_t trigger) {
}
AnalogAudioView::~AnalogAudioView() {
// save app settings
app_settings.rx_frequency = field_frequency.value();
app_settings.lna = receiver_model.lna();
app_settings.vga = receiver_model.vga();
app_settings.rx_amp = receiver_model.rf_amp();
app_settings.step = receiver_model.frequency_step();
app_settings.modulation = (uint8_t)receiver_model.modulation();
app_settings.am_config_index = receiver_model.am_configuration();
app_settings.nbfm_config_index = receiver_model.nbfm_configuration();
app_settings.wfm_config_index = receiver_model.wfm_configuration();
app_settings.squelch = receiver_model.squelch_level();
settings.save("rx_audio", &app_settings);
// TODO: Manipulating audio codec here, and in ui_receiver.cpp. Good to do
// both?
audio::output::stop();
receiver_model.set_sampling_rate(3072000); // Just a hack to avoid hanging other apps if the last modulation was SPEC
receiver_model.disable();
baseband::shutdown();
}
void AnalogAudioView::on_hide() {
// TODO: Terrible kludge because widget system doesn't notify Waterfall that
// it's being shown or hidden.
waterfall.on_hide();
View::on_hide();
}
void AnalogAudioView::set_parent_rect(const Rect new_parent_rect) {
void AnalogAudioView::set_parent_rect(Rect new_parent_rect) {
View::set_parent_rect(new_parent_rect);
const ui::Rect waterfall_rect{0, header_height, new_parent_rect.width(), new_parent_rect.height() - header_height};
ui::Rect waterfall_rect{0, header_height, new_parent_rect.width(), new_parent_rect.height() - header_height};
waterfall.set_parent_rect(waterfall_rect);
}
@@ -279,21 +240,19 @@ void AnalogAudioView::focus() {
field_frequency.focus();
}
void AnalogAudioView::on_tuning_frequency_changed(rf::Frequency f) {
receiver_model.set_tuning_frequency(f);
}
void AnalogAudioView::on_baseband_bandwidth_changed(uint32_t bandwidth_hz) {
receiver_model.set_baseband_bandwidth(bandwidth_hz);
}
void AnalogAudioView::on_modulation_changed(const ReceiverModel::Mode modulation) {
// TODO: Terrible kludge because widget system doesn't notify Waterfall that
// it's being shown or hidden.
waterfall.on_hide();
void AnalogAudioView::on_modulation_changed(ReceiverModel::Mode modulation) {
// This app doesn't know what to do with "Capture" mode.
if (modulation > ReceiverModel::Mode::SpectrumAnalysis)
modulation = ReceiverModel::Mode::SpectrumAnalysis;
baseband::spectrum_streaming_stop();
update_modulation(modulation);
on_show_options_modulation();
waterfall.on_show();
baseband::spectrum_streaming_start();
}
void AnalogAudioView::remove_options_widget() {
@@ -345,7 +304,7 @@ void AnalogAudioView::on_show_options_rf_gain() {
void AnalogAudioView::on_show_options_modulation() {
std::unique_ptr<Widget> widget;
const auto modulation = static_cast<ReceiverModel::Mode>(receiver_model.modulation());
const auto modulation = receiver_model.modulation();
switch (modulation) {
case ReceiverModel::Mode::AMAudio:
widget = std::make_unique<AMOptionsView>(options_view_rect, &style_options_group);
@@ -372,6 +331,7 @@ void AnalogAudioView::on_show_options_modulation() {
break;
default:
chDbgPanic("Unhandled Mode");
break;
}
@@ -388,7 +348,7 @@ void AnalogAudioView::on_reference_ppm_correction_changed(int32_t v) {
persistent_memory::set_correction_ppb(v * 1000);
}
void AnalogAudioView::update_modulation(const ReceiverModel::Mode modulation) {
void AnalogAudioView::update_modulation(ReceiverModel::Mode modulation) {
audio::output::mute();
record_view.stop();
@@ -409,7 +369,8 @@ void AnalogAudioView::update_modulation(const ReceiverModel::Mode modulation) {
image_tag = portapack::spi_flash::image_tag_wideband_spectrum;
break;
default:
return;
chDbgPanic("Unhandled Mode");
break;
}
baseband::run_image(image_tag);
@@ -448,25 +409,8 @@ void AnalogAudioView::update_modulation(const ReceiverModel::Mode modulation) {
}
}
void AnalogAudioView::handle_coded_squelch(const uint32_t value) {
float diff, min_diff = value;
size_t min_idx{0};
size_t c;
// Find nearest match
for (c = 0; c < tone_keys.size(); c++) {
diff = abs(((float)value / 100.0) - tone_keys[c].second);
if (diff < min_diff) {
min_idx = c;
min_diff = diff;
}
}
// Arbitrary confidence threshold
if (min_diff < 40)
text_ctcss.set("CTCSS " + tone_keys[min_idx].first);
else
text_ctcss.set("???");
void AnalogAudioView::handle_coded_squelch(uint32_t value) {
text_ctcss.set(tone_key_string_by_value(value, text_ctcss.parent_rect().width() / 8));
}
} /* namespace ui */
+32 -47
View File
@@ -26,17 +26,19 @@
#include "receiver_model.hpp"
#include "ui_receiver.hpp"
#include "ui_freq_field.hpp"
#include "ui_spectrum.hpp"
#include "ui_record_view.hpp"
#include "ui_styles.hpp"
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "tone_key.hpp"
namespace ui {
class AMOptionsView : public View {
public:
AMOptionsView(const Rect parent_rect, const Style* const style);
AMOptionsView(Rect parent_rect, const Style* style);
private:
Text label_config{
@@ -46,15 +48,15 @@ class AMOptionsView : public View {
OptionsField options_config{
{3 * 8, 0 * 16},
6, // number of blanking characters
6, // Max option length
{
// using common messages from freqman.cpp
// Using common messages from freqman_ui.cpp
}};
};
class NBFMOptionsView : public View {
public:
NBFMOptionsView(const Rect parent_rect, const Style* const style);
NBFMOptionsView(Rect parent_rect, const Style* style);
private:
Text label_config{
@@ -63,16 +65,16 @@ class NBFMOptionsView : public View {
};
OptionsField options_config{
{3 * 8, 0 * 16},
4,
3, // Max option length
{
// using common messages from freqman.cpp
// Using common messages from freqman_ui.cpp
}};
Text text_squelch{
{9 * 8, 0 * 16, 8 * 8, 1 * 16},
{7 * 8, 0 * 16, 8 * 8, 1 * 16},
"SQ /99"};
NumberField field_squelch{
{12 * 8, 0 * 16},
{10 * 8, 0 * 16},
2,
{0, 99},
1,
@@ -82,7 +84,7 @@ class NBFMOptionsView : public View {
class WFMOptionsView : public View {
public:
WFMOptionsView(const Rect parent_rect, const Style* const style);
WFMOptionsView(Rect parent_rect, const Style* style);
private:
Text label_config{
@@ -91,9 +93,9 @@ class WFMOptionsView : public View {
};
OptionsField options_config{
{3 * 8, 0 * 16},
4,
4, // Max option length
{
// using common messages from freqman.cpp
// Using common messages from freqman_ui.cpp
}};
};
@@ -101,7 +103,7 @@ class AnalogAudioView;
class SPECOptionsView : public View {
public:
SPECOptionsView(AnalogAudioView* view, const Rect parent_rect, const Style* const style);
SPECOptionsView(AnalogAudioView* view, Rect parent_rect, const Style* style);
private:
Text label_config{
@@ -136,12 +138,10 @@ class SPECOptionsView : public View {
class AnalogAudioView : public View {
public:
AnalogAudioView(NavigationView& nav);
AnalogAudioView(NavigationView& nav, ReceiverModel::settings_t override);
~AnalogAudioView();
void on_hide() override;
void set_parent_rect(const Rect new_parent_rect) override;
void set_parent_rect(Rect new_parent_rect) override;
void focus() override;
std::string title() const override { return "Audio RX"; };
@@ -155,9 +155,11 @@ class AnalogAudioView : public View {
private:
static constexpr ui::Dim header_height = 3 * 16;
// app save settings
std::app_settings settings{};
std::app_settings::AppSettings app_settings{};
NavigationView& nav_;
RxRadioState radio_state_{};
app_settings::SettingsManager settings_{
"rx_audio", app_settings::Mode::RX,
app_settings::Options::UseGlobalTargetFrequency};
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};
@@ -166,24 +168,18 @@ class AnalogAudioView : public View {
uint32_t spec_bw = 20000000;
uint16_t spec_trigger = 63;
NavigationView& nav_;
// bool exit_on_squelch { false };
RSSI rssi{
{21 * 8, 0, 6 * 8, 4},
};
{21 * 8, 0, 6 * 8, 4}};
Channel channel{
{21 * 8, 5, 6 * 8, 4},
};
{21 * 8, 5, 6 * 8, 4}};
Audio audio{
{21 * 8, 10, 6 * 8, 4},
};
{21 * 8, 10, 6 * 8, 4}};
FrequencyField field_frequency{
RxFrequencyField field_frequency{
{5 * 8, 0 * 16},
};
nav_};
LNAGainField field_lna{
{15 * 8, 0 * 16}};
@@ -205,7 +201,7 @@ class AnalogAudioView : public View {
{28 * 8, 0 * 16}};
Text text_ctcss{
{19 * 8, 1 * 16, 11 * 8, 1 * 16},
{16 * 8, 1 * 16, 14 * 8, 1 * 16},
""};
std::unique_ptr<Widget> options_widget{};
@@ -218,37 +214,26 @@ class AnalogAudioView : public View {
4096,
4};
spectrum::WaterfallWidget waterfall{true};
spectrum::WaterfallView waterfall{true};
void on_tuning_frequency_changed(rf::Frequency f);
void on_baseband_bandwidth_changed(uint32_t bandwidth_hz);
void on_modulation_changed(const ReceiverModel::Mode modulation);
void on_modulation_changed(ReceiverModel::Mode modulation);
void on_show_options_frequency();
void on_show_options_rf_gain();
void on_show_options_modulation();
void on_frequency_step_changed(rf::Frequency f);
void on_reference_ppm_correction_changed(int32_t v);
void on_edit_frequency();
void remove_options_widget();
void set_options_widget(std::unique_ptr<Widget> new_widget);
void update_modulation(const ReceiverModel::Mode modulation);
void update_modulation(ReceiverModel::Mode modulation);
// void squelched();
void handle_coded_squelch(const uint32_t value);
/*MessageHandlerRegistration message_handler_squelch_signal {
Message::ID::RequestSignal,
[this](const Message* const p) {
(void)p;
this->squelched();
}
};*/
void handle_coded_squelch(uint32_t value);
MessageHandlerRegistration message_handler_coded_squelch{
Message::ID::CodedSquelch,
[this](const Message* const p) {
[this](const Message* p) {
const auto message = *reinterpret_cast<const CodedSquelchMessage*>(p);
this->handle_coded_squelch(message.value);
}};
+1 -36
View File
@@ -56,31 +56,6 @@ AnalogTvView::AnalogTvView(
&field_volume,
&tv});
// Set on_change before initialising the field
field_frequency.on_change = [this](rf::Frequency f) {
this->on_tuning_frequency_changed(f);
};
// load app settings
auto rc = settings.load("rx_tv", &app_settings);
if (rc == SETTINGS_OK) {
field_lna.set_value(app_settings.lna);
field_vga.set_value(app_settings.vga);
receiver_model.set_rf_amp(app_settings.rx_amp);
field_frequency.set_value(app_settings.rx_frequency);
} else
field_frequency.set_value(receiver_model.tuning_frequency());
field_frequency.set_step(receiver_model.frequency_step());
field_frequency.on_edit = [this, &nav]() {
// TODO: Provide separate modal method/scheme?
auto new_view = nav.push<FrequencyKeypadView>(receiver_model.tuning_frequency());
new_view->on_changed = [this](rf::Frequency f) {
this->on_tuning_frequency_changed(f);
this->field_frequency.set_value(f);
};
};
field_frequency.on_show_options = [this]() {
this->on_show_options_frequency();
};
@@ -103,7 +78,7 @@ AnalogTvView::AnalogTvView(
};
tv.on_select = [this](int32_t offset) {
field_frequency.set_value(receiver_model.tuning_frequency() + offset);
field_frequency.set_value(receiver_model.target_frequency() + offset);
};
update_modulation(static_cast<ReceiverModel::Mode>(modulation));
@@ -111,12 +86,6 @@ AnalogTvView::AnalogTvView(
}
AnalogTvView::~AnalogTvView() {
// save app settings
app_settings.rx_frequency = field_frequency.value();
settings.save("rx_tv", &app_settings);
// TODO: Manipulating audio codec here, and in ui_receiver.cpp. Good to do
// both?
audio::output::stop();
receiver_model.disable();
baseband::shutdown();
@@ -140,10 +109,6 @@ void AnalogTvView::focus() {
field_frequency.focus();
}
void AnalogTvView::on_tuning_frequency_changed(rf::Frequency f) {
receiver_model.set_tuning_frequency(f);
}
void AnalogTvView::on_baseband_bandwidth_changed(uint32_t bandwidth_hz) {
receiver_model.set_baseband_bandwidth(bandwidth_hz);
}
+11 -15
View File
@@ -27,10 +27,12 @@
#include "receiver_model.hpp"
#include "ui_receiver.hpp"
#include "ui_freq_field.hpp"
#include "ui_tv.hpp"
#include "ui_record_view.hpp"
#include "ui_styles.hpp"
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "tone_key.hpp"
@@ -52,30 +54,26 @@ class AnalogTvView : public View {
private:
static constexpr ui::Dim header_height = 3 * 16;
// app save settings
std::app_settings settings{};
std::app_settings::AppSettings app_settings{};
NavigationView& nav_;
RxRadioState radio_state_{};
app_settings::SettingsManager settings_{
"rx_tv", app_settings::Mode::RX};
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};
NavigationView& nav_;
RSSI rssi{
{21 * 8, 0, 6 * 8, 4},
};
{21 * 8, 0, 6 * 8, 4}};
Channel channel{
{21 * 8, 5, 6 * 8, 4},
};
{21 * 8, 5, 6 * 8, 4}};
Audio audio{
{21 * 8, 10, 6 * 8, 4},
};
{21 * 8, 10, 6 * 8, 4}};
FrequencyField field_frequency{
RxFrequencyField field_frequency{
{5 * 8, 0 * 16},
};
nav_};
LNAGainField field_lna{
{15 * 8, 0 * 16}};
@@ -99,7 +97,6 @@ class AnalogTvView : public View {
tv::TVWidget tv{};
void on_tuning_frequency_changed(rf::Frequency f);
void on_baseband_bandwidth_changed(uint32_t bandwidth_hz);
void on_modulation_changed(const ReceiverModel::Mode modulation);
void on_show_options_frequency();
@@ -107,7 +104,6 @@ class AnalogTvView : public View {
void on_show_options_modulation();
void on_frequency_step_changed(rf::Frequency f);
void on_reference_ppm_correction_changed(int32_t v);
void on_edit_frequency();
void remove_options_widget();
void set_options_widget(std::unique_ptr<Widget> new_widget);
+32 -92
View File
@@ -21,15 +21,16 @@
*/
#include "capture_app.hpp"
#include "baseband_api.hpp"
#include "portapack.hpp"
#include "ui_freqman.hpp"
using namespace portapack;
namespace ui {
CaptureAppView::CaptureAppView(NavigationView& nav) {
CaptureAppView::CaptureAppView(NavigationView& nav)
: nav_{nav} {
baseband::run_image(portapack::spi_flash::image_tag_capture);
add_children({
@@ -42,90 +43,47 @@ CaptureAppView::CaptureAppView(NavigationView& nav) {
&field_lna,
&field_vga,
&option_bandwidth,
&option_format,
&record_view,
&waterfall,
});
// Hack for initialization
// TODO: This should be included in a more global section so apps dont need to do it
receiver_model.set_sampling_rate(3072000);
receiver_model.set_baseband_bandwidth(1750000);
//-------------------
field_frequency.set_value(receiver_model.tuning_frequency());
field_frequency.set_step(receiver_model.frequency_step());
field_frequency.on_change = [this](rf::Frequency f) {
this->on_tuning_frequency_changed(f);
};
field_frequency.on_edit = [this, &nav]() {
// TODO: Provide separate modal method/scheme?
auto new_view = nav.push<FrequencyKeypadView>(receiver_model.tuning_frequency());
new_view->on_changed = [this](rf::Frequency f) {
this->on_tuning_frequency_changed(f);
this->field_frequency.set_value(f);
};
};
field_frequency_step.set_by_value(receiver_model.frequency_step());
field_frequency_step.on_change = [this](size_t, OptionsField::value_t v) {
receiver_model.set_frequency_step(v);
this->field_frequency.set_step(v);
};
option_bandwidth.on_change = [this](size_t, uint32_t base_rate) {
sampling_rate = 8 * base_rate; // Decimation by 8 done on baseband side
/* base_rate 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 */
waterfall.on_hide();
record_view.set_sampling_rate(sampling_rate);
receiver_model.set_sampling_rate(sampling_rate);
/* Set up proper anti aliasing BPF bandwith in MAX2837 before ADC sampling according to the new added BW Options . */
switch (sampling_rate) { // we use the var fs (sampling_rate) , to set up BPF aprox < fs_max/2 by Nyquist theorem.
case 0 ... 2000000: // BW Captured range (0 <= 250kHz max ) fs = 8 x 250 kHz
anti_alias_baseband_bandwidth_filter = 1750000; // Minimum BPF MAX2837 for all those lower BW options.
break;
case 4000000 ... 6000000: // BW capture range (500k ... 750kHz max ) fs_max = 8 x 750kHz = 6Mhz
// BW 500k ... 750kHz , ex. 500kHz (fs = 8*BW = 4Mhz) , BW 600kHz (fs = 4,8Mhz) , BW 750 kHz (fs = 6Mhz)
anti_alias_baseband_bandwidth_filter = 2500000; // in some IC MAX2837 appear 2250000 , but both works similar.
break;
case 8800000: // BW capture 1,1Mhz fs = 8 x 1,1Mhz = 8,8Mhz . (1Mhz showed slightly higher noise background).
anti_alias_baseband_bandwidth_filter = 3500000;
break;
case 14000000: // BW capture 1,75Mhz , fs = 8 x 1,75Mhz = 14Mhz
// good BPF ,good matching, but LCD making flicker , refresh rate should be < 20 Hz , but reasonable picture
anti_alias_baseband_bandwidth_filter = 5000000;
break;
case 16000000: // BW capture 2Mhz , fs = 8 x 2Mhz = 16Mhz
// good BPF ,good matching, but LCD making flicker , refresh rate should be < 20 Hz , but reasonable picture
anti_alias_baseband_bandwidth_filter = 6000000;
break;
case 20000000: // BW capture 2,5Mhz , fs= 8 x 2,5 Mhz = 20Mhz
// good BPF ,good matching, but LCD making flicker , refresh rate should be < 20 Hz , but reasonable picture
anti_alias_baseband_bandwidth_filter = 7000000;
break;
default: // BW capture 2,75Mhz, fs = 8 x 2,75Mhz= 22Mhz max ADC sampling) and others.
// We tested also 9Mhz FPB stightly too much noise floor , better 8Mhz
anti_alias_baseband_bandwidth_filter = 8000000;
}
receiver_model.set_baseband_bandwidth(anti_alias_baseband_bandwidth_filter);
waterfall.on_show();
option_format.set_selected_index(0); // Default to C16
option_format.on_change = [this](size_t, uint32_t file_type) {
record_view.set_file_type((RecordView::FileType)file_type);
};
option_bandwidth.set_selected_index(7); // 500k, Preselected starting default option 500kHz
freqman_set_bandwidth_option(SPEC_MODULATION, option_bandwidth);
option_bandwidth.on_change = [this](size_t, uint32_t base_rate) {
/* base_rate 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 */
// For lower bandwidths, (12k5, 16k, 20k), increase the oversample rate to get a higher sample rate.
OversampleRate oversample_rate = base_rate >= 25'000 ? OversampleRate::Rate8x : OversampleRate::Rate16x;
// HackRF suggests a minimum sample rate of 2M.
// Oversampling helps get to higher sample rates when recording lower bandwidths.
uint32_t sampling_rate = toUType(oversample_rate) * base_rate;
// Set up proper anti aliasing BPF bandwidth in MAX2837 before ADC sampling according to the new added BW Options.
auto anti_alias_baseband_bandwidth_filter = filter_bandwidth_for_sampling_rate(sampling_rate);
waterfall.stop();
record_view.set_sampling_rate(sampling_rate, oversample_rate); // NB: Actually updates the baseband.
receiver_model.set_sampling_rate(sampling_rate);
receiver_model.set_baseband_bandwidth(anti_alias_baseband_bandwidth_filter);
waterfall.start();
};
receiver_model.set_modulation(ReceiverModel::Mode::Capture);
receiver_model.enable();
option_bandwidth.set_selected_index(7); // Preselected default option 500kHz.
record_view.on_error = [&nav](std::string message) {
nav.display_modal("Error", message);
@@ -133,28 +91,14 @@ CaptureAppView::CaptureAppView(NavigationView& nav) {
}
CaptureAppView::~CaptureAppView() {
// Hack for preventing halting other apps
// TODO: This should be also part of something global
receiver_model.set_sampling_rate(3072000);
receiver_model.set_baseband_bandwidth(1750000);
receiver_model.set_modulation(ReceiverModel::Mode::WidebandFMAudio);
// ----------------------------
receiver_model.disable();
baseband::shutdown();
}
void CaptureAppView::on_hide() {
// TODO: Terrible kludge because widget system doesn't notify Waterfall that
// it's being shown or hidden.
waterfall.on_hide();
View::on_hide();
}
void CaptureAppView::set_parent_rect(const Rect new_parent_rect) {
View::set_parent_rect(new_parent_rect);
const ui::Rect waterfall_rect{0, header_height, new_parent_rect.width(), new_parent_rect.height() - header_height};
ui::Rect waterfall_rect{0, header_height, new_parent_rect.width(), new_parent_rect.height() - header_height};
waterfall.set_parent_rect(waterfall_rect);
}
@@ -162,8 +106,4 @@ void CaptureAppView::focus() {
record_view.focus();
}
void CaptureAppView::on_tuning_frequency_changed(rf::Frequency f) {
receiver_model.set_tuning_frequency(f);
}
} /* namespace ui */
+23 -35
View File
@@ -26,25 +26,11 @@
#include "ui_widget.hpp"
#include "ui_navigation.hpp"
#include "ui_receiver.hpp"
#include "ui_freq_field.hpp"
#include "ui_record_view.hpp"
#include "ui_spectrum.hpp"
#define BW_OPTIONS \
{" 8k5", 8500}, \
{" 11k", 11000}, \
{" 16k", 16000}, \
{" 25k", 25000}, \
{" 50k", 50000}, \
{" 100k", 100000}, \
{" 250k", 250000}, \
{" 500k", 500000}, /* Previous Limit bandwith Option with perfect micro SD write .C16 format operaton.*/ \
{" 600k", 600000}, /* That extended option is still possible to record with FW version Mayhem v1.41 (< 2,5MB/sec) */ \
{" 750k", 750000}, /* From that BW onwards, the LCD is ok, but the recorded file is auto decimated,(not real file size) */ \
{"1100k", 1100000}, \
{"1750k", 1750000}, \
{"2000k", 2000000}, \
{"2500k", 2500000}, \
{"2750k", 2750000}, // That is our max Capture option , to keep using later / 8 decimation (22Mhz sampling ADC)
#include "app_settings.hpp"
#include "radio_state.hpp"
namespace ui {
@@ -53,41 +39,37 @@ class CaptureAppView : public View {
CaptureAppView(NavigationView& nav);
~CaptureAppView();
void on_hide() override;
void set_parent_rect(const Rect new_parent_rect) override;
void focus() override;
void set_parent_rect(const Rect new_parent_rect) override;
std::string title() const override { return "Capture"; };
private:
static constexpr ui::Dim header_height = 3 * 16;
uint32_t sampling_rate = 0;
uint32_t anti_alias_baseband_bandwidth_filter = 2500000; // we rename the previous var , and change type static constexpr to normal var.
void on_tuning_frequency_changed(rf::Frequency f);
NavigationView& nav_;
RxRadioState radio_state_{ReceiverModel::Mode::Capture};
app_settings::SettingsManager settings_{
"rx_capture", app_settings::Mode::RX,
app_settings::Options::UseGlobalTargetFrequency};
Labels labels{
{{0 * 8, 1 * 16}, "Rate:", Color::light_grey()},
{{11 * 8, 1 * 16}, "Format:", Color::light_grey()},
};
RSSI rssi{
{24 * 8, 0, 6 * 8, 4},
};
{24 * 8, 0, 6 * 8, 4}};
Channel channel{
{24 * 8, 5, 6 * 8, 4},
};
{24 * 8, 5, 6 * 8, 4}};
FrequencyField field_frequency{
RxFrequencyField field_frequency{
{0 * 8, 0 * 16},
};
nav_};
FrequencyStepView field_frequency_step{
{10 * 8, 0 * 16},
};
{10 * 8, 0 * 16}};
RFAmpField field_rf_amp{
{16 * 8, 0 * 16}};
@@ -101,7 +83,13 @@ class CaptureAppView : public View {
OptionsField option_bandwidth{
{5 * 8, 1 * 16},
5,
{BW_OPTIONS}};
{}};
OptionsField option_format{
{18 * 8, 1 * 16},
3,
{{"C16", RecordView::FileType::RawS16},
{"C8", RecordView::FileType::RawS8}}};
RecordView record_view{
{0 * 8, 2 * 16, 30 * 8, 1 * 16},
@@ -111,7 +99,7 @@ class CaptureAppView : public View {
16384,
3};
spectrum::WaterfallWidget waterfall{};
spectrum::WaterfallView waterfall{};
};
} /* namespace ui */
+2 -14
View File
@@ -110,17 +110,9 @@ ERTAppView::ERTAppView(NavigationView&) {
&recent_entries_view,
});
// load app settings
auto rc = settings.load("rx_ert", &app_settings);
if (rc == SETTINGS_OK) {
field_lna.set_value(app_settings.lna);
field_vga.set_value(app_settings.vga);
field_rf_amp.set_value(app_settings.rx_amp);
}
if (!settings_.loaded())
receiver_model.set_target_frequency(initial_target_frequency);
receiver_model.set_tuning_frequency(initial_target_frequency);
receiver_model.set_sampling_rate(sampling_rate);
receiver_model.set_baseband_bandwidth(baseband_bandwidth);
receiver_model.enable();
logger = std::make_unique<ERTLogger>();
@@ -130,11 +122,7 @@ ERTAppView::ERTAppView(NavigationView&) {
}
ERTAppView::~ERTAppView() {
// save app settings
settings.save("rx_ert", &app_settings);
receiver_model.disable();
baseband::shutdown();
}
+8 -7
View File
@@ -29,6 +29,7 @@
#include "event_m0.hpp"
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "log_file.hpp"
#include "ert_packet.hpp"
@@ -108,10 +109,6 @@ using ERTRecentEntriesView = RecentEntriesView<ERTRecentEntries>;
class ERTAppView : public View {
public:
static constexpr uint32_t initial_target_frequency = 911600000;
static constexpr uint32_t sampling_rate = 4194304;
static constexpr uint32_t baseband_bandwidth = 2500000;
ERTAppView(NavigationView& nav);
~ERTAppView();
@@ -126,12 +123,16 @@ class ERTAppView : public View {
std::string title() const override { return "ERT RX"; };
private:
static constexpr uint32_t initial_target_frequency = 911600000;
ERTRecentEntries recent{};
std::unique_ptr<ERTLogger> logger{};
// app save settings
std::app_settings settings{};
std::app_settings::AppSettings app_settings{};
RxRadioState radio_state_{
2500000 /* bandwidth */,
4194304 /* sampling rate */};
app_settings::SettingsManager settings_{
"rx_ert", app_settings::Mode::RX};
const RecentEntriesColumns columns{{
{"ID", 10},
+33 -72
View File
@@ -26,13 +26,15 @@
#include "ui_fileman.hpp"
#include "io_file.hpp"
#include "metadata_file.hpp"
#include "utility.hpp"
#include "baseband_api.hpp"
#include "portapack.hpp"
#include "cpld_update.hpp"
#include "portapack_persistent_memory.hpp"
using namespace portapack;
namespace fs = std::filesystem;
namespace ui {
@@ -40,51 +42,36 @@ void GpsSimAppView::set_ready() {
ready_signal = true;
}
void GpsSimAppView::on_file_changed(std::filesystem::path new_file_path) {
File data_file, info_file;
char file_data[257];
void GpsSimAppView::on_file_changed(const fs::path& new_file_path) {
file_path = fs::path(u"/") + new_file_path;
File::Size file_size{};
// Get file size
auto data_open_error = data_file.open("/" + new_file_path.string());
if (data_open_error.is_valid()) {
file_error();
return;
}
file_path = new_file_path;
// Get original record frequency if available
std::filesystem::path info_file_path = file_path;
info_file_path.replace_extension(u".TXT");
sample_rate = 500000;
auto info_open_error = info_file.open("/" + info_file_path.string());
if (!info_open_error.is_valid()) {
memset(file_data, 0, 257);
auto read_size = info_file.read(file_data, 256);
if (!read_size.is_error()) {
auto pos1 = strstr(file_data, "center_frequency=");
if (pos1) {
pos1 += 17;
field_frequency.set_value(strtoll(pos1, nullptr, 10));
}
auto pos2 = strstr(file_data, "sample_rate=");
if (pos2) {
pos2 += 12;
sample_rate = strtoll(pos2, nullptr, 10);
}
{ // Get the size of the data file.
File data_file;
auto error = data_file.open(file_path);
if (error) {
file_error();
return;
}
file_size = data_file.size();
}
text_sample_rate.set(unit_auto_scale(sample_rate, 3, 1) + "Hz");
// Get original record frequency if available.
auto metadata_path = get_metadata_path(file_path);
auto metadata = read_metadata_file(metadata_path);
auto file_size = data_file.size();
auto duration = (file_size * 1000) / (1 * 2 * sample_rate);
if (metadata) {
field_frequency.set_value(metadata->center_frequency);
transmitter_model.set_sampling_rate(metadata->sample_rate);
}
progressbar.set_max(file_size / 1024);
text_filename.set(file_path.filename().string().substr(0, 12));
// UI Fixup.
text_sample_rate.set(unit_auto_scale(transmitter_model.sampling_rate(), 3, 1) + "Hz");
progressbar.set_max(file_size);
text_filename.set(truncate(file_path.filename().string(), 12));
auto duration = ms_duration(file_size, transmitter_model.sampling_rate(), 2);
text_duration.set(to_string_time_ms(duration));
button_play.focus();
@@ -129,7 +116,6 @@ void GpsSimAppView::start() {
if (reader) {
button_play.set_bitmap(&bitmap_stop);
baseband::set_sample_rate(sample_rate);
replay_thread = std::make_unique<ReplayThread>(
std::move(reader),
@@ -141,8 +127,6 @@ void GpsSimAppView::start() {
});
}
transmitter_model.set_sampling_rate(sample_rate);
transmitter_model.set_baseband_bandwidth(baseband_bandwidth);
transmitter_model.enable();
}
@@ -183,25 +167,16 @@ GpsSimAppView::GpsSimAppView(
&text_duration,
&progressbar,
&field_frequency,
&tx_view, // now it handles previous rfgain , rfamp.
&tx_view, // now it handles previous rfgain, rfamp.
&check_loop,
&button_play,
&waterfall,
});
field_frequency.set_value(target_frequency());
field_frequency.set_step(receiver_model.frequency_step());
field_frequency.on_change = [this](rf::Frequency f) {
this->on_target_frequency_changed(f);
};
field_frequency.on_edit = [this, &nav]() {
// TODO: Provide separate modal method/scheme?
auto new_view = nav.push<FrequencyKeypadView>(this->target_frequency());
new_view->on_changed = [this](rf::Frequency f) {
this->on_target_frequency_changed(f);
this->field_frequency.set_value(f);
};
};
if (!settings_.loaded()) {
field_frequency.set_value(initial_target_frequency);
transmitter_model.set_sampling_rate(2600000);
}
field_frequency.set_step(5000);
@@ -219,8 +194,7 @@ GpsSimAppView::GpsSimAppView(
GpsSimAppView::~GpsSimAppView() {
transmitter_model.disable();
hackrf::cpld::load_sram_no_verify(); // to leave all RX ok, without ghost signal problem at the exit .
baseband::shutdown(); // better this function at the end, not load_sram() that sometimes produces hang up.
baseband::shutdown();
}
void GpsSimAppView::on_hide() {
@@ -239,17 +213,4 @@ void GpsSimAppView::set_parent_rect(const Rect new_parent_rect) {
waterfall.set_parent_rect(waterfall_rect);
}
void GpsSimAppView::on_target_frequency_changed(rf::Frequency f) {
set_target_frequency(f);
}
void GpsSimAppView::set_target_frequency(const rf::Frequency new_value) {
persistent_memory::set_tuned_frequency(new_value);
;
}
rf::Frequency GpsSimAppView::target_frequency() const {
return persistent_memory::tuned_frequency();
}
} /* namespace ui */
+16 -10
View File
@@ -27,15 +27,18 @@
#define SHORT_UI true
#define NORMAL_UI false
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "ui_widget.hpp"
#include "ui_navigation.hpp"
#include "ui_receiver.hpp"
#include "ui_freq_field.hpp"
#include "replay_thread.hpp"
#include "ui_spectrum.hpp"
#include "ui_transmitter.hpp"
#include <string>
#include <memory>
#include "ui_transmitter.hpp"
namespace ui {
@@ -51,24 +54,27 @@ class GpsSimAppView : public View {
std::string title() const override { return "GPS Sim TX"; };
private:
static constexpr uint32_t initial_target_frequency = 1575420000;
NavigationView& nav_;
RxRadioState radio_state_{
3000000 /* bandwidth */,
500000 /* sampling rate */
};
app_settings::SettingsManager settings_{
"tx_gps", app_settings::Mode::TX};
static constexpr ui::Dim header_height = 3 * 16;
uint32_t sample_rate = 0;
int32_t tx_gain{47};
bool rf_amp{true}; // aux private var to store temporal, same as Replay App rf_amp user selection.
static constexpr uint32_t baseband_bandwidth = 3000000; // filter bandwidth
const size_t read_size{16384};
const size_t buffer_count{3};
void on_file_changed(std::filesystem::path new_file_path);
void on_target_frequency_changed(rf::Frequency f);
void on_file_changed(const std::filesystem::path& new_file_path);
void on_tx_progress(const uint32_t progress);
void set_target_frequency(const rf::Frequency new_value);
rf::Frequency target_frequency() const;
void toggle();
void start();
void stop(const bool do_loop);
@@ -98,9 +104,9 @@ class GpsSimAppView : public View {
ProgressBar progressbar{
{18 * 8, 1 * 16, 12 * 8, 16}};
FrequencyField field_frequency{
TxFrequencyField field_frequency{
{0 * 8, 2 * 16},
};
nav_};
TransmitterView2 tx_view{
// new handling of NumberField field_rfgain, NumberField field_rfamp
@@ -119,7 +125,7 @@ class GpsSimAppView : public View {
Color::green(),
Color::black()};
spectrum::WaterfallWidget waterfall{};
spectrum::WaterfallView waterfall{};
MessageHandlerRegistration message_handler_replay_thread_error{
Message::ID::ReplayThreadDone,
+5 -21
View File
@@ -27,7 +27,6 @@
#include "lge_app.hpp"
#include "baseband_api.hpp"
#include "cpld_update.hpp"
#include "ui_textentry.hpp"
#include "string_format.hpp"
@@ -44,13 +43,8 @@ void LGEView::focus() {
}
LGEView::~LGEView() {
// save app settings
app_settings.tx_frequency = transmitter_model.tuning_frequency();
settings.save("tx_lge", &app_settings);
transmitter_model.disable();
hackrf::cpld::load_sram_no_verify(); // to leave all RX ok, without ghost signal problem at the exit .
baseband::shutdown(); // better this function at the end, not load_sram() that sometimes produces hang up.
baseband::shutdown();
}
void LGEView::generate_lge_frame(const uint8_t command, const uint16_t address_a, const uint16_t address_b, std::vector<uint8_t>& data) {
@@ -243,12 +237,11 @@ void LGEView::generate_frame_collier() {
void LGEView::start_tx() {
if (tx_mode == ALL) {
transmitter_model.set_tuning_frequency(channels[channel_index]);
transmitter_model.set_target_frequency(channels[channel_index]);
tx_view.on_show(); // Refresh tuning frequency display
tx_view.set_dirty();
}
transmitter_model.set_sampling_rate(2280000);
transmitter_model.set_baseband_bandwidth(1750000);
transmitter_model.enable();
chThdSleep(100);
@@ -306,15 +299,6 @@ LGEView::LGEView(NavigationView& nav) {
&console,
&tx_view});
// load app settings
auto rc = settings.load("tx_lge", &app_settings);
if (rc == SETTINGS_OK) {
transmitter_model.set_rf_amp(app_settings.tx_amp);
transmitter_model.set_channel_bandwidth(app_settings.channel_bandwidth);
transmitter_model.set_tuning_frequency(app_settings.tx_frequency);
transmitter_model.set_tx_gain(app_settings.tx_gain);
}
field_room.set_value(1);
field_team.set_value(1);
field_player.set_value(1);
@@ -333,9 +317,9 @@ LGEView::LGEView(NavigationView& nav) {
};
tx_view.on_edit_frequency = [this, &nav]() {
auto new_view = nav.push<FrequencyKeypadView>(receiver_model.tuning_frequency());
auto new_view = nav.push<FrequencyKeypadView>(transmitter_model.target_frequency());
new_view->on_changed = [this](rf::Frequency f) {
receiver_model.set_tuning_frequency(f);
transmitter_model.set_target_frequency(f);
};
};
+7 -3
View File
@@ -30,6 +30,7 @@
#include "transmitter_model.hpp"
#include "portapack.hpp"
#include "app_settings.hpp"
#include "radio_state.hpp"
namespace ui {
@@ -49,9 +50,12 @@ class LGEView : public View {
ALL
};
// app save settings
std::app_settings settings{};
std::app_settings::AppSettings app_settings{};
TxRadioState radio_state_{
1750000 /* bandwidth */,
2280000 /* sampling rate */
};
app_settings::SettingsManager settings_{
"tx_lge", app_settings::Mode::TX};
tx_modes tx_mode = IDLE;
+21 -63
View File
@@ -51,14 +51,8 @@ void POCSAGLogger::log_decoded(
namespace ui {
void POCSAGAppView::update_freq(rf::Frequency f) {
set_target_frequency(f);
portapack::persistent_memory::set_tuned_frequency(f); // Maybe not ?
}
POCSAGAppView::POCSAGAppView(NavigationView& nav) {
uint32_t ignore_address;
POCSAGAppView::POCSAGAppView(NavigationView& nav)
: nav_{nav} {
baseband::run_image(portapack::spi_flash::image_tag_pocsag);
add_children({&rssi,
@@ -74,44 +68,16 @@ POCSAGAppView::POCSAGAppView(NavigationView& nav) {
&sym_ignore,
&console});
// Set on_change before initialising the field
field_frequency.on_change = [this](rf::Frequency f) {
update_freq(f);
};
if (!settings_.loaded())
field_frequency.set_value(initial_target_frequency);
// load app settings TODO: needed?
auto rc = settings.load("rx_pocsag", &app_settings);
if (rc == SETTINGS_OK) {
field_lna.set_value(app_settings.lna);
field_vga.set_value(app_settings.vga);
field_rf_amp.set_value(app_settings.rx_amp);
field_frequency.set_value(app_settings.rx_frequency);
} else {
field_lna.set_value(receiver_model.lna());
field_vga.set_value(receiver_model.vga());
field_rf_amp.set_value(receiver_model.rf_amp());
field_frequency.set_value(receiver_model.tuning_frequency());
}
receiver_model.set_modulation(ReceiverModel::Mode::NarrowbandFMAudio);
receiver_model.set_sampling_rate(3072000);
receiver_model.set_baseband_bandwidth(1750000);
receiver_model.enable();
field_frequency.set_step(receiver_model.frequency_step());
field_frequency.on_edit = [this, &nav]() {
// TODO: Provide separate modal method/scheme?
auto new_view = nav.push<FrequencyKeypadView>(receiver_model.tuning_frequency());
new_view->on_changed = [this](rf::Frequency f) {
update_freq(f);
field_frequency.set_value(f);
};
};
// TODO app setting instead?
// TODO: app setting instead?
uint32_t ignore_address;
ignore_address = persistent_memory::pocsag_ignore_address();
// TODO is this common enough for a helper?
// TODO: is this common enough for a helper?
for (size_t c = 0; c < 7; c++) {
sym_ignore.set_sym(6 - c, ignore_address % 10);
ignore_address /= 10;
@@ -122,16 +88,15 @@ POCSAGAppView::POCSAGAppView(NavigationView& nav) {
logger->append(LOG_ROOT_DIR "/POCSAG.TXT");
audio::output::start();
audio::output::unmute();
baseband::set_pocsag();
}
POCSAGAppView::~POCSAGAppView() {
// save app settings
app_settings.rx_frequency = field_frequency.value();
settings.save("rx_pocsag", &app_settings);
void POCSAGAppView::focus() {
field_frequency.focus();
}
POCSAGAppView::~POCSAGAppView() {
audio::output::stop();
// Save ignored address
@@ -177,9 +142,10 @@ void POCSAGAppView::on_packet(const POCSAGPacketMessage* message) {
console.write(console_info);
if (logger && logging()) {
logger->log_decoded(message->packet, to_string_dec_uint(pocsag_state.address) +
" F" + to_string_dec_uint(pocsag_state.function) +
" Address only");
logger->log_decoded(
message->packet, to_string_dec_uint(pocsag_state.address) +
" F" + to_string_dec_uint(pocsag_state.function) +
" Address only");
}
last_address = pocsag_state.address;
@@ -196,25 +162,17 @@ void POCSAGAppView::on_packet(const POCSAGPacketMessage* message) {
}
if (logger && logging())
logger->log_decoded(message->packet, to_string_dec_uint(pocsag_state.address) +
" F" + to_string_dec_uint(pocsag_state.function) +
" Alpha: " + pocsag_state.output);
logger->log_decoded(
message->packet, to_string_dec_uint(pocsag_state.address) +
" F" + to_string_dec_uint(pocsag_state.function) +
" Alpha: " + pocsag_state.output);
}
}
// TODO: make setting.
// Log raw data whatever it contains
if (logger && logging())
logger->log_raw_data(message->packet, target_frequency());
}
void POCSAGAppView::set_target_frequency(const uint32_t new_value) {
target_frequency_ = new_value;
receiver_model.set_tuning_frequency(new_value);
}
uint32_t POCSAGAppView::target_frequency() const {
return target_frequency_;
/*if (logger && logging())
logger->log_raw_data(message->packet, receiver_model.target_frequency());*/
}
} /* namespace ui */
+12 -19
View File
@@ -24,11 +24,13 @@
#define __POCSAG_APP_H__
#include "ui_widget.hpp"
#include "ui_freq_field.hpp"
#include "ui_receiver.hpp"
#include "ui_rssi.hpp"
#include "log_file.hpp"
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "pocsag.hpp"
#include "pocsag_packet.hpp"
@@ -53,16 +55,17 @@ class POCSAGAppView : public View {
~POCSAGAppView();
std::string title() const override { return "POCSAG RX"; };
void focus() override;
private:
static constexpr uint32_t initial_target_frequency = 466175000;
bool logging() const { return check_log.value(); };
bool ignore() const { return check_ignore.value(); };
// app save settings
std::app_settings settings{};
std::app_settings::AppSettings app_settings{};
NavigationView& nav_;
RxRadioState radio_state_{};
app_settings::SettingsManager settings_{
"rx_pocsag", app_settings::Mode::RX};
uint32_t last_address = 0xFFFFFFFF;
pocsag::POCSAGState pocsag_state{};
@@ -74,18 +77,15 @@ class POCSAGAppView : public View {
VGAGainField field_vga{
{18 * 8, 0 * 16}};
RSSI rssi{
{21 * 8, 0, 6 * 8, 4},
};
{21 * 8, 0, 6 * 8, 4}};
Channel channel{
{21 * 8, 5, 6 * 8, 4},
};
{21 * 8, 5, 6 * 8, 4}};
Audio audio{
{21 * 8, 10, 6 * 8, 4},
};
{21 * 8, 10, 6 * 8, 4}};
FrequencyField field_frequency{
RxFrequencyField field_frequency{
{0 * 8, 0 * 8},
};
nav_};
AudioVolumeField field_volume{
{28 * 8, 0 * 16}};
@@ -111,15 +111,8 @@ class POCSAGAppView : public View {
std::unique_ptr<POCSAGLogger> logger{};
uint32_t target_frequency_ = initial_target_frequency;
void update_freq(rf::Frequency f);
void on_packet(const POCSAGPacketMessage* message);
uint32_t target_frequency() const;
void set_target_frequency(const uint32_t new_value);
MessageHandlerRegistration message_handler_packet{
Message::ID::POCSAGPacket,
[this](Message* const p) {
+40 -76
View File
@@ -26,12 +26,16 @@
#include "ui_fileman.hpp"
#include "io_file.hpp"
#include "cpld_update.hpp"
#include "io_convert.hpp"
#include "baseband_api.hpp"
#include "metadata_file.hpp"
#include "portapack.hpp"
#include "portapack_persistent_memory.hpp"
#include "utility.hpp"
using namespace portapack;
namespace fs = std::filesystem;
namespace ui {
@@ -39,51 +43,42 @@ void ReplayAppView::set_ready() {
ready_signal = true;
}
void ReplayAppView::on_file_changed(std::filesystem::path new_file_path) {
File data_file, info_file;
char file_data[257];
void ReplayAppView::on_file_changed(const fs::path& new_file_path) {
file_path = fs::path(u"/") + new_file_path;
File::Size file_size{};
// Get file size
auto data_open_error = data_file.open("/" + new_file_path.string());
if (data_open_error.is_valid()) {
file_error();
return;
}
file_path = new_file_path;
// Get original record frequency if available
std::filesystem::path info_file_path = file_path;
info_file_path.replace_extension(u".TXT");
sample_rate = 500000;
auto info_open_error = info_file.open("/" + info_file_path.string());
if (!info_open_error.is_valid()) {
memset(file_data, 0, 257);
auto read_size = info_file.read(file_data, 256);
if (!read_size.is_error()) {
auto pos1 = strstr(file_data, "center_frequency=");
if (pos1) {
pos1 += 17;
field_frequency.set_value(strtoll(pos1, nullptr, 10));
}
auto pos2 = strstr(file_data, "sample_rate=");
if (pos2) {
pos2 += 12;
sample_rate = strtoll(pos2, nullptr, 10);
}
{ // Get the size of the data file.
File data_file;
auto error = data_file.open(file_path);
if (error) {
file_error();
return;
}
file_size = data_file.size();
}
// Get original record frequency if available.
auto metadata_path = get_metadata_path(file_path);
auto metadata = read_metadata_file(metadata_path);
if (metadata) {
field_frequency.set_value(metadata->center_frequency);
sample_rate = metadata->sample_rate;
} else {
// TODO: This is interesting because it implies that the
// The capture will just be replayed at the freq set on the
// FrequencyField. Is that an intentional behavior?
sample_rate = 500000;
}
// UI Fixup.
text_sample_rate.set(unit_auto_scale(sample_rate, 3, 0) + "Hz");
auto file_size = data_file.size();
auto duration = (file_size * 1000) / (2 * 2 * sample_rate);
progressbar.set_max(file_size);
text_filename.set(file_path.filename().string().substr(0, 12));
text_filename.set(truncate(file_path.filename().string(), 12));
uint8_t sample_size = capture_file_sample_size(current()->path);
auto duration = ms_duration(file_size, sample_rate, sample_size);
text_duration.set(to_string_time_ms(duration));
button_play.focus();
@@ -118,7 +113,7 @@ void ReplayAppView::start() {
std::unique_ptr<stream::Reader> reader;
auto p = std::make_unique<FileReader>();
auto p = std::make_unique<FileConvertReader>();
auto open_error = p->open(file_path);
if (open_error.is_valid()) {
file_error();
@@ -188,35 +183,19 @@ ReplayAppView::ReplayAppView(
&text_duration,
&progressbar,
&field_frequency,
&tx_view, // now it handles previous rfgain , rfamp.
&tx_view, // now it handles previous rfgain, rfamp.
&check_loop,
&button_play,
&waterfall,
});
field_frequency.set_value(target_frequency());
field_frequency.set_step(receiver_model.frequency_step());
field_frequency.on_change = [this](rf::Frequency f) {
this->on_target_frequency_changed(f);
};
field_frequency.on_edit = [this, &nav]() {
// TODO: Provide separate modal method/scheme?
auto new_view = nav.push<FrequencyKeypadView>(this->target_frequency());
new_view->on_changed = [this](rf::Frequency f) {
this->on_target_frequency_changed(f);
this->field_frequency.set_value(f);
};
};
field_frequency.set_step(5000);
button_play.on_select = [this](ImageButton&) {
this->toggle();
};
button_open.on_select = [this, &nav](Button&) {
auto open_view = nav.push<FileLoadView>(".C16");
open_view->on_changed = [this](std::filesystem::path new_file_path) {
auto open_view = nav.push<FileLoadView>(".C*");
open_view->on_changed = [this](fs::path new_file_path) {
on_file_changed(new_file_path);
};
};
@@ -227,9 +206,7 @@ ReplayAppView::~ReplayAppView() {
display.fill_rectangle({0, 0, 240, 320}, Color::black()); // Solving sometimes visible bottom waterfall artifacts, clearing all LCD pixels.
chThdSleepMilliseconds(40); // (that happened sometimes if we interrupt the waterfall play at the beggining of the play around 25% and exit )
hackrf::cpld::load_sram_no_verify(); // to leave all RX reception ok, without "ghost interference signal problem" at the exit .
baseband::shutdown(); // better this function at the end, after load_sram(). If not , sometimes produced hang up (now not , it is ok).
baseband::shutdown();
}
void ReplayAppView::on_hide() {
@@ -247,17 +224,4 @@ void ReplayAppView::set_parent_rect(const Rect new_parent_rect) {
waterfall.set_parent_rect(waterfall_rect);
}
void ReplayAppView::on_target_frequency_changed(rf::Frequency f) {
set_target_frequency(f);
}
void ReplayAppView::set_target_frequency(const rf::Frequency new_value) {
persistent_memory::set_tuned_frequency(new_value);
;
}
rf::Frequency ReplayAppView::target_frequency() const {
return persistent_memory::tuned_frequency();
}
} /* namespace ui */
+12 -9
View File
@@ -26,15 +26,18 @@
#define SHORT_UI true
#define NORMAL_UI false
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "ui_widget.hpp"
#include "ui_navigation.hpp"
#include "ui_receiver.hpp"
#include "ui_freq_field.hpp"
#include "replay_thread.hpp"
#include "ui_spectrum.hpp"
#include "ui_transmitter.hpp"
#include <string>
#include <memory>
#include "ui_transmitter.hpp"
namespace ui {
@@ -51,6 +54,9 @@ class ReplayAppView : public View {
private:
NavigationView& nav_;
TxRadioState radio_state_{};
app_settings::SettingsManager settings_{
"tx_replay", app_settings::Mode::TX};
static constexpr ui::Dim header_height = 3 * 16;
@@ -61,13 +67,9 @@ class ReplayAppView : public View {
const size_t read_size{16384};
const size_t buffer_count{3};
void on_file_changed(std::filesystem::path new_file_path);
void on_target_frequency_changed(rf::Frequency f);
void on_file_changed(const std::filesystem::path& new_file_path);
void on_tx_progress(const uint32_t progress);
void set_target_frequency(const rf::Frequency new_value);
rf::Frequency target_frequency() const;
void toggle();
void start();
void stop(const bool do_loop);
@@ -97,9 +99,10 @@ class ReplayAppView : public View {
ProgressBar progressbar{
{18 * 8, 1 * 16, 12 * 8, 16}};
FrequencyField field_frequency{
// TODO: Does this need to be a frequency field at all?
TxFrequencyField field_frequency{
{0 * 8, 2 * 16},
};
nav_};
TransmitterView2 tx_view{
// new handling of NumberField field_rfgain, NumberField field_rfamp
@@ -118,7 +121,7 @@ class ReplayAppView : public View {
Color::green(),
Color::black()};
spectrum::WaterfallWidget waterfall{};
spectrum::WaterfallView waterfall{};
MessageHandlerRegistration message_handler_replay_thread_error{
Message::ID::ReplayThreadDone,
+5 -20
View File
@@ -25,7 +25,7 @@
#include "soundboard_app.hpp"
#include "string_format.hpp"
#include "tonesets.hpp"
#include "cpld_update.hpp"
#include "ui_tone_key.hpp"
using namespace tonekey;
using namespace portapack;
@@ -106,6 +106,7 @@ void SoundBoardView::start_tx(const uint32_t id) {
EventDispatcher::send_message(message);
});
// TODO: Delete all this and use tx model.
baseband::set_audiotx_config(
1536000 / 20, // Update vu-meter at 20Hz
transmitter_model.channel_bandwidth(),
@@ -119,8 +120,6 @@ void SoundBoardView::start_tx(const uint32_t id) {
);
baseband::set_sample_rate(sample_rate);
transmitter_model.set_sampling_rate(1536000);
transmitter_model.set_baseband_bandwidth(1750000);
transmitter_model.enable();
tx_view.set_transmitting(true);
@@ -235,15 +234,6 @@ SoundBoardView::SoundBoardView(
&button_next_page,
&tx_view});
// load app settings
auto rc = settings.load("tx_soundboard", &app_settings);
if (rc == SETTINGS_OK) {
transmitter_model.set_rf_amp(app_settings.tx_amp);
transmitter_model.set_channel_bandwidth(app_settings.channel_bandwidth);
transmitter_model.set_tuning_frequency(app_settings.tx_frequency);
transmitter_model.set_tx_gain(app_settings.tx_gain);
}
refresh_list();
button_next_page.on_select = [this](Button&) {
@@ -266,9 +256,9 @@ SoundBoardView::SoundBoardView(
check_random.set_value(false);
tx_view.on_edit_frequency = [this, &nav]() {
auto new_view = nav.push<FrequencyKeypadView>(receiver_model.tuning_frequency());
auto new_view = nav.push<FrequencyKeypadView>(transmitter_model.target_frequency());
new_view->on_changed = [this](rf::Frequency f) {
transmitter_model.set_tuning_frequency(f);
transmitter_model.set_target_frequency(f);
};
};
@@ -283,14 +273,9 @@ SoundBoardView::SoundBoardView(
}
SoundBoardView::~SoundBoardView() {
// save app settings
app_settings.tx_frequency = transmitter_model.tuning_frequency();
settings.save("tx_soundboard", &app_settings);
stop();
transmitter_model.disable();
hackrf::cpld::load_sram_no_verify(); // to leave all RX ok, without ghost signal problem at the exit.
baseband::shutdown(); // better this function at the end, not load_sram() that sometimes produces hang up.
baseband::shutdown();
}
} // namespace ui
+8 -5
View File
@@ -29,8 +29,8 @@
#include "baseband_api.hpp"
#include "lfsr_random.hpp"
#include "io_wave.hpp"
#include "tone_key.hpp"
#include "app_settings.hpp"
#include "radio_state.hpp"
namespace ui {
@@ -49,11 +49,14 @@ class SoundBoardView : public View {
std::string title() const override { return "Soundbrd TX"; };
private:
NavigationView& nav_;
TxRadioState radio_state_{
1750000 /* bandwidth */,
1536000 /* sampling rate */
};
app_settings::SettingsManager settings_{
"tx_soundboard", app_settings::Mode::TX};
// app save settings
std::app_settings settings{};
std::app_settings::AppSettings app_settings{};
NavigationView& nav_;
enum tx_modes {
NORMAL = 0,
@@ -32,7 +32,7 @@ class SpectrumAnalysisModel {
namespace ui {
class SpectrumAnalysisView : public spectrum::WaterfallWidget {
class SpectrumAnalysisView : public spectrum::WaterfallView {
public:
private:
SpectrumAnalysisModel model;
+7 -38
View File
@@ -78,9 +78,9 @@ void TPMSLogger::on_packet(const tpms::Packet& packet, const uint32_t target_fre
const auto hex_formatted = packet.symbols_formatted();
// TODO: function doesn't take uint64_t, so when >= 1<<32, weirdness will ensue!
const auto tuning_frequency_str = to_string_dec_uint(target_frequency, 10);
const auto target_frequency_str = to_string_dec_uint(target_frequency, 10);
std::string entry = tuning_frequency_str + " " + tpms::format::signal_type(packet.signal_type()) + " " + hex_formatted.data + "/" + hex_formatted.errors;
std::string entry = target_frequency_str + " " + tpms::format::signal_type(packet.signal_type()) + " " + hex_formatted.data + "/" + hex_formatted.errors;
log_file.write_entry(packet.received_at(), entry);
}
@@ -157,25 +157,15 @@ TPMSAppView::TPMSAppView(NavigationView&) {
&field_vga,
&recent_entries_view});
// load app settings
auto rc = settings.load("rx_tpms", &app_settings);
if (rc == SETTINGS_OK) {
field_lna.set_value(app_settings.lna);
field_vga.set_value(app_settings.vga);
field_rf_amp.set_value(app_settings.rx_amp);
options_band.set_by_value(app_settings.rx_frequency);
} else
options_band.set_by_value(receiver_model.tuning_frequency());
if (!settings_.loaded())
receiver_model.set_target_frequency(initial_target_frequency);
receiver_model.set_tuning_frequency(tuning_frequency());
receiver_model.set_sampling_rate(sampling_rate);
receiver_model.set_baseband_bandwidth(baseband_bandwidth);
receiver_model.enable();
options_band.on_change = [this](size_t, OptionsField::value_t v) {
this->on_band_changed(v);
receiver_model.set_target_frequency(v);
};
options_band.set_by_value(target_frequency());
options_band.set_by_value(receiver_model.target_frequency());
options_pressure.on_change = [this](size_t, int32_t i) {
tpms::format::use_kpa = !i;
@@ -198,10 +188,6 @@ TPMSAppView::TPMSAppView(NavigationView&) {
}
TPMSAppView::~TPMSAppView() {
// save app settings
app_settings.rx_frequency = target_frequency_;
settings.save("rx_tpms", &app_settings);
receiver_model.disable();
baseband::shutdown();
}
@@ -224,7 +210,7 @@ void TPMSAppView::set_parent_rect(const Rect new_parent_rect) {
void TPMSAppView::on_packet(const tpms::Packet& packet) {
if (logger) {
logger->on_packet(packet, target_frequency());
logger->on_packet(packet, receiver_model.target_frequency());
}
const auto reading_opt = packet.reading();
@@ -241,21 +227,4 @@ void TPMSAppView::on_show_list() {
recent_entries_view.focus();
}
void TPMSAppView::on_band_changed(const uint32_t new_band_frequency) {
set_target_frequency(new_band_frequency);
}
void TPMSAppView::set_target_frequency(const uint32_t new_value) {
target_frequency_ = new_value;
receiver_model.set_tuning_frequency(tuning_frequency());
}
uint32_t TPMSAppView::target_frequency() const {
return target_frequency_;
}
uint32_t TPMSAppView::tuning_frequency() const {
return target_frequency() - (sampling_rate / 4);
}
} /* namespace ui */
+6 -18
View File
@@ -28,6 +28,7 @@
#include "ui_rssi.hpp"
#include "ui_channel.hpp"
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "event_m0.hpp"
#include "log_file.hpp"
@@ -38,10 +39,6 @@
namespace std {
constexpr bool operator==(const tpms::TransponderID& lhs, const tpms::TransponderID& rhs) {
return (lhs.value() == rhs.value());
}
} /* namespace std */
struct TPMSRecentEntry {
@@ -106,12 +103,12 @@ class TPMSAppView : public View {
private:
static constexpr uint32_t initial_target_frequency = 315000000;
static constexpr uint32_t sampling_rate = 2457600;
static constexpr uint32_t baseband_bandwidth = 1750000;
// app save settings
std::app_settings settings{};
std::app_settings::AppSettings app_settings{};
RxRadioState radio_state_{
1750000 /* bandwidth */,
2457600 /* sampling rate */};
app_settings::SettingsManager settings_{
"rx_tpms", app_settings::Mode::RX};
MessageHandlerRegistration message_handler_packet{
Message::ID::TPMSPacket,
@@ -175,18 +172,9 @@ class TPMSAppView : public View {
}};
TPMSRecentEntriesView recent_entries_view{columns, recent};
uint32_t target_frequency_ = initial_target_frequency;
void on_packet(const tpms::Packet& packet);
void on_show_list();
void update_view();
void on_band_changed(const uint32_t new_band_frequency);
uint32_t target_frequency() const;
void set_target_frequency(const uint32_t new_value);
uint32_t tuning_frequency() const;
};
} /* namespace ui */
+1 -2
View File
@@ -47,7 +47,7 @@ using namespace portapack;
namespace ui {
void AboutView::on_show() {
transmitter_model.set_tuning_frequency(1337000000); // TODO: Change
transmitter_model.set_target_frequency(1337000000); // TODO: Change
transmitter_model.set_baseband_configuration({
.mode = 0,
.sampling_rate = 1536000,
@@ -56,7 +56,6 @@ void AboutView::on_show() {
transmitter_model.set_rf_amp(true);
transmitter_model.set_lna(40);
transmitter_model.set_vga(40);
transmitter_model.set_baseband_bandwidth(1750000);
transmitter_model.enable();
baseband::set_audiotx_data(32, 50, false, 0);
+3 -25
View File
@@ -288,13 +288,6 @@ void ADSBRxView::focus() {
}
ADSBRxView::~ADSBRxView() {
receiver_model.set_tuning_frequency(prevFreq); // Restore previous frequency on exit
// save app settings
settings.save("rx_adsb", &app_settings);
// TODO: once all apps keep there own settin previous frequency logic can be removed
receiver_model.set_tuning_frequency(prevFreq);
rtc_time::signal_tick_second -= signal_token_tick_second;
receiver_model.disable();
baseband::shutdown();
@@ -475,7 +468,8 @@ void ADSBRxView::updateDetailsAndMap(int ageStep) {
details_view->update(entry);
}
// Store if the view is present and the list isn't full
else if (otherMarkersCanBeSent && (markerStored != MARKER_LIST_FULL) && entry.pos.valid && (entry.age_state <= 2)) {
// 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;
@@ -502,17 +496,6 @@ ADSBRxView::ADSBRxView(NavigationView& nav) {
&rssi,
&recent_entries_view});
// load app settings
auto rc = settings.load("rx_adsb", &app_settings);
if (rc == SETTINGS_OK) {
field_lna.set_value(app_settings.lna);
field_vga.set_value(app_settings.vga);
field_rf_amp.set_value(app_settings.rx_amp);
} else {
field_lna.set_value(40);
field_vga.set_value(40);
}
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();
@@ -528,14 +511,9 @@ ADSBRxView::ADSBRxView(NavigationView& nav) {
on_tick_second();
};
prevFreq = receiver_model.tuning_frequency(); // Store previous frequency on creation
baseband::set_adsb();
receiver_model.set_tuning_frequency(1090000000);
receiver_model.set_modulation(ReceiverModel::Mode::SpectrumAnalysis);
receiver_model.set_sampling_rate(2000000);
receiver_model.set_baseband_bandwidth(2500000);
receiver_model.set_target_frequency(1'090'000'000);
receiver_model.enable();
}
+8 -4
View File
@@ -33,6 +33,7 @@
#include "adsb.hpp"
#include "message.hpp"
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "crc.hpp"
using namespace adsb;
@@ -340,7 +341,13 @@ class ADSBRxView : public View {
void sort_entries_by_state();
private:
rf::Frequency prevFreq = {0};
RxRadioState radio_state_{
2500000 /* bandwidth */,
2000000 /* sampling rate */,
ReceiverModel::Mode::SpectrumAnalysis};
app_settings::SettingsManager settings_{
"rx_adsb", app_settings::Mode::RX};
std::unique_ptr<ADSBLogger> logger{};
void on_frame(const ADSBFrameMessage* message);
void on_tick_second();
@@ -350,9 +357,6 @@ class ADSBRxView : public View {
#define MARKER_UPDATE_SECONDS (5)
int ticksSinceMarkerRefresh{MARKER_UPDATE_SECONDS - 1};
// app save settings
std::app_settings settings{};
std::app_settings::AppSettings app_settings{};
const RecentEntriesColumns columns{{{"ICAO/Call", 9},
{"Lvl", 3},
+3 -19
View File
@@ -26,7 +26,6 @@
#include "manchester.hpp"
#include "string_format.hpp"
#include "portapack.hpp"
#include "cpld_update.hpp"
#include "baseband_api.hpp"
#include <cstring>
@@ -273,13 +272,8 @@ void ADSBTxView::focus() {
}
ADSBTxView::~ADSBTxView() {
// save app settings
app_settings.tx_frequency = transmitter_model.tuning_frequency();
settings.save("tx_adsb", &app_settings);
transmitter_model.disable();
hackrf::cpld::load_sram_no_verify(); // to leave all RX ok, withouth ghost signal problem at the exit .
baseband::shutdown(); // better this function at the end, not load_sram() that sometimes produces hang up.
baseband::shutdown();
}
void ADSBTxView::generate_frames() {
@@ -303,8 +297,6 @@ void ADSBTxView::generate_frames() {
void ADSBTxView::start_tx() {
generate_frames();
transmitter_model.set_sampling_rate(4000000U);
transmitter_model.set_baseband_bandwidth(10000000);
transmitter_model.enable();
baseband::set_adsb();
@@ -327,18 +319,10 @@ ADSBTxView::ADSBTxView(
&text_frame,
&tx_view});
// load app settings
auto rc = settings.load("tx_adsb", &app_settings);
if (rc == SETTINGS_OK) {
transmitter_model.set_tuning_frequency(app_settings.tx_frequency);
transmitter_model.set_rf_amp(app_settings.tx_amp);
transmitter_model.set_tx_gain(app_settings.tx_gain);
}
tx_view.on_edit_frequency = [this, &nav]() {
auto new_view = nav.push<FrequencyKeypadView>(receiver_model.tuning_frequency());
auto new_view = nav.push<FrequencyKeypadView>(transmitter_model.target_frequency());
new_view->on_changed = [this](rf::Frequency f) {
transmitter_model.set_tuning_frequency(f);
transmitter_model.set_target_frequency(f);
};
};
+7 -3
View File
@@ -29,6 +29,7 @@
#include "message.hpp"
#include "transmitter_model.hpp"
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "portapack.hpp"
using namespace adsb;
@@ -201,9 +202,12 @@ class ADSBTxView : public View {
-1
};*/
// app save settings
std::app_settings settings{};
std::app_settings::AppSettings app_settings{};
TxRadioState radio_state_{
10000000 /* bandwidth */,
4000000 /* sampling rate */
};
app_settings::SettingsManager settings_{
"tx_adsb", app_settings::Mode::TX};
// tx_modes tx_mode = IDLE;
NavigationView& nav_;
+4 -33
View File
@@ -43,11 +43,8 @@ void AFSKRxView::focus() {
field_frequency.focus();
}
void AFSKRxView::update_freq(rf::Frequency f) {
receiver_model.set_tuning_frequency(f);
}
AFSKRxView::AFSKRxView(NavigationView& nav) {
AFSKRxView::AFSKRxView(NavigationView& nav)
: nav_{nav} {
baseband::run_image(portapack::spi_flash::image_tag_afsk_rx);
add_children({&rssi,
@@ -62,16 +59,8 @@ AFSKRxView::AFSKRxView(NavigationView& nav) {
&button_modem_setup,
&console});
// load app settings
auto rc = settings.load("rx_afsk", &app_settings);
if (rc == SETTINGS_OK) {
field_lna.set_value(app_settings.lna);
field_vga.set_value(app_settings.vga);
field_rf_amp.set_value(app_settings.rx_amp);
}
// Auto-configure modem for LCR RX (will be removed later)
update_freq(467225500); // 462713300
// Auto-configure modem for LCR RX (TODO remove)
field_frequency.set_value(467225500);
auto def_bell202 = &modem_defs[0];
persistent_memory::set_modem_baudrate(def_bell202->baudrate);
serial_format_t serial_format;
@@ -81,18 +70,7 @@ AFSKRxView::AFSKRxView(NavigationView& nav) {
serial_format.bit_order = LSB_FIRST;
persistent_memory::set_serial_format(serial_format);
field_frequency.set_value(receiver_model.tuning_frequency());
field_frequency.set_step(100);
field_frequency.on_change = [this](rf::Frequency f) {
update_freq(f);
};
field_frequency.on_edit = [this, &nav]() {
auto new_view = nav.push<FrequencyKeypadView>(receiver_model.tuning_frequency());
new_view->on_changed = [this](rf::Frequency f) {
update_freq(f);
field_frequency.set_value(f);
};
};
check_log.set_value(logging);
check_log.on_select = [this](Checkbox&, bool v) {
@@ -113,9 +91,6 @@ AFSKRxView::AFSKRxView(NavigationView& nav) {
audio::set_rate(audio::Rate::Hz_24000);
audio::output::start();
receiver_model.set_sampling_rate(3072000);
receiver_model.set_baseband_bandwidth(1750000);
receiver_model.set_modulation(ReceiverModel::Mode::NarrowbandFMAudio);
receiver_model.enable();
}
@@ -167,10 +142,6 @@ void AFSKRxView::on_data(uint32_t value, bool is_data) {
}
AFSKRxView::~AFSKRxView() {
// save app settings
app_settings.rx_frequency = field_frequency.value();
settings.save("rx_afsk", &app_settings);
audio::output::stop();
receiver_model.disable();
baseband::shutdown();
+11 -11
View File
@@ -26,8 +26,10 @@
#include "ui.hpp"
#include "ui_navigation.hpp"
#include "ui_receiver.hpp"
#include "ui_record_view.hpp" // DEBUG
#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"
@@ -57,9 +59,10 @@ class AFSKRxView : public View {
private:
void on_data(uint32_t value, bool is_data);
// app save settings
std::app_settings settings{};
std::app_settings::AppSettings app_settings{};
NavigationView& nav_;
RxRadioState radio_state_{};
app_settings::SettingsManager settings_{
"rx_afsk", app_settings::Mode::RX};
uint8_t console_color{0};
uint32_t prev_value{0};
@@ -73,18 +76,16 @@ class AFSKRxView : public View {
VGAGainField field_vga{
{18 * 8, 0 * 16}};
RSSI rssi{
{21 * 8, 0, 6 * 8, 4},
};
{21 * 8, 0, 6 * 8, 4}};
Channel channel{
{21 * 8, 5, 6 * 8, 4},
};
{21 * 8, 5, 6 * 8, 4}};
AudioVolumeField field_volume{
{28 * 8, 0 * 16}};
FrequencyField field_frequency{
RxFrequencyField field_frequency{
{0 * 8, 0 * 16},
};
nav_};
Checkbox check_log{
{0 * 8, 1 * 16},
@@ -103,7 +104,6 @@ class AFSKRxView : public View {
Console console{
{0, 4 * 16, 240, 240}};
void update_freq(rf::Frequency f);
void on_data_afsk(const AFSKDataMessage& message);
std::unique_ptr<AFSKLogger> logger{};
+1 -31
View File
@@ -73,12 +73,8 @@ void APRSRxView::focus() {
options_region.focus();
}
void APRSRxView::update_freq(rf::Frequency f) {
receiver_model.set_tuning_frequency(f);
}
APRSRxView::APRSRxView(NavigationView& nav, Rect parent_rect)
: View(parent_rect) {
: View(parent_rect), nav_{nav} {
baseband::run_image(portapack::spi_flash::image_tag_aprs_rx);
add_children({&rssi,
@@ -92,14 +88,6 @@ APRSRxView::APRSRxView(NavigationView& nav, Rect parent_rect)
&record_view,
&console});
// load app settings
auto rc = settings.load("rx_aprs", &app_settings);
if (rc == SETTINGS_OK) {
field_lna.set_value(app_settings.lna);
field_vga.set_value(app_settings.vga);
field_rf_amp.set_value(app_settings.rx_amp);
}
// DEBUG
record_view.on_error = [&nav](std::string message) {
nav.display_modal("Error", message);
@@ -119,19 +107,7 @@ APRSRxView::APRSRxView(NavigationView& nav, Rect parent_rect)
}
};
field_frequency.set_value(receiver_model.tuning_frequency());
field_frequency.set_step(100);
field_frequency.on_change = [this](rf::Frequency f) {
update_freq(f);
};
field_frequency.on_edit = [this, &nav]() {
auto new_view = nav.push<FrequencyKeypadView>(receiver_model.tuning_frequency());
new_view->on_changed = [this](rf::Frequency f) {
update_freq(f);
field_frequency.set_value(f);
};
};
options_region.set_selected_index(0, true);
logger = std::make_unique<APRSLogger>();
@@ -143,9 +119,6 @@ APRSRxView::APRSRxView(NavigationView& nav, Rect parent_rect)
audio::set_rate(audio::Rate::Hz_24000);
audio::output::start();
receiver_model.set_sampling_rate(3072000);
receiver_model.set_baseband_bandwidth(1750000);
receiver_model.set_modulation(ReceiverModel::Mode::NarrowbandFMAudio);
receiver_model.enable();
}
@@ -211,9 +184,6 @@ void APRSRxView::on_show() {
}
APRSRxView::~APRSRxView() {
// save app settings
settings.save("rx_aprs", &app_settings);
audio::output::stop();
receiver_model.disable();
baseband::shutdown();
+11 -12
View File
@@ -26,9 +26,11 @@
#include "ui.hpp"
#include "ui_navigation.hpp"
#include "ui_receiver.hpp"
#include "ui_record_view.hpp" // DEBUG
#include "ui_freq_field.hpp"
#include "ui_record_view.hpp"
#include "ui_geomap.hpp"
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "recent_entries.hpp"
#include "ui_tabview.hpp"
@@ -187,9 +189,10 @@ class APRSRxView : public View {
void on_data(uint32_t value, bool is_data);
bool reset_console = false;
// app save settings
std::app_settings settings{};
std::app_settings::AppSettings app_settings{};
NavigationView& nav_;
RxRadioState radio_state_{};
app_settings::SettingsManager settings_{
"rx_aprs", app_settings::Mode::RX};
uint8_t console_color{0};
std::string str_log{""};
@@ -201,11 +204,9 @@ class APRSRxView : public View {
VGAGainField field_vga{
{18 * 8, 0 * 16}};
RSSI rssi{
{21 * 8, 0, 6 * 8, 4},
};
{21 * 8, 0, 6 * 8, 4}};
Channel channel{
{21 * 8, 5, 6 * 8, 4},
};
{21 * 8, 5, 6 * 8, 4}};
AudioVolumeField field_volume{
{28 * 8, 0 * 16}};
@@ -218,9 +219,9 @@ class APRSRxView : public View {
{"AUS", 2},
{"NZ ", 3}}};
FrequencyField field_frequency{
RxFrequencyField field_frequency{
{3 * 8, 0 * 16},
};
nav_};
// DEBUG
RecordView record_view{
@@ -234,8 +235,6 @@ class APRSRxView : public View {
Console console{
{0, 2 * 16, 240, 240}};
void update_freq(rf::Frequency f);
std::unique_ptr<APRSLogger> logger{};
};
+3 -20
View File
@@ -26,7 +26,6 @@
#include "aprs.hpp"
#include "string_format.hpp"
#include "portapack.hpp"
#include "cpld_update.hpp"
#include "baseband_api.hpp"
#include "portapack_shared_memory.hpp"
#include "portapack_persistent_memory.hpp"
@@ -44,13 +43,8 @@ void APRSTXView::focus() {
}
APRSTXView::~APRSTXView() {
// save app settings
app_settings.tx_frequency = transmitter_model.tuning_frequency();
settings.save("tx_aprs", &app_settings);
transmitter_model.disable();
hackrf::cpld::load_sram_no_verify(); // to leave all RX ok, without ghost signal problem at the exit.
baseband::shutdown(); // better this function at the end, not load_sram() that sometimes produces hang up.
baseband::shutdown();
}
void APRSTXView::start_tx() {
@@ -62,9 +56,6 @@ void APRSTXView::start_tx() {
// uint8_t * bb_data_ptr = shared_memory.bb_data.data;
// text_payload.set(to_string_hex_array(bb_data_ptr + 56, 15));
transmitter_model.set_tuning_frequency(persistent_memory::tuned_frequency());
transmitter_model.set_sampling_rate(AFSK_TX_SAMPLERATE);
transmitter_model.set_baseband_bandwidth(1750000);
transmitter_model.enable();
baseband::set_afsk_data(
@@ -97,14 +88,6 @@ APRSTXView::APRSTXView(NavigationView& nav) {
&button_set,
&tx_view});
// load app settings
auto rc = settings.load("tx_aprs", &app_settings);
if (rc == SETTINGS_OK) {
transmitter_model.set_rf_amp(app_settings.tx_amp);
transmitter_model.set_tuning_frequency(app_settings.tx_frequency);
transmitter_model.set_tx_gain(app_settings.tx_gain);
}
button_set.on_select = [this, &nav](Button&) {
text_prompt(
nav,
@@ -116,9 +99,9 @@ APRSTXView::APRSTXView(NavigationView& nav) {
};
tx_view.on_edit_frequency = [this, &nav]() {
auto new_view = nav.push<FrequencyKeypadView>(receiver_model.tuning_frequency());
auto new_view = nav.push<FrequencyKeypadView>(transmitter_model.target_frequency());
new_view->on_changed = [this](rf::Frequency f) {
receiver_model.set_tuning_frequency(f);
transmitter_model.set_target_frequency(f);
};
};
+8 -3
View File
@@ -27,8 +27,10 @@
#include "ui_transmitter.hpp"
#include "message.hpp"
#include "modems.hpp"
#include "transmitter_model.hpp"
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "portapack.hpp"
namespace ui {
@@ -43,9 +45,12 @@ class APRSTXView : public View {
std::string title() const override { return "APRS TX"; };
private:
// app save settings
std::app_settings settings{};
std::app_settings::AppSettings app_settings{};
TxRadioState radio_state_{
1750000 /* bandwidth */,
AFSK_TX_SAMPLERATE /* sampling rate */
};
app_settings::SettingsManager settings_{
"tx_aprs", app_settings::Mode::TX};
std::string payload{""};
+10 -26
View File
@@ -24,7 +24,6 @@
#include "string_format.hpp"
#include "baseband_api.hpp"
#include "cpld_update.hpp"
#include "portapack_persistent_memory.hpp"
using namespace portapack;
@@ -137,13 +136,8 @@ void BHTView::on_tx_progress(const uint32_t progress, const bool done) {
}
BHTView::~BHTView() {
// save app settings
app_settings.tx_frequency = transmitter_model.tuning_frequency();
settings.save("tx_bht", &app_settings);
transmitter_model.disable();
hackrf::cpld::load_sram_no_verify(); // to leave all RX ok, without ghost signal problem at the exit .
baseband::shutdown(); // better this function at the end, not load_sram() that sometimes produces hang up.
baseband::shutdown();
}
BHTView::BHTView(NavigationView& nav) {
@@ -157,21 +151,12 @@ BHTView::BHTView(NavigationView& nav) {
&progressbar,
&tx_view});
// load app settings
auto rc = settings.load("tx_bht", &app_settings);
if (rc == SETTINGS_OK) {
transmitter_model.set_rf_amp(app_settings.tx_amp);
transmitter_model.set_channel_bandwidth(app_settings.channel_bandwidth);
transmitter_model.set_tuning_frequency(app_settings.tx_frequency);
transmitter_model.set_tx_gain(app_settings.tx_gain);
}
field_speed.set_value(1);
tx_view.on_edit_frequency = [this, &nav]() {
auto new_view = nav.push<FrequencyKeypadView>(receiver_model.tuning_frequency());
auto new_view = nav.push<FrequencyKeypadView>(transmitter_model.target_frequency());
new_view->on_changed = [this](rf::Frequency f) {
transmitter_model.set_tuning_frequency(f);
transmitter_model.set_target_frequency(f);
};
};
@@ -233,7 +218,6 @@ EPARView::EPARView(
field_city.set_value(0);
field_group.set_selected_index(2);
field_city.on_change = [this](int32_t) { generate_message(); };
field_group.on_change = [this](size_t, int32_t) { generate_message(); };
@@ -243,11 +227,11 @@ EPARView::EPARView(
size_t n = 0;
for (auto& relay_state : relay_states) {
relay_state.on_change = relay_state_fn;
relay_state.set_parent_rect({static_cast<Coord>(90 + (n * 36)),
80,
24, 24});
relay_state.set_options(relay_options);
relay_state.on_change = relay_state_fn; // NB: set after set_options to avoid startup call.
add_child(&relay_state);
n++;
}
@@ -316,11 +300,11 @@ XylosView::XylosView(
};
field_header_a.on_change = field_fn;
field_header_b.on_change = [this](int32_t) { generate_message(); };
field_city.on_change = [this](int32_t) { generate_message(); };
field_family.on_change = [this](int32_t) { generate_message(); };
field_subfamily.on_change = [this](int32_t) { generate_message(); };
field_receiver.on_change = [this](int32_t) { generate_message(); };
field_header_b.on_change = field_fn;
field_city.on_change = field_fn;
field_family.on_change = field_fn;
field_subfamily.on_change = field_fn;
field_receiver.on_change = field_fn;
checkbox_wcsubfamily.on_select = [this](Checkbox&, bool v) {
field_subfamily.set_focusable(!v);
@@ -341,11 +325,11 @@ XylosView::XylosView(
size_t n = 0;
for (auto& relay_state : relay_states) {
relay_state.on_change = relay_state_fn;
relay_state.set_parent_rect({static_cast<Coord>(54 + (n * 36)),
134,
24, 24});
relay_state.set_options(relay_options);
relay_state.on_change = relay_state_fn; // NB: set after set_options to avoid startup call.
add_child(&relay_state);
n++;
}
+4 -3
View File
@@ -32,6 +32,7 @@
#include "transmitter_model.hpp"
#include "encoders.hpp"
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "portapack.hpp"
namespace ui {
@@ -165,9 +166,9 @@ class BHTView : public View {
std::string title() const override { return "BHT TX"; };
private:
// app save settings
std::app_settings settings{};
std::app_settings::AppSettings app_settings{};
TxRadioState radio_state_{};
app_settings::SettingsManager settings_{
"tx_bht", app_settings::Mode::TX};
void on_tx_progress(const uint32_t progress, const bool done);
void start_tx();
+4 -33
View File
@@ -40,11 +40,8 @@ void BTLERxView::focus() {
field_frequency.focus();
}
void BTLERxView::update_freq(rf::Frequency f) {
receiver_model.set_tuning_frequency(f);
}
BTLERxView::BTLERxView(NavigationView& nav) {
BTLERxView::BTLERxView(NavigationView& nav)
: nav_{nav} {
baseband::run_image(portapack::spi_flash::image_tag_btle_rx);
add_children({&rssi,
@@ -56,16 +53,8 @@ BTLERxView::BTLERxView(NavigationView& nav) {
&button_modem_setup,
&console});
// load app settings
auto rc = settings.load("rx_btle", &app_settings);
if (rc == SETTINGS_OK) {
field_lna.set_value(app_settings.lna);
field_vga.set_value(app_settings.vga);
field_rf_amp.set_value(app_settings.rx_amp);
}
// Auto-configure modem for LCR RX (will be removed later)
update_freq(2426000000);
// Auto-configure modem for LCR RX (TODO: remove later)
field_frequency.set_value(2426000000);
auto def_bell202 = &modem_defs[0];
persistent_memory::set_modem_baudrate(def_bell202->baudrate);
serial_format_t serial_format;
@@ -75,18 +64,7 @@ BTLERxView::BTLERxView(NavigationView& nav) {
serial_format.bit_order = LSB_FIRST;
persistent_memory::set_serial_format(serial_format);
field_frequency.set_value(receiver_model.tuning_frequency());
field_frequency.set_step(100);
field_frequency.on_change = [this](rf::Frequency f) {
update_freq(f);
};
field_frequency.on_edit = [this, &nav]() {
auto new_view = nav.push<FrequencyKeypadView>(receiver_model.tuning_frequency());
new_view->on_changed = [this](rf::Frequency f) {
update_freq(f);
field_frequency.set_value(f);
};
};
button_modem_setup.on_select = [&nav](Button&) {
nav.push<ModemSetupView>();
@@ -98,9 +76,6 @@ BTLERxView::BTLERxView(NavigationView& nav) {
audio::set_rate(audio::Rate::Hz_24000);
audio::output::start();
receiver_model.set_sampling_rate(4000000);
receiver_model.set_baseband_bandwidth(4000000);
receiver_model.set_modulation(ReceiverModel::Mode::WidebandFMAudio);
receiver_model.enable();
}
@@ -148,10 +123,6 @@ void BTLERxView::on_data(uint32_t value, bool is_data) {
}
BTLERxView::~BTLERxView() {
// save app settings
app_settings.rx_frequency = field_frequency.value();
settings.save("rx_btle", &app_settings);
audio::output::stop();
receiver_model.disable();
baseband::shutdown();
+14 -13
View File
@@ -27,8 +27,10 @@
#include "ui.hpp"
#include "ui_navigation.hpp"
#include "ui_receiver.hpp"
#include "ui_freq_field.hpp"
#include "app_settings.hpp"
#include "ui_record_view.hpp" // DEBUG
#include "radio_state.hpp"
#include "ui_record_view.hpp"
#include "utility.hpp"
@@ -46,9 +48,13 @@ class BTLERxView : public View {
private:
void on_data(uint32_t value, bool is_data);
// app save settings
std::app_settings settings{};
std::app_settings::AppSettings app_settings{};
NavigationView& nav_;
RxRadioState radio_state_{
4000000 /* bandwidth */,
4000000 /* sampling rate */,
ReceiverModel::Mode::WidebandFMAudio};
app_settings::SettingsManager settings_{
"rx_btle", app_settings::Mode::RX};
uint8_t console_color{0};
uint32_t prev_value{0};
@@ -60,15 +66,13 @@ class BTLERxView : public View {
VGAGainField field_vga{
{18 * 8, 0 * 16}};
RSSI rssi{
{21 * 8, 0, 6 * 8, 4},
};
{21 * 8, 0, 6 * 8, 4}};
Channel channel{
{21 * 8, 5, 6 * 8, 4},
};
{21 * 8, 5, 6 * 8, 4}};
FrequencyField field_frequency{
RxFrequencyField field_frequency{
{0 * 8, 0 * 16},
};
nav_};
Button button_modem_setup{
{240 - 12 * 8, 1 * 16, 96, 24},
@@ -77,9 +81,6 @@ class BTLERxView : public View {
Console console{
{0, 4 * 16, 240, 240}};
void update_freq(rf::Frequency f);
// void on_data_afsk(const AFSKDataMessage& message);
MessageHandlerRegistration message_handler_packet{
Message::ID::AFSKData,
[this](Message* const p) {
+3 -20
View File
@@ -23,7 +23,6 @@
#include "ui_coasterp.hpp"
#include "baseband_api.hpp"
#include "cpld_update.hpp"
#include "portapack_persistent_memory.hpp"
#include <cstring>
@@ -38,13 +37,8 @@ void CoasterPagerView::focus() {
}
CoasterPagerView::~CoasterPagerView() {
// save app settings
app_settings.tx_frequency = transmitter_model.tuning_frequency();
settings.save("tx_coaster", &app_settings);
transmitter_model.disable();
hackrf::cpld::load_sram_no_verify(); // to leave all RX ok, without ghost signal problem at the exit .
baseband::shutdown(); // better this function at the end, not load_sram() that sometimes produces hang up.
baseband::shutdown();
}
void CoasterPagerView::generate_frame() {
@@ -73,8 +67,6 @@ void CoasterPagerView::generate_frame() {
void CoasterPagerView::start_tx() {
generate_frame();
transmitter_model.set_sampling_rate(2280000);
transmitter_model.set_baseband_bandwidth(1750000);
transmitter_model.enable();
baseband::set_fsk_data(19 * 8, 2280000 / 1000, 5000, 32);
@@ -123,15 +115,6 @@ CoasterPagerView::CoasterPagerView(NavigationView& nav) {
&text_message,
&tx_view});
// load app settings
auto rc = settings.load("tx_coaster", &app_settings);
if (rc == SETTINGS_OK) {
transmitter_model.set_rf_amp(app_settings.tx_amp);
transmitter_model.set_channel_bandwidth(app_settings.channel_bandwidth);
transmitter_model.set_tuning_frequency(app_settings.tx_frequency);
transmitter_model.set_tx_gain(app_settings.tx_gain);
}
// Bytes to nibbles
for (c = 0; c < 16; c++)
sym_data.set_sym(c, (data_init[c >> 1] >> ((c & 1) ? 0 : 4)) & 0x0F);
@@ -141,9 +124,9 @@ CoasterPagerView::CoasterPagerView(NavigationView& nav) {
generate_frame();
tx_view.on_edit_frequency = [this, &nav]() {
auto new_view = nav.push<FrequencyKeypadView>(receiver_model.tuning_frequency());
auto new_view = nav.push<FrequencyKeypadView>(transmitter_model.target_frequency());
new_view->on_changed = [this](rf::Frequency f) {
receiver_model.set_tuning_frequency(f);
transmitter_model.set_target_frequency(f);
};
};
+7 -3
View File
@@ -28,6 +28,7 @@
#include "message.hpp"
#include "transmitter_model.hpp"
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "portapack.hpp"
namespace ui {
@@ -50,9 +51,12 @@ class CoasterPagerView : public View {
tx_modes tx_mode = IDLE;
// app save settings
std::app_settings settings{};
std::app_settings::AppSettings app_settings{};
TxRadioState radio_state_{
1750000 /* bandwidth */,
2280000 /* sampling rate */
};
app_settings::SettingsManager settings_{
"tx_coaster", app_settings::Mode::TX};
void start_tx();
void generate_frame();
+132 -7
View File
@@ -25,10 +25,14 @@
#include "radio.hpp"
#include "string_format.hpp"
#include "crc.hpp"
#include "audio.hpp"
#include "ui_sd_card_debug.hpp"
#include "ui_font_fixed_8x16.hpp"
#include "ui_styles.hpp"
#include "ui_painter.hpp"
#include "portapack.hpp"
#include "portapack_persistent_memory.hpp"
@@ -247,6 +251,7 @@ void ControlsSwitchesWidget::on_show() {
bool ControlsSwitchesWidget::on_key(const KeyEvent key) {
key_event_mask = 1 << toUType(key);
long_press_key_event_mask = key_is_long_pressed(key) ? key_event_mask : 0;
return true;
}
@@ -259,9 +264,9 @@ void ControlsSwitchesWidget::paint(Painter& painter) {
{32, 64, 16, 16}, // Down
{32, 0, 16, 16}, // Up
{32, 32, 16, 16}, // Select
{96, 0, 16, 16}, // Dfu
{16, 96, 16, 16}, // Encoder phase 0
{48, 96, 16, 16}, // Encoder phase 1
{96, 0, 16, 16}, // Dfu
{96, 64, 16, 16}, // Touch
}};
@@ -278,9 +283,9 @@ void ControlsSwitchesWidget::paint(Painter& painter) {
{32 + 1, 64 + 1, 16 - 2, 16 - 2}, // Down
{32 + 1, 0 + 1, 16 - 2, 16 - 2}, // Up
{32 + 1, 32 + 1, 16 - 2, 16 - 2}, // Select
{96 + 1, 0 + 1, 16 - 2, 16 - 2}, // Dfu
{16 + 1, 96 + 1, 16 - 2, 16 - 2}, // Encoder phase 0
{48 + 1, 96 + 1, 16 - 2, 16 - 2}, // Encoder phase 1
{96 + 1, 0 + 1, 16 - 2, 16 - 2}, // Dfu
}};
auto switches_raw = control::debug::switches();
@@ -324,6 +329,14 @@ void ControlsSwitchesWidget::paint(Painter& painter) {
switches_event >>= 1;
}
switches_event = long_press_key_event_mask;
for (const auto r : events_rects) {
if (switches_event & 1)
painter.fill_rectangle(r + pos, Color::cyan());
switches_event >>= 1;
}
}
void ControlsSwitchesWidget::on_frame_sync() {
@@ -334,12 +347,21 @@ void ControlsSwitchesWidget::on_frame_sync() {
DebugControlsView::DebugControlsView(NavigationView& nav) {
add_children({
&text_title,
&labels,
&switches_widget,
&options_switches_mode,
&button_done,
});
button_done.on_select = [&nav](Button&) { nav.pop(); };
button_done.on_select = [&nav](Button&) {
set_switches_long_press_config(0);
nav.pop();
};
options_switches_mode.on_change = [this](size_t, OptionsField::value_t v) {
(void)v;
set_switches_long_press_config(options_switches_mode.selected_index_value());
};
}
void DebugControlsView::focus() {
@@ -381,22 +403,38 @@ DebugMenuView::DebugMenuView(NavigationView& nav) {
{"Peripherals", ui::Color::dark_cyan(), &bitmap_icon_peripherals, [&nav]() { nav.push<DebugPeripheralsMenuView>(); }},
{"Temperature", ui::Color::dark_cyan(), &bitmap_icon_temperature, [&nav]() { nav.push<TemperatureView>(); }},
{"Buttons Test", ui::Color::dark_cyan(), &bitmap_icon_controls, [&nav]() { nav.push<DebugControlsView>(); }},
{"Pmem", ui::Color::dark_cyan(), &bitmap_icon_memory, [&nav]() { nav.push<DebugPmemView>(); }},
{"Touch Test", ui::Color::dark_cyan(), &bitmap_icon_notepad, [&nav]() { nav.push<DebugScreenTest>(); }},
{"P.Memory", ui::Color::dark_cyan(), &bitmap_icon_memory, [&nav]() { nav.push<DebugPmemView>(); }},
{"Debug Dump", ui::Color::dark_cyan(), &bitmap_icon_memory, [&nav]() { portapack::persistent_memory::debug_dump(); }},
{"Fonts Viewer", ui::Color::dark_cyan(), &bitmap_icon_notepad, [&nav]() { nav.push<DebugFontsView>(); }},
});
set_max_rows(2); // allow wider buttons
}
/* 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, &button_ok});
add_children({&text_page, &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()) + " CRC: " + to_string_hex(data.check_value, 8));
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));
button_ok.on_select = [&nav](Button&) {
nav.pop();
@@ -429,6 +467,93 @@ uint32_t DebugPmemView::registers_widget_feed(const size_t register_number) {
return data.regfile[(page_size * page + register_number) / 4] >> (register_number % 4 * 8);
}
/* DebugFontsView *******************************************************/
uint16_t DebugFontsView::display_font(Painter& painter, uint16_t y_offset, const Style* font_style, std::string_view font_name) {
auto char_width{font_style->font.char_width()};
auto char_height{font_style->font.line_height()};
auto cpl{((screen_width / char_width) - 6) & 0xF8}; // Display a multiple of 8 characters per line
uint16_t line_pos{y_offset};
painter.draw_string({0, y_offset}, *font_style, font_name);
// Displaying ASCII+extended characters from 0x20 to 0xFF
for (uint8_t c = 0; c <= 0xDF; c++) {
line_pos = y_offset + ((c / cpl) + 2) * char_height;
if ((c % cpl) == 0)
painter.draw_string({0, line_pos}, *font_style, "Ox" + to_string_hex(c + 0x20, 2));
painter.draw_char({((c % cpl) + 5) * char_width, line_pos}, *font_style, (char)(c + 0x20));
}
return line_pos + char_height;
}
void DebugFontsView::paint(Painter& painter) {
int16_t line_pos;
line_pos = display_font(painter, 32, &Styles::white, "Fixed 8x16");
display_font(painter, line_pos + 16, &Styles::white_small, "Fixed 5x8");
}
DebugFontsView::DebugFontsView(NavigationView& nav)
: nav_{nav} {
set_focusable(true);
}
/* DebugScreenTest ****************************************************/
DebugScreenTest::DebugScreenTest(NavigationView& nav)
: nav_{nav} {
set_focusable(true);
}
bool DebugScreenTest::on_key(const KeyEvent key) {
Painter painter;
switch (key) {
case KeyEvent::Select:
nav_.pop();
break;
case KeyEvent::Down:
painter.fill_rectangle({0, 0, screen_width, screen_height}, semirand());
break;
case KeyEvent::Left:
pen_color = semirand();
break;
default:
break;
}
return true;
}
bool DebugScreenTest::on_encoder(EncoderEvent delta) {
pen_size = clip<int32_t>(pen_size + delta, 1, screen_width);
return true;
}
bool DebugScreenTest::on_touch(const TouchEvent event) {
Painter painter;
pen_pos = event.point;
painter.fill_rectangle({pen_pos.x() - pen_size / 2, pen_pos.y() - pen_size / 2, pen_size, pen_size}, pen_color);
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();
}
/* DebugLCRView *******************************************************/
/*DebugLCRView::DebugLCRView(NavigationView& nav, std::string lcr_string) {
std::string debug_text;
+46 -8
View File
@@ -31,6 +31,7 @@
#include "rffc507x.hpp"
#include "portapack.hpp"
#include "memory_map.hpp"
#include "irq_controls.hpp"
#include <functional>
#include <utility>
@@ -220,7 +221,8 @@ class ControlsSwitchesWidget : public Widget {
ControlsSwitchesWidget(
Rect parent_rect)
: Widget{parent_rect},
key_event_mask(0) {
key_event_mask(0),
long_press_key_event_mask{0} {
set_focusable(true);
}
@@ -231,6 +233,7 @@ class ControlsSwitchesWidget : public Widget {
private:
uint8_t key_event_mask;
uint8_t long_press_key_event_mask;
MessageHandlerRegistration message_handler_frame_sync{
Message::ID::DisplayFrameSync,
@@ -250,15 +253,22 @@ class DebugControlsView : public View {
std::string title() const override { return "Buttons Test"; };
private:
Text text_title{
{64, 16, 184, 16},
"Controls State",
};
Labels labels{
{{8 * 8, 1 * 16}, "Controls State", Color::white()},
{{0 * 8, 14 * 16}, "Long-Press Mode:", Color::grey()}};
ControlsSwitchesWidget switches_widget{
{80, 80, 80, 112},
};
OptionsField options_switches_mode{
{17 * 8, 14 * 16},
8,
{
{"Disabled", 0},
{"Enabled", 0xFF}, // all KeyEvent bits to long-press mode
}};
Button button_done{
{72, 264, 96, 24},
"Done"};
@@ -269,7 +279,7 @@ class DebugPmemView : public View {
DebugPmemView(NavigationView& nav);
void focus() override;
bool on_encoder(const EncoderEvent delta) override;
std::string title() const override { return "Pmem"; }
std::string title() const override { return "P.Mem Debug"; }
private:
struct pmem_data {
@@ -282,13 +292,14 @@ class DebugPmemView : public View {
int32_t page{0};
const pmem_data& data;
volatile const pmem_data& data;
Text text_page{{16, 16, 208, 16}};
RegistersWidget registers_widget;
Text text_checksum{{16, 240, 208, 16}};
Text text_checksum{{16, 232, 208, 16}};
Text text_checksum2{{16, 248, 208, 16}};
Button button_ok{
{240 / 3, 270, 240 / 3, 24},
@@ -299,6 +310,33 @@ class DebugPmemView : public View {
uint32_t registers_widget_feed(const size_t register_number);
};
class DebugFontsView : public View {
public:
DebugFontsView(NavigationView& nav);
void paint(Painter& painter) override;
std::string title() const override { return "Fonts"; };
private:
uint16_t display_font(Painter& painter, uint16_t y_offset, const Style* font_style, std::string_view font_name);
NavigationView& nav_;
};
class DebugScreenTest : public View {
public:
DebugScreenTest(NavigationView& nav);
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:
NavigationView& nav_;
Point pen_pos{};
Color pen_color{0};
int16_t pen_size{10};
};
/*class DebugLCRView : public View {
public:
DebugLCRView(NavigationView& nav, std::string lcrstring);
+54
View File
@@ -22,6 +22,7 @@
#include "ui_dfu_menu.hpp"
#include "portapack_shared_memory.hpp"
#include "performance_counter.hpp"
#include "portapack.hpp"
namespace ui {
@@ -80,4 +81,57 @@ void DfuMenu::paint(Painter& painter) {
ui::Color::dark_cyan());
}
DfuMenu2::DfuMenu2(NavigationView& nav)
: nav_(nav) {
add_children({&text_head,
&labels,
&text_info_line_1,
&text_info_line_2,
&text_info_line_3,
&text_info_line_4,
&text_info_line_5,
&text_info_line_6,
&text_info_line_7,
&text_info_line_8});
}
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("");
constexpr auto margin = 5;
constexpr auto lines = 8 + 2;
painter.fill_rectangle(
{{5 * CHARACTER_WIDTH - margin, 3 * LINE_HEIGHT - margin},
{19 * CHARACTER_WIDTH + margin * 2, lines * LINE_HEIGHT + margin * 2}},
ui::Color::black());
painter.fill_rectangle(
{{4 * CHARACTER_WIDTH - margin, 3 * LINE_HEIGHT - margin},
{CHARACTER_WIDTH, lines * LINE_HEIGHT + margin * 2}},
ui::Color::dark_cyan());
painter.fill_rectangle(
{{24 * CHARACTER_WIDTH + margin, 3 * LINE_HEIGHT - margin},
{CHARACTER_WIDTH, lines * LINE_HEIGHT + margin * 2}},
ui::Color::dark_cyan());
painter.fill_rectangle(
{{4 * CHARACTER_WIDTH - margin, 3 * LINE_HEIGHT - margin - 8},
{21 * CHARACTER_WIDTH + margin * 2, 8}},
ui::Color::dark_cyan());
painter.fill_rectangle(
{{4 * CHARACTER_WIDTH - margin, (lines + 3) * LINE_HEIGHT + margin},
{21 * CHARACTER_WIDTH + margin * 2, 8}},
ui::Color::dark_cyan());
}
} /* namespace ui */
+32
View File
@@ -67,6 +67,38 @@ class DfuMenu : public View {
Text text_info_line_8{{15 * CHARACTER_WIDTH, 12 * LINE_HEIGHT, 5 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
};
class DfuMenu2 : public View {
public:
DfuMenu2(NavigationView& nav);
~DfuMenu2() = default;
void paint(Painter& painter) override;
private:
NavigationView& nav_;
Text text_head{{6 * CHARACTER_WIDTH, 3 * LINE_HEIGHT, 8 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, "Receiver"};
Labels labels{
{{5 * CHARACTER_WIDTH, 5 * LINE_HEIGHT}, "Tgt freq:", Color::dark_cyan()},
{{5 * CHARACTER_WIDTH, 6 * LINE_HEIGHT}, "Bandwidt:", Color::dark_cyan()},
{{5 * CHARACTER_WIDTH, 7 * LINE_HEIGHT}, "Sampl Rt:", 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}, "", 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}, ""};
Text text_info_line_3{{14 * CHARACTER_WIDTH, 7 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
Text text_info_line_4{{14 * CHARACTER_WIDTH, 8 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
Text text_info_line_5{{14 * CHARACTER_WIDTH, 9 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
Text text_info_line_6{{14 * CHARACTER_WIDTH, 10 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
Text text_info_line_7{{14 * CHARACTER_WIDTH, 11 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
Text text_info_line_8{{14 * CHARACTER_WIDTH, 12 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
};
} /* namespace ui */
#endif /*__UI_DFU_MENU_H__*/
+3 -20
View File
@@ -24,7 +24,6 @@
#include "baseband_api.hpp"
#include "string_format.hpp"
#include "cpld_update.hpp"
using namespace portapack;
@@ -200,13 +199,8 @@ void EncodersView::focus() {
}
EncodersView::~EncodersView() {
// save app settings
app_settings.tx_frequency = transmitter_model.tuning_frequency();
settings.save("tx_ook", &app_settings);
transmitter_model.disable();
hackrf::cpld::load_sram_no_verify(); // ghost signal c/m to the problem at the exit .
baseband::shutdown(); // better this function after load_sram()
baseband::shutdown();
}
void EncodersView::update_progress() {
@@ -266,8 +260,6 @@ void EncodersView::start_tx(const bool scan) {
repeat_index = 1;
update_progress();
transmitter_model.set_sampling_rate(OOK_SAMPLERATE);
transmitter_model.set_baseband_bandwidth(1750000);
transmitter_model.enable();
baseband::set_ook_data(
@@ -290,19 +282,10 @@ EncodersView::EncodersView(
&progressbar,
&tx_view});
// load app settings
auto rc = settings.load("tx_ook", &app_settings);
if (rc == SETTINGS_OK) {
transmitter_model.set_rf_amp(app_settings.tx_amp);
transmitter_model.set_channel_bandwidth(app_settings.channel_bandwidth);
transmitter_model.set_tuning_frequency(app_settings.tx_frequency);
transmitter_model.set_tx_gain(app_settings.tx_gain);
}
tx_view.on_edit_frequency = [this, &nav]() {
auto new_view = nav.push<FrequencyKeypadView>(transmitter_model.tuning_frequency());
auto new_view = nav.push<FrequencyKeypadView>(transmitter_model.target_frequency());
new_view->on_changed = [this](rf::Frequency f) {
transmitter_model.set_tuning_frequency(f);
transmitter_model.set_target_frequency(f);
};
};
+7 -3
View File
@@ -27,6 +27,7 @@
#include "encoders.hpp"
#include "de_bruijn.hpp"
#include "app_settings.hpp"
#include "radio_state.hpp"
using namespace encoders;
@@ -183,9 +184,12 @@ class EncodersView : public View {
SCAN
};
// app save settings
std::app_settings settings{};
std::app_settings::AppSettings app_settings{};
TxRadioState radio_state_{
1750000 /* bandwidth */,
OOK_SAMPLERATE /* sampling rate */
};
app_settings::SettingsManager settings_{
"tx_ook", app_settings::Mode::TX};
tx_modes tx_mode = IDLE;
uint32_t repeat_index{0};
+54 -36
View File
@@ -26,6 +26,7 @@
#include <algorithm>
#include "ui_fileman.hpp"
#include "ui_playlist.hpp"
#include "ui_ss_viewer.hpp"
#include "ui_text_editor.hpp"
#include "string_format.hpp"
@@ -35,6 +36,16 @@
using namespace portapack;
namespace fs = std::filesystem;
namespace ui {
static const fs::path txt_ext{u".TXT"};
static const fs::path ppl_ext{u".PPL"};
static const fs::path c8_ext{u".C8"};
static const fs::path c16_ext{u".C16"};
static const fs::path cxx_ext{u".C*"};
static const fs::path png_ext{u".PNG"};
static const fs::path bmp_ext{u".BMP"};
} // namespace ui
namespace {
using namespace ui;
@@ -47,25 +58,6 @@ std::string truncate(const fs::path& path, size_t max_length) {
return ::truncate(path.string(), max_length);
}
// Case insensitive path equality on underlying "native" string.
bool iequal(
const fs::path& lhs,
const fs::path& rhs) {
const auto& lhs_str = lhs.native();
const auto& rhs_str = rhs.native();
// NB: Not correct for Unicode/locales.
if (lhs_str.length() == rhs_str.length()) {
for (size_t i = 0; i < lhs_str.length(); ++i)
if (towupper(lhs_str[i]) != towupper(rhs_str[i]))
return false;
return true;
}
return false;
}
// 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) {
auto it = std::lower_bound(
@@ -86,19 +78,17 @@ void insert_sorted(std::vector<fileman_entry>& entries, fileman_entry&& entry) {
fs::path get_partner_file(fs::path path) {
if (fs::is_directory(path))
return {};
const fs::path txt_path{u".TXT"};
const fs::path c16_path{u".C16"};
auto ext = path.extension();
if (iequal(ext, txt_path))
ext = c16_path;
else if (iequal(ext, c16_path))
ext = txt_path;
else
if (is_cxx_capture_file(path))
path.replace_extension(txt_ext);
else if (path_iequal(ext, txt_ext)) {
path.replace_extension(c8_ext);
if (!fs::file_exists(path))
path.replace_extension(c16_ext);
} else
return {};
path.replace_extension(ext);
return fs::file_exists(path) && !fs::is_directory(path) ? path : fs::path{};
}
@@ -154,6 +144,7 @@ void FileManBaseView::load_directory_contents(const fs::path& dir_path) {
current_path = dir_path;
entry_list.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));
@@ -163,7 +154,7 @@ void FileManBaseView::load_directory_contents(const fs::path& dir_path) {
continue;
if (fs::is_regular_file(entry.status())) {
if (!filtering || iequal(entry.path().extension(), extension_filter))
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});
} else if (fs::is_directory(entry.status())) {
insert_sorted(entry_list, {entry.path(), 0, true});
@@ -301,7 +292,7 @@ const FileManBaseView::file_assoc_t& FileManBaseView::get_assoc(
size_t index = 0;
for (; index < file_types.size() - 1; ++index)
if (iequal(ext, file_types[index].extension))
if (path_iequal(ext, file_types[index].extension))
return file_types[index];
// Default to last entry in the list.
@@ -412,11 +403,17 @@ void FileManagerView::refresh_widgets(const bool v) {
set_dirty();
}
void FileManagerView::on_rename() {
void FileManagerView::on_rename(std::string_view hint) {
auto& entry = get_selected_entry();
name_buffer = entry.path.filename().string();
uint32_t cursor_pos = (uint32_t)name_buffer.length();
// Append the hint to the filename stem as a rename suggestion.
name_buffer = entry.path.stem().string();
if (!hint.empty())
name_buffer += "_" + std::string{hint};
name_buffer += entry.path.extension().string();
// Set the rename cursor to before the extension to make renaming simpler.
uint32_t cursor_pos = (uint32_t)name_buffer.length();
if (auto pos = name_buffer.find_last_of(".");
pos != name_buffer.npos && !entry.is_directory)
cursor_pos = pos;
@@ -443,6 +440,11 @@ void FileManagerView::on_rename() {
}
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!");
return;
}
auto name = get_selected_entry().path.filename().string();
nav_.push<ModalMessageView>(
"Delete", "Delete " + name + "\nAre you sure?", YESNO,
@@ -507,12 +509,20 @@ bool FileManagerView::handle_file_open() {
auto path = get_selected_full_path();
auto ext = path.extension();
if (iequal(u".TXT", ext) || iequal(u".PPL", ext)) {
if (path_iequal(txt_ext, ext)) {
nav_.push<TextEditorView>(path);
return true;
} else if (iequal(u".PNG", ext)) {
} else if (is_cxx_capture_file(path) || path_iequal(ppl_ext, ext)) {
// TODO: Enough memory to push?
nav_.push<PlaylistView>(path);
return true;
} else if (path_iequal(png_ext, ext)) {
nav_.push<ScreenshotViewer>(path);
return true;
} else if (path_iequal(bmp_ext, ext)) {
nav_.push<SplashViewer>(path);
reload_current();
return true;
}
return false;
@@ -546,6 +556,7 @@ FileManagerView::FileManagerView(
&button_new_dir,
&button_new_file,
&button_open_notepad,
&button_rename_timestamp,
});
menu_view.on_highlight = [this]() {
@@ -569,7 +580,7 @@ FileManagerView::FileManagerView(
button_rename.on_select = [this]() {
if (selected_is_valid())
on_rename();
on_rename("");
};
button_delete.on_select = [this]() {
@@ -615,6 +626,13 @@ FileManagerView::FileManagerView(
} else
nav_.display_modal("Open in Notepad", "Can't open that in Notepad.");
};
button_rename_timestamp.on_select = [this]() {
if (selected_is_valid() && !get_selected_entry().is_directory) {
on_rename(::truncate(to_string_timestamp(rtc_time::now()), 8));
} else
nav_.display_modal("Timestamp Rename", "Can't rename that.");
};
}
} // namespace ui
+7 -1
View File
@@ -207,7 +207,7 @@ class FileManagerView : public FileManBaseView {
ClipboardMode clipboard_mode{ClipboardMode::None};
void refresh_widgets(const bool v);
void on_rename();
void on_rename(std::string_view hint);
void on_delete();
void on_paste();
void on_new_dir();
@@ -272,6 +272,12 @@ class FileManagerView : public FileManBaseView {
{},
&bitmap_icon_notepad,
Color::orange()};
NewButton button_rename_timestamp{
{4 * 8, 34 * 8, 4 * 8, 32},
{},
&bitmap_icon_options_datetime,
Color::orange()};
};
} /* namespace ui */
@@ -25,6 +25,8 @@
namespace ui {
static const char16_t* firmware_folder = u"/FIRMWARE";
Thread* FlashUtilityView::thread{nullptr};
static constexpr size_t max_filename_length = 26;
@@ -35,7 +37,9 @@ FlashUtilityView::FlashUtilityView(NavigationView& nav)
menu_view.set_parent_rect({0, 3 * 8, 240, 33 * 8});
for (const auto& entry : std::filesystem::directory_iterator(u"FIRMWARE", u"*.bin")) {
ensure_directory(firmware_folder);
for (const auto& entry : std::filesystem::directory_iterator(firmware_folder, u"*.bin")) {
auto filename = entry.path().filename();
auto path = entry.path().native();
@@ -40,7 +40,7 @@ class FlashUtilityView : public View {
void focus() override;
std::string title() const override { return "FlashUtility"; }; // Removed the space because the title and speaker icon overlapped.
std::string title() const override { return "Flash Utility"; };
private:
NavigationView& nav_;
+412 -190
View File
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
* Copyright (C) 2023 Kyle Reed
*
* This file is part of PortaPack.
*
@@ -22,281 +23,502 @@
#include "ui_freqman.hpp"
#include "portapack.hpp"
#include "event_m0.hpp"
#include "portapack.hpp"
#include "rtc_time.hpp"
#include "tone_key.hpp"
#include "ui_receiver.hpp"
#include "ui_styles.hpp"
#include "utility.hpp"
#include <memory>
using namespace portapack;
using namespace ui;
namespace fs = std::filesystem;
// TODO: Clean up after moving to better lookup tables.
using options_t = OptionsField::options_t;
extern options_t freqman_modulations;
extern options_t freqman_bandwidths[4];
extern options_t freqman_steps;
extern options_t freqman_steps_short;
/* Set options. */
void freqman_set_modulation_option(OptionsField& option) {
option.set_options(freqman_modulations);
}
void freqman_set_bandwidth_option(freqman_index_t modulation, OptionsField& option) {
if (is_valid(modulation))
option.set_options(freqman_bandwidths[modulation]);
}
void freqman_set_step_option(OptionsField& option) {
option.set_options(freqman_steps);
}
void freqman_set_step_option_short(OptionsField& option) {
option.set_options(freqman_steps_short);
}
namespace ui {
static int32_t current_category_id = 0;
/* FreqManBaseView ***************************************/
size_t FreqManBaseView::current_category_index = 0;
FreqManBaseView::FreqManBaseView(
NavigationView& nav)
: nav_(nav) {
add_children({&options_category,
&label_category,
&button_exit});
add_children(
{&label_category,
&options_category,
&button_exit});
// initialize
refresh_list();
options_category.on_change = [this](size_t category_id, int32_t) {
change_category(category_id);
options_category.on_change = [this](size_t new_index, int32_t) {
change_category(new_index);
};
options_category.set_selected_index(current_category_id);
button_exit.on_select = [this, &nav](Button&) {
nav.pop();
};
refresh_categories();
};
void FreqManBaseView::focus() {
button_exit.focus();
// TODO: Shouldn't be on focus.
if (error_ == ERROR_ACCESS) {
nav_.display_modal("Error", "File acces error", ABORT, nullptr);
nav_.display_modal("Error", "File access error", ABORT, nullptr);
} else if (error_ == ERROR_NOFILES) {
nav_.display_modal("Error", "No database files\nin /freqman", ABORT, nullptr);
nav_.display_modal("Error", "No database files\nin /FREQMAN", ABORT, nullptr);
} else {
options_category.focus();
}
}
void FreqManBaseView::get_freqman_files() {
std::vector<std::string>().swap(file_list);
void FreqManBaseView::change_category(size_t new_index) {
if (categories().empty())
return;
auto files = scan_root_files(u"FREQMAN", u"*.TXT");
for (auto file : files) {
std::string file_name = file.stem().string();
// don't propose tmp / hidden files in freqman's list
if (file_name.length() && file_name[0] != '.') {
file_list.emplace_back(file_name);
}
}
};
void FreqManBaseView::change_category(int32_t category_id) {
current_category_id = category_id;
if (file_list.empty()) return;
std::vector<freqman_entry>().swap(database);
if (!load_freqman_file(file_list[categories[category_id].second], database)) {
current_category_index = new_index;
if (!db_.open(get_freqman_path(current_category()))) {
error_ = ERROR_ACCESS;
}
menu_view.set_db(database);
text_empty.hidden(!database.empty());
menu_view.set_dirty();
set_dirty();
freqlist_view.set_db(db_);
}
void FreqManBaseView::refresh_list() {
categories.clear();
get_freqman_files();
void FreqManBaseView::refresh_categories() {
OptionsField::options_t new_categories;
for (size_t n = 0; n < file_list.size(); n++)
categories.emplace_back(std::make_pair(file_list[n].substr(0, 14), n));
scan_root_files(
freqman_dir, u"*.TXT", [&new_categories](const fs::path& path) {
// Skip temp/hidden files.
if (path.empty() || path.native()[0] == u'.')
return;
// Alphabetical sort
std::sort(categories.begin(), categories.end(), [](auto& left, auto& right) {
// The UI layer will truncate long file names when displaying.
new_categories.emplace_back(path.stem().string(), new_categories.size());
});
// Alphabetically sort the categories.
std::sort(new_categories.begin(), new_categories.end(), [](auto& left, auto& right) {
return left.first < right.first;
});
options_category.set_options(categories);
if ((unsigned)current_category_id >= categories.size())
current_category_id = categories.size() - 1;
// Preserve last selection; ensure in range.
current_category_index = clip(current_category_index, 0u, new_categories.size());
auto saved_index = current_category_index;
options_category.set_options(std::move(new_categories));
options_category.set_selected_index(saved_index);
}
void FrequencySaveView::save_current_file() {
save_freqman_file(file_list[categories[current_category_id].second], database);
nav_.pop();
void FreqManBaseView::refresh_list(int delta_selected) {
// Update the index and ensures in bounds.
freqlist_view.set_index(freqlist_view.get_index() + delta_selected);
freqlist_view.set_dirty();
}
void FrequencySaveView::on_save_name() {
text_prompt(nav_, desc_buffer, 28, [this](std::string& buffer) {
database.push_back({value_, 0, buffer, SINGLE});
save_current_file();
});
}
void FrequencySaveView::on_save_timestamp() {
database.push_back({value_, 0, live_timestamp.string(), SINGLE});
save_current_file();
}
/* FrequencySaveView *************************************/
FrequencySaveView::FrequencySaveView(
NavigationView& nav,
const rf::Frequency value)
: FreqManBaseView(nav),
value_(value) {
desc_buffer.reserve(28);
: FreqManBaseView(nav) {
add_children(
{&labels,
&big_display,
&button_clear,
&button_edit,
&button_save,
&text_description});
// Todo: add back ?
/*for (size_t n = 0; n < database.size(); n++) {
if (database[n].value == value_) {
error_ = ERROR_DUPLICATE;
break;
}
}*/
entry_.type = freqman_type::Single;
entry_.frequency_a = value;
entry_.description = to_string_timestamp(rtc_time::now());
refresh_ui();
add_children({&labels,
&big_display,
&button_save_name,
&button_save_timestamp,
&live_timestamp});
big_display.set(value);
button_save_name.on_select = [this, &nav](Button&) {
on_save_name();
};
button_save_timestamp.on_select = [this, &nav](Button&) {
on_save_timestamp();
button_clear.on_select = [this, &nav](Button&) {
entry_.description = "";
refresh_ui();
};
options_category.on_change = [this, value](size_t category_id, int32_t) {
change_category(category_id);
big_display.set(value);
button_edit.on_select = [this, &nav](Button&) {
temp_buffer_ = entry_.description;
text_prompt(nav_, temp_buffer_, desc_edit_max, [this](std::string& new_desc) {
entry_.description = new_desc;
refresh_ui();
});
};
button_save.on_select = [this, &nav](Button&) {
db_.insert_entry(db_.entry_count(), entry_);
nav_.pop();
};
}
void FrequencyLoadView::refresh_widgets(const bool v) {
menu_view.hidden(v);
text_empty.hidden(!v);
// display.fill_rectangle(menu_view.screen_rect(), Color::black());
set_dirty();
void FrequencySaveView::refresh_ui() {
big_display.set(entry_.frequency_a);
text_description.set(entry_.description);
}
/* FrequencyLoadView *************************************/
FrequencyLoadView::FrequencyLoadView(
NavigationView& nav)
: FreqManBaseView(nav) {
on_refresh_widgets = [this](bool v) {
refresh_widgets(v);
add_children({&freqlist_view});
// Resize to fill screen. +2 keeps text out of border.
freqlist_view.set_parent_rect({0, 3 * 8, screen_width, 15 * 16 + 2});
freqlist_view.on_select = [&nav, this](size_t index) {
auto entry = db_[index];
// TODO: Maybe return center of range if user choses a range when the app
// needs a unique frequency, instead of frequency_a?
auto has_range = entry.type == freqman_type::Range ||
entry.type == freqman_type::HamRadio;
if (on_range_loaded && has_range)
on_range_loaded(entry.frequency_a, entry.frequency_b);
else if (on_frequency_loaded)
on_frequency_loaded(entry.frequency_a);
nav_.pop(); // NB: this will call dtor.
};
freqlist_view.on_leave = [this]() {
button_exit.focus();
};
}
add_children({&menu_view,
&text_empty});
/* FrequencyManagerView **********************************/
// Resize menu view to fill screen
menu_view.set_parent_rect({0, 3 * 8, 240, 30 * 8});
void FrequencyManagerView::on_edit_entry() {
auto edit_view = nav_.push<FrequencyEditView>(current_entry());
edit_view->on_save = [this](const freqman_entry& entry) {
db_.replace_entry(current_index(), entry);
freqlist_view.set_dirty();
};
}
menu_view.on_select = [&nav, this](FreqManUIList&) {
nav_.pop();
void FrequencyManagerView::on_edit_freq() {
auto freq_edit_view = nav_.push<FrequencyKeypadView>(current_entry().frequency_a);
freq_edit_view->on_changed = [this](rf::Frequency f) {
auto entry = current_entry();
entry.frequency_a = f;
db_.replace_entry(current_index(), entry);
freqlist_view.set_dirty();
};
}
auto& entry = database[menu_view.get_index()];
void FrequencyManagerView::on_edit_desc() {
temp_buffer_ = current_entry().description;
text_prompt(nav_, temp_buffer_, desc_edit_max, [this](std::string& new_desc) {
auto entry = current_entry();
entry.description = std::move(new_desc);
db_.replace_entry(current_index(), entry);
freqlist_view.set_dirty();
});
}
if (entry.type == RANGE) {
// User chose a frequency range entry
if (on_range_loaded)
on_range_loaded(entry.frequency_a, entry.frequency_b);
else if (on_frequency_loaded)
on_frequency_loaded(entry.frequency_a);
// TODO: Maybe return center of range if user choses a range when the app needs a unique frequency, instead of frequency_a ?
} else {
// User chose an unique frequency entry
if (on_frequency_loaded)
on_frequency_loaded(entry.frequency_a);
void FrequencyManagerView::on_add_category() {
temp_buffer_.clear();
text_prompt(nav_, temp_buffer_, 20, [this](std::string& new_name) {
if (!new_name.empty()) {
create_freqman_file(new_name);
refresh_categories();
}
});
}
void FrequencyManagerView::on_del_category() {
nav_.push<ModalMessageView>(
"Delete", "Delete " + current_category() + "\nAre you sure?", YESNO,
[this](bool choice) {
if (choice) {
db_.close(); // Ensure file is closed.
auto path = get_freqman_path(current_category());
delete_file(path);
refresh_categories();
}
});
}
void FrequencyManagerView::on_add_entry() {
freqman_entry entry{
.frequency_a = 100'000'000,
.description = std::string{"Entry "} + to_string_dec_uint(db_.entry_count()),
.type = freqman_type::Single,
};
// Add will insert below the currently selected item.
db_.insert_entry(current_index() + 1, entry);
refresh_list(1);
}
void FrequencyManagerView::on_edit_freq(rf::Frequency f) {
database[menu_view.get_index()].frequency_a = f;
save_freqman_file(file_list[categories[current_category_id].second], database);
change_category(current_category_id);
}
void FrequencyManagerView::on_del_entry() {
if (db_.empty())
return;
void FrequencyManagerView::on_edit_desc(NavigationView& nav) {
text_prompt(nav, desc_buffer, 28, [this](std::string& buffer) {
database[menu_view.get_index()].description = buffer;
save_freqman_file(file_list[categories[current_category_id].second], database);
change_category(current_category_id);
});
}
void FrequencyManagerView::on_new_category(NavigationView& nav) {
text_prompt(nav, desc_buffer, 12, [this](std::string& buffer) {
File freqman_file;
create_freqman_file(buffer, freqman_file);
refresh_list();
change_category(current_category_id);
});
}
void FrequencyManagerView::on_delete() {
if (database.empty()) {
delete_freqman_file(file_list[categories[current_category_id].second]);
refresh_list();
} else {
database.erase(database.begin() + menu_view.get_index());
save_freqman_file(file_list[categories[current_category_id].second], database);
}
change_category(current_category_id);
}
void FrequencyManagerView::refresh_widgets(const bool v) {
button_edit_freq.hidden(v);
button_edit_desc.hidden(v);
button_delete.hidden(v);
text_empty.hidden(!v);
menu_view.hidden(v);
menu_view.set_dirty();
labels.hidden(v);
// display.fill_rectangle(menu_view.screen_rect(), Color::black());
set_dirty();
}
FrequencyManagerView::~FrequencyManagerView() {
// save_freqman_file(file_list[categories[current_category_id].second], database);
nav_.push<ModalMessageView>(
"Delete", "Delete " + trim(pretty_string(current_entry(), 23)) + "\nAre you sure?", YESNO,
[this](bool choice) {
if (choice) {
db_.delete_entry(current_index());
refresh_list();
}
});
}
FrequencyManagerView::FrequencyManagerView(
NavigationView& nav)
: FreqManBaseView(nav) {
on_refresh_widgets = [this](bool v) {
refresh_widgets(v);
add_children(
{&freqlist_view,
&button_add_category,
&button_del_category,
&button_edit_entry,
&rect_padding,
&button_edit_freq,
&button_edit_desc,
&button_add_entry,
&button_del_entry});
freqlist_view.on_select = [this](size_t) {
button_edit_entry.focus();
};
// Allows for quickly exiting control.
freqlist_view.on_leave = [this]() {
button_edit_entry.focus();
};
add_children({&labels,
&button_new_category,
&menu_view,
&text_empty,
&button_edit_freq,
&button_edit_desc,
&button_delete});
menu_view.on_select = [this](FreqManUIList&) {
button_edit_freq.focus();
button_add_category.on_select = [this]() {
on_add_category();
};
button_new_category.on_select = [this, &nav](Button&) {
desc_buffer = "";
on_new_category(nav);
button_del_category.on_select = [this]() {
on_del_category();
};
button_edit_freq.on_select = [this, &nav](Button&) {
if (database.empty()) {
database.push_back({0, 0, "", SINGLE});
}
auto new_view = nav.push<FrequencyKeypadView>(database[menu_view.get_index()].frequency_a);
new_view->on_changed = [this](rf::Frequency f) {
on_edit_freq(f);
};
button_edit_entry.on_select = [this](Button&) {
on_edit_entry();
};
button_edit_desc.on_select = [this, &nav](Button&) {
if (database.empty()) {
database.push_back({0, 0, "", SINGLE});
}
desc_buffer = database[menu_view.get_index()].description;
on_edit_desc(nav);
button_edit_freq.on_select = [this](Button&) {
on_edit_freq();
};
button_delete.on_select = [this, &nav](Button&) {
on_delete();
button_edit_desc.on_select = [this](Button&) {
on_edit_desc();
};
button_add_entry.on_select = [this]() {
on_add_entry();
};
button_del_entry.on_select = [this]() {
on_del_entry();
};
}
/* FrequencyEditView *************************************/
FrequencyEditView::FrequencyEditView(
NavigationView& nav,
freqman_entry entry)
: nav_{nav},
entry_{std::move(entry)} {
add_children({&labels,
&field_type,
&field_freq_a,
&field_freq_b,
&field_modulation,
&field_bandwidth,
&field_step,
&field_tone,
&field_description,
&text_validation,
&button_save,
&button_cancel});
freqman_set_modulation_option(field_modulation);
populate_bandwidth_options();
populate_step_options();
populate_tone_options();
// Add the "invalid/unset" option. Kind of hacky, but...
field_modulation.options().insert(
field_modulation.options().begin(), {"None", -1});
field_step.options().insert(
field_step.options().begin(), {"None", -1});
field_type.set_by_value((int32_t)entry_.type);
field_type.on_change = [this](size_t, auto value) {
entry_.type = static_cast<freqman_type>(value);
refresh_ui();
};
// TODO: this pattern should be able to be wrapped up.
field_freq_a.set_value(entry_.frequency_a);
field_freq_a.on_change = [this](rf::Frequency f) {
entry_.frequency_a = f;
refresh_ui();
};
field_freq_a.on_edit = [this]() {
auto freq_view = nav_.push<FrequencyKeypadView>(field_freq_a.value());
freq_view->on_changed = [this](rf::Frequency f) {
field_freq_a.set_value(f);
};
};
field_freq_b.set_value(entry_.frequency_b);
field_freq_b.on_change = [this](rf::Frequency f) {
entry_.frequency_b = f;
refresh_ui();
};
field_freq_b.on_edit = [this]() {
auto freq_view = nav_.push<FrequencyKeypadView>(field_freq_b.value());
freq_view->on_changed = [this](rf::Frequency f) {
field_freq_b.set_value(f);
};
};
field_modulation.set_by_value((int32_t)entry_.modulation);
field_modulation.on_change = [this](size_t, auto value) {
entry_.modulation = static_cast<freqman_index_t>(value);
populate_bandwidth_options();
};
field_bandwidth.set_by_value((int32_t)entry_.bandwidth);
field_bandwidth.on_change = [this](size_t, auto value) {
entry_.bandwidth = static_cast<freqman_index_t>(value);
};
field_step.set_by_value((int32_t)entry_.step);
field_step.on_change = [this](size_t, auto value) {
entry_.step = static_cast<freqman_index_t>(value);
};
field_tone.set_by_value((int32_t)entry_.tone);
field_tone.on_change = [this](size_t, auto value) {
entry_.tone = static_cast<freqman_index_t>(value);
};
field_description.set_text(entry_.description);
field_description.on_change = [this](TextField& tf) {
entry_.description = tf.get_text();
};
field_description.on_select = [this](TextField& tf) {
temp_buffer_ = tf.get_text();
text_prompt(nav_, temp_buffer_, FreqManBaseView::desc_edit_max,
[this, &tf](std::string& new_desc) {
tf.set_text(new_desc);
});
};
button_save.on_select = [this](Button&) {
if (on_save)
on_save(std::move(entry_));
nav_.pop();
};
button_cancel.on_select = [this](Button&) {
nav_.pop();
};
refresh_ui();
}
void FrequencyEditView::focus() {
button_cancel.focus();
}
void FrequencyEditView::refresh_ui() {
// This needs to be called whenever a UI option is changed
// in a way that causes fields to be unused or would make the
// entry fail validation.
auto is_range = entry_.type == freqman_type::Range;
auto is_ham = entry_.type == freqman_type::HamRadio;
auto has_freq_b = is_range || is_ham;
field_freq_b.set_style(has_freq_b ? &Styles::white : &Styles::grey);
field_step.set_style(is_range ? &Styles::white : &Styles::grey);
field_tone.set_style(is_ham ? &Styles::white : &Styles::grey);
if (is_valid(entry_)) {
text_validation.set("Valid");
text_validation.set_style(&Styles::green);
} else {
text_validation.set("Error");
text_validation.set_style(&Styles::red);
}
}
// TODO: The following functions shouldn't be needed once
// freqman_db lookup tables are complete.
void FrequencyEditView::populate_bandwidth_options() {
OptionsField::options_t options;
options.push_back({"None", -1});
if (entry_.modulation < std::size(freqman_bandwidths)) {
auto& bandwidths = freqman_bandwidths[entry_.modulation];
for (auto i = 0u; i < bandwidths.size(); ++i) {
auto& item = bandwidths[i];
options.push_back({item.first, (OptionsField::value_t)i});
}
}
field_bandwidth.set_options(std::move(options));
}
void FrequencyEditView::populate_step_options() {
OptionsField::options_t options;
options.push_back({"None", -1});
for (auto i = 0u; i < freqman_steps.size(); ++i) {
auto& item = freqman_steps[i];
options.push_back({item.first, (OptionsField::value_t)i});
}
field_step.set_options(std::move(options));
}
void FrequencyEditView::populate_tone_options() {
using namespace tonekey;
OptionsField::options_t options;
options.push_back({"None", -1});
for (auto i = 0u; i < tone_keys.size(); ++i) {
auto& item = tone_keys[i];
options.push_back({fx100_string(item.second), (OptionsField::value_t)i});
}
field_tone.set_options(std::move(options));
}
} // namespace ui
+175 -69
View File
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
* Copyright (C) 2023 Kyle Reed
*
* This file is part of PortaPack.
*
@@ -20,94 +21,99 @@
* Boston, MA 02110-1301, USA.
*/
#include "freqman.hpp"
#include "freqman_db.hpp"
#include "ui.hpp"
#include "ui_widget.hpp"
#include "ui_painter.hpp"
#include "ui_freqlist.hpp"
#include "ui_menu.hpp"
#include "ui_navigation.hpp"
#include "ui_painter.hpp"
#include "ui_receiver.hpp"
#include "ui_textentry.hpp"
#include "freqman.hpp"
#include "ui_freqlist.hpp"
#include "ui_widget.hpp"
namespace ui {
class FreqManBaseView : public View {
public:
FreqManBaseView(
NavigationView& nav);
FreqManBaseView(NavigationView& nav);
void focus() override;
static constexpr size_t desc_edit_max = 0x80;
protected:
using option_t = std::pair<std::string, int32_t>;
using options_t = std::vector<option_t>;
using options_t = OptionsField::options_t;
NavigationView& nav_;
freqman_error error_{NO_ERROR};
options_t categories{};
std::function<void(void)> on_select_frequency{nullptr};
std::function<void(bool)> on_refresh_widgets{nullptr};
std::function<void(void)> on_select_frequency{};
void get_freqman_files();
void change_category(int32_t category_id);
void refresh_list();
void change_category(size_t new_index);
/* Access the categories directly from the OptionsField.
* This avoids holding multiple copies of the file list. */
const options_t& categories() const { return options_category.options(); }
const auto& current_category() const { return options_category.selected_index_name(); }
auto current_index() const { return freqlist_view.get_index(); }
freqman_entry current_entry() const { return db_[current_index()]; }
void refresh_categories();
void refresh_list(int delta_selected = 0);
freqman_db database{};
std::vector<std::string> file_list{};
FreqmanDB db_{};
/* The top section (category) is 20px tall. */
Labels label_category{
{{0, 4}, "Category:", Color::light_grey()}};
{{0, 2}, "Category:", Color::light_grey()}};
OptionsField options_category{
{9 * 8, 4},
14,
{9 * 8, 2},
14 /* length */,
{}};
FreqManUIList menu_view{
{0, 3 * 8, 240, 23 * 8}};
Text text_empty{
{7 * 8, 12 * 8, 16 * 8, 16},
"Empty category !",
};
FreqManUIList freqlist_view{
{0, 3 * 8, screen_width, 12 * 16 + 2 /* 2 Keeps text out of border. */}};
Button button_exit{
{16 * 8, 34 * 8, 14 * 8, 4 * 8},
{15 * 8, 17 * 16, 15 * 8, 2 * 16},
"Exit"};
private:
protected:
/* Static so selected category is persisted across UI instances. */
static size_t current_category_index;
};
// TODO: support for new category.
class FrequencySaveView : public FreqManBaseView {
public:
FrequencySaveView(NavigationView& nav, const rf::Frequency value);
std::string title() const override { return "Save freq."; };
std::string title() const override { return "Save freq"; }
private:
std::string desc_buffer{};
rf::Frequency value_{};
std::string temp_buffer_{};
freqman_entry entry_{};
void on_save_name();
void on_save_timestamp();
void save_current_file();
void refresh_ui();
BigFrequency big_display{
{4, 2 * 16, 28 * 8, 32},
{0, 2 * 16, 28 * 8, 4 * 16},
0};
Labels labels{
{{1 * 8, 12 * 8}, "Save as:", Color::white()}};
{{0 * 8, 6 * 16}, "Description:", Color::white()}};
Button button_save_name{
{1 * 8, 17 * 8, 12 * 8, 48},
"Name (set)"};
Button button_save_timestamp{
{1 * 8, 25 * 8, 12 * 8, 48},
"Timestamp:"};
LiveDateTime live_timestamp{
{14 * 8, 27 * 8, 16 * 8, 16}};
Text text_description{{0 * 8, 7 * 16, 30 * 8, 1 * 16}};
Button button_clear{
{4 * 8, 10 * 16, 10 * 8, 2 * 16},
"Clear"};
Button button_edit{
{16 * 8, 10 * 16, 10 * 8, 2 * 16},
"Edit"};
Button button_save{
{0 * 8, 17 * 16, 15 * 8, 2 * 16},
"Save"};
};
class FrequencyLoadView : public FreqManBaseView {
@@ -116,46 +122,146 @@ class FrequencyLoadView : public FreqManBaseView {
std::function<void(rf::Frequency, rf::Frequency)> on_range_loaded{};
FrequencyLoadView(NavigationView& nav);
std::string title() const override { return "Load freq."; };
private:
void refresh_widgets(const bool v);
std::string title() const override { return "Load freq"; }
};
class FrequencyManagerView : public FreqManBaseView {
public:
FrequencyManagerView(NavigationView& nav);
~FrequencyManagerView();
std::string title() const override { return "Freqman"; };
std::string title() const override { return "Freqman"; }
private:
std::string desc_buffer{};
std::string temp_buffer_{};
void refresh_widgets(const bool v);
void on_edit_freq(rf::Frequency f);
void on_edit_desc(NavigationView& nav);
void on_new_category(NavigationView& nav);
void on_delete();
void on_edit_entry();
void on_edit_freq();
void on_edit_desc();
void on_add_category();
void on_del_category();
void on_add_entry();
void on_del_entry();
Labels labels{
{{4 * 8 + 4, 26 * 8}, "Edit:", Color::light_grey()}};
NewButton button_add_category{
{23 * 8, 0 * 16, 7 * 4, 20},
{},
&bitmap_icon_new_file,
Color::white(),
true};
Button button_new_category{
{23 * 8, 2, 7 * 8, 20},
"New"};
NewButton button_del_category{
{26 * 8 + 4, 0 * 16, 7 * 4, 20},
{},
&bitmap_icon_trash,
Color::red(),
true};
Button button_edit_entry{
{0 * 8, 14 * 16 - 4, 15 * 8, 1 * 16 + 4},
"Edit"};
Rectangle rect_padding{
{15 * 8, 14 * 16 - 4, 15 * 8, 1 * 16 + 4},
Color::grey()};
Button button_edit_freq{
{0 * 8, 29 * 8, 14 * 8, 32},
{0 * 8, 15 * 16, 15 * 8, 2 * 16},
"Frequency"};
Button button_edit_desc{
{0 * 8, 34 * 8, 14 * 8, 32},
{0 * 8, 17 * 16, 15 * 8, 2 * 16},
"Description"};
Button button_delete{
{16 * 8, 29 * 8, 14 * 8, 32},
"Delete"};
NewButton button_add_entry{
{15 * 8, 15 * 16, 7 * 8 + 4, 2 * 16},
{},
&bitmap_icon_add,
Color::white(),
true};
NewButton button_del_entry{
{22 * 8 + 4, 15 * 16, 7 * 8 + 4, 2 * 16},
{},
&bitmap_icon_delete,
Color::red(),
true};
};
class FrequencyEditView : public View {
public:
std::function<void(const freqman_entry&)> on_save{};
FrequencyEditView(
NavigationView& nav,
freqman_entry entry);
std::string title() const override { return "Freqman Edit"; }
void focus() override;
private:
NavigationView& nav_;
freqman_entry entry_;
std::string temp_buffer_{};
void refresh_ui();
void populate_bandwidth_options();
void populate_step_options();
void populate_tone_options();
Labels labels{
{{5 * 8, 1 * 16}, "Edit Frequency Entry", Color::white()},
{{0 * 8, 3 * 16}, "Entry Type :", Color::light_grey()},
{{0 * 8, 4 * 16}, "Frequency A:", Color::light_grey()},
{{0 * 8, 5 * 16}, "Frequency B:", Color::light_grey()},
{{0 * 8, 6 * 16}, "Modulation :", Color::light_grey()},
{{0 * 8, 7 * 16}, "Bandwidth :", Color::light_grey()},
{{0 * 8, 8 * 16}, "Step :", Color::light_grey()},
{{0 * 8, 9 * 16}, "Tone Freq :", Color::light_grey()},
{{0 * 8, 10 * 16}, "Description:", Color::light_grey()},
};
OptionsField field_type{
{13 * 8, 3 * 16},
8,
{
{"Single", 0},
{"Range", 1},
{"HamRadio", 2},
{"Raw", 3},
}};
FrequencyField field_freq_a{{13 * 8, 4 * 16}};
FrequencyField field_freq_b{{13 * 8, 5 * 16}};
OptionsField field_modulation{{13 * 8, 6 * 16}, 5, {}};
OptionsField field_bandwidth{{13 * 8, 7 * 16}, 7, {}};
OptionsField field_step{{13 * 8, 8 * 16}, 12, {}};
OptionsField field_tone{{13 * 8, 9 * 16}, 13, {}};
TextField field_description{
{13 * 8, 10 * 16, 17 * 8, 1 * 16},
{}};
Text text_validation{
{12 * 8, 12 * 16, 5 * 8, 1 * 16},
{}};
Button button_save{
{0 * 8, 17 * 16, 15 * 8, 2 * 16},
"Save"};
Button button_cancel{
{15 * 8, 17 * 16, 15 * 8, 2 * 16},
"Cancel"};
};
} /* namespace ui */
void freqman_set_bandwidth_option(freqman_index_t modulation, ui::OptionsField& option);
void freqman_set_modulation_option(ui::OptionsField& option);
void freqman_set_step_option(ui::OptionsField& option);
void freqman_set_step_option_short(ui::OptionsField& option);
void freqman_set_tone_option(ui::OptionsField& option);
+2 -6
View File
@@ -25,7 +25,6 @@
#include "ui_freqman.hpp"
#include "baseband_api.hpp"
#include "cpld_update.hpp"
#include "string_format.hpp"
using namespace portapack;
@@ -180,13 +179,12 @@ void JammerView::focus() {
JammerView::~JammerView() {
transmitter_model.disable();
hackrf::cpld::load_sram_no_verify(); // to leave all RX ok, without ghost signal problem at the exit .
baseband::shutdown(); // better this function at the end, not load_sram() that sometimes produces hang up.
baseband::shutdown();
}
void JammerView::on_retune(const rf::Frequency freq, const uint32_t range) {
if (freq) {
transmitter_model.set_tuning_frequency(freq);
transmitter_model.set_target_frequency(freq);
text_range_number.set(to_string_dec_uint(range, 2));
}
}
@@ -261,9 +259,7 @@ void JammerView::start_tx() {
button_transmit.set_style(&style_cancel);
button_transmit.set_text("STOP");
transmitter_model.set_sampling_rate(3072000U);
transmitter_model.set_rf_amp(field_amp.value());
transmitter_model.set_baseband_bandwidth(3500000U);
transmitter_model.set_tx_gain(field_gain.value());
transmitter_model.enable();
+5
View File
@@ -29,6 +29,7 @@
#include "message.hpp"
#include "jammer.hpp"
#include "lfsr_random.hpp"
#include "radio_state.hpp"
using namespace jammer;
@@ -99,6 +100,10 @@ class JammerView : public View {
private:
NavigationView& nav_;
TxRadioState radio_state_{
3500000 /* bandwidth */,
3072000 /* sampling rate */
};
void start_tx();
void on_timer();
+4 -8
View File
@@ -23,7 +23,6 @@
#include "ui_keyfob.hpp"
#include "baseband_api.hpp"
#include "cpld_update.hpp"
#include "string_format.hpp"
using namespace portapack;
@@ -141,8 +140,7 @@ KeyfobView::~KeyfobView() {
settings.save("tx_keyfob", &app_settings);
transmitter_model.disable();
hackrf::cpld::load_sram_no_verify(); // to leave all RX ok, without ghost signal problem at the exit .
baseband::shutdown(); // better this function at the end, not load_sram() that sometimes produces hang up.
baseband::shutdown();
}
void KeyfobView::update_progress(const uint32_t progress) {
@@ -190,8 +188,6 @@ void KeyfobView::start_tx() {
size_t bitstream_length = generate_frame();
transmitter_model.set_sampling_rate(OOK_SAMPLERATE);
transmitter_model.set_baseband_bandwidth(1750000);
transmitter_model.enable();
baseband::set_ook_data(
@@ -236,12 +232,12 @@ KeyfobView::KeyfobView(
options_make.set_selected_index(0);
transmitter_model.set_tuning_frequency(433920000); // Fixed 433.92MHz
transmitter_model.set_target_frequency(433920000); // Fixed 433.92MHz
tx_view.on_edit_frequency = [this, &nav]() {
auto new_view = nav.push<FrequencyKeypadView>(transmitter_model.tuning_frequency());
auto new_view = nav.push<FrequencyKeypadView>(transmitter_model.target_frequency());
new_view->on_changed = [this](rf::Frequency f) {
transmitter_model.set_tuning_frequency(f);
transmitter_model.set_target_frequency(f);
};
};
+7 -3
View File
@@ -24,6 +24,7 @@
#include "ui_transmitter.hpp"
#include "transmitter_model.hpp"
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "encoders.hpp"
using namespace encoders;
@@ -42,9 +43,12 @@ class KeyfobView : public View {
private:
NavigationView& nav_;
// app save settings
std::app_settings settings{};
std::app_settings::AppSettings app_settings{};
TxRadioState radio_state_{
1750000 /* bandwidth */,
OOK_SAMPLERATE /* sampling rate */
};
app_settings::SettingsManager settings_{
"tx_keyfob", , app_settings::Mode::TX};
// 1013210ns / bit
static constexpr uint32_t subaru_samples_per_bit = (OOK_SAMPLERATE * 0.00101321);
+3 -22
View File
@@ -24,10 +24,8 @@
#include "ui_modemsetup.hpp"
#include "lcr.hpp"
#include "modems.hpp"
#include "baseband_api.hpp"
#include "string_format.hpp"
#include "cpld_update.hpp"
#include "serializer.hpp"
@@ -40,13 +38,8 @@ void LCRView::focus() {
}
LCRView::~LCRView() {
// save app settings
app_settings.tx_frequency = transmitter_model.tuning_frequency();
settings.save("tx_lcr", &app_settings);
transmitter_model.disable();
hackrf::cpld::load_sram_no_verify(); // to leave all RX ok, without ghost signal problem at the exit.
baseband::shutdown(); // better this function at the end, not load_sram() that sometimes produces hang up.
baseband::shutdown();
}
/*
@@ -134,9 +127,6 @@ void LCRView::start_tx(const bool scan) {
modems::generate_data(lcr::generate_message(rgsb, litterals_list, options_ec.selected_index()), lcr_message_data);
transmitter_model.set_tuning_frequency(persistent_memory::tuned_frequency());
transmitter_model.set_sampling_rate(AFSK_TX_SAMPLERATE);
transmitter_model.set_baseband_bandwidth(1750000);
transmitter_model.enable();
memcpy(shared_memory.bb_data.data, lcr_message_data, sizeof(lcr_message_data));
@@ -176,15 +166,6 @@ LCRView::LCRView(NavigationView& nav) {
&button_clear,
&tx_view});
// load app settings
auto rc = settings.load("tx_lcr", &app_settings);
if (rc == SETTINGS_OK) {
transmitter_model.set_rf_amp(app_settings.tx_amp);
transmitter_model.set_channel_bandwidth(app_settings.channel_bandwidth);
transmitter_model.set_tuning_frequency(app_settings.tx_frequency);
transmitter_model.set_tx_gain(app_settings.tx_gain);
}
options_scanlist.set_selected_index(0);
const auto button_set_am_fn = [this, &nav](Button& button) {
@@ -251,9 +232,9 @@ LCRView::LCRView(NavigationView& nav) {
};
tx_view.on_edit_frequency = [this, &nav]() {
auto new_view = nav.push<FrequencyKeypadView>(transmitter_model.tuning_frequency());
auto new_view = nav.push<FrequencyKeypadView>(transmitter_model.target_frequency());
new_view->on_changed = [this](rf::Frequency f) {
transmitter_model.set_tuning_frequency(f);
transmitter_model.set_target_frequency(f);
};
};
+8 -3
View File
@@ -26,8 +26,10 @@
#include "ui_transmitter.hpp"
#include "message.hpp"
#include "modems.hpp"
#include "transmitter_model.hpp"
#include "app_settings.hpp"
#include "radio_state.hpp"
namespace ui {
@@ -79,9 +81,12 @@ class LCRView : public View {
SCAN
};
// app save settings
std::app_settings settings{};
std::app_settings::AppSettings app_settings{};
TxRadioState radio_state_{
1750000 /* bandwidth */,
AFSK_TX_SAMPLERATE /* sampling rate */
};
app_settings::SettingsManager settings_{
"tx_lcr", app_settings::Mode::TX};
tx_modes tx_mode = IDLE;
uint8_t scan_count{0}, scan_index{0};
+35 -65
View File
@@ -23,9 +23,11 @@
#include "ui_level.hpp"
#include "ui_fileman.hpp"
#include "ui_freqman.hpp"
#include "file.hpp"
using namespace portapack;
using namespace tonekey;
using portapack::memory::map::backup_ram;
namespace ui {
@@ -35,13 +37,6 @@ void LevelView::focus() {
}
LevelView::~LevelView() {
// save app settings
app_settings.lna = field_lna.value();
app_settings.vga = field_vga.value();
app_settings.rx_amp = field_rf_amp.value();
settings.save("level", &app_settings);
receiver_model.disable();
baseband::shutdown();
}
@@ -71,42 +66,31 @@ LevelView::LevelView(NavigationView& nav)
change_mode(NFM_MODULATION); // Start on AM
field_mode.set_by_value(NFM_MODULATION); // Reflect the mode into the manual selector
// HELPER: Pre-setting a manual range, based on stored frequency
freq = persistent_memory::tuned_frequency();
receiver_model.set_tuning_frequency(freq);
button_frequency.set_text("<" + to_string_short_freq(freq) + " MHz>");
// load auto common app settings
auto rc = settings.load("level", &app_settings);
if (rc == SETTINGS_OK) {
field_lna.set_value(app_settings.lna);
field_vga.set_value(app_settings.vga);
field_rf_amp.set_value(app_settings.rx_amp);
receiver_model.set_rf_amp(app_settings.rx_amp);
}
freq_ = receiver_model.target_frequency();
button_frequency.set_text("<" + to_string_short_freq(freq_) + " MHz>");
button_frequency.on_select = [this, &nav](ButtonWithEncoder& button) {
auto new_view = nav_.push<FrequencyKeypadView>(freq);
auto new_view = nav_.push<FrequencyKeypadView>(freq_);
new_view->on_changed = [this, &button](rf::Frequency f) {
freq = f;
receiver_model.set_tuning_frequency(f); // Retune to actual freq
button_frequency.set_text("<" + to_string_short_freq(freq) + " MHz>");
freq_ = f;
receiver_model.set_target_frequency(f); // Retune to actual freq
button_frequency.set_text("<" + to_string_short_freq(freq_) + " MHz>");
};
};
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);
if (freq < 1) {
freq = 1;
freq_ = freq_ + (button_frequency.get_encoder_delta() * def_step);
if (freq_ < 1) {
freq_ = 1;
}
if (freq > (MAX_UFREQ - def_step)) {
freq = MAX_UFREQ;
if (freq_ > (MAX_UFREQ - def_step)) {
freq_ = MAX_UFREQ;
}
button_frequency.set_encoder_delta(0);
receiver_model.set_tuning_frequency(freq); // Retune to actual freq
button_frequency.set_text("<" + to_string_short_freq(freq) + " MHz>");
receiver_model.set_target_frequency(freq_); // Retune to actual freq
button_frequency.set_text("<" + to_string_short_freq(freq_) + " MHz>");
};
field_mode.on_change = [this](size_t, OptionsField::value_t v) {
@@ -196,8 +180,6 @@ size_t LevelView::change_mode(freqman_index_t new_mod) {
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); };
receiver_model.set_sampling_rate(3072000);
receiver_model.set_baseband_bandwidth(1750000);
text_ctcss.set(" ");
break;
case NFM_MODULATION:
@@ -208,8 +190,6 @@ size_t LevelView::change_mode(freqman_index_t new_mod) {
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); };
receiver_model.set_sampling_rate(3072000);
receiver_model.set_baseband_bandwidth(1750000);
break;
case WFM_MODULATION:
freqman_set_bandwidth_option(new_mod, field_bw);
@@ -219,45 +199,35 @@ size_t LevelView::change_mode(freqman_index_t new_mod) {
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); };
receiver_model.set_sampling_rate(3072000);
receiver_model.set_baseband_bandwidth(1750000);
text_ctcss.set(" ");
break;
case SPEC_MODULATION:
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.set_by_value(0);
field_bw.on_change = [this](size_t, OptionsField::value_t sampling_rate) {
auto anti_alias_baseband_bandwidth_filter = filter_bandwidth_for_sampling_rate(sampling_rate);
receiver_model.set_sampling_rate(sampling_rate);
receiver_model.set_baseband_bandwidth(anti_alias_baseband_bandwidth_filter);
};
default:
break;
}
if (new_mod != SPEC_MODULATION) {
// reset receiver model to fix bug when going from SPEC to audio, the sound is distorded
receiver_model.set_sampling_rate(3072000);
receiver_model.set_baseband_bandwidth(1750000);
}
return step_mode.selected_index();
}
void LevelView::handle_coded_squelch(const uint32_t value) {
static int32_t last_idx = -1;
float diff, min_diff = value;
size_t min_idx{0};
size_t c;
if (field_mode.selected_index() != NFM_MODULATION) {
text_ctcss.set(" ");
return;
}
// Find nearest match
for (c = 0; c < tone_keys.size(); c++) {
diff = abs(((float)value / 100.0) - tone_keys[c].second);
if (diff < min_diff) {
min_idx = c;
min_diff = diff;
}
}
// Arbitrary confidence threshold
if (last_idx < 0 || (unsigned)last_idx != min_idx) {
last_idx = min_idx;
if (min_diff < 40)
text_ctcss.set("T: " + tone_keys[min_idx].first);
else
text_ctcss.set(" ");
}
if (field_mode.selected_index() == NFM_MODULATION)
text_ctcss.set(tone_key_string_by_value(value, text_ctcss.parent_rect().width() / 8));
else
text_ctcss.set(" ");
}
} /* namespace ui */
+22 -19
View File
@@ -24,20 +24,21 @@
#ifndef _UI_LEVEL
#define _UI_LEVEL
#include "ui.hpp"
#include "receiver_model.hpp"
#include "ui_receiver.hpp"
#include "ui_styles.hpp"
#include "freqman.hpp"
#include "analog_audio_app.hpp"
#include "audio.hpp"
#include "ui_mictx.hpp"
#include "portapack_persistent_memory.hpp"
#include "baseband_api.hpp"
#include "ui_spectrum.hpp"
#include "string_format.hpp"
#include "file.hpp"
#include "app_settings.hpp"
#include "audio.hpp"
#include "baseband_api.hpp"
#include "file.hpp"
#include "freqman_db.hpp"
#include "portapack_persistent_memory.hpp"
#include "radio_state.hpp"
#include "receiver_model.hpp"
#include "string_format.hpp"
#include "ui.hpp"
#include "ui_mictx.hpp"
#include "ui_receiver.hpp"
#include "ui_spectrum.hpp"
#include "ui_styles.hpp"
namespace ui {
@@ -53,13 +54,18 @@ class LevelView : public View {
private:
NavigationView& nav_;
RxRadioState radio_state_{};
app_settings::SettingsManager settings_{
"rx_level", app_settings::Mode::RX};
size_t change_mode(freqman_index_t mod_type);
void on_statistics_update(const ChannelStatistics& statistics);
void set_display_freq(int64_t freq);
// TODO: needed?
int32_t db{0};
long long int MAX_UFREQ = {7200000000}; // maximum usable freq
rf::Frequency freq = {0};
rf::Frequency freq_ = {0};
Labels labels{
{{0 * 8, 0 * 16}, "LNA: VGA: AMP: VOL: ", Color::light_grey()},
@@ -85,7 +91,7 @@ class LevelView : public View {
OptionsField field_mode{
{15 * 8, 1 * 16},
3,
4,
{}};
OptionsField step_mode{
@@ -104,11 +110,11 @@ class LevelView : public View {
{"audio off", 0},
{"audio on", 1}
//{"tone on", 2},
//{"tone off", 2},
//{"tone off", 3},
}};
Text text_ctcss{
{22 * 8, 3 * 16 + 4, 14 * 8, 1 * 8},
{22 * 8, 3 * 16 + 4, 8 * 8, 1 * 8},
""};
// RSSI: XX/XX/XXX,dt: XX
@@ -168,9 +174,6 @@ class LevelView : public View {
[this](const Message* const p) {
this->on_statistics_update(static_cast<const ChannelStatisticsMessage*>(p)->statistics);
}};
// app save settings
std::app_settings settings{};
std::app_settings::AppSettings app_settings{};
};
} /* namespace ui */
+100 -131
View File
@@ -22,12 +22,15 @@
*/
#include "ui_looking_glass_app.hpp"
#include "convert.hpp"
#include "file_reader.hpp"
#include "string_format.hpp"
using namespace portapack;
namespace ui {
void GlassView::focus() {
button_marker.focus();
range_presets.focus();
}
GlassView::~GlassView() {
@@ -38,7 +41,7 @@ GlassView::~GlassView() {
void GlassView::get_max_power(const ChannelSpectrum& spectrum, uint8_t bin, uint8_t& max_power) {
if (mode == LOOKING_GLASS_SINGLEPASS) {
// analog audio app like view
// <20MHz spectrum mode
if (bin < 120) {
if (spectrum.db[SPEC_NB_BINS - 120 + bin] > max_power)
max_power = spectrum.db[SPEC_NB_BINS - 120 + bin];
@@ -47,15 +50,13 @@ void GlassView::get_max_power(const ChannelSpectrum& spectrum, uint8_t bin, uint
max_power = spectrum.db[bin - 120];
}
} else {
// view is made in multiple pass, use original bin picking
// FAST mode: center 12 bins are ignored in fast mode , (DC spike is blanked) leftmost and rightmost 2 bins are ignored
// SLOW mode: leftmost 'offset' bins are ignored
// FAST or SLOW mode
if (bin < 120) {
if (spectrum.db[SPEC_NB_BINS - offset - 120 + bin] > max_power)
max_power = spectrum.db[SPEC_NB_BINS - offset - 120 + bin];
if (spectrum.db[134 + bin] > max_power)
max_power = spectrum.db[134 + bin];
} else {
if (spectrum.db[offset + bin - 120] > max_power)
max_power = spectrum.db[offset + bin - 120];
if (spectrum.db[bin - 118] > max_power)
max_power = spectrum.db[bin - 118];
}
}
}
@@ -66,7 +67,7 @@ rf::Frequency GlassView::get_freq_from_bin_pos(uint8_t pos) {
// starting from the middle, minus 8 ignored bin on each side. Since pos is [-120,120] after the (pos - 120), it's divided by SCREEN_W(240)/2 => 120
freq_at_pos = f_center_ini + ((pos - 120) * ((looking_glass_range - ((16 * looking_glass_range) / SPEC_NB_BINS)) / 2)) / (SCREEN_W / 2);
} else
freq_at_pos = f_min - (offset * each_bin_size) + (pos * looking_glass_range) / SCREEN_W;
freq_at_pos = f_min + (2 * offset * each_bin_size) + (pos * looking_glass_range) / SCREEN_W;
return freq_at_pos;
}
@@ -77,23 +78,6 @@ void GlassView::on_marker_change() {
PlotMarker(marker_pixel_index); // Refresh marker on screen
}
// Returns the next multiple of num that is a multiple of multiplier
int64_t GlassView::next_mult_of(int64_t num, int64_t multiplier) {
return ((num / multiplier) + 1) * multiplier;
}
void GlassView::adjust_range(int64_t* f_min, int64_t* f_max, int64_t width) {
int64_t span = *f_max - *f_min;
int64_t num_intervals = span / width;
if (span % width != 0) {
num_intervals++;
}
int64_t new_span = num_intervals * width;
int64_t delta_span = (new_span - span) / 2;
*f_min -= delta_span;
*f_max += delta_span;
}
void GlassView::retune() {
// Start a new sweep.
// Tune rx for this new slice directly because the model
@@ -166,6 +150,31 @@ void GlassView::add_spectrum_pixel(uint8_t power) {
}
}
bool GlassView::process_bins(uint8_t* powerlevel) {
bins_Hz_size += each_bin_size; // add pixel to fulfilled bag of Hz
if (bins_Hz_size >= marker_pixel_step) // new pixel fullfilled
{
if (*powerlevel > min_color_power)
add_spectrum_pixel(*powerlevel); // Pixel will represent max_power
else
add_spectrum_pixel(0); // Filtered out, show black
*powerlevel = 0;
if (!pixel_index) // Received indication that a waterfall line has been completed
{
bins_Hz_size = 0; // Since this is an entire pixel line, we don't carry "Pixels into next bin"
if (mode != LOOKING_GLASS_SINGLEPASS) {
f_center = f_center_ini;
retune();
} else
baseband::spectrum_streaming_start();
return true; // signal a new line
}
bins_Hz_size -= marker_pixel_step; // reset bins size, but carrying the eventual excess Hz into next pixel
}
return false;
}
// Apparently, the spectrum object returns an array of SPEC_NB_BINS (256) bins
// Each having the radio signal power for it's corresponding frequency slot
void GlassView::on_channel_spectrum(const ChannelSpectrum& spectrum) {
@@ -174,31 +183,19 @@ void GlassView::on_channel_spectrum(const ChannelSpectrum& spectrum) {
// we actually need SCREEN_W (240) of those bins
for (bin = 0; bin < bin_length; bin++) {
get_max_power(spectrum, bin, max_power);
if (ignore_dc && bin == 119) {
bins_Hz_size += 12 * each_bin_size; // add the ignored DC spike to "pixel fulfilled bag of Hz"
}
bins_Hz_size += each_bin_size; // add this bin Hz count into the "pixel fulfilled bag of Hz"
if (bins_Hz_size >= marker_pixel_step) // new pixel fullfilled
{
if (min_color_power < max_power)
add_spectrum_pixel(max_power); // Pixel will represent max_power
else
add_spectrum_pixel(0); // Filtered out, show black
max_power = 0;
if (!pixel_index) // Received indication that a waterfall line has been completed
{
bins_Hz_size = 0; // Since this is an entire pixel line, we don't carry "Pixels into next bin"
if (mode != LOOKING_GLASS_SINGLEPASS) {
f_center = f_center_ini;
retune();
} else
baseband::spectrum_streaming_start();
return; // signal a new line
// process dc spike if enable
if (bin == 119) {
uint8_t next_max_power = 0;
get_max_power(spectrum, bin + 1, next_max_power);
for (uint8_t it = 0; it < ignore_dc; it++) {
uint8_t med_max_power = (max_power + next_max_power) / 2; // due to the way process_bins works we have to keep resetting the color
if (process_bins(&med_max_power) == true)
return; // new line signaled, return
}
bins_Hz_size -= marker_pixel_step; // reset bins size, but carrying the eventual excess Hz into next pixel
}
// process actual bin
if (process_bins(&max_power) == true)
return; // new line signaled, return
}
if (mode != LOOKING_GLASS_SINGLEPASS) {
f_center += looking_glass_step;
@@ -225,36 +222,33 @@ void GlassView::on_range_changed() {
f_max = f_max * MHZ_DIV;
looking_glass_range = f_max - f_min;
if (looking_glass_range <= LOOKING_GLASS_SLICE_WIDTH_MAX) {
adjust_range(&f_min, &f_max, SCREEN_W);
looking_glass_range = f_max - f_min;
// if the view is done in one pass, show it like in analog_audio_app
mode = LOOKING_GLASS_SINGLEPASS;
offset = 0;
offset = 2;
bin_length = SCREEN_W;
ignore_dc = 0;
looking_glass_bandwidth = looking_glass_range;
looking_glass_sampling_rate = looking_glass_bandwidth;
each_bin_size = looking_glass_bandwidth / SCREEN_W;
looking_glass_step = looking_glass_bandwidth;
f_center_ini = f_min + (looking_glass_bandwidth / 2); // Initial center frequency for sweep
} else { // if ( mode == LOOKING_GLASS_SLOWSCAN || mode == LOOKING_GLASS_FASTSCAN )
f_center_ini = f_min + (looking_glass_bandwidth / 2); // Initial center frequency for sweep
portapack::display.fill_rectangle({17 * 8, 4 * 16, 2 * 8, 16}, Color::black()); // Clear old marker and whole marker rectangle btw
} else {
// view is made in multiple pass, use original bin picking
mode = scan_type.selected_index_value();
if (mode == LOOKING_GLASS_FASTSCAN) {
offset = 2;
bin_length = SCREEN_W;
ignore_dc = 1;
} else { // if( mode == LOOKING_GLASS_SLOWSCAN )
offset = 16;
bin_length = 80;
ignore_dc = 0;
}
adjust_range(&f_min, &f_max, SCREEN_W);
looking_glass_range = f_max - f_min;
looking_glass_bandwidth = LOOKING_GLASS_SLICE_WIDTH_MAX;
looking_glass_sampling_rate = LOOKING_GLASS_SLICE_WIDTH_MAX;
each_bin_size = LOOKING_GLASS_SLICE_WIDTH_MAX / SPEC_NB_BINS;
looking_glass_step = (bin_length + (ignore_dc * 12)) * each_bin_size;
if (mode == LOOKING_GLASS_FASTSCAN) {
offset = 2;
ignore_dc = 4;
bin_length = SCREEN_W;
} else { // if( mode == LOOKING_GLASS_SLOWSCAN )
offset = 2;
bin_length = 80;
ignore_dc = 0;
}
looking_glass_step = (bin_length + ignore_dc) * each_bin_size;
f_center_ini = f_min - (offset * each_bin_size) + (looking_glass_bandwidth / 2); // Initial center frequency for sweep
}
search_span = looking_glass_range / MHZ_DIV;
@@ -280,7 +274,7 @@ void GlassView::on_range_changed() {
receiver_model.set_squelch_level(0);
f_center = f_center_ini; // Reset sweep into first slice
baseband::set_spectrum(looking_glass_bandwidth, field_trigger.value());
receiver_model.set_tuning_frequency(f_center); // tune rx for this slice
receiver_model.set_target_frequency(f_center); // tune rx for this slice
}
void GlassView::PlotMarker(uint8_t pos) {
@@ -476,10 +470,10 @@ GlassView::GlassView(
};
button_marker.on_select = [this](ButtonWithEncoder&) {
receiver_model.set_tuning_frequency(marker); // Center tune rx in marker freq.
receiver_model.set_frequency_step(MHZ_DIV); // Preset a 1 MHz frequency step into RX -> AUDIO
nav_.pop();
nav_.push<AnalogAudioView>(); // Jump into audio view
receiver_model.set_target_frequency(marker); // Center tune rx in marker freq.
auto settings = receiver_model.settings();
settings.frequency_step = MHZ_DIV; // Preset a 1 MHz frequency step into RX -> AUDIO
nav_.replace<AnalogAudioView>(settings); // Jump into audio view
};
field_trigger.on_change = [this](int32_t v) {
@@ -500,10 +494,10 @@ GlassView::GlassView(
};
button_jump.on_select = [this](Button&) {
receiver_model.set_tuning_frequency(max_freq_hold); // Center tune rx in marker freq.
receiver_model.set_frequency_step(MHZ_DIV); // Preset a 1 MHz frequency step into RX -> AUDIO
nav_.pop();
nav_.push<AnalogAudioView>(); // Jump into audio view
receiver_model.set_target_frequency(max_freq_hold); // Center tune rx in marker freq.
auto settings = receiver_model.settings();
settings.frequency_step = MHZ_DIV; // Preset a 1 MHz frequency step into RX -> AUDIO
nav_.replace<AnalogAudioView>(settings); // Jump into audio view
};
button_rst.on_select = [this](Button&) {
@@ -519,7 +513,6 @@ GlassView::GlassView(
marker_pixel_index = 120;
on_range_changed();
receiver_model.set_modulation(ReceiverModel::Mode::SpectrumAnalysis);
receiver_model.set_sampling_rate(looking_glass_sampling_rate); // 20mhz
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);
@@ -527,71 +520,47 @@ GlassView::GlassView(
}
void GlassView::load_Presets() {
File presets_file; // LOAD /WHIPCALC/ANTENNAS.TXT from microSD
auto result = presets_file.open("LOOKINGGLASS/PRESETS.TXT");
presets_db.clear(); // Start with fresh db
if (result.is_valid()) {
presets_Default(); // There is no txt, store a default range
} else {
std::string line; // There is a txt file
char one_char[1]; // Read it char by char
for (size_t pointer = 0; pointer < presets_file.size(); pointer++) {
presets_file.seek(pointer);
presets_file.read(one_char, 1);
if ((int)one_char[0] > 31) { // ascii space upwards
line += one_char[0]; // Add it to the textline
} else if (one_char[0] == '\n') { // New Line
txtline_process(line); // make sense of this textline
line.clear(); // Ready for next textline
}
File presets_file;
auto error = presets_file.open("LOOKINGGLASS/PRESETS.TXT");
presets_db.clear();
if (!error) {
auto reader = FileLineReader(presets_file);
for (const auto& line : reader) {
if (line.length() == 0 || line[0] == '#')
continue;
auto cols = split_string(line, ',');
if (cols.size() != 3)
continue;
preset_entry entry{};
parse_int(cols[0], entry.min);
parse_int(cols[1], entry.max);
entry.label = trimr(cols[2]);
if (entry.min == 0 || entry.max == 0 || entry.min >= entry.max)
continue; // Invalid line.
presets_db.emplace_back(std::move(entry));
}
if (line.length() > 0)
txtline_process(line); // Last line had no newline at end ?
if (!presets_db.size())
presets_Default(); // no antenna on txt, use default
}
// Couldn't load any from the file, load a default instead.
if (presets_db.empty())
presets_Default();
populate_Presets();
}
void GlassView::txtline_process(std::string& line) {
if (line.find("#") != std::string::npos)
return; // Line is just a comment
size_t comma = line.find(","); // Get first comma position
if (comma == std::string::npos)
return; // No comma at all
size_t previous = 0;
preset_entry new_preset;
new_preset.min = std::stoi(line.substr(0, comma));
if (!new_preset.min)
return; // No frequency!
previous = comma + 1;
comma = line.find(",", previous); // Search for next delimiter
if (comma == std::string::npos)
return; // No comma at all
new_preset.max = std::stoi(line.substr(previous, comma - previous));
if (!new_preset.max)
return; // No frequency!
new_preset.label = line.substr(comma + 1);
if (new_preset.label.size() == 0)
return; // No label ?
presets_db.push_back(new_preset); // Add this preset.
}
void GlassView::populate_Presets() {
using option_t = std::pair<std::string, int32_t>;
using options_t = std::vector<option_t>;
options_t entries;
for (preset_entry preset : presets_db) { // go thru all available presets
for (const auto& preset : presets_db)
entries.emplace_back(preset.label, entries.size());
}
range_presets.set_options(entries);
}
@@ -26,7 +26,9 @@
#include "ui.hpp"
#include "portapack.hpp"
#include "app_settings.hpp"
#include "baseband_api.hpp"
#include "radio_state.hpp"
#include "receiver_model.hpp"
#include "ui_widget.hpp"
#include "ui_navigation.hpp"
@@ -37,6 +39,7 @@
#include "spectrum_color_lut.hpp"
namespace ui {
#define LOOKING_GLASS_SLICE_WIDTH_MAX 20000000
#define MHZ_DIV 1000000
@@ -60,7 +63,7 @@ class GlassView : public View {
GlassView& operator=(const GlassView& nav);
~GlassView();
std::string title() const override { return "LookingGlass"; };
std::string title() const override { return "Looking Glass"; };
void on_show() override;
void on_hide() override;
@@ -68,6 +71,9 @@ class GlassView : public View {
private:
NavigationView& nav_;
RxRadioState radio_state_{ReceiverModel::Mode::SpectrumAnalysis};
app_settings::SettingsManager settings_{
"rx_glass", app_settings::Mode::RX};
struct preset_entry {
rf::Frequency min{};
@@ -81,12 +87,12 @@ class GlassView : public View {
void get_max_power(const ChannelSpectrum& spectrum, uint8_t bin, uint8_t& max_power);
rf::Frequency get_freq_from_bin_pos(uint8_t pos);
void on_marker_change();
int64_t next_mult_of(int64_t num, int64_t multiplier);
void adjust_range(int64_t* f_min, int64_t* f_max, int64_t width);
void retune();
bool move_to_next_position();
bool process_bins(uint8_t* powerlevel);
void on_channel_spectrum(const ChannelSpectrum& spectrum);
void do_timers();
int64_t next_mult_of(int64_t num, int64_t multiplier);
void adjust_range(int64_t* f_min, int64_t* f_max, int64_t width);
void on_range_changed();
void on_lna_changed(int32_t v_db);
void on_vga_changed(int32_t v_db);
+116 -101
View File
@@ -22,22 +22,21 @@
#include "ui_mictx.hpp"
#include "baseband_api.hpp"
#include "audio.hpp"
#include "wm8731.hpp"
using wolfson::wm8731::WM8731;
#include "tonesets.hpp"
#include "portapack_hal.hpp"
#include "cpld_update.hpp"
#include "string_format.hpp"
#include "baseband_api.hpp"
#include "irq_controls.hpp"
#include "ui_freqman.hpp"
#include "portapack_hal.hpp"
#include "string_format.hpp"
#include "tonesets.hpp"
#include "ui_tone_key.hpp"
#include "wm8731.hpp"
#include <cstring>
using namespace tonekey;
using namespace portapack;
using wolfson::wm8731::WM8731;
WM8731 audio_codec_wm8731{i2c0, 0x1a};
@@ -68,6 +67,7 @@ void MicTXView::on_tx_progress(const bool done) {
}
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,
@@ -86,7 +86,7 @@ void MicTXView::set_tx(bool enable) {
rxaudio(false); // Then turn off audio RX
transmitting = true;
configure_baseband();
transmitter_model.set_tuning_frequency(tx_frequency); // Now,no need: transmitter_model.set_tx_gain(tx_gain), nor (rf_amp);
transmitter_model.set_target_frequency(tx_frequency); // Now, no need: transmitter_model.set_tx_gain(tx_gain), nor (rf_amp);
transmitter_model.enable();
portapack::pin_i2s0_rx_sda.mode(3); // This is already done in audio::init but gets changed by the CPLD overlay reprogramming
} else {
@@ -140,48 +140,37 @@ void MicTXView::do_timing() {
}
}
/* Hmmmm. Maybe useless now.
void MicTXView::on_tuning_frequency_changed(rf::Frequency f) {
transmitter_model.set_tuning_frequency(f);
//if ( rx_enabled )
receiver_model.set_tuning_frequency(f); //Update freq also for RX
}
*/
void MicTXView::rxaudio(bool is_on) {
if (is_on) {
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
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 (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)
if (enable_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,40k).
} else { // NFM BW:8k5 or 11k / FM BW 16k
// 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_nbfm_configuration(n); is called above, depending user's selection (8k5, 11k, 16k).
}
}
receiver_model.set_sampling_rate(3072000);
receiver_model.set_baseband_bandwidth(1750000);
// receiver_model.set_tuning_frequency(field_frequency.value()); //probably this too can be commented out.
if (bool_same_F_tx_rx_enabled) // when stop TX ,define to which freq RX we return
receiver_model.set_tuning_frequency(tx_frequency); // Update freq also for RX = TX
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_tuning_frequency(rx_frequency); // Now with seperate freq controls!
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.enable();
hackrf::cpld::load_sram_no_verify(); // to have a good RX without any ghost inside Mic App
audio::output::start();
} else { // These incredibly convoluted steps are required for the vumeter to reappear when stopping RX.
receiver_model.set_modulation(ReceiverModel::Mode::NarrowbandFMAudio); // This fixes something with AM RX...
@@ -207,7 +196,7 @@ 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.
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,
@@ -271,7 +260,7 @@ MicTXView::MicTXView(
mic_gain = v / 10.0;
configure_baseband();
};
options_gain.set_selected_index(1); // x1.0 preselected default.
options_gain.set_selected_index(1); // x1.0 preselected default.
if (audio::debug::codec_name() == "WM8731") {
options_wm8731_boost_mode.on_change = [this](size_t, int8_t v) {
@@ -279,45 +268,45 @@ MicTXView::MicTXView(
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)
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)
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)
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)
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_0ff
configure_baseband(); // to update in real timme,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); // 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 -
};
options_wm8731_boost_mode.set_selected_index(3); // preset GUI index 3 as default WM -> -02 dB's .
options_wm8731_boost_mode.set_selected_index(3); // preset GUI index 3 as default WM -> -02 dB's.
} 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)
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,
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.
configure_baseband(); // sending fixed >>8_FM, var-parameters msg, to audiotx from this M0 to M4 process.
};
}
// options_ak4951_alc_mode.set_selected_index(0);
tx_frequency = transmitter_model.tuning_frequency();
field_frequency.set_value(transmitter_model.tuning_frequency());
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
transmitter_model.set_tuning_frequency(f);
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_tuning_frequency(f); // Update common freq also for RX
receiver_model.set_target_frequency(f); // Update common freq also for RX
}
};
field_frequency.on_edit = [this, &nav]() {
@@ -327,10 +316,10 @@ MicTXView::MicTXView(
new_view->on_changed = [this](rf::Frequency f) {
tx_frequency = f;
if (!rx_enabled) {
transmitter_model.set_tuning_frequency(f);
transmitter_model.set_target_frequency(f);
} else {
if (bool_same_F_tx_rx_enabled)
receiver_model.set_tuning_frequency(f); // Update freq also for RX
receiver_model.set_target_frequency(f); // Update freq also for RX
}
this->field_frequency.set_value(f);
set_dirty();
@@ -340,11 +329,11 @@ MicTXView::MicTXView(
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
field_bw.set_value(10); // 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)
// 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 }
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;
@@ -353,7 +342,7 @@ MicTXView::MicTXView(
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,
options_t rxbw; // Aux structure to change dynamically field_rxbw contents,
switch (v) {
case 0: //{ "FM", 0 }
@@ -365,15 +354,12 @@ MicTXView::MicTXView(
// 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.
options_tone_key.hidden(0); // we are in FM mode, we should have active the Key-tones & CTCSS option.
rxbw.emplace_back(" NFM1:8k5 ", 0); // restore the original dynamic field_rxbw value.
rxbw.emplace_back(" NFM2:11k ", 1);
rxbw.emplace_back(" FM :16k ", 2);
field_rxbw.set_options(rxbw); // store that aux GUI option to the field_rxbw.
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.
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;
@@ -385,50 +371,47 @@ MicTXView::MicTXView(
// 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.
options_tone_key.hidden(0); // we are in WFM mode, we should have active the Key-tones & CTCSS option.
rxbw.emplace_back(" 200k-WFM ", 0); // We allow the user selection of the 3 x WFM BW filters, (0) WFM-200K, (1) WFM-180K , (2) WFM-40K .
rxbw.emplace_back(" 180k-WFM ", 1);
rxbw.emplace_back(" 40k-WFM ", 2);
field_rxbw.set_options(rxbw); // store that aux GUI option to the field_rxbw.
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.
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.
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(" DSB1-9k ", 0); // we offer in AM DSB two audio BW 9k / 6k .
rxbw.emplace_back(" DSB2-6k ", 1);
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.
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.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.
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.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.
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;
@@ -437,8 +420,8 @@ MicTXView::MicTXView(
rxaudio(rx_enabled); // Update now if we have RX audio on
check_rogerbeep.hidden(0); // make visible again the "rogerbeep" selection.
rxbw.emplace_back("SSB1:USB+3k", 0); // added dynamically two options (index 0,1) to that DSB-C case to the field_rxbw value.
rxbw.emplace_back("SSB2:LSB-3k", 1);
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.
@@ -490,9 +473,9 @@ MicTXView::MicTXView(
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)
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_tuning_frequency(v ? tx_frequency : rx_frequency); // To go to the proper tuned freq. when toggling it
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) {
@@ -521,17 +504,17 @@ MicTXView::MicTXView(
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),
// 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_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)
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)
}
}
};
@@ -542,13 +525,13 @@ MicTXView::MicTXView(
field_squelch.set_value(0);
receiver_model.set_squelch_level(0);
rx_frequency = receiver_model.tuning_frequency();
rx_frequency = receiver_model.target_frequency();
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_tuning_frequency(f);
receiver_model.set_target_frequency(f);
};
field_rxfrequency.on_edit = [this, &nav]() { // available when field rxfrequency not hidden => user selected separated freq RX/TX-
focused_ui = 1;
@@ -557,7 +540,7 @@ MicTXView::MicTXView(
new_view->on_changed = [this](rf::Frequency f) {
rx_frequency = f;
if (rx_enabled)
receiver_model.set_tuning_frequency(f);
receiver_model.set_target_frequency(f);
this->field_rxfrequency.set_value(f);
set_dirty();
};
@@ -606,6 +589,9 @@ MicTXView::MicTXView(
}
};
// These shouldn't be necessary, but because
// this app uses both transmitter_model and directly
// configures the baseband, these end up being required.
transmitter_model.set_sampling_rate(sampling_rate);
transmitter_model.set_baseband_bandwidth(1750000);
@@ -615,14 +601,43 @@ MicTXView::MicTXView(
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.
}
MicTXView::MicTXView(
NavigationView& nav,
ReceiverModel::settings_t override)
: MicTXView(nav) {
// 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);
break;
case ReceiverModel::Mode::NarrowbandFMAudio:
options_mode.set_selected_index(0);
break;
case ReceiverModel::Mode::WidebandFMAudio:
options_mode.set_selected_index(1);
break;
// Unsupported modulations.
case ReceiverModel::Mode::SpectrumAnalysis:
case ReceiverModel::Mode::Capture:
default:
break;
}
// 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.
}
MicTXView::~MicTXView() {
audio::input::stop();
transmitter_model.set_tuning_frequency(tx_frequency); // Save Tx frequency instead of Rx. Or maybe we need some "System Wide" changes to seperate Tx and Rx frequency.
transmitter_model.set_target_frequency(tx_frequency); // Save Tx frequency instead of Rx. Or maybe we need some "System Wide" changes to seperate Tx and Rx frequency.
transmitter_model.disable();
if (rx_enabled) // Also turn off audio rx if enabled
rxaudio(false);
hackrf::cpld::load_sram_no_verify(); // to leave all RX ok, without ghost signal problem at the exit .
baseband::shutdown(); // better this function at the end, not load_sram() that sometimes produces hang up.
baseband::shutdown();
}
} // namespace ui
+39 -23
View File
@@ -20,18 +20,21 @@
* Boston, MA 02110-1301, USA.
*/
// TODO: Consolidate Modulation/Bandwidth modes/settings with freqman/receiver_model.
#ifndef __UI_MICTX_H__
#define __UI_MICTX_H__
#define SHORT_UI true
#define NORMAL_UI false
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "ui.hpp"
#include "ui_widget.hpp"
#include "ui_navigation.hpp"
#include "ui_receiver.hpp"
#include "transmitter_model.hpp"
#include "tone_key.hpp"
#include "message.hpp"
#include "receiver_model.hpp"
#include "ui_transmitter.hpp"
@@ -41,6 +44,7 @@ namespace ui {
class MicTXView : public View {
public:
MicTXView(NavigationView& nav);
MicTXView(NavigationView& nav, ReceiverModel::settings_t override);
~MicTXView();
MicTXView(const MicTXView&) = delete;
@@ -71,7 +75,7 @@ class MicTXView : public View {
void update_vumeter();
void do_timing();
void set_tx(bool enable);
// void on_tuning_frequency_changed(rf::Frequency f);
// void on_target_frequency_changed(rf::Frequency f);
void on_tx_progress(const bool done);
void configure_baseband();
@@ -79,6 +83,15 @@ class MicTXView : public View {
void set_ptt_visibility(bool v);
RxRadioState rx_radio_state_{};
TxRadioState tx_radio_state_{
1750000 /* bandwidth */,
sampling_rate /* sampling rate */
};
app_settings::SettingsManager settings_{
"tx_mic", app_settings::Mode::RX_TX,
app_settings::Options::UseGlobalTargetFrequency};
bool transmitting{false};
bool va_enabled{false};
bool ptt_enabled{true};
@@ -106,18 +119,19 @@ class MicTXView : public View {
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)
bool enable_wfm{false}; // added to distinguish in the FM mode, RX BW : NFM (8K5, 11K), FM (16K), WFM(200K)
Labels labels_WM8731{
{{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
{{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()},
@@ -136,7 +150,7 @@ class MicTXView : public View {
{{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
{{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()},
@@ -165,15 +179,15 @@ class MicTXView : public View {
{"x2.0", 20}}};
OptionsField options_ak4951_alc_mode{
{20 * 8, 1 * 8}, // Coordinates are: int:x (px), int:y (px)
11,
{20 * 8, 1 * 8},
10, // Label has 10 chars
{
{" OFF-12kHz", 0}, // Nothing changed from ORIGINAL,keeping ALL programmable AK4951 Digital Block->OFF, sampling 24Khz)
{" OFF-12kHz", 0}, // Nothing changed from ORIGINAL, keeping ALL programmable AK4951 Digital Block->OFF, sampling 24Khz)
{"+12dB-6kHz", 1}, // ALC-> on, (+12dB's) Auto Vol max + Wind Noise cancel + LPF 6kHz + Pre-amp Mic (+21dB=original)
{"+09dB-6kHz", 2}, // ALC-> on, (+09dB's) Auto Vol max + Wind Noise cancel + LPF 6kHz + Pre-amp Mic (+21dB=original)
{"+06dB-6kHz", 3}, // ALC-> on, (+06dB's) Auto Vol max + Wind Noise cancel + LPF 6kHz + Pre-amp Mic (+21dB=original)
{"+03dB-2kHz", 4}, // ALC-> on, (+03dB's) Auto Vol max + Wind Noise cancel + LPF 3,5k + Pre-amp Mic (+21dB=original)+ EQ boosting ~<2kHz (f0~1k1,fb:1,7K, k=1,8)
{"+03dB-4kHz", 5}, // ALC-> on, (+03dB's) Auto Vol max + Wind Noise cancel + LPF 4kHz + Pre-amp Mic (+21dB=original)+ EQ boosting ~<3kHz (f0~1k4,fb~2,4k, k=1,8)
{"+03dB-2kHz", 4}, // ALC-> on, (+03dB's) Auto Vol max + Wind Noise cancel + LPF 3,5k + Pre-amp Mic (+21dB=original)+ EQ boosting ~<2kHz (f0~1k1, fb:1,7K, k=1,8)
{"+03dB-4kHz", 5}, // ALC-> on, (+03dB's) Auto Vol max + Wind Noise cancel + LPF 4kHz + Pre-amp Mic (+21dB=original)+ EQ boosting ~<3kHz (f0~1k4, fb~2,4k, k=1,8)
{"+03dB-6kHz", 6}, // ALC-> on, (+03dB's) Auto Vol max + Wind Noise cancel + LPF 6kHz + Pre-amp Mic (+21dB=original)
{"+00dB-6kHz", 7}, // ALC-> on, (+00dB's) Auto Vol max + Wind Noise cancel + LPF 6kHz + Pre-amp Mic (+21dB=original)
{"-03dB-6kHz", 8}, // ALC-> on, (-03dB's) Auto Vol max + Wind Noise cancel + LPF 6kHz + Pre-amp Mic (+21dB=original)
@@ -183,16 +197,17 @@ class MicTXView : public View {
}};
OptionsField options_wm8731_boost_mode{
{22 * 8, 1 * 8}, // Coordinates are: int:x (px), int:y (px)
5,
{22 * 8, 1 * 8},
8, // Label has 8 chars
{
{"ON +12dB", 0}, // WM8731 Mic Boost ON ,original+12dBs condition, easy to saturate ADC sat in high voice ,relative G = +12 dB's respect ref level
{"ON +06dB", 1}, // WM8731 Mic Boost ON ,original+6 dBs condition, easy to saturate ADC sat in high voice ,relative G = +06 dB's respect ref level
{"OFF+04dB", 2}, // WM8731 Mic Boost OFF to avoid ADC sat in high voice ,relative G = +04 dB's (respect ref level) , always effective sampling 24khz
{"OFF-02dB", 3}, // WM8731 Mic Boost OFF to avoid ADC sat in high voice ,relative G = -02 dB's (respect ref level)
{"OFF-08dB", 4}, // WM8731 Mic Boost OFF to avoid ADC sat in high voice ,relative G = -12 dB's (respect ref level)
{"ON +12dB", 0}, // WM8731 Mic Boost ON, original+12dBs condition, easy to saturate ADC sat in high voice, relative G = +12 dB's respect ref level
{"ON +06dB", 1}, // WM8731 Mic Boost ON, original+6 dBs condition, easy to saturate ADC sat in high voice, relative G = +06 dB's respect ref level
{"OFF+04dB", 2}, // WM8731 Mic Boost OFF to avoid ADC sat in high voice, relative G = +04 dB's (respect ref level), always effective sampling 24khz
{"OFF-02dB", 3}, // WM8731 Mic Boost OFF to avoid ADC sat in high voice, relative G = -02 dB's (respect ref level)
{"OFF-08dB", 4}, // WM8731 Mic Boost OFF to avoid ADC sat in high voice, relative G = -12 dB's (respect ref level)
}};
// TODO: Use TxFrequencyField
FrequencyField field_frequency{
{5 * 8, 3 * 8},
};
@@ -215,7 +230,7 @@ class MicTXView : public View {
{
{"NFM/FM", 0},
{" WFM ", 1},
{" AM ", 2}, // in fact that TX mode = AM -DSB with carrier .
{" 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).
@@ -257,7 +272,7 @@ class MicTXView : public View {
OptionsField options_tone_key{
{12 * 8, (13 * 8) - 5},
23,
18,
{}};
Checkbox check_rogerbeep{
@@ -283,11 +298,11 @@ class MicTXView : public View {
OptionsField field_rxbw{
{19 * 8, (23 * 8) + 2},
3,
7,
{
{" NFM1:8k5 ", 0},
{" NFM2:11k ", 1},
{" FM :16k ", 2},
{" 8k5 ", 0}, // Initial dynamic values when we start Mic App.
{" 11k ", 1},
{" 16k ", 2},
}};
NumberField field_squelch{
@@ -298,6 +313,7 @@ class MicTXView : public View {
' ',
};
// TODO: Use RxFrequencyField
FrequencyField field_rxfrequency{
{10 * 8, (25 * 8) + 2},
};
@@ -38,8 +38,6 @@ class ModemSetupView : public View {
std::string title() const override { return "Modem setup"; };
private:
void update_freq(rf::Frequency f);
Labels labels{
{{2 * 8, 11 * 8}, "Baudrate:", Color::light_grey()},
{{2 * 8, 13 * 8}, "Mark: Hz", Color::light_grey()},
+3 -21
View File
@@ -26,7 +26,6 @@
#include "baseband_api.hpp"
#include "hackrf_gpio.hpp"
#include "portapack_shared_memory.hpp"
#include "cpld_update.hpp"
#include "ui_textentry.hpp"
#include "string_format.hpp"
@@ -98,13 +97,8 @@ void MorseView::focus() {
}
MorseView::~MorseView() {
// save app settings
app_settings.tx_frequency = transmitter_model.tuning_frequency();
settings.save("tx_morse", &app_settings);
transmitter_model.disable();
hackrf::cpld::load_sram_no_verify(); // to leave all RX ok, without ghost signal problem at the exit .
baseband::shutdown(); // better this function at the end, not load_sram() that sometimes produces hang up.
baseband::shutdown();
}
void MorseView::paint(Painter&) {
@@ -123,9 +117,6 @@ bool MorseView::start_tx() {
}
progressbar.set_max(symbol_count);
transmitter_model.set_sampling_rate(1536000U);
transmitter_model.set_baseband_bandwidth(1750000);
transmitter_model.enable();
if (modulation == CW) {
@@ -206,15 +197,6 @@ MorseView::MorseView(
&progressbar,
&tx_view});
// load app settings
auto rc = settings.load("tx_morse", &app_settings);
if (rc == SETTINGS_OK) {
transmitter_model.set_rf_amp(app_settings.tx_amp);
transmitter_model.set_channel_bandwidth(app_settings.channel_bandwidth);
transmitter_model.set_tuning_frequency(app_settings.tx_frequency);
transmitter_model.set_tx_gain(app_settings.tx_gain);
}
// Default settings
field_speed.set_value(15); // 15wps
field_tone.set_value(700); // 700Hz FM tone
@@ -251,9 +233,9 @@ MorseView::MorseView(
};
tx_view.on_edit_frequency = [this, &nav]() {
auto new_view = nav.push<FrequencyKeypadView>(receiver_model.tuning_frequency());
auto new_view = nav.push<FrequencyKeypadView>(transmitter_model.target_frequency());
new_view->on_changed = [this](rf::Frequency f) {
receiver_model.set_tuning_frequency(f);
transmitter_model.set_target_frequency(f);
};
};
+7 -3
View File
@@ -28,6 +28,7 @@
#include "ui_navigation.hpp"
#include "ui_transmitter.hpp"
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "portapack.hpp"
#include "message.hpp"
#include "volume.hpp"
@@ -68,9 +69,12 @@ class MorseView : public View {
std::string message{};
uint32_t time_units{0};
// app save settings
std::app_settings settings{};
std::app_settings::AppSettings app_settings{};
TxRadioState radio_state_{
1750000 /* bandwidth */,
1536000 /* sampling rate */
};
app_settings::SettingsManager settings_{
"tx_morse", app_settings::Mode::TX};
enum modulation_t {
CW = 0,
+3 -32
View File
@@ -40,11 +40,8 @@ void NRFRxView::focus() {
field_frequency.focus();
}
void NRFRxView::update_freq(rf::Frequency f) {
receiver_model.set_tuning_frequency(f);
}
NRFRxView::NRFRxView(NavigationView& nav) {
NRFRxView::NRFRxView(NavigationView& nav)
: nav_{nav} {
baseband::run_image(portapack::spi_flash::image_tag_nrf_rx);
add_children({&rssi,
@@ -56,16 +53,8 @@ NRFRxView::NRFRxView(NavigationView& nav) {
&button_modem_setup,
&console});
// load app settings
auto rc = settings.load("rx_nrf", &app_settings);
if (rc == SETTINGS_OK) {
field_lna.set_value(app_settings.lna);
field_vga.set_value(app_settings.vga);
field_rf_amp.set_value(app_settings.rx_amp);
}
// Auto-configure modem for LCR RX (will be removed later)
update_freq(2480000000);
field_frequency.set_value(2480000000);
auto def_bell202 = &modem_defs[0];
persistent_memory::set_modem_baudrate(def_bell202->baudrate);
serial_format_t serial_format;
@@ -75,18 +64,7 @@ NRFRxView::NRFRxView(NavigationView& nav) {
serial_format.bit_order = LSB_FIRST;
persistent_memory::set_serial_format(serial_format);
field_frequency.set_value(receiver_model.tuning_frequency());
field_frequency.set_step(100);
field_frequency.on_change = [this](rf::Frequency f) {
update_freq(f);
};
field_frequency.on_edit = [this, &nav]() {
auto new_view = nav.push<FrequencyKeypadView>(receiver_model.tuning_frequency());
new_view->on_changed = [this](rf::Frequency f) {
update_freq(f);
field_frequency.set_value(f);
};
};
button_modem_setup.on_select = [&nav](Button&) {
nav.push<ModemSetupView>();
@@ -98,9 +76,6 @@ NRFRxView::NRFRxView(NavigationView& nav) {
audio::set_rate(audio::Rate::Hz_24000);
audio::output::start();
receiver_model.set_sampling_rate(4000000);
receiver_model.set_baseband_bandwidth(4000000);
receiver_model.set_modulation(ReceiverModel::Mode::WidebandFMAudio);
receiver_model.enable();
}
@@ -151,10 +126,6 @@ void NRFRxView::on_data(uint32_t value, bool is_data) {
}
NRFRxView::~NRFRxView() {
// save app settings
app_settings.rx_frequency = field_frequency.value();
settings.save("rx_nrf", &app_settings);
audio::output::stop();
receiver_model.disable();
baseband::shutdown();
+13 -12
View File
@@ -27,7 +27,9 @@
#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 "ui_record_view.hpp" // DEBUG
#include "utility.hpp"
@@ -46,9 +48,13 @@ class NRFRxView : public View {
private:
void on_data(uint32_t value, bool is_data);
// app save settings
std::app_settings settings{};
std::app_settings::AppSettings app_settings{};
NavigationView& nav_;
RxRadioState radio_state_{
4000000 /* bandwidth */,
4000000 /* sampling rate */,
ReceiverModel::Mode::WidebandFMAudio};
app_settings::SettingsManager settings_{
"rx_nrf", app_settings::Mode::RX};
uint8_t console_color{0};
uint32_t prev_value{0};
@@ -60,15 +66,13 @@ class NRFRxView : public View {
VGAGainField field_vga{
{18 * 8, 0 * 16}};
RSSI rssi{
{21 * 8, 0, 6 * 8, 4},
};
{21 * 8, 0, 6 * 8, 4}};
Channel channel{
{21 * 8, 5, 6 * 8, 4},
};
{21 * 8, 5, 6 * 8, 4}};
FrequencyField field_frequency{
RxFrequencyField field_frequency{
{0 * 8, 0 * 16},
};
nav_};
Button button_modem_setup{
{240 - 12 * 8, 1 * 16, 96, 24},
@@ -77,9 +81,6 @@ class NRFRxView : public View {
Console console{
{0, 4 * 16, 240, 240}};
void update_freq(rf::Frequency f);
// void on_data_afsk(const AFSKDataMessage& message);
MessageHandlerRegistration message_handler_packet{
Message::ID::AFSKData,
[this](Message* const p) {
-2
View File
@@ -129,9 +129,7 @@ void NumbersStationView::start_tx() {
prepare_audio();
transmitter_model.set_sampling_rate(1536000U);
transmitter_model.set_rf_amp(true);
transmitter_model.set_baseband_bandwidth(1750000);
transmitter_model.enable();
baseband::set_audiotx_data(
+6
View File
@@ -34,6 +34,7 @@
#include "message.hpp"
#include "file.hpp"
#include "io_wave.hpp"
#include "radio_state.hpp"
namespace ui {
@@ -54,6 +55,11 @@ class NumbersStationView : public View {
private:
NavigationView& nav_;
TxRadioState radio_state_{
1750000 /* bandwidth */,
1536000 /* sampling rate */
};
// Sequencing state machine
enum segments {
IDLE = 0,
+2 -4
View File
@@ -77,11 +77,9 @@ void NuoptixView::transmit(bool setup) {
timecode = number_timecode.value();
}
transmitter_model.set_sampling_rate(1536000U);
transmitter_model.set_rf_amp(true);
transmitter_model.set_lna(40);
transmitter_model.set_vga(40);
transmitter_model.set_baseband_bandwidth(1750000);
transmitter_model.enable();
dtmf_message[0] = '*'; // "Pre-tone for restart" method #1
@@ -156,9 +154,9 @@ NuoptixView::NuoptixView(
number_timecode.set_value(1);
tx_view.on_edit_frequency = [this, &nav]() {
auto new_view = nav.push<FrequencyKeypadView>(receiver_model.tuning_frequency());
auto new_view = nav.push<FrequencyKeypadView>(transmitter_model.target_frequency());
new_view->on_changed = [this](rf::Frequency f) {
receiver_model.set_tuning_frequency(f);
transmitter_model.target_frequency(f);
};
};
+6 -1
View File
@@ -33,6 +33,7 @@
#include "message.hpp"
#include "volume.hpp"
#include "audio.hpp"
#include "radio_state.hpp"
#define NUOPTIX_TONE_LENGTH ((TONES_SAMPLERATE * 0.049) - 1) // 49ms
@@ -54,9 +55,13 @@ class NuoptixView : public View {
IMPROVISE
};
TxRadioState radio_state_{
1750000 /* bandwidth */,
1536000 /* sampling rate */
};
tx_modes tx_mode{IDLE};
void on_tuning_frequency_changed(rf::Frequency f);
void transmit(bool setup);
void on_tx_progress(const uint32_t progress, const bool done);
+370 -214
View File
@@ -2,6 +2,7 @@
* Copyright (C) 2016 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
* Copyleft () 2022 NotPike
* Copyright (C) 2023 Kyle Reed, zxkmm
*
* This file is part of PortaPack.
*
@@ -22,10 +23,15 @@
*/
#include "ui_playlist.hpp"
#include "string_format.hpp"
#include "ui_fileman.hpp"
#include "convert.hpp"
#include "file_reader.hpp"
#include "io_file.hpp"
#include "io_convert.hpp"
#include "string_format.hpp"
#include "ui_fileman.hpp"
#include "utility.hpp"
#include "baseband_api.hpp"
#include "portapack.hpp"
@@ -34,223 +40,326 @@
#include <fstream>
using namespace portapack;
namespace fs = std::filesystem;
/* TODO
* - Should frequency overrides be saved in the playlist?
* - Start playing from current track (vs start over)?
*/
namespace ui {
void PlaylistView::set_ready() {
ready_signal = true;
}
// TODO: consolidate extesions into a shared header?
static const fs::path ppl_ext = u".PPL";
void PlaylistView::load_file(std::filesystem::path playlist_path) {
void PlaylistView::load_file(const fs::path& playlist_path) {
File playlist_file;
auto error = playlist_file.open(playlist_path.string());
if (!error.is_valid()) {
std::string line;
char one_char[1];
for (size_t pointer = 0; pointer < playlist_file.size(); pointer++) {
playlist_file.seek(pointer);
playlist_file.read(one_char, 1);
if ((int)one_char[0] >= ' ') {
line += one_char[0];
} else if (one_char[0] == '\n') {
total_tracks++;
txtline_process(line);
line.clear();
}
}
if (line.length() > 0) {
total_tracks++;
txtline_process(line);
}
if (error)
return;
auto reader = FileLineReader(playlist_file);
for (const auto& line : reader) {
if (line.length() == 0 || line[0] == '#')
continue; // Empty or comment line.
auto cols = split_string(line, ',');
auto entry = load_entry(trim(cols[0]));
if (!entry)
continue;
// Read optional delay value.
if (cols.size() > 1)
parse_int(cols[1], entry->ms_delay);
playlist_db_.emplace_back(*std::move(entry));
}
playlist_masterdb = playlist_db;
return;
}
void PlaylistView::txtline_process(std::string& line) {
playlist_entry new_item;
rf::Frequency f;
size_t previous = 0;
size_t current = line.find(',');
std::string freqs = line.substr(0, current);
previous = current + 1;
current = line.find(',', previous);
std::string file = line.substr(previous, current - previous);
previous = current + 1;
current = line.find(',', previous);
uint32_t sample_rate = strtoll(line.substr(previous).c_str(), nullptr, 10);
Optional<PlaylistView::playlist_entry> PlaylistView::load_entry(fs::path&& path) {
File capture_file;
f = strtoll(freqs.c_str(), nullptr, 0);
new_item.replay_frequency = f;
new_item.replay_file = "/" + file;
new_item.sample_rate = sample_rate;
new_item.next_delay = 0;
auto error = capture_file.open(path);
if (error)
return {};
playlist_db.push_back(std::move(new_item));
// Read metafile if it exists.
auto metadata_path = get_metadata_path(path);
auto metadata = read_metadata_file(metadata_path);
// If no metadata found, fallback to the TX frequency.
if (!metadata)
metadata = {transmitter_model.target_frequency(), 500'000};
return playlist_entry{
std::move(path),
*metadata,
capture_file.size(),
0u};
}
void PlaylistView::on_file_changed(std::filesystem::path new_file_path, rf::Frequency replay_frequency, uint32_t replay_sample_rate) {
File data_file;
// Get file size
auto data_open_error = data_file.open("/" + new_file_path.string());
if (!data_open_error.is_valid()) {
track_number++;
} else if (data_open_error.is_valid()) {
file_error("C16 file\n" + new_file_path.string() + "\nread error.");
void PlaylistView::on_file_changed(const fs::path& new_file_path) {
stop();
current_index_ = 0;
playlist_path_ = new_file_path;
playlist_db_.clear();
load_file(playlist_path_);
update_ui();
// TODO: fix in UI framework with 'try_focus()'?
// Hack around focus getting called by ctor before parent is set.
if (parent())
button_play.focus();
}
void PlaylistView::open_file(bool prompt_save) {
if (playlist_dirty_ && prompt_save) {
nav_.display_modal(
"Save?", "Save changes?", YESNO,
[this](bool choice) {
if (choice)
save_file(false);
});
nav_.set_on_pop([this]() { open_file(false); });
return;
}
file_path = new_file_path;
field_frequency.set_value(replay_frequency);
sample_rate = replay_sample_rate;
text_sample_rate.set(unit_auto_scale(sample_rate, 3, 0) + "Hz");
auto file_size = data_file.size();
auto duration = (file_size * 1000) / (2 * 2 * sample_rate);
progressbar.set_max(file_size);
text_filename.set(file_path.filename().string().substr(0, 12));
text_duration.set(to_string_time_ms(duration));
// text_track.set(std::to_string(track_number) + "/" + std::to_string(total_tracks));
button_play.focus();
auto open_view = nav_.push<FileLoadView>(".PPL");
open_view->push_dir(u"PLAYLIST");
open_view->on_changed = [this](fs::path new_file_path) {
on_file_changed(new_file_path);
};
}
void PlaylistView::on_tx_progress(const uint32_t progress) {
progressbar.set_value(progress);
void PlaylistView::save_file(bool show_dialogs) {
if (!playlist_dirty_ || playlist_path_.empty())
return;
ensure_directory(playlist_path_.parent_path());
File playlist_file;
auto error = playlist_file.create(playlist_path_.string());
if (error) {
if (show_dialogs)
nav_.display_modal(
"Save Error",
"Could not save file\n" + playlist_path_.string());
return;
}
for (const auto& entry : playlist_db_) {
playlist_file.write_line(
entry.path.string() + "," +
to_string_dec_uint(entry.ms_delay));
}
playlist_dirty_ = false;
if (show_dialogs)
nav_.display_modal(
"Save",
"Saved playlist\n" + playlist_path_.string());
}
void PlaylistView::focus() {
button_open.focus();
void PlaylistView::add_entry(fs::path&& path) {
if (playlist_path_.empty()) {
playlist_path_ = next_filename_matching_pattern(u"/PLAYLIST/PLAY_????.PPL");
// Hack around focus getting called by ctor before parent is set.
if (parent())
button_play.focus();
}
auto entry = load_entry(std::move(path));
if (entry) {
playlist_db_.emplace_back(*std::move(entry));
current_index_ = playlist_db_.size() - 1;
playlist_dirty_ = true;
}
update_ui();
}
void PlaylistView::file_error(std::string error_message) {
nav_.display_modal("Error", "Error for \n" + file_path.string() + "\n" + error_message);
void PlaylistView::delete_entry() {
if (playlist_db_.empty())
return;
// Ugh, the STL is gross.
playlist_db_.erase(playlist_db_.begin() + current_index_);
if (current_index_ > 0)
--current_index_;
playlist_dirty_ = true;
update_ui();
}
void PlaylistView::show_file_error(const fs::path& path, const std::string& message) {
nav_.display_modal("Error", "Error opening file \n" + path.string() + "\n" + message);
}
PlaylistView::playlist_entry* PlaylistView::current() {
return playlist_db_.empty() ? nullptr : &playlist_db_[current_index_];
}
bool PlaylistView::is_active() const {
return (bool)replay_thread;
return replay_thread_ != nullptr;
}
bool PlaylistView::loop() const {
return (bool)playlist_db.size();
bool PlaylistView::at_end() const {
return current_index_ >= playlist_db_.size();
}
void PlaylistView::toggle() {
if (is_active()) {
stop(false);
total_tracks = 0;
track_number = 0;
playlist_db.clear();
playlist_masterdb.clear();
} else {
total_tracks = 0;
track_number = 0;
playlist_db.clear();
playlist_masterdb.clear();
load_file(now_play_list_file);
if (!playlist_db.empty()) {
start();
}
}
if (is_active())
stop();
else
start();
}
void PlaylistView::start() {
stop(false);
if (playlist_db_.empty())
return; // Nothing to do.
playlist_entry item = playlist_db.front();
playlist_db.pop_front();
// playlist_entry item = playlist_db[0];
// for (playlist_entry item : playlist_db) {
// file_path = item.replay_file;
// rf::Frequency replay_frequency = strtoll(item.replay_frequency.c_str(),nullptr,10);
on_file_changed(item.replay_file, item.replay_frequency, item.sample_rate);
on_target_frequency_changed(item.replay_frequency);
current_index_ = 0u;
send_current_track();
}
std::unique_ptr<stream::Reader> reader;
/* Advance to the next track in the playlist. */
bool PlaylistView::next_track() {
++current_index_;
auto p = std::make_unique<FileReader>();
auto open_error = p->open(file_path);
if (open_error.is_valid()) {
file_error("Illegal grammar");
return; // Fixes TX bug if there's a file error
} else {
reader = std::move(p);
// Reset to the 0 once at the end to prevent current_index_
// from being outside the bounds of playlist_db_ when painting.
if (at_end()) {
current_index_ = 0;
return check_loop.value(); // Keep going if looping.
}
if (reader) {
button_play.set_bitmap(&bitmap_stop);
baseband::set_sample_rate(sample_rate * 8);
return true;
}
replay_thread = std::make_unique<ReplayThread>(
std::move(reader),
read_size, buffer_count,
&ready_signal,
[](uint32_t return_code) {
ReplayThreadDoneMessage message{return_code};
EventDispatcher::send_message(message);
});
/* Transmits the current_entry_ */
void PlaylistView::send_current_track() {
// Prepare to send a file.
replay_thread_.reset();
transmitter_model.disable();
ready_signal_ = false;
if (!current())
return;
// TODO: Use a timer so the UI isn't frozen.
if (current()->ms_delay > 0)
chThdSleepMilliseconds(current()->ms_delay);
// Open the sample file to send.
auto reader = std::make_unique<FileConvertReader>();
auto error = reader->open(current()->path);
if (error) {
show_file_error(current()->path, "Can't open file to send.");
return;
}
transmitter_model.set_sampling_rate(sample_rate * 8);
// ReplayThread starts immediately on construction so
// these need to be set before creating the ReplayThread.
transmitter_model.set_target_frequency(current()->metadata.center_frequency);
transmitter_model.set_sampling_rate(current()->metadata.sample_rate * 8);
transmitter_model.set_baseband_bandwidth(baseband_bandwidth);
transmitter_model.enable();
// Set baseband sample rate too for waterfall to be correct.
// TODO: Why doesn't the transmitter_model just handle this?
baseband::set_sample_rate(transmitter_model.sampling_rate());
// Reset the transmit progress bar.
progressbar_transmit.set_value(0);
// Use the ReplayThread class to send the data.
replay_thread_ = std::make_unique<ReplayThread>(
std::move(reader),
/* read_size */ 0x4000,
/* buffer_count */ 3,
&ready_signal_,
[](uint32_t return_code) {
ReplayThreadDoneMessage message{return_code};
EventDispatcher::send_message(message);
});
// Now it's sending, update the UI.
update_ui();
}
void PlaylistView::stop(const bool do_loop) {
if (is_active()) {
replay_thread.reset();
}
void PlaylistView::stop() {
// This terminates the underlying chThread.
replay_thread_.reset();
transmitter_model.disable();
// TODO: the logic here could be more beautiful but maybe they are all same for compiler anyway....
// Notes of the logic here in case if it needed to be changed in the future:
// 1. check_loop.value() is for the LOOP checkbox
// 2. do_loop is a part of the replay thread, not a user - control thing.
// 3. when (total_tracks > track_number) is true, it means that the current track is not the last track.
// Thus, (do_loop && (total_tracks != track_number)) is for the case when the start() func were called with true AND not the last track.
// Which means it do loop until the last track.
if (check_loop.value()) {
if (do_loop) {
if (playlist_db.size() > 0) {
start();
} else {
playlist_db = playlist_masterdb;
start();
}
} else {
transmitter_model.disable();
button_play.set_bitmap(&bitmap_play);
}
} else if (!check_loop.value()) {
if (do_loop && (total_tracks > track_number)) {
if (playlist_db.size() > 0) {
start();
} else {
playlist_db = playlist_masterdb;
start();
}
} else {
transmitter_model.disable();
button_play.set_bitmap(&bitmap_play);
}
}
ready_signal = false;
// Reset the transmit progress bar.
progressbar_transmit.set_value(0);
update_ui();
}
void PlaylistView::handle_replay_thread_done(const uint32_t return_code) {
void PlaylistView::update_ui() {
if (playlist_db_.empty()) {
text_filename.set("-");
text_sample_rate.set("-");
text_duration.set("-");
if (playlist_path_.empty())
text_track.set("Open playlist or add capture.");
else
text_track.set(playlist_path_.filename().string());
progressbar_track.set_value(0);
progressbar_track.set_max(0);
progressbar_transmit.set_max(0);
} else {
chDbgAssert(!at_end(), "update_ui #1", "current_index_ invalid");
text_filename.set(current()->path.filename().string());
text_sample_rate.set(unit_auto_scale(current()->metadata.sample_rate, 3, (current()->metadata.sample_rate > 1000000) ? 2 : 0) + "Hz");
uint8_t sample_size = capture_file_sample_size(current()->path);
auto duration = ms_duration(current()->file_size, current()->metadata.sample_rate, sample_size);
text_duration.set(to_string_time_ms(duration));
field_frequency.set_value(current()->metadata.center_frequency);
text_track.set(
to_string_dec_uint(current_index_ + 1) + "/" +
to_string_dec_uint(playlist_db_.size()) + " " +
playlist_path_.filename().string());
progressbar_track.set_max(playlist_db_.size() - 1);
progressbar_track.set_value(current_index_);
progressbar_transmit.set_max(current()->file_size * sizeof(complex16_t) / sample_size);
}
button_play.set_bitmap(is_active() ? &bitmap_stop : &bitmap_play);
}
void PlaylistView::on_tx_progress(uint32_t progress) {
progressbar_transmit.set_value(progress);
}
void PlaylistView::handle_replay_thread_done(uint32_t return_code) {
if (return_code == ReplayThread::END_OF_FILE) {
stop(true);
} else if (return_code == ReplayThread::READ_ERROR) {
stop(false);
file_error("Replay thread read error");
if (next_track()) {
send_current_track();
return;
}
}
progressbar.set_value(0);
if (return_code == ReplayThread::READ_ERROR)
show_file_error(current()->path, "Replay read failed.");
stop();
}
PlaylistView::PlaylistView(
@@ -259,47 +368,101 @@ PlaylistView::PlaylistView(
baseband::run_image(portapack::spi_flash::image_tag_replay);
add_children({
&button_open,
&text_filename,
&text_sample_rate,
&text_duration,
&progressbar,
&progressbar_track,
&progressbar_transmit,
&field_frequency,
&tx_view, // this handles now the previous rfgain, rfamp
&tx_view,
&check_loop,
&button_play,
// &text_track,
// TODO: add track number
&text_track,
&button_prev,
&button_add,
&button_delete,
&button_open,
&button_save,
&button_next,
&waterfall,
});
field_frequency.set_value(target_frequency());
field_frequency.set_step(receiver_model.frequency_step());
waterfall.show_audio_spectrum_view(false);
field_frequency.set_value(100'000'000);
field_frequency.on_change = [this](rf::Frequency f) {
this->on_target_frequency_changed(f);
if (current())
current()->metadata.center_frequency = f;
};
field_frequency.on_edit = [this, &nav]() {
// TODO: Provide separate modal method/scheme?
auto new_view = nav.push<FrequencyKeypadView>(this->target_frequency());
new_view->on_changed = [this](rf::Frequency f) {
this->on_target_frequency_changed(f);
this->field_frequency.set_value(f);
field_frequency.on_edit = [this]() {
auto freq_view = nav_.push<FrequencyKeypadView>(field_frequency.value());
freq_view->on_changed = [this](rf::Frequency f) {
field_frequency.set_value(f);
};
};
field_frequency.set_step(5000);
button_play.on_select = [this](ImageButton&) {
this->toggle();
toggle();
};
button_open.on_select = [this, &nav](Button&) {
auto open_view = nav.push<FileLoadView>(".PPL");
open_view->on_changed = [this](std::filesystem::path new_file_path) {
now_play_list_file = new_file_path;
load_file(new_file_path);
button_add.on_select = [this, &nav]() {
if (is_active())
return;
auto open_view = nav_.push<FileLoadView>(".C*");
open_view->push_dir(u"CAPTURES");
open_view->on_changed = [this](fs::path path) {
add_entry(std::move(path));
};
};
button_delete.on_select = [this, &nav]() {
if (is_active())
return;
delete_entry();
};
button_open.on_select = [this, &nav]() {
if (is_active())
stop();
open_file();
};
button_save.on_select = [this]() {
if (is_active())
stop();
save_file();
};
button_prev.on_select = [this]() {
if (is_active())
return;
--current_index_;
if (at_end())
current_index_ = playlist_db_.size() - 1;
update_ui();
};
button_next.on_select = [this]() {
if (is_active())
return;
++current_index_;
if (at_end())
current_index_ = 0;
update_ui();
};
update_ui();
}
PlaylistView::PlaylistView(
NavigationView& nav,
const fs::path& path)
: PlaylistView(nav) {
auto ext = path.extension();
if (path_iequal(ext, ppl_ext))
on_file_changed(path);
else if (is_cxx_capture_file(path))
add_entry(fs::path{path});
}
PlaylistView::~PlaylistView() {
@@ -307,33 +470,26 @@ PlaylistView::~PlaylistView() {
baseband::shutdown();
}
void PlaylistView::set_parent_rect(Rect new_parent_rect) {
View::set_parent_rect(new_parent_rect);
ui::Rect waterfall_rect{
0, header_height, new_parent_rect.width(),
new_parent_rect.height() - header_height};
waterfall.set_parent_rect(waterfall_rect);
}
void PlaylistView::focus() {
if (playlist_path_.empty())
button_add.focus();
else
button_play.focus();
}
void PlaylistView::on_hide() {
stop(false);
// TODO: Terrible kludge because widget system doesn't notify Waterfall that
// it's being shown or hidden.
stop();
waterfall.on_hide();
View::on_hide();
}
void PlaylistView::set_parent_rect(const Rect new_parent_rect) {
View::set_parent_rect(new_parent_rect);
const ui::Rect waterfall_rect{0, header_height, new_parent_rect.width(),
new_parent_rect.height() - header_height};
waterfall.set_parent_rect(waterfall_rect);
}
void PlaylistView::on_target_frequency_changed(rf::Frequency f) {
set_target_frequency(f);
}
void PlaylistView::set_target_frequency(const rf::Frequency new_value) {
persistent_memory::set_tuned_frequency(new_value);
;
}
rf::Frequency PlaylistView::target_frequency() const {
return persistent_memory::tuned_frequency();
}
} /* namespace ui */
+129 -76
View File
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2016 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
* Copyright (C) 2023 Kyle Reed, zxkmm
*
* This file is part of PortaPack.
*
@@ -23,141 +24,193 @@
#define SHORT_UI true
#define NORMAL_UI false
#include "ui_widget.hpp"
#include "app_settings.hpp"
#include "bitmap.hpp"
#include "file.hpp"
#include "metadata_file.hpp"
#include "radio_state.hpp"
#include "replay_thread.hpp"
#include "ui_navigation.hpp"
#include "ui_receiver.hpp"
#include "replay_thread.hpp"
#include "ui_spectrum.hpp"
#include "ui_transmitter.hpp"
#include "ui_widget.hpp"
#include <string>
#include <memory>
#include <deque>
#include "ui_transmitter.hpp"
#include <vector>
namespace ui {
class PlaylistView : public View {
public:
PlaylistView(NavigationView& nav);
PlaylistView(NavigationView& nav, const std::filesystem::path& path);
~PlaylistView();
// Following 2 called by 'NavigationView::update_view' after view is created.
void set_parent_rect(Rect new_parent_rect) override;
void focus() override;
void on_hide() override;
void set_parent_rect(const Rect new_parent_rect) override;
void focus() override;
std::string title() const override { return "Playlist"; };
std::string title() const override { return "Replay"; };
private:
NavigationView& nav_;
TxRadioState radio_state_{};
app_settings::SettingsManager settings_{
"tx_playlist", app_settings::Mode::TX};
static constexpr ui::Dim header_height = 3 * 16;
// More header == less spectrum view.
static constexpr ui::Dim header_height = 6 * 16;
static constexpr uint32_t baseband_bandwidth = 2500000;
struct playlist_entry {
rf::Frequency replay_frequency{0};
std::string replay_file{};
uint32_t sample_rate{};
uint32_t next_delay{};
std::filesystem::path path{};
capture_metadata metadata{};
File::Size file_size{};
uint32_t ms_delay{};
};
std::deque<playlist_entry> playlist_db{};
std::deque<playlist_entry> playlist_masterdb{};
uint32_t sample_rate = 0;
int32_t tx_gain{47};
bool rf_amp{true}; // aux private var to store temporal, Replay App rf_amp user selection.
static constexpr uint32_t baseband_bandwidth = 2500000;
const size_t read_size{16384};
const size_t buffer_count{3};
void load_file(std::filesystem::path playlist_path);
void txtline_process(std::string&);
void on_file_changed(std::filesystem::path new_file_path, rf::Frequency replay_frequency, uint32_t replay_sample_rate);
void on_target_frequency_changed(rf::Frequency f);
void on_tx_progress(const uint32_t progress);
void set_target_frequency(const rf::Frequency new_value);
rf::Frequency target_frequency() const;
std::unique_ptr<ReplayThread> replay_thread_{};
bool ready_signal_{}; // Used to signal the ReplayThread.
size_t current_index_{0};
bool playlist_dirty_{};
std::vector<playlist_entry> playlist_db_{};
std::filesystem::path playlist_path_{};
void load_file(const std::filesystem::path& path);
Optional<playlist_entry> load_entry(std::filesystem::path&& path);
void on_file_changed(const std::filesystem::path& path);
void open_file(bool prompt_save = true);
void save_file(bool show_dialogs = true);
void add_entry(std::filesystem::path&& path);
void delete_entry();
void reset();
void show_file_error(
const std::filesystem::path& path,
const std::string& message);
playlist_entry* current();
bool is_active() const;
bool at_end() const;
void toggle();
void start();
void stop(const bool do_loop);
bool is_active() const;
bool loop() const;
void set_ready();
void handle_replay_thread_done(const uint32_t return_code);
void file_error(std::string error_message);
bool next_track();
void send_current_track();
void stop();
std::filesystem::path file_path{};
std::unique_ptr<ReplayThread> replay_thread{};
bool ready_signal{false};
size_t track_number{0};
size_t total_tracks{0};
std::filesystem::path now_play_list_file{};
void update_ui();
Button button_open{
{0 * 8, 0 * 16, 10 * 8, 2 * 16},
"Open file"};
/* There are called by Message handlers. */
void on_tx_progress(uint32_t progress);
void handle_replay_thread_done(uint32_t return_code);
Text text_filename{
{11 * 8, 0 * 16, 12 * 8, 16},
"-"};
Text text_sample_rate{
{24 * 8, 0 * 16, 6 * 8, 16},
"-"};
Text text_duration{
{11 * 8, 1 * 16, 6 * 8, 16},
"-"};
ProgressBar progressbar{
{18 * 8, 1 * 16, 12 * 8, 16}};
{0 * 8, 0 * 16, 30 * 8, 16}};
FrequencyField field_frequency{
{0 * 8, 2 * 16},
};
{0 * 8, 1 * 16}};
Text text_sample_rate{
{10 * 8, 1 * 16, 7 * 8, 16}};
/*v making there's 1px line (instead of two) between two progress bars,
* by letting 1px overlapped.
* So, since they overlapped 1px, they are visually same, and looks better.
*/
ProgressBar progressbar_track{
{18 * 8, 1 * 16, 12 * 8, 8 + 1}};
ProgressBar progressbar_transmit{
{18 * 8, 3 * 8 - 1, 12 * 8, 8}};
Text text_duration{
{0 * 8, 2 * 16, 5 * 8, 16}};
// TODO: delay duration field.
TransmitterView2 tx_view{
// new handling of NumberField field_rfgain, NumberField field_rfamp
74, 1 * 8, SHORT_UI // x(columns), y (line) position. (Used in Repay / GPS Simul / Play list App)
// 10*8, 2*8, NORMAL_UI // x(columns), y (line) position. (Used in Mic App)
};
9 * 8, 1 * 8, SHORT_UI};
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()};
// TODO: add track number
// Text text_track{
// {18 * 8, 1 * 16, 12 * 8, 16},
// "0/0"};
spectrum::WaterfallWidget waterfall{};
Text text_track{
{0 * 8, 3 * 16, 30 * 8, 16}};
NewButton button_prev{
{2 * 8, 4 * 16, 4 * 8, 2 * 16},
"",
&bitmap_arrow_left,
Color::dark_grey()};
NewButton button_next{
{6 * 8, 4 * 16, 4 * 8, 2 * 16},
"",
&bitmap_arrow_right,
Color::dark_grey()};
NewButton button_add{
{11 * 8, 4 * 16, 4 * 8, 2 * 16},
"",
&bitmap_icon_new_file,
Color::orange()};
NewButton button_delete{
{15 * 8, 4 * 16, 4 * 8, 2 * 16},
"",
&bitmap_icon_delete,
Color::orange()};
NewButton button_open{
{20 * 8, 4 * 16, 4 * 8, 2 * 16},
"",
&bitmap_icon_load,
Color::dark_blue()};
NewButton button_save{
{24 * 8, 4 * 16, 4 * 8, 2 * 16},
"",
&bitmap_icon_save,
Color::dark_blue()};
spectrum::WaterfallView waterfall{};
MessageHandlerRegistration message_handler_replay_thread_error{
Message::ID::ReplayThreadDone,
[this](const Message* const p) {
const auto message = *reinterpret_cast<const ReplayThreadDoneMessage*>(p);
this->handle_replay_thread_done(message.return_code);
[this](const Message* p) {
auto message = *reinterpret_cast<const ReplayThreadDoneMessage*>(p);
handle_replay_thread_done(message.return_code);
}};
MessageHandlerRegistration message_handler_fifo_signal{
Message::ID::RequestSignal,
[this](const Message* const p) {
const auto message = static_cast<const RequestSignalMessage*>(p);
[this](const Message* p) {
auto message = static_cast<const RequestSignalMessage*>(p);
if (message->signal == RequestSignalMessage::Signal::FillRequest) {
this->set_ready();
ready_signal_ = true;
}
}};
MessageHandlerRegistration message_handler_tx_progress{
Message::ID::TXProgress,
[this](const Message* const p) {
const auto message = *reinterpret_cast<const TXProgressMessage*>(p);
this->on_tx_progress(message.progress);
[this](const Message* p) {
auto message = *reinterpret_cast<const TXProgressMessage*>(p);
on_tx_progress(message.progress);
}};
};
+4 -22
View File
@@ -23,7 +23,6 @@
#include "ui_pocsag_tx.hpp"
#include "baseband_api.hpp"
#include "cpld_update.hpp"
#include "string_format.hpp"
#include "ui_textentry.hpp"
@@ -39,13 +38,8 @@ void POCSAGTXView::focus() {
}
POCSAGTXView::~POCSAGTXView() {
// save app settings
app_settings.tx_frequency = transmitter_model.tuning_frequency();
settings.save("tx_pocsag", &app_settings);
transmitter_model.disable();
hackrf::cpld::load_sram_no_verify(); // to leave all RX ok, without ghost signal problem at the exit
baseband::shutdown(); // better this function at the end, not load_sram() that sometimes produces hang up.
baseband::shutdown();
}
void POCSAGTXView::on_tx_progress(const uint32_t progress, const bool done) {
@@ -85,11 +79,8 @@ bool POCSAGTXView::start_tx() {
progressbar.set_max(total_frames);
transmitter_model.set_sampling_rate(2280000);
transmitter_model.set_rf_amp(true);
transmitter_model.set_lna(40);
transmitter_model.set_vga(40);
transmitter_model.set_baseband_bandwidth(1750000);
transmitter_model.set_tx_gain(40);
transmitter_model.enable();
uint8_t* data_ptr = shared_memory.bb_data.data;
@@ -143,15 +134,6 @@ POCSAGTXView::POCSAGTXView(
&progressbar,
&tx_view});
// load app settings
auto rc = settings.load("tx_pocsag", &app_settings);
if (rc == SETTINGS_OK) {
transmitter_model.set_rf_amp(app_settings.tx_amp);
transmitter_model.set_channel_bandwidth(app_settings.channel_bandwidth);
transmitter_model.set_tuning_frequency(app_settings.tx_frequency);
transmitter_model.set_tx_gain(app_settings.tx_gain);
}
options_bitrate.set_selected_index(1); // 1200bps
options_type.set_selected_index(0); // Address only
@@ -172,9 +154,9 @@ POCSAGTXView::POCSAGTXView(
};
tx_view.on_edit_frequency = [this, &nav]() {
auto new_view = nav.push<FrequencyKeypadView>(transmitter_model.tuning_frequency());
auto new_view = nav.push<FrequencyKeypadView>(transmitter_model.target_frequency());
new_view->on_changed = [this](rf::Frequency f) {
transmitter_model.set_tuning_frequency(f);
transmitter_model.set_target_frequency(f);
};
};
+8 -4
View File
@@ -32,6 +32,7 @@
#include "message.hpp"
#include "transmitter_model.hpp"
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "pocsag.hpp"
using namespace pocsag;
@@ -58,6 +59,13 @@ class POCSAGTXView : public View {
std::string message{};
NavigationView& nav_;
TxRadioState radio_state_{
1750000 /* bandwidth */,
2280000 /* sampling rate */
};
app_settings::SettingsManager settings_{
"tx_pocsag", app_settings::Mode::TX};
BCHCode BCH_code{
{1, 0, 1, 0, 0, 1},
5,
@@ -65,10 +73,6 @@ class POCSAGTXView : public View {
21,
2};
// app save settings
std::app_settings settings{};
std::app_settings::AppSettings app_settings{};
void on_set_text(NavigationView& nav);
void on_tx_progress(const uint32_t progress, const bool done);
bool start_tx();
+3 -19
View File
@@ -24,7 +24,6 @@
#include "portapack.hpp"
#include "baseband_api.hpp"
#include "cpld_update.hpp"
#include "portapack_shared_memory.hpp"
#include <cstring>
@@ -160,13 +159,8 @@ void RDSView::focus() {
}
RDSView::~RDSView() {
// save app settings
app_settings.tx_frequency = transmitter_model.tuning_frequency();
settings.save("tx_rds", &app_settings);
transmitter_model.disable();
hackrf::cpld::load_sram_no_verify(); // to leave all RX ok, without ghost signal problem at the exit.
baseband::shutdown(); // better this function at the end, not load_sram() that sometimes produces hang up.
baseband::shutdown();
}
void RDSView::start_tx() {
@@ -193,8 +187,6 @@ void RDSView::start_tx() {
else
frame_datetime.clear();
transmitter_model.set_sampling_rate(2280000U);
transmitter_model.set_baseband_bandwidth(1750000);
transmitter_model.enable();
tx_thread = std::make_unique<RDSThread>(frames);
@@ -218,14 +210,6 @@ RDSView::RDSView(
&tx_view,
});
// load app settings
auto rc = settings.load("tx_rds", &app_settings);
if (rc == SETTINGS_OK) {
transmitter_model.set_rf_amp(app_settings.tx_amp);
transmitter_model.set_channel_bandwidth(app_settings.channel_bandwidth);
transmitter_model.set_tuning_frequency(app_settings.tx_frequency);
transmitter_model.set_tx_gain(app_settings.tx_gain);
}
check_TP.set_value(true);
sym_pi_code.set_sym(0, 0xF);
@@ -239,9 +223,9 @@ RDSView::RDSView(
options_pty.set_selected_index(0); // None
tx_view.on_edit_frequency = [this, &nav]() {
auto new_view = nav.push<FrequencyKeypadView>(receiver_model.tuning_frequency());
auto new_view = nav.push<FrequencyKeypadView>(transmitter_model.target_frequency());
new_view->on_changed = [this](rf::Frequency f) {
receiver_model.set_tuning_frequency(f);
transmitter_model.set_target_frequency(f);
};
};
+7 -3
View File
@@ -25,6 +25,7 @@
#include "ui_textentry.hpp"
#include "ui_tabview.hpp"
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "rds.hpp"
using namespace rds;
@@ -140,9 +141,12 @@ class RDSView : public View {
NavigationView& nav_;
RDS_flags rds_flags{};
// app save settings
std::app_settings settings{};
std::app_settings::AppSettings app_settings{};
TxRadioState radio_state_{
1750000 /* bandwidth */,
2280000 /* sampling rate */
};
app_settings::SettingsManager settings_{
"tx_rds", app_settings::Mode::TX};
std::vector<RDSGroup> frame_psn{};
std::vector<RDSGroup> frame_radiotext{};
File diff suppressed because it is too large Load Diff
+153 -112
View File
@@ -39,12 +39,18 @@
#include "string_format.hpp"
#include "file.hpp"
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "ui_recon_settings.hpp"
namespace ui {
#define RECON_CFG_FILE "SETTINGS/recon.cfg"
#define RECON_FREQMAN_MAX_PER_FILE 99 // maximum tested limit for load frequency from freqman to work
enum class recon_mode : uint8_t {
Recon,
Scanner,
Manual
};
class ReconView : public View {
public:
@@ -60,8 +66,14 @@ class ReconView : public View {
private:
NavigationView& nav_;
RxRadioState radio_state_{};
app_settings::SettingsManager settings_{
"rx_recon", app_settings::Mode::RX};
void set_loop_config(bool v);
void clear_freqlist_for_ui_action();
void reset_indexes();
void update_description();
void audio_output_start();
bool check_sd_card();
size_t change_mode(freqman_index_t mod_type);
@@ -76,72 +88,89 @@ class ReconView : public View {
void recon_redraw();
void handle_retune();
void handle_coded_squelch(const uint32_t value);
void handle_remove_current_item();
bool recon_load_config_from_sd();
bool recon_save_config_to_sd();
bool recon_save_freq(const std::string& freq_file_path, size_t index, bool warn_if_exists);
bool recon_save_freq(const std::filesystem::path& path, size_t index, bool warn_if_exists);
// placeholder for possible void recon_start_recording();
void recon_stop_recording();
// Returns true if 'current_index' is in bounds of frequency_list.
bool current_is_valid();
freqman_entry& current_entry();
// TODO: consolidate mode bools and use recon_mode.
recon_mode mode() const {
if (manual_mode) return recon_mode::Manual;
if (scanner_mode) return recon_mode::Scanner;
return recon_mode::Recon;
}
jammer::jammer_range_t frequency_range{false, 0, MAX_UFREQ}; // perfect for manual recon task too...
int32_t squelch{0};
int32_t squelch{RECON_DEF_SQUELCH};
int32_t db{0};
int32_t timer{0};
int32_t wait{RECON_DEF_WAIT_DURATION}; // in msec. if > 0 wait duration after a lock, if < 0 duration is set to 'wait' unless there is no more activity
freqman_db frequency_list = {};
freqman_db frequency_list{};
int32_t current_index{0};
bool continuous_lock{false};
bool freqlist_cleared_for_ui_action{false}; // flag positioned by ui widgets to manage freqlist unload/load
std::string input_file = {"RECON"};
std::string output_file = {"RECON_RESULTS"};
std::string description = {"...no description..."};
bool autosave = {true};
bool autostart = {true};
bool continuous = {true};
bool filedelete = {true};
bool load_freqs = {true};
bool load_ranges = {true};
bool load_hamradios = {true};
bool update_ranges = {true};
bool fwd = {true};
bool recon = true;
uint32_t recon_lock_nb_match = {3};
uint32_t recon_lock_duration = {RECON_MIN_LOCK_DURATION};
uint32_t recon_match_mode = {RECON_MATCH_CONTINUOUS};
std::string input_file{"RECON"};
std::string output_file{"RECON_RESULTS"};
std::string description{"...no description..."};
bool autosave{true};
bool autostart{true};
bool continuous{true};
bool filedelete{true};
bool load_freqs{true};
bool load_ranges{true};
bool load_hamradios{true};
bool update_ranges{true};
bool fwd{true};
bool recon{true};
bool user_pause{false};
bool auto_record_locked{false};
bool is_recording{false};
uint32_t recon_lock_nb_match{RECON_DEF_NB_MATCH};
uint32_t recon_lock_duration{RECON_MIN_LOCK_DURATION};
uint32_t recon_match_mode{RECON_MATCH_CONTINUOUS};
bool scanner_mode{false};
bool manual_mode{false};
bool sd_card_mounted = false;
int32_t stepper = 0;
int32_t index_stepper = 0;
int64_t freq = 0;
uint32_t step = 0;
freqman_index_t def_modulation = 0;
freqman_index_t def_bandwidth = 0;
freqman_index_t def_step = 0;
tone_index tone = 0;
freqman_entry last_entry = {};
bool entry_has_changed = false;
bool sd_card_mounted{false};
int32_t stepper{0};
int32_t index_stepper{0};
int64_t freq{0};
uint32_t step{0};
freqman_index_t def_step{0};
freqman_entry last_entry{};
bool entry_has_changed{false};
uint32_t freq_lock{0};
int64_t minfreq = 0;
int64_t maxfreq = 0;
bool has_looped = false;
int8_t status = -1; // 0 recon , 1 locking , 2 locked
int32_t last_timer = -1;
int8_t last_db = -127;
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_max = -127;
int32_t last_index = -1;
int32_t last_squelch_index = -1;
int64_t last_freq = 0;
std::string freq_file_path = {};
int64_t minfreq{0};
int64_t maxfreq{0};
bool has_looped{false};
int8_t status{-1}; // 0 recon , 1 locking , 2 locked
int32_t last_timer{-1};
int8_t last_db{-127};
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_max{-127};
int32_t last_index{-1};
int64_t last_freq{0};
std::string freq_file_path{};
systime_t chrono_start{};
systime_t chrono_end{};
std::unique_ptr<RecordView> record_view{};
Labels labels{
{{0 * 8, 0 * 16}, "LNA: VGA: AMP: VOL: ", Color::light_grey()},
{{0 * 8, 1 * 16}, "BW: SQ: W,L: , ", Color::light_grey()},
{{3 * 8, 10 * 16}, "START END MANUAL", Color::light_grey()},
{{0 * 8, (26 * 8) + 4}, "MODE:", Color::light_grey()},
{{11 * 8, (26 * 8) + 4}, "STEP:", Color::light_grey()},
};
{{3 * 8, 8 * 16}, "START END", Color::light_grey()},
{{0 * 8, (22 * 8)}, " S: ", Color::light_grey()},
{{0 * 8, (24 * 8) + 4}, "NBLCKS:x W,L: , ", Color::light_grey()},
{{0 * 8, (26 * 8) + 4}, "MODE: , SQUELCH: ", Color::light_grey()}};
LNAGainField field_lna{
{4 * 8, 0 * 16}};
@@ -155,65 +184,41 @@ class ReconView : public View {
AudioVolumeField field_volume{
{24 * 8, 0 * 16}};
OptionsField field_bw{
{3 * 8, 1 * 16},
6,
{}};
NumberField field_squelch{
{12 * 8, 1 * 16},
3,
{-90, 20},
1,
' ',
Text file_name{
// show file used
{0, 1 * 16, SCREEN_W, 16},
};
NumberField field_wait{
{20 * 8, 1 * 16},
5,
{-RECON_MAX_LOCK_DURATION, RECON_MAX_LOCK_DURATION},
STATS_UPDATE_INTERVAL,
' ',
};
NumberField field_lock_wait{
{26 * 8, 1 * 16},
4,
{RECON_MIN_LOCK_DURATION, RECON_MAX_LOCK_DURATION},
STATS_UPDATE_INTERVAL,
' ',
Text desc_cycle{
{0, 2 * 16, SCREEN_W, 16},
};
RSSI rssi{
{0 * 16, 2 * 16, SCREEN_W - 8 * 8 + 4, 14},
{0 * 16, 3 * 16 + 2, SCREEN_W - 8 * 8 + 4, 12},
};
ButtonWithEncoder text_cycle{
{0, 3 * 16, 4 * 8, 16},
{0, 4 * 16, 4 * 8, 16},
""};
// "/XXX -XXX db" => 12 chars max
Text text_max{
{4 * 8, 3 * 16, 12 * 8, 16},
{4 * 8, 4 * 16, 12 * 8, 16},
};
// "XX/XX" => 5 chars max
Text text_nb_locks{
{16 * 8, 3 * 16, 5 * 8, 16},
};
Text desc_cycle{
{0, 4 * 16, SCREEN_W, 16},
{16 * 8, 4 * 16, 5 * 8, 16},
};
Text big_display{
// Show frequency in text mode
{0, 5 * 16, 23 * 8, 16},
{0, 5 * 16, 21 * 8, 16},
};
Text freq_stats{
// Show frequency stats in text mode
{0, 6 * 16, 23 * 8, 16},
{0, 6 * 16, 21 * 8, 16},
};
// TIMER: 9999
@@ -224,48 +229,87 @@ class ReconView : public View {
// T: Senn. 32.000k
Text text_ctcss{
{12 * 8 + 4, 7 * 16, 14 * 8, 1 * 8},
{14 * 8, 7 * 16, 8 * 8, 1 * 8},
""};
Button button_config{
{SCREEN_W - 7 * 8, 2 * 16, 7 * 8, 28},
"CONFIG"};
Button button_looking_glass{
{SCREEN_W - 7 * 8, 5 * 16, 7 * 8, 28},
"GLASS"};
// Button can be RECON or SCANNER
Button button_scanner_mode{
{SCREEN_W - 7 * 8, 8 * 16, 7 * 8, 28},
{SCREEN_W - 7 * 8, 3 * 16, 7 * 8, 28},
"RECON"};
Text file_name{
// show file used
{0, 8 * 16 + 6, SCREEN_W - 7 * 8, 16},
};
Button button_loop_config{
{SCREEN_W - 7 * 8, 5 * 16, 7 * 8, 28},
"[LOOP]"};
Button button_config{
{SCREEN_W - 7 * 8, 7 * 16, 7 * 8, 28},
"CONFIG"};
ButtonWithEncoder button_manual_start{
{0 * 8, 11 * 16, 11 * 8, 28},
{0 * 8, 9 * 16, 11 * 8, 28},
""};
ButtonWithEncoder button_manual_end{
{12 * 8 - 6, 11 * 16, 11 * 8, 28},
{12 * 8 - 6, 9 * 16, 11 * 8, 28},
""};
OptionsField field_recon_match_mode{
{0 * 8, 11 * 16},
16, // CONTINUOUS MATCH MODE / SPARSE TIMED MATCH MODE
{
{"MATCH:CONTINOUS", 0},
{"MATCH:SPARSE", 1}}};
OptionsField step_mode{
{18 * 8, 11 * 16},
12,
{}};
Button button_manual_recon{
{23 * 8 - 3, 11 * 16, 7 * 8, 28},
{23 * 8, 9 * 16, 7 * 8, 28},
"SEARCH"};
NumberField field_nblocks{
{8 * 8, 24 * 8 + 4},
2,
{1, 99},
1,
' ',
};
NumberField field_wait{
{19 * 8, 24 * 8 + 4},
5,
{-RECON_MAX_LOCK_DURATION, RECON_MAX_LOCK_DURATION},
STATS_UPDATE_INTERVAL,
' ',
};
NumberField field_lock_wait{
{25 * 8, 24 * 8 + 4},
4,
{0, RECON_MAX_LOCK_DURATION},
STATS_UPDATE_INTERVAL,
' ',
};
OptionsField field_mode{
{5 * 8, (26 * 8) + 4},
{6 * 8, (26 * 8) + 4},
4,
{}};
OptionsField field_bw{
{11 * 8, (26 * 8) + 4},
6,
{}};
OptionsField step_mode{
{17 * 8, (26 * 8) + 4},
12,
{}};
NumberField field_squelch{
{26 * 8, (26 * 8) + 4},
3,
{-90, 20},
1,
' ',
};
ButtonWithEncoder button_pause{
{0, (15 * 16) - 4, 72, 28},
@@ -307,9 +351,6 @@ class ReconView : public View {
[this](const Message* const p) {
this->on_statistics_update(static_cast<const ChannelStatisticsMessage*>(p)->statistics);
}};
// app save settings
std::app_settings settings{};
std::app_settings::AppSettings app_settings{};
};
} /* namespace ui */
+21 -45
View File
@@ -21,12 +21,13 @@
* Boston, MA 02110-1301, USA.
*/
#include "ui_recon_settings.hpp"
#include "ui_navigation.hpp"
#include "ui_fileman.hpp"
#include "ui_navigation.hpp"
#include "ui_recon_settings.hpp"
#include "ui_textentry.hpp"
#include "file.hpp"
#include "freqman_db.hpp"
#include "portapack.hpp"
#include "portapack_persistent_memory.hpp"
@@ -44,12 +45,10 @@ ReconSetupViewMain::ReconSetupViewMain(NavigationView& nav, Rect parent_rect, st
&button_output_file,
&checkbox_autosave_freqs,
&checkbox_autostart_recon,
&checkbox_continuous,
&checkbox_clear_output});
checkbox_autosave_freqs.set_value(persistent_memory::recon_autosave_freqs());
checkbox_autostart_recon.set_value(persistent_memory::recon_autostart_recon());
checkbox_continuous.set_value(persistent_memory::recon_continuous());
checkbox_clear_output.set_value(persistent_memory::recon_clear_output());
text_input_file.set(_input_file);
@@ -57,11 +56,9 @@ ReconSetupViewMain::ReconSetupViewMain(NavigationView& nav, Rect parent_rect, st
button_load_freqs.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) {
std::string dir_filter = "FREQMAN/";
std::string str_file_path = new_file_path.string();
if (str_file_path.find(dir_filter) != string::npos) { // assert file from the FREQMAN folder
// get the filename without txt extension so we can use load_freqman_file fcn
if (new_file_path.native().find(freqman_dir.native()) == 0) {
_input_file = new_file_path.stem().string();
text_input_file.set(_input_file);
} else {
@@ -72,10 +69,9 @@ ReconSetupViewMain::ReconSetupViewMain(NavigationView& nav, Rect parent_rect, st
button_save_freqs.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) {
std::string dir_filter = "FREQMAN/";
std::string str_file_path = new_file_path.string();
if (str_file_path.find(dir_filter) != string::npos) { // assert file from the FREQMAN folder
if (new_file_path.native().find(freqman_dir.native()) == 0) {
_output_file = new_file_path.stem().string();
button_output_file.set_text(_output_file);
} else {
@@ -87,7 +83,7 @@ ReconSetupViewMain::ReconSetupViewMain(NavigationView& nav, Rect parent_rect, st
button_output_file.on_select = [this, &nav](Button&) {
text_prompt(nav, _output_file, 28,
[this](std::string& buffer) {
_output_file = buffer;
_output_file = std::move(buffer);
button_output_file.set_text(_output_file);
});
};
@@ -96,49 +92,38 @@ ReconSetupViewMain::ReconSetupViewMain(NavigationView& nav, Rect parent_rect, st
void ReconSetupViewMain::save(std::string& input_file, std::string& output_file) {
persistent_memory::set_recon_autosave_freqs(checkbox_autosave_freqs.value());
persistent_memory::set_recon_autostart_recon(checkbox_autostart_recon.value());
persistent_memory::set_recon_continuous(checkbox_continuous.value());
persistent_memory::set_recon_clear_output(checkbox_clear_output.value());
input_file = _input_file;
output_file = _output_file;
};
void ReconSetupViewMore::save(uint32_t& recon_lock_duration, uint32_t& recon_lock_nb_match, uint32_t& recon_match_mode) {
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_update_ranges_when_recon(checkbox_update_ranges_when_recon.value());
recon_lock_duration = field_recon_lock_duration.value();
recon_lock_nb_match = field_recon_lock_nb_match.value();
recon_match_mode = field_recon_match_mode.selected_index_value();
persistent_memory::set_recon_auto_record_locked(checkbox_auto_record_locked.value());
};
void ReconSetupViewMain::focus() {
button_load_freqs.focus();
}
ReconSetupViewMore::ReconSetupViewMore(NavigationView& nav, Rect parent_rect, uint32_t recon_lock_duration, uint32_t recon_lock_nb_match, uint32_t recon_match_mode)
: View(parent_rect), _recon_lock_duration{recon_lock_duration}, _recon_lock_nb_match{recon_lock_nb_match}, _recon_match_mode{recon_match_mode} {
ReconSetupViewMore::ReconSetupViewMore(NavigationView& nav, Rect parent_rect)
: View(parent_rect) {
(void)nav;
hidden(true);
add_children({
&checkbox_load_freqs,
&checkbox_load_ranges,
&checkbox_load_hamradios,
&checkbox_update_ranges_when_recon,
&text_recon_lock_duration,
&field_recon_lock_duration,
&text_recon_lock_nb,
&field_recon_lock_nb_match,
&field_recon_match_mode,
});
add_children({&checkbox_load_freqs,
&checkbox_load_ranges,
&checkbox_load_hamradios,
&checkbox_update_ranges_when_recon,
&checkbox_auto_record_locked});
checkbox_load_freqs.set_value(persistent_memory::recon_load_freqs());
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());
field_recon_lock_duration.set_value(_recon_lock_duration);
field_recon_lock_nb_match.set_value(_recon_lock_nb_match);
field_recon_match_mode.set_by_value(_recon_match_mode);
checkbox_auto_record_locked.set_value(persistent_memory::recon_auto_record_locked());
};
void ReconSetupViewMore::focus() {
@@ -149,14 +134,8 @@ void ReconSetupView::focus() {
viewMain.focus();
}
ReconSetupView::ReconSetupView(
NavigationView& nav,
std::string _input_file,
std::string _output_file,
uint32_t _recon_lock_duration,
uint32_t _recon_lock_nb_match,
uint32_t _recon_match_mode)
: nav_{nav}, input_file{_input_file}, output_file{_output_file}, recon_lock_duration{_recon_lock_duration}, recon_lock_nb_match{_recon_lock_nb_match}, recon_match_mode{_recon_match_mode} {
ReconSetupView::ReconSetupView(NavigationView& nav, std::string _input_file, std::string _output_file)
: nav_{nav}, input_file{_input_file}, output_file{_output_file} {
add_children({&tab_view,
&viewMain,
&viewMore,
@@ -164,13 +143,10 @@ ReconSetupView::ReconSetupView(
button_save.on_select = [this, &nav](Button&) {
viewMain.save(input_file, output_file);
viewMore.save(recon_lock_duration, recon_lock_nb_match, recon_match_mode);
viewMore.save();
std::vector<std::string> messages;
messages.push_back(input_file);
messages.push_back(output_file);
messages.push_back(to_string_dec_uint(recon_lock_duration));
messages.push_back(to_string_dec_uint(recon_lock_nb_match));
messages.push_back(to_string_dec_uint(recon_match_mode));
on_changed(messages);
nav.pop();
};
+16 -53
View File
@@ -50,6 +50,8 @@
// maximum lock duration
#define RECON_MAX_LOCK_DURATION 9900
#define RECON_DEF_SQUELCH -14
// default number of match to have a lock
#define RECON_DEF_NB_MATCH 3
#define RECON_MIN_LOCK_DURATION 100 // have to be >= and a multiple of STATS_UPDATE_INTERVAL
@@ -71,8 +73,8 @@ class ReconSetupViewMain : public View {
void focus() override;
private:
std::string _input_file = {"RECON"};
std::string _output_file = {"RECON_RESULTS"};
std::string _input_file{"RECON"};
std::string _output_file{"RECON_RESULTS"};
Button button_load_freqs{
{1 * 8, 12, 18 * 8, 22},
@@ -98,29 +100,21 @@ class ReconSetupViewMain : public View {
3,
"autostart recon"};
Checkbox checkbox_continuous{
{1 * 8, 11 * 16 - 4},
3,
"continuous"};
Checkbox checkbox_clear_output{
{1 * 8, 13 * 16 - 4},
{1 * 8, 11 * 16 - 4},
3,
"clear output at start"};
};
class ReconSetupViewMore : public View {
public:
ReconSetupViewMore(NavigationView& nav, Rect parent_rect, uint32_t _recon_lock_duration, uint32_t _recon_lock_nb_match, uint32_t _recon_match_mode);
ReconSetupViewMore(NavigationView& nav, Rect parent_rect);
void save(uint32_t& recon_lock_duration, uint32_t& recon_lock_nb_match, uint32_t& recon_match_mode);
void save();
void focus() override;
private:
uint32_t _recon_lock_duration = STATS_UPDATE_INTERVAL;
uint32_t _recon_lock_nb_match = RECON_DEF_NB_MATCH;
uint32_t _recon_match_mode = RECON_MATCH_CONTINUOUS;
Checkbox checkbox_load_freqs{
{1 * 8, 12},
3,
@@ -140,43 +134,15 @@ class ReconSetupViewMore : public View {
{1 * 8, 102},
3,
"auto update m-ranges"};
NumberField field_recon_lock_duration{
{1 * 8, 132}, // position X , Y
4, // number of displayed digits (even empty)
{STATS_UPDATE_INTERVAL, RECON_MAX_LOCK_DURATION}, // range of number
STATS_UPDATE_INTERVAL, // rotary encoder increment
' ', // filling character
false // can loop
};
Text text_recon_lock_duration{
{1 * 8, 132, 22 * 8, 22},
" ms (lock duration)"};
NumberField field_recon_lock_nb_match{
{1 * 8, 162},
4,
{1, 99},
1,
' ',
false};
Text text_recon_lock_nb{
{1 * 8, 162, 25 * 8, 22},
" x (nb lock to match freq)"};
OptionsField field_recon_match_mode{
{1 * 8, 192},
20, // CONTINUOUS MATCH MODE / SPARSE TIMED MATCH MODE
{
{"SQL MATCH: CONTINOUS", 0},
{"SQL MATCH: SPARSE", 1}}};
Checkbox checkbox_auto_record_locked{
{1 * 8, 132},
3,
"record locked periods"};
};
class ReconSetupView : public View {
public:
ReconSetupView(NavigationView& nav, std::string _input_file, std::string _output_file, uint32_t _recon_lock_duration, uint32_t _recon_lock_nb_match, uint32_t _recon_match_mode);
ReconSetupView(NavigationView& nav, std::string _input_file, std::string _output_file);
std::function<void(std::vector<std::string> messages)> on_changed{};
@@ -187,16 +153,13 @@ class ReconSetupView : public View {
private:
NavigationView& nav_;
std::string input_file = {"RECON"};
std::string output_file = {"RECON_RESULTS"};
uint32_t recon_lock_duration = STATS_UPDATE_INTERVAL;
uint32_t recon_lock_nb_match = RECON_DEF_NB_MATCH;
uint32_t recon_match_mode = RECON_MATCH_CONTINUOUS;
std::string input_file{"RECON"};
std::string output_file{"RECON_RESULTS"};
Rect view_rect = {0, 3 * 8, SCREEN_W, 230};
Rect view_rect{0, 3 * 8, SCREEN_W, 230};
ReconSetupViewMain viewMain{nav_, view_rect, input_file, output_file};
ReconSetupViewMore viewMore{nav_, view_rect, recon_lock_duration, recon_lock_nb_match, recon_match_mode};
ReconSetupViewMore viewMore{nav_, view_rect};
TabView tab_view{
{"Main", Color::cyan(), &viewMain},
+195 -242
View File
@@ -21,19 +21,25 @@
*/
#include "ui_scanner.hpp"
#include "optional.hpp"
#include "ui_fileman.hpp"
#include "ui_freqman.hpp"
using namespace portapack;
namespace fs = std::filesystem;
namespace ui {
static const fs::path default_scan_file{u"FREQMAN/SCANNER.TXT"};
ScannerThread::ScannerThread(std::vector<rf::Frequency> frequency_list)
: frequency_list_{std::move(frequency_list)} {
_manual_search = false;
create_thread();
}
ScannerThread::ScannerThread(const jammer::jammer_range_t& frequency_range, size_t def_step_hz)
ScannerThread::ScannerThread(const scanner_range_t& frequency_range, size_t def_step_hz)
: frequency_range_(frequency_range), def_step_hz_(def_step_hz) {
_manual_search = true;
create_thread();
@@ -79,7 +85,7 @@ void ScannerThread::set_freq_del(const rf::Frequency v) {
}
// Force a one-time forward or reverse frequency index change; OK to do this without pausing scan thread
//(used when rotary encoder is turned)
// (used when rotary encoder is turned)
void ScannerThread::set_index_stepper(const int32_t v) {
_index_stepper = v;
}
@@ -122,7 +128,7 @@ void ScannerThread::run() {
if (force_one_step)
_index_stepper = 0;
receiver_model.set_tuning_frequency(frequency_list_[frequency_index]); // Retune
receiver_model.set_target_frequency(frequency_list_[frequency_index]); // Retune
}
message.freq = frequency_list_[frequency_index];
message.range = frequency_index; // Inform freq (for coloring purposes also!)
@@ -160,7 +166,7 @@ void ScannerThread::run() {
if (force_one_step)
_index_stepper = 0;
receiver_model.set_tuning_frequency(frequency_range_.min + frequency_index * def_step_hz_); // Retune
receiver_model.set_target_frequency(frequency_range_.min + frequency_index * def_step_hz_); // Retune
}
message.freq = frequency_range_.min + frequency_index * def_step_hz_;
message.range = 0; // Inform freq (for coloring purposes also!)
@@ -227,19 +233,28 @@ void ScannerView::handle_retune(int64_t freq, uint32_t freq_idx) {
}
if (!manual_search) {
if (frequency_list.size() > 0) {
if (entries.size() > 0)
text_current_index.set(to_string_dec_uint(freq_idx + 1, 3));
}
if (freq_idx < description_list.size() && description_list[freq_idx].size() > 1)
desc_current_index.set(description_list[freq_idx]); // Show description from file
if (freq_idx < entries.size() && entries[freq_idx].description.size() > 1)
text_current_desc.set(entries[freq_idx].description); // Show description from file
else
desc_current_index.set(desc_freq_list_scan); // Show Scan file name (no description in file)
text_current_desc.set(loaded_filename()); // Show Scan file name (no description in file)
}
}
std::string ScannerView::loaded_filename() const {
auto filename = loaded_path.filename().string();
if (filename.length() > 23) { // Truncate and add ellipses if long file name
filename.resize(22);
filename = filename + "+";
}
return filename;
}
void ScannerView::focus() {
field_mode.focus();
button_load.focus();
}
ScannerView::~ScannerView() {
@@ -253,93 +268,87 @@ ScannerView::~ScannerView() {
void ScannerView::show_max_index() { // show total number of freqs to scan
text_current_index.set("---");
if (frequency_list.size() == FREQMAN_MAX_PER_FILE) {
if (entries.size() == FREQMAN_MAX_PER_FILE) {
text_max_index.set_style(&Styles::red);
text_max_index.set("/ " + to_string_dec_uint(FREQMAN_MAX_PER_FILE) + " (DB MAX!)");
} else {
text_max_index.set_style(&Styles::grey);
text_max_index.set("/ " + to_string_dec_uint(frequency_list.size()));
text_max_index.set("/ " + to_string_dec_uint(entries.size()));
}
}
ScannerView::ScannerView(
NavigationView& nav)
: nav_{nav}, loaded_file_name{"SCANNER"} {
add_children({&labels,
&field_lna,
&field_vga,
&field_rf_amp,
&field_volume,
&field_bw,
&field_squelch,
&field_browse_wait,
&field_lock_wait,
&button_load,
&button_clear,
&rssi,
&text_current_index,
&text_max_index,
&desc_current_index,
&big_display,
&button_manual_start,
&button_manual_end,
&field_mode,
&field_step,
&button_manual_search,
&button_pause,
&button_dir,
&button_audio_app,
&button_mic_app,
&button_add,
&button_remove
: nav_{nav} {
add_children({
&labels,
&field_lna,
&field_vga,
&field_rf_amp,
&field_volume,
&field_bw,
&field_squelch,
&field_browse_wait,
&field_lock_wait,
&button_load,
&button_clear,
&rssi,
&text_current_index,
&text_max_index,
&text_current_desc,
&big_display,
&button_manual_start,
&button_manual_end,
&field_mode,
&field_step,
&button_manual_search,
&button_pause,
&button_dir,
&button_audio_app,
&button_mic_app,
&button_add,
&button_remove,
});
// Populate option text for these fields
freqman_set_modulation_option(field_mode);
freqman_set_step_option(field_step);
// Default starting modulation (these may be overridden in SCANNER.TXT)
change_mode(AM_MODULATION); // Default modulation
field_mode.set_by_value(AM_MODULATION); // Reflect the mode into the manual selector
field_step.set_by_value(9000); // Default step interval (Hz)
// Default starting modulation (from saved App Settings if enabled, and may be overridden in SCANNER.TXT)
field_mode.set_by_value((OptionsField::value_t)receiver_model.modulation()); // Reflect the mode into the manual selector
field_step.set_by_value(receiver_model.frequency_step()); // Default step interval (Hz)
change_mode((freqman_index_t)field_mode.selected_index_value());
// FUTURE: perhaps additional settings should be stored in persistent memory vs using defaults
// HELPER: Pre-setting a manual range, based on stored frequency
rf::Frequency stored_freq = persistent_memory::tuned_frequency();
rf::Frequency stored_freq = receiver_model.target_frequency();
frequency_range.min = stored_freq - 1000000;
button_manual_start.set_text(to_string_short_freq(frequency_range.min));
frequency_range.max = stored_freq + 1000000;
button_manual_end.set_text(to_string_short_freq(frequency_range.max));
// Button to load txt files from the FREQMAN folder
// Button to load a Freqman file.
button_load.on_select = [this, &nav](Button&) {
auto open_view = nav.push<FileLoadView>(".TXT");
open_view->on_changed = [this](std::filesystem::path new_file_path) {
std::string dir_filter = "FREQMAN/";
std::string str_file_path = new_file_path.string();
if (str_file_path.find(dir_filter) != string::npos) { // assert file from the FREQMAN folder
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) {
scan_pause();
// get the filename without txt extension so we can use load_freqman_file fcn
std::string str_file_name = new_file_path.stem().string();
frequency_file_load(str_file_name, true);
frequency_file_load(new_file_path);
} else {
nav_.display_modal("LOAD ERROR", "A valid file from\nFREQMAN directory is\nrequired.");
nav.display_modal("LOAD ERROR", "A valid file from\nFREQMAN directory is\nrequired.");
}
};
};
// Button to clear in-memory frequency list
button_clear.on_select = [this, &nav](Button&) {
if (scan_thread && frequency_list.size()) {
if (scan_thread && entries.size()) {
scan_thread->stop(); // STOP SCANNER THREAD
frequency_list.clear();
description_list.clear();
entries.clear();
show_max_index(); // UPDATE new list size on screen
text_current_index.set("");
desc_current_index.set(desc_freq_list_scan);
text_current_desc.set(loaded_filename());
scan_thread->set_freq_lock(0); // Reset the scanner lock
// FUTURE: Consider switching to manual search mode automatically after clear (but would need to validate freq range)
@@ -393,30 +402,30 @@ ScannerView::ScannerView(
button_audio_app.on_select = [this](Button&) {
if (scan_thread)
scan_thread->stop();
nav_.pop();
nav_.push<AnalogAudioView>();
auto settings = receiver_model.settings();
settings.frequency_step = field_step.selected_index_value();
nav_.replace<AnalogAudioView>(settings);
};
// Button to switch to Mic app
button_mic_app.on_select = [this](Button&) {
if (scan_thread)
scan_thread->stop();
nav_.pop();
nav_.push<MicTXView>();
// MicTX wants Modulation and Bandwidth overrides, but that's only stored on the RX model.
nav_.replace<MicTXView>(receiver_model.settings());
};
// Button to delete current frequency from scan Freq List
button_remove.on_select = [this](Button&) {
if (scan_thread && (frequency_list.size() > current_index)) {
if (scan_thread && (entries.size() > current_index)) {
scan_thread->set_scanning(false); // PAUSE Scanning if necessary
// Remove frequency from the Freq List in memory (it is not removed from the file)
scan_thread->set_freq_del(frequency_list[current_index]);
description_list.erase(description_list.begin() + current_index);
frequency_list.erase(frequency_list.begin() + current_index);
// Remove frequency from the Freq List in memory (it is not removed from the file).
scan_thread->set_freq_del(entries[current_index].freq);
entries.erase(entries.begin() + current_index);
show_max_index(); // UPDATE new list size on screen
desc_current_index.set(""); // Clean up description (cosmetic detail)
text_current_desc.set(""); // Clean up description (cosmetic detail)
scan_thread->set_freq_lock(0); // Reset the scanner lock
}
};
@@ -435,19 +444,21 @@ ScannerView::ScannerView(
manual_search = false; // Switch to List Scan mode
}
audio::output::stop();
if (scan_thread)
scan_thread->stop(); // STOP SCANNER THREAD
if (userpause) // If user-paused, resume
user_resume();
start_scan_thread(); // RESTART SCANNER THREAD in selected mode
restart_scan();
};
// Mode field was changed (AM/NFM/WFM)
field_mode.on_change = [this](size_t, OptionsField::value_t v) {
static freqman_index_t last_mode = AM_MODULATION;
// unsupported SPEC mode fix
if (v == SPEC_MODULATION) {
if (last_mode == AM_MODULATION)
v = WFM_MODULATION;
else
v = AM_MODULATION;
field_mode.set_selected_index(v);
}
last_mode = v;
receiver_model.disable();
baseband::shutdown();
change_mode((freqman_index_t)v);
@@ -458,19 +469,10 @@ ScannerView::ScannerView(
// Step field was changed (Hz) -- only affects manual Search mode
field_step.on_change = [this](size_t, OptionsField::value_t v) {
(void)v; // prevent compiler Unused warning
receiver_model.set_frequency_step(v);
if (manual_search && scan_thread) {
// Restart scan thread with new step value
scan_thread->stop(); // STOP SCANNER THREAD
// Resuming pause automatically
// FUTURE: Consider whether we should stay in Pause mode...
if (userpause) // If user-paused, resume
user_resume();
start_scan_thread(); // RESTART SCANNER THREAD in Manual Search mode
}
if (manual_search && scan_thread)
restart_scan();
};
// Button to toggle Forward/Reverse
@@ -486,65 +488,35 @@ ScannerView::ScannerView(
// Button to add current frequency (found during Search) to the Scan Frequency List
button_add.on_select = [this](Button&) {
File scanner_file;
const std::string freq_file_path = "FREQMAN/" + loaded_file_name + ".TXT";
auto result = scanner_file.open(freq_file_path); // First search if freq is already in txt
FreqmanDB db;
if (db.open(loaded_path, /*create*/ true)) {
freqman_entry entry{
.frequency_a = current_frequency,
.type = freqman_type::Single,
};
if (!result.is_valid()) {
const std::string frequency_to_add = "f=" + to_string_dec_uint(current_frequency / 1000) + to_string_dec_uint(current_frequency % 1000UL, 3, '0');
bool found = false;
constexpr size_t buffer_size = 1024;
char buffer[buffer_size];
for (size_t pointer = 0, freq_str_idx = 0; pointer < scanner_file.size(); pointer += buffer_size) {
size_t adjusted_buffer_size;
if (pointer + buffer_size >= scanner_file.size()) {
memset(buffer, 0, sizeof(buffer));
adjusted_buffer_size = scanner_file.size() - pointer;
} else {
adjusted_buffer_size = buffer_size;
}
scanner_file.seek(pointer);
scanner_file.read(buffer, adjusted_buffer_size);
for (size_t i = 0; i < adjusted_buffer_size; ++i) {
if (buffer[i] == frequency_to_add.data()[freq_str_idx]) {
++freq_str_idx;
if (freq_str_idx >= frequency_to_add.size()) {
found = true;
break;
}
} else {
freq_str_idx = 0;
}
}
if (found) {
break;
}
}
// Look for existing entry with same frequency.
auto it = db.find_entry([&entry](const auto& e) {
return e.frequency_a == entry.frequency_a;
});
auto found = (it != db.end());
if (found) {
nav_.display_modal("Error", "Frequency already exists");
bigdisplay_update(-1); // After showing an error
bigdisplay_update(-1); // Need to poke this control after displaying modal?
} else {
scanner_file.append(freq_file_path); // Second: append if it is not there
scanner_file.write_line(frequency_to_add);
db.append_entry(entry);
// Add to frequency_list in memory too, since we can now switch back from manual mode
// Note that we are allowing freqs to be added to file (code above) that exceed the max count we can load into memory
if (frequency_list.size() < FREQMAN_MAX_PER_FILE) {
frequency_list.push_back(current_frequency);
description_list.push_back("");
// Note that we are allowing freqs to be added to file (code above) that exceed the
// max count we can load into memory.
if (entries.size() < FREQMAN_MAX_PER_FILE) {
entries.push_back({current_frequency, ""});
show_max_index(); // Display updated frequency list size
}
}
} else {
nav_.display_modal("Error", "Cannot open " + loaded_file_name + ".TXT\nfor appending freq.");
bigdisplay_update(-1); // After showing an error
nav_.display_modal("Error", "Cannot open " + loaded_path.filename().string() + "\nfor appending freq.");
bigdisplay_update(-1); // Need to poke this control after displaying modal?
}
};
@@ -556,108 +528,78 @@ ScannerView::ScannerView(
field_lock_wait.set_value(2);
field_squelch.on_change = [this](int32_t v) { squelch = v; };
field_squelch.set_value(-10);
field_squelch.set_value(-30);
// LEARN FREQUENCIES
std::string scanner_txt = "SCANNER";
frequency_file_load(scanner_txt);
// LOAD FREQUENCIES
frequency_file_load(default_scan_file);
}
void ScannerView::frequency_file_load(std::string file_name, bool stop_all_before) {
bool found_range{false};
bool found_single{false};
freqman_index_t def_mod_index{-1};
freqman_index_t def_bw_index{-1};
freqman_index_t def_step_index{-1};
void ScannerView::frequency_file_load(const fs::path& path) {
freqman_index_t def_mod_index{freqman_invalid_index};
freqman_index_t def_bw_index{freqman_invalid_index};
freqman_index_t def_step_index{freqman_invalid_index};
// stop everything running now if required
if (stop_all_before) {
scan_thread->stop();
frequency_list.clear(); // clear the existing frequency list (expected behavior)
description_list.clear();
FreqmanDB db;
if (!db.open(path)) {
text_current_desc.set("NO " + path.filename().string());
loaded_path = default_scan_file;
return;
}
if (load_freqman_file(file_name, database)) {
loaded_file_name = file_name; // keep loaded filename in memory
for (auto& entry : database) { // READ LINE PER LINE
if (frequency_list.size() < FREQMAN_MAX_PER_FILE) { // We got space!
//
// Get modulation & bw & step from file if specified
// Note these values could be different for each line in the file, but we only look at the first one
//
// Note that freqman requires a very specific string for these parameters,
// so check syntax in frequency file if specified value isn't being loaded
//
if (def_mod_index == -1)
def_mod_index = entry.modulation;
entries.clear();
loaded_path = path;
Optional<scanner_range_t> range;
if (def_bw_index == -1)
def_bw_index = entry.bandwidth;
for (auto entry : db) {
if (is_invalid(def_mod_index))
def_mod_index = entry.modulation;
if (def_step_index == -1)
def_step_index = entry.step;
if (is_invalid(def_bw_index))
def_bw_index = entry.bandwidth;
// Get frequency
if (entry.type == RANGE) {
if (!found_range) {
// Set Start & End Search Range instead of populating the small in-memory frequency table
// NOTE: There may be multiple single frequencies in file, but only one search range is supported.
found_range = true;
frequency_range.min = entry.frequency_a;
button_manual_start.set_text(to_string_short_freq(frequency_range.min));
frequency_range.max = entry.frequency_b;
button_manual_end.set_text(to_string_short_freq(frequency_range.max));
}
} else if (entry.type == SINGLE) {
found_single = true;
frequency_list.push_back(entry.frequency_a);
description_list.push_back(entry.description);
} else if (entry.type == HAMRADIO) {
// For HAM repeaters, add both receive & transmit frequencies to scan list and modify description
// (FUTURE fw versions might handle these differently)
found_single = true;
frequency_list.push_back(entry.frequency_a);
description_list.push_back("R:" + entry.description);
if (is_invalid(def_step_index))
def_step_index = entry.step;
if ((entry.frequency_a != entry.frequency_b) &&
(frequency_list.size() < FREQMAN_MAX_PER_FILE)) {
frequency_list.push_back(entry.frequency_b);
description_list.push_back("T:" + entry.description);
}
}
} else {
break; // No more space: Stop reading the txt file !
}
switch (entry.type) {
case freqman_type::Single:
entries.push_back({entry.frequency_a, entry.description});
break;
case freqman_type::HamRadio:
entries.push_back({entry.frequency_a, "R: " + entry.description});
entries.push_back({entry.frequency_b, "T: " + entry.description});
break;
case freqman_type::Range:
// NB: Only the first range will be loaded.
if (!range)
range = {entry.frequency_a, entry.frequency_b};
break;
default:
break;
}
} else {
loaded_file_name = "SCANNER"; // back to the default frequency file
desc_current_index.set(" NO " + file_name + ".TXT FILE ...");
if (entries.size() >= FREQMAN_MAX_PER_FILE)
break;
}
desc_freq_list_scan = loaded_file_name + ".TXT";
if (desc_freq_list_scan.length() > 23) { // Truncate description and add ellipses if long file name
desc_freq_list_scan.resize(20);
desc_freq_list_scan = desc_freq_list_scan + "...";
}
if (is_valid(def_mod_index) && def_mod_index != (freqman_index_t)field_mode.selected_index_value())
field_mode.set_by_value(def_mod_index);
if ((def_mod_index != -1) && (def_mod_index != (freqman_index_t)field_mode.selected_index_value()))
field_mode.set_by_value(def_mod_index); // Update mode (also triggers a change callback that disables & reenables RF background)
if (def_bw_index != -1) // Update BW if specified in file
if (is_valid(def_bw_index))
field_bw.set_selected_index(def_bw_index);
if (def_step_index != -1) // Update step if specified in file
if (is_valid(def_step_index))
field_step.set_selected_index(def_step_index);
audio::output::stop();
// Found range, set it and update UI.
if (range) {
frequency_range = *range;
button_manual_start.set_text(to_string_short_freq(frequency_range.min));
button_manual_end.set_text(to_string_short_freq(frequency_range.max));
}
if (userpause) // If user-paused, resume
user_resume();
// Scan list if we found one, otherwise do manual range search
manual_search = !found_single;
start_scan_thread();
// Scan entries if any, otherwise do manual range search.
manual_search = entries.empty();
restart_scan();
}
void ScannerView::update_squelch_while_paused(int32_t max_db) {
@@ -746,7 +688,8 @@ void ScannerView::user_resume() {
userpause = false; // Resume scanning
}
void ScannerView::change_mode(freqman_index_t new_mod) { // Before this, do a scan_thread->stop(); After this do a start_scan_thread()
// Before this, do a scan_thread->stop(); After this do a start_scan_thread()
void ScannerView::change_mode(freqman_index_t new_mod) {
using option_t = std::pair<std::string, int32_t>;
using options_t = std::vector<option_t>;
options_t bw;
@@ -759,31 +702,22 @@ void ScannerView::change_mode(freqman_index_t new_mod) { // Before this, do a s
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);
field_bw.set_by_value(0);
receiver_model.set_am_configuration(field_bw.selected_index_value());
field_bw.set_by_value(receiver_model.am_configuration());
field_bw.on_change = [this](size_t, OptionsField::value_t n) { receiver_model.set_am_configuration(n); };
receiver_model.set_sampling_rate(3072000);
receiver_model.set_baseband_bandwidth(1750000);
break;
case NFM_MODULATION: // bw 16k (2) default
case NFM_MODULATION:
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);
field_bw.set_by_value(2);
receiver_model.set_nbfm_configuration(field_bw.selected_index_value());
field_bw.set_by_value(receiver_model.nbfm_configuration());
field_bw.on_change = [this](size_t, OptionsField::value_t n) { receiver_model.set_nbfm_configuration(n); };
receiver_model.set_sampling_rate(3072000);
receiver_model.set_baseband_bandwidth(1750000);
break;
case WFM_MODULATION:
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);
field_bw.set_by_value(0);
receiver_model.set_wfm_configuration(field_bw.selected_index_value());
field_bw.set_by_value(receiver_model.wfm_configuration());
field_bw.on_change = [this](size_t, OptionsField::value_t n) { receiver_model.set_wfm_configuration(n); };
receiver_model.set_sampling_rate(3072000);
receiver_model.set_baseband_bandwidth(2000000);
break;
default:
break;
@@ -792,22 +726,41 @@ void ScannerView::change_mode(freqman_index_t new_mod) { // Before this, do a s
void ScannerView::start_scan_thread() {
receiver_model.enable();
// Disable squelch on the model because RSSI handler is where the
// actual squelching is applied for this app.
receiver_model.set_squelch_level(0);
show_max_index();
// Start Scanner Thread
// FUTURE: Consider passing additional control flags (fwd,userpause,etc) to scanner thread at start (perhaps in a data structure)
if (manual_search) {
button_manual_search.set_text("SCAN"); // Update meaning of Manual Scan button
desc_current_index.set(desc_freq_range_search);
text_current_desc.set("SEARCHING...");
scan_thread = std::make_unique<ScannerThread>(frequency_range, field_step.selected_index_value());
} else {
button_manual_search.set_text("SRCH"); // Update meaning of Manual Scan button
desc_current_index.set(desc_freq_list_scan);
scan_thread = std::make_unique<ScannerThread>(frequency_list);
text_current_desc.set(loaded_filename());
// TODO: just pass ref to the thread?
std::vector<rf::Frequency> frequency_list;
frequency_list.reserve(entries.size());
for (const auto& entry : entries)
frequency_list.push_back(entry.freq);
scan_thread = std::make_unique<ScannerThread>(std::move(frequency_list));
}
scan_thread->set_scanning_direction(fwd);
}
void ScannerView::restart_scan() {
audio::output::stop();
if (scan_thread) // STOP SCANNER THREAD
scan_thread->stop();
if (userpause) // If user-paused, resume
user_resume();
start_scan_thread(); // RESTART SCANNER THREAD in selected mode
}
} /* namespace ui */
+45 -23
View File
@@ -20,18 +20,20 @@
* Boston, MA 02110-1301, USA.
*/
#include "ui.hpp"
#include "audio.hpp"
#include "analog_audio_app.hpp"
#include "baseband_api.hpp"
#include "file.hpp"
#include "freqman.hpp"
#include "freqman_db.hpp"
#include "portapack_persistent_memory.hpp"
#include "radio_state.hpp"
#include "receiver_model.hpp"
#include "string_format.hpp"
#include "ui.hpp"
#include "ui_mictx.hpp"
#include "ui_receiver.hpp"
#include "ui_styles.hpp"
#include "freqman.hpp"
#include "analog_audio_app.hpp"
#include "audio.hpp"
#include "ui_mictx.hpp"
#include "portapack_persistent_memory.hpp"
#include "baseband_api.hpp"
#include "string_format.hpp"
#include "file.hpp"
#define SCANNER_SLEEP_MS 50 // ms that Scanner Thread sleeps per loop
#define STATISTICS_UPDATES_PER_SEC 10
@@ -39,10 +41,28 @@
namespace ui {
// TODO: There is too much duplicated data in these classes.
// ScannerThread should just use more from the View.
// Or perhaps ScannerThread should just be in the View.
// TODO: Too many functions mix work and UI update.
// Consolidate UI fixup to a single function.
// TODO: Just use freqman_entry.
struct scanner_entry_t {
rf::Frequency freq;
std::string description;
};
struct scanner_range_t {
int64_t min;
int64_t max;
};
class ScannerThread {
public:
ScannerThread(std::vector<rf::Frequency> frequency_list);
ScannerThread(const jammer::jammer_range_t& frequency_range, size_t def_step_hz);
ScannerThread(const scanner_range_t& frequency_range, size_t def_step_hz);
~ScannerThread();
void set_scanning(const bool v);
@@ -64,7 +84,7 @@ class ScannerThread {
private:
std::vector<rf::Frequency> frequency_list_{};
jammer::jammer_range_t frequency_range_{false, 0, 0};
scanner_range_t frequency_range_{0, 0};
size_t def_step_hz_{0};
Thread* thread{nullptr};
@@ -88,27 +108,30 @@ class ScannerView : public View {
void focus() override;
std::string title() const override { return "Scanner"; };
std::vector<rf::Frequency> frequency_list{};
std::vector<string> description_list{};
// void set_parent_rect(const Rect new_parent_rect) override;
private:
app_settings::SettingsManager settings_{
"rx_scanner", app_settings::Mode::RX};
NavigationView& nav_;
RxRadioState radio_state_{};
void start_scan_thread();
void restart_scan();
void change_mode(freqman_index_t mod_type);
void show_max_index();
void scan_pause();
void scan_resume();
void user_resume();
void frequency_file_load(std::string file_name, bool stop_all_before = false);
void frequency_file_load(const std::filesystem::path& path);
void bigdisplay_update(int32_t);
void update_squelch_while_paused(int32_t max_db);
void on_statistics_update(const ChannelStatistics& statistics);
void handle_retune(int64_t freq, uint32_t freq_idx);
jammer::jammer_range_t frequency_range{false, 0, 0}; // perfect for manual scan task too...
std::string loaded_filename() const;
scanner_range_t frequency_range{0, 0};
int32_t squelch{0};
uint32_t browse_timer{0};
uint32_t lock_timer{0};
@@ -117,10 +140,12 @@ class ScannerView : public View {
rf::Frequency bigdisplay_current_frequency{0};
uint32_t browse_wait{0};
uint32_t lock_wait{0};
freqman_db database{};
std::string loaded_file_name;
std::filesystem::path loaded_path{};
std::vector<scanner_entry_t> entries{};
uint32_t current_index{0};
rf::Frequency current_frequency{0};
bool userpause{false};
bool manual_search{false};
bool fwd{true}; // to preserve direction setting even if scan_thread restarted
@@ -132,9 +157,6 @@ class ScannerView : public View {
BDC_RED
};
std::string desc_freq_range_search = "SEARCHING...";
std::string desc_freq_list_scan = "";
Labels labels{
{{0 * 8, 0 * 16}, "LNA: VGA: AMP: VOL:", Color::light_grey()},
{{0 * 8, 1 * 16}, "BW: SQ: Wsa: Wsl:", Color::light_grey()},
@@ -199,7 +221,7 @@ class ScannerView : public View {
{4 * 8, 3 * 16, 18 * 8, 16},
};
Text desc_current_index{
Text text_current_desc{
{0, 4 * 16, 240 - 6 * 8, 16},
};
+7 -20
View File
@@ -122,17 +122,7 @@ void SearchView::do_detection() {
locked = true;
locked_bin = bin_max;
// TODO
/*nav_.pop();
receiver_model.disable();
baseband::shutdown();
nav_.pop();*/
/*if (options_goto.selected_index() == 1)
nav_.push<AnalogAudioView>(false);
else if (options_goto.selected_index() == 2)
nav_.push<POCSAGAppView>();
*/
// TODO: open Audio.
} else
text_infos.set("Out of range");
}
@@ -221,7 +211,7 @@ void SearchView::on_channel_spectrum(const ChannelSpectrum& spectrum) {
slice_counter = 0;
} else
slice_counter++;
receiver_model.set_tuning_frequency(slices[slice_counter].center_frequency);
receiver_model.set_target_frequency(slices[slice_counter].center_frequency);
baseband::set_spectrum(SEARCH_SLICE_WIDTH, 31); // Clear
} else {
// Unique slice
@@ -271,7 +261,7 @@ void SearchView::on_range_changed() {
}
} else {
slices[0].center_frequency = (f_max + f_min) / 2;
receiver_model.set_tuning_frequency(slices[0].center_frequency);
receiver_model.set_target_frequency(slices[0].center_frequency);
slices_nb = 1;
text_slices.set(" 1");
@@ -376,25 +366,25 @@ SearchView::SearchView(
power_threshold = value;
};
field_frequency_min.set_value(receiver_model.tuning_frequency() - 1000000);
field_frequency_min.set_value(receiver_model.target_frequency() - 1000000);
field_frequency_min.set_step(100000);
field_frequency_min.on_change = [this](rf::Frequency) {
this->on_range_changed();
};
field_frequency_min.on_edit = [this, &nav]() {
auto new_view = nav.push<FrequencyKeypadView>(receiver_model.tuning_frequency());
auto new_view = nav.push<FrequencyKeypadView>(receiver_model.target_frequency() - 1000000);
new_view->on_changed = [this](rf::Frequency f) {
this->field_frequency_min.set_value(f);
};
};
field_frequency_max.set_value(receiver_model.tuning_frequency() + 1000000);
field_frequency_max.set_value(receiver_model.target_frequency() + 1000000);
field_frequency_max.set_step(100000);
field_frequency_max.on_change = [this](rf::Frequency) {
this->on_range_changed();
};
field_frequency_max.on_edit = [this, &nav]() {
auto new_view = nav.push<FrequencyKeypadView>(receiver_model.tuning_frequency());
auto new_view = nav.push<FrequencyKeypadView>(receiver_model.target_frequency() + 1000000);
new_view->on_changed = [this](rf::Frequency f) {
this->field_frequency_max.set_value(f);
};
@@ -414,9 +404,6 @@ SearchView::SearchView(
on_range_changed();
receiver_model.set_modulation(ReceiverModel::Mode::SpectrumAnalysis);
receiver_model.set_sampling_rate(SEARCH_SLICE_WIDTH);
receiver_model.set_baseband_bandwidth(2500000);
receiver_model.enable();
}
+7 -4
View File
@@ -21,6 +21,7 @@
*/
#include "receiver_model.hpp"
#include "radio_state.hpp"
#include "spectrum_color_lut.hpp"
@@ -89,6 +90,10 @@ class SearchView : public View {
private:
NavigationView& nav_;
RxRadioState radio_state_{
2500000 /* bandwidth */,
SEARCH_SLICE_WIDTH /* sampling rate */,
ReceiverModel::Mode::SpectrumAnalysis};
struct slice_t {
rf::Frequency center_frequency;
@@ -142,11 +147,9 @@ class SearchView : public View {
{{26 * 8, 25 * 8}, "MHz", Color::light_grey()}};
FrequencyField field_frequency_min{
{1 * 8, 1 * 16},
};
{1 * 8, 1 * 16}};
FrequencyField field_frequency_max{
{11 * 8, 1 * 16},
};
{11 * 8, 1 * 16}};
LNAGainField field_lna{
{22 * 8, 1 * 16}};
VGAGainField field_vga{
+153 -125
View File
@@ -24,6 +24,7 @@
#include "ui_settings.hpp"
#include "ui_navigation.hpp"
#include "ui_receiver.hpp"
#include "ui_touch_calibration.hpp"
#include "portapack_persistent_memory.hpp"
@@ -32,16 +33,27 @@ using namespace lpc43xx;
#include "audio.hpp"
#include "portapack.hpp"
using portapack::receiver_model;
using namespace portapack;
#include "file.hpp"
namespace fs = std::filesystem;
#include "string_format.hpp"
#include "ui_styles.hpp"
#include "cpld_update.hpp"
#include "freqman.hpp"
namespace pmem = portapack::persistent_memory;
namespace ui {
/* Sends a UI refresh message to cause the status bar to redraw. */
static void send_system_refresh() {
StatusRefreshMessage message{};
EventDispatcher::send_message(message);
}
/* SetDateTimeView ***************************************/
SetDateTimeView::SetDateTimeView(
NavigationView& nav) {
button_save.on_select = [&nav, this](Button&) {
@@ -105,13 +117,15 @@ SetDateTimeModel SetDateTimeView::form_collect() {
.second = static_cast<uint8_t>(field_second.value())};
}
/* SetRadioView ******************************************/
SetRadioView::SetRadioView(
NavigationView& nav) {
button_cancel.on_select = [&nav](Button&) {
nav.pop();
};
const auto reference = portapack::clock_manager.get_reference();
const auto reference = clock_manager.get_reference();
std::string source_name("---");
switch (reference.source) {
@@ -156,19 +170,18 @@ SetRadioView::SetRadioView(
&button_cancel});
SetFrequencyCorrectionModel model{
static_cast<int8_t>(portapack::persistent_memory::correction_ppb() / 1000), 0};
static_cast<int8_t>(pmem::correction_ppb() / 1000), 0};
form_init(model);
check_clkout.set_value(portapack::persistent_memory::clkout_enabled());
check_clkout.set_value(pmem::clkout_enabled());
check_clkout.on_select = [this](Checkbox&, bool v) {
clock_manager.enable_clock_output(v);
portapack::persistent_memory::set_clkout_enabled(v);
StatusRefreshMessage message{};
EventDispatcher::send_message(message);
pmem::set_clkout_enabled(v);
send_system_refresh();
};
field_clkout_freq.set_value(portapack::persistent_memory::clkout_freq());
field_clkout_freq.set_value(pmem::clkout_freq());
value_freq_step.set_style(&Styles::light_grey);
field_clkout_freq.on_select = [this](NumberField&) {
@@ -193,9 +206,9 @@ SetRadioView::SetRadioView(
field_clkout_freq.set_step(pow(10, freq_step_khz));
};
check_bias.set_value(portapack::get_antenna_bias());
check_bias.set_value(get_antenna_bias());
check_bias.on_select = [this](Checkbox&, bool v) {
portapack::set_antenna_bias(v);
set_antenna_bias(v);
// Update the radio.
receiver_model.set_antenna_bias();
@@ -205,15 +218,14 @@ SetRadioView::SetRadioView(
if (!v)
radio::set_antenna_bias(false);
StatusRefreshMessage message{};
EventDispatcher::send_message(message);
send_system_refresh();
};
button_save.on_select = [this, &nav](Button&) {
const auto model = this->form_collect();
portapack::persistent_memory::set_correction_ppb(model.ppm * 1000);
portapack::persistent_memory::set_clkout_freq(model.freq);
clock_manager.enable_clock_output(portapack::persistent_memory::clkout_enabled());
pmem::set_correction_ppb(model.ppm * 1000);
pmem::set_clkout_freq(model.freq);
clock_manager.enable_clock_output(pmem::clkout_enabled());
nav.pop();
};
}
@@ -233,54 +245,86 @@ SetFrequencyCorrectionModel SetRadioView::form_collect() {
};
}
/* SetUIView *********************************************/
SetUIView::SetUIView(NavigationView& nav) {
add_children({&checkbox_disable_touchscreen,
&checkbox_speaker,
&checkbox_bloff,
&options_bloff,
&checkbox_showsplash,
&checkbox_showclock,
&options_clockformat,
&checkbox_guireturnflag,
&labels,
&toggle_camera,
&toggle_sleep,
&toggle_stealth,
&toggle_converter,
&toggle_bias_tee,
&toggle_clock,
&toggle_mute,
&toggle_sd_card,
&button_save,
&button_cancel});
checkbox_disable_touchscreen.set_value(persistent_memory::disable_touchscreen());
checkbox_speaker.set_value(persistent_memory::config_speaker());
checkbox_showsplash.set_value(persistent_memory::config_splash());
checkbox_showclock.set_value(!persistent_memory::hide_clock());
checkbox_guireturnflag.set_value(persistent_memory::show_gui_return_icon());
// Display "Disable speaker" option only if AK4951 Codec which has separate speaker/headphone control
if (audio::speaker_disable_supported()) {
add_child(&toggle_speaker);
}
const auto backlight_config = persistent_memory::config_backlight_timer();
checkbox_disable_touchscreen.set_value(pmem::disable_touchscreen());
checkbox_showsplash.set_value(pmem::config_splash());
checkbox_showclock.set_value(!pmem::hide_clock());
checkbox_guireturnflag.set_value(pmem::show_gui_return_icon());
const auto backlight_config = pmem::config_backlight_timer();
checkbox_bloff.set_value(backlight_config.timeout_enabled());
options_bloff.set_by_value(backlight_config.timeout_enum());
if (persistent_memory::clock_with_date()) {
if (pmem::clock_with_date()) {
options_clockformat.set_selected_index(1);
} else {
options_clockformat.set_selected_index(0);
}
// NB: Invert so "active" == "not hidden"
toggle_camera.set_value(!pmem::ui_hide_camera());
toggle_sleep.set_value(!pmem::ui_hide_sleep());
toggle_stealth.set_value(!pmem::ui_hide_stealth());
toggle_converter.set_value(!pmem::ui_hide_converter());
toggle_bias_tee.set_value(!pmem::ui_hide_bias_tee());
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_sd_card.set_value(!pmem::ui_hide_sd_card());
button_save.on_select = [&nav, this](Button&) {
persistent_memory::set_config_backlight_timer({(persistent_memory::backlight_timeout_t)options_bloff.selected_index_value(),
checkbox_bloff.value()});
pmem::set_config_backlight_timer({(pmem::backlight_timeout_t)options_bloff.selected_index_value(),
checkbox_bloff.value()});
if (checkbox_showclock.value()) {
if (options_clockformat.selected_index() == 1)
persistent_memory::set_clock_with_date(true);
pmem::set_clock_with_date(true);
else
persistent_memory::set_clock_with_date(false);
pmem::set_clock_with_date(false);
}
if (checkbox_speaker.value()) audio::output::speaker_mute(); // Just mute audio if speaker is disabled
persistent_memory::set_config_speaker(checkbox_speaker.value()); // Store Speaker status
StatusRefreshMessage message{}; // Refresh status bar with/out speaker
EventDispatcher::send_message(message);
pmem::set_config_splash(checkbox_showsplash.value());
pmem::set_clock_hidden(!checkbox_showclock.value());
pmem::set_gui_return_icon(checkbox_guireturnflag.value());
pmem::set_disable_touchscreen(checkbox_disable_touchscreen.value());
pmem::set_ui_hide_camera(!toggle_camera.value());
pmem::set_ui_hide_sleep(!toggle_sleep.value());
pmem::set_ui_hide_stealth(!toggle_stealth.value());
pmem::set_ui_hide_converter(!toggle_converter.value());
pmem::set_ui_hide_bias_tee(!toggle_bias_tee.value());
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_sd_card(!toggle_sd_card.value());
send_system_refresh();
persistent_memory::set_config_splash(checkbox_showsplash.value());
persistent_memory::set_clock_hidden(!checkbox_showclock.value());
persistent_memory::set_gui_return_icon(checkbox_guireturnflag.value());
persistent_memory::set_disable_touchscreen(checkbox_disable_touchscreen.value());
nav.pop();
};
button_cancel.on_select = [&nav, this](Button&) {
@@ -292,21 +336,20 @@ void SetUIView::focus() {
button_save.focus();
}
// ---------------------------------------------------------
// Appl. Settings
// ---------------------------------------------------------
/* SetAppSettingsView ************************************/
SetAppSettingsView::SetAppSettingsView(NavigationView& nav) {
add_children({&checkbox_load_app_settings,
&checkbox_save_app_settings,
&button_save,
&button_cancel});
checkbox_load_app_settings.set_value(persistent_memory::load_app_settings());
checkbox_save_app_settings.set_value(persistent_memory::save_app_settings());
checkbox_load_app_settings.set_value(pmem::load_app_settings());
checkbox_save_app_settings.set_value(pmem::save_app_settings());
button_save.on_select = [&nav, this](Button&) {
persistent_memory::set_load_app_settings(checkbox_load_app_settings.value());
persistent_memory::set_save_app_settings(checkbox_save_app_settings.value());
pmem::set_load_app_settings(checkbox_load_app_settings.value());
pmem::set_save_app_settings(checkbox_save_app_settings.value());
nav.pop();
};
button_cancel.on_select = [&nav, this](Button&) {
@@ -318,9 +361,8 @@ void SetAppSettingsView::focus() {
button_save.focus();
}
// ---------------------------------------------------------
// Converter Settings
// ---------------------------------------------------------
/* SetConverterSettingsView ******************************/
SetConverterSettingsView::SetConverterSettingsView(NavigationView& nav) {
add_children({&check_show_converter,
&check_converter,
@@ -328,48 +370,45 @@ SetConverterSettingsView::SetConverterSettingsView(NavigationView& nav) {
&button_converter_freq,
&button_return});
check_show_converter.set_value(!portapack::persistent_memory::config_hide_converter());
check_show_converter.set_value(!pmem::ui_hide_converter());
check_show_converter.on_select = [this](Checkbox&, bool v) {
portapack::persistent_memory::set_config_hide_converter(!v);
pmem::set_ui_hide_converter(!v);
if (!v) {
check_converter.set_value(false);
}
// Retune to take converter change in account
receiver_model.set_tuning_frequency(portapack::persistent_memory::tuned_frequency());
// Retune to take converter change in account.
receiver_model.set_target_frequency(receiver_model.target_frequency());
// Refresh status bar with/out converter
StatusRefreshMessage message{};
EventDispatcher::send_message(message);
send_system_refresh();
};
check_converter.set_value(portapack::persistent_memory::config_converter());
check_converter.set_value(pmem::config_converter());
check_converter.on_select = [this](Checkbox&, bool v) {
if (v) {
check_show_converter.set_value(true);
portapack::persistent_memory::set_config_hide_converter(false);
pmem::set_ui_hide_converter(false);
}
portapack::persistent_memory::set_config_converter(v);
pmem::set_config_converter(v);
// Retune to take converter change in account
receiver_model.set_tuning_frequency(portapack::persistent_memory::tuned_frequency());
receiver_model.set_target_frequency(receiver_model.target_frequency());
// Refresh status bar with/out converter
StatusRefreshMessage message{};
EventDispatcher::send_message(message);
send_system_refresh();
};
converter_mode.set_by_value(portapack::persistent_memory::config_updown_converter());
converter_mode.set_by_value(pmem::config_updown_converter());
converter_mode.on_change = [this](size_t, OptionsField::value_t v) {
portapack::persistent_memory::set_config_updown_converter(v);
pmem::set_config_updown_converter(v);
// Refresh status bar with icon up or down
StatusRefreshMessage message{};
EventDispatcher::send_message(message);
send_system_refresh();
};
button_converter_freq.set_text(to_string_short_freq(portapack::persistent_memory::config_converter_freq()) + "MHz");
button_converter_freq.set_text(to_string_short_freq(pmem::config_converter_freq()) + "MHz");
button_converter_freq.on_select = [this, &nav](Button& button) {
auto new_view = nav.push<FrequencyKeypadView>(portapack::persistent_memory::config_converter_freq());
auto new_view = nav.push<FrequencyKeypadView>(pmem::config_converter_freq());
new_view->on_changed = [this, &button](rf::Frequency f) {
portapack::persistent_memory::set_config_converter_freq(f);
pmem::set_config_converter_freq(f);
// Retune to take converter change in account
receiver_model.set_tuning_frequency(portapack::persistent_memory::tuned_frequency());
receiver_model.set_target_frequency(receiver_model.target_frequency());
button_converter_freq.set_text("<" + to_string_short_freq(f) + " MHz>");
};
};
@@ -383,9 +422,8 @@ void SetConverterSettingsView::focus() {
button_return.focus();
}
// ---------------------------------------------------------
// Frequency Correction Settings
// ---------------------------------------------------------
/* SetFrequencyCorrectionView ****************************/
SetFrequencyCorrectionView::SetFrequencyCorrectionView(NavigationView& nav) {
add_children({&text_freqCorrection_about,
&frequency_rx_correction_mode,
@@ -394,38 +432,38 @@ SetFrequencyCorrectionView::SetFrequencyCorrectionView(NavigationView& nav) {
&button_freq_tx_correction,
&button_return});
frequency_rx_correction_mode.set_by_value(portapack::persistent_memory::config_freq_rx_correction_updown());
frequency_rx_correction_mode.set_by_value(pmem::config_freq_rx_correction_updown());
frequency_rx_correction_mode.on_change = [this](size_t, OptionsField::value_t v) {
portapack::persistent_memory::set_freq_rx_correction_updown(v);
pmem::set_freq_rx_correction_updown(v);
};
frequency_tx_correction_mode.set_by_value(portapack::persistent_memory::config_freq_rx_correction_updown());
frequency_tx_correction_mode.set_by_value(pmem::config_freq_rx_correction_updown());
frequency_tx_correction_mode.on_change = [this](size_t, OptionsField::value_t v) {
portapack::persistent_memory::set_freq_tx_correction_updown(v);
pmem::set_freq_tx_correction_updown(v);
};
button_freq_rx_correction.set_text(to_string_short_freq(portapack::persistent_memory::config_freq_rx_correction()) + "MHz (Rx)");
button_freq_rx_correction.set_text(to_string_short_freq(pmem::config_freq_rx_correction()) + "MHz (Rx)");
button_freq_rx_correction.on_select = [this, &nav](Button& button) {
auto new_view = nav.push<FrequencyKeypadView>(portapack::persistent_memory::config_converter_freq());
auto new_view = nav.push<FrequencyKeypadView>(pmem::config_converter_freq());
new_view->on_changed = [this, &button](rf::Frequency f) {
if (f >= MAX_FREQ_CORRECTION)
f = MAX_FREQ_CORRECTION;
portapack::persistent_memory::set_config_freq_rx_correction(f);
pmem::set_config_freq_rx_correction(f);
// Retune to take converter change in account
receiver_model.set_tuning_frequency(portapack::persistent_memory::tuned_frequency());
receiver_model.set_target_frequency(receiver_model.target_frequency());
button_freq_rx_correction.set_text("<" + to_string_short_freq(f) + " MHz>");
};
};
button_freq_tx_correction.set_text(to_string_short_freq(portapack::persistent_memory::config_freq_tx_correction()) + "MHz (Tx)");
button_freq_tx_correction.set_text(to_string_short_freq(pmem::config_freq_tx_correction()) + "MHz (Tx)");
button_freq_tx_correction.on_select = [this, &nav](Button& button) {
auto new_view = nav.push<FrequencyKeypadView>(portapack::persistent_memory::config_converter_freq());
auto new_view = nav.push<FrequencyKeypadView>(pmem::config_converter_freq());
new_view->on_changed = [this, &button](rf::Frequency f) {
if (f >= MAX_FREQ_CORRECTION)
f = MAX_FREQ_CORRECTION;
portapack::persistent_memory::set_config_freq_tx_correction(f);
pmem::set_config_freq_tx_correction(f);
// Retune to take converter change in account
receiver_model.set_tuning_frequency(portapack::persistent_memory::tuned_frequency());
receiver_model.set_target_frequency(receiver_model.target_frequency());
button_freq_tx_correction.set_text("<" + to_string_short_freq(f) + " MHz>");
};
};
@@ -439,9 +477,8 @@ void SetFrequencyCorrectionView::focus() {
button_return.focus();
}
// ---------------------------------------------------------
// Persistent Memory Settings
// ---------------------------------------------------------
/* SetPersistentMemoryView *******************************/
SetPersistentMemoryView::SetPersistentMemoryView(NavigationView& nav) {
add_children({&text_pmem_about,
&text_pmem_informations,
@@ -452,48 +489,42 @@ SetPersistentMemoryView::SetPersistentMemoryView(NavigationView& nav) {
&button_load_mem_defaults,
&button_return});
check_use_sdcard_for_pmem.set_value(portapack::persistent_memory::should_use_sdcard_for_pmem());
check_use_sdcard_for_pmem.set_value(pmem::should_use_sdcard_for_pmem());
check_use_sdcard_for_pmem.on_select = [this](Checkbox&, bool v) {
File pmem_flag_file_handle;
std::string pmem_flag_file = PMEM_FILEFLAG;
if (v) {
auto result = pmem_flag_file_handle.open(pmem_flag_file);
if (result.is_valid()) {
auto result = pmem_flag_file_handle.create(pmem_flag_file); // third: create if it is not there
if (!result.is_valid()) {
text_pmem_status.set("pmem flag file created");
} else {
text_pmem_status.set("!err. creating pmem flagfile!");
}
} else {
if (fs::file_exists(PMEM_FILEFLAG)) {
text_pmem_status.set("pmem flag already present");
} else {
auto error = pmem_flag_file_handle.create(PMEM_FILEFLAG);
if (error)
text_pmem_status.set("!err. creating pmem flagfile!");
else
text_pmem_status.set("pmem flag file created");
}
} else {
auto result = delete_file(pmem_flag_file);
if (result.code() != FR_OK) {
auto result = delete_file(PMEM_FILEFLAG);
if (result.code() != FR_OK)
text_pmem_status.set("!err. deleting pmem flagfile!");
} else {
else
text_pmem_status.set("pmem flag file deleted");
}
}
};
button_save_mem_to_file.on_select = [&nav, this](Button&) {
if (!portapack::persistent_memory::save_persistent_settings_to_file()) {
if (!pmem::save_persistent_settings_to_file())
text_pmem_status.set("!problem saving settings!");
} else {
else
text_pmem_status.set("settings saved");
}
};
button_load_mem_from_file.on_select = [&nav, this](Button&) {
if (!portapack::persistent_memory::load_persistent_settings_from_file()) {
if (!pmem::load_persistent_settings_from_file()) {
text_pmem_status.set("!problem loading settings!");
} else {
text_pmem_status.set("settings loaded");
// Refresh status bar with icon up or down
StatusRefreshMessage message{};
EventDispatcher::send_message(message);
send_system_refresh();
}
};
@@ -504,7 +535,7 @@ SetPersistentMemoryView::SetPersistentMemoryView(NavigationView& nav) {
YESNO,
[this](bool choice) {
if (choice) {
portapack::persistent_memory::cache::defaults();
pmem::cache::defaults();
}
});
};
@@ -518,19 +549,19 @@ void SetPersistentMemoryView::focus() {
button_return.focus();
}
// ---------------------------------------------------------
// Audio Settings
// ---------------------------------------------------------
/* SetAudioView ******************************************/
SetAudioView::SetAudioView(NavigationView& nav) {
add_children({&labels,
&field_tone_mix,
&button_save,
&button_cancel});
field_tone_mix.set_value(persistent_memory::tone_mix());
field_tone_mix.set_value(pmem::tone_mix());
button_save.on_select = [&nav, this](Button&) {
persistent_memory::set_tone_mix(field_tone_mix.value());
pmem::set_tone_mix(field_tone_mix.value());
audio::output::update_audio_mute();
nav.pop();
};
@@ -543,18 +574,17 @@ void SetAudioView::focus() {
button_save.focus();
}
// ---------------------------------------------------------
// QR Code Settings
// ------------------------------------------------------
/* SetQRCodeView *****************************************/
SetQRCodeView::SetQRCodeView(NavigationView& nav) {
add_children({&checkbox_bigger_qr,
&button_save,
&button_cancel});
checkbox_bigger_qr.set_value(persistent_memory::show_bigger_qr_code());
checkbox_bigger_qr.set_value(pmem::show_bigger_qr_code());
button_save.on_select = [&nav, this](Button&) {
persistent_memory::set_show_bigger_qr_code(checkbox_bigger_qr.value());
pmem::set_show_bigger_qr_code(checkbox_bigger_qr.value());
nav.pop();
};
@@ -567,19 +597,18 @@ void SetQRCodeView::focus() {
button_save.focus();
}
// ---------------------------------------------------------
// Rotary Encoder Dial Settings
// ---------------------------------------------------------
/* SetEncoderDialView ************************************/
SetEncoderDialView::SetEncoderDialView(NavigationView& nav) {
add_children({&labels,
&field_encoder_dial_sensitivity,
&button_save,
&button_cancel});
field_encoder_dial_sensitivity.set_by_value(persistent_memory::config_encoder_dial_sensitivity());
field_encoder_dial_sensitivity.set_by_value(pmem::config_encoder_dial_sensitivity());
button_save.on_select = [&nav, this](Button&) {
persistent_memory::set_encoder_dial_sensitivity(field_encoder_dial_sensitivity.selected_index_value());
pmem::set_encoder_dial_sensitivity(field_encoder_dial_sensitivity.selected_index_value());
nav.pop();
};
@@ -592,11 +621,10 @@ void SetEncoderDialView::focus() {
button_save.focus();
}
// ---------------------------------------------------------
// Settings main menu
// ---------------------------------------------------------
/* SettingsMenuView **************************************/
SettingsMenuView::SettingsMenuView(NavigationView& nav) {
if (portapack::persistent_memory::show_gui_return_icon()) {
if (pmem::show_gui_return_icon()) {
add_items({{"..", ui::Color::light_grey(), &bitmap_icon_previous, [&nav]() { nav.pop(); }}});
}
add_items({

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