POCSAG: decoder fixes, serial output, TX polarity cleanup (#3130)

RX: fix address/batch-boundary handling, add numeric continuation,
add heuristic type detection toggle, pass inverted polarity flag,
stream decoded messages over USB serial.

TX: rename phase to polarity (Standard/Inverted), fix function
validation (&&→||), fix serial shell payload read (offset tracking,
31-byte cap, preserve msglen).

Decoder: remove color escapes from output (plain text for all
consumers), guard count_alpha_fill against empty string, keep
numeric_len across continuation batches.

Serial format: POCSAG <baud> <addr> <func> <pol> <type> "<msg>" hex:<hex>
Quote escaping for embedded " and \\ in alpha messages.
This commit is contained in:
VasylSamoilov
2026-04-10 09:35:43 +03:00
committed by GitHub
parent fc18992442
commit dd006b4830
11 changed files with 549 additions and 92 deletions
+20 -10
View File
@@ -56,7 +56,8 @@ void POCSAGTXView::on_remete(const PocsagTosendMessage data) {
if (data.function == 'C') tmp = 2;
if (data.function == 'D') tmp = 3;
options_function.set_selected_index(tmp);
options_phase.set_selected_index(data.phase == 'P' ? 0 : 1);
/* 'S' = Standard (CCIR Rec. 584 polarity), 'I' = Inverted */
options_polarity.set_selected_index(data.polarity == 'S' ? 0 : 1);
field_address.set_value(data.addr);
message = (char*)data.msg;
buffer = message;
@@ -64,7 +65,7 @@ void POCSAGTXView::on_remete(const PocsagTosendMessage data) {
options_bitrate.dirty();
options_type.dirty();
options_function.dirty();
options_phase.dirty();
options_polarity.dirty();
field_address.dirty();
text_message.dirty();
tx_view.focus();
@@ -100,7 +101,17 @@ bool POCSAGTXView::start_tx() {
return false;
}
}
MessageType phase = (MessageType)options_phase.selected_index_value();
/*
* TX polarity inversion (CCIR Rec. 584):
*
* The FSK modulator (proc_fsk.cpp) maps bit 1 to positive deviation.
* Standard POCSAG requires bit 1 to negative deviation.
*
* "Standard" (value 0): invert codewords so that original bit 1 becomes
* bit 0 in the modulator, producing negative deviation. This is the standard.
* "Inverted" (value 1): send codewords as-is. Bit 1 produces positive deviation.
*/
bool invert_polarity = (options_polarity.selected_index_value() == 0);
pocsag_encode(type, BCH_code, options_function.selected_index_value(), message, address, codewords);
@@ -118,10 +129,7 @@ bool POCSAGTXView::start_tx() {
bi = 0;
for (i = 0; i < codewords.size(); i++) {
if (phase == 0)
codeword = ~(codewords[i]);
else
codeword = codewords[i];
codeword = invert_polarity ? ~(codewords[i]) : codewords[i];
data_ptr[bi++] = (codeword >> 24) & 0xFF;
data_ptr[bi++] = (codeword >> 16) & 0xFF;
@@ -166,15 +174,17 @@ POCSAGTXView::POCSAGTXView(
&field_address,
&options_type,
&options_function,
&options_phase,
&options_polarity,
&text_message,
&text_message_l2,
&button_message,
&progressbar,
&tx_view});
options_bitrate.set_selected_index(1); // 1200bps
options_type.set_selected_index(0); // Address only
options_bitrate.set_selected_index(1); // 1200 bps
options_type.set_selected_index(2); // Alphanumeric
options_function.set_selected_index(0); // Function A
options_polarity.set_selected_index(0); // Standard (CCIR Rec. 584)
field_address.set_value(persistent_memory::pocsag_last_address());
+13 -5
View File
@@ -84,7 +84,7 @@ class POCSAGTXView : public View {
{{3 * 8, 6 * 8}, "Address:", Theme::getInstance()->fg_light->foreground},
{{6 * 8, 8 * 8}, "Type:", Theme::getInstance()->fg_light->foreground},
{{2 * 8, 10 * 8}, "Function:", Theme::getInstance()->fg_light->foreground},
{{5 * 8, 12 * 8}, "Phase:", Theme::getInstance()->fg_light->foreground},
{{2 * 8, 12 * 8}, "Polarity:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 14 * 8}, "Message:", Theme::getInstance()->fg_light->foreground}};
OptionsField options_bitrate{
@@ -113,12 +113,20 @@ class POCSAGTXView : public View {
{"C", 2},
{"D", 3}}};
OptionsField options_phase{
/*
* TX polarity (CCIR Rec. 584):
* "Standard" (value 0): bit 1 = negative deviation (CCIR Rec. 584 standard).
* Codewords are bitwise-inverted before the FSK modulator
* because the modulator maps bit 1 to positive deviation.
* "Inverted" (value 1): bit 1 = positive deviation (non-standard).
* Codewords are sent as-is to the modulator.
*/
OptionsField options_polarity{
{11 * 8, 12 * 8},
1,
8,
{
{"P", 0},
{"N", 1},
{"Standard", 0},
{"Inverted", 1},
}};
Text text_message{