mirror of
https://github.com/AlexandreRouma/SDRPlusPlus.git
synced 2025-01-13 11:47:13 +01:00
beginning of pager decoder
This commit is contained in:
parent
4b6835141e
commit
3fc893568a
@ -43,6 +43,7 @@ option(OPT_BUILD_FALCON9_DECODER "Build the falcon9 live decoder (Dependencies:
|
||||
option(OPT_BUILD_KG_SSTV_DECODER "Build the KG SSTV (KG-STV) decoder module (no dependencies required)" OFF)
|
||||
option(OPT_BUILD_M17_DECODER "Build the M17 decoder module (Dependencies: codec2)" OFF)
|
||||
option(OPT_BUILD_METEOR_DEMODULATOR "Build the meteor demodulator module (no dependencies required)" ON)
|
||||
option(OPT_BUILD_PAGER_DECODER "Build the pager decoder module (no dependencies required)" OFF)
|
||||
option(OPT_BUILD_RADIO "Main audio modulation decoder (AM, FM, SSB, etc...)" ON)
|
||||
option(OPT_BUILD_WEATHER_SAT_DECODER "Build the HRPT decoder module (no dependencies required)" OFF)
|
||||
|
||||
@ -234,6 +235,10 @@ if (OPT_BUILD_METEOR_DEMODULATOR)
|
||||
add_subdirectory("decoder_modules/meteor_demodulator")
|
||||
endif (OPT_BUILD_METEOR_DEMODULATOR)
|
||||
|
||||
if (OPT_BUILD_PAGER_DECODER)
|
||||
add_subdirectory("decoder_modules/pager_decoder")
|
||||
endif (OPT_BUILD_PAGER_DECODER)
|
||||
|
||||
if (OPT_BUILD_RADIO)
|
||||
add_subdirectory("decoder_modules/radio")
|
||||
endif (OPT_BUILD_RADIO)
|
||||
@ -242,6 +247,7 @@ if (OPT_BUILD_WEATHER_SAT_DECODER)
|
||||
add_subdirectory("decoder_modules/weather_sat_decoder")
|
||||
endif (OPT_BUILD_WEATHER_SAT_DECODER)
|
||||
|
||||
add_subdirectory("decoder_modules/pager_decoder")
|
||||
|
||||
# Misc
|
||||
if (OPT_BUILD_DISCORD_PRESENCE)
|
||||
@ -302,7 +308,7 @@ if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
|
||||
add_custom_target(do_always ALL cp \"$<TARGET_FILE_DIR:sdrpp_core>/libsdrpp_core.dylib\" \"$<TARGET_FILE_DIR:sdrpp>\")
|
||||
endif ()
|
||||
|
||||
# cmake .. "-DCMAKE_TOOLCHAIN_FILE=C:/dev/vcpkg/scripts/buildsystems/vcpkg.cmake" -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_SCANNER=ON -DOPT_BUILD_SCHEDULER=ON -DOPT_BUILD_USRP_SOURCE=ON
|
||||
# cmake .. "-DCMAKE_TOOLCHAIN_FILE=C:/dev/vcpkg/scripts/buildsystems/vcpkg.cmake" -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_SCANNER=ON -DOPT_BUILD_SCHEDULER=ON -DOPT_BUILD_USRP_SOURCE=ON -DOPT_BUILD_PAGER_DECODER=ON
|
||||
|
||||
# Create module cmake file
|
||||
configure_file(${CMAKE_SOURCE_DIR}/sdrpp_module.cmake ${CMAKE_CURRENT_BINARY_DIR}/sdrpp_module.cmake @ONLY)
|
||||
|
8
decoder_modules/pager_decoder/CMakeLists.txt
Normal file
8
decoder_modules/pager_decoder/CMakeLists.txt
Normal file
@ -0,0 +1,8 @@
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
project(pager_decoder)
|
||||
|
||||
file(GLOB_RECURSE SRC "src/*.cpp" "src/*.c")
|
||||
|
||||
include(${SDRPP_MODULE_CMAKE})
|
||||
|
||||
target_include_directories(pager_decoder PRIVATE "src/")
|
8
decoder_modules/pager_decoder/src/decoder.h
Normal file
8
decoder_modules/pager_decoder/src/decoder.h
Normal file
@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
class Decoder {
|
||||
public:
|
||||
|
||||
virtual void showMenu();
|
||||
|
||||
};
|
255
decoder_modules/pager_decoder/src/main.cpp
Normal file
255
decoder_modules/pager_decoder/src/main.cpp
Normal file
@ -0,0 +1,255 @@
|
||||
#include <imgui.h>
|
||||
#include <config.h>
|
||||
#include <core.h>
|
||||
#include <gui/style.h>
|
||||
#include <gui/gui.h>
|
||||
#include <signal_path/signal_path.h>
|
||||
#include <module.h>
|
||||
#include <filesystem>
|
||||
#include <dsp/stream.h>
|
||||
#include <dsp/buffer/reshaper.h>
|
||||
#include <dsp/multirate/rational_resampler.h>
|
||||
#include <dsp/sink/handler_sink.h>
|
||||
#include <gui/widgets/folder_select.h>
|
||||
#include <gui/widgets/symbol_diagram.h>
|
||||
#include <fstream>
|
||||
#include <chrono>
|
||||
#include <dsp/demod/quadrature.h>
|
||||
#include <dsp/clock_recovery/mm.h>
|
||||
#include <dsp/taps/root_raised_cosine.h>
|
||||
#include <dsp/correction/dc_blocker.h>
|
||||
#include <dsp/loop/fast_agc.h>
|
||||
#include <utils/optionlist.h>
|
||||
#include <dsp/digital/binary_slicer.h>
|
||||
#include <dsp/routing/doubler.h>
|
||||
#include "pocsag/pocsag.h"
|
||||
|
||||
#define CONCAT(a, b) ((std::string(a) + b).c_str())
|
||||
|
||||
SDRPP_MOD_INFO{
|
||||
/* Name: */ "pager_decoder",
|
||||
/* Description: */ "POCSAG and Flex Pager Decoder"
|
||||
/* Author: */ "Ryzerth",
|
||||
/* Version: */ 0, 1, 0,
|
||||
/* Max instances */ -1
|
||||
};
|
||||
|
||||
const char* msgTypes[] = {
|
||||
"Numeric",
|
||||
"Unknown (0b01)",
|
||||
"Unknown (0b10)",
|
||||
"Alphanumeric",
|
||||
};
|
||||
|
||||
ConfigManager config;
|
||||
|
||||
#define INPUT_SAMPLE_RATE 24000.0
|
||||
#define INPUT_BANDWIDTH 12500.0
|
||||
#define INPUT_BAUD_RATE 2400.0
|
||||
|
||||
enum Protocol {
|
||||
PROTOCOL_POCSAG,
|
||||
PROTOCOL_FLEX
|
||||
};
|
||||
|
||||
class PagerDecoderModule : public ModuleManager::Instance {
|
||||
public:
|
||||
PagerDecoderModule(std::string name) : diag(0.6, 2400) {
|
||||
this->name = name;
|
||||
|
||||
// Define protocols
|
||||
protocols.define("POCSAG", PROTOCOL_POCSAG);
|
||||
protocols.define("FLEX", PROTOCOL_FLEX);
|
||||
|
||||
// Load config
|
||||
config.acquire();
|
||||
if (!config.conf.contains(name)) {
|
||||
config.conf[name]["showLines"] = false;
|
||||
}
|
||||
showLines = config.conf[name]["showLines"];
|
||||
if (showLines) {
|
||||
diag.lines.push_back(-1.0);
|
||||
diag.lines.push_back(-1.0/3.0);
|
||||
diag.lines.push_back(1.0/3.0);
|
||||
diag.lines.push_back(1.0);
|
||||
}
|
||||
config.release(true);
|
||||
|
||||
// Initialize VFO
|
||||
vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, 0, INPUT_BANDWIDTH, INPUT_SAMPLE_RATE, INPUT_BANDWIDTH, INPUT_BANDWIDTH, true);
|
||||
vfo->setSnapInterval(1);
|
||||
|
||||
// Initialize DSP here (negative dev to invert signal)
|
||||
demod.init(vfo->output, -4500.0, INPUT_SAMPLE_RATE);
|
||||
dcBlock.init(&demod.out, 0.001);
|
||||
float taps[] = { 0.1f, 0.1f, 0.1f, 0.1f, 0.1f, 0.1f, 0.1f, 0.1f, 0.1f, 0.1f };
|
||||
shape = dsp::taps::fromArray<float>(10, taps);
|
||||
fir.init(&dcBlock.out, shape);
|
||||
recov.init(&fir.out, INPUT_SAMPLE_RATE/INPUT_BAUD_RATE, 1e5, 0.1, 0.05);
|
||||
doubler.init(&recov.out);
|
||||
slicer.init(&doubler.outB);
|
||||
dataHandler.init(&slicer.out, _dataHandler, this);
|
||||
reshape.init(&doubler.outA, 2400.0, (INPUT_BAUD_RATE / 30.0) - 2400.0);
|
||||
diagHandler.init(&reshape.out, _diagHandler, this);
|
||||
|
||||
// Initialize decode
|
||||
decoder.onMessage.bind(&PagerDecoderModule::messageHandler, this);
|
||||
|
||||
// Start DSP Here
|
||||
demod.start();
|
||||
dcBlock.start();
|
||||
fir.start();
|
||||
recov.start();
|
||||
doubler.start();
|
||||
slicer.start();
|
||||
dataHandler.start();
|
||||
reshape.start();
|
||||
diagHandler.start();
|
||||
|
||||
gui::menu.registerEntry(name, menuHandler, this, this);
|
||||
}
|
||||
|
||||
~PagerDecoderModule() {
|
||||
gui::menu.removeEntry(name);
|
||||
// Stop DSP
|
||||
if (enabled) {
|
||||
demod.stop();
|
||||
dcBlock.stop();
|
||||
fir.stop();
|
||||
recov.stop();
|
||||
doubler.stop();
|
||||
slicer.stop();
|
||||
dataHandler.stop();
|
||||
reshape.stop();
|
||||
diagHandler.stop();
|
||||
sigpath::vfoManager.deleteVFO(vfo);
|
||||
}
|
||||
|
||||
sigpath::sinkManager.unregisterStream(name);
|
||||
}
|
||||
|
||||
void postInit() {}
|
||||
|
||||
void enable() {
|
||||
double bw = gui::waterfall.getBandwidth();
|
||||
vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, std::clamp<double>(0, -bw / 2.0, bw / 2.0), INPUT_BANDWIDTH, INPUT_SAMPLE_RATE, INPUT_BANDWIDTH, INPUT_BANDWIDTH, true);
|
||||
vfo->setSnapInterval(250);
|
||||
|
||||
// Start DSP
|
||||
demod.start();
|
||||
dcBlock.start();
|
||||
fir.start();
|
||||
recov.start();
|
||||
doubler.start();
|
||||
slicer.start();
|
||||
dataHandler.start();
|
||||
reshape.start();
|
||||
diagHandler.start();
|
||||
|
||||
enabled = true;
|
||||
}
|
||||
|
||||
void disable() {
|
||||
demod.stop();
|
||||
dcBlock.stop();
|
||||
fir.stop();
|
||||
recov.stop();
|
||||
doubler.stop();
|
||||
slicer.stop();
|
||||
dataHandler.stop();
|
||||
reshape.stop();
|
||||
diagHandler.stop();
|
||||
reshape.stop();
|
||||
diagHandler.stop();
|
||||
|
||||
sigpath::vfoManager.deleteVFO(vfo);
|
||||
enabled = false;
|
||||
}
|
||||
|
||||
bool isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
private:
|
||||
static void menuHandler(void* ctx) {
|
||||
PagerDecoderModule* _this = (PagerDecoderModule*)ctx;
|
||||
|
||||
float menuWidth = ImGui::GetContentRegionAvail().x;
|
||||
|
||||
if (!_this->enabled) { style::beginDisabled(); }
|
||||
|
||||
ImGui::LeftLabel("Protocol");
|
||||
ImGui::FillWidth();
|
||||
if (ImGui::Combo(("##pager_decoder_proto_" + _this->name).c_str(), &_this->protoId, _this->protocols.txt)) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
ImGui::SetNextItemWidth(menuWidth);
|
||||
_this->diag.draw();
|
||||
|
||||
if (!_this->enabled) { style::endDisabled(); }
|
||||
}
|
||||
|
||||
static void _dataHandler(uint8_t* data, int count, void* ctx) {
|
||||
PagerDecoderModule* _this = (PagerDecoderModule*)ctx;
|
||||
_this->decoder.process(data, count);
|
||||
}
|
||||
|
||||
static void _diagHandler(float* data, int count, void* ctx) {
|
||||
PagerDecoderModule* _this = (PagerDecoderModule*)ctx;
|
||||
float* buf = _this->diag.acquireBuffer();
|
||||
memcpy(buf, data, count * sizeof(float));
|
||||
_this->diag.releaseBuffer();
|
||||
}
|
||||
|
||||
void messageHandler(pocsag::Address addr, pocsag::MessageType type, const std::string& msg) {
|
||||
flog::debug("[{}]: '{}'", (uint32_t)addr, msg);
|
||||
}
|
||||
|
||||
std::string name;
|
||||
bool enabled = true;
|
||||
|
||||
int protoId = 0;
|
||||
|
||||
OptionList<std::string, Protocol> protocols;
|
||||
|
||||
pocsag::Decoder decoder;
|
||||
|
||||
// DSP Chain
|
||||
VFOManager::VFO* vfo;
|
||||
dsp::demod::Quadrature demod;
|
||||
dsp::correction::DCBlocker<float> dcBlock;
|
||||
dsp::tap<float> shape;
|
||||
dsp::filter::FIR<float, float> fir;
|
||||
dsp::clock_recovery::MM<float> recov;
|
||||
dsp::routing::Doubler<float> doubler;
|
||||
dsp::digital::BinarySlicer slicer;
|
||||
dsp::buffer::Reshaper<float> reshape;
|
||||
dsp::sink::Handler<uint8_t> dataHandler;
|
||||
dsp::sink::Handler<float> diagHandler;
|
||||
|
||||
ImGui::SymbolDiagram diag;
|
||||
|
||||
bool showLines = false;
|
||||
};
|
||||
|
||||
MOD_EXPORT void _INIT_() {
|
||||
// Create default recording directory
|
||||
json def = json({});
|
||||
config.setPath(core::args["root"].s() + "/pager_decoder_config.json");
|
||||
config.load(def);
|
||||
config.enableAutoSave();
|
||||
}
|
||||
|
||||
MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) {
|
||||
return new PagerDecoderModule(name);
|
||||
}
|
||||
|
||||
MOD_EXPORT void _DELETE_INSTANCE_(void* instance) {
|
||||
delete (PagerDecoderModule*)instance;
|
||||
}
|
||||
|
||||
MOD_EXPORT void _END_() {
|
||||
config.disableAutoSave();
|
||||
config.save();
|
||||
}
|
32
decoder_modules/pager_decoder/src/pocsag/decoder.h
Normal file
32
decoder_modules/pager_decoder/src/pocsag/decoder.h
Normal file
@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
#include "../decoder.h"
|
||||
#include <utils/optionlist.h>
|
||||
#include <gui/widgets/symbol_diagram.h>
|
||||
#include <gui/style.h>
|
||||
|
||||
class POCSAGDecoder : public Decoder {
|
||||
public:
|
||||
POCSAGDecoder() : diag(0.6, 2400) {
|
||||
// Define baudrate options
|
||||
baudrates.define(512, "512 Baud", 512);
|
||||
baudrates.define(1200, "1200 Baud", 1200);
|
||||
baudrates.define(2400, "2400 Baud", 2400);
|
||||
}
|
||||
|
||||
void showMenu() {
|
||||
ImGui::LeftLabel("Baudrate");
|
||||
ImGui::FillWidth();
|
||||
if (ImGui::Combo(("##pager_decoder_proto_" + name).c_str(), &brId, baudrates.txt)) {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::string name;
|
||||
|
||||
ImGui::SymbolDiagram diag;
|
||||
|
||||
int brId = 2;
|
||||
|
||||
OptionList<int, int> baudrates;
|
||||
};
|
71
decoder_modules/pager_decoder/src/pocsag/dsp.h
Normal file
71
decoder_modules/pager_decoder/src/pocsag/dsp.h
Normal file
@ -0,0 +1,71 @@
|
||||
#pragma once
|
||||
#include <dsp/stream.h>
|
||||
#include <dsp/buffer/reshaper.h>
|
||||
#include <dsp/multirate/rational_resampler.h>
|
||||
#include <dsp/sink/handler_sink.h>
|
||||
#include <dsp/demod/quadrature.h>
|
||||
#include <dsp/clock_recovery/mm.h>
|
||||
#include <dsp/taps/root_raised_cosine.h>
|
||||
#include <dsp/correction/dc_blocker.h>
|
||||
#include <dsp/loop/fast_agc.h>
|
||||
#include <dsp/digital/binary_slicer.h>
|
||||
#include <dsp/routing/doubler.h>
|
||||
|
||||
class POCSAGDSP : dsp::Processor<dsp::complex_t, uint8_t> {
|
||||
using base_type = dsp::Processor<dsp::complex_t, uint8_t>;
|
||||
public:
|
||||
POCSAGDSP() {}
|
||||
POCSAGDSP(dsp::stream<dsp::complex_t>* in, double samplerate, double baudrate) { init(in, samplerate, baudrate); }
|
||||
|
||||
void init(dsp::stream<dsp::complex_t>* in, double samplerate, double baudrate) {
|
||||
// Save settings
|
||||
// TODO
|
||||
|
||||
// Configure blocks
|
||||
demod.init(NULL, -4500.0, samplerate);
|
||||
dcBlock.init(NULL, 0.001);
|
||||
float taps[] = { 0.1f, 0.1f, 0.1f, 0.1f, 0.1f, 0.1f, 0.1f, 0.1f, 0.1f, 0.1f };
|
||||
shape = dsp::taps::fromArray<float>(10, taps);
|
||||
fir.init(NULL, shape);
|
||||
recov.init(NULL, samplerate/baudrate, 1e5, 0.1, 0.05);
|
||||
|
||||
// Free useless buffers
|
||||
dcBlock.out.free();
|
||||
fir.out.free();
|
||||
recov.out.free();
|
||||
|
||||
// Init base
|
||||
base_type::init(in);
|
||||
}
|
||||
|
||||
int process(int count, dsp::complex_t* in, float* softOut, uint8_t* out) {
|
||||
count = demod.process(count, in, demod.out.readBuf);
|
||||
count = dcBlock.process(count, demod.out.readBuf, demod.out.readBuf);
|
||||
count = fir.process(count, demod.out.readBuf, demod.out.readBuf);
|
||||
count = recov.process(count, demod.out.readBuf, softOut);
|
||||
dsp::digital::BinarySlicer::process(count, softOut, out);
|
||||
return count;
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count = base_type::_in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
count = process(count, base_type::_in->readBuf, soft.writeBuf, base_type::out.writeBuf);
|
||||
|
||||
base_type::_in->flush();
|
||||
if (!base_type::out.swap(count)) { return -1; }
|
||||
if (!soft.swap(count)) { return -1; }
|
||||
return count;
|
||||
}
|
||||
|
||||
dsp::stream<float> soft;
|
||||
|
||||
private:
|
||||
dsp::demod::Quadrature demod;
|
||||
dsp::correction::DCBlocker<float> dcBlock;
|
||||
dsp::tap<float> shape;
|
||||
dsp::filter::FIR<float, float> fir;
|
||||
dsp::clock_recovery::MM<float> recov;
|
||||
|
||||
};
|
140
decoder_modules/pager_decoder/src/pocsag/pocsag.cpp
Normal file
140
decoder_modules/pager_decoder/src/pocsag/pocsag.cpp
Normal file
@ -0,0 +1,140 @@
|
||||
#include "pocsag.h"
|
||||
#include <string.h>
|
||||
#include <utils/flog.h>
|
||||
|
||||
#define POCSAG_FRAME_SYNC_CODEWORD ((uint32_t)(0b01111100110100100001010111011000))
|
||||
#define POCSAG_IDLE_CODEWORD_DATA ((uint32_t)(0b011110101100100111000))
|
||||
#define POCSAG_BATCH_BIT_COUNT (POCSAG_BATCH_CODEWORD_COUNT*32)
|
||||
|
||||
#define POCSAG_GEN_POLY ((uint32_t)(0b11101101001))
|
||||
|
||||
namespace pocsag {
|
||||
const char NUMERIC_CHARSET[] = {
|
||||
'0',
|
||||
'1',
|
||||
'2',
|
||||
'3',
|
||||
'4',
|
||||
'5',
|
||||
'6',
|
||||
'7',
|
||||
'8',
|
||||
'9',
|
||||
'*',
|
||||
'U',
|
||||
' ',
|
||||
'-',
|
||||
']',
|
||||
'['
|
||||
};
|
||||
|
||||
void Decoder::process(uint8_t* symbols, int count) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
// Get symbol
|
||||
uint32_t s = symbols[i];
|
||||
|
||||
// If not sync, try to acquire sync (TODO: sync confidence)
|
||||
if (!synced) {
|
||||
// Append new symbol to sync shift register
|
||||
syncSR = (syncSR << 1) | s;
|
||||
|
||||
// Test for sync
|
||||
synced = (distance(syncSR, POCSAG_FRAME_SYNC_CODEWORD) <= POCSAG_SYNC_DIST);
|
||||
|
||||
// Go to next symbol
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO: Flush message on desync
|
||||
|
||||
// Append bit to batch
|
||||
batch[batchOffset >> 5] |= (s << (31 - (batchOffset & 0b11111)));
|
||||
batchOffset++;
|
||||
|
||||
// On end of batch, decode and reset
|
||||
if (batchOffset >= POCSAG_BATCH_BIT_COUNT) {
|
||||
decodeBatch();
|
||||
batchOffset = 0;
|
||||
synced = false;
|
||||
memset(batch, 0, sizeof(batch));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int Decoder::distance(uint32_t a, uint32_t b) {
|
||||
uint32_t diff = a ^ b;
|
||||
int dist = 0;
|
||||
for (int i = 0; i < 32; i++) {
|
||||
dist += (diff >> i ) & 1;
|
||||
}
|
||||
return dist;
|
||||
}
|
||||
|
||||
bool Decoder::correctCodeword(Codeword in, Codeword& out) {
|
||||
|
||||
|
||||
return true; // TODO
|
||||
}
|
||||
|
||||
void Decoder::flushMessage() {
|
||||
if (!msg.empty()) {
|
||||
onMessage(addr, msgType, msg);
|
||||
msg.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void Decoder::decodeBatch() {
|
||||
for (int i = 0; i < POCSAG_BATCH_CODEWORD_COUNT; i++) {
|
||||
// Get codeword
|
||||
Codeword cw = batch[i];
|
||||
|
||||
// Correct errors. If corrupted, skip
|
||||
if (!correctCodeword(cw, cw)) { continue; }
|
||||
// TODO: End message if two consecutive are corrupt
|
||||
|
||||
// Get codeword type
|
||||
CodewordType type = (CodewordType)((cw >> 31) & 1);
|
||||
if (type == CODEWORD_TYPE_ADDRESS && (cw >> 11) == POCSAG_IDLE_CODEWORD_DATA) {
|
||||
type = CODEWORD_TYPE_IDLE;
|
||||
}
|
||||
|
||||
// Decode codeword
|
||||
if (type == CODEWORD_TYPE_IDLE) {
|
||||
// If a non-empty message is available, send it out and clear
|
||||
flushMessage();
|
||||
flog::debug("[{}:{}]: IDLE", (i >> 1), i&1);
|
||||
}
|
||||
else if (type == CODEWORD_TYPE_ADDRESS) {
|
||||
// If a non-empty message is available, send it out and clear
|
||||
flushMessage();
|
||||
|
||||
// Decode message type
|
||||
msgType = (MessageType)((cw >> 11) & 0b11);
|
||||
|
||||
// Decode address and append lower 8 bits from position
|
||||
addr = ((cw >> 13) & 0b111111111111111111) << 3;
|
||||
addr |= (i >> 1);
|
||||
}
|
||||
else if (type == CODEWORD_TYPE_MESSAGE) {
|
||||
// Extract the 20 data bits
|
||||
uint32_t data = (cw >> 11) & 0b11111111111111111111;
|
||||
|
||||
// Decode data depending on message type
|
||||
if (msgType == MESSAGE_TYPE_NUMERIC) {
|
||||
// Numeric messages pack 5 characters per message codeword
|
||||
msg += NUMERIC_CHARSET[(data >> 16) & 0b1111];
|
||||
msg += NUMERIC_CHARSET[(data >> 12) & 0b1111];
|
||||
msg += NUMERIC_CHARSET[(data >> 8) & 0b1111];
|
||||
msg += NUMERIC_CHARSET[(data >> 4) & 0b1111];
|
||||
msg += NUMERIC_CHARSET[data & 0b1111];
|
||||
}
|
||||
else if (msgType == MESSAGE_TYPE_ALPHANUMERIC) {
|
||||
|
||||
}
|
||||
|
||||
// Save last data
|
||||
lastMsgData = data;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
48
decoder_modules/pager_decoder/src/pocsag/pocsag.h
Normal file
48
decoder_modules/pager_decoder/src/pocsag/pocsag.h
Normal file
@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <stdint.h>
|
||||
#include <utils/new_event.h>
|
||||
|
||||
#define POCSAG_SYNC_DIST 4
|
||||
#define POCSAG_BATCH_CODEWORD_COUNT 16
|
||||
|
||||
namespace pocsag {
|
||||
enum CodewordType {
|
||||
CODEWORD_TYPE_IDLE = -1,
|
||||
CODEWORD_TYPE_ADDRESS = 0,
|
||||
CODEWORD_TYPE_MESSAGE = 1
|
||||
};
|
||||
|
||||
enum MessageType {
|
||||
MESSAGE_TYPE_NUMERIC = 0b00,
|
||||
MESSAGE_TYPE_ALPHANUMERIC = 0b11
|
||||
};
|
||||
|
||||
using Codeword = uint32_t;
|
||||
using Address = uint32_t;
|
||||
|
||||
class Decoder {
|
||||
public:
|
||||
void process(uint8_t* symbols, int count);
|
||||
|
||||
NewEvent<Address, MessageType, const std::string&> onMessage;
|
||||
|
||||
private:
|
||||
static int distance(uint32_t a, uint32_t b);
|
||||
bool correctCodeword(Codeword in, Codeword& out);
|
||||
void flushMessage();
|
||||
void decodeBatch();
|
||||
|
||||
uint32_t syncSR = 0;
|
||||
bool synced = false;
|
||||
int batchOffset = 0;
|
||||
|
||||
Codeword batch[POCSAG_BATCH_CODEWORD_COUNT];
|
||||
|
||||
Address addr;
|
||||
MessageType msgType;
|
||||
std::string msg;
|
||||
|
||||
uint32_t lastMsgData;
|
||||
};
|
||||
}
|
@ -334,7 +334,7 @@ Modules in beta are still included in releases for the most part but not enabled
|
||||
| hackrf_source | Working | libhackrf | OPT_BUILD_HACKRF_SOURCE | ✅ | ✅ | ✅ |
|
||||
| hermes_source | Beta | - | OPT_BUILD_HERMES_SOURCE | ✅ | ✅ | ✅ |
|
||||
| limesdr_source | Working | liblimesuite | OPT_BUILD_LIMESDR_SOURCE | ⛔ | ✅ | ✅ |
|
||||
| perseus_source | Beta | libperseus-sdr | OPT_BUILD_PERSEUS_SOURCE | ⛔ | ⛔ | ⛔ |
|
||||
| perseus_source | Beta | libperseus-sdr | OPT_BUILD_PERSEUS_SOURCE | ⛔ | ✅ | ✅ |
|
||||
| plutosdr_source | Working | libiio, libad9361 | OPT_BUILD_PLUTOSDR_SOURCE | ✅ | ✅ | ✅ |
|
||||
| rfspace_source | Working | - | OPT_BUILD_RFSPACE_SOURCE | ✅ | ✅ | ✅ |
|
||||
| rtl_sdr_source | Working | librtlsdr | OPT_BUILD_RTL_SDR_SOURCE | ✅ | ✅ | ✅ |
|
||||
@ -367,6 +367,7 @@ Modules in beta are still included in releases for the most part but not enabled
|
||||
| kgsstv_decoder | Unfinished | - | OPT_BUILD_KGSSTV_DECODER | ⛔ | ⛔ | ⛔ |
|
||||
| m17_decoder | Beta | - | OPT_BUILD_M17_DECODER | ⛔ | ✅ | ⛔ |
|
||||
| meteor_demodulator | Working | - | OPT_BUILD_METEOR_DEMODULATOR | ✅ | ✅ | ⛔ |
|
||||
| pager_decoder | Unfinished | - | OPT_BUILD_PAGER_DECODER | ⛔ | ⛔ | ⛔ |
|
||||
| radio | Working | - | OPT_BUILD_RADIO | ✅ | ✅ | ✅ |
|
||||
| weather_sat_decoder | Unfinished | - | OPT_BUILD_WEATHER_SAT_DECODER | ⛔ | ⛔ | ⛔ |
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user