Compare commits

...

29 Commits

Author SHA1 Message Date
Brumi-2021 0dcf2af50d Minor GUI change to the Mic App (#1084)
* Mic App GUI tidy up
2023-05-27 18:13:17 +02:00
Bernd Herzog cf07428a5d changed the vendor id of sd to usb (#1082) 2023-05-27 21:39:04 +12:00
E.T 35b381ae77 Fix SD over USB (#1081)
Remove clkin_detect_init() from cpu_clock_init() mirroring upstream change from the hackrf repo

Co-authored-by: Eisenberger Tamas <e.tamas@iwstudio.hu>
2023-05-27 11:06:05 +12:00
gullradriel 0fc748a234 precision marker tweak on single pass (#1079)
* precision marker tweaks
2023-05-26 22:42:12 +02:00
gullradriel 638ea78db9 cosmetic fix on option config (#1080) 2023-05-26 19:47:41 +02:00
gullradriel e81747ef7b fixed wrong function declaration (#1078) 2023-05-26 19:35:17 +02:00
gullradriel 89e24cb358 Freqman mem reduce (#1076)
-made frequency lists a widget to avoid a full frequency_list copy
-reduced FREQMAN_MAX_PER_FILE to a working limit
2023-05-26 18:17:47 +02:00
Kyle Reed 00667cecf9 Notepad menu (#1072)
* WIP notepad menu
2023-05-26 10:02:17 +02:00
zxkmm 53fcdedb88 init commit for fix touchscreen bad corner (#1071)
fix a rounding issue and allow more sensible touchscreen
2023-05-26 09:50:32 +02:00
zxkmm 5bdf9363e9 fix for bad track count (#1069)
* fix for bad track count
* code format
* Thanks kallanreed for providing a more clear logic!
2023-05-25 22:07:27 +02:00
Mark Thompson 6b44a77ef6 Support for Rotary Encoder Dial sensitivity levels, issue #965 (#1057)
* Support for 3 levels of rotary encoder sensitivity #965

Backend support; UI will still need to call set function to configure.

* Support for 3 levels of rotary encoder sensitivity #965

Backend support only.  UI will still need to be changed to call the set_sensitivity() function to configure.

* Removed trailing space

* Deleted blank lines to see if format checker will be happier

* Simpler support for multiple levels of encoder sensitivity, for issue #965

Removed the convoluted code :-) and instead just using a 2-dimensional array to choose which transition map to use.  For now I only have 2 (vs 3) levels enabled as well, to save code space and because high-sensitivity is very touchy.

* Simpler version of configurable encoder sensitivity, issue #965

* Formatting

* Formatting test for Clang

* Formatting test

* Formatting (removed helpful comment)

* Formatting test (remove commented-out code)

* Formatting & swapping medium/low so default mode=0

* Swapped medium/low so default mode=0

* Adding UI & PMEM support to make encoder dial sensitivity configurable, issue #965

* Adding UI & PMEM support to make encoder dial sensitivity configurable, issue #965

* Adding UI & PMEM support to make encoder dial sensitivity configurable, issue #965

* Adding UI & PMEM support to make encoder dial sensitivity configurable, issue #965

* Adding UI & PMEM support to make encoder dial sensitivity configurable, issue #965

* Adding UI & PMEM support to make encoder dial sensitivity configurable, issue #965

* Removed unneeded range check (trusting in pmem checksum)
2023-05-24 19:32:12 -07:00
gullradriel edc6dc819c Recon: reducing memory imprint, live list removal function in Recon (#1058)
* reducing memory imprint before launching a nav
* added remove from loaded list in Recon / delete from output file in Scanner functionality
* fix initial color for filename
2023-05-24 20:52:05 +02:00
Kenny ffd72eb232 ui_spectrum_painter tx gain initialization (#1059)
When starting the app, actual tx gain is 47 while the ui shows 10. "Increasing" gain to 11 takes effect, reducing actual gain as displayed in UI. This change makes the UI gain value consistent with actual tx gain.
2023-05-24 19:54:40 +02:00
Kyle Reed feee1f7ab3 Set default TX gain to 35 (#1062)
* Set default TX gain to 10

I'd rather people who know better have to turn this up, than people who don't know better to be jamming their neighbors accidentally.

* Update transmitter_model.hpp

Set default to 35 per discussion.
2023-05-24 19:31:59 +02:00
Kyle Reed 2d79cd4b24 Fix button_exit width (#1064)
I moved the exit button without making it narrower in the last change. This shrinks the width, so it fits on the screen.
2023-05-24 19:20:48 +02:00
Kyle Reed 637bcfdbc7 Use receiver/transmitter models everywhere (#1056)
* Use receiver/transmitter models everywhere

* Run formatter

* Fix a copy-paste bug, make transmitter_model actually set tx_gain.

---------

Co-authored-by: kallanreed <kallanreed@outlook.com>
2023-05-23 21:22:30 -07:00
zxkmm 22c9dd31bf playlist fix -fix for review comments (#1054) 2023-05-23 18:28:50 +02:00
Brumi-2021 9e63d80667 Solving_error_dfu_util_v_011_integrated_vscode (#1055) 2023-05-23 18:28:34 +02:00
Mark Thompson 67b5b57533 Support "repeat" when a direction button is held down (#1053)
* Support "repeat" when holding a direction button

* Support "repeat" when a direction button is held

* Support "repeat" when a direction button is held

* Support "repeat" when a direction button is held

* Formatting violation - removed a trailing space

* Removed unneeded return() statement
2023-05-23 18:55:26 +12:00
Kyle Reed c2314f4838 Support open in notepad from fileman (#1052)
* Support open in notepad from fileman
* Align Filename Exit button with UI
2023-05-23 06:40:03 +02:00
zxkmm 1cb682473a playlist fix (#1043)
* first commit for playlist fix
* format for playlist fix
* playlist fix -textual change
thanks @kallanreed for giving the new name of the extension XD
2023-05-23 06:39:22 +02:00
Mark Thompson 63dd85cbfc Fixed logic error in unused code module (#1049) 2023-05-23 16:26:16 +12:00
gullradriel 6dbc8460b4 hackrf submodule update (#1048) 2023-05-22 23:28:47 +02:00
gullradriel ec8bf04baa looking glass: correct max_hold computation with new modes (#1047) 2023-05-22 23:24:46 +02:00
Mark Thompson a1189faad1 Fixed Scanner RESUME button when delay timer Wsa==0, issue #1041 (#1046) 2023-05-22 23:21:00 +02:00
gullradriel ba9cc55b5a add original or complementary author (#1045) 2023-05-22 22:17:28 +02:00
gullradriel c2e527bce2 removed unneeded RECON directory (#1044) 2023-05-22 22:16:29 +02:00
Kyle Reed 98f3bf151f Lazy line caching for Notepad (#1042)
* easier 'now', start adding text editor

* Adding scrolling to notepad

* Better scrolling

* Better scrolling, off-by-1 bugs

* MVP fit and finish

* Add tiny font and use in Notepad

* Font tweaking, tiny font cursor

* Fix warning

* Format changed files

* WIP No file limit

* WIP - adding CircularBuffer type

* WIP Caching

* add  unit test for circular_buffer

* WIP still have a bug when moving cache forward

* Finish lazy line caching

---------

Co-authored-by: kallanreed <kallanreed@outlook.com>
2023-05-22 13:08:59 -07:00
Erwin Ried 802a4e243b Update README.md (#1040) 2023-05-22 20:07:44 +02:00
70 changed files with 1997 additions and 639 deletions
-4
View File
@@ -45,10 +45,6 @@ Consider that the hardware and firmware has been created and maintain by a [lot]
To support the people behind the hardware, please buy a genuine [HackRF](https://greatscottgadgets.com/hackrf/) and [PortaPack](https://store.sharebrained.com/products/portapack-for-hackrf-one-kit).
As a last option, if you want to send money directly to me for getting more boards, antennas and such:
[![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=CBPQA4HRRPJQ6&source=url)
## What if I really want something specific?
If what you need can be relevant in general, you can [request a feature](https://github.com/eried/portapack-mayhem/issues/new?labels=enhancement&template=feature_request.md).
+1 -1
View File
@@ -53,7 +53,7 @@ add_custom_target(
add_custom_target(
program
COMMAND dfu-util --device 1fc9:000c --download ${HACKRF_FIRMWARE_DFU_IMAGE}
COMMAND dfu-util --device 1fc9:000c --download ${HACKRF_FIRMWARE_DFU_IMAGE} || (exit 0) # We need to add it for dfu-utils v.011 , (in v.09 it is not necessary)
COMMAND sleep 3s
COMMAND hackrf_spiflash -w ${FIRMWARE_FILENAME}
DEPENDS ${FIRMWARE_FILENAME}
+1
View File
@@ -218,6 +218,7 @@ set(CPPSRC
ui/ui_btngrid.cpp
ui/ui_receiver.cpp
ui/ui_rssi.cpp
ui/ui_freqlist.cpp
ui/ui_tv.cpp
ui/ui_spectrum.cpp
ui/ui_tabview.cpp
+3 -3
View File
@@ -395,7 +395,7 @@ AISAppView::AISAppView(NavigationView& nav)
receiver_model.set_tuning_frequency(tuning_frequency());
receiver_model.set_sampling_rate(sampling_rate);
receiver_model.set_baseband_bandwidth(baseband_bandwidth);
receiver_model.enable(); // Before using radio::enable(), but not updating Ant.DC-Bias.
receiver_model.enable();
options_channel.on_change = [this](size_t, OptionsField::value_t v) {
this->on_frequency_changed(v);
@@ -420,7 +420,7 @@ AISAppView::~AISAppView() {
app_settings.rx_frequency = target_frequency_;
settings.save("rx_ais", &app_settings);
receiver_model.disable(); // to switch off all, including DC bias.
receiver_model.disable();
baseband::shutdown();
}
@@ -471,7 +471,7 @@ void AISAppView::on_frequency_changed(const uint32_t new_target_frequency) {
void AISAppView::set_target_frequency(const uint32_t new_value) {
target_frequency_ = new_value;
radio::set_tuning_frequency(tuning_frequency());
receiver_model.set_tuning_frequency(tuning_frequency());
}
uint32_t AISAppView::target_frequency() const {
@@ -52,7 +52,7 @@ class AMOptionsView : public View {
OptionsField options_config{
{3 * 8, 0 * 16},
5,
6, // number of blanking characters
{
// using common messages from freqman.cpp
}};
+2 -12
View File
@@ -121,17 +121,7 @@ ERTAppView::ERTAppView(NavigationView&) {
receiver_model.set_tuning_frequency(initial_target_frequency);
receiver_model.set_sampling_rate(sampling_rate);
receiver_model.set_baseband_bandwidth(baseband_bandwidth);
receiver_model.enable(); // Before using radio::enable(), but not updating Ant.DC-Bias.
/* radio::enable({
initial_target_frequency,
sampling_rate,
baseband_bandwidth,
rf::Direction::Receive,
receiver_model.rf_amp(),
static_cast<int8_t>(receiver_model.lna()),
static_cast<int8_t>(receiver_model.vga()),
}); */
receiver_model.enable();
logger = std::make_unique<ERTLogger>();
if (logger) {
@@ -143,7 +133,7 @@ ERTAppView::~ERTAppView() {
// save app settings
settings.save("rx_ert", &app_settings);
receiver_model.disable(); // to switch off all, including DC bias and change flag enabled_
receiver_model.disable();
baseband::shutdown();
}
+5 -11
View File
@@ -141,15 +141,9 @@ void GpsSimAppView::start() {
});
}
rf_amp = (transmitter_model.rf_amp()); // recover rf_amp settings applied from ui_transmiter.cpp
radio::enable({receiver_model.tuning_frequency(),
sample_rate,
baseband_bandwidth,
rf::Direction::Transmit,
rf_amp, // previous code line : "receiver_model.rf_amp()," was passing the same rf_amp of all Receiver Apps
static_cast<int8_t>(receiver_model.lna()),
static_cast<int8_t>(receiver_model.vga())});
transmitter_model.set_sampling_rate(sample_rate);
transmitter_model.set_baseband_bandwidth(baseband_bandwidth);
transmitter_model.enable();
}
void GpsSimAppView::stop(const bool do_loop) {
@@ -159,7 +153,7 @@ void GpsSimAppView::stop(const bool do_loop) {
if (do_loop && check_loop.value()) {
start();
} else {
radio::disable();
transmitter_model.disable();
button_play.set_bitmap(&bitmap_play);
}
@@ -224,7 +218,7 @@ GpsSimAppView::GpsSimAppView(
}
GpsSimAppView::~GpsSimAppView() {
radio::disable();
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.
}
+5 -15
View File
@@ -141,18 +141,9 @@ void ReplayAppView::start() {
});
}
// Enable Bias Tee if selected
radio::set_antenna_bias(portapack::get_antenna_bias());
rf_amp = (transmitter_model.rf_amp()); // recover rf_amp settings applied from ui_transmiter.cpp
radio::enable({receiver_model.tuning_frequency(),
sample_rate * 8,
baseband_bandwidth,
rf::Direction::Transmit,
rf_amp, // previous code line : "receiver_model.rf_amp()," was passing the same rf_amp of all Receiver Apps
static_cast<int8_t>(receiver_model.lna()),
static_cast<int8_t>(receiver_model.vga())});
transmitter_model.set_sampling_rate(sample_rate * 8);
transmitter_model.set_baseband_bandwidth(baseband_bandwidth);
transmitter_model.enable();
if (portapack::persistent_memory::stealth_mode()) {
DisplaySleepMessage message;
@@ -167,8 +158,7 @@ void ReplayAppView::stop(const bool do_loop) {
if (do_loop && check_loop.value()) {
start();
} else {
radio::set_antenna_bias(false); // Turn off Bias Tee
radio::disable();
transmitter_model.disable();
button_play.set_bitmap(&bitmap_play);
}
@@ -233,7 +223,7 @@ ReplayAppView::ReplayAppView(
}
ReplayAppView::~ReplayAppView() {
radio::disable();
transmitter_model.disable();
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 )
+3 -14
View File
@@ -170,17 +170,7 @@ TPMSAppView::TPMSAppView(NavigationView&) {
receiver_model.set_tuning_frequency(tuning_frequency());
receiver_model.set_sampling_rate(sampling_rate);
receiver_model.set_baseband_bandwidth(baseband_bandwidth);
receiver_model.enable(); // Before using radio::enable(), but not updating Ant.DC-Bias.
/* radio::enable({
tuning_frequency(),
sampling_rate,
baseband_bandwidth,
rf::Direction::Receive,
receiver_model.rf_amp(),
static_cast<int8_t>(receiver_model.lna()),
static_cast<int8_t>(receiver_model.vga()),
}); */
receiver_model.enable();
options_band.on_change = [this](size_t, OptionsField::value_t v) {
this->on_band_changed(v);
@@ -212,8 +202,7 @@ TPMSAppView::~TPMSAppView() {
app_settings.rx_frequency = target_frequency_;
settings.save("rx_tpms", &app_settings);
receiver_model.disable(); // to switch off all, including DC bias and change flag enabled_
receiver_model.disable();
baseband::shutdown();
}
@@ -258,7 +247,7 @@ void TPMSAppView::on_band_changed(const uint32_t new_band_frequency) {
void TPMSAppView::set_target_frequency(const uint32_t new_value) {
target_frequency_ = new_value;
radio::set_tuning_frequency(tuning_frequency());
receiver_model.set_tuning_frequency(tuning_frequency());
}
uint32_t TPMSAppView::target_frequency() const {
+1 -1
View File
@@ -178,7 +178,7 @@ void AboutView::draw_demoglyph(ui::Point p, char ch, ui::Color* pal) {
int16_t lbx, il;
// Map ASCII to font bitmap
if ((ch >= 32) || (ch < 96))
if ((ch >= 32) && (ch < 96))
che = char_map[ch - 32];
else
che = 0xFF;
+22 -10
View File
@@ -26,6 +26,7 @@
#include <algorithm>
#include "ui_fileman.hpp"
#include "ui_text_editor.hpp"
#include "string_format.hpp"
#include "portapack.hpp"
#include "event_m0.hpp"
@@ -510,16 +511,19 @@ FileManagerView::FileManagerView(
refresh_widgets(v);
};
add_children({&menu_view,
&labels,
&text_date,
&button_rename,
&button_delete,
&button_cut,
&button_copy,
&button_paste,
&button_new_dir,
&button_new_file});
add_children({
&menu_view,
&labels,
&text_date,
&button_rename,
&button_delete,
&button_cut,
&button_copy,
&button_paste,
&button_new_dir,
&button_new_file,
&button_open_notepad,
});
menu_view.on_highlight = [this]() {
if (selected_is_valid())
@@ -578,6 +582,14 @@ FileManagerView::FileManagerView(
button_new_file.on_select = [this]() {
on_new_file();
};
button_open_notepad.on_select = [this]() {
if (selected_is_valid() && !get_selected_entry().is_directory) {
auto path = get_selected_full_path();
nav_.replace<TextEditorView>(path);
} else
nav_.display_modal("Open in Notepad", "Can't open that in Notepad.");
};
}
} // namespace ui
+9 -2
View File
@@ -77,7 +77,8 @@ class FileManBaseView : public View {
{u".C8", &bitmap_icon_file_iq, ui::Color::dark_cyan()},
{u".C16", &bitmap_icon_file_iq, ui::Color::dark_cyan()},
{u".WAV", &bitmap_icon_file_wav, ui::Color::dark_magenta()},
{u"", &bitmap_icon_file, ui::Color::light_grey()} // NB: Must be last.
{u".PPL", &bitmap_icon_file_iq, ui::Color::white()}, // PPL is the file extension for playlist app
{u"", &bitmap_icon_file, ui::Color::light_grey()} // NB: Must be last.
};
std::filesystem::path get_selected_full_path() const;
@@ -120,7 +121,7 @@ class FileManBaseView : public View {
""};
Button button_exit{
{21 * 8, 34 * 8, 9 * 8, 32},
{22 * 8, 34 * 8, 8 * 8, 32},
"Exit"};
};
@@ -263,6 +264,12 @@ class FileManagerView : public FileManBaseView {
{},
&bitmap_icon_new_file,
Color::green()};
NewButton button_open_notepad{
{0 * 8, 34 * 8, 4 * 8, 32},
{},
&bitmap_icon_notepad,
Color::orange()};
};
} /* namespace ui */
@@ -40,7 +40,7 @@ class FlashUtilityView : public View {
void focus() override;
std::string title() const override { return "Flash Utility"; };
std::string title() const override { return "FlashUtility"; }; // Removed the space because the title and speaker icon overlapped.
private:
NavigationView& nav_;
+11 -33
View File
@@ -97,8 +97,10 @@ void FreqManBaseView::change_category(int32_t category_id) {
if (!load_freqman_file(file_list[categories[current_category_id].second], database))
error_ = ERROR_ACCESS;
else
else {
menu_view.set_db(database);
refresh_list();
}
}
void FreqManBaseView::refresh_list() {
@@ -108,20 +110,6 @@ void FreqManBaseView::refresh_list() {
} else {
if (on_refresh_widgets)
on_refresh_widgets(false);
menu_view.clear();
for (size_t n = 0; n < database.size(); n++) {
menu_view.add_item({freqman_item_string(database[n], 30),
ui::Color::white(),
nullptr,
[this](KeyEvent) {
if (on_select_frequency)
on_select_frequency();
}});
}
menu_view.set_highlighted(0); // Refresh
}
}
@@ -206,18 +194,13 @@ FrequencyLoadView::FrequencyLoadView(
// Resize menu view to fill screen
menu_view.set_parent_rect({0, 3 * 8, 240, 30 * 8});
// Just to allow exit on left
menu_view.on_left = [&nav, this]() {
nav.pop();
};
change_category(last_category_id);
refresh_list();
on_select_frequency = [&nav, this]() {
menu_view.on_select = [&nav, this](FreqManUIList&) {
nav_.pop();
auto& entry = database[menu_view.highlighted_index()];
auto& entry = database[menu_view.get_index()];
if (entry.type == RANGE) {
// User chose a frequency range entry
@@ -235,14 +218,14 @@ FrequencyLoadView::FrequencyLoadView(
}
void FrequencyManagerView::on_edit_freq(rf::Frequency f) {
database[menu_view.highlighted_index()].frequency_a = f;
database[menu_view.get_index()].frequency_a = f;
save_freqman_file(file_list[categories[current_category_id].second], database);
refresh_list();
}
void FrequencyManagerView::on_edit_desc(NavigationView& nav) {
text_prompt(nav, desc_buffer, 28, [this](std::string& buffer) {
database[menu_view.highlighted_index()].description = buffer;
database[menu_view.get_index()].description = buffer;
refresh_list();
save_freqman_file(file_list[categories[current_category_id].second], database);
});
@@ -258,7 +241,7 @@ void FrequencyManagerView::on_new_category(NavigationView& nav) {
}
void FrequencyManagerView::on_delete() {
database.erase(database.begin() + menu_view.highlighted_index());
database.erase(database.begin() + menu_view.get_index());
save_freqman_file(file_list[categories[current_category_id].second], database);
refresh_list();
}
@@ -292,15 +275,10 @@ FrequencyManagerView::FrequencyManagerView(
&button_edit_desc,
&button_delete});
// Just to allow exit on left
menu_view.on_left = [&nav, this]() {
nav.pop();
};
change_category(last_category_id);
refresh_list();
on_select_frequency = [this]() {
menu_view.on_select = [this](FreqManUIList&) {
button_edit_freq.focus();
};
@@ -310,14 +288,14 @@ FrequencyManagerView::FrequencyManagerView(
};
button_edit_freq.on_select = [this, &nav](Button&) {
auto new_view = nav.push<FrequencyKeypadView>(database[menu_view.highlighted_index()].frequency_a);
auto new_view = nav.push<FrequencyKeypadView>(database[menu_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&) {
desc_buffer = database[menu_view.highlighted_index()].description;
desc_buffer = database[menu_view.get_index()].description;
on_edit_desc(nav);
};
+4 -3
View File
@@ -28,6 +28,7 @@
#include "ui_receiver.hpp"
#include "ui_textentry.hpp"
#include "freqman.hpp"
#include "ui_freqlist.hpp"
namespace ui {
@@ -65,9 +66,9 @@ class FreqManBaseView : public View {
14,
{}};
MenuView menu_view{
{0, 3 * 8, 240, 23 * 8},
true};
FreqManUIList menu_view{
{0, 3 * 8, 240, 23 * 8}};
Text text_empty{
{7 * 8, 12 * 8, 16 * 8, 16},
"Empty category !",
-1
View File
@@ -286,7 +286,6 @@ void JammerView::stop_tx() {
button_transmit.set_style(&style_val);
button_transmit.set_text("START");
transmitter_model.disable();
radio::disable();
baseband::set_jammer(false, JammerType::TYPE_FSK, 0);
jamming = false;
cooling = false;
+1
View File
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2018 Furrtek
* Copyright (C) 2023 gullradriel, Nilorea Studio Inc.
*
* This file is part of PortaPack.
*
+1
View File
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2018 Furrtek
* Copyright (C) 2023 gullradriel, Nilorea Studio Inc.
*
* This file is part of PortaPack.
*
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2020 euquiq
* Copyright (C) 2023 gullradriel, Nilorea Studio Inc.
*
* This file is part of PortaPack.
*
@@ -69,7 +70,7 @@ void GlassView::get_max_power(const ChannelSpectrum& spectrum, uint8_t bin, uint
void GlassView::on_marker_change() {
if (mode == LOOKING_GLASS_SINGLEPASS) {
marker = f_min + (marker_pixel_index * looking_glass_range) / SCREEN_W;
marker = f_min + (8 * each_bin_size) + (marker_pixel_index * (looking_glass_range - 16 * each_bin_size)) / SCREEN_W;
} else // if( mode == LOOKING_GLASS_SLOWSCAN || mode == LOOKING_GLASS_FASTSCAN )
{
marker = f_min + (offset * each_bin_size) + (marker_pixel_index * looking_glass_range) / SCREEN_W;
@@ -96,8 +97,10 @@ void GlassView::adjust_range(int64_t* f_min, int64_t* f_max, int64_t width) {
}
void GlassView::retune() {
// Start a new sweep
radio::set_tuning_frequency(f_center); // tune rx for this new slice directly, faster than using persistent memory saving
// Start a new sweep.
// Tune rx for this new slice directly because the model
// saves to persistent memory which is slower.
radio::set_tuning_frequency(f_center);
chThdSleepMilliseconds(5);
baseband::spectrum_streaming_start(); // Do the RX
}
@@ -143,9 +146,13 @@ void GlassView::add_spectrum_pixel(uint8_t power) {
// save max powerwull freq
if (spectrum_data[xpos] > max_freq_power) {
max_freq_power = spectrum_data[xpos];
max_freq_hold = f_center + ((looking_glass_range)*xpos) / SCREEN_W;
if (mode == LOOKING_GLASS_SINGLEPASS) {
max_freq_hold = f_min + (xpos * looking_glass_range) / SCREEN_W;
} else // if( mode == LOOKING_GLASS_SLOWSCAN || mode == LOOKING_GLASS_FASTSCAN )
{
max_freq_hold = f_min + (offset * each_bin_size) + (xpos * looking_glass_range) / SCREEN_W;
}
}
int16_t point = y_max_range.clip(((spectrum_data[xpos] - raw_min) * (320 - (108 + 16))) / raw_delta);
uint8_t color_gradient = (point * 255) / 212;
// clear if not in peak view
@@ -227,7 +234,6 @@ void GlassView::on_range_changed() {
} else {
mode = scan_type.selected_index_value();
}
if (mode == LOOKING_GLASS_SINGLEPASS) {
// if the view is done in one pass, show it like in analog_audio_app
offset = 0;
@@ -244,17 +250,24 @@ void GlassView::on_range_changed() {
bin_length = 80;
ignore_dc = 0;
}
each_bin_size = looking_glass_bandwidth / SPEC_NB_BINS;
if (mode != LOOKING_GLASS_SINGLEPASS) {
looking_glass_step = (bin_length + (ignore_dc * 12)) * each_bin_size;
} else {
looking_glass_step = SPEC_NB_BINS * each_bin_size;
}
adjust_range(&f_min, &f_max, SCREEN_W);
looking_glass_range = f_max - f_min;
marker_pixel_step = looking_glass_range / SCREEN_W; // Each pixel value in Hz
if (mode == LOOKING_GLASS_SINGLEPASS) {
looking_glass_bandwidth = looking_glass_range;
looking_glass_sampling_rate = looking_glass_bandwidth / 2;
each_bin_size = looking_glass_bandwidth / bin_length;
looking_glass_step = bin_length * each_bin_size;
} else { // if ( mode == LOOKING_GLASS_SLOWSCAN || mode == LOOKING_GLASS_FASTSCAN )
looking_glass_bandwidth = LOOKING_GLASS_SLICE_WIDTH_MAX;
looking_glass_sampling_rate = LOOKING_GLASS_SLICE_WIDTH_MAX;
each_bin_size = LOOKING_GLASS_SLICE_WIDTH_MAX / SPEC_NB_BINS;
looking_glass_step = (bin_length + (ignore_dc * 12)) * each_bin_size;
}
search_span = looking_glass_range / MHZ_DIV;
button_range.set_text(" "); // clear up to 6 chars
marker_pixel_step = looking_glass_range / SCREEN_W; // Each pixel value in Hz
//
button_range.set_text(" "); // clear up to 6 chars
if (locked_range) {
button_range.set_text(">" + to_string_dec_uint(search_span) + "<");
} else {
@@ -264,18 +277,6 @@ void GlassView::on_range_changed() {
pixel_index = 0; // reset pixel counter
max_power = 0; // reset save max power level
bins_Hz_size = 0; // reset amount of Hz filled up by pixels
if (mode == LOOKING_GLASS_SINGLEPASS) {
looking_glass_bandwidth = looking_glass_range;
looking_glass_sampling_rate = looking_glass_bandwidth / 2;
each_bin_size = looking_glass_bandwidth / SCREEN_W;
} else // if ( mode == LOOKING_GLASS_SLOWSCAN || mode == LOOKING_GLASS_FASTSCAN )
{
looking_glass_sampling_rate = LOOKING_GLASS_SLICE_WIDTH_MAX;
looking_glass_bandwidth = LOOKING_GLASS_SLICE_WIDTH_MAX;
each_bin_size = looking_glass_bandwidth / SPEC_NB_BINS;
}
on_marker_change();
// set the sample rate and bandwidth
+34 -32
View File
@@ -123,14 +123,15 @@ class MicTXView : public View {
{{4 * 8, 10 * 8}, "LVL:", Color::light_grey()}, // we delete { {11 * 8, 5 * 8 }, "Amp:", Color::light_grey() },
{{12 * 8, 10 * 8}, "ATT:", Color::light_grey()},
{{20 * 8, 10 * 8}, "DEC:", Color::light_grey()},
{{4 * 8, (13 * 8) - 2}, "TONE KEY:", Color::light_grey()},
{{7 * 8, 23 * 8}, "VOL:", Color::light_grey()},
{{14 * 8, 23 * 8}, "RXBW:", Color::light_grey()}, // we remove the label "FM" because we will display all MOD types RX_BW.
{{20 * 8, 25 * 8}, "SQ:", Color::light_grey()},
{{5 * 8, 25 * 8}, "F_RX:", Color::light_grey()},
{{5 * 8, 27 * 8}, "LNA:", Color::light_grey()},
{{12 * 8, 27 * 8}, "VGA:", Color::light_grey()},
{{19 * 8, 27 * 8}, "AMP:", Color::light_grey()}};
{{3 * 8, (13 * 8) - 5}, "TONE KEY:", Color::light_grey()},
{{3 * 8, (18 * 8) - 1}, "======== Receiver ========", Color::green()},
{{5 * 8, (23 * 8) + 2}, "VOL:", Color::light_grey()},
{{14 * 8, (23 * 8) + 2}, "RXBW:", Color::light_grey()}, // we remove the label "FM" because we will display all MOD types RX_BW.
{{20 * 8, (25 * 8) + 2}, "SQ:", Color::light_grey()},
{{5 * 8, (25 * 8) + 2}, "F_RX:", Color::light_grey()},
{{5 * 8, (27 * 8) + 2}, "LNA:", Color::light_grey()},
{{12 * 8, (27 * 8) + 2}, "VGA:", Color::light_grey()},
{{19 * 8, (27 * 8) + 2}, "AMP:", Color::light_grey()}};
Labels labels_AK4951{
{{3 * 8, 1 * 8}, "MIC-GAIN:", Color::light_grey()},
{{17 * 8, 1 * 8}, "ALC", Color::light_grey()},
@@ -141,14 +142,15 @@ class MicTXView : public View {
{{4 * 8, 10 * 8}, "LVL:", Color::light_grey()}, // we delete { {11 * 8, 5 * 8 }, "Amp:", Color::light_grey() },
{{12 * 8, 10 * 8}, "ATT:", Color::light_grey()},
{{20 * 8, 10 * 8}, "DEC:", Color::light_grey()},
{{4 * 8, (13 * 8) - 2}, "TONE KEY:", Color::light_grey()},
{{(6 * 8) + 4, 23 * 8}, "VOL:", Color::light_grey()},
{{14 * 8, 23 * 8}, "RXBW:", Color::light_grey()}, // we remove the label "FM" because we will display all MOD types RX_BW.
{{20 * 8, 25 * 8}, "SQ:", Color::light_grey()},
{{5 * 8, 25 * 8}, "F_RX:", Color::light_grey()},
{{5 * 8, 27 * 8}, "LNA:", Color::light_grey()},
{{12 * 8, 27 * 8}, "VGA:", Color::light_grey()},
{{19 * 8, 27 * 8}, "AMP:", Color::light_grey()}};
{{3 * 8, (13 * 8) - 5}, "TONE KEY:", Color::light_grey()},
{{3 * 8, (18 * 8) - 1}, "======== Receiver ========", Color::green()},
{{(5 * 8), (23 * 8) + 2}, "VOL:", Color::light_grey()},
{{14 * 8, (23 * 8) + 2}, "RXBW:", Color::light_grey()}, // we remove the label "FM" because we will display all MOD types RX_BW.
{{20 * 8, (25 * 8) + 2}, "SQ:", Color::light_grey()},
{{5 * 8, (25 * 8) + 2}, "F_RX:", Color::light_grey()},
{{5 * 8, (27 * 8) + 2}, "LNA:", Color::light_grey()},
{{12 * 8, (27 * 8) + 2}, "VGA:", Color::light_grey()},
{{19 * 8, (27 * 8) + 2}, "AMP:", Color::light_grey()}};
VuMeter vumeter{
{0 * 8, 1 * 8, 2 * 8, 33 * 8},
@@ -210,7 +212,7 @@ class MicTXView : public View {
OptionsField options_mode{
{24 * 8, 5 * 8},
4,
6,
{
{"NFM/FM", 0},
{" WFM ", 1},
@@ -230,7 +232,7 @@ class MicTXView : public View {
OptionsField field_va{
{17 * 8, 8 * 8},
3,
4,
{{" OFF", 0},
{" PTT", 1},
{"AUTO", 2}}};
@@ -255,30 +257,30 @@ class MicTXView : public View {
' '};
OptionsField options_tone_key{
{10 * 8, (15 * 8) - 2},
{12 * 8, (13 * 8) - 5},
23,
{}};
Checkbox check_rogerbeep{
{3 * 8, (16 * 8) + 7},
{3 * 8, (14 * 8) + 4},
10,
"Roger beep",
false};
Checkbox check_rxactive{
{3 * 8, (21 * 8) - 4},
18, // it was 8, but if it is string size should be 18
"RX audio listening",
{3 * 8, (21 * 8) - 7},
8, // it was 18, but if it is string size should be 8
"RX audio",
false};
Checkbox check_common_freq_tx_rx{
{18 * 8, (16 * 8) + 7},
{18 * 8, (21 * 8) - 7},
8,
"F = F_RX",
"F TX=RX",
false};
NumberField field_volume{
{11 * 8, 23 * 8},
{9 * 8, (23 * 8) + 2},
2,
{0, 99},
1,
@@ -286,7 +288,7 @@ class MicTXView : public View {
};
OptionsField field_rxbw{
{19 * 8, 23 * 8},
{19 * 8, (23 * 8) + 2},
3,
{
{" NFM1:8k5 ", 0},
@@ -295,7 +297,7 @@ class MicTXView : public View {
}};
NumberField field_squelch{
{23 * 8, 25 * 8},
{23 * 8, (25 * 8) + 2},
2,
{0, 99},
1,
@@ -303,11 +305,11 @@ class MicTXView : public View {
};
FrequencyField field_rxfrequency{
{10 * 8, 25 * 8},
{10 * 8, (25 * 8) + 2},
};
NumberField field_rxlna{
{9 * 8, 27 * 8},
{9 * 8, (27 * 8) + 2},
2,
{0, 40},
8,
@@ -315,7 +317,7 @@ class MicTXView : public View {
};
NumberField field_rxvga{
{16 * 8, 27 * 8},
{16 * 8, (27 * 8) + 2},
2,
{0, 62},
2,
@@ -323,7 +325,7 @@ class MicTXView : public View {
};
NumberField field_rxamp{
{24 * 8, 27 * 8},
{24 * 8, (27 * 8) + 2},
1,
{0, 1},
1,
+66 -34
View File
@@ -31,6 +31,7 @@
#include "portapack.hpp"
#include "portapack_persistent_memory.hpp"
#include <unistd.h>
#include <fstream>
using namespace portapack;
@@ -53,11 +54,13 @@ void PlaylistView::load_file(std::filesystem::path playlist_path) {
if ((int)one_char[0] >= ' ') {
line += one_char[0];
} else if (one_char[0] == '\n') {
total_tracks++;
txtline_process(line);
line.clear();
}
}
if (line.length() > 0) {
total_tracks++;
txtline_process(line);
}
}
@@ -91,8 +94,10 @@ void PlaylistView::on_file_changed(std::filesystem::path new_file_path, rf::Freq
File data_file;
// Get file size
auto data_open_error = data_file.open("/" + new_file_path.string());
if (data_open_error.is_valid()) {
file_error();
if (!data_open_error.is_valid()) {
track_number++;
} else if (data_open_error.is_valid()) {
file_error("C16 file\n" + new_file_path.string() + "\nread error.");
return;
}
@@ -109,6 +114,7 @@ void PlaylistView::on_file_changed(std::filesystem::path new_file_path, rf::Freq
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(std::to_string(track_number) + "/" + std::to_string(total_tracks));
button_play.focus();
}
@@ -121,8 +127,8 @@ void PlaylistView::focus() {
button_open.focus();
}
void PlaylistView::file_error() {
nav_.display_modal("Error", "File " + file_path.string() + " read error. " + file_path.string());
void PlaylistView::file_error(std::string error_message) {
nav_.display_modal("Error", "Error for \n" + file_path.string() + "\n" + error_message);
}
bool PlaylistView::is_active() const {
@@ -136,8 +142,19 @@ bool PlaylistView::loop() const {
void PlaylistView::toggle() {
if (is_active()) {
stop(false);
} else {
start();
total_tracks = 0;
track_number = 0;
playlist_db.clear();
playlist_masterdb.clear();
} else { // Thanks kallanreed for providing this logic!
total_tracks = 0;
track_number = 0;
playlist_db.clear();
playlist_masterdb.clear();
load_file(now_play_list_file);
if (!playlist_db.empty()) {
start();
}
}
}
@@ -158,7 +175,7 @@ void PlaylistView::start() {
auto p = std::make_unique<FileReader>();
auto open_error = p->open(file_path);
if (open_error.is_valid()) {
file_error();
file_error("Illegal grammar");
return; // Fixes TX bug if there's a file error
} else {
reader = std::move(p);
@@ -178,37 +195,48 @@ void PlaylistView::start() {
});
}
// Enable Bias Tee if selected
radio::set_antenna_bias(portapack::get_antenna_bias());
rf_amp = (transmitter_model.rf_amp()); // recover rf_amp settings applied from ui_transmiter.cpp
radio::enable({receiver_model.tuning_frequency(),
sample_rate * 8,
baseband_bandwidth,
rf::Direction::Transmit,
rf_amp, // previous code line : "receiver_model.rf_amp()," was passing the same rf_amp of all Receiver Apps
static_cast<int8_t>(receiver_model.lna()),
static_cast<int8_t>(receiver_model.vga())});
// }
transmitter_model.set_sampling_rate(sample_rate * 8);
transmitter_model.set_baseband_bandwidth(baseband_bandwidth);
transmitter_model.enable();
}
void PlaylistView::stop(const bool do_loop) {
if (is_active()) {
replay_thread.reset();
}
if (do_loop) {
if (playlist_db.size() > 0) {
start();
// 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 {
playlist_db = playlist_masterdb;
start();
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);
}
} else {
radio::set_antenna_bias(false); // Turn off Bias Tee
radio::disable();
button_play.set_bitmap(&bitmap_play);
}
ready_signal = false;
@@ -219,7 +247,7 @@ void PlaylistView::handle_replay_thread_done(const uint32_t return_code) {
stop(true);
} else if (return_code == ReplayThread::READ_ERROR) {
stop(false);
file_error();
file_error("Replay thread read error");
}
progressbar.set_value(0);
@@ -240,6 +268,8 @@ PlaylistView::PlaylistView(
&tx_view, // this handles now the previous rfgain, rfamp
&check_loop,
&button_play,
// &text_track,
// TODO: add track number
&waterfall,
});
@@ -264,15 +294,16 @@ PlaylistView::PlaylistView(
};
button_open.on_select = [this, &nav](Button&) {
auto open_view = nav.push<FileLoadView>(".TXT");
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;
load_file(new_file_path);
};
};
}
PlaylistView::~PlaylistView() {
radio::disable();
transmitter_model.disable();
baseband::shutdown();
}
@@ -287,7 +318,8 @@ void PlaylistView::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};
const ui::Rect waterfall_rect{0, header_height, new_parent_rect.width(),
new_parent_rect.height() - header_height};
waterfall.set_parent_rect(waterfall_rect);
}
+12 -3
View File
@@ -39,10 +39,13 @@ namespace ui {
class PlaylistView : public View {
public:
PlaylistView(NavigationView& nav);
~PlaylistView();
void on_hide() override;
void set_parent_rect(const Rect new_parent_rect) override;
void focus() override;
std::string title() const override { return "Playlist"; };
@@ -66,15 +69,14 @@ class PlaylistView : public View {
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 txtline_process(std::string&);
void on_file_changed(std::filesystem::path new_file_path, rf::Frequency replay_frequency, uint32_t replay_sample_rate);
void on_target_frequency_changed(rf::Frequency f);
void on_tx_progress(const uint32_t progress);
void set_target_frequency(const rf::Frequency new_value);
rf::Frequency target_frequency() const;
void toggle();
void start();
void stop(const bool do_loop);
@@ -82,11 +84,14 @@ class PlaylistView : public View {
bool loop() const;
void set_ready();
void handle_replay_thread_done(const uint32_t return_code);
void file_error();
void file_error(std::string error_message);
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};
std::filesystem::path now_play_list_file{};
Button button_open{
{0 * 8, 0 * 16, 10 * 8, 2 * 16},
@@ -125,6 +130,10 @@ class PlaylistView : public View {
&bitmap_play,
Color::green(),
Color::black()};
// TODO: add track number
// Text text_track{
// {18 * 8, 1 * 16, 12 * 8, 16},
// "0/0"};
spectrum::WaterfallWidget waterfall{};
+61 -18
View File
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2018 Furrtek
* Copyright (C) 2023 gullradriel, Nilorea Studio Inc.
*
* This file is part of PortaPack.
*
@@ -29,6 +30,28 @@ using portapack::memory::map::backup_ram;
namespace ui {
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::reset_indexes() {
last_entry.modulation = -1;
last_entry.bandwidth = -1;
last_entry.step = -1;
description = "...no description...";
current_index = 0;
}
void ReconView::colorize_waits() {
// colorize wait on match
if (wait == 0) {
@@ -366,7 +389,7 @@ ReconView::ReconView(NavigationView& nav)
&field_squelch,
&field_wait,
&field_lock_wait,
&button_recon_setup,
&button_config,
&button_scanner_mode,
&button_looking_glass,
&file_name,
@@ -428,6 +451,7 @@ ReconView::ReconView(NavigationView& nav)
}
button_manual_start.on_select = [this, &nav](ButtonWithEncoder& button) {
clear_freqlist_for_ui_action();
auto new_view = nav_.push<FrequencyKeypadView>(frequency_range.min);
new_view->on_changed = [this, &button](rf::Frequency f) {
frequency_range.min = f;
@@ -436,6 +460,7 @@ ReconView::ReconView(NavigationView& nav)
};
button_manual_end.on_select = [this, &nav](ButtonWithEncoder& button) {
clear_freqlist_for_ui_action();
auto new_view = nav.push<FrequencyKeypadView>(frequency_range.max);
new_view->on_changed = [this, &button](rf::Frequency f) {
frequency_range.max = f;
@@ -549,7 +574,9 @@ ReconView::ReconView(NavigationView& nav)
button_remove.on_select = [this](ButtonWithEncoder&) {
if (frequency_list.size() > 0) {
if (scanner_mode) {
if (!manual_mode) {
// scanner or recon (!scanner) mode
// in both we delete index from live view, but only remove in output file in scanner_mode
if (current_index >= (int32_t)frequency_list.size()) {
current_index = frequency_list.size() - 1;
}
@@ -575,11 +602,11 @@ ReconView::ReconView(NavigationView& nav)
desc_cycle.set("...no description...");
}
text_cycle.set_text(to_string_dec_uint(current_index + 1, 3));
}
// also remove from output file if in scanner mode
if (scanner_mode) {
File freqman_file;
delete_file(freq_file_path);
auto result = freqman_file.create(freq_file_path);
if (!result.is_valid()) {
for (size_t n = 0; n < frequency_list.size(); n++) {
@@ -589,7 +616,7 @@ ReconView::ReconView(NavigationView& nav)
}
}
}
} else // RECON MODE / MANUAL, only remove matching from output
} else if (manual_mode) // only remove from output
{
File recon_file;
File tmp_recon_file;
@@ -653,6 +680,7 @@ ReconView::ReconView(NavigationView& nav)
};
button_manual_recon.on_select = [this](Button&) {
button_remove.set_text("DELETE");
scanner_mode = false;
manual_mode = true;
recon_pause();
@@ -663,7 +691,10 @@ ReconView::ReconView(NavigationView& nav)
} else {
audio::output::stop();
frequency_list.clear();
// 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);
freqman_entry manual_freq_entry;
def_step = step_mode.selected_index(); // max range val
@@ -758,10 +789,12 @@ ReconView::ReconView(NavigationView& nav)
scanner_mode = false;
button_scanner_mode.set_style(&style_blue);
button_scanner_mode.set_text("RECON");
button_remove.set_text("DELETE");
} else {
scanner_mode = true;
button_scanner_mode.set_style(&style_red);
button_scanner_mode.set_text("SCANNER");
button_scanner_mode.set_text("REMOVE");
}
frequency_file_load(true);
if (autostart) {
@@ -771,10 +804,12 @@ ReconView::ReconView(NavigationView& nav)
}
};
button_recon_setup.on_select = [this, &nav](Button&) {
audio::output::stop();
button_config.on_select = [this, &nav](Button&) {
clear_freqlist_for_ui_action();
auto open_view = nav.push<ReconSetupView>(input_file, output_file, recon_lock_duration, recon_lock_nb_match, recon_match_mode);
open_view->on_changed = [this](std::vector<std::string> result) {
freqlist_cleared_for_ui_action = false;
input_file = result[0];
output_file = result[1];
freq_file_path = "/FREQMAN/" + output_file + ".TXT";
@@ -867,8 +902,10 @@ void ReconView::frequency_file_load(bool stop_all_before) {
audio::output::stop();
def_step = step_mode.selected_index(); // use def_step from manual selector
frequency_list.clear(); // clear the existing frequency list (expected behavior)
std::string file_input = input_file; // default recon 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); // clear the existing frequency list (expected behavior)
std::string file_input = input_file; // default recon mode
if (scanner_mode) {
file_input = output_file;
file_name.set_style(&style_red);
@@ -879,7 +916,6 @@ void ReconView::frequency_file_load(bool stop_all_before) {
button_scanner_mode.set_style(&style_blue);
button_scanner_mode.set_text("RECON");
}
file_name.set_style(&style_white);
desc_cycle.set_style(&style_white);
if (!load_freqman_file_ex(file_input, frequency_list, load_freqs, load_ranges, load_hamradios)) {
file_name.set_style(&style_red);
@@ -933,15 +969,10 @@ void ReconView::frequency_file_load(bool stop_all_before) {
default:
break;
}
last_entry.modulation = -1;
last_entry.bandwidth = -1;
last_entry.step = -1;
reset_indexes();
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);
std::string description = "...no description...";
current_index = 0;
if (frequency_list.size() != 0) {
switch (frequency_list[current_index].type) {
case RANGE:
@@ -975,6 +1006,18 @@ void ReconView::frequency_file_load(bool stop_all_before) {
}
void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
// hack to reload the list if it was cleared by going into CONFIG
if (freqlist_cleared_for_ui_action) {
if (!manual_mode) {
frequency_file_load(false);
}
if (autostart) {
recon_resume();
} else {
recon_pause();
}
freqlist_cleared_for_ui_action = false;
}
db = statistics.max_db;
if (recon) {
if (!timer) {
+6 -1
View File
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2018 Furrtek
* Copyright (C) 2023 gullradriel, Nilorea Studio Inc.
*
* This file is part of PortaPack.
*
@@ -100,6 +101,8 @@ class ReconView : public View {
private:
NavigationView& nav_;
void clear_freqlist_for_ui_action();
void reset_indexes();
void audio_output_start();
bool check_sd_card();
size_t change_mode(freqman_index_t mod_type);
@@ -126,8 +129,10 @@ class ReconView : public View {
freqman_db frequency_list = {};
int32_t current_index{0};
bool continuous_lock{false};
bool freqlist_cleared_for_ui_action{false}; // flag positioned by ui widgets to manage freqlist unload/load
std::string input_file = {"RECON"};
std::string output_file = {"RECON_RESULTS"};
std::string description = {"...no description..."};
bool autosave = {true};
bool autostart = {true};
bool continuous = {true};
@@ -270,7 +275,7 @@ class ReconView : public View {
{12 * 8 + 4, 7 * 16, 14 * 8, 1 * 8},
""};
Button button_recon_setup{
Button button_config{
{SCREEN_W - 7 * 8, 2 * 16, 7 * 8, 28},
"CONFIG"};
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
* Copyright (C) 2023 gullradriel, Nilorea Studio Inc.
*
* This file is part of PortaPack.
*
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
* Copyright (C) 2023 gullradriel, Nilorea Studio Inc.
*
* This file is part of PortaPack.
*
+3 -3
View File
@@ -744,9 +744,9 @@ void ScannerView::scan_resume() {
}
void ScannerView::user_resume() {
browse_timer = browse_wait * STATISTICS_UPDATES_PER_SEC; // Will trigger a scan_resume() on_statistics_update, also advancing to next freq.
button_pause.set_text("<PAUSE>"); // Show button for pause, arrows indicate rotary encoder enabled for freq change
userpause = false; // Resume scanning
browse_timer = browse_wait * STATISTICS_UPDATES_PER_SEC + 1; // Will trigger a scan_resume() on_statistics_update, also advancing to next freq.
button_pause.set_text("<PAUSE>"); // Show button for pause, arrows indicate rotary encoder enabled for freq change
userpause = false; // Resume scanning
}
void ScannerView::on_headphone_volume_changed(int32_t v) {
+42 -3
View File
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
* Copyright (C) 2023 gullradriel, Nilorea Studio Inc.
*
* This file is part of PortaPack.
*
@@ -195,6 +196,15 @@ SetRadioView::SetRadioView(
check_bias.set_value(portapack::get_antenna_bias());
check_bias.on_select = [this](Checkbox&, bool v) {
portapack::set_antenna_bias(v);
// Update the radio.
receiver_model.set_antenna_bias();
transmitter_model.set_antenna_bias();
// The models won't actually disable this if they are not 'enabled_'.
// Be extra sure this is turned off.
if (!v)
radio::set_antenna_bias(false);
StatusRefreshMessage message{};
EventDispatcher::send_message(message);
};
@@ -519,9 +529,9 @@ void SetPersistentMemoryView::focus() {
button_return.focus();
}
//
// Audio settings
//
// ---------------------------------------------------------
// Audio Settings
// ---------------------------------------------------------
SetAudioView::SetAudioView(NavigationView& nav) {
add_children({&labels,
&field_tone_mix,
@@ -544,6 +554,9 @@ void SetAudioView::focus() {
button_save.focus();
}
// ---------------------------------------------------------
// QR Code Settings
// ------------------------------------------------------
SetQRCodeView::SetQRCodeView(NavigationView& nav) {
add_children({&checkbox_bigger_qr,
&button_save,
@@ -565,6 +578,31 @@ void SetQRCodeView::focus() {
button_save.focus();
}
// ---------------------------------------------------------
// Rotary Encoder Dial Settings
// ---------------------------------------------------------
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());
button_save.on_select = [&nav, this](Button&) {
persistent_memory::set_encoder_dial_sensitivity(field_encoder_dial_sensitivity.selected_index_value());
nav.pop();
};
button_cancel.on_select = [&nav, this](Button&) {
nav.pop();
};
}
void SetEncoderDialView::focus() {
button_save.focus();
}
// ---------------------------------------------------------
// Settings main menu
// ---------------------------------------------------------
@@ -583,6 +621,7 @@ SettingsMenuView::SettingsMenuView(NavigationView& nav) {
{"FreqCorrection", ui::Color::dark_cyan(), &bitmap_icon_options_radio, [&nav]() { nav.push<SetFrequencyCorrectionView>(); }},
{"QR Code", ui::Color::dark_cyan(), &bitmap_icon_qr_code, [&nav]() { nav.push<SetQRCodeView>(); }},
{"P.Memory Mgmt", ui::Color::dark_cyan(), &bitmap_icon_memory, [&nav]() { nav.push<SetPersistentMemoryView>(); }},
{"Encoder Dial", ui::Color::dark_cyan(), &bitmap_icon_setup, [&nav]() { nav.push<SetEncoderDialView>(); }},
});
set_max_rows(2); // allow wider buttons
}
+33
View File
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
* Copyright (C) 2023 gullradriel, Nilorea Studio Inc.
*
* This file is part of PortaPack.
*
@@ -431,6 +432,38 @@ class SetQRCodeView : public View {
};
};
using portapack::persistent_memory::encoder_dial_sensitivity;
class SetEncoderDialView : public View {
public:
SetEncoderDialView(NavigationView& nav);
void focus() override;
std::string title() const override { return "Encoder Dial"; };
private:
Labels labels{
{{2 * 8, 3 * 16}, "Dial sensitivity:", Color::light_grey()},
};
OptionsField field_encoder_dial_sensitivity{
{20 * 8, 3 * 16},
6,
{{"LOW", encoder_dial_sensitivity::DIAL_SENSITIVITY_LOW},
{"NORMAL", encoder_dial_sensitivity::DIAL_SENSITIVITY_MEDIUM},
{"HIGH", encoder_dial_sensitivity::DIAL_SENSITIVITY_HIGH}}};
Button button_save{
{2 * 8, 16 * 16, 12 * 8, 32},
"Save"};
Button button_cancel{
{16 * 8, 16 * 16, 12 * 8, 32},
"Cancel",
};
};
class SetPersistentMemoryView : public View {
public:
SetPersistentMemoryView(NavigationView& nav);
+2 -4
View File
@@ -107,7 +107,7 @@ SondeView::SondeView(NavigationView& nav) {
receiver_model.set_tuning_frequency(tuning_frequency());
receiver_model.set_sampling_rate(sampling_rate);
receiver_model.set_baseband_bandwidth(baseband_bandwidth);
receiver_model.enable(); // Before using radio::enable(), but not updating Ant.DC-Bias.
receiver_model.enable();
// QR code with geo URI
button_see_qr.on_select = [this, &nav](Button&) {
@@ -155,7 +155,7 @@ SondeView::~SondeView() {
baseband::set_pitch_rssi(0, false);
receiver_model.disable(); // to switch off all, including DC bias.
receiver_model.disable();
baseband::shutdown();
audio::output::stop();
}
@@ -258,8 +258,6 @@ void SondeView::on_headphone_volume_changed(int32_t v) {
void SondeView::set_target_frequency(const uint32_t new_value) {
target_frequency_ = new_value;
// radio::set_tuning_frequency(tuning_frequency());
// we better remember the tuned frequency, by using this function instead:
receiver_model.set_tuning_frequency(target_frequency_);
}
@@ -69,7 +69,7 @@ SpectrumPainterView::SpectrumPainterView(
};
};
tx_gain = 10;
tx_gain = portapack::transmitter_model.tx_gain();
field_rfgain.set_value(tx_gain); // Initial default value (-12 dB's max ).
field_rfgain.on_change = [this](int32_t v) { // allow initial value change just after opened file.
tx_gain = v;
@@ -93,16 +93,9 @@ SpectrumPainterView::SpectrumPainterView(
if (tx_mode == 0 && image_input_avaliable == false)
return;
// Enable Bias Tee if selected
radio::set_antenna_bias(portapack::get_antenna_bias());
radio::enable({portapack::receiver_model.tuning_frequency(),
3072000U,
1750000,
rf::Direction::Transmit,
rf_amp,
static_cast<int8_t>(portapack::receiver_model.lna()),
static_cast<int8_t>(portapack::receiver_model.vga())});
portapack::transmitter_model.set_sampling_rate(3072000U);
portapack::transmitter_model.set_baseband_bandwidth(1750000);
portapack::transmitter_model.enable();
if (portapack::persistent_memory::stealth_mode()) {
DisplaySleepMessage message;
+6 -13
View File
@@ -81,19 +81,14 @@ TestView::TestView(NavigationView& nav) {
if (logger)
logger->append("saucepan.txt");
radio::enable({
tuning_frequency(),
sampling_rate,
baseband_bandwidth,
rf::Direction::Receive,
receiver_model.rf_amp(),
static_cast<int8_t>(receiver_model.lna()),
static_cast<int8_t>(receiver_model.vga()),
});
receiver_model.set_tuning_frequency(tuning_frequency());
receiver_model.set_sampling_rate(sampling_rate);
receiver_model.set_baseband_bandwidth(baseband_bandwidth);
receiver_model.enable();
}
TestView::~TestView() {
radio::disable();
receiver_model.disable();
baseband::shutdown();
}
@@ -127,8 +122,6 @@ void TestView::on_packet(const testapp::Packet& packet) {
if (logger && logging)
logger->log_raw_data(packet, raw_alt - cal_value);
// radio::disable();
/*text_serial.set(packet.serial_number());
text_voltage.set(unit_auto_scale(packet.battery_voltage(), 2, 3) + "V");
@@ -139,7 +132,7 @@ void TestView::on_packet(const testapp::Packet& packet) {
void TestView::set_target_frequency(const uint32_t new_value) {
target_frequency_ = new_value;
radio::set_tuning_frequency(tuning_frequency());
receiver_model.set_tuning_frequency(tuning_frequency());
}
uint32_t TestView::tuning_frequency() const {
+477 -168
View File
@@ -21,58 +21,263 @@
#include "ui_fileman.hpp"
#include "ui_text_editor.hpp"
#include "ui_textentry.hpp"
#include "log_file.hpp"
#include "string_format.hpp"
using namespace portapack;
namespace fs = std::filesystem;
namespace {
template <typename T>
T mid(const T& val1, const T& val2, const T& val3) {
return std::max(val1, std::min(val2, val3));
}
/*void log(const std::string& msg) {
LogFile log{};
log.append("LOGS/Notepad.txt");
log.write_entry(msg);
}*/
} // namespace
namespace ui {
TextEditorView::TextEditorView(NavigationView& nav)
: nav_{nav} {
add_children(
{&button_open,
&text_position});
/* FileWrapper ******************************************************/
FileWrapper::FileWrapper() {
}
Optional<FileWrapper::Error> FileWrapper::open(const fs::path& path) {
file_ = File();
auto error = file_.open(path);
if (!error)
initialize();
return error;
}
std::string FileWrapper::get_text(Offset line, Offset col, Offset length) {
// TODO: better way to return errors.
auto range = line_range(line);
int32_t to_read = length;
if (!range)
return "[UNCACHED LINE]";
// Don't read past end of line.
if (range->start + col + to_read >= range->end)
to_read = range->end - col - range->start;
if (to_read <= 0)
return {};
return read(range->start + col, to_read);
}
Optional<FileWrapper::Range> FileWrapper::line_range(Line line) {
ensure_cached(line);
auto offset = offset_for_line(line);
if (!offset)
return {};
auto start = *offset == 0 ? start_offset_ : (newlines_[*offset - 1] + 1);
auto end = newlines_[*offset] + 1;
return {Range{start, end}};
}
FileWrapper::Offset FileWrapper::line_length(Line line) {
auto range = line_range(line);
if (range)
return range->end - range->start;
return 0;
}
void FileWrapper::initialize() {
start_offset_ = 0;
start_line_ = 0;
line_count_ = 0;
newlines_.clear();
line_ending_ = LineEnding::LF;
Offset offset = 0;
auto result = next_newline(offset);
while (result) {
++line_count_;
if (newlines_.size() < max_newlines)
newlines_.push_back(*result);
offset = *result + 1;
result = next_newline(offset);
}
}
std::string FileWrapper::read(Offset offset, Offset length) {
// TODO: better way to return errors.
if (offset + length > file_.size())
return {"[BAD OFFSET]"};
std::string buffer(length, '\0');
file_.seek(offset);
auto result = file_.read(&buffer[0], length);
if (result.is_ok())
buffer.resize(*result);
else
return result.error().what();
return buffer;
}
Optional<FileWrapper::Offset> FileWrapper::offset_for_line(Line line) const {
if (line >= line_count_)
return {};
Offset actual = line - start_line_;
if (actual < newlines_.size()) // NB: underflow wrap.
return {actual};
return {};
}
void FileWrapper::ensure_cached(Line line) {
if (line >= line_count_)
return;
auto result = offset_for_line(line);
if (result)
return;
if (line < start_line_) {
while (line < start_line_ && start_offset_ >= 2) {
// start_offset_ - 1 should be a newline. Need to
// find the new value for start_offset_. start_line_
// has to be > 0 to get into this block so there should
// always be one newline before start_offset_.
auto offset = previous_newline(start_offset_ - 2);
newlines_.push_front(start_offset_ - 1);
if (!offset) {
// Must be at beginning.
start_line_ = 0;
start_offset_ = 0;
} else {
// Found an previous newline, the new start_line_
// starts at the newline offset + 1.
start_line_--;
start_offset_ = *offset + 1;
}
}
} else {
while (line >= start_line_ + newlines_.size()) {
auto offset = next_newline(newlines_.back() + 1);
if (offset) {
start_line_++;
start_offset_ = newlines_.front() + 1;
newlines_.push_back(*offset);
} /* else at the EOF. */
}
}
}
Optional<FileWrapper::Offset> FileWrapper::previous_newline(Offset start) {
char buffer[buffer_size];
Offset offset = start;
auto to_read = buffer_size;
do {
if (offset < to_read) {
// NB: Char at 'offset' was read in the previous iteration.
to_read = offset;
offset = 0;
} else
offset -= to_read;
file_.seek(offset);
auto result = file_.read(buffer, to_read);
if (result.is_error())
break;
// Find newlines in the buffer backwards.
for (int32_t i = *result - 1; i >= 0; --i) {
switch (buffer[i]) {
case '\n':
return {offset + i};
}
}
if (offset == 0)
break;
} while (true);
return {}; // Didn't find one.
}
Optional<FileWrapper::Offset> FileWrapper::next_newline(Offset start) {
char buffer[buffer_size];
Offset offset = start;
// EOF, nothing to do.
if (start >= size())
return {};
file_.seek(offset);
while (true) {
auto result = file_.read(buffer, buffer_size);
if (result.is_error())
return {};
// Find newlines in the buffer.
for (Offset i = 0; i < *result; ++i) {
switch (buffer[i]) {
case '\n':
return {offset + i};
}
}
offset += *result;
if (*result < buffer_size)
break;
}
// Fake a newline at the end for consistency.
return {offset};
}
/* 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)} {
set_focusable(true);
// log_.append("LOGS/NOTEPAD.TXT");
button_open.on_select = [this](Button&) {
auto open_view = nav_.push<FileLoadView>(".TXT");
open_view->on_changed = [this](std::filesystem::path path) {
open_file(path);
};
};
}
void TextEditorView::on_focus() {
refresh_ui();
}
void TextEditorView::paint(Painter& painter) {
void TextViewer::paint(Painter& painter) {
auto first_line = paint_state_.first_line;
auto first_col = paint_state_.first_col;
if (!paint_state_.has_file)
if (!has_file())
return;
// Move the viewport vertically.
if (cursor_.line < first_line)
first_line = cursor_.line;
else if (cursor_.line >= first_line + max_line)
first_line = cursor_.line - max_line + 1;
// Move the viewport horizontally.
if (cursor_.col < first_col)
first_col = cursor_.col;
if (cursor_.col >= first_col + max_col)
first_col = cursor_.col - max_col + 1;
// Viewport updated? Redraw text.
if (first_line != paint_state_.first_line ||
first_col != paint_state_.first_col) {
paint_state_.first_line = first_line;
@@ -88,7 +293,7 @@ void TextEditorView::paint(Painter& painter) {
paint_cursor(painter);
}
bool TextEditorView::on_key(const KeyEvent key) {
bool TextViewer::on_key(const KeyEvent key) {
int16_t delta_col = 0;
int16_t delta_line = 0;
@@ -100,19 +305,22 @@ bool TextEditorView::on_key(const KeyEvent key) {
delta_line = -1;
else if (key == KeyEvent::Down)
delta_line = 1;
/* else if (key == KeyEvent::Select)
; // TODO: Edit/Menu */
else if (key == KeyEvent::Select && on_select) {
on_select();
return true;
}
// Always allow cursor direction to be updated.
cursor_.dir = delta_col != 0 ? ScrollDirection::Horizontal : ScrollDirection::Vertical;
auto updated = apply_scrolling_constraints(delta_line, delta_col);
if (updated)
refresh_ui();
redraw();
return updated;
}
bool TextEditorView::on_encoder(EncoderEvent delta) {
bool TextViewer::on_encoder(EncoderEvent delta) {
bool updated = false;
if (cursor_.dir == ScrollDirection::Horizontal)
@@ -121,178 +329,101 @@ bool TextEditorView::on_encoder(EncoderEvent delta) {
updated = apply_scrolling_constraints(delta, 0);
if (updated)
refresh_ui();
redraw();
return updated;
}
bool TextEditorView::apply_scrolling_constraints(int16_t delta_line, int16_t delta_col) {
bool TextViewer::apply_scrolling_constraints(int16_t delta_line, int16_t delta_col) {
if (!has_file())
return false;
int32_t new_line = cursor_.line + delta_line;
int32_t new_col = cursor_.col + delta_col;
auto new_line_length = info_.line_length(new_line);
int32_t new_line_length = file_->line_length(new_line);
if (new_col < 0)
--new_line;
else if (new_col > new_line_length && delta_line == 0) {
// Only want to wrap if moving horizontally.
else if (new_col >= new_line_length && delta_line == 0) {
// Only wrap if moving horizontally.
new_col = 0;
++new_line;
}
if (new_line < 0 || (uint32_t)new_line >= info_.line_count())
// Snap to first/last to make navigating easier.
if (new_line < 0 && new_col > 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) {
new_line = last_line;
new_col = last_col;
}
}
if (new_line < 0 || (uint32_t)new_line >= file_->line_count())
return false;
new_line_length = info_.line_length(new_line);
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;
else if (new_col > new_line_length || new_col < 0)
new_col = new_line_length;
else if (new_col >= new_line_length || new_col < 0)
new_col = new_line_length - 1;
cursor_.line = new_line;
cursor_.col = new_col;
if (on_cursor_moved)
on_cursor_moved();
return true;
}
void TextEditorView::refresh_ui() {
if (paint_state_.has_file) {
text_position.set(
to_string_dec_uint(cursor_.col + 1) + ":" +
to_string_dec_uint(cursor_.line + 1) + "/" +
to_string_dec_uint(info_.line_count()) +
(info_.truncated ? "*" : "") +
" Size: " +
to_string_file_size(info_.size));
focus();
} else {
button_open.focus();
}
void TextViewer::redraw(bool redraw_text) {
paint_state_.redraw_text = redraw_text;
set_dirty();
}
void TextEditorView::refresh_file_info() {
constexpr size_t buffer_size = 128;
char buffer[buffer_size];
uint32_t base_offset = 0;
file_.seek(0);
info_.newlines.clear();
info_.line_ending = LineEnding::LF;
info_.size = file_.size();
info_.truncated = false;
while (true) {
auto result = file_.read(buffer, buffer_size);
if (result.is_error())
break; // TODO: report error?
// TODO: CRLF state machine for cross block.
for (uint32_t i = 0; i < result.value(); ++i) {
switch (buffer[i]) {
case '\n':
info_.newlines.push_back(base_offset + i);
}
}
base_offset += result.value();
// Fake a newline at the end for consistency.
// Could check if there already is a trailing newline, but it doesn't hurt.
if (result.value() < buffer_size) {
info_.newlines.push_back(base_offset);
break;
}
// HACK HACK: only show first 1000 lines for now.
if (info_.newlines.size() >= 1000) {
info_.truncated = true;
break;
}
}
refresh_ui();
}
void TextEditorView::open_file(const fs::path& path) {
// TODO: need a temp backing file for edits.
auto result = file_.open(path);
paint_state_.has_file = !result.is_valid(); /* Has an error. */
if (paint_state_.has_file) {
refresh_file_info();
paint_state_.first_line = 0;
paint_state_.first_col = 0;
cursor_.line = 0;
cursor_.col = 0;
} else {
nav_.display_modal("Read Error", "Cannot open file:\n" + result.value().what());
paint_state_.has_file = false;
}
paint_state_.redraw_text = true;
refresh_ui();
}
std::string TextEditorView::read(uint32_t offset, uint32_t length) {
if (offset >= info_.size)
return {"[BAD OFFSET]"};
std::string buffer(length + 1, '\0');
file_.seek(offset);
auto result = file_.read(&buffer[0], length);
if (result.is_ok())
/* resize? */;
else
return result.error().what();
return buffer;
}
void TextEditorView::paint_text(Painter& painter, uint32_t line, uint16_t col) {
// TODO: A line cache would use more memory but save a lot of IO.
void TextViewer::paint_text(Painter& painter, uint32_t line, uint16_t col) {
// CONSIDER: A line cache would use more memory but save a lot of IO.
// Only the new lines/characters would need to be refetched.
auto r = screen_rect();
auto& lines = info_.newlines;
auto line_start = info_.line_start(line);
// Draw the lines from the file
for (uint32_t i = 0; i < max_line && i < lines.size(); ++i) {
auto line_end = lines[line + i];
int32_t read_length = max_col;
for (auto i = 0u; i < max_line; ++i) {
if (line + i >= file_->line_count())
break;
// Don't read past end of the line.
if (line_start + col + (uint32_t)read_length > line_end)
read_length = line_end - col - line_start;
auto str = file_->get_text(line + i, col, max_col);
if (read_length > 0)
// Draw text.
if (str.length() > 0)
painter.draw_string(
{0, r.location().y() + (int)i * char_height},
style_default, read(line_start + col, read_length));
{0, r.top() + (int)i * char_height},
style_text, str);
// Erase empty line sectons.
if (read_length >= 0) {
int32_t clear_width = max_col - read_length;
if (clear_width > 0)
painter.fill_rectangle(
{(max_col - clear_width) * char_width,
r.location().y() + (int)i * char_height,
clear_width * char_width, char_height},
style_default.background);
}
line_start = lines[line + i] + 1 /* newline */;
// Clear empty line sections.
int32_t clear_width = max_col - str.length();
if (clear_width > 0)
painter.fill_rectangle(
{(max_col - clear_width) * char_width,
r.top() + (int)i * char_height,
clear_width * char_width, char_height},
style_text.background);
}
}
void TextEditorView::paint_cursor(Painter& painter) {
void TextViewer::paint_cursor(Painter& painter) {
if (!has_focus())
return;
auto draw_cursor = [this, &painter](uint32_t line, uint16_t col, Color c) {
auto r = screen_rect();
line = line - paint_state_.first_line;
@@ -300,23 +431,201 @@ void TextEditorView::paint_cursor(Painter& painter) {
painter.draw_rectangle(
{(int)col * char_width - 1,
r.location().y() + (int)line * char_height,
r.top() + (int)line * char_height,
char_width + 1, char_height},
c);
};
// TOOD: bug where cursor doesn't clear at EOF.
// TODO: XOR cursor?
// Clear old cursor.
draw_cursor(paint_state_.line, paint_state_.col, style_default.background);
draw_cursor(cursor_.line, cursor_.col, style_default.foreground);
// Clear old cursor. CONSIDER: XOR cursor?
draw_cursor(paint_state_.line, paint_state_.col, style_text.background);
draw_cursor(cursor_.line, cursor_.col, style_text.foreground);
paint_state_.line = cursor_.line;
paint_state_.col = cursor_.col;
}
uint16_t TextEditorView::line_length() const {
return info_.line_length(cursor_.line);
void TextViewer::reset_file(FileWrapper* file) {
file_ = file;
paint_state_.first_line = 0;
paint_state_.first_col = 0;
cursor_.line = 0;
cursor_.col = 0;
redraw(true);
}
uint16_t TextViewer::line_length() {
return file_->line_length(cursor_.line);
}
/* TextEditorMenu ***************************************************/
TextEditorMenu::TextEditorMenu()
: View{{7 * 4, 9 * 4, 25 * 8, 25 * 8}} {
add_children(
{
&rect_frame,
&button_cut,
&button_paste,
&button_copy,
&button_delline,
&button_edit,
&button_addline,
&button_open,
&button_save,
&button_exit,
});
}
void TextEditorMenu::on_show() {
hide_children(false);
button_edit.focus();
}
void TextEditorMenu::on_hide() {
hide_children(true);
}
void TextEditorMenu::hide_children(bool hidden) {
for (auto child : children()) {
child->hidden(hidden);
}
}
/* TextEditorView ***************************************************/
TextEditorView::TextEditorView(NavigationView& nav)
: nav_{nav} {
add_children(
{
&viewer,
&menu,
&button_menu,
&text_position,
&text_size,
});
viewer.on_select = [this]() {
// Treat as if menu button was pressed.
if (button_menu.on_select)
button_menu.on_select();
};
viewer.on_cursor_moved = [this]() {
update_position();
};
menu.hidden(true);
menu.on_cut() = [this]() {
show_nyi();
};
menu.on_paste() = [this]() {
show_nyi();
};
menu.on_copy() = [this]() {
show_nyi();
};
menu.on_delete_line() = [this]() {
show_nyi();
};
menu.on_edit_line() = [this]() {
show_nyi();
};
menu.on_add_line() = [this]() {
show_nyi();
};
menu.on_open() = [this]() {
// TODO: confirm.
show_file_picker();
};
menu.on_save() = [this]() {
show_nyi();
};
menu.on_exit() = [this]() {
// TODO: confirm.
nav_.pop();
};
button_menu.on_select = [this]() {
if (file_) {
// Toggle menu.
hide_menu(!menu.hidden());
} else {
show_file_picker();
}
};
}
TextEditorView::TextEditorView(NavigationView& nav, const fs::path& path)
: TextEditorView(nav) {
open_file(path);
}
void TextEditorView::on_show() {
if (file_)
viewer.focus();
else
button_menu.focus();
}
void TextEditorView::open_file(const fs::path& path) {
auto file = std::make_unique<FileWrapper>();
auto error = file->open(path);
if (error) {
nav_.display_modal("Read Error", "Cannot open file:\n" + error->what());
file_.reset();
viewer.clear_file();
} else {
file_ = std::move(file);
viewer.set_file(*file_);
}
refresh_ui();
}
void TextEditorView::refresh_ui() {
if (file_) {
update_position();
text_size.set(
"Lines:" + to_string_dec_uint(file_->line_count()) +
" (" + to_string_file_size(file_->size()) + ")");
} else {
text_position.set("");
text_size.set("");
}
}
void TextEditorView::update_position() {
if (viewer.has_file()) {
text_position.set(
"Ln " + to_string_dec_uint(viewer.line() + 1) +
", Col " + to_string_dec_uint(viewer.col() + 1));
}
}
void TextEditorView::hide_menu(bool hidden) {
menu.hidden(hidden);
// Only let the viewer be focused when the menu is
// not shown, otherwise menu focus gets confusing.
viewer.set_focusable(hidden);
if (hidden)
viewer.focus();
viewer.redraw(true);
set_dirty();
}
void TextEditorView::show_file_picker() {
auto open_view = nav_.push<FileLoadView>("");
open_view->on_changed = [this](std::filesystem::path path) {
open_file(path);
hide_menu();
};
}
void TextEditorView::show_nyi() {
nav_.display_modal("Soon...", "Coming soon.");
}
} // namespace ui
+216 -70
View File
@@ -27,16 +27,21 @@
#include "ui_navigation.hpp"
#include "ui_painter.hpp"
#include "ui_widget.hpp"
//#include "ui_textentry.hpp"
#include "circular_buffer.hpp"
#include "file.hpp"
#include "log_file.hpp"
#include "optional.hpp"
#include <memory>
#include <string>
#include <vector>
namespace ui {
/* TODO:
* - Copy on write into temp file so startup is fast.
*/
enum class LineEnding : uint8_t {
LF,
CRLF
@@ -47,88 +52,118 @@ enum class ScrollDirection : uint8_t {
Horizontal
};
// TODO: RAM is _very_ limited. Need to
// rework this to not store every line.
// Should be able to get away with only
// abount one screen of lines so long as
// you can't scroll more than one screen
// at a time.
struct FileInfo {
/* Offsets of newlines. */
std::vector<uint32_t> newlines;
LineEnding line_ending;
File::Size size;
bool truncated;
uint32_t line_count() const {
return newlines.size();
}
uint32_t line_start(uint32_t line) const {
return line == 0 ? 0 : (newlines[line - 1] + 1);
}
uint16_t line_length(uint32_t line) const {
if (line >= line_count())
return 0;
auto start = line_start(line);
auto end = newlines[line];
return end - start;
}
};
/*class TextViewer : public Widget {
};*/
class TextEditorView : public View {
/* Wraps a file and provides an API for accessing lines efficiently. */
class FileWrapper {
public:
TextEditorView(NavigationView& nav);
// TextEditorView(NavigationView& nav, const std::filesystem::path& path);
std::string title() const override {
return "Notepad";
using Error = std::filesystem::filesystem_error;
using Offset = uint32_t; // TODO: make enums?
using Line = uint32_t;
using Column = uint32_t;
using Range = struct {
// Offset of the line start.
Offset start;
// Offset of one past the line end.
Offset end;
};
void on_focus() override;
FileWrapper();
/* Prevent copies. */
FileWrapper(const FileWrapper&) = delete;
FileWrapper& operator=(const FileWrapper&) = delete;
Optional<Error> open(const std::filesystem::path& path);
std::string get_text(Line line, Column col, Offset length);
File::Size size() const { return file_.size(); }
uint32_t line_count() const { return line_count_; }
Optional<Range> line_range(Line line);
Offset line_length(Line line);
private:
/* Number of newline offsets to cache. */
static constexpr Offset max_newlines = 64;
static constexpr size_t buffer_size = 512;
void initialize();
std::string read(Offset offset, Offset length = 30);
/* Returns the offset into the newline cache if valid. */
Optional<Offset> offset_for_line(Line line) const;
/* Ensure specified line is in the newline cache. */
void ensure_cached(Line line);
/* Helpers for finding the prev/next newline. */
Optional<Offset> previous_newline(Offset start);
Optional<Offset> next_newline(Offset start);
File file_{};
/* Total number of lines in the file. */
Offset line_count_{0};
/* The offset and line of the newlines cache. */
Offset start_offset_{0};
Offset start_line_{0};
LineEnding line_ending_{LineEnding::LF};
CircularBuffer<Offset, max_newlines + 1> newlines_{};
};
/* Control that renders a text file. */
class TextViewer : public Widget {
public:
TextViewer(Rect parent_rect);
TextViewer(const TextViewer&) = delete;
TextViewer(TextViewer&&) = delete;
TextViewer& operator=(const TextViewer&) = delete;
TextViewer& operator=(TextViewer&&) = delete;
std::function<void()> on_select{};
std::function<void()> on_cursor_moved{};
void paint(Painter& painter) override;
bool on_key(KeyEvent delta) override;
bool on_encoder(EncoderEvent delta) override;
void redraw(bool redraw_text = false);
void set_file(FileWrapper& file) { reset_file(&file); }
void clear_file() { reset_file(); }
bool has_file() const { return file_ != nullptr; }
uint32_t line() const { return cursor_.line; }
uint32_t col() const { return cursor_.col; }
private:
static constexpr uint8_t max_line = 32;
static constexpr uint8_t max_col = 48;
static constexpr int8_t char_width = 5;
static constexpr int8_t char_height = 8;
// TODO: should these be common somewhere?
static constexpr Style style_default{
static constexpr Style style_text{
.font = font::fixed_5x8,
.background = Color::black(),
.foreground = Color::white(),
};
const uint8_t max_line = 32;
const uint8_t max_col = 48;
/* Returns true if the cursor was updated. */
bool apply_scrolling_constraints(
int16_t delta_line,
int16_t delta_col);
void refresh_ui();
void refresh_file_info();
void open_file(const std::filesystem::path& path);
std::string read(uint32_t offset, uint32_t length = 30);
void paint_text(Painter& painter, uint32_t line, uint16_t col);
void paint_cursor(Painter& painter);
void reset_file(FileWrapper* file = nullptr);
// Gets the length of the current line.
uint16_t line_length() const;
uint16_t line_length();
NavigationView& nav_;
File file_{};
FileInfo info_{};
// LogFile log_{ };
FileWrapper* file_{};
struct {
// Previous cursor state.
@@ -139,7 +174,6 @@ class TextEditorView : public View {
uint32_t first_line{};
uint16_t first_col{};
bool redraw_text{true};
bool has_file{false};
} paint_state_{};
struct {
@@ -147,21 +181,133 @@ class TextEditorView : public View {
uint16_t col{};
ScrollDirection dir{ScrollDirection::Vertical};
} cursor_{};
};
/* 8px grid is 30 wide, 38 tall. */
/* 16px font height or 19 rows. */
/* Titlebar is 16px tall, so 18 rows left. */
/* 240 x 320, (304 with titlebar) */
/* Menu control for the TextEditor. */
class TextEditorMenu : public View {
public:
TextEditorMenu();
// TODO: The scrollable view should be its own widget
// otherwise control navigation doesn't work.
void on_show() override;
void on_hide() override;
Button button_open{
{24 * 8, 34 * 8, 6 * 8, 4 * 8},
"Open"};
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_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; }
private:
void hide_children(bool hidden);
Rectangle rect_frame{
{0 * 8, 0 * 8, 23 * 8, 23 * 8},
Color::dark_grey()};
NewButton button_cut{
{1 * 8, 1 * 8, 7 * 8, 7 * 8},
"Cut",
&bitmap_icon_cut,
Color::dark_grey()};
NewButton button_paste{
{8 * 8, 1 * 8, 7 * 8, 7 * 8},
"Paste",
&bitmap_icon_paste,
Color::dark_grey()};
NewButton button_copy{
{15 * 8, 1 * 8, 7 * 8, 7 * 8},
"Copy",
&bitmap_icon_copy,
Color::dark_grey()};
NewButton button_delline{
{1 * 8, 8 * 8, 7 * 8, 7 * 8},
"-Line",
&bitmap_icon_delete,
Color::dark_red()};
NewButton button_edit{
{8 * 8, 8 * 8, 7 * 8, 7 * 8},
"Edit",
&bitmap_icon_rename,
Color::dark_blue()};
NewButton button_addline{
{15 * 8, 8 * 8, 7 * 8, 7 * 8},
"+Line",
&bitmap_icon_scanner,
Color::dark_blue()};
NewButton button_open{
{1 * 8, 15 * 8, 7 * 8, 7 * 8},
"Open",
&bitmap_icon_load,
Color::green()};
NewButton button_save{
{8 * 8, 15 * 8, 7 * 8, 7 * 8},
"Save",
&bitmap_icon_save,
Color::green()};
NewButton button_exit{
{15 * 8, 15 * 8, 7 * 8, 7 * 8},
"Exit",
&bitmap_icon_previous,
Color::dark_red()};
};
/* View viewing and minor edits on a text file. */
class TextEditorView : public View {
public:
TextEditorView(NavigationView& nav);
TextEditorView(
NavigationView& nav,
const std::filesystem::path& path);
std::string title() const override {
return "Notepad";
};
void on_show() override;
private:
void open_file(const std::filesystem::path& path);
void refresh_ui();
void update_position();
void hide_menu(bool hidden = true);
void show_file_picker();
void show_nyi();
NavigationView& nav_;
std::unique_ptr<FileWrapper> file_{};
TextViewer viewer{
/* 272 = 320 - 16 (top bar) - 32 (bottom controls) */
{0, 0, 240, 272}};
TextEditorMenu menu{};
NewButton button_menu{
{26 * 8, 34 * 8, 4 * 8, 4 * 8},
{},
&bitmap_icon_controls,
Color::dark_grey()};
Text text_position{
{0 * 8, 36 * 8, 24 * 8, 2 * 8},
{0 * 8, 34 * 8, 26 * 8, 2 * 8},
""};
Text text_size{
{0 * 8, 36 * 8, 26 * 8, 2 * 8},
""};
};
+30 -30
View File
@@ -2714,38 +2714,38 @@ static constexpr Bitmap bitmap_icon_remote{
bitmap_icon_remote_data};
static constexpr uint8_t bitmap_icon_save_data[] = {
0x00,
0x01,
0x00,
0x01,
0x00,
0x01,
0x00,
0x01,
0x4E,
0x05,
0x91,
0x03,
0x3F,
0x19,
0x01,
0xFC,
0x07,
0x0A,
0x0A,
0x0A,
0x12,
0xF2,
0x21,
0x02,
0x20,
0xF9,
0xFF,
0xF9,
0xFF,
0xFD,
0x7F,
0xFD,
0x7F,
0xFF,
0x3F,
0xFF,
0x3F,
0xFF,
0x1F,
0xFF,
0x02,
0x20,
0x02,
0x20,
0x02,
0x20,
0xFA,
0x27,
0xFA,
0x2F,
0x0A,
0x28,
0xFA,
0x2F,
0x0A,
0x28,
0xFA,
0x2F,
0xFC,
0x1F,
0x00,
0x00,
};
static constexpr Bitmap bitmap_icon_save{
{16, 16},
+5 -4
View File
@@ -97,8 +97,8 @@ File::Result<File::Offset> File::seek(const Offset new_position) {
return {static_cast<File::Offset>(old_position)};
}
File::Size File::size() {
return {static_cast<File::Size>(f_size(&f))};
File::Size File::size() const {
return f_size(&f);
}
Optional<File::Error> File::write_line(const std::string& s) {
@@ -241,10 +241,11 @@ std::filesystem::filesystem_error rename_file(
std::filesystem::filesystem_error copy_file(
const std::filesystem::path& file_path,
const std::filesystem::path& dest_path) {
// Decent compromise between memory and speed.
constexpr size_t buffer_size = 512;
uint8_t buffer[buffer_size];
File src;
File dst;
constexpr size_t buffer_size = 128;
uint8_t buffer[buffer_size];
auto error = src.open(file_path);
if (error.is_valid()) return error.value();
+9 -2
View File
@@ -311,6 +311,10 @@ class File {
return value_;
}
const T& operator*() const& {
return value_;
}
Error error() const {
return error_;
}
@@ -339,6 +343,9 @@ class File {
File(){};
~File();
File(File&&) = default;
File& operator=(File&&) = default;
/* Prevent copies */
File(const File&) = delete;
File& operator=(const File&) = delete;
@@ -352,8 +359,8 @@ class File {
Result<Size> write(const void* const data, const Size bytes_to_write);
Result<Offset> seek(const uint64_t Offset);
Timestamp created_date();
Size size();
// Timestamp created_date() const;
Size size() const;
template <size_t N>
Result<Size> write(const std::array<uint8_t, N>& data) {
+17 -13
View File
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
* Copyright (C) 2023 gullradriel, Nilorea Studio Inc.
*
* This file is part of PortaPack.
*
@@ -113,35 +114,40 @@ bool load_freqman_file_ex(std::string& file_stem, freqman_db& db, bool load_freq
char* line_end;
std::string description;
rf::Frequency frequency_a, frequency_b;
char file_data[257];
const int read_buf_size = 128;
char file_data[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;
db.clear();
// these are not enough to really start with a new, clean, empty vector
// swap is the only way to achieve a perfect memory liberation
// db.clear();
// db.shrink_to_fit():
std::vector<freqman_entry>().swap(db);
auto result = freqman_file.open("FREQMAN/" + file_stem + ".TXT");
if (result.is_valid())
return false;
while (1) {
// Read a 256 bytes block from file
// Read a read_buf_size bytes block from file
freqman_file.seek(file_position);
memset(file_data, 0, 257);
auto read_size = freqman_file.read(file_data, 256);
memset(file_data, 0, read_buf_size + 1);
auto read_size = freqman_file.read(file_data, read_buf_size);
if (read_size.is_error())
return false; // Read error
file_position += 256;
file_position += 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() < 256)
if (read_size.value() < read_buf_size)
*(line_start + read_size.value()) = 0x0A;
// Look for complete lines in buffer
@@ -221,8 +227,7 @@ bool load_freqman_file_ex(std::string& file_stem, freqman_db& db, bool load_freq
pos += 2;
length = std::min(strcspn(pos, ",\x0A"), (size_t)FREQMAN_DESC_MAX_LEN);
description = string(pos, length);
} else
description = "-";
}
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++;
@@ -230,14 +235,14 @@ bool load_freqman_file_ex(std::string& file_stem, freqman_db& db, bool load_freq
}
line_start = line_end + 1;
if (line_start - file_data >= 256) break;
if (line_start - file_data >= read_buf_size) break;
}
if (read_size.value() != 256)
if (read_size.value() != read_buf_size)
break; // End of file
// Restart at beginning of last incomplete line
file_position -= (file_data + 256 - line_start);
file_position -= (file_data + read_buf_size - line_start);
}
/* populate implicitly specified modulation / bandwidth */
@@ -315,7 +320,6 @@ bool save_freqman_file(std::string& file_stem, freqman_db& db) {
auto& entry = db[n];
get_freq_string(entry, item_string);
freqman_file.write_line(item_string);
delete &item_string;
}
delete_file(freq_file_path);
rename_file(tmp_freq_file_path, freq_file_path);
+5 -4
View File
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
* Copyright (C) 2023 gullradriel, Nilorea Studio Inc.
*
* This file is part of PortaPack.
*
@@ -31,10 +32,10 @@
#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 115 // Maximum of entries we can read. This is a hardware limit
// It was tested and lowered to leave a bit of space to the caller
#define FREQMAN_MAX_PER_FILE_STR "115" // STRING OF FREQMAN_MAX_PER_FILE
#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
using namespace ui;
using namespace std;
+32
View File
@@ -27,11 +27,28 @@
bool Debounce::feed(const uint8_t bit) {
history_ = (history_ << 1) | (bit & 1);
// "Repeat" handling - simulated button release
if (repeat_ctr_) {
// Make sure the button is still being held continuously
if (history_ == 0xFF) {
// Simulate button press every REPEAT_SUBSEQUENT_DELAY ticks
if (--repeat_ctr_ == 0) {
state_ = !state_;
repeat_ctr_ = REPEAT_SUBSEQUENT_DELAY / 2;
return true;
}
} else {
// It's a real button release; stop simulating
repeat_ctr_ = 0;
}
}
if (state_ == 0) {
// Previous button state was 0 (released);
// Has button been held for DEBOUNCE_COUNT ticks?
if ((history_ & DEBOUNCE_MASK) == DEBOUNCE_MASK) {
state_ = 1;
held_time_ = 0;
return true;
}
} else {
@@ -41,6 +58,21 @@ bool Debounce::feed(const uint8_t bit) {
state_ = 0;
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) {
// Delay reached; trigger repeat code on NEXT tick
repeat_ctr_ = 1;
held_time_ = 0;
}
} else {
// Button not continuously pressed; reset counter
held_time_ = 0;
}
}
}
return false;
}
+11
View File
@@ -28,6 +28,10 @@
#define DEBOUNCE_COUNT 4
#define DEBOUNCE_MASK ((1 << DEBOUNCE_COUNT) - 1)
// # of timer0 ticks before a held button starts being counted as repeated presses
#define REPEAT_INITIAL_DELAY 250
#define REPEAT_SUBSEQUENT_DELAY 92
class Debounce {
public:
bool feed(const uint8_t bit);
@@ -36,9 +40,16 @@ class Debounce {
return state_;
}
void enable_repeat() {
repeat_enabled_ = true;
}
private:
uint8_t history_{0};
uint8_t state_{0};
bool repeat_enabled_{0};
uint16_t repeat_ctr_{0};
uint16_t held_time_{0};
};
#endif /*__DEBOUNCE_H__*/
+71 -18
View File
@@ -23,23 +23,75 @@
#include "utility.hpp"
static const int8_t transition_map[] = {
0, // 0000: noop
0, // 0001: start
0, // 0010: start
0, // 0011: rate
1, // 0100: end
0, // 0101: noop
0, // 0110: rate
-1, // 0111: end
-1, // 1000: end
0, // 1001: rate
0, // 1010: noop
1, // 1011: end
0, // 1100: rate
0, // 1101: start
0, // 1110: start
0, // 1111: noop
#include "portapack.hpp"
#include "portapack_persistent_memory.hpp"
// Now supporting multiple levels of rotary encoder dial sensitivity
//
// Portapack H2 normally has a 30-step encoder, meaning one step (pulse) every
// 12 degrees of rotation.
//
// For each encoder "pulse" there are 4 state transitions, and we can choose
// between looking at all of them (high sensitivity), half of them (medium/default),
// or one quarter of them (low sensitivity).
static const int8_t transition_map[][16] = {
// Normal (Medium) Sensitivity -- default
{
0, // 0000: noop
0, // 0001: ccw start
0, // 0010: cw start
0, // 0011: rate
1, // 0100: cw end
0, // 0101: noop
0, // 0110: rate
-1, // 0111: ccw end
-1, // 1000: ccw end
0, // 1001: rate
0, // 1010: noop
1, // 1011: cw end
0, // 1100: rate
0, // 1101: cw start
0, // 1110: ccw start
0, // 1111: noop
},
// Low Sensitivity
{
0, // 0000: noop
0, // 0001: ccw start
0, // 0010: cw start
0, // 0011: rate
1, // 0100: cw end
0, // 0101: noop
0, // 0110: rate
0, // 0111: ccw end
-1, // 1000: ccw end
0, // 1001: rate
0, // 1010: noop
0, // 1011: cw end
0, // 1100: rate
0, // 1101: cw start
0, // 1110: ccw start
0, // 1111: noop
},
// High Sensitivity
{
0, // 0000: noop
-1, // 0001: ccw start
1, // 0010: cw start
0, // 0011: rate
1, // 0100: cw end
0, // 0101: noop
0, // 0110: rate
-1, // 0111: ccw end
-1, // 1000: ccw end
0, // 1001: rate
0, // 1010: noop
1, // 1011: cw end
0, // 1100: rate
1, // 1101: cw start
-1, // 1110: ccw start
0, // 1111: noop
},
};
int_fast8_t Encoder::update(
@@ -50,5 +102,6 @@ int_fast8_t Encoder::update(
state <<= 1;
state |= phase_1;
return transition_map[state & 0xf];
// dial sensitivity setting is stored in pmem
return transition_map[portapack::persistent_memory::config_encoder_dial_sensitivity()][state & 0xf];
}
+4 -4
View File
@@ -71,10 +71,10 @@ Samples get() {
const auto yp_reg = LPC_ADC0->DR[portapack::adc0_touch_yp_input];
const auto yn_reg = LPC_ADC0->DR[portapack::adc0_touch_yn_input];
return {
(xp_reg >> 6) & 0x3ff,
(xn_reg >> 6) & 0x3ff,
(yp_reg >> 6) & 0x3ff,
(yn_reg >> 6) & 0x3ff,
((xp_reg >> 6) & 0x3ff) * 16,
((xn_reg >> 6) & 0x3ff) * 16,
((yp_reg >> 6) & 0x3ff) * 16,
((yn_reg >> 6) & 0x3ff) * 16,
};
}
+4
View File
@@ -187,6 +187,10 @@ void controls_init() {
*/
gptStart(&GPTD1, &timer0_config);
gptStartContinuous(&GPTD1, timer0_match_count);
// Enable repeat for directional switches only
for (size_t i = 0; i < 4; i++)
switch_debounce[i].enable_repeat();
}
SwitchesState get_switches_state() {
+2 -1
View File
@@ -521,7 +521,8 @@ void shutdown(const bool leave_screen_on) {
display.shutdown();
}
radio::disable();
receiver_model.disable();
transmitter_model.disable();
audio::shutdown();
hackrf::cpld::init_from_eeprom();
+4
View File
@@ -36,6 +36,8 @@
#include "clock_manager.hpp"
#include "temperature_logger.hpp"
/* TODO: This would be better as a class to add
* guardrails on setting properties. */
namespace portapack {
extern portapack::IO io;
@@ -59,6 +61,8 @@ extern bool antenna_bias;
extern TemperatureLogger temperature_logger;
/* Get or set the antenna_bias flag.
* NB: Does not actually update the radio state. */
void set_antenna_bias(const bool v);
bool get_antenna_bias();
+15 -12
View File
@@ -42,6 +42,9 @@ using namespace hackrf::one;
#include "portapack.hpp"
#include "portapack_persistent_memory.hpp"
/* Direct access to the radio. Setting values incorrectly can damage
* the device. Applications should use ReceiverModel or TransmitterModel
* instead of calling these functions directly. */
namespace radio {
static constexpr uint32_t ssp1_cpsr = 2;
@@ -245,18 +248,7 @@ void set_antenna_bias(const bool on) {
}
}
void disable() {
set_antenna_bias(false);
baseband_codec.set_mode(max5864::Mode::Shutdown);
second_if->set_mode(max2837::Mode::Standby);
first_if.disable();
set_rf_amp(false);
led_rx.off();
led_tx.off();
}
void enable(Configuration configuration) {
/*void enable(Configuration configuration) {
configure(configuration);
}
@@ -268,6 +260,17 @@ void configure(Configuration configuration) {
set_baseband_rate(configuration.baseband_rate);
set_baseband_filter_bandwidth(configuration.baseband_filter_bandwidth);
set_direction(configuration.direction);
}*/
void disable() {
set_antenna_bias(false);
baseband_codec.set_mode(max5864::Mode::Shutdown);
second_if->set_mode(max2837::Mode::Standby);
first_if.disable();
set_rf_amp(false);
led_rx.off();
led_tx.off();
}
namespace debug {
+6 -2
View File
@@ -27,6 +27,9 @@
#include <cstdint>
#include <cstddef>
/* Direct access to the radio. Setting values incorrectly can damage
* the device. Applications should use ReceiverModel or TransmitterModel
* instead of calling these functions directly. */
namespace radio {
struct Configuration {
@@ -51,8 +54,9 @@ void set_baseband_filter_bandwidth(const uint32_t bandwidth_minimum);
void set_baseband_rate(const uint32_t rate);
void set_antenna_bias(const bool on);
void enable(Configuration configuration);
void configure(Configuration configuration);
/* Use ReceiverModel or TransmitterModel instead. */
// void enable(Configuration configuration);
// void configure(Configuration configuration);
void disable();
namespace debug {
+5 -6
View File
@@ -118,14 +118,14 @@ void ReceiverModel::set_vga(int32_t v_db) {
update_vga();
}
int32_t ReceiverModel::tx_gain() const {
/*int32_t ReceiverModel::tx_gain() const {
return tx_gain_db_;
}
void ReceiverModel::set_tx_gain(int32_t v_db) {
tx_gain_db_ = v_db;
update_tx_gain();
}
}*/
uint32_t ReceiverModel::sampling_rate() const {
return sampling_rate_;
@@ -171,7 +171,7 @@ void ReceiverModel::enable() {
update_rf_amp();
update_lna();
update_vga();
update_tx_gain();
// update_tx_gain();
update_baseband_bandwidth();
update_sampling_rate();
update_modulation();
@@ -185,7 +185,6 @@ void ReceiverModel::enable() {
void ReceiverModel::disable() {
enabled_ = false;
radio::set_antenna_bias(false);
// TODO: Responsibility for enabling/disabling the radio is muddy.
// Some happens in ReceiverModel, some inside radio namespace.
@@ -230,9 +229,9 @@ void ReceiverModel::update_vga() {
radio::set_vga_gain(vga_gain_db_);
}
void ReceiverModel::update_tx_gain() {
/*void ReceiverModel::update_tx_gain() {
radio::set_tx_gain(tx_gain_db_);
}
}*/
void ReceiverModel::set_am_configuration(const size_t n) {
if (n < am_configs.size()) {
+13 -5
View File
@@ -30,6 +30,7 @@
#include "max283x.hpp"
#include "volume.hpp"
// TODO: consider a base class for ReceiverModel & TransmitterModel.
class ReceiverModel {
public:
enum class Mode {
@@ -60,8 +61,9 @@ class ReceiverModel {
int32_t vga() const;
void set_vga(int32_t v_db);
int32_t tx_gain() const;
void set_tx_gain(int32_t v_db);
// TODO: Why does receiver need tx_gain?
// int32_t tx_gain() const;
// void set_tx_gain(int32_t v_db);
uint32_t sampling_rate() const;
void set_sampling_rate(uint32_t v);
@@ -87,7 +89,13 @@ class ReceiverModel {
size_t wfm_configuration() const;
void set_wfm_configuration(const size_t n);
void set_configuration_without_init(const Mode new_mode, const rf::Frequency new_frequency_step, const size_t new_am_config_index, const size_t new_nbfm_config_index, const size_t new_wfm_config_index, uint8_t new_squelch_level);
void set_configuration_without_init(
const Mode new_mode,
const rf::Frequency new_frequency_step,
const size_t new_am_config_index,
const size_t new_nbfm_config_index,
const size_t new_wfm_config_index,
uint8_t new_squelch_level);
private:
rf::Frequency frequency_step_{25000};
@@ -96,7 +104,7 @@ class ReceiverModel {
int32_t lna_gain_db_{32};
uint32_t baseband_bandwidth_{max283x::filter::bandwidth_minimum};
int32_t vga_gain_db_{32};
int32_t tx_gain_db_{47};
// int32_t tx_gain_db_{47};
Mode mode_{Mode::NarrowbandFMAudio};
uint32_t sampling_rate_{3072000};
size_t am_config_index = 0;
@@ -113,7 +121,7 @@ class ReceiverModel {
void update_lna();
void update_baseband_bandwidth();
void update_vga();
void update_tx_gain();
// void update_tx_gain();
void update_sampling_rate();
void update_headphone_volume();
+6 -6
View File
@@ -34,16 +34,16 @@ Metrics calculate_metrics(const Frame& frame) {
* fast *enough*?), so maybe leave it alone at least for now.
*/
const auto x_max = frame.x.xp;
const auto x_min = frame.x.xn;
const auto x_max = frame.x.xp / 16;
const auto x_min = frame.x.xn / 16;
const auto x_range = x_max - x_min;
const float x_position = (frame.x.yp + frame.x.yn) * 0.5f;
const float x_position = (frame.x.yp / 16 + frame.x.yn / 16) * 0.5f;
const float x_norm = (x_position - x_min) / x_range;
const auto y_max = frame.y.yn;
const auto y_min = frame.y.yp;
const auto y_max = frame.y.yn / 16;
const auto y_min = frame.y.yp / 16;
const auto y_range = y_max - y_min;
const float y_position = (frame.y.xp + frame.y.xn) * 0.5f;
const float y_position = (frame.y.xp / 16 + frame.y.xn / 16) * 0.5f;
const float y_norm = (y_position - y_min) / y_range;
const auto z_max = frame.pressure.yp;
+1 -1
View File
@@ -125,6 +125,7 @@ void TransmitterModel::enable() {
update_vga();
update_baseband_bandwidth();
update_sampling_rate();
update_tx_gain();
led_tx.on();
signal_token_tick_second = rtc_time::signal_tick_second += [this]() {
@@ -138,7 +139,6 @@ void TransmitterModel::enable() {
void TransmitterModel::disable() {
enabled_ = false;
radio::set_antenna_bias(false);
// TODO: Responsibility for enabling/disabling the radio is muddy.
// Some happens in ReceiverModel, some inside radio namespace.
+2 -1
View File
@@ -71,7 +71,8 @@ class TransmitterModel {
uint32_t channel_bandwidth_{1};
uint32_t baseband_bandwidth_{max2837::filter::bandwidth_minimum};
int32_t vga_gain_db_{8};
int32_t tx_gain_db_{47};
/* 35 should give approx 1m transmission range. */
int32_t tx_gain_db_{35};
uint32_t sampling_rate_{3072000};
SignalToken signal_token_tick_second{};
@@ -338,11 +338,11 @@ const uint8_t fixed_5x8_glyph_data[] = {
0x02,
// Index: 44 (0x2C) Char: 0x004C ('L')
0x20,
0x40,
0x08,
0x21,
0x84,
0x10,
0xC2,
0x01,
0x03,
// Index: 45 (0x2D) Char: 0x004D ('M')
0xA0,
@@ -654,8 +654,8 @@ const uint8_t fixed_5x8_glyph_data[] = {
// Index: 89 (0x59) Char: 0x0079 ('y')
0x00,
0x00,
0xE5,
0x80,
0xE4,
0x90,
0x01,
+157
View File
@@ -0,0 +1,157 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include "ui_freqlist.hpp"
#include "baseband_api.hpp"
#include "utility.hpp"
#include <algorithm>
namespace ui {
FreqManUIList::FreqManUIList(
Rect parent_rect,
bool instant_exec)
: Widget{parent_rect},
instant_exec_{instant_exec} {
this->set_focusable(true);
}
void FreqManUIList::set_highlighted_index(int index) {
if ((unsigned)(current_index + index) >= freqlist_db.size())
return;
if (index < 0) {
index = 0;
if (current_index > 0)
current_index--;
}
if (index >= freqlist_nb_lines) {
index = freqlist_nb_lines - 1;
if ((unsigned)(current_index + index) < freqlist_db.size())
current_index++;
else
current_index = freqlist_db.size() - freqlist_nb_lines - 1;
}
highlighted_index = index;
}
uint8_t FreqManUIList::get_index() {
return current_index + highlighted_index;
}
void FreqManUIList::paint(Painter& painter) {
freqlist_nb_lines = 0;
const auto r = screen_rect();
uint8_t focused = has_focus();
const Rect r_widget_screen{r.left() + focused, r.top() + focused, r.width() - 2 * focused, r.height() - 2 * +focused};
painter.fill_rectangle(
r_widget_screen,
Color::black());
if (freqlist_db.size() == 0)
return;
uint8_t nb_lines = 0;
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)) {
std::string description = freqman_item_string(freqlist_db[it], 30);
// line is within the widget
painter.draw_string(
{0, r.location().y() + (int)nb_lines * char_height},
style_default, description);
if (nb_lines == highlighted_index) {
const Rect r_highlighted_freq{0, r.location().y() + (int)nb_lines * char_height, 240, char_height};
painter.draw_rectangle(
r_highlighted_freq,
Color::white());
}
nb_lines++;
} else {
// rect is filled, we can break
break;
}
}
freqlist_nb_lines = nb_lines;
if (has_focus() || highlighted()) {
const Rect r_focus{r.left(), r.top(), r.width(), r.height()};
painter.draw_rectangle(
r_focus,
Color::white());
}
}
void FreqManUIList::set_db(freqman_db& db) {
freqlist_db = db;
current_index = 0;
highlighted_index = 0;
}
void FreqManUIList::on_focus() {
if (on_highlight)
on_highlight(*this);
}
bool FreqManUIList::on_key(const KeyEvent key) {
if (key == KeyEvent::Select) {
if (on_select) {
on_select(*this);
return true;
}
} else {
if (on_dir) {
return on_dir(*this, key);
}
}
return false;
}
bool FreqManUIList::on_touch(const TouchEvent event) {
switch (event.type) {
case TouchEvent::Type::Start:
set_highlighted(true);
set_dirty();
if (on_touch_press) {
on_touch_press(*this);
}
if (on_select && instant_exec_) {
on_select(*this);
}
return true;
case TouchEvent::Type::End:
set_highlighted(false);
set_dirty();
if (on_touch_release) {
on_touch_release(*this);
}
if (on_select && !instant_exec_) {
on_select(*this);
}
return true;
default:
return false;
}
}
bool FreqManUIList::on_encoder(EncoderEvent delta) {
set_highlighted_index((int)highlighted_index + delta);
set_dirty();
return true;
}
} /* namespace ui */
+80
View File
@@ -0,0 +1,80 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
*
* 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 __UI_FREQLIST_H__
#define __UI_FREQLIST_H__
#include "ui.hpp"
#include "ui_font_fixed_5x8.hpp"
#include "ui_widget.hpp"
#include "ui_painter.hpp"
#include "event_m0.hpp"
#include "message.hpp"
#include "freqman.hpp"
#include <cstdint>
namespace ui {
class FreqManUIList : public Widget {
public:
std::function<void(FreqManUIList&)> on_select{};
std::function<void(FreqManUIList&)> on_touch_release{}; // Executed when releasing touch, after on_select.
std::function<void(FreqManUIList&)> on_touch_press{}; // Executed when touching, before on_select.
std::function<bool(FreqManUIList&, KeyEvent)> on_dir{};
std::function<void(FreqManUIList&)> on_highlight{};
FreqManUIList(Rect parent_rect, bool instant_exec); // instant_exec: Execute on_select when you touching instead of releasing
FreqManUIList(
Rect parent_rect)
: FreqManUIList{parent_rect, false} {
}
FreqManUIList()
: FreqManUIList{{}, {}} {
}
void paint(Painter& painter) override;
void on_focus() override;
bool on_key(const KeyEvent key) override;
bool on_touch(const TouchEvent event) override;
bool on_encoder(EncoderEvent delta) override;
void set_highlighted_index(int index); // set highlighted_index and return capped highlighted_index value to set in caller
uint8_t get_index(); // return highlighed + index
void set_db(freqman_db& db);
private:
static constexpr Style style_default{
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::white(),
};
static constexpr int8_t char_height = 16;
bool instant_exec_{false};
freqman_db freqlist_db{};
int current_index{0};
int highlighted_index{0};
int freqlist_nb_lines{0};
};
} // namespace ui
#endif /*__UI_FREQLIST_H__*/
+13 -11
View File
@@ -22,7 +22,7 @@
#include "ui_navigation.hpp"
//#include "modules.h"
// #include "modules.h"
#include "portapack.hpp"
#include "event_m0.hpp"
@@ -46,13 +46,13 @@
#include "ui_fileman.hpp"
#include "ui_freqman.hpp"
#include "ui_jammer.hpp"
//#include "ui_keyfob.hpp"
// #include "ui_keyfob.hpp"
#include "ui_lcr.hpp"
#include "ui_mictx.hpp"
#include "ui_morse.hpp"
//#include "ui_numbers.hpp"
//#include "ui_nuoptix.hpp"
//#include "ui_playdead.hpp"
// #include "ui_numbers.hpp"
// #include "ui_nuoptix.hpp"
// #include "ui_playdead.hpp"
#include "ui_pocsag_tx.hpp"
#include "ui_rds.hpp"
#include "ui_remote.hpp"
@@ -65,7 +65,7 @@
#include "ui_siggen.hpp"
#include "ui_sonde.hpp"
#include "ui_sstvtx.hpp"
//#include "ui_test.hpp"
// #include "ui_test.hpp"
#include "ui_text_editor.hpp"
#include "ui_tone_search.hpp"
#include "ui_touchtunes.hpp"
@@ -76,7 +76,7 @@
#include "ui_sd_over_usb.hpp"
#include "ui_spectrum_painter.hpp"
//#include "acars_app.hpp"
// #include "acars_app.hpp"
#include "ais_app.hpp"
#include "analog_audio_app.hpp"
#include "analog_tv_app.hpp"
@@ -307,11 +307,10 @@ void SystemStatusView::on_stealth() {
}
void SystemStatusView::on_bias_tee() {
if (!portapack::antenna_bias) {
if (!portapack::get_antenna_bias()) {
nav_.display_modal("Bias voltage", "Enable DC voltage on\nantenna connector?", YESNO, [this](bool v) {
if (v) {
portapack::set_antenna_bias(true);
// radio::set_antenna_bias(true);
receiver_model.set_antenna_bias();
transmitter_model.set_antenna_bias();
refresh();
@@ -319,9 +318,12 @@ void SystemStatusView::on_bias_tee() {
});
} else {
portapack::set_antenna_bias(false);
// radio::set_antenna_bias(false);
receiver_model.set_antenna_bias();
transmitter_model.set_antenna_bias();
// Ensure this is disabled. The models don't actually
// update the radio unless they are 'enabled_'.
radio::set_antenna_bias(false);
refresh();
}
}
@@ -563,7 +565,7 @@ TransmittersMenuView::TransmittersMenuView(NavigationView& nav) {
{"SSTV", ui::Color::green(), &bitmap_icon_sstv, [&nav]() { nav.push<SSTVTXView>(); }},
{"TEDI/LCR", ui::Color::yellow(), &bitmap_icon_lcr, [&nav]() { nav.push<LCRView>(); }},
{"TouchTune", ui::Color::yellow(), &bitmap_icon_remote, [&nav]() { nav.push<TouchTunesView>(); }},
{"Playlist", ui::Color::yellow(), &bitmap_icon_remote, [&nav]() { nav.push<PlaylistView>(); }},
{"Playlist", ui::Color::green(), &bitmap_icon_scanner, [&nav]() { nav.push<PlaylistView>(); }},
{"S.Painter", ui::Color::orange(), &bitmap_icon_morse, [&nav]() { nav.push<SpectrumPainterView>(); }},
//{ "Remote", ui::Color::dark_grey(), &bitmap_icon_remote, [&nav](){ nav.push<RemoteView>(); } },
});
@@ -807,10 +807,6 @@ void cpu_clock_init(void) {
// CCU2_CLK_APLL_CFG = 0;
// CCU2_CLK_SDIO_CFG = 0;
#endif
if (detected_platform() == BOARD_ID_HACKRF1_R9) {
clkin_detect_init();
}
}
clock_source_t activate_best_clock_source(void) {
@@ -25,10 +25,10 @@
#include "usb_type.h"
#include "usb_descriptor.h"
#define USB_VENDOR_ID (0x1D50)
#define USB_VENDOR_ID (0x0781) /* SanDisk Corp. */
#ifdef HACKRF_ONE
#define USB_PRODUCT_ID (0x6089)
#define USB_PRODUCT_ID (0xa7a8) /* SD card reader */
#elif JAWBREAKER
#define USB_PRODUCT_ID (0x604B)
#elif RAD1O
@@ -37,7 +37,7 @@
#define USB_PRODUCT_ID (0xFFFF)
#endif
#define USB_API_VERSION (0x0107)
#define USB_API_VERSION (0x0127) /* hardware revision */
#define USB_WORD(x) (x & 0xFF), ((x >> 8) & 0xFF)
+120
View File
@@ -0,0 +1,120 @@
/*
* 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 __CIRCULAR_BUFFER_H__
#define __CIRCULAR_BUFFER_H__
#include <stddef.h> // For size_t
#include <memory>
/* Implements a fixed-size, circular buffer.
* NB: Holds Capacity - 1 items.
* There are no bounds checks on accessors so ensure there are
* items in the buffer before accessing front/back/operator[]. */
template <typename T, size_t Capacity>
class CircularBuffer {
public:
CircularBuffer() = default;
CircularBuffer(const CircularBuffer&) = delete;
CircularBuffer(CircularBuffer&&) = delete;
CircularBuffer& operator=(const CircularBuffer&) = delete;
CircularBuffer& operator=(CircularBuffer&&) = delete;
void push_front(T val) {
head_ = head_ > 0 ? head_ - 1 : last_index;
if (head_ == end_)
pop_back_internal();
data_[head_] = std::move(val);
}
void pop_front() {
if (!empty())
pop_front_internal();
}
void push_back(T val) {
data_[end_] = std::move(val);
end_ = end_ < last_index ? end_ + 1 : 0;
if (head_ == end_)
pop_front_internal();
}
void pop_back() {
if (!empty())
pop_back_internal();
}
T& operator[](size_t ix) & {
ix += head_;
if (ix >= Capacity)
ix -= Capacity;
return data_[ix];
}
const T& operator[](size_t ix) const& {
return const_cast<CircularBuffer*>(this)->operator[](ix);
}
const T& front() const& {
return data_[head_];
}
const T& back() const& {
auto end = end_ > 0 ? end_ - 1 : last_index;
return data_[end];
}
size_t size() const& {
auto end = end_;
if (end < head_)
end += Capacity;
return end - head_;
}
bool empty() const {
return head_ == end_;
}
void clear() {
head_ = 0;
end_ = 0;
}
private:
void pop_front_internal() {
head_ = head_ < last_index ? head_ + 1 : 0;
}
void pop_back_internal() {
end_ = end_ > 0 ? end_ - 1 : last_index;
}
static constexpr size_t last_index = Capacity - 1;
size_t head_{0};
size_t end_{0};
T data_[Capacity]{};
};
#endif /*__CIRCULAR_BUFFER_H__*/
+19 -5
View File
@@ -28,14 +28,28 @@ template <typename T>
class Optional {
public:
constexpr Optional()
: value_{}, valid_{false} {};
: value_{}, valid_{false} {}
constexpr Optional(const T& value)
: value_{value}, valid_{true} {};
: value_{value}, valid_{true} {}
constexpr Optional(T&& value)
: value_{std::move(value)}, valid_{true} {};
: value_{std::move(value)}, valid_{true} {}
bool is_valid() const { return valid_; };
const T& value() const { return value_; };
bool is_valid() const { return valid_; }
operator bool() const { return valid_; }
// TODO: Throw if not valid?
T& value() & { return value_; }
T& operator*() & { return value_; }
const T& value() const& { return value_; }
const T& operator*() const& { return value_; }
T&& value() && { return value_; }
T&& operator*() && { return value_; }
const T&& value() const&& { return value_; }
const T&& operator*() const&& { return value_; }
T* operator->() { return &value_; }
const T* operator->() const { return &value_; }
private:
T value_;
@@ -302,6 +302,9 @@ struct data_t {
uint32_t frequency_tx_correction;
bool updown_frequency_tx_correction;
// Rotary encoder dial sensitivity (encoder.cpp/hpp)
uint8_t encoder_dial_sensitivity;
constexpr data_t()
: structure_version(data_structure_version_enum::VERSION_CURRENT),
tuned_frequency(tuned_frequency_reset_value),
@@ -337,7 +340,8 @@ struct data_t {
frequency_rx_correction(0),
updown_frequency_rx_correction(0),
frequency_tx_correction(0),
updown_frequency_tx_correction(0) {
updown_frequency_tx_correction(0),
encoder_dial_sensitivity(0) {
}
};
@@ -808,6 +812,14 @@ void set_config_freq_rx_correction(uint32_t v) {
data->frequency_rx_correction = v;
}
// rotary encoder dial settings
uint8_t config_encoder_dial_sensitivity() {
return data->encoder_dial_sensitivity;
}
void set_encoder_dial_sensitivity(uint8_t v) {
data->encoder_dial_sensitivity = v;
}
// sd persisting settings
int save_persistent_settings_to_file(std::string filename) {
delete_file(filename);
@@ -99,6 +99,13 @@ struct backlight_config_t {
bool _timeout_enabled;
};
enum encoder_dial_sensitivity {
DIAL_SENSITIVITY_MEDIUM = 0,
DIAL_SENSITIVITY_LOW = 1,
DIAL_SENSITIVITY_HIGH = 2,
NUM_DIAL_SENSITIVITY
};
namespace cache {
/* Set values in cache to sensible defaults. */
@@ -199,6 +206,8 @@ void set_config_login(bool v);
void set_config_speaker(bool v);
void set_config_backlight_timer(const backlight_config_t& new_value);
void set_disable_touchscreen(bool v);
uint8_t config_encoder_dial_sensitivity();
void set_encoder_dial_sensitivity(uint8_t v);
// uint8_t ui_config_textentry();
// void set_config_textentry(uint8_t new_value);
Binary file not shown.

Before

Width:  |  Height:  |  Size: 171 B

After

Width:  |  Height:  |  Size: 188 B

+1
View File
@@ -35,6 +35,7 @@ set(CMAKE_CXX_COMPILER g++)
add_executable(application_test EXCLUDE_FROM_ALL
${PROJECT_SOURCE_DIR}/main.cpp
${PROJECT_SOURCE_DIR}/test_basics.cpp
${PROJECT_SOURCE_DIR}/test_circular_buffer.cpp
${PROJECT_SOURCE_DIR}/test_optional.cpp
)
@@ -0,0 +1,252 @@
/*
* Copyright (C) 2023
*
* 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 "doctest.h"
#include "circular_buffer.hpp"
TEST_SUITE_BEGIN("circular buffer");
SCENARIO("Items can be pushed and popped from front and back.") {
GIVEN("an empty buffer") {
CircularBuffer<int, 5> cb;
REQUIRE(cb.empty());
REQUIRE(cb.size() == 0);
WHEN("push_back()") {
cb.push_back(1);
THEN("size should increase") {
CHECK(cb.empty() == false);
CHECK(cb.size() == 1);
}
cb.push_back(2);
THEN("size should increase") {
CHECK(cb.empty() == false);
CHECK(cb.size() == 2);
}
THEN("back() should be the last item pushed") {
CHECK(cb.back() == 2);
}
THEN("front() should be the first item pushed") {
CHECK(cb.front() == 1);
}
}
WHEN("push_front()") {
cb.push_front(3);
THEN("size should increase") {
CHECK(cb.empty() == false);
CHECK(cb.size() == 1);
}
cb.push_front(4);
THEN("size should increase") {
CHECK(cb.empty() == false);
CHECK(cb.size() == 2);
}
THEN("back() should be first item pushed") {
CHECK(cb.back() == 3);
}
THEN("front() should be the last item pushed") {
CHECK(cb.front() == 4);
}
}
WHEN("pop_back()") {
cb.pop_back();
THEN("size should not change") {
REQUIRE(cb.empty());
REQUIRE(cb.size() == 0);
}
}
WHEN("pop_front()") {
cb.pop_back();
THEN("size should not change") {
REQUIRE(cb.empty());
REQUIRE(cb.size() == 0);
}
}
}
GIVEN("a buffer with items") {
CircularBuffer<int, 5> cb;
cb.push_back(1);
cb.push_back(2);
cb.push_back(3);
REQUIRE(!cb.empty());
REQUIRE(cb.size() == 3);
REQUIRE(cb.front() == 1);
REQUIRE(cb.back() == 3);
WHEN("pop_back()") {
cb.pop_back();
THEN("size should decrease") {
CHECK(cb.empty() == false);
CHECK(cb.size() == 2);
}
THEN("back item should be removed") {
CHECK(cb.back() == 2);
}
THEN("front item should be unchanged") {
CHECK(cb.front() == 1);
}
}
WHEN("pop_front()") {
cb.pop_front();
THEN("size should decrease") {
CHECK(cb.empty() == false);
CHECK(cb.size() == 2);
}
THEN("front item should be removed") {
CHECK(cb.front() == 2);
}
THEN("back item should be unchanged") {
CHECK(cb.back() == 3);
}
}
WHEN("clear()") {
cb.clear();
THEN("size should be empty") {
CHECK(cb.empty());
CHECK(cb.size() == 0);
}
}
}
GIVEN("a full buffer") {
CircularBuffer<int, 4> cb;
cb.push_back(1);
cb.push_back(2);
cb.push_back(3);
REQUIRE(!cb.empty());
REQUIRE(cb.size() == 3);
REQUIRE(cb.front() == 1);
REQUIRE(cb.back() == 3);
WHEN("push_back()") {
cb.push_back(4);
THEN("size should not be changed") {
CHECK(!cb.empty());
CHECK(cb.size() == 3);
}
THEN("front should be popped") {
CHECK(cb.front() == 2);
}
THEN("back should be new value") {
CHECK(cb.back() == 4);
}
}
WHEN("push_front()") {
cb.push_front(4);
THEN("size should not be changed") {
CHECK(!cb.empty());
CHECK(cb.size() == 3);
}
THEN("front should be new value") {
CHECK(cb.front() == 4);
}
THEN("back should be popped") {
CHECK(cb.back() == 2);
}
}
}
}
SCENARIO("Items can be accessed randomly") {
GIVEN("buffer with items") {
CircularBuffer<int, 4> cb;
cb.push_front(1);
cb.push_back(2);
cb.push_back(3);
WHEN("accessing items") {
THEN("front should be at 0") {
CHECK(cb.front() == cb[0]);
}
THEN("last should be at size() - 1") {
CHECK(cb.back() == cb[cb.size() - 1]);
}
THEN("all should be accessible") {
// Assumes values are in order.
for (size_t i = 0; i < cb.size(); i++)
CHECK(cb[i] == i + 1);
}
}
WHEN("accessing items after push_front") {
cb.push_front(4);
THEN("front should be at 0") {
CHECK(cb.front() == cb[0]);
}
THEN("last should be at size() - 1") {
CHECK(cb.back() == cb[cb.size() - 1]);
}
}
WHEN("accessing items after push_back") {
cb.push_back(4);
THEN("front should be at 0") {
CHECK(cb.front() == cb[0]);
}
THEN("last should be at size() - 1") {
CHECK(cb.back() == cb[cb.size() - 1]);
}
}
}
}
TEST_SUITE_END();
@@ -34,9 +34,28 @@ TEST_CASE("Instance with value should be valid.") {
REQUIRE(o.is_valid());
}
TEST_CASE("Instance with value should be return true.") {
Optional<int> o{1};
REQUIRE((bool)o);
}
TEST_CASE("value() should return value.") {
Optional<int> o{1};
REQUIRE(o.value() == 1);
}
TEST_CASE("operator* should return value.") {
Optional<int> o{1};
REQUIRE(*o == 1);
}
TEST_CASE("operator-> should return value members.") {
struct S {
int i;
};
Optional<S> o{S{1}};
REQUIRE(o->i == 1);
}
TEST_SUITE_END();
+1 -1
Submodule hackrf updated: ebe1ca003a...50c22aa05b
@@ -1,3 +1,2 @@
##FREQ FILE SAMPLE RATE
315000000,SAMPLES/TeslaChargePort_US.C16,500000
433920000,SAMPLES/TeslaChargePort_EU_AU.C16,500000
View File