Compare commits

...

30 Commits

Author SHA1 Message Date
Brumi-2021 6bb2a3559d Minor change to the Mic App UI , (adding colour warning indication, based on RF settings GAIN , AMP) (#1001)
* Add colour Power_tx UI warning to Mic App
* Cleaning compile warnings
2023-05-17 12:26:14 +02:00
gullradriel 1a829707f4 fixing PR comments (#991)
* cleanings, factorisation, indentation.
2023-05-15 10:50:21 +02:00
Mark Thompson 22430fb8d4 Support frequency files that don't have a C/R at the end of the last line (#997)
* Support FREQ files that don't have 0x0A at end of last line
2023-05-15 09:11:56 +02:00
jLynx acc20844cd Added clang formatting spec and PR validation (#992)
* Update .clang-format
2023-05-15 11:53:13 +12:00
Bernd Herzog e694e73a75 fixed crash when BtnGridView has more than one page (#993)
* fixed crash when BtnGridView has more than one page

* removed empty buttons from view
2023-05-15 00:08:48 +02:00
Bernd Herzog b4da86d491 New app: Spectrum Painter (#988)
* added spectrum painter app
2023-05-14 23:09:43 +02:00
gullradriel a27881ecd6 Recon event nothread (#984)
* deleted unreliable Thread system and use exclusively the statistics events instead
* reducing used variables, reducing number of functions, revamped main drawing and locking system
* max speed is fixed at a maximum of 10 scans/secs due to statistics not coming quicker than that
2023-05-14 21:20:10 +02:00
Mark Thompson 9ce2f3fdea Fixed FREQMAN "Range" files to use kHz vs KHz, added 2 HAM freq files, and use valid BW settings for WFM (#985)
* Added HAM 2M & 70CM band file
* Update BW to a valid value
Valid WFM BW values are 40k,180k,or 200k
* Use 11k for HAM bandwidth (typically 12.5k but not an option currently)
* Changes FM_STANDARD step value to 100kHz per ITU 1984
200kHz is best here in the USA, but I understand that 100kHz is better in the EU
2023-05-14 13:55:41 +02:00
gullradriel 2a4c49c38c Freqman 40k fix (#987)
* removing space which would prevent correct reading of 'bw=40k,' in freqman files

* setting correct 4 char long size in WFM bandwidth options

---------

Co-authored-by: GullCode <gullradriel@hotmail.com>
2023-05-14 19:16:01 +12:00
gullradriel 415d8cfa54 fixing indent (#982)
resolving conversations in #968
2023-05-13 08:56:29 +02:00
Kyle Reed a8cdde7bea Logging Updates (#978)
* log_file ensures directory exists
* Fix audio timestamp path
* Fix auto inc, add subfolder for recordings
* Fix auto inc and support subfolder
2023-05-12 20:08:07 +02:00
ittercho 8b395239d2 Add example signal and correct FM radio stations (#980)
-fixed RADIO_FM_NO.txt and RADIO_FM_PL.txt
-added aurora rgb led command samples
2023-05-12 18:53:08 +02:00
Mark Thompson 730e7ad72b Two minor patches - freqman file processing & NumberField "can_loop" option (#981)
-Enhanced frequency file reading:
Correctly read freq files that contain a mix of SINGLE and RANGE or HAM_RADIO types (strstr in file processing was ignoring EOL and was therefore finding the f= on the next line).  Also changed to simply ignore blank or unrecognized lines versus adding  them as SIMPLE entries to freq table.
This allow comments and white line in freqman files.
-Fixed "can_loop" option in NumberField:
When NumberField range.first was non-zero, and can_loop was true, turning the encoder dial in the downward direction did not result in numbers looping back to range.second as was expected.  This fix allows looping in downward direction in the case where range.first is non-zero.
2023-05-12 18:48:32 +02:00
gullradriel 1e4ea753ba save load settings / thread timer fix (#979)
* fixing bad parameter in save and load settings, as well as default settings. Tuned thread timers a bit
* uniformisation of default values
* indentation
* fix resume recon after lock
2023-05-12 12:06:01 +02:00
Kyle Reed a86993af91 Minor fileman bug fixes (#977)
-sets max file count to 100, fixes partner rename and delete in subfolders.
2023-05-12 07:54:42 +02:00
Kyle Reed a832bd433d Fileman icon toolbar (#975)
* Add cut/copy/paste menu with icons
* Add icons, file limit to fileman
2023-05-11 22:46:38 +02:00
Brumi-2021 f9fdeb3419 Adding the 3 x WFM BW filters to Mic App (#976)
adding the 3 x WFM BW filters to Mic App and other minor msg correction and adding comments to code
2023-05-11 22:45:32 +02:00
gullradriel 3c2fc74f82 Recon skip fix (#974)
* definitely fixed, tested, documented bad consecutive match skip problem
* stability fix for config when no list
* fixed boundaries
* replaced 1000000 by #define OneMHz 1000000
2023-05-11 22:42:46 +02:00
gullradriel 69927e3c2d All logs to LOGS directory (#973)
* Added #define LOG_ROOT_DIR
* using LOG_ROOT_DIR as root path of log
2023-05-11 21:56:24 +02:00
jLynx 1edd3716a1 Revert "specific directory for screens and logs (#971)" (#972)
This reverts commit bc1ad5598c.
2023-05-11 11:44:19 +12:00
gullradriel bc1ad5598c specific directory for screens and logs (#971)
* Changed to LOGS directory, creating if not existing. Adjusted to upper case.
* Changed to SCREENSHOTS directory, creating if not existing
2023-05-10 21:11:06 +02:00
Kyle Reed 8cae998146 Fileman copy/paste support (#970)
* Add copy/paste UI instead of file save
2023-05-10 18:51:09 +02:00
gullradriel 9a22a760ad Record button remove (#968)
* removed unused debug and record buttons
* added log checkbox
* changed ignore and log to false at app start for pocsag
* fixed warning: suggest parentheses around arithmetic in operand of '^' proc signal
* ui comsetic fix
2023-05-10 14:12:49 +02:00
gullradriel 909b00bdae Fixing units KHz => kHz (#967)
Co-authored-by: GullCode <gullradriel@hotmail.com>
2023-05-10 13:26:21 +02:00
gullradriel 58e731c56e Wfm freqm options (#966)
* added WFM 180K and 40K options in freqman
* Make audio app use freqman funcs to load modulations bandwidths
2023-05-10 13:16:23 +02:00
wifwucite 04e77e0ee8 Update PRESETS.TXT (#962)
LoRa bands added
2023-05-08 22:03:45 +02:00
Mark Thompson 55b93a7044 Update Looking Glass PRESETS.TXT to add yet more bands (#964)
Added FM RADIO, AVIATION, AMATEUR RADIO (HAM), and ITU-defined FREQ BANDS.  (we do realize that 902-928MHz is also used for ISM, and that FM list is having two nearly identical bands.)
2023-05-08 21:58:15 +02:00
Kyle Reed f22046eccc Add "Auto" mode to FrequencyField (#959)
* Add "Auto" mode to FrequencyField
* Use exponential function instead of hyperbolic
* Fix tabs
* Back to hyperbolic, better feel
2023-05-07 22:01:43 +02:00
Brumi-2021 dfadd38e32 Adding 2 WFM filters more to current Audio App ( bw 180Khz and 40 khz to support rx for weather NOAA APT) (#961)
* First part new filter 40k for NOAA in Audio App

* Adding a third WFM filter 180Khz general pursose
2023-05-07 16:08:45 +02:00
jLynx 3cb927c840 Delete sdcard/FIRMWARE directory 2023-05-06 08:19:26 +12:00
133 changed files with 3768 additions and 2047 deletions
+7
View File
@@ -0,0 +1,7 @@
---
BasedOnStyle: Chromium
IndentWidth: '2'
TabWidth: '2'
UseTab: Always
...
+18
View File
@@ -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
+4
View File
@@ -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
+3
View File
@@ -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
+1 -1
View File
@@ -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() {
+1 -1
View File
@@ -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;
+21 -11
View File
@@ -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
+19 -16
View File
@@ -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 { };
+1 -1
View File
@@ -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" );
}
}
+1 -1
View File
@@ -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();
+14 -14
View File
@@ -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 }
+1 -1
View File
@@ -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" );
}
}
+3 -2
View File
@@ -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);
+12 -13
View File
@@ -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));
}
}
+10 -9
View File
@@ -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 }
};
+1 -1
View File
@@ -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);
+1 -1
View File
@@ -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 {
-10
View File
@@ -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);
+1 -14
View File
@@ -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 }
};
+201 -83
View File
@@ -22,7 +22,6 @@
/* TODO:
* - Paging menu items
* - Copy/Move
*/
#include <algorithm>
@@ -43,8 +42,7 @@ bool is_hidden_file(const fs::path& path) {
// Gets a truncated name from a path for display.
std::string truncate(const fs::path& path, size_t max_length) {
auto name = path.string();
return name.length() <= max_length ? name : name.substr(0, max_length);
return ::truncate(path.string(), max_length);
}
// Gets a human readable file size string.
@@ -146,6 +144,22 @@ bool partner_file_prompt(
return true;
}
fs::path get_unique_filename(const fs::path& path, const fs::path& file) {
auto stem = file.stem();
auto ext = file.extension();
auto serial = 1;
fs::path new_path = file;
while (fs::file_exists(path / new_path)) {
new_path = stem;
new_path += fs::path{ u"_" };
new_path += to_string_dec_int(serial++);
new_path += ext;
}
return new_path;
}
}
namespace ui {
@@ -192,12 +206,13 @@ const fileman_entry& FileManBaseView::get_selected_entry() const {
FileManBaseView::FileManBaseView(
NavigationView& nav,
std::string filter
) : nav_ (nav),
extension_filter { filter }
) : nav_{ nav },
extension_filter{ filter }
{
add_children({
&labels,
&text_current,
&text_info,
&button_exit
});
@@ -206,7 +221,7 @@ FileManBaseView::FileManBaseView(
};
if (!sdcIsCardInserted(&SDCD1)) {
empty_root = true;
empty_ = EmptyReason::NoSDC;
text_current.set("NO SD CARD!");
return;
}
@@ -214,7 +229,7 @@ FileManBaseView::FileManBaseView(
load_directory_contents(current_path);
if (!entry_list.size()) {
empty_root = true;
empty_ = EmptyReason::NoFiles;
text_current.set("EMPTY SD CARD!");
} else {
menu_view.on_left = [this]() {
@@ -224,7 +239,7 @@ FileManBaseView::FileManBaseView(
}
void FileManBaseView::focus() {
if (empty_root) {
if (empty_ != EmptyReason::NotEmpty) {
button_exit.focus();
} else {
menu_view.focus();
@@ -287,8 +302,13 @@ void FileManBaseView::refresh_list() {
}
});
}
// HACK: Should page menu items instead of limiting the number.
if (menu_view.item_count() >= max_items_shown)
break;
}
text_info.set(menu_view.item_count() >= max_items_shown ? "Too many files!" : "");
menu_view.set_highlighted(prev_highlight);
}
@@ -310,34 +330,8 @@ const FileManBaseView::file_assoc_t& FileManBaseView::get_assoc(
return file_types[index];
}
/*void FileSaveView::on_save_name() {
text_prompt(nav_, &filename_buffer, 8, [this](std::string * buffer) {
nav_.pop();
});
}
FileSaveView::FileSaveView(
NavigationView& nav
) : FileManBaseView(nav)
{
name_buffer.clear();
add_children({
&text_save,
&button_save_name,
&live_timestamp
});
button_save_name.on_select = [this, &nav](Button&) {
on_save_name();
};
}*/
/* FileLoadView **************************************************************/
void FileLoadView::refresh_widgets(const bool) {
set_dirty();
}
FileLoadView::FileLoadView(
NavigationView& nav,
std::string filter
@@ -367,6 +361,68 @@ FileLoadView::FileLoadView(
};
}
void FileLoadView::refresh_widgets(const bool) {
set_dirty();
}
/* FileSaveView **************************************************************/
/*
FileSaveView::FileSaveView(
NavigationView& nav,
const fs::path& path,
const fs::path& file
) : nav_{ nav },
path_{ path },
file_{ file }
{
add_children({
&labels,
&text_path,
&button_edit_path,
&text_name,
&button_edit_name,
&button_save,
&button_cancel,
});
button_edit_path.on_select = [this](Button&) {
buffer_ = path_.string();
text_prompt(nav_, buffer_, max_filename_length,
[this](std::string&) {
path_ = buffer_;
refresh_widgets();
});
};
button_edit_name.on_select = [this](Button&) {
buffer_ = file_.string();
text_prompt(nav_, buffer_, max_filename_length,
[this](std::string&) {
file_ = buffer_;
refresh_widgets();
});
};
button_save.on_select = [this](Button&) {
if (on_save)
on_save(path_ / file_);
else
nav_.pop();
};
button_cancel.on_select = [this](Button&) {
nav_.pop();
};
refresh_widgets();
}
void FileSaveView::refresh_widgets() {
text_path.set(truncate(path_, 30));
text_name.set(truncate(file_, 30));
set_dirty();
}
*/
/* FileManagerView ***********************************************************/
void FileManagerView::on_rename() {
@@ -387,7 +443,7 @@ void FileManagerView::on_rename() {
[this, renamed_path](const fs::path& partner, bool should_rename) mutable {
if (should_rename) {
auto new_name = renamed_path.replace_extension(partner.extension());
rename_file(current_path / partner, current_path / new_name);
rename_file(partner, current_path / new_name);
}
reload_current();
}
@@ -409,7 +465,7 @@ void FileManagerView::on_delete() {
nav_, get_selected_full_path(), "Delete",
[this](const fs::path& partner, bool should_delete) {
if (should_delete)
delete_file(current_path / partner);
delete_file(partner);
reload_current();
}
);
@@ -429,6 +485,34 @@ void FileManagerView::on_new_dir() {
});
}
void FileManagerView::on_new_file() {
name_buffer = "";
text_prompt(nav_, name_buffer, max_filename_length, [this](std::string& file_name) {
make_new_file(current_path / file_name);
reload_current();
});
}
void FileManagerView::on_paste() {
// TODO: handle partner file. Need to fix nav stack first.
auto new_name = get_unique_filename(current_path, clipboard_path.filename());
fs::filesystem_error result;
if (clipboard_mode == ClipboardMode::Cut)
result = rename_file(clipboard_path, current_path / new_name);
else if (clipboard_mode == ClipboardMode::Copy)
result = copy_file(clipboard_path, current_path / new_name);
if (result.code() != FR_OK)
nav_.display_modal("Paste Failed", result.what());
clipboard_path = fs::path{ };
clipboard_mode = ClipboardMode::None;
menu_view.focus();
reload_current();
}
bool FileManagerView::selected_is_valid() const {
return !entry_list.empty() &&
get_selected_entry().path != parent_dir_path;
@@ -437,63 +521,97 @@ bool FileManagerView::selected_is_valid() const {
void FileManagerView::refresh_widgets(const bool v) {
button_rename.hidden(v);
button_delete.hidden(v);
button_cut.hidden(v);
button_copy.hidden(v);
button_paste.hidden(v);
button_new_dir.hidden(v);
set_dirty();
}
button_new_file.hidden(v);
FileManagerView::~FileManagerView() {
set_dirty();
}
FileManagerView::FileManagerView(
NavigationView& nav
) : FileManBaseView(nav, "")
{
if (!empty_root) {
on_refresh_widgets = [this](bool v) {
refresh_widgets(v);
};
add_children({
&menu_view,
&labels,
&text_date,
&button_rename,
&button_delete,
&button_new_dir,
});
menu_view.on_highlight = [this]() {
// TODO: enable/disable buttons.
if (selected_is_valid())
text_date.set(to_string_FAT_timestamp(file_created_date(get_selected_full_path())));
else
text_date.set("");
};
refresh_list();
// Don't bother with the UI in the case of no SDC.
if (empty_ == EmptyReason::NoSDC)
return;
on_refresh_widgets = [this](bool v) {
refresh_widgets(v);
};
on_select_entry = [this](KeyEvent key) {
if (key == KeyEvent::Select && get_selected_entry().is_directory) {
push_dir(get_selected_entry().path);
} else {
button_rename.focus();
}
};
button_rename.on_select = [this](Button&) {
if (selected_is_valid())
on_rename();
};
add_children({
&menu_view,
&labels,
&text_date,
&button_rename,
&button_delete,
&button_cut,
&button_copy,
&button_paste,
&button_new_dir,
&button_new_file
});
menu_view.on_highlight = [this]() {
if (selected_is_valid())
text_date.set(to_string_FAT_timestamp(file_created_date(get_selected_full_path())));
else
text_date.set("");
};
refresh_list();
button_delete.on_select = [this](Button&) {
if (selected_is_valid())
on_delete();
};
on_select_entry = [this](KeyEvent key) {
if (key == KeyEvent::Select && get_selected_entry().is_directory) {
push_dir(get_selected_entry().path);
} else {
button_rename.focus();
}
};
button_rename.on_select = [this]() {
if (selected_is_valid())
on_rename();
};
button_new_dir.on_select = [this](Button&) {
on_new_dir();
};
}
button_delete.on_select = [this]() {
if (selected_is_valid())
on_delete();
};
button_cut.on_select = [this]() {
if (selected_is_valid() && !get_selected_entry().is_directory) {
clipboard_path = get_selected_full_path();
clipboard_mode = ClipboardMode::Cut;
} else
nav_.display_modal("Cut", "Can't cut that.");
};
button_copy.on_select = [this]() {
if (selected_is_valid() && !get_selected_entry().is_directory) {
clipboard_path = get_selected_full_path();
clipboard_mode = ClipboardMode::Copy;
} else
nav_.display_modal("Copy", "Can't copy that.");
};
button_paste.on_select = [this]() {
if (clipboard_mode != ClipboardMode::None)
on_paste();
else
nav_.display_modal("Paste", "Cut or copy a file first.");
};
button_new_dir.on_select = [this]() {
on_new_dir();
};
button_new_file.on_select = [this]() {
on_new_file();
};
}
}
+133 -38
View File
@@ -36,6 +36,18 @@ struct fileman_entry {
bool is_directory { };
};
enum class EmptyReason : uint8_t {
NotEmpty,
NoFiles,
NoSDC
};
enum class ClipboardMode : uint8_t {
None,
Cut,
Copy
};
class FileManBaseView : public View {
public:
FileManBaseView(
@@ -43,11 +55,15 @@ public:
std::string filter
);
virtual ~FileManBaseView() { }
void focus() override;
std::string title() const override { return "Fileman"; };
void push_dir(const std::filesystem::path& path);
protected:
static constexpr size_t max_filename_length = 50;
static constexpr size_t max_filename_length = 64;
static constexpr size_t max_items_shown = 100;
struct file_assoc_t {
std::filesystem::path extension;
@@ -65,11 +81,9 @@ protected:
{ u"", &bitmap_icon_file, ui::Color::light_grey() } // NB: Must be last.
};
std::filesystem::path get_selected_full_path() const;
const fileman_entry& get_selected_entry() const;
void push_dir(const std::filesystem::path& path);
void pop_dir();
void refresh_list();
void reload_current();
@@ -78,7 +92,7 @@ protected:
NavigationView& nav_;
bool empty_root { false };
EmptyReason empty_ { EmptyReason::NotEmpty };
std::function<void(KeyEvent)> on_select_entry { nullptr };
std::function<void(bool)> on_refresh_widgets { nullptr };
@@ -102,59 +116,106 @@ protected:
{ 0, 2 * 8, 240, 26 * 8 },
true
};
// HACK: for item count limit.
Text text_info {
{ 1 * 8, 35 * 8, 15 * 8, 16 },
""
};
Button button_exit {
{ 16 * 8, 34 * 8, 14 * 8, 32 },
{ 21 * 8, 34 * 8, 9 * 8, 32 },
"Exit"
};
};
/*class FileSaveView : public FileManBaseView {
public:
FileSaveView(NavigationView& nav);
~FileSaveView();
private:
std::string name_buffer { };
void on_save_name();
Text text_save {
{ 4 * 8, 15 * 8, 8 * 8, 16 },
"Save as:",
};
Button button_save_name {
{ 4 * 8, 18 * 8, 12 * 8, 32 },
"Name (set)"
};
LiveDateTime live_timestamp {
{ 17 * 8, 24 * 8, 11 * 8, 16 }
};
};*/
class FileLoadView : public FileManBaseView {
public:
std::function<void(std::filesystem::path)> on_changed { };
FileLoadView(NavigationView& nav, std::string filter);
virtual ~FileLoadView() { }
private:
void refresh_widgets(const bool v);
};
/*
// It would be nice to be able to launch FileLoadView
// but it will OOM if launched from within FileManager.
class FileSaveView : public View {
public:
FileSaveView(
NavigationView& nav,
const std::filesystem::path& path,
const std::filesystem::path& file);
std::function<void(std::filesystem::path)> on_save { };
private:
static constexpr size_t max_filename_length = 64;
void refresh_widgets();
NavigationView& nav_;
std::filesystem::path path_;
std::filesystem::path file_;
std::string buffer_ { };
Labels labels {
{ { 0 * 8, 1 * 16 }, "Path:", Color::light_grey() },
{ { 0 * 8, 6 * 16 }, "Filename:", Color::light_grey() },
};
Text text_path {
{ 0 * 8, 2 * 16, 30 * 8, 16 },
"",
};
Button button_edit_path {
{ 18 * 8, 3 * 16, 11 * 8, 32 },
"Edit Path"
};
Text text_name {
{ 0 * 8, 7 * 16, 30 * 8, 16 },
"",
};
Button button_edit_name {
{ 18 * 8, 8 * 16, 11 * 8, 32 },
"Edit Name"
};
Button button_save {
{ 10 * 8, 16 * 16, 9 * 8, 32 },
"Save"
};
Button button_cancel {
{ 20 * 8, 16 * 16, 9 * 8, 32 },
"Cancel"
};
};
*/
class FileManagerView : public FileManBaseView {
public:
FileManagerView(NavigationView& nav);
~FileManagerView();
virtual ~FileManagerView() { }
private:
// Passed by ref to other views needing lifetime extension.
std::string name_buffer { };
std::filesystem::path clipboard_path { };
ClipboardMode clipboard_mode { ClipboardMode::None };
void refresh_widgets(const bool v);
void on_rename();
void on_delete();
void on_paste();
void on_new_dir();
void on_new_file();
// True if the selected entry is a real file item.
bool selected_is_valid() const;
@@ -168,19 +229,53 @@ private:
""
};
Button button_rename {
{ 0 * 8, 29 * 8, 14 * 8, 32 },
"Rename"
NewButton button_rename {
{ 0 * 8, 29 * 8, 4 * 8, 32 },
{ },
&bitmap_icon_rename,
Color::dark_blue()
};
Button button_delete {
{ 16 * 8, 29 * 8, 14 * 8, 32 },
"Delete"
NewButton button_delete {
{ 4 * 8, 29 * 8, 4 * 8, 32 },
{ },
&bitmap_icon_trash,
Color::red()
};
Button button_new_dir {
{ 0 * 8, 34 * 8, 14 * 8, 32 },
"New Dir"
NewButton button_cut {
{ 9 * 8, 29 * 8, 4 * 8, 32 },
{ },
&bitmap_icon_cut,
Color::dark_grey()
};
NewButton button_copy {
{ 13 * 8, 29 * 8, 4 * 8, 32 },
{ },
&bitmap_icon_copy,
Color::dark_grey()
};
NewButton button_paste {
{ 17 * 8, 29 * 8, 4 * 8, 32 },
{ },
&bitmap_icon_paste,
Color::dark_grey()
};
NewButton button_new_dir {
{ 22 * 8, 29 * 8, 4 * 8, 32 },
{ },
&bitmap_icon_new_dir,
Color::green()
};
NewButton button_new_file {
{ 26 * 8, 29 * 8, 4 * 8, 32 },
{ },
&bitmap_icon_new_file,
Color::green()
};
};
+18 -30
View File
@@ -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)
}
}
};
+11 -25
View File
@@ -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,
-8
View File
@@ -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];
+1 -14
View File
@@ -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 }
};
File diff suppressed because it is too large Load Diff
+66 -123
View File
@@ -38,81 +38,10 @@
#include "string_format.hpp"
#include "file.hpp"
#include "app_settings.hpp"
// maximum usable freq
#define MAX_UFREQ 7200000000
#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);
@@ -120,37 +49,37 @@ namespace ui {
void focus() override;
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, 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,13 +130,41 @@ namespace ui {
bool load_hamradios = { true };
bool update_ranges = { true };
bool fwd = { true };
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
{
@@ -241,7 +199,7 @@ namespace ui {
{ 3 * 8, 1 * 16 },
6,
{ }
};
};
NumberField field_squelch {
{ 12 * 8, 1 * 16 },
@@ -254,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 {
@@ -277,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 },
};
@@ -309,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 {
@@ -394,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) {
@@ -404,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) {
@@ -419,7 +362,7 @@ namespace ui {
}
};
// app save settings
std::app_settings settings { };
std::app_settings settings { };
std::app_settings::AppSettings app_settings { };
};
+3 -128
View File
@@ -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.");
}
+50 -17
View File
@@ -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
+1 -1
View File
@@ -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_;
+1 -1
View File
@@ -446,7 +446,7 @@ namespace ui {
else
{
auto result = delete_file( pmem_flag_file );
if( result != 0 )
if( result.code() != FR_OK )
{
text_pmem_status.set("!err. deleting pmem flagfile!");
}
+1 -2
View File
@@ -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"
};
};
}
-1
View File
@@ -79,7 +79,6 @@ TestView::TestView(NavigationView& nav) {
cal_value = raw_alt - 0x80;
};
logger = std::make_unique<TestLogger>();
if (logger)
logger->append("saucepan.txt");
+8 -2
View File
@@ -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) {
+4
View File
@@ -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);
+131
View File
@@ -2543,6 +2543,137 @@ static constexpr Bitmap bitmap_target {
{ 16, 16 }, bitmap_target_data
};
static constexpr uint8_t bitmap_icon_trash_data[] = {
0x00, 0x00,
0xC0, 0x01,
0x20, 0x02,
0xFC, 0x1F,
0x00, 0x00,
0xA8, 0x0A,
0xA8, 0x0A,
0xA8, 0x0A,
0xA8, 0x0A,
0xA8, 0x0A,
0xA8, 0x0A,
0xA8, 0x0A,
0xA8, 0x0A,
0x08, 0x08,
0xF0, 0x07,
0x00, 0x00,
};
static constexpr Bitmap bitmap_icon_trash {
{ 16, 16 }, bitmap_icon_trash_data
};
static constexpr uint8_t bitmap_icon_copy_data[] = {
0x00, 0x00,
0xFC, 0x00,
0x84, 0x01,
0xC4, 0x0F,
0x74, 0x18,
0x44, 0x38,
0x44, 0x78,
0x74, 0x40,
0x44, 0x44,
0x44, 0x44,
0x74, 0x5F,
0x44, 0x44,
0x44, 0x44,
0x7C, 0x40,
0xC0, 0x7F,
0x00, 0x00,
};
static constexpr Bitmap bitmap_icon_copy {
{ 16, 16 }, bitmap_icon_copy_data
};
static constexpr uint8_t bitmap_icon_cut_data[] = {
0x00, 0x00,
0x10, 0x10,
0x30, 0x18,
0x20, 0x08,
0x60, 0x0C,
0x40, 0x04,
0xC0, 0x06,
0x80, 0x00,
0x80, 0x01,
0x80, 0x01,
0xC0, 0x03,
0x78, 0x1E,
0x44, 0x22,
0x44, 0x22,
0x44, 0x22,
0x38, 0x1C,
};
static constexpr Bitmap bitmap_icon_cut {
{ 16, 16 }, bitmap_icon_cut_data
};
static constexpr uint8_t bitmap_icon_paste_data[] = {
0x00, 0x00,
0xE0, 0x00,
0x18, 0x03,
0xE4, 0x04,
0x04, 0x04,
0x04, 0x04,
0x84, 0x3F,
0x84, 0x20,
0x84, 0x2E,
0x84, 0x20,
0x84, 0x2E,
0x84, 0x20,
0x84, 0x2E,
0xF8, 0x20,
0x80, 0x3F,
0x00, 0x00,
};
static constexpr Bitmap bitmap_icon_paste {
{ 16, 16 }, bitmap_icon_paste_data
};
static constexpr uint8_t bitmap_icon_new_dir_data[] = {
0x00, 0x00,
0x1E, 0x00,
0x21, 0x00,
0xE1, 0x7F,
0x01, 0xC0,
0x81, 0x81,
0x81, 0x81,
0x81, 0x81,
0xF1, 0x8F,
0xF1, 0x8F,
0x81, 0x81,
0x81, 0x81,
0x81, 0x81,
0x03, 0xC0,
0xFE, 0x7F,
0x00, 0x00,
};
static constexpr Bitmap bitmap_icon_new_dir {
{ 16, 16 }, bitmap_icon_new_dir_data
};
static constexpr uint8_t bitmap_icon_new_file_data[] = {
0x00, 0x00,
0xFC, 0x07,
0x04, 0x0C,
0x04, 0x1C,
0x04, 0x3C,
0x84, 0x21,
0x84, 0x21,
0x84, 0x21,
0xF4, 0x2F,
0xF4, 0x2F,
0x84, 0x21,
0x84, 0x21,
0x84, 0x21,
0x04, 0x20,
0xFC, 0x3F,
0x00, 0x00,
};
static constexpr Bitmap bitmap_icon_new_file {
{ 16, 16 }, bitmap_icon_new_file_data
};
} /* namespace ui */
+128 -34
View File
@@ -124,50 +124,85 @@ Optional<File::Error> File::sync() {
}
}
static std::filesystem::path find_last_file_matching_pattern(const std::filesystem::path& pattern) {
std::filesystem::path last_match;
for(const auto& entry : std::filesystem::directory_iterator(u"", pattern)) {
if( std::filesystem::is_regular_file(entry.status()) ) {
/* Range used for filename matching.
* Start and end are inclusive positions of "???" */
struct pattern_range {
size_t start;
size_t end;
};
/* Finds the last file matching the specified pattern that
* can be automatically incremented (digits in pattern).
* NB: assumes a patten with contiguous '?' like "FOO_???.txt". */
static std::filesystem::path find_last_ordinal_match(
const std::filesystem::path& folder,
const std::filesystem::path& pattern,
pattern_range range
) {
auto last_match = std::filesystem::path();
auto can_increment = [range](const auto& path) {
for (auto i = range.start; i <= range.end; ++i)
if (!isdigit(path.native()[i]))
return false;
return true;
};
for (const auto& entry : std::filesystem::directory_iterator(folder, pattern)) {
if (std::filesystem::is_regular_file(entry.status()) && can_increment(entry.path())) {
const auto& match = entry.path();
if( match > last_match ) {
last_match = match;
}
}
}
return last_match;
}
static std::filesystem::path increment_filename_stem_ordinal(std::filesystem::path path) {
auto t = path.replace_extension().native();
auto it = t.rbegin();
/* Given a file path like "FOO_0001.txt" increment it to "FOO_0002.txt". */
static std::filesystem::path increment_filename_ordinal(
const std::filesystem::path& path, pattern_range range
) {
auto name = path.filename().native();
// Increment decimal number before the extension.
for(; it != t.rend(); ++it) {
const auto c = *it;
if( c < '0' ) {
for (auto i = range.end; i >= range.start; --i) {
auto& c = name[i];
// Not a digit or would overflow the counter.
if (c < u'0' || c > u'9' || (c == u'9' && i == range.start))
return { };
} else if( c < '9' ) {
*it += 1;
if (c == u'9')
c = '0';
else {
c++;
break;
} else if( c == '9' ) {
*it = '0';
} else {
return { };
}
}
return t;
return { name };
}
std::filesystem::path next_filename_stem_matching_pattern(std::filesystem::path filename_pattern) {
const auto next_filename = find_last_file_matching_pattern(filename_pattern.replace_extension(u".*"));
if( next_filename.empty() ) {
auto pattern_s = filename_pattern.replace_extension().native();
std::replace(std::begin(pattern_s), std::end(pattern_s), '?', '0');
return pattern_s;
} else {
return increment_filename_stem_ordinal(next_filename);
std::filesystem::path next_filename_matching_pattern(const std::filesystem::path& filename_pattern) {
auto path = filename_pattern.parent_path();
auto pattern = filename_pattern.filename();
auto range = pattern_range {
pattern.native().find_first_of(u'?'),
pattern.native().find_last_of(u'?')
};
const auto match = find_last_ordinal_match(path, pattern, range);
if (match.empty()) {
auto pattern_str = pattern.native();
for (auto i = range.start; i <= range.end; ++i)
pattern_str[i] = u'0';
return path / pattern_str;
}
auto next_name = increment_filename_ordinal(match, range);
return next_name.empty() ? next_name : path / next_name;
}
std::vector<std::filesystem::path> scan_root_files(const std::filesystem::path& directory,
@@ -197,12 +232,44 @@ std::vector<std::filesystem::path> scan_root_directories(const std::filesystem::
return directory_list;
}
uint32_t delete_file(const std::filesystem::path& file_path) {
return f_unlink(reinterpret_cast<const TCHAR*>(file_path.c_str()));
std::filesystem::filesystem_error delete_file(const std::filesystem::path& file_path) {
return { f_unlink(reinterpret_cast<const TCHAR*>(file_path.c_str())) };
}
uint32_t rename_file(const std::filesystem::path& file_path, const std::filesystem::path& new_name) {
return f_rename(reinterpret_cast<const TCHAR*>(file_path.c_str()), reinterpret_cast<const TCHAR*>(new_name.c_str()));
std::filesystem::filesystem_error rename_file(
const std::filesystem::path& file_path,
const std::filesystem::path& new_name
) {
return { f_rename(reinterpret_cast<const TCHAR*>(file_path.c_str()), reinterpret_cast<const TCHAR*>(new_name.c_str())) };
}
std::filesystem::filesystem_error copy_file(
const std::filesystem::path& file_path,
const std::filesystem::path& dest_path
) {
File src;
File dst;
constexpr size_t buffer_size = 128;
uint8_t buffer[buffer_size];
auto error = src.open(file_path);
if (error.is_valid()) return error.value();
error = dst.create(dest_path);
if (error.is_valid()) return error.value();
while (true) {
auto result = src.read(buffer, buffer_size);
if (result.is_error()) return result.error();
result = dst.write(buffer, result.value());
if (result.is_error()) return result.error();
if (result.value() < buffer_size)
break;
}
return { };
}
FATTimestamp file_created_date(const std::filesystem::path& file_path) {
@@ -213,8 +280,33 @@ FATTimestamp file_created_date(const std::filesystem::path& file_path) {
return { filinfo.fdate, filinfo.ftime };
}
uint32_t make_new_directory(const std::filesystem::path& dir_path) {
return f_mkdir(reinterpret_cast<const TCHAR*>(dir_path.c_str()));
std::filesystem::filesystem_error make_new_file(
const std::filesystem::path& file_path
) {
File f;
auto result = f.create(file_path);
return result.is_valid()
? result.value()
: std::filesystem::filesystem_error{ };
}
std::filesystem::filesystem_error make_new_directory(
const std::filesystem::path& dir_path
) {
return { f_mkdir(reinterpret_cast<const TCHAR*>(dir_path.c_str())) };
}
std::filesystem::filesystem_error ensure_directory(
const std::filesystem::path& dir_path
) {
if (dir_path.empty() || std::filesystem::file_exists(dir_path))
return { };
auto result = ensure_directory(dir_path.parent_path());
if (result.code())
return result;
return make_new_directory(dir_path);
}
namespace std {
@@ -339,8 +431,10 @@ directory_iterator::directory_iterator(
) : pattern { wild }
{
impl = std::make_shared<Impl>();
const auto result = f_findfirst(&impl->dir, &impl->filinfo, reinterpret_cast<const TCHAR*>(path.c_str()), reinterpret_cast<const TCHAR*>(pattern.c_str()));
if( result != FR_OK || impl->filinfo.fname[0] == (TCHAR)'\0') {
const auto result = f_findfirst(&impl->dir, &impl->filinfo,
reinterpret_cast<const TCHAR*>(path.c_str()),
reinterpret_cast<const TCHAR*>(pattern.c_str()));
if (result != FR_OK || impl->filinfo.fname[0] == (TCHAR)'\0') {
impl.reset();
// TODO: Throw exception if/when I enable exceptions...
}
+16 -5
View File
@@ -59,6 +59,10 @@ struct filesystem_error {
std::string what() const;
bool ok() const {
return err == FR_OK;
}
private:
uint32_t err { FR_OK };
};
@@ -252,16 +256,23 @@ struct FATTimestamp {
uint16_t FAT_time;
};
uint32_t delete_file(const std::filesystem::path& file_path);
uint32_t rename_file(const std::filesystem::path& file_path, const std::filesystem::path& new_name);
std::filesystem::filesystem_error delete_file(const std::filesystem::path& file_path);
std::filesystem::filesystem_error rename_file(const std::filesystem::path& file_path, const std::filesystem::path& new_name);
std::filesystem::filesystem_error copy_file(const std::filesystem::path& file_path, const std::filesystem::path& dest_path);
FATTimestamp file_created_date(const std::filesystem::path& file_path);
uint32_t make_new_directory(const std::filesystem::path& dir_path);
std::filesystem::filesystem_error make_new_file(const std::filesystem::path& file_path);
std::filesystem::filesystem_error make_new_directory(const std::filesystem::path& dir_path);
std::filesystem::filesystem_error ensure_directory(const std::filesystem::path& dir_path);
std::vector<std::filesystem::path> scan_root_files(const std::filesystem::path& directory, const std::filesystem::path& extension);
std::vector<std::filesystem::path> scan_root_directories(const std::filesystem::path& directory);
/* Gets an auto incrementing filename. */
std::filesystem::path next_filename_stem_matching_pattern(std::filesystem::path filename_stem_pattern);
/* Gets an auto incrementing filename stem.
* Pattern should be like "FOO_???.txt" where ??? will be replaced by digits.
* Pattern may also contain a folder path like "LOGS/FOO_???.txt".
* Pattern '?' must be contiguous (bad: "FOO?_??")
* Returns empty path if a filename could not be created. */
std::filesystem::path next_filename_matching_pattern(const std::filesystem::path& pattern);
/* Values added to FatFs FRESULT enum, values outside the FRESULT data type */
static_assert(sizeof(FIL::err) == 1, "FatFs FIL::err size not expected.");
+38 -33
View File
@@ -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=");
+1 -1
View File
@@ -87,7 +87,7 @@ struct freqman_entry {
freqman_entry_type type { }; // SINGLE,RANGE,HAMRADIO
freqman_index_t modulation { }; // AM,NFM,WFM
freqman_index_t bandwidth { }; // AM_DSB, ...
freqman_index_t step { }; // 5Khz (SA AM,...
freqman_index_t step { }; // 5khz (SA AM,...
tone_index tone { }; // 0XZ, 11 1ZB,...
};
+7
View File
@@ -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);
}
+4 -2
View File
@@ -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 */
+11 -2
View File
@@ -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);
}
+2
View File
@@ -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__*/
+21 -10
View File
@@ -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;
}
+4
View File
@@ -167,6 +167,10 @@ void MenuView::clear() {
offset = 0;
}
size_t MenuView::item_count() const {
return menu_items.size();
}
void MenuView::add_item(MenuItem new_item) {
menu_items.push_back(new_item);
+1
View File
@@ -83,6 +83,7 @@ public:
void add_item(MenuItem new_item);
void add_items(std::initializer_list<MenuItem> new_items);
void clear();
size_t item_count() const;
MenuItemView* item_view(size_t index) const;
+16 -1
View File
@@ -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;
}
+2
View File
@@ -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 },
-131
View File
@@ -61,137 +61,6 @@ void text_prompt(
}*/
}
/* TextField ***********************************************************/
TextField::TextField(
std::string& str,
size_t max_length,
Point position,
uint32_t length
) : Widget{ { position, { 8 * static_cast<int>(length), 16 } } },
text_{ str },
max_length_{ std::max<size_t>(max_length, str.length()) },
char_count_{ std::max<uint32_t>(length, 1) },
cursor_pos_{ text_.length() },
insert_mode_{ true }
{
set_focusable(true);
}
const std::string& TextField::value() const {
return text_;
}
void TextField::set_cursor(uint32_t pos) {
cursor_pos_ = std::min<size_t>(pos, text_.length());
set_dirty();
}
void TextField::set_insert_mode() {
insert_mode_ = true;
}
void TextField::set_overwrite_mode() {
insert_mode_ = false;
}
void TextField::char_add(char c) {
// Don't add if inserting and at max_length and
// don't overwrite if past the end of the text.
if ((text_.length() >= max_length_ && insert_mode_) ||
(cursor_pos_ >= text_.length() && !insert_mode_))
return;
if (insert_mode_)
text_.insert(cursor_pos_, 1, c);
else
text_[cursor_pos_] = c;
cursor_pos_++;
set_dirty();
}
void TextField::char_delete() {
if (cursor_pos_ == 0)
return;
cursor_pos_--;
text_.erase(cursor_pos_, 1);
set_dirty();
}
void TextField::paint(Painter& painter) {
constexpr int char_width = 8;
auto rect = screen_rect();
auto text_style = has_focus() ? style().invert() : style();
auto offset = 0;
// Does the string need to be shifted?
if (cursor_pos_ >= char_count_)
offset = cursor_pos_ - char_count_ + 1;
// Clear the control.
painter.fill_rectangle(rect, text_style.background);
// Draw the text starting at the offset.
for (uint32_t i = 0; i < char_count_ && i + offset < text_.length(); i++) {
painter.draw_char(
{ rect.location().x() + (static_cast<int>(i) * char_width), rect.location().y() },
text_style,
text_[i + offset]
);
}
// Determine cursor position on screen (either the cursor position or the last char).
int32_t cursor_x = char_width * (offset > 0 ? char_count_ - 1 : cursor_pos_);
Point cursor_point{ screen_pos().x() + cursor_x, screen_pos().y() };
auto cursor_style = text_style.invert();
// Invert the cursor character when in overwrite mode.
if (!insert_mode_ && (cursor_pos_) < text_.length())
painter.draw_char(cursor_point, cursor_style, text_[cursor_pos_]);
// Draw the cursor.
Rect cursor_box{ cursor_point, { char_width, 16 } };
painter.draw_rectangle(cursor_box, cursor_style.background);
}
bool TextField::on_key(const KeyEvent key) {
if (key == KeyEvent::Left && cursor_pos_ > 0)
cursor_pos_--;
else if (key == KeyEvent::Right && cursor_pos_ < text_.length())
cursor_pos_++;
else if (key == KeyEvent::Select)
insert_mode_ = !insert_mode_;
else
return false;
set_dirty();
return true;
}
bool TextField::on_encoder(const EncoderEvent delta) {
int32_t new_pos = cursor_pos_ + delta;
// Let the encoder wrap around the ends of the text.
if (new_pos < 0)
new_pos = text_.length();
else if (static_cast<size_t>(new_pos) > text_.length())
new_pos = 0;
set_cursor(new_pos);
return true;
}
bool TextField::on_touch(const TouchEvent event) {
if (event.type == TouchEvent::Type::Start)
focus();
set_dirty();
return true;
}
/* TextEntryView ***********************************************************/
void TextEntryView::char_delete() {
-46
View File
@@ -28,52 +28,6 @@
namespace ui {
// A TextField is bound to a string reference and allows the string
// to be manipulated. The field itself does not provide the UI for
// setting the value. It provides the UI of rendering the text,
// a cursor, and an API to edit the string content.
class TextField : public Widget {
public:
TextField(std::string& str, Point position, uint32_t length = 30)
: TextField{str, 64, position, length} { }
// Str: the string containing the content to edit.
// Max_length: max length the string is allowed to use.
// Position: the top-left corner of the control.
// Length: the number of characters to display.
// - Characters are 8 pixels wide.
// - The screen can show 30 characters max.
// - The control is 16 pixels tall.
TextField(std::string& str, size_t max_length, Point position, uint32_t length = 30);
TextField(const TextField&) = delete;
TextField(TextField&&) = delete;
TextField& operator=(const TextField&) = delete;
TextField& operator=(TextField&&) = delete;
const std::string& value() const;
void set_cursor(uint32_t pos);
void set_insert_mode();
void set_overwrite_mode();
void char_add(char c);
void char_delete();
void paint(Painter& painter) override;
bool on_key(const KeyEvent key) override;
bool on_encoder(const EncoderEvent delta) override;
bool on_touch(const TouchEvent event) override;
protected:
std::string& text_;
size_t max_length_;
uint32_t char_count_;
uint32_t cursor_pos_;
bool insert_mode_;
};
class TextEntryView : public View {
public:
std::function<void(std::string&)> on_changed { };
@@ -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__*/
+6 -2
View File
@@ -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>(); } },
});
}
+1 -1
View File
@@ -135,7 +135,7 @@ namespace ui
Color::dark_grey()};
ImageButton button_back{
{0, 0 * 16, 12 * 8, 16},//back button is long enough to cover the title area to make it easier to touch
{0, 0 * 16, 12 * 8, 16}, // Back button also covers the title for easier touch.
&bitmap_icon_previous,
Color::white(),
Color::dark_grey()};
+10 -6
View File
@@ -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);
}
+3 -1
View File
@@ -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;
+8
View File
@@ -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
+1 -1
View File
@@ -67,7 +67,7 @@ void SigGenProcessor::execute(const buffer_c8_t& buffer) {
// 16 bits LFSR .taps: 16, 15, 13, 4 ;feedback polynomial: x^16 + x^15 + x^13 + x^4 + 1
// Periode 65535= 2^n-1, quite continuous .
if (counter == 0) { // we slow down the shift register, because the pseudo random noise clock freq was too high for modulator.
bit_16 = ((lfsr_16 >> 0) ^ (lfsr_16 >> 1) ^ (lfsr_16 >> 3) ^ (lfsr_16 >> 4) ^ (lfsr_16 >> 12) & 1);
bit_16 = ((lfsr_16 >> 0) ^ (lfsr_16 >> 1) ^ (lfsr_16 >> 3) ^ (lfsr_16 >> 4) ^ ((lfsr_16 >> 12) & 1));
lfsr_16 = (lfsr_16 >> 1) | (bit_16 << 15);
sample = (lfsr_16 & 0x00FF); // main pseudo random noise generator.
}
+178
View File
@@ -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;
}
};
+1
View File
@@ -20,3 +20,4 @@
*/
#include "dsp_fft.hpp"
#include "complex.hpp"
+42
View File
@@ -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__*/
+53
View File
@@ -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
+4 -4
View File
@@ -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;
+38
View File
@@ -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__*/
+1 -1
View File
@@ -68,7 +68,7 @@ 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 };
};
+86
View File
@@ -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);
}
+35
View File
@@ -0,0 +1,35 @@
/*
* 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
/* Period parameters */
#define N 624
#define M 397
#define MATRIX_A 0x9908b0dfUL /* constant vector a */
#define UMASK 0x80000000UL /* most significant w-r bits */
#define LMASK 0x7fffffffUL /* least significant r bits */
#define MIXBITS(u, v) (((u)&UMASK) | ((v)&LMASK))
#define TWIST(u, v) ((MIXBITS(u, v) >> 1) ^ ((v)&1UL ? MATRIX_A : 0UL))
/* initializes state[N] with a seed */
extern void init_genrand(unsigned long s);
extern long genrand_int31(void);
+1
View File
@@ -104,6 +104,7 @@ constexpr image_tag_t image_tag_rds { 'P', 'R', 'D', 'S' };
constexpr image_tag_t image_tag_replay { 'P', 'R', 'E', 'P' };
constexpr image_tag_t image_tag_gps { 'P', 'G', 'P', 'S' };
constexpr image_tag_t image_tag_siggen { 'P', 'S', 'I', 'G' };
constexpr image_tag_t image_tag_spectrum_painter { 'P', 'S', 'P', 'T' };
constexpr image_tag_t image_tag_sstv_tx { 'P', 'S', 'T', 'X' };
constexpr image_tag_t image_tag_tones { 'P', 'T', 'O', 'N' };
constexpr image_tag_t image_tag_flash_utility { 'P', 'F', 'U', 'T' };
+10
View File
@@ -57,6 +57,16 @@ struct Color {
)}
{
}
uint8_t to_greyscale() {
uint32_t r = ((v >> 11) & 31U) << 3;
uint32_t g = ((v >> 5) & 63U) << 2;
uint32_t b = (v & 31U) << 3;
uint32_t grey = (r * 299 + g * 587 + b * 114) / 1000;
return (uint8_t)grey;
}
Color operator-() const {
return (v ^ 0xffff);
+142 -2
View File
@@ -1143,9 +1143,18 @@ NewButton::NewButton(
Rect parent_rect,
std::string text,
const Bitmap* bitmap
) : NewButton { parent_rect, text, bitmap, Color::dark_cyan() }
{ }
NewButton::NewButton(
Rect parent_rect,
std::string text,
const Bitmap* bitmap,
Color color
) : Widget { parent_rect },
text_ { text },
bitmap_ (bitmap)
bitmap_ { bitmap },
color_ { color }
{
set_focusable(true);
}
@@ -1535,6 +1544,137 @@ bool OptionsField::on_touch(const TouchEvent event) {
return true;
}
/* TextField ***********************************************************/
TextField::TextField(
std::string& str,
size_t max_length,
Point position,
uint32_t length
) : Widget{ { position, { 8 * static_cast<int>(length), 16 } } },
text_{ str },
max_length_{ std::max<size_t>(max_length, str.length()) },
char_count_{ std::max<uint32_t>(length, 1) },
cursor_pos_{ text_.length() },
insert_mode_{ true }
{
set_focusable(true);
}
const std::string& TextField::value() const {
return text_;
}
void TextField::set_cursor(uint32_t pos) {
cursor_pos_ = std::min<size_t>(pos, text_.length());
set_dirty();
}
void TextField::set_insert_mode() {
insert_mode_ = true;
}
void TextField::set_overwrite_mode() {
insert_mode_ = false;
}
void TextField::char_add(char c) {
// Don't add if inserting and at max_length and
// don't overwrite if past the end of the text.
if ((text_.length() >= max_length_ && insert_mode_) ||
(cursor_pos_ >= text_.length() && !insert_mode_))
return;
if (insert_mode_)
text_.insert(cursor_pos_, 1, c);
else
text_[cursor_pos_] = c;
cursor_pos_++;
set_dirty();
}
void TextField::char_delete() {
if (cursor_pos_ == 0)
return;
cursor_pos_--;
text_.erase(cursor_pos_, 1);
set_dirty();
}
void TextField::paint(Painter& painter) {
constexpr int char_width = 8;
auto rect = screen_rect();
auto text_style = has_focus() ? style().invert() : style();
auto offset = 0;
// Does the string need to be shifted?
if (cursor_pos_ >= char_count_)
offset = cursor_pos_ - char_count_ + 1;
// Clear the control.
painter.fill_rectangle(rect, text_style.background);
// Draw the text starting at the offset.
for (uint32_t i = 0; i < char_count_ && i + offset < text_.length(); i++) {
painter.draw_char(
{ rect.location().x() + (static_cast<int>(i) * char_width), rect.location().y() },
text_style,
text_[i + offset]
);
}
// Determine cursor position on screen (either the cursor position or the last char).
int32_t cursor_x = char_width * (offset > 0 ? char_count_ - 1 : cursor_pos_);
Point cursor_point{ screen_pos().x() + cursor_x, screen_pos().y() };
auto cursor_style = text_style.invert();
// Invert the cursor character when in overwrite mode.
if (!insert_mode_ && (cursor_pos_) < text_.length())
painter.draw_char(cursor_point, cursor_style, text_[cursor_pos_]);
// Draw the cursor.
Rect cursor_box{ cursor_point, { char_width, 16 } };
painter.draw_rectangle(cursor_box, cursor_style.background);
}
bool TextField::on_key(const KeyEvent key) {
if (key == KeyEvent::Left && cursor_pos_ > 0)
cursor_pos_--;
else if (key == KeyEvent::Right && cursor_pos_ < text_.length())
cursor_pos_++;
else if (key == KeyEvent::Select)
insert_mode_ = !insert_mode_;
else
return false;
set_dirty();
return true;
}
bool TextField::on_encoder(const EncoderEvent delta) {
int32_t new_pos = cursor_pos_ + delta;
// Let the encoder wrap around the ends of the text.
if (new_pos < 0)
new_pos = text_.length();
else if (static_cast<size_t>(new_pos) > text_.length())
new_pos = 0;
set_cursor(new_pos);
return true;
}
bool TextField::on_touch(const TouchEvent event) {
if (event.type == TouchEvent::Type::Start)
focus();
set_dirty();
return true;
}
/* NumberField ***********************************************************/
NumberField::NumberField(
@@ -1560,7 +1700,7 @@ int32_t NumberField::value() const {
void NumberField::set_value(int32_t new_value, bool trigger_change) {
if (can_loop) {
if (new_value >= 0)
if (new_value >= range.first)
new_value = new_value % (range.second + 1);
else
new_value = range.second + new_value + 1;
+48 -4
View File
@@ -414,7 +414,6 @@ private:
bool instant_exec_ { false };
};
class ButtonWithEncoder : public Widget {
public:
std::function<void(ButtonWithEncoder&)> on_select { };
@@ -457,8 +456,6 @@ private:
bool instant_exec_ { false };
};
class NewButton : public Widget {
public:
std::function<void(void)> on_select { };
@@ -469,6 +466,7 @@ public:
NewButton(const NewButton&) = delete;
NewButton& operator=(const NewButton&) = delete;
NewButton(Rect parent_rect, std::string text, const Bitmap* bitmap);
NewButton(Rect parent_rect, std::string text, const Bitmap* bitmap, Color color);
NewButton(
) : NewButton { { }, { }, { } }
{
@@ -489,8 +487,8 @@ public:
private:
std::string text_;
Color color_ = Color::dark_cyan();
const Bitmap* bitmap_;
Color color_;
};
class Image : public Widget {
@@ -610,6 +608,52 @@ private:
size_t selected_index_ { 0 };
};
// A TextField is bound to a string reference and allows the string
// to be manipulated. The field itself does not provide the UI for
// setting the value. It provides the UI of rendering the text,
// a cursor, and an API to edit the string content.
class TextField : public Widget {
public:
TextField(std::string& str, Point position, uint32_t length = 30)
: TextField{str, 64, position, length} { }
// Str: the string containing the content to edit.
// Max_length: max length the string is allowed to use.
// Position: the top-left corner of the control.
// Length: the number of characters to display.
// - Characters are 8 pixels wide.
// - The screen can show 30 characters max.
// - The control is 16 pixels tall.
TextField(std::string& str, size_t max_length, Point position, uint32_t length = 30);
TextField(const TextField&) = delete;
TextField(TextField&&) = delete;
TextField& operator=(const TextField&) = delete;
TextField& operator=(TextField&&) = delete;
const std::string& value() const;
void set_cursor(uint32_t pos);
void set_insert_mode();
void set_overwrite_mode();
void char_add(char c);
void char_delete();
void paint(Painter& painter) override;
bool on_key(const KeyEvent key) override;
bool on_encoder(const EncoderEvent delta) override;
bool on_touch(const TouchEvent event) override;
protected:
std::string& text_;
size_t max_length_;
uint32_t char_count_;
uint32_t cursor_pos_;
bool insert_mode_;
};
class NumberField : public Widget {
public:
std::function<void(NumberField&)> on_select { };
Binary file not shown.

After

Width:  |  Height:  |  Size: 293 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 286 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 286 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 B

Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -1 +1 @@
a=76000000,b=90000000,m=WFM,bw=16k,s=50KHz,d=WFM Japan
a=76000000,b=90000000,m=WFM,bw=200k,s=50kHz,d=WFM Japan
+1 -1
View File
@@ -1 +1 @@
a=65800000,b=74000000,m=WFM,bw=16k,s=30KHz,d=WFM OIRT
a=65800000,b=74000000,m=WFM,bw=200k,s=30kHz,d=WFM OIRT
+1 -1
View File
@@ -1 +1 @@
a=87500000,b=108000000,m=WFM,bw=16k,s=50KHz,d=WFM Standard
a=87500000,b=108000000,m=WFM,bw=200k,s=100kHz,d=WFM Standard
+2
View File
@@ -0,0 +1,2 @@
a=144000000,b=147990000,m=NFM,bw=11k,s=5kHz,d=HAM 2m
+2
View File
@@ -0,0 +1,2 @@
a=441000000,b=450000000,m=NFM,bw=11k,s=25kHz,d=HAM 70cm
+4
View File
@@ -0,0 +1,4 @@
f=92500000,m=WFM,bw=200k,d=NRK Oslo
f=94800000,m=WFM,bw=200k,d=NRK Bergen
f=97200000,m=WFM,bw=200k,d=NRK Melhus
f=98600000,m=WFM,bw=200k,d=NRK Arendal
@@ -9,7 +9,6 @@ f=91300000,m=WFM,bw=200k,d=RMF Łobez
f=91500000,m=WFM,bw=200k,d=RMF Ostroleka
f=91700000,m=WFM,bw=200k,d=RMF Wagrowiec
f=91900000,m=WFM,bw=200k,d=RMF Siedlce
f=92500000,m=WFM,bw=200k,d=NRK Oslo
f=92600000,m=WFM,bw=200k,d=RMF Elbląg
f=92900000,m=WFM,bw=200k,d=RMF Wroclaw
f=93000000,m=WFM,bw=200k,d=RMF Katowice
@@ -19,7 +18,6 @@ f=93600000,m=WFM,bw=200k,d=RMF Ryki
f=93800000,m=WFM,bw=200k,d=RMF Luban
f=94300000,m=WFM,bw=200k,d=RMF Płock
f=94600000,m=WFM,bw=200k,d=RMF Poznan
f=94800000,m=WFM,bw=200k,d=NRK Bergen
f=94800000,m=WFM,bw=200k,d=RMF Zagan
f=95100000,m=WFM,bw=200k,d=RMF Suwałki
f=95300000,m=WFM,bw=200k,d=RMF Olsztyn
@@ -30,10 +28,8 @@ f=96100000,m=WFM,bw=200k,d=RMF Gorzow Wielkopolski
f=96400000,m=WFM,bw=200k,d=RMF Bialogard
f=96600000,m=WFM,bw=200k,d=RMF Walcz
f=97100000,m=WFM,bw=200k,d=RMF Włoszczowa
f=97200000,m=WFM,bw=200k,d=NRK Melhus
f=98000000,m=WFM,bw=200k,d=RMF Kalisz
f=98400000,m=WFM,bw=200k,d=RMF Gdansk
f=98600000,m=WFM,bw=200k,d=NRK Arendal
f=98900000,m=WFM,bw=200k,d=RMF Konin
f=99300000,m=WFM,bw=200k,d=RMF Opole
f=99500000,m=WFM,bw=200k,d=RMF Kluczbork
+15 -15
View File
@@ -1,6 +1,6 @@
a=150000,b=285000,m=AM,bw=DSB,s=5KHz,d=Longwave broadcast band|BCB (EU)
a=525000,b=1605000,m=AM,bw=DSB,s=5KHz,d=AM broadcast band|BCB (EU & J)
a=530000,b=1710000,m=AM,bw=DSB,s=5KHz,d=AM broadcast band|BCB (US)
a=150000,b=285000,m=AM,bw=DSB,s=5kHz,d=Longwave broadcast band|BCB (EU)
a=525000,b=1605000,m=AM,bw=DSB,s=5kHz,d=AM broadcast band|BCB (EU & J)
a=530000,b=1710000,m=AM,bw=DSB,s=5kHz,d=AM broadcast band|BCB (US)
a=1800000,b=29700000,m=WFM,bw=16k,d=Amateur_radio|Amateur
a=26900000,b=27400000,m=WFM,bw=16k,d=Citizens_band_radio|Citizens band
a=28000000,b=30000000,m=WFM,bw=16k,d=Amateur_radio|Amateur
@@ -8,15 +8,15 @@ a=29000000,b=54000000,m=WFM,bw=16k,d=Land mobile
a=50000000,b=54000000,m=WFM,bw=16k,d=Amateur_radio|Amateur
a=65000000,b=85000000,m=WFM,bw=16k,d=Land mobile (EU)
a=108000000,b=136000000,m=WFM,bw=16k,d=Aircraft
a=120000000,b=160000000,m=WFM,bw=16k,s=50KHz,d=Land mobile (EU)
a=132000000,b=174000000,m=WFM,bw=16k,s=50KHz,d=Land mobile
a=142000000,b=170000000,m=WFM,bw=16k,s=50KHz,d=Land mobile (J)
a=144000000,b=148000000,m=WFM,bw=16k,s=50KHz,d=Amateur_radio|Amateur
a=216000000,b=222000000,m=WFM,bw=16k,s=50KHz,d=Land mobile
a=222000000,b=225000000,m=WFM,bw=16k,s=50KHz,d=Amateur_radio|Amateur
a=335000000,b=384000000,m=WFM,bw=16k,s=50KHz,d=Land mobile (J)
a=406000000,b=512000000,m=WFM,bw=16k,s=50KHz,d=Land mobile
a=450000000,b=470000000,m=WFM,bw=16k,s=50KHz,d=Land mobile (J)
a=430000000,b=450000000,m=WFM,bw=16k,s=50KHz,d=Amateur_radio|Amateur
a=806000000,b=947000000,m=WFM,bw=16k,s=50KHz,d=Land mobile
a=1200000000,b=1600000000,m=WFM,bw=16k,s=50KHz,d=Amateur|Land mobile|GPS
a=120000000,b=160000000,m=WFM,bw=16k,s=50kHz,d=Land mobile (EU)
a=132000000,b=174000000,m=WFM,bw=16k,s=50kHz,d=Land mobile
a=142000000,b=170000000,m=WFM,bw=16k,s=50kHz,d=Land mobile (J)
a=144000000,b=148000000,m=WFM,bw=16k,s=50kHz,d=Amateur_radio|Amateur
a=216000000,b=222000000,m=WFM,bw=16k,s=50kHz,d=Land mobile
a=222000000,b=225000000,m=WFM,bw=16k,s=50kHz,d=Amateur_radio|Amateur
a=335000000,b=384000000,m=WFM,bw=16k,s=50kHz,d=Land mobile (J)
a=406000000,b=512000000,m=WFM,bw=16k,s=50kHz,d=Land mobile
a=450000000,b=470000000,m=WFM,bw=16k,s=50kHz,d=Land mobile (J)
a=430000000,b=450000000,m=WFM,bw=16k,s=50kHz,d=Amateur_radio|Amateur
a=806000000,b=947000000,m=WFM,bw=16k,s=50kHz,d=Land mobile
a=1200000000,b=1600000000,m=WFM,bw=16k,s=50kHz,d=Amateur|Land mobile|GPS
+36 -7
View File
@@ -2,21 +2,50 @@
# (Freq range in MHz, min separation 3MHz)
# (Description up to 20 char)
# (fields comma delimiter)
140,380,VHF MICS AND MARINE
260,500,315/433 MHz KEYFOBS
390,420,RADIOSONDES
420,660,UHF MICS
# WATER METERS
850,900,Water meters
902,928,ISM 900MHz
# PAGERS https://www.fcc.gov/wireless/bureau-divisions/mobility-division/paging
34,36,USA PAGERS 35
43,45,USA PAGERS 44
151,153,USA PAGERS 152
453,454,USA PAGERS 454
929,931,USA PAGERS 930
# KEYFOBS
260,500,315/433 MHz KEYFOBS
# MICS, RADIOSONDES, LEYFOB
140,380,VHF MICS AND MARINE
390,420,RADIOSONDES
420,660,UHF MICS
# LoRa
433,435,LoRa 443EU
863,870,LoRa 868EU
902,928,LoRa 915US
779,787,LoRa 780CN
# BROADCAST RADIO
65,74,FM BROADCAST RUSSIA
76,95,FM BROADCAST JAPAN
76,108,FM BROADCAST BRAZIL
87,108,FM BROADCAST
# AVIATION
108,137,AVIATION 108
960,1215,AVIATION 960
# HAM RADIO
28,30,HAM 10-METER
50,54,HAM 6-METER
144,148,HAM 2-METER
219,225,HAM 1.25-METER
420,450,HAM 70-CM
902,928,HAM 33-CM
1240,1300,HAM 23-CM
# WATER METERS
850,900,Water meters
902,928,ISM 900MHz
# WIFI
2320,2560,BL / WIFI 2.4GHz
5160,5900,WIFI 5GHz
# ITU-DEFINED BANDS
1,3,MF BAND
3,30,HF BAND
30,300,VHF BAND
300,3000,UHF BAND
3000,7250,SHF BAND
# FULL
10,7250,FULL RANGE
Binary file not shown.
+2
View File
@@ -0,0 +1,2 @@
sample_rate=500000
center_frequency=433920000
Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More