mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-09-02 04:49:01 +00:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a8cdde7bea | |||
| 8b395239d2 | |||
| 730e7ad72b | |||
| 1e4ea753ba | |||
| a86993af91 | |||
| a832bd433d | |||
| f9fdeb3419 | |||
| 3c2fc74f82 | |||
| 69927e3c2d | |||
| 1edd3716a1 | |||
| bc1ad5598c | |||
| 8cae998146 | |||
| 9a22a760ad | |||
| 909b00bdae | |||
| 58e731c56e | |||
| 04e77e0ee8 | |||
| 55b93a7044 | |||
| f22046eccc | |||
| dfadd38e32 | |||
| 3cb927c840 |
@@ -13,6 +13,7 @@
|
||||
/sdcard/FREQMAN/BHT*
|
||||
/sdcard/FREQMAN/R.TXT
|
||||
/sdcard/FREQMAN/XXX.TXT
|
||||
/armbin
|
||||
|
||||
# Compiled Object files
|
||||
*.slo
|
||||
@@ -59,6 +60,9 @@ CMakeFiles/
|
||||
*.sublime-workspace
|
||||
.vscode
|
||||
|
||||
# VSCodium extensions
|
||||
.history
|
||||
|
||||
# Host OS leftovers
|
||||
.DS_Store
|
||||
/firmware/CMakeCache.txt
|
||||
|
||||
@@ -100,7 +100,7 @@ ACARSAppView::ACARSAppView(NavigationView& nav) {
|
||||
|
||||
logger = std::make_unique<ACARSLogger>();
|
||||
if (logger)
|
||||
logger->append("acars.txt");
|
||||
logger->append(LOG_ROOT_DIR "/ACARS.TXT");
|
||||
}
|
||||
|
||||
ACARSAppView::~ACARSAppView() {
|
||||
|
||||
@@ -399,7 +399,7 @@ AISAppView::AISAppView(NavigationView& nav) : nav_ { nav } {
|
||||
|
||||
logger = std::make_unique<AISLogger>();
|
||||
if( logger ) {
|
||||
logger->append(u"ais.txt");
|
||||
logger->append( LOG_ROOT_DIR "/AIS.TXT" );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,8 @@ using namespace tonekey;
|
||||
|
||||
#include "string_format.hpp"
|
||||
|
||||
#include "freqman.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
/* AMOptionsView *********************************************************/
|
||||
@@ -51,6 +53,7 @@ AMOptionsView::AMOptionsView(
|
||||
&options_config,
|
||||
});
|
||||
|
||||
freqman_set_bandwidth_option( AM_MODULATION , options_config ); // adding the common message from freqman.cpp to the options_config
|
||||
options_config.set_selected_index(receiver_model.am_configuration());
|
||||
options_config.on_change = [this](size_t n, OptionsField::value_t) {
|
||||
receiver_model.set_am_configuration(n);
|
||||
@@ -72,6 +75,7 @@ NBFMOptionsView::NBFMOptionsView(
|
||||
&field_squelch
|
||||
});
|
||||
|
||||
freqman_set_bandwidth_option( NFM_MODULATION , options_config ); // adding the common message from freqman.cpp to the options_config
|
||||
options_config.set_selected_index(receiver_model.nbfm_configuration());
|
||||
options_config.on_change = [this](size_t n, OptionsField::value_t) {
|
||||
receiver_model.set_nbfm_configuration(n);
|
||||
@@ -83,6 +87,27 @@ NBFMOptionsView::NBFMOptionsView(
|
||||
};
|
||||
}
|
||||
|
||||
/* WFMOptionsView *******************************************************/
|
||||
|
||||
WFMOptionsView::WFMOptionsView(
|
||||
const Rect parent_rect, const Style* const style
|
||||
) : View { parent_rect }
|
||||
{
|
||||
set_style(style);
|
||||
|
||||
add_children({
|
||||
&label_config,
|
||||
&options_config,
|
||||
});
|
||||
|
||||
freqman_set_bandwidth_option( WFM_MODULATION , options_config ); // adding the common message from freqman.cpp to the options_config
|
||||
options_config.set_selected_index(receiver_model.wfm_configuration());
|
||||
options_config.on_change = [this](size_t n, OptionsField::value_t) {
|
||||
receiver_model.set_wfm_configuration(n);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/* SPECOptionsView *******************************************************/
|
||||
|
||||
SPECOptionsView::SPECOptionsView(
|
||||
@@ -346,6 +371,7 @@ void AnalogAudioView::on_show_options_modulation() {
|
||||
break;
|
||||
|
||||
case ReceiverModel::Mode::WidebandFMAudio:
|
||||
widget = std::make_unique<WFMOptionsView>(options_view_rect, &style_options_group);
|
||||
waterfall.show_audio_spectrum_view(true);
|
||||
text_ctcss.hidden(true);
|
||||
break;
|
||||
|
||||
@@ -54,12 +54,7 @@ private:
|
||||
OptionsField options_config {
|
||||
{ 3 * 8, 0 * 16 },
|
||||
5,
|
||||
{
|
||||
{ "DSB 9k ", 0 },
|
||||
{ "DSB 6k ", 0 },
|
||||
{ "USB+3k ", 0 },
|
||||
{ "LSB-3k ", 0 },
|
||||
{ "CW ", 0 },
|
||||
{ // using common messages from freqman.cpp
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -76,10 +71,7 @@ private:
|
||||
OptionsField options_config {
|
||||
{ 3 * 8, 0 * 16 },
|
||||
4,
|
||||
{
|
||||
{ " 8k5", 0 },
|
||||
{ "11k ", 0 },
|
||||
{ "16k ", 0 },
|
||||
{ // using common messages from freqman.cpp
|
||||
}
|
||||
};
|
||||
|
||||
@@ -96,6 +88,24 @@ private:
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
class WFMOptionsView : public View {
|
||||
public:
|
||||
WFMOptionsView(const Rect parent_rect, const Style* const style);
|
||||
|
||||
private:
|
||||
Text label_config {
|
||||
{ 0 * 8, 0 * 16, 2 * 8, 1 * 16 },
|
||||
"BW",
|
||||
};
|
||||
OptionsField options_config {
|
||||
{ 3 * 8, 0 * 16 },
|
||||
3,
|
||||
{ // using common messages from freqman.cpp
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
class AnalogAudioView;
|
||||
|
||||
class SPECOptionsView : public View {
|
||||
@@ -222,7 +232,7 @@ private:
|
||||
|
||||
RecordView record_view {
|
||||
{ 0 * 8, 2 * 16, 30 * 8, 1 * 16 },
|
||||
u"AUD",
|
||||
u"AUD", u"AUDIO",
|
||||
RecordView::FileType::WAV,
|
||||
4096,
|
||||
4
|
||||
|
||||
@@ -108,7 +108,7 @@ private:
|
||||
|
||||
RecordView record_view {
|
||||
{ 0 * 8, 2 * 16, 30 * 8, 1 * 16 },
|
||||
u"BBD_????", RecordView::FileType::RawS16, 16384, 3
|
||||
u"BBD_????.*", u"CAPTURES", RecordView::FileType::RawS16, 16384, 3
|
||||
};
|
||||
|
||||
spectrum::WaterfallWidget waterfall { };
|
||||
|
||||
@@ -132,7 +132,7 @@ ERTAppView::ERTAppView(NavigationView&) {
|
||||
|
||||
logger = std::make_unique<ERTLogger>();
|
||||
if( logger ) {
|
||||
logger->append(u"ert.txt");
|
||||
logger->append( LOG_ROOT_DIR "/ERT.TXT" );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -131,7 +131,7 @@ POCSAGAppView::POCSAGAppView(NavigationView& nav) {
|
||||
|
||||
logger = std::make_unique<POCSAGLogger>();
|
||||
if (logger)
|
||||
logger->append("pocsag.txt");
|
||||
logger->append(LOG_ROOT_DIR "/POCSAG.TXT");
|
||||
|
||||
audio::output::start();
|
||||
audio::output::unmute();
|
||||
|
||||
@@ -64,8 +64,8 @@ private:
|
||||
std::app_settings settings { };
|
||||
std::app_settings::AppSettings app_settings { };
|
||||
|
||||
bool logging { true };
|
||||
bool ignore { true };
|
||||
bool logging { false };
|
||||
bool ignore { false };
|
||||
uint32_t last_address = 0xFFFFFFFF;
|
||||
pocsag::POCSAGState pocsag_state { };
|
||||
|
||||
@@ -91,13 +91,7 @@ private:
|
||||
FrequencyField field_frequency {
|
||||
{ 0 * 8, 0 * 8 },
|
||||
};
|
||||
Checkbox check_log {
|
||||
{ 24 * 8, 21 },
|
||||
3,
|
||||
"LOG",
|
||||
true
|
||||
};
|
||||
NumberField field_volume{
|
||||
NumberField field_volume{
|
||||
{ 28 * 8, 0 * 16 },
|
||||
2,
|
||||
{ 0, 99 },
|
||||
@@ -106,16 +100,22 @@ private:
|
||||
};
|
||||
|
||||
Checkbox check_ignore {
|
||||
{ 1 * 8, 21 },
|
||||
12,
|
||||
"Ignore addr:",
|
||||
true
|
||||
{ 0 * 8, 21 },
|
||||
8,
|
||||
"Ign addr",
|
||||
false
|
||||
};
|
||||
SymField sym_ignore {
|
||||
{ 16 * 8, 21 },
|
||||
{ 13 * 8, 25 },
|
||||
7,
|
||||
SymField::SYMFIELD_DEC
|
||||
};
|
||||
Checkbox check_log {
|
||||
{ 240 - 8 * 8, 21 },
|
||||
3,
|
||||
"LOG",
|
||||
false
|
||||
};
|
||||
|
||||
Console console {
|
||||
{ 0, 3 * 16, 240, 256 }
|
||||
|
||||
@@ -196,7 +196,7 @@ TPMSAppView::TPMSAppView(NavigationView&) {
|
||||
|
||||
logger = std::make_unique<TPMSLogger>();
|
||||
if( logger ) {
|
||||
logger->append(u"tpms.txt");
|
||||
logger->append( LOG_ROOT_DIR "/TPMS.TXT" );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -351,7 +351,7 @@ void ADSBRxView::sort_entries_by_state()
|
||||
}
|
||||
|
||||
void ADSBRxView::on_frame(const ADSBFrameMessage * message) {
|
||||
logger = std::make_unique<ADSBLogger>();
|
||||
logger = std::make_unique<ADSBLogger>();
|
||||
rtc::RTC datetime;
|
||||
std::string callsign;
|
||||
std::string str_info;
|
||||
@@ -422,6 +422,7 @@ void ADSBRxView::on_frame(const ADSBFrameMessage * message) {
|
||||
} else if(msg_type == AIRBORNE_VEL && msg_sub >= VEL_GND_SUBSONIC && msg_sub <= VEL_AIR_SUPERSONIC){
|
||||
entry.set_frame_velo(frame);
|
||||
if (logger) {
|
||||
logger->append( LOG_ROOT_DIR "/ADSB.TXT" );
|
||||
logentry += "Type:" + to_string_dec_uint(msg_sub) +
|
||||
" Hdg:" + to_string_dec_uint(entry.velo.heading) +
|
||||
" Spd: "+ to_string_dec_int(entry.velo.speed);
|
||||
@@ -432,7 +433,7 @@ void ADSBRxView::on_frame(const ADSBFrameMessage * message) {
|
||||
} // frame.get_DF() == DF_ADSB
|
||||
|
||||
if (logger) {
|
||||
logger->append(u"adsb.txt");
|
||||
logger->append( LOG_ROOT_DIR "/ADSB.TXT" );
|
||||
// will log each frame in format:
|
||||
// 20171103100227 8DADBEEFDEADBEEFDEADBEEFDEADBEEF ICAO:nnnnnn callsign Alt:nnnnnn Latnnn.nn Lonnnn.nn
|
||||
logger->log_str(logentry);
|
||||
|
||||
@@ -60,9 +60,9 @@ AFSKRxView::AFSKRxView(NavigationView& nav) {
|
||||
&field_lna,
|
||||
&field_vga,
|
||||
&field_frequency,
|
||||
&check_log,
|
||||
&text_debug,
|
||||
&button_modem_setup,
|
||||
&record_view,
|
||||
&console
|
||||
});
|
||||
|
||||
@@ -74,13 +74,6 @@ AFSKRxView::AFSKRxView(NavigationView& nav) {
|
||||
field_rf_amp.set_value(app_settings.rx_amp);
|
||||
}
|
||||
|
||||
|
||||
// DEBUG
|
||||
record_view.on_error = [&nav](std::string message) {
|
||||
nav.display_modal("Error", message);
|
||||
};
|
||||
record_view.set_sampling_rate(24000);
|
||||
|
||||
// Auto-configure modem for LCR RX (will be removed later)
|
||||
update_freq(467225500); // 462713300
|
||||
auto def_bell202 = &modem_defs[0];
|
||||
@@ -105,13 +98,18 @@ AFSKRxView::AFSKRxView(NavigationView& nav) {
|
||||
};
|
||||
};
|
||||
|
||||
check_log.set_value(logging);
|
||||
check_log.on_select = [this](Checkbox&, bool v) {
|
||||
logging = v;
|
||||
};
|
||||
|
||||
button_modem_setup.on_select = [&nav](Button&) {
|
||||
nav.push<ModemSetupView>();
|
||||
};
|
||||
|
||||
logger = std::make_unique<AFSKLogger>();
|
||||
if (logger)
|
||||
logger->append("AFSK_LOG.TXT");
|
||||
logger->append(LOG_ROOT_DIR "/AFSK.TXT");
|
||||
|
||||
// Auto-configure modem for LCR RX (will be removed later)
|
||||
baseband::set_afsk(persistent_memory::modem_baudrate(), 8, 0, false);
|
||||
@@ -153,22 +151,23 @@ void AFSKRxView::on_data(uint32_t value, bool is_data) {
|
||||
|
||||
console.write(str_console);
|
||||
|
||||
if (logger) str_log += str_byte;
|
||||
if (logger && logging) str_log += str_byte;
|
||||
|
||||
if ((value != 0x7F) && (prev_value == 0x7F)) {
|
||||
// Message split
|
||||
console.writeln("");
|
||||
console_color++;
|
||||
|
||||
if (logger) {
|
||||
if (logger && logging) {
|
||||
logger->log_raw_data(str_log);
|
||||
str_log = "";
|
||||
}
|
||||
}
|
||||
prev_value = value;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
// Baudrate estimation
|
||||
text_debug.set("~" + to_string_dec_uint(value));
|
||||
text_debug.set("Baudrate estimation: ~" + to_string_dec_uint(value));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -64,6 +64,7 @@ private:
|
||||
uint8_t console_color { 0 };
|
||||
uint32_t prev_value { 0 };
|
||||
std::string str_log { "" };
|
||||
bool logging { false };
|
||||
|
||||
RFAmpField field_rf_amp {
|
||||
{ 13 * 8, 0 * 16 }
|
||||
@@ -85,23 +86,23 @@ private:
|
||||
{ 0 * 8, 0 * 16 },
|
||||
};
|
||||
|
||||
Text text_debug {
|
||||
{ 0 * 8, 1 * 16, 10 * 8, 16 },
|
||||
Checkbox check_log {
|
||||
{ 0 * 8, 1 * 16 },
|
||||
3,
|
||||
"LOG",
|
||||
false
|
||||
};
|
||||
|
||||
Text text_debug {
|
||||
{ 0 * 8, 12 + 2 * 16, 240, 16 },
|
||||
"DEBUG"
|
||||
};
|
||||
|
||||
|
||||
Button button_modem_setup {
|
||||
{ 12 * 8, 1 * 16, 96, 24 },
|
||||
{ 240 - 12 * 8, 1 * 16, 96, 24 },
|
||||
"Modem setup"
|
||||
};
|
||||
|
||||
// DEBUG
|
||||
RecordView record_view {
|
||||
{ 0 * 8, 3 * 16, 30 * 8, 1 * 16 },
|
||||
u"AFS_????", RecordView::FileType::WAV, 4096, 4
|
||||
};
|
||||
|
||||
Console console {
|
||||
{ 0, 4 * 16, 240, 240 }
|
||||
};
|
||||
|
||||
@@ -143,7 +143,7 @@ APRSRxView::APRSRxView(NavigationView& nav, Rect parent_rect) : View(parent_rect
|
||||
|
||||
logger = std::make_unique<APRSLogger>();
|
||||
if (logger)
|
||||
logger->append("APRS_RX_LOG.TXT");
|
||||
logger->append(LOG_ROOT_DIR "/APRS.TXT");
|
||||
|
||||
baseband::set_aprs(1200);
|
||||
|
||||
|
||||
@@ -235,7 +235,7 @@ private:
|
||||
// DEBUG
|
||||
RecordView record_view {
|
||||
{ 0 * 8, 1 * 16, 30 * 8, 1 * 16 },
|
||||
u"AFS_????", RecordView::FileType::WAV, 4096, 4
|
||||
u"AFS_????.WAV", u"APRS", RecordView::FileType::WAV, 4096, 4
|
||||
};
|
||||
|
||||
Console console {
|
||||
|
||||
@@ -54,9 +54,7 @@ BTLERxView::BTLERxView(NavigationView& nav) {
|
||||
&field_lna,
|
||||
&field_vga,
|
||||
&field_frequency,
|
||||
&text_debug,
|
||||
&button_modem_setup,
|
||||
&record_view,
|
||||
&console
|
||||
});
|
||||
|
||||
@@ -68,13 +66,6 @@ BTLERxView::BTLERxView(NavigationView& nav) {
|
||||
field_rf_amp.set_value(app_settings.rx_amp);
|
||||
}
|
||||
|
||||
|
||||
// DEBUG
|
||||
record_view.on_error = [&nav](std::string message) {
|
||||
nav.display_modal("Error", message);
|
||||
};
|
||||
record_view.set_sampling_rate(24000);
|
||||
|
||||
// Auto-configure modem for LCR RX (will be removed later)
|
||||
update_freq(2426000000);
|
||||
auto def_bell202 = &modem_defs[0];
|
||||
@@ -103,7 +94,6 @@ BTLERxView::BTLERxView(NavigationView& nav) {
|
||||
nav.push<ModemSetupView>();
|
||||
};
|
||||
|
||||
|
||||
// Auto-configure modem for LCR RX (will be removed later)
|
||||
baseband::set_btle(persistent_memory::modem_baudrate(), 8, 0, false);
|
||||
|
||||
|
||||
@@ -53,7 +53,6 @@ private:
|
||||
|
||||
uint8_t console_color { 0 };
|
||||
uint32_t prev_value { 0 };
|
||||
std::string str_log { "" };
|
||||
|
||||
RFAmpField field_rf_amp {
|
||||
{ 13 * 8, 0 * 16 }
|
||||
@@ -75,23 +74,11 @@ private:
|
||||
{ 0 * 8, 0 * 16 },
|
||||
};
|
||||
|
||||
Text text_debug {
|
||||
{ 0 * 8, 1 * 16, 10 * 8, 16 },
|
||||
"DEBUG"
|
||||
};
|
||||
|
||||
|
||||
Button button_modem_setup {
|
||||
{ 12 * 8, 1 * 16, 96, 24 },
|
||||
{ 240 - 12 * 8, 1 * 16, 96, 24 },
|
||||
"Modem setup"
|
||||
};
|
||||
|
||||
// DEBUG
|
||||
RecordView record_view {
|
||||
{ 0 * 8, 3 * 16, 30 * 8, 1 * 16 },
|
||||
u"AFS_????", RecordView::FileType::WAV, 4096, 4
|
||||
};
|
||||
|
||||
Console console {
|
||||
{ 0, 4 * 16, 240, 240 }
|
||||
};
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
|
||||
/* TODO:
|
||||
* - Paging menu items
|
||||
* - Copy/Move
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
@@ -43,8 +42,7 @@ bool is_hidden_file(const fs::path& path) {
|
||||
|
||||
// Gets a truncated name from a path for display.
|
||||
std::string truncate(const fs::path& path, size_t max_length) {
|
||||
auto name = path.string();
|
||||
return name.length() <= max_length ? name : name.substr(0, max_length);
|
||||
return ::truncate(path.string(), max_length);
|
||||
}
|
||||
|
||||
// Gets a human readable file size string.
|
||||
@@ -146,6 +144,22 @@ bool partner_file_prompt(
|
||||
return true;
|
||||
}
|
||||
|
||||
fs::path get_unique_filename(const fs::path& path, const fs::path& file) {
|
||||
auto stem = file.stem();
|
||||
auto ext = file.extension();
|
||||
auto serial = 1;
|
||||
fs::path new_path = file;
|
||||
|
||||
while (fs::file_exists(path / new_path)) {
|
||||
new_path = stem;
|
||||
new_path += fs::path{ u"_" };
|
||||
new_path += to_string_dec_int(serial++);
|
||||
new_path += ext;
|
||||
}
|
||||
|
||||
return new_path;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace ui {
|
||||
@@ -192,12 +206,13 @@ const fileman_entry& FileManBaseView::get_selected_entry() const {
|
||||
FileManBaseView::FileManBaseView(
|
||||
NavigationView& nav,
|
||||
std::string filter
|
||||
) : nav_ (nav),
|
||||
extension_filter { filter }
|
||||
) : nav_{ nav },
|
||||
extension_filter{ filter }
|
||||
{
|
||||
add_children({
|
||||
&labels,
|
||||
&text_current,
|
||||
&text_info,
|
||||
&button_exit
|
||||
});
|
||||
|
||||
@@ -206,7 +221,7 @@ FileManBaseView::FileManBaseView(
|
||||
};
|
||||
|
||||
if (!sdcIsCardInserted(&SDCD1)) {
|
||||
empty_root = true;
|
||||
empty_ = EmptyReason::NoSDC;
|
||||
text_current.set("NO SD CARD!");
|
||||
return;
|
||||
}
|
||||
@@ -214,7 +229,7 @@ FileManBaseView::FileManBaseView(
|
||||
load_directory_contents(current_path);
|
||||
|
||||
if (!entry_list.size()) {
|
||||
empty_root = true;
|
||||
empty_ = EmptyReason::NoFiles;
|
||||
text_current.set("EMPTY SD CARD!");
|
||||
} else {
|
||||
menu_view.on_left = [this]() {
|
||||
@@ -224,7 +239,7 @@ FileManBaseView::FileManBaseView(
|
||||
}
|
||||
|
||||
void FileManBaseView::focus() {
|
||||
if (empty_root) {
|
||||
if (empty_ != EmptyReason::NotEmpty) {
|
||||
button_exit.focus();
|
||||
} else {
|
||||
menu_view.focus();
|
||||
@@ -287,8 +302,13 @@ void FileManBaseView::refresh_list() {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// HACK: Should page menu items instead of limiting the number.
|
||||
if (menu_view.item_count() >= max_items_shown)
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
text_info.set(menu_view.item_count() >= max_items_shown ? "Too many files!" : "");
|
||||
menu_view.set_highlighted(prev_highlight);
|
||||
}
|
||||
|
||||
@@ -310,34 +330,8 @@ const FileManBaseView::file_assoc_t& FileManBaseView::get_assoc(
|
||||
return file_types[index];
|
||||
}
|
||||
|
||||
/*void FileSaveView::on_save_name() {
|
||||
text_prompt(nav_, &filename_buffer, 8, [this](std::string * buffer) {
|
||||
nav_.pop();
|
||||
});
|
||||
}
|
||||
FileSaveView::FileSaveView(
|
||||
NavigationView& nav
|
||||
) : FileManBaseView(nav)
|
||||
{
|
||||
name_buffer.clear();
|
||||
|
||||
add_children({
|
||||
&text_save,
|
||||
&button_save_name,
|
||||
&live_timestamp
|
||||
});
|
||||
|
||||
button_save_name.on_select = [this, &nav](Button&) {
|
||||
on_save_name();
|
||||
};
|
||||
}*/
|
||||
|
||||
/* FileLoadView **************************************************************/
|
||||
|
||||
void FileLoadView::refresh_widgets(const bool) {
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
FileLoadView::FileLoadView(
|
||||
NavigationView& nav,
|
||||
std::string filter
|
||||
@@ -367,6 +361,68 @@ FileLoadView::FileLoadView(
|
||||
};
|
||||
}
|
||||
|
||||
void FileLoadView::refresh_widgets(const bool) {
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
/* FileSaveView **************************************************************/
|
||||
/*
|
||||
FileSaveView::FileSaveView(
|
||||
NavigationView& nav,
|
||||
const fs::path& path,
|
||||
const fs::path& file
|
||||
) : nav_{ nav },
|
||||
path_{ path },
|
||||
file_{ file }
|
||||
{
|
||||
add_children({
|
||||
&labels,
|
||||
&text_path,
|
||||
&button_edit_path,
|
||||
&text_name,
|
||||
&button_edit_name,
|
||||
&button_save,
|
||||
&button_cancel,
|
||||
});
|
||||
|
||||
button_edit_path.on_select = [this](Button&) {
|
||||
buffer_ = path_.string();
|
||||
text_prompt(nav_, buffer_, max_filename_length,
|
||||
[this](std::string&) {
|
||||
path_ = buffer_;
|
||||
refresh_widgets();
|
||||
});
|
||||
};
|
||||
|
||||
button_edit_name.on_select = [this](Button&) {
|
||||
buffer_ = file_.string();
|
||||
text_prompt(nav_, buffer_, max_filename_length,
|
||||
[this](std::string&) {
|
||||
file_ = buffer_;
|
||||
refresh_widgets();
|
||||
});
|
||||
};
|
||||
|
||||
button_save.on_select = [this](Button&) {
|
||||
if (on_save)
|
||||
on_save(path_ / file_);
|
||||
else
|
||||
nav_.pop();
|
||||
};
|
||||
|
||||
button_cancel.on_select = [this](Button&) {
|
||||
nav_.pop();
|
||||
};
|
||||
|
||||
refresh_widgets();
|
||||
}
|
||||
|
||||
void FileSaveView::refresh_widgets() {
|
||||
text_path.set(truncate(path_, 30));
|
||||
text_name.set(truncate(file_, 30));
|
||||
set_dirty();
|
||||
}
|
||||
*/
|
||||
/* FileManagerView ***********************************************************/
|
||||
|
||||
void FileManagerView::on_rename() {
|
||||
@@ -387,7 +443,7 @@ void FileManagerView::on_rename() {
|
||||
[this, renamed_path](const fs::path& partner, bool should_rename) mutable {
|
||||
if (should_rename) {
|
||||
auto new_name = renamed_path.replace_extension(partner.extension());
|
||||
rename_file(current_path / partner, current_path / new_name);
|
||||
rename_file(partner, current_path / new_name);
|
||||
}
|
||||
reload_current();
|
||||
}
|
||||
@@ -409,7 +465,7 @@ void FileManagerView::on_delete() {
|
||||
nav_, get_selected_full_path(), "Delete",
|
||||
[this](const fs::path& partner, bool should_delete) {
|
||||
if (should_delete)
|
||||
delete_file(current_path / partner);
|
||||
delete_file(partner);
|
||||
reload_current();
|
||||
}
|
||||
);
|
||||
@@ -429,6 +485,34 @@ void FileManagerView::on_new_dir() {
|
||||
});
|
||||
}
|
||||
|
||||
void FileManagerView::on_new_file() {
|
||||
name_buffer = "";
|
||||
text_prompt(nav_, name_buffer, max_filename_length, [this](std::string& file_name) {
|
||||
make_new_file(current_path / file_name);
|
||||
reload_current();
|
||||
});
|
||||
}
|
||||
|
||||
void FileManagerView::on_paste() {
|
||||
// TODO: handle partner file. Need to fix nav stack first.
|
||||
auto new_name = get_unique_filename(current_path, clipboard_path.filename());
|
||||
fs::filesystem_error result;
|
||||
|
||||
if (clipboard_mode == ClipboardMode::Cut)
|
||||
result = rename_file(clipboard_path, current_path / new_name);
|
||||
|
||||
else if (clipboard_mode == ClipboardMode::Copy)
|
||||
result = copy_file(clipboard_path, current_path / new_name);
|
||||
|
||||
if (result.code() != FR_OK)
|
||||
nav_.display_modal("Paste Failed", result.what());
|
||||
|
||||
clipboard_path = fs::path{ };
|
||||
clipboard_mode = ClipboardMode::None;
|
||||
menu_view.focus();
|
||||
reload_current();
|
||||
}
|
||||
|
||||
bool FileManagerView::selected_is_valid() const {
|
||||
return !entry_list.empty() &&
|
||||
get_selected_entry().path != parent_dir_path;
|
||||
@@ -437,63 +521,97 @@ bool FileManagerView::selected_is_valid() const {
|
||||
void FileManagerView::refresh_widgets(const bool v) {
|
||||
button_rename.hidden(v);
|
||||
button_delete.hidden(v);
|
||||
button_cut.hidden(v);
|
||||
button_copy.hidden(v);
|
||||
button_paste.hidden(v);
|
||||
button_new_dir.hidden(v);
|
||||
set_dirty();
|
||||
}
|
||||
button_new_file.hidden(v);
|
||||
|
||||
FileManagerView::~FileManagerView() {
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
FileManagerView::FileManagerView(
|
||||
NavigationView& nav
|
||||
) : FileManBaseView(nav, "")
|
||||
{
|
||||
if (!empty_root) {
|
||||
on_refresh_widgets = [this](bool v) {
|
||||
refresh_widgets(v);
|
||||
};
|
||||
|
||||
add_children({
|
||||
&menu_view,
|
||||
&labels,
|
||||
&text_date,
|
||||
&button_rename,
|
||||
&button_delete,
|
||||
&button_new_dir,
|
||||
});
|
||||
|
||||
menu_view.on_highlight = [this]() {
|
||||
// TODO: enable/disable buttons.
|
||||
if (selected_is_valid())
|
||||
text_date.set(to_string_FAT_timestamp(file_created_date(get_selected_full_path())));
|
||||
else
|
||||
text_date.set("");
|
||||
};
|
||||
|
||||
refresh_list();
|
||||
// Don't bother with the UI in the case of no SDC.
|
||||
if (empty_ == EmptyReason::NoSDC)
|
||||
return;
|
||||
|
||||
on_refresh_widgets = [this](bool v) {
|
||||
refresh_widgets(v);
|
||||
};
|
||||
|
||||
on_select_entry = [this](KeyEvent key) {
|
||||
if (key == KeyEvent::Select && get_selected_entry().is_directory) {
|
||||
push_dir(get_selected_entry().path);
|
||||
} else {
|
||||
button_rename.focus();
|
||||
}
|
||||
};
|
||||
|
||||
button_rename.on_select = [this](Button&) {
|
||||
if (selected_is_valid())
|
||||
on_rename();
|
||||
};
|
||||
add_children({
|
||||
&menu_view,
|
||||
&labels,
|
||||
&text_date,
|
||||
&button_rename,
|
||||
&button_delete,
|
||||
&button_cut,
|
||||
&button_copy,
|
||||
&button_paste,
|
||||
&button_new_dir,
|
||||
&button_new_file
|
||||
});
|
||||
|
||||
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("");
|
||||
};
|
||||
|
||||
refresh_list();
|
||||
|
||||
button_delete.on_select = [this](Button&) {
|
||||
if (selected_is_valid())
|
||||
on_delete();
|
||||
};
|
||||
on_select_entry = [this](KeyEvent key) {
|
||||
if (key == KeyEvent::Select && get_selected_entry().is_directory) {
|
||||
push_dir(get_selected_entry().path);
|
||||
} else {
|
||||
button_rename.focus();
|
||||
}
|
||||
};
|
||||
|
||||
button_rename.on_select = [this]() {
|
||||
if (selected_is_valid())
|
||||
on_rename();
|
||||
};
|
||||
|
||||
button_new_dir.on_select = [this](Button&) {
|
||||
on_new_dir();
|
||||
};
|
||||
}
|
||||
button_delete.on_select = [this]() {
|
||||
if (selected_is_valid())
|
||||
on_delete();
|
||||
};
|
||||
|
||||
button_cut.on_select = [this]() {
|
||||
if (selected_is_valid() && !get_selected_entry().is_directory) {
|
||||
clipboard_path = get_selected_full_path();
|
||||
clipboard_mode = ClipboardMode::Cut;
|
||||
} else
|
||||
nav_.display_modal("Cut", "Can't cut that.");
|
||||
};
|
||||
|
||||
button_copy.on_select = [this]() {
|
||||
if (selected_is_valid() && !get_selected_entry().is_directory) {
|
||||
clipboard_path = get_selected_full_path();
|
||||
clipboard_mode = ClipboardMode::Copy;
|
||||
} else
|
||||
nav_.display_modal("Copy", "Can't copy that.");
|
||||
};
|
||||
|
||||
button_paste.on_select = [this]() {
|
||||
if (clipboard_mode != ClipboardMode::None)
|
||||
on_paste();
|
||||
else
|
||||
nav_.display_modal("Paste", "Cut or copy a file first.");
|
||||
};
|
||||
|
||||
button_new_dir.on_select = [this]() {
|
||||
on_new_dir();
|
||||
};
|
||||
|
||||
button_new_file.on_select = [this]() {
|
||||
on_new_file();
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -36,6 +36,18 @@ struct fileman_entry {
|
||||
bool is_directory { };
|
||||
};
|
||||
|
||||
enum class EmptyReason : uint8_t {
|
||||
NotEmpty,
|
||||
NoFiles,
|
||||
NoSDC
|
||||
};
|
||||
|
||||
enum class ClipboardMode : uint8_t {
|
||||
None,
|
||||
Cut,
|
||||
Copy
|
||||
};
|
||||
|
||||
class FileManBaseView : public View {
|
||||
public:
|
||||
FileManBaseView(
|
||||
@@ -43,11 +55,14 @@ public:
|
||||
std::string filter
|
||||
);
|
||||
|
||||
virtual ~FileManBaseView() { }
|
||||
|
||||
void focus() override;
|
||||
std::string title() const override { return "Fileman"; };
|
||||
|
||||
protected:
|
||||
static constexpr size_t max_filename_length = 50;
|
||||
static constexpr size_t max_filename_length = 64;
|
||||
static constexpr size_t max_items_shown = 100;
|
||||
|
||||
struct file_assoc_t {
|
||||
std::filesystem::path extension;
|
||||
@@ -65,7 +80,6 @@ protected:
|
||||
{ u"", &bitmap_icon_file, ui::Color::light_grey() } // NB: Must be last.
|
||||
};
|
||||
|
||||
|
||||
std::filesystem::path get_selected_full_path() const;
|
||||
const fileman_entry& get_selected_entry() const;
|
||||
|
||||
@@ -78,7 +92,7 @@ protected:
|
||||
|
||||
NavigationView& nav_;
|
||||
|
||||
bool empty_root { false };
|
||||
EmptyReason empty_ { EmptyReason::NotEmpty };
|
||||
std::function<void(KeyEvent)> on_select_entry { nullptr };
|
||||
std::function<void(bool)> on_refresh_widgets { nullptr };
|
||||
|
||||
@@ -102,59 +116,106 @@ protected:
|
||||
{ 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 {
|
||||
{ 16 * 8, 34 * 8, 14 * 8, 32 },
|
||||
{ 21 * 8, 34 * 8, 9 * 8, 32 },
|
||||
"Exit"
|
||||
};
|
||||
};
|
||||
|
||||
/*class FileSaveView : public FileManBaseView {
|
||||
public:
|
||||
FileSaveView(NavigationView& nav);
|
||||
~FileSaveView();
|
||||
|
||||
private:
|
||||
std::string name_buffer { };
|
||||
|
||||
void on_save_name();
|
||||
|
||||
Text text_save {
|
||||
{ 4 * 8, 15 * 8, 8 * 8, 16 },
|
||||
"Save as:",
|
||||
};
|
||||
Button button_save_name {
|
||||
{ 4 * 8, 18 * 8, 12 * 8, 32 },
|
||||
"Name (set)"
|
||||
};
|
||||
LiveDateTime live_timestamp {
|
||||
{ 17 * 8, 24 * 8, 11 * 8, 16 }
|
||||
};
|
||||
};*/
|
||||
|
||||
class FileLoadView : public FileManBaseView {
|
||||
public:
|
||||
std::function<void(std::filesystem::path)> on_changed { };
|
||||
|
||||
FileLoadView(NavigationView& nav, std::string filter);
|
||||
virtual ~FileLoadView() { }
|
||||
|
||||
private:
|
||||
void refresh_widgets(const bool v);
|
||||
};
|
||||
|
||||
/*
|
||||
// It would be nice to be able to launch FileLoadView
|
||||
// but it will OOM if launched from within FileManager.
|
||||
class FileSaveView : public View {
|
||||
public:
|
||||
FileSaveView(
|
||||
NavigationView& nav,
|
||||
const std::filesystem::path& path,
|
||||
const std::filesystem::path& file);
|
||||
|
||||
std::function<void(std::filesystem::path)> on_save { };
|
||||
|
||||
private:
|
||||
static constexpr size_t max_filename_length = 64;
|
||||
|
||||
void refresh_widgets();
|
||||
|
||||
NavigationView& nav_;
|
||||
std::filesystem::path path_;
|
||||
std::filesystem::path file_;
|
||||
std::string buffer_ { };
|
||||
|
||||
Labels labels {
|
||||
{ { 0 * 8, 1 * 16 }, "Path:", Color::light_grey() },
|
||||
{ { 0 * 8, 6 * 16 }, "Filename:", Color::light_grey() },
|
||||
};
|
||||
|
||||
Text text_path {
|
||||
{ 0 * 8, 2 * 16, 30 * 8, 16 },
|
||||
"",
|
||||
};
|
||||
|
||||
Button button_edit_path {
|
||||
{ 18 * 8, 3 * 16, 11 * 8, 32 },
|
||||
"Edit Path"
|
||||
};
|
||||
|
||||
Text text_name {
|
||||
{ 0 * 8, 7 * 16, 30 * 8, 16 },
|
||||
"",
|
||||
};
|
||||
|
||||
Button button_edit_name {
|
||||
{ 18 * 8, 8 * 16, 11 * 8, 32 },
|
||||
"Edit Name"
|
||||
};
|
||||
|
||||
Button button_save {
|
||||
{ 10 * 8, 16 * 16, 9 * 8, 32 },
|
||||
"Save"
|
||||
};
|
||||
|
||||
Button button_cancel {
|
||||
{ 20 * 8, 16 * 16, 9 * 8, 32 },
|
||||
"Cancel"
|
||||
};
|
||||
};
|
||||
*/
|
||||
|
||||
class FileManagerView : public FileManBaseView {
|
||||
public:
|
||||
FileManagerView(NavigationView& nav);
|
||||
~FileManagerView();
|
||||
virtual ~FileManagerView() { }
|
||||
|
||||
private:
|
||||
// Passed by ref to other views needing lifetime extension.
|
||||
std::string name_buffer { };
|
||||
std::filesystem::path clipboard_path { };
|
||||
ClipboardMode clipboard_mode { ClipboardMode::None };
|
||||
|
||||
void refresh_widgets(const bool v);
|
||||
void on_rename();
|
||||
void on_delete();
|
||||
void on_paste();
|
||||
void on_new_dir();
|
||||
void on_new_file();
|
||||
|
||||
// True if the selected entry is a real file item.
|
||||
bool selected_is_valid() const;
|
||||
@@ -168,19 +229,53 @@ private:
|
||||
""
|
||||
};
|
||||
|
||||
Button button_rename {
|
||||
{ 0 * 8, 29 * 8, 14 * 8, 32 },
|
||||
"Rename"
|
||||
NewButton button_rename {
|
||||
{ 0 * 8, 29 * 8, 4 * 8, 32 },
|
||||
{ },
|
||||
&bitmap_icon_rename,
|
||||
Color::dark_blue()
|
||||
};
|
||||
|
||||
Button button_delete {
|
||||
{ 16 * 8, 29 * 8, 14 * 8, 32 },
|
||||
"Delete"
|
||||
NewButton button_delete {
|
||||
{ 4 * 8, 29 * 8, 4 * 8, 32 },
|
||||
{ },
|
||||
&bitmap_icon_trash,
|
||||
Color::red()
|
||||
};
|
||||
|
||||
Button button_new_dir {
|
||||
{ 0 * 8, 34 * 8, 14 * 8, 32 },
|
||||
"New Dir"
|
||||
NewButton button_cut {
|
||||
{ 9 * 8, 29 * 8, 4 * 8, 32 },
|
||||
{ },
|
||||
&bitmap_icon_cut,
|
||||
Color::dark_grey()
|
||||
};
|
||||
|
||||
NewButton button_copy {
|
||||
{ 13 * 8, 29 * 8, 4 * 8, 32 },
|
||||
{ },
|
||||
&bitmap_icon_copy,
|
||||
Color::dark_grey()
|
||||
};
|
||||
|
||||
NewButton button_paste {
|
||||
{ 17 * 8, 29 * 8, 4 * 8, 32 },
|
||||
{ },
|
||||
&bitmap_icon_paste,
|
||||
Color::dark_grey()
|
||||
};
|
||||
|
||||
NewButton button_new_dir {
|
||||
{ 22 * 8, 29 * 8, 4 * 8, 32 },
|
||||
{ },
|
||||
&bitmap_icon_new_dir,
|
||||
Color::green()
|
||||
};
|
||||
|
||||
NewButton button_new_file {
|
||||
{ 26 * 8, 29 * 8, 4 * 8, 32 },
|
||||
{ },
|
||||
&bitmap_icon_new_file,
|
||||
Color::green()
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -167,14 +167,14 @@ void MicTXView::rxaudio(bool is_on) {
|
||||
}
|
||||
else { // We are in NFM/FM or WFM (NFM BW:8k5 or 11k / FM BW 16k / WFM BW:200k)
|
||||
|
||||
if (enable_wfm) { // WFM , BW 200Khz aprox ,
|
||||
if (enable_wfm) { // WFM , BW 200Khz aprox , or the two new addional BW filters (180k, 40k)
|
||||
baseband::run_image(portapack::spi_flash::image_tag_wfm_audio);
|
||||
receiver_model.set_modulation(ReceiverModel::Mode::WidebandFMAudio);
|
||||
receiver_model.set_wfm_configuration(0); // there are only 1 x config filters 200k WFM . (not like 8k5/11k/16k)
|
||||
// receiver_model.set_wfm_configuration(n); // it is called above , depending user's selection (200k, 180k,40k).
|
||||
} else { // NFM BW:8k5 or 11k / FM BW 16k
|
||||
baseband::run_image(portapack::spi_flash::image_tag_nfm_audio);
|
||||
receiver_model.set_modulation(ReceiverModel::Mode::NarrowbandFMAudio); //
|
||||
// receiver_model.set_nbfm_configuration(n); is called above , depending 8k5, 11k, 16k
|
||||
// receiver_model.set_nbfm_configuration(n); is called above , depending user's selection (8k5, 11k, 16k).
|
||||
}
|
||||
}
|
||||
receiver_model.set_sampling_rate(3072000);
|
||||
@@ -423,10 +423,12 @@ MicTXView::MicTXView(
|
||||
rxaudio(rx_enabled); //Update now if we have RX audio on
|
||||
options_tone_key.hidden(0); // we are in WFM mode , we should have active the Key-tones & CTCSS option.
|
||||
|
||||
rxbw.emplace_back(" 200k-WFM ", 0); // locked a fixed option , to display it .
|
||||
rxbw.emplace_back(" 200k-WFM ", 0); // We allow the user selection of the 3 x WFM BW filters, (0) WFM-200K, (1) WFM-180K , (2) WFM-40K .
|
||||
rxbw.emplace_back(" 180k-WFM ", 1);
|
||||
rxbw.emplace_back(" 40k-WFM ", 2);
|
||||
field_rxbw.set_options(rxbw); // store that aux GUI option to the field_rxbw.
|
||||
|
||||
field_rxbw.hidden(0); // we are in WFM mode, we need to show to the user the selected BW WFM filer .
|
||||
field_rxbw.hidden(0); // we are in WFM mode, we need to show to the user the selected BW WFM filter .
|
||||
field_bw.hidden(0); // we are in WFM mode, we need to allow WFM deviation parameter , in non FM mode.
|
||||
break;
|
||||
case 2: //{ "AM", 2 }
|
||||
@@ -436,7 +438,7 @@ MicTXView::MicTXView(
|
||||
set_dirty(); // Refresh display
|
||||
options_tone_key.hidden(1); // we hide that Key-tones & CTCSS input selecction, (no meaning in AM/DSB/SSB).
|
||||
|
||||
rxbw.emplace_back(" DSB1-9k ", 0); // we offer in AM DSB two audio BW 9k / 6k .
|
||||
rxbw.emplace_back(" DSB1-9k ", 0); // we offer in AM DSB two audio BW 9k / 6k .
|
||||
rxbw.emplace_back(" DSB2-6k ", 1);
|
||||
field_rxbw.set_options(rxbw); // store that aux GUI option to the field_rxbw.
|
||||
|
||||
@@ -450,7 +452,7 @@ MicTXView::MicTXView(
|
||||
check_rogerbeep.set_value(false); // reset the possible activation of roger beep, because it is not compatible with SSB , by now.
|
||||
check_rogerbeep.hidden(1); // hide that roger beep selection.
|
||||
|
||||
rxbw.emplace_back(" USB+3k ", 0); // locked a fixed option , to display it .
|
||||
rxbw.emplace_back(" USB+3k ", 0); // locked a fixed option , to display it .
|
||||
field_rxbw.set_options(rxbw); // store that aux GUI option to the field_rxbw.
|
||||
|
||||
set_dirty(); // Refresh display
|
||||
@@ -461,7 +463,7 @@ MicTXView::MicTXView(
|
||||
check_rogerbeep.set_value(false); // reset the possible activation of roger beep, because it is not compatible with SSB , by now.
|
||||
check_rogerbeep.hidden(1); // hide that roger beep selection.
|
||||
|
||||
rxbw.emplace_back(" LSB-3k ", 0); // locked a fixed option , to display it .
|
||||
rxbw.emplace_back(" LSB-3k ", 0); // locked a fixed option , to display it .
|
||||
field_rxbw.set_options(rxbw); // store that aux GUI option to the field_rxbw.
|
||||
|
||||
set_dirty(); // Refresh display
|
||||
@@ -565,12 +567,14 @@ MicTXView::MicTXView(
|
||||
}
|
||||
else { // we are not in NFM/FM mode .(we could be in any of the rest : AM /USB/LSB/DSB-SC)
|
||||
if (enable_am) { // we are in AM TX mode , we will allow both independent RX audio BW : AM 9K (9K00AE3 / AM 6K (6K00AE3). (In AM option v can be 0 (9k) , 1 (6k)
|
||||
receiver_model.set_am_configuration(v ); // we are in AM TX mode , we need to select proper AM full path config AM-9K filter. 0+0 =>AM-9K(0), 0+1=1 =>AM-6K(1),
|
||||
receiver_model.set_am_configuration(v); // we are in AM TX mode , we need to select proper AM full path config AM-9K filter. 0+0 =>AM-9K(0), 0+1=1 =>AM-6K(1),
|
||||
}
|
||||
|
||||
if (enable_dsb) { // we are in DSB-SC in TX mode , we will allow both independent RX SSB demodulation (USB / LSB side band). in that submenu, v is 0 (SSB1 USB) or 1 (SSB2 LSB)
|
||||
receiver_model.set_am_configuration(v +2 ); // we are in DSB-SC TX mode , we need to select proper SSB filter. 0+2 =>usb(2), 1+2=3 =>lsb(3),
|
||||
}
|
||||
if (enable_wfm) {
|
||||
receiver_model.set_wfm_configuration(v); // we are in WFM case, we need to select proper WFB RX BW filter , WFM BW 200K(0), WFM BW 180K(1) , WFM BW 40K(2)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -54,9 +54,7 @@ NRFRxView::NRFRxView(NavigationView& nav) {
|
||||
&field_lna,
|
||||
&field_vga,
|
||||
&field_frequency,
|
||||
&text_debug,
|
||||
&button_modem_setup,
|
||||
&record_view,
|
||||
&console
|
||||
});
|
||||
|
||||
@@ -68,12 +66,6 @@ NRFRxView::NRFRxView(NavigationView& nav) {
|
||||
field_rf_amp.set_value(app_settings.rx_amp);
|
||||
}
|
||||
|
||||
// DEBUG
|
||||
record_view.on_error = [&nav](std::string message) {
|
||||
nav.display_modal("Error", message);
|
||||
};
|
||||
record_view.set_sampling_rate(24000);
|
||||
|
||||
// Auto-configure modem for LCR RX (will be removed later)
|
||||
update_freq(2480000000);
|
||||
auto def_bell202 = &modem_defs[0];
|
||||
|
||||
@@ -52,7 +52,6 @@ private:
|
||||
|
||||
uint8_t console_color { 0 };
|
||||
uint32_t prev_value { 0 };
|
||||
std::string str_log { "" };
|
||||
|
||||
RFAmpField field_rf_amp {
|
||||
{ 13 * 8, 0 * 16 }
|
||||
@@ -74,23 +73,11 @@ private:
|
||||
{ 0 * 8, 0 * 16 },
|
||||
};
|
||||
|
||||
Text text_debug {
|
||||
{ 0 * 8, 1 * 16, 10 * 8, 16 },
|
||||
"DEBUG"
|
||||
};
|
||||
|
||||
|
||||
Button button_modem_setup {
|
||||
{ 12 * 8, 1 * 16, 96, 24 },
|
||||
{ 240 - 12 * 8, 1 * 16, 96, 24 },
|
||||
"Modem setup"
|
||||
};
|
||||
|
||||
// DEBUG
|
||||
RecordView record_view {
|
||||
{ 0 * 8, 3 * 16, 30 * 8, 1 * 16 },
|
||||
u"AFS_????", RecordView::FileType::WAV, 4096, 4
|
||||
};
|
||||
|
||||
Console console {
|
||||
{ 0, 4 * 16, 240, 240 }
|
||||
};
|
||||
|
||||
@@ -91,10 +91,10 @@ namespace ui {
|
||||
return _recon;
|
||||
}
|
||||
|
||||
void ReconThread::set_freq_lock(const uint32_t v) {
|
||||
void ReconThread::set_freq_lock(const int32_t v) {
|
||||
_freq_lock = v;
|
||||
}
|
||||
uint32_t ReconThread::is_freq_lock() {
|
||||
int32_t ReconThread::is_freq_lock() {
|
||||
return _freq_lock;
|
||||
}
|
||||
int64_t ReconThread::get_current_freq() {
|
||||
@@ -103,7 +103,6 @@ namespace ui {
|
||||
|
||||
void ReconThread::change_recon_direction() {
|
||||
_fwd = !_fwd;
|
||||
//chThdSleepMilliseconds(300); //Give some pause after reversing recon direction
|
||||
}
|
||||
|
||||
bool ReconThread::get_recon_direction() {
|
||||
@@ -204,6 +203,7 @@ namespace ui {
|
||||
|
||||
while( !chThdShouldTerminate() && frequency_list_.size() > 0 )
|
||||
{
|
||||
uint32_t remaining_sleep = _lock_duration ;
|
||||
if( !_freq_delete )
|
||||
{
|
||||
has_looped = false ;
|
||||
@@ -408,7 +408,6 @@ namespace ui {
|
||||
default:
|
||||
break;
|
||||
}
|
||||
restart_recon = true ;
|
||||
}
|
||||
// send a pause message with the right freq
|
||||
if( has_looped && !_continuous )
|
||||
@@ -429,9 +428,24 @@ namespace ui {
|
||||
restart_recon = false ;
|
||||
}
|
||||
} // if( _freq_lock == 0 || _stepper != 0 || _index_stepper != 0 )
|
||||
|
||||
// this while loop is needed to stabilize consecutive match without skipping
|
||||
// the little the rest, the more reactive and more CPU hog
|
||||
// the bigger the rest, less CPU and more delay when switching. I choose 5 msecs.
|
||||
while( _freq_lock != -1 && _freq_lock < (int32_t)_lock_nb_match && !chThdShouldTerminate() )
|
||||
{
|
||||
chThdSleepMilliseconds( 5 );
|
||||
if( remaining_sleep >= 5 )
|
||||
remaining_sleep -= 5 ;
|
||||
else
|
||||
remaining_sleep = 0 ; // leave some process time
|
||||
}
|
||||
if( _freq_lock == -1 )
|
||||
_freq_lock = 0 ;
|
||||
} // if( _recon || _stepper != 0 || _index_stepper != 0 )
|
||||
} // if( !freq_delete )
|
||||
chThdSleepMilliseconds( _lock_duration ); //Needed to (eventually) stabilize the receiver into new freq
|
||||
if( remaining_sleep )
|
||||
chThdSleepMilliseconds( remaining_sleep ); //Needed to (eventually) stabilize the receiver into new freq
|
||||
} //while( !chThdShouldTerminate() && frequency_list_.size() > 0 )
|
||||
}//if (frequency_list_.size() > 0 )
|
||||
} //ReconThread::run
|
||||
@@ -631,7 +645,7 @@ namespace ui {
|
||||
|
||||
ReconView::~ReconView() {
|
||||
|
||||
ReconSetupSaveStrings( "RECON/RECON.CFG" , input_file , output_file , recon_lock_duration , recon_lock_nb_match , squelch , recon_match_mode , wait , recon_lock_duration , field_volume.value() );
|
||||
ReconSetupSaveStrings( "RECON/RECON.CFG" , input_file , output_file , recon_lock_duration , recon_lock_nb_match , squelch , recon_match_mode , wait , lock_wait , field_volume.value() );
|
||||
|
||||
// save app settings
|
||||
settings.save("recon", &app_settings);
|
||||
@@ -648,6 +662,8 @@ namespace ui {
|
||||
if( recon_thread )
|
||||
{
|
||||
int32_t nb_match = recon_thread->is_freq_lock();
|
||||
if( nb_match == -1 )
|
||||
nb_match = 0 ;
|
||||
if( last_db != db )
|
||||
{
|
||||
last_db = db ;
|
||||
@@ -719,9 +735,15 @@ namespace ui {
|
||||
//HELPER: Pre-setting a manual range, based on stored frequency
|
||||
rf::Frequency stored_freq = persistent_memory::tuned_frequency();
|
||||
receiver_model.set_tuning_frequency( stored_freq );
|
||||
frequency_range.min = stored_freq - 1000000;
|
||||
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));
|
||||
frequency_range.max = stored_freq + 1000000;
|
||||
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));
|
||||
// Loading settings
|
||||
autostart = persistent_memory::recon_autostart_recon();
|
||||
@@ -736,8 +758,6 @@ namespace ui {
|
||||
field_volume.set_value( volume );
|
||||
if( sd_card_mounted )
|
||||
{
|
||||
//Loading input and output file from settings
|
||||
ReconSetupLoadStrings( "RECON/RECON.CFG" , input_file , output_file , recon_lock_duration , recon_lock_nb_match , squelch , recon_match_mode , wait , recon_lock_duration , volume );
|
||||
// load auto common app settings
|
||||
auto rc = settings.load("recon", &app_settings);
|
||||
if(rc == SETTINGS_OK) {
|
||||
@@ -769,11 +789,11 @@ namespace ui {
|
||||
{
|
||||
auto new_view = nav_.push<FrequencyKeypadView>(current_index);
|
||||
new_view->on_changed = [this, &button](rf::Frequency f) {
|
||||
f = f / 1000000 ;
|
||||
f = f / OneMHz ;
|
||||
if( f >= 1 && f <= frequency_list.size() )
|
||||
{
|
||||
recon_thread-> set_index_stepper( f - 1 - current_index );
|
||||
recon_thread->set_freq_lock( 0 );
|
||||
recon_thread->set_freq_lock( -1 );
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -781,9 +801,9 @@ namespace ui {
|
||||
|
||||
button_manual_start.on_change = [this]() {
|
||||
frequency_range.min = frequency_range.min + button_manual_start.get_encoder_delta() * freqman_entry_get_step_value( def_step );
|
||||
if( frequency_range.min < 1 )
|
||||
if( frequency_range.min < 0 )
|
||||
{
|
||||
frequency_range.min = 1 ;
|
||||
frequency_range.min = 0 ;
|
||||
}
|
||||
if( frequency_range.min > ( MAX_UFREQ - freqman_entry_get_step_value( def_step ) ) )
|
||||
{
|
||||
@@ -836,7 +856,7 @@ namespace ui {
|
||||
recon_thread -> set_recon_direction( fwd );
|
||||
button_dir.set_text( "FW>" );
|
||||
recon_thread-> set_index_stepper( 1 );
|
||||
recon_thread->set_freq_lock( 0 );
|
||||
recon_thread->set_freq_lock( -1 );
|
||||
}
|
||||
else if( text_cycle.get_encoder_delta() < 0 )
|
||||
{
|
||||
@@ -844,7 +864,7 @@ namespace ui {
|
||||
recon_thread -> set_recon_direction( fwd );
|
||||
button_dir.set_text( "<RW" );
|
||||
recon_thread-> set_index_stepper( -1 );
|
||||
recon_thread->set_freq_lock( 0 );
|
||||
recon_thread->set_freq_lock( -1 );
|
||||
}
|
||||
}
|
||||
text_cycle.set_encoder_delta( 0 );
|
||||
@@ -907,7 +927,7 @@ namespace ui {
|
||||
recon_thread -> set_recon_direction( fwd );
|
||||
button_dir.set_text( "FW>" );
|
||||
recon_thread-> set_stepper( 1 );
|
||||
recon_thread->set_freq_lock( 0 );
|
||||
recon_thread->set_freq_lock( -1 );
|
||||
}
|
||||
else if( button_pause.get_encoder_delta() < 0 )
|
||||
{
|
||||
@@ -915,7 +935,7 @@ namespace ui {
|
||||
recon_thread -> set_recon_direction( fwd );
|
||||
button_dir.set_text( "<RW" );
|
||||
recon_thread-> set_stepper( -1 );
|
||||
recon_thread->set_freq_lock( 0 );
|
||||
recon_thread->set_freq_lock( -1 );
|
||||
}
|
||||
}
|
||||
button_pause.set_encoder_delta( 0 );
|
||||
@@ -1117,7 +1137,7 @@ namespace ui {
|
||||
recon_thread -> set_recon_direction( fwd );
|
||||
button_dir.set_text( "FW>" );
|
||||
recon_thread-> set_stepper( 1 );
|
||||
recon_thread->set_freq_lock( 0 );
|
||||
recon_thread->set_freq_lock( -1 );
|
||||
}
|
||||
else if( button_remove.get_encoder_delta() < 0 )
|
||||
{
|
||||
@@ -1125,7 +1145,7 @@ namespace ui {
|
||||
recon_thread -> set_recon_direction( fwd );
|
||||
button_dir.set_text( "<RW" );
|
||||
recon_thread-> set_stepper( -1 );
|
||||
recon_thread->set_freq_lock( 0 );
|
||||
recon_thread->set_freq_lock( -1 );
|
||||
}
|
||||
}
|
||||
button_remove.set_encoder_delta( 0 );
|
||||
@@ -1317,7 +1337,7 @@ namespace ui {
|
||||
recon_thread -> set_recon_direction( fwd );
|
||||
button_dir.set_text( "FW>" );
|
||||
recon_thread-> set_stepper( 1 );
|
||||
recon_thread->set_freq_lock( 0 );
|
||||
recon_thread->set_freq_lock( -1 );
|
||||
}
|
||||
else if( button_add.get_encoder_delta() < 0 )
|
||||
{
|
||||
@@ -1325,7 +1345,7 @@ namespace ui {
|
||||
recon_thread -> set_recon_direction( fwd );
|
||||
button_dir.set_text( "<RW" );
|
||||
recon_thread-> set_stepper( -1 );
|
||||
recon_thread->set_freq_lock( 0 );
|
||||
recon_thread->set_freq_lock( -1 );
|
||||
}
|
||||
}
|
||||
button_add.set_encoder_delta( 0 );
|
||||
@@ -1366,10 +1386,9 @@ namespace ui {
|
||||
|
||||
button_recon_setup.on_select = [this,&nav](Button&) {
|
||||
|
||||
ReconSetupSaveStrings( "RECON/RECON.CFG" , input_file , output_file , recon_lock_duration , recon_lock_nb_match , squelch , recon_match_mode , wait , recon_lock_duration , field_volume.value() );
|
||||
|
||||
if( frequency_list.size() != 0 )
|
||||
user_pause();
|
||||
audio::output::stop();
|
||||
recon_thread->stop(); //STOP SCANNER THREAD
|
||||
frequency_list.clear();
|
||||
|
||||
auto open_view = nav.push<ReconSetupView>(input_file,output_file,recon_lock_duration,recon_lock_nb_match,recon_match_mode);
|
||||
open_view -> on_changed = [this](std::vector<std::string> result) {
|
||||
@@ -1379,7 +1398,7 @@ namespace ui {
|
||||
recon_lock_nb_match = strtol( result[3].c_str() , nullptr , 10 );
|
||||
recon_match_mode = strtol( result[4].c_str() , nullptr , 10 );
|
||||
|
||||
ReconSetupSaveStrings( "RECON/RECON.CFG" , input_file , output_file , recon_lock_duration , recon_lock_nb_match , squelch , recon_match_mode , wait , recon_lock_duration , field_volume.value() );
|
||||
ReconSetupSaveStrings( "RECON/RECON.CFG" , input_file , output_file , recon_lock_duration , recon_lock_nb_match , squelch , recon_match_mode , wait , lock_wait , field_volume.value() );
|
||||
|
||||
autosave = persistent_memory::recon_autosave_freqs();
|
||||
autostart = persistent_memory::recon_autostart_recon();
|
||||
@@ -1390,61 +1409,56 @@ namespace ui {
|
||||
load_hamradios = persistent_memory::recon_load_hamradios();
|
||||
|
||||
update_ranges = persistent_memory::recon_update_ranges_when_recon();
|
||||
|
||||
frequency_file_load( true );
|
||||
frequency_file_load( false );
|
||||
if( recon_thread )
|
||||
{
|
||||
recon_thread->set_lock_duration( recon_lock_duration );
|
||||
recon_thread->set_lock_nb_match( recon_lock_nb_match );
|
||||
recon_thread->set_continuous( continuous );
|
||||
recon_thread->set_recon_direction( fwd );
|
||||
if( autostart )
|
||||
{
|
||||
user_resume();
|
||||
}
|
||||
else
|
||||
{
|
||||
user_pause();
|
||||
}
|
||||
if( update_ranges && frequency_list.size() != 0 )
|
||||
{
|
||||
current_index = 0 ;
|
||||
button_manual_start.set_text( to_string_short_freq( frequency_list[ current_index ] . frequency_a ) );
|
||||
frequency_range.min = frequency_list[ current_index ] . frequency_a ;
|
||||
if( frequency_list[ current_index ] . frequency_b != 0 )
|
||||
{
|
||||
button_manual_end.set_text( to_string_short_freq( frequency_list[ current_index ] . frequency_b ) );
|
||||
frequency_range.max = frequency_list[ current_index ] . frequency_b ;
|
||||
}
|
||||
else
|
||||
{
|
||||
button_manual_end.set_text( to_string_short_freq( frequency_list[ current_index ] . frequency_a ) );
|
||||
frequency_range.max = frequency_list[ current_index ] . frequency_a ;
|
||||
}
|
||||
}
|
||||
}
|
||||
if( autostart )
|
||||
field_lock_wait.set_value( lock_wait );
|
||||
show_max();
|
||||
if( userpause != true )
|
||||
{
|
||||
timer = 0 ;
|
||||
user_resume();
|
||||
}
|
||||
else
|
||||
{
|
||||
user_pause();
|
||||
if( recon_thread && frequency_list.size() != 0 )
|
||||
{
|
||||
RetuneMessage message { };
|
||||
message.freq = recon_thread->get_current_freq();
|
||||
message.range = current_index ;
|
||||
EventDispatcher::send_message(message);
|
||||
}
|
||||
}
|
||||
|
||||
if( update_ranges )
|
||||
{
|
||||
button_manual_start.set_text( to_string_short_freq( frequency_list[ current_index ] . frequency_a ) );
|
||||
frequency_range.min = frequency_list[ current_index ] . frequency_a ;
|
||||
if( frequency_list[ current_index ] . frequency_b != 0 )
|
||||
{
|
||||
button_manual_end.set_text( to_string_short_freq( frequency_list[ current_index ] . frequency_b ) );
|
||||
frequency_range.max = frequency_list[ current_index ] . frequency_b ;
|
||||
}
|
||||
else
|
||||
{
|
||||
button_manual_end.set_text( to_string_short_freq( frequency_list[ current_index ] . frequency_a ) );
|
||||
frequency_range.max = frequency_list[ current_index ] . frequency_a ;
|
||||
}
|
||||
}
|
||||
|
||||
lock_wait = ( 4 * ( recon_lock_duration * recon_lock_nb_match ) ) / 100 ;
|
||||
lock_wait = lock_wait * 100 ; // poor man's rounding
|
||||
if( lock_wait < 400 )
|
||||
lock_wait = 400 ;
|
||||
field_lock_wait.set_value( lock_wait );
|
||||
show_max();
|
||||
return ;
|
||||
};
|
||||
|
||||
if( userpause != true )
|
||||
{
|
||||
timer = 0 ;
|
||||
user_resume();
|
||||
}
|
||||
else
|
||||
{
|
||||
RetuneMessage message { };
|
||||
message.freq = recon_thread->get_current_freq();
|
||||
message.range = current_index ;
|
||||
EventDispatcher::send_message(message);
|
||||
}
|
||||
};
|
||||
|
||||
field_wait.on_change = [this](int32_t v)
|
||||
@@ -1507,13 +1521,12 @@ namespace ui {
|
||||
button_scanner_mode.set_style( &style_blue );
|
||||
button_scanner_mode.set_text( "RECON" );
|
||||
file_name.set( "=>" );
|
||||
field_squelch.set_value( squelch );
|
||||
|
||||
//Loading input and output file from settings
|
||||
ReconSetupLoadStrings( "RECON/RECON.CFG" , input_file , output_file , recon_lock_duration , recon_lock_nb_match , squelch , recon_match_mode , wait , lock_wait , volume );
|
||||
|
||||
field_squelch.set_value( squelch );
|
||||
field_wait.set_value(wait);
|
||||
lock_wait = ( 4 * ( recon_lock_duration * recon_lock_nb_match ) );
|
||||
lock_wait = lock_wait / 100 ; lock_wait = lock_wait * 100 ; // poor man's rounding
|
||||
if( lock_wait < 400 )
|
||||
lock_wait = 400 ;
|
||||
field_lock_wait.set_value(lock_wait);
|
||||
field_volume.set_value((receiver_model.headphone_volume() - audio::headphone::volume_range().max).decibel() + 99);
|
||||
|
||||
@@ -1668,7 +1681,7 @@ namespace ui {
|
||||
status = 0 ;
|
||||
update_stats = true ;
|
||||
continuous_lock = false ;
|
||||
recon_thread->set_freq_lock( 0 ); //in lock period, still analyzing the signal
|
||||
recon_thread->set_freq_lock( -1 ); //in lock period, still analyzing the signal
|
||||
recon_resume(); // RESUME!
|
||||
big_display.set_style(&style_white);
|
||||
}
|
||||
@@ -1703,7 +1716,7 @@ namespace ui {
|
||||
}
|
||||
}
|
||||
}
|
||||
else // freq_lock < max_lock , LOCKING
|
||||
else if( freq_lock >= 0 ) // freq_lock < max_lock , LOCKING
|
||||
{
|
||||
if( actual_db > squelch ) //MATCHING LEVEL
|
||||
{
|
||||
@@ -1734,6 +1747,7 @@ namespace ui {
|
||||
{
|
||||
timer = 0 ;
|
||||
update_stats = true ;
|
||||
recon_thread->set_freq_lock( -1 );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1795,17 +1809,17 @@ namespace ui {
|
||||
}
|
||||
|
||||
void ReconView::user_pause() {
|
||||
timer = 0 ; // Will trigger a recon_resume() on_statistics_update, also advancing to next freq.
|
||||
//button_pause.set_text("<RESUME>"); //PAUSED, show resume
|
||||
timer = 0 ; // Will trigger a recon_resume() on_statistics_update, also advancing to next freq.
|
||||
button_pause.set_text("<RESUME>"); //PAUSED, show resume
|
||||
userpause=true;
|
||||
continuous_lock=false;
|
||||
recon_pause();
|
||||
}
|
||||
|
||||
void ReconView::user_resume() {
|
||||
timer = 0 ; // Will trigger a recon_resume() on_statistics_update, also advancing to next freq.
|
||||
//button_pause.set_text("<PAUSE>"); //Show button for pause
|
||||
userpause=false; // Resume recon
|
||||
timer = 0 ; // Will trigger a recon_resume() on_statistics_update, also advancing to next freq.
|
||||
button_pause.set_text("<PAUSE>"); //Show button for pause
|
||||
userpause=false; // Resume recon
|
||||
continuous_lock=false;
|
||||
recon_resume();
|
||||
}
|
||||
|
||||
@@ -42,6 +42,12 @@
|
||||
// maximum usable freq
|
||||
#define MAX_UFREQ 7200000000
|
||||
|
||||
// 1Mhz helper
|
||||
#ifdef OneMHz
|
||||
#undef OneMHz
|
||||
#endif
|
||||
#define OneMHz 1000000
|
||||
|
||||
namespace ui {
|
||||
|
||||
class ReconThread {
|
||||
@@ -59,8 +65,8 @@ namespace ui {
|
||||
void set_match_mode( const uint32_t v );
|
||||
uint32_t get_lock_nb_match();
|
||||
|
||||
void set_freq_lock(const uint32_t v);
|
||||
uint32_t is_freq_lock();
|
||||
void set_freq_lock(const int32_t v);
|
||||
int32_t is_freq_lock();
|
||||
int64_t get_current_freq();
|
||||
|
||||
void set_stepper(const int64_t v);
|
||||
@@ -182,8 +188,8 @@ namespace ui {
|
||||
int32_t squelch { 0 };
|
||||
int32_t db { 0 };
|
||||
int32_t timer { 0 };
|
||||
int32_t wait { 5000 }; // in msec. if > 0 wait duration after a lock, if < 0 duration is set to 'wait' unless there is no more activity
|
||||
uint32_t lock_wait { 500 }; // in msec. Represent the maximum amount of time we will wait for a lock to complete before switching to next
|
||||
int32_t wait { 1000 }; // in msec. if > 0 wait duration after a lock, if < 0 duration is set to 'wait' unless there is no more activity
|
||||
uint32_t lock_wait { 1000 }; // in msec. Represent the maximum amount of time we will wait for a lock to complete before switching to next
|
||||
int32_t def_step { 0 };
|
||||
freqman_db frequency_list = { };
|
||||
uint32_t current_index { 0 };
|
||||
@@ -200,7 +206,7 @@ namespace ui {
|
||||
bool load_hamradios = { true };
|
||||
bool update_ranges = { true };
|
||||
bool fwd = { true };
|
||||
uint32_t recon_lock_nb_match = { 10 };
|
||||
uint32_t recon_lock_nb_match = { 3 };
|
||||
uint32_t recon_lock_duration = { 50 };
|
||||
uint32_t recon_match_mode = { 0 };
|
||||
bool scanner_mode { false };
|
||||
|
||||
@@ -84,7 +84,21 @@ namespace ui {
|
||||
file_position -= (file_data + 256 - line_start);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( it < nb_params )
|
||||
{
|
||||
/* bad number of params, signal defaults */
|
||||
input_file = "RECON" ;
|
||||
output_file = "RECON_RESULTS" ;
|
||||
recon_lock_duration = 50 ;
|
||||
recon_lock_nb_match = 3 ;
|
||||
recon_squelch_level = -14 ;
|
||||
recon_match_mode = 0 ;
|
||||
wait = 1000 ;
|
||||
lock_wait = 1000 ;
|
||||
volume = 40 ;
|
||||
return false ;
|
||||
}
|
||||
|
||||
if( it > 0 )
|
||||
@@ -105,7 +119,7 @@ namespace ui {
|
||||
if( it > 3 )
|
||||
recon_lock_nb_match = strtoll( params[ 3 ].c_str() , nullptr , 10 );
|
||||
else
|
||||
recon_lock_nb_match = 10 ;
|
||||
recon_lock_nb_match = 3 ;
|
||||
|
||||
if( it > 4 )
|
||||
recon_squelch_level = strtoll( params[ 4 ].c_str() , nullptr , 10 );
|
||||
@@ -120,7 +134,7 @@ namespace ui {
|
||||
if( it > 6 )
|
||||
wait = strtoll( params[ 6 ].c_str() , nullptr , 10 );
|
||||
else
|
||||
wait = 5000 ;
|
||||
wait = 1000 ;
|
||||
|
||||
if( it > 7 )
|
||||
lock_wait = strtoll( params[ 7 ].c_str() , nullptr , 10 );
|
||||
@@ -132,11 +146,6 @@ namespace ui {
|
||||
else
|
||||
volume = 40 ;
|
||||
|
||||
if( it < nb_params )
|
||||
{
|
||||
/* bad number of params, signal defaults */
|
||||
return false ;
|
||||
}
|
||||
return true ;
|
||||
}
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ public:
|
||||
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override { return "Flash Utility"; };
|
||||
std::string title() const override { return "SD over USB"; };
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
|
||||
@@ -446,7 +446,7 @@ namespace ui {
|
||||
else
|
||||
{
|
||||
auto result = delete_file( pmem_flag_file );
|
||||
if( result != 0 )
|
||||
if( result.code() != FR_OK )
|
||||
{
|
||||
text_pmem_status.set("!err. deleting pmem flagfile!");
|
||||
}
|
||||
|
||||
@@ -135,10 +135,9 @@ SondeView::SondeView(NavigationView& nav) {
|
||||
|
||||
logger = std::make_unique<SondeLogger>();
|
||||
if (logger)
|
||||
logger->append(u"sonde.txt");
|
||||
logger->append( LOG_ROOT_DIR "/SONDE.TXT" );
|
||||
|
||||
// initialize audio:
|
||||
|
||||
field_volume.set_value((receiver_model.headphone_volume() - audio::headphone::volume_range().max).decibel() + 99);
|
||||
|
||||
field_volume.on_change = [this](int32_t v) {
|
||||
|
||||
@@ -79,7 +79,6 @@ TestView::TestView(NavigationView& nav) {
|
||||
cal_value = raw_alt - 0x80;
|
||||
};
|
||||
|
||||
|
||||
logger = std::make_unique<TestLogger>();
|
||||
if (logger)
|
||||
logger->append("saucepan.txt");
|
||||
|
||||
@@ -73,8 +73,8 @@ void NBFMConfig::apply(const uint8_t squelch_level) const {
|
||||
|
||||
void WFMConfig::apply() const {
|
||||
const WFMConfigureMessage message {
|
||||
taps_200k_wfm_decim_0,
|
||||
taps_200k_wfm_decim_1,
|
||||
decim_0, // taps_200k_decim_0 , taps_180k_wfm_decim_0, taps_40k_wfm_decim_0
|
||||
decim_1, // taps_200k_decim_1 or taps_180k_wfm_decim_1, taps_40k_wfm_decim_1
|
||||
taps_64_lp_156_198,
|
||||
75000,
|
||||
audio_48k_hpf_30hz_config,
|
||||
@@ -84,6 +84,7 @@ void WFMConfig::apply() const {
|
||||
audio::set_rate(audio::Rate::Hz_48000);
|
||||
}
|
||||
|
||||
|
||||
void set_tone(const uint32_t index, const uint32_t delta, const uint32_t duration) {
|
||||
shared_memory.bb_data.tones_data.tone_defs[index].delta = delta;
|
||||
shared_memory.bb_data.tones_data.tone_defs[index].duration = duration;
|
||||
|
||||
@@ -53,6 +53,9 @@ struct NBFMConfig {
|
||||
};
|
||||
|
||||
struct WFMConfig {
|
||||
const fir_taps_real<24> decim_0; // To handle both WFM filters , 200k and 40K for NOAA APT
|
||||
const fir_taps_real<16> decim_1;
|
||||
|
||||
void apply() const;
|
||||
};
|
||||
|
||||
|
||||
@@ -2543,6 +2543,137 @@ static constexpr Bitmap bitmap_target {
|
||||
{ 16, 16 }, bitmap_target_data
|
||||
};
|
||||
|
||||
static constexpr uint8_t bitmap_icon_trash_data[] = {
|
||||
0x00, 0x00,
|
||||
0xC0, 0x01,
|
||||
0x20, 0x02,
|
||||
0xFC, 0x1F,
|
||||
0x00, 0x00,
|
||||
0xA8, 0x0A,
|
||||
0xA8, 0x0A,
|
||||
0xA8, 0x0A,
|
||||
0xA8, 0x0A,
|
||||
0xA8, 0x0A,
|
||||
0xA8, 0x0A,
|
||||
0xA8, 0x0A,
|
||||
0xA8, 0x0A,
|
||||
0x08, 0x08,
|
||||
0xF0, 0x07,
|
||||
0x00, 0x00,
|
||||
};
|
||||
static constexpr Bitmap bitmap_icon_trash {
|
||||
{ 16, 16 }, bitmap_icon_trash_data
|
||||
};
|
||||
|
||||
static constexpr uint8_t bitmap_icon_copy_data[] = {
|
||||
0x00, 0x00,
|
||||
0xFC, 0x00,
|
||||
0x84, 0x01,
|
||||
0xC4, 0x0F,
|
||||
0x74, 0x18,
|
||||
0x44, 0x38,
|
||||
0x44, 0x78,
|
||||
0x74, 0x40,
|
||||
0x44, 0x44,
|
||||
0x44, 0x44,
|
||||
0x74, 0x5F,
|
||||
0x44, 0x44,
|
||||
0x44, 0x44,
|
||||
0x7C, 0x40,
|
||||
0xC0, 0x7F,
|
||||
0x00, 0x00,
|
||||
};
|
||||
static constexpr Bitmap bitmap_icon_copy {
|
||||
{ 16, 16 }, bitmap_icon_copy_data
|
||||
};
|
||||
|
||||
static constexpr uint8_t bitmap_icon_cut_data[] = {
|
||||
0x00, 0x00,
|
||||
0x10, 0x10,
|
||||
0x30, 0x18,
|
||||
0x20, 0x08,
|
||||
0x60, 0x0C,
|
||||
0x40, 0x04,
|
||||
0xC0, 0x06,
|
||||
0x80, 0x00,
|
||||
0x80, 0x01,
|
||||
0x80, 0x01,
|
||||
0xC0, 0x03,
|
||||
0x78, 0x1E,
|
||||
0x44, 0x22,
|
||||
0x44, 0x22,
|
||||
0x44, 0x22,
|
||||
0x38, 0x1C,
|
||||
};
|
||||
static constexpr Bitmap bitmap_icon_cut {
|
||||
{ 16, 16 }, bitmap_icon_cut_data
|
||||
};
|
||||
|
||||
static constexpr uint8_t bitmap_icon_paste_data[] = {
|
||||
0x00, 0x00,
|
||||
0xE0, 0x00,
|
||||
0x18, 0x03,
|
||||
0xE4, 0x04,
|
||||
0x04, 0x04,
|
||||
0x04, 0x04,
|
||||
0x84, 0x3F,
|
||||
0x84, 0x20,
|
||||
0x84, 0x2E,
|
||||
0x84, 0x20,
|
||||
0x84, 0x2E,
|
||||
0x84, 0x20,
|
||||
0x84, 0x2E,
|
||||
0xF8, 0x20,
|
||||
0x80, 0x3F,
|
||||
0x00, 0x00,
|
||||
};
|
||||
static constexpr Bitmap bitmap_icon_paste {
|
||||
{ 16, 16 }, bitmap_icon_paste_data
|
||||
};
|
||||
|
||||
static constexpr uint8_t bitmap_icon_new_dir_data[] = {
|
||||
0x00, 0x00,
|
||||
0x1E, 0x00,
|
||||
0x21, 0x00,
|
||||
0xE1, 0x7F,
|
||||
0x01, 0xC0,
|
||||
0x81, 0x81,
|
||||
0x81, 0x81,
|
||||
0x81, 0x81,
|
||||
0xF1, 0x8F,
|
||||
0xF1, 0x8F,
|
||||
0x81, 0x81,
|
||||
0x81, 0x81,
|
||||
0x81, 0x81,
|
||||
0x03, 0xC0,
|
||||
0xFE, 0x7F,
|
||||
0x00, 0x00,
|
||||
};
|
||||
static constexpr Bitmap bitmap_icon_new_dir {
|
||||
{ 16, 16 }, bitmap_icon_new_dir_data
|
||||
};
|
||||
|
||||
static constexpr uint8_t bitmap_icon_new_file_data[] = {
|
||||
0x00, 0x00,
|
||||
0xFC, 0x07,
|
||||
0x04, 0x0C,
|
||||
0x04, 0x1C,
|
||||
0x04, 0x3C,
|
||||
0x84, 0x21,
|
||||
0x84, 0x21,
|
||||
0x84, 0x21,
|
||||
0xF4, 0x2F,
|
||||
0xF4, 0x2F,
|
||||
0x84, 0x21,
|
||||
0x84, 0x21,
|
||||
0x84, 0x21,
|
||||
0x04, 0x20,
|
||||
0xFC, 0x3F,
|
||||
0x00, 0x00,
|
||||
};
|
||||
static constexpr Bitmap bitmap_icon_new_file {
|
||||
{ 16, 16 }, bitmap_icon_new_file_data
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
|
||||
+128
-34
@@ -124,50 +124,85 @@ Optional<File::Error> File::sync() {
|
||||
}
|
||||
}
|
||||
|
||||
static std::filesystem::path find_last_file_matching_pattern(const std::filesystem::path& pattern) {
|
||||
std::filesystem::path last_match;
|
||||
for(const auto& entry : std::filesystem::directory_iterator(u"", pattern)) {
|
||||
if( std::filesystem::is_regular_file(entry.status()) ) {
|
||||
/* Range used for filename matching.
|
||||
* Start and end are inclusive positions of "???" */
|
||||
struct pattern_range {
|
||||
size_t start;
|
||||
size_t end;
|
||||
};
|
||||
|
||||
/* Finds the last file matching the specified pattern that
|
||||
* can be automatically incremented (digits in pattern).
|
||||
* NB: assumes a patten with contiguous '?' like "FOO_???.txt". */
|
||||
static std::filesystem::path find_last_ordinal_match(
|
||||
const std::filesystem::path& folder,
|
||||
const std::filesystem::path& pattern,
|
||||
pattern_range range
|
||||
) {
|
||||
auto last_match = std::filesystem::path();
|
||||
auto can_increment = [range](const auto& path) {
|
||||
for (auto i = range.start; i <= range.end; ++i)
|
||||
if (!isdigit(path.native()[i]))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
for (const auto& entry : std::filesystem::directory_iterator(folder, pattern)) {
|
||||
if (std::filesystem::is_regular_file(entry.status()) && can_increment(entry.path())) {
|
||||
const auto& match = entry.path();
|
||||
if( match > last_match ) {
|
||||
last_match = match;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return last_match;
|
||||
}
|
||||
|
||||
static std::filesystem::path increment_filename_stem_ordinal(std::filesystem::path path) {
|
||||
auto t = path.replace_extension().native();
|
||||
auto it = t.rbegin();
|
||||
/* Given a file path like "FOO_0001.txt" increment it to "FOO_0002.txt". */
|
||||
static std::filesystem::path increment_filename_ordinal(
|
||||
const std::filesystem::path& path, pattern_range range
|
||||
) {
|
||||
auto name = path.filename().native();
|
||||
|
||||
// Increment decimal number before the extension.
|
||||
for(; it != t.rend(); ++it) {
|
||||
const auto c = *it;
|
||||
if( c < '0' ) {
|
||||
for (auto i = range.end; i >= range.start; --i) {
|
||||
auto& c = name[i];
|
||||
|
||||
// Not a digit or would overflow the counter.
|
||||
if (c < u'0' || c > u'9' || (c == u'9' && i == range.start))
|
||||
return { };
|
||||
} else if( c < '9' ) {
|
||||
*it += 1;
|
||||
|
||||
if (c == u'9')
|
||||
c = '0';
|
||||
else {
|
||||
c++;
|
||||
break;
|
||||
} else if( c == '9' ) {
|
||||
*it = '0';
|
||||
} else {
|
||||
return { };
|
||||
}
|
||||
}
|
||||
|
||||
return t;
|
||||
return { name };
|
||||
}
|
||||
|
||||
std::filesystem::path next_filename_stem_matching_pattern(std::filesystem::path filename_pattern) {
|
||||
const auto next_filename = find_last_file_matching_pattern(filename_pattern.replace_extension(u".*"));
|
||||
if( next_filename.empty() ) {
|
||||
auto pattern_s = filename_pattern.replace_extension().native();
|
||||
std::replace(std::begin(pattern_s), std::end(pattern_s), '?', '0');
|
||||
return pattern_s;
|
||||
} else {
|
||||
return increment_filename_stem_ordinal(next_filename);
|
||||
std::filesystem::path next_filename_matching_pattern(const std::filesystem::path& filename_pattern) {
|
||||
auto path = filename_pattern.parent_path();
|
||||
auto pattern = filename_pattern.filename();
|
||||
auto range = pattern_range {
|
||||
pattern.native().find_first_of(u'?'),
|
||||
pattern.native().find_last_of(u'?')
|
||||
};
|
||||
|
||||
const auto match = find_last_ordinal_match(path, pattern, range);
|
||||
|
||||
if (match.empty()) {
|
||||
auto pattern_str = pattern.native();
|
||||
for (auto i = range.start; i <= range.end; ++i)
|
||||
pattern_str[i] = u'0';
|
||||
return path / pattern_str;
|
||||
}
|
||||
|
||||
auto next_name = increment_filename_ordinal(match, range);
|
||||
return next_name.empty() ? next_name : path / next_name;
|
||||
}
|
||||
|
||||
std::vector<std::filesystem::path> scan_root_files(const std::filesystem::path& directory,
|
||||
@@ -197,12 +232,44 @@ std::vector<std::filesystem::path> scan_root_directories(const std::filesystem::
|
||||
return directory_list;
|
||||
}
|
||||
|
||||
uint32_t delete_file(const std::filesystem::path& file_path) {
|
||||
return f_unlink(reinterpret_cast<const TCHAR*>(file_path.c_str()));
|
||||
std::filesystem::filesystem_error delete_file(const std::filesystem::path& file_path) {
|
||||
return { f_unlink(reinterpret_cast<const TCHAR*>(file_path.c_str())) };
|
||||
}
|
||||
|
||||
uint32_t rename_file(const std::filesystem::path& file_path, const std::filesystem::path& new_name) {
|
||||
return f_rename(reinterpret_cast<const TCHAR*>(file_path.c_str()), reinterpret_cast<const TCHAR*>(new_name.c_str()));
|
||||
std::filesystem::filesystem_error rename_file(
|
||||
const std::filesystem::path& file_path,
|
||||
const std::filesystem::path& new_name
|
||||
) {
|
||||
return { f_rename(reinterpret_cast<const TCHAR*>(file_path.c_str()), reinterpret_cast<const TCHAR*>(new_name.c_str())) };
|
||||
}
|
||||
|
||||
std::filesystem::filesystem_error copy_file(
|
||||
const std::filesystem::path& file_path,
|
||||
const std::filesystem::path& dest_path
|
||||
) {
|
||||
File src;
|
||||
File dst;
|
||||
constexpr size_t buffer_size = 128;
|
||||
uint8_t buffer[buffer_size];
|
||||
|
||||
auto error = src.open(file_path);
|
||||
if (error.is_valid()) return error.value();
|
||||
|
||||
error = dst.create(dest_path);
|
||||
if (error.is_valid()) return error.value();
|
||||
|
||||
while (true) {
|
||||
auto result = src.read(buffer, buffer_size);
|
||||
if (result.is_error()) return result.error();
|
||||
|
||||
result = dst.write(buffer, result.value());
|
||||
if (result.is_error()) return result.error();
|
||||
|
||||
if (result.value() < buffer_size)
|
||||
break;
|
||||
}
|
||||
|
||||
return { };
|
||||
}
|
||||
|
||||
FATTimestamp file_created_date(const std::filesystem::path& file_path) {
|
||||
@@ -213,8 +280,33 @@ FATTimestamp file_created_date(const std::filesystem::path& file_path) {
|
||||
return { filinfo.fdate, filinfo.ftime };
|
||||
}
|
||||
|
||||
uint32_t make_new_directory(const std::filesystem::path& dir_path) {
|
||||
return f_mkdir(reinterpret_cast<const TCHAR*>(dir_path.c_str()));
|
||||
std::filesystem::filesystem_error make_new_file(
|
||||
const std::filesystem::path& file_path
|
||||
) {
|
||||
File f;
|
||||
auto result = f.create(file_path);
|
||||
return result.is_valid()
|
||||
? result.value()
|
||||
: std::filesystem::filesystem_error{ };
|
||||
}
|
||||
|
||||
std::filesystem::filesystem_error make_new_directory(
|
||||
const std::filesystem::path& dir_path
|
||||
) {
|
||||
return { f_mkdir(reinterpret_cast<const TCHAR*>(dir_path.c_str())) };
|
||||
}
|
||||
|
||||
std::filesystem::filesystem_error ensure_directory(
|
||||
const std::filesystem::path& dir_path
|
||||
) {
|
||||
if (dir_path.empty() || std::filesystem::file_exists(dir_path))
|
||||
return { };
|
||||
|
||||
auto result = ensure_directory(dir_path.parent_path());
|
||||
if (result.code())
|
||||
return result;
|
||||
|
||||
return make_new_directory(dir_path);
|
||||
}
|
||||
|
||||
namespace std {
|
||||
@@ -339,8 +431,10 @@ directory_iterator::directory_iterator(
|
||||
) : pattern { wild }
|
||||
{
|
||||
impl = std::make_shared<Impl>();
|
||||
const auto result = f_findfirst(&impl->dir, &impl->filinfo, reinterpret_cast<const TCHAR*>(path.c_str()), reinterpret_cast<const TCHAR*>(pattern.c_str()));
|
||||
if( result != FR_OK || impl->filinfo.fname[0] == (TCHAR)'\0') {
|
||||
const auto result = f_findfirst(&impl->dir, &impl->filinfo,
|
||||
reinterpret_cast<const TCHAR*>(path.c_str()),
|
||||
reinterpret_cast<const TCHAR*>(pattern.c_str()));
|
||||
if (result != FR_OK || impl->filinfo.fname[0] == (TCHAR)'\0') {
|
||||
impl.reset();
|
||||
// TODO: Throw exception if/when I enable exceptions...
|
||||
}
|
||||
|
||||
@@ -59,6 +59,10 @@ struct filesystem_error {
|
||||
|
||||
std::string what() const;
|
||||
|
||||
bool ok() const {
|
||||
return err == FR_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
uint32_t err { FR_OK };
|
||||
};
|
||||
@@ -252,16 +256,23 @@ struct FATTimestamp {
|
||||
uint16_t FAT_time;
|
||||
};
|
||||
|
||||
uint32_t delete_file(const std::filesystem::path& file_path);
|
||||
uint32_t rename_file(const std::filesystem::path& file_path, const std::filesystem::path& new_name);
|
||||
std::filesystem::filesystem_error delete_file(const std::filesystem::path& file_path);
|
||||
std::filesystem::filesystem_error rename_file(const std::filesystem::path& file_path, const std::filesystem::path& new_name);
|
||||
std::filesystem::filesystem_error copy_file(const std::filesystem::path& file_path, const std::filesystem::path& dest_path);
|
||||
FATTimestamp file_created_date(const std::filesystem::path& file_path);
|
||||
uint32_t make_new_directory(const std::filesystem::path& dir_path);
|
||||
std::filesystem::filesystem_error make_new_file(const std::filesystem::path& file_path);
|
||||
std::filesystem::filesystem_error make_new_directory(const std::filesystem::path& dir_path);
|
||||
std::filesystem::filesystem_error ensure_directory(const std::filesystem::path& dir_path);
|
||||
|
||||
std::vector<std::filesystem::path> scan_root_files(const std::filesystem::path& directory, const std::filesystem::path& extension);
|
||||
std::vector<std::filesystem::path> scan_root_directories(const std::filesystem::path& directory);
|
||||
|
||||
/* Gets an auto incrementing filename. */
|
||||
std::filesystem::path next_filename_stem_matching_pattern(std::filesystem::path filename_stem_pattern);
|
||||
/* Gets an auto incrementing filename stem.
|
||||
* Pattern should be like "FOO_???.txt" where ??? will be replaced by digits.
|
||||
* Pattern may also contain a folder path like "LOGS/FOO_???.txt".
|
||||
* Pattern '?' must be contiguous (bad: "FOO?_??")
|
||||
* Returns empty path if a filename could not be created. */
|
||||
std::filesystem::path next_filename_matching_pattern(const std::filesystem::path& pattern);
|
||||
|
||||
/* Values added to FatFs FRESULT enum, values outside the FRESULT data type */
|
||||
static_assert(sizeof(FIL::err) == 1, "FatFs FIL::err size not expected.");
|
||||
|
||||
@@ -47,44 +47,46 @@ options_t freqman_entry_bandwidths[ 4 ] = {
|
||||
},
|
||||
{ //WFM
|
||||
{ "200k" , 0 },
|
||||
{ "180k" , 1 },
|
||||
{ " 40k" , 2 },
|
||||
}
|
||||
};
|
||||
|
||||
options_t freqman_entry_steps = {
|
||||
{ "0.1KHz " , 100 },
|
||||
{ "1KHz " , 1000 },
|
||||
{ "5KHz (SA AM)" , 5000 },
|
||||
{ "6.25KHz(NFM)" , 6250 },
|
||||
{ "8.33KHz(AIR)" , 8330 },
|
||||
{ "9KHz (EU AM)" , 9000 },
|
||||
{ "10KHz(US AM)" , 10000 },
|
||||
{ "12.5KHz(NFM)" , 12500 },
|
||||
{ "15KHz (HFM)" , 15000 },
|
||||
{ "25KHz (N1)" , 25000 },
|
||||
{ "30KHz (OIRT)" , 30000 },
|
||||
{ "50KHz (FM1)" , 50000 },
|
||||
{ "100KHz (FM2)" , 100000 },
|
||||
{ "250KHz (N2)" , 250000 },
|
||||
{ "500KHz (WFM)" , 500000 },
|
||||
{ "0.1kHz " , 100 },
|
||||
{ "1kHz " , 1000 },
|
||||
{ "5kHz (SA AM)" , 5000 },
|
||||
{ "6.25kHz(NFM)" , 6250 },
|
||||
{ "8.33kHz(AIR)" , 8330 },
|
||||
{ "9kHz (EU AM)" , 9000 },
|
||||
{ "10kHz(US AM)" , 10000 },
|
||||
{ "12.5kHz(NFM)" , 12500 },
|
||||
{ "15kHz (HFM)" , 15000 },
|
||||
{ "25kHz (N1)" , 25000 },
|
||||
{ "30kHz (OIRT)" , 30000 },
|
||||
{ "50kHz (FM1)" , 50000 },
|
||||
{ "100kHz (FM2)" , 100000 },
|
||||
{ "250kHz (N2)" , 250000 },
|
||||
{ "500kHz (WFM)" , 500000 },
|
||||
{ "1MHz " , 1000000 }
|
||||
};
|
||||
|
||||
options_t freqman_entry_steps_short = {
|
||||
{ "0.1KHz" , 100 },
|
||||
{ "1KHz" , 1000 },
|
||||
{ "5KHz" , 5000 },
|
||||
{ "6.25KHz" , 6250 },
|
||||
{ "8.33KHz" , 8330 },
|
||||
{ "9KHz" , 9000 },
|
||||
{ "10KHz" , 10000 },
|
||||
{ "12.5KHz" , 12500 },
|
||||
{ "15KHz" , 15000 },
|
||||
{ "25KHz" , 25000 },
|
||||
{ "30KHz" , 30000 },
|
||||
{ "50KHz" , 50000 },
|
||||
{ "100KHz" , 100000 },
|
||||
{ "250KHz" , 250000 },
|
||||
{ "500KHz" , 500000 },
|
||||
{ "0.1kHz" , 100 },
|
||||
{ "1kHz" , 1000 },
|
||||
{ "5kHz" , 5000 },
|
||||
{ "6.25kHz" , 6250 },
|
||||
{ "8.33kHz" , 8330 },
|
||||
{ "9kHz" , 9000 },
|
||||
{ "10kHz" , 10000 },
|
||||
{ "12.5kHz" , 12500 },
|
||||
{ "15kHz" , 15000 },
|
||||
{ "25kHz" , 25000 },
|
||||
{ "30kHz" , 30000 },
|
||||
{ "50kHz" , 50000 },
|
||||
{ "100kHz" , 100000 },
|
||||
{ "250kHz" , 250000 },
|
||||
{ "500kHz" , 500000 },
|
||||
{ "1MHz" , 1000000 }
|
||||
};
|
||||
|
||||
@@ -144,17 +146,15 @@ bool load_freqman_file_ex(std::string& file_stem, freqman_db& db, bool load_freq
|
||||
// Reset line_start to beginning of buffer
|
||||
line_start = file_data;
|
||||
|
||||
if (!strstr(file_data, "f=") && !strstr(file_data, "a=") && !strstr(file_data, "r=") )
|
||||
break;
|
||||
|
||||
// Look for complete lines in buffer
|
||||
while ((line_end = strstr(line_start, "\x0A"))) {
|
||||
|
||||
*line_end = 0; // Stop strstr() searches below at EOL
|
||||
modulation = -1 ;
|
||||
bandwidth = -1 ;
|
||||
step = -1 ;
|
||||
tone = -1 ;
|
||||
type=SINGLE ;
|
||||
type = ERROR_TYPE;
|
||||
|
||||
frequency_a = frequency_b = 0;
|
||||
// Read frequency
|
||||
@@ -162,6 +162,7 @@ bool load_freqman_file_ex(std::string& file_stem, freqman_db& db, bool load_freq
|
||||
if(pos) {
|
||||
pos += 2;
|
||||
frequency_a = strtoll(pos, nullptr, 10);
|
||||
type = SINGLE;
|
||||
} else {
|
||||
// ...or range
|
||||
pos = strstr(line_start, "a=");
|
||||
|
||||
@@ -87,7 +87,7 @@ struct freqman_entry {
|
||||
freqman_entry_type type { }; // SINGLE,RANGE,HAMRADIO
|
||||
freqman_index_t modulation { }; // AM,NFM,WFM
|
||||
freqman_index_t bandwidth { }; // AM_DSB, ...
|
||||
freqman_index_t step { }; // 5Khz (SA AM,...
|
||||
freqman_index_t step { }; // 5khz (SA AM,...
|
||||
tone_index tone { }; // 0XZ, 11 1ZB,...
|
||||
};
|
||||
|
||||
|
||||
@@ -27,11 +27,18 @@
|
||||
#include "file.hpp"
|
||||
|
||||
#include "lpc43xx_cpp.hpp"
|
||||
|
||||
using namespace lpc43xx;
|
||||
|
||||
#define LOG_ROOT_DIR "LOGS"
|
||||
|
||||
class LogFile {
|
||||
public:
|
||||
Optional<File::Error> append(const std::filesystem::path& filename) {
|
||||
auto result = ensure_directory(filename.parent_path());
|
||||
if (result.code())
|
||||
return { result };
|
||||
|
||||
return file.append(filename);
|
||||
}
|
||||
|
||||
|
||||
@@ -52,8 +52,10 @@ static constexpr std::array<baseband::NBFMConfig, 3> nbfm_configs { {
|
||||
{ taps_16k0_decim_0, taps_16k0_decim_1, taps_16k0_channel, 5000 },
|
||||
} };
|
||||
|
||||
static constexpr std::array<baseband::WFMConfig, 1> wfm_configs { {
|
||||
{ },
|
||||
static constexpr std::array<baseband::WFMConfig, 3> wfm_configs { {
|
||||
{taps_200k_wfm_decim_0, taps_200k_wfm_decim_1 },
|
||||
{taps_180k_wfm_decim_0, taps_180k_wfm_decim_1 },
|
||||
{taps_40k_wfm_decim_0, taps_40k_wfm_decim_1 },
|
||||
} };
|
||||
|
||||
} /* namespace */
|
||||
|
||||
@@ -256,8 +256,17 @@ double get_decimals(double num, int16_t mult, bool round) {
|
||||
return intnum;
|
||||
}
|
||||
|
||||
std::string trimr(std::string str)
|
||||
{
|
||||
std::string trim(const std::string& str) {
|
||||
auto first = str.find_first_not_of(' ');
|
||||
auto last = str.find_last_not_of(' ');
|
||||
return str.substr(first, last - first);
|
||||
}
|
||||
|
||||
std::string trimr(std::string str) {
|
||||
size_t last = str.find_last_not_of(' ');
|
||||
return (last!=std::string::npos) ? str.substr(0, last+1) : ""; // Remove the trailing spaces
|
||||
}
|
||||
|
||||
std::string truncate(const std::string& str, size_t length) {
|
||||
return str.length() <= length ? str : str.substr(0, length);
|
||||
}
|
||||
@@ -59,6 +59,8 @@ std::string to_string_FAT_timestamp(const FATTimestamp& timestamp);
|
||||
std::string unit_auto_scale(double n, const uint32_t base_nano, uint32_t precision);
|
||||
double get_decimals(double num, int16_t mult, bool round = false); //euquiq added
|
||||
|
||||
std::string trim(const std::string& str); // Remove whitespace at ends.
|
||||
std::string trimr(std::string str); // Remove trailing spaces
|
||||
std::string truncate(const std::string& str, size_t length);
|
||||
|
||||
#endif/*__STRING_FORMAT_H__*/
|
||||
|
||||
@@ -167,6 +167,10 @@ void MenuView::clear() {
|
||||
offset = 0;
|
||||
}
|
||||
|
||||
size_t MenuView::item_count() const {
|
||||
return menu_items.size();
|
||||
}
|
||||
|
||||
void MenuView::add_item(MenuItem new_item) {
|
||||
menu_items.push_back(new_item);
|
||||
|
||||
|
||||
@@ -83,6 +83,7 @@ public:
|
||||
void add_item(MenuItem new_item);
|
||||
void add_items(std::initializer_list<MenuItem> new_items);
|
||||
void clear();
|
||||
size_t item_count() const;
|
||||
|
||||
MenuItemView* item_view(size_t index) const;
|
||||
|
||||
|
||||
@@ -87,7 +87,22 @@ bool FrequencyField::on_key(const ui::KeyEvent event) {
|
||||
}
|
||||
|
||||
bool FrequencyField::on_encoder(const EncoderEvent delta) {
|
||||
set_value(value() + (delta * step));
|
||||
if (step == 0) { // 'Auto' mode.'
|
||||
auto ms = RTT2MS(halGetCounterValue());
|
||||
auto delta_ms = last_ms_ <= ms ? ms - last_ms_ : ms;
|
||||
last_ms_ = ms;
|
||||
|
||||
// The goal is to map 'scale' to a range of about 10 to 10M.
|
||||
// The faster the encoder is rotated, the larger the step.
|
||||
// Linear doesn't feel right. Hyperbolic felt better.
|
||||
// To get these magic numbers, I graphed the function until the
|
||||
// curve shape seemed about right then tested on device.
|
||||
delta_ms = std::min(145ull, delta_ms) + 5; // Prevent DIV/0
|
||||
int64_t scale = 200'000'000 / (0.001'55 * pow(delta_ms, 5.45)) + 8;
|
||||
set_value(value() + (delta * scale));
|
||||
} else {
|
||||
set_value(value() + (delta * step));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -63,6 +63,7 @@ private:
|
||||
const range_t range;
|
||||
rf::Frequency value_ { 0 };
|
||||
rf::Frequency step { 25000 };
|
||||
uint64_t last_ms_ { 0 };
|
||||
|
||||
rf::Frequency clamp_value(rf::Frequency value);
|
||||
};
|
||||
@@ -247,6 +248,7 @@ public:
|
||||
parent_pos,
|
||||
5,
|
||||
{
|
||||
{ " Auto", 0 }, /* Faster == larger step. */
|
||||
{ " 10", 10 }, /* Fine tuning SSB voice pitch,in HF and QO-100 sat #669 */
|
||||
{ " 50", 50 }, /* added 50Hz/10Hz,but we do not increase GUI digit decimal */
|
||||
{ " 100", 100 },
|
||||
|
||||
@@ -61,137 +61,6 @@ void text_prompt(
|
||||
}*/
|
||||
}
|
||||
|
||||
/* TextField ***********************************************************/
|
||||
|
||||
TextField::TextField(
|
||||
std::string& str,
|
||||
size_t max_length,
|
||||
Point position,
|
||||
uint32_t length
|
||||
) : Widget{ { position, { 8 * static_cast<int>(length), 16 } } },
|
||||
text_{ str },
|
||||
max_length_{ std::max<size_t>(max_length, str.length()) },
|
||||
char_count_{ std::max<uint32_t>(length, 1) },
|
||||
cursor_pos_{ text_.length() },
|
||||
insert_mode_{ true }
|
||||
{
|
||||
set_focusable(true);
|
||||
}
|
||||
|
||||
const std::string& TextField::value() const {
|
||||
return text_;
|
||||
}
|
||||
|
||||
void TextField::set_cursor(uint32_t pos) {
|
||||
cursor_pos_ = std::min<size_t>(pos, text_.length());
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
void TextField::set_insert_mode() {
|
||||
insert_mode_ = true;
|
||||
}
|
||||
|
||||
void TextField::set_overwrite_mode() {
|
||||
insert_mode_ = false;
|
||||
}
|
||||
|
||||
void TextField::char_add(char c) {
|
||||
// Don't add if inserting and at max_length and
|
||||
// don't overwrite if past the end of the text.
|
||||
if ((text_.length() >= max_length_ && insert_mode_) ||
|
||||
(cursor_pos_ >= text_.length() && !insert_mode_))
|
||||
return;
|
||||
|
||||
if (insert_mode_)
|
||||
text_.insert(cursor_pos_, 1, c);
|
||||
else
|
||||
text_[cursor_pos_] = c;
|
||||
|
||||
cursor_pos_++;
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
void TextField::char_delete() {
|
||||
if (cursor_pos_ == 0)
|
||||
return;
|
||||
|
||||
cursor_pos_--;
|
||||
text_.erase(cursor_pos_, 1);
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
void TextField::paint(Painter& painter) {
|
||||
constexpr int char_width = 8;
|
||||
|
||||
auto rect = screen_rect();
|
||||
auto text_style = has_focus() ? style().invert() : style();
|
||||
auto offset = 0;
|
||||
|
||||
// Does the string need to be shifted?
|
||||
if (cursor_pos_ >= char_count_)
|
||||
offset = cursor_pos_ - char_count_ + 1;
|
||||
|
||||
// Clear the control.
|
||||
painter.fill_rectangle(rect, text_style.background);
|
||||
|
||||
// Draw the text starting at the offset.
|
||||
for (uint32_t i = 0; i < char_count_ && i + offset < text_.length(); i++) {
|
||||
painter.draw_char(
|
||||
{ rect.location().x() + (static_cast<int>(i) * char_width), rect.location().y() },
|
||||
text_style,
|
||||
text_[i + offset]
|
||||
);
|
||||
}
|
||||
|
||||
// Determine cursor position on screen (either the cursor position or the last char).
|
||||
int32_t cursor_x = char_width * (offset > 0 ? char_count_ - 1 : cursor_pos_);
|
||||
Point cursor_point{ screen_pos().x() + cursor_x, screen_pos().y() };
|
||||
auto cursor_style = text_style.invert();
|
||||
|
||||
// Invert the cursor character when in overwrite mode.
|
||||
if (!insert_mode_ && (cursor_pos_) < text_.length())
|
||||
painter.draw_char(cursor_point, cursor_style, text_[cursor_pos_]);
|
||||
|
||||
// Draw the cursor.
|
||||
Rect cursor_box{ cursor_point, { char_width, 16 } };
|
||||
painter.draw_rectangle(cursor_box, cursor_style.background);
|
||||
}
|
||||
|
||||
bool TextField::on_key(const KeyEvent key) {
|
||||
if (key == KeyEvent::Left && cursor_pos_ > 0)
|
||||
cursor_pos_--;
|
||||
else if (key == KeyEvent::Right && cursor_pos_ < text_.length())
|
||||
cursor_pos_++;
|
||||
else if (key == KeyEvent::Select)
|
||||
insert_mode_ = !insert_mode_;
|
||||
else
|
||||
return false;
|
||||
|
||||
set_dirty();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TextField::on_encoder(const EncoderEvent delta) {
|
||||
int32_t new_pos = cursor_pos_ + delta;
|
||||
|
||||
// Let the encoder wrap around the ends of the text.
|
||||
if (new_pos < 0)
|
||||
new_pos = text_.length();
|
||||
else if (static_cast<size_t>(new_pos) > text_.length())
|
||||
new_pos = 0;
|
||||
|
||||
set_cursor(new_pos);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TextField::on_touch(const TouchEvent event) {
|
||||
if (event.type == TouchEvent::Type::Start)
|
||||
focus();
|
||||
|
||||
set_dirty();
|
||||
return true;
|
||||
}
|
||||
|
||||
/* TextEntryView ***********************************************************/
|
||||
|
||||
void TextEntryView::char_delete() {
|
||||
|
||||
@@ -28,52 +28,6 @@
|
||||
|
||||
namespace ui {
|
||||
|
||||
// A TextField is bound to a string reference and allows the string
|
||||
// to be manipulated. The field itself does not provide the UI for
|
||||
// setting the value. It provides the UI of rendering the text,
|
||||
// a cursor, and an API to edit the string content.
|
||||
class TextField : public Widget {
|
||||
public:
|
||||
TextField(std::string& str, Point position, uint32_t length = 30)
|
||||
: TextField{str, 64, position, length} { }
|
||||
|
||||
// Str: the string containing the content to edit.
|
||||
// Max_length: max length the string is allowed to use.
|
||||
// Position: the top-left corner of the control.
|
||||
// Length: the number of characters to display.
|
||||
// - Characters are 8 pixels wide.
|
||||
// - The screen can show 30 characters max.
|
||||
// - The control is 16 pixels tall.
|
||||
TextField(std::string& str, size_t max_length, Point position, uint32_t length = 30);
|
||||
|
||||
TextField(const TextField&) = delete;
|
||||
TextField(TextField&&) = delete;
|
||||
TextField& operator=(const TextField&) = delete;
|
||||
TextField& operator=(TextField&&) = delete;
|
||||
|
||||
const std::string& value() const;
|
||||
|
||||
void set_cursor(uint32_t pos);
|
||||
void set_insert_mode();
|
||||
void set_overwrite_mode();
|
||||
|
||||
void char_add(char c);
|
||||
void char_delete();
|
||||
|
||||
void paint(Painter& painter) override;
|
||||
|
||||
bool on_key(const KeyEvent key) override;
|
||||
bool on_encoder(const EncoderEvent delta) override;
|
||||
bool on_touch(const TouchEvent event) override;
|
||||
|
||||
protected:
|
||||
std::string& text_;
|
||||
size_t max_length_;
|
||||
uint32_t char_count_;
|
||||
uint32_t cursor_pos_;
|
||||
bool insert_mode_;
|
||||
};
|
||||
|
||||
class TextEntryView : public View {
|
||||
public:
|
||||
std::function<void(std::string&)> on_changed { };
|
||||
|
||||
@@ -367,13 +367,15 @@ void SystemStatusView::on_bias_tee() {
|
||||
}*/
|
||||
|
||||
void SystemStatusView::on_camera() {
|
||||
auto path = next_filename_stem_matching_pattern(u"SCR_????");
|
||||
ensure_directory("SCREENSHOTS");
|
||||
auto path = next_filename_matching_pattern(u"SCREENSHOTS/SCR_????.PNG");
|
||||
|
||||
if( path.empty() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
PNGWriter png;
|
||||
auto create_error = png.create(path.replace_extension(u".PNG"));
|
||||
auto create_error = png.create(path);
|
||||
if( create_error.is_valid() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -135,7 +135,7 @@ namespace ui
|
||||
Color::dark_grey()};
|
||||
|
||||
ImageButton button_back{
|
||||
{0, 0 * 16, 12 * 8, 16},//back button is long enough to cover the title area to make it easier to touch
|
||||
{0, 0 * 16, 12 * 8, 16}, // Back button also covers the title for easier touch.
|
||||
&bitmap_icon_previous,
|
||||
Color::white(),
|
||||
Color::dark_grey()};
|
||||
|
||||
@@ -55,16 +55,19 @@ namespace ui {
|
||||
|
||||
RecordView::RecordView(
|
||||
const Rect parent_rect,
|
||||
std::filesystem::path filename_stem_pattern,
|
||||
const std::filesystem::path& filename_stem_pattern,
|
||||
const std::filesystem::path& folder,
|
||||
const FileType file_type,
|
||||
const size_t write_size,
|
||||
const size_t buffer_count
|
||||
) : View { parent_rect },
|
||||
filename_stem_pattern { filename_stem_pattern },
|
||||
folder { folder },
|
||||
file_type { file_type },
|
||||
write_size { write_size },
|
||||
buffer_count { buffer_count }
|
||||
{
|
||||
ensure_directory(folder);
|
||||
add_children({
|
||||
&rect_background,
|
||||
//&button_pitch_rssi,
|
||||
@@ -154,7 +157,6 @@ void RecordView::start() {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
std::filesystem::path base_path;
|
||||
if(filename_date_frequency) {
|
||||
rtcGetTime(&RTCD1, &datetime);
|
||||
@@ -167,9 +169,11 @@ void RecordView::start() {
|
||||
to_string_dec_uint(datetime.minute()) +
|
||||
to_string_dec_uint(datetime.second());
|
||||
|
||||
base_path = filename_stem_pattern.string() + "_" + date_time + "_" + to_string_freq(receiver_model.tuning_frequency()) + "Hz";
|
||||
base_path = filename_stem_pattern.string() + "_" + date_time + "_" +
|
||||
trim(to_string_freq(receiver_model.tuning_frequency())) + "Hz";
|
||||
base_path = folder / base_path;
|
||||
} else {
|
||||
base_path = next_filename_stem_matching_pattern(filename_stem_pattern);
|
||||
base_path = next_filename_matching_pattern(folder / filename_stem_pattern);
|
||||
}
|
||||
|
||||
if( base_path.empty() ) {
|
||||
@@ -217,7 +221,7 @@ void RecordView::start() {
|
||||
};
|
||||
|
||||
if( writer ) {
|
||||
text_record_filename.set(base_path.replace_extension().string());
|
||||
text_record_filename.set(truncate(base_path.filename().string(), 8));
|
||||
button_record.set_bitmap(&bitmap_stop);
|
||||
capture_thread = std::make_unique<CaptureThread>(
|
||||
std::move(writer),
|
||||
@@ -275,7 +279,7 @@ void RecordView::on_tick_second() {
|
||||
void RecordView::update_status_display() {
|
||||
if( is_active() ) {
|
||||
const auto dropped_percent = std::min(99U, capture_thread->state().dropped_percent());
|
||||
const auto s = to_string_dec_uint(dropped_percent, 2, ' ') + "\%";
|
||||
const auto s = to_string_dec_uint(dropped_percent, 2, ' ') + "%";
|
||||
text_record_dropped.set(s);
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,8 @@ public:
|
||||
|
||||
RecordView(
|
||||
const Rect parent_rect,
|
||||
std::filesystem::path filename_stem_pattern,
|
||||
const std::filesystem::path& filename_stem_pattern,
|
||||
const std::filesystem::path& folder,
|
||||
FileType file_type,
|
||||
const size_t write_size,
|
||||
const size_t buffer_count
|
||||
@@ -83,6 +84,7 @@ private:
|
||||
rtc::RTC datetime { };
|
||||
|
||||
const std::filesystem::path filename_stem_pattern;
|
||||
const std::filesystem::path folder;
|
||||
const FileType file_type;
|
||||
const size_t write_size;
|
||||
const size_t buffer_count;
|
||||
|
||||
@@ -67,7 +67,7 @@ void SigGenProcessor::execute(const buffer_c8_t& buffer) {
|
||||
// 16 bits LFSR .taps: 16, 15, 13, 4 ;feedback polynomial: x^16 + x^15 + x^13 + x^4 + 1
|
||||
// Periode 65535= 2^n-1, quite continuous .
|
||||
if (counter == 0) { // we slow down the shift register, because the pseudo random noise clock freq was too high for modulator.
|
||||
bit_16 = ((lfsr_16 >> 0) ^ (lfsr_16 >> 1) ^ (lfsr_16 >> 3) ^ (lfsr_16 >> 4) ^ (lfsr_16 >> 12) & 1);
|
||||
bit_16 = ((lfsr_16 >> 0) ^ (lfsr_16 >> 1) ^ (lfsr_16 >> 3) ^ (lfsr_16 >> 4) ^ ((lfsr_16 >> 12) & 1) );
|
||||
lfsr_16 = (lfsr_16 >> 1) | (bit_16 << 15);
|
||||
sample = (lfsr_16 & 0x00FF); // main pseudo random noise generator.
|
||||
}
|
||||
|
||||
@@ -450,6 +450,59 @@ constexpr fir_taps_real<64> taps_64_lp_156_198 {
|
||||
} },
|
||||
};
|
||||
|
||||
// WFM 180kHZ General purpose filter with sharp transition , it improves Commercial WFM S/N in weak signals //////////////////////////////////////////////
|
||||
|
||||
// IFIR image-reject filter: fs=3072000, pass=90000, stop=250000, decim=4, fout=768000
|
||||
constexpr fir_taps_real<24> taps_180k_wfm_decim_0 = {
|
||||
.low_frequency_normalized = -90000.0f / 3072000.0f,
|
||||
.high_frequency_normalized = 90000.0f / 3072000.0f,
|
||||
.transition_normalized = 160000.0f / 3072000.0f,
|
||||
.taps = { {
|
||||
55, 122, 244, 424, 666, 965, 1308, 1669,
|
||||
2019, 2321, 2544, 2663, 2663, 2544, 2321, 2019,
|
||||
1669, 1308, 965, 666, 424, 244, 122, 55,
|
||||
} },
|
||||
};
|
||||
|
||||
// IFIR prototype filter: fs=768000, pass=90000, stop=110000, decim=2, fout=384000
|
||||
constexpr fir_taps_real<16> taps_180k_wfm_decim_1 = {
|
||||
.low_frequency_normalized = -90000.0f / 768000.0f,
|
||||
.high_frequency_normalized = 90000.0f / 768000.0f,
|
||||
.transition_normalized = 20000.0f / 768000.0f,
|
||||
.taps = { {
|
||||
55, 19, -356, -916, -529, 2139, 6695, 10392,
|
||||
10392, 6695, 2139, -529, -916, -356, 19, 55,
|
||||
} },
|
||||
};
|
||||
|
||||
|
||||
|
||||
// WFM 40kHZ filter for NOAA APT reception in 137Mhz band with sharp transition //////////////////////////////////////////////
|
||||
|
||||
// IFIR image-reject filter: fs=3072000, pass=20000, stop=97000, decim=4, fout=768000
|
||||
constexpr fir_taps_real<24> taps_40k_wfm_decim_0 = {
|
||||
.low_frequency_normalized = -20000.0f / 3072000.0f,
|
||||
.high_frequency_normalized = 20000.0f / 3072000.0f,
|
||||
.transition_normalized = 67000.0f / 3072000.0f,
|
||||
.taps = { {
|
||||
46, 112, 230, 408, 650, 953, 1301, 1671,
|
||||
2029, 2340, 2570, 2692, 2692, 2570, 2340, 2029,
|
||||
1671, 1301, 953, 650, 408, 230, 112, 46,
|
||||
} },
|
||||
};
|
||||
|
||||
// IFIR prototype filter: fs=768000, pass=20000, stop=55000, decim=2, fout=384000
|
||||
constexpr fir_taps_real<16> taps_40k_wfm_decim_1 = {
|
||||
.low_frequency_normalized = -20000.0f / 768000.0f,
|
||||
.high_frequency_normalized = 20000.0f / 768000.0f,
|
||||
.transition_normalized = 35000.0f / 768000.0f,
|
||||
.taps = { {
|
||||
83, 299, 743, 1456, 2396, 3418, 4297, 4808,
|
||||
4808, 4297, 3418, 2396, 1456, 743, 299, 83,
|
||||
} },
|
||||
};
|
||||
|
||||
|
||||
// TPMS decimation filters ////////////////////////////////////////////////
|
||||
|
||||
// IFIR image-reject filter: fs=2457600, pass=100000, stop=407200, decim=4, fout=614400
|
||||
|
||||
@@ -1143,9 +1143,18 @@ NewButton::NewButton(
|
||||
Rect parent_rect,
|
||||
std::string text,
|
||||
const Bitmap* bitmap
|
||||
) : NewButton { parent_rect, text, bitmap, Color::dark_cyan() }
|
||||
{ }
|
||||
|
||||
NewButton::NewButton(
|
||||
Rect parent_rect,
|
||||
std::string text,
|
||||
const Bitmap* bitmap,
|
||||
Color color
|
||||
) : Widget { parent_rect },
|
||||
text_ { text },
|
||||
bitmap_ (bitmap)
|
||||
bitmap_ { bitmap },
|
||||
color_ { color }
|
||||
{
|
||||
set_focusable(true);
|
||||
}
|
||||
@@ -1535,6 +1544,137 @@ bool OptionsField::on_touch(const TouchEvent event) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* TextField ***********************************************************/
|
||||
|
||||
TextField::TextField(
|
||||
std::string& str,
|
||||
size_t max_length,
|
||||
Point position,
|
||||
uint32_t length
|
||||
) : Widget{ { position, { 8 * static_cast<int>(length), 16 } } },
|
||||
text_{ str },
|
||||
max_length_{ std::max<size_t>(max_length, str.length()) },
|
||||
char_count_{ std::max<uint32_t>(length, 1) },
|
||||
cursor_pos_{ text_.length() },
|
||||
insert_mode_{ true }
|
||||
{
|
||||
set_focusable(true);
|
||||
}
|
||||
|
||||
const std::string& TextField::value() const {
|
||||
return text_;
|
||||
}
|
||||
|
||||
void TextField::set_cursor(uint32_t pos) {
|
||||
cursor_pos_ = std::min<size_t>(pos, text_.length());
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
void TextField::set_insert_mode() {
|
||||
insert_mode_ = true;
|
||||
}
|
||||
|
||||
void TextField::set_overwrite_mode() {
|
||||
insert_mode_ = false;
|
||||
}
|
||||
|
||||
void TextField::char_add(char c) {
|
||||
// Don't add if inserting and at max_length and
|
||||
// don't overwrite if past the end of the text.
|
||||
if ((text_.length() >= max_length_ && insert_mode_) ||
|
||||
(cursor_pos_ >= text_.length() && !insert_mode_))
|
||||
return;
|
||||
|
||||
if (insert_mode_)
|
||||
text_.insert(cursor_pos_, 1, c);
|
||||
else
|
||||
text_[cursor_pos_] = c;
|
||||
|
||||
cursor_pos_++;
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
void TextField::char_delete() {
|
||||
if (cursor_pos_ == 0)
|
||||
return;
|
||||
|
||||
cursor_pos_--;
|
||||
text_.erase(cursor_pos_, 1);
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
void TextField::paint(Painter& painter) {
|
||||
constexpr int char_width = 8;
|
||||
|
||||
auto rect = screen_rect();
|
||||
auto text_style = has_focus() ? style().invert() : style();
|
||||
auto offset = 0;
|
||||
|
||||
// Does the string need to be shifted?
|
||||
if (cursor_pos_ >= char_count_)
|
||||
offset = cursor_pos_ - char_count_ + 1;
|
||||
|
||||
// Clear the control.
|
||||
painter.fill_rectangle(rect, text_style.background);
|
||||
|
||||
// Draw the text starting at the offset.
|
||||
for (uint32_t i = 0; i < char_count_ && i + offset < text_.length(); i++) {
|
||||
painter.draw_char(
|
||||
{ rect.location().x() + (static_cast<int>(i) * char_width), rect.location().y() },
|
||||
text_style,
|
||||
text_[i + offset]
|
||||
);
|
||||
}
|
||||
|
||||
// Determine cursor position on screen (either the cursor position or the last char).
|
||||
int32_t cursor_x = char_width * (offset > 0 ? char_count_ - 1 : cursor_pos_);
|
||||
Point cursor_point{ screen_pos().x() + cursor_x, screen_pos().y() };
|
||||
auto cursor_style = text_style.invert();
|
||||
|
||||
// Invert the cursor character when in overwrite mode.
|
||||
if (!insert_mode_ && (cursor_pos_) < text_.length())
|
||||
painter.draw_char(cursor_point, cursor_style, text_[cursor_pos_]);
|
||||
|
||||
// Draw the cursor.
|
||||
Rect cursor_box{ cursor_point, { char_width, 16 } };
|
||||
painter.draw_rectangle(cursor_box, cursor_style.background);
|
||||
}
|
||||
|
||||
bool TextField::on_key(const KeyEvent key) {
|
||||
if (key == KeyEvent::Left && cursor_pos_ > 0)
|
||||
cursor_pos_--;
|
||||
else if (key == KeyEvent::Right && cursor_pos_ < text_.length())
|
||||
cursor_pos_++;
|
||||
else if (key == KeyEvent::Select)
|
||||
insert_mode_ = !insert_mode_;
|
||||
else
|
||||
return false;
|
||||
|
||||
set_dirty();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TextField::on_encoder(const EncoderEvent delta) {
|
||||
int32_t new_pos = cursor_pos_ + delta;
|
||||
|
||||
// Let the encoder wrap around the ends of the text.
|
||||
if (new_pos < 0)
|
||||
new_pos = text_.length();
|
||||
else if (static_cast<size_t>(new_pos) > text_.length())
|
||||
new_pos = 0;
|
||||
|
||||
set_cursor(new_pos);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TextField::on_touch(const TouchEvent event) {
|
||||
if (event.type == TouchEvent::Type::Start)
|
||||
focus();
|
||||
|
||||
set_dirty();
|
||||
return true;
|
||||
}
|
||||
|
||||
/* NumberField ***********************************************************/
|
||||
|
||||
NumberField::NumberField(
|
||||
@@ -1560,7 +1700,7 @@ int32_t NumberField::value() const {
|
||||
|
||||
void NumberField::set_value(int32_t new_value, bool trigger_change) {
|
||||
if (can_loop) {
|
||||
if (new_value >= 0)
|
||||
if (new_value >= range.first)
|
||||
new_value = new_value % (range.second + 1);
|
||||
else
|
||||
new_value = range.second + new_value + 1;
|
||||
|
||||
@@ -414,7 +414,6 @@ private:
|
||||
bool instant_exec_ { false };
|
||||
};
|
||||
|
||||
|
||||
class ButtonWithEncoder : public Widget {
|
||||
public:
|
||||
std::function<void(ButtonWithEncoder&)> on_select { };
|
||||
@@ -457,8 +456,6 @@ private:
|
||||
bool instant_exec_ { false };
|
||||
};
|
||||
|
||||
|
||||
|
||||
class NewButton : public Widget {
|
||||
public:
|
||||
std::function<void(void)> on_select { };
|
||||
@@ -469,6 +466,7 @@ public:
|
||||
NewButton(const NewButton&) = delete;
|
||||
NewButton& operator=(const NewButton&) = delete;
|
||||
NewButton(Rect parent_rect, std::string text, const Bitmap* bitmap);
|
||||
NewButton(Rect parent_rect, std::string text, const Bitmap* bitmap, Color color);
|
||||
NewButton(
|
||||
) : NewButton { { }, { }, { } }
|
||||
{
|
||||
@@ -489,8 +487,8 @@ public:
|
||||
|
||||
private:
|
||||
std::string text_;
|
||||
Color color_ = Color::dark_cyan();
|
||||
const Bitmap* bitmap_;
|
||||
Color color_;
|
||||
};
|
||||
|
||||
class Image : public Widget {
|
||||
@@ -610,6 +608,52 @@ private:
|
||||
size_t selected_index_ { 0 };
|
||||
};
|
||||
|
||||
// A TextField is bound to a string reference and allows the string
|
||||
// to be manipulated. The field itself does not provide the UI for
|
||||
// setting the value. It provides the UI of rendering the text,
|
||||
// a cursor, and an API to edit the string content.
|
||||
class TextField : public Widget {
|
||||
public:
|
||||
TextField(std::string& str, Point position, uint32_t length = 30)
|
||||
: TextField{str, 64, position, length} { }
|
||||
|
||||
// Str: the string containing the content to edit.
|
||||
// Max_length: max length the string is allowed to use.
|
||||
// Position: the top-left corner of the control.
|
||||
// Length: the number of characters to display.
|
||||
// - Characters are 8 pixels wide.
|
||||
// - The screen can show 30 characters max.
|
||||
// - The control is 16 pixels tall.
|
||||
TextField(std::string& str, size_t max_length, Point position, uint32_t length = 30);
|
||||
|
||||
TextField(const TextField&) = delete;
|
||||
TextField(TextField&&) = delete;
|
||||
TextField& operator=(const TextField&) = delete;
|
||||
TextField& operator=(TextField&&) = delete;
|
||||
|
||||
const std::string& value() const;
|
||||
|
||||
void set_cursor(uint32_t pos);
|
||||
void set_insert_mode();
|
||||
void set_overwrite_mode();
|
||||
|
||||
void char_add(char c);
|
||||
void char_delete();
|
||||
|
||||
void paint(Painter& painter) override;
|
||||
|
||||
bool on_key(const KeyEvent key) override;
|
||||
bool on_encoder(const EncoderEvent delta) override;
|
||||
bool on_touch(const TouchEvent event) override;
|
||||
|
||||
protected:
|
||||
std::string& text_;
|
||||
size_t max_length_;
|
||||
uint32_t char_count_;
|
||||
uint32_t cursor_pos_;
|
||||
bool insert_mode_;
|
||||
};
|
||||
|
||||
class NumberField : public Widget {
|
||||
public:
|
||||
std::function<void(NumberField&)> on_select { };
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 293 B |
Binary file not shown.
|
After Width: | Height: | Size: 286 B |
Binary file not shown.
|
After Width: | Height: | Size: 258 B |
Binary file not shown.
|
After Width: | Height: | Size: 253 B |
Binary file not shown.
|
After Width: | Height: | Size: 286 B |
Binary file not shown.
|
After Width: | Height: | Size: 231 B |
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,4 @@
|
||||
f=92500000,m=WFM,bw=200k,d=NRK Oslo
|
||||
f=94800000,m=WFM,bw=200k,d=NRK Bergen
|
||||
f=97200000,m=WFM,bw=200k,d=NRK Melhus
|
||||
f=98600000,m=WFM,bw=200k,d=NRK Arendal
|
||||
@@ -9,7 +9,6 @@ f=91300000,m=WFM,bw=200k,d=RMF Łobez
|
||||
f=91500000,m=WFM,bw=200k,d=RMF Ostroleka
|
||||
f=91700000,m=WFM,bw=200k,d=RMF Wagrowiec
|
||||
f=91900000,m=WFM,bw=200k,d=RMF Siedlce
|
||||
f=92500000,m=WFM,bw=200k,d=NRK Oslo
|
||||
f=92600000,m=WFM,bw=200k,d=RMF Elbląg
|
||||
f=92900000,m=WFM,bw=200k,d=RMF Wroclaw
|
||||
f=93000000,m=WFM,bw=200k,d=RMF Katowice
|
||||
@@ -19,7 +18,6 @@ f=93600000,m=WFM,bw=200k,d=RMF Ryki
|
||||
f=93800000,m=WFM,bw=200k,d=RMF Luban
|
||||
f=94300000,m=WFM,bw=200k,d=RMF Płock
|
||||
f=94600000,m=WFM,bw=200k,d=RMF Poznan
|
||||
f=94800000,m=WFM,bw=200k,d=NRK Bergen
|
||||
f=94800000,m=WFM,bw=200k,d=RMF Zagan
|
||||
f=95100000,m=WFM,bw=200k,d=RMF Suwałki
|
||||
f=95300000,m=WFM,bw=200k,d=RMF Olsztyn
|
||||
@@ -30,10 +28,8 @@ f=96100000,m=WFM,bw=200k,d=RMF Gorzow Wielkopolski
|
||||
f=96400000,m=WFM,bw=200k,d=RMF Bialogard
|
||||
f=96600000,m=WFM,bw=200k,d=RMF Walcz
|
||||
f=97100000,m=WFM,bw=200k,d=RMF Włoszczowa
|
||||
f=97200000,m=WFM,bw=200k,d=NRK Melhus
|
||||
f=98000000,m=WFM,bw=200k,d=RMF Kalisz
|
||||
f=98400000,m=WFM,bw=200k,d=RMF Gdansk
|
||||
f=98600000,m=WFM,bw=200k,d=NRK Arendal
|
||||
f=98900000,m=WFM,bw=200k,d=RMF Konin
|
||||
f=99300000,m=WFM,bw=200k,d=RMF Opole
|
||||
f=99500000,m=WFM,bw=200k,d=RMF Kluczbork
|
||||
+15
-15
@@ -1,6 +1,6 @@
|
||||
a=150000,b=285000,m=AM,bw=DSB,s=5KHz,d=Longwave broadcast band|BCB (EU)
|
||||
a=525000,b=1605000,m=AM,bw=DSB,s=5KHz,d=AM broadcast band|BCB (EU & J)
|
||||
a=530000,b=1710000,m=AM,bw=DSB,s=5KHz,d=AM broadcast band|BCB (US)
|
||||
a=150000,b=285000,m=AM,bw=DSB,s=5kHz,d=Longwave broadcast band|BCB (EU)
|
||||
a=525000,b=1605000,m=AM,bw=DSB,s=5kHz,d=AM broadcast band|BCB (EU & J)
|
||||
a=530000,b=1710000,m=AM,bw=DSB,s=5kHz,d=AM broadcast band|BCB (US)
|
||||
a=1800000,b=29700000,m=WFM,bw=16k,d=Amateur_radio|Amateur
|
||||
a=26900000,b=27400000,m=WFM,bw=16k,d=Citizens_band_radio|Citizens band
|
||||
a=28000000,b=30000000,m=WFM,bw=16k,d=Amateur_radio|Amateur
|
||||
@@ -8,15 +8,15 @@ a=29000000,b=54000000,m=WFM,bw=16k,d=Land mobile
|
||||
a=50000000,b=54000000,m=WFM,bw=16k,d=Amateur_radio|Amateur
|
||||
a=65000000,b=85000000,m=WFM,bw=16k,d=Land mobile (EU)
|
||||
a=108000000,b=136000000,m=WFM,bw=16k,d=Aircraft
|
||||
a=120000000,b=160000000,m=WFM,bw=16k,s=50KHz,d=Land mobile (EU)
|
||||
a=132000000,b=174000000,m=WFM,bw=16k,s=50KHz,d=Land mobile
|
||||
a=142000000,b=170000000,m=WFM,bw=16k,s=50KHz,d=Land mobile (J)
|
||||
a=144000000,b=148000000,m=WFM,bw=16k,s=50KHz,d=Amateur_radio|Amateur
|
||||
a=216000000,b=222000000,m=WFM,bw=16k,s=50KHz,d=Land mobile
|
||||
a=222000000,b=225000000,m=WFM,bw=16k,s=50KHz,d=Amateur_radio|Amateur
|
||||
a=335000000,b=384000000,m=WFM,bw=16k,s=50KHz,d=Land mobile (J)
|
||||
a=406000000,b=512000000,m=WFM,bw=16k,s=50KHz,d=Land mobile
|
||||
a=450000000,b=470000000,m=WFM,bw=16k,s=50KHz,d=Land mobile (J)
|
||||
a=430000000,b=450000000,m=WFM,bw=16k,s=50KHz,d=Amateur_radio|Amateur
|
||||
a=806000000,b=947000000,m=WFM,bw=16k,s=50KHz,d=Land mobile
|
||||
a=1200000000,b=1600000000,m=WFM,bw=16k,s=50KHz,d=Amateur|Land mobile|GPS
|
||||
a=120000000,b=160000000,m=WFM,bw=16k,s=50kHz,d=Land mobile (EU)
|
||||
a=132000000,b=174000000,m=WFM,bw=16k,s=50kHz,d=Land mobile
|
||||
a=142000000,b=170000000,m=WFM,bw=16k,s=50kHz,d=Land mobile (J)
|
||||
a=144000000,b=148000000,m=WFM,bw=16k,s=50kHz,d=Amateur_radio|Amateur
|
||||
a=216000000,b=222000000,m=WFM,bw=16k,s=50kHz,d=Land mobile
|
||||
a=222000000,b=225000000,m=WFM,bw=16k,s=50kHz,d=Amateur_radio|Amateur
|
||||
a=335000000,b=384000000,m=WFM,bw=16k,s=50kHz,d=Land mobile (J)
|
||||
a=406000000,b=512000000,m=WFM,bw=16k,s=50kHz,d=Land mobile
|
||||
a=450000000,b=470000000,m=WFM,bw=16k,s=50kHz,d=Land mobile (J)
|
||||
a=430000000,b=450000000,m=WFM,bw=16k,s=50kHz,d=Amateur_radio|Amateur
|
||||
a=806000000,b=947000000,m=WFM,bw=16k,s=50kHz,d=Land mobile
|
||||
a=1200000000,b=1600000000,m=WFM,bw=16k,s=50kHz,d=Amateur|Land mobile|GPS
|
||||
|
||||
@@ -2,21 +2,50 @@
|
||||
# (Freq range in MHz, min separation 3MHz)
|
||||
# (Description up to 20 char)
|
||||
# (fields comma delimiter)
|
||||
140,380,VHF MICS AND MARINE
|
||||
260,500,315/433 MHz KEYFOBS
|
||||
390,420,RADIOSONDES
|
||||
420,660,UHF MICS
|
||||
# WATER METERS
|
||||
850,900,Water meters
|
||||
902,928,ISM 900MHz
|
||||
# PAGERS https://www.fcc.gov/wireless/bureau-divisions/mobility-division/paging
|
||||
34,36,USA PAGERS 35
|
||||
43,45,USA PAGERS 44
|
||||
151,153,USA PAGERS 152
|
||||
453,454,USA PAGERS 454
|
||||
929,931,USA PAGERS 930
|
||||
# KEYFOBS
|
||||
260,500,315/433 MHz KEYFOBS
|
||||
# MICS, RADIOSONDES, LEYFOB
|
||||
140,380,VHF MICS AND MARINE
|
||||
390,420,RADIOSONDES
|
||||
420,660,UHF MICS
|
||||
# LoRa
|
||||
433,435,LoRa 443EU
|
||||
863,870,LoRa 868EU
|
||||
902,928,LoRa 915US
|
||||
779,787,LoRa 780CN
|
||||
# BROADCAST RADIO
|
||||
65,74,FM BROADCAST RUSSIA
|
||||
76,95,FM BROADCAST JAPAN
|
||||
76,108,FM BROADCAST BRAZIL
|
||||
87,108,FM BROADCAST
|
||||
# AVIATION
|
||||
108,137,AVIATION 108
|
||||
960,1215,AVIATION 960
|
||||
# HAM RADIO
|
||||
28,30,HAM 10-METER
|
||||
50,54,HAM 6-METER
|
||||
144,148,HAM 2-METER
|
||||
219,225,HAM 1.25-METER
|
||||
420,450,HAM 70-CM
|
||||
902,928,HAM 33-CM
|
||||
1240,1300,HAM 23-CM
|
||||
# WATER METERS
|
||||
850,900,Water meters
|
||||
902,928,ISM 900MHz
|
||||
# WIFI
|
||||
2320,2560,BL / WIFI 2.4GHz
|
||||
5160,5900,WIFI 5GHz
|
||||
# ITU-DEFINED BANDS
|
||||
1,3,MF BAND
|
||||
3,30,HF BAND
|
||||
30,300,VHF BAND
|
||||
300,3000,UHF BAND
|
||||
3000,7250,SHF BAND
|
||||
# FULL
|
||||
10,7250,FULL RANGE
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,2 @@
|
||||
sample_rate=500000
|
||||
center_frequency=433920000
|
||||
Binary file not shown.
@@ -0,0 +1,2 @@
|
||||
sample_rate=500000
|
||||
center_frequency=433920000
|
||||
Binary file not shown.
@@ -0,0 +1,2 @@
|
||||
sample_rate=500000
|
||||
center_frequency=433920000
|
||||
Binary file not shown.
@@ -0,0 +1,2 @@
|
||||
sample_rate=500000
|
||||
center_frequency=433920000
|
||||
Binary file not shown.
@@ -0,0 +1,2 @@
|
||||
sample_rate=500000
|
||||
center_frequency=433920000
|
||||
Binary file not shown.
@@ -0,0 +1,2 @@
|
||||
sample_rate=500000
|
||||
center_frequency=433920000
|
||||
Binary file not shown.
@@ -0,0 +1,2 @@
|
||||
sample_rate=500000
|
||||
center_frequency=433920000
|
||||
Binary file not shown.
@@ -0,0 +1,2 @@
|
||||
sample_rate=500000
|
||||
center_frequency=433920000
|
||||
Binary file not shown.
@@ -0,0 +1,2 @@
|
||||
sample_rate=500000
|
||||
center_frequency=433920000
|
||||
Binary file not shown.
@@ -0,0 +1,2 @@
|
||||
sample_rate=500000
|
||||
center_frequency=433920000
|
||||
Binary file not shown.
@@ -0,0 +1,2 @@
|
||||
sample_rate=500000
|
||||
center_frequency=433920000
|
||||
Binary file not shown.
@@ -0,0 +1,2 @@
|
||||
sample_rate=500000
|
||||
center_frequency=433920000
|
||||
Binary file not shown.
@@ -0,0 +1,2 @@
|
||||
sample_rate=500000
|
||||
center_frequency=433920000
|
||||
Binary file not shown.
@@ -0,0 +1,2 @@
|
||||
sample_rate=500000
|
||||
center_frequency=433920000
|
||||
Binary file not shown.
@@ -0,0 +1,2 @@
|
||||
sample_rate=500000
|
||||
center_frequency=433920000
|
||||
Binary file not shown.
@@ -0,0 +1,2 @@
|
||||
sample_rate=500000
|
||||
center_frequency=433920000
|
||||
@@ -0,0 +1,11 @@
|
||||
# Aurora RGB
|
||||
|
||||
More info: [Aurora](https://www.aurora-gaming.pl/portfolio-item/passion-v5/)
|
||||
|
||||
Captured by: [nnesetto](https://github.com/nnesetto)
|
||||
|
||||
The controller operates at the frequency of 433.92 MHz.
|
||||
|
||||

|
||||
|
||||

|
||||
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user