mirror of
https://github.com/eried/portapack-mayhem.git
synced 2024-10-01 01:26:06 -04:00
Decode status widget (#1431)
* Initial cleanup of pocsag beta, using DSP filters * Better filter params * Better filter * Add signal diagnostics widgets * POCSAG procs sends stats messages * Only draw 32 bits * Add AudioNormalizer filter
This commit is contained in:
parent
2435ee780f
commit
4819a2f4e2
@ -105,6 +105,8 @@ POCSAGAppView::POCSAGAppView(NavigationView& nav)
|
|||||||
&field_volume,
|
&field_volume,
|
||||||
&image_status,
|
&image_status,
|
||||||
&text_packet_count,
|
&text_packet_count,
|
||||||
|
&widget_bits,
|
||||||
|
&widget_frames,
|
||||||
&button_ignore_last,
|
&button_ignore_last,
|
||||||
&button_config,
|
&button_config,
|
||||||
&console});
|
&console});
|
||||||
@ -271,7 +273,31 @@ void POCSAGAppView::on_packet(const POCSAGPacketMessage* message) {
|
|||||||
image_status.set_foreground(get_status_color(pocsag_state));
|
image_status.set_foreground(get_status_color(pocsag_state));
|
||||||
}
|
}
|
||||||
|
|
||||||
void POCSAGAppView::on_stats(const POCSAGStatsMessage*) {
|
void POCSAGAppView::on_stats(const POCSAGStatsMessage* stats) {
|
||||||
|
widget_bits.set_bits(stats->current_bits);
|
||||||
|
widget_frames.set_frames(stats->current_frames);
|
||||||
|
widget_frames.set_sync(stats->has_sync);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BitsIndicator::paint(Painter&) {
|
||||||
|
auto p = screen_pos();
|
||||||
|
for (size_t i = 0; i < sizeof(bits_) * 8; ++i) {
|
||||||
|
auto is_set = ((bits_ >> i) & 0x1) == 1;
|
||||||
|
|
||||||
|
int x = p.x() + (i / height);
|
||||||
|
int y = p.y() + (i % height);
|
||||||
|
display.draw_pixel({x, y}, is_set ? Color::white() : Color::black());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FrameIndicator::paint(Painter& painter) {
|
||||||
|
auto p = screen_pos();
|
||||||
|
painter.draw_rectangle({p, {2, height}}, has_sync_ ? Color::green() : Color::grey());
|
||||||
|
|
||||||
|
for (size_t i = 0; i < height; ++i) {
|
||||||
|
auto p2 = p + Point{2, 16 - (int)i};
|
||||||
|
painter.draw_hline(p2, 2, i < frame_count_ ? Color::white() : Color::black());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} /* namespace ui */
|
} /* namespace ui */
|
||||||
|
@ -52,6 +52,50 @@ class POCSAGLogger {
|
|||||||
|
|
||||||
namespace ui {
|
namespace ui {
|
||||||
|
|
||||||
|
class BitsIndicator : public Widget {
|
||||||
|
public:
|
||||||
|
BitsIndicator(Point position)
|
||||||
|
: Widget{{position, {2, height}}} {}
|
||||||
|
|
||||||
|
void paint(Painter& painter) override;
|
||||||
|
void set_bits(uint32_t bits) {
|
||||||
|
if (bits != bits_) {
|
||||||
|
bits_ = bits;
|
||||||
|
set_dirty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static constexpr uint8_t height = 16;
|
||||||
|
uint32_t bits_ = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class FrameIndicator : public Widget {
|
||||||
|
public:
|
||||||
|
FrameIndicator(Point position)
|
||||||
|
: Widget{{position, {4, height}}} {}
|
||||||
|
|
||||||
|
void paint(Painter& painter) override;
|
||||||
|
void set_frames(uint8_t frame_count) {
|
||||||
|
if (frame_count != frame_count_) {
|
||||||
|
frame_count_ = frame_count;
|
||||||
|
set_dirty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_sync(bool has_sync) {
|
||||||
|
if (has_sync != has_sync_) {
|
||||||
|
has_sync_ = has_sync;
|
||||||
|
set_dirty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static constexpr uint8_t height = 16;
|
||||||
|
uint8_t frame_count_ = 0;
|
||||||
|
bool has_sync_ = false;
|
||||||
|
};
|
||||||
|
|
||||||
struct POCSAGSettings {
|
struct POCSAGSettings {
|
||||||
bool enable_small_font = false;
|
bool enable_small_font = false;
|
||||||
bool enable_logging = false;
|
bool enable_logging = false;
|
||||||
@ -187,7 +231,8 @@ class POCSAGAppView : public View {
|
|||||||
2,
|
2,
|
||||||
{0, 99},
|
{0, 99},
|
||||||
1,
|
1,
|
||||||
' '};
|
' ',
|
||||||
|
true /*wrap*/};
|
||||||
AudioVolumeField field_volume{
|
AudioVolumeField field_volume{
|
||||||
{28 * 8, 0 * 16}};
|
{28 * 8, 0 * 16}};
|
||||||
|
|
||||||
@ -201,6 +246,12 @@ class POCSAGAppView : public View {
|
|||||||
{3 * 8, 1 * 16 + 2, 5 * 8, 16},
|
{3 * 8, 1 * 16 + 2, 5 * 8, 16},
|
||||||
"0"};
|
"0"};
|
||||||
|
|
||||||
|
BitsIndicator widget_bits{
|
||||||
|
{9 * 7 + 6, 1 * 16 + 2}};
|
||||||
|
|
||||||
|
FrameIndicator widget_frames{
|
||||||
|
{9 * 8, 1 * 16 + 2}};
|
||||||
|
|
||||||
Button button_ignore_last{
|
Button button_ignore_last{
|
||||||
{10 * 8, 1 * 16, 12 * 8, 20},
|
{10 * 8, 1 * 16, 12 * 8, 20},
|
||||||
"Ignore Last"};
|
"Ignore Last"};
|
||||||
|
@ -449,7 +449,7 @@ DeclareTargets(PPOC pocsag)
|
|||||||
set(MODE_CPPSRC
|
set(MODE_CPPSRC
|
||||||
proc_pocsag2.cpp
|
proc_pocsag2.cpp
|
||||||
)
|
)
|
||||||
DeclareTargets(PPOC pocsag2)
|
DeclareTargets(PPO2 pocsag2)
|
||||||
|
|
||||||
### RDS
|
### RDS
|
||||||
|
|
||||||
|
@ -56,6 +56,12 @@ void POCSAGProcessor::execute(const buffer_c8_t& buffer) {
|
|||||||
processDemodulatedSamples(audio.p, 16);
|
processDemodulatedSamples(audio.p, 16);
|
||||||
extractFrames();
|
extractFrames();
|
||||||
|
|
||||||
|
samples_processed += buffer.count;
|
||||||
|
if (samples_processed >= stat_update_threshold) {
|
||||||
|
send_stats();
|
||||||
|
samples_processed = 0;
|
||||||
|
}
|
||||||
|
|
||||||
// Clear the output before sending to audio chip.
|
// Clear the output before sending to audio chip.
|
||||||
// Only clear the audio buffer when there hasn't been any audio for a while.
|
// Only clear the audio buffer when there hasn't been any audio for a while.
|
||||||
if (squelch_.enabled() && squelch_history == 0) {
|
if (squelch_.enabled() && squelch_history == 0) {
|
||||||
@ -137,6 +143,11 @@ void POCSAGProcessor::configure() {
|
|||||||
configured = true;
|
configured = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void POCSAGProcessor::send_stats() const {
|
||||||
|
POCSAGStatsMessage message(m_fifo.codeword, m_numCode, m_gotSync);
|
||||||
|
shared_memory.application_queue.push(message);
|
||||||
|
}
|
||||||
|
|
||||||
// -----------------------------
|
// -----------------------------
|
||||||
// Frame extractraction methods
|
// Frame extractraction methods
|
||||||
// -----------------------------
|
// -----------------------------
|
||||||
|
@ -137,6 +137,9 @@ class POCSAGProcessor : public BasebandProcessor {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
static constexpr size_t baseband_fs = 3072000;
|
static constexpr size_t baseband_fs = 3072000;
|
||||||
|
static constexpr uint8_t stat_update_interval = 10;
|
||||||
|
static constexpr uint32_t stat_update_threshold =
|
||||||
|
baseband_fs / stat_update_interval;
|
||||||
|
|
||||||
std::array<complex16_t, 512> dst{};
|
std::array<complex16_t, 512> dst{};
|
||||||
const buffer_c16_t dst_buffer{
|
const buffer_c16_t dst_buffer{
|
||||||
@ -158,7 +161,10 @@ class POCSAGProcessor : public BasebandProcessor {
|
|||||||
bool configured = false;
|
bool configured = false;
|
||||||
pocsag::POCSAGPacket packet{};
|
pocsag::POCSAGPacket packet{};
|
||||||
|
|
||||||
|
uint32_t samples_processed = 0;
|
||||||
|
|
||||||
void configure();
|
void configure();
|
||||||
|
void send_stats() const;
|
||||||
|
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
// Frame extractraction methods and members
|
// Frame extractraction methods and members
|
||||||
|
@ -25,7 +25,6 @@
|
|||||||
|
|
||||||
#include "proc_pocsag2.hpp"
|
#include "proc_pocsag2.hpp"
|
||||||
|
|
||||||
#include "dsp_iir_config.hpp"
|
|
||||||
#include "event_m4.hpp"
|
#include "event_m4.hpp"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
@ -33,50 +32,106 @@
|
|||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
void POCSAGProcessor::execute(const buffer_c8_t& buffer) {
|
void POCSAGProcessor::execute(const buffer_c8_t& buffer) {
|
||||||
if (!configured) return;
|
if (!configured) return;
|
||||||
|
|
||||||
|
// buffer has 2048 samples
|
||||||
|
// decim0 out: 2048/8 = 256 samples
|
||||||
|
// decim1 out: 256/8 = 32 samples
|
||||||
|
// channel out: 32/2 = 16 samples
|
||||||
|
|
||||||
// Get 24kHz audio
|
// Get 24kHz audio
|
||||||
const auto decim_0_out = decim_0.execute(buffer, dst_buffer);
|
const auto decim_0_out = decim_0.execute(buffer, dst_buffer);
|
||||||
const auto decim_1_out = decim_1.execute(decim_0_out, dst_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);
|
const auto channel_out = channel_filter.execute(decim_1_out, dst_buffer);
|
||||||
auto audio = demod.execute(channel_out, audio_buffer);
|
auto audio = demod.execute(channel_out, audio_buffer);
|
||||||
|
|
||||||
// If squelching, check for audio before smoothing because smoothing
|
// Check if there's any signal in the audio buffer.
|
||||||
// causes the squelch noise detection to fail. Likely because squelch
|
bool has_audio = squelch.execute(audio);
|
||||||
// looks for HF noise and smoothing is basically a lowpass filter.
|
squelch_history = (squelch_history << 1) | (has_audio ? 1 : 0);
|
||||||
// NB: Squelch in this processor is only for the the audio output.
|
|
||||||
// Squelching will likely drop data "noise" and break processing.
|
// Has there been any signal?
|
||||||
if (squelch_.enabled()) {
|
if (squelch_history == 0) {
|
||||||
bool has_audio = squelch_.execute(audio);
|
// No signal for a while, flush and reset.
|
||||||
squelch_history = (squelch_history << 1) | (has_audio ? 1 : 0);
|
if (!has_been_reset) {
|
||||||
|
OnDataFrame(m_numCode, getRate());
|
||||||
|
resetVals();
|
||||||
|
send_stats();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear the audio stream before sending.
|
||||||
|
for (size_t i = 0; i < audio.count; ++i)
|
||||||
|
audio.p[i] = 0.0;
|
||||||
|
|
||||||
|
audio_output.write(audio);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
smooth.Process(audio.p, audio.count);
|
// Filter out high-frequency noise. TODO: compensate gain?
|
||||||
|
lpf.execute_in_place(audio);
|
||||||
|
normalizer.execute_in_place(audio);
|
||||||
|
audio_output.write(audio);
|
||||||
|
|
||||||
processDemodulatedSamples(audio.p, 16);
|
processDemodulatedSamples(audio.p, 16);
|
||||||
extractFrames();
|
extractFrames();
|
||||||
|
|
||||||
// Clear the output before sending to audio chip.
|
samples_processed += buffer.count;
|
||||||
if (squelch_.enabled() && squelch_history == 0) {
|
if (samples_processed >= stat_update_threshold) {
|
||||||
for (size_t i = 0; i < audio.count; ++i) {
|
send_stats();
|
||||||
audio.p[i] = 0.0;
|
samples_processed = 0;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
audio_output.write(audio);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ====================================================================
|
void POCSAGProcessor::on_message(const Message* const message) {
|
||||||
//
|
switch (message->id) {
|
||||||
// ====================================================================
|
case Message::ID::POCSAGConfigure:
|
||||||
|
configure();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Message::ID::NBFMConfigure: {
|
||||||
|
auto config = reinterpret_cast<const NBFMConfigureMessage*>(message);
|
||||||
|
squelch.set_threshold(config->squelch_level / 99.0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void POCSAGProcessor::configure() {
|
||||||
|
constexpr size_t decim_0_output_fs = baseband_fs / decim_0.decimation_factor;
|
||||||
|
constexpr size_t decim_1_output_fs = decim_0_output_fs / decim_1.decimation_factor;
|
||||||
|
const size_t channel_filter_output_fs = decim_1_output_fs / 2;
|
||||||
|
const size_t demod_input_fs = channel_filter_output_fs;
|
||||||
|
|
||||||
|
decim_0.configure(taps_11k0_decim_0.taps);
|
||||||
|
decim_1.configure(taps_11k0_decim_1.taps);
|
||||||
|
channel_filter.configure(taps_11k0_channel.taps, 2);
|
||||||
|
demod.configure(demod_input_fs, 4'500); // FSK +/- 4k5Hz.
|
||||||
|
|
||||||
|
// Don't process the audio stream.
|
||||||
|
audio_output.configure(false);
|
||||||
|
|
||||||
|
// Set up the frame extraction, limits of baud.
|
||||||
|
setFrameExtractParams(demod_input_fs, 4000, 300, 32);
|
||||||
|
|
||||||
|
// Set ready to process data.
|
||||||
|
configured = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void POCSAGProcessor::send_stats() const {
|
||||||
|
POCSAGStatsMessage message(m_fifo.codeword, m_numCode, m_gotSync);
|
||||||
|
shared_memory.application_queue.push(message);
|
||||||
|
}
|
||||||
|
|
||||||
int POCSAGProcessor::OnDataWord(uint32_t word, int pos) {
|
int POCSAGProcessor::OnDataWord(uint32_t word, int pos) {
|
||||||
packet.set(pos, word);
|
packet.set(pos, word);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ====================================================================
|
|
||||||
//
|
|
||||||
// ====================================================================
|
|
||||||
int POCSAGProcessor::OnDataFrame(int len, int baud) {
|
int POCSAGProcessor::OnDataFrame(int len, int baud) {
|
||||||
if (len > 0) {
|
if (len > 0) {
|
||||||
packet.set_bitrate(baud);
|
packet.set_bitrate(baud);
|
||||||
@ -88,57 +143,6 @@ int POCSAGProcessor::OnDataFrame(int len, int baud) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void POCSAGProcessor::on_message(const Message* const message) {
|
|
||||||
switch (message->id) {
|
|
||||||
case Message::ID::POCSAGConfigure:
|
|
||||||
configure();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Message::ID::NBFMConfigure: {
|
|
||||||
auto config = reinterpret_cast<const NBFMConfigureMessage*>(message);
|
|
||||||
squelch_.set_threshold(config->squelch_level / 100.0);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void POCSAGProcessor::configure() {
|
|
||||||
constexpr size_t decim_0_input_fs = baseband_fs;
|
|
||||||
constexpr size_t decim_0_output_fs = decim_0_input_fs / decim_0.decimation_factor;
|
|
||||||
|
|
||||||
constexpr size_t decim_1_input_fs = decim_0_output_fs;
|
|
||||||
constexpr size_t decim_1_output_fs = decim_1_input_fs / decim_1.decimation_factor;
|
|
||||||
|
|
||||||
constexpr size_t channel_filter_input_fs = decim_1_output_fs;
|
|
||||||
const size_t channel_filter_output_fs = channel_filter_input_fs / 2;
|
|
||||||
|
|
||||||
const size_t demod_input_fs = channel_filter_output_fs;
|
|
||||||
|
|
||||||
decim_0.configure(taps_11k0_decim_0.taps);
|
|
||||||
decim_1.configure(taps_11k0_decim_1.taps);
|
|
||||||
channel_filter.configure(taps_11k0_channel.taps, 2);
|
|
||||||
demod.configure(demod_input_fs, 4'500); // FSK +/- 4k5Hz.
|
|
||||||
|
|
||||||
// Smoothing should be roughly sample rate over max baud
|
|
||||||
// 24k / 3.2k = 7.5
|
|
||||||
smooth.SetSize(8);
|
|
||||||
|
|
||||||
// Don't have audio process the stream.
|
|
||||||
audio_output.configure(false);
|
|
||||||
|
|
||||||
// Set up the frame extraction, limits of baud
|
|
||||||
setFrameExtractParams(demod_input_fs, 4000, 300, 32);
|
|
||||||
|
|
||||||
// Mark the class as ready to accept data
|
|
||||||
configured = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------
|
|
||||||
// Frame extractraction methods
|
|
||||||
// -----------------------------
|
|
||||||
#define BAUD_STABLE (104)
|
#define BAUD_STABLE (104)
|
||||||
#define MAX_CONSEC_SAME (32)
|
#define MAX_CONSEC_SAME (32)
|
||||||
#define MAX_WITHOUT_SINGLE (64)
|
#define MAX_WITHOUT_SINGLE (64)
|
||||||
@ -149,9 +153,6 @@ void POCSAGProcessor::configure() {
|
|||||||
|
|
||||||
#define M_IDLE (0x7a89c197)
|
#define M_IDLE (0x7a89c197)
|
||||||
|
|
||||||
// ====================================================================
|
|
||||||
//
|
|
||||||
// ====================================================================
|
|
||||||
inline int bitsDiff(unsigned long left, unsigned long right) {
|
inline int bitsDiff(unsigned long left, unsigned long right) {
|
||||||
unsigned long xord = left ^ right;
|
unsigned long xord = left ^ right;
|
||||||
int count = 0;
|
int count = 0;
|
||||||
@ -162,9 +163,6 @@ inline int bitsDiff(unsigned long left, unsigned long right) {
|
|||||||
return (count);
|
return (count);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ====================================================================
|
|
||||||
//
|
|
||||||
// ====================================================================
|
|
||||||
void POCSAGProcessor::initFrameExtraction() {
|
void POCSAGProcessor::initFrameExtraction() {
|
||||||
m_averageSymbolLen_1024 = m_maxSymSamples_1024;
|
m_averageSymbolLen_1024 = m_maxSymSamples_1024;
|
||||||
m_lastStableSymbolLen_1024 = m_minSymSamples_1024;
|
m_lastStableSymbolLen_1024 = m_minSymSamples_1024;
|
||||||
@ -177,12 +175,10 @@ void POCSAGProcessor::initFrameExtraction() {
|
|||||||
resetVals();
|
resetVals();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ====================================================================
|
|
||||||
//
|
|
||||||
// ====================================================================
|
|
||||||
void POCSAGProcessor::resetVals() {
|
void POCSAGProcessor::resetVals() {
|
||||||
|
if (has_been_reset) return;
|
||||||
|
|
||||||
// Reset the parameters
|
// Reset the parameters
|
||||||
// --------------------
|
|
||||||
m_goodTransitions = 0;
|
m_goodTransitions = 0;
|
||||||
m_badTransitions = 0;
|
m_badTransitions = 0;
|
||||||
m_averageSymbolLen_1024 = m_maxSymSamples_1024;
|
m_averageSymbolLen_1024 = m_maxSymSamples_1024;
|
||||||
@ -190,7 +186,6 @@ void POCSAGProcessor::resetVals() {
|
|||||||
m_valMid = 0;
|
m_valMid = 0;
|
||||||
|
|
||||||
// And reset the counts
|
// And reset the counts
|
||||||
// --------------------
|
|
||||||
m_lastTransPos_1024 = 0;
|
m_lastTransPos_1024 = 0;
|
||||||
m_lastBitPos_1024 = 0;
|
m_lastBitPos_1024 = 0;
|
||||||
m_lastSample = 0;
|
m_lastSample = 0;
|
||||||
@ -200,13 +195,14 @@ void POCSAGProcessor::resetVals() {
|
|||||||
|
|
||||||
// Extraction
|
// Extraction
|
||||||
m_fifo.numBits = 0;
|
m_fifo.numBits = 0;
|
||||||
|
m_fifo.codeword = 0;
|
||||||
m_gotSync = false;
|
m_gotSync = false;
|
||||||
m_numCode = 0;
|
m_numCode = 0;
|
||||||
|
|
||||||
|
has_been_reset = true;
|
||||||
|
samples_processed = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ====================================================================
|
|
||||||
//
|
|
||||||
// ====================================================================
|
|
||||||
void POCSAGProcessor::setFrameExtractParams(long a_samplesPerSec, long a_maxBaud, long a_minBaud, long maxRunOfSameValue) {
|
void POCSAGProcessor::setFrameExtractParams(long a_samplesPerSec, long a_maxBaud, long a_minBaud, long maxRunOfSameValue) {
|
||||||
m_samplesPerSec = a_samplesPerSec;
|
m_samplesPerSec = a_samplesPerSec;
|
||||||
m_minSymSamples_1024 = (uint32_t)(1024.0f * (float)a_samplesPerSec / (float)a_maxBaud);
|
m_minSymSamples_1024 = (uint32_t)(1024.0f * (float)a_samplesPerSec / (float)a_maxBaud);
|
||||||
@ -223,14 +219,13 @@ void POCSAGProcessor::setFrameExtractParams(long a_samplesPerSec, long a_maxBaud
|
|||||||
initFrameExtraction();
|
initFrameExtraction();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ====================================================================
|
|
||||||
//
|
|
||||||
// ====================================================================
|
|
||||||
int POCSAGProcessor::processDemodulatedSamples(float* sampleBuff, int noOfSamples) {
|
int POCSAGProcessor::processDemodulatedSamples(float* sampleBuff, int noOfSamples) {
|
||||||
bool transition = false;
|
bool transition = false;
|
||||||
uint32_t samplePos_1024 = 0;
|
uint32_t samplePos_1024 = 0;
|
||||||
uint32_t len_1024 = 0;
|
uint32_t len_1024 = 0;
|
||||||
|
|
||||||
|
has_been_reset = false;
|
||||||
|
|
||||||
// Loop through the block of data
|
// Loop through the block of data
|
||||||
// ------------------------------
|
// ------------------------------
|
||||||
for (int pos = 0; pos < noOfSamples; ++pos) {
|
for (int pos = 0; pos < noOfSamples; ++pos) {
|
||||||
@ -399,9 +394,6 @@ int POCSAGProcessor::processDemodulatedSamples(float* sampleBuff, int noOfSample
|
|||||||
return getNoOfBits();
|
return getNoOfBits();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ====================================================================
|
|
||||||
//
|
|
||||||
// ====================================================================
|
|
||||||
void POCSAGProcessor::storeBit() {
|
void POCSAGProcessor::storeBit() {
|
||||||
if (++m_bitsStart >= BIT_BUF_SIZE) {
|
if (++m_bitsStart >= BIT_BUF_SIZE) {
|
||||||
m_bitsStart = 0;
|
m_bitsStart = 0;
|
||||||
@ -429,9 +421,6 @@ void POCSAGProcessor::storeBit() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ====================================================================
|
|
||||||
//
|
|
||||||
// ====================================================================
|
|
||||||
int POCSAGProcessor::extractFrames() {
|
int POCSAGProcessor::extractFrames() {
|
||||||
int msgCnt = 0;
|
int msgCnt = 0;
|
||||||
// While there is unread data in the bits buffer
|
// While there is unread data in the bits buffer
|
||||||
@ -471,7 +460,7 @@ int POCSAGProcessor::extractFrames() {
|
|||||||
// If at the end of a 16 word block
|
// If at the end of a 16 word block
|
||||||
// --------------------------------
|
// --------------------------------
|
||||||
if (m_numCode >= 15) {
|
if (m_numCode >= 15) {
|
||||||
msgCnt += OnDataFrame(m_numCode + 1, (m_samplesPerSec << 10) / m_lastStableSymbolLen_1024);
|
msgCnt += OnDataFrame(m_numCode + 1, getRate());
|
||||||
m_gotSync = false;
|
m_gotSync = false;
|
||||||
m_numCode = -1;
|
m_numCode = -1;
|
||||||
}
|
}
|
||||||
@ -482,9 +471,6 @@ int POCSAGProcessor::extractFrames() {
|
|||||||
return msgCnt;
|
return msgCnt;
|
||||||
} // extractFrames
|
} // extractFrames
|
||||||
|
|
||||||
// ====================================================================
|
|
||||||
//
|
|
||||||
// ====================================================================
|
|
||||||
short POCSAGProcessor::getBit() {
|
short POCSAGProcessor::getBit() {
|
||||||
if (m_bitsEnd != m_bitsStart) {
|
if (m_bitsEnd != m_bitsStart) {
|
||||||
if (++m_bitsEnd >= BIT_BUF_SIZE) {
|
if (++m_bitsEnd >= BIT_BUF_SIZE) {
|
||||||
@ -496,9 +482,6 @@ short POCSAGProcessor::getBit() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ====================================================================
|
|
||||||
//
|
|
||||||
// ====================================================================
|
|
||||||
int POCSAGProcessor::getNoOfBits() {
|
int POCSAGProcessor::getNoOfBits() {
|
||||||
int bits = m_bitsEnd - m_bitsStart;
|
int bits = m_bitsEnd - m_bitsStart;
|
||||||
if (bits < 0) {
|
if (bits < 0) {
|
||||||
@ -507,16 +490,10 @@ int POCSAGProcessor::getNoOfBits() {
|
|||||||
return bits;
|
return bits;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ====================================================================
|
|
||||||
//
|
|
||||||
// ====================================================================
|
|
||||||
uint32_t POCSAGProcessor::getRate() {
|
uint32_t POCSAGProcessor::getRate() {
|
||||||
return ((m_samplesPerSec << 10) + 512) / m_lastStableSymbolLen_1024;
|
return ((m_samplesPerSec << 10) + 512) / m_lastStableSymbolLen_1024;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ====================================================================
|
|
||||||
//
|
|
||||||
// ====================================================================
|
|
||||||
int main() {
|
int main() {
|
||||||
EventDispatcher event_dispatcher{std::make_unique<POCSAGProcessor>()};
|
EventDispatcher event_dispatcher{std::make_unique<POCSAGProcessor>()};
|
||||||
event_dispatcher.run();
|
event_dispatcher.run();
|
||||||
|
@ -26,107 +26,87 @@
|
|||||||
#ifndef __PROC_POCSAG2_H__
|
#ifndef __PROC_POCSAG2_H__
|
||||||
#define __PROC_POCSAG2_H__
|
#define __PROC_POCSAG2_H__
|
||||||
|
|
||||||
|
#include "audio_output.hpp"
|
||||||
#include "baseband_processor.hpp"
|
#include "baseband_processor.hpp"
|
||||||
#include "baseband_thread.hpp"
|
#include "baseband_thread.hpp"
|
||||||
#include "rssi_thread.hpp"
|
|
||||||
|
|
||||||
#include "dsp_decimate.hpp"
|
#include "dsp_decimate.hpp"
|
||||||
#include "dsp_demodulate.hpp"
|
#include "dsp_demodulate.hpp"
|
||||||
|
#include "dsp_iir_config.hpp"
|
||||||
#include "pocsag_packet.hpp"
|
|
||||||
|
|
||||||
#include "pocsag.hpp"
|
|
||||||
#include "message.hpp"
|
#include "message.hpp"
|
||||||
#include "audio_output.hpp"
|
#include "pocsag.hpp"
|
||||||
|
#include "pocsag_packet.hpp"
|
||||||
#include "portapack_shared_memory.hpp"
|
#include "portapack_shared_memory.hpp"
|
||||||
|
#include "rssi_thread.hpp"
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <bitset>
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
// Class used to smooth demodulated waveform prior to decoding
|
|
||||||
// -----------------------------------------------------------
|
|
||||||
template <class ValType, class CalcType>
|
|
||||||
class SmoothVals {
|
|
||||||
protected:
|
|
||||||
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
|
|
||||||
int m_count; //
|
|
||||||
|
|
||||||
|
/* Takes audio stream and automatically normalizes it to +/-1.0f */
|
||||||
|
class AudioNormalizer {
|
||||||
public:
|
public:
|
||||||
SmoothVals()
|
void execute_in_place(const buffer_f32_t& audio) {
|
||||||
: m_lastVals(NULL), m_size(1), m_sumVal(0), m_pos(0), m_count(0) {
|
// Decay min/max every second (@24kHz).
|
||||||
m_lastVals = new ValType[m_size];
|
if (counter_ >= 24'000) {
|
||||||
}
|
// 90% decay factor seems to work well.
|
||||||
|
// This keeps large transients from wrecking the filter.
|
||||||
// --------------------------------------------------
|
max_ *= 0.9f;
|
||||||
// --------------------------------------------------
|
min_ *= 0.9f;
|
||||||
virtual ~SmoothVals() {
|
counter_ = 0;
|
||||||
delete[] m_lastVals;
|
calculate_thresholds();
|
||||||
}
|
|
||||||
|
|
||||||
SmoothVals(const SmoothVals<float, float>&) {
|
|
||||||
}
|
|
||||||
|
|
||||||
SmoothVals& operator=(const SmoothVals<float, float>&) {
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --------------------------------------------------
|
|
||||||
// Set size of smoothing
|
|
||||||
// --------------------------------------------------
|
|
||||||
void SetSize(int size) {
|
|
||||||
m_size = std::max(size, 1);
|
|
||||||
m_pos = 0;
|
|
||||||
delete[] m_lastVals;
|
|
||||||
m_lastVals = new ValType[m_size];
|
|
||||||
m_sumVal = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --------------------------------------------------
|
|
||||||
// Get size of smoothing
|
|
||||||
// --------------------------------------------------
|
|
||||||
int Size() { return m_size; }
|
|
||||||
|
|
||||||
// --------------------------------------------------
|
|
||||||
// In place processing
|
|
||||||
// --------------------------------------------------
|
|
||||||
void Process(ValType* valBuff, int numVals) {
|
|
||||||
ValType tmpVal;
|
|
||||||
|
|
||||||
if (m_count > (1024 * 10)) {
|
|
||||||
// Recalculate the sum value occasionaly, stops accumulated errors when using float
|
|
||||||
m_count = 0;
|
|
||||||
m_sumVal = 0;
|
|
||||||
for (int i = 0; i < m_size; ++i) {
|
|
||||||
m_sumVal += (CalcType)m_lastVals[i];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use a rolling smoothed value while processing the buffer
|
counter_ += audio.count;
|
||||||
for (int buffPos = 0; buffPos < numVals; ++buffPos) {
|
|
||||||
m_pos++;
|
for (size_t i = 0; i < audio.count; ++i) {
|
||||||
if (m_pos >= m_size) {
|
auto& val = audio.p[i];
|
||||||
m_pos = 0;
|
|
||||||
|
if (val > max_) {
|
||||||
|
max_ = val;
|
||||||
|
calculate_thresholds();
|
||||||
|
}
|
||||||
|
if (val < min_) {
|
||||||
|
min_ = val;
|
||||||
|
calculate_thresholds();
|
||||||
}
|
}
|
||||||
|
|
||||||
m_sumVal -= (CalcType)m_lastVals[m_pos]; // Subtract the oldest value
|
if (val >= t_hi_)
|
||||||
m_lastVals[m_pos] = valBuff[buffPos]; // Store the new value
|
val = 1.0f;
|
||||||
m_sumVal += (CalcType)m_lastVals[m_pos]; // Add on the new value
|
else if (val <= t_lo_)
|
||||||
|
val = -1.0f;
|
||||||
tmpVal = (ValType)(m_sumVal / m_size); // Scale by number of values smoothed
|
else
|
||||||
valBuff[buffPos] = tmpVal;
|
val = 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_count += numVals;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void calculate_thresholds() {
|
||||||
|
auto center = (max_ + min_) / 2.0f;
|
||||||
|
auto range = (max_ - min_) / 2.0f;
|
||||||
|
|
||||||
|
// 10% off center force either +/-1.0f.
|
||||||
|
// Higher == larger dead zone.
|
||||||
|
// Lower == more false positives.
|
||||||
|
auto threshold = range * 0.1;
|
||||||
|
t_hi_ = center + threshold;
|
||||||
|
t_lo_ = center - threshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t counter_ = 0;
|
||||||
|
float min_ = 99.0f;
|
||||||
|
float max_ = -99.0f;
|
||||||
|
float t_hi_ = 1.0;
|
||||||
|
float t_lo_ = 1.0;
|
||||||
};
|
};
|
||||||
|
|
||||||
// --------------------------------------------------
|
// How to detect clock signal across baud rates?
|
||||||
// Class to process base band data to pocsag frames
|
// Maybe have a bit extraction state machine that reset
|
||||||
// --------------------------------------------------
|
// then watches for the clocks, but there are multiple
|
||||||
|
// clock and the last one is the right one.
|
||||||
|
// So keep updating clock until a sync?
|
||||||
|
|
||||||
|
class BitExtractor {};
|
||||||
|
|
||||||
|
class WordExtractor {};
|
||||||
|
|
||||||
class POCSAGProcessor : public BasebandProcessor {
|
class POCSAGProcessor : public BasebandProcessor {
|
||||||
public:
|
public:
|
||||||
void execute(const buffer_c8_t& buffer) override;
|
void execute(const buffer_c8_t& buffer) override;
|
||||||
@ -137,28 +117,54 @@ class POCSAGProcessor : public BasebandProcessor {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
static constexpr size_t baseband_fs = 3072000;
|
static constexpr size_t baseband_fs = 3072000;
|
||||||
|
static constexpr uint8_t stat_update_interval = 10;
|
||||||
std::array<complex16_t, 512> dst{};
|
static constexpr uint32_t stat_update_threshold =
|
||||||
const buffer_c16_t dst_buffer{
|
baseband_fs / stat_update_interval;
|
||||||
dst.data(),
|
|
||||||
dst.size()};
|
|
||||||
std::array<float, 32> audio{};
|
|
||||||
const buffer_f32_t audio_buffer{
|
|
||||||
audio.data(),
|
|
||||||
audio.size()};
|
|
||||||
|
|
||||||
dsp::decimate::FIRC8xR16x24FS4Decim8 decim_0{};
|
|
||||||
dsp::decimate::FIRC16xR16x32Decim8 decim_1{};
|
|
||||||
dsp::decimate::FIRAndDecimateComplex channel_filter{};
|
|
||||||
dsp::demodulate::FM demod{};
|
|
||||||
SmoothVals<float, float> smooth = {};
|
|
||||||
|
|
||||||
AudioOutput audio_output{};
|
|
||||||
|
|
||||||
bool configured = false;
|
|
||||||
pocsag::POCSAGPacket packet{};
|
|
||||||
|
|
||||||
void configure();
|
void configure();
|
||||||
|
void send_stats() const;
|
||||||
|
|
||||||
|
// Set once app is ready to receive messages.
|
||||||
|
bool configured = false;
|
||||||
|
|
||||||
|
// Buffer for decimated IQ data.
|
||||||
|
std::array<complex16_t, 512> dst{};
|
||||||
|
const buffer_c16_t dst_buffer{dst.data(), dst.size()};
|
||||||
|
|
||||||
|
// Buffer for demodulated audio.
|
||||||
|
std::array<float, 32> audio{};
|
||||||
|
const buffer_f32_t audio_buffer{audio.data(), audio.size()};
|
||||||
|
|
||||||
|
// Decimate to 48kHz.
|
||||||
|
dsp::decimate::FIRC8xR16x24FS4Decim8 decim_0{};
|
||||||
|
dsp::decimate::FIRC16xR16x32Decim8 decim_1{};
|
||||||
|
|
||||||
|
// Filter to 24kHz and demodulate.
|
||||||
|
dsp::decimate::FIRAndDecimateComplex channel_filter{};
|
||||||
|
dsp::demodulate::FM demod{};
|
||||||
|
|
||||||
|
// LPF to reduce noise.
|
||||||
|
// scipy.signal.butter(2, 1800, "lowpass", fs=24000, analog=False)
|
||||||
|
IIRBiquadFilter lpf{{{0.04125354f, 0.082507070f, 0.04125354f},
|
||||||
|
{1.00000000f, -1.34896775f, 0.51398189f}}};
|
||||||
|
|
||||||
|
// Squelch to ignore noise.
|
||||||
|
FMSquelch squelch{};
|
||||||
|
uint64_t squelch_history = 0;
|
||||||
|
|
||||||
|
// Attempts to de-noise signal and normalize to +/- 1.0f.
|
||||||
|
AudioNormalizer normalizer{};
|
||||||
|
|
||||||
|
// Handles writing audio stream to hardware.
|
||||||
|
AudioOutput audio_output{};
|
||||||
|
|
||||||
|
// Holds the data sent to the app.
|
||||||
|
pocsag::POCSAGPacket packet{};
|
||||||
|
|
||||||
|
bool has_been_reset = true;
|
||||||
|
uint32_t samples_processed = 0;
|
||||||
|
|
||||||
|
//--------------------------------------------------
|
||||||
|
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
// Frame extractraction methods and members
|
// Frame extractraction methods and members
|
||||||
@ -169,8 +175,6 @@ class POCSAGProcessor : public BasebandProcessor {
|
|||||||
int numBits;
|
int numBits;
|
||||||
};
|
};
|
||||||
|
|
||||||
#define BIT_BUF_SIZE (64)
|
|
||||||
|
|
||||||
void resetVals();
|
void resetVals();
|
||||||
void setFrameExtractParams(long a_samplesPerSec, long a_maxBaud = 8000, long a_minBaud = 200, long maxRunOfSameValue = 32);
|
void setFrameExtractParams(long a_samplesPerSec, long a_maxBaud = 8000, long a_minBaud = 200, long maxRunOfSameValue = 32);
|
||||||
|
|
||||||
@ -207,7 +211,8 @@ class POCSAGProcessor : public BasebandProcessor {
|
|||||||
uint32_t m_maxSymSamples_1024{0};
|
uint32_t m_maxSymSamples_1024{0};
|
||||||
uint32_t m_maxRunOfSameValue{0};
|
uint32_t m_maxRunOfSameValue{0};
|
||||||
|
|
||||||
bitset<(size_t)BIT_BUF_SIZE> m_bits{0};
|
static constexpr long BIT_BUF_SIZE = 64;
|
||||||
|
std::bitset<64> m_bits{0};
|
||||||
long m_bitsStart{0};
|
long m_bitsStart{0};
|
||||||
long m_bitsEnd{0};
|
long m_bitsEnd{0};
|
||||||
|
|
||||||
@ -216,8 +221,7 @@ class POCSAGProcessor : public BasebandProcessor {
|
|||||||
int m_numCode{0};
|
int m_numCode{0};
|
||||||
bool m_inverted{false};
|
bool m_inverted{false};
|
||||||
|
|
||||||
FMSquelch squelch_{};
|
//--------------------------------------------------
|
||||||
uint64_t squelch_history = 0;
|
|
||||||
|
|
||||||
/* NB: Threads should be the last members in the class definition. */
|
/* NB: Threads should be the last members in the class definition. */
|
||||||
BasebandThread baseband_thread{baseband_fs, this, baseband::Direction::Receive};
|
BasebandThread baseband_thread{baseband_fs, this, baseband::Direction::Receive};
|
||||||
|
@ -77,12 +77,6 @@ constexpr iir_biquad_config_t audio_24k_deemph_300_6_config{
|
|||||||
{0.03780475f, 0.03780475f, 0.00000000f},
|
{0.03780475f, 0.03780475f, 0.00000000f},
|
||||||
{1.00000000f, -0.92439049f, 0.00000000f}};
|
{1.00000000f, -0.92439049f, 0.00000000f}};
|
||||||
|
|
||||||
// scipy.signal.butter(1, 2400 / 12000.0, 'lowpass', analog=False)
|
|
||||||
// NOTE: Technically, order-1 filter, b[2] = a[2] = 0.
|
|
||||||
constexpr iir_biquad_config_t audio_24k_lpf_2400hz_config{
|
|
||||||
{0.03780475f, 0.03780475f, 0.00000000f},
|
|
||||||
{1.00000000f, -0.92439049f, 0.00000000f}};
|
|
||||||
|
|
||||||
// scipy.signal.butter(1, 300 / 8000.0, 'lowpass', analog=False)
|
// scipy.signal.butter(1, 300 / 8000.0, 'lowpass', analog=False)
|
||||||
// NOTE: Technically, order-1 filter, b[2] = a[2] = 0.
|
// NOTE: Technically, order-1 filter, b[2] = a[2] = 0.
|
||||||
constexpr iir_biquad_config_t audio_16k_deemph_300_6_config{
|
constexpr iir_biquad_config_t audio_16k_deemph_300_6_config{
|
||||||
|
@ -344,12 +344,18 @@ class POCSAGPacketMessage : public Message {
|
|||||||
class POCSAGStatsMessage : public Message {
|
class POCSAGStatsMessage : public Message {
|
||||||
public:
|
public:
|
||||||
constexpr POCSAGStatsMessage(
|
constexpr POCSAGStatsMessage(
|
||||||
uint16_t baud_rate)
|
uint32_t current_bits,
|
||||||
|
uint8_t current_frames,
|
||||||
|
bool has_sync)
|
||||||
: Message{ID::POCSAGStats},
|
: Message{ID::POCSAGStats},
|
||||||
baud_rate_{baud_rate} {
|
current_bits{current_bits},
|
||||||
|
current_frames{current_frames},
|
||||||
|
has_sync{has_sync} {
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t baud_rate_;
|
uint32_t current_bits = 0;
|
||||||
|
uint8_t current_frames = 0;
|
||||||
|
bool has_sync = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
class ACARSPacketMessage : public Message {
|
class ACARSPacketMessage : public Message {
|
||||||
|
Loading…
Reference in New Issue
Block a user