Compare commits

...

58 Commits

Author SHA1 Message Date
jLynx d326ec831c 1.7.2 (#1132)
* Update version.txt

* Update past_version.txt
2023-06-08 17:38:48 +12:00
dc2dc faac5b45c6 Update POCSAG.TXT (#1127)
Added Dapnet frequency for Hams Germany
2023-06-08 06:13:11 +02:00
dc2dc 5e930a7717 Update HAM_2M_BAND.TXT (#1128)
wrong Frequency for Ham Band 2 Meters
2023-06-08 06:12:08 +02:00
dc2dc aff5e22843 Update RADIOSONDE.TXT (#1129)
https://radiosondy.info/
added frequencies and added regions
2023-06-08 06:10:36 +02:00
Mark Thompson 344a8dc0a0 Fix crash when empty FreqMan files (#1113) and Fix missing Comma when editing text (#1125) (#1131)
* Fix crash issues with empty FreqMan files
* Added ability to delete empty files
* Fix missing Comma character when editing text #1125
2023-06-08 05:46:07 +02:00
gullradriel 7576432e44 Fixing file too big,setting VHF as hamradio entry since it's scanning a tx and a rx (#1124)
Co-authored-by: GullCode <gullradriel@hotmail.com>
2023-06-08 10:53:25 +12:00
E.T 3ddc753c88 Pmem reset (#1115)
* reset config when holding down select durring boot

it still need to be tripple checked if adc or control init really doesnt use any pmem

* one less magic number :)

* dual key action left+right for pmem reset

---------

Co-authored-by: Eisenberger Tamas <e.tamas@iwstudio.hu>
2023-06-08 10:52:29 +12:00
E.T d3a7414e51 pmem debug (#1112)
* pmem debug

* remove std::to_string

* fix review findings

---------

Co-authored-by: Eisenberger Tamas <e.tamas@iwstudio.hu>
2023-06-07 22:32:44 +02:00
gullradriel 1406c81e03 fix for save freq and category position (#1120)
* fix for save freq and category position
* applying conversation fixes, and save keep freqs when changing category
2023-06-07 18:05:12 +02:00
Kyle Reed 0f28fefc82 Consolidate styles (#1116)
* Consolidate styles into a Styles class and clean up code

* Format

* Add style_bg_dark_grey for nav bar

* Fix bugs found in PR

* Rename styles

* Add bg_white style

---------

Co-authored-by: kallanreed <kallanreed@outlook.com>
Co-authored-by: gullradriel <3157857+gullradriel@users.noreply.github.com>
2023-06-07 08:33:32 -07:00
E.T 496b124baf fix missmatched deletes in spectrum painter (#1121) 2023-06-07 15:41:30 +02:00
gullradriel 5a5445e980 Dsp fatfs fix (#1118)
* fixing overflow by one in ../firmware/baseband/dsp_goertzel.cpp by expanding the original array size
* fix underflow by -1 if seq is not stopping the loop
2023-06-07 11:25:35 +02:00
gullradriel eecdd3acda fixing edit freq,edit desc, delete freq (#1117)
* fixing edit freq,edit desc, delete freq
* fixing scrolling issues
* fixing indent
2023-06-07 10:17:55 +02:00
gullradriel c66df8c807 add back correct highlight to list (#1114) 2023-06-07 10:17:29 +02:00
Mark Thompson f2a7617b1b WFM bandwidth widget dial direction (clockwise==increasing) & OptionsField widget change (circular list) (#1109)
* Put WFM bandwidth options in ascending order

* Put WFM bandwidth options in ascending order

* Put WFM bandwidth options in ascending order

* Put WFM bandwidth options in ascending order

* Put WFM bandwidth options in ascending order

* Clang formatting attempt

* OptionsField wrapping - for your consideration

Perhaps if OptionsWidget supported wrapping, then it would be easier to dial through all available options without having to know which direction to turn the dial.
2023-06-06 18:46:08 +02:00
Kyle Reed af448cf444 Persist audio (#1110)
* Add credits
* Persist audio, add volume to apps
* Remove comment
* Hack around apparent alignment problem
2023-06-05 20:09:50 +02:00
E.T 9c39061590 Touch fix bl timeout (#1108)
* Touch debug from kallanreed

* Revert "init commit for fix touchscreen bad corner (#1071)"

This reverts commit 53fcdedb88.

* increase touch sensitivity by lowering the threshold

---------

Co-authored-by: Eisenberger Tamas <e.tamas@iwstudio.hu>
2023-06-04 22:41:55 +02:00
Kyle Reed 28319652c1 Add AudioVolumeField -- cleanup (#1107)
* WIP Adding AudioVolumeField

* Fix build break

* Add Bernd to about

---------

Co-authored-by: kallanreed <kallanreed@outlook.com>
2023-06-04 12:56:46 -07:00
gullradriel 7e8a139732 moved pmem setting restore before top bar is loaded (#1105)
* moved pmem setting restore before top bar is loaded
* added a check pmem from sdcard function, replaced occurences, added a save to file on back button
* Added defines for flag file and dump file, changed test func name to should_use_sdcard_for_pmem, used file_exists, removed save and load pmem parameter
* changed a text to a more comprehesible one
2023-06-04 21:25:25 +02:00
Mark Thompson e3e179e380 Increase text brightness for aged flights in ADS-B RX (#1106) 2023-06-04 16:56:53 +02:00
Mark Thompson 4f768140c1 Disallow Focus positioning to Empty lists #1097 (#1104)
* Disallow Focus positioning to Empty lists #1097

* Disallow Focus positioning to Empty lists #1097
2023-06-03 23:01:53 -07:00
Kyle Reed e5728b3501 Fileman default open and Screenshot viewer (#1102)
* WIP expensive way

* Add default file handling to fileman

* Remove screenshot_reader and tests

* Read data in chunks

* Format

* Fix error text position

* PR feedback

---------

Co-authored-by: kallanreed <kallanreed@outlook.com>
2023-06-03 19:26:39 -07:00
gullradriel f66f438487 Recon/FreqMan set limit in load_freqman_file_ex (#1098)
* changed a hardcoded value by a define, changed the order for the database clean so it happens first, changed load_freqman_file_ex so it can take a max num of freqs as a parameter with a default to FREQMAN_MAX_PER_FILE
* added a swap and a set_db to clear as much as possible before the loading of a category
* added a define for Recon max per files
* remove unneeded comment
2023-06-03 22:42:34 +02:00
E.T 2fd3bf0136 Sd over usb macos (#1096)
* sd over usb works at least up to intel + big sur, not yet on m1 + ventura
* style
2023-06-02 12:20:11 +02:00
gullradriel c3264f0d15 set tx and rx before launching mic app from recon (#1094)
Co-authored-by: GullCode <gullradriel@hotmail.com>
2023-06-02 17:53:15 +12:00
Kyle Reed 8d7fdeb633 Add edit support for Notepad (#1093)
* WIP file editing

* WIP file editing

* Add "on_pop" handler to navigation.

* WIP Editing

* WIP for draft

* Fix mock and unit tests,  support +newline at end.

* Clean up Painter API and use string_view

* Fix optional rvalue functions

* Fix Result 'take' to be more standard

* FileWrapper stack buffer reads

* Grasping at straws

* Nit

* Move set_on_pop impl to cpp

* Workaround "Open" when file not dirty.

---------

Co-authored-by: kallanreed <kallanreed@outlook.com>
2023-06-01 15:45:55 -07:00
gullradriel 69011754c9 simplifications, better precision, less sleeps (#1091)
* simplifications, better precision, less sleeps
* fixing glitches
* fixed res freeze
* correct frequency picking for marker, yay !
* added comments into the code in hard parts
* took out unneeded sleep
Special thanks to users vmakeev, tel, f1ghy, u-foka for their resilience in testing the bunch of bins I've produced to fix the thing :-)
2023-06-01 13:03:32 +02:00
Kyle Reed e50d8dc148 Move file_wrapper and make testable. (#1085)
* WIP Move file_wrapper and make testable.

* More tests, get text_editor compiling

* Back to working

* Run formatter

---------

Co-authored-by: kallanreed <kallanreed@outlook.com>
2023-05-28 08:44:21 -07:00
gullradriel 23c24355ab Yellow coloration for truncated freqman files (#1086) 2023-05-28 17:09:46 +02:00
Brumi-2021 0dcf2af50d Minor GUI change to the Mic App (#1084)
* Mic App GUI tidy up
2023-05-27 18:13:17 +02:00
Bernd Herzog cf07428a5d changed the vendor id of sd to usb (#1082) 2023-05-27 21:39:04 +12:00
E.T 35b381ae77 Fix SD over USB (#1081)
Remove clkin_detect_init() from cpu_clock_init() mirroring upstream change from the hackrf repo

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

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

* Support for 3 levels of rotary encoder sensitivity #965

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

* Removed trailing space

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

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

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

* Simpler version of configurable encoder sensitivity, issue #965

* Formatting

* Formatting test for Clang

* Formatting test

* Formatting (removed helpful comment)

* Formatting test (remove commented-out code)

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

* Swapped medium/low so default mode=0

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

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

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

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

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

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

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

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

* Update transmitter_model.hpp

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

* Run formatter

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

---------

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

* Support "repeat" when a direction button is held

* Support "repeat" when a direction button is held

* Support "repeat" when a direction button is held

* Formatting violation - removed a trailing space

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

* Adding scrolling to notepad

* Better scrolling

* Better scrolling, off-by-1 bugs

* MVP fit and finish

* Add tiny font and use in Notepad

* Font tweaking, tiny font cursor

* Fix warning

* Format changed files

* WIP No file limit

* WIP - adding CircularBuffer type

* WIP Caching

* add  unit test for circular_buffer

* WIP still have a bug when moving cache forward

* Finish lazy line caching

---------

Co-authored-by: kallanreed <kallanreed@outlook.com>
2023-05-22 13:08:59 -07:00
Erwin Ried 802a4e243b Update README.md (#1040) 2023-05-22 20:07:44 +02:00
149 changed files with 4477 additions and 1943 deletions
+1 -1
View File
@@ -1 +1 @@
v1.7.0
v1.7.1
+1 -1
View File
@@ -1 +1 @@
v1.7.1
v1.7.2
-4
View File
@@ -45,10 +45,6 @@ Consider that the hardware and firmware has been created and maintain by a [lot]
To support the people behind the hardware, please buy a genuine [HackRF](https://greatscottgadgets.com/hackrf/) and [PortaPack](https://store.sharebrained.com/products/portapack-for-hackrf-one-kit).
As a last option, if you want to send money directly to me for getting more boards, antennas and such:
[![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=CBPQA4HRRPJQ6&source=url)
## What if I really want something specific?
If what you need can be relevant in general, you can [request a feature](https://github.com/eried/portapack-mayhem/issues/new?labels=enhancement&template=feature_request.md).
+1 -1
View File
@@ -53,7 +53,7 @@ add_custom_target(
add_custom_target(
program
COMMAND dfu-util --device 1fc9:000c --download ${HACKRF_FIRMWARE_DFU_IMAGE}
COMMAND dfu-util --device 1fc9:000c --download ${HACKRF_FIRMWARE_DFU_IMAGE} || (exit 0) # We need to add it for dfu-utils v.011 , (in v.09 it is not necessary)
COMMAND sleep 3s
COMMAND hackrf_spiflash -w ${FIRMWARE_FILENAME}
DEPENDS ${FIRMWARE_FILENAME}
+3
View File
@@ -218,8 +218,10 @@ set(CPPSRC
ui/ui_btngrid.cpp
ui/ui_receiver.cpp
ui/ui_rssi.cpp
ui/ui_freqlist.cpp
ui/ui_tv.cpp
ui/ui_spectrum.cpp
ui/ui_styles.cpp
ui/ui_tabview.cpp
ui/ui_textentry.cpp
ui/ui_transmitter.cpp
@@ -259,6 +261,7 @@ set(CPPSRC
apps/ui_siggen.cpp
apps/ui_sonde.cpp
apps/ui_sstvtx.cpp
apps/ui_ss_viewer.cpp
# apps/ui_test.cpp
apps/ui_text_editor.cpp
apps/ui_tone_search.cpp
+3 -3
View File
@@ -395,7 +395,7 @@ AISAppView::AISAppView(NavigationView& nav)
receiver_model.set_tuning_frequency(tuning_frequency());
receiver_model.set_sampling_rate(sampling_rate);
receiver_model.set_baseband_bandwidth(baseband_bandwidth);
receiver_model.enable(); // Before using radio::enable(), but not updating Ant.DC-Bias.
receiver_model.enable();
options_channel.on_change = [this](size_t, OptionsField::value_t v) {
this->on_frequency_changed(v);
@@ -420,7 +420,7 @@ AISAppView::~AISAppView() {
app_settings.rx_frequency = target_frequency_;
settings.save("rx_ais", &app_settings);
receiver_model.disable(); // to switch off all, including DC bias.
receiver_model.disable();
baseband::shutdown();
}
@@ -471,7 +471,7 @@ void AISAppView::on_frequency_changed(const uint32_t new_target_frequency) {
void AISAppView::set_target_frequency(const uint32_t new_value) {
target_frequency_ = new_value;
radio::set_tuning_frequency(tuning_frequency());
receiver_model.set_tuning_frequency(tuning_frequency());
}
uint32_t AISAppView::target_frequency() const {
+8 -16
View File
@@ -42,6 +42,8 @@ namespace ui {
/* AMOptionsView *********************************************************/
static const Style& style_options_group = Styles::bg_blue;
AMOptionsView::AMOptionsView(
const Rect parent_rect,
const Style* const style)
@@ -54,8 +56,8 @@ AMOptionsView::AMOptionsView(
});
freqman_set_bandwidth_option(AM_MODULATION, options_config); // adding the common message from freqman.cpp to the options_config
options_config.set_selected_index(receiver_model.am_configuration());
options_config.on_change = [this](size_t n, OptionsField::value_t) {
options_config.set_by_value(receiver_model.am_configuration());
options_config.on_change = [this](size_t, OptionsField::value_t n) {
receiver_model.set_am_configuration(n);
};
}
@@ -74,8 +76,8 @@ NBFMOptionsView::NBFMOptionsView(
&field_squelch});
freqman_set_bandwidth_option(NFM_MODULATION, options_config); // adding the common message from freqman.cpp to the options_config
options_config.set_selected_index(receiver_model.nbfm_configuration());
options_config.on_change = [this](size_t n, OptionsField::value_t) {
options_config.set_by_value(receiver_model.nbfm_configuration());
options_config.on_change = [this](size_t, OptionsField::value_t n) {
receiver_model.set_nbfm_configuration(n);
};
@@ -99,8 +101,8 @@ WFMOptionsView::WFMOptionsView(
});
freqman_set_bandwidth_option(WFM_MODULATION, options_config); // adding the common message from freqman.cpp to the options_config
options_config.set_selected_index(receiver_model.wfm_configuration());
options_config.on_change = [this](size_t n, OptionsField::value_t) {
options_config.set_by_value(receiver_model.wfm_configuration());
options_config.on_change = [this](size_t, OptionsField::value_t n) {
receiver_model.set_wfm_configuration(n);
};
}
@@ -198,11 +200,6 @@ AnalogAudioView::AnalogAudioView(
this->on_show_options_modulation();
};
field_volume.set_value((receiver_model.headphone_volume() - audio::headphone::volume_range().max).decibel() + 99);
field_volume.on_change = [this](int32_t v) {
this->on_headphone_volume_changed(v);
};
record_view.on_error = [&nav](std::string message) {
nav.display_modal("Error", message);
};
@@ -391,11 +388,6 @@ void AnalogAudioView::on_reference_ppm_correction_changed(int32_t v) {
persistent_memory::set_correction_ppb(v * 1000);
}
void AnalogAudioView::on_headphone_volume_changed(int32_t v) {
const auto new_volume = volume_t::decibel(v - 99) + audio::headphone::volume_range().max;
receiver_model.set_headphone_volume(new_volume);
}
void AnalogAudioView::update_modulation(const ReceiverModel::Mode modulation) {
audio::output::mute();
record_view.stop();
+4 -16
View File
@@ -28,18 +28,12 @@
#include "ui_receiver.hpp"
#include "ui_spectrum.hpp"
#include "ui_record_view.hpp"
#include "ui_font_fixed_8x16.hpp"
#include "ui_styles.hpp"
#include "app_settings.hpp"
#include "tone_key.hpp"
namespace ui {
constexpr Style style_options_group{
.font = font::fixed_8x16,
.background = Color::blue(),
.foreground = Color::white(),
};
class AMOptionsView : public View {
public:
AMOptionsView(const Rect parent_rect, const Style* const style);
@@ -52,7 +46,7 @@ class AMOptionsView : public View {
OptionsField options_config{
{3 * 8, 0 * 16},
5,
6, // number of blanking characters
{
// using common messages from freqman.cpp
}};
@@ -207,13 +201,8 @@ class AnalogAudioView : public View {
{"SPEC", toUType(ReceiverModel::Mode::SpectrumAnalysis)},
}};
NumberField field_volume{
{28 * 8, 0 * 16},
2,
{0, 99},
1,
' ',
};
AudioVolumeField field_volume{
{28 * 8, 0 * 16}};
Text text_ctcss{
{19 * 8, 1 * 16, 11 * 8, 1 * 16},
@@ -239,7 +228,6 @@ class AnalogAudioView : public View {
void on_show_options_modulation();
void on_frequency_step_changed(rf::Frequency f);
void on_reference_ppm_correction_changed(int32_t v);
void on_headphone_volume_changed(int32_t v);
void on_edit_frequency();
void remove_options_widget();
+2 -10
View File
@@ -41,6 +41,8 @@ namespace ui {
/* AnalogTvView *******************************************************/
static const Style& style_options_group_new = Styles::bg_blue;
AnalogTvView::AnalogTvView(
NavigationView& nav)
: nav_(nav) {
@@ -100,11 +102,6 @@ AnalogTvView::AnalogTvView(
this->on_show_options_modulation();
};
field_volume.set_value(0);
field_volume.on_change = [this](int32_t v) {
this->on_headphone_volume_changed(v);
};
tv.on_select = [this](int32_t offset) {
field_frequency.set_value(receiver_model.tuning_frequency() + offset);
};
@@ -225,11 +222,6 @@ void AnalogTvView::on_reference_ppm_correction_changed(int32_t v) {
persistent_memory::set_correction_ppb(v * 1000);
}
void AnalogTvView::on_headphone_volume_changed(int32_t v) {
(void)v; // avoid warning
// tv::TVView::set_headphone_volume(this,v);
}
void AnalogTvView::update_modulation(const ReceiverModel::Mode modulation) {
audio::output::mute();
+3 -15
View File
@@ -29,19 +29,13 @@
#include "ui_receiver.hpp"
#include "ui_tv.hpp"
#include "ui_record_view.hpp"
#include "ui_styles.hpp"
#include "app_settings.hpp"
#include "ui_font_fixed_8x16.hpp"
#include "tone_key.hpp"
namespace ui {
constexpr Style style_options_group_new{
.font = font::fixed_8x16,
.background = Color::blue(),
.foreground = Color::white(),
};
class AnalogTvView : public View {
public:
AnalogTvView(NavigationView& nav);
@@ -98,13 +92,8 @@ class AnalogTvView : public View {
{"TV ", toUType(ReceiverModel::Mode::WidebandFMAudio)},
}};
NumberField field_volume{
{27 * 8, 0 * 16},
3,
{0, 255},
1,
' ',
};
AudioVolumeField field_volume{
{27 * 8, 0 * 16}};
std::unique_ptr<Widget> options_widget{};
@@ -118,7 +107,6 @@ class AnalogTvView : public View {
void on_show_options_modulation();
void on_frequency_step_changed(rf::Frequency f);
void on_reference_ppm_correction_changed(int32_t v);
void on_headphone_volume_changed(int32_t v);
void on_edit_frequency();
void remove_options_widget();
+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.
}
-1
View File
@@ -24,7 +24,6 @@
#include "ui_widget.hpp"
#include "ui_navigation.hpp"
#include "ui_transmitter.hpp"
#include "ui_font_fixed_8x16.hpp"
#include "rfm69.hpp"
#include "message.hpp"
+15 -37
View File
@@ -79,18 +79,21 @@ POCSAGAppView::POCSAGAppView(NavigationView& nav) {
update_freq(f);
};
// load app settings
// load app settings TODO: needed?
auto rc = settings.load("rx_pocsag", &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);
field_frequency.set_value(app_settings.rx_frequency);
} else
} else {
field_lna.set_value(receiver_model.lna());
field_vga.set_value(receiver_model.vga());
field_rf_amp.set_value(receiver_model.rf_amp());
field_frequency.set_value(receiver_model.tuning_frequency());
}
receiver_model.set_modulation(ReceiverModel::Mode::NarrowbandFMAudio);
receiver_model.set_sampling_rate(3072000);
receiver_model.set_baseband_bandwidth(1750000);
receiver_model.enable();
@@ -105,22 +108,10 @@ POCSAGAppView::POCSAGAppView(NavigationView& nav) {
};
};
check_log.set_value(logging);
check_log.on_select = [this](Checkbox&, bool v) {
logging = v;
};
field_volume.set_value((receiver_model.headphone_volume() - audio::headphone::volume_range().max).decibel() + 99);
field_volume.on_change = [this](int32_t v) {
this->on_headphone_volume_changed(v);
};
check_ignore.set_value(ignore);
check_ignore.on_select = [this](Checkbox&, bool v) {
ignore = v;
};
// TODO app setting instead?
ignore_address = persistent_memory::pocsag_ignore_address();
// TODO is this common enough for a helper?
for (size_t c = 0; c < 7; c++) {
sym_ignore.set_sym(6 - c, ignore_address % 10);
ignore_address /= 10;
@@ -150,20 +141,6 @@ POCSAGAppView::~POCSAGAppView() {
baseband::shutdown();
}
void POCSAGAppView::focus() {
field_frequency.focus();
}
void POCSAGAppView::on_headphone_volume_changed(int32_t v) {
const auto new_volume = volume_t::decibel(v - 99) + audio::headphone::volume_range().max;
receiver_model.set_headphone_volume(new_volume);
}
// Useless ?
void POCSAGAppView::set_parent_rect(const Rect new_parent_rect) {
View::set_parent_rect(new_parent_rect);
}
void POCSAGAppView::on_packet(const POCSAGPacketMessage* message) {
std::string alphanum_text = "";
@@ -172,14 +149,14 @@ void POCSAGAppView::on_packet(const POCSAGPacketMessage* message) {
else {
pocsag_decode_batch(message->packet, &pocsag_state);
if ((ignore) && (pocsag_state.address == sym_ignore.value_dec_u32())) {
if ((ignore()) && (pocsag_state.address == sym_ignore.value_dec_u32())) {
// Ignore (inform, but no log)
// console.write("\n\x1B\x03" + to_string_time(message->packet.timestamp()) +
// " Ignored address " + to_string_dec_uint(pocsag_state.address));
return;
}
// Too many errors for reliable decode
if ((ignore) && (pocsag_state.errors >= 3)) {
if ((ignore()) && (pocsag_state.errors >= 3)) {
return;
}
@@ -199,7 +176,7 @@ void POCSAGAppView::on_packet(const POCSAGPacketMessage* message) {
console.write(console_info);
if (logger && logging) {
if (logger && logging()) {
logger->log_decoded(message->packet, to_string_dec_uint(pocsag_state.address) +
" F" + to_string_dec_uint(pocsag_state.function) +
" Address only");
@@ -218,15 +195,16 @@ void POCSAGAppView::on_packet(const POCSAGPacketMessage* message) {
console.write(pocsag_state.output);
}
if (logger && logging)
if (logger && logging())
logger->log_decoded(message->packet, to_string_dec_uint(pocsag_state.address) +
" F" + to_string_dec_uint(pocsag_state.function) +
" Alpha: " + pocsag_state.output);
}
}
// TODO: make setting.
// Log raw data whatever it contains
if (logger && logging)
if (logger && logging())
logger->log_raw_data(message->packet, target_frequency());
}
+8 -15
View File
@@ -52,20 +52,18 @@ class POCSAGAppView : public View {
POCSAGAppView(NavigationView& nav);
~POCSAGAppView();
void set_parent_rect(const Rect new_parent_rect) override;
void focus() override;
std::string title() const override { return "POCSAG RX"; };
private:
static constexpr uint32_t initial_target_frequency = 466175000;
bool logging() const { return check_log.value(); };
bool ignore() const { return check_ignore.value(); };
// app save settings
std::app_settings settings{};
std::app_settings::AppSettings app_settings{};
bool logging{false};
bool ignore{false};
uint32_t last_address = 0xFFFFFFFF;
pocsag::POCSAGState pocsag_state{};
@@ -88,13 +86,9 @@ class POCSAGAppView : public View {
FrequencyField field_frequency{
{0 * 8, 0 * 8},
};
NumberField field_volume{
{28 * 8, 0 * 16},
2,
{0, 99},
1,
' ',
};
AudioVolumeField field_volume{
{28 * 8, 0 * 16}};
Checkbox check_ignore{
{0 * 8, 21},
@@ -105,10 +99,11 @@ class POCSAGAppView : public View {
{13 * 8, 25},
7,
SymField::SYMFIELD_DEC};
Checkbox check_log{
{240 - 8 * 8, 21},
3,
"LOG",
"Log",
false};
Console console{
@@ -122,8 +117,6 @@ class POCSAGAppView : public View {
void on_packet(const POCSAGPacketMessage* message);
void on_headphone_volume_changed(int32_t v);
uint32_t target_frequency() const;
void set_target_frequency(const uint32_t new_value);
+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
View File
@@ -25,7 +25,6 @@
#include "ui_widget.hpp"
#include "ui_navigation.hpp"
#include "ui_font_fixed_8x16.hpp"
#include <cstdint>
+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;
@@ -33,6 +33,8 @@ void AboutView::update() {
console.writeln("heurist1,intoxsick,ckuethe");
console.writeln("notpike,jLynx,zigad");
console.writeln("MichalLeonBorsuk,jimilinuxguy");
console.writeln("kallanreed,bernd-herzog");
console.writeln("NotherNgineer,zxkmm,u-foka");
console.writeln("");
break;
@@ -3,7 +3,6 @@
#include "ui_widget.hpp"
#include "ui_navigation.hpp"
#include "ui_font_fixed_8x16.hpp"
#include <cstdint>
+1 -1
View File
@@ -53,7 +53,7 @@ void RecentEntriesTable<AircraftRecentEntries>::draw(
target_color = Color::light_grey();
} else {
aged_color = 0x08;
target_color = Color::dark_grey();
target_color = Color::grey();
}
std::string entry_string = "\x1B";
-1
View File
@@ -24,7 +24,6 @@
#include "ui_receiver.hpp"
#include "ui_geomap.hpp"
#include "ui_font_fixed_8x16.hpp"
#include "string_format.hpp"
#include "file.hpp"
+1
View File
@@ -55,6 +55,7 @@ AFSKRxView::AFSKRxView(NavigationView& nav) {
&field_rf_amp,
&field_lna,
&field_vga,
&field_volume,
&field_frequency,
&check_log,
&text_debug,
+3
View File
@@ -79,6 +79,9 @@ class AFSKRxView : public View {
{21 * 8, 5, 6 * 8, 4},
};
AudioVolumeField field_volume{
{28 * 8, 0 * 16}};
FrequencyField field_frequency{
{0 * 8, 0 * 16},
};
+1
View File
@@ -86,6 +86,7 @@ APRSRxView::APRSRxView(NavigationView& nav, Rect parent_rect)
&field_rf_amp,
&field_lna,
&field_vga,
&field_volume,
&options_region,
&field_frequency,
&record_view,
+3
View File
@@ -207,6 +207,9 @@ class APRSRxView : public View {
{21 * 8, 5, 6 * 8, 4},
};
AudioVolumeField field_volume{
{28 * 8, 0 * 16}};
OptionsField options_region{
{0 * 8, 0 * 8},
3,
-1
View File
@@ -24,7 +24,6 @@
#include "ui_textentry.hpp"
#include "ui_widget.hpp"
#include "ui_navigation.hpp"
#include "ui_font_fixed_8x16.hpp"
#include "ui_transmitter.hpp"
#include "message.hpp"
-1
View File
@@ -25,7 +25,6 @@
#include "ui_tabview.hpp"
#include "ui_navigation.hpp"
#include "ui_transmitter.hpp"
#include "ui_font_fixed_8x16.hpp"
#include "bht.hpp"
#include "bitmap.hpp"
@@ -24,7 +24,6 @@
#include "ui_widget.hpp"
#include "ui_navigation.hpp"
#include "ui_transmitter.hpp"
#include "ui_font_fixed_8x16.hpp"
#include "message.hpp"
#include "transmitter_model.hpp"
+49 -1
View File
@@ -253,7 +253,7 @@ bool ControlsSwitchesWidget::on_key(const KeyEvent key) {
void ControlsSwitchesWidget::paint(Painter& painter) {
const auto pos = screen_pos();
const std::array<Rect, 8> button_rects{{
const std::array<Rect, 9> button_rects{{
{64, 32, 16, 16}, // Right
{0, 32, 16, 16}, // Left
{32, 64, 16, 16}, // Down
@@ -262,12 +262,16 @@ void ControlsSwitchesWidget::paint(Painter& painter) {
{16, 96, 16, 16}, // Encoder phase 0
{48, 96, 16, 16}, // Encoder phase 1
{96, 0, 16, 16}, // Dfu
{96, 64, 16, 16}, // Touch
}};
for (const auto r : button_rects) {
painter.fill_rectangle(r + pos, Color::blue());
}
if (get_touch_frame().touch)
painter.fill_rectangle(button_rects[8] + pos, Color::yellow());
const std::array<Rect, 8> raw_rects{{
{64 + 1, 32 + 1, 16 - 2, 16 - 2}, // Right
{0 + 1, 32 + 1, 16 - 2, 16 - 2}, // Left
@@ -377,10 +381,54 @@ DebugMenuView::DebugMenuView(NavigationView& nav) {
{"Peripherals", ui::Color::dark_cyan(), &bitmap_icon_peripherals, [&nav]() { nav.push<DebugPeripheralsMenuView>(); }},
{"Temperature", ui::Color::dark_cyan(), &bitmap_icon_temperature, [&nav]() { nav.push<TemperatureView>(); }},
{"Buttons Test", ui::Color::dark_cyan(), &bitmap_icon_controls, [&nav]() { nav.push<DebugControlsView>(); }},
{"Pmem", ui::Color::dark_cyan(), &bitmap_icon_memory, [&nav]() { nav.push<DebugPmemView>(); }},
});
set_max_rows(2); // allow wider buttons
}
/* DebugPmemView *********************************************************/
DebugPmemView::DebugPmemView(NavigationView& nav)
: data{*reinterpret_cast<pmem_data*>(memory::map::backup_ram.base())}, registers_widget(RegistersWidgetConfig{page_size, 8}, std::bind(&DebugPmemView::registers_widget_feed, this, std::placeholders::_1)) {
static_assert(sizeof(pmem_data) == memory::map::backup_ram.size());
add_children({&text_page, &registers_widget, &text_checksum, &button_ok});
registers_widget.set_parent_rect({0, 32, 240, 192});
text_checksum.set("Size: " + to_string_dec_uint(portapack::persistent_memory::data_size()) + " CRC: " + to_string_hex(data.check_value, 8));
button_ok.on_select = [&nav](Button&) {
nav.pop();
};
update();
}
bool DebugPmemView::on_encoder(const EncoderEvent delta) {
page = std::max(0l, std::min((int32_t)page_max, page + delta));
update();
return true;
}
void DebugPmemView::focus() {
button_ok.focus();
}
void DebugPmemView::update() {
text_page.set(to_string_hex(page_size * page, 2) + "+");
registers_widget.update();
}
uint32_t DebugPmemView::registers_widget_feed(const size_t register_number) {
if (page_size * page + register_number >= memory::map::backup_ram.size()) {
return 0xff;
}
return data.regfile[(page_size * page + register_number) / 4] >> (register_number % 4 * 8);
}
/*DebugLCRView::DebugLCRView(NavigationView& nav, std::string lcr_string) {
std::string debug_text;
+36
View File
@@ -30,6 +30,7 @@
#include "rffc507x.hpp"
#include "portapack.hpp"
#include "memory_map.hpp"
#include <functional>
#include <utility>
@@ -263,6 +264,41 @@ class DebugControlsView : public View {
"Done"};
};
class DebugPmemView : public View {
public:
DebugPmemView(NavigationView& nav);
void focus() override;
bool on_encoder(const EncoderEvent delta) override;
std::string title() const override { return "Pmem"; }
private:
struct pmem_data {
uint32_t regfile[63];
uint32_t check_value;
};
static constexpr uint8_t page_size{96}; // Must be multiply of 4 otherwise bit shifting for register view wont work properly
static constexpr uint8_t page_max{(portapack::memory::map::backup_ram.size() + page_size - 1) / page_size - 1};
int32_t page{0};
const pmem_data& data;
Text text_page{{16, 16, 208, 16}};
RegistersWidget registers_widget;
Text text_checksum{{16, 240, 208, 16}};
Button button_ok{
{240 / 3, 270, 240 / 3, 24},
"OK",
};
void update();
uint32_t registers_widget_feed(const size_t register_number);
};
/*class DebugLCRView : public View {
public:
DebugLCRView(NavigationView& nav, std::string lcrstring);
-11
View File
@@ -195,17 +195,6 @@ class EncodersView : public View {
void start_tx(const bool scan);
void on_tx_progress(const uint32_t progress, const bool done);
/*const Style style_address {
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::red(),
};
const Style style_data {
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::blue(),
};*/
Rect view_rect = {0, 4 * 8, 240, 168};
EncodersConfigView view_config{nav_, view_rect};
+106 -69
View File
@@ -26,6 +26,8 @@
#include <algorithm>
#include "ui_fileman.hpp"
#include "ui_ss_viewer.hpp"
#include "ui_text_editor.hpp"
#include "string_format.hpp"
#include "portapack.hpp"
#include "event_m0.hpp"
@@ -66,15 +68,16 @@ bool iequal(
// Inserts the entry into the entry list sorted directories first then by file name.
void insert_sorted(std::vector<fileman_entry>& entries, fileman_entry&& entry) {
auto it = std::lower_bound(std::begin(entries), std::end(entries), entry,
[](const fileman_entry& lhs, const fileman_entry& rhs) {
if (lhs.is_directory && !rhs.is_directory)
return true;
else if (!lhs.is_directory && rhs.is_directory)
return false;
else
return lhs.path < rhs.path;
});
auto it = std::lower_bound(
std::begin(entries), std::end(entries), entry,
[](const fileman_entry& lhs, const fileman_entry& rhs) {
if (lhs.is_directory && !rhs.is_directory)
return true;
else if (!lhs.is_directory && rhs.is_directory)
return false;
else
return lhs.path < rhs.path;
});
entries.insert(it, std::move(entry));
}
@@ -397,6 +400,18 @@ void FileSaveView::refresh_widgets() {
*/
/* FileManagerView ***********************************************************/
void FileManagerView::refresh_widgets(const bool v) {
button_rename.hidden(v);
button_delete.hidden(v);
button_cut.hidden(v);
button_copy.hidden(v);
button_paste.hidden(v);
button_new_dir.hidden(v);
button_new_file.hidden(v);
set_dirty();
}
void FileManagerView::on_rename() {
auto& entry = get_selected_entry();
name_buffer = entry.path.filename().string();
@@ -406,44 +421,47 @@ void FileManagerView::on_rename() {
pos != name_buffer.npos && !entry.is_directory)
cursor_pos = pos;
text_prompt(nav_, name_buffer, cursor_pos, max_filename_length,
[this](std::string& renamed) {
auto renamed_path = fs::path{renamed};
rename_file(get_selected_full_path(), current_path / renamed_path);
text_prompt(
nav_, name_buffer, cursor_pos, max_filename_length,
[this](std::string& renamed) {
auto renamed_path = fs::path{renamed};
rename_file(get_selected_full_path(), current_path / renamed_path);
auto has_partner = partner_file_prompt(nav_, get_selected_full_path(), "Rename",
[this, renamed_path](const fs::path& partner, bool should_rename) mutable {
if (should_rename) {
auto new_name = renamed_path.replace_extension(partner.extension());
rename_file(partner, current_path / new_name);
}
reload_current();
});
if (!has_partner)
reload_current();
auto has_partner = partner_file_prompt(
nav_, get_selected_full_path(), "Rename",
[this, renamed_path](const fs::path& partner, bool should_rename) mutable {
if (should_rename) {
auto new_name = renamed_path.replace_extension(partner.extension());
rename_file(partner, current_path / new_name);
}
reload_current();
});
if (!has_partner)
reload_current();
});
}
void FileManagerView::on_delete() {
auto name = get_selected_entry().path.filename().string();
nav_.push<ModalMessageView>("Delete", "Delete " + name + "\nAre you sure?", YESNO,
[this](bool choice) {
if (choice) {
delete_file(get_selected_full_path());
nav_.push<ModalMessageView>(
"Delete", "Delete " + name + "\nAre you sure?", YESNO,
[this](bool choice) {
if (choice) {
delete_file(get_selected_full_path());
auto has_partner = partner_file_prompt(
nav_, get_selected_full_path(), "Delete",
[this](const fs::path& partner, bool should_delete) {
if (should_delete)
delete_file(partner);
reload_current();
});
auto has_partner = partner_file_prompt(
nav_, get_selected_full_path(), "Delete",
[this](const fs::path& partner, bool should_delete) {
if (should_delete)
delete_file(partner);
reload_current();
});
if (!has_partner)
reload_current();
}
});
if (!has_partner)
reload_current();
}
});
}
void FileManagerView::on_new_dir() {
@@ -454,14 +472,6 @@ void FileManagerView::on_new_dir() {
});
}
void FileManagerView::on_new_file() {
name_buffer = "";
text_prompt(nav_, name_buffer, max_filename_length, [this](std::string& file_name) {
make_new_file(current_path / file_name);
reload_current();
});
}
void FileManagerView::on_paste() {
// TODO: handle partner file. Need to fix nav stack first.
auto new_name = get_unique_filename(current_path, clipboard_path.filename());
@@ -482,23 +492,37 @@ void FileManagerView::on_paste() {
reload_current();
}
void FileManagerView::on_new_file() {
name_buffer = "";
text_prompt(nav_, name_buffer, max_filename_length, [this](std::string& file_name) {
make_new_file(current_path / file_name);
reload_current();
});
}
bool FileManagerView::handle_file_open() {
if (!selected_is_valid())
return false;
auto path = get_selected_full_path();
auto ext = path.extension();
if (iequal(u".TXT", ext) || iequal(u".PPL", ext)) {
nav_.push<TextEditorView>(path);
return true;
} else if (iequal(u".PNG", ext)) {
nav_.push<ScreenshotViewer>(path);
return true;
}
return false;
}
bool FileManagerView::selected_is_valid() const {
return !entry_list.empty() &&
get_selected_entry().path != parent_dir_path;
}
void FileManagerView::refresh_widgets(const bool v) {
button_rename.hidden(v);
button_delete.hidden(v);
button_cut.hidden(v);
button_copy.hidden(v);
button_paste.hidden(v);
button_new_dir.hidden(v);
button_new_file.hidden(v);
set_dirty();
}
FileManagerView::FileManagerView(
NavigationView& nav)
: FileManBaseView(nav, "") {
@@ -510,16 +534,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())
@@ -533,6 +560,8 @@ FileManagerView::FileManagerView(
on_select_entry = [this](KeyEvent key) {
if (key == KeyEvent::Select && get_selected_entry().is_directory) {
push_dir(get_selected_entry().path);
} else if (key == KeyEvent::Select && handle_file_open()) {
return;
} else {
button_rename.focus();
}
@@ -578,6 +607,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
+11 -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"};
};
@@ -212,6 +213,8 @@ class FileManagerView : public FileManBaseView {
void on_new_dir();
void on_new_file();
bool handle_file_open();
// True if the selected entry is a real file item.
bool selected_is_valid() const;
@@ -263,6 +266,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_;
+81 -112
View File
@@ -29,29 +29,21 @@ using namespace portapack;
namespace ui {
static int32_t last_category_id{0};
static int32_t current_category_id = 0;
FreqManBaseView::FreqManBaseView(
NavigationView& nav)
: nav_(nav) {
file_list = get_freqman_files();
add_children({&label_category,
add_children({&options_category,
&label_category,
&button_exit});
if (file_list.size()) {
add_child(&options_category);
populate_categories();
} else
error_ = ERROR_NOFILES;
// initialize
change_category(last_category_id);
// Default function
on_change_category = [this](int32_t category_id) {
refresh_list();
options_category.on_change = [this](size_t category_id, int32_t) {
change_category(category_id);
};
options_category.set_selected_index(current_category_id);
button_exit.on_select = [this, &nav](Button&) {
nav.pop();
@@ -70,8 +62,39 @@ void FreqManBaseView::focus() {
}
}
void FreqManBaseView::populate_categories() {
void FreqManBaseView::get_freqman_files() {
std::vector<std::string>().swap(file_list);
auto files = scan_root_files(u"FREQMAN", u"*.TXT");
for (auto file : files) {
std::string file_name = file.stem().string();
// don't propose tmp / hidden files in freqman's list
if (file_name.length() && file_name[0] != '.') {
file_list.emplace_back(file_name);
}
}
};
void FreqManBaseView::change_category(int32_t category_id) {
current_category_id = category_id;
if (file_list.empty()) return;
std::vector<freqman_entry>().swap(database);
if (!load_freqman_file(file_list[categories[category_id].second], database)) {
error_ = ERROR_ACCESS;
}
menu_view.set_db(database);
text_empty.hidden(!database.empty());
menu_view.set_dirty();
set_dirty();
}
void FreqManBaseView::refresh_list() {
categories.clear();
get_freqman_files();
for (size_t n = 0; n < file_list.size(); n++)
categories.emplace_back(std::make_pair(file_list[n].substr(0, 14), n));
@@ -82,65 +105,13 @@ void FreqManBaseView::populate_categories() {
});
options_category.set_options(categories);
options_category.set_selected_index(last_category_id);
options_category.on_change = [this](size_t category_id, int32_t) {
if (on_change_category)
on_change_category(category_id);
};
}
void FreqManBaseView::change_category(int32_t category_id) {
if (!file_list.size()) return;
last_category_id = current_category_id = category_id;
if (!load_freqman_file(file_list[categories[current_category_id].second], database))
error_ = ERROR_ACCESS;
else
refresh_list();
}
void FreqManBaseView::refresh_list() {
if (!database.size()) {
if (on_refresh_widgets)
on_refresh_widgets(true);
} else {
if (on_refresh_widgets)
on_refresh_widgets(false);
menu_view.clear();
for (size_t n = 0; n < database.size(); n++) {
menu_view.add_item({freqman_item_string(database[n], 30),
ui::Color::white(),
nullptr,
[this](KeyEvent) {
if (on_select_frequency)
on_select_frequency();
}});
}
menu_view.set_highlighted(0); // Refresh
}
if ((unsigned)current_category_id >= categories.size())
current_category_id = categories.size() - 1;
}
void FrequencySaveView::save_current_file() {
if (database.size() > FREQMAN_MAX_PER_FILE) {
nav_.display_modal(
"Error", "Too many entries, maximum is\n" FREQMAN_MAX_PER_FILE_STR ". Trim list ?",
YESNO,
[this](bool choice) {
if (choice) {
database.resize(FREQMAN_MAX_PER_FILE);
save_freqman_file(file_list[categories[current_category_id].second], database);
}
nav_.pop();
});
} else {
save_freqman_file(file_list[categories[current_category_id].second], database);
nav_.pop();
}
save_freqman_file(file_list[categories[current_category_id].second], database);
nav_.pop();
}
void FrequencySaveView::on_save_name() {
@@ -164,11 +135,11 @@ FrequencySaveView::FrequencySaveView(
// Todo: add back ?
/*for (size_t n = 0; n < database.size(); n++) {
if (database[n].value == value_) {
error_ = ERROR_DUPLICATE;
break;
}
}*/
if (database[n].value == value_) {
error_ = ERROR_DUPLICATE;
break;
}
}*/
add_children({&labels,
&big_display,
@@ -184,6 +155,11 @@ FrequencySaveView::FrequencySaveView(
button_save_timestamp.on_select = [this, &nav](Button&) {
on_save_timestamp();
};
options_category.on_change = [this, value](size_t category_id, int32_t) {
change_category(category_id);
big_display.set(value);
};
}
void FrequencyLoadView::refresh_widgets(const bool v) {
@@ -206,18 +182,10 @@ FrequencyLoadView::FrequencyLoadView(
// Resize menu view to fill screen
menu_view.set_parent_rect({0, 3 * 8, 240, 30 * 8});
// Just to allow exit on left
menu_view.on_left = [&nav, this]() {
nav.pop();
};
change_category(last_category_id);
refresh_list();
on_select_frequency = [&nav, this]() {
menu_view.on_select = [&nav, this](FreqManUIList&) {
nav_.pop();
auto& entry = database[menu_view.highlighted_index()];
auto& entry = database[menu_view.get_index()];
if (entry.type == RANGE) {
// User chose a frequency range entry
@@ -235,16 +203,16 @@ FrequencyLoadView::FrequencyLoadView(
}
void FrequencyManagerView::on_edit_freq(rf::Frequency f) {
database[menu_view.highlighted_index()].frequency_a = f;
database[menu_view.get_index()].frequency_a = f;
save_freqman_file(file_list[categories[current_category_id].second], database);
refresh_list();
change_category(current_category_id);
}
void FrequencyManagerView::on_edit_desc(NavigationView& nav) {
text_prompt(nav, desc_buffer, 28, [this](std::string& buffer) {
database[menu_view.highlighted_index()].description = buffer;
refresh_list();
database[menu_view.get_index()].description = buffer;
save_freqman_file(file_list[categories[current_category_id].second], database);
change_category(current_category_id);
});
}
@@ -252,23 +220,30 @@ void FrequencyManagerView::on_new_category(NavigationView& nav) {
text_prompt(nav, desc_buffer, 12, [this](std::string& buffer) {
File freqman_file;
create_freqman_file(buffer, freqman_file);
refresh_list();
change_category(current_category_id);
});
populate_categories();
refresh_list();
}
void FrequencyManagerView::on_delete() {
database.erase(database.begin() + menu_view.highlighted_index());
save_freqman_file(file_list[categories[current_category_id].second], database);
refresh_list();
if (database.empty()) {
delete_freqman_file(file_list[categories[current_category_id].second]);
refresh_list();
} else {
database.erase(database.begin() + menu_view.get_index());
save_freqman_file(file_list[categories[current_category_id].second], database);
}
change_category(current_category_id);
}
void FrequencyManagerView::refresh_widgets(const bool v) {
button_edit_freq.hidden(v);
button_edit_desc.hidden(v);
button_delete.hidden(v);
menu_view.hidden(v);
text_empty.hidden(!v);
menu_view.hidden(v);
menu_view.set_dirty();
labels.hidden(v);
// display.fill_rectangle(menu_view.screen_rect(), Color::black());
set_dirty();
}
@@ -292,15 +267,7 @@ FrequencyManagerView::FrequencyManagerView(
&button_edit_desc,
&button_delete});
// Just to allow exit on left
menu_view.on_left = [&nav, this]() {
nav.pop();
};
change_category(last_category_id);
refresh_list();
on_select_frequency = [this]() {
menu_view.on_select = [this](FreqManUIList&) {
button_edit_freq.focus();
};
@@ -310,23 +277,25 @@ FrequencyManagerView::FrequencyManagerView(
};
button_edit_freq.on_select = [this, &nav](Button&) {
auto new_view = nav.push<FrequencyKeypadView>(database[menu_view.highlighted_index()].frequency_a);
if (database.empty()) {
database.push_back({0, 0, "", SINGLE});
}
auto new_view = nav.push<FrequencyKeypadView>(database[menu_view.get_index()].frequency_a);
new_view->on_changed = [this](rf::Frequency f) {
on_edit_freq(f);
};
};
button_edit_desc.on_select = [this, &nav](Button&) {
desc_buffer = database[menu_view.highlighted_index()].description;
if (database.empty()) {
database.push_back({0, 0, "", SINGLE});
}
desc_buffer = database[menu_view.get_index()].description;
on_edit_desc(nav);
};
button_delete.on_select = [this, &nav](Button&) {
nav.push<ModalMessageView>("Confirm", "Are you sure ?", YESNO,
[this](bool choice) {
if (choice)
on_delete();
});
on_delete();
};
}
+10 -9
View File
@@ -28,6 +28,7 @@
#include "ui_receiver.hpp"
#include "ui_textentry.hpp"
#include "freqman.hpp"
#include "ui_freqlist.hpp"
namespace ui {
@@ -45,17 +46,15 @@ class FreqManBaseView : public View {
NavigationView& nav_;
freqman_error error_{NO_ERROR};
options_t categories{};
std::function<void(int32_t category_id)> on_change_category{nullptr};
std::function<void(void)> on_select_frequency{nullptr};
std::function<void(bool)> on_refresh_widgets{nullptr};
std::vector<std::string> file_list{};
int32_t current_category_id{0};
void populate_categories();
void get_freqman_files();
void change_category(int32_t category_id);
void refresh_list();
freqman_db database{};
std::vector<std::string> file_list{};
Labels label_category{
{{0, 4}, "Category:", Color::light_grey()}};
@@ -65,17 +64,19 @@ class FreqManBaseView : public View {
14,
{}};
MenuView menu_view{
{0, 3 * 8, 240, 23 * 8},
true};
FreqManUIList menu_view{
{0, 3 * 8, 240, 23 * 8}};
Text text_empty{
{7 * 8, 12 * 8, 16 * 8, 16},
"Empty category !",
};
Button button_exit{
{20 * 8, 34 * 8, 10 * 8, 4 * 8},
{16 * 8, 34 * 8, 14 * 8, 4 * 8},
"Exit"};
private:
};
class FrequencySaveView : public FreqManBaseView {
@@ -153,7 +154,7 @@ class FrequencyManagerView : public FreqManBaseView {
"Description"};
Button button_delete{
{18 * 8, 27 * 8, 12 * 8, 32},
{16 * 8, 29 * 8, 14 * 8, 32},
"Delete"};
};
-6
View File
@@ -36,8 +36,6 @@ void RangeView::focus() {
check_enabled.focus();
}
extern constexpr Style RangeView::style_info;
void RangeView::update_start(rf::Frequency f) {
// Change everything except max
frequency_range.min = f;
@@ -200,9 +198,6 @@ void JammerView::set_jammer_channel(uint32_t i, uint32_t width, uint64_t center,
jammer_channels[i].duration = 30720 * duration;
}
extern constexpr Style JammerView::style_val;
extern constexpr Style JammerView::style_cancel;
void JammerView::start_tx() {
uint32_t c, i = 0;
size_t num_channels;
@@ -286,7 +281,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;
+4 -16
View File
@@ -22,7 +22,7 @@
#include "ui.hpp"
#include "ui_widget.hpp"
#include "ui_font_fixed_8x16.hpp"
#include "ui_styles.hpp"
#include "ui_navigation.hpp"
#include "ui_tabview.hpp"
#include "transmitter_model.hpp"
@@ -52,11 +52,7 @@ class RangeView : public View {
uint32_t width{};
rf::Frequency center{};
static constexpr Style style_info{
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::grey(),
};
const Style& style_info = Styles::grey;
Labels labels{
{{2 * 8, 8 * 8 + 4}, "Start", Color::light_grey()},
@@ -117,16 +113,8 @@ class JammerView : public View {
int16_t mscounter = 0; // euquiq: Internal ms counter for do_timer()
lfsr_word_t lfsr_v = 1; // euquiq: Used to generate "random" Jitter
static constexpr Style style_val{
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::green(),
};
static constexpr Style style_cancel{
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::red(),
};
const Style& style_val = Styles::green;
const Style& style_cancel = Styles::red;
RangeView view_range_a{nav_};
RangeView view_range_b{nav_};
+13 -20
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.
*
@@ -67,9 +68,6 @@ LevelView::LevelView(NavigationView& nav)
rssi.set_vertical_rssi(true);
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);
change_mode(NFM_MODULATION); // Start on AM
field_mode.set_by_value(NFM_MODULATION); // Reflect the mode into the manual selector
@@ -134,7 +132,7 @@ LevelView::LevelView(NavigationView& nav)
audio::output::stop();
} else if (v == 1) {
audio::output::start();
this->on_headphone_volume_changed((receiver_model.headphone_volume() - audio::headphone::volume_range().max).decibel() + 99);
receiver_model.set_headphone_volume(receiver_model.headphone_volume()); // WM8731 hack.
} else {
}
};
@@ -153,13 +151,8 @@ LevelView::LevelView(NavigationView& nav)
// FILL STEP OPTIONS
freqman_set_modulation_option(field_mode);
freqman_set_step_option_short(step_mode);
freq_stats_rssi.set_style(&style_white);
freq_stats_db.set_style(&style_white);
}
void LevelView::on_headphone_volume_changed(int32_t v) {
const auto new_volume = volume_t::decibel(v - 99) + audio::headphone::volume_range().max;
receiver_model.set_headphone_volume(new_volume);
freq_stats_rssi.set_style(&Styles::white);
freq_stats_db.set_style(&Styles::white);
}
void LevelView::on_statistics_update(const ChannelStatistics& statistics) {
@@ -198,11 +191,11 @@ size_t LevelView::change_mode(freqman_index_t new_mod) {
case AM_MODULATION:
freqman_set_bandwidth_option(new_mod, field_bw);
// bw DSB (0) default
field_bw.set_selected_index(0);
field_bw.set_by_value(0);
baseband::run_image(portapack::spi_flash::image_tag_am_audio);
receiver_model.set_modulation(ReceiverModel::Mode::AMAudio);
receiver_model.set_am_configuration(field_bw.selected_index());
field_bw.on_change = [this](size_t n, OptionsField::value_t) { receiver_model.set_am_configuration(n); };
receiver_model.set_am_configuration(field_bw.selected_index_value());
field_bw.on_change = [this](size_t, OptionsField::value_t n) { receiver_model.set_am_configuration(n); };
receiver_model.set_sampling_rate(3072000);
receiver_model.set_baseband_bandwidth(1750000);
text_ctcss.set(" ");
@@ -210,22 +203,22 @@ size_t LevelView::change_mode(freqman_index_t new_mod) {
case NFM_MODULATION:
freqman_set_bandwidth_option(new_mod, field_bw);
// bw 16k (2) default
field_bw.set_selected_index(2);
field_bw.set_by_value(2);
baseband::run_image(portapack::spi_flash::image_tag_nfm_audio);
receiver_model.set_modulation(ReceiverModel::Mode::NarrowbandFMAudio);
receiver_model.set_nbfm_configuration(field_bw.selected_index());
field_bw.on_change = [this](size_t n, OptionsField::value_t) { receiver_model.set_nbfm_configuration(n); };
receiver_model.set_nbfm_configuration(field_bw.selected_index_value());
field_bw.on_change = [this](size_t, OptionsField::value_t n) { receiver_model.set_nbfm_configuration(n); };
receiver_model.set_sampling_rate(3072000);
receiver_model.set_baseband_bandwidth(1750000);
break;
case WFM_MODULATION:
freqman_set_bandwidth_option(new_mod, field_bw);
// bw 200k (0) only/default
field_bw.set_selected_index(0);
field_bw.set_by_value(0);
baseband::run_image(portapack::spi_flash::image_tag_wfm_audio);
receiver_model.set_modulation(ReceiverModel::Mode::WidebandFMAudio);
receiver_model.set_wfm_configuration(field_bw.selected_index());
field_bw.on_change = [this](size_t n, OptionsField::value_t) { receiver_model.set_wfm_configuration(n); };
receiver_model.set_wfm_configuration(field_bw.selected_index_value());
field_bw.on_change = [this](size_t, OptionsField::value_t n) { receiver_model.set_wfm_configuration(n); };
receiver_model.set_sampling_rate(3072000);
receiver_model.set_baseband_bandwidth(1750000);
text_ctcss.set(" ");
+4 -51
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.
*
@@ -26,7 +27,7 @@
#include "ui.hpp"
#include "receiver_model.hpp"
#include "ui_receiver.hpp"
#include "ui_font_fixed_8x16.hpp"
#include "ui_styles.hpp"
#include "freqman.hpp"
#include "analog_audio_app.hpp"
#include "audio.hpp"
@@ -47,48 +48,6 @@ class LevelView : public View {
void focus() override;
const Style style_grey{
// level
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::grey(),
};
const Style style_white{
// level
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::white(),
};
const Style style_yellow{
// Found signal
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::yellow(),
};
const Style style_green{
// Found signal
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::green(),
};
const Style style_red{
// erasing freq
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::red(),
};
const Style style_blue{
// quick level, wait == 0
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::blue(),
};
std::string title() const override { return "Level"; };
private:
@@ -116,13 +75,8 @@ class LevelView : public View {
RFAmpField field_rf_amp{
{18 * 8, 0 * 16}};
NumberField field_volume{
{24 * 8, 0 * 16},
2,
{0, 99},
1,
' ',
};
AudioVolumeField field_volume{
{24 * 8, 0 * 16}};
OptionsField field_bw{
{3 * 8, 1 * 16},
@@ -201,7 +155,6 @@ class LevelView : public View {
};
void handle_coded_squelch(const uint32_t value);
void on_headphone_volume_changed(int32_t v);
MessageHandlerRegistration message_handler_coded_squelch{
Message::ID::CodedSquelch,
+114 -134
View File
@@ -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.
*
@@ -45,18 +46,10 @@ void GlassView::get_max_power(const ChannelSpectrum& spectrum, uint8_t bin, uint
if (spectrum.db[bin - 120] > max_power)
max_power = spectrum.db[bin - 120];
}
} else if (mode == LOOKING_GLASS_FASTSCAN) {
} else {
// view is made in multiple pass, use original bin picking
// Center 12 bins are ignored (DC spike is blanked) Leftmost and rightmost 2 bins are ignored
if (bin < 120) {
if (spectrum.db[SPEC_NB_BINS - 2 - 120 + bin] > max_power)
max_power = spectrum.db[SPEC_NB_BINS - 2 - 120 + bin];
} else {
if (spectrum.db[2 + bin - 120] > max_power)
max_power = spectrum.db[2 + bin - 120];
}
} else // if( mode == LOOKING_GLASS_SLOWSCAN )
{
// FAST mode: center 12 bins are ignored in fast mode , (DC spike is blanked) leftmost and rightmost 2 bins are ignored
// SLOW mode: leftmost 'offset' bins are ignored
if (bin < 120) {
if (spectrum.db[SPEC_NB_BINS - offset - 120 + bin] > max_power)
max_power = spectrum.db[SPEC_NB_BINS - offset - 120 + bin];
@@ -67,13 +60,19 @@ void GlassView::get_max_power(const ChannelSpectrum& spectrum, uint8_t bin, uint
}
}
void GlassView::on_marker_change() {
rf::Frequency GlassView::get_freq_from_bin_pos(uint8_t pos) {
rf::Frequency freq_at_pos = 0;
if (mode == LOOKING_GLASS_SINGLEPASS) {
marker = f_min + (marker_pixel_index * looking_glass_range) / SCREEN_W;
} else // if( mode == LOOKING_GLASS_SLOWSCAN || mode == LOOKING_GLASS_FASTSCAN )
{
marker = f_min + (offset * each_bin_size) + (marker_pixel_index * looking_glass_range) / SCREEN_W;
}
// starting from the middle, minus 8 ignored bin on each side. Since pos is [-120,120] after the (pos - 120), it's divided by SCREEN_W(240)/2 => 120
freq_at_pos = f_center_ini + ((pos - 120) * ((looking_glass_range - ((16 * looking_glass_range) / SPEC_NB_BINS)) / 2)) / (SCREEN_W / 2);
} else
freq_at_pos = f_min - (offset * each_bin_size) + (pos * looking_glass_range) / SCREEN_W;
return freq_at_pos;
}
void GlassView::on_marker_change() {
marker = get_freq_from_bin_pos(marker_pixel_index);
button_marker.set_text(to_string_short_freq(marker));
PlotMarker(marker_pixel_index); // Refresh marker on screen
}
@@ -96,9 +95,11 @@ 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
chThdSleepMilliseconds(5);
// 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); // stabilize freq
baseband::spectrum_streaming_start(); // Do the RX
}
@@ -143,9 +144,8 @@ 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;
max_freq_hold = get_freq_from_bin_pos(xpos);
}
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
@@ -172,16 +172,12 @@ void GlassView::on_channel_spectrum(const ChannelSpectrum& spectrum) {
baseband::spectrum_streaming_stop();
// Convert bins of this spectrum slice into a representative max_power and when enough, into pixels
// we actually need SCREEN_W (240) of those bins
for (bin = offset; bin < bin_length + offset; bin++) {
for (bin = 0; bin < bin_length; bin++) {
get_max_power(spectrum, bin, max_power);
if (ignore_dc && bin == 119) {
uint8_t next_max_power = 0;
get_max_power(spectrum, 120, next_max_power);
bins_Hz_size += 12 * each_bin_size; // add the ignored DC spike to "pixel fulfilled bag of Hz"
max_power = (max_power + next_max_power) / 2;
}
bins_Hz_size += each_bin_size; // add this bin Hz count into the "pixel fulfilled bag of Hz"
bins_Hz_size += each_bin_size; // add this bin Hz count into the "pixel fulfilled bag of Hz"
if (bins_Hz_size >= marker_pixel_step) // new pixel fullfilled
{
if (min_color_power < max_power)
@@ -194,15 +190,21 @@ void GlassView::on_channel_spectrum(const ChannelSpectrum& spectrum) {
if (!pixel_index) // Received indication that a waterfall line has been completed
{
bins_Hz_size = 0; // Since this is an entire pixel line, we don't carry "Pixels into next bin"
f_center = f_center_ini;
retune();
if (mode != LOOKING_GLASS_SINGLEPASS) {
f_center = f_center_ini;
retune();
} else
baseband::spectrum_streaming_start();
return; // signal a new line
}
bins_Hz_size -= marker_pixel_step; // reset bins size, but carrying the eventual excess Hz into next pixel
}
}
f_center += looking_glass_step;
retune();
if (mode != LOOKING_GLASS_SINGLEPASS) {
f_center += looking_glass_step;
retune();
} else
baseband::spectrum_streaming_start();
}
void GlassView::on_hide() {
@@ -216,45 +218,49 @@ void GlassView::on_show() {
}
void GlassView::on_range_changed() {
reset_live_view(false);
reset_live_view(true);
f_min = field_frequency_min.value();
f_max = field_frequency_max.value();
f_min = f_min * MHZ_DIV; // Transpose into full frequency realm
f_max = f_max * MHZ_DIV;
looking_glass_range = f_max - f_min;
if (looking_glass_range < LOOKING_GLASS_SLICE_WIDTH_MAX) {
mode = LOOKING_GLASS_SINGLEPASS;
} else {
mode = scan_type.selected_index_value();
}
if (mode == LOOKING_GLASS_SINGLEPASS) {
if (looking_glass_range <= LOOKING_GLASS_SLICE_WIDTH_MAX) {
adjust_range(&f_min, &f_max, SCREEN_W);
looking_glass_range = f_max - f_min;
// if the view is done in one pass, show it like in analog_audio_app
mode = LOOKING_GLASS_SINGLEPASS;
offset = 0;
bin_length = SCREEN_W;
ignore_dc = 0;
} else if (mode == LOOKING_GLASS_FASTSCAN) {
looking_glass_bandwidth = looking_glass_range;
looking_glass_sampling_rate = looking_glass_bandwidth;
each_bin_size = looking_glass_bandwidth / SCREEN_W;
looking_glass_step = looking_glass_bandwidth;
f_center_ini = f_min + (looking_glass_bandwidth / 2); // Initial center frequency for sweep
} else { // if ( mode == LOOKING_GLASS_SLOWSCAN || mode == LOOKING_GLASS_FASTSCAN )
// view is made in multiple pass, use original bin picking
offset = 0;
bin_length = SCREEN_W;
ignore_dc = 1;
} else // if( mode == LOOKING_GLASS_SLOWSCAN )
{
offset = 16;
bin_length = 80;
ignore_dc = 0;
}
each_bin_size = looking_glass_bandwidth / SPEC_NB_BINS;
if (mode != LOOKING_GLASS_SINGLEPASS) {
mode = scan_type.selected_index_value();
if (mode == LOOKING_GLASS_FASTSCAN) {
offset = 2;
bin_length = SCREEN_W;
ignore_dc = 1;
} else { // if( mode == LOOKING_GLASS_SLOWSCAN )
offset = 16;
bin_length = 80;
ignore_dc = 0;
}
adjust_range(&f_min, &f_max, SCREEN_W);
looking_glass_range = f_max - f_min;
looking_glass_bandwidth = LOOKING_GLASS_SLICE_WIDTH_MAX;
looking_glass_sampling_rate = LOOKING_GLASS_SLICE_WIDTH_MAX;
each_bin_size = LOOKING_GLASS_SLICE_WIDTH_MAX / SPEC_NB_BINS;
looking_glass_step = (bin_length + (ignore_dc * 12)) * each_bin_size;
} else {
looking_glass_step = SPEC_NB_BINS * each_bin_size;
f_center_ini = f_min - (offset * each_bin_size) + (looking_glass_bandwidth / 2); // Initial center frequency for sweep
}
adjust_range(&f_min, &f_max, SCREEN_W);
looking_glass_range = f_max - f_min;
marker_pixel_step = looking_glass_range / SCREEN_W; // Each pixel value in Hz
search_span = looking_glass_range / MHZ_DIV;
button_range.set_text(" "); // clear up to 6 chars
marker_pixel_step = looking_glass_range / SCREEN_W; // Each pixel value in Hz
//
button_range.set_text(" "); // clear up to 6 chars
if (locked_range) {
button_range.set_text(">" + to_string_dec_uint(search_span) + "<");
} else {
@@ -264,18 +270,7 @@ void GlassView::on_range_changed() {
pixel_index = 0; // reset pixel counter
max_power = 0; // reset save max power level
bins_Hz_size = 0; // reset amount of Hz filled up by pixels
if (mode == LOOKING_GLASS_SINGLEPASS) {
looking_glass_bandwidth = looking_glass_range;
looking_glass_sampling_rate = looking_glass_bandwidth / 2;
each_bin_size = looking_glass_bandwidth / SCREEN_W;
} else // if ( mode == LOOKING_GLASS_SLOWSCAN || mode == LOOKING_GLASS_FASTSCAN )
{
looking_glass_sampling_rate = LOOKING_GLASS_SLICE_WIDTH_MAX;
looking_glass_bandwidth = LOOKING_GLASS_SLICE_WIDTH_MAX;
each_bin_size = looking_glass_bandwidth / SPEC_NB_BINS;
}
//
on_marker_change();
// set the sample rate and bandwidth
@@ -283,9 +278,8 @@ void GlassView::on_range_changed() {
receiver_model.set_baseband_bandwidth(looking_glass_bandwidth);
receiver_model.set_squelch_level(0);
f_center_ini = f_min + (looking_glass_bandwidth / 2); // Initial center frequency for sweep
f_center = f_center_ini; // Reset sweep into first slice
baseband::set_spectrum(looking_glass_sampling_rate, field_trigger.value());
f_center = f_center_ini; // Reset sweep into first slice
baseband::set_spectrum(looking_glass_bandwidth, field_trigger.value());
receiver_model.set_tuning_frequency(f_center); // tune rx for this slice
}
@@ -301,6 +295,42 @@ void GlassView::PlotMarker(uint8_t pos) {
portapack::display.fill_rectangle({pos, 106 + shift_y, 1, 2}, Color::red()); // Red marker bottom
}
void GlassView::clip_min(int32_t v) {
int32_t min_size = steps;
if (locked_range)
min_size = search_span;
if (min_size < 2)
min_size = 2;
if (v > 7200 - min_size) {
v = 7200 - min_size;
}
if (v > (field_frequency_max.value() - min_size))
field_frequency_max.set_value(v + min_size);
if (locked_range)
field_frequency_max.set_value(v + min_size);
else
field_frequency_min.set_value(v);
on_range_changed();
}
void GlassView::clip_max(int32_t v) {
int32_t min_size = steps;
if (locked_range)
min_size = search_span;
if (min_size < 2)
min_size = 2;
if (v < min_size) {
v = min_size;
}
if (v < (field_frequency_min.value() + min_size))
field_frequency_min.set_value(v - min_size);
if (locked_range)
field_frequency_min.set_value(v - min_size);
else
field_frequency_max.set_value(v);
on_range_changed();
}
GlassView::GlassView(
NavigationView& nav)
: nav_(nav) {
@@ -328,21 +358,7 @@ GlassView::GlassView(
load_Presets(); // Load available presets from TXT files (or default)
field_frequency_min.on_change = [this](int32_t v) {
reset_live_view(true);
int32_t min_size = steps;
if (locked_range)
min_size = search_span;
if (min_size < 2)
min_size = 2;
if (v > 7200 - min_size) {
v = 7200 - min_size;
field_frequency_min.set_value(v);
}
if (v > (field_frequency_max.value() - min_size))
field_frequency_max.set_value(v + min_size);
if (locked_range)
field_frequency_max.set_value(v + min_size);
on_range_changed();
clip_min(v);
};
field_frequency_min.set_value(presets_db[0].min); // Defaults to first preset
field_frequency_min.set_step(steps);
@@ -350,37 +366,12 @@ GlassView::GlassView(
field_frequency_min.on_select = [this, &nav](NumberField& field) {
auto new_view = nav_.push<FrequencyKeypadView>(field_frequency_min.value() * MHZ_DIV);
new_view->on_changed = [this, &field](rf::Frequency f) {
int32_t freq = f / MHZ_DIV;
int32_t min_size = steps;
if (locked_range)
min_size = search_span;
if (min_size < 2)
min_size = 2;
if (freq > (7200 - min_size))
freq = 7200 - min_size;
field_frequency_min.set_value(freq);
if (field_frequency_max.value() < (freq + min_size))
field_frequency_max.set_value(freq + min_size);
on_range_changed();
clip_min(f / MHZ_DIV);
};
};
field_frequency_max.on_change = [this](int32_t v) {
reset_live_view(true);
int32_t min_size = steps;
if (locked_range)
min_size = search_span;
if (min_size < 2)
min_size = 2;
if (v < min_size) {
v = min_size;
field_frequency_max.set_value(v);
}
if (v < (field_frequency_min.value() + min_size))
field_frequency_min.set_value(v - min_size);
if (locked_range)
field_frequency_min.set_value(v - min_size);
on_range_changed();
clip_max(v);
};
field_frequency_max.set_value(presets_db[0].max); // Defaults to first preset
field_frequency_max.set_step(steps);
@@ -388,18 +379,7 @@ GlassView::GlassView(
field_frequency_max.on_select = [this, &nav](NumberField& field) {
auto new_view = nav_.push<FrequencyKeypadView>(field_frequency_max.value() * MHZ_DIV);
new_view->on_changed = [this, &field](rf::Frequency f) {
int32_t min_size = steps;
if (locked_range)
min_size = search_span;
if (min_size < 2)
min_size = 2;
int32_t freq = f / MHZ_DIV;
if (freq < min_size)
freq = min_size;
field_frequency_max.set_value(freq);
if (field_frequency_min.value() > (freq - min_size))
field_frequency_min.set_value(freq - min_size);
on_range_changed();
clip_max(f / MHZ_DIV);
};
};
@@ -503,18 +483,18 @@ GlassView::GlassView(
};
field_trigger.on_change = [this](int32_t v) {
baseband::set_spectrum(looking_glass_sampling_rate, v);
baseband::set_spectrum(looking_glass_bandwidth, v);
};
field_trigger.set_value(32); // Defaults to 32, as normal triggering resolution
button_range.on_select = [this](Button&) {
if (locked_range) {
locked_range = false;
button_range.set_style(&style_white);
button_range.set_style(&Styles::white);
button_range.set_text(" " + to_string_dec_uint(search_span) + " ");
} else {
locked_range = true;
button_range.set_style(&style_red);
button_range.set_style(&Styles::red);
button_range.set_text(">" + to_string_dec_uint(search_span) + "<");
}
};
@@ -531,10 +511,10 @@ GlassView::GlassView(
};
display.scroll_set_area(109, 319);
baseband::set_spectrum(looking_glass_sampling_rate, field_trigger.value()); // trigger:
// Discord User jteich: WidebandSpectrum::on_message to set the trigger value. In WidebandSpectrum::execute ,
// it keeps adding the output of the fft to the buffer until "trigger" number of calls are made,
// at which time it pushes the buffer up with channel_spectrum.feed
baseband::set_spectrum(looking_glass_bandwidth, field_trigger.value()); // trigger:
// Discord User jteich: WidebandSpectrum::on_message to set the trigger value. In WidebandSpectrum::execute ,
// it keeps adding the output of the fft to the buffer until "trigger" number of calls are made,
// at which time it pushes the buffer up with channel_spectrum.feed
marker_pixel_index = 120;
on_range_changed();
@@ -31,6 +31,7 @@
#include "ui_widget.hpp"
#include "ui_navigation.hpp"
#include "ui_receiver.hpp"
#include "ui_styles.hpp"
#include "string_format.hpp"
#include "analog_audio_app.hpp"
#include "spectrum_color_lut.hpp"
@@ -74,22 +75,11 @@ class GlassView : public View {
std::string label{};
};
const Style style_white{
// free range
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::white(),
};
const Style style_red{
// locked range
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::red(),
};
std::vector<preset_entry> presets_db{};
void clip_min(int32_t v);
void clip_max(int32_t v);
void get_max_power(const ChannelSpectrum& spectrum, uint8_t bin, uint8_t& max_power);
rf::Frequency get_freq_from_bin_pos(uint8_t pos);
void on_marker_change();
int64_t next_mult_of(int64_t num, int64_t multiplier);
void adjust_range(int64_t* f_min, int64_t* f_max, int64_t width);
-10
View File
@@ -196,13 +196,6 @@ void MicTXView::rxaudio(bool is_on) {
}
}
void MicTXView::on_headphone_volume_changed(int32_t v) {
// if (rx_enabled) {
const auto new_volume = volume_t::decibel(v - 99) + audio::headphone::volume_range().max;
receiver_model.set_headphone_volume(new_volume);
//}
}
void MicTXView::set_ptt_visibility(bool v) {
tx_button.hidden(!v);
}
@@ -526,9 +519,6 @@ MicTXView::MicTXView(
set_dirty(); // Refresh interface
};
field_volume.set_value((receiver_model.headphone_volume() - audio::headphone::volume_range().max).decibel() + 99);
field_volume.on_change = [this](int32_t v) { this->on_headphone_volume_changed(v); };
field_rxbw.on_change = [this](size_t, int32_t v) {
if (!(enable_am || enable_usb || enable_lsb || enable_dsb || enable_wfm)) {
// In Previous fw versions, that nbfm_configuration(n) was done in any mode (FM/AM/SSB/DSB)...strictly speaking only need it in (NFM/FM)
+35 -39
View File
@@ -76,7 +76,6 @@ class MicTXView : public View {
void configure_baseband();
void rxaudio(bool is_on);
void on_headphone_volume_changed(int32_t v);
void set_ptt_visibility(bool v);
@@ -123,14 +122,15 @@ class MicTXView : public View {
{{4 * 8, 10 * 8}, "LVL:", Color::light_grey()}, // we delete { {11 * 8, 5 * 8 }, "Amp:", Color::light_grey() },
{{12 * 8, 10 * 8}, "ATT:", Color::light_grey()},
{{20 * 8, 10 * 8}, "DEC:", Color::light_grey()},
{{4 * 8, (13 * 8) - 2}, "TONE KEY:", Color::light_grey()},
{{7 * 8, 23 * 8}, "VOL:", Color::light_grey()},
{{14 * 8, 23 * 8}, "RXBW:", Color::light_grey()}, // we remove the label "FM" because we will display all MOD types RX_BW.
{{20 * 8, 25 * 8}, "SQ:", Color::light_grey()},
{{5 * 8, 25 * 8}, "F_RX:", Color::light_grey()},
{{5 * 8, 27 * 8}, "LNA:", Color::light_grey()},
{{12 * 8, 27 * 8}, "VGA:", Color::light_grey()},
{{19 * 8, 27 * 8}, "AMP:", Color::light_grey()}};
{{3 * 8, (13 * 8) - 5}, "TONE KEY:", Color::light_grey()},
{{3 * 8, (18 * 8) - 1}, "======== Receiver ========", Color::green()},
{{5 * 8, (23 * 8) + 2}, "VOL:", Color::light_grey()},
{{14 * 8, (23 * 8) + 2}, "RXBW:", Color::light_grey()}, // we remove the label "FM" because we will display all MOD types RX_BW.
{{20 * 8, (25 * 8) + 2}, "SQ:", Color::light_grey()},
{{5 * 8, (25 * 8) + 2}, "F_RX:", Color::light_grey()},
{{5 * 8, (27 * 8) + 2}, "LNA:", Color::light_grey()},
{{12 * 8, (27 * 8) + 2}, "VGA:", Color::light_grey()},
{{19 * 8, (27 * 8) + 2}, "AMP:", Color::light_grey()}};
Labels labels_AK4951{
{{3 * 8, 1 * 8}, "MIC-GAIN:", Color::light_grey()},
{{17 * 8, 1 * 8}, "ALC", Color::light_grey()},
@@ -141,14 +141,15 @@ class MicTXView : public View {
{{4 * 8, 10 * 8}, "LVL:", Color::light_grey()}, // we delete { {11 * 8, 5 * 8 }, "Amp:", Color::light_grey() },
{{12 * 8, 10 * 8}, "ATT:", Color::light_grey()},
{{20 * 8, 10 * 8}, "DEC:", Color::light_grey()},
{{4 * 8, (13 * 8) - 2}, "TONE KEY:", Color::light_grey()},
{{(6 * 8) + 4, 23 * 8}, "VOL:", Color::light_grey()},
{{14 * 8, 23 * 8}, "RXBW:", Color::light_grey()}, // we remove the label "FM" because we will display all MOD types RX_BW.
{{20 * 8, 25 * 8}, "SQ:", Color::light_grey()},
{{5 * 8, 25 * 8}, "F_RX:", Color::light_grey()},
{{5 * 8, 27 * 8}, "LNA:", Color::light_grey()},
{{12 * 8, 27 * 8}, "VGA:", Color::light_grey()},
{{19 * 8, 27 * 8}, "AMP:", Color::light_grey()}};
{{3 * 8, (13 * 8) - 5}, "TONE KEY:", Color::light_grey()},
{{3 * 8, (18 * 8) - 1}, "======== Receiver ========", Color::green()},
{{(5 * 8), (23 * 8) + 2}, "VOL:", Color::light_grey()},
{{14 * 8, (23 * 8) + 2}, "RXBW:", Color::light_grey()}, // we remove the label "FM" because we will display all MOD types RX_BW.
{{20 * 8, (25 * 8) + 2}, "SQ:", Color::light_grey()},
{{5 * 8, (25 * 8) + 2}, "F_RX:", Color::light_grey()},
{{5 * 8, (27 * 8) + 2}, "LNA:", Color::light_grey()},
{{12 * 8, (27 * 8) + 2}, "VGA:", Color::light_grey()},
{{19 * 8, (27 * 8) + 2}, "AMP:", Color::light_grey()}};
VuMeter vumeter{
{0 * 8, 1 * 8, 2 * 8, 33 * 8},
@@ -210,7 +211,7 @@ class MicTXView : public View {
OptionsField options_mode{
{24 * 8, 5 * 8},
4,
6,
{
{"NFM/FM", 0},
{" WFM ", 1},
@@ -230,7 +231,7 @@ class MicTXView : public View {
OptionsField field_va{
{17 * 8, 8 * 8},
3,
4,
{{" OFF", 0},
{" PTT", 1},
{"AUTO", 2}}};
@@ -255,38 +256,33 @@ class MicTXView : public View {
' '};
OptionsField options_tone_key{
{10 * 8, (15 * 8) - 2},
{12 * 8, (13 * 8) - 5},
23,
{}};
Checkbox check_rogerbeep{
{3 * 8, (16 * 8) + 7},
{3 * 8, (14 * 8) + 4},
10,
"Roger beep",
false};
Checkbox check_rxactive{
{3 * 8, (21 * 8) - 4},
18, // it was 8, but if it is string size should be 18
"RX audio listening",
{3 * 8, (21 * 8) - 7},
8, // it was 18, but if it is string size should be 8
"RX audio",
false};
Checkbox check_common_freq_tx_rx{
{18 * 8, (16 * 8) + 7},
{18 * 8, (21 * 8) - 7},
8,
"F = F_RX",
"F TX=RX",
false};
NumberField field_volume{
{11 * 8, 23 * 8},
2,
{0, 99},
1,
' ',
};
AudioVolumeField field_volume{
{9 * 8, (23 * 8) + 2}};
OptionsField field_rxbw{
{19 * 8, 23 * 8},
{19 * 8, (23 * 8) + 2},
3,
{
{" NFM1:8k5 ", 0},
@@ -295,7 +291,7 @@ class MicTXView : public View {
}};
NumberField field_squelch{
{23 * 8, 25 * 8},
{23 * 8, (25 * 8) + 2},
2,
{0, 99},
1,
@@ -303,11 +299,11 @@ class MicTXView : public View {
};
FrequencyField field_rxfrequency{
{10 * 8, 25 * 8},
{10 * 8, (25 * 8) + 2},
};
NumberField field_rxlna{
{9 * 8, 27 * 8},
{9 * 8, (27 * 8) + 2},
2,
{0, 40},
8,
@@ -315,7 +311,7 @@ class MicTXView : public View {
};
NumberField field_rxvga{
{16 * 8, 27 * 8},
{16 * 8, (27 * 8) + 2},
2,
{0, 62},
2,
@@ -323,7 +319,7 @@ class MicTXView : public View {
};
NumberField field_rxamp{
{24 * 8, 27 * 8},
{24 * 8, (27 * 8) + 2},
1,
{0, 1},
1,
-6
View File
@@ -27,7 +27,6 @@
#include "ui_widget.hpp"
#include "ui_receiver.hpp"
#include "ui_navigation.hpp"
#include "ui_font_fixed_8x16.hpp"
#include "rtc_time.hpp"
#include "clock_manager.hpp"
#include "baseband_api.hpp"
@@ -63,11 +62,6 @@ class NumbersStationView : public View {
SIGNOFF
};
Style style_red{
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::red()};
typedef struct {
char code;
uint32_t index;
-1
View File
@@ -25,7 +25,6 @@
#include "ui.hpp"
#include "ui_widget.hpp"
#include "ui_font_fixed_8x16.hpp"
#include "baseband_api.hpp"
#include "ui_navigation.hpp"
#include "ui_transmitter.hpp"
+65 -33
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);
total_tracks = 0;
track_number = 0;
playlist_db.clear();
playlist_masterdb.clear();
} else {
start();
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{};
+122 -85
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,26 +30,48 @@ using portapack::memory::map::backup_ram;
namespace ui {
void ReconView::clear_freqlist_for_ui_action() {
audio::output::stop();
// flag to detect and reload frequency_list
freqlist_cleared_for_ui_action = true;
// if in manual mode, there is enough memory to load freqman files, else we have to unload/reload
if (!manual_mode) {
// clear and shrink_to_fit are not enough to really start with a new, clean, empty vector
// swap is the only way to achieve a perfect memory liberation
std::vector<freqman_entry>().swap(frequency_list);
} else {
frequency_list.shrink_to_fit();
}
}
void ReconView::reset_indexes() {
last_entry.modulation = -1;
last_entry.bandwidth = -1;
last_entry.step = -1;
description = "...no description...";
current_index = 0;
}
void ReconView::colorize_waits() {
// colorize wait on match
if (wait == 0) {
field_wait.set_style(&style_blue);
field_wait.set_style(&Styles::blue);
} else if (wait >= 500) {
field_wait.set_style(&style_white);
field_wait.set_style(&Styles::white);
} else if (wait > -500 && wait < 500) {
field_wait.set_style(&style_red);
field_wait.set_style(&Styles::red);
} else if (wait <= -500) {
field_wait.set_style(&style_green);
field_wait.set_style(&Styles::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);
field_lock_wait.set_style(&Styles::yellow);
} else {
field_lock_wait.set_style(&style_white);
field_lock_wait.set_style(&Styles::white);
}
} else {
field_lock_wait.set_style(&style_white);
field_lock_wait.set_style(&Styles::white);
}
}
@@ -159,7 +182,6 @@ bool ReconView::recon_load_config_from_sd() {
squelch = -14;
recon_match_mode = RECON_MATCH_CONTINUOUS;
wait = RECON_DEF_WAIT_DURATION;
volume = 40;
return false;
}
@@ -198,11 +220,6 @@ bool ReconView::recon_load_config_from_sd() {
else
wait = RECON_DEF_WAIT_DURATION;
if (it > 7)
volume = strtoll(params[7].c_str(), nullptr, 10);
else
volume = 40;
return true;
}
@@ -221,13 +238,12 @@ bool ReconView::recon_save_config_to_sd() {
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));
return true;
}
void ReconView::audio_output_start() {
audio::output::start();
this->on_headphone_volume_changed((receiver_model.headphone_volume() - audio::headphone::volume_range().max).decibel() + 99);
receiver_model.set_headphone_volume(receiver_model.headphone_volume()); // WM8731 hack.
}
void ReconView::recon_redraw() {
@@ -247,17 +263,17 @@ void ReconView::recon_redraw() {
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);
big_display.set_style(&Styles::white);
if (recon)
button_pause.set_text("<PAUSE>");
else
button_pause.set_text("<RESUME>");
} else if (freq_lock == 1 && recon_lock_nb_match != 1) {
// STARTING LOCK FREQ
big_display.set_style(&style_yellow);
big_display.set_style(&Styles::yellow);
button_pause.set_text("<SKPLCK>");
} else if (freq_lock >= recon_lock_nb_match) {
big_display.set_style(&style_green);
big_display.set_style(&Styles::green);
button_pause.set_text("<UNLOCK>");
// FREQ IS STRONG: GREEN and recon will pause when on_statistics_update()
if ((!scanner_mode) && autosave && frequency_list.size() > 0) {
@@ -366,7 +382,7 @@ ReconView::ReconView(NavigationView& nav)
&field_squelch,
&field_wait,
&field_lock_wait,
&button_recon_setup,
&button_config,
&button_scanner_mode,
&button_looking_glass,
&file_name,
@@ -415,7 +431,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
auto rc = settings.load("recon", &app_settings);
@@ -428,6 +443,7 @@ ReconView::ReconView(NavigationView& nav)
}
button_manual_start.on_select = [this, &nav](ButtonWithEncoder& button) {
clear_freqlist_for_ui_action();
auto new_view = nav_.push<FrequencyKeypadView>(frequency_range.min);
new_view->on_changed = [this, &button](rf::Frequency f) {
frequency_range.min = f;
@@ -436,6 +452,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;
@@ -543,13 +560,28 @@ ReconView::ReconView(NavigationView& nav)
};
button_mic_app.on_select = [this](Button&) {
if (frequency_list.size() > 0 && current_index >= 0 && (unsigned)current_index < frequency_list.size()) {
if (frequency_list[current_index].type == HAMRADIO) {
// if it's a HAMRADIO entry, then frequency_a is the freq at which the repeater reveive, so we have to set it in transmit in mic app
transmitter_model.set_tuning_frequency(frequency_list[current_index].frequency_a);
// if it's a HAMRADIO entry, then frequency_b is the freq at which the repeater transmit, so we have to set it in receive in mic app
receiver_model.set_tuning_frequency(frequency_list[current_index].frequency_b);
} else {
// it's single or range so we us actual tuned frequency
transmitter_model.set_tuning_frequency(freq);
receiver_model.set_tuning_frequency(freq);
}
}
// there is no way yet to set modulation and bandwidth from Recon to MicApp
nav_.pop();
nav_.push<MicTXView>();
};
button_remove.on_select = [this](ButtonWithEncoder&) {
if (frequency_list.size() > 0) {
if (scanner_mode) {
if (!manual_mode) {
// scanner or recon (!scanner) mode
// in both we delete index from live view, but only remove in output file in scanner_mode
if (current_index >= (int32_t)frequency_list.size()) {
current_index = frequency_list.size() - 1;
}
@@ -575,11 +607,11 @@ ReconView::ReconView(NavigationView& nav)
desc_cycle.set("...no description...");
}
text_cycle.set_text(to_string_dec_uint(current_index + 1, 3));
}
// also remove from output file if in scanner mode
if (scanner_mode) {
File freqman_file;
delete_file(freq_file_path);
auto result = freqman_file.create(freq_file_path);
if (!result.is_valid()) {
for (size_t n = 0; n < frequency_list.size(); n++) {
@@ -589,7 +621,7 @@ ReconView::ReconView(NavigationView& nav)
}
}
}
} else // RECON MODE / MANUAL, only remove matching from output
} else if (manual_mode) // only remove from output
{
File recon_file;
File tmp_recon_file;
@@ -653,6 +685,7 @@ ReconView::ReconView(NavigationView& nav)
};
button_manual_recon.on_select = [this](Button&) {
button_remove.set_text("DELETE");
scanner_mode = false;
manual_mode = true;
recon_pause();
@@ -663,7 +696,10 @@ ReconView::ReconView(NavigationView& nav)
} else {
audio::output::stop();
frequency_list.clear();
// clear and shrink_to_fit are not enough to really start with a new, clean, empty vector
// swap is the only way to achieve a perfect memory liberation
std::vector<freqman_entry>().swap(frequency_list);
freqman_entry manual_freq_entry;
def_step = step_mode.selected_index(); // max range val
@@ -680,18 +716,18 @@ ReconView::ReconView(NavigationView& nav)
frequency_list.push_back(manual_freq_entry);
big_display.set_style(&style_white); // Back to white color
big_display.set_style(&Styles::white); // Back to white color
freq_stats.set_style(&style_white);
freq_stats.set_style(&Styles::white);
freq_stats.set("0/0/0");
text_cycle.set_text("1");
text_max.set("/1");
button_scanner_mode.set_style(&style_white);
button_scanner_mode.set_style(&Styles::white);
button_scanner_mode.set_text("MSEARCH");
file_name.set_style(&style_white);
file_name.set_style(&Styles::white);
file_name.set("MANUAL RANGE RECON");
desc_cycle.set_style(&style_white);
desc_cycle.set_style(&Styles::white);
last_entry.modulation = -1;
last_entry.bandwidth = -1;
@@ -731,12 +767,12 @@ ReconView::ReconView(NavigationView& nav)
recon_resume();
}
if (scanner_mode) {
file_name.set_style(&style_red);
button_scanner_mode.set_style(&style_red);
file_name.set_style(&Styles::red);
button_scanner_mode.set_style(&Styles::red);
button_scanner_mode.set_text("SCANNER");
} else {
file_name.set_style(&style_blue);
button_scanner_mode.set_style(&style_blue);
file_name.set_style(&Styles::blue);
button_scanner_mode.set_style(&Styles::blue);
button_scanner_mode.set_text("RECON");
}
};
@@ -756,12 +792,14 @@ ReconView::ReconView(NavigationView& nav)
manual_mode = false;
if (scanner_mode) {
scanner_mode = false;
button_scanner_mode.set_style(&style_blue);
button_scanner_mode.set_style(&Styles::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_style(&Styles::red);
button_scanner_mode.set_text("SCANNER");
button_scanner_mode.set_text("REMOVE");
}
frequency_file_load(true);
if (autostart) {
@@ -771,10 +809,12 @@ ReconView::ReconView(NavigationView& nav)
}
};
button_recon_setup.on_select = [this, &nav](Button&) {
audio::output::stop();
button_config.on_select = [this, &nav](Button&) {
clear_freqlist_for_ui_action();
auto open_view = nav.push<ReconSetupView>(input_file, output_file, recon_lock_duration, recon_lock_nb_match, recon_match_mode);
open_view->on_changed = [this](std::vector<std::string> result) {
freqlist_cleared_for_ui_action = false;
input_file = result[0];
output_file = result[1];
freq_file_path = "/FREQMAN/" + output_file + ".TXT";
@@ -820,12 +860,8 @@ ReconView::ReconView(NavigationView& nav)
squelch = v;
};
field_volume.on_change = [this](int32_t v) {
this->on_headphone_volume_changed(v);
};
// PRE-CONFIGURATION:
button_scanner_mode.set_style(&style_blue);
button_scanner_mode.set_style(&Styles::blue);
button_scanner_mode.set_text("RECON");
file_name.set("=>");
@@ -838,8 +874,6 @@ ReconView::ReconView(NavigationView& nav)
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
freqman_set_modulation_option(field_mode);
freqman_set_step_option(step_mode);
@@ -867,36 +901,37 @@ void ReconView::frequency_file_load(bool stop_all_before) {
audio::output::stop();
def_step = step_mode.selected_index(); // use def_step from manual selector
frequency_list.clear(); // clear the existing frequency list (expected behavior)
std::string file_input = input_file; // default recon mode
// clear and shrink_to_fit are not enough to really start with a new, clean, empty vector
// swap is the only way to achieve a perfect memory liberation
std::vector<freqman_entry>().swap(frequency_list); // clear the existing frequency list (expected behavior)
std::string file_input = input_file; // default recon mode
if (scanner_mode) {
file_input = output_file;
file_name.set_style(&style_red);
button_scanner_mode.set_style(&style_red);
file_name.set_style(&Styles::red);
button_scanner_mode.set_style(&Styles::red);
button_scanner_mode.set_text("SCANNER");
} else {
file_name.set_style(&style_blue);
button_scanner_mode.set_style(&style_blue);
file_name.set_style(&Styles::blue);
button_scanner_mode.set_style(&Styles::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);
desc_cycle.set_style(&style_red);
desc_cycle.set_style(&Styles::white);
if (!load_freqman_file_ex(file_input, frequency_list, load_freqs, load_ranges, load_hamradios, RECON_FREQMAN_MAX_PER_FILE)) {
file_name.set_style(&Styles::red);
desc_cycle.set_style(&Styles::red);
desc_cycle.set(" NO " + file_input + ".TXT FILE ...");
file_name.set("=> NO DATA");
} else {
file_name.set("=> " + file_input);
if (frequency_list.size() == 0) {
file_name.set_style(&style_red);
desc_cycle.set_style(&style_red);
file_name.set_style(&Styles::red);
desc_cycle.set_style(&Styles::red);
desc_cycle.set("/0 no entries in list");
file_name.set("BadOrEmpty " + file_input);
} else {
if (frequency_list.size() > FREQMAN_MAX_PER_FILE) {
file_name.set_style(&style_yellow);
desc_cycle.set_style(&style_yellow);
if (frequency_list.size() > RECON_FREQMAN_MAX_PER_FILE) {
file_name.set_style(&Styles::yellow);
desc_cycle.set_style(&Styles::yellow);
}
}
}
@@ -933,15 +968,10 @@ void ReconView::frequency_file_load(bool stop_all_before) {
default:
break;
}
last_entry.modulation = -1;
last_entry.bandwidth = -1;
last_entry.step = -1;
reset_indexes();
step_mode.set_selected_index(def_step); // Impose the default step into the manual step selector
receiver_model.enable();
receiver_model.set_squelch_level(0);
std::string description = "...no description...";
current_index = 0;
if (frequency_list.size() != 0) {
switch (frequency_list[current_index].type) {
case RANGE:
@@ -975,6 +1005,18 @@ void ReconView::frequency_file_load(bool stop_all_before) {
}
void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
// hack to reload the list if it was cleared by going into CONFIG
if (freqlist_cleared_for_ui_action) {
if (!manual_mode) {
frequency_file_load(false);
}
if (autostart) {
recon_resume();
} else {
recon_pause();
}
freqlist_cleared_for_ui_action = false;
}
db = statistics.max_db;
if (recon) {
if (!timer) {
@@ -982,7 +1024,7 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
continuous_lock = false;
freq_lock = 0;
timer = recon_lock_duration;
big_display.set_style(&style_white);
big_display.set_style(&Styles::white);
}
if (freq_lock < recon_lock_nb_match) // LOCKING
{
@@ -1197,7 +1239,7 @@ void ReconView::recon_pause() {
audio_output_start();
big_display.set_style(&style_white);
big_display.set_style(&Styles::white);
button_pause.set_text("<RESUME>"); // PAUSED, show resume
}
@@ -1209,7 +1251,7 @@ void ReconView::recon_resume() {
audio::output::stop();
big_display.set_style(&style_white);
big_display.set_style(&Styles::white);
button_pause.set_text("<PAUSE>");
}
@@ -1245,11 +1287,6 @@ void ReconView::on_stepper_delta(int32_t v) {
timer = 0;
}
void ReconView::on_headphone_volume_changed(int32_t v) {
const auto new_volume = volume_t::decibel(v - 99) + audio::headphone::volume_range().max;
receiver_model.set_headphone_volume(new_volume);
}
size_t ReconView::change_mode(freqman_index_t new_mod) {
field_mode.on_change = [this](size_t, OptionsField::value_t) {};
field_bw.on_change = [this](size_t, OptionsField::value_t) {};
@@ -1261,11 +1298,11 @@ size_t ReconView::change_mode(freqman_index_t new_mod) {
case AM_MODULATION:
freqman_set_bandwidth_option(new_mod, field_bw);
// bw DSB (0) default
field_bw.set_selected_index(0);
field_bw.set_by_value(0);
baseband::run_image(portapack::spi_flash::image_tag_am_audio);
receiver_model.set_modulation(ReceiverModel::Mode::AMAudio);
receiver_model.set_am_configuration(field_bw.selected_index());
field_bw.on_change = [this](size_t n, OptionsField::value_t) { receiver_model.set_am_configuration(n); };
receiver_model.set_am_configuration(field_bw.selected_index_value());
field_bw.on_change = [this](size_t, OptionsField::value_t n) { receiver_model.set_am_configuration(n); };
receiver_model.set_sampling_rate(3072000);
receiver_model.set_baseband_bandwidth(1750000);
text_ctcss.set(" ");
@@ -1273,22 +1310,22 @@ size_t ReconView::change_mode(freqman_index_t new_mod) {
case NFM_MODULATION:
freqman_set_bandwidth_option(new_mod, field_bw);
// bw 16k (2) default
field_bw.set_selected_index(2);
field_bw.set_by_value(2);
baseband::run_image(portapack::spi_flash::image_tag_nfm_audio);
receiver_model.set_modulation(ReceiverModel::Mode::NarrowbandFMAudio);
receiver_model.set_nbfm_configuration(field_bw.selected_index());
field_bw.on_change = [this](size_t n, OptionsField::value_t) { receiver_model.set_nbfm_configuration(n); };
receiver_model.set_nbfm_configuration(field_bw.selected_index_value());
field_bw.on_change = [this](size_t, OptionsField::value_t n) { receiver_model.set_nbfm_configuration(n); };
receiver_model.set_sampling_rate(3072000);
receiver_model.set_baseband_bandwidth(1750000);
break;
case WFM_MODULATION:
freqman_set_bandwidth_option(new_mod, field_bw);
// bw 200k (0) default
field_bw.set_selected_index(0);
field_bw.set_by_value(0);
baseband::run_image(portapack::spi_flash::image_tag_wfm_audio);
receiver_model.set_modulation(ReceiverModel::Mode::WidebandFMAudio);
receiver_model.set_wfm_configuration(field_bw.selected_index());
field_bw.on_change = [this](size_t n, OptionsField::value_t) { receiver_model.set_wfm_configuration(n); };
receiver_model.set_wfm_configuration(field_bw.selected_index_value());
field_bw.on_change = [this](size_t, OptionsField::value_t n) { receiver_model.set_wfm_configuration(n); };
receiver_model.set_sampling_rate(3072000);
receiver_model.set_baseband_bandwidth(1750000);
text_ctcss.set(" ");
+10 -53
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.
*
@@ -26,7 +27,7 @@
#include "ui.hpp"
#include "receiver_model.hpp"
#include "ui_receiver.hpp"
#include "ui_font_fixed_8x16.hpp"
#include "ui_styles.hpp"
#include "freqman.hpp"
#include "analog_audio_app.hpp"
#include "audio.hpp"
@@ -43,6 +44,7 @@
namespace ui {
#define RECON_CFG_FILE "SETTINGS/recon.cfg"
#define RECON_FREQMAN_MAX_PER_FILE 99 // maximum tested limit for load frequency from freqman to work
class ReconView : public View {
public:
@@ -51,48 +53,6 @@ class ReconView : public View {
void focus() override;
const Style style_grey{
// recon
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::grey(),
};
const Style style_white{
// recon
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::white(),
};
const Style style_yellow{
// found signal
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::yellow(),
};
const Style style_green{
// Found signal
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::green(),
};
const Style style_red{
// erasing freq
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::red(),
};
const Style style_blue{
// quick recon, wait == 0
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::blue(),
};
std::string title() const override { return "Recon"; };
// void set_parent_rect(const Rect new_parent_rect) override;
@@ -100,6 +60,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);
@@ -108,7 +70,6 @@ class ReconView : public View {
void recon_resume();
void frequency_file_load(bool stop_all_before = false);
void on_statistics_update(const ChannelStatistics& statistics);
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();
@@ -126,8 +87,10 @@ class ReconView : public View {
freqman_db frequency_list = {};
int32_t current_index{0};
bool continuous_lock{false};
bool freqlist_cleared_for_ui_action{false}; // flag positioned by ui widgets to manage freqlist unload/load
std::string input_file = {"RECON"};
std::string output_file = {"RECON_RESULTS"};
std::string description = {"...no description..."};
bool autosave = {true};
bool autostart = {true};
bool continuous = {true};
@@ -144,7 +107,6 @@ class ReconView : public View {
bool scanner_mode{false};
bool manual_mode{false};
bool sd_card_mounted = false;
int32_t volume = 40;
int32_t stepper = 0;
int32_t index_stepper = 0;
int64_t freq = 0;
@@ -190,13 +152,8 @@ class ReconView : public View {
RFAmpField field_rf_amp{
{18 * 8, 0 * 16}};
NumberField field_volume{
{24 * 8, 0 * 16},
2,
{0, 99},
1,
' ',
};
AudioVolumeField field_volume{
{24 * 8, 0 * 16}};
OptionsField field_bw{
{3 * 8, 1 * 16},
@@ -270,7 +227,7 @@ class ReconView : public View {
{12 * 8 + 4, 7 * 16, 14 * 8, 1 * 8},
""};
Button button_recon_setup{
Button button_config{
{SCREEN_W - 7 * 8, 2 * 16, 7 * 8, 28},
"CONFIG"};
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
* Copyright (C) 2023 gullradriel, Nilorea Studio Inc.
*
* This file is part of PortaPack.
*
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
* Copyright (C) 2023 gullradriel, Nilorea Studio Inc.
*
* This file is part of PortaPack.
*
@@ -59,7 +60,7 @@
//#define SCREEN_H 320
// recon settings nb params
#define RECON_SETTINGS_NB_PARAMS 8
#define RECON_SETTINGS_NB_PARAMS 7
namespace ui {
+21 -29
View File
@@ -179,16 +179,16 @@ void ScannerView::bigdisplay_update(int32_t v) {
switch (bigdisplay_current_color) {
case BDC_GREY:
big_display.set_style(&style_grey);
big_display.set_style(&Styles::grey);
break;
case BDC_YELLOW:
big_display.set_style(&style_yellow);
big_display.set_style(&Styles::yellow);
break;
case BDC_GREEN:
big_display.set_style(&style_green);
big_display.set_style(&Styles::green);
break;
case BDC_RED:
big_display.set_style(&style_red);
big_display.set_style(&Styles::red);
break;
default:
break;
@@ -254,10 +254,10 @@ void ScannerView::show_max_index() { // show total number of freqs to scan
text_current_index.set("---");
if (frequency_list.size() == FREQMAN_MAX_PER_FILE) {
text_max_index.set_style(&style_red);
text_max_index.set_style(&Styles::red);
text_max_index.set("/ " + to_string_dec_uint(FREQMAN_MAX_PER_FILE) + " (DB MAX!)");
} else {
text_max_index.set_style(&style_grey);
text_max_index.set_style(&Styles::grey);
text_max_index.set("/ " + to_string_dec_uint(frequency_list.size()));
}
}
@@ -558,9 +558,6 @@ ScannerView::ScannerView(
field_squelch.on_change = [this](int32_t v) { squelch = v; };
field_squelch.set_value(-10);
field_volume.set_value((receiver_model.headphone_volume() - audio::headphone::volume_range().max).decibel() + 99);
field_volume.on_change = [this](int32_t v) { this->on_headphone_volume_changed(v); };
// LEARN FREQUENCIES
std::string scanner_txt = "SCANNER";
frequency_file_load(scanner_txt);
@@ -667,8 +664,8 @@ void ScannerView::update_squelch_while_paused(int32_t max_db) {
// Update audio & color based on signal level even if paused
if (++color_timer > 2) { // Counter to reduce color toggling when weak signal
if (max_db > squelch) {
audio::output::start(); // Re-enable audio when signal goes above squelch
on_headphone_volume_changed(field_volume.value()); // quick fix to make sure WM8731S chips don't stay silent after pause
audio::output::start(); // Re-enable audio when signal goes above squelch
receiver_model.set_headphone_volume(receiver_model.headphone_volume()); // quick fix to make sure WM8731S chips don't stay silent after pause
bigdisplay_update(BDC_GREEN);
} else {
audio::output::stop(); // Silence audio when signal drops below squelch
@@ -730,7 +727,7 @@ void ScannerView::scan_pause() {
scan_thread->set_scanning(false); // WE STOP SCANNING
}
audio::output::start();
on_headphone_volume_changed(field_volume.value()); // quick fix to make sure WM8731S chips don't stay silent after pause
receiver_model.set_headphone_volume(receiver_model.headphone_volume()); // quick fix to make sure WM8731S chips don't stay silent after pause
}
void ScannerView::scan_resume() {
@@ -744,14 +741,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
}
void ScannerView::on_headphone_volume_changed(int32_t v) {
const auto new_volume = volume_t::decibel(v - 99) + audio::headphone::volume_range().max;
receiver_model.set_headphone_volume(new_volume);
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::change_mode(freqman_index_t new_mod) { // Before this, do a scan_thread->stop(); After this do a start_scan_thread()
@@ -767,9 +759,9 @@ void ScannerView::change_mode(freqman_index_t new_mod) { // Before this, do a s
freqman_set_bandwidth_option(new_mod, field_bw);
baseband::run_image(portapack::spi_flash::image_tag_am_audio);
receiver_model.set_modulation(ReceiverModel::Mode::AMAudio);
field_bw.set_selected_index(0);
receiver_model.set_am_configuration(field_bw.selected_index());
field_bw.on_change = [this](size_t n, OptionsField::value_t) { receiver_model.set_am_configuration(n); };
field_bw.set_by_value(0);
receiver_model.set_am_configuration(field_bw.selected_index_value());
field_bw.on_change = [this](size_t, OptionsField::value_t n) { receiver_model.set_am_configuration(n); };
receiver_model.set_sampling_rate(3072000);
receiver_model.set_baseband_bandwidth(1750000);
break;
@@ -777,9 +769,9 @@ void ScannerView::change_mode(freqman_index_t new_mod) { // Before this, do a s
freqman_set_bandwidth_option(new_mod, field_bw);
baseband::run_image(portapack::spi_flash::image_tag_nfm_audio);
receiver_model.set_modulation(ReceiverModel::Mode::NarrowbandFMAudio);
field_bw.set_selected_index(2);
receiver_model.set_nbfm_configuration(field_bw.selected_index());
field_bw.on_change = [this](size_t n, OptionsField::value_t) { receiver_model.set_nbfm_configuration(n); };
field_bw.set_by_value(2);
receiver_model.set_nbfm_configuration(field_bw.selected_index_value());
field_bw.on_change = [this](size_t, OptionsField::value_t n) { receiver_model.set_nbfm_configuration(n); };
receiver_model.set_sampling_rate(3072000);
receiver_model.set_baseband_bandwidth(1750000);
break;
@@ -787,9 +779,9 @@ void ScannerView::change_mode(freqman_index_t new_mod) { // Before this, do a s
freqman_set_bandwidth_option(new_mod, field_bw);
baseband::run_image(portapack::spi_flash::image_tag_wfm_audio);
receiver_model.set_modulation(ReceiverModel::Mode::WidebandFMAudio);
field_bw.set_selected_index(0);
receiver_model.set_wfm_configuration(field_bw.selected_index());
field_bw.on_change = [this](size_t n, OptionsField::value_t) { receiver_model.set_wfm_configuration(n); };
field_bw.set_by_value(0);
receiver_model.set_wfm_configuration(field_bw.selected_index_value());
field_bw.on_change = [this](size_t, OptionsField::value_t n) { receiver_model.set_wfm_configuration(n); };
receiver_model.set_sampling_rate(3072000);
receiver_model.set_baseband_bandwidth(2000000);
break;
+3 -37
View File
@@ -23,7 +23,7 @@
#include "ui.hpp"
#include "receiver_model.hpp"
#include "ui_receiver.hpp"
#include "ui_font_fixed_8x16.hpp"
#include "ui_styles.hpp"
#include "freqman.hpp"
#include "analog_audio_app.hpp"
#include "audio.hpp"
@@ -87,34 +87,6 @@ class ScannerView : public View {
void focus() override;
const Style style_grey{
// scanning
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::grey(),
};
const Style style_yellow{
// Found signal
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::dark_yellow(),
};
const Style style_green{
// Found signal
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::green(),
};
const Style style_red{
// erasing freq
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::red(),
};
std::string title() const override { return "Scanner"; };
std::vector<rf::Frequency> frequency_list{};
std::vector<string> description_list{};
@@ -134,7 +106,6 @@ class ScannerView : public View {
void bigdisplay_update(int32_t);
void update_squelch_while_paused(int32_t max_db);
void on_statistics_update(const ChannelStatistics& statistics);
void on_headphone_volume_changed(int32_t v);
void handle_retune(int64_t freq, uint32_t freq_idx);
jammer::jammer_range_t frequency_range{false, 0, 0}; // perfect for manual scan task too...
@@ -182,13 +153,8 @@ class ScannerView : public View {
RFAmpField field_rf_amp{
{18 * 8, 0 * 16}};
NumberField field_volume{
{24 * 8, 0 * 16},
2,
{0, 99},
1,
' ',
};
AudioVolumeField field_volume{
{24 * 8, 0 * 16}};
OptionsField field_bw{
{3 * 8, 1 * 16},
+7 -7
View File
@@ -117,7 +117,7 @@ void SearchView::do_detection() {
recent_entries_view.set_dirty();
text_infos.set("Locked ! ");
big_display.set_style(&style_locked);
big_display.set_style(&Styles::green);
locked = true;
locked_bin = bin_max;
@@ -152,7 +152,7 @@ void SearchView::do_detection() {
recent_entries_view.set_dirty();
text_infos.set("Listening");
big_display.set_style(&style_grey);
big_display.set_style(&Styles::grey);
}
}
}
@@ -362,11 +362,11 @@ SearchView::SearchView(
nav.push<FrequencyKeypadView>(entry.frequency);
};
text_mean.set_style(&style_grey);
text_slices.set_style(&style_grey);
text_rate.set_style(&style_grey);
progress_timers.set_style(&style_grey);
big_display.set_style(&style_grey);
text_mean.set_style(&Styles::grey);
text_slices.set_style(&Styles::grey);
text_rate.set_style(&Styles::grey);
progress_timers.set_style(&Styles::grey);
big_display.set_style(&Styles::grey);
check_snap.set_value(true);
options_snap.set_selected_index(1); // 12.5kHz
+1 -14
View File
@@ -25,7 +25,7 @@
#include "spectrum_color_lut.hpp"
#include "ui_receiver.hpp"
#include "ui_font_fixed_8x16.hpp"
#include "ui_styles.hpp"
#include "recent_entries.hpp"
namespace ui {
@@ -90,19 +90,6 @@ class SearchView : public View {
private:
NavigationView& nav_;
const Style style_grey{
// For informations and lost signal
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::grey(),
};
const Style style_locked{
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::green(),
};
struct slice_t {
rf::Frequency center_frequency;
uint8_t max_power;
+53 -25
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.
*
@@ -35,7 +36,7 @@ using portapack::receiver_model;
using namespace portapack;
#include "string_format.hpp"
#include "ui_font_fixed_8x16.hpp"
#include "ui_styles.hpp"
#include "cpld_update.hpp"
#include "freqman.hpp"
@@ -128,9 +129,9 @@ SetRadioView::SetRadioView(
value_source.set(source_name);
value_source_frequency.set(to_string_dec_uint(reference.frequency / 1000000, 2) + "." + to_string_dec_uint((reference.frequency % 1000000) / 100, 4, '0') + " MHz");
label_source.set_style(&style_text);
value_source.set_style(&style_text);
value_source_frequency.set_style(&style_text);
label_source.set_style(&Styles::light_grey);
value_source.set_style(&Styles::light_grey);
value_source_frequency.set_style(&Styles::light_grey);
add_children({
&label_source,
@@ -168,7 +169,7 @@ SetRadioView::SetRadioView(
};
field_clkout_freq.set_value(portapack::persistent_memory::clkout_freq());
value_freq_step.set_style(&style_text);
value_freq_step.set_style(&Styles::light_grey);
field_clkout_freq.on_select = [this](NumberField&) {
freq_step_khz++;
@@ -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);
};
@@ -436,27 +446,16 @@ SetPersistentMemoryView::SetPersistentMemoryView(NavigationView& nav) {
add_children({&text_pmem_about,
&text_pmem_informations,
&text_pmem_status,
&check_load_mem_at_startup,
&check_use_sdcard_for_pmem,
&button_save_mem_to_file,
&button_load_mem_from_file,
&button_load_mem_defaults,
&button_return});
bool load_mem_at_startup = false;
File pmem_flag_file_handle;
std::string folder = "SETTINGS";
make_new_directory(folder);
std::string pmem_flag_file = "/SETTINGS/PMEM_FILEFLAG";
auto result = pmem_flag_file_handle.open(pmem_flag_file);
if (!result.is_valid()) {
load_mem_at_startup = true;
}
check_load_mem_at_startup.set_value(load_mem_at_startup);
check_load_mem_at_startup.on_select = [this](Checkbox&, bool v) {
check_use_sdcard_for_pmem.set_value(portapack::persistent_memory::should_use_sdcard_for_pmem());
check_use_sdcard_for_pmem.on_select = [this](Checkbox&, bool v) {
File pmem_flag_file_handle;
std::string pmem_flag_file = "/SETTINGS/PMEM_FILEFLAG";
std::string pmem_flag_file = PMEM_FILEFLAG;
if (v) {
auto result = pmem_flag_file_handle.open(pmem_flag_file);
if (result.is_valid()) {
@@ -480,7 +479,7 @@ SetPersistentMemoryView::SetPersistentMemoryView(NavigationView& nav) {
};
button_save_mem_to_file.on_select = [&nav, this](Button&) {
if (!portapack::persistent_memory::save_persistent_settings_to_file("SETTINGS/pmem_settings")) {
if (!portapack::persistent_memory::save_persistent_settings_to_file()) {
text_pmem_status.set("!problem saving settings!");
} else {
text_pmem_status.set("settings saved");
@@ -488,7 +487,7 @@ SetPersistentMemoryView::SetPersistentMemoryView(NavigationView& nav) {
};
button_load_mem_from_file.on_select = [&nav, this](Button&) {
if (!portapack::persistent_memory::load_persistent_settings_from_file("SETTINGS/pmem_settings")) {
if (!portapack::persistent_memory::load_persistent_settings_from_file()) {
text_pmem_status.set("!problem loading settings!");
} else {
text_pmem_status.set("settings loaded");
@@ -519,9 +518,9 @@ void SetPersistentMemoryView::focus() {
button_return.focus();
}
//
// Audio settings
//
// ---------------------------------------------------------
// Audio Settings
// ---------------------------------------------------------
SetAudioView::SetAudioView(NavigationView& nav) {
add_children({&labels,
&field_tone_mix,
@@ -544,6 +543,9 @@ void SetAudioView::focus() {
button_save.focus();
}
// ---------------------------------------------------------
// QR Code Settings
// ------------------------------------------------------
SetQRCodeView::SetQRCodeView(NavigationView& nav) {
add_children({&checkbox_bigger_qr,
&button_save,
@@ -565,6 +567,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 +610,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 -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.
*
@@ -126,11 +127,6 @@ class SetRadioView : public View {
std::string title() const override { return "Radio"; };
private:
const Style style_text{
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::light_grey(),
};
uint8_t freq_step_khz = 3;
Text label_source{
@@ -431,6 +427,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);
@@ -452,10 +480,10 @@ class SetPersistentMemoryView : public View {
{0, 3 * 16, 240, 16},
""};
Checkbox check_load_mem_at_startup{
Checkbox check_use_sdcard_for_pmem{
{18, 6 * 16},
19,
"load from sd at startup"};
"use sdcard for p.mem"};
Button button_save_mem_to_file{
{0, 8 * 16, 240, 32},
+3 -3
View File
@@ -131,9 +131,9 @@ SIGFRXView::SIGFRXView(
&text_data,
&button_exit});
text_type.set_style(&style_white);
text_channel.set_style(&style_white);
text_data.set_style(&style_white);
text_type.set_style(&Styles::bg_white);
text_channel.set_style(&Styles::bg_white);
text_data.set_style(&Styles::bg_white);
button_exit.on_select = [&nav](Button&) {
nav.pop();
+1 -6
View File
@@ -24,8 +24,8 @@
#include "ui_widget.hpp"
#include "ui_painter.hpp"
#include "ui_menu.hpp"
#include "ui_styles.hpp"
#include "ui_navigation.hpp"
#include "ui_font_fixed_8x16.hpp"
#include "clock_manager.hpp"
#include "message.hpp"
#include "rf_path.hpp"
@@ -50,11 +50,6 @@ class SIGFRXView : public View {
uint8_t last_channel;
uint8_t detect_counter = 0;
const Style style_white{
.font = font::fixed_8x16,
.background = Color::white(),
.foreground = Color::black()};
const uint16_t sigfrx_marks[18] = {
10, 8, 0,
60, 52, 90,
+2 -16
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&) {
@@ -128,13 +128,6 @@ SondeView::SondeView(NavigationView& nav) {
if (logger)
logger->append(LOG_ROOT_DIR "/SONDE.TXT");
// initialize audio:
field_volume.set_value((receiver_model.headphone_volume() - audio::headphone::volume_range().max).decibel() + 99);
field_volume.on_change = [this](int32_t v) {
this->on_headphone_volume_changed(v);
};
audio::output::start();
audio::output::speaker_unmute();
@@ -155,7 +148,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();
}
@@ -251,15 +244,8 @@ void SondeView::on_packet(const sonde::Packet& packet) {
}
}
void SondeView::on_headphone_volume_changed(int32_t v) {
const auto new_volume = volume_t::decibel(v - 99) + audio::headphone::volume_range().max;
receiver_model.set_headphone_volume(new_volume);
}
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_);
}
+2 -8
View File
@@ -109,13 +109,8 @@ class SondeView : public View {
{21 * 8, 0, 6 * 8, 4},
};
NumberField field_volume{
{28 * 8, 0 * 16},
2,
{0, 99},
1,
' ',
};
AudioVolumeField field_volume{
{28 * 8, 0 * 16}};
Checkbox check_beep{
{22 * 8, 6 * 16},
@@ -181,7 +176,6 @@ class SondeView : public View {
}};
void on_packet(const sonde::Packet& packet);
void on_headphone_volume_changed(int32_t v);
char* float_to_char(float x, char* p);
void set_target_frequency(const uint32_t new_value);
@@ -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;
@@ -234,14 +234,14 @@ std::vector<uint8_t> SpectrumInputImageView::get_line(uint16_t y) {
grey_buffer[px] = color.to_greyscale();
}
delete buffer;
delete[] buffer;
std::vector<uint8_t> values(width);
for (int i = 0; i < width; i++) {
values[i] = grey_buffer[i];
}
delete grey_buffer;
delete[] grey_buffer;
return values;
}
@@ -20,6 +20,7 @@
*/
#include "ui_spectrum_painter_text.hpp"
#include "ui_font_fixed_8x16.hpp"
#include "cpld_update.hpp"
#include "bmp.hpp"
#include "baseband_api.hpp"
@@ -28,7 +29,6 @@
#include "io_file.hpp"
#include "file.hpp"
#include "portapack_persistent_memory.hpp"
#include "ui_font_fixed_8x16.hpp"
namespace ui {
@@ -0,0 +1,99 @@
/*
* Copyright (C) 2023 Kyle Reed
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include "ui_ss_viewer.hpp"
using namespace portapack;
namespace fs = std::filesystem;
namespace ui {
ScreenshotViewer::ScreenshotViewer(
NavigationView& nav,
const std::filesystem::path& path)
: nav_{nav},
path_{path} {
set_focusable(true);
}
bool ScreenshotViewer::on_key(KeyEvent) {
nav_.pop();
return true;
}
void ScreenshotViewer::paint(Painter& painter) {
constexpr size_t pixel_width = 240;
constexpr size_t pixel_height = 320;
File file{};
painter.fill_rectangle({0, 0, pixel_width, pixel_height}, Color::black());
auto show_invalid = [&]() {
painter.draw_string({10, 160}, Styles::white, "Not a valid screenshot.");
};
auto error = file.open(path_);
if (error) {
painter.draw_string({10, 160}, Styles::white, error->what());
return;
}
// Screenshots from PNGWriter are all this size.
if (file.size() != 232383) {
show_invalid();
return;
}
constexpr size_t read_chunk = 80; // NB: must be a factor of pixel_width.
constexpr size_t buffer_size = sizeof(ColorRGB888) * read_chunk;
uint8_t buffer[buffer_size];
std::array<Color, pixel_width> pixel_data;
// Seek past all the headers.
file.seek(43);
for (auto line = 0u; line < pixel_height; ++line) {
// Seek past the per-line header.
file.seek(file.tell() + 6);
// Per comment in PNGWriter, read in chunks of 80.
// NB: Reading in one large chunk caused corruption so there's
// likely a bug lurking in the SD Card/FatFs layer.
for (auto offset = 0u; offset < pixel_width; offset += read_chunk) {
auto read = file.read(buffer, buffer_size);
if (!read || *read != buffer_size) {
show_invalid();
return;
}
auto c8 = (ColorRGB888*)buffer;
for (auto i = 0u; i < read_chunk; ++i) {
pixel_data[i + offset] = Color(c8->r, c8->g, c8->b);
++c8;
}
}
display.draw_pixels({0, (int)line, pixel_width, 1}, pixel_data);
}
}
} // namespace ui
@@ -0,0 +1,47 @@
/*
* 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 __UI_SS_VIEWER_H__
#define __UI_SS_VIEWER_H__
#include "ui.hpp"
#include "ui_navigation.hpp"
#include "ui_painter.hpp"
#include "ui_styles.hpp"
#include "ui_widget.hpp"
#include "file.hpp"
namespace ui {
class ScreenshotViewer : public View {
public:
ScreenshotViewer(NavigationView& nav, const std::filesystem::path& path);
bool on_key(KeyEvent key) override;
void paint(Painter& painter) override;
private:
NavigationView& nav_;
std::filesystem::path path_{};
};
} // namespace ui
#endif // __UI_SS_VIEWER_H__
-1
View File
@@ -27,7 +27,6 @@
#include "ui_widget.hpp"
#include "ui_receiver.hpp"
#include "ui_navigation.hpp"
#include "ui_font_fixed_8x16.hpp"
#include "baseband_api.hpp"
#include "ui_transmitter.hpp"
#include "message.hpp"
+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 {
+398 -174
View File
@@ -21,58 +21,53 @@
#include "ui_fileman.hpp"
#include "ui_text_editor.hpp"
#include "ui_textentry.hpp"
#include "log_file.hpp"
#include "string_format.hpp"
using namespace portapack;
namespace fs = std::filesystem;
namespace {
template <typename T>
T mid(const T& val1, const T& val2, const T& val3) {
return std::max(val1, std::min(val2, val3));
}
/*void log(const std::string& msg) {
LogFile log{};
log.append("LOGS/Notepad.txt");
log.write_entry(msg);
}*/
} // namespace
namespace ui {
TextEditorView::TextEditorView(NavigationView& nav)
: nav_{nav} {
add_children(
{&button_open,
&text_position});
/* TextViewer *******************************************************/
TextViewer::TextViewer(Rect parent_rect)
: Widget(parent_rect),
max_line{static_cast<uint8_t>(parent_rect.height() / char_height)},
max_col{static_cast<uint8_t>(parent_rect.width() / char_width)} {
set_focusable(true);
// log_.append("LOGS/NOTEPAD.TXT");
button_open.on_select = [this](Button&) {
auto open_view = nav_.push<FileLoadView>(".TXT");
open_view->on_changed = [this](std::filesystem::path path) {
open_file(path);
};
};
}
void TextEditorView::on_focus() {
refresh_ui();
}
void TextEditorView::paint(Painter& painter) {
void TextViewer::paint(Painter& painter) {
auto first_line = paint_state_.first_line;
auto first_col = paint_state_.first_col;
if (!paint_state_.has_file)
if (!has_file())
return;
// Move the viewport vertically.
if (cursor_.line < first_line)
first_line = cursor_.line;
else if (cursor_.line >= first_line + max_line)
first_line = cursor_.line - max_line + 1;
// Move the viewport horizontally.
if (cursor_.col < first_col)
first_col = cursor_.col;
if (cursor_.col >= first_col + max_col)
first_col = cursor_.col - max_col + 1;
// Viewport updated? Redraw text.
if (first_line != paint_state_.first_line ||
first_col != paint_state_.first_col) {
paint_state_.first_line = first_line;
@@ -88,7 +83,7 @@ void TextEditorView::paint(Painter& painter) {
paint_cursor(painter);
}
bool TextEditorView::on_key(const KeyEvent key) {
bool TextViewer::on_key(const KeyEvent key) {
int16_t delta_col = 0;
int16_t delta_line = 0;
@@ -100,19 +95,22 @@ bool TextEditorView::on_key(const KeyEvent key) {
delta_line = -1;
else if (key == KeyEvent::Down)
delta_line = 1;
/* else if (key == KeyEvent::Select)
; // TODO: Edit/Menu */
else if (key == KeyEvent::Select && on_select) {
on_select();
return true;
}
// Always allow cursor direction to be updated.
cursor_.dir = delta_col != 0 ? ScrollDirection::Horizontal : ScrollDirection::Vertical;
auto updated = apply_scrolling_constraints(delta_line, delta_col);
if (updated)
refresh_ui();
redraw();
return updated;
}
bool TextEditorView::on_encoder(EncoderEvent delta) {
bool TextViewer::on_encoder(EncoderEvent delta) {
bool updated = false;
if (cursor_.dir == ScrollDirection::Horizontal)
@@ -121,178 +119,109 @@ bool TextEditorView::on_encoder(EncoderEvent delta) {
updated = apply_scrolling_constraints(delta, 0);
if (updated)
refresh_ui();
redraw();
return updated;
}
bool TextEditorView::apply_scrolling_constraints(int16_t delta_line, int16_t delta_col) {
void TextViewer::redraw(bool redraw_text) {
paint_state_.redraw_text = redraw_text;
set_dirty();
}
uint32_t TextViewer::offset() const {
auto range = file_->line_range(cursor_.line);
if (range)
return range->start + col();
return 0;
}
uint16_t TextViewer::line_length() {
return file_->line_length(cursor_.line);
}
bool TextViewer::apply_scrolling_constraints(int16_t delta_line, int16_t delta_col) {
if (!has_file())
return false;
int32_t new_line = cursor_.line + delta_line;
int32_t new_col = cursor_.col + delta_col;
auto new_line_length = info_.line_length(new_line);
int32_t new_line_length = file_->line_length(new_line);
if (new_col < 0)
--new_line;
else if (new_col > new_line_length && delta_line == 0) {
// Only want to wrap if moving horizontally.
else if (new_col >= new_line_length && delta_line == 0) {
// Only wrap if moving horizontally.
new_col = 0;
++new_line;
}
if (new_line < 0 || (uint32_t)new_line >= info_.line_count())
// Snap to first/last to make navigating easier.
if (new_line < 0 && new_col > 0) {
new_line = 0;
new_col = 0;
} else if (new_line >= (int32_t)file_->line_count()) {
auto last_line = file_->line_count() - 1;
int32_t last_col = file_->line_length(last_line) - 1;
if (new_col < last_col) {
new_line = last_line;
new_col = last_col;
}
}
if (new_line < 0 || (uint32_t)new_line >= file_->line_count())
return false;
new_line_length = info_.line_length(new_line);
new_line_length = file_->line_length(new_line);
// TODO: don't wrap with encoder?
// Wrap or clamp column.
if (new_line_length == 0)
new_col = 0;
else if (new_col > new_line_length || new_col < 0)
new_col = new_line_length;
else if (new_col >= new_line_length || new_col < 0)
new_col = new_line_length - 1;
cursor_.line = new_line;
cursor_.col = new_col;
if (on_cursor_moved)
on_cursor_moved();
return true;
}
void TextEditorView::refresh_ui() {
if (paint_state_.has_file) {
text_position.set(
to_string_dec_uint(cursor_.col + 1) + ":" +
to_string_dec_uint(cursor_.line + 1) + "/" +
to_string_dec_uint(info_.line_count()) +
(info_.truncated ? "*" : "") +
" Size: " +
to_string_file_size(info_.size));
focus();
} else {
button_open.focus();
}
set_dirty();
}
void TextEditorView::refresh_file_info() {
constexpr size_t buffer_size = 128;
char buffer[buffer_size];
uint32_t base_offset = 0;
file_.seek(0);
info_.newlines.clear();
info_.line_ending = LineEnding::LF;
info_.size = file_.size();
info_.truncated = false;
while (true) {
auto result = file_.read(buffer, buffer_size);
if (result.is_error())
break; // TODO: report error?
// TODO: CRLF state machine for cross block.
for (uint32_t i = 0; i < result.value(); ++i) {
switch (buffer[i]) {
case '\n':
info_.newlines.push_back(base_offset + i);
}
}
base_offset += result.value();
// Fake a newline at the end for consistency.
// Could check if there already is a trailing newline, but it doesn't hurt.
if (result.value() < buffer_size) {
info_.newlines.push_back(base_offset);
break;
}
// HACK HACK: only show first 1000 lines for now.
if (info_.newlines.size() >= 1000) {
info_.truncated = true;
break;
}
}
refresh_ui();
}
void TextEditorView::open_file(const fs::path& path) {
// TODO: need a temp backing file for edits.
auto result = file_.open(path);
paint_state_.has_file = !result.is_valid(); /* Has an error. */
if (paint_state_.has_file) {
refresh_file_info();
paint_state_.first_line = 0;
paint_state_.first_col = 0;
cursor_.line = 0;
cursor_.col = 0;
} else {
nav_.display_modal("Read Error", "Cannot open file:\n" + result.value().what());
paint_state_.has_file = false;
}
paint_state_.redraw_text = true;
refresh_ui();
}
std::string TextEditorView::read(uint32_t offset, uint32_t length) {
if (offset >= info_.size)
return {"[BAD OFFSET]"};
std::string buffer(length + 1, '\0');
file_.seek(offset);
auto result = file_.read(&buffer[0], length);
if (result.is_ok())
/* resize? */;
else
return result.error().what();
return buffer;
}
void TextEditorView::paint_text(Painter& painter, uint32_t line, uint16_t col) {
// TODO: A line cache would use more memory but save a lot of IO.
// Only the new lines/characters would need to be refetched.
void TextViewer::paint_text(Painter& painter, uint32_t line, uint16_t col) {
auto r = screen_rect();
auto& lines = info_.newlines;
auto line_start = info_.line_start(line);
char buffer[max_col + 1];
// 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 result = file_->get_text(line + i, col, buffer, max_col);
if (read_length > 0)
if (result && *result > 0)
painter.draw_string(
{0, r.location().y() + (int)i * char_height},
style_default, read(line_start + col, read_length));
{0, r.top() + (int)i * char_height},
Styles::white_small, {buffer, *result});
// 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. This is less visually jarring than full clear.
int32_t clear_width = max_col - (result ? *result : 0);
if (clear_width > 0)
painter.fill_rectangle(
{(max_col - clear_width) * char_width,
r.top() + (int)i * char_height,
clear_width * char_width, char_height},
Styles::white_small.background);
}
}
void TextEditorView::paint_cursor(Painter& painter) {
void TextViewer::paint_cursor(Painter& painter) {
if (!has_focus())
return;
auto draw_cursor = [this, &painter](uint32_t line, uint16_t col, Color c) {
auto r = screen_rect();
line = line - paint_state_.first_line;
@@ -300,23 +229,318 @@ void TextEditorView::paint_cursor(Painter& painter) {
painter.draw_rectangle(
{(int)col * char_width - 1,
r.location().y() + (int)line * char_height,
r.top() + (int)line * char_height,
char_width + 1, char_height},
c);
};
// TOOD: bug where cursor doesn't clear at EOF.
// TODO: XOR cursor?
// Clear old cursor.
draw_cursor(paint_state_.line, paint_state_.col, style_default.background);
draw_cursor(cursor_.line, cursor_.col, style_default.foreground);
// Clear old cursor. CONSIDER: XOR cursor?
draw_cursor(paint_state_.line, paint_state_.col, Styles::white_small.background);
draw_cursor(cursor_.line, cursor_.col, Styles::white_small.foreground);
paint_state_.line = cursor_.line;
paint_state_.col = cursor_.col;
}
uint16_t TextEditorView::line_length() const {
return info_.line_length(cursor_.line);
void TextViewer::reset_file(FileWrapper* file) {
file_ = file;
paint_state_.first_line = 0;
paint_state_.first_col = 0;
cursor_.line = 0;
cursor_.col = 0;
redraw(true);
}
} // namespace ui
/* TextEditorMenu ***************************************************/
TextEditorMenu::TextEditorMenu()
: View{{7 * 4, 9 * 4, 25 * 8, 25 * 8}} {
add_children(
{
&rect_frame,
&button_cut,
&button_paste,
&button_copy,
&button_delline,
&button_edit,
&button_addline,
&button_open,
&button_save,
&button_exit,
});
}
void TextEditorMenu::on_show() {
hide_children(false);
button_edit.focus();
}
void TextEditorMenu::on_hide() {
hide_children(true);
}
void TextEditorMenu::hide_children(bool hidden) {
for (auto child : children()) {
child->hidden(hidden);
}
}
/* TextEditorView ***************************************************/
TextEditorView::TextEditorView(NavigationView& nav)
: nav_{nav} {
add_children(
{
&viewer,
&menu,
&button_menu,
&text_position,
&text_size,
});
viewer.on_select = [this]() {
// Treat as if menu button was pressed.
if (button_menu.on_select)
button_menu.on_select();
};
viewer.on_cursor_moved = [this]() {
update_position();
};
menu.hidden(true);
menu.on_cut() = [this]() {
show_nyi();
};
menu.on_paste() = [this]() {
show_nyi();
};
menu.on_copy() = [this]() {
show_nyi();
};
menu.on_delete_line() = [this]() {
prepare_for_write();
file_->delete_line(viewer.line());
refresh_ui();
hide_menu(true);
};
menu.on_edit_line() = [this]() {
show_edit_line();
};
menu.on_add_line() = [this]() {
prepare_for_write();
if (viewer.offset() < file_->size() - 1)
file_->insert_line(viewer.line());
else
file_->insert_line(-1); // Add after last line.
refresh_ui();
hide_menu(true);
};
menu.on_open() = [this]() {
/*show_save_prompt([this]() {
show_file_picker();
});*/
// HACK: above should work but it's faulting.
if (!file_dirty_) {
show_file_picker();
} else {
show_save_prompt(nullptr);
show_file_picker(false);
}
};
menu.on_save() = [this]() {
save_temp_file();
hide_menu(true);
};
menu.on_exit() = [this]() {
show_save_prompt([this]() {
nav_.pop();
});
};
button_menu.on_select = [this]() {
if (file_) {
// Toggle menu.
hide_menu(!menu.hidden());
} else {
show_file_picker();
}
};
}
TextEditorView::TextEditorView(NavigationView& nav, const fs::path& path)
: TextEditorView(nav) {
open_file(path);
}
TextEditorView::~TextEditorView() {
delete_temp_file();
}
void TextEditorView::on_show() {
if (file_)
viewer.focus();
else
button_menu.focus();
}
void TextEditorView::open_file(const fs::path& path) {
file_.reset();
viewer.clear_file();
delete_temp_file();
path_ = {};
file_dirty_ = false;
has_temp_file_ = false;
auto result = FileWrapper::open(path);
if (!result) {
nav_.display_modal("Read Error", "Cannot open file:\n" + result.error().what());
} else {
file_ = *std::move(result);
path_ = path;
viewer.set_file(*file_);
}
refresh_ui();
}
void TextEditorView::refresh_ui() {
if (file_) {
update_position();
text_size.set(
"Lines:" + to_string_dec_uint(file_->line_count()) +
" (" + to_string_file_size(file_->size()) + ")");
} else {
text_position.set("");
text_size.set("");
}
}
void TextEditorView::update_position() {
if (viewer.has_file()) {
text_position.set(
"Ln " + to_string_dec_uint(viewer.line() + 1) +
", Col " + to_string_dec_uint(viewer.col() + 1));
}
}
void TextEditorView::hide_menu(bool hidden) {
menu.hidden(hidden);
// Only let the viewer be focused when the menu is
// not shown, otherwise menu focus gets confusing.
viewer.set_focusable(hidden);
if (hidden)
viewer.focus();
viewer.redraw(true);
set_dirty();
}
void TextEditorView::show_file_picker(bool immediate) {
// TODO: immediate is a hack until nav_.on_pop is fixed.
auto open_view = immediate ? nav_.push<FileLoadView>("") : nav_.push_under_current<FileLoadView>("");
if (open_view) {
open_view->on_changed = [this](std::filesystem::path path) {
open_file(path);
hide_menu();
};
}
}
void TextEditorView::show_edit_line() {
auto str = file_->get_text(viewer.line(), 0, viewer.line_length());
if (!str) {
nav_.display_modal("Error", "Failed to get line text.");
return;
}
edit_line_buffer_ = *std::move(str);
text_prompt(
nav_,
edit_line_buffer_,
viewer.col(),
max_edit_length,
[this](std::string& buffer) {
auto range = file_->line_range(viewer.line());
if (!range)
return;
prepare_for_write();
file_->replace_range(*range, buffer);
});
nav_.set_on_pop([this]() {
edit_line_buffer_.clear();
refresh_ui();
hide_menu(true);
});
}
void TextEditorView::show_nyi() {
nav_.display_modal("Soon...", "Coming soon.");
}
void TextEditorView::show_save_prompt(std::function<void()> continuation) {
if (!file_dirty_) {
if (continuation)
continuation();
return;
}
nav_.display_modal(
"Save?", "Save changes?", YESNO,
[this](bool choice) {
if (choice)
save_temp_file();
});
nav_.set_on_pop(continuation);
}
void TextEditorView::prepare_for_write() {
file_dirty_ = true;
if (has_temp_file_)
return;
// Copy to temp file on write.
has_temp_file_ = true;
delete_temp_file();
copy_file(path_, get_temp_path());
file_->assume_file(get_temp_path());
}
void TextEditorView::delete_temp_file() const {
auto temp_path = get_temp_path();
if (!temp_path.empty()) {
delete_file(temp_path);
}
}
void TextEditorView::save_temp_file() {
if (file_dirty_) {
delete_file(path_);
copy_file(get_temp_path(), path_);
file_dirty_ = false;
}
}
fs::path TextEditorView::get_temp_path() const {
if (!path_.empty())
return path_ + "~";
return {};
}
} // namespace ui
+174 -83
View File
@@ -19,116 +19,80 @@
* Boston, MA 02110-1301, USA.
*/
/* TODO:
* - Busy indicator while reading files.
*/
#ifndef __UI_TEXT_EDITOR_H__
#define __UI_TEXT_EDITOR_H__
#include "ui.hpp"
#include "ui_font_fixed_5x8.hpp"
#include "ui_navigation.hpp"
#include "ui_painter.hpp"
#include "ui_styles.hpp"
#include "ui_widget.hpp"
//#include "ui_textentry.hpp"
#include "file.hpp"
#include "log_file.hpp"
#include "file_wrapper.hpp"
#include "optional.hpp"
#include <memory>
#include <string>
#include <vector>
namespace ui {
enum class LineEnding : uint8_t {
LF,
CRLF
};
enum class ScrollDirection : uint8_t {
Vertical,
Horizontal
};
// TODO: RAM is _very_ limited. Need to
// rework this to not store every line.
// Should be able to get away with only
// abount one screen of lines so long as
// you can't scroll more than one screen
// at a time.
struct FileInfo {
/* Offsets of newlines. */
std::vector<uint32_t> newlines;
LineEnding line_ending;
File::Size size;
bool truncated;
uint32_t line_count() const {
return newlines.size();
}
uint32_t line_start(uint32_t line) const {
return line == 0 ? 0 : (newlines[line - 1] + 1);
}
uint16_t line_length(uint32_t line) const {
if (line >= line_count())
return 0;
auto start = line_start(line);
auto end = newlines[line];
return end - start;
}
};
/*class TextViewer : public Widget {
};*/
class TextEditorView : public View {
/* Control that renders a text file. */
class TextViewer : public Widget {
public:
TextEditorView(NavigationView& nav);
// TextEditorView(NavigationView& nav, const std::filesystem::path& path);
TextViewer(Rect parent_rect);
std::string title() const override {
return "Notepad";
};
TextViewer(const TextViewer&) = delete;
TextViewer(TextViewer&&) = delete;
TextViewer& operator=(const TextViewer&) = delete;
TextViewer& operator=(TextViewer&&) = delete;
std::function<void()> on_select{};
std::function<void()> on_cursor_moved{};
void on_focus() override;
void paint(Painter& painter) override;
bool on_key(KeyEvent delta) override;
bool on_key(KeyEvent key) override;
bool on_encoder(EncoderEvent delta) override;
void redraw(bool redraw_text = false);
void set_file(FileWrapper& file) { reset_file(&file); }
void clear_file() { reset_file(); }
bool has_file() const { return file_ != nullptr; }
uint32_t line() const { return cursor_.line; }
uint32_t col() const { return cursor_.col; }
uint32_t offset() const;
// Gets the length of the current line.
uint16_t line_length();
private:
static constexpr uint8_t max_line = 32;
static constexpr uint8_t max_col = 48;
static constexpr int8_t char_width = 5;
static constexpr int8_t char_height = 8;
// TODO: should these be common somewhere?
static constexpr Style style_default{
.font = font::fixed_5x8,
.background = Color::black(),
.foreground = Color::white(),
};
const uint8_t max_line = 32;
const uint8_t max_col = 48;
/* Returns true if the cursor was updated. */
bool apply_scrolling_constraints(
int16_t delta_line,
int16_t delta_col);
void refresh_ui();
void refresh_file_info();
void open_file(const std::filesystem::path& path);
std::string read(uint32_t offset, uint32_t length = 30);
void paint_text(Painter& painter, uint32_t line, uint16_t col);
void paint_cursor(Painter& painter);
// Gets the length of the current line.
uint16_t line_length() const;
void reset_file(FileWrapper* file = nullptr);
NavigationView& nav_;
File file_{};
FileInfo info_{};
// LogFile log_{ };
FileWrapper* file_{};
struct {
// Previous cursor state.
@@ -139,7 +103,6 @@ class TextEditorView : public View {
uint32_t first_line{};
uint16_t first_col{};
bool redraw_text{true};
bool has_file{false};
} paint_state_{};
struct {
@@ -147,21 +110,149 @@ class TextEditorView : public View {
uint16_t col{};
ScrollDirection dir{ScrollDirection::Vertical};
} cursor_{};
};
/* 8px grid is 30 wide, 38 tall. */
/* 16px font height or 19 rows. */
/* Titlebar is 16px tall, so 18 rows left. */
/* 240 x 320, (304 with titlebar) */
/* Menu control for the TextEditor. */
class TextEditorMenu : public View {
public:
TextEditorMenu();
// TODO: The scrollable view should be its own widget
// otherwise control navigation doesn't work.
void on_show() override;
void on_hide() override;
Button button_open{
{24 * 8, 34 * 8, 6 * 8, 4 * 8},
"Open"};
std::function<void()>& on_cut() { return button_cut.on_select; }
std::function<void()>& on_paste() { return button_paste.on_select; }
std::function<void()>& on_copy() { return button_copy.on_select; }
std::function<void()>& on_delete_line() { return button_delline.on_select; }
std::function<void()>& on_edit_line() { return button_edit.on_select; }
std::function<void()>& on_add_line() { return button_addline.on_select; }
std::function<void()>& on_open() { return button_open.on_select; }
std::function<void()>& on_save() { return button_save.on_select; }
std::function<void()>& on_exit() { return button_exit.on_select; }
private:
void hide_children(bool hidden);
Rectangle rect_frame{
{0 * 8, 0 * 8, 23 * 8, 23 * 8},
Color::dark_grey()};
NewButton button_cut{
{1 * 8, 1 * 8, 7 * 8, 7 * 8},
"Cut",
&bitmap_icon_cut,
Color::dark_grey()};
NewButton button_paste{
{8 * 8, 1 * 8, 7 * 8, 7 * 8},
"Paste",
&bitmap_icon_paste,
Color::dark_grey()};
NewButton button_copy{
{15 * 8, 1 * 8, 7 * 8, 7 * 8},
"Copy",
&bitmap_icon_copy,
Color::dark_grey()};
NewButton button_delline{
{1 * 8, 8 * 8, 7 * 8, 7 * 8},
"-Line",
&bitmap_icon_delete,
Color::dark_red()};
NewButton button_edit{
{8 * 8, 8 * 8, 7 * 8, 7 * 8},
"Edit",
&bitmap_icon_rename,
Color::dark_blue()};
NewButton button_addline{
{15 * 8, 8 * 8, 7 * 8, 7 * 8},
"+Line",
&bitmap_icon_scanner,
Color::dark_blue()};
NewButton button_open{
{1 * 8, 15 * 8, 7 * 8, 7 * 8},
"Open",
&bitmap_icon_load,
Color::green()};
NewButton button_save{
{8 * 8, 15 * 8, 7 * 8, 7 * 8},
"Save",
&bitmap_icon_save,
Color::green()};
NewButton button_exit{
{15 * 8, 15 * 8, 7 * 8, 7 * 8},
"Exit",
&bitmap_icon_previous,
Color::dark_red()};
};
/* View viewing and minor edits on a text file. */
class TextEditorView : public View {
public:
TextEditorView(NavigationView& nav);
TextEditorView(
NavigationView& nav,
const std::filesystem::path& path);
~TextEditorView();
std::string title() const override {
return "Notepad";
};
void on_show() override;
private:
static constexpr size_t max_edit_length = 1024;
std::string edit_line_buffer_{};
void open_file(const std::filesystem::path& path);
void save_file();
void refresh_ui();
void update_position();
void hide_menu(bool hidden = true);
void show_file_picker(bool immediate = true);
void show_edit_line();
void show_nyi();
void show_save_prompt(std::function<void()> continuation);
void prepare_for_write();
void create_temp_file() const;
void delete_temp_file() const;
void save_temp_file();
std::filesystem::path get_temp_path() const;
NavigationView& nav_;
std::unique_ptr<FileWrapper> file_{};
std::filesystem::path path_{};
bool file_dirty_{false};
bool has_temp_file_{false};
TextViewer viewer{
/* 272 = 320 - 16 (top bar) - 32 (bottom controls) */
{0, 0, 240, 272}};
TextEditorMenu menu{};
NewButton button_menu{
{26 * 8, 34 * 8, 4 * 8, 4 * 8},
{},
&bitmap_icon_controls,
Color::dark_grey()};
Text text_position{
{0 * 8, 36 * 8, 24 * 8, 2 * 8},
{0 * 8, 34 * 8, 26 * 8, 2 * 8},
""};
Text text_size{
{0 * 8, 36 * 8, 26 * 8, 2 * 8},
""};
};
@@ -23,7 +23,6 @@
#include "receiver_model.hpp"
#include "ui_receiver.hpp"
#include "ui_font_fixed_8x16.hpp"
namespace ui {
+30 -30
View File
@@ -2714,38 +2714,38 @@ static constexpr Bitmap bitmap_icon_remote{
bitmap_icon_remote_data};
static constexpr uint8_t bitmap_icon_save_data[] = {
0x00,
0x01,
0x00,
0x01,
0x00,
0x01,
0x00,
0x01,
0x4E,
0x05,
0x91,
0x03,
0x3F,
0x19,
0x01,
0xFC,
0x07,
0x0A,
0x0A,
0x0A,
0x12,
0xF2,
0x21,
0x02,
0x20,
0xF9,
0xFF,
0xF9,
0xFF,
0xFD,
0x7F,
0xFD,
0x7F,
0xFF,
0x3F,
0xFF,
0x3F,
0xFF,
0x1F,
0xFF,
0x02,
0x20,
0x02,
0x20,
0x02,
0x20,
0xFA,
0x27,
0xFA,
0x2F,
0x0A,
0x28,
0xFA,
0x2F,
0x0A,
0x28,
0xFA,
0x2F,
0xFC,
0x1F,
0x00,
0x00,
};
static constexpr Bitmap bitmap_icon_save{
{16, 16},
+15 -22
View File
@@ -26,10 +26,11 @@
#include <hal.h>
#include <string>
#include "ui_painter.hpp"
#include "portapack.hpp"
#include "ui_font_fixed_8x16.hpp"
#include "string_format.hpp"
#include "ui_styles.hpp"
using namespace ui;
void runtime_error(LED);
std::string number_to_hex_string(uint32_t);
@@ -37,33 +38,29 @@ void draw_line(int32_t, const char*, regarm_t);
static bool error_shown = false;
void draw_guru_meditation_header(uint8_t source, const char* hint) {
ui::Painter painter;
ui::Style style_default{
.font = ui::font::fixed_8x16,
.background = ui::Color::black(),
.foreground = ui::Color::white()};
Painter painter;
painter.fill_rectangle(
{0, 0, portapack::display.width(), portapack::display.height()},
ui::Color::red());
Color::red());
constexpr int border = 8;
painter.fill_rectangle(
{border, border, portapack::display.width() - (border * 2), portapack::display.height() - (border * 2)},
ui::Color::black());
Color::black());
// NOTE: in situations like a hard fault it seems not possible to write strings longer than 16 characters.
painter.draw_string({48, 24}, style_default, "M? Guru");
painter.draw_string({48 + 8 * 8, 24}, style_default, "Meditation");
painter.draw_string({48, 24}, Styles::white, "M? Guru");
painter.draw_string({48 + 8 * 8, 24}, Styles::white, "Meditation");
if (source == CORTEX_M0)
painter.draw_string({48 + 8, 24}, style_default, "0");
painter.draw_string({48 + 8, 24}, Styles::white, "0");
if (source == CORTEX_M4)
painter.draw_string({48 + 8, 24}, style_default, "4");
painter.draw_string({48 + 8, 24}, Styles::white, "4");
painter.draw_string({15, 55}, style_default, "Hint: ");
painter.draw_string({15 + 8 * 8, 55}, style_default, hint);
painter.draw_string({15, 55}, Styles::white, "Hint: ");
painter.draw_string({15 + 8 * 8, 55}, Styles::white, hint);
}
void draw_guru_meditation(uint8_t source, const char* hint) {
@@ -114,14 +111,10 @@ void draw_guru_meditation(uint8_t source, const char* hint, struct extctx* ctxp,
}
void draw_line(int32_t y_offset, const char* label, regarm_t value) {
ui::Painter painter;
ui::Style style_default{
.font = ui::font::fixed_8x16,
.background = ui::Color::black(),
.foreground = ui::Color::white()};
Painter painter;
painter.draw_string({15, y_offset}, style_default, label);
painter.draw_string({15 + 8 * 8, y_offset}, style_default, to_string_hex((uint32_t)value, 8));
painter.draw_string({15, y_offset}, Styles::white, label);
painter.draw_string({15 + 8 * 8, y_offset}, Styles::white, to_string_hex((uint32_t)value, 8));
}
void runtime_error(LED led) {
-1
View File
@@ -42,7 +42,6 @@ using namespace lpc43xx;
#include <array>
#include "ui_font_fixed_8x16.hpp"
#include "ui_navigation.hpp"
extern "C" {
+29 -14
View File
@@ -44,8 +44,9 @@ Optional<File::Error> File::open_fatfs(const std::filesystem::path& filename, BY
}
}
Optional<File::Error> File::open(const std::filesystem::path& filename) {
return open_fatfs(filename, FA_READ);
Optional<File::Error> File::open(const std::filesystem::path& filename, bool read_only) {
BYTE mode = read_only ? FA_READ : FA_READ | FA_WRITE;
return open_fatfs(filename, mode);
}
Optional<File::Error> File::append(const std::filesystem::path& filename) {
@@ -60,7 +61,7 @@ File::~File() {
f_close(&f);
}
File::Result<File::Size> File::read(void* const data, const Size bytes_to_read) {
File::Result<File::Size> File::read(void* data, Size bytes_to_read) {
UINT bytes_read = 0;
const auto result = f_read(&f, data, bytes_to_read, &bytes_read);
if (result == FR_OK) {
@@ -70,7 +71,7 @@ File::Result<File::Size> File::read(void* const data, const Size bytes_to_read)
}
}
File::Result<File::Size> File::write(const void* const data, const Size bytes_to_write) {
File::Result<File::Size> File::write(const void* data, Size bytes_to_write) {
UINT bytes_written = 0;
const auto result = f_write(&f, data, bytes_to_write, &bytes_written);
if (result == FR_OK) {
@@ -84,9 +85,13 @@ File::Result<File::Size> File::write(const void* const data, const Size bytes_to
}
}
File::Result<File::Offset> File::seek(const Offset new_position) {
File::Offset File::tell() const {
return f_tell(&f);
}
File::Result<File::Offset> File::seek(Offset new_position) {
/* NOTE: Returns *old* position, not new position */
const auto old_position = f_tell(&f);
const auto old_position = tell();
const auto result = f_lseek(&f, new_position);
if (result != FR_OK) {
return {static_cast<Error>(result)};
@@ -97,8 +102,17 @@ 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::Result<File::Offset> File::truncate() {
const auto position = f_tell(&f);
const auto result = f_truncate(&f);
if (result != FR_OK) {
return {static_cast<Error>(result)};
}
return {static_cast<File::Offset>(position)};
}
File::Size File::size() const {
return f_size(&f);
}
Optional<File::Error> File::write_line(const std::string& s) {
@@ -241,25 +255,26 @@ std::filesystem::filesystem_error rename_file(
std::filesystem::filesystem_error copy_file(
const std::filesystem::path& file_path,
const std::filesystem::path& dest_path) {
// Decent compromise between memory and speed.
constexpr size_t buffer_size = 512;
uint8_t buffer[buffer_size];
File src;
File dst;
constexpr size_t buffer_size = 128;
uint8_t buffer[buffer_size];
auto error = src.open(file_path);
if (error.is_valid()) return error.value();
if (error) return error.value();
error = dst.create(dest_path);
if (error.is_valid()) return error.value();
if (error) return error.value();
while (true) {
auto result = src.read(buffer, buffer_size);
if (result.is_error()) return result.error();
result = dst.write(buffer, result.value());
result = dst.write(buffer, *result);
if (result.is_error()) return result.error();
if (result.value() < buffer_size)
if (*result < buffer_size)
break;
}
+33 -11
View File
@@ -288,6 +288,7 @@ class File {
using Timestamp = uint32_t;
using Error = std::filesystem::filesystem_error;
// TODO: move to common.
template <typename T>
struct Result {
enum class Type {
@@ -303,14 +304,26 @@ class File {
return type == Type::Success;
}
operator bool() const {
return is_ok();
}
bool is_error() const {
return type == Type::Error;
}
const T& value() const {
const T& value() const& {
return value_;
}
const T& operator*() const& {
return value_;
}
T&& operator*() && {
return std::move(value_);
}
Error error() const {
return error_;
}
@@ -318,9 +331,9 @@ class File {
Result() = delete;
constexpr Result(
T value)
T&& value)
: type{Type::Success},
value_{value} {
value_{std::forward<T>(value)} {
}
constexpr Result(
@@ -330,30 +343,39 @@ class File {
}
~Result() {
if (type == Type::Success) {
if (is_ok())
value_.~T();
}
}
};
File(){};
~File();
File(File&& other) {
std::swap(f, other.f);
}
File& operator=(File&& other) {
std::swap(f, other.f);
return *this;
}
/* Prevent copies */
File(const File&) = delete;
File& operator=(const File&) = delete;
// TODO: Return Result<>.
Optional<Error> open(const std::filesystem::path& filename);
Optional<Error> open(const std::filesystem::path& filename, bool read_only = true);
Optional<Error> append(const std::filesystem::path& filename);
Optional<Error> create(const std::filesystem::path& filename);
Result<Size> read(void* const data, const Size bytes_to_read);
Result<Size> write(const void* const data, const Size bytes_to_write);
Result<Size> read(void* data, const Size bytes_to_read);
Result<Size> write(const void* data, Size bytes_to_write);
Result<Offset> seek(const uint64_t Offset);
Timestamp created_date();
Size size();
Offset tell() const;
Result<Offset> seek(uint64_t Offset);
Result<Offset> truncate();
// Timestamp created_date() const;
Size size() const;
template <size_t N>
Result<Size> write(const std::array<uint8_t, N>& data) {
+507
View File
@@ -0,0 +1,507 @@
/*
* 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 __FILE_WRAPPER_HPP__
#define __FILE_WRAPPER_HPP__
#include "circular_buffer.hpp"
#include "file.hpp"
#include "optional.hpp"
#include <memory>
#include <string_view>
enum class LineEnding : uint8_t {
LF,
CRLF
};
/* TODO:
* - CRLF handling.
* - Avoid full re-read on edits.
* - Would need to read old/new text when editing to track newlines.
* - How to surface errors? Exceptions?
*/
/* FatFs docs http://elm-chan.org/fsw/ff/00index_e.html */
/* BufferType requires the following members
* Size size()
* Result<Size> read(void* data, Size bytes_to_read)
* Result<Size> write(const void* data, Size bytes_to_write)
* Result<Offset> seek(uint32_t offset)
* Result<Offset> truncate()
* Optional<Error> sync()
*/
/* Wraps a buffer and provides an API for accessing lines efficiently. */
template <typename BufferType, uint32_t CacheSize>
class BufferWrapper {
public:
using Offset = uint32_t;
using Line = uint32_t;
using Column = uint32_t;
using Range = struct {
// Offset of the start, inclusive.
Offset start;
// Offset of the end, exclusive.
Offset end;
Offset length() const { return end - start; }
};
BufferWrapper(BufferType* buffer)
: wrapped_{buffer} {
initialize();
}
virtual ~BufferWrapper() {}
/* Prevent copies */
BufferWrapper(const BufferWrapper&) = delete;
BufferWrapper& operator=(const BufferWrapper&) = delete;
Optional<std::string> get_text(Line line, Column col, Offset length) {
std::string buffer;
buffer.resize(length);
auto result = get_text(line, col, &buffer[0], length);
if (!result)
return {};
buffer.resize(*result);
return buffer;
}
Optional<Offset> get_text(Line line, Column col, char* output, Offset length) {
auto range = line_range(line);
int32_t to_read = length;
if (!range)
return {};
// 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, output, to_read);
}
/* Gets the size of the buffer in bytes. */
File::Size size() const { return wrapped_->size(); }
/* Get the count of the lines in the buffer. */
uint32_t line_count() const { return line_count_; }
/* Gets the range of the line if valid. */
Optional<Range> line_range(Line line) {
ensure_cached(line);
auto index = index_for_line(line);
if (!index)
return {};
auto start = *index == 0 ? start_offset_ : (newlines_[*index - 1] + 1);
auto end = newlines_[*index] + 1;
return Range{start, end};
}
/* Gets the length of the line, or 0 if invalid. */
Offset line_length(Line line) {
auto range = line_range(line);
return range ? range->length() : 0;
}
/* Gets the buffer offset of the line & col if valid. */
Optional<Offset> get_offset(Line line, Column col) {
auto range = line_range(line);
if (range)
return range->start + col;
return {};
}
/* Gets the index of the first line in the cache.
* Only really useful for unit testing or diagnostics. */
Offset start_line() { return start_line_; };
/* Inserts a line before the specified line or at the
* end of the buffer if line >= line_count. */
void insert_line(Line line) {
auto range = line_range(line);
if (range)
replace_range({range->start, range->start}, "\n");
else if (line >= line_count_)
replace_range({(Offset)size(), (Offset)size()}, "\n");
}
/* Deletes the specified line. */
void delete_line(Line line) {
auto range = line_range(line);
if (range)
replace_range(*range, {});
}
/* Replace the specified range with the string contents.
* A range with start/end set to the same value will insert.
* A range with an empty string will delete. */
void replace_range(Range range, std::string_view value) {
if (range.start > size() || range.end > size() || range.start > range.end)
return;
/* If delta_length == 0, it's an overwrite. Could still have
* added or removed newlines so caches will need to be rebuilt.
* If delta_length > 0, the file needs to grow and content needs
* to be shifted forward until the end of the range.
* If delta_length < 0, the file needs to be truncated and the
* content after the value needs to be shifted backward. */
int32_t delta_length = value.length() - range.length();
if (delta_length > 0)
expand(range.end, delta_length);
else if (delta_length < 0)
shrink(range.end, delta_length);
write(range.start, value);
wrapped_->sync();
rebuild_cache();
}
protected:
BufferWrapper() {}
void set_buffer(BufferType* buffer) {
wrapped_ = buffer;
initialize();
}
private:
/* Number of newline offsets to cache. */
static constexpr Offset max_newlines = CacheSize;
/* Size of stack buffer used for reading/writing. */
static constexpr Offset buffer_size = 512;
void initialize() {
start_offset_ = 0;
start_line_ = 0;
line_count_ = 0;
rebuild_cache();
}
void rebuild_cache() {
newlines_.clear();
// Special case for empty files to keep them consistent.
if (size() == 0) {
line_count_ = 1;
newlines_.push_back(0);
return;
}
// TODO: think through this for edit cases.
// E.g. don't read to end, maybe could specify
// a range to re-read because it should be possible
// to tell where the dirty regions are. After the
// dirty region, it should be possible to fixup
// the line_count data.
// TODO: seems like shrink/expand could do this while
// they are running.
line_count_ = start_line_;
Offset offset = start_offset_;
auto result = next_newline(offset);
while (result) {
++line_count_;
if (newlines_.size() < max_newlines)
newlines_.push_back(*result);
offset = *result + 1;
result = next_newline(offset);
}
}
Optional<Offset> read(Offset offset, char* buffer, Offset length) {
if (offset + length > size())
return {};
wrapped_->seek(offset);
auto result = wrapped_->read(buffer, length);
if (result.is_error())
return {};
return *result;
}
bool write(Offset offset, std::string_view value) {
wrapped_->seek(offset);
auto result = wrapped_->write(value.data(), value.length());
return result.is_ok();
}
/* Returns the index of the line in the newline cache if valid. */
Optional<Offset> index_for_line(Line line) const {
if (line >= line_count_)
return {};
Offset actual = line - start_line_;
if (actual >= newlines_.size()) // NB: underflow wrap.
return {};
return actual;
}
/* Ensure specified line is in the newline cache. */
void ensure_cached(Line line) {
if (line >= line_count_)
return;
auto index = index_for_line(line);
if (index)
return;
if (line < start_line_) {
while (line < start_line_ && start_offset_ >= 2) {
// start_offset_ - 1 should be a newline. Need to
// find the new value for start_offset_. start_line_
// has to be > 0 to get into this block so there should
// always be one newline before start_offset_.
auto offset = previous_newline(start_offset_ - 2);
newlines_.push_front(start_offset_ - 1);
if (!offset) {
// Must be at beginning.
start_line_ = 0;
start_offset_ = 0;
} else {
// Found an previous newline, the new start_line_
// starts at the newline offset + 1.
start_line_--;
start_offset_ = *offset + 1;
}
}
} else {
while (line >= start_line_ + newlines_.size()) {
auto offset = next_newline(newlines_.back() + 1);
if (offset) {
start_line_++;
start_offset_ = newlines_.front() + 1;
newlines_.push_back(*offset);
} /* else at the EOF. */
}
}
}
/* Finding the first newline backward from offset. */
Optional<Offset> previous_newline(Offset offset) {
char buffer[buffer_size];
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;
wrapped_->seek(offset);
auto result = wrapped_->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.
}
/* Finding the first newline forward from offset. */
Optional<Offset> next_newline(Offset offset) {
// EOF, no more newlines to find.
if (offset >= size())
return {};
char buffer[buffer_size];
wrapped_->seek(offset);
while (true) {
auto result = wrapped_->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;
}
// For consistency, treat the end of the file as a "newline".
return size() - 1;
}
/* Grow the file and move file content so that the
* content at src is shifted forward by 'delta'. */
void expand(Offset src, int32_t delta) {
if (delta <= 0) // Not an expand.
return;
char buffer[buffer_size];
auto to_read = buffer_size;
// Number of bytes left to shift.
Offset remaining = size() - src;
Offset offset = size();
while (remaining > 0) {
offset -= std::min(remaining, buffer_size);
to_read = std::min(remaining, buffer_size);
wrapped_->seek(offset);
auto result = wrapped_->read(buffer, to_read);
if (result.is_error())
break;
wrapped_->seek(offset + delta);
result = wrapped_->write(buffer, *result);
if (result.is_error())
break;
remaining -= *result;
}
}
/* Shrink the file and move file content so that the
* content at src is shifted backward by 'delta'. */
void shrink(Offset src, int32_t delta) {
if (delta >= 0) // Not a shrink.
return;
char buffer[buffer_size];
auto offset = src;
while (true) {
wrapped_->seek(offset);
auto result = wrapped_->read(buffer, buffer_size);
if (result.is_error())
break;
wrapped_->seek(offset + delta);
result = wrapped_->write(buffer, *result);
if (result.is_error() || *result < buffer_size)
break;
offset += *result;
}
// Delete the extra bytes at the end of the file.
wrapped_->truncate();
}
BufferType* wrapped_{};
/* Total number of lines in the buffer. */
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_{};
};
/* A BufferWrapper over a file. */
class FileWrapper : public BufferWrapper<File, 64> {
public:
template <typename T>
using Result = File::Result<T>;
using Error = File::Error;
static Result<std::unique_ptr<FileWrapper>> open(const std::filesystem::path& path) {
auto fw = std::unique_ptr<FileWrapper>(new FileWrapper());
auto error = fw->file_.open(path, /*read_only*/ false);
if (error)
return *error;
fw->initialize();
return fw;
}
/* Underlying file. */
File& file() { return file_; }
/* Swaps out the underlying file for the specified file.
* The swapped file is expected have the same contents.
* For copy-on-write scenario with a temp file. */
bool assume_file(const std::filesystem::path& path) {
File file;
auto error = file.open(path, /*read_only*/ false);
if (error)
return false;
file_ = std::move(file);
return true;
}
private:
FileWrapper() {}
void initialize() {
set_buffer(&file_);
}
File file_{};
};
template <uint32_t CacheSize = 64, typename T>
BufferWrapper<T, CacheSize> wrap_buffer(T& buffer) {
return {&buffer};
}
#endif // __FILE_WRAPPER_HPP__
+32 -52
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.
*
@@ -44,9 +45,9 @@ options_t freqman_entry_bandwidths[4] = {
{"16k", 2}},
{
// WFM
{"200k", 0},
{"180k", 1},
{"40k", 2},
{"180k", 1},
{"200k", 0},
}};
options_t freqman_entry_steps = {
@@ -85,27 +86,14 @@ options_t freqman_entry_steps_short = {
{"500kHz", 500000},
{"1MHz", 1000000}};
std::vector<std::string> get_freqman_files() {
std::vector<std::string> file_list;
auto files = scan_root_files(u"FREQMAN", u"*.TXT");
for (auto file : files) {
std::string file_name = file.stem().string();
// don't propose tmp / hidden files in freqman's list
if (file_name.length() && file_name[0] != '.') {
file_list.emplace_back(file_name);
}
}
return file_list;
};
bool load_freqman_file(std::string& file_stem, freqman_db& db) {
return load_freqman_file_ex(file_stem, db, true, true, true);
return load_freqman_file_ex(file_stem, db, true, true, true, FREQMAN_MAX_PER_FILE);
}
bool load_freqman_file_ex(std::string& file_stem, freqman_db& db, bool load_freqs, bool load_ranges, bool load_hamradios) {
bool load_freqman_file_ex(std::string& file_stem, freqman_db& db, bool load_freqs, bool load_ranges, bool load_hamradios, uint8_t max_num_freqs = FREQMAN_MAX_PER_FILE) {
// swap with empty vector to ensure memory is immediately released
std::vector<freqman_entry>().swap(db);
File freqman_file;
size_t length, n = 0, file_position = 0;
char* pos;
@@ -113,35 +101,33 @@ bool load_freqman_file_ex(std::string& file_stem, freqman_db& db, bool load_freq
char* line_end;
std::string description;
rf::Frequency frequency_a, frequency_b;
char file_data[257];
char file_data[FREQMAN_READ_BUF_SIZE + 1];
freqman_entry_type type;
freqman_index_t modulation = 0;
freqman_index_t bandwidth = 0;
freqman_index_t step = 0;
freqman_index_t tone = 0;
db.clear();
auto result = freqman_file.open("FREQMAN/" + file_stem + ".TXT");
if (result.is_valid())
return false;
while (1) {
// Read a 256 bytes block from file
// Read a FREQMAN_READ_BUF_SIZE block from file
freqman_file.seek(file_position);
memset(file_data, 0, 257);
auto read_size = freqman_file.read(file_data, 256);
memset(file_data, 0, FREQMAN_READ_BUF_SIZE + 1);
auto read_size = freqman_file.read(file_data, FREQMAN_READ_BUF_SIZE);
if (read_size.is_error())
return false; // Read error
file_position += 256;
file_position += FREQMAN_READ_BUF_SIZE;
// Reset line_start to beginning of buffer
line_start = file_data;
// If EOF reached, insert 0x0A after, in case the last line doesn't have a C/R
if (read_size.value() < 256)
if (read_size.value() < FREQMAN_READ_BUF_SIZE)
*(line_start + read_size.value()) = 0x0A;
// Look for complete lines in buffer
@@ -221,23 +207,22 @@ bool load_freqman_file_ex(std::string& file_stem, freqman_db& db, bool load_freq
pos += 2;
length = std::min(strcspn(pos, ",\x0A"), (size_t)FREQMAN_DESC_MAX_LEN);
description = string(pos, length);
} else
description = "-";
}
if ((type == SINGLE && load_freqs) || (type == RANGE && load_ranges) || (type == HAMRADIO && load_hamradios)) {
db.push_back({frequency_a, frequency_b, description, type, modulation, bandwidth, step, tone});
n++;
if (n > FREQMAN_MAX_PER_FILE) return true;
if (n > max_num_freqs) return true;
}
line_start = line_end + 1;
if (line_start - file_data >= 256) break;
if (line_start - file_data >= FREQMAN_READ_BUF_SIZE) break;
}
if (read_size.value() != 256)
if (read_size.value() != FREQMAN_READ_BUF_SIZE)
break; // End of file
// Restart at beginning of last incomplete line
file_position -= (file_data + 256 - line_start);
file_position -= (file_data + FREQMAN_READ_BUF_SIZE - line_start);
}
/* populate implicitly specified modulation / bandwidth */
@@ -296,29 +281,24 @@ bool get_freq_string(freqman_entry& entry, std::string& item_string) {
return true;
}
bool delete_freqman_file(std::string& file_stem) {
File freqman_file;
std::string freq_file_path = "/FREQMAN/" + file_stem + ".TXT";
delete_file(freq_file_path);
return false;
}
bool save_freqman_file(std::string& file_stem, freqman_db& db) {
File freqman_file;
std::string freq_file_path = "FREQMAN/" + file_stem + ".TXT";
std::string tmp_freq_file_path = "FREQMAN/" + file_stem + ".TXT.TMP";
if (!db.size()) {
delete_file("FREQMAN/" + file_stem + ".TXT");
return true;
}
delete_file(tmp_freq_file_path);
auto result = freqman_file.open(tmp_freq_file_path);
std::string freq_file_path = "/FREQMAN/" + file_stem + ".TXT";
delete_file(freq_file_path);
auto result = freqman_file.create(freq_file_path);
if (!result.is_valid()) {
for (size_t n = 0; n < db.size(); n++) {
std::string item_string;
auto& entry = db[n];
get_freq_string(entry, item_string);
freqman_file.write_line(item_string);
delete &item_string;
std::string line;
get_freq_string(db[n], line);
freqman_file.write_line(line);
}
delete_file(freq_file_path);
rename_file(tmp_freq_file_path, freq_file_path);
return true;
}
return false;
+8 -6
View File
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
* Copyright (C) 2023 gullradriel, Nilorea Studio Inc.
*
* This file is part of PortaPack.
*
@@ -31,10 +32,11 @@
#include "string_format.hpp"
#include "ui_widget.hpp"
#define FREQMAN_DESC_MAX_LEN 24 // This is the number of characters that can be drawn in front of "R: TEXT..." before taking a full screen line
#define FREQMAN_MAX_PER_FILE 115 // Maximum of entries we can read. This is a hardware limit
// It was tested and lowered to leave a bit of space to the caller
#define FREQMAN_MAX_PER_FILE_STR "115" // STRING OF FREQMAN_MAX_PER_FILE
#define FREQMAN_DESC_MAX_LEN 24 // This is the number of characters that can be drawn in front of "R: TEXT..." before taking a full screen line
#define FREQMAN_MAX_PER_FILE 60 // Maximum of entries we can read. This is a hardware limit
// It was tested and lowered to leave a bit of space to the caller
#define FREQMAN_MAX_PER_FILE_STR "60" // STRING OF FREQMAN_MAX_PER_FILE
#define FREQMAN_READ_BUF_SIZE 96 // max freqman line size including desc is 90, +6 for a bit of space
using namespace ui;
using namespace std;
@@ -93,10 +95,10 @@ struct freqman_entry {
using freqman_db = std::vector<freqman_entry>;
std::vector<std::string> get_freqman_files();
bool load_freqman_file(std::string& file_stem, freqman_db& db);
bool load_freqman_file_ex(std::string& file_stem, freqman_db& db, bool load_freqs, bool load_ranges, bool load_hamradios);
bool load_freqman_file_ex(std::string& file_stem, freqman_db& db, bool load_freqs, bool load_ranges, bool load_hamradios, uint8_t limit);
bool get_freq_string(freqman_entry& entry, std::string& item_string);
bool delete_freqman_file(std::string& file_stem);
bool save_freqman_file(std::string& file_stem, freqman_db& db);
bool create_freqman_file(std::string& file_stem, File& freqman_file);
+32
View File
@@ -27,11 +27,28 @@
bool Debounce::feed(const uint8_t bit) {
history_ = (history_ << 1) | (bit & 1);
// "Repeat" handling - simulated button release
if (repeat_ctr_) {
// Make sure the button is still being held continuously
if (history_ == 0xFF) {
// Simulate button press every REPEAT_SUBSEQUENT_DELAY ticks
if (--repeat_ctr_ == 0) {
state_ = !state_;
repeat_ctr_ = REPEAT_SUBSEQUENT_DELAY / 2;
return true;
}
} else {
// It's a real button release; stop simulating
repeat_ctr_ = 0;
}
}
if (state_ == 0) {
// Previous button state was 0 (released);
// Has button been held for DEBOUNCE_COUNT ticks?
if ((history_ & DEBOUNCE_MASK) == DEBOUNCE_MASK) {
state_ = 1;
held_time_ = 0;
return true;
}
} else {
@@ -41,6 +58,21 @@ bool Debounce::feed(const uint8_t bit) {
state_ = 0;
return true;
}
// Repeat support is limited to the 4 directional buttons
if (repeat_enabled_) {
// Has button been held continuously for DEBOUNCE_REPEAT_DELAY?
if (history_ == 0xFF) {
if (++held_time_ == REPEAT_INITIAL_DELAY) {
// Delay reached; trigger repeat code on NEXT tick
repeat_ctr_ = 1;
held_time_ = 0;
}
} else {
// Button not continuously pressed; reset counter
held_time_ = 0;
}
}
}
return false;
}
+11
View File
@@ -28,6 +28,10 @@
#define DEBOUNCE_COUNT 4
#define DEBOUNCE_MASK ((1 << DEBOUNCE_COUNT) - 1)
// # of timer0 ticks before a held button starts being counted as repeated presses
#define REPEAT_INITIAL_DELAY 250
#define REPEAT_SUBSEQUENT_DELAY 92
class Debounce {
public:
bool feed(const uint8_t bit);
@@ -36,9 +40,16 @@ class Debounce {
return state_;
}
void enable_repeat() {
repeat_enabled_ = true;
}
private:
uint8_t history_{0};
uint8_t state_{0};
bool repeat_enabled_{0};
uint16_t repeat_ctr_{0};
uint16_t held_time_{0};
};
#endif /*__DEBOUNCE_H__*/
+71 -18
View File
@@ -23,23 +23,75 @@
#include "utility.hpp"
static const int8_t transition_map[] = {
0, // 0000: noop
0, // 0001: start
0, // 0010: start
0, // 0011: rate
1, // 0100: end
0, // 0101: noop
0, // 0110: rate
-1, // 0111: end
-1, // 1000: end
0, // 1001: rate
0, // 1010: noop
1, // 1011: end
0, // 1100: rate
0, // 1101: start
0, // 1110: start
0, // 1111: noop
#include "portapack.hpp"
#include "portapack_persistent_memory.hpp"
// Now supporting multiple levels of rotary encoder dial sensitivity
//
// Portapack H2 normally has a 30-step encoder, meaning one step (pulse) every
// 12 degrees of rotation.
//
// For each encoder "pulse" there are 4 state transitions, and we can choose
// between looking at all of them (high sensitivity), half of them (medium/default),
// or one quarter of them (low sensitivity).
static const int8_t transition_map[][16] = {
// Normal (Medium) Sensitivity -- default
{
0, // 0000: noop
0, // 0001: ccw start
0, // 0010: cw start
0, // 0011: rate
1, // 0100: cw end
0, // 0101: noop
0, // 0110: rate
-1, // 0111: ccw end
-1, // 1000: ccw end
0, // 1001: rate
0, // 1010: noop
1, // 1011: cw end
0, // 1100: rate
0, // 1101: cw start
0, // 1110: ccw start
0, // 1111: noop
},
// Low Sensitivity
{
0, // 0000: noop
0, // 0001: ccw start
0, // 0010: cw start
0, // 0011: rate
1, // 0100: cw end
0, // 0101: noop
0, // 0110: rate
0, // 0111: ccw end
-1, // 1000: ccw end
0, // 1001: rate
0, // 1010: noop
0, // 1011: cw end
0, // 1100: rate
0, // 1101: cw start
0, // 1110: ccw start
0, // 1111: noop
},
// High Sensitivity
{
0, // 0000: noop
-1, // 0001: ccw start
1, // 0010: cw start
0, // 0011: rate
1, // 0100: cw end
0, // 0101: noop
0, // 0110: rate
-1, // 0111: ccw end
-1, // 1000: ccw end
0, // 1001: rate
0, // 1010: noop
1, // 1011: cw end
0, // 1100: rate
1, // 1101: cw start
-1, // 1110: ccw start
0, // 1111: noop
},
};
int_fast8_t Encoder::update(
@@ -50,5 +102,6 @@ int_fast8_t Encoder::update(
state <<= 1;
state |= phase_1;
return transition_map[state & 0xf];
// dial sensitivity setting is stored in pmem
return transition_map[portapack::persistent_memory::config_encoder_dial_sensitivity()][state & 0xf];
}
+4
View File
@@ -187,6 +187,10 @@ void controls_init() {
*/
gptStart(&GPTD1, &timer0_config);
gptStartContinuous(&GPTD1, timer0_match_count);
// Enable repeat for directional switches only
for (size_t i = 0; i < 4; i++)
switch_debounce[i].enable_repeat();
}
SwitchesState get_switches_state() {
+22 -18
View File
@@ -233,20 +233,23 @@ static PortaPackModel portapack_model() {
if (!model.is_valid()) {
const auto switches_state = get_switches_state();
if (switches_state[(size_t)ui::KeyEvent::Up]) {
save_config(1);
// model = PortaPackModel::R2_20170522; // Commented these out as they should be set down below anyway
} else if (switches_state[(size_t)ui::KeyEvent::Down]) {
save_config(2);
// model = PortaPackModel::R1_20150901;
} else if (switches_state[(size_t)ui::KeyEvent::Left]) {
save_config(3);
// model = PortaPackModel::R1_20150901;
} else if (switches_state[(size_t)ui::KeyEvent::Right]) {
save_config(4);
// model = PortaPackModel::R2_20170522;
} else if (switches_state[(size_t)ui::KeyEvent::Select]) {
save_config(0);
// Only save config if no other multi key boot action is triggered (like pmem reset)
if (switches_state.count() == 1) {
if (switches_state[(size_t)ui::KeyEvent::Up]) {
save_config(1);
// model = PortaPackModel::R2_20170522; // Commented these out as they should be set down below anyway
} else if (switches_state[(size_t)ui::KeyEvent::Down]) {
save_config(2);
// model = PortaPackModel::R1_20150901;
} else if (switches_state[(size_t)ui::KeyEvent::Left]) {
save_config(3);
// model = PortaPackModel::R1_20150901;
} else if (switches_state[(size_t)ui::KeyEvent::Right]) {
save_config(4);
// model = PortaPackModel::R2_20170522;
} else if (switches_state[(size_t)ui::KeyEvent::Select]) {
save_config(0);
}
}
if (load_config() == 1) {
@@ -469,13 +472,13 @@ bool init() {
i2c0.start(i2c_config_fast_clock);
chThdSleepMilliseconds(10);
/* Cache some configuration data from persistent memory. */
persistent_memory::cache::init();
touch::adc::init();
controls_init();
chThdSleepMilliseconds(10);
/* Cache some configuration data from persistent memory. */
persistent_memory::cache::init();
clock_manager.set_reference_ppb(persistent_memory::correction_ppb());
clock_manager.enable_if_clocks();
clock_manager.enable_codec_clocks();
@@ -521,7 +524,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 {
+16 -23
View File
@@ -35,6 +35,7 @@ using namespace portapack;
#include "dsp_fir_taps.hpp"
#include "dsp_iir.hpp"
#include "dsp_iir_config.hpp"
#include "utility.hpp"
namespace {
@@ -118,15 +119,6 @@ void ReceiverModel::set_vga(int32_t v_db) {
update_vga();
}
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_;
}
@@ -146,14 +138,25 @@ void ReceiverModel::set_modulation(const Mode v) {
}
volume_t ReceiverModel::headphone_volume() const {
return headphone_volume_;
return persistent_memory::headphone_volume();
}
void ReceiverModel::set_headphone_volume(volume_t v) {
headphone_volume_ = v;
persistent_memory::set_headphone_volume(v);
update_headphone_volume();
}
int32_t ReceiverModel::normalized_headphone_volume() const {
return (headphone_volume() - audio::headphone::volume_range().max).decibel() + 99;
}
void ReceiverModel::set_normalized_headphone_volume(int32_t v) {
// TODO: Linear map instead to ensure 0 is minimal value or fix volume_range_t::normalize.
v = clip<int32_t>(v, 0, 99);
auto new_volume = volume_t::decibel(v - 99) + audio::headphone::volume_range().max;
set_headphone_volume(new_volume);
}
uint8_t ReceiverModel::squelch_level() const {
return squelch_level_;
}
@@ -171,13 +174,11 @@ void ReceiverModel::enable() {
update_rf_amp();
update_lna();
update_vga();
update_tx_gain();
update_baseband_bandwidth();
update_sampling_rate();
update_modulation();
// TODO: would challenge if this should belong to the
// receiver_model namespace:
// TODO: maybe not the perfect place for this, but it's reasonable.
update_headphone_volume();
led_rx.on();
@@ -185,7 +186,6 @@ void ReceiverModel::enable() {
void ReceiverModel::disable() {
enabled_ = false;
radio::set_antenna_bias(false);
// TODO: Responsibility for enabling/disabling the radio is muddy.
// Some happens in ReceiverModel, some inside radio namespace.
@@ -230,10 +230,6 @@ void ReceiverModel::update_vga() {
radio::set_vga_gain(vga_gain_db_);
}
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()) {
am_config_index = n;
@@ -266,10 +262,7 @@ void ReceiverModel::update_sampling_rate() {
}
void ReceiverModel::update_headphone_volume() {
// TODO: Manipulating audio codec here, and in ui_receiver.cpp. Good to do
// both?
audio::headphone::set_volume(headphone_volume_);
audio::headphone::set_volume(headphone_volume());
}
void ReceiverModel::update_modulation() {
+13 -7
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,9 +61,6 @@ 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);
uint32_t sampling_rate() const;
void set_sampling_rate(uint32_t v);
@@ -72,6 +70,10 @@ class ReceiverModel {
volume_t headphone_volume() const;
void set_headphone_volume(volume_t v);
/* Volume range 0-99, normalized for audio HW. */
int32_t normalized_headphone_volume() const;
void set_normalized_headphone_volume(int32_t v);
uint8_t squelch_level() const;
void set_squelch_level(uint8_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,13 +104,11 @@ 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};
Mode mode_{Mode::NarrowbandFMAudio};
uint32_t sampling_rate_{3072000};
size_t am_config_index = 0;
size_t nbfm_config_index = 0;
size_t wfm_config_index = 0;
volume_t headphone_volume_{-43.0_dB};
uint8_t squelch_level_{80};
int32_t tuning_offset();
@@ -113,7 +119,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();
+2 -2
View File
@@ -23,7 +23,6 @@
#define __RECENT_ENTRIES_H__
#include "ui_widget.hpp"
#include "ui_font_fixed_8x16.hpp"
#include <cstddef>
#include <cstdint>
@@ -128,7 +127,6 @@ class RecentEntriesTable : public Widget {
RecentEntriesTable(
Entries& recent)
: recent{recent} {
set_focusable(true);
}
void paint(Painter& painter) override {
@@ -138,6 +136,8 @@ class RecentEntriesTable : public Widget {
Rect target_rect{r.location(), {r.width(), s.font.line_height()}};
const size_t visible_item_count = r.height() / s.font.line_height();
set_focusable(!recent.empty());
auto selected = find(recent, selected_key);
if (selected == std::end(recent)) {
selected = std::begin(recent);
+1 -1
View File
@@ -38,7 +38,7 @@ using sample_t = uint16_t;
constexpr sample_t sample_max = 1023;
constexpr sample_t touch_threshold = sample_max / 5;
constexpr sample_t touch_threshold = sample_max / 16;
struct Samples {
sample_t xp;
+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{};
+2 -2
View File
@@ -43,8 +43,8 @@ class AlphanumView : public TextEntryView {
bool on_encoder(const EncoderEvent delta) override;
private:
const char* const keys_upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ' .<";
const char* const keys_lower = "abcdefghijklmnopqrstuvwxyz' .<";
const char* const keys_upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ, .<";
const char* const keys_lower = "abcdefghijklmnopqrstuvwxyz, .<";
const char* const keys_digit = "0123456789!\"#'()*+-/:;=>?@[\\]<";
const std::pair<std::string, const char*> key_sets[3] = {
@@ -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,
+177
View File
@@ -0,0 +1,177 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include "ui_freqlist.hpp"
#include "baseband_api.hpp"
#include "utility.hpp"
#include <algorithm>
namespace ui {
FreqManUIList::FreqManUIList(
Rect parent_rect,
bool instant_exec)
: Widget{parent_rect},
instant_exec_{instant_exec} {
this->set_focusable(true);
}
void FreqManUIList::set_highlighted_index(int index) {
if ((unsigned)(current_index + index) >= freqlist_db.size())
return;
if (index < 0) {
index = 0;
if (current_index > 0)
current_index--;
}
if (index >= freqlist_nb_lines) {
index = freqlist_nb_lines - 1;
if ((unsigned)(current_index + index) < freqlist_db.size())
current_index++;
else
current_index = freqlist_db.size() - freqlist_nb_lines - 1;
}
highlighted_index = index;
}
uint8_t FreqManUIList::get_index() {
return current_index + highlighted_index;
}
void FreqManUIList::paint(Painter& painter) {
freqlist_nb_lines = 0;
const auto r = screen_rect();
uint8_t focused = has_focus();
const Rect r_widget_screen{r.left() + focused, r.top() + focused, r.width() - 2 * focused, r.height() - 2 * +focused};
painter.fill_rectangle(
r_widget_screen,
Color::black());
// only return after clearing the screen so previous entries are not shown anymore
if (freqlist_db.size() == 0)
return;
// coloration if file is too big
auto text_color = &Styles::white;
if (freqlist_db.size() > FREQMAN_MAX_PER_FILE)
text_color = &Styles::yellow;
uint8_t nb_lines = 0;
for (uint8_t it = current_index; it < freqlist_db.size(); it++) {
uint8_t line_height = (int)nb_lines * char_height;
if (line_height < (r.height() - char_height)) { // line is within the widget
std::string description = freqman_item_string(freqlist_db[it], 30);
if (nb_lines == highlighted_index) {
const Rect r_highlighted_freq{0, r.location().y() + (int)nb_lines * char_height, 240, char_height};
painter.fill_rectangle(
r_highlighted_freq,
Color::white());
painter.draw_string(
{0, r.location().y() + (int)nb_lines * char_height},
Styles::bg_white, description);
} else {
painter.draw_string(
{0, r.location().y() + (int)nb_lines * char_height},
*text_color, description);
}
nb_lines++;
} else {
// rect is filled, we can break
break;
}
}
freqlist_nb_lines = nb_lines;
if (has_focus() || highlighted()) {
const Rect r_focus{r.left(), r.top(), r.width(), r.height()};
painter.draw_rectangle(
r_focus,
Color::white());
}
}
void FreqManUIList::set_db(freqman_db& db) {
freqlist_db = db;
if (db.size() == 0) {
current_index = 0;
highlighted_index = 0;
} else {
if ((unsigned)(current_index + highlighted_index) >= db.size()) {
current_index = db.size() - 1 - highlighted_index;
}
if (current_index < 0) {
current_index = 0;
if (highlighted_index > 0)
highlighted_index--;
}
}
}
void FreqManUIList::on_focus() {
if (on_highlight)
on_highlight(*this);
}
bool FreqManUIList::on_key(const KeyEvent key) {
if (key == KeyEvent::Select) {
if (on_select) {
on_select(*this);
return true;
}
} else {
if (on_dir) {
return on_dir(*this, key);
}
}
return false;
}
bool FreqManUIList::on_touch(const TouchEvent event) {
switch (event.type) {
case TouchEvent::Type::Start:
set_highlighted(true);
set_dirty();
if (on_touch_press) {
on_touch_press(*this);
}
if (on_select && instant_exec_) {
on_select(*this);
}
return true;
case TouchEvent::Type::End:
set_highlighted(false);
set_dirty();
if (on_touch_release) {
on_touch_release(*this);
}
if (on_select && !instant_exec_) {
on_select(*this);
}
return true;
default:
return false;
}
}
bool FreqManUIList::on_encoder(EncoderEvent delta) {
set_highlighted_index((int)highlighted_index + delta);
set_dirty();
return true;
}
} /* namespace ui */
+75
View File
@@ -0,0 +1,75 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#ifndef __UI_FREQLIST_H__
#define __UI_FREQLIST_H__
#include "ui.hpp"
#include "ui_widget.hpp"
#include "ui_painter.hpp"
#include "ui_styles.hpp"
#include "event_m0.hpp"
#include "message.hpp"
#include "freqman.hpp"
#include <cstdint>
namespace ui {
class FreqManUIList : public Widget {
public:
std::function<void(FreqManUIList&)> on_select{};
std::function<void(FreqManUIList&)> on_touch_release{}; // Executed when releasing touch, after on_select.
std::function<void(FreqManUIList&)> on_touch_press{}; // Executed when touching, before on_select.
std::function<bool(FreqManUIList&, KeyEvent)> on_dir{};
std::function<void(FreqManUIList&)> on_highlight{};
FreqManUIList(Rect parent_rect, bool instant_exec); // instant_exec: Execute on_select when you touching instead of releasing
FreqManUIList(
Rect parent_rect)
: FreqManUIList{parent_rect, false} {
}
FreqManUIList()
: FreqManUIList{{}, {}} {
}
void paint(Painter& painter) override;
void on_focus() override;
bool on_key(const KeyEvent key) override;
bool on_touch(const TouchEvent event) override;
bool on_encoder(EncoderEvent delta) override;
void set_highlighted_index(int index); // internal set highlighted_index in list handler
uint8_t get_index(); // return highlighed + index
uint8_t set_index(uint8_t index); // try to set current_index + highlighed from index, return capped index
void set_db(freqman_db& db);
private:
static constexpr int8_t char_height = 16;
bool instant_exec_{false};
freqman_db freqlist_db{};
int current_index{0};
int highlighted_index{0};
int freqlist_nb_lines{0};
};
} // namespace ui
#endif /*__UI_FREQLIST_H__*/

Some files were not shown because too many files have changed in this diff Show More