Compare commits

...

10 Commits

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

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

* Refactor load to user FreqmanDB

* Recon file parsing cleanup

* use strtol for parse_int

* recon file cleanup

* Fix bugs in Recon changes

* PR feedback

---------

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

* format_issues_Mic_app

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

* Workaround for worn encoders & more sensitivity levels

* Workaround for worn encoders & more sensitivity levels

* Encoder sensitivity levels 0-4

* Encoder sensitivity levels 0-4

* Fix comment

* Clang

* Clang

* Change default encoder sensitivity in pmem

* Set default encoder sensitivity

* Set default encoder sensitivity

* Revert widget type for encoder sensitivity

* Revert widget type for encoder dial sensitivity

* Revert worn encoder workaround (didn't work)

* 5 levels of encoder dial sensitivity

* 5 levels of encoder dial sensitivity
2023-07-16 17:05:27 -05:00
33 changed files with 527 additions and 556 deletions
@@ -25,10 +25,10 @@
#include "audio.hpp"
#include "baseband_api.hpp"
#include "file.hpp"
#include "freqman.hpp"
#include "portapack.hpp"
#include "portapack_persistent_memory.hpp"
#include "string_format.hpp"
#include "ui_freqman.hpp"
#include "utility.hpp"
using namespace portapack;
@@ -23,6 +23,7 @@
#include "capture_app.hpp"
#include "baseband_api.hpp"
#include "portapack.hpp"
#include "ui_freqman.hpp"
using namespace portapack;
@@ -47,6 +48,14 @@ CaptureAppView::CaptureAppView(NavigationView& nav)
&waterfall,
});
/* THE ONE LINE BELOW IS A TEMPORARY KLUDGE WORKAROUND FOR A MYSTERY M4 BASEBAND HANG ISSUE WHEN THE CAPTURE
APP IS THE FIRST APP STARTED AFTER POWER-UP, OR THE CAPTURE APP IS RUN AFTER RUNNING REPLAY WITH A HIGH
SAMPLE RATE. IT SHOULD NOT BE NECESSARY SINCE sampling_rate IS ALREADY INITIALIZED BY RADIOSTATE BUT
APPARENTLY IS ADDING JUST THE RIGHT AMOUNT OF DELAY. ISSUE DOES NOT AFFECT ALL PORTAPACK UNITS.
INVESTIGATION IS ONGOING, SEE ISSUE #1283. */
receiver_model.set_sampling_rate(3072000);
/* END MYSTERY HANG WORKAROUND. */
field_frequency_step.set_by_value(receiver_model.frequency_step());
field_frequency_step.on_change = [this](size_t, OptionsField::value_t v) {
receiver_model.set_frequency_step(v);
@@ -31,7 +31,6 @@
#include "ui_spectrum.hpp"
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "freqman.hpp"
namespace ui {
+2 -2
View File
@@ -65,12 +65,12 @@ void GpsSimAppView::on_file_changed(const fs::path& new_file_path) {
field_frequency.set_value(metadata->center_frequency);
sample_rate = metadata->sample_rate;
} else {
sample_rate = 500000;
sample_rate = 2600000;
}
// UI Fixup.
text_sample_rate.set(unit_auto_scale(sample_rate, 3, 1) + "Hz");
progressbar.set_max(file_size / 1024);
progressbar.set_max(file_size);
text_filename.set(truncate(file_path.filename().string(), 12));
auto duration = ms_duration(file_size, sample_rate, 2);
+30 -8
View File
@@ -24,7 +24,6 @@
#include "ui_freqman.hpp"
#include "event_m0.hpp"
#include "freqman.hpp"
#include "portapack.hpp"
#include "rtc_time.hpp"
#include "tone_key.hpp"
@@ -35,11 +34,33 @@
#include <memory>
using namespace portapack;
using namespace ui;
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;
using options_t = OptionsField::options_t;
extern options_t freqman_modulations;
extern options_t freqman_bandwidths[4];
extern options_t freqman_steps;
extern options_t freqman_steps_short;
/* Set options. */
void freqman_set_modulation_option(OptionsField& option) {
option.set_options(freqman_modulations);
}
void freqman_set_bandwidth_option(freqman_index_t modulation, OptionsField& option) {
if (is_valid(modulation))
option.set_options(freqman_bandwidths[modulation]);
}
void freqman_set_step_option(OptionsField& option) {
option.set_options(freqman_steps);
}
void freqman_set_step_option_short(OptionsField& option) {
option.set_options(freqman_steps_short);
}
namespace ui {
@@ -155,7 +176,7 @@ FrequencySaveView::FrequencySaveView(
};
button_save.on_select = [this, &nav](Button&) {
db_.insert_entry(entry_, db_.entry_count());
db_.insert_entry(db_.entry_count(), entry_);
nav_.pop();
};
}
@@ -255,7 +276,7 @@ void FrequencyManagerView::on_add_entry() {
};
// Add will insert below the currently selected item.
db_.insert_entry(entry, current_index() + 1);
db_.insert_entry(current_index() + 1, entry);
refresh_list(1);
}
@@ -488,12 +509,13 @@ void FrequencyEditView::populate_step_options() {
}
void FrequencyEditView::populate_tone_options() {
using namespace tonekey;
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});
for (auto i = 1u; i < tone_keys.size(); ++i) {
auto& item = tone_keys[i];
options.push_back({fx100_string(item.second), (OptionsField::value_t)i});
}
field_tone.set_options(std::move(options));
+6
View File
@@ -256,3 +256,9 @@ class FrequencyEditView : public View {
};
} /* namespace ui */
void freqman_set_bandwidth_option(freqman_index_t modulation, ui::OptionsField& option);
void freqman_set_modulation_option(ui::OptionsField& option);
void freqman_set_step_option(ui::OptionsField& option);
void freqman_set_step_option_short(ui::OptionsField& option);
void freqman_set_tone_option(ui::OptionsField& option);
+1
View File
@@ -23,6 +23,7 @@
#include "ui_level.hpp"
#include "ui_fileman.hpp"
#include "ui_freqman.hpp"
#include "file.hpp"
using namespace portapack;
+12 -12
View File
@@ -24,21 +24,21 @@
#ifndef _UI_LEVEL
#define _UI_LEVEL
#include "ui.hpp"
#include "receiver_model.hpp"
#include "ui_receiver.hpp"
#include "ui_styles.hpp"
#include "freqman.hpp"
#include "analog_audio_app.hpp"
#include "audio.hpp"
#include "ui_mictx.hpp"
#include "portapack_persistent_memory.hpp"
#include "baseband_api.hpp"
#include "ui_spectrum.hpp"
#include "string_format.hpp"
#include "file.hpp"
#include "app_settings.hpp"
#include "audio.hpp"
#include "baseband_api.hpp"
#include "file.hpp"
#include "freqman_db.hpp"
#include "portapack_persistent_memory.hpp"
#include "radio_state.hpp"
#include "receiver_model.hpp"
#include "string_format.hpp"
#include "ui.hpp"
#include "ui_mictx.hpp"
#include "ui_receiver.hpp"
#include "ui_spectrum.hpp"
#include "ui_styles.hpp"
namespace ui {
+17 -22
View File
@@ -25,6 +25,7 @@
#include "audio.hpp"
#include "baseband_api.hpp"
#include "irq_controls.hpp"
#include "ui_freqman.hpp"
#include "portapack_hal.hpp"
#include "string_format.hpp"
#include "tonesets.hpp"
@@ -355,13 +356,10 @@ MicTXView::MicTXView(
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.
rxbw.emplace_back(" NFM1:8k5 ", 0); // restore the original dynamic field_rxbw value.
rxbw.emplace_back(" NFM2:11k ", 1);
rxbw.emplace_back(" FM :16k ", 2);
field_rxbw.set_options(rxbw); // store that aux GUI option to the field_rxbw.
field_rxbw.hidden(0); // we are in FM mode, we need to allow the user set up of the RX NFM BW selection (8K5, 11K, 16K)
field_bw.hidden(0); // we are in FM mode, we need to allow FM deviation parameter, in non FM mode.
freqman_set_bandwidth_option(NFM_MODULATION, field_rxbw); // restore dynamic field_rxbw value with NFM options from freqman_db.cpp
field_rxbw.set_by_value(2); // // bw 16k (2) default
field_rxbw.hidden(0); // we are in FM mode, we need to allow the user set up of the RX NFM BW selection (8K5, 11K, 16K)
field_bw.hidden(0); // we are in FM mode, we need to allow FM deviation parameter, in non FM mode.
break;
case 1: //{ "WFM", 1 }
enable_am = false;
@@ -375,13 +373,10 @@ MicTXView::MicTXView(
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.
rxbw.emplace_back(" 200k-WFM ", 0); // We allow the user selection of the 3 x WFM BW filters, (0) WFM-200K, (1) WFM-180K, (2) WFM-40K.
rxbw.emplace_back(" 180k-WFM ", 1);
rxbw.emplace_back(" 40k-WFM ", 2);
field_rxbw.set_options(rxbw); // store that aux GUI option to the field_rxbw.
field_rxbw.hidden(0); // we are in WFM mode, we need to show to the user the selected BW WFM filter.
field_bw.hidden(0); // we are in WFM mode, we need to allow WFM deviation parameter, in non FM mode.
freqman_set_bandwidth_option(WFM_MODULATION, field_rxbw); // restore dynamic field_rxbw value with WFM options from freqman_db.cpp
field_rxbw.set_by_value(0); // bw 200k (0) default
field_rxbw.hidden(0); // we are in WFM mode, we need to show to the user the selected BW WFM filter.
field_bw.hidden(0); // we are in WFM mode, we need to allow WFM deviation parameter, in non FM mode.
break;
case 2: //{ "AM", 2 }
enable_am = true;
@@ -390,8 +385,8 @@ MicTXView::MicTXView(
set_dirty(); // Refresh display
options_tone_key.hidden(1); // we hide that Key-tones & CTCSS input selecction, (no meaning in AM/DSB/SSB).
rxbw.emplace_back(" DSB1-9k ", 0); // we offer in AM DSB two audio BW 9k / 6k.
rxbw.emplace_back(" DSB2-6k ", 1);
rxbw.emplace_back("DSB 9k", 0); // we offer in AM DSB two audio BW 9k / 6k.
rxbw.emplace_back("DSB 6k", 1);
field_rxbw.set_options(rxbw); // store that aux GUI option to the field_rxbw.
field_rxbw.hidden(0); // we show fixed RX AM BW 6Khz
@@ -404,8 +399,8 @@ MicTXView::MicTXView(
check_rogerbeep.set_value(false); // reset the possible activation of roger beep, because it is not compatible with SSB, by now.
check_rogerbeep.hidden(1); // hide that roger beep selection.
rxbw.emplace_back(" USB+3k ", 0); // locked a fixed option, to display it.
field_rxbw.set_options(rxbw); // store that aux GUI option to the field_rxbw.
rxbw.emplace_back("USB+3k", 0); // locked a fixed option, to display it.
field_rxbw.set_options(rxbw); // store that aux GUI option to the field_rxbw.
set_dirty(); // Refresh display
break;
@@ -415,8 +410,8 @@ MicTXView::MicTXView(
check_rogerbeep.set_value(false); // reset the possible activation of roger beep, because it is not compatible with SSB, by now.
check_rogerbeep.hidden(1); // hide that roger beep selection.
rxbw.emplace_back(" LSB-3k ", 0); // locked a fixed option, to display it.
field_rxbw.set_options(rxbw); // store that aux GUI option to the field_rxbw.
rxbw.emplace_back("LSB-3k", 0); // locked a fixed option, to display it.
field_rxbw.set_options(rxbw); // store that aux GUI option to the field_rxbw.
set_dirty(); // Refresh display
break;
@@ -425,8 +420,8 @@ MicTXView::MicTXView(
rxaudio(rx_enabled); // Update now if we have RX audio on
check_rogerbeep.hidden(0); // make visible again the "rogerbeep" selection.
rxbw.emplace_back("SSB1:USB+3k", 0); // added dynamically two options (index 0,1) to that DSB-C case to the field_rxbw value.
rxbw.emplace_back("SSB2:LSB-3k", 1);
rxbw.emplace_back("USB+3k", 0); // added dynamically two options (index 0,1) to that DSB-SC case to the field_rxbw value.
rxbw.emplace_back("LSB-3k", 1);
field_rxbw.set_options(rxbw); // store that aux GUI option to the field_rxbw.
+6 -6
View File
@@ -180,7 +180,7 @@ class MicTXView : public View {
OptionsField options_ak4951_alc_mode{
{20 * 8, 1 * 8},
11,
10, // Label has 10 chars
{
{" 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)
@@ -198,7 +198,7 @@ class MicTXView : public View {
OptionsField options_wm8731_boost_mode{
{22 * 8, 1 * 8},
5,
8, // Label has 8 chars
{
{"ON +12dB", 0}, // WM8731 Mic Boost ON, original+12dBs condition, easy to saturate ADC sat in high voice, relative G = +12 dB's respect ref level
{"ON +06dB", 1}, // WM8731 Mic Boost ON, original+6 dBs condition, easy to saturate ADC sat in high voice, relative G = +06 dB's respect ref level
@@ -298,11 +298,11 @@ class MicTXView : public View {
OptionsField field_rxbw{
{19 * 8, (23 * 8) + 2},
3,
7,
{
{" NFM1:8k5 ", 0},
{" NFM2:11k ", 1},
{" FM :16k ", 2},
{" 8k5 ", 0}, // Initial dynamic values when we start Mic App.
{" 11k ", 1},
{" 16k ", 2},
}};
NumberField field_squelch{
+116 -228
View File
@@ -23,13 +23,17 @@
#include "ui_recon.hpp"
#include "ui_fileman.hpp"
#include "ui_freqman.hpp"
#include "capture_app.hpp"
#include "convert.hpp"
#include "file.hpp"
#include "file_reader.hpp"
#include "tone_key.hpp"
using namespace portapack;
using namespace tonekey;
using portapack::memory::map::backup_ram;
namespace fs = std::filesystem;
namespace ui {
@@ -124,152 +128,81 @@ 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;
bool ReconView::recon_save_freq(const fs::path& path, size_t freq_index, bool warn_if_exists) {
if (frequency_list.size() == 0 || !current_is_valid())
return false;
FreqmanDB freq_db;
if (!freq_db.open(path, /*create*/ true))
return false;
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 = freqman_type::Single;
// 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()) {
char one_char[1]{}; // Read it char by char
std::string line{}; // and put read line in here
bool found{false};
for (size_t pointer = 0; pointer < recon_file.size(); pointer++) {
recon_file.seek(pointer);
recon_file.read(one_char, 1);
if ((int)one_char[0] > 31) { // ascii space upwards
line += one_char[0]; // add it to the textline
} else if (one_char[0] == '\n') { // New Line
if (line.compare(0, frequency_to_add.size(), frequency_to_add) == 0) {
found = true;
break;
}
line.clear(); // Ready for next textline
}
}
if (!found) {
result = recon_file.append(freq_file_path); // Second: append if it is not there
if (!result.is_valid()) {
recon_file.write_line(frequency_to_add);
}
}
if (found && warn_if_exists) {
nav_.display_modal("Error", "Frequency already exists");
}
} else {
result = recon_file.create(freq_file_path); // First freq if no output file
if (!result.is_valid()) {
recon_file.write_line(frequency_to_add);
}
// For ranges, save the current frequency instead.
if (entry.type == freqman_type::Range) {
entry.frequency_a = freq;
entry.frequency_b = 0;
entry.type = freqman_type::Single;
entry.modulation = last_entry.modulation;
entry.bandwidth = last_entry.bandwidth;
entry.step = freqman_invalid_index;
}
auto it = freq_db.find_entry(entry);
auto found = (it != freq_db.end());
if (found && warn_if_exists)
nav_.display_modal("Error", "Frequency already exists");
if (!found)
freq_db.append_entry(entry);
return true;
}
bool ReconView::recon_load_config_from_sd() {
File settings_file{};
size_t length{0};
size_t file_position{0};
char* pos{NULL};
char* line_start{NULL};
char* line_end{NULL};
char file_data[257]{};
uint32_t it{0};
uint32_t nb_params{RECON_SETTINGS_NB_PARAMS};
std::string params[RECON_SETTINGS_NB_PARAMS]{};
make_new_directory(u"SETTINGS");
auto result = settings_file.open(RECON_CFG_FILE);
if (!result.is_valid()) {
while (it < nb_params) {
// Read a 256 bytes block from file
settings_file.seek(file_position);
memset(file_data, 0, 257);
auto read_size = settings_file.read(file_data, 256);
if (read_size.is_error())
break;
file_position += 256;
// Reset line_start to beginning of buffer
line_start = file_data;
pos = line_start;
while ((line_end = strstr(line_start, "\x0A"))) {
length = line_end - line_start - 1;
params[it] = std::string(pos, length);
it++;
line_start = line_end + 1;
pos = line_start;
if (line_start - file_data >= 256)
break;
if (it >= nb_params)
break;
}
if (read_size.value() != 256)
break; // End of file
// Restart at beginning of last incomplete line
file_position -= (file_data + 256 - line_start);
}
}
if (it < nb_params) {
/* bad number of params, signal defaults */
input_file = "RECON";
output_file = "RECON_RESULTS";
recon_lock_duration = RECON_MIN_LOCK_DURATION;
recon_lock_nb_match = RECON_DEF_NB_MATCH;
squelch = -14;
recon_match_mode = RECON_MATCH_CONTINUOUS;
wait = RECON_DEF_WAIT_DURATION;
File settings_file;
auto error = settings_file.open(RECON_CFG_FILE);
if (error)
return false;
auto complete = false;
auto line_nb = 0;
auto reader = FileLineReader(settings_file);
for (const auto& line : reader) {
switch (line_nb) {
case 0:
input_file = trim(line);
break;
case 1:
output_file = trim(line);
break;
case 2:
parse_int(line, recon_lock_duration);
break;
case 3:
parse_int(line, recon_lock_nb_match);
break;
case 4:
parse_int(line, squelch);
break;
case 5:
parse_int(line, recon_match_mode);
break;
case 6:
parse_int(line, recon_match_mode);
complete = true; // NB: Last entry.
break;
}
if (complete) break;
line_nb++;
}
if (it > 0)
input_file = params[0];
else
input_file = "RECON";
if (it > 1)
output_file = params[1];
else
output_file = "RECON_RESULTS";
if (it > 2)
recon_lock_duration = strtoll(params[2].c_str(), nullptr, 10);
else
recon_lock_duration = RECON_MIN_LOCK_DURATION;
if (it > 3)
recon_lock_nb_match = strtoll(params[3].c_str(), nullptr, 10);
else
recon_lock_nb_match = RECON_DEF_NB_MATCH;
if (it > 4)
squelch = strtoll(params[4].c_str(), nullptr, 10);
else
squelch = -14;
if (it > 5)
recon_match_mode = strtoll(params[5].c_str(), nullptr, 10);
else
recon_match_mode = RECON_MATCH_CONTINUOUS;
if (it > 6)
wait = strtoll(params[6].c_str(), nullptr, 10);
else
wait = RECON_DEF_WAIT_DURATION;
return true;
return complete;
}
bool ReconView::recon_save_config_to_sd() {
@@ -486,6 +419,7 @@ ReconView::ReconView(NavigationView& nav)
if (frequency_list.size() > 0) {
auto new_view = nav_.push<FrequencyKeypadView>(current_index);
new_view->on_changed = [this, &button](rf::Frequency f) {
// NB: This is using the freq keypad to select an index.
f = f / OneMHz;
if (f >= 1 && f <= frequency_list.size()) {
index_stepper = f - 1 - current_index;
@@ -496,17 +430,18 @@ ReconView::ReconView(NavigationView& nav)
};
button_manual_start.on_change = [this]() {
frequency_range.min = frequency_range.min + button_manual_start.get_encoder_delta() * freqman_entry_get_step_value(def_step);
auto step_val = freqman_entry_get_step_value(def_step);
frequency_range.min = frequency_range.min + button_manual_start.get_encoder_delta() * step_val;
if (frequency_range.min < 0) {
frequency_range.min = 0;
}
if (frequency_range.min > (MAX_UFREQ - freqman_entry_get_step_value(def_step))) {
frequency_range.min = MAX_UFREQ - freqman_entry_get_step_value(def_step);
if (frequency_range.min > (MAX_UFREQ - step_val)) {
frequency_range.min = MAX_UFREQ - step_val;
}
if (frequency_range.min > (frequency_range.max - freqman_entry_get_step_value(def_step))) {
frequency_range.max = frequency_range.min + freqman_entry_get_step_value(def_step);
if (frequency_range.min > (frequency_range.max - step_val)) {
frequency_range.max = frequency_range.min + step_val;
if (frequency_range.max > MAX_UFREQ) {
frequency_range.min = MAX_UFREQ - freqman_entry_get_step_value(def_step);
frequency_range.min = MAX_UFREQ - step_val;
frequency_range.max = MAX_UFREQ;
}
}
@@ -516,18 +451,19 @@ ReconView::ReconView(NavigationView& nav)
};
button_manual_end.on_change = [this]() {
frequency_range.max = frequency_range.max + button_manual_end.get_encoder_delta() * freqman_entry_get_step_value(def_step);
if (frequency_range.max < (freqman_entry_get_step_value(def_step) + 1)) {
frequency_range.max = (freqman_entry_get_step_value(def_step) + 1);
auto step_val = freqman_entry_get_step_value(def_step);
frequency_range.max = frequency_range.max + button_manual_end.get_encoder_delta() * step_val;
if (frequency_range.max < (step_val + 1)) {
frequency_range.max = (step_val + 1);
}
if (frequency_range.max > MAX_UFREQ) {
frequency_range.max = MAX_UFREQ;
}
if (frequency_range.max < (frequency_range.min + freqman_entry_get_step_value(def_step))) {
frequency_range.min = frequency_range.max - freqman_entry_get_step_value(def_step);
if (frequency_range.max < (freqman_entry_get_step_value(def_step) + 1)) {
if (frequency_range.max < (frequency_range.min + step_val)) {
frequency_range.min = frequency_range.max - step_val;
if (frequency_range.max < (step_val + 1)) {
frequency_range.min = 1;
frequency_range.max = (freqman_entry_get_step_value(def_step) + 1);
frequency_range.max = (step_val + 1);
}
}
button_manual_start.set_text(to_string_short_freq(frequency_range.min));
@@ -607,87 +543,9 @@ ReconView::ReconView(NavigationView& nav)
};
button_remove.on_select = [this](ButtonWithEncoder&) {
// TODO: Use FreqmanDB
if (frequency_list.size() > 0) {
if (!manual_mode) {
// scanner or recon (!scanner) mode
// in both we delete index from live view, but only remove in output file in scanner_mode
if (current_index >= (int32_t)frequency_list.size()) {
current_index = frequency_list.size() - 1;
}
frequency_list.erase(frequency_list.begin() + current_index);
if (current_index >= (int32_t)frequency_list.size()) {
current_index = frequency_list.size() - 1;
}
if (frequency_list.size() > 0) {
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) {
File freqman_file{};
delete_file(freq_file_path);
auto result = freqman_file.create(freq_file_path);
if (!result.is_valid()) {
for (size_t n = 0; n < frequency_list.size(); n++) {
auto line = to_freqman_string(*frequency_list[n]);
freqman_file.write_line(line);
}
}
}
} else if (manual_mode) // only remove from output
{
File recon_file{};
File tmp_recon_file{};
std::string tmp_freq_file_path{freq_file_path + ".TMP"};
handle_remove_current_item();
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 = freqman_type::Single;
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
if (!result.is_valid()) {
bool found = false;
result = recon_file.open(freq_file_path); // First recon if freq is already in txt
if (!result.is_valid()) {
char one_char[1]{}; // Read it char by char
std::string line{}; // and put read line in here
for (size_t pointer = 0; pointer < recon_file.size(); pointer++) {
recon_file.seek(pointer);
recon_file.read(one_char, 1);
if ((int)one_char[0] > 31) { // ascii space upwards
line += one_char[0]; // add it to the textline
} else if (one_char[0] == '\n') { // new Line
if (line.compare(0, frequency_to_add.size(), frequency_to_add) == 0) {
found = true;
} else {
tmp_recon_file.write_line(frequency_to_add);
}
line.clear(); // ready for next textline
}
}
if (found) {
delete_file(freq_file_path);
rename_file(tmp_freq_file_path, freq_file_path);
} else {
delete_file(tmp_freq_file_path);
}
}
}
}
receiver_model.set_target_frequency(current_entry().frequency_a); // retune
}
if (frequency_list.size() == 0) {
text_cycle.set_text(" ");
desc_cycle.set("no entries in list"); // Show new description
delete_file(freq_file_path);
}
// TODO: refresh UI.
timer = 0;
freq_lock = 0;
};
@@ -766,9 +624,8 @@ ReconView::ReconView(NavigationView& nav)
};
button_restart.on_select = [this](Button&) {
frequency_file_load(true);
if (frequency_list.size() > 0) {
def_step = step_mode.selected_index(); // Use def_step from manual selector
frequency_file_load(true);
if (fwd) {
button_dir.set_text("FW>");
} else {
@@ -921,8 +778,7 @@ ReconView::ReconView(NavigationView& nav)
recon_redraw();
}
void ReconView::frequency_file_load(bool stop_all_before) {
(void)(stop_all_before);
void ReconView::frequency_file_load(bool) {
if (field_mode.selected_index_value() != SPEC_MODULATION)
audio::output::stop();
@@ -940,6 +796,7 @@ void ReconView::frequency_file_load(bool stop_all_before) {
desc_cycle.set_style(&Styles::blue);
button_scanner_mode.set_text("RECON");
}
freqman_load_options options{
.load_freqs = load_freqs,
.load_ranges = load_ranges,
@@ -1430,4 +1287,35 @@ void ReconView::handle_coded_squelch(const uint32_t value) {
text_ctcss.set(" ");
}
void ReconView::handle_remove_current_item() {
if (frequency_list.empty() || !current_is_valid())
return;
auto entry = current_entry(); // Copy the current entry.
// In Scanner or Recon modes, remove from the in-memory list.
if (mode() != recon_mode::Manual) {
if (current_is_valid()) {
frequency_list.erase(frequency_list.begin() + current_index);
}
if (frequency_list.size() > 0) {
current_index = clip<int32_t>(current_index, 0u, frequency_list.size() - 1);
text_cycle.set_text(to_string_dec_uint(current_index + 1, 3));
update_description();
} else {
current_index = 0;
}
}
// In Scanner or Manual mode, remove the entry from the output file.
if (mode() != recon_mode::Recon) {
FreqmanDB freq_db;
if (!freq_db.open(freq_file_path))
return;
freq_db.delete_entry(entry);
}
}
} /* namespace ui */
+17 -3
View File
@@ -46,6 +46,12 @@ namespace ui {
#define RECON_CFG_FILE "SETTINGS/recon.cfg"
enum class recon_mode : uint8_t {
Recon,
Scanner,
Manual
};
class ReconView : public View {
public:
ReconView(NavigationView& nav);
@@ -82,9 +88,10 @@ class ReconView : public View {
void recon_redraw();
void handle_retune();
void handle_coded_squelch(const uint32_t value);
void handle_remove_current_item();
bool recon_load_config_from_sd();
bool recon_save_config_to_sd();
bool recon_save_freq(const std::string& freq_file_path, size_t index, bool warn_if_exists);
bool recon_save_freq(const std::filesystem::path& path, size_t index, bool warn_if_exists);
// placeholder for possible void recon_start_recording();
void recon_stop_recording();
@@ -92,8 +99,15 @@ class ReconView : public View {
bool current_is_valid();
freqman_entry& current_entry();
// TODO: consolidate mode bools and use recon_mode.
recon_mode mode() const {
if (manual_mode) return recon_mode::Manual;
if (scanner_mode) return recon_mode::Scanner;
return recon_mode::Recon;
}
jammer::jammer_range_t frequency_range{false, 0, MAX_UFREQ}; // perfect for manual recon task too...
int32_t squelch{0};
int32_t squelch{RECON_DEF_SQUELCH};
int32_t db{0};
int32_t timer{0};
int32_t wait{RECON_DEF_WAIT_DURATION}; // in msec. if > 0 wait duration after a lock, if < 0 duration is set to 'wait' unless there is no more activity
@@ -117,7 +131,7 @@ class ReconView : public View {
bool user_pause{false};
bool auto_record_locked{false};
bool is_recording{false};
uint32_t recon_lock_nb_match{3};
uint32_t recon_lock_nb_match{RECON_DEF_NB_MATCH};
uint32_t recon_lock_duration{RECON_MIN_LOCK_DURATION};
uint32_t recon_match_mode{RECON_MATCH_CONTINUOUS};
bool scanner_mode{false};
@@ -70,11 +70,9 @@ ReconSetupViewMain::ReconSetupViewMain(NavigationView& nav, Rect parent_rect, st
button_save_freqs.on_select = [this, &nav](Button&) {
auto open_view = nav.push<FileLoadView>(".TXT");
open_view->on_changed = [this, &nav](std::filesystem::path new_file_path) {
std::string dir_filter = "FREQMAN/";
std::string str_file_path = new_file_path.string();
if (str_file_path.find(dir_filter) != string::npos) { // assert file from the FREQMAN folder
if (new_file_path.native().find(freqman_dir.native()) == 0) {
_output_file = new_file_path.stem().string();
button_output_file.set_text(_output_file);
text_input_file.set(_output_file);
} else {
nav.display_modal("LOAD ERROR", "A valid file from\nFREQMAN directory is\nrequired.");
}
@@ -84,7 +82,7 @@ ReconSetupViewMain::ReconSetupViewMain(NavigationView& nav, Rect parent_rect, st
button_output_file.on_select = [this, &nav](Button&) {
text_prompt(nav, _output_file, 28,
[this](std::string& buffer) {
_output_file = buffer;
_output_file = std::move(buffer);
button_output_file.set_text(_output_file);
});
};
@@ -50,6 +50,8 @@
// maximum lock duration
#define RECON_MAX_LOCK_DURATION 9900
#define RECON_DEF_SQUELCH -14
// default number of match to have a lock
#define RECON_DEF_NB_MATCH 3
#define RECON_MIN_LOCK_DURATION 100 // have to be >= and a multiple of STATS_UPDATE_INTERVAL
+1
View File
@@ -22,6 +22,7 @@
#include "ui_scanner.hpp"
#include "ui_fileman.hpp"
#include "ui_freqman.hpp"
using namespace portapack;
+6 -4
View File
@@ -480,10 +480,12 @@ class SetEncoderDialView : public View {
OptionsField field_encoder_dial_sensitivity{
{20 * 8, 3 * 16},
6,
{{"LOW", encoder_dial_sensitivity::DIAL_SENSITIVITY_LOW},
{"NORMAL", encoder_dial_sensitivity::DIAL_SENSITIVITY_MEDIUM},
{"HIGH", encoder_dial_sensitivity::DIAL_SENSITIVITY_HIGH}}};
7,
{{"LOWEST", encoder_dial_sensitivity::DIAL_SENSITIVITY_LOWEST},
{"LOW", encoder_dial_sensitivity::DIAL_SENSITIVITY_LOW},
{"NORMAL", encoder_dial_sensitivity::DIAL_SENSITIVITY_NORMAL},
{"HIGH", encoder_dial_sensitivity::DIAL_SENSITIVITY_HIGH},
{"HIGHEST", encoder_dial_sensitivity::DIAL_SENSITIVITY_HIGHEST}}};
Button button_save{
{2 * 8, 16 * 16, 12 * 8, 32},
@@ -20,13 +20,14 @@
*/
#include "ui_spectrum_painter.hpp"
#include "bmp.hpp"
#include "baseband_api.hpp"
#include "ui_fileman.hpp"
#include "io_file.hpp"
#include "file.hpp"
#include "io_file.hpp"
#include "portapack_persistent_memory.hpp"
#include "ui_fileman.hpp"
#include "ui_freqman.hpp"
using namespace portapack;
+4 -1
View File
@@ -45,8 +45,11 @@ Optional<File::Error> File::open_fatfs(const std::filesystem::path& filename, BY
}
}
Optional<File::Error> File::open(const std::filesystem::path& filename, bool read_only) {
Optional<File::Error> File::open(const std::filesystem::path& filename, bool read_only, bool create) {
BYTE mode = read_only ? FA_READ : FA_READ | FA_WRITE;
if (create)
mode |= FA_OPEN_ALWAYS;
return open_fatfs(filename, mode);
}
+1 -1
View File
@@ -319,7 +319,7 @@ class File {
File& operator=(const File&) = delete;
// TODO: Return Result<>.
Optional<Error> open(const std::filesystem::path& filename, bool read_only = true);
Optional<Error> open(const std::filesystem::path& filename, bool read_only = true, bool create = false);
Optional<Error> append(const std::filesystem::path& filename);
Optional<Error> create(const std::filesystem::path& filename);
+2 -2
View File
@@ -463,9 +463,9 @@ class FileWrapper : public BufferWrapper<File, 64> {
template <typename T>
using Result = File::Result<T>;
using Error = File::Error;
static Result<std::unique_ptr<FileWrapper>> open(const std::filesystem::path& path) {
static Result<std::unique_ptr<FileWrapper>> open(const std::filesystem::path& path, bool create = false) {
auto fw = std::unique_ptr<FileWrapper>(new FileWrapper());
auto error = fw->file_.open(path, /*read_only*/ false);
auto error = fw->file_.open(path, /*read_only*/ false, create);
if (error)
return *error;
+2 -78
View File
@@ -22,92 +22,16 @@
* Boston, MA 02110-1301, USA.
*/
#include "file.hpp"
#include "freqman.hpp"
#include "tone_key.hpp"
#include "ui_widget.hpp"
#include <algorithm>
using option_t = ui::OptionsField::option_t;
using options_t = ui::OptionsField::options_t;
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>;
extern options_t freqman_modulations;
extern options_t freqman_bandwidths[4];
extern options_t freqman_steps;
extern options_t freqman_steps_short;
extern const option_t* find_by_index(const options_t& options, freqman_index_t index);
// TODO: remove in favor of FreqmanDB
/* Freqman file handling. */
bool load_freqman_file(const std::string& file_stem, freqman_db& db, freqman_load_options options) {
return parse_freqman_file(get_freqman_path(file_stem), db, options);
}
bool delete_freqman_file(const std::string& file_stem) {
delete_file(get_freqman_path(file_stem));
return true;
}
bool save_freqman_file(const std::string& file_stem, freqman_db& db) {
auto path = get_freqman_path(file_stem);
delete_file(path);
File freqman_file;
auto error = freqman_file.create(path);
if (error)
return false;
for (size_t n = 0; n < db.size(); n++)
freqman_file.write_line(to_freqman_string(*db[n]));
return true;
}
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_modulations);
}
void freqman_set_bandwidth_option(freqman_index_t modulation, OptionsField& option) {
if (is_valid(modulation))
option.set_options(freqman_bandwidths[modulation]);
}
void freqman_set_step_option(OptionsField& option) {
option.set_options(freqman_steps);
}
void freqman_set_step_option_short(OptionsField& option) {
option.set_options(freqman_steps_short);
}
/* Option value lookup. */
// TODO: use Optional instead of magic values.
int32_t freqman_entry_get_modulation_value(freqman_index_t modulation) {
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 < freqman_modulations.size()) {
if (auto opt = find_by_index(freqman_bandwidths[modulation], bandwidth))
return opt->second;
}
return -1;
}
int32_t freqman_entry_get_step_value(freqman_index_t step) {
if (auto opt = find_by_index(freqman_steps, step))
return opt->second;
-20
View File
@@ -25,13 +25,7 @@
#ifndef __FREQMAN_H__
#define __FREQMAN_H__
#include <cstring>
#include <string>
#include "file.hpp"
#include "freqman_db.hpp"
#include "string_format.hpp"
#include "ui_receiver.hpp"
#include "ui_widget.hpp"
// Defined for back-compat.
#define FREQMAN_MAX_PER_FILE freqman_default_max_entries
@@ -50,21 +44,7 @@ enum freqman_entry_modulation : uint8_t {
SPEC_MODULATION
};
// 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);
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);
#endif /*__FREQMAN_H__*/
+83 -37
View File
@@ -40,10 +40,6 @@ 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>;
@@ -164,6 +160,27 @@ const option_t* find_by_index(const options_t& options, freqman_index_t index) {
*}
*/
bool operator==(const freqman_entry& lhs, const freqman_entry& rhs) {
auto equal = lhs.type == rhs.type &&
lhs.frequency_a == rhs.frequency_a &&
lhs.description == rhs.description &&
lhs.modulation == rhs.modulation &&
lhs.bandwidth == rhs.bandwidth;
if (!equal)
return false;
if (lhs.type == freqman_type::Range) {
equal = lhs.frequency_b == rhs.frequency_b &&
lhs.step == rhs.step;
} else if (lhs.type == freqman_type::HamRadio) {
equal = lhs.frequency_b == rhs.frequency_b &&
lhs.tone == rhs.tone;
}
return equal;
}
std::string freqman_entry_get_modulation_string(freqman_index_t modulation) {
if (auto opt = find_by_index(freqman_modulations, modulation))
return opt->first;
@@ -190,6 +207,23 @@ std::string freqman_entry_get_step_string_short(freqman_index_t step) {
return {};
}
const std::filesystem::path get_freqman_path(const std::string& stem) {
return freqman_dir / stem + freqman_extension;
}
bool create_freqman_file(const std::string& file_stem) {
auto fs_error = make_new_file(get_freqman_path(file_stem));
return fs_error.ok();
}
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);
}
void delete_freqman_file(const std::string& file_stem) {
delete_file(get_freqman_path(file_stem));
}
std::string pretty_string(const freqman_entry& entry, size_t max_length) {
std::string str;
@@ -198,12 +232,12 @@ std::string pretty_string(const freqman_entry& entry, size_t max_length) {
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;
str = to_string_rounded_freq(entry.frequency_a, 1) + "M-" +
to_string_rounded_freq(entry.frequency_b, 1) + "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;
str = "R:" + to_string_rounded_freq(entry.frequency_a, 1) + "M,T:" +
to_string_rounded_freq(entry.frequency_b, 1) + "M: " + entry.description;
break;
case freqman_type::Raw:
str = entry.description;
@@ -342,27 +376,20 @@ bool parse_freqman_entry(std::string_view str, freqman_entry& entry) {
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)
FreqmanDB freqman_db;
freqman_db.set_read_raw(false); // Don't return malformed lines.
if (!freqman_db.open(path))
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;
db.reserve(freqman_db.entry_count());
for (auto entry : freqman_db) {
// Filter by entry type.
if ((entry.type == freqman_type::Single && !options.load_freqs) ||
if (entry.type == freqman_type::Unknown ||
(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;
@@ -411,6 +438,7 @@ bool is_valid(const freqman_entry& entry) {
// TODO: Consider additional validation:
// - Tone only on HamRadio.
// - Step only on Range
// - Fail on failed parse_int.
// - Fail if bandwidth set before modulation.
@@ -419,8 +447,8 @@ bool is_valid(const freqman_entry& entry) {
/* FreqmanDB ***********************************/
bool FreqmanDB::open(const std::filesystem::path& path) {
auto result = FileWrapper::open(path);
bool FreqmanDB::open(const std::filesystem::path& path, bool create) {
auto result = FileWrapper::open(path, create);
if (!result)
return false;
@@ -432,15 +460,15 @@ 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);
freqman_entry FreqmanDB::operator[](Index index) const {
auto length = wrapper_->line_length(index);
auto line_text = wrapper_->get_text(index, 0, length);
if (line_text) {
freqman_entry entry;
if (parse_freqman_entry(*line_text, entry))
return entry;
else {
else if (read_raw_) {
entry.type = freqman_type::Raw;
entry.description = trim(*line_text);
return entry;
@@ -450,15 +478,18 @@ freqman_entry FreqmanDB::operator[](FileWrapper::Line line) const {
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::insert_entry(Index index, const freqman_entry& entry) {
index = clip<uint32_t>(index, 0u, entry_count());
wrapper_->insert_line(index);
replace_entry(index, entry);
}
void FreqmanDB::replace_entry(FileWrapper::Line line, const freqman_entry& entry) {
auto range = wrapper_->line_range(line);
void FreqmanDB::append_entry(const freqman_entry& entry) {
insert_entry(entry_count(), entry);
}
void FreqmanDB::replace_entry(Index index, const freqman_entry& entry) {
auto range = wrapper_->line_range(index);
if (!range)
return;
@@ -467,8 +498,23 @@ void FreqmanDB::replace_entry(FileWrapper::Line line, const freqman_entry& entry
wrapper_->replace_range(*range, to_freqman_string(entry));
}
void FreqmanDB::delete_entry(FileWrapper::Line line) {
wrapper_->delete_line(line);
void FreqmanDB::delete_entry(Index index) {
wrapper_->delete_line(index);
}
bool FreqmanDB::delete_entry(const freqman_entry& entry) {
auto it = find_entry(entry);
if (it == end())
return false;
delete_entry(it.index());
return true;
}
FreqmanDB::iterator FreqmanDB::find_entry(const freqman_entry& entry) {
return find_entry([&entry](const auto& other) {
return entry == other;
});
}
uint32_t FreqmanDB::entry_count() const {
+63 -21
View File
@@ -52,9 +52,6 @@ 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=
@@ -151,6 +148,8 @@ struct freqman_entry {
freqman_index_t tone{freqman_invalid_index};
};
bool operator==(const freqman_entry& lhs, const freqman_entry& rhs);
// 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);
@@ -173,6 +172,12 @@ struct freqman_load_options {
using freqman_entry_ptr = std::unique_ptr<freqman_entry>;
using freqman_db = std::vector<freqman_entry_ptr>;
/* Gets the full path for a given file stem (no extension). */
const std::filesystem::path get_freqman_path(const std::string& stem);
bool create_freqman_file(const std::string& file_stem);
bool load_freqman_file(const std::string& file_stem, freqman_db& db, freqman_load_options options);
void delete_freqman_file(const std::string& file_stem);
/* Gets a pretty string representation for an entry. */
std::string pretty_string(const freqman_entry& item, size_t max_length = 30);
@@ -185,54 +190,91 @@ bool parse_freqman_file(const std::filesystem::path& path, freqman_db& db, freqm
/* 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
/* API wrapper over a Freqman file. Provides CRUD operations
* for freqman_entry instances that are read/written directly
* to the underlying file. */
class FreqmanDB {
public:
using Index = FileWrapper::Line;
/* NB: This iterator is very basic: forward only, read-only. */
class iterator {
public:
iterator(FreqmanDB& db, FileWrapper::Offset line)
: db_{db}, line_{line} {}
iterator(FreqmanDB& db, Index index)
: db_{db}, index_{index} {}
iterator& operator++() {
line_++;
index_++;
if (index_ >= db_.entry_count())
index_ = end_index;
return *this;
}
freqman_entry operator*() const {
return db_[line_];
return db_[index_];
}
bool operator==(const iterator& other) {
return &db_ == &other.db_ && index_ == other.index_;
}
bool operator!=(const iterator& other) {
return &db_ != &other.db_ || line_ != other.line_;
return !(*this == other);
}
Index index() const {
return index_;
}
/* Value indicating the 'end' iterator. */
static constexpr Index end_index = (Index)-1;
private:
FreqmanDB& db_;
FileWrapper::Line line_;
Index index_;
};
bool open(const std::filesystem::path& path);
bool open(const std::filesystem::path& path, bool create = false);
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);
freqman_entry operator[](Index index) const;
void insert_entry(Index index, const freqman_entry& entry);
void append_entry(const freqman_entry& entry);
void replace_entry(Index index, const freqman_entry& entry);
void delete_entry(Index index);
bool delete_entry(const freqman_entry& entry);
template <typename Fn>
iterator find_entry(const Fn& predicate) {
// TODO: use std::find, but need to make the iterator compliant.
auto it = begin();
const auto it_end = end();
while (it != it_end) {
if (predicate(*it))
return it;
++it;
}
return it_end;
}
iterator find_entry(const freqman_entry& entry);
uint32_t entry_count() const;
bool empty() const;
/* When true, Raw entries are returned instead of Unknown. */
void set_read_raw(bool v) { read_raw_ = v; }
iterator begin() {
return {*this, 0};
}
iterator end() {
return {*this, entry_count()};
return {*this, iterator::end_index};
}
private:
std::unique_ptr<FileWrapper> wrapper_{};
bool read_raw_{true};
};
#endif /* __FREQMAN_DB_H__ */
+27 -62
View File
@@ -32,66 +32,24 @@
// 12 degrees of rotation.
//
// For each encoder "pulse" there are 4 state transitions, and we can choose
// between looking at all of them (high sensitivity), half of them (medium/default),
// or one quarter of them (low sensitivity).
static const int8_t transition_map[][16] = {
// Normal (Medium) Sensitivity -- default
{
0, // 0000: noop
0, // 0001: ccw start
0, // 0010: cw start
0, // 0011: rate
1, // 0100: cw end
0, // 0101: noop
0, // 0110: rate
-1, // 0111: ccw end
-1, // 1000: ccw end
0, // 1001: rate
0, // 1010: noop
1, // 1011: cw end
0, // 1100: rate
0, // 1101: cw start
0, // 1110: ccw start
0, // 1111: noop
},
// Low Sensitivity
{
0, // 0000: noop
0, // 0001: ccw start
0, // 0010: cw start
0, // 0011: rate
1, // 0100: cw end
0, // 0101: noop
0, // 0110: rate
0, // 0111: ccw end
-1, // 1000: ccw end
0, // 1001: rate
0, // 1010: noop
0, // 1011: cw end
0, // 1100: rate
0, // 1101: cw start
0, // 1110: ccw start
0, // 1111: noop
},
// High Sensitivity
{
0, // 0000: noop
-1, // 0001: ccw start
1, // 0010: cw start
0, // 0011: rate
1, // 0100: cw end
0, // 0101: noop
0, // 0110: rate
-1, // 0111: ccw end
-1, // 1000: ccw end
0, // 1001: rate
0, // 1010: noop
1, // 1011: cw end
0, // 1100: rate
1, // 1101: cw start
-1, // 1110: ccw start
0, // 1111: noop
},
// how many transitions are needed before movement is registered
static const int8_t transition_map[16] = {
0, // 0000: noop
-1, // 0001: ccw start
1, // 0010: cw start
0, // 0011: rate
1, // 0100: cw end
0, // 0101: noop
0, // 0110: rate
-1, // 0111: ccw end
-1, // 1000: ccw end
0, // 1001: rate
0, // 1010: noop
1, // 1011: cw end
0, // 1100: rate
1, // 1101: cw start
-1, // 1110: ccw start
0, // 1111: noop
};
int_fast8_t Encoder::update(
@@ -102,6 +60,13 @@ int_fast8_t Encoder::update(
state <<= 1;
state |= phase_1;
// dial sensitivity setting is stored in pmem
return transition_map[portapack::persistent_memory::config_encoder_dial_sensitivity()][state & 0xf];
int_fast8_t retval = transition_map[state & 0xf];
transition_count += retval;
if (abs(transition_count) > portapack::persistent_memory::config_encoder_dial_sensitivity())
transition_count = 0;
else
retval = 0;
return retval;
}
+1
View File
@@ -32,6 +32,7 @@ class Encoder {
private:
uint_fast8_t state{0};
int_fast8_t transition_count{0};
};
#endif /*__ENCODER_H__*/
+17 -18
View File
@@ -42,34 +42,33 @@ ReplayProcessor::ReplayProcessor() {
}
void ReplayProcessor::execute(const buffer_c8_t& buffer) {
/* 4MHz, 2048 samples */
/* 2.6MHz, 2048 samples */
if (!configured) return;
// File data is in C16 format, we need C8
// File samplerate is 500kHz, we're at 4MHz
// iq_buffer can only be 512 C16 samples (RAM limitation)
// To fill up the 2048-sample C8 buffer, we need:
// 2048 samples * 2 bytes per sample = 4096 bytes
// Since we're oversampling by 4M/500k = 8, we only need 2048/8 = 256 samples from the file and duplicate them 8 times each
// So 256 * 4 bytes per sample (C16) = 1024 bytes from the file
if (stream) { // sizeof(*buffer.p) = sizeof(C8) = 2*int8 = 2 bytes //buffer.count = 2048
const size_t bytes_to_read = sizeof(*buffer.p) * 1 * (buffer.count); // *2 (C16), /8 (oversampling) should be == 1024
bytes_read += stream->read(iq_buffer.p, bytes_to_read);
}
// File data is in C8 format, which is what we need
// File samplerate is 2.6MHz, which is what we need
// To fill up the 2048-sample C8 buffer @ 2 bytes per sample = 4096 bytes
if (stream) {
const size_t bytes_to_read = sizeof(*buffer.p) * 1 * (buffer.count);
size_t bytes_read_this_iteration = stream->read(iq_buffer.p, bytes_to_read);
bytes_read += bytes_read_this_iteration;
// Fill and "stretch"
for (size_t i = 0; i < buffer.count; i++) {
auto re_out = iq_buffer.p[i].real();
auto im_out = iq_buffer.p[i].imag();
buffer.p[i] = {(int8_t)re_out, (int8_t)im_out};
// NB: Couldn't we have just read the data into buffer.p to start with, or some DMA/cache coherency concern?
//
// for (size_t i = 0; i < buffer.count; i++) {
// auto re_out = iq_buffer.p[i].real();
// auto im_out = iq_buffer.p[i].imag();
// buffer.p[i] = {(int8_t)re_out, (int8_t)im_out};
// }
memcpy(buffer.p, iq_buffer.p, bytes_read_this_iteration); // memcpy should be more efficient than 1 byte at a time
}
spectrum_samples += buffer.count;
if (spectrum_samples >= spectrum_interval_samples) {
spectrum_samples -= spectrum_interval_samples;
txprogress_message.progress = bytes_read / 1024; // Inform UI about progress
txprogress_message.progress = bytes_read; // Inform UI about progress
txprogress_message.done = false;
shared_memory.application_queue.push(txprogress_message);
+1 -1
View File
@@ -59,7 +59,7 @@ void ReplayProcessor::execute(const buffer_c8_t& buffer) {
// Fill and "stretch"
for (size_t i = 0; i < buffer.count; i++) {
if (i & 3) {
if (i & 7) {
buffer.p[i] = buffer.p[i - 1];
} else {
auto re_out = iq_buffer.p[i >> 3].real() >> 8;
+57 -12
View File
@@ -22,26 +22,71 @@
#ifndef __CONVERT_H__
#define __CONVERT_H__
#include <charconv>
#include <cstdlib>
#include <cstring>
#include <limits>
#include <string>
#include <string_view>
#include <type_traits>
/* Zero-allocation conversion helper. */
/* Notes:
* - T must be an integer type.
* - For base 16, input _must not_ contain a '0x' or '0X' prefix.
* - For base 8 a leading 0 on the literal is allowed.
* - Leading whitespce will cause the parse to fail.
*/
// TODO: from_chars seems to cause code bloat.
// Look into using strtol and friends instead.
constexpr size_t max_parse_int_length = 64;
/* This undefined type is used to see the size of the
* unhandled type and to create a compilation error. */
template <size_t N>
struct UnhandledSize;
/* Returns true when errno is ERANGE. */
inline bool range_error() {
return errno == ERANGE;
}
/* Assigns 'value' to 'out_val' and returns true if 'value' is
* in bounds for type TOut. */
template <typename TVal, typename TOut>
bool checked_assign(TVal value, TOut& out_val) {
// No chance for overflow, just return.
if constexpr (sizeof(TVal) <= sizeof(TOut)) {
out_val = value;
return true;
}
out_val = static_cast<TOut>(value);
return value >= static_cast<TVal>(std::numeric_limits<TOut>::min()) &&
value <= static_cast<TVal>(std::numeric_limits<TOut>::max());
}
/* Zero-allocation conversion helper. 'str' must be smaller than 'max_parse_int_length'. */
template <typename T>
std::enable_if_t<std::is_integral_v<T>, bool> parse_int(std::string_view str, T& out_val, int base = 10) {
auto result = std::from_chars(str.data(), str.data() + str.length(), out_val, base);
return static_cast<int>(result.ec) == 0;
// Always initialize the output.
out_val = {};
if (str.size() > max_parse_int_length)
return false;
// Copy onto the stack and null terminate.
char zstr[max_parse_int_length + 1];
std::memcpy(zstr, str.data(), str.size());
zstr[str.size()] = '\0';
// A little C++ type magic to select the correct conversion function.
if constexpr (sizeof(T) == sizeof(long long)) {
if constexpr (std::is_unsigned_v<T>)
return checked_assign(strtoull(zstr, nullptr, base), out_val) && !range_error();
else
return checked_assign(strtoll(zstr, nullptr, base), out_val) && !range_error();
} else if constexpr (sizeof(T) <= sizeof(long)) {
if constexpr (std::is_unsigned_v<T>)
return checked_assign(strtoul(zstr, nullptr, base), out_val) && !range_error();
else
return checked_assign(strtol(zstr, nullptr, base), out_val) && !range_error();
} else {
UnhandledSize<sizeof(T) * 8> unhandled_case;
return false;
}
return true;
}
#endif /*__CONVERT_H__*/
@@ -269,7 +269,7 @@ struct data_t {
frequency_rx_correction(0),
frequency_tx_correction(0),
encoder_dial_sensitivity(0),
encoder_dial_sensitivity(DIAL_SENSITIVITY_NORMAL),
UNUSED_8(0),
headphone_volume_cb(-600),
misc_config(),
@@ -363,6 +363,7 @@ void defaults() {
set_config_backlight_timer(backlight_config_t{});
set_config_splash(true);
set_encoder_dial_sensitivity(DIAL_SENSITIVITY_NORMAL);
// Default values for recon app.
set_recon_autosave_freqs(false);
@@ -107,9 +107,11 @@ struct backlight_config_t {
};
enum encoder_dial_sensitivity {
DIAL_SENSITIVITY_MEDIUM = 0,
DIAL_SENSITIVITY_LOW = 1,
DIAL_SENSITIVITY_HIGH = 2,
DIAL_SENSITIVITY_HIGHEST = 0,
DIAL_SENSITIVITY_HIGH = 1,
DIAL_SENSITIVITY_NORMAL = 2,
DIAL_SENSITIVITY_LOW = 3,
DIAL_SENSITIVITY_LOWEST = 4,
NUM_DIAL_SENSITIVITY
};
+1 -1
View File
@@ -573,7 +573,7 @@ void ProgressBar::paint(Painter& painter) {
const auto sr = screen_rect();
const auto s = style();
v_scaled = (sr.size().width() * _value) / _max;
v_scaled = (sr.size().width() * (uint64_t)_value) / _max;
painter.fill_rectangle({sr.location(), {v_scaled, sr.size().height()}}, style().foreground);
painter.fill_rectangle({{sr.location().x() + v_scaled, sr.location().y()}, {sr.size().width() - v_scaled, sr.size().height()}}, s.background);
+28 -3
View File
@@ -46,10 +46,11 @@ TEST_CASE("It should convert string_views.") {
CHECK_EQ(val, 12345);
}
TEST_CASE("It should return false for invalid input") {
// 'from_chars' supported this, but 'strtol' just returns 0.
/*TEST_CASE("It should return false for invalid input") {
int val = 0;
REQUIRE_FALSE(parse_int("QWERTY", val));
}
}*/
TEST_CASE("It should return false for overflow input") {
uint8_t val = 0;
@@ -58,7 +59,7 @@ TEST_CASE("It should return false for overflow input") {
TEST_CASE("It should convert base 16.") {
int val = 0;
REQUIRE(parse_int("30", val, 16)); // NB: No '0x'
REQUIRE(parse_int("0x30", val, 16));
CHECK_EQ(val, 0x30);
}
@@ -74,4 +75,28 @@ TEST_CASE("It should convert as much of the string as it can.") {
CHECK_EQ(val, 12345);
}
TEST_CASE("It should convert 64-bit.") {
int64_t val = 0;
REQUIRE(parse_int("123456789", val));
CHECK_EQ(val, 123456789);
}
TEST_CASE("It should convert 32-bit.") {
int32_t val = 0;
REQUIRE(parse_int("123456", val));
CHECK_EQ(val, 123456);
}
TEST_CASE("It should convert 16-bit.") {
int16_t val = 0;
REQUIRE(parse_int("12345", val));
CHECK_EQ(val, 12345);
}
TEST_CASE("It should convert 8-bit.") {
int8_t val = 0;
REQUIRE(parse_int("123", val));
CHECK_EQ(val, 123);
}
TEST_SUITE_END();