Compare commits

...

27 Commits

Author SHA1 Message Date
Kyle Reed 25923e82a4 New Freqman edit UI (#1272)
* WIP new edit UI

* Fix textfield highlight

* WIP new edit

* Wrap up first pass of freqman edit

* Fix indexing of options

---------

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

* Added to_string_rounded_freq function

* Clang

* Clang try #2

* Use constexpr for pow10 array

* Additional string_format test cases

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

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

* Update ui_text.hpp

Update chars 0xA0-0xFF to match unicode

* Use unicode 0xB0 for degree character

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

* Update changelog.py

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

* 2nd_commit_for_changelog_fix_2nd_way

* methods-3

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

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

* Added fonts viewer (debug) app

* Clang

* Use degree symbol vs asterisk for latitude/longitude degrees

* Save a few bytes by not overriding on_key

* Save a few bytes by not overriding on_key

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

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

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

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

* Support for viewing BMP files and setting as Splash screen

* Support for viewing BMP files and setting as Splash screen

* Support for viewing BMP files and setting as Splash screen

* Update ui_ss_viewer.cpp

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

* TX/RX cleanup

* Update all apps using RadioState and setting modulation mode

* Set apps to use AM mode

* Don't push modulation update in RadioState.

* Support passing overrides to Audio and MicTX

* Support set_nearest on OptionsField, fix recon step

* Fix audio, typo

---------

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

* Generate CTCSS messages at fixed rate regardless of tone freq

* Function for generating CTCSS description strings

* Function for generating CTCSS description strings

* Increase width of CTCSS text to include tone freq

* Increase width of CTCSS text field to include frequency

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

* Use function in tone_key.cpp for displaying CTCSS string

* Use function in tone_key.cpp for CTCSS descr strings

* Use function in tone_key.cpp for CTCSS descr strings

* Clang test

* Clang

* Clang

* Support for reading CTCSS tones from FreqMan file

* Clang

* Clean up and eliminate floating point

* Clean up and eliminate floating point

* Corrected CTCSS field length

* Corrected CTCSS field length

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

* Finish pmem refactor

* Fix dump order

* Set defaults for splash and backlight

* Fix break

---------

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

* Fixed CTCSS tone detection

* Correct tone index for XZ/WZ and sort table by tone freq
2023-07-01 15:36:07 +02:00
126 changed files with 4672 additions and 2393 deletions
+44 -17
View File
@@ -1,28 +1,55 @@
import os
import re
import sys
raw_git = os.popen('git log next --since="24 hours" --pretty=format:"- %h - {USERNAME}*+%al-%an*: %s"').read()
import requests
from datetime import datetime, timedelta, timezone
# Set up your personal access token and the repository details
token = os.environ.get('GH_TOKEN')
repo_owner = "eried"
repo_name = "portapack-mayhem"
def compute_username(line):
stripped = re.search(r'(?<=\*)(.*?)(?=\*)', line).group(0)
def print_stable_changelog(previous_sha):
url = f"compare/{previous_sha}...next"
commits = handle_get_request(url)
for commit in commits["commits"]:
# Print the commit details
print(format_output(commit))
pattern = re.compile("[$@+&?].*[$@+&?]")
if pattern.match(stripped):
stripped = re.sub("[$@+&?].*[$@+&?]", "", stripped)
stripped = re.match(r'.+?(?=-)', stripped).group(0)
def print_nightly_changelog():
# Calculate the date and time 24 hours ago
since_time = (datetime.now(timezone.utc) - timedelta(hours=24)).isoformat() # Check that this is UTC
url = "commits"
commits = handle_get_request(url, since_time)
for commit in commits:
# Print the commit details
print(format_output(commit))
def handle_get_request(path, offset=None):
headers = {"Authorization": f"Bearer {token}"}
params = {"since": offset}
url_base = f"https://api.github.com/repos/{repo_owner}/{repo_name}/"
response = requests.get(url_base + path, headers=headers, params=params)
# Check if the request was successful (status code 200)
if response.status_code == 200:
return response.json()
else:
stripped = re.sub(r'^.*?-', "", stripped)
return "@" + stripped
print(f"Request failed with status code: {response.status_code}")
return None
def compile_line(line):
username = compute_username(line)
line = re.sub(r'[*].*[*]', "", line)
line = line.replace("{USERNAME}", username)
return line
def format_output(commit):
message_lines = commit["commit"]["message"].split("\n")
author = commit["author"]["login"] if commit["author"] and "login" in commit["author"] else commit["commit"]["author"]["name"]
return '- ' + commit["sha"][:8] + ' - @' + author + ': ' + message_lines[0]
for row in raw_git.splitlines():
print(compile_line(row))
if len(sys.argv) < 2:
print_nightly_changelog()
else:
past_version = sys.argv[1]
print_stable_changelog(past_version)
@@ -47,6 +47,9 @@ jobs:
run: mkdir ${{ github.workspace }}/build
- name: Run the Docker image
run: docker run -e VERSION_STRING=${{ steps.version_date.outputs.date }} -i -v ${{ github.workspace }}:/havoc portapack-dev
- name: Create Small SD Card ZIP - No World Map
run: |
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version_date.outputs.date }}.bin && zip -r sdcard-no-map.zip sdcard
- name: Download world map
run: |
wget https://github.com/eried/portapack-mayhem/releases/download/world_map/world_map.zip
@@ -60,6 +63,8 @@ jobs:
run: |
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version_date.outputs.date }}.bin && zip -r sdcard.zip sdcard
- name: Create changelog
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
run: |
CHANGELOG=$(python3 .github/workflows/changelog.py)
CHANGELOG="${CHANGELOG//'%'/'%25'}"
@@ -103,3 +108,13 @@ jobs:
asset_path: ./sdcard.zip
asset_name: mayhem_nightly_${{ steps.version_date.outputs.date }}_COPY_TO_SDCARD.zip
asset_content_type: application/zip
- name: Upload SD Card Assets - No Map
id: upload-sd-card-asset-no-map
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./sdcard-no-map.zip
asset_name: mayhem_nightly_${{ steps.version_date.outputs.date }}_COPY_TO_SDCARD-no-world-map.zip
asset_content_type: application/zip
+16 -1
View File
@@ -31,6 +31,9 @@ jobs:
run: mkdir ${{ github.workspace }}/build
- name: Run the Docker image
run: docker run -e VERSION_STRING=${{ steps.version.outputs.version }} -i -v ${{ github.workspace }}:/havoc portapack-dev
- name: Create Small SD Card ZIP - No World Map
run: |
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version.outputs.version }}.bin && zip -r sdcard-no-map.zip sdcard
- name: Download world map
run: |
wget https://github.com/eried/portapack-mayhem/releases/download/world_map/world_map.zip
@@ -44,8 +47,10 @@ jobs:
run: |
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version.outputs.version }}.bin && zip -r sdcard.zip sdcard
- name: Create changelog
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
run: |
CHANGELOG=$(python3 .github/workflows/stable_changelog.py ${{ steps.past_version.outputs.past_version }})
CHANGELOG=$(python3 .github/workflows/changelog.py ${{ steps.past_version.outputs.past_version }})
CHANGELOG="${CHANGELOG//'%'/'%25'}"
CHANGELOG="${CHANGELOG//$'\n'/'%0A'}"
CHANGELOG="${CHANGELOG//$'\r'/'%0D'}"
@@ -96,4 +101,14 @@ jobs:
asset_path: ./sdcard.zip
asset_name: mayhem_${{ steps.version.outputs.version }}_COPY_TO_SDCARD.zip
asset_content_type: application/zip
- name: Upload SD Card Assets - No Map
id: upload-sd-card-asset-no-map
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./sdcard-no-map.zip
asset_name: mayhem_${{ steps.version.outputs.version }}_COPY_TO_SDCARD-no-world-map.zip
asset_content_type: application/zip
-30
View File
@@ -1,30 +0,0 @@
import os
import re
import sys
past_version = sys.argv[1]
raw_git = os.popen('git log ' + past_version + '..next --pretty=format:"- %h - {USERNAME}*+%al-%an*: %s"').read()
def compute_username(line):
stripped = re.search(r'(?<=\*)(.*?)(?=\*)', line).group(0)
pattern = re.compile("[$@+&?].*[$@+&?]")
if pattern.match(stripped):
stripped = re.sub("[$@+&?].*[$@+&?]", "", stripped)
stripped = re.match(r'.+?(?=-)', stripped).group(0)
else:
stripped = re.sub(r'^.*?-', "", stripped)
return "@" + stripped
def compile_line(line):
username = compute_username(line)
line = re.sub(r'[*].*[*]', "", line)
line = line.replace("{USERNAME}", username)
return line
for row in raw_git.splitlines():
print(compile_line(row))
+2
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
@@ -234,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
+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 -9
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,11 +410,7 @@ void AnalogAudioView::update_modulation(ReceiverModel::Mode modulation) {
}
void AnalogAudioView::handle_coded_squelch(uint32_t value) {
tone_index idx = tone_key_index_by_value(value);
if (idx >= 0)
text_ctcss.set("CTCSS " + tone_key_string(idx));
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{};
@@ -71,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) {
@@ -81,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();
}
+1 -1
View File
@@ -50,7 +50,7 @@ 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};
+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};
-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?
@@ -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"
+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};
+1 -2
View File
@@ -513,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};
+41
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"
@@ -401,6 +404,7 @@ 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
@@ -462,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;
+11
View File
@@ -310,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);
+5
View File
@@ -41,6 +41,7 @@ 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 {
@@ -503,6 +504,10 @@ bool FileManagerView::handle_file_open() {
} 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;
+393 -193
View File
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
* Copyright (C) 2023 Kyle Reed
*
* This file is part of PortaPack.
*
@@ -22,281 +23,480 @@
#include "ui_freqman.hpp"
#include "portapack.hpp"
#include "event_m0.hpp"
#include "freqman.hpp"
#include "portapack.hpp"
#include "rtc_time.hpp"
#include "tone_key.hpp"
#include "ui_receiver.hpp"
#include "ui_styles.hpp"
#include "utility.hpp"
#include <memory>
using namespace portapack;
namespace fs = std::filesystem;
// TODO: Clean up after moving to better lookup tables.
extern ui::OptionsField::options_t freqman_bandwidths[4];
extern ui::OptionsField::options_t freqman_steps;
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;
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;
}
freqlist_view.set_db(database);
text_empty.hidden(!database.empty());
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);
// Todo: add back ?
/*for (size_t n = 0; n < database.size(); n++) {
if (database[n].value == value_) {
error_ = ERROR_DUPLICATE;
break;
}
}*/
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();
};
options_category.on_change = [this, value](size_t category_id, int32_t) {
change_category(category_id);
big_display.set(value);
};
}
FrequencyLoadView::~FrequencyLoadView() {
std::vector<freqman_entry>().swap(database);
}
void FrequencyLoadView::refresh_widgets(const bool v) {
freqlist_view.hidden(v);
text_empty.hidden(!v);
// display.fill_rectangle(freqlist_view.screen_rect(), Color::black());
set_dirty();
}
FrequencyLoadView::FrequencyLoadView(
NavigationView& nav)
: FreqManBaseView(nav) {
on_refresh_widgets = [this](bool v) {
refresh_widgets(v);
add_children(
{&labels,
&big_display,
&button_clear,
&button_edit,
&button_save,
&text_description});
entry_.type = freqman_type::Single;
entry_.frequency_a = value;
entry_.description = to_string_timestamp(rtc_time::now());
refresh_ui();
button_clear.on_select = [this, &nav](Button&) {
entry_.description = "";
refresh_ui();
};
add_children({&freqlist_view,
&text_empty});
button_edit.on_select = [this, &nav](Button&) {
temp_buffer_ = entry_.description;
text_prompt(nav_, temp_buffer_, desc_edit_max, [this](std::string& new_desc) {
entry_.description = new_desc;
refresh_ui();
});
};
// Resize menu view to fill screen
freqlist_view.set_parent_rect({0, 3 * 8, 240, 30 * 8});
freqlist_view.on_select = [&nav, this](FreqManUIList&) {
auto& entry = database[freqlist_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);
}
// swap with empty vector to ensure memory is immediately released
std::vector<freqman_entry>().swap(database);
button_save.on_select = [this, &nav](Button&) {
db_.insert_entry(entry_, db_.entry_count());
nav_.pop();
};
}
void FrequencyManagerView::on_edit_freq(rf::Frequency f) {
database[freqlist_view.get_index()].frequency_a = f;
save_freqman_file(file_list[categories[current_category_id].second], database);
change_category(current_category_id);
void FrequencySaveView::refresh_ui() {
big_display.set(entry_.frequency_a);
text_description.set(entry_.description);
}
void FrequencyManagerView::on_edit_desc(NavigationView& nav) {
text_prompt(nav, desc_buffer, 28, [this](std::string& buffer) {
database[freqlist_view.get_index()].description = buffer;
save_freqman_file(file_list[categories[current_category_id].second], database);
change_category(current_category_id);
/* FrequencyLoadView *************************************/
FrequencyLoadView::FrequencyLoadView(
NavigationView& nav)
: FreqManBaseView(nav) {
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();
};
}
/* FrequencyManagerView **********************************/
void FrequencyManagerView::on_edit_entry() {
auto edit_view = nav_.push<FrequencyEditView>(current_entry());
edit_view->on_save = [this](const freqman_entry& entry) {
db_.replace_entry(current_index(), entry);
freqlist_view.set_dirty();
};
}
void FrequencyManagerView::on_edit_freq() {
auto freq_edit_view = nav_.push<FrequencyKeypadView>(current_entry().frequency_a);
freq_edit_view->on_changed = [this](rf::Frequency f) {
auto entry = current_entry();
entry.frequency_a = f;
db_.replace_entry(current_index(), entry);
freqlist_view.set_dirty();
};
}
void FrequencyManagerView::on_edit_desc() {
temp_buffer_ = current_entry().description;
text_prompt(nav_, temp_buffer_, desc_edit_max, [this](std::string& new_desc) {
auto entry = current_entry();
entry.description = std::move(new_desc);
db_.replace_entry(current_index(), entry);
freqlist_view.set_dirty();
});
}
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_add_category() {
temp_buffer_.clear();
text_prompt(nav_, temp_buffer_, 20, [this](std::string& new_name) {
if (!new_name.empty()) {
create_freqman_file(new_name);
refresh_categories();
}
});
}
void FrequencyManagerView::on_delete() {
if (database.empty()) {
delete_freqman_file(file_list[categories[current_category_id].second]);
refresh_list();
} else {
database.erase(database.begin() + freqlist_view.get_index());
save_freqman_file(file_list[categories[current_category_id].second], database);
}
change_category(current_category_id);
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::refresh_widgets(const bool v) {
button_edit_freq.hidden(v);
button_edit_desc.hidden(v);
button_delete.hidden(v);
text_empty.hidden(!v);
freqlist_view.hidden(v);
labels.hidden(v);
// display.fill_rectangle(freqlist_view.screen_rect(), Color::black());
set_dirty();
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);
}
FrequencyManagerView::~FrequencyManagerView() {
// save_freqman_file(file_list[categories[current_category_id].second], database);
void FrequencyManagerView::on_del_entry() {
if (db_.empty())
return;
nav_.push<ModalMessageView>(
"Delete", "Delete " + trim(pretty_string(current_entry(), 23)) + "\nAre you sure?", YESNO,
[this](bool choice) {
if (choice) {
db_.delete_entry(current_index());
refresh_list();
}
});
}
FrequencyManagerView::FrequencyManagerView(
NavigationView& nav)
: FreqManBaseView(nav) {
on_refresh_widgets = [this](bool v) {
refresh_widgets(v);
add_children(
{&freqlist_view,
&button_add_category,
&button_del_category,
&button_edit_entry,
&rect_padding,
&button_edit_freq,
&button_edit_desc,
&button_add_entry,
&button_del_entry});
freqlist_view.on_select = [this](size_t) {
button_edit_entry.focus();
};
// Allows for quickly exiting control.
freqlist_view.on_leave = [this]() {
button_edit_entry.focus();
};
add_children({&labels,
&button_new_category,
&freqlist_view,
&text_empty,
&button_edit_freq,
&button_edit_desc,
&button_delete});
freqlist_view.on_select = [this](FreqManUIList&) {
button_edit_freq.focus();
button_add_category.on_select = [this]() {
on_add_category();
};
button_new_category.on_select = [this, &nav](Button&) {
desc_buffer = "";
on_new_category(nav);
button_del_category.on_select = [this]() {
on_del_category();
};
button_edit_freq.on_select = [this, &nav](Button&) {
if (database.empty()) {
database.push_back({0, 0, "", SINGLE});
}
auto new_view = nav.push<FrequencyKeypadView>(database[freqlist_view.get_index()].frequency_a);
new_view->on_changed = [this](rf::Frequency f) {
on_edit_freq(f);
};
button_edit_entry.on_select = [this](Button&) {
on_edit_entry();
};
button_edit_desc.on_select = [this, &nav](Button&) {
if (database.empty()) {
database.push_back({0, 0, "", SINGLE});
}
desc_buffer = database[freqlist_view.get_index()].description;
on_edit_desc(nav);
button_edit_freq.on_select = [this](Button&) {
on_edit_freq();
};
button_delete.on_select = [this, &nav](Button&) {
on_delete();
button_edit_desc.on_select = [this](Button&) {
on_edit_desc();
};
button_add_entry.on_select = [this]() {
on_add_entry();
};
button_del_entry.on_select = [this]() {
on_del_entry();
};
}
/* FrequencyEditView *************************************/
FrequencyEditView::FrequencyEditView(
NavigationView& nav,
freqman_entry entry)
: nav_{nav},
entry_{std::move(entry)} {
add_children({&labels,
&field_type,
&field_freq_a,
&field_freq_b,
&field_modulation,
&field_bandwidth,
&field_step,
&field_tone,
&field_description,
&text_validation,
&button_save,
&button_cancel});
freqman_set_modulation_option(field_modulation);
populate_bandwidth_options();
populate_step_options();
populate_tone_options();
// Add the "invalid/unset" option. Kind of hacky, but...
field_modulation.options().insert(
field_modulation.options().begin(), {"None", -1});
field_step.options().insert(
field_step.options().begin(), {"None", -1});
field_type.set_by_value((int32_t)entry_.type);
field_type.on_change = [this](size_t, auto value) {
entry_.type = static_cast<freqman_type>(value);
refresh_ui();
};
// TODO: this pattern should be able to be wrapped up.
field_freq_a.set_value(entry_.frequency_a);
field_freq_a.on_change = [this](rf::Frequency f) {
entry_.frequency_a = f;
refresh_ui();
};
field_freq_a.on_edit = [this]() {
auto freq_view = nav_.push<FrequencyKeypadView>(field_freq_a.value());
freq_view->on_changed = [this](rf::Frequency f) {
field_freq_a.set_value(f);
};
};
field_freq_b.set_value(entry_.frequency_b);
field_freq_b.on_change = [this](rf::Frequency f) {
entry_.frequency_b = f;
refresh_ui();
};
field_freq_b.on_edit = [this]() {
auto freq_view = nav_.push<FrequencyKeypadView>(field_freq_b.value());
freq_view->on_changed = [this](rf::Frequency f) {
field_freq_b.set_value(f);
};
};
field_modulation.set_by_value((int32_t)entry_.modulation);
field_modulation.on_change = [this](size_t, auto value) {
entry_.modulation = static_cast<freqman_index_t>(value);
populate_bandwidth_options();
};
field_bandwidth.set_by_value((int32_t)entry_.bandwidth);
field_bandwidth.on_change = [this](size_t, auto value) {
entry_.bandwidth = static_cast<freqman_index_t>(value);
};
field_step.set_by_value((int32_t)entry_.step);
field_step.on_change = [this](size_t, auto value) {
entry_.step = static_cast<freqman_index_t>(value);
};
field_tone.set_by_value((int32_t)entry_.tone);
field_tone.on_change = [this](size_t, auto value) {
entry_.tone = static_cast<freqman_index_t>(value);
};
field_description.set_text(entry_.description);
field_description.on_change = [this](TextField& tf) {
entry_.description = tf.get_text();
};
field_description.on_select = [this](TextField& tf) {
temp_buffer_ = tf.get_text();
text_prompt(nav_, temp_buffer_, FreqManBaseView::desc_edit_max,
[this, &tf](std::string& new_desc) {
tf.set_text(new_desc);
});
};
button_save.on_select = [this](Button&) {
if (on_save)
on_save(std::move(entry_));
nav_.pop();
};
button_cancel.on_select = [this](Button&) {
nav_.pop();
};
refresh_ui();
}
void FrequencyEditView::focus() {
button_cancel.focus();
}
void FrequencyEditView::refresh_ui() {
// This needs to be called whenever a UI option is changed
// in a way that causes fields to be unused or would make the
// entry fail validation.
auto is_range = entry_.type == freqman_type::Range;
auto is_ham = entry_.type == freqman_type::HamRadio;
auto has_freq_b = is_range || is_ham;
field_freq_b.set_style(has_freq_b ? &Styles::white : &Styles::grey);
field_step.set_style(is_range ? &Styles::white : &Styles::grey);
field_tone.set_style(is_ham ? &Styles::white : &Styles::grey);
if (is_valid(entry_)) {
text_validation.set("Valid");
text_validation.set_style(&Styles::green);
} else {
text_validation.set("Error");
text_validation.set_style(&Styles::red);
}
}
// TODO: The following functions shouldn't be needed once
// freqman_db lookup tables are complete.
void FrequencyEditView::populate_bandwidth_options() {
OptionsField::options_t options;
options.push_back({"None", -1});
if (entry_.modulation < std::size(freqman_bandwidths)) {
auto& bandwidths = freqman_bandwidths[entry_.modulation];
for (auto i = 1u; i < bandwidths.size(); ++i) {
auto& item = bandwidths[i];
options.push_back({item.first, (OptionsField::value_t)i});
}
}
field_bandwidth.set_options(std::move(options));
}
void FrequencyEditView::populate_step_options() {
OptionsField::options_t options;
options.push_back({"None", -1});
for (auto i = 1u; i < freqman_steps.size(); ++i) {
auto& item = freqman_steps[i];
options.push_back({item.first, (OptionsField::value_t)i});
}
field_step.set_options(std::move(options));
}
void FrequencyEditView::populate_tone_options() {
OptionsField::options_t options;
options.push_back({"None", -1});
for (auto i = 1u; i < tonekey::tone_keys.size(); ++i) {
auto& item = tonekey::tone_keys[i];
options.push_back({item.first, (OptionsField::value_t)i});
}
field_tone.set_options(std::move(options));
}
} // namespace ui
+165 -69
View File
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
* Copyright (C) 2023 Kyle Reed
*
* This file is part of PortaPack.
*
@@ -20,94 +21,99 @@
* Boston, MA 02110-1301, USA.
*/
#include "freqman.hpp"
#include "freqman_db.hpp"
#include "ui.hpp"
#include "ui_widget.hpp"
#include "ui_painter.hpp"
#include "ui_freqlist.hpp"
#include "ui_menu.hpp"
#include "ui_navigation.hpp"
#include "ui_painter.hpp"
#include "ui_receiver.hpp"
#include "ui_textentry.hpp"
#include "freqman.hpp"
#include "ui_freqlist.hpp"
#include "ui_widget.hpp"
namespace ui {
class FreqManBaseView : public View {
public:
FreqManBaseView(
NavigationView& nav);
FreqManBaseView(NavigationView& nav);
void focus() override;
static constexpr size_t desc_edit_max = 0x80;
protected:
using option_t = std::pair<std::string, int32_t>;
using options_t = std::vector<option_t>;
using options_t = OptionsField::options_t;
NavigationView& nav_;
freqman_error error_{NO_ERROR};
options_t categories{};
std::function<void(void)> on_select_frequency{nullptr};
std::function<void(bool)> on_refresh_widgets{nullptr};
std::function<void(void)> on_select_frequency{};
void get_freqman_files();
void change_category(int32_t category_id);
void refresh_list();
void change_category(size_t new_index);
/* Access the categories directly from the OptionsField.
* This avoids holding multiple copies of the file list. */
const options_t& categories() const { return options_category.options(); }
const auto& current_category() const { return options_category.selected_index_name(); }
auto current_index() const { return freqlist_view.get_index(); }
freqman_entry current_entry() const { return db_[current_index()]; }
void refresh_categories();
void refresh_list(int delta_selected = 0);
freqman_db database{};
std::vector<std::string> file_list{};
FreqmanDB db_{};
/* The top section (category) is 20px tall. */
Labels label_category{
{{0, 4}, "Category:", Color::light_grey()}};
{{0, 2}, "Category:", Color::light_grey()}};
OptionsField options_category{
{9 * 8, 4},
14,
{9 * 8, 2},
14 /* length */,
{}};
FreqManUIList freqlist_view{
{0, 3 * 8, 240, 23 * 8}};
Text text_empty{
{7 * 8, 12 * 8, 16 * 8, 16},
"Empty category !",
};
{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,47 +122,137 @@ class FrequencyLoadView : public FreqManBaseView {
std::function<void(rf::Frequency, rf::Frequency)> on_range_loaded{};
FrequencyLoadView(NavigationView& nav);
~FrequencyLoadView();
std::string title() const override { return "Load freq."; };
private:
void refresh_widgets(const bool v);
std::string title() const override { return "Load freq"; }
};
class FrequencyManagerView : public FreqManBaseView {
public:
FrequencyManagerView(NavigationView& nav);
~FrequencyManagerView();
std::string title() const override { return "Freqman"; };
std::string title() const override { return "Freqman"; }
private:
std::string desc_buffer{};
std::string temp_buffer_{};
void refresh_widgets(const bool v);
void on_edit_freq(rf::Frequency f);
void on_edit_desc(NavigationView& nav);
void on_new_category(NavigationView& nav);
void on_delete();
void on_edit_entry();
void on_edit_freq();
void on_edit_desc();
void on_add_category();
void on_del_category();
void on_add_entry();
void on_del_entry();
Labels labels{
{{4 * 8 + 4, 26 * 8}, "Edit:", Color::light_grey()}};
NewButton button_add_category{
{23 * 8, 0 * 16, 7 * 4, 20},
{},
&bitmap_icon_new_file,
Color::white(),
true};
Button button_new_category{
{23 * 8, 2, 7 * 8, 20},
"New"};
NewButton button_del_category{
{26 * 8 + 4, 0 * 16, 7 * 4, 20},
{},
&bitmap_icon_trash,
Color::red(),
true};
Button button_edit_entry{
{0 * 8, 14 * 16 - 4, 15 * 8, 1 * 16 + 4},
"Edit"};
Rectangle rect_padding{
{15 * 8, 14 * 16 - 4, 15 * 8, 1 * 16 + 4},
Color::grey()};
Button button_edit_freq{
{0 * 8, 29 * 8, 14 * 8, 32},
{0 * 8, 15 * 16, 15 * 8, 2 * 16},
"Frequency"};
Button button_edit_desc{
{0 * 8, 34 * 8, 14 * 8, 32},
{0 * 8, 17 * 16, 15 * 8, 2 * 16},
"Description"};
Button button_delete{
{16 * 8, 29 * 8, 14 * 8, 32},
"Delete"};
NewButton button_add_entry{
{15 * 8, 15 * 16, 7 * 8 + 4, 2 * 16},
{},
&bitmap_icon_add,
Color::white(),
true};
NewButton button_del_entry{
{22 * 8 + 4, 15 * 16, 7 * 8 + 4, 2 * 16},
{},
&bitmap_icon_delete,
Color::red(),
true};
};
class FrequencyEditView : public View {
public:
std::function<void(const freqman_entry&)> on_save{};
FrequencyEditView(
NavigationView& nav,
freqman_entry entry);
std::string title() const override { return "Freqman Edit"; }
void focus() override;
private:
NavigationView& nav_;
freqman_entry entry_;
std::string temp_buffer_{};
void refresh_ui();
void populate_bandwidth_options();
void populate_step_options();
void populate_tone_options();
Labels labels{
{{5 * 8, 1 * 16}, "Edit Frequency Entry", Color::white()},
{{0 * 8, 3 * 16}, "Entry Type :", Color::light_grey()},
{{0 * 8, 4 * 16}, "Frequency A:", Color::light_grey()},
{{0 * 8, 5 * 16}, "Frequency B:", Color::light_grey()},
{{0 * 8, 6 * 16}, "Modulation :", Color::light_grey()},
{{0 * 8, 7 * 16}, "Bandwidth :", Color::light_grey()},
{{0 * 8, 8 * 16}, "Step :", Color::light_grey()},
{{0 * 8, 9 * 16}, "Tone Freq :", Color::light_grey()},
{{0 * 8, 10 * 16}, "Description:", Color::light_grey()},
};
OptionsField field_type{{13 * 8, 3 * 16}, 8, {
{"Single", 0},
{"Range", 1},
{"HamRadio", 2},
{"Raw", 3},
}};
FrequencyField field_freq_a{{13 * 8, 4 * 16}};
FrequencyField field_freq_b{{13 * 8, 5 * 16}};
OptionsField field_modulation{{13 * 8, 6 * 16}, 5, {}};
OptionsField field_bandwidth{{13 * 8, 7 * 16}, 7, {}};
OptionsField field_step{{13 * 8, 8 * 16}, 12, {}};
OptionsField field_tone{{13 * 8, 9 * 16}, 13, {}};
TextField field_description{
{13 * 8, 10 * 16, 17 * 8, 1 * 16},
{}};
Text text_validation{
{12 * 8, 12 * 16, 5 * 8, 1 * 16},
{}};
Button button_save{
{0 * 8, 17 * 16, 15 * 8, 2 * 16},
"Save"};
Button button_cancel{
{15 * 8, 17 * 16, 15 * 8, 2 * 16},
"Cancel"};
};
} /* namespace ui */
+11 -17
View File
@@ -26,6 +26,7 @@
#include "file.hpp"
using namespace portapack;
using namespace tonekey;
using portapack::memory::map::backup_ram;
namespace ui {
@@ -212,27 +213,20 @@ size_t LevelView::change_mode(freqman_index_t new_mod) {
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 tone_index last_squelch_index = -1;
if (field_mode.selected_index() == NFM_MODULATION) {
tone_index idx = tone_key_index_by_value(value);
if ((last_squelch_index < 0) || (last_squelch_index != idx)) {
last_squelch_index = idx;
if (idx >= 0) {
text_ctcss.set("T: " + tone_key_string(idx));
return;
}
} else {
return;
}
}
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 */
+1 -1
View File
@@ -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);
@@ -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};
+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;
+186 -198
View File
@@ -23,13 +23,24 @@
#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);
@@ -38,6 +49,10 @@ void ReconView::set_loop_config(bool v) {
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
@@ -51,22 +66,41 @@ void ReconView::clear_freqlist_for_ui_action() {
audio::output::stop();
// flag to detect and reload frequency_list
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);
// 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) {
@@ -93,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()) {
@@ -171,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;
@@ -306,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();
}
}
}
@@ -366,7 +385,6 @@ ReconView::~ReconView() {
recon_save_config_to_sd();
if (field_mode.selected_index_value() != SPEC_MODULATION)
audio::output::stop();
receiver_model.set_modulation(ReceiverModel::Mode::WidebandFMAudio);
receiver_model.disable();
baseband::shutdown();
}
@@ -549,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&) {
@@ -568,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
@@ -597,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) {
@@ -622,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);
}
}
@@ -633,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
@@ -675,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(" ");
@@ -703,25 +709,23 @@ ReconView::ReconView(NavigationView& nav)
} else {
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);
freqman_entry manual_freq_entry;
// 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>());
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
@@ -731,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();
@@ -776,7 +779,7 @@ 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);
@@ -787,7 +790,7 @@ ReconView::ReconView(NavigationView& nav)
}
};
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);
}
@@ -808,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);
@@ -829,7 +832,7 @@ ReconView::ReconView(NavigationView& nav)
open_view->on_changed = [this](std::vector<std::string> result) {
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();
@@ -888,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);
@@ -930,14 +933,18 @@ void ReconView::frequency_file_load(bool stop_all_before) {
file_name.set_style(&Styles::red);
button_scanner_mode.set_style(&Styles::red);
desc_cycle.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);
desc_cycle.set_style(&Styles::blue);
button_scanner_mode.set_text("RECON");
}
if (!load_freqman_file(file_input, frequency_list, load_freqs, load_ranges, load_hamradios)) {
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(" NO " + file_input + ".TXT FILE ...");
file_name.set("=> NO DATA");
@@ -954,82 +961,61 @@ void ReconView::frequency_file_load(bool stop_all_before) {
}
}
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) {
chrono_end = chTimeNow();
time_interval = chrono_end - chrono_start;
chrono_start = chrono_end;
// 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) {
@@ -1049,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
@@ -1091,6 +1077,10 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
// contents of a possible recon_start_recording(), but not yet since it's only called once
if (auto_record_locked && !is_recording) {
button_audio_app.set_style(&Styles::red);
if (field_mode.selected_index_value() == SPEC_MODULATION)
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;
@@ -1133,7 +1123,7 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
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;
@@ -1163,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;
@@ -1182,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;
@@ -1236,22 +1226,22 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
// 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) {
@@ -1409,8 +1399,16 @@ size_t ReconView::change_mode(freqman_index_t new_mod) {
default:
break;
}
if (new_mod != SPEC_MODULATION)
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) {
@@ -1426,20 +1424,10 @@ size_t ReconView::change_mode(freqman_index_t new_mod) {
}
void ReconView::handle_coded_squelch(const uint32_t value) {
if (field_mode.selected_index() == NFM_MODULATION) {
tone_index idx = tone_key_index_by_value(value);
if ((last_squelch_index < 0) || (last_squelch_index != idx)) {
last_squelch_index = idx;
if (idx >= 0) {
text_ctcss.set("T: " + tone_key_string(idx));
return;
}
} else {
return;
}
}
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 */
+6 -2
View File
@@ -67,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);
@@ -87,6 +88,10 @@ class ReconView : public View {
// 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};
int32_t db{0};
@@ -139,7 +144,6 @@ class ReconView : public View {
int8_t last_rssi_med{-127};
int8_t last_rssi_max{-127};
int32_t last_index{-1};
tone_index last_squelch_index{-1};
int64_t last_freq{0};
std::string freq_file_path{};
systime_t chrono_start{};
@@ -270,7 +274,7 @@ 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,
' ',
};
@@ -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 {
@@ -135,7 +135,7 @@ class ReconSetupViewMore : public View {
Checkbox checkbox_auto_record_locked{
{1 * 8, 132},
3,
"auto record locked periods"};
"record locked periods"};
};
class ReconSetupView : public View {
+62 -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
@@ -493,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;
@@ -575,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) {
@@ -586,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 {
@@ -649,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;
+1 -12
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");
}
@@ -414,7 +404,6 @@ SearchView::SearchView(
on_range_changed();
receiver_model.set_modulation(ReceiverModel::Mode::SpectrumAnalysis);
receiver_model.enable();
}
+2 -2
View File
@@ -92,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;
+15 -17
View File
@@ -35,6 +35,9 @@ using namespace lpc43xx;
#include "portapack.hpp"
using namespace portapack;
#include "file.hpp"
namespace fs = std::filesystem;
#include "string_format.hpp"
#include "ui_styles.hpp"
#include "cpld_update.hpp"
@@ -489,35 +492,30 @@ SetPersistentMemoryView::SetPersistentMemoryView(NavigationView& nav) {
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 (!pmem::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&) {
+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__
+51 -31
View File
@@ -301,6 +301,38 @@ void TextEditorMenu::hide_children(bool hidden) {
/* TextEditorView ***************************************************/
static fs::path get_temp_path(const fs::path& path) {
if (!path.empty())
return path + "~";
return {};
}
static void delete_temp_file(const fs::path& path) {
auto temp_path = get_temp_path(path);
if (!temp_path.empty()) {
delete_file(temp_path);
}
}
static void save_temp_file(const fs::path& path) {
delete_file(path);
copy_file(get_temp_path(path), path);
}
static void show_save_prompt(
NavigationView& nav,
std::function<void()> on_save,
std::function<void()> continuation) {
nav.display_modal(
"Save?", "Save changes?", YESNO,
[on_save](bool choice) {
if (choice && on_save)
on_save();
});
nav.set_on_pop(continuation);
}
TextEditorView::TextEditorView(NavigationView& nav)
: nav_{nav} {
add_children(
@@ -381,9 +413,7 @@ TextEditorView::TextEditorView(NavigationView& nav)
};
menu.on_exit() = [this]() {
show_save_prompt([this]() {
nav_.pop();
});
nav_.pop();
};
button_menu.on_select = [this]() {
@@ -402,7 +432,15 @@ TextEditorView::TextEditorView(NavigationView& nav, const fs::path& path)
}
TextEditorView::~TextEditorView() {
delete_temp_file();
// NB: Be careful here. The UI will render after this instance
// has been destroyed. Everything needed to render the UI
// and perform the save actions must be value captured.
if (file_dirty_) {
ui::show_save_prompt(
nav_,
[p = path_]() { ui::save_temp_file(p); },
[p = std::move(path_)]() { delete_temp_file(p); });
}
}
void TextEditorView::on_show() {
@@ -415,7 +453,7 @@ void TextEditorView::on_show() {
void TextEditorView::open_file(const fs::path& path) {
file_.reset();
viewer.clear_file();
delete_temp_file();
delete_temp_file(path_);
path_ = {};
file_dirty_ = false;
@@ -516,13 +554,10 @@ void TextEditorView::show_save_prompt(std::function<void()> continuation) {
return;
}
nav_.display_modal(
"Save?", "Save changes?", YESNO,
[this](bool choice) {
if (choice)
save_temp_file();
});
nav_.set_on_pop(continuation);
ui::show_save_prompt(
nav_,
[this]() { save_temp_file(); },
continuation);
}
void TextEditorView::prepare_for_write() {
@@ -533,31 +568,16 @@ void TextEditorView::prepare_for_write() {
// Copy to temp file on write.
has_temp_file_ = true;
delete_temp_file();
copy_file(path_, get_temp_path());
file_->assume_file(get_temp_path());
}
void TextEditorView::delete_temp_file() const {
auto temp_path = get_temp_path();
if (!temp_path.empty()) {
delete_file(temp_path);
}
delete_temp_file(path_);
copy_file(path_, get_temp_path(path_));
file_->assume_file(get_temp_path(path_));
}
void TextEditorView::save_temp_file() {
if (file_dirty_) {
delete_file(path_);
copy_file(get_temp_path(), path_);
ui::save_temp_file(path_);
file_dirty_ = false;
}
}
fs::path TextEditorView::get_temp_path() const {
if (!path_.empty())
return path_ + "~";
return {};
}
} // namespace ui
@@ -229,10 +229,7 @@ class TextEditorView : public View {
void show_save_prompt(std::function<void()> continuation);
void prepare_for_write();
void create_temp_file() const;
void delete_temp_file() const;
void save_temp_file();
std::filesystem::path get_temp_path() const;
NavigationView& nav_;
std::unique_ptr<FileWrapper> file_{};
+38
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,
+43
View File
@@ -0,0 +1,43 @@
/*
* 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 __FIELD_BINDER_H__
#define __FIELD_BINDER_H__
/* There's a common pattern that appears in applications that use
* field widgets. The field value is set, on_change and on_select
* handlers are registered. on_select will read the value and
* set the new value once changed. on_change will be called which
* will write the new value to the backing variable. */
struct binding_traits {
};
template <typename T>
constexpr auto get_traits() {
return {};
}
template <typename TField, typename TTraits = get_traits<TField>()>
class FieldBinding {
};
#endif /*__FIELD_BINDER_H__*/
+14 -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(
@@ -507,13 +505,12 @@ bool path_iequal(
}
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...
+23 -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;
@@ -207,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);
@@ -219,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() {}
@@ -266,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_; }
+51 -405
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,449 +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},
{"SPEC", 3}};
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},
},
{
// SPEC
{"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)
}};
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}};
// 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);
}
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 delete_freqman_file(const std::string& file_stem) {
delete_file(get_freqman_path(file_stem));
return true;
}
bool load_freqman_file(std::string& file_stem, freqman_db& db, bool load_freqs, bool load_ranges, bool load_hamradios, uint8_t max_num_freqs) {
// swap with empty vector to ensure memory is immediately released
std::vector<freqman_entry>().swap(db);
File freqman_file{};
size_t length = 0, n = 0, file_position = 0;
char* pos = NULL;
char* line_start = NULL;
char* line_end = NULL;
std::string description{NULL};
rf::Frequency frequency_a = 0, frequency_b = 0;
char file_data[FREQMAN_READ_BUF_SIZE + 1] = {0};
freqman_entry_type type = NOTYPE;
freqman_index_t modulation = -1;
freqman_index_t bandwidth = -1;
freqman_index_t step = -1;
freqman_index_t tone = -1;
bool save_freqman_file(const std::string& file_stem, freqman_db& db) {
auto path = get_freqman_path(file_stem);
delete_file(path);
auto result = freqman_file.open("FREQMAN/" + file_stem + ".TXT");
if (result.is_valid())
File freqman_file;
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 = NOTYPE;
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);
description.shrink_to_fit();
}
if ((type == SINGLE && load_freqs) || (type == RANGE && load_ranges) || (type == HAMRADIO && load_hamradios)) {
freqman_entry entry = {frequency_a, frequency_b, std::move(description), type, modulation, bandwidth, step, tone};
db.emplace_back(entry);
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;
}
}
}
db.shrink_to_fit();
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;
}
+17 -70
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,23 +28,13 @@
#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 90 // 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_READ_BUF_SIZE 96 // max freqman line size including desc is 90, + a bit of space
using namespace ui;
using namespace std;
using namespace tonekey;
// needs to be signed as -1 means not set
typedef int8_t freqman_index_t;
// Defined for back-compat.
#define FREQMAN_MAX_PER_FILE freqman_default_max_entries
enum freqman_error : int8_t {
NO_ERROR = 0,
@@ -52,72 +43,28 @@ enum freqman_error : int8_t {
ERROR_DUPLICATE
};
enum freqman_entry_type : int8_t {
SINGLE = 0, // f=
RANGE, // a=,b=
HAMRADIO, // r=,t=
NOTYPE // undetected
};
enum freqman_entry_modulation : uint8_t {
AM_MODULATION = 0,
NFM_MODULATION,
WFM_MODULATION,
SPEC_MODULATION,
SPEC_MODULATION
};
// Entry step placed for AlainD freqman version (or any other enhanced version)
enum freqman_entry_step : int8_t {
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
};
// 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{NULL}; // 'd=desc'
freqman_entry_type type{SINGLE}; // SINGLE,RANGE,HAMRADIO
freqman_index_t modulation{AM_MODULATION}; // AM,NFM,WFM
freqman_index_t bandwidth{0}; // AM_DSB, ...
freqman_index_t step{0}; // 5khz (SA AM,...
tone_index tone{0}; // 0XZ, 11 1ZB,...
};
using freqman_db = std::vector<freqman_entry>;
bool load_freqman_file(std::string& file_stem, freqman_db& db, bool load_freqs = true, bool load_ranges = true, bool load_hamradios = true, uint8_t max_num_freqs = FREQMAN_MAX_PER_FILE);
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__*/
+483
View File
@@ -0,0 +1,483 @@
/*
* 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)
},
};
// TODO: these should be indexes.
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},
};
// TODO: these should be indexes.
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 = "R:" + to_string_dec_uint(entry.frequency_a / 1'000'000) + "M,T:" +
to_string_dec_uint(entry.frequency_b / 1'000'000) + "M: " + entry.description;
break;
case freqman_type::Raw:
str = 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;
case freqman_type::Raw:
return entry.description;
default:
return {};
};
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);
}
}
return is_valid(entry);
}
// 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;
}
bool is_valid(const freqman_entry& entry) {
// No valid frequency combination was set.
if (entry.type == freqman_type::Unknown)
return false;
// Frequency A must be set for all types
if (entry.frequency_a == 0)
return false;
// Frequency B must be set for type Range or Ham Radio
if (entry.type == freqman_type::Range || entry.type == freqman_type::HamRadio) {
if (entry.frequency_b == 0)
return false;
}
// Ranges should have frequencies A <= B.
if (entry.type == freqman_type::Range) {
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;
}
/* 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;
else {
entry.type = freqman_type::Raw;
entry.description = trim(*line_text);
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;
// 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;
}
+238
View File
@@ -0,0 +1,238 @@
/*
* 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=
Raw, // line content in description
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{}; // '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);
/* Returns true if the entry is well-formed. */
bool is_valid(const freqman_entry& entry);
/* 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__ */
+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__*/
+70 -10
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) {
@@ -89,8 +111,8 @@ std::string to_string_dec_uint(
auto term = p + sizeof(p) - 1;
auto q = to_string_dec_uint_pad_internal(term, n, l, fill);
// TODO: is this needed, seems like pad already does this.
// Right justify.
// (This code is redundant and won't do anything if a fill character was specified)
while ((term - q) < l) {
*(--q) = ' ';
}
@@ -115,6 +137,7 @@ std::string to_string_dec_int(
}
// Right justify.
// (This code is redundant and won't do anything if a fill character was specified)
while ((term - q) < l) {
*(--q) = ' ';
}
@@ -139,13 +162,47 @@ std::string to_string_decimal(float decimal, int8_t precision) {
return result;
}
// right-justified frequency in Hz, always 10 characters
std::string to_string_freq(const uint64_t f) {
auto final_str = to_string_dec_int(f / 1000000, 4) + to_string_dec_int(f % 1000000, 6, '0');
std::string final_str{""};
if (f < 1000000)
final_str = to_string_dec_int(f, 10, ' ');
else
final_str = to_string_dec_int(f / 1000000, 4) + to_string_dec_int(f % 1000000, 6, '0');
return final_str;
}
// right-justified frequency in MHz, rounded to 4 decimal places, always 9 characters
std::string to_string_short_freq(const uint64_t f) {
auto final_str = to_string_dec_int(f / 1000000, 4) + "." + to_string_dec_int((f / 100) % 10000, 4, '0');
auto final_str = to_string_dec_int(f / 1000000, 4) + "." + to_string_dec_int(((f + 50) / 100) % 10000, 4, '0');
return final_str;
}
// non-justified non-padded frequency in MHz, rounded to specified number of decimal places
std::string to_string_rounded_freq(const uint64_t f, int8_t precision) {
std::string final_str{""};
static constexpr uint32_t pow10[7] = {
1,
10,
100,
1000,
10000,
100000,
1000000,
};
if (precision < 1) {
final_str = to_string_dec_uint64(f / 1000000);
} else {
if (precision > 6)
precision = 6;
uint32_t divisor = pow10[6 - precision];
final_str = to_string_dec_uint64(f / 1000000) + "." + to_string_dec_int(((f + (divisor / 2)) / divisor) % pow10[precision], precision, '0');
}
return final_str;
}
@@ -177,7 +234,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;
@@ -263,7 +320,7 @@ std::string unit_auto_scale(double n, const uint32_t base_nano, uint32_t precisi
string = to_string_dec_int(integer_part);
if (precision)
string += '.' + to_string_dec_uint(fractional_part, precision);
string += '.' + to_string_dec_uint(fractional_part, precision, '0');
if (prefix_index != 3)
string += unit_prefix[prefix_index];
@@ -285,6 +342,9 @@ 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 + 1)};
}
+9 -4
View File
@@ -41,14 +41,18 @@ 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);
@@ -57,6 +61,7 @@ std::string to_string_hex_array(uint8_t* const array, const int32_t l = 0);
std::string to_string_freq(const uint64_t f);
std::string to_string_short_freq(const uint64_t f);
std::string to_string_rounded_freq(const uint64_t f, int8_t precision);
std::string to_string_time_ms(const uint32_t ms);
std::string to_string_datetime(const rtc::RTC& value, const TimeFormat format = YMDHMS);
+114 -84
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 M5", 233.600},
{"37 M6", 241.800},
{"38 M7", 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,20 +98,65 @@ std::string tone_key_string(tone_index index) {
return tone_keys[index].first;
}
std::string tone_key_string_by_value(uint32_t value) {
return tone_key_string(tone_key_index_by_value(value));
// 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) {
float diff;
float min_diff{(float)value};
float fvalue{(float)((min_diff + 50.0) / 100.0)};
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(fvalue - tone_keys[idx].second);
diff = abs(value - tone_keys[idx].second);
if (diff < min_diff) {
min_idx = idx;
min_diff = diff;
@@ -137,7 +167,7 @@ tone_index tone_key_index_by_value(uint32_t value) {
}
// Arbitrary confidence threshold
if (min_diff < 40.0)
if (min_diff < TONE_FREQ_TOLERANCE_CENTIHZ)
return min_idx;
else
return -1;
@@ -147,7 +177,7 @@ 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;
+13 -10
View File
@@ -23,24 +23,27 @@
#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 int32_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);
std::string tone_key_string_by_value(uint32_t value);
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();
};
+122 -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,147 @@
#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 (freqlist_db == nullptr || (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 == nullptr || 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->at(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};
auto is_selected = offset == selected_index_;
auto style = 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. Show an empty entry if 'Unknown'.
if (entry.type != freqman_type::Unknown)
text = pretty_string(entry, line_max_length);
// Otherwise, if 'Raw' indicate an invalid entry by color.
if (entry.type == freqman_type::Raw)
style = &Styles::light_grey;
}
// 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, (is_selected ? style->invert() : *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 */
+22 -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,53 +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{nullptr};
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
+2 -2
View File
@@ -84,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: \xB0 ' \"", Color::light_grey()}, // 0xB0 is degree ° symbol in our 8x16 font
{{1 * 8, 2 * 16}, "Lon: \xB0 ' \"", Color::light_grey()},
};
NumberField field_altitude{
+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) {
+1 -1
View File
@@ -48,7 +48,7 @@ class TextEntryView : public View {
void char_add(const char c);
void char_delete();
TextField text_input;
TextEdit text_input;
Button button_ok{
{22 * 8, 33 * 8, 7 * 8, 32},
"OK"};
+47
View File
@@ -0,0 +1,47 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2017 Furrtek
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include "ui_tone_key.hpp"
#include <utility>
using namespace ui;
namespace tonekey {
void tone_keys_populate(OptionsField& field) {
OptionsField::options_t tone_key_options;
std::string tone_name;
for (size_t c = 0; c < tone_keys.size(); c++) {
auto f = tone_keys[c].second;
if ((c != 0) && (f < 1000 * 100))
tone_name = "CTCSS " + fx100_string(f) + " #" + tone_keys[c].first;
else
tone_name = tone_keys[c].first;
tone_key_options.emplace_back(tone_name, c);
}
field.set_options(std::move(tone_key_options));
}
} // namespace tonekey
+36
View File
@@ -0,0 +1,36 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2017 Furrtek
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#ifndef __UI_TONE_KEY_H_
#define __UI_TONE_KEY_H_
#include "tone_key.hpp"
#include "ui.hpp"
#include "ui_widget.hpp"
namespace tonekey {
void tone_keys_populate(ui::OptionsField& field);
} // namespace tonekey
#endif /*__UI_TONE_KEY_H_*/
+23 -55
View File
@@ -77,6 +77,7 @@
#include "ui_flash_utility.hpp"
#include "ui_sd_over_usb.hpp"
#include "ui_spectrum_painter.hpp"
#include "ui_ss_viewer.hpp"
// #include "acars_app.hpp"
#include "ais_app.hpp"
@@ -179,16 +180,21 @@ SystemStatusView::SystemStatusView(
this->on_converter();
};
button_speaker.on_select = [this](ImageButton&) {
this->on_speaker();
toggle_speaker.on_change = [this](bool v) {
pmem::set_config_speaker_disable(v);
audio::output::update_audio_mute();
refresh();
};
button_mute.on_select = [this](ImageButton&) {
this->on_mute();
toggle_mute.on_change = [this](bool v) {
pmem::set_config_audio_mute(v);
audio::output::update_audio_mute();
refresh();
};
button_stealth.on_select = [this](ImageButton&) {
this->on_stealth();
toggle_stealth.on_change = [this](bool v) {
pmem::set_stealth_mode(v);
refresh();
};
button_bias_tee.on_select = [this](ImageButton&) {
@@ -208,6 +214,11 @@ SystemStatusView::SystemStatusView(
this->on_clk();
};
// Initialize toggle buttons
toggle_speaker.set_value(pmem::config_speaker_disable());
toggle_mute.set_value(pmem::config_audio_mute());
toggle_stealth.set_value(pmem::stealth_mode());
audio::output::update_audio_mute();
refresh();
}
@@ -218,43 +229,22 @@ void SystemStatusView::refresh() {
status_icons.clear();
if (!pmem::ui_hide_camera()) status_icons.add(&button_camera);
if (!pmem::ui_hide_sleep()) status_icons.add(&button_sleep);
if (!pmem::ui_hide_stealth()) status_icons.add(&button_stealth);
if (!pmem::ui_hide_stealth()) status_icons.add(&toggle_stealth);
if (!pmem::ui_hide_converter()) status_icons.add(&button_converter);
if (!pmem::ui_hide_bias_tee()) status_icons.add(&button_bias_tee);
if (!pmem::ui_hide_clock()) status_icons.add(&button_clock_status);
if (!pmem::ui_hide_mute()) status_icons.add(&button_mute);
if (!pmem::ui_hide_mute()) status_icons.add(&toggle_mute);
// Display "Disable speaker" icon only if AK4951 Codec which has separate speaker/headphone control
if (audio::speaker_disable_supported() && !pmem::ui_hide_speaker())
status_icons.add(&button_speaker);
if (audio::speaker_disable_supported() && !pmem::ui_hide_speaker()) status_icons.add(&toggle_speaker);
if (!pmem::ui_hide_sd_card()) status_icons.add(&sd_card_status_view);
status_icons.update_layout();
// Update icon display (try to keep all in on place).
// Speaker Enable/Disable (AK4951 boards only)
if (pmem::config_speaker_disable()) {
button_speaker.set_foreground(Color::light_grey());
button_speaker.set_bitmap(&bitmap_icon_speaker_mute);
} else {
button_speaker.set_foreground(Color::green());
button_speaker.set_bitmap(&bitmap_icon_speaker);
}
// Audio Mute (both headphones & speaker)
if (pmem::config_audio_mute()) {
button_mute.set_foreground(Color::light_grey());
button_mute.set_bitmap(&bitmap_icon_speaker_and_headphones_mute);
} else {
button_mute.set_foreground(Color::green());
button_mute.set_bitmap(&bitmap_icon_speaker_and_headphones);
}
// Clock status
bool external_clk = portapack::clock_manager.get_reference().source == ClockManager::ReferenceSource::External;
button_clock_status.set_bitmap(external_clk ? &bitmap_icon_clk_ext : &bitmap_icon_clk_int);
button_clock_status.set_foreground(
pmem::clkout_enabled() ? Color::green() : Color::light_grey());
button_clock_status.set_foreground(pmem::clkout_enabled() ? Color::green() : Color::light_grey());
// Antenna DC Bias
if (portapack::get_antenna_bias()) {
@@ -266,14 +256,9 @@ void SystemStatusView::refresh() {
}
// Converter
button_converter.set_bitmap(
pmem::config_updown_converter() ? &bitmap_icon_downconvert : &bitmap_icon_upconvert);
button_converter.set_bitmap(pmem::config_updown_converter() ? &bitmap_icon_downconvert : &bitmap_icon_upconvert);
button_converter.set_foreground(pmem::config_converter() ? Color::red() : Color::light_grey());
// Stealth
button_stealth.set_foreground(
pmem::stealth_mode() ? Color::green() : Color::light_grey());
set_dirty();
}
@@ -314,23 +299,6 @@ void SystemStatusView::on_converter() {
refresh();
}
void SystemStatusView::on_speaker() {
pmem::set_config_speaker_disable(!pmem::config_speaker_disable());
audio::output::update_audio_mute();
refresh();
}
void SystemStatusView::on_mute() {
pmem::set_config_audio_mute(!pmem::config_audio_mute());
audio::output::update_audio_mute();
refresh();
}
void SystemStatusView::on_stealth() {
pmem::set_stealth_mode(!pmem::stealth_mode());
refresh();
}
void SystemStatusView::on_bias_tee() {
if (!portapack::get_antenna_bias()) {
nav_.display_modal("Bias voltage",
@@ -767,7 +735,7 @@ BMPView::BMPView(NavigationView& nav) {
}
void BMPView::paint(Painter&) {
if (!portapack::display.drawBMP2({0, 0}, "splash.bmp"))
if (!portapack::display.drawBMP2({0, 0}, splash_dot_bmp))
portapack::display.drawBMP({(240 - 230) / 2, (320 - 50) / 2 - 10}, splash_bmp, false);
}
+10 -10
View File
@@ -184,17 +184,22 @@ class SystemStatusView : public View {
StatusTray status_icons{{screen_width, 0}};
// TODO: Convert to ImageToggle buttons.
ImageButton button_speaker{
ImageToggle toggle_speaker{
{0, 0, 2 * 8, 1 * 16},
&bitmap_icon_speaker_mute,
&bitmap_icon_speaker,
Color::light_grey(),
Color::dark_grey(),
Color::green(),
Color::dark_grey()};
ImageButton button_mute{
ImageToggle toggle_mute{
{0, 0, 2 * 8, 1 * 16},
&bitmap_icon_speaker_and_headphones_mute,
&bitmap_icon_speaker_and_headphones,
Color::light_grey(),
Color::dark_grey(),
Color::green(),
Color::dark_grey()};
ImageButton button_converter{
@@ -203,11 +208,9 @@ class SystemStatusView : public View {
Color::light_grey(),
Color::dark_grey()};
ImageButton button_stealth{
ImageToggle toggle_stealth{
{0, 0, 2 * 8, 1 * 16},
&bitmap_icon_stealth,
Color::light_grey(),
Color::dark_grey()};
&bitmap_icon_stealth};
ImageButton button_camera{
{0, 0, 2 * 8, 1 * 16},
@@ -237,9 +240,6 @@ class SystemStatusView : public View {
{0, 0 * 16, 2 * 8, 1 * 16}};
void on_converter();
void on_speaker();
void on_mute();
void on_stealth();
void on_bias_tee();
void on_camera();
void on_title();
+7 -3
View File
@@ -53,7 +53,9 @@ void NarrowbandFMAudio::execute(const buffer_c8_t& buffer) {
if (ctcss_detect_enabled) {
/* 24kHz int16_t[16]
* -> FIR filter, <300Hz pass, >300Hz stop, gain of 1
* -> 12kHz int16_t[8] */
* -> 12kHz int16_t[8]
*
* Note we're only processing a small section of the wave each time this fn is called */
auto audio_ctcss = ctcss_filter.execute(audio, work_audio_buffer);
// s16 to f32 for hpf
@@ -72,16 +74,18 @@ void NarrowbandFMAudio::execute(const buffer_c8_t& buffer) {
cur_sample = audio_f[c];
if (cur_sample * prev_sample < 0.0) {
z_acc += z_timer;
z_timer = 0;
z_timer = 1;
z_count++;
} else
z_timer++;
prev_sample = cur_sample;
}
if (z_count >= 30) {
z_filter_count++;
if ((z_filter_count >= Z_MIN_FILTER_COUNT) && (z_count >= Z_MIN_ZERO_CROSSINGS)) {
ctcss_message.value = (100 * 12000 / 2 * z_count) / z_acc;
shared_memory.application_queue.push(ctcss_message);
z_filter_count = 0;
z_count = 0;
z_acc = 0;
}
+4 -1
View File
@@ -36,6 +36,9 @@
#include <cstdint>
#define Z_MIN_FILTER_COUNT 224
#define Z_MIN_ZERO_CROSSINGS 20
class NarrowbandFMAudio : public BasebandProcessor {
public:
void execute(const buffer_c8_t& buffer) override;
@@ -88,7 +91,7 @@ class NarrowbandFMAudio : public BasebandProcessor {
bool pitch_rssi_enabled{false};
float cur_sample{}, prev_sample{};
uint32_t z_acc{0}, z_timer{0}, z_count{0};
uint32_t z_acc{0}, z_timer{0}, z_count{0}, z_filter_count{0};
bool ctcss_detect_enabled{true};
static constexpr float k = 32768.0f;
static constexpr float ki = 1.0f / k;
+1 -1
View File
@@ -412,7 +412,7 @@ void ILI9341::drawBMP(const ui::Point p, const uint8_t* bitmap, const bool trans
24bpp RGB
32bpp ARGB
*/
bool ILI9341::drawBMP2(const ui::Point p, const std::string file) {
bool ILI9341::drawBMP2(const ui::Point p, const std::filesystem::path& file) {
File bmpimage;
size_t file_pos = 0;
uint16_t pointer = 0;
+2 -1
View File
@@ -25,6 +25,7 @@
#include "ui.hpp"
#include "ui_text.hpp"
#include "file.hpp"
#include <cstdint>
#include <array>
@@ -58,7 +59,7 @@ class ILI9341 {
void draw_pixel(const ui::Point p, const ui::Color color);
void drawBMP(const ui::Point p, const uint8_t* bitmap, const bool transparency);
bool drawBMP2(const ui::Point p, const std::string file);
bool drawBMP2(const ui::Point p, const std::filesystem::path& file);
void render_line(const ui::Point p, const uint8_t count, const ui::Color* line_buffer);
void render_box(const ui::Point p, const ui::Size s, const ui::Color* line_buffer);
+172 -310
View File
@@ -23,27 +23,21 @@
#include "portapack_persistent_memory.hpp"
#include "audio.hpp"
#include "portapack.hpp"
#include "hal.h"
#include "utility.hpp"
#include "memory_map.hpp"
#include "crc.hpp"
#include <algorithm>
#include <utility>
#include <string>
#include <fstream>
#include "file.hpp"
#include "hal.h"
#include "irq_controls.hpp"
#include "memory_map.hpp"
#include "portapack.hpp"
#include "string_format.hpp"
#include "ui_styles.hpp"
#include "ui_painter.hpp"
#include "utility.hpp"
#include <algorithm>
#include <string>
#include <fstream>
#include <utility>
#include <ch.h>
@@ -71,9 +65,11 @@ using modem_baudrate_range_t = range_t<int32_t>;
constexpr modem_baudrate_range_t modem_baudrate_range{50, 9600};
constexpr int32_t modem_baudrate_reset_value{1200};
/*using modem_bw_range_t = range_t<int32_t>;
constexpr modem_bw_range_t modem_bw_range { 1000, 50000 };
constexpr int32_t modem_bw_reset_value { 15000 };*/
/*
using modem_bw_range_t = range_t<int32_t>;
constexpr modem_bw_range_t modem_bw_range { 1000, 50000 };
constexpr int32_t modem_bw_reset_value { 15000 };
*/
using modem_repeat_range_t = range_t<int32_t>;
constexpr modem_repeat_range_t modem_repeat_range{1, 99};
@@ -81,176 +77,38 @@ constexpr int32_t modem_repeat_reset_value{5};
using clkout_freq_range_t = range_t<uint32_t>;
constexpr clkout_freq_range_t clkout_freq_range{10, 60000};
constexpr uint32_t clkout_freq_reset_value{10000};
constexpr uint16_t clkout_freq_reset_value{10000};
enum data_structure_version_enum : uint32_t {
VERSION_CURRENT = 0x10000003,
VERSION_CURRENT = 0x10000004,
};
static const uint32_t TOUCH_CALIBRATION_MAGIC = 0x074af82f;
/* UI config.
* NB: Will be default init - override in defaults(). */
struct ui_config_t {
private:
enum bits_t {
BacklightTimeoutLSB = 0,
BacklightTimeoutEnable = 3,
ClkoutFreqLSB = 4,
ShowGUIReturnIcon = 20,
LoadAppSettings = 21,
SaveAppSettings = 22,
ShowBiggerQRCode = 23,
DisableTouchscreen = 24,
HideClock = 25,
ClockWithDate = 26,
ClkOutEnabled = 27,
UNUSED = 28,
StealthMode = 29,
ConfigLogin = 30,
ConfigSplash = 31,
};
uint16_t clkout_freq;
enum bits_mask_t : uint32_t {
BacklightTimeoutMask = ((1 << 3) - 1) << bits_t::BacklightTimeoutLSB,
ClkoutFreqMask = ((1 << 16) - 1) << bits_t::ClkoutFreqLSB,
};
// NB: bitsfields have to be the same type or the compiler will
// split into a new byte hence uint8_t for these booleans.
uint8_t backlight_timeout : 3;
uint8_t enable_backlight_timeout : 1;
uint8_t show_gui_return_icon : 1;
uint8_t load_app_settings : 1;
uint8_t save_app_settings : 1;
uint8_t show_large_qr_code : 1;
uint32_t values;
constexpr bool bit_read(const bits_t n) const {
return ((values >> n) & 1) != 0;
}
constexpr void bit_write(const bits_t n, const bool v) {
if (bit_read(n) != v) {
values ^= 1 << n;
}
}
public:
backlight_config_t config_backlight_timer() {
const auto timeout_enum = (backlight_timeout_t)((values & bits_mask_t::BacklightTimeoutMask) >> bits_t::BacklightTimeoutLSB);
const bool timeout_enabled = bit_read(bits_t::BacklightTimeoutEnable);
return backlight_config_t(timeout_enum, timeout_enabled);
}
void set_config_backlight_timer(const backlight_config_t& new_value) {
values = (values & ~bits_mask_t::BacklightTimeoutMask) | ((new_value.timeout_enum() << bits_t::BacklightTimeoutLSB) & bits_mask_t::BacklightTimeoutMask);
bit_write(bits_t::BacklightTimeoutEnable, new_value.timeout_enabled());
}
constexpr uint32_t clkout_freq() {
uint32_t freq = (values & bits_mask_t::ClkoutFreqMask) >> bits_t::ClkoutFreqLSB;
if (freq < clkout_freq_range.minimum || freq > clkout_freq_range.maximum) {
values = (values & ~bits_mask_t::ClkoutFreqMask) | (clkout_freq_reset_value << bits_t::ClkoutFreqLSB);
return clkout_freq_reset_value;
} else {
return freq;
}
}
constexpr void set_clkout_freq(uint32_t freq) {
values = (values & ~bits_mask_t::ClkoutFreqMask) | (clkout_freq_range.clip(freq) << bits_t::ClkoutFreqLSB);
}
// ui_config is an uint32_t var storing information bitwise
// bits 0-2 store the backlight timer
// bits 4-19 (16 bits) store the clkout frequency
// bits 21-31 store the different single bit configs depicted below
// bit 20 store the display state of the gui return icon, hidden (0) or shown (1)
constexpr bool show_gui_return_icon() const { // add return icon in touchscreen menue
return bit_read(bits_t::ShowGUIReturnIcon);
}
constexpr void set_gui_return_icon(bool v) {
bit_write(bits_t::ShowGUIReturnIcon, v);
}
constexpr bool load_app_settings() const { // load (last saved) app settings on startup of app
return bit_read(bits_t::LoadAppSettings);
}
constexpr void set_load_app_settings(bool v) {
bit_write(bits_t::LoadAppSettings, v);
}
constexpr bool save_app_settings() const { // save app settings when closing app
return bit_read(bits_t::SaveAppSettings);
}
constexpr void set_save_app_settings(bool v) {
bit_write(bits_t::SaveAppSettings, v);
}
constexpr bool show_bigger_qr_code() const { // show bigger QR code
return bit_read(bits_t::ShowBiggerQRCode);
}
constexpr void set_show_bigger_qr_code(bool v) {
bit_write(bits_t::ShowBiggerQRCode, v);
}
constexpr bool disable_touchscreen() const { // Option to disable touch screen
return bit_read(bits_t::DisableTouchscreen);
}
constexpr void set_disable_touchscreen(bool v) {
bit_write(bits_t::DisableTouchscreen, v);
}
constexpr bool hide_clock() const { // clock hidden from main menu
return bit_read(bits_t::HideClock);
}
constexpr void set_clock_hidden(bool v) {
bit_write(bits_t::HideClock, v);
}
constexpr bool clock_with_date() const { // show clock with date, if not hidden
return bit_read(bits_t::ClockWithDate);
}
constexpr void set_clock_with_date(bool v) {
bit_write(bits_t::ClockWithDate, v);
}
constexpr bool clkout_enabled() const {
return bit_read(bits_t::ClkOutEnabled);
}
constexpr void set_clkout_enabled(bool v) {
bit_write(bits_t::ClkOutEnabled, v);
}
constexpr bool stealth_mode() const {
return bit_read(bits_t::StealthMode);
}
constexpr void set_stealth_mode(bool v) {
bit_write(bits_t::StealthMode, v);
}
constexpr bool config_login() const {
return bit_read(bits_t::ConfigLogin);
}
constexpr void set_config_login(bool v) {
bit_write(bits_t::ConfigLogin, v);
}
constexpr bool config_splash() const {
return bit_read(bits_t::ConfigSplash);
}
constexpr void set_config_splash(bool v) {
bit_write(bits_t::ConfigSplash, v);
}
constexpr ui_config_t()
: values(
(1 << ConfigSplash) | (clkout_freq_reset_value << ClkoutFreqLSB) | (7 << BacklightTimeoutLSB)) {
}
bool disable_touchscreen : 1;
bool hide_clock : 1;
bool clock_show_date : 1;
bool clkout_enabled : 1;
bool UNUSED_1 : 1;
bool stealth_mode : 1;
bool config_login : 1;
bool config_splash : 1;
};
static_assert(sizeof(ui_config_t) == sizeof(uint32_t));
/* Additional UI config.
* NB: Will be default init - override in defaults(). */
@@ -266,67 +124,48 @@ struct ui_config2_t {
bool hide_sd_card : 1;
bool hide_mute : 1;
bool UNUSED : 7;
bool UNUSED_1 : 1;
bool UNUSED_2 : 1;
bool UNUSED_3 : 1;
bool UNUSED_4 : 1;
bool UNUSED_5 : 1;
bool UNUSED_6 : 1;
bool UNUSED_7 : 1;
uint8_t placeholder_1;
uint8_t placeholder_2;
uint8_t PLACEHOLDER_2;
uint8_t PLACEHOLDER_3;
};
static_assert(sizeof(ui_config2_t) == sizeof(uint32_t));
/* Additional config.
* NB: Will be default init - override in defaults(). */
struct misc_config_t {
private:
enum bits_t {
ConfigAudioMute = 0,
ConfigSpeakerDisable = 1,
};
bool mute_audio : 1;
bool disable_speaker : 1;
bool UNUSED_2 : 1;
bool UNUSED_3 : 1;
bool UNUSED_4 : 1;
bool UNUSED_5 : 1;
bool UNUSED_6 : 1;
bool UNUSED_7 : 1;
// misc_config_t bits:
// ConfigAudioMute = set to mute all audio output (speakers & headphones)
// ConfigSpeakerDisable = set to disable only the speaker and leave headphones enabled (only supported on AK4951 codec)
uint32_t values;
constexpr bool bit_read(const bits_t n) const {
return ((values >> n) & 1) != 0;
}
constexpr void bit_write(const bits_t n, const bool v) {
if (bit_read(n) != v) {
values ^= 1 << n;
}
}
public:
constexpr bool config_audio_mute() const {
return bit_read(bits_t::ConfigAudioMute);
}
constexpr void set_config_audio_mute(bool v) {
bit_write(bits_t::ConfigAudioMute, v);
}
constexpr bool config_speaker_disable() const {
return bit_read(bits_t::ConfigSpeakerDisable);
}
constexpr void set_config_speaker_disable(bool v) {
bit_write(bits_t::ConfigSpeakerDisable, v);
}
constexpr misc_config_t()
: values(0) {
}
uint8_t PLACEHOLDER_1;
uint8_t PLACEHOLDER_2;
uint8_t PLACEHOLDER_3;
};
static_assert(sizeof(misc_config_t) == sizeof(uint32_t));
/* IMPORTANT: Report your changes here in the dump_persistent_memory function a few lines later !! */
/* IMPORTANT: Update dump_persistent_memory (below) when changing data_t. */
/* Struct must pack the same way on M4 and M0 cores.
* NB: When adding new members, keep 32bit-aligned.*/
/* struct must pack the same way on M4 and M0 cores. */
struct data_t {
data_structure_version_enum structure_version;
int64_t target_frequency;
int32_t correction_ppb;
uint32_t touch_calibration_magic;
touch::Calibration touch_calibration;
touch::Calibration touch_calibration; // 7 * 32 bits.
// Modem
uint32_t modem_def_index;
@@ -337,7 +176,7 @@ struct data_t {
int32_t modem_baudrate;
int32_t modem_repeat;
// Play dead unlock
// Play dead unlock (Used?)
uint32_t playdead_magic;
uint32_t playing_dead;
uint32_t playdead_sequence;
@@ -356,26 +195,30 @@ struct data_t {
// Recon App
uint64_t recon_config;
bool placeholder_0;
// enable or disable converter
bool converter;
// set up converter (false) or down converter (true) converter
bool updown_converter;
bool updown_frequency_rx_correction;
bool updown_frequency_tx_correction;
bool UNUSED_4 : 1;
bool UNUSED_5 : 1;
bool UNUSED_6 : 1;
bool UNUSED_7 : 1;
// up/down converter offset
int64_t converter_frequency_offset;
// frequency correction
uint32_t frequency_rx_correction;
bool updown_frequency_rx_correction;
uint32_t frequency_tx_correction;
bool updown_frequency_tx_correction;
// Rotary encoder dial sensitivity (encoder.cpp/hpp)
uint8_t encoder_dial_sensitivity;
uint16_t encoder_dial_sensitivity : 4;
uint16_t UNUSED_8 : 12;
// Headphone volume in centibels. (Only really needs 10 bits)
int32_t headphone_volume_cb;
// Headphone volume in centibels.
int16_t headphone_volume_cb;
// Misc flags
misc_config_t misc_config;
@@ -411,15 +254,23 @@ struct data_t {
hardware_config(0),
recon_config(0),
placeholder_0(0),
converter(0),
updown_converter(0),
converter(false),
updown_converter(false),
updown_frequency_rx_correction(false),
updown_frequency_tx_correction(false),
UNUSED_4(false),
UNUSED_5(false),
UNUSED_6(false),
UNUSED_7(false),
converter_frequency_offset(0),
frequency_rx_correction(0),
updown_frequency_rx_correction(0),
frequency_tx_correction(0),
updown_frequency_tx_correction(0),
encoder_dial_sensitivity(0),
UNUSED_8(0),
headphone_volume_cb(-600),
misc_config(),
ui_config2() {
@@ -510,7 +361,10 @@ namespace cache {
void defaults() {
cached_backup_ram = backup_ram_t();
// defaults values for recon app
set_config_backlight_timer(backlight_config_t{});
set_config_splash(true);
// Default values for recon app.
set_recon_autosave_freqs(false);
set_recon_autostart_recon(true);
set_recon_continuous(true);
@@ -626,14 +480,16 @@ void set_modem_baudrate(const int32_t new_value) {
data->modem_baudrate = modem_baudrate_range.clip(new_value);
}
/*int32_t modem_bw() {
modem_bw_range.reset_if_outside(data->modem_bw, modem_bw_reset_value);
return data->modem_bw;
}
/*
int32_t modem_bw() {
modem_bw_range.reset_if_outside(data->modem_bw, modem_bw_reset_value);
return data->modem_bw;
}
void set_modem_bw(const int32_t new_value) {
data->modem_bw = modem_bw_range.clip(new_value);
}*/
void set_modem_bw(const int32_t new_value) {
data->modem_bw = modem_bw_range.clip(new_value);
}
*/
uint8_t modem_repeat() {
modem_repeat_range.reset_if_outside(data->modem_repeat, modem_repeat_reset_value);
@@ -652,56 +508,56 @@ void set_serial_format(const serial_format_t new_value) {
data->serial_format = new_value;
}
bool show_gui_return_icon() { // add return icon in touchscreen menue
return data->ui_config.show_gui_return_icon();
bool show_gui_return_icon() { // add return icon in touchscreen menu
return data->ui_config.show_gui_return_icon != 0;
}
bool load_app_settings() { // load (last saved) app settings on startup of app
return data->ui_config.load_app_settings();
return data->ui_config.load_app_settings != 0;
}
bool save_app_settings() { // save app settings when closing app
return data->ui_config.save_app_settings();
return data->ui_config.save_app_settings != 0;
}
bool show_bigger_qr_code() { // show bigger QR code
return data->ui_config.show_bigger_qr_code();
return data->ui_config.show_large_qr_code != 0;
}
bool disable_touchscreen() { // Option to disable touch screen
return data->ui_config.disable_touchscreen();
return data->ui_config.disable_touchscreen;
}
bool hide_clock() { // clock hidden from main menu
return data->ui_config.hide_clock();
bool hide_clock() { // Hide clock from main menu
return data->ui_config.hide_clock;
}
bool clock_with_date() { // show clock with date, if not hidden
return data->ui_config.clock_with_date();
bool clock_with_date() { // Show clock with date, if not hidden
return data->ui_config.clock_show_date;
}
bool clkout_enabled() {
return data->ui_config.clkout_enabled();
return data->ui_config.clkout_enabled;
}
bool config_audio_mute() {
return data->misc_config.config_audio_mute();
return data->misc_config.mute_audio;
}
bool config_speaker_disable() {
return data->misc_config.config_speaker_disable();
return data->misc_config.disable_speaker;
}
bool stealth_mode() {
return data->ui_config.stealth_mode();
return data->ui_config.stealth_mode;
}
bool config_login() {
return data->ui_config.config_login();
return data->ui_config.config_login;
}
bool config_splash() {
return data->ui_config.config_splash();
return data->ui_config.config_splash;
}
uint8_t config_cpld() {
@@ -709,59 +565,60 @@ uint8_t config_cpld() {
}
backlight_config_t config_backlight_timer() {
return data->ui_config.config_backlight_timer();
return {static_cast<backlight_timeout_t>(data->ui_config.backlight_timeout),
data->ui_config.enable_backlight_timeout == 1};
}
void set_gui_return_icon(bool v) {
data->ui_config.set_gui_return_icon(v);
data->ui_config.show_gui_return_icon = v ? 1 : 0;
}
void set_load_app_settings(bool v) {
data->ui_config.set_load_app_settings(v);
data->ui_config.load_app_settings = v ? 1 : 0;
}
void set_save_app_settings(bool v) {
data->ui_config.set_save_app_settings(v);
data->ui_config.save_app_settings = v ? 1 : 0;
}
void set_show_bigger_qr_code(bool v) {
data->ui_config.set_show_bigger_qr_code(v);
data->ui_config.show_large_qr_code = v ? 1 : 0;
}
void set_disable_touchscreen(bool v) {
data->ui_config.set_disable_touchscreen(v);
data->ui_config.disable_touchscreen = v;
}
void set_clock_hidden(bool v) {
data->ui_config.set_clock_hidden(v);
data->ui_config.hide_clock = v;
}
void set_clock_with_date(bool v) {
data->ui_config.set_clock_with_date(v);
data->ui_config.clock_show_date = v;
}
void set_clkout_enabled(bool v) {
data->ui_config.set_clkout_enabled(v);
data->ui_config.clkout_enabled = v;
}
void set_config_audio_mute(bool v) {
data->misc_config.set_config_audio_mute(v);
data->misc_config.mute_audio = v;
}
void set_config_speaker_disable(bool v) {
data->misc_config.set_config_speaker_disable(v);
data->misc_config.disable_speaker = v;
}
void set_stealth_mode(bool v) {
data->ui_config.set_stealth_mode(v);
data->ui_config.stealth_mode = v;
}
void set_config_login(bool v) {
data->ui_config.set_config_login(v);
data->ui_config.config_login = v;
}
void set_config_splash(bool v) {
data->ui_config.set_config_splash(v);
data->ui_config.config_splash = v;
}
void set_config_cpld(uint8_t i) {
@@ -769,7 +626,8 @@ void set_config_cpld(uint8_t i) {
}
void set_config_backlight_timer(const backlight_config_t& new_value) {
data->ui_config.set_config_backlight_timer(new_value);
data->ui_config.backlight_timeout = static_cast<uint8_t>(new_value.timeout_enum());
data->ui_config.enable_backlight_timeout = static_cast<uint8_t>(new_value.timeout_enabled());
}
uint32_t pocsag_last_address() {
@@ -788,12 +646,17 @@ void set_pocsag_ignore_address(uint32_t address) {
data->pocsag_ignore_address = address;
}
uint32_t clkout_freq() {
return data->ui_config.clkout_freq();
uint16_t clkout_freq() {
auto freq = data->ui_config.clkout_freq;
if (freq < clkout_freq_range.minimum || freq > clkout_freq_range.maximum)
set_clkout_freq(clkout_freq_reset_value);
return data->ui_config.clkout_freq;
}
void set_clkout_freq(uint32_t freq) {
data->ui_config.set_clkout_freq(freq);
void set_clkout_freq(uint16_t freq) {
data->ui_config.clkout_freq = freq;
}
/* Recon app */
@@ -982,26 +845,23 @@ bool should_use_sdcard_for_pmem() {
}
int save_persistent_settings_to_file() {
std::string filename = PMEM_SETTING_FILE;
delete_file(filename);
File outfile;
auto result = outfile.create(filename);
if (result.is_valid()) {
auto error = outfile.create(PMEM_SETTING_FILE);
if (error)
return false;
}
outfile.write(reinterpret_cast<char*>(&cached_backup_ram), sizeof(backup_ram_t));
return true;
}
int load_persistent_settings_from_file() {
std::string filename = PMEM_SETTING_FILE;
File infile;
auto result = infile.open(filename);
if (!result.is_valid()) {
infile.read(reinterpret_cast<char*>(&cached_backup_ram), sizeof(backup_ram_t));
return true;
}
return false;
auto error = infile.open(PMEM_SETTING_FILE);
if (error)
return false;
infile.read(reinterpret_cast<char*>(&cached_backup_ram), sizeof(backup_ram_t));
return true;
}
// Pmem size helper
@@ -1057,33 +917,37 @@ bool debug_dump() {
pmem_dump_file.write_line("tone_mix: " + to_string_dec_uint(data->tone_mix));
pmem_dump_file.write_line("hardware_config: " + to_string_dec_uint(data->hardware_config));
pmem_dump_file.write_line("recon_config: 0x" + to_string_hex(data->recon_config, 16));
pmem_dump_file.write_line("placeholder_0: " + to_string_dec_int(data->placeholder_0));
pmem_dump_file.write_line("converter: " + to_string_dec_int(data->converter));
pmem_dump_file.write_line("updown_converter: " + to_string_dec_int(data->updown_converter));
pmem_dump_file.write_line("updown_frequency_rx_correction: " + to_string_dec_int(data->updown_frequency_rx_correction));
pmem_dump_file.write_line("updown_frequency_tx_correction: " + to_string_dec_int(data->updown_frequency_tx_correction));
// pmem_dump_file.write_line("UNUSED_4: " + to_string_dec_int(data->UNUSED_4));
// pmem_dump_file.write_line("UNUSED_5: " + to_string_dec_int(data->UNUSED_5));
// pmem_dump_file.write_line("UNUSED_6: " + to_string_dec_int(data->UNUSED_6));
// pmem_dump_file.write_line("UNUSED_7: " + to_string_dec_int(data->UNUSED_7));
pmem_dump_file.write_line("converter_frequency_offset: " + to_string_dec_int(data->converter_frequency_offset));
pmem_dump_file.write_line("frequency_rx_correction: " + to_string_dec_uint(data->frequency_rx_correction));
pmem_dump_file.write_line("updown_frequency_rx_correction: " + to_string_dec_int(data->updown_frequency_rx_correction));
pmem_dump_file.write_line("frequency_tx_correction: " + to_string_dec_uint(data->frequency_tx_correction));
pmem_dump_file.write_line("updown_frequency_tx_correction: " + to_string_dec_int(data->updown_frequency_tx_correction));
pmem_dump_file.write_line("encoder_dial_sensitivity: " + to_string_dec_uint(data->encoder_dial_sensitivity));
// pmem_dump_file.write_line("UNUSED_8: " + to_string_dec_uint(data->UNUSED_8));
pmem_dump_file.write_line("headphone_volume_cb: " + to_string_dec_int(data->headphone_volume_cb));
// ui_config bits
const auto backlight_timer = portapack::persistent_memory::config_backlight_timer();
pmem_dump_file.write_line("ui_config clkout_freq: " + to_string_dec_uint(clkout_freq()));
pmem_dump_file.write_line("ui_config backlight_timer.timeout_enabled: " + to_string_dec_uint(backlight_timer.timeout_enabled()));
pmem_dump_file.write_line("ui_config backlight_timer.timeout_seconds: " + to_string_dec_uint(backlight_timer.timeout_seconds()));
pmem_dump_file.write_line("ui_config clkout_freq: " + to_string_dec_uint(clkout_freq()));
pmem_dump_file.write_line("ui_config show_gui_return_icon: " + to_string_dec_uint(data->ui_config.show_gui_return_icon()));
pmem_dump_file.write_line("ui_config load_app_settings: " + to_string_dec_uint(data->ui_config.load_app_settings()));
pmem_dump_file.write_line("ui_config save_app_settings: " + to_string_dec_uint(data->ui_config.save_app_settings()));
pmem_dump_file.write_line("ui_config show_bigger_qr_code: " + to_string_dec_uint(data->ui_config.show_bigger_qr_code()));
pmem_dump_file.write_line("ui_config disable_touchscreen: " + to_string_dec_uint(data->ui_config.disable_touchscreen()));
pmem_dump_file.write_line("ui_config hide_clock: " + to_string_dec_uint(data->ui_config.hide_clock()));
pmem_dump_file.write_line("ui_config clock_with_date: " + to_string_dec_uint(data->ui_config.clock_with_date()));
pmem_dump_file.write_line("ui_config clkout_enabled: " + to_string_dec_uint(data->ui_config.clkout_enabled()));
pmem_dump_file.write_line("ui_config stealth_mode: " + to_string_dec_uint(data->ui_config.stealth_mode()));
pmem_dump_file.write_line("ui_config config_login: " + to_string_dec_uint(data->ui_config.config_login()));
pmem_dump_file.write_line("ui_config config_splash: " + to_string_dec_uint(data->ui_config.config_splash()));
pmem_dump_file.write_line("ui_config show_gui_return_icon: " + to_string_dec_uint(data->ui_config.show_gui_return_icon));
pmem_dump_file.write_line("ui_config load_app_settings: " + to_string_dec_uint(data->ui_config.load_app_settings));
pmem_dump_file.write_line("ui_config save_app_settings: " + to_string_dec_uint(data->ui_config.save_app_settings));
pmem_dump_file.write_line("ui_config show_bigger_qr_code: " + to_string_dec_uint(data->ui_config.show_large_qr_code));
pmem_dump_file.write_line("ui_config disable_touchscreen: " + to_string_dec_uint(data->ui_config.disable_touchscreen));
pmem_dump_file.write_line("ui_config hide_clock: " + to_string_dec_uint(data->ui_config.hide_clock));
pmem_dump_file.write_line("ui_config clock_with_date: " + to_string_dec_uint(data->ui_config.clock_show_date));
pmem_dump_file.write_line("ui_config clkout_enabled: " + to_string_dec_uint(data->ui_config.clkout_enabled));
pmem_dump_file.write_line("ui_config stealth_mode: " + to_string_dec_uint(data->ui_config.stealth_mode));
pmem_dump_file.write_line("ui_config config_login: " + to_string_dec_uint(data->ui_config.config_login));
pmem_dump_file.write_line("ui_config config_splash: " + to_string_dec_uint(data->ui_config.config_splash));
// ui_config2 bits
pmem_dump_file.write_line("ui_config2 hide_speaker: " + to_string_dec_uint(data->ui_config2.hide_speaker));
@@ -1137,8 +1001,6 @@ bool debug_dump() {
// transmitter_model
pmem_dump_file.write_line("\n[Transmitter Model]");
pmem_dump_file.write_line("target_frequency: " + to_string_dec_uint(transmitter_model.target_frequency()));
pmem_dump_file.write_line("lna: " + to_string_dec_int(transmitter_model.lna()));
pmem_dump_file.write_line("vga: " + to_string_dec_int(transmitter_model.vga()));
pmem_dump_file.write_line("rf_amp: " + to_string_dec_int(transmitter_model.rf_amp()));
pmem_dump_file.write_line("baseband_bandwidth: " + to_string_dec_uint(transmitter_model.baseband_bandwidth()));
pmem_dump_file.write_line("sampling_rate: " + to_string_dec_uint(transmitter_model.sampling_rate()));
@@ -34,9 +34,10 @@
#include "volume.hpp"
// persistant memory from/to sdcard flag file
#define PMEM_FILEFLAG "/SETTINGS/PMEM_FILEFLAG"
#define PMEM_FILEFLAG u"/SETTINGS/PMEM_FILEFLAG"
// persistant memory from/to sdcard flag file
#define PMEM_SETTING_FILE "/SETTINGS/pmem_settings"
#define PMEM_SETTING_FILE u"/SETTINGS/pmem_settings"
using namespace modems;
using namespace serializer;
@@ -225,8 +226,8 @@ void set_pocsag_ignore_address(uint32_t address);
bool clkout_enabled();
void set_clkout_enabled(bool v);
uint32_t clkout_freq();
void set_clkout_freq(uint32_t freq);
uint16_t clkout_freq();
void set_clkout_freq(uint16_t freq);
/* Recon app */
bool recon_autosave_freqs();
+17 -2
View File
@@ -24,11 +24,26 @@
namespace ui {
Glyph Font::glyph(const char c) const {
size_t index;
if (c < c_start) {
// Non-display C0 Control characters - map to blank (index 0)
return {w, h, data};
}
const size_t index = c - c_start;
if (index >= c_count) {
// Handle gap in glyphs table for C1 Control characters 0x80-0x9F
if (c < C1_CONTROL_CHARS_START) {
// ASCII chars <0x80:
index = c - c_start;
} else if (c >= C1_CONTROL_CHARS_START + C1_CONTROL_CHARS_COUNT) {
// Latin 1 chars 0xA0-0xFF
index = c - c_start - C1_CONTROL_CHARS_COUNT;
} else {
// C1 Control characters - map to blank
return {w, h, data};
}
if (index >= c_count) { // Latin Extended characters > 0xFF - not supported
return {w, h, data};
} else {
return {w, h, &data[index * data_stride]};
+4
View File
@@ -28,6 +28,10 @@
#include "ui.hpp"
// C1 Control Characters are an unprintable range of glyphs missing from the font files
#define C1_CONTROL_CHARS_START 0x80
#define C1_CONTROL_CHARS_COUNT 32
namespace ui {
class Glyph {
+113 -50
View File
@@ -349,7 +349,7 @@ Text::Text(
Rect parent_rect,
std::string text)
: Widget{parent_rect},
text{text} {
text{std::move(text)} {
}
Text::Text(
@@ -357,14 +357,14 @@ Text::Text(
: Text{parent_rect, {}} {
}
void Text::set(const std::string value) {
text = value;
void Text::set(std::string_view value) {
text = std::string{value};
set_dirty();
}
void Text::paint(Painter& painter) {
const auto rect = screen_rect();
const auto s = style();
auto s = has_focus() ? style().invert() : style();
painter.fill_rectangle(rect, s.background);
@@ -1112,11 +1112,13 @@ NewButton::NewButton(
Rect parent_rect,
std::string text,
const Bitmap* bitmap,
Color color)
Color color,
bool vertical_center)
: Widget{parent_rect},
text_{text},
bitmap_{bitmap},
color_{color} {
color_{color},
vertical_center_{vertical_center} {
set_focusable(true);
}
@@ -1143,6 +1145,11 @@ void NewButton::set_color(Color color) {
set_dirty();
}
void NewButton::set_vertical_center(bool value) {
vertical_center_ = value;
set_dirty();
}
ui::Color NewButton::color() {
return color_;
}
@@ -1164,28 +1171,34 @@ void NewButton::paint(Painter& painter) {
const Style paint_style = {style().font, bg, fg};
painter.draw_rectangle({r.location(), {r.size().width(), 1}}, Color::light_grey());
painter.draw_rectangle({r.location().x(), r.location().y() + r.size().height() - 1, r.size().width(), 1}, Color::dark_grey());
painter.draw_rectangle({r.location().x() + r.size().width() - 1, r.location().y(), 1, r.size().height()}, Color::dark_grey());
painter.draw_rectangle({r.location(), {r.width(), 1}}, Color::light_grey());
painter.draw_rectangle({r.left(), r.top() + r.height() - 1, r.width(), 1}, Color::dark_grey());
painter.draw_rectangle({r.left() + r.width() - 1, r.top(), 1, r.height()}, Color::dark_grey());
painter.fill_rectangle(
{r.location().x(), r.location().y() + 1, r.size().width() - 1, r.size().height() - 2},
{r.left(), r.top() + 1, r.width() - 1, r.height() - 2},
paint_style.background);
int y = r.location().y();
int y = r.top();
if (bitmap_) {
int offset_y = vertical_center_ ? (r.height() / 2) - (bitmap_->size.height() / 2) : 6;
Point bmp_pos = {r.left() + (r.width() / 2) - (bitmap_->size.width() / 2), r.top() + offset_y};
y += bitmap_->size.height() - offset_y;
painter.draw_bitmap(
{r.location().x() + (r.size().width() / 2) - 8, r.location().y() + 6},
bmp_pos,
*bitmap_,
color_, // Color::green(), //fg,
bg);
y += 10;
}
const auto label_r = paint_style.font.size_of(text_);
painter.draw_string(
{r.location().x() + (r.size().width() - label_r.width()) / 2, y + (r.size().height() - label_r.height()) / 2},
paint_style,
text_);
if (!text_.empty()) {
const auto label_r = paint_style.font.size_of(text_);
painter.draw_string(
{r.left() + (r.width() - label_r.width()) / 2, y + (r.height() - label_r.height()) / 2},
paint_style,
text_);
}
}
void NewButton::on_focus() {
@@ -1390,12 +1403,12 @@ void ImageToggle::set_value(bool b) {
/* ImageOptionsField *****************************************************/
ImageOptionsField::ImageOptionsField(
const Rect parent_rect,
const Color foreground,
const Color background,
const options_t options)
Rect parent_rect,
Color foreground,
Color background,
options_t options)
: Widget{parent_rect},
options{options},
options{std::move(options)},
foreground_{foreground},
background_{background} {
set_focusable(true);
@@ -1437,7 +1450,7 @@ void ImageOptionsField::set_by_value(value_t v) {
}
void ImageOptionsField::set_options(options_t new_options) {
options = new_options;
options = std::move(new_options);
// Set an invalid index to force on_change.
selected_index_ = (size_t)-1;
@@ -1482,11 +1495,11 @@ bool ImageOptionsField::on_touch(const TouchEvent event) {
OptionsField::OptionsField(
Point parent_pos,
int length,
size_t length,
options_t options)
: Widget{{parent_pos, {8 * length, 16}}},
: Widget{{parent_pos, {8 * (int)length, 16}}},
length_{length},
options{options} {
options_{std::move(options)} {
set_focusable(true);
}
@@ -1494,16 +1507,20 @@ size_t OptionsField::selected_index() const {
return selected_index_;
}
size_t OptionsField::selected_index_value() const {
return options[selected_index_].second;
const OptionsField::name_t& OptionsField::selected_index_name() const {
return options_[selected_index_].first;
}
const OptionsField::value_t& OptionsField::selected_index_value() const {
return options_[selected_index_].second;
}
void OptionsField::set_selected_index(const size_t new_index, bool trigger_change) {
if (new_index < options.size()) {
if (new_index < options_.size()) {
if (new_index != selected_index() || trigger_change) {
selected_index_ = new_index;
if (on_change) {
on_change(selected_index(), options[selected_index()].second);
on_change(selected_index(), options_[selected_index()].second);
}
set_dirty();
}
@@ -1512,7 +1529,7 @@ void OptionsField::set_selected_index(const size_t new_index, bool trigger_chang
void OptionsField::set_by_value(value_t v) {
size_t new_index = 0;
for (const auto& option : options) {
for (const auto& option : options_) {
if (option.second == v) {
set_selected_index(new_index);
return;
@@ -1524,8 +1541,26 @@ void OptionsField::set_by_value(value_t v) {
set_selected_index(0);
}
void OptionsField::set_by_nearest_value(value_t v) {
size_t new_index = 0;
size_t curr_index = 0;
int32_t min_diff = INT32_MAX;
for (const auto& option : options_) {
auto diff = abs(v - option.second);
if (diff < min_diff) {
min_diff = diff;
new_index = curr_index;
}
curr_index++;
}
set_selected_index(new_index);
}
void OptionsField::set_options(options_t new_options) {
options = new_options;
options_ = std::move(new_options);
// Set an invalid index to force on_change.
selected_index_ = (size_t)-1;
@@ -1538,12 +1573,14 @@ void OptionsField::paint(Painter& painter) {
painter.fill_rectangle({screen_rect().location(), {(int)length_ * 8, 16}}, ui::Color::black());
if (selected_index() < options.size()) {
const auto text = options[selected_index()].first;
if (selected_index() < options_.size()) {
std::string_view temp = selected_index_name();
if (temp.length() > length_)
temp = temp.substr(0, length_);
painter.draw_string(
screen_pos(),
paint_style,
text);
temp);
}
}
@@ -1556,8 +1593,8 @@ void OptionsField::on_focus() {
bool OptionsField::on_encoder(const EncoderEvent delta) {
int32_t new_value = selected_index() + delta;
if (new_value < 0)
new_value = options.size() - 1;
else if ((size_t)new_value >= options.size())
new_value = options_.size() - 1;
else if ((size_t)new_value >= options_.size())
new_value = 0;
set_selected_index(new_value);
@@ -1571,9 +1608,9 @@ bool OptionsField::on_touch(const TouchEvent event) {
return true;
}
/* TextField ***********************************************************/
/* TextEdit ***********************************************************/
TextField::TextField(
TextEdit::TextEdit(
std::string& str,
size_t max_length,
Point position,
@@ -1587,24 +1624,24 @@ TextField::TextField(
set_focusable(true);
}
const std::string& TextField::value() const {
const std::string& TextEdit::value() const {
return text_;
}
void TextField::set_cursor(uint32_t pos) {
void TextEdit::set_cursor(uint32_t pos) {
cursor_pos_ = std::min<size_t>(pos, text_.length());
set_dirty();
}
void TextField::set_insert_mode() {
void TextEdit::set_insert_mode() {
insert_mode_ = true;
}
void TextField::set_overwrite_mode() {
void TextEdit::set_overwrite_mode() {
insert_mode_ = false;
}
void TextField::char_add(char c) {
void TextEdit::char_add(char c) {
// Don't add if inserting and at max_length and
// don't overwrite if past the end of the text.
if ((text_.length() >= max_length_ && insert_mode_) ||
@@ -1620,7 +1657,7 @@ void TextField::char_add(char c) {
set_dirty();
}
void TextField::char_delete() {
void TextEdit::char_delete() {
if (cursor_pos_ == 0)
return;
@@ -1629,7 +1666,7 @@ void TextField::char_delete() {
set_dirty();
}
void TextField::paint(Painter& painter) {
void TextEdit::paint(Painter& painter) {
constexpr int char_width = 8;
auto rect = screen_rect();
@@ -1665,7 +1702,7 @@ void TextField::paint(Painter& painter) {
painter.draw_rectangle(cursor_box, cursor_style.background);
}
bool TextField::on_key(const KeyEvent key) {
bool TextEdit::on_key(const KeyEvent key) {
if (key == KeyEvent::Left && cursor_pos_ > 0)
cursor_pos_--;
else if (key == KeyEvent::Right && cursor_pos_ < text_.length())
@@ -1679,7 +1716,7 @@ bool TextField::on_key(const KeyEvent key) {
return true;
}
bool TextField::on_encoder(const EncoderEvent delta) {
bool TextEdit::on_encoder(const EncoderEvent delta) {
int32_t new_pos = cursor_pos_ + delta;
// Let the encoder wrap around the ends of the text.
@@ -1692,7 +1729,7 @@ bool TextField::on_encoder(const EncoderEvent delta) {
return true;
}
bool TextField::on_touch(const TouchEvent event) {
bool TextEdit::on_touch(const TouchEvent event) {
if (event.type == TouchEvent::Type::Start)
focus();
@@ -1700,6 +1737,32 @@ bool TextField::on_touch(const TouchEvent event) {
return true;
}
/* TextField *************************************************************/
TextField::TextField(Rect parent_rect, std::string text)
: Text(parent_rect, std::move(text)) {
set_focusable(true);
}
const std::string& TextField::get_text() const {
return text;
}
void TextField::set_text(std::string_view value) {
set(value);
if (on_change)
on_change(*this);
}
bool TextField::on_key(KeyEvent key) {
if (key == KeyEvent::Select && on_select) {
on_select(*this);
return true;
}
return false;
}
/* NumberField ***********************************************************/
NumberField::NumberField(
+46 -23
View File
@@ -33,10 +33,11 @@
#include "portapack.hpp"
#include "utility.hpp"
#include <memory>
#include <vector>
#include <string>
#include <functional>
#include <memory>
#include <string>
#include <string_view>
#include <vector>
namespace ui {
@@ -202,11 +203,11 @@ class Text : public Widget {
Text(Rect parent_rect, std::string text);
Text(Rect parent_rect);
void set(const std::string value);
void set(std::string_view value);
void paint(Painter& painter) override;
private:
protected:
std::string text;
};
@@ -455,7 +456,7 @@ class NewButton : public Widget {
NewButton(const NewButton&) = delete;
NewButton& operator=(const NewButton&) = delete;
NewButton(Rect parent_rect, std::string text, const Bitmap* bitmap);
NewButton(Rect parent_rect, std::string text, const Bitmap* bitmap, Color color);
NewButton(Rect parent_rect, std::string text, const Bitmap* bitmap, Color color, bool vertical_center = false);
NewButton()
: NewButton{{}, {}, {}} {
}
@@ -463,6 +464,7 @@ class NewButton : public Widget {
void set_bitmap(const Bitmap* bitmap);
void set_text(const std::string value);
void set_color(Color value);
void set_vertical_center(bool value);
std::string text() const;
const Bitmap* bitmap();
ui::Color color();
@@ -477,6 +479,7 @@ class NewButton : public Widget {
std::string text_;
const Bitmap* bitmap_;
Color color_;
bool vertical_center_{false};
};
class Image : public Widget {
@@ -575,10 +578,10 @@ class ImageOptionsField : public Widget {
std::function<void(void)> on_show_options{};
ImageOptionsField(
const Rect parent_rect,
const Color foreground,
const Color background,
const options_t options);
Rect parent_rect,
Color foreground,
Color background,
options_t options);
ImageOptionsField()
: ImageOptionsField{{}, Color::white(), Color::black(), {}} {
@@ -615,15 +618,19 @@ class OptionsField : public Widget {
std::function<void(size_t, value_t)> on_change{};
std::function<void(void)> on_show_options{};
OptionsField(Point parent_pos, int length, options_t options);
OptionsField(Point parent_pos, size_t length, options_t options);
options_t& options() { return options_; }
const options_t& options() const { return options_; }
void set_options(options_t new_options);
size_t selected_index() const;
size_t selected_index_value() const;
const name_t& selected_index_name() const;
const value_t& selected_index_value() const;
void set_selected_index(const size_t new_index, bool trigger_change = true);
void set_by_value(value_t v);
void set_by_nearest_value(value_t v);
void paint(Painter& painter) override;
@@ -632,19 +639,19 @@ class OptionsField : public Widget {
bool on_touch(const TouchEvent event) override;
private:
const int length_;
options_t options;
const size_t length_;
options_t options_;
size_t selected_index_{0};
};
// A TextField is bound to a string reference and allows the string
// A TextEdit is bound to a string reference and allows the string
// to be manipulated. The field itself does not provide the UI for
// setting the value. It provides the UI of rendering the text,
// a cursor, and an API to edit the string content.
class TextField : public Widget {
class TextEdit : public Widget {
public:
TextField(std::string& str, Point position, uint32_t length = 30)
: TextField{str, 64, position, length} {}
TextEdit(std::string& str, Point position, uint32_t length = 30)
: TextEdit{str, 64, position, length} {}
// Str: the string containing the content to edit.
// Max_length: max length the string is allowed to use.
@@ -653,12 +660,12 @@ class TextField : public Widget {
// - Characters are 8 pixels wide.
// - The screen can show 30 characters max.
// - The control is 16 pixels tall.
TextField(std::string& str, size_t max_length, Point position, uint32_t length = 30);
TextEdit(std::string& str, size_t max_length, Point position, uint32_t length = 30);
TextField(const TextField&) = delete;
TextField(TextField&&) = delete;
TextField& operator=(const TextField&) = delete;
TextField& operator=(TextField&&) = delete;
TextEdit(const TextEdit&) = delete;
TextEdit(TextEdit&&) = delete;
TextEdit& operator=(const TextEdit&) = delete;
TextEdit& operator=(TextEdit&&) = delete;
const std::string& value() const;
@@ -683,6 +690,22 @@ class TextField : public Widget {
bool insert_mode_;
};
class TextField : public Text {
public:
std::function<void(TextField&)> on_select{};
std::function<void(TextField&)> on_change{};
TextField(Rect parent_rect, std::string text);
const std::string& get_text() const;
void set_text(std::string_view value);
bool on_key(KeyEvent key) override;
private:
using Text::set;
};
class NumberField : public Widget {
public:
std::function<void(NumberField&)> on_select{};
Binary file not shown.

After

Width:  |  Height:  |  Size: 234 B

+10
View File
@@ -39,11 +39,20 @@ add_executable(application_test EXCLUDE_FROM_ALL
${PROJECT_SOURCE_DIR}/test_convert.cpp
${PROJECT_SOURCE_DIR}/test_file_reader.cpp
${PROJECT_SOURCE_DIR}/test_file_wrapper.cpp
${PROJECT_SOURCE_DIR}/test_freqman_db.cpp
${PROJECT_SOURCE_DIR}/test_mock_file.cpp
${PROJECT_SOURCE_DIR}/test_optional.cpp
${PROJECT_SOURCE_DIR}/test_string_format.cpp
${PROJECT_SOURCE_DIR}/test_utility.cpp
${PROJECT_SOURCE_DIR}/../../application/file_reader.cpp
${PROJECT_SOURCE_DIR}/../../application/freqman_db.cpp
# Dependencies
${PROJECT_SOURCE_DIR}/../../application/file.cpp
${PROJECT_SOURCE_DIR}/../../application/string_format.cpp
${PROJECT_SOURCE_DIR}/../../application/tone_key.cpp
${PROJECT_SOURCE_DIR}/linker_stubs.cpp
)
target_include_directories(application_test PRIVATE
@@ -62,6 +71,7 @@ target_include_directories(application_test PRIVATE
)
target_compile_options(application_test PRIVATE
-std=c++17
-DLPC43XX
-DLPC43XX_M0
-D__NEWLIB__
@@ -0,0 +1,80 @@
/*
* 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.
*/
/* This file contains stub functions necessary to enable linking.
* Try to minimize dependecies by breaking code into separate files
* or using templates and mock types. Because the test code is built
* and executed on the dev machine, a lot of core firmware code
* will not or cannot work (e.g. filesystem). We could build abstractions
* but that's just device overhead that only supports testing. */
#include <string>
/* FatFS stubs */
#include "ff.h"
FRESULT f_close(FIL*) {
return FR_OK;
}
FRESULT f_closedir(DIR*) {
return FR_OK;
}
FRESULT f_findfirst(DIR*, FILINFO*, const TCHAR*, const TCHAR*) {
return FR_OK;
}
FRESULT f_findnext(DIR*, FILINFO*) {
return FR_OK;
}
FRESULT f_getfree(const TCHAR*, DWORD*, FATFS**) {
return FR_OK;
}
FRESULT f_lseek(FIL*, FSIZE_t) {
return FR_OK;
}
FRESULT f_mkdir(const TCHAR*) {
return FR_OK;
}
FRESULT f_open(FIL*, const TCHAR*, BYTE) {
return FR_OK;
}
FRESULT f_read(FIL*, void*, UINT, UINT*) {
return FR_OK;
}
FRESULT f_rename(const TCHAR*, const TCHAR*) {
return FR_OK;
}
FRESULT f_stat(const TCHAR*, FILINFO*) {
return FR_OK;
}
FRESULT f_sync(FIL*) {
return FR_OK;
}
FRESULT f_truncate(FIL*) {
return FR_OK;
}
FRESULT f_unlink(const TCHAR*) {
return FR_OK;
}
FRESULT f_write(FIL*, const void*, UINT, UINT*) {
return FR_OK;
}
/* Debug */
void __debug_log(const std::string&) {}
@@ -38,6 +38,20 @@ TEST_CASE("It can iterate file lines.") {
CHECK_EQ(line_count, 3);
}
TEST_CASE("It can iterate multiple times.") {
MockFile f{"abc\ndef\nhij"};
BufferLineReader<MockFile> reader{f};
int line_count = 0;
int line_count2 = 0;
for (const auto& line : reader)
++line_count;
for (const auto& line : reader)
++line_count2;
CHECK_EQ(line_count, 3);
CHECK_EQ(line_count2, 3);
}
TEST_CASE("It can iterate file ending with newline.") {
MockFile f{"abc\ndef\nhij\n"};
BufferLineReader<MockFile> reader{f};
@@ -133,6 +147,18 @@ TEST_CASE("It will split only empty columns.") {
TEST_SUITE_END();
TEST_CASE("count_lines returns 1 for single line") {
MockFile f{"abs"};
BufferLineReader<MockFile> reader{f};
CHECK_EQ(count_lines(reader), 1);
}
TEST_CASE("count_lines returns 2 for 2 lines") {
MockFile f{"abs"};
BufferLineReader<MockFile> reader{f};
CHECK_EQ(count_lines(reader), 1);
}
/* Simple example of how to use this to read settings by lines. */
TEST_CASE("It can parse a settings file.") {
MockFile f{"100,File.txt,5\n200,File2.txt,7"};
@@ -0,0 +1,247 @@
/*
* 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 "doctest.h"
#include "freqman_db.hpp"
TEST_SUITE_BEGIN("Freqman Parsing");
TEST_CASE("It can parse basic single freq entry.") {
freqman_entry e;
REQUIRE(
parse_freqman_entry(
"f=123000000,d=This is the description.", e));
CHECK_EQ(e.frequency_a, 123'000'000);
CHECK_EQ(e.description, "This is the description.");
CHECK_EQ(e.type, freqman_type::Single);
}
TEST_CASE("It can parse basic range freq entry.") {
freqman_entry e;
REQUIRE(
parse_freqman_entry(
"a=123000000,b=423000000,d=This is the description.", e));
CHECK_EQ(e.frequency_a, 123'000'000);
CHECK_EQ(e.frequency_b, 423'000'000);
CHECK_EQ(e.description, "This is the description.");
CHECK_EQ(e.type, freqman_type::Range);
}
TEST_CASE("It can parse basic ham radio freq entry.") {
freqman_entry e;
REQUIRE(
parse_freqman_entry(
"r=123000000,t=423000000,d=This is the description.", e));
CHECK_EQ(e.frequency_a, 123'000'000);
CHECK_EQ(e.frequency_b, 423'000'000);
CHECK_EQ(e.description, "This is the description.");
CHECK_EQ(e.type, freqman_type::HamRadio);
}
TEST_CASE("It can parse modulation") {
freqman_entry e;
REQUIRE(
parse_freqman_entry(
"f=123000000,d=This is the description.,m=AM", e));
CHECK_EQ(e.modulation, 0);
REQUIRE(
parse_freqman_entry(
"f=123000000,d=This is the description.,m=NFM", e));
CHECK_EQ(e.modulation, 1);
REQUIRE(
parse_freqman_entry(
"f=123000000,d=This is the description.,m=WFM", e));
CHECK_EQ(e.modulation, 2);
REQUIRE(
parse_freqman_entry(
"f=123000000,d=This is the description.,m=SPEC", e));
CHECK_EQ(e.modulation, 3);
REQUIRE(
parse_freqman_entry(
"f=123000000,d=This is the description.,m=FOO", e));
CHECK_EQ(e.modulation, freqman_invalid_index);
}
TEST_CASE("It can parse bandwidth") {
freqman_entry e;
REQUIRE(
parse_freqman_entry(
"f=123000000,d=This is the description.,m=AM,bw=DSB 6k", e));
CHECK_EQ(e.bandwidth, 1);
REQUIRE(
parse_freqman_entry(
"f=123000000,d=This is the description.,bw=DSB 6k", e));
// Modulation wasn't set.
CHECK_EQ(e.bandwidth, freqman_invalid_index);
REQUIRE(
parse_freqman_entry(
"f=123000000,d=This is the description.,m=WFM,bw=50k", e));
// Invalid bandwidth value.
CHECK_EQ(e.bandwidth, freqman_invalid_index);
REQUIRE(
parse_freqman_entry(
"f=123000000,d=This is the description.,m=SPEC,bw=16k", e));
CHECK_EQ(e.modulation, 3);
REQUIRE(
parse_freqman_entry(
"f=123000000,m=NFM,bw=11k,d=This is the description.", e));
CHECK_EQ(e.modulation, 1);
}
TEST_CASE("It can parse frequency step") {
freqman_entry e;
REQUIRE(
parse_freqman_entry(
"f=123000000,d=This is the description.,s=0.1kHz", e));
CHECK_EQ(e.step, 0);
REQUIRE(
parse_freqman_entry(
"f=123000000,d=This is the description.,s=50kHz", e));
CHECK_EQ(e.step, 11);
REQUIRE(
parse_freqman_entry(
"f=123000000,d=This is the description.,s=FOO", e));
CHECK_EQ(e.step, freqman_invalid_index);
}
TEST_CASE("It can parse tone freq") {
freqman_entry e;
REQUIRE(
parse_freqman_entry(
"f=123000000,d=This is the description.,c=0.0", e));
CHECK_EQ(e.tone, 0);
REQUIRE(
parse_freqman_entry(
"f=123000000,c=67.0,d=This is the description.", e));
CHECK_EQ(e.tone, 1);
REQUIRE(
parse_freqman_entry(
"f=123000000,c=67,d=This is the description.", e));
// Fractional can be omitted.
CHECK_EQ(e.tone, 1);
REQUIRE(
parse_freqman_entry(
"f=123000000,c=69.33,d=This is the description.", e));
// Fractional extra digits can be omitted.
CHECK_EQ(e.tone, 2);
REQUIRE(
parse_freqman_entry(
"f=123000000,d=This is the description.,c=72", e));
// Should choose nearest.
CHECK_EQ(e.tone, 3);
}
TEST_CASE("It can serialize basic Single entry") {
auto str = to_freqman_string(freqman_entry{
.frequency_a = 123'456'000,
.description = "Foobar",
.type = freqman_type::Single,
});
CHECK(str == "f=123456000,d=Foobar");
}
TEST_CASE("It can serialize basic Range entry") {
auto str = to_freqman_string(freqman_entry{
.frequency_a = 123'456'000,
.frequency_b = 423'456'000,
.description = "Foobar",
.type = freqman_type::Range,
});
CHECK(str == "a=123456000,b=423456000,d=Foobar");
}
TEST_CASE("It can serialize basic HamRadio entry") {
auto str = to_freqman_string(freqman_entry{
.frequency_a = 123'456'000,
.frequency_b = 423'456'000,
.description = "Foobar",
.type = freqman_type::HamRadio,
});
CHECK(str == "r=123456000,t=423456000,d=Foobar");
}
// New tables for a future PR.
/*
TEST_CASE("It can parse modulation") {
freqman_entry e;
REQUIRE(
parse_freqman_entry(
"f=123000000,d=This is the description.,m=AM", e));
CHECK_EQ(e.modulation, freqman_modulation::AM);
REQUIRE(
parse_freqman_entry(
"f=123000000,d=This is the description.,m=NFM", e));
CHECK_EQ(e.modulation, freqman_modulation::NFM);
REQUIRE(
parse_freqman_entry(
"f=123000000,d=This is the description.,m=WFM", e));
CHECK_EQ(e.modulation, freqman_modulation::WFM);
REQUIRE(
parse_freqman_entry(
"f=123000000,d=This is the description.,m=SPEC", e));
CHECK_EQ(e.modulation, freqman_modulation::SPEC);
REQUIRE(
parse_freqman_entry(
"f=123000000,d=This is the description.,m=FOO", e));
CHECK_EQ(e.modulation, freqman_modulation::Unknown);
}
TEST_CASE("It can parse frequency step") {
freqman_entry e;
REQUIRE(
parse_freqman_entry(
"f=123000000,d=This is the description.,s=0.1kHz", e));
CHECK_EQ(e.step, freqman_step::_100Hz);
REQUIRE(
parse_freqman_entry(
"f=123000000,d=This is the description.,s=50kHz", e));
CHECK_EQ(e.step, freqman_step::_50kHz);
REQUIRE(
parse_freqman_entry(
"f=123000000,d=This is the description.,s=FOO", e));
CHECK_EQ(e.step, freqman_step::Unknown);
}
*/
TEST_SUITE_END();
@@ -0,0 +1,81 @@
/*
* 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 "doctest.h"
#include "string_format.hpp"
/* TODO: Tests for all string_format functions. */
TEST_CASE("to_string_dec_uint64 returns correct value.") {
CHECK_EQ(to_string_dec_uint64(0), "0");
CHECK_EQ(to_string_dec_uint64(1), "1");
CHECK_EQ(to_string_dec_uint64(1'000'000), "1000000");
CHECK_EQ(to_string_dec_uint64(1'234'567'890), "1234567890");
CHECK_EQ(to_string_dec_uint64(1'234'567'891), "1234567891");
}
TEST_CASE("to_string_freq returns correct value.") {
CHECK_EQ(to_string_freq(0), " 0");
CHECK_EQ(to_string_freq(1), " 1");
CHECK_EQ(to_string_freq(1'000'000), " 1000000");
CHECK_EQ(to_string_freq(1'234'567'890), "1234567890");
CHECK_EQ(to_string_freq(1'234'567'891), "1234567891");
}
TEST_CASE("to_string_short_freq returns correct value.") {
CHECK_EQ(to_string_short_freq(0), " 0.0000");
CHECK_EQ(to_string_short_freq(49), " 0.0000");
CHECK_EQ(to_string_short_freq(50), " 0.0001");
CHECK_EQ(to_string_short_freq(1'000'049), " 1.0000");
CHECK_EQ(to_string_short_freq(1'000'050), " 1.0001");
CHECK_EQ(to_string_short_freq(1'234'567'850), "1234.5679");
CHECK_EQ(to_string_short_freq(1'234'567'849), "1234.5678");
}
TEST_CASE("to_string_rounded_freq returns correct value.") {
CHECK_EQ(to_string_rounded_freq(0, 0), "0");
CHECK_EQ(to_string_rounded_freq(0, 2), "0.00");
CHECK_EQ(to_string_rounded_freq(49, 4), "0.0000");
CHECK_EQ(to_string_rounded_freq(50, 4), "0.0001");
CHECK_EQ(to_string_rounded_freq(23'456, 4), "0.0235");
CHECK_EQ(to_string_rounded_freq(1'000'000, 4), "1.0000");
CHECK_EQ(to_string_rounded_freq(1'000'567'849, 4), "1000.5678");
CHECK_EQ(to_string_rounded_freq(1'234'567'891, 0), "1234");
CHECK_EQ(to_string_rounded_freq(1'234'567'891, 1), "1234.6");
CHECK_EQ(to_string_rounded_freq(1'234'567'891, 2), "1234.57");
CHECK_EQ(to_string_rounded_freq(1'234'567'891, 3), "1234.568");
CHECK_EQ(to_string_rounded_freq(1'234'567'891, 4), "1234.5679");
CHECK_EQ(to_string_rounded_freq(1'234'567'891, 5), "1234.56789");
CHECK_EQ(to_string_rounded_freq(1'234'567'891, 6), "1234.567891");
CHECK_EQ(to_string_rounded_freq(1'234'567'891, 9), "1234.567891");
}
TEST_CASE("trim removes whitespace.") {
CHECK(trim(" foo\n") == "foo");
}
TEST_CASE("trim returns empty for only whitespace.") {
CHECK(trim(" \n").empty());
}
TEST_CASE("trim empty returns empty.") {
CHECK(trim("").empty());
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
These fonts are for free and can be use for commercial and non-commercial purposes. These aren't professionally made--only beta test. Let me know if there are problems with spaces and kernings.
+93
View File
@@ -0,0 +1,93 @@
Copyright 2017 The Sedgwick Ave Project Authors (https://github.com/googlefonts/sedgwickave)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

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