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