mirror of
https://github.com/AlexandreRouma/SDRPlusPlus.git
synced 2025-01-30 11:24:44 +01:00
add VOR receiver module
This commit is contained in:
parent
ea3675da47
commit
4799d0e3a8
@ -53,6 +53,7 @@ option(OPT_BUILD_METEOR_DEMODULATOR "Build the meteor demodulator module (no dep
|
||||
option(OPT_BUILD_PAGER_DECODER "Build the pager decoder module (no dependencies required)" ON)
|
||||
option(OPT_BUILD_RADIO "Main audio modulation decoder (AM, FM, SSB, etc...)" ON)
|
||||
option(OPT_BUILD_RYFI_DECODER "RyFi data link decoder" OFF)
|
||||
option(OPT_BUILD_VOR_RECEIVER "VOR beacon receiver" ON)
|
||||
option(OPT_BUILD_WEATHER_SAT_DECODER "Build the HRPT decoder module (no dependencies required)" OFF)
|
||||
|
||||
# Misc
|
||||
@ -289,6 +290,10 @@ if (OPT_BUILD_RYFI_DECODER)
|
||||
add_subdirectory("decoder_modules/ryfi_decoder")
|
||||
endif (OPT_BUILD_RYFI_DECODER)
|
||||
|
||||
if (OPT_BUILD_VOR_RECEIVER)
|
||||
add_subdirectory("decoder_modules/vor_receiver")
|
||||
endif (OPT_BUILD_VOR_RECEIVER)
|
||||
|
||||
if (OPT_BUILD_WEATHER_SAT_DECODER)
|
||||
add_subdirectory("decoder_modules/weather_sat_decoder")
|
||||
endif (OPT_BUILD_WEATHER_SAT_DECODER)
|
||||
|
8
decoder_modules/vor_receiver/CMakeLists.txt
Normal file
8
decoder_modules/vor_receiver/CMakeLists.txt
Normal file
@ -0,0 +1,8 @@
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
project(vor_receiver)
|
||||
|
||||
file(GLOB_RECURSE SRC "src/*.cpp")
|
||||
|
||||
include(${SDRPP_MODULE_CMAKE})
|
||||
|
||||
target_include_directories(vor_receiver PRIVATE "src/")
|
128
decoder_modules/vor_receiver/src/main.cpp
Normal file
128
decoder_modules/vor_receiver/src/main.cpp
Normal file
@ -0,0 +1,128 @@
|
||||
#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/buffer/reshaper.h>
|
||||
#include <dsp/sink/handler_sink.h>
|
||||
#include <gui/widgets/constellation_diagram.h>
|
||||
#include "vor_decoder.h"
|
||||
#include <fstream>
|
||||
|
||||
#define CONCAT(a, b) ((std::string(a) + b).c_str())
|
||||
|
||||
SDRPP_MOD_INFO{
|
||||
/* Name: */ "vor_receiver",
|
||||
/* Description: */ "VOR Receiver for SDR++",
|
||||
/* Author: */ "Ryzerth",
|
||||
/* Version: */ 0, 1, 0,
|
||||
/* Max instances */ -1
|
||||
};
|
||||
|
||||
ConfigManager config;
|
||||
|
||||
#define INPUT_SAMPLE_RATE VOR_IN_SR
|
||||
|
||||
class VORReceiverModule : public ModuleManager::Instance {
|
||||
public:
|
||||
VORReceiverModule(std::string name) {
|
||||
this->name = name;
|
||||
|
||||
// Load config
|
||||
config.acquire();
|
||||
// TODO: Load config
|
||||
config.release();
|
||||
|
||||
vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, 0, INPUT_SAMPLE_RATE, INPUT_SAMPLE_RATE, INPUT_SAMPLE_RATE, INPUT_SAMPLE_RATE, true);
|
||||
decoder = new vor::Decoder(vfo->output, 1);
|
||||
decoder->onBearing.bind(&VORReceiverModule::onBearing, this);
|
||||
|
||||
decoder->start();
|
||||
|
||||
gui::menu.registerEntry(name, menuHandler, this, this);
|
||||
}
|
||||
|
||||
~VORReceiverModule() {
|
||||
decoder->stop();
|
||||
sigpath::vfoManager.deleteVFO(vfo);
|
||||
gui::menu.removeEntry(name);
|
||||
delete decoder;
|
||||
}
|
||||
|
||||
void postInit() {}
|
||||
|
||||
void enable() {
|
||||
double bw = gui::waterfall.getBandwidth();
|
||||
vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, 0, INPUT_SAMPLE_RATE, INPUT_SAMPLE_RATE, INPUT_SAMPLE_RATE, INPUT_SAMPLE_RATE, true);
|
||||
|
||||
decoder->setInput(vfo->output);
|
||||
|
||||
decoder->start();
|
||||
|
||||
enabled = true;
|
||||
}
|
||||
|
||||
void disable() {
|
||||
decoder->stop();
|
||||
|
||||
sigpath::vfoManager.deleteVFO(vfo);
|
||||
enabled = false;
|
||||
}
|
||||
|
||||
bool isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
private:
|
||||
static void menuHandler(void* ctx) {
|
||||
VORReceiverModule* _this = (VORReceiverModule*)ctx;
|
||||
|
||||
float menuWidth = ImGui::GetContentRegionAvail().x;
|
||||
|
||||
if (!_this->enabled) { style::beginDisabled(); }
|
||||
|
||||
ImGui::Text("Bearing: %f°", _this->bearing);
|
||||
ImGui::Text("Quality: %0.1f%%", _this->quality);
|
||||
|
||||
if (!_this->enabled) { style::endDisabled(); }
|
||||
}
|
||||
|
||||
void onBearing(float nbearing, float nquality) {
|
||||
bearing = (180.0f * nbearing / FL_M_PI);
|
||||
quality = nquality * 100.0f;
|
||||
}
|
||||
|
||||
std::string name;
|
||||
bool enabled = true;
|
||||
|
||||
// DSP Chain
|
||||
VFOManager::VFO* vfo;
|
||||
vor::Decoder* decoder;
|
||||
|
||||
float bearing = 0.0f, quality = 0.0f;
|
||||
};
|
||||
|
||||
MOD_EXPORT void _INIT_() {
|
||||
// Create default recording directory
|
||||
std::string root = (std::string)core::args["root"];
|
||||
json def = json({});
|
||||
config.setPath(root + "/vor_receiver_config.json");
|
||||
config.load(def);
|
||||
config.enableAutoSave();
|
||||
}
|
||||
|
||||
MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) {
|
||||
return new VORReceiverModule(name);
|
||||
}
|
||||
|
||||
MOD_EXPORT void _DELETE_INSTANCE_(void* instance) {
|
||||
delete (VORReceiverModule*)instance;
|
||||
}
|
||||
|
||||
MOD_EXPORT void _END_() {
|
||||
config.disableAutoSave();
|
||||
config.save();
|
||||
}
|
50
decoder_modules/vor_receiver/src/vor_decoder.cpp
Normal file
50
decoder_modules/vor_receiver/src/vor_decoder.cpp
Normal file
@ -0,0 +1,50 @@
|
||||
#include "vor_decoder.h"
|
||||
|
||||
#define STDDEV_NORM_FACTOR 1.813799364234218f // 2.0f * FL_M_PI / sqrt(12)
|
||||
|
||||
namespace vor {
|
||||
Decoder::Decoder(dsp::stream<dsp::complex_t>* in, double integrationTime) {
|
||||
rx.init(in);
|
||||
reshape.init(&rx.out, round(1000.0 * integrationTime), 0);
|
||||
symSink.init(&reshape.out, dataHandler, this);
|
||||
}
|
||||
|
||||
Decoder::~Decoder() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
void Decoder::setInput(dsp::stream<dsp::complex_t>* in) {
|
||||
rx.setInput(in);
|
||||
}
|
||||
|
||||
void Decoder::start() {
|
||||
rx.start();
|
||||
reshape.start();
|
||||
symSink.start();
|
||||
}
|
||||
|
||||
void Decoder::stop() {
|
||||
rx.stop();
|
||||
reshape.stop();
|
||||
symSink.stop();
|
||||
}
|
||||
|
||||
void Decoder::dataHandler(float* data, int count, void* ctx) {
|
||||
// Get the instance from context
|
||||
Decoder* _this = (Decoder*)ctx;
|
||||
|
||||
// Compute the mean and standard deviation of the
|
||||
float mean, stddev;
|
||||
volk_32f_stddev_and_mean_32f_x2(&stddev, &mean, data, count);
|
||||
|
||||
// Compute the signal quality
|
||||
float quality = std::max<float>(1.0f - (stddev / STDDEV_NORM_FACTOR), 0.0f);
|
||||
|
||||
// Convert the phase difference to a compass heading
|
||||
mean = -mean;
|
||||
if (mean < 0) { mean = 2.0f*FL_M_PI + mean; }
|
||||
|
||||
// Call the handler
|
||||
_this->onBearing(mean, quality);
|
||||
}
|
||||
}
|
49
decoder_modules/vor_receiver/src/vor_decoder.h
Normal file
49
decoder_modules/vor_receiver/src/vor_decoder.h
Normal file
@ -0,0 +1,49 @@
|
||||
#include "vor_receiver.h"
|
||||
#include <dsp/buffer/reshaper.h>
|
||||
#include <dsp/sink/handler_sink.h>
|
||||
#include <utils/new_event.h>
|
||||
|
||||
namespace vor {
|
||||
// Note: hard coded to 22KHz samplerate
|
||||
class Decoder {
|
||||
public:
|
||||
/**
|
||||
* Create an instance of a VOR decoder.
|
||||
* @param in Input IQ stream at 22 KHz sampling rate.
|
||||
* @param integrationTime Integration time of the bearing data in seconds.
|
||||
*/
|
||||
Decoder(dsp::stream<dsp::complex_t>* in, double integrationTime);
|
||||
|
||||
// Destructor
|
||||
~Decoder();
|
||||
|
||||
/**
|
||||
* Set the input stream.
|
||||
* @param in Input IQ stream at 22 KHz sampling rate.
|
||||
*/
|
||||
void setInput(dsp::stream<dsp::complex_t>* in);
|
||||
|
||||
/**
|
||||
* Start the decoder.
|
||||
*/
|
||||
void start();
|
||||
|
||||
/**
|
||||
* Stop the decoder.
|
||||
*/
|
||||
void stop();
|
||||
|
||||
/**
|
||||
* handler(bearing, signalQuality);
|
||||
*/
|
||||
NewEvent<float, float> onBearing;
|
||||
|
||||
private:
|
||||
static void dataHandler(float* data, int count, void* ctx);
|
||||
|
||||
// DSP
|
||||
Receiver rx;
|
||||
dsp::buffer::Reshaper<float> reshape;
|
||||
dsp::sink::Handler<float> symSink;
|
||||
};
|
||||
}
|
2019
decoder_modules/vor_receiver/src/vor_fm_filter.h
Normal file
2019
decoder_modules/vor_receiver/src/vor_fm_filter.h
Normal file
File diff suppressed because it is too large
Load Diff
106
decoder_modules/vor_receiver/src/vor_receiver.h
Normal file
106
decoder_modules/vor_receiver/src/vor_receiver.h
Normal file
@ -0,0 +1,106 @@
|
||||
#include <dsp/sink.h>
|
||||
#include <dsp/types.h>
|
||||
#include <dsp/demod/am.h>
|
||||
#include <dsp/demod/quadrature.h>
|
||||
#include <dsp/convert/real_to_complex.h>
|
||||
#include <dsp/channel/frequency_xlator.h>
|
||||
#include <dsp/filter/fir.h>
|
||||
#include <dsp/math/delay.h>
|
||||
#include <dsp/math/conjugate.h>
|
||||
#include <dsp/channel/rx_vfo.h>
|
||||
#include "vor_fm_filter.h"
|
||||
#include <utils/wav.h>
|
||||
|
||||
#define VOR_IN_SR 25e3
|
||||
|
||||
namespace vor {
|
||||
class Receiver : public dsp::Processor<dsp::complex_t, float> {
|
||||
using base_type = dsp::Processor<dsp::complex_t, float>;
|
||||
public:
|
||||
Receiver() {}
|
||||
|
||||
Receiver(dsp::stream<dsp::complex_t>* in) { init(in); }
|
||||
|
||||
~Receiver() {
|
||||
if (!base_type::_block_init) { return; }
|
||||
base_type::stop();
|
||||
dsp::taps::free(fmfTaps);
|
||||
}
|
||||
|
||||
void init(dsp::stream<dsp::complex_t>* in) {
|
||||
amd.init(NULL, dsp::demod::AM<float>::CARRIER, VOR_IN_SR, 50.0f / VOR_IN_SR, 5.0f / VOR_IN_SR, 100.0f / VOR_IN_SR, VOR_IN_SR);
|
||||
amr2c.init(NULL);
|
||||
fmr2c.init(NULL);
|
||||
fmx.init(NULL, -9960, VOR_IN_SR);
|
||||
fmfTaps = dsp::taps::fromArray(FM_TAPS_COUNT, fm_taps);
|
||||
fmf.init(NULL, fmfTaps);
|
||||
fmd.init(NULL, 600, VOR_IN_SR);
|
||||
amde.init(NULL, FM_TAPS_COUNT / 2);
|
||||
amv.init(NULL, VOR_IN_SR, 1000, 30, 30);
|
||||
fmv.init(NULL, VOR_IN_SR, 1000, 30, 30);
|
||||
|
||||
base_type::init(in);
|
||||
}
|
||||
|
||||
int process(dsp::complex_t* in, float* out, int count) {
|
||||
// Demodulate the AM outer modulation
|
||||
volk_32fc_magnitude_32f(amd.out.writeBuf, (lv_32fc_t*)in, count);
|
||||
amr2c.process(count, amd.out.writeBuf, amr2c.out.writeBuf);
|
||||
|
||||
// Isolate the FM subcarrier
|
||||
fmx.process(count, amr2c.out.writeBuf, fmx.out.writeBuf);
|
||||
fmf.process(count, fmx.out.writeBuf, fmx.out.writeBuf);
|
||||
|
||||
// Demodulate the FM subcarrier
|
||||
fmd.process(count, fmx.out.writeBuf, fmd.out.writeBuf);
|
||||
fmr2c.process(count, fmd.out.writeBuf, fmr2c.out.writeBuf);
|
||||
|
||||
// Delay the AM signal by the same amount as the FM one
|
||||
amde.process(count, amr2c.out.writeBuf, amr2c.out.writeBuf);
|
||||
|
||||
// Isolate the 30Hz component on both the AM and FM channels
|
||||
int rcount = amv.process(count, amr2c.out.writeBuf, amv.out.writeBuf);
|
||||
fmv.process(count, fmr2c.out.writeBuf, fmv.out.writeBuf);
|
||||
|
||||
// If no data was returned, we're done for this round
|
||||
if (!rcount) { return 0; }
|
||||
|
||||
// Conjugate FM reference
|
||||
volk_32fc_conjugate_32fc((lv_32fc_t*)fmv.out.writeBuf, (lv_32fc_t*)fmv.out.writeBuf, rcount);
|
||||
|
||||
// Multiply both together
|
||||
volk_32fc_x2_multiply_32fc((lv_32fc_t*)amv.out.writeBuf, (lv_32fc_t*)amv.out.writeBuf, (lv_32fc_t*)fmv.out.writeBuf, rcount);
|
||||
|
||||
// Compute angle
|
||||
volk_32fc_s32f_atan2_32f(out, (lv_32fc_t*)amv.out.writeBuf, 1.0f, rcount);
|
||||
|
||||
return rcount;
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count = base_type::_in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
int outCount = process(base_type::_in->readBuf, base_type::out.writeBuf, count);
|
||||
|
||||
// Swap if some data was generated
|
||||
base_type::_in->flush();
|
||||
if (outCount) {
|
||||
if (!base_type::out.swap(outCount)) { return -1; }
|
||||
}
|
||||
return outCount;
|
||||
}
|
||||
|
||||
private:
|
||||
dsp::demod::AM<float> amd;
|
||||
dsp::convert::RealToComplex amr2c;
|
||||
dsp::convert::RealToComplex fmr2c;
|
||||
dsp::channel::FrequencyXlator fmx;
|
||||
dsp::tap<float> fmfTaps;
|
||||
dsp::filter::FIR<dsp::complex_t, float> fmf;
|
||||
dsp::demod::Quadrature fmd;
|
||||
dsp::math::Delay<dsp::complex_t> amde;
|
||||
dsp::channel::RxVFO amv;
|
||||
dsp::channel::RxVFO fmv;
|
||||
};
|
||||
}
|
@ -367,6 +367,7 @@ Modules in beta are still included in releases for the most part but not enabled
|
||||
| meteor_demodulator | Working | - | OPT_BUILD_METEOR_DEMODULATOR | ✅ | ✅ | ⛔ |
|
||||
| pager_decoder | Unfinished | - | OPT_BUILD_PAGER_DECODER | ⛔ | ⛔ | ⛔ |
|
||||
| radio | Working | - | OPT_BUILD_RADIO | ✅ | ✅ | ✅ |
|
||||
| radio | Unfinished | - | OPT_BUILD_VOR_RECEIVER | ⛔ | ⛔ | ⛔ |
|
||||
| weather_sat_decoder | Unfinished | - | OPT_BUILD_WEATHER_SAT_DECODER | ⛔ | ⛔ | ⛔ |
|
||||
|
||||
## Misc
|
||||
|
Loading…
x
Reference in New Issue
Block a user