mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-09-14 02:29:29 +00:00
Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a476647d70 | |||
| 95a48e5693 | |||
| 09404ca198 | |||
| 564f76b47d | |||
| c6424f1623 | |||
| a4325ac20d | |||
| d8a6422d37 | |||
| cc963c3562 | |||
| 63f99742fc | |||
| a4636d7872 | |||
| bd2ee03e44 | |||
| deeb81c183 | |||
| 12dbf957b3 | |||
| e1cc0b1ad0 | |||
| f079d57fc6 | |||
| 09b9ed642f | |||
| e74a9f3b41 | |||
| ff7a9d10cb | |||
| 5cfd7f1dc2 | |||
| 853ca2ef53 | |||
| cb0a4854f5 | |||
| 1188157a3e |
@@ -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_);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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_;
|
||||
};
|
||||
|
||||
|
||||
@@ -86,7 +86,7 @@ CaptureAppView::CaptureAppView(NavigationView& nav)
|
||||
};
|
||||
|
||||
receiver_model.enable();
|
||||
option_bandwidth.set_selected_index(7); // Preselected default option 500kHz.
|
||||
option_bandwidth.set_by_value(500000); // better by_value than by option_bandwidth.set_selected_index(4), Preselected default option 500kHz.
|
||||
|
||||
record_view.on_error = [&nav](std::string message) {
|
||||
nav.display_modal("Error", message);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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{};
|
||||
|
||||
@@ -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}};
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -57,14 +57,14 @@ class DfuMenu : public View {
|
||||
{{6 * CHARACTER_WIDTH, 11 * LINE_HEIGHT}, "M4 miss:", Color::dark_cyan()},
|
||||
{{6 * CHARACTER_WIDTH, 12 * LINE_HEIGHT}, "uptime:", Color::dark_cyan()}};
|
||||
|
||||
Text text_info_line_1{{15 * CHARACTER_WIDTH, 5 * LINE_HEIGHT, 5 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_2{{15 * CHARACTER_WIDTH, 6 * LINE_HEIGHT, 5 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_3{{15 * CHARACTER_WIDTH, 7 * LINE_HEIGHT, 5 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_4{{15 * CHARACTER_WIDTH, 8 * LINE_HEIGHT, 5 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_5{{15 * CHARACTER_WIDTH, 9 * LINE_HEIGHT, 5 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_6{{15 * CHARACTER_WIDTH, 10 * LINE_HEIGHT, 5 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_7{{15 * CHARACTER_WIDTH, 11 * LINE_HEIGHT, 5 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_8{{15 * CHARACTER_WIDTH, 12 * LINE_HEIGHT, 5 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_1{{15 * CHARACTER_WIDTH, 5 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_2{{15 * CHARACTER_WIDTH, 6 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_3{{15 * CHARACTER_WIDTH, 7 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_4{{15 * CHARACTER_WIDTH, 8 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_5{{15 * CHARACTER_WIDTH, 9 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_6{{15 * CHARACTER_WIDTH, 10 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_7{{15 * CHARACTER_WIDTH, 11 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_8{{15 * CHARACTER_WIDTH, 12 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
};
|
||||
|
||||
class DfuMenu2 : public View {
|
||||
|
||||
@@ -185,7 +185,6 @@ FileManBaseView::FileManBaseView(
|
||||
extension_filter{filter} {
|
||||
add_children({&labels,
|
||||
&text_current,
|
||||
&text_info,
|
||||
&button_exit});
|
||||
|
||||
button_exit.on_select = [this, &nav](Button&) {
|
||||
@@ -250,8 +249,10 @@ void FileManBaseView::refresh_list() {
|
||||
auto entry_name = truncate(entry.path, 20);
|
||||
|
||||
if (entry.is_directory) {
|
||||
auto size_str = (entry.path == parent_dir_path) ? "" : to_string_dec_uint(file_count(entry.path));
|
||||
|
||||
menu_view.add_item(
|
||||
{entry_name,
|
||||
{entry_name + std::string(21 - entry_name.length(), ' ') + size_str,
|
||||
ui::Color::yellow(),
|
||||
&bitmap_icon_dir,
|
||||
[this](KeyEvent key) {
|
||||
@@ -278,7 +279,6 @@ void FileManBaseView::refresh_list() {
|
||||
break;
|
||||
}
|
||||
|
||||
text_info.set(menu_view.item_count() >= max_items_shown ? "Too many files!" : "");
|
||||
menu_view.set_highlighted(prev_highlight);
|
||||
}
|
||||
|
||||
@@ -342,7 +342,6 @@ FileSaveView::FileSaveView(
|
||||
file_{ file }
|
||||
{
|
||||
add_children({
|
||||
&labels,
|
||||
&text_path,
|
||||
&button_edit_path,
|
||||
&text_name,
|
||||
@@ -546,7 +545,6 @@ FileManagerView::FileManagerView(
|
||||
|
||||
add_children({
|
||||
&menu_view,
|
||||
&labels,
|
||||
&text_date,
|
||||
&button_rename,
|
||||
&button_delete,
|
||||
@@ -560,10 +558,16 @@ FileManagerView::FileManagerView(
|
||||
});
|
||||
|
||||
menu_view.on_highlight = [this]() {
|
||||
if (selected_is_valid())
|
||||
text_date.set(to_string_FAT_timestamp(file_created_date(get_selected_full_path())));
|
||||
else
|
||||
text_date.set("");
|
||||
if (menu_view.highlighted_index() >= max_items_shown - 1) {
|
||||
text_date.set_style(&Styles::red);
|
||||
text_date.set("Too many files!");
|
||||
} else {
|
||||
text_date.set_style(&Styles::grey);
|
||||
if (selected_is_valid())
|
||||
text_date.set((is_directory(get_selected_full_path()) ? "Created " : "Modified ") + to_string_FAT_timestamp(file_created_date(get_selected_full_path())));
|
||||
else
|
||||
text_date.set("");
|
||||
}
|
||||
};
|
||||
|
||||
refresh_list();
|
||||
|
||||
@@ -115,11 +115,6 @@ class FileManBaseView : public View {
|
||||
{0, 2 * 8, 240, 26 * 8},
|
||||
true};
|
||||
|
||||
// HACK: for item count limit.
|
||||
Text text_info{
|
||||
{1 * 8, 35 * 8, 15 * 8, 16},
|
||||
""};
|
||||
|
||||
Button button_exit{
|
||||
{22 * 8, 34 * 8, 8 * 8, 32},
|
||||
"Exit"};
|
||||
@@ -218,11 +213,8 @@ class FileManagerView : public FileManBaseView {
|
||||
// True if the selected entry is a real file item.
|
||||
bool selected_is_valid() const;
|
||||
|
||||
Labels labels{
|
||||
{{0, 26 * 8}, "Created ", Color::light_grey()}};
|
||||
|
||||
Text text_date{
|
||||
{8 * 8, 26 * 8, 19 * 8, 16},
|
||||
{0 * 8, 26 * 8, 28 * 8, 16},
|
||||
""};
|
||||
|
||||
NewButton button_rename{
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -37,6 +37,22 @@ namespace fs = std::filesystem;
|
||||
|
||||
namespace ui {
|
||||
|
||||
void ReconView::check_update_ranges_from_current() {
|
||||
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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ReconView::current_is_valid() {
|
||||
return (unsigned)current_index < frequency_list.size();
|
||||
}
|
||||
@@ -160,67 +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);
|
||||
complete = true; // NB: Last entry.
|
||||
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));
|
||||
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() {
|
||||
@@ -290,19 +255,7 @@ void ReconView::handle_retune() {
|
||||
}
|
||||
if (last_index != current_index) {
|
||||
last_index = current_index;
|
||||
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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
check_update_ranges_from_current();
|
||||
text_cycle.set_text(to_string_dec_uint(current_index + 1, 3));
|
||||
update_description();
|
||||
}
|
||||
@@ -315,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();
|
||||
@@ -373,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();
|
||||
@@ -562,9 +502,14 @@ ReconView::ReconView(NavigationView& nav)
|
||||
recon_pause();
|
||||
if (!frequency_range.min || !frequency_range.max) {
|
||||
nav_.display_modal("Error", "Both START and END freqs\nneed a value");
|
||||
} else if (frequency_range.min > frequency_range.max) {
|
||||
nav_.display_modal("Error", "END freq\nis lower than START");
|
||||
} else {
|
||||
if (frequency_range.min > frequency_range.max) {
|
||||
std::swap(frequency_range.min, frequency_range.max);
|
||||
button_manual_start.set_text(to_string_short_freq(frequency_range.min));
|
||||
button_manual_end.set_text(to_string_short_freq(frequency_range.max));
|
||||
}
|
||||
|
||||
// no need to stop audio in SPEC
|
||||
if (field_mode.selected_index_value() != SPEC_MODULATION)
|
||||
audio::output::stop();
|
||||
|
||||
@@ -690,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;
|
||||
@@ -747,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);
|
||||
@@ -839,17 +776,7 @@ void ReconView::frequency_file_load(bool) {
|
||||
receiver_model.enable();
|
||||
receiver_model.set_squelch_level(0);
|
||||
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;
|
||||
}
|
||||
}
|
||||
check_update_ranges_from_current();
|
||||
update_description();
|
||||
handle_retune();
|
||||
}
|
||||
@@ -859,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;
|
||||
|
||||
@@ -886,7 +815,6 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
|
||||
status = 0;
|
||||
freq_lock = 0;
|
||||
timer = local_recon_lock_duration;
|
||||
big_display.set_style(&Styles::white);
|
||||
}
|
||||
if (freq_lock < recon_lock_nb_match) // LOCKING
|
||||
{
|
||||
@@ -901,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) {
|
||||
@@ -955,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;
|
||||
}
|
||||
|
||||
@@ -68,8 +68,9 @@ 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);
|
||||
void clear_freqlist_for_ui_action();
|
||||
void reset_indexes();
|
||||
@@ -89,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();
|
||||
@@ -163,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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -110,11 +110,25 @@ class ScannerView : public View {
|
||||
std::string title() const override { return "Scanner"; };
|
||||
|
||||
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_;
|
||||
RxRadioState radio_state_{};
|
||||
|
||||
void start_scan_thread();
|
||||
void restart_scan();
|
||||
@@ -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{};
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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_{};
|
||||
|
||||
|
||||
@@ -579,6 +579,17 @@ bool is_empty_directory(const path& file_path) {
|
||||
return !((result == FR_OK) && (filinfo.fname[0] != (TCHAR)'\0'));
|
||||
}
|
||||
|
||||
int file_count(const path& directory) {
|
||||
int count{0};
|
||||
|
||||
for (auto& entry : std::filesystem::directory_iterator(directory, (const TCHAR*)u"*")) {
|
||||
(void)entry; // avoid unused warning
|
||||
++count;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
space_info space(const path& p) {
|
||||
DWORD free_clusters{0};
|
||||
FATFS* fs;
|
||||
|
||||
@@ -250,6 +250,8 @@ bool file_exists(const path& file_path);
|
||||
bool is_directory(const path& file_path);
|
||||
bool is_empty_directory(const path& file_path);
|
||||
|
||||
int file_count(const path& dir_path);
|
||||
|
||||
space_info space(const path& p);
|
||||
|
||||
} /* namespace filesystem */
|
||||
|
||||
@@ -76,16 +76,19 @@ options_t freqman_bandwidths[4] = {
|
||||
// SPEC -- TODO: these should be indexes.
|
||||
{"12k5", 12500},
|
||||
{"16k", 16000},
|
||||
{"20k", 20000},
|
||||
{"25k", 25000},
|
||||
{"32k", 32000},
|
||||
{"50k", 50000},
|
||||
{"75k", 75000},
|
||||
{"100k", 100000},
|
||||
{"150k", 150000},
|
||||
{"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) */
|
||||
{"600k", 600000}, /* We doubled x2 previous REC BW limit , now extended BW from 600k to 1M with fast enough SD card in C16 or C8 format .*/
|
||||
{"650k", 650000},
|
||||
{"750k", 750000}, /* From this BW onwards, the LCD is ok, but the recorded file is decimated, (not real file size) */
|
||||
{"1100k", 1100000},
|
||||
{"750k", 750000},
|
||||
{"1000k", 1000000}, /* New limit bandwith option for recording in C16 (in fast SD card) or in C8 */
|
||||
{"1500k", 1500000}, /* From this BW onwards, the LCD is ok, but M4 CPU is having periodical sample rec dropps, (not real file size, accelerated replay) */
|
||||
{"1750k", 1750000},
|
||||
{"2000k", 2000000},
|
||||
{"2500k", 2500000},
|
||||
@@ -267,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));
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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()};
|
||||
|
||||
@@ -35,6 +35,8 @@
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
|
||||
#define MAX_UFREQ 7200000000 // maximum usable frequency
|
||||
|
||||
namespace ui {
|
||||
|
||||
class FrequencyField : public Widget {
|
||||
|
||||
@@ -383,26 +383,30 @@ void WaterfallView::on_audio_spectrum() {
|
||||
// TODO: Comments below refer to a fixed oversample rate (8x), cleanup.
|
||||
uint32_t filter_bandwidth_for_sampling_rate(int32_t sampling_rate) {
|
||||
switch (sampling_rate) { // Use the var fs (sampling_rate) to set up BPF aprox < fs_max / 2 by Nyquist theorem.
|
||||
case 0 ... 2'000'000: // BW Captured range (0 <= 250kHz max) fs = 8 x 250 kHz.
|
||||
case 0 ... 3'500'000: // BW Captured range (0 <= 250kHz max) fs = 8x250 kHz =2000., 16x150 khz =2400, 32x100 khz =3200, (32x75k = 2400), (future 64x40 khz =2400)
|
||||
return 1'750'000; // Minimum BPF MAX2837 for all those lower BW options.
|
||||
|
||||
case 4'000'000 ... 6'000'000: // BW capture range (500k...750kHz max) fs_max = 8 x 750kHz = 6Mhz
|
||||
// BW 500k...750kHz, ex. 500kHz (fs = 8 x BW = 4Mhz), BW 600kHz (fs = 4,8Mhz), BW 750 kHz (fs = 6Mhz).
|
||||
return 2'500'000; // In some IC, MAX2837 appears as 2250000, but both work similarly.
|
||||
|
||||
case 8'800'000: // BW capture 1,1Mhz fs = 8 x 1,1Mhz = 8,8Mhz. (1Mhz showed slightly higher noise background).
|
||||
return 3'500'000;
|
||||
case 8'000'000: // BW capture 1Mhz fs = 8 x 1Mhz = 8Mhz. (1Mhz showed slightly higher noise background).
|
||||
return 3'500'000; // some low SD cards, if not showing avg. writting speed >4MB/sec, they will produce sammples drop at REC with 1MB and C16 format.
|
||||
|
||||
case 12'000'000: // BW capture 1,5Mhz, fs = 8 x 1,5Mhz = 12Mhz
|
||||
// Good BPF, good matching, we have some periodical M4 % samples drop.
|
||||
return 5'000'000;
|
||||
|
||||
case 14'000'000: // BW capture 1,75Mhz, fs = 8 x 1,75Mhz = 14Mhz
|
||||
// Good BPF, good matching, but LCD flickers, refresh rate should be < 20 Hz, reasonable picture.
|
||||
// Good BPF, good matching, we have some periodical M4 % samples drop.
|
||||
return 5'000'000;
|
||||
|
||||
case 16'000'000: // BW capture 2Mhz, fs = 8 x 2Mhz = 16Mhz
|
||||
// Good BPF, good matching, but LCD flickers, refresh rate should be < 20 Hz, reasonable picture.
|
||||
// Good BPF, good matching, we have some periodical M4 % samples drop.
|
||||
return 6'000'000;
|
||||
|
||||
case 20'000'000: // BW capture 2,5Mhz, fs = 8 x 2,5 Mhz = 20Mhz
|
||||
// Good BPF, good matching, but LCD flickers, refresh rate should be < 20 Hz, reasonable picture.
|
||||
// Good BPF, good matching, we have some periodical M4 % samples drop.
|
||||
return 7'000'000;
|
||||
|
||||
default: // BW capture 2,75Mhz, fs = 8 x 2,75Mhz = 22Mhz max ADC sampling and others.
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -107,13 +107,13 @@ uint32_t RecordView::set_sampling_rate(uint32_t new_sampling_rate) {
|
||||
auto oversample_rate = get_oversample_rate(new_sampling_rate);
|
||||
auto actual_sampling_rate = new_sampling_rate * toUType(oversample_rate);
|
||||
|
||||
/* We are changing "REC" icon background to yellow in BW rec Options >600kHz
|
||||
* where we are NOT recording full IQ .C16 files (recorded files are decimated ones).
|
||||
* Those decimated recorded files, has not the full IQ samples.
|
||||
* are ok as recorded spectrum indication, but they should not be used by Replay app.
|
||||
/* We are changing "REC" icon background to yellow in BW rec Options >1Mhz
|
||||
* > 1Mhz BW options , we are NOT recording full IQ .C16 files (those files has some periodical missing dropped samples).
|
||||
* Those recorded files, has not the full IQ samples information, looks like decimated in file size.
|
||||
* 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 > 4'800'000) { // > BW >600kHz (fs=8*BW), (750kHz...2750kHz)
|
||||
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());
|
||||
@@ -144,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 and x16 for decimation.
|
||||
if (rate < OversampleRate::x8)
|
||||
// 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::x16)
|
||||
rate = OversampleRate::x16;
|
||||
else if (rate > OversampleRate::x64) // clipping while /128 is not implemented yet , (but it is not necessary for 12k5)
|
||||
rate = OversampleRate::x64;
|
||||
|
||||
return rate;
|
||||
}
|
||||
|
||||
@@ -27,9 +27,12 @@
|
||||
#include "utility.hpp"
|
||||
|
||||
CaptureProcessor::CaptureProcessor() {
|
||||
decim_0_4.configure(taps_200k_decim_0.taps, 33554432);
|
||||
decim_0_8.configure(taps_200k_decim_0.taps, 33554432);
|
||||
decim_1.configure(taps_200k_decim_1.taps, 131072);
|
||||
decim_0_4.configure(taps_200k_decim_0.taps, 33554432); // to be used with decim1 (/2), then total two stages decim (/8)
|
||||
decim_0_8.configure(taps_200k_decim_0.taps, 33554432); // to be used with decim1 (/2), then total two stages decim (/16)
|
||||
decim_0_8_180k.configure(taps_180k_wfm_decim_0.taps, 33554432); // to be used alone - no additional decim1 (/2), then total single stage decim (/8)
|
||||
|
||||
decim_1_2.configure(taps_200k_decim_1.taps, 131072);
|
||||
decim_1_8.configure(taps_16k0_decim_1.taps, 131072); // tentative decim1 /8 and taps, pending to be optimized.
|
||||
|
||||
channel_spectrum.set_decimation_factor(1);
|
||||
baseband_thread.start();
|
||||
@@ -37,8 +40,17 @@ CaptureProcessor::CaptureProcessor() {
|
||||
|
||||
void CaptureProcessor::execute(const buffer_c8_t& buffer) {
|
||||
/* 2.4576MHz, 2048 samples */
|
||||
const auto decim_0_out = decim_0_execute(buffer, dst_buffer);
|
||||
const auto decim_1_out = decim_1.execute(decim_0_out, dst_buffer);
|
||||
const auto decim_0_out = decim_0_execute(buffer, dst_buffer); // selectable 3 possible decim_0, (/4. /8 200k soft filter , /8 180k sharp )
|
||||
const auto decim_1_out = decim_1_execute(decim_0_out, dst_buffer); // selectable 3 possible decim_1, (/8. /2 200k or bypassed /1 )
|
||||
|
||||
/* this code was valid when we had only 2 decim1 cases.
|
||||
const auto decim_1_out = baseband_fs < 4800'000
|
||||
? decim_1_2.execute(decim_0_out, dst_buffer) // < 600khz double decim. stage , means 500khz and lower bit rates.
|
||||
// ? decim_1_8.execute(decim_0_out, dst_buffer) // < 600khz double decim. stage , means 500khz and lower bit rates.
|
||||
: decim_0_out; // >= 600khz single decim. stage , means 600khz and upper bit rates.
|
||||
|
||||
} */
|
||||
|
||||
const auto& decimator_out = decim_1_out;
|
||||
const auto& channel = decimator_out;
|
||||
|
||||
@@ -85,13 +97,48 @@ void CaptureProcessor::sample_rate_config(const SampleRateConfigMessage& message
|
||||
|
||||
baseband_thread.set_sampling_rate(baseband_fs);
|
||||
|
||||
// Current fw , we are using only 2 decim_0 modes, /4 , /8
|
||||
auto decim_0_factor = oversample_rate == OversampleRate::x8
|
||||
? decim_0_4.decimation_factor
|
||||
: decim_0_8.decimation_factor;
|
||||
size_t decim_0_output_fs = baseband_fs / decim_0_factor;
|
||||
|
||||
size_t decim_0_output_fs = baseband_fs / decim_0_factor;
|
||||
size_t decim_1_input_fs = decim_0_output_fs;
|
||||
size_t decim_1_output_fs = decim_1_input_fs / decim_1.decimation_factor;
|
||||
|
||||
size_t decim_1_factor;
|
||||
switch (oversample_rate) { // we are using 3 decim_1 modes, /1 , /2 , /8
|
||||
case OversampleRate::x8:
|
||||
if (baseband_fs < 4800'000) {
|
||||
decim_1_factor = decim_1_2.decimation_factor; // /8 = /4x2
|
||||
} else {
|
||||
decim_1_factor = 2 * 1; // 600khz and onwards, single decim /8 = /8x1 (we applied additional *2 correction to speed up waterfall, no effect to scale spectrum)
|
||||
}
|
||||
break;
|
||||
|
||||
case OversampleRate::x16:
|
||||
decim_1_factor = 2 * decim_1_2.decimation_factor; // /16 = /8x2 (we applied additional *2 correction to increase waterfall spped >=600k and smooth & avoid abnormal motion >1M5 )
|
||||
break;
|
||||
|
||||
case OversampleRate::x32:
|
||||
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:
|
||||
decim_1_factor = 2; // just default initial value to remove compile warning.
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
auto decim_1_factor = oversample_rate == OversampleRate::x32 // that was ok, when we had only 2 oversampling x8 , x16
|
||||
? decim_1_8.decimation_factor
|
||||
: decim_1_2.decimation_factor;
|
||||
|
||||
*/
|
||||
size_t decim_1_output_fs = decim_1_input_fs / decim_1_factor;
|
||||
|
||||
channel_filter_low_f = taps_200k_decim_1.low_frequency_normalized * decim_1_input_fs;
|
||||
channel_filter_high_f = taps_200k_decim_1.high_frequency_normalized * decim_1_input_fs;
|
||||
@@ -111,11 +158,45 @@ void CaptureProcessor::capture_config(const CaptureConfigMessage& message) {
|
||||
|
||||
buffer_c16_t CaptureProcessor::decim_0_execute(const buffer_c8_t& src, const buffer_c16_t& dst) {
|
||||
switch (oversample_rate) {
|
||||
case OversampleRate::x8:
|
||||
return decim_0_4.execute(src, dst);
|
||||
case OversampleRate::x8: // we can get /8 by two means , decim0 (/4) + decim1 (/2) . or just decim0 (/8)
|
||||
if (baseband_fs < 4800'000) { // 600khz (600k x 8)
|
||||
return decim_0_4.execute(src, dst); // decim_0 , /4 with double decim stage
|
||||
} else {
|
||||
return decim_0_8_180k.execute(src, dst); // decim_0 /8 with single decim stage
|
||||
}
|
||||
|
||||
case OversampleRate::x16:
|
||||
return decim_0_8.execute(src, dst);
|
||||
return decim_0_8.execute(src, dst); // decim_0 , /8 with double decim stage
|
||||
|
||||
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 {};
|
||||
}
|
||||
}
|
||||
|
||||
buffer_c16_t CaptureProcessor::decim_1_execute(const buffer_c16_t& src, const buffer_c16_t& dst) {
|
||||
switch (oversample_rate) {
|
||||
case OversampleRate::x8: // we can get /8 by two means , decim0 (/4) + decim1 (/2) . or just decim0 (/8)
|
||||
if (baseband_fs < 4800'000) { // 600khz (600k x 8)
|
||||
return decim_1_2.execute(src, dst);
|
||||
} else {
|
||||
return src;
|
||||
}
|
||||
|
||||
case OversampleRate::x16:
|
||||
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 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");
|
||||
|
||||
@@ -56,8 +56,10 @@ class CaptureProcessor : public BasebandProcessor {
|
||||
* use decim_0_8 for an overall decimation factor of 16. */
|
||||
dsp::decimate::FIRC8xR16x24FS4Decim4 decim_0_4{};
|
||||
dsp::decimate::FIRC8xR16x24FS4Decim8 decim_0_8{};
|
||||
dsp::decimate::FIRC8xR16x24FS4Decim8 decim_0_8_180k{};
|
||||
dsp::decimate::FIRC16xR16x16Decim2 decim_1_2{};
|
||||
dsp::decimate::FIRC16xR16x32Decim8 decim_1_8{};
|
||||
|
||||
dsp::decimate::FIRC16xR16x16Decim2 decim_1{};
|
||||
int32_t channel_filter_low_f = 0;
|
||||
int32_t channel_filter_high_f = 0;
|
||||
int32_t channel_filter_transition = 0;
|
||||
@@ -79,6 +81,9 @@ class CaptureProcessor : public BasebandProcessor {
|
||||
|
||||
/* Dispatch to the correct decim_0 based on oversample rate. */
|
||||
buffer_c16_t decim_0_execute(const buffer_c8_t& src, const buffer_c16_t& dst);
|
||||
|
||||
/* Dispatch to the correct decim_1 based on oversample rate. */
|
||||
buffer_c16_t decim_1_execute(const buffer_c16_t& src, const buffer_c16_t& dst);
|
||||
};
|
||||
|
||||
#endif /*__PROC_CAPTURE_HPP__*/
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -456,6 +456,9 @@ bool ILI9341::drawBMP2(const ui::Point p, const std::filesystem::path& file) {
|
||||
width = bmp_header.width;
|
||||
height = bmp_header.height;
|
||||
|
||||
if (width != 240)
|
||||
return false;
|
||||
|
||||
file_pos = bmp_header.image_data;
|
||||
|
||||
py = height + 16;
|
||||
|
||||
@@ -815,6 +815,12 @@ enum class OversampleRate : uint8_t {
|
||||
|
||||
/* Oversample rate of 32 times the sample rate. */
|
||||
x32 = 32,
|
||||
|
||||
/* Oversample rate of 64 times the sample rate. */
|
||||
x64 = 64,
|
||||
|
||||
/* Oversample rate of 128 times the sample rate. */
|
||||
x128 = 128,
|
||||
};
|
||||
|
||||
class SampleRateConfigMessage : public Message {
|
||||
|
||||
@@ -40,16 +40,28 @@
|
||||
* The baseband needs to know how to correctly decimate (or interpolate) so
|
||||
* the set of allowed scalars is fixed (See OversampleRate enum).
|
||||
* In testing, a minimum rate of 400kHz seems to the functional minimum.
|
||||
*
|
||||
* There are several different concepts or terms related to Capture and Replay,
|
||||
* (1) oversampling (x8, x16 ,...) / decimation (/8, /16...)
|
||||
* In Capture App , when ADC can not handle directly a requiered low sample rates ,
|
||||
* we need to apply oversampling (x8. x16 ex) , getting more real samples than needed) by "x_number" ,
|
||||
* and later to write it to SD card with the proper needed real sample rate , we apply Decimation , "/ number" .
|
||||
*
|
||||
* (2) up-sampling or re-escale or interpolation. (x8, x16, ...)
|
||||
* In Replay-list App, when we got too low bit rate data for Hackrf ,
|
||||
* we need to upsampling or interpolate or resampling to increase those low bit rates
|
||||
* to a proper sample rate higher than the min. to be able to be transmitted by Hackrf.
|
||||
*/
|
||||
|
||||
/* Gets the oversample rate for a given sample rate.
|
||||
* 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 < 25'000) return OversampleRate::x32;
|
||||
if (sample_rate < 50'000) return OversampleRate::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;
|
||||
return OversampleRate::x8; // 250k .. 1Mhz, that decim x8 , is already applied.(OVerSampling and decim OK)
|
||||
}
|
||||
|
||||
/* Gets the actual sample rate for a given sample rate.
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -365,9 +365,13 @@ void Text::set(std::string_view value) {
|
||||
void Text::paint(Painter& painter) {
|
||||
const auto rect = screen_rect();
|
||||
auto s = has_focus() ? style().invert() : style();
|
||||
auto max_len = (unsigned)rect.width() / s.font.char_width();
|
||||
|
||||
painter.fill_rectangle(rect, s.background);
|
||||
|
||||
if (text.length() > max_len)
|
||||
text.resize(max_len);
|
||||
|
||||
painter.draw_string(
|
||||
rect.location(),
|
||||
s,
|
||||
|
||||
@@ -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.") {
|
||||
|
||||
Reference in New Issue
Block a user