From dc9a16c54be987d7f962b95027daaf517e3001b8 Mon Sep 17 00:00:00 2001 From: Kyle Reed <3761006+kallanreed@users.noreply.github.com> Date: Tue, 22 Aug 2023 07:56:10 -0700 Subject: [PATCH] Looking Glass - persist range/preset settings, UI tweaks (#1401) * WIP * Add encoder support for TextField * Working settings, use TextField * Remove unneeded blanking rectangle --- .../application/apps/ui_looking_glass_app.cpp | 180 ++++++++---------- .../application/apps/ui_looking_glass_app.hpp | 53 +++--- firmware/common/ui_widget.cpp | 9 + firmware/common/ui_widget.hpp | 2 + 4 files changed, 120 insertions(+), 124 deletions(-) diff --git a/firmware/application/apps/ui_looking_glass_app.cpp b/firmware/application/apps/ui_looking_glass_app.cpp index 8f88b08a..c5d3dd2a 100644 --- a/firmware/application/apps/ui_looking_glass_app.cpp +++ b/firmware/application/apps/ui_looking_glass_app.cpp @@ -74,8 +74,8 @@ rf::Frequency GlassView::get_freq_from_bin_pos(uint8_t 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 + field_marker.set_text(to_string_short_freq(marker)); + plot_marker(marker_pixel_index); // Refresh marker on screen } void GlassView::retune() { @@ -87,21 +87,13 @@ void GlassView::retune() { baseband::spectrum_streaming_start(); // Do the RX } -void GlassView::on_lna_changed(int32_t v_db) { - receiver_model.set_lna(v_db); -} - -void GlassView::on_vga_changed(int32_t v_db) { - receiver_model.set_vga(v_db); -} - void GlassView::reset_live_view(bool clear_screen) { max_freq_hold = 0; max_freq_power = -1000; if (clear_screen) { // only clear screen in peak mode if (live_frequency_view == 2) { - display.fill_rectangle({{0, 108 + 16}, {SCREEN_W, 320 - (108 + 16)}}, {0, 0, 0}); + display.fill_rectangle({{0, 108 + 16}, {SCREEN_W, SCREEN_H - (108 + 16)}}, {0, 0, 0}); } } } @@ -134,15 +126,15 @@ void GlassView::add_spectrum_pixel(uint8_t power) { uint8_t color_gradient = (point * 255) / 212; // clear if not in peak view if (live_frequency_view != 2) { - display.fill_rectangle({{xpos, 108 + 16}, {1, 320 - point}}, {0, 0, 0}); + display.fill_rectangle({{xpos, 108 + 16}, {1, SCREEN_H - point}}, {0, 0, 0}); } - display.fill_rectangle({{xpos, 320 - point}, {1, point}}, {color_gradient, 0, uint8_t(255 - color_gradient)}); + display.fill_rectangle({{xpos, SCREEN_H - point}, {1, point}}, {color_gradient, 0, uint8_t(255 - color_gradient)}); } if (last_max_freq != max_freq_hold) { last_max_freq = max_freq_hold; freq_stats.set("MAX HOLD: " + to_string_short_freq(max_freq_hold)); } - PlotMarker(marker_pixel_index); + plot_marker(marker_pixel_index); } else { display.draw_pixels({{0, display.scroll(1)}, {SCREEN_W, 1}}, spectrum_row); // new line at top, one less var, speedier } @@ -176,7 +168,7 @@ bool GlassView::process_bins(uint8_t* powerlevel) { } // Apparently, the spectrum object returns an array of SPEC_NB_BINS (256) bins -// Each having the radio signal power for it's corresponding frequency slot +// Each having the radio signal power for its corresponding frequency slot 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 @@ -231,8 +223,7 @@ void GlassView::on_range_changed() { 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 - portapack::display.fill_rectangle({17 * 8, 4 * 16, 2 * 8, 16}, Color::black()); // Clear old marker and whole marker rectangle btw + f_center_ini = f_min + (looking_glass_bandwidth / 2); // Initial center frequency for sweep } else { // view is made in multiple pass, use original bin picking mode = scan_type.selected_index_value(); @@ -253,19 +244,13 @@ void GlassView::on_range_changed() { } search_span = looking_glass_range / MHZ_DIV; 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 { - button_range.set_text(" " + to_string_dec_uint(search_span) + " "); - } 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 - // + on_marker_change(); + update_range_field(); // set the sample rate and bandwidth receiver_model.set_sampling_rate(looking_glass_sampling_rate); @@ -277,7 +262,7 @@ void GlassView::on_range_changed() { receiver_model.set_target_frequency(f_center); // tune rx for this slice } -void GlassView::PlotMarker(uint8_t pos) { +void GlassView::plot_marker(uint8_t pos) { uint8_t shift_y = 0; if (live_frequency_view > 0) // plot one line down when in live view { @@ -289,7 +274,7 @@ 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) { +void GlassView::update_min(int32_t v) { int32_t min_size = steps; if (locked_range) min_size = search_span; @@ -299,15 +284,14 @@ void GlassView::clip_min(int32_t v) { v = 7200 - min_size; } if (v > (field_frequency_max.value() - min_size)) - field_frequency_max.set_value(v + min_size); + field_frequency_max.set_value(v + min_size, false); if (locked_range) - field_frequency_max.set_value(v + min_size); + field_frequency_max.set_value(v + min_size, false); else - field_frequency_min.set_value(v); - on_range_changed(); + field_frequency_min.set_value(v, false); } -void GlassView::clip_max(int32_t v) { +void GlassView::update_max(int32_t v) { int32_t min_size = steps; if (locked_range) min_size = search_span; @@ -317,12 +301,21 @@ void GlassView::clip_max(int32_t v) { v = min_size; } if (v < (field_frequency_min.value() + min_size)) - field_frequency_min.set_value(v - min_size); + field_frequency_min.set_value(v - min_size, false); if (locked_range) - field_frequency_min.set_value(v - min_size); + field_frequency_min.set_value(v - min_size, false); else - field_frequency_max.set_value(v); - on_range_changed(); + field_frequency_max.set_value(v, false); +} + +void GlassView::update_range_field() { + if (!locked_range) { + field_range.set_style(&Styles::white); + field_range.set_text(" " + to_string_dec_uint(search_span) + " "); + } else { + field_range.set_style(&Styles::red); + field_range.set_text(">" + to_string_dec_uint(search_span) + "<"); + } } GlassView::GlassView( @@ -335,7 +328,7 @@ GlassView::GlassView( &field_frequency_max, &field_lna, &field_vga, - &button_range, + &field_range, &steps_config, &scan_type, &view_config, @@ -343,69 +336,67 @@ GlassView::GlassView( &filter_config, &field_rf_amp, &range_presets, - &button_marker, + &field_marker, &field_trigger, &button_jump, &button_rst, &freq_stats}); - load_Presets(); // Load available presets from TXT files (or default) + load_presets(); // Load available presets from TXT files (or default). + preset_index = clip(preset_index, 0, presets_db.size()); + field_frequency_min.set_value(f_min / MHZ_DIV); field_frequency_min.on_change = [this](int32_t v) { - clip_min(v); + range_presets.set_selected_index(0); // Manual + update_min(v); + on_range_changed(); }; - field_frequency_min.set_value(presets_db[0].min); // Defaults to first preset - field_frequency_min.set_step(steps); - field_frequency_min.on_select = [this, &nav](NumberField& field) { auto new_view = nav_.push(field_frequency_min.value() * MHZ_DIV); new_view->on_changed = [this, &field](rf::Frequency f) { - clip_min(f / MHZ_DIV); + field_frequency_min.set_value(f / MHZ_DIV); }; }; + field_frequency_max.set_value(f_max / MHZ_DIV); field_frequency_max.on_change = [this](int32_t v) { - clip_max(v); + range_presets.set_selected_index(0); // Manual + update_max(v); + on_range_changed(); }; - field_frequency_max.set_value(presets_db[0].max); // Defaults to first preset - field_frequency_max.set_step(steps); - field_frequency_max.on_select = [this, &nav](NumberField& field) { auto new_view = nav_.push(field_frequency_max.value() * MHZ_DIV); new_view->on_changed = [this, &field](rf::Frequency f) { - clip_max(f / MHZ_DIV); + field_frequency_max.set_value(f / MHZ_DIV); }; }; - field_lna.on_change = [this](int32_t v) { + field_lna.on_change = [this](int32_t v_db) { reset_live_view(true); - this->on_lna_changed(v); + receiver_model.set_lna(v_db); }; field_lna.set_value(receiver_model.lna()); field_vga.on_change = [this](int32_t v_db) { reset_live_view(true); - this->on_vga_changed(v_db); + receiver_model.set_vga(v_db); }; field_vga.set_value(receiver_model.vga()); - steps_config.on_change = [this](size_t n, OptionsField::value_t v) { - (void)n; + steps_config.on_change = [this](size_t, OptionsField::value_t v) { field_frequency_min.set_step(v); field_frequency_max.set_step(v); steps = v; }; steps_config.set_selected_index(0); // default of 1 Mhz steps - scan_type.on_change = [this](size_t n, OptionsField::value_t v) { - (void)n; + scan_type.on_change = [this](size_t, OptionsField::value_t v) { mode = v; on_range_changed(); }; scan_type.set_selected_index(0); // default legacy fast scan - view_config.on_change = [this](size_t n, OptionsField::value_t v) { - (void)n; + view_config.on_change = [this](size_t, OptionsField::value_t v) { // clear between changes reset_live_view(true); if (v == 0) { @@ -436,41 +427,37 @@ GlassView::GlassView( }; view_config.set_selected_index(0); // default spectrum - level_integration.on_change = [this](size_t n, OptionsField::value_t v) { - (void)n; + level_integration.on_change = [this](size_t, OptionsField::value_t v) { reset_live_view(true); live_frequency_integrate = v; }; level_integration.set_selected_index(3); // default integration of ( 3 * old value + new_value ) / 4 - filter_config.on_change = [this](size_t n, OptionsField::value_t v) { - (void)n; + filter_config.on_change = [this](size_t, OptionsField::value_t v) { reset_live_view(true); min_color_power = v; }; filter_config.set_selected_index(0); - range_presets.on_change = [this](size_t n, OptionsField::value_t v) { - (void)n; + range_presets.on_change = [this](size_t ix, OptionsField::value_t v) { + preset_index = ix; + if (ix == 0) return; // Don't update range for "Manual". + + // NB: Don't trigger updates, presets directly set the range + // values without applying step or range lock. field_frequency_min.set_value(presets_db[v].min, false); field_frequency_max.set_value(presets_db[v].max, false); on_range_changed(); }; + range_presets.set_selected_index(preset_index); - button_marker.on_change = [this]() { - if (((int)marker_pixel_index + button_marker.get_encoder_delta()) < 0) { - marker_pixel_index = 0; - } else if (((int)marker_pixel_index + button_marker.get_encoder_delta()) > SCREEN_W) { - marker_pixel_index = SCREEN_W; - } else { - marker_pixel_index = marker_pixel_index + button_marker.get_encoder_delta(); - } + field_marker.on_encoder_change = [this](TextField&, EncoderEvent delta) { + marker_pixel_index = clip(marker_pixel_index + delta, 0, SCREEN_W); on_marker_change(); - button_marker.set_encoder_delta(0); }; - button_marker.on_select = [this](ButtonWithEncoder&) { - receiver_model.set_target_frequency(marker); // Center tune rx in marker freq. + field_marker.on_select = [this](TextField&) { + receiver_model.set_target_frequency(marker); // Center tune rx to marker freq. auto settings = receiver_model.settings(); settings.frequency_step = MHZ_DIV; // Preset a 1 MHz frequency step into RX -> AUDIO nav_.replace(settings); // Jump into audio view @@ -481,16 +468,9 @@ GlassView::GlassView( }; 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(&Styles::white); - button_range.set_text(" " + to_string_dec_uint(search_span) + " "); - } else { - locked_range = true; - button_range.set_style(&Styles::red); - button_range.set_text(">" + to_string_dec_uint(search_span) + "<"); - } + field_range.on_select = [this](TextField&) { + locked_range = !locked_range; + update_range_field(); }; button_jump.on_select = [this](Button&) { @@ -506,12 +486,12 @@ GlassView::GlassView( display.scroll_set_area(109, 319); baseband::set_spectrum(looking_glass_bandwidth, field_trigger.value()); // trigger: - // Discord User jteich: WidebandSpectrum::on_message to set the trigger value. In WidebandSpectrum::execute , + // 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(); + marker_pixel_index = SCREEN_W / 2; + on_range_changed(); // Force an update. receiver_model.set_sampling_rate(looking_glass_sampling_rate); // 20mhz receiver_model.set_baseband_bandwidth(looking_glass_bandwidth); // possible values: 1.75/2.5/3.5/5/5.5/6/7/8/9/10/12/14/15/20/24/28MHz @@ -519,11 +499,14 @@ GlassView::GlassView( receiver_model.enable(); } -void GlassView::load_Presets() { +void GlassView::load_presets() { File presets_file; auto error = presets_file.open("LOOKINGGLASS/PRESETS.TXT"); presets_db.clear(); + // Add the "Manual" entry. + presets_db.push_back({0, 0, "Manual"}); + if (!error) { auto reader = FileLineReader(presets_file); for (const auto& line : reader) { @@ -546,14 +529,16 @@ void GlassView::load_Presets() { } } - // Couldn't load any from the file, load a default instead. - if (presets_db.empty()) - presets_Default(); + /* + // Didn't load any from the file, add a default. + if (presets_db.size() == 1) + presets_db.push_back({2320, 2560, "DEFAULT WIFI 2.4GHz"}); + */ - populate_Presets(); + populate_presets(); } -void GlassView::populate_Presets() { +void GlassView::populate_presets() { using option_t = std::pair; using options_t = std::vector; options_t entries; @@ -561,12 +546,7 @@ void GlassView::populate_Presets() { for (const auto& preset : presets_db) entries.emplace_back(preset.label, entries.size()); - range_presets.set_options(entries); -} - -void GlassView::presets_Default() { - presets_db.clear(); - presets_db.push_back({2320, 2560, "DEFAULT WIFI 2.4GHz"}); + range_presets.set_options(std::move(entries)); } } // namespace ui diff --git a/firmware/application/apps/ui_looking_glass_app.hpp b/firmware/application/apps/ui_looking_glass_app.hpp index 0a6b1669..1fbbf82c 100644 --- a/firmware/application/apps/ui_looking_glass_app.hpp +++ b/firmware/application/apps/ui_looking_glass_app.hpp @@ -72,8 +72,18 @@ class GlassView : public View { private: NavigationView& nav_; RxRadioState radio_state_{ReceiverModel::Mode::SpectrumAnalysis}; + // Settings + rf::Frequency f_min = 260 * MHZ_DIV; // Default to 315/433 remote range. + rf::Frequency f_max = 500 * MHZ_DIV; + uint8_t preset_index = 0; app_settings::SettingsManager settings_{ - "rx_glass", app_settings::Mode::RX}; + "rx_glass"sv, + app_settings::Mode::RX, + { + {"min"sv, &f_min}, + {"max"sv, &f_max}, + {"preset"sv, &preset_index}, + }}; struct preset_entry { rf::Frequency min{}; @@ -82,8 +92,9 @@ class GlassView : public View { }; std::vector presets_db{}; - void clip_min(int32_t v); - void clip_max(int32_t v); + void update_min(int32_t v); + void update_max(int32_t v); + void update_range_field(); 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(); @@ -94,17 +105,13 @@ class GlassView : public View { 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); void on_range_changed(); - void on_lna_changed(int32_t v_db); - void on_vga_changed(int32_t v_db); void reset_live_view(bool clear_screen); void add_spectrum_pixel(uint8_t power); - void PlotMarker(uint8_t pos); - void load_Presets(); + void plot_marker(uint8_t pos); + void load_presets(); void txtline_process(std::string& line); - void populate_Presets(); - void presets_Default(); + void populate_presets(); - rf::Frequency f_min{0}, f_max{0}; rf::Frequency search_span{0}; rf::Frequency f_center{0}; rf::Frequency f_center_ini{0}; @@ -113,7 +120,7 @@ class GlassView : public View { rf::Frequency marker_pixel_step{0}; // size of one spectrum bin in Hz rf::Frequency each_bin_size{0}; - // consumed number of Hz, used to know if we have filled a 'bag' , a corresponding pixel length on screen + // consumed number of Hz, used to know if we have filled a 'bag', a corresponding pixel length on screen rf::Frequency bins_Hz_size{0}; rf::Frequency looking_glass_sampling_rate{0}; rf::Frequency looking_glass_bandwidth{0}; @@ -141,10 +148,10 @@ class GlassView : public View { uint8_t ignore_dc = 0; Labels labels{ - {{0, 0}, "MIN: MAX: LNA VGA ", Color::light_grey()}, - {{0, 1 * 16}, "RANGE: FILTER: AMP:", Color::light_grey()}, + {{0, 0 * 16}, "MIN: MAX: LNA VGA ", Color::light_grey()}, + {{0, 1 * 16}, "RANGE: FILTER: AMP:", Color::light_grey()}, {{0, 2 * 16}, "PRESET:", Color::light_grey()}, - {{0, 3 * 16}, "MARKER: MHz", Color::light_grey()}, + {{0, 3 * 16}, "MARKER: MHz", Color::light_grey()}, {{0, 4 * 16}, "RES: STEP:", Color::light_grey()}}; NumberField field_frequency_min{ @@ -167,8 +174,8 @@ class GlassView : public View { VGAGainField field_vga{ {27 * 8, 0 * 16}}; - Button button_range{ - {7 * 8, 1 * 16, 4 * 8, 16}, + TextField field_range{ + {6 * 8, 1 * 16, 6 * 8, 16}, ""}; OptionsField filter_config{ @@ -176,23 +183,21 @@ class GlassView : public View { 4, { {"OFF ", 0}, - {"MID ", 118}, // 85 +25 (110) + a bit more to kill all blue + {"MID ", 118}, // 85 + 25 (110) + a bit more to kill all blue {"HIGH", 202}, // 168 + 25 (193) }}; RFAmpField field_rf_amp{ - {29 * 8, 1 * 16}}; + {28 * 8, 1 * 16}}; OptionsField range_presets{ {7 * 8, 2 * 16}, 20, - { - {" NONE (WIFI 2.4GHz)", 0}, - }}; + {}}; - ButtonWithEncoder button_marker{ - {7 * 8, 3 * 16, 10 * 8, 16}, - " "}; + TextField field_marker{ + {7 * 8, 3 * 16, 9 * 8, 16}, + ""}; NumberField field_trigger{ {4 * 8, 4 * 16}, diff --git a/firmware/common/ui_widget.cpp b/firmware/common/ui_widget.cpp index 1e4dc9c0..a38d9e1b 100644 --- a/firmware/common/ui_widget.cpp +++ b/firmware/common/ui_widget.cpp @@ -1764,6 +1764,15 @@ bool TextField::on_key(KeyEvent key) { return false; } +bool TextField::on_encoder(EncoderEvent delta) { + if (on_encoder_change) { + on_encoder_change(*this, delta); + return true; + } + + return false; +} + /* NumberField ***********************************************************/ NumberField::NumberField( diff --git a/firmware/common/ui_widget.hpp b/firmware/common/ui_widget.hpp index c2558766..2f2d68a6 100644 --- a/firmware/common/ui_widget.hpp +++ b/firmware/common/ui_widget.hpp @@ -694,6 +694,7 @@ class TextField : public Text { public: std::function on_select{}; std::function on_change{}; + std::function on_encoder_change{}; TextField(Rect parent_rect, std::string text); @@ -701,6 +702,7 @@ class TextField : public Text { void set_text(std::string_view value); bool on_key(KeyEvent key) override; + bool on_encoder(EncoderEvent delta) override; private: using Text::set;