mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-08-22 07:29:03 +00:00
FLEX RX: fix long-address numeric decode, improve short message format (#3136)
- Fix long-address numeric message decoding: read body[0] from Vy (j+1) for multi-word messages, from w1 for single-word. Fix MF word count for long addresses (n_field words, not n_field+1). - Fix long-address short message: read 5 additional BCD digits from Vy for 8-digit decode. - Fix BCD table: index 10 is '.' (dot) not ' ' (space) in both RX and TX. - Clean up address decode: identify address type by value range per Table 3.8.1-1. Remove is_group/is_temp_group from packet structs. - Rename SMSG to SHORT. Format message payload directly in baseband: TONE, NUM abc, SRC N, SRC N N=M R=R, RESERVED hex. - Add BIW1 heartbeat packet (biw_field=0xFF) on every decoded frame. - Move timezone table to file scope, simplify console log_message, add No signal initial status, simplify serial BIW output format.
This commit is contained in:
+29
-111
@@ -10,6 +10,9 @@ using namespace portapack;
|
||||
|
||||
namespace ui::external_app::flex_rx {
|
||||
|
||||
static const int flex_tz_table[] = {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};
|
||||
|
||||
FlexAppView::FlexAppView(NavigationView& nav)
|
||||
: nav_{nav} {
|
||||
// Load baseband image for FLEX decoding
|
||||
@@ -42,6 +45,7 @@ FlexAppView::FlexAppView(NavigationView& nav)
|
||||
// Initialize FLEX baseband
|
||||
baseband::set_flex_config();
|
||||
|
||||
text_status1.set("No signal");
|
||||
console.writeln("Ready");
|
||||
}
|
||||
|
||||
@@ -54,42 +58,9 @@ void FlexAppView::focus() {
|
||||
field_frequency.focus();
|
||||
}
|
||||
|
||||
// Redraw all messages to console
|
||||
void FlexAppView::redraw_console() {
|
||||
console.clear(true);
|
||||
for (const auto& msg : messages) {
|
||||
console.writeln(msg);
|
||||
}
|
||||
}
|
||||
|
||||
// Add message to log with automatic line wrapping
|
||||
// Add message to console
|
||||
void FlexAppView::log_message(const std::string& message) {
|
||||
const size_t chars_per_line = screen_width / 8;
|
||||
// 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);
|
||||
|
||||
size_t total_lines = 0;
|
||||
for (size_t i = 0; i < messages.size(); i++) {
|
||||
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;
|
||||
}
|
||||
|
||||
// If console would overflow, remove oldest messages and redraw
|
||||
if (total_lines > console_lines) {
|
||||
while (total_lines > console_lines && !messages.empty()) {
|
||||
const auto& oldest = messages.front();
|
||||
size_t oldest_lines = (oldest.length() + chars_per_line - 1) / chars_per_line;
|
||||
if (oldest_lines == 0) oldest_lines = 1;
|
||||
total_lines -= oldest_lines;
|
||||
messages.erase(messages.begin());
|
||||
}
|
||||
redraw_console();
|
||||
} else {
|
||||
console.writeln(message);
|
||||
}
|
||||
console.writeln(message);
|
||||
}
|
||||
|
||||
// Update frequency and save for persistence
|
||||
@@ -118,7 +89,7 @@ static const char* flex_type_tag(uint32_t type) {
|
||||
case 7:
|
||||
return "NNUM";
|
||||
case 8:
|
||||
return "SMSG";
|
||||
return "SHORT";
|
||||
case 9:
|
||||
return "BIW";
|
||||
default:
|
||||
@@ -168,12 +139,14 @@ void FlexAppView::on_packet(const FlexPacketMessage* message) {
|
||||
}
|
||||
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;
|
||||
int ofs = (zone < 32) ? flex_tz_table[zone] : 0;
|
||||
int hrs = ofs / 60;
|
||||
int mins = (ofs < 0 ? -ofs : ofs) % 60;
|
||||
auto tzs = std::string("UTC") + (ofs >= 0 ? "+" : "") +
|
||||
to_string_dec_int(ofs / 60);
|
||||
to_string_dec_int(hrs);
|
||||
if (mins != 0)
|
||||
tzs += ":" + to_string_dec_int(mins, 2, '0');
|
||||
memcpy(status_tz_, tzs.c_str(), tzs.size() + 1);
|
||||
}
|
||||
break;
|
||||
@@ -197,8 +170,10 @@ void FlexAppView::on_packet(const FlexPacketMessage* message) {
|
||||
text_status2.set(s2);
|
||||
}
|
||||
|
||||
// Console: skip BIW events (shown in status bar), show messages only
|
||||
if (pkt.type != 9) {
|
||||
// Console: skip BIW, tone-only (V=010), SHORT reserved (t=3)
|
||||
bool skip_gui = (pkt.type == 9 || pkt.type == 2);
|
||||
if (pkt.type == 8 && pkt.function == 3) skip_gui = true;
|
||||
if (!skip_gui) {
|
||||
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) + " ";
|
||||
@@ -206,7 +181,7 @@ void FlexAppView::on_packet(const FlexPacketMessage* message) {
|
||||
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 += " +TG";
|
||||
line += to_string_dec_uint(pkt.biw_v1);
|
||||
line += "@F";
|
||||
line += to_string_dec_uint(pkt.biw_v2);
|
||||
@@ -216,9 +191,9 @@ void FlexAppView::on_packet(const FlexPacketMessage* message) {
|
||||
line += " INS ";
|
||||
line += pkt.message;
|
||||
} else if (pkt.addr_type == 2) {
|
||||
// Temp address delivery: "GRP5 ALN message"
|
||||
// Temp group delivery: "GRP5 ALN message"
|
||||
uint32_t slot = (uint32_t)(pkt.capcode + 0x8000 - 0x1F7800) & 0x0F;
|
||||
line += "GRP";
|
||||
line += "TG";
|
||||
line += to_string_dec_uint(slot);
|
||||
line += " ";
|
||||
line += type;
|
||||
@@ -228,7 +203,6 @@ void FlexAppView::on_packet(const FlexPacketMessage* 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;
|
||||
@@ -243,7 +217,6 @@ void FlexAppView::on_packet(const FlexPacketMessage* message) {
|
||||
// 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 += '/';
|
||||
@@ -256,76 +229,21 @@ void FlexAppView::on_packet(const FlexPacketMessage* message) {
|
||||
s += pkt.phase;
|
||||
|
||||
if (pkt.type == 9) {
|
||||
// BIW: format from raw values for serial
|
||||
s += "|BIW";
|
||||
s += "|BIW|w=";
|
||||
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;
|
||||
}
|
||||
s += "|t=";
|
||||
s += to_string_dec_uint(pkt.biw_field);
|
||||
s += '|';
|
||||
s += to_string_dec_uint(pkt.biw_v1);
|
||||
s += '|';
|
||||
s += to_string_dec_uint(pkt.biw_v2);
|
||||
s += '|';
|
||||
s += to_string_dec_uint(pkt.biw_v3);
|
||||
} 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
|
||||
|
||||
@@ -37,13 +37,8 @@ class FlexAppView : public View {
|
||||
uint16_t status_cz_{0};
|
||||
uint16_t status_cc_{0};
|
||||
|
||||
// Message storage for console redraw
|
||||
static constexpr size_t MAX_MESSAGES = 20;
|
||||
std::vector<std::string> messages{};
|
||||
|
||||
// Helper methods
|
||||
void log_message(const std::string& message);
|
||||
void redraw_console();
|
||||
void update_freq(rf::Frequency f);
|
||||
|
||||
// UI Elements - Row 0, dynamically positioned
|
||||
|
||||
+4
-4
@@ -211,7 +211,7 @@ static uint32_t flex_numeric_vector(uint32_t type, uint32_t mw_start, uint32_t m
|
||||
// Short addr: 3 digits in d0-d11. Long addr: 3 + 5 in 2nd word.
|
||||
// Tone-only: call with empty string (all digits become space 0xC).
|
||||
static uint32_t flex_short_vector(int is_long, const std::string& msg, uint32_t* vy_out) {
|
||||
static const char bcd_chars[20] = "0123456789 U -][";
|
||||
static const char bcd_chars[20] = "0123456789.U -][";
|
||||
auto to_bcd = [&](char c) -> uint8_t {
|
||||
for (int k = 0; k < 16; k++)
|
||||
if (bcd_chars[k] == c) return k;
|
||||
@@ -321,7 +321,7 @@ static int flex_encode_alpha(const std::string& msg, uint32_t* words, int max_wo
|
||||
// ===== Numeric BCD encoding =====
|
||||
|
||||
static int flex_encode_numeric(const std::string& msg, uint32_t* words, int max_words, uint32_t* k_out) {
|
||||
static const char bcd[20] = "0123456789 U -][";
|
||||
static const char bcd[20] = "0123456789.U -][";
|
||||
uint32_t mw[8] = {0};
|
||||
int bit = 2;
|
||||
int word_idx = 0;
|
||||
@@ -721,8 +721,8 @@ bool FlexTXView::start_tx() {
|
||||
|
||||
int msg_type = options_type.selected_index();
|
||||
if (msg_type == 1) {
|
||||
if (message.find_first_not_of("0123456789 U-][") != std::string::npos) {
|
||||
nav_.display_modal("Bad message", "Numeric: 0-9 U - ] [ space");
|
||||
if (message.find_first_not_of("0123456789.U -][") != std::string::npos) {
|
||||
nav_.display_modal("Bad message", "Numeric: 0-9 . U - ] [ space");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
+151
-84
@@ -8,6 +8,9 @@
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
|
||||
// BCD character table for FLEX numeric messages (index 0-15)
|
||||
static const char flex_bcd[] = "0123456789.U -][";
|
||||
|
||||
// Lightweight string helpers (no snprintf/heap on bare-metal M4)
|
||||
namespace {
|
||||
|
||||
@@ -566,7 +569,7 @@ void FlexProcessor::flex_sym(unsigned char sym) {
|
||||
* register. If found, we validate timing. If not found,
|
||||
* we fall back to the nominal 25ms skip (current behavior).
|
||||
*
|
||||
* Only the MSB (bit_a) matters for C detection — it's a
|
||||
* Only the MSB (bit_a) matters for C detection - it's a
|
||||
* 2-level pattern even in 4FSK modes. */
|
||||
unsigned char s2_sym = sync.polarity ? (3 - sym) : sym;
|
||||
int bit_a = (s2_sym > 1) ? 1 : 0;
|
||||
@@ -687,7 +690,7 @@ void FlexProcessor::decode_phase(char PhaseNo) {
|
||||
/* Check if phase is all idle BEFORE BCH correction.
|
||||
* Idle fill uses alternating 0xFFFFFFFF and 0x00000000 words.
|
||||
* If every word is one of these two patterns, the phase has no
|
||||
* real data — skip it to avoid BCH "correcting" idle into garbage. */
|
||||
* real data - skip it to avoid BCH "correcting" idle into garbage. */
|
||||
{
|
||||
int all_idle = 1;
|
||||
for (int i = 0; i < 88; i++) {
|
||||
@@ -722,6 +725,23 @@ void FlexProcessor::decode_phase(char PhaseNo) {
|
||||
|
||||
if (voffset < aoffset || voffset >= 88) return;
|
||||
|
||||
/* Always send BIW1 packet so the app knows we decoded a frame.
|
||||
* This updates the status bar even for idle frames. */
|
||||
{
|
||||
flex::FlexPacket bpkt{};
|
||||
bpkt.type = 9;
|
||||
bpkt.bitrate = sync.baud * (sync.levels == 4 ? 2 : 1);
|
||||
bpkt.cycle = fiw.cycleno;
|
||||
bpkt.frame = fiw.frameno;
|
||||
bpkt.phase = PhaseNo;
|
||||
bpkt.is_inverted = sync.polarity;
|
||||
bpkt.fiw_roaming = fiw.roaming;
|
||||
bpkt.function = 0;
|
||||
bpkt.biw_field = 0xFF;
|
||||
bpkt.message[0] = '\0';
|
||||
send_packet(bpkt);
|
||||
}
|
||||
|
||||
/* Parse BIW words (indices 1 through aoffset-1).
|
||||
* Each BIW word has a 3-bit type field (bits 4-6) that determines content.
|
||||
* Send each as a BIW event packet. */
|
||||
@@ -778,7 +798,7 @@ void FlexProcessor::decode_phase(char PhaseNo) {
|
||||
* Tone-only addresses sit at the end of the address field with no
|
||||
* corresponding vector. We find the last vector that passes checksum.
|
||||
* Note: for long addresses, the 2nd vector word (Vy) is a message word
|
||||
* that won't pass checksum — so we count all passing words, not just
|
||||
* that won't pass checksum - so we count all passing words, not just
|
||||
* consecutive ones from the start. */
|
||||
int n_valid_vecs = 0;
|
||||
for (int vi = 0; vi < (voffset - aoffset); vi++) {
|
||||
@@ -801,16 +821,13 @@ void FlexProcessor::decode_phase(char PhaseNo) {
|
||||
if (j >= 88) break;
|
||||
if (phaseptr[i] == 0x00000000 || phaseptr[i] == 0x001FFFFF) continue;
|
||||
|
||||
/* Extract group/temp flags from raw address word (bits 20, 19)
|
||||
* before parse_capcode classifies the word by range. */
|
||||
uint32_t raw_aw = phaseptr[i];
|
||||
int is_group = (raw_aw >> 20) & 1;
|
||||
int is_temp_group = is_group ? ((raw_aw >> 19) & 1) : 0;
|
||||
/* Address word - all 21 information bits are address data
|
||||
* per 3.8.2. Address type is determined by value range
|
||||
* (Table 3.8.1-1). Temporary addresses are range
|
||||
* 0x1F7800-0x1F780F (3.8.2.3), identified via addr_type. */
|
||||
int is_priority = (addr_count < prio_count) ? 1 : 0;
|
||||
|
||||
parse_capcode(phaseptr[i]);
|
||||
decode.is_group = is_group;
|
||||
decode.is_temp_group = is_temp_group;
|
||||
decode.is_priority = is_priority;
|
||||
addr_count++;
|
||||
|
||||
@@ -841,7 +858,7 @@ void FlexProcessor::decode_phase(char PhaseNo) {
|
||||
/* Set 2-3 */
|
||||
cap = (int64_t)(aw1 - 2064383) + (int64_t)(aw2 - 1867776) * 32768LL + 2068479LL;
|
||||
} else {
|
||||
/* Unknown set — skip */
|
||||
/* Unknown set - skip */
|
||||
i++;
|
||||
addr_count++; // second address word counts
|
||||
vec_count += 2;
|
||||
@@ -852,7 +869,7 @@ void FlexProcessor::decode_phase(char PhaseNo) {
|
||||
i++; // consumed 2 address words
|
||||
addr_count++; // second address word also counts
|
||||
|
||||
/* Long addresses always have vectors — they cannot be tone-only.
|
||||
/* Long addresses always have vectors - they cannot be tone-only.
|
||||
* (Tone-only is only for short addresses at the end of AF.)
|
||||
* The second vector word (Vy) contains the first message word,
|
||||
* not a checksummed vector, so skip the pre-scan check here. */
|
||||
@@ -927,9 +944,9 @@ void FlexProcessor::decode_phase(char PhaseNo) {
|
||||
parse_alphanumeric(phaseptr, word_bad, PhaseNo, mw1, mw2, 0);
|
||||
}
|
||||
} else if (decode.type == flex::PageType::STANDARD_NUMERIC || decode.type == flex::PageType::SPECIAL_NUMERIC || decode.type == flex::PageType::NUMBERED_NUMERIC) {
|
||||
parse_numeric(phaseptr, PhaseNo, j);
|
||||
parse_numeric(phaseptr, word_bad, PhaseNo, j);
|
||||
} else if (decode.type == flex::PageType::TONE) {
|
||||
/* Vector type 2: Short Message / Tone.
|
||||
/* Vector type 2: Short Message (3.9.2).
|
||||
* Sub-type t1t0 in bits 7-8, data d0-d11 in bits 9-20. */
|
||||
uint32_t t = (viw >> 7) & 0x03;
|
||||
uint32_t d = (viw >> 9) & 0x0FFF;
|
||||
@@ -937,65 +954,69 @@ void FlexProcessor::decode_phase(char PhaseNo) {
|
||||
flex::FlexPacket packet{};
|
||||
packet.bitrate = sync.baud * (sync.levels == 4 ? 2 : 1);
|
||||
packet.capcode = decode.capcode;
|
||||
packet.function = 0;
|
||||
packet.function = t;
|
||||
packet.cycle = fiw.cycleno;
|
||||
packet.frame = fiw.frameno;
|
||||
packet.phase = PhaseNo;
|
||||
packet.is_inverted = sync.polarity;
|
||||
packet.fiw_roaming = fiw.roaming;
|
||||
packet.addr_type = static_cast<uint8_t>(decode.addr_type);
|
||||
packet.is_group = decode.is_group;
|
||||
packet.is_temp_group = decode.is_temp_group;
|
||||
packet.is_priority = decode.is_priority;
|
||||
packet.type = 8; // SHORT
|
||||
|
||||
if (t == 0 && d == 0) {
|
||||
/* No data — pure tone via vector */
|
||||
packet.type = 8; // SMSG
|
||||
strcpy(packet.message, "sub=tone");
|
||||
} else if (t == 0) {
|
||||
/* Numeric: 3 BCD digits in d0-d11 */
|
||||
const char bcd[] = "0123456789 U -][";
|
||||
char digits[4];
|
||||
digits[0] = bcd[(d >> 0) & 0xF];
|
||||
digits[1] = bcd[(d >> 4) & 0xF];
|
||||
digits[2] = bcd[(d >> 8) & 0xF];
|
||||
digits[3] = '\0';
|
||||
packet.type = 8; // SMSG
|
||||
{
|
||||
char *p = packet.message, *e = p + sizeof(packet.message);
|
||||
p = str_append(p, e, "sub=numeric|digits=");
|
||||
str_append(p, e, digits);
|
||||
if (t == 0 && d == 0xCCC) {
|
||||
/* Tone-only: all digits are space (0xC) per STD-43A
|
||||
* Table 3.9.2-1 note. For long addresses, also check Vy. */
|
||||
bool tone = true;
|
||||
if (decode.long_address && j + 1 < 88) {
|
||||
uint32_t vy = phaseptr[j + 1] & 0xFFFFF;
|
||||
if (vy != 0xCCCCC) tone = false;
|
||||
}
|
||||
if (tone)
|
||||
strcpy(packet.message, "TONE");
|
||||
else
|
||||
goto short_numeric;
|
||||
} else if (t == 0) {
|
||||
short_numeric:
|
||||
/* Numeric: 3 BCD digits from Vx (d0-d11).
|
||||
* Long addresses: 5 more digits from Vy (d12-d31),
|
||||
* 8 digits total. d32 is spare (set to 0). */
|
||||
char *p = packet.message, *e = p + sizeof(packet.message);
|
||||
p = str_append(p, e, "NUM ");
|
||||
*p++ = flex_bcd[(d >> 0) & 0xF];
|
||||
*p++ = flex_bcd[(d >> 4) & 0xF];
|
||||
*p++ = flex_bcd[(d >> 8) & 0xF];
|
||||
if (decode.long_address && j + 1 < 88) {
|
||||
uint32_t vy = phaseptr[j + 1];
|
||||
*p++ = flex_bcd[(vy >> 0) & 0xF];
|
||||
*p++ = flex_bcd[(vy >> 4) & 0xF];
|
||||
*p++ = flex_bcd[(vy >> 8) & 0xF];
|
||||
*p++ = flex_bcd[(vy >> 12) & 0xF];
|
||||
*p++ = flex_bcd[(vy >> 16) & 0xF];
|
||||
}
|
||||
*p = '\0';
|
||||
} else if (t == 1) {
|
||||
/* Source: S2S1S0 in d0-d2 */
|
||||
packet.type = 8;
|
||||
{
|
||||
char *p = packet.message, *e = p + sizeof(packet.message);
|
||||
p = str_append(p, e, "sub=source|src=");
|
||||
str_uint(p, e, d & 0x07);
|
||||
}
|
||||
char *p = packet.message, *e = p + sizeof(packet.message);
|
||||
p = str_append(p, e, "SRC ");
|
||||
str_uint(p, e, d & 0x07);
|
||||
} else if (t == 2) {
|
||||
/* Numbered: S(3) + N(6) + R(1) */
|
||||
uint32_t src = d & 0x07;
|
||||
uint32_t n = (d >> 3) & 0x3F;
|
||||
uint32_t r = (d >> 9) & 0x01;
|
||||
packet.type = 8;
|
||||
{
|
||||
char *p = packet.message, *e = p + sizeof(packet.message);
|
||||
p = str_append(p, e, "sub=numbered|src=");
|
||||
p = str_uint(p, e, src);
|
||||
p = str_append(p, e, "|seq=");
|
||||
p = str_uint(p, e, n);
|
||||
p = str_append(p, e, "|new=");
|
||||
str_uint(p, e, r);
|
||||
}
|
||||
char *p = packet.message, *e = p + sizeof(packet.message);
|
||||
p = str_append(p, e, "SRC ");
|
||||
p = str_uint(p, e, src);
|
||||
p = str_append(p, e, " N=");
|
||||
p = str_uint(p, e, n);
|
||||
p = str_append(p, e, " R=");
|
||||
str_uint(p, e, r);
|
||||
} else {
|
||||
packet.type = 8;
|
||||
{
|
||||
char *p = packet.message, *e = p + sizeof(packet.message);
|
||||
p = str_append(p, e, "sub=reserved|raw=");
|
||||
str_hex(p, e, d, 3);
|
||||
}
|
||||
/* Reserved */
|
||||
char *p = packet.message, *e = p + sizeof(packet.message);
|
||||
p = str_append(p, e, "RESERVED ");
|
||||
str_hex(p, e, d, 3);
|
||||
}
|
||||
send_packet(packet);
|
||||
} else if (decode.type == flex::PageType::BINARY) {
|
||||
@@ -1058,8 +1079,6 @@ void FlexProcessor::decode_phase(char PhaseNo) {
|
||||
packet.is_inverted = sync.polarity;
|
||||
packet.fiw_roaming = fiw.roaming;
|
||||
packet.addr_type = static_cast<uint8_t>(decode.addr_type);
|
||||
packet.is_group = decode.is_group;
|
||||
packet.is_temp_group = decode.is_temp_group;
|
||||
packet.is_priority = decode.is_priority;
|
||||
if (hex_hdr_valid) {
|
||||
packet.frag = hex_f;
|
||||
@@ -1093,8 +1112,6 @@ void FlexProcessor::decode_phase(char PhaseNo) {
|
||||
packet.is_inverted = sync.polarity;
|
||||
packet.fiw_roaming = fiw.roaming;
|
||||
packet.addr_type = static_cast<uint8_t>(decode.addr_type);
|
||||
packet.is_group = decode.is_group;
|
||||
packet.is_temp_group = decode.is_temp_group;
|
||||
packet.is_priority = decode.is_priority;
|
||||
|
||||
if (itype == 0) {
|
||||
@@ -1253,8 +1270,6 @@ void FlexProcessor::parse_alphanumeric(uint32_t* phaseptr, const uint8_t* word_b
|
||||
packet.is_inverted = sync.polarity;
|
||||
packet.fiw_roaming = fiw.roaming;
|
||||
packet.addr_type = static_cast<uint8_t>(decode.addr_type);
|
||||
packet.is_group = decode.is_group;
|
||||
packet.is_temp_group = decode.is_temp_group;
|
||||
packet.is_priority = decode.is_priority;
|
||||
if (hdr_valid) {
|
||||
packet.frag = hdr_f;
|
||||
@@ -1276,9 +1291,8 @@ void FlexProcessor::parse_alphanumeric(uint32_t* phaseptr, const uint8_t* word_b
|
||||
send_packet(packet);
|
||||
}
|
||||
|
||||
void FlexProcessor::parse_numeric(uint32_t* phaseptr, char PhaseNo, int j) {
|
||||
void FlexProcessor::parse_numeric(uint32_t* phaseptr, const uint8_t* word_bad, char PhaseNo, int j) {
|
||||
char message[256] = {0};
|
||||
const char flex_bcd[] = "0123456789 U -][";
|
||||
|
||||
/* Extract NNUM header fields from first message word if applicable.
|
||||
* Layout: K5K4(2) + N0-N5(6) + R0(1) + S0(1) + BCD digits... */
|
||||
@@ -1288,29 +1302,34 @@ void FlexProcessor::parse_numeric(uint32_t* phaseptr, char PhaseNo, int j) {
|
||||
int w1 = phaseptr[j] >> 7;
|
||||
int w2 = w1 >> 7;
|
||||
w1 = w1 & 0x7f;
|
||||
w2 = (w2 & 0x07) + w1;
|
||||
int n_field = w2 & 0x07; // word_count - 1
|
||||
w2 = n_field + w1;
|
||||
|
||||
// Bounds check: phase buffer is 88 words (indices 0-87)
|
||||
// w1 and w2 are incremented below, so clamp to 86 max
|
||||
if (w1 > 86) return;
|
||||
if (w2 > 86) w2 = 86;
|
||||
if (w1 > 87) return;
|
||||
if (w2 > 87) w2 = 87;
|
||||
|
||||
int dw;
|
||||
dw = phaseptr[w1];
|
||||
/* For long addresses (3.9.1):
|
||||
* 1-word: b field points to Vy. body[0] at w1.
|
||||
* Multi-word: body[0] at Vy (j+1). b field points to MF body[1]. */
|
||||
int body0_idx;
|
||||
if (decode.long_address && n_field > 0)
|
||||
body0_idx = j + 1; // Vy = 2nd vector word
|
||||
else
|
||||
body0_idx = w1;
|
||||
if (body0_idx < 0 || body0_idx >= 88) return;
|
||||
|
||||
int dw = phaseptr[body0_idx];
|
||||
|
||||
if (is_nnum) {
|
||||
/* Extract N, R, S from the first message word's BCD stream.
|
||||
* After K5K4 (2 bits), next 6 bits = N, then R, then S.
|
||||
* These are consumed by the skip count (count starts at 4+10=14). */
|
||||
uint32_t first_word = phaseptr[w1];
|
||||
nnum_n = (first_word >> 2) & 0x3F; // bits 2-7
|
||||
nnum_r = (first_word >> 8) & 0x01; // bit 8
|
||||
nnum_s = (first_word >> 9) & 0x01; // bit 9
|
||||
nnum_n = (dw >> 2) & 0x3F; // bits 2-7
|
||||
nnum_r = (dw >> 8) & 0x01; // bit 8
|
||||
nnum_s = (dw >> 9) & 0x01; // bit 9
|
||||
}
|
||||
|
||||
w1++;
|
||||
w2++;
|
||||
|
||||
unsigned char digit = 0;
|
||||
int count = 4;
|
||||
if (is_nnum)
|
||||
@@ -1319,20 +1338,72 @@ void FlexProcessor::parse_numeric(uint32_t* phaseptr, char PhaseNo, int j) {
|
||||
count += 2; // skip K5K4(2)
|
||||
|
||||
int idx = 0;
|
||||
for (int i = w1; i <= w2; i++) {
|
||||
|
||||
/* Phase 1: decode body[0] bits.
|
||||
* For short addresses, body[0] is at w1 and we advance to w1+1.
|
||||
* For long addresses, body[0] is at Vy (j+1), then we continue from w1. */
|
||||
if (word_bad[body0_idx]) {
|
||||
/* Uncorrectable word — emit '?' for each digit slot */
|
||||
int data_bits = 21 - (count - 4); /* bits available after skip */
|
||||
int lost_digits = data_bits / 4;
|
||||
while (lost_digits-- > 0 && idx < 255)
|
||||
message[idx++] = '?';
|
||||
count = 4; /* reset for next word */
|
||||
digit = 0;
|
||||
} else {
|
||||
for (int k = 0; k < 21; k++) {
|
||||
digit = (digit >> 1) & 0x0F;
|
||||
if (dw & 0x01) digit ^= 0x08;
|
||||
dw >>= 1;
|
||||
if (--count == 0) {
|
||||
if (digit != 0x0C && idx < 255) {
|
||||
if (idx < 255) {
|
||||
message[idx++] = flex_bcd[digit];
|
||||
}
|
||||
count = 4;
|
||||
}
|
||||
}
|
||||
dw = phaseptr[i];
|
||||
}
|
||||
|
||||
/* Phase 2: decode remaining body words from MF.
|
||||
* Short: body[1..n] at w1+1 .. w2.
|
||||
* Long: MF has n_field words at w1 .. w1+n_field-1.
|
||||
* (n_field = total_words - 1; body[0] is at Vy, not in MF) */
|
||||
int start, end;
|
||||
if (decode.long_address) {
|
||||
start = w1;
|
||||
end = w1 + n_field - 1; // empty when n_field=0
|
||||
} else {
|
||||
start = w1 + 1;
|
||||
end = w2;
|
||||
}
|
||||
for (int i = start; i <= end && i < 88; i++) {
|
||||
if (word_bad[i]) {
|
||||
/* Uncorrectable word — emit '?' for each digit slot (5 per word) */
|
||||
int lost_digits = 21 / 4; /* 5 digits per 21-bit word */
|
||||
while (lost_digits-- > 0 && idx < 255)
|
||||
message[idx++] = '?';
|
||||
count = 4;
|
||||
digit = 0;
|
||||
continue;
|
||||
}
|
||||
dw = phaseptr[i];
|
||||
for (int k = 0; k < 21; k++) {
|
||||
digit = (digit >> 1) & 0x0F;
|
||||
if (dw & 0x01) digit ^= 0x08;
|
||||
dw >>= 1;
|
||||
if (--count == 0) {
|
||||
if (idx < 255) {
|
||||
message[idx++] = flex_bcd[digit];
|
||||
}
|
||||
count = 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Trim trailing BCD space padding (0x0C = ' ').
|
||||
* The encoder pads unused nibble slots with 0x0C */
|
||||
while (idx > 0 && message[idx - 1] == ' ')
|
||||
idx--;
|
||||
message[idx] = '\0';
|
||||
|
||||
flex::FlexPacket packet{};
|
||||
@@ -1353,8 +1424,6 @@ void FlexProcessor::parse_numeric(uint32_t* phaseptr, char PhaseNo, int j) {
|
||||
packet.is_inverted = sync.polarity;
|
||||
packet.fiw_roaming = fiw.roaming;
|
||||
packet.addr_type = static_cast<uint8_t>(decode.addr_type);
|
||||
packet.is_group = decode.is_group;
|
||||
packet.is_temp_group = decode.is_temp_group;
|
||||
packet.is_priority = decode.is_priority;
|
||||
if (is_nnum) {
|
||||
packet.seq = nnum_n;
|
||||
@@ -1381,8 +1450,6 @@ void FlexProcessor::parse_tone_only(uint32_t*, char PhaseNo, int) {
|
||||
packet.is_inverted = sync.polarity;
|
||||
packet.fiw_roaming = fiw.roaming;
|
||||
packet.addr_type = static_cast<uint8_t>(decode.addr_type);
|
||||
packet.is_group = decode.is_group;
|
||||
packet.is_temp_group = decode.is_temp_group;
|
||||
packet.is_priority = decode.is_priority;
|
||||
strcpy(packet.message, "");
|
||||
|
||||
|
||||
@@ -121,8 +121,6 @@ struct FlexDecode {
|
||||
int long_address = 0;
|
||||
int64_t capcode = 0;
|
||||
AddrType addr_type = AddrType::SHORT;
|
||||
int is_group = 0;
|
||||
int is_temp_group = 0;
|
||||
int is_priority = 0;
|
||||
};
|
||||
|
||||
@@ -184,7 +182,7 @@ class FlexProcessor : public BasebandProcessor {
|
||||
// Parsing
|
||||
void parse_capcode(uint32_t aw1);
|
||||
void parse_alphanumeric(uint32_t* phaseptr, const uint8_t* word_bad, char PhaseNo, int mw1, int mw2, int flex_groupmessage);
|
||||
void parse_numeric(uint32_t* phaseptr, char PhaseNo, int j);
|
||||
void parse_numeric(uint32_t* phaseptr, const uint8_t* word_bad, char PhaseNo, int j);
|
||||
void parse_tone_only(uint32_t* phaseptr, char PhaseNo, int j);
|
||||
void parse_unknown(uint32_t* phaseptr, char PhaseNo, int mw1, int mw2);
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ struct FlexPacket {
|
||||
uint32_t bitrate; // 1600, 3200, 6400
|
||||
uint64_t capcode; // supports long addresses (up to 4,297,068,542)
|
||||
uint32_t function; // 0-3 (or BIW word index for type=9)
|
||||
uint32_t type; // 0=SEC 1=INS 2=TON 3=NUM 4=SNUM 5=ALN 6=HEX 7=NNUM 8=SMSG 9=BIW
|
||||
uint32_t type; // 0=SEC 1=INS 2=TON 3=NUM 4=SNUM 5=ALN 6=HEX 7=NNUM 8=SHORT 9=BIW
|
||||
char message[256]; // Decoded message text (not used for BIW)
|
||||
uint32_t status; // 0=OK, other=Errors
|
||||
uint8_t cycle; // FIW cycle (0-14)
|
||||
@@ -34,19 +34,17 @@ struct FlexPacket {
|
||||
uint8_t addr_type; // 0=short 1=long 2=temp 3=oper 4=net 5=info 6=rsvd 7=unk
|
||||
|
||||
// Fragment flags (ALN/SEC/HEX)
|
||||
uint8_t frag; // F field: 3=first, 0/1/2=continuation
|
||||
uint8_t more_frag; // C bit
|
||||
uint8_t seq; // N field (0-63)
|
||||
uint8_t is_new; // R bit
|
||||
uint8_t maildrop; // M bit
|
||||
uint8_t sig; // 7-bit signature
|
||||
uint8_t has_flags; // 1=fragment flags valid
|
||||
uint8_t sec_enc; // secure encoding (0-3)
|
||||
uint8_t nnum_s; // NNUM S flag
|
||||
uint8_t fiw_roaming; // FIW n bit: 1=roaming supported
|
||||
uint8_t is_group; // 1=group address
|
||||
uint8_t is_temp_group; // 1=temporary group
|
||||
uint8_t is_priority; // 1=priority address (in BIW1 P section)
|
||||
uint8_t frag; // F field: 3=first, 0/1/2=continuation
|
||||
uint8_t more_frag; // C bit
|
||||
uint8_t seq; // N field (0-63)
|
||||
uint8_t is_new; // R bit
|
||||
uint8_t maildrop; // M bit
|
||||
uint8_t sig; // 7-bit signature
|
||||
uint8_t has_flags; // 1=fragment flags valid
|
||||
uint8_t sec_enc; // secure encoding (0-3)
|
||||
uint8_t nnum_s; // NNUM S flag
|
||||
uint8_t fiw_roaming; // FIW n bit: 1=roaming supported
|
||||
uint8_t is_priority; // 1=priority address (in BIW1 P section)
|
||||
|
||||
// BIW raw values (type=9 only). biw_field identifies the content.
|
||||
// 0=SSID1 1=DATE 2=TIME 5=SYSINFO 7=SSID2
|
||||
|
||||
Reference in New Issue
Block a user