mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-09-14 02:29:29 +00:00
Compare commits
65 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 63be4de418 | |||
| 497ca3f934 | |||
| 60de625c37 | |||
| a09c0e4c2d | |||
| a60f4ce9a8 | |||
| fcb681f4ae | |||
| 8530fa8194 | |||
| 89e8956df1 | |||
| 82ba35c162 | |||
| d750afe38f | |||
| 9b665a43c5 | |||
| 80c769b97d | |||
| 44dd8fd083 | |||
| 4e128e8930 | |||
| c2279e297c | |||
| cdd524b9f3 | |||
| 99809c7919 | |||
| 2390d79111 | |||
| 3b41d73ddd | |||
| 3d46c28911 | |||
| ca7840557b | |||
| ceaa115025 | |||
| 8dbcefbc75 | |||
| b28b96fb4a | |||
| e15a8ed2d8 | |||
| 19b77bf03b | |||
| 7b1541d2e8 | |||
| 830fea63ed | |||
| 3d59de55f8 | |||
| 5c1f5a7d2e | |||
| 4e985420d4 | |||
| 3e584a9652 | |||
| 39acaf846a | |||
| 76948cffa8 | |||
| 4ea8abb53b | |||
| 32085f317b | |||
| faa4367295 | |||
| 3f8a4957af | |||
| b5ea81cf96 | |||
| e14fa5eab0 | |||
| 960ecf616c | |||
| 996ccb0e40 | |||
| 407fee23b9 | |||
| 199570d4a5 | |||
| 5cd9c377d2 | |||
| e08273b8b8 | |||
| 6b0f90c321 | |||
| 0dea488e98 | |||
| e60c4dc34b | |||
| 0c75713820 | |||
| 6a28987a36 | |||
| 7331979107 | |||
| f22808f8ca | |||
| e1c519d71e | |||
| 909f37f89b | |||
| 63b0093321 | |||
| fa06df1400 | |||
| f83027d451 | |||
| 7ebf3d3cdd | |||
| 5daa0dfbb1 | |||
| 7b669d7001 | |||
| ba2407d691 | |||
| 5743d3a3b9 | |||
| 7f1c0f6f7e | |||
| 650d299c99 |
@@ -1 +1 @@
|
||||
v1.7.1
|
||||
v1.7.2
|
||||
|
||||
@@ -1 +1 @@
|
||||
v1.7.2
|
||||
v1.7.3
|
||||
|
||||
@@ -177,6 +177,7 @@ set(CPPSRC
|
||||
event_m0.cpp
|
||||
file_reader.cpp
|
||||
file.cpp
|
||||
freqman_db.cpp
|
||||
freqman.cpp
|
||||
io_file.cpp
|
||||
io_wave.cpp
|
||||
@@ -184,6 +185,7 @@ set(CPPSRC
|
||||
irq_lcd_frame.cpp
|
||||
irq_rtc.cpp
|
||||
log_file.cpp
|
||||
metadata_file.cpp
|
||||
portapack.cpp
|
||||
qrcodegen.cpp
|
||||
radio.cpp
|
||||
@@ -233,6 +235,7 @@ set(CPPSRC
|
||||
ui/ui_styles.cpp
|
||||
ui/ui_tabview.cpp
|
||||
ui/ui_textentry.cpp
|
||||
ui/ui_tone_key.cpp
|
||||
ui/ui_transmitter.cpp
|
||||
apps/ui_about_simple.cpp
|
||||
apps/ui_adsb_rx.cpp
|
||||
@@ -286,7 +289,7 @@ set(CPPSRC
|
||||
apps/ert_app.cpp
|
||||
apps/lge_app.cpp
|
||||
apps/pocsag_app.cpp
|
||||
apps/replay_app.cpp
|
||||
# apps/replay_app.cpp
|
||||
apps/ui_playlist.cpp
|
||||
apps/gps_sim_app.cpp
|
||||
apps/soundboard_app.cpp
|
||||
|
||||
@@ -94,8 +94,6 @@ constexpr std::string_view volume = "volume="sv;
|
||||
// TODO: Maybe just use a dictionary which would allow for custom settings.
|
||||
// TODO: Create a control value binding which will allow controls to
|
||||
// be declaratively bound to a setting and persistence will be magic.
|
||||
// TODO: radio settings should be pushed and popped to prevent cross-app
|
||||
// radio bugs caused by sharing a global model.
|
||||
|
||||
ResultCode load_settings(const std::string& app_name, AppSettings& settings) {
|
||||
if (!portapack::persistent_memory::load_app_settings())
|
||||
@@ -116,6 +114,8 @@ ResultCode load_settings(const std::string& app_name, AppSettings& settings) {
|
||||
|
||||
if (flags_enabled(settings.mode, Mode::RX)) {
|
||||
read_setting(*data, setting::rx_frequency, settings.rx_frequency);
|
||||
read_setting(*data, setting::lna, settings.lna);
|
||||
read_setting(*data, setting::vga, settings.vga);
|
||||
read_setting(*data, setting::rx_amp, settings.rx_amp);
|
||||
read_setting(*data, setting::modulation, settings.modulation);
|
||||
read_setting(*data, setting::am_config_index, settings.am_config_index);
|
||||
@@ -126,8 +126,6 @@ ResultCode load_settings(const std::string& app_name, AppSettings& settings) {
|
||||
|
||||
read_setting(*data, setting::baseband_bandwidth, settings.baseband_bandwidth);
|
||||
read_setting(*data, setting::sampling_rate, settings.sampling_rate);
|
||||
read_setting(*data, setting::lna, settings.lna);
|
||||
read_setting(*data, setting::vga, settings.vga);
|
||||
read_setting(*data, setting::step, settings.step);
|
||||
read_setting(*data, setting::volume, settings.volume);
|
||||
|
||||
@@ -155,6 +153,8 @@ ResultCode save_settings(const std::string& app_name, AppSettings& settings) {
|
||||
|
||||
if (flags_enabled(settings.mode, Mode::RX)) {
|
||||
write_setting(settings_file, setting::rx_frequency, settings.rx_frequency);
|
||||
write_setting(settings_file, setting::lna, settings.lna);
|
||||
write_setting(settings_file, setting::vga, settings.vga);
|
||||
write_setting(settings_file, setting::rx_amp, settings.rx_amp);
|
||||
write_setting(settings_file, setting::modulation, settings.modulation);
|
||||
write_setting(settings_file, setting::am_config_index, settings.am_config_index);
|
||||
@@ -165,8 +165,6 @@ ResultCode save_settings(const std::string& app_name, AppSettings& settings) {
|
||||
|
||||
write_setting(settings_file, setting::baseband_bandwidth, settings.baseband_bandwidth);
|
||||
write_setting(settings_file, setting::sampling_rate, settings.sampling_rate);
|
||||
write_setting(settings_file, setting::lna, settings.lna);
|
||||
write_setting(settings_file, setting::vga, settings.vga);
|
||||
write_setting(settings_file, setting::step, settings.step);
|
||||
write_setting(settings_file, setting::volume, settings.volume);
|
||||
|
||||
@@ -175,20 +173,21 @@ ResultCode save_settings(const std::string& app_name, AppSettings& settings) {
|
||||
|
||||
void copy_to_radio_model(const AppSettings& settings) {
|
||||
// NB: Don't actually adjust the radio here or it will hang.
|
||||
// Specifically 'modulation' which requires a running baseband.
|
||||
|
||||
if (flags_enabled(settings.mode, Mode::TX)) {
|
||||
if (!flags_enabled(settings.options, Options::UseGlobalTargetFrequency))
|
||||
transmitter_model.set_target_frequency(settings.tx_frequency);
|
||||
persistent_memory::set_target_frequency(settings.tx_frequency);
|
||||
|
||||
transmitter_model.configure_from_app_settings(settings);
|
||||
}
|
||||
|
||||
if (flags_enabled(settings.mode, Mode::RX)) {
|
||||
if (!flags_enabled(settings.options, Options::UseGlobalTargetFrequency))
|
||||
transmitter_model.set_target_frequency(settings.rx_frequency);
|
||||
persistent_memory::set_target_frequency(settings.rx_frequency);
|
||||
|
||||
receiver_model.configure_from_app_settings(settings);
|
||||
receiver_model.set_configuration_without_init(
|
||||
receiver_model.set_configuration_without_update(
|
||||
static_cast<ReceiverModel::Mode>(settings.modulation),
|
||||
settings.step,
|
||||
settings.am_config_index,
|
||||
@@ -205,14 +204,10 @@ void copy_from_radio_model(AppSettings& settings) {
|
||||
if (flags_enabled(settings.mode, Mode::TX)) {
|
||||
settings.tx_frequency = transmitter_model.target_frequency();
|
||||
settings.baseband_bandwidth = transmitter_model.baseband_bandwidth();
|
||||
settings.sampling_rate = transmitter_model.sampling_rate();
|
||||
settings.tx_amp = transmitter_model.rf_amp();
|
||||
settings.tx_gain = transmitter_model.tx_gain();
|
||||
settings.channel_bandwidth = transmitter_model.channel_bandwidth();
|
||||
|
||||
// TODO: Do these make sense for TX?
|
||||
settings.sampling_rate = transmitter_model.sampling_rate();
|
||||
settings.lna = transmitter_model.lna();
|
||||
settings.vga = transmitter_model.vga();
|
||||
}
|
||||
|
||||
if (flags_enabled(settings.mode, Mode::RX)) {
|
||||
|
||||
@@ -55,7 +55,8 @@ void ACARSLogger::log_raw_data(const acars::Packet& packet, const uint32_t frequ
|
||||
|
||||
namespace ui {
|
||||
|
||||
ACARSAppView::ACARSAppView(NavigationView& nav) {
|
||||
ACARSAppView::ACARSAppView(NavigationView& nav)
|
||||
: nav_{nav} {
|
||||
baseband::run_image(portapack::spi_flash::image_tag_acars);
|
||||
|
||||
add_children({&rssi,
|
||||
@@ -69,19 +70,6 @@ ACARSAppView::ACARSAppView(NavigationView& nav) {
|
||||
|
||||
receiver_model.enable();
|
||||
|
||||
field_frequency.set_value(receiver_model.target_frequency());
|
||||
field_frequency.set_step(receiver_model.frequency_step());
|
||||
field_frequency.on_change = [this](rf::Frequency f) {
|
||||
receiver_model.set_target_frequency(f);
|
||||
};
|
||||
field_frequency.on_edit = [this, &nav]() {
|
||||
// TODO: Provide separate modal method/scheme?
|
||||
auto new_view = nav.push<FrequencyKeypadView>(receiver_model.target_frequency());
|
||||
new_view->on_changed = [this](rf::Frequency f) {
|
||||
field_frequency.set_value(f);
|
||||
};
|
||||
};
|
||||
|
||||
check_log.set_value(logging);
|
||||
check_log.on_select = [this](Checkbox&, bool v) {
|
||||
logging = v;
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include "radio_state.hpp"
|
||||
#include "ui_widget.hpp"
|
||||
#include "ui_receiver.hpp"
|
||||
#include "ui_freq_field.hpp"
|
||||
#include "ui_rssi.hpp"
|
||||
#include "log_file.hpp"
|
||||
|
||||
@@ -57,6 +58,7 @@ class ACARSAppView : public View {
|
||||
std::string title() const override { return "ACARS (WIP)"; };
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
RxRadioState radio_state_{
|
||||
1750000 /* bandwidth */,
|
||||
2457600 /* sampling rate */
|
||||
@@ -74,15 +76,13 @@ class ACARSAppView : public View {
|
||||
VGAGainField field_vga{
|
||||
{18 * 8, 0 * 16}};
|
||||
RSSI rssi{
|
||||
{21 * 8, 0, 6 * 8, 4},
|
||||
};
|
||||
{21 * 8, 0, 6 * 8, 4}};
|
||||
Channel channel{
|
||||
{21 * 8, 5, 6 * 8, 4},
|
||||
};
|
||||
{21 * 8, 5, 6 * 8, 4}};
|
||||
|
||||
FrequencyField field_frequency{
|
||||
RxFrequencyField field_frequency{
|
||||
{0 * 8, 0 * 8},
|
||||
};
|
||||
nav_};
|
||||
Checkbox check_log{
|
||||
{22 * 8, 21},
|
||||
3,
|
||||
|
||||
@@ -22,21 +22,17 @@
|
||||
|
||||
#include "analog_audio_app.hpp"
|
||||
|
||||
#include "audio.hpp"
|
||||
#include "baseband_api.hpp"
|
||||
|
||||
#include "file.hpp"
|
||||
#include "freqman.hpp"
|
||||
#include "portapack.hpp"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
using namespace portapack;
|
||||
using namespace tonekey;
|
||||
|
||||
#include "audio.hpp"
|
||||
#include "file.hpp"
|
||||
|
||||
#include "string_format.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
#include "string_format.hpp"
|
||||
|
||||
#include "freqman.hpp"
|
||||
using namespace portapack;
|
||||
using namespace tonekey;
|
||||
|
||||
namespace ui {
|
||||
|
||||
@@ -45,8 +41,8 @@ namespace ui {
|
||||
static const Style& style_options_group = Styles::bg_blue;
|
||||
|
||||
AMOptionsView::AMOptionsView(
|
||||
const Rect parent_rect,
|
||||
const Style* const style)
|
||||
Rect parent_rect,
|
||||
const Style* style)
|
||||
: View{parent_rect} {
|
||||
set_style(style);
|
||||
|
||||
@@ -65,8 +61,8 @@ AMOptionsView::AMOptionsView(
|
||||
/* NBFMOptionsView *******************************************************/
|
||||
|
||||
NBFMOptionsView::NBFMOptionsView(
|
||||
const Rect parent_rect,
|
||||
const Style* const style)
|
||||
Rect parent_rect,
|
||||
const Style* style)
|
||||
: View{parent_rect} {
|
||||
set_style(style);
|
||||
|
||||
@@ -90,8 +86,8 @@ NBFMOptionsView::NBFMOptionsView(
|
||||
/* WFMOptionsView *******************************************************/
|
||||
|
||||
WFMOptionsView::WFMOptionsView(
|
||||
const Rect parent_rect,
|
||||
const Style* const style)
|
||||
Rect parent_rect,
|
||||
const Style* style)
|
||||
: View{parent_rect} {
|
||||
set_style(style);
|
||||
|
||||
@@ -111,8 +107,8 @@ WFMOptionsView::WFMOptionsView(
|
||||
|
||||
SPECOptionsView::SPECOptionsView(
|
||||
AnalogAudioView* view,
|
||||
const Rect parent_rect,
|
||||
const Style* const style)
|
||||
Rect parent_rect,
|
||||
const Style* style)
|
||||
: View{parent_rect} {
|
||||
set_style(style);
|
||||
|
||||
@@ -137,6 +133,9 @@ SPECOptionsView::SPECOptionsView(
|
||||
AnalogAudioView::AnalogAudioView(
|
||||
NavigationView& nav)
|
||||
: nav_(nav) {
|
||||
// A baseband image _must_ be running before add waterfall view.
|
||||
baseband::run_image(portapack::spi_flash::image_tag_wideband_spectrum);
|
||||
|
||||
add_children({&rssi,
|
||||
&channel,
|
||||
&audio,
|
||||
@@ -152,19 +151,6 @@ AnalogAudioView::AnalogAudioView(
|
||||
// Filename Datetime and Frequency
|
||||
record_view.set_filename_date_frequency(true);
|
||||
|
||||
field_frequency.set_value(receiver_model.target_frequency());
|
||||
field_frequency.on_change = [this](rf::Frequency f) {
|
||||
receiver_model.set_target_frequency(f);
|
||||
};
|
||||
field_frequency.set_step(receiver_model.frequency_step());
|
||||
field_frequency.on_edit = [this, &nav]() {
|
||||
// TODO: Provide separate modal method/scheme?
|
||||
auto new_view = nav.push<FrequencyKeypadView>(receiver_model.target_frequency());
|
||||
new_view->on_changed = [this](rf::Frequency f) {
|
||||
field_frequency.set_value(f);
|
||||
};
|
||||
};
|
||||
|
||||
field_frequency.on_show_options = [this]() {
|
||||
this->on_show_options_frequency();
|
||||
};
|
||||
@@ -177,9 +163,12 @@ AnalogAudioView::AnalogAudioView(
|
||||
this->on_show_options_rf_gain();
|
||||
};
|
||||
|
||||
const auto modulation = receiver_model.modulation();
|
||||
options_modulation.set_by_value(toUType(modulation));
|
||||
auto modulation = receiver_model.modulation();
|
||||
// This app doesn't handle "Capture" mode.
|
||||
if (modulation > ReceiverModel::Mode::SpectrumAnalysis)
|
||||
modulation = ReceiverModel::Mode::SpectrumAnalysis;
|
||||
|
||||
options_modulation.set_by_value(toUType(modulation));
|
||||
options_modulation.on_change = [this](size_t, OptionsField::value_t v) {
|
||||
this->on_modulation_changed(static_cast<ReceiverModel::Mode>(v));
|
||||
};
|
||||
@@ -197,8 +186,18 @@ AnalogAudioView::AnalogAudioView(
|
||||
|
||||
audio::output::start();
|
||||
|
||||
update_modulation(static_cast<ReceiverModel::Mode>(modulation));
|
||||
on_modulation_changed(static_cast<ReceiverModel::Mode>(modulation));
|
||||
// This call starts the correct baseband image to run
|
||||
// and sets the radio up as necessary for the given modulation.
|
||||
on_modulation_changed(modulation);
|
||||
}
|
||||
|
||||
AnalogAudioView::AnalogAudioView(
|
||||
NavigationView& nav,
|
||||
ReceiverModel::settings_t override)
|
||||
: AnalogAudioView(nav) {
|
||||
// TODO: Which other settings make sense to override?
|
||||
on_frequency_step_changed(override.frequency_step);
|
||||
options_modulation.set_by_value(toUType(override.mode));
|
||||
}
|
||||
|
||||
size_t AnalogAudioView::get_spec_bw_index() {
|
||||
@@ -225,40 +224,15 @@ void AnalogAudioView::set_spec_trigger(uint16_t trigger) {
|
||||
}
|
||||
|
||||
AnalogAudioView::~AnalogAudioView() {
|
||||
// // save app settings
|
||||
// app_settings.rx_frequency = field_frequency.value();
|
||||
// app_settings.lna = receiver_model.lna();
|
||||
// app_settings.vga = receiver_model.vga();
|
||||
// app_settings.rx_amp = receiver_model.rf_amp();
|
||||
// app_settings.step = receiver_model.frequency_step();
|
||||
// app_settings.modulation = (uint8_t)receiver_model.modulation();
|
||||
// app_settings.am_config_index = receiver_model.am_configuration();
|
||||
// app_settings.nbfm_config_index = receiver_model.nbfm_configuration();
|
||||
// app_settings.wfm_config_index = receiver_model.wfm_configuration();
|
||||
// app_settings.squelch = receiver_model.squelch_level();
|
||||
// settings.save("rx_audio", &app_settings);
|
||||
|
||||
// TODO: Manipulating audio codec here, and in ui_receiver.cpp. Good to do
|
||||
// both?
|
||||
audio::output::stop();
|
||||
|
||||
receiver_model.set_sampling_rate(3072000); // Just a hack to avoid hanging other apps if the last modulation was SPEC
|
||||
receiver_model.disable();
|
||||
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
void AnalogAudioView::on_hide() {
|
||||
// TODO: Terrible kludge because widget system doesn't notify Waterfall that
|
||||
// it's being shown or hidden.
|
||||
waterfall.on_hide();
|
||||
View::on_hide();
|
||||
}
|
||||
|
||||
void AnalogAudioView::set_parent_rect(const Rect new_parent_rect) {
|
||||
void AnalogAudioView::set_parent_rect(Rect new_parent_rect) {
|
||||
View::set_parent_rect(new_parent_rect);
|
||||
|
||||
const ui::Rect waterfall_rect{0, header_height, new_parent_rect.width(), new_parent_rect.height() - header_height};
|
||||
ui::Rect waterfall_rect{0, header_height, new_parent_rect.width(), new_parent_rect.height() - header_height};
|
||||
waterfall.set_parent_rect(waterfall_rect);
|
||||
}
|
||||
|
||||
@@ -270,13 +244,15 @@ void AnalogAudioView::on_baseband_bandwidth_changed(uint32_t bandwidth_hz) {
|
||||
receiver_model.set_baseband_bandwidth(bandwidth_hz);
|
||||
}
|
||||
|
||||
void AnalogAudioView::on_modulation_changed(const ReceiverModel::Mode modulation) {
|
||||
// TODO: Terrible kludge because widget system doesn't notify Waterfall that
|
||||
// it's being shown or hidden.
|
||||
waterfall.on_hide();
|
||||
void AnalogAudioView::on_modulation_changed(ReceiverModel::Mode modulation) {
|
||||
// This app doesn't know what to do with "Capture" mode.
|
||||
if (modulation > ReceiverModel::Mode::SpectrumAnalysis)
|
||||
modulation = ReceiverModel::Mode::SpectrumAnalysis;
|
||||
|
||||
baseband::spectrum_streaming_stop();
|
||||
update_modulation(modulation);
|
||||
on_show_options_modulation();
|
||||
waterfall.on_show();
|
||||
baseband::spectrum_streaming_start();
|
||||
}
|
||||
|
||||
void AnalogAudioView::remove_options_widget() {
|
||||
@@ -328,7 +304,7 @@ void AnalogAudioView::on_show_options_rf_gain() {
|
||||
void AnalogAudioView::on_show_options_modulation() {
|
||||
std::unique_ptr<Widget> widget;
|
||||
|
||||
const auto modulation = static_cast<ReceiverModel::Mode>(receiver_model.modulation());
|
||||
const auto modulation = receiver_model.modulation();
|
||||
switch (modulation) {
|
||||
case ReceiverModel::Mode::AMAudio:
|
||||
widget = std::make_unique<AMOptionsView>(options_view_rect, &style_options_group);
|
||||
@@ -355,6 +331,7 @@ void AnalogAudioView::on_show_options_modulation() {
|
||||
break;
|
||||
|
||||
default:
|
||||
chDbgPanic("Unhandled Mode");
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -371,7 +348,7 @@ void AnalogAudioView::on_reference_ppm_correction_changed(int32_t v) {
|
||||
persistent_memory::set_correction_ppb(v * 1000);
|
||||
}
|
||||
|
||||
void AnalogAudioView::update_modulation(const ReceiverModel::Mode modulation) {
|
||||
void AnalogAudioView::update_modulation(ReceiverModel::Mode modulation) {
|
||||
audio::output::mute();
|
||||
record_view.stop();
|
||||
|
||||
@@ -392,7 +369,8 @@ void AnalogAudioView::update_modulation(const ReceiverModel::Mode modulation) {
|
||||
image_tag = portapack::spi_flash::image_tag_wideband_spectrum;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
chDbgPanic("Unhandled Mode");
|
||||
break;
|
||||
}
|
||||
|
||||
baseband::run_image(image_tag);
|
||||
@@ -431,25 +409,8 @@ void AnalogAudioView::update_modulation(const ReceiverModel::Mode modulation) {
|
||||
}
|
||||
}
|
||||
|
||||
void AnalogAudioView::handle_coded_squelch(const uint32_t value) {
|
||||
float diff, min_diff = value;
|
||||
size_t min_idx{0};
|
||||
size_t c;
|
||||
|
||||
// Find nearest match
|
||||
for (c = 0; c < tone_keys.size(); c++) {
|
||||
diff = abs(((float)value / 100.0) - tone_keys[c].second);
|
||||
if (diff < min_diff) {
|
||||
min_idx = c;
|
||||
min_diff = diff;
|
||||
}
|
||||
}
|
||||
|
||||
// Arbitrary confidence threshold
|
||||
if (min_diff < 40)
|
||||
text_ctcss.set("CTCSS " + tone_keys[min_idx].first);
|
||||
else
|
||||
text_ctcss.set("???");
|
||||
void AnalogAudioView::handle_coded_squelch(uint32_t value) {
|
||||
text_ctcss.set(tone_key_string_by_value(value, text_ctcss.parent_rect().width() / 8));
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include "receiver_model.hpp"
|
||||
|
||||
#include "ui_receiver.hpp"
|
||||
#include "ui_freq_field.hpp"
|
||||
#include "ui_spectrum.hpp"
|
||||
#include "ui_record_view.hpp"
|
||||
#include "ui_styles.hpp"
|
||||
@@ -37,7 +38,7 @@ namespace ui {
|
||||
|
||||
class AMOptionsView : public View {
|
||||
public:
|
||||
AMOptionsView(const Rect parent_rect, const Style* const style);
|
||||
AMOptionsView(Rect parent_rect, const Style* style);
|
||||
|
||||
private:
|
||||
Text label_config{
|
||||
@@ -55,7 +56,7 @@ class AMOptionsView : public View {
|
||||
|
||||
class NBFMOptionsView : public View {
|
||||
public:
|
||||
NBFMOptionsView(const Rect parent_rect, const Style* const style);
|
||||
NBFMOptionsView(Rect parent_rect, const Style* style);
|
||||
|
||||
private:
|
||||
Text label_config{
|
||||
@@ -70,10 +71,10 @@ class NBFMOptionsView : public View {
|
||||
}};
|
||||
|
||||
Text text_squelch{
|
||||
{9 * 8, 0 * 16, 8 * 8, 1 * 16},
|
||||
{7 * 8, 0 * 16, 8 * 8, 1 * 16},
|
||||
"SQ /99"};
|
||||
NumberField field_squelch{
|
||||
{12 * 8, 0 * 16},
|
||||
{10 * 8, 0 * 16},
|
||||
2,
|
||||
{0, 99},
|
||||
1,
|
||||
@@ -83,7 +84,7 @@ class NBFMOptionsView : public View {
|
||||
|
||||
class WFMOptionsView : public View {
|
||||
public:
|
||||
WFMOptionsView(const Rect parent_rect, const Style* const style);
|
||||
WFMOptionsView(Rect parent_rect, const Style* style);
|
||||
|
||||
private:
|
||||
Text label_config{
|
||||
@@ -102,7 +103,7 @@ class AnalogAudioView;
|
||||
|
||||
class SPECOptionsView : public View {
|
||||
public:
|
||||
SPECOptionsView(AnalogAudioView* view, const Rect parent_rect, const Style* const style);
|
||||
SPECOptionsView(AnalogAudioView* view, Rect parent_rect, const Style* style);
|
||||
|
||||
private:
|
||||
Text label_config{
|
||||
@@ -137,12 +138,10 @@ class SPECOptionsView : public View {
|
||||
class AnalogAudioView : public View {
|
||||
public:
|
||||
AnalogAudioView(NavigationView& nav);
|
||||
AnalogAudioView(NavigationView& nav, ReceiverModel::settings_t override);
|
||||
~AnalogAudioView();
|
||||
|
||||
void on_hide() override;
|
||||
|
||||
void set_parent_rect(const Rect new_parent_rect) override;
|
||||
|
||||
void set_parent_rect(Rect new_parent_rect) override;
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override { return "Audio RX"; };
|
||||
@@ -156,6 +155,7 @@ class AnalogAudioView : public View {
|
||||
private:
|
||||
static constexpr ui::Dim header_height = 3 * 16;
|
||||
|
||||
NavigationView& nav_;
|
||||
RxRadioState radio_state_{};
|
||||
app_settings::SettingsManager settings_{
|
||||
"rx_audio", app_settings::Mode::RX,
|
||||
@@ -168,24 +168,18 @@ class AnalogAudioView : public View {
|
||||
uint32_t spec_bw = 20000000;
|
||||
uint16_t spec_trigger = 63;
|
||||
|
||||
NavigationView& nav_;
|
||||
// bool exit_on_squelch { false };
|
||||
|
||||
RSSI rssi{
|
||||
{21 * 8, 0, 6 * 8, 4},
|
||||
};
|
||||
{21 * 8, 0, 6 * 8, 4}};
|
||||
|
||||
Channel channel{
|
||||
{21 * 8, 5, 6 * 8, 4},
|
||||
};
|
||||
{21 * 8, 5, 6 * 8, 4}};
|
||||
|
||||
Audio audio{
|
||||
{21 * 8, 10, 6 * 8, 4},
|
||||
};
|
||||
{21 * 8, 10, 6 * 8, 4}};
|
||||
|
||||
FrequencyField field_frequency{
|
||||
RxFrequencyField field_frequency{
|
||||
{5 * 8, 0 * 16},
|
||||
};
|
||||
nav_};
|
||||
|
||||
LNAGainField field_lna{
|
||||
{15 * 8, 0 * 16}};
|
||||
@@ -207,7 +201,7 @@ class AnalogAudioView : public View {
|
||||
{28 * 8, 0 * 16}};
|
||||
|
||||
Text text_ctcss{
|
||||
{19 * 8, 1 * 16, 11 * 8, 1 * 16},
|
||||
{16 * 8, 1 * 16, 14 * 8, 1 * 16},
|
||||
""};
|
||||
|
||||
std::unique_ptr<Widget> options_widget{};
|
||||
@@ -220,36 +214,26 @@ class AnalogAudioView : public View {
|
||||
4096,
|
||||
4};
|
||||
|
||||
spectrum::WaterfallWidget waterfall{true};
|
||||
spectrum::WaterfallView waterfall{true};
|
||||
|
||||
void on_baseband_bandwidth_changed(uint32_t bandwidth_hz);
|
||||
void on_modulation_changed(const ReceiverModel::Mode modulation);
|
||||
void on_modulation_changed(ReceiverModel::Mode modulation);
|
||||
void on_show_options_frequency();
|
||||
void on_show_options_rf_gain();
|
||||
void on_show_options_modulation();
|
||||
void on_frequency_step_changed(rf::Frequency f);
|
||||
void on_reference_ppm_correction_changed(int32_t v);
|
||||
void on_edit_frequency();
|
||||
|
||||
void remove_options_widget();
|
||||
void set_options_widget(std::unique_ptr<Widget> new_widget);
|
||||
|
||||
void update_modulation(const ReceiverModel::Mode modulation);
|
||||
void update_modulation(ReceiverModel::Mode modulation);
|
||||
|
||||
// void squelched();
|
||||
void handle_coded_squelch(const uint32_t value);
|
||||
|
||||
/*MessageHandlerRegistration message_handler_squelch_signal {
|
||||
Message::ID::RequestSignal,
|
||||
[this](const Message* const p) {
|
||||
(void)p;
|
||||
this->squelched();
|
||||
}
|
||||
};*/
|
||||
void handle_coded_squelch(uint32_t value);
|
||||
|
||||
MessageHandlerRegistration message_handler_coded_squelch{
|
||||
Message::ID::CodedSquelch,
|
||||
[this](const Message* const p) {
|
||||
[this](const Message* p) {
|
||||
const auto message = *reinterpret_cast<const CodedSquelchMessage*>(p);
|
||||
this->handle_coded_squelch(message.value);
|
||||
}};
|
||||
|
||||
@@ -56,22 +56,6 @@ AnalogTvView::AnalogTvView(
|
||||
&field_volume,
|
||||
&tv});
|
||||
|
||||
// Set on_change before initialising the field
|
||||
field_frequency.on_change = [this](rf::Frequency f) {
|
||||
this->on_target_frequency_changed(f);
|
||||
};
|
||||
|
||||
field_frequency.set_value(receiver_model.target_frequency());
|
||||
field_frequency.set_step(receiver_model.frequency_step());
|
||||
field_frequency.on_edit = [this, &nav]() {
|
||||
// TODO: Provide separate modal method/scheme?
|
||||
auto new_view = nav.push<FrequencyKeypadView>(receiver_model.target_frequency());
|
||||
new_view->on_changed = [this](rf::Frequency f) {
|
||||
this->on_target_frequency_changed(f);
|
||||
this->field_frequency.set_value(f);
|
||||
};
|
||||
};
|
||||
|
||||
field_frequency.on_show_options = [this]() {
|
||||
this->on_show_options_frequency();
|
||||
};
|
||||
@@ -125,10 +109,6 @@ void AnalogTvView::focus() {
|
||||
field_frequency.focus();
|
||||
}
|
||||
|
||||
void AnalogTvView::on_target_frequency_changed(rf::Frequency f) {
|
||||
receiver_model.set_target_frequency(f);
|
||||
}
|
||||
|
||||
void AnalogTvView::on_baseband_bandwidth_changed(uint32_t bandwidth_hz) {
|
||||
receiver_model.set_baseband_bandwidth(bandwidth_hz);
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include "receiver_model.hpp"
|
||||
|
||||
#include "ui_receiver.hpp"
|
||||
#include "ui_freq_field.hpp"
|
||||
#include "ui_tv.hpp"
|
||||
#include "ui_record_view.hpp"
|
||||
#include "ui_styles.hpp"
|
||||
@@ -53,6 +54,7 @@ class AnalogTvView : public View {
|
||||
private:
|
||||
static constexpr ui::Dim header_height = 3 * 16;
|
||||
|
||||
NavigationView& nav_;
|
||||
RxRadioState radio_state_{};
|
||||
app_settings::SettingsManager settings_{
|
||||
"rx_tv", app_settings::Mode::RX};
|
||||
@@ -60,23 +62,18 @@ class AnalogTvView : public View {
|
||||
const Rect options_view_rect{0 * 8, 1 * 16, 30 * 8, 1 * 16};
|
||||
const Rect nbfm_view_rect{0 * 8, 1 * 16, 18 * 8, 1 * 16};
|
||||
|
||||
NavigationView& nav_;
|
||||
|
||||
RSSI rssi{
|
||||
{21 * 8, 0, 6 * 8, 4},
|
||||
};
|
||||
{21 * 8, 0, 6 * 8, 4}};
|
||||
|
||||
Channel channel{
|
||||
{21 * 8, 5, 6 * 8, 4},
|
||||
};
|
||||
{21 * 8, 5, 6 * 8, 4}};
|
||||
|
||||
Audio audio{
|
||||
{21 * 8, 10, 6 * 8, 4},
|
||||
};
|
||||
{21 * 8, 10, 6 * 8, 4}};
|
||||
|
||||
FrequencyField field_frequency{
|
||||
RxFrequencyField field_frequency{
|
||||
{5 * 8, 0 * 16},
|
||||
};
|
||||
nav_};
|
||||
|
||||
LNAGainField field_lna{
|
||||
{15 * 8, 0 * 16}};
|
||||
@@ -100,7 +97,6 @@ class AnalogTvView : public View {
|
||||
|
||||
tv::TVWidget tv{};
|
||||
|
||||
void on_target_frequency_changed(rf::Frequency f);
|
||||
void on_baseband_bandwidth_changed(uint32_t bandwidth_hz);
|
||||
void on_modulation_changed(const ReceiverModel::Mode modulation);
|
||||
void on_show_options_frequency();
|
||||
@@ -108,7 +104,6 @@ class AnalogTvView : public View {
|
||||
void on_show_options_modulation();
|
||||
void on_frequency_step_changed(rf::Frequency f);
|
||||
void on_reference_ppm_correction_changed(int32_t v);
|
||||
void on_edit_frequency();
|
||||
|
||||
void remove_options_widget();
|
||||
void set_options_widget(std::unique_ptr<Widget> new_widget);
|
||||
|
||||
@@ -21,15 +21,16 @@
|
||||
*/
|
||||
|
||||
#include "capture_app.hpp"
|
||||
|
||||
#include "baseband_api.hpp"
|
||||
|
||||
#include "portapack.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
|
||||
namespace ui {
|
||||
|
||||
CaptureAppView::CaptureAppView(NavigationView& nav) {
|
||||
CaptureAppView::CaptureAppView(NavigationView& nav)
|
||||
: nav_{nav} {
|
||||
freqman_set_bandwidth_option(SPEC_MODULATION, option_bandwidth);
|
||||
baseband::run_image(portapack::spi_flash::image_tag_capture);
|
||||
|
||||
add_children({
|
||||
@@ -46,20 +47,6 @@ CaptureAppView::CaptureAppView(NavigationView& nav) {
|
||||
&waterfall,
|
||||
});
|
||||
|
||||
field_frequency.set_value(receiver_model.target_frequency());
|
||||
field_frequency.set_step(receiver_model.frequency_step());
|
||||
field_frequency.on_change = [this](rf::Frequency f) {
|
||||
this->on_target_frequency_changed(f);
|
||||
};
|
||||
field_frequency.on_edit = [this, &nav]() {
|
||||
// TODO: Provide separate modal method/scheme?
|
||||
auto new_view = nav.push<FrequencyKeypadView>(receiver_model.target_frequency());
|
||||
new_view->on_changed = [this](rf::Frequency f) {
|
||||
this->on_target_frequency_changed(f);
|
||||
this->field_frequency.set_value(f);
|
||||
};
|
||||
};
|
||||
|
||||
field_frequency_step.set_by_value(receiver_model.frequency_step());
|
||||
field_frequency_step.on_change = [this](size_t, OptionsField::value_t v) {
|
||||
receiver_model.set_frequency_step(v);
|
||||
@@ -68,57 +55,22 @@ CaptureAppView::CaptureAppView(NavigationView& nav) {
|
||||
|
||||
option_bandwidth.on_change = [this](size_t, uint32_t base_rate) {
|
||||
sampling_rate = 8 * base_rate; // Decimation by 8 done on baseband side
|
||||
/* base_rate is used for FFT calculation and display LCD, and also in recording writing SD Card rate. */
|
||||
/* ex. sampling_rate values, 4Mhz, when recording 500 kHz (BW) and fs 8 Mhz , when selected 1 Mhz BW ...*/
|
||||
/* ex. recording 500kHz BW to .C16 file, base_rate clock 500kHz x2(I,Q) x 2 bytes (int signed) =2MB/sec rate SD Card */
|
||||
|
||||
/* base_rate is used for FFT calculation and display LCD, and also in recording writing SD Card rate. */
|
||||
/* ex. sampling_rate values, 4Mhz, when recording 500 kHz (BW) and fs 8 Mhz, when selected 1 Mhz BW ... */
|
||||
/* ex. recording 500kHz BW to .C16 file, base_rate clock 500kHz x2(I,Q) x 2 bytes (int signed) =2MB/sec rate SD Card */
|
||||
waterfall.on_hide();
|
||||
|
||||
/* Set up proper anti aliasing BPF bandwith in MAX2837 before ADC sampling according to the new added BW Options. */
|
||||
auto anti_alias_baseband_bandwidth_filter = filter_bandwidth_for_sampling_rate(sampling_rate);
|
||||
|
||||
record_view.set_sampling_rate(sampling_rate);
|
||||
receiver_model.set_sampling_rate(sampling_rate);
|
||||
/* Set up proper anti aliasing BPF bandwith in MAX2837 before ADC sampling according to the new added BW Options . */
|
||||
|
||||
switch (sampling_rate) { // we use the var fs (sampling_rate) , to set up BPF aprox < fs_max/2 by Nyquist theorem.
|
||||
|
||||
case 0 ... 2000000: // BW Captured range (0 <= 250kHz max ) fs = 8 x 250 kHz
|
||||
anti_alias_baseband_bandwidth_filter = 1750000; // Minimum BPF MAX2837 for all those lower BW options.
|
||||
break;
|
||||
|
||||
case 4000000 ... 6000000: // BW capture range (500k ... 750kHz max ) fs_max = 8 x 750kHz = 6Mhz
|
||||
// BW 500k ... 750kHz , ex. 500kHz (fs = 8*BW = 4Mhz) , BW 600kHz (fs = 4,8Mhz) , BW 750 kHz (fs = 6Mhz)
|
||||
anti_alias_baseband_bandwidth_filter = 2500000; // in some IC MAX2837 appear 2250000 , but both works similar.
|
||||
break;
|
||||
|
||||
case 8800000: // BW capture 1,1Mhz fs = 8 x 1,1Mhz = 8,8Mhz . (1Mhz showed slightly higher noise background).
|
||||
anti_alias_baseband_bandwidth_filter = 3500000;
|
||||
break;
|
||||
|
||||
case 14000000: // BW capture 1,75Mhz , fs = 8 x 1,75Mhz = 14Mhz
|
||||
// good BPF ,good matching, but LCD making flicker , refresh rate should be < 20 Hz , but reasonable picture
|
||||
anti_alias_baseband_bandwidth_filter = 5000000;
|
||||
break;
|
||||
|
||||
case 16000000: // BW capture 2Mhz , fs = 8 x 2Mhz = 16Mhz
|
||||
// good BPF ,good matching, but LCD making flicker , refresh rate should be < 20 Hz , but reasonable picture
|
||||
anti_alias_baseband_bandwidth_filter = 6000000;
|
||||
break;
|
||||
|
||||
case 20000000: // BW capture 2,5Mhz , fs= 8 x 2,5 Mhz = 20Mhz
|
||||
// good BPF ,good matching, but LCD making flicker , refresh rate should be < 20 Hz , but reasonable picture
|
||||
anti_alias_baseband_bandwidth_filter = 7000000;
|
||||
break;
|
||||
|
||||
default: // BW capture 2,75Mhz, fs = 8 x 2,75Mhz= 22Mhz max ADC sampling) and others.
|
||||
// We tested also 9Mhz FPB stightly too much noise floor , better 8Mhz
|
||||
anti_alias_baseband_bandwidth_filter = 8000000;
|
||||
}
|
||||
receiver_model.set_baseband_bandwidth(anti_alias_baseband_bandwidth_filter);
|
||||
|
||||
waterfall.on_show();
|
||||
};
|
||||
|
||||
option_bandwidth.set_selected_index(7); // 500k, Preselected starting default option 500kHz
|
||||
|
||||
receiver_model.set_modulation(ReceiverModel::Mode::Capture);
|
||||
option_bandwidth.set_selected_index(7); // Preselected default option 500kHz.
|
||||
receiver_model.enable();
|
||||
|
||||
record_view.on_error = [&nav](std::string message) {
|
||||
@@ -132,8 +84,6 @@ CaptureAppView::~CaptureAppView() {
|
||||
}
|
||||
|
||||
void CaptureAppView::on_hide() {
|
||||
// TODO: Terrible kludge because widget system doesn't notify Waterfall that
|
||||
// it's being shown or hidden.
|
||||
waterfall.on_hide();
|
||||
View::on_hide();
|
||||
}
|
||||
@@ -141,7 +91,7 @@ void CaptureAppView::on_hide() {
|
||||
void CaptureAppView::set_parent_rect(const Rect new_parent_rect) {
|
||||
View::set_parent_rect(new_parent_rect);
|
||||
|
||||
const ui::Rect waterfall_rect{0, header_height, new_parent_rect.width(), new_parent_rect.height() - header_height};
|
||||
ui::Rect waterfall_rect{0, header_height, new_parent_rect.width(), new_parent_rect.height() - header_height};
|
||||
waterfall.set_parent_rect(waterfall_rect);
|
||||
}
|
||||
|
||||
@@ -149,8 +99,4 @@ void CaptureAppView::focus() {
|
||||
record_view.focus();
|
||||
}
|
||||
|
||||
void CaptureAppView::on_target_frequency_changed(rf::Frequency f) {
|
||||
receiver_model.set_target_frequency(f);
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -26,26 +26,12 @@
|
||||
#include "ui_widget.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "ui_receiver.hpp"
|
||||
#include "ui_freq_field.hpp"
|
||||
#include "ui_record_view.hpp"
|
||||
#include "ui_spectrum.hpp"
|
||||
#include "app_settings.hpp"
|
||||
#include "radio_state.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)
|
||||
#include "freqman.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
@@ -55,42 +41,38 @@ class CaptureAppView : public View {
|
||||
~CaptureAppView();
|
||||
|
||||
void on_hide() override;
|
||||
|
||||
void set_parent_rect(const Rect new_parent_rect) override;
|
||||
|
||||
void focus() override;
|
||||
void set_parent_rect(const Rect new_parent_rect) override;
|
||||
|
||||
std::string title() const override { return "Capture"; };
|
||||
|
||||
private:
|
||||
static constexpr ui::Dim header_height = 3 * 16;
|
||||
|
||||
RxRadioState radio_state_{};
|
||||
NavigationView& nav_;
|
||||
RxRadioState radio_state_{ReceiverModel::Mode::Capture};
|
||||
app_settings::SettingsManager settings_{
|
||||
"rx_capture", app_settings::Mode::RX,
|
||||
app_settings::Options::UseGlobalTargetFrequency};
|
||||
|
||||
uint32_t sampling_rate = 0;
|
||||
uint32_t anti_alias_baseband_bandwidth_filter = 2500000; // we rename the previous var , and change type static constexpr to normal var.
|
||||
|
||||
void on_target_frequency_changed(rf::Frequency f);
|
||||
|
||||
Labels labels{
|
||||
{{0 * 8, 1 * 16}, "Rate:", Color::light_grey()},
|
||||
};
|
||||
|
||||
RSSI rssi{
|
||||
{24 * 8, 0, 6 * 8, 4},
|
||||
};
|
||||
{24 * 8, 0, 6 * 8, 4}};
|
||||
|
||||
Channel channel{
|
||||
{24 * 8, 5, 6 * 8, 4},
|
||||
};
|
||||
{24 * 8, 5, 6 * 8, 4}};
|
||||
|
||||
FrequencyField field_frequency{
|
||||
RxFrequencyField field_frequency{
|
||||
{0 * 8, 0 * 16},
|
||||
};
|
||||
nav_};
|
||||
|
||||
FrequencyStepView field_frequency_step{
|
||||
{10 * 8, 0 * 16},
|
||||
};
|
||||
{10 * 8, 0 * 16}};
|
||||
|
||||
RFAmpField field_rf_amp{
|
||||
{16 * 8, 0 * 16}};
|
||||
@@ -104,7 +86,7 @@ class CaptureAppView : public View {
|
||||
OptionsField option_bandwidth{
|
||||
{5 * 8, 1 * 16},
|
||||
5,
|
||||
{BW_OPTIONS}};
|
||||
{}};
|
||||
|
||||
RecordView record_view{
|
||||
{0 * 8, 2 * 16, 30 * 8, 1 * 16},
|
||||
@@ -114,7 +96,7 @@ class CaptureAppView : public View {
|
||||
16384,
|
||||
3};
|
||||
|
||||
spectrum::WaterfallWidget waterfall{};
|
||||
spectrum::WaterfallView waterfall{};
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -130,8 +130,7 @@ class ERTAppView : public View {
|
||||
|
||||
RxRadioState radio_state_{
|
||||
2500000 /* bandwidth */,
|
||||
4194304 /* sampling rate */
|
||||
};
|
||||
4194304 /* sampling rate */};
|
||||
app_settings::SettingsManager settings_{
|
||||
"rx_ert", app_settings::Mode::RX};
|
||||
|
||||
|
||||
@@ -26,13 +26,15 @@
|
||||
|
||||
#include "ui_fileman.hpp"
|
||||
#include "io_file.hpp"
|
||||
#include "metadata_file.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
#include "baseband_api.hpp"
|
||||
#include "portapack.hpp"
|
||||
#include "cpld_update.hpp"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace ui {
|
||||
|
||||
@@ -40,51 +42,38 @@ void GpsSimAppView::set_ready() {
|
||||
ready_signal = true;
|
||||
}
|
||||
|
||||
void GpsSimAppView::on_file_changed(std::filesystem::path new_file_path) {
|
||||
File data_file, info_file;
|
||||
char file_data[257];
|
||||
void GpsSimAppView::on_file_changed(const fs::path& new_file_path) {
|
||||
file_path = fs::path(u"/") + new_file_path;
|
||||
File::Size file_size{};
|
||||
|
||||
// Get file size
|
||||
auto data_open_error = data_file.open("/" + new_file_path.string());
|
||||
if (data_open_error.is_valid()) {
|
||||
file_error();
|
||||
return;
|
||||
}
|
||||
|
||||
file_path = new_file_path;
|
||||
|
||||
// Get original record frequency if available
|
||||
std::filesystem::path info_file_path = file_path;
|
||||
info_file_path.replace_extension(u".TXT");
|
||||
|
||||
sample_rate = 500000;
|
||||
|
||||
auto info_open_error = info_file.open("/" + info_file_path.string());
|
||||
if (!info_open_error.is_valid()) {
|
||||
memset(file_data, 0, 257);
|
||||
auto read_size = info_file.read(file_data, 256);
|
||||
if (!read_size.is_error()) {
|
||||
auto pos1 = strstr(file_data, "center_frequency=");
|
||||
if (pos1) {
|
||||
pos1 += 17;
|
||||
field_frequency.set_value(strtoll(pos1, nullptr, 10));
|
||||
}
|
||||
|
||||
auto pos2 = strstr(file_data, "sample_rate=");
|
||||
if (pos2) {
|
||||
pos2 += 12;
|
||||
sample_rate = strtoll(pos2, nullptr, 10);
|
||||
}
|
||||
{ // Get the size of the data file.
|
||||
File data_file;
|
||||
auto error = data_file.open(file_path);
|
||||
if (error) {
|
||||
file_error();
|
||||
return;
|
||||
}
|
||||
|
||||
file_size = data_file.size();
|
||||
}
|
||||
|
||||
// Get original record frequency if available.
|
||||
auto metadata_path = get_metadata_path(file_path);
|
||||
auto metadata = read_metadata_file(metadata_path);
|
||||
|
||||
if (metadata) {
|
||||
field_frequency.set_value(metadata->center_frequency);
|
||||
sample_rate = metadata->sample_rate;
|
||||
} else {
|
||||
sample_rate = 500000;
|
||||
}
|
||||
|
||||
// UI Fixup.
|
||||
text_sample_rate.set(unit_auto_scale(sample_rate, 3, 1) + "Hz");
|
||||
|
||||
auto file_size = data_file.size();
|
||||
auto duration = (file_size * 1000) / (1 * 2 * sample_rate);
|
||||
|
||||
progressbar.set_max(file_size / 1024);
|
||||
text_filename.set(file_path.filename().string().substr(0, 12));
|
||||
text_filename.set(truncate(file_path.filename().string(), 12));
|
||||
|
||||
auto duration = ms_duration(file_size, sample_rate, 2);
|
||||
text_duration.set(to_string_time_ms(duration));
|
||||
|
||||
button_play.focus();
|
||||
@@ -187,18 +176,7 @@ GpsSimAppView::GpsSimAppView(
|
||||
&waterfall,
|
||||
});
|
||||
|
||||
field_frequency.set_value(transmitter_model.target_frequency());
|
||||
field_frequency.set_step(5000);
|
||||
field_frequency.on_change = [this](rf::Frequency f) {
|
||||
transmitter_model.set_target_frequency(f);
|
||||
};
|
||||
field_frequency.on_edit = [this, &nav]() {
|
||||
// TODO: Provide separate modal method/scheme?
|
||||
auto new_view = nav.push<FrequencyKeypadView>(transmitter_model.target_frequency());
|
||||
new_view->on_changed = [this](rf::Frequency f) {
|
||||
this->field_frequency.set_value(f);
|
||||
};
|
||||
};
|
||||
|
||||
button_play.on_select = [this](ImageButton&) {
|
||||
this->toggle();
|
||||
@@ -214,8 +192,7 @@ GpsSimAppView::GpsSimAppView(
|
||||
|
||||
GpsSimAppView::~GpsSimAppView() {
|
||||
transmitter_model.disable();
|
||||
hackrf::cpld::load_sram_no_verify(); // to leave all RX ok, without ghost signal problem at the exit .
|
||||
baseband::shutdown(); // better this function at the end, not load_sram() that sometimes produces hang up.
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
void GpsSimAppView::on_hide() {
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#include "ui_widget.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "ui_receiver.hpp"
|
||||
#include "ui_freq_field.hpp"
|
||||
#include "replay_thread.hpp"
|
||||
#include "ui_spectrum.hpp"
|
||||
#include "ui_transmitter.hpp"
|
||||
@@ -70,7 +71,7 @@ class GpsSimAppView : public View {
|
||||
const size_t read_size{16384};
|
||||
const size_t buffer_count{3};
|
||||
|
||||
void on_file_changed(std::filesystem::path new_file_path);
|
||||
void on_file_changed(const std::filesystem::path& new_file_path);
|
||||
void on_tx_progress(const uint32_t progress);
|
||||
|
||||
void toggle();
|
||||
@@ -102,9 +103,9 @@ class GpsSimAppView : public View {
|
||||
ProgressBar progressbar{
|
||||
{18 * 8, 1 * 16, 12 * 8, 16}};
|
||||
|
||||
FrequencyField field_frequency{
|
||||
TxFrequencyField field_frequency{
|
||||
{0 * 8, 2 * 16},
|
||||
};
|
||||
nav_};
|
||||
|
||||
TransmitterView2 tx_view{
|
||||
// new handling of NumberField field_rfgain, NumberField field_rfamp
|
||||
@@ -123,7 +124,7 @@ class GpsSimAppView : public View {
|
||||
Color::green(),
|
||||
Color::black()};
|
||||
|
||||
spectrum::WaterfallWidget waterfall{};
|
||||
spectrum::WaterfallView waterfall{};
|
||||
|
||||
MessageHandlerRegistration message_handler_replay_thread_error{
|
||||
Message::ID::ReplayThreadDone,
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
#include "lge_app.hpp"
|
||||
|
||||
#include "baseband_api.hpp"
|
||||
#include "cpld_update.hpp"
|
||||
#include "ui_textentry.hpp"
|
||||
|
||||
#include "string_format.hpp"
|
||||
@@ -45,8 +44,7 @@ void LGEView::focus() {
|
||||
|
||||
LGEView::~LGEView() {
|
||||
transmitter_model.disable();
|
||||
hackrf::cpld::load_sram_no_verify(); // to leave all RX ok, without ghost signal problem at the exit .
|
||||
baseband::shutdown(); // better this function at the end, not load_sram() that sometimes produces hang up.
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
void LGEView::generate_lge_frame(const uint8_t command, const uint16_t address_a, const uint16_t address_b, std::vector<uint8_t>& data) {
|
||||
|
||||
@@ -51,9 +51,8 @@ void POCSAGLogger::log_decoded(
|
||||
|
||||
namespace ui {
|
||||
|
||||
POCSAGAppView::POCSAGAppView(NavigationView& nav) {
|
||||
uint32_t ignore_address;
|
||||
|
||||
POCSAGAppView::POCSAGAppView(NavigationView& nav)
|
||||
: nav_{nav} {
|
||||
baseband::run_image(portapack::spi_flash::image_tag_pocsag);
|
||||
|
||||
add_children({&rssi,
|
||||
@@ -70,28 +69,15 @@ POCSAGAppView::POCSAGAppView(NavigationView& nav) {
|
||||
&console});
|
||||
|
||||
if (!settings_.loaded())
|
||||
receiver_model.set_target_frequency(initial_target_frequency);
|
||||
field_frequency.set_value(initial_target_frequency);
|
||||
|
||||
receiver_model.set_modulation(ReceiverModel::Mode::NarrowbandFMAudio);
|
||||
receiver_model.enable();
|
||||
|
||||
field_frequency.set_value(receiver_model.target_frequency());
|
||||
field_frequency.on_change = [this](rf::Frequency f) {
|
||||
receiver_model.set_target_frequency(f);
|
||||
};
|
||||
field_frequency.set_step(receiver_model.frequency_step());
|
||||
field_frequency.on_edit = [this, &nav]() {
|
||||
// TODO: Provide separate modal method/scheme?
|
||||
auto new_view = nav.push<FrequencyKeypadView>(receiver_model.target_frequency());
|
||||
new_view->on_changed = [this](rf::Frequency f) {
|
||||
field_frequency.set_value(f);
|
||||
};
|
||||
};
|
||||
|
||||
// TODO app setting instead?
|
||||
// TODO: app setting instead?
|
||||
uint32_t ignore_address;
|
||||
ignore_address = persistent_memory::pocsag_ignore_address();
|
||||
|
||||
// TODO is this common enough for a helper?
|
||||
// TODO: is this common enough for a helper?
|
||||
for (size_t c = 0; c < 7; c++) {
|
||||
sym_ignore.set_sym(6 - c, ignore_address % 10);
|
||||
ignore_address /= 10;
|
||||
@@ -102,13 +88,12 @@ POCSAGAppView::POCSAGAppView(NavigationView& nav) {
|
||||
logger->append(LOG_ROOT_DIR "/POCSAG.TXT");
|
||||
|
||||
audio::output::start();
|
||||
audio::output::unmute();
|
||||
|
||||
baseband::set_pocsag();
|
||||
}
|
||||
|
||||
void POCSAGAppView::focus() {
|
||||
check_log.focus();
|
||||
field_frequency.focus();
|
||||
}
|
||||
|
||||
POCSAGAppView::~POCSAGAppView() {
|
||||
@@ -157,9 +142,10 @@ void POCSAGAppView::on_packet(const POCSAGPacketMessage* message) {
|
||||
console.write(console_info);
|
||||
|
||||
if (logger && logging()) {
|
||||
logger->log_decoded(message->packet, to_string_dec_uint(pocsag_state.address) +
|
||||
" F" + to_string_dec_uint(pocsag_state.function) +
|
||||
" Address only");
|
||||
logger->log_decoded(
|
||||
message->packet, to_string_dec_uint(pocsag_state.address) +
|
||||
" F" + to_string_dec_uint(pocsag_state.function) +
|
||||
" Address only");
|
||||
}
|
||||
|
||||
last_address = pocsag_state.address;
|
||||
@@ -176,16 +162,17 @@ void POCSAGAppView::on_packet(const POCSAGPacketMessage* message) {
|
||||
}
|
||||
|
||||
if (logger && logging())
|
||||
logger->log_decoded(message->packet, to_string_dec_uint(pocsag_state.address) +
|
||||
" F" + to_string_dec_uint(pocsag_state.function) +
|
||||
" Alpha: " + pocsag_state.output);
|
||||
logger->log_decoded(
|
||||
message->packet, to_string_dec_uint(pocsag_state.address) +
|
||||
" F" + to_string_dec_uint(pocsag_state.function) +
|
||||
" Alpha: " + pocsag_state.output);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: make setting.
|
||||
// Log raw data whatever it contains
|
||||
if (logger && logging())
|
||||
logger->log_raw_data(message->packet, receiver_model.target_frequency());
|
||||
/*if (logger && logging())
|
||||
logger->log_raw_data(message->packet, receiver_model.target_frequency());*/
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#define __POCSAG_APP_H__
|
||||
|
||||
#include "ui_widget.hpp"
|
||||
#include "ui_freq_field.hpp"
|
||||
#include "ui_receiver.hpp"
|
||||
#include "ui_rssi.hpp"
|
||||
|
||||
@@ -61,6 +62,7 @@ class POCSAGAppView : public View {
|
||||
bool logging() const { return check_log.value(); };
|
||||
bool ignore() const { return check_ignore.value(); };
|
||||
|
||||
NavigationView& nav_;
|
||||
RxRadioState radio_state_{};
|
||||
app_settings::SettingsManager settings_{
|
||||
"rx_pocsag", app_settings::Mode::RX};
|
||||
@@ -75,18 +77,15 @@ class POCSAGAppView : public View {
|
||||
VGAGainField field_vga{
|
||||
{18 * 8, 0 * 16}};
|
||||
RSSI rssi{
|
||||
{21 * 8, 0, 6 * 8, 4},
|
||||
};
|
||||
{21 * 8, 0, 6 * 8, 4}};
|
||||
Channel channel{
|
||||
{21 * 8, 5, 6 * 8, 4},
|
||||
};
|
||||
{21 * 8, 5, 6 * 8, 4}};
|
||||
Audio audio{
|
||||
{21 * 8, 10, 6 * 8, 4},
|
||||
};
|
||||
{21 * 8, 10, 6 * 8, 4}};
|
||||
|
||||
FrequencyField field_frequency{
|
||||
RxFrequencyField field_frequency{
|
||||
{0 * 8, 0 * 8},
|
||||
};
|
||||
nav_};
|
||||
|
||||
AudioVolumeField field_volume{
|
||||
{28 * 8, 0 * 16}};
|
||||
|
||||
@@ -26,12 +26,14 @@
|
||||
|
||||
#include "ui_fileman.hpp"
|
||||
#include "io_file.hpp"
|
||||
#include "cpld_update.hpp"
|
||||
#include "baseband_api.hpp"
|
||||
#include "metadata_file.hpp"
|
||||
#include "portapack.hpp"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace ui {
|
||||
|
||||
@@ -39,51 +41,41 @@ void ReplayAppView::set_ready() {
|
||||
ready_signal = true;
|
||||
}
|
||||
|
||||
void ReplayAppView::on_file_changed(std::filesystem::path new_file_path) {
|
||||
File data_file, info_file;
|
||||
char file_data[257];
|
||||
void ReplayAppView::on_file_changed(const fs::path& new_file_path) {
|
||||
file_path = fs::path(u"/") + new_file_path;
|
||||
File::Size file_size{};
|
||||
|
||||
// Get file size
|
||||
auto data_open_error = data_file.open("/" + new_file_path.string());
|
||||
if (data_open_error.is_valid()) {
|
||||
file_error();
|
||||
return;
|
||||
}
|
||||
|
||||
file_path = new_file_path;
|
||||
|
||||
// Get original record frequency if available
|
||||
std::filesystem::path info_file_path = file_path;
|
||||
info_file_path.replace_extension(u".TXT");
|
||||
|
||||
sample_rate = 500000;
|
||||
|
||||
auto info_open_error = info_file.open("/" + info_file_path.string());
|
||||
if (!info_open_error.is_valid()) {
|
||||
memset(file_data, 0, 257);
|
||||
auto read_size = info_file.read(file_data, 256);
|
||||
if (!read_size.is_error()) {
|
||||
auto pos1 = strstr(file_data, "center_frequency=");
|
||||
if (pos1) {
|
||||
pos1 += 17;
|
||||
field_frequency.set_value(strtoll(pos1, nullptr, 10));
|
||||
}
|
||||
|
||||
auto pos2 = strstr(file_data, "sample_rate=");
|
||||
if (pos2) {
|
||||
pos2 += 12;
|
||||
sample_rate = strtoll(pos2, nullptr, 10);
|
||||
}
|
||||
{ // Get the size of the data file.
|
||||
File data_file;
|
||||
auto error = data_file.open(file_path);
|
||||
if (error) {
|
||||
file_error();
|
||||
return;
|
||||
}
|
||||
|
||||
file_size = data_file.size();
|
||||
}
|
||||
|
||||
// Get original record frequency if available.
|
||||
auto metadata_path = get_metadata_path(file_path);
|
||||
auto metadata = read_metadata_file(metadata_path);
|
||||
|
||||
if (metadata) {
|
||||
field_frequency.set_value(metadata->center_frequency);
|
||||
sample_rate = metadata->sample_rate;
|
||||
} else {
|
||||
// TODO: This is interesting because it implies that the
|
||||
// The capture will just be replayed at the freq set on the
|
||||
// FrequencyField. Is that an intentional behavior?
|
||||
sample_rate = 500000;
|
||||
}
|
||||
|
||||
// UI Fixup.
|
||||
text_sample_rate.set(unit_auto_scale(sample_rate, 3, 0) + "Hz");
|
||||
|
||||
auto file_size = data_file.size();
|
||||
auto duration = (file_size * 1000) / (2 * 2 * sample_rate);
|
||||
|
||||
progressbar.set_max(file_size);
|
||||
text_filename.set(file_path.filename().string().substr(0, 12));
|
||||
text_filename.set(truncate(file_path.filename().string(), 12));
|
||||
|
||||
auto duration = ms_duration(file_size, sample_rate, 4);
|
||||
text_duration.set(to_string_time_ms(duration));
|
||||
|
||||
button_play.focus();
|
||||
@@ -188,34 +180,19 @@ ReplayAppView::ReplayAppView(
|
||||
&text_duration,
|
||||
&progressbar,
|
||||
&field_frequency,
|
||||
&tx_view, // now it handles previous rfgain , rfamp.
|
||||
&tx_view, // now it handles previous rfgain, rfamp.
|
||||
&check_loop,
|
||||
&button_play,
|
||||
&waterfall,
|
||||
});
|
||||
|
||||
field_frequency.set_value(transmitter_model.target_frequency());
|
||||
field_frequency.set_step(receiver_model.frequency_step());
|
||||
field_frequency.on_change = [this](rf::Frequency f) {
|
||||
transmitter_model.set_target_frequency(f);
|
||||
};
|
||||
field_frequency.on_edit = [this, &nav]() {
|
||||
// TODO: Provide separate modal method/scheme?
|
||||
auto new_view = nav.push<FrequencyKeypadView>(transmitter_model.target_frequency());
|
||||
new_view->on_changed = [this](rf::Frequency f) {
|
||||
field_frequency.set_value(f);
|
||||
};
|
||||
};
|
||||
|
||||
field_frequency.set_step(5000);
|
||||
|
||||
button_play.on_select = [this](ImageButton&) {
|
||||
this->toggle();
|
||||
};
|
||||
|
||||
button_open.on_select = [this, &nav](Button&) {
|
||||
auto open_view = nav.push<FileLoadView>(".C16");
|
||||
open_view->on_changed = [this](std::filesystem::path new_file_path) {
|
||||
open_view->on_changed = [this](fs::path new_file_path) {
|
||||
on_file_changed(new_file_path);
|
||||
};
|
||||
};
|
||||
@@ -226,9 +203,7 @@ ReplayAppView::~ReplayAppView() {
|
||||
|
||||
display.fill_rectangle({0, 0, 240, 320}, Color::black()); // Solving sometimes visible bottom waterfall artifacts, clearing all LCD pixels.
|
||||
chThdSleepMilliseconds(40); // (that happened sometimes if we interrupt the waterfall play at the beggining of the play around 25% and exit )
|
||||
hackrf::cpld::load_sram_no_verify(); // to leave all RX reception ok, without "ghost interference signal problem" at the exit .
|
||||
|
||||
baseband::shutdown(); // better this function at the end, after load_sram(). If not , sometimes produced hang up (now not , it is ok).
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
void ReplayAppView::on_hide() {
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#include "ui_widget.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "ui_receiver.hpp"
|
||||
#include "ui_freq_field.hpp"
|
||||
#include "replay_thread.hpp"
|
||||
#include "ui_spectrum.hpp"
|
||||
#include "ui_transmitter.hpp"
|
||||
@@ -66,7 +67,7 @@ class ReplayAppView : public View {
|
||||
const size_t read_size{16384};
|
||||
const size_t buffer_count{3};
|
||||
|
||||
void on_file_changed(std::filesystem::path new_file_path);
|
||||
void on_file_changed(const std::filesystem::path& new_file_path);
|
||||
void on_tx_progress(const uint32_t progress);
|
||||
|
||||
void toggle();
|
||||
@@ -98,9 +99,10 @@ class ReplayAppView : public View {
|
||||
ProgressBar progressbar{
|
||||
{18 * 8, 1 * 16, 12 * 8, 16}};
|
||||
|
||||
FrequencyField field_frequency{
|
||||
// TODO: Does this need to be a frequency field at all?
|
||||
TxFrequencyField field_frequency{
|
||||
{0 * 8, 2 * 16},
|
||||
};
|
||||
nav_};
|
||||
|
||||
TransmitterView2 tx_view{
|
||||
// new handling of NumberField field_rfgain, NumberField field_rfamp
|
||||
@@ -119,7 +121,7 @@ class ReplayAppView : public View {
|
||||
Color::green(),
|
||||
Color::black()};
|
||||
|
||||
spectrum::WaterfallWidget waterfall{};
|
||||
spectrum::WaterfallView waterfall{};
|
||||
|
||||
MessageHandlerRegistration message_handler_replay_thread_error{
|
||||
Message::ID::ReplayThreadDone,
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
#include "soundboard_app.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "tonesets.hpp"
|
||||
#include "cpld_update.hpp"
|
||||
#include "ui_tone_key.hpp"
|
||||
|
||||
using namespace tonekey;
|
||||
using namespace portapack;
|
||||
@@ -275,8 +275,7 @@ SoundBoardView::SoundBoardView(
|
||||
SoundBoardView::~SoundBoardView() {
|
||||
stop();
|
||||
transmitter_model.disable();
|
||||
hackrf::cpld::load_sram_no_verify(); // to leave all RX ok, without ghost signal problem at the exit.
|
||||
baseband::shutdown(); // better this function at the end, not load_sram() that sometimes produces hang up.
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
|
||||
@@ -29,7 +29,6 @@
|
||||
#include "baseband_api.hpp"
|
||||
#include "lfsr_random.hpp"
|
||||
#include "io_wave.hpp"
|
||||
#include "tone_key.hpp"
|
||||
#include "app_settings.hpp"
|
||||
#include "radio_state.hpp"
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ class SpectrumAnalysisModel {
|
||||
|
||||
namespace ui {
|
||||
|
||||
class SpectrumAnalysisView : public spectrum::WaterfallWidget {
|
||||
class SpectrumAnalysisView : public spectrum::WaterfallView {
|
||||
public:
|
||||
private:
|
||||
SpectrumAnalysisModel model;
|
||||
|
||||
@@ -106,8 +106,7 @@ class TPMSAppView : public View {
|
||||
|
||||
RxRadioState radio_state_{
|
||||
1750000 /* bandwidth */,
|
||||
2457600 /* sampling rate */
|
||||
};
|
||||
2457600 /* sampling rate */};
|
||||
app_settings::SettingsManager settings_{
|
||||
"rx_tpms", app_settings::Mode::RX};
|
||||
|
||||
|
||||
@@ -468,7 +468,8 @@ void ADSBRxView::updateDetailsAndMap(int ageStep) {
|
||||
details_view->update(entry);
|
||||
}
|
||||
// Store if the view is present and the list isn't full
|
||||
else if (otherMarkersCanBeSent && (markerStored != MARKER_LIST_FULL) && entry.pos.valid && (entry.age_state <= 2)) {
|
||||
// Note -- Storing the selected entry too, in case map panning occurs
|
||||
if (otherMarkersCanBeSent && (markerStored != MARKER_LIST_FULL) && entry.pos.valid && (entry.age_state <= 2)) {
|
||||
marker.lon = entry.pos.longitude;
|
||||
marker.lat = entry.pos.latitude;
|
||||
marker.angle = entry.velo.heading;
|
||||
@@ -512,8 +513,7 @@ ADSBRxView::ADSBRxView(NavigationView& nav) {
|
||||
|
||||
baseband::set_adsb();
|
||||
|
||||
receiver_model.set_target_frequency(1090000000);
|
||||
receiver_model.set_modulation(ReceiverModel::Mode::SpectrumAnalysis);
|
||||
receiver_model.set_target_frequency(1'090'000'000);
|
||||
receiver_model.enable();
|
||||
}
|
||||
|
||||
|
||||
@@ -343,8 +343,8 @@ class ADSBRxView : public View {
|
||||
private:
|
||||
RxRadioState radio_state_{
|
||||
2500000 /* bandwidth */,
|
||||
2000000 /* sampling rate */
|
||||
};
|
||||
2000000 /* sampling rate */,
|
||||
ReceiverModel::Mode::SpectrumAnalysis};
|
||||
app_settings::SettingsManager settings_{
|
||||
"rx_adsb", app_settings::Mode::RX};
|
||||
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
#include "manchester.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "portapack.hpp"
|
||||
#include "cpld_update.hpp"
|
||||
#include "baseband_api.hpp"
|
||||
|
||||
#include <cstring>
|
||||
@@ -274,8 +273,7 @@ void ADSBTxView::focus() {
|
||||
|
||||
ADSBTxView::~ADSBTxView() {
|
||||
transmitter_model.disable();
|
||||
hackrf::cpld::load_sram_no_verify(); // to leave all RX ok, withouth ghost signal problem at the exit.
|
||||
baseband::shutdown(); // better this function at the end, not load_sram() that sometimes produces hang up.
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
void ADSBTxView::generate_frames() {
|
||||
|
||||
@@ -43,11 +43,8 @@ void AFSKRxView::focus() {
|
||||
field_frequency.focus();
|
||||
}
|
||||
|
||||
void AFSKRxView::update_freq(rf::Frequency f) {
|
||||
receiver_model.set_target_frequency(f);
|
||||
}
|
||||
|
||||
AFSKRxView::AFSKRxView(NavigationView& nav) {
|
||||
AFSKRxView::AFSKRxView(NavigationView& nav)
|
||||
: nav_{nav} {
|
||||
baseband::run_image(portapack::spi_flash::image_tag_afsk_rx);
|
||||
|
||||
add_children({&rssi,
|
||||
@@ -62,8 +59,8 @@ AFSKRxView::AFSKRxView(NavigationView& nav) {
|
||||
&button_modem_setup,
|
||||
&console});
|
||||
|
||||
// Auto-configure modem for LCR RX (will be removed later)
|
||||
update_freq(467225500); // 462713300
|
||||
// Auto-configure modem for LCR RX (TODO remove)
|
||||
field_frequency.set_value(467225500);
|
||||
auto def_bell202 = &modem_defs[0];
|
||||
persistent_memory::set_modem_baudrate(def_bell202->baudrate);
|
||||
serial_format_t serial_format;
|
||||
@@ -73,17 +70,7 @@ AFSKRxView::AFSKRxView(NavigationView& nav) {
|
||||
serial_format.bit_order = LSB_FIRST;
|
||||
persistent_memory::set_serial_format(serial_format);
|
||||
|
||||
field_frequency.set_value(receiver_model.target_frequency());
|
||||
field_frequency.set_step(100);
|
||||
field_frequency.on_change = [this](rf::Frequency f) {
|
||||
update_freq(f);
|
||||
};
|
||||
field_frequency.on_edit = [this, &nav]() {
|
||||
auto new_view = nav.push<FrequencyKeypadView>(receiver_model.target_frequency());
|
||||
new_view->on_changed = [this](rf::Frequency f) {
|
||||
field_frequency.set_value(f);
|
||||
};
|
||||
};
|
||||
|
||||
check_log.set_value(logging);
|
||||
check_log.on_select = [this](Checkbox&, bool v) {
|
||||
@@ -104,7 +91,6 @@ AFSKRxView::AFSKRxView(NavigationView& nav) {
|
||||
audio::set_rate(audio::Rate::Hz_24000);
|
||||
audio::output::start();
|
||||
|
||||
receiver_model.set_modulation(ReceiverModel::Mode::NarrowbandFMAudio);
|
||||
receiver_model.enable();
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,8 @@
|
||||
#include "ui.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "ui_receiver.hpp"
|
||||
#include "ui_record_view.hpp" // DEBUG
|
||||
#include "ui_freq_field.hpp"
|
||||
#include "ui_record_view.hpp"
|
||||
#include "app_settings.hpp"
|
||||
#include "radio_state.hpp"
|
||||
#include "log_file.hpp"
|
||||
@@ -58,6 +59,7 @@ class AFSKRxView : public View {
|
||||
private:
|
||||
void on_data(uint32_t value, bool is_data);
|
||||
|
||||
NavigationView& nav_;
|
||||
RxRadioState radio_state_{};
|
||||
app_settings::SettingsManager settings_{
|
||||
"rx_afsk", app_settings::Mode::RX};
|
||||
@@ -74,18 +76,16 @@ class AFSKRxView : public View {
|
||||
VGAGainField field_vga{
|
||||
{18 * 8, 0 * 16}};
|
||||
RSSI rssi{
|
||||
{21 * 8, 0, 6 * 8, 4},
|
||||
};
|
||||
{21 * 8, 0, 6 * 8, 4}};
|
||||
Channel channel{
|
||||
{21 * 8, 5, 6 * 8, 4},
|
||||
};
|
||||
{21 * 8, 5, 6 * 8, 4}};
|
||||
|
||||
AudioVolumeField field_volume{
|
||||
{28 * 8, 0 * 16}};
|
||||
|
||||
FrequencyField field_frequency{
|
||||
RxFrequencyField field_frequency{
|
||||
{0 * 8, 0 * 16},
|
||||
};
|
||||
nav_};
|
||||
|
||||
Checkbox check_log{
|
||||
{0 * 8, 1 * 16},
|
||||
@@ -104,7 +104,6 @@ class AFSKRxView : public View {
|
||||
Console console{
|
||||
{0, 4 * 16, 240, 240}};
|
||||
|
||||
void update_freq(rf::Frequency f);
|
||||
void on_data_afsk(const AFSKDataMessage& message);
|
||||
|
||||
std::unique_ptr<AFSKLogger> logger{};
|
||||
|
||||
@@ -73,12 +73,8 @@ void APRSRxView::focus() {
|
||||
options_region.focus();
|
||||
}
|
||||
|
||||
void APRSRxView::update_freq(rf::Frequency f) {
|
||||
receiver_model.set_target_frequency(f);
|
||||
}
|
||||
|
||||
APRSRxView::APRSRxView(NavigationView& nav, Rect parent_rect)
|
||||
: View(parent_rect) {
|
||||
: View(parent_rect), nav_{nav} {
|
||||
baseband::run_image(portapack::spi_flash::image_tag_aprs_rx);
|
||||
|
||||
add_children({&rssi,
|
||||
@@ -111,18 +107,7 @@ APRSRxView::APRSRxView(NavigationView& nav, Rect parent_rect)
|
||||
}
|
||||
};
|
||||
|
||||
field_frequency.set_value(receiver_model.target_frequency());
|
||||
field_frequency.set_step(100);
|
||||
field_frequency.on_change = [this](rf::Frequency f) {
|
||||
update_freq(f);
|
||||
};
|
||||
field_frequency.on_edit = [this, &nav]() {
|
||||
auto new_view = nav.push<FrequencyKeypadView>(receiver_model.target_frequency());
|
||||
new_view->on_changed = [this](rf::Frequency f) {
|
||||
field_frequency.set_value(f);
|
||||
};
|
||||
};
|
||||
|
||||
options_region.set_selected_index(0, true);
|
||||
|
||||
logger = std::make_unique<APRSLogger>();
|
||||
@@ -134,7 +119,6 @@ APRSRxView::APRSRxView(NavigationView& nav, Rect parent_rect)
|
||||
audio::set_rate(audio::Rate::Hz_24000);
|
||||
audio::output::start();
|
||||
|
||||
receiver_model.set_modulation(ReceiverModel::Mode::NarrowbandFMAudio);
|
||||
receiver_model.enable();
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,8 @@
|
||||
#include "ui.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "ui_receiver.hpp"
|
||||
#include "ui_record_view.hpp" // DEBUG
|
||||
#include "ui_freq_field.hpp"
|
||||
#include "ui_record_view.hpp"
|
||||
#include "ui_geomap.hpp"
|
||||
#include "app_settings.hpp"
|
||||
#include "radio_state.hpp"
|
||||
@@ -188,6 +189,7 @@ class APRSRxView : public View {
|
||||
void on_data(uint32_t value, bool is_data);
|
||||
bool reset_console = false;
|
||||
|
||||
NavigationView& nav_;
|
||||
RxRadioState radio_state_{};
|
||||
app_settings::SettingsManager settings_{
|
||||
"rx_aprs", app_settings::Mode::RX};
|
||||
@@ -202,11 +204,9 @@ class APRSRxView : public View {
|
||||
VGAGainField field_vga{
|
||||
{18 * 8, 0 * 16}};
|
||||
RSSI rssi{
|
||||
{21 * 8, 0, 6 * 8, 4},
|
||||
};
|
||||
{21 * 8, 0, 6 * 8, 4}};
|
||||
Channel channel{
|
||||
{21 * 8, 5, 6 * 8, 4},
|
||||
};
|
||||
{21 * 8, 5, 6 * 8, 4}};
|
||||
|
||||
AudioVolumeField field_volume{
|
||||
{28 * 8, 0 * 16}};
|
||||
@@ -219,9 +219,9 @@ class APRSRxView : public View {
|
||||
{"AUS", 2},
|
||||
{"NZ ", 3}}};
|
||||
|
||||
FrequencyField field_frequency{
|
||||
RxFrequencyField field_frequency{
|
||||
{3 * 8, 0 * 16},
|
||||
};
|
||||
nav_};
|
||||
|
||||
// DEBUG
|
||||
RecordView record_view{
|
||||
@@ -235,8 +235,6 @@ class APRSRxView : public View {
|
||||
Console console{
|
||||
{0, 2 * 16, 240, 240}};
|
||||
|
||||
void update_freq(rf::Frequency f);
|
||||
|
||||
std::unique_ptr<APRSLogger> logger{};
|
||||
};
|
||||
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
#include "aprs.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "portapack.hpp"
|
||||
#include "cpld_update.hpp"
|
||||
#include "baseband_api.hpp"
|
||||
#include "portapack_shared_memory.hpp"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
@@ -45,8 +44,7 @@ void APRSTXView::focus() {
|
||||
|
||||
APRSTXView::~APRSTXView() {
|
||||
transmitter_model.disable();
|
||||
hackrf::cpld::load_sram_no_verify(); // to leave all RX ok, without ghost signal problem at the exit.
|
||||
baseband::shutdown(); // better this function at the end, not load_sram() that sometimes produces hang up.
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
void APRSTXView::start_tx() {
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
#include "string_format.hpp"
|
||||
|
||||
#include "baseband_api.hpp"
|
||||
#include "cpld_update.hpp"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
@@ -138,8 +137,7 @@ void BHTView::on_tx_progress(const uint32_t progress, const bool done) {
|
||||
|
||||
BHTView::~BHTView() {
|
||||
transmitter_model.disable();
|
||||
hackrf::cpld::load_sram_no_verify(); // to leave all RX ok, without ghost signal problem at the exit .
|
||||
baseband::shutdown(); // better this function at the end, not load_sram() that sometimes produces hang up.
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
BHTView::BHTView(NavigationView& nav) {
|
||||
|
||||
@@ -40,11 +40,8 @@ void BTLERxView::focus() {
|
||||
field_frequency.focus();
|
||||
}
|
||||
|
||||
void BTLERxView::update_freq(rf::Frequency f) {
|
||||
receiver_model.set_target_frequency(f);
|
||||
}
|
||||
|
||||
BTLERxView::BTLERxView(NavigationView& nav) {
|
||||
BTLERxView::BTLERxView(NavigationView& nav)
|
||||
: nav_{nav} {
|
||||
baseband::run_image(portapack::spi_flash::image_tag_btle_rx);
|
||||
|
||||
add_children({&rssi,
|
||||
@@ -56,8 +53,8 @@ BTLERxView::BTLERxView(NavigationView& nav) {
|
||||
&button_modem_setup,
|
||||
&console});
|
||||
|
||||
// Auto-configure modem for LCR RX (will be removed later)
|
||||
update_freq(2426000000);
|
||||
// Auto-configure modem for LCR RX (TODO: remove later)
|
||||
field_frequency.set_value(2426000000);
|
||||
auto def_bell202 = &modem_defs[0];
|
||||
persistent_memory::set_modem_baudrate(def_bell202->baudrate);
|
||||
serial_format_t serial_format;
|
||||
@@ -67,17 +64,7 @@ BTLERxView::BTLERxView(NavigationView& nav) {
|
||||
serial_format.bit_order = LSB_FIRST;
|
||||
persistent_memory::set_serial_format(serial_format);
|
||||
|
||||
field_frequency.set_value(receiver_model.target_frequency());
|
||||
field_frequency.set_step(100);
|
||||
field_frequency.on_change = [this](rf::Frequency f) {
|
||||
update_freq(f);
|
||||
};
|
||||
field_frequency.on_edit = [this, &nav]() {
|
||||
auto new_view = nav.push<FrequencyKeypadView>(receiver_model.target_frequency());
|
||||
new_view->on_changed = [this](rf::Frequency f) {
|
||||
field_frequency.set_value(f);
|
||||
};
|
||||
};
|
||||
|
||||
button_modem_setup.on_select = [&nav](Button&) {
|
||||
nav.push<ModemSetupView>();
|
||||
@@ -89,7 +76,6 @@ BTLERxView::BTLERxView(NavigationView& nav) {
|
||||
audio::set_rate(audio::Rate::Hz_24000);
|
||||
audio::output::start();
|
||||
|
||||
receiver_model.set_modulation(ReceiverModel::Mode::WidebandFMAudio);
|
||||
receiver_model.enable();
|
||||
}
|
||||
|
||||
|
||||
@@ -27,9 +27,10 @@
|
||||
#include "ui.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "ui_receiver.hpp"
|
||||
#include "ui_freq_field.hpp"
|
||||
#include "app_settings.hpp"
|
||||
#include "radio_state.hpp"
|
||||
#include "ui_record_view.hpp" // DEBUG
|
||||
#include "ui_record_view.hpp"
|
||||
|
||||
#include "utility.hpp"
|
||||
|
||||
@@ -47,10 +48,11 @@ class BTLERxView : public View {
|
||||
private:
|
||||
void on_data(uint32_t value, bool is_data);
|
||||
|
||||
NavigationView& nav_;
|
||||
RxRadioState radio_state_{
|
||||
4000000 /* bandwidth */,
|
||||
4000000 /* sampling rate */
|
||||
};
|
||||
4000000 /* sampling rate */,
|
||||
ReceiverModel::Mode::WidebandFMAudio};
|
||||
app_settings::SettingsManager settings_{
|
||||
"rx_btle", app_settings::Mode::RX};
|
||||
|
||||
@@ -64,15 +66,13 @@ class BTLERxView : public View {
|
||||
VGAGainField field_vga{
|
||||
{18 * 8, 0 * 16}};
|
||||
RSSI rssi{
|
||||
{21 * 8, 0, 6 * 8, 4},
|
||||
};
|
||||
{21 * 8, 0, 6 * 8, 4}};
|
||||
Channel channel{
|
||||
{21 * 8, 5, 6 * 8, 4},
|
||||
};
|
||||
{21 * 8, 5, 6 * 8, 4}};
|
||||
|
||||
FrequencyField field_frequency{
|
||||
RxFrequencyField field_frequency{
|
||||
{0 * 8, 0 * 16},
|
||||
};
|
||||
nav_};
|
||||
|
||||
Button button_modem_setup{
|
||||
{240 - 12 * 8, 1 * 16, 96, 24},
|
||||
@@ -81,9 +81,6 @@ class BTLERxView : public View {
|
||||
Console console{
|
||||
{0, 4 * 16, 240, 240}};
|
||||
|
||||
void update_freq(rf::Frequency f);
|
||||
// void on_data_afsk(const AFSKDataMessage& message);
|
||||
|
||||
MessageHandlerRegistration message_handler_packet{
|
||||
Message::ID::AFSKData,
|
||||
[this](Message* const p) {
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
#include "ui_coasterp.hpp"
|
||||
|
||||
#include "baseband_api.hpp"
|
||||
#include "cpld_update.hpp"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
|
||||
#include <cstring>
|
||||
@@ -39,8 +38,7 @@ void CoasterPagerView::focus() {
|
||||
|
||||
CoasterPagerView::~CoasterPagerView() {
|
||||
transmitter_model.disable();
|
||||
hackrf::cpld::load_sram_no_verify(); // to leave all RX ok, without ghost signal problem at the exit .
|
||||
baseband::shutdown(); // better this function at the end, not load_sram() that sometimes produces hang up.
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
void CoasterPagerView::generate_frame() {
|
||||
|
||||
@@ -30,6 +30,9 @@
|
||||
#include "audio.hpp"
|
||||
|
||||
#include "ui_sd_card_debug.hpp"
|
||||
#include "ui_font_fixed_8x16.hpp"
|
||||
#include "ui_styles.hpp"
|
||||
#include "ui_painter.hpp"
|
||||
|
||||
#include "portapack.hpp"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
@@ -248,6 +251,7 @@ void ControlsSwitchesWidget::on_show() {
|
||||
|
||||
bool ControlsSwitchesWidget::on_key(const KeyEvent key) {
|
||||
key_event_mask = 1 << toUType(key);
|
||||
long_press_key_event_mask = switch_long_press_occurred((size_t)key) ? key_event_mask : 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -325,6 +329,14 @@ void ControlsSwitchesWidget::paint(Painter& painter) {
|
||||
|
||||
switches_event >>= 1;
|
||||
}
|
||||
|
||||
switches_event = long_press_key_event_mask;
|
||||
for (const auto r : events_rects) {
|
||||
if (switches_event & 1)
|
||||
painter.fill_rectangle(r + pos, Color::cyan());
|
||||
|
||||
switches_event >>= 1;
|
||||
}
|
||||
}
|
||||
|
||||
void ControlsSwitchesWidget::on_frame_sync() {
|
||||
@@ -335,12 +347,21 @@ void ControlsSwitchesWidget::on_frame_sync() {
|
||||
|
||||
DebugControlsView::DebugControlsView(NavigationView& nav) {
|
||||
add_children({
|
||||
&text_title,
|
||||
&labels,
|
||||
&switches_widget,
|
||||
&options_switches_mode,
|
||||
&button_done,
|
||||
});
|
||||
|
||||
button_done.on_select = [&nav](Button&) { nav.pop(); };
|
||||
button_done.on_select = [&nav](Button&) {
|
||||
switches_long_press_enable(0);
|
||||
nav.pop();
|
||||
};
|
||||
|
||||
options_switches_mode.on_change = [this](size_t, OptionsField::value_t v) {
|
||||
(void)v;
|
||||
switches_long_press_enable(options_switches_mode.selected_index_value());
|
||||
};
|
||||
}
|
||||
|
||||
void DebugControlsView::focus() {
|
||||
@@ -383,6 +404,8 @@ DebugMenuView::DebugMenuView(NavigationView& nav) {
|
||||
{"Temperature", ui::Color::dark_cyan(), &bitmap_icon_temperature, [&nav]() { nav.push<TemperatureView>(); }},
|
||||
{"Buttons Test", ui::Color::dark_cyan(), &bitmap_icon_controls, [&nav]() { nav.push<DebugControlsView>(); }},
|
||||
{"P.Memory", ui::Color::dark_cyan(), &bitmap_icon_memory, [&nav]() { nav.push<DebugPmemView>(); }},
|
||||
{"Fonts Viewer", ui::Color::dark_cyan(), &bitmap_icon_notepad, [&nav]() { nav.push<DebugFontsView>(); }},
|
||||
{"Debug Dump", ui::Color::dark_cyan(), &bitmap_icon_memory, [&nav]() { portapack::persistent_memory::debug_dump(); }},
|
||||
});
|
||||
set_max_rows(2); // allow wider buttons
|
||||
}
|
||||
@@ -443,6 +466,43 @@ uint32_t DebugPmemView::registers_widget_feed(const size_t register_number) {
|
||||
return data.regfile[(page_size * page + register_number) / 4] >> (register_number % 4 * 8);
|
||||
}
|
||||
|
||||
/* DebugFontsView *******************************************************/
|
||||
|
||||
uint16_t DebugFontsView::display_font(Painter& painter, uint16_t y_offset, const Style* font_style, std::string_view font_name) {
|
||||
auto char_width{font_style->font.char_width()};
|
||||
auto char_height{font_style->font.line_height()};
|
||||
auto cpl{((screen_width / char_width) - 6) & 0xF8}; // Display a multiple of 8 characters per line
|
||||
uint16_t line_pos{y_offset};
|
||||
|
||||
painter.draw_string({0, y_offset}, *font_style, font_name);
|
||||
|
||||
// Displaying ASCII+extended characters from 0x20 to 0xFF
|
||||
for (uint8_t c = 0; c <= 0xDF; c++) {
|
||||
line_pos = y_offset + ((c / cpl) + 2) * char_height;
|
||||
|
||||
if ((c % cpl) == 0)
|
||||
painter.draw_string({0, line_pos}, *font_style, "Ox" + to_string_hex(c + 0x20, 2));
|
||||
|
||||
painter.draw_char({((c % cpl) + 5) * char_width, line_pos}, *font_style, (char)(c + 0x20));
|
||||
}
|
||||
|
||||
return line_pos + char_height;
|
||||
}
|
||||
|
||||
void DebugFontsView::paint(Painter& painter) {
|
||||
int16_t line_pos;
|
||||
|
||||
line_pos = display_font(painter, 32, &Styles::white, "Fixed 8x16");
|
||||
display_font(painter, line_pos + 16, &Styles::white_small, "Fixed 5x8");
|
||||
}
|
||||
|
||||
DebugFontsView::DebugFontsView(NavigationView& nav)
|
||||
: nav_{nav} {
|
||||
set_focusable(true);
|
||||
}
|
||||
|
||||
/* DebugLCRView *******************************************************/
|
||||
|
||||
/*DebugLCRView::DebugLCRView(NavigationView& nav, std::string lcr_string) {
|
||||
|
||||
std::string debug_text;
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#include "rffc507x.hpp"
|
||||
#include "portapack.hpp"
|
||||
#include "memory_map.hpp"
|
||||
#include "irq_controls.hpp"
|
||||
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
@@ -220,7 +221,8 @@ class ControlsSwitchesWidget : public Widget {
|
||||
ControlsSwitchesWidget(
|
||||
Rect parent_rect)
|
||||
: Widget{parent_rect},
|
||||
key_event_mask(0) {
|
||||
key_event_mask(0),
|
||||
long_press_key_event_mask{0} {
|
||||
set_focusable(true);
|
||||
}
|
||||
|
||||
@@ -231,6 +233,7 @@ class ControlsSwitchesWidget : public Widget {
|
||||
|
||||
private:
|
||||
uint8_t key_event_mask;
|
||||
uint8_t long_press_key_event_mask;
|
||||
|
||||
MessageHandlerRegistration message_handler_frame_sync{
|
||||
Message::ID::DisplayFrameSync,
|
||||
@@ -250,15 +253,22 @@ class DebugControlsView : public View {
|
||||
std::string title() const override { return "Buttons Test"; };
|
||||
|
||||
private:
|
||||
Text text_title{
|
||||
{64, 16, 184, 16},
|
||||
"Controls State",
|
||||
};
|
||||
Labels labels{
|
||||
{{8 * 8, 1 * 16}, "Controls State", Color::white()},
|
||||
{{0 * 8, 14 * 16}, "Long-Press Mode:", Color::grey()}};
|
||||
|
||||
ControlsSwitchesWidget switches_widget{
|
||||
{80, 80, 80, 112},
|
||||
};
|
||||
|
||||
OptionsField options_switches_mode{
|
||||
{17 * 8, 14 * 16},
|
||||
8,
|
||||
{
|
||||
{"Disabled", 0},
|
||||
{"Enabled", 0xFF}, // all KeyEvent bits to long-press mode
|
||||
}};
|
||||
|
||||
Button button_done{
|
||||
{72, 264, 96, 24},
|
||||
"Done"};
|
||||
@@ -300,6 +310,17 @@ class DebugPmemView : public View {
|
||||
uint32_t registers_widget_feed(const size_t register_number);
|
||||
};
|
||||
|
||||
class DebugFontsView : public View {
|
||||
public:
|
||||
DebugFontsView(NavigationView& nav);
|
||||
void paint(Painter& painter) override;
|
||||
std::string title() const override { return "Fonts"; };
|
||||
|
||||
private:
|
||||
uint16_t display_font(Painter& painter, uint16_t y_offset, const Style* font_style, std::string_view font_name);
|
||||
NavigationView& nav_;
|
||||
};
|
||||
|
||||
/*class DebugLCRView : public View {
|
||||
public:
|
||||
DebugLCRView(NavigationView& nav, std::string lcrstring);
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "ui_dfu_menu.hpp"
|
||||
#include "portapack_shared_memory.hpp"
|
||||
#include "performance_counter.hpp"
|
||||
#include "portapack.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
@@ -80,4 +81,57 @@ void DfuMenu::paint(Painter& painter) {
|
||||
ui::Color::dark_cyan());
|
||||
}
|
||||
|
||||
DfuMenu2::DfuMenu2(NavigationView& nav)
|
||||
: nav_(nav) {
|
||||
add_children({&text_head,
|
||||
&labels,
|
||||
&text_info_line_1,
|
||||
&text_info_line_2,
|
||||
&text_info_line_3,
|
||||
&text_info_line_4,
|
||||
&text_info_line_5,
|
||||
&text_info_line_6,
|
||||
&text_info_line_7,
|
||||
&text_info_line_8});
|
||||
}
|
||||
|
||||
void DfuMenu2::paint(Painter& painter) {
|
||||
text_info_line_1.set(to_string_dec_uint(portapack::receiver_model.target_frequency(), 10));
|
||||
text_info_line_2.set(to_string_dec_uint(portapack::receiver_model.baseband_bandwidth(), 10));
|
||||
text_info_line_3.set(to_string_dec_uint(portapack::receiver_model.sampling_rate(), 10));
|
||||
text_info_line_4.set(to_string_dec_uint((uint32_t)portapack::receiver_model.modulation(), 10));
|
||||
text_info_line_5.set(to_string_dec_uint(portapack::receiver_model.am_configuration(), 10));
|
||||
text_info_line_6.set(to_string_dec_uint(portapack::receiver_model.nbfm_configuration(), 10));
|
||||
text_info_line_7.set(to_string_dec_uint(portapack::receiver_model.wfm_configuration(), 10));
|
||||
text_info_line_8.set("");
|
||||
|
||||
constexpr auto margin = 5;
|
||||
constexpr auto lines = 8 + 2;
|
||||
|
||||
painter.fill_rectangle(
|
||||
{{5 * CHARACTER_WIDTH - margin, 3 * LINE_HEIGHT - margin},
|
||||
{19 * CHARACTER_WIDTH + margin * 2, lines * LINE_HEIGHT + margin * 2}},
|
||||
ui::Color::black());
|
||||
|
||||
painter.fill_rectangle(
|
||||
{{4 * CHARACTER_WIDTH - margin, 3 * LINE_HEIGHT - margin},
|
||||
{CHARACTER_WIDTH, lines * LINE_HEIGHT + margin * 2}},
|
||||
ui::Color::dark_cyan());
|
||||
|
||||
painter.fill_rectangle(
|
||||
{{24 * CHARACTER_WIDTH + margin, 3 * LINE_HEIGHT - margin},
|
||||
{CHARACTER_WIDTH, lines * LINE_HEIGHT + margin * 2}},
|
||||
ui::Color::dark_cyan());
|
||||
|
||||
painter.fill_rectangle(
|
||||
{{4 * CHARACTER_WIDTH - margin, 3 * LINE_HEIGHT - margin - 8},
|
||||
{21 * CHARACTER_WIDTH + margin * 2, 8}},
|
||||
ui::Color::dark_cyan());
|
||||
|
||||
painter.fill_rectangle(
|
||||
{{4 * CHARACTER_WIDTH - margin, (lines + 3) * LINE_HEIGHT + margin},
|
||||
{21 * CHARACTER_WIDTH + margin * 2, 8}},
|
||||
ui::Color::dark_cyan());
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -67,6 +67,38 @@ class DfuMenu : public View {
|
||||
Text text_info_line_8{{15 * CHARACTER_WIDTH, 12 * LINE_HEIGHT, 5 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
};
|
||||
|
||||
class DfuMenu2 : public View {
|
||||
public:
|
||||
DfuMenu2(NavigationView& nav);
|
||||
~DfuMenu2() = default;
|
||||
|
||||
void paint(Painter& painter) override;
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
|
||||
Text text_head{{6 * CHARACTER_WIDTH, 3 * LINE_HEIGHT, 8 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, "Receiver"};
|
||||
|
||||
Labels labels{
|
||||
{{5 * CHARACTER_WIDTH, 5 * LINE_HEIGHT}, "Tgt freq:", Color::dark_cyan()},
|
||||
{{5 * CHARACTER_WIDTH, 6 * LINE_HEIGHT}, "Bandwidt:", Color::dark_cyan()},
|
||||
{{5 * CHARACTER_WIDTH, 7 * LINE_HEIGHT}, "Sampl Rt:", Color::dark_cyan()},
|
||||
{{5 * CHARACTER_WIDTH, 8 * LINE_HEIGHT}, "Modulatn:", Color::dark_cyan()},
|
||||
{{5 * CHARACTER_WIDTH, 9 * LINE_HEIGHT}, "AM cfg:", Color::dark_cyan()},
|
||||
{{5 * CHARACTER_WIDTH, 10 * LINE_HEIGHT}, "NBFM cfg:", Color::dark_cyan()},
|
||||
{{5 * CHARACTER_WIDTH, 11 * LINE_HEIGHT}, "WFM cfg:", Color::dark_cyan()},
|
||||
{{5 * CHARACTER_WIDTH, 12 * LINE_HEIGHT}, "", Color::dark_cyan()}};
|
||||
|
||||
Text text_info_line_1{{14 * CHARACTER_WIDTH, 5 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_2{{14 * CHARACTER_WIDTH, 6 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_3{{14 * CHARACTER_WIDTH, 7 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_4{{14 * CHARACTER_WIDTH, 8 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_5{{14 * CHARACTER_WIDTH, 9 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_6{{14 * CHARACTER_WIDTH, 10 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_7{{14 * CHARACTER_WIDTH, 11 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_8{{14 * CHARACTER_WIDTH, 12 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
#endif /*__UI_DFU_MENU_H__*/
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
|
||||
#include "baseband_api.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "cpld_update.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
|
||||
@@ -201,8 +200,7 @@ void EncodersView::focus() {
|
||||
|
||||
EncodersView::~EncodersView() {
|
||||
transmitter_model.disable();
|
||||
hackrf::cpld::load_sram_no_verify(); // ghost signal c/m to the problem at the exit .
|
||||
baseband::shutdown(); // better this function after load_sram()
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
void EncodersView::update_progress() {
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include "ui_fileman.hpp"
|
||||
#include "ui_playlist.hpp"
|
||||
#include "ui_ss_viewer.hpp"
|
||||
#include "ui_text_editor.hpp"
|
||||
#include "string_format.hpp"
|
||||
@@ -35,6 +36,14 @@
|
||||
using namespace portapack;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace ui {
|
||||
static const fs::path txt_ext{u".TXT"};
|
||||
static const fs::path ppl_ext{u".PPL"};
|
||||
static const fs::path c16_ext{u".C16"};
|
||||
static const fs::path png_ext{u".PNG"};
|
||||
static const fs::path bmp_ext{u".BMP"};
|
||||
} // namespace ui
|
||||
|
||||
namespace {
|
||||
using namespace ui;
|
||||
|
||||
@@ -47,25 +56,6 @@ std::string truncate(const fs::path& path, size_t max_length) {
|
||||
return ::truncate(path.string(), max_length);
|
||||
}
|
||||
|
||||
// Case insensitive path equality on underlying "native" string.
|
||||
bool iequal(
|
||||
const fs::path& lhs,
|
||||
const fs::path& rhs) {
|
||||
const auto& lhs_str = lhs.native();
|
||||
const auto& rhs_str = rhs.native();
|
||||
|
||||
// NB: Not correct for Unicode/locales.
|
||||
if (lhs_str.length() == rhs_str.length()) {
|
||||
for (size_t i = 0; i < lhs_str.length(); ++i)
|
||||
if (towupper(lhs_str[i]) != towupper(rhs_str[i]))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Inserts the entry into the entry list sorted directories first then by file name.
|
||||
void insert_sorted(std::vector<fileman_entry>& entries, fileman_entry&& entry) {
|
||||
auto it = std::lower_bound(
|
||||
@@ -86,15 +76,12 @@ void insert_sorted(std::vector<fileman_entry>& entries, fileman_entry&& entry) {
|
||||
fs::path get_partner_file(fs::path path) {
|
||||
if (fs::is_directory(path))
|
||||
return {};
|
||||
|
||||
const fs::path txt_path{u".TXT"};
|
||||
const fs::path c16_path{u".C16"};
|
||||
auto ext = path.extension();
|
||||
|
||||
if (iequal(ext, txt_path))
|
||||
ext = c16_path;
|
||||
else if (iequal(ext, c16_path))
|
||||
ext = txt_path;
|
||||
if (path_iequal(ext, txt_ext))
|
||||
ext = c16_ext;
|
||||
else if (path_iequal(ext, c16_ext))
|
||||
ext = txt_ext;
|
||||
else
|
||||
return {};
|
||||
|
||||
@@ -163,7 +150,7 @@ void FileManBaseView::load_directory_contents(const fs::path& dir_path) {
|
||||
continue;
|
||||
|
||||
if (fs::is_regular_file(entry.status())) {
|
||||
if (!filtering || iequal(entry.path().extension(), extension_filter))
|
||||
if (!filtering || path_iequal(entry.path().extension(), extension_filter))
|
||||
insert_sorted(entry_list, {entry.path(), (uint32_t)entry.size(), false});
|
||||
} else if (fs::is_directory(entry.status())) {
|
||||
insert_sorted(entry_list, {entry.path(), 0, true});
|
||||
@@ -301,7 +288,7 @@ const FileManBaseView::file_assoc_t& FileManBaseView::get_assoc(
|
||||
size_t index = 0;
|
||||
|
||||
for (; index < file_types.size() - 1; ++index)
|
||||
if (iequal(ext, file_types[index].extension))
|
||||
if (path_iequal(ext, file_types[index].extension))
|
||||
return file_types[index];
|
||||
|
||||
// Default to last entry in the list.
|
||||
@@ -507,12 +494,20 @@ bool FileManagerView::handle_file_open() {
|
||||
auto path = get_selected_full_path();
|
||||
auto ext = path.extension();
|
||||
|
||||
if (iequal(u".TXT", ext) || iequal(u".PPL", ext)) {
|
||||
if (path_iequal(txt_ext, ext)) {
|
||||
nav_.push<TextEditorView>(path);
|
||||
return true;
|
||||
} else if (iequal(u".PNG", ext)) {
|
||||
} else if (path_iequal(c16_ext, ext) || path_iequal(ppl_ext, ext)) {
|
||||
// TODO: Enough memory to push?
|
||||
nav_.push<PlaylistView>(path);
|
||||
return true;
|
||||
} else if (path_iequal(png_ext, ext)) {
|
||||
nav_.push<ScreenshotViewer>(path);
|
||||
return true;
|
||||
} else if (path_iequal(bmp_ext, ext)) {
|
||||
nav_.push<SplashViewer>(path);
|
||||
reload_current();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
|
||||
namespace ui {
|
||||
|
||||
static const char16_t* firmware_folder = u"/FIRMWARE";
|
||||
|
||||
Thread* FlashUtilityView::thread{nullptr};
|
||||
static constexpr size_t max_filename_length = 26;
|
||||
|
||||
@@ -35,7 +37,9 @@ FlashUtilityView::FlashUtilityView(NavigationView& nav)
|
||||
|
||||
menu_view.set_parent_rect({0, 3 * 8, 240, 33 * 8});
|
||||
|
||||
for (const auto& entry : std::filesystem::directory_iterator(u"FIRMWARE", u"*.bin")) {
|
||||
ensure_directory(firmware_folder);
|
||||
|
||||
for (const auto& entry : std::filesystem::directory_iterator(firmware_folder, u"*.bin")) {
|
||||
auto filename = entry.path().filename();
|
||||
auto path = entry.path().native();
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ class FlashUtilityView : public View {
|
||||
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override { return "FlashUtility"; }; // Removed the space because the title and speaker icon overlapped.
|
||||
std::string title() const override { return "Flash Utility"; };
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
#include "portapack.hpp"
|
||||
#include "event_m0.hpp"
|
||||
|
||||
#include <memory>
|
||||
|
||||
using namespace portapack;
|
||||
|
||||
namespace ui {
|
||||
@@ -63,7 +65,8 @@ void FreqManBaseView::focus() {
|
||||
}
|
||||
|
||||
void FreqManBaseView::get_freqman_files() {
|
||||
std::vector<std::string>().swap(file_list);
|
||||
// Assume this does change much, clear will preserve the existing alloc.
|
||||
file_list.clear();
|
||||
|
||||
auto files = scan_root_files(u"FREQMAN", u"*.TXT");
|
||||
|
||||
@@ -71,7 +74,7 @@ void FreqManBaseView::get_freqman_files() {
|
||||
std::string file_name = file.stem().string();
|
||||
// don't propose tmp / hidden files in freqman's list
|
||||
if (file_name.length() && file_name[0] != '.') {
|
||||
file_list.emplace_back(file_name);
|
||||
file_list.emplace_back(std::move(file_name));
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -81,14 +84,11 @@ void FreqManBaseView::change_category(int32_t category_id) {
|
||||
|
||||
if (file_list.empty()) return;
|
||||
|
||||
std::vector<freqman_entry>().swap(database);
|
||||
|
||||
if (!load_freqman_file(file_list[categories[category_id].second], database)) {
|
||||
if (!load_freqman_file(file_list[categories[category_id].second], database, {})) {
|
||||
error_ = ERROR_ACCESS;
|
||||
}
|
||||
menu_view.set_db(database);
|
||||
freqlist_view.set_db(database);
|
||||
text_empty.hidden(!database.empty());
|
||||
menu_view.set_dirty();
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
@@ -116,13 +116,13 @@ void FrequencySaveView::save_current_file() {
|
||||
|
||||
void FrequencySaveView::on_save_name() {
|
||||
text_prompt(nav_, desc_buffer, 28, [this](std::string& buffer) {
|
||||
database.push_back({value_, 0, buffer, SINGLE});
|
||||
database.push_back(std::make_unique<freqman_entry>(freqman_entry{value_, 0, buffer, freqman_type::Single}));
|
||||
save_current_file();
|
||||
});
|
||||
}
|
||||
|
||||
void FrequencySaveView::on_save_timestamp() {
|
||||
database.push_back({value_, 0, live_timestamp.string(), SINGLE});
|
||||
database.push_back(std::make_unique<freqman_entry>(freqman_entry{value_, 0, live_timestamp.string(), freqman_type::Single}));
|
||||
save_current_file();
|
||||
}
|
||||
|
||||
@@ -163,9 +163,9 @@ FrequencySaveView::FrequencySaveView(
|
||||
}
|
||||
|
||||
void FrequencyLoadView::refresh_widgets(const bool v) {
|
||||
menu_view.hidden(v);
|
||||
freqlist_view.hidden(v);
|
||||
text_empty.hidden(!v);
|
||||
// display.fill_rectangle(menu_view.screen_rect(), Color::black());
|
||||
// display.fill_rectangle(freqlist_view.screen_rect(), Color::black());
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
@@ -176,41 +176,40 @@ FrequencyLoadView::FrequencyLoadView(
|
||||
refresh_widgets(v);
|
||||
};
|
||||
|
||||
add_children({&menu_view,
|
||||
add_children({&freqlist_view,
|
||||
&text_empty});
|
||||
|
||||
// Resize menu view to fill screen
|
||||
menu_view.set_parent_rect({0, 3 * 8, 240, 30 * 8});
|
||||
freqlist_view.set_parent_rect({0, 3 * 8, 240, 30 * 8});
|
||||
|
||||
menu_view.on_select = [&nav, this](FreqManUIList&) {
|
||||
nav_.pop();
|
||||
|
||||
auto& entry = database[menu_view.get_index()];
|
||||
|
||||
if (entry.type == RANGE) {
|
||||
// User chose a frequency range entry
|
||||
freqlist_view.on_select = [&nav, this](FreqManUIList&) {
|
||||
auto& entry = database[freqlist_view.get_index()];
|
||||
if (entry->type == freqman_type::Range) {
|
||||
if (on_range_loaded)
|
||||
on_range_loaded(entry.frequency_a, entry.frequency_b);
|
||||
on_range_loaded(entry->frequency_a, entry->frequency_b);
|
||||
else if (on_frequency_loaded)
|
||||
on_frequency_loaded(entry.frequency_a);
|
||||
// TODO: Maybe return center of range if user choses a range when the app needs a unique frequency, instead of frequency_a ?
|
||||
on_frequency_loaded(entry->frequency_a);
|
||||
// TODO: Maybe return center of range if user choses a range when the app
|
||||
// needs a unique frequency, instead of frequency_a?
|
||||
// TODO: HamRadio?
|
||||
} else {
|
||||
// User chose an unique frequency entry
|
||||
if (on_frequency_loaded)
|
||||
on_frequency_loaded(entry.frequency_a);
|
||||
on_frequency_loaded(entry->frequency_a);
|
||||
}
|
||||
|
||||
nav_.pop(); // NB: this will call dtor.
|
||||
};
|
||||
}
|
||||
|
||||
void FrequencyManagerView::on_edit_freq(rf::Frequency f) {
|
||||
database[menu_view.get_index()].frequency_a = f;
|
||||
database[freqlist_view.get_index()]->frequency_a = f;
|
||||
save_freqman_file(file_list[categories[current_category_id].second], database);
|
||||
change_category(current_category_id);
|
||||
}
|
||||
|
||||
void FrequencyManagerView::on_edit_desc(NavigationView& nav) {
|
||||
text_prompt(nav, desc_buffer, 28, [this](std::string& buffer) {
|
||||
database[menu_view.get_index()].description = buffer;
|
||||
database[freqlist_view.get_index()]->description = std::move(buffer);
|
||||
save_freqman_file(file_list[categories[current_category_id].second], database);
|
||||
change_category(current_category_id);
|
||||
});
|
||||
@@ -230,7 +229,7 @@ void FrequencyManagerView::on_delete() {
|
||||
delete_freqman_file(file_list[categories[current_category_id].second]);
|
||||
refresh_list();
|
||||
} else {
|
||||
database.erase(database.begin() + menu_view.get_index());
|
||||
database.erase(database.begin() + freqlist_view.get_index());
|
||||
save_freqman_file(file_list[categories[current_category_id].second], database);
|
||||
}
|
||||
change_category(current_category_id);
|
||||
@@ -241,10 +240,9 @@ void FrequencyManagerView::refresh_widgets(const bool v) {
|
||||
button_edit_desc.hidden(v);
|
||||
button_delete.hidden(v);
|
||||
text_empty.hidden(!v);
|
||||
menu_view.hidden(v);
|
||||
menu_view.set_dirty();
|
||||
freqlist_view.hidden(v);
|
||||
labels.hidden(v);
|
||||
// display.fill_rectangle(menu_view.screen_rect(), Color::black());
|
||||
// display.fill_rectangle(freqlist_view.screen_rect(), Color::black());
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
@@ -261,13 +259,13 @@ FrequencyManagerView::FrequencyManagerView(
|
||||
|
||||
add_children({&labels,
|
||||
&button_new_category,
|
||||
&menu_view,
|
||||
&freqlist_view,
|
||||
&text_empty,
|
||||
&button_edit_freq,
|
||||
&button_edit_desc,
|
||||
&button_delete});
|
||||
|
||||
menu_view.on_select = [this](FreqManUIList&) {
|
||||
freqlist_view.on_select = [this](FreqManUIList&) {
|
||||
button_edit_freq.focus();
|
||||
};
|
||||
|
||||
@@ -277,20 +275,20 @@ FrequencyManagerView::FrequencyManagerView(
|
||||
};
|
||||
|
||||
button_edit_freq.on_select = [this, &nav](Button&) {
|
||||
if (database.empty()) {
|
||||
database.push_back({0, 0, "", SINGLE});
|
||||
}
|
||||
auto new_view = nav.push<FrequencyKeypadView>(database[menu_view.get_index()].frequency_a);
|
||||
if (database.empty())
|
||||
database.push_back(std::make_unique<freqman_entry>(freqman_entry{0, 0, "", freqman_type::Single}));
|
||||
|
||||
auto new_view = nav.push<FrequencyKeypadView>(database[freqlist_view.get_index()]->frequency_a);
|
||||
new_view->on_changed = [this](rf::Frequency f) {
|
||||
on_edit_freq(f);
|
||||
};
|
||||
};
|
||||
|
||||
button_edit_desc.on_select = [this, &nav](Button&) {
|
||||
if (database.empty()) {
|
||||
database.push_back({0, 0, "", SINGLE});
|
||||
}
|
||||
desc_buffer = database[menu_view.get_index()].description;
|
||||
if (database.empty())
|
||||
database.push_back(std::make_unique<freqman_entry>(freqman_entry{0, 0, "", freqman_type::Single}));
|
||||
|
||||
desc_buffer = database[freqlist_view.get_index()]->description;
|
||||
on_edit_desc(nav);
|
||||
};
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ class FreqManBaseView : public View {
|
||||
14,
|
||||
{}};
|
||||
|
||||
FreqManUIList menu_view{
|
||||
FreqManUIList freqlist_view{
|
||||
{0, 3 * 8, 240, 23 * 8}};
|
||||
|
||||
Text text_empty{
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
#include "ui_freqman.hpp"
|
||||
|
||||
#include "baseband_api.hpp"
|
||||
#include "cpld_update.hpp"
|
||||
#include "string_format.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
@@ -180,8 +179,7 @@ void JammerView::focus() {
|
||||
|
||||
JammerView::~JammerView() {
|
||||
transmitter_model.disable();
|
||||
hackrf::cpld::load_sram_no_verify(); // to leave all RX ok, without ghost signal problem at the exit .
|
||||
baseband::shutdown(); // better this function at the end, not load_sram() that sometimes produces hang up.
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
void JammerView::on_retune(const rf::Frequency freq, const uint32_t range) {
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
#include "ui_keyfob.hpp"
|
||||
|
||||
#include "baseband_api.hpp"
|
||||
#include "cpld_update.hpp"
|
||||
#include "string_format.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
@@ -141,8 +140,7 @@ KeyfobView::~KeyfobView() {
|
||||
settings.save("tx_keyfob", &app_settings);
|
||||
|
||||
transmitter_model.disable();
|
||||
hackrf::cpld::load_sram_no_verify(); // to leave all RX ok, without ghost signal problem at the exit .
|
||||
baseband::shutdown(); // better this function at the end, not load_sram() that sometimes produces hang up.
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
void KeyfobView::update_progress(const uint32_t progress) {
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
#include "lcr.hpp"
|
||||
#include "baseband_api.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "cpld_update.hpp"
|
||||
|
||||
#include "serializer.hpp"
|
||||
|
||||
@@ -40,8 +39,7 @@ void LCRView::focus() {
|
||||
|
||||
LCRView::~LCRView() {
|
||||
transmitter_model.disable();
|
||||
hackrf::cpld::load_sram_no_verify(); // to leave all RX ok, without ghost signal problem at the exit.
|
||||
baseband::shutdown(); // better this function at the end, not load_sram() that sometimes produces hang up.
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include "file.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
using namespace tonekey;
|
||||
using portapack::memory::map::backup_ram;
|
||||
|
||||
namespace ui {
|
||||
@@ -199,41 +200,33 @@ size_t LevelView::change_mode(freqman_index_t new_mod) {
|
||||
field_bw.on_change = [this](size_t, OptionsField::value_t n) { receiver_model.set_wfm_configuration(n); };
|
||||
text_ctcss.set(" ");
|
||||
break;
|
||||
case SPEC_MODULATION:
|
||||
freqman_set_bandwidth_option(new_mod, field_bw);
|
||||
baseband::run_image(portapack::spi_flash::image_tag_capture);
|
||||
receiver_model.set_modulation(ReceiverModel::Mode::Capture);
|
||||
field_bw.set_by_value(0);
|
||||
field_bw.on_change = [this](size_t, OptionsField::value_t sampling_rate) {
|
||||
auto anti_alias_baseband_bandwidth_filter = filter_bandwidth_for_sampling_rate(sampling_rate);
|
||||
receiver_model.set_sampling_rate(sampling_rate);
|
||||
receiver_model.set_baseband_bandwidth(anti_alias_baseband_bandwidth_filter);
|
||||
};
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (new_mod != SPEC_MODULATION) {
|
||||
// reset receiver model to fix bug when going from SPEC to audio, the sound is distorded
|
||||
receiver_model.set_sampling_rate(3072000);
|
||||
receiver_model.set_baseband_bandwidth(1750000);
|
||||
}
|
||||
|
||||
return step_mode.selected_index();
|
||||
}
|
||||
|
||||
void LevelView::handle_coded_squelch(const uint32_t value) {
|
||||
static int32_t last_idx = -1;
|
||||
|
||||
float diff, min_diff = value;
|
||||
size_t min_idx{0};
|
||||
size_t c;
|
||||
|
||||
if (field_mode.selected_index() != NFM_MODULATION) {
|
||||
text_ctcss.set(" ");
|
||||
return;
|
||||
}
|
||||
|
||||
// Find nearest match
|
||||
for (c = 0; c < tone_keys.size(); c++) {
|
||||
diff = abs(((float)value / 100.0) - tone_keys[c].second);
|
||||
if (diff < min_diff) {
|
||||
min_idx = c;
|
||||
min_diff = diff;
|
||||
}
|
||||
}
|
||||
|
||||
// Arbitrary confidence threshold
|
||||
if (last_idx < 0 || (unsigned)last_idx != min_idx) {
|
||||
last_idx = min_idx;
|
||||
if (min_diff < 40)
|
||||
text_ctcss.set("T: " + tone_keys[min_idx].first);
|
||||
else
|
||||
text_ctcss.set(" ");
|
||||
}
|
||||
if (field_mode.selected_index() == NFM_MODULATION)
|
||||
text_ctcss.set(tone_key_string_by_value(value, text_ctcss.parent_rect().width() / 8));
|
||||
else
|
||||
text_ctcss.set(" ");
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -91,7 +91,7 @@ class LevelView : public View {
|
||||
|
||||
OptionsField field_mode{
|
||||
{15 * 8, 1 * 16},
|
||||
3,
|
||||
4,
|
||||
{}};
|
||||
|
||||
OptionsField step_mode{
|
||||
@@ -114,7 +114,7 @@ class LevelView : public View {
|
||||
}};
|
||||
|
||||
Text text_ctcss{
|
||||
{22 * 8, 3 * 16 + 4, 14 * 8, 1 * 8},
|
||||
{22 * 8, 3 * 16 + 4, 8 * 8, 1 * 8},
|
||||
""};
|
||||
|
||||
// RSSI: XX/XX/XXX,dt: XX
|
||||
|
||||
@@ -30,7 +30,7 @@ using namespace portapack;
|
||||
|
||||
namespace ui {
|
||||
void GlassView::focus() {
|
||||
button_marker.focus();
|
||||
range_presets.focus();
|
||||
}
|
||||
|
||||
GlassView::~GlassView() {
|
||||
@@ -471,9 +471,9 @@ GlassView::GlassView(
|
||||
|
||||
button_marker.on_select = [this](ButtonWithEncoder&) {
|
||||
receiver_model.set_target_frequency(marker); // Center tune rx in marker freq.
|
||||
receiver_model.set_frequency_step(MHZ_DIV); // Preset a 1 MHz frequency step into RX -> AUDIO
|
||||
nav_.pop();
|
||||
nav_.push<AnalogAudioView>(); // Jump into audio view
|
||||
auto settings = receiver_model.settings();
|
||||
settings.frequency_step = MHZ_DIV; // Preset a 1 MHz frequency step into RX -> AUDIO
|
||||
nav_.replace<AnalogAudioView>(settings); // Jump into audio view
|
||||
};
|
||||
|
||||
field_trigger.on_change = [this](int32_t v) {
|
||||
@@ -495,9 +495,9 @@ GlassView::GlassView(
|
||||
|
||||
button_jump.on_select = [this](Button&) {
|
||||
receiver_model.set_target_frequency(max_freq_hold); // Center tune rx in marker freq.
|
||||
receiver_model.set_frequency_step(MHZ_DIV); // Preset a 1 MHz frequency step into RX -> AUDIO
|
||||
nav_.pop();
|
||||
nav_.push<AnalogAudioView>(); // Jump into audio view
|
||||
auto settings = receiver_model.settings();
|
||||
settings.frequency_step = MHZ_DIV; // Preset a 1 MHz frequency step into RX -> AUDIO
|
||||
nav_.replace<AnalogAudioView>(settings); // Jump into audio view
|
||||
};
|
||||
|
||||
button_rst.on_select = [this](Button&) {
|
||||
@@ -513,7 +513,6 @@ GlassView::GlassView(
|
||||
marker_pixel_index = 120;
|
||||
on_range_changed();
|
||||
|
||||
receiver_model.set_modulation(ReceiverModel::Mode::SpectrumAnalysis);
|
||||
receiver_model.set_sampling_rate(looking_glass_sampling_rate); // 20mhz
|
||||
receiver_model.set_baseband_bandwidth(looking_glass_bandwidth); // possible values: 1.75/2.5/3.5/5/5.5/6/7/8/9/10/12/14/15/20/24/28MHz
|
||||
receiver_model.set_squelch_level(0);
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "portapack.hpp"
|
||||
#include "app_settings.hpp"
|
||||
#include "baseband_api.hpp"
|
||||
#include "radio_state.hpp"
|
||||
#include "receiver_model.hpp"
|
||||
@@ -62,7 +63,7 @@ class GlassView : public View {
|
||||
GlassView& operator=(const GlassView& nav);
|
||||
|
||||
~GlassView();
|
||||
std::string title() const override { return "LookingGlass"; };
|
||||
std::string title() const override { return "Looking Glass"; };
|
||||
|
||||
void on_show() override;
|
||||
void on_hide() override;
|
||||
@@ -70,7 +71,9 @@ class GlassView : public View {
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
RxRadioState radio_state_{};
|
||||
RxRadioState radio_state_{ReceiverModel::Mode::SpectrumAnalysis};
|
||||
app_settings::SettingsManager settings_{
|
||||
"rx_glass", app_settings::Mode::RX};
|
||||
|
||||
struct preset_entry {
|
||||
rf::Frequency min{};
|
||||
|
||||
@@ -22,22 +22,20 @@
|
||||
|
||||
#include "ui_mictx.hpp"
|
||||
|
||||
#include "baseband_api.hpp"
|
||||
#include "audio.hpp"
|
||||
|
||||
#include "wm8731.hpp"
|
||||
using wolfson::wm8731::WM8731;
|
||||
|
||||
#include "tonesets.hpp"
|
||||
#include "portapack_hal.hpp"
|
||||
#include "cpld_update.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "baseband_api.hpp"
|
||||
#include "irq_controls.hpp"
|
||||
#include "portapack_hal.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "tonesets.hpp"
|
||||
#include "ui_tone_key.hpp"
|
||||
#include "wm8731.hpp"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
using namespace tonekey;
|
||||
using namespace portapack;
|
||||
using wolfson::wm8731::WM8731;
|
||||
|
||||
WM8731 audio_codec_wm8731{i2c0, 0x1a};
|
||||
|
||||
@@ -146,25 +144,25 @@ void MicTXView::rxaudio(bool is_on) {
|
||||
audio::input::stop();
|
||||
baseband::shutdown();
|
||||
|
||||
if (enable_am || enable_usb || enable_lsb || enable_dsb) { // "NFM/FM",0 ," WFM ",1 , " AM ",2, " USB ", 3, " LSB ",4, " DSB-SC", 5
|
||||
if (enable_am || enable_usb || enable_lsb || enable_dsb) { // "NFM/FM", 0, " WFM ", 1, " AM ", 2, " USB ", 3, " LSB ", 4, " DSB-SC", 5
|
||||
baseband::run_image(portapack::spi_flash::image_tag_am_audio);
|
||||
receiver_model.set_modulation(ReceiverModel::Mode::AMAudio); // that AM demodulation engine is common to all Amplitude mod : AM/USB/LSB/DSB (2,3,4,5)
|
||||
if (options_mode.selected_index() < 5) // We will be called here with 2,3,4,5 . We treat here demod. filter 2,3,4; (excluding DSB-C case (5) it is treated more down).
|
||||
receiver_model.set_am_configuration(options_mode.selected_index() - 1); // selecting proper filter(2,3,4). 2-1=1=>6k-AM(1) , 3-1=2=>+3k-USB(2), 4-1=3=>-3K-LSB(3),
|
||||
} else { // We are in NFM/FM or WFM (NFM BW:8k5 or 11k / FM BW 16k / WFM BW:200k)
|
||||
if (options_mode.selected_index() < 5) // We will be called here with 2,3,4,5. We treat here demod. filter 2,3,4; (excluding DSB-C case (5) it is treated more down).
|
||||
receiver_model.set_am_configuration(options_mode.selected_index() - 1); // selecting proper filter(2,3,4). 2-1=1=>6k-AM(1), 3-1=2=>+3k-USB(2), 4-1=3=>-3K-LSB(3),
|
||||
} 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 , or the two new addional BW filters (180k, 40k)
|
||||
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(n); // it is called above , depending user's selection (200k, 180k,40k).
|
||||
} else { // NFM BW:8k5 or 11k / FM BW 16k
|
||||
// receiver_model.set_wfm_configuration(n); // it is called above, depending user's selection (200k, 180k, 0k).
|
||||
} 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 user's selection (8k5, 11k, 16k).
|
||||
// receiver_model.set_nbfm_configuration(n); is called above, depending user's selection (8k5, 11k, 16k).
|
||||
}
|
||||
}
|
||||
// receiver_model.set_target_frequency(field_frequency.value()); //probably this too can be commented out.
|
||||
if (bool_same_F_tx_rx_enabled) // when stop TX ,define to which freq RX we return
|
||||
|
||||
if (bool_same_F_tx_rx_enabled) // when stop TX, define to which freq RX we return
|
||||
receiver_model.set_target_frequency(tx_frequency); // Update freq also for RX = TX
|
||||
else
|
||||
receiver_model.set_target_frequency(rx_frequency); // Now with separate freq controls!
|
||||
@@ -172,7 +170,6 @@ void MicTXView::rxaudio(bool is_on) {
|
||||
receiver_model.set_vga(rx_vga);
|
||||
receiver_model.set_rf_amp(rx_amp);
|
||||
receiver_model.enable();
|
||||
hackrf::cpld::load_sram_no_verify(); // to have a good RX without any ghost inside Mic App
|
||||
audio::output::start();
|
||||
} else { // These incredibly convoluted steps are required for the vumeter to reappear when stopping RX.
|
||||
receiver_model.set_modulation(ReceiverModel::Mode::NarrowbandFMAudio); // This fixes something with AM RX...
|
||||
@@ -198,7 +195,7 @@ MicTXView::MicTXView(
|
||||
baseband::run_image(portapack::spi_flash::image_tag_mic_tx);
|
||||
|
||||
if (audio::debug::codec_name() == "WM8731") {
|
||||
add_children({&labels_WM8731, // we have audio codec WM8731, same MIC menu as original.
|
||||
add_children({&labels_WM8731, // we have audio codec WM8731, same MIC menu as original.
|
||||
&vumeter,
|
||||
&options_gain, // MIC GAIN float factor on the GUI.
|
||||
&options_wm8731_boost_mode,
|
||||
@@ -262,7 +259,7 @@ MicTXView::MicTXView(
|
||||
mic_gain = v / 10.0;
|
||||
configure_baseband();
|
||||
};
|
||||
options_gain.set_selected_index(1); // x1.0 preselected default.
|
||||
options_gain.set_selected_index(1); // x1.0 preselected default.
|
||||
|
||||
if (audio::debug::codec_name() == "WM8731") {
|
||||
options_wm8731_boost_mode.on_change = [this](size_t, int8_t v) {
|
||||
@@ -270,30 +267,30 @@ MicTXView::MicTXView(
|
||||
case 0: // +12 dB’s respect reference level orig fw 1.5.x fw FM : when +20dB's boost ON) and shift bits (>>8),
|
||||
shift_bits_s16 = 6; // now mic-boost on (+20dBs) and shift bits (>>6), +20+12=32 dB’s (orig fw +20 dBs+ 0dBs)=> +12dB's respect ref.
|
||||
break;
|
||||
case 1: // +06 dB’s reference level , (when +20dB's boost ON)
|
||||
case 1: // +06 dB’s reference level, (when +20dB's boost ON)
|
||||
shift_bits_s16 = 7; // now mic-boost on (+20dBs) and shift bits (>>7), +20+06=26 dB’s (orig fw +20 dBs+ 0dBs) => +06dB's respect ref.
|
||||
break;
|
||||
case 2:
|
||||
shift_bits_s16 = 4; // +04 dB’s respect ref level , (when +20dB's boost OFF)
|
||||
shift_bits_s16 = 4; // +04 dB’s respect ref level, (when +20dB's boost OFF)
|
||||
break; // now mic-boost off (+00dBs) shift bits (4) (+0+24dB's)=24 dBs => +04dB's respect ref.
|
||||
case 3:
|
||||
shift_bits_s16 = 5; // -02 dB’s respect ref level , (when +20dB's boost OFF)
|
||||
shift_bits_s16 = 5; // -02 dB’s respect ref level, (when +20dB's boost OFF)
|
||||
break; // now mic-boost off (+00dBs) shift bits (5) (+0+18dB's)=18 dBs => -02dB's respect ref.
|
||||
case 4:
|
||||
shift_bits_s16 = 6; // -08 dB’s respect ref level , (when +20dB's boost OFF)
|
||||
shift_bits_s16 = 6; // -08 dB’s respect ref level, (when +20dB's boost OFF)
|
||||
break; // now mic-boost off (+00dBs) shift bits (6) (+0+12dB's)=12 dBs => -08dB's respect ref.
|
||||
}
|
||||
ak4951_alc_and_wm8731_boost_GUI = v; // 0,..4 WM8731_boost dB's options, (combination boost on/off , and effective gain in captured data >>x)
|
||||
audio::input::start(ak4951_alc_and_wm8731_boost_GUI); // Detected (WM8731) , set up the proper wm_boost on/off , 0..4 (0,1) boost_on , (2,3,4) boost_0ff
|
||||
configure_baseband(); // to update in real time, sending msg , var-parameters >>shift_bits FM msg ,to audio_tx from M0 to M4 Proc -
|
||||
ak4951_alc_and_wm8731_boost_GUI = v; // 0..4, WM8731_boost dB's options, (combination boost on/off, and effective gain in captured data >>x)
|
||||
audio::input::start(ak4951_alc_and_wm8731_boost_GUI); // Detected (WM8731), set up the proper wm_boost on/off, 0..4 (0,1) boost_on, (2,3,4) boost_off
|
||||
configure_baseband(); // to update in real time, sending msg, var-parameters >>shift_bits FM msg, to audio_tx from M0 to M4 Proc -
|
||||
};
|
||||
options_wm8731_boost_mode.set_selected_index(3); // preset GUI index 3 as default WM -> -02 dB's .
|
||||
options_wm8731_boost_mode.set_selected_index(3); // preset GUI index 3 as default WM -> -02 dB's.
|
||||
} else {
|
||||
shift_bits_s16 = 8; // Initialized default fixed >>8_FM for FM tx mod , shift audio data for AK4951 ,using top 8 bits s16 data (>>8)
|
||||
shift_bits_s16 = 8; // Initialized default fixed >>8_FM for FM tx mod, shift audio data for AK4951, using top 8 bits s16 data (>>8)
|
||||
options_ak4951_alc_mode.on_change = [this](size_t, int8_t v) {
|
||||
ak4951_alc_and_wm8731_boost_GUI = v; // 0,..11, AK4951 Mic -Automatic volume Level Control options,
|
||||
ak4951_alc_and_wm8731_boost_GUI = v; // 0..11, AK4951 Mic -Automatic volume Level Control options,
|
||||
audio::input::start(ak4951_alc_and_wm8731_boost_GUI); // Detected (AK4951) ==> Set up proper ALC mode from 0..11 options
|
||||
configure_baseband(); // sending fixed >>8_FM , var-parameters msg , to audiotx from this M0 to M4 process.
|
||||
configure_baseband(); // sending fixed >>8_FM, var-parameters msg, to audiotx from this M0 to M4 process.
|
||||
};
|
||||
}
|
||||
|
||||
@@ -331,11 +328,11 @@ MicTXView::MicTXView(
|
||||
field_bw.on_change = [this](uint32_t v) {
|
||||
transmitter_model.set_channel_bandwidth(v * 1000);
|
||||
};
|
||||
field_bw.set_value(10); // pre-default first time, TX deviation FM for NFM / FM
|
||||
field_bw.set_value(10); // pre-default first time, TX deviation FM for NFM / FM
|
||||
|
||||
// now , no need direct update , field_rfgain , field_rfamp (it is done in ui_transmitter.cpp)
|
||||
// now, no need direct update, field_rfgain, field_rfamp (it is done in ui_transmitter.cpp)
|
||||
|
||||
options_mode.on_change = [this](size_t, int32_t v) { //{ "NFM/FM", 0 }, { " WFM ", 1 },{ "AM", 2 },{ "USB", 3 },{ "LSB", 4 },{ "DSB", 5 }
|
||||
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;
|
||||
enable_lsb = false;
|
||||
@@ -344,7 +341,7 @@ MicTXView::MicTXView(
|
||||
|
||||
using option_t = std::pair<std::string, int32_t>;
|
||||
using options_t = std::vector<option_t>;
|
||||
options_t rxbw; // Aux structure to change dynamically field_rxbw contents,
|
||||
options_t rxbw; // Aux structure to change dynamically field_rxbw contents,
|
||||
|
||||
switch (v) {
|
||||
case 0: //{ "FM", 0 }
|
||||
@@ -356,15 +353,15 @@ MicTXView::MicTXView(
|
||||
// field_bw.set_value(transmitter_model.channel_bandwidth() / 1000);
|
||||
// if (rx_enabled)
|
||||
rxaudio(rx_enabled); // Update now if we have RX audio on
|
||||
options_tone_key.hidden(0); // we are in FM mode , we should have active the Key-tones & CTCSS option.
|
||||
options_tone_key.hidden(0); // we are in FM mode, we should have active the Key-tones & CTCSS option.
|
||||
|
||||
rxbw.emplace_back(" NFM1:8k5 ", 0); // restore the original dynamic field_rxbw value.
|
||||
rxbw.emplace_back(" NFM2:11k ", 1);
|
||||
rxbw.emplace_back(" FM :16k ", 2);
|
||||
field_rxbw.set_options(rxbw); // store that aux GUI option to the field_rxbw.
|
||||
|
||||
field_rxbw.hidden(0); // we are in FM mode, we need to allow the user set up of the RX NFM BW selection (8K5, 11K, 16K)
|
||||
field_bw.hidden(0); // we are in FM mode, we need to allow FM deviation parameter , in non FM mode.
|
||||
field_rxbw.hidden(0); // we are in FM mode, we need to allow the user set up of the RX NFM BW selection (8K5, 11K, 16K)
|
||||
field_bw.hidden(0); // we are in FM mode, we need to allow FM deviation parameter, in non FM mode.
|
||||
break;
|
||||
case 1: //{ "WFM", 1 }
|
||||
enable_am = false;
|
||||
@@ -376,38 +373,38 @@ MicTXView::MicTXView(
|
||||
// field_bw.set_value(transmitter_model.channel_bandwidth() / 1000);
|
||||
// if (rx_enabled)
|
||||
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.
|
||||
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); // We allow the user selection of the 3 x WFM BW filters, (0) WFM-200K, (1) WFM-180K , (2) WFM-40K .
|
||||
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 filter .
|
||||
field_bw.hidden(0); // we are in WFM mode, we need to allow WFM deviation parameter , in non FM mode.
|
||||
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 }
|
||||
enable_am = true;
|
||||
rxaudio(rx_enabled); // Update now if we have RX audio on
|
||||
options_tone_key.set_selected_index(0); // we are NOT in FM mode , we reset the possible previous key-tones &CTCSS selection.
|
||||
options_tone_key.set_selected_index(0); // we are NOT in FM mode, we reset the possible previous key-tones &CTCSS selection.
|
||||
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.
|
||||
|
||||
field_rxbw.hidden(0); // we show fixed RX AM BW 6Khz
|
||||
field_bw.hidden(1); // we hide the FM TX deviation parameter , in non FM mode.
|
||||
field_rxbw.hidden(0); // we show fixed RX AM BW 6Khz
|
||||
field_bw.hidden(1); // we hide the FM TX deviation parameter, in non FM mode.
|
||||
check_rogerbeep.hidden(0); // make visible again the "rogerbeep" selection.
|
||||
break;
|
||||
case 3: //{ "USB", 3 }
|
||||
enable_usb = true;
|
||||
rxaudio(rx_enabled); // Update now if we have RX audio on
|
||||
check_rogerbeep.set_value(false); // reset the possible activation of roger beep, because it is not compatible with SSB , by now.
|
||||
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
|
||||
@@ -415,10 +412,10 @@ MicTXView::MicTXView(
|
||||
case 4: //{ "LSB", 4 }
|
||||
enable_lsb = true;
|
||||
rxaudio(rx_enabled); // Update now if we have RX audio on
|
||||
check_rogerbeep.set_value(false); // reset the possible activation of roger beep, because it is not compatible with SSB , by now.
|
||||
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
|
||||
@@ -428,7 +425,7 @@ MicTXView::MicTXView(
|
||||
rxaudio(rx_enabled); // Update now if we have RX audio on
|
||||
check_rogerbeep.hidden(0); // make visible again the "rogerbeep" selection.
|
||||
|
||||
rxbw.emplace_back("SSB1:USB+3k", 0); // added dynamically two options (index 0,1) to that DSB-C case to the field_rxbw value.
|
||||
rxbw.emplace_back("SSB1:USB+3k", 0); // added dynamically two options (index 0,1) to that DSB-C case to the field_rxbw value.
|
||||
rxbw.emplace_back("SSB2:LSB-3k", 1);
|
||||
|
||||
field_rxbw.set_options(rxbw); // store that aux GUI option to the field_rxbw.
|
||||
@@ -481,7 +478,7 @@ MicTXView::MicTXView(
|
||||
|
||||
check_common_freq_tx_rx.on_select = [this](Checkbox&, bool v) {
|
||||
bool_same_F_tx_rx_enabled = v;
|
||||
field_rxfrequency.hidden(v); // Hide or show separated freq RX field . (When no hide user can enter down indep. freq for RX)
|
||||
field_rxfrequency.hidden(v); // Hide or show separated freq RX field. (When no hide user can enter down indep. freq for RX)
|
||||
set_dirty(); // Refresh GUI interface
|
||||
receiver_model.set_target_frequency(v ? tx_frequency : rx_frequency); // To go to the proper tuned freq. when toggling it
|
||||
};
|
||||
@@ -512,17 +509,17 @@ MicTXView::MicTXView(
|
||||
|
||||
field_rxbw.on_change = [this](size_t, int32_t v) {
|
||||
if (!(enable_am || enable_usb || enable_lsb || enable_dsb || enable_wfm)) {
|
||||
// In Previous fw versions, that nbfm_configuration(n) was done in any mode (FM/AM/SSB/DSB)...strictly speaking only need it in (NFM/FM)
|
||||
receiver_model.set_nbfm_configuration(v); // we are in NFM/FM case, we need to select proper NFM/FM RX channel filter , NFM BW 8K5(0), NFM BW 11K(1) , FM BW 16K (2)
|
||||
} 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),
|
||||
// In Previous fw versions, that nbfm_configuration(n) was done in any mode (FM/AM/SSB/DSB)...strictly speaking only need it in (NFM/FM)
|
||||
receiver_model.set_nbfm_configuration(v); // we are in NFM/FM case, we need to select proper NFM/FM RX channel filter, NFM BW 8K5(0), NFM BW 11K(1), FM BW 16K (2)
|
||||
} 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),
|
||||
}
|
||||
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_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)
|
||||
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)
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -597,20 +594,55 @@ MicTXView::MicTXView(
|
||||
}
|
||||
};
|
||||
|
||||
// These shouldn't be necessary, but because
|
||||
// this app uses both transmitter_model and directly
|
||||
// configures the baseband, these end up being required.
|
||||
transmitter_model.set_sampling_rate(sampling_rate);
|
||||
transmitter_model.set_baseband_bandwidth(1750000);
|
||||
|
||||
set_tx(false);
|
||||
|
||||
audio::set_rate(audio::Rate::Hz_24000);
|
||||
audio::input::start(ak4951_alc_and_wm8731_boost_GUI); // When detected AK4951 => set up ALC mode; when detected WM8731 => set up mic_boost ON/OFF.
|
||||
}
|
||||
|
||||
MicTXView::MicTXView(
|
||||
NavigationView& nav,
|
||||
ReceiverModel::settings_t override)
|
||||
: MicTXView(nav) {
|
||||
// Try to use the modulation/bandwidth from RX settings.
|
||||
// TODO: These concepts should be merged so there's only one.
|
||||
// TODO: enums/constants for these indexes.
|
||||
switch (override.mode) {
|
||||
case ReceiverModel::Mode::AMAudio:
|
||||
options_mode.set_selected_index(2);
|
||||
break;
|
||||
case ReceiverModel::Mode::NarrowbandFMAudio:
|
||||
options_mode.set_selected_index(0);
|
||||
break;
|
||||
case ReceiverModel::Mode::WidebandFMAudio:
|
||||
options_mode.set_selected_index(1);
|
||||
break;
|
||||
|
||||
// Unsupported modulations.
|
||||
case ReceiverModel::Mode::SpectrumAnalysis:
|
||||
case ReceiverModel::Mode::Capture:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO: bandwidth selection is tied too tightly to the UI
|
||||
// controls. It's not possible to set the bandwidth here without
|
||||
// refactoring. Also options_mode seems to have a category error.
|
||||
}
|
||||
|
||||
MicTXView::~MicTXView() {
|
||||
audio::input::stop();
|
||||
transmitter_model.set_target_frequency(tx_frequency); // Save Tx frequency instead of Rx. Or maybe we need some "System Wide" changes to seperate Tx and Rx frequency.
|
||||
transmitter_model.disable();
|
||||
if (rx_enabled) // Also turn off audio rx if enabled
|
||||
rxaudio(false);
|
||||
hackrf::cpld::load_sram_no_verify(); // to leave all RX ok, without ghost signal problem at the exit .
|
||||
baseband::shutdown(); // better this function at the end, not load_sram() that sometimes produces hang up.
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
// TODO: Consolidate Modulation/Bandwidth modes/settings with freqman/receiver_model.
|
||||
|
||||
#ifndef __UI_MICTX_H__
|
||||
#define __UI_MICTX_H__
|
||||
|
||||
@@ -33,7 +35,6 @@
|
||||
#include "ui_navigation.hpp"
|
||||
#include "ui_receiver.hpp"
|
||||
#include "transmitter_model.hpp"
|
||||
#include "tone_key.hpp"
|
||||
#include "message.hpp"
|
||||
#include "receiver_model.hpp"
|
||||
#include "ui_transmitter.hpp"
|
||||
@@ -43,6 +44,7 @@ namespace ui {
|
||||
class MicTXView : public View {
|
||||
public:
|
||||
MicTXView(NavigationView& nav);
|
||||
MicTXView(NavigationView& nav, ReceiverModel::settings_t override);
|
||||
~MicTXView();
|
||||
|
||||
MicTXView(const MicTXView&) = delete;
|
||||
@@ -117,18 +119,19 @@ class MicTXView : public View {
|
||||
uint8_t shift_bits_s16{4}; // shift bits factor to the captured ADC S16 audio sample.
|
||||
|
||||
// AM TX Stuff
|
||||
// TODO: Some of this stuff is mutually exclusive. Need a better representation.
|
||||
bool enable_am{false};
|
||||
bool enable_dsb{false};
|
||||
bool enable_usb{false};
|
||||
bool enable_lsb{false};
|
||||
bool enable_wfm{false}; // added to distinguish in the FM mode , RX BW : NFM (8K5, 11K), FM (16K), WFM(200K)
|
||||
bool enable_wfm{false}; // added to distinguish in the FM mode, RX BW : NFM (8K5, 11K), FM (16K), WFM(200K)
|
||||
|
||||
Labels labels_WM8731{
|
||||
{{3 * 8, 1 * 8}, "MIC-GAIN:", Color::light_grey()},
|
||||
{{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
|
||||
{{18 * 8, (5 * 8)}, "Mode:", Color::light_grey()}, // now , no need to handle GAIN , Amp here It is handled by ui_transmitter.cpp
|
||||
{{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()},
|
||||
@@ -147,7 +150,7 @@ class MicTXView : public View {
|
||||
{{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()},
|
||||
{{18 * 8, (5 * 8)}, "Mode:", Color::light_grey()}, // now , no need to handle GAIN , Amp here It is handled by ui_transmitter.cpp
|
||||
{{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()},
|
||||
@@ -176,15 +179,15 @@ class MicTXView : public View {
|
||||
{"x2.0", 20}}};
|
||||
|
||||
OptionsField options_ak4951_alc_mode{
|
||||
{20 * 8, 1 * 8}, // Coordinates are: int:x (px), int:y (px)
|
||||
{20 * 8, 1 * 8},
|
||||
11,
|
||||
{
|
||||
{" OFF-12kHz", 0}, // Nothing changed from ORIGINAL,keeping ALL programmable AK4951 Digital Block->OFF, sampling 24Khz)
|
||||
{" OFF-12kHz", 0}, // Nothing changed from ORIGINAL, keeping ALL programmable AK4951 Digital Block->OFF, sampling 24Khz)
|
||||
{"+12dB-6kHz", 1}, // ALC-> on, (+12dB's) Auto Vol max + Wind Noise cancel + LPF 6kHz + Pre-amp Mic (+21dB=original)
|
||||
{"+09dB-6kHz", 2}, // ALC-> on, (+09dB's) Auto Vol max + Wind Noise cancel + LPF 6kHz + Pre-amp Mic (+21dB=original)
|
||||
{"+06dB-6kHz", 3}, // ALC-> on, (+06dB's) Auto Vol max + Wind Noise cancel + LPF 6kHz + Pre-amp Mic (+21dB=original)
|
||||
{"+03dB-2kHz", 4}, // ALC-> on, (+03dB's) Auto Vol max + Wind Noise cancel + LPF 3,5k + Pre-amp Mic (+21dB=original)+ EQ boosting ~<2kHz (f0~1k1,fb:1,7K, k=1,8)
|
||||
{"+03dB-4kHz", 5}, // ALC-> on, (+03dB's) Auto Vol max + Wind Noise cancel + LPF 4kHz + Pre-amp Mic (+21dB=original)+ EQ boosting ~<3kHz (f0~1k4,fb~2,4k, k=1,8)
|
||||
{"+03dB-2kHz", 4}, // ALC-> on, (+03dB's) Auto Vol max + Wind Noise cancel + LPF 3,5k + Pre-amp Mic (+21dB=original)+ EQ boosting ~<2kHz (f0~1k1, fb:1,7K, k=1,8)
|
||||
{"+03dB-4kHz", 5}, // ALC-> on, (+03dB's) Auto Vol max + Wind Noise cancel + LPF 4kHz + Pre-amp Mic (+21dB=original)+ EQ boosting ~<3kHz (f0~1k4, fb~2,4k, k=1,8)
|
||||
{"+03dB-6kHz", 6}, // ALC-> on, (+03dB's) Auto Vol max + Wind Noise cancel + LPF 6kHz + Pre-amp Mic (+21dB=original)
|
||||
{"+00dB-6kHz", 7}, // ALC-> on, (+00dB's) Auto Vol max + Wind Noise cancel + LPF 6kHz + Pre-amp Mic (+21dB=original)
|
||||
{"-03dB-6kHz", 8}, // ALC-> on, (-03dB's) Auto Vol max + Wind Noise cancel + LPF 6kHz + Pre-amp Mic (+21dB=original)
|
||||
@@ -194,16 +197,17 @@ class MicTXView : public View {
|
||||
}};
|
||||
|
||||
OptionsField options_wm8731_boost_mode{
|
||||
{22 * 8, 1 * 8}, // Coordinates are: int:x (px), int:y (px)
|
||||
{22 * 8, 1 * 8},
|
||||
5,
|
||||
{
|
||||
{"ON +12dB", 0}, // WM8731 Mic Boost ON ,original+12dBs condition, easy to saturate ADC sat in high voice ,relative G = +12 dB's respect ref level
|
||||
{"ON +06dB", 1}, // WM8731 Mic Boost ON ,original+6 dBs condition, easy to saturate ADC sat in high voice ,relative G = +06 dB's respect ref level
|
||||
{"OFF+04dB", 2}, // WM8731 Mic Boost OFF to avoid ADC sat in high voice ,relative G = +04 dB's (respect ref level) , always effective sampling 24khz
|
||||
{"OFF-02dB", 3}, // WM8731 Mic Boost OFF to avoid ADC sat in high voice ,relative G = -02 dB's (respect ref level)
|
||||
{"OFF-08dB", 4}, // WM8731 Mic Boost OFF to avoid ADC sat in high voice ,relative G = -12 dB's (respect ref level)
|
||||
{"ON +12dB", 0}, // WM8731 Mic Boost ON, original+12dBs condition, easy to saturate ADC sat in high voice, relative G = +12 dB's respect ref level
|
||||
{"ON +06dB", 1}, // WM8731 Mic Boost ON, original+6 dBs condition, easy to saturate ADC sat in high voice, relative G = +06 dB's respect ref level
|
||||
{"OFF+04dB", 2}, // WM8731 Mic Boost OFF to avoid ADC sat in high voice, relative G = +04 dB's (respect ref level), always effective sampling 24khz
|
||||
{"OFF-02dB", 3}, // WM8731 Mic Boost OFF to avoid ADC sat in high voice, relative G = -02 dB's (respect ref level)
|
||||
{"OFF-08dB", 4}, // WM8731 Mic Boost OFF to avoid ADC sat in high voice, relative G = -12 dB's (respect ref level)
|
||||
}};
|
||||
|
||||
// TODO: Use TxFrequencyField
|
||||
FrequencyField field_frequency{
|
||||
{5 * 8, 3 * 8},
|
||||
};
|
||||
@@ -226,7 +230,7 @@ class MicTXView : public View {
|
||||
{
|
||||
{"NFM/FM", 0},
|
||||
{" WFM ", 1},
|
||||
{" AM ", 2}, // in fact that TX mode = AM -DSB with carrier .
|
||||
{" AM ", 2}, // in fact that TX mode = AM -DSB with carrier.
|
||||
{" USB ", 3},
|
||||
{" LSB ", 4},
|
||||
{"DSB-SC", 5} // We are TX Double Side AM Band with suppressed carrier, and allowing in RX both indep SSB lateral band (USB/LSB).
|
||||
@@ -268,7 +272,7 @@ class MicTXView : public View {
|
||||
|
||||
OptionsField options_tone_key{
|
||||
{12 * 8, (13 * 8) - 5},
|
||||
23,
|
||||
18,
|
||||
{}};
|
||||
|
||||
Checkbox check_rogerbeep{
|
||||
@@ -309,6 +313,7 @@ class MicTXView : public View {
|
||||
' ',
|
||||
};
|
||||
|
||||
// TODO: Use RxFrequencyField
|
||||
FrequencyField field_rxfrequency{
|
||||
{10 * 8, (25 * 8) + 2},
|
||||
};
|
||||
|
||||
@@ -38,8 +38,6 @@ class ModemSetupView : public View {
|
||||
std::string title() const override { return "Modem setup"; };
|
||||
|
||||
private:
|
||||
void update_freq(rf::Frequency f);
|
||||
|
||||
Labels labels{
|
||||
{{2 * 8, 11 * 8}, "Baudrate:", Color::light_grey()},
|
||||
{{2 * 8, 13 * 8}, "Mark: Hz", Color::light_grey()},
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
#include "baseband_api.hpp"
|
||||
#include "hackrf_gpio.hpp"
|
||||
#include "portapack_shared_memory.hpp"
|
||||
#include "cpld_update.hpp"
|
||||
#include "ui_textentry.hpp"
|
||||
#include "string_format.hpp"
|
||||
|
||||
@@ -99,8 +98,7 @@ void MorseView::focus() {
|
||||
|
||||
MorseView::~MorseView() {
|
||||
transmitter_model.disable();
|
||||
hackrf::cpld::load_sram_no_verify(); // to leave all RX ok, without ghost signal problem at the exit .
|
||||
baseband::shutdown(); // better this function at the end, not load_sram() that sometimes produces hang up.
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
void MorseView::paint(Painter&) {
|
||||
|
||||
@@ -40,11 +40,8 @@ void NRFRxView::focus() {
|
||||
field_frequency.focus();
|
||||
}
|
||||
|
||||
void NRFRxView::update_freq(rf::Frequency f) {
|
||||
receiver_model.set_target_frequency(f);
|
||||
}
|
||||
|
||||
NRFRxView::NRFRxView(NavigationView& nav) {
|
||||
NRFRxView::NRFRxView(NavigationView& nav)
|
||||
: nav_{nav} {
|
||||
baseband::run_image(portapack::spi_flash::image_tag_nrf_rx);
|
||||
|
||||
add_children({&rssi,
|
||||
@@ -57,7 +54,7 @@ NRFRxView::NRFRxView(NavigationView& nav) {
|
||||
&console});
|
||||
|
||||
// Auto-configure modem for LCR RX (will be removed later)
|
||||
update_freq(2480000000);
|
||||
field_frequency.set_value(2480000000);
|
||||
auto def_bell202 = &modem_defs[0];
|
||||
persistent_memory::set_modem_baudrate(def_bell202->baudrate);
|
||||
serial_format_t serial_format;
|
||||
@@ -67,17 +64,7 @@ NRFRxView::NRFRxView(NavigationView& nav) {
|
||||
serial_format.bit_order = LSB_FIRST;
|
||||
persistent_memory::set_serial_format(serial_format);
|
||||
|
||||
field_frequency.set_value(receiver_model.target_frequency());
|
||||
field_frequency.set_step(100);
|
||||
field_frequency.on_change = [this](rf::Frequency f) {
|
||||
update_freq(f);
|
||||
};
|
||||
field_frequency.on_edit = [this, &nav]() {
|
||||
auto new_view = nav.push<FrequencyKeypadView>(receiver_model.target_frequency());
|
||||
new_view->on_changed = [this](rf::Frequency f) {
|
||||
field_frequency.set_value(f);
|
||||
};
|
||||
};
|
||||
|
||||
button_modem_setup.on_select = [&nav](Button&) {
|
||||
nav.push<ModemSetupView>();
|
||||
@@ -89,7 +76,6 @@ NRFRxView::NRFRxView(NavigationView& nav) {
|
||||
audio::set_rate(audio::Rate::Hz_24000);
|
||||
audio::output::start();
|
||||
|
||||
receiver_model.set_modulation(ReceiverModel::Mode::WidebandFMAudio);
|
||||
receiver_model.enable();
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include "ui.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "ui_receiver.hpp"
|
||||
#include "ui_freq_field.hpp"
|
||||
#include "app_settings.hpp"
|
||||
#include "radio_state.hpp"
|
||||
#include "ui_record_view.hpp" // DEBUG
|
||||
@@ -47,10 +48,11 @@ class NRFRxView : public View {
|
||||
private:
|
||||
void on_data(uint32_t value, bool is_data);
|
||||
|
||||
NavigationView& nav_;
|
||||
RxRadioState radio_state_{
|
||||
4000000 /* bandwidth */,
|
||||
4000000 /* sampling rate */
|
||||
};
|
||||
4000000 /* sampling rate */,
|
||||
ReceiverModel::Mode::WidebandFMAudio};
|
||||
app_settings::SettingsManager settings_{
|
||||
"rx_nrf", app_settings::Mode::RX};
|
||||
|
||||
@@ -64,15 +66,13 @@ class NRFRxView : public View {
|
||||
VGAGainField field_vga{
|
||||
{18 * 8, 0 * 16}};
|
||||
RSSI rssi{
|
||||
{21 * 8, 0, 6 * 8, 4},
|
||||
};
|
||||
{21 * 8, 0, 6 * 8, 4}};
|
||||
Channel channel{
|
||||
{21 * 8, 5, 6 * 8, 4},
|
||||
};
|
||||
{21 * 8, 5, 6 * 8, 4}};
|
||||
|
||||
FrequencyField field_frequency{
|
||||
RxFrequencyField field_frequency{
|
||||
{0 * 8, 0 * 16},
|
||||
};
|
||||
nav_};
|
||||
|
||||
Button button_modem_setup{
|
||||
{240 - 12 * 8, 1 * 16, 96, 24},
|
||||
@@ -81,9 +81,6 @@ class NRFRxView : public View {
|
||||
Console console{
|
||||
{0, 4 * 16, 240, 240}};
|
||||
|
||||
void update_freq(rf::Frequency f);
|
||||
// void on_data_afsk(const AFSKDataMessage& message);
|
||||
|
||||
MessageHandlerRegistration message_handler_packet{
|
||||
Message::ID::AFSKData,
|
||||
[this](Message* const p) {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* Copyright (C) 2016 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2016 Furrtek
|
||||
* Copyleft (ↄ) 2022 NotPike
|
||||
* Copyright (C) 2023 Kyle Reed, zxkmm
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -22,12 +23,13 @@
|
||||
*/
|
||||
|
||||
#include "ui_playlist.hpp"
|
||||
|
||||
#include "convert.hpp"
|
||||
#include "file_reader.hpp"
|
||||
#include "string_format.hpp"
|
||||
|
||||
#include "ui_fileman.hpp"
|
||||
#include "io_file.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "ui_fileman.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
#include "baseband_api.hpp"
|
||||
#include "portapack.hpp"
|
||||
@@ -36,219 +38,326 @@
|
||||
#include <fstream>
|
||||
|
||||
using namespace portapack;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
/* TODO
|
||||
* - Should frequency overrides be saved in the playlist?
|
||||
* - Start playing from current track (vs start over)?
|
||||
*/
|
||||
|
||||
namespace ui {
|
||||
|
||||
void PlaylistView::set_ready() {
|
||||
ready_signal = true;
|
||||
}
|
||||
// TODO: consolidate extesions into a shared header?
|
||||
static const fs::path c16_ext = u".C16";
|
||||
static const fs::path ppl_ext = u".PPL";
|
||||
|
||||
void PlaylistView::load_file(std::filesystem::path playlist_path) {
|
||||
void PlaylistView::load_file(const fs::path& playlist_path) {
|
||||
File playlist_file;
|
||||
auto error = playlist_file.open(playlist_path.string());
|
||||
|
||||
if (!error) {
|
||||
auto reader = FileLineReader(playlist_file);
|
||||
for (const auto& line : reader) {
|
||||
if (line.length() == 0 || line[0] == '#')
|
||||
continue; // Empty or comment line.
|
||||
if (error)
|
||||
return;
|
||||
|
||||
auto cols = split_string(line, ',');
|
||||
if (cols.size() < 3)
|
||||
continue; // Line doesn't have enough columns.
|
||||
auto reader = FileLineReader(playlist_file);
|
||||
for (const auto& line : reader) {
|
||||
if (line.length() == 0 || line[0] == '#')
|
||||
continue; // Empty or comment line.
|
||||
|
||||
playlist_entry entry{};
|
||||
auto cols = split_string(line, ',');
|
||||
auto entry = load_entry(trim(cols[0]));
|
||||
|
||||
parse_int(cols[0], entry.replay_frequency);
|
||||
parse_int(cols[2], entry.sample_rate);
|
||||
if (entry.replay_frequency == 0 || entry.sample_rate == 0)
|
||||
continue; // Invalid freq or rate.
|
||||
if (!entry)
|
||||
continue;
|
||||
|
||||
entry.replay_file = std::string{"/"} + std::string{cols[1]};
|
||||
// Read optional delay value.
|
||||
if (cols.size() > 1)
|
||||
parse_int(cols[1], entry->ms_delay);
|
||||
|
||||
if (cols.size() == 4) // Optional delay value.
|
||||
parse_int(cols[3], entry.next_delay);
|
||||
|
||||
playlist_db.emplace_back(std::move(entry));
|
||||
}
|
||||
playlist_db_.emplace_back(*std::move(entry));
|
||||
}
|
||||
|
||||
total_tracks = playlist_db.size();
|
||||
playlist_masterdb = playlist_db;
|
||||
text_track.set(to_string_dec_uint(track_number) + "/" + to_string_dec_uint(total_tracks) + " " + now_play_list_file.string());
|
||||
tracks_progressbar.set_max(total_tracks);
|
||||
button_play.focus();
|
||||
}
|
||||
|
||||
void PlaylistView::on_file_changed(std::filesystem::path new_file_path, rf::Frequency replay_frequency, uint32_t replay_sample_rate, uint32_t next_delay) {
|
||||
File data_file;
|
||||
Optional<PlaylistView::playlist_entry> PlaylistView::load_entry(fs::path&& path) {
|
||||
File capture_file;
|
||||
|
||||
// Get file size
|
||||
auto error = data_file.open("/" + new_file_path.string());
|
||||
auto error = capture_file.open(path);
|
||||
if (error)
|
||||
return {};
|
||||
|
||||
if (error) {
|
||||
file_error("C16 file\n" + new_file_path.string() + "\nread error.");
|
||||
// Read metafile if it exists.
|
||||
auto metadata_path = get_metadata_path(path);
|
||||
auto metadata = read_metadata_file(metadata_path);
|
||||
|
||||
// If no metadata found, fallback to the TX frequency.
|
||||
if (!metadata)
|
||||
metadata = {transmitter_model.target_frequency(), 500'000};
|
||||
|
||||
return playlist_entry{
|
||||
std::move(path),
|
||||
*metadata,
|
||||
capture_file.size(),
|
||||
0u};
|
||||
}
|
||||
|
||||
void PlaylistView::on_file_changed(const fs::path& new_file_path) {
|
||||
stop();
|
||||
|
||||
current_index_ = 0;
|
||||
playlist_path_ = new_file_path;
|
||||
playlist_db_.clear();
|
||||
load_file(playlist_path_);
|
||||
|
||||
update_ui();
|
||||
|
||||
// TODO: fix in UI framework with 'try_focus()'?
|
||||
// Hack around focus getting called by ctor before parent is set.
|
||||
if (parent())
|
||||
button_play.focus();
|
||||
}
|
||||
|
||||
void PlaylistView::open_file(bool prompt_save) {
|
||||
if (playlist_dirty_ && prompt_save) {
|
||||
nav_.display_modal(
|
||||
"Save?", "Save changes?", YESNO,
|
||||
[this](bool choice) {
|
||||
if (choice)
|
||||
save_file(false);
|
||||
});
|
||||
nav_.set_on_pop([this]() { open_file(false); });
|
||||
return;
|
||||
}
|
||||
|
||||
track_number = track_number >= total_tracks ? 1 : track_number + 1; // prevent track_number out of range
|
||||
|
||||
file_path = new_file_path;
|
||||
field_frequency.set_value(replay_frequency);
|
||||
|
||||
sample_rate = replay_sample_rate;
|
||||
|
||||
text_sample_rate.set(unit_auto_scale(sample_rate, 3, 0) + "Hz");
|
||||
|
||||
now_delay = next_delay;
|
||||
|
||||
auto file_size = data_file.size();
|
||||
auto duration = (file_size * 1000) / (2 * 2 * sample_rate);
|
||||
|
||||
on_track_progressbar.set_max(file_size);
|
||||
text_filename.set(file_path.filename().string().substr(0, 12));
|
||||
text_duration.set(to_string_time_ms(duration));
|
||||
text_track.set(to_string_dec_uint(track_number) + "/" + to_string_dec_uint(total_tracks) + " " + now_play_list_file.string());
|
||||
tracks_progressbar.set_value(track_number);
|
||||
auto open_view = nav_.push<FileLoadView>(".PPL");
|
||||
open_view->push_dir(u"PLAYLIST");
|
||||
open_view->on_changed = [this](fs::path new_file_path) {
|
||||
on_file_changed(new_file_path);
|
||||
};
|
||||
}
|
||||
|
||||
void PlaylistView::on_tx_progress(const uint32_t progress) {
|
||||
on_track_progressbar.set_value(progress);
|
||||
void PlaylistView::save_file(bool show_dialogs) {
|
||||
if (!playlist_dirty_ || playlist_path_.empty())
|
||||
return;
|
||||
|
||||
ensure_directory(playlist_path_.parent_path());
|
||||
|
||||
File playlist_file;
|
||||
auto error = playlist_file.create(playlist_path_.string());
|
||||
|
||||
if (error) {
|
||||
if (show_dialogs)
|
||||
nav_.display_modal(
|
||||
"Save Error",
|
||||
"Could not save file\n" + playlist_path_.string());
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto& entry : playlist_db_) {
|
||||
playlist_file.write_line(
|
||||
entry.path.string() + "," +
|
||||
to_string_dec_uint(entry.ms_delay));
|
||||
}
|
||||
|
||||
playlist_dirty_ = false;
|
||||
|
||||
if (show_dialogs)
|
||||
nav_.display_modal(
|
||||
"Save",
|
||||
"Saved playlist\n" + playlist_path_.string());
|
||||
}
|
||||
|
||||
void PlaylistView::focus() {
|
||||
button_open.focus();
|
||||
void PlaylistView::add_entry(fs::path&& path) {
|
||||
if (playlist_path_.empty()) {
|
||||
playlist_path_ = next_filename_matching_pattern(u"/PLAYLIST/PLAY_????.PPL");
|
||||
|
||||
// Hack around focus getting called by ctor before parent is set.
|
||||
if (parent())
|
||||
button_play.focus();
|
||||
}
|
||||
|
||||
auto entry = load_entry(std::move(path));
|
||||
if (entry) {
|
||||
playlist_db_.emplace_back(*std::move(entry));
|
||||
current_index_ = playlist_db_.size() - 1;
|
||||
playlist_dirty_ = true;
|
||||
}
|
||||
|
||||
update_ui();
|
||||
}
|
||||
|
||||
void PlaylistView::file_error(std::string error_message) {
|
||||
clean_playlist();
|
||||
stop(false);
|
||||
nav_.display_modal("Error", "Error for \n" + file_path.string() + "\n" + error_message);
|
||||
void PlaylistView::delete_entry() {
|
||||
if (playlist_db_.empty())
|
||||
return;
|
||||
|
||||
// Ugh, the STL is gross.
|
||||
playlist_db_.erase(playlist_db_.begin() + current_index_);
|
||||
|
||||
if (current_index_ > 0)
|
||||
--current_index_;
|
||||
|
||||
playlist_dirty_ = true;
|
||||
update_ui();
|
||||
}
|
||||
|
||||
void PlaylistView::clean_playlist() {
|
||||
total_tracks = 0;
|
||||
track_number = 0;
|
||||
playlist_db.clear();
|
||||
playlist_masterdb.clear();
|
||||
void PlaylistView::show_file_error(const fs::path& path, const std::string& message) {
|
||||
nav_.display_modal("Error", "Error opening file \n" + path.string() + "\n" + message);
|
||||
}
|
||||
|
||||
PlaylistView::playlist_entry* PlaylistView::current() {
|
||||
return playlist_db_.empty() ? nullptr : &playlist_db_[current_index_];
|
||||
}
|
||||
|
||||
bool PlaylistView::is_active() const {
|
||||
return (bool)replay_thread;
|
||||
return replay_thread_ != nullptr;
|
||||
}
|
||||
|
||||
bool PlaylistView::loop() const {
|
||||
return (bool)playlist_db.size();
|
||||
bool PlaylistView::at_end() const {
|
||||
return current_index_ >= playlist_db_.size();
|
||||
}
|
||||
|
||||
void PlaylistView::toggle() {
|
||||
if (is_active()) {
|
||||
stop(false);
|
||||
clean_playlist();
|
||||
} else {
|
||||
clean_playlist();
|
||||
if (std::filesystem::file_exists(now_play_list_file.string())) {
|
||||
load_file(now_play_list_file);
|
||||
if (!playlist_db.empty()) {
|
||||
start();
|
||||
}
|
||||
} else {
|
||||
text_track.set("0/0 no input playlist file");
|
||||
}
|
||||
}
|
||||
if (is_active())
|
||||
stop();
|
||||
else
|
||||
start();
|
||||
}
|
||||
|
||||
void PlaylistView::start() {
|
||||
stop(false);
|
||||
if (playlist_db_.empty())
|
||||
return; // Nothing to do.
|
||||
|
||||
playlist_entry item = playlist_db.front();
|
||||
playlist_db.pop_front();
|
||||
on_file_changed(item.replay_file, item.replay_frequency, item.sample_rate, item.next_delay);
|
||||
transmitter_model.set_target_frequency(item.replay_frequency);
|
||||
|
||||
std::unique_ptr<stream::Reader> reader;
|
||||
|
||||
auto p = std::make_unique<FileReader>();
|
||||
auto open_error = p->open(file_path);
|
||||
if (open_error.is_valid()) {
|
||||
file_error("Illegal grammar");
|
||||
return; // Fixes TX bug if there's a file error
|
||||
} else {
|
||||
reader = std::move(p);
|
||||
}
|
||||
|
||||
if (reader) {
|
||||
button_play.set_bitmap(&bitmap_stop);
|
||||
if (now_delay) { // this `if` is because, if the delay is 0, it will sleep forever
|
||||
chThdSleepMilliseconds(now_delay);
|
||||
}
|
||||
|
||||
replay_thread = std::make_unique<ReplayThread>(
|
||||
std::move(reader),
|
||||
read_size, buffer_count,
|
||||
&ready_signal,
|
||||
[](uint32_t return_code) {
|
||||
ReplayThreadDoneMessage message{return_code};
|
||||
EventDispatcher::send_message(message);
|
||||
});
|
||||
}
|
||||
|
||||
transmitter_model.set_sampling_rate(sample_rate * 8);
|
||||
transmitter_model.set_baseband_bandwidth(baseband_bandwidth);
|
||||
transmitter_model.enable();
|
||||
current_index_ = 0u;
|
||||
send_current_track();
|
||||
}
|
||||
|
||||
void PlaylistView::stop(const bool do_loop) {
|
||||
if (is_active()) {
|
||||
replay_thread.reset();
|
||||
/* Advance to the next track in the playlist. */
|
||||
bool PlaylistView::next_track() {
|
||||
++current_index_;
|
||||
|
||||
// Reset to the 0 once at the end to prevent current_index_
|
||||
// from being outside the bounds of playlist_db_ when painting.
|
||||
if (at_end()) {
|
||||
current_index_ = 0;
|
||||
return check_loop.value(); // Keep going if looping.
|
||||
}
|
||||
|
||||
// TODO: the logic here could be more beautiful but maybe they are all same for compiler anyway....
|
||||
// Notes of the logic here in case if it needed to be changed in the future:
|
||||
// 1. check_loop.value() is for the LOOP checkbox
|
||||
// 2. do_loop is a part of the replay thread, not a user - control thing.
|
||||
// 3. when (total_tracks > track_number) is true, it means that the current track is not the last track.
|
||||
// Thus, (do_loop && (total_tracks != track_number)) is for the case when the start() func were called with true AND not the last track.
|
||||
// Which means it do loop until the last track.
|
||||
|
||||
if (check_loop.value()) {
|
||||
if (do_loop) {
|
||||
if (playlist_db.size() > 0) {
|
||||
start();
|
||||
} else {
|
||||
playlist_db = playlist_masterdb;
|
||||
start();
|
||||
}
|
||||
} else {
|
||||
transmitter_model.disable();
|
||||
button_play.set_bitmap(&bitmap_play);
|
||||
}
|
||||
} else if (!check_loop.value()) {
|
||||
if (do_loop && (total_tracks > track_number)) {
|
||||
if (playlist_db.size() > 0) {
|
||||
start();
|
||||
} else {
|
||||
playlist_db = playlist_masterdb;
|
||||
start();
|
||||
}
|
||||
} else {
|
||||
transmitter_model.disable();
|
||||
button_play.set_bitmap(&bitmap_play);
|
||||
}
|
||||
}
|
||||
|
||||
ready_signal = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void PlaylistView::handle_replay_thread_done(const uint32_t return_code) {
|
||||
if (return_code == ReplayThread::END_OF_FILE) {
|
||||
stop(true);
|
||||
} else if (return_code == ReplayThread::READ_ERROR) {
|
||||
file_error("Replay thread read error");
|
||||
/* Transmits the current_entry_ */
|
||||
void PlaylistView::send_current_track() {
|
||||
// Prepare to send a file.
|
||||
replay_thread_.reset();
|
||||
transmitter_model.disable();
|
||||
ready_signal_ = false;
|
||||
|
||||
if (!current())
|
||||
return;
|
||||
|
||||
// TODO: Use a timer so the UI isn't frozen.
|
||||
if (current()->ms_delay > 0)
|
||||
chThdSleepMilliseconds(current()->ms_delay);
|
||||
|
||||
// Open the sample file to send.
|
||||
auto reader = std::make_unique<FileReader>();
|
||||
auto error = reader->open(current()->path);
|
||||
if (error) {
|
||||
show_file_error(current()->path, "Can't open file to send.");
|
||||
return;
|
||||
}
|
||||
|
||||
on_track_progressbar.set_value(0);
|
||||
// ReplayThread starts immediately on construction so
|
||||
// these need to be set before creating the ReplayThread.
|
||||
transmitter_model.set_target_frequency(current()->metadata.center_frequency);
|
||||
transmitter_model.set_sampling_rate(current()->metadata.sample_rate * 8);
|
||||
transmitter_model.set_baseband_bandwidth(baseband_bandwidth);
|
||||
transmitter_model.enable();
|
||||
|
||||
// Set baseband sample rate too for waterfall to be correct.
|
||||
// TODO: Why doesn't the transmitter_model just handle this?
|
||||
baseband::set_sample_rate(transmitter_model.sampling_rate());
|
||||
|
||||
// Reset the transmit progress bar.
|
||||
progressbar_transmit.set_value(0);
|
||||
|
||||
// Use the ReplayThread class to send the data.
|
||||
replay_thread_ = std::make_unique<ReplayThread>(
|
||||
std::move(reader),
|
||||
/* read_size */ 0x4000,
|
||||
/* buffer_count */ 3,
|
||||
&ready_signal_,
|
||||
[](uint32_t return_code) {
|
||||
ReplayThreadDoneMessage message{return_code};
|
||||
EventDispatcher::send_message(message);
|
||||
});
|
||||
|
||||
// Now it's sending, update the UI.
|
||||
update_ui();
|
||||
}
|
||||
|
||||
void PlaylistView::stop() {
|
||||
// This terminates the underlying chThread.
|
||||
replay_thread_.reset();
|
||||
transmitter_model.disable();
|
||||
|
||||
// Reset the transmit progress bar.
|
||||
progressbar_transmit.set_value(0);
|
||||
update_ui();
|
||||
}
|
||||
|
||||
void PlaylistView::update_ui() {
|
||||
if (playlist_db_.empty()) {
|
||||
text_filename.set("-");
|
||||
text_sample_rate.set("-");
|
||||
text_duration.set("-");
|
||||
|
||||
if (playlist_path_.empty())
|
||||
text_track.set("Open playlist or add capture.");
|
||||
else
|
||||
text_track.set(playlist_path_.filename().string());
|
||||
|
||||
progressbar_track.set_value(0);
|
||||
progressbar_track.set_max(0);
|
||||
progressbar_transmit.set_max(0);
|
||||
|
||||
} else {
|
||||
chDbgAssert(!at_end(), "update_ui #1", "current_index_ invalid");
|
||||
|
||||
text_filename.set(current()->path.filename().string());
|
||||
text_sample_rate.set(unit_auto_scale(current()->metadata.sample_rate, 3, 0) + "Hz");
|
||||
|
||||
auto duration = ms_duration(current()->file_size, current()->metadata.sample_rate, 4);
|
||||
text_duration.set(to_string_time_ms(duration));
|
||||
field_frequency.set_value(current()->metadata.center_frequency);
|
||||
|
||||
text_track.set(
|
||||
to_string_dec_uint(current_index_ + 1) + "/" +
|
||||
to_string_dec_uint(playlist_db_.size()) + " " +
|
||||
playlist_path_.filename().string());
|
||||
|
||||
progressbar_track.set_max(playlist_db_.size() - 1);
|
||||
progressbar_track.set_value(current_index_);
|
||||
progressbar_transmit.set_max(current()->file_size);
|
||||
}
|
||||
|
||||
button_play.set_bitmap(is_active() ? &bitmap_stop : &bitmap_play);
|
||||
}
|
||||
|
||||
void PlaylistView::on_tx_progress(uint32_t progress) {
|
||||
progressbar_transmit.set_value(progress);
|
||||
}
|
||||
|
||||
void PlaylistView::handle_replay_thread_done(uint32_t return_code) {
|
||||
if (return_code == ReplayThread::END_OF_FILE) {
|
||||
if (next_track()) {
|
||||
send_current_track();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (return_code == ReplayThread::READ_ERROR)
|
||||
show_file_error(current()->path, "Replay read failed.");
|
||||
|
||||
stop();
|
||||
}
|
||||
|
||||
PlaylistView::PlaylistView(
|
||||
@@ -257,52 +366,101 @@ PlaylistView::PlaylistView(
|
||||
baseband::run_image(portapack::spi_flash::image_tag_replay);
|
||||
|
||||
add_children({
|
||||
&button_open,
|
||||
&text_filename,
|
||||
&text_sample_rate,
|
||||
&text_duration,
|
||||
&tracks_progressbar,
|
||||
&on_track_progressbar,
|
||||
&progressbar_track,
|
||||
&progressbar_transmit,
|
||||
&field_frequency,
|
||||
&tx_view, // this handles now the previous rfgain, rfamp
|
||||
&tx_view,
|
||||
&check_loop,
|
||||
&button_play,
|
||||
&text_track,
|
||||
&button_prev,
|
||||
&button_add,
|
||||
&button_delete,
|
||||
&button_open,
|
||||
&button_save,
|
||||
&button_next,
|
||||
&waterfall,
|
||||
});
|
||||
|
||||
waterfall.show_audio_spectrum_view(false);
|
||||
|
||||
field_frequency.set_value(transmitter_model.target_frequency());
|
||||
field_frequency.set_step(receiver_model.frequency_step());
|
||||
field_frequency.set_value(100'000'000);
|
||||
field_frequency.on_change = [this](rf::Frequency f) {
|
||||
transmitter_model.set_target_frequency(f);
|
||||
if (current())
|
||||
current()->metadata.center_frequency = f;
|
||||
};
|
||||
field_frequency.on_edit = [this, &nav]() {
|
||||
// TODO: Provide separate modal method/scheme?
|
||||
auto new_view = nav.push<FrequencyKeypadView>(transmitter_model.target_frequency());
|
||||
new_view->on_changed = [this](rf::Frequency f) {
|
||||
this->field_frequency.set_value(f);
|
||||
field_frequency.on_edit = [this]() {
|
||||
auto freq_view = nav_.push<FrequencyKeypadView>(field_frequency.value());
|
||||
freq_view->on_changed = [this](rf::Frequency f) {
|
||||
field_frequency.set_value(f);
|
||||
};
|
||||
};
|
||||
|
||||
field_frequency.set_step(5000);
|
||||
|
||||
button_play.on_select = [this](ImageButton&) {
|
||||
this->toggle();
|
||||
toggle();
|
||||
};
|
||||
|
||||
button_open.on_select = [this, &nav](Button&) {
|
||||
auto open_view = nav.push<FileLoadView>(".PPL");
|
||||
open_view->on_changed = [this](std::filesystem::path new_file_path) {
|
||||
now_play_list_file = new_file_path;
|
||||
if (std::filesystem::file_exists(now_play_list_file.string())) {
|
||||
load_file(now_play_list_file);
|
||||
} else {
|
||||
text_track.set("0/0 no input playlist file");
|
||||
}
|
||||
button_add.on_select = [this, &nav]() {
|
||||
if (is_active())
|
||||
return;
|
||||
auto open_view = nav_.push<FileLoadView>(".C16");
|
||||
open_view->push_dir(u"CAPTURES");
|
||||
open_view->on_changed = [this](fs::path path) {
|
||||
add_entry(std::move(path));
|
||||
};
|
||||
};
|
||||
|
||||
button_delete.on_select = [this, &nav]() {
|
||||
if (is_active())
|
||||
return;
|
||||
delete_entry();
|
||||
};
|
||||
|
||||
button_open.on_select = [this, &nav]() {
|
||||
if (is_active())
|
||||
stop();
|
||||
open_file();
|
||||
};
|
||||
|
||||
button_save.on_select = [this]() {
|
||||
if (is_active())
|
||||
stop();
|
||||
save_file();
|
||||
};
|
||||
|
||||
button_prev.on_select = [this]() {
|
||||
if (is_active())
|
||||
return;
|
||||
--current_index_;
|
||||
if (at_end())
|
||||
current_index_ = playlist_db_.size() - 1;
|
||||
update_ui();
|
||||
};
|
||||
|
||||
button_next.on_select = [this]() {
|
||||
if (is_active())
|
||||
return;
|
||||
++current_index_;
|
||||
if (at_end())
|
||||
current_index_ = 0;
|
||||
update_ui();
|
||||
};
|
||||
|
||||
update_ui();
|
||||
}
|
||||
|
||||
PlaylistView::PlaylistView(
|
||||
NavigationView& nav,
|
||||
const fs::path& path)
|
||||
: PlaylistView(nav) {
|
||||
auto ext = path.extension();
|
||||
if (path_iequal(ext, ppl_ext))
|
||||
on_file_changed(path);
|
||||
else if (path_iequal(ext, c16_ext))
|
||||
add_entry(fs::path{path});
|
||||
}
|
||||
|
||||
PlaylistView::~PlaylistView() {
|
||||
@@ -310,20 +468,26 @@ PlaylistView::~PlaylistView() {
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
void PlaylistView::set_parent_rect(Rect new_parent_rect) {
|
||||
View::set_parent_rect(new_parent_rect);
|
||||
|
||||
ui::Rect waterfall_rect{
|
||||
0, header_height, new_parent_rect.width(),
|
||||
new_parent_rect.height() - header_height};
|
||||
waterfall.set_parent_rect(waterfall_rect);
|
||||
}
|
||||
|
||||
void PlaylistView::focus() {
|
||||
if (playlist_path_.empty())
|
||||
button_add.focus();
|
||||
else
|
||||
button_play.focus();
|
||||
}
|
||||
|
||||
void PlaylistView::on_hide() {
|
||||
stop(false);
|
||||
// TODO: Terrible kludge because widget system doesn't notify Waterfall that
|
||||
// it's being shown or hidden.
|
||||
stop();
|
||||
waterfall.on_hide();
|
||||
View::on_hide();
|
||||
}
|
||||
|
||||
void PlaylistView::set_parent_rect(const Rect new_parent_rect) {
|
||||
View::set_parent_rect(new_parent_rect);
|
||||
|
||||
const ui::Rect waterfall_rect{0, header_height, new_parent_rect.width(),
|
||||
new_parent_rect.height() - header_height};
|
||||
waterfall.set_parent_rect(waterfall_rect);
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2016 Furrtek
|
||||
* Copyright (C) 2023 Kyle Reed, zxkmm
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -24,33 +25,35 @@
|
||||
#define NORMAL_UI false
|
||||
|
||||
#include "app_settings.hpp"
|
||||
#include "bitmap.hpp"
|
||||
#include "file.hpp"
|
||||
#include "metadata_file.hpp"
|
||||
#include "radio_state.hpp"
|
||||
#include "ui_widget.hpp"
|
||||
#include "replay_thread.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "ui_receiver.hpp"
|
||||
#include "replay_thread.hpp"
|
||||
#include "ui_spectrum.hpp"
|
||||
#include "ui_transmitter.hpp"
|
||||
#include "ui_widget.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <deque>
|
||||
#include <vector>
|
||||
|
||||
namespace ui {
|
||||
|
||||
class PlaylistView : public View {
|
||||
public:
|
||||
PlaylistView(NavigationView& nav);
|
||||
|
||||
PlaylistView(NavigationView& nav, const std::filesystem::path& path);
|
||||
~PlaylistView();
|
||||
|
||||
// Following 2 called by 'NavigationView::update_view' after view is created.
|
||||
void set_parent_rect(Rect new_parent_rect) override;
|
||||
void focus() override;
|
||||
void on_hide() override;
|
||||
|
||||
void set_parent_rect(const Rect new_parent_rect) override;
|
||||
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override { return "Playlist"; };
|
||||
std::string title() const override { return "Replay"; };
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
@@ -58,82 +61,88 @@ class PlaylistView : public View {
|
||||
app_settings::SettingsManager settings_{
|
||||
"tx_playlist", app_settings::Mode::TX};
|
||||
|
||||
// add or remove lines here to allow more header and less spectrum view, & vice versa
|
||||
static constexpr ui::Dim header_height = 4 * 16;
|
||||
// More header == less spectrum view.
|
||||
static constexpr ui::Dim header_height = 6 * 16;
|
||||
static constexpr uint32_t baseband_bandwidth = 2500000;
|
||||
|
||||
struct playlist_entry {
|
||||
rf::Frequency replay_frequency{0};
|
||||
std::string replay_file{};
|
||||
uint32_t sample_rate{};
|
||||
uint32_t next_delay{};
|
||||
std::filesystem::path path{};
|
||||
capture_metadata metadata{};
|
||||
File::Size file_size{};
|
||||
uint32_t ms_delay{};
|
||||
};
|
||||
std::deque<playlist_entry> playlist_db{};
|
||||
std::deque<playlist_entry> playlist_masterdb{};
|
||||
uint32_t sample_rate = 0;
|
||||
int32_t tx_gain{47};
|
||||
bool rf_amp{true}; // aux private var to store temporal, Replay App rf_amp user selection.
|
||||
static constexpr uint32_t baseband_bandwidth = 2500000;
|
||||
const size_t read_size{16384};
|
||||
const size_t buffer_count{3};
|
||||
|
||||
void load_file(std::filesystem::path playlist_path);
|
||||
void on_file_changed(std::filesystem::path new_file_path, rf::Frequency replay_frequency, uint32_t replay_sample_rate, uint32_t next_delay);
|
||||
void on_tx_progress(const uint32_t progress);
|
||||
void set_target_frequency(const rf::Frequency new_value);
|
||||
rf::Frequency target_frequency() const;
|
||||
std::unique_ptr<ReplayThread> replay_thread_{};
|
||||
bool ready_signal_{}; // Used to signal the ReplayThread.
|
||||
|
||||
size_t current_index_{0};
|
||||
bool playlist_dirty_{};
|
||||
std::vector<playlist_entry> playlist_db_{};
|
||||
std::filesystem::path playlist_path_{};
|
||||
|
||||
void load_file(const std::filesystem::path& path);
|
||||
Optional<playlist_entry> load_entry(std::filesystem::path&& path);
|
||||
void on_file_changed(const std::filesystem::path& path);
|
||||
void open_file(bool prompt_save = true);
|
||||
void save_file(bool show_dialogs = true);
|
||||
void add_entry(std::filesystem::path&& path);
|
||||
void delete_entry();
|
||||
void reset();
|
||||
void show_file_error(
|
||||
const std::filesystem::path& path,
|
||||
const std::string& message);
|
||||
|
||||
playlist_entry* current();
|
||||
|
||||
bool is_active() const;
|
||||
bool at_end() const;
|
||||
|
||||
void toggle();
|
||||
void start();
|
||||
void stop(const bool do_loop);
|
||||
bool is_active() const;
|
||||
bool loop() const;
|
||||
void set_ready();
|
||||
void handle_replay_thread_done(const uint32_t return_code);
|
||||
void file_error(std::string error_message);
|
||||
void clean_playlist();
|
||||
bool next_track();
|
||||
void send_current_track();
|
||||
void stop();
|
||||
|
||||
std::filesystem::path file_path{};
|
||||
std::unique_ptr<ReplayThread> replay_thread{};
|
||||
bool ready_signal{false};
|
||||
size_t track_number{0};
|
||||
size_t total_tracks{0};
|
||||
uint32_t now_delay{0};
|
||||
std::filesystem::path now_play_list_file{};
|
||||
void update_ui();
|
||||
|
||||
Button button_open{
|
||||
{0 * 8, 0 * 16, 10 * 8, 2 * 16},
|
||||
"Open file"};
|
||||
/* There are called by Message handlers. */
|
||||
void on_tx_progress(uint32_t progress);
|
||||
void handle_replay_thread_done(uint32_t return_code);
|
||||
|
||||
Text text_filename{
|
||||
{11 * 8, 0 * 16, 12 * 8, 16},
|
||||
"-"};
|
||||
Text text_sample_rate{
|
||||
{24 * 8, 0 * 16, 6 * 8, 16},
|
||||
"-"};
|
||||
|
||||
Text text_duration{
|
||||
{11 * 8, 1 * 16, 6 * 8, 16},
|
||||
"-"};
|
||||
ProgressBar tracks_progressbar{
|
||||
{18 * 8, 1 * 16, 12 * 8, 8}};
|
||||
|
||||
ProgressBar on_track_progressbar{
|
||||
{18 * 8, 3 * 8, 12 * 8, 8}};
|
||||
{0 * 8, 0 * 16, 30 * 8, 16}};
|
||||
|
||||
FrequencyField field_frequency{
|
||||
{0 * 8, 2 * 16},
|
||||
};
|
||||
{0 * 8, 1 * 16}};
|
||||
|
||||
Text text_sample_rate{
|
||||
{10 * 8, 1 * 16, 7 * 8, 16}};
|
||||
|
||||
/*v making there's 1px line (instead of two) between two progress bars,
|
||||
* by letting 1px overlapped.
|
||||
* So, since they overlapped 1px, they are visually same, and looks better.
|
||||
*/
|
||||
|
||||
ProgressBar progressbar_track{
|
||||
{18 * 8, 1 * 16, 12 * 8, 8 + 1}};
|
||||
|
||||
ProgressBar progressbar_transmit{
|
||||
{18 * 8, 3 * 8 - 1, 12 * 8, 8}};
|
||||
|
||||
Text text_duration{
|
||||
{0 * 8, 2 * 16, 5 * 8, 16}};
|
||||
|
||||
// TODO: delay duration field.
|
||||
|
||||
TransmitterView2 tx_view{
|
||||
// new handling of NumberField field_rfgain, NumberField field_rfamp
|
||||
74, 1 * 8, SHORT_UI // x(columns), y (line) position. (Used in Repay / GPS Simul / Play list App)
|
||||
// 10*8, 2*8, NORMAL_UI // x(columns), y (line) position. (Used in Mic App)
|
||||
};
|
||||
9 * 8, 1 * 8, SHORT_UI};
|
||||
|
||||
Checkbox check_loop{
|
||||
{21 * 8, 2 * 16},
|
||||
4,
|
||||
"Loop",
|
||||
true};
|
||||
|
||||
ImageButton button_play{
|
||||
{28 * 8, 2 * 16, 2 * 8, 1 * 16},
|
||||
&bitmap_play,
|
||||
@@ -141,32 +150,67 @@ class PlaylistView : public View {
|
||||
Color::black()};
|
||||
|
||||
Text text_track{
|
||||
{0 * 8, 3 * 16, 16 * 8, 16},
|
||||
"0/0 no input playlist file"};
|
||||
{0 * 8, 3 * 16, 30 * 8, 16}};
|
||||
|
||||
spectrum::WaterfallWidget waterfall{};
|
||||
NewButton button_prev{
|
||||
{2 * 8, 4 * 16, 4 * 8, 2 * 16},
|
||||
"",
|
||||
&bitmap_arrow_left,
|
||||
Color::dark_grey()};
|
||||
|
||||
NewButton button_next{
|
||||
{6 * 8, 4 * 16, 4 * 8, 2 * 16},
|
||||
"",
|
||||
&bitmap_arrow_right,
|
||||
Color::dark_grey()};
|
||||
|
||||
NewButton button_add{
|
||||
{11 * 8, 4 * 16, 4 * 8, 2 * 16},
|
||||
"",
|
||||
&bitmap_icon_new_file,
|
||||
Color::orange()};
|
||||
|
||||
NewButton button_delete{
|
||||
{15 * 8, 4 * 16, 4 * 8, 2 * 16},
|
||||
"",
|
||||
&bitmap_icon_delete,
|
||||
Color::orange()};
|
||||
|
||||
NewButton button_open{
|
||||
{20 * 8, 4 * 16, 4 * 8, 2 * 16},
|
||||
"",
|
||||
&bitmap_icon_load,
|
||||
Color::dark_blue()};
|
||||
|
||||
NewButton button_save{
|
||||
{24 * 8, 4 * 16, 4 * 8, 2 * 16},
|
||||
"",
|
||||
&bitmap_icon_save,
|
||||
Color::dark_blue()};
|
||||
|
||||
spectrum::WaterfallView waterfall{};
|
||||
|
||||
MessageHandlerRegistration message_handler_replay_thread_error{
|
||||
Message::ID::ReplayThreadDone,
|
||||
[this](const Message* const p) {
|
||||
const auto message = *reinterpret_cast<const ReplayThreadDoneMessage*>(p);
|
||||
this->handle_replay_thread_done(message.return_code);
|
||||
[this](const Message* p) {
|
||||
auto message = *reinterpret_cast<const ReplayThreadDoneMessage*>(p);
|
||||
handle_replay_thread_done(message.return_code);
|
||||
}};
|
||||
|
||||
MessageHandlerRegistration message_handler_fifo_signal{
|
||||
Message::ID::RequestSignal,
|
||||
[this](const Message* const p) {
|
||||
const auto message = static_cast<const RequestSignalMessage*>(p);
|
||||
[this](const Message* p) {
|
||||
auto message = static_cast<const RequestSignalMessage*>(p);
|
||||
if (message->signal == RequestSignalMessage::Signal::FillRequest) {
|
||||
this->set_ready();
|
||||
ready_signal_ = true;
|
||||
}
|
||||
}};
|
||||
|
||||
MessageHandlerRegistration message_handler_tx_progress{
|
||||
Message::ID::TXProgress,
|
||||
[this](const Message* const p) {
|
||||
const auto message = *reinterpret_cast<const TXProgressMessage*>(p);
|
||||
this->on_tx_progress(message.progress);
|
||||
[this](const Message* p) {
|
||||
auto message = *reinterpret_cast<const TXProgressMessage*>(p);
|
||||
on_tx_progress(message.progress);
|
||||
}};
|
||||
};
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
#include "ui_pocsag_tx.hpp"
|
||||
|
||||
#include "baseband_api.hpp"
|
||||
#include "cpld_update.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "ui_textentry.hpp"
|
||||
|
||||
@@ -40,8 +39,7 @@ void POCSAGTXView::focus() {
|
||||
|
||||
POCSAGTXView::~POCSAGTXView() {
|
||||
transmitter_model.disable();
|
||||
hackrf::cpld::load_sram_no_verify(); // to leave all RX ok, without ghost signal problem at the exit
|
||||
baseband::shutdown(); // better this function at the end, not load_sram() that sometimes produces hang up.
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
void POCSAGTXView::on_tx_progress(const uint32_t progress, const bool done) {
|
||||
@@ -82,8 +80,7 @@ bool POCSAGTXView::start_tx() {
|
||||
progressbar.set_max(total_frames);
|
||||
|
||||
transmitter_model.set_rf_amp(true);
|
||||
transmitter_model.set_lna(40);
|
||||
transmitter_model.set_vga(40);
|
||||
transmitter_model.set_tx_gain(40);
|
||||
transmitter_model.enable();
|
||||
|
||||
uint8_t* data_ptr = shared_memory.bb_data.data;
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
|
||||
#include "portapack.hpp"
|
||||
#include "baseband_api.hpp"
|
||||
#include "cpld_update.hpp"
|
||||
#include "portapack_shared_memory.hpp"
|
||||
|
||||
#include <cstring>
|
||||
@@ -161,8 +160,7 @@ void RDSView::focus() {
|
||||
|
||||
RDSView::~RDSView() {
|
||||
transmitter_model.disable();
|
||||
hackrf::cpld::load_sram_no_verify(); // to leave all RX ok, without ghost signal problem at the exit.
|
||||
baseband::shutdown(); // better this function at the end, not load_sram() that sometimes produces hang up.
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
void RDSView::start_tx() {
|
||||
|
||||
@@ -23,41 +23,84 @@
|
||||
|
||||
#include "ui_recon.hpp"
|
||||
#include "ui_fileman.hpp"
|
||||
#include "capture_app.hpp"
|
||||
#include "file.hpp"
|
||||
#include "tone_key.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
using namespace tonekey;
|
||||
using portapack::memory::map::backup_ram;
|
||||
|
||||
namespace ui {
|
||||
|
||||
bool ReconView::current_is_valid() {
|
||||
return (unsigned)current_index < frequency_list.size();
|
||||
}
|
||||
|
||||
freqman_entry& ReconView::current_entry() {
|
||||
return *frequency_list[current_index];
|
||||
}
|
||||
|
||||
void ReconView::set_loop_config(bool v) {
|
||||
continuous = v;
|
||||
button_loop_config.set_style(v ? &Styles::green : &Styles::white);
|
||||
persistent_memory::set_recon_continuous(continuous);
|
||||
}
|
||||
|
||||
void ReconView::clear_freqlist_for_ui_action() {
|
||||
audio::output::stop();
|
||||
// flag to detect and reload frequency_list
|
||||
freqlist_cleared_for_ui_action = true;
|
||||
// if in manual mode, there is enough memory to load freqman files, else we have to unload/reload
|
||||
if (!manual_mode) {
|
||||
// clear and shrink_to_fit are not enough to really start with a new, clean, empty vector
|
||||
// swap is the only way to achieve a perfect memory liberation
|
||||
std::vector<freqman_entry>().swap(frequency_list);
|
||||
} else {
|
||||
frequency_list.shrink_to_fit();
|
||||
void ReconView::recon_stop_recording() {
|
||||
if (is_recording) {
|
||||
if (field_mode.selected_index_value() == SPEC_MODULATION)
|
||||
button_audio_app.set_text("RAW");
|
||||
else
|
||||
button_audio_app.set_text("AUDIO");
|
||||
button_audio_app.set_style(&Styles::white);
|
||||
record_view->stop();
|
||||
button_config.set_style(&Styles::white); // disable config while recording as it's causing an IO error pop up at exit
|
||||
is_recording = false;
|
||||
}
|
||||
}
|
||||
|
||||
void ReconView::clear_freqlist_for_ui_action() {
|
||||
recon_stop_recording();
|
||||
if (field_mode.selected_index_value() != SPEC_MODULATION)
|
||||
audio::output::stop();
|
||||
// flag to detect and reload frequency_list
|
||||
if (!manual_mode) {
|
||||
// Clear doesn't actually free, re-assign so destructor runs on previous instance.
|
||||
frequency_list = freqman_db{};
|
||||
} else
|
||||
frequency_list.shrink_to_fit();
|
||||
freqlist_cleared_for_ui_action = true;
|
||||
}
|
||||
|
||||
void ReconView::reset_indexes() {
|
||||
last_entry.modulation = -1;
|
||||
last_entry.bandwidth = -1;
|
||||
last_entry.step = -1;
|
||||
description = "...no description...";
|
||||
last_entry.modulation = freqman_invalid_index;
|
||||
last_entry.bandwidth = freqman_invalid_index;
|
||||
last_entry.step = freqman_invalid_index;
|
||||
current_index = 0;
|
||||
}
|
||||
|
||||
void ReconView::update_description() {
|
||||
if (frequency_list.empty() || current_entry().description.empty()) {
|
||||
description = "...no description...";
|
||||
return;
|
||||
} else {
|
||||
switch (current_entry().type) {
|
||||
case freqman_type::Range:
|
||||
description = "R: ";
|
||||
break;
|
||||
case freqman_type::HamRadio:
|
||||
description = "H: ";
|
||||
break;
|
||||
default:
|
||||
description = "S: ";
|
||||
}
|
||||
description += current_entry().description;
|
||||
}
|
||||
|
||||
desc_cycle.set(description);
|
||||
}
|
||||
|
||||
void ReconView::colorize_waits() {
|
||||
// colorize wait on match
|
||||
if (wait == 0) {
|
||||
@@ -84,15 +127,15 @@ void ReconView::colorize_waits() {
|
||||
bool ReconView::recon_save_freq(const std::string& freq_file_path, size_t freq_index, bool warn_if_exists) {
|
||||
File recon_file;
|
||||
|
||||
if (frequency_list.size() == 0 || (frequency_list.size() && current_index > (int32_t)frequency_list.size()))
|
||||
if (frequency_list.size() == 0 || !current_is_valid())
|
||||
return false;
|
||||
|
||||
freqman_entry entry = frequency_list[freq_index];
|
||||
freqman_entry entry = *frequency_list[freq_index]; // Makes a copy.
|
||||
entry.frequency_a = freq;
|
||||
entry.frequency_b = 0;
|
||||
entry.modulation = last_entry.modulation;
|
||||
entry.bandwidth = last_entry.bandwidth;
|
||||
entry.type = SINGLE;
|
||||
entry.type = freqman_type::Single;
|
||||
|
||||
std::string frequency_to_add;
|
||||
get_freq_string(entry, frequency_to_add);
|
||||
@@ -162,7 +205,7 @@ bool ReconView::recon_load_config_from_sd() {
|
||||
pos = line_start;
|
||||
while ((line_end = strstr(line_start, "\x0A"))) {
|
||||
length = line_end - line_start - 1;
|
||||
params[it] = string(pos, length);
|
||||
params[it] = std::string(pos, length);
|
||||
it++;
|
||||
line_start = line_end + 1;
|
||||
pos = line_start;
|
||||
@@ -234,8 +277,8 @@ bool ReconView::recon_save_config_to_sd() {
|
||||
|
||||
make_new_directory(u"SETTINGS");
|
||||
|
||||
auto result = settings_file.create(RECON_CFG_FILE);
|
||||
if (result.is_valid())
|
||||
auto error = settings_file.create(RECON_CFG_FILE);
|
||||
if (error)
|
||||
return false;
|
||||
settings_file.write_line(input_file);
|
||||
settings_file.write_line(output_file);
|
||||
@@ -248,7 +291,8 @@ bool ReconView::recon_save_config_to_sd() {
|
||||
}
|
||||
|
||||
void ReconView::audio_output_start() {
|
||||
audio::output::start();
|
||||
if (field_mode.selected_index_value() != SPEC_MODULATION)
|
||||
audio::output::start();
|
||||
receiver_model.set_headphone_volume(receiver_model.headphone_volume()); // WM8731 hack.
|
||||
}
|
||||
|
||||
@@ -281,10 +325,6 @@ void ReconView::recon_redraw() {
|
||||
} else if (freq_lock >= recon_lock_nb_match) {
|
||||
big_display.set_style(&Styles::green);
|
||||
button_pause.set_text("<UNLOCK>");
|
||||
// FREQ IS STRONG: GREEN and recon will pause when on_statistics_update()
|
||||
if ((!scanner_mode) && autosave && frequency_list.size() > 0) {
|
||||
recon_save_freq(freq_file_path, current_index, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (last_db != db || last_list_size != frequency_list.size()) {
|
||||
@@ -300,53 +340,38 @@ void ReconView::handle_retune() {
|
||||
receiver_model.set_target_frequency(freq); // Retune
|
||||
}
|
||||
if (frequency_list.size() > 0) {
|
||||
if (last_entry.modulation != frequency_list[current_index].modulation && frequency_list[current_index].modulation >= 0) {
|
||||
last_entry.modulation = frequency_list[current_index].modulation;
|
||||
field_mode.set_selected_index(frequency_list[current_index].modulation);
|
||||
last_entry.bandwidth = -1;
|
||||
if (last_entry.modulation != current_entry().modulation && is_valid(current_entry().modulation)) {
|
||||
last_entry.modulation = current_entry().modulation;
|
||||
field_mode.set_selected_index(current_entry().modulation);
|
||||
last_entry.bandwidth = freqman_invalid_index;
|
||||
}
|
||||
// Set bandwidth if any
|
||||
if (last_entry.bandwidth != frequency_list[current_index].bandwidth && frequency_list[current_index].bandwidth >= 0) {
|
||||
last_entry.bandwidth = frequency_list[current_index].bandwidth;
|
||||
field_bw.set_selected_index(frequency_list[current_index].bandwidth);
|
||||
if (last_entry.bandwidth != current_entry().bandwidth && is_valid(current_entry().bandwidth)) {
|
||||
last_entry.bandwidth = current_entry().bandwidth;
|
||||
field_bw.set_selected_index(current_entry().bandwidth);
|
||||
}
|
||||
if (last_entry.step != frequency_list[current_index].step && frequency_list[current_index].step >= 0) {
|
||||
last_entry.step = frequency_list[current_index].step;
|
||||
if (last_entry.step != current_entry().step && is_valid(current_entry().step)) {
|
||||
last_entry.step = current_entry().step;
|
||||
step = freqman_entry_get_step_value(last_entry.step);
|
||||
step_mode.set_selected_index(step);
|
||||
}
|
||||
if (last_index != current_index) {
|
||||
last_index = current_index;
|
||||
if ((int32_t)frequency_list.size() && current_index < (int32_t)frequency_list.size() && frequency_list[current_index].type == RANGE) {
|
||||
if (frequency_list.size() && current_is_valid() && current_entry().type == freqman_type::Range) {
|
||||
if (update_ranges && !manual_mode) {
|
||||
button_manual_start.set_text(to_string_short_freq(frequency_list[current_index].frequency_a));
|
||||
frequency_range.min = frequency_list[current_index].frequency_a;
|
||||
if (frequency_list[current_index].frequency_b != 0) {
|
||||
button_manual_end.set_text(to_string_short_freq(frequency_list[current_index].frequency_b));
|
||||
frequency_range.max = frequency_list[current_index].frequency_b;
|
||||
button_manual_start.set_text(to_string_short_freq(current_entry().frequency_a));
|
||||
frequency_range.min = current_entry().frequency_a;
|
||||
if (current_entry().frequency_b != 0) {
|
||||
button_manual_end.set_text(to_string_short_freq(current_entry().frequency_b));
|
||||
frequency_range.max = current_entry().frequency_b;
|
||||
} else {
|
||||
button_manual_end.set_text(to_string_short_freq(frequency_list[current_index].frequency_a));
|
||||
frequency_range.max = frequency_list[current_index].frequency_a;
|
||||
button_manual_end.set_text(to_string_short_freq(current_entry().frequency_a));
|
||||
frequency_range.max = current_entry().frequency_a;
|
||||
}
|
||||
}
|
||||
}
|
||||
text_cycle.set_text(to_string_dec_uint(current_index + 1, 3));
|
||||
if (frequency_list[current_index].description.size() > 0) {
|
||||
switch (frequency_list[current_index].type) {
|
||||
case RANGE:
|
||||
desc_cycle.set("R: " + frequency_list[current_index].description); // Show new description
|
||||
break;
|
||||
case HAMRADIO:
|
||||
desc_cycle.set("H: " + frequency_list[current_index].description); // Show new description
|
||||
break;
|
||||
default:
|
||||
case SINGLE:
|
||||
desc_cycle.set("S: " + frequency_list[current_index].description); // Show new description
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
desc_cycle.set("...no description..."); // Show new description
|
||||
}
|
||||
update_description();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -356,16 +381,20 @@ void ReconView::focus() {
|
||||
}
|
||||
|
||||
ReconView::~ReconView() {
|
||||
// Save recon config.
|
||||
recon_stop_recording();
|
||||
recon_save_config_to_sd();
|
||||
|
||||
audio::output::stop();
|
||||
if (field_mode.selected_index_value() != SPEC_MODULATION)
|
||||
audio::output::stop();
|
||||
receiver_model.disable();
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
ReconView::ReconView(NavigationView& nav)
|
||||
: nav_{nav} {
|
||||
chrono_start = chTimeNow();
|
||||
record_view = std::make_unique<RecordView>(Rect{0, 0, 30 * 8, 1 * 16},
|
||||
u"AUTO_AUDIO_", u"AUDIO",
|
||||
RecordView::FileType::WAV, 4096, 4);
|
||||
add_children({&labels,
|
||||
&field_lna,
|
||||
&field_vga,
|
||||
@@ -401,7 +430,14 @@ ReconView::ReconView(NavigationView& nav)
|
||||
&button_dir,
|
||||
&button_restart,
|
||||
&button_mic_app,
|
||||
&button_remove});
|
||||
&button_remove,
|
||||
record_view.get()});
|
||||
|
||||
record_view->hidden(true);
|
||||
record_view->set_filename_date_frequency(true);
|
||||
record_view->on_error = [&nav](std::string message) {
|
||||
nav.display_modal("Error", message);
|
||||
};
|
||||
|
||||
def_step = 0;
|
||||
// HELPER: Pre-setting a manual range, based on stored frequency
|
||||
@@ -426,6 +462,7 @@ ReconView::ReconView(NavigationView& nav)
|
||||
load_ranges = persistent_memory::recon_load_ranges();
|
||||
load_hamradios = persistent_memory::recon_load_hamradios();
|
||||
update_ranges = persistent_memory::recon_update_ranges_when_recon();
|
||||
auto_record_locked = persistent_memory::recon_auto_record_locked();
|
||||
|
||||
button_manual_start.on_select = [this, &nav](ButtonWithEncoder& button) {
|
||||
clear_freqlist_for_ui_action();
|
||||
@@ -530,8 +567,12 @@ ReconView::ReconView(NavigationView& nav)
|
||||
};
|
||||
|
||||
button_audio_app.on_select = [this](Button&) {
|
||||
nav_.pop();
|
||||
nav_.push<AnalogAudioView>();
|
||||
auto settings = receiver_model.settings();
|
||||
settings.frequency_step = step_mode.selected_index_value();
|
||||
if (field_mode.selected_index_value() == SPEC_MODULATION)
|
||||
nav_.replace<CaptureAppView>();
|
||||
else
|
||||
nav_.replace<AnalogAudioView>(settings);
|
||||
};
|
||||
|
||||
button_loop_config.on_select = [this](Button&) {
|
||||
@@ -549,20 +590,20 @@ ReconView::ReconView(NavigationView& nav)
|
||||
// TODO: *BUG* Both transmitter_model and receiver_model share the same pmem setting for target_frequency.
|
||||
button_mic_app.on_select = [this](Button&) {
|
||||
if (frequency_list.size() > 0 && current_index >= 0 && (unsigned)current_index < frequency_list.size()) {
|
||||
if (frequency_list[current_index].type == HAMRADIO) {
|
||||
// if it's a HAMRADIO entry, then frequency_a is the freq at which the repeater reveive, so we have to set it in transmit in mic app
|
||||
transmitter_model.set_target_frequency(frequency_list[current_index].frequency_a);
|
||||
// if it's a HAMRADIO entry, then frequency_b is the freq at which the repeater transmit, so we have to set it in receive in mic app
|
||||
receiver_model.set_target_frequency(frequency_list[current_index].frequency_b);
|
||||
if (current_entry().type == freqman_type::HamRadio) {
|
||||
// if it's a HamRadio entry, then frequency_a is the freq at which the repeater receives, so we have to set it in transmit in mic app
|
||||
transmitter_model.set_target_frequency(current_entry().frequency_a);
|
||||
// if it's a HamRadio entry, then frequency_b is the freq at which the repeater transmits, so we have to set it in receive in mic app
|
||||
receiver_model.set_target_frequency(current_entry().frequency_b);
|
||||
} else {
|
||||
// it's single or range so we us actual tuned frequency
|
||||
transmitter_model.set_target_frequency(freq);
|
||||
receiver_model.set_target_frequency(freq);
|
||||
}
|
||||
}
|
||||
// there is no way yet to set modulation and bandwidth from Recon to MicApp
|
||||
nav_.pop();
|
||||
nav_.push<MicTXView>();
|
||||
|
||||
// MicTX wants Modulation and Bandwidth overrides, but that's only stored on the RX model.
|
||||
nav_.replace<MicTXView>(receiver_model.settings());
|
||||
};
|
||||
|
||||
button_remove.on_select = [this](ButtonWithEncoder&) {
|
||||
@@ -578,23 +619,8 @@ ReconView::ReconView(NavigationView& nav)
|
||||
current_index = frequency_list.size() - 1;
|
||||
}
|
||||
if (frequency_list.size() > 0) {
|
||||
if (frequency_list[current_index].description.size() > 0) {
|
||||
switch (frequency_list[current_index].type) {
|
||||
case RANGE:
|
||||
desc_cycle.set("R: " + frequency_list[current_index].description);
|
||||
break;
|
||||
case HAMRADIO:
|
||||
desc_cycle.set("H: " + frequency_list[current_index].description);
|
||||
break;
|
||||
default:
|
||||
case SINGLE:
|
||||
desc_cycle.set("S: " + frequency_list[current_index].description);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
desc_cycle.set("...no description...");
|
||||
}
|
||||
text_cycle.set_text(to_string_dec_uint(current_index + 1, 3));
|
||||
update_description();
|
||||
}
|
||||
// also remove from output file if in scanner mode
|
||||
if (scanner_mode) {
|
||||
@@ -604,7 +630,7 @@ ReconView::ReconView(NavigationView& nav)
|
||||
if (!result.is_valid()) {
|
||||
for (size_t n = 0; n < frequency_list.size(); n++) {
|
||||
std::string line;
|
||||
get_freq_string(frequency_list[n], line);
|
||||
get_freq_string(*frequency_list[n], line);
|
||||
freqman_file.write_line(line);
|
||||
}
|
||||
}
|
||||
@@ -616,12 +642,12 @@ ReconView::ReconView(NavigationView& nav)
|
||||
std::string tmp_freq_file_path{freq_file_path + ".TMP"};
|
||||
std::string frequency_to_add{};
|
||||
|
||||
freqman_entry entry = frequency_list[current_index];
|
||||
freqman_entry entry = current_entry();
|
||||
entry.frequency_a = freq;
|
||||
entry.frequency_b = 0;
|
||||
entry.modulation = last_entry.modulation;
|
||||
entry.bandwidth = last_entry.bandwidth;
|
||||
entry.type = SINGLE;
|
||||
entry.type = freqman_type::Single;
|
||||
|
||||
get_freq_string(entry, frequency_to_add);
|
||||
|
||||
@@ -656,7 +682,7 @@ ReconView::ReconView(NavigationView& nav)
|
||||
}
|
||||
}
|
||||
}
|
||||
receiver_model.set_target_frequency(frequency_list[current_index].frequency_a); // retune
|
||||
receiver_model.set_target_frequency(current_entry().frequency_a); // retune
|
||||
}
|
||||
if (frequency_list.size() == 0) {
|
||||
text_cycle.set_text(" ");
|
||||
@@ -682,27 +708,25 @@ ReconView::ReconView(NavigationView& nav)
|
||||
} else if (frequency_range.min > frequency_range.max) {
|
||||
nav_.display_modal("Error", "END freq\nis lower than START");
|
||||
} else {
|
||||
audio::output::stop();
|
||||
if (field_mode.selected_index_value() != SPEC_MODULATION)
|
||||
audio::output::stop();
|
||||
|
||||
// clear and shrink_to_fit are not enough to really start with a new, clean, empty vector
|
||||
// swap is the only way to achieve a perfect memory liberation
|
||||
std::vector<freqman_entry>().swap(frequency_list);
|
||||
// Clear doesn't actually free, re-assign so destructor runs on previous instance.
|
||||
frequency_list = freqman_db{};
|
||||
current_index = 0;
|
||||
frequency_list.push_back(std::make_unique<freqman_entry>());
|
||||
|
||||
freqman_entry manual_freq_entry;
|
||||
|
||||
def_step = step_mode.selected_index(); // max range val
|
||||
|
||||
manual_freq_entry.type = RANGE;
|
||||
manual_freq_entry.description =
|
||||
to_string_short_freq(frequency_range.min).erase(0, 1) + ">" + to_string_short_freq(frequency_range.max).erase(0, 1) + " S:" // current Manual range
|
||||
+ freqman_entry_get_step_string_short(def_step); // euquiq: lame kludge to reduce spacing in step freq
|
||||
manual_freq_entry.frequency_a = frequency_range.min; // min range val
|
||||
manual_freq_entry.frequency_b = frequency_range.max; // max range val
|
||||
manual_freq_entry.modulation = -1;
|
||||
manual_freq_entry.bandwidth = -1;
|
||||
manual_freq_entry.step = def_step;
|
||||
|
||||
frequency_list.push_back(manual_freq_entry);
|
||||
def_step = step_mode.selected_index();
|
||||
current_entry().type = freqman_type::Range;
|
||||
current_entry().description =
|
||||
to_string_short_freq(frequency_range.min).erase(0, 1) + ">" + // euquiq: lame kludge to reduce spacing in step freq
|
||||
to_string_short_freq(frequency_range.max).erase(0, 1) + " S:" +
|
||||
freqman_entry_get_step_string_short(def_step);
|
||||
current_entry().frequency_a = frequency_range.min;
|
||||
current_entry().frequency_b = frequency_range.max;
|
||||
current_entry().modulation = freqman_invalid_index;
|
||||
current_entry().bandwidth = freqman_invalid_index;
|
||||
current_entry().step = def_step;
|
||||
|
||||
big_display.set_style(&Styles::white); // Back to white color
|
||||
|
||||
@@ -712,18 +736,17 @@ ReconView::ReconView(NavigationView& nav)
|
||||
text_cycle.set_text("1");
|
||||
text_max.set("/1");
|
||||
button_scanner_mode.set_style(&Styles::white);
|
||||
button_scanner_mode.set_text("MSEARCH");
|
||||
button_scanner_mode.set_text("MANUAL");
|
||||
file_name.set_style(&Styles::white);
|
||||
file_name.set("MANUAL RANGE RECON");
|
||||
desc_cycle.set_style(&Styles::white);
|
||||
|
||||
last_entry.modulation = -1;
|
||||
last_entry.bandwidth = -1;
|
||||
last_entry.step = -1;
|
||||
last_entry.modulation = freqman_invalid_index;
|
||||
last_entry.bandwidth = freqman_invalid_index;
|
||||
last_entry.step = freqman_invalid_index;
|
||||
last_index = -1;
|
||||
|
||||
current_index = 0;
|
||||
freq = manual_freq_entry.frequency_a;
|
||||
freq = current_entry().frequency_a;
|
||||
handle_retune();
|
||||
recon_redraw();
|
||||
recon_resume();
|
||||
@@ -757,15 +780,18 @@ ReconView::ReconView(NavigationView& nav)
|
||||
if (scanner_mode) {
|
||||
file_name.set_style(&Styles::red);
|
||||
button_scanner_mode.set_style(&Styles::red);
|
||||
button_scanner_mode.set_text("SCANNER");
|
||||
button_scanner_mode.set_text("SCAN");
|
||||
} else {
|
||||
file_name.set_style(&Styles::blue);
|
||||
button_scanner_mode.set_style(&Styles::blue);
|
||||
button_scanner_mode.set_text("RECON");
|
||||
}
|
||||
if (frequency_list.size() > FREQMAN_MAX_PER_FILE) {
|
||||
file_name.set_style(&Styles::yellow);
|
||||
}
|
||||
};
|
||||
|
||||
button_add.on_select = [this](ButtonWithEncoder&) { // frequency_list[current_index]
|
||||
button_add.on_select = [this](ButtonWithEncoder&) {
|
||||
if (!scanner_mode) {
|
||||
recon_save_freq(freq_file_path, current_index, true);
|
||||
}
|
||||
@@ -786,7 +812,7 @@ ReconView::ReconView(NavigationView& nav)
|
||||
} else {
|
||||
scanner_mode = true;
|
||||
button_scanner_mode.set_style(&Styles::red);
|
||||
button_scanner_mode.set_text("SCANNER");
|
||||
button_scanner_mode.set_text("SCAN");
|
||||
button_scanner_mode.set_text("REMOVE");
|
||||
}
|
||||
frequency_file_load(true);
|
||||
@@ -798,11 +824,13 @@ ReconView::ReconView(NavigationView& nav)
|
||||
};
|
||||
|
||||
button_config.on_select = [this, &nav](Button&) {
|
||||
if (is_recording) // disabling config while recording
|
||||
return;
|
||||
clear_freqlist_for_ui_action();
|
||||
|
||||
freq_lock = 0;
|
||||
timer = 0;
|
||||
auto open_view = nav.push<ReconSetupView>(input_file, output_file);
|
||||
open_view->on_changed = [this](std::vector<std::string> result) {
|
||||
freqlist_cleared_for_ui_action = false;
|
||||
input_file = result[0];
|
||||
output_file = result[1];
|
||||
freq_file_path = "/FREQMAN/" + output_file + ".TXT";
|
||||
@@ -815,8 +843,10 @@ ReconView::ReconView(NavigationView& nav)
|
||||
load_ranges = persistent_memory::recon_load_ranges();
|
||||
load_hamradios = persistent_memory::recon_load_hamradios();
|
||||
update_ranges = persistent_memory::recon_update_ranges_when_recon();
|
||||
auto_record_locked = persistent_memory::recon_auto_record_locked();
|
||||
|
||||
frequency_file_load(false);
|
||||
freqlist_cleared_for_ui_action = false;
|
||||
|
||||
if (autostart) {
|
||||
recon_resume();
|
||||
@@ -833,6 +863,9 @@ ReconView::ReconView(NavigationView& nav)
|
||||
|
||||
field_wait.on_change = [this](int32_t v) {
|
||||
wait = v;
|
||||
// replacing -100 by 200 else it's freezing the device
|
||||
if (wait == -100)
|
||||
wait = -200;
|
||||
colorize_waits();
|
||||
};
|
||||
|
||||
@@ -891,113 +924,100 @@ ReconView::ReconView(NavigationView& nav)
|
||||
|
||||
void ReconView::frequency_file_load(bool stop_all_before) {
|
||||
(void)(stop_all_before);
|
||||
audio::output::stop();
|
||||
if (field_mode.selected_index_value() != SPEC_MODULATION)
|
||||
audio::output::stop();
|
||||
|
||||
def_step = step_mode.selected_index(); // use def_step from manual selector
|
||||
// clear and shrink_to_fit are not enough to really start with a new, clean, empty vector
|
||||
// swap is the only way to achieve a perfect memory liberation
|
||||
std::vector<freqman_entry>().swap(frequency_list); // clear the existing frequency list (expected behavior)
|
||||
std::string file_input = input_file; // default recon mode
|
||||
std::string file_input = input_file; // default recon mode
|
||||
if (scanner_mode) {
|
||||
file_input = output_file;
|
||||
file_name.set_style(&Styles::red);
|
||||
button_scanner_mode.set_style(&Styles::red);
|
||||
button_scanner_mode.set_text("SCANNER");
|
||||
desc_cycle.set_style(&Styles::red);
|
||||
button_scanner_mode.set_text("SCAN");
|
||||
} else {
|
||||
file_name.set_style(&Styles::blue);
|
||||
button_scanner_mode.set_style(&Styles::blue);
|
||||
desc_cycle.set_style(&Styles::blue);
|
||||
button_scanner_mode.set_text("RECON");
|
||||
}
|
||||
desc_cycle.set_style(&Styles::white);
|
||||
if (!load_freqman_file_ex(file_input, frequency_list, load_freqs, load_ranges, load_hamradios, RECON_FREQMAN_MAX_PER_FILE)) {
|
||||
freqman_load_options options{
|
||||
.load_freqs = load_freqs,
|
||||
.load_ranges = load_ranges,
|
||||
.load_hamradios = load_hamradios};
|
||||
if (!load_freqman_file(file_input, frequency_list, options)) {
|
||||
file_name.set_style(&Styles::red);
|
||||
desc_cycle.set_style(&Styles::red);
|
||||
desc_cycle.set(" NO " + file_input + ".TXT FILE ...");
|
||||
file_name.set("=> NO DATA");
|
||||
} else {
|
||||
file_name.set(file_input + "=>" + output_file);
|
||||
if (frequency_list.size() == 0) {
|
||||
file_name.set_style(&Styles::red);
|
||||
desc_cycle.set_style(&Styles::red);
|
||||
desc_cycle.set("/0 no entries in list");
|
||||
file_name.set("BadOrEmpty " + file_input);
|
||||
} else {
|
||||
if (frequency_list.size() > RECON_FREQMAN_MAX_PER_FILE) {
|
||||
if (frequency_list.size() > FREQMAN_MAX_PER_FILE) {
|
||||
file_name.set_style(&Styles::yellow);
|
||||
desc_cycle.set_style(&Styles::yellow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (frequency_list[0].step >= 0)
|
||||
step = freqman_entry_get_step_value(frequency_list[0].step);
|
||||
else
|
||||
step = freqman_entry_get_step_value(def_step);
|
||||
|
||||
switch (frequency_list[0].type) {
|
||||
case SINGLE:
|
||||
freq = frequency_list[0].frequency_a;
|
||||
break;
|
||||
case RANGE:
|
||||
minfreq = frequency_list[0].frequency_a;
|
||||
maxfreq = frequency_list[0].frequency_b;
|
||||
if (fwd) {
|
||||
freq = minfreq;
|
||||
} else {
|
||||
freq = maxfreq;
|
||||
}
|
||||
if (frequency_list[0].step >= 0)
|
||||
step = freqman_entry_get_step_value(frequency_list[0].step);
|
||||
break;
|
||||
case HAMRADIO:
|
||||
minfreq = frequency_list[0].frequency_a;
|
||||
maxfreq = frequency_list[0].frequency_b;
|
||||
if (fwd) {
|
||||
freq = minfreq;
|
||||
} else {
|
||||
freq = maxfreq;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
if (frequency_list.empty()) {
|
||||
text_cycle.set_text(" ");
|
||||
return; // Can't really do much.
|
||||
}
|
||||
|
||||
reset_indexes();
|
||||
step = freqman_entry_get_step_value(
|
||||
is_valid(current_entry().step) ? current_entry().step : def_step);
|
||||
|
||||
if (current_entry().type == freqman_type::Single) {
|
||||
freq = current_entry().frequency_a;
|
||||
} else if (current_entry().type != freqman_type::Unknown) {
|
||||
minfreq = current_entry().frequency_a;
|
||||
maxfreq = current_entry().frequency_b;
|
||||
freq = fwd ? minfreq : maxfreq;
|
||||
}
|
||||
|
||||
step_mode.set_selected_index(def_step); // Impose the default step into the manual step selector
|
||||
receiver_model.enable();
|
||||
receiver_model.set_squelch_level(0);
|
||||
if (frequency_list.size() != 0) {
|
||||
switch (frequency_list[current_index].type) {
|
||||
case RANGE:
|
||||
description = "R: " + frequency_list[current_index].description;
|
||||
break;
|
||||
case HAMRADIO:
|
||||
description = "H: " + frequency_list[current_index].description;
|
||||
break;
|
||||
default:
|
||||
case SINGLE:
|
||||
description = "S: " + frequency_list[current_index].description;
|
||||
break;
|
||||
text_cycle.set_text(to_string_dec_uint(current_index + 1, 3));
|
||||
if (update_ranges && !manual_mode) {
|
||||
button_manual_start.set_text(to_string_short_freq(current_entry().frequency_a));
|
||||
frequency_range.min = current_entry().frequency_a;
|
||||
if (current_entry().frequency_b != 0) {
|
||||
button_manual_end.set_text(to_string_short_freq(current_entry().frequency_b));
|
||||
frequency_range.max = current_entry().frequency_b;
|
||||
} else {
|
||||
button_manual_end.set_text(to_string_short_freq(current_entry().frequency_a));
|
||||
frequency_range.max = current_entry().frequency_a;
|
||||
}
|
||||
text_cycle.set_text(to_string_dec_uint(current_index + 1, 3));
|
||||
if (update_ranges && !manual_mode) {
|
||||
button_manual_start.set_text(to_string_short_freq(frequency_list[current_index].frequency_a));
|
||||
frequency_range.min = frequency_list[current_index].frequency_a;
|
||||
if (frequency_list[current_index].frequency_b != 0) {
|
||||
button_manual_end.set_text(to_string_short_freq(frequency_list[current_index].frequency_b));
|
||||
frequency_range.max = frequency_list[current_index].frequency_b;
|
||||
} else {
|
||||
button_manual_end.set_text(to_string_short_freq(frequency_list[current_index].frequency_a));
|
||||
frequency_range.max = frequency_list[current_index].frequency_a;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
text_cycle.set_text(" ");
|
||||
}
|
||||
desc_cycle.set(description);
|
||||
update_description();
|
||||
handle_retune();
|
||||
}
|
||||
|
||||
void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
|
||||
systime_t time_interval = 100;
|
||||
uint32_t local_recon_lock_duration = recon_lock_duration;
|
||||
|
||||
chrono_end = chTimeNow();
|
||||
if (field_mode.selected_index_value() == SPEC_MODULATION) {
|
||||
time_interval = chrono_end - chrono_start;
|
||||
// capping here to avoid double check a frequency because time_interval is more than exactly 100 by a few units
|
||||
// this is because we are making a measurement and not using a fixed value
|
||||
if (time_interval > 100)
|
||||
time_interval = 100;
|
||||
if (field_lock_wait.value() == 0) {
|
||||
if (time_interval <= 1) // capping here to avoid freeze because too quick
|
||||
local_recon_lock_duration = 2; // minimum working tested value
|
||||
else
|
||||
local_recon_lock_duration = time_interval;
|
||||
}
|
||||
}
|
||||
chrono_start = chrono_end;
|
||||
|
||||
// hack to reload the list if it was cleared by going into CONFIG
|
||||
if (freqlist_cleared_for_ui_action) {
|
||||
if (!manual_mode) {
|
||||
@@ -1016,7 +1036,7 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
|
||||
status = 0;
|
||||
continuous_lock = false;
|
||||
freq_lock = 0;
|
||||
timer = recon_lock_duration;
|
||||
timer = local_recon_lock_duration;
|
||||
big_display.set_style(&Styles::white);
|
||||
}
|
||||
if (freq_lock < recon_lock_nb_match) // LOCKING
|
||||
@@ -1024,7 +1044,9 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
|
||||
if (status != 1) {
|
||||
status = 1;
|
||||
if (wait != 0) {
|
||||
audio::output::stop();
|
||||
recon_stop_recording();
|
||||
if (field_mode.selected_index_value() != SPEC_MODULATION)
|
||||
audio::output::stop();
|
||||
}
|
||||
}
|
||||
if (db > squelch) // MATCHING LEVEL
|
||||
@@ -1046,8 +1068,28 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
|
||||
if (status != 2) {
|
||||
continuous_lock = false;
|
||||
status = 2;
|
||||
// FREQ IS STRONG: GREEN and recon will pause when on_statistics_update()
|
||||
if ((!scanner_mode) && autosave && frequency_list.size() > 0) {
|
||||
recon_save_freq(freq_file_path, current_index, false);
|
||||
}
|
||||
if (wait != 0) {
|
||||
audio_output_start();
|
||||
if (field_mode.selected_index_value() != SPEC_MODULATION)
|
||||
audio_output_start();
|
||||
// contents of a possible recon_start_recording(), but not yet since it's only called once
|
||||
if (auto_record_locked && !is_recording) {
|
||||
button_audio_app.set_style(&Styles::red);
|
||||
if (field_mode.selected_index_value() == SPEC_MODULATION)
|
||||
button_audio_app.set_text("RAW REC");
|
||||
else
|
||||
button_audio_app.set_text("WAV REC");
|
||||
record_view->start();
|
||||
button_config.set_style(&Styles::light_grey); // disable config while recording as it's causing an IO error pop up at exit
|
||||
is_recording = true;
|
||||
}
|
||||
// FREQ IS STRONG: GREEN and recon will pause when on_statistics_update()
|
||||
if ((!scanner_mode) && autosave && frequency_list.size() > 0) {
|
||||
recon_save_freq(freq_file_path, current_index, false);
|
||||
}
|
||||
}
|
||||
if (wait >= 0) {
|
||||
timer = wait;
|
||||
@@ -1066,8 +1108,9 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
|
||||
text_timer.set("TIMER: " + to_string_dec_int(timer));
|
||||
}
|
||||
if (timer) {
|
||||
if (!continuous_lock || recon_match_mode == RECON_MATCH_SPARSE)
|
||||
timer -= STATS_UPDATE_INTERVAL;
|
||||
if (!continuous_lock || recon_match_mode == RECON_MATCH_SPARSE) {
|
||||
timer -= time_interval;
|
||||
}
|
||||
if (timer < 0) {
|
||||
timer = 0;
|
||||
}
|
||||
@@ -1078,11 +1121,10 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
|
||||
if (frequency_list.size() > 0) {
|
||||
has_looped = false;
|
||||
entry_has_changed = false;
|
||||
|
||||
if (recon || stepper != 0 || index_stepper != 0) {
|
||||
if (index_stepper == 0) {
|
||||
/* we are doing a range */
|
||||
if (frequency_list[current_index].type == RANGE) {
|
||||
if (current_entry().type == freqman_type::Range) {
|
||||
if ((fwd && stepper == 0) || stepper > 0) {
|
||||
// forward
|
||||
freq += step;
|
||||
@@ -1112,7 +1154,7 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (frequency_list[current_index].type == SINGLE) {
|
||||
} else if (current_entry().type == freqman_type::Single) {
|
||||
if ((fwd && stepper == 0) || stepper > 0) { // forward
|
||||
current_index++;
|
||||
entry_has_changed = true;
|
||||
@@ -1131,7 +1173,7 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
|
||||
current_index = frequency_list.size() - 1;
|
||||
}
|
||||
}
|
||||
} else if (frequency_list[current_index].type == HAMRADIO) {
|
||||
} else if (current_entry().type == freqman_type::HamRadio) {
|
||||
if ((fwd && stepper == 0) || stepper > 0) { // forward
|
||||
if ((minfreq != maxfreq) && freq == minfreq) {
|
||||
freq = maxfreq;
|
||||
@@ -1178,28 +1220,29 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
|
||||
|
||||
entry_has_changed = true;
|
||||
|
||||
if (!recon) // for some motive, audio output gets stopped.
|
||||
// for some motive, audio output gets stopped.
|
||||
if (!recon && field_mode.selected_index_value() != SPEC_MODULATION)
|
||||
audio_output_start();
|
||||
}
|
||||
// reload entry if changed
|
||||
if (entry_has_changed) {
|
||||
timer = 0;
|
||||
switch (frequency_list[current_index].type) {
|
||||
case SINGLE:
|
||||
freq = frequency_list[current_index].frequency_a;
|
||||
switch (current_entry().type) {
|
||||
case freqman_type::Single:
|
||||
freq = current_entry().frequency_a;
|
||||
break;
|
||||
case RANGE:
|
||||
minfreq = frequency_list[current_index].frequency_a;
|
||||
maxfreq = frequency_list[current_index].frequency_b;
|
||||
case freqman_type::Range:
|
||||
minfreq = current_entry().frequency_a;
|
||||
maxfreq = current_entry().frequency_b;
|
||||
if ((fwd && !stepper && !index_stepper) || stepper > 0 || index_stepper > 0) {
|
||||
freq = minfreq;
|
||||
} else if ((!fwd && !stepper && !index_stepper) || stepper < 0 || index_stepper < 0) {
|
||||
freq = maxfreq;
|
||||
}
|
||||
break;
|
||||
case HAMRADIO:
|
||||
minfreq = frequency_list[current_index].frequency_a;
|
||||
maxfreq = frequency_list[current_index].frequency_b;
|
||||
case freqman_type::HamRadio:
|
||||
minfreq = current_entry().frequency_a;
|
||||
maxfreq = current_entry().frequency_b;
|
||||
if ((fwd && !stepper && !index_stepper) || stepper > 0 || index_stepper > 0) {
|
||||
freq = minfreq;
|
||||
} else if ((!fwd && !stepper && !index_stepper) || stepper < 0 || index_stepper < 0) {
|
||||
@@ -1230,7 +1273,8 @@ void ReconView::recon_pause() {
|
||||
continuous_lock = false;
|
||||
recon = false;
|
||||
|
||||
audio_output_start();
|
||||
if (field_mode.selected_index_value() != SPEC_MODULATION)
|
||||
audio_output_start();
|
||||
|
||||
big_display.set_style(&Styles::white);
|
||||
button_pause.set_text("<RESUME>"); // PAUSED, show resume
|
||||
@@ -1242,7 +1286,8 @@ void ReconView::recon_resume() {
|
||||
continuous_lock = false;
|
||||
recon = true;
|
||||
|
||||
audio::output::stop();
|
||||
if (field_mode.selected_index_value() != SPEC_MODULATION)
|
||||
audio::output::stop();
|
||||
|
||||
big_display.set_style(&Styles::white);
|
||||
button_pause.set_text("<PAUSE>");
|
||||
@@ -1283,10 +1328,28 @@ void ReconView::on_stepper_delta(int32_t v) {
|
||||
size_t ReconView::change_mode(freqman_index_t new_mod) {
|
||||
field_mode.on_change = [this](size_t, OptionsField::value_t) {};
|
||||
field_bw.on_change = [this](size_t, OptionsField::value_t) {};
|
||||
|
||||
recon_stop_recording();
|
||||
remove_child(record_view.get());
|
||||
record_view.reset();
|
||||
if (new_mod == SPEC_MODULATION) {
|
||||
audio::output::stop();
|
||||
record_view = std::make_unique<RecordView>(Rect{0, 0, 30 * 8, 1 * 16},
|
||||
u"AUTO_RAW_", u"CAPTURES",
|
||||
RecordView::FileType::RawS16, 16384, 3);
|
||||
} else {
|
||||
record_view = std::make_unique<RecordView>(Rect{0, 0, 30 * 8, 1 * 16},
|
||||
u"AUTO_AUDIO_", u"AUDIO",
|
||||
RecordView::FileType::WAV, 4096, 4);
|
||||
}
|
||||
add_child(record_view.get());
|
||||
record_view->hidden(true);
|
||||
record_view->set_filename_date_frequency(true);
|
||||
record_view->on_error = [this](std::string message) {
|
||||
nav_.display_modal("Error", message);
|
||||
};
|
||||
receiver_model.disable();
|
||||
baseband::shutdown();
|
||||
|
||||
size_t recording_sampling_rate = 0;
|
||||
switch (new_mod) {
|
||||
case AM_MODULATION:
|
||||
freqman_set_bandwidth_option(new_mod, field_bw);
|
||||
@@ -1297,6 +1360,7 @@ size_t ReconView::change_mode(freqman_index_t new_mod) {
|
||||
receiver_model.set_am_configuration(field_bw.selected_index_value());
|
||||
field_bw.on_change = [this](size_t, OptionsField::value_t n) { receiver_model.set_am_configuration(n); };
|
||||
text_ctcss.set(" ");
|
||||
recording_sampling_rate = 12000;
|
||||
break;
|
||||
case NFM_MODULATION:
|
||||
freqman_set_bandwidth_option(new_mod, field_bw);
|
||||
@@ -1306,6 +1370,7 @@ size_t ReconView::change_mode(freqman_index_t new_mod) {
|
||||
receiver_model.set_modulation(ReceiverModel::Mode::NarrowbandFMAudio);
|
||||
receiver_model.set_nbfm_configuration(field_bw.selected_index_value());
|
||||
field_bw.on_change = [this](size_t, OptionsField::value_t n) { receiver_model.set_nbfm_configuration(n); };
|
||||
recording_sampling_rate = 24000;
|
||||
break;
|
||||
case WFM_MODULATION:
|
||||
freqman_set_bandwidth_option(new_mod, field_bw);
|
||||
@@ -1316,18 +1381,43 @@ size_t ReconView::change_mode(freqman_index_t new_mod) {
|
||||
receiver_model.set_wfm_configuration(field_bw.selected_index_value());
|
||||
field_bw.on_change = [this](size_t, OptionsField::value_t n) { receiver_model.set_wfm_configuration(n); };
|
||||
text_ctcss.set(" ");
|
||||
recording_sampling_rate = 48000;
|
||||
break;
|
||||
case SPEC_MODULATION:
|
||||
freqman_set_bandwidth_option(new_mod, field_bw);
|
||||
// bw 200k (0) default
|
||||
baseband::run_image(portapack::spi_flash::image_tag_capture);
|
||||
receiver_model.set_modulation(ReceiverModel::Mode::Capture);
|
||||
field_bw.set_by_value(0);
|
||||
field_bw.on_change = [this](size_t, OptionsField::value_t sampling_rate) {
|
||||
auto anti_alias_baseband_bandwidth_filter = filter_bandwidth_for_sampling_rate(sampling_rate);
|
||||
record_view->set_sampling_rate(sampling_rate);
|
||||
receiver_model.set_sampling_rate(sampling_rate);
|
||||
receiver_model.set_baseband_bandwidth(anti_alias_baseband_bandwidth_filter);
|
||||
};
|
||||
text_ctcss.set(" ");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (new_mod != SPEC_MODULATION) {
|
||||
button_audio_app.set_text("AUDIO");
|
||||
record_view->set_sampling_rate(recording_sampling_rate);
|
||||
// reset receiver model to fix bug when going from SPEC to audio, the sound is distorded
|
||||
receiver_model.set_sampling_rate(3072000);
|
||||
receiver_model.set_baseband_bandwidth(1750000);
|
||||
} else {
|
||||
button_audio_app.set_text("RAW");
|
||||
}
|
||||
|
||||
field_mode.set_selected_index(new_mod);
|
||||
field_mode.on_change = [this](size_t, OptionsField::value_t v) {
|
||||
if (v != -1) {
|
||||
change_mode(v);
|
||||
}
|
||||
};
|
||||
|
||||
if (!recon) // for some motive, audio output gets stopped.
|
||||
// for some motive, audio output gets stopped.
|
||||
if (!recon && field_mode.selected_index_value() != SPEC_MODULATION)
|
||||
audio::output::start(); // so if recon was stopped we resume audio
|
||||
receiver_model.enable();
|
||||
|
||||
@@ -1335,32 +1425,10 @@ size_t ReconView::change_mode(freqman_index_t new_mod) {
|
||||
}
|
||||
|
||||
void ReconView::handle_coded_squelch(const uint32_t value) {
|
||||
float diff{0.0};
|
||||
float min_diff{(float)value};
|
||||
size_t min_idx{0};
|
||||
size_t c{0};
|
||||
|
||||
if (field_mode.selected_index() != NFM_MODULATION) {
|
||||
if (field_mode.selected_index() == NFM_MODULATION)
|
||||
text_ctcss.set(tone_key_string_by_value(value, text_ctcss.parent_rect().width() / 8));
|
||||
else
|
||||
text_ctcss.set(" ");
|
||||
return;
|
||||
}
|
||||
|
||||
// Find nearest match
|
||||
for (c = 0; c < tone_keys.size(); c++) {
|
||||
diff = abs(((float)value / 100.0) - tone_keys[c].second);
|
||||
if (diff < min_diff) {
|
||||
min_idx = c;
|
||||
min_diff = diff;
|
||||
}
|
||||
}
|
||||
|
||||
// Arbitrary confidence threshold
|
||||
if (last_squelch_index < 0 || (unsigned)last_squelch_index != min_idx) {
|
||||
last_squelch_index = min_idx;
|
||||
if (min_diff < 40)
|
||||
text_ctcss.set("T: " + tone_keys[min_idx].first);
|
||||
else
|
||||
text_ctcss.set(" ");
|
||||
}
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -45,7 +45,6 @@
|
||||
namespace ui {
|
||||
|
||||
#define RECON_CFG_FILE "SETTINGS/recon.cfg"
|
||||
#define RECON_FREQMAN_MAX_PER_FILE 99 // maximum tested limit for load frequency from freqman to work
|
||||
|
||||
class ReconView : public View {
|
||||
public:
|
||||
@@ -68,6 +67,7 @@ class ReconView : public View {
|
||||
void set_loop_config(bool v);
|
||||
void clear_freqlist_for_ui_action();
|
||||
void reset_indexes();
|
||||
void update_description();
|
||||
void audio_output_start();
|
||||
bool check_sd_card();
|
||||
size_t change_mode(freqman_index_t mod_type);
|
||||
@@ -85,6 +85,12 @@ class ReconView : public View {
|
||||
bool recon_load_config_from_sd();
|
||||
bool recon_save_config_to_sd();
|
||||
bool recon_save_freq(const std::string& freq_file_path, size_t index, bool warn_if_exists);
|
||||
// placeholder for possible void recon_start_recording();
|
||||
void recon_stop_recording();
|
||||
|
||||
// Returns true if 'current_index' is in bounds of frequency_list.
|
||||
bool current_is_valid();
|
||||
freqman_entry& current_entry();
|
||||
|
||||
jammer::jammer_range_t frequency_range{false, 0, MAX_UFREQ}; // perfect for manual recon task too...
|
||||
int32_t squelch{0};
|
||||
@@ -109,6 +115,8 @@ class ReconView : public View {
|
||||
bool fwd{true};
|
||||
bool recon{true};
|
||||
bool user_pause{false};
|
||||
bool auto_record_locked{false};
|
||||
bool is_recording{false};
|
||||
uint32_t recon_lock_nb_match{3};
|
||||
uint32_t recon_lock_duration{RECON_MIN_LOCK_DURATION};
|
||||
uint32_t recon_match_mode{RECON_MATCH_CONTINUOUS};
|
||||
@@ -119,10 +127,7 @@ class ReconView : public View {
|
||||
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};
|
||||
@@ -139,16 +144,19 @@ class ReconView : public View {
|
||||
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{};
|
||||
systime_t chrono_start{};
|
||||
systime_t chrono_end{};
|
||||
|
||||
std::unique_ptr<RecordView> record_view{};
|
||||
|
||||
Labels labels{
|
||||
{{0 * 8, 0 * 16}, "LNA: VGA: AMP: VOL: ", Color::light_grey()},
|
||||
{{3 * 8, 8 * 16}, "START END", Color::light_grey()},
|
||||
{{0 * 8, (22 * 8)}, " S: ", Color::light_grey()},
|
||||
{{0 * 8, (24 * 8) + 4}, "NBLCKS:x W,L: , ", Color::light_grey()},
|
||||
{{0 * 8, (26 * 8) + 4}, "MODE: , SQUELCH: ", Color::light_grey()}};
|
||||
{{0 * 8, (26 * 8) + 4}, "MODE: , SQUELCH: ", Color::light_grey()}};
|
||||
|
||||
LNAGainField field_lna{
|
||||
{4 * 8, 0 * 16}};
|
||||
@@ -266,18 +274,18 @@ class ReconView : public View {
|
||||
NumberField field_lock_wait{
|
||||
{25 * 8, 24 * 8 + 4},
|
||||
4,
|
||||
{RECON_MIN_LOCK_DURATION, RECON_MAX_LOCK_DURATION},
|
||||
{0, RECON_MAX_LOCK_DURATION},
|
||||
STATS_UPDATE_INTERVAL,
|
||||
' ',
|
||||
};
|
||||
|
||||
OptionsField field_mode{
|
||||
{6 * 8, (26 * 8) + 4},
|
||||
3,
|
||||
4,
|
||||
{}};
|
||||
|
||||
OptionsField field_bw{
|
||||
{10 * 8, (26 * 8) + 4},
|
||||
{11 * 8, (26 * 8) + 4},
|
||||
6,
|
||||
{}};
|
||||
|
||||
|
||||
@@ -103,6 +103,7 @@ void ReconSetupViewMore::save() {
|
||||
persistent_memory::set_recon_load_ranges(checkbox_load_ranges.value());
|
||||
persistent_memory::set_recon_load_hamradios(checkbox_load_hamradios.value());
|
||||
persistent_memory::set_recon_update_ranges_when_recon(checkbox_update_ranges_when_recon.value());
|
||||
persistent_memory::set_recon_auto_record_locked(checkbox_auto_record_locked.value());
|
||||
};
|
||||
|
||||
void ReconSetupViewMain::focus() {
|
||||
@@ -117,12 +118,14 @@ ReconSetupViewMore::ReconSetupViewMore(NavigationView& nav, Rect parent_rect)
|
||||
add_children({&checkbox_load_freqs,
|
||||
&checkbox_load_ranges,
|
||||
&checkbox_load_hamradios,
|
||||
&checkbox_update_ranges_when_recon});
|
||||
&checkbox_update_ranges_when_recon,
|
||||
&checkbox_auto_record_locked});
|
||||
|
||||
checkbox_load_freqs.set_value(persistent_memory::recon_load_freqs());
|
||||
checkbox_load_ranges.set_value(persistent_memory::recon_load_ranges());
|
||||
checkbox_load_hamradios.set_value(persistent_memory::recon_load_hamradios());
|
||||
checkbox_update_ranges_when_recon.set_value(persistent_memory::recon_update_ranges_when_recon());
|
||||
checkbox_auto_record_locked.set_value(persistent_memory::recon_auto_record_locked());
|
||||
};
|
||||
|
||||
void ReconSetupViewMore::focus() {
|
||||
|
||||
@@ -132,6 +132,10 @@ class ReconSetupViewMore : public View {
|
||||
{1 * 8, 102},
|
||||
3,
|
||||
"auto update m-ranges"};
|
||||
Checkbox checkbox_auto_record_locked{
|
||||
{1 * 8, 132},
|
||||
3,
|
||||
"record locked periods"};
|
||||
};
|
||||
|
||||
class ReconSetupView : public View {
|
||||
|
||||
@@ -239,7 +239,7 @@ void ScannerView::handle_retune(int64_t freq, uint32_t freq_idx) {
|
||||
}
|
||||
|
||||
void ScannerView::focus() {
|
||||
field_mode.focus();
|
||||
button_load.focus();
|
||||
}
|
||||
|
||||
ScannerView::~ScannerView() {
|
||||
@@ -318,7 +318,7 @@ ScannerView::ScannerView(
|
||||
std::string dir_filter = "FREQMAN/";
|
||||
std::string str_file_path = new_file_path.string();
|
||||
|
||||
if (str_file_path.find(dir_filter) != string::npos) { // assert file from the FREQMAN folder
|
||||
if (str_file_path.find(dir_filter) != std::string::npos) { // assert file from the FREQMAN folder
|
||||
scan_pause();
|
||||
// get the filename without txt extension so we can use load_freqman_file fcn
|
||||
std::string str_file_name = new_file_path.stem().string();
|
||||
@@ -392,16 +392,17 @@ ScannerView::ScannerView(
|
||||
button_audio_app.on_select = [this](Button&) {
|
||||
if (scan_thread)
|
||||
scan_thread->stop();
|
||||
nav_.pop();
|
||||
nav_.push<AnalogAudioView>();
|
||||
auto settings = receiver_model.settings();
|
||||
settings.frequency_step = field_step.selected_index_value();
|
||||
nav_.replace<AnalogAudioView>(settings);
|
||||
};
|
||||
|
||||
// Button to switch to Mic app
|
||||
button_mic_app.on_select = [this](Button&) {
|
||||
if (scan_thread)
|
||||
scan_thread->stop();
|
||||
nav_.pop();
|
||||
nav_.push<MicTXView>();
|
||||
// MicTX wants Modulation and Bandwidth overrides, but that's only stored on the RX model.
|
||||
nav_.replace<MicTXView>(receiver_model.settings());
|
||||
};
|
||||
|
||||
// Button to delete current frequency from scan Freq List
|
||||
@@ -447,6 +448,16 @@ ScannerView::ScannerView(
|
||||
|
||||
// Mode field was changed (AM/NFM/WFM)
|
||||
field_mode.on_change = [this](size_t, OptionsField::value_t v) {
|
||||
static freqman_index_t last_mode = AM_MODULATION;
|
||||
// unsupported SPEC mode fix
|
||||
if (v == SPEC_MODULATION) {
|
||||
if (last_mode == AM_MODULATION)
|
||||
v = WFM_MODULATION;
|
||||
else
|
||||
v = AM_MODULATION;
|
||||
field_mode.set_selected_index(v);
|
||||
}
|
||||
last_mode = v;
|
||||
receiver_model.disable();
|
||||
baseband::shutdown();
|
||||
change_mode((freqman_index_t)v);
|
||||
@@ -565,9 +576,9 @@ ScannerView::ScannerView(
|
||||
void ScannerView::frequency_file_load(std::string file_name, bool stop_all_before) {
|
||||
bool found_range{false};
|
||||
bool found_single{false};
|
||||
freqman_index_t def_mod_index{-1};
|
||||
freqman_index_t def_bw_index{-1};
|
||||
freqman_index_t def_step_index{-1};
|
||||
freqman_index_t def_mod_index{freqman_invalid_index};
|
||||
freqman_index_t def_bw_index{freqman_invalid_index};
|
||||
freqman_index_t def_step_index{freqman_invalid_index};
|
||||
|
||||
// stop everything running now if required
|
||||
if (stop_all_before) {
|
||||
@@ -576,56 +587,56 @@ void ScannerView::frequency_file_load(std::string file_name, bool stop_all_befor
|
||||
description_list.clear();
|
||||
}
|
||||
|
||||
if (load_freqman_file(file_name, database)) {
|
||||
loaded_file_name = file_name; // keep loaded filename in memory
|
||||
for (auto& entry : database) { // READ LINE PER LINE
|
||||
if (frequency_list.size() < FREQMAN_MAX_PER_FILE) { // We got space!
|
||||
//
|
||||
// Get modulation & bw & step from file if specified
|
||||
// Note these values could be different for each line in the file, but we only look at the first one
|
||||
//
|
||||
// Note that freqman requires a very specific string for these parameters,
|
||||
// so check syntax in frequency file if specified value isn't being loaded
|
||||
//
|
||||
if (def_mod_index == -1)
|
||||
def_mod_index = entry.modulation;
|
||||
if (load_freqman_file(file_name, database, {})) {
|
||||
loaded_file_name = file_name;
|
||||
for (auto& entry_ptr : database) {
|
||||
if (frequency_list.size() >= FREQMAN_MAX_PER_FILE)
|
||||
break;
|
||||
|
||||
if (def_bw_index == -1)
|
||||
def_bw_index = entry.bandwidth;
|
||||
auto& entry = *entry_ptr;
|
||||
|
||||
if (def_step_index == -1)
|
||||
def_step_index = entry.step;
|
||||
// Get modulation & bw & step from file if specified
|
||||
// Note these values could be different for each line in the file, but we only look at the first one
|
||||
//
|
||||
// Note that freqman requires a very specific string for these parameters,
|
||||
// so check syntax in frequency file if specified value isn't being loaded
|
||||
//
|
||||
if (is_invalid(def_mod_index))
|
||||
def_mod_index = entry.modulation;
|
||||
|
||||
// Get frequency
|
||||
if (entry.type == RANGE) {
|
||||
if (!found_range) {
|
||||
// Set Start & End Search Range instead of populating the small in-memory frequency table
|
||||
// NOTE: There may be multiple single frequencies in file, but only one search range is supported.
|
||||
found_range = true;
|
||||
frequency_range.min = entry.frequency_a;
|
||||
button_manual_start.set_text(to_string_short_freq(frequency_range.min));
|
||||
frequency_range.max = entry.frequency_b;
|
||||
button_manual_end.set_text(to_string_short_freq(frequency_range.max));
|
||||
}
|
||||
} else if (entry.type == SINGLE) {
|
||||
found_single = true;
|
||||
frequency_list.push_back(entry.frequency_a);
|
||||
description_list.push_back(entry.description);
|
||||
} else if (entry.type == HAMRADIO) {
|
||||
// For HAM repeaters, add both receive & transmit frequencies to scan list and modify description
|
||||
// (FUTURE fw versions might handle these differently)
|
||||
found_single = true;
|
||||
frequency_list.push_back(entry.frequency_a);
|
||||
description_list.push_back("R:" + entry.description);
|
||||
if (is_invalid(def_bw_index))
|
||||
def_bw_index = entry.bandwidth;
|
||||
|
||||
if ((entry.frequency_a != entry.frequency_b) &&
|
||||
(frequency_list.size() < FREQMAN_MAX_PER_FILE)) {
|
||||
frequency_list.push_back(entry.frequency_b);
|
||||
description_list.push_back("T:" + entry.description);
|
||||
}
|
||||
if (is_invalid(def_step_index))
|
||||
def_step_index = entry.step;
|
||||
|
||||
// Get frequency
|
||||
if (entry.type == freqman_type::Range) {
|
||||
if (!found_range) {
|
||||
// Set Start & End Search Range instead of populating the small in-memory frequency table
|
||||
// NOTE: There may be multiple single frequencies in file, but only one search range is supported.
|
||||
found_range = true;
|
||||
frequency_range.min = entry.frequency_a;
|
||||
button_manual_start.set_text(to_string_short_freq(frequency_range.min));
|
||||
frequency_range.max = entry.frequency_b;
|
||||
button_manual_end.set_text(to_string_short_freq(frequency_range.max));
|
||||
}
|
||||
} else if (entry.type == freqman_type::Single) {
|
||||
found_single = true;
|
||||
frequency_list.push_back(entry.frequency_a);
|
||||
description_list.push_back(entry.description);
|
||||
} else if (entry.type == freqman_type::HamRadio) {
|
||||
// For HAM repeaters, add both receive & transmit frequencies to scan list and modify description
|
||||
// (FUTURE fw versions might handle these differently)
|
||||
found_single = true;
|
||||
frequency_list.push_back(entry.frequency_a);
|
||||
description_list.push_back("R:" + entry.description);
|
||||
|
||||
if ((entry.frequency_a != entry.frequency_b) &&
|
||||
(frequency_list.size() < FREQMAN_MAX_PER_FILE)) {
|
||||
frequency_list.push_back(entry.frequency_b);
|
||||
description_list.push_back("T:" + entry.description);
|
||||
}
|
||||
} else {
|
||||
break; // No more space: Stop reading the txt file !
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -639,13 +650,13 @@ void ScannerView::frequency_file_load(std::string file_name, bool stop_all_befor
|
||||
desc_freq_list_scan = desc_freq_list_scan + "...";
|
||||
}
|
||||
|
||||
if ((def_mod_index != -1) && (def_mod_index != (freqman_index_t)field_mode.selected_index_value()))
|
||||
if (is_valid(def_mod_index) && def_mod_index != (freqman_index_t)field_mode.selected_index_value())
|
||||
field_mode.set_by_value(def_mod_index); // Update mode (also triggers a change callback that disables & reenables RF background)
|
||||
|
||||
if (def_bw_index != -1) // Update BW if specified in file
|
||||
if (is_valid(def_bw_index)) // Update BW if specified in file
|
||||
field_bw.set_selected_index(def_bw_index);
|
||||
|
||||
if (def_step_index != -1) // Update step if specified in file
|
||||
if (is_valid(def_step_index)) // Update step if specified in file
|
||||
field_step.set_selected_index(def_step_index);
|
||||
|
||||
audio::output::stop();
|
||||
|
||||
@@ -90,13 +90,13 @@ class ScannerView : public View {
|
||||
|
||||
std::string title() const override { return "Scanner"; };
|
||||
std::vector<rf::Frequency> frequency_list{};
|
||||
std::vector<string> description_list{};
|
||||
std::vector<std::string> description_list{};
|
||||
|
||||
// void set_parent_rect(const Rect new_parent_rect) override;
|
||||
|
||||
private:
|
||||
app_settings::SettingsManager settings_{
|
||||
"scanner", app_settings::Mode::RX};
|
||||
"rx_scanner", app_settings::Mode::RX};
|
||||
|
||||
NavigationView& nav_;
|
||||
RxRadioState radio_state_{};
|
||||
|
||||
@@ -122,17 +122,7 @@ void SearchView::do_detection() {
|
||||
locked = true;
|
||||
locked_bin = bin_max;
|
||||
|
||||
// TODO
|
||||
/*nav_.pop();
|
||||
receiver_model.disable();
|
||||
baseband::shutdown();
|
||||
nav_.pop();*/
|
||||
|
||||
/*if (options_goto.selected_index() == 1)
|
||||
nav_.push<AnalogAudioView>(false);
|
||||
else if (options_goto.selected_index() == 2)
|
||||
nav_.push<POCSAGAppView>();
|
||||
*/
|
||||
// TODO: open Audio.
|
||||
} else
|
||||
text_infos.set("Out of range");
|
||||
}
|
||||
@@ -382,7 +372,7 @@ SearchView::SearchView(
|
||||
this->on_range_changed();
|
||||
};
|
||||
field_frequency_min.on_edit = [this, &nav]() {
|
||||
auto new_view = nav.push<FrequencyKeypadView>(receiver_model.target_frequency());
|
||||
auto new_view = nav.push<FrequencyKeypadView>(receiver_model.target_frequency() - 1000000);
|
||||
new_view->on_changed = [this](rf::Frequency f) {
|
||||
this->field_frequency_min.set_value(f);
|
||||
};
|
||||
@@ -394,7 +384,7 @@ SearchView::SearchView(
|
||||
this->on_range_changed();
|
||||
};
|
||||
field_frequency_max.on_edit = [this, &nav]() {
|
||||
auto new_view = nav.push<FrequencyKeypadView>(receiver_model.target_frequency());
|
||||
auto new_view = nav.push<FrequencyKeypadView>(receiver_model.target_frequency() + 1000000);
|
||||
new_view->on_changed = [this](rf::Frequency f) {
|
||||
this->field_frequency_max.set_value(f);
|
||||
};
|
||||
@@ -414,7 +404,6 @@ SearchView::SearchView(
|
||||
|
||||
on_range_changed();
|
||||
|
||||
receiver_model.set_modulation(ReceiverModel::Mode::SpectrumAnalysis);
|
||||
receiver_model.enable();
|
||||
}
|
||||
|
||||
|
||||
@@ -92,8 +92,8 @@ class SearchView : public View {
|
||||
NavigationView& nav_;
|
||||
RxRadioState radio_state_{
|
||||
2500000 /* bandwidth */,
|
||||
SEARCH_SLICE_WIDTH /* sampling rate */
|
||||
};
|
||||
SEARCH_SLICE_WIDTH /* sampling rate */,
|
||||
ReceiverModel::Mode::SpectrumAnalysis};
|
||||
|
||||
struct slice_t {
|
||||
rf::Frequency center_frequency;
|
||||
@@ -147,11 +147,9 @@ class SearchView : public View {
|
||||
{{26 * 8, 25 * 8}, "MHz", Color::light_grey()}};
|
||||
|
||||
FrequencyField field_frequency_min{
|
||||
{1 * 8, 1 * 16},
|
||||
};
|
||||
{1 * 8, 1 * 16}};
|
||||
FrequencyField field_frequency_max{
|
||||
{11 * 8, 1 * 16},
|
||||
};
|
||||
{11 * 8, 1 * 16}};
|
||||
LNAGainField field_lna{
|
||||
{22 * 8, 1 * 16}};
|
||||
VGAGainField field_vga{
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include "ui_settings.hpp"
|
||||
|
||||
#include "ui_navigation.hpp"
|
||||
#include "ui_receiver.hpp"
|
||||
#include "ui_touch_calibration.hpp"
|
||||
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
@@ -32,16 +33,27 @@ using namespace lpc43xx;
|
||||
|
||||
#include "audio.hpp"
|
||||
#include "portapack.hpp"
|
||||
using portapack::receiver_model;
|
||||
using namespace portapack;
|
||||
|
||||
#include "file.hpp"
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
#include "string_format.hpp"
|
||||
#include "ui_styles.hpp"
|
||||
#include "cpld_update.hpp"
|
||||
|
||||
#include "freqman.hpp"
|
||||
namespace pmem = portapack::persistent_memory;
|
||||
|
||||
namespace ui {
|
||||
|
||||
/* Sends a UI refresh message to cause the status bar to redraw. */
|
||||
static void send_system_refresh() {
|
||||
StatusRefreshMessage message{};
|
||||
EventDispatcher::send_message(message);
|
||||
}
|
||||
|
||||
/* SetDateTimeView ***************************************/
|
||||
|
||||
SetDateTimeView::SetDateTimeView(
|
||||
NavigationView& nav) {
|
||||
button_save.on_select = [&nav, this](Button&) {
|
||||
@@ -105,13 +117,15 @@ SetDateTimeModel SetDateTimeView::form_collect() {
|
||||
.second = static_cast<uint8_t>(field_second.value())};
|
||||
}
|
||||
|
||||
/* SetRadioView ******************************************/
|
||||
|
||||
SetRadioView::SetRadioView(
|
||||
NavigationView& nav) {
|
||||
button_cancel.on_select = [&nav](Button&) {
|
||||
nav.pop();
|
||||
};
|
||||
|
||||
const auto reference = portapack::clock_manager.get_reference();
|
||||
const auto reference = clock_manager.get_reference();
|
||||
|
||||
std::string source_name("---");
|
||||
switch (reference.source) {
|
||||
@@ -156,19 +170,18 @@ SetRadioView::SetRadioView(
|
||||
&button_cancel});
|
||||
|
||||
SetFrequencyCorrectionModel model{
|
||||
static_cast<int8_t>(portapack::persistent_memory::correction_ppb() / 1000), 0};
|
||||
static_cast<int8_t>(pmem::correction_ppb() / 1000), 0};
|
||||
|
||||
form_init(model);
|
||||
|
||||
check_clkout.set_value(portapack::persistent_memory::clkout_enabled());
|
||||
check_clkout.set_value(pmem::clkout_enabled());
|
||||
check_clkout.on_select = [this](Checkbox&, bool v) {
|
||||
clock_manager.enable_clock_output(v);
|
||||
portapack::persistent_memory::set_clkout_enabled(v);
|
||||
StatusRefreshMessage message{};
|
||||
EventDispatcher::send_message(message);
|
||||
pmem::set_clkout_enabled(v);
|
||||
send_system_refresh();
|
||||
};
|
||||
|
||||
field_clkout_freq.set_value(portapack::persistent_memory::clkout_freq());
|
||||
field_clkout_freq.set_value(pmem::clkout_freq());
|
||||
value_freq_step.set_style(&Styles::light_grey);
|
||||
|
||||
field_clkout_freq.on_select = [this](NumberField&) {
|
||||
@@ -193,9 +206,9 @@ SetRadioView::SetRadioView(
|
||||
field_clkout_freq.set_step(pow(10, freq_step_khz));
|
||||
};
|
||||
|
||||
check_bias.set_value(portapack::get_antenna_bias());
|
||||
check_bias.set_value(get_antenna_bias());
|
||||
check_bias.on_select = [this](Checkbox&, bool v) {
|
||||
portapack::set_antenna_bias(v);
|
||||
set_antenna_bias(v);
|
||||
|
||||
// Update the radio.
|
||||
receiver_model.set_antenna_bias();
|
||||
@@ -205,15 +218,14 @@ SetRadioView::SetRadioView(
|
||||
if (!v)
|
||||
radio::set_antenna_bias(false);
|
||||
|
||||
StatusRefreshMessage message{};
|
||||
EventDispatcher::send_message(message);
|
||||
send_system_refresh();
|
||||
};
|
||||
|
||||
button_save.on_select = [this, &nav](Button&) {
|
||||
const auto model = this->form_collect();
|
||||
portapack::persistent_memory::set_correction_ppb(model.ppm * 1000);
|
||||
portapack::persistent_memory::set_clkout_freq(model.freq);
|
||||
clock_manager.enable_clock_output(portapack::persistent_memory::clkout_enabled());
|
||||
pmem::set_correction_ppb(model.ppm * 1000);
|
||||
pmem::set_clkout_freq(model.freq);
|
||||
clock_manager.enable_clock_output(pmem::clkout_enabled());
|
||||
nav.pop();
|
||||
};
|
||||
}
|
||||
@@ -233,54 +245,86 @@ SetFrequencyCorrectionModel SetRadioView::form_collect() {
|
||||
};
|
||||
}
|
||||
|
||||
/* SetUIView *********************************************/
|
||||
|
||||
SetUIView::SetUIView(NavigationView& nav) {
|
||||
add_children({&checkbox_disable_touchscreen,
|
||||
&checkbox_speaker,
|
||||
&checkbox_bloff,
|
||||
&options_bloff,
|
||||
&checkbox_showsplash,
|
||||
&checkbox_showclock,
|
||||
&options_clockformat,
|
||||
&checkbox_guireturnflag,
|
||||
&labels,
|
||||
&toggle_camera,
|
||||
&toggle_sleep,
|
||||
&toggle_stealth,
|
||||
&toggle_converter,
|
||||
&toggle_bias_tee,
|
||||
&toggle_clock,
|
||||
&toggle_mute,
|
||||
&toggle_sd_card,
|
||||
&button_save,
|
||||
&button_cancel});
|
||||
|
||||
checkbox_disable_touchscreen.set_value(persistent_memory::disable_touchscreen());
|
||||
checkbox_speaker.set_value(persistent_memory::config_speaker());
|
||||
checkbox_showsplash.set_value(persistent_memory::config_splash());
|
||||
checkbox_showclock.set_value(!persistent_memory::hide_clock());
|
||||
checkbox_guireturnflag.set_value(persistent_memory::show_gui_return_icon());
|
||||
// Display "Disable speaker" option only if AK4951 Codec which has separate speaker/headphone control
|
||||
if (audio::speaker_disable_supported()) {
|
||||
add_child(&toggle_speaker);
|
||||
}
|
||||
|
||||
const auto backlight_config = persistent_memory::config_backlight_timer();
|
||||
checkbox_disable_touchscreen.set_value(pmem::disable_touchscreen());
|
||||
checkbox_showsplash.set_value(pmem::config_splash());
|
||||
checkbox_showclock.set_value(!pmem::hide_clock());
|
||||
checkbox_guireturnflag.set_value(pmem::show_gui_return_icon());
|
||||
|
||||
const auto backlight_config = pmem::config_backlight_timer();
|
||||
checkbox_bloff.set_value(backlight_config.timeout_enabled());
|
||||
options_bloff.set_by_value(backlight_config.timeout_enum());
|
||||
|
||||
if (persistent_memory::clock_with_date()) {
|
||||
if (pmem::clock_with_date()) {
|
||||
options_clockformat.set_selected_index(1);
|
||||
} else {
|
||||
options_clockformat.set_selected_index(0);
|
||||
}
|
||||
|
||||
// NB: Invert so "active" == "not hidden"
|
||||
toggle_camera.set_value(!pmem::ui_hide_camera());
|
||||
toggle_sleep.set_value(!pmem::ui_hide_sleep());
|
||||
toggle_stealth.set_value(!pmem::ui_hide_stealth());
|
||||
toggle_converter.set_value(!pmem::ui_hide_converter());
|
||||
toggle_bias_tee.set_value(!pmem::ui_hide_bias_tee());
|
||||
toggle_clock.set_value(!pmem::ui_hide_clock());
|
||||
toggle_speaker.set_value(!pmem::ui_hide_speaker());
|
||||
toggle_mute.set_value(!pmem::ui_hide_mute());
|
||||
toggle_sd_card.set_value(!pmem::ui_hide_sd_card());
|
||||
|
||||
button_save.on_select = [&nav, this](Button&) {
|
||||
persistent_memory::set_config_backlight_timer({(persistent_memory::backlight_timeout_t)options_bloff.selected_index_value(),
|
||||
checkbox_bloff.value()});
|
||||
pmem::set_config_backlight_timer({(pmem::backlight_timeout_t)options_bloff.selected_index_value(),
|
||||
checkbox_bloff.value()});
|
||||
|
||||
if (checkbox_showclock.value()) {
|
||||
if (options_clockformat.selected_index() == 1)
|
||||
persistent_memory::set_clock_with_date(true);
|
||||
pmem::set_clock_with_date(true);
|
||||
else
|
||||
persistent_memory::set_clock_with_date(false);
|
||||
pmem::set_clock_with_date(false);
|
||||
}
|
||||
|
||||
if (checkbox_speaker.value()) audio::output::speaker_mute(); // Just mute audio if speaker is disabled
|
||||
persistent_memory::set_config_speaker(checkbox_speaker.value()); // Store Speaker status
|
||||
StatusRefreshMessage message{}; // Refresh status bar with/out speaker
|
||||
EventDispatcher::send_message(message);
|
||||
pmem::set_config_splash(checkbox_showsplash.value());
|
||||
pmem::set_clock_hidden(!checkbox_showclock.value());
|
||||
pmem::set_gui_return_icon(checkbox_guireturnflag.value());
|
||||
pmem::set_disable_touchscreen(checkbox_disable_touchscreen.value());
|
||||
|
||||
pmem::set_ui_hide_camera(!toggle_camera.value());
|
||||
pmem::set_ui_hide_sleep(!toggle_sleep.value());
|
||||
pmem::set_ui_hide_stealth(!toggle_stealth.value());
|
||||
pmem::set_ui_hide_converter(!toggle_converter.value());
|
||||
pmem::set_ui_hide_bias_tee(!toggle_bias_tee.value());
|
||||
pmem::set_ui_hide_clock(!toggle_clock.value());
|
||||
pmem::set_ui_hide_speaker(!toggle_speaker.value());
|
||||
pmem::set_ui_hide_mute(!toggle_mute.value());
|
||||
pmem::set_ui_hide_sd_card(!toggle_sd_card.value());
|
||||
send_system_refresh();
|
||||
|
||||
persistent_memory::set_config_splash(checkbox_showsplash.value());
|
||||
persistent_memory::set_clock_hidden(!checkbox_showclock.value());
|
||||
persistent_memory::set_gui_return_icon(checkbox_guireturnflag.value());
|
||||
persistent_memory::set_disable_touchscreen(checkbox_disable_touchscreen.value());
|
||||
nav.pop();
|
||||
};
|
||||
button_cancel.on_select = [&nav, this](Button&) {
|
||||
@@ -292,21 +336,20 @@ void SetUIView::focus() {
|
||||
button_save.focus();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// Appl. Settings
|
||||
// ---------------------------------------------------------
|
||||
/* SetAppSettingsView ************************************/
|
||||
|
||||
SetAppSettingsView::SetAppSettingsView(NavigationView& nav) {
|
||||
add_children({&checkbox_load_app_settings,
|
||||
&checkbox_save_app_settings,
|
||||
&button_save,
|
||||
&button_cancel});
|
||||
|
||||
checkbox_load_app_settings.set_value(persistent_memory::load_app_settings());
|
||||
checkbox_save_app_settings.set_value(persistent_memory::save_app_settings());
|
||||
checkbox_load_app_settings.set_value(pmem::load_app_settings());
|
||||
checkbox_save_app_settings.set_value(pmem::save_app_settings());
|
||||
|
||||
button_save.on_select = [&nav, this](Button&) {
|
||||
persistent_memory::set_load_app_settings(checkbox_load_app_settings.value());
|
||||
persistent_memory::set_save_app_settings(checkbox_save_app_settings.value());
|
||||
pmem::set_load_app_settings(checkbox_load_app_settings.value());
|
||||
pmem::set_save_app_settings(checkbox_save_app_settings.value());
|
||||
nav.pop();
|
||||
};
|
||||
button_cancel.on_select = [&nav, this](Button&) {
|
||||
@@ -318,9 +361,8 @@ void SetAppSettingsView::focus() {
|
||||
button_save.focus();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// Converter Settings
|
||||
// ---------------------------------------------------------
|
||||
/* SetConverterSettingsView ******************************/
|
||||
|
||||
SetConverterSettingsView::SetConverterSettingsView(NavigationView& nav) {
|
||||
add_children({&check_show_converter,
|
||||
&check_converter,
|
||||
@@ -328,46 +370,43 @@ SetConverterSettingsView::SetConverterSettingsView(NavigationView& nav) {
|
||||
&button_converter_freq,
|
||||
&button_return});
|
||||
|
||||
check_show_converter.set_value(!portapack::persistent_memory::config_hide_converter());
|
||||
check_show_converter.set_value(!pmem::ui_hide_converter());
|
||||
check_show_converter.on_select = [this](Checkbox&, bool v) {
|
||||
portapack::persistent_memory::set_config_hide_converter(!v);
|
||||
pmem::set_ui_hide_converter(!v);
|
||||
if (!v) {
|
||||
check_converter.set_value(false);
|
||||
}
|
||||
// Retune to take converter change in account.
|
||||
receiver_model.set_target_frequency(receiver_model.target_frequency());
|
||||
// Refresh status bar with/out converter
|
||||
StatusRefreshMessage message{};
|
||||
EventDispatcher::send_message(message);
|
||||
send_system_refresh();
|
||||
};
|
||||
|
||||
check_converter.set_value(portapack::persistent_memory::config_converter());
|
||||
check_converter.set_value(pmem::config_converter());
|
||||
check_converter.on_select = [this](Checkbox&, bool v) {
|
||||
if (v) {
|
||||
check_show_converter.set_value(true);
|
||||
portapack::persistent_memory::set_config_hide_converter(false);
|
||||
pmem::set_ui_hide_converter(false);
|
||||
}
|
||||
portapack::persistent_memory::set_config_converter(v);
|
||||
pmem::set_config_converter(v);
|
||||
// Retune to take converter change in account
|
||||
receiver_model.set_target_frequency(receiver_model.target_frequency());
|
||||
// Refresh status bar with/out converter
|
||||
StatusRefreshMessage message{};
|
||||
EventDispatcher::send_message(message);
|
||||
send_system_refresh();
|
||||
};
|
||||
|
||||
converter_mode.set_by_value(portapack::persistent_memory::config_updown_converter());
|
||||
converter_mode.set_by_value(pmem::config_updown_converter());
|
||||
converter_mode.on_change = [this](size_t, OptionsField::value_t v) {
|
||||
portapack::persistent_memory::set_config_updown_converter(v);
|
||||
pmem::set_config_updown_converter(v);
|
||||
// Refresh status bar with icon up or down
|
||||
StatusRefreshMessage message{};
|
||||
EventDispatcher::send_message(message);
|
||||
send_system_refresh();
|
||||
};
|
||||
|
||||
button_converter_freq.set_text(to_string_short_freq(portapack::persistent_memory::config_converter_freq()) + "MHz");
|
||||
button_converter_freq.set_text(to_string_short_freq(pmem::config_converter_freq()) + "MHz");
|
||||
button_converter_freq.on_select = [this, &nav](Button& button) {
|
||||
auto new_view = nav.push<FrequencyKeypadView>(portapack::persistent_memory::config_converter_freq());
|
||||
auto new_view = nav.push<FrequencyKeypadView>(pmem::config_converter_freq());
|
||||
new_view->on_changed = [this, &button](rf::Frequency f) {
|
||||
portapack::persistent_memory::set_config_converter_freq(f);
|
||||
pmem::set_config_converter_freq(f);
|
||||
// Retune to take converter change in account
|
||||
receiver_model.set_target_frequency(receiver_model.target_frequency());
|
||||
button_converter_freq.set_text("<" + to_string_short_freq(f) + " MHz>");
|
||||
@@ -383,9 +422,8 @@ void SetConverterSettingsView::focus() {
|
||||
button_return.focus();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// Frequency Correction Settings
|
||||
// ---------------------------------------------------------
|
||||
/* SetFrequencyCorrectionView ****************************/
|
||||
|
||||
SetFrequencyCorrectionView::SetFrequencyCorrectionView(NavigationView& nav) {
|
||||
add_children({&text_freqCorrection_about,
|
||||
&frequency_rx_correction_mode,
|
||||
@@ -394,36 +432,36 @@ SetFrequencyCorrectionView::SetFrequencyCorrectionView(NavigationView& nav) {
|
||||
&button_freq_tx_correction,
|
||||
&button_return});
|
||||
|
||||
frequency_rx_correction_mode.set_by_value(portapack::persistent_memory::config_freq_rx_correction_updown());
|
||||
frequency_rx_correction_mode.set_by_value(pmem::config_freq_rx_correction_updown());
|
||||
frequency_rx_correction_mode.on_change = [this](size_t, OptionsField::value_t v) {
|
||||
portapack::persistent_memory::set_freq_rx_correction_updown(v);
|
||||
pmem::set_freq_rx_correction_updown(v);
|
||||
};
|
||||
|
||||
frequency_tx_correction_mode.set_by_value(portapack::persistent_memory::config_freq_rx_correction_updown());
|
||||
frequency_tx_correction_mode.set_by_value(pmem::config_freq_rx_correction_updown());
|
||||
frequency_tx_correction_mode.on_change = [this](size_t, OptionsField::value_t v) {
|
||||
portapack::persistent_memory::set_freq_tx_correction_updown(v);
|
||||
pmem::set_freq_tx_correction_updown(v);
|
||||
};
|
||||
|
||||
button_freq_rx_correction.set_text(to_string_short_freq(portapack::persistent_memory::config_freq_rx_correction()) + "MHz (Rx)");
|
||||
button_freq_rx_correction.set_text(to_string_short_freq(pmem::config_freq_rx_correction()) + "MHz (Rx)");
|
||||
button_freq_rx_correction.on_select = [this, &nav](Button& button) {
|
||||
auto new_view = nav.push<FrequencyKeypadView>(portapack::persistent_memory::config_converter_freq());
|
||||
auto new_view = nav.push<FrequencyKeypadView>(pmem::config_converter_freq());
|
||||
new_view->on_changed = [this, &button](rf::Frequency f) {
|
||||
if (f >= MAX_FREQ_CORRECTION)
|
||||
f = MAX_FREQ_CORRECTION;
|
||||
portapack::persistent_memory::set_config_freq_rx_correction(f);
|
||||
pmem::set_config_freq_rx_correction(f);
|
||||
// Retune to take converter change in account
|
||||
receiver_model.set_target_frequency(receiver_model.target_frequency());
|
||||
button_freq_rx_correction.set_text("<" + to_string_short_freq(f) + " MHz>");
|
||||
};
|
||||
};
|
||||
|
||||
button_freq_tx_correction.set_text(to_string_short_freq(portapack::persistent_memory::config_freq_tx_correction()) + "MHz (Tx)");
|
||||
button_freq_tx_correction.set_text(to_string_short_freq(pmem::config_freq_tx_correction()) + "MHz (Tx)");
|
||||
button_freq_tx_correction.on_select = [this, &nav](Button& button) {
|
||||
auto new_view = nav.push<FrequencyKeypadView>(portapack::persistent_memory::config_converter_freq());
|
||||
auto new_view = nav.push<FrequencyKeypadView>(pmem::config_converter_freq());
|
||||
new_view->on_changed = [this, &button](rf::Frequency f) {
|
||||
if (f >= MAX_FREQ_CORRECTION)
|
||||
f = MAX_FREQ_CORRECTION;
|
||||
portapack::persistent_memory::set_config_freq_tx_correction(f);
|
||||
pmem::set_config_freq_tx_correction(f);
|
||||
// Retune to take converter change in account
|
||||
receiver_model.set_target_frequency(receiver_model.target_frequency());
|
||||
button_freq_tx_correction.set_text("<" + to_string_short_freq(f) + " MHz>");
|
||||
@@ -439,9 +477,8 @@ void SetFrequencyCorrectionView::focus() {
|
||||
button_return.focus();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// Persistent Memory Settings
|
||||
// ---------------------------------------------------------
|
||||
/* SetPersistentMemoryView *******************************/
|
||||
|
||||
SetPersistentMemoryView::SetPersistentMemoryView(NavigationView& nav) {
|
||||
add_children({&text_pmem_about,
|
||||
&text_pmem_informations,
|
||||
@@ -452,48 +489,42 @@ SetPersistentMemoryView::SetPersistentMemoryView(NavigationView& nav) {
|
||||
&button_load_mem_defaults,
|
||||
&button_return});
|
||||
|
||||
check_use_sdcard_for_pmem.set_value(portapack::persistent_memory::should_use_sdcard_for_pmem());
|
||||
check_use_sdcard_for_pmem.set_value(pmem::should_use_sdcard_for_pmem());
|
||||
check_use_sdcard_for_pmem.on_select = [this](Checkbox&, bool v) {
|
||||
File pmem_flag_file_handle;
|
||||
std::string pmem_flag_file = PMEM_FILEFLAG;
|
||||
if (v) {
|
||||
auto result = pmem_flag_file_handle.open(pmem_flag_file);
|
||||
if (result.is_valid()) {
|
||||
auto result = pmem_flag_file_handle.create(pmem_flag_file); // third: create if it is not there
|
||||
if (!result.is_valid()) {
|
||||
text_pmem_status.set("pmem flag file created");
|
||||
} else {
|
||||
text_pmem_status.set("!err. creating pmem flagfile!");
|
||||
}
|
||||
} else {
|
||||
if (fs::file_exists(PMEM_FILEFLAG)) {
|
||||
text_pmem_status.set("pmem flag already present");
|
||||
} else {
|
||||
auto error = pmem_flag_file_handle.create(PMEM_FILEFLAG);
|
||||
if (error)
|
||||
text_pmem_status.set("!err. creating pmem flagfile!");
|
||||
else
|
||||
text_pmem_status.set("pmem flag file created");
|
||||
}
|
||||
} else {
|
||||
auto result = delete_file(pmem_flag_file);
|
||||
if (result.code() != FR_OK) {
|
||||
auto result = delete_file(PMEM_FILEFLAG);
|
||||
if (result.code() != FR_OK)
|
||||
text_pmem_status.set("!err. deleting pmem flagfile!");
|
||||
} else {
|
||||
else
|
||||
text_pmem_status.set("pmem flag file deleted");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
button_save_mem_to_file.on_select = [&nav, this](Button&) {
|
||||
if (!portapack::persistent_memory::save_persistent_settings_to_file()) {
|
||||
if (!pmem::save_persistent_settings_to_file())
|
||||
text_pmem_status.set("!problem saving settings!");
|
||||
} else {
|
||||
else
|
||||
text_pmem_status.set("settings saved");
|
||||
}
|
||||
};
|
||||
|
||||
button_load_mem_from_file.on_select = [&nav, this](Button&) {
|
||||
if (!portapack::persistent_memory::load_persistent_settings_from_file()) {
|
||||
if (!pmem::load_persistent_settings_from_file()) {
|
||||
text_pmem_status.set("!problem loading settings!");
|
||||
} else {
|
||||
text_pmem_status.set("settings loaded");
|
||||
// Refresh status bar with icon up or down
|
||||
StatusRefreshMessage message{};
|
||||
EventDispatcher::send_message(message);
|
||||
send_system_refresh();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -504,7 +535,7 @@ SetPersistentMemoryView::SetPersistentMemoryView(NavigationView& nav) {
|
||||
YESNO,
|
||||
[this](bool choice) {
|
||||
if (choice) {
|
||||
portapack::persistent_memory::cache::defaults();
|
||||
pmem::cache::defaults();
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -518,19 +549,19 @@ void SetPersistentMemoryView::focus() {
|
||||
button_return.focus();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// Audio Settings
|
||||
// ---------------------------------------------------------
|
||||
/* SetAudioView ******************************************/
|
||||
|
||||
SetAudioView::SetAudioView(NavigationView& nav) {
|
||||
add_children({&labels,
|
||||
&field_tone_mix,
|
||||
&button_save,
|
||||
&button_cancel});
|
||||
|
||||
field_tone_mix.set_value(persistent_memory::tone_mix());
|
||||
field_tone_mix.set_value(pmem::tone_mix());
|
||||
|
||||
button_save.on_select = [&nav, this](Button&) {
|
||||
persistent_memory::set_tone_mix(field_tone_mix.value());
|
||||
pmem::set_tone_mix(field_tone_mix.value());
|
||||
audio::output::update_audio_mute();
|
||||
nav.pop();
|
||||
};
|
||||
|
||||
@@ -543,18 +574,17 @@ void SetAudioView::focus() {
|
||||
button_save.focus();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// QR Code Settings
|
||||
// ------------------------------------------------------
|
||||
/* SetQRCodeView *****************************************/
|
||||
|
||||
SetQRCodeView::SetQRCodeView(NavigationView& nav) {
|
||||
add_children({&checkbox_bigger_qr,
|
||||
&button_save,
|
||||
&button_cancel});
|
||||
|
||||
checkbox_bigger_qr.set_value(persistent_memory::show_bigger_qr_code());
|
||||
checkbox_bigger_qr.set_value(pmem::show_bigger_qr_code());
|
||||
|
||||
button_save.on_select = [&nav, this](Button&) {
|
||||
persistent_memory::set_show_bigger_qr_code(checkbox_bigger_qr.value());
|
||||
pmem::set_show_bigger_qr_code(checkbox_bigger_qr.value());
|
||||
nav.pop();
|
||||
};
|
||||
|
||||
@@ -567,19 +597,18 @@ void SetQRCodeView::focus() {
|
||||
button_save.focus();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// Rotary Encoder Dial Settings
|
||||
// ---------------------------------------------------------
|
||||
/* SetEncoderDialView ************************************/
|
||||
|
||||
SetEncoderDialView::SetEncoderDialView(NavigationView& nav) {
|
||||
add_children({&labels,
|
||||
&field_encoder_dial_sensitivity,
|
||||
&button_save,
|
||||
&button_cancel});
|
||||
|
||||
field_encoder_dial_sensitivity.set_by_value(persistent_memory::config_encoder_dial_sensitivity());
|
||||
field_encoder_dial_sensitivity.set_by_value(pmem::config_encoder_dial_sensitivity());
|
||||
|
||||
button_save.on_select = [&nav, this](Button&) {
|
||||
persistent_memory::set_encoder_dial_sensitivity(field_encoder_dial_sensitivity.selected_index_value());
|
||||
pmem::set_encoder_dial_sensitivity(field_encoder_dial_sensitivity.selected_index_value());
|
||||
nav.pop();
|
||||
};
|
||||
|
||||
@@ -592,11 +621,10 @@ void SetEncoderDialView::focus() {
|
||||
button_save.focus();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// Settings main menu
|
||||
// ---------------------------------------------------------
|
||||
/* SettingsMenuView **************************************/
|
||||
|
||||
SettingsMenuView::SettingsMenuView(NavigationView& nav) {
|
||||
if (portapack::persistent_memory::show_gui_return_icon()) {
|
||||
if (pmem::show_gui_return_icon()) {
|
||||
add_items({{"..", ui::Color::light_grey(), &bitmap_icon_previous, [&nav]() { nav.pop(); }}});
|
||||
}
|
||||
add_items({
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include "ui_widget.hpp"
|
||||
#include "ui_menu.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "bitmap.hpp"
|
||||
#include "ff.h"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
|
||||
@@ -34,7 +35,7 @@
|
||||
|
||||
namespace ui {
|
||||
|
||||
#define MAX_FREQ_CORRECTION 4294967295 // maximum possible for an uint32_t
|
||||
#define MAX_FREQ_CORRECTION INT32_MAX
|
||||
|
||||
struct SetDateTimeModel {
|
||||
uint16_t year;
|
||||
@@ -208,21 +209,16 @@ class SetUIView : public View {
|
||||
|
||||
private:
|
||||
Checkbox checkbox_disable_touchscreen{
|
||||
{3 * 8, 2 * 16},
|
||||
{3 * 8, 1 * 16},
|
||||
20,
|
||||
"Disable touchscreen"};
|
||||
|
||||
Checkbox checkbox_speaker{
|
||||
{3 * 8, 4 * 16},
|
||||
20,
|
||||
"Hide H1 Speaker option"};
|
||||
|
||||
Checkbox checkbox_bloff{
|
||||
{3 * 8, 6 * 16},
|
||||
{3 * 8, 3 * 16},
|
||||
20,
|
||||
"Backlight off after:"};
|
||||
OptionsField options_bloff{
|
||||
{52, 7 * 16 + 8},
|
||||
{60, 4 * 16 + 8},
|
||||
20,
|
||||
{
|
||||
{"5 seconds", backlight_timeout_t::Timeout5Sec},
|
||||
@@ -236,25 +232,65 @@ class SetUIView : public View {
|
||||
}};
|
||||
|
||||
Checkbox checkbox_showsplash{
|
||||
{3 * 8, 9 * 16},
|
||||
{3 * 8, 6 * 16},
|
||||
20,
|
||||
"Show splash"};
|
||||
|
||||
Checkbox checkbox_showclock{
|
||||
{3 * 8, 11 * 16},
|
||||
{3 * 8, 8 * 16},
|
||||
20,
|
||||
"Show clock with:"};
|
||||
|
||||
OptionsField options_clockformat{
|
||||
{52, 12 * 16 + 8},
|
||||
{60, 9 * 16 + 8},
|
||||
20,
|
||||
{{"time only", 0},
|
||||
{"time and date", 1}}};
|
||||
|
||||
Checkbox checkbox_guireturnflag{
|
||||
{3 * 8, 14 * 16},
|
||||
25,
|
||||
"add return icon in GUI"};
|
||||
{3 * 8, 11 * 16},
|
||||
20,
|
||||
"Back button in menu"};
|
||||
|
||||
Labels labels{
|
||||
{{3 * 8, 13 * 16}, "Show/Hide Status Icons", Color::light_grey()},
|
||||
};
|
||||
|
||||
ImageToggle toggle_camera{
|
||||
{7 * 8, 14 * 16 + 2, 16, 16},
|
||||
&bitmap_icon_camera};
|
||||
|
||||
ImageToggle toggle_sleep{
|
||||
{9 * 8, 14 * 16 + 2, 16, 16},
|
||||
&bitmap_icon_sleep};
|
||||
|
||||
ImageToggle toggle_stealth{
|
||||
{11 * 8, 14 * 16 + 2, 16, 16},
|
||||
&bitmap_icon_stealth};
|
||||
|
||||
ImageToggle toggle_converter{
|
||||
{13 * 8, 14 * 16 + 2, 16, 16},
|
||||
&bitmap_icon_upconvert};
|
||||
|
||||
ImageToggle toggle_bias_tee{
|
||||
{15 * 8, 14 * 16 + 2, 16, 16},
|
||||
&bitmap_icon_biast_off};
|
||||
|
||||
ImageToggle toggle_clock{
|
||||
{17 * 8, 14 * 16 + 2, 8, 16},
|
||||
&bitmap_icon_clk_ext};
|
||||
|
||||
ImageToggle toggle_mute{
|
||||
{18 * 8, 14 * 16 + 2, 16, 16},
|
||||
&bitmap_icon_speaker_and_headphones_mute};
|
||||
|
||||
ImageToggle toggle_speaker{
|
||||
{20 * 8, 14 * 16 + 2, 16, 16},
|
||||
&bitmap_icon_speaker_mute};
|
||||
|
||||
ImageToggle toggle_sd_card{
|
||||
{22 * 8, 14 * 16 + 2, 16, 16},
|
||||
&bitmap_sd_card_ok};
|
||||
|
||||
Button button_save{
|
||||
{2 * 8, 16 * 16, 12 * 8, 32},
|
||||
|
||||
@@ -41,7 +41,8 @@ void SondeLogger::on_packet(const sonde::Packet& packet) {
|
||||
|
||||
namespace ui {
|
||||
|
||||
SondeView::SondeView(NavigationView& nav) {
|
||||
SondeView::SondeView(NavigationView& nav)
|
||||
: nav_{nav} {
|
||||
baseband::run_image(portapack::spi_flash::image_tag_sonde);
|
||||
|
||||
add_children({&labels,
|
||||
@@ -66,20 +67,9 @@ SondeView::SondeView(NavigationView& nav) {
|
||||
&button_see_map});
|
||||
|
||||
if (!settings_.loaded())
|
||||
receiver_model.set_target_frequency(initial_target_frequency);
|
||||
field_frequency.set_value(initial_target_frequency);
|
||||
|
||||
field_frequency.set_value(receiver_model.target_frequency());
|
||||
field_frequency.set_step(500); // euquiq: was 10000, but we are using this for fine-tunning
|
||||
field_frequency.on_change = [this](rf::Frequency f) {
|
||||
receiver_model.set_target_frequency(f);
|
||||
};
|
||||
field_frequency.on_edit = [this, &nav]() {
|
||||
// TODO: Provide separate modal method/scheme?
|
||||
auto new_view = nav.push<FrequencyKeypadView>(receiver_model.target_frequency());
|
||||
new_view->on_changed = [this](rf::Frequency f) {
|
||||
field_frequency.set_value(f);
|
||||
};
|
||||
};
|
||||
|
||||
geopos.set_read_only(true);
|
||||
|
||||
@@ -117,7 +107,6 @@ SondeView::SondeView(NavigationView& nav) {
|
||||
logger->append(LOG_ROOT_DIR "/SONDE.TXT");
|
||||
|
||||
audio::output::start();
|
||||
audio::output::speaker_unmute();
|
||||
|
||||
// inject a PitchRSSIConfigureMessage in order to arm
|
||||
// the pitch rssi events that will be used by the
|
||||
@@ -138,7 +127,7 @@ SondeView::~SondeView() {
|
||||
}
|
||||
|
||||
void SondeView::focus() {
|
||||
field_vga.focus();
|
||||
field_frequency.focus();
|
||||
}
|
||||
|
||||
// used to convert float to character pointer, since unfortunately function like
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
#include "ui_navigation.hpp"
|
||||
#include "ui_receiver.hpp"
|
||||
#include "ui_freq_field.hpp"
|
||||
#include "ui_rssi.hpp"
|
||||
#include "ui_qrcode.hpp"
|
||||
#include "ui_geomap.hpp"
|
||||
@@ -65,6 +66,7 @@ class SondeView : public View {
|
||||
std::string title() const override { return "Radiosnd RX"; };
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
RxRadioState radio_state_{
|
||||
1750000 /* bandwidth */,
|
||||
2457600 /* sampling rate */
|
||||
@@ -96,9 +98,9 @@ class SondeView : public View {
|
||||
{{4 * 8, 7 * 16}, "Temp:", Color::light_grey()},
|
||||
{{0 * 8, 8 * 16}, "Humidity:", Color::light_grey()}};
|
||||
|
||||
FrequencyField field_frequency{
|
||||
RxFrequencyField field_frequency{
|
||||
{0 * 8, 0 * 8},
|
||||
};
|
||||
nav_};
|
||||
|
||||
RFAmpField field_rf_amp{
|
||||
{13 * 8, 0 * 16}};
|
||||
@@ -110,8 +112,7 @@ class SondeView : public View {
|
||||
{18 * 8, 0 * 16}};
|
||||
|
||||
RSSI rssi{
|
||||
{21 * 8, 0, 6 * 8, 4},
|
||||
};
|
||||
{21 * 8, 0, 6 * 8, 4}};
|
||||
|
||||
AudioVolumeField field_volume{
|
||||
{28 * 8, 0 * 16}};
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
*/
|
||||
|
||||
#include "ui_spectrum_painter.hpp"
|
||||
#include "cpld_update.hpp"
|
||||
#include "bmp.hpp"
|
||||
#include "baseband_api.hpp"
|
||||
|
||||
@@ -58,17 +57,9 @@ SpectrumPainterView::SpectrumPainterView(
|
||||
input_image.set_parent_rect(view_rect);
|
||||
input_text.set_parent_rect(view_rect);
|
||||
|
||||
field_frequency.set_value(transmitter_model.target_frequency());
|
||||
freqman_set_bandwidth_option(SPEC_MODULATION, option_bandwidth);
|
||||
|
||||
field_frequency.set_step(5000);
|
||||
field_frequency.on_change = [this](rf::Frequency f) {
|
||||
transmitter_model.set_target_frequency(f);
|
||||
};
|
||||
field_frequency.on_edit = [this, &nav]() {
|
||||
auto new_view = nav.push<FrequencyKeypadView>(transmitter_model.target_frequency());
|
||||
new_view->on_changed = [this](rf::Frequency f) {
|
||||
field_frequency.set_value(f);
|
||||
};
|
||||
};
|
||||
|
||||
tx_gain = transmitter_model.tx_gain();
|
||||
field_rfgain.set_value(tx_gain); // Initial default value (-12 dB's max ).
|
||||
@@ -176,7 +167,6 @@ void SpectrumPainterView::frame_sync() {
|
||||
|
||||
SpectrumPainterView::~SpectrumPainterView() {
|
||||
transmitter_model.disable();
|
||||
hackrf::cpld::load_sram_no_verify();
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
|
||||
@@ -94,9 +94,9 @@ class SpectrumPainterView : public View {
|
||||
{{1 * 8, footer_location + 2 * 16}, "BW: Du: P:", Color::light_grey()},
|
||||
};
|
||||
|
||||
FrequencyField field_frequency{
|
||||
TxFrequencyField field_frequency{
|
||||
{0 * 8, footer_location + 1 * 16},
|
||||
};
|
||||
nav_};
|
||||
|
||||
NumberField field_rfgain{
|
||||
{14 * 8, footer_location + 1 * 16},
|
||||
@@ -127,7 +127,7 @@ class SpectrumPainterView : public View {
|
||||
OptionsField option_bandwidth{
|
||||
{4 * 8, footer_location + 2 * 16},
|
||||
5,
|
||||
{BW_OPTIONS}};
|
||||
{}};
|
||||
|
||||
NumberField field_duration{
|
||||
{13 * 8, footer_location + 2 * 16},
|
||||
|
||||
@@ -26,6 +26,8 @@ namespace fs = std::filesystem;
|
||||
|
||||
namespace ui {
|
||||
|
||||
const std::filesystem::path splash_dot_bmp{u"/splash.bmp"};
|
||||
|
||||
ScreenshotViewer::ScreenshotViewer(
|
||||
NavigationView& nav,
|
||||
const std::filesystem::path& path)
|
||||
@@ -40,11 +42,9 @@ bool ScreenshotViewer::on_key(KeyEvent) {
|
||||
}
|
||||
|
||||
void ScreenshotViewer::paint(Painter& painter) {
|
||||
constexpr size_t pixel_width = 240;
|
||||
constexpr size_t pixel_height = 320;
|
||||
File file{};
|
||||
|
||||
painter.fill_rectangle({0, 0, pixel_width, pixel_height}, Color::black());
|
||||
painter.fill_rectangle({0, 0, screen_width, screen_height}, Color::black());
|
||||
|
||||
auto show_invalid = [&]() {
|
||||
painter.draw_string({10, 160}, Styles::white, "Not a valid screenshot.");
|
||||
@@ -65,19 +65,19 @@ void ScreenshotViewer::paint(Painter& painter) {
|
||||
constexpr size_t read_chunk = 80; // NB: must be a factor of pixel_width.
|
||||
constexpr size_t buffer_size = sizeof(ColorRGB888) * read_chunk;
|
||||
uint8_t buffer[buffer_size];
|
||||
std::array<Color, pixel_width> pixel_data;
|
||||
std::array<Color, screen_width> pixel_data;
|
||||
|
||||
// Seek past all the headers.
|
||||
file.seek(43);
|
||||
|
||||
for (auto line = 0u; line < pixel_height; ++line) {
|
||||
for (auto line = 0u; line < screen_height; ++line) {
|
||||
// Seek past the per-line header.
|
||||
file.seek(file.tell() + 6);
|
||||
|
||||
// Per comment in PNGWriter, read in chunks of 80.
|
||||
// NB: Reading in one large chunk caused corruption so there's
|
||||
// likely a bug lurking in the SD Card/FatFs layer.
|
||||
for (auto offset = 0u; offset < pixel_width; offset += read_chunk) {
|
||||
for (auto offset = 0u; offset < screen_width; offset += read_chunk) {
|
||||
auto read = file.read(buffer, buffer_size);
|
||||
|
||||
if (!read || *read != buffer_size) {
|
||||
@@ -92,8 +92,42 @@ void ScreenshotViewer::paint(Painter& painter) {
|
||||
}
|
||||
}
|
||||
|
||||
display.draw_pixels({0, (int)line, pixel_width, 1}, pixel_data);
|
||||
display.draw_pixels({0, (int)line, screen_width, 1}, pixel_data);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
SplashViewer::SplashViewer(
|
||||
NavigationView& nav,
|
||||
const std::filesystem::path& path)
|
||||
: nav_{nav},
|
||||
path_{path} {
|
||||
valid_image = false;
|
||||
set_focusable(true);
|
||||
}
|
||||
|
||||
bool SplashViewer::on_key(const KeyEvent key) {
|
||||
if (valid_image && key == KeyEvent::Right) {
|
||||
delete_file(splash_dot_bmp);
|
||||
copy_file(path_, splash_dot_bmp);
|
||||
}
|
||||
|
||||
nav_.pop();
|
||||
return true;
|
||||
}
|
||||
|
||||
void SplashViewer::paint(Painter& painter) {
|
||||
painter.fill_rectangle({0, 0, screen_width, screen_height}, Color::black());
|
||||
|
||||
if (!portapack::display.drawBMP2({0, 0}, path_)) {
|
||||
painter.draw_string({10, 160}, Styles::white, "Not a valid splash image.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Show option to set splash screen if it's not already the splash screen
|
||||
if (!path_iequal(path_, splash_dot_bmp)) {
|
||||
painter.draw_string({0, 0}, Styles::white, "*RIGHT BUTTON UPDATES SPLASH*");
|
||||
valid_image = true;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
|
||||
@@ -31,6 +31,8 @@
|
||||
|
||||
namespace ui {
|
||||
|
||||
extern const std::filesystem::path splash_dot_bmp;
|
||||
|
||||
class ScreenshotViewer : public View {
|
||||
public:
|
||||
ScreenshotViewer(NavigationView& nav, const std::filesystem::path& path);
|
||||
@@ -42,6 +44,19 @@ class ScreenshotViewer : public View {
|
||||
std::filesystem::path path_{};
|
||||
};
|
||||
|
||||
class SplashViewer : public View {
|
||||
public:
|
||||
SplashViewer(NavigationView& nav, const std::filesystem::path& path);
|
||||
bool on_key(KeyEvent key) override;
|
||||
void paint(Painter& painter) override;
|
||||
void update_ss(void);
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
std::filesystem::path path_{};
|
||||
bool valid_image{};
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
|
||||
#endif // __UI_SS_VIEWER_H__
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
|
||||
#include "portapack.hpp"
|
||||
#include "hackrf_hal.hpp"
|
||||
#include "cpld_update.hpp"
|
||||
|
||||
#include <cstring>
|
||||
#include <stdio.h>
|
||||
@@ -89,8 +88,7 @@ void SSTVTXView::paint(Painter&) {
|
||||
|
||||
SSTVTXView::~SSTVTXView() {
|
||||
transmitter_model.disable();
|
||||
hackrf::cpld::load_sram_no_verify(); // to leave all RX ok, without ghost signal problem at the exit.
|
||||
baseband::shutdown(); // better this function at the end, not load_sram() that sometimes produces hang up.
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
void SSTVTXView::prepare_scanline() {
|
||||
|
||||
@@ -40,7 +40,8 @@ void TestLogger::log_raw_data(const testapp::Packet& packet, const int32_t alt)
|
||||
|
||||
namespace ui {
|
||||
|
||||
TestView::TestView(NavigationView& nav) {
|
||||
TestView::TestView(NavigationView& nav)
|
||||
: nav_{nav} {
|
||||
baseband::run_image(portapack::spi_flash::image_tag_test);
|
||||
|
||||
add_children({&labels,
|
||||
@@ -54,18 +55,7 @@ TestView::TestView(NavigationView& nav) {
|
||||
&button_cal,
|
||||
&check_log});
|
||||
|
||||
field_frequency.set_value(receiver_model.target_frequency());
|
||||
field_frequency.set_step(10000);
|
||||
field_frequency.on_change = [this](rf::Frequency f) {
|
||||
receiver_model.set_target_frequency(f);
|
||||
};
|
||||
field_frequency.on_edit = [this, &nav]() {
|
||||
// TODO: Provide separate modal method/scheme?
|
||||
auto new_view = nav.push<FrequencyKeypadView>(receiver_model.target_frequency());
|
||||
new_view->on_changed = [this](rf::Frequency f) {
|
||||
field_frequency.set_value(f);
|
||||
};
|
||||
};
|
||||
|
||||
check_log.on_select = [this](Checkbox&, bool v) {
|
||||
logging = v;
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
#include "ui_navigation.hpp"
|
||||
#include "ui_receiver.hpp"
|
||||
#include "ui_freq_field.hpp"
|
||||
#include "ui_rssi.hpp"
|
||||
|
||||
#include "event_m0.hpp"
|
||||
@@ -59,6 +60,7 @@ class TestView : public View {
|
||||
std::string title() const override { return "Test app"; };
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
RxRadioState radio_state_{
|
||||
1750000 /* bandwidth */,
|
||||
2457600 * 2 /* sampling rate */
|
||||
@@ -75,9 +77,9 @@ class TestView : public View {
|
||||
Labels labels{
|
||||
{{0 * 8, 1 * 16}, "Data:", Color::light_grey()}};
|
||||
|
||||
FrequencyField field_frequency{
|
||||
RxFrequencyField field_frequency{
|
||||
{0 * 8, 0 * 8},
|
||||
};
|
||||
nav_};
|
||||
RFAmpField field_rf_amp{
|
||||
{13 * 8, 0 * 16}};
|
||||
|
||||
|
||||
@@ -42,10 +42,9 @@ namespace ui {
|
||||
/* TextViewer *******************************************************/
|
||||
|
||||
TextViewer::TextViewer(Rect parent_rect)
|
||||
: Widget(parent_rect),
|
||||
max_line{static_cast<uint8_t>(parent_rect.height() / char_height)},
|
||||
max_col{static_cast<uint8_t>(parent_rect.width() / char_width)} {
|
||||
: Widget(parent_rect) {
|
||||
set_focusable(true);
|
||||
set_font_zoom(false);
|
||||
}
|
||||
|
||||
void TextViewer::paint(Painter& painter) {
|
||||
@@ -138,6 +137,16 @@ uint32_t TextViewer::offset() const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void TextViewer::cursor_home() {
|
||||
cursor_.col = 0;
|
||||
redraw();
|
||||
}
|
||||
|
||||
void TextViewer::cursor_end() {
|
||||
cursor_.col = line_length() - 1;
|
||||
redraw();
|
||||
}
|
||||
|
||||
uint16_t TextViewer::line_length() {
|
||||
return file_->line_length(cursor_.line);
|
||||
}
|
||||
@@ -158,18 +167,14 @@ bool TextViewer::apply_scrolling_constraints(int16_t delta_line, int16_t delta_c
|
||||
++new_line;
|
||||
}
|
||||
|
||||
// Snap to first/last to make navigating easier.
|
||||
if (new_line < 0 && new_col > 0) {
|
||||
// Snap to first/last line to make navigating easier.
|
||||
if (new_line < 0 && cursor_.line > 0) {
|
||||
new_line = 0;
|
||||
new_col = 0;
|
||||
} else if (new_line >= (int32_t)file_->line_count()) {
|
||||
auto last_line = file_->line_count() - 1;
|
||||
int32_t last_col = file_->line_length(last_line) - 1;
|
||||
|
||||
if (new_col < last_col) {
|
||||
if (cursor_.line < last_line)
|
||||
new_line = last_line;
|
||||
new_col = last_col;
|
||||
}
|
||||
}
|
||||
|
||||
if (new_line < 0 || (uint32_t)new_line >= file_->line_count())
|
||||
@@ -177,7 +182,6 @@ bool TextViewer::apply_scrolling_constraints(int16_t delta_line, int16_t delta_c
|
||||
|
||||
new_line_length = file_->line_length(new_line);
|
||||
|
||||
// TODO: don't wrap with encoder?
|
||||
// Wrap or clamp column.
|
||||
if (new_line_length == 0)
|
||||
new_col = 0;
|
||||
@@ -207,7 +211,7 @@ void TextViewer::paint_text(Painter& painter, uint32_t line, uint16_t col) {
|
||||
if (result && *result > 0)
|
||||
painter.draw_string(
|
||||
{0, r.top() + (int)i * char_height},
|
||||
Styles::white_small, {buffer, *result});
|
||||
style(), {buffer, *result});
|
||||
|
||||
// Clear empty line sections. This is less visually jarring than full clear.
|
||||
int32_t clear_width = max_col - (result ? *result : 0);
|
||||
@@ -216,7 +220,7 @@ void TextViewer::paint_text(Painter& painter, uint32_t line, uint16_t col) {
|
||||
{(max_col - clear_width) * char_width,
|
||||
r.top() + (int)i * char_height,
|
||||
clear_width * char_width, char_height},
|
||||
Styles::white_small.background);
|
||||
style().background);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,8 +241,8 @@ void TextViewer::paint_cursor(Painter& painter) {
|
||||
};
|
||||
|
||||
// Clear old cursor. CONSIDER: XOR cursor?
|
||||
draw_cursor(paint_state_.line, paint_state_.col, Styles::white_small.background);
|
||||
draw_cursor(cursor_.line, cursor_.col, Styles::white_small.foreground);
|
||||
draw_cursor(paint_state_.line, paint_state_.col, style().background);
|
||||
draw_cursor(cursor_.line, cursor_.col, style().foreground);
|
||||
paint_state_.line = cursor_.line;
|
||||
paint_state_.col = cursor_.col;
|
||||
}
|
||||
@@ -252,6 +256,15 @@ void TextViewer::reset_file(FileWrapper* file) {
|
||||
redraw(true);
|
||||
}
|
||||
|
||||
void TextViewer::set_font_zoom(bool zoom) {
|
||||
font_zoom = zoom;
|
||||
font_style = font_zoom ? &Styles::white : &Styles::white_small;
|
||||
char_height = style().font.line_height();
|
||||
char_width = style().font.char_width();
|
||||
max_line = (uint8_t)(parent_rect().height() / char_height);
|
||||
max_col = (uint8_t)(parent_rect().width() / char_width);
|
||||
}
|
||||
|
||||
/* TextEditorMenu ***************************************************/
|
||||
|
||||
TextEditorMenu::TextEditorMenu()
|
||||
@@ -259,9 +272,9 @@ TextEditorMenu::TextEditorMenu()
|
||||
add_children(
|
||||
{
|
||||
&rect_frame,
|
||||
&button_cut,
|
||||
&button_paste,
|
||||
&button_copy,
|
||||
&button_home,
|
||||
&button_end,
|
||||
&button_zoom,
|
||||
&button_delline,
|
||||
&button_edit,
|
||||
&button_addline,
|
||||
@@ -310,14 +323,20 @@ TextEditorView::TextEditorView(NavigationView& nav)
|
||||
};
|
||||
|
||||
menu.hidden(true);
|
||||
menu.on_cut() = [this]() {
|
||||
show_nyi();
|
||||
menu.on_home() = [this]() {
|
||||
viewer.cursor_home();
|
||||
hide_menu(true);
|
||||
};
|
||||
menu.on_paste() = [this]() {
|
||||
show_nyi();
|
||||
|
||||
menu.on_end() = [this]() {
|
||||
viewer.cursor_end();
|
||||
hide_menu(true);
|
||||
};
|
||||
menu.on_copy() = [this]() {
|
||||
show_nyi();
|
||||
|
||||
menu.on_zoom() = [this]() {
|
||||
viewer.toggle_font_zoom();
|
||||
refresh_ui();
|
||||
hide_menu(true);
|
||||
};
|
||||
|
||||
menu.on_delete_line() = [this]() {
|
||||
@@ -490,10 +509,6 @@ void TextEditorView::show_edit_line() {
|
||||
});
|
||||
}
|
||||
|
||||
void TextEditorView::show_nyi() {
|
||||
nav_.display_modal("Soon...", "Coming soon.");
|
||||
}
|
||||
|
||||
void TextEditorView::show_save_prompt(std::function<void()> continuation) {
|
||||
if (!file_dirty_) {
|
||||
if (continuation)
|
||||
|
||||
@@ -72,15 +72,23 @@ class TextViewer : public Widget {
|
||||
uint32_t col() const { return cursor_.col; }
|
||||
uint32_t offset() const;
|
||||
|
||||
void cursor_home();
|
||||
void cursor_end();
|
||||
|
||||
// Gets the length of the current line.
|
||||
uint16_t line_length();
|
||||
|
||||
private:
|
||||
static constexpr int8_t char_width = 5;
|
||||
static constexpr int8_t char_height = 8;
|
||||
const Style& style() { return *font_style; }
|
||||
void set_font_zoom(bool zoom);
|
||||
void toggle_font_zoom() { set_font_zoom(!font_zoom); };
|
||||
|
||||
const uint8_t max_line = 32;
|
||||
const uint8_t max_col = 48;
|
||||
private:
|
||||
bool font_zoom{};
|
||||
const Style* font_style{};
|
||||
int8_t char_width{};
|
||||
int8_t char_height{};
|
||||
uint8_t max_line{};
|
||||
uint8_t max_col{};
|
||||
|
||||
/* Returns true if the cursor was updated. */
|
||||
bool apply_scrolling_constraints(
|
||||
@@ -120,14 +128,12 @@ class TextEditorMenu : public View {
|
||||
void on_show() override;
|
||||
void on_hide() override;
|
||||
|
||||
std::function<void()>& on_cut() { return button_cut.on_select; }
|
||||
std::function<void()>& on_paste() { return button_paste.on_select; }
|
||||
std::function<void()>& on_copy() { return button_copy.on_select; }
|
||||
|
||||
std::function<void()>& on_home() { return button_home.on_select; }
|
||||
std::function<void()>& on_end() { return button_end.on_select; }
|
||||
std::function<void()>& on_zoom() { return button_zoom.on_select; }
|
||||
std::function<void()>& on_delete_line() { return button_delline.on_select; }
|
||||
std::function<void()>& on_edit_line() { return button_edit.on_select; }
|
||||
std::function<void()>& on_add_line() { return button_addline.on_select; }
|
||||
|
||||
std::function<void()>& on_open() { return button_open.on_select; }
|
||||
std::function<void()>& on_save() { return button_save.on_select; }
|
||||
std::function<void()>& on_exit() { return button_exit.on_select; }
|
||||
@@ -139,23 +145,23 @@ class TextEditorMenu : public View {
|
||||
{0 * 8, 0 * 8, 23 * 8, 23 * 8},
|
||||
Color::dark_grey()};
|
||||
|
||||
NewButton button_cut{
|
||||
NewButton button_home{
|
||||
{1 * 8, 1 * 8, 7 * 8, 7 * 8},
|
||||
"Cut",
|
||||
&bitmap_icon_cut,
|
||||
"Home",
|
||||
&bitmap_arrow_left,
|
||||
Color::dark_grey()};
|
||||
|
||||
NewButton button_paste{
|
||||
NewButton button_end{
|
||||
{8 * 8, 1 * 8, 7 * 8, 7 * 8},
|
||||
"Paste",
|
||||
&bitmap_icon_paste,
|
||||
"End",
|
||||
&bitmap_arrow_right,
|
||||
Color::dark_grey()};
|
||||
|
||||
NewButton button_copy{
|
||||
NewButton button_zoom{
|
||||
{15 * 8, 1 * 8, 7 * 8, 7 * 8},
|
||||
"Copy",
|
||||
&bitmap_icon_copy,
|
||||
Color::dark_grey()};
|
||||
"Zoom",
|
||||
&bitmap_icon_search,
|
||||
Color::orange()};
|
||||
|
||||
NewButton button_delline{
|
||||
{1 * 8, 8 * 8, 7 * 8, 7 * 8},
|
||||
@@ -220,7 +226,6 @@ class TextEditorView : public View {
|
||||
void hide_menu(bool hidden = true);
|
||||
void show_file_picker(bool immediate = true);
|
||||
void show_edit_line();
|
||||
void show_nyi();
|
||||
void show_save_prompt(std::function<void()> continuation);
|
||||
|
||||
void prepare_for_write();
|
||||
@@ -258,4 +263,4 @@ class TextEditorView : public View {
|
||||
|
||||
} // namespace ui
|
||||
|
||||
#endif // __UI_TEXT_EDITOR_H__
|
||||
#endif // __UI_TEXT_EDITOR_H__
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
|
||||
#include "baseband_api.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "cpld_update.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
using namespace encoders;
|
||||
@@ -39,8 +38,7 @@ void TouchTunesView::focus() {
|
||||
|
||||
TouchTunesView::~TouchTunesView() {
|
||||
transmitter_model.disable();
|
||||
hackrf::cpld::load_sram_no_verify(); // to leave all RX ok, without ghost signal problem at the exit.
|
||||
baseband::shutdown(); // better this function at the end, not load_sram() that sometimes produces hang up.
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
void TouchTunesView::stop_tx() {
|
||||
|
||||
@@ -23,8 +23,10 @@
|
||||
#include "ui_whipcalc.hpp"
|
||||
|
||||
#include "ch.h"
|
||||
#include "portapack.hpp"
|
||||
#include "convert.hpp"
|
||||
#include "event_m0.hpp"
|
||||
#include "file_reader.hpp"
|
||||
#include "portapack.hpp"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
@@ -102,7 +104,8 @@ void WhipCalcView::update_result() {
|
||||
}
|
||||
}
|
||||
|
||||
WhipCalcView::WhipCalcView(NavigationView& nav) {
|
||||
WhipCalcView::WhipCalcView(NavigationView& nav)
|
||||
: nav_{nav} {
|
||||
add_children({&labels,
|
||||
//&antennas_on_memory,
|
||||
&field_frequency,
|
||||
@@ -112,28 +115,12 @@ WhipCalcView::WhipCalcView(NavigationView& nav) {
|
||||
&console,
|
||||
&button_exit});
|
||||
|
||||
File antennas_file;
|
||||
auto error = antennas_file.open("WHIPCALC/ANTENNAS.TXT");
|
||||
|
||||
if (!error.is_valid()) {
|
||||
std::string line;
|
||||
char one_char[1];
|
||||
for (size_t pointer = 0; pointer < antennas_file.size(); pointer++) {
|
||||
antennas_file.seek(pointer);
|
||||
antennas_file.read(one_char, 1);
|
||||
if ((int)one_char[0] >= ' ')
|
||||
line += one_char[0]; // Add it to the textline
|
||||
else if (one_char[0] == '\n') { // New Line
|
||||
txtline_process(line); // make sense of this textline
|
||||
line.clear(); // Ready for next textline
|
||||
}
|
||||
}
|
||||
if (line.length() > 0)
|
||||
txtline_process(line); // Last line had no newline at end ?
|
||||
}
|
||||
// Try loading antennas from file.
|
||||
load_antenna_db();
|
||||
|
||||
if (!antenna_db.size())
|
||||
add_default_antenna();
|
||||
|
||||
// antennas_on_memory.set(to_string_dec_int(antenna_db.size(),0) + " antennas"); //tell user
|
||||
|
||||
options_type.set_selected_index(2); // Quarter wave
|
||||
@@ -142,56 +129,50 @@ WhipCalcView::WhipCalcView(NavigationView& nav) {
|
||||
};
|
||||
|
||||
field_frequency.set_step(1000000); // 1MHz step
|
||||
field_frequency.on_change = [this](rf::Frequency) {
|
||||
this->update_result();
|
||||
};
|
||||
field_frequency.on_edit = [this, &nav]() {
|
||||
// TODO: Provide separate modal method/scheme?
|
||||
auto new_view = nav.push<FrequencyKeypadView>(transmitter_model.target_frequency());
|
||||
new_view->on_changed = [this](rf::Frequency f) {
|
||||
this->field_frequency.set_value(f);
|
||||
this->update_result();
|
||||
};
|
||||
field_frequency.updated = [this](rf::Frequency) {
|
||||
update_result();
|
||||
};
|
||||
|
||||
button_exit.on_select = [this, &nav](Button&) {
|
||||
nav.pop();
|
||||
};
|
||||
|
||||
field_frequency.set_value(transmitter_model.target_frequency());
|
||||
update_result();
|
||||
}
|
||||
|
||||
void ui::WhipCalcView::txtline_process(std::string& line) {
|
||||
if (line.find("#") != std::string::npos)
|
||||
return; // Line is just a comment
|
||||
void WhipCalcView::load_antenna_db() {
|
||||
File antennas_file;
|
||||
auto error = antennas_file.open("/WHIPCALC/ANTENNAS.TXT");
|
||||
|
||||
char separator = ',';
|
||||
size_t previous = 0;
|
||||
uint16_t value = 0;
|
||||
antenna_entry new_antenna;
|
||||
size_t current = line.find(separator);
|
||||
if (error)
|
||||
return;
|
||||
|
||||
while (current != std::string::npos) {
|
||||
if (!previous)
|
||||
new_antenna.label.assign(line, 0, current); // antenna label
|
||||
else {
|
||||
value = std::stoi(line.substr(previous, current - previous));
|
||||
if (!value) return; // No element length? abort antenna
|
||||
new_antenna.elements.push_back(value); // Store this new element
|
||||
auto reader = FileLineReader(antennas_file);
|
||||
for (const auto& line : reader) {
|
||||
if (line.length() == 0 || line[0] == '#')
|
||||
continue; // Empty or comment line.
|
||||
|
||||
auto cols = split_string(line, ',');
|
||||
if (cols.size() < 2)
|
||||
continue; // Line doesn't have enough columns.
|
||||
|
||||
antenna_entry new_antenna{
|
||||
std::string{cols[0]}};
|
||||
|
||||
// Add antenna elements.
|
||||
for (auto i = 1ul; i < cols.size(); ++i) {
|
||||
uint16_t length = 0;
|
||||
if (parse_int(cols[i], length)) {
|
||||
new_antenna.elements.push_back(length);
|
||||
}
|
||||
}
|
||||
previous = current + 1;
|
||||
current = line.find(separator, previous); // Search for next space delimiter
|
||||
|
||||
if (!new_antenna.elements.empty())
|
||||
antenna_db.push_back(std::move(new_antenna));
|
||||
}
|
||||
|
||||
if (!previous) return; // Not even a label ? drop this antenna!
|
||||
value = std::stoi(line.substr(previous, current - previous)); // Last element
|
||||
|
||||
if (!value) return;
|
||||
new_antenna.elements.push_back(value);
|
||||
antenna_db.push_back(new_antenna); // Add this antenna
|
||||
}
|
||||
|
||||
void ui::WhipCalcView::add_default_antenna() {
|
||||
void WhipCalcView::add_default_antenna() {
|
||||
antenna_db.push_back({"ANT500", {185, 315, 450, 586, 724, 862}}); // store a default ant500
|
||||
}
|
||||
} // namespace ui
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include "ui.hpp"
|
||||
#include "ui_widget.hpp"
|
||||
#include "ui_receiver.hpp"
|
||||
#include "ui_freq_field.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include <vector>
|
||||
@@ -47,10 +48,10 @@ class WhipCalcView : public View {
|
||||
std::vector<uint16_t> elements{};
|
||||
};
|
||||
|
||||
NavigationView& nav_;
|
||||
std::vector<antenna_entry> antenna_db{};
|
||||
void update_result();
|
||||
uint16_t string_to_number(std::string);
|
||||
void txtline_process(std::string&);
|
||||
void load_antenna_db();
|
||||
void add_default_antenna();
|
||||
|
||||
Labels labels{
|
||||
@@ -59,9 +60,9 @@ class WhipCalcView : public View {
|
||||
{{5 * 8, 3 * 16}, "Metric:", Color::light_grey()},
|
||||
{{3 * 8, 4 * 16}, "Imperial:", Color::light_grey()}};
|
||||
|
||||
FrequencyField field_frequency{
|
||||
TxFrequencyField field_frequency{
|
||||
{13 * 8, 1 * 16},
|
||||
};
|
||||
nav_};
|
||||
|
||||
OptionsField options_type{
|
||||
{13 * 8, 2 * 16},
|
||||
|
||||
@@ -134,6 +134,10 @@ static audio::Codec* audio_codec = nullptr;
|
||||
|
||||
namespace output {
|
||||
|
||||
static bool cfg_speaker_disable = false;
|
||||
static bool nav_requested_mute = false;
|
||||
static bool app_requested_mute = false;
|
||||
|
||||
void start() {
|
||||
i2s::i2s0::tx_start();
|
||||
unmute();
|
||||
@@ -145,23 +149,49 @@ void stop() {
|
||||
}
|
||||
|
||||
void mute() {
|
||||
app_requested_mute = true;
|
||||
i2s::i2s0::tx_mute();
|
||||
audio_codec->headphone_disable();
|
||||
}
|
||||
|
||||
void unmute() {
|
||||
i2s::i2s0::tx_unmute();
|
||||
audio_codec->headphone_enable();
|
||||
}
|
||||
|
||||
void speaker_mute() {
|
||||
i2s::i2s0::tx_mute();
|
||||
audio_codec->speaker_disable();
|
||||
}
|
||||
|
||||
void unmute() {
|
||||
app_requested_mute = false;
|
||||
if (!nav_requested_mute) {
|
||||
i2s::i2s0::tx_unmute();
|
||||
audio_codec->headphone_enable();
|
||||
if (!cfg_speaker_disable) {
|
||||
audio_codec->speaker_enable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The following functions are used by the navigation-bar Speaker Mute only,
|
||||
// and override all other audio mute/unmute requests from apps
|
||||
void speaker_mute() {
|
||||
nav_requested_mute = true;
|
||||
i2s::i2s0::tx_mute();
|
||||
audio_codec->speaker_disable();
|
||||
audio_codec->headphone_disable();
|
||||
}
|
||||
|
||||
void speaker_unmute() {
|
||||
i2s::i2s0::tx_unmute();
|
||||
audio_codec->speaker_enable();
|
||||
nav_requested_mute = false;
|
||||
if (!app_requested_mute) {
|
||||
unmute();
|
||||
}
|
||||
}
|
||||
|
||||
void update_audio_mute() {
|
||||
cfg_speaker_disable = portapack::persistent_memory::config_speaker_disable();
|
||||
if (cfg_speaker_disable) {
|
||||
audio_codec->speaker_disable();
|
||||
}
|
||||
|
||||
if (portapack::persistent_memory::config_audio_mute())
|
||||
speaker_mute();
|
||||
else
|
||||
speaker_unmute();
|
||||
}
|
||||
|
||||
} /* namespace output */
|
||||
@@ -242,4 +272,8 @@ void set_rate(const Rate rate) {
|
||||
clock_manager.set_base_audio_clock_divider(toUType(rate));
|
||||
}
|
||||
|
||||
bool speaker_disable_supported() {
|
||||
return audio_codec->speaker_disable_supported();
|
||||
}
|
||||
|
||||
} /* namespace audio */
|
||||
|
||||
@@ -43,6 +43,7 @@ class Codec {
|
||||
|
||||
virtual void speaker_enable() = 0;
|
||||
virtual void speaker_disable() = 0;
|
||||
virtual bool speaker_disable_supported() const = 0;
|
||||
|
||||
virtual void headphone_enable() = 0;
|
||||
virtual void headphone_disable() = 0;
|
||||
@@ -65,8 +66,11 @@ void stop();
|
||||
void mute();
|
||||
void unmute();
|
||||
|
||||
void speaker_disable();
|
||||
void speaker_enable();
|
||||
void speaker_mute();
|
||||
void speaker_unmute();
|
||||
void update_audio_mute();
|
||||
|
||||
} /* namespace output */
|
||||
|
||||
@@ -104,6 +108,7 @@ size_t reg_bits();
|
||||
|
||||
void init(audio::Codec* const codec);
|
||||
void shutdown();
|
||||
bool speaker_disable_supported();
|
||||
|
||||
enum class Rate {
|
||||
Hz_12000 = 4,
|
||||
|
||||
@@ -5341,6 +5341,234 @@ static constexpr Bitmap bitmap_icon_new_dir{
|
||||
{16, 16},
|
||||
bitmap_icon_new_dir_data};
|
||||
|
||||
static constexpr uint8_t bitmap_icon_paint_data[] = {
|
||||
0xFE,
|
||||
0x3F,
|
||||
0xFF,
|
||||
0x3F,
|
||||
0xFF,
|
||||
0xFF,
|
||||
0xFF,
|
||||
0xBF,
|
||||
0xFE,
|
||||
0xBF,
|
||||
0x00,
|
||||
0x80,
|
||||
0x80,
|
||||
0xFF,
|
||||
0x80,
|
||||
0x00,
|
||||
0x80,
|
||||
0x00,
|
||||
0xC0,
|
||||
0x01,
|
||||
0xC0,
|
||||
0x01,
|
||||
0xC0,
|
||||
0x01,
|
||||
0xC0,
|
||||
0x01,
|
||||
0xC0,
|
||||
0x01,
|
||||
0xC0,
|
||||
0x01,
|
||||
0xC0,
|
||||
0x01,
|
||||
};
|
||||
static constexpr Bitmap bitmap_icon_paint{
|
||||
{16, 16},
|
||||
bitmap_icon_paint_data};
|
||||
|
||||
static constexpr uint8_t bitmap_icon_touchtunes_data[] = {
|
||||
0xE0,
|
||||
0x07,
|
||||
0x30,
|
||||
0x0C,
|
||||
0x7C,
|
||||
0x3E,
|
||||
0xC4,
|
||||
0x23,
|
||||
0x26,
|
||||
0x64,
|
||||
0x12,
|
||||
0x48,
|
||||
0x0F,
|
||||
0xF3,
|
||||
0x09,
|
||||
0x95,
|
||||
0x0F,
|
||||
0xF1,
|
||||
0x09,
|
||||
0x91,
|
||||
0x0F,
|
||||
0xF1,
|
||||
0xC9,
|
||||
0x91,
|
||||
0xE9,
|
||||
0x91,
|
||||
0xC9,
|
||||
0x90,
|
||||
0x0F,
|
||||
0xF0,
|
||||
0xFF,
|
||||
0xFF,
|
||||
};
|
||||
static constexpr Bitmap bitmap_icon_touchtunes{
|
||||
{16, 16},
|
||||
bitmap_icon_touchtunes_data};
|
||||
|
||||
static constexpr uint8_t bitmap_arrow_left_data[] = {
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x20,
|
||||
0x00,
|
||||
0x30,
|
||||
0x00,
|
||||
0x38,
|
||||
0x00,
|
||||
0xFC,
|
||||
0x7F,
|
||||
0xFE,
|
||||
0x7F,
|
||||
0xFC,
|
||||
0x7F,
|
||||
0x38,
|
||||
0x00,
|
||||
0x30,
|
||||
0x00,
|
||||
0x20,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
};
|
||||
static constexpr Bitmap bitmap_arrow_left{
|
||||
{16, 16},
|
||||
bitmap_arrow_left_data};
|
||||
|
||||
static constexpr uint8_t bitmap_arrow_right_data[] = {
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x0C,
|
||||
0x00,
|
||||
0x1C,
|
||||
0xFE,
|
||||
0x3F,
|
||||
0xFE,
|
||||
0x7F,
|
||||
0xFE,
|
||||
0x3F,
|
||||
0x00,
|
||||
0x1C,
|
||||
0x00,
|
||||
0x0C,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
};
|
||||
static constexpr Bitmap bitmap_arrow_right{
|
||||
{16, 16},
|
||||
bitmap_arrow_right_data};
|
||||
|
||||
static constexpr uint8_t bitmap_icon_speaker_and_headphones_data[] = {
|
||||
0x20,
|
||||
0x10,
|
||||
0x30,
|
||||
0x20,
|
||||
0x38,
|
||||
0x44,
|
||||
0x3E,
|
||||
0x48,
|
||||
0x3E,
|
||||
0x91,
|
||||
0x3E,
|
||||
0x92,
|
||||
0x38,
|
||||
0x92,
|
||||
0x30,
|
||||
0x92,
|
||||
0x20,
|
||||
0x92,
|
||||
0x00,
|
||||
0x92,
|
||||
0x30,
|
||||
0x91,
|
||||
0x48,
|
||||
0x48,
|
||||
0x84,
|
||||
0x44,
|
||||
0x84,
|
||||
0x20,
|
||||
0x86,
|
||||
0x11,
|
||||
0x86,
|
||||
0x01,
|
||||
};
|
||||
static constexpr Bitmap bitmap_icon_speaker_and_headphones{
|
||||
{16, 16},
|
||||
bitmap_icon_speaker_and_headphones_data};
|
||||
|
||||
static constexpr uint8_t bitmap_icon_speaker_and_headphones_mute_data[] = {
|
||||
0x40,
|
||||
0x00,
|
||||
0x60,
|
||||
0x44,
|
||||
0x70,
|
||||
0x6C,
|
||||
0x7C,
|
||||
0x38,
|
||||
0x7C,
|
||||
0x10,
|
||||
0x7C,
|
||||
0x38,
|
||||
0x70,
|
||||
0x6C,
|
||||
0x60,
|
||||
0x44,
|
||||
0x40,
|
||||
0x00,
|
||||
0x00,
|
||||
0x44,
|
||||
0x30,
|
||||
0x6C,
|
||||
0x48,
|
||||
0x38,
|
||||
0x84,
|
||||
0x10,
|
||||
0x84,
|
||||
0x38,
|
||||
0x86,
|
||||
0x6D,
|
||||
0x86,
|
||||
0x45,
|
||||
};
|
||||
static constexpr Bitmap bitmap_icon_speaker_and_headphones_mute{
|
||||
{16, 16},
|
||||
bitmap_icon_speaker_and_headphones_mute_data};
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
#endif /*__BITMAP_HPP__*/
|
||||
|
||||
@@ -488,6 +488,24 @@ path operator/(const path& lhs, const path& rhs) {
|
||||
return result;
|
||||
}
|
||||
|
||||
bool path_iequal(
|
||||
const path& lhs,
|
||||
const path& rhs) {
|
||||
const auto& lhs_str = lhs.native();
|
||||
const auto& rhs_str = rhs.native();
|
||||
|
||||
// NB: Not correct for Unicode/locales.
|
||||
if (lhs_str.length() == rhs_str.length()) {
|
||||
for (size_t i = 0; i < lhs_str.length(); ++i)
|
||||
if (towupper(lhs_str[i]) != towupper(rhs_str[i]))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
directory_iterator::directory_iterator(
|
||||
std::filesystem::path path,
|
||||
std::filesystem::path wild)
|
||||
|
||||
@@ -168,6 +168,9 @@ bool operator>(const path& lhs, const path& rhs);
|
||||
path operator+(const path& lhs, const path& rhs);
|
||||
path operator/(const path& lhs, const path& rhs);
|
||||
|
||||
/* Case insensitive path equality on underlying "native" string. */
|
||||
bool path_iequal(const path& lhs, const path& rhs);
|
||||
|
||||
using file_status = BYTE;
|
||||
|
||||
static_assert(sizeof(path::value_type) == 2, "sizeof(std::filesystem::path::value_type) != 2");
|
||||
|
||||
@@ -36,7 +36,8 @@
|
||||
*/
|
||||
|
||||
/* Iterates lines in buffer split on '\n'.
|
||||
* NB: very basic iterator impl, don't try anything fancy with it. */
|
||||
* NB: very basic iterator impl, don't try anything fancy with it.
|
||||
* For example, you _must_ deref the iterator after advancing it. */
|
||||
template <typename BufferType>
|
||||
class BufferLineReader {
|
||||
public:
|
||||
@@ -133,4 +134,18 @@ using FileLineReader = BufferLineReader<File>;
|
||||
* are used or they will dangle. */
|
||||
std::vector<std::string_view> split_string(std::string_view str, char c);
|
||||
|
||||
/* Returns the number of lines in a file. */
|
||||
template <typename BufferType>
|
||||
uint32_t count_lines(BufferLineReader<BufferType>& reader) {
|
||||
uint32_t count = 0;
|
||||
auto it = reader.begin();
|
||||
|
||||
do {
|
||||
*it; // Necessary to force the file read.
|
||||
++count;
|
||||
} while (++it != reader.end());
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -21,257 +21,66 @@
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "file.hpp"
|
||||
#include "freqman.hpp"
|
||||
#include "tone_key.hpp"
|
||||
#include "ui_widget.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
using namespace tonekey;
|
||||
using namespace ui;
|
||||
|
||||
using option_t = std::pair<std::string, int32_t>;
|
||||
using options_t = std::vector<option_t>;
|
||||
|
||||
options_t freqman_entry_modulations = {
|
||||
{"AM", 0},
|
||||
{"NFM", 1},
|
||||
{"WFM", 2}};
|
||||
extern options_t freqman_modulations;
|
||||
extern options_t freqman_bandwidths[4];
|
||||
extern options_t freqman_steps;
|
||||
extern options_t freqman_steps_short;
|
||||
|
||||
options_t freqman_entry_bandwidths[4] = {
|
||||
{// AM
|
||||
{"DSB 9k", 0},
|
||||
{"DSB 6k", 1},
|
||||
{"USB+3k", 2},
|
||||
{"LSB-3k", 3},
|
||||
{"CW", 4}},
|
||||
{// NFM
|
||||
{"8k5", 0},
|
||||
{"11k", 1},
|
||||
{"16k", 2}},
|
||||
{
|
||||
// WFM
|
||||
{"40k", 2},
|
||||
{"180k", 1},
|
||||
{"200k", 0},
|
||||
}};
|
||||
|
||||
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},
|
||||
{"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},
|
||||
{"1MHz", 1000000}};
|
||||
|
||||
bool load_freqman_file(std::string& file_stem, freqman_db& db) {
|
||||
return load_freqman_file_ex(file_stem, db, true, true, true, FREQMAN_MAX_PER_FILE);
|
||||
const option_t* find_by_index(const options_t& options, freqman_index_t index) {
|
||||
if (index < options.size())
|
||||
return &options[index];
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool load_freqman_file_ex(std::string& file_stem, freqman_db& db, bool load_freqs, bool load_ranges, bool load_hamradios, uint8_t max_num_freqs = FREQMAN_MAX_PER_FILE) {
|
||||
// swap with empty vector to ensure memory is immediately released
|
||||
std::vector<freqman_entry>().swap(db);
|
||||
|
||||
File freqman_file;
|
||||
size_t length, n = 0, file_position = 0;
|
||||
char* pos;
|
||||
char* line_start;
|
||||
char* line_end;
|
||||
std::string description;
|
||||
rf::Frequency frequency_a, frequency_b;
|
||||
char file_data[FREQMAN_READ_BUF_SIZE + 1];
|
||||
freqman_entry_type type;
|
||||
freqman_index_t modulation = 0;
|
||||
freqman_index_t bandwidth = 0;
|
||||
freqman_index_t step = 0;
|
||||
freqman_index_t tone = 0;
|
||||
|
||||
auto result = freqman_file.open("FREQMAN/" + file_stem + ".TXT");
|
||||
if (result.is_valid())
|
||||
return false;
|
||||
|
||||
while (1) {
|
||||
// Read a FREQMAN_READ_BUF_SIZE block from file
|
||||
freqman_file.seek(file_position);
|
||||
|
||||
memset(file_data, 0, FREQMAN_READ_BUF_SIZE + 1);
|
||||
auto read_size = freqman_file.read(file_data, FREQMAN_READ_BUF_SIZE);
|
||||
if (read_size.is_error())
|
||||
return false; // Read error
|
||||
|
||||
file_position += FREQMAN_READ_BUF_SIZE;
|
||||
|
||||
// Reset line_start to beginning of buffer
|
||||
line_start = file_data;
|
||||
|
||||
// If EOF reached, insert 0x0A after, in case the last line doesn't have a C/R
|
||||
if (read_size.value() < FREQMAN_READ_BUF_SIZE)
|
||||
*(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 = ERROR_TYPE;
|
||||
|
||||
frequency_a = frequency_b = 0;
|
||||
// Read frequency
|
||||
pos = strstr(line_start, "f=");
|
||||
if (pos) {
|
||||
pos += 2;
|
||||
frequency_a = strtoll(pos, nullptr, 10);
|
||||
type = SINGLE;
|
||||
} else {
|
||||
// ...or range
|
||||
pos = strstr(line_start, "a=");
|
||||
if (pos) {
|
||||
pos += 2;
|
||||
frequency_a = strtoll(pos, nullptr, 10);
|
||||
type = RANGE;
|
||||
pos = strstr(line_start, "b=");
|
||||
if (pos) {
|
||||
pos += 2;
|
||||
frequency_b = strtoll(pos, nullptr, 10);
|
||||
} else
|
||||
frequency_b = 0;
|
||||
} else {
|
||||
// ... or hamradio
|
||||
pos = strstr(line_start, "r=");
|
||||
if (pos) {
|
||||
pos += 2;
|
||||
frequency_a = strtoll(pos, nullptr, 10);
|
||||
type = HAMRADIO;
|
||||
pos = strstr(line_start, "t=");
|
||||
if (pos) {
|
||||
pos += 2;
|
||||
frequency_b = strtoll(pos, nullptr, 10);
|
||||
} else
|
||||
frequency_b = frequency_a;
|
||||
} else
|
||||
frequency_a = 0;
|
||||
}
|
||||
}
|
||||
// modulation if any
|
||||
pos = strstr(line_start, "m=");
|
||||
if (pos) {
|
||||
pos += 2;
|
||||
modulation = freqman_entry_get_modulation_from_str(pos);
|
||||
}
|
||||
// bandwidth if any
|
||||
pos = strstr(line_start, "bw=");
|
||||
if (pos) {
|
||||
pos += 3;
|
||||
bandwidth = freqman_entry_get_bandwidth_from_str(modulation, pos);
|
||||
}
|
||||
// step if any
|
||||
pos = strstr(line_start, "s=");
|
||||
if (pos) {
|
||||
pos += 2;
|
||||
step = freqman_entry_get_step_from_str_short(pos);
|
||||
}
|
||||
// ctcss tone if any
|
||||
/* disabled until better form
|
||||
pos = strstr(line_start, "c=");
|
||||
if (pos) {
|
||||
pos += 2;
|
||||
tone = tone_key_index_by_value( strtoll( pos , nullptr , 10 ) );
|
||||
} */
|
||||
// Read description until , or LF
|
||||
pos = strstr(line_start, "d=");
|
||||
if (pos) {
|
||||
pos += 2;
|
||||
length = std::min(strcspn(pos, ",\x0A"), (size_t)FREQMAN_DESC_MAX_LEN);
|
||||
description = string(pos, length);
|
||||
}
|
||||
if ((type == SINGLE && load_freqs) || (type == RANGE && load_ranges) || (type == HAMRADIO && load_hamradios)) {
|
||||
db.push_back({frequency_a, frequency_b, description, type, modulation, bandwidth, step, tone});
|
||||
n++;
|
||||
if (n > max_num_freqs) return true;
|
||||
}
|
||||
|
||||
line_start = line_end + 1;
|
||||
if (line_start - file_data >= FREQMAN_READ_BUF_SIZE) break;
|
||||
}
|
||||
|
||||
if (read_size.value() != FREQMAN_READ_BUF_SIZE)
|
||||
break; // End of file
|
||||
|
||||
// Restart at beginning of last incomplete line
|
||||
file_position -= (file_data + FREQMAN_READ_BUF_SIZE - line_start);
|
||||
}
|
||||
|
||||
/* populate implicitly specified modulation / bandwidth */
|
||||
if (db.size() > 2) {
|
||||
modulation = db[0].modulation;
|
||||
bandwidth = db[0].bandwidth;
|
||||
|
||||
for (unsigned int it = 1; it < db.size(); it++) {
|
||||
if (db[it].modulation < 0) {
|
||||
db[it].modulation = modulation;
|
||||
} else {
|
||||
modulation = db[it].modulation;
|
||||
}
|
||||
if (db[it].bandwidth < 0) {
|
||||
db[it].bandwidth = bandwidth;
|
||||
} else {
|
||||
modulation = db[it].bandwidth;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
// TODO: move into FreqmanDB type
|
||||
/* Freqman file handling. */
|
||||
bool load_freqman_file(const std::string& file_stem, freqman_db& db, freqman_load_options options) {
|
||||
fs::path path{u"FREQMAN/"};
|
||||
path += file_stem + ".TXT";
|
||||
return parse_freqman_file(path, db, options);
|
||||
}
|
||||
|
||||
bool get_freq_string(freqman_entry& entry, std::string& item_string) {
|
||||
rf::Frequency frequency_a, frequency_b;
|
||||
|
||||
frequency_a = entry.frequency_a;
|
||||
if (entry.type == SINGLE) {
|
||||
if (entry.type == freqman_type::Single) {
|
||||
// Single
|
||||
item_string = "f=" + to_string_dec_uint(frequency_a / 1000) + to_string_dec_uint(frequency_a % 1000UL, 3, '0');
|
||||
} else if (entry.type == RANGE) {
|
||||
} else if (entry.type == freqman_type::Range) {
|
||||
// Range
|
||||
frequency_b = entry.frequency_b;
|
||||
item_string = "a=" + to_string_dec_uint(frequency_a / 1000) + to_string_dec_uint(frequency_a % 1000UL, 3, '0');
|
||||
item_string += ",b=" + to_string_dec_uint(frequency_b / 1000) + to_string_dec_uint(frequency_b % 1000UL, 3, '0');
|
||||
if (entry.step >= 0) {
|
||||
if (is_valid(entry.step)) {
|
||||
item_string += ",s=" + freqman_entry_get_step_string_short(entry.step);
|
||||
}
|
||||
} else if (entry.type == HAMRADIO) {
|
||||
} else if (entry.type == freqman_type::HamRadio) {
|
||||
frequency_b = entry.frequency_b;
|
||||
item_string = "r=" + to_string_dec_uint(frequency_a / 1000) + to_string_dec_uint(frequency_a % 1000UL, 3, '0');
|
||||
item_string += ",t=" + to_string_dec_uint(frequency_b / 1000) + to_string_dec_uint(frequency_b % 1000UL, 3, '0');
|
||||
if (entry.tone >= 0) {
|
||||
item_string += ",c=" + tone_key_string(entry.tone);
|
||||
if (is_valid(entry.tone)) {
|
||||
item_string += ",c=" + tone_key_value_string(entry.tone);
|
||||
}
|
||||
}
|
||||
if (entry.modulation >= 0 && (unsigned)entry.modulation < freqman_entry_modulations.size()) {
|
||||
if (is_valid(entry.modulation) && entry.modulation < freqman_modulations.size()) {
|
||||
item_string += ",m=" + freqman_entry_get_modulation_string(entry.modulation);
|
||||
if (entry.bandwidth >= 0 && (unsigned)entry.bandwidth < freqman_entry_bandwidths[entry.modulation].size()) {
|
||||
if (is_valid(entry.bandwidth) && (unsigned)entry.bandwidth < freqman_bandwidths[entry.modulation].size()) {
|
||||
item_string += ",bw=" + freqman_entry_get_bandwidth_string(entry.modulation, entry.bandwidth);
|
||||
}
|
||||
}
|
||||
@@ -281,14 +90,14 @@ bool get_freq_string(freqman_entry& entry, std::string& item_string) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool delete_freqman_file(std::string& file_stem) {
|
||||
bool delete_freqman_file(const std::string& file_stem) {
|
||||
File freqman_file;
|
||||
std::string freq_file_path = "/FREQMAN/" + file_stem + ".TXT";
|
||||
delete_file(freq_file_path);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool save_freqman_file(std::string& file_stem, freqman_db& db) {
|
||||
bool save_freqman_file(const std::string& file_stem, freqman_db& db) {
|
||||
File freqman_file;
|
||||
std::string freq_file_path = "/FREQMAN/" + file_stem + ".TXT";
|
||||
delete_file(freq_file_path);
|
||||
@@ -296,7 +105,7 @@ bool save_freqman_file(std::string& file_stem, freqman_db& db) {
|
||||
if (!result.is_valid()) {
|
||||
for (size_t n = 0; n < db.size(); n++) {
|
||||
std::string line;
|
||||
get_freq_string(db[n], line);
|
||||
get_freq_string(*db[n], line);
|
||||
freqman_file.write_line(line);
|
||||
}
|
||||
return true;
|
||||
@@ -304,7 +113,7 @@ bool save_freqman_file(std::string& file_stem, freqman_db& db) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool create_freqman_file(std::string& file_stem, File& freqman_file) {
|
||||
bool create_freqman_file(const std::string& file_stem, File& freqman_file) {
|
||||
auto result = freqman_file.create("FREQMAN/" + file_stem + ".TXT");
|
||||
|
||||
if (result.is_valid())
|
||||
@@ -317,17 +126,17 @@ std::string freqman_item_string(freqman_entry& entry, size_t max_length) {
|
||||
std::string item_string;
|
||||
|
||||
switch (entry.type) {
|
||||
case SINGLE:
|
||||
case freqman_type::Single:
|
||||
item_string = to_string_short_freq(entry.frequency_a) + "M: " + entry.description;
|
||||
break;
|
||||
case RANGE:
|
||||
case freqman_type::Range:
|
||||
item_string = "R: " + entry.description;
|
||||
break;
|
||||
case HAMRADIO:
|
||||
case freqman_type::HamRadio:
|
||||
item_string = "H: " + entry.description;
|
||||
break;
|
||||
default:
|
||||
item_string = "!UNKNOW TYPE " + entry.description;
|
||||
item_string = "!UNKNOWN TYPE " + entry.description;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -337,115 +146,69 @@ std::string freqman_item_string(freqman_entry& entry, size_t max_length) {
|
||||
return item_string;
|
||||
}
|
||||
|
||||
/* Set options. */
|
||||
void freqman_set_modulation_option(OptionsField& option) {
|
||||
option.set_options(freqman_entry_modulations);
|
||||
option.set_options(freqman_modulations);
|
||||
}
|
||||
|
||||
void freqman_set_bandwidth_option(freqman_index_t modulation, OptionsField& option) {
|
||||
option.set_options(freqman_entry_bandwidths[modulation]);
|
||||
if (is_valid(modulation))
|
||||
option.set_options(freqman_bandwidths[modulation]);
|
||||
}
|
||||
|
||||
void freqman_set_step_option(OptionsField& option) {
|
||||
option.set_options(freqman_entry_steps);
|
||||
option.set_options(freqman_steps);
|
||||
}
|
||||
|
||||
void freqman_set_step_option_short(OptionsField& option) {
|
||||
option.set_options(freqman_entry_steps_short);
|
||||
option.set_options(freqman_steps_short);
|
||||
}
|
||||
|
||||
/* Option name lookup. */
|
||||
std::string freqman_entry_get_modulation_string(freqman_index_t modulation) {
|
||||
if (modulation < 0 || (unsigned)modulation >= freqman_entry_modulations.size()) {
|
||||
return std::string(""); // unknown modulation
|
||||
}
|
||||
return freqman_entry_modulations[modulation].first;
|
||||
if (auto opt = find_by_index(freqman_modulations, modulation))
|
||||
return opt->first;
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string freqman_entry_get_bandwidth_string(freqman_index_t modulation, freqman_index_t bandwidth) {
|
||||
if (modulation < 0 || (unsigned)modulation >= freqman_entry_modulations.size()) {
|
||||
return std::string(""); // unknown modulation
|
||||
if (modulation < freqman_modulations.size()) {
|
||||
if (auto opt = find_by_index(freqman_bandwidths[modulation], bandwidth))
|
||||
return opt->first;
|
||||
}
|
||||
if (bandwidth < 0 || (unsigned)bandwidth > freqman_entry_bandwidths[modulation].size()) {
|
||||
return std::string(""); // unknown modulation
|
||||
}
|
||||
return freqman_entry_bandwidths[modulation][bandwidth].first;
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string freqman_entry_get_step_string(freqman_index_t step) {
|
||||
if (step < 0 || (unsigned)step >= freqman_entry_steps.size()) {
|
||||
return std::string(""); // unknown modulation
|
||||
}
|
||||
return freqman_entry_steps[step].first;
|
||||
if (auto opt = find_by_index(freqman_steps, step))
|
||||
return opt->first;
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string freqman_entry_get_step_string_short(freqman_index_t step) {
|
||||
if (step < 0 || (unsigned)step >= freqman_entry_steps_short.size()) {
|
||||
return std::string(""); // unknown modulation
|
||||
}
|
||||
return freqman_entry_steps_short[step].first;
|
||||
if (auto opt = find_by_index(freqman_steps_short, step))
|
||||
return opt->first;
|
||||
return {};
|
||||
}
|
||||
|
||||
/* Option value lookup. */
|
||||
// TODO: use Optional instead of magic values.
|
||||
int32_t freqman_entry_get_modulation_value(freqman_index_t modulation) {
|
||||
if (modulation < 0 || (unsigned)modulation >= freqman_entry_modulations.size()) {
|
||||
return -1; // unknown modulation
|
||||
}
|
||||
return freqman_entry_modulations[modulation].second;
|
||||
if (auto opt = find_by_index(freqman_modulations, modulation))
|
||||
return opt->second;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int32_t freqman_entry_get_bandwidth_value(freqman_index_t modulation, freqman_index_t bandwidth) {
|
||||
if (modulation < 0 || (unsigned)modulation >= freqman_entry_modulations.size()) {
|
||||
return -1; // unknown modulation
|
||||
if (modulation < freqman_modulations.size()) {
|
||||
if (auto opt = find_by_index(freqman_bandwidths[modulation], bandwidth))
|
||||
return opt->second;
|
||||
}
|
||||
if (bandwidth < 0 || (unsigned)bandwidth > freqman_entry_bandwidths[modulation].size()) {
|
||||
return -1; // unknown bandwidth for modulation
|
||||
}
|
||||
return freqman_entry_bandwidths[modulation][bandwidth].second;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int32_t freqman_entry_get_step_value(freqman_index_t step) {
|
||||
if (step < 0 || (unsigned)step >= freqman_entry_steps.size()) {
|
||||
return -1; // unknown modulation
|
||||
}
|
||||
return freqman_entry_steps[step].second;
|
||||
}
|
||||
|
||||
freqman_index_t freqman_entry_get_modulation_from_str(char* str) {
|
||||
if (!str)
|
||||
return -1;
|
||||
for (freqman_index_t index = 0; (unsigned)index < freqman_entry_modulations.size(); index++) {
|
||||
if (strncmp(freqman_entry_modulations[index].first.c_str(), str, freqman_entry_modulations[index].first.size()) == 0)
|
||||
return index;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
freqman_index_t freqman_entry_get_bandwidth_from_str(freqman_index_t modulation, char* str) {
|
||||
if (!str)
|
||||
return -1;
|
||||
if (modulation < 0 || (unsigned)modulation >= freqman_entry_modulations.size())
|
||||
return -1;
|
||||
for (freqman_index_t index = 0; (unsigned)index < freqman_entry_bandwidths[modulation].size(); index++) {
|
||||
if (strncmp(freqman_entry_bandwidths[modulation][index].first.c_str(), str, freqman_entry_bandwidths[modulation][index].first.size()) == 0)
|
||||
return index;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
freqman_index_t freqman_entry_get_step_from_str(char* str) {
|
||||
if (!str)
|
||||
return -1;
|
||||
for (freqman_index_t index = 0; (unsigned)index < freqman_entry_steps.size(); index++) {
|
||||
if (strncmp(freqman_entry_steps[index].first.c_str(), str, freqman_entry_steps[index].first.size()) == 0)
|
||||
return index;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
freqman_index_t freqman_entry_get_step_from_str_short(char* str) {
|
||||
if (!str)
|
||||
return -1;
|
||||
for (freqman_index_t index = 0; (unsigned)index < freqman_entry_steps_short.size(); index++) {
|
||||
if (strncmp(freqman_entry_steps_short[index].first.c_str(), str, freqman_entry_steps_short[index].first.size()) == 0)
|
||||
return index;
|
||||
}
|
||||
if (auto opt = find_by_index(freqman_steps, step))
|
||||
return opt->second;
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -27,88 +27,41 @@
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include "file.hpp"
|
||||
#include "ui_receiver.hpp"
|
||||
#include "tone_key.hpp"
|
||||
#include "freqman_db.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "ui_receiver.hpp"
|
||||
#include "ui_widget.hpp"
|
||||
|
||||
#define FREQMAN_DESC_MAX_LEN 24 // This is the number of characters that can be drawn in front of "R: TEXT..." before taking a full screen line
|
||||
#define FREQMAN_MAX_PER_FILE 60 // Maximum of entries we can read. This is a hardware limit
|
||||
// It was tested and lowered to leave a bit of space to the caller
|
||||
#define FREQMAN_MAX_PER_FILE_STR "60" // STRING OF FREQMAN_MAX_PER_FILE
|
||||
#define FREQMAN_READ_BUF_SIZE 96 // max freqman line size including desc is 90, +6 for a bit of space
|
||||
// Defined for back-compat.
|
||||
#define FREQMAN_MAX_PER_FILE freqman_default_max_entries
|
||||
|
||||
using namespace ui;
|
||||
using namespace std;
|
||||
using namespace tonekey;
|
||||
|
||||
// needs to be signed as -1 means not set
|
||||
typedef int8_t freqman_index_t;
|
||||
|
||||
enum freqman_error {
|
||||
enum freqman_error : int8_t {
|
||||
NO_ERROR = 0,
|
||||
ERROR_ACCESS,
|
||||
ERROR_NOFILES,
|
||||
ERROR_DUPLICATE
|
||||
};
|
||||
|
||||
enum freqman_entry_type {
|
||||
SINGLE = 0, // f=
|
||||
RANGE, // a=,b=
|
||||
HAMRADIO, // r=,t=
|
||||
ERROR_TYPE
|
||||
};
|
||||
|
||||
enum freqman_entry_modulation {
|
||||
enum freqman_entry_modulation : uint8_t {
|
||||
AM_MODULATION = 0,
|
||||
NFM_MODULATION,
|
||||
WFM_MODULATION,
|
||||
MODULATION_DEF,
|
||||
ERROR_MODULATION
|
||||
SPEC_MODULATION
|
||||
};
|
||||
|
||||
// Entry step placed for AlainD freqman version (or any other enhanced version)
|
||||
enum freqman_entry_step {
|
||||
STEP_DEF = -1, // default
|
||||
AM_US, // 10 kHz AM/CB
|
||||
AM_EUR, // 9 kHz LW/MW
|
||||
NFM_1, // 12,5 kHz (Analogic PMR 446)
|
||||
NFM_2, // 6,25 kHz (Digital PMR 446)
|
||||
FM_1, // 100 kHz
|
||||
FM_2, // 50 kHz
|
||||
N_1, // 25 kHz
|
||||
N_2, // 250 kHz
|
||||
AIRBAND, // AIRBAND 8,33 kHz
|
||||
ERROR_STEP
|
||||
};
|
||||
|
||||
struct freqman_entry {
|
||||
rf::Frequency frequency_a{0}; // 'f=freq' or 'a=freq_start' or 'r=recv_freq'
|
||||
rf::Frequency frequency_b{0}; // 'b=freq_end' or 't=tx_freq'
|
||||
std::string description{}; // 'd=desc'
|
||||
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,...
|
||||
tone_index tone{}; // 0XZ, 11 1ZB,...
|
||||
};
|
||||
|
||||
using freqman_db = std::vector<freqman_entry>;
|
||||
|
||||
bool load_freqman_file(std::string& file_stem, freqman_db& db);
|
||||
bool load_freqman_file_ex(std::string& file_stem, freqman_db& db, bool load_freqs, bool load_ranges, bool load_hamradios, uint8_t limit);
|
||||
bool load_freqman_file(const std::string& file_stem, freqman_db& db, freqman_load_options options);
|
||||
bool get_freq_string(freqman_entry& entry, std::string& item_string);
|
||||
bool delete_freqman_file(std::string& file_stem);
|
||||
bool save_freqman_file(std::string& file_stem, freqman_db& db);
|
||||
bool create_freqman_file(std::string& file_stem, File& freqman_file);
|
||||
bool delete_freqman_file(const std::string& file_stem);
|
||||
bool save_freqman_file(const std::string& file_stem, freqman_db& db);
|
||||
bool create_freqman_file(const std::string& file_stem, File& freqman_file);
|
||||
|
||||
std::string freqman_item_string(freqman_entry& item, size_t max_length);
|
||||
|
||||
void freqman_set_bandwidth_option(freqman_index_t modulation, OptionsField& option);
|
||||
void freqman_set_modulation_option(OptionsField& option);
|
||||
void freqman_set_step_option(OptionsField& option);
|
||||
void freqman_set_step_option_short(OptionsField& option);
|
||||
void freqman_set_tone_option(OptionsField& option);
|
||||
void freqman_set_bandwidth_option(freqman_index_t modulation, ui::OptionsField& option);
|
||||
void freqman_set_modulation_option(ui::OptionsField& option);
|
||||
void freqman_set_step_option(ui::OptionsField& option);
|
||||
void freqman_set_step_option_short(ui::OptionsField& option);
|
||||
void freqman_set_tone_option(ui::OptionsField& option);
|
||||
|
||||
std::string freqman_entry_get_modulation_string(freqman_index_t modulation);
|
||||
std::string freqman_entry_get_bandwidth_string(freqman_index_t modulation, freqman_index_t bandwidth);
|
||||
@@ -119,9 +72,4 @@ int32_t freqman_entry_get_modulation_value(freqman_index_t modulation);
|
||||
int32_t freqman_entry_get_bandwidth_value(freqman_index_t modulation, freqman_index_t bandwidth);
|
||||
int32_t freqman_entry_get_step_value(freqman_index_t step);
|
||||
|
||||
freqman_index_t freqman_entry_get_modulation_from_str(char* str);
|
||||
freqman_index_t freqman_entry_get_bandwidth_from_str(freqman_index_t modulation, char* str);
|
||||
freqman_index_t freqman_entry_get_step_from_str(char* str);
|
||||
freqman_index_t freqman_entry_get_step_from_str_short(char* str);
|
||||
|
||||
#endif /*__FREQMAN_H__*/
|
||||
|
||||
@@ -0,0 +1,279 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2016 Furrtek
|
||||
* Copyright (C) 2023 gullradriel, Nilorea Studio Inc.
|
||||
* Copyright (C) 2023 Kyle Reed
|
||||
*
|
||||
* 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 "convert.hpp"
|
||||
#include "file.hpp"
|
||||
#include "file_reader.hpp"
|
||||
#include "freqman_db.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "tone_key.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <cctype>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
using option_t = std::pair<std::string, int32_t>;
|
||||
using options_t = std::vector<option_t>;
|
||||
|
||||
options_t freqman_modulations = {
|
||||
{"AM", 0},
|
||||
{"NFM", 1},
|
||||
{"WFM", 2},
|
||||
{"SPEC", 3},
|
||||
};
|
||||
|
||||
options_t freqman_bandwidths[4] = {
|
||||
{
|
||||
// AM
|
||||
{"DSB 9k", 0},
|
||||
{"DSB 6k", 1},
|
||||
{"USB+3k", 2},
|
||||
{"LSB-3k", 3},
|
||||
{"CW", 4},
|
||||
},
|
||||
{
|
||||
// NFM
|
||||
{"8k5", 0},
|
||||
{"11k", 1},
|
||||
{"16k", 2},
|
||||
},
|
||||
{
|
||||
// WFM
|
||||
{"40k", 2},
|
||||
{"180k", 1},
|
||||
{"200k", 0},
|
||||
},
|
||||
{
|
||||
// SPEC -- TODO: these should be indexes.
|
||||
{"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) */
|
||||
{"650k", 650000},
|
||||
{"750k", 750000}, /* From this BW onwards, the LCD is ok, but the recorded file is 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)
|
||||
},
|
||||
};
|
||||
|
||||
options_t freqman_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},
|
||||
{"1MHz ", 1000000},
|
||||
};
|
||||
|
||||
options_t freqman_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},
|
||||
{"1MHz", 1000000},
|
||||
};
|
||||
|
||||
uint8_t find_by_name(const options_t& options, std::string_view name) {
|
||||
for (auto ix = 0u; ix < options.size(); ++ix)
|
||||
if (options[ix].first == name)
|
||||
return ix;
|
||||
|
||||
return freqman_invalid_index;
|
||||
}
|
||||
|
||||
/* Impl for next round of changes.
|
||||
*template <typename T, size_t N>
|
||||
*const T* find_by_name(const std::array<T, N>& info, std::string_view name) {
|
||||
* for (const auto& it : info) {
|
||||
* if (it.name == name)
|
||||
* return ⁢
|
||||
* }
|
||||
*
|
||||
* return nullptr;
|
||||
*}
|
||||
*/
|
||||
|
||||
bool parse_freqman_entry(std::string_view str, freqman_entry& entry) {
|
||||
if (str.empty() || str[0] == '#')
|
||||
return false;
|
||||
|
||||
entry = freqman_entry{};
|
||||
auto cols = split_string(str, ',');
|
||||
|
||||
for (auto col : cols) {
|
||||
if (col.empty())
|
||||
continue;
|
||||
|
||||
auto pair = split_string(col, '=');
|
||||
if (pair.size() != 2)
|
||||
continue;
|
||||
|
||||
auto key = pair[0];
|
||||
auto value = pair[1];
|
||||
|
||||
if (key == "a") {
|
||||
entry.type = freqman_type::Range;
|
||||
parse_int(value, entry.frequency_a);
|
||||
} else if (key == "b") {
|
||||
parse_int(value, entry.frequency_b);
|
||||
} else if (key == "bw") {
|
||||
// NB: Requires modulation to be set first
|
||||
if (entry.modulation < std::size(freqman_bandwidths)) {
|
||||
entry.bandwidth = find_by_name(freqman_bandwidths[entry.modulation], value);
|
||||
}
|
||||
} else if (key == "c") {
|
||||
// Split into whole and fractional parts.
|
||||
auto parts = split_string(value, '.');
|
||||
int32_t tone_freq = 0;
|
||||
int32_t whole_part = 0;
|
||||
parse_int(parts[0], whole_part);
|
||||
|
||||
// Tones are stored as frequency / 100 for some reason.
|
||||
// E.g. 14572 would be 145.7 (NB: 1s place is dropped).
|
||||
// TODO: Might be easier to just store the codes?
|
||||
// Multiply the whole part by 100 to get the tone frequency.
|
||||
tone_freq = whole_part * 100;
|
||||
|
||||
// Add the fractional part, if present.
|
||||
if (parts.size() > 1) {
|
||||
auto c = parts[1].front();
|
||||
auto digit = std::isdigit(c) ? c - '0' : 0;
|
||||
tone_freq += digit * 10;
|
||||
}
|
||||
entry.tone = static_cast<freqman_index_t>(
|
||||
tonekey::tone_key_index_by_value(tone_freq));
|
||||
} else if (key == "d") {
|
||||
entry.description = trim(value);
|
||||
} else if (key == "f") {
|
||||
entry.type = freqman_type::Single;
|
||||
parse_int(value, entry.frequency_a);
|
||||
} else if (key == "m") {
|
||||
entry.modulation = find_by_name(freqman_modulations, value);
|
||||
} else if (key == "r") {
|
||||
entry.type = freqman_type::HamRadio;
|
||||
parse_int(value, entry.frequency_a);
|
||||
} else if (key == "s") {
|
||||
entry.step = find_by_name(freqman_steps_short, value);
|
||||
} else if (key == "t") {
|
||||
parse_int(value, entry.frequency_b);
|
||||
}
|
||||
}
|
||||
|
||||
// No valid frequency combination was set.
|
||||
if (entry.type == freqman_type::Unknown)
|
||||
return false;
|
||||
|
||||
// Ranges should have both frequencies set and A <= B.
|
||||
if (entry.type == freqman_type::Range || entry.type == freqman_type::HamRadio) {
|
||||
if (entry.frequency_a == 0 || entry.frequency_b == 0)
|
||||
return false;
|
||||
|
||||
if (entry.frequency_a > entry.frequency_b)
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: Consider additional validation:
|
||||
// - Tone only on HamRadio.
|
||||
// - Fail on failed parse_int.
|
||||
// - Fail if bandwidth set before modulation.
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool parse_freqman_file(const fs::path& path, freqman_db& db, freqman_load_options options) {
|
||||
File f;
|
||||
auto error = f.open(path);
|
||||
if (error)
|
||||
return false;
|
||||
|
||||
auto reader = FileLineReader(f);
|
||||
auto line_count = count_lines(reader);
|
||||
|
||||
// Attempt to avoid a re-alloc if possible.
|
||||
db.clear();
|
||||
db.reserve(line_count);
|
||||
|
||||
for (const auto& line : reader) {
|
||||
freqman_entry entry{};
|
||||
if (!parse_freqman_entry(line, entry))
|
||||
continue;
|
||||
|
||||
// Filter by entry type.
|
||||
if ((entry.type == freqman_type::Single && !options.load_freqs) ||
|
||||
(entry.type == freqman_type::Range && !options.load_ranges) ||
|
||||
(entry.type == freqman_type::HamRadio && !options.load_hamradios)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Use previous entry's mod/band if current's aren't set.
|
||||
if (!db.empty()) {
|
||||
if (is_invalid(entry.modulation))
|
||||
entry.modulation = db.back()->modulation;
|
||||
if (is_invalid(entry.bandwidth))
|
||||
entry.bandwidth = db.back()->bandwidth;
|
||||
}
|
||||
|
||||
// Move the entry onto the heap and push.
|
||||
db.push_back(std::make_unique<freqman_entry>(std::move(entry)));
|
||||
|
||||
// Limit to max_entries when specified.
|
||||
if (options.max_entries > 0 && db.size() >= options.max_entries)
|
||||
break;
|
||||
}
|
||||
|
||||
db.shrink_to_fit();
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2016 Furrtek
|
||||
* Copyright (C) 2023 gullradriel, Nilorea Studio Inc., Kyle Reed
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef __FREQMAN_DB_H__
|
||||
#define __FREQMAN_DB_H__
|
||||
|
||||
#include "file.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
using freqman_index_t = uint8_t;
|
||||
constexpr freqman_index_t freqman_invalid_index = static_cast<freqman_index_t>(-1);
|
||||
|
||||
/* Returns true if the value is not invalid_index. */
|
||||
constexpr bool is_valid(freqman_index_t index) {
|
||||
return index != freqman_invalid_index;
|
||||
}
|
||||
|
||||
/* Returns true if the value is invalid_index. */
|
||||
constexpr bool is_invalid(freqman_index_t index) {
|
||||
return index == freqman_invalid_index;
|
||||
}
|
||||
|
||||
enum class freqman_type : uint8_t {
|
||||
Single, // f=
|
||||
Range, // a=,b=
|
||||
HamRadio, // r=,t=
|
||||
Unknown,
|
||||
};
|
||||
|
||||
/* Tables for next round of changes.
|
||||
* // TODO: attempt to consolidate all of these values
|
||||
* // and settings into a single location.
|
||||
* // Should tone_keys get consolidated here too?
|
||||
* enum class freqman_modulation : uint8_t {
|
||||
AM,
|
||||
NFM,
|
||||
WFM,
|
||||
SPEC,
|
||||
Unknown,
|
||||
* };
|
||||
*
|
||||
* struct freqman_modulation_info {
|
||||
* freqman_modulation modulation;
|
||||
* std::string_view name;
|
||||
* };
|
||||
*
|
||||
* constexpr std::array<freqman_modulation_info, 5> freqman_modulations = {
|
||||
freqman_modulation_info{ freqman_modulation::AM, "AM" },
|
||||
freqman_modulation_info{ freqman_modulation::NFM, "NFM" },
|
||||
freqman_modulation_info{ freqman_modulation::WFM, "WFM" },
|
||||
freqman_modulation_info{ freqman_modulation::SPEC, "SPEC" },
|
||||
freqman_modulation_info{ freqman_modulation::Unknown, "Unknown" }
|
||||
* };
|
||||
* static_assert(std::size(freqman_modulations) == (size_t)freqman_modulation::Unknown + 1);
|
||||
*
|
||||
* enum class freqman_step : uint8_t {
|
||||
_100Hz,
|
||||
_1kHz,
|
||||
_5kHz,
|
||||
_6_25kHz,
|
||||
_8_33kHz,
|
||||
_9kHz,
|
||||
_10kHz,
|
||||
_12_5kHz,
|
||||
_15kHz,
|
||||
_25kHz,
|
||||
_30kHz,
|
||||
_50kHz,
|
||||
_100kHz,
|
||||
_250kHz,
|
||||
_500kHz,
|
||||
_1MHz,
|
||||
Unknown,
|
||||
* };
|
||||
*
|
||||
* struct freqman_step_info {
|
||||
* freqman_step step;
|
||||
* std::string_view name;
|
||||
* std::string_view display_name;
|
||||
* uint32_t value;
|
||||
* };
|
||||
*
|
||||
* // TODO: FrequencyStepView should use this list.
|
||||
* constexpr std::array<freqman_step_info, 18> freqman_steps = {
|
||||
freqman_step_info{ freqman_step::_100Hz, "0.1kHz", "0.1kHz ", 100 },
|
||||
freqman_step_info{ freqman_step::_1kHz, "1kHz", "1kHz ", 1'000 },
|
||||
freqman_step_info{ freqman_step::_5kHz, "5kHz", "5kHz (SA AM)", 5'000 },
|
||||
freqman_step_info{ freqman_step::_6_25kHz, "6.25kHz", "6.25kHz(NFM)", 6'250 },
|
||||
freqman_step_info{ freqman_step::_8_33kHz, "8.33kHz", "8.33kHz(AIR)", 8'330 },
|
||||
freqman_step_info{ freqman_step::_9kHz, "9kHz", "9kHz (EU AM)", 9'000 },
|
||||
freqman_step_info{ freqman_step::_10kHz, "10kHz", "10kHz(US AM)", 10'000 },
|
||||
freqman_step_info{ freqman_step::_12_5kHz, "12.5kHz", "12.5kHz(NFM)", 12'500 },
|
||||
freqman_step_info{ freqman_step::_15kHz, "15kHz", "15kHz (HFM)", 15'000 },
|
||||
freqman_step_info{ freqman_step::_25kHz, "25kHz", "25kHz (N1)", 25'000 },
|
||||
freqman_step_info{ freqman_step::_30kHz, "30kHz", "30kHz (OIRT)", 30'000 },
|
||||
freqman_step_info{ freqman_step::_50kHz, "50kHz", "50kHz (FM1)", 50'000 },
|
||||
freqman_step_info{ freqman_step::_100kHz, "100kHz", "100kHz (FM2)", 100'000 },
|
||||
freqman_step_info{ freqman_step::_250kHz, "250kHz", "250kHz (N2)", 250'000 },
|
||||
freqman_step_info{ freqman_step::_500kHz, "500kHz", "500kHz (WFM)", 500'000 },
|
||||
freqman_step_info{ freqman_step::_1MHz, "1MHz", "1MHz ", 1'000'000 },
|
||||
freqman_step_info{ freqman_step::Unknown, "Unknown", "Unknown ", 0 },
|
||||
* };
|
||||
* static_assert(std::size(freqman_steps) == (size_t)freqman_step::Unknown + 1);
|
||||
*/
|
||||
|
||||
/* Freqman Entry *******************************/
|
||||
struct freqman_entry {
|
||||
int64_t frequency_a{0}; // 'f=freq' or 'a=freq_start' or 'r=recv_freq'
|
||||
int64_t frequency_b{0}; // 'b=freq_end' or 't=tx_freq'
|
||||
std::string description{0}; // 'd=desc'
|
||||
freqman_type type{freqman_type::Unknown};
|
||||
freqman_index_t modulation{freqman_invalid_index};
|
||||
freqman_index_t bandwidth{freqman_invalid_index};
|
||||
freqman_index_t step{freqman_invalid_index};
|
||||
freqman_index_t tone{freqman_invalid_index};
|
||||
};
|
||||
|
||||
/* A reasonable maximum number of items to load from a freqman file.
|
||||
* Apps using freqman_db should be tested and this value tuned to
|
||||
* ensure app memory stability. */
|
||||
constexpr size_t freqman_default_max_entries = 90;
|
||||
|
||||
struct freqman_load_options {
|
||||
/* Loads all entries when set to 0. */
|
||||
size_t max_entries{freqman_default_max_entries};
|
||||
bool load_freqs{true};
|
||||
bool load_ranges{true};
|
||||
bool load_hamradios{true};
|
||||
};
|
||||
|
||||
using freqman_entry_ptr = std::unique_ptr<freqman_entry>;
|
||||
using freqman_db = std::vector<freqman_entry_ptr>;
|
||||
|
||||
bool parse_freqman_entry(std::string_view str, freqman_entry& entry);
|
||||
bool parse_freqman_file(const std::filesystem::path& path, freqman_db& db, freqman_load_options options);
|
||||
|
||||
/* Type for next round of changes.
|
||||
*class FreqmanDB {
|
||||
* public:
|
||||
* FreqmanDB();
|
||||
* FreqmanDB(const FreqmanDB&) = delete;
|
||||
* FreqmanDB(FreqmanDB&&) = delete;
|
||||
* FreqmanDB& operator=(const FreqmanDB&) = delete;
|
||||
* FreqmanDB& operator=(FreqmanDB&&) = delete;
|
||||
*
|
||||
* size_t size() const { return 0; };
|
||||
*
|
||||
* private:
|
||||
* freqman_db entries_;
|
||||
*};
|
||||
*/
|
||||
|
||||
#endif /* __FREQMAN_DB_H__ */
|
||||
@@ -30,7 +30,7 @@ bool Debounce::feed(const uint8_t bit) {
|
||||
// "Repeat" handling - simulated button release
|
||||
if (repeat_ctr_) {
|
||||
// Make sure the button is still being held continuously
|
||||
if (history_ == 0xFF) {
|
||||
if ((history_ == 0xFF) && !long_press_enabled_) {
|
||||
// Simulate button press every REPEAT_SUBSEQUENT_DELAY ticks
|
||||
if (--repeat_ctr_ == 0) {
|
||||
state_ = !state_;
|
||||
@@ -49,29 +49,59 @@ bool Debounce::feed(const uint8_t bit) {
|
||||
if ((history_ & DEBOUNCE_MASK) == DEBOUNCE_MASK) {
|
||||
state_ = 1;
|
||||
held_time_ = 0;
|
||||
|
||||
// If long_press_enabled_, state() function masks the button press until it's released
|
||||
// or until LONG_PRESS_DELAY is reached
|
||||
if (long_press_enabled_) {
|
||||
pulse_upon_release_ = true;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
// Previous button state was 1 (pressed);
|
||||
// Has button been released for DEBOUNCE_COUNT ticks?
|
||||
if ((history_ & DEBOUNCE_MASK) == 0) {
|
||||
state_ = 0;
|
||||
// Button has been released when long_press_enabled_ and before LONG_PRESS_DELAY was reached;
|
||||
// allow state() function to finally return a single press indication
|
||||
// (in long press mode, apps won't see button press until the button is released)
|
||||
if (pulse_upon_release_) {
|
||||
// leaving state_==1 for one cycle
|
||||
pulse_upon_release_ = 0;
|
||||
} else {
|
||||
state_ = 0;
|
||||
}
|
||||
|
||||
// Reset long_press_occurred_ flag after button is released
|
||||
long_press_occurred_ = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Repeat support is limited to the 4 directional buttons
|
||||
if (repeat_enabled_) {
|
||||
// Has button been held continuously for DEBOUNCE_REPEAT_DELAY?
|
||||
if (history_ == 0xFF) {
|
||||
if (++held_time_ == REPEAT_INITIAL_DELAY) {
|
||||
// Has button been held continuously?
|
||||
if (history_ == 0xFF) {
|
||||
held_time_++;
|
||||
if (pulse_upon_release_) {
|
||||
// Button is being held down and long_press support is enabled for this key:
|
||||
// if LONG_PRESS_DELAY is reached then finally report that switch is pressed and set flag
|
||||
// indicating it was a LONG press
|
||||
// (note that repease_support and long_press support are mutually exclusive)
|
||||
if (held_time_ == LONG_PRESS_DELAY) {
|
||||
long_press_occurred_ = true;
|
||||
pulse_upon_release_ = 0;
|
||||
held_time_ = 0;
|
||||
return true;
|
||||
}
|
||||
} else if (repeat_enabled_ && !long_press_enabled_) {
|
||||
// Repeat support -- 4 directional buttons only (unless long_press is enabled)
|
||||
if (held_time_ == REPEAT_INITIAL_DELAY) {
|
||||
// Delay reached; trigger repeat code on NEXT tick
|
||||
repeat_ctr_ = 1;
|
||||
held_time_ = 0;
|
||||
}
|
||||
} else {
|
||||
// Button not continuously pressed; reset counter
|
||||
held_time_ = 0;
|
||||
}
|
||||
} else {
|
||||
// Button not continuously pressed; reset counter
|
||||
held_time_ = 0;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -31,25 +31,39 @@
|
||||
// # of timer0 ticks before a held button starts being counted as repeated presses
|
||||
#define REPEAT_INITIAL_DELAY 250
|
||||
#define REPEAT_SUBSEQUENT_DELAY 92
|
||||
#define LONG_PRESS_DELAY 1000
|
||||
|
||||
class Debounce {
|
||||
public:
|
||||
bool feed(const uint8_t bit);
|
||||
|
||||
uint8_t state() const {
|
||||
return state_;
|
||||
return (pulse_upon_release_) ? 0 : state_;
|
||||
}
|
||||
|
||||
void enable_repeat() {
|
||||
repeat_enabled_ = true;
|
||||
}
|
||||
|
||||
void set_long_press_support(bool v) {
|
||||
long_press_enabled_ = v;
|
||||
}
|
||||
|
||||
bool long_press_occurred() {
|
||||
bool v = long_press_occurred_;
|
||||
long_press_occurred_ = false;
|
||||
return v;
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t history_{0};
|
||||
uint8_t state_{0};
|
||||
bool repeat_enabled_{0};
|
||||
uint16_t repeat_ctr_{0};
|
||||
uint16_t held_time_{0};
|
||||
bool pulse_upon_release_{0};
|
||||
bool long_press_enabled_{0};
|
||||
bool long_press_occurred_{0};
|
||||
};
|
||||
|
||||
#endif /*__DEBOUNCE_H__*/
|
||||
|
||||
@@ -22,17 +22,17 @@
|
||||
#include "io_file.hpp"
|
||||
|
||||
File::Result<File::Size> FileReader::read(void* const buffer, const File::Size bytes) {
|
||||
auto read_result = file.read(buffer, bytes);
|
||||
auto read_result = file_.read(buffer, bytes);
|
||||
if (read_result.is_ok()) {
|
||||
bytes_read += read_result.value();
|
||||
bytes_read_ += read_result.value();
|
||||
}
|
||||
return read_result;
|
||||
}
|
||||
|
||||
File::Result<File::Size> FileWriter::write(const void* const buffer, const File::Size bytes) {
|
||||
auto write_result = file.write(buffer, bytes);
|
||||
auto write_result = file_.write(buffer, bytes);
|
||||
if (write_result.is_ok()) {
|
||||
bytes_written += write_result.value();
|
||||
bytes_written_ += write_result.value();
|
||||
}
|
||||
return write_result;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user