mirror of
https://github.com/AlexandreRouma/SDRPlusPlus.git
synced 2025-01-27 18:14:44 +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_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_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_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_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)
|
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")
|
add_subdirectory("decoder_modules/meteor_demodulator")
|
||||||
endif (OPT_BUILD_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)
|
if (OPT_BUILD_RADIO)
|
||||||
add_subdirectory("decoder_modules/radio")
|
add_subdirectory("decoder_modules/radio")
|
||||||
endif (OPT_BUILD_RADIO)
|
endif (OPT_BUILD_RADIO)
|
||||||
@ -242,6 +247,7 @@ if (OPT_BUILD_WEATHER_SAT_DECODER)
|
|||||||
add_subdirectory("decoder_modules/weather_sat_decoder")
|
add_subdirectory("decoder_modules/weather_sat_decoder")
|
||||||
endif (OPT_BUILD_WEATHER_SAT_DECODER)
|
endif (OPT_BUILD_WEATHER_SAT_DECODER)
|
||||||
|
|
||||||
|
add_subdirectory("decoder_modules/pager_decoder")
|
||||||
|
|
||||||
# Misc
|
# Misc
|
||||||
if (OPT_BUILD_DISCORD_PRESENCE)
|
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>\")
|
add_custom_target(do_always ALL cp \"$<TARGET_FILE_DIR:sdrpp_core>/libsdrpp_core.dylib\" \"$<TARGET_FILE_DIR:sdrpp>\")
|
||||||
endif ()
|
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
|
# Create module cmake file
|
||||||
configure_file(${CMAKE_SOURCE_DIR}/sdrpp_module.cmake ${CMAKE_CURRENT_BINARY_DIR}/sdrpp_module.cmake @ONLY)
|
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 | ✅ | ✅ | ✅ |
|
| hackrf_source | Working | libhackrf | OPT_BUILD_HACKRF_SOURCE | ✅ | ✅ | ✅ |
|
||||||
| hermes_source | Beta | - | OPT_BUILD_HERMES_SOURCE | ✅ | ✅ | ✅ |
|
| hermes_source | Beta | - | OPT_BUILD_HERMES_SOURCE | ✅ | ✅ | ✅ |
|
||||||
| limesdr_source | Working | liblimesuite | OPT_BUILD_LIMESDR_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 | ✅ | ✅ | ✅ |
|
| plutosdr_source | Working | libiio, libad9361 | OPT_BUILD_PLUTOSDR_SOURCE | ✅ | ✅ | ✅ |
|
||||||
| rfspace_source | Working | - | OPT_BUILD_RFSPACE_SOURCE | ✅ | ✅ | ✅ |
|
| rfspace_source | Working | - | OPT_BUILD_RFSPACE_SOURCE | ✅ | ✅ | ✅ |
|
||||||
| rtl_sdr_source | Working | librtlsdr | OPT_BUILD_RTL_SDR_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 | ⛔ | ⛔ | ⛔ |
|
| kgsstv_decoder | Unfinished | - | OPT_BUILD_KGSSTV_DECODER | ⛔ | ⛔ | ⛔ |
|
||||||
| m17_decoder | Beta | - | OPT_BUILD_M17_DECODER | ⛔ | ✅ | ⛔ |
|
| m17_decoder | Beta | - | OPT_BUILD_M17_DECODER | ⛔ | ✅ | ⛔ |
|
||||||
| meteor_demodulator | Working | - | OPT_BUILD_METEOR_DEMODULATOR | ✅ | ✅ | ⛔ |
|
| meteor_demodulator | Working | - | OPT_BUILD_METEOR_DEMODULATOR | ✅ | ✅ | ⛔ |
|
||||||
|
| pager_decoder | Unfinished | - | OPT_BUILD_PAGER_DECODER | ⛔ | ⛔ | ⛔ |
|
||||||
| radio | Working | - | OPT_BUILD_RADIO | ✅ | ✅ | ✅ |
|
| radio | Working | - | OPT_BUILD_RADIO | ✅ | ✅ | ✅ |
|
||||||
| weather_sat_decoder | Unfinished | - | OPT_BUILD_WEATHER_SAT_DECODER | ⛔ | ⛔ | ⛔ |
|
| weather_sat_decoder | Unfinished | - | OPT_BUILD_WEATHER_SAT_DECODER | ⛔ | ⛔ | ⛔ |
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user