mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-08-15 12:23:43 +00:00
106e56abc3
* Add FLEX pager support - Introduced a new FLEX configuration function in baseband_api. - Added FLEX application view and associated UI elements. - Implemented FLEX processing logic in proc_flex, including demodulation and message handling. - Updated CMakeLists to include new FLEX source files and headers. - Enhanced message system to support FLEX-specific messages and statistics. This commit lays the groundwork for FLEX pager functionality, allowing for the reception and processing of FLEX messages. * Fixed baseband and moved app to external with some other fixes. * Format code --------- Co-authored-by: RocketGod <57732082+RocketGod-git@users.noreply.github.com>
811 lines
24 KiB
C++
811 lines
24 KiB
C++
#include "proc_flex.hpp"
|
|
#include "event_m4.hpp"
|
|
#include "audio_dma.hpp"
|
|
#include "pocsag.hpp"
|
|
#include "dsp_fir_taps.hpp"
|
|
#include "portapack_shared_memory.hpp"
|
|
|
|
#include <cmath>
|
|
#include <cstring>
|
|
#include <cstdio> // for snprintf
|
|
|
|
// Constants from demod_flex.c
|
|
#define FREQ_SAMP 24000 // Our sample rate
|
|
#define DC_OFFSET_FILTER 0.010
|
|
#define PHASE_LOCKED_RATE 0.045
|
|
#define PHASE_UNLOCKED_RATE 0.050
|
|
#define LOCK_LEN 24
|
|
#define IDLE_THRESHOLD 0
|
|
#define DEMOD_TIMEOUT 100
|
|
#define FLEX_SYNC_MARKER 0xA6C6AAAAul
|
|
#define SLICE_THRESHOLD 0.667
|
|
|
|
// Implement EccContainer here to avoid linking pocsag.cpp which pulls in app headers
|
|
using namespace pocsag;
|
|
|
|
EccContainer::EccContainer() {
|
|
setup_ecc();
|
|
}
|
|
|
|
void EccContainer::setup_ecc() {
|
|
unsigned int srr = 0x3b4;
|
|
unsigned int i, n, j, k;
|
|
|
|
for (i = 0; i <= 20; i++) {
|
|
ecs[i] = srr;
|
|
if ((srr & 0x01) != 0)
|
|
srr = (srr >> 1) ^ 0x3B4;
|
|
else
|
|
srr = srr >> 1;
|
|
}
|
|
|
|
for (i = 0; i < 1024; i++) bch[i] = 0;
|
|
|
|
for (n = 0; n <= 20; n++) {
|
|
for (i = 0; i <= 20; i++) {
|
|
j = (i << 5) + n;
|
|
k = ecs[n] ^ ecs[i];
|
|
bch[k] = j + 0x2000;
|
|
}
|
|
}
|
|
|
|
for (n = 0; n <= 20; n++) {
|
|
k = ecs[n];
|
|
j = n + (0x1f << 5);
|
|
bch[k] = j + 0x1000;
|
|
}
|
|
|
|
for (n = 0; n <= 20; n++) {
|
|
for (i = 0; i < 10; i++) {
|
|
k = ecs[n] ^ (1 << i);
|
|
j = n + (0x1f << 5);
|
|
bch[k] = j + 0x2000;
|
|
}
|
|
}
|
|
|
|
for (n = 0; n < 10; n++) {
|
|
k = 1 << n;
|
|
bch[k] = 0x3ff + 0x1000;
|
|
}
|
|
|
|
for (n = 0; n < 10; n++) {
|
|
for (i = 0; i < 10; i++) {
|
|
if (i != n) {
|
|
k = (1 << n) ^ (1 << i);
|
|
bch[k] = 0x3ff + 0x2000;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int EccContainer::error_correct(uint32_t& val) {
|
|
int i, synd, errl, acc, pari, ecc, b1, b2;
|
|
|
|
errl = 0;
|
|
pari = 0;
|
|
|
|
ecc = 0;
|
|
for (i = 31; i >= 11; --i) {
|
|
if (val & (1 << i)) {
|
|
ecc = ecc ^ ecs[31 - i];
|
|
pari = pari ^ 0x01;
|
|
}
|
|
}
|
|
|
|
acc = 0;
|
|
for (i = 10; i >= 1; --i) {
|
|
acc = acc << 1;
|
|
if (val & (1 << i)) {
|
|
acc = acc ^ 0x01;
|
|
}
|
|
}
|
|
|
|
synd = ecc ^ acc;
|
|
errl = 0;
|
|
|
|
if (synd != 0) {
|
|
if (bch[synd] != 0) {
|
|
b1 = bch[synd] & 0x1f;
|
|
b2 = bch[synd] >> 5;
|
|
b2 = b2 & 0x1f;
|
|
|
|
if (b2 != 0x1f) {
|
|
val ^= 0x01 << (31 - b2);
|
|
ecc = ecc ^ ecs[b2];
|
|
}
|
|
|
|
if (b1 != 0x1f) {
|
|
val ^= 0x01 << (31 - b1);
|
|
ecc = ecc ^ ecs[b1];
|
|
}
|
|
|
|
errl = bch[synd] >> 12;
|
|
} else {
|
|
errl = 3;
|
|
}
|
|
|
|
if (errl == 1) pari = pari ^ 0x01;
|
|
}
|
|
|
|
if (errl == 4) errl = 3;
|
|
|
|
return errl;
|
|
}
|
|
|
|
namespace {
|
|
|
|
// Helpers
|
|
unsigned int popcount(unsigned int n) {
|
|
// Simple popcount for 32-bit integer
|
|
n = n - ((n >> 1) & 0x55555555);
|
|
n = (n & 0x33333333) + ((n >> 2) & 0x33333333);
|
|
return (((n + (n >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24;
|
|
}
|
|
|
|
uint32_t bit_reverse_32(uint32_t x) {
|
|
x = ((x >> 1) & 0x55555555) | ((x & 0x55555555) << 1);
|
|
x = ((x >> 2) & 0x33333333) | ((x & 0x33333333) << 2);
|
|
x = ((x >> 4) & 0x0F0F0F0F) | ((x & 0x0F0F0F0F) << 4);
|
|
x = ((x >> 8) & 0x00FF00FF) | ((x & 0x00FF00FF) << 8);
|
|
x = (x >> 16) | (x << 16);
|
|
return x;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
void FlexProcessor::send_debug(const char* text, uint32_t v1, uint32_t v2) {
|
|
if (shared_memory.application_queue.is_empty()) return;
|
|
|
|
FlexDebugMessage message(v1, v2, text);
|
|
shared_memory.application_queue.push(message);
|
|
}
|
|
|
|
void FlexProcessor::execute(const buffer_c8_t& buffer) {
|
|
if (!configured) return;
|
|
|
|
// Heartbeat debug every ~1 second (24000Hz / 4096 buffer size * ~6)
|
|
static int debug_count = 0;
|
|
debug_count++;
|
|
if (debug_count > 1000) {
|
|
send_debug("Running", 0, 0);
|
|
debug_count = 0;
|
|
}
|
|
|
|
// Decimate and demodulate: 3.072MHz -> 24kHz
|
|
auto decim_0_out = decim_0_iq.execute(buffer, dst_buffer);
|
|
auto decim_1_out = decim_1_iq.execute(decim_0_out, dst_buffer);
|
|
auto channel_out = channel_filter.execute(decim_1_out, dst_buffer);
|
|
auto audio = demod.execute(channel_out, audio_buffer);
|
|
|
|
process_audio(audio);
|
|
}
|
|
|
|
void FlexProcessor::process_audio(const buffer_f32_t& audio) {
|
|
for (size_t i = 0; i < audio.count; ++i) {
|
|
flex_demodulate(audio.p[i]);
|
|
}
|
|
}
|
|
|
|
void FlexProcessor::flex_demodulate(double sample) {
|
|
if (build_symbol(sample) == 1) {
|
|
demodulator.nonconsec = 0;
|
|
demodulator.symbol_count++;
|
|
// modulation.symbol_rate = ... // Unused in main logic usually, just stats
|
|
|
|
/*Determine the modal symbol*/
|
|
int j;
|
|
int decmax = 0;
|
|
int modal_symbol = 0;
|
|
for (j = 0; j < 4; j++) {
|
|
if (demodulator.symcount[j] > decmax) {
|
|
modal_symbol = j;
|
|
decmax = demodulator.symcount[j];
|
|
}
|
|
}
|
|
demodulator.symcount[0] = 0;
|
|
demodulator.symcount[1] = 0;
|
|
demodulator.symcount[2] = 0;
|
|
demodulator.symcount[3] = 0;
|
|
|
|
if (demodulator.locked) {
|
|
/*Process the symbol*/
|
|
flex_sym(modal_symbol);
|
|
} else {
|
|
/*Check for lock pattern*/
|
|
/*Shift symbols into buffer, symbols are converted so that the max and min symbols map to 1 and 2 i.e each contain a single 1 */
|
|
demodulator.lock_buf = (demodulator.lock_buf << 2) | (modal_symbol ^ 0x1);
|
|
uint64_t lock_pattern = demodulator.lock_buf ^ 0x6666666666666666ull;
|
|
uint64_t lock_mask = (1ull << (2 * LOCK_LEN)) - 1;
|
|
|
|
if ((lock_pattern & lock_mask) == 0 || ((~lock_pattern) & lock_mask) == 0) {
|
|
demodulator.locked = 1;
|
|
demodulator.lock_buf = 0;
|
|
demodulator.symbol_count = 0;
|
|
demodulator.sample_count = 0;
|
|
}
|
|
}
|
|
|
|
/*Time out after X periods with no zero crossing*/
|
|
demodulator.timeout++;
|
|
if (demodulator.timeout > DEMOD_TIMEOUT) {
|
|
demodulator.locked = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
int FlexProcessor::build_symbol(double sample) {
|
|
const int64_t phase_max = 100 * demodulator.sample_freq;
|
|
const int64_t phase_rate = phase_max * demodulator.baud / demodulator.sample_freq;
|
|
const double phasepercent = 100.0 * demodulator.phase / phase_max;
|
|
|
|
demodulator.sample_count++;
|
|
|
|
/*Remove DC offset (FIR filter)*/
|
|
if (state.Current == flex::State::SYNC1) {
|
|
modulation.zero = (modulation.zero * (FREQ_SAMP * DC_OFFSET_FILTER) + sample) / ((FREQ_SAMP * DC_OFFSET_FILTER) + 1);
|
|
}
|
|
sample -= modulation.zero;
|
|
|
|
if (demodulator.locked) {
|
|
if (state.Current == flex::State::SYNC1) {
|
|
demodulator.envelope_sum += std::abs(sample);
|
|
demodulator.envelope_count++;
|
|
modulation.envelope = demodulator.envelope_sum / demodulator.envelope_count;
|
|
}
|
|
} else {
|
|
modulation.envelope = 0;
|
|
demodulator.envelope_sum = 0;
|
|
demodulator.envelope_count = 0;
|
|
demodulator.baud = 1600;
|
|
demodulator.timeout = 0;
|
|
demodulator.nonconsec = 0;
|
|
state.Current = flex::State::SYNC1;
|
|
}
|
|
|
|
/* MID 80% SYMBOL PERIOD */
|
|
if (phasepercent > 10 && phasepercent < 90) {
|
|
if (sample > 0) {
|
|
if (sample > modulation.envelope * SLICE_THRESHOLD)
|
|
demodulator.symcount[3]++;
|
|
else
|
|
demodulator.symcount[2]++;
|
|
} else {
|
|
if (sample < -modulation.envelope * SLICE_THRESHOLD)
|
|
demodulator.symcount[0]++;
|
|
else
|
|
demodulator.symcount[1]++;
|
|
}
|
|
}
|
|
|
|
/* ZERO CROSSING */
|
|
if ((demodulator.sample_last < 0 && sample >= 0) || (demodulator.sample_last >= 0 && sample < 0)) {
|
|
double phase_error = 0.0;
|
|
if (phasepercent < 50) {
|
|
phase_error = demodulator.phase;
|
|
} else {
|
|
phase_error = demodulator.phase - phase_max;
|
|
}
|
|
|
|
if (demodulator.locked) {
|
|
demodulator.phase -= phase_error * PHASE_LOCKED_RATE;
|
|
} else {
|
|
demodulator.phase -= phase_error * PHASE_UNLOCKED_RATE;
|
|
}
|
|
|
|
if (phasepercent > 10 && phasepercent < 90) {
|
|
demodulator.nonconsec++;
|
|
if (demodulator.nonconsec > 20 && demodulator.locked) {
|
|
demodulator.locked = 0;
|
|
}
|
|
} else {
|
|
demodulator.nonconsec = 0;
|
|
}
|
|
|
|
demodulator.timeout = 0;
|
|
}
|
|
demodulator.sample_last = sample;
|
|
|
|
/* END OF SYMBOL PERIOD */
|
|
demodulator.phase += phase_rate;
|
|
|
|
if (demodulator.phase > phase_max) {
|
|
demodulator.phase -= phase_max;
|
|
return 1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
unsigned int FlexProcessor::flex_sync(unsigned char sym) {
|
|
int retval = 0;
|
|
sync.syncbuf = (sync.syncbuf << 1) | ((sym < 2) ? 1 : 0);
|
|
|
|
retval = flex_sync_check(sync.syncbuf);
|
|
if (retval != 0) {
|
|
sync.polarity = 0;
|
|
} else {
|
|
retval = flex_sync_check(~sync.syncbuf);
|
|
if (retval != 0) {
|
|
sync.polarity = 1;
|
|
}
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
unsigned int FlexProcessor::flex_sync_check(uint64_t buf) {
|
|
// 64-bit FLEX sync code: AAAA:BBBBBBBB:CCCC
|
|
unsigned int marker = (buf & 0x0000FFFFFFFF0000ULL) >> 16;
|
|
unsigned short codehigh = (buf & 0xFFFF000000000000ULL) >> 48;
|
|
unsigned short codelow = ~(buf & 0x000000000000FFFFULL);
|
|
|
|
int retval = 0;
|
|
// Hamming distance check (popcount of XOR)
|
|
unsigned int diff_marker = popcount(marker ^ FLEX_SYNC_MARKER);
|
|
unsigned int diff_code = popcount(codelow ^ codehigh);
|
|
|
|
if (diff_marker < 4 && diff_code < 4) {
|
|
retval = codehigh;
|
|
} else {
|
|
retval = 0;
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
void FlexProcessor::decode_mode(unsigned int sync_code) {
|
|
struct FlexModeDef {
|
|
int sync;
|
|
unsigned int baud;
|
|
unsigned int levels;
|
|
} flex_modes[] = {
|
|
{0x870C, 1600, 2},
|
|
{0xB068, 1600, 4},
|
|
{0x7B18, 3200, 2},
|
|
{0xDEA0, 3200, 4},
|
|
{0x4C7C, 3200, 4},
|
|
{0, 0, 0}};
|
|
|
|
for (int i = 0; flex_modes[i].sync != 0; i++) {
|
|
unsigned int diff = popcount((unsigned int)flex_modes[i].sync ^ sync_code);
|
|
if (diff < 4) {
|
|
sync.sync = sync_code;
|
|
sync.baud = flex_modes[i].baud;
|
|
sync.levels = flex_modes[i].levels;
|
|
return;
|
|
}
|
|
}
|
|
// Default
|
|
sync.baud = 1600;
|
|
sync.levels = 2;
|
|
}
|
|
|
|
void FlexProcessor::read_2fsk(unsigned int sym, uint32_t* dat) {
|
|
*dat = (*dat >> 1) | ((sym > 1) ? 0x80000000 : 0);
|
|
}
|
|
|
|
int FlexProcessor::bch_fix_errors(uint32_t* data_to_fix) {
|
|
// Reverse bits for EccContainer (POCSAG MSB-first expectation vs FLEX LSB-first in our representation)
|
|
uint32_t reversed = bit_reverse_32(*data_to_fix);
|
|
int result = ecc.error_correct(reversed);
|
|
if (result == 0 || result == 1 || result == 2) {
|
|
*data_to_fix = bit_reverse_32(reversed);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
int FlexProcessor::decode_fiw() {
|
|
uint32_t fiw_val = fiw.rawdata;
|
|
int decode_error = bch_fix_errors(&fiw_val);
|
|
|
|
if (decode_error > 2) {
|
|
return 1;
|
|
}
|
|
|
|
fiw.checksum = fiw_val & 0xF;
|
|
fiw.cycleno = (fiw_val >> 4) & 0xF;
|
|
fiw.frameno = (fiw_val >> 8) & 0x7F;
|
|
fiw.fix3 = (fiw_val >> 15) & 0x3F;
|
|
|
|
unsigned int checksum = (fiw_val & 0xF);
|
|
checksum += ((fiw_val >> 4) & 0xF);
|
|
checksum += ((fiw_val >> 8) & 0xF);
|
|
checksum += ((fiw_val >> 12) & 0xF);
|
|
checksum += ((fiw_val >> 16) & 0xF);
|
|
checksum += ((fiw_val >> 20) & 0x01);
|
|
checksum &= 0xF;
|
|
|
|
if (checksum == 0xF) {
|
|
return 0;
|
|
} else {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
int FlexProcessor::read_data(unsigned char sym) {
|
|
int bit_a = (sym > 1);
|
|
int bit_b = 0;
|
|
if (sync.levels == 4) {
|
|
bit_b = (sym == 1) || (sym == 2);
|
|
}
|
|
|
|
if (sync.baud == 1600) {
|
|
data.phase_toggle = 0;
|
|
}
|
|
|
|
unsigned int idx = ((data.data_bit_counter >> 5) & 0xFFF8) | (data.data_bit_counter & 0x0007);
|
|
if (idx >= 88) return 0; // Boundary check
|
|
|
|
if (data.phase_toggle == 0) {
|
|
data.PhaseA.buf[idx] = (data.PhaseA.buf[idx] >> 1) | (bit_a ? 0x80000000 : 0);
|
|
data.PhaseB.buf[idx] = (data.PhaseB.buf[idx] >> 1) | (bit_b ? 0x80000000 : 0);
|
|
data.phase_toggle = 1;
|
|
|
|
if ((data.data_bit_counter & 0xFF) == 0xFF) {
|
|
if (data.PhaseA.buf[idx] == 0x00000000 || data.PhaseA.buf[idx] == 0xffffffff) data.PhaseA.idle_count++;
|
|
if (data.PhaseB.buf[idx] == 0x00000000 || data.PhaseB.buf[idx] == 0xffffffff) data.PhaseB.idle_count++;
|
|
}
|
|
} else {
|
|
data.PhaseC.buf[idx] = (data.PhaseC.buf[idx] >> 1) | (bit_a ? 0x80000000 : 0);
|
|
data.PhaseD.buf[idx] = (data.PhaseD.buf[idx] >> 1) | (bit_b ? 0x80000000 : 0);
|
|
data.phase_toggle = 0;
|
|
|
|
if ((data.data_bit_counter & 0xFF) == 0xFF) {
|
|
if (data.PhaseC.buf[idx] == 0x00000000 || data.PhaseC.buf[idx] == 0xffffffff) data.PhaseC.idle_count++;
|
|
if (data.PhaseD.buf[idx] == 0x00000000 || data.PhaseD.buf[idx] == 0xffffffff) data.PhaseD.idle_count++;
|
|
}
|
|
}
|
|
|
|
if (sync.baud == 1600 || data.phase_toggle == 0) {
|
|
data.data_bit_counter++;
|
|
}
|
|
|
|
int idle = 0;
|
|
if (sync.baud == 1600) {
|
|
if (sync.levels == 2) {
|
|
idle = (data.PhaseA.idle_count > IDLE_THRESHOLD);
|
|
} else {
|
|
idle = ((data.PhaseA.idle_count > IDLE_THRESHOLD) && (data.PhaseB.idle_count > IDLE_THRESHOLD));
|
|
}
|
|
} else {
|
|
if (sync.levels == 2) {
|
|
idle = ((data.PhaseA.idle_count > IDLE_THRESHOLD) && (data.PhaseC.idle_count > IDLE_THRESHOLD));
|
|
} else {
|
|
idle = ((data.PhaseA.idle_count > IDLE_THRESHOLD) && (data.PhaseB.idle_count > IDLE_THRESHOLD) && (data.PhaseC.idle_count > IDLE_THRESHOLD) && (data.PhaseD.idle_count > IDLE_THRESHOLD));
|
|
}
|
|
}
|
|
return idle;
|
|
}
|
|
|
|
void FlexProcessor::flex_sym(unsigned char sym) {
|
|
unsigned char sym_rectified;
|
|
if (sync.polarity) {
|
|
sym_rectified = 3 - sym;
|
|
} else {
|
|
sym_rectified = sym;
|
|
}
|
|
|
|
switch (state.Current) {
|
|
case flex::State::SYNC1: {
|
|
unsigned int sync_code = flex_sync(sym);
|
|
if (sync_code != 0) {
|
|
decode_mode(sync_code);
|
|
if (sync.baud != 0 && sync.levels != 0) {
|
|
state.Current = flex::State::FIW;
|
|
send_debug("SYNC1 Found", sync.baud, sync_code);
|
|
} else {
|
|
state.Current = flex::State::SYNC1;
|
|
}
|
|
} else {
|
|
state.Current = flex::State::SYNC1;
|
|
}
|
|
state.fiwcount = 0;
|
|
fiw.rawdata = 0;
|
|
break;
|
|
}
|
|
case flex::State::FIW: {
|
|
state.fiwcount++;
|
|
if (state.fiwcount >= 16) {
|
|
read_2fsk(sym_rectified, &fiw.rawdata);
|
|
}
|
|
if (state.fiwcount == 48) {
|
|
if (decode_fiw() == 0) {
|
|
state.sync2_count = 0;
|
|
demodulator.baud = sync.baud;
|
|
state.Current = flex::State::SYNC2;
|
|
send_debug("FIW OK", fiw.frameno, fiw.cycleno);
|
|
} else {
|
|
state.Current = flex::State::SYNC1;
|
|
send_debug("FIW Fail", fiw.rawdata, 0);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case flex::State::SYNC2: {
|
|
if (++state.sync2_count == sync.baud * 25 / 1000) {
|
|
state.data_count = 0;
|
|
// Clear phase data
|
|
for (int i = 0; i < 88; i++) {
|
|
data.PhaseA.buf[i] = 0;
|
|
data.PhaseB.buf[i] = 0;
|
|
data.PhaseC.buf[i] = 0;
|
|
data.PhaseD.buf[i] = 0;
|
|
}
|
|
data.PhaseA.idle_count = 0;
|
|
data.PhaseB.idle_count = 0;
|
|
data.PhaseC.idle_count = 0;
|
|
data.PhaseD.idle_count = 0;
|
|
data.phase_toggle = 0;
|
|
data.data_bit_counter = 0;
|
|
|
|
state.Current = flex::State::DATA;
|
|
}
|
|
break;
|
|
}
|
|
case flex::State::DATA: {
|
|
int idle = read_data(sym_rectified);
|
|
if (++state.data_count == sync.baud * 1760 / 1000 || idle) {
|
|
decode_data();
|
|
demodulator.baud = 1600;
|
|
state.Current = flex::State::SYNC1;
|
|
state.data_count = 0;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void FlexProcessor::decode_data() {
|
|
if (sync.baud == 1600) {
|
|
if (sync.levels == 2) {
|
|
decode_phase('A');
|
|
} else {
|
|
decode_phase('A');
|
|
decode_phase('B');
|
|
}
|
|
} else {
|
|
if (sync.levels == 2) {
|
|
decode_phase('A');
|
|
decode_phase('C');
|
|
} else {
|
|
decode_phase('A');
|
|
decode_phase('B');
|
|
decode_phase('C');
|
|
decode_phase('D');
|
|
}
|
|
}
|
|
}
|
|
|
|
void FlexProcessor::decode_phase(char PhaseNo) {
|
|
uint32_t* phaseptr = nullptr;
|
|
switch (PhaseNo) {
|
|
case 'A':
|
|
phaseptr = data.PhaseA.buf;
|
|
break;
|
|
case 'B':
|
|
phaseptr = data.PhaseB.buf;
|
|
break;
|
|
case 'C':
|
|
phaseptr = data.PhaseC.buf;
|
|
break;
|
|
case 'D':
|
|
phaseptr = data.PhaseD.buf;
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
for (int i = 0; i < 88; i++) {
|
|
int decode_error = bch_fix_errors(&phaseptr[i]);
|
|
if (decode_error > 2) return;
|
|
phaseptr[i] &= 0x001FFFFF; // Extract message bits
|
|
}
|
|
|
|
uint32_t biw = phaseptr[0];
|
|
if (biw == 0 || biw == 0x001FFFFF) return;
|
|
|
|
int voffset = (biw >> 10) & 0x3f;
|
|
int aoffset = ((biw >> 8) & 0x03) + 1;
|
|
|
|
for (int i = aoffset; i < voffset; i++) {
|
|
int j = voffset + i - aoffset;
|
|
if (phaseptr[i] == 0x00000000 || phaseptr[i] == 0x001FFFFF) continue;
|
|
|
|
parse_capcode(phaseptr[i]);
|
|
if (decode.long_address) continue; // Skip long addresses for now
|
|
|
|
if (decode.capcode > 4297068542ll || decode.capcode < 0) continue;
|
|
|
|
uint32_t viw = phaseptr[j];
|
|
int type_val = (viw >> 4) & 0x07;
|
|
|
|
switch (type_val) {
|
|
case 0:
|
|
decode.type = flex::PageType::SECURE;
|
|
break;
|
|
case 1:
|
|
decode.type = flex::PageType::SHORT_INSTRUCTION;
|
|
break;
|
|
case 2:
|
|
decode.type = flex::PageType::TONE;
|
|
break;
|
|
case 3:
|
|
decode.type = flex::PageType::STANDARD_NUMERIC;
|
|
break;
|
|
case 4:
|
|
decode.type = flex::PageType::SPECIAL_NUMERIC;
|
|
break;
|
|
case 5:
|
|
decode.type = flex::PageType::ALPHANUMERIC;
|
|
break;
|
|
case 6:
|
|
decode.type = flex::PageType::BINARY;
|
|
break;
|
|
case 7:
|
|
decode.type = flex::PageType::NUMBERED_NUMERIC;
|
|
break;
|
|
}
|
|
|
|
int mw1 = (viw >> 7) & 0x7F;
|
|
int len = (viw >> 14) & 0x7F;
|
|
int mw2 = mw1 + (len - 1);
|
|
|
|
if (mw1 == 0 && mw2 == 0) continue;
|
|
if (decode.type == flex::PageType::TONE) mw1 = mw2 = 0;
|
|
|
|
if (decode.type == flex::PageType::ALPHANUMERIC || decode.type == flex::PageType::SECURE) {
|
|
if (mw1 > 87 || mw2 > 87) continue;
|
|
parse_alphanumeric(phaseptr, PhaseNo, mw1, mw2, 0);
|
|
} else if (decode.type == flex::PageType::STANDARD_NUMERIC || decode.type == flex::PageType::SPECIAL_NUMERIC || decode.type == flex::PageType::NUMBERED_NUMERIC) {
|
|
parse_numeric(phaseptr, PhaseNo, j);
|
|
} else if (decode.type == flex::PageType::TONE) {
|
|
parse_tone_only(phaseptr, PhaseNo, j);
|
|
} else {
|
|
// Unknown or unsupported
|
|
}
|
|
}
|
|
}
|
|
|
|
void FlexProcessor::parse_capcode(uint32_t aw1) {
|
|
decode.long_address = (aw1 < 0x008001L) || (aw1 > 0x1E0000L) || (aw1 > 0x1E7FFEL);
|
|
decode.capcode = aw1 - 0x8000;
|
|
}
|
|
|
|
void FlexProcessor::parse_alphanumeric(uint32_t* phaseptr, char, int mw1, int mw2, int) {
|
|
char message[128] = {0}; // Fixed buffer for message
|
|
int currentChar = 0;
|
|
|
|
// int frag = (phaseptr[mw1] >> 11) & 0x03;
|
|
// int cont = (phaseptr[mw1] >> 0x0A) & 0x01;
|
|
// Helper logic for fragmentation (ignored for basic display)
|
|
|
|
mw1++;
|
|
|
|
for (int i = mw1; i <= mw2; i++) {
|
|
unsigned int dw = phaseptr[i];
|
|
unsigned char ch;
|
|
|
|
// Extract chars (7-bit ASCII)
|
|
// If i > mw1 (not first word) or fragment check (simplified here)
|
|
if (i > mw1) {
|
|
ch = dw & 0x7F;
|
|
if (ch != 0x03 && currentChar < 127) message[currentChar++] = ch;
|
|
}
|
|
|
|
ch = (dw >> 7) & 0x7F;
|
|
if (ch != 0x03 && currentChar < 127) message[currentChar++] = ch;
|
|
|
|
ch = (dw >> 14) & 0x7F;
|
|
if (ch != 0x03 && currentChar < 127) message[currentChar++] = ch;
|
|
}
|
|
message[currentChar] = '\0';
|
|
|
|
flex::FlexPacket packet;
|
|
packet.bitrate = sync.baud;
|
|
packet.capcode = decode.capcode;
|
|
packet.function = 0; // TODO extract function if available
|
|
packet.type = 5; // ALPHANUMERIC
|
|
packet.status = 0; // OK
|
|
memcpy(packet.message, message, currentChar + 1);
|
|
|
|
send_packet(packet);
|
|
}
|
|
|
|
void FlexProcessor::parse_numeric(uint32_t* phaseptr, char, int j) {
|
|
// Simplified numeric parsing
|
|
char message[128] = {0};
|
|
const char flex_bcd[] = "0123456789 U -][";
|
|
|
|
int w1 = phaseptr[j] >> 7;
|
|
int w2 = w1 >> 7;
|
|
w1 = w1 & 0x7f;
|
|
w2 = (w2 & 0x07) + w1;
|
|
|
|
int dw;
|
|
// Handle short vs long logic if needed (simplified)
|
|
dw = phaseptr[w1];
|
|
w1++;
|
|
w2++;
|
|
|
|
unsigned char digit = 0;
|
|
int count = 4; // Standard numeric skip
|
|
if (decode.type == flex::PageType::NUMBERED_NUMERIC)
|
|
count += 10;
|
|
else
|
|
count += 2;
|
|
|
|
int idx = 0;
|
|
for (int i = w1; i <= w2; i++) {
|
|
for (int k = 0; k < 21; k++) {
|
|
digit = (digit >> 1) & 0x0F;
|
|
if (dw & 0x01) digit ^= 0x08;
|
|
dw >>= 1;
|
|
if (--count == 0) {
|
|
if (digit != 0x0C && idx < 127) {
|
|
message[idx++] = flex_bcd[digit];
|
|
}
|
|
count = 4;
|
|
}
|
|
}
|
|
dw = phaseptr[i];
|
|
}
|
|
message[idx] = '\0';
|
|
|
|
flex::FlexPacket packet;
|
|
packet.bitrate = sync.baud;
|
|
packet.capcode = decode.capcode;
|
|
packet.function = 0;
|
|
packet.type = 3; // NUMERIC
|
|
packet.status = 0;
|
|
memcpy(packet.message, message, idx + 1);
|
|
|
|
send_packet(packet);
|
|
}
|
|
|
|
void FlexProcessor::parse_tone_only(uint32_t*, char, int) {
|
|
flex::FlexPacket packet;
|
|
packet.bitrate = sync.baud;
|
|
packet.capcode = decode.capcode;
|
|
packet.function = 0;
|
|
packet.type = 2; // TONE
|
|
packet.status = 0;
|
|
snprintf(packet.message, sizeof(packet.message), "Tone Only");
|
|
|
|
send_packet(packet);
|
|
}
|
|
|
|
void FlexProcessor::parse_unknown(uint32_t*, char, int, int) {
|
|
// Ignored
|
|
}
|
|
|
|
void FlexProcessor::on_message(const Message* const message) {
|
|
if (message->id == Message::ID::FlexConfigure) {
|
|
configure();
|
|
}
|
|
}
|
|
|
|
void FlexProcessor::configure() {
|
|
decim_0_iq.configure(taps_11k0_decim_0.taps);
|
|
decim_1_iq.configure(taps_11k0_decim_1.taps);
|
|
channel_filter.configure(taps_11k0_channel.taps, 2); // Decim 2 -> 24kHz output
|
|
|
|
demod.configure(24000, 4800);
|
|
demodulator.sample_freq = 24000;
|
|
|
|
configured = true;
|
|
send_debug("Configured", 0, 0);
|
|
}
|
|
|
|
void FlexProcessor::send_packet(const flex::FlexPacket& packet) {
|
|
FlexPacketMessage message(packet);
|
|
shared_memory.application_queue.push(message);
|
|
}
|
|
|
|
void FlexProcessor::send_stats() {
|
|
// Stats
|
|
}
|
|
|
|
int main() {
|
|
EventDispatcher event_dispatcher{std::make_unique<FlexProcessor>()};
|
|
event_dispatcher.run();
|
|
return 0;
|
|
}
|