mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-09-14 10:39:30 +00:00
Compare commits
105 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6bb2a3559d | |||
| 1a829707f4 | |||
| 22430fb8d4 | |||
| acc20844cd | |||
| e694e73a75 | |||
| b4da86d491 | |||
| a27881ecd6 | |||
| 9ce2f3fdea | |||
| 2a4c49c38c | |||
| 415d8cfa54 | |||
| a8cdde7bea | |||
| 8b395239d2 | |||
| 730e7ad72b | |||
| 1e4ea753ba | |||
| a86993af91 | |||
| a832bd433d | |||
| f9fdeb3419 | |||
| 3c2fc74f82 | |||
| 69927e3c2d | |||
| 1edd3716a1 | |||
| bc1ad5598c | |||
| 8cae998146 | |||
| 9a22a760ad | |||
| 909b00bdae | |||
| 58e731c56e | |||
| 04e77e0ee8 | |||
| 55b93a7044 | |||
| f22046eccc | |||
| dfadd38e32 | |||
| 3cb927c840 | |||
| 8ba5715db0 | |||
| c7e8506ad0 | |||
| 8cb0a91bb5 | |||
| 1bcbefeb96 | |||
| 31031edbd1 | |||
| 7116f92d07 | |||
| df8e79f9e6 | |||
| e80e4e3bfd | |||
| da6c6bb03c | |||
| 75718c79b9 | |||
| 9b263def37 | |||
| c7d88da1a6 | |||
| 7b6f8b271c | |||
| 1b18b3ac45 | |||
| 68fc2a143f | |||
| 1deebaff09 | |||
| d6118c9fc4 | |||
| 44a62aef21 | |||
| 0742fc169d | |||
| 62859f901f | |||
| c6316f5aa6 | |||
| 5f2043d229 | |||
| 074658c4bb | |||
| d641ae5b47 | |||
| bec70b4ee5 | |||
| c4373d1560 | |||
| 1dae2c0d25 | |||
| 658d0c9b3a | |||
| 2f343adf21 | |||
| f7a5f2c437 | |||
| 5a336d5e71 | |||
| 91675e26cb | |||
| 078da8ca16 | |||
| 77260bc68a | |||
| ca143f9788 | |||
| 3fc23354ce | |||
| d48e25167f | |||
| cfe5a6bfe4 | |||
| d77102426a | |||
| 11f4edc892 | |||
| 804fa0d3c4 | |||
| d5f20c45b9 | |||
| 2cba96ff36 | |||
| 7139abb947 | |||
| bf4ed416bd | |||
| 06643df6a5 | |||
| 81752c8d78 | |||
| 0cb8c9200a | |||
| 8e64221384 | |||
| fe1837ec23 | |||
| ee53b28e60 | |||
| 18c986cd76 | |||
| a1c3cbcea9 | |||
| 1135a42932 | |||
| a74d0e5167 | |||
| c5579e25fd | |||
| e8b8f0ca5c | |||
| 98e7116230 | |||
| c916eaf43f | |||
| e3169a3495 | |||
| 4b37f1bb2f | |||
| cb93f2636e | |||
| 596c304b08 | |||
| 63f7be02b6 | |||
| de99afa19b | |||
| 5c4e27ea29 | |||
| 54e4230191 | |||
| f069383c2a | |||
| d3a356d415 | |||
| f0e4fdcbcc | |||
| d5f6f42fae | |||
| 7a89858cc6 | |||
| 15c9a76536 | |||
| d7359a8cd5 | |||
| 50e5bc60ee |
@@ -0,0 +1,7 @@
|
||||
---
|
||||
BasedOnStyle: Chromium
|
||||
IndentWidth: '2'
|
||||
TabWidth: '2'
|
||||
UseTab: Always
|
||||
|
||||
...
|
||||
@@ -0,0 +1,18 @@
|
||||
name: Check formatting
|
||||
|
||||
on: #[push, pull_request] To be enabled once formatting is fixed in repo
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
check_date:
|
||||
runs-on: ubuntu-latest
|
||||
name: Check formatting
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: print latest_commit
|
||||
run: echo ${{ github.sha }}
|
||||
- name: clang-format Check
|
||||
uses: jidicula/clang-format-action@v4.11.0
|
||||
with:
|
||||
check-path: "firmware"
|
||||
fallback-style: Chromium
|
||||
@@ -1 +1 @@
|
||||
v1.5.4
|
||||
v1.6.0
|
||||
|
||||
@@ -1 +1 @@
|
||||
v1.6.0
|
||||
v1.7.0
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -281,6 +281,9 @@ set(CPPSRC
|
||||
apps/ui_level.cpp
|
||||
apps/tpms_app.cpp
|
||||
apps/tpms_app.cpp
|
||||
apps/ui_spectrum_painter.cpp
|
||||
apps/ui_spectrum_painter_image.cpp
|
||||
apps/ui_spectrum_painter_text.cpp
|
||||
protocols/aprs.cpp
|
||||
protocols/ax25.cpp
|
||||
protocols/bht.cpp
|
||||
|
||||
@@ -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 },
|
||||
4,
|
||||
{ // 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
|
||||
|
||||
@@ -29,6 +29,23 @@
|
||||
#include "ui_record_view.hpp"
|
||||
#include "ui_spectrum.hpp"
|
||||
|
||||
#define BW_OPTIONS \
|
||||
{ " 8k5", 8500 }, \
|
||||
{ " 11k", 11000 }, \
|
||||
{ " 16k", 16000 }, \
|
||||
{ " 25k", 25000 }, \
|
||||
{ " 50k", 50000 }, \
|
||||
{ " 100k", 100000 }, \
|
||||
{ " 250k", 250000 }, \
|
||||
{ " 500k", 500000 }, /* Previous Limit bandwith Option with perfect micro SD write .C16 format operaton.*/ \
|
||||
{ " 600k", 600000 }, /* That extended option is still possible to record with FW version Mayhem v1.41 (< 2,5MB/sec) */ \
|
||||
{ " 750k", 750000 }, /* From that BW onwards, the LCD is ok, but the recorded file is auto decimated,(not real file size) */ \
|
||||
{ "1100k", 1100000 }, \
|
||||
{ "1750k", 1750000 }, \
|
||||
{ "2000k", 2000000 }, \
|
||||
{ "2500k", 2500000 }, \
|
||||
{ "2750k", 2750000 } // That is our max Capture option , to keep using later / 8 decimation (22Mhz sampling ADC)
|
||||
|
||||
namespace ui {
|
||||
|
||||
class CaptureAppView : public View {
|
||||
@@ -88,27 +105,13 @@ private:
|
||||
{ 5 * 8, 1 * 16 },
|
||||
5,
|
||||
{
|
||||
{ " 8k5", 8500 },
|
||||
{ " 11k ", 11000 },
|
||||
{ " 16k ", 16000 },
|
||||
{ " 25k ", 25000 },
|
||||
{ " 50k ", 50000 },
|
||||
{ "100k ", 100000 },
|
||||
{ "250k ", 250000 },
|
||||
{ "500k ", 500000 }, // Previous Limit bandwith Option with perfect micro SD write .C16 format operaton.
|
||||
{ "600k ", 600000 }, // That extended option is still possible to record with FW version Mayhem v1.41 (< 2,5MB/sec)
|
||||
{ "750k ", 750000 }, // From that BW onwards, the LCD is ok, but the recorded file is auto decimated,(not real file size)
|
||||
{ "1100k", 1100000 },
|
||||
{ "1750k", 1750000 },
|
||||
{ "2000k", 2000000 },
|
||||
{ "2500k", 2500000 },
|
||||
{ "2750k", 2750000 } // That is our max Capture option , to keep using later / 8 decimation (22Mhz sampling ADC)
|
||||
BW_OPTIONS
|
||||
}
|
||||
};
|
||||
|
||||
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 }
|
||||
|
||||
@@ -206,7 +206,7 @@ void SoundBoardView::refresh_list() {
|
||||
file_list[n].string().substr(0, 30),
|
||||
ui::Color::white(),
|
||||
nullptr,
|
||||
[this](){
|
||||
[this](KeyEvent){
|
||||
on_select_entry();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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 },
|
||||
};
|
||||
|
||||
Checkbox check_log {
|
||||
{ 0 * 8, 1 * 16 },
|
||||
3,
|
||||
"LOG",
|
||||
false
|
||||
};
|
||||
|
||||
Text text_debug {
|
||||
{ 0 * 8, 1 * 16, 10 * 8, 16 },
|
||||
{ 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 }
|
||||
};
|
||||
|
||||
@@ -35,7 +35,8 @@ DfuMenu::DfuMenu(NavigationView& nav) : nav_ (nav) {
|
||||
&text_info_line_4,
|
||||
&text_info_line_5,
|
||||
&text_info_line_6,
|
||||
&text_info_line_7
|
||||
&text_info_line_7,
|
||||
&text_info_line_8
|
||||
});
|
||||
}
|
||||
|
||||
@@ -48,14 +49,16 @@ void DfuMenu::paint(Painter& painter) {
|
||||
text_info_line_4.set(to_string_dec_uint(shared_memory.m4_heap_usage, 6));
|
||||
text_info_line_5.set(to_string_dec_uint(shared_memory.m4_stack_usage, 6));
|
||||
text_info_line_6.set(to_string_dec_uint(shared_memory.m4_cpu_usage, 6));
|
||||
text_info_line_7.set(to_string_dec_uint(chTimeNow()/1000, 6));
|
||||
text_info_line_7.set(to_string_dec_uint(shared_memory.m4_buffer_missed, 6));
|
||||
text_info_line_8.set(to_string_dec_uint(chTimeNow()/1000, 6));
|
||||
|
||||
constexpr auto margin = 5;
|
||||
constexpr auto lines = 8 + 2;
|
||||
|
||||
painter.fill_rectangle(
|
||||
{
|
||||
{6 * CHARACTER_WIDTH - margin, 3 * LINE_HEIGHT - margin},
|
||||
{15 * CHARACTER_WIDTH + margin * 2, 9 * LINE_HEIGHT + margin * 2}
|
||||
{15 * CHARACTER_WIDTH + margin * 2, lines * LINE_HEIGHT + margin * 2}
|
||||
},
|
||||
ui::Color::black()
|
||||
);
|
||||
@@ -63,7 +66,7 @@ void DfuMenu::paint(Painter& painter) {
|
||||
painter.fill_rectangle(
|
||||
{
|
||||
{5 * CHARACTER_WIDTH - margin, 3 * LINE_HEIGHT - margin},
|
||||
{CHARACTER_WIDTH, 9 * LINE_HEIGHT + margin * 2}
|
||||
{CHARACTER_WIDTH, lines * LINE_HEIGHT + margin * 2}
|
||||
},
|
||||
ui::Color::dark_cyan()
|
||||
);
|
||||
@@ -71,7 +74,7 @@ void DfuMenu::paint(Painter& painter) {
|
||||
painter.fill_rectangle(
|
||||
{
|
||||
{21 * CHARACTER_WIDTH + margin, 3 * LINE_HEIGHT - margin},
|
||||
{CHARACTER_WIDTH, 9 * LINE_HEIGHT + margin * 2}
|
||||
{CHARACTER_WIDTH, lines * LINE_HEIGHT + margin * 2}
|
||||
},
|
||||
ui::Color::dark_cyan()
|
||||
);
|
||||
@@ -86,7 +89,7 @@ void DfuMenu::paint(Painter& painter) {
|
||||
|
||||
painter.fill_rectangle(
|
||||
{
|
||||
{5 * CHARACTER_WIDTH - margin, 12 * LINE_HEIGHT + margin},
|
||||
{5 * CHARACTER_WIDTH - margin, (lines+3) * LINE_HEIGHT + margin},
|
||||
{17 * CHARACTER_WIDTH + margin * 2, 8}
|
||||
},
|
||||
ui::Color::dark_cyan()
|
||||
|
||||
@@ -54,7 +54,8 @@ private:
|
||||
{ { 6 * CHARACTER_WIDTH, 8 * LINE_HEIGHT }, "M4 heap:", Color::dark_cyan() },
|
||||
{ { 6 * CHARACTER_WIDTH, 9 * LINE_HEIGHT }, "M4 stack:", Color::dark_cyan() },
|
||||
{ { 6 * CHARACTER_WIDTH,10 * LINE_HEIGHT }, "M4 cpu %:", Color::dark_cyan() },
|
||||
{ { 6 * CHARACTER_WIDTH,11 * LINE_HEIGHT }, "uptime:", Color::dark_cyan() }
|
||||
{ { 6 * CHARACTER_WIDTH,11 * LINE_HEIGHT }, "M4 miss:", Color::dark_cyan() },
|
||||
{ { 6 * CHARACTER_WIDTH,12 * LINE_HEIGHT }, "uptime:", Color::dark_cyan() }
|
||||
};
|
||||
|
||||
Text text_info_line_1 {{ 15 * CHARACTER_WIDTH, 5 * LINE_HEIGHT, 5 * CHARACTER_WIDTH, 1 * LINE_HEIGHT }, ""};
|
||||
@@ -64,6 +65,7 @@ private:
|
||||
Text text_info_line_5 {{ 15 * CHARACTER_WIDTH, 9 * LINE_HEIGHT, 5 * CHARACTER_WIDTH, 1 * LINE_HEIGHT }, ""};
|
||||
Text text_info_line_6 {{ 15 * CHARACTER_WIDTH,10 * LINE_HEIGHT, 5 * CHARACTER_WIDTH, 1 * LINE_HEIGHT }, ""};
|
||||
Text text_info_line_7 {{ 15 * CHARACTER_WIDTH,11 * LINE_HEIGHT, 5 * CHARACTER_WIDTH, 1 * LINE_HEIGHT }, ""};
|
||||
Text text_info_line_8 {{ 15 * CHARACTER_WIDTH,12 * LINE_HEIGHT, 5 * CHARACTER_WIDTH, 1 * LINE_HEIGHT }, ""};
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -20,83 +20,199 @@
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
/* TODO:
|
||||
* - Paging menu items
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#include "ui_fileman.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "portapack.hpp"
|
||||
#include "event_m0.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace {
|
||||
using namespace ui;
|
||||
|
||||
bool is_hidden_file(const fs::path& path) {
|
||||
return !path.empty() && path.native()[0] == u'.';
|
||||
}
|
||||
|
||||
// Gets a truncated name from a path for display.
|
||||
std::string truncate(const fs::path& path, size_t max_length) {
|
||||
return ::truncate(path.string(), max_length);
|
||||
}
|
||||
|
||||
// Gets a human readable file size string.
|
||||
std::string get_pretty_size(uint32_t file_size) {
|
||||
static const std::string suffix[5] = { "B", "kB", "MB", "GB", "??" };
|
||||
size_t suffix_index = 0;
|
||||
|
||||
while (file_size >= 1024) {
|
||||
file_size /= 1024;
|
||||
suffix_index++;
|
||||
}
|
||||
|
||||
if (suffix_index > 4)
|
||||
suffix_index = 4;
|
||||
|
||||
return to_string_dec_uint(file_size) + suffix[suffix_index];
|
||||
}
|
||||
|
||||
// Case insensitive path equality on underlying "native" string.
|
||||
bool iequal(
|
||||
const fs::path& lhs,
|
||||
const fs::path& rhs
|
||||
) {
|
||||
const auto& lhs_str = lhs.native();
|
||||
const auto& rhs_str = rhs.native();
|
||||
|
||||
// NB: Not correct for Unicode/locales.
|
||||
if (lhs_str.length() == rhs_str.length()) {
|
||||
for (size_t i = 0; i < lhs_str.length(); ++i)
|
||||
if (towupper(lhs_str[i]) != towupper(rhs_str[i]))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Inserts the entry into the entry list sorted directories first then by file name.
|
||||
void insert_sorted(std::vector<fileman_entry>& entries, fileman_entry&& entry) {
|
||||
auto it = std::lower_bound(std::begin(entries), std::end(entries), entry,
|
||||
[](const fileman_entry& lhs, const fileman_entry& rhs) {
|
||||
if (lhs.is_directory && !rhs.is_directory)
|
||||
return true;
|
||||
else if (!lhs.is_directory && rhs.is_directory)
|
||||
return false;
|
||||
else
|
||||
return lhs.path < rhs.path;
|
||||
});
|
||||
|
||||
entries.insert(it, std::move(entry));
|
||||
}
|
||||
|
||||
// Returns the partner file path or an empty path if no partner is found.
|
||||
fs::path get_partner_file(fs::path path) {
|
||||
if (fs::is_directory(path))
|
||||
return { };
|
||||
|
||||
const fs::path txt_path{ u".TXT" };
|
||||
const fs::path c16_path{ u".C16" };
|
||||
auto ext = path.extension();
|
||||
|
||||
if (iequal(ext, txt_path))
|
||||
ext = c16_path;
|
||||
else if (iequal(ext, c16_path))
|
||||
ext = txt_path;
|
||||
else
|
||||
return { };
|
||||
|
||||
path.replace_extension(ext);
|
||||
return fs::file_exists(path) && !fs::is_directory(path) ? path : fs::path{ };
|
||||
}
|
||||
|
||||
// Modal prompt to update the partner file if it exists.
|
||||
// Runs continuation on_partner_action to update the partner file.
|
||||
// Returns true is a partner is found, otherwise false.
|
||||
// Path must be the full path to the file.
|
||||
bool partner_file_prompt(
|
||||
NavigationView& nav,
|
||||
const fs::path& path,
|
||||
std::string action_name,
|
||||
std::function<void(const fs::path&, bool)> on_partner_action
|
||||
) {
|
||||
auto partner = get_partner_file(path);
|
||||
|
||||
if (partner.empty())
|
||||
return false;
|
||||
|
||||
nav.push_under_current<ModalMessageView>(
|
||||
"Partner File",
|
||||
partner.filename().string() + "\n" + action_name + " this file too?",
|
||||
YESNO,
|
||||
[&nav, partner, on_partner_action](bool choice) {
|
||||
if (on_partner_action)
|
||||
on_partner_action(partner, choice);
|
||||
}
|
||||
);
|
||||
|
||||
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 {
|
||||
|
||||
void FileManBaseView::load_directory_contents(const std::filesystem::path& dir_path) {
|
||||
/* FileManBaseView ***********************************************************/
|
||||
|
||||
void FileManBaseView::load_directory_contents(const fs::path& dir_path) {
|
||||
current_path = dir_path;
|
||||
|
||||
text_current.set(dir_path.string().length()? dir_path.string().substr(0, 30 - 6):"(sd root)");
|
||||
|
||||
entry_list.clear();
|
||||
|
||||
auto filtering = (bool)extension_filter.size();
|
||||
|
||||
// List directories and files, put directories up top
|
||||
if (dir_path.string().length())
|
||||
entry_list.push_back({ u"..", 0, true });
|
||||
auto filtering = !extension_filter.empty();
|
||||
|
||||
for (const auto& entry : std::filesystem::directory_iterator(dir_path, u"*")) {
|
||||
text_current.set(dir_path.empty() ? "(sd root)" : truncate(dir_path, 24));
|
||||
|
||||
for (const auto& entry : fs::directory_iterator(dir_path, u"*")) {
|
||||
// Hide files starting with '.' (hidden / tmp).
|
||||
if (is_hidden_file(entry.path()))
|
||||
continue;
|
||||
|
||||
// do not display dir / files starting with '.' (hidden / tmp)
|
||||
if (entry.path().string().length() && entry.path().filename().string()[0] != '.') {
|
||||
if (std::filesystem::is_regular_file(entry.status())) {
|
||||
bool matched = true;
|
||||
if (filtering) {
|
||||
auto entry_extension = entry.path().extension().string();
|
||||
|
||||
for (auto &c: entry_extension)
|
||||
c = toupper(c);
|
||||
|
||||
if (entry_extension != extension_filter)
|
||||
matched = false;
|
||||
}
|
||||
|
||||
if (matched)
|
||||
entry_list.push_back({ entry.path(), (uint32_t)entry.size(), false });
|
||||
} else if (std::filesystem::is_directory(entry.status())) {
|
||||
entry_list.insert(entry_list.begin(), { entry.path(), 0, true });
|
||||
}
|
||||
if (fs::is_regular_file(entry.status())) {
|
||||
if (!filtering || iequal(entry.path().extension(), extension_filter))
|
||||
insert_sorted(entry_list, { entry.path(), (uint32_t)entry.size(), false });
|
||||
} else if (fs::is_directory(entry.status())) {
|
||||
insert_sorted(entry_list, { entry.path(), 0, true });
|
||||
}
|
||||
}
|
||||
|
||||
// Add "parent" directory if not at the root.
|
||||
if (!dir_path.empty())
|
||||
entry_list.insert(entry_list.begin(), { parent_dir_path, 0, true });
|
||||
}
|
||||
|
||||
std::filesystem::path FileManBaseView::get_selected_path() {
|
||||
auto selected_path_str = current_path.string();
|
||||
auto entry_path = entry_list[menu_view.highlighted_index()].entry_path.string();
|
||||
|
||||
if (entry_path == "..") {
|
||||
selected_path_str = get_parent_dir().string();
|
||||
} else {
|
||||
if (selected_path_str.back() != '/')
|
||||
selected_path_str += '/';
|
||||
|
||||
selected_path_str += entry_path;
|
||||
}
|
||||
|
||||
return selected_path_str;
|
||||
fs::path FileManBaseView::get_selected_full_path() const {
|
||||
if (get_selected_entry().path == parent_dir_path)
|
||||
return current_path.parent_path();
|
||||
|
||||
return current_path / get_selected_entry().path;
|
||||
}
|
||||
|
||||
std::filesystem::path FileManBaseView::get_parent_dir() {
|
||||
auto current_path_str = current_path.string();
|
||||
return current_path.string().substr(0, current_path_str.find_last_of('/'));
|
||||
const fileman_entry& FileManBaseView::get_selected_entry() const {
|
||||
// TODO: return reference to an "empty" entry on OOB?
|
||||
return entry_list[menu_view.highlighted_index()];
|
||||
}
|
||||
|
||||
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
|
||||
});
|
||||
|
||||
@@ -105,121 +221,117 @@ FileManBaseView::FileManBaseView(
|
||||
};
|
||||
|
||||
if (!sdcIsCardInserted(&SDCD1)) {
|
||||
empty_root=true;
|
||||
empty_ = EmptyReason::NoSDC;
|
||||
text_current.set("NO SD CARD!");
|
||||
return;
|
||||
}
|
||||
|
||||
load_directory_contents(current_path);
|
||||
|
||||
if (!entry_list.size()) {
|
||||
empty_ = EmptyReason::NoFiles;
|
||||
text_current.set("EMPTY SD CARD!");
|
||||
} else {
|
||||
load_directory_contents(current_path);
|
||||
if (!entry_list.size())
|
||||
{
|
||||
empty_root = true;
|
||||
text_current.set("EMPTY SD CARD!");
|
||||
} else {
|
||||
menu_view.on_left = [&nav, this]() {
|
||||
load_directory_contents(get_parent_dir());
|
||||
refresh_list();
|
||||
};
|
||||
}
|
||||
menu_view.on_left = [this]() {
|
||||
pop_dir();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
void FileManBaseView::focus() {
|
||||
if (empty_root) {
|
||||
if (empty_ != EmptyReason::NotEmpty) {
|
||||
button_exit.focus();
|
||||
} else {
|
||||
menu_view.focus();
|
||||
}
|
||||
}
|
||||
|
||||
void FileManBaseView::push_dir(const fs::path& path) {
|
||||
if (path == parent_dir_path) {
|
||||
pop_dir();
|
||||
} else {
|
||||
current_path /= path;
|
||||
saved_index_stack.push_back(menu_view.highlighted_index());
|
||||
menu_view.set_highlighted(0);
|
||||
reload_current();
|
||||
}
|
||||
}
|
||||
|
||||
void FileManBaseView::pop_dir() {
|
||||
if (saved_index_stack.empty())
|
||||
return;
|
||||
|
||||
current_path = current_path.parent_path();
|
||||
reload_current();
|
||||
menu_view.set_highlighted(saved_index_stack.back());
|
||||
saved_index_stack.pop_back();
|
||||
}
|
||||
|
||||
void FileManBaseView::refresh_list() {
|
||||
if (on_refresh_widgets)
|
||||
on_refresh_widgets(false);
|
||||
|
||||
auto prev_highlight = menu_view.highlighted_index();
|
||||
menu_view.clear();
|
||||
|
||||
for (size_t n = 0; n < entry_list.size(); n++) {
|
||||
auto entry = &entry_list[n];
|
||||
auto entry_name = entry->entry_path.filename().string().substr(0, 20);
|
||||
|
||||
if (entry->is_directory) {
|
||||
|
||||
for (const auto& entry : entry_list) {
|
||||
auto entry_name = truncate(entry.path, 20);
|
||||
|
||||
if (entry.is_directory) {
|
||||
menu_view.add_item({
|
||||
entry_name,
|
||||
ui::Color::yellow(),
|
||||
&bitmap_icon_dir,
|
||||
[this](){
|
||||
[this](KeyEvent key) {
|
||||
if (on_select_entry)
|
||||
on_select_entry();
|
||||
on_select_entry(key);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
} else {
|
||||
|
||||
auto file_size = entry->size;
|
||||
size_t suffix_index = 0;
|
||||
|
||||
while (file_size >= 1024) {
|
||||
file_size /= 1024;
|
||||
suffix_index++;
|
||||
}
|
||||
if (suffix_index > 4)
|
||||
suffix_index = 4;
|
||||
|
||||
std::string size_str = to_string_dec_uint(file_size) + suffix[suffix_index];
|
||||
|
||||
auto entry_extension = entry->entry_path.extension().string();
|
||||
for (auto &c: entry_extension)
|
||||
c = toupper(c);
|
||||
|
||||
// Associate extension to icon and color
|
||||
size_t c;
|
||||
for (c = 0; c < file_types.size() - 1; c++) {
|
||||
if (entry_extension == file_types[c].extension)
|
||||
break;
|
||||
}
|
||||
const auto& assoc = get_assoc(entry.path.extension());
|
||||
auto size_str = get_pretty_size(entry.size);
|
||||
|
||||
menu_view.add_item({
|
||||
entry_name + std::string(21 - entry_name.length(), ' ') + size_str,
|
||||
file_types[c].color,
|
||||
file_types[c].icon,
|
||||
[this](){
|
||||
assoc.color,
|
||||
assoc.icon,
|
||||
[this](KeyEvent key) {
|
||||
if (on_select_entry)
|
||||
on_select_entry();
|
||||
on_select_entry(key);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
// HACK: Should page menu items instead of limiting the number.
|
||||
if (menu_view.item_count() >= max_items_shown)
|
||||
break;
|
||||
}
|
||||
|
||||
menu_view.set_highlighted(0); // Refresh
|
||||
|
||||
text_info.set(menu_view.item_count() >= max_items_shown ? "Too many files!" : "");
|
||||
menu_view.set_highlighted(prev_highlight);
|
||||
}
|
||||
|
||||
/*void FileSaveView::on_save_name() {
|
||||
text_prompt(nav_, &filename_buffer, 8, [this](std::string * buffer) {
|
||||
nav_.pop();
|
||||
});
|
||||
void FileManBaseView::reload_current() {
|
||||
load_directory_contents(current_path);
|
||||
refresh_list();
|
||||
}
|
||||
FileSaveView::FileSaveView(
|
||||
NavigationView& nav
|
||||
) : FileManBaseView(nav)
|
||||
|
||||
const FileManBaseView::file_assoc_t& FileManBaseView::get_assoc(
|
||||
const fs::path& ext) const
|
||||
{
|
||||
name_buffer.clear();
|
||||
|
||||
add_children({
|
||||
&text_save,
|
||||
&button_save_name,
|
||||
&live_timestamp
|
||||
});
|
||||
|
||||
button_save_name.on_select = [this, &nav](Button&) {
|
||||
on_save_name();
|
||||
};
|
||||
}*/
|
||||
size_t index = 0;
|
||||
|
||||
void FileLoadView::refresh_widgets(const bool v) {
|
||||
(void)v; //avoid unused warning
|
||||
set_dirty();
|
||||
for (; index < file_types.size() - 1; ++index)
|
||||
if (iequal(ext, file_types[index].extension))
|
||||
return file_types[index];
|
||||
|
||||
// Default to last entry in the list.
|
||||
return file_types[index];
|
||||
}
|
||||
|
||||
/* FileLoadView **************************************************************/
|
||||
|
||||
FileLoadView::FileLoadView(
|
||||
NavigationView& nav,
|
||||
std::string filter
|
||||
@@ -238,156 +350,268 @@ FileLoadView::FileLoadView(
|
||||
|
||||
refresh_list();
|
||||
|
||||
on_select_entry = [&nav, this]() {
|
||||
if (entry_list[menu_view.highlighted_index()].is_directory) {
|
||||
load_directory_contents(get_selected_path());
|
||||
refresh_list();
|
||||
on_select_entry = [this](KeyEvent) {
|
||||
if (get_selected_entry().is_directory) {
|
||||
push_dir(get_selected_entry().path);
|
||||
} else {
|
||||
nav_.pop();
|
||||
if (on_changed)
|
||||
on_changed(current_path.string() + '/' + entry_list[menu_view.highlighted_index()].entry_path.string());
|
||||
on_changed(get_selected_full_path());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void FileManagerView::on_rename(NavigationView& nav) {
|
||||
text_prompt(nav, name_buffer, max_filename_length, [this](std::string& buffer) {
|
||||
std::string destination_path = current_path.string();
|
||||
if (destination_path.back() != '/')
|
||||
destination_path += '/';
|
||||
destination_path = destination_path + buffer;
|
||||
rename_file(get_selected_path(), destination_path);
|
||||
load_directory_contents(current_path);
|
||||
refresh_list();
|
||||
});
|
||||
void FileLoadView::refresh_widgets(const bool) {
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
void FileManagerView::on_refactor(NavigationView& nav) {
|
||||
text_prompt(nav, name_buffer, max_filename_length, [this](std::string& buffer) {
|
||||
|
||||
std::string destination_path = current_path.string();
|
||||
if (destination_path.back() != '/')//if the path is not ended with '/', add '/'
|
||||
destination_path += '/';
|
||||
|
||||
auto selected_path = get_selected_path();
|
||||
auto extension = selected_path.extension().string();
|
||||
|
||||
if(extension.empty()){// Is Dir
|
||||
destination_path = destination_path + buffer;
|
||||
extension_buffer = "";
|
||||
}else{//is File
|
||||
destination_path = destination_path + buffer + extension_buffer;
|
||||
}
|
||||
|
||||
rename_file(get_selected_path(), destination_path); //rename the selected file
|
||||
|
||||
if (!extension.empty() && selected_path.string().back() != '/' && extension.substr(1) == "C16") { //substr(1) is for ignore the dot
|
||||
// Rename its partner ( C16 <-> TXT ) file.
|
||||
auto partner_file_path = selected_path.string().substr(0, selected_path.string().size() - 4) + ".TXT";
|
||||
destination_path = destination_path.substr(0, destination_path.size() - 4) + ".TXT";
|
||||
rename_file(partner_file_path, destination_path);
|
||||
} else if (!extension.empty() && selected_path.string().back() != '/' && extension.substr(1) == "TXT") {
|
||||
// If the file user choose is a TXT file.
|
||||
auto partner_file_path = selected_path.string().substr(0, selected_path.string().size() - 4) + ".C16";
|
||||
destination_path = destination_path.substr(0, destination_path.size() - 4) + ".C16";
|
||||
rename_file(partner_file_path, destination_path);
|
||||
}
|
||||
|
||||
load_directory_contents(current_path);
|
||||
refresh_list();
|
||||
|
||||
/* 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() {
|
||||
auto& entry = get_selected_entry();
|
||||
name_buffer = entry.path.filename().string();
|
||||
uint32_t cursor_pos = (uint32_t)name_buffer.length();
|
||||
|
||||
if (auto pos = name_buffer.find_last_of(".");
|
||||
pos != name_buffer.npos && !entry.is_directory)
|
||||
cursor_pos = pos;
|
||||
|
||||
text_prompt(nav_, name_buffer, cursor_pos, max_filename_length,
|
||||
[this](std::string& renamed) {
|
||||
auto renamed_path = fs::path{ renamed };
|
||||
rename_file(get_selected_full_path(), current_path / renamed_path);
|
||||
|
||||
auto has_partner = partner_file_prompt(nav_, get_selected_full_path(), "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(partner, current_path / new_name);
|
||||
}
|
||||
reload_current();
|
||||
}
|
||||
);
|
||||
|
||||
if (!has_partner)
|
||||
reload_current();
|
||||
});
|
||||
}
|
||||
|
||||
void FileManagerView::on_delete() {
|
||||
delete_file(get_selected_path());
|
||||
load_directory_contents(current_path);
|
||||
refresh_list();
|
||||
auto name = get_selected_entry().path.filename().string();
|
||||
nav_.push<ModalMessageView>("Delete", "Delete " + name + "\nAre you sure?", YESNO,
|
||||
[this](bool choice) {
|
||||
if (choice) {
|
||||
delete_file(get_selected_full_path());
|
||||
|
||||
auto has_partner = partner_file_prompt(
|
||||
nav_, get_selected_full_path(), "Delete",
|
||||
[this](const fs::path& partner, bool should_delete) {
|
||||
if (should_delete)
|
||||
delete_file(partner);
|
||||
reload_current();
|
||||
}
|
||||
);
|
||||
|
||||
if (!has_partner)
|
||||
reload_current();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void FileManagerView::on_new_dir() {
|
||||
name_buffer = "";
|
||||
text_prompt(nav_, name_buffer, max_filename_length, [this](std::string& dir_name) {
|
||||
make_new_directory(current_path / dir_name);
|
||||
reload_current();
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void FileManagerView::refresh_widgets(const bool v) {
|
||||
button_rename.hidden(v);
|
||||
button_new_dir.hidden(v);
|
||||
button_refactor.hidden(v);
|
||||
button_delete.hidden(v);
|
||||
set_dirty();
|
||||
}
|
||||
button_cut.hidden(v);
|
||||
button_copy.hidden(v);
|
||||
button_paste.hidden(v);
|
||||
button_new_dir.hidden(v);
|
||||
button_new_file.hidden(v);
|
||||
|
||||
FileManagerView::~FileManagerView() {
|
||||
// Flush ?
|
||||
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_refactor,
|
||||
&button_new_dir,
|
||||
&button_delete
|
||||
});
|
||||
|
||||
menu_view.on_highlight = [this]() {
|
||||
text_date.set(to_string_FAT_timestamp(file_created_date(get_selected_path())));
|
||||
};
|
||||
|
||||
refresh_list();
|
||||
|
||||
on_select_entry = [this]() {
|
||||
if (entry_list[menu_view.highlighted_index()].is_directory) {
|
||||
load_directory_contents(get_selected_path());
|
||||
refresh_list();
|
||||
} else
|
||||
button_rename.focus();
|
||||
};
|
||||
|
||||
button_new_dir.on_select = [this, &nav](Button&) {
|
||||
name_buffer.clear();
|
||||
|
||||
text_prompt(nav, name_buffer, max_filename_length, [this](std::string& buffer) {
|
||||
make_new_directory(current_path.string() + '/' + buffer);
|
||||
load_directory_contents(current_path);
|
||||
refresh_list();
|
||||
});
|
||||
};
|
||||
|
||||
button_rename.on_select = [this, &nav](Button&) {
|
||||
name_buffer = entry_list[menu_view.highlighted_index()].entry_path.filename().string().substr(0, max_filename_length);
|
||||
on_rename(nav);
|
||||
};
|
||||
// Don't bother with the UI in the case of no SDC.
|
||||
if (empty_ == EmptyReason::NoSDC)
|
||||
return;
|
||||
|
||||
button_refactor.on_select = [this, &nav](Button&) {
|
||||
name_buffer = entry_list[menu_view.highlighted_index()].entry_path.filename().string().substr(0, max_filename_length);
|
||||
size_t pos = name_buffer.find_last_of(".");
|
||||
on_refresh_widgets = [this](bool v) {
|
||||
refresh_widgets(v);
|
||||
};
|
||||
|
||||
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();
|
||||
|
||||
if (pos != std::string::npos) {
|
||||
extension_buffer = name_buffer.substr(pos);
|
||||
name_buffer = name_buffer.substr(0, pos);
|
||||
}
|
||||
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();
|
||||
};
|
||||
|
||||
on_refactor(nav);
|
||||
};
|
||||
button_delete.on_select = [this]() {
|
||||
if (selected_is_valid())
|
||||
on_delete();
|
||||
};
|
||||
|
||||
button_delete.on_select = [this, &nav](Button&) {
|
||||
// Use display_modal ?
|
||||
nav.push<ModalMessageView>("Delete", "Delete " + entry_list[menu_view.highlighted_index()].entry_path.filename().string() + "\nAre you sure?", YESNO,
|
||||
[this](bool choice) {
|
||||
if (choice)
|
||||
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();
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -31,11 +31,23 @@
|
||||
namespace ui {
|
||||
|
||||
struct fileman_entry {
|
||||
std::filesystem::path entry_path { };
|
||||
std::filesystem::path path { };
|
||||
uint32_t size { };
|
||||
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,50 +55,58 @@ public:
|
||||
std::string filter
|
||||
);
|
||||
|
||||
void focus() override;
|
||||
|
||||
void load_directory_contents(const std::filesystem::path& dir_path);
|
||||
std::filesystem::path get_selected_path();
|
||||
|
||||
virtual ~FileManBaseView() { }
|
||||
|
||||
void focus() override;
|
||||
std::string title() const override { return "Fileman"; };
|
||||
void push_dir(const std::filesystem::path& path);
|
||||
|
||||
protected:
|
||||
NavigationView& nav_;
|
||||
|
||||
static constexpr size_t max_filename_length = 30 - 2;
|
||||
|
||||
const std::string suffix[5] = { "B", "kB", "MB", "GB", "??" };
|
||||
|
||||
static constexpr size_t max_filename_length = 64;
|
||||
static constexpr size_t max_items_shown = 100;
|
||||
|
||||
struct file_assoc_t {
|
||||
std::string extension;
|
||||
std::filesystem::path extension;
|
||||
const Bitmap* icon;
|
||||
ui::Color color;
|
||||
};
|
||||
|
||||
const std::vector<file_assoc_t> file_types = {
|
||||
{ ".TXT", &bitmap_icon_file_text, ui::Color::white() },
|
||||
{ ".PNG", &bitmap_icon_file_image, ui::Color::green() },
|
||||
{ ".BMP", &bitmap_icon_file_image, ui::Color::green() },
|
||||
{ ".C8", &bitmap_icon_file_iq, ui::Color::dark_cyan() },
|
||||
{ ".C16", &bitmap_icon_file_iq, ui::Color::dark_cyan() },
|
||||
{ ".WAV", &bitmap_icon_file_wav, ui::Color::dark_magenta() },
|
||||
{ "", &bitmap_icon_file, ui::Color::light_grey() }
|
||||
{ u".TXT", &bitmap_icon_file_text, ui::Color::white() },
|
||||
{ u".PNG", &bitmap_icon_file_image, ui::Color::green() },
|
||||
{ u".BMP", &bitmap_icon_file_image, ui::Color::green() },
|
||||
{ u".C8", &bitmap_icon_file_iq, ui::Color::dark_cyan() },
|
||||
{ u".C16", &bitmap_icon_file_iq, ui::Color::dark_cyan() },
|
||||
{ u".WAV", &bitmap_icon_file_wav, ui::Color::dark_magenta() },
|
||||
{ u"", &bitmap_icon_file, ui::Color::light_grey() } // NB: Must be last.
|
||||
};
|
||||
|
||||
bool empty_root { false };
|
||||
std::function<void(void)> on_select_entry { nullptr };
|
||||
std::function<void(bool)> on_refresh_widgets { nullptr };
|
||||
std::vector<fileman_entry> entry_list { };
|
||||
std::filesystem::path current_path { u"" };
|
||||
std::string extension_filter { "" };
|
||||
|
||||
void change_category(int32_t category_id);
|
||||
std::filesystem::path get_parent_dir();
|
||||
|
||||
std::filesystem::path get_selected_full_path() const;
|
||||
const fileman_entry& get_selected_entry() const;
|
||||
|
||||
void pop_dir();
|
||||
void refresh_list();
|
||||
void reload_current();
|
||||
void load_directory_contents(const std::filesystem::path& dir_path);
|
||||
const file_assoc_t& get_assoc(const std::filesystem::path& ext) const;
|
||||
|
||||
NavigationView& nav_;
|
||||
|
||||
EmptyReason empty_ { EmptyReason::NotEmpty };
|
||||
std::function<void(KeyEvent)> on_select_entry { nullptr };
|
||||
std::function<void(bool)> on_refresh_widgets { nullptr };
|
||||
|
||||
const std::filesystem::path parent_dir_path { u".." };
|
||||
std::filesystem::path current_path { u"" };
|
||||
std::filesystem::path extension_filter { u"" };
|
||||
|
||||
std::vector<fileman_entry> entry_list { };
|
||||
std::vector<uint32_t> saved_index_stack { };
|
||||
|
||||
Labels labels {
|
||||
{ { 0, 0 }, "Path:", Color::light_grey() }
|
||||
};
|
||||
|
||||
Text text_current {
|
||||
{ 6 * 8, 0 * 8, 24 * 8, 16 },
|
||||
"",
|
||||
@@ -96,59 +116,109 @@ 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::string extension_buffer { };
|
||||
std::filesystem::path clipboard_path { };
|
||||
ClipboardMode clipboard_mode { ClipboardMode::None };
|
||||
|
||||
void refresh_widgets(const bool v);
|
||||
void on_rename(NavigationView& nav);
|
||||
void on_refactor(NavigationView& nav);
|
||||
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;
|
||||
|
||||
Labels labels {
|
||||
{ { 0, 26 * 8 }, "Created ", Color::light_grey() }
|
||||
@@ -159,26 +229,54 @@ private:
|
||||
""
|
||||
};
|
||||
|
||||
Button button_rename {
|
||||
{ 0 * 8, 29 * 8, 9 * 8, 32 },
|
||||
"Rename"
|
||||
NewButton button_rename {
|
||||
{ 0 * 8, 29 * 8, 4 * 8, 32 },
|
||||
{ },
|
||||
&bitmap_icon_rename,
|
||||
Color::dark_blue()
|
||||
};
|
||||
|
||||
Button button_refactor{
|
||||
{ 10 * 8, 29 * 8, 10 * 8, 32 },
|
||||
"Refactor"
|
||||
NewButton button_delete {
|
||||
{ 4 * 8, 29 * 8, 4 * 8, 32 },
|
||||
{ },
|
||||
&bitmap_icon_trash,
|
||||
Color::red()
|
||||
};
|
||||
|
||||
Button button_delete {
|
||||
{ 21 * 8, 29 * 8, 9 * 8, 32 },
|
||||
"Delete"
|
||||
NewButton button_cut {
|
||||
{ 9 * 8, 29 * 8, 4 * 8, 32 },
|
||||
{ },
|
||||
&bitmap_icon_cut,
|
||||
Color::dark_grey()
|
||||
};
|
||||
|
||||
Button button_new_dir {
|
||||
{ 0 * 8, 34 * 8, 14 * 8, 32 },
|
||||
"New dir"
|
||||
|
||||
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()
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -44,7 +44,7 @@ FlashUtilityView::FlashUtilityView(NavigationView& nav) : nav_ (nav) {
|
||||
filename.string().substr(0, max_filename_length),
|
||||
ui::Color::red(),
|
||||
&bitmap_icon_temperature,
|
||||
[this, path]() {
|
||||
[this, path](KeyEvent) {
|
||||
this->firmware_selected(path);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -120,7 +120,7 @@ void FreqManBaseView::refresh_list() {
|
||||
freqman_item_string(database[n], 30),
|
||||
ui::Color::white(),
|
||||
nullptr,
|
||||
[this](){
|
||||
[this](KeyEvent){
|
||||
if (on_select_frequency)
|
||||
on_select_frequency();
|
||||
}
|
||||
|
||||
@@ -48,8 +48,6 @@ namespace ui {
|
||||
|
||||
void focus() override;
|
||||
|
||||
void big_display_freq( int64_t f );
|
||||
|
||||
const Style style_grey { // level
|
||||
.font = font::fixed_8x16,
|
||||
.background = Color::black(),
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace ui
|
||||
{
|
||||
void GlassView::focus()
|
||||
{
|
||||
field_marker.focus();
|
||||
button_marker.focus();
|
||||
}
|
||||
|
||||
GlassView::~GlassView()
|
||||
@@ -43,11 +43,6 @@ namespace ui
|
||||
return ((num / multiplier) + 1) * multiplier;
|
||||
}
|
||||
|
||||
// Returns the previous multiple of num that is a multiple of multiplier
|
||||
//int64_t GlassView::prev_mult_of(int64_t num, int64_t multiplier) {
|
||||
// return (num / multiplier) * multiplier;
|
||||
//}
|
||||
|
||||
void GlassView::adjust_range(int64_t* f_min, int64_t* f_max, int64_t width) {
|
||||
int64_t span = *f_max - *f_min;
|
||||
int64_t num_intervals = span / width;
|
||||
@@ -130,7 +125,7 @@ namespace ui
|
||||
last_max_freq = max_freq_hold ;
|
||||
freq_stats.set( "MAX HOLD: "+to_string_short_freq( max_freq_hold ) );
|
||||
}
|
||||
PlotMarker(field_marker.value());
|
||||
PlotMarker( marker );
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -144,13 +139,84 @@ namespace ui
|
||||
// Each having the radio signal power for it's corresponding frequency slot
|
||||
void GlassView::on_channel_spectrum(const ChannelSpectrum &spectrum)
|
||||
{
|
||||
// default fast scan offset
|
||||
uint8_t offset = 2 ;
|
||||
baseband::spectrum_streaming_stop();
|
||||
if( fast_scan || ( LOOKING_GLASS_SLICE_WIDTH < LOOKING_GLASS_SLICE_WIDTH_MAX ) )
|
||||
{
|
||||
// Convert bins of this spectrum slice into a representative max_power and when enough, into pixels
|
||||
// Spectrum.db has 256 bins. Center 12 bins are ignored (DC spike is blanked) Leftmost and rightmost 2 bins are ignored
|
||||
// All things said and done, we actually need 240 of those bins:
|
||||
// Spectrum.db has 256 bins.
|
||||
// All things said and done, we actually need 240 of those bins
|
||||
for (uint8_t bin = 0; bin < 240; bin++)
|
||||
{
|
||||
// if the view is done in one pass, show it like in analog_audio_app
|
||||
if( ( LOOKING_GLASS_SLICE_WIDTH < LOOKING_GLASS_SLICE_WIDTH_MAX ) )
|
||||
{
|
||||
// Center 16 bins are ignored (DC spike is blanked)
|
||||
if (bin < 120)
|
||||
{
|
||||
if (spectrum.db[256 - 120 + bin] > max_power) // 134
|
||||
max_power = spectrum.db[256 - 120 + bin];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (spectrum.db[ bin - 120] > max_power) // 118
|
||||
max_power = spectrum.db[bin - 120];
|
||||
}
|
||||
}
|
||||
else // view is made in multiple pass, use original bin picking
|
||||
{
|
||||
// Center 12 bins are ignored (DC spike is blanked) Leftmost and rightmost 2 bins are ignored
|
||||
if (bin < 120)
|
||||
{
|
||||
if (spectrum.db[134 + bin] > max_power) // 134
|
||||
max_power = spectrum.db[134 + bin];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (spectrum.db[bin - 118] > max_power) // 118
|
||||
max_power = spectrum.db[bin - 118];
|
||||
}
|
||||
}
|
||||
|
||||
if( bin == 120 )
|
||||
{
|
||||
bins_Hz_size += 12 * each_bin_size; // add DC bin Hz count into the "pixel fulfilled bag of Hz"
|
||||
}
|
||||
else
|
||||
{
|
||||
bins_Hz_size += each_bin_size; // add this bin Hz count into the "pixel fulfilled bag of Hz"
|
||||
}
|
||||
|
||||
if (bins_Hz_size >= marker_pixel_step) // new pixel fullfilled
|
||||
{
|
||||
if (min_color_power < max_power)
|
||||
add_spectrum_pixel(max_power); // Pixel will represent max_power
|
||||
else
|
||||
add_spectrum_pixel(0); // Filtered out, show black
|
||||
|
||||
max_power = 0;
|
||||
|
||||
if (!pixel_index) // Received indication that a waterfall line has been completed
|
||||
{
|
||||
bins_Hz_size = 0; // Since this is an entire pixel line, we don't carry "Pixels into next bin"
|
||||
f_center = f_center_ini - offset * each_bin_size ; // Start a new sweep
|
||||
radio::set_tuning_frequency(f_center); // tune rx for this new slice directly, faster than using persistent memory saving
|
||||
chThdSleepMilliseconds(10);
|
||||
baseband::spectrum_streaming_start(); // Do the RX
|
||||
return;
|
||||
}
|
||||
bins_Hz_size -= marker_pixel_step; // reset bins size, but carrying the eventual excess Hz into next pixel
|
||||
}
|
||||
}
|
||||
f_center += ( 256 - ( 2 * offset ) ) * each_bin_size ; // Move into the next bandwidth slice NOTE: spectrum.sampling_rate = LOOKING_GLASS_SLICE_WIDTH
|
||||
// lost bins are taken in account so next slice first ignored bins overlap previous kept ones
|
||||
}
|
||||
else //slow scan
|
||||
{
|
||||
offset = 32 ;
|
||||
uint8_t bin_length = 80 ;
|
||||
for (uint8_t bin = offset ; bin < bin_length + offset ; bin++)
|
||||
{
|
||||
if (bin < 120)
|
||||
{
|
||||
@@ -165,38 +231,6 @@ namespace ui
|
||||
|
||||
bins_Hz_size += each_bin_size; // add this bin Hz count into the "pixel fulfilled bag of Hz"
|
||||
|
||||
if (bins_Hz_size >= marker_pixel_step) // new pixel fullfilled
|
||||
{
|
||||
if (min_color_power < max_power)
|
||||
add_spectrum_pixel(max_power); // Pixel will represent max_power
|
||||
else
|
||||
add_spectrum_pixel(0); // Filtered out, show black
|
||||
|
||||
max_power = 0;
|
||||
|
||||
if (!pixel_index) // Received indication that a waterfall line has been completed
|
||||
{
|
||||
bins_Hz_size = 0; // Since this is an entire pixel line, we don't carry "Pixels into next bin"
|
||||
f_center = f_center_ini - 2 * each_bin_size ; // Start a new sweep
|
||||
radio::set_tuning_frequency(f_center); // tune rx for this new slice directly, faster than using persistent memory saving
|
||||
chThdSleepMilliseconds(10);
|
||||
baseband::spectrum_streaming_start(); // Do the RX
|
||||
return;
|
||||
}
|
||||
bins_Hz_size -= marker_pixel_step; // reset bins size, but carrying the eventual excess Hz into next pixel
|
||||
}
|
||||
}
|
||||
f_center += 240 * each_bin_size ; // Move into the next bandwidth slice NOTE: spectrum.sampling_rate = LOOKING_GLASS_SLICE_WIDTH
|
||||
}
|
||||
else //slow scan
|
||||
{
|
||||
for (uint8_t bin = 0; bin < 120 ; bin++)
|
||||
{
|
||||
if (spectrum.db[134 + bin] > max_power) // 134
|
||||
max_power = spectrum.db[134 + bin];
|
||||
|
||||
bins_Hz_size += each_bin_size; // add this bin Hz count into the "pixel fulfilled bag of Hz"
|
||||
|
||||
if (bins_Hz_size >= marker_pixel_step) // new pixel fullfilled
|
||||
{
|
||||
if (min_color_power < max_power)
|
||||
@@ -209,7 +243,7 @@ namespace ui
|
||||
if (!pixel_index) // Received indication that a waterfall line has been completed
|
||||
{
|
||||
bins_Hz_size = 0; // Since this is an entire pixel line, we don't carry "Pixels into next bin"
|
||||
f_center = f_center_ini - 2 * each_bin_size ; // Start a new sweep
|
||||
f_center = f_center_ini - offset * each_bin_size ; // Start a new sweep
|
||||
radio::set_tuning_frequency(f_center); // tune rx for this new slice directly, faster than using persistent memory saving
|
||||
chThdSleepMilliseconds(10);
|
||||
baseband::spectrum_streaming_start(); // Do the RX
|
||||
@@ -218,7 +252,7 @@ namespace ui
|
||||
bins_Hz_size -= marker_pixel_step; // reset bins size, but carrying the eventual excess Hz into next pixel
|
||||
}
|
||||
}
|
||||
f_center += 120 * each_bin_size ;
|
||||
f_center += bin_length * each_bin_size ;
|
||||
}
|
||||
radio::set_tuning_frequency(f_center); // tune rx for this new slice directly, faster than using persistent memory saving
|
||||
chThdSleepMilliseconds(5);
|
||||
@@ -243,9 +277,7 @@ namespace ui
|
||||
f_min = field_frequency_min.value();
|
||||
f_max = field_frequency_max.value();
|
||||
search_span = f_max - f_min;
|
||||
|
||||
field_marker.set_range(f_min, f_max); // Move the marker between range
|
||||
field_marker.set_value(f_min + (search_span / 2)); // Put MARKER AT MIDDLE RANGE
|
||||
|
||||
if( locked_range )
|
||||
{
|
||||
button_range.set_text(">"+to_string_dec_uint(search_span)+"<");
|
||||
@@ -260,25 +292,16 @@ namespace ui
|
||||
adjust_range( &f_min , &f_max , 240 );
|
||||
|
||||
marker_pixel_step = (f_max - f_min) / 240; // Each pixel value in Hz
|
||||
text_marker_pm.set(to_string_dec_uint((marker_pixel_step / X2_MHZ_DIV) + 1)); // Give idea of +/- marker precision
|
||||
marker = f_min + (f_max - f_min) / 2 ;
|
||||
button_marker.set_text( to_string_short_freq( marker ) );
|
||||
PlotMarker( marker ); // Refresh marker on screen
|
||||
|
||||
int32_t marker_step = marker_pixel_step / MHZ_DIV;
|
||||
if (!marker_step)
|
||||
field_marker.set_step(1); // in case selected range is less than 240 (pixels)
|
||||
else
|
||||
field_marker.set_step(marker_step); // step needs to be a pixel wide.
|
||||
|
||||
f_center_ini = f_min + (LOOKING_GLASS_SLICE_WIDTH / 2); // Initial center frequency for sweep
|
||||
|
||||
PlotMarker(field_marker.value()); // Refresh marker on screen
|
||||
|
||||
f_center = f_center_ini; // Reset sweep into first slice
|
||||
pixel_index = 0; // reset pixel counter
|
||||
max_power = 0;
|
||||
bins_Hz_size = 0; // reset amount of Hz filled up by pixels
|
||||
if( next_mult_of( (f_max - f_min) , 240 ) <= LOOKING_GLASS_SLICE_WIDTH_MAX )
|
||||
if( (f_max - f_min) <= LOOKING_GLASS_SLICE_WIDTH_MAX )
|
||||
{
|
||||
LOOKING_GLASS_SLICE_WIDTH = next_mult_of( (f_max - f_min) , 240 );
|
||||
LOOKING_GLASS_SLICE_WIDTH = (f_max - f_min) ;
|
||||
receiver_model.set_sampling_rate(LOOKING_GLASS_SLICE_WIDTH);
|
||||
receiver_model.set_baseband_bandwidth(LOOKING_GLASS_SLICE_WIDTH/2);
|
||||
}
|
||||
@@ -288,20 +311,26 @@ namespace ui
|
||||
receiver_model.set_sampling_rate(LOOKING_GLASS_SLICE_WIDTH);
|
||||
receiver_model.set_baseband_bandwidth(LOOKING_GLASS_SLICE_WIDTH);
|
||||
}
|
||||
if( next_mult_of( LOOKING_GLASS_SLICE_WIDTH , 256 ) > LOOKING_GLASS_SLICE_WIDTH_MAX )
|
||||
LOOKING_GLASS_SLICE_WIDTH = LOOKING_GLASS_SLICE_WIDTH_MAX ;
|
||||
else
|
||||
LOOKING_GLASS_SLICE_WIDTH = next_mult_of( LOOKING_GLASS_SLICE_WIDTH , 256 );
|
||||
|
||||
receiver_model.set_squelch_level(0);
|
||||
each_bin_size = LOOKING_GLASS_SLICE_WIDTH / 240 ;
|
||||
each_bin_size = LOOKING_GLASS_SLICE_WIDTH / 256 ;
|
||||
f_center_ini = f_min + (LOOKING_GLASS_SLICE_WIDTH / 2) ; // Initial center frequency for sweep
|
||||
f_center = f_center_ini ; // Reset sweep into first slice
|
||||
baseband::set_spectrum(LOOKING_GLASS_SLICE_WIDTH, field_trigger.value());
|
||||
receiver_model.set_tuning_frequency(f_center_ini); // tune rx for this slice
|
||||
}
|
||||
|
||||
void GlassView::PlotMarker(rf::Frequency pos)
|
||||
{
|
||||
pos = pos * MHZ_DIV;
|
||||
pos -= f_min;
|
||||
pos = pos / marker_pixel_step; // Real pixel
|
||||
|
||||
uint8_t shift_y = 0 ;
|
||||
if( live_frequency_view > 0 )
|
||||
if( live_frequency_view > 0 ) // plot one line down when in live view
|
||||
{
|
||||
shift_y = 16 ;
|
||||
}
|
||||
@@ -329,8 +358,7 @@ namespace ui
|
||||
&filter_config,
|
||||
&field_rf_amp,
|
||||
&range_presets,
|
||||
&field_marker,
|
||||
&text_marker_pm,
|
||||
&button_marker,
|
||||
&field_trigger,
|
||||
&button_jump,
|
||||
&button_rst,
|
||||
@@ -509,16 +537,21 @@ namespace ui
|
||||
this->on_range_changed();
|
||||
};
|
||||
|
||||
field_marker.on_change = [this](int32_t v)
|
||||
button_marker.on_change = [this]()
|
||||
{
|
||||
PlotMarker(v); // Refresh marker on screen
|
||||
marker = marker + button_marker.get_encoder_delta() * marker_pixel_step ;
|
||||
if( marker < f_min )
|
||||
marker = f_min ;
|
||||
if( marker > f_max )
|
||||
marker = f_max ;
|
||||
button_marker.set_text( to_string_short_freq( marker ) );
|
||||
button_marker.set_encoder_delta( 0 );
|
||||
PlotMarker( marker ); // Refresh marker on screen
|
||||
};
|
||||
|
||||
field_marker.on_select = [this](NumberField &)
|
||||
button_marker.on_select = [this](ButtonWithEncoder &)
|
||||
{
|
||||
f_center = field_marker.value();
|
||||
f_center = f_center * MHZ_DIV;
|
||||
receiver_model.set_tuning_frequency(f_center); // Center tune rx in marker freq.
|
||||
receiver_model.set_tuning_frequency(marker); // Center tune rx in marker freq.
|
||||
receiver_model.set_frequency_step(MHZ_DIV); // Preset a 1 MHz frequency step into RX -> AUDIO
|
||||
nav_.pop();
|
||||
nav_.push<AnalogAudioView>(); // Jump into audio view
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
|
||||
namespace ui
|
||||
{
|
||||
#define LOOKING_GLASS_SLICE_WIDTH_MAX 19999920
|
||||
#define LOOKING_GLASS_SLICE_WIDTH_MAX 20000000
|
||||
#define MHZ_DIV 1000000
|
||||
#define X2_MHZ_DIV 2000000
|
||||
|
||||
@@ -81,14 +81,13 @@ namespace ui
|
||||
|
||||
std::vector<preset_entry> presets_db{};
|
||||
|
||||
// Each slice bandwidth 20 MHz and a multiple of 240
|
||||
// since we are using LOOKING_GLASS_SLICE_WIDTH/240 as the each_bin_size
|
||||
// Each slice bandwidth 20 MHz and a multiple of 256
|
||||
// since we are using LOOKING_GLASS_SLICE_WIDTH/256 as the each_bin_size
|
||||
// it should also be a multiple of 2 since we are using LOOKING_GLASS_SLICE_WIDTH / 2 as centering freq
|
||||
int64_t LOOKING_GLASS_SLICE_WIDTH = 19999920;
|
||||
int64_t LOOKING_GLASS_SLICE_WIDTH = 20000000;
|
||||
|
||||
// frequency rounding helpers
|
||||
int64_t next_mult_of(int64_t num, int64_t multiplier);
|
||||
//int64_t prev_mult_of(int64_t num, int64_t multiplier);
|
||||
void adjust_range(int64_t* f_min, int64_t* f_max, int64_t width);
|
||||
|
||||
void on_channel_spectrum(const ChannelSpectrum& spectrum);
|
||||
@@ -108,8 +107,9 @@ namespace ui
|
||||
rf::Frequency search_span { 0 };
|
||||
rf::Frequency f_center { 0 };
|
||||
rf::Frequency f_center_ini { 0 };
|
||||
rf::Frequency marker { 0 };
|
||||
rf::Frequency marker_pixel_step { 0 };
|
||||
rf::Frequency each_bin_size { LOOKING_GLASS_SLICE_WIDTH / 240 };
|
||||
rf::Frequency each_bin_size { LOOKING_GLASS_SLICE_WIDTH / 256 };
|
||||
rf::Frequency bins_Hz_size { 0 };
|
||||
uint8_t min_color_power { 0 };
|
||||
uint32_t pixel_index { 0 };
|
||||
@@ -129,7 +129,7 @@ namespace ui
|
||||
{{0, 0}, "MIN: MAX: LNA VGA ", Color::light_grey()},
|
||||
{{0, 1 * 16}, "RANGE: FILTER: AMP:", Color::light_grey()},
|
||||
{{0, 2 * 16}, "PRESET:", Color::light_grey()},
|
||||
{{0, 3 * 16}, "MARKER: MHz +/- MHz", Color::light_grey()},
|
||||
{{0, 3 * 16}, "MARKER: MHz", Color::light_grey()},
|
||||
{{0, 4 * 16}, "RES: STEP:", Color::light_grey()}
|
||||
};
|
||||
|
||||
@@ -180,16 +180,10 @@ namespace ui
|
||||
{" NONE (WIFI 2.4GHz)", 0},
|
||||
}};
|
||||
|
||||
NumberField field_marker{
|
||||
{7 * 8, 3 * 16},
|
||||
4,
|
||||
{0, 7200},
|
||||
25,
|
||||
' '};
|
||||
|
||||
Text text_marker_pm{
|
||||
{20 * 8, 3 * 16, 2 * 8, 16},
|
||||
""};
|
||||
ButtonWithEncoder button_marker{
|
||||
{7 * 8, 3 * 16 , 10 * 8 , 16},
|
||||
" "
|
||||
};
|
||||
|
||||
NumberField field_trigger{
|
||||
{4 * 8, 4 * 16},
|
||||
|
||||
@@ -90,9 +90,7 @@ void MicTXView::set_tx(bool enable) {
|
||||
rxaudio(false); //Then turn off audio RX
|
||||
transmitting = true;
|
||||
configure_baseband();
|
||||
transmitter_model.set_tuning_frequency(tx_frequency);
|
||||
transmitter_model.set_tx_gain(tx_gain);
|
||||
transmitter_model.set_rf_amp(rf_amp);
|
||||
transmitter_model.set_tuning_frequency(tx_frequency); // Now,no need: transmitter_model.set_tx_gain(tx_gain), nor (rf_amp);
|
||||
transmitter_model.enable();
|
||||
portapack::pin_i2s0_rx_sda.mode(3); // This is already done in audio::init but gets changed by the CPLD overlay reprogramming
|
||||
} else {
|
||||
@@ -167,14 +165,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);
|
||||
@@ -234,8 +232,7 @@ MicTXView::MicTXView(
|
||||
&field_va_attack,
|
||||
&field_va_decay,
|
||||
&field_bw,
|
||||
&field_rfgain,
|
||||
&field_rfamp,
|
||||
&tx_view, // it already integrates previous rfgain, rfamp.
|
||||
&options_mode,
|
||||
&field_frequency,
|
||||
&options_tone_key,
|
||||
@@ -264,8 +261,7 @@ MicTXView::MicTXView(
|
||||
&field_va_attack,
|
||||
&field_va_decay,
|
||||
&field_bw,
|
||||
&field_rfgain,
|
||||
&field_rfamp,
|
||||
&tx_view, // it already integrates previous rfgain, rfamp.
|
||||
&options_mode,
|
||||
&field_frequency,
|
||||
&options_tone_key,
|
||||
@@ -366,20 +362,8 @@ MicTXView::MicTXView(
|
||||
};
|
||||
field_bw.set_value(10); // pre-default first time, TX deviation FM for NFM / FM
|
||||
|
||||
|
||||
tx_gain = transmitter_model.tx_gain();
|
||||
field_rfgain.on_change = [this](int32_t v) {
|
||||
tx_gain = v;
|
||||
|
||||
};
|
||||
field_rfgain.set_value(tx_gain);
|
||||
// now , no need direct update , field_rfgain , field_rfamp (it is done in ui_transmitter.cpp)
|
||||
|
||||
rf_amp = transmitter_model.rf_amp();
|
||||
field_rfamp.on_change = [this](int32_t v) {
|
||||
rf_amp = (bool)v;
|
||||
};
|
||||
field_rfamp.set_value(rf_amp ? 14 : 0);
|
||||
|
||||
options_mode.on_change = [this](size_t, int32_t v) { //{ "NFM/FM", 0 }, { " WFM ", 1 },{ "AM", 2 },{ "USB", 3 },{ "LSB", 4 },{ "DSB", 5 }
|
||||
enable_am = false;
|
||||
enable_usb = false;
|
||||
@@ -423,10 +407,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 +422,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 +436,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 +447,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 +551,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)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#include "tone_key.hpp"
|
||||
#include "message.hpp"
|
||||
#include "receiver_model.hpp"
|
||||
#include "ui_transmitter.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
@@ -115,11 +116,9 @@ private:
|
||||
{ { 17 * 8, 1 * 8 }, "Boost", Color::light_grey() },
|
||||
{ { 3 * 8, 3 * 8 }, "F:", Color::light_grey() },
|
||||
{ { 15 * 8, 3 * 8 }, "FM TXBW: kHz", Color::light_grey() }, // to be more symetric and consistent to the below FM RXBW
|
||||
{ { 3 * 8, 5 * 8 }, "GAIN:", Color::light_grey() },
|
||||
{ {11 * 8, 5 * 8 }, "Amp:", Color::light_grey() },
|
||||
{ { 18 * 8, (5 * 8) }, "Mode:", Color::light_grey() },
|
||||
{ { 3 * 8, 8 * 8 }, "TX Activation:", Color::light_grey() },
|
||||
{ { 4 * 8, 10 * 8 }, "LVL:", Color::light_grey() },
|
||||
{ { 18 * 8, (5 * 8) }, "Mode:", Color::light_grey() }, // now , no need to handle GAIN , Amp here It is handled by ui_transmitter.cpp
|
||||
{ { 3 * 8, 8 * 8 }, "TX Activation:", Color::light_grey() }, // we delete { { 3 * 8, 5 * 8 }, "GAIN:", Color::light_grey() },
|
||||
{ { 4 * 8, 10 * 8 }, "LVL:", Color::light_grey() }, // we delete { {11 * 8, 5 * 8 }, "Amp:", Color::light_grey() },
|
||||
{ {12 * 8, 10 * 8 }, "ATT:", Color::light_grey() },
|
||||
{ {20 * 8, 10 * 8 }, "DEC:", Color::light_grey() },
|
||||
{ { 4 * 8, ( 13 * 8 ) - 2 }, "TONE KEY:", Color::light_grey() },
|
||||
@@ -136,11 +135,9 @@ private:
|
||||
{ { 17 * 8, 1 * 8 }, "ALC", Color::light_grey() },
|
||||
{ { 3 * 8, 3 * 8 }, "F:", Color::light_grey() },
|
||||
{ { 15 * 8, 3 * 8 }, "FM TXBW: kHz", Color::light_grey() },
|
||||
{ { 3 * 8, 5 * 8 }, "GAIN:", Color::light_grey() },
|
||||
{ {11 * 8, 5 * 8 }, "Amp:", Color::light_grey() },
|
||||
{ { 18 * 8, (5 * 8) }, "Mode:", Color::light_grey() },
|
||||
{ { 3 * 8, 8 * 8 }, "TX Activation:", Color::light_grey() },
|
||||
{ { 4 * 8, 10 * 8 }, "LVL:", Color::light_grey() },
|
||||
{ { 18 * 8, (5 * 8) }, "Mode:", Color::light_grey() }, // now , no need to handle GAIN , Amp here It is handled by ui_transmitter.cpp
|
||||
{ { 3 * 8, 8 * 8 }, "TX Activation:", Color::light_grey() }, // we delete { { 3 * 8, 5 * 8 }, "GAIN:", Color::light_grey() },
|
||||
{ { 4 * 8, 10 * 8 }, "LVL:", Color::light_grey() }, // we delete { {11 * 8, 5 * 8 }, "Amp:", Color::light_grey() },
|
||||
{ {12 * 8, 10 * 8 }, "ATT:", Color::light_grey() },
|
||||
{ {20 * 8, 10 * 8 }, "DEC:", Color::light_grey() },
|
||||
{ { 4 * 8, ( 13 * 8 ) - 2 }, "TONE KEY:", Color::light_grey() },
|
||||
@@ -212,22 +209,11 @@ OptionsField options_wm8731_boost_mode {
|
||||
1,
|
||||
' '
|
||||
};
|
||||
|
||||
NumberField field_rfgain {
|
||||
{ 8 * 8, 5 * 8 },
|
||||
2,
|
||||
{ 0, 47 },
|
||||
1,
|
||||
' '
|
||||
|
||||
TransmitterView2 tx_view { // new handling of NumberField field_rfgain, NumberField field_rfamp
|
||||
2 * 8 // y line position.
|
||||
};
|
||||
NumberField field_rfamp {
|
||||
{ 15 * 8, 5 * 8 },
|
||||
2,
|
||||
{ 0, 14 },
|
||||
14,
|
||||
' '
|
||||
};
|
||||
|
||||
|
||||
OptionsField options_mode {
|
||||
{ 24 * 8, 5 * 8 },
|
||||
4,
|
||||
|
||||
@@ -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 }
|
||||
};
|
||||
|
||||
+897
-1202
File diff suppressed because it is too large
Load Diff
@@ -38,79 +38,10 @@
|
||||
#include "string_format.hpp"
|
||||
#include "file.hpp"
|
||||
#include "app_settings.hpp"
|
||||
|
||||
#include "ui_recon_settings.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
class ReconThread {
|
||||
public:
|
||||
ReconThread(freqman_db *database );
|
||||
~ReconThread();
|
||||
|
||||
void set_recon(const bool v);
|
||||
void set_freq_delete(const bool v);
|
||||
bool is_recon();
|
||||
|
||||
void set_lock_duration( const uint32_t v );
|
||||
uint32_t get_lock_duration();
|
||||
void set_lock_nb_match( const uint32_t v );
|
||||
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();
|
||||
int64_t get_current_freq();
|
||||
|
||||
void set_stepper(const int64_t v);
|
||||
void set_index_stepper(const int64_t v);
|
||||
|
||||
void change_recon_direction();
|
||||
bool get_recon_direction();
|
||||
void set_recon_direction( const bool v);
|
||||
|
||||
void set_continuous(const bool v);
|
||||
|
||||
void set_default_modulation( freqman_index_t index );
|
||||
freqman_index_t get_current_modulation();
|
||||
void set_default_bandwidth( freqman_index_t index );
|
||||
freqman_index_t get_current_bandwidth();
|
||||
void set_default_step( freqman_index_t index );
|
||||
void set_freq_index( int16_t index );
|
||||
int16_t get_freq_index();
|
||||
|
||||
void run();
|
||||
void stop();
|
||||
|
||||
ReconThread(const ReconThread&) = delete;
|
||||
ReconThread(ReconThread&&) = delete;
|
||||
ReconThread& operator=(const ReconThread&) = delete;
|
||||
ReconThread& operator=(ReconThread&&) = delete;
|
||||
|
||||
private:
|
||||
freqman_db &frequency_list_ ;
|
||||
Thread* thread { nullptr };
|
||||
int64_t freq = 0 ;
|
||||
uint32_t step = 0 ;
|
||||
freqman_index_t def_modulation = 0 ;
|
||||
freqman_index_t def_bandwidth = 0 ;
|
||||
freqman_index_t def_step = 0 ;
|
||||
tone_index tone = 0 ;
|
||||
freqman_entry last_entry = { } ;
|
||||
int16_t frequency_index = 0 ;
|
||||
|
||||
bool _recon { true };
|
||||
bool _freq_delete { false };
|
||||
bool _fwd { true };
|
||||
bool _continuous { true };
|
||||
bool entry_has_changed = false ;
|
||||
int64_t _stepper { 0 };
|
||||
int64_t _index_stepper { 0 };
|
||||
int32_t _freq_lock { 0 };
|
||||
uint32_t _lock_duration { 50 };
|
||||
uint32_t _lock_nb_match { 10 };
|
||||
static msg_t static_fn(void* arg);
|
||||
};
|
||||
|
||||
class ReconView : public View {
|
||||
public:
|
||||
ReconView(NavigationView& nav);
|
||||
@@ -118,39 +49,37 @@ namespace ui {
|
||||
|
||||
void focus() override;
|
||||
|
||||
void big_display_freq( int64_t f );
|
||||
|
||||
const Style style_grey { // recon
|
||||
const Style style_grey { // recon
|
||||
.font = font::fixed_8x16,
|
||||
.background = Color::black(),
|
||||
.foreground = Color::grey(),
|
||||
};
|
||||
|
||||
const Style style_white { // recon
|
||||
const Style style_white { // recon
|
||||
.font = font::fixed_8x16,
|
||||
.background = Color::black(),
|
||||
.foreground = Color::white(),
|
||||
};
|
||||
|
||||
const Style style_yellow { //Found signal
|
||||
const Style style_yellow { //found signal
|
||||
.font = font::fixed_8x16,
|
||||
.background = Color::black(),
|
||||
.foreground = Color::yellow(),
|
||||
};
|
||||
|
||||
const Style style_green { //Found signal
|
||||
const Style style_green { //Found signal
|
||||
.font = font::fixed_8x16,
|
||||
.background = Color::black(),
|
||||
.foreground = Color::green(),
|
||||
};
|
||||
|
||||
const Style style_red { //erasing freq
|
||||
const Style style_red { //erasing freq
|
||||
.font = font::fixed_8x16,
|
||||
.background = Color::black(),
|
||||
.foreground = Color::red(),
|
||||
};
|
||||
|
||||
const Style style_blue { // quick recon, wait == 0
|
||||
const Style style_blue { // quick recon, wait == 0
|
||||
.font = font::fixed_8x16,
|
||||
.background = Color::black(),
|
||||
.foreground = Color::blue(),
|
||||
@@ -163,30 +92,31 @@ namespace ui {
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
|
||||
void start_recon_thread();
|
||||
void audio_output_start();
|
||||
bool check_sd_card();
|
||||
size_t change_mode( freqman_index_t mod_type);
|
||||
void show_max( bool refresh_display = false );
|
||||
void recon_pause();
|
||||
void recon_resume();
|
||||
void user_pause();
|
||||
void user_resume();
|
||||
void frequency_file_load( bool stop_all_before = false);
|
||||
void on_statistics_update(const ChannelStatistics& statistics);
|
||||
void on_headphone_volume_changed(int32_t v);
|
||||
void set_display_freq( int64_t freq );
|
||||
void handle_retune( int64_t freq , uint32_t index );
|
||||
bool check_sd_card();
|
||||
void on_index_delta(int32_t v);
|
||||
void on_stepper_delta(int32_t v);
|
||||
void recon_redraw();
|
||||
void handle_retune();
|
||||
void handle_coded_squelch(const uint32_t value);
|
||||
bool ReconSetupLoadStrings( const std::string &source, std::string &input_file , std::string &output_file , uint32_t &recon_lock_duration , uint32_t &recon_lock_nb_match , int32_t &recon_squelch_level , uint32_t &recon_match_mode , int32_t &wait , int32_t &volume );
|
||||
bool ReconSetupSaveStrings( const std::string &dest, const std::string &input_file , const std::string &output_file , uint32_t recon_lock_duration , uint32_t recon_lock_nb_match , int32_t recon_squelch_level , uint32_t recon_match_mode , int32_t wait , int32_t volume );
|
||||
bool ReconSaveFreq( const std::string &freq_file_path , size_t index , bool warn_if_exists );
|
||||
|
||||
jammer::jammer_range_t frequency_range { false, 0, 0 }; //perfect for manual recon task too...
|
||||
jammer::jammer_range_t frequency_range { false, 0, MAX_UFREQ }; //perfect for manual recon task too...
|
||||
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 def_step { 0 };
|
||||
int32_t wait { RECON_DEF_WAIT_DURATION }; // in msec. if > 0 wait duration after a lock, if < 0 duration is set to 'wait' unless there is no more activity
|
||||
freqman_db frequency_list = { };
|
||||
uint32_t current_index { 0 };
|
||||
int32_t current_index { 0 };
|
||||
bool userpause { false };
|
||||
bool continuous_lock { false };
|
||||
std::string input_file = { "RECON" };
|
||||
@@ -200,15 +130,41 @@ namespace ui {
|
||||
bool load_hamradios = { true };
|
||||
bool update_ranges = { true };
|
||||
bool fwd = { true };
|
||||
// maximum usable freq
|
||||
long long int MAX_UFREQ = { 7200000000 };
|
||||
uint32_t recon_lock_nb_match = { 10 };
|
||||
uint32_t recon_lock_duration = { 50 };
|
||||
uint32_t recon_match_mode = { 0 };
|
||||
bool recon = true ;
|
||||
uint32_t recon_lock_nb_match = { 3 };
|
||||
uint32_t recon_lock_duration = { RECON_MIN_LOCK_DURATION };
|
||||
uint32_t recon_match_mode = { RECON_MATCH_CONTINUOUS };
|
||||
bool scanner_mode { false };
|
||||
bool manual_mode { false };
|
||||
bool sd_card_mounted = false ;
|
||||
int32_t volume = 40 ;
|
||||
int32_t stepper = 0 ;
|
||||
int32_t index_stepper = 0 ;
|
||||
int64_t freq = 0 ;
|
||||
uint32_t step = 0 ;
|
||||
freqman_index_t def_modulation = 0 ;
|
||||
freqman_index_t def_bandwidth = 0 ;
|
||||
freqman_index_t def_step = 0 ;
|
||||
tone_index tone = 0 ;
|
||||
freqman_entry last_entry = { } ;
|
||||
bool entry_has_changed = false ;
|
||||
uint32_t freq_lock { 0 };
|
||||
int64_t minfreq = 0 ;
|
||||
int64_t maxfreq = 0 ;
|
||||
bool has_looped = false ;
|
||||
int8_t status = -1 ; // 0 recon , 1 locking , 2 locked
|
||||
int32_t last_timer = -1 ;
|
||||
int8_t last_db = -127 ;
|
||||
uint16_t last_nb_match = 999 ;
|
||||
uint16_t last_freq_lock = 999 ;
|
||||
size_t last_list_size = 0 ;
|
||||
int8_t last_rssi_min = -127 ;
|
||||
int8_t last_rssi_med = -127 ;
|
||||
int8_t last_rssi_max = -127 ;
|
||||
int32_t last_index = -1 ;
|
||||
int32_t last_squelch_index = -1 ;
|
||||
int64_t last_freq = 0 ;
|
||||
std::string freq_file_path = { } ;
|
||||
|
||||
Labels labels
|
||||
{
|
||||
@@ -243,7 +199,7 @@ namespace ui {
|
||||
{ 3 * 8, 1 * 16 },
|
||||
6,
|
||||
{ }
|
||||
};
|
||||
};
|
||||
|
||||
NumberField field_squelch {
|
||||
{ 12 * 8, 1 * 16 },
|
||||
@@ -256,21 +212,21 @@ namespace ui {
|
||||
NumberField field_wait {
|
||||
{ 20 * 8, 1 * 16 },
|
||||
5,
|
||||
{ -9000, 9000 },
|
||||
100,
|
||||
{ -RECON_MAX_LOCK_DURATION , RECON_MAX_LOCK_DURATION },
|
||||
STATS_UPDATE_INTERVAL,
|
||||
' ',
|
||||
};
|
||||
|
||||
NumberField field_lock_wait {
|
||||
{ 26 * 8, 1 * 16 },
|
||||
4,
|
||||
{ 100 , 9000 },
|
||||
100,
|
||||
{ RECON_MIN_LOCK_DURATION , RECON_MAX_LOCK_DURATION },
|
||||
STATS_UPDATE_INTERVAL,
|
||||
' ',
|
||||
};
|
||||
|
||||
RSSI rssi {
|
||||
{ 0 * 16, 2 * 16, 240 - 8 * 8 + 4 , 16 },
|
||||
{ 0 * 16, 2 * 16, SCREEN_W - 8 * 8 + 4 , 16 },
|
||||
};
|
||||
|
||||
ButtonWithEncoder text_cycle {
|
||||
@@ -279,28 +235,23 @@ namespace ui {
|
||||
};
|
||||
|
||||
Text text_max {
|
||||
{ 4 * 8, 3 * 16, 240 - 7 * 8 - 4 * 8 , 16 },
|
||||
{ 4 * 8, 3 * 16, SCREEN_W - 7 * 8 - 4 * 8 , 16 },
|
||||
};
|
||||
|
||||
Text desc_cycle {
|
||||
{0, 4 * 16, 240 , 16 },
|
||||
{0, 4 * 16, SCREEN_W , 16 },
|
||||
};
|
||||
|
||||
/* BigFrequency big_display { //Show frequency in glamour
|
||||
{ 4, 7 * 16 - 8 , 28 * 8, 52 },
|
||||
0
|
||||
}; */
|
||||
|
||||
Text big_display { //Show frequency in text mode
|
||||
Text big_display { //Show frequency in text mode
|
||||
{ 0, 5 * 16 , 23 * 8, 16 },
|
||||
};
|
||||
|
||||
Text freq_stats { //Show frequency stats in text mode
|
||||
Text freq_stats { //Show frequency stats in text mode
|
||||
{ 0, 6 * 16 , 23 * 8, 16 },
|
||||
};
|
||||
|
||||
// TIMER: 9999
|
||||
Text text_timer { //Show frequency stats in text mode
|
||||
Text text_timer { //Show frequency stats in text mode
|
||||
{ 0, 7 * 16 , 11 * 8, 16 },
|
||||
};
|
||||
|
||||
@@ -311,23 +262,23 @@ namespace ui {
|
||||
};
|
||||
|
||||
Button button_recon_setup {
|
||||
{ 240 - 7 * 8 , 2 * 16 , 7 * 8, 28 },
|
||||
{ SCREEN_W - 7 * 8 , 2 * 16 , 7 * 8, 28 },
|
||||
"CONFIG"
|
||||
};
|
||||
|
||||
Button button_looking_glass {
|
||||
{ 240 - 7 * 8 , 5 * 16 , 7 * 8, 28 },
|
||||
{ SCREEN_W - 7 * 8 , 5 * 16 , 7 * 8, 28 },
|
||||
"GLASS"
|
||||
};
|
||||
|
||||
// Button can be RECON or SCANNER
|
||||
Button button_scanner_mode {
|
||||
{ 240 - 7 * 8 , 8 * 16 , 7 * 8, 28 },
|
||||
{ SCREEN_W - 7 * 8 , 8 * 16 , 7 * 8, 28 },
|
||||
"RECON"
|
||||
};
|
||||
|
||||
Text file_name { //Show file used
|
||||
{ 0 , 8 * 16 + 6 , 240 - 7 * 8, 16 },
|
||||
Text file_name { //show file used
|
||||
{ 0 , 8 * 16 + 6 , SCREEN_W - 7 * 8, 16 },
|
||||
};
|
||||
|
||||
ButtonWithEncoder button_manual_start {
|
||||
@@ -396,8 +347,6 @@ namespace ui {
|
||||
"<REMOVE>"
|
||||
};
|
||||
|
||||
std::unique_ptr<ReconThread> recon_thread { };
|
||||
|
||||
MessageHandlerRegistration message_handler_coded_squelch {
|
||||
Message::ID::CodedSquelch,
|
||||
[this](const Message* const p) {
|
||||
@@ -406,14 +355,6 @@ namespace ui {
|
||||
}
|
||||
};
|
||||
|
||||
MessageHandlerRegistration message_handler_retune {
|
||||
Message::ID::Retune,
|
||||
[this](const Message* const p) {
|
||||
const auto message = *reinterpret_cast<const RetuneMessage*>(p);
|
||||
this->handle_retune(message.freq,message.range);
|
||||
}
|
||||
};
|
||||
|
||||
MessageHandlerRegistration message_handler_stats {
|
||||
Message::ID::ChannelStatistics,
|
||||
[this](const Message* const p) {
|
||||
@@ -421,7 +362,7 @@ namespace ui {
|
||||
}
|
||||
};
|
||||
// app save settings
|
||||
std::app_settings settings { };
|
||||
std::app_settings settings { };
|
||||
std::app_settings::AppSettings app_settings { };
|
||||
};
|
||||
|
||||
|
||||
@@ -34,131 +34,6 @@ using namespace portapack;
|
||||
|
||||
namespace ui {
|
||||
|
||||
bool ReconSetupLoadStrings( std::string source, std::string &input_file , std::string &output_file , uint32_t &recon_lock_duration , uint32_t &recon_lock_nb_match , int32_t &recon_squelch_level , uint32_t &recon_match_mode , int32_t &wait , uint32_t &lock_wait , int32_t &volume )
|
||||
{
|
||||
File settings_file;
|
||||
size_t length, file_position = 0;
|
||||
char * pos;
|
||||
char * line_start;
|
||||
char * line_end;
|
||||
char file_data[257];
|
||||
|
||||
uint32_t it = 0 ;
|
||||
uint32_t nb_params = 9 ;
|
||||
std::string params[ 9 ];
|
||||
|
||||
bool check_sd_card = (sd_card::status() == sd_card::Status::Mounted) ? true : false ;
|
||||
|
||||
if( check_sd_card )
|
||||
{
|
||||
auto result = settings_file.open( source );
|
||||
if( !result.is_valid() )
|
||||
{
|
||||
while( it < nb_params )
|
||||
{
|
||||
// Read a 256 bytes block from file
|
||||
settings_file.seek(file_position);
|
||||
memset(file_data, 0, 257);
|
||||
auto read_size = settings_file.read(file_data, 256);
|
||||
if (read_size.is_error())
|
||||
break ;
|
||||
file_position += 256;
|
||||
// Reset line_start to beginning of buffer
|
||||
line_start = file_data;
|
||||
pos=line_start;
|
||||
while ((line_end = strstr(line_start, "\x0A"))) {
|
||||
length = line_end - line_start - 1 ;
|
||||
params[ it ] = string( pos , length );
|
||||
it ++ ;
|
||||
line_start = line_end + 1;
|
||||
pos=line_start ;
|
||||
if (line_start - file_data >= 256)
|
||||
break;
|
||||
if( it >= nb_params )
|
||||
break ;
|
||||
}
|
||||
if (read_size.value() != 256)
|
||||
break; // End of file
|
||||
|
||||
// Restart at beginning of last incomplete line
|
||||
file_position -= (file_data + 256 - line_start);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if( it > 0 )
|
||||
input_file = params[ 0 ];
|
||||
else
|
||||
input_file = "RECON" ;
|
||||
|
||||
if( it > 1 )
|
||||
output_file= params[ 1 ];
|
||||
else
|
||||
output_file = "RECON_RESULTS" ;
|
||||
|
||||
if( it > 2 )
|
||||
recon_lock_duration = strtoll( params[ 2 ].c_str() , nullptr , 10 );
|
||||
else
|
||||
recon_lock_duration = 50 ;
|
||||
|
||||
if( it > 3 )
|
||||
recon_lock_nb_match = strtoll( params[ 3 ].c_str() , nullptr , 10 );
|
||||
else
|
||||
recon_lock_nb_match = 10 ;
|
||||
|
||||
if( it > 4 )
|
||||
recon_squelch_level = strtoll( params[ 4 ].c_str() , nullptr , 10 );
|
||||
else
|
||||
recon_squelch_level = -14 ;
|
||||
|
||||
if( it > 5 )
|
||||
recon_match_mode = strtoll( params[ 5 ].c_str() , nullptr , 10 );
|
||||
else
|
||||
recon_match_mode = 0 ;
|
||||
|
||||
if( it > 6 )
|
||||
wait = strtoll( params[ 6 ].c_str() , nullptr , 10 );
|
||||
else
|
||||
wait = 5000 ;
|
||||
|
||||
if( it > 7 )
|
||||
lock_wait = strtoll( params[ 7 ].c_str() , nullptr , 10 );
|
||||
else
|
||||
lock_wait = 1000 ;
|
||||
|
||||
if( it > 8 )
|
||||
volume = strtoll( params[ 8 ].c_str() , nullptr , 10 );
|
||||
else
|
||||
volume = 40 ;
|
||||
|
||||
if( it < nb_params )
|
||||
{
|
||||
/* bad number of params, signal defaults */
|
||||
return false ;
|
||||
}
|
||||
return true ;
|
||||
}
|
||||
|
||||
bool ReconSetupSaveStrings( std::string dest, std::string input_file , std::string output_file , uint32_t recon_lock_duration , uint32_t recon_lock_nb_match , int32_t recon_squelch_level , uint32_t recon_match_mode , int32_t wait , uint32_t lock_wait , int32_t volume )
|
||||
{
|
||||
File settings_file;
|
||||
|
||||
auto result = settings_file.create( dest );
|
||||
if( result.is_valid() )
|
||||
return false ;
|
||||
settings_file.write_line( input_file );
|
||||
settings_file.write_line( output_file );
|
||||
settings_file.write_line( to_string_dec_uint( recon_lock_duration ) );
|
||||
settings_file.write_line( to_string_dec_uint( recon_lock_nb_match ) );
|
||||
settings_file.write_line( to_string_dec_int( recon_squelch_level ) );
|
||||
settings_file.write_line( to_string_dec_uint( recon_match_mode ) );
|
||||
settings_file.write_line( to_string_dec_int( wait ) );
|
||||
settings_file.write_line( to_string_dec_uint( lock_wait ) );
|
||||
settings_file.write_line( to_string_dec_int( volume ) );
|
||||
return true ;
|
||||
}
|
||||
|
||||
ReconSetupViewMain::ReconSetupViewMain( NavigationView &nav , Rect parent_rect , std::string input_file , std::string output_file ) : View( parent_rect ) , _input_file { input_file } , _output_file { output_file }
|
||||
{
|
||||
hidden(true);
|
||||
@@ -178,8 +53,8 @@ namespace ui {
|
||||
checkbox_continuous.set_value( persistent_memory::recon_continuous() );
|
||||
checkbox_clear_output.set_value( persistent_memory::recon_clear_output() );
|
||||
|
||||
text_input_file.set( _input_file );
|
||||
button_output_file.set_text( _output_file );
|
||||
text_input_file.set( _input_file );
|
||||
button_output_file.set_text( _output_file );
|
||||
|
||||
button_load_freqs.on_select = [this, &nav](Button&) {
|
||||
auto open_view = nav.push<FileLoadView>(".TXT");
|
||||
@@ -203,7 +78,7 @@ namespace ui {
|
||||
std::string str_file_path = new_file_path.string();
|
||||
if (str_file_path.find(dir_filter) != string::npos) { // assert file from the FREQMAN folder
|
||||
_output_file = new_file_path.stem().string();
|
||||
button_output_file.set_text( _output_file );
|
||||
button_output_file.set_text( _output_file );
|
||||
} else {
|
||||
nav.display_modal("LOAD ERROR", "A valid file from\nFREQMAN directory is\nrequired.");
|
||||
}
|
||||
|
||||
@@ -20,6 +20,9 @@
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef _UI_RECON_SETTINGS
|
||||
#define _UI_RECON_SETTINGS
|
||||
|
||||
#include "serializer.hpp"
|
||||
#include "ui.hpp"
|
||||
#include "ui_widget.hpp"
|
||||
@@ -27,10 +30,38 @@
|
||||
#include "ui_navigation.hpp"
|
||||
#include "string_format.hpp"
|
||||
|
||||
namespace ui {
|
||||
// maximum usable freq
|
||||
#define MAX_UFREQ 7200000000
|
||||
|
||||
bool ReconSetupLoadStrings( std::string source, std::string &input_file , std::string &output_file , uint32_t &recon_lock_duration , uint32_t &recon_lock_nb_match , int32_t &recon_squelch_level , uint32_t &recon_match_mode , int32_t &wait , uint32_t &lock_wait , int32_t &volume );
|
||||
bool ReconSetupSaveStrings( std::string dest, const std::string input_file , const std::string output_file , const uint32_t recon_lock_duration , const uint32_t recon_lock_nb_match , int32_t recon_squelch_level , uint32_t recon_match_mode , int32_t wait , uint32_t lock_wait , int32_t volume );
|
||||
// 1Mhz helper
|
||||
#ifdef OneMHz
|
||||
#undef OneMHz
|
||||
#endif
|
||||
#define OneMHz 1000000
|
||||
|
||||
// modes
|
||||
#define RECON_MATCH_CONTINUOUS 0
|
||||
#define RECON_MATCH_SPARSE 1
|
||||
|
||||
// statistics update interval in ms (change here if the statistics API is changing it's pace)
|
||||
#define STATS_UPDATE_INTERVAL 100
|
||||
|
||||
// maximum lock duration
|
||||
#define RECON_MAX_LOCK_DURATION 9900
|
||||
|
||||
// default number of match to have a lock
|
||||
#define RECON_DEF_NB_MATCH 3
|
||||
#define RECON_MIN_LOCK_DURATION 100 // have to be >= and a multiple of STATS_UPDATE_INTERVAL
|
||||
#define RECON_DEF_WAIT_DURATION 1000 // will be incremented/decremented by STATS_UPDATE_INTERVAL steps
|
||||
|
||||
// screen size helper
|
||||
#define SCREEN_W 240
|
||||
//#define SCREEN_H 320
|
||||
|
||||
// recon settings nb params
|
||||
#define RECON_SETTINGS_NB_PARAMS 8
|
||||
|
||||
namespace ui {
|
||||
|
||||
class ReconSetupViewMain : public View {
|
||||
public:
|
||||
@@ -57,7 +88,7 @@ namespace ui {
|
||||
};
|
||||
Button button_output_file {
|
||||
{ 1 * 8 , 5 * 16 - 2, 18 * 8, 22 },
|
||||
"RECON_RESULTS"
|
||||
"RECON_RESULTS"
|
||||
};
|
||||
|
||||
Checkbox checkbox_autosave_freqs {
|
||||
@@ -86,7 +117,7 @@ namespace ui {
|
||||
|
||||
class ReconSetupViewMore : public View {
|
||||
public:
|
||||
ReconSetupViewMore( NavigationView& nav, Rect parent_rect , const uint32_t _recon_lock_duration , const uint32_t _recon_lock_nb_match , const uint32_t _recon_match_mode );
|
||||
ReconSetupViewMore( NavigationView& nav, Rect parent_rect , uint32_t _recon_lock_duration , uint32_t _recon_lock_nb_match , uint32_t _recon_match_mode );
|
||||
|
||||
void Save( uint32_t &recon_lock_duration , uint32_t &recon_lock_nb_match , uint32_t &recon_match_mode );
|
||||
|
||||
@@ -94,9 +125,9 @@ namespace ui {
|
||||
|
||||
private:
|
||||
|
||||
const uint32_t _recon_lock_duration = 50 ;
|
||||
const uint32_t _recon_lock_nb_match = 10 ;
|
||||
const uint32_t _recon_match_mode = 0 ;
|
||||
uint32_t _recon_lock_duration = STATS_UPDATE_INTERVAL ;
|
||||
uint32_t _recon_lock_nb_match = RECON_DEF_NB_MATCH ;
|
||||
uint32_t _recon_match_mode = RECON_MATCH_CONTINUOUS ;
|
||||
|
||||
Checkbox checkbox_load_freqs {
|
||||
{ 1 * 8, 12 },
|
||||
@@ -128,10 +159,10 @@ namespace ui {
|
||||
NumberField field_recon_lock_duration {
|
||||
{ 1 * 8, 132 }, // position X , Y
|
||||
4, // number of displayed digits (even empty)
|
||||
{ 50 , 990 }, // range of number
|
||||
10, // rotary encoder increment
|
||||
' ', // filling character
|
||||
false // can loop
|
||||
{ -RECON_MAX_LOCK_DURATION , RECON_MAX_LOCK_DURATION }, // range of number
|
||||
STATS_UPDATE_INTERVAL, // rotary encoder increment
|
||||
' ', // filling character
|
||||
false // can loop
|
||||
};
|
||||
Text text_recon_lock_nb {
|
||||
{ 1 * 8 , 162 , 25 * 8 , 22 },
|
||||
@@ -158,7 +189,7 @@ namespace ui {
|
||||
|
||||
class ReconSetupView : public View {
|
||||
public:
|
||||
ReconSetupView( NavigationView& nav , std::string _input_file , std::string _output_file , const uint32_t _recon_lock_duration , const uint32_t _recon_lock_nb_match , const uint32_t _recon_match_mode );
|
||||
ReconSetupView( NavigationView& nav , std::string _input_file , std::string _output_file , uint32_t _recon_lock_duration , uint32_t _recon_lock_nb_match , uint32_t _recon_match_mode );
|
||||
|
||||
std::function<void( std::vector<std::string> messages )> on_changed { };
|
||||
|
||||
@@ -172,11 +203,11 @@ namespace ui {
|
||||
|
||||
std::string input_file = { "RECON" };
|
||||
std::string output_file = { "RECON_RESULTS" };
|
||||
uint32_t recon_lock_duration = 50 ;
|
||||
uint32_t recon_lock_nb_match = 10 ;
|
||||
uint32_t recon_match_mode = 0 ;
|
||||
uint32_t recon_lock_duration = STATS_UPDATE_INTERVAL ;
|
||||
uint32_t recon_lock_nb_match = RECON_DEF_NB_MATCH ;
|
||||
uint32_t recon_match_mode = RECON_MATCH_CONTINUOUS ;
|
||||
|
||||
Rect view_rect = { 0, 3 * 8, 240, 230 };
|
||||
Rect view_rect = { 0, 3 * 8, SCREEN_W, 230 };
|
||||
|
||||
ReconSetupViewMain viewMain{ nav_ , view_rect , input_file , output_file };
|
||||
ReconSetupViewMore viewMore{ nav_ , view_rect , recon_lock_duration , recon_lock_nb_match , recon_match_mode };
|
||||
@@ -192,3 +223,5 @@ namespace ui {
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
#endif
|
||||
|
||||
@@ -88,8 +88,6 @@ public:
|
||||
|
||||
void focus() override;
|
||||
|
||||
void big_display_freq(rf::Frequency f);
|
||||
|
||||
const Style style_grey { // scanning
|
||||
.font = font::fixed_8x16,
|
||||
.background = Color::black(),
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -41,7 +41,6 @@ using namespace portapack;
|
||||
#include "freqman.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
SetDateTimeView::SetDateTimeView(
|
||||
NavigationView& nav
|
||||
) {
|
||||
@@ -306,7 +305,6 @@ namespace ui {
|
||||
checkbox_load_app_settings.set_value(persistent_memory::load_app_settings());
|
||||
checkbox_save_app_settings.set_value(persistent_memory::save_app_settings());
|
||||
|
||||
|
||||
button_save.on_select = [&nav, this](Button&) {
|
||||
persistent_memory::set_load_app_settings(checkbox_load_app_settings.value());
|
||||
persistent_memory::set_save_app_settings(checkbox_save_app_settings.value());
|
||||
@@ -405,11 +403,16 @@ namespace ui {
|
||||
&check_load_mem_at_startup,
|
||||
&button_save_mem_to_file,
|
||||
&button_load_mem_from_file,
|
||||
&button_load_mem_defaults,
|
||||
&button_return
|
||||
});
|
||||
|
||||
bool load_mem_at_startup = false ;
|
||||
File pmem_flag_file_handle ;
|
||||
|
||||
std::string folder = "SETTINGS";
|
||||
make_new_directory(folder);
|
||||
|
||||
std::string pmem_flag_file = "/SETTINGS/PMEM_FILEFLAG" ;
|
||||
auto result = pmem_flag_file_handle.open(pmem_flag_file);
|
||||
if(!result.is_valid())
|
||||
@@ -443,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!");
|
||||
}
|
||||
@@ -479,6 +482,17 @@ namespace ui {
|
||||
}
|
||||
};
|
||||
|
||||
button_load_mem_defaults.on_select = [&nav, this](Button&) {
|
||||
nav.push<ModalMessageView>(
|
||||
"Warning!",
|
||||
"This will reset the p.mem\nand set the default settings",
|
||||
YESNO,
|
||||
[this](bool choice) {
|
||||
if (choice) {
|
||||
portapack::persistent_memory::cache::defaults();
|
||||
}
|
||||
} );
|
||||
};
|
||||
|
||||
button_return.on_select = [&nav, this](Button&) {
|
||||
nav.pop();
|
||||
|
||||
@@ -314,7 +314,7 @@ private:
|
||||
25,
|
||||
"Save app settings"
|
||||
};
|
||||
|
||||
|
||||
Button button_save {
|
||||
{ 2 * 8, 16 * 16, 12 * 8, 32 },
|
||||
"Save"
|
||||
@@ -464,15 +464,20 @@ private:
|
||||
};
|
||||
|
||||
Button button_save_mem_to_file {
|
||||
{ 0, 9 * 16, 240, 32 },
|
||||
{ 0, 8 * 16, 240, 32 },
|
||||
"save p.mem to sdcard"
|
||||
};
|
||||
|
||||
Button button_load_mem_from_file {
|
||||
{ 0, 12 * 16, 240, 32 },
|
||||
{ 0, 10 * 16 + 4 , 240, 32 },
|
||||
"load p.mem from sdcard"
|
||||
};
|
||||
|
||||
|
||||
Button button_load_mem_defaults {
|
||||
{ 0, 12 * 16 + 8 , 240, 32 },
|
||||
"! reset p.mem, load defaults !"
|
||||
};
|
||||
|
||||
Button button_return {
|
||||
{ 16 * 8, 16 * 16, 12 * 8, 32 },
|
||||
"Return",
|
||||
|
||||
@@ -49,26 +49,26 @@ private:
|
||||
void on_tx_progress(const uint32_t progress, const bool done);
|
||||
|
||||
const std::string shape_strings[7] = {
|
||||
"CW",
|
||||
"Sine",
|
||||
"Triangle",
|
||||
"Saw up",
|
||||
"Saw down",
|
||||
"Square",
|
||||
"Noise"
|
||||
"CW-just carrier",
|
||||
"Sine signal ",
|
||||
"Triangle signal",
|
||||
"Saw up signal ",
|
||||
"Saw down signal",
|
||||
"Square signal ",
|
||||
"Noise signal " // using 16 bits LFSR register, 16 order polynomial feedback.
|
||||
};
|
||||
|
||||
bool auto_update { false };
|
||||
|
||||
Labels labels {
|
||||
{ { 6 * 8, 4 + 10 }, "Shape:", Color::light_grey() },
|
||||
{ { 7 * 8, 7 * 8 }, "Tone: Hz", Color::light_grey() },
|
||||
{ { 3 * 8, 4 + 10 }, "Shape:", Color::light_grey() },
|
||||
{ { 6 * 8, 7 * 8 }, "Tone: Hz", Color::light_grey() },
|
||||
{ { 22 * 8, 15 * 8 + 4 }, "s.", Color::light_grey() },
|
||||
{ { 8 * 8, 20 * 8 }, "Modulation: FM", Color::light_grey() }
|
||||
};
|
||||
|
||||
ImageOptionsField options_shape {
|
||||
{ 13 * 8, 4, 32, 32 },
|
||||
{ 10 * 8, 4, 32, 32 },
|
||||
Color::white(),
|
||||
Color::black(),
|
||||
{
|
||||
@@ -83,7 +83,7 @@ private:
|
||||
};
|
||||
|
||||
Text text_shape {
|
||||
{ 18 * 8, 4 + 10, 8 * 8, 16 },
|
||||
{ 15 * 8, 4 + 10, 8 * 8, 16 },
|
||||
""
|
||||
};
|
||||
|
||||
@@ -94,12 +94,12 @@ private:
|
||||
};
|
||||
|
||||
Button button_update {
|
||||
{ 6 * 8, 10 * 8, 8 * 8, 3 * 8 },
|
||||
{ 5 * 8, 10 * 8, 8 * 8, 3 * 8 },
|
||||
"Update"
|
||||
};
|
||||
|
||||
Checkbox checkbox_auto {
|
||||
{ 16 * 8, 10 * 8 },
|
||||
{ 15 * 8, 10 * 8 },
|
||||
4,
|
||||
"Auto"
|
||||
};
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -0,0 +1,233 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Bernd Herzog
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ui_spectrum_painter.hpp"
|
||||
#include "cpld_update.hpp"
|
||||
#include "bmp.hpp"
|
||||
#include "baseband_api.hpp"
|
||||
|
||||
#include "ui_fileman.hpp"
|
||||
#include "io_file.hpp"
|
||||
#include "file.hpp"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
SpectrumPainterView::SpectrumPainterView(
|
||||
NavigationView& nav
|
||||
) : nav_ (nav) {
|
||||
baseband::run_image(portapack::spi_flash::image_tag_spectrum_painter);
|
||||
|
||||
add_children({
|
||||
&labels,
|
||||
&tab_view,
|
||||
&input_image,
|
||||
&input_text,
|
||||
&progressbar,
|
||||
&field_frequency,
|
||||
&field_rfgain,
|
||||
&field_rfamp,
|
||||
&check_loop,
|
||||
&button_play,
|
||||
&option_bandwidth,
|
||||
&field_duration,
|
||||
&field_pause,
|
||||
});
|
||||
|
||||
Rect view_rect = { 0, 3 * 8, 240, 80 };
|
||||
input_image.set_parent_rect(view_rect);
|
||||
input_text.set_parent_rect(view_rect);
|
||||
|
||||
field_frequency.set_value(target_frequency());
|
||||
field_frequency.set_step(5000);
|
||||
field_frequency.on_change = [this](rf::Frequency f) {
|
||||
this->on_target_frequency_changed(f);
|
||||
};
|
||||
field_frequency.on_edit = [this, &nav]() {
|
||||
auto new_view = nav.push<FrequencyKeypadView>(this->target_frequency());
|
||||
new_view->on_changed = [this](rf::Frequency f) {
|
||||
this->on_target_frequency_changed(f);
|
||||
this->field_frequency.set_value(f);
|
||||
};
|
||||
};
|
||||
|
||||
tx_gain = 10;
|
||||
field_rfgain.set_value(tx_gain); // Initial default value (-12 dB's max ).
|
||||
field_rfgain.on_change = [this](int32_t v) { // allow initial value change just after opened file.
|
||||
tx_gain = v;
|
||||
portapack::transmitter_model.set_tx_gain(tx_gain);
|
||||
};
|
||||
|
||||
field_rfamp.set_value(rf_amp ? 14 : 0); // Initial default value True. (TX RF amp on , +14dB's)
|
||||
field_rfamp.on_change = [this](int32_t v) { // allow initial value change just after opened file.
|
||||
rf_amp = (bool)v;
|
||||
portapack::transmitter_model.set_rf_amp(rf_amp);
|
||||
};
|
||||
|
||||
input_image.on_input_avaliable = [this]() {
|
||||
image_input_avaliable = true;
|
||||
};
|
||||
|
||||
button_play.on_select = [this](ImageButton&) {
|
||||
if (tx_active == false) {
|
||||
tx_mode = tab_view.selected();
|
||||
|
||||
if (tx_mode == 0 && image_input_avaliable == false)
|
||||
return;
|
||||
|
||||
//Enable Bias Tee if selected
|
||||
radio::set_antenna_bias(portapack::get_antenna_bias());
|
||||
|
||||
radio::enable({
|
||||
portapack::receiver_model.tuning_frequency(),
|
||||
3072000U,
|
||||
1750000,
|
||||
rf::Direction::Transmit,
|
||||
rf_amp,
|
||||
static_cast<int8_t>(portapack::receiver_model.lna()),
|
||||
static_cast<int8_t>(portapack::receiver_model.vga())
|
||||
});
|
||||
|
||||
if (portapack::persistent_memory::stealth_mode()){
|
||||
DisplaySleepMessage message;
|
||||
EventDispatcher::send_message(message);
|
||||
}
|
||||
|
||||
button_play.set_bitmap(&bitmap_stop);
|
||||
|
||||
if (tx_mode == 0) {
|
||||
tx_current_max_lines = input_image.get_height();
|
||||
tx_current_width = input_image.get_width();
|
||||
}
|
||||
else {
|
||||
tx_current_max_lines = input_text.get_height();
|
||||
tx_current_width = input_text.get_width();
|
||||
}
|
||||
|
||||
progressbar.set_max(tx_current_max_lines);
|
||||
progressbar.set_value(0);
|
||||
|
||||
baseband::set_spectrum_painter_config(tx_current_width, tx_current_max_lines, false, option_bandwidth.selected_index_value());
|
||||
}
|
||||
else {
|
||||
stop_tx();
|
||||
}
|
||||
};
|
||||
|
||||
option_bandwidth.on_change = [this](size_t, ui::OptionsField::value_t value) {
|
||||
baseband::set_spectrum_painter_config(tx_current_width, tx_current_max_lines, true, value);
|
||||
};
|
||||
|
||||
field_duration.set_value(10);
|
||||
field_pause.set_value(5);
|
||||
}
|
||||
|
||||
void SpectrumPainterView::start_tx() {
|
||||
tx_current_line = 0;
|
||||
tx_active = true;
|
||||
tx_timestamp_start = chTimeNow();
|
||||
}
|
||||
|
||||
void SpectrumPainterView::stop_tx() {
|
||||
button_play.set_bitmap(&bitmap_play);
|
||||
portapack::transmitter_model.disable();
|
||||
tx_active = false;
|
||||
tx_current_line = 0;
|
||||
}
|
||||
|
||||
void SpectrumPainterView::frame_sync() {
|
||||
if (tx_active) {
|
||||
if (fifo->is_empty()) {
|
||||
|
||||
int32_t sequence_duration = (field_duration.value() + ( check_loop.value() ? field_pause.value() : 0)) * 1000;
|
||||
int32_t sequence_time = tx_time_elapsed() % sequence_duration;
|
||||
bool is_pausing = sequence_time > field_duration.value() * 1000;
|
||||
|
||||
if (is_pausing) {
|
||||
fifo->in(std::vector<uint8_t>(tx_current_width));
|
||||
} else {
|
||||
auto current_time_line = sequence_time * tx_current_max_lines / (field_duration.value() * 1000);
|
||||
|
||||
if (tx_current_line > current_time_line && !check_loop.value()) {
|
||||
fifo->in(std::vector<uint8_t>(tx_current_width));
|
||||
stop_tx();
|
||||
return;
|
||||
}
|
||||
|
||||
tx_current_line = current_time_line;
|
||||
progressbar.set_value(current_time_line);
|
||||
|
||||
if (tx_mode == 0) {
|
||||
std::vector<uint8_t> line = input_image.get_line(current_time_line);
|
||||
fifo->in(line);
|
||||
}
|
||||
else {
|
||||
std::vector<uint8_t> line = input_text.get_line(current_time_line);
|
||||
fifo->in(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SpectrumPainterView::~SpectrumPainterView() {
|
||||
portapack::transmitter_model.disable();
|
||||
hackrf::cpld::load_sram_no_verify();
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
void SpectrumPainterView::focus() {
|
||||
tab_view.focus();
|
||||
}
|
||||
|
||||
void SpectrumPainterView::on_target_frequency_changed(rf::Frequency f) {
|
||||
set_target_frequency(f);
|
||||
}
|
||||
|
||||
void SpectrumPainterView::set_target_frequency(const rf::Frequency new_value) {
|
||||
portapack::persistent_memory::set_tuned_frequency(new_value);
|
||||
}
|
||||
|
||||
rf::Frequency SpectrumPainterView::target_frequency() const {
|
||||
return portapack::persistent_memory::tuned_frequency();
|
||||
}
|
||||
|
||||
void SpectrumPainterView::paint(Painter& painter) {
|
||||
View::paint(painter);
|
||||
|
||||
size_t c;
|
||||
Point pos = { 0, screen_pos().y() + 8 + footer_location };
|
||||
|
||||
for (c = 0; c < 20; c++) {
|
||||
painter.draw_bitmap(
|
||||
pos,
|
||||
bitmap_stripes,
|
||||
ui::Color(191, 191, 0),
|
||||
ui::Color::black()
|
||||
);
|
||||
if (c != 9)
|
||||
pos += { 24, 0 };
|
||||
else
|
||||
pos = { 0, screen_pos().y() + 8 + footer_location + 32 + 8 };
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Bernd Herzog
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_widget.hpp"
|
||||
|
||||
#include "ui_navigation.hpp"
|
||||
#include "ui_tabview.hpp"
|
||||
#include "capture_app.hpp"
|
||||
#include "baseband_api.hpp"
|
||||
|
||||
#include "portapack.hpp"
|
||||
#include "message.hpp"
|
||||
|
||||
#include "ui_spectrum_painter_image.hpp"
|
||||
#include "ui_spectrum_painter_text.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
class SpectrumPainterView : public View {
|
||||
public:
|
||||
SpectrumPainterView(NavigationView& nav);
|
||||
~SpectrumPainterView();
|
||||
|
||||
SpectrumPainterView(const SpectrumPainterView&) = delete;
|
||||
SpectrumPainterView(SpectrumPainterView&&) = delete;
|
||||
SpectrumPainterView& operator=(const SpectrumPainterView&) = delete;
|
||||
SpectrumPainterView& operator=(SpectrumPainterView&&) = delete;
|
||||
|
||||
void focus() override;
|
||||
void paint(Painter& painter) override;
|
||||
|
||||
std::string title() const override { return "Spec.Painter"; };
|
||||
|
||||
private:
|
||||
void on_target_frequency_changed(rf::Frequency f);
|
||||
void set_target_frequency(const rf::Frequency new_value);
|
||||
rf::Frequency target_frequency() const;
|
||||
|
||||
NavigationView& nav_;
|
||||
bool image_input_avaliable { false };
|
||||
bool tx_active { false };
|
||||
uint32_t tx_mode { 0 };
|
||||
uint16_t tx_current_line { 0 };
|
||||
uint16_t tx_current_max_lines { 0 };
|
||||
uint16_t tx_current_width { 0 };
|
||||
systime_t tx_timestamp_start { 0 };
|
||||
|
||||
inline uint32_t tx_time_elapsed() {
|
||||
auto now = chTimeNow();
|
||||
return now - tx_timestamp_start;
|
||||
}
|
||||
|
||||
int32_t tx_gain { 47 };
|
||||
bool rf_amp { false };
|
||||
|
||||
SpectrumInputImageView input_image { nav_ };
|
||||
SpectrumInputTextView input_text { nav_ };
|
||||
|
||||
std::array<View*, 2> input_views { { &input_image, &input_text } };
|
||||
|
||||
TabView tab_view {
|
||||
{ "Image", Color::white(), input_views[0] },
|
||||
{ "Text", Color::white(), input_views[1] }
|
||||
};
|
||||
|
||||
static constexpr int32_t footer_location = 15 * 16 + 8;
|
||||
ProgressBar progressbar {
|
||||
{ 4, footer_location - 16, 240-8, 16 }
|
||||
};
|
||||
|
||||
Labels labels {
|
||||
{ { 10 * 8, footer_location + 1 * 16 }, "GAIN A:", Color::light_grey() },
|
||||
{ { 1 * 8, footer_location + 2 * 16 }, "BW: Du: P:", Color::light_grey() },
|
||||
};
|
||||
|
||||
FrequencyField field_frequency {
|
||||
{ 0 * 8, footer_location + 1 * 16 },
|
||||
};
|
||||
|
||||
NumberField field_rfgain {
|
||||
{ 14 * 8, footer_location + 1 * 16 },
|
||||
2,
|
||||
{ 0, 47 },
|
||||
1,
|
||||
' '
|
||||
};
|
||||
|
||||
NumberField field_rfamp {
|
||||
{ 19 * 8, footer_location + 1 * 16 },
|
||||
2,
|
||||
{ 0, 14 },
|
||||
14,
|
||||
' '
|
||||
};
|
||||
|
||||
Checkbox check_loop {
|
||||
{ 21 * 8, footer_location + 1 * 16 },
|
||||
4,
|
||||
"Loop",
|
||||
true
|
||||
};
|
||||
|
||||
ImageButton button_play {
|
||||
{ 28 * 8, footer_location + 1 * 16, 2 * 8, 1 * 16 },
|
||||
&bitmap_play,
|
||||
Color::green(),
|
||||
Color::black()
|
||||
};
|
||||
|
||||
OptionsField option_bandwidth {
|
||||
{ 4 * 8, footer_location + 2 * 16 },
|
||||
5,
|
||||
{
|
||||
BW_OPTIONS
|
||||
}
|
||||
};
|
||||
|
||||
NumberField field_duration {
|
||||
{ 13 * 8, footer_location + 2 * 16 },
|
||||
3,
|
||||
{ 1, 999 },
|
||||
1,
|
||||
' '
|
||||
};
|
||||
|
||||
NumberField field_pause {
|
||||
{ 19 * 8, footer_location + 2 * 16 },
|
||||
2,
|
||||
{ 0, 99 },
|
||||
1,
|
||||
' '
|
||||
};
|
||||
|
||||
SpectrumPainterFIFO* fifo { nullptr };
|
||||
void start_tx();
|
||||
void frame_sync();
|
||||
void stop_tx();
|
||||
|
||||
MessageHandlerRegistration message_handler_fifo_signal {
|
||||
Message::ID::SpectrumPainterBufferResponseConfigure,
|
||||
[this](const Message* const p) {
|
||||
const auto message = static_cast<const SpectrumPainterBufferConfigureResponseMessage*>(p);
|
||||
this->fifo = message->fifo;
|
||||
this->start_tx();
|
||||
}
|
||||
};
|
||||
|
||||
MessageHandlerRegistration message_handler_sample {
|
||||
Message::ID::DisplayFrameSync,
|
||||
[this](const Message* const) {
|
||||
this->frame_sync();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Bernd Herzog
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ui_spectrum_painter_image.hpp"
|
||||
#include "cpld_update.hpp"
|
||||
#include "bmp.hpp"
|
||||
#include "baseband_api.hpp"
|
||||
|
||||
#include "ui_fileman.hpp"
|
||||
#include "io_file.hpp"
|
||||
#include "file.hpp"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
SpectrumInputImageView::SpectrumInputImageView(NavigationView& nav) {
|
||||
hidden(true);
|
||||
|
||||
add_children({
|
||||
&button_load_image
|
||||
});
|
||||
|
||||
button_load_image.on_select = [this, &nav](Button&) {
|
||||
auto open_view = nav.push<FileLoadView>(".bmp");
|
||||
|
||||
constexpr auto data_directory = u"SPECTRUM";
|
||||
if (std::filesystem::is_directory(data_directory) == false) {
|
||||
if (make_new_directory(data_directory).ok())
|
||||
open_view->push_dir(data_directory);
|
||||
}
|
||||
else
|
||||
open_view->push_dir(data_directory);
|
||||
|
||||
open_view->on_changed = [this](std::filesystem::path new_file_path) {
|
||||
this->file = new_file_path.string();
|
||||
painted = false;
|
||||
this->set_dirty();
|
||||
|
||||
this->on_input_avaliable();
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
SpectrumInputImageView::~SpectrumInputImageView() {
|
||||
}
|
||||
|
||||
void SpectrumInputImageView::focus() {
|
||||
button_load_image.focus();
|
||||
}
|
||||
|
||||
bool SpectrumInputImageView::drawBMP_scaled(const ui::Rect r, const std::string file) {
|
||||
File bmpimage;
|
||||
size_t file_pos = 0;
|
||||
uint16_t pointer = 0;
|
||||
int16_t px = 0, py, zoom_factor = 0;
|
||||
bmp_header_t bmp_header;
|
||||
char buffer[257];
|
||||
ui::Color line_buffer[240];
|
||||
|
||||
auto result = bmpimage.open(file);
|
||||
if(result.is_valid())
|
||||
return false;
|
||||
|
||||
bmpimage.seek(file_pos);
|
||||
auto read_size = bmpimage.read(&bmp_header, sizeof(bmp_header));
|
||||
if (!((bmp_header.signature == 0x4D42) && // "BM" Signature
|
||||
(bmp_header.planes == 1) && // Seems always to be 1
|
||||
(bmp_header.compression == 0 || bmp_header.compression == 3 ))) { // No compression
|
||||
return false;
|
||||
}
|
||||
|
||||
switch(bmp_header.bpp) {
|
||||
case 16:
|
||||
file_pos = 0x36;
|
||||
memset(buffer, 0, 16);
|
||||
bmpimage.read(buffer, 16);
|
||||
if(buffer[1] == 0x7C)
|
||||
type = 3; // A1R5G5B5
|
||||
else
|
||||
type = 0; // R5G6B5
|
||||
break;
|
||||
case 24:
|
||||
type = 1;
|
||||
break;
|
||||
case 32:
|
||||
default:
|
||||
type = 2;
|
||||
break;
|
||||
}
|
||||
|
||||
width = bmp_header.width;
|
||||
height = bmp_header.height;
|
||||
|
||||
data_start = file_pos = bmp_header.image_data;
|
||||
|
||||
while (r.width() < (width >> zoom_factor) || r.height() < (height >> zoom_factor)) {
|
||||
zoom_factor++;
|
||||
}
|
||||
|
||||
py = height + 16;
|
||||
|
||||
while(1) {
|
||||
while(px < width) {
|
||||
bmpimage.seek(file_pos);
|
||||
memset(buffer, 0, 257);
|
||||
read_size = bmpimage.read(buffer, 256);
|
||||
if (read_size.is_error())
|
||||
return false; // Read error
|
||||
|
||||
pointer = 0;
|
||||
while(pointer < 256) {
|
||||
if(pointer + 4 > 256)
|
||||
break;
|
||||
switch(type) {
|
||||
case 0: // R5G6B5
|
||||
if ((((1 << zoom_factor) - 1) & px) == 0x00)
|
||||
line_buffer[px >> zoom_factor] = ui::Color(((uint16_t)buffer[pointer] & 0x1F) | ((uint16_t)buffer[pointer] & 0xE0) << 1 | ((uint16_t)buffer[pointer + 1] & 0x7F) << 9);
|
||||
|
||||
pointer += 2;
|
||||
file_pos += 2;
|
||||
break;
|
||||
|
||||
case 3: // A1R5G5B5
|
||||
if ((((1 << zoom_factor) - 1) & px) == 0x00)
|
||||
line_buffer[px >> zoom_factor] = ui::Color((uint16_t)buffer[pointer] | ((uint16_t)buffer[pointer + 1] << 8));
|
||||
|
||||
pointer += 2;
|
||||
file_pos += 2;
|
||||
break;
|
||||
|
||||
case 1: // 24
|
||||
default:
|
||||
if ((((1 << zoom_factor) - 1) & px) == 0x00)
|
||||
line_buffer[px >> zoom_factor] = ui::Color(buffer[pointer + 2], buffer[pointer + 1], buffer[pointer]);
|
||||
pointer += 3;
|
||||
file_pos += 3;
|
||||
break;
|
||||
|
||||
case 2: // 32
|
||||
if ((((1 << zoom_factor) - 1) & px) == 0x00)
|
||||
line_buffer[px >> zoom_factor] = ui::Color(buffer[pointer + 2], buffer[pointer + 1], buffer[pointer]);
|
||||
pointer += 4;
|
||||
file_pos += 4;
|
||||
break;
|
||||
}
|
||||
|
||||
px++;
|
||||
if(px >= width) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(read_size.value() != 256)
|
||||
break;
|
||||
}
|
||||
|
||||
if ((((1 << zoom_factor) - 1) & py) == 0x00)
|
||||
portapack::display.render_line({ r.left(), r.top() + (py >> zoom_factor) }, px >> zoom_factor, line_buffer);
|
||||
|
||||
px = 0;
|
||||
py--;
|
||||
|
||||
if(read_size.value() < 256 || py < 0)
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
uint16_t SpectrumInputImageView::get_width(){
|
||||
return this->width;
|
||||
}
|
||||
|
||||
uint16_t SpectrumInputImageView::get_height(){
|
||||
return this->height;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> SpectrumInputImageView::get_line(uint16_t y) {
|
||||
File bmpimage;
|
||||
bmpimage.open(this->file);
|
||||
|
||||
//seek to line
|
||||
uint32_t line_size = width * (type == 2 ? 4 : (type == 1 ? 3 : 2));
|
||||
uint32_t line_offset = y * line_size;
|
||||
bmpimage.seek(data_start + line_offset);
|
||||
|
||||
// allocate memory and read
|
||||
auto buffer = new uint8_t[line_size];
|
||||
auto bytes_read = bmpimage.read(buffer, line_size);
|
||||
|
||||
// greyscale
|
||||
auto grey_buffer = new uint8_t[width];
|
||||
int pointer = 0;
|
||||
for (uint16_t px = 0; px < width; px++) {
|
||||
ui::Color color;
|
||||
switch(type) {
|
||||
case 0: // R5G6B5
|
||||
pointer = px * 2;
|
||||
color = ui::Color(((uint16_t)buffer[pointer] & 0x1F) | ((uint16_t)buffer[pointer] & 0xE0) << 1 | ((uint16_t)buffer[pointer + 1] & 0x7F) << 9);
|
||||
break;
|
||||
|
||||
case 3: // A1R5G5B5
|
||||
pointer = px * 2;
|
||||
color = ui::Color((uint16_t)buffer[pointer] | ((uint16_t)buffer[pointer + 1] << 8));
|
||||
break;
|
||||
|
||||
case 1: // 24
|
||||
default:
|
||||
pointer = px * 3;
|
||||
color = ui::Color(buffer[pointer + 2], buffer[pointer + 1], buffer[pointer]);
|
||||
break;
|
||||
|
||||
case 2: // 32
|
||||
pointer = px * 4;
|
||||
color = ui::Color(buffer[pointer + 2], buffer[pointer + 1], buffer[pointer]);
|
||||
break;
|
||||
}
|
||||
|
||||
grey_buffer[px] = color.to_greyscale();
|
||||
}
|
||||
|
||||
delete buffer;
|
||||
|
||||
std::vector<uint8_t> values(width);
|
||||
for(int i = 0; i < width; i++) {
|
||||
values[i] = grey_buffer[i];
|
||||
}
|
||||
|
||||
delete grey_buffer;
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
void SpectrumInputImageView::paint(Painter& painter) {
|
||||
painter.fill_rectangle(
|
||||
{{0, 40}, {240, 204}},
|
||||
style().background
|
||||
);
|
||||
|
||||
if (!painted) {
|
||||
// This is very slow for big pictures. Do only once.
|
||||
this->drawBMP_scaled({{ 0, 40 }, {240, 160}}, this->file);
|
||||
painted = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Bernd Herzog
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_widget.hpp"
|
||||
|
||||
#include "ui_navigation.hpp"
|
||||
#include "ui_tabview.hpp"
|
||||
#include "ui_transmitter.hpp"
|
||||
#include "baseband_api.hpp"
|
||||
|
||||
#include "portapack.hpp"
|
||||
#include "message.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
class SpectrumInputImageView : public View {
|
||||
public:
|
||||
SpectrumInputImageView(NavigationView& nav);
|
||||
~SpectrumInputImageView();
|
||||
|
||||
void focus() override;
|
||||
void paint(Painter&) override;
|
||||
|
||||
uint16_t get_width();
|
||||
uint16_t get_height();
|
||||
std::vector<uint8_t> get_line(uint16_t);
|
||||
|
||||
std::function<void()> on_input_avaliable { };
|
||||
|
||||
private:
|
||||
bool painted {false};
|
||||
std::string file {""};
|
||||
uint16_t width {0};
|
||||
uint16_t height {0};
|
||||
uint8_t type {0};
|
||||
uint32_t data_start {0};
|
||||
|
||||
Button button_load_image {
|
||||
{ 0 * 8, 11 * 16 - 4, 30 * 8, 28 },
|
||||
"Load Image ..."
|
||||
};
|
||||
|
||||
bool drawBMP_scaled(const ui::Rect r, const std::string file);
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Bernd Herzog
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ui_spectrum_painter_text.hpp"
|
||||
#include "cpld_update.hpp"
|
||||
#include "bmp.hpp"
|
||||
#include "baseband_api.hpp"
|
||||
|
||||
#include "ui_fileman.hpp"
|
||||
#include "io_file.hpp"
|
||||
#include "file.hpp"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
#include "ui_font_fixed_8x16.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
SpectrumInputTextView::SpectrumInputTextView(NavigationView& nav) {
|
||||
hidden(true);
|
||||
|
||||
add_children({
|
||||
&text_message_0,
|
||||
&text_message_1,
|
||||
&text_message_2,
|
||||
&text_message_3,
|
||||
&text_message_4,
|
||||
&text_message_5,
|
||||
&text_message_6,
|
||||
&text_message_7,
|
||||
&text_message_8,
|
||||
&text_message_9,
|
||||
&button_message
|
||||
});
|
||||
|
||||
button_message.on_select = [this, &nav](Button&) {
|
||||
this->on_set_text(nav);
|
||||
};
|
||||
}
|
||||
|
||||
SpectrumInputTextView::~SpectrumInputTextView() {
|
||||
}
|
||||
|
||||
void SpectrumInputTextView::on_set_text(NavigationView& nav) {
|
||||
text_prompt(nav, buffer, 300);
|
||||
}
|
||||
|
||||
void SpectrumInputTextView::focus() {
|
||||
button_message.focus();
|
||||
}
|
||||
|
||||
void SpectrumInputTextView::paint(Painter& painter) {
|
||||
message = buffer;
|
||||
for (uint32_t i = 0 ; i < text_message.size(); i++) {
|
||||
if (message.length() > i * 30)
|
||||
text_message[i]->set(message.substr(i * 30, 30));
|
||||
else
|
||||
text_message[i]->set("");
|
||||
}
|
||||
|
||||
painter.fill_rectangle(
|
||||
{{0, 40}, {240, 204}},
|
||||
style().background
|
||||
);
|
||||
}
|
||||
|
||||
constexpr uint32_t pixel_repeat = 32;
|
||||
uint16_t SpectrumInputTextView::get_width(){
|
||||
return 16 * pixel_repeat;
|
||||
}
|
||||
|
||||
uint16_t SpectrumInputTextView::get_height(){
|
||||
return this->message.length() * 8;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> SpectrumInputTextView::get_line(uint16_t y) {
|
||||
auto character_position = y / 8;
|
||||
auto character = this->message[character_position];
|
||||
auto glyph_data = ui::font::fixed_8x16.glyph(character).pixels();
|
||||
|
||||
auto line_in_character = y % 8;
|
||||
std::vector<uint8_t> data(16 * pixel_repeat);
|
||||
|
||||
for (uint32_t index = 0; index < 16; index++) {
|
||||
auto glyph_byte = index;
|
||||
auto glyph_bit = line_in_character;
|
||||
uint8_t glyph_pixel = (glyph_data[glyph_byte] & (1 << glyph_bit)) >> glyph_bit;
|
||||
|
||||
for (uint32_t j = 0; j < pixel_repeat; j++)
|
||||
data[index*pixel_repeat + j] = glyph_pixel * 255;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Bernd Herzog
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_widget.hpp"
|
||||
|
||||
#include "ui_navigation.hpp"
|
||||
#include "ui_tabview.hpp"
|
||||
#include "ui_transmitter.hpp"
|
||||
#include "baseband_api.hpp"
|
||||
|
||||
#include "portapack.hpp"
|
||||
#include "message.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
class SpectrumInputTextView : public View {
|
||||
public:
|
||||
SpectrumInputTextView(NavigationView& nav);
|
||||
~SpectrumInputTextView();
|
||||
|
||||
void focus() override;
|
||||
void paint(Painter&) override;
|
||||
|
||||
uint16_t get_width();
|
||||
uint16_t get_height();
|
||||
std::vector<uint8_t> get_line(uint16_t);
|
||||
|
||||
private:
|
||||
std::string buffer { "PORTAPACK" };
|
||||
std::string message { };
|
||||
void on_set_text(NavigationView& nav);
|
||||
|
||||
Text text_message_0 {
|
||||
{ 0 * 8, 0 * 16, 30 * 8, 16 },
|
||||
""
|
||||
};
|
||||
|
||||
Text text_message_1 {
|
||||
{ 0 * 8, 1 * 16, 30 * 8, 16 },
|
||||
""
|
||||
};
|
||||
|
||||
Text text_message_2 {
|
||||
{ 0 * 8, 2 * 16, 30 * 8, 16 },
|
||||
""
|
||||
};
|
||||
|
||||
Text text_message_3 {
|
||||
{ 0 * 8, 3 * 16, 30 * 8, 16 },
|
||||
""
|
||||
};
|
||||
|
||||
Text text_message_4 {
|
||||
{ 0 * 8, 4 * 16, 30 * 8, 16 },
|
||||
""
|
||||
};
|
||||
|
||||
Text text_message_5 {
|
||||
{ 0 * 8, 5 * 16, 30 * 8, 16 },
|
||||
""
|
||||
};
|
||||
|
||||
Text text_message_6 {
|
||||
{ 0 * 8, 6 * 16, 30 * 8, 16 },
|
||||
""
|
||||
};
|
||||
|
||||
Text text_message_7 {
|
||||
{ 0 * 8, 7 * 16, 30 * 8, 16 },
|
||||
""
|
||||
};
|
||||
|
||||
Text text_message_8 {
|
||||
{ 0 * 8, 8 * 16, 30 * 8, 16 },
|
||||
""
|
||||
};
|
||||
|
||||
Text text_message_9 {
|
||||
{ 0 * 8, 9 * 16, 30 * 8, 16 },
|
||||
""
|
||||
};
|
||||
|
||||
std::array<Text*, 10> text_message { {
|
||||
&text_message_0,
|
||||
&text_message_1,
|
||||
&text_message_2,
|
||||
&text_message_3,
|
||||
&text_message_4,
|
||||
&text_message_5,
|
||||
&text_message_6,
|
||||
&text_message_7,
|
||||
&text_message_8,
|
||||
&text_message_9,
|
||||
} };
|
||||
|
||||
Button button_message {
|
||||
{ 0 * 8, 11 * 16 - 4, 30 * 8, 28 },
|
||||
"Set message"
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
@@ -298,6 +299,11 @@ void set_siggen_config(const uint32_t bw, const uint32_t shape, const uint32_t d
|
||||
send_message(&message);
|
||||
}
|
||||
|
||||
void set_spectrum_painter_config(const uint16_t width, const uint16_t height, bool update, int32_t bw){
|
||||
SpectrumPainterBufferConfigureRequestMessage message { width, height, update, bw };
|
||||
send_message(&message);
|
||||
}
|
||||
|
||||
static bool baseband_image_running = false;
|
||||
|
||||
void run_image(const portapack::spi_flash::image_tag_t image_tag) {
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -88,6 +91,7 @@ void set_rds_data(const uint16_t message_length);
|
||||
void set_spectrum(const size_t sampling_rate, const size_t trigger);
|
||||
void set_siggen_tone(const uint32_t tone);
|
||||
void set_siggen_config(const uint32_t bw, const uint32_t shape, const uint32_t duration);
|
||||
void set_spectrum_painter_config(const uint16_t width, const uint16_t height, bool update, int32_t bw);
|
||||
void request_beep();
|
||||
|
||||
void run_image(const portapack::spi_flash::image_tag_t image_tag);
|
||||
|
||||
@@ -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 */
|
||||
|
||||
|
||||
+171
-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 {
|
||||
@@ -250,6 +342,15 @@ std::string filesystem_error::what() const {
|
||||
}
|
||||
}
|
||||
|
||||
path path::parent_path() const {
|
||||
const auto index = _s.find_last_of(preferred_separator);
|
||||
if( index == _s.npos ) {
|
||||
return { }; // NB: Deviation from STL.
|
||||
} else {
|
||||
return _s.substr(0, index);
|
||||
}
|
||||
}
|
||||
|
||||
path path::extension() const {
|
||||
const auto t = filename().native();
|
||||
const auto index = t.find_last_of(u'.');
|
||||
@@ -296,6 +397,14 @@ path& path::replace_extension(const path& replacement) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool operator==(const path& lhs, const path& rhs) {
|
||||
return lhs.native() == rhs.native();
|
||||
}
|
||||
|
||||
bool operator!=(const path& lhs, const path& rhs) {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
bool operator<(const path& lhs, const path& rhs) {
|
||||
return lhs.native() < rhs.native();
|
||||
}
|
||||
@@ -304,14 +413,28 @@ bool operator>(const path& lhs, const path& rhs) {
|
||||
return lhs.native() > rhs.native();
|
||||
}
|
||||
|
||||
path operator+(const path& lhs, const path& rhs) {
|
||||
path result = lhs;
|
||||
result += rhs;
|
||||
return result;
|
||||
}
|
||||
|
||||
path operator/(const path& lhs, const path& rhs) {
|
||||
path result = lhs;
|
||||
result /= rhs;
|
||||
return result;
|
||||
}
|
||||
|
||||
directory_iterator::directory_iterator(
|
||||
std::filesystem::path path,
|
||||
std::filesystem::path wild
|
||||
) : 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 ) {
|
||||
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...
|
||||
}
|
||||
@@ -333,6 +456,20 @@ bool is_regular_file(const file_status s) {
|
||||
return !(s & AM_DIR);
|
||||
}
|
||||
|
||||
bool file_exists(const path& file_path) {
|
||||
FILINFO filinfo;
|
||||
auto fr = f_stat(reinterpret_cast<const TCHAR*>(file_path.c_str()), &filinfo);
|
||||
|
||||
return fr == FR_OK;
|
||||
}
|
||||
|
||||
bool is_directory(const path& file_path) {
|
||||
FILINFO filinfo;
|
||||
auto fr = f_stat(reinterpret_cast<const TCHAR*>(file_path.c_str()), &filinfo);
|
||||
|
||||
return fr == FR_OK && is_directory(static_cast<file_status>(filinfo.fattrib));
|
||||
}
|
||||
|
||||
space_info space(const path& p) {
|
||||
DWORD free_clusters { 0 };
|
||||
FATFS* fs;
|
||||
|
||||
@@ -59,6 +59,10 @@ struct filesystem_error {
|
||||
|
||||
std::string what() const;
|
||||
|
||||
bool ok() const {
|
||||
return err == FR_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
uint32_t err { FR_OK };
|
||||
};
|
||||
@@ -123,6 +127,7 @@ struct path {
|
||||
return *this;
|
||||
}
|
||||
|
||||
path parent_path() const;
|
||||
path extension() const;
|
||||
path filename() const;
|
||||
path stem() const;
|
||||
@@ -151,14 +156,25 @@ struct path {
|
||||
return *this;
|
||||
}
|
||||
|
||||
path& operator/=(const path& p) {
|
||||
if (_s.back() != preferred_separator)
|
||||
_s += preferred_separator;
|
||||
_s += p._s;
|
||||
return *this;
|
||||
}
|
||||
|
||||
path& replace_extension(const path& replacement = path());
|
||||
|
||||
private:
|
||||
string_type _s;
|
||||
};
|
||||
|
||||
bool operator==(const path& lhs, const path& rhs);
|
||||
bool operator!=(const path& lhs, const path& rhs);
|
||||
bool operator<(const path& lhs, const path& rhs);
|
||||
bool operator>(const path& lhs, const path& rhs);
|
||||
path operator+(const path& lhs, const path& rhs);
|
||||
path operator/(const path& lhs, const path& rhs);
|
||||
|
||||
using file_status = BYTE;
|
||||
|
||||
@@ -227,6 +243,8 @@ inline bool operator!=(const directory_iterator& lhs, const directory_iterator&
|
||||
|
||||
bool is_directory(const file_status s);
|
||||
bool is_regular_file(const file_status s);
|
||||
bool file_exists(const path& file_path);
|
||||
bool is_directory(const path& file_path);
|
||||
|
||||
space_info space(const path& p);
|
||||
|
||||
@@ -238,14 +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);
|
||||
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,19 @@ 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;
|
||||
// If EOF reached, insert 0x0A after, in case the last line doesn't have a C/R
|
||||
if (read_size.value() < 256)
|
||||
*(line_start + read_size.value()) = 0x0A;
|
||||
|
||||
// Look for complete lines in buffer
|
||||
while ((line_end = strstr(line_start, "\x0A"))) {
|
||||
|
||||
*line_end = 0; // Stop strstr() searches below at EOL
|
||||
modulation = -1 ;
|
||||
bandwidth = -1 ;
|
||||
step = -1 ;
|
||||
tone = -1 ;
|
||||
type=SINGLE ;
|
||||
type = ERROR_TYPE;
|
||||
|
||||
frequency_a = frequency_b = 0;
|
||||
// Read frequency
|
||||
@@ -162,6 +166,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=");
|
||||
@@ -229,7 +234,7 @@ bool load_freqman_file_ex(std::string& file_stem, freqman_db& db, bool load_freq
|
||||
{
|
||||
db.push_back({ frequency_a, frequency_b, description, type , modulation , bandwidth , step , tone });
|
||||
n++;
|
||||
if (n >= FREQMAN_MAX_PER_FILE) return true;
|
||||
if (n > FREQMAN_MAX_PER_FILE) return true;
|
||||
}
|
||||
|
||||
line_start = line_end + 1;
|
||||
|
||||
@@ -31,9 +31,10 @@
|
||||
#include "string_format.hpp"
|
||||
#include "ui_widget.hpp"
|
||||
|
||||
#define FREQMAN_DESC_MAX_LEN 30
|
||||
#define FREQMAN_MAX_PER_FILE 500 /* MAX PER FILES */
|
||||
#define FREQMAN_MAX_PER_FILE_STR "500" /*STRING OF FREQMAN_MAX_PER_FILE */
|
||||
#define FREQMAN_DESC_MAX_LEN 24 // This is the number of characters that can be drawn in front of "R: TEXT..." before taking a full screen line
|
||||
#define FREQMAN_MAX_PER_FILE 115 // Maximum of entries we can read. This is a hardware limit
|
||||
// It was tested and lowered to leave a bit of space to the caller
|
||||
#define FREQMAN_MAX_PER_FILE_STR "115" // STRING OF FREQMAN_MAX_PER_FILE
|
||||
|
||||
using namespace ui;
|
||||
using namespace std;
|
||||
@@ -86,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);
|
||||
}
|
||||
|
||||
|
||||
@@ -489,10 +489,13 @@ bool init() {
|
||||
|
||||
chThdSleepMilliseconds(10);
|
||||
|
||||
if( !portapack::cpld::update_if_necessary(portapack_cpld_config()) ) {
|
||||
portapack::cpld::CpldUpdateStatus result = portapack::cpld::update_if_necessary(portapack_cpld_config());
|
||||
if ( result == portapack::cpld::CpldUpdateStatus::Program_failed ) {
|
||||
|
||||
chThdSleepMilliseconds(10);
|
||||
// If using a "2021/12 QFP100", press and hold the left button while booting. Should only need to do once.
|
||||
if (load_config() != 3 && load_config() != 4){
|
||||
// Mode left (R1) and right (R2,H2,H2+) bypass going into hackrf mode after failing CPLD update
|
||||
// Mode center (autodetect), up (R1) and down (R2,H2,H2+) will go into hackrf mode after failing CPLD update
|
||||
if (load_config() != 3 /* left */ && load_config() != 4 /* right */){
|
||||
shutdown_base();
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -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__*/
|
||||
|
||||
@@ -81,16 +81,21 @@ void Manager::feed(const Frame& frame) {
|
||||
const auto touch_raw = frame.touch;
|
||||
//const auto touch_stable = touch_debounce.state();
|
||||
const auto touch_stable = frame.touch;
|
||||
bool touch_pressure = false;
|
||||
bool touch_down_pressure = false;
|
||||
bool touch_up_pressure = false;
|
||||
|
||||
// Only feed coordinate averaging if there's a touch.
|
||||
// TODO: Separate threshold to gate coordinates for filtering?
|
||||
if( touch_raw ) {
|
||||
const auto metrics = calculate_metrics(frame);
|
||||
|
||||
// TODO: Add touch pressure hysteresis?
|
||||
touch_pressure = (metrics.r < r_touch_threshold);
|
||||
if( touch_pressure ) {
|
||||
constexpr float r_touch_down_threshold = 3200.0f;
|
||||
constexpr float r_touch_up_threshold = r_touch_down_threshold * 2.0f;
|
||||
|
||||
touch_down_pressure = (metrics.r < r_touch_down_threshold);
|
||||
touch_up_pressure = (metrics.r < r_touch_up_threshold);
|
||||
|
||||
if( touch_down_pressure ) {
|
||||
filter_x.feed(metrics.x * 1024);
|
||||
filter_y.feed(metrics.y * 1024);
|
||||
}
|
||||
@@ -101,7 +106,7 @@ void Manager::feed(const Frame& frame) {
|
||||
|
||||
switch(state) {
|
||||
case State::NoTouch:
|
||||
if( touch_stable && touch_pressure && !persistent_memory::disable_touchscreen()) {
|
||||
if( touch_stable && touch_down_pressure && !persistent_memory::disable_touchscreen()) {
|
||||
if( point_stable() ) {
|
||||
state = State::TouchDetected;
|
||||
touch_started();
|
||||
@@ -110,7 +115,7 @@ void Manager::feed(const Frame& frame) {
|
||||
break;
|
||||
|
||||
case State::TouchDetected:
|
||||
if( touch_stable && touch_pressure ) {
|
||||
if( touch_stable && touch_up_pressure ) {
|
||||
touch_moved();
|
||||
} else {
|
||||
state = State::NoTouch;
|
||||
|
||||
@@ -219,7 +219,6 @@ private:
|
||||
TouchDetected,
|
||||
};
|
||||
|
||||
static constexpr float r_touch_threshold = 640;
|
||||
static constexpr size_t touch_count_threshold { 3 };
|
||||
static constexpr uint32_t touch_stable_bound { 8 };
|
||||
|
||||
|
||||
@@ -30,10 +30,6 @@
|
||||
|
||||
namespace ui {
|
||||
|
||||
void AlphanumView::paint(Painter&) {
|
||||
draw_cursor();
|
||||
}
|
||||
|
||||
AlphanumView::AlphanumView(
|
||||
NavigationView& nav,
|
||||
std::string& str,
|
||||
@@ -41,7 +37,7 @@ AlphanumView::AlphanumView(
|
||||
) : TextEntryView(nav, str, max_length)
|
||||
{
|
||||
size_t n;
|
||||
|
||||
|
||||
add_children({
|
||||
&button_mode,
|
||||
&text_raw,
|
||||
@@ -77,16 +73,7 @@ AlphanumView::AlphanumView(
|
||||
field_raw.set_value('0');
|
||||
field_raw.on_select = [this](NumberField&) {
|
||||
char_add(field_raw.value());
|
||||
update_text();
|
||||
};
|
||||
|
||||
button_ok.on_select = [this, &nav](Button&) {
|
||||
if (on_changed)
|
||||
on_changed(_str);
|
||||
nav.pop();
|
||||
};
|
||||
|
||||
update_text();
|
||||
}
|
||||
|
||||
void AlphanumView::set_mode(const uint32_t new_mode) {
|
||||
@@ -120,8 +107,6 @@ void AlphanumView::on_button(Button& button) {
|
||||
char_delete();
|
||||
else
|
||||
char_add(c);
|
||||
|
||||
update_text();
|
||||
}
|
||||
|
||||
bool AlphanumView::on_encoder(const EncoderEvent delta) {
|
||||
|
||||
@@ -40,7 +40,6 @@ public:
|
||||
AlphanumView& operator=(const AlphanumView&) = delete;
|
||||
AlphanumView& operator=(AlphanumView&&) = delete;
|
||||
|
||||
void paint(Painter& painter) override;
|
||||
bool on_encoder(const EncoderEvent delta) override;
|
||||
|
||||
private:
|
||||
|
||||
@@ -136,14 +136,22 @@ void BtnGridView::update_items() {
|
||||
more = false;
|
||||
|
||||
for (NewButton* item : menu_item_views) {
|
||||
if (i >= menu_items.size()) break;
|
||||
|
||||
// Assign item data to NewButtons according to offset
|
||||
item->set_text(menu_items[i + offset].text);
|
||||
item->set_bitmap(menu_items[i + offset].bitmap);
|
||||
item->set_color(menu_items[i + offset].color);
|
||||
item->on_select = menu_items[i + offset].on_select;
|
||||
item->set_dirty();
|
||||
if ((i + offset) >= menu_items.size()) {
|
||||
item->hidden(true);
|
||||
item->set_text(" ");
|
||||
item->set_bitmap(nullptr);
|
||||
item->on_select = [](){};
|
||||
item->set_dirty();
|
||||
}
|
||||
else {
|
||||
// Assign item data to NewButtons according to offset
|
||||
item->hidden(false);
|
||||
item->set_text(menu_items[i + offset].text);
|
||||
item->set_bitmap(menu_items[i + offset].bitmap);
|
||||
item->set_color(menu_items[i + offset].color);
|
||||
item->on_select = menu_items[i + offset].on_select;
|
||||
item->set_dirty();
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
@@ -167,18 +175,21 @@ bool BtnGridView::set_highlighted(int32_t new_value) {
|
||||
highlighted_item = new_value;
|
||||
offset = new_value - displayed_max + rows_;
|
||||
update_items();
|
||||
set_dirty();
|
||||
} else if ((uint32_t)new_value < offset) {
|
||||
// Shift BtnGridView down
|
||||
highlighted_item = new_value;
|
||||
offset = (new_value / rows_) * rows_;
|
||||
update_items();
|
||||
set_dirty();
|
||||
} else {
|
||||
// Just update highlight
|
||||
highlighted_item = new_value;
|
||||
if (visible())
|
||||
item_view(highlighted_item - offset)->focus();
|
||||
}
|
||||
|
||||
if (visible())
|
||||
item_view(highlighted_item - offset)->focus();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -163,6 +163,12 @@ void MenuView::clear() {
|
||||
}
|
||||
|
||||
menu_items.clear();
|
||||
highlighted_item = 0;
|
||||
offset = 0;
|
||||
}
|
||||
|
||||
size_t MenuView::item_count() const {
|
||||
return menu_items.size();
|
||||
}
|
||||
|
||||
void MenuView::add_item(MenuItem new_item) {
|
||||
@@ -209,7 +215,7 @@ MenuItemView* MenuView::item_view(size_t index) const {
|
||||
bool MenuView::set_highlighted(int32_t new_value) {
|
||||
int32_t item_count = (int32_t)menu_items.size();
|
||||
|
||||
if (new_value < 0)
|
||||
if (new_value < 0 || item_count == 0)
|
||||
return false;
|
||||
|
||||
if (new_value >= item_count)
|
||||
@@ -238,7 +244,7 @@ bool MenuView::set_highlighted(int32_t new_value) {
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t MenuView::highlighted_index() {
|
||||
uint32_t MenuView::highlighted_index() const {
|
||||
return highlighted_item;
|
||||
}
|
||||
|
||||
@@ -262,7 +268,7 @@ bool MenuView::on_key(const KeyEvent key) {
|
||||
case KeyEvent::Select:
|
||||
case KeyEvent::Right:
|
||||
if( menu_items[highlighted_item].on_select ) {
|
||||
menu_items[highlighted_item].on_select();
|
||||
menu_items[highlighted_item].on_select(key);
|
||||
}
|
||||
return true;
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ struct MenuItem {
|
||||
std::string text;
|
||||
ui::Color color;
|
||||
const Bitmap* bitmap;
|
||||
std::function<void(void)> on_select;
|
||||
std::function<void(KeyEvent)> on_select;
|
||||
|
||||
// TODO: Prevent default-constructed MenuItems.
|
||||
// I managed to construct a menu with three extra, unspecified menu items
|
||||
@@ -83,11 +83,12 @@ 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;
|
||||
|
||||
bool set_highlighted(int32_t new_value);
|
||||
uint32_t highlighted_index();
|
||||
uint32_t highlighted_index() const;
|
||||
|
||||
void set_parent_rect(const Rect new_parent_rect) override;
|
||||
void on_focus() override;
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -434,6 +434,23 @@ namespace ui {
|
||||
void RSSIGraph::set_nb_columns( int16_t nb )
|
||||
{
|
||||
nb_columns = nb ;
|
||||
while( graph_list.size() > nb_columns )
|
||||
{
|
||||
graph_list.erase( graph_list.begin() );
|
||||
}
|
||||
}
|
||||
|
||||
void RSSIGraph::on_hide() {
|
||||
nb_columns_before_hide = nb_columns ;
|
||||
nb_columns = 1 ;
|
||||
while( graph_list.size() > nb_columns )
|
||||
{
|
||||
graph_list.erase( graph_list.begin() );
|
||||
}
|
||||
}
|
||||
|
||||
void RSSIGraph::on_show() {
|
||||
nb_columns = nb_columns_before_hide ;
|
||||
}
|
||||
|
||||
void RSSI::on_focus() {
|
||||
|
||||
@@ -115,8 +115,12 @@ namespace ui {
|
||||
void paint(Painter& painter) override;
|
||||
void add_values(int16_t rssi_min, int16_t rssi_avg, int16_t rssi_max, int16_t db );
|
||||
void set_nb_columns( int16_t nb );
|
||||
|
||||
void on_hide() override ;
|
||||
void on_show() override ;
|
||||
|
||||
private:
|
||||
uint16_t nb_columns_before_hide = 16 ;
|
||||
uint16_t nb_columns = 16 ;
|
||||
RSSIGraphList graph_list { } ;
|
||||
};
|
||||
|
||||
@@ -29,9 +29,25 @@ using namespace portapack;
|
||||
|
||||
namespace ui {
|
||||
|
||||
void text_prompt(NavigationView& nav, std::string& str, const size_t max_length, const std::function<void(std::string&)> on_done) {
|
||||
void text_prompt(
|
||||
NavigationView& nav,
|
||||
std::string& str,
|
||||
size_t max_length,
|
||||
std::function<void(std::string&)> on_done
|
||||
) {
|
||||
text_prompt(nav, str, str.length(), max_length, on_done);
|
||||
}
|
||||
|
||||
void text_prompt(
|
||||
NavigationView& nav,
|
||||
std::string& str,
|
||||
uint32_t cursor_pos,
|
||||
size_t max_length,
|
||||
std::function<void(std::string&)> on_done
|
||||
) {
|
||||
//if (persistent_memory::ui_config_textentry() == 0) {
|
||||
auto te_view = nav.push<AlphanumView>(str, max_length);
|
||||
te_view->set_cursor(cursor_pos);
|
||||
te_view->on_changed = [on_done](std::string& value) {
|
||||
if (on_done)
|
||||
on_done(value);
|
||||
@@ -45,76 +61,38 @@ void text_prompt(NavigationView& nav, std::string& str, const size_t max_length,
|
||||
}*/
|
||||
}
|
||||
|
||||
void TextEntryView::update_text() {
|
||||
if (cursor_pos < 30)
|
||||
text_input.set(_str + std::string(_max_length - _str.length(), ' '));
|
||||
else
|
||||
text_input.set('<' + _str.substr(cursor_pos - 29, 29));
|
||||
|
||||
draw_cursor();
|
||||
}
|
||||
/* TextEntryView ***********************************************************/
|
||||
|
||||
void TextEntryView::char_delete() {
|
||||
if (!cursor_pos) return;
|
||||
|
||||
cursor_pos--;
|
||||
_str.resize(cursor_pos);
|
||||
text_input.char_delete();
|
||||
}
|
||||
|
||||
void TextEntryView::char_add(const char c) {
|
||||
if (cursor_pos >= _max_length) return;
|
||||
|
||||
_str += c;
|
||||
cursor_pos++;
|
||||
text_input.char_add(c);
|
||||
}
|
||||
|
||||
void TextEntryView::draw_cursor() {
|
||||
Point draw_pos;
|
||||
|
||||
draw_pos = { text_input.screen_rect().location().x() + std::min((Coord)cursor_pos, (Coord)28) * 8,
|
||||
text_input.screen_rect().location().y() + 16 };
|
||||
|
||||
// Erase previous
|
||||
display.fill_rectangle(
|
||||
{ { text_input.screen_rect().location().x(), draw_pos.y() }, { text_input.screen_rect().size().width(), 4 } },
|
||||
Color::black()
|
||||
);
|
||||
// Draw new
|
||||
display.fill_rectangle(
|
||||
{ draw_pos, { 8, 4 } },
|
||||
Color::white()
|
||||
);
|
||||
void TextEntryView::set_cursor(uint32_t pos) {
|
||||
text_input.set_cursor(pos);
|
||||
}
|
||||
|
||||
void TextEntryView::focus() {
|
||||
button_ok.focus();
|
||||
text_input.focus();
|
||||
}
|
||||
|
||||
TextEntryView::TextEntryView(
|
||||
NavigationView& nav,
|
||||
std::string& str,
|
||||
size_t max_length
|
||||
) : _str(str),
|
||||
_max_length(max_length)
|
||||
) : text_input{ str, max_length, { 0, 0 } }
|
||||
{
|
||||
|
||||
// Trim from right
|
||||
//_str->erase(std::find_if(_str->rbegin(), _str->rend(), std::not1(std::ptr_fun<int, int>(std::isspace))).base(), _str->end());
|
||||
if (_str.length() > _max_length)
|
||||
_str.resize(_max_length);
|
||||
_str.reserve(_max_length);
|
||||
|
||||
cursor_pos = _str.length();
|
||||
|
||||
add_children({
|
||||
&text_input,
|
||||
&button_ok
|
||||
});
|
||||
|
||||
button_ok.on_select = [this, &nav](Button&) {
|
||||
_str.resize(cursor_pos);
|
||||
|
||||
button_ok.on_select = [this, &str, &nav](Button&) {
|
||||
if (on_changed)
|
||||
on_changed(_str);
|
||||
on_changed(str);
|
||||
nav.pop();
|
||||
};
|
||||
}
|
||||
|
||||
@@ -34,6 +34,8 @@ public:
|
||||
|
||||
void focus() override;
|
||||
std::string title() const override { return "Text entry"; };
|
||||
|
||||
void set_cursor(uint32_t pos);
|
||||
|
||||
protected:
|
||||
TextEntryView(NavigationView& nav, std::string& str, size_t max_length);
|
||||
@@ -45,24 +47,30 @@ protected:
|
||||
|
||||
void char_add(const char c);
|
||||
void char_delete();
|
||||
void draw_cursor();
|
||||
void update_text();
|
||||
|
||||
std::string& _str;
|
||||
size_t _max_length;
|
||||
uint32_t cursor_pos { 0 };
|
||||
|
||||
Text text_input {
|
||||
{ 0, 0, 240, 16 }
|
||||
};
|
||||
|
||||
TextField text_input;
|
||||
Button button_ok {
|
||||
{ 10 * 8, 33 * 8, 9 * 8, 32 },
|
||||
"OK"
|
||||
};
|
||||
};
|
||||
|
||||
void text_prompt(NavigationView& nav, std::string& str, size_t max_length, const std::function<void(std::string&)> on_done = nullptr);
|
||||
// Show the TextEntry view to receive keyboard input.
|
||||
// NB: This function returns immediately. 'str' is taken
|
||||
// by reference and its lifetime must be ensured by the
|
||||
// caller until the TextEntry view is dismissed.
|
||||
void text_prompt(
|
||||
NavigationView& nav,
|
||||
std::string& str,
|
||||
size_t max_length,
|
||||
std::function<void(std::string&)> on_done = nullptr);
|
||||
|
||||
void text_prompt(
|
||||
NavigationView& nav,
|
||||
std::string& str,
|
||||
uint32_t cursor_pos,
|
||||
size_t max_length,
|
||||
std::function<void(std::string&)> on_done = nullptr);
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
|
||||
@@ -189,4 +189,72 @@ TransmitterView::~TransmitterView() {
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
/* TransmitterView2 *******************************************************/
|
||||
|
||||
void TransmitterView2::paint(Painter& painter) {
|
||||
// Not using TransmitterView2, but if we delete it,we got , top line 1 a blanking rect.
|
||||
(void) painter; // Avoid warning: unused parameter .
|
||||
}
|
||||
|
||||
void TransmitterView2::on_tx_gain_changed(int32_t tx_gain) {
|
||||
transmitter_model.set_tx_gain(tx_gain);
|
||||
update_gainlevel_styles();
|
||||
}
|
||||
|
||||
void TransmitterView2::on_tx_amp_changed(bool rf_amp) {
|
||||
transmitter_model.set_rf_amp(rf_amp);
|
||||
update_gainlevel_styles();
|
||||
}
|
||||
|
||||
void TransmitterView2::update_gainlevel_styles() {
|
||||
const Style *new_style_ptr = NULL;
|
||||
int8_t tot_gain = transmitter_model.tx_gain() + (transmitter_model.rf_amp() ? 14 : 0);
|
||||
|
||||
if(tot_gain > POWER_THRESHOLD_HIGH) {
|
||||
new_style_ptr = &style_power_high;
|
||||
} else if(tot_gain > POWER_THRESHOLD_MED) {
|
||||
new_style_ptr = &style_power_med;
|
||||
} else if(tot_gain > POWER_THRESHOLD_LOW) {
|
||||
new_style_ptr = &style_power_low;
|
||||
}
|
||||
|
||||
field_gain.set_style(new_style_ptr);
|
||||
text_gain.set_style(new_style_ptr);
|
||||
field_amp.set_style(new_style_ptr);
|
||||
text_amp.set_style(new_style_ptr);
|
||||
}
|
||||
|
||||
void TransmitterView2::on_show() {
|
||||
field_gain.set_value(transmitter_model.tx_gain());
|
||||
field_amp.set_value(transmitter_model.rf_amp() ? 14 : 0);
|
||||
|
||||
update_gainlevel_styles();
|
||||
}
|
||||
|
||||
TransmitterView2::TransmitterView2( const Coord y)
|
||||
{
|
||||
set_parent_rect({ 3*8, y, 20 * 8, 1 * 8 }); // set_parent_rect({ 0, y, 30 * 8, 6 * 8 });
|
||||
|
||||
add_children({
|
||||
&text_gain,
|
||||
&field_gain,
|
||||
&text_amp,
|
||||
&field_amp,
|
||||
});
|
||||
|
||||
field_gain.on_change = [this](uint32_t tx_gain) {
|
||||
on_tx_gain_changed(tx_gain);
|
||||
};
|
||||
|
||||
field_amp.on_change = [this](uint32_t rf_amp) {
|
||||
on_tx_amp_changed((bool) rf_amp);
|
||||
};
|
||||
}
|
||||
|
||||
TransmitterView2::~TransmitterView2() {
|
||||
audio::output::stop();
|
||||
transmitter_model.disable();
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -165,6 +165,69 @@ private:
|
||||
void update_gainlevel_styles(void);
|
||||
};
|
||||
|
||||
|
||||
class TransmitterView2 : public View {
|
||||
public:
|
||||
|
||||
TransmitterView2(const Coord y);
|
||||
|
||||
~TransmitterView2();
|
||||
|
||||
void on_show() override;
|
||||
void paint(Painter& painter) override;
|
||||
|
||||
private:
|
||||
const Style style_power_low {
|
||||
.font = font::fixed_8x16,
|
||||
.background = Color::black(),
|
||||
.foreground = Color::yellow(),
|
||||
};
|
||||
const Style style_power_med {
|
||||
.font = font::fixed_8x16,
|
||||
.background = Color::black(),
|
||||
.foreground = Color::orange(),
|
||||
};
|
||||
const Style style_power_high {
|
||||
.font = font::fixed_8x16,
|
||||
.background = Color::black(),
|
||||
.foreground = Color::red(),
|
||||
};
|
||||
|
||||
Text text_gain {
|
||||
{ 0, 3 * 8, 5 * 8, 1 * 16 },
|
||||
"Gain:"
|
||||
};
|
||||
|
||||
NumberField field_gain {
|
||||
{ 5 * 8, 3 * 8 },
|
||||
2,
|
||||
{ max2837::tx::gain_db_range.minimum, max2837::tx::gain_db_range.maximum },
|
||||
max2837::tx::gain_db_step,
|
||||
' '
|
||||
};
|
||||
|
||||
Text text_amp {
|
||||
{ 8 * 8, 3 * 8, 5 * 8, 1 * 16 },
|
||||
"Amp:"
|
||||
};
|
||||
|
||||
NumberField field_amp {
|
||||
{ 12 * 8, 3 * 8 },
|
||||
2,
|
||||
{ 0, 14 },
|
||||
14,
|
||||
' '
|
||||
};
|
||||
|
||||
|
||||
void on_tx_gain_changed(int32_t tx_gain);
|
||||
void on_tx_amp_changed(bool rf_amp);
|
||||
|
||||
void update_gainlevel_styles(void);
|
||||
};
|
||||
|
||||
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
#endif/*__UI_TRANSMITTER_H__*/
|
||||
|
||||
@@ -73,6 +73,7 @@
|
||||
#include "ui_whipcalc.hpp"
|
||||
#include "ui_flash_utility.hpp"
|
||||
#include "ui_sd_over_usb.hpp"
|
||||
#include "ui_spectrum_painter.hpp"
|
||||
|
||||
//#include "acars_app.hpp"
|
||||
#include "ais_app.hpp"
|
||||
@@ -367,13 +368,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;
|
||||
}
|
||||
@@ -594,6 +597,7 @@ TransmittersMenuView::TransmittersMenuView(NavigationView& nav) {
|
||||
{ "TEDI/LCR", ui::Color::yellow(), &bitmap_icon_lcr, [&nav](){ nav.push<LCRView>(); } },
|
||||
{ "TouchTune", ui::Color::yellow(), &bitmap_icon_remote, [&nav](){ nav.push<TouchTunesView>(); } },
|
||||
{ "Playlist", ui::Color::yellow(), &bitmap_icon_remote, [&nav](){ nav.push<PlaylistView>(); } },
|
||||
{ "S.Painter", ui::Color::orange(), &bitmap_icon_morse, [&nav](){ nav.push<SpectrumPainterView>(); } },
|
||||
//{ "Remote", ui::Color::dark_grey(), &bitmap_icon_remote, [&nav](){ nav.push<RemoteView>(); } },
|
||||
});
|
||||
}
|
||||
|
||||
@@ -76,6 +76,15 @@ namespace ui
|
||||
{
|
||||
return reinterpret_cast<T *>(push_view(std::unique_ptr<View>(new T(*this, std::forward<Args>(args)...))));
|
||||
}
|
||||
|
||||
// Pushes a new view under the current on the stack so the current view returns into this new one.
|
||||
template <class T, class... Args>
|
||||
void push_under_current(Args &&...args)
|
||||
{
|
||||
auto new_view = std::unique_ptr<View>(new T(*this, std::forward<Args>(args)...));
|
||||
view_stack.insert(view_stack.end() - 1, std::move(new_view));
|
||||
}
|
||||
|
||||
template <class T, class... Args>
|
||||
T *replace(Args &&...args)
|
||||
{
|
||||
@@ -126,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;
|
||||
|
||||
@@ -140,6 +140,7 @@ set(CPPSRC
|
||||
debug.cpp
|
||||
${COMMON}/gcc.cpp
|
||||
${COMMON}/performance_counter.cpp
|
||||
${COMMON}/random.cpp
|
||||
tone_gen.cpp
|
||||
)
|
||||
|
||||
@@ -463,6 +464,13 @@ set(MODE_CPPSRC
|
||||
)
|
||||
DeclareTargets(PSIG siggen)
|
||||
|
||||
### Spectrum painter
|
||||
|
||||
set(MODE_CPPSRC
|
||||
proc_spectrum_painter.cpp
|
||||
)
|
||||
DeclareTargets(PSPT spectrum_painter)
|
||||
|
||||
### SSTV TX
|
||||
|
||||
set(MODE_CPPSRC
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
*/
|
||||
|
||||
#include "baseband_dma.hpp"
|
||||
#include "portapack_shared_memory.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
@@ -103,8 +104,12 @@ static constexpr auto& gpdma_channel_sgpio = gpdma::channels[portapack::sgpio_gp
|
||||
|
||||
static ThreadWait thread_wait;
|
||||
|
||||
volatile uint32_t buffer_transfered = 0;
|
||||
volatile uint32_t buffer_handled = 0;
|
||||
|
||||
static void transfer_complete() {
|
||||
const auto next_lli_index = gpdma_channel_sgpio.next_lli() - &lli_loop[0];
|
||||
buffer_transfered++;
|
||||
thread_wait.wake_from_interrupt(next_lli_index);
|
||||
}
|
||||
|
||||
@@ -158,6 +163,10 @@ void disable() {
|
||||
|
||||
baseband::buffer_t wait_for_buffer() {
|
||||
const auto next_index = thread_wait.sleep();
|
||||
buffer_handled++;
|
||||
|
||||
auto buffer_missed = buffer_transfered - buffer_handled;
|
||||
shared_memory.m4_buffer_missed = buffer_missed;
|
||||
|
||||
if( next_index >= 0 ) {
|
||||
const size_t free_index = (next_index + transfers_per_buffer - 2) & transfers_mask;
|
||||
|
||||
@@ -83,6 +83,12 @@ SSB::SSB() : hilbert() {
|
||||
}
|
||||
|
||||
void SSB::execute(const buffer_s16_t& audio, const buffer_c8_t& buffer, bool& configured_in, uint32_t& new_beep_index, uint32_t& new_beep_timer,TXProgressMessage& new_txprogress_message, AudioLevelReportMessage& new_level_message, uint32_t& new_power_acc_count, uint32_t& new_divider ) {
|
||||
//unused
|
||||
(void)configured_in ;
|
||||
(void)new_beep_index ;
|
||||
(void)new_beep_timer ;
|
||||
(void)new_txprogress_message ;
|
||||
|
||||
// No way to activate correctly the roger beep in this option, Maybe not enough M4 CPU power , Let's block roger beep in SSB selection by now .
|
||||
int32_t sample = 0;
|
||||
int8_t re = 0, im = 0;
|
||||
|
||||
@@ -49,7 +49,7 @@ void SigGenProcessor::execute(const buffer_c8_t& buffer) {
|
||||
// Sine
|
||||
sample = (sine_table_i8[(tone_phase & 0xFF000000) >> 24]);
|
||||
} else if (tone_shape == 2) {
|
||||
// Tri
|
||||
// Triangle
|
||||
int8_t a = (tone_phase & 0xFF000000) >> 24;
|
||||
sample = (a & 0x80) ? ((a << 1) ^ 0xFF) - 0x80 : (a << 1) + 0x80;
|
||||
} else if (tone_shape == 3) {
|
||||
@@ -61,24 +61,40 @@ void SigGenProcessor::execute(const buffer_c8_t& buffer) {
|
||||
} else if (tone_shape == 5) {
|
||||
// Square
|
||||
sample = (((tone_phase & 0xFF000000) >> 24) & 0x80) ? 127 : -128;
|
||||
} else if (tone_shape == 6) {
|
||||
// Noise
|
||||
sample = (lfsr & 0xFF000000) >> 24;
|
||||
feedback = ((lfsr >> 31) ^ (lfsr >> 29) ^ (lfsr >> 15) ^ (lfsr >> 11)) & 1;
|
||||
lfsr = (lfsr << 1) | feedback;
|
||||
if (!lfsr) lfsr = 0x1337; // Shouldn't do this :(
|
||||
} else if (tone_shape == 6) {
|
||||
// Noise generator, pseudo random noise generator, 16 bits linear-feedback shift register (LFSR) algorithm, variant Fibonacci.
|
||||
// https://en.wikipedia.org/wiki/Linear-feedback_shift_register
|
||||
// 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));
|
||||
lfsr_16 = (lfsr_16 >> 1) | (bit_16 << 15);
|
||||
sample = (lfsr_16 & 0x00FF); // main pseudo random noise generator.
|
||||
}
|
||||
if (counter == 5) { // after many empiric test, that combination mix of >>4 and >>5, gives a reasonable trade off white noise / good rf power level .
|
||||
sample = ((lfsr_16 & 0b0000111111110000) >> 4); // just changing the spectrum shape .
|
||||
}
|
||||
if (counter == 10) {
|
||||
sample = ((lfsr_16 & 0b0001111111100000) >> 5); // just changing the spectrum shape .
|
||||
}
|
||||
counter++;
|
||||
if (counter ==15) {
|
||||
counter=0;
|
||||
}
|
||||
}
|
||||
|
||||
if (tone_shape < 6) { // we are in periodic signals, we need tone phases update.
|
||||
tone_phase += tone_delta;
|
||||
}
|
||||
|
||||
tone_phase += tone_delta;
|
||||
|
||||
// Do FM
|
||||
// Do FM modulation
|
||||
delta = sample * fm_delta;
|
||||
|
||||
phase += delta;
|
||||
sphase = phase + (64 << 24);
|
||||
|
||||
re = (sine_table_i8[(sphase & 0xFF000000) >> 24]);
|
||||
im = (sine_table_i8[(phase & 0xFF000000) >> 24]);
|
||||
im = (sine_table_i8[( phase & 0xFF000000) >> 24]);
|
||||
}
|
||||
|
||||
buffer.p[i] = {re, im};
|
||||
@@ -104,7 +120,8 @@ void SigGenProcessor::on_message(const Message* const msg) {
|
||||
fm_delta = message.bw * (0xFFFFFFULL / 1536000);
|
||||
tone_shape = message.shape;
|
||||
|
||||
lfsr = 0x54DF0119;
|
||||
// lfsr = seed_value ; // Finally not used , init lfsr 8 bits.
|
||||
lfsr_16 = seed_value_16; // init lfsr 16 bits.
|
||||
|
||||
configured = true;
|
||||
break;
|
||||
|
||||
@@ -38,14 +38,18 @@ private:
|
||||
|
||||
BasebandThread baseband_thread { 1536000, this, NORMALPRIO + 20, baseband::Direction::Transmit };
|
||||
|
||||
uint32_t tone_delta { 0 }, fm_delta { };
|
||||
uint32_t lfsr { }, feedback { }, tone_shape { };
|
||||
uint32_t tone_delta { 0 }, fm_delta { },tone_phase { 0 };
|
||||
uint8_t tone_shape { };
|
||||
uint32_t sample_count { 0 };
|
||||
bool auto_off { };
|
||||
uint32_t tone_phase { 0 }, phase { 0 }, delta { 0 }, sphase { 0 };
|
||||
int8_t sample { 0 };
|
||||
int8_t re { 0 }, im { 0 };
|
||||
|
||||
int32_t phase { 0 }, sphase { 0 }, delta { 0 }; // they may have sign in the pseudo random sample generation.
|
||||
int8_t sample { 0 }, re { 0 }, im { 0 }; // they have sign + and -.
|
||||
uint16_t seed_value_16 = {0xACE1}; // seed 16 bits lfsr : any nonzero start state will work.
|
||||
uint16_t lfsr_16 { }, bit_16 { }; // bit must be 16-bit to allow bit<<15 later in the code */
|
||||
uint8_t counter {0};
|
||||
// uint8_t seed_value = {0x56}; // Finally not used lfsr of 8 bits , seed 8blfsr : any nonzero start state will work.
|
||||
// uint8_t lfsr { }, bit { }; // Finally not used lfsr of 8 bits , bit must be 8-bit to allow bit<<7 later in the code */
|
||||
|
||||
TXProgressMessage txprogress_message { };
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Bernd Herzog
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "proc_spectrum_painter.hpp"
|
||||
#include "event_m4.hpp"
|
||||
#include "dsp_fft.hpp"
|
||||
#include "random.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
// TODO move to class members SpectrumPainterProcessor
|
||||
complex16_t *current_line_data = nullptr;
|
||||
complex16_t * volatile next_line_data = nullptr;
|
||||
uint32_t current_line_index = 0;
|
||||
uint32_t current_line_width = 0;
|
||||
int32_t current_bw = 0;
|
||||
|
||||
std::vector<uint8_t> fifo_data[1 << SpectrumPainterBufferConfigureResponseMessage::fifo_k] { };
|
||||
SpectrumPainterFIFO fifo { fifo_data, SpectrumPainterBufferConfigureResponseMessage::fifo_k };
|
||||
|
||||
int max_val = 127;
|
||||
|
||||
// This is called at 3072000/2048 = 1500Hz
|
||||
void SpectrumPainterProcessor::execute(const buffer_c8_t& buffer) {
|
||||
|
||||
for (uint32_t i = 0; i < buffer.count; i++) {
|
||||
if (current_line_data != nullptr) {
|
||||
auto data = current_line_data[(current_line_index++ * current_bw / 3072 ) % current_line_width];
|
||||
buffer.p[i] = {(int8_t) data.real(), (int8_t) data.imag()};
|
||||
}
|
||||
else
|
||||
buffer.p[i] = {0, 0};
|
||||
}
|
||||
|
||||
// collect new data
|
||||
if (next_line_data != nullptr) {
|
||||
if (current_line_data != nullptr)
|
||||
delete current_line_data;
|
||||
|
||||
current_line_data = next_line_data;
|
||||
next_line_data = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
WORKING_AREA(thread_wa, 4096);
|
||||
|
||||
void SpectrumPainterProcessor::run() {
|
||||
int ui = 0;
|
||||
init_genrand(22267);
|
||||
|
||||
while (true) {
|
||||
if (fifo.is_empty() == false && next_line_data == nullptr) {
|
||||
std::vector<uint8_t> data;
|
||||
fifo.out(data);
|
||||
|
||||
auto picture_width = data.size();
|
||||
|
||||
auto fft_width = picture_width * 2;
|
||||
auto qu = fft_width/4;
|
||||
|
||||
complex16_t *v = new complex16_t[fft_width];
|
||||
complex16_t *tmp = new complex16_t[fft_width];
|
||||
|
||||
for (uint32_t fft_index = 0; fft_index < fft_width; fft_index++) {
|
||||
if (fft_index < qu) {
|
||||
}
|
||||
else if (fft_index < qu*3) {
|
||||
//TODO: Improve index handling
|
||||
auto image_index = fft_index-qu;
|
||||
|
||||
auto bin_power = data[image_index]; // 0 to 255
|
||||
auto bin_phase = genrand_int31(); // 0 to 255
|
||||
|
||||
// rotate by random angle
|
||||
auto phase_cos = (sine_table_i8[((int)(bin_phase + 0x40)) & 0xFF]); // -127 to 127
|
||||
auto phase_sin = (sine_table_i8[((int)(bin_phase)) & 0xFF]);
|
||||
|
||||
auto real = (int16_t)((int16_t)phase_cos * bin_power / 255); // -127 to 127
|
||||
auto imag = (int16_t)((int16_t)phase_sin * bin_power / 255); // -127 to 127
|
||||
|
||||
auto fftshift_index = 0;
|
||||
if (fft_index < qu*2) // first half (fft_index = qu; fft_index < qu*2)
|
||||
fftshift_index = fft_index + 2*qu; // goes to back
|
||||
else // 2nd half (fft_index = qu*2; fft_index < qu*3)
|
||||
fftshift_index = fft_index - 2*qu; // goes to front
|
||||
|
||||
v[fftshift_index] = {real, imag};
|
||||
}
|
||||
}
|
||||
|
||||
ifft<complex16_t>(v, fft_width, tmp);
|
||||
|
||||
// normalize
|
||||
volatile int32_t maximum = 1;
|
||||
for (uint32_t i = 0; i < fft_width; i++) {
|
||||
if (v[i].real() > maximum)
|
||||
maximum = v[i].real();
|
||||
if (v[i].real() < -maximum)
|
||||
maximum = -v[i].real();
|
||||
if (v[i].imag() > maximum)
|
||||
maximum = v[i].imag();
|
||||
if (v[i].imag() < -maximum)
|
||||
maximum = -v[i].imag();
|
||||
}
|
||||
|
||||
if (maximum == 1) { // a black line
|
||||
for (uint32_t i = 0; i < fft_width; i++)
|
||||
v[i] = {0, 0};
|
||||
}
|
||||
else {
|
||||
for (uint32_t i = 0; i < fft_width; i++) {
|
||||
v[i] = { (int8_t)((int32_t)v[i].real() * 120 / maximum), (int8_t)((int32_t)v[i].imag() * 120 / maximum)};
|
||||
}
|
||||
}
|
||||
|
||||
delete tmp;
|
||||
next_line_data = v;
|
||||
ui++;
|
||||
}
|
||||
else {
|
||||
chThdSleepMilliseconds(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SpectrumPainterProcessor::on_message(const Message* const msg) {
|
||||
|
||||
switch(msg->id) {
|
||||
case Message::ID::SpectrumPainterBufferRequestConfigure:
|
||||
{
|
||||
const auto message = *reinterpret_cast<const SpectrumPainterBufferConfigureRequestMessage*>(msg);
|
||||
current_line_width = message.width;
|
||||
current_bw = message.bw / 500;
|
||||
|
||||
if (message.update == false) {
|
||||
SpectrumPainterBufferConfigureResponseMessage response { &fifo };
|
||||
shared_memory.application_queue.push(response);
|
||||
|
||||
if (configured == false) {
|
||||
thread = chThdCreateStatic(thread_wa, sizeof(thread_wa),
|
||||
NORMALPRIO, SpectrumPainterProcessor::fn,
|
||||
this
|
||||
);
|
||||
|
||||
configured = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
EventDispatcher event_dispatcher { std::make_unique<SpectrumPainterProcessor>() };
|
||||
event_dispatcher.run();
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Bernd Herzog
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "portapack_shared_memory.hpp"
|
||||
#include "baseband_processor.hpp"
|
||||
#include "baseband_thread.hpp"
|
||||
|
||||
class SpectrumPainterProcessor : public BasebandProcessor {
|
||||
public:
|
||||
void execute(const buffer_c8_t& buffer) override;
|
||||
void on_message(const Message* const p) override;
|
||||
void run();
|
||||
|
||||
private:
|
||||
bool configured { false };
|
||||
BasebandThread baseband_thread { 3072000, this, NORMALPRIO + 20, baseband::Direction::Transmit };
|
||||
Thread* thread {nullptr};
|
||||
|
||||
protected:
|
||||
static msg_t fn(void* arg) {
|
||||
auto obj = static_cast<SpectrumPainterProcessor*>(arg);
|
||||
obj->run();
|
||||
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
@@ -33,7 +33,7 @@
|
||||
namespace portapack {
|
||||
namespace cpld {
|
||||
|
||||
bool update_if_necessary(
|
||||
CpldUpdateStatus update_if_necessary(
|
||||
const Config config
|
||||
) {
|
||||
jtag::GPIOTarget target {
|
||||
@@ -51,7 +51,7 @@ bool update_if_necessary(
|
||||
|
||||
/* Run-Test/Idle */
|
||||
if( !cpld.idcode_ok() ) {
|
||||
return false;
|
||||
return CpldUpdateStatus::Idcode_check_failed;
|
||||
}
|
||||
|
||||
cpld.sample();
|
||||
@@ -62,7 +62,7 @@ bool update_if_necessary(
|
||||
* in passive state.
|
||||
*/
|
||||
if( !cpld.silicon_id_ok() ) {
|
||||
return false;
|
||||
return CpldUpdateStatus::Silicon_id_check_failed;
|
||||
}
|
||||
|
||||
/* Verify CPLD contents against current bitstream. */
|
||||
@@ -86,7 +86,7 @@ bool update_if_necessary(
|
||||
cpld.disable();
|
||||
}
|
||||
|
||||
return ok;
|
||||
return ok ? CpldUpdateStatus::Success : CpldUpdateStatus::Program_failed;
|
||||
}
|
||||
|
||||
} /* namespace cpld */
|
||||
|
||||
@@ -27,7 +27,14 @@
|
||||
namespace portapack {
|
||||
namespace cpld {
|
||||
|
||||
bool update_if_necessary(
|
||||
enum class CpldUpdateStatus {
|
||||
Success = 0,
|
||||
Idcode_check_failed = 1,
|
||||
Silicon_id_check_failed = 2,
|
||||
Program_failed = 3
|
||||
};
|
||||
|
||||
CpldUpdateStatus update_if_necessary(
|
||||
const Config config
|
||||
);
|
||||
|
||||
|
||||
@@ -20,3 +20,4 @@
|
||||
*/
|
||||
|
||||
#include "dsp_fft.hpp"
|
||||
#include "complex.hpp"
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include "complex.hpp"
|
||||
#include "hal.h"
|
||||
#include "utility.hpp"
|
||||
#include "sine_table_int8.hpp"
|
||||
|
||||
namespace std {
|
||||
/* https://github.com/AE9RB/fftbench/blob/master/cxlr.hpp
|
||||
@@ -135,4 +136,45 @@ void fft_c_preswapped(std::array<T, N>& data, const size_t from, const size_t to
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
ifft(v,N):
|
||||
[0] If N==1 then return.
|
||||
[1] For k = 0 to N/2-1, let ve[k] = v[2*k]
|
||||
[2] Compute ifft(ve, N/2);
|
||||
[3] For k = 0 to N/2-1, let vo[k] = v[2*k+1]
|
||||
[4] Compute ifft(vo, N/2);
|
||||
[5] For m = 0 to N/2-1, do [6] through [9]
|
||||
[6] Let w.real() = cos(2*PI*m/N)
|
||||
[7] Let w.imag() = sin(2*PI*m/N)
|
||||
[8] Let v[m] = ve[m] + w*vo[m]
|
||||
[9] Let v[m+N/2] = ve[m] - w*vo[m]
|
||||
*/
|
||||
template<typename T>
|
||||
void ifft( T *v, int n, T *tmp )
|
||||
{
|
||||
if(n>1) {
|
||||
int k,m;
|
||||
T z, w, *vo, *ve;
|
||||
ve = tmp; vo = tmp+n/2;
|
||||
for(k=0; k<n/2; k++) {
|
||||
ve[k] = v[2*k];
|
||||
vo[k] = v[2*k+1];
|
||||
}
|
||||
ifft( ve, n/2, v ); /* FFT on even-indexed elements of v[] */
|
||||
ifft( vo, n/2, v ); /* FFT on odd-indexed elements of v[] */
|
||||
for(m=0; m<n/2; m++) {
|
||||
w.real(sine_table_i8[((int)(m/(double)n * 0x100 + 0x40)) & 0xFF]);
|
||||
w.imag(sine_table_i8[((int)(m/(double)n * 0x100)) & 0xFF]);
|
||||
|
||||
z.real((w.real()*vo[m].real() - w.imag()*vo[m].imag())/127); /* Re(w*vo[m]) */
|
||||
z.imag((w.real()*vo[m].imag() + w.imag()*vo[m].real())/127); /* Im(w*vo[m]) */
|
||||
v[ m ].real(ve[m].real() + z.real());
|
||||
v[ m ].imag(ve[m].imag() + z.imag());
|
||||
v[m+n/2].real(ve[m].real() - z.real());
|
||||
v[m+n/2].imag(ve[m].imag() - z.imag());
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
#endif/*__DSP_FFT_H__*/
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -489,8 +489,8 @@ bool ILI9341::drawBMP2(const ui::Point p, const std::string file) {
|
||||
if(pointer + 4 > 256)
|
||||
break;
|
||||
switch(type) {
|
||||
case 0:
|
||||
case 3:
|
||||
case 0: // R5G6B5
|
||||
case 3: // A1R5G5B5
|
||||
if(!type)
|
||||
line_buffer[px] = ui::Color((uint16_t)buffer[pointer] | ((uint16_t)buffer[pointer + 1] << 8));
|
||||
else
|
||||
@@ -498,13 +498,13 @@ bool ILI9341::drawBMP2(const ui::Point p, const std::string file) {
|
||||
pointer += 2;
|
||||
file_pos += 2;
|
||||
break;
|
||||
case 1:
|
||||
case 1: // 24
|
||||
default:
|
||||
line_buffer[px] = ui::Color(buffer[pointer + 2], buffer[pointer + 1], buffer[pointer]);
|
||||
pointer += 3;
|
||||
file_pos += 3;
|
||||
break;
|
||||
case 2:
|
||||
case 2: // 32
|
||||
line_buffer[px] = ui::Color(buffer[pointer + 2], buffer[pointer + 1], buffer[pointer]);
|
||||
pointer += 4;
|
||||
file_pos += 4;
|
||||
|
||||
@@ -109,6 +109,8 @@ public:
|
||||
AudioSpectrum = 52,
|
||||
APRSPacket = 53,
|
||||
APRSRxConfigure = 54,
|
||||
SpectrumPainterBufferRequestConfigure = 55,
|
||||
SpectrumPainterBufferResponseConfigure = 56,
|
||||
MAX
|
||||
};
|
||||
|
||||
@@ -1143,4 +1145,40 @@ public:
|
||||
uint32_t return_code;
|
||||
};
|
||||
|
||||
class SpectrumPainterBufferConfigureRequestMessage : public Message {
|
||||
public:
|
||||
constexpr SpectrumPainterBufferConfigureRequestMessage(
|
||||
uint16_t width,
|
||||
uint16_t height,
|
||||
bool update,
|
||||
int32_t bw
|
||||
) : Message { ID::SpectrumPainterBufferRequestConfigure },
|
||||
width { width },
|
||||
height { height },
|
||||
update { update },
|
||||
bw { bw }
|
||||
{
|
||||
}
|
||||
|
||||
uint16_t width;
|
||||
uint16_t height;
|
||||
bool update;
|
||||
int32_t bw;
|
||||
};
|
||||
|
||||
using SpectrumPainterFIFO = FIFO<std::vector<uint8_t>>;
|
||||
class SpectrumPainterBufferConfigureResponseMessage : public Message {
|
||||
public:
|
||||
static constexpr size_t fifo_k = 2;
|
||||
|
||||
constexpr SpectrumPainterBufferConfigureResponseMessage(
|
||||
SpectrumPainterFIFO* fifo
|
||||
) : Message { ID::SpectrumPainterBufferResponseConfigure },
|
||||
fifo { fifo }
|
||||
{
|
||||
}
|
||||
|
||||
SpectrumPainterFIFO* fifo { nullptr };
|
||||
};
|
||||
|
||||
#endif/*__MESSAGE_H__*/
|
||||
|
||||
@@ -68,7 +68,8 @@ struct SharedMemory {
|
||||
uint8_t volatile request_m4_performance_counter{ 0 };
|
||||
uint8_t volatile m4_cpu_usage{ 0 };
|
||||
uint16_t volatile m4_stack_usage{ 0 };
|
||||
uint16_t volatile m4_heap_usage{ 0 };
|
||||
uint32_t volatile m4_heap_usage{ 0 };
|
||||
uint16_t volatile m4_buffer_missed{ 0 };
|
||||
};
|
||||
|
||||
extern SharedMemory& shared_memory;
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Bernd Herzog
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "random.hpp"
|
||||
|
||||
static unsigned long state[N]; /* the array for the state vector */
|
||||
static int rnd_left = 1;
|
||||
static int rnd_init = 0;
|
||||
static unsigned long *rnd_next;
|
||||
|
||||
/* initializes state[N] with a seed */
|
||||
void init_genrand(unsigned long s)
|
||||
{
|
||||
int j;
|
||||
state[0] = s & 0xffffffffUL;
|
||||
|
||||
for (j = 1; j < N; j++)
|
||||
{
|
||||
state[j] = (1812433253UL * (state[j - 1] ^ (state[j - 1] >> 30)) + j);
|
||||
/* See Knuth TAOCP Vol2. 3rd Ed. P.106 for multiplier. */
|
||||
/* In the previous versions, MSBs of the seed affect */
|
||||
/* only MSBs of the array state[]. */
|
||||
/* 2002/01/09 modified by Makoto Matsumoto */
|
||||
state[j] &= 0xffffffffUL; /* for >32 bit machines */
|
||||
}
|
||||
|
||||
rnd_left = 1;
|
||||
rnd_init = 1;
|
||||
}
|
||||
|
||||
static void next_state(void)
|
||||
{
|
||||
unsigned long *p = state;
|
||||
int j;
|
||||
|
||||
/* if init_genrand() has not been called, */
|
||||
/* a default initial seed is used */
|
||||
if (rnd_init == 0)
|
||||
init_genrand(5489UL);
|
||||
|
||||
rnd_left = N;
|
||||
rnd_next = state;
|
||||
|
||||
for (j = N - M + 1; --j; p++)
|
||||
*p = p[M] ^ TWIST(p[0], p[1]);
|
||||
|
||||
for (j = M; --j; p++)
|
||||
*p = p[M - N] ^ TWIST(p[0], p[1]);
|
||||
|
||||
*p = p[M - N] ^ TWIST(p[0], state[0]);
|
||||
}
|
||||
|
||||
long genrand_int31(void)
|
||||
{
|
||||
unsigned long y;
|
||||
|
||||
if (--rnd_left == 0)
|
||||
next_state();
|
||||
y = *rnd_next++;
|
||||
|
||||
/* Tempering */
|
||||
y ^= (y >> 11);
|
||||
y ^= (y << 7) & 0x9d2c5680UL;
|
||||
y ^= (y << 15) & 0xefc60000UL;
|
||||
y ^= (y >> 18);
|
||||
|
||||
return (long)(y >> 1);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user