mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-08-16 21:03:24 +00:00
FLEX RX: comprehensive decoder rewrite (#3131)
Baseband (proc_flex.cpp): - Fix numeric vector field width (3-bit n+1 for types 3,4,7) - Fix capcode range classification for long address components - Add long address decode (Set 1-2, 1-3/1-4, 2-3) - Add vector checksum validation (4-bit nibble sum) - Add tone-only boundary detection via vector pre-scan - Add idle phase detection (skip all-zeros/all-ones before BCH) - Skip spurious TON for capcode 1 (BCH-corrected idle artifact) - Add BIW parsing: SSID1/SSID2, DATE, TIME, TZ, SYSMSG, CHAN - Add alpha fragment flags: frag, more_frag, seq, new, maildrop, sig - Add secure message enc= field (alpha/binary/separate) - Add numbered numeric flags: seq, new, fmt - Add HEX/Binary header decode: block_bits, new, maildrop, rtl - Add short instruction decode: temp group slot/target, sys events - Add short message (SMSG) decode: tone/numeric/source/numbered - Add group/temp-group/priority flags per address - Add S2 C-pattern detection with ±1 symbol boundary correction - Extract FIW roaming, repeat, traffic fields - Replace snprintf with lightweight str_* helpers (no heap/_sbrk) - Mark uncorrectable BCH words as ? instead of aborting phase - ETX/NUL handling: trim trailing padding, show mid-message as ? App (ui_flex_rx.cpp): - Add status bar: cycle/frame, bitrate, polarity, time, timezone - Add network info bar: LID, CZ, CC, roaming flag - Pipe-delimited serial output for all message types - BIW events to serial and status bar (not console) - Human-readable console: +GRP for temp group, G/TG/P flags - USB serial via UsbSerialAsyncmsg Packet (flex_defs.hpp): - uint64_t capcode (long address support) - 256-byte message buffer - Fragment flags, BIW raw values, address type, group/priority Known limitation: phase label unreliable on multi-phase modes (3200/2FSK, 6400/4FSK). Data always decodes correctly.
This commit is contained in:
+293
-21
@@ -4,6 +4,7 @@
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "memory_map.hpp"
|
||||
#include "usb_serial_asyncmsg.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
|
||||
@@ -19,6 +20,8 @@ FlexAppView::FlexAppView(NavigationView& nav)
|
||||
&field_lna,
|
||||
&field_vga,
|
||||
&rssi,
|
||||
&text_status1,
|
||||
&text_status2,
|
||||
&console});
|
||||
|
||||
// Restore saved frequency
|
||||
@@ -54,12 +57,7 @@ void FlexAppView::focus() {
|
||||
// Redraw all messages to console
|
||||
void FlexAppView::redraw_console() {
|
||||
console.clear(true);
|
||||
bool first = true;
|
||||
for (const auto& msg : messages) {
|
||||
if (!first) {
|
||||
console.writeln(""); // Blank line between messages
|
||||
}
|
||||
first = false;
|
||||
console.writeln(msg);
|
||||
}
|
||||
}
|
||||
@@ -67,15 +65,13 @@ void FlexAppView::redraw_console() {
|
||||
// Add message to log with automatic line wrapping
|
||||
void FlexAppView::log_message(const std::string& message) {
|
||||
const size_t chars_per_line = screen_width / 8;
|
||||
// Console height accounts for status bar and controls row
|
||||
const size_t console_lines = (screen_height - 2 * 16) / 16;
|
||||
// Console height matches widget: starts at 3*16, height = screen_height - 4*16
|
||||
const size_t console_lines = (screen_height - 4 * 16) / 16;
|
||||
|
||||
messages.push_back(message);
|
||||
|
||||
// Calculate total lines used (messages + blank lines between them)
|
||||
size_t total_lines = 0;
|
||||
for (size_t i = 0; i < messages.size(); i++) {
|
||||
if (i > 0) total_lines++; // Count blank line separator
|
||||
size_t msg_lines = (messages[i].length() + chars_per_line - 1) / chars_per_line;
|
||||
if (msg_lines == 0) msg_lines = 1;
|
||||
total_lines += msg_lines;
|
||||
@@ -88,15 +84,10 @@ void FlexAppView::log_message(const std::string& message) {
|
||||
size_t oldest_lines = (oldest.length() + chars_per_line - 1) / chars_per_line;
|
||||
if (oldest_lines == 0) oldest_lines = 1;
|
||||
total_lines -= oldest_lines;
|
||||
if (messages.size() > 1) total_lines--; // Remove separator line too
|
||||
messages.erase(messages.begin());
|
||||
}
|
||||
redraw_console();
|
||||
} else {
|
||||
// Just append new message
|
||||
if (messages.size() > 1) {
|
||||
console.writeln(""); // Blank line before new message
|
||||
}
|
||||
console.writeln(message);
|
||||
}
|
||||
}
|
||||
@@ -107,9 +98,287 @@ void FlexAppView::update_freq(rf::Frequency f) {
|
||||
receiver_model.set_target_frequency(f);
|
||||
}
|
||||
|
||||
// Type tag from numeric type code
|
||||
static const char* flex_type_tag(uint32_t type) {
|
||||
switch (type) {
|
||||
case 0:
|
||||
return "SEC";
|
||||
case 1:
|
||||
return "INS";
|
||||
case 2:
|
||||
return "TON";
|
||||
case 3:
|
||||
return "NUM";
|
||||
case 4:
|
||||
return "SNUM";
|
||||
case 5:
|
||||
return "ALN";
|
||||
case 6:
|
||||
return "HEX";
|
||||
case 7:
|
||||
return "NNUM";
|
||||
case 8:
|
||||
return "SMSG";
|
||||
case 9:
|
||||
return "BIW";
|
||||
default:
|
||||
return "UNK";
|
||||
}
|
||||
}
|
||||
|
||||
// Handle decoded FLEX packet from baseband
|
||||
void FlexAppView::on_packet(const FlexPacketMessage* message) {
|
||||
log_message(message->packet.message);
|
||||
const auto& pkt = message->packet;
|
||||
const char* type = flex_type_tag(pkt.type);
|
||||
const char* pol = pkt.is_inverted ? "I" : "N";
|
||||
|
||||
// Update status row 1: C/F speed polarity time timezone
|
||||
{
|
||||
std::string s1 = "C" + to_string_dec_uint(pkt.cycle) +
|
||||
"/F" + to_string_dec_uint(pkt.frame) +
|
||||
" " + to_string_dec_uint(pkt.bitrate) +
|
||||
" " + pol;
|
||||
if (status_time_[0]) {
|
||||
s1 += " ";
|
||||
s1 += status_time_;
|
||||
}
|
||||
if (status_tz_[0]) {
|
||||
s1 += " ";
|
||||
s1 += status_tz_;
|
||||
}
|
||||
text_status1.set(s1);
|
||||
}
|
||||
|
||||
// Update status row 2 from BIW data
|
||||
if (pkt.type == 9) {
|
||||
switch (pkt.biw_field) {
|
||||
case 0: // SSID1
|
||||
status_lid_ = pkt.biw_v1;
|
||||
status_cz_ = pkt.biw_v2;
|
||||
break;
|
||||
case 2: { // Time
|
||||
uint32_t si = (pkt.biw_v3 * 75) / 10;
|
||||
auto h = to_string_dec_uint(pkt.biw_v1, 2, '0');
|
||||
auto m = to_string_dec_uint(pkt.biw_v2, 2, '0');
|
||||
auto sec = to_string_dec_uint(si, 2, '0');
|
||||
// Store for row 1
|
||||
auto ts = h + ":" + m + ":" + sec;
|
||||
memcpy(status_time_, ts.c_str(), ts.size() + 1);
|
||||
break;
|
||||
}
|
||||
case 5: { // SysInfo (timezone)
|
||||
if (pkt.biw_v1 == 4 || pkt.biw_v1 == 5) {
|
||||
static const int tz[] = {0, 60, 120, 180, 240, 300, 360, 420, 480, 540, 600, 660, 720,
|
||||
210, 270, 330, 0, 345, 390, 570, -210, -660, -600, -540, -480, -420, -360, -300, -240, -180, -120, -60};
|
||||
uint16_t zone = pkt.biw_v2 & 0x1F;
|
||||
int ofs = (zone < 32) ? tz[zone] : 0;
|
||||
auto tzs = std::string("UTC") + (ofs >= 0 ? "+" : "") +
|
||||
to_string_dec_int(ofs / 60);
|
||||
memcpy(status_tz_, tzs.c_str(), tzs.size() + 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 7: // SSID2
|
||||
status_cc_ = pkt.biw_v1;
|
||||
break;
|
||||
}
|
||||
// Rebuild row 2
|
||||
std::string s2;
|
||||
if (status_lid_) s2 += "LID:" + to_string_dec_uint(status_lid_);
|
||||
if (status_cz_) {
|
||||
s2 += " CZ:";
|
||||
s2 += to_string_dec_uint(status_cz_);
|
||||
}
|
||||
if (status_cc_) {
|
||||
s2 += " CC:";
|
||||
s2 += to_string_dec_uint(status_cc_);
|
||||
}
|
||||
if (pkt.fiw_roaming) s2 += " R";
|
||||
text_status2.set(s2);
|
||||
}
|
||||
|
||||
// Console: skip BIW events (shown in status bar), show messages only
|
||||
if (pkt.type != 9) {
|
||||
auto cf = to_string_dec_uint(pkt.cycle) + "/" + to_string_dec_uint(pkt.frame);
|
||||
std::string line = cf + " " + to_string_dec_uint(pkt.bitrate) +
|
||||
" " + pol + " " + std::string(1, pkt.phase) + " ";
|
||||
|
||||
if (pkt.type == 1 && pkt.message[0] == 'i' && pkt.message[2] == 't') {
|
||||
// INS temp group: "1234567 +GRP5@F42"
|
||||
line += to_string_dec_uint(pkt.capcode);
|
||||
line += " +GRP";
|
||||
line += to_string_dec_uint(pkt.biw_v1);
|
||||
line += "@F";
|
||||
line += to_string_dec_uint(pkt.biw_v2);
|
||||
} else if (pkt.type == 1) {
|
||||
// Other INS types
|
||||
line += to_string_dec_uint(pkt.capcode);
|
||||
line += " INS ";
|
||||
line += pkt.message;
|
||||
} else if (pkt.addr_type == 2) {
|
||||
// Temp address delivery: "GRP5 ALN message"
|
||||
uint32_t slot = (uint32_t)(pkt.capcode + 0x8000 - 0x1F7800) & 0x0F;
|
||||
line += "GRP";
|
||||
line += to_string_dec_uint(slot);
|
||||
line += " ";
|
||||
line += type;
|
||||
if (pkt.message[0]) {
|
||||
line += " ";
|
||||
line += pkt.message;
|
||||
}
|
||||
} else {
|
||||
line += to_string_dec_uint(pkt.capcode);
|
||||
if (pkt.is_group) line += pkt.is_temp_group ? " TG" : " G";
|
||||
if (pkt.is_priority) line += " P";
|
||||
line += " ";
|
||||
line += type;
|
||||
if (pkt.message[0]) {
|
||||
line += " ";
|
||||
line += pkt.message;
|
||||
}
|
||||
}
|
||||
log_message(line);
|
||||
}
|
||||
|
||||
// Serial: pipe-delimited
|
||||
if (portapack::usb_serial.serial_connected()) {
|
||||
std::string s;
|
||||
s.reserve(320);
|
||||
s = "FLEX|";
|
||||
s += to_string_dec_uint(pkt.cycle);
|
||||
s += '/';
|
||||
s += to_string_dec_uint(pkt.frame);
|
||||
s += '|';
|
||||
s += to_string_dec_uint(pkt.bitrate);
|
||||
s += '|';
|
||||
s += pol;
|
||||
s += '|';
|
||||
s += pkt.phase;
|
||||
|
||||
if (pkt.type == 9) {
|
||||
// BIW: format from raw values for serial
|
||||
s += "|BIW";
|
||||
s += to_string_dec_uint(pkt.function);
|
||||
switch (pkt.biw_field) {
|
||||
case 0:
|
||||
s += "|SSID|lid=";
|
||||
s += to_string_dec_uint(pkt.biw_v1);
|
||||
s += "|cz=";
|
||||
s += to_string_dec_uint(pkt.biw_v2);
|
||||
break;
|
||||
case 1:
|
||||
s += "|DATE|";
|
||||
s += to_string_dec_uint(pkt.biw_v1);
|
||||
s += '-';
|
||||
s += to_string_dec_uint(pkt.biw_v2, 2, '0');
|
||||
s += '-';
|
||||
s += to_string_dec_uint(pkt.biw_v3, 2, '0');
|
||||
break;
|
||||
case 2: {
|
||||
uint32_t si = (pkt.biw_v3 * 75) / 10;
|
||||
s += "|TIME|";
|
||||
s += to_string_dec_uint(pkt.biw_v1, 2, '0');
|
||||
s += ':';
|
||||
s += to_string_dec_uint(pkt.biw_v2, 2, '0');
|
||||
s += ':';
|
||||
s += to_string_dec_uint(si, 2, '0');
|
||||
break;
|
||||
}
|
||||
case 5: {
|
||||
uint16_t a = pkt.biw_v1, info = pkt.biw_v2;
|
||||
if (a == 4 || a == 5) {
|
||||
static const int tz[] = {0, 60, 120, 180, 240, 300, 360, 420, 480, 540, 600, 660, 720,
|
||||
210, 270, 330, 0, 345, 390, 570, -210, -660, -600, -540, -480, -420, -360, -300, -240, -180, -120, -60};
|
||||
uint16_t zone = info & 0x1F;
|
||||
int ofs = (zone < 32) ? tz[zone] : 0;
|
||||
int dst = (info >> 5) & 1;
|
||||
s += "|TZ|UTC";
|
||||
s += (ofs >= 0 ? "+" : "");
|
||||
s += to_string_dec_int(ofs / 60);
|
||||
s += "h";
|
||||
int m = (ofs < 0 ? -ofs : ofs) % 60;
|
||||
if (m) {
|
||||
s += to_string_dec_uint(m, 2, '0');
|
||||
s += "m";
|
||||
}
|
||||
s += "|dst=";
|
||||
s += dst ? "no" : "yes";
|
||||
} else if (a <= 3) {
|
||||
static const char* t[] = {"all", "home", "roaming", "ssid"};
|
||||
s += "|SYSMSG|target=";
|
||||
s += t[a];
|
||||
} else if (a == 6) {
|
||||
s += "|CHAN|ofs=";
|
||||
s += to_string_dec_uint(info & 0x3F);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 7:
|
||||
s += "|SSID2|cc=";
|
||||
s += to_string_dec_uint(pkt.biw_v1);
|
||||
s += "|tmf=";
|
||||
s += to_string_dec_uint(pkt.biw_v2);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
s += '|';
|
||||
s += type;
|
||||
s += "|cap=";
|
||||
s += to_string_dec_uint(pkt.capcode);
|
||||
if (pkt.is_group) s += pkt.is_temp_group ? "|grp=temp" : "|grp=1";
|
||||
if (pkt.is_priority) s += "|pri=1";
|
||||
if (pkt.addr_type == 2) {
|
||||
// Temporary address: show slot number
|
||||
// capcode = aw - 0x8000, aw = capcode + 0x8000
|
||||
// slot = (aw - 0x1F7800) & 0x0F = (capcode + 0x8000 - 0x1F7800) & 0x0F
|
||||
uint32_t slot = (uint32_t)(pkt.capcode + 0x8000 - 0x1F7800) & 0x0F;
|
||||
s += "|slot=";
|
||||
s += to_string_dec_uint(slot);
|
||||
}
|
||||
if (pkt.has_flags) {
|
||||
if (pkt.type == 7) {
|
||||
s += "|seq=";
|
||||
s += to_string_dec_uint(pkt.seq);
|
||||
s += "|new=";
|
||||
s += to_string_dec_uint(pkt.is_new);
|
||||
s += "|fmt=";
|
||||
s += pkt.nnum_s ? "idrom" : "std";
|
||||
} else {
|
||||
s += "|frag=";
|
||||
s += (pkt.frag == 3) ? "first" : to_string_dec_uint(pkt.frag).c_str();
|
||||
s += "|mf=";
|
||||
s += to_string_dec_uint(pkt.more_frag);
|
||||
s += "|seq=";
|
||||
s += to_string_dec_uint(pkt.seq);
|
||||
if (pkt.frag == 3) {
|
||||
s += "|new=";
|
||||
s += to_string_dec_uint(pkt.is_new);
|
||||
s += "|md=";
|
||||
s += to_string_dec_uint(pkt.maildrop);
|
||||
s += "|sig=";
|
||||
s += to_string_hex(pkt.sig, 2);
|
||||
}
|
||||
if (pkt.type == 0) {
|
||||
static const char* enc[] = {"alpha", "sep", "bin", "rsvd"};
|
||||
s += "|enc=";
|
||||
s += enc[pkt.sec_enc & 3];
|
||||
}
|
||||
if (pkt.type == 6 && pkt.frag == 3) {
|
||||
uint8_t b = pkt.function & 0x0F;
|
||||
s += "|bb=";
|
||||
s += to_string_dec_uint(b == 0 ? 16 : b);
|
||||
if (pkt.function & 0x10) s += "|rtl=1";
|
||||
}
|
||||
}
|
||||
}
|
||||
if (pkt.message[0]) {
|
||||
s += "|\"";
|
||||
s += pkt.message;
|
||||
s += '"';
|
||||
}
|
||||
}
|
||||
UsbSerialAsyncmsg::asyncmsg(s);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle stats message (currently unused)
|
||||
@@ -118,12 +387,15 @@ void FlexAppView::on_stats(const FlexStatsMessage*) {
|
||||
|
||||
// Debug handler - uncomment to see baseband debug messages
|
||||
void FlexAppView::on_debug(const FlexDebugMessage* message) {
|
||||
(void)message; // Suppress unused parameter warning
|
||||
// std::string text = "DBG: ";
|
||||
// text += message->text;
|
||||
// text += " " + to_string_hex(message->val1, 8);
|
||||
// text += " " + to_string_hex(message->val2, 8);
|
||||
// log_message(text);
|
||||
if (portapack::usb_serial.serial_connected()) {
|
||||
std::string s = "DBG|";
|
||||
s += message->text;
|
||||
s += "|";
|
||||
s += to_string_dec_int(message->val1);
|
||||
s += "|";
|
||||
s += to_string_dec_int(message->val2);
|
||||
UsbSerialAsyncmsg::asyncmsg(s);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ui::external_app::flex_rx
|
||||
Reference in New Issue
Block a user