Compare commits

...

59 Commits

Author SHA1 Message Date
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
144 changed files with 5799 additions and 3230 deletions
+62 -8
View File
@@ -1,19 +1,50 @@
import os
import re
import sys
import json
from urllib.error import HTTPError
raw_git = os.popen('git log next --since="24 hours" --pretty=format:"- %h - {USERNAME}*+%al-%an*: %s"').read()
main_repo_url = "https://github.com/eried/portapack-mayhem.git"
main_repo_identifier = "eried/portapack-mayhem"
# ^^&^ change these if main repo changed
os.system('gh repo set-default ' + main_repo_url)
raw_git = os.popen('git log next --since="1200 hours" --pretty=format:"- ^%h^ - {USERNAME}*_%al_%an*: %s"').read()
# ^ as github's rule, a real username can contains "-" but not "_" and "*", so use these two to seperate things.
# ^ during test at 2023-07-10, %al could return 3 different types of values:
# : 123+abc <<<< abc is real username
# : def <<<< def is 3rd level domain of unhidden email, drop this, fetch info from github cli client
# : note that github will probably change in the future
def compute_username(line):
stripped = re.search(r'(?<=\*)(.*?)(?=\*)', line).group(0)
line_org = line
stripped = re.search(r'(?<=\*)(.*?)(?=\*)', line).group(0) # get the string between "*" and "*"
now_item_hash = re.search(r'(?<=\^)(.*?)(?=\^)', line).group(0) # get the string between "*" and "*"
stripped_org = stripped
stripped = re.search(r'(?<=\_)(.*?)(?=\_)', stripped).group(0) # get the string between "_" and "_"
stripped_fallback = stripped
# now it's like "123+abc" or "def"
if ("+" in stripped): # 123+abc
stripped = re.sub(r'^.*?\+', "", stripped)
elif not ("+" in stripped): # maybe not real username, dropped, fetch from github cli client
fetched_now_item_json = os.popen(
'gh search commits repo:' + main_repo_identifier + ' --json author --hash ' + now_item_hash).read()
stripped = extract_first_login(fetched_now_item_json)
if stripped is False: # 403
return "@" + stripped_fallback
elif stripped == "app/": # user did their commit with github app
return "@" + stripped_fallback
else: # exception and edge cases
return "@" + stripped_fallback
if stripped is None: # if commit hash can't find AKA commit is only in fork
return "@" + stripped_fallback
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
@@ -21,8 +52,31 @@ def compile_line(line):
username = compute_username(line)
line = re.sub(r'[*].*[*]', "", line)
line = line.replace("{USERNAME}", username)
line = re.sub(r'\^', '', line, count=2)
return line
def extract_first_login(json_data):
if not json_data: # if returned null
return None
try:
data = json.loads(json_data)
if isinstance(data, list) and len(data) > 0:
first_object = data[0]
if 'author' in first_object and 'login' in first_object['author']:
return first_object['author']['login']
except HTTPError as e:
if e.code == 403 and "API rate limit exceeded" in str(e):
return False
except json.decoder.JSONDecodeError:
return None
return None
for row in raw_git.splitlines():
print(compile_line(row))
@@ -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
@@ -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,6 +47,8 @@ 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="${CHANGELOG//'%'/'%25'}"
@@ -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.2
+62 -8
View File
@@ -1,21 +1,52 @@
import os
import re
import sys
import json
from urllib.error import HTTPError
past_version = sys.argv[1]
raw_git = os.popen('git log ' + past_version + '..next --pretty=format:"- %h - {USERNAME}*+%al-%an*: %s"').read()
main_repo_url = "https://github.com/eried/portapack-mayhem.git"
main_repo_identifier = "eried/portapack-mayhem"
# ^^&^ change these if main repo changed
os.system('gh repo set-default ' + main_repo_url)
raw_git = os.popen('git log ' + past_version + '..next --pretty=format:"- ^%h^ - {USERNAME}*_%al_%an*: %s"').read()
# ^ as github's rule, a real username can contains "-" but not "_" and "*", so use these two to seperate things
# during test at 2023-07-10, %al could return 3 different types of values:
# 123+abc <<<< abc is real username
# def <<<< def is 3rd level domain of unhidden email, drop this, fetch info from github cli client
# note that github will probably change in the future
def compute_username(line):
stripped = re.search(r'(?<=\*)(.*?)(?=\*)', line).group(0)
line_org = line
stripped = re.search(r'(?<=\*)(.*?)(?=\*)', line).group(0) # get the string between "*" and "*"
now_item_hash = re.search(r'(?<=\^)(.*?)(?=\^)', line).group(0) # get the string between "*" and "*"
stripped_org = stripped
stripped = re.search(r'(?<=\_)(.*?)(?=\_)', stripped).group(0) # get the string between "_" and "_"
stripped_fallback = stripped
# now it's like "123+abc" or "def"
if ("+" in stripped): # 123+abc
stripped = re.sub(r'^.*?\+', "", stripped)
elif not ("+" in stripped): # maybe not real username, dropped, fetch from github cli client
fetched_now_item_json = os.popen(
'gh search commits repo:' + main_repo_identifier + ' --json author --hash ' + now_item_hash).read()
stripped = extract_first_login(fetched_now_item_json)
if stripped is False: # 403
return "@" + stripped_fallback
elif stripped == "app/":
return "@" + stripped_fallback
else: # exception and edge cases
return "@" + stripped_fallback
if stripped is None: # if commit hash can't find AKA commit is only in fork
return "@" + stripped_fallback
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
@@ -23,8 +54,31 @@ def compile_line(line):
username = compute_username(line)
line = re.sub(r'[*].*[*]', "", line)
line = line.replace("{USERNAME}", username)
line = re.sub(r'\^', '', line, count=2)
return line
def extract_first_login(json_data):
if not json_data: # if returned null
return None
try:
data = json.loads(json_data)
if isinstance(data, list) and len(data) > 0:
first_object = data[0]
if 'author' in first_object and 'login' in first_object['author']:
return first_object['author']['login']
except HTTPError as e:
if e.code == 403 and "API rate limit exceeded" in str(e):
return False
except json.decoder.JSONDecodeError:
return None
return None
for row in raw_git.splitlines():
print(compile_line(row))
+1 -1
View File
@@ -1 +1 @@
v1.7.2
v1.7.3
+4 -1
View File
@@ -177,6 +177,7 @@ set(CPPSRC
event_m0.cpp
file_reader.cpp
file.cpp
freqman_db.cpp
freqman.cpp
io_file.cpp
io_wave.cpp
@@ -184,6 +185,7 @@ set(CPPSRC
irq_lcd_frame.cpp
irq_rtc.cpp
log_file.cpp
metadata_file.cpp
portapack.cpp
qrcodegen.cpp
radio.cpp
@@ -233,6 +235,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
@@ -286,7 +289,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
+6 -9
View File
@@ -114,6 +114,8 @@ ResultCode load_settings(const std::string& app_name, AppSettings& settings) {
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);
@@ -124,8 +126,6 @@ ResultCode load_settings(const std::string& app_name, AppSettings& settings) {
read_setting(*data, setting::baseband_bandwidth, settings.baseband_bandwidth);
read_setting(*data, setting::sampling_rate, settings.sampling_rate);
read_setting(*data, setting::lna, settings.lna);
read_setting(*data, setting::vga, settings.vga);
read_setting(*data, setting::step, settings.step);
read_setting(*data, setting::volume, settings.volume);
@@ -153,6 +153,8 @@ ResultCode save_settings(const std::string& app_name, AppSettings& settings) {
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);
@@ -163,8 +165,6 @@ ResultCode save_settings(const std::string& app_name, AppSettings& settings) {
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::lna, settings.lna);
write_setting(settings_file, setting::vga, settings.vga);
write_setting(settings_file, setting::step, settings.step);
write_setting(settings_file, setting::volume, settings.volume);
@@ -173,6 +173,7 @@ ResultCode save_settings(const std::string& app_name, AppSettings& settings) {
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))
@@ -203,14 +204,10 @@ 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();
// TODO: Do these make sense for TX?
settings.sampling_rate = transmitter_model.sampling_rate();
settings.lna = transmitter_model.lna();
settings.vga = transmitter_model.vga();
}
if (flags_enabled(settings.mode, Mode::RX)) {
+11 -22
View File
@@ -31,8 +31,6 @@
#include "string_format.hpp"
#include "utility.hpp"
#include "debug.hpp"
using namespace portapack;
using namespace tonekey;
@@ -135,8 +133,7 @@ SPECOptionsView::SPECOptionsView(
AnalogAudioView::AnalogAudioView(
NavigationView& nav)
: nav_(nav) {
// A baseband image _must_ be running before
// interacting with the waterfall view.
// A baseband image _must_ be running before add waterfall view.
baseband::run_image(portapack::spi_flash::image_tag_wideband_spectrum);
add_children({&rssi,
@@ -194,6 +191,15 @@ AnalogAudioView::AnalogAudioView(
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() {
return spec_bw_index;
}
@@ -404,24 +410,7 @@ void AnalogAudioView::update_modulation(ReceiverModel::Mode modulation) {
}
void AnalogAudioView::handle_coded_squelch(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("???");
text_ctcss.set(tone_key_string_by_value(value, text_ctcss.parent_rect().width() / 8));
}
} /* namespace ui */
@@ -71,10 +71,10 @@ class NBFMOptionsView : public View {
}};
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,
@@ -138,6 +138,7 @@ class SPECOptionsView : public View {
class AnalogAudioView : public View {
public:
AnalogAudioView(NavigationView& nav);
AnalogAudioView(NavigationView& nav, ReceiverModel::settings_t override);
~AnalogAudioView();
void set_parent_rect(Rect new_parent_rect) override;
@@ -200,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{};
@@ -213,7 +214,7 @@ class AnalogAudioView : public View {
4096,
4};
spectrum::WaterfallWidget waterfall{true};
spectrum::WaterfallView waterfall{true};
void on_baseband_bandwidth_changed(uint32_t bandwidth_hz);
void on_modulation_changed(ReceiverModel::Mode modulation);
+3 -39
View File
@@ -30,6 +30,7 @@ namespace ui {
CaptureAppView::CaptureAppView(NavigationView& nav)
: nav_{nav} {
freqman_set_bandwidth_option(SPEC_MODULATION, option_bandwidth);
baseband::run_image(portapack::spi_flash::image_tag_capture);
add_children({
@@ -59,41 +60,8 @@ CaptureAppView::CaptureAppView(NavigationView& nav)
/* 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();
/* 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;
}
/* Set up proper anti aliasing BPF bandwith 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);
record_view.set_sampling_rate(sampling_rate);
receiver_model.set_sampling_rate(sampling_rate);
@@ -103,8 +71,6 @@ CaptureAppView::CaptureAppView(NavigationView& nav)
};
option_bandwidth.set_selected_index(7); // Preselected default option 500kHz.
receiver_model.set_modulation(ReceiverModel::Mode::Capture);
receiver_model.enable();
record_view.on_error = [&nav](std::string message) {
@@ -113,8 +79,6 @@ CaptureAppView::CaptureAppView(NavigationView& nav)
}
CaptureAppView::~CaptureAppView() {
// Most other apps can't handle "Capture" mode, set to something standard.
receiver_model.set_modulation(ReceiverModel::Mode::WidebandFMAudio);
receiver_model.disable();
baseband::shutdown();
}
+4 -21
View File
@@ -31,23 +31,7 @@
#include "ui_spectrum.hpp"
#include "app_settings.hpp"
#include "radio_state.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 this BW onwards, the LCD is ok, but the recorded file is 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 "freqman.hpp"
namespace ui {
@@ -66,13 +50,12 @@ class CaptureAppView : public View {
static constexpr ui::Dim header_height = 3 * 16;
NavigationView& nav_;
RxRadioState radio_state_{};
RxRadioState radio_state_{ReceiverModel::Mode::Capture};
app_settings::SettingsManager settings_{
"rx_capture", app_settings::Mode::RX,
app_settings::Options::UseGlobalTargetFrequency};
uint32_t sampling_rate = 0;
uint32_t anti_alias_baseband_bandwidth_filter = 2500000;
Labels labels{
{{0 * 8, 1 * 16}, "Rate:", Color::light_grey()},
@@ -103,7 +86,7 @@ class CaptureAppView : public View {
OptionsField option_bandwidth{
{5 * 8, 1 * 16},
5,
{BW_OPTIONS}};
{}};
RecordView record_view{
{0 * 8, 2 * 16, 30 * 8, 1 * 16},
@@ -113,7 +96,7 @@ class CaptureAppView : public View {
16384,
3};
spectrum::WaterfallWidget waterfall{};
spectrum::WaterfallView waterfall{};
};
} /* namespace ui */
+1 -2
View File
@@ -130,8 +130,7 @@ class ERTAppView : public View {
RxRadioState radio_state_{
2500000 /* bandwidth */,
4194304 /* sampling rate */
};
4194304 /* sampling rate */};
app_settings::SettingsManager settings_{
"rx_ert", app_settings::Mode::RX};
+28 -39
View File
@@ -26,6 +26,7 @@
#include "ui_fileman.hpp"
#include "io_file.hpp"
#include "metadata_file.hpp"
#include "utility.hpp"
#include "baseband_api.hpp"
@@ -33,6 +34,7 @@
#include "portapack_persistent_memory.hpp"
using namespace portapack;
namespace fs = std::filesystem;
namespace ui {
@@ -40,51 +42,38 @@ 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();
}
// 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 {
sample_rate = 500000;
}
// UI Fixup.
text_sample_rate.set(unit_auto_scale(sample_rate, 3, 1) + "Hz");
auto file_size = data_file.size();
auto duration = ms_duration(file_size, sample_rate, 2);
progressbar.set_max(file_size / 1024);
text_filename.set(file_path.filename().string().substr(0, 12));
text_filename.set(truncate(file_path.filename().string(), 12));
auto duration = ms_duration(file_size, sample_rate, 2);
text_duration.set(to_string_time_ms(duration));
button_play.focus();
+2 -2
View File
@@ -71,7 +71,7 @@ class GpsSimAppView : 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_file_changed(const std::filesystem::path& new_file_path);
void on_tx_progress(const uint32_t progress);
void toggle();
@@ -124,7 +124,7 @@ class GpsSimAppView : public View {
Color::green(),
Color::black()};
spectrum::WaterfallWidget waterfall{};
spectrum::WaterfallView waterfall{};
MessageHandlerRegistration message_handler_replay_thread_error{
Message::ID::ReplayThreadDone,
-1
View File
@@ -71,7 +71,6 @@ POCSAGAppView::POCSAGAppView(NavigationView& nav)
if (!settings_.loaded())
field_frequency.set_value(initial_target_frequency);
receiver_model.set_modulation(ReceiverModel::Mode::NarrowbandFMAudio);
receiver_model.enable();
// TODO: app setting instead?
+32 -40
View File
@@ -27,11 +27,13 @@
#include "ui_fileman.hpp"
#include "io_file.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 +41,41 @@ 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 = ms_duration(file_size, sample_rate, 4);
progressbar.set_max(file_size);
text_filename.set(file_path.filename().string().substr(0, 12));
text_filename.set(truncate(file_path.filename().string(), 12));
auto duration = ms_duration(file_size, sample_rate, 4);
text_duration.set(to_string_time_ms(duration));
button_play.focus();
@@ -200,7 +192,7 @@ ReplayAppView::ReplayAppView(
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) {
open_view->on_changed = [this](fs::path new_file_path) {
on_file_changed(new_file_path);
};
};
+2 -2
View File
@@ -67,7 +67,7 @@ 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_file_changed(const std::filesystem::path& new_file_path);
void on_tx_progress(const uint32_t progress);
void toggle();
@@ -121,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,
@@ -25,6 +25,7 @@
#include "soundboard_app.hpp"
#include "string_format.hpp"
#include "tonesets.hpp"
#include "ui_tone_key.hpp"
using namespace tonekey;
using namespace portapack;
@@ -29,7 +29,6 @@
#include "baseband_api.hpp"
#include "lfsr_random.hpp"
#include "io_wave.hpp"
#include "tone_key.hpp"
#include "app_settings.hpp"
#include "radio_state.hpp"
@@ -32,7 +32,7 @@ class SpectrumAnalysisModel {
namespace ui {
class SpectrumAnalysisView : public spectrum::WaterfallWidget {
class SpectrumAnalysisView : public spectrum::WaterfallView {
public:
private:
SpectrumAnalysisModel model;
+1 -2
View File
@@ -106,8 +106,7 @@ class TPMSAppView : public View {
RxRadioState radio_state_{
1750000 /* bandwidth */,
2457600 /* sampling rate */
};
2457600 /* sampling rate */};
app_settings::SettingsManager settings_{
"rx_tpms", app_settings::Mode::RX};
+3 -3
View File
@@ -468,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;
@@ -512,8 +513,7 @@ ADSBRxView::ADSBRxView(NavigationView& nav) {
baseband::set_adsb();
receiver_model.set_target_frequency(1090000000);
receiver_model.set_modulation(ReceiverModel::Mode::SpectrumAnalysis);
receiver_model.set_target_frequency(1'090'000'000);
receiver_model.enable();
}
+2 -2
View File
@@ -343,8 +343,8 @@ class ADSBRxView : public View {
private:
RxRadioState radio_state_{
2500000 /* bandwidth */,
2000000 /* sampling rate */
};
2000000 /* sampling rate */,
ReceiverModel::Mode::SpectrumAnalysis};
app_settings::SettingsManager settings_{
"rx_adsb", app_settings::Mode::RX};
-1
View File
@@ -91,7 +91,6 @@ AFSKRxView::AFSKRxView(NavigationView& nav)
audio::set_rate(audio::Rate::Hz_24000);
audio::output::start();
receiver_model.set_modulation(ReceiverModel::Mode::NarrowbandFMAudio);
receiver_model.enable();
}
-1
View File
@@ -119,7 +119,6 @@ APRSRxView::APRSRxView(NavigationView& nav, Rect parent_rect)
audio::set_rate(audio::Rate::Hz_24000);
audio::output::start();
receiver_model.set_modulation(ReceiverModel::Mode::NarrowbandFMAudio);
receiver_model.enable();
}
-1
View File
@@ -76,7 +76,6 @@ BTLERxView::BTLERxView(NavigationView& nav)
audio::set_rate(audio::Rate::Hz_24000);
audio::output::start();
receiver_model.set_modulation(ReceiverModel::Mode::WidebandFMAudio);
receiver_model.enable();
}
+2 -2
View File
@@ -51,8 +51,8 @@ class BTLERxView : public View {
NavigationView& nav_;
RxRadioState radio_state_{
4000000 /* bandwidth */,
4000000 /* sampling rate */
};
4000000 /* sampling rate */,
ReceiverModel::Mode::WidebandFMAudio};
app_settings::SettingsManager settings_{
"rx_btle", app_settings::Mode::RX};
+62 -2
View File
@@ -30,6 +30,9 @@
#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"
@@ -248,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 = switch_long_press_occurred((size_t)key) ? key_event_mask : 0;
return true;
}
@@ -325,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() {
@@ -335,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&) {
switches_long_press_enable(0);
nav.pop();
};
options_switches_mode.on_change = [this](size_t, OptionsField::value_t v) {
(void)v;
switches_long_press_enable(options_switches_mode.selected_index_value());
};
}
void DebugControlsView::focus() {
@@ -383,6 +404,8 @@ DebugMenuView::DebugMenuView(NavigationView& nav) {
{"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>(); }},
{"P.Memory", ui::Color::dark_cyan(), &bitmap_icon_memory, [&nav]() { nav.push<DebugPmemView>(); }},
{"Fonts Viewer", ui::Color::dark_cyan(), &bitmap_icon_notepad, [&nav]() { nav.push<DebugFontsView>(); }},
{"Debug Dump", ui::Color::dark_cyan(), &bitmap_icon_memory, [&nav]() { portapack::persistent_memory::debug_dump(); }},
});
set_max_rows(2); // allow wider buttons
}
@@ -443,6 +466,43 @@ 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);
}
/* DebugLCRView *******************************************************/
/*DebugLCRView::DebugLCRView(NavigationView& nav, std::string lcr_string) {
std::string debug_text;
+26 -5
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"};
@@ -300,6 +310,17 @@ 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 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__*/
+25 -30
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,14 @@
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 c16_ext{u".C16"};
static const fs::path png_ext{u".PNG"};
static const fs::path bmp_ext{u".BMP"};
} // namespace ui
namespace {
using namespace ui;
@@ -47,25 +56,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,15 +76,12 @@ 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;
if (path_iequal(ext, txt_ext))
ext = c16_ext;
else if (path_iequal(ext, c16_ext))
ext = txt_ext;
else
return {};
@@ -163,7 +150,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))
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 +288,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.
@@ -507,12 +494,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 (path_iequal(c16_ext, ext) || 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;
@@ -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_;
+196 -191
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,280 +23,284 @@
#include "ui_freqman.hpp"
#include "portapack.hpp"
#include "event_m0.hpp"
#include "portapack.hpp"
#include "rtc_time.hpp"
#include "utility.hpp"
#include <memory>
using namespace portapack;
namespace fs = std::filesystem;
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_, 30, [this](std::string& new_desc) {
entry_.description = new_desc;
refresh_ui();
});
};
button_save.on_select = [this, &nav](Button&) {
db_.insert_entry(entry_, db_.entry_count());
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_freq() {
// TODO: range edit support?
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();
};
}
menu_view.on_select = [&nav, this](FreqManUIList&) {
nav_.pop();
void FrequencyManagerView::on_edit_desc() {
temp_buffer_ = current_entry().description;
text_prompt(nav_, temp_buffer_, 28, [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();
});
}
auto& entry = database[menu_view.get_index()];
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_, 12, [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(entry, current_index() + 1);
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" + 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,
&labels,
&button_add_category,
&button_del_category,
&button_edit_freq,
&button_edit_desc,
&button_add_entry,
&button_del_entry});
freqlist_view.on_select = [this](size_t) {
button_edit_freq.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&) {
// Allows for quickly exiting control.
freqlist_view.on_leave = [this]() {
button_edit_freq.focus();
};
button_new_category.on_select = [this, &nav](Button&) {
desc_buffer = "";
on_new_category(nav);
button_add_category.on_select = [this]() {
on_add_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_del_category.on_select = [this]() {
on_del_category();
};
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();
};
}
+85 -64
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,15 +21,16 @@
* 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 {
@@ -40,74 +42,77 @@ class FreqManBaseView : public View {
void focus() override;
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};
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 +121,62 @@ 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"; };
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_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()}};
{{5 * 8, 14 * 16 - 4}, "Edit:", Color::light_grey()}};
Button button_new_category{
{23 * 8, 2, 7 * 8, 20},
"New"};
NewButton button_add_category{
{23 * 8, 0 * 16, 7 * 4, 20},
{},
&bitmap_icon_new_file,
Color::white(),
true};
NewButton button_del_category{
{26 * 8 + 4, 0 * 16, 7 * 4, 20},
{},
&bitmap_icon_trash,
Color::red(),
true};
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};
};
} /* namespace ui */
+21 -28
View File
@@ -26,6 +26,7 @@
#include "file.hpp"
using namespace portapack;
using namespace tonekey;
using portapack::memory::map::backup_ram;
namespace ui {
@@ -199,41 +200,33 @@ size_t LevelView::change_mode(freqman_index_t new_mod) {
field_bw.on_change = [this](size_t, OptionsField::value_t n) { receiver_model.set_wfm_configuration(n); };
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 */
+2 -2
View File
@@ -91,7 +91,7 @@ class LevelView : public View {
OptionsField field_mode{
{15 * 8, 1 * 16},
3,
4,
{}};
OptionsField step_mode{
@@ -114,7 +114,7 @@ class LevelView : public View {
}};
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
@@ -471,9 +471,9 @@ GlassView::GlassView(
button_marker.on_select = [this](ButtonWithEncoder&) {
receiver_model.set_target_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
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) {
@@ -495,9 +495,9 @@ GlassView::GlassView(
button_jump.on_select = [this](Button&) {
receiver_model.set_target_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
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&) {
@@ -513,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);
@@ -63,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;
@@ -71,7 +71,7 @@ class GlassView : public View {
private:
NavigationView& nav_;
RxRadioState radio_state_{};
RxRadioState radio_state_{ReceiverModel::Mode::SpectrumAnalysis};
app_settings::SettingsManager settings_{
"rx_glass", app_settings::Mode::RX};
+87 -58
View File
@@ -22,21 +22,20 @@
#include "ui_mictx.hpp"
#include "baseband_api.hpp"
#include "audio.hpp"
#include "wm8731.hpp"
using wolfson::wm8731::WM8731;
#include "tonesets.hpp"
#include "baseband_api.hpp"
#include "irq_controls.hpp"
#include "portapack_hal.hpp"
#include "string_format.hpp"
#include "irq_controls.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};
@@ -145,25 +144,25 @@ void MicTXView::rxaudio(bool 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).
}
}
if (bool_same_F_tx_rx_enabled) // when stop TX ,define to which freq RX we return
if (bool_same_F_tx_rx_enabled) // when stop TX, define to which freq RX we return
receiver_model.set_target_frequency(tx_frequency); // Update freq also for RX = TX
else
receiver_model.set_target_frequency(rx_frequency); // Now with separate freq controls!
@@ -196,7 +195,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,
@@ -260,7 +259,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) {
@@ -268,30 +267,30 @@ 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 time, sending msg , var-parameters >>shift_bits FM msg ,to audio_tx from M0 to M4 Proc -
ak4951_alc_and_wm8731_boost_GUI = v; // 0..4, WM8731_boost dB's options, (combination boost on/off, and effective gain in captured data >>x)
audio::input::start(ak4951_alc_and_wm8731_boost_GUI); // 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.
};
}
@@ -329,11 +328,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;
@@ -342,7 +341,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 }
@@ -354,15 +353,15 @@ 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.
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;
@@ -374,38 +373,38 @@ 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(" 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.
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(" DSB1-9k ", 0); // we offer in AM DSB two audio BW 9k / 6k.
rxbw.emplace_back(" DSB2-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 .
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
@@ -413,10 +412,10 @@ MicTXView::MicTXView(
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 .
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
@@ -426,7 +425,7 @@ 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("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);
field_rxbw.set_options(rxbw); // store that aux GUI option to the field_rxbw.
@@ -479,7 +478,7 @@ 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_target_frequency(v ? tx_frequency : rx_frequency); // To go to the proper tuned freq. when toggling it
};
@@ -510,17 +509,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)
}
}
};
@@ -607,6 +606,36 @@ 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_target_frequency(tx_frequency); // Save Tx frequency instead of Rx. Or maybe we need some "System Wide" changes to seperate Tx and Rx frequency.
+19 -16
View File
@@ -20,6 +20,8 @@
* Boston, MA 02110-1301, USA.
*/
// TODO: Consolidate Modulation/Bandwidth modes/settings with freqman/receiver_model.
#ifndef __UI_MICTX_H__
#define __UI_MICTX_H__
@@ -33,7 +35,6 @@
#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"
@@ -43,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;
@@ -117,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()},
@@ -147,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()},
@@ -176,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)
{20 * 8, 1 * 8},
11,
{
{" 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)
@@ -194,14 +197,14 @@ class MicTXView : public View {
}};
OptionsField options_wm8731_boost_mode{
{22 * 8, 1 * 8}, // Coordinates are: int:x (px), int:y (px)
{22 * 8, 1 * 8},
5,
{
{"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
@@ -227,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).
@@ -269,7 +272,7 @@ class MicTXView : public View {
OptionsField options_tone_key{
{12 * 8, (13 * 8) - 5},
23,
18,
{}};
Checkbox check_rogerbeep{
-1
View File
@@ -76,7 +76,6 @@ NRFRxView::NRFRxView(NavigationView& nav)
audio::set_rate(audio::Rate::Hz_24000);
audio::output::start();
receiver_model.set_modulation(ReceiverModel::Mode::WidebandFMAudio);
receiver_model.enable();
}
+2 -2
View File
@@ -51,8 +51,8 @@ class NRFRxView : public View {
NavigationView& nav_;
RxRadioState radio_state_{
4000000 /* bandwidth */,
4000000 /* sampling rate */
};
4000000 /* sampling rate */,
ReceiverModel::Mode::WidebandFMAudio};
app_settings::SettingsManager settings_{
"rx_nrf", app_settings::Mode::RX};
+270 -88
View File
@@ -23,11 +23,12 @@
*/
#include "ui_playlist.hpp"
#include "convert.hpp"
#include "file_reader.hpp"
#include "io_file.hpp"
#include "string_format.hpp"
#include "ui_fileman.hpp"
#include "io_file.hpp"
#include "utility.hpp"
#include "baseband_api.hpp"
@@ -39,11 +40,17 @@
using namespace portapack;
namespace fs = std::filesystem;
/* TODO: wouldn't it be easier if the playlist were just a list of C16 files
* (and maybe delays) and then read the data file next to the C16 file? */
/* TODO
* - Should frequency overrides be saved in the playlist?
* - Start playing from current track (vs start over)?
*/
namespace ui {
// TODO: consolidate extesions into a shared header?
static const fs::path c16_ext = u".C16";
static const fs::path ppl_ext = u".PPL";
void PlaylistView::load_file(const fs::path& playlist_path) {
File playlist_file;
auto error = playlist_file.open(playlist_path.string());
@@ -57,57 +64,154 @@ void PlaylistView::load_file(const fs::path& playlist_path) {
continue; // Empty or comment line.
auto cols = split_string(line, ',');
if (cols.size() < 3)
continue; // Line doesn't have enough columns.
auto entry = load_entry(trim(cols[0]));
playlist_entry entry{};
if (!entry)
continue;
parse_int(cols[0], entry.replay_frequency);
parse_int(cols[2], entry.sample_rate);
if (entry.replay_frequency == 0 || entry.sample_rate == 0)
continue; // Invalid freq or rate.
// Read optional delay value.
if (cols.size() > 1)
parse_int(cols[1], entry->ms_delay);
entry.replay_file = fs::path{u"/"} + std::string{cols[1]};
if (cols.size() == 4) // Optional delay value.
parse_int(cols[3], entry.initial_delay);
playlist_db_.emplace_back(std::move(entry));
playlist_db_.emplace_back(*std::move(entry));
}
}
Optional<PlaylistView::playlist_entry> PlaylistView::load_entry(fs::path&& path) {
File capture_file;
auto error = capture_file.open(path);
if (error)
return {};
// 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(const fs::path& new_file_path) {
stop();
reset();
current_index_ = 0;
playlist_path_ = new_file_path;
playlist_db_.clear();
load_file(playlist_path_);
update_ui();
button_play.focus();
// 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::reset() {
current_track_ = 0;
current_entry_ = nullptr;
current_entry_size_ = 0;
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;
}
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::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::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::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 replay_thread_ != nullptr;
}
bool PlaylistView::loop() const {
return check_loop.value();
}
bool PlaylistView::is_done() const {
return current_track_ >= playlist_db_.size();
bool PlaylistView::at_end() const {
return current_index_ >= playlist_db_.size();
}
void PlaylistView::toggle() {
@@ -118,25 +222,24 @@ void PlaylistView::toggle() {
}
void PlaylistView::start() {
reset();
if (playlist_db_.empty())
return; // Nothing to do.
if (next_track())
send_current_track();
current_index_ = 0u;
send_current_track();
}
/* Advance to the next track in the playlist. */
bool PlaylistView::next_track() {
if (is_done()) {
if (loop())
current_track_ = 0;
else
return false; // All done.
++current_index_;
// 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.
}
current_entry_ = &playlist_db_[current_track_];
current_track_++;
return true;
}
@@ -147,30 +250,25 @@ void PlaylistView::send_current_track() {
transmitter_model.disable();
ready_signal_ = false;
if (!current_entry_)
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<FileReader>();
auto error = reader->open(current_entry_->replay_file);
auto error = reader->open(current()->path);
if (error) {
show_file_error(current_entry_->replay_file, "Can't open file to send.");
show_file_error(current()->path, "Can't open file to send.");
return;
}
// Get the sample file size for the progress bar.
current_entry_size_ = reader->file().size();
progressbar_transmit.set_value(0);
// Wait a bit before sending if specified.
// TODO: this pauses the main thread, move into ReplayThread instead.
if (current_entry_->initial_delay > 0)
chThdSleepMilliseconds(current_entry_->initial_delay);
// ReplayThread starts immediately on contruction so
// ReplayThread starts immediately on construction so
// these need to be set before creating the ReplayThread.
transmitter_model.set_target_frequency(current_entry_->replay_frequency);
transmitter_model.set_sampling_rate(current_entry_->sample_rate * 8);
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();
@@ -178,6 +276,9 @@ void PlaylistView::send_current_track() {
// 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),
@@ -197,37 +298,48 @@ void PlaylistView::stop() {
// This terminates the underlying chThread.
replay_thread_.reset();
transmitter_model.disable();
// Reset the transmit progress bar.
progressbar_transmit.set_value(0);
update_ui();
}
void PlaylistView::update_ui() {
if (playlist_db_.empty()) {
text_track.set("Open a playlist file.");
} else {
text_track.set(
to_string_dec_uint(current_track_) + "/" +
to_string_dec_uint(playlist_db_.size()) + " " +
playlist_path_.filename().string());
}
button_play.set_bitmap(is_active() ? &bitmap_stop : &bitmap_play);
progressbar_track.set_value(current_track_);
progressbar_track.set_max(playlist_db_.size());
progressbar_transmit.set_max(current_entry_size_);
if (current_entry_) {
auto duration = ms_duration(current_entry_size_, current_entry_->sample_rate, 4);
text_filename.set(truncate(current_entry_->replay_file.filename().string(), 12));
text_sample_rate.set(unit_auto_scale(current_entry_->sample_rate, 3, 0) + "Hz");
text_duration.set(to_string_time_ms(duration));
text_frequency.set(to_string_short_freq(current_entry_->replay_frequency));
} else {
text_filename.set("-");
text_sample_rate.set("-");
text_duration.set("-");
text_frequency.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, 0) + "Hz");
auto duration = ms_duration(current()->file_size, current()->metadata.sample_rate, 4);
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);
}
button_play.set_bitmap(is_active() ? &bitmap_stop : &bitmap_play);
}
void PlaylistView::on_tx_progress(uint32_t progress) {
@@ -243,7 +355,7 @@ void PlaylistView::handle_replay_thread_done(uint32_t return_code) {
}
if (return_code == ReplayThread::READ_ERROR)
show_file_error(current_entry_->replay_file, "Replay read failed.");
show_file_error(current()->path, "Replay read failed.");
stop();
}
@@ -254,36 +366,103 @@ PlaylistView::PlaylistView(
baseband::run_image(portapack::spi_flash::image_tag_replay);
add_children({
&button_open,
&text_filename,
&text_sample_rate,
&text_duration,
&progressbar_track,
&progressbar_transmit,
&text_frequency,
&field_frequency,
&tx_view,
&check_loop,
&button_play,
&text_track,
&button_prev,
&button_add,
&button_delete,
&button_open,
&button_save,
&button_next,
&waterfall,
});
waterfall.show_audio_spectrum_view(false);
field_frequency.set_value(100'000'000);
field_frequency.on_change = [this](rf::Frequency f) {
if (current())
current()->metadata.center_frequency = 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);
};
};
button_play.on_select = [this](ImageButton&) {
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) {
on_file_changed(new_file_path);
button_add.on_select = [this, &nav]() {
if (is_active())
return;
auto open_view = nav_.push<FileLoadView>(".C16");
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 (path_iequal(ext, c16_ext))
add_entry(fs::path{path});
}
PlaylistView::~PlaylistView() {
transmitter_model.disable();
baseband::shutdown();
@@ -292,20 +471,23 @@ PlaylistView::~PlaylistView() {
void PlaylistView::set_parent_rect(Rect new_parent_rect) {
View::set_parent_rect(new_parent_rect);
const ui::Rect waterfall_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();
waterfall.on_hide();
View::on_hide();
}
void PlaylistView::focus() {
button_open.focus();
}
} /* namespace ui */
+77 -33
View File
@@ -25,13 +25,16 @@
#define NORMAL_UI false
#include "app_settings.hpp"
#include "bitmap.hpp"
#include "file.hpp"
#include "metadata_file.hpp"
#include "radio_state.hpp"
#include "ui_widget.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>
@@ -42,17 +45,15 @@ namespace ui {
class PlaylistView : public View {
public:
PlaylistView(NavigationView& nav);
PlaylistView(NavigationView& nav, const std::filesystem::path& path);
~PlaylistView();
// Disable copy to make -Weffc++ happy.
PlaylistView(const PlaylistView&) = delete;
PlaylistView& operator=(const PlaylistView&) = delete;
// Following 2 called by 'NavigationView::update_view' after view is created.
void set_parent_rect(Rect new_parent_rect) override;
void on_hide() override;
void focus() override;
void on_hide() override;
std::string title() const override { return "Playlist"; };
std::string title() const override { return "Replay"; };
private:
NavigationView& nav_;
@@ -61,35 +62,40 @@ class PlaylistView : public View {
"tx_playlist", app_settings::Mode::TX};
// More header == less spectrum view.
static constexpr ui::Dim header_height = 4 * 16;
static constexpr ui::Dim header_height = 6 * 16;
static constexpr uint32_t baseband_bandwidth = 2500000;
struct playlist_entry {
rf::Frequency replay_frequency{0};
std::filesystem::path replay_file{};
uint32_t sample_rate{};
uint32_t initial_delay{};
std::filesystem::path path{};
capture_metadata metadata{};
File::Size file_size{};
uint32_t ms_delay{};
};
std::unique_ptr<ReplayThread> replay_thread_{};
bool ready_signal_{}; // Used to signal the ReplayThread.
size_t current_track_{0};
const playlist_entry* current_entry_{};
size_t current_entry_size_{0};
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 loop() const;
bool is_done() const;
bool at_end() const;
void toggle();
void start();
@@ -103,31 +109,33 @@ class PlaylistView : public View {
void on_tx_progress(uint32_t progress);
void handle_replay_thread_done(uint32_t return_code);
Button button_open{
{0 * 8, 0 * 16, 10 * 8, 2 * 16},
"Open PPL"};
Text text_filename{
{11 * 8, 0 * 16, 12 * 8, 16}};
{0 * 8, 0 * 16, 30 * 8, 16}};
FrequencyField field_frequency{
{0 * 8, 1 * 16}};
Text text_sample_rate{
{24 * 8, 0 * 16, 6 * 8, 16}};
{10 * 8, 1 * 16, 7 * 8, 16}};
Text text_duration{
{11 * 8, 1 * 16, 6 * 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}};
{18 * 8, 1 * 16, 12 * 8, 8 + 1}};
ProgressBar progressbar_transmit{
{18 * 8, 3 * 8, 12 * 8, 8}};
{18 * 8, 3 * 8 - 1, 12 * 8, 8}};
Text text_frequency{
{0 * 8, 2 * 16, 9 * 8, 16}};
Text text_duration{
{0 * 8, 2 * 16, 5 * 8, 16}};
// TODO: delay duration field.
TransmitterView2 tx_view{
74, 1 * 8, SHORT_UI // x(columns), y (line) position.
};
9 * 8, 1 * 8, SHORT_UI};
Checkbox check_loop{
{21 * 8, 2 * 16},
@@ -144,7 +152,43 @@ class PlaylistView : public View {
Text text_track{
{0 * 8, 3 * 16, 30 * 8, 16}};
spectrum::WaterfallWidget waterfall{};
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,
+1 -2
View File
@@ -80,8 +80,7 @@ bool POCSAGTXView::start_tx() {
progressbar.set_max(total_frames);
transmitter_model.set_rf_amp(true);
transmitter_model.set_lna(40);
transmitter_model.set_vga(40);
transmitter_model.set_tx_gain(40);
transmitter_model.enable();
uint8_t* data_ptr = shared_memory.bb_data.data;
+318 -251
View File
@@ -23,41 +23,84 @@
#include "ui_recon.hpp"
#include "ui_fileman.hpp"
#include "capture_app.hpp"
#include "file.hpp"
#include "tone_key.hpp"
using namespace portapack;
using namespace tonekey;
using portapack::memory::map::backup_ram;
namespace ui {
bool ReconView::current_is_valid() {
return (unsigned)current_index < frequency_list.size();
}
freqman_entry& ReconView::current_entry() {
return *frequency_list[current_index];
}
void ReconView::set_loop_config(bool v) {
continuous = v;
button_loop_config.set_style(v ? &Styles::green : &Styles::white);
persistent_memory::set_recon_continuous(continuous);
}
void ReconView::clear_freqlist_for_ui_action() {
audio::output::stop();
// flag to detect and reload frequency_list
freqlist_cleared_for_ui_action = true;
// if in manual mode, there is enough memory to load freqman files, else we have to unload/reload
if (!manual_mode) {
// clear and shrink_to_fit are not enough to really start with a new, clean, empty vector
// swap is the only way to achieve a perfect memory liberation
std::vector<freqman_entry>().swap(frequency_list);
} else {
frequency_list.shrink_to_fit();
void ReconView::recon_stop_recording() {
if (is_recording) {
if (field_mode.selected_index_value() == SPEC_MODULATION)
button_audio_app.set_text("RAW");
else
button_audio_app.set_text("AUDIO");
button_audio_app.set_style(&Styles::white);
record_view->stop();
button_config.set_style(&Styles::white); // disable config while recording as it's causing an IO error pop up at exit
is_recording = false;
}
}
void ReconView::clear_freqlist_for_ui_action() {
recon_stop_recording();
if (field_mode.selected_index_value() != SPEC_MODULATION)
audio::output::stop();
// flag to detect and reload frequency_list
if (!manual_mode) {
// Clear doesn't actually free, re-assign so destructor runs on previous instance.
frequency_list = freqman_db{};
} else
frequency_list.shrink_to_fit();
freqlist_cleared_for_ui_action = true;
}
void ReconView::reset_indexes() {
last_entry.modulation = -1;
last_entry.bandwidth = -1;
last_entry.step = -1;
description = "...no description...";
last_entry.modulation = freqman_invalid_index;
last_entry.bandwidth = freqman_invalid_index;
last_entry.step = freqman_invalid_index;
current_index = 0;
}
void ReconView::update_description() {
if (frequency_list.empty() || current_entry().description.empty()) {
description = "...no description...";
return;
} else {
switch (current_entry().type) {
case freqman_type::Range:
description = "R: ";
break;
case freqman_type::HamRadio:
description = "H: ";
break;
default:
description = "S: ";
}
description += current_entry().description;
}
desc_cycle.set(description);
}
void ReconView::colorize_waits() {
// colorize wait on match
if (wait == 0) {
@@ -84,18 +127,18 @@ void ReconView::colorize_waits() {
bool ReconView::recon_save_freq(const std::string& freq_file_path, size_t freq_index, bool warn_if_exists) {
File recon_file;
if (frequency_list.size() == 0 || (frequency_list.size() && current_index > (int32_t)frequency_list.size()))
if (frequency_list.size() == 0 || !current_is_valid())
return false;
freqman_entry entry = frequency_list[freq_index];
freqman_entry entry = *frequency_list[freq_index]; // Makes a copy.
entry.frequency_a = freq;
entry.frequency_b = 0;
entry.modulation = last_entry.modulation;
entry.bandwidth = last_entry.bandwidth;
entry.type = SINGLE;
entry.type = freqman_type::Single;
std::string frequency_to_add;
get_freq_string(entry, frequency_to_add);
// TODO: Use FreqmanDB
auto frequency_to_add = to_freqman_string(entry);
auto result = recon_file.open(freq_file_path); // First recon if freq is already in txt
if (!result.is_valid()) {
@@ -162,7 +205,7 @@ bool ReconView::recon_load_config_from_sd() {
pos = line_start;
while ((line_end = strstr(line_start, "\x0A"))) {
length = line_end - line_start - 1;
params[it] = string(pos, length);
params[it] = std::string(pos, length);
it++;
line_start = line_end + 1;
pos = line_start;
@@ -234,8 +277,8 @@ bool ReconView::recon_save_config_to_sd() {
make_new_directory(u"SETTINGS");
auto result = settings_file.create(RECON_CFG_FILE);
if (result.is_valid())
auto error = settings_file.create(RECON_CFG_FILE);
if (error)
return false;
settings_file.write_line(input_file);
settings_file.write_line(output_file);
@@ -248,7 +291,8 @@ bool ReconView::recon_save_config_to_sd() {
}
void ReconView::audio_output_start() {
audio::output::start();
if (field_mode.selected_index_value() != SPEC_MODULATION)
audio::output::start();
receiver_model.set_headphone_volume(receiver_model.headphone_volume()); // WM8731 hack.
}
@@ -281,10 +325,6 @@ void ReconView::recon_redraw() {
} else if (freq_lock >= recon_lock_nb_match) {
big_display.set_style(&Styles::green);
button_pause.set_text("<UNLOCK>");
// FREQ IS STRONG: GREEN and recon will pause when on_statistics_update()
if ((!scanner_mode) && autosave && frequency_list.size() > 0) {
recon_save_freq(freq_file_path, current_index, false);
}
}
}
if (last_db != db || last_list_size != frequency_list.size()) {
@@ -300,53 +340,38 @@ void ReconView::handle_retune() {
receiver_model.set_target_frequency(freq); // Retune
}
if (frequency_list.size() > 0) {
if (last_entry.modulation != frequency_list[current_index].modulation && frequency_list[current_index].modulation >= 0) {
last_entry.modulation = frequency_list[current_index].modulation;
field_mode.set_selected_index(frequency_list[current_index].modulation);
last_entry.bandwidth = -1;
if (last_entry.modulation != current_entry().modulation && is_valid(current_entry().modulation)) {
last_entry.modulation = current_entry().modulation;
field_mode.set_selected_index(current_entry().modulation);
last_entry.bandwidth = freqman_invalid_index;
}
// Set bandwidth if any
if (last_entry.bandwidth != frequency_list[current_index].bandwidth && frequency_list[current_index].bandwidth >= 0) {
last_entry.bandwidth = frequency_list[current_index].bandwidth;
field_bw.set_selected_index(frequency_list[current_index].bandwidth);
if (last_entry.bandwidth != current_entry().bandwidth && is_valid(current_entry().bandwidth)) {
last_entry.bandwidth = current_entry().bandwidth;
field_bw.set_selected_index(current_entry().bandwidth);
}
if (last_entry.step != frequency_list[current_index].step && frequency_list[current_index].step >= 0) {
last_entry.step = frequency_list[current_index].step;
if (last_entry.step != current_entry().step && is_valid(current_entry().step)) {
last_entry.step = current_entry().step;
step = freqman_entry_get_step_value(last_entry.step);
step_mode.set_selected_index(step);
}
if (last_index != current_index) {
last_index = current_index;
if ((int32_t)frequency_list.size() && current_index < (int32_t)frequency_list.size() && frequency_list[current_index].type == RANGE) {
if (frequency_list.size() && current_is_valid() && current_entry().type == freqman_type::Range) {
if (update_ranges && !manual_mode) {
button_manual_start.set_text(to_string_short_freq(frequency_list[current_index].frequency_a));
frequency_range.min = frequency_list[current_index].frequency_a;
if (frequency_list[current_index].frequency_b != 0) {
button_manual_end.set_text(to_string_short_freq(frequency_list[current_index].frequency_b));
frequency_range.max = frequency_list[current_index].frequency_b;
button_manual_start.set_text(to_string_short_freq(current_entry().frequency_a));
frequency_range.min = current_entry().frequency_a;
if (current_entry().frequency_b != 0) {
button_manual_end.set_text(to_string_short_freq(current_entry().frequency_b));
frequency_range.max = current_entry().frequency_b;
} else {
button_manual_end.set_text(to_string_short_freq(frequency_list[current_index].frequency_a));
frequency_range.max = frequency_list[current_index].frequency_a;
button_manual_end.set_text(to_string_short_freq(current_entry().frequency_a));
frequency_range.max = current_entry().frequency_a;
}
}
}
text_cycle.set_text(to_string_dec_uint(current_index + 1, 3));
if (frequency_list[current_index].description.size() > 0) {
switch (frequency_list[current_index].type) {
case RANGE:
desc_cycle.set("R: " + frequency_list[current_index].description); // Show new description
break;
case HAMRADIO:
desc_cycle.set("H: " + frequency_list[current_index].description); // Show new description
break;
default:
case SINGLE:
desc_cycle.set("S: " + frequency_list[current_index].description); // Show new description
break;
}
} else {
desc_cycle.set("...no description..."); // Show new description
}
update_description();
}
}
}
@@ -356,16 +381,20 @@ void ReconView::focus() {
}
ReconView::~ReconView() {
// Save recon config.
recon_stop_recording();
recon_save_config_to_sd();
audio::output::stop();
if (field_mode.selected_index_value() != SPEC_MODULATION)
audio::output::stop();
receiver_model.disable();
baseband::shutdown();
}
ReconView::ReconView(NavigationView& nav)
: nav_{nav} {
chrono_start = chTimeNow();
record_view = std::make_unique<RecordView>(Rect{0, 0, 30 * 8, 1 * 16},
u"AUTO_AUDIO_", u"AUDIO",
RecordView::FileType::WAV, 4096, 4);
add_children({&labels,
&field_lna,
&field_vga,
@@ -401,7 +430,14 @@ ReconView::ReconView(NavigationView& nav)
&button_dir,
&button_restart,
&button_mic_app,
&button_remove});
&button_remove,
record_view.get()});
record_view->hidden(true);
record_view->set_filename_date_frequency(true);
record_view->on_error = [&nav](std::string message) {
nav.display_modal("Error", message);
};
def_step = 0;
// HELPER: Pre-setting a manual range, based on stored frequency
@@ -426,6 +462,7 @@ ReconView::ReconView(NavigationView& nav)
load_ranges = persistent_memory::recon_load_ranges();
load_hamradios = persistent_memory::recon_load_hamradios();
update_ranges = persistent_memory::recon_update_ranges_when_recon();
auto_record_locked = persistent_memory::recon_auto_record_locked();
button_manual_start.on_select = [this, &nav](ButtonWithEncoder& button) {
clear_freqlist_for_ui_action();
@@ -530,8 +567,12 @@ ReconView::ReconView(NavigationView& nav)
};
button_audio_app.on_select = [this](Button&) {
nav_.pop();
nav_.push<AnalogAudioView>();
auto settings = receiver_model.settings();
settings.frequency_step = step_mode.selected_index_value();
if (field_mode.selected_index_value() == SPEC_MODULATION)
nav_.replace<CaptureAppView>();
else
nav_.replace<AnalogAudioView>(settings);
};
button_loop_config.on_select = [this](Button&) {
@@ -549,23 +590,24 @@ ReconView::ReconView(NavigationView& nav)
// TODO: *BUG* Both transmitter_model and receiver_model share the same pmem setting for target_frequency.
button_mic_app.on_select = [this](Button&) {
if (frequency_list.size() > 0 && current_index >= 0 && (unsigned)current_index < frequency_list.size()) {
if (frequency_list[current_index].type == HAMRADIO) {
// if it's a HAMRADIO entry, then frequency_a is the freq at which the repeater reveive, so we have to set it in transmit in mic app
transmitter_model.set_target_frequency(frequency_list[current_index].frequency_a);
// if it's a HAMRADIO entry, then frequency_b is the freq at which the repeater transmit, so we have to set it in receive in mic app
receiver_model.set_target_frequency(frequency_list[current_index].frequency_b);
if (current_entry().type == freqman_type::HamRadio) {
// if it's a HamRadio entry, then frequency_a is the freq at which the repeater receives, so we have to set it in transmit in mic app
transmitter_model.set_target_frequency(current_entry().frequency_a);
// if it's a HamRadio entry, then frequency_b is the freq at which the repeater transmits, so we have to set it in receive in mic app
receiver_model.set_target_frequency(current_entry().frequency_b);
} else {
// it's single or range so we us actual tuned frequency
transmitter_model.set_target_frequency(freq);
receiver_model.set_target_frequency(freq);
}
}
// there is no way yet to set modulation and bandwidth from Recon to MicApp
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_remove.on_select = [this](ButtonWithEncoder&) {
// TODO: Use FreqmanDB
if (frequency_list.size() > 0) {
if (!manual_mode) {
// scanner or recon (!scanner) mode
@@ -578,23 +620,8 @@ ReconView::ReconView(NavigationView& nav)
current_index = frequency_list.size() - 1;
}
if (frequency_list.size() > 0) {
if (frequency_list[current_index].description.size() > 0) {
switch (frequency_list[current_index].type) {
case RANGE:
desc_cycle.set("R: " + frequency_list[current_index].description);
break;
case HAMRADIO:
desc_cycle.set("H: " + frequency_list[current_index].description);
break;
default:
case SINGLE:
desc_cycle.set("S: " + frequency_list[current_index].description);
break;
}
} else {
desc_cycle.set("...no description...");
}
text_cycle.set_text(to_string_dec_uint(current_index + 1, 3));
update_description();
}
// also remove from output file if in scanner mode
if (scanner_mode) {
@@ -603,8 +630,7 @@ ReconView::ReconView(NavigationView& nav)
auto result = freqman_file.create(freq_file_path);
if (!result.is_valid()) {
for (size_t n = 0; n < frequency_list.size(); n++) {
std::string line;
get_freq_string(frequency_list[n], line);
auto line = to_freqman_string(*frequency_list[n]);
freqman_file.write_line(line);
}
}
@@ -614,16 +640,15 @@ ReconView::ReconView(NavigationView& nav)
File recon_file{};
File tmp_recon_file{};
std::string tmp_freq_file_path{freq_file_path + ".TMP"};
std::string frequency_to_add{};
freqman_entry entry = frequency_list[current_index];
freqman_entry entry = current_entry();
entry.frequency_a = freq;
entry.frequency_b = 0;
entry.modulation = last_entry.modulation;
entry.bandwidth = last_entry.bandwidth;
entry.type = SINGLE;
entry.type = freqman_type::Single;
get_freq_string(entry, frequency_to_add);
auto frequency_to_add = to_freqman_string(entry);
delete_file(tmp_freq_file_path);
auto result = tmp_recon_file.create(tmp_freq_file_path); // First recon if freq is already in txt
@@ -656,7 +681,7 @@ ReconView::ReconView(NavigationView& nav)
}
}
}
receiver_model.set_target_frequency(frequency_list[current_index].frequency_a); // retune
receiver_model.set_target_frequency(current_entry().frequency_a); // retune
}
if (frequency_list.size() == 0) {
text_cycle.set_text(" ");
@@ -682,27 +707,25 @@ ReconView::ReconView(NavigationView& nav)
} else if (frequency_range.min > frequency_range.max) {
nav_.display_modal("Error", "END freq\nis lower than START");
} else {
audio::output::stop();
if (field_mode.selected_index_value() != SPEC_MODULATION)
audio::output::stop();
// clear and shrink_to_fit are not enough to really start with a new, clean, empty vector
// swap is the only way to achieve a perfect memory liberation
std::vector<freqman_entry>().swap(frequency_list);
// Clear doesn't actually free, re-assign so destructor runs on previous instance.
frequency_list = freqman_db{};
current_index = 0;
frequency_list.push_back(std::make_unique<freqman_entry>());
freqman_entry manual_freq_entry;
def_step = step_mode.selected_index(); // max range val
manual_freq_entry.type = RANGE;
manual_freq_entry.description =
to_string_short_freq(frequency_range.min).erase(0, 1) + ">" + to_string_short_freq(frequency_range.max).erase(0, 1) + " S:" // current Manual range
+ freqman_entry_get_step_string_short(def_step); // euquiq: lame kludge to reduce spacing in step freq
manual_freq_entry.frequency_a = frequency_range.min; // min range val
manual_freq_entry.frequency_b = frequency_range.max; // max range val
manual_freq_entry.modulation = -1;
manual_freq_entry.bandwidth = -1;
manual_freq_entry.step = def_step;
frequency_list.push_back(manual_freq_entry);
def_step = step_mode.selected_index();
current_entry().type = freqman_type::Range;
current_entry().description =
to_string_short_freq(frequency_range.min).erase(0, 1) + ">" + // euquiq: lame kludge to reduce spacing in step freq
to_string_short_freq(frequency_range.max).erase(0, 1) + " S:" +
freqman_entry_get_step_string_short(def_step);
current_entry().frequency_a = frequency_range.min;
current_entry().frequency_b = frequency_range.max;
current_entry().modulation = freqman_invalid_index;
current_entry().bandwidth = freqman_invalid_index;
current_entry().step = def_step;
big_display.set_style(&Styles::white); // Back to white color
@@ -712,18 +735,17 @@ ReconView::ReconView(NavigationView& nav)
text_cycle.set_text("1");
text_max.set("/1");
button_scanner_mode.set_style(&Styles::white);
button_scanner_mode.set_text("MSEARCH");
button_scanner_mode.set_text("MANUAL");
file_name.set_style(&Styles::white);
file_name.set("MANUAL RANGE RECON");
desc_cycle.set_style(&Styles::white);
last_entry.modulation = -1;
last_entry.bandwidth = -1;
last_entry.step = -1;
last_entry.modulation = freqman_invalid_index;
last_entry.bandwidth = freqman_invalid_index;
last_entry.step = freqman_invalid_index;
last_index = -1;
current_index = 0;
freq = manual_freq_entry.frequency_a;
freq = current_entry().frequency_a;
handle_retune();
recon_redraw();
recon_resume();
@@ -757,15 +779,18 @@ ReconView::ReconView(NavigationView& nav)
if (scanner_mode) {
file_name.set_style(&Styles::red);
button_scanner_mode.set_style(&Styles::red);
button_scanner_mode.set_text("SCANNER");
button_scanner_mode.set_text("SCAN");
} else {
file_name.set_style(&Styles::blue);
button_scanner_mode.set_style(&Styles::blue);
button_scanner_mode.set_text("RECON");
}
if (frequency_list.size() > FREQMAN_MAX_PER_FILE) {
file_name.set_style(&Styles::yellow);
}
};
button_add.on_select = [this](ButtonWithEncoder&) { // frequency_list[current_index]
button_add.on_select = [this](ButtonWithEncoder&) {
if (!scanner_mode) {
recon_save_freq(freq_file_path, current_index, true);
}
@@ -786,7 +811,7 @@ ReconView::ReconView(NavigationView& nav)
} else {
scanner_mode = true;
button_scanner_mode.set_style(&Styles::red);
button_scanner_mode.set_text("SCANNER");
button_scanner_mode.set_text("SCAN");
button_scanner_mode.set_text("REMOVE");
}
frequency_file_load(true);
@@ -798,14 +823,16 @@ ReconView::ReconView(NavigationView& nav)
};
button_config.on_select = [this, &nav](Button&) {
if (is_recording) // disabling config while recording
return;
clear_freqlist_for_ui_action();
freq_lock = 0;
timer = 0;
auto open_view = nav.push<ReconSetupView>(input_file, output_file);
open_view->on_changed = [this](std::vector<std::string> result) {
freqlist_cleared_for_ui_action = false;
input_file = result[0];
output_file = result[1];
freq_file_path = "/FREQMAN/" + output_file + ".TXT";
freq_file_path = get_freqman_path(output_file).string();
recon_save_config_to_sd();
autosave = persistent_memory::recon_autosave_freqs();
@@ -815,8 +842,10 @@ ReconView::ReconView(NavigationView& nav)
load_ranges = persistent_memory::recon_load_ranges();
load_hamradios = persistent_memory::recon_load_hamradios();
update_ranges = persistent_memory::recon_update_ranges_when_recon();
auto_record_locked = persistent_memory::recon_auto_record_locked();
frequency_file_load(false);
freqlist_cleared_for_ui_action = false;
if (autostart) {
recon_resume();
@@ -833,6 +862,9 @@ ReconView::ReconView(NavigationView& nav)
field_wait.on_change = [this](int32_t v) {
wait = v;
// replacing -100 by 200 else it's freezing the device
if (wait == -100)
wait = -200;
colorize_waits();
};
@@ -859,7 +891,7 @@ ReconView::ReconView(NavigationView& nav)
// Loading input and output file from settings
recon_load_config_from_sd();
freq_file_path = "/FREQMAN/" + output_file + ".TXT";
freq_file_path = get_freqman_path(output_file).string();
field_recon_match_mode.set_selected_index(recon_match_mode);
field_squelch.set_value(squelch);
@@ -891,113 +923,100 @@ ReconView::ReconView(NavigationView& nav)
void ReconView::frequency_file_load(bool stop_all_before) {
(void)(stop_all_before);
audio::output::stop();
if (field_mode.selected_index_value() != SPEC_MODULATION)
audio::output::stop();
def_step = step_mode.selected_index(); // use def_step from manual selector
// clear and shrink_to_fit are not enough to really start with a new, clean, empty vector
// swap is the only way to achieve a perfect memory liberation
std::vector<freqman_entry>().swap(frequency_list); // clear the existing frequency list (expected behavior)
std::string file_input = input_file; // default recon mode
std::string file_input = input_file; // default recon mode
if (scanner_mode) {
file_input = output_file;
file_name.set_style(&Styles::red);
button_scanner_mode.set_style(&Styles::red);
button_scanner_mode.set_text("SCANNER");
desc_cycle.set_style(&Styles::red);
button_scanner_mode.set_text("SCAN");
} else {
file_name.set_style(&Styles::blue);
button_scanner_mode.set_style(&Styles::blue);
desc_cycle.set_style(&Styles::blue);
button_scanner_mode.set_text("RECON");
}
desc_cycle.set_style(&Styles::white);
if (!load_freqman_file_ex(file_input, frequency_list, load_freqs, load_ranges, load_hamradios, RECON_FREQMAN_MAX_PER_FILE)) {
freqman_load_options options{
.load_freqs = load_freqs,
.load_ranges = load_ranges,
.load_hamradios = load_hamradios};
if (!load_freqman_file(file_input, frequency_list, options)) {
file_name.set_style(&Styles::red);
desc_cycle.set_style(&Styles::red);
desc_cycle.set(" NO " + file_input + ".TXT FILE ...");
file_name.set("=> NO DATA");
} else {
file_name.set(file_input + "=>" + output_file);
if (frequency_list.size() == 0) {
file_name.set_style(&Styles::red);
desc_cycle.set_style(&Styles::red);
desc_cycle.set("/0 no entries in list");
file_name.set("BadOrEmpty " + file_input);
} else {
if (frequency_list.size() > RECON_FREQMAN_MAX_PER_FILE) {
if (frequency_list.size() > FREQMAN_MAX_PER_FILE) {
file_name.set_style(&Styles::yellow);
desc_cycle.set_style(&Styles::yellow);
}
}
}
if (frequency_list[0].step >= 0)
step = freqman_entry_get_step_value(frequency_list[0].step);
else
step = freqman_entry_get_step_value(def_step);
switch (frequency_list[0].type) {
case SINGLE:
freq = frequency_list[0].frequency_a;
break;
case RANGE:
minfreq = frequency_list[0].frequency_a;
maxfreq = frequency_list[0].frequency_b;
if (fwd) {
freq = minfreq;
} else {
freq = maxfreq;
}
if (frequency_list[0].step >= 0)
step = freqman_entry_get_step_value(frequency_list[0].step);
break;
case HAMRADIO:
minfreq = frequency_list[0].frequency_a;
maxfreq = frequency_list[0].frequency_b;
if (fwd) {
freq = minfreq;
} else {
freq = maxfreq;
}
break;
default:
break;
if (frequency_list.empty()) {
text_cycle.set_text(" ");
return; // Can't really do much.
}
reset_indexes();
step = freqman_entry_get_step_value(
is_valid(current_entry().step) ? current_entry().step : def_step);
if (current_entry().type == freqman_type::Single) {
freq = current_entry().frequency_a;
} else if (current_entry().type != freqman_type::Unknown) {
minfreq = current_entry().frequency_a;
maxfreq = current_entry().frequency_b;
freq = fwd ? minfreq : maxfreq;
}
step_mode.set_selected_index(def_step); // Impose the default step into the manual step selector
receiver_model.enable();
receiver_model.set_squelch_level(0);
if (frequency_list.size() != 0) {
switch (frequency_list[current_index].type) {
case RANGE:
description = "R: " + frequency_list[current_index].description;
break;
case HAMRADIO:
description = "H: " + frequency_list[current_index].description;
break;
default:
case SINGLE:
description = "S: " + frequency_list[current_index].description;
break;
text_cycle.set_text(to_string_dec_uint(current_index + 1, 3));
if (update_ranges && !manual_mode) {
button_manual_start.set_text(to_string_short_freq(current_entry().frequency_a));
frequency_range.min = current_entry().frequency_a;
if (current_entry().frequency_b != 0) {
button_manual_end.set_text(to_string_short_freq(current_entry().frequency_b));
frequency_range.max = current_entry().frequency_b;
} else {
button_manual_end.set_text(to_string_short_freq(current_entry().frequency_a));
frequency_range.max = current_entry().frequency_a;
}
text_cycle.set_text(to_string_dec_uint(current_index + 1, 3));
if (update_ranges && !manual_mode) {
button_manual_start.set_text(to_string_short_freq(frequency_list[current_index].frequency_a));
frequency_range.min = frequency_list[current_index].frequency_a;
if (frequency_list[current_index].frequency_b != 0) {
button_manual_end.set_text(to_string_short_freq(frequency_list[current_index].frequency_b));
frequency_range.max = frequency_list[current_index].frequency_b;
} else {
button_manual_end.set_text(to_string_short_freq(frequency_list[current_index].frequency_a));
frequency_range.max = frequency_list[current_index].frequency_a;
}
}
} else {
text_cycle.set_text(" ");
}
desc_cycle.set(description);
update_description();
handle_retune();
}
void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
systime_t time_interval = 100;
uint32_t local_recon_lock_duration = recon_lock_duration;
chrono_end = chTimeNow();
if (field_mode.selected_index_value() == SPEC_MODULATION) {
time_interval = chrono_end - chrono_start;
// capping here to avoid double check a frequency because time_interval is more than exactly 100 by a few units
// this is because we are making a measurement and not using a fixed value
if (time_interval > 100)
time_interval = 100;
if (field_lock_wait.value() == 0) {
if (time_interval <= 1) // capping here to avoid freeze because too quick
local_recon_lock_duration = 2; // minimum working tested value
else
local_recon_lock_duration = time_interval;
}
}
chrono_start = chrono_end;
// hack to reload the list if it was cleared by going into CONFIG
if (freqlist_cleared_for_ui_action) {
if (!manual_mode) {
@@ -1016,7 +1035,7 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
status = 0;
continuous_lock = false;
freq_lock = 0;
timer = recon_lock_duration;
timer = local_recon_lock_duration;
big_display.set_style(&Styles::white);
}
if (freq_lock < recon_lock_nb_match) // LOCKING
@@ -1024,7 +1043,9 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
if (status != 1) {
status = 1;
if (wait != 0) {
audio::output::stop();
recon_stop_recording();
if (field_mode.selected_index_value() != SPEC_MODULATION)
audio::output::stop();
}
}
if (db > squelch) // MATCHING LEVEL
@@ -1046,8 +1067,28 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
if (status != 2) {
continuous_lock = false;
status = 2;
// FREQ IS STRONG: GREEN and recon will pause when on_statistics_update()
if ((!scanner_mode) && autosave && frequency_list.size() > 0) {
recon_save_freq(freq_file_path, current_index, false);
}
if (wait != 0) {
audio_output_start();
if (field_mode.selected_index_value() != SPEC_MODULATION)
audio_output_start();
// contents of a possible recon_start_recording(), but not yet since it's only called once
if (auto_record_locked && !is_recording) {
button_audio_app.set_style(&Styles::red);
if (field_mode.selected_index_value() == SPEC_MODULATION)
button_audio_app.set_text("RAW REC");
else
button_audio_app.set_text("WAV REC");
record_view->start();
button_config.set_style(&Styles::light_grey); // disable config while recording as it's causing an IO error pop up at exit
is_recording = true;
}
// FREQ IS STRONG: GREEN and recon will pause when on_statistics_update()
if ((!scanner_mode) && autosave && frequency_list.size() > 0) {
recon_save_freq(freq_file_path, current_index, false);
}
}
if (wait >= 0) {
timer = wait;
@@ -1066,8 +1107,9 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
text_timer.set("TIMER: " + to_string_dec_int(timer));
}
if (timer) {
if (!continuous_lock || recon_match_mode == RECON_MATCH_SPARSE)
timer -= STATS_UPDATE_INTERVAL;
if (!continuous_lock || recon_match_mode == RECON_MATCH_SPARSE) {
timer -= time_interval;
}
if (timer < 0) {
timer = 0;
}
@@ -1078,11 +1120,10 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
if (frequency_list.size() > 0) {
has_looped = false;
entry_has_changed = false;
if (recon || stepper != 0 || index_stepper != 0) {
if (index_stepper == 0) {
/* we are doing a range */
if (frequency_list[current_index].type == RANGE) {
if (current_entry().type == freqman_type::Range) {
if ((fwd && stepper == 0) || stepper > 0) {
// forward
freq += step;
@@ -1112,7 +1153,7 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
}
}
}
} else if (frequency_list[current_index].type == SINGLE) {
} else if (current_entry().type == freqman_type::Single) {
if ((fwd && stepper == 0) || stepper > 0) { // forward
current_index++;
entry_has_changed = true;
@@ -1131,7 +1172,7 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
current_index = frequency_list.size() - 1;
}
}
} else if (frequency_list[current_index].type == HAMRADIO) {
} else if (current_entry().type == freqman_type::HamRadio) {
if ((fwd && stepper == 0) || stepper > 0) { // forward
if ((minfreq != maxfreq) && freq == minfreq) {
freq = maxfreq;
@@ -1178,28 +1219,29 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
entry_has_changed = true;
if (!recon) // for some motive, audio output gets stopped.
// for some motive, audio output gets stopped.
if (!recon && field_mode.selected_index_value() != SPEC_MODULATION)
audio_output_start();
}
// reload entry if changed
if (entry_has_changed) {
timer = 0;
switch (frequency_list[current_index].type) {
case SINGLE:
freq = frequency_list[current_index].frequency_a;
switch (current_entry().type) {
case freqman_type::Single:
freq = current_entry().frequency_a;
break;
case RANGE:
minfreq = frequency_list[current_index].frequency_a;
maxfreq = frequency_list[current_index].frequency_b;
case freqman_type::Range:
minfreq = current_entry().frequency_a;
maxfreq = current_entry().frequency_b;
if ((fwd && !stepper && !index_stepper) || stepper > 0 || index_stepper > 0) {
freq = minfreq;
} else if ((!fwd && !stepper && !index_stepper) || stepper < 0 || index_stepper < 0) {
freq = maxfreq;
}
break;
case HAMRADIO:
minfreq = frequency_list[current_index].frequency_a;
maxfreq = frequency_list[current_index].frequency_b;
case freqman_type::HamRadio:
minfreq = current_entry().frequency_a;
maxfreq = current_entry().frequency_b;
if ((fwd && !stepper && !index_stepper) || stepper > 0 || index_stepper > 0) {
freq = minfreq;
} else if ((!fwd && !stepper && !index_stepper) || stepper < 0 || index_stepper < 0) {
@@ -1230,7 +1272,8 @@ void ReconView::recon_pause() {
continuous_lock = false;
recon = false;
audio_output_start();
if (field_mode.selected_index_value() != SPEC_MODULATION)
audio_output_start();
big_display.set_style(&Styles::white);
button_pause.set_text("<RESUME>"); // PAUSED, show resume
@@ -1242,7 +1285,8 @@ void ReconView::recon_resume() {
continuous_lock = false;
recon = true;
audio::output::stop();
if (field_mode.selected_index_value() != SPEC_MODULATION)
audio::output::stop();
big_display.set_style(&Styles::white);
button_pause.set_text("<PAUSE>");
@@ -1283,10 +1327,28 @@ void ReconView::on_stepper_delta(int32_t v) {
size_t ReconView::change_mode(freqman_index_t new_mod) {
field_mode.on_change = [this](size_t, OptionsField::value_t) {};
field_bw.on_change = [this](size_t, OptionsField::value_t) {};
recon_stop_recording();
remove_child(record_view.get());
record_view.reset();
if (new_mod == SPEC_MODULATION) {
audio::output::stop();
record_view = std::make_unique<RecordView>(Rect{0, 0, 30 * 8, 1 * 16},
u"AUTO_RAW_", u"CAPTURES",
RecordView::FileType::RawS16, 16384, 3);
} else {
record_view = std::make_unique<RecordView>(Rect{0, 0, 30 * 8, 1 * 16},
u"AUTO_AUDIO_", u"AUDIO",
RecordView::FileType::WAV, 4096, 4);
}
add_child(record_view.get());
record_view->hidden(true);
record_view->set_filename_date_frequency(true);
record_view->on_error = [this](std::string message) {
nav_.display_modal("Error", message);
};
receiver_model.disable();
baseband::shutdown();
size_t recording_sampling_rate = 0;
switch (new_mod) {
case AM_MODULATION:
freqman_set_bandwidth_option(new_mod, field_bw);
@@ -1297,6 +1359,7 @@ size_t ReconView::change_mode(freqman_index_t new_mod) {
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); };
text_ctcss.set(" ");
recording_sampling_rate = 12000;
break;
case NFM_MODULATION:
freqman_set_bandwidth_option(new_mod, field_bw);
@@ -1306,6 +1369,7 @@ size_t ReconView::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); };
recording_sampling_rate = 24000;
break;
case WFM_MODULATION:
freqman_set_bandwidth_option(new_mod, field_bw);
@@ -1316,18 +1380,43 @@ size_t ReconView::change_mode(freqman_index_t new_mod) {
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); };
text_ctcss.set(" ");
recording_sampling_rate = 48000;
break;
case SPEC_MODULATION:
freqman_set_bandwidth_option(new_mod, field_bw);
// bw 200k (0) default
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);
record_view->set_sampling_rate(sampling_rate);
receiver_model.set_sampling_rate(sampling_rate);
receiver_model.set_baseband_bandwidth(anti_alias_baseband_bandwidth_filter);
};
text_ctcss.set(" ");
break;
default:
break;
}
if (new_mod != SPEC_MODULATION) {
button_audio_app.set_text("AUDIO");
record_view->set_sampling_rate(recording_sampling_rate);
// 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);
} else {
button_audio_app.set_text("RAW");
}
field_mode.set_selected_index(new_mod);
field_mode.on_change = [this](size_t, OptionsField::value_t v) {
if (v != -1) {
change_mode(v);
}
};
if (!recon) // for some motive, audio output gets stopped.
// for some motive, audio output gets stopped.
if (!recon && field_mode.selected_index_value() != SPEC_MODULATION)
audio::output::start(); // so if recon was stopped we resume audio
receiver_model.enable();
@@ -1335,32 +1424,10 @@ size_t ReconView::change_mode(freqman_index_t new_mod) {
}
void ReconView::handle_coded_squelch(const uint32_t value) {
float diff{0.0};
float min_diff{(float)value};
size_t min_idx{0};
size_t c{0};
if (field_mode.selected_index() != NFM_MODULATION) {
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(" ");
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_squelch_index < 0 || (unsigned)last_squelch_index != min_idx) {
last_squelch_index = min_idx;
if (min_diff < 40)
text_ctcss.set("T: " + tone_keys[min_idx].first);
else
text_ctcss.set(" ");
}
}
} /* namespace ui */
+17 -9
View File
@@ -45,7 +45,6 @@
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
class ReconView : public View {
public:
@@ -68,6 +67,7 @@ class ReconView : public View {
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);
@@ -85,6 +85,12 @@ class ReconView : public View {
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);
// 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();
jammer::jammer_range_t frequency_range{false, 0, MAX_UFREQ}; // perfect for manual recon task too...
int32_t squelch{0};
@@ -109,6 +115,8 @@ class ReconView : public View {
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{3};
uint32_t recon_lock_duration{RECON_MIN_LOCK_DURATION};
uint32_t recon_match_mode{RECON_MATCH_CONTINUOUS};
@@ -119,10 +127,7 @@ class ReconView : public View {
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};
uint32_t freq_lock{0};
@@ -139,16 +144,19 @@ class ReconView : public View {
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{};
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()},
{{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()}};
{{0 * 8, (26 * 8) + 4}, "MODE: , SQUELCH: ", Color::light_grey()}};
LNAGainField field_lna{
{4 * 8, 0 * 16}};
@@ -266,18 +274,18 @@ class ReconView : public View {
NumberField field_lock_wait{
{25 * 8, 24 * 8 + 4},
4,
{RECON_MIN_LOCK_DURATION, RECON_MAX_LOCK_DURATION},
{0, RECON_MAX_LOCK_DURATION},
STATS_UPDATE_INTERVAL,
' ',
};
OptionsField field_mode{
{6 * 8, (26 * 8) + 4},
3,
4,
{}};
OptionsField field_bw{
{10 * 8, (26 * 8) + 4},
{11 * 8, (26 * 8) + 4},
6,
{}};
@@ -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"
@@ -55,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 {
@@ -103,6 +102,7 @@ void ReconSetupViewMore::save() {
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());
persistent_memory::set_recon_auto_record_locked(checkbox_auto_record_locked.value());
};
void ReconSetupViewMain::focus() {
@@ -117,12 +117,14 @@ ReconSetupViewMore::ReconSetupViewMore(NavigationView& nav, Rect parent_rect)
add_children({&checkbox_load_freqs,
&checkbox_load_ranges,
&checkbox_load_hamradios,
&checkbox_update_ranges_when_recon});
&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());
checkbox_auto_record_locked.set_value(persistent_memory::recon_auto_record_locked());
};
void ReconSetupViewMore::focus() {
@@ -132,6 +132,10 @@ class ReconSetupViewMore : public View {
{1 * 8, 102},
3,
"auto update m-ranges"};
Checkbox checkbox_auto_record_locked{
{1 * 8, 132},
3,
"record locked periods"};
};
class ReconSetupView : public View {
+72 -64
View File
@@ -314,17 +314,13 @@ ScannerView::ScannerView(
// Button to load txt files from the FREQMAN folder
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.stem().string(), true);
} 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.");
}
};
};
@@ -392,16 +388,17 @@ 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
@@ -447,6 +444,16 @@ ScannerView::ScannerView(
// 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);
@@ -483,6 +490,7 @@ ScannerView::ScannerView(
bigdisplay_update(BDC_GREY); // Back to grey color
};
// TODO: remove this parsing?
// Button to add current frequency (found during Search) to the Scan Frequency List
button_add.on_select = [this](Button&) {
File scanner_file;
@@ -565,9 +573,9 @@ ScannerView::ScannerView(
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};
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) {
@@ -576,56 +584,56 @@ void ScannerView::frequency_file_load(std::string file_name, bool stop_all_befor
description_list.clear();
}
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;
if (load_freqman_file(file_name, database, {})) {
loaded_file_name = file_name;
for (auto& entry_ptr : database) {
if (frequency_list.size() >= FREQMAN_MAX_PER_FILE)
break;
if (def_bw_index == -1)
def_bw_index = entry.bandwidth;
auto& entry = *entry_ptr;
if (def_step_index == -1)
def_step_index = entry.step;
// 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 (is_invalid(def_mod_index))
def_mod_index = entry.modulation;
// 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_bw_index))
def_bw_index = entry.bandwidth;
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);
}
if (is_invalid(def_step_index))
def_step_index = entry.step;
// Get frequency
if (entry.type == freqman_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 == freqman_type::Single) {
found_single = true;
frequency_list.push_back(entry.frequency_a);
description_list.push_back(entry.description);
} else if (entry.type == freqman_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 ((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 !
}
}
} else {
@@ -639,13 +647,13 @@ void ScannerView::frequency_file_load(std::string file_name, bool stop_all_befor
desc_freq_list_scan = desc_freq_list_scan + "...";
}
if ((def_mod_index != -1) && (def_mod_index != (freqman_index_t)field_mode.selected_index_value()))
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); // 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)) // Update BW if specified in file
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)) // Update step if specified in file
field_step.set_selected_index(def_step_index);
audio::output::stop();
+1 -1
View File
@@ -90,7 +90,7 @@ class ScannerView : public View {
std::string title() const override { return "Scanner"; };
std::vector<rf::Frequency> frequency_list{};
std::vector<string> description_list{};
std::vector<std::string> description_list{};
// void set_parent_rect(const Rect new_parent_rect) override;
+17 -16
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");
}
@@ -378,14 +368,26 @@ SearchView::SearchView(
field_frequency_min.set_value(receiver_model.target_frequency() - 1000000);
field_frequency_min.set_step(100000);
field_frequency_min.updated = [this](rf::Frequency) {
on_range_changed();
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.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.target_frequency() + 1000000);
field_frequency_max.set_step(100000);
field_frequency_max.updated = [this](rf::Frequency) {
on_range_changed();
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.target_frequency() + 1000000);
new_view->on_changed = [this](rf::Frequency f) {
this->field_frequency_max.set_value(f);
};
};
field_lna.set_value(receiver_model.lna());
@@ -402,7 +404,6 @@ SearchView::SearchView(
on_range_changed();
receiver_model.set_modulation(ReceiverModel::Mode::SpectrumAnalysis);
receiver_model.enable();
}
+6 -11
View File
@@ -26,7 +26,6 @@
#include "spectrum_color_lut.hpp"
#include "ui_receiver.hpp"
#include "ui_freq_field.hpp"
#include "ui_styles.hpp"
#include "recent_entries.hpp"
@@ -93,8 +92,8 @@ class SearchView : public View {
NavigationView& nav_;
RxRadioState radio_state_{
2500000 /* bandwidth */,
SEARCH_SLICE_WIDTH /* sampling rate */
};
SEARCH_SLICE_WIDTH /* sampling rate */,
ReceiverModel::Mode::SpectrumAnalysis};
struct slice_t {
rf::Frequency center_frequency;
@@ -147,14 +146,10 @@ class SearchView : public View {
{{1 * 8, 25 * 8}, "Accuracy +/-4.9kHz", Color::light_grey()},
{{26 * 8, 25 * 8}, "MHz", Color::light_grey()}};
RxFrequencyField field_frequency_min{
{1 * 8, 1 * 16},
nav_,
false};
RxFrequencyField field_frequency_max{
{11 * 8, 1 * 16},
nav_,
false};
FrequencyField field_frequency_min{
{1 * 8, 1 * 16}};
FrequencyField field_frequency_max{
{11 * 8, 1 * 16}};
LNAGainField field_lna{
{22 * 8, 1 * 16}};
VGAGainField field_vga{
+147 -116
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,6 +245,8 @@ SetFrequencyCorrectionModel SetRadioView::form_collect() {
};
}
/* SetUIView *********************************************/
SetUIView::SetUIView(NavigationView& nav) {
add_children({&checkbox_disable_touchscreen,
&checkbox_bloff,
@@ -241,39 +255,76 @@ SetUIView::SetUIView(NavigationView& nav) {
&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_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);
}
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());
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();
nav.pop();
};
button_cancel.on_select = [&nav, this](Button&) {
@@ -285,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&) {
@@ -311,9 +361,8 @@ void SetAppSettingsView::focus() {
button_save.focus();
}
// ---------------------------------------------------------
// Converter Settings
// ---------------------------------------------------------
/* SetConverterSettingsView ******************************/
SetConverterSettingsView::SetConverterSettingsView(NavigationView& nav) {
add_children({&check_show_converter,
&check_converter,
@@ -321,46 +370,43 @@ 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_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_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_target_frequency(receiver_model.target_frequency());
button_converter_freq.set_text("<" + to_string_short_freq(f) + " MHz>");
@@ -376,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,
@@ -387,36 +432,36 @@ 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_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_target_frequency(receiver_model.target_frequency());
button_freq_tx_correction.set_text("<" + to_string_short_freq(f) + " MHz>");
@@ -432,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,
@@ -445,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();
}
};
@@ -497,7 +535,7 @@ SetPersistentMemoryView::SetPersistentMemoryView(NavigationView& nav) {
YESNO,
[this](bool choice) {
if (choice) {
portapack::persistent_memory::cache::defaults();
pmem::cache::defaults();
}
});
};
@@ -511,22 +549,18 @@ void SetPersistentMemoryView::focus() {
button_return.focus();
}
// ---------------------------------------------------------
// Audio Settings
// ---------------------------------------------------------
/* SetAudioView ******************************************/
SetAudioView::SetAudioView(NavigationView& nav) {
add_children({&labels,
&field_tone_mix,
&checkbox_speaker_disable,
&button_save,
&button_cancel});
field_tone_mix.set_value(persistent_memory::tone_mix());
checkbox_speaker_disable.set_value(persistent_memory::config_speaker_disable());
field_tone_mix.set_value(pmem::tone_mix());
button_save.on_select = [&nav, this](Button&) {
persistent_memory::set_tone_mix(field_tone_mix.value());
persistent_memory::set_config_speaker_disable(checkbox_speaker_disable.value());
pmem::set_tone_mix(field_tone_mix.value());
audio::output::update_audio_mute();
nav.pop();
};
@@ -540,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();
};
@@ -564,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();
};
@@ -589,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({
+51 -15
View File
@@ -27,6 +27,7 @@
#include "ui_widget.hpp"
#include "ui_menu.hpp"
#include "ui_navigation.hpp"
#include "bitmap.hpp"
#include "ff.h"
#include "portapack_persistent_memory.hpp"
@@ -34,7 +35,7 @@
namespace ui {
#define MAX_FREQ_CORRECTION 4294967295 // maximum possible for an uint32_t
#define MAX_FREQ_CORRECTION INT32_MAX
struct SetDateTimeModel {
uint16_t year;
@@ -208,16 +209,16 @@ class SetUIView : public View {
private:
Checkbox checkbox_disable_touchscreen{
{3 * 8, 2 * 16},
{3 * 8, 1 * 16},
20,
"Disable touchscreen"};
Checkbox checkbox_bloff{
{3 * 8, 4 * 16},
{3 * 8, 3 * 16},
20,
"Backlight off after:"};
OptionsField options_bloff{
{60, 5 * 16 + 8},
{60, 4 * 16 + 8},
20,
{
{"5 seconds", backlight_timeout_t::Timeout5Sec},
@@ -231,25 +232,65 @@ class SetUIView : public View {
}};
Checkbox checkbox_showsplash{
{3 * 8, 7 * 16},
{3 * 8, 6 * 16},
20,
"Show splash"};
Checkbox checkbox_showclock{
{3 * 8, 9 * 16},
{3 * 8, 8 * 16},
20,
"Show clock with:"};
OptionsField options_clockformat{
{60, 10 * 16 + 8},
{60, 9 * 16 + 8},
20,
{{"time only", 0},
{"time and date", 1}}};
Checkbox checkbox_guireturnflag{
{3 * 8, 12 * 16},
25,
"Show return icon in GUI"};
{3 * 8, 11 * 16},
20,
"Back button in menu"};
Labels labels{
{{3 * 8, 13 * 16}, "Show/Hide Status Icons", Color::light_grey()},
};
ImageToggle toggle_camera{
{7 * 8, 14 * 16 + 2, 16, 16},
&bitmap_icon_camera};
ImageToggle toggle_sleep{
{9 * 8, 14 * 16 + 2, 16, 16},
&bitmap_icon_sleep};
ImageToggle toggle_stealth{
{11 * 8, 14 * 16 + 2, 16, 16},
&bitmap_icon_stealth};
ImageToggle toggle_converter{
{13 * 8, 14 * 16 + 2, 16, 16},
&bitmap_icon_upconvert};
ImageToggle toggle_bias_tee{
{15 * 8, 14 * 16 + 2, 16, 16},
&bitmap_icon_biast_off};
ImageToggle toggle_clock{
{17 * 8, 14 * 16 + 2, 8, 16},
&bitmap_icon_clk_ext};
ImageToggle toggle_mute{
{18 * 8, 14 * 16 + 2, 16, 16},
&bitmap_icon_speaker_and_headphones_mute};
ImageToggle toggle_speaker{
{20 * 8, 14 * 16 + 2, 16, 16},
&bitmap_icon_speaker_mute};
ImageToggle toggle_sd_card{
{22 * 8, 14 * 16 + 2, 16, 16},
&bitmap_sd_card_ok};
Button button_save{
{2 * 8, 16 * 16, 12 * 8, 32},
@@ -388,11 +429,6 @@ class SetAudioView : public View {
1,
'0'};
Checkbox checkbox_speaker_disable{
{2 * 8, 6 * 16},
25,
"Disable AK speaker amp"};
Button button_save{
{2 * 8, 16 * 16, 12 * 8, 32},
"Save"};
@@ -57,6 +57,8 @@ SpectrumPainterView::SpectrumPainterView(
input_image.set_parent_rect(view_rect);
input_text.set_parent_rect(view_rect);
freqman_set_bandwidth_option(SPEC_MODULATION, option_bandwidth);
field_frequency.set_step(5000);
tx_gain = transmitter_model.tx_gain();
@@ -127,7 +127,7 @@ class SpectrumPainterView : public View {
OptionsField option_bandwidth{
{4 * 8, footer_location + 2 * 16},
5,
{BW_OPTIONS}};
{}};
NumberField field_duration{
{13 * 8, footer_location + 2 * 16},
+42 -8
View File
@@ -26,6 +26,8 @@ namespace fs = std::filesystem;
namespace ui {
const std::filesystem::path splash_dot_bmp{u"/splash.bmp"};
ScreenshotViewer::ScreenshotViewer(
NavigationView& nav,
const std::filesystem::path& path)
@@ -40,11 +42,9 @@ bool ScreenshotViewer::on_key(KeyEvent) {
}
void ScreenshotViewer::paint(Painter& painter) {
constexpr size_t pixel_width = 240;
constexpr size_t pixel_height = 320;
File file{};
painter.fill_rectangle({0, 0, pixel_width, pixel_height}, Color::black());
painter.fill_rectangle({0, 0, screen_width, screen_height}, Color::black());
auto show_invalid = [&]() {
painter.draw_string({10, 160}, Styles::white, "Not a valid screenshot.");
@@ -65,19 +65,19 @@ void ScreenshotViewer::paint(Painter& painter) {
constexpr size_t read_chunk = 80; // NB: must be a factor of pixel_width.
constexpr size_t buffer_size = sizeof(ColorRGB888) * read_chunk;
uint8_t buffer[buffer_size];
std::array<Color, pixel_width> pixel_data;
std::array<Color, screen_width> pixel_data;
// Seek past all the headers.
file.seek(43);
for (auto line = 0u; line < pixel_height; ++line) {
for (auto line = 0u; line < screen_height; ++line) {
// Seek past the per-line header.
file.seek(file.tell() + 6);
// Per comment in PNGWriter, read in chunks of 80.
// NB: Reading in one large chunk caused corruption so there's
// likely a bug lurking in the SD Card/FatFs layer.
for (auto offset = 0u; offset < pixel_width; offset += read_chunk) {
for (auto offset = 0u; offset < screen_width; offset += read_chunk) {
auto read = file.read(buffer, buffer_size);
if (!read || *read != buffer_size) {
@@ -92,8 +92,42 @@ void ScreenshotViewer::paint(Painter& painter) {
}
}
display.draw_pixels({0, (int)line, pixel_width, 1}, pixel_data);
display.draw_pixels({0, (int)line, screen_width, 1}, pixel_data);
}
}
} // namespace ui
SplashViewer::SplashViewer(
NavigationView& nav,
const std::filesystem::path& path)
: nav_{nav},
path_{path} {
valid_image = false;
set_focusable(true);
}
bool SplashViewer::on_key(const KeyEvent key) {
if (valid_image && key == KeyEvent::Right) {
delete_file(splash_dot_bmp);
copy_file(path_, splash_dot_bmp);
}
nav_.pop();
return true;
}
void SplashViewer::paint(Painter& painter) {
painter.fill_rectangle({0, 0, screen_width, screen_height}, Color::black());
if (!portapack::display.drawBMP2({0, 0}, path_)) {
painter.draw_string({10, 160}, Styles::white, "Not a valid splash image.");
return;
}
// Show option to set splash screen if it's not already the splash screen
if (!path_iequal(path_, splash_dot_bmp)) {
painter.draw_string({0, 0}, Styles::white, "*RIGHT BUTTON UPDATES SPLASH*");
valid_image = true;
}
}
} // namespace ui
@@ -31,6 +31,8 @@
namespace ui {
extern const std::filesystem::path splash_dot_bmp;
class ScreenshotViewer : public View {
public:
ScreenshotViewer(NavigationView& nav, const std::filesystem::path& path);
@@ -42,6 +44,19 @@ class ScreenshotViewer : public View {
std::filesystem::path path_{};
};
class SplashViewer : public View {
public:
SplashViewer(NavigationView& nav, const std::filesystem::path& path);
bool on_key(KeyEvent key) override;
void paint(Painter& painter) override;
void update_ss(void);
private:
NavigationView& nav_;
std::filesystem::path path_{};
bool valid_image{};
};
} // namespace ui
#endif // __UI_SS_VIEWER_H__
+43 -28
View File
@@ -42,10 +42,9 @@ namespace ui {
/* TextViewer *******************************************************/
TextViewer::TextViewer(Rect parent_rect)
: Widget(parent_rect),
max_line{static_cast<uint8_t>(parent_rect.height() / char_height)},
max_col{static_cast<uint8_t>(parent_rect.width() / char_width)} {
: Widget(parent_rect) {
set_focusable(true);
set_font_zoom(false);
}
void TextViewer::paint(Painter& painter) {
@@ -138,6 +137,16 @@ uint32_t TextViewer::offset() const {
return 0;
}
void TextViewer::cursor_home() {
cursor_.col = 0;
redraw();
}
void TextViewer::cursor_end() {
cursor_.col = line_length() - 1;
redraw();
}
uint16_t TextViewer::line_length() {
return file_->line_length(cursor_.line);
}
@@ -158,18 +167,14 @@ bool TextViewer::apply_scrolling_constraints(int16_t delta_line, int16_t delta_c
++new_line;
}
// Snap to first/last to make navigating easier.
if (new_line < 0 && new_col > 0) {
// Snap to first/last line to make navigating easier.
if (new_line < 0 && cursor_.line > 0) {
new_line = 0;
new_col = 0;
} else if (new_line >= (int32_t)file_->line_count()) {
auto last_line = file_->line_count() - 1;
int32_t last_col = file_->line_length(last_line) - 1;
if (new_col < last_col) {
if (cursor_.line < last_line)
new_line = last_line;
new_col = last_col;
}
}
if (new_line < 0 || (uint32_t)new_line >= file_->line_count())
@@ -177,7 +182,6 @@ bool TextViewer::apply_scrolling_constraints(int16_t delta_line, int16_t delta_c
new_line_length = file_->line_length(new_line);
// TODO: don't wrap with encoder?
// Wrap or clamp column.
if (new_line_length == 0)
new_col = 0;
@@ -207,7 +211,7 @@ void TextViewer::paint_text(Painter& painter, uint32_t line, uint16_t col) {
if (result && *result > 0)
painter.draw_string(
{0, r.top() + (int)i * char_height},
Styles::white_small, {buffer, *result});
style(), {buffer, *result});
// Clear empty line sections. This is less visually jarring than full clear.
int32_t clear_width = max_col - (result ? *result : 0);
@@ -216,7 +220,7 @@ void TextViewer::paint_text(Painter& painter, uint32_t line, uint16_t col) {
{(max_col - clear_width) * char_width,
r.top() + (int)i * char_height,
clear_width * char_width, char_height},
Styles::white_small.background);
style().background);
}
}
@@ -237,8 +241,8 @@ void TextViewer::paint_cursor(Painter& painter) {
};
// Clear old cursor. CONSIDER: XOR cursor?
draw_cursor(paint_state_.line, paint_state_.col, Styles::white_small.background);
draw_cursor(cursor_.line, cursor_.col, Styles::white_small.foreground);
draw_cursor(paint_state_.line, paint_state_.col, style().background);
draw_cursor(cursor_.line, cursor_.col, style().foreground);
paint_state_.line = cursor_.line;
paint_state_.col = cursor_.col;
}
@@ -252,6 +256,15 @@ void TextViewer::reset_file(FileWrapper* file) {
redraw(true);
}
void TextViewer::set_font_zoom(bool zoom) {
font_zoom = zoom;
font_style = font_zoom ? &Styles::white : &Styles::white_small;
char_height = style().font.line_height();
char_width = style().font.char_width();
max_line = (uint8_t)(parent_rect().height() / char_height);
max_col = (uint8_t)(parent_rect().width() / char_width);
}
/* TextEditorMenu ***************************************************/
TextEditorMenu::TextEditorMenu()
@@ -259,9 +272,9 @@ TextEditorMenu::TextEditorMenu()
add_children(
{
&rect_frame,
&button_cut,
&button_paste,
&button_copy,
&button_home,
&button_end,
&button_zoom,
&button_delline,
&button_edit,
&button_addline,
@@ -310,14 +323,20 @@ TextEditorView::TextEditorView(NavigationView& nav)
};
menu.hidden(true);
menu.on_cut() = [this]() {
show_nyi();
menu.on_home() = [this]() {
viewer.cursor_home();
hide_menu(true);
};
menu.on_paste() = [this]() {
show_nyi();
menu.on_end() = [this]() {
viewer.cursor_end();
hide_menu(true);
};
menu.on_copy() = [this]() {
show_nyi();
menu.on_zoom() = [this]() {
viewer.toggle_font_zoom();
refresh_ui();
hide_menu(true);
};
menu.on_delete_line() = [this]() {
@@ -490,10 +509,6 @@ void TextEditorView::show_edit_line() {
});
}
void TextEditorView::show_nyi() {
nav_.display_modal("Soon...", "Coming soon.");
}
void TextEditorView::show_save_prompt(std::function<void()> continuation) {
if (!file_dirty_) {
if (continuation)
+27 -22
View File
@@ -72,15 +72,23 @@ class TextViewer : public Widget {
uint32_t col() const { return cursor_.col; }
uint32_t offset() const;
void cursor_home();
void cursor_end();
// Gets the length of the current line.
uint16_t line_length();
private:
static constexpr int8_t char_width = 5;
static constexpr int8_t char_height = 8;
const Style& style() { return *font_style; }
void set_font_zoom(bool zoom);
void toggle_font_zoom() { set_font_zoom(!font_zoom); };
const uint8_t max_line = 32;
const uint8_t max_col = 48;
private:
bool font_zoom{};
const Style* font_style{};
int8_t char_width{};
int8_t char_height{};
uint8_t max_line{};
uint8_t max_col{};
/* Returns true if the cursor was updated. */
bool apply_scrolling_constraints(
@@ -120,14 +128,12 @@ class TextEditorMenu : public View {
void on_show() override;
void on_hide() override;
std::function<void()>& on_cut() { return button_cut.on_select; }
std::function<void()>& on_paste() { return button_paste.on_select; }
std::function<void()>& on_copy() { return button_copy.on_select; }
std::function<void()>& on_home() { return button_home.on_select; }
std::function<void()>& on_end() { return button_end.on_select; }
std::function<void()>& on_zoom() { return button_zoom.on_select; }
std::function<void()>& on_delete_line() { return button_delline.on_select; }
std::function<void()>& on_edit_line() { return button_edit.on_select; }
std::function<void()>& on_add_line() { return button_addline.on_select; }
std::function<void()>& on_open() { return button_open.on_select; }
std::function<void()>& on_save() { return button_save.on_select; }
std::function<void()>& on_exit() { return button_exit.on_select; }
@@ -139,23 +145,23 @@ class TextEditorMenu : public View {
{0 * 8, 0 * 8, 23 * 8, 23 * 8},
Color::dark_grey()};
NewButton button_cut{
NewButton button_home{
{1 * 8, 1 * 8, 7 * 8, 7 * 8},
"Cut",
&bitmap_icon_cut,
"Home",
&bitmap_arrow_left,
Color::dark_grey()};
NewButton button_paste{
NewButton button_end{
{8 * 8, 1 * 8, 7 * 8, 7 * 8},
"Paste",
&bitmap_icon_paste,
"End",
&bitmap_arrow_right,
Color::dark_grey()};
NewButton button_copy{
NewButton button_zoom{
{15 * 8, 1 * 8, 7 * 8, 7 * 8},
"Copy",
&bitmap_icon_copy,
Color::dark_grey()};
"Zoom",
&bitmap_icon_search,
Color::orange()};
NewButton button_delline{
{1 * 8, 8 * 8, 7 * 8, 7 * 8},
@@ -220,7 +226,6 @@ class TextEditorView : public View {
void hide_menu(bool hidden = true);
void show_file_picker(bool immediate = true);
void show_edit_line();
void show_nyi();
void show_save_prompt(std::function<void()> continuation);
void prepare_for_write();
@@ -258,4 +263,4 @@ class TextEditorView : public View {
} // namespace ui
#endif // __UI_TEXT_EDITOR_H__
#endif // __UI_TEXT_EDITOR_H__
+34 -46
View File
@@ -23,8 +23,10 @@
#include "ui_whipcalc.hpp"
#include "ch.h"
#include "portapack.hpp"
#include "convert.hpp"
#include "event_m0.hpp"
#include "file_reader.hpp"
#include "portapack.hpp"
#include <cstring>
@@ -113,28 +115,12 @@ WhipCalcView::WhipCalcView(NavigationView& nav)
&console,
&button_exit});
File antennas_file;
auto error = antennas_file.open("WHIPCALC/ANTENNAS.TXT");
if (!error.is_valid()) {
std::string line;
char one_char[1];
for (size_t pointer = 0; pointer < antennas_file.size(); pointer++) {
antennas_file.seek(pointer);
antennas_file.read(one_char, 1);
if ((int)one_char[0] >= ' ')
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
}
}
if (line.length() > 0)
txtline_process(line); // Last line had no newline at end ?
}
// Try loading antennas from file.
load_antenna_db();
if (!antenna_db.size())
add_default_antenna();
// antennas_on_memory.set(to_string_dec_int(antenna_db.size(),0) + " antennas"); //tell user
options_type.set_selected_index(2); // Quarter wave
@@ -151,40 +137,42 @@ WhipCalcView::WhipCalcView(NavigationView& nav)
nav.pop();
};
field_frequency.set_value(transmitter_model.target_frequency());
update_result();
}
void ui::WhipCalcView::txtline_process(std::string& line) {
if (line.find("#") != std::string::npos)
return; // Line is just a comment
void WhipCalcView::load_antenna_db() {
File antennas_file;
auto error = antennas_file.open("/WHIPCALC/ANTENNAS.TXT");
char separator = ',';
size_t previous = 0;
uint16_t value = 0;
antenna_entry new_antenna;
size_t current = line.find(separator);
if (error)
return;
while (current != std::string::npos) {
if (!previous)
new_antenna.label.assign(line, 0, current); // antenna label
else {
value = std::stoi(line.substr(previous, current - previous));
if (!value) return; // No element length? abort antenna
new_antenna.elements.push_back(value); // Store this new element
auto reader = FileLineReader(antennas_file);
for (const auto& line : reader) {
if (line.length() == 0 || line[0] == '#')
continue; // Empty or comment line.
auto cols = split_string(line, ',');
if (cols.size() < 2)
continue; // Line doesn't have enough columns.
antenna_entry new_antenna{
std::string{cols[0]}};
// Add antenna elements.
for (auto i = 1ul; i < cols.size(); ++i) {
uint16_t length = 0;
if (parse_int(cols[i], length)) {
new_antenna.elements.push_back(length);
}
}
previous = current + 1;
current = line.find(separator, previous); // Search for next space delimiter
if (!new_antenna.elements.empty())
antenna_db.push_back(std::move(new_antenna));
}
if (!previous) return; // Not even a label ? drop this antenna!
value = std::stoi(line.substr(previous, current - previous)); // Last element
if (!value) return;
new_antenna.elements.push_back(value);
antenna_db.push_back(new_antenna); // Add this antenna
}
void ui::WhipCalcView::add_default_antenna() {
void WhipCalcView::add_default_antenna() {
antenna_db.push_back({"ANT500", {185, 315, 450, 586, 724, 862}}); // store a default ant500
}
} // namespace ui
+2 -4
View File
@@ -51,8 +51,7 @@ class WhipCalcView : public View {
NavigationView& nav_;
std::vector<antenna_entry> antenna_db{};
void update_result();
uint16_t string_to_number(std::string);
void txtline_process(std::string&);
void load_antenna_db();
void add_default_antenna();
Labels labels{
@@ -63,8 +62,7 @@ class WhipCalcView : public View {
TxFrequencyField field_frequency{
{13 * 8, 1 * 16},
nav_,
false};
nav_};
OptionsField options_type{
{13 * 8, 2 * 16},
+7
View File
@@ -184,6 +184,9 @@ void speaker_unmute() {
void update_audio_mute() {
cfg_speaker_disable = portapack::persistent_memory::config_speaker_disable();
if (cfg_speaker_disable) {
audio_codec->speaker_disable();
}
if (portapack::persistent_memory::config_audio_mute())
speaker_mute();
@@ -269,4 +272,8 @@ void set_rate(const Rate rate) {
clock_manager.set_base_audio_clock_divider(toUType(rate));
}
bool speaker_disable_supported() {
return audio_codec->speaker_disable_supported();
}
} /* namespace audio */
+2
View File
@@ -43,6 +43,7 @@ class Codec {
virtual void speaker_enable() = 0;
virtual void speaker_disable() = 0;
virtual bool speaker_disable_supported() const = 0;
virtual void headphone_enable() = 0;
virtual void headphone_disable() = 0;
@@ -107,6 +108,7 @@ size_t reg_bits();
void init(audio::Codec* const codec);
void shutdown();
bool speaker_disable_supported();
enum class Rate {
Hz_12000 = 4,
+266
View File
@@ -3819,6 +3819,44 @@ static constexpr Bitmap bitmap_icon_looking{
{16, 16},
bitmap_icon_looking_data};
static constexpr uint8_t bitmap_icon_add_data[] = {
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x80,
0x01,
0x80,
0x01,
0x80,
0x01,
0x80,
0x01,
0xF8,
0x1F,
0xF8,
0x1F,
0x80,
0x01,
0x80,
0x01,
0x80,
0x01,
0x80,
0x01,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
};
static constexpr Bitmap bitmap_icon_add{
{16, 16},
bitmap_icon_add_data};
static constexpr uint8_t bitmap_icon_delete_data[] = {
0x00,
0x00,
@@ -5341,6 +5379,234 @@ static constexpr Bitmap bitmap_icon_new_dir{
{16, 16},
bitmap_icon_new_dir_data};
static constexpr uint8_t bitmap_icon_paint_data[] = {
0xFE,
0x3F,
0xFF,
0x3F,
0xFF,
0xFF,
0xFF,
0xBF,
0xFE,
0xBF,
0x00,
0x80,
0x80,
0xFF,
0x80,
0x00,
0x80,
0x00,
0xC0,
0x01,
0xC0,
0x01,
0xC0,
0x01,
0xC0,
0x01,
0xC0,
0x01,
0xC0,
0x01,
0xC0,
0x01,
};
static constexpr Bitmap bitmap_icon_paint{
{16, 16},
bitmap_icon_paint_data};
static constexpr uint8_t bitmap_icon_touchtunes_data[] = {
0xE0,
0x07,
0x30,
0x0C,
0x7C,
0x3E,
0xC4,
0x23,
0x26,
0x64,
0x12,
0x48,
0x0F,
0xF3,
0x09,
0x95,
0x0F,
0xF1,
0x09,
0x91,
0x0F,
0xF1,
0xC9,
0x91,
0xE9,
0x91,
0xC9,
0x90,
0x0F,
0xF0,
0xFF,
0xFF,
};
static constexpr Bitmap bitmap_icon_touchtunes{
{16, 16},
bitmap_icon_touchtunes_data};
static constexpr uint8_t bitmap_arrow_left_data[] = {
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x20,
0x00,
0x30,
0x00,
0x38,
0x00,
0xFC,
0x7F,
0xFE,
0x7F,
0xFC,
0x7F,
0x38,
0x00,
0x30,
0x00,
0x20,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
};
static constexpr Bitmap bitmap_arrow_left{
{16, 16},
bitmap_arrow_left_data};
static constexpr uint8_t bitmap_arrow_right_data[] = {
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x04,
0x00,
0x0C,
0x00,
0x1C,
0xFE,
0x3F,
0xFE,
0x7F,
0xFE,
0x3F,
0x00,
0x1C,
0x00,
0x0C,
0x00,
0x04,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
};
static constexpr Bitmap bitmap_arrow_right{
{16, 16},
bitmap_arrow_right_data};
static constexpr uint8_t bitmap_icon_speaker_and_headphones_data[] = {
0x20,
0x10,
0x30,
0x20,
0x38,
0x44,
0x3E,
0x48,
0x3E,
0x91,
0x3E,
0x92,
0x38,
0x92,
0x30,
0x92,
0x20,
0x92,
0x00,
0x92,
0x30,
0x91,
0x48,
0x48,
0x84,
0x44,
0x84,
0x20,
0x86,
0x11,
0x86,
0x01,
};
static constexpr Bitmap bitmap_icon_speaker_and_headphones{
{16, 16},
bitmap_icon_speaker_and_headphones_data};
static constexpr uint8_t bitmap_icon_speaker_and_headphones_mute_data[] = {
0x40,
0x00,
0x60,
0x44,
0x70,
0x6C,
0x7C,
0x38,
0x7C,
0x10,
0x7C,
0x38,
0x70,
0x6C,
0x60,
0x44,
0x40,
0x00,
0x00,
0x44,
0x30,
0x6C,
0x48,
0x38,
0x84,
0x10,
0x84,
0x38,
0x86,
0x6D,
0x86,
0x45,
};
static constexpr Bitmap bitmap_icon_speaker_and_headphones_mute{
{16, 16},
bitmap_icon_speaker_and_headphones_mute_data};
} /* namespace ui */
#endif /*__BITMAP_HPP__*/
+32 -17
View File
@@ -252,12 +252,9 @@ std::filesystem::path next_filename_matching_pattern(const std::filesystem::path
std::vector<std::filesystem::path> scan_root_files(const std::filesystem::path& directory,
const std::filesystem::path& extension) {
std::vector<std::filesystem::path> file_list{};
for (const auto& entry : std::filesystem::directory_iterator(directory, extension)) {
if (std::filesystem::is_regular_file(entry.status())) {
file_list.push_back(entry.path());
}
}
scan_root_files(directory, extension, [&file_list](const std::filesystem::path& p) {
file_list.push_back(p);
});
return file_list;
}
@@ -287,7 +284,7 @@ std::filesystem::filesystem_error rename_file(
std::filesystem::filesystem_error copy_file(
const std::filesystem::path& file_path,
const std::filesystem::path& dest_path) {
// Decent compromise between memory and speed.
// 512 seems to be the largest block size FatFS likes.
constexpr size_t buffer_size = 512;
uint8_t buffer[buffer_size];
File src;
@@ -324,10 +321,11 @@ FATTimestamp file_created_date(const std::filesystem::path& file_path) {
std::filesystem::filesystem_error make_new_file(
const std::filesystem::path& file_path) {
File f;
auto result = f.create(file_path);
return result.is_valid()
? result.value()
: std::filesystem::filesystem_error{};
auto error = f.create(file_path);
if (error)
return *error;
return {};
}
std::filesystem::filesystem_error make_new_directory(
@@ -488,14 +486,31 @@ path operator/(const path& lhs, const path& rhs) {
return result;
}
bool path_iequal(
const path& lhs,
const 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;
}
directory_iterator::directory_iterator(
std::filesystem::path path,
std::filesystem::path wild)
: pattern{wild} {
const std::filesystem::path& path,
const std::filesystem::path& wild)
: path_{path}, wild_{wild} {
impl = std::make_shared<Impl>();
const auto result = f_findfirst(&impl->dir, &impl->filinfo,
reinterpret_cast<const TCHAR*>(path.c_str()),
reinterpret_cast<const TCHAR*>(pattern.c_str()));
auto result = f_findfirst(&impl->dir, &impl->filinfo,
path_.tchar(), wild_.tchar());
if (result != FR_OK || impl->filinfo.fname[0] == (TCHAR)'\0') {
impl.reset();
// TODO: Throw exception if/when I enable exceptions...
+26 -16
View File
@@ -76,36 +76,30 @@ struct path {
: _s{} {
}
path(
const path& p)
path(const path& p)
: _s{p._s} {
}
path(
path&& p)
path(path&& p)
: _s{std::move(p._s)} {
}
template <class Source>
path(
const Source& source)
path(const Source& source)
: path{std::begin(source), std::end(source)} {
}
template <class InputIt>
path(
InputIt first,
InputIt last)
path(InputIt first,
InputIt last)
: _s{first, last} {
}
path(
const char16_t* const s)
path(const char16_t* const s)
: _s{s} {
}
path(
const TCHAR* const s)
path(const TCHAR* const s)
: _s{reinterpret_cast<const std::filesystem::path::value_type*>(s)} {
}
@@ -132,6 +126,10 @@ struct path {
return native().c_str();
}
const TCHAR* tchar() const {
return reinterpret_cast<const TCHAR*>(native().c_str());
}
const string_type& native() const {
return _s;
}
@@ -149,7 +147,7 @@ struct path {
}
path& operator/=(const path& p) {
if (_s.back() != preferred_separator)
if (_s.back() != preferred_separator && p._s.front() != preferred_separator)
_s += preferred_separator;
_s += p._s;
return *this;
@@ -168,6 +166,9 @@ bool operator>(const path& lhs, const path& rhs);
path operator+(const path& lhs, const path& rhs);
path operator/(const path& lhs, const path& rhs);
/* Case insensitive path equality on underlying "native" string. */
bool path_iequal(const path& lhs, const path& rhs);
using file_status = BYTE;
static_assert(sizeof(path::value_type) == 2, "sizeof(std::filesystem::path::value_type) != 2");
@@ -204,7 +205,8 @@ class directory_iterator {
};
std::shared_ptr<Impl> impl{};
const path pattern{};
std::filesystem::path path_{};
std::filesystem::path wild_{};
friend bool operator!=(const directory_iterator& lhs, const directory_iterator& rhs);
@@ -216,7 +218,8 @@ class directory_iterator {
using iterator_category = std::input_iterator_tag;
directory_iterator() noexcept {};
directory_iterator(std::filesystem::path path, std::filesystem::path wild);
directory_iterator(const std::filesystem::path& path,
const std::filesystem::path& wild);
~directory_iterator() {}
@@ -263,6 +266,13 @@ std::filesystem::filesystem_error make_new_file(const std::filesystem::path& fil
std::filesystem::filesystem_error make_new_directory(const std::filesystem::path& dir_path);
std::filesystem::filesystem_error ensure_directory(const std::filesystem::path& dir_path);
template <typename TCallback>
void scan_root_files(const std::filesystem::path& directory, const std::filesystem::path& extension, const TCallback& fn) {
for (const auto& entry : std::filesystem::directory_iterator(directory, extension)) {
if (std::filesystem::is_regular_file(entry.status()))
fn(entry.path());
}
}
std::vector<std::filesystem::path> scan_root_files(const std::filesystem::path& directory, const std::filesystem::path& extension);
std::vector<std::filesystem::path> scan_root_directories(const std::filesystem::path& directory);
+16 -1
View File
@@ -36,7 +36,8 @@
*/
/* Iterates lines in buffer split on '\n'.
* NB: very basic iterator impl, don't try anything fancy with it. */
* NB: very basic iterator impl, don't try anything fancy with it.
* For example, you _must_ deref the iterator after advancing it. */
template <typename BufferType>
class BufferLineReader {
public:
@@ -133,4 +134,18 @@ using FileLineReader = BufferLineReader<File>;
* are used or they will dangle. */
std::vector<std::string_view> split_string(std::string_view str, char c);
/* Returns the number of lines in a file. */
template <typename BufferType>
uint32_t count_lines(BufferLineReader<BufferType>& reader) {
uint32_t count = 0;
auto it = reader.begin();
do {
*it; // Necessary to force the file read.
++count;
} while (++it != reader.end());
return count;
}
#endif
+2 -1
View File
@@ -59,6 +59,7 @@ class BufferWrapper {
using Offset = uint32_t;
using Line = uint32_t;
using Column = uint32_t;
using Size = File::Size;
using Range = struct {
// Offset of the start, inclusive.
Offset start;
@@ -108,7 +109,7 @@ class BufferWrapper {
}
/* Gets the size of the buffer in bytes. */
File::Size size() const { return wrapped_->size(); }
Size size() const { return wrapped_->size(); }
/* Get the count of the lines in the buffer. */
uint32_t line_count() const { return line_count_; }
+50 -386
View File
@@ -2,6 +2,7 @@
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
* Copyright (C) 2023 gullradriel, Nilorea Studio Inc.
* Copyright (C) 2023 Kyle Reed
*
* This file is part of PortaPack.
*
@@ -21,431 +22,94 @@
* Boston, MA 02110-1301, USA.
*/
#include "file.hpp"
#include "freqman.hpp"
#include "tone_key.hpp"
#include "ui_widget.hpp"
#include <algorithm>
namespace fs = std::filesystem;
using namespace tonekey;
using namespace ui;
using option_t = std::pair<std::string, int32_t>;
using options_t = std::vector<option_t>;
options_t freqman_entry_modulations = {
{"AM", 0},
{"NFM", 1},
{"WFM", 2}};
extern options_t freqman_modulations;
extern options_t freqman_bandwidths[4];
extern options_t freqman_steps;
extern options_t freqman_steps_short;
options_t freqman_entry_bandwidths[4] = {
{// AM
{"DSB 9k", 0},
{"DSB 6k", 1},
{"USB+3k", 2},
{"LSB-3k", 3},
{"CW", 4}},
{// NFM
{"8k5", 0},
{"11k", 1},
{"16k", 2}},
{
// WFM
{"40k", 2},
{"180k", 1},
{"200k", 0},
}};
extern const option_t* find_by_index(const options_t& options, freqman_index_t index);
options_t freqman_entry_steps = {
{"0.1kHz ", 100},
{"1kHz ", 1000},
{"5kHz (SA AM)", 5000},
{"6.25kHz(NFM)", 6250},
{"8.33kHz(AIR)", 8330},
{"9kHz (EU AM)", 9000},
{"10kHz(US AM)", 10000},
{"12.5kHz(NFM)", 12500},
{"15kHz (HFM)", 15000},
{"25kHz (N1)", 25000},
{"30kHz (OIRT)", 30000},
{"50kHz (FM1)", 50000},
{"100kHz (FM2)", 100000},
{"250kHz (N2)", 250000},
{"500kHz (WFM)", 500000},
{"1MHz ", 1000000}};
options_t freqman_entry_steps_short = {
{"0.1kHz", 100},
{"1kHz", 1000},
{"5kHz", 5000},
{"6.25kHz", 6250},
{"8.33kHz", 8330},
{"9kHz", 9000},
{"10kHz", 10000},
{"12.5kHz", 12500},
{"15kHz", 15000},
{"25kHz", 25000},
{"30kHz", 30000},
{"50kHz", 50000},
{"100kHz", 100000},
{"250kHz", 250000},
{"500kHz", 500000},
{"1MHz", 1000000}};
bool load_freqman_file(std::string& file_stem, freqman_db& db) {
return load_freqman_file_ex(file_stem, db, true, true, true, FREQMAN_MAX_PER_FILE);
// TODO: remove in favor of FreqmanDB
/* Freqman file handling. */
bool load_freqman_file(const std::string& file_stem, freqman_db& db, freqman_load_options options) {
return parse_freqman_file(get_freqman_path(file_stem), db, options);
}
bool load_freqman_file_ex(std::string& file_stem, freqman_db& db, bool load_freqs, bool load_ranges, bool load_hamradios, uint8_t max_num_freqs = FREQMAN_MAX_PER_FILE) {
// swap with empty vector to ensure memory is immediately released
std::vector<freqman_entry>().swap(db);
bool delete_freqman_file(const std::string& file_stem) {
delete_file(get_freqman_path(file_stem));
return true;
}
bool save_freqman_file(const std::string& file_stem, freqman_db& db) {
auto path = get_freqman_path(file_stem);
delete_file(path);
File freqman_file;
size_t length, n = 0, file_position = 0;
char* pos;
char* line_start;
char* line_end;
std::string description;
rf::Frequency frequency_a, frequency_b;
char file_data[FREQMAN_READ_BUF_SIZE + 1];
freqman_entry_type type;
freqman_index_t modulation = 0;
freqman_index_t bandwidth = 0;
freqman_index_t step = 0;
freqman_index_t tone = 0;
auto result = freqman_file.open("FREQMAN/" + file_stem + ".TXT");
if (result.is_valid())
auto error = freqman_file.create(path);
if (error)
return false;
while (1) {
// Read a FREQMAN_READ_BUF_SIZE block from file
freqman_file.seek(file_position);
memset(file_data, 0, FREQMAN_READ_BUF_SIZE + 1);
auto read_size = freqman_file.read(file_data, FREQMAN_READ_BUF_SIZE);
if (read_size.is_error())
return false; // Read error
file_position += FREQMAN_READ_BUF_SIZE;
// Reset line_start to beginning of buffer
line_start = file_data;
// If EOF reached, insert 0x0A after, in case the last line doesn't have a C/R
if (read_size.value() < FREQMAN_READ_BUF_SIZE)
*(line_start + read_size.value()) = 0x0A;
// Look for complete lines in buffer
while ((line_end = strstr(line_start, "\x0A"))) {
*line_end = 0; // Stop strstr() searches below at EOL
modulation = -1;
bandwidth = -1;
step = -1;
tone = -1;
type = ERROR_TYPE;
frequency_a = frequency_b = 0;
// Read frequency
pos = strstr(line_start, "f=");
if (pos) {
pos += 2;
frequency_a = strtoll(pos, nullptr, 10);
type = SINGLE;
} else {
// ...or range
pos = strstr(line_start, "a=");
if (pos) {
pos += 2;
frequency_a = strtoll(pos, nullptr, 10);
type = RANGE;
pos = strstr(line_start, "b=");
if (pos) {
pos += 2;
frequency_b = strtoll(pos, nullptr, 10);
} else
frequency_b = 0;
} else {
// ... or hamradio
pos = strstr(line_start, "r=");
if (pos) {
pos += 2;
frequency_a = strtoll(pos, nullptr, 10);
type = HAMRADIO;
pos = strstr(line_start, "t=");
if (pos) {
pos += 2;
frequency_b = strtoll(pos, nullptr, 10);
} else
frequency_b = frequency_a;
} else
frequency_a = 0;
}
}
// modulation if any
pos = strstr(line_start, "m=");
if (pos) {
pos += 2;
modulation = freqman_entry_get_modulation_from_str(pos);
}
// bandwidth if any
pos = strstr(line_start, "bw=");
if (pos) {
pos += 3;
bandwidth = freqman_entry_get_bandwidth_from_str(modulation, pos);
}
// step if any
pos = strstr(line_start, "s=");
if (pos) {
pos += 2;
step = freqman_entry_get_step_from_str_short(pos);
}
// ctcss tone if any
/* disabled until better form
pos = strstr(line_start, "c=");
if (pos) {
pos += 2;
tone = tone_key_index_by_value( strtoll( pos , nullptr , 10 ) );
} */
// Read description until , or LF
pos = strstr(line_start, "d=");
if (pos) {
pos += 2;
length = std::min(strcspn(pos, ",\x0A"), (size_t)FREQMAN_DESC_MAX_LEN);
description = string(pos, length);
}
if ((type == SINGLE && load_freqs) || (type == RANGE && load_ranges) || (type == HAMRADIO && load_hamradios)) {
db.push_back({frequency_a, frequency_b, description, type, modulation, bandwidth, step, tone});
n++;
if (n > max_num_freqs) return true;
}
line_start = line_end + 1;
if (line_start - file_data >= FREQMAN_READ_BUF_SIZE) break;
}
if (read_size.value() != FREQMAN_READ_BUF_SIZE)
break; // End of file
// Restart at beginning of last incomplete line
file_position -= (file_data + FREQMAN_READ_BUF_SIZE - line_start);
}
/* populate implicitly specified modulation / bandwidth */
if (db.size() > 2) {
modulation = db[0].modulation;
bandwidth = db[0].bandwidth;
for (unsigned int it = 1; it < db.size(); it++) {
if (db[it].modulation < 0) {
db[it].modulation = modulation;
} else {
modulation = db[it].modulation;
}
if (db[it].bandwidth < 0) {
db[it].bandwidth = bandwidth;
} else {
modulation = db[it].bandwidth;
}
}
}
return true;
}
bool get_freq_string(freqman_entry& entry, std::string& item_string) {
rf::Frequency frequency_a, frequency_b;
frequency_a = entry.frequency_a;
if (entry.type == SINGLE) {
// Single
item_string = "f=" + to_string_dec_uint(frequency_a / 1000) + to_string_dec_uint(frequency_a % 1000UL, 3, '0');
} else if (entry.type == RANGE) {
// Range
frequency_b = entry.frequency_b;
item_string = "a=" + to_string_dec_uint(frequency_a / 1000) + to_string_dec_uint(frequency_a % 1000UL, 3, '0');
item_string += ",b=" + to_string_dec_uint(frequency_b / 1000) + to_string_dec_uint(frequency_b % 1000UL, 3, '0');
if (entry.step >= 0) {
item_string += ",s=" + freqman_entry_get_step_string_short(entry.step);
}
} else if (entry.type == HAMRADIO) {
frequency_b = entry.frequency_b;
item_string = "r=" + to_string_dec_uint(frequency_a / 1000) + to_string_dec_uint(frequency_a % 1000UL, 3, '0');
item_string += ",t=" + to_string_dec_uint(frequency_b / 1000) + to_string_dec_uint(frequency_b % 1000UL, 3, '0');
if (entry.tone >= 0) {
item_string += ",c=" + tone_key_string(entry.tone);
}
}
if (entry.modulation >= 0 && (unsigned)entry.modulation < freqman_entry_modulations.size()) {
item_string += ",m=" + freqman_entry_get_modulation_string(entry.modulation);
if (entry.bandwidth >= 0 && (unsigned)entry.bandwidth < freqman_entry_bandwidths[entry.modulation].size()) {
item_string += ",bw=" + freqman_entry_get_bandwidth_string(entry.modulation, entry.bandwidth);
}
}
if (entry.description.size())
item_string += ",d=" + entry.description;
for (size_t n = 0; n < db.size(); n++)
freqman_file.write_line(to_freqman_string(*db[n]));
return true;
}
bool delete_freqman_file(std::string& file_stem) {
File freqman_file;
std::string freq_file_path = "/FREQMAN/" + file_stem + ".TXT";
delete_file(freq_file_path);
return false;
}
bool save_freqman_file(std::string& file_stem, freqman_db& db) {
File freqman_file;
std::string freq_file_path = "/FREQMAN/" + file_stem + ".TXT";
delete_file(freq_file_path);
auto result = freqman_file.create(freq_file_path);
if (!result.is_valid()) {
for (size_t n = 0; n < db.size(); n++) {
std::string line;
get_freq_string(db[n], line);
freqman_file.write_line(line);
}
return true;
}
return false;
}
bool create_freqman_file(std::string& file_stem, File& freqman_file) {
auto result = freqman_file.create("FREQMAN/" + file_stem + ".TXT");
if (result.is_valid())
return false;
return true;
}
std::string freqman_item_string(freqman_entry& entry, size_t max_length) {
std::string item_string;
switch (entry.type) {
case SINGLE:
item_string = to_string_short_freq(entry.frequency_a) + "M: " + entry.description;
break;
case RANGE:
item_string = "R: " + entry.description;
break;
case HAMRADIO:
item_string = "H: " + entry.description;
break;
default:
item_string = "!UNKNOW TYPE " + entry.description;
break;
}
if (item_string.size() > max_length)
return item_string.substr(0, max_length - 3) + "...";
return item_string;
bool create_freqman_file(const std::string& file_stem) {
auto fs_error = make_new_file(get_freqman_path(file_stem));
return fs_error.ok();
}
/* Set options. */
void freqman_set_modulation_option(OptionsField& option) {
option.set_options(freqman_entry_modulations);
option.set_options(freqman_modulations);
}
void freqman_set_bandwidth_option(freqman_index_t modulation, OptionsField& option) {
option.set_options(freqman_entry_bandwidths[modulation]);
if (is_valid(modulation))
option.set_options(freqman_bandwidths[modulation]);
}
void freqman_set_step_option(OptionsField& option) {
option.set_options(freqman_entry_steps);
option.set_options(freqman_steps);
}
void freqman_set_step_option_short(OptionsField& option) {
option.set_options(freqman_entry_steps_short);
}
std::string freqman_entry_get_modulation_string(freqman_index_t modulation) {
if (modulation < 0 || (unsigned)modulation >= freqman_entry_modulations.size()) {
return std::string(""); // unknown modulation
}
return freqman_entry_modulations[modulation].first;
}
std::string freqman_entry_get_bandwidth_string(freqman_index_t modulation, freqman_index_t bandwidth) {
if (modulation < 0 || (unsigned)modulation >= freqman_entry_modulations.size()) {
return std::string(""); // unknown modulation
}
if (bandwidth < 0 || (unsigned)bandwidth > freqman_entry_bandwidths[modulation].size()) {
return std::string(""); // unknown modulation
}
return freqman_entry_bandwidths[modulation][bandwidth].first;
}
std::string freqman_entry_get_step_string(freqman_index_t step) {
if (step < 0 || (unsigned)step >= freqman_entry_steps.size()) {
return std::string(""); // unknown modulation
}
return freqman_entry_steps[step].first;
}
std::string freqman_entry_get_step_string_short(freqman_index_t step) {
if (step < 0 || (unsigned)step >= freqman_entry_steps_short.size()) {
return std::string(""); // unknown modulation
}
return freqman_entry_steps_short[step].first;
option.set_options(freqman_steps_short);
}
/* Option value lookup. */
// TODO: use Optional instead of magic values.
int32_t freqman_entry_get_modulation_value(freqman_index_t modulation) {
if (modulation < 0 || (unsigned)modulation >= freqman_entry_modulations.size()) {
return -1; // unknown modulation
}
return freqman_entry_modulations[modulation].second;
if (auto opt = find_by_index(freqman_modulations, modulation))
return opt->second;
return -1;
}
int32_t freqman_entry_get_bandwidth_value(freqman_index_t modulation, freqman_index_t bandwidth) {
if (modulation < 0 || (unsigned)modulation >= freqman_entry_modulations.size()) {
return -1; // unknown modulation
if (modulation < freqman_modulations.size()) {
if (auto opt = find_by_index(freqman_bandwidths[modulation], bandwidth))
return opt->second;
}
if (bandwidth < 0 || (unsigned)bandwidth > freqman_entry_bandwidths[modulation].size()) {
return -1; // unknown bandwidth for modulation
}
return freqman_entry_bandwidths[modulation][bandwidth].second;
return -1;
}
int32_t freqman_entry_get_step_value(freqman_index_t step) {
if (step < 0 || (unsigned)step >= freqman_entry_steps.size()) {
return -1; // unknown modulation
}
return freqman_entry_steps[step].second;
}
freqman_index_t freqman_entry_get_modulation_from_str(char* str) {
if (!str)
return -1;
for (freqman_index_t index = 0; (unsigned)index < freqman_entry_modulations.size(); index++) {
if (strncmp(freqman_entry_modulations[index].first.c_str(), str, freqman_entry_modulations[index].first.size()) == 0)
return index;
}
return -1;
}
freqman_index_t freqman_entry_get_bandwidth_from_str(freqman_index_t modulation, char* str) {
if (!str)
return -1;
if (modulation < 0 || (unsigned)modulation >= freqman_entry_modulations.size())
return -1;
for (freqman_index_t index = 0; (unsigned)index < freqman_entry_bandwidths[modulation].size(); index++) {
if (strncmp(freqman_entry_bandwidths[modulation][index].first.c_str(), str, freqman_entry_bandwidths[modulation][index].first.size()) == 0)
return index;
}
return -1;
}
freqman_index_t freqman_entry_get_step_from_str(char* str) {
if (!str)
return -1;
for (freqman_index_t index = 0; (unsigned)index < freqman_entry_steps.size(); index++) {
if (strncmp(freqman_entry_steps[index].first.c_str(), str, freqman_entry_steps[index].first.size()) == 0)
return index;
}
return -1;
}
freqman_index_t freqman_entry_get_step_from_str_short(char* str) {
if (!str)
return -1;
for (freqman_index_t index = 0; (unsigned)index < freqman_entry_steps_short.size(); index++) {
if (strncmp(freqman_entry_steps_short[index].first.c_str(), str, freqman_entry_steps_short[index].first.size()) == 0)
return index;
}
if (auto opt = find_by_index(freqman_steps, step))
return opt->second;
return -1;
}
+19 -76
View File
@@ -2,6 +2,7 @@
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
* Copyright (C) 2023 gullradriel, Nilorea Studio Inc.
* Copyright (C) 2023 Kyle Reed
*
* This file is part of PortaPack.
*
@@ -27,101 +28,43 @@
#include <cstring>
#include <string>
#include "file.hpp"
#include "ui_receiver.hpp"
#include "tone_key.hpp"
#include "freqman_db.hpp"
#include "string_format.hpp"
#include "ui_receiver.hpp"
#include "ui_widget.hpp"
#define FREQMAN_DESC_MAX_LEN 24 // This is the number of characters that can be drawn in front of "R: TEXT..." before taking a full screen line
#define FREQMAN_MAX_PER_FILE 60 // Maximum of entries we can read. This is a hardware limit
// It was tested and lowered to leave a bit of space to the caller
#define FREQMAN_MAX_PER_FILE_STR "60" // STRING OF FREQMAN_MAX_PER_FILE
#define FREQMAN_READ_BUF_SIZE 96 // max freqman line size including desc is 90, +6 for a bit of space
// Defined for back-compat.
#define FREQMAN_MAX_PER_FILE freqman_default_max_entries
using namespace ui;
using namespace std;
using namespace tonekey;
// needs to be signed as -1 means not set
typedef int8_t freqman_index_t;
enum freqman_error {
enum freqman_error : int8_t {
NO_ERROR = 0,
ERROR_ACCESS,
ERROR_NOFILES,
ERROR_DUPLICATE
};
enum freqman_entry_type {
SINGLE = 0, // f=
RANGE, // a=,b=
HAMRADIO, // r=,t=
ERROR_TYPE
};
enum freqman_entry_modulation {
enum freqman_entry_modulation : uint8_t {
AM_MODULATION = 0,
NFM_MODULATION,
WFM_MODULATION,
MODULATION_DEF,
ERROR_MODULATION
SPEC_MODULATION
};
// Entry step placed for AlainD freqman version (or any other enhanced version)
enum freqman_entry_step {
STEP_DEF = -1, // default
AM_US, // 10 kHz AM/CB
AM_EUR, // 9 kHz LW/MW
NFM_1, // 12,5 kHz (Analogic PMR 446)
NFM_2, // 6,25 kHz (Digital PMR 446)
FM_1, // 100 kHz
FM_2, // 50 kHz
N_1, // 25 kHz
N_2, // 250 kHz
AIRBAND, // AIRBAND 8,33 kHz
ERROR_STEP
};
// TODO: Replace with FreqmanDB.
bool load_freqman_file(const std::string& file_stem, freqman_db& db, freqman_load_options options);
bool delete_freqman_file(const std::string& file_stem);
bool save_freqman_file(const std::string& file_stem, freqman_db& db);
bool create_freqman_file(const std::string& file_stem);
struct freqman_entry {
rf::Frequency frequency_a{0}; // 'f=freq' or 'a=freq_start' or 'r=recv_freq'
rf::Frequency frequency_b{0}; // 'b=freq_end' or 't=tx_freq'
std::string description{}; // 'd=desc'
freqman_entry_type type{}; // SINGLE,RANGE,HAMRADIO
freqman_index_t modulation{}; // AM,NFM,WFM
freqman_index_t bandwidth{}; // AM_DSB, ...
freqman_index_t step{}; // 5khz (SA AM,...
tone_index tone{}; // 0XZ, 11 1ZB,...
};
using freqman_db = std::vector<freqman_entry>;
bool load_freqman_file(std::string& file_stem, freqman_db& db);
bool load_freqman_file_ex(std::string& file_stem, freqman_db& db, bool load_freqs, bool load_ranges, bool load_hamradios, uint8_t limit);
bool get_freq_string(freqman_entry& entry, std::string& item_string);
bool delete_freqman_file(std::string& file_stem);
bool save_freqman_file(std::string& file_stem, freqman_db& db);
bool create_freqman_file(std::string& file_stem, File& freqman_file);
std::string freqman_item_string(freqman_entry& item, size_t max_length);
void freqman_set_bandwidth_option(freqman_index_t modulation, OptionsField& option);
void freqman_set_modulation_option(OptionsField& option);
void freqman_set_step_option(OptionsField& option);
void freqman_set_step_option_short(OptionsField& option);
void freqman_set_tone_option(OptionsField& option);
std::string freqman_entry_get_modulation_string(freqman_index_t modulation);
std::string freqman_entry_get_bandwidth_string(freqman_index_t modulation, freqman_index_t bandwidth);
std::string freqman_entry_get_step_string(freqman_index_t step);
std::string freqman_entry_get_step_string_short(freqman_index_t step);
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);
// TODO: Can these be removed after Recon is migrated to FreqmanDB?
int32_t freqman_entry_get_modulation_value(freqman_index_t modulation);
int32_t freqman_entry_get_bandwidth_value(freqman_index_t modulation, freqman_index_t bandwidth);
int32_t freqman_entry_get_step_value(freqman_index_t step);
freqman_index_t freqman_entry_get_modulation_from_str(char* str);
freqman_index_t freqman_entry_get_bandwidth_from_str(freqman_index_t modulation, char* str);
freqman_index_t freqman_entry_get_step_from_str(char* str);
freqman_index_t freqman_entry_get_step_from_str_short(char* str);
#endif /*__FREQMAN_H__*/
+460
View File
@@ -0,0 +1,460 @@
/*
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
* Copyright (C) 2023 gullradriel, Nilorea Studio Inc.
* Copyright (C) 2023 Kyle Reed
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include "convert.hpp"
#include "file.hpp"
#include "file_reader.hpp"
#include "freqman_db.hpp"
#include "string_format.hpp"
#include "tone_key.hpp"
#include "utility.hpp"
#include <array>
#include <cctype>
#include <string_view>
#include <vector>
namespace fs = std::filesystem;
const std::filesystem::path freqman_dir{u"/FREQMAN"};
const std::filesystem::path freqman_extension{u".TXT"};
const std::filesystem::path get_freqman_path(const std::string& stem) {
return freqman_dir / stem + freqman_extension;
}
// NB: Don't include UI headers to keep this code unit testable.
using option_t = std::pair<std::string, int32_t>;
using options_t = std::vector<option_t>;
options_t freqman_modulations = {
{"AM", 0},
{"NFM", 1},
{"WFM", 2},
{"SPEC", 3},
};
options_t freqman_bandwidths[4] = {
{
// AM
{"DSB 9k", 0},
{"DSB 6k", 1},
{"USB+3k", 2},
{"LSB-3k", 3},
{"CW", 4},
},
{
// NFM
{"8k5", 0},
{"11k", 1},
{"16k", 2},
},
{
// WFM
{"40k", 2},
{"180k", 1},
{"200k", 0},
},
{
// SPEC -- TODO: these should be indexes.
{"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) */
{"650k", 650000},
{"750k", 750000}, /* From this BW onwards, the LCD is ok, but the recorded file is 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)
},
};
options_t freqman_steps = {
{"0.1kHz ", 100},
{"1kHz ", 1000},
{"5kHz (SA AM)", 5000},
{"6.25kHz(NFM)", 6250},
{"8.33kHz(AIR)", 8330},
{"9kHz (EU AM)", 9000},
{"10kHz(US AM)", 10000},
{"12.5kHz(NFM)", 12500},
{"15kHz (HFM)", 15000},
{"25kHz (N1)", 25000},
{"30kHz (OIRT)", 30000},
{"50kHz (FM1)", 50000},
{"100kHz (FM2)", 100000},
{"250kHz (N2)", 250000},
{"500kHz (WFM)", 500000},
{"1MHz ", 1000000},
};
options_t freqman_steps_short = {
{"0.1kHz", 100},
{"1kHz", 1000},
{"5kHz", 5000},
{"6.25kHz", 6250},
{"8.33kHz", 8330},
{"9kHz", 9000},
{"10kHz", 10000},
{"12.5kHz", 12500},
{"15kHz", 15000},
{"25kHz", 25000},
{"30kHz", 30000},
{"50kHz", 50000},
{"100kHz", 100000},
{"250kHz", 250000},
{"500kHz", 500000},
{"1MHz", 1000000},
};
uint8_t find_by_name(const options_t& options, std::string_view name) {
for (auto ix = 0u; ix < options.size(); ++ix)
if (options[ix].first == name)
return ix;
return freqman_invalid_index;
}
const option_t* find_by_index(const options_t& options, freqman_index_t index) {
if (index < options.size())
return &options[index];
else
return nullptr;
}
/* Impl for next round of changes.
*template <typename T, size_t N>
*const T* find_by_name(const std::array<T, N>& info, std::string_view name) {
* for (const auto& it : info) {
* if (it.name == name)
* return &it;
* }
*
* return nullptr;
*}
*/
std::string freqman_entry_get_modulation_string(freqman_index_t modulation) {
if (auto opt = find_by_index(freqman_modulations, modulation))
return opt->first;
return {};
}
std::string freqman_entry_get_bandwidth_string(freqman_index_t modulation, freqman_index_t bandwidth) {
if (modulation < freqman_modulations.size()) {
if (auto opt = find_by_index(freqman_bandwidths[modulation], bandwidth))
return opt->first;
}
return {};
}
std::string freqman_entry_get_step_string(freqman_index_t step) {
if (auto opt = find_by_index(freqman_steps, step))
return opt->first;
return {};
}
std::string freqman_entry_get_step_string_short(freqman_index_t step) {
if (auto opt = find_by_index(freqman_steps_short, step))
return opt->first;
return {};
}
std::string pretty_string(const freqman_entry& entry, size_t max_length) {
std::string str;
switch (entry.type) {
case freqman_type::Single:
str = to_string_short_freq(entry.frequency_a) + "M: " + entry.description;
break;
case freqman_type::Range:
str = to_string_dec_uint(entry.frequency_a / 1'000'000) + "M-" +
to_string_dec_uint(entry.frequency_b / 1'000'000) + "M: " + entry.description;
break;
case freqman_type::HamRadio:
str = "" + to_string_dec_uint(entry.frequency_a / 1'000'000) + "M," +
to_string_dec_uint(entry.frequency_b / 1'000'000) + "M: " + entry.description;
break;
default:
str = "UNK:" + entry.description;
break;
}
// Truncate. '+' indicates if string has been truncated.
if (str.size() > max_length)
return str.substr(0, max_length - 1) + "+";
return str;
}
std::string to_freqman_string(const freqman_entry& entry) {
std::string serialized;
serialized.reserve(0x80);
// Append a key=value to the string.
auto append_field = [&serialized](std::string_view name, std::string_view value) {
if (!serialized.empty())
serialized += ",";
serialized += std::string{name} + "=" + std::string{value};
};
switch (entry.type) {
case freqman_type::Single:
append_field("f", to_string_dec_uint64(entry.frequency_a));
break;
case freqman_type::Range:
append_field("a", to_string_dec_uint64(entry.frequency_a));
append_field("b", to_string_dec_uint64(entry.frequency_b));
if (is_valid(entry.step))
append_field("s", freqman_entry_get_step_string_short(entry.step));
break;
case freqman_type::HamRadio:
append_field("r", to_string_dec_uint64(entry.frequency_a));
append_field("t", to_string_dec_uint64(entry.frequency_b));
if (is_valid(entry.tone))
append_field("c", tonekey::tone_key_value_string(entry.tone));
break;
default:
return {}; // TODO: Comment type with description?
};
if (is_valid(entry.modulation) && entry.modulation < freqman_modulations.size()) {
append_field("m", freqman_entry_get_modulation_string(entry.modulation));
if (is_valid(entry.bandwidth) && (unsigned)entry.bandwidth < freqman_bandwidths[entry.modulation].size())
append_field("bw", freqman_entry_get_bandwidth_string(entry.modulation, entry.bandwidth));
}
if (entry.description.size() > 0)
append_field("d", entry.description);
serialized.shrink_to_fit();
return serialized;
}
freqman_index_t parse_tone_key(std::string_view value) {
// Split into whole and fractional parts.
auto parts = split_string(value, '.');
int32_t tone_freq = 0;
int32_t whole_part = 0;
parse_int(parts[0], whole_part);
// Tones are stored as frequency / 100 for some reason.
// E.g. 14572 would be 145.7 (NB: 1s place is dropped).
// TODO: Might be easier to just store the codes?
// Multiply the whole part by 100 to get the tone frequency.
tone_freq = whole_part * 100;
// Add the fractional part, if present.
if (parts.size() > 1) {
auto c = parts[1].front();
auto digit = std::isdigit(c) ? c - '0' : 0;
tone_freq += digit * 10;
}
return static_cast<freqman_index_t>(tonekey::tone_key_index_by_value(tone_freq));
}
bool parse_freqman_entry(std::string_view str, freqman_entry& entry) {
if (str.empty() || str[0] == '#')
return false;
entry = freqman_entry{};
auto cols = split_string(str, ',');
for (auto col : cols) {
if (col.empty())
continue;
auto pair = split_string(col, '=');
if (pair.size() != 2)
continue;
auto key = pair[0];
auto value = pair[1];
if (key == "a") {
entry.type = freqman_type::Range;
parse_int(value, entry.frequency_a);
} else if (key == "b") {
parse_int(value, entry.frequency_b);
} else if (key == "bw") {
// NB: Requires modulation to be set first
if (entry.modulation < std::size(freqman_bandwidths)) {
entry.bandwidth = find_by_name(freqman_bandwidths[entry.modulation], value);
}
} else if (key == "c") {
entry.tone = parse_tone_key(value);
} else if (key == "d") {
entry.description = trim(value);
} else if (key == "f") {
entry.type = freqman_type::Single;
parse_int(value, entry.frequency_a);
} else if (key == "m") {
entry.modulation = find_by_name(freqman_modulations, value);
} else if (key == "r") {
entry.type = freqman_type::HamRadio;
parse_int(value, entry.frequency_a);
} else if (key == "s") {
entry.step = find_by_name(freqman_steps_short, value);
} else if (key == "t") {
parse_int(value, entry.frequency_b);
}
}
// No valid frequency combination was set.
if (entry.type == freqman_type::Unknown)
return false;
// Ranges should have both frequencies set and A <= B.
if (entry.type == freqman_type::Range || entry.type == freqman_type::HamRadio) {
if (entry.frequency_a == 0 || entry.frequency_b == 0)
return false;
if (entry.frequency_a > entry.frequency_b)
return false;
}
// TODO: Consider additional validation:
// - Tone only on HamRadio.
// - Fail on failed parse_int.
// - Fail if bandwidth set before modulation.
return true;
}
// TODO: Use FreqmanDB iterator.
bool parse_freqman_file(const fs::path& path, freqman_db& db, freqman_load_options options) {
File f;
auto error = f.open(path);
if (error)
return false;
auto reader = FileLineReader(f);
auto line_count = count_lines(reader);
// Attempt to avoid a re-alloc if possible.
db.clear();
db.reserve(line_count);
for (const auto& line : reader) {
freqman_entry entry{};
if (!parse_freqman_entry(line, entry))
continue;
// Filter by entry type.
if ((entry.type == freqman_type::Single && !options.load_freqs) ||
(entry.type == freqman_type::Range && !options.load_ranges) ||
(entry.type == freqman_type::HamRadio && !options.load_hamradios)) {
continue;
}
// Use previous entry's mod/band if current's aren't set.
if (!db.empty()) {
if (is_invalid(entry.modulation))
entry.modulation = db.back()->modulation;
if (is_invalid(entry.bandwidth))
entry.bandwidth = db.back()->bandwidth;
}
// Move the entry onto the heap and push.
db.push_back(std::make_unique<freqman_entry>(std::move(entry)));
// Limit to max_entries when specified.
if (options.max_entries > 0 && db.size() >= options.max_entries)
break;
}
db.shrink_to_fit();
return true;
}
/* FreqmanDB ***********************************/
bool FreqmanDB::open(const std::filesystem::path& path) {
auto result = FileWrapper::open(path);
if (!result)
return false;
wrapper_ = *std::move(result);
return true;
}
void FreqmanDB::close() {
wrapper_.reset();
}
freqman_entry FreqmanDB::operator[](FileWrapper::Line line) const {
auto length = wrapper_->line_length(line);
auto line_text = wrapper_->get_text(line, 0, length);
if (line_text) {
freqman_entry entry;
if (parse_freqman_entry(*line_text, entry))
return entry;
}
return {};
}
void FreqmanDB::insert_entry(const freqman_entry& entry, FileWrapper::Line line) {
// TODO: Can be more efficient.
line = clip<uint32_t>(line, 0u, entry_count());
wrapper_->insert_line(line);
replace_entry(line, entry);
}
void FreqmanDB::replace_entry(FileWrapper::Line line, const freqman_entry& entry) {
auto range = wrapper_->line_range(line);
if (!range)
return; // TODO: Message?
// Don't overwrite the '\n'.
range->end--;
wrapper_->replace_range(*range, to_freqman_string(entry));
}
void FreqmanDB::delete_entry(FileWrapper::Line line) {
wrapper_->delete_line(line);
}
uint32_t FreqmanDB::entry_count() const {
// FileWrapper always presents a single line even for empty files.
return empty() ? 0u : wrapper_->line_count();
}
bool FreqmanDB::empty() const {
// FileWrapper always presents a single line even for empty files.
// A DB is only really empty if the file size is 0.
return !wrapper_ || wrapper_->size() == 0;
}
+234
View File
@@ -0,0 +1,234 @@
/*
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
* Copyright (C) 2023 gullradriel, Nilorea Studio Inc.
* Copyright (C) 2023 Kyle Reed
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#ifndef __FREQMAN_DB_H__
#define __FREQMAN_DB_H__
#include "file.hpp"
#include "file_wrapper.hpp"
#include "utility.hpp"
#include <array>
#include <memory>
#include <string>
#include <string_view>
#include <vector>
/* Defined in freqman_db.cpp */
extern const std::filesystem::path freqman_dir;
extern const std::filesystem::path freqman_extension;
using freqman_index_t = uint8_t;
constexpr freqman_index_t freqman_invalid_index = static_cast<freqman_index_t>(-1);
/* Returns true if the value is not invalid_index. */
constexpr bool is_valid(freqman_index_t index) {
return index != freqman_invalid_index;
}
/* Returns true if the value is invalid_index. */
constexpr bool is_invalid(freqman_index_t index) {
return index == freqman_invalid_index;
}
/* Gets the full path for a given file stem (no extension). */
const std::filesystem::path get_freqman_path(const std::string& stem);
enum class freqman_type : uint8_t {
Single, // f=
Range, // a=,b=
HamRadio, // r=,t=
Unknown,
};
/* Tables for next round of changes.
* // TODO: attempt to consolidate all of these values
* // and settings into a single location.
* // Should tone_keys get consolidated here too?
* enum class freqman_modulation : uint8_t {
AM,
NFM,
WFM,
SPEC,
Unknown,
* };
*
* struct freqman_modulation_info {
* freqman_modulation modulation;
* std::string_view name;
* };
*
* constexpr std::array<freqman_modulation_info, 5> freqman_modulations = {
freqman_modulation_info{ freqman_modulation::AM, "AM" },
freqman_modulation_info{ freqman_modulation::NFM, "NFM" },
freqman_modulation_info{ freqman_modulation::WFM, "WFM" },
freqman_modulation_info{ freqman_modulation::SPEC, "SPEC" },
freqman_modulation_info{ freqman_modulation::Unknown, "Unknown" }
* };
* static_assert(std::size(freqman_modulations) == (size_t)freqman_modulation::Unknown + 1);
*
* enum class freqman_step : uint8_t {
_100Hz,
_1kHz,
_5kHz,
_6_25kHz,
_8_33kHz,
_9kHz,
_10kHz,
_12_5kHz,
_15kHz,
_25kHz,
_30kHz,
_50kHz,
_100kHz,
_250kHz,
_500kHz,
_1MHz,
Unknown,
* };
*
* struct freqman_step_info {
* freqman_step step;
* std::string_view name;
* std::string_view display_name;
* uint32_t value;
* };
*
* // TODO: FrequencyStepView should use this list.
* constexpr std::array<freqman_step_info, 18> freqman_steps = {
freqman_step_info{ freqman_step::_100Hz, "0.1kHz", "0.1kHz ", 100 },
freqman_step_info{ freqman_step::_1kHz, "1kHz", "1kHz ", 1'000 },
freqman_step_info{ freqman_step::_5kHz, "5kHz", "5kHz (SA AM)", 5'000 },
freqman_step_info{ freqman_step::_6_25kHz, "6.25kHz", "6.25kHz(NFM)", 6'250 },
freqman_step_info{ freqman_step::_8_33kHz, "8.33kHz", "8.33kHz(AIR)", 8'330 },
freqman_step_info{ freqman_step::_9kHz, "9kHz", "9kHz (EU AM)", 9'000 },
freqman_step_info{ freqman_step::_10kHz, "10kHz", "10kHz(US AM)", 10'000 },
freqman_step_info{ freqman_step::_12_5kHz, "12.5kHz", "12.5kHz(NFM)", 12'500 },
freqman_step_info{ freqman_step::_15kHz, "15kHz", "15kHz (HFM)", 15'000 },
freqman_step_info{ freqman_step::_25kHz, "25kHz", "25kHz (N1)", 25'000 },
freqman_step_info{ freqman_step::_30kHz, "30kHz", "30kHz (OIRT)", 30'000 },
freqman_step_info{ freqman_step::_50kHz, "50kHz", "50kHz (FM1)", 50'000 },
freqman_step_info{ freqman_step::_100kHz, "100kHz", "100kHz (FM2)", 100'000 },
freqman_step_info{ freqman_step::_250kHz, "250kHz", "250kHz (N2)", 250'000 },
freqman_step_info{ freqman_step::_500kHz, "500kHz", "500kHz (WFM)", 500'000 },
freqman_step_info{ freqman_step::_1MHz, "1MHz", "1MHz ", 1'000'000 },
freqman_step_info{ freqman_step::Unknown, "Unknown", "Unknown ", 0 },
* };
* static_assert(std::size(freqman_steps) == (size_t)freqman_step::Unknown + 1);
*/
/* Freqman Entry *******************************/
struct freqman_entry {
int64_t frequency_a{0}; // 'f=freq' or 'a=freq_start' or 'r=recv_freq'
int64_t frequency_b{0}; // 'b=freq_end' or 't=tx_freq'
std::string description{0}; // 'd=desc'
freqman_type type{freqman_type::Unknown};
freqman_index_t modulation{freqman_invalid_index};
freqman_index_t bandwidth{freqman_invalid_index};
freqman_index_t step{freqman_invalid_index};
freqman_index_t tone{freqman_invalid_index};
};
// TODO: These shouldn't be exported.
std::string freqman_entry_get_modulation_string(freqman_index_t modulation);
std::string freqman_entry_get_bandwidth_string(freqman_index_t modulation, freqman_index_t bandwidth);
std::string freqman_entry_get_step_string(freqman_index_t step);
std::string freqman_entry_get_step_string_short(freqman_index_t step);
/* A reasonable maximum number of items to load from a freqman file.
* Apps using freqman_db should be tested and this value tuned to
* ensure app memory stability. */
constexpr size_t freqman_default_max_entries = 150;
struct freqman_load_options {
/* Loads all entries when set to 0. */
size_t max_entries{freqman_default_max_entries};
bool load_freqs{true};
bool load_ranges{true};
bool load_hamradios{true};
};
using freqman_entry_ptr = std::unique_ptr<freqman_entry>;
using freqman_db = std::vector<freqman_entry_ptr>;
/* Gets a pretty string representation for an entry. */
std::string pretty_string(const freqman_entry& item, size_t max_length = 30);
/* Gets the freqman file representation for an entry. */
std::string to_freqman_string(const freqman_entry& entry);
bool parse_freqman_entry(std::string_view str, freqman_entry& entry);
bool parse_freqman_file(const std::filesystem::path& path, freqman_db& db, freqman_load_options options);
/* The tricky part of using the file directly is that there can be comments
* and empty lines in the file. This messes up the 'count' calculation.
* Either have to live with 'count' being an upper bound have the callers
* know to expect that entries may be empty. */
// NB: This won't apply implicit mod/bandwidth.
// TODO: Reuse for parse_freqman_file
class FreqmanDB {
public:
class iterator {
public:
iterator(FreqmanDB& db, FileWrapper::Offset line)
: db_{db}, line_{line} {}
iterator& operator++() {
line_++;
return *this;
}
freqman_entry operator*() const {
return db_[line_];
}
bool operator!=(const iterator& other) {
return &db_ != &other.db_ || line_ != other.line_;
}
private:
FreqmanDB& db_;
FileWrapper::Line line_;
};
bool open(const std::filesystem::path& path);
void close();
freqman_entry operator[](FileWrapper::Line line) const;
void insert_entry(const freqman_entry& entry, FileWrapper::Line line);
void replace_entry(FileWrapper::Line line, const freqman_entry& entry);
void delete_entry(FileWrapper::Line line);
uint32_t entry_count() const;
bool empty() const;
iterator begin() {
return {*this, 0};
}
iterator end() {
return {*this, entry_count()};
}
private:
std::unique_ptr<FileWrapper> wrapper_{};
};
#endif /* __FREQMAN_DB_H__ */
+40 -10
View File
@@ -30,7 +30,7 @@ bool Debounce::feed(const uint8_t bit) {
// "Repeat" handling - simulated button release
if (repeat_ctr_) {
// Make sure the button is still being held continuously
if (history_ == 0xFF) {
if ((history_ == 0xFF) && !long_press_enabled_) {
// Simulate button press every REPEAT_SUBSEQUENT_DELAY ticks
if (--repeat_ctr_ == 0) {
state_ = !state_;
@@ -49,29 +49,59 @@ bool Debounce::feed(const uint8_t bit) {
if ((history_ & DEBOUNCE_MASK) == DEBOUNCE_MASK) {
state_ = 1;
held_time_ = 0;
// If long_press_enabled_, state() function masks the button press until it's released
// or until LONG_PRESS_DELAY is reached
if (long_press_enabled_) {
pulse_upon_release_ = true;
return false;
}
return true;
}
} else {
// Previous button state was 1 (pressed);
// Has button been released for DEBOUNCE_COUNT ticks?
if ((history_ & DEBOUNCE_MASK) == 0) {
state_ = 0;
// Button has been released when long_press_enabled_ and before LONG_PRESS_DELAY was reached;
// allow state() function to finally return a single press indication
// (in long press mode, apps won't see button press until the button is released)
if (pulse_upon_release_) {
// leaving state_==1 for one cycle
pulse_upon_release_ = 0;
} else {
state_ = 0;
}
// Reset long_press_occurred_ flag after button is released
long_press_occurred_ = false;
return true;
}
// Repeat support is limited to the 4 directional buttons
if (repeat_enabled_) {
// Has button been held continuously for DEBOUNCE_REPEAT_DELAY?
if (history_ == 0xFF) {
if (++held_time_ == REPEAT_INITIAL_DELAY) {
// Has button been held continuously?
if (history_ == 0xFF) {
held_time_++;
if (pulse_upon_release_) {
// Button is being held down and long_press support is enabled for this key:
// if LONG_PRESS_DELAY is reached then finally report that switch is pressed and set flag
// indicating it was a LONG press
// (note that repease_support and long_press support are mutually exclusive)
if (held_time_ == LONG_PRESS_DELAY) {
long_press_occurred_ = true;
pulse_upon_release_ = 0;
held_time_ = 0;
return true;
}
} else if (repeat_enabled_ && !long_press_enabled_) {
// Repeat support -- 4 directional buttons only (unless long_press is enabled)
if (held_time_ == REPEAT_INITIAL_DELAY) {
// Delay reached; trigger repeat code on NEXT tick
repeat_ctr_ = 1;
held_time_ = 0;
}
} else {
// Button not continuously pressed; reset counter
held_time_ = 0;
}
} else {
// Button not continuously pressed; reset counter
held_time_ = 0;
}
}
return false;
+15 -1
View File
@@ -31,25 +31,39 @@
// # of timer0 ticks before a held button starts being counted as repeated presses
#define REPEAT_INITIAL_DELAY 250
#define REPEAT_SUBSEQUENT_DELAY 92
#define LONG_PRESS_DELAY 1000
class Debounce {
public:
bool feed(const uint8_t bit);
uint8_t state() const {
return state_;
return (pulse_upon_release_) ? 0 : state_;
}
void enable_repeat() {
repeat_enabled_ = true;
}
void set_long_press_support(bool v) {
long_press_enabled_ = v;
}
bool long_press_occurred() {
bool v = long_press_occurred_;
long_press_occurred_ = false;
return v;
}
private:
uint8_t history_{0};
uint8_t state_{0};
bool repeat_enabled_{0};
uint16_t repeat_ctr_{0};
uint16_t held_time_{0};
bool pulse_upon_release_{0};
bool long_press_enabled_{0};
bool long_press_occurred_{0};
};
#endif /*__DEBOUNCE_H__*/
+19 -2
View File
@@ -195,16 +195,33 @@ void controls_init() {
SwitchesState get_switches_state() {
SwitchesState result;
// Right, Left, Down, Up, & Select switches
for (size_t i = 0; i < result.size() - 1; i++) {
// TODO: Ignore multiple keys at the same time?
result[i] = switch_debounce[i].state();
}
result[result.size() - 1] = switch_debounce[switch_debounce.size() - 1].state();
// Grab Dfu switch from bit 7 and return in bit 5 so that all switches are grouped together in this 6-bit result
result[(size_t)Switch::Dfu] = switch_debounce[7].state();
return result;
}
// Configure which switches support long press (note those switches will not support Repeat function)
void switches_long_press_enable(SwitchesState switches_long_press_enabled) {
// Right, Left, Down, Up, & Select switches
for (size_t i = 0; i < switches_long_press_enabled.size() - 1; i++) {
switch_debounce[i].set_long_press_support(switches_long_press_enabled[i]);
}
// Dfu switch
switch_debounce[7].set_long_press_support(switches_long_press_enabled[(size_t)Switch::Dfu]);
}
bool switch_long_press_occurred(size_t v) {
return (v == (size_t)Switch::Dfu) ? switch_debounce[7].long_press_occurred() : switch_debounce[v].long_press_occurred();
}
EncoderPosition get_encoder_position() {
return encoder_position;
}
+3
View File
@@ -27,6 +27,7 @@
#include "touch.hpp"
// Must be same values as in ui::KeyEvent
enum class Switch {
Right = 0,
Left = 1,
@@ -44,6 +45,8 @@ void controls_init();
SwitchesState get_switches_state();
EncoderPosition get_encoder_position();
touch::Frame get_touch_frame();
void switches_long_press_enable(SwitchesState v);
bool switch_long_press_occurred(size_t v);
namespace control {
namespace debug {
+88
View File
@@ -0,0 +1,88 @@
/*
* Copyright (C) 2023 Kyle Reed
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include "metadata_file.hpp"
#include "convert.hpp"
#include "file_reader.hpp"
#include "string_format.hpp"
#include <string_view>
namespace fs = std::filesystem;
using namespace std::literals;
const std::string_view center_freq_name = "center_frequency"sv;
const std::string_view sample_rate_name = "sample_rate"sv;
fs::path get_metadata_path(const fs::path& capture_path) {
auto temp = capture_path;
return temp.replace_extension(u".TXT");
}
Optional<File::Error> write_metadata_file(const fs::path& path, capture_metadata metadata) {
File f;
auto error = f.create(path);
if (error)
return error;
error = f.write_line(std::string{center_freq_name} + "=" +
to_string_dec_uint(metadata.center_frequency));
if (error)
return error;
error = f.write_line(std::string{sample_rate_name} + "=" +
to_string_dec_uint(metadata.sample_rate));
if (error)
return error;
return {};
}
Optional<capture_metadata> read_metadata_file(const fs::path& path) {
File f;
auto error = f.open(path);
if (error)
return {};
capture_metadata metadata{};
auto reader = FileLineReader(f);
for (const auto& line : reader) {
auto cols = split_string(line, '=');
if (cols.size() != 2)
continue; // Bad line.
if (cols[0] == center_freq_name)
parse_int(cols[1], metadata.center_frequency);
else if (cols[0] == sample_rate_name)
parse_int(cols[1], metadata.sample_rate);
else
continue;
}
if (metadata.center_frequency == 0 || metadata.sample_rate == 0)
return {}; // Parse failed.
return metadata;
}
+39
View File
@@ -0,0 +1,39 @@
/*
* Copyright (C) 2023 Kyle Reed
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#ifndef __METADATA_FILE_HPP__
#define __METADATA_FILE_HPP__
#include "file.hpp"
#include "optional.hpp"
#include "rf_path.hpp"
struct capture_metadata {
rf::Frequency center_frequency;
uint32_t sample_rate;
};
std::filesystem::path get_metadata_path(const std::filesystem::path& capture_path);
Optional<File::Error> write_metadata_file(const std::filesystem::path& path, capture_metadata metadata);
Optional<capture_metadata> read_metadata_file(const std::filesystem::path& path);
#endif // __METADATA_FILE_HPP__
+29 -20
View File
@@ -28,38 +28,47 @@
#include "receiver_model.hpp"
#include "transmitter_model.hpp"
/* Stashes current radio bandwidth and sampling rate.
* Inits to defaults, and restores previous when destroyed.
* The idea here is that an app will eventually call enable
* on the model which will sync all of the model state into
* the radio. We tried lazily setting these using the
* set_configuration_without_update function, but there are
* still various UI components (mainly Waterfall) that need
* the radio set in order to load properly.
/* Reinitialized model with defaults so each app can start
* with a clean, default model instance. We tried lazily
* setting these using the set_configuration_without_update
* function, but there are still various UI components
* (e.g. Waterfall) that need the radio set in order to load.
* NB: This member must be added before SettingsManager. */
template <typename TModel, TModel* model>
class RadioState {
public:
RadioState()
: RadioState(max2837::filter::bandwidth_minimum, 3072000) {
RadioState() {
model->initialize();
}
RadioState(uint32_t new_bandwidth, uint32_t new_sampling_rate)
: prev_bandwidth_{model->baseband_bandwidth()},
prev_sampling_rate_{model->sampling_rate()} {
// Update the new settings in the radio.
RadioState(uint32_t new_bandwidth, uint32_t new_sampling_rate) {
model->initialize();
model->set_sampling_rate(new_sampling_rate);
model->set_baseband_bandwidth(new_bandwidth);
}
~RadioState() {
model->set_configuration_without_update(
prev_bandwidth_, prev_sampling_rate_);
// NB: only enabled for RX model.
template <
typename U = TModel,
typename Mode = std::enable_if_t<sizeof(typename U::Mode), typename U::Mode> >
RadioState(Mode new_mode) {
model->initialize();
model->settings().mode = new_mode;
}
private:
const uint32_t prev_bandwidth_;
const uint32_t prev_sampling_rate_;
// NB: only enabled for RX model.
template <
typename U = TModel,
typename Mode = std::enable_if_t<sizeof(typename U::Mode), typename U::Mode> >
RadioState(
uint32_t new_bandwidth,
uint32_t new_sampling_rate,
Mode new_mode) {
model->initialize();
model->set_sampling_rate(new_sampling_rate);
model->set_baseband_bandwidth(new_bandwidth);
model->settings().mode = new_mode;
}
};
using RxRadioState = RadioState<ReceiverModel, &portapack::receiver_model>;
+154 -156
View File
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2023 Kyle Reed
*
* This file is part of PortaPack.
*
@@ -39,9 +40,9 @@ using namespace portapack;
namespace {
static constexpr std::array<baseband::AMConfig, 5> am_configs{{
// we config here all the non COMMON parameters to each AM modulation type in RX.
{taps_9k0_decim_2, taps_9k0_dsb_channel, AMConfigureMessage::Modulation::DSB}, // AM DSB-C BW 9khz (+-4k5) commercial EU bandwidth .
{taps_6k0_decim_2, taps_6k0_dsb_channel, AMConfigureMessage::Modulation::DSB}, // AM DSB-C BW 6khz (+-3k0) narrow AM , ham equipments.
// we config here all the non COMMON parameters to each AM modulation type in RX.
{taps_9k0_decim_2, taps_9k0_dsb_channel, AMConfigureMessage::Modulation::DSB}, // AM DSB-C BW 9khz (+-4k5) commercial EU bandwidth .
{taps_6k0_decim_2, taps_6k0_dsb_channel, AMConfigureMessage::Modulation::DSB}, // AM DSB-C BW 6khz (+-3k0) narrow AM , ham equipments.
{taps_6k0_decim_2, taps_2k8_usb_channel, AMConfigureMessage::Modulation::SSB}, // SSB USB BW 2K8 (+ 2K8)
{taps_6k0_decim_2, taps_2k8_lsb_channel, AMConfigureMessage::Modulation::SSB}, // SSB LSB BW 2K8 (- 2K8)
{taps_6k0_decim_2, taps_0k7_usb_channel, AMConfigureMessage::Modulation::SSB}, // SSB USB BW 0K7 (+ 0K7) used to get audio tone from CW Morse, assuming tx shifted +700hz aprox
@@ -70,72 +71,114 @@ void ReceiverModel::set_target_frequency(rf::Frequency f) {
update_tuning_frequency();
}
uint32_t ReceiverModel::baseband_bandwidth() const {
return settings_.baseband_bandwidth;
}
void ReceiverModel::set_baseband_bandwidth(uint32_t v) {
settings_.baseband_bandwidth = v;
update_baseband_bandwidth();
}
uint32_t ReceiverModel::sampling_rate() const {
return settings_.sampling_rate;
}
void ReceiverModel::set_sampling_rate(uint32_t v) {
settings_.sampling_rate = v;
update_sampling_rate();
}
rf::Frequency ReceiverModel::frequency_step() const {
return frequency_step_;
return settings_.frequency_step;
}
void ReceiverModel::set_frequency_step(rf::Frequency f) {
frequency_step_ = f;
settings_.frequency_step = f;
}
uint8_t ReceiverModel::lna() const {
return settings_.lna_gain_db;
}
void ReceiverModel::set_lna(uint8_t v_db) {
settings_.lna_gain_db = v_db;
update_lna();
}
uint8_t ReceiverModel::vga() const {
return settings_.vga_gain_db;
}
void ReceiverModel::set_vga(uint8_t v_db) {
settings_.vga_gain_db = v_db;
update_vga();
}
bool ReceiverModel::rf_amp() const {
return settings_.rf_amp;
}
void ReceiverModel::set_rf_amp(bool enabled) {
settings_.rf_amp = enabled;
update_rf_amp();
}
ReceiverModel::Mode ReceiverModel::modulation() const {
return settings_.mode;
}
void ReceiverModel::set_modulation(Mode v) {
settings_.mode = v;
update_modulation();
}
uint8_t ReceiverModel::am_configuration() const {
return settings_.am_config_index;
}
void ReceiverModel::set_am_configuration(uint8_t n) {
if (n < am_configs.size()) {
settings_.am_config_index = n;
update_modulation();
}
}
uint8_t ReceiverModel::nbfm_configuration() const {
return settings_.nbfm_config_index;
}
void ReceiverModel::set_nbfm_configuration(uint8_t n) {
if (n < nbfm_configs.size()) {
settings_.nbfm_config_index = n;
update_modulation();
}
}
uint8_t ReceiverModel::wfm_configuration() const {
return settings_.wfm_config_index;
}
void ReceiverModel::set_wfm_configuration(uint8_t n) {
if (n < wfm_configs.size()) {
settings_.wfm_config_index = n;
update_modulation();
}
}
uint8_t ReceiverModel::squelch_level() const {
return settings_.squelch_level;
}
void ReceiverModel::set_squelch_level(uint8_t v) {
settings_.squelch_level = v;
update_modulation();
}
void ReceiverModel::set_antenna_bias() {
update_antenna_bias();
}
bool ReceiverModel::rf_amp() const {
return rf_amp_;
}
void ReceiverModel::set_rf_amp(bool enabled) {
rf_amp_ = enabled;
update_rf_amp();
}
int32_t ReceiverModel::lna() const {
return lna_gain_db_;
}
void ReceiverModel::set_lna(int32_t v_db) {
lna_gain_db_ = v_db;
update_lna();
}
uint32_t ReceiverModel::baseband_bandwidth() const {
return baseband_bandwidth_;
}
void ReceiverModel::set_baseband_bandwidth(uint32_t v) {
baseband_bandwidth_ = v;
update_baseband_bandwidth();
}
int32_t ReceiverModel::vga() const {
return vga_gain_db_;
}
void ReceiverModel::set_vga(int32_t v_db) {
vga_gain_db_ = v_db;
update_vga();
}
uint32_t ReceiverModel::sampling_rate() const {
return sampling_rate_;
}
void ReceiverModel::set_sampling_rate(uint32_t v) {
sampling_rate_ = v;
update_sampling_rate();
}
ReceiverModel::Mode ReceiverModel::modulation() const {
return mode_;
}
void ReceiverModel::set_modulation(const Mode v) {
mode_ = v;
update_modulation();
}
volume_t ReceiverModel::headphone_volume() const {
return persistent_memory::headphone_volume();
}
@@ -157,15 +200,6 @@ void ReceiverModel::set_normalized_headphone_volume(uint8_t v) {
set_headphone_volume(new_volume);
}
uint8_t ReceiverModel::squelch_level() const {
return squelch_level_;
}
void ReceiverModel::set_squelch_level(uint8_t v) {
squelch_level_ = v;
update_modulation();
}
void ReceiverModel::enable() {
enabled_ = true;
radio::set_direction(rf::Direction::Receive);
@@ -193,6 +227,35 @@ void ReceiverModel::disable() {
led_rx.off();
}
void ReceiverModel::initialize() {
settings_ = settings_t{};
}
void ReceiverModel::set_configuration_without_update(
Mode new_mode,
rf::Frequency new_frequency_step,
size_t new_am_config_index,
size_t new_nbfm_config_index,
size_t new_wfm_config_index,
uint8_t new_squelch_level) {
settings_.mode = new_mode;
settings_.frequency_step = new_frequency_step;
settings_.am_config_index = new_am_config_index;
settings_.nbfm_config_index = new_nbfm_config_index;
settings_.wfm_config_index = new_wfm_config_index;
settings_.squelch_level = new_squelch_level;
}
void ReceiverModel::configure_from_app_settings(
const app_settings::AppSettings& settings) {
settings_.baseband_bandwidth = settings.baseband_bandwidth;
settings_.sampling_rate = settings.sampling_rate;
settings_.lna_gain_db = settings.lna;
settings_.vga_gain_db = settings.vga;
settings_.rf_amp = settings.rx_amp;
settings_.squelch_level = settings.squelch;
}
int32_t ReceiverModel::tuning_offset() {
if ((modulation() == Mode::SpectrumAnalysis)) {
return 0;
@@ -206,78 +269,8 @@ void ReceiverModel::update_tuning_frequency() {
radio::set_tuning_frequency(target_frequency() + tuning_offset());
}
void ReceiverModel::update_antenna_bias() {
if (enabled_)
radio::set_antenna_bias(portapack::get_antenna_bias());
}
void ReceiverModel::update_rf_amp() {
radio::set_rf_amp(rf_amp_);
}
void ReceiverModel::update_lna() {
radio::set_lna_gain(lna_gain_db_);
}
void ReceiverModel::update_baseband_bandwidth() {
radio::set_baseband_filter_bandwidth(baseband_bandwidth_);
}
void ReceiverModel::update_vga() {
radio::set_vga_gain(vga_gain_db_);
}
void ReceiverModel::set_am_configuration(const size_t n) {
if (n < am_configs.size()) {
am_config_index = n;
update_modulation();
}
}
void ReceiverModel::set_nbfm_configuration(const size_t n) {
if (n < nbfm_configs.size()) {
nbfm_config_index = n;
update_modulation();
}
}
void ReceiverModel::set_wfm_configuration(const size_t n) {
if (n < wfm_configs.size()) {
wfm_config_index = n;
update_modulation();
}
}
void ReceiverModel::set_configuration_without_update(
uint32_t baseband_bandwidth,
uint32_t sampling_rate) {
baseband_bandwidth_ = baseband_bandwidth;
sampling_rate_ = sampling_rate;
}
void ReceiverModel::set_configuration_without_update(
Mode new_mode,
rf::Frequency new_frequency_step,
size_t new_am_config_index,
size_t new_nbfm_config_index,
size_t new_wfm_config_index,
uint8_t new_squelch_level) {
mode_ = new_mode;
frequency_step_ = new_frequency_step;
am_config_index = new_am_config_index;
nbfm_config_index = new_nbfm_config_index;
wfm_config_index = new_wfm_config_index;
squelch_level_ = new_squelch_level;
}
void ReceiverModel::configure_from_app_settings(
const app_settings::AppSettings& settings) {
baseband_bandwidth_ = settings.baseband_bandwidth;
sampling_rate_ = settings.sampling_rate;
lna_gain_db_ = settings.lna;
vga_gain_db_ = settings.vga;
rf_amp_ = settings.rx_amp;
squelch_level_ = settings.squelch;
radio::set_baseband_filter_bandwidth(baseband_bandwidth());
}
void ReceiverModel::update_sampling_rate() {
@@ -290,8 +283,16 @@ void ReceiverModel::update_sampling_rate() {
update_tuning_frequency();
}
void ReceiverModel::update_headphone_volume() {
audio::headphone::set_volume(headphone_volume());
void ReceiverModel::update_lna() {
radio::set_lna_gain(lna());
}
void ReceiverModel::update_vga() {
radio::set_vga_gain(vga());
}
void ReceiverModel::update_rf_amp() {
radio::set_rf_amp(rf_amp());
}
void ReceiverModel::update_modulation() {
@@ -315,26 +316,23 @@ void ReceiverModel::update_modulation() {
}
}
size_t ReceiverModel::am_configuration() const {
return am_config_index;
}
void ReceiverModel::update_am_configuration() {
am_configs[am_config_index].apply();
}
size_t ReceiverModel::nbfm_configuration() const {
return nbfm_config_index;
am_configs[am_configuration()].apply();
}
void ReceiverModel::update_nbfm_configuration() {
nbfm_configs[nbfm_config_index].apply(squelch_level_);
}
size_t ReceiverModel::wfm_configuration() const {
return wfm_config_index;
nbfm_configs[nbfm_configuration()].apply(squelch_level());
}
void ReceiverModel::update_wfm_configuration() {
wfm_configs[wfm_config_index].apply();
wfm_configs[wfm_configuration()].apply();
}
void ReceiverModel::update_antenna_bias() {
if (enabled_)
radio::set_antenna_bias(portapack::get_antenna_bias());
}
void ReceiverModel::update_headphone_volume() {
audio::headphone::set_volume(headphone_volume());
}
+57 -49
View File
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2023 Kyle Reed
*
* This file is part of PortaPack.
*
@@ -26,16 +27,16 @@
#include <cstddef>
#include "app_settings.hpp"
#include "max283x.hpp"
#include "message.hpp"
#include "rf_path.hpp"
#include "max283x.hpp"
#include "volume.hpp"
// TODO: consider a base class for ReceiverModel & TransmitterModel.
// There are multiple values that are actually shared by both.
class ReceiverModel {
public:
enum class Mode {
enum class Mode : uint8_t {
AMAudio = 0,
NarrowbandFMAudio = 1,
WidebandFMAudio = 2,
@@ -43,33 +44,59 @@ class ReceiverModel {
Capture = 4
};
struct settings_t {
uint32_t baseband_bandwidth = max283x::filter::bandwidth_minimum;
uint32_t sampling_rate = 3'072'000;
rf::Frequency frequency_step = 25'000;
uint8_t lna_gain_db = 32;
uint8_t vga_gain_db = 32;
bool rf_amp = false;
Mode mode = Mode::NarrowbandFMAudio;
uint8_t am_config_index = 0;
uint8_t nbfm_config_index = 0;
uint8_t wfm_config_index = 0;
uint8_t squelch_level = 80;
};
/* The frequency to receive (no offset). */
rf::Frequency target_frequency() const;
void set_target_frequency(rf::Frequency f);
rf::Frequency frequency_step() const;
void set_frequency_step(rf::Frequency f);
void set_antenna_bias();
bool rf_amp() const;
void set_rf_amp(bool enabled);
int32_t lna() const;
void set_lna(int32_t v_db);
uint32_t baseband_bandwidth() const;
void set_baseband_bandwidth(uint32_t v);
int32_t vga() const;
void set_vga(int32_t v_db);
uint32_t sampling_rate() const;
void set_sampling_rate(uint32_t v);
rf::Frequency frequency_step() const;
void set_frequency_step(rf::Frequency f);
uint8_t lna() const;
void set_lna(uint8_t v_db);
uint8_t vga() const;
void set_vga(uint8_t v_db);
bool rf_amp() const;
void set_rf_amp(bool enabled);
Mode modulation() const;
void set_modulation(Mode v);
uint8_t am_configuration() const;
void set_am_configuration(uint8_t n);
uint8_t nbfm_configuration() const;
void set_nbfm_configuration(uint8_t n);
uint8_t wfm_configuration() const;
void set_wfm_configuration(uint8_t n);
uint8_t squelch_level() const;
void set_squelch_level(uint8_t v);
void set_antenna_bias();
volume_t headphone_volume() const;
void set_headphone_volume(volume_t v);
@@ -77,25 +104,11 @@ class ReceiverModel {
uint8_t normalized_headphone_volume() const;
void set_normalized_headphone_volume(uint8_t v);
uint8_t squelch_level() const;
void set_squelch_level(uint8_t v);
void enable();
void disable();
size_t am_configuration() const;
void set_am_configuration(const size_t n);
size_t nbfm_configuration() const;
void set_nbfm_configuration(const size_t n);
size_t wfm_configuration() const;
void set_wfm_configuration(const size_t n);
/* Sets the model values without updating the radio. */
void set_configuration_without_update(
uint32_t baseband_bandwidth,
uint32_t sampling_rate);
/* Resets some members back to default. */
void initialize();
void set_configuration_without_update(
Mode new_mode,
@@ -107,35 +120,30 @@ class ReceiverModel {
void configure_from_app_settings(const app_settings::AppSettings& settings);
/* Get access to the underlying settings to allow
* values to be set directly without calling update. */
settings_t& settings() { return settings_; }
private:
rf::Frequency frequency_step_{25000};
bool enabled_{false};
bool rf_amp_{false};
int32_t lna_gain_db_{32};
uint32_t baseband_bandwidth_{max283x::filter::bandwidth_minimum};
int32_t vga_gain_db_{32};
Mode mode_{Mode::NarrowbandFMAudio};
uint32_t sampling_rate_{3072000};
size_t am_config_index = 0;
size_t nbfm_config_index = 0;
size_t wfm_config_index = 0;
uint8_t squelch_level_{80};
settings_t settings_{};
bool enabled_ = false;
int32_t tuning_offset();
void update_tuning_frequency();
void update_antenna_bias();
void update_rf_amp();
void update_lna();
void update_baseband_bandwidth();
void update_vga();
void update_sampling_rate();
void update_headphone_volume();
void update_lna();
void update_vga();
void update_rf_amp();
void update_modulation();
void update_am_configuration();
void update_nbfm_configuration();
void update_wfm_configuration();
void update_antenna_bias();
void update_headphone_volume();
};
#endif /*__RECEIVER_MODEL_H__*/
+33 -8
View File
@@ -25,16 +25,15 @@
* and fills it backwards towards the front.
* The return value 'q' is a pointer to the start.
* TODO: use std::array for all this. */
template <typename Int>
static char* to_string_dec_uint_internal(
char* p,
uint32_t n) {
Int n) {
*p = 0;
auto q = p;
do {
const uint32_t d = n % 10;
const char c = d + 48;
*(--q) = c;
*(--q) = n % 10 + '0';
n /= 10;
} while (n != 0);
@@ -60,13 +59,36 @@ static char* to_string_dec_uint_pad_internal(
return q;
}
char* to_string_dec_uint(const uint32_t n, StringFormatBuffer& buffer, size_t& length) {
template <typename Int>
char* to_string_dec_uint_internal(Int n, StringFormatBuffer& buffer, size_t& length) {
auto end = &buffer.back();
auto start = to_string_dec_uint_internal(end, n);
length = end - start;
return start;
}
char* to_string_dec_uint(uint32_t n, StringFormatBuffer& buffer, size_t& length) {
return to_string_dec_uint_internal(n, buffer, length);
}
char* to_string_dec_uint64(uint64_t n, StringFormatBuffer& buffer, size_t& length) {
return to_string_dec_uint_internal(n, buffer, length);
}
std::string to_string_dec_uint(uint32_t n) {
StringFormatBuffer b{};
size_t len{};
char* str = to_string_dec_uint(n, b, len);
return std::string(str, len);
}
std::string to_string_dec_uint64(uint64_t n) {
StringFormatBuffer b{};
size_t len{};
char* str = to_string_dec_uint(n, b, len);
return std::string(str, len);
}
std::string to_string_bin(
const uint32_t n,
const uint8_t l) {
@@ -177,7 +199,7 @@ static void to_string_hex_internal(char* p, const uint64_t n, const int32_t l) {
std::string to_string_hex(const uint64_t n, int32_t l) {
char p[32];
l = std::min(l, 31L);
l = std::min<int32_t>(l, 31);
to_string_hex_internal(p, n, l - 1);
p[l] = 0;
return p;
@@ -285,8 +307,11 @@ static const char* whitespace_str = " \t\r\n";
std::string trim(std::string_view str) {
auto first = str.find_first_not_of(whitespace_str);
if (first == std::string::npos)
return {};
auto last = str.find_last_not_of(whitespace_str);
return std::string{str.substr(first, last - first)};
return std::string{str.substr(first, last - first + 1)};
}
std::string trimr(std::string_view str) {
@@ -296,4 +321,4 @@ std::string trimr(std::string_view str) {
std::string truncate(std::string_view str, size_t length) {
return std::string{str.length() <= length ? str : str.substr(0, length)};
}
}
+9 -4
View File
@@ -41,20 +41,25 @@ enum TimeFormat {
const char unit_prefix[7]{'n', 'u', 'm', 0, 'k', 'M', 'G'};
using StringFormatBuffer = std::array<char, 16>;
using StringFormatBuffer = std::array<char, 24>;
/* uint32_t conversion without memory allocations. */
char* to_string_dec_uint(const uint32_t n, StringFormatBuffer& buffer, size_t& length);
/* uint conversion without memory allocations. */
char* to_string_dec_uint(uint32_t n, StringFormatBuffer& buffer, size_t& length);
char* to_string_dec_uint64(uint64_t n, StringFormatBuffer& buffer, size_t& length);
std::string to_string_dec_uint(uint32_t n);
std::string to_string_dec_uint64(uint64_t n);
// TODO: Allow l=0 to not fill/justify? Already using this way in ui_spectrum.hpp...
std::string to_string_bin(const uint32_t n, const uint8_t l = 0);
std::string to_string_dec_uint(const uint32_t n, const int32_t l = 0, const char fill = ' ');
std::string to_string_dec_uint(const uint32_t n, const int32_t l, const char fill = ' ');
std::string to_string_dec_int(const int32_t n, const int32_t l = 0, const char fill = 0);
std::string to_string_decimal(float decimal, int8_t precision);
std::string to_string_hex(const uint64_t n, const int32_t l = 0);
std::string to_string_hex_array(uint8_t* const array, const int32_t l = 0);
// NB: These pad-left and don't work correctly for values less than 1M.
std::string to_string_freq(const uint64_t f);
std::string to_string_short_freq(const uint64_t f);
std::string to_string_time_ms(const uint32_t ms);
+137 -82
View File
@@ -25,86 +25,71 @@
namespace tonekey {
// Keep list in ascending order by tone frequency
const tone_key_t tone_keys = {
{"None", 0.0},
{"0 XZ", 67.000},
{"1 WZ", 69.400},
{"2 XA", 71.900},
{"3 WA", 74.400},
{"4 XB", 77.000},
{"5 WB", 79.700},
{"6 YZ", 82.500},
{"7 YA", 85.400},
{"8 YB", 88.500},
{"9 ZZ", 91.500},
{"10 ZA", 94.800},
{"11 ZB", 97.400},
{"12 1Z", 100.000},
{"13 1A", 103.500},
{"14 1B", 107.200},
{"15 2Z", 110.900},
{"16 2A", 114.800},
{"17 2B", 118.800},
{"18 3Z", 123.000},
{"19 3A", 127.300},
{"20 3B", 131.800},
{"21 4Z", 136.500},
{"22 4A", 141.300},
{"23 4B", 146.200},
{"24 5Z", 151.400},
{"25 5A", 156.700},
{"40 --", 159.800},
{"26 5B", 162.200},
{"41 --", 165.500},
{"27 6Z", 167.900},
{"42 --", 171.300},
{"28 6A", 173.800},
{"43 --", 177.300},
{"29 6B", 179.900},
{"44 --", 183.500},
{"30 7Z", 186.200},
{"45 --", 189.900},
{"31 7A", 192.800},
{"46 --", 196.600},
{"47 --", 199.500},
{"32 M1", 203.500},
{"48 8Z", 206.500},
{"33 M2", 210.700},
{"34 M3", 218.100},
{"35 M4", 225.700},
{"49 9Z", 229.100},
{"36 --", 233.600},
{"37 --", 241.800},
{"38 --", 250.300},
{"50 0Z", 254.100},
{"Axient 28kHz", 28000.0},
{"Senn. 32.768k", 32768.0},
{"Senn. 32.000k", 32000.0},
{"Sony 32.382k", 32382.0},
{"Shure 19kHz", 19000.0}};
{"None", F2Ix100(0.0)},
{"1 XZ", F2Ix100(67.0)},
{"39 WZ", F2Ix100(69.3)},
{"2 XA", F2Ix100(71.9)},
{"3 WA", F2Ix100(74.4)},
{"4 XB", F2Ix100(77.0)},
{"5 WB", F2Ix100(79.7)},
{"6 YZ", F2Ix100(82.5)},
{"7 YA", F2Ix100(85.4)},
{"8 YB", F2Ix100(88.5)},
{"9 ZZ", F2Ix100(91.5)},
{"10 ZA", F2Ix100(94.8)},
{"11 ZB", F2Ix100(97.4)},
{"12 1Z", F2Ix100(100.0)},
{"13 1A", F2Ix100(103.5)},
{"14 1B", F2Ix100(107.2)},
{"15 2Z", F2Ix100(110.9)},
{"16 2A", F2Ix100(114.8)},
{"17 2B", F2Ix100(118.8)},
{"18 3Z", F2Ix100(123.0)},
{"19 3A", F2Ix100(127.3)},
{"20 3B", F2Ix100(131.8)},
{"21 4Z", F2Ix100(136.5)},
{"22 4A", F2Ix100(141.3)},
{"23 4B", F2Ix100(146.2)},
{"24 5Z", F2Ix100(151.4)},
{"25 5A", F2Ix100(156.7)},
{"40 --", F2Ix100(159.8)},
{"26 5B", F2Ix100(162.2)},
{"41 --", F2Ix100(165.5)},
{"27 6Z", F2Ix100(167.9)},
{"42 --", F2Ix100(171.3)},
{"28 6A", F2Ix100(173.8)},
{"43 --", F2Ix100(177.3)},
{"29 6B", F2Ix100(179.9)},
{"44 --", F2Ix100(183.5)},
{"30 7Z", F2Ix100(186.2)},
{"45 --", F2Ix100(189.9)},
{"31 7A", F2Ix100(192.8)},
{"46 --", F2Ix100(196.6)},
{"47 --", F2Ix100(199.5)},
{"32 M1", F2Ix100(203.5)},
{"48 8Z", F2Ix100(206.5)},
{"33 M2", F2Ix100(210.7)},
{"34 M3", F2Ix100(218.1)},
{"35 M4", F2Ix100(225.7)},
{"49 9Z", F2Ix100(229.1)},
{"36 M5", F2Ix100(233.6)},
{"37 M6", F2Ix100(241.8)},
{"38 M7", F2Ix100(250.3)},
{"50 0Z", F2Ix100(254.1)},
{"Shure 19kHz", F2Ix100(19000.0)},
{"Axient 28kHz", F2Ix100(28000.0)},
{"Senn. 32.000k", F2Ix100(32000.0)},
{"Sony 32.382k", F2Ix100(32382.0)},
{"Senn. 32.768k", F2Ix100(32768.0)}};
void tone_keys_populate(OptionsField& field) {
using option_t = std::pair<std::string, int32_t>;
using options_t = std::vector<option_t>;
options_t tone_key_options;
std::string tone_name;
for (size_t c = 0; c < tone_keys.size(); c++) {
if (c && c < 51) {
auto f = tone_keys[c].second;
tone_name = "CTCSS " + tone_keys[c].first + " " + to_string_dec_uint(f) + "." + to_string_dec_uint((uint32_t)(f * 10) % 10);
} else {
tone_name = tone_keys[c].first;
}
tone_key_options.emplace_back(tone_name, c);
}
field.set_options(tone_key_options);
std::string fx100_string(uint32_t f) {
return to_string_dec_uint(f / 100) + "." + to_string_dec_uint((f / 10) % 10);
}
float tone_key_frequency(const tone_index index) {
return tone_keys[index].second;
float tone_key_frequency(tone_index index) {
return float(tone_keys[index].second) / 100.0;
}
std::string tone_key_string(tone_index index) {
@@ -113,19 +98,89 @@ std::string tone_key_string(tone_index index) {
return tone_keys[index].first;
}
// Return string showing frequency only from specific table index
std::string tone_key_value_string(tone_index index) {
if (index < 0 || (unsigned)index >= tone_keys.size())
return std::string("");
return fx100_string(tone_keys[index].second);
}
// Return variable-length string showing CTCSS tone from tone frequency
// Value is in 0.01 Hz units
std::string tone_key_string_by_value(uint32_t value, size_t max_length) {
static uint8_t tone_display_toggle{0};
static uint32_t last_value;
tone_index idx;
std::string freq_str;
// If >10Hz difference between consecutive samples, it's probably noise, so ignore
if (abs(value - last_value) > 10 * 100) {
last_value = value;
tone_display_toggle = 0;
return " ";
}
last_value = value;
// Only display 1/10 Hz accuracy if <1000 Hz; max 5 characters
if (value < 1000 * 100)
freq_str = "T:" + fx100_string(value);
else
freq_str = "T:" + to_string_dec_uint(value / 100);
// Check field length is enough for character counts in the string below
if (max_length >= 7 + 2 + 5) {
idx = tone_key_index_by_value(value);
if (idx != -1)
return freq_str + " #" + tone_key_string(idx);
} else {
// Not enough space; toggle between display of tone received and tone code #
if (tone_display_toggle++ >= TONE_DISPLAY_TOGGLE_COUNTER) {
if (tone_display_toggle >= TONE_DISPLAY_TOGGLE_COUNTER * 2) tone_display_toggle = 0;
// Look for a match in the table (otherwise just display frequency)
idx = tone_key_index_by_value(value);
if (idx != -1)
return "T:" + tone_key_string(idx);
}
}
return freq_str;
}
// Search tone_key table for tone frequency value
// Value is in 0.01 Hz units
tone_index tone_key_index_by_value(uint32_t value) {
uint32_t diff;
uint32_t min_diff{UINT32_MAX};
tone_index min_idx{-1};
tone_index idx;
// Find nearest match
for (idx = 0; idx < (tone_index)tone_keys.size(); idx++) {
diff = abs(value - tone_keys[idx].second);
if (diff < min_diff) {
min_idx = idx;
min_diff = diff;
} else {
// list is sorted in frequency order; if diff is getting larger than we've passed it
break;
}
}
// Arbitrary confidence threshold
if (min_diff < TONE_FREQ_TOLERANCE_CENTIHZ)
return min_idx;
else
return -1;
}
tone_index tone_key_index_by_string(char* str) {
if (!str)
return -1;
for (tone_index index = 0; (unsigned)index < tone_keys.size(); index++) {
if (tone_keys[index].first.compare(str) >= 0)
if (tone_keys[index].first.compare(str) >= 0) // TODO: why >=?
return index;
}
return -1;
}
/* tone_index tone_key_index_by_value( int32_t freq )
{
return -1 ;
} */
} // namespace tonekey
+14 -11
View File
@@ -23,25 +23,28 @@
#ifndef __TONE_KEY_H_
#define __TONE_KEY_H_
#include "ui.hpp"
#include "ui_widget.hpp"
using namespace ui;
#include <cstdint>
#include <string>
#include <vector>
namespace tonekey {
typedef int16_t tone_index;
#define TONE_FREQ_TOLERANCE_CENTIHZ (4 * 100)
#define TONE_DISPLAY_TOGGLE_COUNTER 3
#define F2Ix100(x) (int32_t)(x * 100.0)
using tone_key_t = std::vector<std::pair<std::string, float>>;
using tone_index = int32_t;
using tone_key_t = std::vector<std::pair<std::string, uint32_t>>;
extern const tone_key_t tone_keys;
void tone_keys_populate(OptionsField& field);
float tone_key_frequency(const tone_index index);
float tone_key_frequency(tone_index index);
std::string tone_key_string(const tone_index index);
tone_index tone_key_index_by_string(char* str);
// tone_index tone_key_index_by_value( int32_t freq );
std::string fx100_string(uint32_t f);
std::string tone_key_string(tone_index index);
std::string tone_key_value_string(tone_index index);
std::string tone_key_string_by_value(uint32_t value, size_t max_length);
tone_index tone_key_index_by_value(uint32_t value);
} // namespace tonekey
+57 -93
View File
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
* Copyright (C) 2023 Kyle Reed
*
* This file is part of PortaPack.
*
@@ -22,15 +23,13 @@
#include "transmitter_model.hpp"
#include "audio.hpp"
#include "baseband_api.hpp"
#include "portapack_persistent_memory.hpp"
#include "event_m0.hpp"
#include "hackrf_gpio.hpp"
#include "portapack.hpp"
#include "rtc_time.hpp"
#include "event_m0.hpp"
#include "portapack_persistent_memory.hpp"
#include "radio.hpp"
#include "audio.hpp"
using namespace hackrf::one;
using namespace portapack;
@@ -44,75 +43,52 @@ void TransmitterModel::set_target_frequency(rf::Frequency f) {
update_tuning_frequency();
}
void TransmitterModel::set_antenna_bias() {
update_antenna_bias();
}
bool TransmitterModel::rf_amp() const {
return rf_amp_;
}
void TransmitterModel::set_rf_amp(bool enabled) {
rf_amp_ = enabled;
update_rf_amp();
}
int32_t TransmitterModel::lna() const {
return lna_gain_db_;
}
void TransmitterModel::set_lna(int32_t v_db) {
lna_gain_db_ = v_db;
update_lna();
}
uint32_t TransmitterModel::baseband_bandwidth() const {
return baseband_bandwidth_;
return settings_.baseband_bandwidth;
}
void TransmitterModel::set_baseband_bandwidth(uint32_t v) {
baseband_bandwidth_ = v;
settings_.baseband_bandwidth = v;
update_baseband_bandwidth();
}
int32_t TransmitterModel::vga() const {
return vga_gain_db_;
}
void TransmitterModel::set_vga(int32_t v_db) {
vga_gain_db_ = v_db;
update_vga();
}
uint32_t TransmitterModel::channel_bandwidth() const {
return channel_bandwidth_;
}
void TransmitterModel::set_channel_bandwidth(uint32_t v) {
channel_bandwidth_ = v;
}
uint32_t TransmitterModel::sampling_rate() const {
return sampling_rate_;
return settings_.sampling_rate;
}
void TransmitterModel::set_sampling_rate(uint32_t v) {
sampling_rate_ = v;
settings_.sampling_rate = v;
update_sampling_rate();
}
int32_t TransmitterModel::tx_gain() const {
return tx_gain_db_;
uint32_t TransmitterModel::channel_bandwidth() const {
return settings_.channel_bandwidth;
}
void TransmitterModel::set_tx_gain(int32_t v_db) {
tx_gain_db_ = v_db;
void TransmitterModel::set_channel_bandwidth(uint32_t v) {
settings_.channel_bandwidth = v;
}
uint8_t TransmitterModel::tx_gain() const {
return settings_.tx_gain_db;
}
void TransmitterModel::set_tx_gain(uint8_t v_db) {
settings_.tx_gain_db = v_db;
update_tx_gain();
}
void TransmitterModel::on_tick_second() {
if (portapack::persistent_memory::stealth_mode())
led_tx.toggle();
bool TransmitterModel::rf_amp() const {
return settings_.rf_amp;
}
void TransmitterModel::set_rf_amp(bool enabled) {
settings_.rf_amp = enabled;
update_rf_amp();
}
void TransmitterModel::set_antenna_bias() {
update_antenna_bias();
}
void TransmitterModel::enable() {
@@ -121,8 +97,6 @@ void TransmitterModel::enable() {
update_tuning_frequency();
update_antenna_bias();
update_rf_amp();
update_lna();
update_vga();
update_baseband_bandwidth();
update_sampling_rate();
update_tx_gain();
@@ -147,53 +121,25 @@ void TransmitterModel::disable() {
led_tx.off();
}
void TransmitterModel::set_configuration_without_update(
uint32_t baseband_bandwidth,
uint32_t sampling_rate) {
baseband_bandwidth_ = baseband_bandwidth;
sampling_rate_ = sampling_rate;
void TransmitterModel::initialize() {
settings_ = settings_t{};
}
void TransmitterModel::configure_from_app_settings(
const app_settings::AppSettings& settings) {
baseband_bandwidth_ = settings.baseband_bandwidth;
channel_bandwidth_ = settings.channel_bandwidth;
tx_gain_db_ = settings.tx_gain;
rf_amp_ = settings.tx_amp;
// TODO: Do these make sense for TX?
lna_gain_db_ = settings.lna;
vga_gain_db_ = settings.vga;
sampling_rate_ = settings.sampling_rate;
settings_.baseband_bandwidth = settings.baseband_bandwidth;
settings_.sampling_rate = settings.sampling_rate;
settings_.channel_bandwidth = settings.channel_bandwidth;
settings_.tx_gain_db = settings.tx_gain;
settings_.rf_amp = settings.tx_amp;
}
void TransmitterModel::update_tuning_frequency() {
radio::set_tuning_frequency(target_frequency());
}
void TransmitterModel::update_antenna_bias() {
if (enabled_)
radio::set_antenna_bias(portapack::get_antenna_bias());
}
void TransmitterModel::update_rf_amp() {
radio::set_rf_amp(rf_amp_);
}
void TransmitterModel::update_lna() {
radio::set_lna_gain(lna_gain_db_);
}
void TransmitterModel::update_baseband_bandwidth() {
radio::set_baseband_filter_bandwidth(baseband_bandwidth_);
}
void TransmitterModel::update_vga() {
radio::set_vga_gain(vga_gain_db_);
}
void TransmitterModel::update_tx_gain() {
radio::set_tx_gain(tx_gain_db_);
radio::set_baseband_filter_bandwidth(baseband_bandwidth());
}
void TransmitterModel::update_sampling_rate() {
@@ -206,3 +152,21 @@ void TransmitterModel::update_sampling_rate() {
radio::set_baseband_rate(sampling_rate());
update_tuning_frequency();
}
void TransmitterModel::update_tx_gain() {
radio::set_tx_gain(tx_gain());
}
void TransmitterModel::update_rf_amp() {
radio::set_rf_amp(rf_amp());
}
void TransmitterModel::update_antenna_bias() {
if (enabled_)
radio::set_antenna_bias(portapack::get_antenna_bias());
}
void TransmitterModel::on_tick_second() {
if (portapack::persistent_memory::stealth_mode())
led_tx.toggle();
}
+31 -39
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.
*
@@ -26,77 +27,68 @@
#include <cstdint>
#include <cstddef>
#include "receiver_model.hpp"
#include "app_settings.hpp"
#include "message.hpp"
#include "rf_path.hpp"
#include "max2837.hpp"
#include "volume.hpp"
#include "message.hpp"
#include "receiver_model.hpp"
#include "rf_path.hpp"
#include "signal.hpp"
class TransmitterModel {
public:
struct settings_t {
uint32_t baseband_bandwidth = max283x::filter::bandwidth_minimum;
uint32_t sampling_rate = 3'072'000;
uint32_t channel_bandwidth = 1;
/* 35 should give approx 1m transmission range. */
uint8_t tx_gain_db = 35;
bool rf_amp = false;
};
/* The frequency to transmit on. */
rf::Frequency target_frequency() const;
void set_target_frequency(rf::Frequency f);
void set_antenna_bias();
bool rf_amp() const;
void set_rf_amp(bool enabled);
// TODO: does this make sense on TX?
int32_t lna() const;
void set_lna(int32_t v_db);
uint32_t baseband_bandwidth() const;
void set_baseband_bandwidth(uint32_t v);
// TODO: does this make sense on TX?
int32_t vga() const;
void set_vga(int32_t v_db);
int32_t tx_gain() const;
void set_tx_gain(int32_t v_db);
// TODO: Doesn't actually affect radio.
uint32_t channel_bandwidth() const;
void set_channel_bandwidth(uint32_t v);
// TODO: does this make sense on TX?
uint32_t sampling_rate() const;
void set_sampling_rate(uint32_t v);
uint8_t tx_gain() const;
void set_tx_gain(uint8_t v_db);
bool rf_amp() const;
void set_rf_amp(bool enabled);
void set_antenna_bias();
void enable();
void disable();
/* Sets the model values without updating the radio. */
void set_configuration_without_update(
uint32_t baseband_bandwidth,
uint32_t sampling_rate);
void initialize();
void configure_from_app_settings(const app_settings::AppSettings& settings);
/* Get access to the underlying settings to allow
* values to be set directly without calling update. */
settings_t& settings() { return settings_; }
private:
bool enabled_{false};
bool rf_amp_{false};
int32_t lna_gain_db_{0};
uint32_t channel_bandwidth_{1};
uint32_t baseband_bandwidth_{max2837::filter::bandwidth_minimum};
int32_t vga_gain_db_{8};
/* 35 should give approx 1m transmission range. */
int32_t tx_gain_db_{35};
uint32_t sampling_rate_{3072000};
settings_t settings_{};
bool enabled_ = false;
SignalToken signal_token_tick_second{};
void update_tuning_frequency();
void update_antenna_bias();
void update_rf_amp();
void update_lna();
void update_baseband_bandwidth();
void update_vga();
void update_tx_gain();
void update_sampling_rate();
void update_tx_gain();
void update_rf_amp();
void update_antenna_bias();
void on_tick_second();
};
+17 -8
View File
@@ -37,9 +37,13 @@ AlphanumView::AlphanumView(
: TextEntryView(nav, str, max_length) {
size_t n;
add_children({&button_mode,
&text_raw,
&field_raw});
add_children({
&labels,
&field_raw,
&text_raw_to_char,
&button_delete,
&button_mode,
});
const auto button_fn = [this](Button& button) {
this->on_button(button);
@@ -65,10 +69,19 @@ AlphanumView::AlphanumView(
set_mode(mode + 1);
};
button_delete.on_select = [this](Button&) {
char_delete();
};
field_raw.set_value('0');
field_raw.on_select = [this](NumberField&) {
char_add(field_raw.value());
};
// make text_raw_to_char widget display the char value from field_raw
field_raw.on_change = [this](auto) {
text_raw_to_char.set(std::string{static_cast<char>(field_raw.value())});
};
}
void AlphanumView::set_mode(const uint32_t new_mode) {
@@ -96,11 +109,7 @@ void AlphanumView::set_mode(const uint32_t new_mode) {
void AlphanumView::on_button(Button& button) {
const auto c = button.text()[0];
if (c == '<')
char_delete();
else
char_add(c);
char_add(c);
}
bool AlphanumView::on_encoder(const EncoderEvent delta) {
+20 -15
View File
@@ -43,14 +43,14 @@ class AlphanumView : public TextEntryView {
bool on_encoder(const EncoderEvent delta) override;
private:
const char* const keys_upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ, .<";
const char* const keys_lower = "abcdefghijklmnopqrstuvwxyz, .<";
const char* const keys_digit = "0123456789!\"#'()*+-/:;=>?@[\\]<";
const char* const keys_upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ, ._";
const char* const keys_lower = "abcdefghijklmnopqrstuvwxyz, ._";
const char* const keys_digit = "0123456789!\"#'()*+-/:;=<>@[\\]?";
const std::pair<std::string, const char*> key_sets[3] = {
{"Upper", keys_upper},
{"Lower", keys_lower},
{"Digit", keys_digit}};
{"ABC", keys_upper},
{"abc", keys_lower},
{"123", keys_digit}};
int16_t focused_button = 0;
uint32_t mode = 0; // Uppercase
@@ -60,13 +60,10 @@ class AlphanumView : public TextEntryView {
std::array<Button, 30> buttons{};
Button button_mode{
{21 * 8, 33 * 8, 8 * 8, 32},
""};
Labels labels{
{{1 * 8, 33 * 8}, "Raw:", Color::light_grey()},
{{1 * 8, 35 * 8}, "AKA:", Color::light_grey()}};
Text text_raw{
{1 * 8, 33 * 8, 4 * 8, 16},
"Raw:"};
NumberField field_raw{
{5 * 8, 33 * 8},
3,
@@ -74,9 +71,17 @@ class AlphanumView : public TextEntryView {
1,
'0'};
Button button_ok{
{10 * 8, 33 * 8, 9 * 8, 32},
"OK"};
Text text_raw_to_char{
{5 * 8, 35 * 8, 4 * 8, 16},
"0"};
Button button_delete{
{9 * 8, 33 * 8, 6 * 8, 32},
"<DEL"};
Button button_mode{
{16 * 8, 33 * 8, 5 * 8, 32},
""};
};
} /* namespace ui */
+3 -4
View File
@@ -40,15 +40,14 @@ class BoundFrequencyField : public FrequencyField {
public:
decltype(FrequencyField::on_change) updated{};
BoundFrequencyField(Point parent_pos, NavigationView& nav, bool update_model = true)
BoundFrequencyField(Point parent_pos, NavigationView& nav)
: FrequencyField(parent_pos) {
// NB: There is no frequency_step on the tx_model.
set_step(portapack::receiver_model.frequency_step());
set_value(model->target_frequency());
on_change = [this, update_model](rf::Frequency f) {
if (update_model)
model->set_target_frequency(f);
on_change = [this](rf::Frequency f) {
model->set_target_frequency(f);
if (updated)
updated(f);
};
+118 -127
View File
@@ -1,5 +1,7 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2023 gullradriel, Nilorea Studio Inc.
* Copyright (C) 2023 Kyle Reed
*
* This file is part of PortaPack.
*
@@ -24,154 +26,143 @@
#include "baseband_api.hpp"
#include "utility.hpp"
#include <algorithm>
namespace ui {
FreqManUIList::FreqManUIList(
Rect parent_rect,
bool instant_exec)
FreqManUIList::FreqManUIList(Rect parent_rect)
: Widget{parent_rect},
instant_exec_{instant_exec} {
visible_lines_{(unsigned)parent_rect.height() / char_height} {
this->set_focusable(true);
}
void FreqManUIList::set_highlighted_index(int index) {
if ((unsigned)(current_index + index) >= freqlist_db.size())
return;
if (index < 0) {
index = 0;
if (current_index > 0)
current_index--;
}
if (index >= freqlist_nb_lines) {
index = freqlist_nb_lines - 1;
if ((unsigned)(current_index + index) < freqlist_db.size())
current_index++;
else
current_index = freqlist_db.size() - freqlist_nb_lines - 1;
}
highlighted_index = index;
}
uint8_t FreqManUIList::get_index() {
return current_index + highlighted_index;
}
void FreqManUIList::paint(Painter& painter) {
freqlist_nb_lines = 0;
const auto r = screen_rect();
uint8_t focused = has_focus();
const Rect r_widget_screen{r.left() + focused, r.top() + focused, r.width() - 2 * focused, r.height() - 2 * +focused};
painter.fill_rectangle(
r_widget_screen,
Color::black());
// only return after clearing the screen so previous entries are not shown anymore
if (freqlist_db.size() == 0)
auto rect = screen_rect();
if (!db_ || db_->empty()) {
auto line_position = rect.location() + Point{7 * 8, 6 * 16};
painter.fill_rectangle(rect, Color::black());
painter.draw_string(line_position, Styles::white, "Empty Category");
return;
// coloration if file is too big
auto text_color = &Styles::white;
if (freqlist_db.size() > FREQMAN_MAX_PER_FILE)
text_color = &Styles::yellow;
uint8_t nb_lines = 0;
for (uint8_t it = current_index; it < freqlist_db.size(); it++) {
uint8_t line_height = (int)nb_lines * char_height;
if (line_height < (r.height() - char_height)) { // line is within the widget
std::string description = freqman_item_string(freqlist_db[it], 30);
if (nb_lines == highlighted_index) {
const Rect r_highlighted_freq{0, r.location().y() + (int)nb_lines * char_height, 240, char_height};
painter.fill_rectangle(
r_highlighted_freq,
Color::white());
painter.draw_string(
{0, r.location().y() + (int)nb_lines * char_height},
Styles::bg_white, description);
} else {
painter.draw_string(
{0, r.location().y() + (int)nb_lines * char_height},
*text_color, description);
}
}
nb_lines++;
} else {
// rect is filled, we can break
break;
}
}
freqlist_nb_lines = nb_lines;
if (has_focus() || highlighted()) {
const Rect r_focus{r.left(), r.top(), r.width(), r.height()};
painter.draw_rectangle(
r_focus,
Color::white());
}
}
// Indicate when a file is too large by drawing in yellow.
auto over_max = db_->entry_count() > freqman_default_max_entries;
auto base_style = over_max ? &Styles::yellow : &Styles::white;
void FreqManUIList::set_db(freqman_db& db) {
freqlist_db = db;
if (db.size() == 0) {
current_index = 0;
highlighted_index = 0;
} else {
if ((unsigned)(current_index + highlighted_index) >= db.size()) {
current_index = db.size() - 1 - highlighted_index;
}
if (current_index < 0) {
current_index = 0;
if (highlighted_index > 0)
highlighted_index--;
// TODO: could minimize redraw/re-read if necessary
// with better change tracking.
for (auto offset = 0u; offset < visible_lines_; ++offset) {
// The whole frame needs to be cleared so every line 'slot'
// is redrawn even when `text` just left empty.
auto text = std::string{};
auto index = start_index_ + offset;
auto line_position = rect.location() + Point{4, 1 + (int)offset * char_height};
// Highlight the selected item.
auto style = (offset == selected_index_) ? &Styles::bg_white : base_style;
if (index < db_->entry_count()) {
auto entry = (*db_)[index];
// db_ is directly backed by a file, so invalid lines cannot be
// pre-filtered. Just show an empty 'slot' in this case.
if (entry.type != freqman_type::Unknown)
text = pretty_string(entry, line_max_length);
}
// Pad right with ' ' so trailing chars are cleaned up.
// draw_glyph has less flicker than fill_rect when drawing.
if (text.length() < line_max_length)
text.resize(line_max_length, ' ');
painter.draw_string(line_position, *style, text);
}
// Draw a bounding rectangle when focused.
painter.draw_rectangle(rect, (has_focus() ? Color::white() : Color::black()));
}
void FreqManUIList::on_focus() {
if (on_highlight)
on_highlight(*this);
set_dirty();
}
void FreqManUIList::on_blur() {
set_dirty();
}
bool FreqManUIList::on_key(const KeyEvent key) {
if (key == KeyEvent::Select) {
if (on_select) {
on_select(*this);
return true;
}
} else {
if (on_dir) {
return on_dir(*this, key);
}
}
return false;
}
if (!db_ || db_->empty())
return false;
bool FreqManUIList::on_touch(const TouchEvent event) {
switch (event.type) {
case TouchEvent::Type::Start:
set_highlighted(true);
set_dirty();
if (on_touch_press) {
on_touch_press(*this);
}
if (on_select && instant_exec_) {
on_select(*this);
}
return true;
case TouchEvent::Type::End:
set_highlighted(false);
set_dirty();
if (on_touch_release) {
on_touch_release(*this);
}
if (on_select && !instant_exec_) {
on_select(*this);
}
return true;
default:
return false;
if (key == KeyEvent::Select && on_select) {
on_select(get_index());
return true;
} else if (key == KeyEvent::Right && on_leave) {
on_leave();
return true;
}
}
bool FreqManUIList::on_encoder(EncoderEvent delta) {
set_highlighted_index((int)highlighted_index + delta);
auto delta = 0;
if (key == KeyEvent::Up && get_index() > 0)
delta = -1;
else if (key == KeyEvent::Down && get_index() < db_->entry_count() - 1)
delta = 1;
else
return false;
adjust_selected_index(delta);
set_dirty();
return true;
}
bool FreqManUIList::on_encoder(EncoderEvent delta) {
if (!db_ || db_->empty())
return false;
adjust_selected_index(delta);
set_dirty();
return true;
}
void FreqManUIList::set_parent_rect(Rect new_parent_rect) {
visible_lines_ = new_parent_rect.height() / char_height;
Widget::set_parent_rect(new_parent_rect);
}
void FreqManUIList::set_index(size_t index) {
start_index_ = 0;
selected_index_ = 0;
adjust_selected_index(index);
}
size_t FreqManUIList::get_index() const {
return start_index_ + selected_index_;
}
void FreqManUIList::set_db(FreqmanDB& db) {
db_ = &db;
start_index_ = 0;
selected_index_ = 0;
set_dirty();
}
void FreqManUIList::adjust_selected_index(int delta) {
int32_t new_index = selected_index_ + delta;
// The selection went off the top of the screen, move up.
if (new_index < 0) {
start_index_ = std::max<int32_t>(start_index_ + new_index, 0);
selected_index_ = 0;
}
// Selection is off the bottom of the screen, move down.
else if (new_index >= (int32_t)visible_lines_) {
start_index_ = std::min<int32_t>(start_index_ + delta, db_->entry_count() - visible_lines_);
selected_index_ = visible_lines_ - 1;
}
// Otherwise, scroll within the screen, but not past the end.
else {
selected_index_ = std::min<int32_t>(new_index, db_->entry_count() - 1);
}
}
} /* namespace ui */
+24 -25
View File
@@ -1,5 +1,7 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2023 gullradriel, Nilorea Studio Inc.
* Copyright (C) 2023 Kyle Reed
*
* This file is part of PortaPack.
*
@@ -23,51 +25,48 @@
#define __UI_FREQLIST_H__
#include "ui.hpp"
#include "ui_widget.hpp"
#include "ui_painter.hpp"
#include "ui_styles.hpp"
#include "ui_widget.hpp"
#include "event_m0.hpp"
#include "message.hpp"
#include "freqman.hpp"
#include "freqman_db.hpp"
#include "message.hpp"
#include <cstdint>
namespace ui {
class FreqManUIList : public Widget {
public:
std::function<void(FreqManUIList&)> on_select{};
std::function<void(FreqManUIList&)> on_touch_release{}; // Executed when releasing touch, after on_select.
std::function<void(FreqManUIList&)> on_touch_press{}; // Executed when touching, before on_select.
std::function<bool(FreqManUIList&, KeyEvent)> on_dir{};
std::function<void(FreqManUIList&)> on_highlight{};
std::function<void(size_t)> on_select{};
std::function<void()> on_leave{}; // Called when Right is pressed.
FreqManUIList(Rect parent_rect, bool instant_exec); // instant_exec: Execute on_select when you touching instead of releasing
FreqManUIList(
Rect parent_rect)
: FreqManUIList{parent_rect, false} {
}
FreqManUIList()
: FreqManUIList{{}, {}} {
}
FreqManUIList(Rect parent_rect);
FreqManUIList(const FreqManUIList& other) = delete;
FreqManUIList& operator=(const FreqManUIList& other) = delete;
void paint(Painter& painter) override;
void on_focus() override;
void on_blur() override;
bool on_key(const KeyEvent key) override;
bool on_touch(const TouchEvent event) override;
bool on_encoder(EncoderEvent delta) override;
void set_parent_rect(Rect new_parent_rect) override;
void set_highlighted_index(int index); // internal set highlighted_index in list handler
uint8_t get_index(); // return highlighed + index
uint8_t set_index(uint8_t index); // try to set current_index + highlighed from index, return capped index
void set_db(freqman_db& db);
void set_index(size_t index);
size_t get_index() const;
void set_db(FreqmanDB& db);
private:
void adjust_selected_index(int index);
static constexpr int8_t char_height = 16;
bool instant_exec_{false};
freqman_db freqlist_db{};
int current_index{0};
int highlighted_index{0};
int freqlist_nb_lines{0};
static constexpr int8_t char_width = 8;
static constexpr int8_t line_max_length = 29;
size_t visible_lines_{0};
FreqmanDB* db_{nullptr};
size_t start_index_{0};
size_t selected_index_{0};
};
} // namespace ui
+121 -30
View File
@@ -31,6 +31,7 @@ using namespace portapack;
#include "string_format.hpp"
#include "complex.hpp"
#include "ui_styles.hpp"
namespace ui {
@@ -80,8 +81,8 @@ GeoPos::GeoPos(
}
void GeoPos::set_read_only(bool v) {
for (auto child : children_)
child->set_focusable(!v);
// only setting altitude to read-only (allow manual panning via lat/lon fields)
field_altitude.set_focusable(!v);
}
// Stupid hack to avoid an event loop
@@ -90,7 +91,15 @@ void GeoPos::set_report_change(bool v) {
}
void GeoPos::focus() {
field_altitude.focus();
if (field_altitude.focusable())
field_altitude.focus();
else
field_lat_degrees.focus();
}
void GeoPos::hide_altitude() {
// Color altitude grey to indicate it's not updated in manual panning mode
field_altitude.set_style(&Styles::grey);
}
void GeoPos::set_altitude(int32_t altitude) {
@@ -132,11 +141,74 @@ int32_t GeoPos::altitude() {
GeoMap::GeoMap(
Rect parent_rect)
: Widget{parent_rect}, markerListLen(0) {
// set_focusable(true);
}
bool GeoMap::on_encoder(const EncoderEvent delta) {
if ((delta > 0) && (map_zoom < 5)) {
map_zoom++;
// Ensure that MOD(240,map_zoom)==0 for the map_zoom_line() function
if (240 % map_zoom != 0) {
map_zoom--;
}
} else if ((delta < 0) && (map_zoom > 1)) {
map_zoom--;
} else {
return false;
}
// Trigger map redraw
markerListUpdated = true;
set_dirty();
return true;
}
void GeoMap::map_zoom_line(ui::Color* buffer) {
if (map_zoom <= 1) {
return;
}
// As long as MOD(width,map_zoom)==0 then we don't need to check buffer overflow case when stretching last pixel;
// For 240 width, than means no check is needed for map_zoom values up to 6
for (int i = (240 / map_zoom) - 1; i >= 0; i--) {
for (int j = 0; j < map_zoom; j++) {
buffer[(i * map_zoom) + j] = buffer[i];
}
}
}
void GeoMap::draw_markers(Painter& painter) {
const auto r = screen_rect();
for (int i = 0; i < markerListLen; ++i) {
GeoMarker& item = markerList[i];
double lat_rad = sin(item.lat * pi / 180);
int x = (map_width * (item.lon + 180) / 360) - x_pos;
int y = (map_height - ((map_world_lon / 2 * log((1 + lat_rad) / (1 - lat_rad))) - map_offset)) - y_pos; // Offset added for the GUI
if (map_zoom != 1) {
x = ((x - (r.width() / 2)) * map_zoom) + (r.width() / 2);
y = ((y - (r.height() / 2)) * map_zoom) + (r.height() / 2);
}
if ((x >= 0) && (x < r.width()) &&
(y > 10) && (y < r.height())) // Dont draw within symbol size of top
{
ui::Point itemPoint(x, y + r.top());
if (y >= 32) { // Dont draw text if it would overlap top
// Text and symbol
draw_marker(painter, itemPoint, item.angle, item.tag, Color::blue(), Color::blue(), Color::magenta());
} else {
// Only symbol
draw_bearing(itemPoint, item.angle, 10, Color::blue());
}
}
}
}
void GeoMap::paint(Painter& painter) {
uint16_t line;
uint16_t line, j;
uint32_t zoom_seek_x, zoom_seek_y;
std::array<ui::Color, 240> map_line_buffer;
const auto r = screen_rect();
@@ -144,39 +216,41 @@ void GeoMap::paint(Painter& painter) {
// or the markers list was updated
int x_diff = abs(x_pos - prev_x_pos);
int y_diff = abs(y_pos - prev_y_pos);
if (markerListUpdated || (x_diff >= 3) || (y_diff >= 3)) {
for (line = 0; line < r.height(); line++) {
map_file.seek(4 + ((x_pos + (map_width * (y_pos + line))) << 1));
map_file.read(map_line_buffer.data(), r.width() << 1);
display.draw_pixels({0, r.top() + line, r.width(), 1}, map_line_buffer);
if (markerListUpdated || (x_diff >= map_zoom * 3) || (y_diff >= map_zoom * 3)) {
if (map_zoom == 1) {
zoom_seek_x = zoom_seek_y = 0;
} else {
zoom_seek_x = (r.width() - (r.width() / map_zoom)) / 2;
zoom_seek_y = (r.height() - (r.height() / map_zoom)) / 2;
}
for (line = 0; line < (r.height() / map_zoom); line++) {
map_file.seek(4 + ((x_pos + zoom_seek_x + (map_width * (y_pos + line + zoom_seek_y))) << 1));
map_file.read(map_line_buffer.data(), (r.width() / map_zoom) << 1);
map_zoom_line(map_line_buffer.data());
for (j = 0; j < map_zoom; j++) {
display.draw_pixels({0, r.top() + (line * map_zoom) + j, r.width(), 1}, map_line_buffer);
}
}
prev_x_pos = x_pos;
prev_y_pos = y_pos;
// Draw crosshairs in center in manual panning mode
if (manual_panning_) {
display.fill_rectangle({r.center() - Point(16, 1), {32, 2}}, Color::red());
display.fill_rectangle({r.center() - Point(1, 16), {2, 32}}, Color::red());
}
// Draw the other markers
for (int i = 0; i < markerListLen; ++i) {
GeoMarker& item = markerList[i];
double lat_rad = sin(item.lat * pi / 180);
int x = (map_width * (item.lon + 180) / 360) - x_pos;
int y = (map_height - ((map_world_lon / 2 * log((1 + lat_rad) / (1 - lat_rad))) - map_offset)) - y_pos; // Offset added for the GUI
if ((x >= 0) && (x < r.width()) &&
(y > 10) && (y < r.height())) // Dont draw within symbol size of top
{
ui::Point itemPoint(x, y + r.top());
if (y >= 32) { // Dont draw text if it would overlap top
// Text and symbol
draw_marker(painter, itemPoint, item.angle, item.tag, Color::blue(), Color::blue(), Color::magenta());
} else {
// Only symbol
draw_bearing(itemPoint, item.angle, 10, Color::blue());
}
}
markerListUpdated = false;
} // Draw the other markers
draw_markers(painter);
markerListUpdated = false;
set_clean();
}
// Draw the marker in the center
draw_marker(painter, r.center(), angle_, tag_, Color::red(), Color::white(), Color::black());
if (!manual_panning_) {
draw_marker(painter, r.center(), angle_, tag_, Color::red(), Color::white(), Color::black());
}
}
bool GeoMap::on_touch(const TouchEvent event) {
@@ -236,6 +310,14 @@ void GeoMap::set_mode(GeoMapMode mode) {
mode_ = mode;
}
void GeoMap::set_manual_panning(bool v) {
manual_panning_ = v;
}
bool GeoMap::manual_panning() {
return manual_panning_;
}
void GeoMap::draw_bearing(const Point origin, const uint16_t angle, uint32_t size, const Color color) {
Point arrow_a, arrow_b, arrow_c;
@@ -311,6 +393,11 @@ void GeoMapView::focus() {
}
void GeoMapView::update_position(float lat, float lon, uint16_t angle, int32_t altitude) {
if (geomap.manual_panning()) {
geomap.set_dirty();
return;
}
lat_ = lat;
lon_ = lon;
altitude_ = altitude;
@@ -342,6 +429,8 @@ void GeoMapView::setup() {
altitude_ = altitude;
lat_ = lat;
lon_ = lon;
geopos.hide_altitude();
geomap.set_manual_panning(true);
geomap.move(lon_, lat_);
geomap.set_dirty();
};
@@ -396,6 +485,7 @@ GeoMapView::GeoMapView(
geomap.set_tag(tag);
geomap.set_angle(angle);
geomap.move(lon_, lat_);
geomap.set_focusable(true);
geopos.set_read_only(true);
}
@@ -425,6 +515,7 @@ GeoMapView::GeoMapView(
geomap.set_mode(mode_);
geomap.move(lon_, lat_);
geomap.set_focusable(true);
button_ok.on_select = [this, on_done, &nav](Button&) {
if (on_done)
+10 -2
View File
@@ -71,6 +71,7 @@ class GeoPos : public View {
void set_lat(float lat);
void set_lon(float lon);
int32_t altitude();
void hide_altitude();
float lat();
float lon();
@@ -83,8 +84,8 @@ class GeoPos : public View {
Labels labels_position{
{{1 * 8, 0 * 16}, "Alt:", Color::light_grey()},
{{1 * 8, 1 * 16}, "Lat: * ' \"", Color::light_grey()}, // No ° symbol in 8x16 font
{{1 * 8, 2 * 16}, "Lon: * ' \"", Color::light_grey()},
{{1 * 8, 1 * 16}, "Lat: \x90 ' \"", Color::light_grey()}, // 0x90 is degree ° symbol in our 8x16 font
{{1 * 8, 2 * 16}, "Lon: \x90 ' \"", Color::light_grey()},
};
NumberField field_altitude{
@@ -157,9 +158,12 @@ class GeoMap : public Widget {
void paint(Painter& painter) override;
bool on_touch(const TouchEvent event) override;
bool on_encoder(const EncoderEvent delta) override;
bool init();
void set_mode(GeoMapMode mode);
void set_manual_panning(bool v);
bool manual_panning();
void move(const float lon, const float lat);
void set_tag(std::string new_tag) {
tag_ = new_tag;
@@ -177,11 +181,15 @@ class GeoMap : public Widget {
private:
void draw_bearing(const Point origin, const uint16_t angle, uint32_t size, const Color color);
void draw_marker(Painter& painter, const ui::Point itemPoint, const uint16_t itemAngle, const std::string itemTag, const Color color = Color::red(), const Color fontColor = Color::white(), const Color backColor = Color::black());
void draw_markers(Painter& painter);
void map_zoom_line(ui::Color* buffer);
bool manual_panning_{false};
GeoMapMode mode_{};
File map_file{};
uint16_t map_width{}, map_height{};
int32_t map_center_x{}, map_center_y{};
int16_t map_zoom{1};
float lon_ratio{}, lat_ratio{};
double map_bottom{};
double map_world_lon{};
+2 -2
View File
@@ -96,7 +96,7 @@ bool FrequencyField::on_encoder(const EncoderEvent delta) {
// To get these magic numbers, I graphed the function until the
// curve shape seemed about right then tested on device.
delta_ms = std::min(145ull, delta_ms) + 5; // Prevent DIV/0
int64_t scale = 200'000'000 / (0.001'55 * pow(delta_ms, 5.45)) + 8;
int64_t scale = 200'000'000 / (0.001'55 * std::pow(delta_ms, 5.45)) + 8;
set_value(value() + (delta * scale));
} else {
set_value(value() + (delta * step));
@@ -280,7 +280,7 @@ FrequencyOptionsView::FrequencyOptionsView(
}
void FrequencyOptionsView::set_step(rf::Frequency f) {
field_step.set_by_value(f);
field_step.set_by_nearest_value(f);
}
void FrequencyOptionsView::set_reference_ppm_correction(int32_t v) {

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