diff --git a/firmware/application/apps/pocsag_app.cpp b/firmware/application/apps/pocsag_app.cpp index b0f35bea8..06f6ac822 100644 --- a/firmware/application/apps/pocsag_app.cpp +++ b/firmware/application/apps/pocsag_app.cpp @@ -282,8 +282,9 @@ void POCSAGAppView::handle_decoded(Timestamp timestamp, const std::string& prefi /* Serial: header + first chunk. * hex field contains rendered alpha as hex bytes (color escapes - * stripped). Non-printable and uncorrectable chars show as '.' - * (0x2E) — original 7-bit values are not preserved. + * stripped). Non-printable chars show as '.' (0x2E), + * uncorrectable chars show as '?' (0x3F) — original 7-bit + * values are not preserved. * Numeric decode goes in the quoted message field only. */ if (portapack::usb_serial.serial_connected()) { /* Build hex representation of decoded alpha characters. */ diff --git a/firmware/common/pocsag.cpp b/firmware/common/pocsag.cpp index a2ea2dbdc..5a0c7ac31 100644 --- a/firmware/common/pocsag.cpp +++ b/firmware/common/pocsag.cpp @@ -537,6 +537,9 @@ bool pocsag_decode_batch(const POCSAGPacket& batch, POCSAGState& state) { uint8_t msg_codewords = 0; // Also build raw alpha for heuristic (before non-printable replacement). std::string raw_alpha{}; + // Track whether any characters came from uncorrectable codewords. + // If so, heuristic scoring is unreliable — skip it and default to alpha. + bool has_bad_chars = false; while (state.codeword_index < codeword_max) { auto codeword = batch[state.codeword_index]; @@ -578,7 +581,9 @@ bool pocsag_decode_batch(const POCSAGPacket& batch, POCSAGState& state) { if (is_address) { // Got another address. Run heuristic before returning if we have pending data. if (!state.type_decided && msg_codewords > 0) { - state.detected = detect_message_type(raw_alpha, nibbles, nibble_count, msg_codewords); + state.detected = has_bad_chars + ? DET_ALPHA + : detect_message_type(raw_alpha, nibbles, nibble_count, msg_codewords); state.type_decided = true; state.msg_codewords = msg_codewords; if (state.detected == DET_NUMERIC) { @@ -600,7 +605,9 @@ bool pocsag_decode_batch(const POCSAGPacket& batch, POCSAGState& state) { if (is_address) { // Message ended. Run heuristic before returning. if (!state.type_decided && msg_codewords > 0) { - state.detected = detect_message_type(raw_alpha, nibbles, nibble_count, msg_codewords); + state.detected = has_bad_chars + ? DET_ALPHA + : detect_message_type(raw_alpha, nibbles, nibble_count, msg_codewords); state.type_decided = true; state.msg_codewords = msg_codewords; if (state.detected == DET_NUMERIC) { @@ -632,9 +639,8 @@ bool pocsag_decode_batch(const POCSAGPacket& batch, POCSAGState& state) { state.ascii_idx += 20; while (state.ascii_idx >= 7) { - // Determine error level for this character. - // If some bits came from previous codeword and some from current, - // use the worst of both error levels. + // Per-character error level from codeword error tracking. + // Characters spanning a codeword boundary get the worst level. uint8_t char_err; if (bits_from_prev >= 7) { // Entire character from previous codeword's leftover bits. @@ -663,8 +669,11 @@ bool pocsag_decode_batch(const POCSAGPacket& batch, POCSAGState& state) { if (!state.type_decided) raw_alpha += ascii_char; - // Translate non-printable chars. - if (ascii_char < 32 || ascii_char > 126) + // Substitute '?' for characters from uncorrectable codewords + if (char_err >= 3) { + state.output += "?"; + has_bad_chars = true; + } else if (ascii_char < 32 || ascii_char > 126) state.output += "."; else state.output += ascii_char; @@ -694,7 +703,9 @@ bool pocsag_decode_batch(const POCSAGPacket& batch, POCSAGState& state) { // End of batch. If we have message data and type not yet decided, run heuristic. if (state.out_type == MESSAGE && !state.type_decided && msg_codewords > 0) { - state.detected = detect_message_type(raw_alpha, nibbles, nibble_count, msg_codewords); + state.detected = has_bad_chars + ? DET_ALPHA + : detect_message_type(raw_alpha, nibbles, nibble_count, msg_codewords); state.type_decided = true; state.msg_codewords = msg_codewords; if (state.detected == DET_NUMERIC) {