mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2024-12-04 23:45:26 +00:00
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
This commit is contained in:
parent
60de625c37
commit
497ca3f934
@ -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
|
||||
|
@ -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"
|
||||
|
||||
|
@ -25,6 +25,8 @@
|
||||
#include "portapack.hpp"
|
||||
#include "event_m0.hpp"
|
||||
|
||||
#include <memory>
|
||||
|
||||
using namespace portapack;
|
||||
|
||||
namespace ui {
|
||||
@ -63,7 +65,8 @@ void FreqManBaseView::focus() {
|
||||
}
|
||||
|
||||
void FreqManBaseView::get_freqman_files() {
|
||||
std::vector<std::string>().swap(file_list);
|
||||
// Assume this does change much, clear will preserve the existing alloc.
|
||||
file_list.clear();
|
||||
|
||||
auto files = scan_root_files(u"FREQMAN", u"*.TXT");
|
||||
|
||||
@ -71,7 +74,7 @@ void FreqManBaseView::get_freqman_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);
|
||||
file_list.emplace_back(std::move(file_name));
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -81,7 +84,7 @@ void FreqManBaseView::change_category(int32_t category_id) {
|
||||
|
||||
if (file_list.empty()) return;
|
||||
|
||||
if (!load_freqman_file(file_list[categories[category_id].second], database)) {
|
||||
if (!load_freqman_file(file_list[categories[category_id].second], database, {})) {
|
||||
error_ = ERROR_ACCESS;
|
||||
}
|
||||
freqlist_view.set_db(database);
|
||||
@ -113,13 +116,13 @@ void FrequencySaveView::save_current_file() {
|
||||
|
||||
void FrequencySaveView::on_save_name() {
|
||||
text_prompt(nav_, desc_buffer, 28, [this](std::string& buffer) {
|
||||
database.push_back({value_, 0, buffer, SINGLE});
|
||||
database.push_back(std::make_unique<freqman_entry>(freqman_entry{value_, 0, buffer, freqman_type::Single}));
|
||||
save_current_file();
|
||||
});
|
||||
}
|
||||
|
||||
void FrequencySaveView::on_save_timestamp() {
|
||||
database.push_back({value_, 0, live_timestamp.string(), SINGLE});
|
||||
database.push_back(std::make_unique<freqman_entry>(freqman_entry{value_, 0, live_timestamp.string(), freqman_type::Single}));
|
||||
save_current_file();
|
||||
}
|
||||
|
||||
@ -159,10 +162,6 @@ FrequencySaveView::FrequencySaveView(
|
||||
};
|
||||
}
|
||||
|
||||
FrequencyLoadView::~FrequencyLoadView() {
|
||||
std::vector<freqman_entry>().swap(database);
|
||||
}
|
||||
|
||||
void FrequencyLoadView::refresh_widgets(const bool v) {
|
||||
freqlist_view.hidden(v);
|
||||
text_empty.hidden(!v);
|
||||
@ -185,33 +184,32 @@ FrequencyLoadView::FrequencyLoadView(
|
||||
|
||||
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 (entry->type == freqman_type::Range) {
|
||||
if (on_range_loaded)
|
||||
on_range_loaded(entry.frequency_a, entry.frequency_b);
|
||||
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 ?
|
||||
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?
|
||||
// TODO: HamRadio?
|
||||
} else {
|
||||
// User chose an unique frequency entry
|
||||
if (on_frequency_loaded)
|
||||
on_frequency_loaded(entry.frequency_a);
|
||||
on_frequency_loaded(entry->frequency_a);
|
||||
}
|
||||
// swap with empty vector to ensure memory is immediately released
|
||||
std::vector<freqman_entry>().swap(database);
|
||||
nav_.pop();
|
||||
|
||||
nav_.pop(); // NB: this will call dtor.
|
||||
};
|
||||
}
|
||||
|
||||
void FrequencyManagerView::on_edit_freq(rf::Frequency f) {
|
||||
database[freqlist_view.get_index()].frequency_a = 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 FrequencyManagerView::on_edit_desc(NavigationView& nav) {
|
||||
text_prompt(nav, desc_buffer, 28, [this](std::string& buffer) {
|
||||
database[freqlist_view.get_index()].description = buffer;
|
||||
database[freqlist_view.get_index()]->description = std::move(buffer);
|
||||
save_freqman_file(file_list[categories[current_category_id].second], database);
|
||||
change_category(current_category_id);
|
||||
});
|
||||
@ -277,20 +275,20 @@ FrequencyManagerView::FrequencyManagerView(
|
||||
};
|
||||
|
||||
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);
|
||||
if (database.empty())
|
||||
database.push_back(std::make_unique<freqman_entry>(freqman_entry{0, 0, "", freqman_type::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_desc.on_select = [this, &nav](Button&) {
|
||||
if (database.empty()) {
|
||||
database.push_back({0, 0, "", SINGLE});
|
||||
}
|
||||
desc_buffer = database[freqlist_view.get_index()].description;
|
||||
if (database.empty())
|
||||
database.push_back(std::make_unique<freqman_entry>(freqman_entry{0, 0, "", freqman_type::Single}));
|
||||
|
||||
desc_buffer = database[freqlist_view.get_index()]->description;
|
||||
on_edit_desc(nav);
|
||||
};
|
||||
|
||||
|
@ -116,7 +116,6 @@ 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."; };
|
||||
|
||||
|
@ -26,6 +26,7 @@
|
||||
#include "file.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
using namespace tonekey;
|
||||
using portapack::memory::map::backup_ram;
|
||||
|
||||
namespace ui {
|
||||
|
@ -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};
|
||||
|
||||
|
@ -35,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"
|
||||
|
@ -23,14 +23,24 @@
|
||||
|
||||
#include "ui_recon.hpp"
|
||||
#include "ui_fileman.hpp"
|
||||
#include "file.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);
|
||||
@ -56,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) {
|
||||
@ -98,15 +127,15 @@ 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);
|
||||
@ -176,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;
|
||||
@ -311,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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -576,11 +590,11 @@ 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 receives, 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 transmits, 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);
|
||||
@ -605,23 +619,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) {
|
||||
@ -631,7 +630,7 @@ ReconView::ReconView(NavigationView& nav)
|
||||
if (!result.is_valid()) {
|
||||
for (size_t n = 0; n < frequency_list.size(); n++) {
|
||||
std::string line;
|
||||
get_freq_string(frequency_list[n], line);
|
||||
get_freq_string(*frequency_list[n], line);
|
||||
freqman_file.write_line(line);
|
||||
}
|
||||
}
|
||||
@ -643,12 +642,12 @@ ReconView::ReconView(NavigationView& nav)
|
||||
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);
|
||||
|
||||
@ -683,7 +682,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(" ");
|
||||
@ -711,25 +710,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
|
||||
|
||||
@ -744,13 +741,12 @@ ReconView::ReconView(NavigationView& nav)
|
||||
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();
|
||||
@ -795,7 +791,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);
|
||||
}
|
||||
@ -945,7 +941,11 @@ void ReconView::frequency_file_load(bool stop_all_before) {
|
||||
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");
|
||||
@ -962,71 +962,39 @@ 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();
|
||||
}
|
||||
|
||||
@ -1156,7 +1124,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;
|
||||
@ -1186,7 +1154,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;
|
||||
@ -1205,7 +1173,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;
|
||||
@ -1259,22 +1227,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) {
|
||||
|
@ -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};
|
||||
|
@ -318,7 +318,7 @@ ScannerView::ScannerView(
|
||||
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 (str_file_path.find(dir_filter) != std::string::npos) { // assert file from the FREQMAN folder
|
||||
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();
|
||||
@ -576,9 +576,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) {
|
||||
@ -587,56 +587,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 {
|
||||
@ -650,13 +650,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();
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
@ -21,293 +21,66 @@
|
||||
* 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>;
|
||||
|
||||
// TODO: Consolidate with receiver_model.
|
||||
// These definitions are spread all over and stiched together with indices.
|
||||
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)
|
||||
}};
|
||||
const option_t* find_by_index(const options_t& options, freqman_index_t index) {
|
||||
if (index < options.size())
|
||||
return &options[index];
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
options_t freqman_entry_steps = {
|
||||
{"0.1kHz ", 100},
|
||||
{"1kHz ", 1000},
|
||||
{"5kHz (SA AM)", 5000},
|
||||
{"6.25kHz(NFM)", 6250},
|
||||
{"8.33kHz(AIR)", 8330},
|
||||
{"9kHz (EU AM)", 9000},
|
||||
{"10kHz(US AM)", 10000},
|
||||
{"12.5kHz(NFM)", 12500},
|
||||
{"15kHz (HFM)", 15000},
|
||||
{"25kHz (N1)", 25000},
|
||||
{"30kHz (OIRT)", 30000},
|
||||
{"50kHz (FM1)", 50000},
|
||||
{"100kHz (FM2)", 100000},
|
||||
{"250kHz (N2)", 250000},
|
||||
{"500kHz (WFM)", 500000},
|
||||
{"1MHz ", 1000000}};
|
||||
|
||||
options_t freqman_entry_steps_short = {
|
||||
{"0.1kHz", 100},
|
||||
{"1kHz", 1000},
|
||||
{"5kHz", 5000},
|
||||
{"6.25kHz", 6250},
|
||||
{"8.33kHz", 8330},
|
||||
{"9kHz", 9000},
|
||||
{"10kHz", 10000},
|
||||
{"12.5kHz", 12500},
|
||||
{"15kHz", 15000},
|
||||
{"25kHz", 25000},
|
||||
{"30kHz", 30000},
|
||||
{"50kHz", 50000},
|
||||
{"100kHz", 100000},
|
||||
{"250kHz", 250000},
|
||||
{"500kHz", 500000},
|
||||
{"1MHz", 1000000}};
|
||||
|
||||
bool load_freqman_file(std::string& file_stem, freqman_db& db, 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;
|
||||
uint32_t tone_freq;
|
||||
char c;
|
||||
|
||||
auto result = freqman_file.open("FREQMAN/" + file_stem + ".TXT");
|
||||
if (result.is_valid())
|
||||
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
|
||||
pos = strstr(line_start, "c=");
|
||||
if (pos) {
|
||||
pos += 2;
|
||||
// find decimal point and replace with 0 if there is one, for strtoll
|
||||
length = strcspn(pos, ".,\x0A");
|
||||
if (pos + length <= line_end) {
|
||||
c = *(pos + length);
|
||||
*(pos + length) = 0;
|
||||
// ASCII Hz to integer Hz x 100
|
||||
tone_freq = strtoll(pos, nullptr, 10) * 100;
|
||||
// stuff saved character back into string in case it was not a decimal point
|
||||
*(pos + length) = c;
|
||||
// now get first digit after decimal point (10ths of Hz)
|
||||
pos += length + 1;
|
||||
if (c == '.' && *pos >= '0' && *pos <= '9')
|
||||
tone_freq += (*pos - '0') * 10;
|
||||
// convert tone_freq (100x the freq in Hz) to a tone_key index
|
||||
tone = tone_key_index_by_value(tone_freq);
|
||||
}
|
||||
}
|
||||
// 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;
|
||||
// TODO: move into FreqmanDB type
|
||||
/* Freqman file handling. */
|
||||
bool load_freqman_file(const std::string& file_stem, freqman_db& db, freqman_load_options options) {
|
||||
fs::path path{u"FREQMAN/"};
|
||||
path += file_stem + ".TXT";
|
||||
return parse_freqman_file(path, db, options);
|
||||
}
|
||||
|
||||
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) {
|
||||
if (entry.type == freqman_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) {
|
||||
} else if (entry.type == freqman_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) {
|
||||
if (is_valid(entry.step)) {
|
||||
item_string += ",s=" + freqman_entry_get_step_string_short(entry.step);
|
||||
}
|
||||
} else if (entry.type == HAMRADIO) {
|
||||
} else if (entry.type == freqman_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) {
|
||||
if (is_valid(entry.tone)) {
|
||||
item_string += ",c=" + tone_key_value_string(entry.tone);
|
||||
}
|
||||
}
|
||||
if (entry.modulation >= 0 && (unsigned)entry.modulation < freqman_entry_modulations.size()) {
|
||||
if (is_valid(entry.modulation) && entry.modulation < freqman_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()) {
|
||||
if (is_valid(entry.bandwidth) && (unsigned)entry.bandwidth < freqman_bandwidths[entry.modulation].size()) {
|
||||
item_string += ",bw=" + freqman_entry_get_bandwidth_string(entry.modulation, entry.bandwidth);
|
||||
}
|
||||
}
|
||||
@ -317,14 +90,14 @@ bool get_freq_string(freqman_entry& entry, std::string& item_string) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool delete_freqman_file(std::string& file_stem) {
|
||||
bool delete_freqman_file(const 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) {
|
||||
bool save_freqman_file(const std::string& file_stem, freqman_db& db) {
|
||||
File freqman_file;
|
||||
std::string freq_file_path = "/FREQMAN/" + file_stem + ".TXT";
|
||||
delete_file(freq_file_path);
|
||||
@ -332,7 +105,7 @@ bool save_freqman_file(std::string& file_stem, freqman_db& db) {
|
||||
if (!result.is_valid()) {
|
||||
for (size_t n = 0; n < db.size(); n++) {
|
||||
std::string line;
|
||||
get_freq_string(db[n], line);
|
||||
get_freq_string(*db[n], line);
|
||||
freqman_file.write_line(line);
|
||||
}
|
||||
return true;
|
||||
@ -340,7 +113,7 @@ bool save_freqman_file(std::string& file_stem, freqman_db& db) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool create_freqman_file(std::string& file_stem, File& freqman_file) {
|
||||
bool create_freqman_file(const std::string& file_stem, File& freqman_file) {
|
||||
auto result = freqman_file.create("FREQMAN/" + file_stem + ".TXT");
|
||||
|
||||
if (result.is_valid())
|
||||
@ -353,17 +126,17 @@ std::string freqman_item_string(freqman_entry& entry, size_t max_length) {
|
||||
std::string item_string;
|
||||
|
||||
switch (entry.type) {
|
||||
case SINGLE:
|
||||
case freqman_type::Single:
|
||||
item_string = to_string_short_freq(entry.frequency_a) + "M: " + entry.description;
|
||||
break;
|
||||
case RANGE:
|
||||
case freqman_type::Range:
|
||||
item_string = "R: " + entry.description;
|
||||
break;
|
||||
case HAMRADIO:
|
||||
case freqman_type::HamRadio:
|
||||
item_string = "H: " + entry.description;
|
||||
break;
|
||||
default:
|
||||
item_string = "!UNKNOW TYPE " + entry.description;
|
||||
item_string = "!UNKNOWN TYPE " + entry.description;
|
||||
break;
|
||||
}
|
||||
|
||||
@ -373,115 +146,69 @@ std::string freqman_item_string(freqman_entry& entry, size_t max_length) {
|
||||
return item_string;
|
||||
}
|
||||
|
||||
/* 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);
|
||||
option.set_options(freqman_steps_short);
|
||||
}
|
||||
|
||||
/* Option name lookup. */
|
||||
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;
|
||||
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 < 0 || (unsigned)modulation >= freqman_entry_modulations.size()) {
|
||||
return std::string(""); // unknown modulation
|
||||
if (modulation < freqman_modulations.size()) {
|
||||
if (auto opt = find_by_index(freqman_bandwidths[modulation], bandwidth))
|
||||
return opt->first;
|
||||
}
|
||||
if (bandwidth < 0 || (unsigned)bandwidth > freqman_entry_bandwidths[modulation].size()) {
|
||||
return std::string(""); // unknown modulation
|
||||
}
|
||||
return freqman_entry_bandwidths[modulation][bandwidth].first;
|
||||
return {};
|
||||
}
|
||||
|
||||
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;
|
||||
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 (step < 0 || (unsigned)step >= freqman_entry_steps_short.size()) {
|
||||
return std::string(""); // unknown modulation
|
||||
}
|
||||
return freqman_entry_steps_short[step].first;
|
||||
if (auto opt = find_by_index(freqman_steps_short, step))
|
||||
return opt->first;
|
||||
return {};
|
||||
}
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
@ -27,23 +27,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,59 +42,26 @@ 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
|
||||
};
|
||||
|
||||
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 load_freqman_file(const std::string& file_stem, freqman_db& db, freqman_load_options options);
|
||||
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);
|
||||
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, 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);
|
||||
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);
|
||||
|
||||
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);
|
||||
@ -115,9 +72,4 @@ 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__*/
|
||||
|
264
firmware/application/freqman_db.cpp
Normal file
264
firmware/application/freqman_db.cpp
Normal file
@ -0,0 +1,264 @@
|
||||
/*
|
||||
* 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 <array>
|
||||
#include <cctype>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
using option_t = std::pair<std::string, int32_t>;
|
||||
using options_t = std::vector<option_t>;
|
||||
|
||||
options_t freqman_modulations = {
|
||||
{"AM", 0},
|
||||
{"NFM", 1},
|
||||
{"WFM", 2},
|
||||
{"SPEC", 3},
|
||||
};
|
||||
|
||||
options_t freqman_bandwidths[4] = {
|
||||
{
|
||||
// AM
|
||||
{"DSB 9k", 0},
|
||||
{"DSB 6k", 1},
|
||||
{"USB+3k", 2},
|
||||
{"LSB-3k", 3},
|
||||
{"CW", 4},
|
||||
},
|
||||
{
|
||||
// NFM
|
||||
{"8k5", 0},
|
||||
{"11k", 1},
|
||||
{"16k", 2},
|
||||
},
|
||||
{
|
||||
// WFM
|
||||
{"40k", 2},
|
||||
{"180k", 1},
|
||||
{"200k", 0},
|
||||
},
|
||||
{
|
||||
// SPEC -- TODO: these should be indexes.
|
||||
{"8k5", 8500},
|
||||
{"11k", 11000},
|
||||
{"16k", 16000},
|
||||
{"25k", 25000},
|
||||
{"50k", 50000},
|
||||
{"100k", 100000},
|
||||
{"250k", 250000},
|
||||
{"500k", 500000}, /* Previous Limit bandwith Option with perfect micro SD write .C16 format operaton.*/
|
||||
{"600k", 600000}, /* That extended option is still possible to record with FW version Mayhem v1.41 (< 2,5MB/sec) */
|
||||
{"650k", 650000},
|
||||
{"750k", 750000}, /* From this BW onwards, the LCD is ok, but the recorded file is decimated, (not real file size) */
|
||||
{"1100k", 1100000},
|
||||
{"1750k", 1750000},
|
||||
{"2000k", 2000000},
|
||||
{"2500k", 2500000},
|
||||
{"2750k", 2750000}, // That is our max Capture option, to keep using later / 8 decimation (22Mhz sampling ADC)
|
||||
},
|
||||
};
|
||||
|
||||
options_t freqman_steps = {
|
||||
{"0.1kHz ", 100},
|
||||
{"1kHz ", 1000},
|
||||
{"5kHz (SA AM)", 5000},
|
||||
{"6.25kHz(NFM)", 6250},
|
||||
{"8.33kHz(AIR)", 8330},
|
||||
{"9kHz (EU AM)", 9000},
|
||||
{"10kHz(US AM)", 10000},
|
||||
{"12.5kHz(NFM)", 12500},
|
||||
{"15kHz (HFM)", 15000},
|
||||
{"25kHz (N1)", 25000},
|
||||
{"30kHz (OIRT)", 30000},
|
||||
{"50kHz (FM1)", 50000},
|
||||
{"100kHz (FM2)", 100000},
|
||||
{"250kHz (N2)", 250000},
|
||||
{"500kHz (WFM)", 500000},
|
||||
{"1MHz ", 1000000},
|
||||
};
|
||||
|
||||
options_t freqman_steps_short = {
|
||||
{"0.1kHz", 100},
|
||||
{"1kHz", 1000},
|
||||
{"5kHz", 5000},
|
||||
{"6.25kHz", 6250},
|
||||
{"8.33kHz", 8330},
|
||||
{"9kHz", 9000},
|
||||
{"10kHz", 10000},
|
||||
{"12.5kHz", 12500},
|
||||
{"15kHz", 15000},
|
||||
{"25kHz", 25000},
|
||||
{"30kHz", 30000},
|
||||
{"50kHz", 50000},
|
||||
{"100kHz", 100000},
|
||||
{"250kHz", 250000},
|
||||
{"500kHz", 500000},
|
||||
{"1MHz", 1000000},
|
||||
};
|
||||
|
||||
uint8_t find_by_name(const options_t& options, std::string_view name) {
|
||||
for (auto ix = 0u; ix < options.size(); ++ix)
|
||||
if (options[ix].first == name)
|
||||
return ix;
|
||||
|
||||
return freqman_invalid_index;
|
||||
}
|
||||
|
||||
/* 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 ⁢
|
||||
* }
|
||||
*
|
||||
* return nullptr;
|
||||
*}
|
||||
*/
|
||||
|
||||
// TODO: How much format validation should this do?
|
||||
// It's very permissive right now, but entries can be invalid.
|
||||
// TODO: parse_int seems to hang on invalid input.
|
||||
bool parse_freqman_entry(std::string_view str, freqman_entry& entry) {
|
||||
if (str.empty() || str[0] == '#')
|
||||
return false;
|
||||
|
||||
auto cols = split_string(str, ',');
|
||||
entry = freqman_entry{};
|
||||
|
||||
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") {
|
||||
// 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;
|
||||
}
|
||||
entry.tone = static_cast<freqman_index_t>(
|
||||
tonekey::tone_key_index_by_value(tone_freq));
|
||||
} 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 true;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
179
firmware/application/freqman_db.hpp
Normal file
179
firmware/application/freqman_db.hpp
Normal file
@ -0,0 +1,179 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2016 Furrtek
|
||||
* Copyright (C) 2023 gullradriel, Nilorea Studio Inc., 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 "utility.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
enum class freqman_type : uint8_t {
|
||||
Single, // f=
|
||||
Range, // a=,b=
|
||||
HamRadio, // r=,t=
|
||||
Unknown,
|
||||
};
|
||||
|
||||
/* Tables for next round of changes.
|
||||
* // TODO: attempt to consolidate all of these values
|
||||
* // and settings into a single location.
|
||||
* // Should tone_keys get consolidated here too?
|
||||
* enum class freqman_modulation : uint8_t {
|
||||
AM,
|
||||
NFM,
|
||||
WFM,
|
||||
SPEC,
|
||||
Unknown,
|
||||
* };
|
||||
*
|
||||
* struct freqman_modulation_info {
|
||||
* freqman_modulation modulation;
|
||||
* std::string_view name;
|
||||
* };
|
||||
*
|
||||
* constexpr std::array<freqman_modulation_info, 5> freqman_modulations = {
|
||||
freqman_modulation_info{ freqman_modulation::AM, "AM" },
|
||||
freqman_modulation_info{ freqman_modulation::NFM, "NFM" },
|
||||
freqman_modulation_info{ freqman_modulation::WFM, "WFM" },
|
||||
freqman_modulation_info{ freqman_modulation::SPEC, "SPEC" },
|
||||
freqman_modulation_info{ freqman_modulation::Unknown, "Unknown" }
|
||||
* };
|
||||
* static_assert(std::size(freqman_modulations) == (size_t)freqman_modulation::Unknown + 1);
|
||||
*
|
||||
* enum class freqman_step : uint8_t {
|
||||
_100Hz,
|
||||
_1kHz,
|
||||
_5kHz,
|
||||
_6_25kHz,
|
||||
_8_33kHz,
|
||||
_9kHz,
|
||||
_10kHz,
|
||||
_12_5kHz,
|
||||
_15kHz,
|
||||
_25kHz,
|
||||
_30kHz,
|
||||
_50kHz,
|
||||
_100kHz,
|
||||
_250kHz,
|
||||
_500kHz,
|
||||
_1MHz,
|
||||
Unknown,
|
||||
* };
|
||||
*
|
||||
* struct freqman_step_info {
|
||||
* freqman_step step;
|
||||
* std::string_view name;
|
||||
* std::string_view display_name;
|
||||
* uint32_t value;
|
||||
* };
|
||||
*
|
||||
* // TODO: FrequencyStepView should use this list.
|
||||
* constexpr std::array<freqman_step_info, 18> freqman_steps = {
|
||||
freqman_step_info{ freqman_step::_100Hz, "0.1kHz", "0.1kHz ", 100 },
|
||||
freqman_step_info{ freqman_step::_1kHz, "1kHz", "1kHz ", 1'000 },
|
||||
freqman_step_info{ freqman_step::_5kHz, "5kHz", "5kHz (SA AM)", 5'000 },
|
||||
freqman_step_info{ freqman_step::_6_25kHz, "6.25kHz", "6.25kHz(NFM)", 6'250 },
|
||||
freqman_step_info{ freqman_step::_8_33kHz, "8.33kHz", "8.33kHz(AIR)", 8'330 },
|
||||
freqman_step_info{ freqman_step::_9kHz, "9kHz", "9kHz (EU AM)", 9'000 },
|
||||
freqman_step_info{ freqman_step::_10kHz, "10kHz", "10kHz(US AM)", 10'000 },
|
||||
freqman_step_info{ freqman_step::_12_5kHz, "12.5kHz", "12.5kHz(NFM)", 12'500 },
|
||||
freqman_step_info{ freqman_step::_15kHz, "15kHz", "15kHz (HFM)", 15'000 },
|
||||
freqman_step_info{ freqman_step::_25kHz, "25kHz", "25kHz (N1)", 25'000 },
|
||||
freqman_step_info{ freqman_step::_30kHz, "30kHz", "30kHz (OIRT)", 30'000 },
|
||||
freqman_step_info{ freqman_step::_50kHz, "50kHz", "50kHz (FM1)", 50'000 },
|
||||
freqman_step_info{ freqman_step::_100kHz, "100kHz", "100kHz (FM2)", 100'000 },
|
||||
freqman_step_info{ freqman_step::_250kHz, "250kHz", "250kHz (N2)", 250'000 },
|
||||
freqman_step_info{ freqman_step::_500kHz, "500kHz", "500kHz (WFM)", 500'000 },
|
||||
freqman_step_info{ freqman_step::_1MHz, "1MHz", "1MHz ", 1'000'000 },
|
||||
freqman_step_info{ freqman_step::Unknown, "Unknown", "Unknown ", 0 },
|
||||
* };
|
||||
* static_assert(std::size(freqman_steps) == (size_t)freqman_step::Unknown + 1);
|
||||
*/
|
||||
|
||||
/* Freqman Entry *******************************/
|
||||
struct freqman_entry {
|
||||
int64_t frequency_a{0}; // 'f=freq' or 'a=freq_start' or 'r=recv_freq'
|
||||
int64_t frequency_b{0}; // 'b=freq_end' or 't=tx_freq'
|
||||
std::string description{0}; // 'd=desc'
|
||||
freqman_type type{freqman_type::Unknown};
|
||||
freqman_index_t modulation{freqman_invalid_index};
|
||||
freqman_index_t bandwidth{freqman_invalid_index};
|
||||
freqman_index_t step{freqman_invalid_index};
|
||||
freqman_index_t tone{freqman_invalid_index};
|
||||
};
|
||||
|
||||
/* 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 = 90;
|
||||
|
||||
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>;
|
||||
|
||||
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);
|
||||
|
||||
/* Type for next round of changes.
|
||||
*class FreqmanDB {
|
||||
* public:
|
||||
* FreqmanDB();
|
||||
* FreqmanDB(const FreqmanDB&) = delete;
|
||||
* FreqmanDB(FreqmanDB&&) = delete;
|
||||
* FreqmanDB& operator=(const FreqmanDB&) = delete;
|
||||
* FreqmanDB& operator=(FreqmanDB&&) = delete;
|
||||
*
|
||||
* size_t size() const { return 0; };
|
||||
*
|
||||
* private:
|
||||
* freqman_db entries_;
|
||||
*};
|
||||
*/
|
||||
|
||||
#endif /* __FREQMAN_DB_H__ */
|
@ -177,7 +177,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;
|
||||
|
@ -88,25 +88,6 @@ std::string fx100_string(uint32_t f) {
|
||||
return to_string_dec_uint(f / 100) + "." + to_string_dec_uint((f / 10) % 10);
|
||||
}
|
||||
|
||||
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++) {
|
||||
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(tone_key_options);
|
||||
}
|
||||
|
||||
float tone_key_frequency(tone_index index) {
|
||||
return float(tone_keys[index].second) / 100.0;
|
||||
}
|
||||
@ -169,7 +150,7 @@ std::string tone_key_string_by_value(uint32_t value, size_t max_length) {
|
||||
// Value is in 0.01 Hz units
|
||||
tone_index tone_key_index_by_value(uint32_t value) {
|
||||
uint32_t diff;
|
||||
uint32_t min_diff{value * 2};
|
||||
uint32_t min_diff{UINT32_MAX};
|
||||
tone_index min_idx{-1};
|
||||
tone_index idx;
|
||||
|
||||
@ -196,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;
|
||||
|
@ -23,10 +23,9 @@
|
||||
#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 {
|
||||
|
||||
@ -34,15 +33,14 @@ namespace tonekey {
|
||||
#define TONE_DISPLAY_TOGGLE_COUNTER 3
|
||||
#define F2Ix100(x) (int32_t)(x * 100.0)
|
||||
|
||||
typedef int32_t tone_index;
|
||||
|
||||
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(tone_index index);
|
||||
|
||||
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);
|
||||
|
@ -76,7 +76,7 @@ void FreqManUIList::paint(Painter& painter) {
|
||||
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);
|
||||
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(
|
||||
|
@ -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));
|
||||
|
47
firmware/application/ui/ui_tone_key.cpp
Normal file
47
firmware/application/ui/ui_tone_key.cpp
Normal 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
firmware/application/ui/ui_tone_key.hpp
Normal file
36
firmware/application/ui/ui_tone_key.hpp
Normal 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_*/
|
@ -1390,12 +1390,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 +1437,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;
|
||||
@ -1486,7 +1486,7 @@ OptionsField::OptionsField(
|
||||
options_t options)
|
||||
: Widget{{parent_pos, {8 * length, 16}}},
|
||||
length_{length},
|
||||
options{options} {
|
||||
options{std::move(options)} {
|
||||
set_focusable(true);
|
||||
}
|
||||
|
||||
@ -1543,7 +1543,7 @@ void OptionsField::set_by_nearest_value(value_t v) {
|
||||
}
|
||||
|
||||
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;
|
||||
|
@ -575,10 +575,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(), {}} {
|
||||
|
@ -39,11 +39,19 @@ 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_utility.cpp
|
||||
|
||||
${PROJECT_SOURCE_DIR}/../../application/file_reader.cpp
|
||||
${PROJECT_SOURCE_DIR}/../../application/freqman_db.cpp
|
||||
${PROJECT_SOURCE_DIR}/../../application/string_format.cpp
|
||||
|
||||
# Dependencies
|
||||
${PROJECT_SOURCE_DIR}/../../application/file.cpp
|
||||
${PROJECT_SOURCE_DIR}/../../application/tone_key.cpp
|
||||
${PROJECT_SOURCE_DIR}/linker_stubs.cpp
|
||||
)
|
||||
|
||||
target_include_directories(application_test PRIVATE
|
||||
|
80
firmware/test/application/linker_stubs.cpp
Normal file
80
firmware/test/application/linker_stubs.cpp
Normal file
@ -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"};
|
||||
|
217
firmware/test/application/test_freqman_db.cpp
Normal file
217
firmware/test/application/test_freqman_db.cpp
Normal file
@ -0,0 +1,217 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
|
||||
#if 0 // 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);
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST_SUITE_END();
|
Loading…
Reference in New Issue
Block a user