Compare commits

...

33 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
jLynx 215ad4fd4c 1.7.1 (#1037)
* Update past_version.txt

* Update version.txt
2023-05-22 21:21:47 +12:00
Erwin Ried 7150d95a98 cleanup (#1036) 2023-05-22 21:14:35 +12:00
gullradriel 80636c53da fix CONFIG not working if nothing changed and got less flickering by separating text output (#1034)
-fix CONFIG dysfunction if nothing had changed: this was a reminiscence of the nothread conversion
-addressed screen flickering: reduced it by separating two text output
-variables: took out unneeded userpause (reminiscence of the nothread conversion)
-solved problem with manual range not updating description on consecutive clicks
-solved problem with direction changing on 'UNLOCK/SKIPLCK'
2023-05-22 10:49:07 +02:00
jLynx 874d3c251c Update dfu_hackrf_one.bat (#1032) 2023-05-22 13:03:53 +12:00
Kyle Reed 6c541af0fd Move tests, add applicaiton tests. (#1031)
Co-authored-by: kallanreed <kallanreed@outlook.com>
2023-05-21 17:33:12 -07:00
Mark Thompson 4d1269051b Fixed debounce code to handle noisy switch transitions #989 (#1030)
* Fix debounce to handle noisy switch transitions

* Fix debounce to handle noisy switch transitions

* Fix debounce to handle noisy switch transitions

* Removed inline comment to see if Clang checker will be happy

* Test fix for supposed Clang formatting issue
2023-05-22 12:06:37 +12:00
gullradriel be1b2716d6 freq correct unit fix (#1029)
* fix units for frequency offset. It was planned to be a uint32_t since a uint16_t is obviously too short
* took the uint32_t limitation for frequency offset in account
2023-05-21 23:47:02 +02:00
Bernd Herzog 34d46a9d5d added first unit test (#1027)
* added first unit test

* improved unit test names
2023-05-21 23:08:24 +02:00
gullradriel cac3a7cf1e Settings directory fix in Level and Recon (#1028)
* removed unneeded sdcard check and directory creation
* changed settings so they use SETTINGS and a better name. Code beautication
2023-05-21 22:31:03 +02:00
gullradriel 0e86e63e97 Fixing colorisation of wait/lockwait fields (#1026)
* Fixing colorisation of wait/lockwait field
2023-05-21 20:42:54 +02:00
gullradriel c0ff5715de fixing bad range for lock field (#1024)
* fixing bad range for lock field
2023-05-21 09:11:58 +02:00
gullradriel 24ce1b9893 fixed bad copy paste from #1016, thanks @notherngineer (#1023)
fixed bad copy paste from #1016
2023-05-21 08:59:42 +02:00
jLynx edd8e7c3b9 Automatically add world map to SD card files (#1022)
* Testing

* Updated scripts

* Updated removed testing file
2023-05-21 18:42:38 +12:00
73 changed files with 9256 additions and 591 deletions
@@ -47,6 +47,12 @@ jobs:
run: mkdir ${{ github.workspace }}/build
- name: Run the Docker image
run: docker run -e VERSION_STRING=${{ steps.version_date.outputs.date }} -i -v ${{ github.workspace }}:/havoc portapack-dev
- name: Download world map
run: |
wget https://github.com/eried/portapack-mayhem/releases/download/world_map/world_map.zip
- name: Unzip world map
run: |
unzip world_map.zip -d sdcard/ADSB
- name: Create Firmware ZIP
run: |
zip -j firmware.zip build/firmware/portapack-h1_h2-mayhem.bin && cd flashing && zip -r ../firmware.zip *
@@ -31,6 +31,12 @@ jobs:
run: mkdir ${{ github.workspace }}/build
- name: Run the Docker image
run: docker run -e VERSION_STRING=${{ steps.version.outputs.version }} -i -v ${{ github.workspace }}:/havoc portapack-dev
- name: Download world map
run: |
wget https://github.com/eried/portapack-mayhem/releases/download/world_map/world_map.zip
- name: Unzip world map
run: |
unzip world_map.zip -d sdcard/ADSB
- name: Create Firmware ZIP
run: |
zip -j firmware.zip build/firmware/portapack-h1_h2-mayhem.bin && cd flashing && zip -r ../firmware.zip *
+1 -1
View File
@@ -1 +1 @@
v1.6.0
v1.7.0
+1 -1
View File
@@ -1 +1 @@
v1.7.0
v1.7.1
+2 -1
View File
@@ -18,7 +18,7 @@
# Boston, MA 02110-1301, USA.
#
cmake_minimum_required(VERSION 2.8.9)
cmake_minimum_required(VERSION 3.0)
cmake_policy(SET CMP0005 NEW)
set(CMAKE_TOOLCHAIN_FILE ${CMAKE_CURRENT_LIST_DIR}/firmware/toolchain-arm-cortex-m.cmake)
@@ -58,4 +58,5 @@ set(HACKRF_CPLD_XSVF_PATH ${HACKRF_PATH}/firmware/cpld/sgpio_if/${HACKRF_CPLD_XS
set(HACKRF_FIRMWARE_DFU_IMAGE ${hackrf_usb_BINARY_DIR}/${HACKRF_FIRMWARE_DFU_FILENAME})
set(HACKRF_FIRMWARE_BIN_IMAGE ${hackrf_usb_BINARY_DIR}/${HACKRF_FIRMWARE_BIN_FILENAME})
enable_testing()
add_subdirectory(firmware)
+6 -6
View File
@@ -1,7 +1,7 @@
GNU GENERAL PUBLIC LICENSE
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/>
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
@@ -290,8 +290,8 @@ to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
{description}
Copyright (C) {year} {fullname}
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
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
@@ -329,11 +329,11 @@ necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
{signature of Ty Coon}, 1 April 1989
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
Public License instead of this License.
-7
View File
@@ -1,7 +0,0 @@
# License
Portapack Mayhem is distributed under [GPL v3.0](LICENSE), however some sub projects might have different (GPL v3.0 compatible) licenses.
## Other Licenses (compatible with GPL v3.0)
- Most of Portapack Mayhem is distributed under GPL-2.0-or-later. [license](LICENSE.GPL-2.0-or-later)
- ChibiOS is distributed under GPL v3.0. [license](/firmware/chibios/license.txt)
- Portapack-ChibiOS is distributed under Apache-2.0 [license](http://www.apache.org/licenses/LICENSE-2.0)
-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).
+2 -1
View File
@@ -35,6 +35,7 @@ set(FIRMWARE_FILENAME ${FIRMWARE_NAME}.bin)
add_subdirectory(application)
add_subdirectory(baseband)
add_subdirectory(test)
# NOTE: Dependencies break if the .bin files aren't included in DEPENDS. WTF, CMake?
add_custom_command(
@@ -52,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;
+7 -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,10 +30,6 @@ using portapack::memory::map::backup_ram;
namespace ui {
bool LevelView::check_sd_card() {
return (sd_card::status() == sd_card::Status::Mounted) ? true : false;
}
void LevelView::focus() {
button_frequency.focus();
}
@@ -74,12 +71,6 @@ LevelView::LevelView(NavigationView& nav)
field_volume.on_change = [this](int32_t v) { this->on_headphone_volume_changed(v); };
field_volume.set_value((receiver_model.headphone_volume() - audio::headphone::volume_range().max).decibel() + 99);
// Level directory
if (check_sd_card()) { // Check to see if SD Card is mounted
make_new_directory(u"/LEVEL");
sd_card_mounted = true;
}
change_mode(NFM_MODULATION); // Start on AM
field_mode.set_by_value(NFM_MODULATION); // Reflect the mode into the manual selector
@@ -89,14 +80,12 @@ LevelView::LevelView(NavigationView& nav)
button_frequency.set_text("<" + to_string_short_freq(freq) + " MHz>");
// load auto common app settings
if (sd_card_mounted) {
auto rc = settings.load("level", &app_settings);
if (rc == SETTINGS_OK) {
field_lna.set_value(app_settings.lna);
field_vga.set_value(app_settings.vga);
field_rf_amp.set_value(app_settings.rx_amp);
receiver_model.set_rf_amp(app_settings.rx_amp);
}
auto rc = settings.load("level", &app_settings);
if (rc == SETTINGS_OK) {
field_lna.set_value(app_settings.lna);
field_vga.set_value(app_settings.vga);
field_rf_amp.set_value(app_settings.rx_amp);
receiver_model.set_rf_amp(app_settings.rx_amp);
}
button_frequency.on_select = [this, &nav](ButtonWithEncoder& button) {
+1 -2
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.
*
@@ -97,11 +98,9 @@ class LevelView : public View {
size_t change_mode(freqman_index_t mod_type);
void on_statistics_update(const ChannelStatistics& statistics);
void set_display_freq(int64_t freq);
bool check_sd_card();
int32_t db{0};
long long int MAX_UFREQ = {7200000000}; // maximum usable freq
bool sd_card_mounted = false;
rf::Frequency freq = {0};
Labels labels{
@@ -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{};
+152 -121
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,7 +30,48 @@ using portapack::memory::map::backup_ram;
namespace ui {
bool ReconView::ReconSaveFreq(const std::string& freq_file_path, size_t freq_index, bool warn_if_exists) {
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) {
field_wait.set_style(&style_blue);
} else if (wait >= 500) {
field_wait.set_style(&style_white);
} else if (wait > -500 && wait < 500) {
field_wait.set_style(&style_red);
} else if (wait <= -500) {
field_wait.set_style(&style_green);
}
// colorize lock time if in SPARSE mode as in continuous the lock_wait time is disarmed at first lock count
if (recon_match_mode == RECON_MATCH_SPARSE) {
if ((recon_lock_duration / STATS_UPDATE_INTERVAL) <= recon_lock_nb_match) {
field_lock_wait.set_style(&style_yellow);
} else {
field_lock_wait.set_style(&style_white);
}
} else {
field_lock_wait.set_style(&style_white);
}
}
bool ReconView::recon_save_freq(const std::string& freq_file_path, size_t freq_index, bool warn_if_exists) {
File recon_file;
if (frequency_list.size() == 0 || (frequency_list.size() && current_index > (int32_t)frequency_list.size()))
@@ -81,7 +123,7 @@ bool ReconView::ReconSaveFreq(const std::string& freq_file_path, size_t freq_ind
return true;
}
bool ReconView::ReconSetupLoadStrings(const std::string& source, std::string& input_file, std::string& output_file, uint32_t& recon_lock_duration, uint32_t& recon_lock_nb_match, int32_t& recon_squelch_level, uint32_t& recon_match_mode, int32_t& wait, int32_t& volume) {
bool ReconView::recon_load_config_from_sd() {
File settings_file;
size_t length, file_position = 0;
char* pos;
@@ -93,39 +135,37 @@ bool ReconView::ReconSetupLoadStrings(const std::string& source, std::string& in
uint32_t nb_params = RECON_SETTINGS_NB_PARAMS;
std::string params[RECON_SETTINGS_NB_PARAMS];
bool check_sd_card = (sd_card::status() == sd_card::Status::Mounted) ? true : false;
make_new_directory(u"SETTINGS");
if (check_sd_card) {
auto result = settings_file.open(source);
if (!result.is_valid()) {
while (it < nb_params) {
// Read a 256 bytes block from file
settings_file.seek(file_position);
memset(file_data, 0, 257);
auto read_size = settings_file.read(file_data, 256);
if (read_size.is_error())
break;
file_position += 256;
// Reset line_start to beginning of buffer
line_start = file_data;
auto result = settings_file.open(RECON_CFG_FILE);
if (!result.is_valid()) {
while (it < nb_params) {
// Read a 256 bytes block from file
settings_file.seek(file_position);
memset(file_data, 0, 257);
auto read_size = settings_file.read(file_data, 256);
if (read_size.is_error())
break;
file_position += 256;
// Reset line_start to beginning of buffer
line_start = file_data;
pos = line_start;
while ((line_end = strstr(line_start, "\x0A"))) {
length = line_end - line_start - 1;
params[it] = string(pos, length);
it++;
line_start = line_end + 1;
pos = line_start;
while ((line_end = strstr(line_start, "\x0A"))) {
length = line_end - line_start - 1;
params[it] = string(pos, length);
it++;
line_start = line_end + 1;
pos = line_start;
if (line_start - file_data >= 256)
break;
if (it >= nb_params)
break;
}
if (read_size.value() != 256)
break; // End of file
// Restart at beginning of last incomplete line
file_position -= (file_data + 256 - line_start);
if (line_start - file_data >= 256)
break;
if (it >= nb_params)
break;
}
if (read_size.value() != 256)
break; // End of file
// Restart at beginning of last incomplete line
file_position -= (file_data + 256 - line_start);
}
}
@@ -135,7 +175,7 @@ bool ReconView::ReconSetupLoadStrings(const std::string& source, std::string& in
output_file = "RECON_RESULTS";
recon_lock_duration = RECON_MIN_LOCK_DURATION;
recon_lock_nb_match = RECON_DEF_NB_MATCH;
recon_squelch_level = -14;
squelch = -14;
recon_match_mode = RECON_MATCH_CONTINUOUS;
wait = RECON_DEF_WAIT_DURATION;
volume = 40;
@@ -163,9 +203,9 @@ bool ReconView::ReconSetupLoadStrings(const std::string& source, std::string& in
recon_lock_nb_match = RECON_DEF_NB_MATCH;
if (it > 4)
recon_squelch_level = strtoll(params[4].c_str(), nullptr, 10);
squelch = strtoll(params[4].c_str(), nullptr, 10);
else
recon_squelch_level = -14;
squelch = -14;
if (it > 5)
recon_match_mode = strtoll(params[5].c_str(), nullptr, 10);
@@ -185,17 +225,19 @@ bool ReconView::ReconSetupLoadStrings(const std::string& source, std::string& in
return true;
}
bool ReconView::ReconSetupSaveStrings(const std::string& dest, const std::string& input_file, const std::string& output_file, uint32_t recon_lock_duration, uint32_t recon_lock_nb_match, int32_t recon_squelch_level, uint32_t recon_match_mode, int32_t wait, int32_t volume) {
bool ReconView::recon_save_config_to_sd() {
File settings_file;
auto result = settings_file.create(dest);
make_new_directory(u"SETTINGS");
auto result = settings_file.create(RECON_CFG_FILE);
if (result.is_valid())
return false;
settings_file.write_line(input_file);
settings_file.write_line(output_file);
settings_file.write_line(to_string_dec_uint(recon_lock_duration));
settings_file.write_line(to_string_dec_uint(recon_lock_nb_match));
settings_file.write_line(to_string_dec_int(recon_squelch_level));
settings_file.write_line(to_string_dec_int(squelch));
settings_file.write_line(to_string_dec_uint(recon_match_mode));
settings_file.write_line(to_string_dec_int(wait));
settings_file.write_line(to_string_dec_int(volume));
@@ -207,10 +249,6 @@ void ReconView::audio_output_start() {
this->on_headphone_volume_changed((receiver_model.headphone_volume() - audio::headphone::volume_range().max).decibel() + 99);
}
bool ReconView::check_sd_card() {
return (sd_card::status() == sd_card::Status::Mounted) ? true : false;
}
void ReconView::recon_redraw() {
if (last_rssi_min != rssi.get_min() || last_rssi_med != rssi.get_avg() || last_rssi_max != rssi.get_max()) {
last_rssi_min = rssi.get_min();
@@ -220,18 +258,16 @@ void ReconView::recon_redraw() {
}
if (last_entry.frequency_a != freq) {
last_entry.frequency_a = freq;
big_display.set("FREQ: " + to_string_short_freq(freq) + " MHz");
big_display.set("FREQ:" + to_string_short_freq(freq) + " MHz");
}
if (last_db != db || last_list_size != frequency_list.size() || last_freq_lock != freq_lock || last_nb_match != recon_lock_nb_match) {
last_freq_lock = freq;
last_list_size = frequency_list.size();
last_db = db;
if (last_nb_match != recon_lock_nb_match || last_freq_lock != freq_lock) {
last_freq_lock = freq_lock;
last_nb_match = recon_lock_nb_match;
text_max.set("/" + to_string_dec_uint(frequency_list.size()) + " " + to_string_dec_int(db) + " db " + to_string_dec_uint(freq_lock) + "/" + to_string_dec_uint(recon_lock_nb_match));
text_nb_locks.set(to_string_dec_uint(freq_lock) + "/" + to_string_dec_uint(recon_lock_nb_match));
if (freq_lock == 0) {
// NO FREQ LOCK, ONGOING STANDARD SCANNING
big_display.set_style(&style_white);
if (!userpause)
if (recon)
button_pause.set_text("<PAUSE>");
else
button_pause.set_text("<RESUME>");
@@ -242,13 +278,17 @@ void ReconView::recon_redraw() {
} else if (freq_lock >= recon_lock_nb_match) {
big_display.set_style(&style_green);
button_pause.set_text("<UNLOCK>");
// FREQ IS STRONG: GREEN and recon will pause when on_statistics_update()
if ((!scanner_mode) && autosave && frequency_list.size() > 0) {
ReconSaveFreq(freq_file_path, current_index, false);
recon_save_freq(freq_file_path, current_index, false);
}
}
}
if (last_db != db || last_list_size != frequency_list.size()) {
last_list_size = frequency_list.size();
last_db = db;
text_max.set("/" + to_string_dec_uint(frequency_list.size()) + " " + to_string_dec_int(db) + " db");
}
}
void ReconView::handle_retune() {
@@ -286,7 +326,7 @@ void ReconView::handle_retune() {
if (last_index != current_index) {
last_index = current_index;
if ((int32_t)frequency_list.size() && current_index < (int32_t)frequency_list.size() && frequency_list[current_index].type == RANGE) {
if (update_ranges) {
if (update_ranges && !manual_mode) {
button_manual_start.set_text(to_string_short_freq(frequency_list[current_index].frequency_a));
frequency_range.min = frequency_list[current_index].frequency_a;
if (frequency_list[current_index].frequency_b != 0) {
@@ -324,9 +364,9 @@ void ReconView::focus() {
}
ReconView::~ReconView() {
ReconSetupSaveStrings("RECON/RECON.CFG", input_file, output_file, recon_lock_duration, recon_lock_nb_match, squelch, recon_match_mode, wait, field_volume.value());
// save app settings
// save app config
recon_save_config_to_sd();
// save app common settings
settings.save("recon", &app_settings);
audio::output::stop();
@@ -345,13 +385,14 @@ ReconView::ReconView(NavigationView& nav)
&field_squelch,
&field_wait,
&field_lock_wait,
&button_recon_setup,
&button_config,
&button_scanner_mode,
&button_looking_glass,
&file_name,
&rssi,
&text_cycle,
&text_max,
&text_nb_locks,
&desc_cycle,
&big_display,
&freq_stats,
@@ -370,11 +411,6 @@ ReconView::ReconView(NavigationView& nav)
&button_mic_app,
&button_remove});
// Recon directory
if (check_sd_card()) { // Check to see if SD Card is mounted
make_new_directory(u"/RECON");
sd_card_mounted = true;
}
def_step = 0;
// HELPER: Pre-setting a manual range, based on stored frequency
rf::Frequency stored_freq = persistent_memory::tuned_frequency();
@@ -398,7 +434,6 @@ ReconView::ReconView(NavigationView& nav)
load_ranges = persistent_memory::recon_load_ranges();
load_hamradios = persistent_memory::recon_load_hamradios();
update_ranges = persistent_memory::recon_update_ranges_when_recon();
field_volume.set_value(volume);
if (sd_card_mounted) {
// load auto common app settings
@@ -412,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;
@@ -420,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;
@@ -495,7 +532,7 @@ ReconView::ReconView(NavigationView& nav)
}
button_pause.set_text("<PAUSE>"); // Show button for non continuous stop
} else {
if (userpause) {
if (!recon) {
recon_resume();
} else {
recon_pause();
@@ -533,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;
}
@@ -559,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++) {
@@ -573,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;
@@ -637,10 +676,10 @@ ReconView::ReconView(NavigationView& nav)
};
button_manual_recon.on_select = [this](Button&) {
button_remove.set_text("DELETE");
scanner_mode = false;
manual_mode = true;
recon_pause();
if (!frequency_range.min || !frequency_range.max) {
nav_.display_modal("Error", "Both START and END freqs\nneed a value");
} else if (frequency_range.min > frequency_range.max) {
@@ -655,10 +694,10 @@ ReconView::ReconView(NavigationView& nav)
manual_freq_entry.type = RANGE;
manual_freq_entry.description =
"R " + to_string_short_freq(frequency_range.min) + ">" + to_string_short_freq(frequency_range.max) + " S" // current Manual range
+ to_string_short_freq(freqman_entry_get_step_value(def_step)).erase(0, 1); // euquiq: lame kludge to reduce spacing in step freq
manual_freq_entry.frequency_a = frequency_range.min; // min range val
manual_freq_entry.frequency_b = frequency_range.max; // max range val
to_string_short_freq(frequency_range.min).erase(0, 1) + ">" + to_string_short_freq(frequency_range.max).erase(0, 1) + " S:" // current Manual range
+ freqman_entry_get_step_string_short(def_step); // euquiq: lame kludge to reduce spacing in step freq
manual_freq_entry.frequency_a = frequency_range.min; // min range val
manual_freq_entry.frequency_b = frequency_range.max; // max range val
manual_freq_entry.modulation = -1;
manual_freq_entry.bandwidth = -1;
manual_freq_entry.step = def_step;
@@ -677,7 +716,11 @@ ReconView::ReconView(NavigationView& nav)
file_name.set_style(&style_white);
file_name.set("MANUAL RANGE RECON");
desc_cycle.set_style(&style_white);
desc_cycle.set("MANUAL RANGE RECON");
last_entry.modulation = -1;
last_entry.bandwidth = -1;
last_entry.step = -1;
last_index = -1;
current_index = 0;
freq = manual_freq_entry.frequency_a;
@@ -696,7 +739,7 @@ ReconView::ReconView(NavigationView& nav)
button_dir.set_text("FW>");
}
timer = 0;
if (userpause)
if (!recon)
recon_resume();
};
@@ -724,7 +767,7 @@ ReconView::ReconView(NavigationView& nav)
button_add.on_select = [this](ButtonWithEncoder&) { // frequency_list[current_index]
if (!scanner_mode) {
ReconSaveFreq(freq_file_path, current_index, true);
recon_save_freq(freq_file_path, current_index, true);
}
};
@@ -739,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) {
@@ -752,21 +797,19 @@ ReconView::ReconView(NavigationView& nav)
}
};
button_recon_setup.on_select = [this, &nav](Button&) {
audio::output::stop();
frequency_list.clear();
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";
recon_lock_duration = strtol(result[2].c_str(), nullptr, 10);
recon_lock_nb_match = strtol(result[3].c_str(), nullptr, 10);
recon_match_mode = strtol(result[4].c_str(), nullptr, 10);
ReconSetupSaveStrings("RECON/RECON.CFG", input_file, output_file, recon_lock_duration, recon_lock_nb_match, squelch, recon_match_mode, wait, field_volume.value());
recon_save_config_to_sd();
autosave = persistent_memory::recon_autosave_freqs();
autostart = persistent_memory::recon_autostart_recon();
@@ -775,48 +818,30 @@ ReconView::ReconView(NavigationView& nav)
load_freqs = persistent_memory::recon_load_freqs();
load_ranges = persistent_memory::recon_load_ranges();
load_hamradios = persistent_memory::recon_load_hamradios();
update_ranges = persistent_memory::recon_update_ranges_when_recon();
field_wait.set_value(wait);
field_lock_wait.set_value(recon_lock_duration);
colorize_waits();
frequency_file_load(false);
if (autostart) {
recon_resume();
} else {
recon_pause();
}
if (userpause != true) {
recon_resume();
}
};
};
field_wait.on_change = [this](int32_t v) {
wait = v;
if (wait == 0) {
field_wait.set_style(&style_blue);
} else if (wait >= 500) {
field_wait.set_style(&style_white);
} else if (wait > -500 && wait < 500) {
field_wait.set_style(&style_red);
} else if (wait <= -500) {
field_wait.set_style(&style_green);
}
colorize_waits();
};
field_lock_wait.on_change = [this](uint32_t v) {
recon_lock_duration = v;
if (recon_match_mode == RECON_MATCH_CONTINUOUS) {
if ((v / STATS_UPDATE_INTERVAL) > recon_lock_nb_match) {
field_lock_wait.set_style(&style_white);
} else if ((v / STATS_UPDATE_INTERVAL) == recon_lock_nb_match) {
field_lock_wait.set_style(&style_yellow);
}
} else // RECON_MATCH_SPARSE
{
field_lock_wait.set_style(&style_white);
}
colorize_waits();
};
field_squelch.on_change = [this](int32_t v) {
@@ -833,12 +858,14 @@ ReconView::ReconView(NavigationView& nav)
file_name.set("=>");
// Loading input and output file from settings
ReconSetupLoadStrings("RECON/RECON.CFG", input_file, output_file, recon_lock_duration, recon_lock_nb_match, squelch, recon_match_mode, wait, volume);
recon_load_config_from_sd();
freq_file_path = "/FREQMAN/" + output_file + ".TXT";
field_squelch.set_value(squelch);
field_wait.set_value(wait);
field_lock_wait.set_value(recon_lock_duration);
colorize_waits();
field_volume.set_value((receiver_model.headphone_volume() - audio::headphone::volume_range().max).decibel() + 99);
// fill modulation and step options
@@ -860,7 +887,6 @@ ReconView::ReconView(NavigationView& nav)
} else {
recon_pause();
}
recon_redraw();
}
@@ -881,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);
@@ -935,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:
@@ -958,7 +978,7 @@ void ReconView::frequency_file_load(bool stop_all_before) {
break;
}
text_cycle.set_text(to_string_dec_uint(current_index + 1, 3));
if (update_ranges) {
if (update_ranges && !manual_mode) {
button_manual_start.set_text(to_string_short_freq(frequency_list[current_index].frequency_a));
frequency_range.min = frequency_list[current_index].frequency_a;
if (frequency_list[current_index].frequency_b != 0) {
@@ -977,8 +997,20 @@ 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 (!userpause) {
if (recon) {
if (!timer) {
status = 0;
continuous_lock = false;
@@ -1177,7 +1209,6 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
break;
}
}
// send a pause message with the right freq
if (has_looped && !continuous) {
recon_pause();
}
@@ -1195,7 +1226,6 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
void ReconView::recon_pause() {
timer = 0;
freq_lock = 0;
userpause = true;
continuous_lock = false;
recon = false;
@@ -1208,7 +1238,6 @@ void ReconView::recon_pause() {
void ReconView::recon_resume() {
timer = 0;
freq_lock = 0;
userpause = false;
continuous_lock = false;
recon = true;
@@ -1222,7 +1251,8 @@ void ReconView::on_index_delta(int32_t v) {
if (v > 0) {
fwd = true;
button_dir.set_text("FW>");
} else {
}
if (v < 0) {
fwd = false;
button_dir.set_text("<RW");
}
@@ -1237,7 +1267,8 @@ void ReconView::on_stepper_delta(int32_t v) {
if (v > 0) {
fwd = true;
button_dir.set_text("FW>");
} else {
}
if (v < 0) {
fwd = false;
button_dir.set_text("<RW");
}
+20 -8
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.
*
@@ -42,6 +43,8 @@
namespace ui {
#define RECON_CFG_FILE "SETTINGS/recon.cfg"
class ReconView : public View {
public:
ReconView(NavigationView& nav);
@@ -98,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);
@@ -109,13 +114,13 @@ class ReconView : public View {
void on_headphone_volume_changed(int32_t v);
void on_index_delta(int32_t v);
void on_stepper_delta(int32_t v);
void colorize_waits();
void recon_redraw();
void handle_retune();
void handle_coded_squelch(const uint32_t value);
bool ReconSetupLoadStrings(const std::string& source, std::string& input_file, std::string& output_file, uint32_t& recon_lock_duration, uint32_t& recon_lock_nb_match, int32_t& recon_squelch_level, uint32_t& recon_match_mode, int32_t& wait, int32_t& volume);
bool ReconSetupSaveStrings(const std::string& dest, const std::string& input_file, const std::string& output_file, uint32_t recon_lock_duration, uint32_t recon_lock_nb_match, int32_t recon_squelch_level, uint32_t recon_match_mode, int32_t wait, int32_t volume);
bool ReconSaveFreq(const std::string& freq_file_path, size_t index, bool warn_if_exists);
bool recon_load_config_from_sd();
bool recon_save_config_to_sd();
bool recon_save_freq(const std::string& freq_file_path, size_t index, bool warn_if_exists);
jammer::jammer_range_t frequency_range{false, 0, MAX_UFREQ}; // perfect for manual recon task too...
int32_t squelch{0};
int32_t db{0};
@@ -123,10 +128,11 @@ class ReconView : public View {
int32_t wait{RECON_DEF_WAIT_DURATION}; // in msec. if > 0 wait duration after a lock, if < 0 duration is set to 'wait' unless there is no more activity
freqman_db frequency_list = {};
int32_t current_index{0};
bool userpause{false};
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};
@@ -227,15 +233,21 @@ class ReconView : public View {
};
RSSI rssi{
{0 * 16, 2 * 16, SCREEN_W - 8 * 8 + 4, 16},
{0 * 16, 2 * 16, SCREEN_W - 8 * 8 + 4, 14},
};
ButtonWithEncoder text_cycle{
{0, 3 * 16, 4 * 8, 16},
""};
// "/XXX -XXX db" => 12 chars max
Text text_max{
{4 * 8, 3 * 16, SCREEN_W - 7 * 8 - 4 * 8, 16},
{4 * 8, 3 * 16, 12 * 8, 16},
};
// "XX/XX" => 5 chars max
Text text_nb_locks{
{16 * 8, 3 * 16, 5 * 8, 16},
};
Text desc_cycle{
@@ -263,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.
*
@@ -92,7 +93,7 @@ ReconSetupViewMain::ReconSetupViewMain(NavigationView& nav, Rect parent_rect, st
};
};
void ReconSetupViewMain::Save(std::string& input_file, std::string& output_file) {
void ReconSetupViewMain::save(std::string& input_file, std::string& output_file) {
persistent_memory::set_recon_autosave_freqs(checkbox_autosave_freqs.value());
persistent_memory::set_recon_autostart_recon(checkbox_autostart_recon.value());
persistent_memory::set_recon_continuous(checkbox_continuous.value());
@@ -100,7 +101,7 @@ void ReconSetupViewMain::Save(std::string& input_file, std::string& output_file)
input_file = _input_file;
output_file = _output_file;
};
void ReconSetupViewMore::Save(uint32_t& recon_lock_duration, uint32_t& recon_lock_nb_match, uint32_t& recon_match_mode) {
void ReconSetupViewMore::save(uint32_t& recon_lock_duration, uint32_t& recon_lock_nb_match, uint32_t& recon_match_mode) {
persistent_memory::set_recon_load_freqs(checkbox_load_freqs.value());
persistent_memory::set_recon_load_ranges(checkbox_load_ranges.value());
persistent_memory::set_recon_load_hamradios(checkbox_load_hamradios.value());
@@ -162,8 +163,8 @@ ReconSetupView::ReconSetupView(
&button_save});
button_save.on_select = [this, &nav](Button&) {
viewMain.Save(input_file, output_file);
viewMore.Save(recon_lock_duration, recon_lock_nb_match, recon_match_mode);
viewMain.save(input_file, output_file);
viewMore.save(recon_lock_duration, recon_lock_nb_match, recon_match_mode);
std::vector<std::string> messages;
messages.push_back(input_file);
messages.push_back(output_file);
+20 -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.
*
@@ -66,7 +67,7 @@ namespace ui {
class ReconSetupViewMain : public View {
public:
ReconSetupViewMain(NavigationView& nav, Rect parent_rect, std::string input_file, std::string output_file);
void Save(std::string& input_file, std::string& output_file);
void save(std::string& input_file, std::string& output_file);
void focus() override;
private:
@@ -111,7 +112,7 @@ class ReconSetupViewMore : public View {
public:
ReconSetupViewMore(NavigationView& nav, Rect parent_rect, uint32_t _recon_lock_duration, uint32_t _recon_lock_nb_match, uint32_t _recon_match_mode);
void Save(uint32_t& recon_lock_duration, uint32_t& recon_lock_nb_match, uint32_t& recon_match_mode);
void save(uint32_t& recon_lock_duration, uint32_t& recon_lock_nb_match, uint32_t& recon_match_mode);
void focus() override;
@@ -139,20 +140,20 @@ class ReconSetupViewMore : public View {
{1 * 8, 102},
3,
"auto update m-ranges"};
NumberField field_recon_lock_duration{
{1 * 8, 132}, // position X , Y
4, // number of displayed digits (even empty)
{STATS_UPDATE_INTERVAL, RECON_MAX_LOCK_DURATION}, // range of number
STATS_UPDATE_INTERVAL, // rotary encoder increment
' ', // filling character
false // can loop
};
Text text_recon_lock_duration{
{1 * 8, 132, 22 * 8, 22},
" ms (lock duration)"};
NumberField field_recon_lock_duration{
{1 * 8, 132}, // position X , Y
4, // number of displayed digits (even empty)
{-RECON_MAX_LOCK_DURATION, RECON_MAX_LOCK_DURATION}, // range of number
STATS_UPDATE_INTERVAL, // rotary encoder increment
' ', // filling character
false // can loop
};
Text text_recon_lock_nb{
{1 * 8, 162, 25 * 8, 22},
" x (nb lock to match freq)"};
NumberField field_recon_lock_nb_match{
{1 * 8, 162},
4,
@@ -160,6 +161,11 @@ class ReconSetupViewMore : public View {
1,
' ',
false};
Text text_recon_lock_nb{
{1 * 8, 162, 25 * 8, 22},
" x (nb lock to match freq)"};
OptionsField field_recon_match_mode{
{1 * 8, 192},
20, // CONTINUOUS MATCH MODE / SPARSE TIMED MATCH MODE
@@ -195,6 +201,7 @@ class ReconSetupView : public View {
TabView tab_view{
{"Main", Color::cyan(), &viewMain},
{"More", Color::green(), &viewMore}};
Button button_save{
{9 * 8, 255, 14 * 8, 40},
"SAVE"};
+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) {
+46 -7
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);
};
@@ -398,8 +408,8 @@ SetFrequencyCorrectionView::SetFrequencyCorrectionView(NavigationView& nav) {
button_freq_rx_correction.on_select = [this, &nav](Button& button) {
auto new_view = nav.push<FrequencyKeypadView>(portapack::persistent_memory::config_converter_freq());
new_view->on_changed = [this, &button](rf::Frequency f) {
if (f > 4000000)
f = 4000000;
if (f >= MAX_FREQ_CORRECTION)
f = MAX_FREQ_CORRECTION;
portapack::persistent_memory::set_config_freq_rx_correction(f);
// Retune to take converter change in account
receiver_model.set_tuning_frequency(portapack::persistent_memory::tuned_frequency());
@@ -411,8 +421,8 @@ SetFrequencyCorrectionView::SetFrequencyCorrectionView(NavigationView& nav) {
button_freq_tx_correction.on_select = [this, &nav](Button& button) {
auto new_view = nav.push<FrequencyKeypadView>(portapack::persistent_memory::config_converter_freq());
new_view->on_changed = [this, &button](rf::Frequency f) {
if (f > 4000000)
f = 4000000;
if (f >= MAX_FREQ_CORRECTION)
f = MAX_FREQ_CORRECTION;
portapack::persistent_memory::set_config_freq_tx_correction(f);
// Retune to take converter change in account
receiver_model.set_tuning_frequency(portapack::persistent_memory::tuned_frequency());
@@ -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
}
+35
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.
*
@@ -33,6 +34,8 @@
namespace ui {
#define MAX_FREQ_CORRECTION 4294967295 // maximum possible for an uint32_t
struct SetDateTimeModel {
uint16_t year;
uint8_t month;
@@ -429,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.
*
+46 -7
View File
@@ -23,17 +23,56 @@
#include "utility.hpp"
// Returns TRUE if button state changed (after debouncing)
bool Debounce::feed(const uint8_t bit) {
history_ = (history_ << 1) | (bit & 1);
if (history_ == 0b00001111) {
state_ = 1;
return true;
}
if (history_ == 0b11110000) {
state_ = 0;
return true;
// "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 {
// Previous button state was 1 (pressed);
// Has button been released for DEBOUNCE_COUNT ticks?
if ((history_ & DEBOUNCE_MASK) == 0) {
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;
}
+15
View File
@@ -24,6 +24,14 @@
#include <cstdint>
// consecutive # of times button input must be same (<=8)
#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);
@@ -32,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 {
+6 -7
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.
@@ -206,7 +205,7 @@ int32_t ReceiverModel::tuning_offset() {
}
void ReceiverModel::update_tuning_frequency() {
radio::set_tuning_frequency(persistent_memory::tuned_frequency());
radio::set_tuning_frequency(persistent_memory::tuned_frequency() + tuning_offset());
}
void ReceiverModel::update_antenna_bias() {
@@ -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>(); } },
});
+202
View File
@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+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_;
@@ -297,11 +297,14 @@ struct data_t {
int64_t converter_frequency_offset;
// frequency correction
int16_t frequency_rx_correction;
uint32_t frequency_rx_correction;
bool updown_frequency_rx_correction;
int16_t frequency_tx_correction;
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) {
}
};
@@ -795,19 +799,27 @@ bool config_freq_rx_correction_updown() {
void set_freq_rx_correction_updown(bool v) {
data->updown_frequency_rx_correction = v;
}
int16_t config_freq_tx_correction() {
uint32_t config_freq_tx_correction() {
return data->frequency_tx_correction;
}
int16_t config_freq_rx_correction() {
uint32_t config_freq_rx_correction() {
return data->frequency_rx_correction;
}
void set_config_freq_tx_correction(uint16_t v) {
void set_config_freq_tx_correction(uint32_t v) {
data->frequency_tx_correction = v;
}
void set_config_freq_rx_correction(uint16_t v) {
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. */
@@ -189,16 +196,18 @@ bool config_freq_tx_correction_updown();
void set_freq_tx_correction_updown(bool v);
bool config_freq_rx_correction_updown();
void set_freq_rx_correction_updown(bool v);
int16_t config_freq_tx_correction();
int16_t config_freq_rx_correction();
void set_config_freq_tx_correction(uint16_t v);
void set_config_freq_rx_correction(uint16_t v);
uint32_t config_freq_tx_correction();
uint32_t config_freq_rx_correction();
void set_config_freq_tx_correction(uint32_t v);
void set_config_freq_rx_correction(uint32_t v);
void set_clock_hidden(bool v);
void set_clock_with_date(bool v);
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);
+30
View File
@@ -0,0 +1,30 @@
# Copyright (C) 2023 Bernd Herzog, 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.
#
project(tests)
set(DOCTESTINC ${PROJECT_SOURCE_DIR}/include)
enable_testing()
add_subdirectory(application)
add_subdirectory(baseband)
add_custom_target(build_tests)
add_dependencies(build_tests application_test baseband_test)
+71
View File
@@ -0,0 +1,71 @@
# Copyright (C) 2023 Bernd Herzog, 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.
#
project(application_test)
enable_language(C CXX ASM)
include(${CHIBIOS_PORTAPACK}/boards/PORTAPACK_APPLICATION/board.cmake)
include(${CHIBIOS_PORTAPACK}/os/hal/platforms/LPC43xx_M0/platform.cmake)
include(${CHIBIOS}/os/hal/hal.cmake)
include(${CHIBIOS_PORTAPACK}/os/ports/GCC/ARMCMx/LPC43xx_M0/port.cmake)
include(${CHIBIOS}/os/kernel/kernel.cmake)
include(${CHIBIOS_PORTAPACK}/os/various/fatfs_bindings/fatfs.cmake)
include(${CHIBIOS}/test/test.cmake)
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
)
target_include_directories(application_test PRIVATE
${DOCTESTINC}
${COMMON}
${PORTINC}
${KERNINC}
${TESTINC}
${HALINC}
${PLATFORMINC}
${BOARDINC}
${CHIBIOS}/os/various
${BASEBAND}
)
target_compile_options(application_test PRIVATE
-DLPC43XX
-DLPC43XX_M0
-D__NEWLIB__
-DHACKRF_ONE
-DTOOLCHAIN_GCC
-DTOOLCHAIN_GCC_ARM
-D_RANDOM_TCC=0
-DVERSION_STRING=\"${VERSION}\"
${USE_CPPOPT}
${USE_OPT}
${CPPWARN}
)
add_test(NAME application_test
COMMAND application_test
)
+23
View File
@@ -0,0 +1,23 @@
/*
* Copyright (C) 2023 Bernd Herzog
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
#include "doctest.h"
+27
View File
@@ -0,0 +1,27 @@
/*
* 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"
TEST_CASE("Testing test framework") {
REQUIRE(1 == 1);
REQUIRE_FALSE(1 == 2);
}
@@ -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();
@@ -0,0 +1,42 @@
/*
* 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 "optional.hpp"
TEST_SUITE_BEGIN("optional");
TEST_CASE("Default instance should not be valid.") {
Optional<int> o;
REQUIRE_FALSE(o.is_valid());
}
TEST_CASE("Instance with value should be valid.") {
Optional<int> o{1};
REQUIRE(o.is_valid());
}
TEST_CASE("value() should return value.") {
Optional<int> o{1};
REQUIRE(o.value() == 1);
}
TEST_SUITE_END();
+66
View File
@@ -0,0 +1,66 @@
# Copyright (C) 2023 Bernd Herzog, 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.
#
project(baseband_test)
enable_language(C CXX ASM)
include(${CHIBIOS_PORTAPACK}/boards/PORTAPACK_BASEBAND/board.cmake)
include(${CHIBIOS_PORTAPACK}/os/hal/platforms/LPC43xx_M4/platform.cmake)
include(${CHIBIOS}/os/hal/hal.cmake)
include(${CHIBIOS_PORTAPACK}/os/ports/GCC/ARMCMx/LPC43xx_M4/port.cmake)
include(${CHIBIOS}/os/kernel/kernel.cmake)
include(${CHIBIOS}/test/test.cmake)
set(CMAKE_CXX_COMPILER g++)
add_executable(baseband_test EXCLUDE_FROM_ALL
${PROJECT_SOURCE_DIR}/main.cpp
${PROJECT_SOURCE_DIR}/dsp_fft_test.cpp
${COMMON}/dsp_fft.cpp
)
target_include_directories(baseband_test PRIVATE
${DOCTESTINC}
${COMMON}
${PORTINC}
${KERNINC}
${TESTINC}
${HALINC}
${PLATFORMINC}
${BOARDINC}
${CHIBIOS}/os/various
${BASEBAND}
)
target_compile_options(baseband_test PRIVATE
-DLPC43XX
-DLPC43XX_M4
-D__NEWLIB__
-DHACKRF_ONE
-DTOOLCHAIN_GCC
-DTOOLCHAIN_GCC_ARM
-D_RANDOM_TCC=0
-DVERSION_STRING=\"${VERSION}\"
)
add_test(NAME baseband_test
COMMAND baseband_test
)
+87
View File
@@ -0,0 +1,87 @@
/*
* Copyright (C) 2023 Bernd Herzog
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include "dsp_fft.hpp"
#include "doctest.h"
TEST_CASE("ifft successfully calculates dc on zero frequency") {
uint32_t fft_width = 8;
complex16_t* v = new complex16_t[fft_width];
complex16_t* tmp = new complex16_t[fft_width];
v[0] = {1024, 0}; // DC bin
v[1] = {0, 0};
v[2] = {0, 0};
v[3] = {0, 0};
v[4] = {0, 0};
v[5] = {0, 0};
v[6] = {0, 0};
v[7] = {0, 0};
ifft<complex16_t>(v, fft_width, tmp);
CHECK(v[0].real() == 1024);
CHECK(v[1].real() == 1024);
CHECK(v[2].real() == 1024);
CHECK(v[3].real() == 1024);
CHECK(v[4].real() == 1024);
CHECK(v[5].real() == 1024);
CHECK(v[6].real() == 1024);
CHECK(v[7].real() == 1024);
for (uint32_t i = 0; i < fft_width; i++)
CHECK(v[i].imag() == 0);
free(v);
free(tmp);
}
TEST_CASE("ifft successfully calculates pure sine of half the sample rate") {
uint32_t fft_width = 8;
complex16_t* v = new complex16_t[fft_width];
complex16_t* tmp = new complex16_t[fft_width];
v[0] = {0, 0};
v[1] = {0, 0};
v[2] = {0, 0};
v[3] = {0, 0};
v[4] = {1024, 0}; // sample rate /2 bin
v[5] = {0, 0};
v[6] = {0, 0};
v[7] = {0, 0};
ifft<complex16_t>(v, fft_width, tmp);
CHECK(v[0].real() == 1024);
CHECK(v[1].real() == -1024);
CHECK(v[2].real() == 1024);
CHECK(v[3].real() == -1024);
CHECK(v[4].real() == 1024);
CHECK(v[5].real() == -1024);
CHECK(v[6].real() == 1024);
CHECK(v[7].real() == -1024);
for (uint32_t i = 0; i < fft_width; i++)
CHECK(v[i].imag() == 0);
free(v);
free(tmp);
}
+23
View File
@@ -0,0 +1,23 @@
/*
* Copyright (C) 2023 Bernd Herzog
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
#include "doctest.h"
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -14,6 +14,6 @@ echo.
pause
echo.
dfu-util-static.exe --device 1fc9:000c --download hackrf_one_usb.dfu --reset
"utils/dfu-util-static.exe" --device 1fc9:000c --download "utils/hackrf_one_usb.dfu" --reset
echo.
pause
+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