diff --git a/firmware/application/apps/pocsag_app.cpp b/firmware/application/apps/pocsag_app.cpp index 1967384f4..45b63b17d 100644 --- a/firmware/application/apps/pocsag_app.cpp +++ b/firmware/application/apps/pocsag_app.cpp @@ -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, - &audio, - &field_rf_amp, - &field_lna, - &field_vga, - &field_frequency, - &check_log, - &field_volume, - &check_ignore, - &sym_ignore, - &console}); + add_children( + {&rssi, + &audio, + &field_rf_amp, + &field_lna, + &field_vga, + &field_frequency, + &field_volume, + &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(); - 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(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) + - " F" + to_string_dec_uint(pocsag_state.function) + - " Address only"); + 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) + - " F" + to_string_dec_uint(pocsag_state.function) + - " Alpha: " + 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); + } } } +} - // 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 */ diff --git a/firmware/application/apps/pocsag_app.hpp b/firmware/application/apps/pocsag_app.hpp index 697689339..e8b74ea97 100644 --- a/firmware/application/apps/pocsag_app.hpp +++ b/firmware/application/apps/pocsag_app.hpp @@ -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 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 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(p); this->on_packet(message); }}; + + MessageHandlerRegistration message_handler_stats{ + Message::ID::POCSAGStats, + [this](Message* const p) { + const auto stats = static_cast(p); + this->on_stats(stats); + }}; }; } /* namespace ui */ diff --git a/firmware/baseband/proc_pocsag.cpp b/firmware/baseband/proc_pocsag.cpp index 6b5b492b4..39e8d12f3 100644 --- a/firmware/baseband/proc_pocsag.cpp +++ b/firmware/baseband/proc_pocsag.cpp @@ -28,7 +28,7 @@ #include #include -#include // std::max +#include #include void POCSAGProcessor::execute(const buffer_c8_t& buffer) { @@ -41,7 +41,7 @@ void POCSAGProcessor::execute(const buffer_c8_t& buffer) { const auto decim_1_out = decim_1.execute(decim_0_out, dst_buffer); const auto channel_out = channel_filter.execute(decim_1_out, dst_buffer); auto audio = demod.execute(channel_out, audio_buffer); - smooth.Process(audio.p, audio.count); // Smooth the data to make decoding more accurate + smooth.Process(audio.p, audio.count); // Smooth the data to make decoding more accurate audio_output.write(audio); processDemodulatedSamples(audio.p, 16); @@ -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 diff --git a/firmware/baseband/proc_pocsag.hpp b/firmware/baseband/proc_pocsag.hpp index 1f5ec6796..9858cd276 100644 --- a/firmware/baseband/proc_pocsag.hpp +++ b/firmware/baseband/proc_pocsag.hpp @@ -48,7 +48,7 @@ using namespace std; template 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 diff --git a/firmware/common/message.hpp b/firmware/common/message.hpp index a5ac3ef05..d80cb4f25 100644 --- a/firmware/common/message.hpp +++ b/firmware/common/message.hpp @@ -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(