From 472571b1edbb46b8e9bfdd3f101e0223a593b318 Mon Sep 17 00:00:00 2001 From: RocketGod <57732082+RocketGod-git@users.noreply.github.com> Date: Fri, 12 Dec 2025 10:40:15 -0800 Subject: [PATCH] Refactor FLEX RX UI to add color cycling and message log (#2885) Replaces the console with a scrollable menu view for displaying FLEX messages, adds support for cycling text colors, and implements message logging with line wrapping and persistence. Also refactors frequency handling for persistence and updates UI element layout and initialization. --- .../external/flex_rx/ui_flex_rx.cpp | 109 ++++++++++++++++-- .../external/flex_rx/ui_flex_rx.hpp | 72 ++++++++---- 2 files changed, 151 insertions(+), 30 deletions(-) diff --git a/firmware/application/external/flex_rx/ui_flex_rx.cpp b/firmware/application/external/flex_rx/ui_flex_rx.cpp index 8bbe8c6bd..e04a688df 100644 --- a/firmware/application/external/flex_rx/ui_flex_rx.cpp +++ b/firmware/application/external/flex_rx/ui_flex_rx.cpp @@ -4,7 +4,6 @@ #include "portapack_persistent_memory.hpp" #include "string_format.hpp" #include "memory_map.hpp" -#include "usb_serial_asyncmsg.hpp" using namespace portapack; @@ -12,26 +11,41 @@ namespace ui::external_app::flex_rx { FlexAppView::FlexAppView(NavigationView& nav) : nav_{nav} { + // Load baseband image for FLEX decoding baseband::run_prepared_image(portapack::memory::map::m4_code.base()); add_children({&field_frequency, &field_rf_amp, &field_lna, &field_vga, + &button_color, &rssi, - &console}); + &menu_view}); - field_frequency.set_value(receiver_model.target_frequency()); + // Restore saved frequency + field_frequency.set_value(frequency_value); + receiver_model.set_target_frequency(frequency_value); + + // Frequency change callback field_frequency.updated = [this](rf::Frequency f) { update_freq(f); }; + // Color button cycles through available colors + button_color.on_select = [this](Button&) { + cycle_color(); + }; + + // Configure receiver receiver_model.set_sampling_rate(3072000); receiver_model.set_baseband_bandwidth(1750000); receiver_model.enable(); receiver_model.set_squelch_level(0); + // Initialize FLEX baseband baseband::set_flex_config(); + + log_message("FLEX RX Ready"); } FlexAppView::~FlexAppView() { @@ -43,32 +57,111 @@ void FlexAppView::focus() { field_frequency.focus(); } +// Cycle to next text color and refresh display +void FlexAppView::cycle_color() { + current_color_index = (current_color_index + 1) % text_colors.size(); + rebuild_menu(); +} + +// Rebuild entire menu with current color +void FlexAppView::rebuild_menu() { + menu_view.clear(); + Color current_color = text_colors[current_color_index]; + for (const auto& msg : log_messages) { + menu_view.add_item({msg, + current_color, + nullptr, + [](KeyEvent) {}}); + } + if (menu_view.item_count() > 0) { + menu_view.set_highlighted(menu_view.item_count() - 1); + } +} + +// Add message to log with automatic line wrapping +void FlexAppView::log_message(const std::string& message) { + // Calculate characters per line based on screen width (8 pixels per char) + const size_t chars_per_line = screen_width / 8; + Color current_color = text_colors[current_color_index]; + + std::string remaining = message; + bool first_line = true; + bool needs_rebuild = false; + size_t lines_added = 0; + + // Split message into screen-width chunks + while (!remaining.empty()) { + std::string line; + if (remaining.length() <= chars_per_line) { + line = remaining; + remaining.clear(); + } else { + line = remaining.substr(0, chars_per_line); + remaining = remaining.substr(chars_per_line); + } + + // Indent continuation lines + if (!first_line) { + line = " " + line; + } + first_line = false; + + // Remove oldest line if at limit + if (log_messages.size() >= MAX_LOG_LINES) { + log_messages.erase(log_messages.begin()); + needs_rebuild = true; + } + + log_messages.push_back(line); + lines_added++; + } + + // Either rebuild all or just add new lines + if (needs_rebuild) { + rebuild_menu(); + } else { + size_t start_idx = log_messages.size() - lines_added; + for (size_t i = start_idx; i < log_messages.size(); i++) { + menu_view.add_item({log_messages[i], + current_color, + nullptr, + [](KeyEvent) {}}); + } + if (menu_view.item_count() > 0) { + menu_view.set_highlighted(menu_view.item_count() - 1); + } + } +} + +// Update frequency and save for persistence void FlexAppView::update_freq(rf::Frequency f) { + frequency_value = f; receiver_model.set_target_frequency(f); } +// Handle decoded FLEX packet from baseband void FlexAppView::on_packet(const FlexPacketMessage* message) { - std::string text = ""; - - text += "FLEX "; + std::string text = "FLEX "; text += to_string_dec_uint(message->packet.bitrate); text += " "; text += to_string_dec_uint(message->packet.capcode); text += ": "; text += message->packet.message; - console.writeln(text); + log_message(text); } +// Handle stats message (currently unused) void FlexAppView::on_stats(const FlexStatsMessage* /* message */) { } +// Handle debug message from baseband void FlexAppView::on_debug(const FlexDebugMessage* message) { std::string text = "DBG: "; text += message->text; text += " " + to_string_hex(message->val1, 8); text += " " + to_string_hex(message->val2, 8); - console.writeln(text); + log_message(text); } } // namespace ui::external_app::flex_rx \ No newline at end of file diff --git a/firmware/application/external/flex_rx/ui_flex_rx.hpp b/firmware/application/external/flex_rx/ui_flex_rx.hpp index f3d6f7d42..034633487 100644 --- a/firmware/application/external/flex_rx/ui_flex_rx.hpp +++ b/firmware/application/external/flex_rx/ui_flex_rx.hpp @@ -6,14 +6,12 @@ #include "ui_receiver.hpp" #include "ui_freq_field.hpp" #include "ui_rssi.hpp" -#include "ui_spectrum.hpp" -#include "ui_record_view.hpp" #include "app_settings.hpp" #include "radio_state.hpp" -#include "freqman_db.hpp" #include #include +#include namespace ui::external_app::flex_rx { @@ -27,37 +25,70 @@ class FlexAppView : public View { private: NavigationView& nav_; - RxRadioState radio_state_{ - 929612500 /* frequency - common pager freq in some regions, or 931M */, - max283x::filter::bandwidth_minimum /* bandwidth */, - 3072000 /* sampling rate */ - }; - // UI Elements - Row 0 + // Saved settings + rf::Frequency frequency_value{931740000}; // Default FLEX frequency + uint32_t current_color_index{0}; // Current text color selection + + // Available text colors for message display + static constexpr std::array text_colors = {{Color::green(), + Color::white(), + Color::cyan(), + Color::magenta(), + Color::yellow(), + Color::blue(), + Color::red()}}; + + RxRadioState radio_state_{}; + + // Message log settings + static constexpr size_t MAX_LOG_LINES = 32; // Limit to prevent memory issues + std::vector log_messages{}; // Stored log lines + + // Helper methods + void log_message(const std::string& message); // Add message with word wrap + void rebuild_menu(); // Rebuild menu after color change or overflow + void update_freq(rf::Frequency f); // Update tuned frequency + void cycle_color(); // Cycle through text colors + + // UI Elements - Row 0, dynamically positioned RxFrequencyField field_frequency{ - {0 * 8, 0 * 16}, + {UI_POS_X(0), UI_POS_Y(0)}, nav_}; RFAmpField field_rf_amp{ - {13 * 8, 0 * 16}}; + {UI_POS_X(13), UI_POS_Y(0)}}; LNAGainField field_lna{ - {15 * 8, 0 * 16}}; + {UI_POS_X(15), UI_POS_Y(0)}}; VGAGainField field_vga{ - {18 * 8, 0 * 16}}; + {UI_POS_X(18), UI_POS_Y(0)}}; + + // Color cycle button + Button button_color{ + {UI_POS_X(21), UI_POS_Y(0), UI_POS_WIDTH(5), UI_POS_HEIGHT(1)}, + "COLOR"}; RSSI rssi{ - {21 * 8, 0, 6 * 8, 4}}; + {UI_POS_X(26), 0, UI_POS_WIDTH(4), 4}}; - // Console - starts at row 1, extends to bottom of screen - Console console{ - {0, 1 * 16, screen_width, screen_height - 1 * 16}}; + // Message display area - scrollable menu view + MenuView menu_view{ + {0, 1 * 16, screen_width, screen_height - 1 * 16}, + true}; - // Logic + // Persistent settings manager + app_settings::SettingsManager settings_{ + "rx_flex", + app_settings::Mode::RX, + {{"frequency", &frequency_value}, + {"color_index", ¤t_color_index}}}; + + // Message handlers void on_packet(const FlexPacketMessage* message); void on_stats(const FlexStatsMessage* message); void on_debug(const FlexDebugMessage* message); - // Message Handlers + // Message handler registrations MessageHandlerRegistration message_handler_packet{ Message::ID::FlexPacket, [this](const Message* const p) { @@ -78,9 +109,6 @@ class FlexAppView : public View { const auto message = *static_cast(p); this->on_debug(&message); }}; - - // Config - void update_freq(rf::Frequency f); }; } // namespace ui::external_app::flex_rx