Compare commits

...

11 Commits

Author SHA1 Message Date
Kyle Reed a476647d70 Don't use raw new/delete (#1398)
* Use unique_ptr in ui_btngrid
* Use unique_ptr for ui_menu
* Use unique_ptr for rssi_dma
* Use unique_ptr for painter
2023-08-21 10:17:23 +02:00
Mark Thompson 95a48e5693 Store search range in settings file (#1397)
* MAX_UFREQ
* Use settings file for search range
2023-08-21 10:16:49 +02:00
Mark Thompson 09404ca198 Default to AK4951 speaker disabled (#1396) 2023-08-20 22:28:23 +02:00
Kyle Reed 564f76b47d Use new settings API in recon and scanner (#1394)
* Use new settings in recon
* Trim on string value read, remove dupe entry
* Check update_range flag when setting values
* Add a few saved settings to scanner
* Add copywrite note
2023-08-20 22:28:02 +02:00
Mark Thompson c6424f1623 Display degree symbol in TPMS, Sonde, and Temperature apps; disabled Font Viewer (#1388)
* Display degrees symbol; disable Font Viewer app to save ROM space
2023-08-20 11:14:22 +02:00
Mark Thompson a4325ac20d Disable audio by default at power-up (#1393) 2023-08-20 11:13:04 +02:00
Kyle Reed d8a6422d37 Consolidate old and new style app settings (#1391)
* Consolidate old and new style app settings

* Remove unused ctor
2023-08-19 09:02:26 -07:00
gullradriel cc963c3562 Recon: Only update manual field if current entry is a range (#1387) 2023-08-18 22:05:28 +02:00
Kyle Reed 63f99742fc First pass at custom app-settings support (#1381)
* First draft of custom app settings support.

* WIP new settings

* Working per-app custom settings

* Revert design to use "bound settings"
2023-08-18 12:35:41 -07:00
Brumi-2021 a4636d7872 Finalise all low bit rate Capture App with x64 (#1386)
* Finalise all low bit rate Capture App with x64

* Adding back also 25k BW option
2023-08-18 14:49:22 +02:00
gullradriel bd2ee03e44 Restoring part of old timer, fixed sd loading (#1385) 2023-08-17 17:28:12 +02:00
35 changed files with 489 additions and 402 deletions
+164 -123
View File
@@ -2,6 +2,7 @@
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
* Copyright (C) 2022 Arjan Onwezen
* Copyright (C) 2023 Kyle Reed
*
* This file is part of PortaPack.
*
@@ -23,7 +24,9 @@
#include "app_settings.hpp"
#include "convert.hpp"
#include "file.hpp"
#include "file_reader.hpp"
#include "portapack.hpp"
#include "portapack_persistent_memory.hpp"
#include "utility.hpp"
@@ -34,145 +37,139 @@
namespace fs = std::filesystem;
using namespace portapack;
using namespace std::literals;
namespace app_settings {
namespace {
fs::path get_settings_path(const std::string& app_name) {
return fs::path{u"/SETTINGS"} / app_name + u".ini";
}
} // namespace
template <typename T>
static void read_setting(
std::string_view file_content,
std::string_view setting_name,
T& value) {
auto pos = file_content.find(setting_name);
if (pos != file_content.npos) {
pos += setting_name.length();
value = strtoll(&file_content[pos], nullptr, 10);
}
void BoundSetting::parse(std::string_view value) {
switch (type_) {
case SettingType::I64:
parse_int(value, as<int64_t>());
break;
case SettingType::I32:
parse_int(value, as<int32_t>());
break;
case SettingType::U32:
parse_int(value, as<uint32_t>());
break;
case SettingType::U8:
parse_int(value, as<uint8_t>());
break;
case SettingType::String:
as<std::string>() = trim(value);
break;
case SettingType::Bool: {
int parsed = 0;
parse_int(value, parsed);
as<bool>() = (parsed != 0);
break;
}
};
}
template <typename T>
static void write_setting(File& file, std::string_view setting_name, const T& value) {
// NB: Not using file.write_line speed this up. This happens on every
void BoundSetting::write(File& file) const {
// NB: Write directly without allocations. This happens on every
// app exit when enabled so should be fast to keep the UX responsive.
StringFormatBuffer buffer;
size_t length = 0;
auto value_str = to_string_dec_uint(value, buffer, length);
file.write(setting_name.data(), setting_name.length());
file.write(value_str, length);
file.write(name_.data(), name_.length());
file.write("=", 1);
switch (type_) {
case SettingType::I64:
file.write(to_string_dec_int(as<int64_t>(), buffer, length), length);
break;
case SettingType::I32:
file.write(to_string_dec_int(as<int32_t>(), buffer, length), length);
break;
case SettingType::U32:
file.write(to_string_dec_uint(as<uint32_t>(), buffer, length), length);
break;
case SettingType::U8:
file.write(to_string_dec_uint(as<uint8_t>(), buffer, length), length);
break;
case SettingType::String: {
const auto& str = as<std::string>();
file.write(str.data(), str.length());
break;
}
case SettingType::Bool:
file.write(as<bool>() ? "1" : "0", 1);
break;
}
file.write("\r\n", 2);
}
static fs::path get_settings_path(const std::string& app_name) {
return fs::path{u"/SETTINGS"} / app_name + u".ini";
SettingsStore::SettingsStore(std::string_view store_name, SettingBindings bindings)
: store_name_{store_name}, bindings_{bindings} {
reload();
}
namespace setting {
constexpr std::string_view baseband_bandwidth = "baseband_bandwidth="sv;
constexpr std::string_view sampling_rate = "sampling_rate="sv;
constexpr std::string_view lna = "lna="sv;
constexpr std::string_view vga = "vga="sv;
constexpr std::string_view rx_amp = "rx_amp="sv;
constexpr std::string_view tx_amp = "tx_amp="sv;
constexpr std::string_view tx_gain = "tx_gain="sv;
constexpr std::string_view channel_bandwidth = "channel_bandwidth="sv;
constexpr std::string_view rx_frequency = "rx_frequency="sv;
constexpr std::string_view tx_frequency = "tx_frequency="sv;
constexpr std::string_view step = "step="sv;
constexpr std::string_view modulation = "modulation="sv;
constexpr std::string_view am_config_index = "am_config_index="sv;
constexpr std::string_view nbfm_config_index = "nbfm_config_index="sv;
constexpr std::string_view wfm_config_index = "wfm_config_index="sv;
constexpr std::string_view squelch = "squelch="sv;
constexpr std::string_view volume = "volume="sv;
} // namespace setting
// TODO: Only load/save values that are declared used.
// This will prevent switching apps from changing setting unnecessarily.
// TODO: Track which values are actually read.
// TODO: Maybe just use a dictionary which would allow for custom settings.
// TODO: Create a control value binding which will allow controls to
// be declaratively bound to a setting and persistence will be magic.
ResultCode load_settings(const std::string& app_name, AppSettings& settings) {
if (!portapack::persistent_memory::load_app_settings())
return ResultCode::SettingsDisabled;
auto file_path = get_settings_path(app_name);
auto data = File::read_file(file_path);
if (!data)
return ResultCode::LoadFailed;
if (flags_enabled(settings.mode, Mode::TX)) {
read_setting(*data, setting::tx_frequency, settings.tx_frequency);
read_setting(*data, setting::tx_amp, settings.tx_amp);
read_setting(*data, setting::tx_gain, settings.tx_gain);
read_setting(*data, setting::channel_bandwidth, settings.channel_bandwidth);
}
if (flags_enabled(settings.mode, Mode::RX)) {
read_setting(*data, setting::rx_frequency, settings.rx_frequency);
read_setting(*data, setting::lna, settings.lna);
read_setting(*data, setting::vga, settings.vga);
read_setting(*data, setting::rx_amp, settings.rx_amp);
read_setting(*data, setting::modulation, settings.modulation);
read_setting(*data, setting::am_config_index, settings.am_config_index);
read_setting(*data, setting::nbfm_config_index, settings.nbfm_config_index);
read_setting(*data, setting::wfm_config_index, settings.wfm_config_index);
read_setting(*data, setting::squelch, settings.squelch);
}
read_setting(*data, setting::baseband_bandwidth, settings.baseband_bandwidth);
read_setting(*data, setting::sampling_rate, settings.sampling_rate);
read_setting(*data, setting::step, settings.step);
read_setting(*data, setting::volume, settings.volume);
return ResultCode::Ok;
SettingsStore::~SettingsStore() {
save();
}
ResultCode save_settings(const std::string& app_name, AppSettings& settings) {
if (!portapack::persistent_memory::save_app_settings())
return ResultCode::SettingsDisabled;
void SettingsStore::reload() {
load_settings(store_name_, bindings_);
}
File settings_file;
auto file_path = get_settings_path(app_name);
ensure_directory(file_path.parent_path());
void SettingsStore::save() const {
save_settings(store_name_, bindings_);
}
auto error = settings_file.create(file_path);
bool load_settings(std::string_view store_name, SettingBindings& bindings) {
File f;
auto path = get_settings_path(std::string{store_name});
auto error = f.open(path);
if (error)
return ResultCode::SaveFailed;
return false;
if (flags_enabled(settings.mode, Mode::TX)) {
write_setting(settings_file, setting::tx_frequency, settings.tx_frequency);
write_setting(settings_file, setting::tx_amp, settings.tx_amp);
write_setting(settings_file, setting::tx_gain, settings.tx_gain);
write_setting(settings_file, setting::channel_bandwidth, settings.channel_bandwidth);
auto reader = FileLineReader(f);
for (const auto& line : reader) {
auto cols = split_string(line, '=');
if (cols.size() != 2)
continue;
// Find a binding with the name.
auto it = std::find_if(
bindings.begin(), bindings.end(),
[name = cols[0]](auto& bound_setting) {
return name == bound_setting.name();
});
// If found, parse the value.
if (it != bindings.end())
it->parse(cols[1]);
}
if (flags_enabled(settings.mode, Mode::RX)) {
write_setting(settings_file, setting::rx_frequency, settings.rx_frequency);
write_setting(settings_file, setting::lna, settings.lna);
write_setting(settings_file, setting::vga, settings.vga);
write_setting(settings_file, setting::rx_amp, settings.rx_amp);
write_setting(settings_file, setting::modulation, settings.modulation);
write_setting(settings_file, setting::am_config_index, settings.am_config_index);
write_setting(settings_file, setting::nbfm_config_index, settings.nbfm_config_index);
write_setting(settings_file, setting::wfm_config_index, settings.wfm_config_index);
write_setting(settings_file, setting::squelch, settings.squelch);
}
write_setting(settings_file, setting::baseband_bandwidth, settings.baseband_bandwidth);
write_setting(settings_file, setting::sampling_rate, settings.sampling_rate);
write_setting(settings_file, setting::step, settings.step);
write_setting(settings_file, setting::volume, settings.volume);
return ResultCode::Ok;
return true;
}
bool save_settings(std::string_view store_name, const SettingBindings& bindings) {
File f;
auto path = get_settings_path(std::string{store_name});
auto error = f.create(path);
if (error)
return false;
for (const auto& bound_setting : bindings)
bound_setting.write(f);
return true;
}
namespace app_settings {
void copy_to_radio_model(const AppSettings& settings) {
// NB: Don't actually adjust the radio here or it will hang.
// NB: Don't actually adjust the radio here or it may hang.
// Specifically 'modulation' which requires a running baseband.
if (flags_enabled(settings.mode, Mode::TX)) {
@@ -230,27 +227,71 @@ void copy_from_radio_model(AppSettings& settings) {
}
/* SettingsManager *************************************************/
SettingsManager::SettingsManager(std::string_view app_name, Mode mode, Options options)
: SettingsManager{app_name, mode, options, {}} {}
SettingsManager::SettingsManager(
std::string app_name,
std::string_view app_name,
Mode mode,
Options options)
: app_name_{std::move(app_name)},
SettingBindings additional_settings)
: SettingsManager{app_name, mode, Options::None, std::move(additional_settings)} {}
SettingsManager::SettingsManager(
std::string_view app_name,
Mode mode,
Options options,
SettingBindings additional_settings)
: app_name_{app_name},
settings_{},
bindings_{},
loaded_{false} {
settings_.mode = mode;
settings_.options = options;
auto result = load_settings(app_name_, settings_);
if (result == ResultCode::Ok) {
loaded_ = true;
copy_to_radio_model(settings_);
if (!portapack::persistent_memory::load_app_settings())
return;
// Pre-alloc enough for app settings and additional settings.
additional_settings.reserve(17 + additional_settings.size());
bindings_ = std::move(additional_settings);
// Transmitter model settings.
if (flags_enabled(mode, Mode::TX)) {
bindings_.emplace_back("tx_frequency"sv, &settings_.tx_frequency);
bindings_.emplace_back("tx_amp"sv, &settings_.tx_amp);
bindings_.emplace_back("tx_gain"sv, &settings_.tx_gain);
bindings_.emplace_back("channel_bandwidth"sv, &settings_.channel_bandwidth);
}
// Receiver model settings.
if (flags_enabled(mode, Mode::RX)) {
bindings_.emplace_back("rx_frequency"sv, &settings_.rx_frequency);
bindings_.emplace_back("lna"sv, &settings_.lna);
bindings_.emplace_back("vga"sv, &settings_.vga);
bindings_.emplace_back("rx_amp"sv, &settings_.rx_amp);
bindings_.emplace_back("modulation"sv, &settings_.modulation);
bindings_.emplace_back("am_config_index"sv, &settings_.am_config_index);
bindings_.emplace_back("nbfm_config_index"sv, &settings_.nbfm_config_index);
bindings_.emplace_back("wfm_config_index"sv, &settings_.wfm_config_index);
bindings_.emplace_back("squelch"sv, &settings_.squelch);
}
// Common model settings.
bindings_.emplace_back("baseband_bandwidth"sv, &settings_.baseband_bandwidth);
bindings_.emplace_back("sampling_rate"sv, &settings_.sampling_rate);
bindings_.emplace_back("step"sv, &settings_.step);
bindings_.emplace_back("volume"sv, &settings_.volume);
loaded_ = load_settings(app_name_, bindings_);
if (loaded_)
copy_to_radio_model(settings_);
}
SettingsManager::~SettingsManager() {
if (portapack::persistent_memory::save_app_settings()) {
copy_from_radio_model(settings_);
save_settings(app_name_, settings_);
save_settings(app_name_, bindings_);
}
}
+77 -13
View File
@@ -2,6 +2,7 @@
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
* Copyright (C) 2022 Arjan Onwezen
* Copyright (C) 2023 Kyle Reed
*
* This file is part of PortaPack.
*
@@ -27,21 +28,85 @@
#include <cstddef>
#include <cstdint>
#include <string>
#include <string_view>
#include <utility>
#include <variant>
#include "file.hpp"
#include "max283x.hpp"
#include "string_format.hpp"
namespace app_settings {
// Bring in the string_view literal.
using std::literals::operator""sv;
enum class ResultCode : uint8_t {
Ok, // settings found
LoadFailed, // settings (file) not found
SaveFailed, // unable to save settings
SettingsDisabled, // load/save disabled in settings
/* Represents a named setting bound to a variable instance. */
/* Using void* instead of std::variant, because variant is a pain to dispatch over. */
class BoundSetting {
/* The type of bound setting. */
enum class SettingType : uint8_t {
I64,
I32,
U32,
U8,
String,
Bool,
};
public:
BoundSetting(std::string_view name, int64_t* target)
: name_{name}, target_{target}, type_{SettingType::I64} {}
BoundSetting(std::string_view name, int32_t* target)
: name_{name}, target_{target}, type_{SettingType::I32} {}
BoundSetting(std::string_view name, uint32_t* target)
: name_{name}, target_{target}, type_{SettingType::U32} {}
BoundSetting(std::string_view name, uint8_t* target)
: name_{name}, target_{target}, type_{SettingType::U8} {}
BoundSetting(std::string_view name, std::string* target)
: name_{name}, target_{target}, type_{SettingType::String} {}
BoundSetting(std::string_view name, bool* target)
: name_{name}, target_{target}, type_{SettingType::Bool} {}
std::string_view name() const { return name_; }
void parse(std::string_view value);
void write(File& file) const;
private:
template <typename T>
constexpr auto& as() const {
return *reinterpret_cast<T*>(target_);
}
std::string_view name_;
void* target_;
SettingType type_;
};
using SettingBindings = std::vector<BoundSetting>;
/* RAII wrapper for Settings that loads/saves to the SD card. */
class SettingsStore {
public:
SettingsStore(std::string_view store_name, SettingBindings bindings);
~SettingsStore();
void reload();
void save() const;
private:
std::string_view store_name_;
SettingBindings bindings_;
};
bool load_settings(std::string_view store_name, SettingBindings& bindings);
bool save_settings(std::string_view store_name, const SettingBindings& bindings);
namespace app_settings {
enum class Mode : uint8_t {
RX = 0x01,
TX = 0x02,
@@ -55,7 +120,6 @@ enum class Options {
UseGlobalTargetFrequency = 0x0001,
};
// TODO: separate types for TX/RX or union?
/* NB: See RX/TX model headers for default values. */
struct AppSettings {
Mode mode = Mode::RX;
@@ -80,21 +144,20 @@ struct AppSettings {
uint8_t volume;
};
ResultCode load_settings(const std::string& app_name, AppSettings& settings);
ResultCode save_settings(const std::string& app_name, AppSettings& settings);
/* Copies common values to the receiver/transmitter models. */
void copy_to_radio_model(const AppSettings& settings);
/* Copies common values from the receiver/transmitter models. */
void copy_from_radio_model(AppSettings& settings);
/* RAII wrapper for automatically loading and saving settings for an app.
/* RAII wrapper for automatically loading and saving radio settings for an app.
* NB: This should be added to a class before any LNA/VGA controls so that
* the receiver/transmitter models are set before the control ctors run. */
class SettingsManager {
public:
SettingsManager(std::string app_name, Mode mode, Options options = Options::None);
SettingsManager(std::string_view app_name, Mode mode, Options options = Options::None);
SettingsManager(std::string_view app_name, Mode mode, SettingBindings additional_settings);
SettingsManager(std::string_view app_name, Mode mode, Options options, SettingBindings additional_settings);
~SettingsManager();
SettingsManager(const SettingsManager&) = delete;
@@ -109,8 +172,9 @@ class SettingsManager {
AppSettings& raw() { return settings_; }
private:
std::string app_name_;
std::string_view app_name_;
AppSettings settings_;
SettingBindings bindings_;
bool loaded_;
};
+4 -2
View File
@@ -71,6 +71,8 @@ POCSAGAppView::POCSAGAppView(NavigationView& nav)
if (!settings_.loaded())
field_frequency.set_value(initial_target_frequency);
check_log.set_value(enable_logging);
receiver_model.enable();
// TODO: app setting instead?
@@ -88,7 +90,6 @@ POCSAGAppView::POCSAGAppView(NavigationView& nav)
logger->append(LOG_ROOT_DIR "/POCSAG.TXT");
audio::output::start();
baseband::set_pocsag();
}
@@ -99,8 +100,9 @@ void POCSAGAppView::focus() {
POCSAGAppView::~POCSAGAppView() {
audio::output::stop();
// Save ignored address
// Save settings.
persistent_memory::set_pocsag_ignore_address(sym_ignore.value_dec_u32());
enable_logging = check_log.value();
receiver_model.disable();
baseband::shutdown();
+5 -1
View File
@@ -64,8 +64,12 @@ class POCSAGAppView : public View {
NavigationView& nav_;
RxRadioState radio_state_{};
// Settings
bool enable_logging = false;
app_settings::SettingsManager settings_{
"rx_pocsag", app_settings::Mode::RX};
"rx_pocsag"sv,
app_settings::Mode::RX,
{{"enable_logging"sv, &enable_logging}}};
uint32_t last_address = 0xFFFFFFFF;
pocsag::POCSAGState pocsag_state{};
+3 -3
View File
@@ -146,9 +146,9 @@ class TPMSAppView : public View {
OptionsField options_temperature{
{9 * 8, 0 * 16},
1,
{{"C", 0},
{"F", 1}}};
2,
{{STR_DEGREES_C, 0},
{STR_DEGREES_F, 1}}};
RFAmpField field_rf_amp{
{13 * 8, 0 * 16}};
+2 -2
View File
@@ -127,7 +127,7 @@ TemperatureWidget::temperature_t TemperatureWidget::temperature(const sample_t s
}
std::string TemperatureWidget::temperature_str(const temperature_t temperature) const {
return to_string_dec_int(temperature, temp_len - 1) + "C";
return to_string_dec_int(temperature, temp_len - 2) + STR_DEGREES_C;
}
Coord TemperatureWidget::screen_y(
@@ -406,7 +406,7 @@ DebugMenuView::DebugMenuView(NavigationView& nav) {
{"Touch Test", ui::Color::dark_cyan(), &bitmap_icon_notepad, [&nav]() { nav.push<DebugScreenTest>(); }},
{"P.Memory", ui::Color::dark_cyan(), &bitmap_icon_memory, [&nav]() { nav.push<DebugPmemView>(); }},
{"Debug Dump", ui::Color::dark_cyan(), &bitmap_icon_memory, [&nav]() { portapack::persistent_memory::debug_dump(); }},
{"Fonts Viewer", ui::Color::dark_cyan(), &bitmap_icon_notepad, [&nav]() { nav.push<DebugFontsView>(); }},
//{"Fonts Viewer", ui::Color::dark_cyan(), &bitmap_icon_notepad, [&nav]() { nav.push<DebugFontsView>(); }}, // temporarily disabled to conserve ROM space
});
set_max_rows(2); // allow wider buttons
}
+1 -1
View File
@@ -105,7 +105,7 @@ class TemperatureWidget : public Widget {
static constexpr temperature_t display_temp_min = -10; // Accomodate negative values, present in cold startup cases
static constexpr temperature_t display_temp_scale = 3;
static constexpr int bar_width = 1;
static constexpr int temp_len = 4; // Now scale shows up to 4 chars ("-10C")
static constexpr int temp_len = 5; // Now scale shows up to 5 chars ("-10ºC")
};
class TemperatureView : public View {
-1
View File
@@ -64,7 +64,6 @@ class LevelView : public View {
// TODO: needed?
int32_t db{0};
long long int MAX_UFREQ = {7200000000}; // maximum usable freq
rf::Frequency freq_ = {0};
Labels labels{
+33 -120
View File
@@ -38,7 +38,7 @@ namespace fs = std::filesystem;
namespace ui {
void ReconView::check_update_ranges_from_current() {
if (frequency_list.size() && current_is_valid()) {
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(current_entry().frequency_a));
frequency_range.min = current_entry().frequency_a;
@@ -176,81 +176,16 @@ bool ReconView::recon_save_freq(const fs::path& path, size_t freq_index, bool wa
return true;
}
bool ReconView::recon_load_config_from_sd() {
make_new_directory(u"SETTINGS");
File settings_file;
auto error = settings_file.open(RECON_CFG_FILE);
if (error)
return false;
auto complete = false;
auto line_nb = 0;
auto reader = FileLineReader(settings_file);
for (const auto& line : reader) {
switch (line_nb) {
case 0:
input_file = trim(line);
break;
case 1:
output_file = trim(line);
break;
case 2:
parse_int(line, recon_lock_duration);
break;
case 3:
parse_int(line, recon_lock_nb_match);
break;
case 4:
parse_int(line, squelch);
break;
case 5:
parse_int(line, recon_match_mode);
break;
case 6:
parse_int(line, wait);
break;
case 7:
if (!update_ranges) {
parse_int(line, frequency_range.min);
button_manual_start.set_text(to_string_short_freq(frequency_range.min));
}
break;
case 8:
if (!update_ranges) {
parse_int(line, frequency_range.max);
button_manual_end.set_text(to_string_short_freq(frequency_range.max));
}
complete = true;
break;
default:
break;
}
if (complete) break;
line_nb++;
}
return complete;
}
bool ReconView::recon_save_config_to_sd() {
File settings_file;
make_new_directory(u"SETTINGS");
auto error = settings_file.create(RECON_CFG_FILE);
if (error)
return false;
settings_file.write_line(input_file);
settings_file.write_line(output_file);
settings_file.write_line(to_string_dec_uint(recon_lock_duration));
settings_file.write_line(to_string_dec_uint(recon_lock_nb_match));
settings_file.write_line(to_string_dec_int(squelch));
settings_file.write_line(to_string_dec_uint(recon_match_mode));
settings_file.write_line(to_string_dec_int(wait));
settings_file.write_line(to_string_dec_uint(frequency_range.min));
settings_file.write_line(to_string_dec_uint(frequency_range.max));
return true;
void ReconView::load_persisted_settings() {
autostart = persistent_memory::recon_autostart_recon();
autosave = persistent_memory::recon_autosave_freqs();
continuous = persistent_memory::recon_continuous();
filedelete = persistent_memory::recon_clear_output();
load_freqs = persistent_memory::recon_load_freqs();
load_ranges = persistent_memory::recon_load_ranges();
load_hamradios = persistent_memory::recon_load_hamradios();
update_ranges = persistent_memory::recon_update_ranges_when_recon();
auto_record_locked = persistent_memory::recon_auto_record_locked();
}
void ReconView::audio_output_start() {
@@ -333,7 +268,6 @@ void ReconView::focus() {
ReconView::~ReconView() {
recon_stop_recording();
recon_save_config_to_sd();
if (field_mode.selected_index_value() != SPEC_MODULATION)
audio::output::stop();
receiver_model.disable();
@@ -391,29 +325,17 @@ ReconView::ReconView(NavigationView& nav)
};
def_step = 0;
// HELPER: Pre-setting a manual range, based on stored frequency
rf::Frequency stored_freq = receiver_model.target_frequency();
if (stored_freq - OneMHz > 0)
frequency_range.min = stored_freq - OneMHz;
else
frequency_range.min = 0;
button_manual_start.set_text(to_string_short_freq(frequency_range.min));
if (stored_freq + OneMHz < MAX_UFREQ)
frequency_range.max = stored_freq + OneMHz;
else
frequency_range.max = MAX_UFREQ;
button_manual_end.set_text(to_string_short_freq(frequency_range.max));
load_persisted_settings();
// Loading settings
autostart = persistent_memory::recon_autostart_recon();
autosave = persistent_memory::recon_autosave_freqs();
continuous = persistent_memory::recon_continuous();
filedelete = persistent_memory::recon_clear_output();
load_freqs = persistent_memory::recon_load_freqs();
load_ranges = persistent_memory::recon_load_ranges();
load_hamradios = persistent_memory::recon_load_hamradios();
update_ranges = persistent_memory::recon_update_ranges_when_recon();
auto_record_locked = persistent_memory::recon_auto_record_locked();
// When update_ranges is set or range invalid, use the rx model frequency instead of the saved values.
if (update_ranges || frequency_range.max == 0) {
rf::Frequency stored_freq = receiver_model.target_frequency();
frequency_range.min = clip<rf::Frequency>(stored_freq - OneMHz, 0, MAX_UFREQ);
frequency_range.max = clip<rf::Frequency>(stored_freq + OneMHz, 0, MAX_UFREQ);
}
button_manual_start.set_text(to_string_short_freq(frequency_range.min));
button_manual_end.set_text(to_string_short_freq(frequency_range.max));
button_manual_start.on_select = [this, &nav](ButtonWithEncoder& button) {
clear_freqlist_for_ui_action();
@@ -713,16 +635,9 @@ ReconView::ReconView(NavigationView& nav)
input_file = result[0];
output_file = result[1];
freq_file_path = get_freqman_path(output_file).string();
recon_save_config_to_sd();
autosave = persistent_memory::recon_autosave_freqs();
autostart = persistent_memory::recon_autostart_recon();
filedelete = persistent_memory::recon_clear_output();
load_freqs = persistent_memory::recon_load_freqs();
load_ranges = persistent_memory::recon_load_ranges();
load_hamradios = persistent_memory::recon_load_hamradios();
update_ranges = persistent_memory::recon_update_ranges_when_recon();
auto_record_locked = persistent_memory::recon_auto_record_locked();
load_persisted_settings();
ui_settings.save();
frequency_file_load(false);
freqlist_cleared_for_ui_action = false;
@@ -770,7 +685,6 @@ ReconView::ReconView(NavigationView& nav)
file_name.set("=>");
// Loading input and output file from settings
recon_load_config_from_sd();
freq_file_path = get_freqman_path(output_file).string();
field_recon_match_mode.set_selected_index(recon_match_mode);
@@ -872,12 +786,14 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
uint32_t local_recon_lock_duration = recon_lock_duration;
chrono_end = chTimeNow();
time_interval = chrono_end - chrono_start;
if (field_lock_wait.value() == 0) {
if (time_interval <= 1) // capping here to avoid freeze because too quick
local_recon_lock_duration = 2; // minimum working tested value
else
local_recon_lock_duration = time_interval;
if (field_mode.selected_index_value() == SPEC_MODULATION) {
time_interval = chrono_end - chrono_start;
if (field_lock_wait.value() == 0) {
if (time_interval <= 1) // capping here to avoid freeze because too quick
local_recon_lock_duration = 2; // minimum working tested value
else
local_recon_lock_duration = time_interval;
}
}
chrono_start = chrono_end;
@@ -898,10 +814,7 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
if (!timer) {
status = 0;
freq_lock = 0;
last_freq_lock = -1; // need to reset these two as else the green coloration can not occur on consecutive matches
last_nb_match = -1;
timer = local_recon_lock_duration;
big_display.set_style(&Styles::white);
}
if (freq_lock < recon_lock_nb_match) // LOCKING
{
@@ -916,7 +829,7 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
if (db > squelch) // MATCHING LEVEL
{
freq_lock++;
timer += local_recon_lock_duration; // give some more time for next lock
timer += time_interval; // give some more time for next lock
} else {
// continuous, direct cut it if not consecutive match after 1 first match
if (recon_match_mode == RECON_MATCH_CONTINUOUS) {
@@ -970,7 +883,7 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
}
if (timer != 0) {
timer -= local_recon_lock_duration;
timer -= time_interval;
if (timer < 0) {
timer = 0;
}
+17 -3
View File
@@ -68,7 +68,7 @@ class ReconView : public View {
RxRadioState radio_state_{};
app_settings::SettingsManager settings_{
"rx_recon", app_settings::Mode::RX};
"rx_recon"sv, app_settings::Mode::RX};
void check_update_ranges_from_current();
void set_loop_config(bool v);
@@ -90,8 +90,7 @@ class ReconView : public View {
void handle_retune();
void handle_coded_squelch(const uint32_t value);
void handle_remove_current_item();
bool recon_load_config_from_sd();
bool recon_save_config_to_sd();
void load_persisted_settings();
bool recon_save_freq(const std::filesystem::path& path, size_t index, bool warn_if_exists);
// placeholder for possible void recon_start_recording();
void recon_stop_recording();
@@ -164,6 +163,21 @@ class ReconView : public View {
systime_t chrono_start{};
systime_t chrono_end{};
// Persisted settings.
SettingsStore ui_settings{
"recon"sv,
{
{"input_file"sv, &input_file},
{"output_file"sv, &output_file},
{"lock_duration"sv, &recon_lock_duration},
{"lock_nb_match"sv, &recon_lock_nb_match},
{"squelch_level"sv, &squelch},
{"match_mode"sv, &recon_match_mode},
{"match_wait"sv, &wait},
{"range_min"sv, &frequency_range.min},
{"range_max"sv, &frequency_range.max},
}};
std::unique_ptr<RecordView> record_view{};
Labels labels{
@@ -31,9 +31,6 @@
#include "ui_navigation.hpp"
#include "string_format.hpp"
// maximum usable freq
#define MAX_UFREQ 7200000000
// 1Mhz helper
#ifdef OneMHz
#undef OneMHz
+7 -10
View File
@@ -319,11 +319,7 @@ ScannerView::ScannerView(
field_step.set_by_value(receiver_model.frequency_step()); // Default step interval (Hz)
change_mode((freqman_index_t)field_mode.selected_index_value());
// FUTURE: perhaps additional settings should be stored in persistent memory vs using defaults
rf::Frequency stored_freq = receiver_model.target_frequency();
frequency_range.min = stored_freq - 1000000;
button_manual_start.set_text(to_string_short_freq(frequency_range.min));
frequency_range.max = stored_freq + 1000000;
button_manual_end.set_text(to_string_short_freq(frequency_range.max));
// Button to load a Freqman file.
@@ -522,13 +518,17 @@ ScannerView::ScannerView(
// PRE-CONFIGURATION:
field_browse_wait.on_change = [this](int32_t v) { browse_wait = v; };
field_browse_wait.set_value(5);
field_browse_wait.set_value(browse_wait);
field_lock_wait.on_change = [this](int32_t v) { lock_wait = v; };
field_lock_wait.set_value(2);
field_lock_wait.set_value(lock_wait);
field_squelch.on_change = [this](int32_t v) { squelch = v; };
field_squelch.set_value(-30);
field_squelch.set_value(squelch);
// Disable squelch on the model because RSSI handler is where the
// actual squelching is applied for this app.
receiver_model.set_squelch_level(0);
// LOAD FREQUENCIES
frequency_file_load(default_scan_file);
@@ -726,9 +726,6 @@ void ScannerView::change_mode(freqman_index_t new_mod) {
void ScannerView::start_scan_thread() {
receiver_model.enable();
// Disable squelch on the model because RSSI handler is where the
// actual squelching is applied for this app.
receiver_model.set_squelch_level(0);
show_max_index();
// Start Scanner Thread
+16 -6
View File
@@ -37,7 +37,7 @@
#define SCANNER_SLEEP_MS 50 // ms that Scanner Thread sleeps per loop
#define STATISTICS_UPDATES_PER_SEC 10
#define MAX_FREQ_LOCK 10 //# of 50ms cycles scanner locks into freq when signal detected, to verify signal is not spureous
#define MAX_FREQ_LOCK 10 //# of 50ms cycles scanner locks into freq when signal detected, to verify signal is not spurious
namespace ui {
@@ -111,8 +111,22 @@ class ScannerView : public View {
private:
RxRadioState radio_state_{};
// Settings
uint32_t browse_wait{5};
uint32_t lock_wait{2};
int32_t squelch{-30};
scanner_range_t frequency_range{0, MAX_UFREQ};
app_settings::SettingsManager settings_{
"rx_scanner", app_settings::Mode::RX};
"rx_scanner"sv,
app_settings::Mode::RX,
{
{"browse_wait"sv, &browse_wait},
{"lock_wait"sv, &lock_wait},
{"scanner_squelch"sv, &squelch},
{"range_min"sv, &frequency_range.min},
{"range_max"sv, &frequency_range.max},
}};
NavigationView& nav_;
@@ -131,15 +145,11 @@ class ScannerView : public View {
std::string loaded_filename() const;
scanner_range_t frequency_range{0, 0};
int32_t squelch{0};
uint32_t browse_timer{0};
uint32_t lock_timer{0};
uint32_t color_timer{0};
int32_t bigdisplay_current_color{-2};
rf::Frequency bigdisplay_current_frequency{0};
uint32_t browse_wait{0};
uint32_t lock_wait{0};
std::filesystem::path loaded_path{};
std::vector<scanner_entry_t> entries{};
+1 -1
View File
@@ -194,7 +194,7 @@ void SondeView::on_packet(const sonde::Packet& packet) {
if (temp_humid_info.temp != 0) {
double decimals = abs(get_decimals(temp_humid_info.temp, 10, true));
text_temp.set(to_string_dec_int((int)temp_humid_info.temp) + "." + to_string_dec_uint(decimals, 1) + "C");
text_temp.set(to_string_dec_int((int)temp_humid_info.temp) + "." + to_string_dec_uint(decimals, 1) + STR_DEGREES_C);
}
gps_info = packet.get_GPS_data();
+3 -1
View File
@@ -360,6 +360,8 @@ TextEditorView::TextEditorView(NavigationView& nav)
&text_size,
});
viewer.set_font_zoom(enable_zoom);
viewer.on_select = [this]() {
// Treat as if menu button was pressed.
if (button_menu.on_select)
@@ -382,7 +384,7 @@ TextEditorView::TextEditorView(NavigationView& nav)
};
menu.on_zoom() = [this]() {
viewer.toggle_font_zoom();
enable_zoom = viewer.toggle_font_zoom();
refresh_ui();
hide_menu(true);
};
+11 -1
View File
@@ -32,6 +32,7 @@
#include "ui_styles.hpp"
#include "ui_widget.hpp"
#include "app_settings.hpp"
#include "file_wrapper.hpp"
#include "optional.hpp"
@@ -80,7 +81,10 @@ class TextViewer : public Widget {
const Style& style() { return *font_style; }
void set_font_zoom(bool zoom);
void toggle_font_zoom() { set_font_zoom(!font_zoom); };
bool toggle_font_zoom() {
set_font_zoom(!font_zoom);
return font_zoom;
};
private:
bool font_zoom{};
@@ -220,6 +224,12 @@ class TextEditorView : public View {
void on_show() override;
private:
// Settings
bool enable_zoom = false;
SettingsStore settings_store_{
"notepad"sv,
{{"enable_zoom"sv, &enable_zoom}}};
static constexpr size_t max_edit_length = 1024;
std::string edit_line_buffer_{};
+6 -5
View File
@@ -77,6 +77,7 @@ options_t freqman_bandwidths[4] = {
{"12k5", 12500},
{"16k", 16000},
{"25k", 25000},
{"32k", 32000},
{"50k", 50000},
{"75k", 75000},
{"100k", 100000},
@@ -269,18 +270,18 @@ std::string to_freqman_string(const freqman_entry& entry) {
switch (entry.type) {
case freqman_type::Single:
append_field("f", to_string_dec_uint64(entry.frequency_a));
append_field("f", to_string_dec_uint(entry.frequency_a));
break;
case freqman_type::Range:
append_field("a", to_string_dec_uint64(entry.frequency_a));
append_field("b", to_string_dec_uint64(entry.frequency_b));
append_field("a", to_string_dec_uint(entry.frequency_a));
append_field("b", to_string_dec_uint(entry.frequency_b));
if (is_valid(entry.step))
append_field("s", freqman_entry_get_step_string_short(entry.step));
break;
case freqman_type::HamRadio:
append_field("r", to_string_dec_uint64(entry.frequency_a));
append_field("t", to_string_dec_uint64(entry.frequency_b));
append_field("r", to_string_dec_uint(entry.frequency_a));
append_field("t", to_string_dec_uint(entry.frequency_b));
if (is_valid(entry.tone))
append_field("c", tonekey::tone_key_value_string(entry.tone));
+17 -10
View File
@@ -59,30 +59,37 @@ static char* to_string_dec_uint_pad_internal(
return q;
}
template <typename Int>
char* to_string_dec_uint_internal(Int n, StringFormatBuffer& buffer, size_t& length) {
static char* to_string_dec_uint_internal(uint64_t n, StringFormatBuffer& buffer, size_t& length) {
auto end = &buffer.back();
auto start = to_string_dec_uint_internal(end, n);
length = end - start;
return start;
}
char* to_string_dec_uint(uint32_t n, StringFormatBuffer& buffer, size_t& length) {
char* to_string_dec_uint(uint64_t n, StringFormatBuffer& buffer, size_t& length) {
return to_string_dec_uint_internal(n, buffer, length);
}
char* to_string_dec_uint64(uint64_t n, StringFormatBuffer& buffer, size_t& length) {
return to_string_dec_uint_internal(n, buffer, length);
char* to_string_dec_int(int64_t n, StringFormatBuffer& buffer, size_t& length) {
bool negative = n < 0;
auto start = to_string_dec_uint(negative ? -n : n, buffer, length);
if (negative) {
*(--start) = '-';
++length;
}
return start;
}
std::string to_string_dec_uint(uint32_t n) {
std::string to_string_dec_int(int64_t n) {
StringFormatBuffer b{};
size_t len{};
char* str = to_string_dec_uint(n, b, len);
char* str = to_string_dec_int(n, b, len);
return std::string(str, len);
}
std::string to_string_dec_uint64(uint64_t n) {
std::string to_string_dec_uint(uint64_t n) {
StringFormatBuffer b{};
size_t len{};
char* str = to_string_dec_uint(n, b, len);
@@ -194,14 +201,14 @@ std::string to_string_rounded_freq(const uint64_t f, int8_t precision) {
};
if (precision < 1) {
final_str = to_string_dec_uint64(f / 1000000);
final_str = to_string_dec_uint(f / 1000000);
} else {
if (precision > 6)
precision = 6;
uint32_t divisor = pow10[6 - precision];
final_str = to_string_dec_uint64(f / 1000000) + "." + to_string_dec_int(((f + (divisor / 2)) / divisor) % pow10[precision], precision, '0');
final_str = to_string_dec_uint(f / 1000000) + "." + to_string_dec_int(((f + (divisor / 2)) / divisor) % pow10[precision], precision, '0');
}
return final_str;
}
+6 -6
View File
@@ -43,17 +43,17 @@ const char unit_prefix[7]{'n', 'u', 'm', 0, 'k', 'M', 'G'};
using StringFormatBuffer = std::array<char, 24>;
/* uint conversion without memory allocations. */
char* to_string_dec_uint(uint32_t n, StringFormatBuffer& buffer, size_t& length);
char* to_string_dec_uint64(uint64_t n, StringFormatBuffer& buffer, size_t& length);
/* Integer conversion without memory allocations. */
char* to_string_dec_int(int64_t n, StringFormatBuffer& buffer, size_t& length);
char* to_string_dec_uint(uint64_t n, StringFormatBuffer& buffer, size_t& length);
std::string to_string_dec_uint(uint32_t n);
std::string to_string_dec_uint64(uint64_t n);
std::string to_string_dec_int(int64_t n);
std::string to_string_dec_uint(uint64_t n);
// TODO: Allow l=0 to not fill/justify? Already using this way in ui_spectrum.hpp...
std::string to_string_bin(const uint32_t n, const uint8_t l = 0);
std::string to_string_dec_uint(const uint32_t n, const int32_t l, const char fill = ' ');
std::string to_string_dec_int(const int32_t n, const int32_t l = 0, const char fill = 0);
std::string to_string_dec_int(const int32_t n, const int32_t l, const char fill = 0);
std::string to_string_decimal(float decimal, int8_t precision);
std::string to_string_hex(const uint64_t n, const int32_t l = 0);
+15 -20
View File
@@ -33,7 +33,6 @@ BtnGridView::BtnGridView(
bool keep_highlight)
: keep_highlight{keep_highlight} {
set_parent_rect(new_parent_rect);
set_focusable(true);
signal_token_tick_second = rtc_time::signal_tick_second += [this]() {
@@ -47,10 +46,6 @@ BtnGridView::BtnGridView(
BtnGridView::~BtnGridView() {
rtc_time::signal_tick_second -= signal_token_tick_second;
for (auto item : menu_item_views) {
delete item;
}
}
void BtnGridView::set_max_rows(int rows) {
@@ -68,31 +63,30 @@ void BtnGridView::set_parent_rect(const Rect new_parent_rect) {
arrow_more.set_parent_rect({228, (Coord)(displayed_max * button_h), 8, 8});
displayed_max *= rows_;
// TODO: Clean this up :(
if (menu_item_views.size()) {
for (auto item : menu_item_views) {
remove_child(item);
delete item;
}
// Delete any existing buttons.
if (!menu_item_views.empty()) {
for (auto& item : menu_item_views)
remove_child(item.get());
menu_item_views.clear();
}
button_w = 240 / rows_;
button_w = screen_width / rows_;
for (size_t c = 0; c < displayed_max; c++) {
auto item = new NewButton{};
menu_item_views.push_back(item);
add_child(item);
auto item = std::make_unique<NewButton>();
add_child(item.get());
item->set_parent_rect({(int)(c % rows_) * button_w,
(int)(c / rows_) * button_h,
button_w, button_h});
menu_item_views.push_back(std::move(item));
}
update_items();
}
void BtnGridView::set_arrow_enabled(bool new_value) {
if (new_value) {
void BtnGridView::set_arrow_enabled(bool enabled) {
if (enabled) {
add_child(&arrow_more);
} else {
remove_child(&arrow_more);
@@ -118,6 +112,7 @@ void BtnGridView::add_items(std::initializer_list<GridItem> new_items) {
for (auto item : new_items) {
menu_items.push_back(item);
}
update_items();
}
@@ -130,7 +125,7 @@ void BtnGridView::update_items() {
} else
more = false;
for (NewButton* item : menu_item_views) {
for (auto& item : menu_item_views) {
if ((i + offset) >= menu_items.size()) {
item->hidden(true);
item->set_text(" ");
@@ -152,7 +147,7 @@ void BtnGridView::update_items() {
}
NewButton* BtnGridView::item_view(size_t index) const {
return menu_item_views[index];
return menu_item_views[index].get();
}
bool BtnGridView::set_highlighted(int32_t new_value) {
+5 -3
View File
@@ -31,8 +31,10 @@
#include "signal.hpp"
#include <cstddef>
#include <string>
#include <functional>
#include <memory>
#include <string>
#include <vector>
namespace ui {
@@ -62,7 +64,7 @@ class BtnGridView : public View {
uint32_t highlighted_index();
void set_parent_rect(const Rect new_parent_rect) override;
void set_arrow_enabled(bool new_value);
void set_arrow_enabled(bool enabled);
void on_focus() override;
void on_blur() override;
bool on_key(const KeyEvent event) override;
@@ -77,7 +79,7 @@ class BtnGridView : public View {
SignalToken signal_token_tick_second{};
std::vector<GridItem> menu_items{};
std::vector<NewButton*> menu_item_views{};
std::vector<std::unique_ptr<NewButton>> menu_item_views{};
Image arrow_more{
{228, 320 - 8, 8, 8},
+10 -16
View File
@@ -103,10 +103,6 @@ MenuView::MenuView(
MenuView::~MenuView() {
rtc_time::signal_tick_second -= signal_token_tick_second;
for (auto item : menu_item_views) {
delete item;
}
}
void MenuView::set_parent_rect(const Rect new_parent_rect) {
@@ -116,23 +112,22 @@ void MenuView::set_parent_rect(const Rect new_parent_rect) {
arrow_more.set_parent_rect({228, (Coord)(displayed_max * item_height), 8, 8});
// TODO: Clean this up :(
if (menu_item_views.size()) {
for (auto item : menu_item_views) {
remove_child(item);
delete item;
}
if (!menu_item_views.empty()) {
for (auto& item : menu_item_views)
remove_child(item.get());
menu_item_views.clear();
}
for (size_t c = 0; c < displayed_max; c++) {
auto item = new MenuItemView{keep_highlight};
menu_item_views.push_back(item);
add_child(item);
auto item = std::make_unique<MenuItemView>(keep_highlight);
add_child(item.get());
Coord y_pos = c * item_height;
item->set_parent_rect({{0, y_pos},
{size().width(), (Coord)item_height}});
menu_item_views.push_back(std::move(item));
}
update_items();
@@ -150,9 +145,8 @@ void MenuView::on_tick_second() {
}
void MenuView::clear() {
for (auto item : menu_item_views) {
for (auto& item : menu_item_views)
item->set_item(nullptr);
}
menu_items.clear();
highlighted_item = 0;
@@ -184,7 +178,7 @@ void MenuView::update_items() {
} else
more = false;
for (auto item : menu_item_views) {
for (auto& item : menu_item_views) {
if (i >= menu_items.size()) break;
// Assign item data to MenuItemViews according to offset
@@ -201,7 +195,7 @@ void MenuView::update_items() {
}
MenuItemView* MenuView::item_view(size_t index) const {
return menu_item_views[index];
return menu_item_views[index].get();
}
bool MenuView::set_highlighted(int32_t new_value) {
+7 -4
View File
@@ -30,8 +30,10 @@
#include "signal.hpp"
#include <cstddef>
#include <string>
#include <functional>
#include <memory>
#include <string>
#include <vector>
namespace ui {
@@ -75,7 +77,8 @@ class MenuView : public View {
std::function<void(void)> on_left{};
std::function<void(void)> on_highlight{nullptr};
MenuView(Rect new_parent_rect = {0, 0, 240, 304}, bool keep_highlight = false);
MenuView(Rect new_parent_rect = {0, 0, screen_width, screen_height - 16},
bool keep_highlight = false);
~MenuView();
@@ -103,10 +106,10 @@ class MenuView : public View {
SignalToken signal_token_tick_second{};
std::vector<MenuItem> menu_items{};
std::vector<MenuItemView*> menu_item_views{};
std::vector<std::unique_ptr<MenuItemView>> menu_item_views{};
Image arrow_more{
{228, 320 - 8, 8, 8},
{228, screen_height - 8, 8, 8},
&bitmap_more,
Color::white(),
Color::black()};
+2
View File
@@ -35,6 +35,8 @@
#include <algorithm>
#include <functional>
#define MAX_UFREQ 7200000000 // maximum usable frequency
namespace ui {
class FrequencyField : public Widget {
+1
View File
@@ -219,6 +219,7 @@ SystemStatusView::SystemStatusView(
toggle_mute.set_value(pmem::config_audio_mute());
toggle_stealth.set_value(pmem::stealth_mode());
audio::output::stop();
audio::output::update_audio_mute();
refresh();
}
+5 -6
View File
@@ -113,8 +113,7 @@ uint32_t RecordView::set_sampling_rate(uint32_t new_sampling_rate) {
* They are ok as recorded spectrum indication, but they should not be used by Replay app. (the voice speed will be accelerated)
* We keep original black background in all the correct IQ .C16 files BW's Options. */
if ((actual_sampling_rate > 8'000'000) || ((actual_sampling_rate <= 1'600'000) && (oversample_rate > OversampleRate::x8))) { // yellow REC button means not ok for REC, BW >1Mhz , BW <= 100khz due to NG aliasing.
// to be updated or removed in the next PR's, according the achieved extended BW's with good quality bandwith REC limits .
if (actual_sampling_rate > 8'000'000) { // yellow REC button means not ok for REC, BW >1Mhz (BW from 12k5 till 1Mhz OK for REC and Replay)
button_record.set_background(ui::Color::yellow());
} else {
button_record.set_background(ui::Color::black());
@@ -145,11 +144,11 @@ OversampleRate RecordView::get_oversample_rate(uint32_t sample_rate) {
auto rate = ::get_oversample_rate(sample_rate);
// Currently proc_capture only supports x8, x16, x32 for decimation.
if (rate < OversampleRate::x8) // clipping while x4 is not implemented yet.
// Currently proc_capture only supports /8, /16, /32 for decimation.
if (rate < OversampleRate::x8) // clipping while /4 is not implemented yet (it will be used >1Mhz onwards when available)
rate = OversampleRate::x8;
else if (rate > OversampleRate::x32) // clipping while x64 is not implemented yet .
rate = OversampleRate::x32;
else if (rate > OversampleRate::x64) // clipping while /128 is not implemented yet , (but it is not necessary for 12k5)
rate = OversampleRate::x64;
return rate;
}
+13 -3
View File
@@ -120,7 +120,11 @@ void CaptureProcessor::sample_rate_config(const SampleRateConfigMessage& message
break;
case OversampleRate::x32:
decim_1_factor = 2 * decim_1_8.decimation_factor; // /x32 = /4x8 (we applied additional *2 correction to speed up waterfall, no effect to scale spectrum)
decim_1_factor = 2 * decim_1_8.decimation_factor; // /32 = /4x8 (we applied additional *2 correction to speed up waterfall, no effect to scale spectrum)
break;
case OversampleRate::x64:
decim_1_factor = 8 * decim_1_8.decimation_factor; // /64 = /8x8 (we applied additional *8 correction to speed up waterfall, no effect to scale spectrum)
break;
default:
@@ -167,6 +171,9 @@ buffer_c16_t CaptureProcessor::decim_0_execute(const buffer_c8_t& src, const buf
case OversampleRate::x32:
return decim_0_4.execute(src, dst); // decim_0 , /4 with double decim stage
case OversampleRate::x64:
return decim_0_8.execute(src, dst); // decim_0 , /8 with double decim stage
default:
chDbgPanic("Unhandled OversampleRate");
return {};
@@ -183,10 +190,13 @@ buffer_c16_t CaptureProcessor::decim_1_execute(const buffer_c16_t& src, const bu
}
case OversampleRate::x16:
return decim_1_2.execute(src, dst); // total decim /16 = /8x2, applied to 150khz
return decim_1_2.execute(src, dst); // total decim /16 = /8x2, applied to 100khz and 150khz
case OversampleRate::x32:
return decim_1_8.execute(src, dst); // total decim /32 = /4x8, appled to <= 100khz , 75k with margin ,(50k, 25k, 12k5 now also) ...
return decim_1_8.execute(src, dst); // total decim /32 = /4x8, appled to 75k , 50k, 32k
case OversampleRate::x64:
return decim_1_8.execute(src, dst); // total decim /64 = /8x8, appled to 16k and 12k5
default:
chDbgPanic("Unhandled OversampleRate");
+16 -17
View File
@@ -25,10 +25,12 @@
#include "random.hpp"
#include <cstdint>
#include <memory>
#include <utility>
// TODO move to class members SpectrumPainterProcessor
complex16_t* current_line_data = nullptr;
complex16_t* volatile next_line_data = nullptr;
std::unique_ptr<complex16_t[]> current_line_data;
std::unique_ptr<complex16_t[]> next_line_data;
uint32_t current_line_index = 0;
uint32_t current_line_width = 0;
int32_t current_bw = 0;
@@ -41,20 +43,17 @@ int max_val = 127;
// This is called at 3072000/2048 = 1500Hz
void SpectrumPainterProcessor::execute(const buffer_c8_t& buffer) {
for (uint32_t i = 0; i < buffer.count; i++) {
if (current_line_data != nullptr) {
if (current_line_data) {
auto data = current_line_data[(current_line_index++ * current_bw / 3072) % current_line_width];
buffer.p[i] = {(int8_t)data.real(), (int8_t)data.imag()};
} else
buffer.p[i] = {0, 0};
}
// collect new data
if (next_line_data != nullptr) {
if (current_line_data != nullptr)
delete current_line_data;
current_line_data = next_line_data;
next_line_data = nullptr;
// Move "next line" into "current line" if set.
if (next_line_data) {
current_line_data = std::move(next_line_data);
next_line_data.reset();
}
}
@@ -65,7 +64,7 @@ void SpectrumPainterProcessor::run() {
init_genrand(22267);
while (true) {
if (fifo.is_empty() == false && next_line_data == nullptr) {
if (fifo.is_empty() == false && !next_line_data) {
std::vector<uint8_t> data;
fifo.out(data);
@@ -74,8 +73,9 @@ void SpectrumPainterProcessor::run() {
auto fft_width = picture_width * 2;
auto qu = fft_width / 4;
complex16_t* v = new complex16_t[fft_width];
complex16_t* tmp = new complex16_t[fft_width];
// TODO: can these be statically allocated?
auto v = std::make_unique<complex16_t[]>(fft_width);
auto tmp = std::make_unique<complex16_t[]>(fft_width);
for (uint32_t fft_index = 0; fft_index < fft_width; fft_index++) {
if (fft_index < qu) {
@@ -103,10 +103,10 @@ void SpectrumPainterProcessor::run() {
}
}
ifft<complex16_t>(v, fft_width, tmp);
ifft<complex16_t>(v.get(), fft_width, tmp.get());
// normalize
volatile int32_t maximum = 1;
int32_t maximum = 1;
for (uint32_t i = 0; i < fft_width; i++) {
if (v[i].real() > maximum)
maximum = v[i].real();
@@ -127,8 +127,7 @@ void SpectrumPainterProcessor::run() {
}
}
delete tmp;
next_line_data = v;
next_line_data = std::move(v);
ui++;
} else {
chThdSleepMilliseconds(1);
+8 -7
View File
@@ -21,9 +21,10 @@
#include "rssi_dma.hpp"
#include <array>
#include <cstdint>
#include <cstddef>
#include <array>
#include <memory>
#include "hal.h"
#include "gpdma.hpp"
@@ -98,8 +99,8 @@ struct buffers_config_t {
static buffers_config_t buffers_config;
static sample_t* samples{nullptr};
static gpdma::channel::LLI* lli{nullptr};
static std::unique_ptr<sample_t[]> samples;
static std::unique_ptr<gpdma::channel::LLI[]> lli;
static ThreadWait thread_wait;
@@ -128,8 +129,8 @@ void allocate(size_t buffer_count, size_t items_per_buffer) {
const auto peripheral = reinterpret_cast<uint32_t>(&LPC_ADC1->DR[portapack::adc1_rssi_input]) + 1;
const auto control_value = control(gpdma::buffer_words(buffers_config.items_per_buffer, 1));
samples = new sample_t[buffers_config.count * buffers_config.items_per_buffer];
lli = new gpdma::channel::LLI[buffers_config.count];
samples = std::make_unique<sample_t[]>(buffers_config.count * buffers_config.items_per_buffer);
lli = std::make_unique<gpdma::channel::LLI[]>(buffers_config.count);
for (size_t i = 0; i < buffers_config.count; i++) {
const auto memory = reinterpret_cast<uint32_t>(&samples[i * buffers_config.items_per_buffer]);
@@ -141,8 +142,8 @@ void allocate(size_t buffer_count, size_t items_per_buffer) {
}
void free() {
delete samples;
delete lli;
samples.reset();
lli.reset();
}
void enable() {
-4
View File
@@ -97,8 +97,6 @@ void init() {
}
void allocate() {
// samples = new sample_t[channel_samples_per_frame];
// lli = new gpdma::channel::LLI;
lli.srcaddr = reinterpret_cast<uint32_t>(&LPC_ADC0->DR[0]);
lli.destaddr = reinterpret_cast<uint32_t>(&shared_memory.touch_adc_frame.dr[0]);
lli.lli = lli_pointer(&lli);
@@ -106,8 +104,6 @@ void allocate() {
}
void free() {
// delete samples;
// delete lli;
}
void enable() {
+3 -4
View File
@@ -57,10 +57,9 @@
* The oversample rate is used to increase the sample rate to improve SNR and quality.
* This is also used as the interpolation rate when replaying captures. */
inline OversampleRate get_oversample_rate(uint32_t sample_rate) {
if (sample_rate < 50'000) return OversampleRate::x128; // 25k..12k5, prepared for future, OVS ok, but decim. x128 still not implemented.
if (sample_rate < 100'000) return OversampleRate::x64; // 50k, prepared for future, OVS ok, but decim. x64 still not implemented.
if (sample_rate < 150'000) return OversampleRate::x32; // 100k needs x32
if (sample_rate < 250'000) return OversampleRate::x16; // 150k needs x16
if (sample_rate < 30'000) return OversampleRate::x64; // 25k, 16k, 12k5.
if (sample_rate < 80'000) return OversampleRate::x32; // 75k, 50k, 32k.
if (sample_rate < 250'000) return OversampleRate::x16; // 100k and 150k.
return OversampleRate::x8; // 250k .. 1Mhz, that decim x8 , is already applied.(OVerSampling and decim OK)
}
@@ -364,6 +364,7 @@ void defaults() {
set_config_backlight_timer(backlight_config_t{});
set_config_splash(true);
set_encoder_dial_sensitivity(DIAL_SENSITIVITY_NORMAL);
set_config_speaker_disable(true); // Disable AK4951 speaker by default (in case of OpenSourceSDRLab H2)
// Default values for recon app.
set_recon_autosave_freqs(false);
+3
View File
@@ -24,6 +24,9 @@
#include <cstdint>
#define STR_DEGREES_C "\xB0\x43" // ºC - 0xB0 is degree ° symbol in our 8x16 font, 0x43 is "C"
#define STR_DEGREES_F "\xB0\x46" // ºF - 0xB0 is degree ° symbol in our 8x16 font, 0x46 is "F"
namespace units {
class Pressure {
@@ -99,4 +99,10 @@ TEST_CASE("It should convert 8-bit.") {
CHECK_EQ(val, 123);
}
TEST_CASE("It should convert negative.") {
int8_t val = 0;
REQUIRE(parse_int("-64", val));
CHECK_EQ(val, -64);
}
TEST_SUITE_END();
@@ -24,12 +24,27 @@
/* TODO: Tests for all string_format functions. */
TEST_CASE("to_string_dec_uint64 returns correct value.") {
CHECK_EQ(to_string_dec_uint64(0), "0");
CHECK_EQ(to_string_dec_uint64(1), "1");
CHECK_EQ(to_string_dec_uint64(1'000'000), "1000000");
CHECK_EQ(to_string_dec_uint64(1'234'567'890), "1234567890");
CHECK_EQ(to_string_dec_uint64(1'234'567'891), "1234567891");
TEST_CASE("to_string_dec_int returns correct value.") {
CHECK_EQ(to_string_dec_int(0), "0");
CHECK_EQ(to_string_dec_int(1), "1");
CHECK_EQ(to_string_dec_int(-1), "-1");
CHECK_EQ(to_string_dec_int(1'000'000), "1000000");
CHECK_EQ(to_string_dec_int(-1'000'000), "-1000000");
CHECK_EQ(to_string_dec_int(1'234'567'890), "1234567890");
CHECK_EQ(to_string_dec_int(-1'234'567'890), "-1234567890");
CHECK_EQ(to_string_dec_int(1'234'567'891), "1234567891");
CHECK_EQ(to_string_dec_int(-1'234'567'891), "-1234567891");
CHECK_EQ(to_string_dec_int(9'876'543'210), "9876543210");
CHECK_EQ(to_string_dec_int(-9'876'543'210), "-9876543210");
}
TEST_CASE("to_string_dec_uint returns correct value.") {
CHECK_EQ(to_string_dec_uint(0), "0");
CHECK_EQ(to_string_dec_uint(1), "1");
CHECK_EQ(to_string_dec_uint(1'000'000), "1000000");
CHECK_EQ(to_string_dec_uint(1'234'567'890), "1234567890");
CHECK_EQ(to_string_dec_uint(1'234'567'891), "1234567891");
CHECK_EQ(to_string_dec_uint(9'876'543'210), "9876543210");
}
TEST_CASE("to_string_freq returns correct value.") {