Pocsag UX Revamp (#1408)

* Set bandwidth_filter in pogsac

* WIP pocsag UI revamp

* Settings UI, better console, UI Revamp

* Add baud rate to error messages

* Reset last_addr in error cases

* Show malformed messages as different color vs hiding

* Use light grey to indicate low quality decode

---------

Co-authored-by: kallanreed <kallanreed@noreply.github.com>
This commit is contained in:
Kyle Reed 2023-08-25 14:45:02 -07:00 committed by GitHub
parent f4496d8f45
commit 9af1308e29
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 247 additions and 115 deletions

View File

@ -22,15 +22,15 @@
#include "pocsag_app.hpp"
#include "audio.hpp"
#include "baseband_api.hpp"
#include "portapack_persistent_memory.hpp"
#include "string_format.hpp"
#include "utility.hpp"
using namespace portapack;
using namespace pocsag;
#include "string_format.hpp"
#include "utility.hpp"
#include "audio.hpp"
namespace pmem = portapack::persistent_memory;
void POCSAGLogger::log_raw_data(const pocsag::POCSAGPacket& packet, const uint32_t frequency) {
std::string entry = "Raw: F:" + to_string_dec_uint(frequency) + "Hz " +
@ -51,44 +51,73 @@ void POCSAGLogger::log_decoded(
namespace ui {
POCSAGSettingsView::POCSAGSettingsView(
NavigationView& nav,
POCSAGSettings& settings)
: settings_{settings} {
add_children(
{&check_log,
&check_log_raw,
&check_small_font,
&check_ignore,
&field_ignore,
&button_save});
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_ignore.set_value(settings_.enable_ignore);
field_ignore.set_value(settings_.address_to_ignore);
button_save.on_select = [this, &nav](Button&) {
settings_.enable_logging = check_log.value();
settings_.enable_raw_log = check_log_raw.value();
settings_.enable_small_font = check_small_font.value();
settings_.enable_ignore = check_ignore.value();
settings_.address_to_ignore = field_ignore.value();
nav.pop();
};
}
POCSAGAppView::POCSAGAppView(NavigationView& nav)
: nav_{nav} {
baseband::run_image(portapack::spi_flash::image_tag_pocsag);
add_children({&rssi,
&channel,
add_children(
{&rssi,
&audio,
&field_rf_amp,
&field_lna,
&field_vga,
&field_frequency,
&check_log,
&field_volume,
&check_ignore,
&sym_ignore,
&image_status,
&button_ignore_last,
&button_config,
&console});
if (!settings_.loaded())
// No app settings, use fallbacks.
if (!app_settings_.loaded()) {
field_frequency.set_value(initial_target_frequency);
check_log.set_value(enable_logging);
receiver_model.enable();
// TODO: app setting instead?
uint32_t ignore_address;
ignore_address = persistent_memory::pocsag_ignore_address();
// TODO: is this common enough for a helper?
for (size_t c = 0; c < 7; c++) {
sym_ignore.set_sym(6 - c, ignore_address % 10);
ignore_address /= 10;
settings_.address_to_ignore = pmem::pocsag_ignore_address();
settings_.enable_ignore = settings_.address_to_ignore > 0;
}
logger = std::make_unique<POCSAGLogger>();
if (logger)
logger->append(LOG_ROOT_DIR "/POCSAG.TXT");
logger.append(LOG_ROOT_DIR "/POCSAG.TXT");
button_ignore_last.on_select = [this](Button&) {
settings_.enable_ignore = true;
settings_.address_to_ignore = last_address;
};
button_config.on_select = [this](Button&) {
nav_.push<POCSAGSettingsView>(settings_);
nav_.set_on_pop([this]() { refresh_ui(); });
};
refresh_ui();
receiver_model.enable();
audio::output::start();
baseband::set_pocsag();
}
@ -99,82 +128,101 @@ void POCSAGAppView::focus() {
POCSAGAppView::~POCSAGAppView() {
audio::output::stop();
// Save settings.
persistent_memory::set_pocsag_ignore_address(sym_ignore.value_dec_u32());
enable_logging = check_log.value();
receiver_model.disable();
baseband::shutdown();
// Save pmem settings. TODO: Even needed anymore?
pmem::set_pocsag_ignore_address(settings_.address_to_ignore);
pmem::set_pocsag_last_address(pocsag_state.address); // For POCSAG TX.
}
void POCSAGAppView::refresh_ui() {
console.set_style(
settings_.enable_small_font
? &Styles::white_small
: &Styles::white);
}
void POCSAGAppView::on_packet(const POCSAGPacketMessage* message) {
std::string alphanum_text = "";
packet_toggle = !packet_toggle;
image_status.set_foreground(packet_toggle
? Color::dark_grey()
: Color::white());
if (message->packet.flag() != NORMAL)
console.writeln("\n\x1B\x0CRC ERROR: " + pocsag::flag_str(message->packet.flag()));
else {
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;
if (logging_raw())
logger.log_raw_data(message->packet, receiver_model.target_frequency());
if (message->packet.flag() != NORMAL) {
console.writeln("\n\x1B\x04" + prefix + " CRC ERROR: " + pocsag::flag_str(message->packet.flag()));
last_address = 0;
return;
} else {
pocsag_decode_batch(message->packet, &pocsag_state);
if ((ignore()) && (pocsag_state.address == sym_ignore.value_dec_u32())) {
// Ignore (inform, but no log)
// console.write("\n\x1B\x03" + to_string_time(message->packet.timestamp()) +
// " Ignored address " + to_string_dec_uint(pocsag_state.address));
/*
// Too many errors for reliable decode.
if (pocsag_state.errors >= 3) {
console.write("\n\x1B\x0D" + prefix + " Too many decode errors.");
last_address = 0;
return;
}
// Too many errors for reliable decode
if ((ignore()) && (pocsag_state.errors >= 3)) {
*/
// Ignored address.
if (ignore() && pocsag_state.address == settings_.address_to_ignore) {
console.write("\n\x1B\x03" + prefix + " Ignored: " + to_string_dec_uint(pocsag_state.address));
last_address = pocsag_state.address;
return;
}
std::string console_info;
const uint32_t roundVal = 50;
const uint32_t bitrate = roundVal * ((message->packet.bitrate() + (roundVal / 2)) / roundVal);
console_info = "\n" + to_string_datetime(message->packet.timestamp(), HM);
console_info += " " + to_string_dec_uint(bitrate);
console_info += " ADDR:" + to_string_dec_uint(pocsag_state.address);
// Color indicates the message has lots of decoding errors.
std::string color = pocsag_state.errors >= 3 ? "\x1B\x07" : "";
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);
// Store last received address for POCSAG TX
persistent_memory::set_pocsag_last_address(pocsag_state.address);
if (pocsag_state.out_type == ADDRESS) {
// Address only
last_address = pocsag_state.address;
console.write(console_info);
if (logger && logging()) {
logger->log_decoded(
message->packet, to_string_dec_uint(pocsag_state.address) +
if (logging()) {
logger.log_decoded(
message->packet,
to_string_dec_uint(pocsag_state.address) +
" F" + to_string_dec_uint(pocsag_state.function) +
" Address only");
}
last_address = pocsag_state.address;
} else if (pocsag_state.out_type == MESSAGE) {
if (pocsag_state.address != last_address) {
// New message
console.writeln(console_info);
console.write(pocsag_state.output);
last_address = pocsag_state.address;
console.writeln(console_info);
console.write(color + pocsag_state.output);
} else {
// Message continues...
console.write(pocsag_state.output);
console.write(color + pocsag_state.output);
}
if (logger && logging())
logger->log_decoded(
message->packet, to_string_dec_uint(pocsag_state.address) +
if (logging()) {
logger.log_decoded(
message->packet,
to_string_dec_uint(pocsag_state.address) +
" F" + to_string_dec_uint(pocsag_state.function) +
" Alpha: " + pocsag_state.output);
" > " + pocsag_state.output);
}
}
}
}
// TODO: make setting.
// Log raw data whatever it contains
/*if (logger && logging())
logger->log_raw_data(message->packet, receiver_model.target_frequency());*/
void POCSAGAppView::on_stats(const POCSAGStatsMessage*) {
}
} /* namespace ui */

View File

@ -27,12 +27,15 @@
#include "ui_freq_field.hpp"
#include "ui_receiver.hpp"
#include "ui_rssi.hpp"
#include "ui_styles.hpp"
#include "log_file.hpp"
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "log_file.hpp"
#include "pocsag.hpp"
#include "pocsag_packet.hpp"
#include "radio_state.hpp"
#include <functional>
class POCSAGLogger {
public:
@ -49,6 +52,59 @@ class POCSAGLogger {
namespace ui {
struct POCSAGSettings {
bool enable_small_font = false;
bool enable_logging = false;
bool enable_raw_log = false;
bool enable_ignore = false;
uint32_t address_to_ignore = 0;
};
class POCSAGSettingsView : public View {
public:
POCSAGSettingsView(NavigationView& nav, POCSAGSettings& settings);
std::string title() const override { return "POCSAG Config"; };
private:
POCSAGSettings& settings_;
Checkbox check_log{
{2 * 8, 2 * 16},
10,
"Enable Log",
false};
Checkbox check_log_raw{
{2 * 8, 4 * 16},
12,
"Log Raw Data",
false};
Checkbox check_small_font{
{2 * 8, 6 * 16},
4,
"Use Small Font",
false};
Checkbox check_ignore{
{2 * 8, 8 * 16},
22,
"Enable Ignored Address",
false};
NumberField field_ignore{
{7 * 8, 9 * 16 + 8},
7,
{0, 9999999},
1,
'0'};
Button button_save{
{12 * 8, 16 * 16, 10 * 8, 2 * 16},
"Save"};
};
class POCSAGAppView : public View {
public:
POCSAGAppView(NavigationView& nav);
@ -58,21 +114,36 @@ class POCSAGAppView : public View {
void focus() override;
private:
static constexpr uint32_t initial_target_frequency = 466175000;
bool logging() const { return check_log.value(); };
bool ignore() const { return check_ignore.value(); };
static constexpr uint32_t initial_target_frequency = 466'175'000;
bool logging() const { return settings_.enable_logging; };
bool logging_raw() const { return settings_.enable_raw_log; };
bool ignore() const { return settings_.enable_ignore; };
NavigationView& nav_;
RxRadioState radio_state_{};
RxRadioState radio_state_{
12'500, // POCSAG is FSK +/- 4.5MHz, 12k5 is a good filter.
3'072'000, // Match baseband_fs in proc_pocsag.
};
// Settings
bool enable_logging = false;
app_settings::SettingsManager settings_{
POCSAGSettings settings_{};
app_settings::SettingsManager app_settings_{
"rx_pocsag"sv,
app_settings::Mode::RX,
{{"enable_logging"sv, &enable_logging}}};
{
{"small_font"sv, &settings_.enable_small_font},
{"enable_logging"sv, &settings_.enable_logging},
{"enable_ignore"sv, &settings_.enable_ignore},
}};
void refresh_ui();
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;
RFAmpField field_rf_amp{
{13 * 8, 0 * 16}};
@ -81,41 +152,32 @@ class POCSAGAppView : public View {
VGAGainField field_vga{
{18 * 8, 0 * 16}};
RSSI rssi{
{21 * 8, 0, 6 * 8, 4}};
Channel channel{
{21 * 8, 5, 6 * 8, 4}};
{21 * 8, 3, 6 * 8, 4}};
Audio audio{
{21 * 8, 10, 6 * 8, 4}};
{21 * 8, 8, 6 * 8, 4}};
RxFrequencyField field_frequency{
{0 * 8, 0 * 8},
nav_};
AudioVolumeField field_volume{
{28 * 8, 0 * 16}};
Checkbox check_ignore{
{0 * 8, 21},
8,
"Ign addr",
false};
SymField sym_ignore{
{13 * 8, 25},
7,
SymField::SYMFIELD_DEC};
Image image_status{
{7 * 8, 1 * 16 + 2, 16, 16},
&bitmap_icon_pocsag,
Color::white(),
Color::black()};
Checkbox check_log{
{240 - 8 * 8, 21},
3,
"Log",
false};
Button button_ignore_last{
{10 * 8, 1 * 16, 12 * 8, 20},
"Ignore Last"};
Button button_config{
{22 * 8, 1 * 16, 8 * 8, 20},
"Config"};
Console console{
{0, 3 * 16, 240, 256}};
std::unique_ptr<POCSAGLogger> logger{};
void on_packet(const POCSAGPacketMessage* message);
{0, 2 * 16 + 6, screen_width, screen_height - 56}};
MessageHandlerRegistration message_handler_packet{
Message::ID::POCSAGPacket,
@ -123,6 +185,13 @@ class POCSAGAppView : public View {
const auto message = static_cast<const POCSAGPacketMessage*>(p);
this->on_packet(message);
}};
MessageHandlerRegistration message_handler_stats{
Message::ID::POCSAGStats,
[this](Message* const p) {
const auto stats = static_cast<const POCSAGStatsMessage*>(p);
this->on_stats(stats);
}};
};
} /* namespace ui */

View File

@ -28,7 +28,7 @@
#include <cstdint>
#include <cstddef>
#include <algorithm> // std::max
#include <algorithm>
#include <cmath>
void POCSAGProcessor::execute(const buffer_c8_t& buffer) {
@ -90,10 +90,13 @@ void POCSAGProcessor::configure() {
decim_0.configure(taps_11k0_decim_0.taps, 33554432);
decim_1.configure(taps_11k0_decim_1.taps, 131072);
channel_filter.configure(taps_11k0_channel.taps, 2);
demod.configure(demod_input_fs, 4500);
demod.configure(demod_input_fs, 4'500); // FSK +/- 4k5Hz.
// Smoothing should be roughly sample rate over max baud
// 24k / 3.2k is 7.5
smooth.SetSize(8);
// TODO: support squelch?
// audio_output.configure(message.audio_hpf_config, message.audio_deemph_config, (float)message.squelch_level / 100.0);
audio_output.configure(false);
// Set up the frame extraction, limits of baud

View File

@ -48,7 +48,7 @@ using namespace std;
template <class ValType, class CalcType>
class SmoothVals {
protected:
ValType* m_lastVals; // Previoius N values
ValType* m_lastVals; // Previous N values
int m_size; // The size N
CalcType m_sumVal; // Running sum of lastVals
int m_pos; // Current position in last vals ring buffer

View File

@ -111,6 +111,7 @@ class Message {
APRSRxConfigure = 54,
SpectrumPainterBufferRequestConfigure = 55,
SpectrumPainterBufferResponseConfigure = 56,
POCSAGStats = 57,
MAX
};
@ -340,6 +341,17 @@ class POCSAGPacketMessage : public Message {
pocsag::POCSAGPacket packet;
};
class POCSAGStatsMessage : public Message {
public:
constexpr POCSAGStatsMessage(
uint16_t baud_rate)
: Message{ID::POCSAGStats},
baud_rate_{baud_rate} {
}
uint16_t baud_rate_;
};
class ACARSPacketMessage : public Message {
public:
constexpr ACARSPacketMessage(