mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2025-12-01 20:52:15 +00:00
Compare commits
7 Commits
nightly-ta
...
nightly-ta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f22808f8ca | ||
|
|
e1c519d71e | ||
|
|
909f37f89b | ||
|
|
63b0093321 | ||
|
|
fa06df1400 | ||
|
|
f83027d451 | ||
|
|
7ebf3d3cdd |
@@ -184,6 +184,7 @@ set(CPPSRC
|
||||
irq_lcd_frame.cpp
|
||||
irq_rtc.cpp
|
||||
log_file.cpp
|
||||
metadata_file.cpp
|
||||
portapack.cpp
|
||||
qrcodegen.cpp
|
||||
radio.cpp
|
||||
|
||||
@@ -22,21 +22,19 @@
|
||||
|
||||
#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 "debug.hpp"
|
||||
|
||||
#include "freqman.hpp"
|
||||
using namespace portapack;
|
||||
using namespace tonekey;
|
||||
|
||||
namespace ui {
|
||||
|
||||
@@ -45,8 +43,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 +63,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 +88,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 +109,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 +135,10 @@ SPECOptionsView::SPECOptionsView(
|
||||
AnalogAudioView::AnalogAudioView(
|
||||
NavigationView& nav)
|
||||
: nav_(nav) {
|
||||
// A baseband image _must_ be running before
|
||||
// interacting with the waterfall view.
|
||||
baseband::run_image(portapack::spi_flash::image_tag_wideband_spectrum);
|
||||
|
||||
add_children({&rssi,
|
||||
&channel,
|
||||
&audio,
|
||||
@@ -164,9 +166,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));
|
||||
};
|
||||
@@ -184,8 +189,9 @@ 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);
|
||||
}
|
||||
|
||||
size_t AnalogAudioView::get_spec_bw_index() {
|
||||
@@ -212,40 +218,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);
|
||||
}
|
||||
|
||||
@@ -257,13 +238,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() {
|
||||
@@ -315,7 +298,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);
|
||||
@@ -342,6 +325,7 @@ void AnalogAudioView::on_show_options_modulation() {
|
||||
break;
|
||||
|
||||
default:
|
||||
chDbgPanic("Unhandled Mode");
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -358,7 +342,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();
|
||||
|
||||
@@ -379,7 +363,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);
|
||||
@@ -418,7 +403,7 @@ void AnalogAudioView::update_modulation(const ReceiverModel::Mode modulation) {
|
||||
}
|
||||
}
|
||||
|
||||
void AnalogAudioView::handle_coded_squelch(const uint32_t value) {
|
||||
void AnalogAudioView::handle_coded_squelch(uint32_t value) {
|
||||
float diff, min_diff = value;
|
||||
size_t min_idx{0};
|
||||
size_t c;
|
||||
|
||||
@@ -38,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{
|
||||
@@ -56,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{
|
||||
@@ -84,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{
|
||||
@@ -103,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{
|
||||
@@ -140,10 +140,7 @@ class AnalogAudioView : public View {
|
||||
AnalogAudioView(NavigationView& nav);
|
||||
~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"; };
|
||||
@@ -170,8 +167,6 @@ class AnalogAudioView : public View {
|
||||
uint32_t spec_bw = 20000000;
|
||||
uint16_t spec_trigger = 63;
|
||||
|
||||
// bool exit_on_squelch { false };
|
||||
|
||||
RSSI rssi{
|
||||
{21 * 8, 0, 6 * 8, 4}};
|
||||
|
||||
@@ -221,7 +216,7 @@ class AnalogAudioView : public View {
|
||||
spectrum::WaterfallWidget 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();
|
||||
@@ -231,22 +226,13 @@ class AnalogAudioView : public View {
|
||||
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);
|
||||
}};
|
||||
|
||||
@@ -113,6 +113,8 @@ CaptureAppView::CaptureAppView(NavigationView& nav)
|
||||
}
|
||||
|
||||
CaptureAppView::~CaptureAppView() {
|
||||
// Most other apps can't handle "Capture" mode, set to something standard.
|
||||
receiver_model.set_modulation(ReceiverModel::Mode::WidebandFMAudio);
|
||||
receiver_model.disable();
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
|
||||
#include "ui_fileman.hpp"
|
||||
#include "io_file.hpp"
|
||||
#include "metadata_file.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
#include "baseband_api.hpp"
|
||||
@@ -33,6 +34,7 @@
|
||||
#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 = ms_duration(file_size, sample_rate, 2);
|
||||
|
||||
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();
|
||||
|
||||
@@ -71,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();
|
||||
|
||||
@@ -27,11 +27,13 @@
|
||||
#include "ui_fileman.hpp"
|
||||
#include "io_file.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 = ms_duration(file_size, sample_rate, 4);
|
||||
|
||||
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();
|
||||
@@ -200,7 +192,7 @@ ReplayAppView::ReplayAppView(
|
||||
|
||||
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);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -67,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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"
|
||||
@@ -71,6 +72,8 @@ class GlassView : public View {
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
RxRadioState radio_state_{};
|
||||
app_settings::SettingsManager settings_{
|
||||
"rx_glass", app_settings::Mode::RX};
|
||||
|
||||
struct preset_entry {
|
||||
rf::Frequency min{};
|
||||
|
||||
@@ -40,7 +40,9 @@ using namespace portapack;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
/* TODO: wouldn't it be easier if the playlist were just a list of C16 files
|
||||
* (and maybe delays) and then read the data file next to the C16 file? */
|
||||
* (and maybe delays) and then read the metadata file next to the C16 file?
|
||||
* TODO: use metadata_file.hpp to read the metadata.
|
||||
* TODO: change PPL format to only allow paths, and !<number> for delay. */
|
||||
|
||||
namespace ui {
|
||||
|
||||
@@ -142,7 +144,9 @@ bool PlaylistView::next_track() {
|
||||
|
||||
/* 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_entry_)
|
||||
@@ -172,6 +176,10 @@ void PlaylistView::send_current_track() {
|
||||
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());
|
||||
|
||||
// Use the ReplayThread class to send the data.
|
||||
replay_thread_ = std::make_unique<ReplayThread>(
|
||||
std::move(reader),
|
||||
|
||||
@@ -96,7 +96,7 @@ class ScannerView : public View {
|
||||
|
||||
private:
|
||||
app_settings::SettingsManager settings_{
|
||||
"scanner", app_settings::Mode::RX};
|
||||
"rx_scanner", app_settings::Mode::RX};
|
||||
|
||||
NavigationView& nav_;
|
||||
RxRadioState radio_state_{};
|
||||
|
||||
@@ -378,14 +378,26 @@ SearchView::SearchView(
|
||||
|
||||
field_frequency_min.set_value(receiver_model.target_frequency() - 1000000);
|
||||
field_frequency_min.set_step(100000);
|
||||
field_frequency_min.updated = [this](rf::Frequency) {
|
||||
on_range_changed();
|
||||
field_frequency_min.on_change = [this](rf::Frequency) {
|
||||
this->on_range_changed();
|
||||
};
|
||||
field_frequency_min.on_edit = [this, &nav]() {
|
||||
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);
|
||||
};
|
||||
};
|
||||
|
||||
field_frequency_max.set_value(receiver_model.target_frequency() + 1000000);
|
||||
field_frequency_max.set_step(100000);
|
||||
field_frequency_max.updated = [this](rf::Frequency) {
|
||||
on_range_changed();
|
||||
field_frequency_max.on_change = [this](rf::Frequency) {
|
||||
this->on_range_changed();
|
||||
};
|
||||
field_frequency_max.on_edit = [this, &nav]() {
|
||||
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);
|
||||
};
|
||||
};
|
||||
|
||||
field_lna.set_value(receiver_model.lna());
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
#include "spectrum_color_lut.hpp"
|
||||
|
||||
#include "ui_receiver.hpp"
|
||||
#include "ui_freq_field.hpp"
|
||||
#include "ui_styles.hpp"
|
||||
#include "recent_entries.hpp"
|
||||
|
||||
@@ -147,14 +146,10 @@ class SearchView : public View {
|
||||
{{1 * 8, 25 * 8}, "Accuracy +/-4.9kHz", Color::light_grey()},
|
||||
{{26 * 8, 25 * 8}, "MHz", Color::light_grey()}};
|
||||
|
||||
RxFrequencyField field_frequency_min{
|
||||
{1 * 8, 1 * 16},
|
||||
nav_,
|
||||
false};
|
||||
RxFrequencyField field_frequency_max{
|
||||
{11 * 8, 1 * 16},
|
||||
nav_,
|
||||
false};
|
||||
FrequencyField field_frequency_min{
|
||||
{1 * 8, 1 * 16}};
|
||||
FrequencyField field_frequency_max{
|
||||
{11 * 8, 1 * 16}};
|
||||
LNAGainField field_lna{
|
||||
{22 * 8, 1 * 16}};
|
||||
VGAGainField field_vga{
|
||||
|
||||
@@ -527,6 +527,7 @@ SetAudioView::SetAudioView(NavigationView& nav) {
|
||||
button_save.on_select = [&nav, this](Button&) {
|
||||
persistent_memory::set_tone_mix(field_tone_mix.value());
|
||||
persistent_memory::set_config_speaker_disable(checkbox_speaker_disable.value());
|
||||
audio::output::update_audio_mute();
|
||||
nav.pop();
|
||||
};
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -113,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
|
||||
@@ -151,40 +137,42 @@ WhipCalcView::WhipCalcView(NavigationView& nav)
|
||||
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
|
||||
|
||||
@@ -51,8 +51,7 @@ class WhipCalcView : public View {
|
||||
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{
|
||||
@@ -63,8 +62,7 @@ class WhipCalcView : public View {
|
||||
|
||||
TxFrequencyField field_frequency{
|
||||
{13 * 8, 1 * 16},
|
||||
nav_,
|
||||
false};
|
||||
nav_};
|
||||
|
||||
OptionsField options_type{
|
||||
{13 * 8, 2 * 16},
|
||||
|
||||
@@ -166,15 +166,6 @@ void unmute() {
|
||||
}
|
||||
}
|
||||
|
||||
void speaker_disable() {
|
||||
cfg_speaker_disable = true;
|
||||
audio_codec->speaker_disable();
|
||||
}
|
||||
|
||||
void speaker_enable() {
|
||||
cfg_speaker_disable = false;
|
||||
}
|
||||
|
||||
// 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() {
|
||||
@@ -191,6 +182,15 @@ void speaker_unmute() {
|
||||
}
|
||||
}
|
||||
|
||||
void update_audio_mute() {
|
||||
cfg_speaker_disable = portapack::persistent_memory::config_speaker_disable();
|
||||
|
||||
if (portapack::persistent_memory::config_audio_mute())
|
||||
speaker_mute();
|
||||
else
|
||||
speaker_unmute();
|
||||
}
|
||||
|
||||
} /* namespace output */
|
||||
|
||||
namespace input {
|
||||
|
||||
@@ -69,6 +69,7 @@ void speaker_disable();
|
||||
void speaker_enable();
|
||||
void speaker_mute();
|
||||
void speaker_unmute();
|
||||
void update_audio_mute();
|
||||
|
||||
} /* namespace output */
|
||||
|
||||
|
||||
@@ -32,11 +32,11 @@
|
||||
#include "string_format.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
|
||||
#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 100 // 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_READ_BUF_SIZE 96 // max freqman line size including desc is 90, +6 for a bit of space
|
||||
|
||||
using namespace ui;
|
||||
using namespace std;
|
||||
@@ -52,7 +52,7 @@ enum freqman_error {
|
||||
ERROR_DUPLICATE
|
||||
};
|
||||
|
||||
enum freqman_entry_type {
|
||||
enum freqman_entry_type : uint8_t {
|
||||
SINGLE = 0, // f=
|
||||
RANGE, // a=,b=
|
||||
HAMRADIO, // r=,t=
|
||||
|
||||
88
firmware/application/metadata_file.cpp
Normal file
88
firmware/application/metadata_file.cpp
Normal file
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* 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 "metadata_file.hpp"
|
||||
|
||||
#include "convert.hpp"
|
||||
#include "file_reader.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include <string_view>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
using namespace std::literals;
|
||||
|
||||
const std::string_view center_freq_name = "center_frequency"sv;
|
||||
const std::string_view sample_rate_name = "sample_rate"sv;
|
||||
|
||||
fs::path get_metadata_path(const fs::path& capture_path) {
|
||||
auto temp = capture_path;
|
||||
return temp.replace_extension(u".TXT");
|
||||
}
|
||||
|
||||
Optional<File::Error> write_metadata_file(const fs::path& path, capture_metadata metadata) {
|
||||
File f;
|
||||
auto error = f.create(path);
|
||||
|
||||
if (error)
|
||||
return error;
|
||||
|
||||
error = f.write_line(std::string{center_freq_name} + "=" +
|
||||
to_string_dec_uint(metadata.center_frequency));
|
||||
if (error)
|
||||
return error;
|
||||
|
||||
error = f.write_line(std::string{sample_rate_name} + "=" +
|
||||
to_string_dec_uint(metadata.sample_rate));
|
||||
if (error)
|
||||
return error;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
Optional<capture_metadata> read_metadata_file(const fs::path& path) {
|
||||
File f;
|
||||
auto error = f.open(path);
|
||||
|
||||
if (error)
|
||||
return {};
|
||||
|
||||
capture_metadata metadata{};
|
||||
|
||||
auto reader = FileLineReader(f);
|
||||
for (const auto& line : reader) {
|
||||
auto cols = split_string(line, '=');
|
||||
|
||||
if (cols.size() != 2)
|
||||
continue; // Bad line.
|
||||
|
||||
if (cols[0] == center_freq_name)
|
||||
parse_int(cols[1], metadata.center_frequency);
|
||||
else if (cols[0] == sample_rate_name)
|
||||
parse_int(cols[1], metadata.sample_rate);
|
||||
else
|
||||
continue;
|
||||
}
|
||||
|
||||
if (metadata.center_frequency == 0 || metadata.sample_rate == 0)
|
||||
return {}; // Parse failed.
|
||||
|
||||
return metadata;
|
||||
}
|
||||
39
firmware/application/metadata_file.hpp
Normal file
39
firmware/application/metadata_file.hpp
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef __METADATA_FILE_HPP__
|
||||
#define __METADATA_FILE_HPP__
|
||||
|
||||
#include "file.hpp"
|
||||
#include "optional.hpp"
|
||||
#include "rf_path.hpp"
|
||||
|
||||
struct capture_metadata {
|
||||
rf::Frequency center_frequency;
|
||||
uint32_t sample_rate;
|
||||
};
|
||||
|
||||
std::filesystem::path get_metadata_path(const std::filesystem::path& capture_path);
|
||||
|
||||
Optional<File::Error> write_metadata_file(const std::filesystem::path& path, capture_metadata metadata);
|
||||
Optional<capture_metadata> read_metadata_file(const std::filesystem::path& path);
|
||||
|
||||
#endif // __METADATA_FILE_HPP__
|
||||
@@ -96,20 +96,6 @@ bool get_antenna_bias() {
|
||||
return antenna_bias;
|
||||
}
|
||||
|
||||
void set_audio_mute(const bool v) {
|
||||
if (v)
|
||||
audio::output::speaker_mute();
|
||||
else
|
||||
audio::output::speaker_unmute();
|
||||
}
|
||||
|
||||
void set_speaker_disable(const bool v) {
|
||||
if (v)
|
||||
audio::output::speaker_disable();
|
||||
else
|
||||
audio::output::speaker_enable();
|
||||
}
|
||||
|
||||
static constexpr uint32_t systick_count(const uint32_t clock_source_f) {
|
||||
return clock_source_f / CH_FREQUENCY;
|
||||
}
|
||||
|
||||
@@ -53,9 +53,6 @@ extern ClockManager clock_manager;
|
||||
extern ReceiverModel receiver_model;
|
||||
extern TransmitterModel transmitter_model;
|
||||
|
||||
void set_audio_mute(const bool v);
|
||||
void set_speaker_disable(const bool v);
|
||||
|
||||
extern uint32_t bl_tick_counter;
|
||||
extern bool antenna_bias;
|
||||
|
||||
|
||||
@@ -30,6 +30,12 @@
|
||||
|
||||
/* Stashes current radio bandwidth and sampling rate.
|
||||
* Inits to defaults, and restores previous when destroyed.
|
||||
* The idea here is that an app will eventually call enable
|
||||
* on the model which will sync all of the model state into
|
||||
* the radio. We tried lazily setting these using the
|
||||
* set_configuration_without_update function, but there are
|
||||
* still various UI components (mainly Waterfall) that need
|
||||
* the radio set in order to load properly.
|
||||
* NB: This member must be added before SettingsManager. */
|
||||
template <typename TModel, TModel* model>
|
||||
class RadioState {
|
||||
@@ -41,8 +47,9 @@ class RadioState {
|
||||
RadioState(uint32_t new_bandwidth, uint32_t new_sampling_rate)
|
||||
: prev_bandwidth_{model->baseband_bandwidth()},
|
||||
prev_sampling_rate_{model->sampling_rate()} {
|
||||
model->set_configuration_without_update(
|
||||
new_bandwidth, new_sampling_rate);
|
||||
// Update the new settings in the radio.
|
||||
model->set_sampling_rate(new_sampling_rate);
|
||||
model->set_baseband_bandwidth(new_bandwidth);
|
||||
}
|
||||
|
||||
~RadioState() {
|
||||
|
||||
@@ -40,15 +40,14 @@ class BoundFrequencyField : public FrequencyField {
|
||||
public:
|
||||
decltype(FrequencyField::on_change) updated{};
|
||||
|
||||
BoundFrequencyField(Point parent_pos, NavigationView& nav, bool update_model = true)
|
||||
BoundFrequencyField(Point parent_pos, NavigationView& nav)
|
||||
: FrequencyField(parent_pos) {
|
||||
// NB: There is no frequency_step on the tx_model.
|
||||
set_step(portapack::receiver_model.frequency_step());
|
||||
set_value(model->target_frequency());
|
||||
|
||||
on_change = [this, update_model](rf::Frequency f) {
|
||||
if (update_model)
|
||||
model->set_target_frequency(f);
|
||||
on_change = [this](rf::Frequency f) {
|
||||
model->set_target_frequency(f);
|
||||
if (updated)
|
||||
updated(f);
|
||||
};
|
||||
|
||||
@@ -36,7 +36,7 @@ FreqManUIList::FreqManUIList(
|
||||
}
|
||||
|
||||
void FreqManUIList::set_highlighted_index(int index) {
|
||||
if ((unsigned)(current_index + index) >= freqlist_db.size())
|
||||
if (freqlist_db == nullptr || (unsigned)(current_index + index) >= freqlist_db->size())
|
||||
return;
|
||||
if (index < 0) {
|
||||
index = 0;
|
||||
@@ -45,10 +45,10 @@ void FreqManUIList::set_highlighted_index(int index) {
|
||||
}
|
||||
if (index >= freqlist_nb_lines) {
|
||||
index = freqlist_nb_lines - 1;
|
||||
if ((unsigned)(current_index + index) < freqlist_db.size())
|
||||
if ((unsigned)(current_index + index) < freqlist_db->size())
|
||||
current_index++;
|
||||
else
|
||||
current_index = freqlist_db.size() - freqlist_nb_lines - 1;
|
||||
current_index = freqlist_db->size() - freqlist_nb_lines - 1;
|
||||
}
|
||||
highlighted_index = index;
|
||||
}
|
||||
@@ -66,17 +66,17 @@ void FreqManUIList::paint(Painter& painter) {
|
||||
r_widget_screen,
|
||||
Color::black());
|
||||
// only return after clearing the screen so previous entries are not shown anymore
|
||||
if (freqlist_db.size() == 0)
|
||||
if (freqlist_db == nullptr || freqlist_db->size() == 0)
|
||||
return;
|
||||
// coloration if file is too big
|
||||
auto text_color = &Styles::white;
|
||||
if (freqlist_db.size() > FREQMAN_MAX_PER_FILE)
|
||||
if (freqlist_db->size() > FREQMAN_MAX_PER_FILE)
|
||||
text_color = &Styles::yellow;
|
||||
uint8_t nb_lines = 0;
|
||||
for (uint8_t it = current_index; it < freqlist_db.size(); it++) {
|
||||
for (uint8_t it = current_index; it < freqlist_db->size(); it++) {
|
||||
uint8_t line_height = (int)nb_lines * char_height;
|
||||
if (line_height < (r.height() - char_height)) { // line is within the widget
|
||||
std::string description = freqman_item_string(freqlist_db[it], 30);
|
||||
std::string description = freqman_item_string(freqlist_db->at(it), 30);
|
||||
if (nb_lines == highlighted_index) {
|
||||
const Rect r_highlighted_freq{0, r.location().y() + (int)nb_lines * char_height, 240, char_height};
|
||||
painter.fill_rectangle(
|
||||
@@ -107,7 +107,7 @@ void FreqManUIList::paint(Painter& painter) {
|
||||
}
|
||||
|
||||
void FreqManUIList::set_db(freqman_db& db) {
|
||||
freqlist_db = db;
|
||||
freqlist_db = &db;
|
||||
if (db.size() == 0) {
|
||||
current_index = 0;
|
||||
highlighted_index = 0;
|
||||
|
||||
@@ -49,6 +49,8 @@ class FreqManUIList : public Widget {
|
||||
FreqManUIList()
|
||||
: FreqManUIList{{}, {}} {
|
||||
}
|
||||
FreqManUIList(const FreqManUIList& other) = delete;
|
||||
FreqManUIList& operator=(const FreqManUIList& other) = delete;
|
||||
|
||||
void paint(Painter& painter) override;
|
||||
void on_focus() override;
|
||||
@@ -64,7 +66,7 @@ class FreqManUIList : public Widget {
|
||||
private:
|
||||
static constexpr int8_t char_height = 16;
|
||||
bool instant_exec_{false};
|
||||
freqman_db freqlist_db{};
|
||||
freqman_db* freqlist_db{nullptr};
|
||||
int current_index{0};
|
||||
int highlighted_index{0};
|
||||
int freqlist_nb_lines{0};
|
||||
|
||||
@@ -31,6 +31,7 @@ using namespace portapack;
|
||||
|
||||
#include "string_format.hpp"
|
||||
#include "complex.hpp"
|
||||
#include "ui_styles.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
@@ -80,8 +81,8 @@ GeoPos::GeoPos(
|
||||
}
|
||||
|
||||
void GeoPos::set_read_only(bool v) {
|
||||
for (auto child : children_)
|
||||
child->set_focusable(!v);
|
||||
// only setting altitude to read-only (allow manual panning via lat/lon fields)
|
||||
field_altitude.set_focusable(!v);
|
||||
}
|
||||
|
||||
// Stupid hack to avoid an event loop
|
||||
@@ -90,7 +91,15 @@ void GeoPos::set_report_change(bool v) {
|
||||
}
|
||||
|
||||
void GeoPos::focus() {
|
||||
field_altitude.focus();
|
||||
if (field_altitude.focusable())
|
||||
field_altitude.focus();
|
||||
else
|
||||
field_lat_degrees.focus();
|
||||
}
|
||||
|
||||
void GeoPos::hide_altitude() {
|
||||
// Color altitude grey to indicate it's not updated in manual panning mode
|
||||
field_altitude.set_style(&Styles::grey);
|
||||
}
|
||||
|
||||
void GeoPos::set_altitude(int32_t altitude) {
|
||||
@@ -132,11 +141,74 @@ int32_t GeoPos::altitude() {
|
||||
GeoMap::GeoMap(
|
||||
Rect parent_rect)
|
||||
: Widget{parent_rect}, markerListLen(0) {
|
||||
// set_focusable(true);
|
||||
}
|
||||
|
||||
bool GeoMap::on_encoder(const EncoderEvent delta) {
|
||||
if ((delta > 0) && (map_zoom < 5)) {
|
||||
map_zoom++;
|
||||
|
||||
// Ensure that MOD(240,map_zoom)==0 for the map_zoom_line() function
|
||||
if (240 % map_zoom != 0) {
|
||||
map_zoom--;
|
||||
}
|
||||
} else if ((delta < 0) && (map_zoom > 1)) {
|
||||
map_zoom--;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Trigger map redraw
|
||||
markerListUpdated = true;
|
||||
set_dirty();
|
||||
return true;
|
||||
}
|
||||
|
||||
void GeoMap::map_zoom_line(ui::Color* buffer) {
|
||||
if (map_zoom <= 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
// As long as MOD(width,map_zoom)==0 then we don't need to check buffer overflow case when stretching last pixel;
|
||||
// For 240 width, than means no check is needed for map_zoom values up to 6
|
||||
for (int i = (240 / map_zoom) - 1; i >= 0; i--) {
|
||||
for (int j = 0; j < map_zoom; j++) {
|
||||
buffer[(i * map_zoom) + j] = buffer[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GeoMap::draw_markers(Painter& painter) {
|
||||
const auto r = screen_rect();
|
||||
|
||||
for (int i = 0; i < markerListLen; ++i) {
|
||||
GeoMarker& item = markerList[i];
|
||||
double lat_rad = sin(item.lat * pi / 180);
|
||||
int x = (map_width * (item.lon + 180) / 360) - x_pos;
|
||||
int y = (map_height - ((map_world_lon / 2 * log((1 + lat_rad) / (1 - lat_rad))) - map_offset)) - y_pos; // Offset added for the GUI
|
||||
|
||||
if (map_zoom != 1) {
|
||||
x = ((x - (r.width() / 2)) * map_zoom) + (r.width() / 2);
|
||||
y = ((y - (r.height() / 2)) * map_zoom) + (r.height() / 2);
|
||||
}
|
||||
|
||||
if ((x >= 0) && (x < r.width()) &&
|
||||
(y > 10) && (y < r.height())) // Dont draw within symbol size of top
|
||||
{
|
||||
ui::Point itemPoint(x, y + r.top());
|
||||
if (y >= 32) { // Dont draw text if it would overlap top
|
||||
// Text and symbol
|
||||
draw_marker(painter, itemPoint, item.angle, item.tag, Color::blue(), Color::blue(), Color::magenta());
|
||||
} else {
|
||||
// Only symbol
|
||||
draw_bearing(itemPoint, item.angle, 10, Color::blue());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GeoMap::paint(Painter& painter) {
|
||||
uint16_t line;
|
||||
uint16_t line, j;
|
||||
uint32_t zoom_seek_x, zoom_seek_y;
|
||||
std::array<ui::Color, 240> map_line_buffer;
|
||||
const auto r = screen_rect();
|
||||
|
||||
@@ -144,39 +216,41 @@ void GeoMap::paint(Painter& painter) {
|
||||
// or the markers list was updated
|
||||
int x_diff = abs(x_pos - prev_x_pos);
|
||||
int y_diff = abs(y_pos - prev_y_pos);
|
||||
if (markerListUpdated || (x_diff >= 3) || (y_diff >= 3)) {
|
||||
for (line = 0; line < r.height(); line++) {
|
||||
map_file.seek(4 + ((x_pos + (map_width * (y_pos + line))) << 1));
|
||||
map_file.read(map_line_buffer.data(), r.width() << 1);
|
||||
display.draw_pixels({0, r.top() + line, r.width(), 1}, map_line_buffer);
|
||||
if (markerListUpdated || (x_diff >= map_zoom * 3) || (y_diff >= map_zoom * 3)) {
|
||||
if (map_zoom == 1) {
|
||||
zoom_seek_x = zoom_seek_y = 0;
|
||||
} else {
|
||||
zoom_seek_x = (r.width() - (r.width() / map_zoom)) / 2;
|
||||
zoom_seek_y = (r.height() - (r.height() / map_zoom)) / 2;
|
||||
}
|
||||
|
||||
for (line = 0; line < (r.height() / map_zoom); line++) {
|
||||
map_file.seek(4 + ((x_pos + zoom_seek_x + (map_width * (y_pos + line + zoom_seek_y))) << 1));
|
||||
map_file.read(map_line_buffer.data(), (r.width() / map_zoom) << 1);
|
||||
map_zoom_line(map_line_buffer.data());
|
||||
for (j = 0; j < map_zoom; j++) {
|
||||
display.draw_pixels({0, r.top() + (line * map_zoom) + j, r.width(), 1}, map_line_buffer);
|
||||
}
|
||||
}
|
||||
prev_x_pos = x_pos;
|
||||
prev_y_pos = y_pos;
|
||||
|
||||
// Draw crosshairs in center in manual panning mode
|
||||
if (manual_panning_) {
|
||||
display.fill_rectangle({r.center() - Point(16, 1), {32, 2}}, Color::red());
|
||||
display.fill_rectangle({r.center() - Point(1, 16), {2, 32}}, Color::red());
|
||||
}
|
||||
|
||||
// Draw the other markers
|
||||
for (int i = 0; i < markerListLen; ++i) {
|
||||
GeoMarker& item = markerList[i];
|
||||
double lat_rad = sin(item.lat * pi / 180);
|
||||
int x = (map_width * (item.lon + 180) / 360) - x_pos;
|
||||
int y = (map_height - ((map_world_lon / 2 * log((1 + lat_rad) / (1 - lat_rad))) - map_offset)) - y_pos; // Offset added for the GUI
|
||||
if ((x >= 0) && (x < r.width()) &&
|
||||
(y > 10) && (y < r.height())) // Dont draw within symbol size of top
|
||||
{
|
||||
ui::Point itemPoint(x, y + r.top());
|
||||
if (y >= 32) { // Dont draw text if it would overlap top
|
||||
// Text and symbol
|
||||
draw_marker(painter, itemPoint, item.angle, item.tag, Color::blue(), Color::blue(), Color::magenta());
|
||||
} else {
|
||||
// Only symbol
|
||||
draw_bearing(itemPoint, item.angle, 10, Color::blue());
|
||||
}
|
||||
}
|
||||
markerListUpdated = false;
|
||||
} // Draw the other markers
|
||||
draw_markers(painter);
|
||||
markerListUpdated = false;
|
||||
set_clean();
|
||||
}
|
||||
|
||||
// Draw the marker in the center
|
||||
draw_marker(painter, r.center(), angle_, tag_, Color::red(), Color::white(), Color::black());
|
||||
if (!manual_panning_) {
|
||||
draw_marker(painter, r.center(), angle_, tag_, Color::red(), Color::white(), Color::black());
|
||||
}
|
||||
}
|
||||
|
||||
bool GeoMap::on_touch(const TouchEvent event) {
|
||||
@@ -236,6 +310,14 @@ void GeoMap::set_mode(GeoMapMode mode) {
|
||||
mode_ = mode;
|
||||
}
|
||||
|
||||
void GeoMap::set_manual_panning(bool v) {
|
||||
manual_panning_ = v;
|
||||
}
|
||||
|
||||
bool GeoMap::manual_panning() {
|
||||
return manual_panning_;
|
||||
}
|
||||
|
||||
void GeoMap::draw_bearing(const Point origin, const uint16_t angle, uint32_t size, const Color color) {
|
||||
Point arrow_a, arrow_b, arrow_c;
|
||||
|
||||
@@ -311,6 +393,11 @@ void GeoMapView::focus() {
|
||||
}
|
||||
|
||||
void GeoMapView::update_position(float lat, float lon, uint16_t angle, int32_t altitude) {
|
||||
if (geomap.manual_panning()) {
|
||||
geomap.set_dirty();
|
||||
return;
|
||||
}
|
||||
|
||||
lat_ = lat;
|
||||
lon_ = lon;
|
||||
altitude_ = altitude;
|
||||
@@ -342,6 +429,8 @@ void GeoMapView::setup() {
|
||||
altitude_ = altitude;
|
||||
lat_ = lat;
|
||||
lon_ = lon;
|
||||
geopos.hide_altitude();
|
||||
geomap.set_manual_panning(true);
|
||||
geomap.move(lon_, lat_);
|
||||
geomap.set_dirty();
|
||||
};
|
||||
@@ -396,6 +485,7 @@ GeoMapView::GeoMapView(
|
||||
geomap.set_tag(tag);
|
||||
geomap.set_angle(angle);
|
||||
geomap.move(lon_, lat_);
|
||||
geomap.set_focusable(true);
|
||||
|
||||
geopos.set_read_only(true);
|
||||
}
|
||||
@@ -425,6 +515,7 @@ GeoMapView::GeoMapView(
|
||||
|
||||
geomap.set_mode(mode_);
|
||||
geomap.move(lon_, lat_);
|
||||
geomap.set_focusable(true);
|
||||
|
||||
button_ok.on_select = [this, on_done, &nav](Button&) {
|
||||
if (on_done)
|
||||
|
||||
@@ -71,6 +71,7 @@ class GeoPos : public View {
|
||||
void set_lat(float lat);
|
||||
void set_lon(float lon);
|
||||
int32_t altitude();
|
||||
void hide_altitude();
|
||||
float lat();
|
||||
float lon();
|
||||
|
||||
@@ -157,9 +158,12 @@ class GeoMap : public Widget {
|
||||
void paint(Painter& painter) override;
|
||||
|
||||
bool on_touch(const TouchEvent event) override;
|
||||
bool on_encoder(const EncoderEvent delta) override;
|
||||
|
||||
bool init();
|
||||
void set_mode(GeoMapMode mode);
|
||||
void set_manual_panning(bool v);
|
||||
bool manual_panning();
|
||||
void move(const float lon, const float lat);
|
||||
void set_tag(std::string new_tag) {
|
||||
tag_ = new_tag;
|
||||
@@ -177,11 +181,15 @@ class GeoMap : public Widget {
|
||||
private:
|
||||
void draw_bearing(const Point origin, const uint16_t angle, uint32_t size, const Color color);
|
||||
void draw_marker(Painter& painter, const ui::Point itemPoint, const uint16_t itemAngle, const std::string itemTag, const Color color = Color::red(), const Color fontColor = Color::white(), const Color backColor = Color::black());
|
||||
void draw_markers(Painter& painter);
|
||||
void map_zoom_line(ui::Color* buffer);
|
||||
|
||||
bool manual_panning_{false};
|
||||
GeoMapMode mode_{};
|
||||
File map_file{};
|
||||
uint16_t map_width{}, map_height{};
|
||||
int32_t map_center_x{}, map_center_y{};
|
||||
int16_t map_zoom{1};
|
||||
float lon_ratio{}, lat_ratio{};
|
||||
double map_bottom{};
|
||||
double map_world_lon{};
|
||||
|
||||
@@ -311,10 +311,12 @@ WaterfallWidget::WaterfallWidget(const bool cursor) {
|
||||
}
|
||||
|
||||
void WaterfallWidget::on_show() {
|
||||
// TODO: Assert that baseband is not shutdown.
|
||||
baseband::spectrum_streaming_start();
|
||||
}
|
||||
|
||||
void WaterfallWidget::on_hide() {
|
||||
// TODO: Assert that baseband is not shutdown.
|
||||
baseband::spectrum_streaming_stop();
|
||||
}
|
||||
|
||||
|
||||
@@ -104,6 +104,10 @@ class FrequencyScale : public Widget {
|
||||
void draw_filter_ranges(Painter& painter, const Rect r);
|
||||
};
|
||||
|
||||
/* NB: These visualizations rely on having a baseband image running.
|
||||
* If the baseband is shutdown or otherwise not running when interacting
|
||||
* with these, they will almost certainly hang the device. */
|
||||
|
||||
class WaterfallView : public Widget {
|
||||
public:
|
||||
void on_show() override;
|
||||
|
||||
@@ -194,6 +194,8 @@ SystemStatusView::SystemStatusView(
|
||||
button_clock_status.on_select = [this](ImageButton&) {
|
||||
this->on_clk();
|
||||
};
|
||||
|
||||
audio::output::update_audio_mute();
|
||||
}
|
||||
|
||||
void SystemStatusView::refresh() {
|
||||
@@ -213,9 +215,6 @@ void SystemStatusView::refresh() {
|
||||
}
|
||||
}
|
||||
|
||||
portapack::set_speaker_disable(portapack::persistent_memory::config_speaker_disable());
|
||||
portapack::set_audio_mute(portapack::persistent_memory::config_audio_mute());
|
||||
|
||||
if (portapack::persistent_memory::config_audio_mute()) {
|
||||
button_speaker.set_foreground(Color::light_grey());
|
||||
button_speaker.set_bitmap(&bitmap_icon_speaker_mute);
|
||||
@@ -287,13 +286,8 @@ void SystemStatusView::on_converter() {
|
||||
}
|
||||
|
||||
void SystemStatusView::on_speaker() {
|
||||
if (portapack::persistent_memory::config_audio_mute()) {
|
||||
portapack::set_audio_mute(false);
|
||||
portapack::persistent_memory::set_config_audio_mute(false);
|
||||
} else {
|
||||
portapack::set_audio_mute(true);
|
||||
portapack::persistent_memory::set_config_audio_mute(true);
|
||||
}
|
||||
portapack::persistent_memory::set_config_audio_mute(!portapack::persistent_memory::config_audio_mute());
|
||||
audio::output::update_audio_mute();
|
||||
refresh();
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ using namespace portapack;
|
||||
#include "io_wave.hpp"
|
||||
|
||||
#include "baseband_api.hpp"
|
||||
#include "metadata_file.hpp"
|
||||
#include "rtc_time.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "utility.hpp"
|
||||
@@ -194,7 +195,10 @@ void RecordView::start() {
|
||||
} break;
|
||||
|
||||
case FileType::RawS16: {
|
||||
const auto metadata_file_error = write_metadata_file(base_path.replace_extension(u".TXT"));
|
||||
const auto metadata_file_error =
|
||||
write_metadata_file(get_metadata_path(base_path),
|
||||
{receiver_model.target_frequency(), sampling_rate / 8});
|
||||
// Not sure why sample_rate is div. 8, but stored value matches rate settings.
|
||||
if (metadata_file_error.is_valid()) {
|
||||
handle_error(metadata_file_error.value());
|
||||
return;
|
||||
@@ -246,24 +250,6 @@ void RecordView::stop() {
|
||||
update_status_display();
|
||||
}
|
||||
|
||||
Optional<File::Error> RecordView::write_metadata_file(const std::filesystem::path& filename) {
|
||||
File file;
|
||||
const auto create_error = file.create(filename);
|
||||
if (create_error.is_valid()) {
|
||||
return create_error;
|
||||
} else {
|
||||
const auto error_line1 = file.write_line("sample_rate=" + to_string_dec_uint(sampling_rate / 8));
|
||||
if (error_line1.is_valid()) {
|
||||
return error_line1;
|
||||
}
|
||||
const auto error_line2 = file.write_line("center_frequency=" + to_string_dec_uint(receiver_model.target_frequency()));
|
||||
if (error_line2.is_valid()) {
|
||||
return error_line2;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
void RecordView::on_tick_second() {
|
||||
update_status_display();
|
||||
}
|
||||
@@ -281,7 +267,7 @@ void RecordView::update_status_display() {
|
||||
|
||||
if (sampling_rate) {
|
||||
const auto space_info = std::filesystem::space(u"");
|
||||
const uint32_t bytes_per_second = file_type == FileType::WAV ? (sampling_rate * 2) : (sampling_rate / 8 * 4);
|
||||
const uint32_t bytes_per_second = file_type == FileType::WAV ? (sampling_rate * 2) : (sampling_rate / 8 * 4); // TODO: Why 8/4??
|
||||
const uint32_t available_seconds = space_info.free / bytes_per_second;
|
||||
const uint32_t seconds = available_seconds % 60;
|
||||
const uint32_t available_minutes = available_seconds / 60;
|
||||
|
||||
@@ -68,7 +68,6 @@ class RecordView : public View {
|
||||
private:
|
||||
void toggle();
|
||||
// void toggle_pitch_rssi();
|
||||
Optional<File::Error> write_metadata_file(const std::filesystem::path& filename);
|
||||
|
||||
void on_tick_second();
|
||||
void update_status_display();
|
||||
|
||||
@@ -175,7 +175,11 @@ void AK4951::headphone_enable() {
|
||||
|
||||
void AK4951::headphone_disable() {
|
||||
set_headphone_power(false);
|
||||
set_dac_power(false);
|
||||
|
||||
// Don't power off DAC unless Speaker is disabled also
|
||||
if (map.r.power_management_2.PMSL == 0) {
|
||||
set_dac_power(false);
|
||||
}
|
||||
}
|
||||
|
||||
void AK4951::speaker_enable() {
|
||||
@@ -210,7 +214,11 @@ void AK4951::speaker_disable() {
|
||||
update(Register::SignalSelect1);
|
||||
|
||||
// Power down DAC, Programmable Filter and speaker: PMDAC=PMPFIL=PMSL bits= “1”→“0”
|
||||
set_dac_power(false);
|
||||
// Exception: Don't power off DAC unless Headphones are disabled too
|
||||
if (map.r.power_management_2.PMHPL == 0) {
|
||||
set_dac_power(false);
|
||||
}
|
||||
|
||||
// map.r.power_management_1.PMPFIL = 0;
|
||||
// update(Register::PowerManagement1);
|
||||
set_speaker_power(false);
|
||||
|
||||
Reference in New Issue
Block a user