mirror of
https://github.com/AlexandreRouma/SDRPlusPlus.git
synced 2025-01-31 03:44: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_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_RADIO "Main audio modulation decoder (AM, FM, SSB, etc...)" ON)
|
||||||
option(OPT_BUILD_RYFI_DECODER "RyFi data link decoder" OFF)
|
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)
|
option(OPT_BUILD_WEATHER_SAT_DECODER "Build the HRPT decoder module (no dependencies required)" OFF)
|
||||||
|
|
||||||
# Misc
|
# Misc
|
||||||
@ -289,6 +290,10 @@ if (OPT_BUILD_RYFI_DECODER)
|
|||||||
add_subdirectory("decoder_modules/ryfi_decoder")
|
add_subdirectory("decoder_modules/ryfi_decoder")
|
||||||
endif (OPT_BUILD_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)
|
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)
|
||||||
|
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 | ✅ | ✅ | ⛔ |
|
| meteor_demodulator | Working | - | OPT_BUILD_METEOR_DEMODULATOR | ✅ | ✅ | ⛔ |
|
||||||
| pager_decoder | Unfinished | - | OPT_BUILD_PAGER_DECODER | ⛔ | ⛔ | ⛔ |
|
| pager_decoder | Unfinished | - | OPT_BUILD_PAGER_DECODER | ⛔ | ⛔ | ⛔ |
|
||||||
| radio | Working | - | OPT_BUILD_RADIO | ✅ | ✅ | ✅ |
|
| radio | Working | - | OPT_BUILD_RADIO | ✅ | ✅ | ✅ |
|
||||||
|
| radio | Unfinished | - | OPT_BUILD_VOR_RECEIVER | ⛔ | ⛔ | ⛔ |
|
||||||
| weather_sat_decoder | Unfinished | - | OPT_BUILD_WEATHER_SAT_DECODER | ⛔ | ⛔ | ⛔ |
|
| weather_sat_decoder | Unfinished | - | OPT_BUILD_WEATHER_SAT_DECODER | ⛔ | ⛔ | ⛔ |
|
||||||
|
|
||||||
## Misc
|
## Misc
|
||||||
|
Loading…
x
Reference in New Issue
Block a user