POCSAG State machine fix (#1410)

* Reset color for well-formed message fragments

* better colors

* Fix POGSAG decode state machine

* Invert is_message to make more clear

* Use new escape string constants

* Run ECC twice, better diagnostics

* center status icon

---------

Co-authored-by: kallanreed <kallanreed@noreply.github.com>
This commit is contained in:
Kyle Reed 2023-08-26 11:43:34 -07:00 committed by GitHub
parent cf25d85d51
commit 933920edfd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 200 additions and 138 deletions

View File

@ -43,10 +43,8 @@ void POCSAGLogger::log_raw_data(const pocsag::POCSAGPacket& packet, const uint32
log_file.write_entry(packet.timestamp(), entry);
}
void POCSAGLogger::log_decoded(
const pocsag::POCSAGPacket& packet,
const std::string text) {
log_file.write_entry(packet.timestamp(), text);
void POCSAGLogger::log_decoded(Timestamp timestamp, const std::string& text) {
log_file.write_entry(timestamp, text);
}
namespace ui {
@ -59,6 +57,7 @@ POCSAGSettingsView::POCSAGSettingsView(
{&check_log,
&check_log_raw,
&check_small_font,
&check_show_bad,
&check_ignore,
&field_ignore,
&button_save});
@ -66,6 +65,7 @@ POCSAGSettingsView::POCSAGSettingsView(
check_log.set_value(settings_.enable_logging);
check_log_raw.set_value(settings_.enable_raw_log);
check_small_font.set_value(settings_.enable_small_font);
check_show_bad.set_value(settings_.hide_bad_data);
check_ignore.set_value(settings_.enable_ignore);
field_ignore.set_value(settings_.address_to_ignore);
@ -73,6 +73,7 @@ POCSAGSettingsView::POCSAGSettingsView(
settings_.enable_logging = check_log.value();
settings_.enable_raw_log = check_log_raw.value();
settings_.enable_small_font = check_small_font.value();
settings_.hide_bad_data = check_show_bad.value();
settings_.enable_ignore = check_ignore.value();
settings_.address_to_ignore = field_ignore.value();
@ -93,6 +94,7 @@ POCSAGAppView::POCSAGAppView(NavigationView& nav)
&field_frequency,
&field_volume,
&image_status,
&text_packet_count,
&button_ignore_last,
&button_config,
&console});
@ -143,83 +145,113 @@ void POCSAGAppView::refresh_ui() {
: &Styles::white);
}
void POCSAGAppView::on_packet(const POCSAGPacketMessage* message) {
packet_toggle = !packet_toggle;
image_status.set_foreground(packet_toggle
? Color::dark_grey()
: Color::white());
void POCSAGAppView::handle_decoded(Timestamp timestamp, const std::string& prefix) {
bool bad_data = pocsag_state.errors >= 3;
// Too many errors for reliable decode.
if (bad_data && hide_bad_data()) {
console.write("\n" STR_COLOR_MAGENTA + prefix + " Too many decode errors.");
last_address = 0;
return;
}
// Ignored address.
if (ignore() && pocsag_state.address == settings_.address_to_ignore) {
console.write("\n" STR_COLOR_CYAN + prefix + " Ignored: " + to_string_dec_uint(pocsag_state.address));
last_address = pocsag_state.address;
return;
}
// Color indicates the message has a lot of decoding errors.
std::string color = bad_data ? STR_COLOR_MAGENTA : STR_COLOR_WHITE;
std::string console_info = "\n" + color + prefix;
console_info += " #" + to_string_dec_uint(pocsag_state.address);
console_info += " F" + to_string_dec_uint(pocsag_state.function);
if (pocsag_state.out_type == ADDRESS) {
last_address = pocsag_state.address;
console.write(console_info);
if (logging()) {
logger.log_decoded(
timestamp,
to_string_dec_uint(pocsag_state.address) +
" F" + to_string_dec_uint(pocsag_state.function) +
" Address only");
}
} else if (pocsag_state.out_type == MESSAGE) {
if (pocsag_state.address != last_address) {
// New message
last_address = pocsag_state.address;
console.writeln(console_info);
console.write(color + pocsag_state.output);
} else {
// Message continues...
console.write(color + pocsag_state.output);
}
if (logging()) {
logger.log_decoded(
timestamp,
to_string_dec_uint(pocsag_state.address) +
" F" + to_string_dec_uint(pocsag_state.function) +
" > " + pocsag_state.output);
}
}
}
static Color get_status_color(const POCSAGState& state) {
if (state.out_type == IDLE)
return Color::white();
switch (state.mode) {
case STATE_CLEAR:
return Color::cyan();
case STATE_HAVE_ADDRESS:
return Color::yellow();
case STATE_GETTING_MSG:
return Color::green();
}
// Shouldn't get here...
return Color::red();
}
void POCSAGAppView::on_packet(const POCSAGPacketMessage* message) {
const uint32_t roundVal = 50;
const uint32_t bitrate_rounded = roundVal * ((message->packet.bitrate() + (roundVal / 2)) / roundVal);
auto bitrate = to_string_dec_uint(bitrate_rounded);
auto timestamp = to_string_datetime(message->packet.timestamp(), HM);
auto prefix = timestamp + " " + bitrate;
// Display packet count to be able to tell whether baseband sent a packet for a tone.
++packet_count;
text_packet_count.set(to_string_dec_uint(packet_count));
if (logging_raw())
logger.log_raw_data(message->packet, receiver_model.target_frequency());
if (message->packet.flag() != NORMAL) {
console.writeln("\n" STR_COLOR_DARK_RED + prefix + " CRC ERROR: " + pocsag::flag_str(message->packet.flag()));
console.writeln("\n" STR_COLOR_RED + prefix + " CRC ERROR: " + pocsag::flag_str(message->packet.flag()));
last_address = 0;
return;
} else {
pocsag_decode_batch(message->packet, &pocsag_state);
// Set color before to be able to see if decode gets stuck.
image_status.set_foreground(Color::magenta());
pocsag_state.codeword_index = 0;
pocsag_state.errors = 0;
/*
// Too many errors for reliable decode.
if (pocsag_state.errors >= 3) {
console.write("\n" STR_COLOR_MAGENTA + prefix + " Too many decode errors.");
last_address = 0;
return;
}
*/
// Handle multiple messages (if any).
while (pocsag_decode_batch(message->packet, pocsag_state))
handle_decoded(message->packet.timestamp(), prefix);
// Ignored address.
if (ignore() && pocsag_state.address == settings_.address_to_ignore) {
console.write("\n" STR_COLOR_DARK_CYAN + prefix + " Ignored: " + to_string_dec_uint(pocsag_state.address));
last_address = pocsag_state.address;
return;
}
// Color indicates the message has lots of decoding errors.
std::string color = pocsag_state.errors >= 3 ? STR_COLOR_MAGENTA : "";
std::string console_info = "\n" + color + prefix;
console_info += " #" + to_string_dec_uint(pocsag_state.address);
console_info += " F" + to_string_dec_uint(pocsag_state.function);
if (pocsag_state.out_type == ADDRESS) {
last_address = pocsag_state.address;
console.write(console_info);
if (logging()) {
logger.log_decoded(
message->packet,
to_string_dec_uint(pocsag_state.address) +
" F" + to_string_dec_uint(pocsag_state.function) +
" Address only");
}
} else if (pocsag_state.out_type == MESSAGE) {
if (pocsag_state.address != last_address) {
// New message
last_address = pocsag_state.address;
console.writeln(console_info);
console.write(color + pocsag_state.output);
} else {
// Message continues...
console.write(color + pocsag_state.output);
}
if (logging()) {
logger.log_decoded(
message->packet,
to_string_dec_uint(pocsag_state.address) +
" F" + to_string_dec_uint(pocsag_state.function) +
" > " + pocsag_state.output);
}
}
// Handle the remainder.
handle_decoded(message->packet.timestamp(), prefix);
}
// Set status icon color to indicate state machine state.
image_status.set_foreground(get_status_color(pocsag_state));
}
void POCSAGAppView::on_stats(const POCSAGStatsMessage*) {

View File

@ -44,7 +44,7 @@ class POCSAGLogger {
}
void log_raw_data(const pocsag::POCSAGPacket& packet, const uint32_t frequency);
void log_decoded(const pocsag::POCSAGPacket& packet, const std::string text);
void log_decoded(Timestamp timestamp, const std::string& text);
private:
LogFile log_file{};
@ -57,6 +57,7 @@ struct POCSAGSettings {
bool enable_logging = false;
bool enable_raw_log = false;
bool enable_ignore = false;
bool hide_bad_data = false;
uint32_t address_to_ignore = 0;
};
@ -87,14 +88,20 @@ class POCSAGSettingsView : public View {
"Use Small Font",
false};
Checkbox check_ignore{
Checkbox check_show_bad{
{2 * 8, 8 * 16},
22,
"Hide Bad Data",
false};
Checkbox check_ignore{
{2 * 8, 10 * 16},
22,
"Enable Ignored Address",
false};
NumberField field_ignore{
{7 * 8, 9 * 16 + 8},
{7 * 8, 11 * 16 + 8},
7,
{0, 9999999},
1,
@ -118,6 +125,7 @@ class POCSAGAppView : public View {
bool logging() const { return settings_.enable_logging; };
bool logging_raw() const { return settings_.enable_raw_log; };
bool ignore() const { return settings_.enable_ignore; };
bool hide_bad_data() const { return settings_.hide_bad_data; };
NavigationView& nav_;
RxRadioState radio_state_{
@ -134,16 +142,19 @@ class POCSAGAppView : public View {
{"small_font"sv, &settings_.enable_small_font},
{"enable_logging"sv, &settings_.enable_logging},
{"enable_ignore"sv, &settings_.enable_ignore},
{"address_to_ignore"sv, &settings_.address_to_ignore},
{"hide_bad_data"sv, &settings_.hide_bad_data},
}};
void refresh_ui();
void handle_decoded(Timestamp timestamp, const std::string& prefix);
void on_packet(const POCSAGPacketMessage* message);
void on_stats(const POCSAGStatsMessage* stats);
uint32_t last_address = 0xFFFFFFFF;
pocsag::POCSAGState pocsag_state{};
POCSAGLogger logger{};
bool packet_toggle = false;
uint16_t packet_count = 0;
RFAmpField field_rf_amp{
{13 * 8, 0 * 16}};
@ -163,11 +174,15 @@ class POCSAGAppView : public View {
{28 * 8, 0 * 16}};
Image image_status{
{7 * 8, 1 * 16 + 2, 16, 16},
{0 * 8 + 4, 1 * 16 + 2, 16, 16},
&bitmap_icon_pocsag,
Color::white(),
Color::black()};
Text text_packet_count{
{3 * 8, 1 * 16 + 2, 5 * 8, 16},
"0"};
Button button_ignore_last{
{10 * 8, 1 * 16, 12 * 8, 20},
"Ignore Last"};

View File

@ -226,14 +226,17 @@ void pocsag_encode(const MessageType type, BCHCode& BCH_code, const uint32_t fun
}
// -------------------------------------------------------------------------------
// Get the number of bits that differ between the two values.
// -------------------------------------------------------------------------------
inline int bitsDiff(unsigned long left, unsigned long right) {
inline uint8_t bitsDiff(unsigned long left, unsigned long right) {
unsigned long xord = left ^ right;
int count = 0;
uint8_t count = 0;
for (int i = 0; i < 32; i++) {
if ((xord & 0x01) != 0) ++count;
if ((xord & 0x01) == 1) ++count;
xord = xord >> 1;
}
return (count);
}
@ -310,7 +313,7 @@ void setupecc() {
// -------------------------------------------------------------------------------
// -------------------------------------------------------------------------------
inline int errorCorrection(uint32_t* val) {
inline int errorCorrection(uint32_t& val) {
// Set up the tables the first time
if (eccSetup == 0) {
setupecc();
@ -327,7 +330,7 @@ inline int errorCorrection(uint32_t* val) {
// for (i=0; i<=20; i++)
ecc = 0;
for (i = 31; i >= 11; --i) {
if ((*val & (1 << i))) {
if (val & (1 << i)) {
ecc = ecc ^ ecs[31 - i];
pari = pari ^ 0x01;
}
@ -337,7 +340,7 @@ inline int errorCorrection(uint32_t* val) {
acc = 0;
for (i = 10; i >= 1; --i) {
acc = acc << 1;
if ((*val & (1 << i))) {
if (val & (1 << i)) {
acc = acc ^ 0x01;
}
}
@ -355,12 +358,12 @@ inline int errorCorrection(uint32_t* val) {
b2 = b2 & 0x1f;
if (b2 != 0x1f) {
*val ^= 0x01 << (31 - b2);
val ^= 0x01 << (31 - b2);
ecc = ecc ^ ecs[b2];
}
if (b1 != 0x1f) {
*val ^= 0x01 << (31 - b1);
val ^= 0x01 << (31 - b1);
ecc = ecc ^ ecs[b1];
}
@ -377,78 +380,86 @@ inline int errorCorrection(uint32_t* val) {
return errl;
}
void pocsag_decode_batch(const POCSAGPacket& batch, POCSAGState* const state) {
int errors = 0;
uint32_t codeword;
char ascii_char;
std::string output_text = "";
bool pocsag_decode_batch(const POCSAGPacket& batch, POCSAGState& state) {
constexpr uint8_t codeword_max = 16;
state.output.clear();
state->out_type = EMPTY;
while (state.codeword_index < codeword_max) {
auto codeword = batch[state.codeword_index];
bool is_address = (codeword & 0x80000000U) == 0;
// For each codeword...
for (size_t i = 0; i < 16; i++) {
codeword = batch[i];
// Error correct twice. First time to fix any errors it can,
// second time to count number of errors that couldn't be fixed.
errorCorrection(codeword);
auto error_count = errorCorrection(codeword);
errorCorrection(&codeword);
errors = errorCorrection(&codeword);
switch (state.mode) {
case STATE_CLEAR:
if (is_address && codeword != POCSAG_IDLEWORD) {
state.function = (codeword >> 11) & 3;
state.address = (codeword >> 10) & 0x1FFFF8U; // 18 MSBs are transmitted
state.mode = STATE_HAVE_ADDRESS;
state.out_type = ADDRESS;
state.errors = error_count;
if (!(codeword & 0x80000000U)) {
// Address codeword
if (state->mode == STATE_CLEAR) {
// if (codeword != POCSAG_IDLEWORD) {
if (!(bitsDiff(codeword, POCSAG_IDLEWORD) < 1)) {
state->function = (codeword >> 11) & 3;
state->address = (codeword >> 10) & 0x1FFFF8U; // 18 MSBs are transmitted
state->mode = STATE_HAVE_ADDRESS;
state->out_type = ADDRESS;
state->errors = errors;
state->ascii_idx = 0;
state->ascii_data = 0;
state.ascii_idx = 0;
state.ascii_data = 0;
} else if (codeword == POCSAG_IDLEWORD) {
state.out_type = IDLE;
}
} else {
state->mode = STATE_CLEAR; // New address = new message
}
} else {
state->errors += errors;
// Message codeword
if (state->mode == STATE_HAVE_ADDRESS) {
// First message codeword: complete address
state->address |= (i >> 1); // Add in the 3 LSBs (frame #)
state->mode = STATE_GETTING_MSG;
}
break;
state->out_type = MESSAGE;
case STATE_HAVE_ADDRESS:
if (is_address) {
// Got another address, return the current state.
state.mode = STATE_CLEAR;
return true;
}
state->ascii_data |= ((codeword >> 11) & 0xFFFFF); // Get 20 message bits
state->ascii_idx += 20;
// First message codeword, complete the address.
state.address |= (state.codeword_index >> 1); // Add in the 3 LSBs (frame #).
state.mode = STATE_GETTING_MSG;
[[fallthrough]];
// Raw 20 bits to 7 bit reversed ASCII
while (state->ascii_idx >= 7) {
state->ascii_idx -= 7;
ascii_char = ((state->ascii_data) >> (state->ascii_idx)) & 0x7F;
case STATE_GETTING_MSG:
if (is_address) {
// Codeword isn't a message, return the current state.
state.mode = STATE_CLEAR;
return true;
}
// Bottom's up
ascii_char = (ascii_char & 0xF0) >> 4 | (ascii_char & 0x0F) << 4; // 01234567 -> 45670123
ascii_char = (ascii_char & 0xCC) >> 2 | (ascii_char & 0x33) << 2; // 45670123 -> 67452301
ascii_char = (ascii_char & 0xAA) >> 2 | (ascii_char & 0x55); // 67452301 -> *7654321
state.out_type = MESSAGE;
state.errors += error_count;
state.ascii_data |= (codeword >> 11) & 0xFFFFF; // Get 20 message bits.
state.ascii_idx += 20;
// Translate non-printable chars
if ((ascii_char < 32) || (ascii_char > 126)) {
// output_text += "[" + to_string_dec_uint(ascii_char) + "]";
output_text += ".";
} else
output_text += ascii_char;
}
// Raw 20 bits to 7 bit reversed ASCII.
// NB: This is processed MSB first, any remaining bits are shifted
// up so a whole 7 bits are processed with the next codeword.
while (state.ascii_idx >= 7) {
state.ascii_idx -= 7;
char ascii_char = (state.ascii_data >> state.ascii_idx) & 0x7F;
state->ascii_data <<= 20; // Remaining bits are for next time...
// Bottom's up (reverse the bits).
ascii_char = (ascii_char & 0xF0) >> 4 | (ascii_char & 0x0F) << 4; // 01234567 -> 45670123
ascii_char = (ascii_char & 0xCC) >> 2 | (ascii_char & 0x33) << 2; // 45670123 -> 67452301
ascii_char = (ascii_char & 0xAA) >> 2 | (ascii_char & 0x55); // 67452301 -> 76543210
// Translate non-printable chars. TODO: Leave CRLF?
if (ascii_char < 32 || ascii_char > 126)
state.output += ".";
else
state.output += ascii_char;
}
state.ascii_data <<= 20; // Remaining bits are for next iteration...
break;
}
state.codeword_index++;
}
state->output = output_text;
if (state->mode == STATE_HAVE_ADDRESS)
state->mode = STATE_CLEAR;
return false;
}
} /* namespace pocsag */

View File

@ -35,7 +35,7 @@
namespace pocsag {
// Todo: these enums suck, make a better decode_batch
// TODO: these enums suck, make a better decode_batch
enum Mode : uint32_t {
STATE_CLEAR,
@ -45,6 +45,7 @@ enum Mode : uint32_t {
enum OutputType : uint32_t {
EMPTY,
IDLE,
ADDRESS,
MESSAGE
};
@ -56,6 +57,7 @@ enum MessageType : uint32_t {
};
struct POCSAGState {
uint8_t codeword_index;
uint32_t function;
uint32_t address;
Mode mode = STATE_CLEAR;
@ -78,7 +80,9 @@ std::string flag_str(PacketFlag packetflag);
void insert_BCH(BCHCode& BCH_code, uint32_t* codeword);
uint32_t get_digit_code(char code);
void pocsag_encode(const MessageType type, BCHCode& BCH_code, const uint32_t function, const std::string message, const uint32_t address, std::vector<uint32_t>& codewords);
void pocsag_decode_batch(const POCSAGPacket& batch, POCSAGState* const state);
// Returns true if the batch has more to process.
bool pocsag_decode_batch(const POCSAGPacket& batch, POCSAGState& state);
} /* namespace pocsag */