Compare commits

...

20 Commits

Author SHA1 Message Date
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
56 changed files with 1258 additions and 402 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}
+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 {
+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_;
-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.
*
@@ -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
+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{};
+49 -15
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,24 @@ 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) {
frequency_list.clear();
}
}
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 +385,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 +447,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 +456,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 +570,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 +598,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 +612,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 +676,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();
@@ -758,10 +782,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 +797,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";
@@ -879,7 +907,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 +960,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 +997,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 {
+292 -118
View File
@@ -21,6 +21,8 @@
#include "ui_fileman.hpp"
#include "ui_text_editor.hpp"
#include "log_file.hpp"
#include "string_format.hpp"
using namespace portapack;
@@ -31,29 +33,256 @@ 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 {
/* FileWrapper ******************************************************/
FileWrapper::FileWrapper() {
}
Optional<FileWrapper::Error> FileWrapper::open(const fs::path& path) {
file_ = File();
auto result = file_.open(path);
if (!result.is_valid()) // No error
initialize();
return result;
}
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.is_valid())
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.is_valid())
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.is_valid())
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.is_valid()) {
++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.is_valid())
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.is_valid()) {
// 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.is_valid()) {
start_line_++;
start_offset_ = newlines_.front() + 1;
newlines_.push_back(*offset);
} /* else at the EOF. */
}
}
}
Optional<FileWrapper::Offset> FileWrapper::previous_newline(Offset start) {
constexpr size_t buffer_size = 128;
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) {
constexpr size_t buffer_size = 128;
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};
}
/* TextEditorView ***************************************************/
TextEditorView::TextEditorView(NavigationView& nav)
: nav_{nav} {
add_children(
{&button_open,
&text_position});
{
&button_open,
&text_position,
&text_size,
});
set_focusable(true);
// log_.append("LOGS/NOTEPAD.TXT");
button_open.on_select = [this](Button&) {
auto open_view = nav_.push<FileLoadView>(".TXT");
auto open_view = nav_.push<FileLoadView>("");
open_view->on_changed = [this](std::filesystem::path path) {
open_file(path);
};
};
}
TextEditorView::TextEditorView(NavigationView& nav, const fs::path& path)
: TextEditorView(nav) {
open_file(path);
}
void TextEditorView::on_focus() {
refresh_ui();
button_open.focus();
}
void TextEditorView::paint(Painter& painter) {
@@ -63,16 +292,19 @@ void TextEditorView::paint(Painter& painter) {
if (!paint_state_.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;
@@ -129,28 +361,41 @@ bool TextEditorView::on_encoder(EncoderEvent delta) {
bool TextEditorView::apply_scrolling_constraints(int16_t delta_line, int16_t delta_col) {
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;
@@ -161,134 +406,65 @@ bool TextEditorView::apply_scrolling_constraints(int16_t delta_line, int16_t del
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();
"Ln " + to_string_dec_uint(cursor_.line + 1) +
", Col " + to_string_dec_uint(cursor_.col + 1));
text_size.set(
"Lines:" + to_string_dec_uint(file_.line_count()) +
" (" + to_string_file_size(file_.size()) + ")");
} else {
button_open.focus();
// if (!button_open.has_focus())
// button_open.focus();
text_position.set("");
text_size.set("");
}
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;
}
if (result.is_valid())
nav_.display_modal("Read Error", "Cannot open file:\n" + result->what());
paint_state_.has_file = !result.is_valid(); // Has an error.
paint_state_.first_line = 0;
paint_state_.first_col = 0;
cursor_.line = 0;
cursor_.col = 0;
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.
// 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));
style_default, 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.location().y() + (int)i * char_height,
clear_width * char_width, char_height},
style_default.background);
}
}
@@ -305,9 +481,7 @@ void TextEditorView::paint_cursor(Painter& painter) {
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);
@@ -315,8 +489,8 @@ void TextEditorView::paint_cursor(Painter& painter) {
paint_state_.col = cursor_.col;
}
uint16_t TextEditorView::line_length() const {
return info_.line_length(cursor_.line);
uint16_t TextEditorView::line_length() {
return file_.line_length(cursor_.line);
}
} // namespace ui
+64 -42
View File
@@ -29,8 +29,9 @@
#include "ui_widget.hpp"
//#include "ui_textentry.hpp"
#include "circular_buffer.hpp"
#include "file.hpp"
#include "log_file.hpp"
#include "optional.hpp"
#include <string>
#include <vector>
@@ -47,44 +48,71 @@ 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;
/* Wraps a file and provides an API for accessing lines efficiently. */
class FileWrapper {
public:
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;
};
uint32_t line_count() const {
return newlines.size();
}
FileWrapper();
uint32_t line_start(uint32_t line) const {
return line == 0 ? 0 : (newlines[line - 1] + 1);
}
/* Prevent copies. */
FileWrapper(const FileWrapper&) = delete;
FileWrapper& operator=(const FileWrapper&) = delete;
uint16_t line_length(uint32_t line) const {
if (line >= line_count())
return 0;
Optional<Error> open(const std::filesystem::path& path);
std::string get_text(Line line, Column col, Offset length);
auto start = line_start(line);
auto end = newlines[line];
return end - start;
}
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;
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_{};
};
/*class TextViewer : public Widget {
};*/
class TextEditorView : public View {
public:
TextEditorView(NavigationView& nav);
// TextEditorView(NavigationView& nav, const std::filesystem::path& path);
TextEditorView(
NavigationView& nav,
const std::filesystem::path& path);
std::string title() const override {
return "Notepad";
@@ -101,7 +129,6 @@ class TextEditorView : public View {
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{
.font = font::fixed_5x8,
.background = Color::black(),
@@ -114,21 +141,17 @@ class TextEditorView : public View {
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);
// 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.
@@ -148,11 +171,6 @@ class TextEditorView : public View {
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) */
// TODO: The scrollable view should be its own widget
// otherwise control navigation doesn't work.
@@ -161,6 +179,10 @@ class TextEditorView : public View {
"Open"};
Text text_position{
{0 * 8, 34 * 8, 24 * 8, 2 * 8},
""};
Text text_size{
{0 * 8, 36 * 8, 24 * 8, 2 * 8},
""};
};
+2 -2
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) {
+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) {
+1 -1
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.
*
@@ -315,7 +316,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);
+1
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.
*
+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
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();
+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,
+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>(); } },
});
+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__*/
+18 -5
View File
@@ -28,14 +28,27 @@ 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_; }
// 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);
+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();
+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