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.
This commit is contained in:
RocketGod
2025-12-12 10:40:15 -08:00
committed by GitHub
parent 106e56abc3
commit 472571b1ed
2 changed files with 151 additions and 30 deletions
+101 -8
View File
@@ -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
+50 -22
View File
@@ -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 <string>
#include <vector>
#include <array>
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<Color, 7> 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<std::string> 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", &current_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<const FlexDebugMessage*>(p);
this->on_debug(&message);
}};
// Config
void update_freq(rf::Frequency f);
};
} // namespace ui::external_app::flex_rx